comate-cli 0.7.0a6__tar.gz → 0.7.0a8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. comate_cli-0.7.0a8/AGENTS.md +115 -0
  2. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/CHANGELOG.md +4 -0
  3. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/PKG-INFO +1 -1
  4. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/event_renderer.py +85 -10
  5. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/history_printer.py +86 -17
  6. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/logging_adapter.py +26 -0
  7. comate_cli-0.7.0a8/comate_cli/terminal_agent/markdown_render.py +138 -0
  8. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/models.py +2 -0
  9. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/slash_commands.py +5 -0
  10. comate_cli-0.7.0a8/comate_cli/terminal_agent/tool_fold.py +206 -0
  11. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tool_result_formatters.py +65 -33
  12. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/transcript_viewer.py +60 -27
  13. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui.py +22 -13
  14. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/commands.py +76 -40
  15. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/history_sync.py +45 -3
  16. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/input_behavior.py +45 -8
  17. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/key_bindings.py +3 -3
  18. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/render_panels.py +48 -0
  19. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/pyproject.toml +1 -1
  20. comate_cli-0.7.0a8/report.md +382 -0
  21. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_event_renderer.py +102 -28
  22. comate_cli-0.7.0a8/tests/test_event_renderer_tool_fold.py +146 -0
  23. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_history_printer.py +57 -1
  24. comate_cli-0.7.0a8/tests/test_history_printer_subtitle_position.py +78 -0
  25. comate_cli-0.7.0a8/tests/test_history_printer_tool_fold.py +78 -0
  26. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_history_sync.py +29 -5
  27. comate_cli-0.7.0a8/tests/test_history_sync_tool_fold.py +98 -0
  28. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_logging_adapter.py +43 -0
  29. comate_cli-0.7.0a8/tests/test_markdown_render.py +102 -0
  30. comate_cli-0.7.0a8/tests/test_plugin_slash_commands.py +107 -0
  31. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_skills_slash_command.py +3 -27
  32. comate_cli-0.7.0a8/tests/test_slash_clear.py +350 -0
  33. comate_cli-0.7.0a8/tests/test_tool_fold.py +89 -0
  34. comate_cli-0.7.0a8/tests/test_tool_fold_panel.py +159 -0
  35. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tool_result_formatters.py +163 -12
  36. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_transcript_viewer.py +64 -6
  37. comate_cli-0.7.0a8/tests/test_transcript_viewer_tool_fold.py +47 -0
  38. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_paste_newline_guard.py +62 -0
  39. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_paste_placeholder.py +46 -1
  40. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_queue_sdk_source.py +35 -11
  41. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/uv.lock +2 -2
  42. comate_cli-0.7.0a6/comate_cli/terminal_agent/markdown_render.py +0 -26
  43. comate_cli-0.7.0a6/tests/test_markdown_render.py +0 -53
  44. comate_cli-0.7.0a6/tests/test_plugin_slash_commands.py +0 -17
  45. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/.gitignore +0 -0
  46. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/README.md +0 -0
  47. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/bash-exit-code-green-dot-bug.md +0 -0
  48. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/__init__.py +0 -0
  49. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/__main__.py +0 -0
  50. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/main.py +0 -0
  51. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/mcp_cli.py +0 -0
  52. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/__init__.py +0 -0
  53. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/animations.py +0 -0
  54. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/app.py +0 -0
  55. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/assistant_render.py +0 -0
  56. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/codenames.py +0 -0
  57. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
  58. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/env_utils.py +0 -0
  59. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/error_display.py +0 -0
  60. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/figures.py +0 -0
  61. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/fragment_utils.py +0 -0
  62. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/input_geometry.py +0 -0
  63. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
  64. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/logo.py +0 -0
  65. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/mention_completer.py +0 -0
  66. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/message_style.py +0 -0
  67. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/path_context_hint.py +0 -0
  68. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
  69. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
  70. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
  71. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
  72. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
  73. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
  74. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
  75. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
  76. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
  77. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
  78. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
  79. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
  80. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
  81. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/preflight.py +0 -0
  82. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/question_view.py +0 -0
  83. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/resume_picker.py +0 -0
  84. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/resume_preview.py +0 -0
  85. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/resume_selector.py +0 -0
  86. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
  87. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
  88. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/selection_menu.py +0 -0
  89. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/startup.py +0 -0
  90. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/status_bar.py +0 -0
  91. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/text_effects.py +0 -0
  92. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tips.py +0 -0
  93. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tool_result_store.py +0 -0
  94. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
  95. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tool_view.py +0 -0
  96. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
  97. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
  98. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
  99. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
  100. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
  101. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/docs/hooks.md +0 -0
  102. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/docs/superpowers/plans/2026-04-03-phrase-shuffle.md +0 -0
  103. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/docs/superpowers/specs/2026-04-01-conditional-diff-subtitle-design.md +0 -0
  104. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/docs/superpowers/specs/2026-04-03-phrase-shuffle-design.md +0 -0
  105. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/conftest.py +0 -0
  106. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
  107. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_animator_shuffle.py +0 -0
  108. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_app_mcp_preload.py +0 -0
  109. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_app_preflight_gate.py +0 -0
  110. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_app_print_mode.py +0 -0
  111. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_app_shutdown.py +0 -0
  112. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_app_usage_line.py +0 -0
  113. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_btw_slash_command.py +0 -0
  114. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_cli_project_root.py +0 -0
  115. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_compact_command_semantics.py +0 -0
  116. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_completion_context_activation.py +0 -0
  117. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_completion_status_panel.py +0 -0
  118. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_context_command.py +0 -0
  119. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_custom_slash_commands.py +0 -0
  120. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_discover_tab.py +0 -0
  121. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_errors_tab.py +0 -0
  122. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_event_renderer_boundary.py +0 -0
  123. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_event_renderer_e2e.py +0 -0
  124. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_event_renderer_log_boundary.py +0 -0
  125. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_event_renderer_log_queue.py +0 -0
  126. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_event_renderer_streaming.py +0 -0
  127. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_format_error.py +0 -0
  128. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_handle_error.py +0 -0
  129. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_history_printer_log.py +0 -0
  130. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_input_behavior.py +0 -0
  131. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_input_history.py +0 -0
  132. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_installed_tab.py +0 -0
  133. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_interrupt_exit_semantics.py +0 -0
  134. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_layout_coordinator.py +0 -0
  135. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_logo.py +0 -0
  136. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_main_args.py +0 -0
  137. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_marketplaces_tab.py +0 -0
  138. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_mcp_cli.py +0 -0
  139. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_mcp_slash_command.py +0 -0
  140. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_mention_completer.py +0 -0
  141. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_path_context_hint.py +0 -0
  142. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_plugin_tui_components.py +0 -0
  143. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_preflight.py +0 -0
  144. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_preflight_copilot.py +0 -0
  145. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_question_key_bindings.py +0 -0
  146. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_question_view.py +0 -0
  147. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_resume_picker.py +0 -0
  148. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_resume_preview.py +0 -0
  149. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_resume_selector.py +0 -0
  150. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_rewind_command_semantics.py +0 -0
  151. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_rpc_protocol.py +0 -0
  152. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_rpc_stdio_bridge.py +0 -0
  153. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_selection_menu.py +0 -0
  154. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_session_query_token_summary.py +0 -0
  155. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_shutdown_noise_guard.py +0 -0
  156. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_shutdown_noise_integration.py +0 -0
  157. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_slash_argument_hint.py +0 -0
  158. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_slash_completer.py +0 -0
  159. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_slash_registry.py +0 -0
  160. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_status_bar.py +0 -0
  161. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_status_bar_transient.py +0 -0
  162. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_task_panel_format.py +0 -0
  163. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_task_panel_key_bindings.py +0 -0
  164. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_task_panel_rendering.py +0 -0
  165. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_task_poll.py +0 -0
  166. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tool_result_store.py +0 -0
  167. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tool_result_viewer.py +0 -0
  168. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tool_result_viewer_key_bindings.py +0 -0
  169. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tool_view.py +0 -0
  170. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_elapsed_status.py +0 -0
  171. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_esc_queue.py +0 -0
  172. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_mcp_init_gate.py +0 -0
  173. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_queue_preview.py +0 -0
  174. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_split_invariance.py +0 -0
  175. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_team_messages.py +0 -0
  176. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
  177. {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_update_check.py +0 -0
@@ -0,0 +1,115 @@
1
+ ## global rule
2
+ 1. 涉及到 Prompt_toolkit 这个TUI框架, 你必须使用 context7 mcp里面的 query-docs和resolve-library-id 功能来查询相关文档, 以确保你对这个框架的理解是正确的. this is MUST
3
+ 2. 总是使用中文编写详细的git commit message
4
+
5
+ ## 12条黄金规则
6
+
7
+ 这些规则适用于本项目中的每一个任务,除非被明确覆盖。核心原则:非简单任务宁可谨慎,不要图快。简单任务可以灵活处理。
8
+
9
+ 1. 动手之前先动脑
10
+ 把你的假设说出来。不确定就问,别猜。遇到模糊需求时,列出多种可能的理解。如果有更简单的方案,要敢于提出来。搞不清楚的时候就停下来,把不清楚的点明确说出来。
11
+
12
+ 2. 简单至上
13
+ 写能解决问题的最少代码,不写投机性代码。不加需求以外的功能。一次性代码不搞抽象封装。自测标准:一个资深工程师看了会不会觉得过度设计?如果会,就简化。
14
+
15
+ 3. 精准修改,不多动
16
+ 只改必须改的地方。只清理自己弄乱的部分。不要顺手"优化"旁边的代码、注释或格式。没坏的东西不要重构。风格跟已有代码保持一致。
17
+
18
+ 4. 以目标驱动执行
19
+ 先定义成功标准,然后循环迭代直到验证通过。不要机械地按步骤走,而是盯着目标不断调整。有了清晰的成功标准,你才能自主地持续迭代。
20
+
21
+ 5. 模型只用于需要判断力的场景
22
+ 适合用 AI 做的事:分类、起草、总结、信息提取。不该用 AI 做的事:路由分发、重试逻辑、确定性转换。如果写代码就能搞定,就用代码搞定。
23
+
24
+ 6. 核实优先于推断(冰山法则)
25
+ 你在上下文中看到的信息只是冰山一角,不能代替真实验证。
26
+ 判断标准不是"我有没有说出猜测的话",而是"我这个结论背后有没有本轮刚验证过的事实支撑"——只要要陈述文件/函数/配置内容或修改代码,却没有刚读过、刚确认过,就是在猜,必须先验证再继续。
27
+
28
+ 7. 遇到冲突要选边,不要和稀泥
29
+ 两种模式互相矛盾时,选一个(优先选更新的或经过更多验证的),说明理由,把另一个标记为待清理。不要把两种冲突的模式混在一起用。
30
+
31
+ 8. 先读再写
32
+ 写新代码之前,先读清楚现有的导出接口、直接调用方、公共工具函数。"看起来互不相关"这种判断很危险。如果不理解代码为什么这样写,先问。
33
+
34
+ 9. 测试要验证意图,不只是行为
35
+ 测试必须体现某个行为**为什么**重要,而不仅仅是**做了什么**。如果业务逻辑变了,测试却不会挂,那这个测试就是有问题的。
36
+
37
+ 10. 每完成一个重要步骤就做检查点
38
+ 总结一下:做了什么、验证了什么、还剩什么。不要在一个自己都说不清楚的状态上继续推进。如果失去了方向感,停下来,重新陈述当前状态。
39
+
40
+ 11. 跟着项目的既有约定走,哪怕你不认同
41
+ 在项目内部,一致性 > 个人偏好。如果你真觉得某个约定有害,把问题提出来讨论,但不要悄悄另搞一套。
42
+
43
+ 12. 有问题就大声说
44
+ 如果有东西被悄悄跳过了,不能说"已完成"。如果有测试被跳过了,不能说"测试全过"。默认立场是把不确定性暴露出来,而不是藏起来。
45
+
46
+
47
+ ## python coding rule
48
+ 1. 代码必须使用 logging 模块进行日志记录,禁止使用 print 语句
49
+ 2. 运行 python必须使用 uv run python 脚本名.py 的方式运行
50
+ 3. 安装pip包必须使用 uv add 包名 的方式安装
51
+
52
+ ## Context 重建铁律
53
+ comate_agent_sdk 的上下文重建规则是高优先级架构约束。涉及 static header / session_state / conversation 三层分离、header_snapshot 的 first-snapshot-wins 语义、resume / fork / compact / clear_history 各自的不变量,以及最低测试护栏清单。
54
+ 任何改动涉及 chat_session.py、context/ir.py、context/compaction.py、agent/history.py、agent/init.py、agent/setup.py 时 MUST 先阅读完整规则。
55
+ → ./rules/context-rebuild.md
56
+
57
+ ## Token 账本语义铁律
58
+
59
+ comate_agent_sdk 的 token 相关概念分属不同账本,后续改动 MUST 先阅读完整规则,禁止为了修某个阈值或展示问题把不同账本混算。
60
+ → ./rules/token-accounting.md
61
+
62
+ ## TUI 编程铁律
63
+ 不做 History TUI,历史只写 scrollback。layout 只允许底部交互带。颜色策略以 shell 背景为准。包含 Team 事件显示铁律和 Pre-flight TUI 铁律。
64
+ 涉及 prompt_toolkit layout、team 事件渲染、独立 Application 弹窗时 MUST 先阅读完整规则。
65
+ → ./rules/tui.md
66
+
67
+ ## 代码放置规则
68
+
69
+ 当需要为某个模块添加新功能时,先判断:这个功能是该模块的 **核心职责** 还是 **业务扩展**?
70
+
71
+ - **核心职责**:直接操作该模块自身管理的数据结构 → 可以加方法
72
+ - **业务扩展**:借用该模块的 API 完成上层业务目标 → 在调用方实现,禁止侵入
73
+
74
+ 判断标准:如果删掉这个方法,该模块的核心抽象(数据结构 + 主流程)是否仍然完整?
75
+ 如果仍然完整,说明这个方法不属于这里。
76
+
77
+ 实践要求:
78
+ 1. 新增方法前,说明它属于核心职责还是业务扩展
79
+ 2. 如果是业务扩展,必须放在调用方或独立 service 中,通过公共 API 操作
80
+ 3. 单个模块超过 500 行时,主动审视是否有职责泄漏
81
+
82
+ ## Prompt 工程规则
83
+
84
+ 涉及 SDK 内置 prompt / 提示词时,后续 agent MUST 默认遵循以下约定,禁止重新回到“局部硬编码 + 局部替换”的分散实现。
85
+
86
+ - SDK 自带 prompt 资源 MUST 放在 `comate_agent_sdk/prompts/` 下,按领域分目录,例如 `agent/`、`subagent/`。
87
+ - 新增 prompt 时,优先判断它是 **SDK 静态资源** 还是 **调用方临时拼接文本**:
88
+ - SDK 静态资源:放入 `comate_agent_sdk/prompts/`,通过统一 loader 读取。
89
+ - 调用方临时拼接文本:保留在调用方,禁止为了“统一”硬塞进不相关模块。
90
+ - MUST 使用两阶段接口:
91
+ - `load_prompt()` 只负责读取包内 markdown 资源。
92
+ - `render_prompt()` 只负责变量渲染。
93
+ - 新 prompt 的变量语法 MUST 统一使用 `{{VAR_NAME}}`,不要新增 `{var}`、`%s`、手写 `replace()` 等新风格。
94
+
95
+ ## System Tool 描述工程规则
96
+
97
+ 涉及 SDK 内置 system tool 的 schema description / system prompt usage_rules / 默认上限值时,
98
+ 后续 agent MUST 默认遵循以下约定。
99
+
100
+ → ./rules/system-tool-prompt-conventions.md
101
+
102
+ ## Tool Envelope 与 Typed Schema 铁律
103
+
104
+ comate_agent_sdk 内置 system tool 的"信封 (`ok/err` 6-key envelope) + typed OutputSchema + `ToolMessage.raw_envelope` 三轨分离"是 typed `ToolResultEvent.output`、SDK 投影、resume 持久化、reminder 引擎共同依赖的事实契约。envelope 一旦被静默改形 / 截断 / 污染框架字段,会让 typed 事件、投影、reminder、jsonl resume **同时静默失效**。
105
+
106
+ 涉及以下任一项 MUST 先阅读完整规则,禁止仅"局部 patch":
107
+
108
+ - `comate_agent_sdk/system_tools/tool_result.py`、`tool_schemas/`(含 `_registry.py` / `_projection.py` / `_base.py` / 各 `<tool>.py`)、`system_tools/tools|task|team/` 下 system tool 的返回结构。
109
+ - `agent/tool_exec.py` 中 `_build_tool_result_message()` / `_extract_tool_envelope()`、`llm/messages.py` 中 `ToolMessage` 的 `content` / `raw_envelope` / `execution_meta` 字段、`mcp/manager.py` 中 `McpToolResult`。
110
+ - `agent/runner_engine/execution/tool_execution.py` 的 `_resolve_tool_output()`、`runner_engine/projection/tool_result_projection.py`、`agent/events.py` 中 `ToolResultEvent.output`、`context/reminder_engine.py`、`subagent/team_session.py`、`chat_session.py` resume 路径。
111
+ - size guard / truncation / artifact spill 与 envelope 关系的任何 plan。
112
+
113
+ 核心铁律一句话:**system tool 必须 `ok()/err()` envelope;envelope 形状不可破坏性变更;envelope 是 typed schema 唯一原料;envelope 只装业务字段,框架元数据走 `execution_meta`;envelope 严禁 slim / 入 prompt / 被 monkey-patch 注册;MCP 用 `McpToolResult.raw_result`、不混 `raw_envelope`;用户 `@tool` 永远是 `CustomToolOutput`。**
114
+
115
+ → ./rules/tool-envelope.md
@@ -13,6 +13,10 @@
13
13
 
14
14
  ### Added
15
15
 
16
+ - 新增 `/clear` slash command:清空当前 leader session 的 conversation,
17
+ 保留 session_id、team membership、inbox 与 TaskStore。成功后会清屏、
18
+ 重打 logo、重置 TUI transcript view,并刷新 status bar;busy 或
19
+ compacting 状态会拒绝执行。
16
20
  - `_ShutdownNoiseGuard` 扩展为四合一接管器:SIGINT、`sys.unraisablehook`、
17
21
  `sys.excepthook`、`warnings.showwarning`。捕获到的噪音统一路由到独立的
18
22
  `comate.noise` logger(`propagate=False`),写入 `~/.comate/logs/agent.log`。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comate-cli
3
- Version: 0.7.0a6
3
+ Version: 0.7.0a8
4
4
  Summary: Comate terminal CLI built on comate-agent-sdk
5
5
  Project-URL: Homepage, https://github.com/AndyLee1024/agent-sdk
6
6
  Project-URL: Repository, https://github.com/AndyLee1024/agent-sdk
@@ -59,6 +59,11 @@ from comate_cli.terminal_agent.tool_result_store import (
59
59
  ToolResultRegistry,
60
60
  )
61
61
  from comate_cli.terminal_agent.tool_view import summarize_tool_args, resolve_display_tool_name, should_show_tool_in_scrollback
62
+ from comate_cli.terminal_agent.tool_fold import (
63
+ ActiveToolFold,
64
+ ToolFoldSnapshot,
65
+ is_foldable_tool,
66
+ )
62
67
  from comate_cli.terminal_agent.env_utils import read_env_int
63
68
  from comate_cli.terminal_agent.custom_slash_commands import FILE_REF_PATTERN
64
69
  logger = logging.getLogger(__name__)
@@ -258,6 +263,7 @@ class EventRenderer:
258
263
  self._history: list[HistoryEntry] = []
259
264
  self._running_tools: dict[str, _RunningTool] = {}
260
265
  self._tool_call_args: dict[str, dict[str, Any]] = {}
266
+ self._active_tool_fold = ActiveToolFold()
261
267
  self._tool_results = tool_results
262
268
  self._fallback_tool_result_sequence = 0
263
269
  self._assistant_buffer = ""
@@ -358,6 +364,21 @@ class EventRenderer:
358
364
  if entry.entry_type == "tool_result":
359
365
  self.flush_pending_logs()
360
366
 
367
+ def _commit_active_tool_fold(self) -> None:
368
+ snapshot = self._active_tool_fold.snapshot(active=False)
369
+ if snapshot is None:
370
+ return
371
+ self._append_history_entry(
372
+ HistoryEntry(
373
+ entry_type="tool_fold",
374
+ text=snapshot.summary,
375
+ severity="error" if snapshot.any_error else "info",
376
+ subtitle=snapshot.error_summary or snapshot.latest_hint,
377
+ tool_call_ids=snapshot.tool_call_ids,
378
+ )
379
+ )
380
+ self._active_tool_fold.clear()
381
+
361
382
  # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
362
383
  # spec §6 流式新管道方法集(Phase 1-6 新增;Phase 7 接入 handle_event)
363
384
  # 容器检测原语(spec §6.3)
@@ -649,6 +670,7 @@ class EventRenderer:
649
670
  if not normalized:
650
671
  return
651
672
  self._flush_assistant_segment()
673
+ self._commit_active_tool_fold()
652
674
  if display_header and display_subtitle:
653
675
  # AskUserQuestion 答复入 scrollback:用 tool_result 样式渲染头行(●),
654
676
  # 答案明细作为 ⎿ subtitle 紧贴其下;行间不插入用户 prefix。
@@ -698,6 +720,7 @@ class EventRenderer:
698
720
  # spec §6.4:turn 结束 force flush 所有 pending(held / container /
699
721
  # text_pending / thinking)保证 scrollback 看到完整内容
700
722
  self._force_flush_all()
723
+ self._commit_active_tool_fold()
701
724
  self._flush_assistant_segment()
702
725
  self.flush_pending_logs()
703
726
  self._pending_tool_starts.clear()
@@ -720,6 +743,7 @@ class EventRenderer:
720
743
  )
