@xopcai/xopc 0.0.83 → 0.0.85

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 (457) hide show
  1. package/README.md +2 -0
  2. package/README.zh-CN.md +3 -1
  3. package/dist/browser-ext/manifest.json +1 -1
  4. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  5. package/dist/extensions/feishu/src/plugin.d.ts +2 -0
  6. package/dist/extensions/feishu/src/plugin.js +10 -0
  7. package/dist/extensions/feishu/src/plugin.js.map +1 -1
  8. package/dist/extensions/feishu/src/workflow-progress.d.ts +27 -0
  9. package/dist/extensions/feishu/src/workflow-progress.js +99 -0
  10. package/dist/extensions/feishu/src/workflow-progress.js.map +1 -0
  11. package/dist/extensions/telegram/src/plugin.d.ts +2 -0
  12. package/dist/extensions/telegram/src/plugin.js +11 -1
  13. package/dist/extensions/telegram/src/plugin.js.map +1 -1
  14. package/dist/extensions/telegram/src/routing-integration.js +2 -2
  15. package/dist/extensions/telegram/src/workflow-progress.d.ts +24 -0
  16. package/dist/extensions/telegram/src/workflow-progress.js +73 -0
  17. package/dist/extensions/telegram/src/workflow-progress.js.map +1 -0
  18. package/dist/extensions/telegram/xopc.extension.json +1 -1
  19. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js +158 -0
  20. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js.map +1 -0
  21. package/dist/extensions/weixin/src/api/api.js +2 -2
  22. package/dist/extensions/weixin/src/auth/accounts.js +1 -1
  23. package/dist/extensions/weixin/src/cdn/upload.js +1 -1
  24. package/dist/extensions/weixin/src/media/data-url.js +1 -1
  25. package/dist/extensions/weixin/src/messaging/debug-mode.js +1 -1
  26. package/dist/extensions/weixin/src/messaging/inbound.js +1 -1
  27. package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
  28. package/dist/extensions/weixin/src/plugin.d.ts +2 -0
  29. package/dist/extensions/weixin/src/plugin.js +11 -1
  30. package/dist/extensions/weixin/src/plugin.js.map +1 -1
  31. package/dist/extensions/weixin/src/storage/sync-buf.js +1 -1
  32. package/dist/extensions/weixin/src/workflow-progress.d.ts +26 -0
  33. package/dist/extensions/weixin/src/workflow-progress.js +99 -0
  34. package/dist/extensions/weixin/src/workflow-progress.js.map +1 -0
  35. package/dist/gateway/static/root/assets/agents-D3_-kNlZ.js +222 -0
  36. package/dist/gateway/static/root/assets/apps-page-D7v7649T.js +1 -0
  37. package/dist/gateway/static/root/assets/channels-settings-nCaMb0a7.js +1 -0
  38. package/dist/gateway/static/root/assets/channels-status-swr-C1gZBcJV.js +8 -0
  39. package/dist/gateway/static/root/assets/createLucideIcon-DPHK1VkS.js +1 -0
  40. package/dist/gateway/static/root/assets/cron-api-CoYK0hlm.js +1 -0
  41. package/dist/gateway/static/root/assets/cron-page-DeGo-Vjc.js +1 -0
  42. package/dist/gateway/static/root/assets/dist-BTWC-BTN.js +45 -0
  43. package/dist/gateway/static/root/assets/{dist-BpQxde0t.js → dist-DaK4dsss.js} +1 -1
  44. package/dist/gateway/static/root/assets/{extension-debug-page-CY27wj_p.js → extension-debug-page-BZngZWbO.js} +1 -1
  45. package/dist/gateway/static/root/assets/extension-page-D6JSyV27.js +1 -0
  46. package/dist/gateway/static/root/assets/extension-settings-page-8PZcmWI7.js +1 -0
  47. package/dist/gateway/static/root/assets/fetch-B2MYHbWg.js +1 -0
  48. package/dist/gateway/static/root/assets/{field-primitives-fa_hiQcX.js → field-primitives-Zzl22MvN.js} +1 -1
  49. package/dist/gateway/static/root/assets/heartbeat-config-api-BtIcpG0O.js +1 -0
  50. package/dist/gateway/static/root/assets/index-D4vM3-P7.js +4700 -0
  51. package/dist/gateway/static/root/assets/index-ew_2L2We.css +1 -0
  52. package/dist/gateway/static/root/assets/logs-page-_d4UJ-qQ.js +1 -0
  53. package/dist/gateway/static/root/assets/sessions-page-5N4aF2Wk.js +1 -0
  54. package/dist/gateway/static/root/assets/settings-form-section-D_tgb8r2.js +1 -0
  55. package/dist/gateway/static/root/assets/settings-page-C18xBt4X.js +3 -0
  56. package/dist/gateway/static/root/assets/share-preview-page-D4EG_vM1.js +2 -0
  57. package/dist/gateway/static/root/assets/skills-page-sPAXhh8w.js +2 -0
  58. package/dist/gateway/static/root/assets/theme-store-DryYl3qD.js +1 -0
  59. package/dist/gateway/static/root/assets/url-BwNL6Rgk.js +3 -0
  60. package/dist/gateway/static/root/assets/utils-CYO9eTCM.js +1 -0
  61. package/dist/gateway/static/root/assets/voice-api-key-field-Ds51havm.js +1 -0
  62. package/dist/gateway/static/root/index.html +7 -6
  63. package/dist/package.js +1 -1
  64. package/dist/src/agent/agent-manager.js +7 -7
  65. package/dist/src/agent/bootstrap/load-bootstrap-files.js +1 -1
  66. package/dist/src/agent/context/workspace-seed.js +3 -3
  67. package/dist/src/agent/embedded/map-stream-events.js +6 -0
  68. package/dist/src/agent/embedded/map-stream-events.js.map +1 -1
  69. package/dist/src/agent/embedded/subscribe-session.js +24 -0
  70. package/dist/src/agent/embedded/subscribe-session.js.map +1 -1
  71. package/dist/src/agent/embedded/types.d.ts +19 -0
  72. package/dist/src/agent/goals/goal-locale.js +2 -2
  73. package/dist/src/agent/goals/goal-run-store.js +4 -4
  74. package/dist/src/agent/goals/persistent-goal-service.js +1 -1
  75. package/dist/src/agent/goals/post-turn.js +2 -2
  76. package/dist/src/agent/image/load-image-media.js +2 -2
  77. package/dist/src/agent/ipc/bus.js +1 -1
  78. package/dist/src/agent/ipc/inbox.js +2 -2
  79. package/dist/src/agent/ipc/socket.js +1 -1
  80. package/dist/src/agent/memory/builtin-memory-store.js +1 -1
  81. package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
  82. package/dist/src/agent/memory/dreaming/events.js +1 -1
  83. package/dist/src/agent/memory/dreaming/last-run.js +1 -1
  84. package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
  85. package/dist/src/agent/memory/dreaming/preview.js +1 -1
  86. package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
  87. package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
  88. package/dist/src/agent/memory/dreaming/utils.js +1 -1
  89. package/dist/src/agent/memory/plugin-discovery.js +1 -1
  90. package/dist/src/agent/models/manager.js +1 -1
  91. package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
  92. package/dist/src/agent/reply/post-compaction-context.js +1 -1
  93. package/dist/src/agent/reply/startup-context.d.ts +3 -0
  94. package/dist/src/agent/reply/startup-context.js +25 -2
  95. package/dist/src/agent/reply/startup-context.js.map +1 -1
  96. package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
  97. package/dist/src/agent/sandbox/path-policy.js +2 -2
  98. package/dist/src/agent/service/build-direct-message-content.js +1 -1
  99. package/dist/src/agent/service.d.ts +1 -0
  100. package/dist/src/agent/service.js +10 -4
  101. package/dist/src/agent/service.js.map +1 -1
  102. package/dist/src/agent/session/session-inspector.js +1 -1
  103. package/dist/src/agent/skills/config.js +1 -1
  104. package/dist/src/agent/skills/hub-hash.js +2 -2
  105. package/dist/src/agent/skills/hub-lock.js +1 -1
  106. package/dist/src/agent/skills/hub-pull.js +3 -3
  107. package/dist/src/agent/skills/index.js +1 -1
  108. package/dist/src/agent/skills/managed-store.js +1 -1
  109. package/dist/src/agent/skills/scanner.js +1 -1
  110. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  111. package/dist/src/agent/skills/skill-manager.js +1 -1
  112. package/dist/src/agent/tools/create-share-tool.d.ts +27 -0
  113. package/dist/src/agent/tools/create-share-tool.js +237 -0
  114. package/dist/src/agent/tools/create-share-tool.js.map +1 -0
  115. package/dist/src/agent/tools/dreaming-tool.js +1 -1
  116. package/dist/src/agent/tools/factory.js +35 -1
  117. package/dist/src/agent/tools/factory.js.map +1 -1
  118. package/dist/src/agent/tools/image-generate-tool.js +1 -1
  119. package/dist/src/agent/tools/index.d.ts +2 -0
  120. package/dist/src/agent/tools/index.js +3 -1
  121. package/dist/src/agent/tools/send-media.js +1 -1
  122. package/dist/src/agent/tools/skill-manage-tool.js +1 -1
  123. package/dist/src/agent/tools/workflow-tool.d.ts +41 -0
  124. package/dist/src/agent/tools/workflow-tool.js +271 -0
  125. package/dist/src/agent/tools/workflow-tool.js.map +1 -0
  126. package/dist/src/agent/tools/write.js +1 -1
  127. package/dist/src/agent/workflow/builtins/audit-repo.d.ts +9 -0
  128. package/dist/src/agent/workflow/builtins/audit-repo.js +115 -0
  129. package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -0
  130. package/dist/src/agent/workflow/builtins/index.d.ts +15 -0
  131. package/dist/src/agent/workflow/builtins/index.js +28 -0
  132. package/dist/src/agent/workflow/builtins/index.js.map +1 -0
  133. package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +9 -0
  134. package/dist/src/agent/workflow/builtins/multi-perspective-review.js +113 -0
  135. package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -0
  136. package/dist/src/agent/workflow/builtins/research.d.ts +9 -0
  137. package/dist/src/agent/workflow/builtins/research.js +129 -0
  138. package/dist/src/agent/workflow/builtins/research.js.map +1 -0
  139. package/dist/src/agent/workflow/catalog.d.ts +51 -0
  140. package/dist/src/agent/workflow/catalog.js +155 -0
  141. package/dist/src/agent/workflow/catalog.js.map +1 -0
  142. package/dist/src/agent/workflow/channel-capability.d.ts +76 -0
  143. package/dist/src/agent/workflow/channel-capability.js +1 -0
  144. package/dist/src/agent/workflow/index.d.ts +11 -0
  145. package/dist/src/agent/workflow/index.js +10 -0
  146. package/dist/src/agent/workflow/last-run-memory.d.ts +42 -0
  147. package/dist/src/agent/workflow/last-run-memory.js +60 -0
  148. package/dist/src/agent/workflow/last-run-memory.js.map +1 -0
  149. package/dist/src/agent/workflow/parser.d.ts +20 -0
  150. package/dist/src/agent/workflow/parser.js +137 -0
  151. package/dist/src/agent/workflow/parser.js.map +1 -0
  152. package/dist/src/agent/workflow/progress-broker.d.ts +80 -0
  153. package/dist/src/agent/workflow/progress-broker.js +263 -0
  154. package/dist/src/agent/workflow/progress-broker.js.map +1 -0
  155. package/dist/src/agent/workflow/runtime.d.ts +31 -0
  156. package/dist/src/agent/workflow/runtime.js +301 -0
  157. package/dist/src/agent/workflow/runtime.js.map +1 -0
  158. package/dist/src/agent/workflow/snapshot.d.ts +18 -0
  159. package/dist/src/agent/workflow/snapshot.js +144 -0
  160. package/dist/src/agent/workflow/snapshot.js.map +1 -0
  161. package/dist/src/agent/workflow/structured-output-tool.d.ts +33 -0
  162. package/dist/src/agent/workflow/structured-output-tool.js +58 -0
  163. package/dist/src/agent/workflow/structured-output-tool.js.map +1 -0
  164. package/dist/src/agent/workflow/subagent-runner.d.ts +42 -0
  165. package/dist/src/agent/workflow/subagent-runner.js +104 -0
  166. package/dist/src/agent/workflow/subagent-runner.js.map +1 -0
  167. package/dist/src/agent/workflow/types.d.ts +137 -0
  168. package/dist/src/agent/workflow/types.js +1 -0
  169. package/dist/src/auth/credentials.js +3 -3
  170. package/dist/src/auth/profiles/store.js +1 -1
  171. package/dist/src/auth/sync-provider-auth.js +1 -1
  172. package/dist/src/browser/cache-dir-policy.js +1 -1
  173. package/dist/src/browser/cdp-local-launcher.js +2 -2
  174. package/dist/src/browser/providers/browser-ext-install.js +4 -4
  175. package/dist/src/browser/providers/cloakbrowser.js +4 -4
  176. package/dist/src/browser/providers/playwright-doctor.js +1 -1
  177. package/dist/src/browser/stealth.js +1 -1
  178. package/dist/src/channels/attachments/inbound-persist.js +1 -1
  179. package/dist/src/channels/attachments/outbound-tts-persist.js +1 -1
  180. package/dist/src/channels/outbound/persist-store.js +1 -1
  181. package/dist/src/channels/pairing/allow-from-file.js +1 -1
  182. package/dist/src/channels/pairing/pairing-store.js +2 -2
  183. package/dist/src/chat-commands/builtins/config.js +2 -2
  184. package/dist/src/chat-commands/builtins/model.js +40 -23
  185. package/dist/src/chat-commands/builtins/model.js.map +1 -1
  186. package/dist/src/chat-commands/builtins/system.js +30 -15
  187. package/dist/src/chat-commands/builtins/system.js.map +1 -1
  188. package/dist/src/chat-commands/builtins/workflow.d.ts +18 -0
  189. package/dist/src/chat-commands/builtins/workflow.js +167 -0
  190. package/dist/src/chat-commands/builtins/workflow.js.map +1 -0
  191. package/dist/src/chat-commands/context.js +1 -1
  192. package/dist/src/chat-commands/format-output.d.ts +28 -0
  193. package/dist/src/chat-commands/format-output.js +45 -0
  194. package/dist/src/chat-commands/format-output.js.map +1 -0
  195. package/dist/src/chat-commands/index.d.ts +1 -0
  196. package/dist/src/chat-commands/index.js +3 -1
  197. package/dist/src/chat-commands/index.js.map +1 -1
  198. package/dist/src/cli/command-catalog.js +110 -8
  199. package/dist/src/cli/command-catalog.js.map +1 -1
  200. package/dist/src/cli/command-loaders.js +2 -0
  201. package/dist/src/cli/command-loaders.js.map +1 -1
  202. package/dist/src/cli/command-manifest.js +9 -1
  203. package/dist/src/cli/command-manifest.js.map +1 -1
  204. package/dist/src/cli/commands/config.js +71 -20
  205. package/dist/src/cli/commands/config.js.map +1 -1
  206. package/dist/src/cli/commands/cron-cli.d.ts +2 -0
  207. package/dist/src/cli/commands/cron-cli.js +15 -0
  208. package/dist/src/cli/commands/cron-cli.js.map +1 -0
  209. package/dist/src/cli/commands/cron.d.ts +4 -1
  210. package/dist/src/cli/commands/cron.js +76 -41
  211. package/dist/src/cli/commands/cron.js.map +1 -1
  212. package/dist/src/cli/commands/doctor/checks/channel-config.js +1 -1
  213. package/dist/src/cli/commands/doctor/checks/channel-config.js.map +1 -1
  214. package/dist/src/cli/commands/doctor/checks/config-health.js +2 -2
  215. package/dist/src/cli/commands/doctor/checks/config-health.js.map +1 -1
  216. package/dist/src/cli/commands/doctor/checks/cron-health.js +1 -1
  217. package/dist/src/cli/commands/doctor/checks/cron-health.js.map +1 -1
  218. package/dist/src/cli/commands/doctor/checks/gateway-health.js +2 -2
  219. package/dist/src/cli/commands/doctor/checks/gateway-health.js.map +1 -1
  220. package/dist/src/cli/commands/doctor/checks/gateway-service.js +2 -2
  221. package/dist/src/cli/commands/doctor/checks/gateway-service.js.map +1 -1
  222. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  223. package/dist/src/cli/commands/doctor/checks/session-integrity.js +1 -1
  224. package/dist/src/cli/commands/doctor/checks/state-integrity.js +2 -2
  225. package/dist/src/cli/commands/doctor/checks/state-integrity.js.map +1 -1
  226. package/dist/src/cli/commands/doctor/checks/workspace-status.js +4 -4
  227. package/dist/src/cli/commands/doctor/checks/workspace-status.js.map +1 -1
  228. package/dist/src/cli/commands/extension-dev.js +1 -1
  229. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  230. package/dist/src/cli/commands/extension-pack.js +1 -1
  231. package/dist/src/cli/commands/gateway/index.d.ts +1 -1
  232. package/dist/src/cli/commands/gateway/index.js +2 -2
  233. package/dist/src/cli/commands/gateway/lifecycle.js +10 -4
  234. package/dist/src/cli/commands/gateway/lifecycle.js.map +1 -1
  235. package/dist/src/cli/commands/gateway/service.d.ts +4 -0
  236. package/dist/src/cli/commands/gateway/service.js +17 -2
  237. package/dist/src/cli/commands/gateway/service.js.map +1 -1
  238. package/dist/src/cli/commands/gateway/shared.js +1 -1
  239. package/dist/src/cli/commands/gateway/subcommands.js +1 -4
  240. package/dist/src/cli/commands/gateway/subcommands.js.map +1 -1
  241. package/dist/src/cli/commands/image.js +1 -1
  242. package/dist/src/cli/commands/init.js +31 -4
  243. package/dist/src/cli/commands/init.js.map +1 -1
  244. package/dist/src/cli/commands/models.d.ts +4 -1
  245. package/dist/src/cli/commands/models.js +86 -74
  246. package/dist/src/cli/commands/models.js.map +1 -1
  247. package/dist/src/cli/commands/onboard.js +4 -2
  248. package/dist/src/cli/commands/onboard.js.map +1 -1
  249. package/dist/src/cli/commands/profile.d.ts +3 -5
  250. package/dist/src/cli/commands/profile.js +31 -31
  251. package/dist/src/cli/commands/profile.js.map +1 -1
  252. package/dist/src/cli/commands/setup.js +6 -1
  253. package/dist/src/cli/commands/setup.js.map +1 -1
  254. package/dist/src/cli/commands/tunnel.js +2 -2
  255. package/dist/src/cli/gateway-run-argv.js +15 -5
  256. package/dist/src/cli/gateway-run-argv.js.map +1 -1
  257. package/dist/src/cli/utils/gateway-client.js +1 -1
  258. package/dist/src/cli/utils/init-workspace-core.js +2 -2
  259. package/dist/src/config/agent-profile.js +1 -1
  260. package/dist/src/config/gateway-bind.js +1 -1
  261. package/dist/src/config/index.js +5 -5
  262. package/dist/src/config/loader.js +2 -2
  263. package/dist/src/config/models-json.js +2 -2
  264. package/dist/src/config/paths-state.js +1 -1
  265. package/dist/src/config/profile.js +2 -2
  266. package/dist/src/config/public-url.d.ts +28 -0
  267. package/dist/src/config/public-url.js +103 -0
  268. package/dist/src/config/public-url.js.map +1 -0
  269. package/dist/src/config/schema.d.ts +82 -0
  270. package/dist/src/config/schema.js +130 -1
  271. package/dist/src/config/schema.js.map +1 -1
  272. package/dist/src/config/workspace-path.js +1 -1
  273. package/dist/src/cron/executor.js +2 -2
  274. package/dist/src/cron/persistence.js +1 -1
  275. package/dist/src/cron/run-log-store.js +1 -1
  276. package/dist/src/daemon/constants.js +1 -1
  277. package/dist/src/daemon/install-plan.js +3 -3
  278. package/dist/src/daemon/install-plan.js.map +1 -1
  279. package/dist/src/daemon/launchd.js +2 -2
  280. package/dist/src/daemon/schtasks.js +38 -1
  281. package/dist/src/daemon/schtasks.js.map +1 -1
  282. package/dist/src/daemon/systemd.js +2 -2
  283. package/dist/src/extensions/bundle-mcp.js +1 -1
  284. package/dist/src/extensions/discover-extensions.js +1 -1
  285. package/dist/src/extensions/health.js +1 -1
  286. package/dist/src/extensions/loader.js +1 -1
  287. package/dist/src/extensions/lockfile.js +2 -2
  288. package/dist/src/gateway/agents-admin.js +2 -2
  289. package/dist/src/gateway/file-path-classifier.js +2 -2
  290. package/dist/src/gateway/heartbeat/service.js +1 -1
  291. package/dist/src/gateway/hono/app.js +33 -2
  292. package/dist/src/gateway/hono/app.js.map +1 -1
  293. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  294. package/dist/src/gateway/hono/lib/extension-store.js +2 -2
  295. package/dist/src/gateway/hono/lib/static-ui.js +2 -2
  296. package/dist/src/gateway/hono/oauth.js +1 -1
  297. package/dist/src/gateway/hono/routes/agents.js +1 -1
  298. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  299. package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
  300. package/dist/src/gateway/hono/routes/dreaming.js +1 -1
  301. package/dist/src/gateway/hono/routes/host-fs.js +2 -2
  302. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  303. package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
  304. package/dist/src/gateway/hono/routes/models.js +1 -1
  305. package/dist/src/gateway/hono/routes/shares.js +631 -34
  306. package/dist/src/gateway/hono/routes/shares.js.map +1 -1
  307. package/dist/src/gateway/hono/routes/site-shares.d.ts +3 -0
  308. package/dist/src/gateway/hono/routes/site-shares.js +228 -0
  309. package/dist/src/gateway/hono/routes/site-shares.js.map +1 -0
  310. package/dist/src/gateway/hono/routes/tunnel.js +97 -8
  311. package/dist/src/gateway/hono/routes/tunnel.js.map +1 -1
  312. package/dist/src/gateway/hono/routes/workspace.js +5 -5
  313. package/dist/src/gateway/hono/sse.js +2 -2
  314. package/dist/src/gateway/host.d.ts +3 -1
  315. package/dist/src/gateway/host.js +3 -1
  316. package/dist/src/gateway/host.js.map +1 -1
  317. package/dist/src/gateway/lock.js +3 -3
  318. package/dist/src/gateway/ports.d.ts +6 -0
  319. package/dist/src/gateway/ports.js +38 -2
  320. package/dist/src/gateway/ports.js.map +1 -1
  321. package/dist/src/gateway/public-url.d.ts +8 -0
  322. package/dist/src/gateway/public-url.js +10 -0
  323. package/dist/src/gateway/public-url.js.map +1 -0
  324. package/dist/src/gateway/security/origin-check.d.ts +9 -1
  325. package/dist/src/gateway/security/origin-check.js +4 -0
  326. package/dist/src/gateway/security/origin-check.js.map +1 -1
  327. package/dist/src/gateway/server.js +15 -0
  328. package/dist/src/gateway/server.js.map +1 -1
  329. package/dist/src/gateway/service/agent-runner.js +2 -2
  330. package/dist/src/gateway/service/marketplace-service.js +2 -2
  331. package/dist/src/gateway/service/run-gateway-agent.js +2 -2
  332. package/dist/src/gateway/service.js +3 -2
  333. package/dist/src/gateway/service.js.map +1 -1
  334. package/dist/src/gateway/workspace-fs-file-list.js +1 -1
  335. package/dist/src/heartbeat/index.js +1 -1
  336. package/dist/src/i18n/goals-bundle.js +1 -1
  337. package/dist/src/i18n/index.d.ts +1 -0
  338. package/dist/src/i18n/index.js +2 -1
  339. package/dist/src/i18n/locales/share-tool.en.js +15 -0
  340. package/dist/src/i18n/locales/share-tool.en.js.map +1 -0
  341. package/dist/src/i18n/locales/share-tool.zh.js +15 -0
  342. package/dist/src/i18n/locales/share-tool.zh.js.map +1 -0
  343. package/dist/src/i18n/share-tool-bundle.d.ts +20 -0
  344. package/dist/src/i18n/share-tool-bundle.js +56 -0
  345. package/dist/src/i18n/share-tool-bundle.js.map +1 -0
  346. package/dist/src/infra/gateway-processes.js +1 -0
  347. package/dist/src/infra/gateway-processes.js.map +1 -1
  348. package/dist/src/infra/restart.js +2 -2
  349. package/dist/src/infra/update-check.js +1 -1
  350. package/dist/src/infra/update-lock.js +3 -3
  351. package/dist/src/infra/update-runner.js +1 -1
  352. package/dist/src/infra/update-startup.js +2 -2
  353. package/dist/src/infra/write-file-atomic.js +2 -2
  354. package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
  355. package/dist/src/providers/index.js +2 -2
  356. package/dist/src/providers/model-registry.js +1 -1
  357. package/dist/src/session/config-store.js +2 -2
  358. package/dist/src/session/parity/jsonl-transcript-io.js +2 -2
  359. package/dist/src/session/parity/sessions-json-file.js +1 -1
  360. package/dist/src/session/parity/transcript-file-lock.js +2 -2
  361. package/dist/src/session/parity/transcript-paths.js +1 -1
  362. package/dist/src/session/search-index-cache.js +1 -1
  363. package/dist/src/session/search-index.js +1 -1
  364. package/dist/src/session/session-title.js +3 -2
  365. package/dist/src/session/session-title.js.map +1 -1
  366. package/dist/src/session/store.js +5 -5
  367. package/dist/src/share/share-auto.d.ts +74 -0
  368. package/dist/src/share/share-auto.js +247 -0
  369. package/dist/src/share/share-auto.js.map +1 -0
  370. package/dist/src/share/share-config.js +63 -4
  371. package/dist/src/share/share-config.js.map +1 -1
  372. package/dist/src/share/share-landing.d.ts +28 -2
  373. package/dist/src/share/share-landing.js +155 -34
  374. package/dist/src/share/share-landing.js.map +1 -1
  375. package/dist/src/share/share-store.d.ts +48 -4
  376. package/dist/src/share/share-store.js +322 -51
  377. package/dist/src/share/share-store.js.map +1 -1
  378. package/dist/src/share/share-thumbnail.d.ts +35 -0
  379. package/dist/src/share/share-thumbnail.js +277 -0
  380. package/dist/src/share/share-thumbnail.js.map +1 -0
  381. package/dist/src/share/share-types.d.ts +68 -10
  382. package/dist/src/share/share-types.js +18 -1
  383. package/dist/src/share/share-types.js.map +1 -1
  384. package/dist/src/share/share-url.js +1 -1
  385. package/dist/src/share/share-zip.d.ts +35 -0
  386. package/dist/src/share/share-zip.js +303 -0
  387. package/dist/src/share/share-zip.js.map +1 -0
  388. package/dist/src/share/site-proxy.d.ts +35 -0
  389. package/dist/src/share/site-proxy.js +234 -0
  390. package/dist/src/share/site-proxy.js.map +1 -0
  391. package/dist/src/share/site-share-config.d.ts +11 -0
  392. package/dist/src/share/site-share-config.js +103 -0
  393. package/dist/src/share/site-share-config.js.map +1 -0
  394. package/dist/src/share/site-share-router.d.ts +23 -0
  395. package/dist/src/share/site-share-router.js +147 -0
  396. package/dist/src/share/site-share-router.js.map +1 -0
  397. package/dist/src/share/site-share-store.d.ts +53 -0
  398. package/dist/src/share/site-share-store.js +400 -0
  399. package/dist/src/share/site-share-store.js.map +1 -0
  400. package/dist/src/share/site-share-types.d.ts +103 -0
  401. package/dist/src/share/site-share-types.js +41 -0
  402. package/dist/src/share/site-share-types.js.map +1 -0
  403. package/dist/src/share/site-static-serve.d.ts +10 -0
  404. package/dist/src/share/site-static-serve.js +145 -0
  405. package/dist/src/share/site-static-serve.js.map +1 -0
  406. package/dist/src/tui/clipboard-image.js +3 -3
  407. package/dist/src/tui/theme-manager.js +1 -1
  408. package/dist/src/tui/tui-commands.js +18 -0
  409. package/dist/src/tui/tui-commands.js.map +1 -1
  410. package/dist/src/tui/tui-keybindings-file.js +1 -1
  411. package/dist/src/tui/tui-scoped-models.js +2 -2
  412. package/dist/src/tui/tui-settings.js +1 -1
  413. package/dist/src/tui/tui-workflow-slash.d.ts +32 -0
  414. package/dist/src/tui/tui-workflow-slash.js +63 -0
  415. package/dist/src/tui/tui-workflow-slash.js.map +1 -0
  416. package/dist/src/tui/tui.js +2 -2
  417. package/dist/src/tunnel/enable-lan-pairing.js +1 -1
  418. package/dist/src/tunnel/frpc-binary.js +3 -3
  419. package/dist/src/tunnel/frpc-config.js +1 -1
  420. package/dist/src/tunnel/frpc-extract.js +1 -1
  421. package/dist/src/tunnel/index.js +2 -2
  422. package/dist/src/tunnel/pair-context.d.ts +7 -1
  423. package/dist/src/tunnel/pair-context.js +25 -9
  424. package/dist/src/tunnel/pair-context.js.map +1 -1
  425. package/dist/src/tunnel/pair-url.d.ts +14 -1
  426. package/dist/src/tunnel/pair-url.js +14 -1
  427. package/dist/src/tunnel/pair-url.js.map +1 -1
  428. package/dist/src/tunnel/tunnel-service.js +2 -2
  429. package/dist/src/tunnel/tunnel-state.js +1 -1
  430. package/dist/src/utils/logger/audit.js +1 -1
  431. package/dist/src/utils/logger/log-store.js +1 -1
  432. package/dist/src/utils/logger/rotation.js +1 -1
  433. package/dist/src/voice/tts/audio.js +1 -1
  434. package/dist/src/voice/tts/providers/edge-speech.js +2 -2
  435. package/package.json +3 -2
  436. package/dist/gateway/static/root/assets/agents-CrpYTHJS.js +0 -222
  437. package/dist/gateway/static/root/assets/apps-page-1mcKh5Rh.js +0 -1
  438. package/dist/gateway/static/root/assets/button-KafIU8dx.js +0 -1
  439. package/dist/gateway/static/root/assets/channels-settings-zd6QNKPx.js +0 -1
  440. package/dist/gateway/static/root/assets/channels-status-swr-uRAuhiUo.js +0 -8
  441. package/dist/gateway/static/root/assets/cron-api-O2Q_ruV6.js +0 -1
  442. package/dist/gateway/static/root/assets/cron-page-By09AQD-.js +0 -1
  443. package/dist/gateway/static/root/assets/dist-C57OMHW8.js +0 -48
  444. package/dist/gateway/static/root/assets/extension-page-C-Ed5ZmP.js +0 -1
  445. package/dist/gateway/static/root/assets/extension-settings-page-raLux7E7.js +0 -1
  446. package/dist/gateway/static/root/assets/fetch-2iRFmd3n.js +0 -3
  447. package/dist/gateway/static/root/assets/heartbeat-config-api-BVl5VHvL.js +0 -1
  448. package/dist/gateway/static/root/assets/index-BuFldCsB.css +0 -1
  449. package/dist/gateway/static/root/assets/index-Y-iqo-gL.js +0 -4693
  450. package/dist/gateway/static/root/assets/logs-page-BdH2n7ZW.js +0 -1
  451. package/dist/gateway/static/root/assets/sessions-page-Vpchzdp-.js +0 -1
  452. package/dist/gateway/static/root/assets/settings-form-section-Kk1yAGBl.js +0 -1
  453. package/dist/gateway/static/root/assets/settings-page-KBm0u6Dz.js +0 -3
  454. package/dist/gateway/static/root/assets/skills-page-BjeXXaOn.js +0 -2
  455. package/dist/gateway/static/root/assets/theme-store-D01dJt95.js +0 -1
  456. package/dist/gateway/static/root/assets/utils-DpTxN4AF.js +0 -1
  457. package/dist/gateway/static/root/assets/voice-api-key-field-CwO8Cf01.js +0 -1
