comate-cli 0.7.0a6__tar.gz → 0.7.0a8__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.0a8/AGENTS.md +115 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/CHANGELOG.md +4 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/PKG-INFO +1 -1
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/event_renderer.py +85 -10
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/history_printer.py +86 -17
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/logging_adapter.py +26 -0
- comate_cli-0.7.0a8/comate_cli/terminal_agent/markdown_render.py +138 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/models.py +2 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/slash_commands.py +5 -0
- comate_cli-0.7.0a8/comate_cli/terminal_agent/tool_fold.py +206 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tool_result_formatters.py +65 -33
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/transcript_viewer.py +60 -27
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui.py +22 -13
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/commands.py +76 -40
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/history_sync.py +45 -3
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/input_behavior.py +45 -8
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/key_bindings.py +3 -3
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/render_panels.py +48 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/pyproject.toml +1 -1
- comate_cli-0.7.0a8/report.md +382 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_event_renderer.py +102 -28
- comate_cli-0.7.0a8/tests/test_event_renderer_tool_fold.py +146 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_history_printer.py +57 -1
- comate_cli-0.7.0a8/tests/test_history_printer_subtitle_position.py +78 -0
- comate_cli-0.7.0a8/tests/test_history_printer_tool_fold.py +78 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_history_sync.py +29 -5
- comate_cli-0.7.0a8/tests/test_history_sync_tool_fold.py +98 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_logging_adapter.py +43 -0
- comate_cli-0.7.0a8/tests/test_markdown_render.py +102 -0
- comate_cli-0.7.0a8/tests/test_plugin_slash_commands.py +107 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_skills_slash_command.py +3 -27
- comate_cli-0.7.0a8/tests/test_slash_clear.py +350 -0
- comate_cli-0.7.0a8/tests/test_tool_fold.py +89 -0
- comate_cli-0.7.0a8/tests/test_tool_fold_panel.py +159 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tool_result_formatters.py +163 -12
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_transcript_viewer.py +64 -6
- comate_cli-0.7.0a8/tests/test_transcript_viewer_tool_fold.py +47 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_paste_newline_guard.py +62 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_paste_placeholder.py +46 -1
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_queue_sdk_source.py +35 -11
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/uv.lock +2 -2
- comate_cli-0.7.0a6/comate_cli/terminal_agent/markdown_render.py +0 -26
- comate_cli-0.7.0a6/tests/test_markdown_render.py +0 -53
- comate_cli-0.7.0a6/tests/test_plugin_slash_commands.py +0 -17
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/.gitignore +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/README.md +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/bash-exit-code-green-dot-bug.md +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/__init__.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/__main__.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/main.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/app.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/figures.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/status_bar.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tool_result_store.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/docs/hooks.md +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/docs/superpowers/plans/2026-04-03-phrase-shuffle.md +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/docs/superpowers/specs/2026-04-01-conditional-diff-subtitle-design.md +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/docs/superpowers/specs/2026-04-03-phrase-shuffle-design.md +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/conftest.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_completion_status_panel.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_context_command.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_event_renderer_streaming.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_format_error.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_handle_error.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_history_printer_log.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_input_history.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_logo.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_main_args.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_preflight.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_question_view.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_resume_picker.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_status_bar.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_task_poll.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tool_view.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_queue_preview.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
- {comate_cli-0.7.0a6 → comate_cli-0.7.0a8}/tests/test_update_check.py +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
## global rule
|
|
2
|
+
1. 涉及到 Prompt_toolkit 这个TUI框架, 你必须使用 context7 mcp里面的 query-docs和resolve-library-id 功能来查询相关文档, 以确保你对这个框架的理解是正确的. this is MUST
|
|
3
|
+
2. 总是使用中文编写详细的git commit message
|
|
4
|
+
|
|
5
|
+
## 12条黄金规则
|
|
6
|
+
|
|
7
|
+
这些规则适用于本项目中的每一个任务,除非被明确覆盖。核心原则:非简单任务宁可谨慎,不要图快。简单任务可以灵活处理。
|
|
8
|
+
|
|
9
|
+
1. 动手之前先动脑
|
|
10
|
+
把你的假设说出来。不确定就问,别猜。遇到模糊需求时,列出多种可能的理解。如果有更简单的方案,要敢于提出来。搞不清楚的时候就停下来,把不清楚的点明确说出来。
|
|
11
|
+
|
|
12
|
+
2. 简单至上
|
|
13
|
+
写能解决问题的最少代码,不写投机性代码。不加需求以外的功能。一次性代码不搞抽象封装。自测标准:一个资深工程师看了会不会觉得过度设计?如果会,就简化。
|
|
14
|
+
|
|
15
|
+
3. 精准修改,不多动
|
|
16
|
+
只改必须改的地方。只清理自己弄乱的部分。不要顺手"优化"旁边的代码、注释或格式。没坏的东西不要重构。风格跟已有代码保持一致。
|
|
17
|
+
|
|
18
|
+
4. 以目标驱动执行
|
|
19
|
+
先定义成功标准,然后循环迭代直到验证通过。不要机械地按步骤走,而是盯着目标不断调整。有了清晰的成功标准,你才能自主地持续迭代。
|
|
20
|
+
|
|
21
|
+
5. 模型只用于需要判断力的场景
|
|
22
|
+
适合用 AI 做的事:分类、起草、总结、信息提取。不该用 AI 做的事:路由分发、重试逻辑、确定性转换。如果写代码就能搞定,就用代码搞定。
|
|
23
|
+
|
|
24
|
+
6. 核实优先于推断(冰山法则)
|
|
25
|
+
你在上下文中看到的信息只是冰山一角,不能代替真实验证。
|
|
26
|
+
判断标准不是"我有没有说出猜测的话",而是"我这个结论背后有没有本轮刚验证过的事实支撑"——只要要陈述文件/函数/配置内容或修改代码,却没有刚读过、刚确认过,就是在猜,必须先验证再继续。
|
|
27
|
+
|
|
28
|
+
7. 遇到冲突要选边,不要和稀泥
|
|
29
|
+
两种模式互相矛盾时,选一个(优先选更新的或经过更多验证的),说明理由,把另一个标记为待清理。不要把两种冲突的模式混在一起用。
|
|
30
|
+
|
|
31
|
+
8. 先读再写
|
|
32
|
+
写新代码之前,先读清楚现有的导出接口、直接调用方、公共工具函数。"看起来互不相关"这种判断很危险。如果不理解代码为什么这样写,先问。
|
|
33
|
+
|
|
34
|
+
9. 测试要验证意图,不只是行为
|
|
35
|
+
测试必须体现某个行为**为什么**重要,而不仅仅是**做了什么**。如果业务逻辑变了,测试却不会挂,那这个测试就是有问题的。
|
|
36
|
+
|
|
37
|
+
10. 每完成一个重要步骤就做检查点
|
|
38
|
+
总结一下:做了什么、验证了什么、还剩什么。不要在一个自己都说不清楚的状态上继续推进。如果失去了方向感,停下来,重新陈述当前状态。
|
|
39
|
+
|
|
40
|
+
11. 跟着项目的既有约定走,哪怕你不认同
|
|
41
|
+
在项目内部,一致性 > 个人偏好。如果你真觉得某个约定有害,把问题提出来讨论,但不要悄悄另搞一套。
|
|
42
|
+
|
|
43
|
+
12. 有问题就大声说
|
|
44
|
+
如果有东西被悄悄跳过了,不能说"已完成"。如果有测试被跳过了,不能说"测试全过"。默认立场是把不确定性暴露出来,而不是藏起来。
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## python coding rule
|
|
48
|
+
1. 代码必须使用 logging 模块进行日志记录,禁止使用 print 语句
|
|
49
|
+
2. 运行 python必须使用 uv run python 脚本名.py 的方式运行
|
|
50
|
+
3. 安装pip包必须使用 uv add 包名 的方式安装
|
|
51
|
+
|
|
52
|
+
## Context 重建铁律
|
|
53
|
+
comate_agent_sdk 的上下文重建规则是高优先级架构约束。涉及 static header / session_state / conversation 三层分离、header_snapshot 的 first-snapshot-wins 语义、resume / fork / compact / clear_history 各自的不变量,以及最低测试护栏清单。
|
|
54
|
+
任何改动涉及 chat_session.py、context/ir.py、context/compaction.py、agent/history.py、agent/init.py、agent/setup.py 时 MUST 先阅读完整规则。
|
|
55
|
+
→ ./rules/context-rebuild.md
|
|
56
|
+
|
|
57
|
+
## Token 账本语义铁律
|
|
58
|
+
|
|
59
|
+
comate_agent_sdk 的 token 相关概念分属不同账本,后续改动 MUST 先阅读完整规则,禁止为了修某个阈值或展示问题把不同账本混算。
|
|
60
|
+
→ ./rules/token-accounting.md
|
|
61
|
+
|
|
62
|
+
## TUI 编程铁律
|
|
63
|
+
不做 History TUI,历史只写 scrollback。layout 只允许底部交互带。颜色策略以 shell 背景为准。包含 Team 事件显示铁律和 Pre-flight TUI 铁律。
|
|
64
|
+
涉及 prompt_toolkit layout、team 事件渲染、独立 Application 弹窗时 MUST 先阅读完整规则。
|
|
65
|
+
→ ./rules/tui.md
|
|
66
|
+
|
|
67
|
+
## 代码放置规则
|
|
68
|
+
|
|
69
|
+
当需要为某个模块添加新功能时,先判断:这个功能是该模块的 **核心职责** 还是 **业务扩展**?
|
|
70
|
+
|
|
71
|
+
- **核心职责**:直接操作该模块自身管理的数据结构 → 可以加方法
|
|
72
|
+
- **业务扩展**:借用该模块的 API 完成上层业务目标 → 在调用方实现,禁止侵入
|
|
73
|
+
|
|
74
|
+
判断标准:如果删掉这个方法,该模块的核心抽象(数据结构 + 主流程)是否仍然完整?
|
|
75
|
+
如果仍然完整,说明这个方法不属于这里。
|
|
76
|
+
|
|
77
|
+
实践要求:
|
|
78
|
+
1. 新增方法前,说明它属于核心职责还是业务扩展
|
|
79
|
+
2. 如果是业务扩展,必须放在调用方或独立 service 中,通过公共 API 操作
|
|
80
|
+
3. 单个模块超过 500 行时,主动审视是否有职责泄漏
|
|
81
|
+
|
|
82
|
+
## Prompt 工程规则
|
|
83
|
+
|
|
84
|
+
涉及 SDK 内置 prompt / 提示词时,后续 agent MUST 默认遵循以下约定,禁止重新回到“局部硬编码 + 局部替换”的分散实现。
|
|
85
|
+
|
|
86
|
+
- SDK 自带 prompt 资源 MUST 放在 `comate_agent_sdk/prompts/` 下,按领域分目录,例如 `agent/`、`subagent/`。
|
|
87
|
+
- 新增 prompt 时,优先判断它是 **SDK 静态资源** 还是 **调用方临时拼接文本**:
|
|
88
|
+
- SDK 静态资源:放入 `comate_agent_sdk/prompts/`,通过统一 loader 读取。
|
|
89
|
+
- 调用方临时拼接文本:保留在调用方,禁止为了“统一”硬塞进不相关模块。
|
|
90
|
+
- MUST 使用两阶段接口:
|
|
91
|
+
- `load_prompt()` 只负责读取包内 markdown 资源。
|
|
92
|
+
- `render_prompt()` 只负责变量渲染。
|
|
93
|
+
- 新 prompt 的变量语法 MUST 统一使用 `{{VAR_NAME}}`,不要新增 `{var}`、`%s`、手写 `replace()` 等新风格。
|
|
94
|
+
|
|
95
|
+
## System Tool 描述工程规则
|
|
96
|
+
|
|
97
|
+
涉及 SDK 内置 system tool 的 schema description / system prompt usage_rules / 默认上限值时,
|
|
98
|
+
后续 agent MUST 默认遵循以下约定。
|
|
99
|
+
|
|
100
|
+
→ ./rules/system-tool-prompt-conventions.md
|
|
101
|
+
|
|
102
|
+
## Tool Envelope 与 Typed Schema 铁律
|
|
103
|
+
|
|
104
|
+
comate_agent_sdk 内置 system tool 的"信封 (`ok/err` 6-key envelope) + typed OutputSchema + `ToolMessage.raw_envelope` 三轨分离"是 typed `ToolResultEvent.output`、SDK 投影、resume 持久化、reminder 引擎共同依赖的事实契约。envelope 一旦被静默改形 / 截断 / 污染框架字段,会让 typed 事件、投影、reminder、jsonl resume **同时静默失效**。
|
|
105
|
+
|
|
106
|
+
涉及以下任一项 MUST 先阅读完整规则,禁止仅"局部 patch":
|
|
107
|
+
|
|
108
|
+
- `comate_agent_sdk/system_tools/tool_result.py`、`tool_schemas/`(含 `_registry.py` / `_projection.py` / `_base.py` / 各 `<tool>.py`)、`system_tools/tools|task|team/` 下 system tool 的返回结构。
|
|
109
|
+
- `agent/tool_exec.py` 中 `_build_tool_result_message()` / `_extract_tool_envelope()`、`llm/messages.py` 中 `ToolMessage` 的 `content` / `raw_envelope` / `execution_meta` 字段、`mcp/manager.py` 中 `McpToolResult`。
|
|
110
|
+
- `agent/runner_engine/execution/tool_execution.py` 的 `_resolve_tool_output()`、`runner_engine/projection/tool_result_projection.py`、`agent/events.py` 中 `ToolResultEvent.output`、`context/reminder_engine.py`、`subagent/team_session.py`、`chat_session.py` resume 路径。
|
|
111
|
+
- size guard / truncation / artifact spill 与 envelope 关系的任何 plan。
|
|
112
|
+
|
|
113
|
+
核心铁律一句话:**system tool 必须 `ok()/err()` envelope;envelope 形状不可破坏性变更;envelope 是 typed schema 唯一原料;envelope 只装业务字段,框架元数据走 `execution_meta`;envelope 严禁 slim / 入 prompt / 被 monkey-patch 注册;MCP 用 `McpToolResult.raw_result`、不混 `raw_envelope`;用户 `@tool` 永远是 `CustomToolOutput`。**
|
|
114
|
+
|
|
115
|
+
→ ./rules/tool-envelope.md
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
|
|
14
14
|
### Added
|
|
15
15
|
|
|
16
|
+
- 新增 `/clear` slash command:清空当前 leader session 的 conversation,
|
|
17
|
+
保留 session_id、team membership、inbox 与 TaskStore。成功后会清屏、
|
|
18
|
+
重打 logo、重置 TUI transcript view,并刷新 status bar;busy 或
|
|
19
|
+
compacting 状态会拒绝执行。
|
|
16
20
|
- `_ShutdownNoiseGuard` 扩展为四合一接管器:SIGINT、`sys.unraisablehook`、
|
|
17
21
|
`sys.excepthook`、`warnings.showwarning`。捕获到的噪音统一路由到独立的
|
|
18
22
|
`comate.noise` logger(`propagate=False`),写入 `~/.comate/logs/agent.log`。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comate-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.0a8
|
|
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
|
|
@@ -59,6 +59,11 @@ from comate_cli.terminal_agent.tool_result_store import (
|
|
|
59
59
|
ToolResultRegistry,
|
|
60
60
|
)
|
|
61
61
|
from comate_cli.terminal_agent.tool_view import summarize_tool_args, resolve_display_tool_name, should_show_tool_in_scrollback
|
|
62
|
+
from comate_cli.terminal_agent.tool_fold import (
|
|
63
|
+
ActiveToolFold,
|
|
64
|
+
ToolFoldSnapshot,
|
|
65
|
+
is_foldable_tool,
|
|
66
|
+
)
|
|
62
67
|
from comate_cli.terminal_agent.env_utils import read_env_int
|
|
63
68
|
from comate_cli.terminal_agent.custom_slash_commands import FILE_REF_PATTERN
|
|
64
69
|
logger = logging.getLogger(__name__)
|
|
@@ -258,6 +263,7 @@ class EventRenderer:
|
|
|
258
263
|
self._history: list[HistoryEntry] = []
|
|
259
264
|
self._running_tools: dict[str, _RunningTool] = {}
|
|
260
265
|
self._tool_call_args: dict[str, dict[str, Any]] = {}
|
|
266
|
+
self._active_tool_fold = ActiveToolFold()
|
|
261
267
|
self._tool_results = tool_results
|
|
262
268
|
self._fallback_tool_result_sequence = 0
|
|
263
269
|
self._assistant_buffer = ""
|
|
@@ -358,6 +364,21 @@ class EventRenderer:
|
|
|
358
364
|
if entry.entry_type == "tool_result":
|
|
359
365
|
self.flush_pending_logs()
|
|
360
366
|
|
|
367
|
+
def _commit_active_tool_fold(self) -> None:
|
|
368
|
+
snapshot = self._active_tool_fold.snapshot(active=False)
|
|
369
|
+
if snapshot is None:
|
|
370
|
+
return
|
|
371
|
+
self._append_history_entry(
|
|
372
|
+
HistoryEntry(
|
|
373
|
+
entry_type="tool_fold",
|
|
374
|
+
text=snapshot.summary,
|
|
375
|
+
severity="error" if snapshot.any_error else "info",
|
|
376
|
+
subtitle=snapshot.error_summary or snapshot.latest_hint,
|
|
377
|
+
tool_call_ids=snapshot.tool_call_ids,
|
|
378
|
+
)
|
|
379
|
+
)
|
|
380
|
+
self._active_tool_fold.clear()
|
|
381
|
+
|
|
361
382
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
362
383
|
# spec §6 流式新管道方法集(Phase 1-6 新增;Phase 7 接入 handle_event)
|
|
363
384
|
# 容器检测原语(spec §6.3)
|
|
@@ -649,6 +670,7 @@ class EventRenderer:
|
|
|
649
670
|
if not normalized:
|
|
650
671
|
return
|
|
651
672
|
self._flush_assistant_segment()
|
|
673
|
+
self._commit_active_tool_fold()
|
|
652
674
|
if display_header and display_subtitle:
|
|
653
675
|
# AskUserQuestion 答复入 scrollback:用 tool_result 样式渲染头行(●),
|
|
654
676
|
# 答案明细作为 ⎿ subtitle 紧贴其下;行间不插入用户 prefix。
|
|
@@ -698,6 +720,7 @@ class EventRenderer:
|
|
|
698
720
|
# spec §6.4:turn 结束 force flush 所有 pending(held / container /
|
|
699
721
|
# text_pending / thinking)保证 scrollback 看到完整内容
|
|
700
722
|
self._force_flush_all()
|
|
723
|
+
self._commit_active_tool_fold()
|
|
701
724
|
self._flush_assistant_segment()
|
|
702
725
|
self.flush_pending_logs()
|
|
703
726
|
self._pending_tool_starts.clear()
|
|
@@ -720,6 +743,7 @@ class EventRenderer:
|
|
|
720
743
|
)
|
|
721
744
|
self._running_tools.clear()
|
|
722
745
|
self._flush_assistant_segment()
|
|
746
|
+
self._commit_active_tool_fold()
|
|
723
747
|
self._pending_tool_starts.clear()
|
|
724
748
|
# spec §6.4:用户中断 → 5 个新字段全部丢弃不入队
|
|
725
749
|
self._drop_all_pending()
|
|
@@ -728,12 +752,16 @@ class EventRenderer:
|
|
|
728
752
|
def history_entries(self) -> list[HistoryEntry]:
|
|
729
753
|
return list(self._history)
|
|
730
754
|
|
|
755
|
+
def active_tool_fold_snapshot(self) -> ToolFoldSnapshot | None:
|
|
756
|
+
return self._active_tool_fold.snapshot(active=True)
|
|
757
|
+
|
|
731
758
|
def reset_history_view(self) -> None:
|
|
732
759
|
"""重置 history 视图状态(用于会话切换后的重新加载)。"""
|
|
733
760
|
self._history = []
|
|
734
761
|
self._recent_team_event_keys.clear()
|
|
735
762
|
self._running_tools.clear()
|
|
736
763
|
self._tool_call_args.clear()
|
|
764
|
+
self._active_tool_fold.clear()
|
|
737
765
|
self._assistant_buffer = ""
|
|
738
766
|
self._pending_tool_starts.clear()
|
|
739
767
|
self._turn_received_text_delta = False
|
|
@@ -1343,6 +1371,7 @@ class EventRenderer:
|
|
|
1343
1371
|
pass
|
|
1344
1372
|
case TextDeltaEvent(delta=delta, message_id=message_id):
|
|
1345
1373
|
if delta:
|
|
1374
|
+
self._commit_active_tool_fold()
|
|
1346
1375
|
normalized_message_id = str(message_id or "").strip()
|
|
1347
1376
|
if normalized_message_id and normalized_message_id != self._active_text_message_id:
|
|
1348
1377
|
self._active_text_message_id = normalized_message_id
|
|
@@ -1382,40 +1411,50 @@ class EventRenderer:
|
|
|
1382
1411
|
attempted=attempted,
|
|
1383
1412
|
compacted=compacted,
|
|
1384
1413
|
reason=reason,
|
|
1385
|
-
tokens_before=
|
|
1414
|
+
tokens_before=_,
|
|
1386
1415
|
tokens_after=tokens_after,
|
|
1387
1416
|
):
|
|
1388
1417
|
self.clear_auto_compacting()
|
|
1389
1418
|
pct = int(tokens / threshold * 100) if threshold > 0 else 0
|
|
1419
|
+
# precheck 的 tokens 是 estimate_precheck() 推算值;
|
|
1420
|
+
# check 的 tokens 是上一次 API 返回的真实 context_usage。
|
|
1421
|
+
trigger_label = "est." if trigger == "precheck" else "usage"
|
|
1390
1422
|
if compacted:
|
|
1423
|
+
reduction_pct = (
|
|
1424
|
+
max(0, int(((tokens - tokens_after) / tokens) * 100))
|
|
1425
|
+
if tokens > 0
|
|
1426
|
+
else 0
|
|
1427
|
+
)
|
|
1391
1428
|
self.append_system_message(
|
|
1392
1429
|
(
|
|
1393
|
-
f"
|
|
1394
|
-
f"{
|
|
1395
|
-
f"
|
|
1430
|
+
f"Auto-compact done ({trigger}): "
|
|
1431
|
+
f"{trigger_label} {tokens:,} / threshold {threshold:,} ({pct}%)\n"
|
|
1432
|
+
f" {INJECTED_ARROW} context reduced to ~{tokens_after:,} tokens "
|
|
1433
|
+
f"(-{reduction_pct}%)"
|
|
1396
1434
|
),
|
|
1397
1435
|
severity="info",
|
|
1398
1436
|
)
|
|
1399
1437
|
elif attempted:
|
|
1400
1438
|
is_recoverable_deferred = str(reason).startswith("partial_compact_no_op:")
|
|
1401
1439
|
label = (
|
|
1402
|
-
"
|
|
1440
|
+
"Auto-compact deferred"
|
|
1403
1441
|
if is_recoverable_deferred
|
|
1404
|
-
else "
|
|
1442
|
+
else "Auto-compact attempt failed"
|
|
1405
1443
|
)
|
|
1406
1444
|
self.append_system_message(
|
|
1407
1445
|
(
|
|
1408
1446
|
f"{label} ({trigger}): "
|
|
1409
|
-
f"
|
|
1410
|
-
f"
|
|
1447
|
+
f"{trigger_label} {tokens:,} / threshold {threshold:,} ({pct}%), "
|
|
1448
|
+
f"reason={reason}"
|
|
1411
1449
|
),
|
|
1412
1450
|
severity="warning",
|
|
1413
1451
|
)
|
|
1414
1452
|
else:
|
|
1415
1453
|
self.append_system_message(
|
|
1416
1454
|
(
|
|
1417
|
-
f"
|
|
1418
|
-
f"{tokens:,}/{threshold:,}
|
|
1455
|
+
f"Auto-compact skipped ({trigger}): "
|
|
1456
|
+
f"{trigger_label} {tokens:,} / threshold {threshold:,} ({pct}%), "
|
|
1457
|
+
f"reason={reason}"
|
|
1419
1458
|
),
|
|
1420
1459
|
severity="warning",
|
|
1421
1460
|
)
|
|
@@ -1423,12 +1462,46 @@ class EventRenderer:
|
|
|
1423
1462
|
args_dict = arguments if isinstance(arguments, dict) else {"_raw": str(arguments)}
|
|
1424
1463
|
# Store args for ToolResult phase lookup.
|
|
1425
1464
|
self._tool_call_args[tool_call_id] = args_dict
|
|
1465
|
+
if is_foldable_tool(tool_name):
|
|
1466
|
+
self._active_tool_fold.add_call(
|
|
1467
|
+
tool_call_id=tool_call_id,
|
|
1468
|
+
tool_name=tool_name,
|
|
1469
|
+
args=args_dict,
|
|
1470
|
+
started_at_monotonic=time.monotonic(),
|
|
1471
|
+
)
|
|
1472
|
+
self._rebuild_loading_line()
|
|
1473
|
+
return (False, None)
|
|
1474
|
+
self._commit_active_tool_fold()
|
|
1426
1475
|
if not should_show_tool_in_scrollback(tool_name, args_dict):
|
|
1427
1476
|
self._rebuild_loading_line()
|
|
1428
1477
|
return (False, None)
|
|
1429
1478
|
self._append_tool_call(tool_name, args_dict, tool_call_id)
|
|
1430
1479
|
case ToolResultEvent(tool=tool_name, result=result, tool_call_id=tool_call_id, is_error=is_error, metadata=metadata, output=output):
|
|
1431
1480
|
stored_args = self._tool_call_args.pop(tool_call_id, {})
|
|
1481
|
+
if self._active_tool_fold.has_call(tool_call_id):
|
|
1482
|
+
display_name = resolve_display_tool_name(tool_name, stored_args)
|
|
1483
|
+
rec = self._make_tool_result_record(
|
|
1484
|
+
tool_call_id=tool_call_id,
|
|
1485
|
+
tool_name=tool_name,
|
|
1486
|
+
display_name=display_name,
|
|
1487
|
+
started_at_monotonic=time.monotonic(),
|
|
1488
|
+
args=stored_args,
|
|
1489
|
+
is_error=is_error,
|
|
1490
|
+
result=result,
|
|
1491
|
+
metadata=metadata,
|
|
1492
|
+
output=output,
|
|
1493
|
+
)
|
|
1494
|
+
error_summary = None
|
|
1495
|
+
if is_error:
|
|
1496
|
+
error_summary = str(result).strip().splitlines()[0] if str(result).strip() else None
|
|
1497
|
+
self._active_tool_fold.mark_result(
|
|
1498
|
+
tool_call_id,
|
|
1499
|
+
is_error=is_error,
|
|
1500
|
+
result_record_sequence=rec.sequence,
|
|
1501
|
+
error_summary=error_summary,
|
|
1502
|
+
)
|
|
1503
|
+
self._rebuild_loading_line()
|
|
1504
|
+
return (False, None)
|
|
1432
1505
|
if not should_show_tool_in_scrollback(tool_name, stored_args, is_result=True, is_error=is_error):
|
|
1433
1506
|
self._running_tools.pop(tool_call_id, None)
|
|
1434
1507
|
self._rebuild_loading_line()
|
|
@@ -1532,6 +1605,7 @@ class EventRenderer:
|
|
|
1532
1605
|
if self._turn_received_text_delta:
|
|
1533
1606
|
pass
|
|
1534
1607
|
elif text:
|
|
1608
|
+
self._commit_active_tool_fold()
|
|
1535
1609
|
self._append_assistant_text(text)
|
|
1536
1610
|
case StepCompleteEvent(step_id=step_id, status=_, duration_ms=_):
|
|
1537
1611
|
# Cancellation/error paths may emit StepCompleteEvent without ToolResultEvent.
|
|
@@ -1539,6 +1613,7 @@ class EventRenderer:
|
|
|
1539
1613
|
self._running_tools.pop(step_id, None)
|
|
1540
1614
|
case StopEvent(reason=reason):
|
|
1541
1615
|
self._flush_assistant_segment()
|
|
1616
|
+
self._commit_active_tool_fold()
|
|
1542
1617
|
self._pending_tool_starts.clear()
|
|
1543
1618
|
# Safety net: if any running tool rows remain, stop event means this turn is ending.
|
|
1544
1619
|
self._running_tools.clear()
|
|
@@ -107,20 +107,40 @@ def _entry_content(
|
|
|
107
107
|
entry: HistoryEntry,
|
|
108
108
|
*,
|
|
109
109
|
terminal_width: int,
|
|
110
|
-
render_markdown_to_plain: Callable[..., str],
|
|
111
|
-
) -> str:
|
|
110
|
+
render_markdown_to_plain: Callable[..., str | Text],
|
|
111
|
+
) -> str | Text:
|
|
112
112
|
if entry.entry_type != "assistant":
|
|
113
113
|
return str(entry.text)
|
|
114
114
|
width = max(terminal_width - 6, 40)
|
|
115
115
|
return render_markdown_to_plain(str(entry.text), width=width)
|
|
116
116
|
|
|
117
117
|
|
|
118
|
+
def _render_prefixed_rich_text(
|
|
119
|
+
content: Text,
|
|
120
|
+
*,
|
|
121
|
+
prefix: str,
|
|
122
|
+
in_run_continuation: bool,
|
|
123
|
+
) -> Text:
|
|
124
|
+
content_lines = content.split("\n") or [Text("")]
|
|
125
|
+
line_text = Text()
|
|
126
|
+
if in_run_continuation:
|
|
127
|
+
line_text.append(" ", style="bold")
|
|
128
|
+
else:
|
|
129
|
+
line_text.append(f"{prefix} ", style="bold")
|
|
130
|
+
line_text.append_text(content_lines[0])
|
|
131
|
+
for line in content_lines[1:]:
|
|
132
|
+
line_text.append("\n")
|
|
133
|
+
line_text.append(" ")
|
|
134
|
+
line_text.append_text(line)
|
|
135
|
+
return line_text
|
|
136
|
+
|
|
137
|
+
|
|
118
138
|
def render_history_group(
|
|
119
139
|
console: Console,
|
|
120
140
|
entries: list[HistoryEntry],
|
|
121
141
|
*,
|
|
122
142
|
terminal_width: int,
|
|
123
|
-
render_markdown_to_plain: Callable[..., str],
|
|
143
|
+
render_markdown_to_plain: Callable[..., str | Text],
|
|
124
144
|
prev_was_assistant: bool = False,
|
|
125
145
|
prev_was_thinking: bool = False,
|
|
126
146
|
assume_run_continues_at_tail: bool = False,
|
|
@@ -240,6 +260,33 @@ def render_history_group(
|
|
|
240
260
|
subtitle_chain_active = True
|
|
241
261
|
continue
|
|
242
262
|
|
|
263
|
+
if entry.entry_type == "tool_fold":
|
|
264
|
+
# 折叠组摘要:参考 thinking 的视觉处理(2 空格缩进 + dim 文案),
|
|
265
|
+
# 不带前缀圆点,行末追加 "(ctrl+o to expand)" 提示 Ctrl+O 展开。
|
|
266
|
+
is_error = entry.severity == "error"
|
|
267
|
+
content = str(entry.text).strip()
|
|
268
|
+
content_lines = [line for line in content.splitlines() if line.strip()] or [content]
|
|
269
|
+
suffix = " (ctrl+o to expand)" if entry.tool_call_ids else ""
|
|
270
|
+
text_style = "dim #FF6B6B" if is_error else "dim"
|
|
271
|
+
|
|
272
|
+
line_text = Text()
|
|
273
|
+
line_text.append(" ", style=text_style)
|
|
274
|
+
line_text.append(content_lines[0], style=text_style)
|
|
275
|
+
if suffix and len(content_lines) == 1:
|
|
276
|
+
line_text.append(suffix, style=text_style)
|
|
277
|
+
for idx_line, line in enumerate(content_lines[1:], start=1):
|
|
278
|
+
line_text.append("\n")
|
|
279
|
+
line_text.append(" ", style=text_style)
|
|
280
|
+
line_text.append(line, style=text_style)
|
|
281
|
+
if suffix and idx_line == len(content_lines) - 1:
|
|
282
|
+
line_text.append(suffix, style=text_style)
|
|
283
|
+
|
|
284
|
+
renderables.append(line_text)
|
|
285
|
+
renderables.append(Text(""))
|
|
286
|
+
prev_was_assistant = False
|
|
287
|
+
subtitle_chain_active = False
|
|
288
|
+
continue
|
|
289
|
+
|
|
243
290
|
if entry.entry_type == "tool_result":
|
|
244
291
|
prefix_char = BLACK_CIRCLE
|
|
245
292
|
prefix_style = "bold red" if entry.severity == "error" else "bold green"
|
|
@@ -253,10 +300,20 @@ def render_history_group(
|
|
|
253
300
|
first_line.append(f"{prefix_char} ", style=prefix_style)
|
|
254
301
|
first_line.append_text(text_lines[0] if text_lines else Text())
|
|
255
302
|
renderables.append(first_line)
|
|
303
|
+
|
|
304
|
+
has_body = len(text_lines) > 1
|
|
305
|
+
# For multi-line Rich Text bodies (Edit/Write inline diff),
|
|
306
|
+
# subtitle ("Added X, removed Y") goes between signature and
|
|
307
|
+
# body so the reader sees the summary before the diff. For
|
|
308
|
+
# single-line text (bash/grep/read previews), subtitle stays
|
|
309
|
+
# after the signature line as before.
|
|
310
|
+
if has_body and entry.subtitle:
|
|
311
|
+
renderables.append(_render_subtitle_line(entry.subtitle, error=is_error))
|
|
256
312
|
for line in text_lines[1:]:
|
|
257
313
|
renderables.append(line)
|
|
258
|
-
if entry.subtitle:
|
|
314
|
+
if not has_body and entry.subtitle:
|
|
259
315
|
renderables.append(_render_subtitle_line(entry.subtitle, error=is_error))
|
|
316
|
+
|
|
260
317
|
renderables.append(Text(""))
|
|
261
318
|
prev_was_assistant = False
|
|
262
319
|
subtitle_chain_active = bool(entry.subtitle)
|
|
@@ -297,10 +354,10 @@ def render_history_group(
|
|
|
297
354
|
style = "bold #FF6B6B" if entry.severity == "error" else "#E8B830"
|
|
298
355
|
content_lines = content.splitlines() or [""]
|
|
299
356
|
line_text = Text()
|
|
300
|
-
line_text.append(content_lines[0], style=style)
|
|
357
|
+
line_text.append(f" {content_lines[0]}", style=style)
|
|
301
358
|
for line in content_lines[1:]:
|
|
302
359
|
line_text.append("\n")
|
|
303
|
-
line_text.append(line, style=style)
|
|
360
|
+
line_text.append(f" {line}", style=style)
|
|
304
361
|
renderables.append(line_text)
|
|
305
362
|
|
|
306
363
|
subtitle_chain_active = False
|
|
@@ -324,16 +381,25 @@ def render_history_group(
|
|
|
324
381
|
terminal_width=terminal_width,
|
|
325
382
|
render_markdown_to_plain=render_markdown_to_plain,
|
|
326
383
|
)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
384
|
+
if isinstance(content, Text):
|
|
385
|
+
renderables.append(
|
|
386
|
+
_render_prefixed_rich_text(
|
|
387
|
+
content,
|
|
388
|
+
prefix=_entry_prefix(entry),
|
|
389
|
+
in_run_continuation=in_run_continuation,
|
|
390
|
+
)
|
|
391
|
+
)
|
|
330
392
|
else:
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
393
|
+
content_lines = content.splitlines() or [""]
|
|
394
|
+
if in_run_continuation:
|
|
395
|
+
first_line = f" {content_lines[0]}"
|
|
396
|
+
else:
|
|
397
|
+
prefix = _entry_prefix(entry)
|
|
398
|
+
first_line = f"{prefix} {content_lines[0]}"
|
|
399
|
+
lines = [first_line]
|
|
400
|
+
for line in content_lines[1:]:
|
|
401
|
+
lines.append(f" {line}")
|
|
402
|
+
renderables.append("\n".join(lines))
|
|
337
403
|
|
|
338
404
|
# 在 assistant run 内部不插入空行;只有 run 末尾才与下一段拉开。
|
|
339
405
|
# assume_run_continues_at_tail=True 时(drain 在 streaming 中),把 batch
|
|
@@ -357,8 +423,11 @@ def render_history_group(
|
|
|
357
423
|
|
|
358
424
|
|
|
359
425
|
async def print_history_group_async(console: Console, group: Group) -> None:
|
|
360
|
-
|
|
426
|
+
# highlight=False:Rich 默认 ReprHighlighter 会把 "/cmd" 误识别为 path/filename
|
|
427
|
+
# 并染成 magenta/bright-magenta(即"粉红色 slash 命令名")。所有 entry 在
|
|
428
|
+
# render_history_group 内已自带 explicit Text 样式,不需要再做自动高亮。
|
|
429
|
+
console.print(group, soft_wrap=True, highlight=False)
|
|
361
430
|
|
|
362
431
|
|
|
363
432
|
def print_history_group_sync(console: Console, group: Group) -> None:
|
|
364
|
-
console.print(group, soft_wrap=True)
|
|
433
|
+
console.print(group, soft_wrap=True, highlight=False)
|
|
@@ -26,6 +26,11 @@ _LOG_LEVEL_ENV_VAR = "COMATE_LOG_LEVEL"
|
|
|
26
26
|
_VALID_LOG_LEVELS: tuple[str, ...] = ("DEBUG", "INFO", "WARNING", "ERROR")
|
|
27
27
|
_DEFAULT_LOG_LEVEL = logging.INFO
|
|
28
28
|
_invalid_log_level_warned = False
|
|
29
|
+
_MEMORY_EXTRACTION_ALLOWED_TOOLS = frozenset(
|
|
30
|
+
{"Bash", "Edit", "Glob", "Grep", "Read", "Write"}
|
|
31
|
+
)
|
|
32
|
+
_ALLOWED_TOOLS_DENIAL_PREFIX = "工具执行被拒绝(allowed_tools):"
|
|
33
|
+
_AVAILABLE_TOOLS_MARKER = "可用工具:"
|
|
29
34
|
|
|
30
35
|
|
|
31
36
|
def _should_drop_recent_duplicate_log(msg_key: str) -> bool:
|
|
@@ -44,6 +49,25 @@ def _should_drop_recent_duplicate_log(msg_key: str) -> bool:
|
|
|
44
49
|
return previous is not None
|
|
45
50
|
|
|
46
51
|
|
|
52
|
+
def _should_suppress_tui_log(record: logging.LogRecord) -> bool:
|
|
53
|
+
if record.name == "comate_agent_sdk.agent.memory":
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
msg = record.getMessage()
|
|
57
|
+
if record.name != "comate_agent_sdk.agent":
|
|
58
|
+
return False
|
|
59
|
+
if _ALLOWED_TOOLS_DENIAL_PREFIX not in msg or _AVAILABLE_TOOLS_MARKER not in msg:
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
available_text = msg.split(_AVAILABLE_TOOLS_MARKER, 1)[1]
|
|
63
|
+
available_tools = frozenset(
|
|
64
|
+
part.strip()
|
|
65
|
+
for part in available_text.split(",")
|
|
66
|
+
if part.strip()
|
|
67
|
+
)
|
|
68
|
+
return available_tools == _MEMORY_EXTRACTION_ALLOWED_TOOLS
|
|
69
|
+
|
|
70
|
+
|
|
47
71
|
class TUILoggingHandler(logging.Handler):
|
|
48
72
|
"""自定义 logging handler,将日志友好地显示在 TUI 中
|
|
49
73
|
|
|
@@ -69,6 +93,8 @@ class TUILoggingHandler(logging.Handler):
|
|
|
69
93
|
msg = self._format_message(record)
|
|
70
94
|
if not msg:
|
|
71
95
|
return
|
|
96
|
+
if _should_suppress_tui_log(record):
|
|
97
|
+
return
|
|
72
98
|
|
|
73
99
|
# 首次显示检查
|
|
74
100
|
msg_key = self._get_message_key(record)
|