721
744
  self._running_tools.clear()
722
745
  self._flush_assistant_segment()
746
+ self._commit_active_tool_fold()
723
747
  self._pending_tool_starts.clear()
724
748
  # spec §6.4:用户中断 → 5 个新字段全部丢弃不入队
725
749
  self._drop_all_pending()
@@ -728,12 +752,16 @@ class EventRenderer:
728
752
  def history_entries(self) -> list[HistoryEntry]:
729
753
  return list(self._history)
730
754
 
755
+ def active_tool_fold_snapshot(self) -> ToolFoldSnapshot | None:
756
+ return self._active_tool_fold.snapshot(active=True)
757
+
731
758
  def reset_history_view(self) -> None:
732
759
  """重置 history 视图状态(用于会话切换后的重新加载)。"""
733
760
  self._history = []
734
761
  self._recent_team_event_keys.clear()
735
762
  self._running_tools.clear()
736
763
  self._tool_call_args.clear()
764
+ self._active_tool_fold.clear()
737
765
  self._assistant_buffer = ""
738
766
  self._pending_tool_starts.clear()
739
767
  self._turn_received_text_delta = False
@@ -1343,6 +1371,7 @@ class EventRenderer:
1343
1371
  pass
1344
1372
  case TextDeltaEvent(delta=delta, message_id=message_id):
