minimal-agent 0.9.0 → 0.10.0
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.
- package/README.md +11 -8
- package/package.json +34 -1
- package/plugins/cron-runner/.claude-plugin/plugin.json +5 -0
- package/plugins/cron-runner/commands/cron.md +26 -0
- package/plugins/cron-runner/commands/crons.md +7 -0
- package/plugins/cron-runner/plugin.js +34 -0
- package/plugins/cron-runner/src/commands.js +348 -0
- package/plugins/cron-runner/src/cronTime.js +148 -0
- package/plugins/cron-runner/src/lock.js +68 -0
- package/plugins/cron-runner/src/runs.js +89 -0
- package/plugins/cron-runner/src/scheduleEngine.js +72 -0
- package/plugins/cron-runner/src/scheduler.js +355 -0
- package/plugins/cron-runner/src/schema.js +140 -0
- package/plugins/cron-runner/src/service.js +167 -0
- package/plugins/cron-runner/src/store.js +298 -0
- package/plugins/cron-runner/src/types.js +1 -0
- package/plugins/workflow-runner/commands/workflow.md +4 -4
- package/plugins/workflow-runner/commands/workflows.md +1 -1
- package/plugins/workflow-runner/plugin.js +2 -1
- package/plugins/workflow-runner/src/agentCommon.js +172 -9
- package/plugins/workflow-runner/src/dslManifest.js +72 -0
- package/plugins/workflow-runner/src/expressions.js +150 -13
- package/plugins/workflow-runner/src/index.js +39 -8
- package/plugins/workflow-runner/src/loader.js +216 -64
- package/plugins/workflow-runner/src/multimodal.js +37 -0
- package/plugins/workflow-runner/src/recovery.js +26 -24
- package/plugins/workflow-runner/src/runner.js +693 -287
- package/plugins/workflow-runner/src/stepExecutors/agent.js +126 -37
- package/plugins/workflow-runner/src/stepExecutors/assert.js +3 -2
- package/plugins/workflow-runner/src/stepExecutors/llm.js +38 -30
- package/plugins/workflow-runner/src/stepExecutors/skill.js +27 -38
- package/plugins/workflow-runner/src/stepExecutors/tool.js +56 -3
- package/plugins/workflow-runner/src/stepExecutors/verify.js +19 -10
- package/plugins/workflow-runner/src/taskIdentity.js +185 -30
- package/plugins/workflow-runner/src/types.js +18 -7
- package/plugins/workflow-runner/src/workflowState.js +355 -55
- package/skills/ai-image/SKILL.md +89 -0
- package/skills/ai-image/scripts/generate.mjs +497 -0
- package/skills/ai-image/scripts/generate_from_plan.mjs +469 -0
- package/skills/ai-image/scripts/generate_from_plan_test.mjs +223 -0
- package/skills/ai-image/scripts/generate_test.mjs +74 -0
- package/skills/browser-automation/SKILL.md +47 -0
- package/skills/docx/SKILL.md +25 -3
- package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
- package/skills/docx/scripts/opc_check.py +253 -0
- package/skills/image-gen-openrouter/SKILL.md +10 -2
- package/skills/image-stats/SKILL.md +65 -0
- package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
- package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
- package/skills/image-stats/scripts/image_stats.py +368 -0
- package/skills/image-stats/scripts/image_stats_test.py +181 -0
- package/skills/pptx/SKILL.md +123 -95
- package/skills/pptx/design.md +82 -0
- package/skills/pptx/examples/README.md +29 -0
- package/skills/pptx/examples/hybrid_example.mjs +107 -0
- package/skills/pptx/examples/hybrid_example.pptx +0 -0
- package/skills/pptx/pptxgenjs.md +41 -0
- package/skills/pptx/scripts/deck_helpers.mjs +224 -0
- package/skills/pptx/scripts/layout_lint.py +293 -0
- package/skills/pptx/scripts/layout_lint_test.py +160 -0
- package/skills/pptx/scripts/opc_check.py +339 -0
- package/skills/pptx/scripts/svg_fallback.mjs +145 -0
- package/skills/pptx/svg.md +67 -0
- package/skills/tts-zh/SKILL.md +19 -2
- package/skills/ui-design/SKILL.md +158 -0
- package/skills/ui-design/claude_design.md +309 -0
- package/skills/ui-design/evals/evals.json +254 -0
- package/skills/ui-design/references/aesthetic-direction.md +83 -0
- package/skills/ui-design/references/claude-design-protocol.md +36 -0
- package/skills/ui-design/references/design-context.md +53 -0
- package/skills/ui-design/references/design-execution-playbook.md +82 -0
- package/skills/ui-design/references/export-pptx.md +124 -0
- package/skills/ui-design/references/layout-composition.md +654 -0
- package/skills/ui-design/references/preview-feedback.md +37 -0
- package/skills/ui-design/references/source-image-layout.md +189 -0
- package/skills/ui-design/references/starter-components.md +78 -0
- package/skills/ui-design/references/vi-aegis-vector.md +144 -0
- package/skills/ui-design/references/vi-amber-prism.md +133 -0
- package/skills/ui-design/references/vi-aqua-glass.md +200 -0
- package/skills/ui-design/references/vi-comfort-vision.md +147 -0
- package/skills/ui-design/references/vi-ember-prism.md +140 -0
- package/skills/ui-design/references/vi-nebula-console.md +151 -0
- package/skills/ui-design/references/vi-obsidian-optics.md +139 -0
- package/skills/ui-design/references/vi-obsidian-xui.md +143 -0
- package/skills/ui-design/references/vi-precision-horizon-dark.md +117 -0
- package/skills/ui-design/references/vi-precision-horizon.md +215 -0
- package/skills/ui-design/references/vi-solar-relay.md +130 -0
- package/skills/ui-design/references/vi-template-workflow.md +48 -0
- package/skills/ui-design/references/vi-verdant-grid.md +141 -0
- package/skills/ui-design/references/vi-violet-meridian.md +172 -0
- package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/assemble_material.py +217 -0
- package/skills/ui-design/scripts/assemble_material_test.py +149 -0
- package/skills/ui-design/scripts/browser_check_ui_artifact.mjs +221 -0
- package/skills/ui-design/scripts/chart_sanity_check.py +261 -0
- package/skills/ui-design/scripts/chart_sanity_check_test.py +165 -0
- package/skills/ui-design/scripts/check_language_policy.py +351 -0
- package/skills/ui-design/scripts/check_ui_artifact.py +435 -0
- package/skills/ui-design/scripts/check_ui_artifact_pptx_test.py +251 -0
- package/skills/ui-design/scripts/deck_pages_check.py +158 -0
- package/skills/ui-design/scripts/deck_pages_check_test.py +134 -0
- package/skills/ui-design/scripts/deck_visual_style_check.py +767 -0
- package/skills/ui-design/scripts/deck_visual_style_check_test.py +713 -0
- package/skills/ui-design/scripts/design_budget_check.py +271 -0
- package/skills/ui-design/scripts/design_budget_check_test.py +183 -0
- package/skills/ui-design/scripts/design_repair_hints.py +162 -0
- package/skills/ui-design/scripts/design_repair_hints_test.py +54 -0
- package/skills/ui-design/scripts/design_style_check.py +397 -0
- package/skills/ui-design/scripts/design_style_check_test.py +233 -0
- package/skills/ui-design/scripts/draft_coverage_check.py +184 -0
- package/skills/ui-design/scripts/draft_coverage_check_test.py +131 -0
- package/skills/ui-design/scripts/export_pptx_from_html.mjs +2367 -0
- package/skills/ui-design/scripts/extract_source_assets.py +607 -0
- package/skills/ui-design/scripts/extract_source_assets_test.py +256 -0
- package/skills/ui-design/scripts/generated_image_review_collect.py +206 -0
- package/skills/ui-design/scripts/generated_image_review_collect_test.py +91 -0
- package/skills/ui-design/scripts/image_plan_lint.py +571 -0
- package/skills/ui-design/scripts/image_plan_lint_test.py +515 -0
- package/skills/ui-design/scripts/image_prompt_rewrite.py +713 -0
- package/skills/ui-design/scripts/image_prompt_rewrite_test.py +966 -0
- package/skills/ui-design/scripts/layout_balance.py +881 -0
- package/skills/ui-design/scripts/layout_balance_test.py +927 -0
- package/skills/ui-design/scripts/normalize_design_json.py +272 -0
- package/skills/ui-design/scripts/normalize_design_json_test.py +119 -0
- package/skills/ui-design/scripts/plan_source_layout.py +1450 -0
- package/skills/ui-design/scripts/plan_source_layout_test.py +1136 -0
- package/skills/ui-design/scripts/resolve_source_input.py +270 -0
- package/skills/ui-design/scripts/resolve_source_input_test.py +98 -0
- package/skills/ui-design/scripts/scaffold_ui_artifact.py +377 -0
- package/skills/ui-design/scripts/source_usage_check.py +581 -0
- package/skills/ui-design/scripts/source_usage_check_test.py +427 -0
- package/skills/ui-design/scripts/text_readability_pixel.py +340 -0
- package/skills/ui-design/scripts/text_readability_pixel_test.py +219 -0
- package/skills/ui-design/scripts/visual_quality_gate.py +301 -0
- package/skills/ui-design/scripts/visual_quality_gate_test.py +233 -0
- package/skills/ui-design/tests/e2e/prompts/deck.md +19 -0
- package/skills/ui-design/tests/e2e/prompts/webpage.md +17 -0
- package/skills/ui-design/tests/e2e/render_check.mjs +101 -0
- package/skills/ui-design/tests/e2e/rubric.json +54 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/language-check.txt +12 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/static-check.txt +15 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/outputs/index.html +837 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/prompt.md +12 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/review.md +21 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/homepage.html +373 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/platform.html +378 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/review.md +26 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/outputs/index.html +656 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/prompt.md +12 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/render_check.mjs +67 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/outputs/index.html +587 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/prompt.md +12 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/review.md +36 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/language-check.txt +8 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/static-check.txt +16 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/outputs/index.html +739 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/prompt.md +11 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/review.md +22 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/checks/browser-check.json +52 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/outputs/index.html +641 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/outputs/index.html +467 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/homepage.html +373 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/platform.html +378 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/review.md +26 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/language-check.txt +4 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/execution_notes.md +10 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/outputs/index.html +587 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/prompt.md +14 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/review.md +13 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/browser-check.json +24 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/execution_notes.md +21 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/outputs/deck.html +451 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-01.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-02.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/review.md +79 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/browser-check.json +26 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/execution_notes.md +20 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/outputs/index.html +534 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/browser-check.json +64 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/language-check.txt +7 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-console.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-homepage.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/console.html +600 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/homepage.html +556 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/prompt.md +29 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/review.md +57 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/browser-check.json +33 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/language-check.txt +6 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/outputs/index.html +682 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/review.md +59 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-01.html +466 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-02.html +433 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/language-check.txt +3 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/execution_notes.md +24 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/outputs/index.html +723 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/prompt.md +15 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/review.md +26 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-dark.json +49 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-light.json +49 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/language-check.txt +11 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-dark.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-light.txt +3 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/execution_notes.md +23 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/dark.html +392 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/light.html +476 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/prompt.md +23 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/review.md +41 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/browser-check.json +54 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/language-check.txt +5 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/outputs/index.html +765 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/prompt.md +15 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/review.md +59 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/outputs/index.html +508 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/browser-check.json +54 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/language-check.txt +6 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/outputs/index.html +788 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/prompt.md +14 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/review.md +60 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/mobile.png +0 -0
- package/skills/video-compose/SKILL.md +8 -1
- package/skills/web-crawler/SKILL.md +70 -0
- package/skills/web-crawler/scripts/crawl.mjs +538 -0
- package/skills/web-crawler/scripts/crawl.test.mjs +204 -0
- package/skills/xlsx/SKILL.md +12 -0
- package/skills/xlsx/scripts/opc_check.py +253 -0
- package/src/advisorState.js +22 -0
- package/src/askBridge.js +87 -3
- package/src/bootstrap/workingDir.js +12 -1
- package/src/cli/args.js +17 -1
- package/src/cli/configMenu.js +236 -0
- package/src/cli/configWizard.js +7 -5
- package/src/cli/print.js +37 -4
- package/src/cli/streamJson.js +77 -24
- package/src/cli/wizards/advisorWizard.js +147 -0
- package/src/cli/wizards/fieldForm.js +149 -0
- package/src/cli/wizards/imageWizard.js +60 -0
- package/src/cli/wizards/openRouterWizard.js +23 -0
- package/src/cli/wizards/providerPresets.js +95 -0
- package/src/cli/wizards/providerWizard.js +219 -0
- package/src/cli/wizards/searchWizard.js +23 -0
- package/src/cli/wizards/ttsWizard.js +34 -0
- package/src/config/configFile.js +441 -83
- package/src/config/configService.js +446 -0
- package/src/config.js +24 -64
- package/src/context/agentHome.js +9 -0
- package/src/context/archive.js +3 -2
- package/src/context/compact.js +8 -5
- package/src/context/contextLock.js +81 -0
- package/src/context/persistContext.js +30 -11
- package/src/context/reactiveCompact.js +4 -2
- package/src/context/recentDirs.js +21 -61
- package/src/context/sessionPath.js +97 -23
- package/src/context/sessionRegistry.js +303 -24
- package/src/context/tokenCounter.js +18 -0
- package/src/context/workspaceIndex.js +731 -0
- package/src/infra/verification.js +3 -2
- package/src/llm/client.js +293 -206
- package/src/llm/providers/anthropicFormat.js +241 -0
- package/src/llm/providers/openaiFormat.js +190 -0
- package/src/loop.js +211 -27
- package/src/main.js +61 -11
- package/src/messageQueue.js +97 -0
- package/src/multimodal.js +203 -0
- package/src/plugin-sdk.js +15 -0
- package/src/plugins/commandRouter.js +3 -0
- package/src/plugins/hookEngine.js +5 -1
- package/src/plugins/pluginLifecycle.js +28 -0
- package/src/plugins/pluginRunner.js +40 -0
- package/src/plugins/transcript.js +4 -3
- package/src/prompts/pluginList.js +2 -2
- package/src/prompts/projectInstructions.js +3 -3
- package/src/prompts/promptAssembly.js +133 -109
- package/src/prompts/skillList.js +12 -22
- package/src/session/runAgentTurn.js +70 -0
- package/src/session/runTurn.js +41 -6
- package/src/toolGate.js +17 -0
- package/src/tools/advisor/advisor.js +379 -0
- package/src/tools/agent/agent.js +555 -0
- package/src/tools/agent/agentTypes.js +59 -0
- package/src/tools/agent/coordinator.js +93 -0
- package/src/tools/agent/worktree.js +270 -0
- package/src/tools/ask/ask.js +7 -2
- package/src/tools/bash/bash.js +112 -18
- package/src/tools/download/download.js +144 -0
- package/src/tools/edit/edit.js +1 -0
- package/src/tools/edit/multi-edit.js +1 -0
- package/src/tools/genimage/genimage.js +242 -0
- package/src/tools/genspeech/genspeech.js +67 -0
- package/src/tools/glob/glob.js +1 -0
- package/src/tools/grep/grep.js +3 -0
- package/src/tools/grep/rgPath.js +2 -0
- package/src/tools/index.js +25 -2
- package/src/tools/read/read.js +1 -0
- package/src/tools/shared/killTree.js +52 -0
- package/src/tools/toolProgressBridge.js +48 -0
- package/src/tools/webbrowser/agentBrowserPath.js +258 -0
- package/src/tools/webbrowser/browser.js +59 -42
- package/src/tools/webbrowser/browserControl.js +118 -0
- package/src/tools/webbrowser/browserDaemon.js +97 -0
- package/src/tools/webbrowser/browserService.js +27 -0
- package/src/tools/webbrowser/browserStream.js +119 -0
- package/src/tools/webbrowser/webbrowser.js +699 -130
- package/src/tools/webfetch/webfetch.js +1 -0
- package/src/tools/websearch/websearch.js +134 -51
- package/src/tools/write/write.js +1 -0
- package/src/ui/App.js +249 -22
- package/src/ui/AskQuestion.js +19 -17
- package/src/ui/InputBox.js +34 -9
- package/src/ui/LiveArea.js +112 -19
- package/src/ui/MessageList.js +100 -14
- package/src/ui/ModelPicker.js +56 -0
- package/src/ui/QueuedMessagesBanner.js +14 -0
- package/src/ui/Root.js +18 -10
- package/src/ui/SessionPicker.js +102 -0
- package/src/ui/StatusLine.js +13 -4
- package/src/ui/WorkspacePicker.js +55 -0
- package/src/ui/figures.js +44 -0
- package/src/ui/hooks/askSlot.js +82 -0
- package/src/ui/hooks/useChat.js +345 -64
- package/src/ui/hooks/usePasteHandler.js +11 -1
- package/src/ui/hooks/useTextBuffer.js +1 -3
- package/src/ui/liveViewport.js +5 -1
- package/src/ui/markdown.js +206 -0
- package/src/ui/textBuffer.js +0 -7
- package/src/ui/theme.js +32 -0
- package/src/utils/debugLog.js +0 -4
- package/src/utils/resourcePaths.js +2 -2
- package/src/utils/stripThink.js +50 -0
- package/src/web/browserBridge.js +447 -0
- package/src/web/protocol.js +19 -0
- package/workflows/adaptive-research.yaml +9 -2
- package/workflows/book-review-short.yaml +31 -7
- package/workflows/e2e-write-greet.yaml +3 -0
- package/workflows/illustrate.yaml +227 -0
- package/workflows/plan.yaml +437 -207
- package/workflows/ppt.yaml +1659 -0
- package/workflows/ralph-loop.yaml +142 -112
- package/workflows/schema.json +81 -23
- package/workflows/webppt.yaml +496 -0
- package/workflows/word.yaml +710 -0
- package/workflows/youtube-shorts.yaml +38 -10
- package/MINIMAL-TRACE.md +0 -77
- package/plugins/workflow-runner/src/voteHelpers.js +0 -110
- package/skills/config/SKILL.md +0 -123
- package/skills/frontend-design/LICENSE.txt +0 -177
- package/skills/frontend-design/SKILL.md +0 -42
- package/skills/pptx/scripts/office/__pycache__/soffice.cpython-314.pyc +0 -0
- package/src/ui/ProgressPanel.js +0 -54
- package/src/ui/StreamingBlock.js +0 -11
- package/src/ui/ToolStatus.js +0 -11
package/src/ui/AskQuestion.js
CHANGED
|
@@ -13,12 +13,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
13
13
|
* 每题选项列表末尾自动追加一个「✏️ 其他(自由输入)」伪选项,命中即切到文本
|
|
14
14
|
* 输入子模式,让用户填选项之外的答案(自由输入按单值处理,覆盖多选集合)。
|
|
15
15
|
*
|
|
16
|
-
* Ink 用法与风格对齐 cli/
|
|
16
|
+
* Ink 用法与风格对齐 cli/wizards/providerWizard.tsx:useInput 手写键位、↑↓ 取模移动
|
|
17
17
|
* 高亮、`> ` 前缀 + cyan 高亮、gray 操作提示、文本输入用累积 draft + backspace。
|
|
18
18
|
* ============================================================
|
|
19
19
|
*/
|
|
20
|
-
import { useState } from 'react';
|
|
20
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
21
21
|
import { Box, Text, useInput } from 'ink';
|
|
22
|
+
import { theme } from './theme.js';
|
|
22
23
|
/** description 过长截断到此长度,防止 live 区被超长文本撑爆。 */
|
|
23
24
|
const DESC_MAX = 80;
|
|
24
25
|
/**
|
|
@@ -31,17 +32,18 @@ const MAX_QUESTIONS = 4;
|
|
|
31
32
|
function clampDesc(s) {
|
|
32
33
|
return s.length > DESC_MAX ? `${s.slice(0, DESC_MAX - 1)}…` : s;
|
|
33
34
|
}
|
|
34
|
-
export function AskQuestion({ questions, onAnswer, onCancel, }) {
|
|
35
|
-
// 硬限 4
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
35
|
+
export function AskQuestion({ questions, source, queuedCount = 0, onAnswer, onCancel, }) {
|
|
36
|
+
// 硬限 4 题;超量截断。注记走 useEffect(只发一次):渲染期写 stderr 会随
|
|
37
|
+
// 每次按键重渲重复打行,且 Ink 渲染期间写 stdout/stderr 会撕裂终端画面。
|
|
38
|
+
const effectiveQuestions = useMemo(() => questions.slice(0, MAX_QUESTIONS), [questions]);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (questions.length <= MAX_QUESTIONS)
|
|
41
|
+
return;
|
|
42
|
+
try {
|
|
43
|
+
process.stderr.write(`[AskUserQuestion] 收到 ${questions.length} 题超过上限 ${MAX_QUESTIONS},已截断\n`);
|
|
44
|
+
}
|
|
45
|
+
catch { /* ignore */ }
|
|
46
|
+
}, [questions]);
|
|
45
47
|
// 当前进行到第几题
|
|
46
48
|
const [questionIndex, setQuestionIndex] = useState(0);
|
|
47
49
|
// 当前题高亮的选项下标(含末尾「其他」伪选项)
|
|
@@ -181,14 +183,14 @@ export function AskQuestion({ questions, onAnswer, onCancel, }) {
|
|
|
181
183
|
});
|
|
182
184
|
// 无题可问(空数组)→ 渲染占位(useInput 已处理取消,这里只防御渲染崩)
|
|
183
185
|
if (!current) {
|
|
184
|
-
return (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color:
|
|
186
|
+
return (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color: theme.subtle, children: "\uFF08\u65E0\u95EE\u9898\uFF09" }) }));
|
|
185
187
|
}
|
|
186
188
|
const progress = `提问 (${questionIndex + 1}/${effectiveQuestions.length}) · ${current.header}`;
|
|
187
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color:
|
|
189
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [source?.kind === 'subagent' && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.subtle, dimColor: true, children: `⚡ 子任务:${source.description ?? '执行任务'}(其他子任务继续运行中)` }) })), queuedCount > 0 && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.subtle, dimColor: true, children: `另有 ${queuedCount} 个问题排队` }) })), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.user, bold: true, children: progress }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: current.question }) }), textMode ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.subtle, children: "\u270F\uFE0F \u81EA\u7531\u8F93\u5165\uFF1A" }), _jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: _jsx(Text, { children: draft || ' ' }) }), _jsx(Text, { color: theme.subtle, children: "\u8F93\u5165\u540E Enter \u63D0\u4EA4 \u00B7 ESC \u8FD4\u56DE" })] })) : (_jsxs(Box, { flexDirection: "column", children: [options.map((opt, i) => {
|
|
188
190
|
const active = i === idx;
|
|
189
191
|
const box = multi ? (picked.has(i) ? '[x] ' : '[ ] ') : '';
|
|
190
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: active ?
|
|
191
|
-
}), _jsxs(Text, { color: idx === otherIndex ?
|
|
192
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: active ? theme.user : undefined, children: [active ? '> ' : ' ', box, opt.label] }), opt.description ? (_jsxs(Text, { color: theme.subtle, children: [' ', clampDesc(opt.description)] })) : null] }, `${i}-${opt.label}`));
|
|
193
|
+
}), _jsxs(Text, { color: idx === otherIndex ? theme.user : undefined, children: [idx === otherIndex ? '> ' : ' ', multi ? ' ' : '', "\u270F\uFE0F \u5176\u4ED6\uFF08\u81EA\u7531\u8F93\u5165\uFF09"] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.subtle, children: multi
|
|
192
194
|
? '↑↓ 移动 · 空格 多选 · Enter 确认本题 · ESC 取消'
|
|
193
195
|
: '↑↓ 选择 · Enter 确认 · ESC 取消' }) })] }))] }));
|
|
194
196
|
}
|
package/src/ui/InputBox.js
CHANGED
|
@@ -16,11 +16,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
16
16
|
* - 保留强制刷新但精准控制时机
|
|
17
17
|
* ============================================================
|
|
18
18
|
*/
|
|
19
|
-
import React, { useRef, useState, useCallback } from 'react';
|
|
19
|
+
import React, { useRef, useState, useCallback, useEffect } from 'react';
|
|
20
20
|
import { Box, Text, useApp, useInput } from 'ink';
|
|
21
21
|
import { useTextBuffer } from './hooks/useTextBuffer.js';
|
|
22
22
|
import { usePasteHandler } from './hooks/usePasteHandler.js';
|
|
23
23
|
import { useInputHistory } from './hooks/useInputHistory.js';
|
|
24
|
+
import { theme } from './theme.js';
|
|
24
25
|
// ============================================================
|
|
25
26
|
// IME 启发式(中文输入法防护)
|
|
26
27
|
// ------------------------------------------------------------
|
|
@@ -41,12 +42,19 @@ import { useInputHistory } from './hooks/useInputHistory.js';
|
|
|
41
42
|
const IME_BURST_GAP_MS = 200; // 字符间隔 < 此值视作 IME 连续输入
|
|
42
43
|
const IME_BURST_MIN_COUNT = 3; // 连续 N 个字符才触发 IME 锁定
|
|
43
44
|
const IME_LOCKOUT_MS = 500; // 触发后禁 Enter 提交的窗口
|
|
44
|
-
function InputBoxImpl({ onSubmit, disabled, onAbort, onClear, onCompact }) {
|
|
45
|
+
function InputBoxImpl({ onSubmit, disabled, onAbort, onClear, onCompact, onWorkspaces }) {
|
|
45
46
|
const buf = useTextBuffer();
|
|
46
47
|
const hist = useInputHistory();
|
|
47
48
|
const ctrlCCountRef = useRef(0);
|
|
48
49
|
const ctrlCTimerRef = useRef(null);
|
|
49
50
|
const { exit } = useApp();
|
|
51
|
+
// 卸载清理:双击退出计数窗口的定时器(组件被 AskQuestion/ConfigMenu 顶替时)
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
return () => {
|
|
54
|
+
if (ctrlCTimerRef.current)
|
|
55
|
+
clearTimeout(ctrlCTimerRef.current);
|
|
56
|
+
};
|
|
57
|
+
}, []);
|
|
50
58
|
// 刷新触发器:值本身不读,只用 setter 触发 InputBoxImpl 重渲(→ BufferView 随父重渲)。
|
|
51
59
|
const [, setRefreshCounter] = useState(0);
|
|
52
60
|
// ✅ Ctrl+R 反向搜索模式(null 表示不在搜索)
|
|
@@ -91,6 +99,10 @@ function InputBoxImpl({ onSubmit, disabled, onAbort, onClear, onCompact }) {
|
|
|
91
99
|
multiline: true,
|
|
92
100
|
});
|
|
93
101
|
useInput((input, key) => {
|
|
102
|
+
if (key.ctrl && input === 'p') {
|
|
103
|
+
onWorkspaces();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
94
106
|
// ---- ✅ 粘贴检测(必须优先处理)----
|
|
95
107
|
// 这会在粘贴期间忽略 Enter 键,防止多行内容被截断
|
|
96
108
|
const pasteResult = handlePasteInput(input, key);
|
|
@@ -172,9 +184,9 @@ function InputBoxImpl({ onSubmit, disabled, onAbort, onClear, onCompact }) {
|
|
|
172
184
|
}
|
|
173
185
|
return;
|
|
174
186
|
}
|
|
175
|
-
// ---- ✅ Ctrl+R 进入反向搜索(仅在空 buf
|
|
187
|
+
// ---- ✅ Ctrl+R 进入反向搜索(仅在空 buf)----
|
|
176
188
|
if (key.ctrl && input === 'r') {
|
|
177
|
-
if (
|
|
189
|
+
if (buf.state.text.length === 0) {
|
|
178
190
|
setSearchMode({ query: '', hit: null });
|
|
179
191
|
}
|
|
180
192
|
return;
|
|
@@ -194,6 +206,16 @@ function InputBoxImpl({ onSubmit, disabled, onAbort, onClear, onCompact }) {
|
|
|
194
206
|
catch (e) {
|
|
195
207
|
console.error('[InputBox] onAbort threw:', e);
|
|
196
208
|
}
|
|
209
|
+
// 强制退出安全阀(2026-07-02「Ctrl+C 退不出来」事故):abort 收尾慢
|
|
210
|
+
// (长时子进程/网络挂起)时用户不能被困在程序里——1s 窗口内再按一次
|
|
211
|
+
// Ctrl+C 直接退出(复用下方双击退出的计数/窗口;工作流现场已由状态目录
|
|
212
|
+
// 持久化 halt 语义,强退不丢进度、可 resume)。ESC 保持只 abort 不退出。
|
|
213
|
+
ctrlCCountRef.current++;
|
|
214
|
+
if (ctrlCCountRef.current >= 2)
|
|
215
|
+
exit();
|
|
216
|
+
if (ctrlCTimerRef.current)
|
|
217
|
+
clearTimeout(ctrlCTimerRef.current);
|
|
218
|
+
ctrlCTimerRef.current = setTimeout(() => { ctrlCCountRef.current = 0; }, 1000);
|
|
197
219
|
return;
|
|
198
220
|
}
|
|
199
221
|
ctrlCCountRef.current++;
|
|
@@ -393,19 +415,22 @@ function ReverseSearchView({ query, hit, }) {
|
|
|
393
415
|
: hit !== null && hit.includes('\n')
|
|
394
416
|
? `${hitFirstLine} …`
|
|
395
417
|
: hitFirstLine;
|
|
396
|
-
return (_jsxs(Box, { borderStyle: "round", borderColor: "magenta", paddingX: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color:
|
|
418
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: "magenta", paddingX: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.plugin, bold: true, children: '(reverse-i-search)`' }), _jsx(Text, { color: theme.warning, children: query }), _jsx(Text, { color: theme.plugin, bold: true, children: "': " }), _jsx(Text, { color: hitFirstLine === null ? theme.subtle : 'white', children: hitDisplay })] }), _jsx(Box, { children: _jsx(Text, { color: theme.subtle, dimColor: true, children: "Enter \u63D0\u4EA4 \u00B7 ESC \u53D6\u6D88 \u00B7 Backspace \u5220\u5B57\u7B26" }) })] }));
|
|
397
419
|
}
|
|
398
420
|
function BufferView({ buf, disabled }) {
|
|
399
421
|
// buf.layout 由 useTextBuffer 的 useMemo([state]) 维护:父组件每次重渲传入的 buf
|
|
400
422
|
// 都已带最新 layout,这里直接读即可(BufferView 故意不 memo,随父重渲)。
|
|
401
423
|
const { lines, cursorRow, cursorCol } = buf.layout;
|
|
402
|
-
const promptColor = disabled ?
|
|
403
|
-
const prompt = disabled ? '
|
|
424
|
+
const promptColor = disabled ? theme.warning : theme.user;
|
|
425
|
+
const prompt = disabled ? '📝 ' : '> ';
|
|
426
|
+
const hasContent = lines.some(l => l.length > 0);
|
|
404
427
|
return (_jsx(Box, { borderStyle: "round", borderColor: promptColor, paddingX: 1, flexDirection: "column", children: lines.map((line, row) => {
|
|
405
428
|
const isFirst = row === 0;
|
|
406
|
-
const isCursorRow = row === cursorRow
|
|
429
|
+
const isCursorRow = row === cursorRow;
|
|
407
430
|
// key 只用 row:保持稳定,避免逐行重建(layout 变化由父级重渲带下来)
|
|
408
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: promptColor, bold: true, children: isFirst ? prompt : ' ' }), isCursorRow ? _jsx(CursorLine, { line: line, col: cursorCol }) :
|
|
431
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: promptColor, bold: true, children: isFirst ? prompt : ' ' }), isCursorRow ? (_jsx(CursorLine, { line: line, col: cursorCol })) : disabled && isFirst && !hasContent ? (
|
|
432
|
+
/* 执行中占位提示:告知可继续输入插话 */
|
|
433
|
+
_jsx(Text, { color: theme.subtle, dimColor: true, children: "AI \u5DE5\u4F5C\u4E2D\uFF0C\u53EF\u7EE7\u7EED\u8F93\u5165\u8865\u5145\u6D88\u606F\uFF08Ctrl+C \u4E2D\u65AD\uFF0C\u53CC\u51FB Ctrl+C \u5F3A\u5236\u9000\u51FA\uFF09" })) : (_jsx(Text, { children: line || ' ' }))] }, row));
|
|
409
434
|
}) }));
|
|
410
435
|
}
|
|
411
436
|
/** 光标行渲染(稳定,不会被重建) */
|
package/src/ui/LiveArea.js
CHANGED
|
@@ -11,6 +11,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
11
11
|
* tool_executing → runningTools 逐行人话描述(gray dim)
|
|
12
12
|
* compacting/idle→ 无内容行
|
|
13
13
|
*
|
|
14
|
+
* ★ 暂态错误行(transientError):工作流步执行中 LLM 调用失败(会被该步重试)时,
|
|
15
|
+
* 不把它当永久红字提交进 scrollback,而是作为**状态行下方的一条黄色暂态行**显示。
|
|
16
|
+
* 它随状态行每帧刷新而被覆盖(不 strand),重试成功后由 useChat 丢弃 → 自然消失。
|
|
17
|
+
* 彻底失败(步 ok:false / 顶层 halt)才由 useChat 改走永久红字(chat.error)。
|
|
18
|
+
* 这条行同样走 INV-1 宽度钳制(clampWidthStart),不破坏防刷屏不变量。
|
|
19
|
+
*
|
|
14
20
|
* ★ 为什么这样能既"多行打字机 + 流式答案"又**不刷屏**(详见 liveViewport.ts 头部):
|
|
15
21
|
* - INV-1:每行都用 wrapToDisplayLines / clampWidthStart 按 CJK/emoji=2 钳到
|
|
16
22
|
* ≤ cols - LIVE_RIGHT_MARGIN → 终端永不自动折行 → ink eraseLines 每帧精确。
|
|
@@ -26,6 +32,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
26
32
|
import React, { useEffect, useRef, useState } from 'react';
|
|
27
33
|
import { Box, Text } from 'ink';
|
|
28
34
|
import { LIVE_RIGHT_MARGIN, clampWidthStart, wrapToDisplayLines, } from './liveViewport.js';
|
|
35
|
+
import { theme } from './theme.js';
|
|
36
|
+
import { figures } from './figures.js';
|
|
29
37
|
const STAGE_LABELS = {
|
|
30
38
|
thinking: '思考中',
|
|
31
39
|
streaming: '回答中',
|
|
@@ -37,7 +45,7 @@ const SPINNER_FRAMES = ['✶', '✸', '✹', '✺', '✹', '✷'];
|
|
|
37
45
|
/** spinner 推进 + 强制 re-render 间隔(ms)。~7Hz,远低于 ink 31Hz throttle,Windows 安全。 */
|
|
38
46
|
const SPINNER_INTERVAL_MS = 150;
|
|
39
47
|
/** 内容区行数硬顶(大终端也不铺满,保持清爽)。 */
|
|
40
|
-
const CONTENT_HARD_CAP =
|
|
48
|
+
const CONTENT_HARD_CAP = 16;
|
|
41
49
|
/** 终端高度里预留给 status(1) + 输入框(3) + StatusLine(1) + error/interrupt + 安全余量。 */
|
|
42
50
|
const RESERVED_ROWS = 9;
|
|
43
51
|
/** reasoning 行 💭 前缀 / 续行缩进的预留宽度(💭=2 + 空格;上界 4 更稳)。 */
|
|
@@ -53,28 +61,108 @@ function lastN(arr, n) {
|
|
|
53
61
|
return arr.length <= n ? arr : arr.slice(arr.length - n);
|
|
54
62
|
}
|
|
55
63
|
function toolDesc(t) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
// 长时工具的步内实时进度(LoopEvent.tool_progress)追加在描述后——
|
|
65
|
+
// 比静态 args 信息量大得多(如生图 "[ai-image-plan] round 1/1 15/31 t2i → ai_p11.png")。
|
|
66
|
+
// 宽度钳制由调用方 clampWidthStart 统一负责(INV-1),这里只拼内容。
|
|
67
|
+
const base = t.argsFriendly && t.argsFriendly.length > 0
|
|
68
|
+
? t.argsFriendly
|
|
69
|
+
: `${t.toolName}(${t.argsPreview.length > TOOL_ARGS_MAX
|
|
70
|
+
? `${t.argsPreview.slice(0, TOOL_ARGS_MAX - 1)}…`
|
|
71
|
+
: t.argsPreview})`;
|
|
72
|
+
return t.progress && t.progress.length > 0 ? `${base} · ${t.progress}` : base;
|
|
73
|
+
}
|
|
74
|
+
/** 不带 progress 的静态描述(蜂群树的描述行用;进度另起 ⎿ 行)。 */
|
|
75
|
+
function toolDescStatic(t) {
|
|
76
|
+
return t.argsFriendly && t.argsFriendly.length > 0
|
|
77
|
+
? t.argsFriendly
|
|
78
|
+
: `${t.toolName}(${t.argsPreview.length > TOOL_ARGS_MAX
|
|
79
|
+
? `${t.argsPreview.slice(0, TOOL_ARGS_MAX - 1)}…`
|
|
80
|
+
: t.argsPreview})`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 蜂群树:runningTools 里有 ≥2 个并行的 Agent 子任务时,把 tool_executing
|
|
84
|
+
* 内容区渲染成树形(借鉴 Claude Code 的 AgentProgressLine 语汇):
|
|
85
|
+
*
|
|
86
|
+
* ⚡ 3 个子任务并行中(另有 1 个工具)
|
|
87
|
+
* ├─ 子任务[explore] 检索路由实现
|
|
88
|
+
* │ ⎿ 第 2 轮 · Reading src/router.ts
|
|
89
|
+
* └─ 子任务[explore] 调研配置系统
|
|
90
|
+
* ⎿ 正在撰写报告…(已输出 850 字)
|
|
91
|
+
*
|
|
92
|
+
* - <2 个 Agent → 返回 null,调用方走既有扁平路径(单 Agent 行为不变)。
|
|
93
|
+
* - 行预算:两行模式(描述行 + ⎿ 进度行)放不下 maxLines 时降级单行模式
|
|
94
|
+
* (进度拼在描述后),仍超则 lastN 截尾——INV-2 恒成立。
|
|
95
|
+
* - 每行经 clampWidthStart 钳宽(INV-1;树形字形 ├└│⎿ 已入 charWidth=2 上界)。
|
|
96
|
+
* - Agent 的 progress 行(agent.ts publish 的「label」第 N 轮 · …)剥掉与
|
|
97
|
+
* 描述行重复的「label」前缀,树里不重复念名字。
|
|
98
|
+
* 导出供单测断言(liveArea.test.ts 蜂群树 describe)。
|
|
99
|
+
*/
|
|
100
|
+
export function buildSwarmLines(runningTools, maxWidth, maxLines) {
|
|
101
|
+
const agents = runningTools.filter((t) => t.toolName === 'Agent');
|
|
102
|
+
if (agents.length < 2)
|
|
103
|
+
return null;
|
|
104
|
+
const others = runningTools.filter((t) => t.toolName !== 'Agent');
|
|
105
|
+
const queued = agents.filter((agent) => agent.progressDetail?.kind === 'subagent' && agent.progressDetail.phase === 'queued').length;
|
|
106
|
+
const activeCount = agents.length - queued;
|
|
107
|
+
const header = `${figures.swarm} ${activeCount} 运行${queued > 0 ? ` · ${queued} 排队` : ''}${others.length > 0 ? `(另有 ${others.length} 个工具)` : ''}`;
|
|
108
|
+
// 进度文案剥冗余 label 前缀(描述行已含「子任务[type] label」)
|
|
109
|
+
const stripLabel = (p) => p.replace(/^「[^」]*」\s*/, '');
|
|
110
|
+
const twoLine = 1 + agents.length * 2 + others.length <= maxLines;
|
|
111
|
+
const lines = [header];
|
|
112
|
+
agents.forEach((t, i) => {
|
|
113
|
+
const isLast = i === agents.length - 1 && others.length === 0;
|
|
114
|
+
const branch = isLast ? figures.corner : figures.branch;
|
|
115
|
+
const progress = t.progress ? stripLabel(t.progress) : '';
|
|
116
|
+
if (twoLine) {
|
|
117
|
+
lines.push(`${branch} ${toolDescStatic(t)}`);
|
|
118
|
+
lines.push(`${isLast ? ' ' : `${figures.pipe} `}${figures.elbow} ${progress || '启动中…'}`);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
lines.push(`${branch} ${toolDescStatic(t)}${progress ? ` · ${progress}` : ''}`);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
for (const t of others)
|
|
125
|
+
lines.push(` ${toolDesc(t)}`);
|
|
126
|
+
return lastN(lines, maxLines).map((l) => clampWidthStart(l, maxWidth));
|
|
62
127
|
}
|
|
63
128
|
/**
|
|
64
129
|
* 纯函数:把一帧的 props(+ 动画 frame / 已耗时秒数)算成 LiveView。
|
|
65
|
-
* idle
|
|
130
|
+
* idle 且无暂态错误时返回 null(live 区隐藏);idle 但有暂态错误时仅渲染该黄色行
|
|
131
|
+
* (让"重试中/已恢复"提示在步间隙 stage 短暂回 idle 时也不闪没)。
|
|
132
|
+
* 导出供单测断言 INV-1(每行宽)/ INV-2(行数)/ 颜色层次。
|
|
66
133
|
*/
|
|
67
134
|
export function buildLiveView(props) {
|
|
68
|
-
const { progressStage, reasoningPreview, streamingText, runningTools, pluginProgress, terminalCols, terminalRows, frame, elapsedSec, } = props;
|
|
69
|
-
if (progressStage === 'idle')
|
|
70
|
-
return null;
|
|
71
|
-
const stage = progressStage;
|
|
135
|
+
const { progressStage, reasoningPreview, streamingText, runningTools, pluginProgress, transientError, terminalCols, terminalRows, frame, elapsedSec, } = props;
|
|
72
136
|
const maxWidth = Math.max(8, terminalCols - LIVE_RIGHT_MARGIN);
|
|
73
137
|
const maxLines = contentMaxLines(terminalRows);
|
|
138
|
+
// 暂态错误行:单行、钳宽(满足 INV-1)。空串视为无。
|
|
139
|
+
const transientErrorLine = transientError && transientError.length > 0
|
|
140
|
+
? clampWidthStart(transientError, maxWidth)
|
|
141
|
+
: null;
|
|
142
|
+
// idle 且无暂态错误 → 整个 live 区隐藏(保持既有行为)。
|
|
143
|
+
// idle 但有暂态错误 → 只渲染那一行黄字,无状态行 / 无 spinner(避免 idle 还转圈)。
|
|
144
|
+
if (progressStage === 'idle') {
|
|
145
|
+
if (transientErrorLine === null)
|
|
146
|
+
return null;
|
|
147
|
+
return {
|
|
148
|
+
statusLine: '',
|
|
149
|
+
statusColor: theme.user,
|
|
150
|
+
contentLines: [],
|
|
151
|
+
contentDim: true,
|
|
152
|
+
transientErrorLine,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const stage = progressStage;
|
|
74
156
|
// ---- 状态行 ----
|
|
75
157
|
let status = `${frame} ${STAGE_LABELS[stage]} (${elapsedSec}s)`;
|
|
76
158
|
if (pluginProgress) {
|
|
77
|
-
|
|
159
|
+
// current 缺省 = 纯文案进度(工作流步骤 title),不渲染数字
|
|
160
|
+
const num = pluginProgress.current !== undefined
|
|
161
|
+
? `${pluginProgress.current}/${pluginProgress.max ?? '?'}`
|
|
162
|
+
: '';
|
|
163
|
+
const msg = pluginProgress.message ?? '';
|
|
164
|
+
const body = [num, msg].filter(Boolean).join(' ');
|
|
165
|
+
status += ` · [plugin:${pluginProgress.pluginId}]${body ? ` ${body}` : ''}`;
|
|
78
166
|
}
|
|
79
167
|
status = clampWidthStart(status, maxWidth);
|
|
80
168
|
// ---- 内容区 ----
|
|
@@ -82,7 +170,7 @@ export function buildLiveView(props) {
|
|
|
82
170
|
let contentDim = true;
|
|
83
171
|
if (stage === 'thinking' && reasoningPreview.length > 0) {
|
|
84
172
|
const wrapped = wrapToDisplayLines(reasoningPreview, maxWidth - REASONING_PREFIX_RESERVE);
|
|
85
|
-
contentLines = lastN(wrapped, maxLines).map((line, i) => i === 0 ?
|
|
173
|
+
contentLines = lastN(wrapped, maxLines).map((line, i) => i === 0 ? `${figures.thought} ${line}` : ` ${line}`);
|
|
86
174
|
contentDim = true;
|
|
87
175
|
}
|
|
88
176
|
else if (stage === 'streaming' && streamingText.length > 0) {
|
|
@@ -90,14 +178,19 @@ export function buildLiveView(props) {
|
|
|
90
178
|
contentDim = false; // 答案 = 主角,默认亮色
|
|
91
179
|
}
|
|
92
180
|
else if (stage === 'tool_executing' && runningTools.length > 0) {
|
|
93
|
-
|
|
181
|
+
// ≥2 个并行 Agent 子任务 → 蜂群树;否则既有扁平路径(单工具/单 Agent 不变)
|
|
182
|
+
const swarm = buildSwarmLines(runningTools, maxWidth, maxLines);
|
|
183
|
+
contentLines =
|
|
184
|
+
swarm ??
|
|
185
|
+
lastN(runningTools, maxLines).map((t) => clampWidthStart(` ${toolDesc(t)}`, maxWidth));
|
|
94
186
|
contentDim = true;
|
|
95
187
|
}
|
|
96
188
|
return {
|
|
97
189
|
statusLine: status,
|
|
98
|
-
statusColor: stage === 'compacting' ?
|
|
190
|
+
statusColor: stage === 'compacting' ? theme.plugin : theme.user,
|
|
99
191
|
contentLines,
|
|
100
192
|
contentDim,
|
|
193
|
+
transientErrorLine,
|
|
101
194
|
};
|
|
102
195
|
}
|
|
103
196
|
export const LiveArea = React.memo(function LiveArea(props) {
|
|
@@ -118,8 +211,8 @@ export const LiveArea = React.memo(function LiveArea(props) {
|
|
|
118
211
|
const timer = setInterval(() => setTick((t) => (t + 1) % 1_000_000), SPINNER_INTERVAL_MS);
|
|
119
212
|
return () => clearInterval(timer);
|
|
120
213
|
}, [progressStage]);
|
|
121
|
-
|
|
122
|
-
|
|
214
|
+
// 不在 idle 直接 return:idle 但存在暂态错误时 buildLiveView 仍会返回一个
|
|
215
|
+
// 只含 transientErrorLine 的 view(让"已恢复/重试中"提示在步间隙也不闪没)。
|
|
123
216
|
const now = Date.now();
|
|
124
217
|
const elapsedSec = startRef.current
|
|
125
218
|
? Math.max(0, Math.floor((now - startRef.current) / 1000))
|
|
@@ -128,5 +221,5 @@ export const LiveArea = React.memo(function LiveArea(props) {
|
|
|
128
221
|
const view = buildLiveView({ ...props, frame, elapsedSec });
|
|
129
222
|
if (!view)
|
|
130
223
|
return null;
|
|
131
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: view.statusColor, bold: true, wrap: "truncate", children: view.statusLine }), view.contentLines.map((line, i) => (_jsx(Text, { color: view.contentDim ?
|
|
224
|
+
return (_jsxs(Box, { flexDirection: "column", children: [view.statusLine.length > 0 && (_jsx(Text, { color: view.statusColor, bold: true, wrap: "truncate", children: view.statusLine })), view.contentLines.map((line, i) => (_jsx(Text, { color: view.contentDim ? theme.subtle : theme.answer, dimColor: view.contentDim, wrap: "truncate", children: line.length > 0 ? line : ' ' }, `live-${i}`))), view.transientErrorLine !== null && (_jsx(Text, { color: theme.warning, wrap: "truncate", children: view.transientErrorLine }))] }));
|
|
132
225
|
});
|
package/src/ui/MessageList.js
CHANGED
|
@@ -34,6 +34,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
34
34
|
*/
|
|
35
35
|
import React from 'react';
|
|
36
36
|
import { Box, Static, Text } from 'ink';
|
|
37
|
+
import { friendlyToolDescription } from '../loop.js';
|
|
38
|
+
import { contentToDisplayText, contentToPlainText } from '../multimodal.js';
|
|
39
|
+
import { splitThinkBlocks } from '../utils/stripThink.js';
|
|
40
|
+
import { figures } from './figures.js';
|
|
41
|
+
import { renderMarkdownLines } from './markdown.js';
|
|
42
|
+
import { theme } from './theme.js';
|
|
37
43
|
/** 中断标记 user message 的固定内容(loop.ts 写入) */
|
|
38
44
|
const INTERRUPT_USER_CONTENT = '(用户按下了 ESC/Ctrl+C 中断了任务)';
|
|
39
45
|
const MAX_TOOL_PREVIEW_LINES = 3;
|
|
@@ -41,6 +47,58 @@ const MAX_TOOL_PREVIEW_LINES = 3;
|
|
|
41
47
|
const NARROW_TERMINAL_ROWS = 28;
|
|
42
48
|
/** reasoning 超过此字符数截尾 —— 防撑爆历史 */
|
|
43
49
|
const REASONING_MAX_CHARS = 2000;
|
|
50
|
+
/**
|
|
51
|
+
* 把 assistant 最终正文拆成展示行:markdown 管线(src/ui/markdown.ts)产出
|
|
52
|
+
* prose/code/fence 三类行,⏺ 锚点落在**第一条非空 prose 行**——答案以代码块
|
|
53
|
+
* 开头时代码顶格在前、⏺ 顺延;全代码答案无 ⏺(代码纯净优先)。
|
|
54
|
+
* 纯文本无 markdown 特征时走快路径,与旧「按 \n 切 + wrap='end'」逐行一致。
|
|
55
|
+
*/
|
|
56
|
+
export function buildAssistantDisplayLines(text) {
|
|
57
|
+
if (text.length === 0)
|
|
58
|
+
return [];
|
|
59
|
+
let anchorPlaced = false;
|
|
60
|
+
return renderMarkdownLines(text).map((line) => {
|
|
61
|
+
if (line.kind !== 'prose') {
|
|
62
|
+
return { content: line.text, prefix: '', kind: line.kind, wrap: 'end' };
|
|
63
|
+
}
|
|
64
|
+
if (!anchorPlaced && line.text.trim() !== '') {
|
|
65
|
+
anchorPlaced = true;
|
|
66
|
+
return { content: line.text, prefix: '⏺ ', kind: line.kind, wrap: 'end' };
|
|
67
|
+
}
|
|
68
|
+
return { content: line.text, prefix: ' ', kind: line.kind, wrap: 'end' };
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 扫 history 里全部 assistant.tool_calls,建 tool_call_id → 人话描述 索引。
|
|
73
|
+
* tool 消息本身只有 content + tool_call_id(不动 history 字节的前提下,
|
|
74
|
+
* 头行要显示「哪个工具在干什么」只能反查发起方)。<Static> 提交时序保证
|
|
75
|
+
* assistant(tool_calls) 恒先于对应 tool 消息入 history,索引命中无竞态。
|
|
76
|
+
*/
|
|
77
|
+
export function buildToolCallIndex(history) {
|
|
78
|
+
const map = new Map();
|
|
79
|
+
for (const m of history) {
|
|
80
|
+
if (m.role !== 'assistant' || !m.tool_calls)
|
|
81
|
+
continue;
|
|
82
|
+
for (const tc of m.tool_calls) {
|
|
83
|
+
map.set(tc.id, {
|
|
84
|
+
toolName: tc.function.name,
|
|
85
|
+
friendly: friendlyToolDescription(tc.function.name, tc.function.arguments),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return map;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 从 Agent 子任务报告的统计尾巴(agent.ts 固定格式
|
|
93
|
+
* 「(子任务「x」[type]:N 轮 · M 次工具调用,K 次失败)」)解析统计数。
|
|
94
|
+
* 失败结果(错误文案)不匹配 → null,头行只显示描述不显示 ✓ 统计。
|
|
95
|
+
*/
|
|
96
|
+
export function agentResultStats(content) {
|
|
97
|
+
const m = /(子任务「[^」]*」\[[^\]]+\]:(\d+) 轮 · (\d+) 次工具调用(?:,(\d+) 次失败)?)/.exec(content);
|
|
98
|
+
if (!m)
|
|
99
|
+
return null;
|
|
100
|
+
return { turns: Number(m[1]), tools: Number(m[2]), failed: m[3] ? Number(m[3]) : 0 };
|
|
101
|
+
}
|
|
44
102
|
/**
|
|
45
103
|
* 把 assistant message 的两个字符串型 reasoning 字段拼成单字符串。
|
|
46
104
|
* `reasoning_details` 是结构化数组,本期不展开。两个字段任一非空即返回拼接结果;
|
|
@@ -80,7 +138,8 @@ export function summarizeTurn(history) {
|
|
|
80
138
|
const turnStartIndices = [];
|
|
81
139
|
for (let i = 0; i < history.length; i++) {
|
|
82
140
|
const m = history[i];
|
|
83
|
-
if (m.role === 'user' &&
|
|
141
|
+
if (m.role === 'user' &&
|
|
142
|
+
contentToPlainText(m.content) !== INTERRUPT_USER_CONTENT) {
|
|
84
143
|
turnStartIndices.push(i);
|
|
85
144
|
}
|
|
86
145
|
}
|
|
@@ -144,6 +203,7 @@ function formatDuration(ms) {
|
|
|
144
203
|
export const MessageList = React.memo(function MessageList({ history, compactGeneration, bannerText, terminalRows, }) {
|
|
145
204
|
const items = React.useMemo(() => {
|
|
146
205
|
const turnMetas = summarizeTurn(history);
|
|
206
|
+
const toolIndex = buildToolCallIndex(history);
|
|
147
207
|
return [
|
|
148
208
|
...(bannerText
|
|
149
209
|
? [
|
|
@@ -158,6 +218,7 @@ export const MessageList = React.memo(function MessageList({ history, compactGen
|
|
|
158
218
|
kind: 'message',
|
|
159
219
|
msg: m,
|
|
160
220
|
turnMeta: turnMetas[idx],
|
|
221
|
+
toolMeta: m.role === 'tool' ? (toolIndex.get(m.tool_call_id) ?? null) : null,
|
|
161
222
|
// 纯派生 key:有 id 用 id,没有就用 idx 兜底。绝不在 render 期 mutate 消息对象
|
|
162
223
|
// (<Static> 已 commit 的行永不重绘,key 仅在 append 对账时用,idx 已足够稳定)。
|
|
163
224
|
key: m.id ?? `message-${idx}`,
|
|
@@ -170,7 +231,7 @@ export const MessageList = React.memo(function MessageList({ history, compactGen
|
|
|
170
231
|
if (item.kind === 'banner') {
|
|
171
232
|
return _jsx(BannerRow, { text: item.text }, item.key);
|
|
172
233
|
}
|
|
173
|
-
return (_jsx(MessageRow, { message: item.msg, turnMeta: item.turnMeta, foldReasoning: foldReasoning }, item.key));
|
|
234
|
+
return (_jsx(MessageRow, { message: item.msg, turnMeta: item.turnMeta, toolMeta: item.toolMeta, foldReasoning: foldReasoning }, item.key));
|
|
174
235
|
} }, compactGeneration));
|
|
175
236
|
});
|
|
176
237
|
function BannerRow({ text }) {
|
|
@@ -179,9 +240,9 @@ function BannerRow({ text }) {
|
|
|
179
240
|
const splitAt = text.indexOf(' ·');
|
|
180
241
|
const head = splitAt >= 0 ? text.slice(0, splitAt) : text;
|
|
181
242
|
const tail = splitAt >= 0 ? text.slice(splitAt) : '';
|
|
182
|
-
return (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color:
|
|
243
|
+
return (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: theme.user, bold: true, children: head }), tail.length > 0 && _jsx(Text, { color: theme.subtle, children: tail })] }));
|
|
183
244
|
}
|
|
184
|
-
function MessageRow({ message, turnMeta, foldReasoning, }) {
|
|
245
|
+
function MessageRow({ message, turnMeta, toolMeta, foldReasoning, }) {
|
|
185
246
|
switch (message.role) {
|
|
186
247
|
case 'system': {
|
|
187
248
|
// v2.x:ephemeral system message 是 UI 临时提示(slash 命令输出等),
|
|
@@ -190,20 +251,27 @@ function MessageRow({ message, turnMeta, foldReasoning, }) {
|
|
|
190
251
|
if (!ephemeral)
|
|
191
252
|
return null;
|
|
192
253
|
const lines = message.content.split('\n');
|
|
193
|
-
return (_jsx(Box, { marginBottom: 1, paddingLeft: 2, children: _jsx(Box, { flexDirection: "column", children: lines.map((line, i) => (_jsx(Text, { color:
|
|
254
|
+
return (_jsx(Box, { marginBottom: 1, paddingLeft: 2, children: _jsx(Box, { flexDirection: "column", children: lines.map((line, i) => (_jsx(Text, { color: theme.subtle, dimColor: true, wrap: "end", children: line }, `eph-${i}`))) }) }));
|
|
194
255
|
}
|
|
195
256
|
case 'user': {
|
|
257
|
+
const rule = figures.hr.repeat(6);
|
|
196
258
|
const headerLine = turnMeta !== null
|
|
197
|
-
?
|
|
259
|
+
? `${rule} Turn ${turnMeta.turnN} · ${formatTime(turnMeta.timestamp)} · ${formatDuration(turnMeta.durationMs)} · ${turnMeta.toolsCount} tools ${rule}`
|
|
198
260
|
: null;
|
|
199
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [headerLine !== null && (_jsx(Text, { color:
|
|
261
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [headerLine !== null && (_jsx(Text, { color: theme.subtle, dimColor: true, children: headerLine })), _jsxs(Box, { children: [_jsx(Text, { color: theme.user, bold: true, children: '> ' }), _jsx(Text, { color: theme.user, wrap: "end", children: contentToDisplayText(message.content) })] })] }));
|
|
200
262
|
}
|
|
201
263
|
case 'assistant': {
|
|
202
264
|
// assistant 可能只有 tool_calls 没有 content(content 为 null)
|
|
203
|
-
const
|
|
265
|
+
const raw = message.content ?? '';
|
|
266
|
+
// 展示层切分内联 <think>(MiniMax 等把思维链写进 content,内核有意不剥):
|
|
267
|
+
// thought 并入 💭 块(灰 dim + 折叠,与结构化 reasoning 同款待遇),
|
|
268
|
+
// 正文只显示净化后的部分。纯展示层——history 字节零改动(KV 缓存约束)。
|
|
269
|
+
const { thought, answer } = splitThinkBlocks(raw);
|
|
204
270
|
const tcCount = message.tool_calls?.length ?? 0;
|
|
205
|
-
// reasoning
|
|
206
|
-
let reasoningText = combineReasoning(message)
|
|
271
|
+
// reasoning 拼接:结构化字段在前,内联 <think> 在后
|
|
272
|
+
let reasoningText = [combineReasoning(message), thought]
|
|
273
|
+
.filter(Boolean)
|
|
274
|
+
.join('\n');
|
|
207
275
|
let truncatedCharsHint = '';
|
|
208
276
|
if (reasoningText.length > REASONING_MAX_CHARS) {
|
|
209
277
|
const omitted = reasoningText.length - REASONING_MAX_CHARS;
|
|
@@ -215,17 +283,35 @@ function MessageRow({ message, turnMeta, foldReasoning, }) {
|
|
|
215
283
|
? reasoningLines.slice(0, 2)
|
|
216
284
|
: reasoningLines;
|
|
217
285
|
const hiddenReasoningCount = reasoningLines.length - visibleReasoningLines.length;
|
|
218
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [visibleReasoningLines.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [visibleReasoningLines.map((line, i) => (_jsx(Text, { color:
|
|
219
|
-
|
|
286
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [visibleReasoningLines.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [visibleReasoningLines.map((line, i) => (_jsx(Text, { color: theme.subtle, dimColor: true, wrap: "end", children: i === 0 ? `${figures.thought} ${line}` : ` ${line}` }, `r-${i}`))), hiddenReasoningCount > 0 && (_jsx(Text, { color: theme.subtle, dimColor: true, children: ` ${figures.fold} ${hiddenReasoningCount} 行 reasoning 折叠(终端高度 < ${NARROW_TERMINAL_ROWS})` })), truncatedCharsHint.length > 0 && (_jsx(Text, { color: theme.subtle, dimColor: true, children: ` ${truncatedCharsHint}` }))] })), buildAssistantDisplayLines(answer).map((line, i) => {
|
|
287
|
+
if (line.kind === 'fence') {
|
|
288
|
+
return (_jsx(Text, { color: theme.subtle, dimColor: true, wrap: line.wrap, children: line.content }, `a-${i}`));
|
|
289
|
+
}
|
|
290
|
+
if (line.kind === 'code') {
|
|
291
|
+
return (_jsx(Text, { wrap: line.wrap, children: line.content.length > 0 ? line.content : ' ' }, `a-${i}`));
|
|
292
|
+
}
|
|
293
|
+
return line.prefix === '⏺ ' ? (_jsxs(Box, { children: [_jsx(Text, { color: theme.anchor, bold: true, wrap: line.wrap, children: line.prefix }), _jsx(Text, { wrap: line.wrap, children: line.content.length > 0 ? line.content : ' ' })] }, `a-${i}`)) : (_jsx(Text, { wrap: line.wrap, children: `${line.prefix}${line.content}`.length > 0
|
|
294
|
+
? `${line.prefix}${line.content}`
|
|
295
|
+
: ' ' }, `a-${i}`));
|
|
296
|
+
}), tcCount > 0 && (_jsx(Text, { color: theme.warning, dimColor: true, wrap: "end", children: ` ${figures.corner} 调用了 ${tcCount} 个工具:${(message.tool_calls ?? [])
|
|
220
297
|
.map((tc) => tc.function.name)
|
|
221
298
|
.join(', ')}` }))] }));
|
|
222
299
|
}
|
|
223
300
|
case 'tool': {
|
|
224
301
|
const lines = message.content.split('\n');
|
|
225
|
-
const
|
|
302
|
+
const previewLines = lines.slice(0, MAX_TOOL_PREVIEW_LINES);
|
|
226
303
|
const hidden = Math.max(0, lines.length - MAX_TOOL_PREVIEW_LINES);
|
|
227
304
|
const shortId = message.tool_call_id.slice(-8);
|
|
228
|
-
|
|
305
|
+
// 头行升级:有 toolMeta(发起方反查命中)显示人话描述;Agent 子任务
|
|
306
|
+
// 再解析统计尾巴显示 ✓ N 轮 · M 次工具。无 meta(历史重载等)兜底旧格式。
|
|
307
|
+
const stats = toolMeta?.toolName === 'Agent' ? agentResultStats(message.content) : null;
|
|
308
|
+
const statsTail = stats
|
|
309
|
+
? ` ${figures.ok} ${stats.turns} 轮 · ${stats.tools} 次工具${stats.failed > 0 ? `(${stats.failed} 次失败)` : ''}`
|
|
310
|
+
: '';
|
|
311
|
+
const headText = toolMeta
|
|
312
|
+
? `[${toolMeta.friendly} · #${shortId}]${statsTail}`
|
|
313
|
+
: `[tool result · #${shortId}]`;
|
|
314
|
+
return (_jsx(Box, { marginBottom: 1, paddingLeft: 2, children: _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.subtle, dimColor: true, wrap: "end", children: headText }), previewLines.map((line, i) => (_jsx(Text, { color: theme.subtle, wrap: "end", children: line.length > 0 ? line : ' ' }, `p-${i}`))), hidden > 0 && (_jsx(Text, { color: theme.subtle, dimColor: true, children: `... ${figures.fold} 完整 ${lines.length} 行(输入 /show ${shortId} 查看)` }))] }) }));
|
|
229
315
|
}
|
|
230
316
|
}
|
|
231
317
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { theme } from './theme.js';
|
|
5
|
+
export function ModelPicker({ models, currentModelId, title, nullOptionLabel, onSelect, onCancel, }) {
|
|
6
|
+
const enabledModels = models.filter((m) => m.enabled && m.apiKey);
|
|
7
|
+
const hasNullOption = Boolean(nullOptionLabel);
|
|
8
|
+
const nullIndex = hasNullOption ? enabledModels.length : -1;
|
|
9
|
+
const listLength = enabledModels.length + (hasNullOption ? 1 : 0);
|
|
10
|
+
const initialIdx = (() => {
|
|
11
|
+
const idx = enabledModels.findIndex((m) => m.id === currentModelId);
|
|
12
|
+
if (idx >= 0)
|
|
13
|
+
return idx;
|
|
14
|
+
if (hasNullOption && !currentModelId)
|
|
15
|
+
return nullIndex;
|
|
16
|
+
return 0;
|
|
17
|
+
})();
|
|
18
|
+
const [idx, setIdx] = useState(Math.max(0, Math.min(initialIdx, listLength - 1)));
|
|
19
|
+
useInput((input, key) => {
|
|
20
|
+
if (key.escape || (key.ctrl && input === 'c')) {
|
|
21
|
+
onCancel();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (key.upArrow) {
|
|
25
|
+
setIdx((i) => (i - 1 + listLength) % listLength);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (key.downArrow) {
|
|
29
|
+
setIdx((i) => (i + 1) % listLength);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (key.return) {
|
|
33
|
+
if (hasNullOption && idx === nullIndex) {
|
|
34
|
+
onSelect(null);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const target = enabledModels[idx];
|
|
38
|
+
if (target)
|
|
39
|
+
onSelect(target.id);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.user, bold: true, children: title }) }), _jsx(Text, { color: theme.subtle, children: "\u5728\u5DF2\u914D\u7F6E\u7684\u6A21\u578B\u4E2D\u9009\u62E9\uFF1B\u9009\u62E9\u540E\u7ACB\u5373\u751F\u6548\uFF0C\u540E\u7EED\u8BF7\u6C42\u4F7F\u7528\u65B0\u6A21\u578B\u3002" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [enabledModels.map((m, i) => {
|
|
44
|
+
const active = i === idx;
|
|
45
|
+
const isCurrent = m.id === currentModelId;
|
|
46
|
+
const displayName = m.displayName || m.model;
|
|
47
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: active ? theme.user : undefined, children: [active ? '> ' : ' ', isCurrent ? '✓ ' : ' ', _jsx(Text, { color: active ? theme.user : undefined, bold: isCurrent, children: displayName }), isCurrent ? _jsx(Text, { color: theme.subtle, children: " \uFF08\u5F53\u524D\uFF09" }) : null] }), _jsxs(Text, { color: theme.subtle, children: [' ', m.model, " \u00B7 ", formatContextWindow(m.contextWindow)] })] }, m.id));
|
|
48
|
+
}), hasNullOption && (_jsxs(Text, { color: idx === nullIndex ? theme.user : theme.subtle, children: [idx === nullIndex ? '> ' : ' ', !currentModelId ? '✓ ' : ' ', nullOptionLabel] }))] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.subtle, children: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 ESC \u53D6\u6D88" }) })] }));
|
|
49
|
+
}
|
|
50
|
+
function formatContextWindow(tokens) {
|
|
51
|
+
if (tokens >= 1_000_000)
|
|
52
|
+
return `${(tokens / 1_000_000).toFixed(1)}M context`;
|
|
53
|
+
if (tokens >= 1_000)
|
|
54
|
+
return `${Math.round(tokens / 1_000)}K context`;
|
|
55
|
+
return `${tokens} context`;
|
|
56
|
+
}
|