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.
- {comate_cli-0.7.8 → comate_cli-0.8.0}/PKG-INFO +1 -1
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/main.py +113 -11
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/animations.py +27 -1
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/app.py +82 -28
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/event_renderer.py +77 -26
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/figures.py +1 -1
- comate_cli-0.8.0/comate_cli/terminal_agent/file_ref_hint.py +47 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/history_printer.py +44 -3
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/logging_adapter.py +14 -0
- comate_cli-0.8.0/comate_cli/terminal_agent/logo.py +246 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/markdown_render.py +36 -1
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +17 -15
- comate_cli-0.8.0/comate_cli/terminal_agent/plugins/operation_state.py +145 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/plugin_picker.py +65 -6
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +143 -40
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +306 -47
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +280 -40
- comate_cli-0.8.0/comate_cli/terminal_agent/print_mode.py +224 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/selection_menu.py +56 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/slash_commands.py +8 -7
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/text_effects.py +21 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tool_result_formatters.py +62 -36
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tool_view.py +31 -57
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui.py +522 -66
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/commands.py +149 -11
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/history_sync.py +11 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/input_behavior.py +42 -10
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/key_bindings.py +46 -3
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/render_panels.py +260 -84
- comate_cli-0.8.0/dist-comate/.gitignore +1 -0
- comate_cli-0.8.0/dist-comate/comate_cli-0.7.10-py3-none-any.whl +0 -0
- comate_cli-0.8.0/dist-comate/comate_cli-0.7.10.tar.gz +0 -0
- comate_cli-0.8.0/dist-hico/.gitignore +1 -0
- comate_cli-0.8.0/dist-hico/hico_cli-0.7.17-py3-none-any.whl +0 -0
- comate_cli-0.8.0/dist-hico/hico_cli-0.7.17.tar.gz +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/pyproject.toml +1 -1
- comate_cli-0.8.0/tests/test_app_print_mode.py +375 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_app_shutdown.py +81 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_app_startup_latency.py +82 -2
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_app_token_cost_config.py +68 -0
- comate_cli-0.8.0/tests/test_app_usage_line.py +156 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_completion_context_activation.py +21 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_completion_status_panel.py +83 -10
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_discover_tab.py +117 -22
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_event_renderer.py +220 -5
- comate_cli-0.8.0/tests/test_file_ref_hint.py +65 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_history_printer.py +53 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_history_printer_subtitle_position.py +38 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_installed_tab.py +252 -17
- comate_cli-0.8.0/tests/test_interrupt_exit_semantics.py +880 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_logging_adapter.py +67 -0
- comate_cli-0.8.0/tests/test_logo.py +219 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_main_args.py +122 -11
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_markdown_render.py +17 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_marketplaces_tab.py +248 -3
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_mcp_slash_command.py +282 -0
- comate_cli-0.8.0/tests/test_model_switch_command.py +91 -0
- comate_cli-0.8.0/tests/test_plugin_operation_state.py +80 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_plugin_slash_commands.py +13 -3
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_plugin_tui_components.py +232 -21
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_rewind_command_semantics.py +56 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_skills_slash_command.py +60 -5
- comate_cli-0.8.0/tests/test_slash_completer.py +129 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_task_panel_rendering.py +13 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tool_fold_panel.py +38 -4
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tool_result_formatters.py +50 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tool_view.py +39 -0
- comate_cli-0.8.0/tests/test_tui_local_interactive_barrier.py +111 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_paste_placeholder.py +66 -50
- comate_cli-0.8.0/tests/test_tui_queue_preview.py +467 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_queue_sdk_source.py +242 -23
- comate_cli-0.8.0/tests/test_tui_scheduled_fire_log.py +86 -0
- comate_cli-0.8.0/tests/test_tui_send_undo_window.py +534 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_startup_latency.py +24 -0
- comate_cli-0.8.0/tests/test_tui_thinking_display.py +128 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_tool_result_registry_lifecycle.py +1 -1
- comate_cli-0.8.0/tests/test_wrap_user_text.py +112 -0
- comate_cli-0.8.0/tools/logo_lab.py +310 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/uv.lock +2 -2
- comate_cli-0.7.8/comate_cli/terminal_agent/logo.py +0 -93
- comate_cli-0.7.8/tests/test_app_print_mode.py +0 -99
- comate_cli-0.7.8/tests/test_app_usage_line.py +0 -60
- comate_cli-0.7.8/tests/test_interrupt_exit_semantics.py +0 -474
- comate_cli-0.7.8/tests/test_logo.py +0 -66
- comate_cli-0.7.8/tests/test_slash_completer.py +0 -68
- comate_cli-0.7.8/tests/test_tui_queue_preview.py +0 -151
- comate_cli-0.7.8/tests/test_tui_thinking_display.py +0 -71
- {comate_cli-0.7.8 → comate_cli-0.8.0}/.gitignore +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/CHANGELOG.md +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/README.md +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/__main__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/config/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/config/model.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/config/picker.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/config/picker_state.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/config/store.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/preflight_wizard.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/startup_profile.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/status_bar.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/statusline/model.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/statusline/picker.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/statusline/store.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tool_fold.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tool_result_store.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/comate_cli/terminal_agent/update_check.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/config/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/config/test_model.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/config/test_picker_state.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/config/test_picker_ui.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/config/test_roundtrip.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/config/test_store_load.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/config/test_store_save.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/conftest.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/statusline/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/statusline/test_model.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/statusline/test_picker_state.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/statusline/test_store.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_context_command.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_event_renderer_streaming.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_event_renderer_tool_fold.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_format_error.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_goal_resume_tui.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_goal_resume_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_goal_slash_command.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_handle_error.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_history_printer_log.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_history_printer_tool_fold.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_history_sync.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_history_sync_tool_fold.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_input_history.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_preflight.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_question_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_resume_picker.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_slash_clear.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_startup_import_budget.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_startup_profile.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_status_bar.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_task_poll.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tool_fold.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_transcript_viewer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_transcript_viewer_tool_fold.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_paste_newline_guard.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_update_check.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.8.0}/tests/test_usage_command.py +0 -0
|
@@ -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]) ->
|
|
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
|
-
|
|
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 =
|
|
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:,}{
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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=
|
|
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(
|
|
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(
|
|
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.
|
|
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
|
-
|
|
703
|
-
|
|
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=_,
|
|
@@ -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
|