1345
1373
  if delta:
1374
+ self._commit_active_tool_fold()
1346
1375
  normalized_message_id = str(message_id or "").strip()
1347
1376
  if normalized_message_id and normalized_message_id != self._active_text_message_id:
1348
1377
  self._active_text_message_id = normalized_message_id
@@ -1382,40 +1411,50 @@ class EventRenderer:
1382
1411
  attempted=attempted,
1383
1412
  compacted=compacted,
1384
1413
  reason=reason,
1385
- tokens_before=tokens_before,
1414
+ tokens_before=_,
1386
1415
  tokens_after=tokens_after,
1387
1416
  ):
1388
1417
  self.clear_auto_compacting()
1389
1418
  pct = int(tokens / threshold * 100) if threshold > 0 else 0
1419
+ # precheck 的 tokens 是 estimate_precheck() 推算值;
1420
+ # check 的 tokens 是上一次 API 返回的真实 context_usage。
1421
+ trigger_label = "est." if trigger == "precheck" else "usage"
1390
1422
  if compacted:
1423
+ reduction_pct = (
1424
+ max(0, int(((tokens - tokens_after) / tokens) * 100))
1425
+ if tokens > 0
1426
+ else 0
1427
+ )
1391
1428
  self.append_system_message(
1392
1429
  (
1393
- f"Context compaction completed ({trigger}): "
1394
- f"{tokens_before:,} {INJECTED_ARROW} {tokens_after:,} tokens "
1395
- f"(check {tokens:,}/{threshold:,}, {pct}%)"
1430
+ f"Auto-compact done ({trigger}): "
1431
+ f"{trigger_label} {tokens:,} / threshold {threshold:,} ({pct}%)\n"
1432
+ f" {INJECTED_ARROW} context reduced to ~{tokens_after:,} tokens "
1433
+ f"(-{reduction_pct}%)"
1396
1434
  ),
1397
1435
  severity="info",
1398
1436
  )
