feat: add agentic video analysis and clarify action results

Report device actions as dispatched and preserve full failure details. Route
Flash through McpActionExecutor and use ToolFailure across helper tools and
agent loops instead of inferring failure from response text.

Require model-provided descriptions for coordinate targets. Keep observed
element metadata separate from those descriptions in safety checks, visual
summaries, history, and frontend labels.

Add Gemini Interactions API video analysis with configurable processing mode,
chunk size, and timeouts. Centralize Google provider capabilities and token
usage normalization, and update google-genai to 2.22.0.

Preserve system instructions on follow-up interactions, replan pending chunks
when falling back to static processing, and reject unanswered native video
and audio segments instead of caching placeholder results. Keep helper-tool
failure status intact through history tools and committee calls.

Validation: 1,762 Python unit tests passed (3 skipped), 40 frontend formatter
tests passed, and the production UI build completed. Ruff lint, formatting,
the quality ratchet, and git diff checks passed.
This commit is contained in:
somew1nd committed 2026-09-08 17:05:54 -07:00
1 parent 611d1a0947
commit a45daf23f8
120 files changed
+6301 -1710

No files matched your search

+4 -4
View File
@@ -43,7 +43,7 @@ def _step(number: int, subgoal_hash: str, summary: str | None = None) -> dict:
"summary": summary or f"Step {number} summary text.",
"action_taken": {"action": "click", "target_text": f"button {number}"},
"operator_raw_thinking": f"thinking {number}",
"last_execution_result": {"status": "success"},
"last_execution_result": {"status": "dispatched"},
"extra_metadata": {"subgoal_hash": subgoal_hash},
}
@@ -182,9 +182,9 @@ RENDERED_CHUNK = (
" - Steps 1–3: opened the app and logged in\n"
"\n"
"③ Step action ledger\n"
"- Step 1 (T+00:10): click['button 1'] -> executed\n"
"- Step 2 (T+00:20): click['button 2'] -> executed\n"
"- Step 3 (T+00:30): click['button 3'] -> executed"
"- Step 1 (T+00:10): click['button 1'] -> dispatched\n"
"- Step 2 (T+00:20): click['button 2'] -> dispatched\n"
"- Step 3 (T+00:30): click['button 3'] -> dispatched"
)
+21 -21
View File
@@ -63,7 +63,7 @@ def _step(i: int, subgoal: str = "hash-a", **overrides) -> dict:
"timestamp": SESSION_START + 10.0 * i,
"summary": f"Visual transition of step {i}.",
"action_taken": {"action": "tap", "target_text": f"btn{i}", "coordinates": [i, i]},
"last_execution_result": {"status": "success"},
"last_execution_result": {"status": "dispatched"},
"operator_raw_thinking": f"thinking {i}",
"interleaved_events": [],
"extra_metadata": {"subgoal_hash": subgoal},
@@ -201,7 +201,7 @@ def _run_turns(ledger, chunker, start: int, upto: int, hash_for=None):
for i in range(start, upto + 1):
ledger.commit_staged(
step_key=f"s{i - 1}" if i > 1 else None,
validator_result={"status": "success"} if i > 1 else None,
validator_result={"status": "dispatched"} if i > 1 else None,
)
rendered = ledger.render([_observation(i)])
ledger.stage_turn(_turn(i))
@@ -226,7 +226,7 @@ def test_action_ledger_lines_carry_step_number_offset_action_and_result():
steps = [_step(1), _step(2, last_execution_result={"status": "failed", "error": "boom"})]
ledger_text = build_action_ledger(steps, SESSION_START)
lines = ledger_text.splitlines()
assert lines[0] == "- Step 1 (T+00:10): Tapped 'btn1' at [1, 1] -> executed"
assert lines[0] == "- Step 1 (T+00:10): Tapped 'btn1' at [1, 1] -> dispatched"
assert lines[1].startswith("- Step 2 (T+00:20): Tapped 'btn2' at [2, 2] -> Error: boom")
@@ -289,7 +289,7 @@ def test_action_ledger_minimal_width_keeps_step_and_time_addressability():
minimal = build_action_ledger(steps, SESSION_START, minimal=True)
for i in range(1, 4):
assert f"- Step {i} (T+00:{10 * i:02d}): Tapped 'btn{i}'" in minimal
assert "-> executed" not in minimal
assert "-> dispatched" not in minimal
# ---------------------------------------------------------------------------
@@ -396,7 +396,7 @@ def test_milestone_switch_closes_previous_segment_whole_and_gates_swap():
# Capsule ready → the next render swaps: frozen ①②③ block, originals out.
capsule.resolve("chunk:1-4", _capsule(1, 4))
ledger.commit_staged(step_key="s8", validator_result={"status": "success"})
ledger.commit_staged(step_key="s8", validator_result={"status": "dispatched"})
rendered_after = ledger.render([_observation(9)])
assert len(chunker.chunks) == 1
assert chunker.awaiting_chunks == ()
@@ -517,7 +517,7 @@ def test_frozen_region_stays_byte_stable_between_swap_events():
# Swap chunk 1 (Steps 1–4) once its capsule is ready.
capsule.resolve("chunk:1-4", _capsule(1, 4))
ledger.commit_staged(step_key="s8", validator_result={"status": "success"})
ledger.commit_staged(step_key="s8", validator_result={"status": "dispatched"})
ledger.render([_observation(9)])
assert len(chunker.chunks) == 1
frozen_before = _frozen_text(ledger)
@@ -537,7 +537,7 @@ def test_frozen_region_stays_byte_stable_between_swap_events():
# Its capsule readies: the next render swaps and re-renders the region.
capsule.resolve(pending.capsule_key, _capsule(5, 8))
ledger.commit_staged(step_key="s12", validator_result={"status": "success"})
ledger.commit_staged(step_key="s12", validator_result={"status": "dispatched"})
ledger.render([_observation(13)])
assert len(chunker.chunks) == 2
frozen_after = _frozen_text(ledger)
@@ -572,7 +572,7 @@ def test_chunk_block_renders_three_bands_in_order():
assert "Verified: fact-1" in block
# ② lines carry step references; ③ is the untouched mechanical ledger.
assert "- Steps 1–4:" in block
assert "- Step 1 (T+00:10): Tapped 'btn1' at [1, 1] -> executed" in block
assert "- Step 1 (T+00:10): Tapped 'btn1' at [1, 1] -> dispatched" in block
assert " ago" not in block
@@ -843,7 +843,7 @@ def test_hard_threshold_renders_l3_snapshot_with_minimal_step_index():
for chunk in chunker.chunks:
for n in range(chunk.start_step_number, chunk.end_step_number + 1):
assert f"- Step {n} (T+" in frozen
assert "-> executed" not in frozen.split("--- Step index")[1]
assert "-> dispatched" not in frozen.split("--- Step index")[1]
def test_step_addressability_survives_every_compression_level():
@@ -1033,7 +1033,7 @@ async def test_flush_harvests_late_capsule_and_persists_ready_version():
assert ledger.frozen_blocks == ()
# A later render (e.g. after a process resume) swaps normally.
ledger.commit_staged(step_key="s8", validator_result={"status": "success"})
ledger.commit_staged(step_key="s8", validator_result={"status": "dispatched"})
ledger.render([_observation(9)])
assert chunker.chunks and chunker.chunks[0].status == "ready"
@@ -1069,7 +1069,7 @@ def test_failed_capsule_redispatches_and_retains_original():
# Bounded retries exhausted → explicit failed state, chunk stays pending.
capsule._failed.add(key)
meter["value"] = 75_000 # pressure render = a re-dispatch occasion
ledger.commit_staged(step_key="s8", validator_result={"status": "success"})
ledger.commit_staged(step_key="s8", validator_result={"status": "dispatched"})
ledger.render([_observation(9)])
assert capsule.submitted.count(key) == 2 # re-dispatched
@@ -1088,7 +1088,7 @@ def test_hard_threshold_force_swaps_pending_chunks_into_l3():
assert ledger.frozen_blocks == ()
meter["value"] = 95_000 # ≥ 0.9 * 100k → hard
ledger.commit_staged(step_key="s8", validator_result={"status": "success"})
ledger.commit_staged(step_key="s8", validator_result={"status": "dispatched"})
ledger.render([_observation(9)])
assert chunker.awaiting_chunks == ()
@@ -1128,7 +1128,7 @@ def test_compression_announces_one_tool_line_from_dispatch_to_swap():
assert "context_tokens" not in args
capsule.resolve("chunk:1-4", _capsule(1, 4))
ledger.commit_staged(step_key="s8", validator_result={"status": "success"})
ledger.commit_staged(step_key="s8", validator_result={"status": "dispatched"})
ledger.render([_observation(9)])
done = engine.traces[-1]
@@ -1155,7 +1155,7 @@ def test_forced_swap_announces_snapshot_without_a_ratio():
assert any(t["status"] == "running" for t in engine.traces)
meter["value"] = 95_000
ledger.commit_staged(step_key="s8", validator_result={"status": "success"})
ledger.commit_staged(step_key="s8", validator_result={"status": "dispatched"})
ledger.render([_observation(9)]) # pressure closes the open tail too, then force-swaps all
running_ids = {t["trace_id"] for t in engine.traces if t["status"] == "running"}
@@ -1313,7 +1313,7 @@ def test_capsule_payload_carries_each_turn_transcript_as_the_operator_saw_it():
for i in range(1, 7):
ledger.commit_staged(
step_key=f"s{i - 1}" if i > 1 else None,
validator_result={"status": "success"} if i > 1 else None,
validator_result={"status": "dispatched"} if i > 1 else None,
)
ledger.render([_observation(i)])
ledger.stage_turn(_rich_turn(i) if i == 2 else _turn(i))
@@ -1329,7 +1329,7 @@ def test_capsule_payload_carries_each_turn_transcript_as_the_operator_saw_it():
assert '[tool call] ask_explorer({"question": "price?"})' in transcript
assert "[tool result ask_explorer]\nPrice shown: ¥39" in transcript
assert '[tool call] click({"target": 2})' in transcript
assert "--- Action Execution Result" in transcript and "Status: success" in transcript
assert "--- Action Execution Result" in transcript and "Status: dispatched" in transcript
assert transcript.index("Price shown") < transcript.index("click(")
# What the scrub edge already removed from the operator's view.
assert "Buy button" not in transcript and "IMG_2" not in transcript
@@ -1364,7 +1364,7 @@ def test_chunk_without_ledger_falls_back_to_per_step_projection():
def test_turn_transcript_is_capped_in_place_inside_the_capsule_request():
lens = StepCapsuleLens(model_name="test", llm=object())
cap = StepCapsuleLens.MAX_TURN_TRANSCRIPT_CHARS
step = {"step_number": 4, "offset": "T+00:40", "action": "tap", "outcome": "executed"}
step = {"step_number": 4, "offset": "T+00:40", "action": "tap", "outcome": "dispatched"}
payload = {
"start_step": 4,
"end_step": 4,
@@ -1399,7 +1399,7 @@ def test_visual_transition_line_is_skipped_when_the_transcript_already_has_it():
"step_number": 4,
"offset": "T+00:40",
"action": "tap",
"outcome": "executed",
"outcome": "dispatched",
"visual_summary": "The cart badge changed from 0 to 1.",
}
resolved = f"[observation]\n{HISTORY_SUMMARY_PREFIX}The cart badge changed from 0 to 1."
@@ -1479,15 +1479,15 @@ async def test_ready_capsule_releases_its_turn_transcripts():
def test_multi_action_turn_renders_one_block_with_every_recorded_step():
lens = StepCapsuleLens(model_name="test", llm=object())
steps = [
{"step_number": n, "offset": f"T+00:{n}0", "action": f"tap btn{n}", "outcome": "executed"}
{"step_number": n, "offset": f"T+00:{n}0", "action": f"tap btn{n}", "outcome": "dispatched"}
for n in (7, 8)
]
payload = {"start_step": 7, "end_step": 8, "steps": steps}
payload["turns"] = [{"steps": steps, "transcript": "[operator]\nfired two taps"}]
text = lens.build_messages(payload)[1].content
assert "## Steps 7–8 (T+00:70 → T+00:80)" in text
assert "- Step 7 (T+00:70): tap btn7 -> executed" in text
assert "- Step 8 (T+00:80): tap btn8 -> executed" in text
assert "- Step 7 (T+00:70): tap btn7 -> dispatched" in text
assert "- Step 8 (T+00:80): tap btn8 -> dispatched" in text
assert "fired two taps" in text
+6 -6
View File
@@ -131,7 +131,7 @@ def test_validator_result_message_carries_session_offset():
ledger,
2,
prev_key="step-1",
prev_result={"status": "success", "execution": [{"action": "tap", "attempts": ["ok"]}]},
prev_result={"status": "dispatched", "execution": [{"action": "tap", "attempts": ["ok"]}]},
)
result_messages = [
@@ -142,7 +142,7 @@ def test_validator_result_message_carries_session_offset():
assert len(result_messages) == 1
text = result_messages[0].content[0]["text"]
assert OFFSET_RE.search(text), text
assert "Status: success" in text
assert "Status: dispatched" in text
assert "ago" not in text
@@ -229,7 +229,7 @@ def test_tool_call_response_pairs_are_never_split():
ledger,
i,
prev_key=f"step-{i - 1}" if i > 1 else None,
prev_result={"status": "success"} if i > 1 else None,
prev_result={"status": "dispatched"} if i > 1 else None,
tool_call_count=2,
)
@@ -279,7 +279,7 @@ def test_no_ago_wording_in_ledger_output():
ledger,
i,
prev_key=f"step-{i - 1}" if i > 1 else None,
prev_result={"status": "success"} if i > 1 else None,
prev_result={"status": "dispatched"} if i > 1 else None,
)
blob = " ".join(str(m.content) for m in ledger.active_messages)
assert " ago" not in blob
@@ -406,7 +406,7 @@ def test_ledger_turn_transcript_reflects_the_scrubbed_active_region():
]
ledger.stage_turn(turn(1))
ledger.commit_staged(step_key="s1", validator_result={"status": "success"})
ledger.commit_staged(step_key="s1", validator_result={"status": "dispatched"})
ledger.stage_turn(turn(2))
ledger.commit_staged(step_key="s2", validator_result={"status": "failed", "error": "boom"})
ledger.render([HumanMessage(content=[{"type": "text", "text": "tail"}])])
@@ -415,6 +415,6 @@ def test_ledger_turn_transcript_reflects_the_scrubbed_active_region():
text = ledger.turn_transcript(first)
assert "thought 1" in text and "[tool call] click({})" in text
assert "Button 1" not in text # depth-1 strip already applied
assert "--- Action Execution Result (T+00:00) ---\nStatus: success" in text
assert "--- Action Execution Result (T+00:00) ---\nStatus: dispatched" in text
assert "Status: failed" in ledger.turn_transcript(second)
assert "thought 2" not in text # a turn transcript never leaks into another turn