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.
Files changed (233) hide show
  1. {comate_cli-0.8.4 → comate_cli-0.8.5}/PKG-INFO +2 -1
  2. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui.py +13 -3
  3. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/update_check.py +76 -98
  4. {comate_cli-0.8.4 → comate_cli-0.8.5}/pyproject.toml +2 -1
  5. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_app_startup_latency.py +6 -12
  6. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_handle_error.py +20 -1
  7. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_provider_error_shield.py +90 -1
  8. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_update_check.py +193 -61
  9. {comate_cli-0.8.4 → comate_cli-0.8.5}/uv.lock +4 -2
  10. {comate_cli-0.8.4 → comate_cli-0.8.5}/.gitignore +0 -0
  11. {comate_cli-0.8.4 → comate_cli-0.8.5}/CHANGELOG.md +0 -0
  12. {comate_cli-0.8.4 → comate_cli-0.8.5}/CONTEXT.md +0 -0
  13. {comate_cli-0.8.4 → comate_cli-0.8.5}/README.md +0 -0
  14. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/__init__.py +0 -0
  15. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/__main__.py +0 -0
  16. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/main.py +0 -0
  17. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/mcp_cli.py +0 -0
  18. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/__init__.py +0 -0
  19. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/animations.py +0 -0
  20. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/app.py +0 -0
  21. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/assistant_render.py +0 -0
  22. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/builtin_commands/.keep +0 -0
  23. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/codenames.py +0 -0
  24. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/__init__.py +0 -0
  25. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/model.py +0 -0
  26. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/picker.py +0 -0
  27. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/picker_state.py +0 -0
  28. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/config/store.py +0 -0
  29. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
  30. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/env_utils.py +0 -0
  31. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/error_display.py +0 -0
  32. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/event_renderer.py +0 -0
  33. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/figures.py +0 -0
  34. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/file_ref_hint.py +0 -0
  35. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/fragment_utils.py +0 -0
  36. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
  37. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/history_printer.py +0 -0
  38. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/input_geometry.py +0 -0
  39. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
  40. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/logging_adapter.py +0 -0
  41. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/logo.py +0 -0
  42. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/markdown_render.py +0 -0
  43. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/mention_completer.py +0 -0
  44. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/message_style.py +0 -0
  45. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/models.py +0 -0
  46. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/path_context_hint.py +0 -0
  47. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
  48. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
  49. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
  50. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
  51. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
  52. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
  53. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
  54. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/operation_state.py +0 -0
  55. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
  56. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
  57. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
  58. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
  59. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
  60. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
  61. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/preflight.py +0 -0
  62. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/preflight_wizard.py +0 -0
  63. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/print_mode.py +0 -0
  64. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/question_view.py +0 -0
  65. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/recap/__init__.py +0 -0
  66. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/recap/controller.py +0 -0
  67. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/resume_picker.py +0 -0
  68. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/resume_preview.py +0 -0
  69. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/resume_selector.py +0 -0
  70. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
  71. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
  72. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/selection_menu.py +0 -0
  73. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/slash_commands.py +0 -0
  74. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/startup.py +0 -0
  75. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/startup_profile.py +0 -0
  76. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/status_bar.py +0 -0
  77. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
  78. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/model.py +0 -0
  79. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/picker.py +0 -0
  80. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
  81. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/statusline/store.py +0 -0
  82. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/text_effects.py +0 -0
  83. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tips.py +0 -0
  84. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_fold.py +0 -0
  85. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
  86. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_result_store.py +0 -0
  87. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
  88. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tool_view.py +0 -0
  89. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
  90. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
  91. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
  92. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
  93. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
  94. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
  95. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
  96. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
  97. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
  98. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
  99. {comate_cli-0.8.4 → comate_cli-0.8.5}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
  100. {comate_cli-0.8.4 → comate_cli-0.8.5}/dist-comate/.gitignore +0 -0
  101. {comate_cli-0.8.4 → comate_cli-0.8.5}/dist-comate/comate_cli-0.7.10-py3-none-any.whl +0 -0
  102. {comate_cli-0.8.4 → comate_cli-0.8.5}/dist-comate/comate_cli-0.7.10.tar.gz +0 -0
  103. {comate_cli-0.8.4 → comate_cli-0.8.5}/dist-hico/.gitignore +0 -0
  104. {comate_cli-0.8.4 → comate_cli-0.8.5}/dist-hico/hico_cli-0.7.17-py3-none-any.whl +0 -0
  105. {comate_cli-0.8.4 → comate_cli-0.8.5}/dist-hico/hico_cli-0.7.17.tar.gz +0 -0
  106. {comate_cli-0.8.4 → comate_cli-0.8.5}/docs/cli-rpc-stdio-guide.md +0 -0
  107. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/config/__init__.py +0 -0
  108. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/config/test_model.py +0 -0
  109. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/config/test_picker_state.py +0 -0
  110. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/config/test_picker_ui.py +0 -0
  111. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/config/test_roundtrip.py +0 -0
  112. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/config/test_store_load.py +0 -0
  113. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/config/test_store_save.py +0 -0
  114. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/conftest.py +0 -0
  115. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
  116. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/recap/test_idle_recap_controller.py +0 -0
  117. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/recap/test_recap_history_rendering.py +0 -0
  118. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/recap/test_tui_idle_recap_integration.py +0 -0
  119. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/statusline/__init__.py +0 -0
  120. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/statusline/test_model.py +0 -0
  121. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/statusline/test_picker_state.py +0 -0
  122. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/statusline/test_store.py +0 -0
  123. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_animator_shuffle.py +0 -0
  124. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_app_mcp_preload.py +0 -0
  125. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_app_preflight_gate.py +0 -0
  126. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_app_print_mode.py +0 -0
  127. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_app_shutdown.py +0 -0
  128. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_app_token_cost_config.py +0 -0
  129. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_app_usage_line.py +0 -0
  130. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_btw_slash_command.py +0 -0
  131. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_cli_project_root.py +0 -0
  132. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_compact_command_semantics.py +0 -0
  133. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_completion_context_activation.py +0 -0
  134. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_completion_status_panel.py +0 -0
  135. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_context_command.py +0 -0
  136. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_custom_slash_commands.py +0 -0
  137. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_discover_tab.py +0 -0
  138. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_errors_tab.py +0 -0
  139. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer.py +0 -0
  140. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer_boundary.py +0 -0
  141. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer_e2e.py +0 -0
  142. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer_log_boundary.py +0 -0
  143. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer_log_queue.py +0 -0
  144. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer_streaming.py +0 -0
  145. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer_tool_args_progress.py +0 -0
  146. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_event_renderer_tool_fold.py +0 -0
  147. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_file_ref_hint.py +0 -0
  148. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_format_error.py +0 -0
  149. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_goal_resume_tui.py +0 -0
  150. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_goal_resume_view.py +0 -0
  151. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_goal_slash_command.py +0 -0
  152. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_history_printer.py +0 -0
  153. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_history_printer_log.py +0 -0
  154. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_history_printer_subtitle_position.py +0 -0
  155. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_history_printer_tool_fold.py +0 -0
  156. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_history_sync.py +0 -0
  157. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_history_sync_tool_fold.py +0 -0
  158. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_input_behavior.py +0 -0
  159. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_input_history.py +0 -0
  160. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_installed_tab.py +0 -0
  161. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_interrupt_exit_semantics.py +0 -0
  162. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_layout_coordinator.py +0 -0
  163. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_logging_adapter.py +0 -0
  164. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_logo.py +0 -0
  165. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_main_args.py +0 -0
  166. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_markdown_render.py +0 -0
  167. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_marketplaces_tab.py +0 -0
  168. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_mcp_cli.py +0 -0
  169. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_mcp_slash_command.py +0 -0
  170. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_mention_completer.py +0 -0
  171. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_model_switch_command.py +0 -0
  172. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_path_context_hint.py +0 -0
  173. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_plugin_operation_state.py +0 -0
  174. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_plugin_slash_commands.py +0 -0
  175. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_plugin_tui_components.py +0 -0
  176. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_preflight.py +0 -0
  177. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_preflight_copilot.py +0 -0
  178. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_question_key_bindings.py +0 -0
  179. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_question_view.py +0 -0
  180. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_resume_picker.py +0 -0
  181. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_resume_preview.py +0 -0
  182. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_resume_selector.py +0 -0
  183. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_rewind_command_semantics.py +0 -0
  184. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_rpc_protocol.py +0 -0
  185. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_rpc_stdio_bridge.py +0 -0
  186. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_selection_menu.py +0 -0
  187. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_session_query_token_summary.py +0 -0
  188. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_shutdown_noise_guard.py +0 -0
  189. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_shutdown_noise_integration.py +0 -0
  190. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_skills_slash_command.py +0 -0
  191. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_slash_argument_hint.py +0 -0
  192. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_slash_clear.py +0 -0
  193. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_slash_completer.py +0 -0
  194. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_slash_registry.py +0 -0
  195. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_startup_import_budget.py +0 -0
  196. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_startup_profile.py +0 -0
  197. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_status_bar.py +0 -0
  198. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_status_bar_transient.py +0 -0
  199. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_task_panel_format.py +0 -0
  200. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_task_panel_key_bindings.py +0 -0
  201. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_task_panel_rendering.py +0 -0
  202. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_task_poll.py +0 -0
  203. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tips.py +0 -0
  204. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tool_fold.py +0 -0
  205. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tool_fold_panel.py +0 -0
  206. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tool_result_formatters.py +0 -0
  207. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tool_result_store.py +0 -0
  208. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tool_result_viewer.py +0 -0
  209. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tool_result_viewer_key_bindings.py +0 -0
  210. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tool_view.py +0 -0
  211. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_transcript_viewer.py +0 -0
  212. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_transcript_viewer_tool_fold.py +0 -0
  213. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_builtin_slash_chain.py +0 -0
  214. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_elapsed_status.py +0 -0
  215. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_esc_queue.py +0 -0
  216. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_llm_retry_panel.py +0 -0
  217. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_local_interactive_barrier.py +0 -0
  218. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_mcp_init_gate.py +0 -0
  219. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_paste_newline_guard.py +0 -0
  220. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_paste_placeholder.py +0 -0
  221. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_queue_preview.py +0 -0
  222. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_queue_sdk_source.py +0 -0
  223. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_scheduled_fire_log.py +0 -0
  224. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_send_undo_window.py +0 -0
  225. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_split_invariance.py +0 -0
  226. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_startup_latency.py +0 -0
  227. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_team_messages.py +0 -0
  228. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_thinking_display.py +0 -0
  229. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
  230. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_usage_command.py +0 -0
  231. {comate_cli-0.8.4 → comate_cli-0.8.5}/tests/test_wrap_user_text.py +0 -0
  232. {comate_cli-0.8.4 → comate_cli-0.8.5}/tools/logo_lab.py +0 -0
  233. {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.4
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._status_bar.refresh()
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._status_bar.refresh()
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._status_bar.refresh()
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("thread.run.start")
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
- if profiler is not None:
174
- profiler.mark("httpx_import.start")
175
- import httpx
176
- if profiler is not None:
177
- profiler.mark("httpx_import.done")
178
-
179
- trust_env = _update_check_trust_env()
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
- active_logger.debug(f"update check failed (source={source_label})", exc_info=True)
213
- return None
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
- try:
216
- if profiler is not None:
217
- profiler.mark("version_compare.start")
218
- from packaging.version import Version
219
-
220
- if Version(latest) > Version(current):
221
- return UpdateInfo(
222
- package=package,
223
- current_version=current,
224
- latest_version=latest,
225
- release_notes_url=release_notes_url,
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
- finally:
235
- if profiler is not None:
236
- profiler.mark("version_compare.done")
237
- return None
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("thread.run.done")
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.4"
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 = threading.Event()
394
- release_update = threading.Event()
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
- assert await asyncio.to_thread(update_started.wait, 0.2)
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 _slow_blocking_check(**kwargs) -> UpdateInfo | None:
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
- check_update,
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 = MagicMock()
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 StopEvent
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()