comate-cli 0.7.4__tar.gz → 0.7.5__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.4 → comate_cli-0.7.5}/PKG-INFO +2 -2
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/app.py +98 -27
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/status_bar.py +30 -10
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tui.py +20 -2
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/update_check.py +23 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/pyproject.toml +2 -2
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_app_startup_latency.py +9 -3
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_completion_status_panel.py +1 -1
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_startup_profile.py +124 -4
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_status_bar.py +18 -4
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tui_startup_latency.py +4 -2
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_update_check.py +46 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/.gitignore +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/CHANGELOG.md +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/README.md +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/__main__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/main.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/config/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/config/model.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/config/picker.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/config/picker_state.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/config/store.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/event_renderer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/figures.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/history_printer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/logging_adapter.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/slash_commands.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/startup_profile.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/statusline/model.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/statusline/picker.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/statusline/store.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tool_fold.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tool_result_store.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/config/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/config/test_model.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/config/test_picker_state.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/config/test_picker_ui.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/config/test_roundtrip.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/config/test_store_load.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/config/test_store_save.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/conftest.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/statusline/__init__.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/statusline/test_model.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/statusline/test_picker_state.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/statusline/test_store.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_app_token_cost_config.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_context_command.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_event_renderer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_event_renderer_streaming.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_event_renderer_tool_fold.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_format_error.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_goal_resume_tui.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_goal_resume_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_goal_slash_command.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_handle_error.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_history_printer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_history_printer_log.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_history_printer_subtitle_position.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_history_printer_tool_fold.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_history_sync.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_history_sync_tool_fold.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_input_history.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_logging_adapter.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_logo.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_main_args.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_markdown_render.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_plugin_slash_commands.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_preflight.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_question_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_resume_picker.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_skills_slash_command.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_slash_clear.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_task_poll.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tool_fold.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tool_fold_panel.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tool_result_formatters.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tool_view.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_transcript_viewer.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_transcript_viewer_tool_fold.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tui_paste_newline_guard.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tui_queue_preview.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tui_queue_sdk_source.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tui_thinking_display.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/tests/test_usage_command.py +0 -0
- {comate_cli-0.7.4 → comate_cli-0.7.5}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comate-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.5
|
|
4
4
|
Summary: Comate terminal CLI built on comate-agent-sdk
|
|
5
5
|
Project-URL: Homepage, https://github.com/AndyLee1024/agent-sdk
|
|
6
6
|
Project-URL: Repository, https://github.com/AndyLee1024/agent-sdk
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
-
Requires-Python:
|
|
16
|
+
Requires-Python: <3.14,>=3.11
|
|
17
17
|
Requires-Dist: charset-normalizer==3.4.7
|
|
18
18
|
Requires-Dist: comate-agent-sdk<0.9.0,>=0.8.0a1
|
|
19
19
|
Requires-Dist: concurrent-log-handler>=0.9.25
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
|
-
import random
|
|
6
|
+
import random # noqa: F401 - tests patch this module-level import for deterministic startup tips.
|
|
7
7
|
import signal
|
|
8
8
|
import sys
|
|
9
9
|
import threading
|
|
@@ -49,8 +49,14 @@ def _resolve_cli_project_root() -> Path:
|
|
|
49
49
|
return Path.cwd().expanduser().resolve()
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
async def _check_update() -> UpdateInfo | None:
|
|
53
|
-
|
|
52
|
+
async def _check_update(*, profiler: StartupProfiler | None = None) -> UpdateInfo | None:
|
|
53
|
+
if profiler is not None:
|
|
54
|
+
profiler.mark("impl.start")
|
|
55
|
+
try:
|
|
56
|
+
return await check_update(log=logger, profiler=profiler)
|
|
57
|
+
finally:
|
|
58
|
+
if profiler is not None:
|
|
59
|
+
profiler.mark("impl.done")
|
|
54
60
|
|
|
55
61
|
|
|
56
62
|
async def _handle_update_on_launch(info: UpdateInfo) -> bool:
|
|
@@ -109,7 +115,7 @@ def _schedule_update_check_on_launch(
|
|
|
109
115
|
async def _run() -> None:
|
|
110
116
|
try:
|
|
111
117
|
profiler.mark("update_check.start")
|
|
112
|
-
update_info = await _check_update()
|
|
118
|
+
update_info = await _check_update(profiler=profiler.child("update_check"))
|
|
113
119
|
profiler.mark("update_check.done")
|
|
114
120
|
if update_info is not None:
|
|
115
121
|
await _handle_background_update_on_launch(
|
|
@@ -228,35 +234,64 @@ async def add(a: int, b: int) -> int:
|
|
|
228
234
|
return a + b
|
|
229
235
|
|
|
230
236
|
|
|
231
|
-
def _build_agent(
|
|
237
|
+
def _build_agent(
|
|
238
|
+
*,
|
|
239
|
+
project_root: Path | None = None,
|
|
240
|
+
profiler: StartupProfiler | None = None,
|
|
241
|
+
) -> Agent:
|
|
232
242
|
from comate_agent_sdk.agent.compaction import CompactionConfig
|
|
233
243
|
from comate_cli.terminal_agent.config import store
|
|
234
244
|
|
|
235
245
|
resolved_project_root = project_root or _resolve_cli_project_root()
|
|
246
|
+
if profiler is not None:
|
|
247
|
+
profiler.mark("agent.config.load.start")
|
|
236
248
|
snapshot = store.load()
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
)
|
|
249
|
+
if profiler is not None:
|
|
250
|
+
profiler.mark("agent.config.load.done")
|
|
251
|
+
|
|
252
|
+
agent_config = AgentConfig(
|
|
253
|
+
role="software_engineering",
|
|
254
|
+
cwd=resolved_project_root,
|
|
255
|
+
env_options=EnvOptions(system_env=True, git_env=True),
|
|
256
|
+
use_streaming_task=True,
|
|
257
|
+
include_cost=bool(getattr(snapshot, "token_cost_enabled", False)),
|
|
258
|
+
memory_background_enabled=snapshot.memory_background_enabled,
|
|
259
|
+
memory_dreaming_enabled=snapshot.memory_dreaming_enabled,
|
|
260
|
+
compaction=CompactionConfig(
|
|
261
|
+
threshold_ratio=snapshot.compaction_threshold_ratio,
|
|
262
|
+
),
|
|
251
263
|
)
|
|
264
|
+
if profiler is not None:
|
|
265
|
+
profiler.mark("agent.construct.start")
|
|
266
|
+
try:
|
|
267
|
+
return Agent(config=agent_config)
|
|
268
|
+
finally:
|
|
269
|
+
if profiler is not None:
|
|
270
|
+
profiler.mark("agent.construct.done")
|
|
252
271
|
|
|
253
272
|
|
|
254
273
|
def _resolve_session(
|
|
255
|
-
agent: Agent,
|
|
274
|
+
agent: Agent,
|
|
275
|
+
resume_session_id: str | None,
|
|
276
|
+
*,
|
|
277
|
+
cwd: Path | None = None,
|
|
278
|
+
profiler: StartupProfiler | None = None,
|
|
256
279
|
) -> tuple[ChatSession, str]:
|
|
257
280
|
if resume_session_id:
|
|
258
|
-
|
|
259
|
-
|
|
281
|
+
if profiler is not None:
|
|
282
|
+
profiler.mark("session.resume.construct.start")
|
|
283
|
+
try:
|
|
284
|
+
return ChatSession.resume(agent, session_id=resume_session_id, cwd=cwd), "resume"
|
|
285
|
+
finally:
|
|
286
|
+
if profiler is not None:
|
|
287
|
+
profiler.mark("session.resume.construct.done")
|
|
288
|
+
if profiler is not None:
|
|
289
|
+
profiler.mark("session.new.construct.start")
|
|
290
|
+
try:
|
|
291
|
+
return ChatSession(agent, cwd=cwd), "new"
|
|
292
|
+
finally:
|
|
293
|
+
if profiler is not None:
|
|
294
|
+
profiler.mark("session.new.construct.done")
|
|
260
295
|
|
|
261
296
|
|
|
262
297
|
def _format_exit_usage_line(usage: object) -> str:
|
|
@@ -287,23 +322,47 @@ def _format_resume_hint(session_id: str | None) -> str | None:
|
|
|
287
322
|
)
|
|
288
323
|
|
|
289
324
|
|
|
290
|
-
async def _preload_mcp_in_tui(
|
|
325
|
+
async def _preload_mcp_in_tui(
|
|
326
|
+
session: ChatSession,
|
|
327
|
+
*,
|
|
328
|
+
profiler: StartupProfiler | None = None,
|
|
329
|
+
) -> None:
|
|
291
330
|
"""在 TUI 内异步加载 MCP,初始化阶段不输出 scrollback 文案。"""
|
|
331
|
+
if profiler is not None:
|
|
332
|
+
profiler.mark("preload.start")
|
|
292
333
|
runtime = session.runtime
|
|
293
334
|
if not bool(runtime.config.mcp_enabled):
|
|
335
|
+
if profiler is not None:
|
|
336
|
+
profiler.mark("preload.skip_disabled")
|
|
294
337
|
return
|
|
295
338
|
|
|
296
339
|
try:
|
|
340
|
+
if profiler is not None:
|
|
341
|
+
profiler.mark("start_preload.start")
|
|
297
342
|
preload_task = runtime.start_mcp_preload()
|
|
343
|
+
if profiler is not None:
|
|
344
|
+
profiler.mark("start_preload.done")
|
|
298
345
|
if preload_task is None:
|
|
346
|
+
if profiler is not None:
|
|
347
|
+
profiler.mark("preload.no_task")
|
|
299
348
|
return
|
|
349
|
+
if profiler is not None:
|
|
350
|
+
profiler.mark("await_preload.start")
|
|
300
351
|
await preload_task
|
|
352
|
+
if profiler is not None:
|
|
353
|
+
profiler.mark("await_preload.done")
|
|
301
354
|
except Exception as e:
|
|
302
355
|
logger.debug(f"MCP init failed: {e}", exc_info=True)
|
|
356
|
+
if profiler is not None:
|
|
357
|
+
profiler.mark("preload.failed")
|
|
303
358
|
return
|
|
304
359
|
|
|
360
|
+
if profiler is not None:
|
|
361
|
+
profiler.mark("inspect_manager.start")
|
|
305
362
|
mgr = runtime._mcp_manager
|
|
306
363
|
if mgr is None:
|
|
364
|
+
if profiler is not None:
|
|
365
|
+
profiler.mark("inspect_manager.no_manager")
|
|
307
366
|
return
|
|
308
367
|
|
|
309
368
|
for alias, reason in mgr.failed_servers:
|
|
@@ -314,6 +373,8 @@ async def _preload_mcp_in_tui(session: ChatSession) -> None:
|
|
|
314
373
|
count = len(loaded)
|
|
315
374
|
aliases = sorted({i.server_alias for i in loaded})
|
|
316
375
|
logger.info(f"MCP Server loaded: {', '.join(aliases)} ({count} tools)")
|
|
376
|
+
if profiler is not None:
|
|
377
|
+
profiler.mark("inspect_manager.done")
|
|
317
378
|
|
|
318
379
|
|
|
319
380
|
async def _run_print_mode(
|
|
@@ -404,7 +465,7 @@ async def run(
|
|
|
404
465
|
return
|
|
405
466
|
|
|
406
467
|
profiler.mark("agent.build.start")
|
|
407
|
-
agent = _build_agent(project_root=project_root)
|
|
468
|
+
agent = _build_agent(project_root=project_root, profiler=profiler)
|
|
408
469
|
profiler.mark("agent.build.done")
|
|
409
470
|
|
|
410
471
|
if rpc_stdio and resume_select and not resume_session_id:
|
|
@@ -412,7 +473,12 @@ async def run(
|
|
|
412
473
|
return
|
|
413
474
|
|
|
414
475
|
if rpc_stdio:
|
|
415
|
-
session, _mode = _resolve_session(
|
|
476
|
+
session, _mode = _resolve_session(
|
|
477
|
+
agent,
|
|
478
|
+
resume_session_id,
|
|
479
|
+
cwd=project_root,
|
|
480
|
+
profiler=profiler,
|
|
481
|
+
)
|
|
416
482
|
bridge = StdioRPCBridge(session)
|
|
417
483
|
try:
|
|
418
484
|
await bridge.run()
|
|
@@ -441,7 +507,12 @@ async def run(
|
|
|
441
507
|
profiler.mark("logging.setup.done")
|
|
442
508
|
|
|
443
509
|
profiler.mark("session.resolve.start")
|
|
444
|
-
session, mode = _resolve_session(
|
|
510
|
+
session, mode = _resolve_session(
|
|
511
|
+
agent,
|
|
512
|
+
resume_session_id,
|
|
513
|
+
cwd=project_root,
|
|
514
|
+
profiler=profiler,
|
|
515
|
+
)
|
|
445
516
|
profiler.mark("session.resolve.done")
|
|
446
517
|
# setup_tui_logging 在 _resolve_session 前安装,用于捕获 session 初始化期 warning/error。
|
|
447
518
|
# 这些日志没有交互 anchor,需在恢复历史或首次用户输入前落为 standalone log。
|
|
@@ -469,7 +540,7 @@ async def run(
|
|
|
469
540
|
)
|
|
470
541
|
|
|
471
542
|
async def _mcp_loader() -> None:
|
|
472
|
-
await _preload_mcp_in_tui(session)
|
|
543
|
+
await _preload_mcp_in_tui(session, profiler=profiler.child("mcp"))
|
|
473
544
|
mgr = session.runtime._mcp_manager
|
|
474
545
|
if mgr and mgr.failed_servers:
|
|
475
546
|
aliases = [alias for alias, _ in mgr.failed_servers]
|
|
@@ -206,6 +206,25 @@ class StatusBar:
|
|
|
206
206
|
turn = getattr(agent, "_turn_number", None)
|
|
207
207
|
return f"#{turn}" if turn is not None else "-"
|
|
208
208
|
|
|
209
|
+
def _context_percent_refresh_started(self) -> bool:
|
|
210
|
+
for owner in (
|
|
211
|
+
self._session,
|
|
212
|
+
getattr(self._session, "_agent", None),
|
|
213
|
+
getattr(getattr(self._session, "_agent", None), "_context", None),
|
|
214
|
+
):
|
|
215
|
+
if owner is None:
|
|
216
|
+
continue
|
|
217
|
+
turn = getattr(owner, "_turn_number", None)
|
|
218
|
+
if turn is None:
|
|
219
|
+
turn = getattr(owner, "turn_number", None)
|
|
220
|
+
if turn is None:
|
|
221
|
+
continue
|
|
222
|
+
try:
|
|
223
|
+
return int(turn) > 0
|
|
224
|
+
except (TypeError, ValueError):
|
|
225
|
+
continue
|
|
226
|
+
return True
|
|
227
|
+
|
|
209
228
|
def _resolve_project_cwd(self) -> str:
|
|
210
229
|
cwd = getattr(self._session, "_cwd", None)
|
|
211
230
|
if cwd is None:
|
|
@@ -260,21 +279,22 @@ class StatusBar:
|
|
|
260
279
|
return " ".join(parts) if parts else "-"
|
|
261
280
|
|
|
262
281
|
async def refresh(self) -> None:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
282
|
+
if self._context_percent_refresh_started():
|
|
283
|
+
try:
|
|
284
|
+
ctx_info = await self._session.get_context_info()
|
|
285
|
+
utilization = float(getattr(ctx_info, "utilization_percent", 0.0))
|
|
286
|
+
except Exception:
|
|
287
|
+
return
|
|
288
|
+
|
|
289
|
+
normalized = max(0.0, min(utilization, 100.0))
|
|
290
|
+
self._context_used_pct = normalized
|
|
291
|
+
self._context_left_pct = max(0.0, 100.0 - normalized)
|
|
268
292
|
|
|
269
293
|
try:
|
|
270
294
|
self._mode = str(self._session.get_mode()).strip().lower() or "act"
|
|
271
295
|
except Exception:
|
|
272
296
|
pass
|
|
273
297
|
|
|
274
|
-
normalized = max(0.0, min(utilization, 100.0))
|
|
275
|
-
self._context_used_pct = normalized
|
|
276
|
-
self._context_left_pct = max(0.0, 100.0 - normalized)
|
|
277
|
-
|
|
278
298
|
branch_task = asyncio.to_thread(self._resolve_git_branch)
|
|
279
299
|
now = time.monotonic()
|
|
280
300
|
diff_is_stale = (
|
|
@@ -338,7 +358,7 @@ class StatusBar:
|
|
|
338
358
|
return max(24, min(width - 6, 72))
|
|
339
359
|
|
|
340
360
|
def context_left_text(self) -> str:
|
|
341
|
-
return f"{self._context_left_pct:.0f}%
|
|
361
|
+
return f"Context {self._context_left_pct:.0f}% left"
|
|
342
362
|
|
|
343
363
|
def _status_text_for_width(self, width: int) -> str:
|
|
344
364
|
mode_text = f"[{self._mode}]"
|
|
@@ -1770,7 +1770,7 @@ class TerminalAgentTUI(
|
|
|
1770
1770
|
try:
|
|
1771
1771
|
if profiler is not None:
|
|
1772
1772
|
profiler.mark("plugins.init.start")
|
|
1773
|
-
await self._init_plugins()
|
|
1773
|
+
await self._init_plugins(profiler=profiler.child("plugins") if profiler is not None else None)
|
|
1774
1774
|
if profiler is not None:
|
|
1775
1775
|
profiler.mark("plugins.init.done")
|
|
1776
1776
|
except asyncio.CancelledError:
|
|
@@ -1784,6 +1784,8 @@ class TerminalAgentTUI(
|
|
|
1784
1784
|
finally:
|
|
1785
1785
|
self._refresh_layers()
|
|
1786
1786
|
|
|
1787
|
+
if profiler is not None:
|
|
1788
|
+
profiler.mark("plugins.task.create")
|
|
1787
1789
|
self._plugin_init_task = asyncio.create_task(
|
|
1788
1790
|
_do_plugin_init(),
|
|
1789
1791
|
name="terminal-plugin-init",
|
|
@@ -1803,11 +1805,15 @@ class TerminalAgentTUI(
|
|
|
1803
1805
|
finally:
|
|
1804
1806
|
self._refresh_layers()
|
|
1805
1807
|
|
|
1808
|
+
if profiler is not None:
|
|
1809
|
+
profiler.mark("mcp.task.create")
|
|
1806
1810
|
self._mcp_init_task = asyncio.create_task(
|
|
1807
1811
|
_do_init(),
|
|
1808
1812
|
name="terminal-mcp-init",
|
|
1809
1813
|
)
|
|
1810
1814
|
|
|
1815
|
+
if profiler is not None:
|
|
1816
|
+
profiler.mark("ui_tick.task.create")
|
|
1811
1817
|
self._ui_tick_task = asyncio.create_task(
|
|
1812
1818
|
self._ui_tick(),
|
|
1813
1819
|
name="terminal-ui-tick",
|
|
@@ -1826,11 +1832,15 @@ class TerminalAgentTUI(
|
|
|
1826
1832
|
self._refresh_layers()
|
|
1827
1833
|
self._invalidate()
|
|
1828
1834
|
|
|
1835
|
+
if profiler is not None:
|
|
1836
|
+
profiler.mark("status_bar.refresh.task.create")
|
|
1829
1837
|
self._startup_status_refresh_task = asyncio.create_task(
|
|
1830
1838
|
_do_startup_status_refresh(),
|
|
1831
1839
|
name="terminal-startup-status-refresh",
|
|
1832
1840
|
)
|
|
1833
1841
|
if hasattr(self, "_session") and self._session is not None:
|
|
1842
|
+
if profiler is not None:
|
|
1843
|
+
profiler.mark("event_pump.task.create")
|
|
1834
1844
|
self._event_pump_task = asyncio.create_task(
|
|
1835
1845
|
self._consume_event_stream(),
|
|
1836
1846
|
name="terminal-session-event-pump",
|
|
@@ -1894,12 +1904,16 @@ class TerminalAgentTUI(
|
|
|
1894
1904
|
return Path(session_cwd).expanduser().resolve()
|
|
1895
1905
|
return Path.cwd().expanduser().resolve()
|
|
1896
1906
|
|
|
1897
|
-
async def _init_plugins(self) -> None:
|
|
1907
|
+
async def _init_plugins(self, *, profiler: Any | None = None) -> None:
|
|
1898
1908
|
"""Load and inject plugin resources at startup."""
|
|
1909
|
+
if profiler is not None:
|
|
1910
|
+
profiler.mark("load_thread.start")
|
|
1899
1911
|
loaded_plugins, plugin_errors = await asyncio.to_thread(
|
|
1900
1912
|
_load_plugins_blocking,
|
|
1901
1913
|
self._plugin_project_path(),
|
|
1902
1914
|
)
|
|
1915
|
+
if profiler is not None:
|
|
1916
|
+
profiler.mark("load_thread.done")
|
|
1903
1917
|
if self._closing:
|
|
1904
1918
|
logger.debug("Plugin load completed after TUI close; discarding results")
|
|
1905
1919
|
return
|
|
@@ -1907,7 +1921,11 @@ class TerminalAgentTUI(
|
|
|
1907
1921
|
self._loaded_plugins = loaded_plugins
|
|
1908
1922
|
self._plugin_errors = plugin_errors
|
|
1909
1923
|
self._plugin_command_names: set[str] = set()
|
|
1924
|
+
if profiler is not None:
|
|
1925
|
+
profiler.mark("inject.start")
|
|
1910
1926
|
self._inject_plugin_resources(self._loaded_plugins)
|
|
1927
|
+
if profiler is not None:
|
|
1928
|
+
profiler.mark("inject.done")
|
|
1911
1929
|
logger.info(
|
|
1912
1930
|
"Loaded %d plugins (%d errors)",
|
|
1913
1931
|
len(self._loaded_plugins),
|
|
@@ -70,33 +70,53 @@ async def check_update(
|
|
|
70
70
|
package: str = PACKAGE_NAME,
|
|
71
71
|
release_notes_url: str = RELEASE_NOTES_URL,
|
|
72
72
|
log: logging.Logger | None = None,
|
|
73
|
+
profiler: Any | None = None,
|
|
73
74
|
) -> UpdateInfo | None:
|
|
74
75
|
"""异步检查 comate-cli 是否有新版本,返回结构化版本信息或 None。"""
|
|
75
76
|
active_logger = log or logger
|
|
76
77
|
try:
|
|
78
|
+
if profiler is not None:
|
|
79
|
+
profiler.mark("current_version.start")
|
|
77
80
|
current = importlib.metadata.version(package)
|
|
78
81
|
except importlib.metadata.PackageNotFoundError:
|
|
79
82
|
return None
|
|
83
|
+
finally:
|
|
84
|
+
if profiler is not None:
|
|
85
|
+
profiler.mark("current_version.done")
|
|
80
86
|
|
|
87
|
+
if profiler is not None:
|
|
88
|
+
profiler.mark("locale.start")
|
|
81
89
|
if _is_chinese_locale():
|
|
82
90
|
url = f"https://mirrors.tuna.tsinghua.edu.cn/pypi/{package}/json"
|
|
83
91
|
source_label = "tuna"
|
|
84
92
|
else:
|
|
85
93
|
url = f"https://pypi.org/pypi/{package}/json"
|
|
86
94
|
source_label = "pypi"
|
|
95
|
+
if profiler is not None:
|
|
96
|
+
profiler.mark("locale.done")
|
|
87
97
|
|
|
88
98
|
try:
|
|
99
|
+
if profiler is not None:
|
|
100
|
+
profiler.mark("httpx_import.start")
|
|
89
101
|
import httpx
|
|
102
|
+
if profiler is not None:
|
|
103
|
+
profiler.mark("httpx_import.done")
|
|
90
104
|
|
|
91
105
|
async with httpx.AsyncClient(timeout=3.0) as client:
|
|
106
|
+
if profiler is not None:
|
|
107
|
+
profiler.mark("http_get.start")
|
|
92
108
|
resp = await client.get(url)
|
|
93
109
|
resp.raise_for_status()
|
|
94
110
|
latest = resp.json()["info"]["version"]
|
|
111
|
+
if profiler is not None:
|
|
112
|
+
profiler.mark("http_get.done")
|
|
95
113
|
except Exception:
|
|
96
114
|
active_logger.debug(f"update check failed (source={source_label})", exc_info=True)
|
|
97
115
|
return None
|
|
98
116
|
|
|
99
117
|
try:
|
|
118
|
+
if profiler is not None:
|
|
119
|
+
profiler.mark("version_compare.start")
|
|
100
120
|
from packaging.version import Version
|
|
101
121
|
|
|
102
122
|
if Version(latest) > Version(current):
|
|
@@ -113,6 +133,9 @@ async def check_update(
|
|
|
113
133
|
latest,
|
|
114
134
|
exc_info=True,
|
|
115
135
|
)
|
|
136
|
+
finally:
|
|
137
|
+
if profiler is not None:
|
|
138
|
+
profiler.mark("version_compare.done")
|
|
116
139
|
return None
|
|
117
140
|
|
|
118
141
|
|
|
@@ -4,10 +4,10 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "comate-cli"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.5"
|
|
8
8
|
description = "Comate terminal CLI built on comate-agent-sdk"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.11"
|
|
10
|
+
requires-python = ">=3.11,<3.14"
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "Andy", email = "andy.dev@aliyun.com" }
|
|
13
13
|
]
|
|
@@ -64,7 +64,11 @@ class TestAppStartupLatency(unittest.IsolatedAsyncioTestCase):
|
|
|
64
64
|
"run_preflight_if_needed",
|
|
65
65
|
AsyncMock(return_value=PreflightResult(status="configured")),
|
|
66
66
|
),
|
|
67
|
-
patch.object(
|
|
67
|
+
patch.object(
|
|
68
|
+
app_module,
|
|
69
|
+
"_build_agent",
|
|
70
|
+
side_effect=lambda *, project_root, profiler=None: order.append("build_agent") or object(),
|
|
71
|
+
),
|
|
68
72
|
patch.object(app_module, "print_logo", side_effect=lambda *args, **kwargs: order.append("print_logo")),
|
|
69
73
|
patch.object(app_module, "_check_update", AsyncMock(return_value=None)),
|
|
70
74
|
patch.object(app_module, "EventRenderer", return_value=MagicMock()),
|
|
@@ -149,7 +153,8 @@ class TestAppStartupLatency(unittest.IsolatedAsyncioTestCase):
|
|
|
149
153
|
await asyncio.wait_for(update_started.wait(), timeout=0.1)
|
|
150
154
|
self.run_called.set()
|
|
151
155
|
|
|
152
|
-
async def _never_finishes() -> UpdateInfo | None:
|
|
156
|
+
async def _never_finishes(*, profiler=None) -> UpdateInfo | None:
|
|
157
|
+
del profiler
|
|
153
158
|
update_started.set()
|
|
154
159
|
try:
|
|
155
160
|
await asyncio.Event().wait()
|
|
@@ -190,7 +195,8 @@ class TestAppStartupLatency(unittest.IsolatedAsyncioTestCase):
|
|
|
190
195
|
super().__init__(session, status_bar, renderer)
|
|
191
196
|
created_tuis.append(self)
|
|
192
197
|
|
|
193
|
-
async def _raises() -> UpdateInfo | None:
|
|
198
|
+
async def _raises(*, profiler=None) -> UpdateInfo | None:
|
|
199
|
+
del profiler
|
|
194
200
|
raise RuntimeError("network down")
|
|
195
201
|
|
|
196
202
|
with (
|
|
@@ -34,7 +34,7 @@ class _FakeStatusBar:
|
|
|
34
34
|
return "act"
|
|
35
35
|
|
|
36
36
|
def info_status_text(self) -> str:
|
|
37
|
-
return "model | ~main / 100%
|
|
37
|
+
return "model | ~main / Context 100% left"
|
|
38
38
|
|
|
39
39
|
def info_status_fragments(self) -> list[tuple[str, str]]:
|
|
40
40
|
return [("class:status", self.info_status_text())]
|
|
@@ -15,11 +15,23 @@ from comate_cli.terminal_agent.tui import TerminalAgentTUI
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class _RecordingProfiler:
|
|
18
|
-
def __init__(self) -> None:
|
|
19
|
-
self.
|
|
18
|
+
def __init__(self, prefix: str = "", phases: list[str] | None = None) -> None:
|
|
19
|
+
self._prefix = prefix.strip(".")
|
|
20
|
+
self.phases = phases if phases is not None else []
|
|
20
21
|
|
|
21
22
|
def mark(self, phase: str) -> None:
|
|
22
|
-
|
|
23
|
+
normalized = phase.strip(".")
|
|
24
|
+
if self._prefix:
|
|
25
|
+
normalized = f"{self._prefix}.{normalized}"
|
|
26
|
+
self.phases.append(normalized)
|
|
27
|
+
|
|
28
|
+
def child(self, prefix: str) -> "_RecordingProfiler":
|
|
29
|
+
normalized = prefix.strip(".")
|
|
30
|
+
if self._prefix and normalized:
|
|
31
|
+
normalized = f"{self._prefix}.{normalized}"
|
|
32
|
+
elif self._prefix:
|
|
33
|
+
normalized = self._prefix
|
|
34
|
+
return _RecordingProfiler(prefix=normalized, phases=self.phases)
|
|
23
35
|
|
|
24
36
|
|
|
25
37
|
class _FakeRenderer:
|
|
@@ -129,7 +141,7 @@ class TestStartupProfilerTUI(unittest.IsolatedAsyncioTestCase):
|
|
|
129
141
|
tui._mcp_init_cancel_timeout_s = 0.05
|
|
130
142
|
tui._mcp_init_task = None
|
|
131
143
|
tui._ui_tick_task = None
|
|
132
|
-
tui._init_plugins = lambda: asyncio.sleep(0)
|
|
144
|
+
tui._init_plugins = lambda *, profiler=None: asyncio.sleep(0)
|
|
133
145
|
|
|
134
146
|
async def _fake_ui_tick() -> None:
|
|
135
147
|
await asyncio.Event().wait()
|
|
@@ -144,8 +156,116 @@ class TestStartupProfilerTUI(unittest.IsolatedAsyncioTestCase):
|
|
|
144
156
|
self.assertIn("tui.run_async.enter", profiler.phases)
|
|
145
157
|
self.assertIn("tui.run_async.done", profiler.phases)
|
|
146
158
|
|
|
159
|
+
async def test_tui_run_marks_background_task_creation_boundaries(self) -> None:
|
|
160
|
+
tui = TerminalAgentTUI.__new__(TerminalAgentTUI)
|
|
161
|
+
tui._app = _FakeApp()
|
|
162
|
+
tui._renderer = _FakeRenderer()
|
|
163
|
+
tui._refresh_layers = lambda: None
|
|
164
|
+
tui._busy = False
|
|
165
|
+
tui._closing = False
|
|
166
|
+
tui._mcp_init_cancel_timeout_s = 0.05
|
|
167
|
+
tui._mcp_init_task = None
|
|
168
|
+
tui._ui_tick_task = None
|
|
169
|
+
tui._init_plugins = lambda *, profiler=None: asyncio.sleep(0)
|
|
170
|
+
tui._status_bar = SimpleNamespace(refresh=AsyncMock())
|
|
171
|
+
tui._session = object()
|
|
172
|
+
tui._consume_event_stream = lambda: asyncio.sleep(0)
|
|
173
|
+
|
|
174
|
+
async def _fake_ui_tick() -> None:
|
|
175
|
+
await asyncio.Event().wait()
|
|
176
|
+
|
|
177
|
+
async def _fake_mcp_init() -> None:
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
tui._ui_tick = _fake_ui_tick
|
|
181
|
+
profiler = _RecordingProfiler()
|
|
182
|
+
|
|
183
|
+
with patch("comate_cli.terminal_agent.tui.patch_stdout", _noop_patch_stdout):
|
|
184
|
+
await tui.run(mcp_init=_fake_mcp_init, profiler=profiler)
|
|
185
|
+
|
|
186
|
+
self.assertIn("plugins.task.create", profiler.phases)
|
|
187
|
+
self.assertIn("mcp.task.create", profiler.phases)
|
|
188
|
+
self.assertIn("ui_tick.task.create", profiler.phases)
|
|
189
|
+
self.assertIn("status_bar.refresh.task.create", profiler.phases)
|
|
190
|
+
self.assertIn("event_pump.task.create", profiler.phases)
|
|
191
|
+
|
|
192
|
+
async def test_init_plugins_marks_load_and_inject_boundaries(self) -> None:
|
|
193
|
+
tui = TerminalAgentTUI.__new__(TerminalAgentTUI)
|
|
194
|
+
tui._closing = False
|
|
195
|
+
tui._loaded_plugins = []
|
|
196
|
+
tui._plugin_errors = []
|
|
197
|
+
tui._plugin_project_path = lambda: Path("/tmp/project")
|
|
198
|
+
tui._inject_plugin_resources = lambda plugins: None
|
|
199
|
+
profiler = _RecordingProfiler()
|
|
200
|
+
|
|
201
|
+
with patch(
|
|
202
|
+
"comate_cli.terminal_agent.tui._load_plugins_blocking",
|
|
203
|
+
return_value=([], []),
|
|
204
|
+
):
|
|
205
|
+
await tui._init_plugins(profiler=profiler.child("plugins"))
|
|
206
|
+
|
|
207
|
+
self.assertIn("plugins.load_thread.start", profiler.phases)
|
|
208
|
+
self.assertIn("plugins.load_thread.done", profiler.phases)
|
|
209
|
+
self.assertIn("plugins.inject.start", profiler.phases)
|
|
210
|
+
self.assertIn("plugins.inject.done", profiler.phases)
|
|
211
|
+
|
|
147
212
|
|
|
148
213
|
class TestStartupProfilerApp(unittest.IsolatedAsyncioTestCase):
|
|
214
|
+
async def test_build_agent_marks_config_and_construct_boundaries(self) -> None:
|
|
215
|
+
profiler = _RecordingProfiler()
|
|
216
|
+
|
|
217
|
+
class _FakeAgent:
|
|
218
|
+
def __init__(self, *, config) -> None:
|
|
219
|
+
self.config = config
|
|
220
|
+
|
|
221
|
+
with (
|
|
222
|
+
patch.object(app_module, "_resolve_cli_project_root", return_value=Path("/tmp/project")),
|
|
223
|
+
patch("comate_cli.terminal_agent.config.store.load") as load_config,
|
|
224
|
+
patch.object(app_module, "Agent", _FakeAgent),
|
|
225
|
+
):
|
|
226
|
+
load_config.return_value = SimpleNamespace(
|
|
227
|
+
token_cost_enabled=False,
|
|
228
|
+
memory_background_enabled=True,
|
|
229
|
+
memory_dreaming_enabled=True,
|
|
230
|
+
compaction_threshold_ratio=0.9,
|
|
231
|
+
)
|
|
232
|
+
app_module._build_agent(project_root=Path("/tmp/project"), profiler=profiler)
|
|
233
|
+
|
|
234
|
+
self.assertIn("agent.config.load.start", profiler.phases)
|
|
235
|
+
self.assertIn("agent.config.load.done", profiler.phases)
|
|
236
|
+
self.assertIn("agent.construct.start", profiler.phases)
|
|
237
|
+
self.assertIn("agent.construct.done", profiler.phases)
|
|
238
|
+
|
|
239
|
+
async def test_background_update_check_marks_internal_boundaries(self) -> None:
|
|
240
|
+
profiler = _RecordingProfiler()
|
|
241
|
+
|
|
242
|
+
with patch.object(app_module, "check_update", AsyncMock(return_value=None)):
|
|
243
|
+
await app_module._check_update(profiler=profiler.child("update_check"))
|
|
244
|
+
|
|
245
|
+
self.assertIn("update_check.impl.start", profiler.phases)
|
|
246
|
+
self.assertIn("update_check.impl.done", profiler.phases)
|
|
247
|
+
|
|
248
|
+
async def test_mcp_preload_marks_start_task_and_await_boundaries(self) -> None:
|
|
249
|
+
preload_task = asyncio.create_task(asyncio.sleep(0))
|
|
250
|
+
manager = SimpleNamespace(failed_servers=[], tool_infos=[])
|
|
251
|
+
runtime = SimpleNamespace(
|
|
252
|
+
config=SimpleNamespace(mcp_enabled=True),
|
|
253
|
+
start_mcp_preload=MagicMock(return_value=preload_task),
|
|
254
|
+
_mcp_manager=manager,
|
|
255
|
+
)
|
|
256
|
+
session = SimpleNamespace(runtime=runtime)
|
|
257
|
+
profiler = _RecordingProfiler()
|
|
258
|
+
|
|
259
|
+
await app_module._preload_mcp_in_tui(session, profiler=profiler.child("mcp")) # type: ignore[arg-type]
|
|
260
|
+
|
|
261
|
+
self.assertIn("mcp.preload.start", profiler.phases)
|
|
262
|
+
self.assertIn("mcp.start_preload.start", profiler.phases)
|
|
263
|
+
self.assertIn("mcp.start_preload.done", profiler.phases)
|
|
264
|
+
self.assertIn("mcp.await_preload.start", profiler.phases)
|
|
265
|
+
self.assertIn("mcp.await_preload.done", profiler.phases)
|
|
266
|
+
self.assertIn("mcp.inspect_manager.start", profiler.phases)
|
|
267
|
+
self.assertIn("mcp.inspect_manager.done", profiler.phases)
|
|
268
|
+
|
|
149
269
|
async def test_app_run_emits_startup_phase_logs_when_enabled(self) -> None:
|
|
150
270
|
fake_session = MagicMock()
|
|
151
271
|
fake_session.session_id = "session-1"
|