@xopcai/xopc 0.0.93 → 0.0.95

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 (356) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  3. package/dist/extensions/feishu/src/workflow-progress.js +1 -1
  4. package/dist/extensions/telegram/src/plugin.js +1 -1
  5. package/dist/extensions/telegram/src/routing-integration.js +2 -2
  6. package/dist/extensions/telegram/src/workflow-progress.js +1 -1
  7. package/dist/extensions/telegram/xopc.extension.json +1 -1
  8. package/dist/extensions/weixin/src/api/api.js +2 -2
  9. package/dist/extensions/weixin/src/auth/accounts.js +1 -1
  10. package/dist/extensions/weixin/src/cdn/upload.js +1 -1
  11. package/dist/extensions/weixin/src/media/data-url.js +1 -1
  12. package/dist/extensions/weixin/src/messaging/debug-mode.js +1 -1
  13. package/dist/extensions/weixin/src/messaging/inbound.js +1 -1
  14. package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
  15. package/dist/extensions/weixin/src/plugin.js +1 -1
  16. package/dist/extensions/weixin/src/storage/sync-buf.js +1 -1
  17. package/dist/extensions/weixin/src/workflow-progress.js +1 -1
  18. package/dist/gateway/static/root/assets/agents-CKe2LMnz.js +222 -0
  19. package/dist/gateway/static/root/assets/apps-page-Mi9mMIZ1.js +1 -0
  20. package/dist/gateway/static/root/assets/channels-settings-BrdyC101.js +1 -0
  21. package/dist/gateway/static/root/assets/{channels-status-swr-CsGkK9h9.js → channels-status-swr-D55Bu0nn.js} +1 -1
  22. package/dist/gateway/static/root/assets/{cron-api-CyAm0xJT.js → cron-api-CPpx2l-E.js} +1 -1
  23. package/dist/gateway/static/root/assets/cron-page-Bx2jB0YN.js +1 -0
  24. package/dist/gateway/static/root/assets/{dist-DHwVV8XK.js → dist-D_AiG_Kg.js} +1 -1
  25. package/dist/gateway/static/root/assets/{extension-debug-page-BK8kcc4F.js → extension-debug-page-6ieHsxRE.js} +1 -1
  26. package/dist/gateway/static/root/assets/{extension-page-Cf8X_QUc.js → extension-page-B8nywHRO.js} +1 -1
  27. package/dist/gateway/static/root/assets/{extension-settings-page-C5-YLMmy.js → extension-settings-page-DrskdEIV.js} +1 -1
  28. package/dist/gateway/static/root/assets/{fetch-BAPnkYbC.js → fetch-B0aeeY0q.js} +1 -1
  29. package/dist/gateway/static/root/assets/{field-primitives-8p7ucXa1.js → field-primitives--9ooY8Xl.js} +1 -1
  30. package/dist/gateway/static/root/assets/{heartbeat-config-api-CpgW2sGp.js → heartbeat-config-api-DUZ_W1w-.js} +1 -1
  31. package/dist/gateway/static/root/assets/index-Bj_l8QDp.css +1 -0
  32. package/dist/gateway/static/root/assets/{index-Do52EfZK.js → index-Dj9FuxCm.js} +99 -98
  33. package/dist/gateway/static/root/assets/logs-page-CaXqhpKf.js +2 -0
  34. package/dist/gateway/static/root/assets/{note-detail-page-WLM6FUIi.js → note-detail-page-DYzym2B0.js} +3 -3
  35. package/dist/gateway/static/root/assets/{note-time-EFyIVhec.js → note-time-B-vSi2dR.js} +1 -1
  36. package/dist/gateway/static/root/assets/notes-page-BkhWdGiT.js +1 -0
  37. package/dist/gateway/static/root/assets/sessions-page-53YFokoe.js +1 -0
  38. package/dist/gateway/static/root/assets/{settings-advanced-gate-CEs8pGh6.js → settings-advanced-gate-BaZmaklx.js} +2 -2
  39. package/dist/gateway/static/root/assets/{settings-form-section-C6cGTVwK.js → settings-form-section-DIJPKpTR.js} +1 -1
  40. package/dist/gateway/static/root/assets/settings-page-Dvb230FF.js +3 -0
  41. package/dist/gateway/static/root/assets/share-preview-page-CRyjTAG6.js +2 -0
  42. package/dist/gateway/static/root/assets/skills-page-C5ZJbfAe.js +2 -0
  43. package/dist/gateway/static/root/assets/{theme-store-D6EsNTPr.js → theme-store-Cg_SuBw0.js} +1 -1
  44. package/dist/gateway/static/root/assets/{url-CTjpm0Uz.js → url-BHHmdJYc.js} +2 -2
  45. package/dist/gateway/static/root/assets/{utils-C86AVfY-.js → utils-lMYoWhqo.js} +1 -1
  46. package/dist/gateway/static/root/assets/voice-api-key-field-Dda2pcUU.js +1 -0
  47. package/dist/gateway/static/root/assets/{workflow-page.utils-DsEriMFW.js → workflow-page.utils-KIladUrU.js} +1 -1
  48. package/dist/gateway/static/root/assets/workflows-page-BTis4Z7Y.js +27 -0
  49. package/dist/gateway/static/root/index.html +5 -5
  50. package/dist/package.js +1 -1
  51. package/dist/src/agent/agent-manager.js +7 -7
  52. package/dist/src/agent/agent-scope.js +1 -1
  53. package/dist/src/agent/bootstrap/load-bootstrap-files.js +1 -1
  54. package/dist/src/agent/context/workspace-seed.js +2 -2
  55. package/dist/src/agent/goals/goal-run-store.js +4 -4
  56. package/dist/src/agent/goals/persistent-goal-service.js +1 -1
  57. package/dist/src/agent/goals/post-turn.js +2 -2
  58. package/dist/src/agent/image/load-image-media.js +2 -2
  59. package/dist/src/agent/inbound/turn-dispatcher.d.ts +1 -0
  60. package/dist/src/agent/inbound/turn-dispatcher.js +3 -0
  61. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  62. package/dist/src/agent/ipc/bus.js +1 -1
  63. package/dist/src/agent/ipc/inbox.js +2 -2
  64. package/dist/src/agent/ipc/socket.js +1 -1
  65. package/dist/src/agent/lifecycle/handlers/compaction.js +1 -1
  66. package/dist/src/agent/lifecycle/handlers/compaction.js.map +1 -1
  67. package/dist/src/agent/mcp/bundle-mcp-materialize.js +2 -2
  68. package/dist/src/agent/mcp/bundle-mcp-materialize.js.map +1 -1
  69. package/dist/src/agent/mcp/bundle-mcp-runtime.js +18 -5
  70. package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
  71. package/dist/src/agent/mcp/mcp-transport-config.js +11 -4
  72. package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
  73. package/dist/src/agent/mcp/mcp-transport.js +2 -2
  74. package/dist/src/agent/mcp/mcp-transport.js.map +1 -1
  75. package/dist/src/agent/memory/builtin-memory-store.js +1 -1
  76. package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
  77. package/dist/src/agent/memory/dreaming/events.js +1 -1
  78. package/dist/src/agent/memory/dreaming/last-run.js +1 -1
  79. package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
  80. package/dist/src/agent/memory/dreaming/preview.js +1 -1
  81. package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
  82. package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
  83. package/dist/src/agent/memory/dreaming/utils.js +1 -1
  84. package/dist/src/agent/memory/plugin-discovery.js +1 -1
  85. package/dist/src/agent/models/manager.js +1 -1
  86. package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
  87. package/dist/src/agent/reply/post-compaction-context.js +1 -1
  88. package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
  89. package/dist/src/agent/sandbox/path-policy.js +2 -2
  90. package/dist/src/agent/service/build-direct-message-content.js +1 -1
  91. package/dist/src/agent/service/process-direct-streaming.d.ts +1 -0
  92. package/dist/src/agent/service/process-direct-streaming.js +15 -12
  93. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  94. package/dist/src/agent/service.d.ts +4 -2
  95. package/dist/src/agent/service.js +24 -8
  96. package/dist/src/agent/service.js.map +1 -1
  97. package/dist/src/agent/service.types.d.ts +3 -1
  98. package/dist/src/agent/session/session-inspector.js +1 -1
  99. package/dist/src/agent/skills/config.js +1 -1
  100. package/dist/src/agent/skills/hub-hash.js +2 -2
  101. package/dist/src/agent/skills/hub-lock.js +1 -1
  102. package/dist/src/agent/skills/hub-pull.js +2 -2
  103. package/dist/src/agent/skills/index.js +1 -1
  104. package/dist/src/agent/skills/managed-store.js +1 -1
  105. package/dist/src/agent/skills/scanner.js +1 -1
  106. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  107. package/dist/src/agent/skills/skill-manager.js +1 -1
  108. package/dist/src/agent/tools/browser/tool/browser-use-tool.js +1 -1
  109. package/dist/src/agent/tools/browser/tool/browser-use-tool.js.map +1 -1
  110. package/dist/src/agent/tools/dreaming-tool.js +1 -1
  111. package/dist/src/agent/tools/factory.js +1 -1
  112. package/dist/src/agent/tools/image-generate-tool.js +1 -1
  113. package/dist/src/agent/tools/search/registry.js +1 -1
  114. package/dist/src/agent/tools/search/registry.js.map +1 -1
  115. package/dist/src/agent/tools/send-media.js +1 -1
  116. package/dist/src/agent/tools/session-search-tool.js +1 -1
  117. package/dist/src/agent/tools/session-search-tool.js.map +1 -1
  118. package/dist/src/agent/tools/skill-manage-tool.js +1 -1
  119. package/dist/src/agent/tools/workflow-tool.js +2 -2
  120. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  121. package/dist/src/agent/tools/write.js +1 -1
  122. package/dist/src/agent/workflow/catalog.js +1 -1
  123. package/dist/src/agent/workflow/progress-broker.js +1 -1
  124. package/dist/src/agent/workflow/progress-broker.js.map +1 -1
  125. package/dist/src/agent/workflow/subagent-runner.js +1 -1
  126. package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
  127. package/dist/src/auth/credentials.js +3 -3
  128. package/dist/src/auth/profiles/store.js +1 -1
  129. package/dist/src/auth/sync-provider-auth.js +1 -1
  130. package/dist/src/browser/cache-dir-policy.js +1 -1
  131. package/dist/src/browser/cdp-local-launcher.js +2 -2
  132. package/dist/src/browser/providers/browser-ext-install.js +3 -3
  133. package/dist/src/browser/providers/cloakbrowser.js +4 -4
  134. package/dist/src/browser/providers/playwright-doctor.js +1 -1
  135. package/dist/src/browser/stealth.js +1 -1
  136. package/dist/src/channels/attachments/inbound-persist.js +1 -1
  137. package/dist/src/channels/attachments/outbound-tts-persist.js +1 -1
  138. package/dist/src/channels/outbound/persist-store.js +1 -1
  139. package/dist/src/channels/pairing/allow-from-file.js +1 -1
  140. package/dist/src/channels/pairing/pairing-store.js +2 -2
  141. package/dist/src/channels/pipeline.js +3 -2
  142. package/dist/src/channels/pipeline.js.map +1 -1
  143. package/dist/src/chat-commands/agent-edit.js +2 -2
  144. package/dist/src/chat-commands/builtins/config.js +2 -2
  145. package/dist/src/chat-commands/context.js +1 -1
  146. package/dist/src/cli/cli-log-level-preset.d.ts +1 -1
  147. package/dist/src/cli/cli-log-level-preset.js +2 -2
  148. package/dist/src/cli/cli-log-level-preset.js.map +1 -1
  149. package/dist/src/cli/commands/config.js +1 -1
  150. package/dist/src/cli/commands/doctor/checks/config-health.js +1 -1
  151. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  152. package/dist/src/cli/commands/doctor/checks/session-integrity.js +2 -2
  153. package/dist/src/cli/commands/doctor/checks/state-integrity.js +1 -1
  154. package/dist/src/cli/commands/doctor/checks/workspace-status.js +1 -1
  155. package/dist/src/cli/commands/extension-dev.js +1 -1
  156. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  157. package/dist/src/cli/commands/extension-pack.js +1 -1
  158. package/dist/src/cli/commands/gateway/logs.js +1 -1
  159. package/dist/src/cli/commands/image.js +1 -1
  160. package/dist/src/cli/commands/init.js +4 -4
  161. package/dist/src/cli/commands/logs.js +3 -3
  162. package/dist/src/cli/commands/logs.js.map +1 -1
  163. package/dist/src/cli/commands/onboard.js +1 -1
  164. package/dist/src/cli/utils/init-workspace-core.js +2 -2
  165. package/dist/src/commands/agents.config.js +1 -1
  166. package/dist/src/config/agent-profile.js +1 -1
  167. package/dist/src/config/gateway-bind.js +1 -1
  168. package/dist/src/config/index.js +5 -5
  169. package/dist/src/config/loader.js +2 -2
  170. package/dist/src/config/models-json.js +2 -2
  171. package/dist/src/config/paths-state.js +1 -1
  172. package/dist/src/config/profile.js +2 -2
  173. package/dist/src/config/workspace-path.js +1 -1
  174. package/dist/src/cron/executor.js +9 -6
  175. package/dist/src/cron/executor.js.map +1 -1
  176. package/dist/src/cron/persistence.js +1 -1
  177. package/dist/src/cron/run-log-store.js +1 -1
  178. package/dist/src/daemon/constants.js +1 -1
  179. package/dist/src/daemon/install-plan.js +2 -2
  180. package/dist/src/daemon/launchd.js +2 -2
  181. package/dist/src/daemon/schtasks.js +2 -2
  182. package/dist/src/daemon/systemd.js +2 -2
  183. package/dist/src/extensions/bundle-mcp.js +1 -1
  184. package/dist/src/extensions/discover-extensions.js +1 -1
  185. package/dist/src/extensions/health.js +1 -1
  186. package/dist/src/extensions/loader.js +1 -1
  187. package/dist/src/extensions/lockfile.js +2 -2
  188. package/dist/src/extensions/update.js +1 -1
  189. package/dist/src/gateway/agents-admin.js +3 -3
  190. package/dist/src/gateway/file-path-classifier.js +2 -2
  191. package/dist/src/gateway/hono/app.js +4 -1
  192. package/dist/src/gateway/hono/app.js.map +1 -1
  193. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  194. package/dist/src/gateway/hono/lib/extension-store.js +2 -2
  195. package/dist/src/gateway/hono/lib/route-logger.d.ts +6 -0
  196. package/dist/src/gateway/hono/lib/route-logger.js +31 -0
  197. package/dist/src/gateway/hono/lib/route-logger.js.map +1 -0
  198. package/dist/src/gateway/hono/lib/static-ui.js +2 -2
  199. package/dist/src/gateway/hono/middleware/auth.js +16 -3
  200. package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
  201. package/dist/src/gateway/hono/middleware/logger.js +1 -1
  202. package/dist/src/gateway/hono/middleware/logger.js.map +1 -1
  203. package/dist/src/gateway/hono/middleware/route-errors.d.ts +5 -0
  204. package/dist/src/gateway/hono/middleware/route-errors.js +27 -0
  205. package/dist/src/gateway/hono/middleware/route-errors.js.map +1 -0
  206. package/dist/src/gateway/hono/oauth.js +1 -1
  207. package/dist/src/gateway/hono/routes/agent-stream.js +6 -0
  208. package/dist/src/gateway/hono/routes/agent-stream.js.map +1 -1
  209. package/dist/src/gateway/hono/routes/agents.js +1 -1
  210. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  211. package/dist/src/gateway/hono/routes/browser-install.js +2 -4
  212. package/dist/src/gateway/hono/routes/browser-install.js.map +1 -1
  213. package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
  214. package/dist/src/gateway/hono/routes/config.js +25 -11
  215. package/dist/src/gateway/hono/routes/config.js.map +1 -1
  216. package/dist/src/gateway/hono/routes/cron.js +5 -0
  217. package/dist/src/gateway/hono/routes/cron.js.map +1 -1
  218. package/dist/src/gateway/hono/routes/dreaming.js +1 -1
  219. package/dist/src/gateway/hono/routes/host-fs.js +4 -6
  220. package/dist/src/gateway/hono/routes/host-fs.js.map +1 -1
  221. package/dist/src/gateway/hono/routes/lazy-bundles.js +14 -1
  222. package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
  223. package/dist/src/gateway/hono/routes/lazy-fallback.js +3 -0
  224. package/dist/src/gateway/hono/routes/lazy-fallback.js.map +1 -1
  225. package/dist/src/gateway/hono/routes/logs.js +39 -7
  226. package/dist/src/gateway/hono/routes/logs.js.map +1 -1
  227. package/dist/src/gateway/hono/routes/mcp.d.ts +3 -0
  228. package/dist/src/gateway/hono/routes/mcp.js +107 -0
  229. package/dist/src/gateway/hono/routes/mcp.js.map +1 -0
  230. package/dist/src/gateway/hono/routes/models.js +1 -1
  231. package/dist/src/gateway/hono/routes/sessions.js +6 -0
  232. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  233. package/dist/src/gateway/hono/routes/shares.js +1 -1
  234. package/dist/src/gateway/hono/routes/update.js +2 -4
  235. package/dist/src/gateway/hono/routes/update.js.map +1 -1
  236. package/dist/src/gateway/hono/routes/voice.js +2 -4
  237. package/dist/src/gateway/hono/routes/voice.js.map +1 -1
  238. package/dist/src/gateway/hono/routes/workspace.js +4 -6
  239. package/dist/src/gateway/hono/routes/workspace.js.map +1 -1
  240. package/dist/src/gateway/hono/sse.js +9 -2
  241. package/dist/src/gateway/hono/sse.js.map +1 -1
  242. package/dist/src/gateway/lock.js +3 -3
  243. package/dist/src/gateway/ports.js +1 -1
  244. package/dist/src/gateway/service/agent-runner.js +3 -3
  245. package/dist/src/gateway/service/agent-runner.js.map +1 -1
  246. package/dist/src/gateway/service/config-coordinator.js +14 -6
  247. package/dist/src/gateway/service/config-coordinator.js.map +1 -1
  248. package/dist/src/gateway/service/marketplace-service.js +3 -3
  249. package/dist/src/gateway/service/marketplace-service.js.map +1 -1
  250. package/dist/src/gateway/service/run-gateway-agent.js +22 -5
  251. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  252. package/dist/src/gateway/service/sse-hub.js +1 -1
  253. package/dist/src/gateway/service/sse-hub.js.map +1 -1
  254. package/dist/src/gateway/service.js +13 -6
  255. package/dist/src/gateway/service.js.map +1 -1
  256. package/dist/src/gateway/workspace-fs-file-list.js +1 -1
  257. package/dist/src/infra/brew.js +1 -1
  258. package/dist/src/infra/package-json.js +1 -1
  259. package/dist/src/infra/package-update-steps.js +1 -1
  260. package/dist/src/infra/path-env.js +2 -2
  261. package/dist/src/infra/restart.js +2 -2
  262. package/dist/src/infra/stable-node-path.js +1 -1
  263. package/dist/src/infra/update-check.js +1 -1
  264. package/dist/src/infra/update-global.js +1 -1
  265. package/dist/src/infra/update-lock.js +3 -3
  266. package/dist/src/infra/update-runner.js +1 -1
  267. package/dist/src/infra/update-startup.js +2 -2
  268. package/dist/src/infra/write-file-atomic.js +2 -2
  269. package/dist/src/mcp/channel-bridge.js +26 -2
  270. package/dist/src/mcp/channel-bridge.js.map +1 -1
  271. package/dist/src/mcp/gateway-http-client.js +24 -2
  272. package/dist/src/mcp/gateway-http-client.js.map +1 -1
  273. package/dist/src/notes/store.js +2 -2
  274. package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
  275. package/dist/src/providers/index.js +2 -2
  276. package/dist/src/providers/model-registry.js +1 -1
  277. package/dist/src/session/config-store.js +12 -6
  278. package/dist/src/session/config-store.js.map +1 -1
  279. package/dist/src/session/index.d.ts +1 -1
  280. package/dist/src/session/index.js +2 -2
  281. package/dist/src/session/init-session-turn.js +2 -2
  282. package/dist/src/session/manager.js +8 -1
  283. package/dist/src/session/manager.js.map +1 -1
  284. package/dist/src/session/parity/jsonl-transcript-io.js +2 -2
  285. package/dist/src/session/parity/sessions-json-file.js +1 -1
  286. package/dist/src/session/parity/transcript-file-lock.js +2 -2
  287. package/dist/src/session/parity/transcript-paths.js +1 -1
  288. package/dist/src/session/resolve-session.js +4 -4
  289. package/dist/src/session/search-index-cache.js +1 -1
  290. package/dist/src/session/search-index.js +1 -1
  291. package/dist/src/session/session-title.d.ts +19 -3
  292. package/dist/src/session/session-title.js +84 -9
  293. package/dist/src/session/session-title.js.map +1 -1
  294. package/dist/src/session/store.js +6 -6
  295. package/dist/src/share/share-auto.js +2 -2
  296. package/dist/src/share/share-store.js +3 -3
  297. package/dist/src/share/share-thumbnail.js +2 -2
  298. package/dist/src/share/share-zip.js +1 -1
  299. package/dist/src/share/site-share-store.js +3 -3
  300. package/dist/src/share/site-static-serve.js +1 -1
  301. package/dist/src/tui/clipboard-image.js +3 -3
  302. package/dist/src/tui/theme-manager.js +1 -1
  303. package/dist/src/tui/tui-keybindings-file.js +1 -1
  304. package/dist/src/tui/tui-scoped-models.js +2 -2
  305. package/dist/src/tui/tui-settings.js +1 -1
  306. package/dist/src/tui/tui.js +3 -3
  307. package/dist/src/tunnel/frpc-binary.js +3 -3
  308. package/dist/src/tunnel/frpc-config.js +1 -1
  309. package/dist/src/tunnel/frpc-extract.js +1 -1
  310. package/dist/src/tunnel/tunnel-state.js +1 -1
  311. package/dist/src/utils/index.js +4 -4
  312. package/dist/src/utils/logger/audit.js +1 -1
  313. package/dist/src/utils/logger/config.js +2 -6
  314. package/dist/src/utils/logger/config.js.map +1 -1
  315. package/dist/src/utils/logger/context.d.ts +3 -22
  316. package/dist/src/utils/logger/context.js +4 -32
  317. package/dist/src/utils/logger/context.js.map +1 -1
  318. package/dist/src/utils/logger/index.d.ts +4 -7
  319. package/dist/src/utils/logger/index.js +9 -28
  320. package/dist/src/utils/logger/index.js.map +1 -1
  321. package/dist/src/utils/logger/log-store.d.ts +14 -32
  322. package/dist/src/utils/logger/log-store.js +68 -119
  323. package/dist/src/utils/logger/log-store.js.map +1 -1
  324. package/dist/src/utils/logger/log-stream.d.ts +5 -70
  325. package/dist/src/utils/logger/log-stream.js +67 -178
  326. package/dist/src/utils/logger/log-stream.js.map +1 -1
  327. package/dist/src/utils/logger/pino-record.d.ts +8 -0
  328. package/dist/src/utils/logger/pino-record.js +83 -0
  329. package/dist/src/utils/logger/pino-record.js.map +1 -0
  330. package/dist/src/utils/logger/rotation.js +1 -1
  331. package/dist/src/utils/logger/stats.d.ts +1 -1
  332. package/dist/src/utils/logger/stats.js +2 -2
  333. package/dist/src/utils/logger/stats.js.map +1 -1
  334. package/dist/src/utils/logger/streams.js +18 -0
  335. package/dist/src/utils/logger/streams.js.map +1 -1
  336. package/dist/src/utils/logger/types.d.ts +0 -9
  337. package/dist/src/utils/logger/types.js.map +1 -1
  338. package/dist/src/utils/logger.js +4 -4
  339. package/dist/src/voice/tts/audio.js +1 -1
  340. package/dist/src/voice/tts/providers/edge-speech.js +2 -2
  341. package/dist/src/workflows/store/event-store.js +1 -1
  342. package/dist/src/workflows/store/run-store.js +1 -1
  343. package/package.json +3 -2
  344. package/dist/gateway/static/root/assets/agents-C7tTJLP9.js +0 -222
  345. package/dist/gateway/static/root/assets/apps-page-BbzdMyrg.js +0 -1
  346. package/dist/gateway/static/root/assets/channels-settings-B49vG2hE.js +0 -1
  347. package/dist/gateway/static/root/assets/cron-page-Bjx7IOdR.js +0 -1
  348. package/dist/gateway/static/root/assets/index-CwDdudZM.css +0 -1
  349. package/dist/gateway/static/root/assets/logs-page-BxukQ-J-.js +0 -1
  350. package/dist/gateway/static/root/assets/notes-page-BYPVYcYn.js +0 -1
  351. package/dist/gateway/static/root/assets/sessions-page-BFD2_-Cl.js +0 -1
  352. package/dist/gateway/static/root/assets/settings-page-BiP5iH46.js +0 -2
  353. package/dist/gateway/static/root/assets/share-preview-page-tnIfJ4K6.js +0 -2
  354. package/dist/gateway/static/root/assets/skills-page-CNDctFIn.js +0 -2
  355. package/dist/gateway/static/root/assets/voice-api-key-field-CalxUkxm.js +0 -1
  356. package/dist/gateway/static/root/assets/workflows-page-D2fRxXJG.js +0 -27
