comate-cli 0.7.8__tar.gz → 0.8.0__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 (227) hide show
  1. {comate_cli-0.7.8 → comate_cli-0.8.0}/PKG-INFO +1 -1
  2. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/main.py +113 -11
  3. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/animations.py +27 -1
  4. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/app.py +82 -28
  5. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/event_renderer.py +77 -26
  6. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/figures.py +1 -1
  7. comate_cli-0.8.0/comate_cli/terminal_agent/file_ref_hint.py +47 -0
  8. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/history_printer.py +44 -3
  9. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/logging_adapter.py +14 -0
  10. comate_cli-0.8.0/comate_cli/terminal_agent/logo.py +246 -0
  11. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/markdown_render.py +36 -1
  12. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +17 -15
  13. comate_cli-0.8.0/comate_cli/terminal_agent/plugins/operation_state.py +145 -0
  14. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/plugin_picker.py +65 -6
  15. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +143 -40
  16. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +306 -47
  17. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +280 -40
  18. comate_cli-0.8.0/comate_cli/terminal_agent/print_mode.py +224 -0
  19. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/selection_menu.py +56 -0
  20. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/slash_commands.py +8 -7
  21. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/text_effects.py +21 -0
  22. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tool_result_formatters.py +62 -36
  23. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tool_view.py +31 -57
  24. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui.py +522 -66
  25. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/commands.py +149 -11
  26. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/history_sync.py +11 -0
  27. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/input_behavior.py +42 -10
  28. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/key_bindings.py +46 -3
  29. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/render_panels.py +260 -84
  30. comate_cli-0.8.0/dist-comate/.gitignore +1 -0
  31. comate_cli-0.8.0/dist-comate/comate_cli-0.7.10-py3-none-any.whl +0 -0
  32. comate_cli-0.8.0/dist-comate/comate_cli-0.7.10.tar.gz +0 -0
  33. comate_cli-0.8.0/dist-hico/.gitignore +1 -0
  34. comate_cli-0.8.0/dist-hico/hico_cli-0.7.17-py3-none-any.whl +0 -0
  35. comate_cli-0.8.0/dist-hico/hico_cli-0.7.17.tar.gz +0 -0
  36. {comate_cli-0.7.8 → comate_cli-0.8.0}/pyproject.toml +1 -1
  37. comate_cli-0.8.0/tests/test_app_print_mode.py +375 -0
  38. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_app_shutdown.py +81 -0
  39. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_app_startup_latency.py +82 -2
  40. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_app_token_cost_config.py +68 -0
  41. comate_cli-0.8.0/tests/test_app_usage_line.py +156 -0
  42. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_completion_context_activation.py +21 -0
  43. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_completion_status_panel.py +83 -10
  44. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_discover_tab.py +117 -22
  45. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_event_renderer.py +220 -5
  46. comate_cli-0.8.0/tests/test_file_ref_hint.py +65 -0
  47. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_history_printer.py +53 -0
  48. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_history_printer_subtitle_position.py +38 -0
  49. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_installed_tab.py +252 -17
  50. comate_cli-0.8.0/tests/test_interrupt_exit_semantics.py +880 -0
  51. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_logging_adapter.py +67 -0
  52. comate_cli-0.8.0/tests/test_logo.py +219 -0
  53. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_main_args.py +122 -11
  54. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_markdown_render.py +17 -0
  55. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_marketplaces_tab.py +248 -3
  56. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_mcp_slash_command.py +282 -0
  57. comate_cli-0.8.0/tests/test_model_switch_command.py +91 -0
  58. comate_cli-0.8.0/tests/test_plugin_operation_state.py +80 -0
  59. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_plugin_slash_commands.py +13 -3
  60. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_plugin_tui_components.py +232 -21
  61. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_rewind_command_semantics.py +56 -0
  62. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_skills_slash_command.py +60 -5
  63. comate_cli-0.8.0/tests/test_slash_completer.py +129 -0
  64. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_task_panel_rendering.py +13 -0
  65. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tool_fold_panel.py +38 -4
  66. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tool_result_formatters.py +50 -0
  67. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tool_view.py +39 -0
  68. comate_cli-0.8.0/tests/test_tui_local_interactive_barrier.py +111 -0
  69. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_paste_placeholder.py +66 -50
  70. comate_cli-0.8.0/tests/test_tui_queue_preview.py +467 -0
  71. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_queue_sdk_source.py +242 -23
  72. comate_cli-0.8.0/tests/test_tui_scheduled_fire_log.py +86 -0
  73. comate_cli-0.8.0/tests/test_tui_send_undo_window.py +534 -0
  74. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_startup_latency.py +24 -0
  75. comate_cli-0.8.0/tests/test_tui_thinking_display.py +128 -0
  76. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_tool_result_registry_lifecycle.py +1 -1
  77. comate_cli-0.8.0/tests/test_wrap_user_text.py +112 -0
  78. comate_cli-0.8.0/tools/logo_lab.py +310 -0
  79. {comate_cli-0.7.8 → comate_cli-0.8.0}/uv.lock +2 -2
  80. comate_cli-0.7.8/comate_cli/terminal_agent/logo.py +0 -93
  81. comate_cli-0.7.8/tests/test_app_print_mode.py +0 -99
  82. comate_cli-0.7.8/tests/test_app_usage_line.py +0 -60
  83. comate_cli-0.7.8/tests/test_interrupt_exit_semantics.py +0 -474
  84. comate_cli-0.7.8/tests/test_logo.py +0 -66
  85. comate_cli-0.7.8/tests/test_slash_completer.py +0 -68
  86. comate_cli-0.7.8/tests/test_tui_queue_preview.py +0 -151
  87. comate_cli-0.7.8/tests/test_tui_thinking_display.py +0 -71
  88. {comate_cli-0.7.8 → comate_cli-0.8.0}/.gitignore +0 -0
  89. {comate_cli-0.7.8 → comate_cli-0.8.0}/CHANGELOG.md +0 -0
  90. {comate_cli-0.7.8 → comate_cli-0.8.0}/README.md +0 -0
  91. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/__init__.py +0 -0
  92. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/__main__.py +0 -0
  93. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/mcp_cli.py +0 -0
  94. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/__init__.py +0 -0
  95. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/assistant_render.py +0 -0
  96. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/codenames.py +0 -0
  97. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/config/__init__.py +0 -0
  98. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/config/model.py +0 -0
  99. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/config/picker.py +0 -0
  100. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/config/picker_state.py +0 -0
  101. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/config/store.py +0 -0
  102. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
  103. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/env_utils.py +0 -0
  104. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/error_display.py +0 -0
  105. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/fragment_utils.py +0 -0
  106. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
  107. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/input_geometry.py +0 -0
  108. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
  109. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/mention_completer.py +0 -0
  110. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/message_style.py +0 -0
  111. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/models.py +0 -0
  112. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/path_context_hint.py +0 -0
  113. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
  114. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
  115. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
  116. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
  117. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
  118. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
  119. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
  120. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
  121. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/preflight.py +0 -0
  122. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/preflight_wizard.py +0 -0
  123. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/question_view.py +0 -0
  124. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/resume_picker.py +0 -0
  125. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/resume_preview.py +0 -0
  126. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/resume_selector.py +0 -0
  127. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
  128. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
  129. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/startup.py +0 -0
  130. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/startup_profile.py +0 -0
  131. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/status_bar.py +0 -0
  132. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
  133. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/statusline/model.py +0 -0
  134. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/statusline/picker.py +0 -0
  135. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
  136. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/statusline/store.py +0 -0
  137. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tips.py +0 -0
  138. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tool_fold.py +0 -0
  139. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tool_result_store.py +0 -0
  140. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
  141. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
  142. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
  143. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
  144. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
  145. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
  146. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
  147. {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/update_check.py +0 -0
  148. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/config/__init__.py +0 -0
  149. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/config/test_model.py +0 -0
  150. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/config/test_picker_state.py +0 -0
  151. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/config/test_picker_ui.py +0 -0
  152. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/config/test_roundtrip.py +0 -0
  153. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/config/test_store_load.py +0 -0
  154. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/config/test_store_save.py +0 -0
  155. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/conftest.py +0 -0
  156. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
  157. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/statusline/__init__.py +0 -0
  158. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/statusline/test_model.py +0 -0
  159. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/statusline/test_picker_state.py +0 -0
  160. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/statusline/test_store.py +0 -0
  161. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_animator_shuffle.py +0 -0
  162. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_app_mcp_preload.py +0 -0
  163. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_app_preflight_gate.py +0 -0
  164. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_btw_slash_command.py +0 -0
  165. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_cli_project_root.py +0 -0
  166. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_compact_command_semantics.py +0 -0
  167. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_context_command.py +0 -0
  168. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_custom_slash_commands.py +0 -0
  169. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_errors_tab.py +0 -0
  170. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_event_renderer_boundary.py +0 -0
  171. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_event_renderer_e2e.py +0 -0
  172. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_event_renderer_log_boundary.py +0 -0
  173. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_event_renderer_log_queue.py +0 -0
  174. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_event_renderer_streaming.py +0 -0
  175. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_event_renderer_tool_fold.py +0 -0
  176. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_format_error.py +0 -0
  177. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_goal_resume_tui.py +0 -0
  178. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_goal_resume_view.py +0 -0
  179. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_goal_slash_command.py +0 -0
  180. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_handle_error.py +0 -0
  181. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_history_printer_log.py +0 -0
  182. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_history_printer_tool_fold.py +0 -0
  183. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_history_sync.py +0 -0
  184. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_history_sync_tool_fold.py +0 -0
  185. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_input_behavior.py +0 -0
  186. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_input_history.py +0 -0
  187. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_layout_coordinator.py +0 -0
  188. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_mcp_cli.py +0 -0
  189. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_mention_completer.py +0 -0
  190. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_path_context_hint.py +0 -0
  191. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_preflight.py +0 -0
  192. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_preflight_copilot.py +0 -0
  193. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_question_key_bindings.py +0 -0
  194. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_question_view.py +0 -0
  195. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_resume_picker.py +0 -0
  196. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_resume_preview.py +0 -0
  197. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_resume_selector.py +0 -0
  198. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_rpc_protocol.py +0 -0
  199. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_rpc_stdio_bridge.py +0 -0
  200. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_selection_menu.py +0 -0
  201. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_session_query_token_summary.py +0 -0
  202. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_shutdown_noise_guard.py +0 -0
  203. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_shutdown_noise_integration.py +0 -0
  204. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_slash_argument_hint.py +0 -0
  205. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_slash_clear.py +0 -0
  206. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_slash_registry.py +0 -0
  207. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_startup_import_budget.py +0 -0
  208. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_startup_profile.py +0 -0
  209. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_status_bar.py +0 -0
  210. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_status_bar_transient.py +0 -0
  211. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_task_panel_format.py +0 -0
  212. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_task_panel_key_bindings.py +0 -0
  213. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_task_poll.py +0 -0
  214. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tool_fold.py +0 -0
  215. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tool_result_store.py +0 -0
  216. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tool_result_viewer.py +0 -0
  217. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tool_result_viewer_key_bindings.py +0 -0
  218. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_transcript_viewer.py +0 -0
  219. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_transcript_viewer_tool_fold.py +0 -0
  220. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_elapsed_status.py +0 -0
  221. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_esc_queue.py +0 -0
  222. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_mcp_init_gate.py +0 -0
  223. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_paste_newline_guard.py +0 -0
  224. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_split_invariance.py +0 -0
  225. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_team_messages.py +0 -0
  226. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_update_check.py +0 -0
  227. {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_usage_command.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comate-cli
3
- Version: 0.7.8
3
+ Version: 0.8.0
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
@@ -26,6 +26,15 @@ class _ArgumentError(ValueError):
26
26
  """Raised when CLI arguments are invalid."""
27
27
 
28
28
 
29
+ PRINT_STDIN_IDLE_TIMEOUT_SECONDS = 3.0
30
+ PRINT_OUTPUT_FORMAT_TEXT = "text"
31
+ PRINT_OUTPUT_FORMAT_JSON = "json"
32
+ PRINT_OUTPUT_FORMATS = frozenset({PRINT_OUTPUT_FORMAT_TEXT, PRINT_OUTPUT_FORMAT_JSON})
33
+ PRINT_OUTPUT_FORMAT_UNSUPPORTED = frozenset({"stream-json"})
34
+
35
+ ParsedArgs = tuple[bool, str | None, bool, str | None, str]
36
+
37
+
29
38
  class _TerminalStateGuard:
30
39
  """Best-effort tty restore guard for abnormal shutdown paths."""
31
40
 
@@ -200,37 +209,68 @@ def _usage_text() -> str:
200
209
  return (
201
210
  "Usage:\n"
202
211
  " comate [--rpc-stdio]\n"
203
- " comate -p <prompt>\n"
212
+ " comate -p [--output-format text|json] <prompt>\n"
204
213
  " comate resume [<session_id>] [--rpc-stdio]\n"
205
214
  " comate mcp <subcommand> [options]"
206
215
  )
207
216
 
208
217
 
209
- def _parse_args(argv: list[str]) -> tuple[bool, str | None, bool, str | None]:
218
+ def _parse_args(argv: list[str]) -> ParsedArgs:
210
219
  rpc_stdio = False
211
220
  print_mode = False
221
+ output_format = PRINT_OUTPUT_FORMAT_TEXT
222
+ output_format_explicit = False
212
223
  positionals: list[str] = []
213
- for arg in argv:
224
+
225
+ index = 0
226
+ while index < len(argv):
227
+ arg = argv[index]
214
228
  if arg == "--rpc-stdio":
215
229
  rpc_stdio = True
230
+ index += 1
216
231
  continue
217
232
  if arg in ("-p", "--print"):
218
233
  print_mode = True
234
+ index += 1
235
+ continue
236
+ if arg == "--output-format" or arg.startswith("--output-format="):
237
+ output_format_explicit = True
238
+ if arg == "--output-format":
239
+ index += 1
240
+ if index >= len(argv):
241
+ raise _ArgumentError("--output-format requires one of: text, json")
242
+ value = argv[index]
243
+ else:
244
+ value = arg.partition("=")[2]
245
+ if not value:
246
+ raise _ArgumentError("--output-format requires one of: text, json")
247
+ if value in PRINT_OUTPUT_FORMAT_UNSUPPORTED:
248
+ raise _ArgumentError(
249
+ "--output-format stream-json is not supported in this release"
250
+ )
251
+ if value not in PRINT_OUTPUT_FORMATS:
252
+ raise _ArgumentError(f"Unsupported --output-format: {value}")
253
+ output_format = value
254
+ index += 1
219
255
  continue
220
256
  if arg.startswith("-"):
221
257
  raise _ArgumentError(f"Unknown option: {arg}")
222
258
  positionals.append(arg)
259
+ index += 1
260
+
261
+ if output_format_explicit and not print_mode:
262
+ raise _ArgumentError("--output-format can only be used with -p/--print")
223
263
 
224
264
  # -p mode: all positionals become the prompt
225
265
  if print_mode:
226
266
  if rpc_stdio:
227
267
  raise _ArgumentError("-p/--print and --rpc-stdio are mutually exclusive")
228
268
  print_prompt = " ".join(positionals) if positionals else ""
229
- return rpc_stdio, None, False, print_prompt
269
+ return rpc_stdio, None, False, print_prompt, output_format
230
270
 
231
271
  # Non -p mode: original logic
232
272
  if not positionals:
233
- return rpc_stdio, None, False, None
273
+ return rpc_stdio, None, False, None, output_format
234
274
 
235
275
  command = positionals[0]
236
276
  if command != "resume":
@@ -241,14 +281,75 @@ def _parse_args(argv: list[str]) -> tuple[bool, str | None, bool, str | None]:
241
281
  raise _ArgumentError(
242
282
  "resume without <session_id> does not support --rpc-stdio"
243
283
  )
244
- return rpc_stdio, None, True, None
284
+ return rpc_stdio, None, True, None, output_format
245
285
 
246
286
  if len(positionals) == 2:
247
- return rpc_stdio, positionals[1], False, None
287
+ return rpc_stdio, positionals[1], False, None, output_format
248
288
 
249
289
  raise _ArgumentError("resume accepts at most one <session_id>")
250
290
 
251
291
 
292
+ def _read_stdin_posix_idle_drain(
293
+ stdin: object,
294
+ *,
295
+ timeout_seconds: float,
296
+ chunk_size: int = 64 * 1024,
297
+ ) -> str:
298
+ import select
299
+
300
+ fileno = getattr(stdin, "fileno", None)
301
+ if not callable(fileno):
302
+ read = getattr(stdin, "read", None)
303
+ return str(read()) if callable(read) else ""
304
+
305
+ try:
306
+ fd = fileno()
307
+ except (AttributeError, OSError, ValueError):
308
+ read = getattr(stdin, "read", None)
309
+ return str(read()) if callable(read) else ""
310
+
311
+ chunks: list[bytes] = []
312
+ while True:
313
+ readable, _, _ = select.select([fd], [], [], timeout_seconds)
314
+ if not readable:
315
+ break
316
+ try:
317
+ chunk = os.read(fd, chunk_size)
318
+ except BlockingIOError:
319
+ break
320
+ except OSError:
321
+ break
322
+ if not chunk:
323
+ break
324
+ chunks.append(chunk)
325
+
326
+ encoding = str(getattr(stdin, "encoding", "") or "utf-8")
327
+ return b"".join(chunks).decode(encoding, errors="replace")
328
+
329
+
330
+ def _read_stdin_if_ready(
331
+ stdin: object,
332
+ *,
333
+ timeout_seconds: float = PRINT_STDIN_IDLE_TIMEOUT_SECONDS,
334
+ ) -> str:
335
+ isatty = getattr(stdin, "isatty", None)
336
+ if callable(isatty) and isatty():
337
+ return ""
338
+
339
+ if os.name != "posix":
340
+ read = getattr(stdin, "read", None)
341
+ return str(read()) if callable(read) else ""
342
+
343
+ try:
344
+ return _read_stdin_posix_idle_drain(
345
+ stdin,
346
+ timeout_seconds=timeout_seconds,
347
+ )
348
+ except (OSError, ValueError):
349
+ read = getattr(stdin, "read", None)
350
+ return str(read()) if callable(read) else ""
351
+
352
+
252
353
  def main(argv: list[str] | None = None) -> None:
253
354
  run_argv = list(argv) if argv is not None else sys.argv[1:]
254
355
 
@@ -278,7 +379,9 @@ def main(argv: list[str] | None = None) -> None:
278
379
  _app_import_done_perf = time.perf_counter()
279
380
 
280
381
  try:
281
- rpc_stdio, resume_session_id, resume_select, print_prompt = _parse_args(run_argv)
382
+ rpc_stdio, resume_session_id, resume_select, print_prompt, print_output_format = (
383
+ _parse_args(run_argv)
384
+ )
282
385
  except _ArgumentError as exc:
283
386
  sys.stderr.write(f"{exc}\n{_usage_text()}\n")
284
387
  raise SystemExit(2) from exc
@@ -286,9 +389,7 @@ def main(argv: list[str] | None = None) -> None:
286
389
  # Assemble print mode message
287
390
  print_message: str | None = None
288
391
  if print_prompt is not None:
289
- stdin_text = ""
290
- if not sys.stdin.isatty():
291
- stdin_text = sys.stdin.read()
392
+ stdin_text = _read_stdin_if_ready(sys.stdin)
292
393
  parts = [p for p in (stdin_text.strip(), print_prompt.strip()) if p]
293
394
  if not parts:
294
395
  sys.stderr.write("Error: -p requires a prompt argument or stdin input\n")
@@ -302,6 +403,7 @@ def main(argv: list[str] | None = None) -> None:
302
403
  resume_session_id=resume_session_id,
303
404
  resume_select=resume_select,
304
405
  print_message=print_message,
406
+ print_output_format=print_output_format,
305
407
  process_start_perf=_PROCESS_START_PERF,
306
408
  app_import_start_perf=_app_import_start_perf,
307
409
  app_import_done_perf=_app_import_done_perf,
@@ -254,7 +254,8 @@ LOADING_SPINNER_FRAMES: tuple[str, ...] = (
254
254
  HIDDEN_THINKING_BADGES: tuple[str, ...] = (
255
255
  "ultra thinking",
256
256
  "deep thinking",
257
- "extended thinking"
257
+ "extended thinking",
258
+ "thinking some more with max effort"
258
259
  )
259
260
 
260
261
 
@@ -395,6 +396,24 @@ class SubmissionAnimator:
395
396
  self._is_active = False
396
397
  self._dirty = True
397
398
 
399
+ def hide_now(self) -> None:
400
+ task = self._task
401
+ if task is not None and not task.done():
402
+ task.cancel()
403
+
404
+ def _consume_cancelled(done_task: asyncio.Task[None]) -> None:
405
+ try:
406
+ done_task.result()
407
+ except asyncio.CancelledError:
408
+ return
409
+
410
+ task.add_done_callback(_consume_cancelled)
411
+ self._task = None
412
+ self._stop_event = None
413
+ self._status_hint = None
414
+ self._is_active = False
415
+ self._dirty = True
416
+
398
417
  @property
399
418
  def is_active(self) -> bool:
400
419
  return self._is_active
@@ -474,6 +493,13 @@ class StreamAnimationController:
474
493
  async def shutdown(self) -> None:
475
494
  await self._stop_if_needed(AnimationPhase.DONE)
476
495
 
496
+ def hide_now(self) -> None:
497
+ self._active_tool_call_ids.clear()
498
+ self._animator.set_status_hint(None)
499
+ self._animator.hide_now()
500
+ self._stopped = True
501
+ self._phase = AnimationPhase.DONE
502
+
477
503
  async def on_event(self, event: object) -> None:
478
504
  if self._stopped:
479
505
  return
@@ -5,7 +5,6 @@ import logging
5
5
  import os
6
6
  import random # noqa: F401 - tests patch this module-level import for deterministic startup tips.
7
7
  import signal
8
- import sys
9
8
  import threading
10
9
  import time
11
10
  from collections.abc import Iterator
@@ -20,13 +19,30 @@ from comate_cli.terminal_agent.startup_profile import StartupProfiler
20
19
  if TYPE_CHECKING:
21
20
  from comate_agent_sdk.agent.chat_session import ChatSession
22
21
  from comate_agent_sdk.agent.core.template import AgentTemplate as Agent
23
- from comate_agent_sdk.agent.events import TextEvent
24
22
  from comate_cli.terminal_agent.event_renderer import EventRenderer
25
23
  from comate_cli.terminal_agent.update_check import UpdateInfo
26
24
 
27
25
  console = Console()
28
26
  logger = logging.getLogger(__name__)
29
27
  _UPDATE_CHECK_DELAY_S = 1.0
28
+ PRINT_MODE_DISALLOWED_TOOLS = (
29
+ "TeamCreate",
30
+ "TeamDelete",
31
+ "SendMessage",
32
+ "YieldTurn",
33
+ )
34
+
35
+
36
+ def _merge_disallowed_tools(*groups: tuple[str, ...] | None) -> tuple[str, ...] | None:
37
+ merged: list[str] = []
38
+ seen: set[str] = set()
39
+ for group in groups:
40
+ for name in group or ():
41
+ if name in seen:
42
+ continue
43
+ seen.add(name)
44
+ merged.append(name)
45
+ return tuple(merged) or None
30
46
 
31
47
 
32
48
  def _resolve_cli_project_root() -> Path:
@@ -221,6 +237,18 @@ def _import_preflight_runner():
221
237
  return run_preflight_if_needed
222
238
 
223
239
 
240
+ def _import_print_mode_runner():
241
+ from comate_cli.terminal_agent.print_mode import run_print_mode
242
+
243
+ return run_print_mode
244
+
245
+
246
+ def _import_print_mode_json_error_writer():
247
+ from comate_cli.terminal_agent.print_mode import write_json_error_result
248
+
249
+ return write_json_error_result
250
+
251
+
224
252
  def print_logo(*args, **kwargs) -> None:
225
253
  from comate_cli.terminal_agent.logo import print_logo as _print_logo
226
254
 
@@ -357,6 +385,7 @@ def _build_agent(
357
385
  *,
358
386
  project_root: Path | None = None,
359
387
  profiler: StartupProfiler | None = None,
388
+ extra_disallowed_tools: tuple[str, ...] | None = None,
360
389
  ) -> Agent:
361
390
  Agent, AgentConfig, CompactionConfig, EnvOptions = _import_sdk_agent_components(profiler)
362
391
  from comate_cli.terminal_agent.config import store
@@ -367,6 +396,7 @@ def _build_agent(
367
396
  snapshot = store.load()
368
397
  if profiler is not None:
369
398
  profiler.mark("agent.config.load.done")
399
+ disallowed_tools = _merge_disallowed_tools(extra_disallowed_tools)
370
400
 
371
401
  agent_config = AgentConfig(
372
402
  role="software_engineering",
@@ -374,11 +404,13 @@ def _build_agent(
374
404
  env_options=EnvOptions(system_env=True, git_env=True),
375
405
  use_streaming_task=True,
376
406
  include_cost=bool(getattr(snapshot, "token_cost_enabled", False)),
407
+ strict_hook_settings=False,
377
408
  memory_background_enabled=snapshot.memory_background_enabled,
378
409
  memory_dreaming_enabled=snapshot.memory_dreaming_enabled,
379
410
  compaction=CompactionConfig(
380
411
  threshold_ratio=snapshot.compaction_threshold_ratio,
381
412
  ),
413
+ disallowed_tools=disallowed_tools,
382
414
  )
383
415
  if profiler is not None:
384
416
  profiler.mark("agent.construct.start")
@@ -425,11 +457,10 @@ def _format_exit_usage_line(usage: object) -> str:
425
457
  input_tokens = max(total_prompt_tokens - total_prompt_cached_tokens, 0)
426
458
  total_tokens = input_tokens + total_completion_tokens
427
459
 
428
- reasoning_part = f" (reasoning {total_reasoning_tokens:,})" if total_reasoning_tokens > 0 else ""
429
460
  return (
430
461
  f"Token usage: total={total_tokens:,} "
431
462
  f"input={input_tokens:,} (+ {total_prompt_cached_tokens:,} cached) "
432
- f"output={total_completion_tokens:,}{reasoning_part}"
463
+ f"output={total_completion_tokens:,} (reasoning {total_reasoning_tokens:,})"
433
464
  )
434
465
 
435
466
 
@@ -502,28 +533,17 @@ async def _run_print_mode(
502
533
  message: str,
503
534
  *,
504
535
  project_root: Path,
536
+ output_format: str = "text",
505
537
  ) -> None:
506
- ChatSession, TextEvent = _import_print_mode_components()
507
- session = ChatSession(agent, cwd=project_root, persistent=False)
508
- try:
509
- await _preload_mcp_in_tui(session)
510
-
511
- final_text = ""
512
- async for event in session.query_stream(message):
513
- if isinstance(event, TextEvent):
514
- final_text = event.content
515
-
516
- if final_text:
517
- sys.stdout.write(final_text)
518
- if not final_text.endswith("\n"):
519
- sys.stdout.write("\n")
520
- sys.stdout.flush()
521
- except Exception as exc:
522
- logger.error(f"Print mode failed: {exc}", exc_info=True)
523
- sys.stderr.write(f"Error: {exc}\n")
524
- raise SystemExit(1) from exc
525
- finally:
526
- await _graceful_shutdown(session)
538
+ run_print_mode = _import_print_mode_runner()
539
+ await run_print_mode(
540
+ agent,
541
+ message,
542
+ project_root=project_root,
543
+ output_format=output_format,
544
+ preload_mcp=_preload_mcp_in_tui,
545
+ graceful_shutdown=_graceful_shutdown,
546
+ )
527
547
 
528
548
 
529
549
  def _install_event_loop_exception_handler() -> None:
@@ -557,6 +577,7 @@ async def run(
557
577
  resume_session_id: str | None = None,
558
578
  resume_select: bool = False,
559
579
  print_message: str | None = None,
580
+ print_output_format: str = "text",
560
581
  process_start_perf: float | None = None,
561
582
  app_import_start_perf: float | None = None,
562
583
  app_import_done_perf: float | None = None,
@@ -576,14 +597,25 @@ async def run(
576
597
  profiler.mark("cli_project_root.done")
577
598
  profiler.mark("preflight.start")
578
599
  run_preflight_if_needed = _import_preflight_runner()
600
+ preflight_console = console
601
+ if print_message is not None and print_output_format == "json":
602
+ preflight_console = Console(stderr=True)
579
603
  preflight_result = await run_preflight_if_needed(
580
- console=console,
604
+ console=preflight_console,
581
605
  project_root=project_root,
582
606
  interactive=not rpc_stdio and print_message is None,
583
607
  )
584
608
  profiler.mark("preflight.done")
585
609
  if preflight_result.should_abort_launch:
586
610
  if print_message is not None:
611
+ if print_output_format == "json":
612
+ write_json_error_result = _import_print_mode_json_error_writer()
613
+ detail = preflight_result.detail or preflight_result.status
614
+ write_json_error_result(
615
+ error=f"Preflight failed: {detail}",
616
+ stop_reason=None,
617
+ session_id=None,
618
+ )
587
619
  raise SystemExit(1)
588
620
  return
589
621
 
@@ -604,7 +636,13 @@ async def run(
604
636
  return
605
637
 
606
638
  profiler.mark("agent.build.start")
607
- agent = _build_agent(project_root=project_root, profiler=profiler)
639
+ agent = _build_agent(
640
+ project_root=project_root,
641
+ profiler=profiler,
642
+ extra_disallowed_tools=(
643
+ PRINT_MODE_DISALLOWED_TOOLS if print_message is not None else None
644
+ ),
645
+ )
608
646
  profiler.mark("agent.build.done")
609
647
 
610
648
  if rpc_stdio and resume_select and not resume_session_id:
@@ -628,7 +666,12 @@ async def run(
628
666
 
629
667
  # --- Print mode branch ---
630
668
  if print_message is not None:
631
- await _run_print_mode(agent, print_message, project_root=project_root)
669
+ await _run_print_mode(
670
+ agent,
671
+ print_message,
672
+ project_root=project_root,
673
+ output_format=print_output_format,
674
+ )
632
675
  return
633
676
 
634
677
  if resume_select and not resume_session_id:
@@ -718,6 +761,17 @@ async def run(
718
761
  )
719
762
  finally:
720
763
  try:
764
+ tui_exit_source = (
765
+ getattr(tui, "exit_source", None)
766
+ or getattr(tui, "_exit_source", None)
767
+ or "unknown"
768
+ )
769
+ logger.info(
770
+ "TUI branch leaving: exit_source=%s session_id=%s active_session_id=%s",
771
+ tui_exit_source,
772
+ getattr(session, "session_id", None),
773
+ getattr(active_session, "session_id", None),
774
+ )
721
775
  if update_check_task is not None:
722
776
  await _cancel_background_task(
723
777
  update_check_task,
@@ -14,6 +14,11 @@ from comate_agent_sdk.agent.events import (
14
14
  CompactionResultEvent,
15
15
  CompactionStartedEvent,
16
16
  PlanApprovalRequiredEvent,
17
+ ScheduledPromptCreatedEvent,
18
+ ScheduledPromptDeletedEvent,
19
+ ScheduledPromptFailedEvent,
20
+ ScheduledPromptFiredEvent,
21
+ ScheduledPromptMissedEvent,
17
22
  SessionInitEvent,
18
23
  StepCompleteEvent,
19
24
  StopEvent,
@@ -67,14 +72,13 @@ from comate_cli.terminal_agent.tool_fold import (
67
72
  is_foldable_tool,
68
73
  )
69
74
  from comate_cli.terminal_agent.env_utils import read_env_int
70
- from comate_cli.terminal_agent.custom_slash_commands import FILE_REF_PATTERN
75
+ from comate_cli.terminal_agent.file_ref_hint import build_file_ref_hints
71
76
  logger = logging.getLogger(__name__)
72
77
 
73
78
  _DEFAULT_TOOL_ERROR_SUMMARY_MAX_LEN = 160
74
79
  _DEFAULT_TOOL_PANEL_MAX_LINES = 4
75
80
  _DEFAULT_TASK_PANEL_MAX_LINES = 6
76
81
  _RECENT_TEAM_EVENT_CACHE_SIZE = 128
77
- _FILE_REF_MAX_COUNT_BYTES = 10 * 1024 * 1024 # 10 MB
78
82
  _SYSTEM_MESSAGE_DEDUPE_WINDOW_SECONDS = 0.5
79
83
 
80
84
 
@@ -84,6 +88,25 @@ def _truncate(content: str, max_len: int = 120) -> str:
84
88
  return f"{content[:max_len]}..."
85
89
 
86
90
 
91
+ def _scheduled_scope(durable: bool) -> str:
92
+ return "durable" if durable else "session-only"
93
+
94
+
95
+ def _scheduled_failure_reason(reason: str) -> str:
96
+ text = " ".join(str(reason or "").split())
97
+ if not text:
98
+ return "unknown"
99
+ lowered = text.lower()
100
+ if "prompt" in lowered and not re.fullmatch(r"[A-Za-z0-9_.:/-]+", text):
101
+ colon = text.find(":")
102
+ if colon > 0:
103
+ text = text[:colon].strip()
104
+ else:
105
+ prompt_at = lowered.find("prompt")
106
+ text = text[:prompt_at].rstrip(": -") or "hidden"
107
+ return _truncate(text, 160)
108
+
109
+
87
110
  def _format_duration(seconds: float) -> str:
88
111
  elapsed = max(seconds, 0.0)
89
112
  if elapsed < 60:
@@ -697,32 +720,14 @@ class EventRenderer:
697
720
  self._maybe_append_file_ref_hint(normalized)
698
721
  self.flush_pending_logs()
699
722
 
723
+ def compute_file_ref_hints(self, text: str) -> list[str]:
724
+ """计算 text 中 @path 引用的展示提示行(scrollback 与 staging 共用单一事实源)。"""
725
+ return build_file_ref_hints(text, self._project_root)
726
+
700
727
  def _maybe_append_file_ref_hint(self, text: str) -> None:
701
728
  """Append dim ⎿ hints for valid @path references."""
702
- if self._project_root is None:
703
- return
704
- for match in FILE_REF_PATTERN.finditer(text):
705
- raw_path = match.group(1)
706
- candidate = self._project_root / raw_path
707
- try:
708
- if candidate.is_dir():
709
- self._append_history_entry(
710
- HistoryEntry(entry_type="file_ref", text=f"Listed directory {raw_path}")
711
- )
712
- continue
713
- if candidate.is_file():
714
- hint = f"Read {raw_path}"
715
- if candidate.stat().st_size <= _FILE_REF_MAX_COUNT_BYTES:
716
- try:
717
- with open(candidate, "rb") as fh:
718
- line_count = sum(1 for _ in fh)
719
- hint += f" ({line_count} lines)"
720
- except OSError:
721
- pass
722
- self._append_history_entry(HistoryEntry(entry_type="file_ref", text=hint))
723
- continue
724
- except OSError:
725
- continue
729
+ for hint in self.compute_file_ref_hints(text):
730
+ self._append_history_entry(HistoryEntry(entry_type="file_ref", text=hint))
726
731
 
727
732
  def close(self) -> None:
728
733
  return
@@ -1540,6 +1545,52 @@ class EventRenderer:
1540
1545
  output=output,
1541
1546
  args=stored_args,
1542
1547
  )
1548
+ case ScheduledPromptCreatedEvent(
1549
+ job_id=job_id,
1550
+ cron=_,
1551
+ human_schedule=human_schedule,
1552
+ recurring=_,
1553
+ durable=durable,
1554
+ ):
1555
+ self.append_system_message(
1556
+ (
1557
+ f"Scheduled task created: job={job_id} "
1558
+ f"schedule={human_schedule} scope={_scheduled_scope(durable)}"
1559
+ ),
1560
+ severity="info",
1561
+ )
1562
+ case ScheduledPromptDeletedEvent(job_id=job_id):
1563
+ self.append_system_message(
1564
+ f"Scheduled task cancelled: job={job_id}",
1565
+ severity="info",
1566
+ )
1567
+ case ScheduledPromptFiredEvent(
1568
+ job_id=_,
1569
+ cron=_,
1570
+ human_schedule=_,
1571
+ recurring=_,
1572
+ durable=_,
1573
+ local_time=_,
1574
+ ):
1575
+ # Fired means the scheduler materialized pending work; the TUI uses it as a
1576
+ # transient loading signal and should not persist every recurring tick.
1577
+ pass
1578
+ case ScheduledPromptMissedEvent(
1579
+ job_id=job_id,
1580
+ human_schedule=human_schedule,
1581
+ ):
1582
+ self.append_system_message(
1583
+ (
1584
+ "Missed scheduled task requires confirmation: "
1585
+ f"job={job_id} schedule={human_schedule}"
1586
+ ),
1587
+ severity="warning",
1588
+ )
1589
+ case ScheduledPromptFailedEvent(job_id=job_id, reason=reason):
1590
+ self.append_system_message(
1591
+ f"Scheduled task failed: job={job_id} reason={_scheduled_failure_reason(reason)}",
1592
+ severity="error",
1593
+ )
1543
1594
  case UsageDeltaEvent(
1544
1595
  source=_,
1545
1596
  model=_,
@@ -95,7 +95,7 @@ TASK_PENDING = "▢" # ▢
95
95
  TASK_BLOCKED = "▫" # ▫
96
96
 
97
97
  # Mode 指示 & 中断
98
- FAST_PLAY_ICON = "⏵" # ⏵ — Act mode(成对使用 ⏵⏵)
98
+ FAST_PLAY_ICON = "⏵" # ⏵ — YOLO mode(成对使用 ⏵⏵)
99
99
  STOP_ICON = "⏹" # ⏹
100
100
 
101
101
  # 警告 & 省略
@@ -0,0 +1,47 @@
1
+ """@路径引用的展示提示行计算。
2
+
3
+ scrollback(event_renderer)与 staging 预览(render_panels)共用这一份逻辑,
4
+ 保证用户在 staging 区看到的 ``Read x (N lines)`` / ``Listed directory x`` 提示
5
+ 与刷入 scrollback 后逐字一致——这是「不让用户感知 staging 区存在」的关键。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ from comate_cli.terminal_agent.custom_slash_commands import FILE_REF_PATTERN
13
+
14
+ # 超过该体积的文件不再逐行统计,避免大文件计数拖慢渲染。
15
+ _FILE_REF_MAX_COUNT_BYTES = 10 * 1024 * 1024 # 10 MB
16
+
17
+
18
+ def build_file_ref_hints(text: str, project_root: Path | None) -> list[str]:
19
+ """扫描 ``text`` 中的 ``@path`` 引用,返回展示用提示行列表。
20
+
21
+ 每条形如 ``Read <path> (<N> lines)`` 或 ``Listed directory <path>``;
22
+ 无效 / 不存在 / 不可访问的引用会被跳过。``project_root`` 为 ``None`` 时返回空列表。
23
+ """
24
+ if project_root is None:
25
+ return []
26
+ hints: list[str] = []
27
+ for match in FILE_REF_PATTERN.finditer(text):
28
+ raw_path = match.group(1)
29
+ candidate = project_root / raw_path
30
+ try:
31
+ if candidate.is_dir():
32
+ hints.append(f"Listed directory {raw_path}")
33
+ continue
34
+ if candidate.is_file():
35
+ hint = f"Read {raw_path}"
36
+ if candidate.stat().st_size <= _FILE_REF_MAX_COUNT_BYTES:
37
+ try:
38
+ with open(candidate, "rb") as fh:
39
+ line_count = sum(1 for _ in fh)
40
+ hint += f" ({line_count} lines)"
41
+ except OSError:
42
+ pass
43
+ hints.append(hint)
44
+ continue
45
+ except OSError:
46
+ continue
47
+ return hints