comate-cli 0.8.3__tar.gz → 0.8.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.8.3 → comate_cli-0.8.5}/PKG-INFO +2 -1
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/event_renderer.py +103 -1
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui.py +13 -3
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/render_panels.py +49 -14
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/update_check.py +76 -98
- {comate_cli-0.8.3 → comate_cli-0.8.5}/docs/cli-rpc-stdio-guide.md +6 -1
- {comate_cli-0.8.3 → comate_cli-0.8.5}/pyproject.toml +2 -1
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/recap/test_idle_recap_controller.py +55 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_app_startup_latency.py +6 -12
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_completion_status_panel.py +62 -0
- comate_cli-0.8.5/tests/test_event_renderer_tool_args_progress.py +193 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_handle_error.py +20 -1
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_rpc_protocol.py +41 -1
- comate_cli-0.8.5/tests/test_tui_provider_error_shield.py +516 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_update_check.py +193 -61
- {comate_cli-0.8.3 → comate_cli-0.8.5}/uv.lock +4 -2
- {comate_cli-0.8.3 → comate_cli-0.8.5}/.gitignore +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/CHANGELOG.md +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/CONTEXT.md +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/README.md +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/__init__.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/__main__.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/main.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/app.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/builtin_commands/.keep +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/__init__.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/model.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/picker.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/picker_state.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/store.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/figures.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/file_ref_hint.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/history_printer.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/logging_adapter.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/operation_state.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/preflight_wizard.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/print_mode.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/recap/__init__.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/recap/controller.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/slash_commands.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/startup_profile.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/status_bar.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/model.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/picker.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/store.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_fold.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_result_store.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/dist-comate/.gitignore +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/dist-comate/comate_cli-0.7.10-py3-none-any.whl +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/dist-comate/comate_cli-0.7.10.tar.gz +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/dist-hico/.gitignore +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/dist-hico/hico_cli-0.7.17-py3-none-any.whl +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/dist-hico/hico_cli-0.7.17.tar.gz +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/config/__init__.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/config/test_model.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/config/test_picker_state.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/config/test_picker_ui.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/config/test_roundtrip.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/config/test_store_load.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/config/test_store_save.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/conftest.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/recap/test_recap_history_rendering.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/recap/test_tui_idle_recap_integration.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/statusline/__init__.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/statusline/test_model.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/statusline/test_picker_state.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/statusline/test_store.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_app_token_cost_config.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_context_command.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_event_renderer.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_event_renderer_streaming.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_event_renderer_tool_fold.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_file_ref_hint.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_format_error.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_goal_resume_tui.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_goal_resume_view.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_goal_slash_command.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_history_printer.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_history_printer_log.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_history_printer_subtitle_position.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_history_printer_tool_fold.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_history_sync.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_history_sync_tool_fold.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_input_history.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_logging_adapter.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_logo.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_main_args.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_markdown_render.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_model_switch_command.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_plugin_operation_state.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_plugin_slash_commands.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_preflight.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_question_view.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_resume_picker.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_skills_slash_command.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_slash_clear.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_startup_import_budget.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_startup_profile.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_status_bar.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_task_poll.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tips.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tool_fold.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tool_fold_panel.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tool_result_formatters.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tool_view.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_transcript_viewer.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_transcript_viewer_tool_fold.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_builtin_slash_chain.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_llm_retry_panel.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_local_interactive_barrier.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_paste_newline_guard.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_queue_preview.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_queue_sdk_source.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_scheduled_fire_log.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_send_undo_window.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_startup_latency.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_thinking_display.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_usage_command.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tests/test_wrap_user_text.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/tools/logo_lab.py +0 -0
- {comate_cli-0.8.3 → comate_cli-0.8.5}/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.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
|
|
@@ -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
|
|
@@ -35,8 +35,10 @@ from comate_agent_sdk.agent.events import (
|
|
|
35
35
|
ThinkingDeltaEvent,
|
|
36
36
|
TaskUpdatedEvent,
|
|
37
37
|
ToolCallEvent,
|
|
38
|
+
ToolCallArgsProgressEvent,
|
|
38
39
|
ToolCallStartEvent,
|
|
39
40
|
ToolResultEvent,
|
|
41
|
+
StreamDisplayResetEvent,
|
|
40
42
|
UsageDeltaEvent,
|
|
41
43
|
UserQuestionEvent,
|
|
42
44
|
)
|
|
@@ -81,6 +83,9 @@ _DEFAULT_TOOL_PANEL_MAX_LINES = 4
|
|
|
81
83
|
_DEFAULT_TASK_PANEL_MAX_LINES = 6
|
|
82
84
|
_RECENT_TEAM_EVENT_CACHE_SIZE = 128
|
|
83
85
|
_SYSTEM_MESSAGE_DEDUPE_WINDOW_SECONDS = 0.5
|
|
86
|
+
TOOL_ARGS_PROGRESS_VISIBLE_TOKENS = 1000
|
|
87
|
+
TOOL_ARGS_PROGRESS_VISIBLE_AFTER_SECONDS = 1.5
|
|
88
|
+
TOOL_ARGS_PROGRESS_VISIBLE_MIN_TOKENS_AFTER_DELAY = 256
|
|
84
89
|
|
|
85
90
|
|
|
86
91
|
def _truncate(content: str, max_len: int = 120) -> str:
|
|
@@ -129,6 +134,13 @@ def _format_tokens(token_count: int) -> str:
|
|
|
129
134
|
return f"{compact}k tok"
|
|
130
135
|
|
|
131
136
|
|
|
137
|
+
def _format_tool_args_tokens(token_count: int) -> str:
|
|
138
|
+
tokens = max(int(token_count), 0)
|
|
139
|
+
if tokens < 1_000:
|
|
140
|
+
return f"{tokens} tokens"
|
|
141
|
+
return f"{tokens / 1_000:.1f}k tokens"
|
|
142
|
+
|
|
143
|
+
|
|
132
144
|
def _split_fragments_by_newline(
|
|
133
145
|
fragments: list[tuple[str, str]],
|
|
134
146
|
) -> list[list[tuple[str, str]]]:
|
|
@@ -245,6 +257,16 @@ class _RunningTool:
|
|
|
245
257
|
subagent_model_name: str = ""
|
|
246
258
|
|
|
247
259
|
|
|
260
|
+
@dataclass
|
|
261
|
+
class _ToolArgsProgressState:
|
|
262
|
+
tool_call_id: str
|
|
263
|
+
tool_name: str
|
|
264
|
+
started_at_monotonic: float
|
|
265
|
+
last_updated_at_monotonic: float
|
|
266
|
+
estimated_tokens: int = 0
|
|
267
|
+
visible: bool = False
|
|
268
|
+
|
|
269
|
+
|
|
248
270
|
@dataclass(frozen=True)
|
|
249
271
|
class ActiveLLMRetrySnapshot:
|
|
250
272
|
retry_id: str
|
|
@@ -309,6 +331,7 @@ class EventRenderer:
|
|
|
309
331
|
self._fallback_tool_result_sequence = 0
|
|
310
332
|
self._assistant_buffer = ""
|
|
311
333
|
self._pending_tool_starts: set[str] = set()
|
|
334
|
+
self._tool_args_progress: dict[str, _ToolArgsProgressState] = {}
|
|
312
335
|
self._loading_state: LoadingState = LoadingState.idle()
|
|
313
336
|
self._current_tasks: list[dict[str, Any]] = []
|
|
314
337
|
self._current_task_title: str | None = None
|
|
@@ -668,6 +691,55 @@ class EventRenderer:
|
|
|
668
691
|
self._text_delta_started = False
|
|
669
692
|
self._active_text_message_id = None
|
|
670
693
|
|
|
694
|
+
def _clear_tool_args_progress(self, tool_call_id: str | None = None) -> None:
|
|
695
|
+
if tool_call_id is None:
|
|
696
|
+
self._tool_args_progress.clear()
|
|
697
|
+
return
|
|
698
|
+
normalized_id = str(tool_call_id or "").strip()
|
|
699
|
+
if normalized_id:
|
|
700
|
+
self._tool_args_progress.pop(normalized_id, None)
|
|
701
|
+
|
|
702
|
+
def _start_tool_args_progress(self, tool_call_id: str, tool_name: str) -> None:
|
|
703
|
+
normalized_id = str(tool_call_id or "").strip()
|
|
704
|
+
if not normalized_id:
|
|
705
|
+
return
|
|
706
|
+
now = time.monotonic()
|
|
707
|
+
state = self._tool_args_progress.get(normalized_id)
|
|
708
|
+
if state is None:
|
|
709
|
+
self._tool_args_progress[normalized_id] = _ToolArgsProgressState(
|
|
710
|
+
tool_call_id=normalized_id,
|
|
711
|
+
tool_name=str(tool_name or "").strip(),
|
|
712
|
+
started_at_monotonic=now,
|
|
713
|
+
last_updated_at_monotonic=now,
|
|
714
|
+
)
|
|
715
|
+
return
|
|
716
|
+
if tool_name:
|
|
717
|
+
state.tool_name = str(tool_name).strip()
|
|
718
|
+
|
|
719
|
+
def _handle_tool_args_progress(self, event: ToolCallArgsProgressEvent) -> None:
|
|
720
|
+
normalized_id = str(event.tool_call_id or "").strip()
|
|
721
|
+
if not normalized_id:
|
|
722
|
+
return
|
|
723
|
+
state = self._tool_args_progress.get(normalized_id)
|
|
724
|
+
if state is None:
|
|
725
|
+
return
|
|
726
|
+
now = time.monotonic()
|
|
727
|
+
state.last_updated_at_monotonic = now
|
|
728
|
+
if event.tool:
|
|
729
|
+
state.tool_name = str(event.tool).strip()
|
|
730
|
+
state.estimated_tokens = max(int(event.estimated_tokens), 0)
|
|
731
|
+
if state.visible:
|
|
732
|
+
return
|
|
733
|
+
elapsed = now - state.started_at_monotonic
|
|
734
|
+
state.visible = (
|
|
735
|
+
state.estimated_tokens >= TOOL_ARGS_PROGRESS_VISIBLE_TOKENS
|
|
736
|
+
or (
|
|
737
|
+
elapsed >= TOOL_ARGS_PROGRESS_VISIBLE_AFTER_SECONDS
|
|
738
|
+
and state.estimated_tokens
|
|
739
|
+
>= TOOL_ARGS_PROGRESS_VISIBLE_MIN_TOKENS_AFTER_DELAY
|
|
740
|
+
)
|
|
741
|
+
)
|
|
742
|
+
|
|
671
743
|
def _should_drop_duplicate_system_message(
|
|
672
744
|
self,
|
|
673
745
|
*,
|
|
@@ -699,6 +771,7 @@ class EventRenderer:
|
|
|
699
771
|
self._active_llm_retry = None
|
|
700
772
|
self._flush_assistant_segment()
|
|
701
773
|
self._pending_tool_starts.clear()
|
|
774
|
+
self._clear_tool_args_progress()
|
|
702
775
|
self._turn_received_text_delta = False
|
|
703
776
|
self._turn_received_thinking_delta = False
|
|
704
777
|
self._text_delta_started = False
|
|
@@ -760,6 +833,7 @@ class EventRenderer:
|
|
|
760
833
|
self._flush_assistant_segment()
|
|
761
834
|
self.flush_pending_logs()
|
|
762
835
|
self._pending_tool_starts.clear()
|
|
836
|
+
self._clear_tool_args_progress()
|
|
763
837
|
self._rebuild_loading_line()
|
|
764
838
|
|
|
765
839
|
def tick_progress(self) -> None:
|
|
@@ -782,6 +856,7 @@ class EventRenderer:
|
|
|
782
856
|
self._flush_assistant_segment()
|
|
783
857
|
self._commit_active_tool_fold()
|
|
784
858
|
self._pending_tool_starts.clear()
|
|
859
|
+
self._clear_tool_args_progress()
|
|
785
860
|
# spec §6.4:用户中断 → 5 个新字段全部丢弃不入队
|
|
786
861
|
self._drop_all_pending()
|
|
787
862
|
self._rebuild_loading_line()
|
|
@@ -802,6 +877,7 @@ class EventRenderer:
|
|
|
802
877
|
self._active_tool_fold.clear()
|
|
803
878
|
self._assistant_buffer = ""
|
|
804
879
|
self._pending_tool_starts.clear()
|
|
880
|
+
self._clear_tool_args_progress()
|
|
805
881
|
self._turn_received_text_delta = False
|
|
806
882
|
self._turn_received_thinking_delta = False
|
|
807
883
|
self._text_delta_started = False
|
|
@@ -869,6 +945,15 @@ class EventRenderer:
|
|
|
869
945
|
拼接到 spinner phrase。无容器时返回 ""。"""
|
|
870
946
|
return self._loading_aux_text
|
|
871
947
|
|
|
948
|
+
def tool_args_progress_text(self) -> str:
|
|
949
|
+
visible_states = [
|
|
950
|
+
state for state in self._tool_args_progress.values() if state.visible
|
|
951
|
+
]
|
|
952
|
+
if not visible_states:
|
|
953
|
+
return ""
|
|
954
|
+
total_tokens = sum(state.estimated_tokens for state in visible_states)
|
|
955
|
+
return f"↓ {_format_tool_args_tokens(total_tokens)}"
|
|
956
|
+
|
|
872
957
|
def active_llm_retry_snapshot(self) -> ActiveLLMRetrySnapshot | None:
|
|
873
958
|
return self._active_llm_retry
|
|
874
959
|
|
|
@@ -1467,7 +1552,14 @@ class EventRenderer:
|
|
|
1467
1552
|
def handle_event(self, event: Any) -> tuple[bool, list[dict[str, Any]] | None]:
|
|
1468
1553
|
if not isinstance(
|
|
1469
1554
|
event,
|
|
1470
|
-
(
|
|
1555
|
+
(
|
|
1556
|
+
TextEvent,
|
|
1557
|
+
TextDeltaEvent,
|
|
1558
|
+
ThinkingDeltaEvent,
|
|
1559
|
+
ToolCallStartEvent,
|
|
1560
|
+
ToolCallArgsProgressEvent,
|
|
1561
|
+
StreamDisplayResetEvent,
|
|
1562
|
+
),
|
|
1471
1563
|
):
|
|
1472
1564
|
self._flush_assistant_segment()
|
|
1473
1565
|
|
|
@@ -1498,6 +1590,12 @@ class EventRenderer:
|
|
|
1498
1590
|
normalized_id = str(tool_call_id or "").strip()
|
|
1499
1591
|
if normalized_id and normalized_id not in self._pending_tool_starts:
|
|
1500
1592
|
self._pending_tool_starts.add(normalized_id)
|
|
1593
|
+
self._start_tool_args_progress(normalized_id, tool_name)
|
|
1594
|
+
case ToolCallArgsProgressEvent():
|
|
1595
|
+
self._handle_tool_args_progress(event)
|
|
1596
|
+
case StreamDisplayResetEvent(reason="stream_fallback"):
|
|
1597
|
+
self._pending_tool_starts.clear()
|
|
1598
|
+
self._clear_tool_args_progress()
|
|
1501
1599
|
case ThinkingEvent(content=thinking):
|
|
1502
1600
|
if self._turn_received_thinking_delta:
|
|
1503
1601
|
pass
|
|
@@ -1566,6 +1664,7 @@ class EventRenderer:
|
|
|
1566
1664
|
severity="warning",
|
|
1567
1665
|
)
|
|
1568
1666
|
case ToolCallEvent(tool=tool_name, args=arguments, tool_call_id=tool_call_id):
|
|
1667
|
+
self._clear_tool_args_progress(tool_call_id)
|
|
1569
1668
|
args_dict = arguments if isinstance(arguments, dict) else {"_raw": str(arguments)}
|
|
1570
1669
|
# Store args for ToolResult phase lookup.
|
|
1571
1670
|
self._tool_call_args[tool_call_id] = args_dict
|
|
@@ -1584,6 +1683,7 @@ class EventRenderer:
|
|
|
1584
1683
|
return (False, None)
|
|
1585
1684
|
self._append_tool_call(tool_name, args_dict, tool_call_id)
|
|
1586
1685
|
case ToolResultEvent(tool=tool_name, result=result, tool_call_id=tool_call_id, is_error=is_error, metadata=metadata, output=output):
|
|
1686
|
+
self._clear_tool_args_progress(tool_call_id)
|
|
1587
1687
|
stored_args = self._tool_call_args.pop(tool_call_id, {})
|
|
1588
1688
|
if self._active_tool_fold.has_call(tool_call_id):
|
|
1589
1689
|
display_name = resolve_display_tool_name(tool_name, stored_args)
|
|
@@ -1764,11 +1864,13 @@ class EventRenderer:
|
|
|
1764
1864
|
# Cancellation/error paths may emit StepCompleteEvent without ToolResultEvent.
|
|
1765
1865
|
# Ensure tool panel state is cleaned up by step id.
|
|
1766
1866
|
self._running_tools.pop(step_id, None)
|
|
1867
|
+
self._clear_tool_args_progress(step_id)
|
|
1767
1868
|
case StopEvent(reason=reason):
|
|
1768
1869
|
self._active_llm_retry = None
|
|
1769
1870
|
self._flush_assistant_segment()
|
|
1770
1871
|
self._commit_active_tool_fold()
|
|
1771
1872
|
self._pending_tool_starts.clear()
|
|
1873
|
+
self._clear_tool_args_progress()
|
|
1772
1874
|
# Safety net: if any running tool rows remain, stop event means this turn is ending.
|
|
1773
1875
|
self._running_tools.clear()
|
|
1774
1876
|
self._hidden_thinking_badge_text = ""
|
|
@@ -1491,10 +1491,20 @@ class TerminalAgentTUI(
|
|
|
1491
1491
|
self._interrupt_requested_at = None
|
|
1492
1492
|
self._allow_latest_recall_on_interrupt = False
|
|
1493
1493
|
self._set_busy(False)
|
|
1494
|
-
await self.
|
|
1494
|
+
await self._refresh_status_bar_safely(reason="error-cleanup")
|
|
1495
1495
|
self._renderer.clear_auto_compacting()
|
|
1496
1496
|
self._refresh_layers()
|
|
1497
1497
|
|
|
1498
|
+
async def _refresh_status_bar_safely(self, *, reason: str) -> None:
|
|
1499
|
+
try:
|
|
1500
|
+
await self._status_bar.refresh()
|
|
1501
|
+
except Exception:
|
|
1502
|
+
logger.warning(
|
|
1503
|
+
"TUI status bar refresh failed during %s; continuing event pump",
|
|
1504
|
+
reason,
|
|
1505
|
+
exc_info=True,
|
|
1506
|
+
)
|
|
1507
|
+
|
|
1498
1508
|
async def _submit_user_message(
|
|
1499
1509
|
self,
|
|
1500
1510
|
text: str,
|
|
@@ -1646,7 +1656,7 @@ class TerminalAgentTUI(
|
|
|
1646
1656
|
f"✻ Running scheduled task ({self._format_scheduled_fire_time()})"
|
|
1647
1657
|
)
|
|
1648
1658
|
if isinstance(event, GoalUpdatedEvent):
|
|
1649
|
-
await self.
|
|
1659
|
+
await self._refresh_status_bar_safely(reason="goal-update")
|
|
1650
1660
|
self._refresh_layers()
|
|
1651
1661
|
self._invalidate()
|
|
1652
1662
|
continue
|
|
@@ -1730,7 +1740,7 @@ class TerminalAgentTUI(
|
|
|
1730
1740
|
self._append_run_elapsed_to_history(stop_reason=stop_reason)
|
|
1731
1741
|
self._run_start_time = None
|
|
1732
1742
|
self._set_busy(False)
|
|
1733
|
-
await self.
|
|
1743
|
+
await self._refresh_status_bar_safely(reason="stop-event")
|
|
1734
1744
|
|
|
1735
1745
|
if waiting_for_input:
|
|
1736
1746
|
self._waiting_for_input = True
|
|
@@ -823,35 +823,55 @@ class RenderPanelsMixin:
|
|
|
823
823
|
return ""
|
|
824
824
|
return str(badge_fn() or "").strip()
|
|
825
825
|
|
|
826
|
+
def _tool_args_progress_text(self) -> str:
|
|
827
|
+
progress_fn = getattr(self._renderer, "tool_args_progress_text", None)
|
|
828
|
+
if not callable(progress_fn):
|
|
829
|
+
return ""
|
|
830
|
+
return str(progress_fn() or "").strip()
|
|
831
|
+
|
|
826
832
|
@staticmethod
|
|
827
833
|
def _fragments_width(frags: list[tuple[str, str]]) -> int:
|
|
828
834
|
return sum(get_cwidth(char) for _, text in frags for char in text)
|
|
829
835
|
|
|
830
|
-
def _running_status_suffix_fragments(
|
|
836
|
+
def _running_status_suffix_fragments(
|
|
837
|
+
self,
|
|
838
|
+
*,
|
|
839
|
+
max_width: int | None = None,
|
|
840
|
+
) -> list[tuple[str, str]]:
|
|
831
841
|
"""Build the dim parenthetical status suffix for active work."""
|
|
832
842
|
status_style = "fg:#6B7280"
|
|
833
843
|
badge_style = "fg:#FBBF24 bold"
|
|
834
844
|
|
|
835
845
|
run_start: float | None = getattr(self, "_run_start_time", None)
|
|
846
|
+
args_progress = self._tool_args_progress_text()
|
|
836
847
|
badge = self._hidden_thinking_badge_text()
|
|
837
|
-
if run_start is None and not badge:
|
|
848
|
+
if run_start is None and not args_progress and not badge:
|
|
838
849
|
return []
|
|
839
850
|
|
|
840
851
|
parts: list[tuple[str, str]] = [(status_style, " (")]
|
|
841
|
-
|
|
842
|
-
if
|
|
852
|
+
has_parts = False
|
|
853
|
+
if run_start is not None:
|
|
843
854
|
elapsed = time.monotonic() - run_start
|
|
844
|
-
parts.append(
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
)
|
|
849
|
-
)
|
|
855
|
+
parts.append((status_style, self._format_run_elapsed(elapsed)))
|
|
856
|
+
has_parts = True
|
|
857
|
+
if args_progress:
|
|
858
|
+
if has_parts:
|
|
859
|
+
parts.append((status_style, f" {BULLET} "))
|
|
860
|
+
parts.append((status_style, args_progress))
|
|
861
|
+
has_parts = True
|
|
862
|
+
if run_start is not None:
|
|
863
|
+
if has_parts:
|
|
864
|
+
parts.append((status_style, f" {BULLET} "))
|
|
865
|
+
parts.append((status_style, "ctrl+c to interrupt"))
|
|
866
|
+
has_parts = True
|
|
850
867
|
if badge:
|
|
851
|
-
if
|
|
868
|
+
if has_parts:
|
|
852
869
|
parts.append((status_style, f" {BULLET} "))
|
|
853
870
|
parts.append((badge_style, badge))
|
|
854
871
|
parts.append((status_style, ")"))
|
|
872
|
+
if max_width is not None and self._fragments_width(parts) > max_width:
|
|
873
|
+
clipped = fit_single_line("".join(text for _, text in parts), max_width)
|
|
874
|
+
return [(status_style, clipped)]
|
|
855
875
|
return parts
|
|
856
876
|
|
|
857
877
|
def _visible_loading_tip(self) -> str:
|
|
@@ -897,7 +917,9 @@ class RenderPanelsMixin:
|
|
|
897
917
|
) -> list[tuple[str, str]]:
|
|
898
918
|
"""用低噪音 spinner + 流光走字渲染 loading 文案."""
|
|
899
919
|
width = self._terminal_width()
|
|
900
|
-
status_suffix = self._running_status_suffix_fragments(
|
|
920
|
+
status_suffix = self._running_status_suffix_fragments(
|
|
921
|
+
max_width=max(width - 10, 8)
|
|
922
|
+
)
|
|
901
923
|
|
|
902
924
|
# glyph(1) + space(1) = 2,再减去运行状态后缀宽度
|
|
903
925
|
phrase_budget = width - 2 - self._fragments_width(status_suffix)
|
|
@@ -921,7 +943,10 @@ class RenderPanelsMixin:
|
|
|
921
943
|
frags: list[tuple[str, str]],
|
|
922
944
|
) -> list[tuple[str, str]]:
|
|
923
945
|
"""Append the running status suffix before any Rich trailing newline."""
|
|
924
|
-
|
|
946
|
+
width = self._terminal_width()
|
|
947
|
+
status_suffix = self._running_status_suffix_fragments(
|
|
948
|
+
max_width=max(width - 8, 8)
|
|
949
|
+
)
|
|
925
950
|
if not status_suffix:
|
|
926
951
|
return frags
|
|
927
952
|
# Rich __rich_console__ 末尾会输出一个 "\n" segment,需要先剥掉再追加,
|
|
@@ -930,7 +955,17 @@ class RenderPanelsMixin:
|
|
|
930
955
|
trimmed = list(frags)
|
|
931
956
|
while trimmed and trimmed[-1][1] == "\n":
|
|
932
957
|
tail.insert(0, trimmed.pop())
|
|
933
|
-
|
|
958
|
+
base_width = self._fragments_width(trimmed)
|
|
959
|
+
suffix_width = self._fragments_width(status_suffix)
|
|
960
|
+
if base_width + suffix_width <= width:
|
|
961
|
+
return trimmed + status_suffix + tail
|
|
962
|
+
|
|
963
|
+
base_budget = max(width - suffix_width, 8)
|
|
964
|
+
clipped_base = fit_single_line(
|
|
965
|
+
"".join(text for _, text in trimmed),
|
|
966
|
+
base_budget,
|
|
967
|
+
)
|
|
968
|
+
return [("class:loading", clipped_base)] + status_suffix + tail
|
|
934
969
|
|
|
935
970
|
def _loading_height(self) -> int:
|
|
936
971
|
if self._should_hide_loading_panel():
|
|
@@ -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:
|
|
@@ -217,8 +217,10 @@ stdio bridge 明确不承担历史重放职责。需要历史请走 `comate resu
|
|
|
217
217
|
| `TextEvent` | 文本块最终落地(入历史) | `content`, `structured_output`, `force_display` |
|
|
218
218
|
| `ThinkingDeltaEvent` / `ThinkingEvent` | 思维链增量 / 落地 | `content`/`delta` |
|
|
219
219
|
| `ToolCallStartEvent` | 工具调用开始(transient) | `tool_call_id`, `tool` |
|
|
220
|
+
| `ToolCallArgsProgressEvent` | 工具参数仍在流式生成中的 UI-only 进度(transient) | `tool_call_id`, `tool`, `estimated_tokens` |
|
|
220
221
|
| `ToolCallEvent` | 工具调用入参确定 | `tool`, `args`, `tool_call_id`, `input` |
|
|
221
222
|
| `ToolResultEvent` | 工具返回 | `tool`, `result`, `is_error`, `output` |
|
|
223
|
+
| `StreamDisplayResetEvent` | 内部 transient display reset;stream fallback 前要求客户端清理 partial display state;facade 不公开 | `reason` |
|
|
222
224
|
| `StepStartEvent` / `StepCompleteEvent` | 单步边界 | — |
|
|
223
225
|
| `StopEvent` | 流终止信号 | `reason` |
|
|
224
226
|
| `UsageDeltaEvent` | token 用量 | — |
|
|
@@ -228,7 +230,10 @@ stdio bridge 明确不承担历史重放职责。需要历史请走 `comate resu
|
|
|
228
230
|
**消费建议**:
|
|
229
231
|
- 只关心最终答案:忽略所有 `*DeltaEvent`,只收集 `TextEvent`,以 `StopEvent` 为终止。
|
|
230
232
|
- 想做打字机效果:按 `message_id` 累加 `TextDeltaEvent`,`TextEvent` 到达后用 `force_display` 决定是否覆盖。
|
|
231
|
-
- 工具调用可视化:用 `ToolCallStartEvent` → `ToolCallEvent` → `ToolResultEvent
|
|
233
|
+
- 工具调用可视化:用 `ToolCallStartEvent` → 可选多个 `ToolCallArgsProgressEvent` → `ToolCallEvent` → `ToolResultEvent`。
|
|
234
|
+
- stream fallback:收到 `StreamDisplayResetEvent(reason="stream_fallback")` 时清理任何 pending tool args progress / pending tool start 展示;不要把它显示为 assistant 内容。
|
|
235
|
+
|
|
236
|
+
`ToolCallArgsProgressEvent` 与 `StreamDisplayResetEvent` 都不是 assistant message content,也不应作为 transcript 内容持久化。`ToolCallArgsProgressEvent.estimated_tokens` 只用于 UI estimate,不进入 provider usage、context token budget 或 compaction 阈值。`ToolCallArgsProgressEvent` 在 facade 中属于 `include_partial_messages=True` 时才公开的 partial event;`StreamDisplayResetEvent` 是 CLI/RPC internal transient display control,facade adapter 会忽略。
|
|
232
237
|
|
|
233
238
|
事件被序列化时,dataclass 字段直接展开到 `params`,并补一个 `event_type` 字段。无法被 JSON 序列化的对象会 fallback 为 `str(value)`。
|
|
234
239
|
|
|
@@ -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.5"
|
|
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]
|