bash-exit-code-green-dot-bug.md:1:# Bug 分析：Bash 工具 exit code 非零时仍显示绿色圆点
bash-exit-code-green-dot-bug.md-2:
bash-exit-code-green-dot-bug.md-3:## 现象
bash-exit-code-green-dot-bug.md-4:
bash-exit-code-green-dot-bug.md-2:
bash-exit-code-green-dot-bug.md-3:## 现象
bash-exit-code-green-dot-bug.md-4:
bash-exit-code-green-dot-bug.md:5:在 `/plugin` 页面 scrollback 中，Bash 工具执行失败（exit code != 0）时，工具结果行显示的是**绿色圆点**（代表成功），而不是**红色叉号**（代表失败）。
bash-exit-code-green-dot-bug.md-6:
bash-exit-code-green-dot-bug.md-7:用户日志示例：
bash-exit-code-green-dot-bug.md-8:
bash-exit-code-green-dot-bug.md-7:用户日志示例：
bash-exit-code-green-dot-bug.md-8:
bash-exit-code-green-dot-bug.md-9:```
bash-exit-code-green-dot-bug.md:10:● Bash(timeout=30s command=cd ... && python -m pytest ...)
bash-exit-code-green-dot-bug.md-11:  ⎿  Exit code 1
bash-exit-code-green-dot-bug.md-12:```
bash-exit-code-green-dot-bug.md-13:
bash-exit-code-green-dot-bug.md-17:
bash-exit-code-green-dot-bug.md-18:Bug 在 **SDK 层** `comate_agent_sdk/system_tools/tools/bash.py`，不在 comate_cli。
bash-exit-code-green-dot-bug.md-19:
bash-exit-code-green-dot-bug.md:20:Bash 工具在命令执行完毕后构造返回 envelope 时，只检查了三种异常情况：
bash-exit-code-green-dot-bug.md-21:- `overflowed` → `err("OUTPUT_OVERFLOW", ...)` — ok=False
bash-exit-code-green-dot-bug.md-22:- `timed_out` → `err("TIMEOUT", ...)` — ok=False
bash-exit-code-green-dot-bug.md-23:- `interrupted` → `err("INTERRUPTED", ...)` — ok=False
bash-exit-code-green-dot-bug.md-27:
bash-exit-code-green-dot-bug.md-28:## 完整调用链（6 层）
bash-exit-code-green-dot-bug.md-29:
bash-exit-code-green-dot-bug.md:30:### 第 1 层：Bash 工具 envelope 构造（bug 所在）
bash-exit-code-green-dot-bug.md-31:
bash-exit-code-green-dot-bug.md-32:**文件**：`comate_agent_sdk/system_tools/tools/bash.py`（约第 404-431 行）
bash-exit-code-green-dot-bug.md-33:
bash-exit-code-green-dot-bug.md-125:
bash-exit-code-green-dot-bug.md-126:### comate_cli 侧的防御性补丁（可选）
bash-exit-code-green-dot-bug.md-127:
bash-exit-code-green-dot-bug.md:128:如果 SDK 修复周期较长，可以在 comate_cli 的 `event_renderer.py` 的 `ToolResultEvent` 处理分支中增加防御逻辑：当 `is_error=False` 但 `output` 是 `BashOutput` 且 `exit_code != 0` 时，强制将 `is_error` 修正为 `True`。但这种方式侵入性大且绕过了 SDK 的语义契约，建议仅在无法及时修改 SDK 时使用。
bash-exit-code-green-dot-bug.md-129:
bash-exit-code-green-dot-bug.md-130:## 涉及文件清单
bash-exit-code-green-dot-bug.md-131:
tests/test_history_sync.py-313:            id="tc_bash_env",
tests/test_history_sync.py-314:            type="function",
tests/test_history_sync.py-315:            function=Function(
tests/test_history_sync.py:316:                name="Bash",
tests/test_history_sync.py-317:                arguments=json.dumps(
tests/test_history_sync.py-318:                    {
tests/test_history_sync.py-319:                        "args": ["npm", "test"],
tests/test_history_sync.py-341:                item_type=ItemType.TOOL_RESULT,
tests/test_history_sync.py-342:                message=ToolMessage(
tests/test_history_sync.py-343:                    tool_call_id="tc_bash_env",
tests/test_history_sync.py:344:                    tool_name="Bash",
tests/test_history_sync.py-345:                    content="ok",
tests/test_history_sync.py-346:                    is_error=False,
tests/test_history_sync.py-347:                ),
tests/test_history_sync.py-346:                    is_error=False,
tests/test_history_sync.py-347:                ),
tests/test_history_sync.py-348:                content_text="ok",
tests/test_history_sync.py:349:                tool_name="Bash",
tests/test_history_sync.py-350:            ),
tests/test_history_sync.py-351:        ]
tests/test_history_sync.py-352:        sync = _DummyHistorySync(items)
comate_cli/terminal_agent/custom_slash_commands.py-316:        output_bytes = len(output.encode("utf-8"))
comate_cli/terminal_agent/custom_slash_commands.py-317:        if output_bytes > item_max_bytes:
comate_cli/terminal_agent/custom_slash_commands.py-318:            raise CustomSlashExpandError(
comate_cli/terminal_agent/custom_slash_commands.py:319:                f"Bash output exceeds {item_max_bytes} bytes for command: {command}"
comate_cli/terminal_agent/custom_slash_commands.py-320:            )
comate_cli/terminal_agent/custom_slash_commands.py-321:
comate_cli/terminal_agent/custom_slash_commands.py-322:        block = _format_bash_output_block(command=command, output=output)
comate_cli/terminal_agent/custom_slash_commands.py-430:        process.kill()
comate_cli/terminal_agent/custom_slash_commands.py-431:        await process.communicate()
comate_cli/terminal_agent/custom_slash_commands.py-432:        raise CustomSlashExpandError(
comate_cli/terminal_agent/custom_slash_commands.py:433:            f"Bash command timed out after {timeout_seconds:.1f}s: {command}"
comate_cli/terminal_agent/custom_slash_commands.py-434:        ) from exc
comate_cli/terminal_agent/custom_slash_commands.py-435:
comate_cli/terminal_agent/custom_slash_commands.py-436:    stdout_text = stdout.decode("utf-8", errors="replace")
comate_cli/terminal_agent/custom_slash_commands.py-443:
comate_cli/terminal_agent/custom_slash_commands.py-444:    if process.returncode != 0:
comate_cli/terminal_agent/custom_slash_commands.py-445:        raise CustomSlashExpandError(
comate_cli/terminal_agent/custom_slash_commands.py:446:            f"Bash command failed (exit {process.returncode}): {command}\n{merged_output}"
comate_cli/terminal_agent/custom_slash_commands.py-447:        )
comate_cli/terminal_agent/custom_slash_commands.py-448:
comate_cli/terminal_agent/custom_slash_commands.py-449:    return merged_output
comate_cli/terminal_agent/custom_slash_commands.py-451:
comate_cli/terminal_agent/custom_slash_commands.py-452:def _format_bash_output_block(*, command: str, output: str) -> str:
comate_cli/terminal_agent/custom_slash_commands.py-453:    return (
comate_cli/terminal_agent/custom_slash_commands.py:454:        "### Bash Command Output\n"
comate_cli/terminal_agent/custom_slash_commands.py-455:        f"`{command}`\n"
comate_cli/terminal_agent/custom_slash_commands.py-456:        "```text\n"
comate_cli/terminal_agent/custom_slash_commands.py-457:        f"{output}\n"
tests/test_rewind_command_semantics.py-165:            {
tests/test_rewind_command_semantics.py-166:                "tool_call_id": "tc_before",
tests/test_rewind_command_semantics.py-167:                "tool_name": "bash",
tests/test_rewind_command_semantics.py:168:                "display_name": "Bash",
tests/test_rewind_command_semantics.py-169:                "started_at_monotonic": 0.0,
tests/test_rewind_command_semantics.py-170:                "completed_at_wall": time.time(),
tests/test_rewind_command_semantics.py-171:                "args": {"command": "ls"},
tests/test_tool_result_store.py-14:        rec = ToolResultRecord(
tests/test_tool_result_store.py-15:            tool_call_id="tc_1",
tests/test_tool_result_store.py-16:            tool_name="bash",
tests/test_tool_result_store.py:17:            display_name="Bash",
tests/test_tool_result_store.py-18:            sequence=1,
tests/test_tool_result_store.py-19:            started_at_monotonic=0.0,
tests/test_tool_result_store.py-20:            completed_at_wall=time.time(),
tests/test_tool_result_store.py-34:        base = dict(
tests/test_tool_result_store.py-35:            tool_call_id="tc_1",
tests/test_tool_result_store.py-36:            tool_name="bash",
tests/test_tool_result_store.py:37:            display_name="Bash",
tests/test_tool_result_store.py-38:            started_at_monotonic=0.0,
tests/test_tool_result_store.py-39:            completed_at_wall=time.time(),
tests/test_tool_result_store.py-40:            args={},
tests/test_tool_result_store.py-109:    def test_record_normalizes_tool_name_to_lowercase(self) -> None:
tests/test_tool_result_store.py-110:        reg = ToolResultRegistry()
tests/test_tool_result_store.py-111:
tests/test_tool_result_store.py:112:        r = reg.record(self._make_kwargs(tool_name="Bash"))
tests/test_tool_result_store.py-113:
tests/test_tool_result_store.py-114:        self.assertEqual(r.tool_name, "bash")
tests/test_tool_result_store.py-115:
tests/test_tool_result_store.py-119:        base = dict(
tests/test_tool_result_store.py-120:            tool_call_id="tc_1",
tests/test_tool_result_store.py-121:            tool_name="bash",
tests/test_tool_result_store.py:122:            display_name="Bash",
tests/test_tool_result_store.py-123:            started_at_monotonic=0.0,
tests/test_tool_result_store.py-124:            completed_at_wall=time.time(),
tests/test_tool_result_store.py-125:            args={},
tests/test_tui_tool_result_registry_lifecycle.py-50:                {
tests/test_tui_tool_result_registry_lifecycle.py-51:                    "tool_call_id": "tc_before",
tests/test_tui_tool_result_registry_lifecycle.py-52:                    "tool_name": "bash",
tests/test_tui_tool_result_registry_lifecycle.py:53:                    "display_name": "Bash",
tests/test_tui_tool_result_registry_lifecycle.py-54:                    "started_at_monotonic": 0.0,
tests/test_tui_tool_result_registry_lifecycle.py-55:                    "completed_at_wall": time.time(),
tests/test_tui_tool_result_registry_lifecycle.py-56:                    "args": {"command": "ls"},
tests/test_history_printer.py-47:    def test_tool_result_subtitle_content_starts_on_hook_line(self) -> None:
tests/test_history_printer.py-48:        entry = HistoryEntry(
tests/test_history_printer.py-49:            entry_type="tool_result",
tests/test_history_printer.py:50:            text="Bash(timeout=120s date '+%Y-%m-%d %H:%M:%S %Z')",
tests/test_history_printer.py-51:            subtitle="2026-05-15 09:15:56 CST",
tests/test_history_printer.py-52:        )
tests/test_history_printer.py-53:        output = _strip_ansi(self._render_to_str([entry]))
tests/test_event_renderer_log_boundary.py-85:    renderer.seed_user_message("跑这个")
tests/test_event_renderer_log_boundary.py-86:    renderer.enqueue_log(severity="warning", message="cleanup deferred")
tests/test_event_renderer_log_boundary.py-87:    renderer.append_static_tool_result(
tests/test_event_renderer_log_boundary.py:88:        signature="Bash(command=ls -la)",
tests/test_event_renderer_log_boundary.py-89:        is_error=False,
tests/test_event_renderer_log_boundary.py-90:        subtitle="Exit code 0",
tests/test_event_renderer_log_boundary.py-91:    )
tests/test_custom_slash_commands.py-166:                raw_args="",
tests/test_custom_slash_commands.py-167:                project_root=project_root,
tests/test_custom_slash_commands.py-168:            )
tests/test_custom_slash_commands.py:169:            self.assertIn("### Bash Command Output", rendered)
tests/test_custom_slash_commands.py-170:            self.assertIn("ok", rendered)
tests/test_custom_slash_commands.py-171:            self.assertIn("### File Content", rendered)
tests/test_custom_slash_commands.py-172:            self.assertIn("\"name\": \"demo\"", rendered)
tests/test_tool_result_viewer.py-11:        return ToolResultRecord(
tests/test_tool_result_viewer.py-12:            tool_call_id=f"tc_{seq}",
tests/test_tool_result_viewer.py-13:            tool_name="bash",
tests/test_tool_result_viewer.py:14:            display_name="Bash",
tests/test_tool_result_viewer.py-15:            sequence=seq,
tests/test_tool_result_viewer.py-16:            started_at_monotonic=0.0,
tests/test_tool_result_viewer.py-17:            completed_at_wall=time.time(),
comate_cli/terminal_agent/tool_result_formatters.py-194:
comate_cli/terminal_agent/tool_result_formatters.py-195:    if output is not None:
comate_cli/terminal_agent/tool_result_formatters.py-196:        try:
comate_cli/terminal_agent/tool_result_formatters.py:197:            from comate_agent_sdk.tool_schemas.bash import BashOutput
comate_cli/terminal_agent/tool_result_formatters.py-198:            from comate_agent_sdk.tool_schemas.read import ReadOutput
comate_cli/terminal_agent/tool_result_formatters.py-199:            from comate_agent_sdk.tool_schemas.search import GlobOutput, GrepOutput, LSOutput
comate_cli/terminal_agent/tool_result_formatters.py-200:            from comate_agent_sdk.tool_schemas.write import WriteOutput
comate_cli/terminal_agent/tool_result_formatters.py-205:            if lowered == "read" and isinstance(output, ReadOutput):
comate_cli/terminal_agent/tool_result_formatters.py-206:                return f"Read {output.lines_returned} lines of {output.total_lines}"
comate_cli/terminal_agent/tool_result_formatters.py-207:
comate_cli/terminal_agent/tool_result_formatters.py:208:            if lowered == "bash" and isinstance(output, BashOutput):
comate_cli/terminal_agent/tool_result_formatters.py-209:                return f"Exit code {output.exit_code}"
comate_cli/terminal_agent/tool_result_formatters.py-210:
comate_cli/terminal_agent/tool_result_formatters.py-211:            if lowered in ("glob", "ls") and isinstance(output, (GlobOutput, LSOutput)):
comate_cli/terminal_agent/tool_result_formatters.py-570:        and rec.typed_output is None
comate_cli/terminal_agent/tool_result_formatters.py-571:        and subtitle
comate_cli/terminal_agent/tool_result_formatters.py-572:        and subtitle != "Completed"
comate_cli/terminal_agent/tool_result_formatters.py:573:        and str(rec.result_text).lstrip().startswith("# Bash Error")
comate_cli/terminal_agent/tool_result_formatters.py-574:    ):
comate_cli/terminal_agent/tool_result_formatters.py-575:        return text, _with_expand_hint(subtitle, show_hint=False)
comate_cli/terminal_agent/tool_result_formatters.py-576:    if lines:
tests/test_tool_view.py-36:    def test_interrupt_running_marks_text_ref_as_interrupted(self) -> None:
tests/test_tool_view.py-37:        view = ToolEventView(fancy_progress_effect=False)
tests/test_tool_view.py-38:        text_ref = view.render_call(
tests/test_tool_view.py:39:            "Bash",
tests/test_tool_view.py-40:            {"command": "npm test"},
tests/test_tool_view.py-41:            "tc_bash_1",
tests/test_tool_view.py-42:        )
tests/test_tool_view.py-43:
tests/test_tool_view.py-44:        view.interrupt_running()
tests/test_tool_view.py-45:        self.assertIn("已中断", text_ref.plain)
tests/test_tool_view.py:46:        self.assertIn("⏹ Bash", text_ref.plain)
tests/test_tool_view.py-47:
tests/test_tool_view.py-48:    def test_has_running_tasks_only_counts_task_tool(self) -> None:
tests/test_tool_view.py-49:        view = ToolEventView(fancy_progress_effect=False)
tests/test_tool_view.py-239:    def test_bash_within_budget(self) -> None:
tests/test_tool_view.py-240:        from comate_cli.terminal_agent.tool_view import summarize_tool_args
tests/test_tool_view.py-241:        args = {"args": ["echo"] + ["x" * 200]}
tests/test_tool_view.py:242:        result = summarize_tool_args("Bash", args, None)
tests/test_tool_view.py-243:        self.assertLessEqual(len(result), 200)
tests/test_tool_view.py-244:
tests/test_tool_view.py-245:    def test_bash_command_within_budget(self) -> None:
tests/test_tool_view.py-245:    def test_bash_command_within_budget(self) -> None:
tests/test_tool_view.py-246:        from comate_cli.terminal_agent.tool_view import summarize_tool_args
tests/test_tool_view.py-247:        args = {"command": "x" * 200}
tests/test_tool_view.py:248:        result = summarize_tool_args("Bash", args, None)
tests/test_tool_view.py-249:        self.assertLessEqual(len(result), 200)
tests/test_tool_view.py-250:
tests/test_tool_view.py-251:    def test_fallback_within_budget(self) -> None:
tests/test_tool_view.py-285:        self.assertLessEqual(len(result), TOOL_SUMMARY_MAX_LENGTH)
tests/test_tool_view.py-286:
tests/test_tool_view.py-287:
tests/test_tool_view.py:288:class TestBashSummaryFallback(unittest.TestCase):
tests/test_tool_view.py-289:    """Bash command fallback should display the command like normal CLI input."""
tests/test_tool_view.py-290:
tests/test_tool_view.py-291:    def test_fallback_command_field_has_no_prefix(self) -> None:
tests/test_tool_view.py-286:
tests/test_tool_view.py-287:
tests/test_tool_view.py-288:class TestBashSummaryFallback(unittest.TestCase):
tests/test_tool_view.py:289:    """Bash command fallback should display the command like normal CLI input."""
tests/test_tool_view.py-290:
tests/test_tool_view.py-291:    def test_fallback_command_field_has_no_prefix(self) -> None:
tests/test_tool_view.py-292:        from comate_cli.terminal_agent.tool_view import summarize_tool_args
tests/test_tool_view.py-291:    def test_fallback_command_field_has_no_prefix(self) -> None:
tests/test_tool_view.py-292:        from comate_cli.terminal_agent.tool_view import summarize_tool_args
tests/test_tool_view.py-293:
tests/test_tool_view.py:294:        result = summarize_tool_args("Bash", {"command": "uname -a"})
tests/test_tool_view.py-295:
tests/test_tool_view.py-296:        self.assertNotIn("command=", result)
tests/test_tool_view.py-297:        self.assertIn("uname -a", result)
tests/test_tool_view.py-299:    def test_fallback_command_field_with_cwd(self) -> None:
tests/test_tool_view.py-300:        from comate_cli.terminal_agent.tool_view import summarize_tool_args
tests/test_tool_view.py-301:
tests/test_tool_view.py:302:        result = summarize_tool_args("Bash", {"command": "ls", "cwd": "/tmp"})
tests/test_tool_view.py-303:
tests/test_tool_view.py-304:        self.assertNotIn("command=", result)
tests/test_tool_view.py-305:        self.assertIn("ls", result)
tests/test_event_renderer_streaming.py-608:        renderer = EventRenderer()
tests/test_event_renderer_streaming.py-609:        renderer.start_turn()
tests/test_event_renderer_streaming.py-610:        renderer.handle_event(TextDeltaEvent(delta="先执行工具。\n", message_id="m1"))
tests/test_event_renderer_streaming.py:611:        renderer.handle_event(ToolCallStartEvent(tool_call_id="tc1", tool="Bash"))
tests/test_event_renderer_streaming.py-612:        renderer.handle_event(TextDeltaEvent(delta="\n已创建新分支。\n", message_id="m2"))
tests/test_event_renderer_streaming.py-613:
tests/test_event_renderer_streaming.py-614:        self.assertEqual(
tests/test_event_renderer_streaming.py-620:        renderer = EventRenderer()
tests/test_event_renderer_streaming.py-621:        renderer.start_turn()
tests/test_event_renderer_streaming.py-622:        renderer.handle_event(TextDeltaEvent(delta="先执行工具。\n", message_id="m1"))
tests/test_event_renderer_streaming.py:623:        renderer.handle_event(ToolCallStartEvent(tool_call_id="tc1", tool="Bash"))
tests/test_event_renderer_streaming.py-624:        renderer.handle_event(TextDeltaEvent(delta="  \n已创建新分支。\n", message_id="m2"))
tests/test_event_renderer_streaming.py-625:
tests/test_event_renderer_streaming.py-626:        self.assertEqual(
tests/test_event_renderer.py-527:        renderer.seed_user_message("hello")
tests/test_event_renderer.py-528:        renderer.handle_event(
tests/test_event_renderer.py-529:            ToolCallEvent(
tests/test_event_renderer.py:530:                tool="Bash",
tests/test_event_renderer.py-531:                args={"args": ["sleep", "10"]},
tests/test_event_renderer.py-532:                tool_call_id="tc_interrupt_1",
tests/test_event_renderer.py-533:            )
tests/test_event_renderer.py-545:        renderer.start_turn()
tests/test_event_renderer.py-546:        renderer.handle_event(
tests/test_event_renderer.py-547:            ToolCallEvent(
tests/test_event_renderer.py:548:                tool="Bash",
tests/test_event_renderer.py-549:                args={"args": ["sleep", "30"]},
tests/test_event_renderer.py-550:                tool_call_id="tc_cancel_1",
tests/test_event_renderer.py-551:            )
tests/test_event_renderer.py-793:        self.assertEqual(len(entries), 1)
tests/test_event_renderer.py-794:
tests/test_event_renderer.py-795:
tests/test_event_renderer.py:796:class TestBashObservableInputDisplay(unittest.TestCase):
tests/test_event_renderer.py-797:    def test_bash_summary_includes_env_and_command(self) -> None:
tests/test_event_renderer.py-798:        summary = summarize_tool_args(
tests/test_event_renderer.py-799:            "Bash",
tests/test_event_renderer.py-796:class TestBashObservableInputDisplay(unittest.TestCase):
tests/test_event_renderer.py-797:    def test_bash_summary_includes_env_and_command(self) -> None:
tests/test_event_renderer.py-798:        summary = summarize_tool_args(
tests/test_event_renderer.py:799:            "Bash",
tests/test_event_renderer.py-800:            {
tests/test_event_renderer.py-801:                "command": "npm test",
tests/test_event_renderer.py-802:                "env": {"NODE_ENV": "test", "DEBUG": "1"},
tests/test_event_renderer.py-811:
tests/test_event_renderer.py-812:    def test_bash_summary_formats_timeout_ms_as_seconds(self) -> None:
tests/test_event_renderer.py-813:        summary = summarize_tool_args(
tests/test_event_renderer.py:814:            "Bash",
tests/test_event_renderer.py-815:            {
tests/test_event_renderer.py-816:                "command": "npm test",
tests/test_event_renderer.py-817:                "timeout_ms": 120000,
tests/test_event_renderer.py-822:
tests/test_event_renderer.py-823:    def test_bash_summary_formats_timeout_alias_as_seconds(self) -> None:
tests/test_event_renderer.py-824:        summary = summarize_tool_args(
tests/test_event_renderer.py:825:            "Bash",
tests/test_event_renderer.py-826:            {
tests/test_event_renderer.py-827:                "command": "npm test",
tests/test_event_renderer.py-828:                "timeout": "1500",
tests/test_event_renderer.py-922:
tests/test_event_renderer.py-923:    def test_bash_exit_code(self) -> None:
tests/test_event_renderer.py-924:        result = "output\nExit code 0"
tests/test_event_renderer.py:925:        subtitle = self._build("Bash", result)
tests/test_event_renderer.py-926:        self.assertEqual(subtitle, "Exit code 0")
tests/test_event_renderer.py-927:
tests/test_event_renderer.py-928:    def test_bash_no_exit_code_fallback(self) -> None:
tests/test_event_renderer.py-927:
tests/test_event_renderer.py-928:    def test_bash_no_exit_code_fallback(self) -> None:
tests/test_event_renderer.py-929:        result = "some output"
tests/test_event_renderer.py:930:        subtitle = self._build("Bash", result)
tests/test_event_renderer.py-931:        self.assertEqual(subtitle, "Completed")
tests/test_event_renderer.py-932:
tests/test_event_renderer.py-933:    def test_glob_counts_files(self) -> None:
tests/test_event_renderer.py-1140:
tests/test_event_renderer.py-1141:    def test_bash_error_result_prefers_exit_code_subtitle(self) -> None:
tests/test_event_renderer.py-1142:        renderer = EventRenderer()
tests/test_event_renderer.py:1143:        renderer._append_tool_call("Bash", {"command": "pytest"}, "call-bash-error")
tests/test_event_renderer.py-1144:        renderer._append_tool_result(
tests/test_event_renderer.py-1145:            tool_name="Bash",
tests/test_event_renderer.py-1146:            tool_call_id="call-bash-error",
tests/test_event_renderer.py-1142:        renderer = EventRenderer()
tests/test_event_renderer.py-1143:        renderer._append_tool_call("Bash", {"command": "pytest"}, "call-bash-error")
tests/test_event_renderer.py-1144:        renderer._append_tool_result(
tests/test_event_renderer.py:1145:            tool_name="Bash",
tests/test_event_renderer.py-1146:            tool_call_id="call-bash-error",
tests/test_event_renderer.py-1147:            is_error=True,
tests/test_event_renderer.py-1148:            result="# Bash Error\n\nCode: EXIT_CODE_NON_ZERO\nMessage: Command exited with non-zero code: 7\n\nExit code: 7",
tests/test_event_renderer.py-1145:            tool_name="Bash",
tests/test_event_renderer.py-1146:            tool_call_id="call-bash-error",
tests/test_event_renderer.py-1147:            is_error=True,
tests/test_event_renderer.py:1148:            result="# Bash Error\n\nCode: EXIT_CODE_NON_ZERO\nMessage: Command exited with non-zero code: 7\n\nExit code: 7",
tests/test_event_renderer.py-1149:        )
tests/test_event_renderer.py-1150:        entries = [e for e in renderer.history_entries() if e.entry_type == "tool_result"]
tests/test_event_renderer.py-1151:        self.assertEqual(len(entries), 1)
tests/test_event_renderer.py-1195:        renderer = EventRenderer(tool_results=registry)
tests/test_event_renderer.py-1196:
tests/test_event_renderer.py-1197:        renderer.handle_event(
tests/test_event_renderer.py:1198:            ToolCallEvent(tool="Bash", args={"command": "ls"}, tool_call_id="tc_1")
tests/test_event_renderer.py-1199:        )
tests/test_event_renderer.py-1200:        renderer.handle_event(
tests/test_event_renderer.py-1201:            ToolResultEvent(
tests/test_event_renderer.py-1199:        )
tests/test_event_renderer.py-1200:        renderer.handle_event(
tests/test_event_renderer.py-1201:            ToolResultEvent(
tests/test_event_renderer.py:1202:                tool="Bash",
tests/test_event_renderer.py-1203:                result="Exit code: 0",
tests/test_event_renderer.py-1204:                tool_call_id="tc_1",
tests/test_event_renderer.py-1205:                is_error=False,
tests/test_event_renderer.py-1287:        renderer.set_tool_result_registry(registry)
tests/test_event_renderer.py-1288:
tests/test_event_renderer.py-1289:        renderer.handle_event(
tests/test_event_renderer.py:1290:            ToolCallEvent(tool="Bash", args={"command": "pwd"}, tool_call_id="tc_3")
tests/test_event_renderer.py-1291:        )
tests/test_event_renderer.py-1292:        renderer.handle_event(
tests/test_event_renderer.py-1293:            ToolResultEvent(
tests/test_event_renderer.py-1291:        )
tests/test_event_renderer.py-1292:        renderer.handle_event(
tests/test_event_renderer.py-1293:            ToolResultEvent(
tests/test_event_renderer.py:1294:                tool="Bash",
tests/test_event_renderer.py-1295:                result="Exit code: 0",
tests/test_event_renderer.py-1296:                tool_call_id="tc_3",
tests/test_event_renderer.py-1297:                is_error=False,
tests/test_tool_result_formatters.py-59:        base = dict(
tests/test_tool_result_formatters.py-60:            tool_call_id="tc_1",
tests/test_tool_result_formatters.py-61:            tool_name="bash",
tests/test_tool_result_formatters.py:62:            display_name="Bash",
tests/test_tool_result_formatters.py-63:            sequence=1,
tests/test_tool_result_formatters.py-64:            started_at_monotonic=0.0,
tests/test_tool_result_formatters.py-65:            completed_at_wall=time.time(),
tests/test_tool_result_formatters.py-84:        self.assertIn(EXPAND_HINT.strip(), full)
tests/test_tool_result_formatters.py-85:
tests/test_tool_result_formatters.py-86:    def test_bash_preview_omits_expand_hint_for_single_output_line(self) -> None:
tests/test_tool_result_formatters.py:87:        from comate_agent_sdk.tool_schemas.bash import BashOutput
tests/test_tool_result_formatters.py-88:
tests/test_tool_result_formatters.py-89:        output = BashOutput(
tests/test_tool_result_formatters.py-90:            stdout="2026-05-14 19:26:48 CST",
tests/test_tool_result_formatters.py-86:    def test_bash_preview_omits_expand_hint_for_single_output_line(self) -> None:
tests/test_tool_result_formatters.py-87:        from comate_agent_sdk.tool_schemas.bash import BashOutput
tests/test_tool_result_formatters.py-88:
tests/test_tool_result_formatters.py:89:        output = BashOutput(
tests/test_tool_result_formatters.py-90:            stdout="2026-05-14 19:26:48 CST",
tests/test_tool_result_formatters.py-91:            stderr="",
tests/test_tool_result_formatters.py-92:            exit_code=0,
tests/test_tool_result_formatters.py-298:        self.assertNotIn("'path':", combined)
tests/test_tool_result_formatters.py-299:
tests/test_tool_result_formatters.py-300:    def test_bash_success_uses_complete_typed_output_fixture(self) -> None:
tests/test_tool_result_formatters.py:301:        from comate_agent_sdk.tool_schemas.bash import BashOutput
tests/test_tool_result_formatters.py-302:
tests/test_tool_result_formatters.py-303:        output = BashOutput(
tests/test_tool_result_formatters.py-304:            stdout="line1\nline2\nline3\nline4",
tests/test_tool_result_formatters.py-300:    def test_bash_success_uses_complete_typed_output_fixture(self) -> None:
tests/test_tool_result_formatters.py-301:        from comate_agent_sdk.tool_schemas.bash import BashOutput
tests/test_tool_result_formatters.py-302:
tests/test_tool_result_formatters.py:303:        output = BashOutput(
tests/test_tool_result_formatters.py-304:            stdout="line1\nline2\nline3\nline4",
tests/test_tool_result_formatters.py-305:            stderr="",
tests/test_tool_result_formatters.py-306:            exit_code=0,
tests/test_tool_result_formatters.py-391:        base = dict(
tests/test_tool_result_formatters.py-392:            tool_call_id="tc_1",
tests/test_tool_result_formatters.py-393:            tool_name="bash",
tests/test_tool_result_formatters.py:394:            display_name="Bash",
tests/test_tool_result_formatters.py-395:            sequence=1,
tests/test_tool_result_formatters.py-396:            started_at_monotonic=0.0,
tests/test_tool_result_formatters.py-397:            completed_at_wall=time.time(),
tests/test_tool_result_formatters.py-411:
tests/test_tool_result_formatters.py-412:        text = viewer_list_row(rec, selected=False)
tests/test_tool_result_formatters.py-413:
tests/test_tool_result_formatters.py:414:        self.assertIn("Bash", text.plain)
tests/test_tool_result_formatters.py-415:
tests/test_tool_result_formatters.py-416:    def test_selected_row_has_reverse_style(self) -> None:
tests/test_tool_result_formatters.py-417:        from comate_cli.terminal_agent.tool_result_formatters import viewer_list_row
tests/test_tool_result_formatters.py-420:
tests/test_tool_result_formatters.py-421:        text = viewer_list_row(rec, selected=True)
tests/test_tool_result_formatters.py-422:
tests/test_tool_result_formatters.py:423:        self.assertIn("Bash", text.plain)
tests/test_tool_result_formatters.py-424:        self.assertIn("reverse", str(text.style))
tests/test_tool_result_formatters.py-425:
tests/test_tool_result_formatters.py-426:
tests/test_tool_result_formatters.py-429:        base = dict(
tests/test_tool_result_formatters.py-430:            tool_call_id="tc_1",
tests/test_tool_result_formatters.py-431:            tool_name="bash",
tests/test_tool_result_formatters.py:432:            display_name="Bash",
tests/test_tool_result_formatters.py-433:            sequence=1,
tests/test_tool_result_formatters.py-434:            started_at_monotonic=0.0,
tests/test_tool_result_formatters.py-435:            completed_at_wall=time.time(),
tests/test_tool_result_viewer_key_bindings.py-31:        return ToolResultRecord(
tests/test_tool_result_viewer_key_bindings.py-32:            tool_call_id="tc_1",
tests/test_tool_result_viewer_key_bindings.py-33:            tool_name="bash",
tests/test_tool_result_viewer_key_bindings.py:34:            display_name="Bash",
tests/test_tool_result_viewer_key_bindings.py-35:            sequence=1,
tests/test_tool_result_viewer_key_bindings.py-36:            started_at_monotonic=0.0,
tests/test_tool_result_viewer_key_bindings.py-37:            completed_at_wall=time.time(),
tests/test_history_printer_log.py-181:    entries = [
tests/test_history_printer_log.py-182:        HistoryEntry(
tests/test_history_printer_log.py-183:            entry_type="tool_result",
tests/test_history_printer_log.py:184:            text="Bash(command)",
tests/test_history_printer_log.py-185:            subtitle="Exit code 0",
tests/test_history_printer_log.py-186:        ),
tests/test_history_printer_log.py-187:        HistoryEntry(

[Showing results with pagination = limit: 250]