@@ -0,0 +1,137 @@
1
+ import { parse } from "acorn";
2
+ //#region src/agent/workflow/parser.ts
3
+ /**
4
+ * Workflow script parser.
5
+ *
6
+ * Responsibilities:
7
+ * 1. Parse the script with acorn (latest ECMA, top-level await + return allowed).
8
+ * 2. Enforce determinism — reject `Date.now()`, `Math.random()`, `new Date()`,
9
+ * `require`, `import`, dynamic eval. This keeps future resume/replay possible
10
+ * and surfaces non-deterministic mistakes early.
11
+ * 3. Require the first statement to be `export const meta = <literal>`, validate
12
+ * the literal shape, and strip that line from the body returned to the runtime.
13
+ *
14
+ * Returning a `{ meta, body }` pair means the runtime can `vm.Script(body)` without
15
+ * any further AST work.
16
+ */
17
+ const NONDETERMINISM_ERROR = "Workflow scripts must be deterministic: Date.now()/Math.random()/new Date() are unavailable. Pass timestamps via args or stamp them after the workflow returns.";
18
+ function parseWorkflowScript(script) {
19
+ let ast;
20
+ try {
21
+ ast = parse(script, {
22
+ ecmaVersion: "latest",
23
+ sourceType: "module",
24
+ allowAwaitOutsideFunction: true,
25
+ allowReturnOutsideFunction: true,
26
+ ranges: false
27
+ });
28
+ } catch (e) {
29
+ const msg = e instanceof Error ? e.message : String(e);
30
+ throw new Error(`Workflow script parse error: ${msg}`);
31
+ }
32
+ assertDeterministicAst(ast);
33
+ assertNoDangerousImports(ast);
34
+ const first = ast.body?.[0];
35
+ if (first?.type !== "ExportNamedDeclaration") throw new Error("`export const meta = { name, description }` must be the first statement in the script");
36
+ const declaration = first.declaration;
37
+ if (declaration?.type !== "VariableDeclaration" || declaration.kind !== "const") throw new Error("meta export must be `export const meta = ...`");
38
+ if (declaration.declarations.length !== 1) throw new Error("meta export must declare only `meta`");
39
+ const declarator = declaration.declarations[0];
40
+ if (declarator.id?.type !== "Identifier" || declarator.id.name !== "meta") throw new Error("meta export must declare `meta`");
41
+ if (!declarator.init) throw new Error("meta must have a literal value");
42
+ const meta = evaluateLiteral(declarator.init, "meta");
43
+ validateMeta(meta);
44
+ return {
45
+ meta,
46
+ body: script.slice(0, first.start) + script.slice(first.end)
47
+ };
48
+ }
49
+ function assertDeterministicAst(node) {
50
+ if (isDateNowCall(node) || isMathRandomCall(node) || isNewDateExpression(node)) throw new Error(NONDETERMINISM_ERROR);
51
+ for (const child of astChildren(node)) assertDeterministicAst(child);
52
+ }
53
+ function assertNoDangerousImports(node) {
54
+ if (node.type === "ImportDeclaration" || node.type === "ImportExpression") throw new Error("Workflow scripts cannot use `import` — only the exposed globals (agent, parallel, pipeline, phase, log, args, cwd, budget) are available.");
55
+ if (node.type === "CallExpression") {
56
+ const callee = node.callee;
57
+ if (callee?.type === "Identifier" && (callee.name === "require" || callee.name === "eval")) throw new Error(`Workflow scripts cannot call \`${callee.name}\`.`);
58
+ }
59
+ for (const child of astChildren(node)) assertNoDangerousImports(child);
60
+ }
61
+ function astChildren(node) {
62
+ const children = [];
63
+ for (const value of Object.values(node)) if (Array.isArray(value)) {
64
+ for (const v of value) if (isAstNode(v)) children.push(v);
65
+ } else if (isAstNode(value)) children.push(value);
66
+ return children;
67
+ }
68
+ function isAstNode(value) {
69
+ return !!value && typeof value === "object" && typeof value.type === "string";
70
+ }
71
+ function isDateNowCall(node) {
72
+ return node.type === "CallExpression" && isMemberExpression(node.callee, "Date", "now");
73
+ }
74
+ function isMathRandomCall(node) {
75
+ return node.type === "CallExpression" && isMemberExpression(node.callee, "Math", "random");
76
+ }
77
+ function isNewDateExpression(node) {
78
+ return node.type === "NewExpression" && node.callee?.type === "Identifier" && node.callee.name === "Date";
79
+ }
80
+ function isMemberExpression(node, objectName, propertyName) {
81
+ if (node?.type !== "MemberExpression" || node.object?.type !== "Identifier" || node.object.name !== objectName) return false;
82
+ const prop = node.property;
83
+ if (!node.computed && prop?.type === "Identifier") return prop.name === propertyName;
84
+ if (prop?.type === "Literal" && typeof prop.value === "string") return prop.value === propertyName;
85
+ return false;
86
+ }
87
+ function evaluateLiteral(node, path) {
88
+ switch (node.type) {
89
+ case "ObjectExpression": {
90
+ const out = {};
91
+ for (const prop of node.properties) {
92
+ if (prop.type === "SpreadElement") throw new Error(`spread not allowed in ${path}`);
93
+ if (prop.type !== "Property") throw new Error(`only plain properties allowed in ${path}`);
94
+ if (prop.computed) throw new Error(`computed keys not allowed in ${path}`);
95
+ if (prop.kind !== "init" || prop.method) throw new Error(`methods/accessors not allowed in ${path}`);
96
+ const key = propertyKey(prop.key, path);
97
+ if (key === "__proto__" || key === "constructor" || key === "prototype") throw new Error(`reserved key name not allowed in ${path}: ${key}`);
98
+ out[key] = evaluateLiteral(prop.value, `${path}.${key}`);
99
+ }
100
+ return out;
101
+ }
102
+ case "ArrayExpression": return node.elements.map((element, index) => {
103
+ if (!element) throw new Error(`sparse arrays not allowed in ${path}`);
104
+ if (element.type === "SpreadElement") throw new Error(`spread not allowed in ${path}`);
105
+ return evaluateLiteral(element, `${path}[${index}]`);
106
+ });
107
+ case "Literal": return node.value;
108
+ case "TemplateLiteral":
109
+ if (node.expressions.length > 0) throw new Error(`template interpolation not allowed in ${path}`);
110
+ return node.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw).join("");
111
+ case "UnaryExpression":
112
+ if (node.operator === "-" && node.argument?.type === "Literal" && typeof node.argument.value === "number") return -node.argument.value;
113
+ throw new Error(`only negative-number unary allowed in ${path}`);
114
+ default: throw new Error(`non-literal node type in ${path}: ${node.type}`);
115
+ }
116
+ }
117
+ function propertyKey(node, path) {
118
+ if (node.type === "Identifier") return node.name;
119
+ if (node.type === "Literal" && (typeof node.value === "string" || typeof node.value === "number")) return String(node.value);
120
+ throw new Error(`unsupported key type in ${path}: ${node.type}`);
121
+ }
122
+ function validateMeta(meta) {
123
+ if (!meta || typeof meta !== "object") throw new Error("meta must be an object");
124
+ const value = meta;
125
+ if (typeof value.name !== "string" || !value.name.trim()) throw new Error("meta.name must be a non-empty string");
126
+ if (!/^[a-z][a-z0-9_-]*$/.test(value.name)) throw new Error(`meta.name must be lowercase snake_case (got "${value.name}"). Example: "audit_repo".`);
127
+ if (typeof value.description !== "string" || !value.description.trim()) throw new Error("meta.description must be a non-empty string");
128
+ if (value.whenToUse !== void 0 && typeof value.whenToUse !== "string") throw new Error("meta.whenToUse must be a string");
129
+ if (value.phases !== void 0) {
130
+ if (!Array.isArray(value.phases)) throw new Error("meta.phases must be an array");
131
+ for (const phase of value.phases) if (!phase || typeof phase !== "object" || typeof phase.title !== "string") throw new Error("each meta phase must have a title string");
132
+ }
133
+ }
134
+ //#endregion
135
+ export { parseWorkflowScript };
136
+
137
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","names":[],"sources":["../../../../src/agent/workflow/parser.ts"],"sourcesContent":["/**\n * Workflow script parser.\n *\n * Responsibilities:\n * 1. Parse the script with acorn (latest ECMA, top-level await + return allowed).\n * 2. Enforce determinism — reject `Date.now()`, `Math.random()`, `new Date()`,\n * `require`, `import`, dynamic eval. This keeps future resume/replay possible\n * and surfaces non-deterministic mistakes early.\n * 3. Require the first statement to be `export const meta = <literal>`, validate\n * the literal shape, and strip that line from the body returned to the runtime.\n *\n * Returning a `{ meta, body }` pair means the runtime can `vm.Script(body)` without\n * any further AST work.\n */\n\nimport { parse } from 'acorn';\nimport type { Node } from 'acorn';\n\nimport type { WorkflowMeta, WorkflowMetaPhase } from './types.js';\n\ntype AnyNode = Node & { [key: string]: any; start: number; end: number };\n\nconst NONDETERMINISM_ERROR =\n 'Workflow scripts must be deterministic: Date.now()/Math.random()/new Date() are unavailable. ' +\n 'Pass timestamps via args or stamp them after the workflow returns.';\n\nexport interface ParsedWorkflow {\n meta: WorkflowMeta;\n body: string;\n}\n\nexport function parseWorkflowScript(script: string): ParsedWorkflow {\n let ast: AnyNode;\n try {\n ast = parse(script, {\n ecmaVersion: 'latest',\n sourceType: 'module',\n allowAwaitOutsideFunction: true,\n allowReturnOutsideFunction: true,\n ranges: false,\n }) as AnyNode;\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n throw new Error(`Workflow script parse error: ${msg}`);\n }\n\n assertDeterministicAst(ast);\n assertNoDangerousImports(ast);\n\n const first = ast.body?.[0] as AnyNode | undefined;\n if (first?.type !== 'ExportNamedDeclaration') {\n throw new Error(\n '`export const meta = { name, description }` must be the first statement in the script',\n );\n }\n\n const declaration = first.declaration as AnyNode | null;\n if (declaration?.type !== 'VariableDeclaration' || declaration.kind !== 'const') {\n throw new Error('meta export must be `export const meta = ...`');\n }\n if (declaration.declarations.length !== 1) {\n throw new Error('meta export must declare only `meta`');\n }\n const declarator = declaration.declarations[0] as AnyNode;\n if (declarator.id?.type !== 'Identifier' || declarator.id.name !== 'meta') {\n throw new Error('meta export must declare `meta`');\n }\n if (!declarator.init) {\n throw new Error('meta must have a literal value');\n }\n\n const meta = evaluateLiteral(declarator.init, 'meta');\n validateMeta(meta);\n\n return {\n meta,\n body: script.slice(0, first.start) + script.slice(first.end),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Determinism / safety guards\n// ---------------------------------------------------------------------------\n\nfunction assertDeterministicAst(node: AnyNode): void {\n if (isDateNowCall(node) || isMathRandomCall(node) || isNewDateExpression(node)) {\n throw new Error(NONDETERMINISM_ERROR);\n }\n for (const child of astChildren(node)) {\n assertDeterministicAst(child);\n }\n}\n\nfunction assertNoDangerousImports(node: AnyNode): void {\n if (node.type === 'ImportDeclaration' || node.type === 'ImportExpression') {\n throw new Error(\n \"Workflow scripts cannot use `import` — only the exposed globals (agent, parallel, pipeline, phase, log, args, cwd, budget) are available.\",\n );\n }\n if (node.type === 'CallExpression') {\n const callee = node.callee as AnyNode | undefined;\n if (callee?.type === 'Identifier' && (callee.name === 'require' || callee.name === 'eval')) {\n throw new Error(`Workflow scripts cannot call \\`${callee.name}\\`.`);\n }\n }\n for (const child of astChildren(node)) {\n assertNoDangerousImports(child);\n }\n}\n\nfunction astChildren(node: AnyNode): AnyNode[] {\n const children: AnyNode[] = [];\n for (const value of Object.values(node)) {\n if (Array.isArray(value)) {\n for (const v of value) {\n if (isAstNode(v)) children.push(v);\n }\n } else if (isAstNode(value)) {\n children.push(value);\n }\n }\n return children;\n}\n\nfunction isAstNode(value: unknown): value is AnyNode {\n return !!value && typeof value === 'object' && typeof (value as AnyNode).type === 'string';\n}\n\nfunction isDateNowCall(node: AnyNode): boolean {\n return node.type === 'CallExpression' && isMemberExpression(node.callee, 'Date', 'now');\n}\n\nfunction isMathRandomCall(node: AnyNode): boolean {\n return node.type === 'CallExpression' && isMemberExpression(node.callee, 'Math', 'random');\n}\n\nfunction isNewDateExpression(node: AnyNode): boolean {\n return (\n node.type === 'NewExpression' &&\n (node.callee as AnyNode | undefined)?.type === 'Identifier' &&\n (node.callee as AnyNode).name === 'Date'\n );\n}\n\nfunction isMemberExpression(\n node: AnyNode | undefined,\n objectName: string,\n propertyName: string,\n): boolean {\n if (\n node?.type !== 'MemberExpression' ||\n (node.object as AnyNode | undefined)?.type !== 'Identifier' ||\n (node.object as AnyNode).name !== objectName\n ) {\n return false;\n }\n const prop = node.property as AnyNode | undefined;\n if (!node.computed && prop?.type === 'Identifier') return prop.name === propertyName;\n if (prop?.type === 'Literal' && typeof prop.value === 'string') return prop.value === propertyName;\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Literal evaluator (meta must be a pure literal)\n// ---------------------------------------------------------------------------\n\nfunction evaluateLiteral(node: AnyNode, path: string): any {\n switch (node.type) {\n case 'ObjectExpression': {\n const out: Record<string, unknown> = {};\n for (const prop of node.properties as AnyNode[]) {\n if (prop.type === 'SpreadElement') {\n throw new Error(`spread not allowed in ${path}`);\n }\n if (prop.type !== 'Property') {\n throw new Error(`only plain properties allowed in ${path}`);\n }\n if (prop.computed) {\n throw new Error(`computed keys not allowed in ${path}`);\n }\n if (prop.kind !== 'init' || prop.method) {\n throw new Error(`methods/accessors not allowed in ${path}`);\n }\n const key = propertyKey(prop.key as AnyNode, path);\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n throw new Error(`reserved key name not allowed in ${path}: ${key}`);\n }\n out[key] = evaluateLiteral(prop.value as AnyNode, `${path}.${key}`);\n }\n return out;\n }\n case 'ArrayExpression':\n return (node.elements as Array<AnyNode | null>).map((element, index) => {\n if (!element) throw new Error(`sparse arrays not allowed in ${path}`);\n if (element.type === 'SpreadElement') {\n throw new Error(`spread not allowed in ${path}`);\n }\n return evaluateLiteral(element, `${path}[${index}]`);\n });\n case 'Literal':\n return node.value;\n case 'TemplateLiteral':\n if (node.expressions.length > 0) {\n throw new Error(`template interpolation not allowed in ${path}`);\n }\n return node.quasis\n .map((quasi: AnyNode) => quasi.value.cooked ?? quasi.value.raw)\n .join('');\n case 'UnaryExpression':\n if (\n node.operator === '-' &&\n node.argument?.type === 'Literal' &&\n typeof node.argument.value === 'number'\n ) {\n return -node.argument.value;\n }\n throw new Error(`only negative-number unary allowed in ${path}`);\n default:\n throw new Error(`non-literal node type in ${path}: ${node.type}`);\n }\n}\n\nfunction propertyKey(node: AnyNode, path: string): string {\n if (node.type === 'Identifier') return node.name;\n if (node.type === 'Literal' && (typeof node.value === 'string' || typeof node.value === 'number')) {\n return String(node.value);\n }\n throw new Error(`unsupported key type in ${path}: ${node.type}`);\n}\n\nfunction validateMeta(meta: unknown): asserts meta is WorkflowMeta {\n if (!meta || typeof meta !== 'object') {\n throw new Error('meta must be an object');\n }\n const value = meta as WorkflowMeta;\n if (typeof value.name !== 'string' || !value.name.trim()) {\n throw new Error('meta.name must be a non-empty string');\n }\n if (!/^[a-z][a-z0-9_-]*$/.test(value.name)) {\n throw new Error(\n `meta.name must be lowercase snake_case (got \"${value.name}\"). Example: \"audit_repo\".`,\n );\n }\n if (typeof value.description !== 'string' || !value.description.trim()) {\n throw new Error('meta.description must be a non-empty string');\n }\n if (value.whenToUse !== undefined && typeof value.whenToUse !== 'string') {\n throw new Error('meta.whenToUse must be a string');\n }\n if (value.phases !== undefined) {\n if (!Array.isArray(value.phases)) {\n throw new Error('meta.phases must be an array');\n }\n for (const phase of value.phases) {\n if (!phase || typeof phase !== 'object' || typeof (phase as WorkflowMetaPhase).title !== 'string') {\n throw new Error('each meta phase must have a title string');\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * WorkflowProgressBroker — the single seam that turns mid-run `workflow`
3
+ * snapshots into IM messages (Telegram today, more channels later).
4
+ *
5
+ * Architecture:
6
+ *
7
+ * pi-agent ──tool_execution_update──▶ AgentEventHandler / SessionEventBus
8
+ * │
9
+ * attachTo(handler) │
10
+ * ▼
11
+ * WorkflowProgressBroker
12
+ * │
13
+ * per-(sessionKey, toolCallId) state
14
+ * │
15
+ * ┌───────────────────┼───────────────────┐
16
+ * ▼ ▼ ▼
17
+ * Telegram cap Feishu cap WeChat cap
18
+ *
19
+ * Why broker + capability instead of "each channel subscribes the bus"?
20
+ * - DRY snapshot aggregation and key-event detection.
21
+ * - Per-channel throttling is enforced by the broker, so a slow / rate-limited
22
+ * channel can't block a fast one.
23
+ * - Adding a new channel = one capability + one register call. Broker code
24
+ * never grows.
25
+ */
26
+ import type { AgentEvent } from '@earendil-works/pi-agent-core';
27
+ import type { Config } from '../../config/schema.js';
28
+ import type { ChannelProgressCapability } from './channel-capability.js';
29
+ import type { WorkflowSnapshot } from './types.js';
30
+ export interface BrokerListenerHandle {
31
+ /** Detach broker from the session bus and clear all in-flight state. */
32
+ dispose(): void;
33
+ }
34
+ /**
35
+ * Tiny façade onto the AgentEventHandler. We don't import the concrete class to
36
+ * keep this module test-friendly — a stub listener pump is fine for unit tests.
37
+ */
38
+ export interface SessionBusLike {
39
+ registerListener(type: AgentEvent['type'] | 'all', listener: (event: AgentEvent, context: {
40
+ sessionKey: string;
41
+ }) => void): () => void;
42
+ }
43
+ export declare class WorkflowProgressBroker {
44
+ private readonly opts;
45
+ private subscribers;
46
+ private states;
47
+ /** Now() factory — overridable in tests for deterministic time. */
48
+ private readonly now;
49
+ /** Cached resolved settings per (channelId), invalidated on registration. */
50
+ private resolved;
51
+ constructor(opts?: {
52
+ getConfig?: () => Config | undefined;
53
+ now?: () => number;
54
+ });
55
+ registerChannel(cap: ChannelProgressCapability): () => void;
56
+ attachTo(bus: SessionBusLike): BrokerListenerHandle;
57
+ /** Visible for tests — direct entry path bypassing the SessionBus glue. */
58
+ onUpdate(sessionKey: string, toolCallId: string, snapshot: WorkflowSnapshot): void;
59
+ /** Visible for tests — direct entry path bypassing the SessionBus glue. */
60
+ onEnd(sessionKey: string, toolCallId: string, snapshot: WorkflowSnapshot | null): void;
61
+ private dispatchToChannel;
62
+ private sendNow;
63
+ private cancelPending;
64
+ private disposeAllPending;
65
+ private getOrCreateState;
66
+ private getOrCreateChannelState;
67
+ /** Resolved (enabled / throttleMs / mode) for a channel, with config overrides. */
68
+ private resolveChannelSettings;
69
+ /** Drop any cached config so the next dispatch re-reads. Call after config reload. */
70
+ invalidateConfigCache(): void;
71
+ /** @internal — for tests only. */
72
+ _stateCount(): number;
73
+ }
74
+ /**
75
+ * Process-wide broker singleton. Channels register against this one; the
76
+ * service wires it to the session bus during startup.
77
+ */
78
+ export declare function getWorkflowProgressBroker(): WorkflowProgressBroker;
79
+ /** Test-only — reset the singleton between cases. */
80
+ export declare function _resetWorkflowProgressBrokerForTests(): void;
@@ -0,0 +1,263 @@
1
+ import { createLogger } from "../../utils/logger/index.js";
2
+ import { init_logger } from "../../utils/logger.js";
3
+ import { renderWorkflowText } from "./snapshot.js";
4
+ //#region src/agent/workflow/progress-broker.ts
5
+ init_logger();
6
+ const log = createLogger("workflow-progress-broker");
7
+ const WORKFLOW_TOOL_NAME = "workflow";
8
+ const RENDER_MAX_AGENTS_PER_PHASE = 4;
9
+ const RENDER_MAX_LOGS = 2;
10
+ var WorkflowProgressBroker = class {
11
+ subscribers = [];
12
+ states = /* @__PURE__ */ new Map();
13
+ /** Now() factory — overridable in tests for deterministic time. */
14
+ now;
15
+ /** Cached resolved settings per (channelId), invalidated on registration. */
16
+ resolved = /* @__PURE__ */ new Map();
17
+ constructor(opts = {}) {
18
+ this.opts = opts;
19
+ this.now = opts.now ?? (() => Date.now());
20
+ }
21
+ registerChannel(cap) {
22
+ if (this.subscribers.some((s) => s.channelId === cap.channelId)) {
23
+ log.warn({ channelId: cap.channelId }, "channel capability already registered; replacing");
24
+ this.subscribers = this.subscribers.filter((s) => s.channelId !== cap.channelId);
25
+ }
26
+ this.subscribers.push(cap);
27
+ this.resolved.delete(cap.channelId);
28
+ return () => {
29
+ this.subscribers = this.subscribers.filter((s) => s !== cap);
30
+ this.resolved.delete(cap.channelId);
31
+ };
32
+ }
33
+ attachTo(bus) {
34
+ const offUpdate = bus.registerListener("tool_execution_update", (event, ctx) => {
35
+ const e = event;
36
+ if (e.toolName !== WORKFLOW_TOOL_NAME) return;
37
+ const snap = extractWorkflowSnapshot(e.partialResult);
38
+ if (!snap) return;
39
+ this.onUpdate(ctx.sessionKey, e.toolCallId, snap);
40
+ });
41
+ const offEnd = bus.registerListener("tool_execution_end", (event, ctx) => {
42
+ const e = event;
43
+ if (e.toolName !== WORKFLOW_TOOL_NAME) return;
44
+ const snap = extractWorkflowSnapshot(e.result, { fromResultEnvelope: true });
45
+ this.onEnd(ctx.sessionKey, e.toolCallId, snap);
46
+ });
47
+ return { dispose: () => {
48
+ offUpdate();
49
+ offEnd();
50
+ this.disposeAllPending();
51
+ } };
52
+ }
53
+ /** Visible for tests — direct entry path bypassing the SessionBus glue. */
54
+ onUpdate(sessionKey, toolCallId, snapshot) {
55
+ const key = stateKey(sessionKey, toolCallId);
56
+ const state = this.getOrCreateState(key, snapshot);
57
+ state.prevSnapshot = state.snapshot;
58
+ state.snapshot = snapshot;
59
+ const isKey = isKeyEvent(state.prevSnapshot, snapshot);
60
+ for (const cap of this.subscribers) this.dispatchToChannel(state, sessionKey, cap, {
61
+ isFinal: false,
62
+ isKey
63
+ });
64
+ }
65
+ /** Visible for tests — direct entry path bypassing the SessionBus glue. */
66
+ onEnd(sessionKey, toolCallId, snapshot) {
67
+ const key = stateKey(sessionKey, toolCallId);
68
+ const state = this.states.get(key);
69
+ if (!state) return;
70
+ if (snapshot) state.snapshot = snapshot;
71
+ for (const cap of this.subscribers) {
72
+ this.cancelPending(state, cap.channelId);
73
+ this.dispatchToChannel(state, sessionKey, cap, {
74
+ isFinal: true,
75
+ isKey: true
76
+ });
77
+ }
78
+ setTimeout(() => this.states.delete(key), 2e3);
79
+ }
80
+ dispatchToChannel(state, sessionKey, cap, flags) {
81
+ const cfg = this.resolveChannelSettings(cap);
82
+ if (!cfg.enabled) return;
83
+ if (cfg.mode === "final-only" && !flags.isFinal) return;
84
+ const chState = this.getOrCreateChannelState(state, cap.channelId);
85
+ if (flags.isFinal || flags.isKey) {
86
+ this.cancelPending(state, cap.channelId);
87
+ this.sendNow(state, sessionKey, cap, cfg, flags.isFinal);
88
+ return;
89
+ }
90
+ const elapsed = this.now() - chState.lastSentAt;
91
+ const wait = Math.max(0, cfg.throttleMs - elapsed);
92
+ if (wait === 0) {
93
+ this.sendNow(state, sessionKey, cap, cfg, false);
94
+ return;
95
+ }
96
+ if (chState.pendingTimer) return;
97
+ chState.pendingTimer = setTimeout(() => {
98
+ chState.pendingTimer = void 0;
99
+ this.sendNow(state, sessionKey, cap, cfg, false);
100
+ }, wait);
101
+ }
102
+ async sendNow(state, sessionKey, cap, cfg, isFinal) {
103
+ const chState = this.getOrCreateChannelState(state, cap.channelId);
104
+ if (chState.inflight) await chState.inflight.catch(() => void 0);
105
+ const text = renderWorkflowText(state.snapshot, isFinal, {
106
+ maxAgentsPerPhase: RENDER_MAX_AGENTS_PER_PHASE,
107
+ maxLogs: RENDER_MAX_LOGS,
108
+ showResultPreviews: isFinal
109
+ });
110
+ const previousMessageId = cfg.mode === "edit" ? chState.lastMessageId : void 0;
111
+ const task = cap.postProgress({
112
+ sessionKey,
113
+ text,
114
+ previousMessageId,
115
+ isFinal,
116
+ mode: cfg.mode
117
+ }).then((r) => {
118
+ chState.lastMessageId = r.messageId;
119
+ chState.lastSentAt = this.now();
120
+ }).catch((err) => {
121
+ const msg = err instanceof Error ? err.message : String(err);
122
+ log.warn({
123
+ err,
124
+ errorMessage: msg,
125
+ channelId: cap.channelId,
126
+ sessionKey
127
+ }, `workflow progress postProgress failed: ${msg}`);
128
+ }).finally(() => {
129
+ chState.inflight = void 0;
130
+ });
131
+ chState.inflight = task;
132
+ await task;
133
+ }
134
+ cancelPending(state, channelId) {
135
+ const chState = state.perChannel.get(channelId);
136
+ if (chState?.pendingTimer) {
137
+ clearTimeout(chState.pendingTimer);
138
+ chState.pendingTimer = void 0;
139
+ }
140
+ }
141
+ disposeAllPending() {
142
+ for (const state of this.states.values()) for (const ch of state.perChannel.values()) if (ch.pendingTimer) clearTimeout(ch.pendingTimer);
143
+ this.states.clear();
144
+ }
145
+ getOrCreateState(key, snapshot) {
146
+ let state = this.states.get(key);
147
+ if (!state) {
148
+ state = {
149
+ snapshot,
150
+ perChannel: /* @__PURE__ */ new Map()
151
+ };
152
+ this.states.set(key, state);
153
+ }
154
+ return state;
155
+ }
156
+ getOrCreateChannelState(state, channelId) {
157
+ let ch = state.perChannel.get(channelId);
158
+ if (!ch) {
159
+ ch = { lastSentAt: 0 };
160
+ state.perChannel.set(channelId, ch);
161
+ }
162
+ return ch;
163
+ }
164
+ /** Resolved (enabled / throttleMs / mode) for a channel, with config overrides. */
165
+ resolveChannelSettings(cap) {
166
+ const cached = this.resolved.get(cap.channelId);
167
+ if (cached) return cached;
168
+ const override = readChannelConfig(this.opts.getConfig?.(), cap.channelId);
169
+ const resolved = {
170
+ enabled: override?.enabled ?? true,
171
+ throttleMs: override?.throttleMs ?? cap.defaultThrottleMs,
172
+ mode: override?.mode ?? cap.defaultMode
173
+ };
174
+ this.resolved.set(cap.channelId, resolved);
175
+ return resolved;
176
+ }
177
+ /** Drop any cached config so the next dispatch re-reads. Call after config reload. */
178
+ invalidateConfigCache() {
179
+ this.resolved.clear();
180
+ }
181
+ /** @internal — for tests only. */
182
+ _stateCount() {
183
+ return this.states.size;
184
+ }
185
+ };
186
+ let singleton = null;
187
+ /**
188
+ * Process-wide broker singleton. Channels register against this one; the
189
+ * service wires it to the session bus during startup.
190
+ */
191
+ function getWorkflowProgressBroker() {
192
+ if (!singleton) singleton = new WorkflowProgressBroker();
193
+ return singleton;
194
+ }
195
+ /** Test-only — reset the singleton between cases. */
196
+ function _resetWorkflowProgressBrokerForTests() {
197
+ singleton = null;
198
+ }
199
+ function stateKey(sessionKey, toolCallId) {
200
+ return `${sessionKey}${toolCallId ?? ""}`;
201
+ }
202
+ /**
203
+ * Compare two snapshots and decide whether the new one is "key" — i.e. worth
204
+ * bypassing the per-channel throttle. Anything visible to the user as a
205
+ * progress milestone qualifies; counts ticking by alone do not.
206
+ */
207
+ function isKeyEvent(prev, next) {
208
+ if (!prev) return true;
209
+ if (prev.currentPhase !== next.currentPhase) return true;
210
+ if (next.errorCount > prev.errorCount) return true;
211
+ if (next.skippedCount > prev.skippedCount) return true;
212
+ if (prev.phases.length !== next.phases.length) return true;
213
+ if (hasNewFailedAgent(prev.agents, next.agents)) return true;
214
+ return false;
215
+ }
216
+ function hasNewFailedAgent(prev, next) {
217
+ const prevBad = new Set(prev.filter((a) => a.status === "error" || a.status === "skipped").map((a) => a.id));
218
+ for (const a of next) if ((a.status === "error" || a.status === "skipped") && !prevBad.has(a.id)) return true;
219
+ return false;
220
+ }
221
+ /**
222
+ * Pull a {@link WorkflowSnapshot} out of an AgentToolResult-shaped value.
223
+ * Returns null when the payload is not snapshot-shaped (text-only updates,
224
+ * non-workflow tools, etc.).
225
+ *
226
+ * `fromResultEnvelope = true` (used for `tool_end.result`) tolerates the
227
+ * `{ content, details }` wrapper.
228
+ */
229
+ function extractWorkflowSnapshot(payload, opts = {}) {
230
+ if (!payload || typeof payload !== "object") return null;
231
+ const rec = payload;
232
+ if (opts.fromResultEnvelope) {
233
+ const details = rec.details;
234
+ if (details && typeof details === "object") return coerce(details);
235
+ return null;
236
+ }
237
+ if ("details" in rec) {
238
+ const details = rec.details;
239
+ if (details && typeof details === "object") return coerce(details);
240
+ return null;
241
+ }
242
+ return coerce(rec);
243
+ }
244
+ function coerce(value) {
245
+ if (!value || typeof value !== "object") return null;
246
+ const rec = value;
247
+ if (typeof rec.name !== "string") return null;
248
+ if (!Array.isArray(rec.agents)) return null;
249
+ return value;
250
+ }
251
+ function readChannelConfig(config, channelId) {
252
+ const channels = config?.channels;
253
+ if (!channels) return void 0;
254
+ const cfg = channels[channelId];
255
+ if (!cfg || typeof cfg !== "object") return void 0;
256
+ const wf = cfg.workflowProgress;
257
+ if (!wf || typeof wf !== "object") return void 0;
258
+ return wf;
259
+ }
260
+ //#endregion
261
+ export { WorkflowProgressBroker, _resetWorkflowProgressBrokerForTests, getWorkflowProgressBroker };
262
+
263
+ //# sourceMappingURL=progress-broker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress-broker.js","names":[],"sources":["../../../../src/agent/workflow/progress-broker.ts"],"sourcesContent":["/**\n * WorkflowProgressBroker — the single seam that turns mid-run `workflow`\n * snapshots into IM messages (Telegram today, more channels later).\n *\n * Architecture:\n *\n * pi-agent ──tool_execution_update──▶ AgentEventHandler / SessionEventBus\n * │\n * attachTo(handler) │\n * ▼\n * WorkflowProgressBroker\n * │\n * per-(sessionKey, toolCallId) state\n * │\n * ┌───────────────────┼───────────────────┐\n * ▼ ▼ ▼\n * Telegram cap Feishu cap WeChat cap\n *\n * Why broker + capability instead of \"each channel subscribes the bus\"?\n * - DRY snapshot aggregation and key-event detection.\n * - Per-channel throttling is enforced by the broker, so a slow / rate-limited\n * channel can't block a fast one.\n * - Adding a new channel = one capability + one register call. Broker code\n * never grows.\n */\n\nimport type { AgentEvent } from '@earendil-works/pi-agent-core';\n\nimport { createLogger } from '../../utils/logger.js';\nimport type { Config } from '../../config/schema.js';\n\nimport type {\n ChannelProgressCapability,\n WorkflowProgressMode,\n} from './channel-capability.js';\nimport { renderWorkflowText } from './snapshot.js';\nimport type { WorkflowAgentSnapshot, WorkflowSnapshot } from './types.js';\n\nconst log = createLogger('workflow-progress-broker');\n\nconst WORKFLOW_TOOL_NAME = 'workflow';\nconst RENDER_MAX_AGENTS_PER_PHASE = 4;\nconst RENDER_MAX_LOGS = 2;\n\n/** Per-channel resolved settings after applying config overrides on capability defaults. */\ninterface ChannelResolvedSettings {\n enabled: boolean;\n throttleMs: number;\n mode: WorkflowProgressMode;\n}\n\n/** Per-(sessionKey, toolCallId) progress state. */\ninterface RunState {\n /** Latest snapshot seen for this run. Always overwritten on update. */\n snapshot: WorkflowSnapshot;\n /** Previous snapshot used to detect \"key events\" (phase change, new errors). */\n prevSnapshot?: WorkflowSnapshot;\n /** Per-channel last-send bookkeeping. */\n perChannel: Map<string, ChannelRunState>;\n}\n\ninterface ChannelRunState {\n /** Server timestamp of the last successful postProgress. */\n lastSentAt: number;\n /** Returned messageId of the last successful postProgress — drives edit mode. */\n lastMessageId?: string;\n /** Pending timer id (Node `setTimeout`) when a throttled flush is scheduled. */\n pendingTimer?: ReturnType<typeof setTimeout>;\n /** Inflight `postProgress` promise; we serialise per-(state, channel) to avoid race. */\n inflight?: Promise<void>;\n}\n\nexport interface BrokerListenerHandle {\n /** Detach broker from the session bus and clear all in-flight state. */\n dispose(): void;\n}\n\n/**\n * Tiny façade onto the AgentEventHandler. We don't import the concrete class to\n * keep this module test-friendly — a stub listener pump is fine for unit tests.\n */\nexport interface SessionBusLike {\n registerListener(\n type: AgentEvent['type'] | 'all',\n listener: (event: AgentEvent, context: { sessionKey: string }) => void,\n ): () => void;\n}\n\nexport class WorkflowProgressBroker {\n private subscribers: ChannelProgressCapability[] = [];\n private states = new Map<string, RunState>();\n /** Now() factory — overridable in tests for deterministic time. */\n private readonly now: () => number;\n /** Cached resolved settings per (channelId), invalidated on registration. */\n private resolved = new Map<string, ChannelResolvedSettings>();\n\n constructor(\n private readonly opts: {\n getConfig?: () => Config | undefined;\n now?: () => number;\n } = {},\n ) {\n this.now = opts.now ?? (() => Date.now());\n }\n\n // ── Registration ────────────────────────────────────────────────────────────\n\n registerChannel(cap: ChannelProgressCapability): () => void {\n if (this.subscribers.some((s) => s.channelId === cap.channelId)) {\n log.warn({ channelId: cap.channelId }, 'channel capability already registered; replacing');\n this.subscribers = this.subscribers.filter((s) => s.channelId !== cap.channelId);\n }\n this.subscribers.push(cap);\n this.resolved.delete(cap.channelId);\n return () => {\n this.subscribers = this.subscribers.filter((s) => s !== cap);\n this.resolved.delete(cap.channelId);\n };\n }\n\n attachTo(bus: SessionBusLike): BrokerListenerHandle {\n const offUpdate = bus.registerListener('tool_execution_update', (event, ctx) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_update' }>;\n if (e.toolName !== WORKFLOW_TOOL_NAME) return;\n const snap = extractWorkflowSnapshot(e.partialResult);\n if (!snap) return;\n this.onUpdate(ctx.sessionKey, e.toolCallId, snap);\n });\n\n const offEnd = bus.registerListener('tool_execution_end', (event, ctx) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n if (e.toolName !== WORKFLOW_TOOL_NAME) return;\n // tool_end ships the final envelope in `result`; reach in for the\n // authoritative snapshot (durationMs / result / final counts).\n const snap = extractWorkflowSnapshot(e.result, { fromResultEnvelope: true });\n this.onEnd(ctx.sessionKey, e.toolCallId, snap);\n });\n\n return {\n dispose: () => {\n offUpdate();\n offEnd();\n this.disposeAllPending();\n },\n };\n }\n\n // ── Core state machine ──────────────────────────────────────────────────────\n\n /** Visible for tests — direct entry path bypassing the SessionBus glue. */\n onUpdate(sessionKey: string, toolCallId: string, snapshot: WorkflowSnapshot): void {\n const key = stateKey(sessionKey, toolCallId);\n const state = this.getOrCreateState(key, snapshot);\n state.prevSnapshot = state.snapshot;\n state.snapshot = snapshot;\n\n const isKey = isKeyEvent(state.prevSnapshot, snapshot);\n for (const cap of this.subscribers) {\n this.dispatchToChannel(state, sessionKey, cap, { isFinal: false, isKey });\n }\n }\n\n /** Visible for tests — direct entry path bypassing the SessionBus glue. */\n onEnd(sessionKey: string, toolCallId: string, snapshot: WorkflowSnapshot | null): void {\n const key = stateKey(sessionKey, toolCallId);\n const state = this.states.get(key);\n if (!state) return;\n if (snapshot) state.snapshot = snapshot;\n\n for (const cap of this.subscribers) {\n // Always flush the final message — bypass throttle and any pending timer.\n this.cancelPending(state, cap.channelId);\n this.dispatchToChannel(state, sessionKey, cap, { isFinal: true, isKey: true });\n }\n // State is GC'd lazily after a small grace period so any straggler\n // `update` event arriving after `end` is silently dropped (instead of\n // resurrecting the run).\n setTimeout(() => this.states.delete(key), 2_000);\n }\n\n // ── Dispatch + throttle ─────────────────────────────────────────────────────\n\n private dispatchToChannel(\n state: RunState,\n sessionKey: string,\n cap: ChannelProgressCapability,\n flags: { isFinal: boolean; isKey: boolean },\n ): void {\n const cfg = this.resolveChannelSettings(cap);\n if (!cfg.enabled) return;\n if (cfg.mode === 'final-only' && !flags.isFinal) return;\n\n const chState = this.getOrCreateChannelState(state, cap.channelId);\n\n // Key events and the final message bypass throttle.\n if (flags.isFinal || flags.isKey) {\n this.cancelPending(state, cap.channelId);\n void this.sendNow(state, sessionKey, cap, cfg, flags.isFinal);\n return;\n }\n\n const elapsed = this.now() - chState.lastSentAt;\n const wait = Math.max(0, cfg.throttleMs - elapsed);\n if (wait === 0) {\n void this.sendNow(state, sessionKey, cap, cfg, false);\n return;\n }\n if (chState.pendingTimer) return; // already scheduled; latest snapshot will be picked up\n chState.pendingTimer = setTimeout(() => {\n chState.pendingTimer = undefined;\n void this.sendNow(state, sessionKey, cap, cfg, false);\n }, wait);\n }\n\n private async sendNow(\n state: RunState,\n sessionKey: string,\n cap: ChannelProgressCapability,\n cfg: ChannelResolvedSettings,\n isFinal: boolean,\n ): Promise<void> {\n const chState = this.getOrCreateChannelState(state, cap.channelId);\n // Serialise per-channel sends so a slow editMessage call doesn't get\n // overtaken by a faster one and leave the bubble out of order.\n if (chState.inflight) await chState.inflight.catch(() => undefined);\n\n const text = renderWorkflowText(state.snapshot, isFinal, {\n maxAgentsPerPhase: RENDER_MAX_AGENTS_PER_PHASE,\n maxLogs: RENDER_MAX_LOGS,\n showResultPreviews: isFinal,\n });\n\n const previousMessageId = cfg.mode === 'edit' ? chState.lastMessageId : undefined;\n const task = cap\n .postProgress({ sessionKey, text, previousMessageId, isFinal, mode: cfg.mode })\n .then((r) => {\n chState.lastMessageId = r.messageId;\n chState.lastSentAt = this.now();\n })\n .catch((err) => {\n const msg = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, errorMessage: msg, channelId: cap.channelId, sessionKey },\n `workflow progress postProgress failed: ${msg}`,\n );\n })\n .finally(() => {\n chState.inflight = undefined;\n });\n\n chState.inflight = task;\n await task;\n }\n\n private cancelPending(state: RunState, channelId: string): void {\n const chState = state.perChannel.get(channelId);\n if (chState?.pendingTimer) {\n clearTimeout(chState.pendingTimer);\n chState.pendingTimer = undefined;\n }\n }\n\n private disposeAllPending(): void {\n for (const state of this.states.values()) {\n for (const ch of state.perChannel.values()) {\n if (ch.pendingTimer) clearTimeout(ch.pendingTimer);\n }\n }\n this.states.clear();\n }\n\n // ── State helpers ───────────────────────────────────────────────────────────\n\n private getOrCreateState(key: string, snapshot: WorkflowSnapshot): RunState {\n let state = this.states.get(key);\n if (!state) {\n state = { snapshot, perChannel: new Map() };\n this.states.set(key, state);\n }\n return state;\n }\n\n private getOrCreateChannelState(state: RunState, channelId: string): ChannelRunState {\n let ch = state.perChannel.get(channelId);\n if (!ch) {\n ch = { lastSentAt: 0 };\n state.perChannel.set(channelId, ch);\n }\n return ch;\n }\n\n /** Resolved (enabled / throttleMs / mode) for a channel, with config overrides. */\n private resolveChannelSettings(cap: ChannelProgressCapability): ChannelResolvedSettings {\n const cached = this.resolved.get(cap.channelId);\n if (cached) return cached;\n const override = readChannelConfig(this.opts.getConfig?.(), cap.channelId);\n const resolved: ChannelResolvedSettings = {\n enabled: override?.enabled ?? true,\n throttleMs: override?.throttleMs ?? cap.defaultThrottleMs,\n mode: override?.mode ?? cap.defaultMode,\n };\n this.resolved.set(cap.channelId, resolved);\n return resolved;\n }\n\n /** Drop any cached config so the next dispatch re-reads. Call after config reload. */\n invalidateConfigCache(): void {\n this.resolved.clear();\n }\n\n // ── Test introspection ──────────────────────────────────────────────────────\n\n /** @internal — for tests only. */\n _stateCount(): number {\n return this.states.size;\n }\n}\n\n// ── Singleton ───────────────────────────────────────────────────────────────\n\nlet singleton: WorkflowProgressBroker | null = null;\n\n/**\n * Process-wide broker singleton. Channels register against this one; the\n * service wires it to the session bus during startup.\n */\nexport function getWorkflowProgressBroker(): WorkflowProgressBroker {\n if (!singleton) singleton = new WorkflowProgressBroker();\n return singleton;\n}\n\n/** Test-only — reset the singleton between cases. */\nexport function _resetWorkflowProgressBrokerForTests(): void {\n singleton = null;\n}\n\n// ── Pure helpers ────────────────────────────────────────────────────────────\n\nfunction stateKey(sessionKey: string, toolCallId: string | undefined): string {\n return `${sessionKey}\u0001${toolCallId ?? ''}`;\n}\n\n/**\n * Compare two snapshots and decide whether the new one is \"key\" — i.e. worth\n * bypassing the per-channel throttle. Anything visible to the user as a\n * progress milestone qualifies; counts ticking by alone do not.\n */\nfunction isKeyEvent(prev: WorkflowSnapshot | undefined, next: WorkflowSnapshot): boolean {\n if (!prev) return true; // first update of the run\n if (prev.currentPhase !== next.currentPhase) return true;\n if (next.errorCount > prev.errorCount) return true;\n if (next.skippedCount > prev.skippedCount) return true;\n // New phase row in the rollup (declared via `phase(...)` mid-run)\n if (prev.phases.length !== next.phases.length) return true;\n if (hasNewFailedAgent(prev.agents, next.agents)) return true;\n return false;\n}\n\nfunction hasNewFailedAgent(\n prev: WorkflowAgentSnapshot[],\n next: WorkflowAgentSnapshot[],\n): boolean {\n const prevBad = new Set(\n prev.filter((a) => a.status === 'error' || a.status === 'skipped').map((a) => a.id),\n );\n for (const a of next) {\n if ((a.status === 'error' || a.status === 'skipped') && !prevBad.has(a.id)) return true;\n }\n return false;\n}\n\n/**\n * Pull a {@link WorkflowSnapshot} out of an AgentToolResult-shaped value.\n * Returns null when the payload is not snapshot-shaped (text-only updates,\n * non-workflow tools, etc.).\n *\n * `fromResultEnvelope = true` (used for `tool_end.result`) tolerates the\n * `{ content, details }` wrapper.\n */\nfunction extractWorkflowSnapshot(\n payload: unknown,\n opts: { fromResultEnvelope?: boolean } = {},\n): WorkflowSnapshot | null {\n if (!payload || typeof payload !== 'object') return null;\n const rec = payload as Record<string, unknown>;\n if (opts.fromResultEnvelope) {\n const details = rec.details;\n if (details && typeof details === 'object') return coerce(details);\n return null;\n }\n if ('details' in rec) {\n const details = rec.details;\n if (details && typeof details === 'object') return coerce(details);\n return null;\n }\n return coerce(rec);\n}\n\nfunction coerce(value: unknown): WorkflowSnapshot | null {\n if (!value || typeof value !== 'object') return null;\n const rec = value as Record<string, unknown>;\n if (typeof rec.name !== 'string') return null;\n if (!Array.isArray(rec.agents)) return null;\n return value as WorkflowSnapshot;\n}\n\nfunction readChannelConfig(\n config: Config | undefined,\n channelId: string,\n): { enabled?: boolean; throttleMs?: number; mode?: WorkflowProgressMode } | undefined {\n const channels = config?.channels as Record<string, unknown> | undefined;\n if (!channels) return undefined;\n const cfg = channels[channelId];\n if (!cfg || typeof cfg !== 'object') return undefined;\n const wf = (cfg as { workflowProgress?: unknown }).workflowProgress;\n if (!wf || typeof wf !== 'object') return undefined;\n return wf as { enabled?: boolean; throttleMs?: number; mode?: WorkflowProgressMode };\n}\n"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Sandboxed workflow runtime.
3
+ *
4
+ * Parses the script via {@link parseWorkflowScript} (which strips the `meta`
5
+ * export), wraps the remaining body in an async IIFE, and runs it inside a
6
+ * Node `vm` context with a curated set of globals:
7
+ *
8
+ * - `agent(prompt, opts)` — spawns a subagent through the injected
9
+ * {@link SubagentRunner} and returns its result (string, or schema-validated
10
+ * object). Failures resolve to `null`.
11
+ * - `parallel(thunks)` — concurrent fan-out; thunks (not promises!) so the
12
+ * limiter sees each agent() call.
13
+ * - `pipeline(items, ...stages)` — per-item sequential stages, items run
14
+ * concurrently (no stage barrier). Each stage receives
15
+ * `(prevResult, originalItem, index)`. A stage that throws drops that item
16
+ * to `null` and skips remaining stages.
17
+ * - `phase(title)` — marks the current phase; surfaces through `onPhase`.
18
+ * - `log(message)` — appends to the workflow log.
19
+ * - `budget` — `{ total, spent(), remaining() }` for self-pacing scripts.
20
+ * - `args`, `cwd`, `process.cwd()`.
21
+ *
22
+ * The runtime is the only code that touches `vm`. It exposes no IO surface,
23
+ * carries no LLM dependency, and is fully driven by injected callbacks — that
24
+ * means the workflow tool, tests, and any future runner share one runtime.
25
+ */
26
+ import type { SubagentRunner, WorkflowRunOptions, WorkflowRunResult, WorkflowSnapshot } from './types.js';
27
+ export interface RunWorkflowDeps {
28
+ runner: SubagentRunner;
29
+ }
30
+ export declare function runWorkflow<T = unknown>(script: string, deps: RunWorkflowDeps, options: WorkflowRunOptions): Promise<WorkflowRunResult<T>>;
31
+ export declare function emptySnapshotFor(name: string, description?: string): WorkflowSnapshot;