comate-cli 0.7.6__tar.gz → 0.7.8__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.6 → comate_cli-0.7.8}/PKG-INFO +1 -1
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/main.py +10 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/app.py +196 -46
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/event_renderer.py +4 -2
- comate_cli-0.7.8/comate_cli/terminal_agent/preflight.py +210 -0
- comate_cli-0.7.6/comate_cli/terminal_agent/preflight.py → comate_cli-0.7.8/comate_cli/terminal_agent/preflight_wizard.py +14 -202
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/startup_profile.py +16 -3
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/status_bar.py +3 -5
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/statusline/model.py +1 -5
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/statusline/picker_state.py +0 -1
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tool_result_formatters.py +156 -28
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tui_parts/history_sync.py +48 -10
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/update_check.py +98 -3
- {comate_cli-0.7.6 → comate_cli-0.7.8}/pyproject.toml +1 -1
- comate_cli-0.7.8/tests/conftest.py +33 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/statusline/test_model.py +3 -5
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/statusline/test_picker_state.py +3 -4
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/statusline/test_store.py +17 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_app_preflight_gate.py +11 -9
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_app_print_mode.py +12 -3
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_app_shutdown.py +20 -7
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_app_startup_latency.py +249 -38
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_app_token_cost_config.py +22 -3
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_event_renderer.py +63 -3
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_history_sync.py +185 -3
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_preflight.py +51 -6
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_preflight_copilot.py +1 -1
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_resume_picker.py +5 -0
- comate_cli-0.7.8/tests/test_startup_import_budget.py +118 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_startup_profile.py +87 -11
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_status_bar.py +17 -2
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tool_result_formatters.py +178 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_update_check.py +211 -55
- {comate_cli-0.7.6 → comate_cli-0.7.8}/uv.lock +2 -2
- comate_cli-0.7.6/tests/conftest.py +0 -14
- {comate_cli-0.7.6 → comate_cli-0.7.8}/.gitignore +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/CHANGELOG.md +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/README.md +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/__init__.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/__main__.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/config/__init__.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/config/model.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/config/picker.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/config/picker_state.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/config/store.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/figures.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/history_printer.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/logging_adapter.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/slash_commands.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/statusline/picker.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/statusline/store.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tool_fold.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tool_result_store.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tui.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/config/__init__.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/config/test_model.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/config/test_picker_state.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/config/test_picker_ui.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/config/test_roundtrip.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/config/test_store_load.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/config/test_store_save.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/statusline/__init__.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_completion_status_panel.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_context_command.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_event_renderer_streaming.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_event_renderer_tool_fold.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_format_error.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_goal_resume_tui.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_goal_resume_view.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_goal_slash_command.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_handle_error.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_history_printer.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_history_printer_log.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_history_printer_subtitle_position.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_history_printer_tool_fold.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_history_sync_tool_fold.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_input_history.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_logging_adapter.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_logo.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_main_args.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_markdown_render.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_plugin_slash_commands.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_question_view.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_skills_slash_command.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_slash_clear.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_task_poll.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tool_fold.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tool_fold_panel.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tool_view.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_transcript_viewer.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_transcript_viewer_tool_fold.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tui_paste_newline_guard.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tui_queue_preview.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tui_queue_sdk_source.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tui_startup_latency.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tui_thinking_display.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
- {comate_cli-0.7.6 → comate_cli-0.7.8}/tests/test_usage_command.py +0 -0
|
@@ -7,7 +7,12 @@ import os
|
|
|
7
7
|
import signal
|
|
8
8
|
import subprocess
|
|
9
9
|
import sys
|
|
10
|
+
import time
|
|
10
11
|
import warnings
|
|
12
|
+
|
|
13
|
+
# 进程启动锚点:在任何重三方库 import(anthropic/prompt_toolkit/rich)之前捕获,
|
|
14
|
+
# 交给 StartupProfiler 测量「run() 之前的 import」这段以往不可见的启动成本。
|
|
15
|
+
_PROCESS_START_PERF = time.perf_counter()
|
|
11
16
|
try:
|
|
12
17
|
import termios
|
|
13
18
|
except ImportError:
|
|
@@ -268,7 +273,9 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
268
273
|
atexit.register(term_guard.restore, reason="atexit")
|
|
269
274
|
atexit.register(noise_guard.begin_shutdown)
|
|
270
275
|
|
|
276
|
+
_app_import_start_perf = time.perf_counter()
|
|
271
277
|
from comate_cli.terminal_agent.app import run
|
|
278
|
+
_app_import_done_perf = time.perf_counter()
|
|
272
279
|
|
|
273
280
|
try:
|
|
274
281
|
rpc_stdio, resume_session_id, resume_select, print_prompt = _parse_args(run_argv)
|
|
@@ -295,6 +302,9 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
295
302
|
resume_session_id=resume_session_id,
|
|
296
303
|
resume_select=resume_select,
|
|
297
304
|
print_message=print_message,
|
|
305
|
+
process_start_perf=_PROCESS_START_PERF,
|
|
306
|
+
app_import_start_perf=_app_import_start_perf,
|
|
307
|
+
app_import_done_perf=_app_import_done_perf,
|
|
298
308
|
)
|
|
299
309
|
)
|
|
300
310
|
except KeyboardInterrupt:
|
|
@@ -11,34 +11,18 @@ import time
|
|
|
11
11
|
from collections.abc import Iterator
|
|
12
12
|
from contextlib import contextmanager, suppress
|
|
13
13
|
from pathlib import Path
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
14
15
|
|
|
15
16
|
from rich.console import Console
|
|
16
17
|
|
|
17
|
-
from comate_agent_sdk.agent import Agent, AgentConfig, ChatSession, TextEvent
|
|
18
|
-
from comate_agent_sdk.context import EnvOptions
|
|
19
|
-
from comate_agent_sdk.tools import tool
|
|
20
|
-
|
|
21
|
-
from comate_cli.terminal_agent.event_renderer import EventRenderer
|
|
22
|
-
from comate_cli.terminal_agent.logo import print_logo
|
|
23
|
-
from comate_cli.terminal_agent.preflight import run_preflight_if_needed
|
|
24
|
-
from comate_cli.terminal_agent.resume_selector import select_resume_session_id
|
|
25
|
-
from comate_cli.terminal_agent.rpc_stdio import StdioRPCBridge
|
|
26
18
|
from comate_cli.terminal_agent.startup_profile import StartupProfiler
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
from
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
decide_update_prompt,
|
|
35
|
-
format_update_hint,
|
|
36
|
-
get_pending_update_prompt_info,
|
|
37
|
-
mark_update_seen,
|
|
38
|
-
record_skip_until_next_version,
|
|
39
|
-
run_update_command,
|
|
40
|
-
show_update_prompt,
|
|
41
|
-
)
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from comate_agent_sdk.agent.chat_session import ChatSession
|
|
22
|
+
from comate_agent_sdk.agent.core.template import AgentTemplate as Agent
|
|
23
|
+
from comate_agent_sdk.agent.events import TextEvent
|
|
24
|
+
from comate_cli.terminal_agent.event_renderer import EventRenderer
|
|
25
|
+
from comate_cli.terminal_agent.update_check import UpdateInfo
|
|
42
26
|
|
|
43
27
|
console = Console()
|
|
44
28
|
logger = logging.getLogger(__name__)
|
|
@@ -54,6 +38,7 @@ async def _check_update(*, profiler: StartupProfiler | None = None) -> UpdateInf
|
|
|
54
38
|
if profiler is not None:
|
|
55
39
|
profiler.mark("impl.start")
|
|
56
40
|
try:
|
|
41
|
+
check_update, _, _, _ = _import_update_check_core()
|
|
57
42
|
return await check_update(log=logger, profiler=profiler)
|
|
58
43
|
finally:
|
|
59
44
|
if profiler is not None:
|
|
@@ -65,6 +50,16 @@ async def _handle_update_on_launch(info: UpdateInfo) -> bool:
|
|
|
65
50
|
|
|
66
51
|
Returns True when launch should stop after a successful update.
|
|
67
52
|
"""
|
|
53
|
+
(
|
|
54
|
+
UpdatePromptChoice,
|
|
55
|
+
UpdatePromptDecision,
|
|
56
|
+
decide_update_prompt,
|
|
57
|
+
format_update_hint,
|
|
58
|
+
mark_update_seen,
|
|
59
|
+
record_skip_until_next_version,
|
|
60
|
+
run_update_command,
|
|
61
|
+
show_update_prompt,
|
|
62
|
+
) = _import_update_prompt_components()
|
|
68
63
|
try:
|
|
69
64
|
decision = decide_update_prompt(info)
|
|
70
65
|
if decision == UpdatePromptDecision.SKIP:
|
|
@@ -87,21 +82,31 @@ async def _handle_update_on_launch(info: UpdateInfo) -> bool:
|
|
|
87
82
|
async def _handle_background_update_on_launch(
|
|
88
83
|
info: UpdateInfo,
|
|
89
84
|
*,
|
|
90
|
-
|
|
85
|
+
renderer: EventRenderer,
|
|
91
86
|
profiler: StartupProfiler,
|
|
92
87
|
) -> None:
|
|
88
|
+
(
|
|
89
|
+
_UpdatePromptChoice,
|
|
90
|
+
UpdatePromptDecision,
|
|
91
|
+
decide_update_prompt,
|
|
92
|
+
format_update_hint,
|
|
93
|
+
mark_update_seen,
|
|
94
|
+
_record_skip_until_next_version,
|
|
95
|
+
_run_update_command,
|
|
96
|
+
_show_update_prompt,
|
|
97
|
+
) = _import_update_prompt_components()
|
|
93
98
|
try:
|
|
94
99
|
decision = decide_update_prompt(info)
|
|
95
100
|
if decision == UpdatePromptDecision.SKIP:
|
|
96
101
|
profiler.mark("update_check.skip")
|
|
97
102
|
return
|
|
98
103
|
if decision == UpdatePromptDecision.SHOW_HINT:
|
|
99
|
-
|
|
104
|
+
renderer.append_system_message(format_update_hint(info))
|
|
100
105
|
mark_update_seen(info)
|
|
101
106
|
profiler.mark("update_check.hint")
|
|
102
107
|
return
|
|
103
108
|
|
|
104
|
-
|
|
109
|
+
renderer.append_system_message(format_update_hint(info))
|
|
105
110
|
profiler.mark("update_check.prompt_deferred")
|
|
106
111
|
except Exception:
|
|
107
112
|
logger.debug("startup background update handling failed", exc_info=True)
|
|
@@ -110,7 +115,7 @@ async def _handle_background_update_on_launch(
|
|
|
110
115
|
|
|
111
116
|
def _schedule_update_check_on_launch(
|
|
112
117
|
*,
|
|
113
|
-
|
|
118
|
+
renderer: EventRenderer,
|
|
114
119
|
profiler: StartupProfiler,
|
|
115
120
|
) -> asyncio.Task[None]:
|
|
116
121
|
async def _run() -> None:
|
|
@@ -124,7 +129,7 @@ def _schedule_update_check_on_launch(
|
|
|
124
129
|
if update_info is not None:
|
|
125
130
|
await _handle_background_update_on_launch(
|
|
126
131
|
update_info,
|
|
127
|
-
|
|
132
|
+
renderer=renderer,
|
|
128
133
|
profiler=profiler,
|
|
129
134
|
)
|
|
130
135
|
except asyncio.CancelledError:
|
|
@@ -196,6 +201,121 @@ def _sigint_guard() -> Iterator[None]:
|
|
|
196
201
|
logger.warning(f"Failed to restore SIGINT handler: {exc}", exc_info=True)
|
|
197
202
|
|
|
198
203
|
|
|
204
|
+
@contextmanager
|
|
205
|
+
def _profile_section(
|
|
206
|
+
profiler: StartupProfiler | None,
|
|
207
|
+
phase: str,
|
|
208
|
+
) -> Iterator[None]:
|
|
209
|
+
if profiler is not None:
|
|
210
|
+
profiler.mark(f"{phase}.start")
|
|
211
|
+
try:
|
|
212
|
+
yield
|
|
213
|
+
finally:
|
|
214
|
+
if profiler is not None:
|
|
215
|
+
profiler.mark(f"{phase}.done")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _import_preflight_runner():
|
|
219
|
+
from comate_cli.terminal_agent.preflight import run_preflight_if_needed
|
|
220
|
+
|
|
221
|
+
return run_preflight_if_needed
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def print_logo(*args, **kwargs) -> None:
|
|
225
|
+
from comate_cli.terminal_agent.logo import print_logo as _print_logo
|
|
226
|
+
|
|
227
|
+
_print_logo(*args, **kwargs)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _import_sdk_agent_components(
|
|
231
|
+
profiler: StartupProfiler | None = None,
|
|
232
|
+
) -> tuple[type, type, type, type]:
|
|
233
|
+
with _profile_section(profiler, "main.import.sdk_agent"):
|
|
234
|
+
from comate_agent_sdk.agent.compaction import CompactionConfig
|
|
235
|
+
from comate_agent_sdk.agent.options import AgentConfig
|
|
236
|
+
from comate_agent_sdk.agent.service import Agent
|
|
237
|
+
from comate_agent_sdk.context import EnvOptions
|
|
238
|
+
|
|
239
|
+
return Agent, AgentConfig, CompactionConfig, EnvOptions
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _import_chat_session() -> type:
|
|
243
|
+
from comate_agent_sdk.agent.chat_session import ChatSession
|
|
244
|
+
|
|
245
|
+
return ChatSession
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _import_print_mode_components() -> tuple[type, type]:
|
|
249
|
+
from comate_agent_sdk.agent.chat_session import ChatSession
|
|
250
|
+
from comate_agent_sdk.agent.events import TextEvent
|
|
251
|
+
|
|
252
|
+
return ChatSession, TextEvent
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _import_rpc_bridge() -> type:
|
|
256
|
+
from comate_cli.terminal_agent.rpc_stdio import StdioRPCBridge
|
|
257
|
+
|
|
258
|
+
return StdioRPCBridge
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _import_resume_selector():
|
|
262
|
+
from comate_cli.terminal_agent.resume_selector import select_resume_session_id
|
|
263
|
+
|
|
264
|
+
return select_resume_session_id
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _import_interactive_tui_components(
|
|
268
|
+
profiler: StartupProfiler | None = None,
|
|
269
|
+
):
|
|
270
|
+
with _profile_section(profiler, "main.import.tui"):
|
|
271
|
+
from comate_cli.terminal_agent.event_renderer import EventRenderer
|
|
272
|
+
from comate_cli.terminal_agent.logging_adapter import setup_tui_logging
|
|
273
|
+
from comate_cli.terminal_agent.status_bar import StatusBar
|
|
274
|
+
from comate_cli.terminal_agent.tui import TerminalAgentTUI
|
|
275
|
+
|
|
276
|
+
return EventRenderer, setup_tui_logging, StatusBar, TerminalAgentTUI
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _import_update_check_core():
|
|
280
|
+
from comate_cli.terminal_agent.update_check import (
|
|
281
|
+
check_update,
|
|
282
|
+
get_pending_update_prompt_info,
|
|
283
|
+
record_update_check_attempt,
|
|
284
|
+
should_check_for_update,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
check_update,
|
|
289
|
+
get_pending_update_prompt_info,
|
|
290
|
+
record_update_check_attempt,
|
|
291
|
+
should_check_for_update,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _import_update_prompt_components():
|
|
296
|
+
from comate_cli.terminal_agent.update_check import (
|
|
297
|
+
UpdatePromptChoice,
|
|
298
|
+
UpdatePromptDecision,
|
|
299
|
+
decide_update_prompt,
|
|
300
|
+
format_update_hint,
|
|
301
|
+
mark_update_seen,
|
|
302
|
+
record_skip_until_next_version,
|
|
303
|
+
run_update_command,
|
|
304
|
+
show_update_prompt,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
UpdatePromptChoice,
|
|
309
|
+
UpdatePromptDecision,
|
|
310
|
+
decide_update_prompt,
|
|
311
|
+
format_update_hint,
|
|
312
|
+
mark_update_seen,
|
|
313
|
+
record_skip_until_next_version,
|
|
314
|
+
run_update_command,
|
|
315
|
+
show_update_prompt,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
|
|
199
319
|
async def _shutdown_session(session: ChatSession, *, label: str) -> None:
|
|
200
320
|
start = time.monotonic()
|
|
201
321
|
try:
|
|
@@ -233,17 +353,12 @@ async def _graceful_shutdown(*sessions: ChatSession) -> None:
|
|
|
233
353
|
logger.info(f"Graceful shutdown completed in {elapsed:.3f}s")
|
|
234
354
|
|
|
235
355
|
|
|
236
|
-
@tool("Add two numbers 涉及到加法运算 必须使用这个工具")
|
|
237
|
-
async def add(a: int, b: int) -> int:
|
|
238
|
-
return a + b
|
|
239
|
-
|
|
240
|
-
|
|
241
356
|
def _build_agent(
|
|
242
357
|
*,
|
|
243
358
|
project_root: Path | None = None,
|
|
244
359
|
profiler: StartupProfiler | None = None,
|
|
245
360
|
) -> Agent:
|
|
246
|
-
|
|
361
|
+
Agent, AgentConfig, CompactionConfig, EnvOptions = _import_sdk_agent_components(profiler)
|
|
247
362
|
from comate_cli.terminal_agent.config import store
|
|
248
363
|
|
|
249
364
|
resolved_project_root = project_root or _resolve_cli_project_root()
|
|
@@ -281,6 +396,7 @@ def _resolve_session(
|
|
|
281
396
|
cwd: Path | None = None,
|
|
282
397
|
profiler: StartupProfiler | None = None,
|
|
283
398
|
) -> tuple[ChatSession, str]:
|
|
399
|
+
ChatSession = _import_chat_session()
|
|
284
400
|
if resume_session_id:
|
|
285
401
|
if profiler is not None:
|
|
286
402
|
profiler.mark("session.resume.construct.start")
|
|
@@ -387,6 +503,7 @@ async def _run_print_mode(
|
|
|
387
503
|
*,
|
|
388
504
|
project_root: Path,
|
|
389
505
|
) -> None:
|
|
506
|
+
ChatSession, TextEvent = _import_print_mode_components()
|
|
390
507
|
session = ChatSession(agent, cwd=project_root, persistent=False)
|
|
391
508
|
try:
|
|
392
509
|
await _preload_mcp_in_tui(session)
|
|
@@ -440,13 +557,25 @@ async def run(
|
|
|
440
557
|
resume_session_id: str | None = None,
|
|
441
558
|
resume_select: bool = False,
|
|
442
559
|
print_message: str | None = None,
|
|
560
|
+
process_start_perf: float | None = None,
|
|
561
|
+
app_import_start_perf: float | None = None,
|
|
562
|
+
app_import_done_perf: float | None = None,
|
|
443
563
|
) -> None:
|
|
444
564
|
_install_event_loop_exception_handler()
|
|
445
|
-
|
|
565
|
+
# process_start_perf 由入口 main() 在模块 import 之前捕获,使 profiler 能测到
|
|
566
|
+
# 「run() 之前的重三方库 import」这段以往不可见的启动成本(柱子 A)。
|
|
567
|
+
profiler = StartupProfiler.from_env(logger=logger, started_at=process_start_perf)
|
|
568
|
+
if app_import_start_perf is not None:
|
|
569
|
+
profiler.mark_at("main.app_import.start", app_import_start_perf)
|
|
570
|
+
profiler.mark_at("main.import.app.start", app_import_start_perf)
|
|
571
|
+
if app_import_done_perf is not None:
|
|
572
|
+
profiler.mark_at("main.app_import.done", app_import_done_perf)
|
|
573
|
+
profiler.mark_at("main.import.app.done", app_import_done_perf)
|
|
446
574
|
profiler.mark("cli_project_root.start")
|
|
447
575
|
project_root = _resolve_cli_project_root()
|
|
448
576
|
profiler.mark("cli_project_root.done")
|
|
449
577
|
profiler.mark("preflight.start")
|
|
578
|
+
run_preflight_if_needed = _import_preflight_runner()
|
|
450
579
|
preflight_result = await run_preflight_if_needed(
|
|
451
580
|
console=console,
|
|
452
581
|
project_root=project_root,
|
|
@@ -462,6 +591,12 @@ async def run(
|
|
|
462
591
|
print_logo(console, project_root=project_root)
|
|
463
592
|
profiler.mark("logo.printed")
|
|
464
593
|
|
|
594
|
+
(
|
|
595
|
+
_check_update_func,
|
|
596
|
+
get_pending_update_prompt_info,
|
|
597
|
+
_record_update_check_attempt,
|
|
598
|
+
_should_check_for_update,
|
|
599
|
+
) = _import_update_check_core()
|
|
465
600
|
pending_info = get_pending_update_prompt_info()
|
|
466
601
|
if pending_info is not None:
|
|
467
602
|
should_stop = await _handle_update_on_launch(pending_info)
|
|
@@ -483,6 +618,7 @@ async def run(
|
|
|
483
618
|
cwd=project_root,
|
|
484
619
|
profiler=profiler,
|
|
485
620
|
)
|
|
621
|
+
StdioRPCBridge = _import_rpc_bridge()
|
|
486
622
|
bridge = StdioRPCBridge(session)
|
|
487
623
|
try:
|
|
488
624
|
await bridge.run()
|
|
@@ -496,6 +632,7 @@ async def run(
|
|
|
496
632
|
return
|
|
497
633
|
|
|
498
634
|
if resume_select and not resume_session_id:
|
|
635
|
+
select_resume_session_id = _import_resume_selector()
|
|
499
636
|
selected_session_id = await select_resume_session_id(console, cwd=project_root)
|
|
500
637
|
if not selected_session_id:
|
|
501
638
|
return
|
|
@@ -504,8 +641,8 @@ async def run(
|
|
|
504
641
|
# 在 _resolve_session 前安装 TUILoggingHandler,确保 session 初始化期间
|
|
505
642
|
# 的 logger.warning()(如 user_instruction token 超限)能被 TUI 系统捕获,
|
|
506
643
|
# 而非 fallthrough 到 Python lastResort StreamHandler 以原始文本输出到 stderr。
|
|
644
|
+
EventRenderer, setup_tui_logging, StatusBar, TerminalAgentTUI = _import_interactive_tui_components(profiler)
|
|
507
645
|
renderer = EventRenderer(project_root=project_root)
|
|
508
|
-
from comate_cli.terminal_agent.logging_adapter import setup_tui_logging
|
|
509
646
|
profiler.mark("logging.setup.start")
|
|
510
647
|
logging_session = setup_tui_logging(renderer, project_root=project_root)
|
|
511
648
|
profiler.mark("logging.setup.done")
|
|
@@ -538,10 +675,22 @@ async def run(
|
|
|
538
675
|
tui = TerminalAgentTUI(session, status_bar, renderer)
|
|
539
676
|
profiler.mark("tui.init.done")
|
|
540
677
|
tui.add_resume_history(mode)
|
|
541
|
-
update_check_task =
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
678
|
+
update_check_task: asyncio.Task[None] | None = None
|
|
679
|
+
(
|
|
680
|
+
_check_update_func,
|
|
681
|
+
_get_pending_update_prompt_info,
|
|
682
|
+
record_update_check_attempt,
|
|
683
|
+
should_check_for_update,
|
|
684
|
+
) = _import_update_check_core()
|
|
685
|
+
if should_check_for_update():
|
|
686
|
+
# attempt 语义:先记录尝试时间,确保 interval 内最多发起一次网络检查
|
|
687
|
+
record_update_check_attempt()
|
|
688
|
+
update_check_task = _schedule_update_check_on_launch(
|
|
689
|
+
renderer=renderer,
|
|
690
|
+
profiler=profiler,
|
|
691
|
+
)
|
|
692
|
+
else:
|
|
693
|
+
profiler.mark("update_check.throttled")
|
|
545
694
|
|
|
546
695
|
async def _mcp_loader() -> None:
|
|
547
696
|
await _preload_mcp_in_tui(session, profiler=profiler.child("mcp"))
|
|
@@ -569,11 +718,12 @@ async def run(
|
|
|
569
718
|
)
|
|
570
719
|
finally:
|
|
571
720
|
try:
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
721
|
+
if update_check_task is not None:
|
|
722
|
+
await _cancel_background_task(
|
|
723
|
+
update_check_task,
|
|
724
|
+
timeout_s=0.1,
|
|
725
|
+
task_name="terminal-update-check",
|
|
726
|
+
)
|
|
577
727
|
if active_session is session:
|
|
578
728
|
await _graceful_shutdown(active_session)
|
|
579
729
|
else:
|
|
@@ -1128,15 +1128,17 @@ class EventRenderer:
|
|
|
1128
1128
|
diff_lines: list[str] | None = None,
|
|
1129
1129
|
model_name: str = "",
|
|
1130
1130
|
subtitle: str | None = None,
|
|
1131
|
+
file_path: str | None = None,
|
|
1131
1132
|
) -> None:
|
|
1132
1133
|
"""Append a tool result to history as a static entry (no timer).
|
|
1133
1134
|
|
|
1134
1135
|
Args:
|
|
1135
1136
|
signature: 工具签名,例如 "Read(path=xxx)"
|
|
1136
1137
|
is_error: 是否为错误结果
|
|
1137
|
-
diff_lines: optional diff lines for Edit
|
|
1138
|
+
diff_lines: optional diff lines for Edit/Write
|
|
1138
1139
|
model_name: model name for Agent tools (rendered dim)
|
|
1139
1140
|
subtitle: optional subtitle rendered as `⎿ ...`
|
|
1141
|
+
file_path: optional source path used for syntax highlighting diff body
|
|
1140
1142
|
"""
|
|
1141
1143
|
sev: Literal["info", "warning", "error"] = "error" if is_error else "info"
|
|
1142
1144
|
sub_sty: Literal["error", "warning", "dim"] = "dim"
|
|
@@ -1145,7 +1147,7 @@ class EventRenderer:
|
|
|
1145
1147
|
if model_name:
|
|
1146
1148
|
text_obj.append(f" {BULLET_OPERATOR} {model_name}", style="dim")
|
|
1147
1149
|
text_obj.append("\n")
|
|
1148
|
-
text_obj.append(render_diff_text(diff_lines))
|
|
1150
|
+
text_obj.append(render_diff_text(diff_lines, file_path=file_path))
|
|
1149
1151
|
self._append_history_entry(
|
|
1150
1152
|
HistoryEntry(entry_type="tool_result", text=text_obj, severity="info", subtitle=subtitle)
|
|
1151
1153
|
)
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Literal, Mapping, Sequence
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from comate_agent_sdk.agent.llm_levels import ALL_LEVELS
|
|
12
|
+
from comate_agent_sdk.agent.settings import (
|
|
13
|
+
USER_SETTINGS_PATH,
|
|
14
|
+
read_settings_json_object,
|
|
15
|
+
write_settings_json_atomic,
|
|
16
|
+
)
|
|
17
|
+
from comate_agent_sdk.facade.config.providers import (
|
|
18
|
+
PROVIDER_PRESETS,
|
|
19
|
+
LevelConfigDraft,
|
|
20
|
+
PreflightCheckResult,
|
|
21
|
+
ProviderPreset,
|
|
22
|
+
build_preview_payload,
|
|
23
|
+
evaluate_preflight_state,
|
|
24
|
+
get_provider_env_var,
|
|
25
|
+
merge_user_settings,
|
|
26
|
+
resolve_provider_presets,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from comate_cli.terminal_agent.figures import BULLET_OPERATOR
|
|
30
|
+
from comate_cli.terminal_agent.config.store import (
|
|
31
|
+
apply_cli_config_snapshot_to_settings,
|
|
32
|
+
snapshot_from_settings_dict,
|
|
33
|
+
)
|
|
34
|
+
from comate_cli.terminal_agent.statusline.model import StatuslineConfig
|
|
35
|
+
from comate_cli.terminal_agent.statusline.store import build_statusline_settings
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
_LEVEL_LABEL_WIDTH = 6
|
|
40
|
+
_FIELD_LABEL_WIDTH = 9
|
|
41
|
+
_VALUE_MAX_WIDTH = 96
|
|
42
|
+
@dataclass(frozen=True, slots=True)
|
|
43
|
+
class PreflightResult:
|
|
44
|
+
status: Literal[
|
|
45
|
+
"skipped",
|
|
46
|
+
"configured",
|
|
47
|
+
"cancelled",
|
|
48
|
+
"blocked_non_interactive",
|
|
49
|
+
"failed",
|
|
50
|
+
]
|
|
51
|
+
detail: str | None = None
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def should_abort_launch(self) -> bool:
|
|
55
|
+
return self.status in {"cancelled", "blocked_non_interactive", "failed"}
|
|
56
|
+
|
|
57
|
+
async def run_preflight_if_needed(
|
|
58
|
+
*,
|
|
59
|
+
console: Console,
|
|
60
|
+
project_root: Path,
|
|
61
|
+
interactive: bool,
|
|
62
|
+
provider_presets: Sequence[ProviderPreset] | None = None,
|
|
63
|
+
) -> PreflightResult:
|
|
64
|
+
check = evaluate_preflight_state(project_root=project_root)
|
|
65
|
+
if not check.needs_preflight:
|
|
66
|
+
return PreflightResult(status="skipped")
|
|
67
|
+
|
|
68
|
+
if not interactive:
|
|
69
|
+
_print_preflight_block_message(console=console, check=check)
|
|
70
|
+
return PreflightResult(status="blocked_non_interactive", detail="missing_llm_config")
|
|
71
|
+
|
|
72
|
+
from comate_cli.terminal_agent.preflight_wizard import _run_preflight_wizard
|
|
73
|
+
|
|
74
|
+
flow = await _run_preflight_wizard(
|
|
75
|
+
check=check,
|
|
76
|
+
project_root=project_root,
|
|
77
|
+
provider_presets=provider_presets,
|
|
78
|
+
)
|
|
79
|
+
if flow.cancel_detail is not None:
|
|
80
|
+
console.print("[yellow]Setup cancelled. Session was not started.[/]")
|
|
81
|
+
return PreflightResult(status="cancelled", detail=flow.cancel_detail)
|
|
82
|
+
draft_with_keys = flow.levels
|
|
83
|
+
if draft_with_keys is None:
|
|
84
|
+
console.print("[yellow]Setup cancelled. Session was not started.[/]")
|
|
85
|
+
return PreflightResult(status="cancelled", detail="unknown_cancelled")
|
|
86
|
+
|
|
87
|
+
settings_path = USER_SETTINGS_PATH.expanduser()
|
|
88
|
+
try:
|
|
89
|
+
merged = merge_user_settings(
|
|
90
|
+
existing=_load_user_settings_json(settings_path),
|
|
91
|
+
levels=draft_with_keys,
|
|
92
|
+
)
|
|
93
|
+
merged = _hydrate_cli_default_settings(merged)
|
|
94
|
+
write_user_settings_atomic(path=settings_path, data=merged)
|
|
95
|
+
except Exception as exc:
|
|
96
|
+
logger.exception("Failed to save pre-flight settings")
|
|
97
|
+
console.print(f"[red]Failed to save provider settings: {exc}[/]")
|
|
98
|
+
return PreflightResult(status="failed", detail="write_failed")
|
|
99
|
+
|
|
100
|
+
recheck = evaluate_preflight_state(project_root=project_root)
|
|
101
|
+
if recheck.needs_preflight:
|
|
102
|
+
console.print(
|
|
103
|
+
"[red]Settings were saved to user scope, but effective configuration is still incomplete. "
|
|
104
|
+
"Please check project/local overrides.[/]"
|
|
105
|
+
)
|
|
106
|
+
return PreflightResult(status="failed", detail="still_missing_after_write")
|
|
107
|
+
|
|
108
|
+
console.print(f"[green]Provider settings saved: {settings_path}[/]")
|
|
109
|
+
return PreflightResult(status="configured")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def write_user_settings_atomic(*, path: Path, data: Mapping[str, Any]) -> None:
|
|
113
|
+
write_settings_json_atomic(path=path, data=data)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _load_user_settings_json(path: Path) -> dict[str, Any]:
|
|
117
|
+
return read_settings_json_object(path)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _deep_merge_missing_settings(
|
|
121
|
+
existing: Mapping[str, Any] | dict[str, Any],
|
|
122
|
+
defaults: Mapping[str, Any],
|
|
123
|
+
) -> dict[str, Any]:
|
|
124
|
+
result: dict[str, Any] = deepcopy(dict(existing))
|
|
125
|
+
for key, default_value in defaults.items():
|
|
126
|
+
current_value = result.get(key)
|
|
127
|
+
if key not in result:
|
|
128
|
+
result[key] = deepcopy(default_value)
|
|
129
|
+
elif isinstance(current_value, dict) and isinstance(default_value, dict):
|
|
130
|
+
result[key] = _deep_merge_missing_settings(current_value, default_value)
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _hydrate_cli_default_settings(data: Mapping[str, Any]) -> dict[str, Any]:
|
|
135
|
+
"""Fill missing CLI-owned defaults during settings writes.
|
|
136
|
+
|
|
137
|
+
This is write-path-only. `store.load()` remains read-only, so CLI startup
|
|
138
|
+
does not create ~/.agent/settings.json unless preflight or a picker saves.
|
|
139
|
+
"""
|
|
140
|
+
hydrated: dict[str, Any] = deepcopy(dict(data))
|
|
141
|
+
cli_raw = hydrated.get("comate_cli")
|
|
142
|
+
cli = deepcopy(cli_raw) if isinstance(cli_raw, dict) else {}
|
|
143
|
+
|
|
144
|
+
statusline_raw = cli.get("statusline")
|
|
145
|
+
if isinstance(statusline_raw, dict):
|
|
146
|
+
statusline_existing = statusline_raw
|
|
147
|
+
else:
|
|
148
|
+
legacy_statusline = hydrated.get("statusline")
|
|
149
|
+
statusline_existing = legacy_statusline if isinstance(legacy_statusline, dict) else {}
|
|
150
|
+
cli["statusline"] = _deep_merge_missing_settings(
|
|
151
|
+
statusline_existing,
|
|
152
|
+
build_statusline_settings(StatuslineConfig.default()),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
hydrated["comate_cli"] = cli
|
|
156
|
+
hydrated.pop("statusline", None)
|
|
157
|
+
snapshot = snapshot_from_settings_dict(hydrated)
|
|
158
|
+
return apply_cli_config_snapshot_to_settings(hydrated, snapshot)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _truncate_value(value: str, max_width: int = _VALUE_MAX_WIDTH) -> str:
|
|
162
|
+
text = value.strip()
|
|
163
|
+
if len(text) <= max_width:
|
|
164
|
+
return text
|
|
165
|
+
if max_width <= 3:
|
|
166
|
+
return text[:max_width]
|
|
167
|
+
return f"{text[:max_width - 3]}..."
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _format_level_summary(*, level: str, entry: LevelConfigDraft) -> str:
|
|
171
|
+
model_value = _truncate_value(f"{entry.provider}:{entry.model}")
|
|
172
|
+
endpoint_value = _truncate_value(entry.base_url or "(default)")
|
|
173
|
+
level_cell = f"{level:<{_LEVEL_LABEL_WIDTH}}"
|
|
174
|
+
model_label = f"{'Model':<{_FIELD_LABEL_WIDTH}}"
|
|
175
|
+
endpoint_label = f"{'Endpoint':<{_FIELD_LABEL_WIDTH}}"
|
|
176
|
+
return (
|
|
177
|
+
f"{level_cell}{model_label}{model_value}\n"
|
|
178
|
+
f"{'':<{_LEVEL_LABEL_WIDTH}}{endpoint_label}{endpoint_value}"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _format_missing_reasons(reasons: tuple[str, ...], *, bullet_indent: str = " ") -> str:
|
|
183
|
+
if not reasons:
|
|
184
|
+
return "What's missing:\n - Unknown issue"
|
|
185
|
+
lines = ["What's missing:"]
|
|
186
|
+
lines.extend(f"{bullet_indent}- {reason}" for reason in reasons)
|
|
187
|
+
return "\n".join(lines)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _print_preflight_block_message(*, console: Console, check: PreflightCheckResult) -> None:
|
|
191
|
+
reason_lines = _format_missing_reasons(check.reasons)
|
|
192
|
+
console.print("[red]Provider setup required[/]")
|
|
193
|
+
console.print("[dim]You need to configure an API provider before you can continue.[/]")
|
|
194
|
+
console.print(f"[dim]{reason_lines}[/]")
|
|
195
|
+
console.print("[dim]Run `comate` in interactive mode to complete provider setup.[/]")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _build_preflight_entry_options() -> list[dict[str, str]]:
|
|
199
|
+
return [
|
|
200
|
+
{
|
|
201
|
+
"value": "continue",
|
|
202
|
+
"label": "Start setup now",
|
|
203
|
+
"description": "",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"value": "cancel",
|
|
207
|
+
"label": "Exit",
|
|
208
|
+
"description": "",
|
|
209
|
+
},
|
|
210
|
+
]
|