1399
1437
  elif attempted:
1400
1438
  is_recoverable_deferred = str(reason).startswith("partial_compact_no_op:")
1401
1439
  label = (
1402
- "Context compaction deferred"
1440
+ "Auto-compact deferred"
1403
1441
  if is_recoverable_deferred
1404
- else "Context compaction attempt failed"
1442
+ else "Auto-compact attempt failed"
1405
1443
  )
1406
1444
  self.append_system_message(
1407
1445
  (
1408
1446
  f"{label} ({trigger}): "
1409
- f"kept {tokens_after:,} tokens "
1410
- f"(check {tokens:,}/{threshold:,}, {pct}%, reason={reason})"
1447
+ f"{trigger_label} {tokens:,} / threshold {threshold:,} ({pct}%), "
1448
+ f"reason={reason}"
1411
1449
  ),
1412
1450
  severity="warning",
1413
1451
  )
1414
1452
  else:
1415
1453
  self.append_system_message(
1416
1454
  (
1417
- f"Context compaction skipped ({trigger}): "
1418
- f"{tokens:,}/{threshold:,} tokens ({pct}%), reason={reason}"
1455
+ f"Auto-compact skipped ({trigger}): "
1456
+ f"{trigger_label} {tokens:,} / threshold {threshold:,} ({pct}%), "
1457
+ f"reason={reason}"
1419
1458
  ),
1420
1459
  severity="warning",
1421
1460
  )
