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
@@ -10,22 +10,32 @@ const CAPTCHA_PATTERNS = [
10
10
  /press\s+&?\s*hold|click\s+to\s+verify|verifying\s+you/i,
11
11
  /点击验证|按住滑块|图片验证|行为验证/i,
12
12
  ];
13
- const OTP_PATTERNS = [/一次性密码|短信验证码|邮件验证码|动态口令|\botp\b|one[- ]time (?:password|code)/i];
13
+ const OTP_PATTERNS = [
14
+ /一次性密码|短信验证码|邮件验证码|动态口令|\botp\b|one[- ]time (?:password|code)/i,
15
+ ];
14
16
  const PASSWORD_PATTERNS = [
15
17
  /请输入.{0,12}(?:密码|口令)|(?:密码|口令).{0,12}(?:必填|不能为空)/i,
16
18
  /(?:enter|type).{0,20}(?:password|passcode)|(?:password|passcode).{0,20}(?:required|cannot be empty)/i,
17
19
  ];
18
- const QR_LOGIN_PATTERNS = [/扫码登录|二维码登录|scan (?:the )?qr|qr code.{0,20}(?:login|sign in)/i];
19
- const LOGIN_PATTERNS = [/请先登录|登录后继续|扫码登录|sign in to continue|log in to continue/i,
20
+ const QR_LOGIN_PATTERNS = [
21
+ /扫码登录|二维码登录|scan (?:the )?qr|qr code.{0,20}(?:login|sign in)/i,
22
+ ];
23
+ const LOGIN_PATTERNS = [
24
+ /请先登录|登录后继续|扫码登录|sign in to continue|log in to continue/i,
20
25
  /please\s+(?:log|sign)\s+in/i,
21
26
  ];
22
- const BLOCK_PATTERNS = [/操作存在异常|访问请求.{0,8}(暂停|拦截)|请求过于频繁|访问受限|access denied|unusual traffic|temporarily blocked/i,
27
+ const BLOCK_PATTERNS = [
28
+ /操作存在异常|访问请求.{0,8}(暂停|拦截)|请求过于频繁|访问受限|access denied|unusual traffic|temporarily blocked/i,
23
29
  // 403/429 必须落在 HTTP/错误语境里再判风控,避免把「售价 $429」「forbidden fruit」这类正常正文误判成拦截。
24
30
  /rate\s+limit(?:ed)?|too\s+many\s+requests|\b403\s+forbidden\b|http[^\n]{0,10}\b(?:403|429)\b|\b(?:error|status)\s*(?:code)?\s*[::]?\s*(?:403|429)\b/i,
25
31
  /被封禁|风控拦截|系统异常/i,
26
32
  ];
27
- const PERMISSION_PATTERNS = [/需要授权|请求权限|permission required|allow access/i];
28
- const SECURITY_WARNING_PATTERNS = [/安全警告|风险提示|security warning|untrusted (?:application|site|publisher)/i];
33
+ const PERMISSION_PATTERNS = [
34
+ /需要授权|请求权限|permission required|allow access/i,
35
+ ];
36
+ const SECURITY_WARNING_PATTERNS = [
37
+ /安全警告|风险提示|security warning|untrusted (?:application|site|publisher)/i,
38
+ ];
29
39
  /** Classifies only explicit page signals. It never attempts to solve or bypass a challenge. */
30
40
  export function detectBrowserChallenge(text) {
31
41
  const normalized = text.replace(/\s+/g, ' ').slice(0, 200_000);
@@ -47,6 +57,18 @@ export function detectBrowserChallenge(text) {
47
57
  return { reason: 'permission', summary: '页面需要人工确认权限' };
48
58
  return null;
49
59
  }
60
+ /**
61
+ * 判定「持有者已经死了」的阈值。
62
+ *
63
+ * 取值有依据,不是拍脑袋:单次动作的 `timeout` 在 zod schema 里是 `.max(300_000)`,
64
+ * 语义是**整个序列**(前置探测 + 动作 + 收尾重探)的总时长。所以任何合法的 running
65
+ * 都有 300s 的硬上界,取 2× 作阈值 —— 合法长动作绝不会被误接管,而泄漏的锁在一个
66
+ * 动作周期后自动可用,不再是「本进程剩余生命周期内浏览器永久不可用」。
67
+ *
68
+ * ⚠️ 改小之前先确认 webbrowser.ts 里 preTimer 与 runSequence 各自的计时器在最长路径上
69
+ * 不会叠加;`browserControl.test.ts` 用假时钟锁住了这个不变量的行为面。
70
+ */
71
+ export const DEFAULT_STALE_AFTER_MS = 600_000;
50
72
  export class BrowserControlCoordinator {
51
73
  state = { owner: 'none', status: 'idle' };
52
74
  listeners = new Set();
@@ -55,11 +77,29 @@ export class BrowserControlCoordinator {
55
77
  interventions = new HumanInterventionCoordinator();
56
78
  defaultOwnerId;
57
79
  defaultConversationKey;
80
+ staleAfterMs;
81
+ now;
82
+ onStaleTakeover;
83
+ /** 进入 running 的时刻;由 setState 统一维护,所以 resume 之后也会重新计时。 */
84
+ runningSince = 0;
58
85
  constructor(input = {}) {
59
86
  this.defaultOwnerId = input.ownerId ?? 'local-owner';
60
87
  this.defaultConversationKey = input.conversationKey ?? 'browser:default';
88
+ this.now = input.now ?? (() => Date.now());
89
+ this.staleAfterMs = input.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
90
+ // 接管**绝不能静默**:不留痕的话,「这把锁曾经泄漏过」就再也查不到了,
91
+ // 而下一次排查会从一个已经自愈的现场开始 —— 那比报错更难查。
92
+ this.onStaleTakeover =
93
+ input.onStaleTakeover ??
94
+ ((info) => {
95
+ process.stderr.write(`[browser] 上一个持有者(runId=${info.previousRunId ?? '?'}` +
96
+ `${info.previousAction ? `, action=${info.previousAction}` : ''})` +
97
+ `已持锁 ${Math.round(info.heldMs / 1000)}s,超过 ${Math.round(this.staleAfterMs / 1000)}s 判为泄漏,已接管。\n`);
98
+ });
99
+ }
100
+ getState() {
101
+ return this.state;
61
102
  }
62
- getState() { return this.state; }
63
103
  subscribe(listener) {
64
104
  this.listeners.add(listener);
65
105
  listener(this.state);
@@ -75,11 +115,24 @@ export class BrowserControlCoordinator {
75
115
  throw new Error('浏览器正在等待另一个任务的人工处理');
76
116
  }
77
117
  if (this.state.status === 'running' && this.state.runId !== input.runId) {
78
- // 带上持锁者身份:这把锁是进程级单例,一旦泄漏,后续每次调用都撞同一句话,
79
- // 而"另一个会话"是谁、卡在哪个动作上,现场早就没了 —— 排障只能靠猜。
80
- throw new Error(`浏览器正在执行另一个会话的原子操作(持有者 runId=${this.state.runId ?? '?'}`
81
- + `${this.state.action ? `, action=${this.state.action}` : ''}`
82
- + `${this.activeIdentity?.conversationKey ? `, conversation=${this.activeIdentity.conversationKey}` : ''})`);
118
+ const heldMs = this.now() - this.runningSince;
119
+ if (heldMs < this.staleAfterMs) {
120
+ // 带上持锁者身份与已持有时长:这把锁是进程级单例,一旦泄漏,后续每次调用都撞
121
+ // 同一句话,而"另一个会话"是谁、卡在哪个动作上、卡了多久,现场早就没了。
122
+ // 时长尤其关键 —— 它把「真并发」和「已泄漏」在报错里一眼分开。
123
+ throw new Error(`浏览器正在执行另一个会话的原子操作(持有者 runId=${this.state.runId ?? '?'}` +
124
+ `${this.state.action ? `, action=${this.state.action}` : ''}` +
125
+ `${this.activeIdentity?.conversationKey ? `, conversation=${this.activeIdentity.conversationKey}` : ''}` +
126
+ `, 已持有 ${Math.round(heldMs / 1000)}s)`);
127
+ }
128
+ // 超过任何合法动作的上界 —— 持有者的 finally 没能跑(进程被外部掐断、
129
+ // finally 自身抛错、或将来某条还没被想到的路径)。不接管的话,这个进程里的
130
+ // 浏览器就永久不可用了。接管并留痕,让「失败一次」不再等于「彻底报废」。
131
+ this.onStaleTakeover({
132
+ previousRunId: this.state.runId,
133
+ previousAction: this.state.action,
134
+ heldMs,
135
+ });
83
136
  }
84
137
  this.activeIdentity = {
85
138
  ownerId: this.defaultOwnerId,
@@ -103,11 +156,14 @@ export class BrowserControlCoordinator {
103
156
  const running = this.state.status === 'running' ? this.state : null;
104
157
  const identity = {
105
158
  ownerId: input?.ownerId ?? this.activeIdentity?.ownerId ?? this.defaultOwnerId,
106
- conversationKey: input?.conversationKey
107
- ?? this.activeIdentity?.conversationKey
108
- ?? running?.conversationKey
109
- ?? this.defaultConversationKey,
110
- runId: input?.runId ?? this.activeIdentity?.runId ?? running?.runId ?? randomUUID(),
159
+ conversationKey: input?.conversationKey ??
160
+ this.activeIdentity?.conversationKey ??
161
+ running?.conversationKey ??
162
+ this.defaultConversationKey,
163
+ runId: input?.runId ??
164
+ this.activeIdentity?.runId ??
165
+ running?.runId ??
166
+ randomUUID(),
111
167
  };
112
168
  const intervention = this.interventions.request({
113
169
  ...identity,
@@ -135,21 +191,37 @@ export class BrowserControlCoordinator {
135
191
  }
136
192
  this.activeIntervention = null;
137
193
  this.activeIdentity = null;
138
- this.setState({ owner: 'user', status: 'interactive', runId: input?.runId, conversationKey: input?.conversationKey });
194
+ this.setState({
195
+ owner: 'user',
196
+ status: 'interactive',
197
+ runId: input?.runId,
198
+ conversationKey: input?.conversationKey,
199
+ });
139
200
  }
140
201
  resume(token) {
141
- if (this.state.status !== 'waiting_human' || !token || !this.activeIntervention || !this.activeIdentity)
202
+ if (this.state.status !== 'waiting_human' ||
203
+ !token ||
204
+ !this.activeIntervention ||
205
+ !this.activeIdentity)
142
206
  return false;
143
207
  const previous = this.state;
144
208
  const ok = this.interventions.resume(this.credential(this.activeIntervention, this.activeIdentity, token));
145
209
  if (!ok)
146
210
  return false;
147
211
  this.activeIntervention = null;
148
- this.setState({ owner: 'agent', status: 'running', runId: previous.runId, conversationKey: previous.conversationKey, action: '正在检查人工操作结果' });
212
+ this.setState({
213
+ owner: 'agent',
214
+ status: 'running',
215
+ runId: previous.runId,
216
+ conversationKey: previous.conversationKey,
217
+ action: '正在检查人工操作结果',
218
+ });
149
219
  return true;
150
220
  }
151
221
  resumeIntervention(credential) {
152
- if (this.state.status !== 'waiting_human' || !this.activeIntervention || !this.activeIdentity)
222
+ if (this.state.status !== 'waiting_human' ||
223
+ !this.activeIntervention ||
224
+ !this.activeIdentity)
153
225
  return false;
154
226
  const previous = this.state;
155
227
  if (!this.interventions.resume(credential))
@@ -179,7 +251,9 @@ export class BrowserControlCoordinator {
179
251
  return true;
180
252
  }
181
253
  cancelIntervention(credential) {
182
- if (this.state.status !== 'waiting_human' || !this.activeIntervention || !this.activeIdentity)
254
+ if (this.state.status !== 'waiting_human' ||
255
+ !this.activeIntervention ||
256
+ !this.activeIdentity)
183
257
  return false;
184
258
  if (!this.interventions.cancel(credential))
185
259
  return false;
@@ -195,12 +269,26 @@ export class BrowserControlCoordinator {
195
269
  if (!intervention)
196
270
  return Promise.resolve('stopped');
197
271
  const waiter = { interventionId: intervention.id };
198
- return this.interventions.wait(waiter.interventionId, signal).then((resolution) => (resolution.status === 'resumed' ? 'resumed' : 'stopped'));
272
+ return this.interventions
273
+ .wait(waiter.interventionId, signal)
274
+ .then((resolution) => resolution.status === 'resumed' ? 'resumed' : 'stopped');
199
275
  }
200
276
  setState(state) {
277
+ // 所有进入 running 的路径都在这里重新计时(beginAgent 之外,还有 resume /
278
+ // resumeIntervention 从人工处理回到 agent 手里)。⚠️ 少了这一条,人工处理
279
+ // 花掉的时间会被算进持锁时长 —— 用户认真填了十分钟验证码,恢复的瞬间就被判成
280
+ // 「泄漏」让别人接管,正好惩罚了最该被保护的那条路径。
281
+ if (state.status === 'running')
282
+ this.runningSince = this.now();
201
283
  this.state = state;
202
- for (const listener of this.listeners)
203
- listener(state);
284
+ for (const listener of this.listeners) {
285
+ try {
286
+ listener(state);
287
+ }
288
+ catch {
289
+ // Observers are telemetry/UI extensions; control state must remain authoritative.
290
+ }
291
+ }
204
292
  }
205
293
  credential(intervention, identity, resumeToken = intervention.resumeToken) {
206
294
  return {
@@ -214,14 +302,23 @@ export class BrowserControlCoordinator {
214
302
  }
215
303
  function summaryForReason(reason) {
216
304
  switch (reason) {
217
- case 'captcha': return '页面需要人工完成安全验证';
218
- case 'otp': return '页面需要人工输入一次性验证码';
219
- case 'password': return '页面需要人工输入密码';
220
- case 'qr_login': return '页面需要人工扫码登录';
221
- case 'login': return '页面需要人工登录';
222
- case 'permission': return '页面需要人工确认权限';
223
- case 'security_warning': return '页面需要人工处理安全警告';
224
- case 'blocked': return '网站已暂停或拦截当前访问';
225
- case 'manual': return '用户已接管浏览器';
305
+ case 'captcha':
306
+ return '页面需要人工完成安全验证';
307
+ case 'otp':
308
+ return '页面需要人工输入一次性验证码';
309
+ case 'password':
310
+ return '页面需要人工输入密码';
311
+ case 'qr_login':
312
+ return '页面需要人工扫码登录';
313
+ case 'login':
314
+ return '页面需要人工登录';
315
+ case 'permission':
316
+ return '页面需要人工确认权限';
317
+ case 'security_warning':
318
+ return '页面需要人工处理安全警告';
319
+ case 'blocked':
320
+ return '网站已暂停或拦截当前访问';
321
+ case 'manual':
322
+ return '用户已接管浏览器';
226
323
  }
227
324
  }
@@ -39,34 +39,52 @@ const CAPTCHA_PATTERNS = [
39
39
  /press\s+&?\s*hold|click\s+to\s+verify|verifying\s+you/i,
40
40
  /点击验证|按住滑块|图片验证|行为验证/i,
41
41
  ];
42
- const OTP_PATTERNS = [/一次性密码|短信验证码|邮件验证码|动态口令|\botp\b|one[- ]time (?:password|code)/i];
42
+ const OTP_PATTERNS = [
43
+ /一次性密码|短信验证码|邮件验证码|动态口令|\botp\b|one[- ]time (?:password|code)/i,
44
+ ];
43
45
  const PASSWORD_PATTERNS = [
44
46
  /请输入.{0,12}(?:密码|口令)|(?:密码|口令).{0,12}(?:必填|不能为空)/i,
45
47
  /(?:enter|type).{0,20}(?:password|passcode)|(?:password|passcode).{0,20}(?:required|cannot be empty)/i,
46
48
  ];
47
- const QR_LOGIN_PATTERNS = [/扫码登录|二维码登录|scan (?:the )?qr|qr code.{0,20}(?:login|sign in)/i];
48
- const LOGIN_PATTERNS = [/请先登录|登录后继续|扫码登录|sign in to continue|log in to continue/i,
49
+ const QR_LOGIN_PATTERNS = [
50
+ /扫码登录|二维码登录|scan (?:the )?qr|qr code.{0,20}(?:login|sign in)/i,
51
+ ];
52
+ const LOGIN_PATTERNS = [
53
+ /请先登录|登录后继续|扫码登录|sign in to continue|log in to continue/i,
49
54
  /please\s+(?:log|sign)\s+in/i,
50
55
  ];
51
- const BLOCK_PATTERNS = [/操作存在异常|访问请求.{0,8}(暂停|拦截)|请求过于频繁|访问受限|access denied|unusual traffic|temporarily blocked/i,
56
+ const BLOCK_PATTERNS = [
57
+ /操作存在异常|访问请求.{0,8}(暂停|拦截)|请求过于频繁|访问受限|access denied|unusual traffic|temporarily blocked/i,
52
58
  // 403/429 必须落在 HTTP/错误语境里再判风控,避免把「售价 $429」「forbidden fruit」这类正常正文误判成拦截。
53
59
  /rate\s+limit(?:ed)?|too\s+many\s+requests|\b403\s+forbidden\b|http[^\n]{0,10}\b(?:403|429)\b|\b(?:error|status)\s*(?:code)?\s*[::]?\s*(?:403|429)\b/i,
54
60
  /被封禁|风控拦截|系统异常/i,
55
61
  ];
56
- const PERMISSION_PATTERNS = [/需要授权|请求权限|permission required|allow access/i];
57
- const SECURITY_WARNING_PATTERNS = [/安全警告|风险提示|security warning|untrusted (?:application|site|publisher)/i];
62
+ const PERMISSION_PATTERNS = [
63
+ /需要授权|请求权限|permission required|allow access/i,
64
+ ];
65
+ const SECURITY_WARNING_PATTERNS = [
66
+ /安全警告|风险提示|security warning|untrusted (?:application|site|publisher)/i,
67
+ ];
58
68
 
59
69
  /** Classifies only explicit page signals. It never attempts to solve or bypass a challenge. */
60
70
  export function detectBrowserChallenge(text: string): BrowserChallenge | null {
61
71
  const normalized = text.replace(/\s+/g, ' ').slice(0, 200_000);
62
- if (OTP_PATTERNS.some((pattern) => pattern.test(normalized))) return { reason: 'otp', summary: '页面需要人工输入一次性验证码' };
63
- if (CAPTCHA_PATTERNS.some((pattern) => pattern.test(normalized))) return { reason: 'captcha', summary: '页面需要人工完成安全验证' };
64
- if (QR_LOGIN_PATTERNS.some((pattern) => pattern.test(normalized))) return { reason: 'qr_login', summary: '页面需要人工扫码登录' };
65
- if (PASSWORD_PATTERNS.some((pattern) => pattern.test(normalized))) return { reason: 'password', summary: '页面需要人工输入密码' };
66
- if (LOGIN_PATTERNS.some((pattern) => pattern.test(normalized))) return { reason: 'login', summary: '页面需要人工登录' };
67
- if (BLOCK_PATTERNS.some((pattern) => pattern.test(normalized))) return { reason: 'blocked', summary: '网站已暂停或拦截当前访问' };
68
- if (SECURITY_WARNING_PATTERNS.some((pattern) => pattern.test(normalized))) return { reason: 'security_warning', summary: '页面需要人工处理安全警告' };
69
- if (PERMISSION_PATTERNS.some((pattern) => pattern.test(normalized))) return { reason: 'permission', summary: '页面需要人工确认权限' };
72
+ if (OTP_PATTERNS.some((pattern) => pattern.test(normalized)))
73
+ return { reason: 'otp', summary: '页面需要人工输入一次性验证码' };
74
+ if (CAPTCHA_PATTERNS.some((pattern) => pattern.test(normalized)))
75
+ return { reason: 'captcha', summary: '页面需要人工完成安全验证' };
76
+ if (QR_LOGIN_PATTERNS.some((pattern) => pattern.test(normalized)))
77
+ return { reason: 'qr_login', summary: '页面需要人工扫码登录' };
78
+ if (PASSWORD_PATTERNS.some((pattern) => pattern.test(normalized)))
79
+ return { reason: 'password', summary: '页面需要人工输入密码' };
80
+ if (LOGIN_PATTERNS.some((pattern) => pattern.test(normalized)))
81
+ return { reason: 'login', summary: '页面需要人工登录' };
82
+ if (BLOCK_PATTERNS.some((pattern) => pattern.test(normalized)))
83
+ return { reason: 'blocked', summary: '网站已暂停或拦截当前访问' };
84
+ if (SECURITY_WARNING_PATTERNS.some((pattern) => pattern.test(normalized)))
85
+ return { reason: 'security_warning', summary: '页面需要人工处理安全警告' };
86
+ if (PERMISSION_PATTERNS.some((pattern) => pattern.test(normalized)))
87
+ return { reason: 'permission', summary: '页面需要人工确认权限' };
70
88
  return null;
71
89
  }
72
90
 
@@ -74,6 +92,25 @@ type HumanWaiter = {
74
92
  interventionId: string;
75
93
  };
76
94
 
95
+ /**
96
+ * 判定「持有者已经死了」的阈值。
97
+ *
98
+ * 取值有依据,不是拍脑袋:单次动作的 `timeout` 在 zod schema 里是 `.max(300_000)`,
99
+ * 语义是**整个序列**(前置探测 + 动作 + 收尾重探)的总时长。所以任何合法的 running
100
+ * 都有 300s 的硬上界,取 2× 作阈值 —— 合法长动作绝不会被误接管,而泄漏的锁在一个
101
+ * 动作周期后自动可用,不再是「本进程剩余生命周期内浏览器永久不可用」。
102
+ *
103
+ * ⚠️ 改小之前先确认 webbrowser.ts 里 preTimer 与 runSequence 各自的计时器在最长路径上
104
+ * 不会叠加;`browserControl.test.ts` 用假时钟锁住了这个不变量的行为面。
105
+ */
106
+ export const DEFAULT_STALE_AFTER_MS = 600_000;
107
+
108
+ export interface StaleTakeover {
109
+ previousRunId?: string;
110
+ previousAction?: string;
111
+ heldMs: number;
112
+ }
113
+
77
114
  export class BrowserControlCoordinator {
78
115
  private state: BrowserControlState = { owner: 'none', status: 'idle' };
79
116
  private listeners = new Set<(state: BrowserControlState) => void>();
@@ -82,13 +119,42 @@ export class BrowserControlCoordinator {
82
119
  private readonly interventions = new HumanInterventionCoordinator();
83
120
  private readonly defaultOwnerId: string;
84
121
  private readonly defaultConversationKey: string;
122
+ private readonly staleAfterMs: number;
123
+ private readonly now: () => number;
124
+ private readonly onStaleTakeover: (info: StaleTakeover) => void;
125
+ /** 进入 running 的时刻;由 setState 统一维护,所以 resume 之后也会重新计时。 */
126
+ private runningSince = 0;
85
127
 
86
- constructor(input: { ownerId?: string; conversationKey?: string } = {}) {
128
+ constructor(
129
+ input: {
130
+ ownerId?: string;
131
+ conversationKey?: string;
132
+ /** 注入时钟:让「超过阈值」在测试里用假时钟验证,而不是真等 10 分钟。 */
133
+ now?: () => number;
134
+ staleAfterMs?: number;
135
+ onStaleTakeover?: (info: StaleTakeover) => void;
136
+ } = {},
137
+ ) {
87
138
  this.defaultOwnerId = input.ownerId ?? 'local-owner';
88
139
  this.defaultConversationKey = input.conversationKey ?? 'browser:default';
140
+ this.now = input.now ?? (() => Date.now());
141
+ this.staleAfterMs = input.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
142
+ // 接管**绝不能静默**:不留痕的话,「这把锁曾经泄漏过」就再也查不到了,
143
+ // 而下一次排查会从一个已经自愈的现场开始 —— 那比报错更难查。
144
+ this.onStaleTakeover =
145
+ input.onStaleTakeover ??
146
+ ((info) => {
147
+ process.stderr.write(
148
+ `[browser] 上一个持有者(runId=${info.previousRunId ?? '?'}` +
149
+ `${info.previousAction ? `, action=${info.previousAction}` : ''})` +
150
+ `已持锁 ${Math.round(info.heldMs / 1000)}s,超过 ${Math.round(this.staleAfterMs / 1000)}s 判为泄漏,已接管。\n`,
151
+ );
152
+ });
89
153
  }
90
154
 
91
- getState(): BrowserControlState { return this.state; }
155
+ getState(): BrowserControlState {
156
+ return this.state;
157
+ }
92
158
 
93
159
  subscribe(listener: (state: BrowserControlState) => void): () => void {
94
160
  this.listeners.add(listener);
@@ -96,23 +162,42 @@ export class BrowserControlCoordinator {
96
162
  return () => this.listeners.delete(listener);
97
163
  }
98
164
 
99
- subscribeInterventions(listener: (event: HumanInterventionEvent) => void): () => void {
165
+ subscribeInterventions(
166
+ listener: (event: HumanInterventionEvent) => void,
167
+ ): () => void {
100
168
  return this.interventions.subscribe(listener);
101
169
  }
102
170
 
103
- beginAgent(input: { runId: string; conversationKey?: string; action?: string }): void {
171
+ beginAgent(input: {
172
+ runId: string;
173
+ conversationKey?: string;
174
+ action?: string;
175
+ }): void {
104
176
  if (this.state.status === 'waiting_human') {
105
177
  if (this.state.runId === input.runId) return;
106
178
  throw new Error('浏览器正在等待另一个任务的人工处理');
107
179
  }
108
180
  if (this.state.status === 'running' && this.state.runId !== input.runId) {
109
- // 带上持锁者身份:这把锁是进程级单例,一旦泄漏,后续每次调用都撞同一句话,
110
- // 而"另一个会话"是谁、卡在哪个动作上,现场早就没了 —— 排障只能靠猜。
111
- throw new Error(
112
- `浏览器正在执行另一个会话的原子操作(持有者 runId=${this.state.runId ?? '?'}`
113
- + `${this.state.action ? `, action=${this.state.action}` : ''}`
114
- + `${this.activeIdentity?.conversationKey ? `, conversation=${this.activeIdentity.conversationKey}` : ''})`,
115
- );
181
+ const heldMs = this.now() - this.runningSince;
182
+ if (heldMs < this.staleAfterMs) {
183
+ // 带上持锁者身份与已持有时长:这把锁是进程级单例,一旦泄漏,后续每次调用都撞
184
+ // 同一句话,而"另一个会话"是谁、卡在哪个动作上、卡了多久,现场早就没了。
185
+ // 时长尤其关键 —— 它把「真并发」和「已泄漏」在报错里一眼分开。
186
+ throw new Error(
187
+ `浏览器正在执行另一个会话的原子操作(持有者 runId=${this.state.runId ?? '?'}` +
188
+ `${this.state.action ? `, action=${this.state.action}` : ''}` +
189
+ `${this.activeIdentity?.conversationKey ? `, conversation=${this.activeIdentity.conversationKey}` : ''}` +
190
+ `, 已持有 ${Math.round(heldMs / 1000)}s)`,
191
+ );
192
+ }
193
+ // 超过任何合法动作的上界 —— 持有者的 finally 没能跑(进程被外部掐断、
194
+ // finally 自身抛错、或将来某条还没被想到的路径)。不接管的话,这个进程里的
195
+ // 浏览器就永久不可用了。接管并留痕,让「失败一次」不再等于「彻底报废」。
196
+ this.onStaleTakeover({
197
+ previousRunId: this.state.runId,
198
+ previousAction: this.state.action,
199
+ heldMs,
200
+ });
116
201
  }
117
202
  this.activeIdentity = {
118
203
  ownerId: this.defaultOwnerId,
@@ -131,20 +216,32 @@ export class BrowserControlCoordinator {
131
216
 
132
217
  requestHuman(
133
218
  reason: BrowserHumanReason,
134
- input?: { runId?: string; conversationKey?: string; ownerId?: string; summary?: string },
219
+ input?: {
220
+ runId?: string;
221
+ conversationKey?: string;
222
+ ownerId?: string;
223
+ summary?: string;
224
+ },
135
225
  ): string {
136
226
  if (this.state.status === 'waiting_human') {
137
- if (!input?.runId || this.state.runId === input.runId) return this.state.resumeToken;
227
+ if (!input?.runId || this.state.runId === input.runId)
228
+ return this.state.resumeToken;
138
229
  throw new Error('另一个浏览器任务正在等待人工处理');
139
230
  }
140
231
  const running = this.state.status === 'running' ? this.state : null;
141
232
  const identity: HumanInterventionIdentity = {
142
- ownerId: input?.ownerId ?? this.activeIdentity?.ownerId ?? this.defaultOwnerId,
143
- conversationKey: input?.conversationKey
144
- ?? this.activeIdentity?.conversationKey
145
- ?? running?.conversationKey
146
- ?? this.defaultConversationKey,
147
- runId: input?.runId ?? this.activeIdentity?.runId ?? running?.runId ?? randomUUID(),
233
+ ownerId:
234
+ input?.ownerId ?? this.activeIdentity?.ownerId ?? this.defaultOwnerId,
235
+ conversationKey:
236
+ input?.conversationKey ??
237
+ this.activeIdentity?.conversationKey ??
238
+ running?.conversationKey ??
239
+ this.defaultConversationKey,
240
+ runId:
241
+ input?.runId ??
242
+ this.activeIdentity?.runId ??
243
+ running?.runId ??
244
+ randomUUID(),
148
245
  };
149
246
  const intervention = this.interventions.request({
150
247
  ...identity,
@@ -169,25 +266,51 @@ export class BrowserControlCoordinator {
169
266
 
170
267
  takeInteractive(input?: { runId?: string; conversationKey?: string }): void {
171
268
  if (this.activeIntervention && this.activeIdentity) {
172
- this.interventions.cancel(this.credential(this.activeIntervention, this.activeIdentity));
269
+ this.interventions.cancel(
270
+ this.credential(this.activeIntervention, this.activeIdentity),
271
+ );
173
272
  }
174
273
  this.activeIntervention = null;
175
274
  this.activeIdentity = null;
176
- this.setState({ owner: 'user', status: 'interactive', runId: input?.runId, conversationKey: input?.conversationKey });
275
+ this.setState({
276
+ owner: 'user',
277
+ status: 'interactive',
278
+ runId: input?.runId,
279
+ conversationKey: input?.conversationKey,
280
+ });
177
281
  }
178
282
 
179
283
  resume(token?: string): boolean {
180
- if (this.state.status !== 'waiting_human' || !token || !this.activeIntervention || !this.activeIdentity) return false;
284
+ if (
285
+ this.state.status !== 'waiting_human' ||
286
+ !token ||
287
+ !this.activeIntervention ||
288
+ !this.activeIdentity
289
+ )
290
+ return false;
181
291
  const previous = this.state;
182
- const ok = this.interventions.resume(this.credential(this.activeIntervention, this.activeIdentity, token));
292
+ const ok = this.interventions.resume(
293
+ this.credential(this.activeIntervention, this.activeIdentity, token),
294
+ );
183
295
  if (!ok) return false;
184
296
  this.activeIntervention = null;
185
- this.setState({ owner: 'agent', status: 'running', runId: previous.runId, conversationKey: previous.conversationKey, action: '正在检查人工操作结果' });
297
+ this.setState({
298
+ owner: 'agent',
299
+ status: 'running',
300
+ runId: previous.runId,
301
+ conversationKey: previous.conversationKey,
302
+ action: '正在检查人工操作结果',
303
+ });
186
304
  return true;
187
305
  }
188
306
 
189
307
  resumeIntervention(credential: HumanInterventionCredential): boolean {
190
- if (this.state.status !== 'waiting_human' || !this.activeIntervention || !this.activeIdentity) return false;
308
+ if (
309
+ this.state.status !== 'waiting_human' ||
310
+ !this.activeIntervention ||
311
+ !this.activeIdentity
312
+ )
313
+ return false;
191
314
  const previous = this.state;
192
315
  if (!this.interventions.resume(credential)) return false;
193
316
  this.activeIntervention = null;
@@ -204,8 +327,11 @@ export class BrowserControlCoordinator {
204
327
 
205
328
  stop(token?: string): boolean {
206
329
  if (this.state.status === 'waiting_human') {
207
- if (!token || !this.activeIntervention || !this.activeIdentity) return false;
208
- const ok = this.interventions.cancel(this.credential(this.activeIntervention, this.activeIdentity, token));
330
+ if (!token || !this.activeIntervention || !this.activeIdentity)
331
+ return false;
332
+ const ok = this.interventions.cancel(
333
+ this.credential(this.activeIntervention, this.activeIdentity, token),
334
+ );
209
335
  if (!ok) return false;
210
336
  }
211
337
  this.activeIntervention = null;
@@ -215,7 +341,12 @@ export class BrowserControlCoordinator {
215
341
  }
216
342
 
217
343
  cancelIntervention(credential: HumanInterventionCredential): boolean {
218
- if (this.state.status !== 'waiting_human' || !this.activeIntervention || !this.activeIdentity) return false;
344
+ if (
345
+ this.state.status !== 'waiting_human' ||
346
+ !this.activeIntervention ||
347
+ !this.activeIdentity
348
+ )
349
+ return false;
219
350
  if (!this.interventions.cancel(credential)) return false;
220
351
  this.activeIntervention = null;
221
352
  this.activeIdentity = null;
@@ -224,18 +355,32 @@ export class BrowserControlCoordinator {
224
355
  }
225
356
 
226
357
  waitForHuman(signal?: AbortSignal): Promise<'resumed' | 'stopped'> {
227
- if (this.state.status !== 'waiting_human') return Promise.resolve('resumed');
358
+ if (this.state.status !== 'waiting_human')
359
+ return Promise.resolve('resumed');
228
360
  const intervention = this.activeIntervention;
229
361
  if (!intervention) return Promise.resolve('stopped');
230
362
  const waiter: HumanWaiter = { interventionId: intervention.id };
231
- return this.interventions.wait(waiter.interventionId, signal).then((resolution) => (
232
- resolution.status === 'resumed' ? 'resumed' : 'stopped'
233
- ));
363
+ return this.interventions
364
+ .wait(waiter.interventionId, signal)
365
+ .then((resolution) =>
366
+ resolution.status === 'resumed' ? 'resumed' : 'stopped',
367
+ );
234
368
  }
235
369
 
236
370
  private setState(state: BrowserControlState): void {
371
+ // 所有进入 running 的路径都在这里重新计时(beginAgent 之外,还有 resume /
372
+ // resumeIntervention 从人工处理回到 agent 手里)。⚠️ 少了这一条,人工处理
373
+ // 花掉的时间会被算进持锁时长 —— 用户认真填了十分钟验证码,恢复的瞬间就被判成
374
+ // 「泄漏」让别人接管,正好惩罚了最该被保护的那条路径。
375
+ if (state.status === 'running') this.runningSince = this.now();
237
376
  this.state = state;
238
- for (const listener of this.listeners) listener(state);
377
+ for (const listener of this.listeners) {
378
+ try {
379
+ listener(state);
380
+ } catch {
381
+ // Observers are telemetry/UI extensions; control state must remain authoritative.
382
+ }
383
+ }
239
384
  }
240
385
 
241
386
  private credential(
@@ -255,14 +400,23 @@ export class BrowserControlCoordinator {
255
400
 
256
401
  function summaryForReason(reason: BrowserHumanReason): string {
257
402
  switch (reason) {
258
- case 'captcha': return '页面需要人工完成安全验证';
259
- case 'otp': return '页面需要人工输入一次性验证码';
260
- case 'password': return '页面需要人工输入密码';
261
- case 'qr_login': return '页面需要人工扫码登录';
262
- case 'login': return '页面需要人工登录';
263
- case 'permission': return '页面需要人工确认权限';
264
- case 'security_warning': return '页面需要人工处理安全警告';
265
- case 'blocked': return '网站已暂停或拦截当前访问';
266
- case 'manual': return '用户已接管浏览器';
403
+ case 'captcha':
404
+ return '页面需要人工完成安全验证';
405
+ case 'otp':
406
+ return '页面需要人工输入一次性验证码';
407
+ case 'password':
408
+ return '页面需要人工输入密码';
409
+ case 'qr_login':
410
+ return '页面需要人工扫码登录';
411
+ case 'login':
412
+ return '页面需要人工登录';
413
+ case 'permission':
414
+ return '页面需要人工确认权限';
415
+ case 'security_warning':
416
+ return '页面需要人工处理安全警告';
417
+ case 'blocked':
418
+ return '网站已暂停或拦截当前访问';
419
+ case 'manual':
420
+ return '用户已接管浏览器';
267
421
  }
268
422
  }