fix(agent): harden the todo predicate and import the TUI server once in the test
is_todo_tool_name returns False for non-string names (a malformed list/dict name used to raise TypeError where the old check returned False), and the kept regression test imports tui_gateway.server at module level so it no longer depends on another test importing it first. Docstrings updated. Co-authored-by: JoaoMarcos44 <joaomarcosdias444@gmail.com>
This commit is contained in:
@@ -1050,7 +1050,7 @@ class AIAgent(
|
||||
|
||||
def _hydrate_todo_store(self, history: List[Dict[str, Any]]) -> None:
|
||||
"""Replay the most recent todo tool response (the gateway builds a fresh AIAgent per message). Only
|
||||
results paired with an earlier assistant ``todo`` call count — a forged bare ``role: tool`` message
|
||||
results paired with an earlier assistant Todo-tool call count — a forged bare ``role: tool`` message
|
||||
must not seed the store (GHSA-5g4g-6jrg-mw3g)."""
|
||||
found = self._latest_todo_response(history)
|
||||
if found is not None:
|
||||
|
||||
@@ -24,6 +24,7 @@ from run_agent import AIAgent
|
||||
from agent.error_classifier import FailoverReason
|
||||
from agent.memory_manager import MemoryManager
|
||||
from agent.prompt_builder import DEFAULT_AGENT_IDENTITY
|
||||
from tui_gateway import server as tui_server
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -754,9 +755,7 @@ class TestHydrateTodoStore:
|
||||
|
||||
assert agent._todo_store.snapshot() == {"todos": todos, "revision": 3}
|
||||
# The TUI resume path (no AIAgent yet) must pair the same call via the same predicate.
|
||||
from tui_gateway import server
|
||||
|
||||
assert server._todo_state_from_history(history)["todos"] == todos
|
||||
assert tui_server._todo_state_from_history(history)["todos"] == todos
|
||||
|
||||
def test_no_todo_in_history(self, agent):
|
||||
history = [
|
||||
|
||||
@@ -282,7 +282,7 @@ TODO_TOOL_NAMES = frozenset((TODO_SCHEMA["name"], *TODO_LEGACY_ALIASES))
|
||||
|
||||
def is_todo_tool_name(name: Any) -> bool:
|
||||
"""True for the Todo tool's current name or a legacy alias (an already-unwrapped dispatch name)."""
|
||||
return name in TODO_TOOL_NAMES
|
||||
return isinstance(name, str) and name in TODO_TOOL_NAMES
|
||||
|
||||
|
||||
def is_todo_tool_call(tool_call: Any) -> bool:
|
||||
@@ -290,8 +290,8 @@ def is_todo_tool_call(tool_call: Any) -> bool:
|
||||
|
||||
Covers the current name, legacy aliases, and the ``tool_call`` bridge (``todo_list`` is deferred by
|
||||
default, and the transcript keeps the bridge name). The bridge is peeled from the recorded arguments
|
||||
only, never live tool-search config, and must wrap exactly one call. Lives here, not in
|
||||
agent.tool_executor, so TUI resume and run_agent never pull model_tools / the executor in to answer it.
|
||||
only, never live tool-search config, and must wrap exactly one call. Keep this module free of model_tools / agent.tool_executor
|
||||
imports: TUI resume and run_agent call this without loading either.
|
||||
"""
|
||||
from agent.message_sanitization import _tc_field
|
||||
|
||||
@@ -299,7 +299,8 @@ def is_todo_tool_call(tool_call: Any) -> bool:
|
||||
name, raw_args = _tc_field(fn, "name") or "", _tc_field(fn, "arguments")
|
||||
if is_todo_tool_name(name):
|
||||
return True
|
||||
# Cheap pre-check before the bridge modules load: no "todo" in the raw args means no todo inside.
|
||||
# Cheap heuristic before the bridge modules load: skip args without a literal "todo". Only a
|
||||
# unicode-escaped name slips past, which json.dumps never writes for ASCII.
|
||||
if isinstance(raw_args, str) and "todo" not in raw_args:
|
||||
return False
|
||||
from tools.tool_search_catalog import TOOL_CALL_NAME
|
||||
|
||||
Reference in New Issue
Block a user