comate-cli 0.8.4__tar.gz → 0.8.6__tar.gz

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