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
|
@@ -13,27 +13,29 @@
|
|
|
13
13
|
* - tool / llm / assert —— 原子动作(execXxxStep 返回 StepResult)
|
|
14
14
|
* - agent / skill —— 有界 TAOR:runQuery(agent) / runQuery(skill);
|
|
15
15
|
* 透传子事件(stage_change/text/reasoning/tool_*)
|
|
16
|
-
* - branch / loop / pause
|
|
17
|
-
* -
|
|
16
|
+
* - branch / loop / pause / echo —— 控制流,直接在 runSteps 内处理(不走 executor)
|
|
17
|
+
* - break / continue —— 循环信号(可带 target 穿透到指定外层循环)
|
|
18
18
|
*
|
|
19
19
|
* 控制流嵌套通过 runSteps 递归实现:branch.then/else / loop.do 子数组
|
|
20
20
|
* 是 StepDef[],调用方语义与顶层 def.steps 一致;变量栈用 VarStack.push/pop
|
|
21
21
|
* 在 loop 迭代间隔离 `${as}` / `${as}_idx`。
|
|
22
22
|
* ============================================================
|
|
23
23
|
*/
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
24
|
+
import { existsSync } from 'node:fs';
|
|
25
|
+
import { resolve } from 'node:path';
|
|
26
|
+
import { dlog, executeTool, getWorkingDir } from '../../../src/plugin-sdk.js';
|
|
27
|
+
import { isMaxTurnsCutoff, truncate } from './agentCommon.js';
|
|
28
|
+
import { evalExpr, interpolate, interpolateDeep, interpolateTyped } from './expressions.js';
|
|
27
29
|
import { runRecovery } from './recovery.js';
|
|
30
|
+
import { buildTaskArbitrator, isContinuationPhrase } from './taskIdentity.js';
|
|
28
31
|
import { execAgentStep } from './stepExecutors/agent.js';
|
|
29
32
|
import { execAssertStep } from './stepExecutors/assert.js';
|
|
30
33
|
import { execVerifyStep } from './stepExecutors/verify.js';
|
|
31
34
|
import { execLlmStep } from './stepExecutors/llm.js';
|
|
32
35
|
import { execSkillStep } from './stepExecutors/skill.js';
|
|
33
36
|
import { execToolStep } from './stepExecutors/tool.js';
|
|
34
|
-
import { VarStack } from './types.js';
|
|
35
|
-
import {
|
|
36
|
-
import { WorkflowState } from './workflowState.js';
|
|
37
|
+
import { normalizeCapture, VarStack } from './types.js';
|
|
38
|
+
import { loopCheckpointName, WorkflowState, } from './workflowState.js';
|
|
37
39
|
/**
|
|
38
40
|
* 插件 ID:把私有 workflow_* 事件镜像成框架通用 plugin_progress 时挂这个 id。
|
|
39
41
|
* LiveArea 状态行会渲染成 "[plugin:workflow-runner] i/total <message>"
|
|
@@ -75,11 +77,40 @@ function buildGoalAnchor(inputDefs, inputValues) {
|
|
|
75
77
|
}
|
|
76
78
|
if (!goalValue)
|
|
77
79
|
return '';
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return
|
|
80
|
+
return `> 🎯 用户原始目标:${truncateGoal(goalValue)}\n> ⚠️ 所有操作必须直接回应此目标,不可偏离或遗忘。`;
|
|
81
|
+
}
|
|
82
|
+
/** 截断过长目标(>200 字符时保留首尾,中间用 … 连接)——goalAnchor 注入每步 prompt,必须紧凑。 */
|
|
83
|
+
function truncateGoal(goal) {
|
|
84
|
+
return goal.length > 200 ? `${goal.slice(0, 120)}…${goal.slice(-60)}` : goal;
|
|
85
|
+
}
|
|
86
|
+
/** 可中断的退避等待:用户 abort 时立即返回(让后续步骤检查 signal 收口,不在这里抛)。 */
|
|
87
|
+
function sleepInterruptible(ms, signal) {
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
if (signal?.aborted) {
|
|
90
|
+
resolve();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const timer = setTimeout(() => {
|
|
94
|
+
signal?.removeEventListener('abort', onAbort);
|
|
95
|
+
resolve();
|
|
96
|
+
}, ms);
|
|
97
|
+
const onAbort = () => {
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
resolve();
|
|
100
|
+
};
|
|
101
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/** 把一段用户输入并入贯穿目标(内存 + 状态目录持久化,resume 不丢)。 */
|
|
105
|
+
async function addGoalSupplement(ctx, state, text) {
|
|
106
|
+
const t = text.trim();
|
|
107
|
+
if (!t)
|
|
108
|
+
return;
|
|
109
|
+
// 锚定必须紧凑(注入每步 prompt),逐条截断;完整原文在 capture 变量 / history 里
|
|
110
|
+
const compact = t.length > 240 ? `${t.slice(0, 240)}…` : t;
|
|
111
|
+
ctx.goalSupplements ??= [];
|
|
112
|
+
ctx.goalSupplements.push(compact);
|
|
113
|
+
await state.appendSupplement(compact);
|
|
83
114
|
}
|
|
84
115
|
function stepKind(step) {
|
|
85
116
|
if (step.type === 'break')
|
|
@@ -107,16 +138,37 @@ function readPath(root, path) {
|
|
|
107
138
|
return cur;
|
|
108
139
|
}
|
|
109
140
|
function bindCapture(captureMap, result, vars) {
|
|
110
|
-
for (const [resultField, varName] of Object.entries(captureMap)) {
|
|
141
|
+
for (const [resultField, varName] of Object.entries(normalizeCapture(captureMap))) {
|
|
111
142
|
const value = resultField.includes('.')
|
|
112
143
|
? readPath(result.raw, resultField)
|
|
113
144
|
: result.raw[resultField];
|
|
114
145
|
vars.set(varName, value);
|
|
115
146
|
}
|
|
116
147
|
}
|
|
148
|
+
/** 取本步 capture 出的 { varName: value },供 checkpoint 记录(resume 回放用)。 */
|
|
149
|
+
function collectCaptured(captureMap, vars) {
|
|
150
|
+
const out = {};
|
|
151
|
+
for (const varName of Object.values(normalizeCapture(captureMap))) {
|
|
152
|
+
out[varName] = vars.get(varName);
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
function resolvePositiveIterationLimit(raw, vars, defaultValue, label) {
|
|
157
|
+
if (raw === undefined)
|
|
158
|
+
return { ok: true, value: defaultValue };
|
|
159
|
+
const interpolated = typeof raw === 'number' ? raw : interpolate(String(raw), vars);
|
|
160
|
+
const n = typeof raw === 'number' ? raw : Number(String(interpolated).trim());
|
|
161
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
error: `${label} max_iterations parse failed: ${JSON.stringify(raw)} -> "${String(interpolated)}" is not a positive number`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return { ok: true, value: Math.floor(n) };
|
|
168
|
+
}
|
|
117
169
|
/**
|
|
118
170
|
* 分发"动作"型 step(tool / agent / llm / skill / assert)到对应 executor。
|
|
119
|
-
* 控制流 step(branch / loop / pause
|
|
171
|
+
* 控制流 step(branch / loop / pause 等)由 runSteps 直接处理,不走这里。
|
|
120
172
|
*/
|
|
121
173
|
async function* dispatchActionStep(step, vars, ctx) {
|
|
122
174
|
if (step.type === 'assert')
|
|
@@ -134,17 +186,34 @@ async function* dispatchActionStep(step, vars, ctx) {
|
|
|
134
186
|
throw new Error(`step ${step.id}: 无可执行字段(tool/agent/llm/skill/assert/verify)`);
|
|
135
187
|
}
|
|
136
188
|
/**
|
|
137
|
-
* 递归执行 steps[]。返回
|
|
189
|
+
* 递归执行 steps[]。返回 StepFlow(completed / halted / break|continue 信号,见上方定义)。
|
|
138
190
|
*
|
|
139
191
|
* yields 混合事件:WorkflowEvent + LoopEvent(skill 子循环转发 / error / interrupted)。
|
|
140
192
|
* index/total 字段相对**当前 block**,嵌套 block 自有计数(branch.then 内 3 步就报 total=3)。
|
|
141
193
|
*/
|
|
142
|
-
async function* runSteps(steps, vars, ctx, state
|
|
194
|
+
async function* runSteps(steps, vars, ctx, state,
|
|
195
|
+
// checkpoint 记录范围:顶层调用(runWorkflow → def.steps)传 { name: id=>id, descendIntoLoop:true };
|
|
196
|
+
// 顶层 loop 的 do[] 由 loop 处理器派生复合名 scope;更深嵌套传 undefined(不记录)。
|
|
197
|
+
cp,
|
|
198
|
+
// resume:本列表中索引 < startFrom 的步已在上次运行完成,本次跳过(变量已由 runWorkflow 回放)。
|
|
199
|
+
// 顶层与「loop resume 进迭代中间」(loop 处理器对 do[] 传 innerStartFrom)都用它。
|
|
200
|
+
startFrom = 0,
|
|
201
|
+
// 仅顶层传:进行中循环的恢复信息(迭代号 + 迭代内起点),由匹配 id 的 loop 消费一次。
|
|
202
|
+
loopResume) {
|
|
143
203
|
for (let i = 0; i < steps.length; i++) {
|
|
144
204
|
const step = steps[i];
|
|
145
205
|
if (ctx.signal?.aborted) {
|
|
146
206
|
yield { type: 'interrupted' };
|
|
147
|
-
return
|
|
207
|
+
return 'halted';
|
|
208
|
+
}
|
|
209
|
+
// resume:跳过已完成的步(变量已回放,无需重跑)。
|
|
210
|
+
if (i < startFrom) {
|
|
211
|
+
yield {
|
|
212
|
+
type: 'workflow_step_skipped',
|
|
213
|
+
id: step.id,
|
|
214
|
+
reason: 'resume: 上次已完成',
|
|
215
|
+
};
|
|
216
|
+
continue;
|
|
148
217
|
}
|
|
149
218
|
// when 条件
|
|
150
219
|
if (step.when) {
|
|
@@ -164,7 +233,7 @@ async function* runSteps(steps, vars, ctx, state) {
|
|
|
164
233
|
type: 'error',
|
|
165
234
|
error: `Workflow halted at step "${step.id}": ${errMsg}`,
|
|
166
235
|
};
|
|
167
|
-
return
|
|
236
|
+
return 'halted';
|
|
168
237
|
}
|
|
169
238
|
if (!pass) {
|
|
170
239
|
yield {
|
|
@@ -178,23 +247,39 @@ async function* runSteps(steps, vars, ctx, state) {
|
|
|
178
247
|
}
|
|
179
248
|
const kind = stepKind(step);
|
|
180
249
|
dlog('wf', `step ${i + 1}/${steps.length} START ${step.id} (${kind})`);
|
|
250
|
+
// 已插值的人话进度名随 start 事件带给 UI:scrollback 进度行显示它而非块内相对
|
|
251
|
+
// 序号,与状态行(下方 plugin_progress)保持一致。未声明 title 的步省略此字段。
|
|
252
|
+
const interpolatedTitle = step.title
|
|
253
|
+
? interpolate(step.title, vars)
|
|
254
|
+
: undefined;
|
|
181
255
|
yield {
|
|
182
256
|
type: 'workflow_step_start',
|
|
183
257
|
id: step.id,
|
|
184
258
|
kind,
|
|
185
259
|
index: i,
|
|
186
260
|
total: steps.length,
|
|
261
|
+
title: interpolatedTitle,
|
|
187
262
|
};
|
|
188
|
-
// P1 TUI 可见:把私有 workflow_step_start 镜像成框架通用 plugin_progress
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
263
|
+
// P1 TUI 可见:把私有 workflow_step_start 镜像成框架通用 plugin_progress。
|
|
264
|
+
// 有 title 的步显示人话阶段名(如「阶段2/6·起草实现计划」)且不带块内序号——
|
|
265
|
+
// 嵌套块的 i/total(顶层 5、循环体 12…)会被用户误读成"跳步/结构乱";
|
|
266
|
+
// 未声明 title 的步保持 legacy 格式 "i/total step <id> (<kind>)"。
|
|
267
|
+
if (interpolatedTitle) {
|
|
268
|
+
yield {
|
|
269
|
+
type: 'plugin_progress',
|
|
270
|
+
pluginId: WF_PLUGIN_ID,
|
|
271
|
+
message: interpolatedTitle,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
yield {
|
|
276
|
+
type: 'plugin_progress',
|
|
277
|
+
pluginId: WF_PLUGIN_ID,
|
|
278
|
+
current: i + 1,
|
|
279
|
+
max: steps.length,
|
|
280
|
+
message: `step ${step.id} (${kind})`,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
198
283
|
// ---------- 控制流:branch ----------
|
|
199
284
|
if (step.type === 'branch') {
|
|
200
285
|
let cond = false;
|
|
@@ -206,13 +291,14 @@ async function* runSteps(steps, vars, ctx, state) {
|
|
|
206
291
|
await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
|
|
207
292
|
yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
|
|
208
293
|
yield { type: 'error', error: `Workflow halted at step "${step.id}": ${errMsg}` };
|
|
209
|
-
return
|
|
294
|
+
return 'halted';
|
|
210
295
|
}
|
|
211
296
|
const branch = cond ? step.then : step.else;
|
|
212
297
|
if (Array.isArray(branch) && branch.length > 0) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
298
|
+
// branch 对 break 透明:子块 break/halt 都原样上抛给最近的循环 / 顶层。
|
|
299
|
+
const r = yield* runSteps(branch, vars, ctx, state);
|
|
300
|
+
if (r !== 'completed')
|
|
301
|
+
return r;
|
|
216
302
|
}
|
|
217
303
|
await state.appendProgress(`✓ ${step.id} (branch=${cond})`);
|
|
218
304
|
await state.writeVars(vars.snapshot());
|
|
@@ -224,25 +310,104 @@ async function* runSteps(steps, vars, ctx, state) {
|
|
|
224
310
|
};
|
|
225
311
|
continue;
|
|
226
312
|
}
|
|
313
|
+
// ---------- 控制流:set(确定性变量赋值) ----------
|
|
314
|
+
//
|
|
315
|
+
// 状态机类工作流(如 ralph-loop 的 phase)必须用 set——曾有 yaml 用
|
|
316
|
+
// `llm: "PLAN"` 当赋值语句:真把 "PLAN" 发给模型、capture 模型自由回复,
|
|
317
|
+
// 变量值非确定,下游 phase == 'PLAN' 比较全部不可靠。
|
|
318
|
+
if (step.type === 'set') {
|
|
319
|
+
const missing = [];
|
|
320
|
+
// 顶层值若是单一 "${EXPR}" 模板走 interpolateTyped 保留类型(布尔/数字/数组
|
|
321
|
+
// 不再字符串化成 "true",下游条件不必写 == true || == 'true' 防御);
|
|
322
|
+
// 多片段模板与嵌套结构行为不变(interpolateDeep)。
|
|
323
|
+
const resolved = {};
|
|
324
|
+
for (const [k, v] of Object.entries(step.set ?? {})) {
|
|
325
|
+
resolved[k] =
|
|
326
|
+
typeof v === 'string'
|
|
327
|
+
? interpolateTyped(v, vars, { onMissing: (e) => missing.push(e) })
|
|
328
|
+
: interpolateDeep(v, vars, { onMissing: (e) => missing.push(e) });
|
|
329
|
+
}
|
|
330
|
+
if (missing.length > 0) {
|
|
331
|
+
yield {
|
|
332
|
+
type: 'warning',
|
|
333
|
+
message: `step "${step.id}": 模板变量未定义:${[...new Set(missing)].map((m) => `\${${m}}`).join('、')}(裸名请用 inputs.<name> 或先 capture/set)`,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
for (const [k, v] of Object.entries(resolved)) {
|
|
337
|
+
vars.setGlobal(k, v);
|
|
338
|
+
}
|
|
339
|
+
const summary = Object.entries(resolved)
|
|
340
|
+
.map(([k, v]) => `${k}=${String(v).slice(0, 40)}`)
|
|
341
|
+
.join(', ');
|
|
342
|
+
await state.appendProgress(`✓ ${step.id} (set ${summary})`);
|
|
343
|
+
await state.writeVars(vars.snapshot());
|
|
344
|
+
// set 记 checkpoint(值入 captures):resume 跳过它时变量仍能回放
|
|
345
|
+
if (cp) {
|
|
346
|
+
await state.recordCheckpoint(cp.name(step.id), `set: ${summary}`, resolved, vars.snapshot());
|
|
347
|
+
}
|
|
348
|
+
yield {
|
|
349
|
+
type: 'workflow_step_end',
|
|
350
|
+
id: step.id,
|
|
351
|
+
ok: true,
|
|
352
|
+
output: `set: ${summary}`,
|
|
353
|
+
};
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
227
356
|
// ---------- 控制流:loop(for-each 或 while) ----------
|
|
228
357
|
if (step.type === 'loop') {
|
|
229
358
|
const isWhileMode = step.over === undefined;
|
|
230
|
-
|
|
359
|
+
// max_iterations 支持 ${} 模板(如 "${inputs.max_iterations}"):插值 + coerce;
|
|
360
|
+
// 失败响亮 halt——曾有模板字符串不插值直接参与 < 比较 → NaN → 静默 0 次迭代。
|
|
361
|
+
let maxIter = 200; // while 模式安全上限(缺省)
|
|
362
|
+
if (step.max_iterations !== undefined) {
|
|
363
|
+
const raw = step.max_iterations;
|
|
364
|
+
const n = typeof raw === 'number'
|
|
365
|
+
? raw
|
|
366
|
+
: Number(interpolate(String(raw), vars).trim());
|
|
367
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
368
|
+
const errMsg = `loop "${step.id}" 的 max_iterations 解析失败:${JSON.stringify(raw)} → "${typeof raw === 'string' ? interpolate(String(raw), vars) : raw}" 不是正数(检查变量是否存在,裸名请用 inputs.<name>)`;
|
|
369
|
+
yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
|
|
370
|
+
yield { type: 'error', error: `Workflow halted at step "${step.id}": ${errMsg}` };
|
|
371
|
+
return 'halted';
|
|
372
|
+
}
|
|
373
|
+
maxIter = Math.floor(n);
|
|
374
|
+
}
|
|
231
375
|
const useFreshContext = step.fresh_context === true;
|
|
232
376
|
// fresh context:循环开始时 snapshot 当前 history
|
|
233
377
|
let baseHistorySnapshot = null;
|
|
234
378
|
if (useFreshContext) {
|
|
235
379
|
baseHistorySnapshot = ctx.history.slice();
|
|
236
380
|
}
|
|
381
|
+
// loop resume:恢复信息匹配本循环时消费一次——从中断时的迭代号起跑,
|
|
382
|
+
// 该迭代内已完成的步经 innerStartFrom 跳过(变量已由 runWorkflow 回放快照)。
|
|
383
|
+
let resumeIterStart = 0;
|
|
384
|
+
let pendingInnerStart = 0;
|
|
385
|
+
if (loopResume && loopResume.loopId === step.id) {
|
|
386
|
+
resumeIterStart = loopResume.iteration;
|
|
387
|
+
pendingInnerStart = loopResume.innerStartFrom;
|
|
388
|
+
loopResume = undefined;
|
|
389
|
+
yield {
|
|
390
|
+
type: 'warning',
|
|
391
|
+
message: `♻ 循环 ${step.id} 从第 ${resumeIterStart + 1} 轮迭代第 ${pendingInnerStart + 1} 步续跑。`,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
// 顶层 loop 的 do[] 步记复合 checkpoint(loopId@@迭代号@@stepId),
|
|
395
|
+
// resume 可恢复到迭代中间;更深嵌套不再下钻(descendIntoLoop=false)。
|
|
396
|
+
const innerCpFor = (iter) => cp?.descendIntoLoop
|
|
397
|
+
? {
|
|
398
|
+
name: (id) => loopCheckpointName(step.id, iter, id),
|
|
399
|
+
descendIntoLoop: false,
|
|
400
|
+
}
|
|
401
|
+
: undefined;
|
|
237
402
|
if (isWhileMode) {
|
|
238
403
|
// ===== WHILE 循环模式 =====
|
|
239
404
|
const asName = step.as ?? '_iter';
|
|
240
|
-
let iterCount =
|
|
405
|
+
let iterCount = resumeIterStart;
|
|
241
406
|
let shouldBreak = false;
|
|
242
407
|
while (iterCount < maxIter) {
|
|
243
408
|
if (ctx.signal?.aborted) {
|
|
244
409
|
yield { type: 'interrupted' };
|
|
245
|
-
return
|
|
410
|
+
return 'halted';
|
|
246
411
|
}
|
|
247
412
|
// 注入迭代变量
|
|
248
413
|
vars.push();
|
|
@@ -253,30 +418,58 @@ async function* runSteps(steps, vars, ctx, state) {
|
|
|
253
418
|
ctx.history.length = 0;
|
|
254
419
|
ctx.history.push(...baseHistorySnapshot);
|
|
255
420
|
}
|
|
421
|
+
// 迭代序号对用户有真实意义(第几轮重做),title 步也保留,只换人话名。
|
|
256
422
|
yield {
|
|
257
423
|
type: 'plugin_progress',
|
|
258
424
|
pluginId: WF_PLUGIN_ID,
|
|
259
425
|
current: iterCount + 1,
|
|
260
426
|
max: maxIter,
|
|
261
|
-
message:
|
|
427
|
+
message: step.title
|
|
428
|
+
? `${interpolate(step.title, vars)}·第 ${iterCount + 1}/${maxIter} 轮`
|
|
429
|
+
: `while-loop ${step.id} 迭代 ${iterCount + 1}/${maxIter}`,
|
|
262
430
|
};
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
431
|
+
// 第 2+ 轮额外发持久 warning:上面的状态行 progress 会被首步 title 立即
|
|
432
|
+
// 覆盖(驻留一瞬),用户感知不到「循环重来了」——曾有 plan 工作流验收
|
|
433
|
+
// 打回静默重启,被用户误读成死循环。while 模式的多轮 = 重做/重试,
|
|
434
|
+
// 值得进 MessageList 留痕;for-each 的逐元素迭代是正常推进,不发。
|
|
435
|
+
if (iterCount > 0) {
|
|
436
|
+
yield {
|
|
437
|
+
type: 'warning',
|
|
438
|
+
message: `♻ 循环「${step.title ? interpolate(step.title, vars) : step.id}」进入第 ${iterCount + 1}/${maxIter} 轮`,
|
|
439
|
+
};
|
|
270
440
|
}
|
|
441
|
+
const innerStart = pendingInnerStart;
|
|
442
|
+
pendingInnerStart = 0; // 仅 resume 后的首个迭代生效
|
|
443
|
+
const r = yield* runSteps(step.do ?? [], vars, ctx, state, innerCpFor(iterCount), innerStart);
|
|
444
|
+
vars.pop();
|
|
271
445
|
iterCount++;
|
|
446
|
+
// halt(error/interrupt/onError:halt)→ 真正向上中止整个工作流。
|
|
447
|
+
if (r === 'halted')
|
|
448
|
+
return 'halted';
|
|
449
|
+
if (typeof r === 'object') {
|
|
450
|
+
// 带 target 且不是本循环 → 穿透上抛给外层循环
|
|
451
|
+
if (r.target !== undefined && r.target !== step.id)
|
|
452
|
+
return r;
|
|
453
|
+
if (r.signal === 'break') {
|
|
454
|
+
shouldBreak = true;
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
// continue → 跳到下一轮迭代(什么都不做,自然进入下一轮)
|
|
458
|
+
}
|
|
272
459
|
}
|
|
273
|
-
|
|
460
|
+
const whyEnd = shouldBreak ? 'break 退出' : `达到上限 ${maxIter}`;
|
|
461
|
+
await state.appendProgress(`✓ ${step.id} (while ×${iterCount}, ${whyEnd})`);
|
|
274
462
|
await state.writeVars(vars.snapshot());
|
|
463
|
+
// 循环完成记自身 checkpoint:resume 据此判定「循环已整体完成」(否则
|
|
464
|
+
// 中断在循环之后的步骤时,resume 会误把整条循环从头重跑)。
|
|
465
|
+
if (cp) {
|
|
466
|
+
await state.recordCheckpoint(cp.name(step.id), `while loop ${iterCount} iterations (${whyEnd})`, collectCaptured(step.capture, vars), vars.snapshot());
|
|
467
|
+
}
|
|
275
468
|
yield {
|
|
276
469
|
type: 'workflow_step_end',
|
|
277
470
|
id: step.id,
|
|
278
471
|
ok: true,
|
|
279
|
-
output: `while loop ${iterCount} iterations (
|
|
472
|
+
output: `while loop ${iterCount} iterations (${whyEnd})`,
|
|
280
473
|
};
|
|
281
474
|
continue;
|
|
282
475
|
}
|
|
@@ -290,21 +483,21 @@ async function* runSteps(steps, vars, ctx, state) {
|
|
|
290
483
|
await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
|
|
291
484
|
yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
|
|
292
485
|
yield { type: 'error', error: `Workflow halted at step "${step.id}": ${errMsg}` };
|
|
293
|
-
return
|
|
486
|
+
return 'halted';
|
|
294
487
|
}
|
|
295
488
|
if (!Array.isArray(arr)) {
|
|
296
489
|
const errMsg = `loop over 必须求值为数组,得到 ${typeof arr}`;
|
|
297
490
|
await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
|
|
298
491
|
yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
|
|
299
492
|
yield { type: 'error', error: `Workflow halted at step "${step.id}": ${errMsg}` };
|
|
300
|
-
return
|
|
493
|
+
return 'halted';
|
|
301
494
|
}
|
|
302
495
|
const asName = step.as ?? 'item';
|
|
303
496
|
let iterCount = 0;
|
|
304
|
-
for (let idx =
|
|
497
|
+
for (let idx = resumeIterStart; idx < arr.length; idx++) {
|
|
305
498
|
if (ctx.signal?.aborted) {
|
|
306
499
|
yield { type: 'interrupted' };
|
|
307
|
-
return
|
|
500
|
+
return 'halted';
|
|
308
501
|
}
|
|
309
502
|
vars.push();
|
|
310
503
|
vars.set(asName, arr[idx]);
|
|
@@ -314,14 +507,26 @@ async function* runSteps(steps, vars, ctx, state) {
|
|
|
314
507
|
ctx.history.length = 0;
|
|
315
508
|
ctx.history.push(...baseHistorySnapshot);
|
|
316
509
|
}
|
|
317
|
-
const
|
|
510
|
+
const innerStart = pendingInnerStart;
|
|
511
|
+
pendingInnerStart = 0;
|
|
512
|
+
const r = yield* runSteps(step.do ?? [], vars, ctx, state, innerCpFor(idx), innerStart);
|
|
318
513
|
vars.pop();
|
|
319
|
-
if (childHalted)
|
|
320
|
-
return true;
|
|
321
514
|
iterCount++;
|
|
515
|
+
if (r === 'halted')
|
|
516
|
+
return 'halted';
|
|
517
|
+
if (typeof r === 'object') {
|
|
518
|
+
if (r.target !== undefined && r.target !== step.id)
|
|
519
|
+
return r; // 穿透上抛
|
|
520
|
+
if (r.signal === 'break')
|
|
521
|
+
break; // 跳出 for-each,循环后兄弟步继续
|
|
522
|
+
// continue → 下一个元素
|
|
523
|
+
}
|
|
322
524
|
}
|
|
323
525
|
await state.appendProgress(`✓ ${step.id} (loop ×${iterCount})`);
|
|
324
526
|
await state.writeVars(vars.snapshot());
|
|
527
|
+
if (cp) {
|
|
528
|
+
await state.recordCheckpoint(cp.name(step.id), `loop ${iterCount} 次`, collectCaptured(step.capture, vars), vars.snapshot());
|
|
529
|
+
}
|
|
325
530
|
yield {
|
|
326
531
|
type: 'workflow_step_end',
|
|
327
532
|
id: step.id,
|
|
@@ -330,287 +535,379 @@ async function* runSteps(steps, vars, ctx, state) {
|
|
|
330
535
|
};
|
|
331
536
|
continue;
|
|
332
537
|
}
|
|
333
|
-
// ---------- 控制流:pause ----------
|
|
538
|
+
// ---------- 控制流:pause(真 HITL 审批闸门) ----------
|
|
334
539
|
//
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
540
|
+
// 经 AskUserQuestion/askBridge 实现真停等:TUI 弹选择界面、用户拍板后继续;
|
|
541
|
+
// 非交互宿主(-p/gateway/测试)askBridge 自动选推荐项(继续执行)且 stderr
|
|
542
|
+
// 注记,绝不挂起——与 -p 单行 JSON 契约兼容。「等用户」骑在既有
|
|
543
|
+
// tool_call→tool_result 回路上(同 AskUserQuestion 工具),引擎零新通道。
|
|
338
544
|
if (step.type === 'pause') {
|
|
339
545
|
const msg = step.prompt
|
|
340
546
|
? interpolate(step.prompt, vars)
|
|
341
|
-
: '
|
|
342
|
-
await state.appendProgress(`- ${step.id} pause:
|
|
343
|
-
//
|
|
344
|
-
|
|
345
|
-
// (-p 模式由 print.ts 落 stderr,权威通道)并镜像 plugin_progress(TUI 状态行可见),
|
|
346
|
-
// 避免审批闸门被悄悄跳过。真·HITL pause 属 P4,本轮不做。
|
|
347
|
-
const warnMsg = `⚠ pause "${step.id}" 在非交互模式被自动跳过,未真正等待人工确认——若这是审批闸门请勿依赖它。提示:${msg}`;
|
|
348
|
-
yield { type: 'warning', message: warnMsg };
|
|
349
|
-
yield {
|
|
350
|
-
type: 'plugin_progress',
|
|
351
|
-
pluginId: WF_PLUGIN_ID,
|
|
352
|
-
current: 0,
|
|
353
|
-
message: warnMsg,
|
|
354
|
-
};
|
|
355
|
-
yield {
|
|
356
|
-
type: 'workflow_step_skipped',
|
|
357
|
-
id: step.id,
|
|
358
|
-
reason: `pause: ${msg}`,
|
|
359
|
-
};
|
|
360
|
-
yield {
|
|
361
|
-
type: 'workflow_step_end',
|
|
362
|
-
id: step.id,
|
|
363
|
-
ok: true,
|
|
364
|
-
output: 'paused (skipped in non-interactive mode)',
|
|
365
|
-
};
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
// ---------- 控制流:parallel ----------
|
|
369
|
-
//
|
|
370
|
-
// 每个 fork 独立 VarStack 副本 + 独立事件缓冲;Promise.all 并发执行。
|
|
371
|
-
// 子 fork 的事件先收进 buffer,再按 fork 顺序一次性 yield,保 UI
|
|
372
|
-
// 时间轴可读(并发 yield 会导致 UI 拿到乱序事件)。
|
|
373
|
-
// 任一 fork halt → 整个 parallel halt。
|
|
374
|
-
if (step.type === 'parallel') {
|
|
375
|
-
const forks = step.fork ?? [];
|
|
376
|
-
const results = await Promise.all(forks.map(async (forkStep) => {
|
|
377
|
-
const forkVars = vars.fork();
|
|
378
|
-
const events = [];
|
|
379
|
-
let halted = false;
|
|
380
|
-
try {
|
|
381
|
-
halted = await collectStepsToBuffer([forkStep], forkVars, ctx, state, events);
|
|
382
|
-
}
|
|
383
|
-
catch (e) {
|
|
384
|
-
halted = true;
|
|
385
|
-
events.push({
|
|
386
|
-
type: 'error',
|
|
387
|
-
error: `fork ${forkStep.id} 抛异常: ${e.message}`,
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
return {
|
|
391
|
-
id: forkStep.id,
|
|
392
|
-
events,
|
|
393
|
-
vars: forkVars.snapshot(),
|
|
394
|
-
halted,
|
|
395
|
-
};
|
|
396
|
-
}));
|
|
397
|
-
// 顺序 yield 所有 fork 事件
|
|
398
|
-
for (const r of results) {
|
|
399
|
-
for (const ev of r.events)
|
|
400
|
-
yield ev;
|
|
401
|
-
}
|
|
402
|
-
if (results.some((r) => r.halted)) {
|
|
403
|
-
await state.appendProgress(`✗ ${step.id}: parallel: 至少一个 fork 失败`);
|
|
547
|
+
: '工作流暂停点:请确认是否继续执行后续步骤。';
|
|
548
|
+
await state.appendProgress(`- ${step.id} pause: 等待用户确认`);
|
|
549
|
+
// pause 会驻留状态行很久(等用户),title 步同样不带块内序号防误读。
|
|
550
|
+
if (step.title) {
|
|
404
551
|
yield {
|
|
405
|
-
type: '
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
error: 'parallel: 至少一个 fork 失败',
|
|
552
|
+
type: 'plugin_progress',
|
|
553
|
+
pluginId: WF_PLUGIN_ID,
|
|
554
|
+
message: `⏸ ${interpolate(step.title, vars)}:等待用户确认…`,
|
|
409
555
|
};
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
410
558
|
yield {
|
|
411
|
-
type: '
|
|
412
|
-
|
|
559
|
+
type: 'plugin_progress',
|
|
560
|
+
pluginId: WF_PLUGIN_ID,
|
|
561
|
+
current: i + 1,
|
|
562
|
+
max: steps.length,
|
|
563
|
+
message: `⏸ 审批闸门 ${step.id}:等待用户确认…`,
|
|
413
564
|
};
|
|
414
|
-
return true;
|
|
415
565
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
await state.writeVars(vars.snapshot());
|
|
425
|
-
yield {
|
|
426
|
-
type: 'workflow_step_end',
|
|
427
|
-
id: step.id,
|
|
428
|
-
ok: true,
|
|
429
|
-
output: aggResult.preview,
|
|
430
|
-
};
|
|
431
|
-
continue;
|
|
432
|
-
}
|
|
433
|
-
// ---------- 控制流:vote ----------
|
|
434
|
-
//
|
|
435
|
-
// 对每个 input 数组项跑 N 个 voter(同 prompt + refuted: boolean),
|
|
436
|
-
// refuted 票数 >= threshold 则视为驳回(item 不进 survived)。
|
|
437
|
-
// 心理学定位:voter 倾向于 refuted=true(找漏洞),所以阈值算"驳回票数"。
|
|
438
|
-
if (step.type === 'vote') {
|
|
439
|
-
const voters_count = step.voters_count ?? 3;
|
|
440
|
-
const threshold = step.threshold ?? Math.ceil(voters_count / 2);
|
|
441
|
-
let inputArr;
|
|
566
|
+
const exec = ctx.executeTool ?? executeTool;
|
|
567
|
+
// 审批语义:明确选「继续」才放行;选「停止」、取消提问(ESC)、或自由输入
|
|
568
|
+
// 其它内容(既非继续也非停止 = 用户想先改点什么)都不放行(审批门上的
|
|
569
|
+
// 含糊 = 不批准,保守处理);桥本身异常才兜底放行(保持可用性)。
|
|
570
|
+
let approved = true;
|
|
571
|
+
let askFailed = false;
|
|
572
|
+
let freeText = '';
|
|
573
|
+
const freeTextMode = step.free_text ?? 'halt';
|
|
442
574
|
try {
|
|
443
|
-
|
|
575
|
+
const r = await exec('AskUserQuestion', JSON.stringify({
|
|
576
|
+
questions: [
|
|
577
|
+
{
|
|
578
|
+
question: truncate(msg, 400),
|
|
579
|
+
header: '审批闸门',
|
|
580
|
+
options: [
|
|
581
|
+
{ label: '继续执行 (推荐)', description: '放行后续步骤' },
|
|
582
|
+
{
|
|
583
|
+
label: '停止(保留现场)',
|
|
584
|
+
description: '中止本次运行;同任务重跑可从断点 resume',
|
|
585
|
+
},
|
|
586
|
+
],
|
|
587
|
+
},
|
|
588
|
+
],
|
|
589
|
+
}), ctx.signal);
|
|
590
|
+
// ⚠️ 只解析【答案行】("A1: …")——回执里 Q 行带着问题原文,
|
|
591
|
+
// 工作流的 pause 提示语若含「停止」字样(如 plan.yaml 的说明文字),
|
|
592
|
+
// 看整个 content 会把用户选的「继续」误判成停止(曾是真事故)。
|
|
593
|
+
const answerText = r.ok
|
|
594
|
+
? r.content
|
|
595
|
+
.split('\n')
|
|
596
|
+
.map((l) => l.trim())
|
|
597
|
+
.filter((l) => /^A\d+[::]/.test(l))
|
|
598
|
+
.join('\n')
|
|
599
|
+
: '';
|
|
600
|
+
if (!r.ok || answerText.includes('停止')) {
|
|
601
|
+
approved = false;
|
|
602
|
+
}
|
|
603
|
+
else if (answerText.includes('继续')) {
|
|
604
|
+
approved = true;
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
// 自由输入(「其他」逃生项):用户想先调整 → 不放行,把原话带进 halt 文案
|
|
608
|
+
freeText = answerText.replace(/^A\d+[::]\s*/, '');
|
|
609
|
+
approved = freeTextMode === 'approve';
|
|
610
|
+
}
|
|
444
611
|
}
|
|
445
|
-
catch
|
|
446
|
-
|
|
447
|
-
await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
|
|
448
|
-
yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
|
|
449
|
-
yield {
|
|
450
|
-
type: 'error',
|
|
451
|
-
error: `Workflow halted at step "${step.id}": ${errMsg}`,
|
|
452
|
-
};
|
|
453
|
-
return true;
|
|
612
|
+
catch {
|
|
613
|
+
askFailed = true; // 桥不可用 → 放行 + 响亮警告(保持旧"不挂起"行为)
|
|
454
614
|
}
|
|
455
|
-
if (
|
|
456
|
-
const errMsg = `vote.input 必须求值为数组,得到 ${typeof inputArr}`;
|
|
457
|
-
await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
|
|
458
|
-
yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
|
|
615
|
+
if (askFailed) {
|
|
459
616
|
yield {
|
|
460
|
-
type: '
|
|
461
|
-
|
|
617
|
+
type: 'warning',
|
|
618
|
+
message: `⚠ pause "${step.id}" 审批闸门不可用(提问桥异常),已自动放行。提示:${msg}`,
|
|
462
619
|
};
|
|
463
|
-
return true;
|
|
464
620
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
621
|
+
if (!approved) {
|
|
622
|
+
const why = freeText
|
|
623
|
+
? `用户在审批闸门 "${step.id}" 给出指示「${truncate(freeText, 120)}」——未放行(现场已保留,按指示调整后重跑同目标可从断点续跑)`
|
|
624
|
+
: `用户在审批闸门 "${step.id}" 选择停止(现场已保留,重跑同目标可从断点续跑)`;
|
|
625
|
+
await state.appendProgress(`■ ${step.id}: ${why}`);
|
|
626
|
+
if (cp)
|
|
627
|
+
await state.recordFailure(step.id, why);
|
|
628
|
+
yield { type: 'workflow_step_end', id: step.id, ok: false, error: why };
|
|
629
|
+
yield { type: 'error', error: `Workflow halted at step "${step.id}": ${why}` };
|
|
630
|
+
return 'halted';
|
|
476
631
|
}
|
|
477
|
-
|
|
632
|
+
// 带补充要求的放行:自由输入并入贯穿目标(后续每步都看到,不止 capture 变量)
|
|
633
|
+
if (freeText)
|
|
634
|
+
await addGoalSupplement(ctx, state, freeText);
|
|
635
|
+
const pauseResult = {
|
|
478
636
|
raw: {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
637
|
+
result: freeText,
|
|
638
|
+
text: freeText,
|
|
639
|
+
approved: true,
|
|
640
|
+
free_text: freeText,
|
|
482
641
|
},
|
|
483
|
-
preview:
|
|
642
|
+
preview: freeText
|
|
643
|
+
? `审批通过:继续执行;补充要求:${truncate(freeText, 120)}`
|
|
644
|
+
: '审批通过:继续执行',
|
|
484
645
|
};
|
|
485
646
|
if (step.capture)
|
|
486
|
-
bindCapture(step.capture,
|
|
487
|
-
await state.appendProgress(`✓ ${step.id} (vote ${survived.length}/${inputArr.length})`);
|
|
647
|
+
bindCapture(step.capture, pauseResult, vars);
|
|
488
648
|
await state.writeVars(vars.snapshot());
|
|
649
|
+
if (cp) {
|
|
650
|
+
await state.recordCheckpoint(cp.name(step.id), pauseResult.preview, collectCaptured(step.capture, vars), vars.snapshot());
|
|
651
|
+
}
|
|
652
|
+
await state.appendProgress(`✓ ${step.id} (pause: 已确认继续)`);
|
|
489
653
|
yield {
|
|
490
654
|
type: 'workflow_step_end',
|
|
491
655
|
id: step.id,
|
|
492
656
|
ok: true,
|
|
493
|
-
output:
|
|
657
|
+
output: pauseResult.preview,
|
|
494
658
|
};
|
|
495
659
|
continue;
|
|
496
660
|
}
|
|
497
|
-
// ---------- 控制流:break
|
|
661
|
+
// ---------- 控制流:break / continue(循环信号) ----------
|
|
498
662
|
//
|
|
499
|
-
// 仅在 loop 的 do[]
|
|
500
|
-
//
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
663
|
+
// 仅在 loop 的 do[] 内有意义。break = 跳出循环;continue = 跳到下一轮迭代。
|
|
664
|
+
// 缺省作用于最近一层循环;带 target 时穿透中间层循环直达指定 id 的 loop
|
|
665
|
+
//(如审批反馈是方向性变化时,从审批小循环 continue 到外层 plan_loop 重启规划)。
|
|
666
|
+
// 通常配合 when 条件使用;游离信号(无外层循环 / target 不存在)冒泡到顶层
|
|
667
|
+
// 按正常完成处理,绝不当 halt。
|
|
668
|
+
if (step.type === 'break' || step.type === 'continue') {
|
|
669
|
+
let shouldFire = true;
|
|
504
670
|
if (step.when) {
|
|
505
671
|
try {
|
|
506
|
-
|
|
672
|
+
shouldFire = Boolean(evalExpr(step.when, vars));
|
|
507
673
|
}
|
|
508
674
|
catch (e) {
|
|
509
|
-
// when 表达式错误 →
|
|
510
|
-
|
|
675
|
+
// when 表达式错误 → 不触发,当 skipped 处理
|
|
676
|
+
shouldFire = false;
|
|
511
677
|
}
|
|
512
678
|
}
|
|
513
|
-
if (
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
679
|
+
if (shouldFire) {
|
|
680
|
+
const label = `${step.type}${step.target ? ` → ${step.target}` : ''}`;
|
|
681
|
+
await state.appendProgress(`↵ ${step.id}: ${label}`);
|
|
682
|
+
yield { type: 'workflow_step_end', id: step.id, ok: true, output: label };
|
|
683
|
+
return { signal: step.type, target: step.target };
|
|
517
684
|
}
|
|
518
685
|
// 条件不满足 → 当 skip 处理,继续下一步
|
|
519
686
|
yield {
|
|
520
687
|
type: 'workflow_step_skipped',
|
|
521
688
|
id: step.id,
|
|
522
|
-
reason:
|
|
689
|
+
reason: `${step.type} 条件不满足`,
|
|
523
690
|
};
|
|
524
691
|
continue;
|
|
525
692
|
}
|
|
526
|
-
// ----------
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
693
|
+
// ---------- 控制流:echo(确定性输出,不烧 LLM) ----------
|
|
694
|
+
//
|
|
695
|
+
// 把 message 插值后作为 text 事件输出给用户。适合工作流末尾的完成报告:
|
|
696
|
+
// 产物/裁决已在变量里时直接插值,省一次 LLM 调用且零幻觉。
|
|
697
|
+
if (step.type === 'echo') {
|
|
698
|
+
const missing = [];
|
|
699
|
+
const text = interpolate(step.message ?? '', vars, {
|
|
700
|
+
onMissing: (e) => missing.push(e),
|
|
701
|
+
});
|
|
702
|
+
if (missing.length > 0) {
|
|
703
|
+
yield {
|
|
704
|
+
type: 'warning',
|
|
705
|
+
message: `step "${step.id}": 模板变量未定义,已按空串插值:${[...new Set(missing)].map((m) => `\${${m}}`).join('、')}`,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
yield { type: 'text', delta: text.endsWith('\n') ? text : `${text}\n` };
|
|
709
|
+
await state.appendProgress(`✓ ${step.id} (echo)`);
|
|
710
|
+
if (cp) {
|
|
711
|
+
await state.recordCheckpoint(cp.name(step.id), truncate(text, 200), {}, vars.snapshot());
|
|
712
|
+
}
|
|
533
713
|
yield {
|
|
534
714
|
type: 'workflow_step_end',
|
|
535
715
|
id: step.id,
|
|
536
716
|
ok: true,
|
|
537
|
-
output:
|
|
717
|
+
output: truncate(text, 200),
|
|
538
718
|
};
|
|
719
|
+
continue;
|
|
539
720
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
721
|
+
// ---------- 普通动作 step:tool / llm / skill / agent / assert / verify ----------
|
|
722
|
+
//
|
|
723
|
+
// in-run retry(self_heal):失败先确定性重跑同一步,至多 maxRetries 次(retry 先于
|
|
724
|
+
// onError:recover)。maxRetries = step.max_retries ?? def.self_heal.max_retries(runWorkflow
|
|
725
|
+
// 注入到 ctx.selfHealMaxRetries)?? 0。语义:max_retries 是「额外重试次数」,总尝试 = 1 + maxRetries。
|
|
726
|
+
const maxRetries = step.max_retries ?? ctx.selfHealMaxRetries ?? 0;
|
|
727
|
+
let attempt = 0;
|
|
728
|
+
while (true) {
|
|
729
|
+
try {
|
|
730
|
+
const result = yield* dispatchActionStep(step, vars, ctx);
|
|
731
|
+
if (step.capture)
|
|
732
|
+
bindCapture(step.capture, result, vars);
|
|
733
|
+
// goal_supplement:本步产出作为用户补充要求贯穿后续每一步(如 stage0 澄清结论)
|
|
734
|
+
if (step.goal_supplement) {
|
|
735
|
+
const t = typeof result.raw.text === 'string' && result.raw.text.trim()
|
|
736
|
+
? result.raw.text
|
|
737
|
+
: String(result.raw.result ?? result.preview ?? '');
|
|
738
|
+
await addGoalSupplement(ctx, state, t);
|
|
739
|
+
}
|
|
740
|
+
await state.appendProgress(`✓ ${step.id} (${kind})`);
|
|
741
|
+
await state.writeVars(vars.snapshot());
|
|
742
|
+
// checkpoint:记录范围见 CpScope(顶层步 + 顶层 loop 的 do[] 一层)。
|
|
743
|
+
if (cp) {
|
|
744
|
+
await state.recordCheckpoint(cp.name(step.id), result.preview, collectCaptured(step.capture, vars), vars.snapshot());
|
|
745
|
+
}
|
|
551
746
|
yield {
|
|
552
|
-
type: '
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
message: `↻ 恢复 ${step.id}…`,
|
|
747
|
+
type: 'workflow_step_end',
|
|
748
|
+
id: step.id,
|
|
749
|
+
ok: true,
|
|
750
|
+
output: result.preview,
|
|
557
751
|
};
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
752
|
+
break; // 成功 → 退出 retry 循环,进入下一步
|
|
753
|
+
}
|
|
754
|
+
catch (e) {
|
|
755
|
+
const errMsg = e.message ?? String(e);
|
|
756
|
+
// in-run retry:还有重试额度且未中断 → 重跑同一步(不动 onError)。
|
|
757
|
+
if (attempt < maxRetries && !ctx.signal?.aborted) {
|
|
758
|
+
attempt++;
|
|
759
|
+
// 退避等待(线性):普通 LLM/网络类错误用 retry_delay_ms。
|
|
760
|
+
// maxTurns 截断默认保持旧行为立即续跑;长文档/PPT 类工作流可显式配置
|
|
761
|
+
// max_turns_retry_delay_ms,在模型端高峰期给下一次续作留出恢复窗口。
|
|
762
|
+
const isMaxTurns = isMaxTurnsCutoff(errMsg);
|
|
763
|
+
const delayBase = isMaxTurns
|
|
764
|
+
? (ctx.maxTurnsRetryDelayMs ?? 0)
|
|
765
|
+
: (ctx.retryDelayMs ?? 0);
|
|
766
|
+
const delayMs = delayBase * attempt;
|
|
767
|
+
const retryKind = isMaxTurns ? '轮数预算用尽,可续跑截断' : '失败';
|
|
768
|
+
const retryProgress = isMaxTurns
|
|
769
|
+
? `↻ ${step.id}: 阶段轮数预算用尽,正在续跑 ${attempt}/${maxRetries}${delayMs > 0 ? `(${delayMs}ms 后)` : ''}`
|
|
770
|
+
: `↻ ${step.id}: ${retryKind},重试 ${attempt}/${maxRetries}${delayMs > 0 ? `(${delayMs}ms 后)` : ''}(${errMsg})`;
|
|
771
|
+
await state.appendProgress(retryProgress);
|
|
772
|
+
yield {
|
|
773
|
+
type: 'warning',
|
|
774
|
+
message: isMaxTurns
|
|
775
|
+
? `step "${step.id}" 阶段轮数预算用尽,正在续跑 ${attempt}/${maxRetries}${delayMs > 0 ? `(${delayMs}ms 后)` : ''}…`
|
|
776
|
+
: `step "${step.id}" 失败:${errMsg};${delayMs > 0 ? `${delayMs}ms 后` : ''}重试 ${attempt}/${maxRetries}…`,
|
|
777
|
+
};
|
|
778
|
+
yield {
|
|
779
|
+
type: 'plugin_progress',
|
|
780
|
+
pluginId: WF_PLUGIN_ID,
|
|
781
|
+
current: i + 1,
|
|
782
|
+
max: steps.length,
|
|
783
|
+
message: isMaxTurns
|
|
784
|
+
? `↻ ${step.id} 阶段轮数预算用尽,正在续跑 ${attempt}/${maxRetries}…`
|
|
785
|
+
: `↻ 重试 ${step.id} ${attempt}/${maxRetries}…`,
|
|
786
|
+
};
|
|
787
|
+
if (delayMs > 0)
|
|
788
|
+
await sleepInterruptible(delayMs, ctx.signal);
|
|
789
|
+
continue; // 重跑同一步
|
|
562
790
|
}
|
|
563
|
-
|
|
564
|
-
//
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
791
|
+
await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
|
|
792
|
+
// onError:'recover' —— retry 用尽仍失败,交给有界恢复 agent 随机应变把这一步救回来。
|
|
793
|
+
// 「确定性工作流 × TAOR」的结合点:happy path 走刚性 DAG,撞上预料之外的就地
|
|
794
|
+
// 降级为 agent 自适应;救回则继续 happy path,救不回才 honest halt。
|
|
795
|
+
if (step.onError === 'recover') {
|
|
796
|
+
yield {
|
|
797
|
+
type: 'warning',
|
|
798
|
+
message: `step "${step.id}" 失败:${errMsg};尝试自适应恢复…`,
|
|
799
|
+
};
|
|
800
|
+
yield {
|
|
801
|
+
type: 'plugin_progress',
|
|
802
|
+
pluginId: WF_PLUGIN_ID,
|
|
803
|
+
current: i + 1,
|
|
804
|
+
max: steps.length,
|
|
805
|
+
message: `↻ 恢复 ${step.id}…`,
|
|
806
|
+
};
|
|
807
|
+
const rec = yield* runRecovery(step, vars, ctx, errMsg);
|
|
808
|
+
if (ctx.signal?.aborted) {
|
|
809
|
+
yield { type: 'interrupted' };
|
|
810
|
+
return 'halted';
|
|
811
|
+
}
|
|
812
|
+
// 验证闸门:只有恢复 agent 真正交付(rec.ok)才绑 capture 走下去;
|
|
813
|
+
// 显式放弃 / 产出不可信(rec.ok=false)则带原因 honest halt,绝不盲绑脏值。
|
|
814
|
+
if (rec.ok) {
|
|
815
|
+
if (step.capture)
|
|
816
|
+
bindCapture(step.capture, rec.value, vars);
|
|
817
|
+
await state.appendProgress(`⚠→✓ ${step.id} recovered`);
|
|
818
|
+
await state.writeVars(vars.snapshot());
|
|
819
|
+
if (cp) {
|
|
820
|
+
await state.recordCheckpoint(cp.name(step.id), rec.value.preview, collectCaptured(step.capture, vars), vars.snapshot());
|
|
821
|
+
}
|
|
822
|
+
yield {
|
|
823
|
+
type: 'workflow_step_end',
|
|
824
|
+
id: step.id,
|
|
825
|
+
ok: true,
|
|
826
|
+
output: rec.value.preview,
|
|
827
|
+
};
|
|
828
|
+
break; // 救回 → 进入下一步
|
|
829
|
+
}
|
|
830
|
+
const combined = `${errMsg}(自适应恢复未成功:${rec.reason})`;
|
|
831
|
+
if (cp)
|
|
832
|
+
await state.recordFailure(step.id, combined);
|
|
570
833
|
yield {
|
|
571
834
|
type: 'workflow_step_end',
|
|
572
835
|
id: step.id,
|
|
573
|
-
ok:
|
|
574
|
-
|
|
836
|
+
ok: false,
|
|
837
|
+
error: combined,
|
|
575
838
|
};
|
|
576
|
-
|
|
839
|
+
yield {
|
|
840
|
+
type: 'error',
|
|
841
|
+
error: `Workflow halted at step "${step.id}": ${combined}`,
|
|
842
|
+
};
|
|
843
|
+
return 'halted';
|
|
577
844
|
}
|
|
578
|
-
|
|
845
|
+
if (step.onError === 'continue') {
|
|
846
|
+
// 降级时给 capture 变量绑【明确占位文本】而非留空:空串静默内插进下游
|
|
847
|
+
// prompt(如「已知需求分析:\n\n」)是更大的最小惊讶违例——模型看到
|
|
848
|
+
// 空段会困惑。占位前缀「(上游步骤 」机器可识别;continue 本就是作者
|
|
849
|
+
// 显式选择「无此内容也继续」,需要严格闸门的步骤应配 onError:halt 或 assert。
|
|
850
|
+
if (step.capture) {
|
|
851
|
+
const placeholder = `(上游步骤 ${step.id} 失败已降级(onError:continue),无内容——` +
|
|
852
|
+
`请基于用户原始目标自行推断。错误:${errMsg.length > 120 ? `${errMsg.slice(0, 120)}…` : errMsg})`;
|
|
853
|
+
for (const varName of Object.values(normalizeCapture(step.capture))) {
|
|
854
|
+
vars.setGlobal(varName, placeholder);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
// 降级此前完全静默(仅 progress.md 有 ✗ 行)——补一条 warning 可见化
|
|
858
|
+
yield {
|
|
859
|
+
type: 'warning',
|
|
860
|
+
message: `step "${step.id}" 失败已降级(onError:continue):${errMsg}`,
|
|
861
|
+
};
|
|
862
|
+
yield {
|
|
863
|
+
type: 'workflow_step_end',
|
|
864
|
+
id: step.id,
|
|
865
|
+
ok: false,
|
|
866
|
+
error: errMsg,
|
|
867
|
+
};
|
|
868
|
+
break; // 跳过本步 → 进入下一步
|
|
869
|
+
}
|
|
870
|
+
const finalErrMsg = isMaxTurnsCutoff(errMsg)
|
|
871
|
+
? `${errMsg};重试额度已用尽,请拆分该步骤或提高 maxTurns。`
|
|
872
|
+
: errMsg;
|
|
873
|
+
if (cp)
|
|
874
|
+
await state.recordFailure(step.id, finalErrMsg);
|
|
579
875
|
yield {
|
|
580
876
|
type: 'workflow_step_end',
|
|
581
877
|
id: step.id,
|
|
582
878
|
ok: false,
|
|
583
|
-
error:
|
|
879
|
+
error: finalErrMsg,
|
|
584
880
|
};
|
|
585
881
|
yield {
|
|
586
882
|
type: 'error',
|
|
587
|
-
error: `Workflow halted at step "${step.id}": ${
|
|
883
|
+
error: `Workflow halted at step "${step.id}": ${finalErrMsg}`,
|
|
588
884
|
};
|
|
589
|
-
return
|
|
885
|
+
return 'halted';
|
|
590
886
|
}
|
|
591
|
-
if (step.onError === 'continue') {
|
|
592
|
-
yield {
|
|
593
|
-
type: 'workflow_step_end',
|
|
594
|
-
id: step.id,
|
|
595
|
-
ok: false,
|
|
596
|
-
error: errMsg,
|
|
597
|
-
};
|
|
598
|
-
continue;
|
|
599
|
-
}
|
|
600
|
-
yield {
|
|
601
|
-
type: 'workflow_step_end',
|
|
602
|
-
id: step.id,
|
|
603
|
-
ok: false,
|
|
604
|
-
error: errMsg,
|
|
605
|
-
};
|
|
606
|
-
yield {
|
|
607
|
-
type: 'error',
|
|
608
|
-
error: `Workflow halted at step "${step.id}": ${errMsg}`,
|
|
609
|
-
};
|
|
610
|
-
return true;
|
|
611
887
|
}
|
|
612
888
|
}
|
|
613
|
-
return
|
|
889
|
+
return 'completed';
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* 聚合工作流【声明】的产物路径(相对 cwd):def.artifacts + 所有 step.artifacts(含嵌套 then/else/do)。
|
|
893
|
+
* 通用:由 yaml 的 `artifacts:` 声明驱动,runner 不硬编码任何具体文件名(如 PLAN.md)。
|
|
894
|
+
* 用途:new-task 检测时清旧任务残留产物、completed 时报实际产物。
|
|
895
|
+
*/
|
|
896
|
+
function collectDeclaredArtifacts(def) {
|
|
897
|
+
const out = new Set();
|
|
898
|
+
for (const a of def.artifacts ?? [])
|
|
899
|
+
out.add(a);
|
|
900
|
+
const walk = (steps) => {
|
|
901
|
+
for (const s of steps ?? []) {
|
|
902
|
+
for (const a of s.artifacts ?? [])
|
|
903
|
+
out.add(a);
|
|
904
|
+
walk(s.then);
|
|
905
|
+
walk(s.else);
|
|
906
|
+
walk(s.do);
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
walk(def.steps);
|
|
910
|
+
return [...out];
|
|
614
911
|
}
|
|
615
912
|
/**
|
|
616
913
|
* 运行一个 workflow。
|
|
@@ -634,26 +931,148 @@ export async function* runWorkflow(def, inputs, ctx) {
|
|
|
634
931
|
// 注入 RunContext.goalAnchor,让 agent/llm/skill 步自动前置到每步 prompt。
|
|
635
932
|
// 提取策略:优先 "goal" 命名的输入 → 其次第一个 required string 输入。
|
|
636
933
|
runCtx.goalAnchor = buildGoalAnchor(def.inputs ?? [], inputs);
|
|
637
|
-
|
|
638
|
-
|
|
934
|
+
// self_heal:把全局 max_retries 默认透传给每步的 in-run retry(step.max_retries 可覆盖)。
|
|
935
|
+
runCtx.selfHealMaxRetries = def.self_heal?.max_retries;
|
|
936
|
+
runCtx.retryDelayMs = def.self_heal?.retry_delay_ms;
|
|
937
|
+
runCtx.maxTurnsRetryDelayMs = def.self_heal?.max_turns_retry_delay_ms;
|
|
938
|
+
// 声明产物清单:state.init 用于 new-task 清理/completed 报产物;
|
|
939
|
+
// 挂到 runCtx 供 tool 步覆盖判定(声明产物自动 pre-Read 解 Write 守卫)。
|
|
940
|
+
const declaredArtifacts = collectDeclaredArtifacts(def);
|
|
941
|
+
runCtx.declaredArtifacts = declaredArtifacts;
|
|
942
|
+
// 任务身份语义仲裁器(hash 不等 + 旧任务未完成时的二审,见 taskIdentity.ts):
|
|
943
|
+
// 短语快路径 → LLM 单轮仲裁 → AskUserQuestion → 兜底 new-task。
|
|
944
|
+
// init 非 generator,仲裁过程的人话记录收进 notes,init 返回后统一 yield。
|
|
945
|
+
const arbitrationNotes = [];
|
|
946
|
+
const state = new WorkflowState(getWorkingDir(), def.name);
|
|
947
|
+
const resume = await state.init(def.name, inputs, def.steps, def.self_heal?.backoff, declaredArtifacts, buildTaskArbitrator({
|
|
948
|
+
provider: runCtx.provider,
|
|
949
|
+
chat: runCtx.chat,
|
|
950
|
+
executeTool: runCtx.executeTool,
|
|
951
|
+
signal: runCtx.signal,
|
|
952
|
+
notes: arbitrationNotes,
|
|
953
|
+
}));
|
|
639
954
|
yield {
|
|
640
955
|
type: 'workflow_start',
|
|
641
956
|
name: def.name,
|
|
642
957
|
totalSteps: def.steps.length,
|
|
643
958
|
};
|
|
644
|
-
//
|
|
959
|
+
// 仲裁过程可见化(命中延续短语 / LLM 判定 / 用户确认 / 兜底)
|
|
960
|
+
for (const n of arbitrationNotes) {
|
|
961
|
+
yield { type: 'warning', message: n };
|
|
962
|
+
}
|
|
963
|
+
// 同任务此前已成功完成(goal hash 匹配 + status=done)→ 不重跑,报产物后正常收尾。
|
|
964
|
+
// 成功运行会 archive() 把状态目录改名 .done,init() 回退读 .done 检测到 completed。
|
|
965
|
+
if (resume.mode === 'completed') {
|
|
966
|
+
const arts = resume.artifacts?.length ? resume.artifacts.join(', ') : '(无记录)';
|
|
967
|
+
yield {
|
|
968
|
+
type: 'text',
|
|
969
|
+
delta: `✓ 同名任务此前已完成(产物:${arts})。如需重跑请先 /new 清理工作流状态。\n`,
|
|
970
|
+
};
|
|
971
|
+
yield { type: 'workflow_done', name: def.name, vars: vars.snapshot() };
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
// 定义结构变更可见化:同任务但 yaml 改过结构 → 旧断点作废、从头开始。
|
|
975
|
+
// 必须明示——曾有用户在 yaml 重构后同 goal 重跑,看到「从第 2 步续跑」
|
|
976
|
+
// 困惑是 bug 还是残留;作废旧断点时不说原因同样会被当成 bug。
|
|
977
|
+
if (resume.defChanged) {
|
|
978
|
+
yield {
|
|
979
|
+
type: 'warning',
|
|
980
|
+
message: '♻ 工作流定义已变更(步骤结构与旧断点不再匹配),旧执行现场已作废,本次从头开始执行。',
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
// fresh/new-task 启动清理可见化:全新任务开始前已删掉的残留产物(上次任务
|
|
984
|
+
// 的声明产物 + /new 后无主孤儿)。用户约定:全新 plan 一律从干净状态开始;
|
|
985
|
+
// 删除范围仅限本工作流声明的产物,同任务 resume 不清理。
|
|
986
|
+
if (resume.cleanedArtifacts?.length) {
|
|
987
|
+
yield {
|
|
988
|
+
type: 'warning',
|
|
989
|
+
message: `全新任务:已清理上次残留的旧产物 ${[...new Set(resume.cleanedArtifacts)].join('、')},从干净状态开始。`,
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
// 仲裁判定的延续 resume:本次 inputs 里的 goal 是「继续」这类延续指令,
|
|
993
|
+
// 不能拿它当目标——两处都要换回旧任务的真实目标:
|
|
994
|
+
// 1. goalAnchor:锚旧目标 + 新输入作为补充指令(如「继续,但改成 12 页」不丢失);
|
|
995
|
+
// 2. VarStack 的 inputs:yaml prompt 正文大量内插 ${inputs.goal}(业务上下文),
|
|
996
|
+
// 不替换的话重跑步骤会渲染成「用户的目标是:『继续』」——锚定与正文打架。
|
|
997
|
+
// goal 承载键用与 buildGoalAnchor/extractGoal 同一策略(通用,零具体 yaml 耦合);
|
|
998
|
+
// 磁盘 inputs.json 保持旧任务值不动,任务身份 hash 语义不受影响。
|
|
999
|
+
if (resume.mode === 'resume') {
|
|
1000
|
+
// 贯穿补充要求:上次运行累积的(pause 反馈 / 澄清结论)从状态目录恢复,
|
|
1001
|
+
// resume 后注入每步锚定不丢失。
|
|
1002
|
+
runCtx.goalSupplements = await state.readSupplements();
|
|
1003
|
+
}
|
|
1004
|
+
if (resume.mode === 'resume' && resume.prevGoal) {
|
|
1005
|
+
const goalKey = (def.inputs ?? []).find((d) => d.name === 'goal' && d.type === 'string')
|
|
1006
|
+
?.name ??
|
|
1007
|
+
(def.inputs ?? []).find((d) => d.required && d.type === 'string')?.name;
|
|
1008
|
+
if (goalKey && typeof inputs[goalKey] === 'string') {
|
|
1009
|
+
vars.setGlobal('inputs', { ...inputs, [goalKey]: resume.prevGoal });
|
|
1010
|
+
}
|
|
1011
|
+
runCtx.goalAnchor = `> 🎯 用户原始目标:${truncateGoal(resume.prevGoal)}\n> ⚠️ 所有操作必须直接回应此目标,不可偏离或遗忘。`;
|
|
1012
|
+
// 新输入是带信息量的补充指令(非纯「继续」延续短语)→ 并入贯穿补充要求;
|
|
1013
|
+
// 经 goalSupplements 统一渲染(与 pause 反馈/澄清结论同通道),且去重防
|
|
1014
|
+
// 反复 resume 时同一句话累积成噪声。
|
|
1015
|
+
const supp = resume.supplementaryGoal;
|
|
1016
|
+
if (supp &&
|
|
1017
|
+
supp !== resume.prevGoal &&
|
|
1018
|
+
!isContinuationPhrase(supp) &&
|
|
1019
|
+
!(runCtx.goalSupplements ?? []).some((s) => s.startsWith(supp.slice(0, 240)))) {
|
|
1020
|
+
await addGoalSupplement(runCtx, state, supp);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
// resume:同任务中断后重跑 —— 回放已完成步的 capture 变量,从 startFrom 起跑。
|
|
1024
|
+
// ⚠️ 仅回放 VarStack 变量,不恢复 TAOR history(plan.yaml 全程用 capture 变量串联故可用;
|
|
1025
|
+
// 依赖隐式 history 的工作流 resume 后上下文较薄,见 types.ts 限制说明)。
|
|
1026
|
+
let startFrom = 0;
|
|
1027
|
+
if (resume.mode === 'resume' && ((resume.startFrom ?? 0) > 0 || resume.loopResume)) {
|
|
1028
|
+
startFrom = resume.startFrom ?? 0;
|
|
1029
|
+
if (resume.capturedVars && resume.completedStepIds) {
|
|
1030
|
+
for (const sid of resume.completedStepIds) {
|
|
1031
|
+
const caps = resume.capturedVars[sid];
|
|
1032
|
+
if (caps)
|
|
1033
|
+
for (const [k, v] of Object.entries(caps))
|
|
1034
|
+
vars.setGlobal(k, v);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
// 循环中断恢复:回放中断时刻的最近 vars 快照(含 setGlobal 的教训/裁决与
|
|
1038
|
+
// 迭代内已完成步的 capture)。inputs 不回放——goal 承载键已由上方仲裁逻辑处理。
|
|
1039
|
+
if (resume.loopResume?.lastVarsSnapshot) {
|
|
1040
|
+
for (const [k, v] of Object.entries(resume.loopResume.lastVarsSnapshot)) {
|
|
1041
|
+
if (k === 'inputs')
|
|
1042
|
+
continue;
|
|
1043
|
+
vars.setGlobal(k, v);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
const loopNote = resume.loopResume
|
|
1047
|
+
? `,循环 ${resume.loopResume.loopId} 恢复到第 ${resume.loopResume.iteration + 1} 轮迭代`
|
|
1048
|
+
: '';
|
|
1049
|
+
const msg = `♻ 检测到同名未完成任务,从第 ${startFrom + 1} 步续跑(已回放 ${resume.completedStepIds?.length ?? 0} 个完成步变量${loopNote})。要全新开始请先 /new。`;
|
|
1050
|
+
yield { type: 'warning', message: msg };
|
|
1051
|
+
}
|
|
1052
|
+
// P1 TUI 可见:开工时给 LiveArea 状态行一条 plugin_progress(startFrom/total)。
|
|
645
1053
|
yield {
|
|
646
1054
|
type: 'plugin_progress',
|
|
647
1055
|
pluginId: WF_PLUGIN_ID,
|
|
648
|
-
current:
|
|
1056
|
+
current: startFrom,
|
|
649
1057
|
max: def.steps.length,
|
|
650
1058
|
message: `▶ workflow ${def.name}`,
|
|
651
1059
|
};
|
|
652
1060
|
let succeeded = false;
|
|
653
1061
|
try {
|
|
654
|
-
const
|
|
655
|
-
|
|
1062
|
+
const flow = yield* runSteps(def.steps, vars, runCtx, state, { name: (id) => id, descendIntoLoop: true }, startFrom, resume.mode === 'resume' ? resume.loopResume : undefined);
|
|
1063
|
+
// 'halted' → 失败/中断(error/interrupted 已 yield),保留现场不归档供排查与 resume;
|
|
1064
|
+
// 'completed' / 信号对象(顶层游离 break/continue)→ 正常完成。
|
|
1065
|
+
if (flow === 'halted')
|
|
656
1066
|
return;
|
|
1067
|
+
const missingArtifacts = declaredArtifacts.filter((a) => !existsSync(resolve(runCtx.cwd, a)));
|
|
1068
|
+
if (missingArtifacts.length > 0) {
|
|
1069
|
+
const why = `工作流声明的产物缺失:${missingArtifacts.join('、')}` +
|
|
1070
|
+
`——所有步骤执行完毕但目标产物未落盘,不标记完成(保留现场供排查/续作)`;
|
|
1071
|
+
await state.recordFailure('<finalize>', why);
|
|
1072
|
+
yield { type: 'workflow_step_end', id: '<finalize>', ok: false, error: why };
|
|
1073
|
+
yield { type: 'error', error: `Workflow halted: ${why}` };
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
657
1076
|
// 完成态也给一条 plugin_progress(放在 workflow_done 之前,保证
|
|
658
1077
|
// workflow_done 仍是事件流最后一条——e2e 测试依赖 events[last]===workflow_done)。
|
|
659
1078
|
yield {
|
|
@@ -667,26 +1086,13 @@ export async function* runWorkflow(def, inputs, ctx) {
|
|
|
667
1086
|
// agent/llm/skill 步已通过 runQuery(TAOR) 直接写入共享 ctx.history,
|
|
668
1087
|
// 每步的完整对话上下文(user→assistant→tool→...)都自然积累在 history 中。
|
|
669
1088
|
// 后续对话和用户调整都能看到完整的工作流执行过程。
|
|
1089
|
+
await state.markDone();
|
|
670
1090
|
yield { type: 'workflow_done', name: def.name, vars: vars.snapshot() };
|
|
671
1091
|
succeeded = true;
|
|
672
1092
|
}
|
|
673
1093
|
finally {
|
|
674
|
-
// 成功时归档(保留 vars.json/progress.md
|
|
1094
|
+
// 成功时归档(保留 vars.json/progress.md/checkpoints 供后续回溯);失败/中断时保留原状供排查与 resume
|
|
675
1095
|
if (succeeded)
|
|
676
1096
|
await state.archive();
|
|
677
1097
|
}
|
|
678
1098
|
}
|
|
679
|
-
/**
|
|
680
|
-
* parallel fork 用:把 runSteps 的 yield 流收集到 buffer 数组,
|
|
681
|
-
* 让外层 Promise.all 拿到完整事件后再顺序 yield。
|
|
682
|
-
*
|
|
683
|
-
* 注:for-await 无法读取 generator 的 return value(JS 限制),
|
|
684
|
-
* 所以"是否 halted"靠"buffer 里有 error event"近似判断 —— 这符合
|
|
685
|
-
* runSteps 的语义(halt 一定会 yield error / interrupted)。
|
|
686
|
-
*/
|
|
687
|
-
async function collectStepsToBuffer(steps, vars, ctx, state, buffer) {
|
|
688
|
-
for await (const ev of runSteps(steps, vars, ctx, state)) {
|
|
689
|
-
buffer.push(ev);
|
|
690
|
-
}
|
|
691
|
-
return buffer.some((ev) => ev.type === 'error' || ev.type === 'interrupted');
|
|
692
|
-
}
|