comate-cli 0.7.0a7__tar.gz → 0.7.0a9__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/AGENTS.md +115 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/CHANGELOG.md +4 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/PKG-INFO +1 -1
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/animations.py +29 -5
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/event_renderer.py +102 -15
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/history_printer.py +86 -17
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/logging_adapter.py +26 -0
- comate_cli-0.7.0a9/comate_cli/terminal_agent/markdown_render.py +138 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/models.py +2 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/slash_commands.py +5 -0
- comate_cli-0.7.0a9/comate_cli/terminal_agent/tool_fold.py +206 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tool_result_formatters.py +65 -33
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/transcript_viewer.py +60 -27
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tui.py +23 -14
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tui_parts/commands.py +76 -40
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tui_parts/history_sync.py +45 -3
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tui_parts/input_behavior.py +13 -4
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tui_parts/key_bindings.py +2 -2
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tui_parts/render_panels.py +93 -10
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/pyproject.toml +1 -1
- comate_cli-0.7.0a9/report.md +382 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_completion_status_panel.py +43 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_event_renderer.py +102 -28
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_event_renderer_streaming.py +32 -1
- comate_cli-0.7.0a9/tests/test_event_renderer_tool_fold.py +146 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_history_printer.py +57 -1
- comate_cli-0.7.0a9/tests/test_history_printer_subtitle_position.py +78 -0
- comate_cli-0.7.0a9/tests/test_history_printer_tool_fold.py +78 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_history_sync.py +29 -5
- comate_cli-0.7.0a9/tests/test_history_sync_tool_fold.py +98 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_logging_adapter.py +43 -0
- comate_cli-0.7.0a9/tests/test_markdown_render.py +102 -0
- comate_cli-0.7.0a9/tests/test_plugin_slash_commands.py +107 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_skills_slash_command.py +3 -27
- comate_cli-0.7.0a9/tests/test_slash_clear.py +350 -0
- comate_cli-0.7.0a9/tests/test_tool_fold.py +89 -0
- comate_cli-0.7.0a9/tests/test_tool_fold_panel.py +159 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tool_result_formatters.py +163 -12
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_transcript_viewer.py +64 -6
- comate_cli-0.7.0a9/tests/test_transcript_viewer_tool_fold.py +47 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tui_paste_placeholder.py +46 -1
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tui_queue_sdk_source.py +35 -11
- comate_cli-0.7.0a9/tests/test_tui_thinking_display.py +71 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/uv.lock +2 -2
- comate_cli-0.7.0a7/comate_cli/terminal_agent/markdown_render.py +0 -26
- comate_cli-0.7.0a7/tests/test_markdown_render.py +0 -53
- comate_cli-0.7.0a7/tests/test_plugin_slash_commands.py +0 -17
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/.gitignore +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/README.md +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/bash-exit-code-green-dot-bug.md +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/__init__.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/__main__.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/main.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/app.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/figures.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/resume_picker.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/resume_preview.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/status_bar.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tool_result_store.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tool_result_viewer.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tui_parts/btw_view.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/docs/hooks.md +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/docs/superpowers/plans/2026-04-03-phrase-shuffle.md +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/docs/superpowers/specs/2026-04-01-conditional-diff-subtitle-design.md +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/docs/superpowers/specs/2026-04-03-phrase-shuffle-design.md +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/conftest.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/fixtures/fake_mcp_misbehaving_stdout.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_btw_slash_command.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_context_command.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_event_renderer_log_boundary.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_event_renderer_log_queue.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_format_error.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_handle_error.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_history_printer_log.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_input_history.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_logo.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_main_args.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_preflight.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_question_view.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_resume_picker.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_resume_preview.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_session_query_token_summary.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_shutdown_noise_guard.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_shutdown_noise_integration.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_status_bar.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_task_poll.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tool_result_store.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tool_result_viewer.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tool_result_viewer_key_bindings.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tool_view.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tui_paste_newline_guard.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tui_queue_preview.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/tests/test_tui_tool_result_registry_lifecycle.py +0 -0
- {comate_cli-0.7.0a7 → comate_cli-0.7.0a9}/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.0a9
|
|
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
|
|
@@ -9,7 +9,7 @@ from enum import Enum
|
|
|
9
9
|
from rich.console import RenderableType
|
|
10
10
|
from rich.text import Text
|
|
11
11
|
|
|
12
|
-
from comate_agent_sdk.agent.events import StopEvent,
|
|
12
|
+
from comate_agent_sdk.agent.events import StopEvent, ToolCallEvent, ToolResultEvent, UserQuestionEvent
|
|
13
13
|
|
|
14
14
|
from comate_cli.terminal_agent.figures import (
|
|
15
15
|
BREATH_DOT_GLYPHS as _FIGURES_BREATH_DOT_GLYPHS,
|
|
@@ -238,6 +238,27 @@ BREATH_DOT_COLORS: tuple[str, ...] = (
|
|
|
238
238
|
)
|
|
239
239
|
BREATH_DOT_GLYPHS: tuple[str, ...] = _FIGURES_BREATH_DOT_GLYPHS
|
|
240
240
|
|
|
241
|
+
LOADING_SPINNER_FRAMES: tuple[str, ...] = (
|
|
242
|
+
"⠋",
|
|
243
|
+
"⠙",
|
|
244
|
+
"⠹",
|
|
245
|
+
"⠸",
|
|
246
|
+
"⠼",
|
|
247
|
+
"⠴",
|
|
248
|
+
"⠦",
|
|
249
|
+
"⠧",
|
|
250
|
+
"⠇",
|
|
251
|
+
"⠏",
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
HIDDEN_THINKING_BADGES: tuple[str, ...] = (
|
|
255
|
+
"ultra thinking",
|
|
256
|
+
"deep reasoning",
|
|
257
|
+
"thinking deeper",
|
|
258
|
+
"extended thinking",
|
|
259
|
+
"working through it",
|
|
260
|
+
)
|
|
261
|
+
|
|
241
262
|
|
|
242
263
|
def breathing_dot_color(frame: int) -> str:
|
|
243
264
|
"""Return the breathing dot color for a given animation frame."""
|
|
@@ -252,6 +273,11 @@ def breathing_dot_glyph(now_monotonic: float | None = None) -> str:
|
|
|
252
273
|
return BREATH_DOT_GLYPHS[phase]
|
|
253
274
|
|
|
254
275
|
|
|
276
|
+
def loading_spinner_glyph(frame: int) -> str:
|
|
277
|
+
"""Return the compact spinner glyph for the main loading line."""
|
|
278
|
+
return LOADING_SPINNER_FRAMES[frame % len(LOADING_SPINNER_FRAMES)]
|
|
279
|
+
|
|
280
|
+
|
|
255
281
|
def _lerp_rgb(
|
|
256
282
|
start_rgb: tuple[int, int, int],
|
|
257
283
|
end_rgb: tuple[int, int, int],
|
|
@@ -385,11 +411,9 @@ class SubmissionAnimator:
|
|
|
385
411
|
return Text("")
|
|
386
412
|
|
|
387
413
|
phrase = self._status_hint if self._status_hint else (self._shuffled[self._phrase_idx] + ELLIPSIS)
|
|
388
|
-
dot_color = breathing_dot_color(self._frame)
|
|
389
|
-
now_monotonic = time.monotonic()
|
|
390
414
|
dot = Text(
|
|
391
|
-
f"{
|
|
392
|
-
style=
|
|
415
|
+
f"{loading_spinner_glyph(self._frame)} ",
|
|
416
|
+
style="bold #9CA3AF",
|
|
393
417
|
)
|
|
394
418
|
sweep = _cyan_sweep_text(phrase, frame=self._frame)
|
|
395
419
|
return Text.assemble(dot, sweep)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import random
|
|
4
5
|
import re
|
|
5
6
|
import threading
|
|
6
7
|
import time
|
|
@@ -37,6 +38,7 @@ from comate_agent_sdk.agent.events import (
|
|
|
37
38
|
from rich.console import RenderableType
|
|
38
39
|
from rich.text import Text
|
|
39
40
|
|
|
41
|
+
from comate_cli.terminal_agent.animations import HIDDEN_THINKING_BADGES
|
|
40
42
|
from comate_cli.terminal_agent.figures import (
|
|
41
43
|
BOTTOM_LEFT_CROP,
|
|
42
44
|
BULLET_OPERATOR,
|
|
@@ -59,6 +61,11 @@ from comate_cli.terminal_agent.tool_result_store import (
|
|
|
59
61
|
ToolResultRegistry,
|
|
60
62
|
)
|
|
61
63
|
from comate_cli.terminal_agent.tool_view import summarize_tool_args, resolve_display_tool_name, should_show_tool_in_scrollback
|
|
64
|
+
from comate_cli.terminal_agent.tool_fold import (
|
|
65
|
+
ActiveToolFold,
|
|
66
|
+
ToolFoldSnapshot,
|
|
67
|
+
is_foldable_tool,
|
|
68
|
+
)
|
|
62
69
|
from comate_cli.terminal_agent.env_utils import read_env_int
|
|
63
70
|
from comate_cli.terminal_agent.custom_slash_commands import FILE_REF_PATTERN
|
|
64
71
|
logger = logging.getLogger(__name__)
|
|
@@ -258,6 +265,7 @@ class EventRenderer:
|
|
|
258
265
|
self._history: list[HistoryEntry] = []
|
|
259
266
|
self._running_tools: dict[str, _RunningTool] = {}
|
|
260
267
|
self._tool_call_args: dict[str, dict[str, Any]] = {}
|
|
268
|
+
self._active_tool_fold = ActiveToolFold()
|
|
261
269
|
self._tool_results = tool_results
|
|
262
270
|
self._fallback_tool_result_sequence = 0
|
|
263
271
|
self._assistant_buffer = ""
|
|
@@ -286,6 +294,7 @@ class EventRenderer:
|
|
|
286
294
|
self._show_thinking_cb: Callable[[], bool] = lambda: True
|
|
287
295
|
self._turn_received_text_delta: bool = False
|
|
288
296
|
self._turn_received_thinking_delta: bool = False
|
|
297
|
+
self._hidden_thinking_badge_text: str = ""
|
|
289
298
|
self._text_delta_started: bool = False
|
|
290
299
|
self._active_text_message_id: str | None = None
|
|
291
300
|
# ━━━━━ spec §4.1 新管道状态字段(Phase 1.2 引入,Phase 4-6 接入) ━━━━━
|
|
@@ -358,6 +367,21 @@ class EventRenderer:
|
|
|
358
367
|
if entry.entry_type == "tool_result":
|
|
359
368
|
self.flush_pending_logs()
|
|
360
369
|
|
|
370
|
+
def _commit_active_tool_fold(self) -> None:
|
|
371
|
+
snapshot = self._active_tool_fold.snapshot(active=False)
|
|
372
|
+
if snapshot is None:
|
|
373
|
+
return
|
|
374
|
+
self._append_history_entry(
|
|
375
|
+
HistoryEntry(
|
|
376
|
+
entry_type="tool_fold",
|
|
377
|
+
text=snapshot.summary,
|
|
378
|
+
severity="error" if snapshot.any_error else "info",
|
|
379
|
+
subtitle=snapshot.error_summary or snapshot.latest_hint,
|
|
380
|
+
tool_call_ids=snapshot.tool_call_ids,
|
|
381
|
+
)
|
|
382
|
+
)
|
|
383
|
+
self._active_tool_fold.clear()
|
|
384
|
+
|
|
361
385
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
362
386
|
# spec §6 流式新管道方法集(Phase 1-6 新增;Phase 7 接入 handle_event)
|
|
363
387
|
# 容器检测原语(spec §6.3)
|
|
@@ -452,6 +476,10 @@ class EventRenderer:
|
|
|
452
476
|
"""
|
|
453
477
|
if not delta:
|
|
454
478
|
return
|
|
479
|
+
if self._show_thinking_cb():
|
|
480
|
+
self._hidden_thinking_badge_text = ""
|
|
481
|
+
elif not self._hidden_thinking_badge_text:
|
|
482
|
+
self._hidden_thinking_badge_text = random.choice(HIDDEN_THINKING_BADGES)
|
|
455
483
|
self._thinking_batch += delta
|
|
456
484
|
while "\n" in self._thinking_batch:
|
|
457
485
|
line, self._thinking_batch = self._thinking_batch.split("\n", 1)
|
|
@@ -543,6 +571,7 @@ class EventRenderer:
|
|
|
543
571
|
"""
|
|
544
572
|
if not delta:
|
|
545
573
|
return
|
|
574
|
+
self._hidden_thinking_badge_text = ""
|
|
546
575
|
if self._thinking_batch:
|
|
547
576
|
self._flush_thinking_batch()
|
|
548
577
|
self._text_pending += delta
|
|
@@ -583,6 +612,7 @@ class EventRenderer:
|
|
|
583
612
|
self._flush_thinking_batch()
|
|
584
613
|
# ⑤ aux
|
|
585
614
|
self._loading_aux_text = ""
|
|
615
|
+
self._hidden_thinking_badge_text = ""
|
|
586
616
|
|
|
587
617
|
def _drop_all_pending(self) -> None:
|
|
588
618
|
"""spec §6.4:interrupt_turn 调用。
|
|
@@ -593,6 +623,7 @@ class EventRenderer:
|
|
|
593
623
|
self._container = None
|
|
594
624
|
self._thinking_batch = ""
|
|
595
625
|
self._loading_aux_text = ""
|
|
626
|
+
self._hidden_thinking_badge_text = ""
|
|
596
627
|
self._text_delta_started = False
|
|
597
628
|
self._active_text_message_id = None
|
|
598
629
|
|
|
@@ -636,6 +667,7 @@ class EventRenderer:
|
|
|
636
667
|
self._container = None
|
|
637
668
|
self._thinking_batch = ""
|
|
638
669
|
self._loading_aux_text = ""
|
|
670
|
+
self._hidden_thinking_badge_text = ""
|
|
639
671
|
self._rebuild_loading_line()
|
|
640
672
|
|
|
641
673
|
def seed_user_message(
|
|
@@ -649,6 +681,7 @@ class EventRenderer:
|
|
|
649
681
|
if not normalized:
|
|
650
682
|
return
|
|
651
683
|
self._flush_assistant_segment()
|
|
684
|
+
self._commit_active_tool_fold()
|
|
652
685
|
if display_header and display_subtitle:
|
|
653
686
|
# AskUserQuestion 答复入 scrollback:用 tool_result 样式渲染头行(●),
|
|
654
687
|
# 答案明细作为 ⎿ subtitle 紧贴其下;行间不插入用户 prefix。
|
|
@@ -698,6 +731,7 @@ class EventRenderer:
|
|
|
698
731
|
# spec §6.4:turn 结束 force flush 所有 pending(held / container /
|
|
699
732
|
# text_pending / thinking)保证 scrollback 看到完整内容
|
|
700
733
|
self._force_flush_all()
|
|
734
|
+
self._commit_active_tool_fold()
|
|
701
735
|
self._flush_assistant_segment()
|
|
702
736
|
self.flush_pending_logs()
|
|
703
737
|
self._pending_tool_starts.clear()
|
|
@@ -720,6 +754,7 @@ class EventRenderer:
|
|
|
720
754
|
)
|
|
721
755
|
self._running_tools.clear()
|
|
722
756
|
self._flush_assistant_segment()
|
|
757
|
+
self._commit_active_tool_fold()
|
|
723
758
|
self._pending_tool_starts.clear()
|
|
724
759
|
# spec §6.4:用户中断 → 5 个新字段全部丢弃不入队
|
|
725
760
|
self._drop_all_pending()
|
|
@@ -728,12 +763,16 @@ class EventRenderer:
|
|
|
728
763
|
def history_entries(self) -> list[HistoryEntry]:
|
|
729
764
|
return list(self._history)
|
|
730
765
|
|
|
766
|
+
def active_tool_fold_snapshot(self) -> ToolFoldSnapshot | None:
|
|
767
|
+
return self._active_tool_fold.snapshot(active=True)
|
|
768
|
+
|
|
731
769
|
def reset_history_view(self) -> None:
|
|
732
770
|
"""重置 history 视图状态(用于会话切换后的重新加载)。"""
|
|
733
771
|
self._history = []
|
|
734
772
|
self._recent_team_event_keys.clear()
|
|
735
773
|
self._running_tools.clear()
|
|
736
774
|
self._tool_call_args.clear()
|
|
775
|
+
self._active_tool_fold.clear()
|
|
737
776
|
self._assistant_buffer = ""
|
|
738
777
|
self._pending_tool_starts.clear()
|
|
739
778
|
self._turn_received_text_delta = False
|
|
@@ -747,6 +786,7 @@ class EventRenderer:
|
|
|
747
786
|
self._thinking_batch = ""
|
|
748
787
|
self._loading_aux_text = ""
|
|
749
788
|
self._loading_state = LoadingState.idle()
|
|
789
|
+
self._hidden_thinking_badge_text = ""
|
|
750
790
|
self._current_tasks = []
|
|
751
791
|
self._current_task_title = None
|
|
752
792
|
self._task_started_at_monotonic = None
|
|
@@ -802,6 +842,10 @@ class EventRenderer:
|
|
|
802
842
|
拼接到 spinner phrase。无容器时返回 ""。"""
|
|
803
843
|
return self._loading_aux_text
|
|
804
844
|
|
|
845
|
+
def hidden_thinking_badge_text(self) -> str:
|
|
846
|
+
"""Return the loading-line badge shown while hidden thinking streams."""
|
|
847
|
+
return self._hidden_thinking_badge_text
|
|
848
|
+
|
|
805
849
|
def append_subtitle(
|
|
806
850
|
self,
|
|
807
851
|
text: str,
|
|
@@ -1182,11 +1226,6 @@ class EventRenderer:
|
|
|
1182
1226
|
if state.is_task:
|
|
1183
1227
|
subagent_name = state.subagent_name or "Agent"
|
|
1184
1228
|
description = state.subagent_description or state.title
|
|
1185
|
-
if description and description != subagent_name:
|
|
1186
|
-
task_title = f"{subagent_name}({description})"
|
|
1187
|
-
else:
|
|
1188
|
-
task_title = subagent_name
|
|
1189
|
-
|
|
1190
1229
|
model_name = ""
|
|
1191
1230
|
if metadata and isinstance(metadata, dict):
|
|
1192
1231
|
model_name = metadata.get("model_name", "")
|
|
@@ -1343,6 +1382,7 @@ class EventRenderer:
|
|
|
1343
1382
|
pass
|
|
1344
1383
|
case TextDeltaEvent(delta=delta, message_id=message_id):
|
|
1345
1384
|
if delta:
|
|
1385
|
+
self._commit_active_tool_fold()
|
|
1346
1386
|
normalized_message_id = str(message_id or "").strip()
|
|
1347
1387
|
if normalized_message_id and normalized_message_id != self._active_text_message_id:
|
|
1348
1388
|
self._active_text_message_id = normalized_message_id
|
|
@@ -1382,40 +1422,50 @@ class EventRenderer:
|
|
|
1382
1422
|
attempted=attempted,
|
|
1383
1423
|
compacted=compacted,
|
|
1384
1424
|
reason=reason,
|
|
1385
|
-
tokens_before=
|
|
1425
|
+
tokens_before=_,
|
|
1386
1426
|
tokens_after=tokens_after,
|
|
1387
1427
|
):
|
|
1388
1428
|
self.clear_auto_compacting()
|
|
1389
1429
|
pct = int(tokens / threshold * 100) if threshold > 0 else 0
|
|
1430
|
+
# precheck 的 tokens 是 estimate_precheck() 推算值;
|
|
1431
|
+
# check 的 tokens 是上一次 API 返回的真实 context_usage。
|
|
1432
|
+
trigger_label = "est." if trigger == "precheck" else "usage"
|
|
1390
1433
|
if compacted:
|
|
1434
|
+
reduction_pct = (
|
|
1435
|
+
max(0, int(((tokens - tokens_after) / tokens) * 100))
|
|
1436
|
+
if tokens > 0
|
|
1437
|
+
else 0
|
|
1438
|
+
)
|
|
1391
1439
|
self.append_system_message(
|
|
1392
1440
|
(
|
|
1393
|
-
f"
|
|
1394
|
-
f"{
|
|
1395
|
-
f"
|
|
1441
|
+
f"Auto-compact done ({trigger}): "
|
|
1442
|
+
f"{trigger_label} {tokens:,} / threshold {threshold:,} ({pct}%)\n"
|
|
1443
|
+
f" {INJECTED_ARROW} context reduced to ~{tokens_after:,} tokens "
|
|
1444
|
+
f"(-{reduction_pct}%)"
|
|
1396
1445
|
),
|
|
1397
1446
|
severity="info",
|
|
1398
1447
|
)
|
|
1399
1448
|
elif attempted:
|
|
1400
1449
|
is_recoverable_deferred = str(reason).startswith("partial_compact_no_op:")
|
|
1401
1450
|
label = (
|
|
1402
|
-
"
|
|
1451
|
+
"Auto-compact deferred"
|
|
1403
1452
|
if is_recoverable_deferred
|
|
1404
|
-
else "
|
|
1453
|
+
else "Auto-compact attempt failed"
|
|
1405
1454
|
)
|
|
1406
1455
|
self.append_system_message(
|
|
1407
1456
|
(
|
|
1408
1457
|
f"{label} ({trigger}): "
|
|
1409
|
-
f"
|
|
1410
|
-
f"
|
|
1458
|
+
f"{trigger_label} {tokens:,} / threshold {threshold:,} ({pct}%), "
|
|
1459
|
+
f"reason={reason}"
|
|
1411
1460
|
),
|
|
1412
1461
|
severity="warning",
|
|
1413
1462
|
)
|
|
1414
1463
|
else:
|
|
1415
1464
|
self.append_system_message(
|
|
1416
1465
|
(
|
|
1417
|
-
f"
|
|
1418
|
-
f"{tokens:,}/{threshold:,}
|
|
1466
|
+
f"Auto-compact skipped ({trigger}): "
|
|
1467
|
+
f"{trigger_label} {tokens:,} / threshold {threshold:,} ({pct}%), "
|
|
1468
|
+
f"reason={reason}"
|
|
1419
1469
|
),
|
|
1420
1470
|
severity="warning",
|
|
1421
1471
|
)
|
|
@@ -1423,12 +1473,46 @@ class EventRenderer:
|
|
|
1423
1473
|
args_dict = arguments if isinstance(arguments, dict) else {"_raw": str(arguments)}
|
|
1424
1474
|
# Store args for ToolResult phase lookup.
|
|
1425
1475
|
self._tool_call_args[tool_call_id] = args_dict
|
|
1476
|
+
if is_foldable_tool(tool_name):
|
|
1477
|
+
self._active_tool_fold.add_call(
|
|
1478
|
+
tool_call_id=tool_call_id,
|
|
1479
|
+
tool_name=tool_name,
|
|
1480
|
+
args=args_dict,
|
|
1481
|
+
started_at_monotonic=time.monotonic(),
|
|
1482
|
+
)
|
|
1483
|
+
self._rebuild_loading_line()
|
|
1484
|
+
return (False, None)
|
|
1485
|
+
self._commit_active_tool_fold()
|
|
1426
1486
|
if not should_show_tool_in_scrollback(tool_name, args_dict):
|
|
1427
1487
|
self._rebuild_loading_line()
|
|
1428
1488
|
return (False, None)
|
|
1429
1489
|
self._append_tool_call(tool_name, args_dict, tool_call_id)
|
|
1430
1490
|
case ToolResultEvent(tool=tool_name, result=result, tool_call_id=tool_call_id, is_error=is_error, metadata=metadata, output=output):
|
|
1431
1491
|
stored_args = self._tool_call_args.pop(tool_call_id, {})
|
|
1492
|
+
if self._active_tool_fold.has_call(tool_call_id):
|
|
1493
|
+
display_name = resolve_display_tool_name(tool_name, stored_args)
|
|
1494
|
+
rec = self._make_tool_result_record(
|
|
1495
|
+
tool_call_id=tool_call_id,
|
|
1496
|
+
tool_name=tool_name,
|
|
1497
|
+
display_name=display_name,
|
|
1498
|
+
started_at_monotonic=time.monotonic(),
|
|
1499
|
+
args=stored_args,
|
|
1500
|
+
is_error=is_error,
|
|
1501
|
+
result=result,
|
|
1502
|
+
metadata=metadata,
|
|
1503
|
+
output=output,
|
|
1504
|
+
)
|
|
1505
|
+
error_summary = None
|
|
1506
|
+
if is_error:
|
|
1507
|
+
error_summary = str(result).strip().splitlines()[0] if str(result).strip() else None
|
|
1508
|
+
self._active_tool_fold.mark_result(
|
|
1509
|
+
tool_call_id,
|
|
1510
|
+
is_error=is_error,
|
|
1511
|
+
result_record_sequence=rec.sequence,
|
|
1512
|
+
error_summary=error_summary,
|
|
1513
|
+
)
|
|
1514
|
+
self._rebuild_loading_line()
|
|
1515
|
+
return (False, None)
|
|
1432
1516
|
if not should_show_tool_in_scrollback(tool_name, stored_args, is_result=True, is_error=is_error):
|
|
1433
1517
|
self._running_tools.pop(tool_call_id, None)
|
|
1434
1518
|
self._rebuild_loading_line()
|
|
@@ -1532,6 +1616,7 @@ class EventRenderer:
|
|
|
1532
1616
|
if self._turn_received_text_delta:
|
|
1533
1617
|
pass
|
|
1534
1618
|
elif text:
|
|
1619
|
+
self._commit_active_tool_fold()
|
|
1535
1620
|
self._append_assistant_text(text)
|
|
1536
1621
|
case StepCompleteEvent(step_id=step_id, status=_, duration_ms=_):
|
|
1537
1622
|
# Cancellation/error paths may emit StepCompleteEvent without ToolResultEvent.
|
|
@@ -1539,9 +1624,11 @@ class EventRenderer:
|
|
|
1539
1624
|
self._running_tools.pop(step_id, None)
|
|
1540
1625
|
case StopEvent(reason=reason):
|
|
1541
1626
|
self._flush_assistant_segment()
|
|
1627
|
+
self._commit_active_tool_fold()
|
|
1542
1628
|
self._pending_tool_starts.clear()
|
|
1543
1629
|
# Safety net: if any running tool rows remain, stop event means this turn is ending.
|
|
1544
1630
|
self._running_tools.clear()
|
|
1631
|
+
self._hidden_thinking_badge_text = ""
|
|
1545
1632
|
self._rebuild_loading_line()
|
|
1546
1633
|
if reason == "waiting_for_input":
|
|
1547
1634
|
return (True, None)
|
|
@@ -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)
|