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.
Files changed (470) hide show
  1. package/README.md +6 -5
  2. package/package.json +60 -4
  3. package/plugins/computer_use/.claude-plugin/plugin.json +1 -1
  4. package/plugins/cron-runner/src/commands.js +4 -4
  5. package/plugins/cron-runner/src/commands.ts +4 -4
  6. package/plugins/workflow-runner/.claude-plugin/plugin.json +2 -2
  7. package/plugins/workflow-runner/commands/workflow.md +57 -90
  8. package/plugins/workflow-runner/commands/workflows.md +4 -4
  9. package/plugins/workflow-runner/plugin.js +24 -13
  10. package/plugins/workflow-runner/plugin.ts +28 -13
  11. package/plugins/workflow-runner/src/discovery.js +160 -0
  12. package/plugins/workflow-runner/src/discovery.ts +194 -0
  13. package/plugins/workflow-runner/src/goalAnchor.js +53 -0
  14. package/plugins/workflow-runner/src/goalAnchor.ts +59 -0
  15. package/plugins/workflow-runner/src/index.js +197 -206
  16. package/plugins/workflow-runner/src/index.ts +248 -249
  17. package/plugins/workflow-runner/src/inputCoercion.js +40 -0
  18. package/plugins/workflow-runner/src/inputCoercion.ts +43 -0
  19. package/plugins/workflow-runner/src/journal.js +224 -0
  20. package/plugins/workflow-runner/src/journal.ts +274 -0
  21. package/plugins/workflow-runner/src/jsonSchema.js +132 -0
  22. package/plugins/workflow-runner/src/jsonSchema.ts +162 -0
  23. package/plugins/workflow-runner/src/moduleResolution.js +64 -0
  24. package/plugins/workflow-runner/src/moduleResolution.ts +71 -0
  25. package/plugins/workflow-runner/src/resourceInputs.js +220 -0
  26. package/plugins/workflow-runner/src/resourceInputs.ts +300 -0
  27. package/plugins/workflow-runner/src/runner.js +400 -489
  28. package/plugins/workflow-runner/src/runner.ts +488 -560
  29. package/plugins/workflow-runner/src/runtime/config.js +46 -0
  30. package/plugins/workflow-runner/src/runtime/config.ts +69 -0
  31. package/plugins/workflow-runner/src/runtime/context.js +1304 -0
  32. package/plugins/workflow-runner/src/runtime/context.ts +1786 -0
  33. package/plugins/workflow-runner/src/runtime/exec.js +34 -0
  34. package/plugins/workflow-runner/src/runtime/exec.ts +53 -0
  35. package/plugins/workflow-runner/src/runtime/flow.js +8 -0
  36. package/plugins/workflow-runner/src/runtime/flow.ts +12 -0
  37. package/plugins/workflow-runner/src/runtime/fs.js +27 -0
  38. package/plugins/workflow-runner/src/runtime/fs.ts +41 -0
  39. package/plugins/workflow-runner/src/runtime/model.js +6 -0
  40. package/plugins/workflow-runner/src/runtime/model.ts +8 -0
  41. package/plugins/workflow-runner/src/runtime/output.js +7 -0
  42. package/plugins/workflow-runner/src/runtime/output.ts +21 -0
  43. package/plugins/workflow-runner/src/runtime/scheduler.js +141 -0
  44. package/plugins/workflow-runner/src/runtime/scheduler.ts +166 -0
  45. package/plugins/workflow-runner/src/runtime/state.js +16 -0
  46. package/plugins/workflow-runner/src/runtime/state.ts +19 -0
  47. package/plugins/workflow-runner/src/runtime/tools.js +3 -0
  48. package/plugins/workflow-runner/src/runtime/tools.ts +6 -0
  49. package/plugins/workflow-runner/src/sandbox.js +141 -0
  50. package/plugins/workflow-runner/src/sandbox.ts +182 -0
  51. package/plugins/workflow-runner/src/scriptGuard.d.mts +38 -0
  52. package/plugins/workflow-runner/src/scriptGuard.mjs +449 -0
  53. package/plugins/workflow-runner/src/taskIdentity.js +151 -307
  54. package/plugins/workflow-runner/src/taskIdentity.ts +207 -355
  55. package/plugins/workflow-runner/src/toolRuntime.js +29 -8
  56. package/plugins/workflow-runner/src/toolRuntime.ts +36 -11
  57. package/plugins/workflow-runner/src/types.js +7 -211
  58. package/plugins/workflow-runner/src/types.ts +154 -623
  59. package/plugins/workflow-runner/src/util.js +47 -0
  60. package/plugins/workflow-runner/src/util.ts +51 -0
  61. package/plugins/workflow-runner/src/workflowContext.js +20 -0
  62. package/plugins/workflow-runner/src/workflowContext.ts +58 -0
  63. package/plugins/workflow-runner/src/workflowDependencies.js +36 -0
  64. package/plugins/workflow-runner/src/workflowDependencies.ts +59 -0
  65. package/plugins/workflow-runner/src/workflowFingerprint.js +118 -0
  66. package/plugins/workflow-runner/src/workflowFingerprint.ts +122 -0
  67. package/plugins/workflow-runner/src/workflowState.js +474 -802
  68. package/plugins/workflow-runner/src/workflowState.ts +575 -970
  69. package/plugins/workflow-runner/test/api.test.ts +223 -0
  70. package/plugins/workflow-runner/test/apiCapabilities.test.ts +614 -0
  71. package/plugins/workflow-runner/test/apiConcurrencyGate.test.ts +104 -0
  72. package/plugins/workflow-runner/test/apiEcho.test.ts +210 -0
  73. package/plugins/workflow-runner/test/apiReadText.test.ts +178 -0
  74. package/plugins/workflow-runner/test/apiResourceLedger.test.ts +184 -0
  75. package/plugins/workflow-runner/test/childWorkflow.test.ts +304 -0
  76. package/plugins/workflow-runner/test/discovery.test.ts +234 -0
  77. package/plugins/workflow-runner/test/goalAnchor.test.ts +106 -0
  78. package/plugins/workflow-runner/test/goalController.test.ts +317 -0
  79. package/plugins/workflow-runner/test/journalLanes.test.ts +198 -0
  80. package/plugins/workflow-runner/test/listResources.test.ts +106 -0
  81. package/plugins/workflow-runner/test/resourceInputs.test.ts +294 -0
  82. package/plugins/workflow-runner/test/sandbox.test.ts +126 -0
  83. package/plugins/workflow-runner/test/scheduler.test.ts +70 -0
  84. package/plugins/workflow-runner/test/scriptGuard.test.ts +276 -0
  85. package/plugins/workflow-runner/test/skillDocSync.test.ts +78 -0
  86. package/plugins/workflow-runner/test/taskIdentity.test.ts +288 -179
  87. package/plugins/workflow-runner/test/workflowFingerprint.test.ts +106 -0
  88. package/plugins/workflow-runner/test/workflowState.test.ts +429 -0
  89. package/skills/pptx/examples/assets/cover-bg.svg +23 -0
  90. package/skills/pptx/examples/assets/icon-spark.svg +5 -0
  91. package/skills/ui-design/SKILL.md +5 -2
  92. package/skills/ui-design/references/layout-composition.md +1 -1
  93. package/skills/workflow-creator/SKILL.md +344 -0
  94. package/skills/workflow-creator/assets/template/workflow.mjs +48 -0
  95. package/skills/workflow-creator/references/api.md +216 -0
  96. package/skills/workflow-creator/references/migration.md +103 -0
  97. package/skills/workflow-creator/references/patterns.md +779 -0
  98. package/skills/workflow-creator/scripts/list-resources.mjs +205 -0
  99. package/skills/workflow-creator/scripts/scaffold.mjs +148 -0
  100. package/skills/workflow-creator/scripts/validate.mjs +275 -0
  101. package/skills/workflow-creator/scripts/workflow-creator.test.mjs +82 -0
  102. package/src/advisor/advisorCoordinator.js +503 -0
  103. package/src/advisor/advisorCoordinator.ts +657 -0
  104. package/src/cli/configMenu.tsx +0 -1
  105. package/src/cli/print.js +27 -6
  106. package/src/cli/print.ts +48 -4
  107. package/src/cli/streamJson.js +5 -6
  108. package/src/cli/streamJson.ts +8 -4
  109. package/src/cli/wizards/advisorWizard.js +4 -2
  110. package/src/cli/wizards/advisorWizard.tsx +8 -2
  111. package/src/cli/wizards/contextAdvisorWizard.js +5 -4
  112. package/src/cli/wizards/contextAdvisorWizard.tsx +7 -6
  113. package/src/collaboration/taskFlow.js +115 -0
  114. package/src/collaboration/taskFlow.ts +169 -0
  115. package/src/config/configDefaults.js +41 -1
  116. package/src/config/configDefaults.ts +43 -1
  117. package/src/config/configDiagnostics.ts +2 -2
  118. package/src/config/configFile.js +40 -9
  119. package/src/config/configFile.ts +60 -11
  120. package/src/config/configService.js +419 -74
  121. package/src/config/configService.ts +703 -145
  122. package/src/config/numericLimits.js +22 -0
  123. package/src/config/numericLimits.ts +24 -0
  124. package/src/config.js +1 -0
  125. package/src/config.ts +3 -2
  126. package/src/context/budgetPlan.js +1 -1
  127. package/src/context/budgetPlan.ts +1 -1
  128. package/src/context/compact.js +7 -2
  129. package/src/context/compact.ts +9 -2
  130. package/src/context/microCompactLite.js +212 -13
  131. package/src/context/microCompactLite.ts +261 -12
  132. package/src/context/sessionRegistry.js +8 -1
  133. package/src/context/sessionRegistry.ts +18 -5
  134. package/src/context/toolResultArchive.js +72 -0
  135. package/src/context/toolResultArchive.ts +95 -0
  136. package/src/goal/errors.js +8 -0
  137. package/src/goal/errors.ts +18 -0
  138. package/src/goal/goalRuntime.js +344 -0
  139. package/src/goal/goalRuntime.ts +466 -0
  140. package/src/goal/goalStore.js +200 -0
  141. package/src/goal/goalStore.ts +219 -0
  142. package/src/goal/index.js +4 -0
  143. package/src/goal/index.ts +4 -0
  144. package/src/goal/statusMapping.js +71 -0
  145. package/src/goal/statusMapping.ts +95 -0
  146. package/src/goal/types.js +7 -0
  147. package/src/goal/types.ts +179 -0
  148. package/src/goal/workflowController.js +306 -0
  149. package/src/goal/workflowController.ts +360 -0
  150. package/src/harness/evidenceLedger.js +2 -0
  151. package/src/harness/evidenceLedger.ts +1 -0
  152. package/src/harness/stallGuard.js +9 -3
  153. package/src/harness/stallGuard.ts +7 -3
  154. package/src/images/imageResolver.js +161 -0
  155. package/src/images/imageResolver.ts +189 -0
  156. package/src/infra/verification.js +34 -5
  157. package/src/infra/verification.ts +39 -4
  158. package/src/llm/client.js +79 -21
  159. package/src/llm/client.ts +182 -35
  160. package/src/llm/gatewayClient.js +201 -0
  161. package/src/llm/gatewayClient.ts +299 -0
  162. package/src/llm/messageProvenance.js +46 -0
  163. package/src/llm/messageProvenance.ts +58 -0
  164. package/src/llm/modelProvider.js +1 -0
  165. package/src/llm/modelProvider.ts +22 -0
  166. package/src/llm/providers/anthropicFormat.js +108 -1
  167. package/src/llm/providers/anthropicFormat.ts +115 -4
  168. package/src/llm/providers/openaiFormat.js +9 -0
  169. package/src/llm/providers/openaiFormat.ts +12 -3
  170. package/src/loop.js +319 -54
  171. package/src/loop.ts +373 -57
  172. package/src/main.js +4 -1
  173. package/src/main.tsx +4 -1
  174. package/src/managedExecution/index.js +2 -0
  175. package/src/managedExecution/index.ts +2 -0
  176. package/src/managedExecution/registry.js +15 -0
  177. package/src/managedExecution/registry.ts +20 -0
  178. package/src/managedExecution/types.js +1 -0
  179. package/src/managedExecution/types.ts +86 -0
  180. package/src/modelRef.js +9 -0
  181. package/src/modelRef.ts +31 -0
  182. package/src/multimodal.js +14 -28
  183. package/src/multimodal.ts +17 -33
  184. package/src/net/lanDiscovery.js +65 -0
  185. package/src/net/lanDiscovery.ts +82 -0
  186. package/src/plugin-sdk.js +28 -3
  187. package/src/plugin-sdk.ts +39 -6
  188. package/src/plugins/pluginApi.ts +16 -1
  189. package/src/plugins/pluginLoader.js +4 -0
  190. package/src/plugins/pluginLoader.ts +6 -0
  191. package/src/plugins/pluginRunner.js +139 -7
  192. package/src/plugins/pluginRunner.ts +173 -9
  193. package/src/plugins/slashFallback.js +113 -0
  194. package/src/plugins/slashFallback.ts +137 -0
  195. package/src/prompts/policyRegistry.js +12 -32
  196. package/src/prompts/policyRegistry.ts +15 -32
  197. package/src/prompts/promptAssembly.js +103 -12
  198. package/src/prompts/promptAssembly.ts +114 -12
  199. package/src/prompts/skillList.js +21 -3
  200. package/src/prompts/skillList.ts +27 -2
  201. package/src/prompts/stableSystemPrompt.js +18 -0
  202. package/src/prompts/stableSystemPrompt.ts +33 -0
  203. package/src/runtime/agentRuntimeContext.js +8 -2
  204. package/src/runtime/agentRuntimeContext.ts +17 -4
  205. package/src/session/runAgentTurn.js +78 -12
  206. package/src/session/runAgentTurn.ts +105 -23
  207. package/src/session/runTurn.js +10 -9
  208. package/src/session/runTurn.ts +17 -8
  209. package/src/session/sessionState.ts +2 -0
  210. package/src/team/auth.js +186 -0
  211. package/src/team/auth.ts +298 -0
  212. package/src/team/cli.js +105 -0
  213. package/src/team/cli.ts +122 -0
  214. package/src/team/onlineToolContract.js +64 -0
  215. package/src/team/onlineToolContract.ts +109 -0
  216. package/src/team/toolPolicy.js +36 -0
  217. package/src/team/toolPolicy.ts +75 -0
  218. package/src/tools/advisor/advisor.js +90 -46
  219. package/src/tools/advisor/advisor.ts +100 -47
  220. package/src/tools/advisor/advisorDecision.js +71 -0
  221. package/src/tools/advisor/advisorDecision.ts +85 -0
  222. package/src/tools/agent/agent.js +18 -5
  223. package/src/tools/agent/agent.ts +21 -7
  224. package/src/tools/agent/agentControl.js +246 -0
  225. package/src/tools/agent/agentControl.ts +284 -0
  226. package/src/tools/agent/agentTaskService.js +40 -1
  227. package/src/tools/agent/agentTaskService.ts +44 -1
  228. package/src/tools/agent/agentTypes.js +14 -2
  229. package/src/tools/agent/agentTypes.ts +15 -2
  230. package/src/tools/applypatch/applyPatch.js +362 -0
  231. package/src/tools/applypatch/applyPatch.ts +428 -0
  232. package/src/tools/bash/bash.js +22 -15
  233. package/src/tools/bash/bash.ts +23 -17
  234. package/src/tools/capabilities.js +10 -5
  235. package/src/tools/capabilities.ts +10 -5
  236. package/src/tools/computeruse/computerIntent.js +32 -0
  237. package/src/tools/computeruse/computerIntent.ts +46 -0
  238. package/src/tools/computeruse/telemetryLog.js +12 -0
  239. package/src/tools/computeruse/telemetryLog.ts +16 -0
  240. package/src/tools/computeruse/vision/telemetry.js +1 -0
  241. package/src/tools/computeruse/vision/telemetry.ts +1 -0
  242. package/src/tools/effectiveToolSet.js +4 -0
  243. package/src/tools/effectiveToolSet.ts +4 -0
  244. package/src/tools/index.js +21 -2
  245. package/src/tools/index.ts +33 -2
  246. package/src/tools/presentresult/presentresult.js +6 -15
  247. package/src/tools/presentresult/presentresult.ts +6 -15
  248. package/src/tools/read/read.js +62 -9
  249. package/src/tools/read/read.ts +63 -9
  250. package/src/tools/shared/fileUtils.js +1 -2
  251. package/src/tools/shared/fileUtils.ts +1 -2
  252. package/src/tools/shared/toolOutputStore.js +23 -1
  253. package/src/tools/shared/toolOutputStore.ts +22 -1
  254. package/src/tools/skill/skill.js +9 -3
  255. package/src/tools/skill/skill.ts +8 -4
  256. package/src/tools/toolPromptGuides.js +117 -0
  257. package/src/tools/toolPromptGuides.ts +137 -0
  258. package/src/tools/webbrowser/agentBrowserPath.js +19 -9
  259. package/src/tools/webbrowser/agentBrowserPath.ts +70 -28
  260. package/src/tools/webbrowser/browser.js +27 -11
  261. package/src/tools/webbrowser/browser.ts +68 -24
  262. package/src/tools/webbrowser/browserAutomation.js +329 -0
  263. package/src/tools/webbrowser/browserAutomation.ts +467 -0
  264. package/src/tools/webbrowser/browserAxState.js +97 -0
  265. package/src/tools/webbrowser/browserAxState.ts +142 -0
  266. package/src/tools/webbrowser/browserBatch.js +1578 -0
  267. package/src/tools/webbrowser/browserBatch.ts +2382 -0
  268. package/src/tools/webbrowser/browserCdp.js +208 -0
  269. package/src/tools/webbrowser/browserCdp.ts +255 -0
  270. package/src/tools/webbrowser/browserConditions.js +24 -0
  271. package/src/tools/webbrowser/browserConditions.ts +45 -0
  272. package/src/tools/webbrowser/browserContent.js +281 -0
  273. package/src/tools/webbrowser/browserContent.ts +438 -0
  274. package/src/tools/webbrowser/browserControl.js +131 -34
  275. package/src/tools/webbrowser/browserControl.ts +210 -56
  276. package/src/tools/webbrowser/browserDaemon.js +22 -14
  277. package/src/tools/webbrowser/browserDaemon.ts +54 -21
  278. package/src/tools/webbrowser/browserFrames.js +47 -0
  279. package/src/tools/webbrowser/browserFrames.ts +54 -0
  280. package/src/tools/webbrowser/browserInteractionResolver.js +17 -0
  281. package/src/tools/webbrowser/browserInteractionResolver.ts +40 -0
  282. package/src/tools/webbrowser/browserLocator.js +140 -0
  283. package/src/tools/webbrowser/browserLocator.ts +194 -0
  284. package/src/tools/webbrowser/browserOutcome.js +10 -17
  285. package/src/tools/webbrowser/browserOutcome.ts +48 -31
  286. package/src/tools/webbrowser/browserRecovery.js +58 -0
  287. package/src/tools/webbrowser/browserRecovery.ts +83 -0
  288. package/src/tools/webbrowser/browserRunRegistry.js +35 -3
  289. package/src/tools/webbrowser/browserRunRegistry.ts +91 -15
  290. package/src/tools/webbrowser/browserService.js +28 -2
  291. package/src/tools/webbrowser/browserService.ts +49 -9
  292. package/src/tools/webbrowser/browserStream.js +132 -33
  293. package/src/tools/webbrowser/browserStream.ts +224 -50
  294. package/src/tools/webbrowser/browserTabs.js +48 -15
  295. package/src/tools/webbrowser/browserTabs.ts +178 -96
  296. package/src/tools/webbrowser/webbrowser.js +679 -144
  297. package/src/tools/webbrowser/webbrowser.ts +975 -236
  298. package/src/tools/webfetch/preapproved.js +0 -2
  299. package/src/tools/webfetch/preapproved.ts +0 -2
  300. package/src/tools/websearch/websearch.js +129 -8
  301. package/src/tools/websearch/websearch.ts +138 -6
  302. package/src/types.ts +440 -38
  303. package/src/ui/App.js +377 -58
  304. package/src/ui/App.tsx +576 -111
  305. package/src/ui/LiveArea.js +14 -8
  306. package/src/ui/LiveArea.tsx +14 -10
  307. package/src/ui/MessageList.js +63 -20
  308. package/src/ui/MessageList.tsx +101 -16
  309. package/src/ui/ModelPicker.js +7 -4
  310. package/src/ui/ModelPicker.tsx +55 -8
  311. package/src/ui/hooks/useChat.js +152 -163
  312. package/src/ui/hooks/useChat.ts +396 -334
  313. package/src/ui/loginCommand.js +56 -0
  314. package/src/ui/loginCommand.ts +63 -0
  315. package/src/ui/toolProgress.js +41 -0
  316. package/src/ui/toolProgress.ts +44 -0
  317. package/src/web/browserBridge.js +20 -2
  318. package/src/web/browserBridge.ts +20 -2
  319. package/src/web/protocol.ts +1897 -194
  320. package/workflows/adaptive-research/workflow.mjs +86 -0
  321. package/workflows/e2e-capabilities/workflow.mjs +102 -0
  322. package/workflows/e2e-llm/workflow.mjs +52 -0
  323. package/workflows/e2e-smoke/workflow.mjs +59 -0
  324. package/workflows/e2e-vision-schema/README.md +48 -0
  325. package/workflows/e2e-vision-schema/workflow.mjs +149 -0
  326. package/workflows/e2e-write-greet/workflow.mjs +37 -0
  327. package/workflows/file-census/README.md +14 -0
  328. package/workflows/file-census/scripts/.gitkeep +2 -0
  329. package/workflows/file-census/workflow.mjs +110 -0
  330. package/workflows/goal/README.md +166 -0
  331. package/workflows/goal/prompts/audit.md +57 -0
  332. package/workflows/goal/prompts/contract.md +56 -0
  333. package/workflows/goal/prompts/iterate.md +37 -0
  334. package/workflows/goal/workflow.mjs +501 -0
  335. package/workflows/illustrate/README.md +90 -0
  336. package/workflows/illustrate/assets/engines.json +20 -0
  337. package/workflows/illustrate/assets/extract-baidu.js +21 -0
  338. package/workflows/illustrate/assets/extract-bing.js +33 -0
  339. package/workflows/illustrate/assets/extract-google.js +31 -0
  340. package/workflows/illustrate/workflow.mjs +301 -0
  341. package/workflows/plan/workflow.mjs +389 -0
  342. package/workflows/question-crops/README.md +35 -0
  343. package/workflows/question-crops/prompts/inventory.md +23 -0
  344. package/workflows/question-crops/scripts/image-ops.mjs +305 -0
  345. package/workflows/question-crops/scripts/image-ops.test.mjs +95 -0
  346. package/workflows/question-crops/workflow.mjs +172 -0
  347. package/workflows/ralph-loop/workflow.mjs +162 -0
  348. package/workflows/research/README.md +74 -0
  349. package/workflows/research/prompts/s0-goal.md +43 -0
  350. package/workflows/research/prompts/s1-early-exit.md +22 -0
  351. package/workflows/research/prompts/s1-map.md +58 -0
  352. package/workflows/research/prompts/s2-options.md +53 -0
  353. package/workflows/research/prompts/s3-deep-dive.md +56 -0
  354. package/workflows/research/prompts/s4-verify.md +74 -0
  355. package/workflows/research/prompts/s5-synthesis.md +81 -0
  356. package/workflows/research/prompts/s5b-fill-gaps.md +32 -0
  357. package/workflows/research/prompts/s6-package.md +32 -0
  358. package/workflows/research/scripts/build-report.mjs +98 -0
  359. package/workflows/research/workflow.mjs +288 -0
  360. package/workflows/wechat-reading-shots/prompts/collect-params.md +16 -0
  361. package/workflows/wechat-reading-shots/prompts/evaluate.md +20 -0
  362. package/workflows/wechat-reading-shots/prompts/open-book.md +28 -0
  363. package/workflows/wechat-reading-shots/prompts/read-page.md +13 -0
  364. package/workflows/wechat-reading-shots/prompts/turn-batch.md +24 -0
  365. package/workflows/wechat-reading-shots/test/control-flow.test.ts +329 -0
  366. package/workflows/wechat-reading-shots/workflow.mjs +266 -0
  367. package/workflows/wrong-question-docx/README.md +55 -0
  368. package/workflows/wrong-question-docx/prompts/figure-qa.md +8 -0
  369. package/workflows/wrong-question-docx/prompts/transcribe.md +25 -0
  370. package/workflows/wrong-question-docx/scripts/workbook.mjs +857 -0
  371. package/workflows/wrong-question-docx/test/control-flow.test.ts +165 -0
  372. package/workflows/wrong-question-docx/test/workbook.test.mjs +303 -0
  373. package/workflows/wrong-question-docx/workflow.mjs +485 -0
  374. package/plugins/workflow-runner/src/agentCommon.js +0 -354
  375. package/plugins/workflow-runner/src/agentCommon.ts +0 -435
  376. package/plugins/workflow-runner/src/controlFlow/branch.js +0 -40
  377. package/plugins/workflow-runner/src/controlFlow/branch.ts +0 -46
  378. package/plugins/workflow-runner/src/controlFlow/loop.js +0 -211
  379. package/plugins/workflow-runner/src/controlFlow/loop.ts +0 -248
  380. package/plugins/workflow-runner/src/controlFlow/pause.js +0 -129
  381. package/plugins/workflow-runner/src/controlFlow/pause.ts +0 -141
  382. package/plugins/workflow-runner/src/controlFlow/signal.js +0 -21
  383. package/plugins/workflow-runner/src/controlFlow/signal.ts +0 -29
  384. package/plugins/workflow-runner/src/controlFlow/types.js +0 -18
  385. package/plugins/workflow-runner/src/controlFlow/types.ts +0 -63
  386. package/plugins/workflow-runner/src/dslManifest.js +0 -72
  387. package/plugins/workflow-runner/src/dslManifest.ts +0 -127
  388. package/plugins/workflow-runner/src/expressions.js +0 -639
  389. package/plugins/workflow-runner/src/expressions.ts +0 -640
  390. package/plugins/workflow-runner/src/loader.js +0 -573
  391. package/plugins/workflow-runner/src/loader.ts +0 -721
  392. package/plugins/workflow-runner/src/multimodal.js +0 -38
  393. package/plugins/workflow-runner/src/multimodal.ts +0 -64
  394. package/plugins/workflow-runner/src/recovery.js +0 -126
  395. package/plugins/workflow-runner/src/recovery.ts +0 -156
  396. package/plugins/workflow-runner/src/stepExecutors/agent.js +0 -173
  397. package/plugins/workflow-runner/src/stepExecutors/agent.ts +0 -234
  398. package/plugins/workflow-runner/src/stepExecutors/assert.js +0 -42
  399. package/plugins/workflow-runner/src/stepExecutors/assert.ts +0 -52
  400. package/plugins/workflow-runner/src/stepExecutors/echo.js +0 -31
  401. package/plugins/workflow-runner/src/stepExecutors/echo.ts +0 -38
  402. package/plugins/workflow-runner/src/stepExecutors/llm.js +0 -99
  403. package/plugins/workflow-runner/src/stepExecutors/llm.ts +0 -133
  404. package/plugins/workflow-runner/src/stepExecutors/set.js +0 -49
  405. package/plugins/workflow-runner/src/stepExecutors/set.ts +0 -56
  406. package/plugins/workflow-runner/src/stepExecutors/skill.js +0 -111
  407. package/plugins/workflow-runner/src/stepExecutors/skill.ts +0 -140
  408. package/plugins/workflow-runner/src/stepExecutors/tool.js +0 -227
  409. package/plugins/workflow-runner/src/stepExecutors/tool.ts +0 -274
  410. package/plugins/workflow-runner/src/stepExecutors/verify.js +0 -61
  411. package/plugins/workflow-runner/src/stepExecutors/verify.ts +0 -72
  412. package/plugins/workflow-runner/src/stepPipeline.js +0 -277
  413. package/plugins/workflow-runner/src/stepPipeline.ts +0 -366
  414. package/plugins/workflow-runner/test/agent.test.ts +0 -587
  415. package/plugins/workflow-runner/test/agentLlmError.test.ts +0 -373
  416. package/plugins/workflow-runner/test/bashBlacklist.sweep.test.ts +0 -65
  417. package/plugins/workflow-runner/test/captureMisbind.test.ts +0 -99
  418. package/plugins/workflow-runner/test/cli.e2e.test.ts +0 -167
  419. package/plugins/workflow-runner/test/context-files.test.ts +0 -320
  420. package/plugins/workflow-runner/test/e2e.test.ts +0 -370
  421. package/plugins/workflow-runner/test/e2eNewLayout.test.ts +0 -314
  422. package/plugins/workflow-runner/test/engineRobustness.test.ts +0 -234
  423. package/plugins/workflow-runner/test/expressions.test.ts +0 -439
  424. package/plugins/workflow-runner/test/foundation.test.ts +0 -126
  425. package/plugins/workflow-runner/test/graceful.test.ts +0 -139
  426. package/plugins/workflow-runner/test/greedyInputs.load.test.ts +0 -47
  427. package/plugins/workflow-runner/test/hostCapability.test.ts +0 -108
  428. package/plugins/workflow-runner/test/illustrate.load.test.ts +0 -183
  429. package/plugins/workflow-runner/test/jsonPayload.test.ts +0 -83
  430. package/plugins/workflow-runner/test/loader.test.ts +0 -617
  431. package/plugins/workflow-runner/test/maxTurnsRetryDelay.test.ts +0 -33
  432. package/plugins/workflow-runner/test/multimodal.test.ts +0 -342
  433. package/plugins/workflow-runner/test/output-schema.test.ts +0 -213
  434. package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +0 -106
  435. package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +0 -269
  436. package/plugins/workflow-runner/test/ppt.load.test.ts +0 -1220
  437. package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +0 -221
  438. package/plugins/workflow-runner/test/recovery.test.ts +0 -265
  439. package/plugins/workflow-runner/test/research.load.test.ts +0 -328
  440. package/plugins/workflow-runner/test/runner.test.ts +0 -911
  441. package/plugins/workflow-runner/test/runnerPreflight.test.ts +0 -436
  442. package/plugins/workflow-runner/test/skillTurnDone.test.ts +0 -71
  443. package/plugins/workflow-runner/test/stateLifecycle.test.ts +0 -198
  444. package/plugins/workflow-runner/test/stepPipeline.test.ts +0 -114
  445. package/plugins/workflow-runner/test/taskArbitration.test.ts +0 -354
  446. package/plugins/workflow-runner/test/taskLifecycle.test.ts +0 -324
  447. package/plugins/workflow-runner/test/toolArtifactOverwrite.test.ts +0 -221
  448. package/plugins/workflow-runner/test/toolCapabilityBoundary.test.ts +0 -29
  449. package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +0 -168
  450. package/plugins/workflow-runner/test/webppt.load.test.ts +0 -250
  451. package/plugins/workflow-runner/test/word.load.test.ts +0 -208
  452. package/plugins/workflow-runner/test/workflows.smoke.test.ts +0 -76
  453. package/plugins/workflow-runner/test/yamlGuardConditions.test.ts +0 -207
  454. package/src/advisorState.js +0 -22
  455. package/src/cli/configWizard.js +0 -293
  456. package/src/tools/toolProgressBridge.js +0 -48
  457. package/workflows/adaptive-research.yaml +0 -50
  458. package/workflows/book-review-short.yaml +0 -125
  459. package/workflows/e2e-write-greet.yaml +0 -31
  460. package/workflows/illustrate.yaml +0 -227
  461. package/workflows/plan.yaml +0 -470
  462. package/workflows/ppt.yaml +0 -1670
  463. package/workflows/ralph-loop.yaml +0 -201
  464. package/workflows/research.yaml +0 -873
  465. package/workflows/schema.json +0 -176
  466. package/workflows/webppt.yaml +0 -496
  467. package/workflows/wechat-reading-shots.yaml +0 -315
  468. package/workflows/word.yaml +0 -720
  469. package/workflows/youtube-shorts.yaml +0 -202
  470. /package/workflows/{research.md → research/DESIGN.md} +0 -0
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Browser locators shared by BrowserBatch and future visible-DOM adapters.
3
+ *
4
+ * This deliberately emits a small, self-contained browser expression instead
5
+ * of retaining DOM node handles. A locator is resolved again for every step,
6
+ * so a navigation or React re-render cannot silently reuse a stale element.
7
+ */
8
+
9
+ export type BrowserLocator = {
10
+ by:
11
+ | 'css'
12
+ | 'ax'
13
+ | 'role'
14
+ | 'text'
15
+ | 'label'
16
+ | 'placeholder'
17
+ | 'alt'
18
+ | 'title'
19
+ | 'testid';
20
+ value: string;
21
+ /** AX node id (or numeric index printed by the latest snapshot). */
22
+ generation?: number;
23
+ name?: string;
24
+ exact?: boolean;
25
+ strict?: boolean;
26
+ nth?: number;
27
+ within?: BrowserLocator;
28
+ has_text?: string;
29
+ frame?: BrowserLocator[];
30
+ framePath?: BrowserLocator[];
31
+ frame_path?: BrowserLocator[];
32
+ /** CDP frame id returned by frameInfo; enables cross-origin frame context. */
33
+ frameId?: string;
34
+ frame_id?: string;
35
+ };
36
+
37
+ const quote = (value: string) => JSON.stringify(value);
38
+
39
+ /** Build an expression returning `Element[]` in the selected document. */
40
+ export function buildLocatorExpression(
41
+ locator: BrowserLocator | undefined,
42
+ selector: string | undefined,
43
+ rootExpression = 'document',
44
+ ): string {
45
+ if (selector) {
46
+ return `Array.from(${rootExpression}.querySelectorAll(${quote(selector)}))`;
47
+ }
48
+ if (!locator) throw new Error('step requires selector or locator');
49
+
50
+ if (locator.by === 'ax') {
51
+ throw new Error(
52
+ 'AX locator must be resolved against the live accessibility tree first',
53
+ );
54
+ }
55
+
56
+ const root = locator.within
57
+ ? `${buildLocatorExpression(locator.within, undefined, rootExpression)}.flatMap((scope) => Array.from(scope.querySelectorAll('*')))`
58
+ : `Array.from(${rootExpression}.querySelectorAll('*'))`;
59
+ const value = quote(locator.value);
60
+ const exact = locator.exact === true;
61
+ const compare = (expression: string, expected = value) =>
62
+ exact
63
+ ? `${expression}.trim()===${expected}`
64
+ : `${expression}.includes(${expected})`;
65
+
66
+ let predicate: string;
67
+ switch (locator.by) {
68
+ case 'css':
69
+ return applyFilters(
70
+ `Array.from(${rootExpression}.querySelectorAll(${value}))`,
71
+ locator,
72
+ );
73
+ case 'role': {
74
+ const name = locator.name ? quote(locator.name) : value;
75
+ const nameCompare = locator.name
76
+ ? `(function(name){return ${exact ? `name.trim()===${name}` : `name.includes(${name})`};})(__name)`
77
+ : 'true';
78
+ predicate = `(__role===${value}&&${nameCompare})`;
79
+ break;
80
+ }
81
+ case 'label':
82
+ predicate = `Array.from(__el.labels||[]).some((label)=>${compare("(label.textContent||'')")})`;
83
+ break;
84
+ case 'placeholder':
85
+ predicate = compare(`__el.getAttribute('placeholder')||''`);
86
+ break;
87
+ case 'alt':
88
+ predicate = compare(`__el.getAttribute('alt')||''`);
89
+ break;
90
+ case 'title':
91
+ predicate = compare(`__el.getAttribute('title')||''`);
92
+ break;
93
+ case 'testid':
94
+ predicate = compare(`__el.getAttribute('data-testid')||''`);
95
+ break;
96
+ case 'text':
97
+ predicate = `(!__el.children.length&&${compare("(__el.textContent||'')")})`;
98
+ break;
99
+ }
100
+
101
+ const expression = `(() => {
102
+ const __accessibleName = (el) => {
103
+ const labelled = el.getAttribute('aria-label');
104
+ if (labelled) return labelled;
105
+ const ids = (el.getAttribute('aria-labelledby')||'').split(/\\s+/).filter(Boolean);
106
+ if (ids.length) return ids.map((id)=>${rootExpression}.getElementById(id)?.textContent||'').join(' ').trim();
107
+ if (el.labels?.length) return Array.from(el.labels).map((label)=>label.textContent||'').join(' ').trim();
108
+ return (el.innerText||el.textContent||'').trim();
109
+ };
110
+ const __implicitRole = (el) => {
111
+ const explicit = el.getAttribute('role');
112
+ if (explicit) return explicit;
113
+ const tag = el.tagName.toLowerCase();
114
+ if (tag === 'button') return 'button';
115
+ if (tag === 'a' && el.hasAttribute('href')) return 'link';
116
+ if (tag === 'textarea') return 'textbox';
117
+ if (tag === 'select') return 'combobox';
118
+ if (tag === 'input') {
119
+ const type = (el.getAttribute('type')||'text').toLowerCase();
120
+ if (type === 'checkbox') return 'checkbox';
121
+ if (type === 'radio') return 'radio';
122
+ if (type === 'button' || type === 'submit' || type === 'reset') return 'button';
123
+ return 'textbox';
124
+ }
125
+ return '';
126
+ };
127
+ return ${root}.filter((__el) => {
128
+ const __role = __implicitRole(__el);
129
+ const __name = __accessibleName(__el);
130
+ return ${predicate};
131
+ });
132
+ })()`;
133
+
134
+ const filtered = applyFilters(expression, locator);
135
+ const framePath = locator.frame ?? locator.framePath ?? locator.frame_path;
136
+ if (!framePath?.length) return filtered;
137
+
138
+ // Same-origin frame traversal is explicit. Cross-origin frames cannot be
139
+ // inspected through Runtime.evaluate and must be handled by a backend that
140
+ // can attach to the child target; never pretend the parent matched instead.
141
+ let frameRoot = rootExpression;
142
+ for (const frame of framePath) {
143
+ const frameSelector =
144
+ frame.by === 'css'
145
+ ? quote(frame.value)
146
+ : quote(`iframe[title*="${frame.value.replaceAll('"', '\\"')}"]`);
147
+ frameRoot = `(() => { const frame = ${frameRoot}.querySelector(${frameSelector}); if (!frame || !frame.contentDocument) throw new Error('frameLocator could not resolve a same-origin iframe'); return frame.contentDocument; })()`;
148
+ }
149
+ return buildLocatorExpression(
150
+ {
151
+ ...locator,
152
+ frame: undefined,
153
+ framePath: undefined,
154
+ frame_path: undefined,
155
+ frameId: undefined,
156
+ frame_id: undefined,
157
+ },
158
+ selector,
159
+ frameRoot,
160
+ );
161
+ }
162
+
163
+ function applyFilters(expression: string, locator: BrowserLocator): string {
164
+ const filters: string[] = [];
165
+ if (locator.has_text)
166
+ filters.push(`(__el.textContent||'').includes(${quote(locator.has_text)})`);
167
+ return filters.length
168
+ ? `Array.from(${expression}).filter((__el)=>${filters.join('&&')})`
169
+ : expression;
170
+ }
171
+
172
+ export function buildTargetExpression(
173
+ locator: BrowserLocator | undefined,
174
+ selector: string | undefined,
175
+ ): string {
176
+ const elements = buildLocatorExpression(locator, selector);
177
+ const strict = locator?.strict !== false;
178
+ const nth = locator?.nth ?? 0;
179
+ return `(() => {
180
+ const __matches = ${elements}.filter((__candidate) => {
181
+ const __style = getComputedStyle(__candidate);
182
+ const __rect = __candidate.getBoundingClientRect();
183
+ return __style.display!=='none' && __style.visibility!=='hidden' && __style.pointerEvents!=='none' && (!!__rect.width || !!__rect.height);
184
+ });
185
+ if (!__matches.length) throw new Error('Locator matched no elements');
186
+ if (__matches.length > 1 && ${strict}) throw new Error('Locator matched '+__matches.length+' elements; use nth or strict:false');
187
+ const __el = __matches[${nth}];
188
+ if (!__el) throw new Error('Locator nth is outside the matched elements');
189
+ const __style = getComputedStyle(__el);
190
+ const __rect = __el.getBoundingClientRect();
191
+ if (__style.display==='none'||__style.visibility==='hidden'||__style.pointerEvents==='none'||(!__rect.width&&!__rect.height)) throw new Error('Locator matched a hidden element');
192
+ return __el;
193
+ })()`;
194
+ }
@@ -85,16 +85,9 @@ export function describeEffect(verdict, action) {
85
85
  case 'changed':
86
86
  return null; // 正常路径不加噪声
87
87
  case 'unchanged':
88
- return (`⚠️ 动作已发出,但页面结构在动作前后**完全没有变化**。\n`
89
- + `这通常意味着三种情况之一:${action} 命中的不是可交互元素、点击被遮挡层拦住了、`
90
- + `或者这个操作本来就不改变页面(比如点了一个纯展示文本)。\n`
91
- + `请对照下面的最新快照确认目标是否正确,不要用同一个选择器直接重试。`
92
- // 豁免了行才提。这不是免责声明,是让模型知道这个结论有多硬:
93
- // 被豁免的位置上,「自己在跳」和「被我改了又在跳」不可区分。
94
- + (verdict.maskedLines > 0
95
- ? `\n(说明:页面上有 ${verdict.maskedLines} 行在持续自行变化(时钟/轮询一类),`
96
- + `已从判定中排除。若你这次操作本该影响的正是这些位置,这个结论可能不准。)`
97
- : ''));
88
+ return `⚠️ 动作已发出,但页面结构在动作前后**完全没有变化**。\n这通常意味着三种情况之一:${action} 命中的不是可交互元素、点击被遮挡层拦住了、或者这个操作本来就不改变页面(比如点了一个纯展示文本)。\n请对照下面的最新快照确认目标是否正确,不要用同一个选择器直接重试。${verdict.maskedLines > 0
89
+ ? `\n(说明:页面上有 ${verdict.maskedLines} 行在持续自行变化(时钟/轮询一类),已从判定中排除。若你这次操作本该影响的正是这些位置,这个结论可能不准。)`
90
+ : ''}`;
98
91
  case 'unknown':
99
92
  return '(未能取得动作前后的页面快照,无法判断这次操作是否生效——请自行核对。)';
100
93
  }
