minimal-agent 0.13.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 +60 -4
- 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 +197 -206
- package/plugins/workflow-runner/src/index.ts +248 -249
- 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 +400 -489
- package/plugins/workflow-runner/src/runner.ts +488 -560
- 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 +151 -307
- package/plugins/workflow-runner/src/taskIdentity.ts +207 -355
- 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 -211
- package/plugins/workflow-runner/src/types.ts +154 -623
- 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 +474 -802
- package/plugins/workflow-runner/src/workflowState.ts +575 -970
- 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/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 +27 -6
- package/src/cli/print.ts +48 -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 +419 -74
- package/src/config/configService.ts +703 -145
- 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 +34 -5
- package/src/infra/verification.ts +39 -4
- 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 +139 -7
- package/src/plugins/pluginRunner.ts +173 -9
- package/src/plugins/slashFallback.js +113 -0
- package/src/plugins/slashFallback.ts +137 -0
- package/src/prompts/policyRegistry.js +12 -32
- package/src/prompts/policyRegistry.ts +15 -32
- 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 +90 -46
- package/src/tools/advisor/advisor.ts +100 -47
- 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 +22 -15
- package/src/tools/bash/bash.ts +23 -17
- 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/presentresult/presentresult.js +6 -15
- package/src/tools/presentresult/presentresult.ts +6 -15
- package/src/tools/read/read.js +62 -9
- package/src/tools/read/read.ts +63 -9
- package/src/tools/shared/fileUtils.js +1 -2
- package/src/tools/shared/fileUtils.ts +1 -2
- 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 -34
- package/src/tools/webbrowser/browserControl.ts +210 -56
- 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 +679 -144
- package/src/tools/webbrowser/webbrowser.ts +975 -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 +440 -38
- 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/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 -639
- package/plugins/workflow-runner/src/expressions.ts +0 -640
- 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 -126
- package/plugins/workflow-runner/src/recovery.ts +0 -156
- 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 -42
- package/plugins/workflow-runner/src/stepExecutors/assert.ts +0 -52
- 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 -227
- package/plugins/workflow-runner/src/stepExecutors/tool.ts +0 -274
- package/plugins/workflow-runner/src/stepExecutors/verify.js +0 -61
- package/plugins/workflow-runner/src/stepExecutors/verify.ts +0 -72
- 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 -167
- 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/e2eNewLayout.test.ts +0 -314
- package/plugins/workflow-runner/test/engineRobustness.test.ts +0 -234
- package/plugins/workflow-runner/test/expressions.test.ts +0 -439
- 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 -106
- 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 -221
- package/plugins/workflow-runner/test/recovery.test.ts +0 -265
- package/plugins/workflow-runner/test/research.load.test.ts +0 -328
- package/plugins/workflow-runner/test/runner.test.ts +0 -911
- package/plugins/workflow-runner/test/runnerPreflight.test.ts +0 -436
- package/plugins/workflow-runner/test/skillTurnDone.test.ts +0 -71
- package/plugins/workflow-runner/test/stateLifecycle.test.ts +0 -198
- package/plugins/workflow-runner/test/stepPipeline.test.ts +0 -114
- package/plugins/workflow-runner/test/taskArbitration.test.ts +0 -354
- package/plugins/workflow-runner/test/taskLifecycle.test.ts +0 -324
- 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 -201
- package/workflows/research.yaml +0 -873
- 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
- /package/workflows/{research.md → research/DESIGN.md} +0 -0
|
@@ -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
|
+
}
|
|
@@ -28,7 +28,19 @@
|
|
|
28
28
|
import { existsSync, readFileSync } from 'node:fs';
|
|
29
29
|
import { spawn } from 'node:child_process';
|
|
30
30
|
import { sanitizeCredentialEnv } from '../config/configService.js';
|
|
31
|
+
import { CONFIG_LIMITS, normalizeLimited } from '../config/numericLimits.js';
|
|
31
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;
|
|
32
44
|
/** 从 "type:value" 字符串解析为 Check 对象。 */
|
|
33
45
|
function parseVerifyArg(arg) {
|
|
34
46
|
const colonIdx = arg.indexOf(':');
|
|
@@ -38,7 +50,10 @@ function parseVerifyArg(arg) {
|
|
|
38
50
|
const value = arg.slice(colonIdx + 1).trim();
|
|
39
51
|
switch (type) {
|
|
40
52
|
case 'shell':
|
|
41
|
-
|
|
53
|
+
// ⚠️ 这里**不填** timeout:解析层只管语法,超时是策略、由 runVerification 决定。
|
|
54
|
+
// 曾经在这里写死 30_000,于是 `check.timeout ?? 默认值` 里的默认值永远走不到 ——
|
|
55
|
+
// 调用方传的超时被一个「解析时顺手填上的」值静默盖掉。
|
|
56
|
+
return { type: 'shell', command: value };
|
|
42
57
|
case 'file_exists':
|
|
43
58
|
return { type: 'file_exists', file: value };
|
|
44
59
|
case 'file_contains': {
|
|
@@ -102,8 +117,18 @@ function runShell(command, timeout, cwd) {
|
|
|
102
117
|
return new Promise((resolve) => {
|
|
103
118
|
const isWin = process.platform === 'win32';
|
|
104
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
|
+
// 关掉这层转义不引入新的注入面。
|
|
105
130
|
const child = isWin
|
|
106
|
-
? spawn('cmd', ['/c', command], opts)
|
|
131
|
+
? spawn('cmd', ['/c', command], { ...opts, windowsVerbatimArguments: true })
|
|
107
132
|
: spawn('bash', ['-c', command], opts);
|
|
108
133
|
let stdout = '';
|
|
109
134
|
let stderr = '';
|
|
@@ -192,7 +217,8 @@ async function verifyTestCount(minCount, cwd = getWorkingDir()) {
|
|
|
192
217
|
* 执行一组检查,返回聚合结果。
|
|
193
218
|
* 全部通过 → passed=true;任一失败 → passed=false + summary 列出失败项。
|
|
194
219
|
*/
|
|
195
|
-
export async function runVerification(checks, cwd = getWorkingDir()) {
|
|
220
|
+
export async function runVerification(checks, cwd = getWorkingDir(), options = {}) {
|
|
221
|
+
const shellTimeout = normalizeLimited(options.defaultShellTimeoutMs, CONFIG_LIMITS.verifyShellTimeoutMs, DEFAULT_SHELL_CHECK_TIMEOUT_MS);
|
|
196
222
|
if (checks.length === 0) {
|
|
197
223
|
return { passed: true, details: [], summary: '无验证项' };
|
|
198
224
|
}
|
|
@@ -201,7 +227,7 @@ export async function runVerification(checks, cwd = getWorkingDir()) {
|
|
|
201
227
|
let result;
|
|
202
228
|
switch (check.type) {
|
|
203
229
|
case 'shell':
|
|
204
|
-
result = await verifyShell(check.command ?? '', check.timeout ??
|
|
230
|
+
result = await verifyShell(check.command ?? '', check.timeout ?? shellTimeout, cwd);
|
|
205
231
|
break;
|
|
206
232
|
case 'file_exists':
|
|
207
233
|
result = verifyFileExists(check.file ?? '', cwd);
|
|
@@ -219,7 +245,10 @@ export async function runVerification(checks, cwd = getWorkingDir()) {
|
|
|
219
245
|
output: `未知验证类型: ${check.type}`,
|
|
220
246
|
};
|
|
221
247
|
}
|
|
222
|
-
|
|
248
|
+
// 回填**原始** check:各 verifier 只收散参(command/file/pattern),返回时会
|
|
249
|
+
// 自己重建一个残缺的 check 对象,把 timeout 这类字段丢掉。details 是给人排障和
|
|
250
|
+
// 给工作流做卡死指纹用的,它必须如实反映「这一条到底是按什么跑的」。
|
|
251
|
+
results.push({ ...result, check });
|
|
223
252
|
}
|
|
224
253
|
const allPassed = results.every((r) => r.passed);
|
|
225
254
|
const failedNames = results.filter((r) => !r.passed).map((r) => formatCheckName(r.check));
|
|
@@ -29,9 +29,22 @@
|
|
|
29
29
|
import { existsSync, readFileSync } from 'node:fs';
|
|
30
30
|
import { spawn } from 'node:child_process';
|
|
31
31
|
import { sanitizeCredentialEnv } from '../config/configService.js';
|
|
32
|
+
import { CONFIG_LIMITS, normalizeLimited } from '../config/numericLimits.js';
|
|
32
33
|
|
|
33
34
|
import { getWorkingDir } from '../bootstrap/workingDir.js';
|
|
34
35
|
|
|
36
|
+
/**
|
|
37
|
+
* `shell:` 检查的默认超时——**唯一真值**。
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ 这个数字此前在本文件里写了两份(`parseCheckString` 的构造处 + `runVerification`
|
|
40
|
+
* 的 `?? 30_000` 兜底),改一处漏一处就会出现「解析时 30s、执行时 60s」这种谁都看不出
|
|
41
|
+
* 的分裂。边界见 `config/numericLimits.ts::CONFIG_LIMITS.verifyShellTimeoutMs`。
|
|
42
|
+
*
|
|
43
|
+
* 为什么默认值不能太大:`verify()` 是工作流硬门,跑不完就阻塞整条流水线。
|
|
44
|
+
* 需要跑全量测试套件的场景应当缩到具体文件,或由调用方显式传 `defaultShellTimeoutMs`。
|
|
45
|
+
*/
|
|
46
|
+
export const DEFAULT_SHELL_CHECK_TIMEOUT_MS = 30_000;
|
|
47
|
+
|
|
35
48
|
export interface Check {
|
|
36
49
|
type: 'shell' | 'file_contains' | 'file_exists' | 'test_count';
|
|
37
50
|
command?: string;
|
|
@@ -63,7 +76,10 @@ function parseVerifyArg(arg: string): Check | null {
|
|
|
63
76
|
|
|
64
77
|
switch (type) {
|
|
65
78
|
case 'shell':
|
|
66
|
-
|
|
79
|
+
// ⚠️ 这里**不填** timeout:解析层只管语法,超时是策略、由 runVerification 决定。
|
|
80
|
+
// 曾经在这里写死 30_000,于是 `check.timeout ?? 默认值` 里的默认值永远走不到 ——
|
|
81
|
+
// 调用方传的超时被一个「解析时顺手填上的」值静默盖掉。
|
|
82
|
+
return { type: 'shell', command: value };
|
|
67
83
|
case 'file_exists':
|
|
68
84
|
return { type: 'file_exists', file: value };
|
|
69
85
|
case 'file_contains': {
|
|
@@ -142,8 +158,18 @@ function runShell(
|
|
|
142
158
|
return new Promise((resolve) => {
|
|
143
159
|
const isWin = process.platform === 'win32';
|
|
144
160
|
const opts = { timeout, cwd, env: sanitizeCredentialEnv() };
|
|
161
|
+
// ⚠️ windowsVerbatimArguments: true 是必须的,不是可选优化:Node 给 cmd.exe/
|
|
162
|
+
// .bat 目标默认加一层它自己的参数转义(CVE-2024-27980 之后的安全加固),
|
|
163
|
+
// 会把 command 字符串里模型自己写好的转义(比如内嵌 PowerShell 单行脚本里的
|
|
164
|
+
// `\"..."`)再转义一遍,导致 cmd.exe 转交给子进程的字符串被改写、PowerShell
|
|
165
|
+
// 解析报 UnexpectedToken——文件内容明明字节级正确,check 却稳定失败。
|
|
166
|
+
// 已实测:一段合法的 `powershell -Command "...; Write-Host \"x\"; ..."`
|
|
167
|
+
// 不加这个选项必挂,加了立刻 exitCode 0;简单命令(echo/dir/bun --version)
|
|
168
|
+
// 行为不受影响。verify() 的 shell: 检查类型专供工作流的 hard_checks 使用,
|
|
169
|
+
// 命令来自可信的 LLM 契约草稿或用户输入,不是不可信的外部数据,
|
|
170
|
+
// 关掉这层转义不引入新的注入面。
|
|
145
171
|
const child = isWin
|
|
146
|
-
? spawn('cmd', ['/c', command], opts)
|
|
172
|
+
? spawn('cmd', ['/c', command], { ...opts, windowsVerbatimArguments: true })
|
|
147
173
|
: spawn('bash', ['-c', command], opts);
|
|
148
174
|
|
|
149
175
|
let stdout = '';
|
|
@@ -260,7 +286,13 @@ async function verifyTestCount(
|
|
|
260
286
|
export async function runVerification(
|
|
261
287
|
checks: Check[],
|
|
262
288
|
cwd: string = getWorkingDir(),
|
|
289
|
+
options: { defaultShellTimeoutMs?: number } = {},
|
|
263
290
|
): Promise<VerifyResult> {
|
|
291
|
+
const shellTimeout = normalizeLimited(
|
|
292
|
+
options.defaultShellTimeoutMs,
|
|
293
|
+
CONFIG_LIMITS.verifyShellTimeoutMs,
|
|
294
|
+
DEFAULT_SHELL_CHECK_TIMEOUT_MS,
|
|
295
|
+
);
|
|
264
296
|
if (checks.length === 0) {
|
|
265
297
|
return { passed: true, details: [], summary: '无验证项' };
|
|
266
298
|
}
|
|
@@ -274,7 +306,7 @@ export async function runVerification(
|
|
|
274
306
|
case 'shell':
|
|
275
307
|
result = await verifyShell(
|
|
276
308
|
check.command ?? '',
|
|
277
|
-
check.timeout ??
|
|
309
|
+
check.timeout ?? shellTimeout,
|
|
278
310
|
cwd,
|
|
279
311
|
);
|
|
280
312
|
break;
|
|
@@ -295,7 +327,10 @@ export async function runVerification(
|
|
|
295
327
|
};
|
|
296
328
|
}
|
|
297
329
|
|
|
298
|
-
|
|
330
|
+
// 回填**原始** check:各 verifier 只收散参(command/file/pattern),返回时会
|
|
331
|
+
// 自己重建一个残缺的 check 对象,把 timeout 这类字段丢掉。details 是给人排障和
|
|
332
|
+
// 给工作流做卡死指纹用的,它必须如实反映「这一条到底是按什么跑的」。
|
|
333
|
+
results.push({ ...result, check });
|
|
299
334
|
}
|
|
300
335
|
|
|
301
336
|
const allPassed = results.every((r) => r.passed);
|