comate-cli 0.8.4__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.4 → comate_cli-0.8.5}/PKG-INFO +2 -1
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui.py +13 -3
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/update_check.py +76 -98
- {comate_cli-0.8.4 → comate_cli-0.8.5}/pyproject.toml +2 -1
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_app_startup_latency.py +6 -12
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_handle_error.py +20 -1
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_provider_error_shield.py +90 -1
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_update_check.py +193 -61
- {comate_cli-0.8.4 → comate_cli-0.8.5}/uv.lock +4 -2
- {comate_cli-0.8.4 → comate_cli-0.8.5}/.gitignore +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/CHANGELOG.md +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/CONTEXT.md +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/README.md +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/__main__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/main.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/app.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/builtin_commands/.keep +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/model.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/picker.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/picker_state.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/store.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/event_renderer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/figures.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/file_ref_hint.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/history_printer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/logging_adapter.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/operation_state.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/preflight_wizard.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/print_mode.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/recap/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/recap/controller.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/slash_commands.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/startup_profile.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/status_bar.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/model.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/picker.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/store.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_fold.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_result_store.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/dist-comate/.gitignore +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/dist-comate/comate_cli-0.7.10-py3-none-any.whl +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/dist-comate/comate_cli-0.7.10.tar.gz +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/dist-hico/.gitignore +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/dist-hico/hico_cli-0.7.17-py3-none-any.whl +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/dist-hico/hico_cli-0.7.17.tar.gz +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/docs/cli-rpc-stdio-guide.md +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/config/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/config/test_model.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/config/test_picker_state.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/config/test_picker_ui.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/config/test_roundtrip.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/config/test_store_load.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/config/test_store_save.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/conftest.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/recap/test_idle_recap_controller.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/recap/test_recap_history_rendering.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/recap/test_tui_idle_recap_integration.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/statusline/__init__.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/statusline/test_model.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/statusline/test_picker_state.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/statusline/test_store.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_app_token_cost_config.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_completion_status_panel.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_context_command.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer_streaming.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer_tool_args_progress.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer_tool_fold.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_file_ref_hint.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_format_error.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_goal_resume_tui.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_goal_resume_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_goal_slash_command.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_history_printer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_history_printer_log.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_history_printer_subtitle_position.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_history_printer_tool_fold.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_history_sync.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_history_sync_tool_fold.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_input_history.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_logging_adapter.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_logo.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_main_args.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_markdown_render.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_model_switch_command.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_plugin_operation_state.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_plugin_slash_commands.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_preflight.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_question_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_resume_picker.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_skills_slash_command.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_slash_clear.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_startup_import_budget.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_startup_profile.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_status_bar.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_task_poll.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tips.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tool_fold.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tool_fold_panel.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tool_result_formatters.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tool_view.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_transcript_viewer.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_transcript_viewer_tool_fold.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_builtin_slash_chain.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_llm_retry_panel.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_local_interactive_barrier.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_paste_newline_guard.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_queue_preview.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_queue_sdk_source.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_scheduled_fire_log.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_send_undo_window.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_startup_latency.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_thinking_display.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_usage_command.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_wrap_user_text.py +0 -0
- {comate_cli-0.8.4 → comate_cli-0.8.5}/tools/logo_lab.py +0 -0
- {comate_cli-0.8.4 → 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
|
|
@@ -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
|
|
@@ -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.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]
|
|
@@ -4,7 +4,6 @@ import asyncio
|
|
|
4
4
|
import json
|
|
5
5
|
import subprocess
|
|
6
6
|
import sys
|
|
7
|
-
import threading
|
|
8
7
|
import unittest
|
|
9
8
|
from pathlib import Path
|
|
10
9
|
from types import SimpleNamespace
|
|
@@ -16,7 +15,6 @@ from comate_cli.terminal_agent.update_check import (
|
|
|
16
15
|
UpdateInfo,
|
|
17
16
|
UpdatePromptChoice,
|
|
18
17
|
UpdatePromptDecision,
|
|
19
|
-
check_update,
|
|
20
18
|
)
|
|
21
19
|
|
|
22
20
|
_CLI_ROOT = Path(__file__).resolve().parents[1]
|
|
@@ -390,8 +388,8 @@ asyncio.run(main())
|
|
|
390
388
|
async def test_pending_update_check_does_not_block_tui_run(self) -> None:
|
|
391
389
|
session = _fake_session()
|
|
392
390
|
created_tuis: list[_FakeTUI] = []
|
|
393
|
-
update_started =
|
|
394
|
-
release_update =
|
|
391
|
+
update_started = asyncio.Event()
|
|
392
|
+
release_update = asyncio.Event()
|
|
395
393
|
|
|
396
394
|
class _RecordingTUI(_FakeTUI):
|
|
397
395
|
def __init__(self, session, status_bar, renderer) -> None:
|
|
@@ -402,14 +400,14 @@ asyncio.run(main())
|
|
|
402
400
|
del mcp_init, profiler
|
|
403
401
|
self.run_called.set()
|
|
404
402
|
assert not update_started.is_set()
|
|
405
|
-
|
|
403
|
+
await asyncio.wait_for(update_started.wait(), timeout=0.2)
|
|
406
404
|
release_update.set()
|
|
407
405
|
await asyncio.sleep(0)
|
|
408
406
|
|
|
409
|
-
def
|
|
407
|
+
async def _slow_async_check(**kwargs) -> UpdateInfo | None:
|
|
410
408
|
del kwargs
|
|
411
409
|
update_started.set()
|
|
412
|
-
release_update.wait(timeout=1.0)
|
|
410
|
+
await asyncio.wait_for(release_update.wait(), timeout=1.0)
|
|
413
411
|
return None
|
|
414
412
|
|
|
415
413
|
try:
|
|
@@ -427,14 +425,10 @@ asyncio.run(main())
|
|
|
427
425
|
app_module,
|
|
428
426
|
"_import_update_check_core",
|
|
429
427
|
return_value=(
|
|
430
|
-
|
|
428
|
+
_slow_async_check,
|
|
431
429
|
MagicMock(return_value=None),
|
|
432
430
|
),
|
|
433
431
|
),
|
|
434
|
-
patch(
|
|
435
|
-
"comate_cli.terminal_agent.update_check.check_update_blocking",
|
|
436
|
-
side_effect=_slow_blocking_check,
|
|
437
|
-
),
|
|
438
432
|
patch.object(
|
|
439
433
|
app_module,
|
|
440
434
|
"_import_interactive_tui_components",
|
|
@@ -8,7 +8,9 @@ from unittest.mock import AsyncMock, MagicMock
|
|
|
8
8
|
|
|
9
9
|
def _make_tui_stub():
|
|
10
10
|
"""Create a minimal TUI stub with mocked dependencies."""
|
|
11
|
-
tui
|
|
11
|
+
from comate_cli.terminal_agent.tui import TerminalAgentTUI
|
|
12
|
+
|
|
13
|
+
tui = TerminalAgentTUI.__new__(TerminalAgentTUI)
|
|
12
14
|
tui._renderer = MagicMock()
|
|
13
15
|
tui._animation_controller = AsyncMock()
|
|
14
16
|
tui._status_bar = MagicMock()
|
|
@@ -16,6 +18,10 @@ def _make_tui_stub():
|
|
|
16
18
|
tui._last_turn_user_preview = "some preview"
|
|
17
19
|
tui._run_start_time = 12345.0
|
|
18
20
|
tui._interrupt_requested_at = 67890.0
|
|
21
|
+
tui._allow_latest_recall_on_interrupt = True
|
|
22
|
+
tui._set_busy = MagicMock()
|
|
23
|
+
tui._append_run_elapsed_to_history = MagicMock()
|
|
24
|
+
tui._refresh_layers = MagicMock()
|
|
19
25
|
return tui
|
|
20
26
|
|
|
21
27
|
|
|
@@ -108,6 +114,19 @@ class TestHandleError(unittest.TestCase):
|
|
|
108
114
|
|
|
109
115
|
tui._refresh_layers.assert_called_once()
|
|
110
116
|
|
|
117
|
+
def test_handle_error_status_refresh_failure_does_not_escape(self):
|
|
118
|
+
from comate_cli.terminal_agent.tui import TerminalAgentTUI
|
|
119
|
+
|
|
120
|
+
tui = _make_tui_stub()
|
|
121
|
+
tui._status_bar.refresh = AsyncMock(side_effect=RuntimeError("refresh boom"))
|
|
122
|
+
exc = Exception("primary failure")
|
|
123
|
+
|
|
124
|
+
asyncio.run(TerminalAgentTUI._handle_error(tui, exc))
|
|
125
|
+
|
|
126
|
+
tui._renderer.append_system_message.assert_called_once()
|
|
127
|
+
tui._renderer.clear_auto_compacting.assert_called_once()
|
|
128
|
+
tui._refresh_layers.assert_called_once()
|
|
129
|
+
|
|
111
130
|
def test_handle_error_warning_severity_for_rate_limit(self):
|
|
112
131
|
"""Rate limit errors should propagate warning severity to transient."""
|
|
113
132
|
from comate_cli.terminal_agent.tui import TerminalAgentTUI
|
|
@@ -21,14 +21,20 @@ from prompt_toolkit.output import DummyOutput
|
|
|
21
21
|
|
|
22
22
|
from comate_agent_sdk.agent import Agent, AgentConfig
|
|
23
23
|
from comate_agent_sdk.agent.chat_session import ChatSession
|
|
24
|
-
from comate_agent_sdk.agent.events import
|
|
24
|
+
from comate_agent_sdk.agent.events import (
|
|
25
|
+
GoalUpdatedEvent,
|
|
26
|
+
ScheduledPromptFiredEvent,
|
|
27
|
+
StopEvent,
|
|
28
|
+
)
|
|
25
29
|
from comate_agent_sdk.llm.exceptions import ModelRateLimitError
|
|
26
30
|
from comate_cli.terminal_agent.tui import TerminalAgentTUI
|
|
31
|
+
from comate_cli.terminal_agent.tui_parts import UIMode
|
|
27
32
|
|
|
28
33
|
|
|
29
34
|
class _RecordingRenderer:
|
|
30
35
|
def __init__(self, *, release_on_system_message: asyncio.Event | None = None) -> None:
|
|
31
36
|
self.system_messages: list[tuple[str, str]] = []
|
|
37
|
+
self.elapsed_messages: list[str] = []
|
|
32
38
|
self.closed = False
|
|
33
39
|
self._release_on_system_message = release_on_system_message
|
|
34
40
|
|
|
@@ -41,6 +47,9 @@ class _RecordingRenderer:
|
|
|
41
47
|
if self._release_on_system_message is not None:
|
|
42
48
|
self._release_on_system_message.set()
|
|
43
49
|
|
|
50
|
+
def append_elapsed_message(self, content: str) -> None:
|
|
51
|
+
self.elapsed_messages.append(content)
|
|
52
|
+
|
|
44
53
|
def finalize_turn(self) -> None:
|
|
45
54
|
pass
|
|
46
55
|
|
|
@@ -84,6 +93,46 @@ class _ProviderErrorSession:
|
|
|
84
93
|
pass
|
|
85
94
|
|
|
86
95
|
|
|
96
|
+
class _TwoScheduledTurnSession:
|
|
97
|
+
session_id = "s1"
|
|
98
|
+
|
|
99
|
+
def __init__(self) -> None:
|
|
100
|
+
self.run_controller = SimpleNamespace(clear=lambda: None)
|
|
101
|
+
self.runtime = SimpleNamespace(_mcp_manager=None)
|
|
102
|
+
|
|
103
|
+
async def events(self):
|
|
104
|
+
for index in range(2):
|
|
105
|
+
yield ScheduledPromptFiredEvent(
|
|
106
|
+
job_id=f"cron-{index}",
|
|
107
|
+
cron="* * * * *",
|
|
108
|
+
human_schedule="every 1 minutes",
|
|
109
|
+
recurring=True,
|
|
110
|
+
durable=False,
|
|
111
|
+
local_time="18:00",
|
|
112
|
+
)
|
|
113
|
+
yield StopEvent(reason="completed")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class _GoalUpdatedThenScheduledTurnSession:
|
|
117
|
+
session_id = "s1"
|
|
118
|
+
|
|
119
|
+
def __init__(self) -> None:
|
|
120
|
+
self.run_controller = SimpleNamespace(clear=lambda: None)
|
|
121
|
+
self.runtime = SimpleNamespace(_mcp_manager=None)
|
|
122
|
+
|
|
123
|
+
async def events(self):
|
|
124
|
+
yield GoalUpdatedEvent(goal=None, reason="created")
|
|
125
|
+
yield ScheduledPromptFiredEvent(
|
|
126
|
+
job_id="cron-after-goal",
|
|
127
|
+
cron="* * * * *",
|
|
128
|
+
human_schedule="every 1 minutes",
|
|
129
|
+
recurring=True,
|
|
130
|
+
durable=False,
|
|
131
|
+
local_time="18:00",
|
|
132
|
+
)
|
|
133
|
+
yield StopEvent(reason="completed")
|
|
134
|
+
|
|
135
|
+
|
|
87
136
|
class _ProviderErrorAfterCancelLLM:
|
|
88
137
|
model = "fake-openai"
|
|
89
138
|
provider = "openai"
|
|
@@ -223,6 +272,46 @@ def _template(llm: object, *, cwd: Path) -> Agent:
|
|
|
223
272
|
|
|
224
273
|
|
|
225
274
|
class TestTUIProviderErrorShield(unittest.IsolatedAsyncioTestCase):
|
|
275
|
+
async def test_event_pump_continues_after_status_refresh_failure_during_scheduled_turn(
|
|
276
|
+
self,
|
|
277
|
+
) -> None:
|
|
278
|
+
renderer = _RecordingRenderer()
|
|
279
|
+
tui = _make_tui_stub(
|
|
280
|
+
session=_TwoScheduledTurnSession(), # type: ignore[arg-type]
|
|
281
|
+
renderer=renderer,
|
|
282
|
+
)
|
|
283
|
+
tui._busy = False
|
|
284
|
+
tui._ui_mode = UIMode.NORMAL
|
|
285
|
+
tui._status_bar.refresh = AsyncMock(
|
|
286
|
+
side_effect=[RuntimeError("refresh boom"), None]
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
await tui._consume_event_stream()
|
|
290
|
+
|
|
291
|
+
self.assertEqual(len(renderer.elapsed_messages), 2)
|
|
292
|
+
self.assertIn("Running scheduled task", renderer.elapsed_messages[0])
|
|
293
|
+
self.assertIn("Running scheduled task", renderer.elapsed_messages[1])
|
|
294
|
+
|
|
295
|
+
async def test_event_pump_continues_after_status_refresh_failure_during_goal_update(
|
|
296
|
+
self,
|
|
297
|
+
) -> None:
|
|
298
|
+
renderer = _RecordingRenderer()
|
|
299
|
+
tui = _make_tui_stub(
|
|
300
|
+
session=_GoalUpdatedThenScheduledTurnSession(), # type: ignore[arg-type]
|
|
301
|
+
renderer=renderer,
|
|
302
|
+
)
|
|
303
|
+
tui._busy = False
|
|
304
|
+
tui._ui_mode = UIMode.NORMAL
|
|
305
|
+
tui._invalidate = MagicMock()
|
|
306
|
+
tui._status_bar.refresh = AsyncMock(
|
|
307
|
+
side_effect=[RuntimeError("goal refresh boom"), None]
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
await tui._consume_event_stream()
|
|
311
|
+
|
|
312
|
+
self.assertEqual(len(renderer.elapsed_messages), 1)
|
|
313
|
+
self.assertIn("Running scheduled task", renderer.elapsed_messages[0])
|
|
314
|
+
|
|
226
315
|
async def test_stop_event_provider_error_uses_project_error_display_path(self) -> None:
|
|
227
316
|
exc = ModelRateLimitError("rate limited", model="fake:model")
|
|
228
317
|
renderer = _RecordingRenderer()
|