comate-cli 0.7.0a9__tar.gz → 0.7.0a10__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.7.0a9 → comate_cli-0.7.0a10}/PKG-INFO +1 -1
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/app.py +10 -1
- comate_cli-0.7.0a10/comate_cli/terminal_agent/config/model.py +52 -0
- comate_cli-0.7.0a10/comate_cli/terminal_agent/config/picker.py +232 -0
- comate_cli-0.7.0a10/comate_cli/terminal_agent/config/picker_state.py +210 -0
- comate_cli-0.7.0a10/comate_cli/terminal_agent/config/store.py +173 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/error_display.py +22 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/event_renderer.py +11 -2
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/history_printer.py +1 -2
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/logging_adapter.py +59 -2
- comate_cli-0.7.0a10/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/slash_commands.py +5 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tool_result_formatters.py +125 -8
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tool_view.py +49 -2
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tui.py +12 -1
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tui_parts/commands.py +40 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tui_parts/history_sync.py +2 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tui_parts/input_behavior.py +3 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tui_parts/key_bindings.py +42 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tui_parts/render_panels.py +1 -1
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tui_parts/ui_mode.py +1 -0
- comate_cli-0.7.0a10/docs/superpowers/plans/2026-05-22-log-style-implementation-plan.md +444 -0
- comate_cli-0.7.0a10/docs/superpowers/specs/2026-05-22-comate-cli-log-styling-design.md +71 -0
- comate_cli-0.7.0a10/docs/superpowers/specs/2026-05-22-log-style-optimization-design.md +114 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/pyproject.toml +1 -1
- comate_cli-0.7.0a10/tests/config/__init__.py +0 -0
- comate_cli-0.7.0a10/tests/config/test_model.py +88 -0
- comate_cli-0.7.0a10/tests/config/test_picker_state.py +205 -0
- comate_cli-0.7.0a10/tests/config/test_picker_ui.py +42 -0
- comate_cli-0.7.0a10/tests/config/test_roundtrip.py +75 -0
- comate_cli-0.7.0a10/tests/config/test_store_load.py +112 -0
- comate_cli-0.7.0a10/tests/config/test_store_save.py +103 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_event_renderer.py +91 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_format_error.py +52 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_history_printer_tool_fold.py +8 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_history_sync.py +114 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_logging_adapter.py +235 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_task_panel_rendering.py +3 -3
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tool_result_formatters.py +71 -1
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tool_view.py +13 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_transcript_viewer.py +44 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/uv.lock +1441 -1457
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/.gitignore +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/AGENTS.md +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/CHANGELOG.md +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/README.md +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/bash-exit-code-green-dot-bug.md +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/__init__.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/__main__.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/main.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.7.0a9/comate_cli/terminal_agent/plugins → comate_cli-0.7.0a10/comate_cli/terminal_agent/config}/__init__.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/figures.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.7.0a9/comate_cli/terminal_agent/plugins/components → comate_cli-0.7.0a10/comate_cli/terminal_agent/plugins}/__init__.py +0 -0
- {comate_cli-0.7.0a9/comate_cli/terminal_agent/plugins/tabs → comate_cli-0.7.0a10/comate_cli/terminal_agent/plugins/components}/__init__.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/status_bar.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tool_fold.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tool_result_store.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/transcript_viewer.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/docs/hooks.md +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/docs/superpowers/plans/2026-04-03-phrase-shuffle.md +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/docs/superpowers/specs/2026-04-01-conditional-diff-subtitle-design.md +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/docs/superpowers/specs/2026-04-03-phrase-shuffle-design.md +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/report.md +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/conftest.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_completion_status_panel.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_context_command.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_event_renderer_streaming.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_event_renderer_tool_fold.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_handle_error.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_history_printer.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_history_printer_log.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_history_printer_subtitle_position.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_history_sync_tool_fold.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_input_history.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_logo.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_main_args.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_markdown_render.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_plugin_slash_commands.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_preflight.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_question_view.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_resume_picker.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_skills_slash_command.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_slash_clear.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_status_bar.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_task_poll.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tool_fold.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tool_fold_panel.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_transcript_viewer_tool_fold.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tui_paste_newline_guard.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tui_queue_preview.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tui_queue_sdk_source.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tui_thinking_display.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
- {comate_cli-0.7.0a9 → comate_cli-0.7.0a10}/tests/test_update_check.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comate-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.0a10
|
|
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
|
|
@@ -186,14 +186,23 @@ async def add(a: int, b: int) -> int:
|
|
|
186
186
|
|
|
187
187
|
|
|
188
188
|
def _build_agent(*, project_root: Path | None = None) -> Agent:
|
|
189
|
+
from comate_agent_sdk.agent.compaction import CompactionConfig
|
|
190
|
+
from comate_cli.terminal_agent.config import store
|
|
191
|
+
|
|
189
192
|
resolved_project_root = project_root or _resolve_cli_project_root()
|
|
193
|
+
snapshot = store.load()
|
|
190
194
|
|
|
191
195
|
return Agent(
|
|
192
196
|
config=AgentConfig(
|
|
193
197
|
role="software_engineering",
|
|
194
198
|
cwd=resolved_project_root,
|
|
195
199
|
env_options=EnvOptions(system_env=True, git_env=True),
|
|
196
|
-
use_streaming_task=True,
|
|
200
|
+
use_streaming_task=True,
|
|
201
|
+
memory_background_enabled=snapshot.memory_background_enabled,
|
|
202
|
+
memory_dreaming_enabled=snapshot.memory_dreaming_enabled,
|
|
203
|
+
compaction=CompactionConfig(
|
|
204
|
+
threshold_ratio=snapshot.compaction_threshold_ratio,
|
|
205
|
+
),
|
|
197
206
|
)
|
|
198
207
|
)
|
|
199
208
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""ConfigSnapshot:/config UI、store、startup 三处共享的偏好快照。
|
|
2
|
+
|
|
3
|
+
Revision 2: 5 项偏好(display_thinking, model_level, compaction, memory_background, memory_dreaming)。
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, replace
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
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
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class ConfigSnapshot:
|
|
22
|
+
"""5 项 CLI 偏好的不可变快照。"""
|
|
23
|
+
|
|
24
|
+
display_thinking: bool = False
|
|
25
|
+
model_level: ModelLevelPreset = "MID"
|
|
26
|
+
compaction_threshold_ratio: float = 0.80
|
|
27
|
+
compaction_threshold_preset: ThresholdPreset = "balanced"
|
|
28
|
+
memory_background_enabled: bool = True
|
|
29
|
+
memory_dreaming_enabled: bool = True
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def default(cls) -> "ConfigSnapshot":
|
|
33
|
+
return cls()
|
|
34
|
+
|
|
35
|
+
def with_display_thinking(self, enabled: bool) -> "ConfigSnapshot":
|
|
36
|
+
return replace(self, display_thinking=enabled)
|
|
37
|
+
|
|
38
|
+
def with_model_level(self, level: ModelLevelPreset) -> "ConfigSnapshot":
|
|
39
|
+
return replace(self, model_level=level)
|
|
40
|
+
|
|
41
|
+
def with_threshold_preset(self, preset: ThresholdPreset) -> "ConfigSnapshot":
|
|
42
|
+
return replace(
|
|
43
|
+
self,
|
|
44
|
+
compaction_threshold_preset=preset,
|
|
45
|
+
compaction_threshold_ratio=COMPACT_PRESETS[preset],
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def with_memory_background(self, enabled: bool) -> "ConfigSnapshot":
|
|
49
|
+
return replace(self, memory_background_enabled=enabled)
|
|
50
|
+
|
|
51
|
+
def with_memory_dreaming(self, enabled: bool) -> "ConfigSnapshot":
|
|
52
|
+
return replace(self, memory_dreaming_enabled=enabled)
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""/config slash command 的嵌入式 TUI 面板。
|
|
2
|
+
|
|
3
|
+
仿 plugins/plugin_picker.py:PluginPickerUI 的模式:
|
|
4
|
+
enter() → container → focus_target() → handle_*。
|
|
5
|
+
|
|
6
|
+
Revision 2: 5 行 ConfigField。
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
from prompt_toolkit.layout.containers import HSplit, Window
|
|
13
|
+
from prompt_toolkit.layout.controls import FormattedTextControl
|
|
14
|
+
|
|
15
|
+
from comate_cli.terminal_agent.figures import (
|
|
16
|
+
EFFORT_HIGH,
|
|
17
|
+
EFFORT_LOW,
|
|
18
|
+
HEAVY_HORIZONTAL,
|
|
19
|
+
PLAY_ICON,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from .model import ConfigSnapshot
|
|
23
|
+
from .picker_state import (
|
|
24
|
+
ConfigField,
|
|
25
|
+
ConfigPickerState,
|
|
26
|
+
PickerExit,
|
|
27
|
+
PickerExitKind,
|
|
28
|
+
ViewMode,
|
|
29
|
+
)
|
|
30
|
+
from . import store
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
_FIELD_LABELS: dict[ConfigField, str] = {
|
|
35
|
+
ConfigField.DISPLAY_THINKING: "Thinking display",
|
|
36
|
+
ConfigField.MODEL_LEVEL: "Default model level",
|
|
37
|
+
ConfigField.AUTO_COMPACT_THRESHOLD: "Auto compact threshold",
|
|
38
|
+
ConfigField.MEMORY_BACKGROUND: "Memory background",
|
|
39
|
+
ConfigField.MEMORY_DREAMING: "Memory dreaming",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_FIELD_ORDER = (
|
|
43
|
+
ConfigField.DISPLAY_THINKING,
|
|
44
|
+
ConfigField.MODEL_LEVEL,
|
|
45
|
+
ConfigField.AUTO_COMPACT_THRESHOLD,
|
|
46
|
+
ConfigField.MEMORY_BACKGROUND,
|
|
47
|
+
ConfigField.MEMORY_DREAMING,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
_ENUM_FIELDS = {ConfigField.MODEL_LEVEL, ConfigField.AUTO_COMPACT_THRESHOLD}
|
|
51
|
+
_BOOL_FIELDS = {ConfigField.DISPLAY_THINKING, ConfigField.MEMORY_BACKGROUND, ConfigField.MEMORY_DREAMING}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ConfigPickerUI:
|
|
55
|
+
"""嵌入式 /config 面板。tui.py 持有单实例,ConditionalContainer 控制可见性。"""
|
|
56
|
+
|
|
57
|
+
def __init__(self) -> None:
|
|
58
|
+
self._state: ConfigPickerState | None = None
|
|
59
|
+
self._content_window: Window | None = None
|
|
60
|
+
self._root: HSplit | None = None
|
|
61
|
+
|
|
62
|
+
# ---- entry / lifecycle ---------------------------------------------
|
|
63
|
+
def enter(self, snapshot: ConfigSnapshot) -> None:
|
|
64
|
+
self._state = ConfigPickerState(origin=snapshot)
|
|
65
|
+
self._state.save_callback = store.save # type: ignore[assignment]
|
|
66
|
+
|
|
67
|
+
def take_result(self) -> tuple[bool, tuple[str, ...]]:
|
|
68
|
+
"""返回 (saved, error_messages)。供 tui.py 在 mode 切回 NORMAL 后落 history。"""
|
|
69
|
+
if self._state is None:
|
|
70
|
+
return (False, ())
|
|
71
|
+
return (self._state.save_called, self._state.last_save_error_messages)
|
|
72
|
+
|
|
73
|
+
# ---- container ------------------------------------------------------
|
|
74
|
+
@property
|
|
75
|
+
def container(self) -> HSplit:
|
|
76
|
+
if self._root is None:
|
|
77
|
+
self._root = self._build_layout()
|
|
78
|
+
return self._root
|
|
79
|
+
|
|
80
|
+
def focus_target(self) -> Window:
|
|
81
|
+
if self._content_window is None:
|
|
82
|
+
self._build_layout()
|
|
83
|
+
assert self._content_window is not None
|
|
84
|
+
return self._content_window
|
|
85
|
+
|
|
86
|
+
# ---- delegated handlers --------------------------------------------
|
|
87
|
+
def handle_up(self) -> None:
|
|
88
|
+
if self._state:
|
|
89
|
+
self._state.handle_up()
|
|
90
|
+
|
|
91
|
+
def handle_down(self) -> None:
|
|
92
|
+
if self._state:
|
|
93
|
+
self._state.handle_down()
|
|
94
|
+
|
|
95
|
+
def handle_tab(self) -> None:
|
|
96
|
+
if self._state:
|
|
97
|
+
self._state.handle_tab()
|
|
98
|
+
|
|
99
|
+
def handle_enter(self) -> PickerExit | None:
|
|
100
|
+
if self._state is None:
|
|
101
|
+
return None
|
|
102
|
+
return self._state.handle_enter() # type: ignore[return-value]
|
|
103
|
+
|
|
104
|
+
def handle_escape(self) -> PickerExit | None:
|
|
105
|
+
if self._state is None:
|
|
106
|
+
return PickerExit(PickerExitKind.CLEAN_EXIT)
|
|
107
|
+
return self._state.handle_escape()
|
|
108
|
+
|
|
109
|
+
def handle_ctrl_s(self) -> PickerExit | None:
|
|
110
|
+
if self._state is None:
|
|
111
|
+
return None
|
|
112
|
+
return self._state.handle_ctrl_s()
|
|
113
|
+
|
|
114
|
+
# ---- fragment builders ---------------------------------------------
|
|
115
|
+
def _header_fragments(self) -> list[tuple[str, str]]:
|
|
116
|
+
return [("class:selection.title", " Config")]
|
|
117
|
+
|
|
118
|
+
def _content_fragments(self) -> list[tuple[str, str]]:
|
|
119
|
+
if self._state is None:
|
|
120
|
+
return [("", "")]
|
|
121
|
+
|
|
122
|
+
if self._state.view_mode == ViewMode.MAIN:
|
|
123
|
+
return self._main_fragments()
|
|
124
|
+
return self._subview_fragments()
|
|
125
|
+
|
|
126
|
+
def _main_fragments(self) -> list[tuple[str, str]]:
|
|
127
|
+
state = self._state
|
|
128
|
+
rows: list[tuple[str, str]] = []
|
|
129
|
+
for f in _FIELD_ORDER:
|
|
130
|
+
focused = f == state.focused_field
|
|
131
|
+
cursor = PLAY_ICON if focused else " "
|
|
132
|
+
marker = EFFORT_HIGH if focused else EFFORT_LOW
|
|
133
|
+
prefix = f" {cursor} {marker} "
|
|
134
|
+
name = _FIELD_LABELS[f]
|
|
135
|
+
value = self._render_field_value(f, state.draft)
|
|
136
|
+
suffix = " \u21b5" if f in _ENUM_FIELDS else ""
|
|
137
|
+
row_style = (
|
|
138
|
+
"class:selection.option.selected"
|
|
139
|
+
if focused
|
|
140
|
+
else "class:selection.option"
|
|
141
|
+
)
|
|
142
|
+
value_style = (
|
|
143
|
+
"class:selection.description.selected"
|
|
144
|
+
if focused
|
|
145
|
+
else "class:selection.description"
|
|
146
|
+
)
|
|
147
|
+
rows.append((row_style, f"{prefix}{name:<26}"))
|
|
148
|
+
rows.append((value_style, f"{value}{suffix}\n"))
|
|
149
|
+
return rows
|
|
150
|
+
|
|
151
|
+
def _render_field_value(self, f: ConfigField, snap: ConfigSnapshot) -> str:
|
|
152
|
+
if f == ConfigField.DISPLAY_THINKING:
|
|
153
|
+
return f"[ {'on ' if snap.display_thinking else 'off'} ]"
|
|
154
|
+
if f == ConfigField.MODEL_LEVEL:
|
|
155
|
+
return f"[ {snap.model_level} ]"
|
|
156
|
+
if f == ConfigField.AUTO_COMPACT_THRESHOLD:
|
|
157
|
+
return f"[ {snap.compaction_threshold_preset} ({snap.compaction_threshold_ratio:.2f}) ]"
|
|
158
|
+
if f == ConfigField.MEMORY_BACKGROUND:
|
|
159
|
+
return f"[ {'on ' if snap.memory_background_enabled else 'off'} ]"
|
|
160
|
+
if f == ConfigField.MEMORY_DREAMING:
|
|
161
|
+
return f"[ {'on ' if snap.memory_dreaming_enabled else 'off'} ]"
|
|
162
|
+
return ""
|
|
163
|
+
|
|
164
|
+
def _subview_fragments(self) -> list[tuple[str, str]]:
|
|
165
|
+
state = self._state
|
|
166
|
+
if state is None or state.view_mode != ViewMode.SUBVIEW or state.subview_field is None:
|
|
167
|
+
return []
|
|
168
|
+
title = _FIELD_LABELS[state.subview_field]
|
|
169
|
+
rows: list[tuple[str, str]] = [
|
|
170
|
+
("class:selection.title", f" {title}\n"),
|
|
171
|
+
("", "\n"),
|
|
172
|
+
]
|
|
173
|
+
for idx, opt in enumerate(state.subview_options()):
|
|
174
|
+
focused = idx == state.subview_cursor
|
|
175
|
+
cursor = PLAY_ICON if focused else " "
|
|
176
|
+
marker = EFFORT_HIGH if focused else EFFORT_LOW
|
|
177
|
+
prefix = f" {cursor} {marker} "
|
|
178
|
+
style = (
|
|
179
|
+
"class:selection.option.selected"
|
|
180
|
+
if focused
|
|
181
|
+
else "class:selection.option"
|
|
182
|
+
)
|
|
183
|
+
rows.append((style, f"{prefix}{opt.label}\n"))
|
|
184
|
+
desc_style = (
|
|
185
|
+
"class:selection.description.selected"
|
|
186
|
+
if focused
|
|
187
|
+
else "class:selection.description"
|
|
188
|
+
)
|
|
189
|
+
rows.append((desc_style, f" {opt.description}\n"))
|
|
190
|
+
return rows
|
|
191
|
+
|
|
192
|
+
def _footer_fragments(self) -> list[tuple[str, str]]:
|
|
193
|
+
if self._state is None:
|
|
194
|
+
return []
|
|
195
|
+
if self._state.view_mode == ViewMode.MAIN:
|
|
196
|
+
if self._state.dirty_warning_active:
|
|
197
|
+
return [("class:warning", " Press Esc again to discard, Ctrl+S to save")]
|
|
198
|
+
if self._state.last_save_error_messages:
|
|
199
|
+
return [("class:error", " Save failed: " + " | ".join(self._state.last_save_error_messages))]
|
|
200
|
+
return [
|
|
201
|
+
(
|
|
202
|
+
"class:selection.hint",
|
|
203
|
+
" Tab cycle \u00b7 Enter open \u00b7 Ctrl+S save \u00b7 Esc cancel",
|
|
204
|
+
)
|
|
205
|
+
]
|
|
206
|
+
return [("class:selection.hint", " Enter confirm \u00b7 Esc back")]
|
|
207
|
+
|
|
208
|
+
def _build_layout(self) -> HSplit:
|
|
209
|
+
header = Window(
|
|
210
|
+
content=FormattedTextControl(self._header_fragments),
|
|
211
|
+
height=1,
|
|
212
|
+
dont_extend_height=True,
|
|
213
|
+
style="class:selection.title",
|
|
214
|
+
)
|
|
215
|
+
divider = Window(
|
|
216
|
+
height=1,
|
|
217
|
+
char=HEAVY_HORIZONTAL,
|
|
218
|
+
style="class:selection.divider",
|
|
219
|
+
)
|
|
220
|
+
self._content_window = Window(
|
|
221
|
+
content=FormattedTextControl(self._content_fragments, focusable=True),
|
|
222
|
+
wrap_lines=True,
|
|
223
|
+
always_hide_cursor=True,
|
|
224
|
+
style="class:selection.body",
|
|
225
|
+
)
|
|
226
|
+
footer = Window(
|
|
227
|
+
content=FormattedTextControl(self._footer_fragments),
|
|
228
|
+
height=1,
|
|
229
|
+
dont_extend_height=True,
|
|
230
|
+
style="class:selection.body",
|
|
231
|
+
)
|
|
232
|
+
return HSplit([header, divider, self._content_window, footer])
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""ConfigPicker 的纯逻辑状态机——不依赖 prompt_toolkit。
|
|
2
|
+
|
|
3
|
+
Revision 2: 5 行 ConfigField。
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Callable
|
|
10
|
+
|
|
11
|
+
from .model import (
|
|
12
|
+
COMPACT_PRESETS,
|
|
13
|
+
ConfigSnapshot,
|
|
14
|
+
ModelLevelPreset,
|
|
15
|
+
ThresholdPreset,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ConfigField(Enum):
|
|
20
|
+
DISPLAY_THINKING = "display_thinking"
|
|
21
|
+
MODEL_LEVEL = "model_level"
|
|
22
|
+
AUTO_COMPACT_THRESHOLD = "auto_compact_threshold"
|
|
23
|
+
MEMORY_BACKGROUND = "memory_background"
|
|
24
|
+
MEMORY_DREAMING = "memory_dreaming"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ViewMode(Enum):
|
|
28
|
+
MAIN = "main"
|
|
29
|
+
SUBVIEW = "subview"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PickerExitKind(Enum):
|
|
33
|
+
SAVED = "saved"
|
|
34
|
+
CLEAN_EXIT = "clean_exit" # 无 dirty,直接退出
|
|
35
|
+
DISCARDED = "discarded" # dirty 但用户两次 esc 丢弃
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True, slots=True)
|
|
39
|
+
class SubviewOption:
|
|
40
|
+
value: str
|
|
41
|
+
label: str
|
|
42
|
+
description: str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True, slots=True)
|
|
46
|
+
class PickerExit:
|
|
47
|
+
kind: PickerExitKind
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_FIELD_ORDER = (
|
|
51
|
+
ConfigField.DISPLAY_THINKING,
|
|
52
|
+
ConfigField.MODEL_LEVEL,
|
|
53
|
+
ConfigField.AUTO_COMPACT_THRESHOLD,
|
|
54
|
+
ConfigField.MEMORY_BACKGROUND,
|
|
55
|
+
ConfigField.MEMORY_DREAMING,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
_THRESHOLD_VALUES: tuple[ThresholdPreset, ...] = ("conservative", "balanced", "aggressive")
|
|
59
|
+
_THRESHOLD_DESCRIPTIONS: dict[str, str] = {
|
|
60
|
+
"conservative": "0.65 — compact more often; smaller per-call ctx.",
|
|
61
|
+
"balanced": "0.80 — default. Compact when ctx >= 80% utilized.",
|
|
62
|
+
"aggressive": "0.92 — compact late; larger ctx per call.",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
_MODEL_LEVEL_VALUES: tuple[ModelLevelPreset, ...] = ("LOW", "MID", "HIGH")
|
|
66
|
+
_MODEL_LEVEL_DESCRIPTIONS: dict[str, str] = {
|
|
67
|
+
"LOW": "Fastest, cheapest. Good for simple tasks.",
|
|
68
|
+
"MID": "Default. Balanced speed and capability.",
|
|
69
|
+
"HIGH": "Most capable. For complex reasoning tasks.",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class ConfigPickerState:
|
|
75
|
+
"""ConfigPickerUI 的全部业务状态——可独立单元测试。"""
|
|
76
|
+
|
|
77
|
+
origin: ConfigSnapshot
|
|
78
|
+
draft: ConfigSnapshot = field(init=False)
|
|
79
|
+
focused_field: ConfigField = field(init=False, default=ConfigField.DISPLAY_THINKING)
|
|
80
|
+
view_mode: ViewMode = field(init=False, default=ViewMode.MAIN)
|
|
81
|
+
subview_field: ConfigField | None = field(init=False, default=None)
|
|
82
|
+
subview_cursor: int = field(init=False, default=0)
|
|
83
|
+
dirty_warning_active: bool = field(init=False, default=False)
|
|
84
|
+
save_called: bool = field(init=False, default=False)
|
|
85
|
+
last_save_error_messages: tuple[str, ...] = field(init=False, default=())
|
|
86
|
+
|
|
87
|
+
save_callback: Callable[[ConfigSnapshot], object] | None = None
|
|
88
|
+
|
|
89
|
+
def __post_init__(self) -> None:
|
|
90
|
+
self.draft = self.origin
|
|
91
|
+
|
|
92
|
+
# ---- queries --------------------------------------------------------
|
|
93
|
+
def is_dirty(self) -> bool:
|
|
94
|
+
return self.draft != self.origin
|
|
95
|
+
|
|
96
|
+
def subview_options(self) -> list[SubviewOption]:
|
|
97
|
+
if self.view_mode != ViewMode.SUBVIEW or self.subview_field is None:
|
|
98
|
+
return []
|
|
99
|
+
if self.subview_field == ConfigField.AUTO_COMPACT_THRESHOLD:
|
|
100
|
+
return [
|
|
101
|
+
SubviewOption(v, v.capitalize(), _THRESHOLD_DESCRIPTIONS[v])
|
|
102
|
+
for v in _THRESHOLD_VALUES
|
|
103
|
+
]
|
|
104
|
+
if self.subview_field == ConfigField.MODEL_LEVEL:
|
|
105
|
+
return [
|
|
106
|
+
SubviewOption(v, v, _MODEL_LEVEL_DESCRIPTIONS[v])
|
|
107
|
+
for v in _MODEL_LEVEL_VALUES
|
|
108
|
+
]
|
|
109
|
+
return []
|
|
110
|
+
|
|
111
|
+
# ---- key handlers ---------------------------------------------------
|
|
112
|
+
def handle_up(self) -> None:
|
|
113
|
+
if self.view_mode == ViewMode.MAIN:
|
|
114
|
+
idx = _FIELD_ORDER.index(self.focused_field)
|
|
115
|
+
self.focused_field = _FIELD_ORDER[(idx - 1) % len(_FIELD_ORDER)]
|
|
116
|
+
else:
|
|
117
|
+
self.subview_cursor = (self.subview_cursor - 1) % len(self.subview_options())
|
|
118
|
+
self.dirty_warning_active = False
|
|
119
|
+
|
|
120
|
+
def handle_down(self) -> None:
|
|
121
|
+
if self.view_mode == ViewMode.MAIN:
|
|
122
|
+
idx = _FIELD_ORDER.index(self.focused_field)
|
|
123
|
+
self.focused_field = _FIELD_ORDER[(idx + 1) % len(_FIELD_ORDER)]
|
|
124
|
+
else:
|
|
125
|
+
self.subview_cursor = (self.subview_cursor + 1) % len(self.subview_options())
|
|
126
|
+
self.dirty_warning_active = False
|
|
127
|
+
|
|
128
|
+
def handle_tab(self) -> None:
|
|
129
|
+
if self.view_mode != ViewMode.MAIN:
|
|
130
|
+
return
|
|
131
|
+
self.dirty_warning_active = False
|
|
132
|
+
f = self.focused_field
|
|
133
|
+
if f == ConfigField.DISPLAY_THINKING:
|
|
134
|
+
self.draft = self.draft.with_display_thinking(not self.draft.display_thinking)
|
|
135
|
+
elif f == ConfigField.MEMORY_BACKGROUND:
|
|
136
|
+
self.draft = self.draft.with_memory_background(not self.draft.memory_background_enabled)
|
|
137
|
+
elif f == ConfigField.MEMORY_DREAMING:
|
|
138
|
+
self.draft = self.draft.with_memory_dreaming(not self.draft.memory_dreaming_enabled)
|
|
139
|
+
elif f == ConfigField.AUTO_COMPACT_THRESHOLD:
|
|
140
|
+
cur = _THRESHOLD_VALUES.index(self.draft.compaction_threshold_preset)
|
|
141
|
+
next_v = _THRESHOLD_VALUES[(cur + 1) % len(_THRESHOLD_VALUES)]
|
|
142
|
+
self.draft = self.draft.with_threshold_preset(next_v)
|
|
143
|
+
elif f == ConfigField.MODEL_LEVEL:
|
|
144
|
+
cur = _MODEL_LEVEL_VALUES.index(self.draft.model_level)
|
|
145
|
+
next_v = _MODEL_LEVEL_VALUES[(cur + 1) % len(_MODEL_LEVEL_VALUES)]
|
|
146
|
+
self.draft = self.draft.with_model_level(next_v)
|
|
147
|
+
|
|
148
|
+
def handle_enter(self) -> PickerExit | None:
|
|
149
|
+
if self.view_mode == ViewMode.MAIN:
|
|
150
|
+
f = self.focused_field
|
|
151
|
+
if f in (ConfigField.AUTO_COMPACT_THRESHOLD, ConfigField.MODEL_LEVEL):
|
|
152
|
+
self.view_mode = ViewMode.SUBVIEW
|
|
153
|
+
self.subview_field = f
|
|
154
|
+
self.subview_cursor = self._initial_subview_cursor(f)
|
|
155
|
+
else:
|
|
156
|
+
# bool 行:Enter 等价 Tab
|
|
157
|
+
self.handle_tab()
|
|
158
|
+
else:
|
|
159
|
+
# SUBVIEW: commit selection to draft, return to MAIN.
|
|
160
|
+
opts = self.subview_options()
|
|
161
|
+
if not opts:
|
|
162
|
+
self._return_to_main()
|
|
163
|
+
return None
|
|
164
|
+
chosen = opts[self.subview_cursor].value
|
|
165
|
+
if self.subview_field == ConfigField.AUTO_COMPACT_THRESHOLD:
|
|
166
|
+
self.draft = self.draft.with_threshold_preset(chosen) # type: ignore[arg-type]
|
|
167
|
+
elif self.subview_field == ConfigField.MODEL_LEVEL:
|
|
168
|
+
self.draft = self.draft.with_model_level(chosen) # type: ignore[arg-type]
|
|
169
|
+
self._return_to_main()
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
def handle_escape(self) -> PickerExit | None:
|
|
173
|
+
if self.view_mode == ViewMode.SUBVIEW:
|
|
174
|
+
self._return_to_main()
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
# MAIN
|
|
178
|
+
if not self.is_dirty():
|
|
179
|
+
return PickerExit(PickerExitKind.CLEAN_EXIT)
|
|
180
|
+
if not self.dirty_warning_active:
|
|
181
|
+
self.dirty_warning_active = True
|
|
182
|
+
return None
|
|
183
|
+
return PickerExit(PickerExitKind.DISCARDED)
|
|
184
|
+
|
|
185
|
+
def handle_ctrl_s(self) -> PickerExit | None:
|
|
186
|
+
if self.view_mode == ViewMode.SUBVIEW:
|
|
187
|
+
return None
|
|
188
|
+
if self.save_callback is None:
|
|
189
|
+
raise RuntimeError("save_callback 未注入;ConfigPickerUI 接入时必须设置")
|
|
190
|
+
result = self.save_callback(self.draft)
|
|
191
|
+
self.dirty_warning_active = False
|
|
192
|
+
if getattr(result, "ok", False):
|
|
193
|
+
self.save_called = True
|
|
194
|
+
self.last_save_error_messages = ()
|
|
195
|
+
return PickerExit(PickerExitKind.SAVED)
|
|
196
|
+
self.last_save_error_messages = tuple(getattr(result, "error_messages", ()) or ())
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
# ---- helpers --------------------------------------------------------
|
|
200
|
+
def _initial_subview_cursor(self, f: ConfigField) -> int:
|
|
201
|
+
if f == ConfigField.AUTO_COMPACT_THRESHOLD:
|
|
202
|
+
return _THRESHOLD_VALUES.index(self.draft.compaction_threshold_preset)
|
|
203
|
+
if f == ConfigField.MODEL_LEVEL:
|
|
204
|
+
return _MODEL_LEVEL_VALUES.index(self.draft.model_level)
|
|
205
|
+
return 0
|
|
206
|
+
|
|
207
|
+
def _return_to_main(self) -> None:
|
|
208
|
+
self.view_mode = ViewMode.MAIN
|
|
209
|
+
self.subview_field = None
|
|
210
|
+
self.subview_cursor = 0
|