refactor: unify agent history and harden runtime diagnostics
Preserve user guidance through history compression, stabilize Flash index targets, and retire withdrawn checks without applying stale verdicts. Improve runtime diagnostics, tool responses, and history rendering. Simplify documentation and comments. Validation: 2076 tests passed in the full suite; 381 affected tests and 26 frontend tests passed after cleanup. Ruff and the quality ratchet passed.
This commit is contained in:
1 parent
e983180b5b
commit
5d92939d91
127 files changed
+13738
-1988
No files matched your search
@@ -48,7 +48,7 @@ def test_build_plan_and_history_separated_layout():
|
||||
assert "--- Execution History ---" in output
|
||||
assert "- **Step 1 (Most Recent Step, Start: 2.5s)**" in output
|
||||
assert " - Thought 1" in output
|
||||
assert " * [Planned Action]: Launched app 'None'" in output
|
||||
assert " * [Action]: Launched app 'None'" in output
|
||||
|
||||
|
||||
def test_build_plan_and_history_granularities():
|
||||
@@ -298,7 +298,7 @@ def test_build_plan_and_history_with_tool_calls():
|
||||
]
|
||||
output = build_plan_and_history(plan, steps, "default", last_n_detailed=1)
|
||||
|
||||
assert "* [Operator Decision Loop]:" in output
|
||||
assert "* [Reasoning & tool calls]:" in output
|
||||
assert '`read_note({"key": "other_note"})` -> - [ ] Milestone' in output
|
||||
|
||||
|
||||
@@ -339,7 +339,7 @@ def test_build_plan_and_history_with_interleaved_events():
|
||||
assert ' - [Tool Call]: `read_note({"file": "notes.txt"})` -> Milestone plan' in output
|
||||
|
||||
# It should use the new section header
|
||||
assert "* [Operator Decision Loop]:" in output
|
||||
assert "* [Reasoning & tool calls]:" in output
|
||||
|
||||
|
||||
def test_build_plan_and_history_concatenates_thoughts():
|
||||
@@ -396,14 +396,14 @@ def test_build_plan_and_history_interleaved_decision_loop():
|
||||
assert plan in output
|
||||
assert "--- Execution History ---" in output
|
||||
assert "- **Step 1 (Most Recent Step, Start: 2.5s)**" in output
|
||||
assert "* [Operator Decision Loop]:" in output
|
||||
assert "* [Reasoning & tool calls]:" in output
|
||||
assert " - I should find search icon." in output
|
||||
assert (
|
||||
'- [Tool Call]: `ask_explorer({"query": "search icon"})` -> {"center":'
|
||||
" [500, 600]}" in output
|
||||
)
|
||||
assert " - Clicking coordinates [500, 600] now." in output
|
||||
assert "* [Planned Action]: Tapped 'Search' at None" in output
|
||||
assert "* [Action]: Tapped 'Search' at None" in output
|
||||
assert "* [Validator Execution Result]" not in output
|
||||
|
||||
|
||||
@@ -460,12 +460,9 @@ def test_build_plan_and_history_safety_net_and_incident():
|
||||
output = build_plan_and_history(plan, steps, "default", last_n_detailed=1)
|
||||
|
||||
assert "- **Step 1 (Most Recent Step, Start: 5.0s)**" in output
|
||||
assert "* [Operator Decision Loop]:" in output
|
||||
assert "* [Reasoning & tool calls]:" in output
|
||||
assert " - Let's click search." in output
|
||||
assert (
|
||||
"* [Planned Action]: Tapped 'Search' at None (Intercepted by"
|
||||
" Pre-Execution Safety Net)" in output
|
||||
)
|
||||
assert "* [Action]: Tapped 'Search' at None (Intercepted by Pre-Execution Safety Net)" in output
|
||||
assert "* [Pre-Execution Safety Net]:" in output
|
||||
assert (
|
||||
"- [Safety Net Check]: (Intercepted by Pre-Execution Safety Net: Target button is"
|
||||
@@ -475,7 +472,7 @@ def test_build_plan_and_history_safety_net_and_incident():
|
||||
assert "Failure Analyzer" not in output
|
||||
assert (
|
||||
"* [Result]: Error: Intercepted by Pre-Execution Safety Net"
|
||||
" (target_disappeared, consecutive failure #2) on action `Tapped 'Search' at"
|
||||
" (target_disappeared, consecutive failure #2) on action `tap 'Search' at"
|
||||
" None`: Target button is not visible" in output
|
||||
)
|
||||
|
||||
@@ -528,16 +525,14 @@ def test_build_plan_and_history_fast_action_burst():
|
||||
]
|
||||
# Detailed view: every member with its outcome.
|
||||
output = build_plan_and_history(plan, steps, "default", last_n_detailed=1)
|
||||
assert (
|
||||
"* [Planned Fast-Action Burst]: 3 actions fired back to back without the"
|
||||
" safety net" in output
|
||||
)
|
||||
assert "* [Fast-Action Burst]: 3 actions fired back to back without the safety net" in output
|
||||
assert " 1. Tapped element at [500, 900] (dispatched)" in output
|
||||
assert " 2. Tapped 'Skip' at [880, 120] (FAILED: Error: tap rejected)" in output
|
||||
assert " 3. Pressed key 'BACK' (skipped)" in output
|
||||
# The failed member is quoted in intent form: it did not happen.
|
||||
assert (
|
||||
"* [Result]: Error: Execution failed (general, consecutive failure #1) on burst"
|
||||
" action 2/3 `Tapped 'Skip' at [880, 120]`: Error: tap rejected; the remaining"
|
||||
" action 2/3 `tap 'Skip' at [880, 120]`: Error: tap rejected; the remaining"
|
||||
" burst actions were not executed" in output
|
||||
)
|
||||
|
||||
@@ -662,7 +657,7 @@ def test_render_step_replay_shows_summary_thoughts_and_full_tool_results():
|
||||
assert "`ask_explorer(" in out
|
||||
assert long_result in out
|
||||
# Action tools are the planned action, not a tool-call line.
|
||||
assert "[Planned Action]: Tapped 'Save' at [0.5, 0.5]" in out
|
||||
assert "[Action]: Tapped 'Save' at [0.5, 0.5]" in out
|
||||
assert "`click(" not in out
|
||||
|
||||
|
||||
@@ -715,6 +710,174 @@ def test_render_step_replay_shows_described_images_in_tool_results():
|
||||
assert "[screenshot: pre-action of Step 1]" in out
|
||||
|
||||
|
||||
def test_render_step_replay_header_uses_the_session_clock():
|
||||
"""The replay header carries the same ``T+mm:ss`` offset the prompts and
|
||||
``search_history`` use; the legacy ``Start:`` text only remains when no
|
||||
session clock / step timestamp exists."""
|
||||
from artemis.utils.task_tree import render_step_replay, replay_time_label
|
||||
|
||||
step = _replay_step("ok", timestamp=1000.0 + 61.0)
|
||||
out = render_step_replay(step, session_start=1000.0)
|
||||
assert "- **Step 4 (T+01:01)**" in out
|
||||
assert "Start:" not in out
|
||||
|
||||
# No session start (or a non-numeric one, e.g. a mocked reader): fallback.
|
||||
assert "- **Step 4 (Start: 12.0s)**" in render_step_replay(step)
|
||||
assert "- **Step 4 (Start: 12.0s)**" in render_step_replay(step, session_start=object())
|
||||
# A step without a timestamp cannot be placed on the session clock.
|
||||
assert replay_time_label(_replay_step("ok"), 1000.0) == "Start: 12.0s"
|
||||
|
||||
|
||||
def test_render_step_replay_labels_are_role_neutral():
|
||||
"""Flash has no Operator, so the block labels name what they hold."""
|
||||
from artemis.utils.task_tree import render_step_replay
|
||||
|
||||
out = render_step_replay(_replay_step("ok"))
|
||||
assert " * [Reasoning & tool calls]:" in out
|
||||
assert " * [Action]: Tapped 'Save' at [0.5, 0.5]" in out
|
||||
assert "Operator Decision Loop" not in out
|
||||
assert "Planned Action" not in out
|
||||
|
||||
|
||||
def test_render_step_replay_prints_a_failure_once():
|
||||
"""A failed step shows its error on the [Result] line only, not appended
|
||||
to the action line as well."""
|
||||
from artemis.utils.task_tree import render_step_replay
|
||||
|
||||
out = render_step_replay(
|
||||
_replay_step(
|
||||
"ok",
|
||||
last_execution_result={
|
||||
"status": "failed",
|
||||
"execution": [{"action": "click", "attempts": ["Error: tap rejected"]}],
|
||||
},
|
||||
)
|
||||
)
|
||||
assert " * [Action]: Tapped 'Save' at [0.5, 0.5]\n" in out
|
||||
assert " * [Result]: Error: tap rejected" in out
|
||||
assert out.count("tap rejected") == 1
|
||||
assert "Execution failed:" not in out
|
||||
|
||||
|
||||
def test_render_step_replay_indents_multiline_reasoning():
|
||||
from artemis.utils.task_tree import render_step_replay
|
||||
|
||||
step = _replay_step(
|
||||
"ok",
|
||||
interleaved_events=[
|
||||
{"type": "thought", "content": "- Save findings first.\nTap Wi-Fi.\nThen verify."}
|
||||
],
|
||||
)
|
||||
out = render_step_replay(step)
|
||||
assert "\n - - Save findings first.\n Tap Wi-Fi.\n Then verify." in out
|
||||
|
||||
|
||||
def test_format_action_clean_directional_swipe_shows_direction_first():
|
||||
"""``swipe(direction="up")`` replays as the direction the agent issued; the
|
||||
recorded path is a parenthesised detail (Flash records both)."""
|
||||
from artemis.utils.task_tree import format_action_clean
|
||||
|
||||
flash_record = {
|
||||
"action": "swipe",
|
||||
"coordinates": [600, 700, 600, 300],
|
||||
"coordinate_space": "normalized",
|
||||
"normalized_start_coordinates": [600, 700],
|
||||
"normalized_end_coordinates": [600, 300],
|
||||
"args": {"direction": "up", "duration": 800},
|
||||
}
|
||||
assert (
|
||||
format_action_clean(flash_record) == "Swiped up (from [600, 700] to [600, 300]) over 800ms"
|
||||
)
|
||||
assert format_action_clean({"action": "swipe", "direction": "down"}) == "Swiped down"
|
||||
assert (
|
||||
format_action_clean(
|
||||
{
|
||||
"action": "swipe",
|
||||
"gesture": "left",
|
||||
"start_coordinates": [750, 500],
|
||||
"end_coordinates": [250, 500],
|
||||
}
|
||||
)
|
||||
== "Swiped left (from [750, 500] to [250, 500])"
|
||||
)
|
||||
# Without a direction the path is the headline, as before.
|
||||
assert (
|
||||
format_action_clean({"action": "swipe", "coordinates": [600, 700, 600, 300]})
|
||||
== "Swiped from [600, 700] to [600, 300]"
|
||||
)
|
||||
|
||||
|
||||
def test_action_intent_phrase_and_incident_line_use_intent_form():
|
||||
"""Failure contexts describe what the agent tried to do, not an outcome."""
|
||||
from artemis.utils.task_tree import (
|
||||
action_intent_phrase,
|
||||
format_action_intent,
|
||||
format_incident_clean,
|
||||
)
|
||||
|
||||
assert format_action_intent({"action": "launch_app", "app_name": "NonexistentApp"}) == (
|
||||
"launch app 'NonexistentApp'"
|
||||
)
|
||||
assert (
|
||||
format_action_intent(
|
||||
{"action": "click", "coordinates": [500, 520], "target_description": "Wi-Fi row"}
|
||||
)
|
||||
== "tap 'Wi-Fi row' (self-described) at [500, 520]"
|
||||
)
|
||||
assert format_action_intent({"action": "swipe", "direction": "up"}) == "swipe up"
|
||||
assert (
|
||||
format_action_intent(
|
||||
{
|
||||
"action": "input_text",
|
||||
"text": "hello",
|
||||
"target_text": "Search",
|
||||
"coordinates": [1, 2],
|
||||
}
|
||||
)
|
||||
== "type 'hello' into 'Search' at [1, 2]"
|
||||
)
|
||||
assert format_action_intent({"action": "press_key", "keycode": "BACK"}) == "press key 'BACK'"
|
||||
assert format_action_intent({"action": "wait_for_delay", "delay_ms": 200}) == "wait 200ms"
|
||||
assert format_action_intent({"action": "long_press", "coordinates": [1, 2]}) == (
|
||||
"long press element at [1, 2]"
|
||||
)
|
||||
assert format_action_intent({"action": "click", "coordinates": [1, 2], "times": 2}) == (
|
||||
"double tap element at [1, 2]"
|
||||
)
|
||||
# Unknown phrases pass through untouched.
|
||||
assert action_intent_phrase("Fast-action burst (2 actions, unvetted): x") == (
|
||||
"Fast-action burst (2 actions, unvetted): x"
|
||||
)
|
||||
assert action_intent_phrase("") == ""
|
||||
|
||||
line = format_incident_clean(
|
||||
{
|
||||
"kind": "exec_error",
|
||||
"category": "general",
|
||||
"consecutive_failures": 2,
|
||||
"action": {"action": "launch_app", "app_name": "NonexistentApp"},
|
||||
"action_description": "Launched app 'NonexistentApp'",
|
||||
"reason": "Error finding package for app: NonexistentApp",
|
||||
}
|
||||
)
|
||||
assert line == (
|
||||
"Error: Execution failed (general, consecutive failure #2) on action"
|
||||
" `launch app 'NonexistentApp'`: Error finding package for app: NonexistentApp"
|
||||
)
|
||||
intercepted = format_incident_clean(
|
||||
{
|
||||
"kind": "safety_net",
|
||||
"category": "target_disappeared",
|
||||
"action": {"action": "click", "target_text": "Skip", "coordinates": [880, 120]},
|
||||
"reason": "Target element 'Skip' was not found on the screen.",
|
||||
}
|
||||
)
|
||||
assert intercepted.startswith(
|
||||
"Error: Intercepted by Pre-Execution Safety Net (target_disappeared, consecutive"
|
||||
" failure #1) on action `tap 'Skip' at [880, 120]`:"
|
||||
)
|
||||
|
||||
|
||||
def test_format_action_clean_reads_flash_arguments_and_maps_manage_app():
|
||||
"""Read Flash action arguments from ``args`` when formatting app launches."""
|
||||
from artemis.utils.task_tree import format_action_clean
|
||||
|
||||
Reference in new issue
Block a user