@xopcai/xopc 0.0.85 → 0.0.87

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 (407) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/adapters/cli-login.js +3 -3
  3. package/dist/extensions/feishu/src/adapters/cli-login.js.map +1 -1
  4. package/dist/extensions/telegram/src/delivery-chat-id.d.ts +1 -1
  5. package/dist/extensions/telegram/src/delivery-chat-id.js +1 -1
  6. package/dist/extensions/telegram/src/delivery-chat-id.js.map +1 -1
  7. package/dist/extensions/telegram/src/routing-integration.js +1 -0
  8. package/dist/extensions/telegram/src/routing-integration.js.map +1 -1
  9. package/dist/extensions/telegram/xopc.extension.json +1 -1
  10. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js +2 -2
  11. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js.map +1 -1
  12. package/dist/extensions/weixin/src/api/api.js +2 -2
  13. package/dist/extensions/weixin/src/api/api.js.map +1 -1
  14. package/dist/extensions/weixin/src/auth/accounts.js +12 -12
  15. package/dist/extensions/weixin/src/auth/accounts.js.map +1 -1
  16. package/dist/extensions/weixin/src/delivery-to.js +2 -2
  17. package/dist/extensions/weixin/src/delivery-to.js.map +1 -1
  18. package/dist/extensions/weixin/src/messaging/debug-mode.js +5 -5
  19. package/dist/extensions/weixin/src/messaging/debug-mode.js.map +1 -1
  20. package/dist/extensions/weixin/src/messaging/inbound.js +11 -11
  21. package/dist/extensions/weixin/src/messaging/inbound.js.map +1 -1
  22. package/dist/extensions/weixin/src/storage/sync-buf.js +4 -4
  23. package/dist/extensions/weixin/src/storage/sync-buf.js.map +1 -1
  24. package/dist/extensions/weixin/src/workflow-progress.d.ts +1 -1
  25. package/dist/extensions/weixin/src/workflow-progress.js.map +1 -1
  26. package/dist/gateway/static/root/assets/agents-BEAbXpuP.js +222 -0
  27. package/dist/gateway/static/root/assets/{apps-page-D7v7649T.js → apps-page-Dg8R-Szf.js} +1 -1
  28. package/dist/gateway/static/root/assets/{channels-settings-nCaMb0a7.js → channels-settings-yohw9YSu.js} +1 -1
  29. package/dist/gateway/static/root/assets/{channels-status-swr-C1gZBcJV.js → channels-status-swr-BSHqqCF1.js} +1 -1
  30. package/dist/gateway/static/root/assets/{cron-api-CoYK0hlm.js → cron-api-0h_QT8U3.js} +1 -1
  31. package/dist/gateway/static/root/assets/{cron-page-DeGo-Vjc.js → cron-page-BkfKFfFk.js} +1 -1
  32. package/dist/gateway/static/root/assets/{dist-DaK4dsss.js → dist-Cmjp2APP.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-debug-page-BZngZWbO.js → extension-debug-page-CFa9z_1N.js} +1 -1
  34. package/dist/gateway/static/root/assets/{extension-page-D6JSyV27.js → extension-page-BI8eaTPq.js} +1 -1
  35. package/dist/gateway/static/root/assets/extension-settings-page-x4BB7q1X.js +1 -0
  36. package/dist/gateway/static/root/assets/{fetch-B2MYHbWg.js → fetch-DRqwef_Q.js} +1 -1
  37. package/dist/gateway/static/root/assets/{field-primitives-Zzl22MvN.js → field-primitives-BiNHBo2Y.js} +1 -1
  38. package/dist/gateway/static/root/assets/{heartbeat-config-api-BtIcpG0O.js → heartbeat-config-api-ZRb8qhuz.js} +1 -1
  39. package/dist/gateway/static/root/assets/{index-D4vM3-P7.js → index-Cu7bKuUi.js} +96 -94
  40. package/dist/gateway/static/root/assets/index-a5gWIdZQ.css +1 -0
  41. package/dist/gateway/static/root/assets/{logs-page-_d4UJ-qQ.js → logs-page-BFZ8GgCv.js} +1 -1
  42. package/dist/gateway/static/root/assets/{sessions-page-5N4aF2Wk.js → sessions-page-CD7AfB-2.js} +1 -1
  43. package/dist/gateway/static/root/assets/settings-form-section-DiqqVs6m.js +1 -0
  44. package/dist/gateway/static/root/assets/settings-page-BBOjEQW3.js +3 -0
  45. package/dist/gateway/static/root/assets/{share-preview-page-D4EG_vM1.js → share-preview-page-n1Gprylk.js} +1 -1
  46. package/dist/gateway/static/root/assets/{skills-page-sPAXhh8w.js → skills-page-CcN_gj--.js} +1 -1
  47. package/dist/gateway/static/root/assets/{theme-store-DryYl3qD.js → theme-store-CZOh1nT3.js} +1 -1
  48. package/dist/gateway/static/root/assets/url-Dd8Q7kZZ.js +3 -0
  49. package/dist/gateway/static/root/assets/{utils-CYO9eTCM.js → utils-CkWBfxs4.js} +1 -1
  50. package/dist/gateway/static/root/assets/{voice-api-key-field-Ds51havm.js → voice-api-key-field-O6awz9hi.js} +1 -1
  51. package/dist/gateway/static/root/index.html +5 -5
  52. package/dist/package.js +1 -1
  53. package/dist/src/agent/agent-scope.d.ts +4 -0
  54. package/dist/src/agent/agent-scope.js +53 -10
  55. package/dist/src/agent/agent-scope.js.map +1 -1
  56. package/dist/src/agent/bootstrap/filter-bootstrap-files.js +2 -1
  57. package/dist/src/agent/bootstrap/filter-bootstrap-files.js.map +1 -1
  58. package/dist/src/agent/embedded/session-tool-result-guard.js +2 -1
  59. package/dist/src/agent/embedded/session-tool-result-guard.js.map +1 -1
  60. package/dist/src/agent/embedded/tool-result-truncation.js +2 -1
  61. package/dist/src/agent/embedded/tool-result-truncation.js.map +1 -1
  62. package/dist/src/agent/fallback/candidates.js +2 -2
  63. package/dist/src/agent/fallback/candidates.js.map +1 -1
  64. package/dist/src/agent/goals/persistent-goal-apis.d.ts +0 -2
  65. package/dist/src/agent/goals/persistent-goal-service.js +0 -1
  66. package/dist/src/agent/goals/persistent-goal-service.js.map +1 -1
  67. package/dist/src/agent/image/generation/normalization.js +2 -12
  68. package/dist/src/agent/image/generation/normalization.js.map +1 -1
  69. package/dist/src/agent/image/generation/provider-registry.d.ts +4 -8
  70. package/dist/src/agent/image/generation/provider-registry.js.map +1 -1
  71. package/dist/src/agent/image/generation/runtime.d.ts +2 -2
  72. package/dist/src/agent/image/generation/runtime.js.map +1 -1
  73. package/dist/src/agent/image/generation/types.d.ts +0 -18
  74. package/dist/src/agent/image/image-helpers.js +6 -1
  75. package/dist/src/agent/image/image-helpers.js.map +1 -1
  76. package/dist/src/agent/image/index.d.ts +1 -1
  77. package/dist/src/agent/inbound/inbound-loop.d.ts +5 -0
  78. package/dist/src/agent/inbound/inbound-loop.js +41 -10
  79. package/dist/src/agent/inbound/inbound-loop.js.map +1 -1
  80. package/dist/src/agent/inbound/turn-dispatcher.d.ts +4 -0
  81. package/dist/src/agent/inbound/turn-dispatcher.js +6 -4
  82. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  83. package/dist/src/agent/mcp/bundle-mcp-materialize.js +2 -1
  84. package/dist/src/agent/mcp/bundle-mcp-materialize.js.map +1 -1
  85. package/dist/src/agent/mcp/bundle-mcp-names.js +2 -1
  86. package/dist/src/agent/mcp/bundle-mcp-names.js.map +1 -1
  87. package/dist/src/agent/mcp/bundle-mcp-runtime.js +2 -1
  88. package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
  89. package/dist/src/agent/mcp/mcp-transport-config.js +2 -1
  90. package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
  91. package/dist/src/agent/mcp/mcp-transport.js +2 -1
  92. package/dist/src/agent/mcp/mcp-transport.js.map +1 -1
  93. package/dist/src/agent/media-generation/runtime-shared.js +2 -9
  94. package/dist/src/agent/media-generation/runtime-shared.js.map +1 -1
  95. package/dist/src/agent/messaging/command-handler.d.ts +6 -0
  96. package/dist/src/agent/messaging/command-handler.js +5 -0
  97. package/dist/src/agent/messaging/command-handler.js.map +1 -1
  98. package/dist/src/agent/prompt/safety.d.ts +0 -7
  99. package/dist/src/agent/prompt/safety.js +1 -20
  100. package/dist/src/agent/prompt/safety.js.map +1 -1
  101. package/dist/src/agent/service/build-direct-message-content.js +1 -1
  102. package/dist/src/agent/service/build-direct-message-content.js.map +1 -1
  103. package/dist/src/agent/service/direct-turn-helpers.d.ts +3 -1
  104. package/dist/src/agent/service/direct-turn-helpers.js +6 -1
  105. package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
  106. package/dist/src/agent/service/process-direct-one-shot.d.ts +4 -0
  107. package/dist/src/agent/service/process-direct-one-shot.js +15 -2
  108. package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
  109. package/dist/src/agent/service/process-direct-streaming.d.ts +4 -0
  110. package/dist/src/agent/service/process-direct-streaming.js +34 -4
  111. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  112. package/dist/src/agent/service/webchat-tts.js +1 -1
  113. package/dist/src/agent/service/webchat-tts.js.map +1 -1
  114. package/dist/src/agent/service.d.ts +8 -0
  115. package/dist/src/agent/service.js +21 -1
  116. package/dist/src/agent/service.js.map +1 -1
  117. package/dist/src/agent/tools/create-share-tool.js +27 -20
  118. package/dist/src/agent/tools/create-share-tool.js.map +1 -1
  119. package/dist/src/agent/tools/factory.js +1 -1
  120. package/dist/src/agent/tools/index.d.ts +0 -1
  121. package/dist/src/agent/tools/index.js +4 -5
  122. package/dist/src/agent/tools/shell.js +0 -13
  123. package/dist/src/agent/tools/shell.js.map +1 -1
  124. package/dist/src/agent/tools/workflow-tool.js +10 -4
  125. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  126. package/dist/src/agent/workflow/builtins/audit-repo.d.ts +5 -1
  127. package/dist/src/agent/workflow/builtins/audit-repo.js +52 -11
  128. package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -1
  129. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +13 -0
  130. package/dist/src/agent/workflow/builtins/debug-incident.js +155 -0
  131. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -0
  132. package/dist/src/agent/workflow/builtins/index.d.ts +3 -1
  133. package/dist/src/agent/workflow/builtins/index.js +11 -1
  134. package/dist/src/agent/workflow/builtins/index.js.map +1 -1
  135. package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +6 -1
  136. package/dist/src/agent/workflow/builtins/multi-perspective-review.js +66 -30
  137. package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -1
  138. package/dist/src/agent/workflow/builtins/pr-review.d.ts +12 -0
  139. package/dist/src/agent/workflow/builtins/pr-review.js +156 -0
  140. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -0
  141. package/dist/src/agent/workflow/builtins/research.d.ts +5 -1
  142. package/dist/src/agent/workflow/builtins/research.js +37 -6
  143. package/dist/src/agent/workflow/builtins/research.js.map +1 -1
  144. package/dist/src/agent/workflow/catalog.d.ts +5 -0
  145. package/dist/src/agent/workflow/catalog.js +6 -2
  146. package/dist/src/agent/workflow/catalog.js.map +1 -1
  147. package/dist/src/agent/workflow/channel-capability.d.ts +3 -3
  148. package/dist/src/agent/workflow/index.d.ts +1 -1
  149. package/dist/src/agent/workflow/lint.d.ts +38 -0
  150. package/dist/src/agent/workflow/lint.js +74 -0
  151. package/dist/src/agent/workflow/lint.js.map +1 -0
  152. package/dist/src/agent/workflow/parser.js +13 -1
  153. package/dist/src/agent/workflow/parser.js.map +1 -1
  154. package/dist/src/agent/workflow/runtime.d.ts +3 -0
  155. package/dist/src/agent/workflow/runtime.js +76 -3
  156. package/dist/src/agent/workflow/runtime.js.map +1 -1
  157. package/dist/src/agent/workflow/types.d.ts +11 -1
  158. package/dist/src/browser/index.js +4 -4
  159. package/dist/src/browser/manager.d.ts +1 -3
  160. package/dist/src/browser/manager.js +0 -6
  161. package/dist/src/browser/manager.js.map +1 -1
  162. package/dist/src/browser/providers/browser-ext-install.d.ts +4 -4
  163. package/dist/src/browser/providers/browser-ext-install.js +38 -85
  164. package/dist/src/browser/providers/browser-ext-install.js.map +1 -1
  165. package/dist/src/browser/providers/cloakbrowser.d.ts +0 -5
  166. package/dist/src/browser/providers/cloakbrowser.js +2 -55
  167. package/dist/src/browser/providers/cloakbrowser.js.map +1 -1
  168. package/dist/src/channels/attachments/voice-stt-webchat.js +10 -8
  169. package/dist/src/channels/attachments/voice-stt-webchat.js.map +1 -1
  170. package/dist/src/channels/pairing/allow-from-file.js +9 -9
  171. package/dist/src/channels/pairing/allow-from-file.js.map +1 -1
  172. package/dist/src/channels/pairing/pairing-store.js +6 -6
  173. package/dist/src/channels/pairing/pairing-store.js.map +1 -1
  174. package/dist/src/chat-commands/builtins/session.js +1 -1
  175. package/dist/src/chat-commands/builtins/session.js.map +1 -1
  176. package/dist/src/chat-commands/builtins/tts.js +2 -2
  177. package/dist/src/chat-commands/builtins/tts.js.map +1 -1
  178. package/dist/src/chat-commands/builtins/workflow.js +7 -2
  179. package/dist/src/chat-commands/builtins/workflow.js.map +1 -1
  180. package/dist/src/chat-commands/context.d.ts +3 -0
  181. package/dist/src/chat-commands/context.js +21 -3
  182. package/dist/src/chat-commands/context.js.map +1 -1
  183. package/dist/src/chat-commands/session-key.d.ts +4 -37
  184. package/dist/src/chat-commands/session-key.js +49 -85
  185. package/dist/src/chat-commands/session-key.js.map +1 -1
  186. package/dist/src/chat-commands/types.d.ts +2 -0
  187. package/dist/src/cli/commands/agent/interactive.js +2 -2
  188. package/dist/src/cli/commands/agent/interactive.js.map +1 -1
  189. package/dist/src/cli/commands/agent/sessions.js +2 -2
  190. package/dist/src/cli/commands/agent/sessions.js.map +1 -1
  191. package/dist/src/cli/commands/agent.js +4 -5
  192. package/dist/src/cli/commands/agent.js.map +1 -1
  193. package/dist/src/cli/commands/channels.js +1 -5
  194. package/dist/src/cli/commands/channels.js.map +1 -1
  195. package/dist/src/cli/commands/gateway/lifecycle-core.js +1 -1
  196. package/dist/src/cli/commands/gateway/lifecycle-core.js.map +1 -1
  197. package/dist/src/cli/commands/gateway/logs.d.ts +9 -0
  198. package/dist/src/cli/commands/gateway/logs.js +50 -17
  199. package/dist/src/cli/commands/gateway/logs.js.map +1 -1
  200. package/dist/src/cli/commands/image.js +22 -21
  201. package/dist/src/cli/commands/image.js.map +1 -1
  202. package/dist/src/cli/commands/session/utils.js +2 -2
  203. package/dist/src/cli/commands/session/utils.js.map +1 -1
  204. package/dist/src/cli/commands/update.js +26 -46
  205. package/dist/src/cli/commands/update.js.map +1 -1
  206. package/dist/src/cli/utils/session.d.ts +0 -5
  207. package/dist/src/cli/utils/session.js +1 -6
  208. package/dist/src/cli/utils/session.js.map +1 -1
  209. package/dist/src/commands/agents.config.js +1 -1
  210. package/dist/src/commands/agents.config.js.map +1 -1
  211. package/dist/src/config/agent-profile.js +5 -27
  212. package/dist/src/config/agent-profile.js.map +1 -1
  213. package/dist/src/config/index.js +2 -2
  214. package/dist/src/config/model-input.js +2 -5
  215. package/dist/src/config/model-input.js.map +1 -1
  216. package/dist/src/config/schema.d.ts +201 -217
  217. package/dist/src/config/schema.js +54 -39
  218. package/dist/src/config/schema.js.map +1 -1
  219. package/dist/src/config/workspace-path-helpers.d.ts +1 -2
  220. package/dist/src/config/workspace-path-helpers.js.map +1 -1
  221. package/dist/src/daemon/install-plan.js +25 -1
  222. package/dist/src/daemon/install-plan.js.map +1 -1
  223. package/dist/src/daemon/launchd.d.ts +8 -0
  224. package/dist/src/daemon/launchd.js +5 -12
  225. package/dist/src/daemon/launchd.js.map +1 -1
  226. package/dist/src/daemon/schtasks.d.ts +25 -0
  227. package/dist/src/daemon/schtasks.js +166 -46
  228. package/dist/src/daemon/schtasks.js.map +1 -1
  229. package/dist/src/daemon/service.js +5 -4
  230. package/dist/src/daemon/service.js.map +1 -1
  231. package/dist/src/daemon/systemd.d.ts +6 -0
  232. package/dist/src/daemon/systemd.js +18 -3
  233. package/dist/src/daemon/systemd.js.map +1 -1
  234. package/dist/src/extensions/activation-context.js +0 -1
  235. package/dist/src/extensions/activation-context.js.map +1 -1
  236. package/dist/src/extensions/normalize-manifest.js +0 -1
  237. package/dist/src/extensions/normalize-manifest.js.map +1 -1
  238. package/dist/src/extensions/types/manifest.d.ts +0 -2
  239. package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
  240. package/dist/src/gateway/agent-builtin-tools.js +1 -0
  241. package/dist/src/gateway/agent-builtin-tools.js.map +1 -1
  242. package/dist/src/gateway/agents-admin.js +10 -2
  243. package/dist/src/gateway/agents-admin.js.map +1 -1
  244. package/dist/src/gateway/heartbeat/service.js +1 -1
  245. package/dist/src/gateway/heartbeat/service.js.map +1 -1
  246. package/dist/src/gateway/hono/app.js +1 -1
  247. package/dist/src/gateway/hono/lib/agent-model.d.ts +18 -10
  248. package/dist/src/gateway/hono/lib/agent-model.js +24 -35
  249. package/dist/src/gateway/hono/lib/agent-model.js.map +1 -1
  250. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  251. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  252. package/dist/src/gateway/hono/lib/safe-voice-config.js +14 -53
  253. package/dist/src/gateway/hono/lib/safe-voice-config.js.map +1 -1
  254. package/dist/src/gateway/hono/routes/config-patch/agents.js +17 -5
  255. package/dist/src/gateway/hono/routes/config-patch/agents.js.map +1 -1
  256. package/dist/src/gateway/hono/routes/config-patch/channels.js +0 -11
  257. package/dist/src/gateway/hono/routes/config-patch/channels.js.map +1 -1
  258. package/dist/src/gateway/hono/routes/goals.js +1 -1
  259. package/dist/src/gateway/hono/routes/goals.js.map +1 -1
  260. package/dist/src/gateway/hono/routes/sessions.js +28 -7
  261. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  262. package/dist/src/gateway/hono/routes/shares.js +14 -12
  263. package/dist/src/gateway/hono/routes/shares.js.map +1 -1
  264. package/dist/src/gateway/hono/routes/tunnel.js +1 -1
  265. package/dist/src/gateway/hono/routes/update.js +4 -2
  266. package/dist/src/gateway/hono/routes/update.js.map +1 -1
  267. package/dist/src/gateway/hono/sse.js +16 -33
  268. package/dist/src/gateway/hono/sse.js.map +1 -1
  269. package/dist/src/gateway/lock.js +10 -10
  270. package/dist/src/gateway/lock.js.map +1 -1
  271. package/dist/src/gateway/ports.js +6 -6
  272. package/dist/src/gateway/ports.js.map +1 -1
  273. package/dist/src/gateway/resolve-webchat-session-key.d.ts +19 -0
  274. package/dist/src/gateway/resolve-webchat-session-key.js +46 -0
  275. package/dist/src/gateway/resolve-webchat-session-key.js.map +1 -0
  276. package/dist/src/gateway/service/run-gateway-agent.js +27 -11
  277. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  278. package/dist/src/gateway/service/sessions-api.d.ts +3 -0
  279. package/dist/src/gateway/service/sessions-api.js +8 -0
  280. package/dist/src/gateway/service/sessions-api.js.map +1 -1
  281. package/dist/src/gateway/service.d.ts +0 -2
  282. package/dist/src/gateway/service.js +2 -7
  283. package/dist/src/gateway/service.js.map +1 -1
  284. package/dist/src/gateway/session-reset-service.d.ts +20 -0
  285. package/dist/src/gateway/session-reset-service.js +54 -0
  286. package/dist/src/gateway/session-reset-service.js.map +1 -0
  287. package/dist/src/gateway/startup-readiness.d.ts +1 -1
  288. package/dist/src/gateway/startup-readiness.js +1 -0
  289. package/dist/src/gateway/startup-readiness.js.map +1 -1
  290. package/dist/src/infra/gateway-processes.js +2 -2
  291. package/dist/src/infra/gateway-processes.js.map +1 -1
  292. package/dist/src/infra/run-command.d.ts +16 -0
  293. package/dist/src/infra/run-command.js +67 -0
  294. package/dist/src/infra/run-command.js.map +1 -0
  295. package/dist/src/infra/update-global.d.ts +45 -0
  296. package/dist/src/infra/update-global.js +224 -0
  297. package/dist/src/infra/update-global.js.map +1 -0
  298. package/dist/src/mcp/channel-bridge.js +1 -1
  299. package/dist/src/mcp/channel-shared.js +2 -1
  300. package/dist/src/mcp/channel-shared.js.map +1 -1
  301. package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
  302. package/dist/src/providers/auth-runtime/auth-profile-store.js.map +1 -1
  303. package/dist/src/providers/auth-runtime/resolve-auth.js +1 -12
  304. package/dist/src/providers/auth-runtime/resolve-auth.js.map +1 -1
  305. package/dist/src/providers/auth-runtime/types.d.ts +6 -12
  306. package/dist/src/routing/agent-session-key.d.ts +58 -0
  307. package/dist/src/routing/agent-session-key.js +164 -0
  308. package/dist/src/routing/agent-session-key.js.map +1 -0
  309. package/dist/src/routing/index.d.ts +1 -1
  310. package/dist/src/routing/index.js +4 -2
  311. package/dist/src/routing/index.js.map +1 -1
  312. package/dist/src/routing/resolve-route.d.ts +15 -0
  313. package/dist/src/routing/resolve-route.js +41 -20
  314. package/dist/src/routing/resolve-route.js.map +1 -1
  315. package/dist/src/routing/resolve-tui-session-key.d.ts +25 -0
  316. package/dist/src/routing/resolve-tui-session-key.js +54 -0
  317. package/dist/src/routing/resolve-tui-session-key.js.map +1 -0
  318. package/dist/src/routing/session-key-utils.d.ts +24 -0
  319. package/dist/src/routing/session-key-utils.js +92 -0
  320. package/dist/src/routing/session-key-utils.js.map +1 -0
  321. package/dist/src/routing/session-key.d.ts +19 -49
  322. package/dist/src/routing/session-key.js +143 -116
  323. package/dist/src/routing/session-key.js.map +1 -1
  324. package/dist/src/session/index.d.ts +6 -0
  325. package/dist/src/session/index.js +7 -1
  326. package/dist/src/session/init-session-turn.d.ts +30 -0
  327. package/dist/src/session/init-session-turn.js +102 -0
  328. package/dist/src/session/init-session-turn.js.map +1 -0
  329. package/dist/src/session/lifecycle-timestamps.d.ts +8 -0
  330. package/dist/src/session/lifecycle-timestamps.js +16 -0
  331. package/dist/src/session/lifecycle-timestamps.js.map +1 -0
  332. package/dist/src/session/manager.d.ts +7 -1
  333. package/dist/src/session/manager.js +8 -1
  334. package/dist/src/session/manager.js.map +1 -1
  335. package/dist/src/session/parity/transcript-paths.js +2 -2
  336. package/dist/src/session/parity/transcript-paths.js.map +1 -1
  337. package/dist/src/session/parity/xopc-session-disk-entry.d.ts +6 -0
  338. package/dist/src/session/reset-policy.d.ts +32 -0
  339. package/dist/src/session/reset-policy.js +65 -0
  340. package/dist/src/session/reset-policy.js.map +1 -0
  341. package/dist/src/session/reset-triggers.d.ts +20 -0
  342. package/dist/src/session/reset-triggers.js +63 -0
  343. package/dist/src/session/reset-triggers.js.map +1 -0
  344. package/dist/src/session/reset-type.d.ts +12 -0
  345. package/dist/src/session/reset-type.js +25 -0
  346. package/dist/src/session/reset-type.js.map +1 -0
  347. package/dist/src/session/resolve-session.d.ts +30 -0
  348. package/dist/src/session/resolve-session.js +93 -0
  349. package/dist/src/session/resolve-session.js.map +1 -0
  350. package/dist/src/session/session-title.js +3 -2
  351. package/dist/src/session/session-title.js.map +1 -1
  352. package/dist/src/session/store.d.ts +11 -4
  353. package/dist/src/session/store.js +57 -6
  354. package/dist/src/session/store.js.map +1 -1
  355. package/dist/src/session/transcript-events.js +2 -1
  356. package/dist/src/session/transcript-events.js.map +1 -1
  357. package/dist/src/share/share-url.d.ts +33 -0
  358. package/dist/src/share/share-url.js +56 -14
  359. package/dist/src/share/share-url.js.map +1 -1
  360. package/dist/src/tui/backends/embedded-backend.js +4 -9
  361. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  362. package/dist/src/tui/backends/gateway-sse-backend.js +1 -1
  363. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  364. package/dist/src/tui/components/chat-log.js +3 -3
  365. package/dist/src/tui/components/chat-log.js.map +1 -1
  366. package/dist/src/tui/theme.d.ts +0 -2
  367. package/dist/src/tui/theme.js +1 -3
  368. package/dist/src/tui/theme.js.map +1 -1
  369. package/dist/src/tui/tui-commands.d.ts +3 -0
  370. package/dist/src/tui/tui-commands.js +45 -10
  371. package/dist/src/tui/tui-commands.js.map +1 -1
  372. package/dist/src/tui/tui-keybindings-file.js +1 -21
  373. package/dist/src/tui/tui-keybindings-file.js.map +1 -1
  374. package/dist/src/tui/tui-session-actions.d.ts +28 -0
  375. package/dist/src/tui/tui-session-actions.js +88 -0
  376. package/dist/src/tui/tui-session-actions.js.map +1 -0
  377. package/dist/src/tui/tui.js +52 -47
  378. package/dist/src/tui/tui.js.map +1 -1
  379. package/dist/src/utils/string-coerce.d.ts +2 -0
  380. package/dist/src/utils/string-coerce.js +10 -1
  381. package/dist/src/utils/string-coerce.js.map +1 -1
  382. package/dist/src/voice/stt/config-slice.d.ts +2 -5
  383. package/dist/src/voice/stt/config-slice.js +5 -26
  384. package/dist/src/voice/stt/config-slice.js.map +1 -1
  385. package/dist/src/voice/stt/types.d.ts +1 -18
  386. package/dist/src/voice/stt/types.js +4 -2
  387. package/dist/src/voice/stt/types.js.map +1 -1
  388. package/dist/src/voice/tts/config-slice.d.ts +3 -7
  389. package/dist/src/voice/tts/config-slice.js +7 -38
  390. package/dist/src/voice/tts/config-slice.js.map +1 -1
  391. package/dist/src/voice/tts/merge-config.js +2 -48
  392. package/dist/src/voice/tts/merge-config.js.map +1 -1
  393. package/dist/src/voice/tts/providers/alibaba-speech.js +1 -1
  394. package/dist/src/voice/tts/providers/alibaba-speech.js.map +1 -1
  395. package/dist/src/voice/tts/types.d.ts +1 -29
  396. package/dist/src/voice/tts/types.js +19 -17
  397. package/dist/src/voice/tts/types.js.map +1 -1
  398. package/package.json +1 -4
  399. package/dist/gateway/static/root/assets/agents-D3_-kNlZ.js +0 -222
  400. package/dist/gateway/static/root/assets/extension-settings-page-8PZcmWI7.js +0 -1
  401. package/dist/gateway/static/root/assets/index-ew_2L2We.css +0 -1
  402. package/dist/gateway/static/root/assets/settings-form-section-D_tgb8r2.js +0 -1
  403. package/dist/gateway/static/root/assets/settings-page-C18xBt4X.js +0 -3
  404. package/dist/gateway/static/root/assets/url-BwNL6Rgk.js +0 -3
  405. package/dist/src/agent/tools/browser-legacy-tools.d.ts +0 -17
  406. package/dist/src/agent/tools/browser-legacy-tools.js +0 -766
  407. package/dist/src/agent/tools/browser-legacy-tools.js.map +0 -1
