comate-cli 0.7.8__tar.gz → 0.7.9__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.7.9}/PKG-INFO +1 -1
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/main.py +113 -11
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/app.py +70 -26
- comate_cli-0.7.9/comate_cli/terminal_agent/print_mode.py +224 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/pyproject.toml +1 -1
- comate_cli-0.7.9/tests/test_app_print_mode.py +375 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_app_startup_latency.py +82 -2
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_app_token_cost_config.py +68 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_main_args.py +122 -11
- comate_cli-0.7.8/tests/test_app_print_mode.py +0 -99
- {comate_cli-0.7.8 → comate_cli-0.7.9}/.gitignore +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/CHANGELOG.md +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/README.md +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/__main__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/config/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/config/model.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/config/picker.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/config/picker_state.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/config/store.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/event_renderer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/figures.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/history_printer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/logging_adapter.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/preflight_wizard.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/slash_commands.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/startup_profile.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/status_bar.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/statusline/model.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/statusline/picker.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/statusline/store.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tool_fold.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tool_result_store.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/comate_cli/terminal_agent/update_check.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/config/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/config/test_model.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/config/test_picker_state.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/config/test_picker_ui.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/config/test_roundtrip.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/config/test_store_load.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/config/test_store_save.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/conftest.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/statusline/__init__.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/statusline/test_model.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/statusline/test_picker_state.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/statusline/test_store.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_completion_status_panel.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_context_command.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_event_renderer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_event_renderer_streaming.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_event_renderer_tool_fold.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_format_error.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_goal_resume_tui.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_goal_resume_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_goal_slash_command.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_handle_error.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_history_printer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_history_printer_log.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_history_printer_subtitle_position.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_history_printer_tool_fold.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_history_sync.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_history_sync_tool_fold.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_input_history.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_logging_adapter.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_logo.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_markdown_render.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_plugin_slash_commands.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_preflight.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_question_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_resume_picker.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_skills_slash_command.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_slash_clear.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_startup_import_budget.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_startup_profile.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_status_bar.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_task_poll.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tool_fold.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tool_fold_panel.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tool_result_formatters.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tool_view.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_transcript_viewer.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_transcript_viewer_tool_fold.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_paste_newline_guard.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_queue_preview.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_queue_sdk_source.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_startup_latency.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_thinking_display.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_update_check.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/tests/test_usage_command.py +0 -0
- {comate_cli-0.7.8 → comate_cli-0.7.9}/uv.lock +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,
|
|
@@ -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")
|
|
@@ -502,28 +534,17 @@ async def _run_print_mode(
|
|
|
502
534
|
message: str,
|
|
503
535
|
*,
|
|
504
536
|
project_root: Path,
|
|
537
|
+
output_format: str = "text",
|
|
505
538
|
) -> 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)
|
|
539
|
+
run_print_mode = _import_print_mode_runner()
|
|
540
|
+
await run_print_mode(
|
|
541
|
+
agent,
|
|
542
|
+
message,
|
|
543
|
+
project_root=project_root,
|
|
544
|
+
output_format=output_format,
|
|
545
|
+
preload_mcp=_preload_mcp_in_tui,
|
|
546
|
+
graceful_shutdown=_graceful_shutdown,
|
|
547
|
+
)
|
|
527
548
|
|
|
528
549
|
|
|
529
550
|
def _install_event_loop_exception_handler() -> None:
|
|
@@ -557,6 +578,7 @@ async def run(
|
|
|
557
578
|
resume_session_id: str | None = None,
|
|
558
579
|
resume_select: bool = False,
|
|
559
580
|
print_message: str | None = None,
|
|
581
|
+
print_output_format: str = "text",
|
|
560
582
|
process_start_perf: float | None = None,
|
|
561
583
|
app_import_start_perf: float | None = None,
|
|
562
584
|
app_import_done_perf: float | None = None,
|
|
@@ -576,14 +598,25 @@ async def run(
|
|
|
576
598
|
profiler.mark("cli_project_root.done")
|
|
577
599
|
profiler.mark("preflight.start")
|
|
578
600
|
run_preflight_if_needed = _import_preflight_runner()
|
|
601
|
+
preflight_console = console
|
|
602
|
+
if print_message is not None and print_output_format == "json":
|
|
603
|
+
preflight_console = Console(stderr=True)
|
|
579
604
|
preflight_result = await run_preflight_if_needed(
|
|
580
|
-
console=
|
|
605
|
+
console=preflight_console,
|
|
581
606
|
project_root=project_root,
|
|
582
607
|
interactive=not rpc_stdio and print_message is None,
|
|
583
608
|
)
|
|
584
609
|
profiler.mark("preflight.done")
|
|
585
610
|
if preflight_result.should_abort_launch:
|
|
586
611
|
if print_message is not None:
|
|
612
|
+
if print_output_format == "json":
|
|
613
|
+
write_json_error_result = _import_print_mode_json_error_writer()
|
|
614
|
+
detail = preflight_result.detail or preflight_result.status
|
|
615
|
+
write_json_error_result(
|
|
616
|
+
error=f"Preflight failed: {detail}",
|
|
617
|
+
stop_reason=None,
|
|
618
|
+
session_id=None,
|
|
619
|
+
)
|
|
587
620
|
raise SystemExit(1)
|
|
588
621
|
return
|
|
589
622
|
|
|
@@ -604,7 +637,13 @@ async def run(
|
|
|
604
637
|
return
|
|
605
638
|
|
|
606
639
|
profiler.mark("agent.build.start")
|
|
607
|
-
agent = _build_agent(
|
|
640
|
+
agent = _build_agent(
|
|
641
|
+
project_root=project_root,
|
|
642
|
+
profiler=profiler,
|
|
643
|
+
extra_disallowed_tools=(
|
|
644
|
+
PRINT_MODE_DISALLOWED_TOOLS if print_message is not None else None
|
|
645
|
+
),
|
|
646
|
+
)
|
|
608
647
|
profiler.mark("agent.build.done")
|
|
609
648
|
|
|
610
649
|
if rpc_stdio and resume_select and not resume_session_id:
|
|
@@ -628,7 +667,12 @@ async def run(
|
|
|
628
667
|
|
|
629
668
|
# --- Print mode branch ---
|
|
630
669
|
if print_message is not None:
|
|
631
|
-
await _run_print_mode(
|
|
670
|
+
await _run_print_mode(
|
|
671
|
+
agent,
|
|
672
|
+
print_message,
|
|
673
|
+
project_root=project_root,
|
|
674
|
+
output_format=print_output_format,
|
|
675
|
+
)
|
|
632
676
|
return
|
|
633
677
|
|
|
634
678
|
if resume_select and not resume_session_id:
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
from collections.abc import Awaitable, Callable
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from comate_agent_sdk.tokens import UsageSummary
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
TEXT_OUTPUT_FORMAT = "text"
|
|
18
|
+
JSON_OUTPUT_FORMAT = "json"
|
|
19
|
+
ERROR_DURING_EXECUTION = "error_during_execution"
|
|
20
|
+
ERROR_MAX_TURNS = "error_max_turns"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _import_print_mode_components() -> tuple[type, type, type, type]:
|
|
24
|
+
from comate_agent_sdk.agent.chat_session import ChatSession
|
|
25
|
+
from comate_agent_sdk.agent.events import SessionInitEvent, StopEvent, TextEvent
|
|
26
|
+
|
|
27
|
+
return ChatSession, SessionInitEvent, StopEvent, TextEvent
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(slots=True)
|
|
31
|
+
class _PrintRunFacts:
|
|
32
|
+
final_text: str = ""
|
|
33
|
+
structured_output: dict[str, Any] | None = None
|
|
34
|
+
stop_reason: str | None = None
|
|
35
|
+
session_id: str | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _empty_usage_summary() -> UsageSummary:
|
|
39
|
+
return UsageSummary(
|
|
40
|
+
total_prompt_tokens=0,
|
|
41
|
+
total_prompt_cost=0.0,
|
|
42
|
+
total_prompt_cached_tokens=0,
|
|
43
|
+
total_prompt_cached_cost=0.0,
|
|
44
|
+
total_completion_tokens=0,
|
|
45
|
+
total_reasoning_tokens=0,
|
|
46
|
+
total_completion_cost=0.0,
|
|
47
|
+
total_tokens=0,
|
|
48
|
+
total_cost=0.0,
|
|
49
|
+
entry_count=0,
|
|
50
|
+
by_model={},
|
|
51
|
+
by_level={},
|
|
52
|
+
by_source={},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def _get_usage_or_empty(session: object | None) -> UsageSummary:
|
|
57
|
+
if session is None:
|
|
58
|
+
return _empty_usage_summary()
|
|
59
|
+
get_usage: Callable[..., Awaitable[object]] | None = getattr(session, "get_usage", None)
|
|
60
|
+
if not callable(get_usage):
|
|
61
|
+
return _empty_usage_summary()
|
|
62
|
+
try:
|
|
63
|
+
usage = await get_usage()
|
|
64
|
+
except Exception as exc:
|
|
65
|
+
logger.warning("Print mode failed to collect usage: %s", exc, exc_info=True)
|
|
66
|
+
return _empty_usage_summary()
|
|
67
|
+
if isinstance(usage, UsageSummary):
|
|
68
|
+
return usage
|
|
69
|
+
return _empty_usage_summary()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _dump_usage(usage: UsageSummary) -> dict[str, Any]:
|
|
73
|
+
return usage.model_dump(mode="json")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _is_error_stop(stop_reason: str | None) -> bool:
|
|
77
|
+
return stop_reason is not None and stop_reason != "completed"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _subtype_for_stop(stop_reason: str | None) -> str:
|
|
81
|
+
if stop_reason == "max_iterations":
|
|
82
|
+
return ERROR_MAX_TURNS
|
|
83
|
+
if _is_error_stop(stop_reason):
|
|
84
|
+
return ERROR_DURING_EXECUTION
|
|
85
|
+
return "success"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _build_result_payload(
|
|
89
|
+
*,
|
|
90
|
+
facts: _PrintRunFacts,
|
|
91
|
+
usage: UsageSummary,
|
|
92
|
+
duration_ms: int,
|
|
93
|
+
errors: list[str] | None = None,
|
|
94
|
+
) -> dict[str, Any]:
|
|
95
|
+
is_error = bool(errors) or _is_error_stop(facts.stop_reason)
|
|
96
|
+
subtype = ERROR_DURING_EXECUTION if errors else _subtype_for_stop(facts.stop_reason)
|
|
97
|
+
payload: dict[str, Any] = {
|
|
98
|
+
"type": "result",
|
|
99
|
+
"subtype": subtype,
|
|
100
|
+
"duration_ms": duration_ms,
|
|
101
|
+
"duration_api_ms": 0,
|
|
102
|
+
"is_error": is_error,
|
|
103
|
+
"num_turns": 1,
|
|
104
|
+
"stop_reason": facts.stop_reason,
|
|
105
|
+
"total_cost_usd": usage.total_cost,
|
|
106
|
+
"usage": _dump_usage(usage),
|
|
107
|
+
"modelUsage": {},
|
|
108
|
+
"permission_denials": [],
|
|
109
|
+
"uuid": str(uuid.uuid4()),
|
|
110
|
+
"session_id": facts.session_id,
|
|
111
|
+
}
|
|
112
|
+
if is_error:
|
|
113
|
+
payload["errors"] = errors or [f"Run stopped: {facts.stop_reason}"]
|
|
114
|
+
else:
|
|
115
|
+
payload["result"] = facts.final_text
|
|
116
|
+
if facts.structured_output is not None:
|
|
117
|
+
payload["structured_output"] = facts.structured_output
|
|
118
|
+
return payload
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _write_text_result(text: str) -> None:
|
|
122
|
+
if not text:
|
|
123
|
+
return
|
|
124
|
+
sys.stdout.write(text)
|
|
125
|
+
if not text.endswith("\n"):
|
|
126
|
+
sys.stdout.write("\n")
|
|
127
|
+
sys.stdout.flush()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _write_json_result(payload: dict[str, Any]) -> None:
|
|
131
|
+
sys.stdout.write(json.dumps(payload, ensure_ascii=False, separators=(",", ":")))
|
|
132
|
+
sys.stdout.write("\n")
|
|
133
|
+
sys.stdout.flush()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def write_json_error_result(
|
|
137
|
+
*,
|
|
138
|
+
error: str,
|
|
139
|
+
stop_reason: str | None = None,
|
|
140
|
+
session_id: str | None = None,
|
|
141
|
+
duration_ms: int = 0,
|
|
142
|
+
) -> None:
|
|
143
|
+
payload = _build_result_payload(
|
|
144
|
+
facts=_PrintRunFacts(stop_reason=stop_reason, session_id=session_id),
|
|
145
|
+
usage=_empty_usage_summary(),
|
|
146
|
+
duration_ms=duration_ms,
|
|
147
|
+
errors=[error],
|
|
148
|
+
)
|
|
149
|
+
_write_json_result(payload)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
async def _consume_query_stream(session: object, message: str) -> _PrintRunFacts:
|
|
153
|
+
ChatSession, SessionInitEvent, StopEvent, TextEvent = _import_print_mode_components()
|
|
154
|
+
del ChatSession
|
|
155
|
+
facts = _PrintRunFacts(session_id=str(getattr(session, "session_id", "") or "") or None)
|
|
156
|
+
async for event in session.query_stream(message):
|
|
157
|
+
if isinstance(event, SessionInitEvent):
|
|
158
|
+
facts.session_id = event.session_id
|
|
159
|
+
continue
|
|
160
|
+
if isinstance(event, TextEvent):
|
|
161
|
+
facts.final_text = event.content
|
|
162
|
+
facts.structured_output = event.structured_output
|
|
163
|
+
continue
|
|
164
|
+
if isinstance(event, StopEvent):
|
|
165
|
+
facts.stop_reason = event.reason
|
|
166
|
+
error = event.metadata.get("error") if event.metadata else None
|
|
167
|
+
if error is not None and facts.stop_reason == "interrupted":
|
|
168
|
+
if isinstance(error, BaseException):
|
|
169
|
+
raise RuntimeError(str(error)) from error
|
|
170
|
+
raise RuntimeError(str(error))
|
|
171
|
+
return facts
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
async def run_print_mode(
|
|
175
|
+
agent: object,
|
|
176
|
+
message: str,
|
|
177
|
+
*,
|
|
178
|
+
project_root: Path,
|
|
179
|
+
output_format: str = TEXT_OUTPUT_FORMAT,
|
|
180
|
+
preload_mcp: Callable[[object], Awaitable[None]],
|
|
181
|
+
graceful_shutdown: Callable[[object], Awaitable[None]],
|
|
182
|
+
) -> None:
|
|
183
|
+
ChatSession, _, _, _ = _import_print_mode_components()
|
|
184
|
+
session: object | None = None
|
|
185
|
+
started = time.perf_counter()
|
|
186
|
+
facts = _PrintRunFacts()
|
|
187
|
+
try:
|
|
188
|
+
session = ChatSession(agent, cwd=project_root, persistent=False)
|
|
189
|
+
facts.session_id = str(getattr(session, "session_id", "") or "") or None
|
|
190
|
+
await preload_mcp(session)
|
|
191
|
+
facts = await _consume_query_stream(session, message)
|
|
192
|
+
usage = await _get_usage_or_empty(session)
|
|
193
|
+
duration_ms = int((time.perf_counter() - started) * 1000)
|
|
194
|
+
if output_format == JSON_OUTPUT_FORMAT:
|
|
195
|
+
payload = _build_result_payload(
|
|
196
|
+
facts=facts,
|
|
197
|
+
usage=usage,
|
|
198
|
+
duration_ms=duration_ms,
|
|
199
|
+
)
|
|
200
|
+
_write_json_result(payload)
|
|
201
|
+
if payload["is_error"]:
|
|
202
|
+
raise SystemExit(1)
|
|
203
|
+
return
|
|
204
|
+
_write_text_result(facts.final_text)
|
|
205
|
+
except SystemExit:
|
|
206
|
+
raise
|
|
207
|
+
except Exception as exc:
|
|
208
|
+
logger.error("Print mode failed: %s", exc, exc_info=True)
|
|
209
|
+
duration_ms = int((time.perf_counter() - started) * 1000)
|
|
210
|
+
usage = await _get_usage_or_empty(session)
|
|
211
|
+
if output_format == JSON_OUTPUT_FORMAT:
|
|
212
|
+
payload = _build_result_payload(
|
|
213
|
+
facts=facts,
|
|
214
|
+
usage=usage,
|
|
215
|
+
duration_ms=duration_ms,
|
|
216
|
+
errors=[str(exc)],
|
|
217
|
+
)
|
|
218
|
+
_write_json_result(payload)
|
|
219
|
+
else:
|
|
220
|
+
sys.stderr.write(f"Error: {exc}\n")
|
|
221
|
+
raise SystemExit(1) from exc
|
|
222
|
+
finally:
|
|
223
|
+
if session is not None:
|
|
224
|
+
await graceful_shutdown(session)
|