@@ -1423,12 +1462,46 @@ class EventRenderer:
1423
1462
  args_dict = arguments if isinstance(arguments, dict) else {"_raw": str(arguments)}
1424
1463
  # Store args for ToolResult phase lookup.
1425
1464
  self._tool_call_args[tool_call_id] = args_dict
1465
+ if is_foldable_tool(tool_name):
1466
+ self._active_tool_fold.add_call(
1467
+ tool_call_id=tool_call_id,
1468
+ tool_name=tool_name,
1469
+ args=args_dict,
1470
+ started_at_monotonic=time.monotonic(),
1471
+ )
1472
+ self._rebuild_loading_line()
1473
+ return (False, None)
1474
+ self._commit_active_tool_fold()
1426
1475
  if not should_show_tool_in_scrollback(tool_name, args_dict):
1427
1476
  self._rebuild_loading_line()
1428
1477
  return (False, None)
1429
1478
  self._append_tool_call(tool_name, args_dict, tool_call_id)
1430
1479
  case ToolResultEvent(tool=tool_name, result=result, tool_call_id=tool_call_id, is_error=is_error, metadata=metadata, output=output):
1431
1480
  stored_args = self._tool_call_args.pop(tool_call_id, {})
1481
+ if self._active_tool_fold.has_call(tool_call_id):
1482
+ display_name = resolve_display_tool_name(tool_name, stored_args)
1483
+ rec = self._make_tool_result_record(
1484
+ tool_call_id=tool_call_id,
1485
+ tool_name=tool_name,
1486
+ display_name=display_name,
1487
+ started_at_monotonic=time.monotonic(),
1488
+ args=stored_args,
1489
+ is_error=is_error,
1490
+ result=result,
1491
+ metadata=metadata,
1492
+ output=output,
1493
+ )
1494
+ error_summary = None
1495
+ if is_error:
1496
+ error_summary = str(result).strip().splitlines()[0] if str(result).strip() else None
1497
+ self._active_tool_fold.mark_result(
1498
+ tool_call_id,
1499
+ is_error=is_error,
1500
+ result_record_sequence=rec.sequence,
1501
+ error_summary=error_summary,
1502
+ )
1503
+ self._rebuild_loading_line()
1504
+ return (False, None)
1432
1505
  if not should_show_tool_in_scrollback(tool_name, stored_args, is_result=True, is_error=is_error):
