comate-cli 0.8.4__tar.gz → 0.8.6__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.8.4 → comate_cli-0.8.6}/PKG-INFO +2 -1
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/error_display.py +40 -41
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/event_renderer.py +2 -2
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/status_bar.py +92 -46
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tool_view.py +2 -2
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui.py +27 -3
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/update_check.py +76 -98
- {comate_cli-0.8.4 → comate_cli-0.8.6}/pyproject.toml +2 -1
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_app_startup_latency.py +6 -12
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_format_error.py +69 -18
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_handle_error.py +20 -1
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_status_bar.py +103 -42
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_provider_error_shield.py +131 -1
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_update_check.py +193 -61
- {comate_cli-0.8.4 → comate_cli-0.8.6}/uv.lock +4 -2
- {comate_cli-0.8.4 → comate_cli-0.8.6}/.gitignore +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/CHANGELOG.md +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/CONTEXT.md +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/README.md +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/__main__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/main.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/app.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/builtin_commands/.keep +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/config/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/config/model.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/config/picker.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/config/picker_state.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/config/store.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/figures.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/file_ref_hint.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/history_printer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/logging_adapter.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/operation_state.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/preflight_wizard.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/print_mode.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/recap/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/recap/controller.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/slash_commands.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/startup_profile.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/statusline/model.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/statusline/picker.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/statusline/store.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tool_fold.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tool_result_store.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/dist-comate/.gitignore +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/dist-comate/comate_cli-0.7.10-py3-none-any.whl +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/dist-comate/comate_cli-0.7.10.tar.gz +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/dist-hico/.gitignore +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/dist-hico/hico_cli-0.7.17-py3-none-any.whl +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/dist-hico/hico_cli-0.7.17.tar.gz +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/docs/cli-rpc-stdio-guide.md +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/config/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/config/test_model.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/config/test_picker_state.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/config/test_picker_ui.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/config/test_roundtrip.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/config/test_store_load.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/config/test_store_save.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/conftest.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/recap/test_idle_recap_controller.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/recap/test_recap_history_rendering.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/recap/test_tui_idle_recap_integration.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/statusline/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/statusline/test_model.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/statusline/test_picker_state.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/statusline/test_store.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_app_token_cost_config.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_completion_status_panel.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_context_command.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_event_renderer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_event_renderer_streaming.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_event_renderer_tool_args_progress.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_event_renderer_tool_fold.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_file_ref_hint.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_goal_resume_tui.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_goal_resume_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_goal_slash_command.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_history_printer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_history_printer_log.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_history_printer_subtitle_position.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_history_printer_tool_fold.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_history_sync.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_history_sync_tool_fold.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_input_history.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_logging_adapter.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_logo.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_main_args.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_markdown_render.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_model_switch_command.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_plugin_operation_state.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_plugin_slash_commands.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_preflight.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_question_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_resume_picker.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_skills_slash_command.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_slash_clear.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_startup_import_budget.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_startup_profile.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_task_poll.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tips.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tool_fold.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tool_fold_panel.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tool_result_formatters.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tool_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_transcript_viewer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_transcript_viewer_tool_fold.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_builtin_slash_chain.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_llm_retry_panel.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_local_interactive_barrier.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_paste_newline_guard.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_queue_preview.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_queue_sdk_source.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_scheduled_fire_log.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_send_undo_window.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_startup_latency.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_thinking_display.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_usage_command.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tests/test_wrap_user_text.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tools/logo_lab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.6}/tools/nico_preview.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comate-cli
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.6
|
|
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
|
|
@@ -18,6 +18,7 @@ Requires-Dist: charset-normalizer==3.4.7
|
|
|
18
18
|
Requires-Dist: comate-agent-sdk<0.9.20,>=0.8.0a1
|
|
19
19
|
Requires-Dist: concurrent-log-handler>=0.9.25
|
|
20
20
|
Requires-Dist: curl-cffi==0.13.0
|
|
21
|
+
Requires-Dist: httpx<0.29.0,>=0.28.1
|
|
21
22
|
Requires-Dist: packaging>=21.0
|
|
22
23
|
Requires-Dist: pillow==12.2.0
|
|
23
24
|
Requires-Dist: prompt-toolkit>=3.0
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Unified error formatter for terminal agent."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import ast
|
|
5
|
+
import json
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
def format_error(exc: Exception) -> tuple[str, str, str]:
|
|
@@ -42,27 +43,14 @@ def format_error(exc: Exception) -> tuple[str, str, str]:
|
|
|
42
43
|
|
|
43
44
|
if exc_type == "ModelProviderError":
|
|
44
45
|
code = getattr(exc, "status_code", None)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"error",
|
|
54
|
-
)
|
|
55
|
-
return f"API error: {exc_msg}", "API error", "error"
|
|
56
|
-
|
|
57
|
-
if code == 401:
|
|
58
|
-
return "Invalid or expired API key", "Auth failed", "error"
|
|
59
|
-
if code == 403:
|
|
60
|
-
return "Access denied to this model", "Access denied", "error"
|
|
61
|
-
if code == 404:
|
|
62
|
-
return "Model not found or invalid API path", "Model not found", "error"
|
|
63
|
-
if code and code >= 500:
|
|
64
|
-
return f"Server error ({code})", "Server error", "warning"
|
|
65
|
-
return f"API error: {exc_msg}", "API error", "error"
|
|
46
|
+
detail = _provider_error_detail(exc_msg)
|
|
47
|
+
status = f" (HTTP {code})" if code is not None else ""
|
|
48
|
+
message = f"Provider error{status}"
|
|
49
|
+
if detail:
|
|
50
|
+
message = f"{message}: {detail}"
|
|
51
|
+
transient = f"Provider error {code}" if code is not None else "Provider error"
|
|
52
|
+
severity = "warning" if code is not None and code >= 500 else "error"
|
|
53
|
+
return message, transient, severity
|
|
66
54
|
|
|
67
55
|
# Session errors
|
|
68
56
|
if exc_type == "ChatSessionClosedError":
|
|
@@ -79,23 +67,34 @@ def format_error(exc: Exception) -> tuple[str, str, str]:
|
|
|
79
67
|
return f"Error: {exc_msg}", "Error occurred", "error"
|
|
80
68
|
|
|
81
69
|
|
|
82
|
-
def
|
|
83
|
-
"""
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
70
|
+
def _provider_error_detail(msg: str) -> str:
|
|
71
|
+
"""Return provider-supplied detail, unwrapped from common SDK envelopes."""
|
|
72
|
+
text = str(msg or "").strip()
|
|
73
|
+
if not text:
|
|
74
|
+
return ""
|
|
75
|
+
|
|
76
|
+
candidates = [text]
|
|
77
|
+
if " - " in text:
|
|
78
|
+
candidates.append(text.split(" - ", 1)[1].strip())
|
|
79
|
+
|
|
80
|
+
for candidate in candidates:
|
|
81
|
+
detail = _error_message_from_mapping_text(candidate)
|
|
82
|
+
if detail:
|
|
83
|
+
return detail
|
|
84
|
+
return text
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _error_message_from_mapping_text(text: str) -> str | None:
|
|
88
|
+
for parser in (json.loads, ast.literal_eval):
|
|
89
|
+
try:
|
|
90
|
+
value = parser(text)
|
|
91
|
+
except (ValueError, SyntaxError, TypeError):
|
|
92
|
+
continue
|
|
93
|
+
if not isinstance(value, dict):
|
|
94
|
+
continue
|
|
95
|
+
error = value.get("error")
|
|
96
|
+
if isinstance(error, dict):
|
|
97
|
+
message = error.get("message")
|
|
98
|
+
if isinstance(message, str) and message.strip():
|
|
99
|
+
return message.strip()
|
|
101
100
|
return None
|
|
@@ -120,10 +120,10 @@ def _format_duration(seconds: float) -> str:
|
|
|
120
120
|
minutes = int(elapsed // 60)
|
|
121
121
|
remaining_seconds = int(elapsed % 60)
|
|
122
122
|
if minutes < 60:
|
|
123
|
-
return f"{minutes}m{remaining_seconds:02d}s"
|
|
123
|
+
return f"{minutes}m {remaining_seconds:02d}s"
|
|
124
124
|
hours = minutes // 60
|
|
125
125
|
remaining_minutes = minutes % 60
|
|
126
|
-
return f"{hours}h{remaining_minutes:02d}m"
|
|
126
|
+
return f"{hours}h {remaining_minutes:02d}m"
|
|
127
127
|
|
|
128
128
|
|
|
129
129
|
def _format_tokens(token_count: int) -> str:
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import contextlib
|
|
4
5
|
import logging
|
|
6
|
+
import os
|
|
5
7
|
import subprocess
|
|
6
8
|
import time
|
|
7
9
|
from pathlib import Path
|
|
@@ -22,11 +24,17 @@ class GitDiffStats(NamedTuple):
|
|
|
22
24
|
removed: int
|
|
23
25
|
|
|
24
26
|
|
|
27
|
+
class _GitCommandResult(NamedTuple):
|
|
28
|
+
returncode: int
|
|
29
|
+
stdout: str
|
|
30
|
+
|
|
31
|
+
|
|
25
32
|
class StatusBar:
|
|
26
33
|
_DEFAULT_TERMINAL_WIDTH: int = 100
|
|
27
34
|
_MIN_TERMINAL_WIDTH: int = 40
|
|
28
35
|
_GIT_DIFF_CACHE_SECONDS: float = 5.0
|
|
29
36
|
_GIT_CMD_TIMEOUT_SECONDS: float = 0.2
|
|
37
|
+
_GIT_FAILURE_BACKOFF_SECONDS: float = 30.0
|
|
30
38
|
_SEPARATOR: str = " \u00b7 " # " · "
|
|
31
39
|
|
|
32
40
|
# 分组颜色
|
|
@@ -46,6 +54,7 @@ class StatusBar:
|
|
|
46
54
|
self._context_left_pct: float = 100.0
|
|
47
55
|
self._git_diff_stats: GitDiffStats | None = None
|
|
48
56
|
self._git_diff_cache_time: float = 0.0
|
|
57
|
+
self._git_failure_backoff_until: float = 0.0
|
|
49
58
|
self._transient_message: str | None = None
|
|
50
59
|
self._transient_severity: str = "info"
|
|
51
60
|
self._transient_until: float | None = None
|
|
@@ -129,43 +138,76 @@ class StatusBar:
|
|
|
129
138
|
normalized = str(mode).strip().lower()
|
|
130
139
|
self._mode = normalized or "act"
|
|
131
140
|
|
|
132
|
-
|
|
133
|
-
|
|
141
|
+
def _git_backoff_active(self) -> bool:
|
|
142
|
+
return time.monotonic() < self._git_failure_backoff_until
|
|
143
|
+
|
|
144
|
+
def _record_git_failure_backoff(self, *, reason: str, args: tuple[str, ...]) -> None:
|
|
145
|
+
self._git_failure_backoff_until = (
|
|
146
|
+
time.monotonic() + self._GIT_FAILURE_BACKOFF_SECONDS
|
|
147
|
+
)
|
|
148
|
+
logger.debug(
|
|
149
|
+
"status bar git refresh disabled temporarily: reason=%s args=%s",
|
|
150
|
+
reason,
|
|
151
|
+
" ".join(args),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
async def _run_git_command(self, *args: str) -> _GitCommandResult | None:
|
|
155
|
+
if self._git_backoff_active():
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
env = os.environ.copy()
|
|
159
|
+
env.setdefault("GIT_TERMINAL_PROMPT", "0")
|
|
160
|
+
env.setdefault("GIT_OPTIONAL_LOCKS", "0")
|
|
161
|
+
env.setdefault("GCM_INTERACTIVE", "Never")
|
|
162
|
+
proc: asyncio.subprocess.Process | None = None
|
|
134
163
|
try:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
164
|
+
proc = await asyncio.create_subprocess_exec(
|
|
165
|
+
"git",
|
|
166
|
+
*args,
|
|
167
|
+
stdin=subprocess.DEVNULL,
|
|
168
|
+
stdout=asyncio.subprocess.PIPE,
|
|
169
|
+
stderr=subprocess.DEVNULL,
|
|
170
|
+
env=env,
|
|
142
171
|
)
|
|
143
|
-
|
|
144
|
-
|
|
172
|
+
stdout, _stderr = await asyncio.wait_for(
|
|
173
|
+
proc.communicate(),
|
|
174
|
+
timeout=self._GIT_CMD_TIMEOUT_SECONDS,
|
|
175
|
+
)
|
|
176
|
+
except asyncio.TimeoutError:
|
|
177
|
+
self._record_git_failure_backoff(reason="timeout", args=args)
|
|
178
|
+
if proc is not None and proc.returncode is None:
|
|
179
|
+
with contextlib.suppress(ProcessLookupError):
|
|
180
|
+
proc.kill()
|
|
181
|
+
with contextlib.suppress(Exception):
|
|
182
|
+
await asyncio.wait_for(proc.wait(), timeout=0.2)
|
|
183
|
+
return None
|
|
145
184
|
except Exception:
|
|
146
|
-
|
|
185
|
+
self._record_git_failure_backoff(reason="error", args=args)
|
|
186
|
+
if proc is not None and proc.returncode is None:
|
|
187
|
+
with contextlib.suppress(ProcessLookupError):
|
|
188
|
+
proc.kill()
|
|
189
|
+
with contextlib.suppress(Exception):
|
|
190
|
+
await asyncio.wait_for(proc.wait(), timeout=0.2)
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
return _GitCommandResult(
|
|
194
|
+
returncode=int(proc.returncode or 0),
|
|
195
|
+
stdout=stdout.decode("utf-8", errors="replace"),
|
|
196
|
+
)
|
|
147
197
|
|
|
198
|
+
async def _resolve_git_branch(self) -> str:
|
|
199
|
+
completed = await self._run_git_command("branch", "--show-current")
|
|
200
|
+
if completed is None:
|
|
201
|
+
return "N/A"
|
|
148
202
|
if completed.returncode != 0:
|
|
149
203
|
return "N/A"
|
|
150
204
|
branch = completed.stdout.strip()
|
|
151
205
|
return branch or "N/A"
|
|
152
206
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
completed = subprocess.run(
|
|
157
|
-
["git", "diff", "--shortstat", "--no-color"],
|
|
158
|
-
check=False,
|
|
159
|
-
capture_output=True,
|
|
160
|
-
text=True,
|
|
161
|
-
encoding="utf-8",
|
|
162
|
-
timeout=StatusBar._GIT_CMD_TIMEOUT_SECONDS,
|
|
163
|
-
)
|
|
164
|
-
except subprocess.TimeoutExpired:
|
|
165
|
-
return None
|
|
166
|
-
except Exception:
|
|
207
|
+
async def _resolve_git_diff_stats(self) -> GitDiffStats | None:
|
|
208
|
+
completed = await self._run_git_command("diff", "--shortstat", "--no-color")
|
|
209
|
+
if completed is None:
|
|
167
210
|
return None
|
|
168
|
-
|
|
169
211
|
if completed.returncode != 0:
|
|
170
212
|
return None
|
|
171
213
|
|
|
@@ -185,16 +227,6 @@ class StatusBar:
|
|
|
185
227
|
|
|
186
228
|
return GitDiffStats(added=added, removed=removed)
|
|
187
229
|
|
|
188
|
-
def _ensure_git_diff_stats(self) -> None:
|
|
189
|
-
now = time.monotonic()
|
|
190
|
-
if (
|
|
191
|
-
self._git_diff_stats is not None
|
|
192
|
-
and now - self._git_diff_cache_time < self._GIT_DIFF_CACHE_SECONDS
|
|
193
|
-
):
|
|
194
|
-
return
|
|
195
|
-
self._git_diff_stats = self._resolve_git_diff_stats()
|
|
196
|
-
self._git_diff_cache_time = now
|
|
197
|
-
|
|
198
230
|
def _resolve_llm_level(self) -> str:
|
|
199
231
|
agent = getattr(self._session, "_agent", None)
|
|
200
232
|
level = getattr(agent, "level", None)
|
|
@@ -294,23 +326,37 @@ class StatusBar:
|
|
|
294
326
|
except Exception:
|
|
295
327
|
pass
|
|
296
328
|
|
|
297
|
-
|
|
329
|
+
cfg = self._config
|
|
330
|
+
branch_task = (
|
|
331
|
+
asyncio.create_task(self._resolve_git_branch())
|
|
332
|
+
if cfg.show_git_branch
|
|
333
|
+
else None
|
|
334
|
+
)
|
|
298
335
|
now = time.monotonic()
|
|
299
336
|
diff_is_stale = (
|
|
300
|
-
|
|
301
|
-
|
|
337
|
+
cfg.show_git_diff
|
|
338
|
+
and (
|
|
339
|
+
self._git_diff_stats is None
|
|
340
|
+
or now - self._git_diff_cache_time >= self._GIT_DIFF_CACHE_SECONDS
|
|
341
|
+
)
|
|
302
342
|
)
|
|
303
|
-
|
|
304
|
-
|
|
343
|
+
diff_task = (
|
|
344
|
+
asyncio.create_task(self._resolve_git_diff_stats())
|
|
345
|
+
if diff_is_stale
|
|
346
|
+
else None
|
|
347
|
+
)
|
|
348
|
+
if branch_task is not None and diff_task is not None:
|
|
305
349
|
branch, diff_stats = await asyncio.gather(branch_task, diff_task)
|
|
350
|
+
self._git_branch = branch
|
|
306
351
|
self._git_diff_stats = diff_stats
|
|
307
352
|
self._git_diff_cache_time = time.monotonic()
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
353
|
+
elif branch_task is not None:
|
|
354
|
+
self._git_branch = await branch_task
|
|
355
|
+
elif diff_task is not None:
|
|
356
|
+
self._git_diff_stats = await diff_task
|
|
357
|
+
self._git_diff_cache_time = time.monotonic()
|
|
311
358
|
|
|
312
359
|
# Refresh token usage cache if any token-related items are enabled.
|
|
313
|
-
cfg = self._config
|
|
314
360
|
wants_token_counts = cfg.show_token_usage or cfg.show_token_input or cfg.show_token_output
|
|
315
361
|
wants_cost = cfg.show_session_cost
|
|
316
362
|
if wants_cost:
|
|
@@ -333,10 +333,10 @@ def _format_duration(seconds: float) -> str:
|
|
|
333
333
|
minutes = int(elapsed // 60)
|
|
334
334
|
remaining_seconds = int(elapsed % 60)
|
|
335
335
|
if minutes < 60:
|
|
336
|
-
return f"{minutes}m{remaining_seconds:02d}s"
|
|
336
|
+
return f"{minutes}m {remaining_seconds:02d}s"
|
|
337
337
|
hours = minutes // 60
|
|
338
338
|
remaining_minutes = minutes % 60
|
|
339
|
-
return f"{hours}h{remaining_minutes:02d}m"
|
|
339
|
+
return f"{hours}h {remaining_minutes:02d}m"
|
|
340
340
|
|
|
341
341
|
|
|
342
342
|
def _sweep_gradient_text(content: str, frame: int) -> Text:
|
|
@@ -186,6 +186,7 @@ class TerminalAgentTUI(
|
|
|
186
186
|
RenderPanelsMixin,
|
|
187
187
|
):
|
|
188
188
|
_NON_BLOCKING_UI_MODES = frozenset({UIMode.NORMAL})
|
|
189
|
+
_STATUS_REFRESH_TIMEOUT_SECONDS = 0.3
|
|
189
190
|
|
|
190
191
|
def __init__(
|
|
191
192
|
self,
|
|
@@ -206,6 +207,7 @@ class TerminalAgentTUI(
|
|
|
206
207
|
set_tool_result_registry(self._tool_result_registry)
|
|
207
208
|
self._task_poll_next_at = time.monotonic() + _TASK_POLL_INTERVAL_S
|
|
208
209
|
self._task_poll_last_list_id: str | None = None
|
|
210
|
+
self._status_refresh_timeout_s = self._STATUS_REFRESH_TIMEOUT_SECONDS
|
|
209
211
|
|
|
210
212
|
self._bind_team_message_callback(self._session)
|
|
211
213
|
|
|
@@ -1491,10 +1493,32 @@ class TerminalAgentTUI(
|
|
|
1491
1493
|
self._interrupt_requested_at = None
|
|
1492
1494
|
self._allow_latest_recall_on_interrupt = False
|
|
1493
1495
|
self._set_busy(False)
|
|
1494
|
-
await self.
|
|
1496
|
+
await self._refresh_status_bar_safely(reason="error-cleanup")
|
|
1495
1497
|
self._renderer.clear_auto_compacting()
|
|
1496
1498
|
self._refresh_layers()
|
|
1497
1499
|
|
|
1500
|
+
async def _refresh_status_bar_safely(self, *, reason: str) -> None:
|
|
1501
|
+
try:
|
|
1502
|
+
timeout_s = max(
|
|
1503
|
+
0.01,
|
|
1504
|
+
float(getattr(self, "_status_refresh_timeout_s", 0.3)),
|
|
1505
|
+
)
|
|
1506
|
+
await asyncio.wait_for(
|
|
1507
|
+
self._status_bar.refresh(),
|
|
1508
|
+
timeout=timeout_s,
|
|
1509
|
+
)
|
|
1510
|
+
except asyncio.TimeoutError:
|
|
1511
|
+
logger.debug(
|
|
1512
|
+
"TUI status bar refresh timed out during %s; continuing event pump",
|
|
1513
|
+
reason,
|
|
1514
|
+
)
|
|
1515
|
+
except Exception:
|
|
1516
|
+
logger.warning(
|
|
1517
|
+
"TUI status bar refresh failed during %s; continuing event pump",
|
|
1518
|
+
reason,
|
|
1519
|
+
exc_info=True,
|
|
1520
|
+
)
|
|
1521
|
+
|
|
1498
1522
|
async def _submit_user_message(
|
|
1499
1523
|
self,
|
|
1500
1524
|
text: str,
|
|
@@ -1646,7 +1670,7 @@ class TerminalAgentTUI(
|
|
|
1646
1670
|
f"✻ Running scheduled task ({self._format_scheduled_fire_time()})"
|
|
1647
1671
|
)
|
|
1648
1672
|
if isinstance(event, GoalUpdatedEvent):
|
|
1649
|
-
await self.
|
|
1673
|
+
await self._refresh_status_bar_safely(reason="goal-update")
|
|
1650
1674
|
self._refresh_layers()
|
|
1651
1675
|
self._invalidate()
|
|
1652
1676
|
continue
|
|
@@ -1730,7 +1754,7 @@ class TerminalAgentTUI(
|
|
|
1730
1754
|
self._append_run_elapsed_to_history(stop_reason=stop_reason)
|
|
1731
1755
|
self._run_start_time = None
|
|
1732
1756
|
self._set_busy(False)
|
|
1733
|
-
await self.
|
|
1757
|
+
await self._refresh_status_bar_safely(reason="stop-event")
|
|
1734
1758
|
|
|
1735
1759
|
if waiting_for_input:
|
|
1736
1760
|
self._waiting_for_input = True
|
|
@@ -110,47 +110,12 @@ async def check_update(
|
|
|
110
110
|
profiler: Any | None = None,
|
|
111
111
|
) -> UpdateInfo | None:
|
|
112
112
|
"""异步检查 comate-cli 是否有新版本,返回结构化版本信息或 None。"""
|
|
113
|
-
if profiler is not None:
|
|
114
|
-
profiler.mark("thread.await.start")
|
|
115
|
-
try:
|
|
116
|
-
return await asyncio.to_thread(
|
|
117
|
-
check_update_blocking,
|
|
118
|
-
package=package,
|
|
119
|
-
release_notes_url=release_notes_url,
|
|
120
|
-
log=log,
|
|
121
|
-
profiler=profiler,
|
|
122
|
-
)
|
|
123
|
-
except asyncio.CancelledError:
|
|
124
|
-
if profiler is not None:
|
|
125
|
-
profiler.mark("thread.await.cancelled")
|
|
126
|
-
raise
|
|
127
|
-
except Exception:
|
|
128
|
-
if profiler is not None:
|
|
129
|
-
profiler.mark("thread.await.failed")
|
|
130
|
-
active_logger = log or logger
|
|
131
|
-
active_logger.debug("update check worker failed", exc_info=True)
|
|
132
|
-
return None
|
|
133
|
-
finally:
|
|
134
|
-
if profiler is not None:
|
|
135
|
-
profiler.mark("thread.await.done")
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def check_update_blocking(
|
|
139
|
-
*,
|
|
140
|
-
package: str = PACKAGE_NAME,
|
|
141
|
-
release_notes_url: str = RELEASE_NOTES_URL,
|
|
142
|
-
log: logging.Logger | None = None,
|
|
143
|
-
profiler: Any | None = None,
|
|
144
|
-
) -> UpdateInfo | None:
|
|
145
|
-
"""同步执行版本检查;由 async wrapper 放入 worker thread,避免阻塞 TUI event loop。"""
|
|
146
113
|
active_logger = log or logger
|
|
114
|
+
source_label = "unknown"
|
|
147
115
|
try:
|
|
148
116
|
if profiler is not None:
|
|
149
|
-
profiler.mark("
|
|
150
|
-
|
|
117
|
+
profiler.mark("current_version.start")
|
|
151
118
|
try:
|
|
152
|
-
if profiler is not None:
|
|
153
|
-
profiler.mark("current_version.start")
|
|
154
119
|
current = importlib.metadata.version(package)
|
|
155
120
|
except importlib.metadata.PackageNotFoundError:
|
|
156
121
|
return None
|
|
@@ -170,74 +135,87 @@ def check_update_blocking(
|
|
|
170
135
|
profiler.mark("locale.done")
|
|
171
136
|
|
|
172
137
|
try:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if profiler is not None:
|
|
181
|
-
profiler.mark(
|
|
182
|
-
"http_client.trust_env.enabled"
|
|
183
|
-
if trust_env
|
|
184
|
-
else "http_client.trust_env.disabled"
|
|
185
|
-
)
|
|
186
|
-
profiler.mark("http_client.construct.start")
|
|
187
|
-
client_kwargs: dict[str, Any] = {"timeout": 3.0, "trust_env": trust_env}
|
|
188
|
-
if not trust_env:
|
|
189
|
-
# 默认路径用 certifi context 绕开 Windows 证书库枚举(见 _get_update_check_ssl_context)。
|
|
190
|
-
# trust_env 逃生通道(企业代理/自签根证书)保持 httpx 默认 verify=True 行为不变。
|
|
191
|
-
ssl_context = _get_update_check_ssl_context()
|
|
192
|
-
if ssl_context is not None:
|
|
193
|
-
client_kwargs["verify"] = ssl_context
|
|
194
|
-
client = httpx.Client(**client_kwargs)
|
|
195
|
-
if profiler is not None:
|
|
196
|
-
profiler.mark("http_client.construct.done")
|
|
197
|
-
|
|
198
|
-
if profiler is not None:
|
|
199
|
-
profiler.mark("http_client.enter.start")
|
|
200
|
-
with client:
|
|
201
|
-
if profiler is not None:
|
|
202
|
-
profiler.mark("http_client.enter.done")
|
|
203
|
-
profiler.mark("http_get.start")
|
|
204
|
-
resp = client.get(url)
|
|
205
|
-
resp.raise_for_status()
|
|
206
|
-
latest = resp.json()["info"]["version"]
|
|
207
|
-
if profiler is not None:
|
|
208
|
-
profiler.mark("http_get.done")
|
|
138
|
+
latest = await _fetch_latest_version_async(
|
|
139
|
+
url,
|
|
140
|
+
trust_env=_update_check_trust_env(),
|
|
141
|
+
profiler=profiler,
|
|
142
|
+
)
|
|
143
|
+
except asyncio.CancelledError:
|
|
144
|
+
raise
|
|
209
145
|
except Exception:
|
|
210
146
|
if profiler is not None:
|
|
211
147
|
profiler.mark("http_client.failed")
|
|
212
|
-
|
|
213
|
-
|
|
148
|
+
raise
|
|
149
|
+
except asyncio.CancelledError:
|
|
150
|
+
raise
|
|
151
|
+
except Exception:
|
|
152
|
+
active_logger.debug(f"update check failed (source={source_label})", exc_info=True)
|
|
153
|
+
return None
|
|
214
154
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
)
|
|
227
|
-
except Exception:
|
|
228
|
-
active_logger.debug(
|
|
229
|
-
"update comparison failed (current=%s, latest=%s)",
|
|
230
|
-
current,
|
|
231
|
-
latest,
|
|
232
|
-
exc_info=True,
|
|
155
|
+
try:
|
|
156
|
+
if profiler is not None:
|
|
157
|
+
profiler.mark("version_compare.start")
|
|
158
|
+
from packaging.version import Version
|
|
159
|
+
|
|
160
|
+
if Version(latest) > Version(current):
|
|
161
|
+
return UpdateInfo(
|
|
162
|
+
package=package,
|
|
163
|
+
current_version=current,
|
|
164
|
+
latest_version=latest,
|
|
165
|
+
release_notes_url=release_notes_url,
|
|
233
166
|
)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
167
|
+
except Exception:
|
|
168
|
+
active_logger.debug(
|
|
169
|
+
"update comparison failed (current=%s, latest=%s)",
|
|
170
|
+
current,
|
|
171
|
+
latest,
|
|
172
|
+
exc_info=True,
|
|
173
|
+
)
|
|
238
174
|
finally:
|
|
239
175
|
if profiler is not None:
|
|
240
|
-
profiler.mark("
|
|
176
|
+
profiler.mark("version_compare.done")
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
async def _fetch_latest_version_async(
|
|
181
|
+
url: str,
|
|
182
|
+
*,
|
|
183
|
+
trust_env: bool,
|
|
184
|
+
profiler: Any | None,
|
|
185
|
+
) -> str:
|
|
186
|
+
if profiler is not None:
|
|
187
|
+
profiler.mark("httpx_import.start")
|
|
188
|
+
import httpx
|
|
189
|
+
if profiler is not None:
|
|
190
|
+
profiler.mark("httpx_import.done")
|
|
191
|
+
profiler.mark(
|
|
192
|
+
"http_client.trust_env.enabled"
|
|
193
|
+
if trust_env
|
|
194
|
+
else "http_client.trust_env.disabled"
|
|
195
|
+
)
|
|
196
|
+
profiler.mark("http_client.construct.start")
|
|
197
|
+
|
|
198
|
+
client_kwargs: dict[str, Any] = {"timeout": 3.0, "trust_env": trust_env}
|
|
199
|
+
if not trust_env:
|
|
200
|
+
ssl_context = _get_update_check_ssl_context()
|
|
201
|
+
if ssl_context is not None:
|
|
202
|
+
client_kwargs["verify"] = ssl_context
|
|
203
|
+
|
|
204
|
+
client = httpx.AsyncClient(**client_kwargs)
|
|
205
|
+
if profiler is not None:
|
|
206
|
+
profiler.mark("http_client.construct.done")
|
|
207
|
+
profiler.mark("http_client.enter.start")
|
|
208
|
+
|
|
209
|
+
async with client:
|
|
210
|
+
if profiler is not None:
|
|
211
|
+
profiler.mark("http_client.enter.done")
|
|
212
|
+
profiler.mark("http_get.start")
|
|
213
|
+
resp = await client.get(url)
|
|
214
|
+
resp.raise_for_status()
|
|
215
|
+
latest = resp.json()["info"]["version"]
|
|
216
|
+
if profiler is not None:
|
|
217
|
+
profiler.mark("http_get.done")
|
|
218
|
+
return str(latest)
|
|
241
219
|
|
|
242
220
|
|
|
243
221
|
def format_update_hint(info: UpdateInfo) -> str:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "comate-cli"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.6"
|
|
8
8
|
description = "Comate terminal CLI built on comate-agent-sdk"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11,<3.14"
|
|
@@ -31,6 +31,7 @@ dependencies = [
|
|
|
31
31
|
"curl-cffi==0.13.0",
|
|
32
32
|
"requests==2.32.5",
|
|
33
33
|
"concurrent-log-handler>=0.9.25",
|
|
34
|
+
"httpx>=0.28.1,<0.29.0",
|
|
34
35
|
]
|
|
35
36
|
|
|
36
37
|
[project.urls]
|