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
|
@@ -15,7 +15,7 @@ import { chmod, mkdir, readdir, realpath, rm, stat } from 'node:fs/promises';
|
|
|
15
15
|
import os from 'node:os';
|
|
16
16
|
import path from 'node:path';
|
|
17
17
|
import { sanitizeCredentialEnv } from '../../config/configService.js';
|
|
18
|
-
import {
|
|
18
|
+
import { pathContainedByAny, pathContains } from '../../utils/pathContains.js';
|
|
19
19
|
/**
|
|
20
20
|
* minimal-agent 进程级固定 session 名:隔离机器上其它 agent-browser 用法,
|
|
21
21
|
* 同时让本进程多次调用复用同一浏览器(与原 playwright「一 session 一 page」语义一致)。
|
|
@@ -38,7 +38,11 @@ export async function ensureBrowserProfile(profileId = 'default') {
|
|
|
38
38
|
return profile;
|
|
39
39
|
}
|
|
40
40
|
export async function browserGlobalArgs(sessionName = BROWSER_SESSION, profileId = 'default') {
|
|
41
|
-
return [
|
|
41
|
+
return [
|
|
42
|
+
...sessionArgs(sessionName),
|
|
43
|
+
'--profile',
|
|
44
|
+
await ensureBrowserProfile(profileId),
|
|
45
|
+
];
|
|
42
46
|
}
|
|
43
47
|
/**
|
|
44
48
|
* 默认 spawn 运行器:收集 stdout/stderr 后结算;spawn 抛错也 resolve 成失败。
|
|
@@ -83,13 +87,21 @@ export const spawnRunner = (bin, args, signal) => new Promise((resolveP) => {
|
|
|
83
87
|
});
|
|
84
88
|
}
|
|
85
89
|
catch (e) {
|
|
86
|
-
resolveP({
|
|
90
|
+
resolveP({
|
|
91
|
+
code: -1,
|
|
92
|
+
stdout: '',
|
|
93
|
+
stderr: e instanceof Error ? e.message : String(e),
|
|
94
|
+
});
|
|
87
95
|
return;
|
|
88
96
|
}
|
|
89
97
|
child.stdout?.setEncoding('utf8');
|
|
90
|
-
child.stdout?.on('data', (c) =>
|
|
98
|
+
child.stdout?.on('data', (c) => {
|
|
99
|
+
stdout += c;
|
|
100
|
+
});
|
|
91
101
|
child.stderr?.setEncoding('utf8');
|
|
92
|
-
child.stderr?.on('data', (c) =>
|
|
102
|
+
child.stderr?.on('data', (c) => {
|
|
103
|
+
stderr += c;
|
|
104
|
+
});
|
|
93
105
|
child.on('error', (e) => finish(-1, e instanceof Error ? e.message : String(e)));
|
|
94
106
|
// 正常路径:'close' 紧随 'exit'(pipe 关闭)→ 以完整输出立即结算,并清掉宽限计时器。
|
|
95
107
|
child.on('close', (code) => finish(code));
|
|
@@ -169,10 +181,14 @@ async function pruneStaleArtifacts(dir) {
|
|
|
169
181
|
if (info.mtimeMs < cutoff)
|
|
170
182
|
await rm(target, { force: true, recursive: true });
|
|
171
183
|
}
|
|
172
|
-
catch {
|
|
184
|
+
catch {
|
|
185
|
+
/* 单个文件清不掉不影响其它 */
|
|
186
|
+
}
|
|
173
187
|
}));
|
|
174
188
|
}
|
|
175
|
-
catch {
|
|
189
|
+
catch {
|
|
190
|
+
/* 目录不存在或不可读:没什么可清的 */
|
|
191
|
+
}
|
|
176
192
|
}
|
|
177
193
|
/**
|
|
178
194
|
* 生成一个托管的产物输出路径,并确保目录存在、旧产物已清理。
|
|
@@ -216,7 +232,9 @@ const ALLOWED_URL_SCHEMES = new Set(['http', 'https', 'about', 'file']);
|
|
|
216
232
|
* 而不是替调用方创建——凭一个路径参数就造目录树同样是越权。
|
|
217
233
|
*/
|
|
218
234
|
export async function validateArtifactOutputPath(candidate, allowedRoots) {
|
|
219
|
-
const absolute = path.isAbsolute(candidate)
|
|
235
|
+
const absolute = path.isAbsolute(candidate)
|
|
236
|
+
? candidate
|
|
237
|
+
: path.resolve(candidate);
|
|
220
238
|
const parent = path.dirname(absolute);
|
|
221
239
|
let realParent;
|
|
222
240
|
try {
|
|
@@ -236,9 +254,7 @@ export async function validateArtifactOutputPath(candidate, allowedRoots) {
|
|
|
236
254
|
if (!pathContainedByAny(roots, realParent)) {
|
|
237
255
|
return {
|
|
238
256
|
ok: false,
|
|
239
|
-
error: `输出路径超出允许范围:${absolute}\n
|
|
240
|
-
+ `允许写入:${roots.join('、')}\n`
|
|
241
|
-
+ '省略 path 参数即可写到托管的临时目录。',
|
|
257
|
+
error: `输出路径超出允许范围:${absolute}\n允许写入:${roots.join('、')}\n省略 path 参数即可写到托管的临时目录。`,
|
|
242
258
|
};
|
|
243
259
|
}
|
|
244
260
|
const managed = await realpath(browserArtifactDir()).catch(() => browserArtifactDir());
|
|
@@ -17,7 +17,7 @@ import os from 'node:os';
|
|
|
17
17
|
import path from 'node:path';
|
|
18
18
|
|
|
19
19
|
import { sanitizeCredentialEnv } from '../../config/configService.js';
|
|
20
|
-
import {
|
|
20
|
+
import { pathContainedByAny, pathContains } from '../../utils/pathContains.js';
|
|
21
21
|
|
|
22
22
|
export interface AgentBrowserRun {
|
|
23
23
|
code: number;
|
|
@@ -46,18 +46,34 @@ export function sessionArgs(sessionName = BROWSER_SESSION): string[] {
|
|
|
46
46
|
/** KakadeAI-owned persistent browser profile. It is never the user's system Chrome profile. */
|
|
47
47
|
export function browserProfilePath(profileId = 'default'): string {
|
|
48
48
|
const safe = /^[a-zA-Z0-9._-]+$/.test(profileId) ? profileId : 'default';
|
|
49
|
-
return path.join(
|
|
49
|
+
return path.join(
|
|
50
|
+
process.env.MINIMAL_AGENT_HOME ?? os.homedir(),
|
|
51
|
+
'.minimal-agent',
|
|
52
|
+
'browser',
|
|
53
|
+
'profiles',
|
|
54
|
+
safe,
|
|
55
|
+
);
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
export async function ensureBrowserProfile(
|
|
58
|
+
export async function ensureBrowserProfile(
|
|
59
|
+
profileId = 'default',
|
|
60
|
+
): Promise<string> {
|
|
53
61
|
const profile = browserProfilePath(profileId);
|
|
54
62
|
await mkdir(profile, { recursive: true, mode: 0o700 });
|
|
55
|
-
if (process.platform !== 'win32')
|
|
63
|
+
if (process.platform !== 'win32')
|
|
64
|
+
await chmod(profile, 0o700).catch(() => undefined);
|
|
56
65
|
return profile;
|
|
57
66
|
}
|
|
58
67
|
|
|
59
|
-
export async function browserGlobalArgs(
|
|
60
|
-
|
|
68
|
+
export async function browserGlobalArgs(
|
|
69
|
+
sessionName = BROWSER_SESSION,
|
|
70
|
+
profileId = 'default',
|
|
71
|
+
): Promise<string[]> {
|
|
72
|
+
return [
|
|
73
|
+
...sessionArgs(sessionName),
|
|
74
|
+
'--profile',
|
|
75
|
+
await ensureBrowserProfile(profileId),
|
|
76
|
+
];
|
|
61
77
|
}
|
|
62
78
|
|
|
63
79
|
/**
|
|
@@ -83,8 +99,12 @@ export const spawnRunner: AgentBrowserRunner = (bin, args, signal) =>
|
|
|
83
99
|
settled = true;
|
|
84
100
|
if (graceTimer) clearTimeout(graceTimer);
|
|
85
101
|
// 主动释放父侧 pipe 读端(daemon 攥着写端、我们已拿到结果,不再依赖它关闭)。
|
|
86
|
-
try {
|
|
87
|
-
|
|
102
|
+
try {
|
|
103
|
+
child?.stdout?.destroy();
|
|
104
|
+
} catch {}
|
|
105
|
+
try {
|
|
106
|
+
child?.stderr?.destroy();
|
|
107
|
+
} catch {}
|
|
88
108
|
resolveP({ code: code ?? -1, stdout, stderr: stderr || errMsg || '' });
|
|
89
109
|
};
|
|
90
110
|
try {
|
|
@@ -95,14 +115,24 @@ export const spawnRunner: AgentBrowserRunner = (bin, args, signal) =>
|
|
|
95
115
|
signal,
|
|
96
116
|
});
|
|
97
117
|
} catch (e) {
|
|
98
|
-
resolveP({
|
|
118
|
+
resolveP({
|
|
119
|
+
code: -1,
|
|
120
|
+
stdout: '',
|
|
121
|
+
stderr: e instanceof Error ? e.message : String(e),
|
|
122
|
+
});
|
|
99
123
|
return;
|
|
100
124
|
}
|
|
101
125
|
child.stdout?.setEncoding('utf8');
|
|
102
|
-
child.stdout?.on('data', (c) =>
|
|
126
|
+
child.stdout?.on('data', (c) => {
|
|
127
|
+
stdout += c;
|
|
128
|
+
});
|
|
103
129
|
child.stderr?.setEncoding('utf8');
|
|
104
|
-
child.stderr?.on('data', (c) =>
|
|
105
|
-
|
|
130
|
+
child.stderr?.on('data', (c) => {
|
|
131
|
+
stderr += c;
|
|
132
|
+
});
|
|
133
|
+
child.on('error', (e) =>
|
|
134
|
+
finish(-1, e instanceof Error ? e.message : String(e)),
|
|
135
|
+
);
|
|
106
136
|
// 正常路径:'close' 紧随 'exit'(pipe 关闭)→ 以完整输出立即结算,并清掉宽限计时器。
|
|
107
137
|
child.on('close', (code) => finish(code));
|
|
108
138
|
// 拉 daemon 的命令:daemon 继承 pipe → 'close' 永不来。以 'exit' 为准,给一小段宽限等 'close'
|
|
@@ -184,11 +214,16 @@ async function pruneStaleArtifacts(dir: string): Promise<void> {
|
|
|
184
214
|
const target = path.join(dir, name);
|
|
185
215
|
try {
|
|
186
216
|
const info = await stat(target);
|
|
187
|
-
if (info.mtimeMs < cutoff)
|
|
188
|
-
|
|
217
|
+
if (info.mtimeMs < cutoff)
|
|
218
|
+
await rm(target, { force: true, recursive: true });
|
|
219
|
+
} catch {
|
|
220
|
+
/* 单个文件清不掉不影响其它 */
|
|
221
|
+
}
|
|
189
222
|
}),
|
|
190
223
|
);
|
|
191
|
-
} catch {
|
|
224
|
+
} catch {
|
|
225
|
+
/* 目录不存在或不可读:没什么可清的 */
|
|
226
|
+
}
|
|
192
227
|
}
|
|
193
228
|
|
|
194
229
|
/**
|
|
@@ -197,10 +232,14 @@ async function pruneStaleArtifacts(dir: string): Promise<void> {
|
|
|
197
232
|
* 文件名带 pid + 随机数:`Date.now()` 单独用会在同一毫秒内两次调用时撞名
|
|
198
233
|
* (两个并发截图互相覆盖,症状是「截出来的图不是那一刻的画面」)。
|
|
199
234
|
*/
|
|
200
|
-
export async function createArtifactPath(
|
|
235
|
+
export async function createArtifactPath(
|
|
236
|
+
prefix = 'browser',
|
|
237
|
+
ext = 'png',
|
|
238
|
+
): Promise<string> {
|
|
201
239
|
const dir = browserArtifactDir();
|
|
202
240
|
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
203
|
-
if (process.platform !== 'win32')
|
|
241
|
+
if (process.platform !== 'win32')
|
|
242
|
+
await chmod(dir, 0o700).catch(() => undefined);
|
|
204
243
|
await pruneStaleArtifacts(dir);
|
|
205
244
|
const unique = `${Date.now()}-${process.pid}-${randomUUID().slice(0, 8)}`;
|
|
206
245
|
return path.join(dir, `${prefix}-${unique}.${ext}`);
|
|
@@ -237,7 +276,9 @@ export async function validateArtifactOutputPath(
|
|
|
237
276
|
candidate: string,
|
|
238
277
|
allowedRoots: string[],
|
|
239
278
|
): Promise<{ ok: true; path: string } | { ok: false; error: string }> {
|
|
240
|
-
const absolute = path.isAbsolute(candidate)
|
|
279
|
+
const absolute = path.isAbsolute(candidate)
|
|
280
|
+
? candidate
|
|
281
|
+
: path.resolve(candidate);
|
|
241
282
|
const parent = path.dirname(absolute);
|
|
242
283
|
let realParent: string;
|
|
243
284
|
try {
|
|
@@ -247,19 +288,22 @@ export async function validateArtifactOutputPath(
|
|
|
247
288
|
}
|
|
248
289
|
const roots = await Promise.all(
|
|
249
290
|
allowedRoots.map(async (root) => {
|
|
250
|
-
try {
|
|
291
|
+
try {
|
|
292
|
+
return await realpath(root);
|
|
293
|
+
} catch {
|
|
294
|
+
return path.resolve(root);
|
|
295
|
+
}
|
|
251
296
|
}),
|
|
252
297
|
);
|
|
253
298
|
if (!pathContainedByAny(roots, realParent)) {
|
|
254
299
|
return {
|
|
255
300
|
ok: false,
|
|
256
|
-
error:
|
|
257
|
-
`输出路径超出允许范围:${absolute}\n`
|
|
258
|
-
+ `允许写入:${roots.join('、')}\n`
|
|
259
|
-
+ '省略 path 参数即可写到托管的临时目录。',
|
|
301
|
+
error: `输出路径超出允许范围:${absolute}\n允许写入:${roots.join('、')}\n省略 path 参数即可写到托管的临时目录。`,
|
|
260
302
|
};
|
|
261
303
|
}
|
|
262
|
-
const managed = await realpath(browserArtifactDir()).catch(() =>
|
|
304
|
+
const managed = await realpath(browserArtifactDir()).catch(() =>
|
|
305
|
+
browserArtifactDir(),
|
|
306
|
+
);
|
|
263
307
|
if (!pathContains(managed, realParent)) {
|
|
264
308
|
const exists = await stat(path.join(realParent, path.basename(absolute)))
|
|
265
309
|
.then(() => true)
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent CDP transport used by BrowserBatch and BrowserCDP.
|
|
3
|
+
*
|
|
4
|
+
* The browser daemon is deliberately still owned by agent-browser; this module
|
|
5
|
+
* only replaces the short lived command clients used for each automation step.
|
|
6
|
+
*/
|
|
7
|
+
import { mkdir, stat } from 'node:fs/promises';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { sessionArgs } from './browser.js';
|
|
11
|
+
import { ensureDaemon } from './browserDaemon.js';
|
|
12
|
+
export const BROWSER_TAB_MARKER_KEY = '__minimalAgentTabMarker';
|
|
13
|
+
class CdpConnection {
|
|
14
|
+
url;
|
|
15
|
+
socket = null;
|
|
16
|
+
opening = null;
|
|
17
|
+
pageTargetId;
|
|
18
|
+
pageSessionId;
|
|
19
|
+
id = 0;
|
|
20
|
+
pending = new Map();
|
|
21
|
+
events = [];
|
|
22
|
+
sequence = 0;
|
|
23
|
+
listeners = new Set();
|
|
24
|
+
constructor(url) {
|
|
25
|
+
this.url = url;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A connection is page-scoped. It must not be put back in a process-wide
|
|
29
|
+
* pool and rebound by another conversation while this caller still holds it.
|
|
30
|
+
*/
|
|
31
|
+
close() {
|
|
32
|
+
const socket = this.socket;
|
|
33
|
+
this.socket = null;
|
|
34
|
+
this.pageTargetId = undefined;
|
|
35
|
+
this.pageSessionId = undefined;
|
|
36
|
+
socket?.close();
|
|
37
|
+
}
|
|
38
|
+
get attachedPageSessionId() {
|
|
39
|
+
return this.pageSessionId;
|
|
40
|
+
}
|
|
41
|
+
get attachedPageTargetId() {
|
|
42
|
+
return this.pageTargetId;
|
|
43
|
+
}
|
|
44
|
+
async evaluateInFrame(frameId, expression, signal) {
|
|
45
|
+
const isolated = (await this.send('Page.createIsolatedWorld', {
|
|
46
|
+
frameId,
|
|
47
|
+
worldName: 'minimal-agent-browser',
|
|
48
|
+
grantUniveralAccess: false,
|
|
49
|
+
}, undefined, 30_000, signal));
|
|
50
|
+
if (typeof isolated.executionContextId !== 'number')
|
|
51
|
+
throw new Error(`无法创建 frame execution context:${frameId}`);
|
|
52
|
+
return this.send('Runtime.evaluate', {
|
|
53
|
+
expression,
|
|
54
|
+
contextId: isolated.executionContextId,
|
|
55
|
+
returnByValue: true,
|
|
56
|
+
awaitPromise: true,
|
|
57
|
+
}, undefined, 30_000, signal);
|
|
58
|
+
}
|
|
59
|
+
async frameOwnerOffset(frameId, signal) {
|
|
60
|
+
// CDP returns the owner quad in top-level viewport coordinates, including
|
|
61
|
+
// nested cross-origin frames. Do not add ancestor offsets a second time.
|
|
62
|
+
const owner = (await this.send('DOM.getFrameOwner', { frameId }, undefined, 30_000, signal));
|
|
63
|
+
if (typeof owner.backendNodeId !== 'number')
|
|
64
|
+
throw new Error(`无法取得 frame owner:${frameId}`);
|
|
65
|
+
const box = (await this.send('DOM.getBoxModel', { backendNodeId: owner.backendNodeId }, undefined, 30_000, signal));
|
|
66
|
+
const border = box.model?.border;
|
|
67
|
+
if (!Array.isArray(border) || border.length < 2)
|
|
68
|
+
throw new Error(`无法取得 frame owner 坐标:${frameId}`);
|
|
69
|
+
const numbers = border.filter((value) => typeof value === 'number');
|
|
70
|
+
if (numbers.length < 2)
|
|
71
|
+
throw new Error(`frame owner 坐标无效:${frameId}`);
|
|
72
|
+
return { x: numbers[0], y: numbers[1] };
|
|
73
|
+
}
|
|
74
|
+
async open() {
|
|
75
|
+
if (this.socket?.readyState === WebSocket.OPEN)
|
|
76
|
+
return;
|
|
77
|
+
if (this.opening)
|
|
78
|
+
return this.opening;
|
|
79
|
+
this.opening = new Promise((resolve, reject) => {
|
|
80
|
+
const socket = new WebSocket(this.url);
|
|
81
|
+
const timer = setTimeout(() => {
|
|
82
|
+
socket.close();
|
|
83
|
+
reject(new Error('CDP connection timed out'));
|
|
84
|
+
}, 10_000);
|
|
85
|
+
socket.onopen = () => {
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
this.socket = socket;
|
|
88
|
+
resolve();
|
|
89
|
+
};
|
|
90
|
+
socket.onerror = () => {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
reject(new Error('Unable to connect to browser CDP'));
|
|
93
|
+
};
|
|
94
|
+
socket.onclose = () => {
|
|
95
|
+
if (this.socket === socket)
|
|
96
|
+
this.socket = null;
|
|
97
|
+
this.pageTargetId = undefined;
|
|
98
|
+
this.pageSessionId = undefined;
|
|
99
|
+
for (const [id, request] of this.pending) {
|
|
100
|
+
clearTimeout(request.timer);
|
|
101
|
+
if (request.signal && request.onAbort)
|
|
102
|
+
request.signal.removeEventListener('abort', request.onAbort);
|
|
103
|
+
request.reject(new Error('CDP connection closed'));
|
|
104
|
+
this.pending.delete(id);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
socket.onmessage = (message) => this.onMessage(String(message.data));
|
|
108
|
+
}).finally(() => {
|
|
109
|
+
this.opening = null;
|
|
110
|
+
});
|
|
111
|
+
return this.opening;
|
|
112
|
+
}
|
|
113
|
+
onMessage(raw) {
|
|
114
|
+
let data;
|
|
115
|
+
try {
|
|
116
|
+
data = JSON.parse(raw);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (typeof data.id === 'number') {
|
|
122
|
+
const request = this.pending.get(data.id);
|
|
123
|
+
if (!request)
|
|
124
|
+
return;
|
|
125
|
+
this.pending.delete(data.id);
|
|
126
|
+
clearTimeout(request.timer);
|
|
127
|
+
if (request.signal && request.onAbort)
|
|
128
|
+
request.signal.removeEventListener('abort', request.onAbort);
|
|
129
|
+
if (data.error)
|
|
130
|
+
request.reject(new Error(JSON.stringify(data.error)));
|
|
131
|
+
else
|
|
132
|
+
request.resolve(data.result ?? {});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (typeof data.method !== 'string')
|
|
136
|
+
return;
|
|
137
|
+
const event = {
|
|
138
|
+
sequence: ++this.sequence,
|
|
139
|
+
method: data.method,
|
|
140
|
+
params: (data.params ?? {}),
|
|
141
|
+
sessionId: typeof data.sessionId === 'string' ? data.sessionId : undefined,
|
|
142
|
+
at: Date.now(),
|
|
143
|
+
};
|
|
144
|
+
if (event.method === 'Target.detachedFromTarget' &&
|
|
145
|
+
String(event.params.sessionId ?? '') === this.pageSessionId) {
|
|
146
|
+
this.pageTargetId = undefined;
|
|
147
|
+
this.pageSessionId = undefined;
|
|
148
|
+
}
|
|
149
|
+
if ((event.method === 'Target.targetDestroyed' ||
|
|
150
|
+
event.method === 'Target.targetCrashed') &&
|
|
151
|
+
String(event.params.targetId ?? '') === this.pageTargetId) {
|
|
152
|
+
this.pageTargetId = undefined;
|
|
153
|
+
this.pageSessionId = undefined;
|
|
154
|
+
}
|
|
155
|
+
this.events.push(event);
|
|
156
|
+
if (this.events.length > 500)
|
|
157
|
+
this.events.splice(0, this.events.length - 500);
|
|
158
|
+
for (const listener of this.listeners) {
|
|
159
|
+
try {
|
|
160
|
+
listener(event);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// One observer must not break delivery to the remaining CDP consumers.
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async send(method, params = {}, target, timeoutMs = 30_000, signal) {
|
|
168
|
+
await this.open();
|
|
169
|
+
const socket = this.socket;
|
|
170
|
+
if (!socket || socket.readyState !== WebSocket.OPEN)
|
|
171
|
+
throw new Error('CDP connection is unavailable');
|
|
172
|
+
const id = ++this.id;
|
|
173
|
+
const pageDomain = /^(Accessibility|DOM|Input|Log|Network|Page|Performance|Runtime|Security)\./.test(method);
|
|
174
|
+
const effectiveTarget = target ?? (pageDomain ? this.pageSessionId : undefined);
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
const abortError = () => signal?.reason instanceof Error
|
|
177
|
+
? signal.reason
|
|
178
|
+
: new Error(`CDP ${method} aborted`);
|
|
179
|
+
const cleanup = () => {
|
|
180
|
+
const request = this.pending.get(id);
|
|
181
|
+
if (!request)
|
|
182
|
+
return false;
|
|
183
|
+
this.pending.delete(id);
|
|
184
|
+
clearTimeout(request.timer);
|
|
185
|
+
if (request.signal && request.onAbort)
|
|
186
|
+
request.signal.removeEventListener('abort', request.onAbort);
|
|
187
|
+
return true;
|
|
188
|
+
};
|
|
189
|
+
const onAbort = () => {
|
|
190
|
+
if (cleanup())
|
|
191
|
+
reject(abortError());
|
|
192
|
+
};
|
|
193
|
+
const timer = setTimeout(() => {
|
|
194
|
+
if (cleanup())
|
|
195
|
+
reject(new Error(`CDP ${method} timed out`));
|
|
196
|
+
}, timeoutMs);
|
|
197
|
+
this.pending.set(id, { resolve, reject, timer, onAbort, signal });
|
|
198
|
+
if (signal?.aborted) {
|
|
199
|
+
onAbort();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
203
|
+
try {
|
|
204
|
+
socket.send(JSON.stringify({
|
|
205
|
+
id,
|
|
206
|
+
method,
|
|
207
|
+
params,
|
|
208
|
+
...(effectiveTarget ? { sessionId: effectiveTarget } : {}),
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
if (cleanup())
|
|
213
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
async attachToPage(preferredUrl, force = false, preferredMarker) {
|
|
218
|
+
if (this.pageSessionId && !force)
|
|
219
|
+
return;
|
|
220
|
+
if (force && this.pageSessionId) {
|
|
221
|
+
const previousSession = this.pageSessionId;
|
|
222
|
+
this.pageSessionId = undefined;
|
|
223
|
+
this.pageTargetId = undefined;
|
|
224
|
+
await this.send('Target.detachFromTarget', {
|
|
225
|
+
sessionId: previousSession,
|
|
226
|
+
}).catch(() => undefined);
|
|
227
|
+
}
|
|
228
|
+
const targets = (await this.send('Target.getTargets'));
|
|
229
|
+
const pages = (targets.targetInfos ?? []).filter((target) => target.type === 'page' && typeof target.targetId === 'string');
|
|
230
|
+
if (!pages.length)
|
|
231
|
+
throw new Error('浏览器没有可绑定的页面标签');
|
|
232
|
+
let selected = preferredMarker
|
|
233
|
+
? undefined
|
|
234
|
+
: preferredUrl
|
|
235
|
+
? pages.find((target) => target.url === preferredUrl)
|
|
236
|
+
: undefined;
|
|
237
|
+
if (preferredMarker) {
|
|
238
|
+
const marker = JSON.stringify(preferredMarker);
|
|
239
|
+
for (const candidate of pages) {
|
|
240
|
+
const attached = (await this.send('Target.attachToTarget', {
|
|
241
|
+
targetId: candidate.targetId,
|
|
242
|
+
flatten: true,
|
|
243
|
+
}));
|
|
244
|
+
const sessionId = attached.sessionId;
|
|
245
|
+
if (typeof sessionId !== 'string')
|
|
246
|
+
continue;
|
|
247
|
+
const probe = await this.send('Runtime.evaluate', {
|
|
248
|
+
expression: `globalThis[${JSON.stringify(BROWSER_TAB_MARKER_KEY)}] === ${marker}`,
|
|
249
|
+
returnByValue: true,
|
|
250
|
+
}, sessionId).catch(() => undefined);
|
|
251
|
+
if (probe?.result
|
|
252
|
+
?.value === true) {
|
|
253
|
+
selected = candidate;
|
|
254
|
+
this.pageTargetId = String(candidate.targetId);
|
|
255
|
+
this.pageSessionId = sessionId;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
await this.send('Target.detachFromTarget', { sessionId }).catch(() => undefined);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (preferredMarker) {
|
|
262
|
+
if (!selected)
|
|
263
|
+
throw new Error('无法通过会话标记绑定到当前浏览器标签');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
selected ??= pages[pages.length - 1];
|
|
267
|
+
const attached = (await this.send('Target.attachToTarget', {
|
|
268
|
+
targetId: selected.targetId,
|
|
269
|
+
flatten: true,
|
|
270
|
+
}));
|
|
271
|
+
if (typeof attached.sessionId !== 'string')
|
|
272
|
+
throw new Error('浏览器未返回页面 target session');
|
|
273
|
+
this.pageTargetId = selected.targetId;
|
|
274
|
+
this.pageSessionId = attached.sessionId;
|
|
275
|
+
}
|
|
276
|
+
read(after = 0, methods, limit = 100) {
|
|
277
|
+
return this.events
|
|
278
|
+
.filter((event) => event.sequence > after &&
|
|
279
|
+
(!methods?.length || methods.includes(event.method)))
|
|
280
|
+
.slice(-Math.max(1, Math.min(limit, 500)));
|
|
281
|
+
}
|
|
282
|
+
subscribe(listener) {
|
|
283
|
+
this.listeners.add(listener);
|
|
284
|
+
return () => this.listeners.delete(listener);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async function cdpUrl(sessionName, bin, run, signal) {
|
|
288
|
+
await ensureDaemon(sessionName, { signal });
|
|
289
|
+
const output = await run(bin, [...sessionArgs(sessionName), 'get', 'cdp-url'], signal);
|
|
290
|
+
const url = `${output.stdout}\n${output.stderr}`.match(/ws:\/\/[^\s"']+/)?.[0];
|
|
291
|
+
if (output.code !== 0 || !url)
|
|
292
|
+
throw new Error('无法取得浏览器 CDP 地址');
|
|
293
|
+
return url;
|
|
294
|
+
}
|
|
295
|
+
export async function getBrowserAutomation(sessionName, bin, run, signal, preferredUrl, preferredMarker) {
|
|
296
|
+
const url = await cdpUrl(sessionName, bin, run, signal);
|
|
297
|
+
// The browser endpoint is shared, but the attached page is not. Returning
|
|
298
|
+
// a fresh connection prevents a later conversation from changing the
|
|
299
|
+
// mutable page session underneath an in-flight batch.
|
|
300
|
+
const conn = new CdpConnection(url);
|
|
301
|
+
await conn.open();
|
|
302
|
+
await conn.attachToPage(preferredUrl, true, preferredMarker);
|
|
303
|
+
return conn;
|
|
304
|
+
}
|
|
305
|
+
export function browserDownloadDir(conversationKey = 'local', runId = 'default') {
|
|
306
|
+
const safe = (value) => value.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 100) || 'default';
|
|
307
|
+
const root = process.env.MINIMAL_AGENT_HOME ?? path.join(os.homedir(), '.minimal-agent');
|
|
308
|
+
return path.join(root, 'browser', 'downloads', safe(conversationKey), safe(runId));
|
|
309
|
+
}
|
|
310
|
+
export async function configureDownloads(cdp, conversationKey, runId) {
|
|
311
|
+
const dir = browserDownloadDir(conversationKey, runId);
|
|
312
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
313
|
+
// Browser domain is current Chrome; Page fallback keeps older Chromium usable.
|
|
314
|
+
await cdp
|
|
315
|
+
.send('Browser.setDownloadBehavior', {
|
|
316
|
+
behavior: 'allow',
|
|
317
|
+
downloadPath: dir,
|
|
318
|
+
eventsEnabled: true,
|
|
319
|
+
})
|
|
320
|
+
.catch(() => cdp.send('Page.setDownloadBehavior', {
|
|
321
|
+
behavior: 'allow',
|
|
322
|
+
downloadPath: dir,
|
|
323
|
+
}));
|
|
324
|
+
return dir;
|
|
325
|
+
}
|
|
326
|
+
export async function finalDownload(pathname) {
|
|
327
|
+
const info = await stat(pathname);
|
|
328
|
+
return { path: pathname, receivedBytes: info.size };
|
|
329
|
+
}
|