minimal-agent 0.12.0 → 0.14.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 +6 -5
- package/package.json +62 -5
- package/plugins/computer_use/.claude-plugin/plugin.json +1 -1
- package/plugins/cron-runner/src/commands.js +4 -4
- package/plugins/cron-runner/src/commands.ts +4 -4
- package/plugins/workflow-runner/.claude-plugin/plugin.json +2 -2
- package/plugins/workflow-runner/commands/workflow.md +57 -90
- package/plugins/workflow-runner/commands/workflows.md +4 -4
- package/plugins/workflow-runner/plugin.js +24 -13
- package/plugins/workflow-runner/plugin.ts +28 -13
- package/plugins/workflow-runner/src/discovery.js +160 -0
- package/plugins/workflow-runner/src/discovery.ts +194 -0
- package/plugins/workflow-runner/src/goalAnchor.js +53 -0
- package/plugins/workflow-runner/src/goalAnchor.ts +59 -0
- package/plugins/workflow-runner/src/index.js +204 -185
- package/plugins/workflow-runner/src/index.ts +249 -224
- package/plugins/workflow-runner/src/inputCoercion.js +40 -0
- package/plugins/workflow-runner/src/inputCoercion.ts +43 -0
- package/plugins/workflow-runner/src/journal.js +224 -0
- package/plugins/workflow-runner/src/journal.ts +274 -0
- package/plugins/workflow-runner/src/jsonSchema.js +132 -0
- package/plugins/workflow-runner/src/jsonSchema.ts +162 -0
- package/plugins/workflow-runner/src/moduleResolution.js +64 -0
- package/plugins/workflow-runner/src/moduleResolution.ts +71 -0
- package/plugins/workflow-runner/src/resourceInputs.js +220 -0
- package/plugins/workflow-runner/src/resourceInputs.ts +300 -0
- package/plugins/workflow-runner/src/runner.js +404 -467
- package/plugins/workflow-runner/src/runner.ts +491 -535
- package/plugins/workflow-runner/src/runtime/config.js +46 -0
- package/plugins/workflow-runner/src/runtime/config.ts +69 -0
- package/plugins/workflow-runner/src/runtime/context.js +1304 -0
- package/plugins/workflow-runner/src/runtime/context.ts +1786 -0
- package/plugins/workflow-runner/src/runtime/exec.js +34 -0
- package/plugins/workflow-runner/src/runtime/exec.ts +53 -0
- package/plugins/workflow-runner/src/runtime/flow.js +8 -0
- package/plugins/workflow-runner/src/runtime/flow.ts +12 -0
- package/plugins/workflow-runner/src/runtime/fs.js +27 -0
- package/plugins/workflow-runner/src/runtime/fs.ts +41 -0
- package/plugins/workflow-runner/src/runtime/model.js +6 -0
- package/plugins/workflow-runner/src/runtime/model.ts +8 -0
- package/plugins/workflow-runner/src/runtime/output.js +7 -0
- package/plugins/workflow-runner/src/runtime/output.ts +21 -0
- package/plugins/workflow-runner/src/runtime/scheduler.js +141 -0
- package/plugins/workflow-runner/src/runtime/scheduler.ts +166 -0
- package/plugins/workflow-runner/src/runtime/state.js +16 -0
- package/plugins/workflow-runner/src/runtime/state.ts +19 -0
- package/plugins/workflow-runner/src/runtime/tools.js +3 -0
- package/plugins/workflow-runner/src/runtime/tools.ts +6 -0
- package/plugins/workflow-runner/src/sandbox.js +141 -0
- package/plugins/workflow-runner/src/sandbox.ts +182 -0
- package/plugins/workflow-runner/src/scriptGuard.d.mts +38 -0
- package/plugins/workflow-runner/src/scriptGuard.mjs +449 -0
- package/plugins/workflow-runner/src/taskIdentity.js +172 -152
- package/plugins/workflow-runner/src/taskIdentity.ts +233 -200
- package/plugins/workflow-runner/src/toolRuntime.js +29 -8
- package/plugins/workflow-runner/src/toolRuntime.ts +36 -11
- package/plugins/workflow-runner/src/types.js +7 -105
- package/plugins/workflow-runner/src/types.ts +155 -520
- package/plugins/workflow-runner/src/util.js +47 -0
- package/plugins/workflow-runner/src/util.ts +51 -0
- package/plugins/workflow-runner/src/workflowContext.js +20 -0
- package/plugins/workflow-runner/src/workflowContext.ts +58 -0
- package/plugins/workflow-runner/src/workflowDependencies.js +36 -0
- package/plugins/workflow-runner/src/workflowDependencies.ts +59 -0
- package/plugins/workflow-runner/src/workflowFingerprint.js +118 -0
- package/plugins/workflow-runner/src/workflowFingerprint.ts +122 -0
- package/plugins/workflow-runner/src/workflowState.js +542 -469
- package/plugins/workflow-runner/src/workflowState.ts +639 -589
- package/plugins/workflow-runner/test/api.test.ts +223 -0
- package/plugins/workflow-runner/test/apiCapabilities.test.ts +614 -0
- package/plugins/workflow-runner/test/apiConcurrencyGate.test.ts +104 -0
- package/plugins/workflow-runner/test/apiEcho.test.ts +210 -0
- package/plugins/workflow-runner/test/apiReadText.test.ts +178 -0
- package/plugins/workflow-runner/test/apiResourceLedger.test.ts +184 -0
- package/plugins/workflow-runner/test/childWorkflow.test.ts +304 -0
- package/plugins/workflow-runner/test/discovery.test.ts +234 -0
- package/plugins/workflow-runner/test/goalAnchor.test.ts +106 -0
- package/plugins/workflow-runner/test/goalController.test.ts +317 -0
- package/plugins/workflow-runner/test/journalLanes.test.ts +198 -0
- package/plugins/workflow-runner/test/listResources.test.ts +106 -0
- package/plugins/workflow-runner/test/resourceInputs.test.ts +294 -0
- package/plugins/workflow-runner/test/sandbox.test.ts +126 -0
- package/plugins/workflow-runner/test/scheduler.test.ts +70 -0
- package/plugins/workflow-runner/test/scriptGuard.test.ts +276 -0
- package/plugins/workflow-runner/test/skillDocSync.test.ts +78 -0
- package/plugins/workflow-runner/test/taskIdentity.test.ts +288 -179
- package/plugins/workflow-runner/test/workflowFingerprint.test.ts +106 -0
- package/plugins/workflow-runner/test/workflowState.test.ts +429 -0
- package/skills/pptx/examples/assets/cover-bg.svg +23 -0
- package/skills/pptx/examples/assets/icon-spark.svg +5 -0
- package/skills/skill-creator/scripts/run_eval.py +3 -2
- package/skills/ui-design/SKILL.md +5 -2
- package/skills/ui-design/references/layout-composition.md +1 -1
- package/skills/workflow-creator/SKILL.md +344 -0
- package/skills/workflow-creator/assets/template/workflow.mjs +48 -0
- package/skills/workflow-creator/references/api.md +216 -0
- package/skills/workflow-creator/references/migration.md +103 -0
- package/skills/workflow-creator/references/patterns.md +779 -0
- package/skills/workflow-creator/scripts/list-resources.mjs +205 -0
- package/skills/workflow-creator/scripts/scaffold.mjs +148 -0
- package/skills/workflow-creator/scripts/validate.mjs +275 -0
- package/skills/workflow-creator/scripts/workflow-creator.test.mjs +82 -0
- package/src/advisor/advisorCoordinator.js +503 -0
- package/src/advisor/advisorCoordinator.ts +657 -0
- package/src/cli/configMenu.tsx +0 -1
- package/src/cli/print.js +44 -6
- package/src/cli/print.ts +73 -4
- package/src/cli/streamJson.js +5 -6
- package/src/cli/streamJson.ts +8 -4
- package/src/cli/wizards/advisorWizard.js +4 -2
- package/src/cli/wizards/advisorWizard.tsx +8 -2
- package/src/cli/wizards/contextAdvisorWizard.js +5 -4
- package/src/cli/wizards/contextAdvisorWizard.tsx +7 -6
- package/src/collaboration/taskFlow.js +115 -0
- package/src/collaboration/taskFlow.ts +169 -0
- package/src/config/configDefaults.js +41 -1
- package/src/config/configDefaults.ts +43 -1
- package/src/config/configDiagnostics.ts +2 -2
- package/src/config/configFile.js +40 -9
- package/src/config/configFile.ts +60 -11
- package/src/config/configService.js +443 -74
- package/src/config/configService.ts +723 -142
- package/src/config/numericLimits.js +22 -0
- package/src/config/numericLimits.ts +24 -0
- package/src/config.js +1 -0
- package/src/config.ts +3 -2
- package/src/context/budgetPlan.js +1 -1
- package/src/context/budgetPlan.ts +1 -1
- package/src/context/compact.js +7 -2
- package/src/context/compact.ts +9 -2
- package/src/context/microCompactLite.js +212 -13
- package/src/context/microCompactLite.ts +261 -12
- package/src/context/sessionRegistry.js +8 -1
- package/src/context/sessionRegistry.ts +18 -5
- package/src/context/toolResultArchive.js +72 -0
- package/src/context/toolResultArchive.ts +95 -0
- package/src/goal/errors.js +8 -0
- package/src/goal/errors.ts +18 -0
- package/src/goal/goalRuntime.js +344 -0
- package/src/goal/goalRuntime.ts +466 -0
- package/src/goal/goalStore.js +200 -0
- package/src/goal/goalStore.ts +219 -0
- package/src/goal/index.js +4 -0
- package/src/goal/index.ts +4 -0
- package/src/goal/statusMapping.js +71 -0
- package/src/goal/statusMapping.ts +95 -0
- package/src/goal/types.js +7 -0
- package/src/goal/types.ts +179 -0
- package/src/goal/workflowController.js +306 -0
- package/src/goal/workflowController.ts +360 -0
- package/src/harness/evidenceLedger.js +2 -0
- package/src/harness/evidenceLedger.ts +1 -0
- package/src/harness/stallGuard.js +9 -3
- package/src/harness/stallGuard.ts +7 -3
- package/src/images/imageResolver.js +161 -0
- package/src/images/imageResolver.ts +189 -0
- package/src/infra/verification.js +56 -20
- package/src/infra/verification.ts +87 -20
- package/src/llm/client.js +79 -21
- package/src/llm/client.ts +182 -35
- package/src/llm/gatewayClient.js +201 -0
- package/src/llm/gatewayClient.ts +299 -0
- package/src/llm/messageProvenance.js +46 -0
- package/src/llm/messageProvenance.ts +58 -0
- package/src/llm/modelProvider.js +1 -0
- package/src/llm/modelProvider.ts +22 -0
- package/src/llm/providers/anthropicFormat.js +108 -1
- package/src/llm/providers/anthropicFormat.ts +115 -4
- package/src/llm/providers/openaiFormat.js +9 -0
- package/src/llm/providers/openaiFormat.ts +12 -3
- package/src/loop.js +319 -54
- package/src/loop.ts +373 -57
- package/src/main.js +4 -1
- package/src/main.tsx +4 -1
- package/src/managedExecution/index.js +2 -0
- package/src/managedExecution/index.ts +2 -0
- package/src/managedExecution/registry.js +15 -0
- package/src/managedExecution/registry.ts +20 -0
- package/src/managedExecution/types.js +1 -0
- package/src/managedExecution/types.ts +86 -0
- package/src/modelRef.js +9 -0
- package/src/modelRef.ts +31 -0
- package/src/multimodal.js +14 -28
- package/src/multimodal.ts +17 -33
- package/src/net/lanDiscovery.js +65 -0
- package/src/net/lanDiscovery.ts +82 -0
- package/src/plugin-sdk.js +28 -3
- package/src/plugin-sdk.ts +39 -6
- package/src/plugins/pluginApi.ts +16 -1
- package/src/plugins/pluginLoader.js +4 -0
- package/src/plugins/pluginLoader.ts +6 -0
- package/src/plugins/pluginRunner.js +152 -9
- package/src/plugins/pluginRunner.ts +186 -8
- package/src/plugins/slashFallback.js +113 -0
- package/src/plugins/slashFallback.ts +137 -0
- package/src/prompts/policyRegistry.js +15 -8
- package/src/prompts/policyRegistry.ts +18 -8
- package/src/prompts/promptAssembly.js +103 -12
- package/src/prompts/promptAssembly.ts +114 -12
- package/src/prompts/skillList.js +21 -3
- package/src/prompts/skillList.ts +27 -2
- package/src/prompts/stableSystemPrompt.js +18 -0
- package/src/prompts/stableSystemPrompt.ts +33 -0
- package/src/runtime/agentRuntimeContext.js +8 -2
- package/src/runtime/agentRuntimeContext.ts +17 -4
- package/src/session/runAgentTurn.js +78 -12
- package/src/session/runAgentTurn.ts +105 -23
- package/src/session/runTurn.js +10 -9
- package/src/session/runTurn.ts +17 -8
- package/src/session/sessionState.ts +2 -0
- package/src/team/auth.js +186 -0
- package/src/team/auth.ts +298 -0
- package/src/team/cli.js +105 -0
- package/src/team/cli.ts +122 -0
- package/src/team/onlineToolContract.js +64 -0
- package/src/team/onlineToolContract.ts +109 -0
- package/src/team/toolPolicy.js +36 -0
- package/src/team/toolPolicy.ts +75 -0
- package/src/tools/advisor/advisor.js +148 -37
- package/src/tools/advisor/advisor.ts +161 -38
- package/src/tools/advisor/advisorDecision.js +71 -0
- package/src/tools/advisor/advisorDecision.ts +85 -0
- package/src/tools/agent/agent.js +18 -5
- package/src/tools/agent/agent.ts +21 -7
- package/src/tools/agent/agentControl.js +246 -0
- package/src/tools/agent/agentControl.ts +284 -0
- package/src/tools/agent/agentTaskService.js +40 -1
- package/src/tools/agent/agentTaskService.ts +44 -1
- package/src/tools/agent/agentTypes.js +14 -2
- package/src/tools/agent/agentTypes.ts +15 -2
- package/src/tools/applypatch/applyPatch.js +362 -0
- package/src/tools/applypatch/applyPatch.ts +428 -0
- package/src/tools/bash/bash.js +70 -18
- package/src/tools/bash/bash.ts +75 -20
- package/src/tools/capabilities.js +10 -5
- package/src/tools/capabilities.ts +10 -5
- package/src/tools/computeruse/computerIntent.js +32 -0
- package/src/tools/computeruse/computerIntent.ts +46 -0
- package/src/tools/computeruse/telemetryLog.js +12 -0
- package/src/tools/computeruse/telemetryLog.ts +16 -0
- package/src/tools/computeruse/vision/telemetry.js +1 -0
- package/src/tools/computeruse/vision/telemetry.ts +1 -0
- package/src/tools/effectiveToolSet.js +4 -0
- package/src/tools/effectiveToolSet.ts +4 -0
- package/src/tools/index.js +21 -2
- package/src/tools/index.ts +33 -2
- package/src/tools/powershell/powershell.js +18 -1
- package/src/tools/powershell/powershell.ts +18 -2
- package/src/tools/presentresult/presentresult.js +6 -15
- package/src/tools/presentresult/presentresult.ts +6 -15
- package/src/tools/read/read.js +69 -11
- package/src/tools/read/read.ts +70 -11
- package/src/tools/shared/fileUtils.js +1 -2
- package/src/tools/shared/fileUtils.ts +1 -2
- package/src/tools/shared/permanentDeleteGuard.js +34 -3
- package/src/tools/shared/permanentDeleteGuard.ts +43 -6
- package/src/tools/shared/toolOutputStore.js +23 -1
- package/src/tools/shared/toolOutputStore.ts +22 -1
- package/src/tools/skill/skill.js +9 -3
- package/src/tools/skill/skill.ts +8 -4
- package/src/tools/toolPromptGuides.js +117 -0
- package/src/tools/toolPromptGuides.ts +137 -0
- package/src/tools/webbrowser/agentBrowserPath.js +19 -9
- package/src/tools/webbrowser/agentBrowserPath.ts +70 -28
- package/src/tools/webbrowser/browser.js +27 -11
- package/src/tools/webbrowser/browser.ts +68 -24
- package/src/tools/webbrowser/browserAutomation.js +329 -0
- package/src/tools/webbrowser/browserAutomation.ts +467 -0
- package/src/tools/webbrowser/browserAxState.js +97 -0
- package/src/tools/webbrowser/browserAxState.ts +142 -0
- package/src/tools/webbrowser/browserBatch.js +1578 -0
- package/src/tools/webbrowser/browserBatch.ts +2382 -0
- package/src/tools/webbrowser/browserCdp.js +208 -0
- package/src/tools/webbrowser/browserCdp.ts +255 -0
- package/src/tools/webbrowser/browserConditions.js +24 -0
- package/src/tools/webbrowser/browserConditions.ts +45 -0
- package/src/tools/webbrowser/browserContent.js +281 -0
- package/src/tools/webbrowser/browserContent.ts +438 -0
- package/src/tools/webbrowser/browserControl.js +131 -30
- package/src/tools/webbrowser/browserControl.ts +210 -50
- package/src/tools/webbrowser/browserDaemon.js +22 -14
- package/src/tools/webbrowser/browserDaemon.ts +54 -21
- package/src/tools/webbrowser/browserFrames.js +47 -0
- package/src/tools/webbrowser/browserFrames.ts +54 -0
- package/src/tools/webbrowser/browserInteractionResolver.js +17 -0
- package/src/tools/webbrowser/browserInteractionResolver.ts +40 -0
- package/src/tools/webbrowser/browserLocator.js +140 -0
- package/src/tools/webbrowser/browserLocator.ts +194 -0
- package/src/tools/webbrowser/browserOutcome.js +10 -17
- package/src/tools/webbrowser/browserOutcome.ts +48 -31
- package/src/tools/webbrowser/browserRecovery.js +58 -0
- package/src/tools/webbrowser/browserRecovery.ts +83 -0
- package/src/tools/webbrowser/browserRunRegistry.js +35 -3
- package/src/tools/webbrowser/browserRunRegistry.ts +91 -15
- package/src/tools/webbrowser/browserService.js +28 -2
- package/src/tools/webbrowser/browserService.ts +49 -9
- package/src/tools/webbrowser/browserStream.js +132 -33
- package/src/tools/webbrowser/browserStream.ts +224 -50
- package/src/tools/webbrowser/browserTabs.js +48 -15
- package/src/tools/webbrowser/browserTabs.ts +178 -96
- package/src/tools/webbrowser/webbrowser.js +683 -144
- package/src/tools/webbrowser/webbrowser.ts +979 -236
- package/src/tools/webfetch/preapproved.js +0 -2
- package/src/tools/webfetch/preapproved.ts +0 -2
- package/src/tools/websearch/websearch.js +129 -8
- package/src/tools/websearch/websearch.ts +138 -6
- package/src/types.ts +450 -42
- package/src/ui/App.js +377 -58
- package/src/ui/App.tsx +576 -111
- package/src/ui/LiveArea.js +14 -8
- package/src/ui/LiveArea.tsx +14 -10
- package/src/ui/MessageList.js +63 -20
- package/src/ui/MessageList.tsx +101 -16
- package/src/ui/ModelPicker.js +7 -4
- package/src/ui/ModelPicker.tsx +55 -8
- package/src/ui/hooks/useChat.js +152 -163
- package/src/ui/hooks/useChat.ts +396 -334
- package/src/ui/loginCommand.js +56 -0
- package/src/ui/loginCommand.ts +63 -0
- package/src/ui/toolProgress.js +41 -0
- package/src/ui/toolProgress.ts +44 -0
- package/src/web/browserBridge.js +20 -2
- package/src/web/browserBridge.ts +20 -2
- package/src/web/protocol.ts +1897 -194
- package/workflows/adaptive-research/workflow.mjs +86 -0
- package/workflows/e2e-capabilities/workflow.mjs +102 -0
- package/workflows/e2e-llm/workflow.mjs +52 -0
- package/workflows/e2e-smoke/workflow.mjs +59 -0
- package/workflows/e2e-vision-schema/README.md +48 -0
- package/workflows/e2e-vision-schema/workflow.mjs +149 -0
- package/workflows/e2e-write-greet/workflow.mjs +37 -0
- package/workflows/file-census/README.md +14 -0
- package/workflows/file-census/scripts/.gitkeep +2 -0
- package/workflows/file-census/workflow.mjs +110 -0
- package/workflows/goal/README.md +166 -0
- package/workflows/goal/prompts/audit.md +57 -0
- package/workflows/goal/prompts/contract.md +56 -0
- package/workflows/goal/prompts/iterate.md +37 -0
- package/workflows/goal/workflow.mjs +501 -0
- package/workflows/illustrate/README.md +90 -0
- package/workflows/illustrate/assets/engines.json +20 -0
- package/workflows/illustrate/assets/extract-baidu.js +21 -0
- package/workflows/illustrate/assets/extract-bing.js +33 -0
- package/workflows/illustrate/assets/extract-google.js +31 -0
- package/workflows/illustrate/workflow.mjs +301 -0
- package/workflows/plan/workflow.mjs +389 -0
- package/workflows/question-crops/README.md +35 -0
- package/workflows/question-crops/prompts/inventory.md +23 -0
- package/workflows/question-crops/scripts/image-ops.mjs +305 -0
- package/workflows/question-crops/scripts/image-ops.test.mjs +95 -0
- package/workflows/question-crops/workflow.mjs +172 -0
- package/workflows/ralph-loop/workflow.mjs +162 -0
- package/workflows/research/DESIGN.md +449 -0
- package/workflows/research/README.md +74 -0
- package/workflows/research/prompts/s0-goal.md +43 -0
- package/workflows/research/prompts/s1-early-exit.md +22 -0
- package/workflows/research/prompts/s1-map.md +58 -0
- package/workflows/research/prompts/s2-options.md +53 -0
- package/workflows/research/prompts/s3-deep-dive.md +56 -0
- package/workflows/research/prompts/s4-verify.md +74 -0
- package/workflows/research/prompts/s5-synthesis.md +81 -0
- package/workflows/research/prompts/s5b-fill-gaps.md +32 -0
- package/workflows/research/prompts/s6-package.md +32 -0
- package/workflows/research/scripts/build-report.mjs +98 -0
- package/workflows/research/workflow.mjs +288 -0
- package/workflows/wechat-reading-shots/prompts/collect-params.md +16 -0
- package/workflows/wechat-reading-shots/prompts/evaluate.md +20 -0
- package/workflows/wechat-reading-shots/prompts/open-book.md +28 -0
- package/workflows/wechat-reading-shots/prompts/read-page.md +13 -0
- package/workflows/wechat-reading-shots/prompts/turn-batch.md +24 -0
- package/workflows/wechat-reading-shots/test/control-flow.test.ts +329 -0
- package/workflows/wechat-reading-shots/workflow.mjs +266 -0
- package/workflows/wrong-question-docx/README.md +55 -0
- package/workflows/wrong-question-docx/prompts/figure-qa.md +8 -0
- package/workflows/wrong-question-docx/prompts/transcribe.md +25 -0
- package/workflows/wrong-question-docx/scripts/workbook.mjs +857 -0
- package/workflows/wrong-question-docx/test/control-flow.test.ts +165 -0
- package/workflows/wrong-question-docx/test/workbook.test.mjs +303 -0
- package/workflows/wrong-question-docx/workflow.mjs +485 -0
- package/plugins/workflow-runner/src/agentCommon.js +0 -354
- package/plugins/workflow-runner/src/agentCommon.ts +0 -435
- package/plugins/workflow-runner/src/controlFlow/branch.js +0 -40
- package/plugins/workflow-runner/src/controlFlow/branch.ts +0 -46
- package/plugins/workflow-runner/src/controlFlow/loop.js +0 -211
- package/plugins/workflow-runner/src/controlFlow/loop.ts +0 -248
- package/plugins/workflow-runner/src/controlFlow/pause.js +0 -129
- package/plugins/workflow-runner/src/controlFlow/pause.ts +0 -141
- package/plugins/workflow-runner/src/controlFlow/signal.js +0 -21
- package/plugins/workflow-runner/src/controlFlow/signal.ts +0 -29
- package/plugins/workflow-runner/src/controlFlow/types.js +0 -18
- package/plugins/workflow-runner/src/controlFlow/types.ts +0 -63
- package/plugins/workflow-runner/src/dslManifest.js +0 -72
- package/plugins/workflow-runner/src/dslManifest.ts +0 -127
- package/plugins/workflow-runner/src/expressions.js +0 -618
- package/plugins/workflow-runner/src/expressions.ts +0 -622
- package/plugins/workflow-runner/src/loader.js +0 -573
- package/plugins/workflow-runner/src/loader.ts +0 -721
- package/plugins/workflow-runner/src/multimodal.js +0 -38
- package/plugins/workflow-runner/src/multimodal.ts +0 -64
- package/plugins/workflow-runner/src/recovery.js +0 -119
- package/plugins/workflow-runner/src/recovery.ts +0 -149
- package/plugins/workflow-runner/src/stepExecutors/agent.js +0 -173
- package/plugins/workflow-runner/src/stepExecutors/agent.ts +0 -234
- package/plugins/workflow-runner/src/stepExecutors/assert.js +0 -41
- package/plugins/workflow-runner/src/stepExecutors/assert.ts +0 -50
- package/plugins/workflow-runner/src/stepExecutors/echo.js +0 -31
- package/plugins/workflow-runner/src/stepExecutors/echo.ts +0 -38
- package/plugins/workflow-runner/src/stepExecutors/llm.js +0 -99
- package/plugins/workflow-runner/src/stepExecutors/llm.ts +0 -133
- package/plugins/workflow-runner/src/stepExecutors/set.js +0 -49
- package/plugins/workflow-runner/src/stepExecutors/set.ts +0 -56
- package/plugins/workflow-runner/src/stepExecutors/skill.js +0 -111
- package/plugins/workflow-runner/src/stepExecutors/skill.ts +0 -140
- package/plugins/workflow-runner/src/stepExecutors/tool.js +0 -208
- package/plugins/workflow-runner/src/stepExecutors/tool.ts +0 -256
- package/plugins/workflow-runner/src/stepExecutors/verify.js +0 -58
- package/plugins/workflow-runner/src/stepExecutors/verify.ts +0 -68
- package/plugins/workflow-runner/src/stepPipeline.js +0 -277
- package/plugins/workflow-runner/src/stepPipeline.ts +0 -366
- package/plugins/workflow-runner/test/agent.test.ts +0 -587
- package/plugins/workflow-runner/test/agentLlmError.test.ts +0 -373
- package/plugins/workflow-runner/test/bashBlacklist.sweep.test.ts +0 -65
- package/plugins/workflow-runner/test/captureMisbind.test.ts +0 -99
- package/plugins/workflow-runner/test/cli.e2e.test.ts +0 -147
- package/plugins/workflow-runner/test/context-files.test.ts +0 -320
- package/plugins/workflow-runner/test/e2e.test.ts +0 -370
- package/plugins/workflow-runner/test/engineRobustness.test.ts +0 -226
- package/plugins/workflow-runner/test/expressions.test.ts +0 -397
- package/plugins/workflow-runner/test/foundation.test.ts +0 -126
- package/plugins/workflow-runner/test/graceful.test.ts +0 -139
- package/plugins/workflow-runner/test/greedyInputs.load.test.ts +0 -47
- package/plugins/workflow-runner/test/hostCapability.test.ts +0 -108
- package/plugins/workflow-runner/test/illustrate.load.test.ts +0 -183
- package/plugins/workflow-runner/test/jsonPayload.test.ts +0 -83
- package/plugins/workflow-runner/test/loader.test.ts +0 -617
- package/plugins/workflow-runner/test/maxTurnsRetryDelay.test.ts +0 -33
- package/plugins/workflow-runner/test/multimodal.test.ts +0 -342
- package/plugins/workflow-runner/test/output-schema.test.ts +0 -213
- package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +0 -87
- package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +0 -269
- package/plugins/workflow-runner/test/ppt.load.test.ts +0 -1220
- package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +0 -220
- package/plugins/workflow-runner/test/recovery.test.ts +0 -265
- package/plugins/workflow-runner/test/runner.test.ts +0 -911
- package/plugins/workflow-runner/test/runnerPreflight.test.ts +0 -379
- package/plugins/workflow-runner/test/skillTurnDone.test.ts +0 -71
- package/plugins/workflow-runner/test/stateLifecycle.test.ts +0 -150
- package/plugins/workflow-runner/test/stepPipeline.test.ts +0 -114
- package/plugins/workflow-runner/test/taskArbitration.test.ts +0 -338
- package/plugins/workflow-runner/test/taskLifecycle.test.ts +0 -270
- package/plugins/workflow-runner/test/toolArtifactOverwrite.test.ts +0 -221
- package/plugins/workflow-runner/test/toolCapabilityBoundary.test.ts +0 -29
- package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +0 -168
- package/plugins/workflow-runner/test/webppt.load.test.ts +0 -250
- package/plugins/workflow-runner/test/word.load.test.ts +0 -208
- package/plugins/workflow-runner/test/workflows.smoke.test.ts +0 -76
- package/plugins/workflow-runner/test/yamlGuardConditions.test.ts +0 -207
- package/src/advisorState.js +0 -22
- package/src/cli/configWizard.js +0 -293
- package/src/tools/toolProgressBridge.js +0 -48
- package/workflows/adaptive-research.yaml +0 -50
- package/workflows/book-review-short.yaml +0 -125
- package/workflows/e2e-write-greet.yaml +0 -31
- package/workflows/illustrate.yaml +0 -227
- package/workflows/plan.yaml +0 -470
- package/workflows/ppt.yaml +0 -1670
- package/workflows/ralph-loop.yaml +0 -199
- package/workflows/schema.json +0 -176
- package/workflows/webppt.yaml +0 -496
- package/workflows/wechat-reading-shots.yaml +0 -315
- package/workflows/word.yaml +0 -720
- package/workflows/youtube-shorts.yaml +0 -202
|
@@ -1,557 +1,630 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ============================================================
|
|
3
|
-
* plugins/workflow-runner/src/workflowState.ts ——
|
|
3
|
+
* plugins/workflow-runner/src/workflowState.ts —— 任务状态持久化
|
|
4
4
|
* ------------------------------------------------------------
|
|
5
|
-
*
|
|
5
|
+
* 自 legacy 移植**持久化那一半**。legacy 版 1178 行里另一半是按「步骤索引 /
|
|
6
|
+
* 循环迭代 / VarStack 快照 / capture 变量」做的断点定位 —— 那些概念在代码
|
|
7
|
+
* 编排里不存在,由 `journal.ts` 的「调用序号 + 内容指纹」整体取代。
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
* completed 时据此报实际存在的产物。不再用从不写入的 artifacts/ 子目录。
|
|
13
|
-
* - resume 模式时回放已完成步骤的 capture 到 vars 中
|
|
14
|
-
* - completed 检测:成功后 archive() 把目录改名 .done,故 init() 在活动目录缺失时
|
|
15
|
-
* 回退读 .done/current.json(status=done + taskId 匹配 → completed),修活「completed 死代码」。
|
|
9
|
+
* 本文件负责的是**跨进程活得下来的东西**:
|
|
10
|
+
* - 目录布局与四态判定(fresh / new-task / completed / resume)
|
|
11
|
+
* - 声明产物的「先归档再清理」(含 glob 展开)
|
|
12
|
+
* - 用户补充要求(supplements)的持久化
|
|
13
|
+
* - 人类可读的 progress 日志
|
|
16
14
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* # (resume 可恢复到迭代中间,见 scanLoopResume)
|
|
27
|
-
* /new 按 .minimal-agent 前缀清理父目录,行为不变。
|
|
15
|
+
* ------------------------------------------------------------
|
|
16
|
+
* ⚠️ **清理必须先归档,不能直接 rm** —— 这条是实战事故换来的:
|
|
17
|
+
* 同主题重跑时 LLM 仲裁误判成 new-task,11 个声明产物被静默直接删除、不可找回。
|
|
18
|
+
* 现在一律先 `rename` 进 `<实例目录>.trash/<stamp>/`(保留相对路径结构),
|
|
19
|
+
* rm 只作为 rename 失败的兜底。`.trash/` 是状态目录的 sibling,跨任务留存
|
|
20
|
+
* (淘汰策略暂未实现,需要时由用户手动清)。
|
|
21
|
+
*
|
|
22
|
+
* ⚠️ 归档失败时**绝不谎报归档位置** —— 一个文件都没成功移走就返回 undefined。
|
|
23
|
+
* 报了位置用户会去回收站找,找不到比一开始就说「已删除」更糟。
|
|
28
24
|
* ============================================================
|
|
29
25
|
*/
|
|
30
|
-
import { createHash } from 'node:crypto';
|
|
31
|
-
import { mkdir, writeFile, appendFile, rm, rename, readFile, readdir } from 'node:fs/promises';
|
|
32
26
|
import { existsSync } from 'node:fs';
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
27
|
+
import { appendFile, mkdir, readFile, readdir, rename, rm, writeFile, } from 'node:fs/promises';
|
|
28
|
+
import { dirname, join, relative } from 'node:path';
|
|
29
|
+
import { computeTaskId, extractGoal, sanitizeWorkflowDirName, sessionStateDir, taskInstanceDir, } from './taskIdentity.js';
|
|
30
|
+
import { safeArtifactPath } from './util.js';
|
|
35
31
|
const DONE_SUFFIX = '.done';
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const parts = name.split(LOOP_CP_SEP);
|
|
50
|
-
if (parts.length !== 3)
|
|
51
|
-
return null;
|
|
52
|
-
const iteration = Number(parts[1]);
|
|
53
|
-
if (!Number.isInteger(iteration) || iteration < 0)
|
|
54
|
-
return null;
|
|
55
|
-
return { loopId: parts[0], iteration, stepId: parts[2] };
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* 工作流定义的**结构指纹**:步骤 id + 执行形态 + 嵌套块(then/else/do)骨架的 hash。
|
|
59
|
-
* prompt 文案、args、condition 等内容字段不参与——改提示词/参数不应作废断点;
|
|
60
|
-
* 改结构(增删步/换 id/换类型/调嵌套)才作废:resume 的恢复点是**索引**、循环断点是
|
|
61
|
-
* 复合名(loopId@@迭代@@stepId),结构一变旧 checkpoint 就对不上,按旧断点续跑会错跳步
|
|
62
|
-
* (曾真实发生:plan.yaml v3→v4 重构后同 goal 重跑提示「从第 2 步续跑」,跳过的步与
|
|
63
|
-
* 新结构毫无对应关系)。current.json 记录运行时的指纹,init 同任务重跑时比对。
|
|
64
|
-
*/
|
|
65
|
-
export function computeDefFingerprint(steps) {
|
|
66
|
-
const skeleton = (list) => (list ?? []).map((s) => ({
|
|
67
|
-
id: s.id,
|
|
68
|
-
kind: s.type ??
|
|
69
|
-
(s.tool ? 'tool' : s.skill ? 'skill' : s.agent ? 'agent' : s.llm ? 'llm' : '?'),
|
|
70
|
-
then: s.then ? skeleton(s.then) : undefined,
|
|
71
|
-
else: s.else ? skeleton(s.else) : undefined,
|
|
72
|
-
do: s.do ? skeleton(s.do) : undefined,
|
|
73
|
-
}));
|
|
74
|
-
return createHash('sha1')
|
|
75
|
-
.update(JSON.stringify(skeleton(steps)))
|
|
76
|
-
.digest('hex')
|
|
77
|
-
.slice(0, 16);
|
|
32
|
+
const TRASH_SUFFIX = '.trash';
|
|
33
|
+
/** 把 glob 段翻成正则片段(`*` 不跨目录分隔符,`?` 单字符)。 */
|
|
34
|
+
function artifactSegToPattern(seg) {
|
|
35
|
+
return seg
|
|
36
|
+
.split(/([*?])/)
|
|
37
|
+
.map((part) => {
|
|
38
|
+
if (part === '*')
|
|
39
|
+
return '[^/]*';
|
|
40
|
+
if (part === '?')
|
|
41
|
+
return '[^/]';
|
|
42
|
+
return part.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
43
|
+
})
|
|
44
|
+
.join('');
|
|
78
45
|
}
|
|
79
46
|
export class WorkflowState {
|
|
80
|
-
|
|
47
|
+
parentStateDir;
|
|
48
|
+
/** 任务实例目录(绝对路径)。init 里确定 taskId 后才有值。 */
|
|
49
|
+
dir = '';
|
|
50
|
+
/** 当前 session 的目录(其下按 taskId 分实例)。 */
|
|
51
|
+
sessionDir;
|
|
81
52
|
workingDir;
|
|
82
|
-
|
|
53
|
+
workflowName;
|
|
54
|
+
taskIdOverride;
|
|
55
|
+
sessionId;
|
|
56
|
+
/** init 里算出的任务身份。 */
|
|
57
|
+
taskId = '';
|
|
58
|
+
/**
|
|
59
|
+
* 输入声明。用于让 `extractGoal` 优先取作者标了 `required` 的那个输入 ——
|
|
60
|
+
* 任务身份与目标锚必须用**同一份**取值规则,否则「拿来算 taskId 的目标」
|
|
61
|
+
* 和「注入给模型的目标」会指向不同的东西。
|
|
62
|
+
*/
|
|
63
|
+
defs;
|
|
64
|
+
progressWrites = Promise.resolve();
|
|
65
|
+
eventWrites = Promise.resolve();
|
|
66
|
+
constructor(workingDir, workflowName, sessionId, defs, taskIdOverride, parentStateDir) {
|
|
67
|
+
this.parentStateDir = parentStateDir;
|
|
83
68
|
this.workingDir = workingDir;
|
|
84
|
-
|
|
85
|
-
this.
|
|
69
|
+
this.workflowName = workflowName;
|
|
70
|
+
this.sessionId = sanitizeWorkflowDirName(sessionId);
|
|
71
|
+
this.sessionDir = parentStateDir
|
|
72
|
+
? join(parentStateDir, 'children', workflowName)
|
|
73
|
+
: sessionStateDir(workingDir, workflowName, sessionId);
|
|
74
|
+
this.defs = defs;
|
|
75
|
+
this.taskIdOverride = taskIdOverride;
|
|
76
|
+
}
|
|
77
|
+
/** journal 落盘位置(由 journal.ts 消费,见任务 5)。 */
|
|
78
|
+
get journalPath() {
|
|
79
|
+
return join(this.dir, 'journal.jsonl');
|
|
80
|
+
}
|
|
81
|
+
get currentPath() {
|
|
82
|
+
return join(this.dir, 'current.json');
|
|
83
|
+
}
|
|
84
|
+
get supplementsPath() {
|
|
85
|
+
return join(this.dir, 'supplements.json');
|
|
86
|
+
}
|
|
87
|
+
get progressPath() {
|
|
88
|
+
return join(this.dir, 'progress.md');
|
|
89
|
+
}
|
|
90
|
+
get eventsPath() {
|
|
91
|
+
return join(this.dir, 'events.jsonl');
|
|
86
92
|
}
|
|
87
93
|
/**
|
|
88
|
-
*
|
|
94
|
+
* 判定本次运行属于四态里的哪一种,并把实例目录准备好。
|
|
89
95
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* 4. resume — 同一任务未完成 → 扫描 checkpoint 决定恢复点
|
|
95
|
-
*
|
|
96
|
-
* hash 相等 = 同任务(权威信号,与时间无关)。hash 不等且旧任务【未完成】时
|
|
97
|
-
* 不再一票否决 new-task:若提供了 arbitrate(语义仲裁器,见 taskIdentity.ts),
|
|
98
|
-
* 由它判定新输入是「旧任务的延续指令(如"继续")→ resume」还是「全新任务 → new-task」。
|
|
96
|
+
* @param inputs 本次调用的输入(用于算 taskId 与 goal)
|
|
97
|
+
* @param scriptHash 编排脚本全文哈希(脚本改了 → 断点作废)
|
|
98
|
+
* @param declaredArtifacts meta.artifacts 种子(运行时还会经 declareArtifact 增长)
|
|
99
|
+
* @param arbitrate 可选的语义仲裁器;不传则 hash 不等一律 new-task
|
|
99
100
|
*/
|
|
100
|
-
async init(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
// 同名 workflow 但 goal 不同 → 全新任务:清旧归档声明产物后 fresh
|
|
125
|
-
// (legacy 归档无 artifacts 字段时用本次声明清单兜底——同名 workflow 的声明产物
|
|
126
|
-
// 语义稳定,旧任务的产物路径与本次一致的概率极高,残留比误删危害大)
|
|
127
|
-
const cleaned = await this.cleanupDeclaredArtifacts(archived.artifacts ?? declaredArtifacts);
|
|
128
|
-
await this.cleanSlate(name, inputs, identity.hash, declaredArtifacts, fp);
|
|
129
|
-
return {
|
|
130
|
-
mode: 'new-task',
|
|
131
|
-
startFrom: 0,
|
|
132
|
-
cleanedArtifacts: [
|
|
133
|
-
...cleaned,
|
|
134
|
-
...(await this.cleanupStaleDeclared(declaredArtifacts)),
|
|
135
|
-
],
|
|
136
|
-
};
|
|
101
|
+
async init(inputs, scriptHash, declaredArtifacts, arbitrate) {
|
|
102
|
+
const goal = extractGoal(inputs, this.defs);
|
|
103
|
+
this.taskId = this.taskIdOverride
|
|
104
|
+
? sanitizeWorkflowDirName(this.taskIdOverride)
|
|
105
|
+
: computeTaskId(this.workflowName, inputs, this.defs);
|
|
106
|
+
const taskDir = this.parentStateDir
|
|
107
|
+
? join(this.sessionDir, this.taskId)
|
|
108
|
+
: taskInstanceDir(this.workingDir, this.workflowName, this.sessionId, this.taskId);
|
|
109
|
+
const doneDir = taskDir + DONE_SUFFIX;
|
|
110
|
+
// ① 同任务、已完成 → 报实际存在的产物,不重跑。
|
|
111
|
+
if (existsSync(doneDir)) {
|
|
112
|
+
this.dir = doneDir;
|
|
113
|
+
const prev = await this.readCurrent();
|
|
114
|
+
// ⚠️ **脚本改过就不算已完成**。上次那份结论是**另一份脚本**产出的 ——
|
|
115
|
+
// 作者刚改完工作流重跑同一个目标,最想要的恰恰是「按新逻辑再来一遍」,
|
|
116
|
+
// 这时回一句「此前已完成」并甩出旧产物,是**静默给错答案**。
|
|
117
|
+
// (这条是端到端实跑抓到的:改完脚本重跑,1 秒返回旧结果。)
|
|
118
|
+
if (prev &&
|
|
119
|
+
((prev.scriptHash && prev.scriptHash !== scriptHash) ||
|
|
120
|
+
!(await this.treeIntact()))) {
|
|
121
|
+
const info = await this.restartChanged(goal, scriptHash, declaredArtifacts, prev, taskDir);
|
|
122
|
+
return { ...info, mode: 'new-task', scriptChanged: true };
|
|
137
123
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// artifacts 清单随 current.json 一起被删,引擎无从知道它属于谁)。
|
|
141
|
-
// 用户约定:全新任务开始时清理残留与中间文件——删除范围**仅限本工作流
|
|
142
|
-
// 声明的产物**(与运行自身对它们的覆盖写权限一致,不碰用户其他文件)。
|
|
143
|
-
await this.cleanSlate(name, inputs, identity.hash, declaredArtifacts, fp);
|
|
144
|
-
return {
|
|
145
|
-
mode: 'fresh',
|
|
146
|
-
startFrom: 0,
|
|
147
|
-
cleanedArtifacts: await this.cleanupStaleDeclared(declaredArtifacts),
|
|
148
|
-
};
|
|
124
|
+
const artifacts = await this.allArtifacts();
|
|
125
|
+
return { mode: 'completed', artifacts };
|
|
149
126
|
}
|
|
150
|
-
//
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// 定义结构已变(或 legacy 无指纹)→ 旧断点不可续,仲裁 resume 无意义,
|
|
161
|
-
// 跳过仲裁直接走 new-task
|
|
162
|
-
oldState.defFingerprint === fp) {
|
|
163
|
-
const prevGoal = extractGoal((await this.readInputs()) ?? {});
|
|
164
|
-
const newGoal = extractGoal(inputs);
|
|
165
|
-
let verdict = 'new';
|
|
166
|
-
try {
|
|
167
|
-
verdict = await arbitrate({
|
|
168
|
-
workflowName: name,
|
|
169
|
-
prevGoal,
|
|
170
|
-
newGoal,
|
|
171
|
-
completedStepIds: await this.scanCheckpoints(steps),
|
|
172
|
-
totalSteps: steps.length,
|
|
173
|
-
artifacts: oldState.artifacts ?? [],
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
catch {
|
|
177
|
-
verdict = 'new';
|
|
178
|
-
}
|
|
179
|
-
if (verdict === 'resume') {
|
|
180
|
-
// 延续旧任务:状态目录原样保留(taskId 仍是旧 goal 的 hash——
|
|
181
|
-
// 下次再输「继续」依然 hash 不等 → 再次仲裁快路径命中,语义自洽)。
|
|
182
|
-
const info = await this.buildResumeInfo(oldState, steps, selfHealBackoff);
|
|
183
|
-
info.prevGoal = prevGoal;
|
|
184
|
-
info.supplementaryGoal = newGoal;
|
|
185
|
-
return info;
|
|
186
|
-
}
|
|
127
|
+
// ② 同任务、未完成 → resume(能续多远由 journal 说了算)。
|
|
128
|
+
if (existsSync(taskDir)) {
|
|
129
|
+
this.dir = taskDir;
|
|
130
|
+
const prev = await this.readCurrent();
|
|
131
|
+
if (prev &&
|
|
132
|
+
((prev.scriptHash && prev.scriptHash !== scriptHash) ||
|
|
133
|
+
!(await this.treeIntact()))) {
|
|
134
|
+
// 脚本结构变了:旧 journal 的调用序号对不上新控制流,断点必须作废。
|
|
135
|
+
const info = await this.restartChanged(goal, scriptHash, declaredArtifacts, prev, taskDir);
|
|
136
|
+
return { ...info, mode: 'new-task', scriptChanged: true };
|
|
187
137
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
cleanedArtifacts: [
|
|
197
|
-
...cleaned,
|
|
198
|
-
...(await this.cleanupStaleDeclared(declaredArtifacts)),
|
|
138
|
+
await this.writeCurrent({
|
|
139
|
+
name: this.workflowName,
|
|
140
|
+
taskId: this.taskId,
|
|
141
|
+
status: 'running',
|
|
142
|
+
startedAt: new Date().toISOString(),
|
|
143
|
+
goal: prev?.goal ?? goal,
|
|
144
|
+
artifacts: [
|
|
145
|
+
...new Set([...(prev?.artifacts ?? []), ...declaredArtifacts]),
|
|
199
146
|
],
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (oldState.status === 'done') {
|
|
204
|
-
return {
|
|
205
|
-
mode: 'completed',
|
|
206
|
-
artifacts: this.existingArtifacts(oldState.artifacts ?? declaredArtifacts),
|
|
207
|
-
};
|
|
147
|
+
scriptHash,
|
|
148
|
+
});
|
|
149
|
+
return { mode: 'resume', lastError: prev?.lastError };
|
|
208
150
|
}
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
151
|
+
// ③ 本 taskId 没有目录。看看同 session 下有没有**未完成的兄弟任务**可以仲裁延续。
|
|
152
|
+
const sibling = await this.findIncompleteSibling();
|
|
153
|
+
// Bound resources and their declared parameters are an exact contract.
|
|
154
|
+
// A different task hash must not be overridden by a semantic guess based
|
|
155
|
+
// only on the (possibly identical) human-readable goal.
|
|
156
|
+
if (sibling && arbitrate && !inputs.__resource_fingerprint) {
|
|
157
|
+
const prev = await this.readCurrentAt(sibling);
|
|
158
|
+
const verdict = await arbitrate({
|
|
159
|
+
workflowName: this.workflowName,
|
|
160
|
+
prevGoal: prev?.goal ?? '',
|
|
161
|
+
newGoal: goal,
|
|
162
|
+
completedCalls: await this.countJournalEntries(sibling),
|
|
163
|
+
artifacts: await this.existingArtifacts(prev?.artifacts ?? []),
|
|
164
|
+
});
|
|
165
|
+
if (verdict === 'resume') {
|
|
166
|
+
// 采纳旧实例目录继续跑:本次输入降级成补充要求,目标锚仍指向旧目标。
|
|
167
|
+
this.dir = sibling;
|
|
168
|
+
this.taskId = prev?.taskId ?? this.taskId;
|
|
169
|
+
return {
|
|
170
|
+
mode: 'resume',
|
|
171
|
+
prevGoal: prev?.goal,
|
|
172
|
+
supplementaryGoal: goal,
|
|
173
|
+
lastError: prev?.lastError,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
227
176
|
}
|
|
228
|
-
//
|
|
229
|
-
|
|
177
|
+
// ④ 全新任务(或首次运行)。
|
|
178
|
+
this.dir = taskDir;
|
|
179
|
+
const info = await this.startFresh(goal, scriptHash, declaredArtifacts);
|
|
180
|
+
return { ...info, mode: sibling ? 'new-task' : 'fresh' };
|
|
230
181
|
}
|
|
231
|
-
/**
|
|
232
|
-
|
|
233
|
-
*
|
|
234
|
-
*
|
|
182
|
+
/** 建实例目录、归档并清理旧产物残留、写 current.json。 */
|
|
183
|
+
/** Retire the actual old journal AND dynamic outputs, not only the status flag.
|
|
184
|
+
* A completed directory must also be retired, otherwise it shadows the fresh
|
|
185
|
+
* incomplete attempt on the next resume. Nothing is deleted on this path.
|
|
235
186
|
*/
|
|
236
|
-
async
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
|
|
255
|
-
mode: 'resume',
|
|
256
|
-
startFrom: loopResume.loopIndex,
|
|
257
|
-
completedStepIds: completedSteps,
|
|
258
|
-
failedStep: oldState.lastFailedStep,
|
|
259
|
-
recoveryStart: loopResume.loopIndex,
|
|
260
|
-
lastError: oldState.lastError,
|
|
261
|
-
capturedVars,
|
|
262
|
-
loopResume,
|
|
263
|
-
};
|
|
187
|
+
async restartChanged(goal, scriptHash, declaredArtifacts, previous, taskDir) {
|
|
188
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
189
|
+
const archiveRoot = join(this.dir + TRASH_SUFFIX, stamp);
|
|
190
|
+
const existing = [
|
|
191
|
+
...new Set([
|
|
192
|
+
...(await this.allArtifacts()),
|
|
193
|
+
...(await this.existingArtifacts(declaredArtifacts)),
|
|
194
|
+
]),
|
|
195
|
+
];
|
|
196
|
+
await this.archiveDeclaredArtifacts(existing, stamp);
|
|
197
|
+
const remaining = existing.filter((path) => existsSync(join(this.workingDir, path)));
|
|
198
|
+
if (remaining.length)
|
|
199
|
+
throw new Error(`旧产物归档失败,停止覆盖:${remaining.join('、')}`);
|
|
200
|
+
await mkdir(archiveRoot, { recursive: true });
|
|
201
|
+
const oldDir = this.dir;
|
|
202
|
+
await rename(oldDir, join(archiveRoot, 'state'));
|
|
203
|
+
// Older versions could leave both .done and an incomplete directory.
|
|
204
|
+
if (oldDir !== taskDir && existsSync(taskDir)) {
|
|
205
|
+
await rename(taskDir, join(archiveRoot, 'incomplete-state'));
|
|
264
206
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
completedStepIds: completedSteps,
|
|
269
|
-
failedStep: oldState.lastFailedStep,
|
|
270
|
-
recoveryStart,
|
|
271
|
-
lastError: oldState.lastError,
|
|
272
|
-
capturedVars,
|
|
273
|
-
};
|
|
207
|
+
this.dir = taskDir;
|
|
208
|
+
const info = await this.startFresh(goal, scriptHash, declaredArtifacts);
|
|
209
|
+
return { ...info, archivedTo: relative(this.workingDir, archiveRoot) };
|
|
274
210
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
211
|
+
async startFresh(goal, scriptHash, declaredArtifacts) {
|
|
212
|
+
// 时间戳只用作回收目录名,不参与任何身份判定 —— 故这里用真实时间是安全的。
|
|
213
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
214
|
+
const { cleaned, archivedTo } = await this.archiveThenClean(declaredArtifacts, stamp);
|
|
215
|
+
await mkdir(this.dir, { recursive: true });
|
|
216
|
+
await writeFile(this.supplementsPath, '[]', 'utf8');
|
|
217
|
+
await this.writeCurrent({
|
|
218
|
+
name: this.workflowName,
|
|
219
|
+
taskId: this.taskId,
|
|
220
|
+
status: 'running',
|
|
221
|
+
startedAt: new Date().toISOString(),
|
|
222
|
+
goal,
|
|
223
|
+
artifacts: declaredArtifacts,
|
|
224
|
+
scriptHash,
|
|
225
|
+
});
|
|
226
|
+
const info = { mode: 'fresh' };
|
|
227
|
+
if (cleaned.length > 0)
|
|
228
|
+
info.cleanedArtifacts = cleaned;
|
|
229
|
+
if (archivedTo)
|
|
230
|
+
info.archivedTo = archivedTo;
|
|
231
|
+
return info;
|
|
232
|
+
}
|
|
233
|
+
/** 同 session 下另一个**未完成**任务的目录(有多个时取最近修改的)。 */
|
|
234
|
+
async findIncompleteSibling() {
|
|
235
|
+
let entries;
|
|
283
236
|
try {
|
|
284
|
-
|
|
237
|
+
entries = await readdir(this.sessionDir, { withFileTypes: true });
|
|
285
238
|
}
|
|
286
239
|
catch {
|
|
287
240
|
return null;
|
|
288
241
|
}
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
continue;
|
|
298
|
-
let iters = byLoop.get(parsed.loopId);
|
|
299
|
-
if (!iters)
|
|
300
|
-
byLoop.set(parsed.loopId, (iters = new Map()));
|
|
301
|
-
let ids = iters.get(parsed.iteration);
|
|
302
|
-
if (!ids)
|
|
303
|
-
iters.set(parsed.iteration, (ids = new Set()));
|
|
304
|
-
ids.add(parsed.stepId);
|
|
305
|
-
}
|
|
306
|
-
if (byLoop.size === 0)
|
|
242
|
+
const candidates = entries
|
|
243
|
+
.filter((e) => e.isDirectory() &&
|
|
244
|
+
!e.name.endsWith(DONE_SUFFIX) &&
|
|
245
|
+
!e.name.endsWith(TRASH_SUFFIX) &&
|
|
246
|
+
e.name !== this.taskId)
|
|
247
|
+
.map((e) => join(this.sessionDir, e.name))
|
|
248
|
+
.filter((d) => existsSync(join(d, 'current.json')));
|
|
249
|
+
if (candidates.length === 0)
|
|
307
250
|
return null;
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const validInner = new Set(doSteps.map((s) => s.id));
|
|
318
|
-
const iteration = Math.max(...iters.keys());
|
|
319
|
-
const innerIds = [...(iters.get(iteration) ?? [])]
|
|
320
|
-
.filter((id) => validInner.has(id))
|
|
321
|
-
.sort((a, b) => doSteps.findIndex((s) => s.id === a) - doSteps.findIndex((s) => s.id === b));
|
|
322
|
-
if (innerIds.length === 0)
|
|
323
|
-
continue;
|
|
324
|
-
const lastInnerIdx = doSteps.findIndex((s) => s.id === innerIds[innerIds.length - 1]);
|
|
325
|
-
const innerStartFrom = Math.max(0, lastInnerIdx + 1 - selfHealBackoff);
|
|
326
|
-
// 中断时刻最近的 vars 快照(迭代内最后一个完成步的 checkpoint)
|
|
327
|
-
let lastVarsSnapshot;
|
|
328
|
-
try {
|
|
329
|
-
const raw = await readFile(join(cpDir, `${loopCheckpointName(loopId, iteration, innerIds[innerIds.length - 1])}.json`), 'utf-8');
|
|
330
|
-
lastVarsSnapshot = JSON.parse(raw).varsSnapshot;
|
|
331
|
-
}
|
|
332
|
-
catch {
|
|
333
|
-
// 快照读失败不阻断:仍可定位迭代,变量靠顶层 capture 回放兜底
|
|
251
|
+
// 多个未完成实例时取最近的那个 —— 用户嘴里的「继续」几乎总是指最近一次。
|
|
252
|
+
let newest = candidates[0];
|
|
253
|
+
let newestAt = '';
|
|
254
|
+
for (const dir of candidates) {
|
|
255
|
+
const cur = await this.readCurrentAt(dir);
|
|
256
|
+
const at = cur?.startedAt ?? '';
|
|
257
|
+
if (at >= newestAt) {
|
|
258
|
+
newestAt = at;
|
|
259
|
+
newest = dir;
|
|
334
260
|
}
|
|
335
|
-
const candidate = {
|
|
336
|
-
loopId,
|
|
337
|
-
loopIndex,
|
|
338
|
-
iteration,
|
|
339
|
-
innerStartFrom,
|
|
340
|
-
completedInnerIds: innerIds,
|
|
341
|
-
lastVarsSnapshot,
|
|
342
|
-
};
|
|
343
|
-
if (!best || candidate.loopIndex > best.loopIndex)
|
|
344
|
-
best = candidate;
|
|
345
261
|
}
|
|
346
|
-
return
|
|
262
|
+
return newest;
|
|
347
263
|
}
|
|
348
|
-
|
|
349
|
-
/** 追加一条用户补充要求到 supplements.json(resume 跨进程不丢)。 */
|
|
350
|
-
async appendSupplement(text) {
|
|
351
|
-
const list = await this.readSupplements();
|
|
352
|
-
list.push(text);
|
|
353
|
-
await writeFile(join(this.dir, SUPPLEMENTS_FILE), JSON.stringify(list, null, 2), 'utf-8').catch(() => { });
|
|
354
|
-
}
|
|
355
|
-
/** 读取已累积的补充要求列表(无文件 / 解析失败返回 [])。 */
|
|
356
|
-
async readSupplements() {
|
|
264
|
+
async countJournalEntries(dir) {
|
|
357
265
|
try {
|
|
358
|
-
const raw = await readFile(join(
|
|
359
|
-
|
|
360
|
-
return Array.isArray(parsed) ? parsed.filter((x) => typeof x === 'string') : [];
|
|
266
|
+
const raw = await readFile(join(dir, 'journal.jsonl'), 'utf8');
|
|
267
|
+
return raw.split('\n').filter((l) => l.trim() !== '').length;
|
|
361
268
|
}
|
|
362
269
|
catch {
|
|
363
|
-
return
|
|
270
|
+
return 0;
|
|
364
271
|
}
|
|
365
272
|
}
|
|
366
|
-
// ----
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const cpDir = join(this.dir, CHECKPOINTS_DIR);
|
|
370
|
-
await mkdir(cpDir, { recursive: true }).catch(() => { });
|
|
371
|
-
const data = {
|
|
372
|
-
stepId,
|
|
373
|
-
completedAt: new Date().toISOString(),
|
|
374
|
-
output,
|
|
375
|
-
captures,
|
|
376
|
-
varsSnapshot,
|
|
377
|
-
};
|
|
378
|
-
await writeFile(join(cpDir, `${stepId}.json`), JSON.stringify(data, null, 2), 'utf-8');
|
|
379
|
-
}
|
|
380
|
-
/** 记录步骤失败:更新 current.json 的 lastFailedStep 和 lastError */
|
|
381
|
-
async recordFailure(stepId, error) {
|
|
382
|
-
const current = await this.readCurrentState();
|
|
383
|
-
if (current) {
|
|
384
|
-
current.lastFailedStep = stepId;
|
|
385
|
-
current.lastError = error;
|
|
386
|
-
current.status = 'failed';
|
|
387
|
-
await this.writeCurrent(current);
|
|
388
|
-
}
|
|
273
|
+
// ---- current.json ----
|
|
274
|
+
async readCurrent() {
|
|
275
|
+
return this.readCurrentAt(this.dir);
|
|
389
276
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
277
|
+
async readCurrentAt(dir) {
|
|
278
|
+
try {
|
|
279
|
+
return JSON.parse(await readFile(join(dir, 'current.json'), 'utf8'));
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return null;
|
|
396
283
|
}
|
|
397
284
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
402
|
-
await appendFile(join(this.dir, 'progress.md'), `- ${ts} ${line}\n`, 'utf-8').catch(() => { });
|
|
285
|
+
async writeCurrent(state) {
|
|
286
|
+
await mkdir(this.dir, { recursive: true });
|
|
287
|
+
await writeFile(this.currentPath, JSON.stringify(state, null, 2), 'utf8');
|
|
403
288
|
}
|
|
404
|
-
/**
|
|
405
|
-
async
|
|
406
|
-
|
|
289
|
+
/** 记一次失败原因(下次 resume 时报给用户 / 喂给模型)。 */
|
|
290
|
+
async recordFailure(error, artifacts) {
|
|
291
|
+
const cur = await this.readCurrent();
|
|
292
|
+
if (!cur)
|
|
293
|
+
return;
|
|
294
|
+
await this.writeCurrent({
|
|
295
|
+
...cur,
|
|
296
|
+
status: 'failed',
|
|
297
|
+
lastError: error,
|
|
298
|
+
...(artifacts ? { artifacts: [...new Set(artifacts)] } : {}),
|
|
299
|
+
});
|
|
407
300
|
}
|
|
408
301
|
/**
|
|
409
|
-
*
|
|
410
|
-
*
|
|
302
|
+
* 标记完成(随后 archive() 把目录改名成 `.done`)。
|
|
303
|
+
*
|
|
304
|
+
* @param artifacts 本次运行**最终**的声明产物集合。必须传 —— `meta.artifacts`
|
|
305
|
+
* 只是静态种子,循环体里经 `declareArtifact()` 登记的逐迭代产物只活在内存里。
|
|
306
|
+
* 不在这里落盘的话,下次 completed 判定会报「本次运行没有声明产物」,
|
|
307
|
+
* new-task 清理也会漏掉它们 —— 而且**表现为静默漏档而不是报错**。
|
|
308
|
+
* (这条是端到端实跑抓到的:上一轮明明 declareArtifact 了,重跑却报 0 产物。)
|
|
411
309
|
*/
|
|
412
|
-
async
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
310
|
+
async markDone(artifacts) {
|
|
311
|
+
const cur = await this.readCurrent();
|
|
312
|
+
if (!cur)
|
|
313
|
+
return;
|
|
314
|
+
if (!existsSync(join(this.dir, 'return.json')))
|
|
315
|
+
await this.writeResult(null);
|
|
316
|
+
const owned = artifacts ?? cur.artifacts ?? [];
|
|
317
|
+
const records = await this.treeRecords();
|
|
318
|
+
const treeArtifacts = [
|
|
319
|
+
...owned,
|
|
320
|
+
...records
|
|
321
|
+
.filter((r) => r.dir !== this.dir)
|
|
322
|
+
.flatMap((r) => r.state.artifacts ?? []),
|
|
323
|
+
];
|
|
324
|
+
await this.writeCurrent({
|
|
325
|
+
...cur,
|
|
326
|
+
status: 'done',
|
|
327
|
+
...(artifacts ? { artifacts: [...new Set(artifacts)] } : {}),
|
|
328
|
+
completion: {
|
|
329
|
+
artifacts: await this.existingArtifacts(treeArtifacts),
|
|
330
|
+
stateFiles: [
|
|
331
|
+
'return.json',
|
|
332
|
+
...records
|
|
333
|
+
.filter((r) => r.dir !== this.dir)
|
|
334
|
+
.map((r) => relative(this.dir, join(r.dir, 'current.json'))),
|
|
335
|
+
],
|
|
336
|
+
},
|
|
337
|
+
});
|
|
435
338
|
}
|
|
436
|
-
|
|
437
|
-
|
|
339
|
+
async writeResult(value) {
|
|
340
|
+
const json = JSON.stringify(value ?? null, (_key, v) => {
|
|
341
|
+
if (typeof v === 'function' ||
|
|
342
|
+
typeof v === 'symbol' ||
|
|
343
|
+
typeof v === 'bigint' ||
|
|
344
|
+
(typeof v === 'number' && !Number.isFinite(v)))
|
|
345
|
+
throw new Error('工作流返回值必须为JSON数据');
|
|
346
|
+
return v;
|
|
347
|
+
});
|
|
348
|
+
await writeFile(join(this.dir, 'return.json'), json, 'utf8');
|
|
349
|
+
return JSON.parse(json);
|
|
350
|
+
}
|
|
351
|
+
async readResult() {
|
|
438
352
|
try {
|
|
439
|
-
|
|
440
|
-
return JSON.parse(raw);
|
|
353
|
+
return JSON.parse(await readFile(join(this.dir, 'return.json'), 'utf8'));
|
|
441
354
|
}
|
|
442
|
-
catch {
|
|
443
|
-
|
|
355
|
+
catch (error) {
|
|
356
|
+
if (!(await this.readCurrent())?.completion)
|
|
357
|
+
return null; // pre-composition state
|
|
358
|
+
throw error;
|
|
444
359
|
}
|
|
445
360
|
}
|
|
446
|
-
/**
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
|
|
361
|
+
/** Children live inside the parent's state directory. Moving the parent also
|
|
362
|
+
* archives all child journals, never another standalone run of the same name. */
|
|
363
|
+
async treeRecords() {
|
|
364
|
+
const records = [];
|
|
365
|
+
const own = await this.readCurrent();
|
|
366
|
+
if (own)
|
|
367
|
+
records.push({ dir: this.dir, state: own });
|
|
368
|
+
const visit = async (dir, depth) => {
|
|
369
|
+
if (depth > 24)
|
|
370
|
+
throw new Error('子工作流状态目录嵌套异常');
|
|
371
|
+
let entries;
|
|
451
372
|
try {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
373
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
if (error.code === 'ENOENT')
|
|
377
|
+
return;
|
|
378
|
+
throw error;
|
|
379
|
+
}
|
|
380
|
+
for (const e of entries) {
|
|
381
|
+
if (!e.isDirectory() || e.name.endsWith(TRASH_SUFFIX))
|
|
382
|
+
continue;
|
|
383
|
+
const childDir = join(dir, e.name);
|
|
384
|
+
const current = await this.readCurrentAt(childDir);
|
|
385
|
+
if (current) {
|
|
386
|
+
records.push({ dir: childDir, state: current });
|
|
387
|
+
await visit(join(childDir, 'children'), depth + 1);
|
|
456
388
|
}
|
|
389
|
+
else
|
|
390
|
+
await visit(childDir, depth + 1);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
await visit(join(this.dir, 'children'), 0);
|
|
394
|
+
return records;
|
|
395
|
+
}
|
|
396
|
+
async allArtifacts() {
|
|
397
|
+
return this.existingArtifacts((await this.treeRecords()).flatMap((r) => r.state.artifacts ?? []));
|
|
398
|
+
}
|
|
399
|
+
async treeIntact() {
|
|
400
|
+
for (const { dir, state } of await this.treeRecords()) {
|
|
401
|
+
if (!state.completion)
|
|
402
|
+
continue;
|
|
403
|
+
if (state.completion.artifacts.some((p) => {
|
|
404
|
+
const safe = safeArtifactPath(this.workingDir, p);
|
|
405
|
+
return !safe || !existsSync(join(this.workingDir, safe));
|
|
406
|
+
}))
|
|
407
|
+
return false;
|
|
408
|
+
if (state.completion.stateFiles.some((p) => {
|
|
409
|
+
const safe = safeArtifactPath(dir, p);
|
|
410
|
+
return !safe || !existsSync(join(dir, safe));
|
|
411
|
+
}))
|
|
412
|
+
return false;
|
|
413
|
+
try {
|
|
414
|
+
JSON.parse(await readFile(join(dir, 'return.json'), 'utf8'));
|
|
457
415
|
}
|
|
458
416
|
catch {
|
|
459
|
-
|
|
417
|
+
return false;
|
|
460
418
|
}
|
|
461
419
|
}
|
|
462
|
-
return
|
|
420
|
+
return true;
|
|
463
421
|
}
|
|
464
|
-
// ----
|
|
465
|
-
/**
|
|
466
|
-
*
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
422
|
+
// ---- 补充要求 ----
|
|
423
|
+
/**
|
|
424
|
+
* 追加一条用户补充要求。去重:已有条目的前缀重复不再追加。
|
|
425
|
+
*
|
|
426
|
+
* 持久化的理由:补充要求要与原始目标**同等贯穿始终**,跨进程 resume 时不能丢。
|
|
427
|
+
*/
|
|
428
|
+
async appendSupplement(text) {
|
|
429
|
+
const t = text.trim();
|
|
430
|
+
if (!t)
|
|
431
|
+
return;
|
|
432
|
+
const list = await this.readSupplements();
|
|
433
|
+
if (list.some((s) => s.startsWith(t.slice(0, 240))))
|
|
434
|
+
return;
|
|
435
|
+
list.push(t);
|
|
436
|
+
await mkdir(this.dir, { recursive: true });
|
|
437
|
+
await writeFile(this.supplementsPath, JSON.stringify(list, null, 2), 'utf8');
|
|
438
|
+
}
|
|
439
|
+
async readSupplements() {
|
|
440
|
+
try {
|
|
441
|
+
const parsed = JSON.parse(await readFile(this.supplementsPath, 'utf8'));
|
|
442
|
+
return Array.isArray(parsed) ? parsed.map((s) => String(s)) : [];
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
return [];
|
|
470
446
|
}
|
|
471
|
-
return paths;
|
|
472
447
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
448
|
+
// ---- 人类可读进度 ----
|
|
449
|
+
/**
|
|
450
|
+
* 追加一行进度。
|
|
451
|
+
*
|
|
452
|
+
* 存在理由:TUI 的 scrollback 会被刷掉,而排障常常发生在「跑完之后」。
|
|
453
|
+
* 落盘的 progress.md 是唯一还能回看的现场(provider 的真实报错也写在这里)。
|
|
454
|
+
*/
|
|
455
|
+
async appendProgress(line) {
|
|
456
|
+
this.progressWrites = this.progressWrites.then(async () => {
|
|
457
|
+
try {
|
|
458
|
+
await mkdir(this.dir, { recursive: true });
|
|
459
|
+
await appendFile(this.progressPath, `${line}\n`, 'utf8');
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
// 进度日志写失败不该影响任务本身
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
await this.progressWrites;
|
|
476
466
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
467
|
+
async appendEvent(event) {
|
|
468
|
+
this.eventWrites = this.eventWrites.then(async () => {
|
|
469
|
+
try {
|
|
470
|
+
await mkdir(this.dir, { recursive: true });
|
|
471
|
+
await appendFile(this.eventsPath, `${JSON.stringify(event)}\n`, 'utf8');
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
// Structured diagnostics must never fail the workflow itself.
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
await this.eventWrites;
|
|
480
478
|
}
|
|
481
|
-
// ---- 公共方法:归档与清理(保持原有签名兼容) ----
|
|
482
479
|
/**
|
|
483
|
-
*
|
|
484
|
-
*
|
|
480
|
+
* 等待所有已排队的 progress/event 写入落盘(不做归档,无其它副作用)。
|
|
481
|
+
*
|
|
482
|
+
* `journaled()`(`runtime/context.ts`)里的 appendProgress 调用是 fire-and-forget
|
|
483
|
+
* ——生产路径的落盘保证来自 `archive()` 在 `runner.ts` 终止路径上对同一条
|
|
484
|
+
* `progressWrites`/`eventWrites` 链的 await。**绕开 `runner.ts` 直接调用能力对象
|
|
485
|
+
* 的测试**(例如只起 `createWorkflowContext()` 就调 `wf.model.generate()`)没有
|
|
486
|
+
* 这条终态保证,读 `progress.md`/`events.jsonl` 前必须显式调用这个方法。
|
|
485
487
|
*/
|
|
488
|
+
async flush() {
|
|
489
|
+
await Promise.all([this.progressWrites, this.eventWrites]);
|
|
490
|
+
}
|
|
491
|
+
// ---- 归档与清理 ----
|
|
492
|
+
/** 成功收尾:`<taskId>/` → `<taskId>.done/`。 */
|
|
486
493
|
async archive() {
|
|
487
|
-
|
|
494
|
+
await this.flush();
|
|
495
|
+
if (!this.dir || !existsSync(this.dir))
|
|
496
|
+
return;
|
|
497
|
+
const dest = this.dir.endsWith(DONE_SUFFIX)
|
|
498
|
+
? this.dir
|
|
499
|
+
: this.dir + DONE_SUFFIX;
|
|
500
|
+
if (dest === this.dir)
|
|
501
|
+
return;
|
|
488
502
|
try {
|
|
489
|
-
await rm(
|
|
503
|
+
await rm(dest, { recursive: true, force: true });
|
|
504
|
+
await rename(this.dir, dest);
|
|
505
|
+
this.dir = dest;
|
|
490
506
|
}
|
|
491
507
|
catch {
|
|
492
|
-
//
|
|
493
|
-
}
|
|
494
|
-
try {
|
|
495
|
-
await rename(this.dir, doneDir);
|
|
496
|
-
}
|
|
497
|
-
catch {
|
|
498
|
-
// rename 失败时静默(某些文件系统/权限问题),保留原目录
|
|
508
|
+
// 归档失败不影响任务结论,下次 init 会按未完成处理
|
|
499
509
|
}
|
|
500
510
|
}
|
|
501
|
-
/**
|
|
511
|
+
/** 删掉本实例的状态目录(不碰 `.trash/`,那是用户的找回通道)。 */
|
|
502
512
|
async cleanup() {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
}
|
|
506
|
-
// ---- 私有方法 ----
|
|
507
|
-
/** 创建全新的空白状态目录(含 checkpoints/ 子目录),并记录声明产物清单。 */
|
|
508
|
-
async cleanSlate(name, inputs, taskId, declaredArtifacts = [], defFingerprint) {
|
|
509
|
-
await this.cleanup();
|
|
510
|
-
await mkdir(this.dir, { recursive: true });
|
|
511
|
-
await mkdir(join(this.dir, CHECKPOINTS_DIR), { recursive: true });
|
|
512
|
-
await this.writeCurrent({
|
|
513
|
-
name,
|
|
514
|
-
taskId,
|
|
515
|
-
status: 'running',
|
|
516
|
-
startedAt: new Date().toISOString(),
|
|
517
|
-
artifacts: declaredArtifacts,
|
|
518
|
-
defFingerprint,
|
|
519
|
-
});
|
|
520
|
-
await writeFile(join(this.dir, 'inputs.json'), JSON.stringify(inputs, null, 2), 'utf-8');
|
|
521
|
-
await writeFile(join(this.dir, 'progress.md'), `# ${name}\n\n`, 'utf-8');
|
|
522
|
-
}
|
|
523
|
-
/** 读取旧任务的 inputs.json(仲裁时取旧 goal 用),失败返回 null */
|
|
524
|
-
async readInputs() {
|
|
513
|
+
if (!this.dir)
|
|
514
|
+
return;
|
|
525
515
|
try {
|
|
526
|
-
|
|
527
|
-
return JSON.parse(raw);
|
|
516
|
+
await rm(this.dir, { recursive: true, force: true });
|
|
528
517
|
}
|
|
529
518
|
catch {
|
|
530
|
-
|
|
519
|
+
/* 忽略 */
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// ---- 声明产物的生命周期 ----
|
|
523
|
+
/**
|
|
524
|
+
* 把声明里的 glob(`research/04-*.md`)展开成 cwd 下实际匹配的相对路径;
|
|
525
|
+
* 非 glob 原样保留。越界路径被过滤(fail-closed)。
|
|
526
|
+
* 声明里写了 glob 却一个都不匹配 → 该声明被丢弃(不存在的产物不参与清理/报告)。
|
|
527
|
+
*/
|
|
528
|
+
async expandDeclaredArtifacts(paths) {
|
|
529
|
+
const out = [];
|
|
530
|
+
for (const p of paths) {
|
|
531
|
+
const safe = safeArtifactPath(this.workingDir, p);
|
|
532
|
+
if (safe === null)
|
|
533
|
+
continue;
|
|
534
|
+
if (!/[*?]/.test(safe)) {
|
|
535
|
+
// ⚠️ 必须归一成 `/`:`safeArtifactPath` 返回的是**平台分隔符**,而下面
|
|
536
|
+
// glob 分支里 walkGlob 拼出来的一律是 `/`。两个分支不统一的话,同一份
|
|
537
|
+
// 声明列表在 Windows 上会同时产出 `out\a.md` 与 `out/b.md` —— 去重
|
|
538
|
+
// (new Set)失效、与 current.json 里存的旧值比不上、报给用户的路径
|
|
539
|
+
// 时而斜杠时而反斜杠。`join()` 吃 `/` 没问题,所以统一到 `/` 是安全的。
|
|
540
|
+
out.push(safe.replace(/\\/g, '/'));
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
const segs = safe.replace(/\\/g, '/').split('/');
|
|
544
|
+
const fullRe = new RegExp(`^${segs.map(artifactSegToPattern).join('/')}$`);
|
|
545
|
+
out.push(...(await this.walkGlob(this.workingDir, segs, fullRe, '')));
|
|
531
546
|
}
|
|
547
|
+
return out;
|
|
532
548
|
}
|
|
533
|
-
|
|
534
|
-
|
|
549
|
+
async walkGlob(absDir, segs, fullRe, relPrefix) {
|
|
550
|
+
const [head, ...rest] = segs;
|
|
551
|
+
if (!head)
|
|
552
|
+
return [];
|
|
553
|
+
let entries;
|
|
535
554
|
try {
|
|
536
|
-
|
|
537
|
-
return JSON.parse(raw);
|
|
555
|
+
entries = await readdir(absDir, { withFileTypes: true });
|
|
538
556
|
}
|
|
539
557
|
catch {
|
|
540
|
-
return
|
|
558
|
+
return [];
|
|
559
|
+
}
|
|
560
|
+
const headRe = new RegExp(`^${artifactSegToPattern(head)}$`);
|
|
561
|
+
const results = [];
|
|
562
|
+
for (const e of entries) {
|
|
563
|
+
if (!headRe.test(e.name))
|
|
564
|
+
continue;
|
|
565
|
+
const rel = relPrefix ? `${relPrefix}/${e.name}` : e.name;
|
|
566
|
+
if (rest.length === 0) {
|
|
567
|
+
if (e.isFile() && fullRe.test(rel))
|
|
568
|
+
results.push(rel);
|
|
569
|
+
}
|
|
570
|
+
else if (e.isDirectory()) {
|
|
571
|
+
results.push(...(await this.walkGlob(join(absDir, e.name), rest, fullRe, rel)));
|
|
572
|
+
}
|
|
541
573
|
}
|
|
574
|
+
return results;
|
|
542
575
|
}
|
|
543
|
-
/**
|
|
544
|
-
async
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
576
|
+
/** 声明产物里**实际存在**的那些(completed 报告用;越界的视为不存在)。 */
|
|
577
|
+
async existingArtifacts(paths) {
|
|
578
|
+
const expanded = await this.expandDeclaredArtifacts(paths);
|
|
579
|
+
return [...new Set(expanded)].filter((rel) => existsSync(join(this.workingDir, rel)));
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* 把实际存在的声明产物移入 `<实例目录>.trash/<stamp>/`,保留相对路径结构。
|
|
583
|
+
* 一个都没成功移走时返回 undefined —— **绝不谎报归档位置**(见文件头注)。
|
|
584
|
+
*/
|
|
585
|
+
async archiveDeclaredArtifacts(existingRels, stamp) {
|
|
586
|
+
if (existingRels.length === 0)
|
|
587
|
+
return undefined;
|
|
588
|
+
const trashRoot = join(this.dir + TRASH_SUFFIX, stamp);
|
|
589
|
+
let archived = 0;
|
|
590
|
+
for (const rel of existingRels) {
|
|
591
|
+
const dest = join(trashRoot, rel);
|
|
592
|
+
try {
|
|
593
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
594
|
+
await rename(join(this.workingDir, rel), dest);
|
|
595
|
+
archived++;
|
|
596
|
+
}
|
|
597
|
+
catch {
|
|
598
|
+
// 单个失败不阻断,残留交给后续 rm 兜底
|
|
599
|
+
}
|
|
548
600
|
}
|
|
549
|
-
|
|
550
|
-
return
|
|
601
|
+
if (archived === 0)
|
|
602
|
+
return undefined;
|
|
603
|
+
const display = relative(this.workingDir, trashRoot);
|
|
604
|
+
return display.startsWith('..') ? trashRoot : display;
|
|
605
|
+
}
|
|
606
|
+
/** rm 兜底:只删工作流自己声明的产物,且路径强制在 cwd 内。 */
|
|
607
|
+
async cleanupDeclaredArtifacts(paths) {
|
|
608
|
+
const done = [];
|
|
609
|
+
for (const rel of await this.expandDeclaredArtifacts(paths)) {
|
|
610
|
+
const abs = join(this.workingDir, rel);
|
|
611
|
+
if (!existsSync(abs))
|
|
612
|
+
continue;
|
|
613
|
+
try {
|
|
614
|
+
await rm(abs, { force: true });
|
|
615
|
+
done.push(rel);
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
/* 单个失败不影响整体 */
|
|
619
|
+
}
|
|
551
620
|
}
|
|
621
|
+
return done;
|
|
552
622
|
}
|
|
553
|
-
/**
|
|
554
|
-
async
|
|
555
|
-
await
|
|
623
|
+
/** 「先归档再清理」组合。fresh/new-task 的启动清理统一走这里。 */
|
|
624
|
+
async archiveThenClean(paths, stamp) {
|
|
625
|
+
const existing = await this.existingArtifacts(paths);
|
|
626
|
+
const archivedTo = await this.archiveDeclaredArtifacts(existing, stamp);
|
|
627
|
+
const cleaned = await this.cleanupDeclaredArtifacts(paths);
|
|
628
|
+
return { cleaned, archivedTo };
|
|
556
629
|
}
|
|
557
630
|
}
|