diff --git a/nanobot/agent/tools/exec_session.py b/nanobot/agent/tools/exec_session.py index c063bdbaf..3a8d59bfc 100644 --- a/nanobot/agent/tools/exec_session.py +++ b/nanobot/agent/tools/exec_session.py @@ -660,7 +660,7 @@ class WriteStdinTool(Tool): close_stdin=close_stdin if first else False, terminate=terminate if first else False, yield_time_ms=step_ms, - max_output_chars=max_output_chars, + max_output_chars=MAX_OUTPUT_CHARS, owner_session_key=current_request_session_key(), ) first = False diff --git a/tests/tools/test_exec_session_tools.py b/tests/tools/test_exec_session_tools.py index 77195e52f..a042a098a 100644 --- a/tests/tools/test_exec_session_tools.py +++ b/tests/tools/test_exec_session_tools.py @@ -16,11 +16,13 @@ from nanobot.agent import context as agent_context from nanobot.agent.loop import AgentLoop from nanobot.agent.tools.context import RequestContext, bind_request_context, reset_request_context from nanobot.agent.tools.exec_session import ( + MAX_OUTPUT_CHARS, ExecSessionManager, ListExecSessionsTool, WriteStdinTool, _BoundedOutputBuffer, _SessionPoll, + _truncate_output, ) from nanobot.agent.tools.registry import is_tool_error_result from nanobot.agent.tools.shell import ExecTool @@ -227,6 +229,52 @@ def test_write_stdin_wait_for_keeps_aggregate_within_output_budget(): assert len(result) < 1100 +def test_write_stdin_wait_for_searches_before_response_truncation(): + async def run() -> tuple[str, list[int]]: + output = "A" * 1500 + "TARGET" + "B" * 1500 + observed_limits: list[int] = [] + + async def write( + *, + session_id: str, + chars: str | None, + close_stdin: bool, + terminate: bool, + yield_time_ms: int, + max_output_chars: int, + owner_session_key: str | None, + ) -> _SessionPoll: + del session_id, chars, close_stdin, terminate, yield_time_ms, owner_session_key + observed_limits.append(max_output_chars) + visible, truncated = _truncate_output(output, max_output_chars) + return _SessionPoll( + output=visible, + done=True, + exit_code=0, + truncated_chars=truncated, + ) + + manager = SimpleNamespace(write=AsyncMock(side_effect=write)) + tool = WriteStdinTool(manager=manager) + result = await tool._wait_for_output( + session_id="session", + chars=None, + close_stdin=False, + terminate=False, + wait_for="TARGET", + wait_timeout_ms=1000, + max_output_chars=1000, + ) + return result, observed_limits + + result, observed_limits = asyncio.run(run()) + + assert observed_limits == [MAX_OUTPUT_CHARS] + assert "Wait target not observed" not in result + assert "(2,006 chars truncated from output)" in result + assert len(result) < 1100 + + def test_exec_one_shot_accepts_max_output_tokens_alias(tmp_path): async def run() -> str: tool = ExecTool(working_dir=str(tmp_path), timeout=5)