comate-cli 0.8.5__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.5 → comate_cli-0.8.6}/PKG-INFO +1 -1
  2. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/error_display.py +40 -41
  3. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/event_renderer.py +2 -2
  4. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/status_bar.py +92 -46
  5. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tool_view.py +2 -2
  6. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui.py +15 -1
  7. {comate_cli-0.8.5 → comate_cli-0.8.6}/pyproject.toml +1 -1
  8. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_format_error.py +69 -18
  9. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_status_bar.py +103 -42
  10. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_provider_error_shield.py +41 -0
  11. {comate_cli-0.8.5 → comate_cli-0.8.6}/uv.lock +2 -2
  12. {comate_cli-0.8.5 → comate_cli-0.8.6}/.gitignore +0 -0
  13. {comate_cli-0.8.5 → comate_cli-0.8.6}/CHANGELOG.md +0 -0
  14. {comate_cli-0.8.5 → comate_cli-0.8.6}/CONTEXT.md +0 -0
  15. {comate_cli-0.8.5 → comate_cli-0.8.6}/README.md +0 -0
  16. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/__init__.py +0 -0
  17. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/__main__.py +0 -0
  18. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/main.py +0 -0
  19. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/mcp_cli.py +0 -0
  20. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/__init__.py +0 -0
  21. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/animations.py +0 -0
  22. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/app.py +0 -0
  23. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/assistant_render.py +0 -0
  24. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/builtin_commands/.keep +0 -0
  25. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/codenames.py +0 -0
  26. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/config/__init__.py +0 -0
  27. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/config/model.py +0 -0
  28. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/config/picker.py +0 -0
  29. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/config/picker_state.py +0 -0
  30. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/config/store.py +0 -0
  31. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
  32. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/env_utils.py +0 -0
  33. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/figures.py +0 -0
  34. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/file_ref_hint.py +0 -0
  35. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/fragment_utils.py +0 -0
  36. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
  37. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/history_printer.py +0 -0
  38. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/input_geometry.py +0 -0
  39. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
  40. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/logging_adapter.py +0 -0
  41. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/logo.py +0 -0
  42. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/markdown_render.py +0 -0
  43. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/mention_completer.py +0 -0
  44. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/message_style.py +0 -0
  45. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/models.py +0 -0
  46. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/path_context_hint.py +0 -0
  47. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
  48. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
  49. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
  50. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
  51. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
  52. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
  53. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
  54. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/operation_state.py +0 -0
  55. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
  56. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
  57. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
  58. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
  59. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
  60. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
  61. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/preflight.py +0 -0
  62. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/preflight_wizard.py +0 -0
  63. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/print_mode.py +0 -0
  64. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/question_view.py +0 -0
  65. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/recap/__init__.py +0 -0
  66. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/recap/controller.py +0 -0
  67. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/resume_picker.py +0 -0
  68. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/resume_preview.py +0 -0
  69. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/resume_selector.py +0 -0
  70. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
  71. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
  72. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/selection_menu.py +0 -0
  73. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/slash_commands.py +0 -0
  74. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/startup.py +0 -0
  75. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/startup_profile.py +0 -0
  76. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/statusline/__init__.py +0 -0
  77. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/statusline/model.py +0 -0
  78. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/statusline/picker.py +0 -0
  79. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
  80. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/statusline/store.py +0 -0
  81. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/text_effects.py +0 -0
  82. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tips.py +0 -0
  83. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tool_fold.py +0 -0
  84. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tool_result_formatters.py +0 -0
  85. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tool_result_store.py +0 -0
  86. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
  87. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
  88. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
  89. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
  90. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
  91. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
  92. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
  93. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
  94. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
  95. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
  96. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
  97. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
  98. {comate_cli-0.8.5 → comate_cli-0.8.6}/comate_cli/terminal_agent/update_check.py +0 -0
  99. {comate_cli-0.8.5 → comate_cli-0.8.6}/dist-comate/.gitignore +0 -0
  100. {comate_cli-0.8.5 → comate_cli-0.8.6}/dist-comate/comate_cli-0.7.10-py3-none-any.whl +0 -0
  101. {comate_cli-0.8.5 → comate_cli-0.8.6}/dist-comate/comate_cli-0.7.10.tar.gz +0 -0
  102. {comate_cli-0.8.5 → comate_cli-0.8.6}/dist-hico/.gitignore +0 -0
  103. {comate_cli-0.8.5 → comate_cli-0.8.6}/dist-hico/hico_cli-0.7.17-py3-none-any.whl +0 -0
  104. {comate_cli-0.8.5 → comate_cli-0.8.6}/dist-hico/hico_cli-0.7.17.tar.gz +0 -0
  105. {comate_cli-0.8.5 → comate_cli-0.8.6}/docs/cli-rpc-stdio-guide.md +0 -0
  106. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/config/__init__.py +0 -0
  107. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/config/test_model.py +0 -0
  108. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/config/test_picker_state.py +0 -0
  109. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/config/test_picker_ui.py +0 -0
  110. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/config/test_roundtrip.py +0 -0
  111. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/config/test_store_load.py +0 -0
  112. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/config/test_store_save.py +0 -0
  113. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/conftest.py +0 -0
  114. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
  115. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/recap/test_idle_recap_controller.py +0 -0
  116. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/recap/test_recap_history_rendering.py +0 -0
  117. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/recap/test_tui_idle_recap_integration.py +0 -0
  118. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/statusline/__init__.py +0 -0
  119. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/statusline/test_model.py +0 -0
  120. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/statusline/test_picker_state.py +0 -0
  121. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/statusline/test_store.py +0 -0
  122. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_animator_shuffle.py +0 -0
  123. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_app_mcp_preload.py +0 -0
  124. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_app_preflight_gate.py +0 -0
  125. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_app_print_mode.py +0 -0
  126. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_app_shutdown.py +0 -0
  127. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_app_startup_latency.py +0 -0
  128. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_app_token_cost_config.py +0 -0
  129. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_app_usage_line.py +0 -0
  130. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_btw_slash_command.py +0 -0
  131. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_cli_project_root.py +0 -0
  132. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_compact_command_semantics.py +0 -0
  133. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_completion_context_activation.py +0 -0
  134. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_completion_status_panel.py +0 -0
  135. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_context_command.py +0 -0
  136. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_custom_slash_commands.py +0 -0
  137. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_discover_tab.py +0 -0
  138. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_errors_tab.py +0 -0
  139. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_event_renderer.py +0 -0
  140. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_event_renderer_boundary.py +0 -0
  141. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_event_renderer_e2e.py +0 -0
  142. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_event_renderer_log_boundary.py +0 -0
  143. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_event_renderer_log_queue.py +0 -0
  144. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_event_renderer_streaming.py +0 -0
  145. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_event_renderer_tool_args_progress.py +0 -0
  146. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_event_renderer_tool_fold.py +0 -0
  147. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_file_ref_hint.py +0 -0
  148. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_goal_resume_tui.py +0 -0
  149. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_goal_resume_view.py +0 -0
  150. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_goal_slash_command.py +0 -0
  151. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_handle_error.py +0 -0
  152. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_history_printer.py +0 -0
  153. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_history_printer_log.py +0 -0
  154. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_history_printer_subtitle_position.py +0 -0
  155. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_history_printer_tool_fold.py +0 -0
  156. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_history_sync.py +0 -0
  157. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_history_sync_tool_fold.py +0 -0
  158. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_input_behavior.py +0 -0
  159. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_input_history.py +0 -0
  160. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_installed_tab.py +0 -0
  161. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_interrupt_exit_semantics.py +0 -0
  162. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_layout_coordinator.py +0 -0
  163. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_logging_adapter.py +0 -0
  164. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_logo.py +0 -0
  165. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_main_args.py +0 -0
  166. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_markdown_render.py +0 -0
  167. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_marketplaces_tab.py +0 -0
  168. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_mcp_cli.py +0 -0
  169. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_mcp_slash_command.py +0 -0
  170. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_mention_completer.py +0 -0
  171. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_model_switch_command.py +0 -0
  172. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_path_context_hint.py +0 -0
  173. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_plugin_operation_state.py +0 -0
  174. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_plugin_slash_commands.py +0 -0
  175. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_plugin_tui_components.py +0 -0
  176. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_preflight.py +0 -0
  177. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_preflight_copilot.py +0 -0
  178. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_question_key_bindings.py +0 -0
  179. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_question_view.py +0 -0
  180. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_resume_picker.py +0 -0
  181. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_resume_preview.py +0 -0
  182. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_resume_selector.py +0 -0
  183. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_rewind_command_semantics.py +0 -0
  184. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_rpc_protocol.py +0 -0
  185. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_rpc_stdio_bridge.py +0 -0
  186. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_selection_menu.py +0 -0
  187. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_session_query_token_summary.py +0 -0
  188. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_shutdown_noise_guard.py +0 -0
  189. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_shutdown_noise_integration.py +0 -0
  190. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_skills_slash_command.py +0 -0
  191. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_slash_argument_hint.py +0 -0
  192. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_slash_clear.py +0 -0
  193. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_slash_completer.py +0 -0
  194. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_slash_registry.py +0 -0
  195. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_startup_import_budget.py +0 -0
  196. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_startup_profile.py +0 -0
  197. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_status_bar_transient.py +0 -0
  198. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_task_panel_format.py +0 -0
  199. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_task_panel_key_bindings.py +0 -0
  200. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_task_panel_rendering.py +0 -0
  201. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_task_poll.py +0 -0
  202. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tips.py +0 -0
  203. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tool_fold.py +0 -0
  204. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tool_fold_panel.py +0 -0
  205. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tool_result_formatters.py +0 -0
  206. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tool_result_store.py +0 -0
  207. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tool_result_viewer.py +0 -0
  208. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tool_result_viewer_key_bindings.py +0 -0
  209. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tool_view.py +0 -0
  210. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_transcript_viewer.py +0 -0
  211. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_transcript_viewer_tool_fold.py +0 -0
  212. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_builtin_slash_chain.py +0 -0
  213. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_elapsed_status.py +0 -0
  214. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_esc_queue.py +0 -0
  215. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_llm_retry_panel.py +0 -0
  216. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_local_interactive_barrier.py +0 -0
  217. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_mcp_init_gate.py +0 -0
  218. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_paste_newline_guard.py +0 -0
  219. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_paste_placeholder.py +0 -0
  220. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_queue_preview.py +0 -0
  221. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_queue_sdk_source.py +0 -0
  222. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_scheduled_fire_log.py +0 -0
  223. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_send_undo_window.py +0 -0
  224. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_split_invariance.py +0 -0
  225. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_startup_latency.py +0 -0
  226. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_team_messages.py +0 -0
  227. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_thinking_display.py +0 -0
  228. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
  229. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_update_check.py +0 -0
  230. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_usage_command.py +0 -0
  231. {comate_cli-0.8.5 → comate_cli-0.8.6}/tests/test_wrap_user_text.py +0 -0
  232. {comate_cli-0.8.5 → comate_cli-0.8.6}/tools/logo_lab.py +0 -0
  233. {comate_cli-0.8.5 → 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.5
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
@@ -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
 
@@ -1497,7 +1499,19 @@ class TerminalAgentTUI(
1497
1499
 
1498
1500
  async def _refresh_status_bar_safely(self, *, reason: str) -> None:
1499
1501
  try:
1500
- await self._status_bar.refresh()
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
+ )
1501
1515
  except Exception:
1502
1516
  logger.warning(
1503
1517
  "TUI status bar refresh failed during %s; continuing event pump",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "comate-cli"
7
- version = "0.8.5"
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"
@@ -26,38 +26,86 @@ class TestFormatError(unittest.TestCase):
26
26
  def test_provider_401(self):
27
27
  from comate_cli.terminal_agent.error_display import format_error
28
28
 
29
- exc = self._make_exc("ModelProviderError", status_code=401)
29
+ exc = self._make_exc(
30
+ "ModelProviderError",
31
+ "provider says the API key is expired",
32
+ status_code=401,
33
+ )
30
34
  msg, transient, severity = format_error(exc)
31
35
  assert severity == "error"
32
- assert "API key" in msg
33
- assert transient == "Auth failed"
36
+ assert msg == "Provider error (HTTP 401): provider says the API key is expired"
37
+ assert transient == "Provider error 401"
34
38
 
35
39
  def test_provider_403(self):
36
40
  from comate_cli.terminal_agent.error_display import format_error
37
41
 
38
- exc = self._make_exc("ModelProviderError", status_code=403)
42
+ exc = self._make_exc(
43
+ "ModelProviderError",
44
+ "预扣费额度失败, 用户剩余额度: ¥0.000015, 需要预扣费额度: ¥0.000555",
45
+ status_code=403,
46
+ )
39
47
  msg, transient, severity = format_error(exc)
40
48
  assert severity == "error"
41
- assert "Access denied" in msg
42
- assert transient == "Access denied"
49
+ assert "预扣费额度失败" in msg
50
+ assert "Access denied" not in msg
51
+ assert transient == "Provider error 403"
43
52
 
44
53
  def test_provider_404(self):
45
54
  from comate_cli.terminal_agent.error_display import format_error
46
55
 
47
- exc = self._make_exc("ModelProviderError", status_code=404)
56
+ exc = self._make_exc(
57
+ "ModelProviderError",
58
+ "provider says the model route does not exist",
59
+ status_code=404,
60
+ )
48
61
  msg, transient, severity = format_error(exc)
49
62
  assert severity == "error"
50
- assert "not found" in msg.lower()
51
- assert transient == "Model not found"
63
+ assert "provider says the model route does not exist" in msg
64
+ assert transient == "Provider error 404"
52
65
 
53
66
  def test_provider_5xx(self):
54
67
  from comate_cli.terminal_agent.error_display import format_error
55
68
 
56
- exc = self._make_exc("ModelProviderError", status_code=502)
69
+ exc = self._make_exc(
70
+ "ModelProviderError",
71
+ "No available channel for model MiniMax-M2.7 under group svip",
72
+ status_code=503,
73
+ )
57
74
  msg, transient, severity = format_error(exc)
58
75
  assert severity == "warning"
59
- assert "502" in msg
60
- assert transient == "Server error"
76
+ assert "No available channel for model MiniMax-M2.7" in msg
77
+ assert "Server error" not in msg
78
+ assert transient == "Provider error 503"
79
+
80
+ def test_provider_openai_wrapped_error_message(self):
81
+ from comate_cli.terminal_agent.error_display import format_error
82
+
83
+ body = (
84
+ "Error code: 403 - {'error': {'message': '预扣费额度失败, "
85
+ "用户剩余额度: ¥0.000015', 'type': 'new_api_error', "
86
+ "'code': 'insufficient_user_quota'}}"
87
+ )
88
+ exc = self._make_exc("ModelProviderError", body, status_code=403)
89
+ msg, transient, severity = format_error(exc)
90
+ assert severity == "error"
91
+ assert msg == "Provider error (HTTP 403): 预扣费额度失败, 用户剩余额度: ¥0.000015"
92
+ assert transient == "Provider error 403"
93
+
94
+ def test_provider_json_error_message(self):
95
+ from comate_cli.terminal_agent.error_display import format_error
96
+
97
+ body = (
98
+ '{"error":{"type":"new_api_error","message":"预扣费额度失败, '
99
+ '用户剩余额度: ¥0.000015, 需要预扣费额度: ¥0.001664"},"type":"error"}'
100
+ )
101
+ exc = self._make_exc("ModelProviderError", body, status_code=403)
102
+ msg, transient, severity = format_error(exc)
103
+ assert severity == "error"
104
+ assert msg == (
105
+ "Provider error (HTTP 403): 预扣费额度失败, "
106
+ "用户剩余额度: ¥0.000015, 需要预扣费额度: ¥0.001664"
107
+ )
108
+ assert transient == "Provider error 403"
61
109
 
62
110
  def test_provider_stream_protocol_error(self):
63
111
  from comate_agent_sdk.llm.exceptions import ProviderStreamProtocolError
@@ -122,9 +170,11 @@ class TestFormatError(unittest.TestCase):
122
170
  exc = self._make_exc("ModelProviderError", body, status_code=400)
123
171
  msg, transient, severity = format_error(exc)
124
172
  assert severity == "error"
125
- assert "35099" in msg
126
- assert "32768" in msg
127
- assert transient == "Context limit exceeded"
173
+ assert msg == (
174
+ "Provider error (HTTP 400): request (35099 tokens) exceeds the "
175
+ "available context size (32768 tokens)"
176
+ )
177
+ assert transient == "Provider error 400"
128
178
 
129
179
  def test_context_size_type_without_parseable_numbers(self):
130
180
  from comate_cli.terminal_agent.error_display import format_error
@@ -136,7 +186,8 @@ class TestFormatError(unittest.TestCase):
136
186
  )
137
187
  msg, transient, severity = format_error(exc)
138
188
  assert severity == "error"
139
- assert transient == "API error" # Falls through to generic 400
189
+ assert "exceed_context_size_error without parseable numbers" in msg
190
+ assert transient == "Provider error 400"
140
191
 
141
192
  def test_provider_400_generic(self):
142
193
  from comate_cli.terminal_agent.error_display import format_error
@@ -144,8 +195,8 @@ class TestFormatError(unittest.TestCase):
144
195
  exc = self._make_exc("ModelProviderError", "bad request", status_code=400)
145
196
  msg, transient, severity = format_error(exc)
146
197
  assert severity == "error"
147
- assert "API error" in msg
148
- assert transient == "API error"
198
+ assert msg == "Provider error (HTTP 400): bad request"
199
+ assert transient == "Provider error 400"
149
200
 
150
201
  def test_session_closed(self):
151
202
  from comate_cli.terminal_agent.error_display import format_error