1433
1506
  self._running_tools.pop(tool_call_id, None)
1434
1507
  self._rebuild_loading_line()
@@ -1532,6 +1605,7 @@ class EventRenderer:
1532
1605
  if self._turn_received_text_delta:
1533
1606
  pass
1534
1607
  elif text:
1608
+ self._commit_active_tool_fold()
1535
1609
  self._append_assistant_text(text)
1536
1610
  case StepCompleteEvent(step_id=step_id, status=_, duration_ms=_):
1537
1611
  # Cancellation/error paths may emit StepCompleteEvent without ToolResultEvent.
@@ -1539,6 +1613,7 @@ class EventRenderer:
1539
1613
  self._running_tools.pop(step_id, None)
1540
1614
  case StopEvent(reason=reason):
1541
1615
  self._flush_assistant_segment()
1616
+ self._commit_active_tool_fold()
1542
1617
  self._pending_tool_starts.clear()
1543
1618
  # Safety net: if any running tool rows remain, stop event means this turn is ending.
1544
1619
  self._running_tools.clear()
@@ -107,20 +107,40 @@ def _entry_content(
107
107
  entry: HistoryEntry,
108
108
  *,
109
109
  terminal_width: int,
110
- render_markdown_to_plain: Callable[..., str],
111
- ) -> str:
110
+ render_markdown_to_plain: Callable[..., str | Text],
111
+ ) -> str | Text:
112
112
  if entry.entry_type != "assistant":
