minimal-agent 0.8.2 → 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 +14 -12
- 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 +3 -1
- package/plugins/workflow-runner/src/agentCommon.js +249 -51
- package/plugins/workflow-runner/src/dslManifest.js +72 -0
- package/plugins/workflow-runner/src/expressions.js +198 -15
- package/plugins/workflow-runner/src/index.js +40 -8
- package/plugins/workflow-runner/src/jsonPayload.js +1 -1
- package/plugins/workflow-runner/src/loader.js +286 -72
- package/plugins/workflow-runner/src/multimodal.js +37 -0
- package/plugins/workflow-runner/src/recovery.js +49 -39
- package/plugins/workflow-runner/src/runner.js +802 -287
- package/plugins/workflow-runner/src/stepExecutors/agent.js +157 -39
- package/plugins/workflow-runner/src/stepExecutors/assert.js +16 -3
- package/plugins/workflow-runner/src/stepExecutors/llm.js +60 -111
- package/plugins/workflow-runner/src/stepExecutors/skill.js +47 -47
- package/plugins/workflow-runner/src/stepExecutors/tool.js +76 -10
- package/plugins/workflow-runner/src/stepExecutors/verify.js +27 -10
- package/plugins/workflow-runner/src/taskIdentity.js +296 -0
- package/plugins/workflow-runner/src/types.js +19 -9
- package/plugins/workflow-runner/src/workflowState.js +516 -33
- package/skills/adversarial-review/SKILL.md +0 -1
- 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 +97 -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 +7 -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 +28 -15
- package/src/llm/client.js +394 -159
- package/src/llm/providers/anthropicFormat.js +241 -0
- package/src/llm/providers/openaiFormat.js +190 -0
- package/src/loop.js +219 -32
- 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/pluginLoader.js +2 -1
- package/src/plugins/pluginRunner.js +49 -21
- package/src/plugins/transcript.js +4 -3
- package/src/prompts/pluginList.js +5 -5
- package/src/prompts/projectInstructions.js +3 -3
- package/src/prompts/promptAssembly.js +133 -144
- package/src/prompts/skillList.js +12 -22
- package/src/prompts/system.js +0 -1
- 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 +29 -10
- package/src/ui/InputBox.js +104 -12
- 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 +358 -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 +13 -5
- package/workflows/book-review-short.yaml +36 -10
- package/workflows/e2e-write-greet.yaml +6 -2
- package/workflows/illustrate.yaml +227 -0
- package/workflows/plan.yaml +460 -179
- package/workflows/ppt.yaml +1659 -0
- package/workflows/ralph-loop.yaml +199 -0
- package/workflows/schema.json +81 -23
- package/workflows/webppt.yaml +496 -0
- package/workflows/word.yaml +710 -0
- package/workflows/youtube-shorts.yaml +46 -15
- package/MINIMAL-TRACE.md +0 -77
- package/plugins/HOW-TO-WRITE-A-PLUGIN.md +0 -186
- package/plugins/ralph-wiggum/.claude-plugin/plugin.json +0 -9
- package/plugins/ralph-wiggum/README.md +0 -197
- package/plugins/ralph-wiggum/commands/cancel-ralph.md +0 -18
- package/plugins/ralph-wiggum/commands/help.md +0 -137
- package/plugins/ralph-wiggum/commands/ralph-loop.md +0 -85
- package/plugins/ralph-wiggum/hooks/hooks.json +0 -15
- package/plugins/ralph-wiggum/hooks/stop-hook.sh +0 -191
- package/plugins/ralph-wiggum/plugin.js +0 -280
- package/plugins/ralph-wiggum/src/goalState.js +0 -258
- package/plugins/ralph-wiggum/src/sentinels.js +0 -76
- package/plugins/ralph-wiggum/src/stopHookRunner.js +0 -104
- package/plugins/ralph-wiggum/src/verificationGate.js +0 -202
- package/plugins/workflow-runner/src/miniReAct.js +0 -223
- 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/src/ui/ProgressPanel.js +0 -54
- package/src/ui/StreamingBlock.js +0 -11
- package/src/ui/ToolStatus.js +0 -11
|
@@ -11,29 +11,31 @@
|
|
|
11
11
|
*
|
|
12
12
|
* 支持的 step kind(loader 已校验):
|
|
13
13
|
* - tool / llm / assert —— 原子动作(execXxxStep 返回 StepResult)
|
|
14
|
-
* - agent / skill —— 有界 TAOR:
|
|
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>"
|
|
@@ -41,7 +43,78 @@ import { WorkflowState } from './workflowState.js';
|
|
|
41
43
|
* (-p 非交互模式另由 src/cli/print.ts 把通用 warning 事件落 stderr。)
|
|
42
44
|
*/
|
|
43
45
|
const WF_PLUGIN_ID = 'workflow-runner';
|
|
46
|
+
/**
|
|
47
|
+
* 从工作流 inputs 定义 + 实际值中提取用户的原始目标,
|
|
48
|
+
* 构建紧凑的目标锚定字符串。
|
|
49
|
+
*
|
|
50
|
+
* 提取策略:
|
|
51
|
+
* 1. 优先取名为 "goal" 的输入(plan.yaml / ralph-loop.yaml 等通用约定)
|
|
52
|
+
* 2. 其次取第一个 required: true 的 string 类型输入
|
|
53
|
+
* 3. 都没有 → 返回空字符串(executor 自动跳过)
|
|
54
|
+
*
|
|
55
|
+
* 锚定字符串格式紧凑(~60-150 字符),注入每步 prompt 前置,
|
|
56
|
+
* 确保 LLM 在任何步骤都能"看到"用户原始目标,避免长上下文稀释。
|
|
57
|
+
*/
|
|
58
|
+
function buildGoalAnchor(inputDefs, inputValues) {
|
|
59
|
+
let goalValue;
|
|
60
|
+
// 策略 1:优先 "goal"
|
|
61
|
+
const goalInput = inputDefs.find((d) => d.name === 'goal' && d.type === 'string');
|
|
62
|
+
if (goalInput) {
|
|
63
|
+
const v = inputValues[goalInput.name];
|
|
64
|
+
if (typeof v === 'string' && v.trim()) {
|
|
65
|
+
goalValue = v.trim();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// 策略 2:fallback 第一个 required string
|
|
69
|
+
if (!goalValue) {
|
|
70
|
+
const firstRequired = inputDefs.find((d) => d.required && d.type === 'string');
|
|
71
|
+
if (firstRequired) {
|
|
72
|
+
const v = inputValues[firstRequired.name];
|
|
73
|
+
if (typeof v === 'string' && v.trim()) {
|
|
74
|
+
goalValue = v.trim();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!goalValue)
|
|
79
|
+
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);
|
|
114
|
+
}
|
|
44
115
|
function stepKind(step) {
|
|
116
|
+
if (step.type === 'break')
|
|
117
|
+
return 'break';
|
|
45
118
|
if (step.type)
|
|
46
119
|
return step.type;
|
|
47
120
|
if (step.tool)
|
|
@@ -65,20 +138,41 @@ function readPath(root, path) {
|
|
|
65
138
|
return cur;
|
|
66
139
|
}
|
|
67
140
|
function bindCapture(captureMap, result, vars) {
|
|
68
|
-
for (const [resultField, varName] of Object.entries(captureMap)) {
|
|
141
|
+
for (const [resultField, varName] of Object.entries(normalizeCapture(captureMap))) {
|
|
69
142
|
const value = resultField.includes('.')
|
|
70
143
|
? readPath(result.raw, resultField)
|
|
71
144
|
: result.raw[resultField];
|
|
72
145
|
vars.set(varName, value);
|
|
73
146
|
}
|
|
74
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
|
+
}
|
|
75
169
|
/**
|
|
76
170
|
* 分发"动作"型 step(tool / agent / llm / skill / assert)到对应 executor。
|
|
77
|
-
* 控制流 step(branch / loop / pause
|
|
171
|
+
* 控制流 step(branch / loop / pause 等)由 runSteps 直接处理,不走这里。
|
|
78
172
|
*/
|
|
79
173
|
async function* dispatchActionStep(step, vars, ctx) {
|
|
80
174
|
if (step.type === 'assert')
|
|
81
|
-
return execAssertStep(step, vars);
|
|
175
|
+
return execAssertStep(step, vars, ctx);
|
|
82
176
|
if (step.type === 'verify')
|
|
83
177
|
return await execVerifyStep(step, vars);
|
|
84
178
|
if (step.tool)
|
|
@@ -92,17 +186,34 @@ async function* dispatchActionStep(step, vars, ctx) {
|
|
|
92
186
|
throw new Error(`step ${step.id}: 无可执行字段(tool/agent/llm/skill/assert/verify)`);
|
|
93
187
|
}
|
|
94
188
|
/**
|
|
95
|
-
* 递归执行 steps[]。返回
|
|
189
|
+
* 递归执行 steps[]。返回 StepFlow(completed / halted / break|continue 信号,见上方定义)。
|
|
96
190
|
*
|
|
97
191
|
* yields 混合事件:WorkflowEvent + LoopEvent(skill 子循环转发 / error / interrupted)。
|
|
98
192
|
* index/total 字段相对**当前 block**,嵌套 block 自有计数(branch.then 内 3 步就报 total=3)。
|
|
99
193
|
*/
|
|
100
|
-
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) {
|
|
101
203
|
for (let i = 0; i < steps.length; i++) {
|
|
102
204
|
const step = steps[i];
|
|
103
205
|
if (ctx.signal?.aborted) {
|
|
104
206
|
yield { type: 'interrupted' };
|
|
105
|
-
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;
|
|
106
217
|
}
|
|
107
218
|
// when 条件
|
|
108
219
|
if (step.when) {
|
|
@@ -122,7 +233,7 @@ async function* runSteps(steps, vars, ctx, state) {
|
|
|
122
233
|
type: 'error',
|
|
123
234
|
error: `Workflow halted at step "${step.id}": ${errMsg}`,
|
|
124
235
|
};
|
|
125
|
-
return
|
|
236
|
+
return 'halted';
|
|
126
237
|
}
|
|
127
238
|
if (!pass) {
|
|
128
239
|
yield {
|
|
@@ -136,23 +247,39 @@ async function* runSteps(steps, vars, ctx, state) {
|
|
|
136
247
|
}
|
|
137
248
|
const kind = stepKind(step);
|
|
138
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;
|
|
139
255
|
yield {
|
|
140
256
|
type: 'workflow_step_start',
|
|
141
257
|
id: step.id,
|
|
142
258
|
kind,
|
|
143
259
|
index: i,
|
|
144
260
|
total: steps.length,
|
|
261
|
+
title: interpolatedTitle,
|
|
145
262
|
};
|
|
146
|
-
// P1 TUI 可见:把私有 workflow_step_start 镜像成框架通用 plugin_progress
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
+
}
|
|
156
283
|
// ---------- 控制流:branch ----------
|
|
157
284
|
if (step.type === 'branch') {
|
|
158
285
|
let cond = false;
|
|
@@ -164,13 +291,14 @@ async function* runSteps(steps, vars, ctx, state) {
|
|
|
164
291
|
await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
|
|
165
292
|
yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
|
|
166
293
|
yield { type: 'error', error: `Workflow halted at step "${step.id}": ${errMsg}` };
|
|
167
|
-
return
|
|
294
|
+
return 'halted';
|
|
168
295
|
}
|
|
169
296
|
const branch = cond ? step.then : step.else;
|
|
170
297
|
if (Array.isArray(branch) && branch.length > 0) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
298
|
+
// branch 对 break 透明:子块 break/halt 都原样上抛给最近的循环 / 顶层。
|
|
299
|
+
const r = yield* runSteps(branch, vars, ctx, state);
|
|
300
|
+
if (r !== 'completed')
|
|
301
|
+
return r;
|
|
174
302
|
}
|
|
175
303
|
await state.appendProgress(`✓ ${step.id} (branch=${cond})`);
|
|
176
304
|
await state.writeVars(vars.snapshot());
|
|
@@ -182,8 +310,170 @@ async function* runSteps(steps, vars, ctx, state) {
|
|
|
182
310
|
};
|
|
183
311
|
continue;
|
|
184
312
|
}
|
|
185
|
-
// ---------- 控制流:
|
|
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
|
+
}
|
|
356
|
+
// ---------- 控制流:loop(for-each 或 while) ----------
|
|
186
357
|
if (step.type === 'loop') {
|
|
358
|
+
const isWhileMode = step.over === undefined;
|
|
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
|
+
}
|
|
375
|
+
const useFreshContext = step.fresh_context === true;
|
|
376
|
+
// fresh context:循环开始时 snapshot 当前 history
|
|
377
|
+
let baseHistorySnapshot = null;
|
|
378
|
+
if (useFreshContext) {
|
|
379
|
+
baseHistorySnapshot = ctx.history.slice();
|
|
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;
|
|
402
|
+
if (isWhileMode) {
|
|
403
|
+
// ===== WHILE 循环模式 =====
|
|
404
|
+
const asName = step.as ?? '_iter';
|
|
405
|
+
let iterCount = resumeIterStart;
|
|
406
|
+
let shouldBreak = false;
|
|
407
|
+
while (iterCount < maxIter) {
|
|
408
|
+
if (ctx.signal?.aborted) {
|
|
409
|
+
yield { type: 'interrupted' };
|
|
410
|
+
return 'halted';
|
|
411
|
+
}
|
|
412
|
+
// 注入迭代变量
|
|
413
|
+
vars.push();
|
|
414
|
+
vars.set(asName, iterCount);
|
|
415
|
+
vars.set(`${asName}_idx`, iterCount);
|
|
416
|
+
// fresh context:每轮重置 history 到循环入口快照
|
|
417
|
+
if (useFreshContext && baseHistorySnapshot) {
|
|
418
|
+
ctx.history.length = 0;
|
|
419
|
+
ctx.history.push(...baseHistorySnapshot);
|
|
420
|
+
}
|
|
421
|
+
// 迭代序号对用户有真实意义(第几轮重做),title 步也保留,只换人话名。
|
|
422
|
+
yield {
|
|
423
|
+
type: 'plugin_progress',
|
|
424
|
+
pluginId: WF_PLUGIN_ID,
|
|
425
|
+
current: iterCount + 1,
|
|
426
|
+
max: maxIter,
|
|
427
|
+
message: step.title
|
|
428
|
+
? `${interpolate(step.title, vars)}·第 ${iterCount + 1}/${maxIter} 轮`
|
|
429
|
+
: `while-loop ${step.id} 迭代 ${iterCount + 1}/${maxIter}`,
|
|
430
|
+
};
|
|
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
|
+
};
|
|
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();
|
|
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
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const whyEnd = shouldBreak ? 'break 退出' : `达到上限 ${maxIter}`;
|
|
461
|
+
await state.appendProgress(`✓ ${step.id} (while ×${iterCount}, ${whyEnd})`);
|
|
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
|
+
}
|
|
468
|
+
yield {
|
|
469
|
+
type: 'workflow_step_end',
|
|
470
|
+
id: step.id,
|
|
471
|
+
ok: true,
|
|
472
|
+
output: `while loop ${iterCount} iterations (${whyEnd})`,
|
|
473
|
+
};
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
// ===== FOR-EACH 循环模式(原有逻辑) =====
|
|
187
477
|
let arr;
|
|
188
478
|
try {
|
|
189
479
|
arr = evalExpr(step.over ?? '[]', vars);
|
|
@@ -193,33 +483,50 @@ async function* runSteps(steps, vars, ctx, state) {
|
|
|
193
483
|
await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
|
|
194
484
|
yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
|
|
195
485
|
yield { type: 'error', error: `Workflow halted at step "${step.id}": ${errMsg}` };
|
|
196
|
-
return
|
|
486
|
+
return 'halted';
|
|
197
487
|
}
|
|
198
488
|
if (!Array.isArray(arr)) {
|
|
199
489
|
const errMsg = `loop over 必须求值为数组,得到 ${typeof arr}`;
|
|
200
490
|
await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
|
|
201
491
|
yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
|
|
202
492
|
yield { type: 'error', error: `Workflow halted at step "${step.id}": ${errMsg}` };
|
|
203
|
-
return
|
|
493
|
+
return 'halted';
|
|
204
494
|
}
|
|
205
495
|
const asName = step.as ?? 'item';
|
|
206
496
|
let iterCount = 0;
|
|
207
|
-
for (let idx =
|
|
497
|
+
for (let idx = resumeIterStart; idx < arr.length; idx++) {
|
|
208
498
|
if (ctx.signal?.aborted) {
|
|
209
499
|
yield { type: 'interrupted' };
|
|
210
|
-
return
|
|
500
|
+
return 'halted';
|
|
211
501
|
}
|
|
212
502
|
vars.push();
|
|
213
503
|
vars.set(asName, arr[idx]);
|
|
214
504
|
vars.set(`${asName}_idx`, idx);
|
|
215
|
-
|
|
505
|
+
// fresh context 也支持 for-each 模式
|
|
506
|
+
if (useFreshContext && baseHistorySnapshot) {
|
|
507
|
+
ctx.history.length = 0;
|
|
508
|
+
ctx.history.push(...baseHistorySnapshot);
|
|
509
|
+
}
|
|
510
|
+
const innerStart = pendingInnerStart;
|
|
511
|
+
pendingInnerStart = 0;
|
|
512
|
+
const r = yield* runSteps(step.do ?? [], vars, ctx, state, innerCpFor(idx), innerStart);
|
|
216
513
|
vars.pop();
|
|
217
|
-
if (childHalted)
|
|
218
|
-
return true;
|
|
219
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
|
+
}
|
|
220
524
|
}
|
|
221
525
|
await state.appendProgress(`✓ ${step.id} (loop ×${iterCount})`);
|
|
222
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
|
+
}
|
|
223
530
|
yield {
|
|
224
531
|
type: 'workflow_step_end',
|
|
225
532
|
id: step.id,
|
|
@@ -228,258 +535,379 @@ async function* runSteps(steps, vars, ctx, state) {
|
|
|
228
535
|
};
|
|
229
536
|
continue;
|
|
230
537
|
}
|
|
231
|
-
// ---------- 控制流:pause ----------
|
|
538
|
+
// ---------- 控制流:pause(真 HITL 审批闸门) ----------
|
|
232
539
|
//
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
//
|
|
540
|
+
// 经 AskUserQuestion/askBridge 实现真停等:TUI 弹选择界面、用户拍板后继续;
|
|
541
|
+
// 非交互宿主(-p/gateway/测试)askBridge 自动选推荐项(继续执行)且 stderr
|
|
542
|
+
// 注记,绝不挂起——与 -p 单行 JSON 契约兼容。「等用户」骑在既有
|
|
543
|
+
// tool_call→tool_result 回路上(同 AskUserQuestion 工具),引擎零新通道。
|
|
236
544
|
if (step.type === 'pause') {
|
|
237
545
|
const msg = step.prompt
|
|
238
546
|
? interpolate(step.prompt, vars)
|
|
239
|
-
: '
|
|
240
|
-
await state.appendProgress(`- ${step.id} pause:
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
// (-p 模式由 print.ts 落 stderr,权威通道)并镜像 plugin_progress(TUI 状态行可见),
|
|
244
|
-
// 避免审批闸门被悄悄跳过。真·HITL pause 属 P4,本轮不做。
|
|
245
|
-
const warnMsg = `⚠ pause "${step.id}" 在非交互模式被自动跳过,未真正等待人工确认——若这是审批闸门请勿依赖它。提示:${msg}`;
|
|
246
|
-
yield { type: 'warning', message: warnMsg };
|
|
247
|
-
yield {
|
|
248
|
-
type: 'plugin_progress',
|
|
249
|
-
pluginId: WF_PLUGIN_ID,
|
|
250
|
-
current: 0,
|
|
251
|
-
message: warnMsg,
|
|
252
|
-
};
|
|
253
|
-
yield {
|
|
254
|
-
type: 'workflow_step_skipped',
|
|
255
|
-
id: step.id,
|
|
256
|
-
reason: `pause: ${msg}`,
|
|
257
|
-
};
|
|
258
|
-
yield {
|
|
259
|
-
type: 'workflow_step_end',
|
|
260
|
-
id: step.id,
|
|
261
|
-
ok: true,
|
|
262
|
-
output: 'paused (skipped in non-interactive mode)',
|
|
263
|
-
};
|
|
264
|
-
continue;
|
|
265
|
-
}
|
|
266
|
-
// ---------- 控制流:parallel ----------
|
|
267
|
-
//
|
|
268
|
-
// 每个 fork 独立 VarStack 副本 + 独立事件缓冲;Promise.all 并发执行。
|
|
269
|
-
// 子 fork 的事件先收进 buffer,再按 fork 顺序一次性 yield,保 UI
|
|
270
|
-
// 时间轴可读(并发 yield 会导致 UI 拿到乱序事件)。
|
|
271
|
-
// 任一 fork halt → 整个 parallel halt。
|
|
272
|
-
if (step.type === 'parallel') {
|
|
273
|
-
const forks = step.fork ?? [];
|
|
274
|
-
const results = await Promise.all(forks.map(async (forkStep) => {
|
|
275
|
-
const forkVars = vars.fork();
|
|
276
|
-
const events = [];
|
|
277
|
-
let halted = false;
|
|
278
|
-
try {
|
|
279
|
-
halted = await collectStepsToBuffer([forkStep], forkVars, ctx, state, events);
|
|
280
|
-
}
|
|
281
|
-
catch (e) {
|
|
282
|
-
halted = true;
|
|
283
|
-
events.push({
|
|
284
|
-
type: 'error',
|
|
285
|
-
error: `fork ${forkStep.id} 抛异常: ${e.message}`,
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
return {
|
|
289
|
-
id: forkStep.id,
|
|
290
|
-
events,
|
|
291
|
-
vars: forkVars.snapshot(),
|
|
292
|
-
halted,
|
|
293
|
-
};
|
|
294
|
-
}));
|
|
295
|
-
// 顺序 yield 所有 fork 事件
|
|
296
|
-
for (const r of results) {
|
|
297
|
-
for (const ev of r.events)
|
|
298
|
-
yield ev;
|
|
299
|
-
}
|
|
300
|
-
if (results.some((r) => r.halted)) {
|
|
301
|
-
await state.appendProgress(`✗ ${step.id}: parallel: 至少一个 fork 失败`);
|
|
547
|
+
: '工作流暂停点:请确认是否继续执行后续步骤。';
|
|
548
|
+
await state.appendProgress(`- ${step.id} pause: 等待用户确认`);
|
|
549
|
+
// pause 会驻留状态行很久(等用户),title 步同样不带块内序号防误读。
|
|
550
|
+
if (step.title) {
|
|
302
551
|
yield {
|
|
303
|
-
type: '
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
error: 'parallel: 至少一个 fork 失败',
|
|
552
|
+
type: 'plugin_progress',
|
|
553
|
+
pluginId: WF_PLUGIN_ID,
|
|
554
|
+
message: `⏸ ${interpolate(step.title, vars)}:等待用户确认…`,
|
|
307
555
|
};
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
308
558
|
yield {
|
|
309
|
-
type: '
|
|
310
|
-
|
|
559
|
+
type: 'plugin_progress',
|
|
560
|
+
pluginId: WF_PLUGIN_ID,
|
|
561
|
+
current: i + 1,
|
|
562
|
+
max: steps.length,
|
|
563
|
+
message: `⏸ 审批闸门 ${step.id}:等待用户确认…`,
|
|
311
564
|
};
|
|
312
|
-
return true;
|
|
313
565
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
await state.writeVars(vars.snapshot());
|
|
323
|
-
yield {
|
|
324
|
-
type: 'workflow_step_end',
|
|
325
|
-
id: step.id,
|
|
326
|
-
ok: true,
|
|
327
|
-
output: aggResult.preview,
|
|
328
|
-
};
|
|
329
|
-
continue;
|
|
330
|
-
}
|
|
331
|
-
// ---------- 控制流:vote ----------
|
|
332
|
-
//
|
|
333
|
-
// 对每个 input 数组项跑 N 个 voter(同 prompt + refuted: boolean),
|
|
334
|
-
// refuted 票数 >= threshold 则视为驳回(item 不进 survived)。
|
|
335
|
-
// 心理学定位:voter 倾向于 refuted=true(找漏洞),所以阈值算"驳回票数"。
|
|
336
|
-
if (step.type === 'vote') {
|
|
337
|
-
const voters_count = step.voters_count ?? 3;
|
|
338
|
-
const threshold = step.threshold ?? Math.ceil(voters_count / 2);
|
|
339
|
-
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';
|
|
340
574
|
try {
|
|
341
|
-
|
|
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
|
+
}
|
|
342
611
|
}
|
|
343
|
-
catch
|
|
344
|
-
|
|
345
|
-
await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
|
|
346
|
-
yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
|
|
347
|
-
yield {
|
|
348
|
-
type: 'error',
|
|
349
|
-
error: `Workflow halted at step "${step.id}": ${errMsg}`,
|
|
350
|
-
};
|
|
351
|
-
return true;
|
|
612
|
+
catch {
|
|
613
|
+
askFailed = true; // 桥不可用 → 放行 + 响亮警告(保持旧"不挂起"行为)
|
|
352
614
|
}
|
|
353
|
-
if (
|
|
354
|
-
const errMsg = `vote.input 必须求值为数组,得到 ${typeof inputArr}`;
|
|
355
|
-
await state.appendProgress(`✗ ${step.id}: ${errMsg}`);
|
|
356
|
-
yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
|
|
615
|
+
if (askFailed) {
|
|
357
616
|
yield {
|
|
358
|
-
type: '
|
|
359
|
-
|
|
617
|
+
type: 'warning',
|
|
618
|
+
message: `⚠ pause "${step.id}" 审批闸门不可用(提问桥异常),已自动放行。提示:${msg}`,
|
|
360
619
|
};
|
|
361
|
-
return true;
|
|
362
620
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
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';
|
|
374
631
|
}
|
|
375
|
-
|
|
632
|
+
// 带补充要求的放行:自由输入并入贯穿目标(后续每步都看到,不止 capture 变量)
|
|
633
|
+
if (freeText)
|
|
634
|
+
await addGoalSupplement(ctx, state, freeText);
|
|
635
|
+
const pauseResult = {
|
|
376
636
|
raw: {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
637
|
+
result: freeText,
|
|
638
|
+
text: freeText,
|
|
639
|
+
approved: true,
|
|
640
|
+
free_text: freeText,
|
|
380
641
|
},
|
|
381
|
-
preview:
|
|
642
|
+
preview: freeText
|
|
643
|
+
? `审批通过:继续执行;补充要求:${truncate(freeText, 120)}`
|
|
644
|
+
: '审批通过:继续执行',
|
|
382
645
|
};
|
|
383
646
|
if (step.capture)
|
|
384
|
-
bindCapture(step.capture,
|
|
385
|
-
await state.appendProgress(`✓ ${step.id} (vote ${survived.length}/${inputArr.length})`);
|
|
647
|
+
bindCapture(step.capture, pauseResult, vars);
|
|
386
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: 已确认继续)`);
|
|
387
653
|
yield {
|
|
388
654
|
type: 'workflow_step_end',
|
|
389
655
|
id: step.id,
|
|
390
656
|
ok: true,
|
|
391
|
-
output:
|
|
657
|
+
output: pauseResult.preview,
|
|
392
658
|
};
|
|
393
659
|
continue;
|
|
394
660
|
}
|
|
395
|
-
// ----------
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
661
|
+
// ---------- 控制流:break / continue(循环信号) ----------
|
|
662
|
+
//
|
|
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;
|
|
670
|
+
if (step.when) {
|
|
671
|
+
try {
|
|
672
|
+
shouldFire = Boolean(evalExpr(step.when, vars));
|
|
673
|
+
}
|
|
674
|
+
catch (e) {
|
|
675
|
+
// when 表达式错误 → 不触发,当 skipped 处理
|
|
676
|
+
shouldFire = false;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
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 };
|
|
684
|
+
}
|
|
685
|
+
// 条件不满足 → 当 skip 处理,继续下一步
|
|
402
686
|
yield {
|
|
403
|
-
type: '
|
|
687
|
+
type: 'workflow_step_skipped',
|
|
404
688
|
id: step.id,
|
|
405
|
-
|
|
406
|
-
output: result.preview,
|
|
689
|
+
reason: `${step.type} 条件不满足`,
|
|
407
690
|
};
|
|
691
|
+
continue;
|
|
408
692
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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) {
|
|
416
703
|
yield {
|
|
417
704
|
type: 'warning',
|
|
418
|
-
message: `step "${step.id}"
|
|
705
|
+
message: `step "${step.id}": 模板变量未定义,已按空串插值:${[...new Set(missing)].map((m) => `\${${m}}`).join('、')}`,
|
|
419
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
|
+
}
|
|
713
|
+
yield {
|
|
714
|
+
type: 'workflow_step_end',
|
|
715
|
+
id: step.id,
|
|
716
|
+
ok: true,
|
|
717
|
+
output: truncate(text, 200),
|
|
718
|
+
};
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
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
|
+
}
|
|
420
746
|
yield {
|
|
421
|
-
type: '
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
message: `↻ 恢复 ${step.id}…`,
|
|
747
|
+
type: 'workflow_step_end',
|
|
748
|
+
id: step.id,
|
|
749
|
+
ok: true,
|
|
750
|
+
output: result.preview,
|
|
426
751
|
};
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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; // 重跑同一步
|
|
431
790
|
}
|
|
432
|
-
|
|
433
|
-
//
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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);
|
|
439
833
|
yield {
|
|
440
834
|
type: 'workflow_step_end',
|
|
441
835
|
id: step.id,
|
|
442
|
-
ok:
|
|
443
|
-
|
|
836
|
+
ok: false,
|
|
837
|
+
error: combined,
|
|
444
838
|
};
|
|
445
|
-
|
|
839
|
+
yield {
|
|
840
|
+
type: 'error',
|
|
841
|
+
error: `Workflow halted at step "${step.id}": ${combined}`,
|
|
842
|
+
};
|
|
843
|
+
return 'halted';
|
|
844
|
+
}
|
|
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; // 跳过本步 → 进入下一步
|
|
446
869
|
}
|
|
447
|
-
const
|
|
870
|
+
const finalErrMsg = isMaxTurnsCutoff(errMsg)
|
|
871
|
+
? `${errMsg};重试额度已用尽,请拆分该步骤或提高 maxTurns。`
|
|
872
|
+
: errMsg;
|
|
873
|
+
if (cp)
|
|
874
|
+
await state.recordFailure(step.id, finalErrMsg);
|
|
448
875
|
yield {
|
|
449
876
|
type: 'workflow_step_end',
|
|
450
877
|
id: step.id,
|
|
451
878
|
ok: false,
|
|
452
|
-
error:
|
|
879
|
+
error: finalErrMsg,
|
|
453
880
|
};
|
|
454
881
|
yield {
|
|
455
882
|
type: 'error',
|
|
456
|
-
error: `Workflow halted at step "${step.id}": ${
|
|
883
|
+
error: `Workflow halted at step "${step.id}": ${finalErrMsg}`,
|
|
457
884
|
};
|
|
458
|
-
return
|
|
885
|
+
return 'halted';
|
|
459
886
|
}
|
|
460
|
-
if (step.onError === 'continue') {
|
|
461
|
-
yield {
|
|
462
|
-
type: 'workflow_step_end',
|
|
463
|
-
id: step.id,
|
|
464
|
-
ok: false,
|
|
465
|
-
error: errMsg,
|
|
466
|
-
};
|
|
467
|
-
continue;
|
|
468
|
-
}
|
|
469
|
-
yield {
|
|
470
|
-
type: 'workflow_step_end',
|
|
471
|
-
id: step.id,
|
|
472
|
-
ok: false,
|
|
473
|
-
error: errMsg,
|
|
474
|
-
};
|
|
475
|
-
yield {
|
|
476
|
-
type: 'error',
|
|
477
|
-
error: `Workflow halted at step "${step.id}": ${errMsg}`,
|
|
478
|
-
};
|
|
479
|
-
return true;
|
|
480
887
|
}
|
|
481
888
|
}
|
|
482
|
-
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];
|
|
483
911
|
}
|
|
484
912
|
/**
|
|
485
913
|
* 运行一个 workflow。
|
|
@@ -499,26 +927,152 @@ export async function* runWorkflow(def, inputs, ctx) {
|
|
|
499
927
|
// onError:'recover' 用:把工作流总目标(description)透传给恢复 agent,
|
|
500
928
|
// 让它知道「这条工作流到底想干什么」。已有 goal 则不覆盖。
|
|
501
929
|
const runCtx = { ...ctx, goal: ctx.goal ?? def.description };
|
|
502
|
-
|
|
503
|
-
|
|
930
|
+
// 通用目标锚定:从 inputs 中提取用户的原始目标,
|
|
931
|
+
// 注入 RunContext.goalAnchor,让 agent/llm/skill 步自动前置到每步 prompt。
|
|
932
|
+
// 提取策略:优先 "goal" 命名的输入 → 其次第一个 required string 输入。
|
|
933
|
+
runCtx.goalAnchor = buildGoalAnchor(def.inputs ?? [], inputs);
|
|
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
|
+
}));
|
|
504
954
|
yield {
|
|
505
955
|
type: 'workflow_start',
|
|
506
956
|
name: def.name,
|
|
507
957
|
totalSteps: def.steps.length,
|
|
508
958
|
};
|
|
509
|
-
//
|
|
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)。
|
|
510
1053
|
yield {
|
|
511
1054
|
type: 'plugin_progress',
|
|
512
1055
|
pluginId: WF_PLUGIN_ID,
|
|
513
|
-
current:
|
|
1056
|
+
current: startFrom,
|
|
514
1057
|
max: def.steps.length,
|
|
515
1058
|
message: `▶ workflow ${def.name}`,
|
|
516
1059
|
};
|
|
517
1060
|
let succeeded = false;
|
|
518
1061
|
try {
|
|
519
|
-
const
|
|
520
|
-
|
|
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')
|
|
521
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
|
+
}
|
|
522
1076
|
// 完成态也给一条 plugin_progress(放在 workflow_done 之前,保证
|
|
523
1077
|
// workflow_done 仍是事件流最后一条——e2e 测试依赖 events[last]===workflow_done)。
|
|
524
1078
|
yield {
|
|
@@ -528,56 +1082,17 @@ export async function* runWorkflow(def, inputs, ctx) {
|
|
|
528
1082
|
max: def.steps.length,
|
|
529
1083
|
message: `✓ workflow ${def.name} 完成`,
|
|
530
1084
|
};
|
|
1085
|
+
// v0.9 重构:不再需要 assistant_message 摘要注入。
|
|
1086
|
+
// agent/llm/skill 步已通过 runQuery(TAOR) 直接写入共享 ctx.history,
|
|
1087
|
+
// 每步的完整对话上下文(user→assistant→tool→...)都自然积累在 history 中。
|
|
1088
|
+
// 后续对话和用户调整都能看到完整的工作流执行过程。
|
|
1089
|
+
await state.markDone();
|
|
531
1090
|
yield { type: 'workflow_done', name: def.name, vars: vars.snapshot() };
|
|
532
1091
|
succeeded = true;
|
|
533
|
-
// ---- 层1:写入摘要到对话历史(让后续对话记住这次 workflow 做了什么)----
|
|
534
|
-
const snapshot = vars.snapshot();
|
|
535
|
-
const summaryText = typeof snapshot.summary_text === 'string'
|
|
536
|
-
? snapshot.summary_text
|
|
537
|
-
: '';
|
|
538
|
-
// 收集关键产出物(从 vars 中提取文件路径)
|
|
539
|
-
const artifacts = [];
|
|
540
|
-
if (typeof snapshot.plan_text === 'string')
|
|
541
|
-
artifacts.push('./PLAN.md');
|
|
542
|
-
// 检查是否有执行结果(execute_plan 步的产出)
|
|
543
|
-
if (typeof snapshot.execution_result === 'string') {
|
|
544
|
-
// 尝试从执行结果中提取文件路径(常见模式:产出物路径)
|
|
545
|
-
const fileMatches = snapshot.execution_result.match(/`[^`]*\.(pptx|docx|pdf|xlsx|md|ts|js|py)[^`]*`/g);
|
|
546
|
-
if (fileMatches)
|
|
547
|
-
artifacts.push(...fileMatches.map((f) => f.replace(/`/g, '')));
|
|
548
|
-
}
|
|
549
|
-
const summaryLines = [
|
|
550
|
-
`**工作流「${def.name}」执行完成**`,
|
|
551
|
-
``,
|
|
552
|
-
`- 步骤: ${def.steps.length}/${def.steps.length} 全部完成`,
|
|
553
|
-
...(artifacts.length > 0 ? [`- 产出物: ${artifacts.join(', ')}`] : []),
|
|
554
|
-
...(summaryText ? [`- 摘要: ${summaryText}`] : []),
|
|
555
|
-
];
|
|
556
|
-
yield {
|
|
557
|
-
type: 'assistant_message',
|
|
558
|
-
message: {
|
|
559
|
-
role: 'assistant',
|
|
560
|
-
content: summaryLines.join('\n'),
|
|
561
|
-
},
|
|
562
|
-
};
|
|
563
1092
|
}
|
|
564
1093
|
finally {
|
|
565
|
-
// 成功时归档(保留 vars.json/progress.md
|
|
1094
|
+
// 成功时归档(保留 vars.json/progress.md/checkpoints 供后续回溯);失败/中断时保留原状供排查与 resume
|
|
566
1095
|
if (succeeded)
|
|
567
1096
|
await state.archive();
|
|
568
1097
|
}
|
|
569
1098
|
}
|
|
570
|
-
/**
|
|
571
|
-
* parallel fork 用:把 runSteps 的 yield 流收集到 buffer 数组,
|
|
572
|
-
* 让外层 Promise.all 拿到完整事件后再顺序 yield。
|
|
573
|
-
*
|
|
574
|
-
* 注:for-await 无法读取 generator 的 return value(JS 限制),
|
|
575
|
-
* 所以"是否 halted"靠"buffer 里有 error event"近似判断 —— 这符合
|
|
576
|
-
* runSteps 的语义(halt 一定会 yield error / interrupted)。
|
|
577
|
-
*/
|
|
578
|
-
async function collectStepsToBuffer(steps, vars, ctx, state, buffer) {
|
|
579
|
-
for await (const ev of runSteps(steps, vars, ctx, state)) {
|
|
580
|
-
buffer.push(ev);
|
|
581
|
-
}
|
|
582
|
-
return buffer.some((ev) => ev.type === 'error' || ev.type === 'interrupted');
|
|
583
|
-
}
|