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
|
@@ -33,6 +33,7 @@ export class EvidenceLedger {
|
|
|
33
33
|
#failedToolCalls = 0;
|
|
34
34
|
|
|
35
35
|
record(toolName: string, argsJson: string, result: ToolResult): void {
|
|
36
|
+
if (!result.ok && result.disposition === 'deferred') return;
|
|
36
37
|
this.#toolCalls += 1;
|
|
37
38
|
if (!result.ok) this.#failedToolCalls += 1;
|
|
38
39
|
const base: RunEvidenceEntry = {
|
|
@@ -18,6 +18,8 @@ export class StallGuard {
|
|
|
18
18
|
for (let index = 0; index < calls.length; index += 1) {
|
|
19
19
|
const call = calls[index];
|
|
20
20
|
const result = results[index];
|
|
21
|
+
if (!result.ok && result.disposition === 'deferred')
|
|
22
|
+
continue;
|
|
21
23
|
if (result.ok) {
|
|
22
24
|
this.#lastFailure = '';
|
|
23
25
|
this.#lastFailureCall = '';
|
|
@@ -41,9 +43,13 @@ export class StallGuard {
|
|
|
41
43
|
};
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
|
-
const
|
|
45
|
-
.map((call, index) => {
|
|
46
|
-
|
|
46
|
+
const batchItems = calls
|
|
47
|
+
.map((call, index) => ({ call, result: results[index] }))
|
|
48
|
+
.filter(({ result }) => result.ok || result.disposition !== 'deferred');
|
|
49
|
+
if (batchItems.length === 0)
|
|
50
|
+
return { stalled: false };
|
|
51
|
+
const batch = hash(batchItems
|
|
52
|
+
.map(({ call, result }) => {
|
|
47
53
|
return `${call.function.name}\0${call.function.arguments}\0${result.ok ? result.content : result.error}`;
|
|
48
54
|
})
|
|
49
55
|
.join('\x01'));
|
|
@@ -35,6 +35,7 @@ export class StallGuard {
|
|
|
35
35
|
for (let index = 0; index < calls.length; index += 1) {
|
|
36
36
|
const call = calls[index]!;
|
|
37
37
|
const result = results[index]!;
|
|
38
|
+
if (!result.ok && result.disposition === 'deferred') continue;
|
|
38
39
|
if (result.ok) {
|
|
39
40
|
this.#lastFailure = '';
|
|
40
41
|
this.#lastFailureCall = '';
|
|
@@ -60,10 +61,13 @@ export class StallGuard {
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
const batchItems = calls
|
|
65
|
+
.map((call, index) => ({ call, result: results[index]! }))
|
|
66
|
+
.filter(({ result }) => result.ok || result.disposition !== 'deferred');
|
|
67
|
+
if (batchItems.length === 0) return { stalled: false };
|
|
63
68
|
const batch = hash(
|
|
64
|
-
|
|
65
|
-
.map((call,
|
|
66
|
-
const result = results[index]!;
|
|
69
|
+
batchItems
|
|
70
|
+
.map(({ call, result }) => {
|
|
67
71
|
return `${call.function.name}\0${call.function.arguments}\0${result.ok ? result.content : result.error}`;
|
|
68
72
|
})
|
|
69
73
|
.join('\x01'),
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, rename, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { extname, join } from 'node:path';
|
|
4
|
+
import { agentStateDir } from '../context/agentHome.js';
|
|
5
|
+
export const DEFAULT_MAX_IMAGE_INPUT_BYTES = 25 * 1024 * 1024;
|
|
6
|
+
export const DEFAULT_MAX_MODEL_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
7
|
+
function is(bytes, offset, signature) {
|
|
8
|
+
return signature.every((value, index) => bytes[offset + index] === value);
|
|
9
|
+
}
|
|
10
|
+
/** Actual bytes are authoritative; names and declared MIME are never trusted. */
|
|
11
|
+
export function detectImageMime(bytes) {
|
|
12
|
+
if (bytes.length >= 8 && is(bytes, 0, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))
|
|
13
|
+
return 'image/png';
|
|
14
|
+
if (bytes.length >= 3 && is(bytes, 0, [0xff, 0xd8, 0xff]))
|
|
15
|
+
return 'image/jpeg';
|
|
16
|
+
if (bytes.length >= 6 && ['GIF87a', 'GIF89a'].includes(bytes.toString('ascii', 0, 6)))
|
|
17
|
+
return 'image/gif';
|
|
18
|
+
if (bytes.length >= 12 && bytes.toString('ascii', 0, 4) === 'RIFF' && bytes.toString('ascii', 8, 12) === 'WEBP')
|
|
19
|
+
return 'image/webp';
|
|
20
|
+
if (bytes.length >= 2 && bytes.toString('ascii', 0, 2) === 'BM')
|
|
21
|
+
return 'image/bmp';
|
|
22
|
+
if (bytes.length >= 4 && (is(bytes, 0, [0x49, 0x49, 0x2a, 0x00]) || is(bytes, 0, [0x4d, 0x4d, 0x00, 0x2a])))
|
|
23
|
+
return 'image/tiff';
|
|
24
|
+
if (bytes.length >= 16 && bytes.toString('ascii', 4, 8) === 'ftyp') {
|
|
25
|
+
const brands = bytes.subarray(8, Math.min(bytes.length, 64)).toString('ascii');
|
|
26
|
+
if (/(?:avif|avis)/.test(brands))
|
|
27
|
+
return 'image/avif';
|
|
28
|
+
if (/(?:heic|heix|hevc|hevx|heim|heis|mif1|msf1)/.test(brands))
|
|
29
|
+
return 'image/heic';
|
|
30
|
+
}
|
|
31
|
+
const prefix = bytes.subarray(0, Math.min(bytes.length, 4096)).toString('utf8').replace(/^\uFEFF/, '').trimStart();
|
|
32
|
+
if (/^(?:<\?xml[^>]*>\s*)?<svg\b/i.test(prefix))
|
|
33
|
+
return 'image/svg+xml';
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function declaredMimeForExtension(path) {
|
|
37
|
+
return {
|
|
38
|
+
'.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png',
|
|
39
|
+
'.gif': 'image/gif', '.webp': 'image/webp', '.bmp': 'image/bmp',
|
|
40
|
+
'.tif': 'image/tiff', '.tiff': 'image/tiff', '.avif': 'image/avif',
|
|
41
|
+
'.heic': 'image/heic', '.heif': 'image/heic', '.svg': 'image/svg+xml',
|
|
42
|
+
}[extname(path).toLowerCase()];
|
|
43
|
+
}
|
|
44
|
+
async function loadSharp() {
|
|
45
|
+
return (await import('sharp')).default;
|
|
46
|
+
}
|
|
47
|
+
async function decodeHeic(bytes) {
|
|
48
|
+
const imported = await import('heic-convert');
|
|
49
|
+
const convert = imported.default;
|
|
50
|
+
if (typeof convert !== 'function')
|
|
51
|
+
throw new Error('HEIC 解码器未正确加载');
|
|
52
|
+
const output = await convert({ buffer: bytes, format: 'JPEG', quality: 0.92 });
|
|
53
|
+
return Buffer.from(output);
|
|
54
|
+
}
|
|
55
|
+
async function normalizeForModel(bytes, detected, maxOutputBytes) {
|
|
56
|
+
const sharp = await loadSharp();
|
|
57
|
+
const decoded = detected === 'image/heic' ? await decodeHeic(bytes) : bytes;
|
|
58
|
+
const image = sharp(decoded, { animated: false }).rotate().resize({
|
|
59
|
+
width: 4096,
|
|
60
|
+
height: 4096,
|
|
61
|
+
fit: 'inside',
|
|
62
|
+
withoutEnlargement: true,
|
|
63
|
+
});
|
|
64
|
+
const metadata = await image.metadata();
|
|
65
|
+
const preserveAlpha = metadata.hasAlpha === true || detected === 'image/svg+xml';
|
|
66
|
+
let output = preserveAlpha
|
|
67
|
+
? await image.clone().png({ compressionLevel: 9 }).toBuffer()
|
|
68
|
+
: await image.clone().jpeg({ quality: 88, mozjpeg: true }).toBuffer();
|
|
69
|
+
if (output.length > maxOutputBytes && !preserveAlpha) {
|
|
70
|
+
output = await image.clone().jpeg({ quality: 75, mozjpeg: true }).toBuffer();
|
|
71
|
+
}
|
|
72
|
+
if (output.length > maxOutputBytes) {
|
|
73
|
+
throw new Error(`规范化后图片仍超过 ${(maxOutputBytes / 1024 / 1024).toFixed(0)}MB 上限`);
|
|
74
|
+
}
|
|
75
|
+
return { bytes: output, mime: preserveAlpha ? 'image/png' : 'image/jpeg' };
|
|
76
|
+
}
|
|
77
|
+
function modelMime(detected) {
|
|
78
|
+
return ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].includes(detected)
|
|
79
|
+
? detected
|
|
80
|
+
: null;
|
|
81
|
+
}
|
|
82
|
+
export async function resolveLocalImage(path, options = {}) {
|
|
83
|
+
const info = await stat(path).catch((error) => {
|
|
84
|
+
throw new Error(`无法读取图片:${path}(${error.message})`);
|
|
85
|
+
});
|
|
86
|
+
if (!info.isFile())
|
|
87
|
+
throw new Error(`图片路径不是文件:${path}`);
|
|
88
|
+
const maxInputBytes = options.maxInputBytes ?? DEFAULT_MAX_IMAGE_INPUT_BYTES;
|
|
89
|
+
const maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_MODEL_IMAGE_BYTES;
|
|
90
|
+
if (info.size > maxInputBytes)
|
|
91
|
+
throw new Error(`图片超过 ${(maxInputBytes / 1024 / 1024).toFixed(0)}MB 输入上限:${path}`);
|
|
92
|
+
const bytes = await readFile(path);
|
|
93
|
+
const detected = detectImageMime(bytes);
|
|
94
|
+
if (!detected) {
|
|
95
|
+
const declared = declaredMimeForExtension(path);
|
|
96
|
+
throw new Error(declared
|
|
97
|
+
? `文件扩展名声明为 ${declared},但内容不是可解码图片:${path}`
|
|
98
|
+
: `无法从文件内容识别图片格式:${path}`);
|
|
99
|
+
}
|
|
100
|
+
const declared = declaredMimeForExtension(path);
|
|
101
|
+
const diagnostic = declared && declared !== detected
|
|
102
|
+
? `扩展名声明 ${declared},实际内容为 ${detected}`
|
|
103
|
+
: undefined;
|
|
104
|
+
const directMime = modelMime(detected);
|
|
105
|
+
if (directMime && bytes.length <= maxOutputBytes) {
|
|
106
|
+
// Decode metadata once so truncated/corrupt files do not masquerade as images.
|
|
107
|
+
const sharp = await loadSharp();
|
|
108
|
+
const metadata = await sharp(bytes, { animated: detected === 'image/gif' }).metadata();
|
|
109
|
+
const needsOrientationFix = detected === 'image/jpeg' && (metadata.orientation ?? 1) > 1;
|
|
110
|
+
if (!needsOrientationFix) {
|
|
111
|
+
return {
|
|
112
|
+
originalPath: path,
|
|
113
|
+
path,
|
|
114
|
+
originalMime: detected,
|
|
115
|
+
mime: directMime,
|
|
116
|
+
size: bytes.length,
|
|
117
|
+
transformed: false,
|
|
118
|
+
...(diagnostic ? { diagnostic } : {}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const normalized = await normalizeForModel(bytes, detected, maxOutputBytes).catch((error) => {
|
|
123
|
+
throw new Error(`图片格式 ${detected} 无法规范化:${path}(${error.message})`);
|
|
124
|
+
});
|
|
125
|
+
const hash = createHash('sha256')
|
|
126
|
+
.update(bytes)
|
|
127
|
+
.update('\0model-image-v1\0')
|
|
128
|
+
.update(normalized.mime)
|
|
129
|
+
.digest('hex');
|
|
130
|
+
const extension = normalized.mime === 'image/png' ? '.png' : '.jpg';
|
|
131
|
+
const dir = join(agentStateDir(), 'image-derivatives', hash.slice(0, 2));
|
|
132
|
+
const target = join(dir, `${hash}${extension}`);
|
|
133
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
134
|
+
const exists = await stat(target).then((item) => item.isFile()).catch(() => false);
|
|
135
|
+
if (!exists) {
|
|
136
|
+
const temp = `${target}.${randomUUID()}.tmp`;
|
|
137
|
+
await writeFile(temp, normalized.bytes, { mode: 0o600 });
|
|
138
|
+
await rename(temp, target).catch(async () => {
|
|
139
|
+
const wonRace = await stat(target).then((item) => item.isFile()).catch(() => false);
|
|
140
|
+
if (!wonRace)
|
|
141
|
+
throw new Error(`无法保存图片派生文件:${target}`);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
originalPath: path,
|
|
146
|
+
path: target,
|
|
147
|
+
originalMime: detected,
|
|
148
|
+
mime: normalized.mime,
|
|
149
|
+
size: normalized.bytes.length,
|
|
150
|
+
transformed: true,
|
|
151
|
+
diagnostic: [diagnostic, `已规范化为 ${normalized.mime}`].filter(Boolean).join(';'),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export async function tryResolveLocalImage(path) {
|
|
155
|
+
try {
|
|
156
|
+
return await resolveLocalImage(path);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, rename, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { extname, join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { agentStateDir } from '../context/agentHome.js';
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_MAX_IMAGE_INPUT_BYTES = 25 * 1024 * 1024;
|
|
8
|
+
export const DEFAULT_MAX_MODEL_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
9
|
+
|
|
10
|
+
export type DetectedImageMime =
|
|
11
|
+
| 'image/jpeg'
|
|
12
|
+
| 'image/png'
|
|
13
|
+
| 'image/gif'
|
|
14
|
+
| 'image/webp'
|
|
15
|
+
| 'image/bmp'
|
|
16
|
+
| 'image/tiff'
|
|
17
|
+
| 'image/avif'
|
|
18
|
+
| 'image/heic'
|
|
19
|
+
| 'image/svg+xml';
|
|
20
|
+
|
|
21
|
+
export interface ResolvedLocalImage {
|
|
22
|
+
originalPath: string;
|
|
23
|
+
path: string;
|
|
24
|
+
originalMime: DetectedImageMime;
|
|
25
|
+
mime: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp';
|
|
26
|
+
size: number;
|
|
27
|
+
transformed: boolean;
|
|
28
|
+
diagnostic?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function is(bytes: Buffer, offset: number, signature: readonly number[]): boolean {
|
|
32
|
+
return signature.every((value, index) => bytes[offset + index] === value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Actual bytes are authoritative; names and declared MIME are never trusted. */
|
|
36
|
+
export function detectImageMime(bytes: Buffer): DetectedImageMime | null {
|
|
37
|
+
if (bytes.length >= 8 && is(bytes, 0, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) return 'image/png';
|
|
38
|
+
if (bytes.length >= 3 && is(bytes, 0, [0xff, 0xd8, 0xff])) return 'image/jpeg';
|
|
39
|
+
if (bytes.length >= 6 && ['GIF87a', 'GIF89a'].includes(bytes.toString('ascii', 0, 6))) return 'image/gif';
|
|
40
|
+
if (bytes.length >= 12 && bytes.toString('ascii', 0, 4) === 'RIFF' && bytes.toString('ascii', 8, 12) === 'WEBP') return 'image/webp';
|
|
41
|
+
if (bytes.length >= 2 && bytes.toString('ascii', 0, 2) === 'BM') return 'image/bmp';
|
|
42
|
+
if (bytes.length >= 4 && (is(bytes, 0, [0x49, 0x49, 0x2a, 0x00]) || is(bytes, 0, [0x4d, 0x4d, 0x00, 0x2a]))) return 'image/tiff';
|
|
43
|
+
if (bytes.length >= 16 && bytes.toString('ascii', 4, 8) === 'ftyp') {
|
|
44
|
+
const brands = bytes.subarray(8, Math.min(bytes.length, 64)).toString('ascii');
|
|
45
|
+
if (/(?:avif|avis)/.test(brands)) return 'image/avif';
|
|
46
|
+
if (/(?:heic|heix|hevc|hevx|heim|heis|mif1|msf1)/.test(brands)) return 'image/heic';
|
|
47
|
+
}
|
|
48
|
+
const prefix = bytes.subarray(0, Math.min(bytes.length, 4096)).toString('utf8').replace(/^\uFEFF/, '').trimStart();
|
|
49
|
+
if (/^(?:<\?xml[^>]*>\s*)?<svg\b/i.test(prefix)) return 'image/svg+xml';
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function declaredMimeForExtension(path: string): string | undefined {
|
|
54
|
+
return ({
|
|
55
|
+
'.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png',
|
|
56
|
+
'.gif': 'image/gif', '.webp': 'image/webp', '.bmp': 'image/bmp',
|
|
57
|
+
'.tif': 'image/tiff', '.tiff': 'image/tiff', '.avif': 'image/avif',
|
|
58
|
+
'.heic': 'image/heic', '.heif': 'image/heic', '.svg': 'image/svg+xml',
|
|
59
|
+
} as Record<string, string | undefined>)[extname(path).toLowerCase()];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function loadSharp() {
|
|
63
|
+
return (await import('sharp')).default;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function decodeHeic(bytes: Buffer): Promise<Buffer> {
|
|
67
|
+
const imported = await import('heic-convert');
|
|
68
|
+
const convert = imported.default;
|
|
69
|
+
if (typeof convert !== 'function') throw new Error('HEIC 解码器未正确加载');
|
|
70
|
+
const output = await convert({ buffer: bytes, format: 'JPEG', quality: 0.92 });
|
|
71
|
+
return Buffer.from(output);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function normalizeForModel(
|
|
75
|
+
bytes: Buffer,
|
|
76
|
+
detected: DetectedImageMime,
|
|
77
|
+
maxOutputBytes: number,
|
|
78
|
+
): Promise<{ bytes: Buffer; mime: 'image/jpeg' | 'image/png' }> {
|
|
79
|
+
const sharp = await loadSharp();
|
|
80
|
+
const decoded = detected === 'image/heic' ? await decodeHeic(bytes) : bytes;
|
|
81
|
+
const image = sharp(decoded, { animated: false }).rotate().resize({
|
|
82
|
+
width: 4096,
|
|
83
|
+
height: 4096,
|
|
84
|
+
fit: 'inside',
|
|
85
|
+
withoutEnlargement: true,
|
|
86
|
+
});
|
|
87
|
+
const metadata = await image.metadata();
|
|
88
|
+
const preserveAlpha = metadata.hasAlpha === true || detected === 'image/svg+xml';
|
|
89
|
+
let output = preserveAlpha
|
|
90
|
+
? await image.clone().png({ compressionLevel: 9 }).toBuffer()
|
|
91
|
+
: await image.clone().jpeg({ quality: 88, mozjpeg: true }).toBuffer();
|
|
92
|
+
if (output.length > maxOutputBytes && !preserveAlpha) {
|
|
93
|
+
output = await image.clone().jpeg({ quality: 75, mozjpeg: true }).toBuffer();
|
|
94
|
+
}
|
|
95
|
+
if (output.length > maxOutputBytes) {
|
|
96
|
+
throw new Error(`规范化后图片仍超过 ${(maxOutputBytes / 1024 / 1024).toFixed(0)}MB 上限`);
|
|
97
|
+
}
|
|
98
|
+
return { bytes: output, mime: preserveAlpha ? 'image/png' : 'image/jpeg' };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function modelMime(detected: DetectedImageMime): ResolvedLocalImage['mime'] | null {
|
|
102
|
+
return ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].includes(detected)
|
|
103
|
+
? detected as ResolvedLocalImage['mime']
|
|
104
|
+
: null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function resolveLocalImage(
|
|
108
|
+
path: string,
|
|
109
|
+
options: { maxInputBytes?: number; maxOutputBytes?: number } = {},
|
|
110
|
+
): Promise<ResolvedLocalImage> {
|
|
111
|
+
const info = await stat(path).catch((error: Error) => {
|
|
112
|
+
throw new Error(`无法读取图片:${path}(${error.message})`);
|
|
113
|
+
});
|
|
114
|
+
if (!info.isFile()) throw new Error(`图片路径不是文件:${path}`);
|
|
115
|
+
const maxInputBytes = options.maxInputBytes ?? DEFAULT_MAX_IMAGE_INPUT_BYTES;
|
|
116
|
+
const maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_MODEL_IMAGE_BYTES;
|
|
117
|
+
if (info.size > maxInputBytes) throw new Error(`图片超过 ${(maxInputBytes / 1024 / 1024).toFixed(0)}MB 输入上限:${path}`);
|
|
118
|
+
const bytes = await readFile(path);
|
|
119
|
+
const detected = detectImageMime(bytes);
|
|
120
|
+
if (!detected) {
|
|
121
|
+
const declared = declaredMimeForExtension(path);
|
|
122
|
+
throw new Error(
|
|
123
|
+
declared
|
|
124
|
+
? `文件扩展名声明为 ${declared},但内容不是可解码图片:${path}`
|
|
125
|
+
: `无法从文件内容识别图片格式:${path}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
const declared = declaredMimeForExtension(path);
|
|
129
|
+
const diagnostic = declared && declared !== detected
|
|
130
|
+
? `扩展名声明 ${declared},实际内容为 ${detected}`
|
|
131
|
+
: undefined;
|
|
132
|
+
const directMime = modelMime(detected);
|
|
133
|
+
if (directMime && bytes.length <= maxOutputBytes) {
|
|
134
|
+
// Decode metadata once so truncated/corrupt files do not masquerade as images.
|
|
135
|
+
const sharp = await loadSharp();
|
|
136
|
+
const metadata = await sharp(bytes, { animated: detected === 'image/gif' }).metadata();
|
|
137
|
+
const needsOrientationFix = detected === 'image/jpeg' && (metadata.orientation ?? 1) > 1;
|
|
138
|
+
if (!needsOrientationFix) {
|
|
139
|
+
return {
|
|
140
|
+
originalPath: path,
|
|
141
|
+
path,
|
|
142
|
+
originalMime: detected,
|
|
143
|
+
mime: directMime,
|
|
144
|
+
size: bytes.length,
|
|
145
|
+
transformed: false,
|
|
146
|
+
...(diagnostic ? { diagnostic } : {}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const normalized = await normalizeForModel(bytes, detected, maxOutputBytes).catch((error) => {
|
|
152
|
+
throw new Error(`图片格式 ${detected} 无法规范化:${path}(${(error as Error).message})`);
|
|
153
|
+
});
|
|
154
|
+
const hash = createHash('sha256')
|
|
155
|
+
.update(bytes)
|
|
156
|
+
.update('\0model-image-v1\0')
|
|
157
|
+
.update(normalized.mime)
|
|
158
|
+
.digest('hex');
|
|
159
|
+
const extension = normalized.mime === 'image/png' ? '.png' : '.jpg';
|
|
160
|
+
const dir = join(agentStateDir(), 'image-derivatives', hash.slice(0, 2));
|
|
161
|
+
const target = join(dir, `${hash}${extension}`);
|
|
162
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
163
|
+
const exists = await stat(target).then((item) => item.isFile()).catch(() => false);
|
|
164
|
+
if (!exists) {
|
|
165
|
+
const temp = `${target}.${randomUUID()}.tmp`;
|
|
166
|
+
await writeFile(temp, normalized.bytes, { mode: 0o600 });
|
|
167
|
+
await rename(temp, target).catch(async () => {
|
|
168
|
+
const wonRace = await stat(target).then((item) => item.isFile()).catch(() => false);
|
|
169
|
+
if (!wonRace) throw new Error(`无法保存图片派生文件:${target}`);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
originalPath: path,
|
|
174
|
+
path: target,
|
|
175
|
+
originalMime: detected,
|
|
176
|
+
mime: normalized.mime,
|
|
177
|
+
size: normalized.bytes.length,
|
|
178
|
+
transformed: true,
|
|
179
|
+
diagnostic: [diagnostic, `已规范化为 ${normalized.mime}`].filter(Boolean).join(';'),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function tryResolveLocalImage(path: string): Promise<ResolvedLocalImage | null> {
|
|
184
|
+
try {
|
|
185
|
+
return await resolveLocalImage(path);
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -16,13 +16,31 @@
|
|
|
16
16
|
* 设计原则:
|
|
17
17
|
* - 纯工具函数,无全局状态,无副作用(除了 shell 子进程)
|
|
18
18
|
* - 不依赖任何插件代码,可被 src/ 和 plugins/ 同时导入
|
|
19
|
-
* -
|
|
19
|
+
* - 工作目录隔离:相对路径以调用方给的 cwd 为锚,缺省 getWorkingDir()
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ cwd 参数不是可有可无的修饰:**验收必须和写入锚在同一个目录**。
|
|
22
|
+
* 会话进入 session worktree 后(EnterWorktree),写文件的工具跟着 worktree 走
|
|
23
|
+
* (loop.ts 按 sessionState.activeWorktree 重定向 cwd),验收若仍锚原 workspace,
|
|
24
|
+
* 就会在空目录里找产物 —— file_exists 恒 false,工作流被判失败并反复重试,
|
|
25
|
+
* 而产物其实好好躺在 worktree 里。调用方拿得到有效 cwd 时务必传进来。
|
|
20
26
|
* ============================================================
|
|
21
27
|
*/
|
|
22
28
|
import { existsSync, readFileSync } from 'node:fs';
|
|
23
29
|
import { spawn } from 'node:child_process';
|
|
24
30
|
import { sanitizeCredentialEnv } from '../config/configService.js';
|
|
31
|
+
import { CONFIG_LIMITS, normalizeLimited } from '../config/numericLimits.js';
|
|
25
32
|
import { getWorkingDir } from '../bootstrap/workingDir.js';
|
|
33
|
+
/**
|
|
34
|
+
* `shell:` 检查的默认超时——**唯一真值**。
|
|
35
|
+
*
|
|
36
|
+
* ⚠️ 这个数字此前在本文件里写了两份(`parseCheckString` 的构造处 + `runVerification`
|
|
37
|
+
* 的 `?? 30_000` 兜底),改一处漏一处就会出现「解析时 30s、执行时 60s」这种谁都看不出
|
|
38
|
+
* 的分裂。边界见 `config/numericLimits.ts::CONFIG_LIMITS.verifyShellTimeoutMs`。
|
|
39
|
+
*
|
|
40
|
+
* 为什么默认值不能太大:`verify()` 是工作流硬门,跑不完就阻塞整条流水线。
|
|
41
|
+
* 需要跑全量测试套件的场景应当缩到具体文件,或由调用方显式传 `defaultShellTimeoutMs`。
|
|
42
|
+
*/
|
|
43
|
+
export const DEFAULT_SHELL_CHECK_TIMEOUT_MS = 30_000;
|
|
26
44
|
/** 从 "type:value" 字符串解析为 Check 对象。 */
|
|
27
45
|
function parseVerifyArg(arg) {
|
|
28
46
|
const colonIdx = arg.indexOf(':');
|
|
@@ -32,7 +50,10 @@ function parseVerifyArg(arg) {
|
|
|
32
50
|
const value = arg.slice(colonIdx + 1).trim();
|
|
33
51
|
switch (type) {
|
|
34
52
|
case 'shell':
|
|
35
|
-
|
|
53
|
+
// ⚠️ 这里**不填** timeout:解析层只管语法,超时是策略、由 runVerification 决定。
|
|
54
|
+
// 曾经在这里写死 30_000,于是 `check.timeout ?? 默认值` 里的默认值永远走不到 ——
|
|
55
|
+
// 调用方传的超时被一个「解析时顺手填上的」值静默盖掉。
|
|
56
|
+
return { type: 'shell', command: value };
|
|
36
57
|
case 'file_exists':
|
|
37
58
|
return { type: 'file_exists', file: value };
|
|
38
59
|
case 'file_contains': {
|
|
@@ -92,12 +113,23 @@ function isAbsolutePath(file) {
|
|
|
92
113
|
/^[A-Za-z]:[\\/]/.test(file) // C:\ 或 C:/
|
|
93
114
|
);
|
|
94
115
|
}
|
|
95
|
-
function runShell(command, timeout) {
|
|
116
|
+
function runShell(command, timeout, cwd) {
|
|
96
117
|
return new Promise((resolve) => {
|
|
97
118
|
const isWin = process.platform === 'win32';
|
|
119
|
+
const opts = { timeout, cwd, env: sanitizeCredentialEnv() };
|
|
120
|
+
// ⚠️ windowsVerbatimArguments: true 是必须的,不是可选优化:Node 给 cmd.exe/
|
|
121
|
+
// .bat 目标默认加一层它自己的参数转义(CVE-2024-27980 之后的安全加固),
|
|
122
|
+
// 会把 command 字符串里模型自己写好的转义(比如内嵌 PowerShell 单行脚本里的
|
|
123
|
+
// `\"..."`)再转义一遍,导致 cmd.exe 转交给子进程的字符串被改写、PowerShell
|
|
124
|
+
// 解析报 UnexpectedToken——文件内容明明字节级正确,check 却稳定失败。
|
|
125
|
+
// 已实测:一段合法的 `powershell -Command "...; Write-Host \"x\"; ..."`
|
|
126
|
+
// 不加这个选项必挂,加了立刻 exitCode 0;简单命令(echo/dir/bun --version)
|
|
127
|
+
// 行为不受影响。verify() 的 shell: 检查类型专供工作流的 hard_checks 使用,
|
|
128
|
+
// 命令来自可信的 LLM 契约草稿或用户输入,不是不可信的外部数据,
|
|
129
|
+
// 关掉这层转义不引入新的注入面。
|
|
98
130
|
const child = isWin
|
|
99
|
-
? spawn('cmd', ['/c', command], {
|
|
100
|
-
: spawn('bash', ['-c', command],
|
|
131
|
+
? spawn('cmd', ['/c', command], { ...opts, windowsVerbatimArguments: true })
|
|
132
|
+
: spawn('bash', ['-c', command], opts);
|
|
101
133
|
let stdout = '';
|
|
102
134
|
let stderr = '';
|
|
103
135
|
child.stdout.on('data', (d) => {
|
|
@@ -114,8 +146,8 @@ function runShell(command, timeout) {
|
|
|
114
146
|
});
|
|
115
147
|
});
|
|
116
148
|
}
|
|
117
|
-
export async function verifyShell(command, timeout) {
|
|
118
|
-
const r = await runShell(command, timeout);
|
|
149
|
+
export async function verifyShell(command, timeout, cwd = getWorkingDir()) {
|
|
150
|
+
const r = await runShell(command, timeout, cwd);
|
|
119
151
|
if (r.errored) {
|
|
120
152
|
return {
|
|
121
153
|
check: { type: 'shell', command },
|
|
@@ -129,9 +161,9 @@ export async function verifyShell(command, timeout) {
|
|
|
129
161
|
output: r.stdout.trim() || r.stderr.trim() || `exit code ${r.exitCode}`,
|
|
130
162
|
};
|
|
131
163
|
}
|
|
132
|
-
export function verifyFileExists(file) {
|
|
133
|
-
//
|
|
134
|
-
const resolved = isAbsolutePath(file) ? file : `${
|
|
164
|
+
export function verifyFileExists(file, cwd = getWorkingDir()) {
|
|
165
|
+
// 相对路径以调用方给的 cwd 为锚(绝对路径识别含 UNC)
|
|
166
|
+
const resolved = isAbsolutePath(file) ? file : `${cwd}/${file}`;
|
|
135
167
|
const exists = existsSync(resolved);
|
|
136
168
|
return {
|
|
137
169
|
check: { type: 'file_exists', file },
|
|
@@ -139,8 +171,8 @@ export function verifyFileExists(file) {
|
|
|
139
171
|
output: exists ? '文件存在' : `文件不存在: ${resolved}`,
|
|
140
172
|
};
|
|
141
173
|
}
|
|
142
|
-
export function verifyFileContains(file, pattern) {
|
|
143
|
-
const resolved = isAbsolutePath(file) ? file : `${
|
|
174
|
+
export function verifyFileContains(file, pattern, cwd = getWorkingDir()) {
|
|
175
|
+
const resolved = isAbsolutePath(file) ? file : `${cwd}/${file}`;
|
|
144
176
|
try {
|
|
145
177
|
const content = readFileSync(resolved, 'utf8');
|
|
146
178
|
const regex = new RegExp(pattern);
|
|
@@ -159,11 +191,11 @@ export function verifyFileContains(file, pattern) {
|
|
|
159
191
|
};
|
|
160
192
|
}
|
|
161
193
|
}
|
|
162
|
-
async function verifyTestCount(minCount) {
|
|
194
|
+
async function verifyTestCount(minCount, cwd = getWorkingDir()) {
|
|
163
195
|
// 跨运行时:开发用 bun → bun test;npm 全局安装用 Node → npm test --silent
|
|
164
196
|
// 用 process.versions.bun 探测比 typeof Bun !== 'undefined' 更稳(不依赖全局类型)
|
|
165
197
|
const cmd = process.versions.bun ? 'bun test' : 'npm test --silent';
|
|
166
|
-
const r = await runShell(cmd, 60_000);
|
|
198
|
+
const r = await runShell(cmd, 60_000, cwd);
|
|
167
199
|
const combined = `${r.stdout}\n${r.stderr}`;
|
|
168
200
|
if (r.errored) {
|
|
169
201
|
return {
|
|
@@ -185,7 +217,8 @@ async function verifyTestCount(minCount) {
|
|
|
185
217
|
* 执行一组检查,返回聚合结果。
|
|
186
218
|
* 全部通过 → passed=true;任一失败 → passed=false + summary 列出失败项。
|
|
187
219
|
*/
|
|
188
|
-
export async function runVerification(checks) {
|
|
220
|
+
export async function runVerification(checks, cwd = getWorkingDir(), options = {}) {
|
|
221
|
+
const shellTimeout = normalizeLimited(options.defaultShellTimeoutMs, CONFIG_LIMITS.verifyShellTimeoutMs, DEFAULT_SHELL_CHECK_TIMEOUT_MS);
|
|
189
222
|
if (checks.length === 0) {
|
|
190
223
|
return { passed: true, details: [], summary: '无验证项' };
|
|
191
224
|
}
|
|
@@ -194,16 +227,16 @@ export async function runVerification(checks) {
|
|
|
194
227
|
let result;
|
|
195
228
|
switch (check.type) {
|
|
196
229
|
case 'shell':
|
|
197
|
-
result = await verifyShell(check.command ?? '', check.timeout ??
|
|
230
|
+
result = await verifyShell(check.command ?? '', check.timeout ?? shellTimeout, cwd);
|
|
198
231
|
break;
|
|
199
232
|
case 'file_exists':
|
|
200
|
-
result = verifyFileExists(check.file ?? '');
|
|
233
|
+
result = verifyFileExists(check.file ?? '', cwd);
|
|
201
234
|
break;
|
|
202
235
|
case 'file_contains':
|
|
203
|
-
result = verifyFileContains(check.file ?? '', check.pattern ?? '');
|
|
236
|
+
result = verifyFileContains(check.file ?? '', check.pattern ?? '', cwd);
|
|
204
237
|
break;
|
|
205
238
|
case 'test_count':
|
|
206
|
-
result = await verifyTestCount(check.minCount ?? 0);
|
|
239
|
+
result = await verifyTestCount(check.minCount ?? 0, cwd);
|
|
207
240
|
break;
|
|
208
241
|
default:
|
|
209
242
|
result = {
|
|
@@ -212,7 +245,10 @@ export async function runVerification(checks) {
|
|
|
212
245
|
output: `未知验证类型: ${check.type}`,
|
|
213
246
|
};
|
|
214
247
|
}
|
|
215
|
-
|
|
248
|
+
// 回填**原始** check:各 verifier 只收散参(command/file/pattern),返回时会
|
|
249
|
+
// 自己重建一个残缺的 check 对象,把 timeout 这类字段丢掉。details 是给人排障和
|
|
250
|
+
// 给工作流做卡死指纹用的,它必须如实反映「这一条到底是按什么跑的」。
|
|
251
|
+
results.push({ ...result, check });
|
|
216
252
|
}
|
|
217
253
|
const allPassed = results.every((r) => r.passed);
|
|
218
254
|
const failedNames = results.filter((r) => !r.passed).map((r) => formatCheckName(r.check));
|