@@ -43,6 +43,7 @@ async function runWorkflow(script, deps, options) {
43
43
  const maxSubagents = Math.max(1, options.maxSubagents ?? DEFAULT_MAX_SUBAGENTS);
44
44
  const limiter = createLimiter(concurrency);
45
45
  const pendingAgentRuns = /* @__PURE__ */ new Set();
46
+ const phaseDefaultModels = buildPhaseModelMap(meta.phases);
46
47
  const log = (message) => {
47
48
  const text = String(message);
48
49
  state.logs.push(text);
@@ -62,6 +63,22 @@ async function runWorkflow(script, deps, options) {
62
63
  const throwIfAborted = () => {
63
64
  if (options.signal?.aborted) throw new Error("workflow aborted");
64
65
  };
66
+ const resolveAgentModel = (normalized, assignedPhase) => {
67
+ const realId = normalized.model?.trim();
68
+ if (realId) {
69
+ if (!deps.resolveModelId) throw new Error("workflow runtime missing resolveModelId; cannot resolve real model id");
70
+ return deps.resolveModelId(realId);
71
+ }
72
+ if (assignedPhase) {
73
+ const phaseRef = phaseDefaultModels.get(assignedPhase);
74
+ if (phaseRef) {
75
+ if (phaseRef.includes("/")) {
76
+ if (!deps.resolveModelId) return void 0;
77
+ return deps.resolveModelId(phaseRef);
78
+ }
79
+ }
80
+ }
81
+ };
65
82
  const agent = async (prompt, agentOptions = {}) => {
66
83
  throwIfAborted();
67
84
  if (budget.total !== null && budget.remaining() <= 0) throw new Error("workflow token budget exhausted");
@@ -82,6 +99,7 @@ async function runWorkflow(script, deps, options) {
82
99
  });
83
100
  try {
84
101
  throwIfAborted();
102
+ const resolvedModel = resolveAgentModel(normalized, assignedPhase);
85
103
  const result = await deps.runner.run(taskPrompt, {
86
104
  label,
87
105
  schema: normalized.schema,
@@ -89,7 +107,7 @@ async function runWorkflow(script, deps, options) {
89
107
  maxIterations: normalized.maxIterations,
90
108
  phase: assignedPhase,
91
109
  signal: options.signal,
92
- instructions: normalized.model ? `The parent workflow requested model "${normalized.model}".` : void 0
110
+ model: resolvedModel
93
111
  });
94
112
  throwIfAborted();
95
113
  const status = result === null ? "error" : "done";
@@ -195,7 +213,7 @@ async function runWorkflow(script, deps, options) {
195
213
  await Promise.allSettled([...pendingAgentRuns]);
196
214
  } catch (e) {
197
215
  await Promise.allSettled([...pendingAgentRuns]);
198
- throw e;
216
+ throw rewriteScriptError(e);
199
217
  }
200
218
  assertStructuredCloneable(result, "workflow result");
201
219
  return {
@@ -247,6 +265,12 @@ function createLimiter(limit) {
247
265
  }
248
266
  };
249
267
  }
268
+ function buildPhaseModelMap(phases) {
269
+ const out = /* @__PURE__ */ new Map();
270
+ if (!phases) return out;
271
+ for (const p of phases) if (p && typeof p.title === "string" && typeof p.model === "string" && p.model.trim()) out.set(p.title, p.model.trim());
272
+ return out;
273
+ }
250
274
  function requireString(value, name) {
251
275
  if (typeof value !== "string") throw new TypeError(`${name} must be a string`);
252
276
  return value;
@@ -267,11 +291,16 @@ function normalizeAgentOptions(value) {
267
291
  if (maxIterations !== void 0) {
268
292
  if (typeof maxIterations !== "number" || !Number.isFinite(maxIterations) || maxIterations < 1) throw new TypeError("agent maxIterations must be a positive number");
269
293
  }
294
+ const modelStr = optionalString(options.model, "agent model");
295
+ if (modelStr !== void 0) {
296
+ const trimmed = modelStr.trim();
297
+ if (trimmed && !trimmed.includes("/")) throw new Error(`agent option 'model' must be a real model id in 'provider/model' form (got '${trimmed}')`);
298
+ }
270
299
  return {
271
300
  label: optionalString(options.label, "agent label"),
272
301
  phase: optionalString(options.phase, "agent phase"),
273
302
  schema: options.schema,
274
- model: optionalString(options.model, "agent model"),
303
+ model: modelStr,
275
304
  toolset,
276
305
  maxIterations
277
306
  };
@@ -280,10 +309,54 @@ function assertStructuredCloneable(value, name) {
280
309
  try {
281
310
  structuredClone(value);
282
311
  } catch (e) {
312
+ const promisePath = findPromisePath(value, "");
313
+ if (promisePath !== null) throw new Error(`${name} contains a Promise at \`${promisePath || "<root>"}\` — missing 'await' before parallel()/pipeline()/agent(). Async return auto-unwraps a bare Promise, but a Promise nested in an object/array is returned as-is.`);
283
314
  const detail = e instanceof Error ? ` ${e.message}` : "";
284
315
  throw new Error(`${name} must be structured-cloneable; did you forget to await agent(), parallel(), or pipeline()?${detail}`);
285
316
  }
286
317
  }
318
+ /**
319
+ * Find the first Promise lurking in `value`, returning a JS-path like
320
+ * "results[0].pending". Returns null when no Promise is found. Bounded
321
+ * depth/breadth so it never explodes on weird user shapes.
322
+ */
323
+ function findPromisePath(value, path, depth = 0) {
324
+ if (depth > 4) return null;
325
+ if (isThenable(value)) return path;
326
+ if (Array.isArray(value)) {
327
+ for (let i = 0; i < Math.min(value.length, 32); i++) {
328
+ const inner = findPromisePath(value[i], `${path}[${i}]`, depth + 1);
329
+ if (inner !== null) return inner;
330
+ }
331
+ return null;
332
+ }
333
+ if (value !== null && typeof value === "object") {
334
+ let count = 0;
335
+ for (const [key, child] of Object.entries(value)) {
336
+ if (count++ > 32) break;
337
+ const inner = findPromisePath(child, path ? `${path}.${key}` : key, depth + 1);
338
+ if (inner !== null) return inner;
339
+ }
340
+ }
341
+ return null;
342
+ }
343
+ function isThenable(value) {
344
+ return value !== null && typeof value === "object" && typeof value.then === "function";
345
+ }
346
+ /**
347
+ * Rewrite TypeErrors that look like "called .map on a Promise" with an
348
+ * await-shaped hint. Static lint catches the obvious form at parse time;
349
+ * this is the safety net for cases lint can't see (dynamic indirection,
350
+ * values stashed in helper closures).
351
+ */
352
+ const PROMISE_METHOD_ERROR = /\.(map|filter|forEach|flat|flatMap|find|some|every|reduce|join|length|then)\b is not a function/;
353
+ function rewriteScriptError(error) {
354
+ const asError = error;
355
+ if (!(asError?.name === "TypeError" && typeof asError.message === "string")) return error instanceof Error ? error : new Error(String(error));
356
+ const message = asError.message;
357
+ if (!PROMISE_METHOD_ERROR.test(message)) return error instanceof Error ? error : new Error(message);
358
+ return /* @__PURE__ */ new Error(`${message}\n\nHint: parallel()/pipeline()/agent() return a Promise — 'await' the call before using its result.\n ❌ const r = parallel(...); r.map(...)\n ✅ const r = await parallel(...); r.map(...)`);
359
+ }
287
360
  function defaultAgentLabel(phase, index) {
288
361
  return phase ? `${phase} agent ${index}` : `agent ${index}`;
289
362
  }
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","names":[],"sources":["../../../../src/agent/workflow/runtime.ts"],"sourcesContent":["/**\n * Sandboxed workflow runtime.\n *\n * Parses the script via {@link parseWorkflowScript} (which strips the `meta`\n * export), wraps the remaining body in an async IIFE, and runs it inside a\n * Node `vm` context with a curated set of globals:\n *\n * - `agent(prompt, opts)` — spawns a subagent through the injected\n * {@link SubagentRunner} and returns its result (string, or schema-validated\n * object). Failures resolve to `null`.\n * - `parallel(thunks)` — concurrent fan-out; thunks (not promises!) so the\n * limiter sees each agent() call.\n * - `pipeline(items, ...stages)` — per-item sequential stages, items run\n * concurrently (no stage barrier). Each stage receives\n * `(prevResult, originalItem, index)`. A stage that throws drops that item\n * to `null` and skips remaining stages.\n * - `phase(title)` — marks the current phase; surfaces through `onPhase`.\n * - `log(message)` — appends to the workflow log.\n * - `budget` — `{ total, spent(), remaining() }` for self-pacing scripts.\n * - `args`, `cwd`, `process.cwd()`.\n *\n * The runtime is the only code that touches `vm`. It exposes no IO surface,\n * carries no LLM dependency, and is fully driven by injected callbacks — that\n * means the workflow tool, tests, and any future runner share one runtime.\n */\n\nimport { availableParallelism } from 'node:os';\nimport { createContext, Script } from 'node:vm';\n\nimport { parseWorkflowScript } from './parser.js';\nimport type {\n AgentScriptOptions,\n SubagentRunner,\n WorkflowRunOptions,\n WorkflowRunResult,\n WorkflowSnapshot,\n WorkflowAgentStatus,\n} from './types.js';\n\nconst DEFAULT_CONCURRENCY_FLOOR = 1;\nconst DEFAULT_CONCURRENCY_CEILING = 16;\nconst DEFAULT_MAX_SUBAGENTS = 1000;\n\ninterface RuntimeState {\n currentPhase?: string;\n logs: string[];\n phases: string[];\n agentCount: number;\n spent: number;\n}\n\nexport interface RunWorkflowDeps {\n runner: SubagentRunner;\n}\n\nexport async function runWorkflow<T = unknown>(\n script: string,\n deps: RunWorkflowDeps,\n options: WorkflowRunOptions,\n): Promise<WorkflowRunResult<T>> {\n const started = Date.now();\n const { meta, body } = parseWorkflowScript(script);\n\n const state: RuntimeState = { logs: [], phases: [], agentCount: 0, spent: 0 };\n const concurrency = clampConcurrency(options.concurrency);\n const maxSubagents = Math.max(1, options.maxSubagents ?? DEFAULT_MAX_SUBAGENTS);\n const limiter = createLimiter(concurrency);\n const pendingAgentRuns = new Set<Promise<unknown>>();\n\n const log = (message: unknown) => {\n const text = String(message);\n state.logs.push(text);\n options.onLog?.(text);\n };\n\n const phase = (title: unknown) => {\n const text = requireString(title, 'phase title');\n state.currentPhase = text;\n if (!state.phases.includes(text)) state.phases.push(text);\n options.onPhase?.(text);\n };\n\n const budget = Object.freeze({\n total: options.tokenBudget ?? null,\n spent: () => state.spent,\n remaining: () =>\n options.tokenBudget == null\n ? Number.POSITIVE_INFINITY\n : Math.max(0, options.tokenBudget - state.spent),\n });\n\n const throwIfAborted = () => {\n if (options.signal?.aborted) throw new Error('workflow aborted');\n };\n\n const agent = async (prompt: unknown, agentOptions: unknown = {}) => {\n throwIfAborted();\n if (budget.total !== null && budget.remaining() <= 0) {\n throw new Error('workflow token budget exhausted');\n }\n if (state.agentCount >= maxSubagents) {\n throw new Error(`workflow agent quota exhausted (max ${maxSubagents})`);\n }\n\n const taskPrompt = requireString(prompt, 'agent prompt');\n const normalized = normalizeAgentOptions(agentOptions);\n const assignedPhase = normalized.phase ?? state.currentPhase;\n const requestedLabel = normalized.label?.trim();\n\n const runPromise = limiter(async () => {\n // Counter increments inside the limiter — id reflects start order, not enqueue order.\n state.agentCount += 1;\n const id = state.agentCount;\n const label = requestedLabel || defaultAgentLabel(assignedPhase, id);\n options.onAgentStart?.({ id, label, phase: assignedPhase, prompt: taskPrompt });\n\n try {\n throwIfAborted();\n const result = await deps.runner.run<unknown>(taskPrompt, {\n label,\n schema: normalized.schema,\n allowedToolNames: normalized.toolset,\n maxIterations: normalized.maxIterations,\n phase: assignedPhase,\n signal: options.signal,\n instructions: normalized.model\n ? `The parent workflow requested model \"${normalized.model}\".`\n : undefined,\n });\n throwIfAborted();\n\n const status: WorkflowAgentStatus = result === null ? 'error' : 'done';\n state.spent += estimateTokens(result);\n options.onAgentEnd?.({ id, label, phase: assignedPhase, result, status });\n return result;\n } catch (e) {\n if (options.signal?.aborted) {\n options.onAgentEnd?.({ id, label, phase: assignedPhase, result: null, status: 'skipped' });\n throw e;\n }\n const message = e instanceof Error ? e.message : String(e);\n log(`agent ${label} failed: ${message}`);\n options.onAgentEnd?.({ id, label, phase: assignedPhase, result: null, status: 'error' });\n return null;\n }\n });\n\n pendingAgentRuns.add(runPromise);\n // `then` (not `finally`) keeps the bookkeeping promise from re-throwing into\n // the unhandled-rejection channel when `runPromise` rejects on abort.\n runPromise.then(\n () => pendingAgentRuns.delete(runPromise),\n () => pendingAgentRuns.delete(runPromise),\n );\n return runPromise;\n };\n\n const parallel = async (thunks: unknown) => {\n throwIfAborted();\n if (!Array.isArray(thunks)) throw new TypeError('parallel() expects an array of functions');\n for (const t of thunks) {\n if (typeof t !== 'function') {\n throw new TypeError(\n 'parallel() expects an array of functions, not promises. Wrap each call: () => agent(...)',\n );\n }\n }\n return Promise.all(\n thunks.map(async (thunk, index) => {\n try {\n return await (thunk as () => unknown)();\n } catch (e) {\n if (options.signal?.aborted) throw e;\n const message = e instanceof Error ? e.message : String(e);\n log(`parallel[${index}] failed: ${message}`);\n return null;\n }\n }),\n );\n };\n\n const pipeline = async (items: unknown, ...stages: Array<unknown>) => {\n throwIfAborted();\n if (!Array.isArray(items)) {\n throw new TypeError('pipeline() expects an array as the first argument');\n }\n for (const stage of stages) {\n if (typeof stage !== 'function') {\n throw new TypeError(\n 'pipeline() stages must be functions: pipeline(items, item => ..., result => ...)',\n );\n }\n }\n const typedStages = stages as Array<\n (prev: unknown, original: unknown, index: number) => unknown\n >;\n return Promise.all(\n items.map(async (item, index) => {\n let value: unknown = item;\n for (const stage of typedStages) {\n try {\n throwIfAborted();\n value = await stage(value, item, index);\n throwIfAborted();\n } catch (e) {\n if (options.signal?.aborted) throw e;\n const message = e instanceof Error ? e.message : String(e);\n log(`pipeline[${index}] failed: ${message}`);\n return null;\n }\n }\n return value;\n }),\n );\n };\n\n const context = createContext({\n agent,\n parallel,\n pipeline,\n log,\n phase,\n args: options.args,\n cwd: options.cwd,\n process: Object.freeze({ cwd: () => options.cwd }),\n budget,\n console: {\n log,\n info: log,\n warn: (m: unknown) => log(`[warn] ${String(m)}`),\n error: (m: unknown) => log(`[error] ${String(m)}`),\n },\n JSON,\n Math,\n Array,\n Object,\n String,\n Number,\n Boolean,\n Set,\n Map,\n Promise,\n });\n\n const wrapped = `(async () => {\\n${body}\\n})()`;\n const script$ = new Script(wrapped, { filename: `${meta.name}.workflow.js` });\n\n let result: unknown;\n try {\n result = await script$.runInContext(context);\n // Wait for any agent() runs the script forgot to await before declaring success.\n await Promise.allSettled([...pendingAgentRuns]);\n } catch (e) {\n // Drain pending agent calls before propagating, so the snapshot reflects final state.\n await Promise.allSettled([...pendingAgentRuns]);\n throw e;\n }\n\n assertStructuredCloneable(result, 'workflow result');\n\n return {\n meta,\n result: result as T,\n logs: state.logs,\n phases: state.phases,\n agentCount: state.agentCount,\n durationMs: Date.now() - started,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Initial snapshot helper — kept here so the runtime is the single source of\n// truth for \"what a fresh snapshot looks like for this workflow\".\n// ---------------------------------------------------------------------------\n\nexport function emptySnapshotFor(name: string, description?: string): WorkflowSnapshot {\n return {\n name,\n description,\n phases: [],\n logs: [],\n agents: [],\n agentCount: 0,\n runningCount: 0,\n doneCount: 0,\n errorCount: 0,\n skippedCount: 0,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\nfunction clampConcurrency(requested?: number): number {\n if (typeof requested === 'number' && Number.isFinite(requested) && requested >= 1) {\n return Math.min(Math.floor(requested), DEFAULT_CONCURRENCY_CEILING);\n }\n let cpu = DEFAULT_CONCURRENCY_CEILING;\n try {\n cpu = availableParallelism();\n } catch {\n // Some sandboxes throw; fall back to the ceiling.\n }\n return Math.max(DEFAULT_CONCURRENCY_FLOOR, Math.min(DEFAULT_CONCURRENCY_CEILING, cpu - 2));\n}\n\nfunction createLimiter(limit: number) {\n let active = 0;\n const queue: Array<() => void> = [];\n const next = () => {\n active -= 1;\n const resume = queue.shift();\n if (resume) resume();\n };\n return async <T>(fn: () => Promise<T>): Promise<T> => {\n if (active >= limit) {\n await new Promise<void>((resolve) => queue.push(resolve));\n }\n active += 1;\n try {\n return await fn();\n } finally {\n next();\n }\n };\n}\n\nfunction requireString(value: unknown, name: string): string {\n if (typeof value !== 'string') throw new TypeError(`${name} must be a string`);\n return value;\n}\n\nfunction optionalString(value: unknown, name: string): string | undefined {\n if (value === undefined) return undefined;\n return requireString(value, name);\n}\n\nfunction normalizeAgentOptions(value: unknown): AgentScriptOptions {\n if (value === undefined || value === null) return {};\n if (typeof value !== 'object') throw new TypeError('agent options must be an object');\n const options = value as AgentScriptOptions;\n const toolset = options.toolset;\n if (toolset !== undefined) {\n if (!Array.isArray(toolset) || toolset.some((t) => typeof t !== 'string')) {\n throw new TypeError('agent toolset must be an array of strings');\n }\n }\n const maxIterations = options.maxIterations;\n if (maxIterations !== undefined) {\n if (typeof maxIterations !== 'number' || !Number.isFinite(maxIterations) || maxIterations < 1) {\n throw new TypeError('agent maxIterations must be a positive number');\n }\n }\n return {\n label: optionalString(options.label, 'agent label'),\n phase: optionalString(options.phase, 'agent phase'),\n schema: options.schema,\n model: optionalString(options.model, 'agent model'),\n toolset,\n maxIterations,\n };\n}\n\nfunction assertStructuredCloneable(value: unknown, name: string): void {\n try {\n structuredClone(value);\n } catch (e) {\n const detail = e instanceof Error ? ` ${e.message}` : '';\n throw new Error(\n `${name} must be structured-cloneable; did you forget to await agent(), parallel(), or pipeline()?${detail}`,\n );\n }\n}\n\nfunction defaultAgentLabel(phase: string | undefined, index: number): string {\n return phase ? `${phase} agent ${index}` : `agent ${index}`;\n}\n\nfunction estimateTokens(value: unknown): number {\n if (value === null || value === undefined) return 0;\n try {\n return Math.ceil(JSON.stringify(value).length / 4);\n } catch {\n return Math.ceil(String(value).length / 4);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runtime.js","names":[],"sources":["../../../../src/agent/workflow/runtime.ts"],"sourcesContent":["/**\n * Sandboxed workflow runtime.\n *\n * Parses the script via {@link parseWorkflowScript} (which strips the `meta`\n * export), wraps the remaining body in an async IIFE, and runs it inside a\n * Node `vm` context with a curated set of globals:\n *\n * - `agent(prompt, opts)` — spawns a subagent through the injected\n * {@link SubagentRunner} and returns its result (string, or schema-validated\n * object). Failures resolve to `null`.\n * - `parallel(thunks)` — concurrent fan-out; thunks (not promises!) so the\n * limiter sees each agent() call.\n * - `pipeline(items, ...stages)` — per-item sequential stages, items run\n * concurrently (no stage barrier). Each stage receives\n * `(prevResult, originalItem, index)`. A stage that throws drops that item\n * to `null` and skips remaining stages.\n * - `phase(title)` — marks the current phase; surfaces through `onPhase`.\n * - `log(message)` — appends to the workflow log.\n * - `budget` — `{ total, spent(), remaining() }` for self-pacing scripts.\n * - `args`, `cwd`, `process.cwd()`.\n *\n * The runtime is the only code that touches `vm`. It exposes no IO surface,\n * carries no LLM dependency, and is fully driven by injected callbacks — that\n * means the workflow tool, tests, and any future runner share one runtime.\n */\n\nimport { availableParallelism } from 'node:os';\nimport { createContext, Script } from 'node:vm';\n\nimport type { Api, Model } from '@earendil-works/pi-ai';\n\nimport { parseWorkflowScript } from './parser.js';\nimport type {\n AgentScriptOptions,\n SubagentRunner,\n WorkflowMetaPhase,\n WorkflowRunOptions,\n WorkflowRunResult,\n WorkflowSnapshot,\n WorkflowAgentStatus,\n} from './types.js';\n\nconst DEFAULT_CONCURRENCY_FLOOR = 1;\nconst DEFAULT_CONCURRENCY_CEILING = 16;\nconst DEFAULT_MAX_SUBAGENTS = 1000;\n\ninterface RuntimeState {\n currentPhase?: string;\n logs: string[];\n phases: string[];\n agentCount: number;\n spent: number;\n}\n\nexport interface RunWorkflowDeps {\n runner: SubagentRunner;\n /** Resolve a real model id (must contain `/`) to a {@link Model}. Throws on unknown id. */\n resolveModelId?: (modelId: string) => Model<Api>;\n}\n\nexport async function runWorkflow<T = unknown>(\n script: string,\n deps: RunWorkflowDeps,\n options: WorkflowRunOptions,\n): Promise<WorkflowRunResult<T>> {\n const started = Date.now();\n const { meta, body } = parseWorkflowScript(script);\n\n const state: RuntimeState = { logs: [], phases: [], agentCount: 0, spent: 0 };\n const concurrency = clampConcurrency(options.concurrency);\n const maxSubagents = Math.max(1, options.maxSubagents ?? DEFAULT_MAX_SUBAGENTS);\n const limiter = createLimiter(concurrency);\n const pendingAgentRuns = new Set<Promise<unknown>>();\n const phaseDefaultModels = buildPhaseModelMap(meta.phases);\n\n const log = (message: unknown) => {\n const text = String(message);\n state.logs.push(text);\n options.onLog?.(text);\n };\n\n const phase = (title: unknown) => {\n const text = requireString(title, 'phase title');\n state.currentPhase = text;\n if (!state.phases.includes(text)) state.phases.push(text);\n options.onPhase?.(text);\n };\n\n const budget = Object.freeze({\n total: options.tokenBudget ?? null,\n spent: () => state.spent,\n remaining: () =>\n options.tokenBudget == null\n ? Number.POSITIVE_INFINITY\n : Math.max(0, options.tokenBudget - state.spent),\n });\n\n const throwIfAborted = () => {\n if (options.signal?.aborted) throw new Error('workflow aborted');\n };\n\n const resolveAgentModel = (\n normalized: AgentScriptOptions,\n assignedPhase: string | undefined,\n ): Model<Api> | undefined => {\n const realId = normalized.model?.trim();\n if (realId) {\n if (!deps.resolveModelId) {\n throw new Error('workflow runtime missing resolveModelId; cannot resolve real model id');\n }\n return deps.resolveModelId(realId);\n }\n if (assignedPhase) {\n const phaseRef = phaseDefaultModels.get(assignedPhase);\n if (phaseRef) {\n if (phaseRef.includes('/')) {\n if (!deps.resolveModelId) return undefined;\n return deps.resolveModelId(phaseRef);\n }\n }\n }\n return undefined;\n };\n\n const agent = async (prompt: unknown, agentOptions: unknown = {}) => {\n throwIfAborted();\n if (budget.total !== null && budget.remaining() <= 0) {\n throw new Error('workflow token budget exhausted');\n }\n if (state.agentCount >= maxSubagents) {\n throw new Error(`workflow agent quota exhausted (max ${maxSubagents})`);\n }\n\n const taskPrompt = requireString(prompt, 'agent prompt');\n const normalized = normalizeAgentOptions(agentOptions);\n const assignedPhase = normalized.phase ?? state.currentPhase;\n const requestedLabel = normalized.label?.trim();\n\n const runPromise = limiter(async () => {\n // Counter increments inside the limiter — id reflects start order, not enqueue order.\n state.agentCount += 1;\n const id = state.agentCount;\n const label = requestedLabel || defaultAgentLabel(assignedPhase, id);\n options.onAgentStart?.({ id, label, phase: assignedPhase, prompt: taskPrompt });\n\n try {\n throwIfAborted();\n const resolvedModel = resolveAgentModel(normalized, assignedPhase);\n const result = await deps.runner.run<unknown>(taskPrompt, {\n label,\n schema: normalized.schema,\n allowedToolNames: normalized.toolset,\n maxIterations: normalized.maxIterations,\n phase: assignedPhase,\n signal: options.signal,\n model: resolvedModel,\n });\n throwIfAborted();\n\n const status: WorkflowAgentStatus = result === null ? 'error' : 'done';\n state.spent += estimateTokens(result);\n options.onAgentEnd?.({ id, label, phase: assignedPhase, result, status });\n return result;\n } catch (e) {\n if (options.signal?.aborted) {\n options.onAgentEnd?.({ id, label, phase: assignedPhase, result: null, status: 'skipped' });\n throw e;\n }\n const message = e instanceof Error ? e.message : String(e);\n log(`agent ${label} failed: ${message}`);\n options.onAgentEnd?.({ id, label, phase: assignedPhase, result: null, status: 'error' });\n return null;\n }\n });\n\n pendingAgentRuns.add(runPromise);\n // `then` (not `finally`) keeps the bookkeeping promise from re-throwing into\n // the unhandled-rejection channel when `runPromise` rejects on abort.\n runPromise.then(\n () => pendingAgentRuns.delete(runPromise),\n () => pendingAgentRuns.delete(runPromise),\n );\n return runPromise;\n };\n\n const parallel = async (thunks: unknown) => {\n throwIfAborted();\n if (!Array.isArray(thunks)) throw new TypeError('parallel() expects an array of functions');\n for (const t of thunks) {\n if (typeof t !== 'function') {\n throw new TypeError(\n 'parallel() expects an array of functions, not promises. Wrap each call: () => agent(...)',\n );\n }\n }\n return Promise.all(\n thunks.map(async (thunk, index) => {\n try {\n return await (thunk as () => unknown)();\n } catch (e) {\n if (options.signal?.aborted) throw e;\n const message = e instanceof Error ? e.message : String(e);\n log(`parallel[${index}] failed: ${message}`);\n return null;\n }\n }),\n );\n };\n\n const pipeline = async (items: unknown, ...stages: Array<unknown>) => {\n throwIfAborted();\n if (!Array.isArray(items)) {\n throw new TypeError('pipeline() expects an array as the first argument');\n }\n for (const stage of stages) {\n if (typeof stage !== 'function') {\n throw new TypeError(\n 'pipeline() stages must be functions: pipeline(items, item => ..., result => ...)',\n );\n }\n }\n const typedStages = stages as Array<\n (prev: unknown, original: unknown, index: number) => unknown\n >;\n return Promise.all(\n items.map(async (item, index) => {\n let value: unknown = item;\n for (const stage of typedStages) {\n try {\n throwIfAborted();\n value = await stage(value, item, index);\n throwIfAborted();\n } catch (e) {\n if (options.signal?.aborted) throw e;\n const message = e instanceof Error ? e.message : String(e);\n log(`pipeline[${index}] failed: ${message}`);\n return null;\n }\n }\n return value;\n }),\n );\n };\n\n const context = createContext({\n agent,\n parallel,\n pipeline,\n log,\n phase,\n args: options.args,\n cwd: options.cwd,\n process: Object.freeze({ cwd: () => options.cwd }),\n budget,\n console: {\n log,\n info: log,\n warn: (m: unknown) => log(`[warn] ${String(m)}`),\n error: (m: unknown) => log(`[error] ${String(m)}`),\n },\n JSON,\n Math,\n Array,\n Object,\n String,\n Number,\n Boolean,\n Set,\n Map,\n Promise,\n });\n\n const wrapped = `(async () => {\\n${body}\\n})()`;\n const script$ = new Script(wrapped, { filename: `${meta.name}.workflow.js` });\n\n let result: unknown;\n try {\n result = await script$.runInContext(context);\n // Wait for any agent() runs the script forgot to await before declaring success.\n await Promise.allSettled([...pendingAgentRuns]);\n } catch (e) {\n // Drain pending agent calls before propagating, so the snapshot reflects final state.\n await Promise.allSettled([...pendingAgentRuns]);\n throw rewriteScriptError(e);\n }\n\n assertStructuredCloneable(result, 'workflow result');\n\n return {\n meta,\n result: result as T,\n logs: state.logs,\n phases: state.phases,\n agentCount: state.agentCount,\n durationMs: Date.now() - started,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Initial snapshot helper — kept here so the runtime is the single source of\n// truth for \"what a fresh snapshot looks like for this workflow\".\n// ---------------------------------------------------------------------------\n\nexport function emptySnapshotFor(name: string, description?: string): WorkflowSnapshot {\n return {\n name,\n description,\n phases: [],\n logs: [],\n agents: [],\n agentCount: 0,\n runningCount: 0,\n doneCount: 0,\n errorCount: 0,\n skippedCount: 0,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\nfunction clampConcurrency(requested?: number): number {\n if (typeof requested === 'number' && Number.isFinite(requested) && requested >= 1) {\n return Math.min(Math.floor(requested), DEFAULT_CONCURRENCY_CEILING);\n }\n let cpu = DEFAULT_CONCURRENCY_CEILING;\n try {\n cpu = availableParallelism();\n } catch {\n // Some sandboxes throw; fall back to the ceiling.\n }\n return Math.max(DEFAULT_CONCURRENCY_FLOOR, Math.min(DEFAULT_CONCURRENCY_CEILING, cpu - 2));\n}\n\nfunction createLimiter(limit: number) {\n let active = 0;\n const queue: Array<() => void> = [];\n const next = () => {\n active -= 1;\n const resume = queue.shift();\n if (resume) resume();\n };\n return async <T>(fn: () => Promise<T>): Promise<T> => {\n if (active >= limit) {\n await new Promise<void>((resolve) => queue.push(resolve));\n }\n active += 1;\n try {\n return await fn();\n } finally {\n next();\n }\n };\n}\n\nfunction buildPhaseModelMap(phases: WorkflowMetaPhase[] | undefined): Map<string, string> {\n const out = new Map<string, string>();\n if (!phases) return out;\n for (const p of phases) {\n if (p && typeof p.title === 'string' && typeof p.model === 'string' && p.model.trim()) {\n out.set(p.title, p.model.trim());\n }\n }\n return out;\n}\n\nfunction requireString(value: unknown, name: string): string {\n if (typeof value !== 'string') throw new TypeError(`${name} must be a string`);\n return value;\n}\n\nfunction optionalString(value: unknown, name: string): string | undefined {\n if (value === undefined) return undefined;\n return requireString(value, name);\n}\n\nfunction normalizeAgentOptions(value: unknown): AgentScriptOptions {\n if (value === undefined || value === null) return {};\n if (typeof value !== 'object') throw new TypeError('agent options must be an object');\n const options = value as AgentScriptOptions;\n const toolset = options.toolset;\n if (toolset !== undefined) {\n if (!Array.isArray(toolset) || toolset.some((t) => typeof t !== 'string')) {\n throw new TypeError('agent toolset must be an array of strings');\n }\n }\n const maxIterations = options.maxIterations;\n if (maxIterations !== undefined) {\n if (typeof maxIterations !== 'number' || !Number.isFinite(maxIterations) || maxIterations < 1) {\n throw new TypeError('agent maxIterations must be a positive number');\n }\n }\n const modelStr = optionalString(options.model, 'agent model');\n if (modelStr !== undefined) {\n const trimmed = modelStr.trim();\n if (trimmed && !trimmed.includes('/')) {\n throw new Error(\n `agent option 'model' must be a real model id in 'provider/model' form (got '${trimmed}')`,\n );\n }\n }\n return {\n label: optionalString(options.label, 'agent label'),\n phase: optionalString(options.phase, 'agent phase'),\n schema: options.schema,\n model: modelStr,\n toolset,\n maxIterations,\n };\n}\n\nfunction assertStructuredCloneable(value: unknown, name: string): void {\n try {\n structuredClone(value);\n } catch (e) {\n const promisePath = findPromisePath(value, '');\n if (promisePath !== null) {\n throw new Error(\n `${name} contains a Promise at \\`${promisePath || '<root>'}\\` — missing 'await' before parallel()/pipeline()/agent(). ` +\n `Async return auto-unwraps a bare Promise, but a Promise nested in an object/array is returned as-is.`,\n );\n }\n const detail = e instanceof Error ? ` ${e.message}` : '';\n throw new Error(\n `${name} must be structured-cloneable; did you forget to await agent(), parallel(), or pipeline()?${detail}`,\n );\n }\n}\n\n/**\n * Find the first Promise lurking in `value`, returning a JS-path like\n * \"results[0].pending\". Returns null when no Promise is found. Bounded\n * depth/breadth so it never explodes on weird user shapes.\n */\nfunction findPromisePath(value: unknown, path: string, depth = 0): string | null {\n if (depth > 4) return null;\n if (isThenable(value)) return path;\n if (Array.isArray(value)) {\n for (let i = 0; i < Math.min(value.length, 32); i++) {\n const inner = findPromisePath(value[i], `${path}[${i}]`, depth + 1);\n if (inner !== null) return inner;\n }\n return null;\n }\n if (value !== null && typeof value === 'object') {\n let count = 0;\n for (const [key, child] of Object.entries(value as Record<string, unknown>)) {\n if (count++ > 32) break;\n const inner = findPromisePath(child, path ? `${path}.${key}` : key, depth + 1);\n if (inner !== null) return inner;\n }\n }\n return null;\n}\n\nfunction isThenable(value: unknown): boolean {\n return (\n value !== null &&\n typeof value === 'object' &&\n typeof (value as { then?: unknown }).then === 'function'\n );\n}\n\n/**\n * Rewrite TypeErrors that look like \"called .map on a Promise\" with an\n * await-shaped hint. Static lint catches the obvious form at parse time;\n * this is the safety net for cases lint can't see (dynamic indirection,\n * values stashed in helper closures).\n */\nconst PROMISE_METHOD_ERROR =\n /\\.(map|filter|forEach|flat|flatMap|find|some|every|reduce|join|length|then)\\b is not a function/;\n\nfunction rewriteScriptError(error: unknown): Error {\n // VM-context errors aren't `instanceof` host classes (the vm has its own\n // global constructors), so check by name + duck-typing message.\n const asError = error as { name?: string; message?: string } | null | undefined;\n const isTypeError = asError?.name === 'TypeError' && typeof asError.message === 'string';\n if (!isTypeError) {\n return error instanceof Error ? error : new Error(String(error));\n }\n const message = (asError as { message: string }).message;\n if (!PROMISE_METHOD_ERROR.test(message)) {\n return error instanceof Error ? error : new Error(message);\n }\n return new Error(\n `${message}\\n\\nHint: parallel()/pipeline()/agent() return a Promise — 'await' the call before using its result.\\n` +\n ` ❌ const r = parallel(...); r.map(...)\\n` +\n ` ✅ const r = await parallel(...); r.map(...)`,\n );\n}\n\nfunction defaultAgentLabel(phase: string | undefined, index: number): string {\n return phase ? `${phase} agent ${index}` : `agent ${index}`;\n}\n\nfunction estimateTokens(value: unknown): number {\n if (value === null || value === undefined) return 0;\n try {\n return Math.ceil(JSON.stringify(value).length / 4);\n } catch {\n return Math.ceil(String(value).length / 4);\n }\n}\n"],"mappings":"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"}
@@ -24,11 +24,19 @@ export interface WorkflowMetaPhase {
24
24
  detail?: string;
25
25
  model?: string;
26
26
  }
27
+ export interface WorkflowMetaEstimatedAgents {
28
+ min: number;
29
+ max: number;
30
+ }
27
31
  export interface WorkflowMeta {
28
32
  name: string;
29
33
  description: string;
30
34
  whenToUse?: string;
31
35
  phases?: WorkflowMetaPhase[];
36
+ /** Discovery tags, e.g. `['code-review', 'planning']`. */
37
+ tags?: string[];
38
+ /** Rough subagent count range for UX / cost hints. */
39
+ estimatedAgents?: WorkflowMetaEstimatedAgents;
32
40
  }
33
41
  export type WorkflowAgentStatus = 'queued' | 'running' | 'done' | 'error' | 'skipped';
34
42
  export interface WorkflowAgentSnapshot {
@@ -94,7 +102,9 @@ export interface AgentScriptOptions {
94
102
  label?: string;
95
103
  phase?: string;
96
104
  schema?: JsonSchema;
97
- /** Model id; currently passed as text guidance, runner may resolve in future. */
105
+ /**
106
+ * Real model id (e.g. `openai/gpt-4o-mini`) — must contain `/`.
107
+ */
98
108
  model?: string;
99
109
  /** Subagent tool allowlist override (forwarded to the runner). */
100
110
  toolset?: string[];
@@ -1,12 +1,12 @@
1
- import { resolveBrowserCommandTimeoutMs } from "./browser-command-timeout.js";
2
- import { BrowserBackSchema, BrowserCdpSchema, BrowserClickSchema, BrowserCloseSchema, BrowserConsoleSchema, BrowserDialogSchema, BrowserGetImagesSchema, BrowserNavigateSchema, BrowserPressSchema, BrowserScreenshotSchema, BrowserScrollSchema, BrowserSnapshotSchema, BrowserTypeSchema, BrowserVisionSchema } from "./schemas.js";
3
- import { assertBrowserUrlAllowed, checkPostRedirectUrl, containsApiKeyPattern, isAlwaysBlockedUrl } from "./url-policy.js";
4
- import { snapshotSummaryHeader, truncateSnapshotAtBoundary } from "./snapshot-helpers.js";
5
1
  import { BrowserManager } from "./manager.js";
6
2
  import { resolveBrowserBackendFromConfig } from "./backend-from-config.js";
3
+ import { resolveBrowserCommandTimeoutMs } from "./browser-command-timeout.js";
7
4
  import { BrowserNotReadyError, buildBrowserSetupDeepLink, checkBrowserReadiness } from "./readiness.js";
5
+ import { assertBrowserUrlAllowed, checkPostRedirectUrl, containsApiKeyPattern, isAlwaysBlockedUrl } from "./url-policy.js";
8
6
  import { CdpSupervisor } from "./cdp-supervisor.js";
7
+ import { snapshotSummaryHeader, truncateSnapshotAtBoundary } from "./snapshot-helpers.js";
9
8
  import { checkBotDetection, cleanupOrphanProcesses, startTracing, stopTracing } from "./session-lifecycle.js";
9
+ import { BrowserBackSchema, BrowserCdpSchema, BrowserClickSchema, BrowserCloseSchema, BrowserConsoleSchema, BrowserDialogSchema, BrowserGetImagesSchema, BrowserNavigateSchema, BrowserPressSchema, BrowserScreenshotSchema, BrowserScrollSchema, BrowserSnapshotSchema, BrowserTypeSchema, BrowserVisionSchema } from "./schemas.js";
10
10
  import { generateMousePath, generateScrollPlan, generateTypingPlan, humanizedClick, humanizedFill, humanizedPress, humanizedScroll, resolveHumanConfig } from "./humanize.js";
11
11
  import { WEBDRIVER_OVERRIDE_SCRIPT, buildLocalStealthArgs, buildStealthArgs, generateFingerprintSeed, makeExecutable, removeQuarantineAttr } from "./stealth.js";
12
12
  import { createBrowserActionRegistry } from "./actions/registry.js";
@@ -1,4 +1,4 @@
1
- import type { BrowserContext, Page } from 'playwright-core';
1
+ import type { Page } from 'playwright-core';
2
2
  import type { BrowserBackend } from './providers/types.js';
3
3
  import type { ExtensionBrowserProvider } from './providers/extension.js';
4
4
  export interface BrowserManagerOptions {
@@ -37,8 +37,6 @@ export declare class BrowserManager {
37
37
  * Extension mode does not create a Playwright {@link BrowserContext}.
38
38
  */
39
39
  ensureConnected(): Promise<void>;
40
- /** @deprecated Use {@link ensureConnected}. */
41
- ensureBrowser(): Promise<BrowserContext>;
42
40
  private _launchLocal;
43
41
  private _connectViaCdp;
44
42
  private _connectViaCloud;
@@ -118,12 +118,6 @@ var BrowserManager = class {
118
118
  this.activeBackendMode = backend.mode;
119
119
  if (this.browser) this._wireBrowserLifecycle(this.browser);
120
120
  }
121
- /** @deprecated Use {@link ensureConnected}. */
122
- async ensureBrowser() {
123
- await this.ensureConnected();
124
- if (!this.context) throw new Error("ensureBrowser: no Playwright context (extension backend uses ensureConnected + registry bridge)");
125
- return this.context;
126
- }
127
121
  async _launchLocal(headless) {
128
122
  const pw = await loadPlaywrightCoreModule();
129
123
  const chromium = pw.chromium ?? pw.default?.chromium;
@@ -1 +1 @@
1
- {"version":3,"file":"manager.js","names":[],"sources":["../../../src/browser/manager.ts"],"sourcesContent":["import type { Browser, BrowserContext, Page } from 'playwright-core';\n\nimport { createLogger } from '../utils/logger.js';\n\nimport { loadPlaywrightCoreModule } from './providers/playwright-doctor.js';\nimport type { BrowserBackend, CloudBrowserProvider, CloudBrowserProviderConfig, ExtensionConnectionConfig } from './providers/types.js';\nimport type { ExtensionBrowserProvider } from './providers/extension.js';\n\nconst log = createLogger('browser-manager');\n\nconst MAX_PAGES = 3;\nconst PAGE_IDLE_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport interface BrowserManagerOptions {\n getHeadless: () => boolean;\n /** Backend connection mode. Default: local Playwright. */\n getBackend?: () => BrowserBackend;\n}\n\n/**\n * Multi-backend browser manager — supports local Playwright, direct CDP, and cloud providers.\n *\n * One browser context shared by all sessions; one {@link Page} per task/session key (max {@link MAX_PAGES}).\n * Backend is selected at first connection based on {@link BrowserManagerOptions.getBackend}.\n */\nexport class BrowserManager {\n private browser: Browser | null = null;\n private context: BrowserContext | null = null;\n private cloudProvider: CloudBrowserProvider | null = null;\n private extensionProvider: ExtensionBrowserProvider | null = null;\n private extensionRelease: (() => Promise<void>) | null = null;\n private cloakChildProcess: import('node:child_process').ChildProcess | null = null;\n private cloakTempProfileDir: string | null = null;\n private pages = new Map<string, { page: Page; lastUsed: number }>();\n private readonly options: BrowserManagerOptions;\n private activeBackendMode: BrowserBackend['mode'] | null = null;\n\n constructor(options: BrowserManagerOptions) {\n this.options = options;\n }\n\n /** Current backend mode (null if not yet connected). */\n get backendMode(): string | null {\n return this.activeBackendMode;\n }\n\n private evictIdlePages(): void {\n const now = Date.now();\n for (const [id, entry] of this.pages) {\n if (entry.page.isClosed() || now - entry.lastUsed > PAGE_IDLE_TIMEOUT_MS) {\n void entry.page.close().catch(() => {});\n this.pages.delete(id);\n log.debug({ taskId: id }, 'Evicted idle or closed browser page');\n }\n }\n }\n\n private _isPlaywrightConnectionAlive(): boolean {\n if (!this.browser || !this.context) return false;\n try {\n if (typeof this.browser.isConnected === 'function' && !this.browser.isConnected()) {\n return false;\n }\n return true;\n } catch {\n return false;\n }\n }\n\n private _wireBrowserLifecycle(browser: Browser): void {\n browser.on('disconnected', () => {\n log.warn({ mode: this.activeBackendMode }, 'Browser disconnected — clearing stale session');\n this._clearPlaywrightSessionRefs();\n });\n }\n\n /** Drop Playwright handles without tearing down extension bridge. */\n private _clearPlaywrightSessionRefs(): void {\n this.pages.clear();\n this.context = null;\n this.browser = null;\n this.cloakChildProcess = null;\n this.cloakTempProfileDir = null;\n if (this.cloudProvider) {\n void this.cloudProvider.disconnect().catch(() => {});\n this.cloudProvider = null;\n }\n }\n\n private async _resetStalePlaywrightSession(): Promise<void> {\n for (const [, entry] of this.pages) {\n await entry.page.close().catch(() => {});\n }\n this.pages.clear();\n\n if (this.cloudProvider) {\n await this.cloudProvider.disconnect().catch(() => {});\n this.cloudProvider = null;\n }\n\n if (this.cloakChildProcess || this.cloakTempProfileDir) {\n const { cleanupCloakBrowser } = await import('./providers/cloakbrowser.js');\n await cleanupCloakBrowser(this.cloakChildProcess, this.cloakTempProfileDir).catch(() => {});\n this.cloakChildProcess = null;\n this.cloakTempProfileDir = null;\n }\n\n await this.context?.close().catch(() => {});\n await this.browser?.close().catch(() => {});\n this.context = null;\n this.browser = null;\n }\n\n /**\n * Ensure Playwright context or Chrome Extension provider is ready.\n * Extension mode does not create a Playwright {@link BrowserContext}.\n */\n async ensureConnected(): Promise<void> {\n if (this.extensionProvider) return;\n\n if (this.context && this._isPlaywrightConnectionAlive()) {\n return;\n }\n\n if (this.context || this.browser) {\n log.warn({ mode: this.activeBackendMode }, 'Browser session unavailable — reconnecting');\n await this._resetStalePlaywrightSession();\n }\n\n const backend = this.options.getBackend?.() ?? { mode: 'local' as const, headless: false };\n\n switch (backend.mode) {\n case 'cdp':\n await this._connectViaCdp(backend.config.wsEndpoint);\n break;\n case 'cloud':\n await this._connectViaCloud(backend.config);\n break;\n case 'extension':\n await this._connectViaExtension(backend.config);\n break;\n case 'cloakbrowser':\n await this._connectViaCloakBrowser(backend.config);\n break;\n case 'local':\n default:\n await this._launchLocal(backend.mode === 'local' ? backend.headless : this.options.getHeadless() === true);\n break;\n }\n\n this.activeBackendMode = backend.mode;\n if (this.browser) {\n this._wireBrowserLifecycle(this.browser);\n }\n }\n\n /** @deprecated Use {@link ensureConnected}. */\n async ensureBrowser(): Promise<BrowserContext> {\n await this.ensureConnected();\n if (!this.context) {\n throw new Error('ensureBrowser: no Playwright context (extension backend uses ensureConnected + registry bridge)');\n }\n return this.context;\n }\n\n private async _launchLocal(headless: boolean): Promise<void> {\n const pw = await loadPlaywrightCoreModule();\n const chromium = pw.chromium ?? (pw as { default?: { chromium?: (typeof pw)['chromium'] } }).default?.chromium;\n if (!chromium?.launch) {\n throw new Error(\n 'playwright-core did not expose chromium (try reinstall: pnpm install playwright-core; install browser: npx playwright install chromium)',\n );\n }\n this.browser = await chromium.launch({\n headless,\n ...(headless ? { channel: 'chromium' } : {}),\n args: ['--no-sandbox', '--disable-setuid-sandbox'],\n });\n this.context = await this.browser.newContext({\n viewport: { width: 1280, height: 720 },\n userAgent:\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',\n });\n log.info({ headless, mode: 'local' }, 'Browser launched (local)');\n }\n\n private async _connectViaCdp(wsEndpoint: string): Promise<void> {\n const pw = await loadPlaywrightCoreModule();\n const chromium = pw.chromium ?? (pw as { default?: { chromium?: (typeof pw)['chromium'] } }).default?.chromium;\n if (!chromium?.connectOverCDP) {\n throw new Error('playwright-core does not support connectOverCDP');\n }\n this.browser = await chromium.connectOverCDP(wsEndpoint);\n const contexts = this.browser.contexts();\n this.context = contexts.length > 0 ? contexts[0] : await this.browser.newContext();\n log.info({ mode: 'cdp', wsEndpoint }, 'Browser connected (CDP)');\n }\n\n private async _connectViaCloud(config: CloudBrowserProviderConfig): Promise<void> {\n const { BrowserbaseProvider } = await import('./providers/browserbase.js');\n const { BrowserUseProvider } = await import('./providers/browser-use.js');\n\n const provider = config.type === 'browserbase'\n ? new BrowserbaseProvider(config)\n : new BrowserUseProvider(config);\n\n const { browser, context } = await provider.connect();\n this.browser = browser;\n this.context = context;\n this.cloudProvider = provider;\n log.info({ mode: 'cloud', provider: config.type }, `Browser connected (${config.type})`);\n }\n\n private async _connectViaExtension(config?: ExtensionConnectionConfig): Promise<void> {\n const { acquireExtensionBrowserServer } = await import('./providers/extension-ws-acquire.js');\n const { provider, release } = await acquireExtensionBrowserServer(config);\n this.extensionProvider = provider;\n this.extensionRelease = release;\n log.info({ port: config?.port ?? 19820 }, 'Extension WS server ready, waiting for Chrome Extension...');\n await provider.waitForConnection();\n // Extension mode does not use Playwright — context stays null.\n // The action registry dispatches directly via extensionProvider.sendCommand().\n log.info({ mode: 'extension' }, 'Browser connected (Chrome Extension)');\n }\n\n private async _connectViaCloakBrowser(config?: import('./providers/types.js').CloakBrowserConfig): Promise<void> {\n const { launchCloakBrowser } = await import('./providers/cloakbrowser.js');\n const result = await launchCloakBrowser(config);\n if (!result.browser || !result.context) {\n throw new Error('BrowserManager: CloakBrowser launch did not return a Playwright connection');\n }\n this.browser = result.browser;\n this.context = result.context;\n this.cloakChildProcess = result.childProcess;\n this.cloakTempProfileDir = result.temporaryProfileDir;\n log.info({ mode: 'cloakbrowser' }, 'Browser connected (CloakBrowser)');\n }\n\n async getPage(taskId: string): Promise<Page> {\n if (this.extensionProvider) {\n throw new Error('BrowserManager.getPage is not used in Chrome Extension backend mode');\n }\n\n this.evictIdlePages();\n await this.ensureConnected();\n\n const existing = this.pages.get(taskId);\n if (existing && !existing.page.isClosed()) {\n existing.lastUsed = Date.now();\n return existing.page;\n }\n if (existing) {\n this.pages.delete(taskId);\n }\n\n if (this.pages.size >= MAX_PAGES) {\n const oldest = [...this.pages.entries()].sort((a, b) => a[1].lastUsed - b[1].lastUsed)[0];\n if (oldest) {\n await oldest[1].page.close().catch(() => {});\n this.pages.delete(oldest[0]);\n }\n }\n\n const ctx = this.context;\n if (!ctx) {\n throw new Error('BrowserManager: Playwright context missing after ensureConnected');\n }\n const page = await ctx.newPage();\n this.pages.set(taskId, { page, lastUsed: Date.now() });\n return page;\n }\n\n async closePage(taskId: string): Promise<void> {\n if (this.extensionProvider) {\n return;\n }\n const entry = this.pages.get(taskId);\n if (entry) {\n await entry.page.close().catch(() => {});\n this.pages.delete(taskId);\n }\n }\n\n /** Get the extension provider (only available in extension mode). */\n getExtensionProvider(): ExtensionBrowserProvider | null {\n return this.extensionProvider;\n }\n\n async shutdown(): Promise<void> {\n for (const [, entry] of this.pages) {\n await entry.page.close().catch(() => {});\n }\n this.pages.clear();\n\n if (this.cloudProvider) {\n await this.cloudProvider.disconnect().catch(() => {});\n this.cloudProvider = null;\n }\n\n if (this.extensionRelease) {\n await this.extensionRelease().catch(() => {});\n this.extensionRelease = null;\n }\n this.extensionProvider = null;\n\n // CloakBrowser cleanup — kill child process and remove temp profile\n if (this.cloakChildProcess || this.cloakTempProfileDir) {\n const { cleanupCloakBrowser } = await import('./providers/cloakbrowser.js');\n await cleanupCloakBrowser(this.cloakChildProcess, this.cloakTempProfileDir).catch(() => {});\n this.cloakChildProcess = null;\n this.cloakTempProfileDir = null;\n }\n\n await this.context?.close().catch(() => {});\n await this.browser?.close().catch(() => {});\n this.context = null;\n this.browser = null;\n this.activeBackendMode = null;\n log.info('Browser shut down');\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"manager.js","names":[],"sources":["../../../src/browser/manager.ts"],"sourcesContent":["import type { Browser, BrowserContext, Page } from 'playwright-core';\n\nimport { createLogger } from '../utils/logger.js';\n\nimport { loadPlaywrightCoreModule } from './providers/playwright-doctor.js';\nimport type { BrowserBackend, CloudBrowserProvider, CloudBrowserProviderConfig, ExtensionConnectionConfig } from './providers/types.js';\nimport type { ExtensionBrowserProvider } from './providers/extension.js';\n\nconst log = createLogger('browser-manager');\n\nconst MAX_PAGES = 3;\nconst PAGE_IDLE_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport interface BrowserManagerOptions {\n getHeadless: () => boolean;\n /** Backend connection mode. Default: local Playwright. */\n getBackend?: () => BrowserBackend;\n}\n\n/**\n * Multi-backend browser manager — supports local Playwright, direct CDP, and cloud providers.\n *\n * One browser context shared by all sessions; one {@link Page} per task/session key (max {@link MAX_PAGES}).\n * Backend is selected at first connection based on {@link BrowserManagerOptions.getBackend}.\n */\nexport class BrowserManager {\n private browser: Browser | null = null;\n private context: BrowserContext | null = null;\n private cloudProvider: CloudBrowserProvider | null = null;\n private extensionProvider: ExtensionBrowserProvider | null = null;\n private extensionRelease: (() => Promise<void>) | null = null;\n private cloakChildProcess: import('node:child_process').ChildProcess | null = null;\n private cloakTempProfileDir: string | null = null;\n private pages = new Map<string, { page: Page; lastUsed: number }>();\n private readonly options: BrowserManagerOptions;\n private activeBackendMode: BrowserBackend['mode'] | null = null;\n\n constructor(options: BrowserManagerOptions) {\n this.options = options;\n }\n\n /** Current backend mode (null if not yet connected). */\n get backendMode(): string | null {\n return this.activeBackendMode;\n }\n\n private evictIdlePages(): void {\n const now = Date.now();\n for (const [id, entry] of this.pages) {\n if (entry.page.isClosed() || now - entry.lastUsed > PAGE_IDLE_TIMEOUT_MS) {\n void entry.page.close().catch(() => {});\n this.pages.delete(id);\n log.debug({ taskId: id }, 'Evicted idle or closed browser page');\n }\n }\n }\n\n private _isPlaywrightConnectionAlive(): boolean {\n if (!this.browser || !this.context) return false;\n try {\n if (typeof this.browser.isConnected === 'function' && !this.browser.isConnected()) {\n return false;\n }\n return true;\n } catch {\n return false;\n }\n }\n\n private _wireBrowserLifecycle(browser: Browser): void {\n browser.on('disconnected', () => {\n log.warn({ mode: this.activeBackendMode }, 'Browser disconnected — clearing stale session');\n this._clearPlaywrightSessionRefs();\n });\n }\n\n /** Drop Playwright handles without tearing down extension bridge. */\n private _clearPlaywrightSessionRefs(): void {\n this.pages.clear();\n this.context = null;\n this.browser = null;\n this.cloakChildProcess = null;\n this.cloakTempProfileDir = null;\n if (this.cloudProvider) {\n void this.cloudProvider.disconnect().catch(() => {});\n this.cloudProvider = null;\n }\n }\n\n private async _resetStalePlaywrightSession(): Promise<void> {\n for (const [, entry] of this.pages) {\n await entry.page.close().catch(() => {});\n }\n this.pages.clear();\n\n if (this.cloudProvider) {\n await this.cloudProvider.disconnect().catch(() => {});\n this.cloudProvider = null;\n }\n\n if (this.cloakChildProcess || this.cloakTempProfileDir) {\n const { cleanupCloakBrowser } = await import('./providers/cloakbrowser.js');\n await cleanupCloakBrowser(this.cloakChildProcess, this.cloakTempProfileDir).catch(() => {});\n this.cloakChildProcess = null;\n this.cloakTempProfileDir = null;\n }\n\n await this.context?.close().catch(() => {});\n await this.browser?.close().catch(() => {});\n this.context = null;\n this.browser = null;\n }\n\n /**\n * Ensure Playwright context or Chrome Extension provider is ready.\n * Extension mode does not create a Playwright {@link BrowserContext}.\n */\n async ensureConnected(): Promise<void> {\n if (this.extensionProvider) return;\n\n if (this.context && this._isPlaywrightConnectionAlive()) {\n return;\n }\n\n if (this.context || this.browser) {\n log.warn({ mode: this.activeBackendMode }, 'Browser session unavailable — reconnecting');\n await this._resetStalePlaywrightSession();\n }\n\n const backend = this.options.getBackend?.() ?? { mode: 'local' as const, headless: false };\n\n switch (backend.mode) {\n case 'cdp':\n await this._connectViaCdp(backend.config.wsEndpoint);\n break;\n case 'cloud':\n await this._connectViaCloud(backend.config);\n break;\n case 'extension':\n await this._connectViaExtension(backend.config);\n break;\n case 'cloakbrowser':\n await this._connectViaCloakBrowser(backend.config);\n break;\n case 'local':\n default:\n await this._launchLocal(backend.mode === 'local' ? backend.headless : this.options.getHeadless() === true);\n break;\n }\n\n this.activeBackendMode = backend.mode;\n if (this.browser) {\n this._wireBrowserLifecycle(this.browser);\n }\n }\n\n private async _launchLocal(headless: boolean): Promise<void> {\n const pw = await loadPlaywrightCoreModule();\n const chromium = pw.chromium ?? (pw as { default?: { chromium?: (typeof pw)['chromium'] } }).default?.chromium;\n if (!chromium?.launch) {\n throw new Error(\n 'playwright-core did not expose chromium (try reinstall: pnpm install playwright-core; install browser: npx playwright install chromium)',\n );\n }\n this.browser = await chromium.launch({\n headless,\n ...(headless ? { channel: 'chromium' } : {}),\n args: ['--no-sandbox', '--disable-setuid-sandbox'],\n });\n this.context = await this.browser.newContext({\n viewport: { width: 1280, height: 720 },\n userAgent:\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',\n });\n log.info({ headless, mode: 'local' }, 'Browser launched (local)');\n }\n\n private async _connectViaCdp(wsEndpoint: string): Promise<void> {\n const pw = await loadPlaywrightCoreModule();\n const chromium = pw.chromium ?? (pw as { default?: { chromium?: (typeof pw)['chromium'] } }).default?.chromium;\n if (!chromium?.connectOverCDP) {\n throw new Error('playwright-core does not support connectOverCDP');\n }\n this.browser = await chromium.connectOverCDP(wsEndpoint);\n const contexts = this.browser.contexts();\n this.context = contexts.length > 0 ? contexts[0] : await this.browser.newContext();\n log.info({ mode: 'cdp', wsEndpoint }, 'Browser connected (CDP)');\n }\n\n private async _connectViaCloud(config: CloudBrowserProviderConfig): Promise<void> {\n const { BrowserbaseProvider } = await import('./providers/browserbase.js');\n const { BrowserUseProvider } = await import('./providers/browser-use.js');\n\n const provider = config.type === 'browserbase'\n ? new BrowserbaseProvider(config)\n : new BrowserUseProvider(config);\n\n const { browser, context } = await provider.connect();\n this.browser = browser;\n this.context = context;\n this.cloudProvider = provider;\n log.info({ mode: 'cloud', provider: config.type }, `Browser connected (${config.type})`);\n }\n\n private async _connectViaExtension(config?: ExtensionConnectionConfig): Promise<void> {\n const { acquireExtensionBrowserServer } = await import('./providers/extension-ws-acquire.js');\n const { provider, release } = await acquireExtensionBrowserServer(config);\n this.extensionProvider = provider;\n this.extensionRelease = release;\n log.info({ port: config?.port ?? 19820 }, 'Extension WS server ready, waiting for Chrome Extension...');\n await provider.waitForConnection();\n // Extension mode does not use Playwright — context stays null.\n // The action registry dispatches directly via extensionProvider.sendCommand().\n log.info({ mode: 'extension' }, 'Browser connected (Chrome Extension)');\n }\n\n private async _connectViaCloakBrowser(config?: import('./providers/types.js').CloakBrowserConfig): Promise<void> {\n const { launchCloakBrowser } = await import('./providers/cloakbrowser.js');\n const result = await launchCloakBrowser(config);\n if (!result.browser || !result.context) {\n throw new Error('BrowserManager: CloakBrowser launch did not return a Playwright connection');\n }\n this.browser = result.browser;\n this.context = result.context;\n this.cloakChildProcess = result.childProcess;\n this.cloakTempProfileDir = result.temporaryProfileDir;\n log.info({ mode: 'cloakbrowser' }, 'Browser connected (CloakBrowser)');\n }\n\n async getPage(taskId: string): Promise<Page> {\n if (this.extensionProvider) {\n throw new Error('BrowserManager.getPage is not used in Chrome Extension backend mode');\n }\n\n this.evictIdlePages();\n await this.ensureConnected();\n\n const existing = this.pages.get(taskId);\n if (existing && !existing.page.isClosed()) {\n existing.lastUsed = Date.now();\n return existing.page;\n }\n if (existing) {\n this.pages.delete(taskId);\n }\n\n if (this.pages.size >= MAX_PAGES) {\n const oldest = [...this.pages.entries()].sort((a, b) => a[1].lastUsed - b[1].lastUsed)[0];\n if (oldest) {\n await oldest[1].page.close().catch(() => {});\n this.pages.delete(oldest[0]);\n }\n }\n\n const ctx = this.context;\n if (!ctx) {\n throw new Error('BrowserManager: Playwright context missing after ensureConnected');\n }\n const page = await ctx.newPage();\n this.pages.set(taskId, { page, lastUsed: Date.now() });\n return page;\n }\n\n async closePage(taskId: string): Promise<void> {\n if (this.extensionProvider) {\n return;\n }\n const entry = this.pages.get(taskId);\n if (entry) {\n await entry.page.close().catch(() => {});\n this.pages.delete(taskId);\n }\n }\n\n /** Get the extension provider (only available in extension mode). */\n getExtensionProvider(): ExtensionBrowserProvider | null {\n return this.extensionProvider;\n }\n\n async shutdown(): Promise<void> {\n for (const [, entry] of this.pages) {\n await entry.page.close().catch(() => {});\n }\n this.pages.clear();\n\n if (this.cloudProvider) {\n await this.cloudProvider.disconnect().catch(() => {});\n this.cloudProvider = null;\n }\n\n if (this.extensionRelease) {\n await this.extensionRelease().catch(() => {});\n this.extensionRelease = null;\n }\n this.extensionProvider = null;\n\n // CloakBrowser cleanup — kill child process and remove temp profile\n if (this.cloakChildProcess || this.cloakTempProfileDir) {\n const { cleanupCloakBrowser } = await import('./providers/cloakbrowser.js');\n await cleanupCloakBrowser(this.cloakChildProcess, this.cloakTempProfileDir).catch(() => {});\n this.cloakChildProcess = null;\n this.cloakTempProfileDir = null;\n }\n\n await this.context?.close().catch(() => {});\n await this.browser?.close().catch(() => {});\n this.context = null;\n this.browser = null;\n this.activeBackendMode = null;\n log.info('Browser shut down');\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Install bundled Chrome extension artifacts into {resolveBinDir()}/browser-ext/{version}/.
3
- * Single version directory per install direct overwrite, no `current` symlink.
2
+ * Install bundled Chrome extension artifacts into {resolveBinDir()}/browser-ext/.
3
+ * Fixed path gateway upgrades overwrite in place so Chrome sideload paths stay stable.
4
4
  */
5
5
  export declare const BROWSER_EXT_REQUIRED_FILES: readonly ["manifest.json", "popup.html", "dist/background.js", "dist/content.js", "dist/popup.js"];
6
6
  export type BrowserExtBundledFrom = 'npm-dist' | 'git-dev' | 'electron-asar' | 'env-override';
@@ -32,8 +32,8 @@ export interface EnsureBrowserExtResult {
32
32
  }
33
33
  /** Validate a directory contains a loadable extension tree. */
34
34
  export declare function validateBrowserExtLayout(dir: string): boolean;
35
- /** Resolve the installed extension directory (version folder, not a symlink). */
36
- export declare function resolveInstalledExtensionPath(cacheDir: string, meta: BrowserExtInstallMeta | null): string | null;
35
+ /** Resolve the installed extension directory (fixed `browser-ext/` root). */
36
+ export declare function resolveInstalledExtensionPath(cacheDir: string, _meta: BrowserExtInstallMeta | null): string | null;
37
37
  /**
38
38
  * Resolve the bundled extension source directory (read-only).
39
39
  */
@@ -6,14 +6,14 @@ import { init_paths, resolveBinDir } from "../../config/paths.js";
6
6
  import { init_write_file_atomic, writeTextAtomic } from "../../infra/write-file-atomic.js";
7
7
  import { resolvePackageRoot } from "../../infra/update-check.js";
8
8
  import { dirname, join } from "node:path";
9
- import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
9
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
10
10
  import { readFile, readdir, rm } from "node:fs/promises";
11
11
  import { spawn } from "node:child_process";
12
12
  import { fileURLToPath } from "node:url";
13
13
  //#region src/browser/providers/browser-ext-install.ts
14
14
  /**
15
- * Install bundled Chrome extension artifacts into {resolveBinDir()}/browser-ext/{version}/.
16
- * Single version directory per install direct overwrite, no `current` symlink.
15
+ * Install bundled Chrome extension artifacts into {resolveBinDir()}/browser-ext/.
16
+ * Fixed path gateway upgrades overwrite in place so Chrome sideload paths stay stable.
17
17
  */
18
18
  init_package_version();
19
19
  init_paths();
@@ -22,9 +22,13 @@ init_logger();
22
22
  init_cache_dir_policy();
23
23
  const log = createLogger("BrowserExtInstall");
24
24
  const META_FILENAME = ".meta.json";
25
- const LEGACY_CURRENT_LINK = "current";
26
25
  const STAGING_MAX_AGE_MS = 3600 * 1e3;
27
- const VERSION_DIR_RE = /^\d+\.\d+\.\d+/;
26
+ const INSTALLED_ARTIFACT_NAMES = [
27
+ "manifest.json",
28
+ "popup.html",
29
+ "dist",
30
+ "icons"
31
+ ];
28
32
  const BROWSER_EXT_REQUIRED_FILES = [
29
33
  "manifest.json",
30
34
  "popup.html",
@@ -64,19 +68,10 @@ function browserExtRoot(cacheDir) {
64
68
  function resolveMetaPath(cacheDir) {
65
69
  return join(browserExtRoot(cacheDir), META_FILENAME);
66
70
  }
67
- function resolveVersionDir(cacheDir, version) {
68
- return join(browserExtRoot(cacheDir), version);
69
- }
70
- /** Resolve the installed extension directory (version folder, not a symlink). */
71
- function resolveInstalledExtensionPath(cacheDir, meta) {
72
- if (meta?.installPath && validateBrowserExtLayout(meta.installPath)) return meta.installPath;
73
- const expectedDir = resolveVersionDir(cacheDir, PACKAGE_VERSION);
74
- if (validateBrowserExtLayout(expectedDir)) return expectedDir;
75
- const legacyCurrent = join(browserExtRoot(cacheDir), LEGACY_CURRENT_LINK);
76
- if (existsSync(legacyCurrent)) try {
77
- const real = realpathSync(legacyCurrent);
78
- if (validateBrowserExtLayout(real)) return real;
79
- } catch {}
71
+ /** Resolve the installed extension directory (fixed `browser-ext/` root). */
72
+ function resolveInstalledExtensionPath(cacheDir, _meta) {
73
+ const root = browserExtRoot(cacheDir);
74
+ if (validateBrowserExtLayout(root)) return root;
80
75
  return null;
81
76
  }
82
77
  function walkAncestorsForGitDevBundled(start) {
@@ -150,55 +145,23 @@ async function cleanupStaleStaging(root) {
150
145
  } catch {}
151
146
  }
152
147
  }
153
- /** Remove legacy `current` link only (safe before a fresh install). */
154
- function removeLegacyCurrentLink(root) {
155
- const legacyCurrent = join(root, LEGACY_CURRENT_LINK);
156
- if (!existsSync(legacyCurrent)) return;
157
- rmSync(legacyCurrent, {
148
+ function removeInstalledArtifacts(root) {
149
+ for (const name of INSTALLED_ARTIFACT_NAMES) {
150
+ const full = join(root, name);
151
+ if (existsSync(full)) rmSync(full, {
152
+ recursive: true,
153
+ force: true
154
+ });
155
+ }
156
+ }
157
+ function promoteStagingToRoot(stagingDir, root) {
158
+ removeInstalledArtifacts(root);
159
+ for (const name of readdirSync(stagingDir)) renameSync(join(stagingDir, name), join(root, name));
160
+ rmSync(stagingDir, {
158
161
  recursive: true,
159
162
  force: true
160
163
  });
161
- log.info("Removed legacy browser-ext/current");
162
- }
163
- async function cleanupSiblingVersionDirs(root, keepVersion) {
164
- if (!existsSync(root)) return;
165
- let entries;
166
- try {
167
- entries = await readdir(root);
168
- } catch {
169
- return;
170
- }
171
- for (const name of entries) {
172
- if (name === META_FILENAME || name.startsWith(".")) continue;
173
- if (name === LEGACY_CURRENT_LINK) {
174
- try {
175
- await rm(join(root, name), {
176
- recursive: true,
177
- force: true
178
- });
179
- } catch (err) {
180
- log.warn({
181
- err,
182
- name
183
- }, "Failed to remove legacy browser extension path");
184
- }
185
- continue;
186
- }
187
- if (!VERSION_DIR_RE.test(name)) continue;
188
- if (name === keepVersion) continue;
189
- try {
190
- await rm(join(root, name), {
191
- recursive: true,
192
- force: true
193
- });
194
- log.info({ version: name }, "Removed old browser extension version directory");
195
- } catch (err) {
196
- log.warn({
197
- err,
198
- version: name
199
- }, "Failed to remove old browser extension version");
200
- }
201
- }
164
+ if (!validateBrowserExtLayout(root)) throw new Error("Bundled browser extension copy failed validation");
202
165
  }
203
166
  /** Copy one bundled file (read/write works when src is inside Electron app.asar). */
204
167
  function copyBundledFile(src, dest) {
@@ -273,45 +236,35 @@ async function ensureBrowserExtensionArtifacts(opts) {
273
236
  installedPath,
274
237
  meta
275
238
  });
276
- const versionKey = bundledManifestVersion;
277
- removeLegacyCurrentLink(root);
278
- if (!needsRefresh && installedPath) {
279
- await cleanupSiblingVersionDirs(root, versionKey);
280
- return {
281
- extensionDir: installedPath,
282
- xopcVersion: PACKAGE_VERSION,
283
- copied: false
284
- };
285
- }
286
- const versionDir = join(root, versionKey);
287
- const stagingDir = join(root, `.staging-${versionKey}-${process.pid}`);
239
+ const extensionDir = root;
240
+ if (!needsRefresh && installedPath) return {
241
+ extensionDir: installedPath,
242
+ xopcVersion: PACKAGE_VERSION,
243
+ copied: false
244
+ };
245
+ const stagingDir = join(root, `.staging-${process.pid}`);
288
246
  if (existsSync(stagingDir)) rmSync(stagingDir, {
289
247
  recursive: true,
290
248
  force: true
291
249
  });
292
250
  copyBundledTree(bundled.dir, stagingDir);
293
- if (existsSync(versionDir)) rmSync(versionDir, {
294
- recursive: true,
295
- force: true
296
- });
297
- renameSync(stagingDir, versionDir);
298
- await cleanupSiblingVersionDirs(root, versionKey);
251
+ promoteStagingToRoot(stagingDir, root);
299
252
  const nextMeta = {
300
253
  xopcVersion: PACKAGE_VERSION,
301
254
  manifestVersion: bundledManifestVersion,
302
255
  source: "bundled",
303
256
  bundledFrom: bundled.bundledFrom,
304
257
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
305
- installPath: versionDir
258
+ installPath: extensionDir
306
259
  };
307
260
  await writeTextAtomic(resolveMetaPath(cacheDir), JSON.stringify(nextMeta, null, 2));
308
261
  log.info({
309
- extensionDir: versionDir,
262
+ extensionDir,
310
263
  xopcVersion: PACKAGE_VERSION,
311
264
  bundledFrom: bundled.bundledFrom
312
265
  }, "Browser extension artifacts installed");
313
266
  return {
314
- extensionDir: versionDir,
267
+ extensionDir,
315
268
  xopcVersion: PACKAGE_VERSION,
316
269
  copied: true
317
270
  };