@@ -1 +1 @@
1
- {"version":3,"file":"browser-use-tool.js","names":[],"sources":["../../../../../../src/agent/tools/browser/tool/browser-use-tool.ts"],"sourcesContent":["/**\n * browser_use — unified browser AgentTool.\n *\n * Modes: command | pipeline | inspect | close.\n * Replaces the 14 fine-grained browser_* tools with a single entry point.\n */\n\nimport type { AgentTool } from '@earendil-works/pi-agent-core';\n\nimport { createLogger } from '../../../../utils/logger.js';\nimport type { Config } from '../../../../config/schema.js';\nimport type { BrowserManager } from '../../../../browser/manager.js';\nimport type { CdpSupervisor } from '../../../../browser/cdp-supervisor.js';\nimport type { Page } from 'playwright-core';\n\nimport { BrowserUseSchema } from './schemas.js';\nimport { createBrowserActionRegistry } from '../../../../browser/actions/registry.js';\nimport type { BrowserActionContext, BrowserActionResult } from '../../../../browser/actions/types.js';\nimport { loadBrowserPipelineSource } from '../../../../browser/pipeline/source.js';\nimport type { BrowserNotReadyError, BrowserSetupHint } from '../../../../browser/readiness.js';\n\nconst log = createLogger('browser_use');\n\nexport interface CreateBrowserUseToolDeps {\n getManager: () => BrowserManager;\n getPageForTask: () => Promise<Page>;\n getTaskId: () => string;\n getConfig: () => Config | undefined;\n getSupervisor?: () => CdpSupervisor | undefined;\n notifyBrowserPageClosed?: (taskId: string) => void;\n /**\n * Preflight readiness check. Returns `null` when the configured backend is\n * usable, otherwise a structured hint the chat surface renders as a setup\n * card. Cache the result upstream (~30s) so back-to-back calls don't reprobe.\n */\n getReadiness?: () => Promise<BrowserNotReadyError | null>;\n /** Pipeline runner (injected to avoid circular deps; lazy-loaded if not provided). */\n runPipeline?: (yaml: string, args: Record<string, unknown>, ctx: BrowserActionContext, dryRun: boolean) => Promise<BrowserActionResult>;\n}\n\nexport function createBrowserUseTool(deps: CreateBrowserUseToolDeps): AgentTool<any, any> {\n const registry = createBrowserActionRegistry();\n\n function buildContext(page: Page, signal?: AbortSignal): BrowserActionContext {\n return {\n page,\n manager: deps.getManager(),\n config: deps.getConfig(),\n taskId: deps.getTaskId(),\n supervisor: deps.getSupervisor?.(),\n signal,\n };\n }\n\n /**\n * Render a {@link BrowserSetupHint} as a tool result. The text body is a\n * JSON envelope (`kind: 'browser_setup_required'`) so the chat surface can\n * detect it and render a Setup card; the embedded `message` keeps the\n * payload human-readable for the LLM so it stops the browser attempt and\n * tells the user to finish setup.\n */\n function formatNotReady(hint: BrowserSetupHint): { content: { type: 'text'; text: string }[]; details: Record<string, unknown> } {\n const message =\n `⚠ Browser is not ready (backend=${hint.backend}, reason=${hint.reason}). ` +\n `Do NOT retry. Tell the user to open Settings → Browser to finish setup, ` +\n `then ask them to confirm before running any browser action again.`;\n const payload = {\n kind: 'browser_setup_required' as const,\n backend: hint.backend,\n reason: hint.reason,\n deepLink: hint.deepLink,\n detail: hint.detail,\n message,\n };\n return {\n content: [{ type: 'text', text: JSON.stringify(payload) }],\n details: {\n ok: false,\n kind: 'browser_setup_required',\n backend: hint.backend,\n reason: hint.reason,\n deepLink: hint.deepLink,\n },\n };\n }\n\n function formatResult(result: BrowserActionResult): { content: { type: 'text'; text: string }[]; details: Record<string, unknown> } {\n if (result.ok) {\n const parts: string[] = [];\n if (result.text) parts.push(result.text);\n if (result.data && !result.text) parts.push(JSON.stringify(result.data, null, 2));\n return {\n content: [{ type: 'text', text: parts.join('\\n') || 'OK' }],\n details: { ok: true, action: result.action, ...(result.artifacts?.length ? { artifacts: result.artifacts.length } : {}) },\n };\n }\n const parts: string[] = [];\n if (result.error) parts.push(`[${result.error.code}] ${result.error.message}`);\n if (result.diagnostics?.url) parts.push(`URL: ${result.diagnostics.url}`);\n if (result.diagnostics?.snapshot) parts.push(`Snapshot: ${result.diagnostics.snapshot.slice(0, 2000)}`);\n return {\n content: [{ type: 'text', text: parts.join('\\n') || 'Failed' }],\n details: { ok: false, action: result.action, error: result.error },\n };\n }\n\n const tool: any = {\n name: 'browser_use',\n label: '🌐 Browser',\n description:\n 'Use a persistent browser for web navigation, page inspection, interaction, screenshots, network capture, and scripted browser pipelines. For non-trivial browser tasks, load the \"browser\" skill first with skill_view.',\n parameters: BrowserUseSchema,\n\n async execute(_toolCallId, params: any, signal, _onUpdate) {\n const { mode, command, args: cmdArgs, pipeline: pipelineParams, options: _options } = params as {\n mode: string;\n command?: string;\n args?: Record<string, unknown>;\n pipeline?: { yaml?: string; script?: string; path?: string; args?: Record<string, unknown>; dryRun?: boolean };\n options?: { timeout?: number; headless?: boolean };\n };\n\n // ─── readiness preflight ────────────────────────────────────────────\n // Probes the configured backend (doctor + WS bridge / CDP / cloud key)\n // before any launch attempt. On failure we short-circuit with a setup\n // hint the chat renders as a card — the agent should stop and ask the\n // user to finish setup instead of looping on launch errors.\n if (deps.getReadiness) {\n try {\n const notReady = await deps.getReadiness();\n if (notReady) {\n return formatNotReady(notReady.hint);\n }\n } catch (e) {\n log.warn({ err: e }, 'Readiness preflight threw; continuing with launch attempt');\n }\n }\n\n // ─── inspect ────────────────────────────────────────────────────────\n if (mode === 'inspect') {\n const mgr = deps.getManager();\n await mgr.ensureConnected();\n const ext = mgr.getExtensionProvider();\n if (ext) {\n const timeout = deps.getConfig?.()?.agents?.defaults?.browser?.commandTimeout;\n const timeoutMs =\n typeof timeout === 'number' && Number.isFinite(timeout) && timeout > 0\n ? Math.floor(timeout * 1000)\n : 30_000;\n const urlRes = await ext.sendCommand('get_url', {}, { timeout: timeoutMs });\n const titleRes = await ext.sendCommand('get_title', {}, { timeout: timeoutMs });\n const snapRes = await ext.sendCommand('snapshot', {}, { timeout: timeoutMs });\n const url = urlRes.ok && urlRes.data ? String((urlRes.data as { url?: string }).url ?? '') : '';\n const title =\n titleRes.ok && titleRes.data ? String((titleRes.data as { title?: string }).title ?? '') : '';\n let snapText = '';\n if (snapRes.ok && snapRes.data) {\n const nodes = (snapRes.data as { nodes?: Array<{ role?: string; name?: string }> }).nodes ?? [];\n snapText = nodes.map((n) => `${n.role ?? '?'}: ${n.name ?? ''}`).join('\\n');\n if (snapText.length > 8000) snapText = `${snapText.slice(0, 8000)}\\n... (truncated)`;\n }\n const text = `URL: ${url}\\nTitle: ${title}\\n${snapText ? `--- Page Snapshot ---\\n${snapText}` : ''}`;\n return {\n content: [{ type: 'text', text }],\n details: { ok: true, mode: 'inspect', url, title },\n };\n }\n const page = await deps.getPageForTask();\n const ctx = buildContext(page, signal);\n const result = await registry.execute('state', ctx, {});\n // Augment with URL / title\n const url = page.url();\n const title = await page.title().catch(() => '');\n const text = `URL: ${url}\\nTitle: ${title}\\n${result.text ?? ''}`;\n return {\n content: [{ type: 'text', text }],\n details: { ok: true, mode: 'inspect', url, title },\n };\n }\n\n // ─── close ──────────────────────────────────────────────────────────\n if (mode === 'close') {\n const taskId = deps.getTaskId();\n await deps.getManager().ensureConnected();\n await deps.getManager().closePage(taskId);\n deps.notifyBrowserPageClosed?.(taskId);\n return {\n content: [{ type: 'text', text: 'Browser page closed.' }],\n details: { ok: true, mode: 'close' },\n };\n }\n\n // ─── command ────────────────────────────────────────────────────────\n if (mode === 'command') {\n if (!command) {\n return {\n content: [{ type: 'text', text: 'Missing `command` parameter for command mode.' }],\n details: { ok: false, mode: 'command' },\n };\n }\n const page = await deps.getPageForTask();\n const ctx = buildContext(page, signal);\n const args = cmdArgs ?? {};\n const result = await registry.execute(command, ctx, args);\n return formatResult(result);\n }\n\n // ─── pipeline ──────────────────────────────────────────────────────\n if (mode === 'pipeline') {\n if (!pipelineParams) {\n return {\n content: [{ type: 'text', text: 'Missing `pipeline` parameter for pipeline mode.' }],\n details: { ok: false, mode: 'pipeline' },\n };\n }\n\n // Resolve YAML source\n let yamlSource = pipelineParams.yaml ?? pipelineParams.script ?? '';\n\n if (!yamlSource && pipelineParams.path) {\n try {\n yamlSource = (await loadBrowserPipelineSource(pipelineParams.path)).source;\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n return {\n content: [{ type: 'text', text: `Failed to read pipeline source: ${msg}` }],\n details: { ok: false, mode: 'pipeline', error: msg },\n };\n }\n }\n\n if (!yamlSource) {\n return {\n content: [{ type: 'text', text: 'Pipeline mode requires `yaml`, `script`, or `path`.' }],\n details: { ok: false, mode: 'pipeline' },\n };\n }\n\n const pipelineArgs = (pipelineParams.args as Record<string, unknown>) ?? {};\n const dryRun = pipelineParams.dryRun === true;\n const page = await deps.getPageForTask();\n const ctx = buildContext(page, signal);\n\n // Use injected runner or lazy-load\n if (deps.runPipeline) {\n const result = await deps.runPipeline(yamlSource, pipelineArgs, ctx, dryRun);\n return formatResult(result);\n }\n\n // Lazy import pipeline runner\n try {\n const { runBrowserPipeline } = await import('../../../../browser/pipeline/runner.js');\n const result = await runBrowserPipeline(yamlSource, pipelineArgs, ctx, registry, dryRun);\n return formatResult(result);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n log.error({ err: e }, `Pipeline execution failed: ${msg}`);\n return {\n content: [{ type: 'text', text: `Pipeline failed: ${msg}` }],\n details: { ok: false, mode: 'pipeline', error: msg },\n };\n }\n }\n\n return {\n content: [{ type: 'text', text: `Unknown mode: ${mode}` }],\n details: { ok: false },\n };\n },\n };\n return tool;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"browser-use-tool.js","names":[],"sources":["../../../../../../src/agent/tools/browser/tool/browser-use-tool.ts"],"sourcesContent":["/**\n * browser_use — unified browser AgentTool.\n *\n * Modes: command | pipeline | inspect | close.\n * Replaces the 14 fine-grained browser_* tools with a single entry point.\n */\n\nimport type { AgentTool } from '@earendil-works/pi-agent-core';\n\nimport { createLogger } from '../../../../utils/logger.js';\nimport type { Config } from '../../../../config/schema.js';\nimport type { BrowserManager } from '../../../../browser/manager.js';\nimport type { CdpSupervisor } from '../../../../browser/cdp-supervisor.js';\nimport type { Page } from 'playwright-core';\n\nimport { BrowserUseSchema } from './schemas.js';\nimport { createBrowserActionRegistry } from '../../../../browser/actions/registry.js';\nimport type { BrowserActionContext, BrowserActionResult } from '../../../../browser/actions/types.js';\nimport { loadBrowserPipelineSource } from '../../../../browser/pipeline/source.js';\nimport type { BrowserNotReadyError, BrowserSetupHint } from '../../../../browser/readiness.js';\n\nconst log = createLogger('Agent:BrowserUse');\n\nexport interface CreateBrowserUseToolDeps {\n getManager: () => BrowserManager;\n getPageForTask: () => Promise<Page>;\n getTaskId: () => string;\n getConfig: () => Config | undefined;\n getSupervisor?: () => CdpSupervisor | undefined;\n notifyBrowserPageClosed?: (taskId: string) => void;\n /**\n * Preflight readiness check. Returns `null` when the configured backend is\n * usable, otherwise a structured hint the chat surface renders as a setup\n * card. Cache the result upstream (~30s) so back-to-back calls don't reprobe.\n */\n getReadiness?: () => Promise<BrowserNotReadyError | null>;\n /** Pipeline runner (injected to avoid circular deps; lazy-loaded if not provided). */\n runPipeline?: (yaml: string, args: Record<string, unknown>, ctx: BrowserActionContext, dryRun: boolean) => Promise<BrowserActionResult>;\n}\n\nexport function createBrowserUseTool(deps: CreateBrowserUseToolDeps): AgentTool<any, any> {\n const registry = createBrowserActionRegistry();\n\n function buildContext(page: Page, signal?: AbortSignal): BrowserActionContext {\n return {\n page,\n manager: deps.getManager(),\n config: deps.getConfig(),\n taskId: deps.getTaskId(),\n supervisor: deps.getSupervisor?.(),\n signal,\n };\n }\n\n /**\n * Render a {@link BrowserSetupHint} as a tool result. The text body is a\n * JSON envelope (`kind: 'browser_setup_required'`) so the chat surface can\n * detect it and render a Setup card; the embedded `message` keeps the\n * payload human-readable for the LLM so it stops the browser attempt and\n * tells the user to finish setup.\n */\n function formatNotReady(hint: BrowserSetupHint): { content: { type: 'text'; text: string }[]; details: Record<string, unknown> } {\n const message =\n `⚠ Browser is not ready (backend=${hint.backend}, reason=${hint.reason}). ` +\n `Do NOT retry. Tell the user to open Settings → Browser to finish setup, ` +\n `then ask them to confirm before running any browser action again.`;\n const payload = {\n kind: 'browser_setup_required' as const,\n backend: hint.backend,\n reason: hint.reason,\n deepLink: hint.deepLink,\n detail: hint.detail,\n message,\n };\n return {\n content: [{ type: 'text', text: JSON.stringify(payload) }],\n details: {\n ok: false,\n kind: 'browser_setup_required',\n backend: hint.backend,\n reason: hint.reason,\n deepLink: hint.deepLink,\n },\n };\n }\n\n function formatResult(result: BrowserActionResult): { content: { type: 'text'; text: string }[]; details: Record<string, unknown> } {\n if (result.ok) {\n const parts: string[] = [];\n if (result.text) parts.push(result.text);\n if (result.data && !result.text) parts.push(JSON.stringify(result.data, null, 2));\n return {\n content: [{ type: 'text', text: parts.join('\\n') || 'OK' }],\n details: { ok: true, action: result.action, ...(result.artifacts?.length ? { artifacts: result.artifacts.length } : {}) },\n };\n }\n const parts: string[] = [];\n if (result.error) parts.push(`[${result.error.code}] ${result.error.message}`);\n if (result.diagnostics?.url) parts.push(`URL: ${result.diagnostics.url}`);\n if (result.diagnostics?.snapshot) parts.push(`Snapshot: ${result.diagnostics.snapshot.slice(0, 2000)}`);\n return {\n content: [{ type: 'text', text: parts.join('\\n') || 'Failed' }],\n details: { ok: false, action: result.action, error: result.error },\n };\n }\n\n const tool: any = {\n name: 'browser_use',\n label: '🌐 Browser',\n description:\n 'Use a persistent browser for web navigation, page inspection, interaction, screenshots, network capture, and scripted browser pipelines. For non-trivial browser tasks, load the \"browser\" skill first with skill_view.',\n parameters: BrowserUseSchema,\n\n async execute(_toolCallId, params: any, signal, _onUpdate) {\n const { mode, command, args: cmdArgs, pipeline: pipelineParams, options: _options } = params as {\n mode: string;\n command?: string;\n args?: Record<string, unknown>;\n pipeline?: { yaml?: string; script?: string; path?: string; args?: Record<string, unknown>; dryRun?: boolean };\n options?: { timeout?: number; headless?: boolean };\n };\n\n // ─── readiness preflight ────────────────────────────────────────────\n // Probes the configured backend (doctor + WS bridge / CDP / cloud key)\n // before any launch attempt. On failure we short-circuit with a setup\n // hint the chat renders as a card — the agent should stop and ask the\n // user to finish setup instead of looping on launch errors.\n if (deps.getReadiness) {\n try {\n const notReady = await deps.getReadiness();\n if (notReady) {\n return formatNotReady(notReady.hint);\n }\n } catch (e) {\n log.warn({ err: e }, 'Readiness preflight threw; continuing with launch attempt');\n }\n }\n\n // ─── inspect ────────────────────────────────────────────────────────\n if (mode === 'inspect') {\n const mgr = deps.getManager();\n await mgr.ensureConnected();\n const ext = mgr.getExtensionProvider();\n if (ext) {\n const timeout = deps.getConfig?.()?.agents?.defaults?.browser?.commandTimeout;\n const timeoutMs =\n typeof timeout === 'number' && Number.isFinite(timeout) && timeout > 0\n ? Math.floor(timeout * 1000)\n : 30_000;\n const urlRes = await ext.sendCommand('get_url', {}, { timeout: timeoutMs });\n const titleRes = await ext.sendCommand('get_title', {}, { timeout: timeoutMs });\n const snapRes = await ext.sendCommand('snapshot', {}, { timeout: timeoutMs });\n const url = urlRes.ok && urlRes.data ? String((urlRes.data as { url?: string }).url ?? '') : '';\n const title =\n titleRes.ok && titleRes.data ? String((titleRes.data as { title?: string }).title ?? '') : '';\n let snapText = '';\n if (snapRes.ok && snapRes.data) {\n const nodes = (snapRes.data as { nodes?: Array<{ role?: string; name?: string }> }).nodes ?? [];\n snapText = nodes.map((n) => `${n.role ?? '?'}: ${n.name ?? ''}`).join('\\n');\n if (snapText.length > 8000) snapText = `${snapText.slice(0, 8000)}\\n... (truncated)`;\n }\n const text = `URL: ${url}\\nTitle: ${title}\\n${snapText ? `--- Page Snapshot ---\\n${snapText}` : ''}`;\n return {\n content: [{ type: 'text', text }],\n details: { ok: true, mode: 'inspect', url, title },\n };\n }\n const page = await deps.getPageForTask();\n const ctx = buildContext(page, signal);\n const result = await registry.execute('state', ctx, {});\n // Augment with URL / title\n const url = page.url();\n const title = await page.title().catch(() => '');\n const text = `URL: ${url}\\nTitle: ${title}\\n${result.text ?? ''}`;\n return {\n content: [{ type: 'text', text }],\n details: { ok: true, mode: 'inspect', url, title },\n };\n }\n\n // ─── close ──────────────────────────────────────────────────────────\n if (mode === 'close') {\n const taskId = deps.getTaskId();\n await deps.getManager().ensureConnected();\n await deps.getManager().closePage(taskId);\n deps.notifyBrowserPageClosed?.(taskId);\n return {\n content: [{ type: 'text', text: 'Browser page closed.' }],\n details: { ok: true, mode: 'close' },\n };\n }\n\n // ─── command ────────────────────────────────────────────────────────\n if (mode === 'command') {\n if (!command) {\n return {\n content: [{ type: 'text', text: 'Missing `command` parameter for command mode.' }],\n details: { ok: false, mode: 'command' },\n };\n }\n const page = await deps.getPageForTask();\n const ctx = buildContext(page, signal);\n const args = cmdArgs ?? {};\n const result = await registry.execute(command, ctx, args);\n return formatResult(result);\n }\n\n // ─── pipeline ──────────────────────────────────────────────────────\n if (mode === 'pipeline') {\n if (!pipelineParams) {\n return {\n content: [{ type: 'text', text: 'Missing `pipeline` parameter for pipeline mode.' }],\n details: { ok: false, mode: 'pipeline' },\n };\n }\n\n // Resolve YAML source\n let yamlSource = pipelineParams.yaml ?? pipelineParams.script ?? '';\n\n if (!yamlSource && pipelineParams.path) {\n try {\n yamlSource = (await loadBrowserPipelineSource(pipelineParams.path)).source;\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n return {\n content: [{ type: 'text', text: `Failed to read pipeline source: ${msg}` }],\n details: { ok: false, mode: 'pipeline', error: msg },\n };\n }\n }\n\n if (!yamlSource) {\n return {\n content: [{ type: 'text', text: 'Pipeline mode requires `yaml`, `script`, or `path`.' }],\n details: { ok: false, mode: 'pipeline' },\n };\n }\n\n const pipelineArgs = (pipelineParams.args as Record<string, unknown>) ?? {};\n const dryRun = pipelineParams.dryRun === true;\n const page = await deps.getPageForTask();\n const ctx = buildContext(page, signal);\n\n // Use injected runner or lazy-load\n if (deps.runPipeline) {\n const result = await deps.runPipeline(yamlSource, pipelineArgs, ctx, dryRun);\n return formatResult(result);\n }\n\n // Lazy import pipeline runner\n try {\n const { runBrowserPipeline } = await import('../../../../browser/pipeline/runner.js');\n const result = await runBrowserPipeline(yamlSource, pipelineArgs, ctx, registry, dryRun);\n return formatResult(result);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n log.error({ err: e }, `Pipeline execution failed: ${msg}`);\n return {\n content: [{ type: 'text', text: `Pipeline failed: ${msg}` }],\n details: { ok: false, mode: 'pipeline', error: msg },\n };\n }\n }\n\n return {\n content: [{ type: 'text', text: `Unknown mode: ${mode}` }],\n details: { ok: false },\n };\n },\n };\n return tool;\n}\n"],"mappings":"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"}
@@ -3,8 +3,8 @@ import { init_logger } from "../../utils/logger.js";
3
3
  import { SHORT_TERM_PROMOTION_LOCK_RELATIVE, SHORT_TERM_RECALL_STORE_RELATIVE } from "../memory/dreaming/constants.js";
4
4
  import { loadDreamingStore, saveDreamingStore } from "../memory/dreaming/short-term-store.js";
5
5
  import { resolveDreamingConfig } from "../memory/dreaming/config.js";
6
- import fs from "node:fs/promises";
7
6
  import path from "node:path";
7
+ import fs from "node:fs/promises";
8
8
  import { Type } from "@sinclair/typebox";
9
9
  //#region src/agent/tools/dreaming-tool.ts
10
10
  init_logger();
@@ -1,6 +1,6 @@
1
+ import { init_session_key, parseSessionKey } from "../../routing/session-key.js";
1
2
  import { createLogger } from "../../utils/logger/index.js";
2
3
  import { init_logger } from "../../utils/logger.js";
3
- import { init_session_key, parseSessionKey } from "../../routing/session-key.js";
4
4
  import { createReadFileTool } from "./read.js";
5
5
  import { createWriteFileTool } from "./write.js";
6
6
  import { createEditFileTool } from "./edit.js";
@@ -4,9 +4,9 @@ import { isFailoverError } from "../failover-error.js";
4
4
  import { imageAssetFromDataUrl, imageFileExtensionForMimeType, mimeTypeFromFileName, parseImageDataUrl, sniffImageMimeType } from "../image/generation/image-assets.js";
5
5
  import { getImageGenerationProvider } from "../image/generation/provider-registry.js";
6
6
  import { generateImage, listImageGenerationProvidersSummary } from "../image/generation/runtime.js";
7
+ import path from "node:path";
7
8
  import { randomBytes } from "node:crypto";
8
9
  import { mkdir, readFile, writeFile } from "node:fs/promises";
9
- import path from "node:path";
10
10
  import { Type } from "@sinclair/typebox";
11
11
  //#region src/agent/tools/image-generate-tool.ts
12
12
  const DEFAULT_COUNT = 1;
@@ -8,7 +8,7 @@ import { DuckDuckGoHtmlProvider } from "./providers/duckduckgo-html.js";
8
8
  import { SearXNGProvider } from "./providers/searxng.js";
9
9
  //#region src/agent/tools/search/registry.ts
10
10
  init_logger();
11
- const log = createLogger("web-search");
11
+ const log = createLogger("Agent:WebSearch");
12
12
  var SearchProviderRegistry = class {
13
13
  providers;
14
14
  fallbackProvider;
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","names":[],"sources":["../../../../../src/agent/tools/search/registry.ts"],"sourcesContent":["import { createLogger } from '../../../utils/logger.js';\nimport type { ResolvedWebSearchConfig, SearchProvider, SearchResult } from './types.js';\nimport { BraveProvider } from './providers/brave.js';\nimport { TavilyProvider } from './providers/tavily.js';\nimport { BingApiProvider } from './providers/bing-api.js';\nimport { BingHtmlProvider } from './providers/bing-html.js';\nimport { DuckDuckGoHtmlProvider } from './providers/duckduckgo-html.js';\nimport { SearXNGProvider } from './providers/searxng.js';\n\nconst log = createLogger('web-search');\n\nexport class SearchProviderRegistry {\n private readonly providers: SearchProvider[];\n private readonly fallbackProvider: SearchProvider;\n\n constructor(config: ResolvedWebSearchConfig) {\n this.providers = this.buildProviders(config);\n this.fallbackProvider =\n config.region === 'cn' ? new BingHtmlProvider() : new DuckDuckGoHtmlProvider();\n log.debug(\n { region: config.region, configuredCount: this.providers.filter((p) => p.isAvailable()).length },\n 'Web search registry ready',\n );\n }\n\n private buildProviders(config: ResolvedWebSearchConfig): SearchProvider[] {\n const list: SearchProvider[] = [];\n\n for (const p of config.providers) {\n if (p.disabled) continue;\n switch (p.type) {\n case 'brave':\n list.push(new BraveProvider(p.apiKey ?? ''));\n break;\n case 'tavily':\n list.push(new TavilyProvider(p.apiKey ?? ''));\n break;\n case 'bing':\n list.push(new BingApiProvider(p.apiKey ?? ''));\n break;\n case 'searxng':\n list.push(new SearXNGProvider(p.url ?? ''));\n break;\n default:\n break;\n }\n }\n\n return list;\n }\n\n hasConfiguredApiProvider(): boolean {\n return this.providers.some((p) => p.isAvailable());\n }\n\n async search(\n query: string,\n count: number,\n signal?: AbortSignal,\n ): Promise<{ results: SearchResult[]; provider: string }> {\n for (const provider of this.providers) {\n if (!provider.isAvailable()) continue;\n try {\n const results = await provider.search(query, count, signal);\n return { results, provider: provider.name };\n } catch (err) {\n log.debug({ provider: provider.name, err }, 'Search provider failed, trying next');\n }\n }\n\n try {\n const results = await this.fallbackProvider.search(query, count, signal);\n return { results, provider: this.fallbackProvider.name };\n } catch (err) {\n log.warn({ err }, 'HTML fallback search failed');\n return { results: [], provider: 'none' };\n }\n }\n}\n"],"mappings":";;;;;;;;;aAAwD;AASxD,MAAM,MAAM,aAAa,aAAa;AAEtC,IAAa,yBAAb,MAAoC;CAClC;CACA;CAEA,YAAY,QAAiC;AAC3C,OAAK,YAAY,KAAK,eAAe,OAAO;AAC5C,OAAK,mBACH,OAAO,WAAW,OAAO,IAAI,kBAAkB,GAAG,IAAI,wBAAwB;AAChF,MAAI,MACF;GAAE,QAAQ,OAAO;GAAQ,iBAAiB,KAAK,UAAU,QAAQ,MAAM,EAAE,aAAa,CAAC,CAAC;GAAQ,EAChG,4BACD;;CAGH,eAAuB,QAAmD;EACxE,MAAM,OAAyB,EAAE;AAEjC,OAAK,MAAM,KAAK,OAAO,WAAW;AAChC,OAAI,EAAE,SAAU;AAChB,WAAQ,EAAE,MAAV;IACE,KAAK;AACH,UAAK,KAAK,IAAI,cAAc,EAAE,UAAU,GAAG,CAAC;AAC5C;IACF,KAAK;AACH,UAAK,KAAK,IAAI,eAAe,EAAE,UAAU,GAAG,CAAC;AAC7C;IACF,KAAK;AACH,UAAK,KAAK,IAAI,gBAAgB,EAAE,UAAU,GAAG,CAAC;AAC9C;IACF,KAAK;AACH,UAAK,KAAK,IAAI,gBAAgB,EAAE,OAAO,GAAG,CAAC;AAC3C;IACF,QACE;;;AAIN,SAAO;;CAGT,2BAAoC;AAClC,SAAO,KAAK,UAAU,MAAM,MAAM,EAAE,aAAa,CAAC;;CAGpD,MAAM,OACJ,OACA,OACA,QACwD;AACxD,OAAK,MAAM,YAAY,KAAK,WAAW;AACrC,OAAI,CAAC,SAAS,aAAa,CAAE;AAC7B,OAAI;AAEF,WAAO;KAAE,SAAA,MADa,SAAS,OAAO,OAAO,OAAO,OAAO;KACzC,UAAU,SAAS;KAAM;YACpC,KAAK;AACZ,QAAI,MAAM;KAAE,UAAU,SAAS;KAAM;KAAK,EAAE,sCAAsC;;;AAItF,MAAI;AAEF,UAAO;IAAE,SAAA,MADa,KAAK,iBAAiB,OAAO,OAAO,OAAO,OAAO;IACtD,UAAU,KAAK,iBAAiB;IAAM;WACjD,KAAK;AACZ,OAAI,KAAK,EAAE,KAAK,EAAE,8BAA8B;AAChD,UAAO;IAAE,SAAS,EAAE;IAAE,UAAU;IAAQ"}
1
+ {"version":3,"file":"registry.js","names":[],"sources":["../../../../../src/agent/tools/search/registry.ts"],"sourcesContent":["import { createLogger } from '../../../utils/logger.js';\nimport type { ResolvedWebSearchConfig, SearchProvider, SearchResult } from './types.js';\nimport { BraveProvider } from './providers/brave.js';\nimport { TavilyProvider } from './providers/tavily.js';\nimport { BingApiProvider } from './providers/bing-api.js';\nimport { BingHtmlProvider } from './providers/bing-html.js';\nimport { DuckDuckGoHtmlProvider } from './providers/duckduckgo-html.js';\nimport { SearXNGProvider } from './providers/searxng.js';\n\nconst log = createLogger('Agent:WebSearch');\n\nexport class SearchProviderRegistry {\n private readonly providers: SearchProvider[];\n private readonly fallbackProvider: SearchProvider;\n\n constructor(config: ResolvedWebSearchConfig) {\n this.providers = this.buildProviders(config);\n this.fallbackProvider =\n config.region === 'cn' ? new BingHtmlProvider() : new DuckDuckGoHtmlProvider();\n log.debug(\n { region: config.region, configuredCount: this.providers.filter((p) => p.isAvailable()).length },\n 'Web search registry ready',\n );\n }\n\n private buildProviders(config: ResolvedWebSearchConfig): SearchProvider[] {\n const list: SearchProvider[] = [];\n\n for (const p of config.providers) {\n if (p.disabled) continue;\n switch (p.type) {\n case 'brave':\n list.push(new BraveProvider(p.apiKey ?? ''));\n break;\n case 'tavily':\n list.push(new TavilyProvider(p.apiKey ?? ''));\n break;\n case 'bing':\n list.push(new BingApiProvider(p.apiKey ?? ''));\n break;\n case 'searxng':\n list.push(new SearXNGProvider(p.url ?? ''));\n break;\n default:\n break;\n }\n }\n\n return list;\n }\n\n hasConfiguredApiProvider(): boolean {\n return this.providers.some((p) => p.isAvailable());\n }\n\n async search(\n query: string,\n count: number,\n signal?: AbortSignal,\n ): Promise<{ results: SearchResult[]; provider: string }> {\n for (const provider of this.providers) {\n if (!provider.isAvailable()) continue;\n try {\n const results = await provider.search(query, count, signal);\n return { results, provider: provider.name };\n } catch (err) {\n log.debug({ provider: provider.name, err }, 'Search provider failed, trying next');\n }\n }\n\n try {\n const results = await this.fallbackProvider.search(query, count, signal);\n return { results, provider: this.fallbackProvider.name };\n } catch (err) {\n log.warn({ err }, 'HTML fallback search failed');\n return { results: [], provider: 'none' };\n }\n }\n}\n"],"mappings":";;;;;;;;;aAAwD;AASxD,MAAM,MAAM,aAAa,kBAAkB;AAE3C,IAAa,yBAAb,MAAoC;CAClC;CACA;CAEA,YAAY,QAAiC;AAC3C,OAAK,YAAY,KAAK,eAAe,OAAO;AAC5C,OAAK,mBACH,OAAO,WAAW,OAAO,IAAI,kBAAkB,GAAG,IAAI,wBAAwB;AAChF,MAAI,MACF;GAAE,QAAQ,OAAO;GAAQ,iBAAiB,KAAK,UAAU,QAAQ,MAAM,EAAE,aAAa,CAAC,CAAC;GAAQ,EAChG,4BACD;;CAGH,eAAuB,QAAmD;EACxE,MAAM,OAAyB,EAAE;AAEjC,OAAK,MAAM,KAAK,OAAO,WAAW;AAChC,OAAI,EAAE,SAAU;AAChB,WAAQ,EAAE,MAAV;IACE,KAAK;AACH,UAAK,KAAK,IAAI,cAAc,EAAE,UAAU,GAAG,CAAC;AAC5C;IACF,KAAK;AACH,UAAK,KAAK,IAAI,eAAe,EAAE,UAAU,GAAG,CAAC;AAC7C;IACF,KAAK;AACH,UAAK,KAAK,IAAI,gBAAgB,EAAE,UAAU,GAAG,CAAC;AAC9C;IACF,KAAK;AACH,UAAK,KAAK,IAAI,gBAAgB,EAAE,OAAO,GAAG,CAAC;AAC3C;IACF,QACE;;;AAIN,SAAO;;CAGT,2BAAoC;AAClC,SAAO,KAAK,UAAU,MAAM,MAAM,EAAE,aAAa,CAAC;;CAGpD,MAAM,OACJ,OACA,OACA,QACwD;AACxD,OAAK,MAAM,YAAY,KAAK,WAAW;AACrC,OAAI,CAAC,SAAS,aAAa,CAAE;AAC7B,OAAI;AAEF,WAAO;KAAE,SAAA,MADa,SAAS,OAAO,OAAO,OAAO,OAAO;KACzC,UAAU,SAAS;KAAM;YACpC,KAAK;AACZ,QAAI,MAAM;KAAE,UAAU,SAAS;KAAM;KAAK,EAAE,sCAAsC;;;AAItF,MAAI;AAEF,UAAO;IAAE,SAAA,MADa,KAAK,iBAAiB,OAAO,OAAO,OAAO,OAAO;IACtD,UAAU,KAAK,iBAAiB;IAAM;WACjD,KAAK;AACZ,OAAI,KAAK,EAAE,KAAK,EAAE,8BAA8B;AAChD,UAAO;IAAE,SAAS,EAAE;IAAE,UAAU;IAAQ"}
@@ -1,7 +1,7 @@
1
1
  import { checkFileSafety } from "../prompt/safety.js";
2
2
  import { resolvePathUnderWorkspace } from "./tool-paths.js";
3
- import { readFile } from "fs/promises";
4
3
  import { basename } from "node:path";
4
+ import { readFile } from "fs/promises";
5
5
  import { Type } from "@sinclair/typebox";
6
6
  //#region src/agent/tools/send-media.ts
7
7
  const SendMediaSchema = Type.Object({
@@ -8,7 +8,7 @@ import { Type } from "@sinclair/typebox";
8
8
  //#region src/agent/tools/session-search-tool.ts
9
9
  init_providers();
10
10
  init_logger();
11
- const log = createLogger("session-search-tool");
11
+ const log = createLogger("Agent:SessionSearch");
12
12
  const MAX_SUMMARY_CHARS = 2e4;
13
13
  function resolveSummaryModel(getConfig) {
14
14
  const envRef = process.env.XOPC_SESSION_SEARCH_MODEL?.trim();
@@ -1 +1 @@
1
- {"version":3,"file":"session-search-tool.js","names":[],"sources":["../../../../src/agent/tools/session-search-tool.ts"],"sourcesContent":["// Cross-session transcript search + optional LLM summaries\nimport { Type } from '@sinclair/typebox';\nimport type { AgentMessage, AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../../providers/index.js';\nimport { getOrLoadSessionSearchIndex } from '../../session/search-index-cache.js';\nimport type { SessionStore } from '../../session/store.js';\nimport { readAgentMessageContent } from '../memory/agent-message-access.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('session-search-tool');\n\nconst MAX_SUMMARY_CHARS = 20_000;\n\nexport { invalidateSessionSearchIndexCache } from '../../session/search-index-cache.js';\n\nfunction resolveSummaryModel(getConfig?: () => Config | undefined) {\n const envRef = process.env.XOPC_SESSION_SEARCH_MODEL?.trim();\n const configRef = getConfig?.()?.agents?.defaults?.sessionSearch?.summaryModel?.trim();\n const ref = envRef || configRef;\n if (ref) {\n try {\n return resolveModel(ref);\n } catch (err) {\n log.warn({ err, ref }, 'session_search: summary model resolve failed, using fallback');\n }\n }\n try {\n return resolveModel('openai/gpt-4o-mini');\n } catch {\n const d = getDefaultModelSync(getConfig?.());\n return resolveModel(d);\n }\n}\n\nfunction extractTextFromContent(content: unknown): string {\n if (typeof content === 'string') {\n return content;\n }\n if (!Array.isArray(content)) {\n return '';\n }\n const parts: string[] = [];\n for (const item of content) {\n if (typeof item !== 'object' || item === null || !('type' in item)) {\n continue;\n }\n const c = item as { type?: string; text?: string };\n if (c.type === 'text' && typeof c.text === 'string') {\n parts.push(c.text);\n }\n }\n return parts.join(' ');\n}\n\nfunction formatMessagesForSummary(messages: AgentMessage[]): string {\n const lines: string[] = [];\n let total = 0;\n for (const msg of messages) {\n const role = String(msg.role || 'unknown').toUpperCase();\n const text = extractTextFromContent(readAgentMessageContent(msg));\n const line = `${role}: ${text}`;\n if (total + line.length > MAX_SUMMARY_CHARS) {\n lines.push('… [truncated]');\n break;\n }\n lines.push(line);\n total += line.length + 1;\n }\n return lines.join('\\n\\n');\n}\n\nasync function summarizeSession(\n messages: AgentMessage[],\n query: string,\n getConfig: (() => Config | undefined) | undefined,\n signal: AbortSignal | undefined,\n logMeta?: { summarizingSessionKey: string },\n): Promise<string> {\n if (messages.length === 0) {\n return 'No messages in session.';\n }\n\n const formatted = formatMessagesForSummary(messages);\n const prompt = `Summarize this conversation for someone searching with: \"${query}\". Focus on facts, decisions, and names. Max 200 words. Use the same language as the conversation when possible.\n\nConversation:\n${formatted}`;\n\n const userMsg: UserMessage = { role: 'user', content: prompt, timestamp: Date.now() };\n const model = resolveSummaryModel(getConfig);\n\n try {\n const result = await complete(\n model,\n { messages: [userMsg] },\n {\n maxTokens: 400,\n temperature: 0.15,\n signal: signal as AbortSignal,\n },\n );\n\n let text = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n text += String((c as { text?: string }).text || '');\n }\n }\n }\n return text.trim() || '[Empty summary]';\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n log.warn(\n {\n err,\n summarizingSessionKey: logMeta?.summarizingSessionKey,\n queryLength: query.length,\n messageCount: messages.length,\n },\n `session_search LLM summarization failed${logMeta?.summarizingSessionKey ? ` (session ${logMeta.summarizingSessionKey})` : ''}: ${msg}`,\n );\n return `[Summarization failed: ${msg}]`;\n }\n}\n\nconst SessionSearchSchema = Type.Object({\n query: Type.Optional(\n Type.String({ description: 'Keyword search over past sessions. Omit to list recent sessions.' }),\n ),\n roleFilter: Type.Optional(\n Type.Union([\n Type.Literal('user'),\n Type.Literal('assistant'),\n Type.Literal('system'),\n Type.Literal('tool'),\n Type.Literal('toolResult'),\n ]),\n ),\n limit: Type.Optional(Type.Number({ minimum: 1, maximum: 15 })),\n excludeSessionKey: Type.Optional(\n Type.String({ description: 'Exclude this session key from keyword results (default: current chat).' }),\n ),\n});\n\nexport interface SessionSearchToolDeps {\n getSessionStore: () => SessionStore;\n getConfig?: () => Config | undefined;\n getCurrentSessionKey?: () => string | undefined;\n}\n\ntype SessionSearchParams = {\n query?: string;\n roleFilter?: 'user' | 'assistant' | 'system' | 'tool' | 'toolResult';\n limit?: number;\n excludeSessionKey?: string;\n};\n\nexport function createSessionSearchTool(deps: SessionSearchToolDeps): AgentTool {\n return {\n name: 'session_search',\n label: 'Session search',\n description:\n 'Search other chat sessions by keywords and get short summaries, or omit `query` to list recent sessions (no LLM cost). Uses the same session store as the gateway. Narrow with roleFilter if needed.',\n parameters: SessionSearchSchema,\n\n async execute(\n _toolCallId: string,\n params: any,\n signal?: AbortSignal,\n ): Promise<AgentToolResult<{}>> {\n const p = params as SessionSearchParams;\n const store = deps.getSessionStore();\n const limit = Math.min(15, Math.max(1, p.limit ?? 5));\n const query = p.query?.trim() ?? '';\n\n try {\n if (!query) {\n const listed = await store.list({\n limit: limit + 5,\n sortBy: 'updatedAt',\n sortOrder: 'desc',\n });\n const items = listed.items.slice(0, limit).map((s) => ({\n key: s.key,\n name: s.name,\n updatedAt: s.updatedAt,\n messageCount: s.messageCount,\n sourceChannel: s.sourceChannel,\n }));\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n { success: true, mode: 'recent', results: items, total: listed.total },\n null,\n 2,\n ),\n },\n ],\n details: { mode: 'recent', items },\n };\n }\n\n const sessionsRoot = store.getSessionsRoot();\n const index = await getOrLoadSessionSearchIndex(sessionsRoot);\n let matches = index.search(query, 80);\n\n const exclude = p.excludeSessionKey?.trim() || deps.getCurrentSessionKey?.() || '';\n if (exclude) {\n matches = matches.filter((m) => m.key !== exclude);\n }\n\n const top = matches.slice(0, limit);\n\n const summaries = await Promise.all(\n top.map(async ({ key, score }) => {\n let messages = index.getSessionMessages(key);\n if (messages.length === 0) {\n messages = await store.load(key);\n }\n\n if (p.roleFilter) {\n messages = messages.filter((m) => m.role === p.roleFilter);\n }\n\n const summary = await summarizeSession(messages, query, deps.getConfig, signal, {\n summarizingSessionKey: key,\n });\n return { sessionKey: key, score, summary };\n }),\n );\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n success: true,\n mode: 'keyword',\n query,\n results: summaries,\n count: summaries.length,\n },\n null,\n 2,\n ),\n },\n ],\n details: { query, summaries },\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: 'text', text: `session_search error: ${message}` }],\n details: { error: message },\n };\n }\n },\n } as any;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"session-search-tool.js","names":[],"sources":["../../../../src/agent/tools/session-search-tool.ts"],"sourcesContent":["// Cross-session transcript search + optional LLM summaries\nimport { Type } from '@sinclair/typebox';\nimport type { AgentMessage, AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../../providers/index.js';\nimport { getOrLoadSessionSearchIndex } from '../../session/search-index-cache.js';\nimport type { SessionStore } from '../../session/store.js';\nimport { readAgentMessageContent } from '../memory/agent-message-access.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('Agent:SessionSearch');\n\nconst MAX_SUMMARY_CHARS = 20_000;\n\nexport { invalidateSessionSearchIndexCache } from '../../session/search-index-cache.js';\n\nfunction resolveSummaryModel(getConfig?: () => Config | undefined) {\n const envRef = process.env.XOPC_SESSION_SEARCH_MODEL?.trim();\n const configRef = getConfig?.()?.agents?.defaults?.sessionSearch?.summaryModel?.trim();\n const ref = envRef || configRef;\n if (ref) {\n try {\n return resolveModel(ref);\n } catch (err) {\n log.warn({ err, ref }, 'session_search: summary model resolve failed, using fallback');\n }\n }\n try {\n return resolveModel('openai/gpt-4o-mini');\n } catch {\n const d = getDefaultModelSync(getConfig?.());\n return resolveModel(d);\n }\n}\n\nfunction extractTextFromContent(content: unknown): string {\n if (typeof content === 'string') {\n return content;\n }\n if (!Array.isArray(content)) {\n return '';\n }\n const parts: string[] = [];\n for (const item of content) {\n if (typeof item !== 'object' || item === null || !('type' in item)) {\n continue;\n }\n const c = item as { type?: string; text?: string };\n if (c.type === 'text' && typeof c.text === 'string') {\n parts.push(c.text);\n }\n }\n return parts.join(' ');\n}\n\nfunction formatMessagesForSummary(messages: AgentMessage[]): string {\n const lines: string[] = [];\n let total = 0;\n for (const msg of messages) {\n const role = String(msg.role || 'unknown').toUpperCase();\n const text = extractTextFromContent(readAgentMessageContent(msg));\n const line = `${role}: ${text}`;\n if (total + line.length > MAX_SUMMARY_CHARS) {\n lines.push('… [truncated]');\n break;\n }\n lines.push(line);\n total += line.length + 1;\n }\n return lines.join('\\n\\n');\n}\n\nasync function summarizeSession(\n messages: AgentMessage[],\n query: string,\n getConfig: (() => Config | undefined) | undefined,\n signal: AbortSignal | undefined,\n logMeta?: { summarizingSessionKey: string },\n): Promise<string> {\n if (messages.length === 0) {\n return 'No messages in session.';\n }\n\n const formatted = formatMessagesForSummary(messages);\n const prompt = `Summarize this conversation for someone searching with: \"${query}\". Focus on facts, decisions, and names. Max 200 words. Use the same language as the conversation when possible.\n\nConversation:\n${formatted}`;\n\n const userMsg: UserMessage = { role: 'user', content: prompt, timestamp: Date.now() };\n const model = resolveSummaryModel(getConfig);\n\n try {\n const result = await complete(\n model,\n { messages: [userMsg] },\n {\n maxTokens: 400,\n temperature: 0.15,\n signal: signal as AbortSignal,\n },\n );\n\n let text = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n text += String((c as { text?: string }).text || '');\n }\n }\n }\n return text.trim() || '[Empty summary]';\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n log.warn(\n {\n err,\n summarizingSessionKey: logMeta?.summarizingSessionKey,\n queryLength: query.length,\n messageCount: messages.length,\n },\n `session_search LLM summarization failed${logMeta?.summarizingSessionKey ? ` (session ${logMeta.summarizingSessionKey})` : ''}: ${msg}`,\n );\n return `[Summarization failed: ${msg}]`;\n }\n}\n\nconst SessionSearchSchema = Type.Object({\n query: Type.Optional(\n Type.String({ description: 'Keyword search over past sessions. Omit to list recent sessions.' }),\n ),\n roleFilter: Type.Optional(\n Type.Union([\n Type.Literal('user'),\n Type.Literal('assistant'),\n Type.Literal('system'),\n Type.Literal('tool'),\n Type.Literal('toolResult'),\n ]),\n ),\n limit: Type.Optional(Type.Number({ minimum: 1, maximum: 15 })),\n excludeSessionKey: Type.Optional(\n Type.String({ description: 'Exclude this session key from keyword results (default: current chat).' }),\n ),\n});\n\nexport interface SessionSearchToolDeps {\n getSessionStore: () => SessionStore;\n getConfig?: () => Config | undefined;\n getCurrentSessionKey?: () => string | undefined;\n}\n\ntype SessionSearchParams = {\n query?: string;\n roleFilter?: 'user' | 'assistant' | 'system' | 'tool' | 'toolResult';\n limit?: number;\n excludeSessionKey?: string;\n};\n\nexport function createSessionSearchTool(deps: SessionSearchToolDeps): AgentTool {\n return {\n name: 'session_search',\n label: 'Session search',\n description:\n 'Search other chat sessions by keywords and get short summaries, or omit `query` to list recent sessions (no LLM cost). Uses the same session store as the gateway. Narrow with roleFilter if needed.',\n parameters: SessionSearchSchema,\n\n async execute(\n _toolCallId: string,\n params: any,\n signal?: AbortSignal,\n ): Promise<AgentToolResult<{}>> {\n const p = params as SessionSearchParams;\n const store = deps.getSessionStore();\n const limit = Math.min(15, Math.max(1, p.limit ?? 5));\n const query = p.query?.trim() ?? '';\n\n try {\n if (!query) {\n const listed = await store.list({\n limit: limit + 5,\n sortBy: 'updatedAt',\n sortOrder: 'desc',\n });\n const items = listed.items.slice(0, limit).map((s) => ({\n key: s.key,\n name: s.name,\n updatedAt: s.updatedAt,\n messageCount: s.messageCount,\n sourceChannel: s.sourceChannel,\n }));\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n { success: true, mode: 'recent', results: items, total: listed.total },\n null,\n 2,\n ),\n },\n ],\n details: { mode: 'recent', items },\n };\n }\n\n const sessionsRoot = store.getSessionsRoot();\n const index = await getOrLoadSessionSearchIndex(sessionsRoot);\n let matches = index.search(query, 80);\n\n const exclude = p.excludeSessionKey?.trim() || deps.getCurrentSessionKey?.() || '';\n if (exclude) {\n matches = matches.filter((m) => m.key !== exclude);\n }\n\n const top = matches.slice(0, limit);\n\n const summaries = await Promise.all(\n top.map(async ({ key, score }) => {\n let messages = index.getSessionMessages(key);\n if (messages.length === 0) {\n messages = await store.load(key);\n }\n\n if (p.roleFilter) {\n messages = messages.filter((m) => m.role === p.roleFilter);\n }\n\n const summary = await summarizeSession(messages, query, deps.getConfig, signal, {\n summarizingSessionKey: key,\n });\n return { sessionKey: key, score, summary };\n }),\n );\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n success: true,\n mode: 'keyword',\n query,\n results: summaries,\n count: summaries.length,\n },\n null,\n 2,\n ),\n },\n ],\n details: { query, summaries },\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: 'text', text: `session_search error: ${message}` }],\n details: { error: message },\n };\n }\n },\n } as any;\n}\n"],"mappings":"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"}
@@ -2,9 +2,9 @@ import { resolveStateDir } from "../../config/paths-state.js";
2
2
  import { init_paths } from "../../config/paths.js";
3
3
  import { createSkillConfigManager } from "../skills/config.js";
4
4
  import { applyPatchToContent, atomicWriteUtf8, effectiveAgentWritePolicy, ensureCategorySegment, isPathInsideDir, maxSkillMdChars, maxSupportFileBytes, mutatableSkillOrNull, resolveCreateSkillDir, scanSkillDirOrError, validateSkillMdContent, validateSkillNameSegment, validateSupportingRelativePath } from "../skills/skill-manage-ops.js";
5
- import { readFile, rm } from "fs/promises";
6
5
  import { join } from "path";
7
6
  import { existsSync, rmSync } from "fs";
7
+ import { readFile, rm } from "fs/promises";
8
8
  import { Type } from "@sinclair/typebox";
9
9
  //#region src/agent/tools/skill-manage-tool.ts
10
10
  init_paths();
@@ -1,6 +1,6 @@
1
+ import { extractProfileAgentId } from "../../config/agent-profile.js";
1
2
  import { createLogger } from "../../utils/logger/index.js";
2
3
  import { init_logger } from "../../utils/logger.js";
3
- import { extractProfileAgentId } from "../../config/agent-profile.js";
4
4
  import { parseWorkflowScript } from "../workflow/parser.js";
5
5
  import "../workflow/index.js";
6
6
  import { Type } from "@sinclair/typebox";
@@ -9,7 +9,7 @@ import { Type } from "@sinclair/typebox";
9
9
  * `workflow` — starts a persisted workflow run in a dedicated chat session.
10
10
  */
11
11
  init_logger();
12
- const log = createLogger("workflow-tool");
12
+ const log = createLogger("Agent:WorkflowTool");
13
13
  const WorkflowToolSchema = Type.Object({
14
14
  name: Type.Optional(Type.String({ description: "Name of a saved workflow to run. Either `name` or `script` is required. Use `name` whenever the user references a known workflow (built-in or in ~/.xopc/workflows/)." })),
15
15
  script: Type.Optional(Type.String({ description: ["Raw JavaScript workflow script (no Markdown fences, no TypeScript syntax). Ignored when `name` is set.", "First statement: export const meta = { name: 'snake_case', description: 'short, human-readable' }."].join(" ") })),
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-tool.js","names":[],"sources":["../../../../src/agent/tools/workflow-tool.ts"],"sourcesContent":["/**\n * `workflow` — starts a persisted workflow run in a dedicated chat session.\n */\n\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\n\nimport { extractProfileAgentId } from '../../config/agent-profile.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { parseWorkflowScript } from '../workflow/index.js';\nimport type { WorkflowCatalog } from '../workflow/catalog.js';\nimport type {\n StartWorkflowRunServiceParams,\n WorkflowRunServiceResult,\n} from '../../workflows/service/workflow-run-service.types.js';\n\nconst log = createLogger('workflow-tool');\n\nconst WorkflowToolSchema = Type.Object({\n name: Type.Optional(\n Type.String({\n description:\n 'Name of a saved workflow to run. Either `name` or `script` is required. ' +\n 'Use `name` whenever the user references a known workflow (built-in or in ~/.xopc/workflows/).',\n }),\n ),\n script: Type.Optional(\n Type.String({\n description: [\n 'Raw JavaScript workflow script (no Markdown fences, no TypeScript syntax). Ignored when `name` is set.',\n \"First statement: export const meta = { name: 'snake_case', description: 'short, human-readable' }.\",\n ].join(' '),\n }),\n ),\n args: Type.Optional(\n Type.Any({\n description: 'Optional JSON value passed as workflow input payload.',\n }),\n ),\n goal: Type.Optional(\n Type.String({\n description: 'Optional goal or task description for this workflow run (defaults to user intent in chat).',\n }),\n ),\n});\n\nexport type WorkflowToolInput = {\n name?: string;\n script?: string;\n args?: unknown;\n goal?: string;\n};\n\nexport interface WorkflowToolDeps {\n catalog: WorkflowCatalog;\n getCurrentSessionKey?: () => string | undefined;\n getConfig: () => import('../../config/schema.js').Config | undefined;\n startWorkflowRun?: (params: StartWorkflowRunServiceParams) => Promise<WorkflowRunServiceResult>;\n}\n\nexport function createWorkflowTool(deps: WorkflowToolDeps): AgentTool {\n return {\n name: 'workflow',\n label: '◆ Workflow',\n description: [\n 'Start a multi-agent workflow run in its own chat session.',\n 'Use `name` for catalog workflows, or `script` for an inline workflow (saved under meta.name before run).',\n 'Returns immediately with runId + sessionKey — track progress in the linked chat session.',\n ].join(' '),\n parameters: WorkflowToolSchema,\n\n async execute(\n _toolCallId: string,\n params: WorkflowToolInput,\n ): Promise<AgentToolResult<{ runId: string; sessionKey: string } | { error: string }>> {\n if (!deps.startWorkflowRun) {\n return {\n content: [{ type: 'text', text: 'workflow: gateway workflow runs are not available in this context' }],\n details: { error: 'workflow_run_unavailable' },\n };\n }\n\n let definitionId: string;\n try {\n definitionId = resolveDefinitionId(params, deps.catalog);\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n return {\n content: [{ type: 'text', text: `workflow: ${message}` }],\n details: { error: message },\n };\n }\n\n const config = deps.getConfig();\n const parentSessionKey = deps.getCurrentSessionKey?.()?.trim();\n const agentId = extractProfileAgentId(parentSessionKey, config);\n\n const goal = params.goal?.trim() || '';\n const source = parentSessionKey\n ? ({ kind: 'chat' as const, sessionKey: parentSessionKey })\n : ({ kind: 'api' as const });\n\n try {\n const result = await deps.startWorkflowRun({\n agentId,\n definitionId,\n goal,\n input: params.args,\n parentSessionKey,\n source,\n });\n\n if (result.ok === false) {\n return {\n content: [{ type: 'text', text: `workflow: ${result.message}` }],\n details: { error: result.message },\n };\n }\n\n const summary = goal\n ? `Started workflow \\`${definitionId}\\` (run ${result.runId}). Open chat session to track progress and continue.`\n : `Started workflow \\`${definitionId}\\` (run ${result.runId}). Open the workflow chat session to track progress.`;\n\n return {\n content: [\n {\n type: 'text',\n text: `${summary}\\n\\nsessionKey: ${result.sessionKey}`,\n },\n ],\n details: {\n runId: result.runId,\n sessionKey: result.sessionKey,\n definitionId,\n parentSessionKey: parentSessionKey ?? null,\n } as { runId: string; sessionKey: string },\n };\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n log.warn({ err: e, errorMessage: message, workflow: definitionId }, `workflow start failed: ${message}`);\n return {\n content: [{ type: 'text', text: `workflow: ${message}` }],\n details: { error: message },\n };\n }\n },\n } as unknown as AgentTool;\n}\n\nfunction resolveDefinitionId(params: WorkflowToolInput, catalog: WorkflowCatalog): string {\n const name = params.name?.trim();\n if (name) {\n catalog.load(name);\n return name;\n }\n if (!params.script?.trim()) {\n throw new Error('either `name` or `script` is required.');\n }\n const script = normalizeScript(params.script);\n const meta = parseWorkflowScript(script).meta;\n catalog.save(meta.name, script);\n return meta.name;\n}\n\nfunction normalizeScript(script: string): string {\n let text = script.trim();\n const fence = text.match(/^```(?:js|javascript)?\\s*\\n([\\s\\S]*?)\\n```$/i);\n if (fence) text = fence[1].trim();\n return text;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"workflow-tool.js","names":[],"sources":["../../../../src/agent/tools/workflow-tool.ts"],"sourcesContent":["/**\n * `workflow` — starts a persisted workflow run in a dedicated chat session.\n */\n\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\n\nimport { extractProfileAgentId } from '../../config/agent-profile.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { parseWorkflowScript } from '../workflow/index.js';\nimport type { WorkflowCatalog } from '../workflow/catalog.js';\nimport type {\n StartWorkflowRunServiceParams,\n WorkflowRunServiceResult,\n} from '../../workflows/service/workflow-run-service.types.js';\n\nconst log = createLogger('Agent:WorkflowTool');\n\nconst WorkflowToolSchema = Type.Object({\n name: Type.Optional(\n Type.String({\n description:\n 'Name of a saved workflow to run. Either `name` or `script` is required. ' +\n 'Use `name` whenever the user references a known workflow (built-in or in ~/.xopc/workflows/).',\n }),\n ),\n script: Type.Optional(\n Type.String({\n description: [\n 'Raw JavaScript workflow script (no Markdown fences, no TypeScript syntax). Ignored when `name` is set.',\n \"First statement: export const meta = { name: 'snake_case', description: 'short, human-readable' }.\",\n ].join(' '),\n }),\n ),\n args: Type.Optional(\n Type.Any({\n description: 'Optional JSON value passed as workflow input payload.',\n }),\n ),\n goal: Type.Optional(\n Type.String({\n description: 'Optional goal or task description for this workflow run (defaults to user intent in chat).',\n }),\n ),\n});\n\nexport type WorkflowToolInput = {\n name?: string;\n script?: string;\n args?: unknown;\n goal?: string;\n};\n\nexport interface WorkflowToolDeps {\n catalog: WorkflowCatalog;\n getCurrentSessionKey?: () => string | undefined;\n getConfig: () => import('../../config/schema.js').Config | undefined;\n startWorkflowRun?: (params: StartWorkflowRunServiceParams) => Promise<WorkflowRunServiceResult>;\n}\n\nexport function createWorkflowTool(deps: WorkflowToolDeps): AgentTool {\n return {\n name: 'workflow',\n label: '◆ Workflow',\n description: [\n 'Start a multi-agent workflow run in its own chat session.',\n 'Use `name` for catalog workflows, or `script` for an inline workflow (saved under meta.name before run).',\n 'Returns immediately with runId + sessionKey — track progress in the linked chat session.',\n ].join(' '),\n parameters: WorkflowToolSchema,\n\n async execute(\n _toolCallId: string,\n params: WorkflowToolInput,\n ): Promise<AgentToolResult<{ runId: string; sessionKey: string } | { error: string }>> {\n if (!deps.startWorkflowRun) {\n return {\n content: [{ type: 'text', text: 'workflow: gateway workflow runs are not available in this context' }],\n details: { error: 'workflow_run_unavailable' },\n };\n }\n\n let definitionId: string;\n try {\n definitionId = resolveDefinitionId(params, deps.catalog);\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n return {\n content: [{ type: 'text', text: `workflow: ${message}` }],\n details: { error: message },\n };\n }\n\n const config = deps.getConfig();\n const parentSessionKey = deps.getCurrentSessionKey?.()?.trim();\n const agentId = extractProfileAgentId(parentSessionKey, config);\n\n const goal = params.goal?.trim() || '';\n const source = parentSessionKey\n ? ({ kind: 'chat' as const, sessionKey: parentSessionKey })\n : ({ kind: 'api' as const });\n\n try {\n const result = await deps.startWorkflowRun({\n agentId,\n definitionId,\n goal,\n input: params.args,\n parentSessionKey,\n source,\n });\n\n if (result.ok === false) {\n return {\n content: [{ type: 'text', text: `workflow: ${result.message}` }],\n details: { error: result.message },\n };\n }\n\n const summary = goal\n ? `Started workflow \\`${definitionId}\\` (run ${result.runId}). Open chat session to track progress and continue.`\n : `Started workflow \\`${definitionId}\\` (run ${result.runId}). Open the workflow chat session to track progress.`;\n\n return {\n content: [\n {\n type: 'text',\n text: `${summary}\\n\\nsessionKey: ${result.sessionKey}`,\n },\n ],\n details: {\n runId: result.runId,\n sessionKey: result.sessionKey,\n definitionId,\n parentSessionKey: parentSessionKey ?? null,\n } as { runId: string; sessionKey: string },\n };\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n log.warn({ err: e, errorMessage: message, workflow: definitionId }, `workflow start failed: ${message}`);\n return {\n content: [{ type: 'text', text: `workflow: ${message}` }],\n details: { error: message },\n };\n }\n },\n } as unknown as AgentTool;\n}\n\nfunction resolveDefinitionId(params: WorkflowToolInput, catalog: WorkflowCatalog): string {\n const name = params.name?.trim();\n if (name) {\n catalog.load(name);\n return name;\n }\n if (!params.script?.trim()) {\n throw new Error('either `name` or `script` is required.');\n }\n const script = normalizeScript(params.script);\n const meta = parseWorkflowScript(script).meta;\n catalog.save(meta.name, script);\n return meta.name;\n}\n\nfunction normalizeScript(script: string): string {\n let text = script.trim();\n const fence = text.match(/^```(?:js|javascript)?\\s*\\n([\\s\\S]*?)\\n```$/i);\n if (fence) text = fence[1].trim();\n return text;\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { checkFileSafety } from "../prompt/safety.js";
2
2
  import { isBareProfileMarkdownFileName, resolvePathUnderWorkspace, resolveProfileMarkdownPathIfBareName } from "./tool-paths.js";
3
3
  import { evaluateFilePolicy } from "../sandbox/exec-policy.js";
4
- import { mkdir, writeFile } from "fs/promises";
5
4
  import { dirname } from "path";
5
+ import { mkdir, writeFile } from "fs/promises";
6
6
  import { Type } from "@sinclair/typebox";
7
7
  //#region src/agent/tools/write.ts
8
8
  const MAX_FILE_SIZE = 10 * 1024 * 1024;
@@ -1,8 +1,8 @@
1
1
  import { init_paths_state, resolveStateDir } from "../../config/paths-state.js";
2
2
  import { BUILTIN_WORKFLOWS } from "./builtins/index.js";
3
3
  import { parseWorkflowScript } from "./parser.js";
4
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
5
4
  import { join } from "node:path";
5
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
6
6
  //#region src/agent/workflow/catalog.ts
7
7
  /**
8
8
  * Catalog for named workflows.
@@ -3,7 +3,7 @@ import { init_logger } from "../../utils/logger.js";
3
3
  import { renderWorkflowText } from "./snapshot.js";
4
4
  //#region src/agent/workflow/progress-broker.ts
5
5
  init_logger();
6
- const log = createLogger("workflow-progress-broker");
6
+ const log = createLogger("Agent:WorkflowProgress");
7
7
  const WORKFLOW_TOOL_NAME = "workflow";
8
8
  const RENDER_MAX_AGENTS_PER_PHASE = 4;
9
9
  const RENDER_MAX_LOGS = 2;
@@ -1 +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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"}
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('Agent:WorkflowProgress');\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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"}
@@ -5,7 +5,7 @@ import { DEFAULT_DELEGATE_TOOLS, DELEGATE_BLOCKED_TOOLS } from "../tools/delegat
5
5
  import { STRUCTURED_OUTPUT_TOOL_NAME, createStructuredOutputTool } from "./structured-output-tool.js";
6
6
  //#region src/agent/workflow/subagent-runner.ts
7
7
  init_logger();
8
- const log = createLogger("workflow-subagent-runner");
8
+ const log = createLogger("Agent:WorkflowSubagent");
9
9
  const DEFAULT_MAX_ITERATIONS = 30;
10
10
  var DelegateSubagentRunner = class {
11
11
  constructor(deps) {
@@ -1 +1 @@
1
- {"version":3,"file":"subagent-runner.js","names":[],"sources":["../../../../src/agent/workflow/subagent-runner.ts"],"sourcesContent":["/**\n * Adapter: spawns one isolated child agent per `agent()` call from a workflow.\n *\n * Wraps the existing `createDelegateChildHandle` so the workflow runtime stays\n * decoupled from the LLM stack (it sees only the `SubagentRunner` interface).\n *\n * Key behaviour:\n * - When `opts.schema` is provided, we inject `structured_output` into the child\n * tool set and unwrap the captured value on success. If the subagent finishes\n * without ever calling `structured_output`, we treat it as failure (`null`).\n * - Failures and aborts resolve to `null`. The workflow runtime continues — this\n * matches the pi-dynamic-workflows contract and keeps fan-out pipelines robust.\n * - We do NOT mutate `createDelegateChildHandle` — we just leverage its\n * `buildChildTools` injection point.\n */\n\nimport type { AgentTool } from '@earendil-works/pi-agent-core';\nimport type { Api, Model } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport { createLogger } from '../../utils/logger.js';\n\nimport {\n type BuildChildToolsOptions,\n createDelegateChildHandle,\n type DelegateChildHandleOptions,\n} from '../child-agent-factory.js';\nimport {\n DEFAULT_DELEGATE_TOOLS,\n DELEGATE_BLOCKED_TOOLS,\n} from '../tools/delegate-tool.js';\nimport type { ToolExecutorConfig } from '../tools/executor.js';\n\nimport {\n createStructuredOutputTool,\n STRUCTURED_OUTPUT_TOOL_NAME,\n type StructuredOutputCapture,\n} from './structured-output-tool.js';\nimport type { SubagentRunOptions, SubagentRunner, SubagentProgressEvent } from './types.js';\n\nconst log = createLogger('workflow-subagent-runner');\n\nconst DEFAULT_MAX_ITERATIONS = 30;\n\nexport interface DelegateSubagentRunnerDeps {\n workspace: string;\n bus: MessageBus;\n /** Resolves the default subagent model (typically the parent agent's primary model). */\n getDefaultModel: () => Model<Api>;\n getConfig: () => Config | undefined;\n toolExecutorConfig?: Partial<ToolExecutorConfig>;\n /**\n * Provided by the workflow tool from `AgentToolsFactory` — mirrors how\n * `delegate-tool` is wired (avoids importing `tools/factory.ts` here and\n * breaking the existing factory ↔ delegate-tool ↔ child-agent-factory\n * dependency contract).\n */\n buildChildTools: (opts: BuildChildToolsOptions) => AgentTool<any, any>[];\n}\n\nexport class DelegateSubagentRunner implements SubagentRunner {\n constructor(private readonly deps: DelegateSubagentRunnerDeps) {}\n\n async run<T = string>(prompt: string, opts: SubagentRunOptions<T>): Promise<T | null> {\n if (opts.signal?.aborted) return null;\n\n const capture: StructuredOutputCapture<T> = { called: false, value: undefined };\n const wantStructured = Boolean(opts.schema);\n\n const allowed = resolveAllowedToolNames(opts.allowedToolNames, wantStructured);\n const model = opts.model ?? safeResolveDefaultModel(this.deps.getDefaultModel);\n if (!model) {\n log.warn({ label: opts.label }, 'subagent run skipped: no primary model resolved');\n return null;\n }\n\n const fullPrompt = buildPrompt(prompt, opts, wantStructured);\n const streamMode = resolveSubagentStreamMode(this.deps.getConfig);\n\n const childOptions: DelegateChildHandleOptions = {\n workspace: this.deps.workspace,\n goal: fullPrompt,\n allowedToolNames: allowed,\n maxIterations: opts.maxIterations ?? DEFAULT_MAX_ITERATIONS,\n model,\n bus: this.deps.bus,\n getConfig: this.deps.getConfig,\n toolExecutorConfig: this.deps.toolExecutorConfig,\n buildChildTools: (childOpts) => {\n const base = this.deps.buildChildTools(childOpts);\n if (!wantStructured || !opts.schema) return base;\n // Replace any existing tool with the same name so the per-run capture wins.\n const filtered = base.filter((t) => t.name !== STRUCTURED_OUTPUT_TOOL_NAME);\n return [\n ...filtered,\n createStructuredOutputTool({ schema: opts.schema, capture }) as unknown as AgentTool<any, any>,\n ];\n },\n progressHooks:\n opts.onProgress && streamMode !== 'off'\n ? {\n mode: streamMode === 'full' ? 'full' : 'steps',\n onProgress: (event) => {\n opts.onProgress?.(mapChildProgressEvent(event));\n },\n }\n : undefined,\n };\n\n const handle = createDelegateChildHandle(childOptions);\n const onAbort = () => handle.abort();\n opts.signal?.addEventListener('abort', onAbort, { once: true });\n\n try {\n const { summary } = await handle.run();\n if (opts.signal?.aborted) return null;\n\n if (wantStructured) {\n if (!capture.called) {\n log.warn({ label: opts.label }, 'subagent finished without calling structured_output');\n return null;\n }\n return capture.value as T;\n }\n return summary as unknown as T;\n } catch (e) {\n if (opts.rethrow) throw e;\n const msg = e instanceof Error ? e.message : String(e);\n log.warn({ err: e, label: opts.label, errorMessage: msg }, `subagent run failed: ${msg}`);\n return null;\n } finally {\n opts.signal?.removeEventListener('abort', onAbort);\n }\n }\n}\n\nfunction resolveAllowedToolNames(\n requested: string[] | undefined,\n wantStructured: boolean,\n): string[] {\n const base = requested && requested.length > 0 ? requested : [...DEFAULT_DELEGATE_TOOLS];\n const filtered = base\n .map((s) => String(s).trim())\n .filter((s) => s.length > 0)\n .filter((s) => !DELEGATE_BLOCKED_TOOLS.has(s));\n if (wantStructured && !filtered.includes(STRUCTURED_OUTPUT_TOOL_NAME)) {\n filtered.push(STRUCTURED_OUTPUT_TOOL_NAME);\n }\n return [...new Set(filtered)];\n}\n\nfunction buildPrompt(prompt: string, opts: SubagentRunOptions<unknown>, structured: boolean): string {\n const parts: string[] = [];\n if (opts.instructions?.trim()) parts.push(opts.instructions.trim());\n if (opts.label) parts.push(`Task label: ${opts.label}`);\n if (opts.phase) parts.push(`Workflow phase: ${opts.phase}`);\n parts.push(prompt);\n if (structured) {\n parts.push(\n [\n 'Final output contract:',\n '- Your final action MUST be a structured_output tool call.',\n '- The structured_output arguments are the return value of this subagent.',\n '- Do not emit a prose final answer instead of structured_output.',\n '- If you need to inspect files or run commands first, do so, then call structured_output exactly once.',\n ].join('\\n'),\n );\n }\n return parts.join('\\n\\n');\n}\n\nfunction safeResolveDefaultModel(get: () => Model<Api>): Model<Api> | null {\n try {\n return get();\n } catch (e) {\n log.warn({ err: e }, 'failed to resolve default subagent model');\n return null;\n }\n}\n\nfunction resolveSubagentStreamMode(\n getConfig: () => Config | undefined,\n): 'off' | 'steps' | 'full' {\n const mode = getConfig()?.agents?.defaults?.workflow?.subagentStream;\n if (mode === 'off' || mode === 'steps' || mode === 'full') return mode;\n return 'steps';\n}\n\nfunction mapChildProgressEvent(event: {\n type: 'tool_start' | 'tool_end' | 'iteration' | 'text_delta' | 'thinking_delta';\n toolCallId?: string;\n toolName?: string;\n args?: Record<string, unknown>;\n isError?: boolean;\n resultPreview?: string;\n error?: string;\n count?: number;\n max?: number;\n delta?: string;\n}): SubagentProgressEvent {\n switch (event.type) {\n case 'tool_start':\n return {\n type: 'tool_start',\n toolCallId: event.toolCallId ?? '',\n toolName: event.toolName ?? 'tool',\n args: event.args ?? {},\n };\n case 'tool_end':\n return {\n type: 'tool_end',\n toolCallId: event.toolCallId ?? '',\n toolName: event.toolName ?? 'tool',\n isError: Boolean(event.isError),\n resultPreview: event.resultPreview,\n error: event.error,\n };\n case 'iteration':\n return {\n type: 'iteration',\n count: event.count ?? 0,\n max: event.max ?? 0,\n };\n case 'text_delta':\n return { type: 'text_delta', delta: event.delta ?? '' };\n case 'thinking_delta':\n return { type: 'thinking_delta', delta: event.delta ?? '' };\n default:\n return { type: 'text_delta', delta: '' };\n }\n}\n"],"mappings":";;;;;;aAqBqD;AAoBrD,MAAM,MAAM,aAAa,2BAA2B;AAEpD,MAAM,yBAAyB;AAkB/B,IAAa,yBAAb,MAA8D;CAC5D,YAAY,MAAmD;AAAlC,OAAA,OAAA;;CAE7B,MAAM,IAAgB,QAAgB,MAAgD;AACpF,MAAI,KAAK,QAAQ,QAAS,QAAO;EAEjC,MAAM,UAAsC;GAAE,QAAQ;GAAO,OAAO,KAAA;GAAW;EAC/E,MAAM,iBAAiB,QAAQ,KAAK,OAAO;EAE3C,MAAM,UAAU,wBAAwB,KAAK,kBAAkB,eAAe;EAC9E,MAAM,QAAQ,KAAK,SAAS,wBAAwB,KAAK,KAAK,gBAAgB;AAC9E,MAAI,CAAC,OAAO;AACV,OAAI,KAAK,EAAE,OAAO,KAAK,OAAO,EAAE,kDAAkD;AAClF,UAAO;;EAGT,MAAM,aAAa,YAAY,QAAQ,MAAM,eAAe;EAC5D,MAAM,aAAa,0BAA0B,KAAK,KAAK,UAAU;EAgCjE,MAAM,SAAS,0BAA0B;GA7BvC,WAAW,KAAK,KAAK;GACrB,MAAM;GACN,kBAAkB;GAClB,eAAe,KAAK,iBAAiB;GACrC;GACA,KAAK,KAAK,KAAK;GACf,WAAW,KAAK,KAAK;GACrB,oBAAoB,KAAK,KAAK;GAC9B,kBAAkB,cAAc;IAC9B,MAAM,OAAO,KAAK,KAAK,gBAAgB,UAAU;AACjD,QAAI,CAAC,kBAAkB,CAAC,KAAK,OAAQ,QAAO;AAG5C,WAAO,CACL,GAFe,KAAK,QAAQ,MAAM,EAAE,SAAS,4BAElC,EACX,2BAA2B;KAAE,QAAQ,KAAK;KAAQ;KAAS,CAAC,CAC7D;;GAEH,eACE,KAAK,cAAc,eAAe,QAC9B;IACE,MAAM,eAAe,SAAS,SAAS;IACvC,aAAa,UAAU;AACrB,UAAK,aAAa,sBAAsB,MAAM,CAAC;;IAElD,GACD,KAAA;GAG6C,CAAC;EACtD,MAAM,gBAAgB,OAAO,OAAO;AACpC,OAAK,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,MAAM,CAAC;AAE/D,MAAI;GACF,MAAM,EAAE,YAAY,MAAM,OAAO,KAAK;AACtC,OAAI,KAAK,QAAQ,QAAS,QAAO;AAEjC,OAAI,gBAAgB;AAClB,QAAI,CAAC,QAAQ,QAAQ;AACnB,SAAI,KAAK,EAAE,OAAO,KAAK,OAAO,EAAE,sDAAsD;AACtF,YAAO;;AAET,WAAO,QAAQ;;AAEjB,UAAO;WACA,GAAG;AACV,OAAI,KAAK,QAAS,OAAM;GACxB,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,OAAI,KAAK;IAAE,KAAK;IAAG,OAAO,KAAK;IAAO,cAAc;IAAK,EAAE,wBAAwB,MAAM;AACzF,UAAO;YACC;AACR,QAAK,QAAQ,oBAAoB,SAAS,QAAQ;;;;AAKxD,SAAS,wBACP,WACA,gBACU;CAEV,MAAM,YADO,aAAa,UAAU,SAAS,IAAI,YAAY,CAAC,GAAG,uBAAuB,EAErF,KAAK,MAAM,OAAO,EAAE,CAAC,MAAM,CAAC,CAC5B,QAAQ,MAAM,EAAE,SAAS,EAAE,CAC3B,QAAQ,MAAM,CAAC,uBAAuB,IAAI,EAAE,CAAC;AAChD,KAAI,kBAAkB,CAAC,SAAS,SAAA,oBAAqC,CACnE,UAAS,KAAK,4BAA4B;AAE5C,QAAO,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC;;AAG/B,SAAS,YAAY,QAAgB,MAAmC,YAA6B;CACnG,MAAM,QAAkB,EAAE;AAC1B,KAAI,KAAK,cAAc,MAAM,CAAE,OAAM,KAAK,KAAK,aAAa,MAAM,CAAC;AACnE,KAAI,KAAK,MAAO,OAAM,KAAK,eAAe,KAAK,QAAQ;AACvD,KAAI,KAAK,MAAO,OAAM,KAAK,mBAAmB,KAAK,QAAQ;AAC3D,OAAM,KAAK,OAAO;AAClB,KAAI,WACF,OAAM,KACJ;EACE;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK,CACb;AAEH,QAAO,MAAM,KAAK,OAAO;;AAG3B,SAAS,wBAAwB,KAA0C;AACzE,KAAI;AACF,SAAO,KAAK;UACL,GAAG;AACV,MAAI,KAAK,EAAE,KAAK,GAAG,EAAE,2CAA2C;AAChE,SAAO;;;AAIX,SAAS,0BACP,WAC0B;CAC1B,MAAM,OAAO,WAAW,EAAE,QAAQ,UAAU,UAAU;AACtD,KAAI,SAAS,SAAS,SAAS,WAAW,SAAS,OAAQ,QAAO;AAClE,QAAO;;AAGT,SAAS,sBAAsB,OAWL;AACxB,SAAQ,MAAM,MAAd;EACE,KAAK,aACH,QAAO;GACL,MAAM;GACN,YAAY,MAAM,cAAc;GAChC,UAAU,MAAM,YAAY;GAC5B,MAAM,MAAM,QAAQ,EAAE;GACvB;EACH,KAAK,WACH,QAAO;GACL,MAAM;GACN,YAAY,MAAM,cAAc;GAChC,UAAU,MAAM,YAAY;GAC5B,SAAS,QAAQ,MAAM,QAAQ;GAC/B,eAAe,MAAM;GACrB,OAAO,MAAM;GACd;EACH,KAAK,YACH,QAAO;GACL,MAAM;GACN,OAAO,MAAM,SAAS;GACtB,KAAK,MAAM,OAAO;GACnB;EACH,KAAK,aACH,QAAO;GAAE,MAAM;GAAc,OAAO,MAAM,SAAS;GAAI;EACzD,KAAK,iBACH,QAAO;GAAE,MAAM;GAAkB,OAAO,MAAM,SAAS;GAAI;EAC7D,QACE,QAAO;GAAE,MAAM;GAAc,OAAO;GAAI"}
1
+ {"version":3,"file":"subagent-runner.js","names":[],"sources":["../../../../src/agent/workflow/subagent-runner.ts"],"sourcesContent":["/**\n * Adapter: spawns one isolated child agent per `agent()` call from a workflow.\n *\n * Wraps the existing `createDelegateChildHandle` so the workflow runtime stays\n * decoupled from the LLM stack (it sees only the `SubagentRunner` interface).\n *\n * Key behaviour:\n * - When `opts.schema` is provided, we inject `structured_output` into the child\n * tool set and unwrap the captured value on success. If the subagent finishes\n * without ever calling `structured_output`, we treat it as failure (`null`).\n * - Failures and aborts resolve to `null`. The workflow runtime continues — this\n * matches the pi-dynamic-workflows contract and keeps fan-out pipelines robust.\n * - We do NOT mutate `createDelegateChildHandle` — we just leverage its\n * `buildChildTools` injection point.\n */\n\nimport type { AgentTool } from '@earendil-works/pi-agent-core';\nimport type { Api, Model } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport { createLogger } from '../../utils/logger.js';\n\nimport {\n type BuildChildToolsOptions,\n createDelegateChildHandle,\n type DelegateChildHandleOptions,\n} from '../child-agent-factory.js';\nimport {\n DEFAULT_DELEGATE_TOOLS,\n DELEGATE_BLOCKED_TOOLS,\n} from '../tools/delegate-tool.js';\nimport type { ToolExecutorConfig } from '../tools/executor.js';\n\nimport {\n createStructuredOutputTool,\n STRUCTURED_OUTPUT_TOOL_NAME,\n type StructuredOutputCapture,\n} from './structured-output-tool.js';\nimport type { SubagentRunOptions, SubagentRunner, SubagentProgressEvent } from './types.js';\n\nconst log = createLogger('Agent:WorkflowSubagent');\n\nconst DEFAULT_MAX_ITERATIONS = 30;\n\nexport interface DelegateSubagentRunnerDeps {\n workspace: string;\n bus: MessageBus;\n /** Resolves the default subagent model (typically the parent agent's primary model). */\n getDefaultModel: () => Model<Api>;\n getConfig: () => Config | undefined;\n toolExecutorConfig?: Partial<ToolExecutorConfig>;\n /**\n * Provided by the workflow tool from `AgentToolsFactory` — mirrors how\n * `delegate-tool` is wired (avoids importing `tools/factory.ts` here and\n * breaking the existing factory ↔ delegate-tool ↔ child-agent-factory\n * dependency contract).\n */\n buildChildTools: (opts: BuildChildToolsOptions) => AgentTool<any, any>[];\n}\n\nexport class DelegateSubagentRunner implements SubagentRunner {\n constructor(private readonly deps: DelegateSubagentRunnerDeps) {}\n\n async run<T = string>(prompt: string, opts: SubagentRunOptions<T>): Promise<T | null> {\n if (opts.signal?.aborted) return null;\n\n const capture: StructuredOutputCapture<T> = { called: false, value: undefined };\n const wantStructured = Boolean(opts.schema);\n\n const allowed = resolveAllowedToolNames(opts.allowedToolNames, wantStructured);\n const model = opts.model ?? safeResolveDefaultModel(this.deps.getDefaultModel);\n if (!model) {\n log.warn({ label: opts.label }, 'subagent run skipped: no primary model resolved');\n return null;\n }\n\n const fullPrompt = buildPrompt(prompt, opts, wantStructured);\n const streamMode = resolveSubagentStreamMode(this.deps.getConfig);\n\n const childOptions: DelegateChildHandleOptions = {\n workspace: this.deps.workspace,\n goal: fullPrompt,\n allowedToolNames: allowed,\n maxIterations: opts.maxIterations ?? DEFAULT_MAX_ITERATIONS,\n model,\n bus: this.deps.bus,\n getConfig: this.deps.getConfig,\n toolExecutorConfig: this.deps.toolExecutorConfig,\n buildChildTools: (childOpts) => {\n const base = this.deps.buildChildTools(childOpts);\n if (!wantStructured || !opts.schema) return base;\n // Replace any existing tool with the same name so the per-run capture wins.\n const filtered = base.filter((t) => t.name !== STRUCTURED_OUTPUT_TOOL_NAME);\n return [\n ...filtered,\n createStructuredOutputTool({ schema: opts.schema, capture }) as unknown as AgentTool<any, any>,\n ];\n },\n progressHooks:\n opts.onProgress && streamMode !== 'off'\n ? {\n mode: streamMode === 'full' ? 'full' : 'steps',\n onProgress: (event) => {\n opts.onProgress?.(mapChildProgressEvent(event));\n },\n }\n : undefined,\n };\n\n const handle = createDelegateChildHandle(childOptions);\n const onAbort = () => handle.abort();\n opts.signal?.addEventListener('abort', onAbort, { once: true });\n\n try {\n const { summary } = await handle.run();\n if (opts.signal?.aborted) return null;\n\n if (wantStructured) {\n if (!capture.called) {\n log.warn({ label: opts.label }, 'subagent finished without calling structured_output');\n return null;\n }\n return capture.value as T;\n }\n return summary as unknown as T;\n } catch (e) {\n if (opts.rethrow) throw e;\n const msg = e instanceof Error ? e.message : String(e);\n log.warn({ err: e, label: opts.label, errorMessage: msg }, `subagent run failed: ${msg}`);\n return null;\n } finally {\n opts.signal?.removeEventListener('abort', onAbort);\n }\n }\n}\n\nfunction resolveAllowedToolNames(\n requested: string[] | undefined,\n wantStructured: boolean,\n): string[] {\n const base = requested && requested.length > 0 ? requested : [...DEFAULT_DELEGATE_TOOLS];\n const filtered = base\n .map((s) => String(s).trim())\n .filter((s) => s.length > 0)\n .filter((s) => !DELEGATE_BLOCKED_TOOLS.has(s));\n if (wantStructured && !filtered.includes(STRUCTURED_OUTPUT_TOOL_NAME)) {\n filtered.push(STRUCTURED_OUTPUT_TOOL_NAME);\n }\n return [...new Set(filtered)];\n}\n\nfunction buildPrompt(prompt: string, opts: SubagentRunOptions<unknown>, structured: boolean): string {\n const parts: string[] = [];\n if (opts.instructions?.trim()) parts.push(opts.instructions.trim());\n if (opts.label) parts.push(`Task label: ${opts.label}`);\n if (opts.phase) parts.push(`Workflow phase: ${opts.phase}`);\n parts.push(prompt);\n if (structured) {\n parts.push(\n [\n 'Final output contract:',\n '- Your final action MUST be a structured_output tool call.',\n '- The structured_output arguments are the return value of this subagent.',\n '- Do not emit a prose final answer instead of structured_output.',\n '- If you need to inspect files or run commands first, do so, then call structured_output exactly once.',\n ].join('\\n'),\n );\n }\n return parts.join('\\n\\n');\n}\n\nfunction safeResolveDefaultModel(get: () => Model<Api>): Model<Api> | null {\n try {\n return get();\n } catch (e) {\n log.warn({ err: e }, 'failed to resolve default subagent model');\n return null;\n }\n}\n\nfunction resolveSubagentStreamMode(\n getConfig: () => Config | undefined,\n): 'off' | 'steps' | 'full' {\n const mode = getConfig()?.agents?.defaults?.workflow?.subagentStream;\n if (mode === 'off' || mode === 'steps' || mode === 'full') return mode;\n return 'steps';\n}\n\nfunction mapChildProgressEvent(event: {\n type: 'tool_start' | 'tool_end' | 'iteration' | 'text_delta' | 'thinking_delta';\n toolCallId?: string;\n toolName?: string;\n args?: Record<string, unknown>;\n isError?: boolean;\n resultPreview?: string;\n error?: string;\n count?: number;\n max?: number;\n delta?: string;\n}): SubagentProgressEvent {\n switch (event.type) {\n case 'tool_start':\n return {\n type: 'tool_start',\n toolCallId: event.toolCallId ?? '',\n toolName: event.toolName ?? 'tool',\n args: event.args ?? {},\n };\n case 'tool_end':\n return {\n type: 'tool_end',\n toolCallId: event.toolCallId ?? '',\n toolName: event.toolName ?? 'tool',\n isError: Boolean(event.isError),\n resultPreview: event.resultPreview,\n error: event.error,\n };\n case 'iteration':\n return {\n type: 'iteration',\n count: event.count ?? 0,\n max: event.max ?? 0,\n };\n case 'text_delta':\n return { type: 'text_delta', delta: event.delta ?? '' };\n case 'thinking_delta':\n return { type: 'thinking_delta', delta: event.delta ?? '' };\n default:\n return { type: 'text_delta', delta: '' };\n }\n}\n"],"mappings":"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"}
@@ -1,11 +1,11 @@
1
1
  import { __esmMin } from "../../_virtual/_rolldown/runtime.js";
2
- import { init_write_file_atomic, writeTextAtomic } from "../infra/write-file-atomic.js";
3
2
  import { createLogger } from "../utils/logger/index.js";
4
3
  import { init_logger } from "../utils/logger.js";
5
- import { getApiKeyFromEnv, init_env_keys } from "../providers/env-keys.js";
6
4
  import { init_paths, resolveAgentAuthProfilesPath, resolveAuthProfilesPath, resolveCredentialsDir, resolveOAuthPath } from "../config/paths.js";
7
- import { mkdir, readFile, rm } from "fs/promises";
5
+ import { init_write_file_atomic, writeTextAtomic } from "../infra/write-file-atomic.js";
6
+ import { getApiKeyFromEnv, init_env_keys } from "../providers/env-keys.js";
8
7
  import { dirname, join } from "path";
8
+ import { mkdir, readFile, rm } from "fs/promises";
9
9
  //#region src/auth/credentials.ts
10
10
  function getCredentialResolver(options) {
11
11
  return new CredentialResolver(options);
@@ -1,6 +1,6 @@
1
1
  import { init_write_file_atomic, writeTextAtomicSync } from "../../infra/write-file-atomic.js";
2
- import { chmodSync, existsSync, mkdirSync, readFileSync } from "node:fs";
3
2
  import path from "node:path";
3
+ import { chmodSync, existsSync, mkdirSync, readFileSync } from "node:fs";
4
4
  import { homedir } from "os";
5
5
  //#region src/auth/profiles/store.ts
6
6
  /**
@@ -1,7 +1,7 @@
1
1
  import { __esmMin } from "../../_virtual/_rolldown/runtime.js";
2
+ import { getDefaultAgentId, init_resolve_route } from "../routing/resolve-route.js";
2
3
  import { init_paths, resolveAgentAuthProfilesPath, resolveAuthProfilesPath, resolveOAuthPath } from "../config/paths.js";
3
4
  import { init_loader, loadConfig } from "../config/loader.js";
4
- import { getDefaultAgentId, init_resolve_route } from "../routing/resolve-route.js";
5
5
  import { existsSync, readFileSync } from "node:fs";
6
6
  //#region src/auth/sync-provider-auth.ts
7
7
  /**
@@ -1,6 +1,6 @@
1
1
  import { __esmMin } from "../../_virtual/_rolldown/runtime.js";
2
- import { isAbsolute, relative, resolve, sep } from "node:path";
3
2
  import { homedir } from "node:os";
3
+ import { isAbsolute, relative, resolve, sep } from "node:path";
4
4
  //#region src/browser/cache-dir-policy.ts
5
5
  function expandHome(input) {
6
6
  if (input === "~") return homedir();
@@ -2,10 +2,10 @@ import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
3
  import { loadPlaywrightCoreModule } from "./providers/playwright-doctor.js";
4
4
  import { pickFreePort } from "./free-port.js";
5
+ import { homedir, platform } from "node:os";
6
+ import { join } from "node:path";
5
7
  import { existsSync } from "node:fs";
6
8
  import { mkdir, rm } from "node:fs/promises";
7
- import { join } from "node:path";
8
- import { homedir, platform } from "node:os";
9
9
  import { spawn } from "node:child_process";
10
10
  //#region src/browser/cdp-local-launcher.ts
11
11
  /**