113
113
  return str(entry.text)
114
114
  width = max(terminal_width - 6, 40)
115
115
  return render_markdown_to_plain(str(entry.text), width=width)
116
116
 
117
117
 
118
+ def _render_prefixed_rich_text(
119
+ content: Text,
120
+ *,
121
+ prefix: str,
122
+ in_run_continuation: bool,
123
+ ) -> Text:
124
+ content_lines = content.split("\n") or [Text("")]
125
+ line_text = Text()
126
+ if in_run_continuation:
127
+ line_text.append(" ", style="bold")
128
+ else:
129
+ line_text.append(f"{prefix} ", style="bold")
130
+ line_text.append_text(content_lines[0])
131
+ for line in content_lines[1:]:
132
+ line_text.append("\n")
133
+ line_text.append(" ")
134
+ line_text.append_text(line)
135
+ return line_text
136
+
137
+
118
138
  def render_history_group(
119
139
  console: Console,
120
140
  entries: list[HistoryEntry],
121
141
  *,
122
142
  terminal_width: int,
123
- render_markdown_to_plain: Callable[..., str],
143
+ render_markdown_to_plain: Callable[..., str | Text],
124
144
  prev_was_assistant: bool = False,
125
145
  prev_was_thinking: bool = False,
126
146
  assume_run_continues_at_tail: bool = False,
@@ -240,6 +260,33 @@ def render_history_group(
240
260
  subtitle_chain_active = True
241
261
  continue
242
262
 
263
+ if entry.entry_type == "tool_fold":
264
+ # 折叠组摘要:参考 thinking 的视觉处理(2 空格缩进 + dim 文案),
265
+ # 不带前缀圆点,行末追加 "(ctrl+o to expand)" 提示 Ctrl+O 展开。
266
+ is_error = entry.severity == "error"
267
+ content = str(entry.text).strip()
268
+ content_lines = [line for line in content.splitlines() if line.strip()] or [content]
269
+ suffix = " (ctrl+o to expand)" if entry.tool_call_ids else ""
270
+ text_style = "dim #FF6B6B" if is_error else "dim"
271
+
272
+ line_text = Text()
273
+ line_text.append(" ", style=text_style)
274
+ line_text.append(content_lines[0], style=text_style)
275
+ if suffix and len(content_lines) == 1:
276
+ line_text.append(suffix, style=text_style)
277
+ for idx_line, line in enumerate(content_lines[1:], start=1):
278
+ line_text.append("\n")
279
+ line_text.append(" ", style=text_style)
280
+ line_text.append(line, style=text_style)
281
+ if suffix and idx_line == len(content_lines) - 1:
282
+ line_text.append(suffix, style=text_style)
283
+
284
+ renderables.append(line_text)
285
+ renderables.append(Text(""))
286
+ prev_was_assistant = False
287
+ subtitle_chain_active = False
288
+ continue
289
+
243
290
  if entry.entry_type == "tool_result":
244
291
  prefix_char = BLACK_CIRCLE
245
292
  prefix_style = "bold red" if entry.severity == "error" else "bold green"
@@ -253,10 +300,20 @@ def render_history_group(
253
300
  first_line.append(f"{prefix_char} ", style=prefix_style)
254
301
  first_line.append_text(text_lines[0] if text_lines else Text())
255
302
  renderables.append(first_line)
303
+
304
+ has_body = len(text_lines) > 1
305
+ # For multi-line Rich Text bodies (Edit/Write inline diff),
306
+ # subtitle ("Added X, removed Y") goes between signature and
307
+ # body so the reader sees the summary before the diff. For
308
+ # single-line text (bash/grep/read previews), subtitle stays
309
+ # after the signature line as before.
310
+ if has_body and entry.subtitle:
311
+ renderables.append(_render_subtitle_line(entry.subtitle, error=is_error))
256
312
  for line in text_lines[1:]:
257
313
  renderables.append(line)
258
- if entry.subtitle:
314
+ if not has_body and entry.subtitle:
259
315
  renderables.append(_render_subtitle_line(entry.subtitle, error=is_error))
316
+
260
317
  renderables.append(Text(""))
261
318
  prev_was_assistant = False
262
319
  subtitle_chain_active = bool(entry.subtitle)
@@ -297,10 +354,10 @@ def render_history_group(
297
354
  style = "bold #FF6B6B" if entry.severity == "error" else "#E8B830"
298
355
  content_lines = content.splitlines() or [""]
299
356
  line_text = Text()
300
- line_text.append(content_lines[0], style=style)
357
+ line_text.append(f" {content_lines[0]}", style=style)
301
358
  for line in content_lines[1:]:
302
359
  line_text.append("\n")
303
- line_text.append(line, style=style)
360
+ line_text.append(f" {line}", style=style)
304
361
  renderables.append(line_text)
305
362
 
306
363
  subtitle_chain_active = False
@@ -324,16 +381,25 @@ def render_history_group(
324
381
  terminal_width=terminal_width,
325
382
  render_markdown_to_plain=render_markdown_to_plain,
326
383
  )
327
- content_lines = content.splitlines() or [""]
328
- if in_run_continuation:
329
- first_line = f" {content_lines[0]}"
384
+ if isinstance(content, Text):
385
+ renderables.append(
386
+ _render_prefixed_rich_text(
387
+ content,
388
+ prefix=_entry_prefix(entry),
389
+ in_run_continuation=in_run_continuation,
390
+ )
391
+ )
330
392
  else:
331
- prefix = _entry_prefix(entry)
332
- first_line = f"{prefix} {content_lines[0]}"
333
- lines = [first_line]
334
- for line in content_lines[1:]:
335
- lines.append(f" {line}")
336
- renderables.append("\n".join(lines))
393
+ content_lines = content.splitlines() or [""]
394
+ if in_run_continuation:
395
+ first_line = f" {content_lines[0]}"
396
+ else:
397
+ prefix = _entry_prefix(entry)
398
+ first_line = f"{prefix} {content_lines[0]}"
399
+ lines = [first_line]
400
+ for line in content_lines[1:]:
401
+ lines.append(f" {line}")
402
+ renderables.append("\n".join(lines))
337
403
 
338
404
  # 在 assistant run 内部不插入空行;只有 run 末尾才与下一段拉开。
339
405
  # assume_run_continues_at_tail=True 时(drain 在 streaming 中),把 batch
@@ -357,8 +423,11 @@ def render_history_group(
357
423
 
358
424
 
359
425
  async def print_history_group_async(console: Console, group: Group) -> None:
360
- console.print(group, soft_wrap=True)
426
+ # highlight=False:Rich 默认 ReprHighlighter 会把 "/cmd" 误识别为 path/filename
427
+ # 并染成 magenta/bright-magenta(即"粉红色 slash 命令名")。所有 entry 在
428
+ # render_history_group 内已自带 explicit Text 样式,不需要再做自动高亮。
429
+ console.print(group, soft_wrap=True, highlight=False)
361
430
 
362
431
 
363
432
  def print_history_group_sync(console: Console, group: Group) -> None:
364
- console.print(group, soft_wrap=True)
433
+ console.print(group, soft_wrap=True, highlight=False)
@@ -26,6 +26,11 @@ _LOG_LEVEL_ENV_VAR = "COMATE_LOG_LEVEL"
26
26
  _VALID_LOG_LEVELS: tuple[str, ...] = ("DEBUG", "INFO", "WARNING", "ERROR")
27
27
  _DEFAULT_LOG_LEVEL = logging.INFO
28
28
  _invalid_log_level_warned = False
29
+ _MEMORY_EXTRACTION_ALLOWED_TOOLS = frozenset(
30
+ {"Bash", "Edit", "Glob", "Grep", "Read", "Write"}
31
+ )
32
+ _ALLOWED_TOOLS_DENIAL_PREFIX = "工具执行被拒绝(allowed_tools):"
33
+ _AVAILABLE_TOOLS_MARKER = "可用工具:"
29
34
 
30
35
 
31
36
  def _should_drop_recent_duplicate_log(msg_key: str) -> bool:
@@ -44,6 +49,25 @@ def _should_drop_recent_duplicate_log(msg_key: str) -> bool:
44
49
  return previous is not None
45
50
 
46
51
 
52
+ def _should_suppress_tui_log(record: logging.LogRecord) -> bool:
53
+ if record.name == "comate_agent_sdk.agent.memory":
54
+ return True
55
+
56
+ msg = record.getMessage()
57
+ if record.name != "comate_agent_sdk.agent":
58
+ return False
59
+ if _ALLOWED_TOOLS_DENIAL_PREFIX not in msg or _AVAILABLE_TOOLS_MARKER not in msg:
60
+ return False
61
+
62
+ available_text = msg.split(_AVAILABLE_TOOLS_MARKER, 1)[1]
63
+ available_tools = frozenset(
64
+ part.strip()
65
+ for part in available_text.split(",")
66
+ if part.strip()
67
+ )
68
+ return available_tools == _MEMORY_EXTRACTION_ALLOWED_TOOLS
69
+
70
+
47
71
  class TUILoggingHandler(logging.Handler):
48
72
  """自定义 logging handler,将日志友好地显示在 TUI 中
49
73
 
@@ -69,6 +93,8 @@ class TUILoggingHandler(logging.Handler):
69
93
  msg = self._format_message(record)
70
94
  if not msg:
71
95
  return
96
+ if _should_suppress_tui_log(record):
97
+ return
72
98
 
73
99
  # 首次显示检查
74
100
  msg_key = self._get_message_key(record)