"""A driver run on its own (L2), as any agent uses it: the agent says which tools wait, and execution goes to whatever executor it gives (L13).""" import io from pathlib import Path from typing import Any from nisse_core.log.canonical import CanonicalEvent, ToolCall, ToolResult from nisse_core.loop.driver import Driver, ToolOutcome from nisse_core.loop.tools import tool from nisse_core.oplog import OpLog from nisse_core.state.store import StateStore class Scripted: def __init__(self, *turns: list[CanonicalEvent]) -> None: self._turns = list(turns) def complete(self, events, tools, model, system=None) -> list[CanonicalEvent]: return self._turns.pop(0) def call(name: str, **arguments: str) -> ToolCall: return ToolCall(call_id=f"c-{name}", tool=name, arguments=arguments) class Reader: """Not a workspace: reads from a dict, as a review agent's might.""" def __init__(self) -> None: self.calls: list[tuple[str, dict[str, Any]]] = [] def dispatch(self, tool: str, arguments: dict[str, Any]) -> ToolOutcome: self.calls.append((tool, arguments)) return ToolOutcome({"a.py": "x = 1"}.get(arguments["path"], "none"), True) READ = tool("read_file", "Read a file.", path="Its path.") POST = tool("post_review", "Post the review. Ends your turn.", body="The review.") ASK = tool("ask_human", "Ask the operator.", question="The question.") def transcript(tmp_path: Path) -> Any: store = StateStore(tmp_path / "s.db", oplog=OpLog("t", io.StringIO())) return store.transcript("x", "driver") def results(log: Any) -> list[str]: return [e.event.result for e in log.read() if isinstance(e.event, ToolResult)] def test_the_agent_says_which_tools_wait(tmp_path: Path) -> None: model = Scripted( [call("read_file", path="a.py")], [call("post_review", body="looks fine")] ) reader = Reader() log = transcript(tmp_path) ran = Driver(model, "m").run( log, system="s", tools=[READ, POST], waiting={"post_review"}, workspace=reader, ) assert (ran.kind, ran.tool, ran.arguments) == ( "wait", "post_review", {"body": "looks fine"}, ) assert reader.calls == [("read_file", {"path": "a.py"})] assert results(log) == ["x = 1", "posted; your turn has ended"] def test_a_tool_not_named_waiting_doesnt_end_the_run(tmp_path: Path) -> None: # ask_human waits in nisse-dev; here no handler answers it and it isn't # waiting, so it is no tool at all, and the run goes on. model = Scripted([call("ask_human", question="?")], [call("post_review", body="")]) log = transcript(tmp_path) ran = Driver(model, "m").run( log, system="s", tools=[ASK, POST], waiting={"post_review"}, workspace=None ) assert ran.tool == "post_review" assert results(log)[0].startswith("no such tool: ask_human")