diff --git a/backend/app/services/agent_runtime/node_executor.py b/backend/app/services/agent_runtime/node_executor.py index bb9d380e7..0414faa11 100644 --- a/backend/app/services/agent_runtime/node_executor.py +++ b/backend/app/services/agent_runtime/node_executor.py @@ -1051,24 +1051,16 @@ async def _tool( lifecycle.pop("step_tool_context", None) lifecycle.update( { - "status": "waiting_user", - "next_route": "wait", + "status": "failed", + "next_route": "terminal", "reason": repair_pause_reason, "pending_tool_calls": [], - "waiting_request": { - "waiting_type": "user", - "correlation_id": _runtime_message_id( - context, - f"tool-repair:{repair_pause_reason}:{repair_pause_tool}", - ), - "reason": repair_pause_reason, - "question": ( - f"Tool {repair_pause_tool or 'unknown'} reached its " - "repair safety limit. Provide corrected requirements " - "or arguments to continue." - ), - }, - "error": None, + "waiting_request": None, + "error": _error( + repair_pause_reason, + f"Tool {repair_pause_tool or 'unknown'} reached its " + "repair safety limit.", + ), } ) else: diff --git a/backend/tests/test_agent_runtime_node_executor.py b/backend/tests/test_agent_runtime_node_executor.py index f47c6ae9f..4d07cdb2c 100644 --- a/backend/tests/test_agent_runtime_node_executor.py +++ b/backend/tests/test_agent_runtime_node_executor.py @@ -784,7 +784,7 @@ async def no_sleep(_seconds: float) -> None: @pytest.mark.asyncio -async def test_tenth_same_tool_failure_pauses_before_next_model_call() -> None: +async def test_tenth_same_tool_failure_fails_run_before_next_model_call() -> None: run_id = uuid.uuid4() proposals = tuple( ModelStepResult( @@ -822,10 +822,17 @@ async def test_tenth_same_tool_failure_pauses_before_next_model_call() -> None: result = await _invoke(run_id, executor) lifecycle = result["lifecycle"] - assert lifecycle["status"] == "waiting_user" + assert lifecycle["status"] == "failed" + assert lifecycle["next_route"] == "terminal" assert lifecycle["reason"] == ( "tool_repair_same_fingerprint_limit_reached" ) + assert lifecycle["error"] == { + "code": "tool_repair_same_fingerprint_limit_reached", + "message": "Tool read_file reached its repair safety limit.", + } + assert lifecycle["pending_tool_calls"] == [] + assert lifecycle.get("waiting_request") is None assert lifecycle["model_step_count"] == 10 repair_episode = lifecycle["tool_repair_episodes"]["by_tool"]["read_file"] assert repair_episode["total_failures"] == 10 @@ -833,23 +840,6 @@ async def test_tenth_same_tool_failure_pauses_before_next_model_call() -> None: assert model.calls == 10 assert len(tools.calls) == 10 - resumed = await executor.execute( - "wait", - cast(RuntimeGraphState, result), - _context(run_id, executor, "command-user-correction"), - resume_value={ - "resume_type": "user_input", - "payload": {"content": "Use README.md as the path."}, - }, - ) - assert resumed["lifecycle"]["tool_repair_episodes"] == { - "version": 1, - "by_tool": {}, - } - assert resumed["lifecycle"]["tool_repair_reset"]["reason"] == ( - "explicit_user_correction" - ) - @pytest.mark.asyncio async def test_duplicate_tool_call_ids_fail_before_any_provider_execution() -> None: