comate-cli 0.8.0__tar.gz → 0.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- comate_cli-0.8.2/CONTEXT.md +39 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/PKG-INFO +1 -1
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/app.py +82 -30
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/config/model.py +5 -17
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/config/picker.py +7 -6
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/config/picker_state.py +7 -27
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/config/store.py +19 -61
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/custom_slash_commands.py +40 -2
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/event_renderer.py +78 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/figures.py +8 -9
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/history_printer.py +117 -7
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/logging_adapter.py +20 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/logo.py +42 -7
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/message_style.py +2 -2
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/models.py +1 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +2 -1
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +2 -1
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/question_view.py +2 -2
- comate_cli-0.8.2/comate_cli/terminal_agent/recap/__init__.py +5 -0
- comate_cli-0.8.2/comate_cli/terminal_agent/recap/controller.py +131 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tips.py +1 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tool_result_formatters.py +8 -6
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tool_result_store.py +2 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/transcript_viewer.py +5 -5
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tui.py +117 -31
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tui_parts/commands.py +27 -46
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tui_parts/input_behavior.py +12 -4
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tui_parts/key_bindings.py +10 -4
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tui_parts/render_panels.py +149 -8
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/update_check.py +0 -58
- comate_cli-0.8.2/docs/cli-rpc-stdio-guide.md +395 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/pyproject.toml +1 -1
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/config/test_model.py +18 -37
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/config/test_picker_state.py +14 -42
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/config/test_picker_ui.py +12 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/config/test_roundtrip.py +3 -8
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/config/test_store_load.py +43 -59
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/config/test_store_save.py +41 -26
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/conftest.py +2 -2
- comate_cli-0.8.2/tests/recap/test_idle_recap_controller.py +384 -0
- comate_cli-0.8.2/tests/recap/test_recap_history_rendering.py +100 -0
- comate_cli-0.8.2/tests/recap/test_tui_idle_recap_integration.py +230 -0
- comate_cli-0.8.2/tests/statusline/__init__.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_app_shutdown.py +71 -4
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_app_startup_latency.py +1 -7
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_app_token_cost_config.py +3 -7
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_app_usage_line.py +0 -2
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_completion_status_panel.py +82 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_custom_slash_commands.py +86 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_event_renderer_streaming.py +108 -5
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_history_printer.py +51 -17
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_history_printer_log.py +3 -3
- comate_cli-0.8.2/tests/test_history_printer_subtitle_position.py +368 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_history_printer_tool_fold.py +4 -4
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_history_sync.py +17 -19
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_installed_tab.py +1 -1
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_interrupt_exit_semantics.py +3 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_logging_adapter.py +77 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_logo.py +13 -9
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_marketplaces_tab.py +2 -2
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_preflight.py +19 -8
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_startup_profile.py +2 -2
- comate_cli-0.8.2/tests/test_tips.py +7 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tool_result_formatters.py +63 -0
- comate_cli-0.8.2/tests/test_tui_builtin_slash_chain.py +213 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_esc_queue.py +29 -2
- comate_cli-0.8.2/tests/test_tui_llm_retry_panel.py +184 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_paste_newline_guard.py +9 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_queue_preview.py +17 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_queue_sdk_source.py +3 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_team_messages.py +3 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_thinking_display.py +3 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_update_check.py +0 -61
- comate_cli-0.8.2/tools/nico_preview.py +280 -0
- comate_cli-0.8.0/tests/test_history_printer_subtitle_position.py +0 -116
- {comate_cli-0.8.0 → comate_cli-0.8.2}/.gitignore +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/CHANGELOG.md +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/README.md +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/__init__.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/__main__.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/main.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/assistant_render.py +0 -0
- /comate_cli-0.8.0/comate_cli/terminal_agent/config/__init__.py → /comate_cli-0.8.2/comate_cli/terminal_agent/builtin_commands/.keep +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.8.0/comate_cli/terminal_agent/plugins → comate_cli-0.8.2/comate_cli/terminal_agent/config}/__init__.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/file_ref_hint.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/goal_resume_view.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.8.0/comate_cli/terminal_agent/plugins/components → comate_cli-0.8.2/comate_cli/terminal_agent/plugins}/__init__.py +0 -0
- {comate_cli-0.8.0/comate_cli/terminal_agent/plugins/tabs → comate_cli-0.8.2/comate_cli/terminal_agent/plugins/components}/__init__.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/plugins/operation_state.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.8.0/comate_cli/terminal_agent/statusline → comate_cli-0.8.2/comate_cli/terminal_agent/plugins/tabs}/__init__.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/preflight_wizard.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/print_mode.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/slash_commands.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/startup_profile.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/status_bar.py +0 -0
- {comate_cli-0.8.0/tests/config → comate_cli-0.8.2/comate_cli/terminal_agent/statusline}/__init__.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/statusline/model.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/statusline/picker.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/statusline/picker_state.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/statusline/store.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tool_fold.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/dist-comate/.gitignore +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/dist-comate/comate_cli-0.7.10-py3-none-any.whl +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/dist-comate/comate_cli-0.7.10.tar.gz +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/dist-hico/.gitignore +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/dist-hico/hico_cli-0.7.17-py3-none-any.whl +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/dist-hico/hico_cli-0.7.17.tar.gz +0 -0
- {comate_cli-0.8.0/tests/statusline → comate_cli-0.8.2/tests/config}/__init__.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/statusline/test_model.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/statusline/test_picker_state.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/statusline/test_store.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_context_command.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_event_renderer.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_event_renderer_tool_fold.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_file_ref_hint.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_format_error.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_goal_resume_tui.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_goal_resume_view.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_goal_slash_command.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_handle_error.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_history_sync_tool_fold.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_input_history.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_main_args.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_markdown_render.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_model_switch_command.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_plugin_operation_state.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_plugin_slash_commands.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_question_view.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_resume_picker.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_skills_slash_command.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_slash_clear.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_startup_import_budget.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_status_bar.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_task_poll.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tool_fold.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tool_fold_panel.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tool_view.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_transcript_viewer.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_transcript_viewer_tool_fold.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_local_interactive_barrier.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_scheduled_fire_log.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_send_undo_window.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_startup_latency.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_usage_command.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tests/test_wrap_user_text.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/tools/logo_lab.py +0 -0
- {comate_cli-0.8.0 → comate_cli-0.8.2}/uv.lock +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Comate CLI Slash Command Model
|
|
2
|
+
|
|
3
|
+
The comate_cli slash command system separates how a command is *executed* from *where its definition comes from*. Two orthogonal axes describe every registered command. The same word "builtin" previously appeared on both axes with different, conflicting override semantics — this glossary resolves that ambiguity.
|
|
4
|
+
|
|
5
|
+
## Language
|
|
6
|
+
|
|
7
|
+
**Core Slash Command**:
|
|
8
|
+
A slash command backed by a hardcoded Python handler registered in `SLASH_COMMAND_SPECS` (e.g. `/help`, `/model`, `/exit`). Registered with `SlashCommandSource = "builtin"` on the *execution* axis.
|
|
9
|
+
_Avoid_: builtin command (ambiguous), native command
|
|
10
|
+
|
|
11
|
+
**Default Custom Command**:
|
|
12
|
+
A markdown-resource slash command shipped inside the `comate_cli` package under `terminal_agent/builtin_commands/`. Loaded via the custom-command pipeline (frontmatter + template), with `source_scope = "default"` on the *source* axis. User-authored custom commands of the same name override it.
|
|
13
|
+
_Avoid_: builtin command (ambiguous), bundled command
|
|
14
|
+
|
|
15
|
+
**Execution Axis** (`SlashCommandSource`):
|
|
16
|
+
Describes how a registered command is dispatched. Values: `"builtin"` (hardcoded handler), `"custom"` (markdown template render), `"skill"` (skill slash). Orthogonal to the Source Axis.
|
|
17
|
+
|
|
18
|
+
**Source Axis** (`CustomSlashCommand.source_scope`):
|
|
19
|
+
Describes where a markdown-template command's definition lives. Values: `"project"`, `"user"`, `"plugin"`, `"default"`. Determines override priority only.
|
|
20
|
+
|
|
21
|
+
**Reserved Name Set** (`builtin_names`):
|
|
22
|
+
The set of names derived from `SLASH_COMMAND_SPECS` (Core Slash Commands) that user-authored custom commands may not shadow. A custom command whose name collides with this set is silently skipped with a warning during discovery. **Default Custom Command names do NOT belong to this set** — they are overrideable by design.
|
|
23
|
+
|
|
24
|
+
**Override Priority Chain**:
|
|
25
|
+
`project > user > default` (plus `plugin` registered independently at runtime). When a Default Custom Command and a user/project custom command share a name, the higher-priority scope wins silently (no warning); only the winning entry reaches the registry.
|
|
26
|
+
|
|
27
|
+
## Relationships
|
|
28
|
+
|
|
29
|
+
- A **Core Slash Command** is always registered with `source="builtin"` on the Execution Axis. Its name belongs to the **Reserved Name Set** and cannot be overridden by any markdown-template command.
|
|
30
|
+
- A **Default Custom Command** is registered with `source="custom"` on the Execution Axis (same dispatch path as project/user/plugin custom commands) and `source_scope="default"` on the Source Axis.
|
|
31
|
+
- The **Execution Axis** and **Source Axis** are independent. A single registered command carries a value on each.
|
|
32
|
+
- The word "builtin" appears only on the Execution Axis (`source="builtin"`) and refers exclusively to **Core Slash Commands**. The Source Axis value `"default"` refers exclusively to **Default Custom Commands**.
|
|
33
|
+
- **Default Custom Commands** are loaded from package resources via `importlib.resources`, parsed by the same frontmatter pipeline as project/user commands, and subject to the **Override Priority Chain** at registration time.
|
|
34
|
+
- A **Default Custom Command** parse failure is a developer-visible error (raised at discovery), not a user-visible warning, because package resources are trusted input maintained by the comate_cli maintainers.
|
|
35
|
+
- The **Reserved Name Set** gates discovery only; it prevents user-authored markdown commands from shadowing **Core Slash Commands**, and only Core Slash Commands.
|
|
36
|
+
|
|
37
|
+
## Flagged ambiguities
|
|
38
|
+
|
|
39
|
+
- None currently. The original "builtin" collision between Execution Axis and Source Axis has been resolved by reserving "builtin" for Core Slash Commands and introducing "default" for the new shipped markdown resources.
|
|
@@ -54,7 +54,7 @@ async def _check_update(*, profiler: StartupProfiler | None = None) -> UpdateInf
|
|
|
54
54
|
if profiler is not None:
|
|
55
55
|
profiler.mark("impl.start")
|
|
56
56
|
try:
|
|
57
|
-
check_update, _
|
|
57
|
+
check_update, _ = _import_update_check_core()
|
|
58
58
|
return await check_update(log=logger, profiler=profiler)
|
|
59
59
|
finally:
|
|
60
60
|
if profiler is not None:
|
|
@@ -308,15 +308,11 @@ def _import_update_check_core():
|
|
|
308
308
|
from comate_cli.terminal_agent.update_check import (
|
|
309
309
|
check_update,
|
|
310
310
|
get_pending_update_prompt_info,
|
|
311
|
-
record_update_check_attempt,
|
|
312
|
-
should_check_for_update,
|
|
313
311
|
)
|
|
314
312
|
|
|
315
313
|
return (
|
|
316
314
|
check_update,
|
|
317
315
|
get_pending_update_prompt_info,
|
|
318
|
-
record_update_check_attempt,
|
|
319
|
-
should_check_for_update,
|
|
320
316
|
)
|
|
321
317
|
|
|
322
318
|
|
|
@@ -387,7 +383,7 @@ def _build_agent(
|
|
|
387
383
|
profiler: StartupProfiler | None = None,
|
|
388
384
|
extra_disallowed_tools: tuple[str, ...] | None = None,
|
|
389
385
|
) -> Agent:
|
|
390
|
-
Agent, AgentConfig,
|
|
386
|
+
Agent, AgentConfig, _, EnvOptions = _import_sdk_agent_components(profiler)
|
|
391
387
|
from comate_cli.terminal_agent.config import store
|
|
392
388
|
|
|
393
389
|
resolved_project_root = project_root or _resolve_cli_project_root()
|
|
@@ -407,9 +403,6 @@ def _build_agent(
|
|
|
407
403
|
strict_hook_settings=False,
|
|
408
404
|
memory_background_enabled=snapshot.memory_background_enabled,
|
|
409
405
|
memory_dreaming_enabled=snapshot.memory_dreaming_enabled,
|
|
410
|
-
compaction=CompactionConfig(
|
|
411
|
-
threshold_ratio=snapshot.compaction_threshold_ratio,
|
|
412
|
-
),
|
|
413
406
|
disallowed_tools=disallowed_tools,
|
|
414
407
|
)
|
|
415
408
|
if profiler is not None:
|
|
@@ -546,6 +539,69 @@ async def _run_print_mode(
|
|
|
546
539
|
)
|
|
547
540
|
|
|
548
541
|
|
|
542
|
+
def _iter_exception_chain(exc: BaseException) -> Iterator[BaseException]:
|
|
543
|
+
seen: set[int] = set()
|
|
544
|
+
current: BaseException | None = exc
|
|
545
|
+
while current is not None and id(current) not in seen:
|
|
546
|
+
seen.add(id(current))
|
|
547
|
+
yield current
|
|
548
|
+
current = current.__cause__ or current.__context__
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _exception_traceback_mentions_memory_prefetch(exc: BaseException) -> bool:
|
|
552
|
+
for chained in _iter_exception_chain(exc):
|
|
553
|
+
tb = chained.__traceback__
|
|
554
|
+
while tb is not None:
|
|
555
|
+
code = tb.tb_frame.f_code
|
|
556
|
+
filename = code.co_filename
|
|
557
|
+
function_name = code.co_name
|
|
558
|
+
if function_name == "_prefetch_relevant_memories":
|
|
559
|
+
return True
|
|
560
|
+
if (
|
|
561
|
+
filename.endswith("memory_runtime.py")
|
|
562
|
+
and function_name == "_prefetch_relevant_memories"
|
|
563
|
+
):
|
|
564
|
+
return True
|
|
565
|
+
tb = tb.tb_next
|
|
566
|
+
return False
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def _loop_context_repr_mentions_memory_prefetch(context: dict) -> bool:
|
|
570
|
+
for key in ("future", "task", "handle"):
|
|
571
|
+
value = context.get(key)
|
|
572
|
+
if value is None:
|
|
573
|
+
continue
|
|
574
|
+
representation = repr(value)
|
|
575
|
+
if "_prefetch_relevant_memories" in representation:
|
|
576
|
+
return True
|
|
577
|
+
if "side_query:memory_select" in representation:
|
|
578
|
+
return True
|
|
579
|
+
return False
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def _is_memory_prefetch_unretrieved_task_exception(context: dict) -> bool:
|
|
583
|
+
message = str(context.get("message") or "")
|
|
584
|
+
if "Task exception was never retrieved" not in message:
|
|
585
|
+
return False
|
|
586
|
+
|
|
587
|
+
exc = context.get("exception")
|
|
588
|
+
if not isinstance(exc, BaseException):
|
|
589
|
+
return False
|
|
590
|
+
|
|
591
|
+
try:
|
|
592
|
+
from comate_agent_sdk.llm.exceptions import ModelProviderError
|
|
593
|
+
except Exception:
|
|
594
|
+
return False
|
|
595
|
+
|
|
596
|
+
if not isinstance(exc, ModelProviderError):
|
|
597
|
+
return False
|
|
598
|
+
|
|
599
|
+
return (
|
|
600
|
+
_loop_context_repr_mentions_memory_prefetch(context)
|
|
601
|
+
or _exception_traceback_mentions_memory_prefetch(exc)
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
|
|
549
605
|
def _install_event_loop_exception_handler() -> None:
|
|
550
606
|
"""Install a custom exception handler to suppress known MCP transport race conditions.
|
|
551
607
|
|
|
@@ -563,6 +619,17 @@ def _install_event_loop_exception_handler() -> None:
|
|
|
563
619
|
if isinstance(exc, RuntimeError) and "cancel scope" in str(exc):
|
|
564
620
|
logger.warning("MCP transport cleanup race detected (suppressed): %s", exc)
|
|
565
621
|
return
|
|
622
|
+
if _is_memory_prefetch_unretrieved_task_exception(context):
|
|
623
|
+
logger.debug(
|
|
624
|
+
"Relevant memory prefetch task failure suppressed during shutdown: %s",
|
|
625
|
+
exc,
|
|
626
|
+
exc_info=(
|
|
627
|
+
(type(exc), exc, exc.__traceback__)
|
|
628
|
+
if isinstance(exc, BaseException)
|
|
629
|
+
else None
|
|
630
|
+
),
|
|
631
|
+
)
|
|
632
|
+
return
|
|
566
633
|
if _default is not None:
|
|
567
634
|
_default(loop, context)
|
|
568
635
|
else:
|
|
@@ -623,12 +690,7 @@ async def run(
|
|
|
623
690
|
print_logo(console, project_root=project_root)
|
|
624
691
|
profiler.mark("logo.printed")
|
|
625
692
|
|
|
626
|
-
(
|
|
627
|
-
_check_update_func,
|
|
628
|
-
get_pending_update_prompt_info,
|
|
629
|
-
_record_update_check_attempt,
|
|
630
|
-
_should_check_for_update,
|
|
631
|
-
) = _import_update_check_core()
|
|
693
|
+
_check_update_func, get_pending_update_prompt_info = _import_update_check_core()
|
|
632
694
|
pending_info = get_pending_update_prompt_info()
|
|
633
695
|
if pending_info is not None:
|
|
634
696
|
should_stop = await _handle_update_on_launch(pending_info)
|
|
@@ -719,21 +781,11 @@ async def run(
|
|
|
719
781
|
profiler.mark("tui.init.done")
|
|
720
782
|
tui.add_resume_history(mode)
|
|
721
783
|
update_check_task: asyncio.Task[None] | None = None
|
|
722
|
-
(
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
) = _import_update_check_core()
|
|
728
|
-
if should_check_for_update():
|
|
729
|
-
# attempt 语义:先记录尝试时间,确保 interval 内最多发起一次网络检查
|
|
730
|
-
record_update_check_attempt()
|
|
731
|
-
update_check_task = _schedule_update_check_on_launch(
|
|
732
|
-
renderer=renderer,
|
|
733
|
-
profiler=profiler,
|
|
734
|
-
)
|
|
735
|
-
else:
|
|
736
|
-
profiler.mark("update_check.throttled")
|
|
784
|
+
_check_update_func, _get_pending_update_prompt_info = _import_update_check_core()
|
|
785
|
+
update_check_task = _schedule_update_check_on_launch(
|
|
786
|
+
renderer=renderer,
|
|
787
|
+
profiler=profiler,
|
|
788
|
+
)
|
|
737
789
|
|
|
738
790
|
async def _mcp_loader() -> None:
|
|
739
791
|
await _preload_mcp_in_tui(session, profiler=profiler.child("mcp"))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""ConfigSnapshot:/config UI、store、startup 三处共享的偏好快照。
|
|
2
2
|
|
|
3
|
-
Revision
|
|
3
|
+
Revision 5: 6 项偏好(display_thinking, model_level, memory_background, memory_dreaming, token_cost, recap)。
|
|
4
4
|
"""
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
@@ -8,13 +8,6 @@ from dataclasses import dataclass, replace
|
|
|
8
8
|
from typing import Literal
|
|
9
9
|
|
|
10
10
|
ModelLevelPreset = Literal["LOW", "MID", "HIGH"]
|
|
11
|
-
ThresholdPreset = Literal["conservative", "balanced", "aggressive"]
|
|
12
|
-
|
|
13
|
-
COMPACT_PRESETS: dict[str, float] = {
|
|
14
|
-
"conservative": 0.65,
|
|
15
|
-
"balanced": 0.80,
|
|
16
|
-
"aggressive": 0.92,
|
|
17
|
-
}
|
|
18
11
|
|
|
19
12
|
|
|
20
13
|
@dataclass(frozen=True, slots=True)
|
|
@@ -23,11 +16,10 @@ class ConfigSnapshot:
|
|
|
23
16
|
|
|
24
17
|
display_thinking: bool = False
|
|
25
18
|
model_level: ModelLevelPreset = "MID"
|
|
26
|
-
compaction_threshold_ratio: float = 0.80
|
|
27
|
-
compaction_threshold_preset: ThresholdPreset = "balanced"
|
|
28
19
|
memory_background_enabled: bool = True
|
|
29
20
|
memory_dreaming_enabled: bool = True
|
|
30
21
|
token_cost_enabled: bool = False
|
|
22
|
+
recap_enabled: bool = True
|
|
31
23
|
|
|
32
24
|
@classmethod
|
|
33
25
|
def default(cls) -> "ConfigSnapshot":
|
|
@@ -39,13 +31,6 @@ class ConfigSnapshot:
|
|
|
39
31
|
def with_model_level(self, level: ModelLevelPreset) -> "ConfigSnapshot":
|
|
40
32
|
return replace(self, model_level=level)
|
|
41
33
|
|
|
42
|
-
def with_threshold_preset(self, preset: ThresholdPreset) -> "ConfigSnapshot":
|
|
43
|
-
return replace(
|
|
44
|
-
self,
|
|
45
|
-
compaction_threshold_preset=preset,
|
|
46
|
-
compaction_threshold_ratio=COMPACT_PRESETS[preset],
|
|
47
|
-
)
|
|
48
|
-
|
|
49
34
|
def with_memory_background(self, enabled: bool) -> "ConfigSnapshot":
|
|
50
35
|
return replace(self, memory_background_enabled=enabled)
|
|
51
36
|
|
|
@@ -54,3 +39,6 @@ class ConfigSnapshot:
|
|
|
54
39
|
|
|
55
40
|
def with_token_cost(self, enabled: bool) -> "ConfigSnapshot":
|
|
56
41
|
return replace(self, token_cost_enabled=enabled)
|
|
42
|
+
|
|
43
|
+
def with_recap(self, enabled: bool) -> "ConfigSnapshot":
|
|
44
|
+
return replace(self, recap_enabled=enabled)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
仿 plugins/plugin_picker.py:PluginPickerUI 的模式:
|
|
4
4
|
enter() → container → focus_target() → handle_*。
|
|
5
5
|
|
|
6
|
-
Revision
|
|
6
|
+
Revision 5: 6 行 ConfigField。
|
|
7
7
|
"""
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
@@ -34,27 +34,28 @@ logger = logging.getLogger(__name__)
|
|
|
34
34
|
_FIELD_LABELS: dict[ConfigField, str] = {
|
|
35
35
|
ConfigField.DISPLAY_THINKING: "Thinking display",
|
|
36
36
|
ConfigField.MODEL_LEVEL: "Default model level",
|
|
37
|
-
ConfigField.AUTO_COMPACT_THRESHOLD: "Auto compact threshold",
|
|
38
37
|
ConfigField.MEMORY_BACKGROUND: "Memory background",
|
|
39
38
|
ConfigField.MEMORY_DREAMING: "Memory dreaming",
|
|
40
39
|
ConfigField.TOKEN_COST: "Token cost",
|
|
40
|
+
ConfigField.RECAP: "Recap",
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
_FIELD_ORDER = (
|
|
44
44
|
ConfigField.DISPLAY_THINKING,
|
|
45
45
|
ConfigField.MODEL_LEVEL,
|
|
46
|
-
ConfigField.AUTO_COMPACT_THRESHOLD,
|
|
47
46
|
ConfigField.MEMORY_BACKGROUND,
|
|
48
47
|
ConfigField.MEMORY_DREAMING,
|
|
49
48
|
ConfigField.TOKEN_COST,
|
|
49
|
+
ConfigField.RECAP,
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
-
_ENUM_FIELDS = {ConfigField.MODEL_LEVEL
|
|
52
|
+
_ENUM_FIELDS = {ConfigField.MODEL_LEVEL}
|
|
53
53
|
_BOOL_FIELDS = {
|
|
54
54
|
ConfigField.DISPLAY_THINKING,
|
|
55
55
|
ConfigField.MEMORY_BACKGROUND,
|
|
56
56
|
ConfigField.MEMORY_DREAMING,
|
|
57
57
|
ConfigField.TOKEN_COST,
|
|
58
|
+
ConfigField.RECAP,
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
|
|
@@ -160,14 +161,14 @@ class ConfigPickerUI:
|
|
|
160
161
|
return f"[ {'on ' if snap.display_thinking else 'off'} ]"
|
|
161
162
|
if f == ConfigField.MODEL_LEVEL:
|
|
162
163
|
return f"[ {snap.model_level} ]"
|
|
163
|
-
if f == ConfigField.AUTO_COMPACT_THRESHOLD:
|
|
164
|
-
return f"[ {snap.compaction_threshold_preset} ({snap.compaction_threshold_ratio:.2f}) ]"
|
|
165
164
|
if f == ConfigField.MEMORY_BACKGROUND:
|
|
166
165
|
return f"[ {'on ' if snap.memory_background_enabled else 'off'} ]"
|
|
167
166
|
if f == ConfigField.MEMORY_DREAMING:
|
|
168
167
|
return f"[ {'on ' if snap.memory_dreaming_enabled else 'off'} ]"
|
|
169
168
|
if f == ConfigField.TOKEN_COST:
|
|
170
169
|
return f"[ {'on ' if snap.token_cost_enabled else 'off'} ]"
|
|
170
|
+
if f == ConfigField.RECAP:
|
|
171
|
+
return f"[ {'on ' if snap.recap_enabled else 'off'} ]"
|
|
171
172
|
return ""
|
|
172
173
|
|
|
173
174
|
def _subview_fragments(self) -> list[tuple[str, str]]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""ConfigPicker 的纯逻辑状态机——不依赖 prompt_toolkit。
|
|
2
2
|
|
|
3
|
-
Revision
|
|
3
|
+
Revision 5: 6 行 ConfigField。
|
|
4
4
|
"""
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
@@ -9,20 +9,18 @@ from enum import Enum
|
|
|
9
9
|
from typing import Callable
|
|
10
10
|
|
|
11
11
|
from .model import (
|
|
12
|
-
COMPACT_PRESETS,
|
|
13
12
|
ConfigSnapshot,
|
|
14
13
|
ModelLevelPreset,
|
|
15
|
-
ThresholdPreset,
|
|
16
14
|
)
|
|
17
15
|
|
|
18
16
|
|
|
19
17
|
class ConfigField(Enum):
|
|
20
18
|
DISPLAY_THINKING = "display_thinking"
|
|
21
19
|
MODEL_LEVEL = "model_level"
|
|
22
|
-
AUTO_COMPACT_THRESHOLD = "auto_compact_threshold"
|
|
23
20
|
MEMORY_BACKGROUND = "memory_background"
|
|
24
21
|
MEMORY_DREAMING = "memory_dreaming"
|
|
25
22
|
TOKEN_COST = "token_cost"
|
|
23
|
+
RECAP = "recap"
|
|
26
24
|
|
|
27
25
|
|
|
28
26
|
class ViewMode(Enum):
|
|
@@ -51,19 +49,12 @@ class PickerExit:
|
|
|
51
49
|
_FIELD_ORDER = (
|
|
52
50
|
ConfigField.DISPLAY_THINKING,
|
|
53
51
|
ConfigField.MODEL_LEVEL,
|
|
54
|
-
ConfigField.AUTO_COMPACT_THRESHOLD,
|
|
55
52
|
ConfigField.MEMORY_BACKGROUND,
|
|
56
53
|
ConfigField.MEMORY_DREAMING,
|
|
57
54
|
ConfigField.TOKEN_COST,
|
|
55
|
+
ConfigField.RECAP,
|
|
58
56
|
)
|
|
59
57
|
|
|
60
|
-
_THRESHOLD_VALUES: tuple[ThresholdPreset, ...] = ("conservative", "balanced", "aggressive")
|
|
61
|
-
_THRESHOLD_DESCRIPTIONS: dict[str, str] = {
|
|
62
|
-
"conservative": "0.65 — compact more often; smaller per-call ctx.",
|
|
63
|
-
"balanced": "0.80 — default. Compact when ctx >= 80% utilized.",
|
|
64
|
-
"aggressive": "0.92 — compact late; larger ctx per call.",
|
|
65
|
-
}
|
|
66
|
-
|
|
67
58
|
_MODEL_LEVEL_VALUES: tuple[ModelLevelPreset, ...] = ("LOW", "MID", "HIGH")
|
|
68
59
|
_MODEL_LEVEL_DESCRIPTIONS: dict[str, str] = {
|
|
69
60
|
"LOW": "Fastest, cheapest. Good for simple tasks.",
|
|
@@ -98,11 +89,6 @@ class ConfigPickerState:
|
|
|
98
89
|
def subview_options(self) -> list[SubviewOption]:
|
|
99
90
|
if self.view_mode != ViewMode.SUBVIEW or self.subview_field is None:
|
|
100
91
|
return []
|
|
101
|
-
if self.subview_field == ConfigField.AUTO_COMPACT_THRESHOLD:
|
|
102
|
-
return [
|
|
103
|
-
SubviewOption(v, v.capitalize(), _THRESHOLD_DESCRIPTIONS[v])
|
|
104
|
-
for v in _THRESHOLD_VALUES
|
|
105
|
-
]
|
|
106
92
|
if self.subview_field == ConfigField.MODEL_LEVEL:
|
|
107
93
|
return [
|
|
108
94
|
SubviewOption(v, v, _MODEL_LEVEL_DESCRIPTIONS[v])
|
|
@@ -140,10 +126,8 @@ class ConfigPickerState:
|
|
|
140
126
|
self.draft = self.draft.with_memory_dreaming(not self.draft.memory_dreaming_enabled)
|
|
141
127
|
elif f == ConfigField.TOKEN_COST:
|
|
142
128
|
self.draft = self.draft.with_token_cost(not self.draft.token_cost_enabled)
|
|
143
|
-
elif f == ConfigField.
|
|
144
|
-
|
|
145
|
-
next_v = _THRESHOLD_VALUES[(cur + 1) % len(_THRESHOLD_VALUES)]
|
|
146
|
-
self.draft = self.draft.with_threshold_preset(next_v)
|
|
129
|
+
elif f == ConfigField.RECAP:
|
|
130
|
+
self.draft = self.draft.with_recap(not self.draft.recap_enabled)
|
|
147
131
|
elif f == ConfigField.MODEL_LEVEL:
|
|
148
132
|
cur = _MODEL_LEVEL_VALUES.index(self.draft.model_level)
|
|
149
133
|
next_v = _MODEL_LEVEL_VALUES[(cur + 1) % len(_MODEL_LEVEL_VALUES)]
|
|
@@ -152,7 +136,7 @@ class ConfigPickerState:
|
|
|
152
136
|
def handle_enter(self) -> PickerExit | None:
|
|
153
137
|
if self.view_mode == ViewMode.MAIN:
|
|
154
138
|
f = self.focused_field
|
|
155
|
-
if f
|
|
139
|
+
if f == ConfigField.MODEL_LEVEL:
|
|
156
140
|
self.view_mode = ViewMode.SUBVIEW
|
|
157
141
|
self.subview_field = f
|
|
158
142
|
self.subview_cursor = self._initial_subview_cursor(f)
|
|
@@ -166,9 +150,7 @@ class ConfigPickerState:
|
|
|
166
150
|
self._return_to_main()
|
|
167
151
|
return None
|
|
168
152
|
chosen = opts[self.subview_cursor].value
|
|
169
|
-
if self.subview_field == ConfigField.
|
|
170
|
-
self.draft = self.draft.with_threshold_preset(chosen) # type: ignore[arg-type]
|
|
171
|
-
elif self.subview_field == ConfigField.MODEL_LEVEL:
|
|
153
|
+
if self.subview_field == ConfigField.MODEL_LEVEL:
|
|
172
154
|
self.draft = self.draft.with_model_level(chosen) # type: ignore[arg-type]
|
|
173
155
|
self._return_to_main()
|
|
174
156
|
return None
|
|
@@ -202,8 +184,6 @@ class ConfigPickerState:
|
|
|
202
184
|
|
|
203
185
|
# ---- helpers --------------------------------------------------------
|
|
204
186
|
def _initial_subview_cursor(self, f: ConfigField) -> int:
|
|
205
|
-
if f == ConfigField.AUTO_COMPACT_THRESHOLD:
|
|
206
|
-
return _THRESHOLD_VALUES.index(self.draft.compaction_threshold_preset)
|
|
207
187
|
if f == ConfigField.MODEL_LEVEL:
|
|
208
188
|
return _MODEL_LEVEL_VALUES.index(self.draft.model_level)
|
|
209
189
|
return 0
|
|
@@ -16,15 +16,12 @@ from typing import Any, get_args
|
|
|
16
16
|
from comate_agent_sdk.agent.settings import USER_SETTINGS_PATH, _write_text_atomic
|
|
17
17
|
|
|
18
18
|
from .model import (
|
|
19
|
-
COMPACT_PRESETS,
|
|
20
19
|
ConfigSnapshot,
|
|
21
20
|
ModelLevelPreset,
|
|
22
|
-
ThresholdPreset,
|
|
23
21
|
)
|
|
24
22
|
|
|
25
23
|
logger = logging.getLogger(__name__)
|
|
26
24
|
|
|
27
|
-
_VALID_PRESETS = set(get_args(ThresholdPreset))
|
|
28
25
|
_VALID_MODEL_LEVELS = {v.lower() for v in get_args(ModelLevelPreset)}
|
|
29
26
|
_TRUTHY = {"true", "1", "on", "yes"}
|
|
30
27
|
_FALSY = {"false", "0", "off", "no"}
|
|
@@ -32,9 +29,9 @@ _CLI_SECTION = "comate_cli"
|
|
|
32
29
|
_CLI_CONFIG_SECTION = "config"
|
|
33
30
|
_CLI_CONFIG_DISPLAY_SECTION = "display"
|
|
34
31
|
_CLI_CONFIG_MODEL_SECTION = "model"
|
|
35
|
-
_CLI_CONFIG_COMPACTION_SECTION = "compaction"
|
|
36
32
|
_CLI_CONFIG_MEMORY_SECTION = "memory"
|
|
37
33
|
_CLI_CONFIG_TOKEN_COST_SECTION = "token_cost"
|
|
34
|
+
_CLI_CONFIG_RECAP_SECTION = "recap"
|
|
38
35
|
|
|
39
36
|
|
|
40
37
|
def _settings_path() -> Path:
|
|
@@ -143,19 +140,6 @@ def _apply_legacy_settings(snapshot: ConfigSnapshot, data: Mapping[str, Any]) ->
|
|
|
143
140
|
model,
|
|
144
141
|
)
|
|
145
142
|
|
|
146
|
-
# compaction.auto_threshold_preset
|
|
147
|
-
compaction = data.get("compaction") or {}
|
|
148
|
-
if isinstance(compaction, dict):
|
|
149
|
-
preset = compaction.get("auto_threshold_preset")
|
|
150
|
-
if isinstance(preset, str):
|
|
151
|
-
if preset in _VALID_PRESETS:
|
|
152
|
-
snapshot = snapshot.with_threshold_preset(preset) # type: ignore[arg-type]
|
|
153
|
-
else:
|
|
154
|
-
logger.warning(
|
|
155
|
-
"settings.json compaction.auto_threshold_preset 非法 (%r),回退 balanced",
|
|
156
|
-
preset,
|
|
157
|
-
)
|
|
158
|
-
|
|
159
143
|
# memory_state.enabled
|
|
160
144
|
memory_state = data.get("memory_state") or {}
|
|
161
145
|
if isinstance(memory_state, dict) and isinstance(memory_state.get("enabled"), bool):
|
|
@@ -211,40 +195,6 @@ def _apply_cli_config(snapshot: ConfigSnapshot, section: dict[str, Any]) -> Conf
|
|
|
211
195
|
raw_model,
|
|
212
196
|
)
|
|
213
197
|
|
|
214
|
-
compaction = _named_object(
|
|
215
|
-
section,
|
|
216
|
-
_CLI_CONFIG_COMPACTION_SECTION,
|
|
217
|
-
"comate_cli.config.compaction",
|
|
218
|
-
)
|
|
219
|
-
raw_preset = _named_string(
|
|
220
|
-
compaction,
|
|
221
|
-
"threshold_preset",
|
|
222
|
-
"comate_cli.config.compaction.threshold_preset",
|
|
223
|
-
)
|
|
224
|
-
if raw_preset is not None:
|
|
225
|
-
if raw_preset in _VALID_PRESETS:
|
|
226
|
-
snapshot = snapshot.with_threshold_preset(raw_preset) # type: ignore[arg-type]
|
|
227
|
-
raw_ratio = compaction.get("threshold_ratio")
|
|
228
|
-
if isinstance(raw_ratio, (int, float)) and not isinstance(raw_ratio, bool):
|
|
229
|
-
expected = COMPACT_PRESETS[raw_preset]
|
|
230
|
-
if float(raw_ratio) != expected:
|
|
231
|
-
logger.warning(
|
|
232
|
-
"settings.json comate_cli.config.compaction.threshold_ratio "
|
|
233
|
-
"与 threshold_preset 不一致 (%r != %r),以 preset 为准",
|
|
234
|
-
raw_ratio,
|
|
235
|
-
expected,
|
|
236
|
-
)
|
|
237
|
-
else:
|
|
238
|
-
logger.warning(
|
|
239
|
-
"settings.json comate_cli.config.compaction.threshold_preset 非法 (%r),保留已有值",
|
|
240
|
-
raw_preset,
|
|
241
|
-
)
|
|
242
|
-
elif "threshold_ratio" in compaction:
|
|
243
|
-
logger.warning(
|
|
244
|
-
"settings.json comate_cli.config.compaction.threshold_ratio 缺少 "
|
|
245
|
-
"threshold_preset,保留已有 preset 派生值"
|
|
246
|
-
)
|
|
247
|
-
|
|
248
198
|
memory = _named_object(
|
|
249
199
|
section,
|
|
250
200
|
_CLI_CONFIG_MEMORY_SECTION,
|
|
@@ -279,6 +229,19 @@ def _apply_cli_config(snapshot: ConfigSnapshot, section: dict[str, Any]) -> Conf
|
|
|
279
229
|
if parsed_bool is not None:
|
|
280
230
|
snapshot = snapshot.with_token_cost(parsed_bool)
|
|
281
231
|
|
|
232
|
+
recap = _named_object(
|
|
233
|
+
section,
|
|
234
|
+
_CLI_CONFIG_RECAP_SECTION,
|
|
235
|
+
"comate_cli.config.recap",
|
|
236
|
+
)
|
|
237
|
+
parsed_bool = _named_bool(
|
|
238
|
+
recap,
|
|
239
|
+
"enabled",
|
|
240
|
+
"comate_cli.config.recap.enabled",
|
|
241
|
+
)
|
|
242
|
+
if parsed_bool is not None:
|
|
243
|
+
snapshot = snapshot.with_recap(parsed_bool)
|
|
244
|
+
|
|
282
245
|
return snapshot
|
|
283
246
|
|
|
284
247
|
|
|
@@ -310,10 +273,6 @@ def save(snapshot: ConfigSnapshot) -> SaveResult:
|
|
|
310
273
|
def build_cli_config_settings(snapshot: ConfigSnapshot) -> dict[str, Any]:
|
|
311
274
|
"""Build the canonical comate_cli.config settings subtree."""
|
|
312
275
|
return {
|
|
313
|
-
"compaction": {
|
|
314
|
-
"threshold_preset": snapshot.compaction_threshold_preset,
|
|
315
|
-
"threshold_ratio": snapshot.compaction_threshold_ratio,
|
|
316
|
-
},
|
|
317
276
|
"display": {"thinking": snapshot.display_thinking},
|
|
318
277
|
"memory": {
|
|
319
278
|
"background_enabled": snapshot.memory_background_enabled,
|
|
@@ -321,6 +280,7 @@ def build_cli_config_settings(snapshot: ConfigSnapshot) -> dict[str, Any]:
|
|
|
321
280
|
},
|
|
322
281
|
"model": {"level": snapshot.model_level.lower()},
|
|
323
282
|
"token_cost": {"enabled": snapshot.token_cost_enabled},
|
|
283
|
+
"recap": {"enabled": snapshot.recap_enabled},
|
|
324
284
|
}
|
|
325
285
|
|
|
326
286
|
|
|
@@ -345,7 +305,10 @@ def apply_cli_config_snapshot_to_settings(
|
|
|
345
305
|
"""Apply canonical comate_cli.config plus SDK mirrors to a settings object."""
|
|
346
306
|
data = deepcopy(dict(existing))
|
|
347
307
|
cli = _ensure_object(data, _CLI_SECTION)
|
|
348
|
-
|
|
308
|
+
existing_cli_config = cli.get(_CLI_CONFIG_SECTION)
|
|
309
|
+
cli_config = deepcopy(existing_cli_config) if isinstance(existing_cli_config, dict) else {}
|
|
310
|
+
cli_config.update(build_cli_config_settings(snapshot))
|
|
311
|
+
cli[_CLI_CONFIG_SECTION] = cli_config
|
|
349
312
|
|
|
350
313
|
data["model"] = snapshot.model_level.lower()
|
|
351
314
|
|
|
@@ -362,11 +325,6 @@ def apply_cli_config_snapshot_to_settings(
|
|
|
362
325
|
display.pop("thinking", None)
|
|
363
326
|
_cleanup_empty_object(data, "display")
|
|
364
327
|
|
|
365
|
-
compaction = data.get("compaction")
|
|
366
|
-
if isinstance(compaction, dict):
|
|
367
|
-
compaction.pop("auto_threshold_preset", None)
|
|
368
|
-
_cleanup_empty_object(data, "compaction")
|
|
369
|
-
|
|
370
328
|
return data
|
|
371
329
|
|
|
372
330
|
|
|
@@ -5,6 +5,7 @@ import logging
|
|
|
5
5
|
import re
|
|
6
6
|
import shlex
|
|
7
7
|
from dataclasses import dataclass
|
|
8
|
+
from importlib.resources import as_file, files
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import Literal
|
|
10
11
|
|
|
@@ -25,6 +26,9 @@ FILE_REF_PATTERN = re.compile(r"(?<!\S)@([^\s]+)")
|
|
|
25
26
|
_MARKER_PREFIX = "<<__COMATE_CUSTOM_BLOCK_"
|
|
26
27
|
_MARKER_SUFFIX = "__>>"
|
|
27
28
|
|
|
29
|
+
_BUILTIN_COMMANDS_PACKAGE = "comate_cli.terminal_agent"
|
|
30
|
+
_BUILTIN_COMMANDS_RESOURCE = "builtin_commands"
|
|
31
|
+
|
|
28
32
|
|
|
29
33
|
class CustomSlashExpandError(RuntimeError):
|
|
30
34
|
pass
|
|
@@ -35,7 +39,7 @@ class CustomSlashCommand:
|
|
|
35
39
|
name: str
|
|
36
40
|
description: str
|
|
37
41
|
template: str
|
|
38
|
-
source_scope: Literal["project", "user", "plugin"]
|
|
42
|
+
source_scope: Literal["project", "user", "plugin", "default"]
|
|
39
43
|
namespace: str
|
|
40
44
|
source_path: Path
|
|
41
45
|
argument_hint: str | None = None
|
|
@@ -114,10 +118,44 @@ def discover_custom_slash_commands(
|
|
|
114
118
|
return CustomSlashLoadResult(commands=tuple(commands), warnings=tuple(warnings))
|
|
115
119
|
|
|
116
120
|
|
|
121
|
+
def discover_default_slash_commands() -> CustomSlashLoadResult:
|
|
122
|
+
"""加载随包分发的 default scope custom commands。
|
|
123
|
+
|
|
124
|
+
default scope 的 md 资源由 comate_cli 维护者维护,是可信输入。解析失败
|
|
125
|
+
直接 raise CustomSlashExpandError(developer-visible error),不返回
|
|
126
|
+
warnings;这与 project/user scope 把 warnings 塞给终端用户的语义不同。
|
|
127
|
+
|
|
128
|
+
扁平布局:扫描 ``comate_cli.terminal_agent.builtin_commands`` 下的所有
|
|
129
|
+
``*.md`` 资源,namespace 强制为空字符串。非 .md 文件被忽略。
|
|
130
|
+
"""
|
|
131
|
+
root = files(_BUILTIN_COMMANDS_PACKAGE).joinpath(_BUILTIN_COMMANDS_RESOURCE)
|
|
132
|
+
if not root.is_dir():
|
|
133
|
+
return CustomSlashLoadResult(commands=(), warnings=())
|
|
134
|
+
|
|
135
|
+
commands: list[CustomSlashCommand] = []
|
|
136
|
+
for entry in sorted(root.iterdir(), key=lambda item: item.name):
|
|
137
|
+
if not entry.name.endswith(".md"):
|
|
138
|
+
continue
|
|
139
|
+
with as_file(entry) as path:
|
|
140
|
+
parsed = _parse_custom_command_file(
|
|
141
|
+
file_path=path,
|
|
142
|
+
scope="default",
|
|
143
|
+
root_dir=path.parent,
|
|
144
|
+
)
|
|
145
|
+
if isinstance(parsed, str):
|
|
146
|
+
raise CustomSlashExpandError(
|
|
147
|
+
f"Built-in command resource {entry.name} is malformed: {parsed}"
|
|
148
|
+
)
|
|
149
|
+
commands.append(parsed)
|
|
150
|
+
|
|
151
|
+
commands.sort(key=lambda item: item.name.lower())
|
|
152
|
+
return CustomSlashLoadResult(commands=tuple(commands), warnings=())
|
|
153
|
+
|
|
154
|
+
|
|
117
155
|
def _parse_custom_command_file(
|
|
118
156
|
*,
|
|
119
157
|
file_path: Path,
|
|
120
|
-
scope: Literal["project", "user"],
|
|
158
|
+
scope: Literal["project", "user", "default"],
|
|
121
159
|
root_dir: Path,
|
|
122
160
|
) -> CustomSlashCommand | str:
|
|
123
161
|
command_name = file_path.stem.strip()
|