@@ -179,7 +172,7 @@ export function suggestSelectorCandidates(snapshot, selector, limit = 5) {
179
172
  export class BrowserAttemptLedger {
180
173
  counts = new Map();
181
174
  static key(conversationKey, action, selector) {
182
- return `${conversationKey}${action}${selector ?? ''}`;
175
+ return `${conversationKey}\u0000${action}\u0000${selector ?? ''}`;
183
176
  }
184
177
  recordFailure(conversationKey, action, selector) {
185
178
  const key = BrowserAttemptLedger.key(conversationKey, action, selector);
@@ -191,12 +184,12 @@ export class BrowserAttemptLedger {
191
184
  this.counts.delete(BrowserAttemptLedger.key(conversationKey, action, selector));
192
185
  }
193
186
  failures(conversationKey, action, selector) {
194
- return this.counts.get(BrowserAttemptLedger.key(conversationKey, action, selector)) ?? 0;
187
+ return (this.counts.get(BrowserAttemptLedger.key(conversationKey, action, selector)) ?? 0);
195
188
  }
196
189
  /** 会话结束时清账,避免长期驻留。 */
197
190
  forget(conversationKey) {
198
191
  for (const key of [...this.counts.keys()]) {
199
- if (key.startsWith(`${conversationKey}`))
192
+ if (key.startsWith(`${conversationKey}\u0000`))
200
193
  this.counts.delete(key);
201
194
  }
202
195
  }
@@ -228,7 +221,9 @@ export function budgetSnapshot(snapshot, opts = {}) {
228
221
  if (lines.length <= maxLines && snapshot.length <= maxChars)
229
222
  return snapshot;
230
223
  const hasRef = (line) => /@e\d+|\bref=e\d+/.test(line);
231
- const clip = (line) => line.length <= MAX_SNAPSHOT_LINE_CHARS ? line : `${line.slice(0, MAX_SNAPSHOT_LINE_CHARS)}…`;
224
+ const clip = (line) => line.length <= MAX_SNAPSHOT_LINE_CHARS
225
+ ? line
226
+ : `${line.slice(0, MAX_SNAPSHOT_LINE_CHARS)}…`;
232
227
  // 两轮**只挑不排**:先在原序上标记要留的可操作行,再用余量补纯文本行。
233
228
  const keep = new Array(lines.length).fill(false);
234
229
  let kept = 0;
@@ -267,7 +262,5 @@ export function budgetSnapshot(snapshot, opts = {}) {
267
262
  }
268
263
  flushGap();
269
264
  const dropped = lines.length - kept;
270
- return (`${out.join('\n')}\n`
271
- + `… 为控制上下文,共省略 ${dropped} 行(按原顺序保留,优先保住可引用的元素)。`
272
- + '需要完整页面请单独调用 snapshot,或用 getContent 读取正文。');
265
+ return `${out.join('\n')}\n… 为控制上下文,共省略 ${dropped} 行(按原顺序保留,优先保住可引用的元素)。需要完整页面请单独调用 snapshot,或用 getContent 读取正文。`;
273
266
  }
@@ -82,36 +82,35 @@ export function classifyPageEffect(
82
82
  after: string | undefined,
83
83
  noiseAfter?: string,
84
84
  ): BrowserEffectVerdict {
85
- if (before === undefined || after === undefined) return { effect: 'unknown', maskedLines: 0 };
85
+ if (before === undefined || after === undefined)
86
+ return { effect: 'unknown', maskedLines: 0 };
86
87
  const actual = differingLineIndexes(before, after);
87
88
  if (actual === null) return { effect: 'changed', maskedLines: 0 }; // 行数变了 = 结构变了
88
89
  if (actual.size === 0) return { effect: 'unchanged', maskedLines: 0 };
89
- const noisy = noiseAfter !== undefined ? differingLineIndexes(after, noiseAfter) : null;
90
+ const noisy =
91
+ noiseAfter !== undefined ? differingLineIndexes(after, noiseAfter) : null;
90
92
  // 没有可用的噪声基线时不做扣除:宁可把「生效了」判宽,也不能把一次真实变化
91
93
  // 误报成没变——那会让模型放弃一条本来走通了的路。
92
94
  if (noisy === null) return { effect: 'changed', maskedLines: 0 };
93
- for (const index of actual) if (!noisy.has(index)) return { effect: 'changed', maskedLines: 0 };
95
+ for (const index of actual)
96
+ if (!noisy.has(index)) return { effect: 'changed', maskedLines: 0 };
94
97
  return { effect: 'unchanged', maskedLines: actual.size };
95
98
  }
96
99
 
97
100
  /** 给模型的效果说明。措辞要让它知道**下一步该干什么**,不只是知道结果。 */
98
- export function describeEffect(verdict: BrowserEffectVerdict, action: string): string | null {
101
+ export function describeEffect(
102
+ verdict: BrowserEffectVerdict,
103
+ action: string,
104
+ ): string | null {
99
105
  switch (verdict.effect) {
100
106
  case 'changed':
101
107
  return null; // 正常路径不加噪声
102
108
  case 'unchanged':
103
- return (
104
- `⚠️ 动作已发出,但页面结构在动作前后**完全没有变化**。\n`
105
- + `这通常意味着三种情况之一:${action} 命中的不是可交互元素、点击被遮挡层拦住了、`
106
- + `或者这个操作本来就不改变页面(比如点了一个纯展示文本)。\n`
107
- + `请对照下面的最新快照确认目标是否正确,不要用同一个选择器直接重试。`
108
- // 豁免了行才提。这不是免责声明,是让模型知道这个结论有多硬:
109
- // 被豁免的位置上,「自己在跳」和「被我改了又在跳」不可区分。
110
- + (verdict.maskedLines > 0
111
- ? `\n(说明:页面上有 ${verdict.maskedLines} 行在持续自行变化(时钟/轮询一类),`
112
- + `已从判定中排除。若你这次操作本该影响的正是这些位置,这个结论可能不准。)`
113
- : '')
114
- );
109
+ return `⚠️ 动作已发出,但页面结构在动作前后**完全没有变化**。\n这通常意味着三种情况之一:${action} 命中的不是可交互元素、点击被遮挡层拦住了、或者这个操作本来就不改变页面(比如点了一个纯展示文本)。\n请对照下面的最新快照确认目标是否正确,不要用同一个选择器直接重试。${
110
+ verdict.maskedLines > 0
111
+ ? `\n(说明:页面上有 ${verdict.maskedLines} 行在持续自行变化(时钟/轮询一类),已从判定中排除。若你这次操作本该影响的正是这些位置,这个结论可能不准。)`
112
+ : ''
113
+ }`;
115
114
  case 'unknown':
116
115
  return '(未能取得动作前后的页面快照,无法判断这次操作是否生效——请自行核对。)';
117
116
  }
@@ -206,29 +205,47 @@ export function suggestSelectorCandidates(
206
205
  export class BrowserAttemptLedger {
207
206
  private counts = new Map<string, number>();
208
207
 
209
- private static key(conversationKey: string, action: string, selector?: string): string {
210
- return `${conversationKey}${action}${selector ?? ''}`;
208
+ private static key(
209
+ conversationKey: string,
210
+ action: string,
211
+ selector?: string,
212
+ ): string {
213
+ return `${conversationKey}\u0000${action}\u0000${selector ?? ''}`;
211
214
  }
212
215
 
213
- recordFailure(conversationKey: string, action: string, selector?: string): number {
216
+ recordFailure(
217
+ conversationKey: string,
218
+ action: string,
219
+ selector?: string,
220
+ ): number {
214
221
  const key = BrowserAttemptLedger.key(conversationKey, action, selector);
215
222
  const next = (this.counts.get(key) ?? 0) + 1;
216
223
  this.counts.set(key, next);
217
224
  return next;
218
225
  }
219
226
 
220
- recordSuccess(conversationKey: string, action: string, selector?: string): void {
221
- this.counts.delete(BrowserAttemptLedger.key(conversationKey, action, selector));
227
+ recordSuccess(
228
+ conversationKey: string,
229
+ action: string,
230
+ selector?: string,
231
+ ): void {
232
+ this.counts.delete(
233
+ BrowserAttemptLedger.key(conversationKey, action, selector),
234
+ );
222
235
  }
223
236
 
224
237
  failures(conversationKey: string, action: string, selector?: string): number {
225
- return this.counts.get(BrowserAttemptLedger.key(conversationKey, action, selector)) ?? 0;
238
+ return (
239
+ this.counts.get(
240
+ BrowserAttemptLedger.key(conversationKey, action, selector),
241
+ ) ?? 0
242
+ );
226
243
  }
227
244
 
228
245
  /** 会话结束时清账,避免长期驻留。 */
229
246
  forget(conversationKey: string): void {
230
247
  for (const key of [...this.counts.keys()]) {
231
- if (key.startsWith(`${conversationKey}`)) this.counts.delete(key);
248
+ if (key.startsWith(`${conversationKey}\u0000`)) this.counts.delete(key);
232
249
  }
233
250
  }
234
251
  }
@@ -267,7 +284,9 @@ export function budgetSnapshot(
267
284
 
268
285
  const hasRef = (line: string): boolean => /@e\d+|\bref=e\d+/.test(line);
269
286
  const clip = (line: string): string =>
270
- line.length <= MAX_SNAPSHOT_LINE_CHARS ? line : `${line.slice(0, MAX_SNAPSHOT_LINE_CHARS)}…`;
287
+ line.length <= MAX_SNAPSHOT_LINE_CHARS
288
+ ? line
289
+ : `${line.slice(0, MAX_SNAPSHOT_LINE_CHARS)}…`;
271
290
 
272
291
  // 两轮**只挑不排**:先在原序上标记要留的可操作行,再用余量补纯文本行。
273
292
  const keep = new Array<boolean>(lines.length).fill(false);
@@ -292,15 +311,13 @@ export function budgetSnapshot(
292
311
  gap = 0;
293
312
  };
294
313
  for (let i = 0; i < lines.length; i += 1) {
295
- if (keep[i]) { flushGap(); out.push(clip(lines[i])); }
296
- else gap += 1;
314
+ if (keep[i]) {
315
+ flushGap();
316
+ out.push(clip(lines[i]));
317
+ } else gap += 1;
297
318
  }
298
319
  flushGap();
299
320
 
300
321
  const dropped = lines.length - kept;
301
- return (
302
- `${out.join('\n')}\n`
303
- + `… 为控制上下文,共省略 ${dropped} 行(按原顺序保留,优先保住可引用的元素)。`
304
- + '需要完整页面请单独调用 snapshot,或用 getContent 读取正文。'
305
- );
322
+ return `${out.join('\n')}\n… 为控制上下文,共省略 ${dropped} 行(按原顺序保留,优先保住可引用的元素)。需要完整页面请单独调用 snapshot,或用 getContent 读取正文。`;
306
323
  }
@@ -0,0 +1,58 @@
1
+ export function classifyBrowserFailure(error) {
2
+ const message = error instanceof Error ? error.message : String(error);
3
+ // Do not treat a generic "element not found" / selector miss as a stale
4
+ // browser target. Those are normal locator failures and must return the
5
+ // page evidence to the model instead of replaying the same action.
6
+ if (/\b(?:tab|target|frame)\b.*(?:closed|crashed|detached|destroyed|not found|invalid)/i.test(message) ||
7
+ /execution context.*(?:destroyed|not found|closed)/i.test(message) ||
8
+ /(?:page|window).*closed/i.test(message))
9
+ return 'stale-tab';
10
+ if (/cdp|socket|websocket|daemon|connection|disconnected|closed/i.test(message))
11
+ return 'browser-disconnected';
12
+ return 'unknown';
13
+ }
14
+ /**
15
+ * agent-browser reports many daemon/target failures as an exit code instead
16
+ * of rejecting the runner promise. Convert only those known recoverable
17
+ * failures into exceptions so the runtime recovery wrapper can see them;
18
+ * ordinary selector/business errors must remain normal tool results.
19
+ */
20
+ export function recoverableAgentBrowserRunFailure(result) {
21
+ if (result.code === 0)
22
+ return undefined;
23
+ const message = result.stderr?.trim() || result.stdout?.trim() || `exit ${result.code}`;
24
+ const error = new Error(message);
25
+ return classifyBrowserFailure(error) === 'unknown' ? undefined : error;
26
+ }
27
+ /** Retry only recoverable browser failures, rebuilding the connection/tab before retrying. */
28
+ export async function withBrowserRecovery(operation, hooks = {}) {
29
+ const maxAttempts = Math.max(1, Math.min(hooks.maxAttempts ?? 3, 4));
30
+ let attempt = 0;
31
+ let recoveredConnection = false;
32
+ let reboundTab = false;
33
+ while (true) {
34
+ if (hooks.signal?.aborted)
35
+ throw hooks.signal.reason ?? new Error('浏览器任务已中断');
36
+ try {
37
+ return await operation();
38
+ }
39
+ catch (error) {
40
+ attempt += 1;
41
+ const kind = classifyBrowserFailure(error);
42
+ if (kind === 'unknown' || attempt >= maxAttempts)
43
+ throw error;
44
+ if (kind === 'browser-disconnected' && !recoveredConnection) {
45
+ await hooks.reconnect?.();
46
+ recoveredConnection = true;
47
+ reboundTab = false;
48
+ continue;
49
+ }
50
+ if (kind === 'stale-tab' && !reboundTab) {
51
+ await hooks.rebindTab?.();
52
+ reboundTab = true;
53
+ continue;
54
+ }
55
+ throw error;
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,83 @@
1
+ export type BrowserRecoveryKind =
2
+ | 'stale-tab'
3
+ | 'browser-disconnected'
4
+ | 'unknown';
5
+
6
+ export function classifyBrowserFailure(error: unknown): BrowserRecoveryKind {
7
+ const message = error instanceof Error ? error.message : String(error);
8
+ // Do not treat a generic "element not found" / selector miss as a stale
9
+ // browser target. Those are normal locator failures and must return the
10
+ // page evidence to the model instead of replaying the same action.
11
+ if (
12
+ /\b(?:tab|target|frame)\b.*(?:closed|crashed|detached|destroyed|not found|invalid)/i.test(
13
+ message,
14
+ ) ||
15
+ /execution context.*(?:destroyed|not found|closed)/i.test(message) ||
16
+ /(?:page|window).*closed/i.test(message)
17
+ )
18
+ return 'stale-tab';
19
+ if (
20
+ /cdp|socket|websocket|daemon|connection|disconnected|closed/i.test(message)
21
+ )
22
+ return 'browser-disconnected';
23
+ return 'unknown';
24
+ }
25
+
26
+ /**
27
+ * agent-browser reports many daemon/target failures as an exit code instead
28
+ * of rejecting the runner promise. Convert only those known recoverable
29
+ * failures into exceptions so the runtime recovery wrapper can see them;
30
+ * ordinary selector/business errors must remain normal tool results.
31
+ */
32
+ export function recoverableAgentBrowserRunFailure(result: {
33
+ code: number;
34
+ stdout?: string;
35
+ stderr?: string;
36
+ }): Error | undefined {
37
+ if (result.code === 0) return undefined;
38
+ const message =
39
+ result.stderr?.trim() || result.stdout?.trim() || `exit ${result.code}`;
40
+ const error = new Error(message);
41
+ return classifyBrowserFailure(error) === 'unknown' ? undefined : error;
42
+ }
43
+
44
+ export type BrowserRecoveryHooks<T> = {
45
+ reconnect?: () => Promise<void>;
46
+ rebindTab?: () => Promise<void>;
47
+ maxAttempts?: number;
48
+ signal?: AbortSignal;
49
+ };
50
+
51
+ /** Retry only recoverable browser failures, rebuilding the connection/tab before retrying. */
52
+ export async function withBrowserRecovery<T>(
53
+ operation: () => Promise<T>,
54
+ hooks: BrowserRecoveryHooks<T> = {},
55
+ ): Promise<T> {
56
+ const maxAttempts = Math.max(1, Math.min(hooks.maxAttempts ?? 3, 4));
57
+ let attempt = 0;
58
+ let recoveredConnection = false;
59
+ let reboundTab = false;
60
+ while (true) {
61
+ if (hooks.signal?.aborted)
62
+ throw hooks.signal.reason ?? new Error('浏览器任务已中断');
63
+ try {
64
+ return await operation();
65
+ } catch (error) {
66
+ attempt += 1;
67
+ const kind = classifyBrowserFailure(error);
68
+ if (kind === 'unknown' || attempt >= maxAttempts) throw error;
69
+ if (kind === 'browser-disconnected' && !recoveredConnection) {
70
+ await hooks.reconnect?.();
71
+ recoveredConnection = true;
72
+ reboundTab = false;
73
+ continue;
74
+ }
75
+ if (kind === 'stale-tab' && !reboundTab) {
76
+ await hooks.rebindTab?.();
77
+ reboundTab = true;
78
+ continue;
79
+ }
80
+ throw error;
81
+ }
82
+ }
83
+ }
@@ -26,8 +26,8 @@ export function isLikelyIrreversibleBrowserTarget(label) {
26
26
  const normalized = label.replace(/\s+/g, ' ').trim();
27
27
  if (!normalized)
28
28
  return false;
29
- return /(?:^|[\s"'=:_-])(?:publish|submit|purchase|buy now|place order|delete|confirm delete|send)(?:$|[\s"'=:_-])/i.test(normalized)
30
- || /(?:发布|发表|提交|立即购买|确认购买|确认下单|删除|确认删除|发送)(?:$|[\s::))】\]])/.test(normalized);
29
+ return (/(?:^|[\s"'=:_-])(?:publish|submit|purchase|buy now|place order|delete|confirm delete|send)(?:$|[\s"'=:_-])/i.test(normalized) ||
30
+ /(?:发布|发表|提交|立即购买|确认购买|确认下单|删除|确认删除|发送)(?:$|[\s::))】\]])/.test(normalized));
31
31
  }
32
32
  /**
33
33
  * Process-wide ownership registry for the shared browser profile.
@@ -40,6 +40,7 @@ export class BrowserRunRegistry {
40
40
  runs = new Map();
41
41
  conversationTabs = new Map();
42
42
  tabOwners = new Map();
43
+ tabMetadata = new Map();
43
44
  ensure(input) {
44
45
  const key = this.key(input.conversationKey, input.runId ?? 'workbench');
45
46
  const existing = this.runs.get(key);
@@ -57,7 +58,9 @@ export class BrowserRunRegistry {
57
58
  runId: input.runId ?? randomUUID(),
58
59
  profileId: input.profileId ?? 'default',
59
60
  claimedTabIds: [],
60
- grants: [...new Set(input.grants ?? ['observe', 'interact'])],
61
+ grants: [
62
+ ...new Set(input.grants ?? ['observe', 'interact']),
63
+ ],
61
64
  allowedUploadRoots: [...new Set(input.allowedUploadRoots ?? [])],
62
65
  ...(input.taskGrantId ? { taskGrantId: input.taskGrantId } : {}),
63
66
  };
@@ -75,6 +78,19 @@ export class BrowserRunRegistry {
75
78
  ownerOf(tabId) {
76
79
  return this.tabOwners.get(tabId);
77
80
  }
81
+ markTab(context, tabId, metadata) {
82
+ this.assertTab(context, tabId);
83
+ const next = {
84
+ tabId,
85
+ ...metadata,
86
+ updatedAt: Date.now(),
87
+ };
88
+ this.tabMetadata.set(tabId, next);
89
+ return next;
90
+ }
91
+ tabInfo(tabId) {
92
+ return this.tabMetadata.get(tabId);
93
+ }
78
94
  claimTab(context, tabId) {
79
95
  const owner = this.tabOwners.get(tabId);
80
96
  if (owner && owner !== context.conversationKey) {
@@ -103,10 +119,26 @@ export class BrowserRunRegistry {
103
119
  this.runs.delete(key);
104
120
  }
105
121
  }
122
+ /**
123
+ * Invalidate every tab lease after the browser process is rebuilt. Tab ids
124
+ * are process-local and can be reused by the replacement daemon; retaining
125
+ * old owners would either block the new owner or route it across sessions.
126
+ * Run contexts remain alive so the active task can claim a replacement tab.
127
+ */
128
+ invalidateAllTabs() {
129
+ this.tabOwners.clear();
130
+ this.conversationTabs.clear();
131
+ this.tabMetadata.clear();
132
+ for (const context of this.runs.values()) {
133
+ context.claimedTabIds = [];
134
+ context.activeTabId = undefined;
135
+ }
136
+ }
106
137
  releaseTab(conversationKey, tabId) {
107
138
  if (this.tabOwners.get(tabId) !== conversationKey)
108
139
  return;
109
140
  this.tabOwners.delete(tabId);
141
+ this.tabMetadata.delete(tabId);
110
142
  const claimed = this.conversationTabs.get(conversationKey);
111
143
  claimed?.delete(tabId);
112
144
  if (claimed?.size === 0)