@xopcai/xopc 0.0.113 → 0.0.114

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 (327) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CBlklyNr.js → agent-avatar-dicebear-Fp-p7bP1.js} +1 -1
  4. package/dist/gateway/static/root/assets/agent-browser-settings-page-CWh7trjd.js +6 -0
  5. package/dist/gateway/static/root/assets/agent-defaults-tabbed-page-kC8OuLoj.js +1 -0
  6. package/dist/gateway/static/root/assets/agents-DJWG26Si.js +71 -0
  7. package/dist/gateway/static/root/assets/{app-management-settings-panel-28E_WfMe.js → app-management-settings-panel-v24i25Oj.js} +1 -1
  8. package/dist/gateway/static/root/assets/{appearance-settings-Do1z0XgJ.js → appearance-settings-ycbdJRuZ.js} +1 -1
  9. package/dist/gateway/static/root/assets/apps-page-0V3DXZ7J.js +1 -0
  10. package/dist/gateway/static/root/assets/{archive-plugin-BAFlsov_.js → archive-plugin-CJzSADJo.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-V3qGPC3x.js → attachment-load-BTAB4zGS.js} +1 -1
  12. package/dist/gateway/static/root/assets/{binary-plugins-DT8NvA6W.js → binary-plugins-DXR1-mct.js} +1 -1
  13. package/dist/gateway/static/root/assets/{block-editor-CdLMEcHd.js → block-editor-CzQri9h2.js} +44 -44
  14. package/dist/gateway/static/root/assets/channels-settings-CIKOkYdO.js +1 -0
  15. package/dist/gateway/static/root/assets/{config-api-Bf5mjpu7.js → config-api-DRqjEvAx.js} +1 -1
  16. package/dist/gateway/static/root/assets/connectors-settings-ClrDS5jH.js +2 -0
  17. package/dist/gateway/static/root/assets/{cron-api--pAB_rps.js → cron-api-BXaZXv1o.js} +1 -1
  18. package/dist/gateway/static/root/assets/cron-page-Chi_4u4P.js +3 -0
  19. package/dist/gateway/static/root/assets/dreaming-settings-B0_rs2tj.js +1 -0
  20. package/dist/gateway/static/root/assets/{extension-debug-page-BGhE7msg.js → extension-debug-page-DSkqMYo7.js} +1 -1
  21. package/dist/gateway/static/root/assets/{extension-page-CvpI45WG.js → extension-page-h2dGDbMN.js} +1 -1
  22. package/dist/gateway/static/root/assets/{extension-settings-page-DAmR5FUe.js → extension-settings-page-hN99RhOi.js} +1 -1
  23. package/dist/gateway/static/root/assets/{fetch-NZmjSXLl.js → fetch-D9QqUtzZ.js} +1 -1
  24. package/dist/gateway/static/root/assets/{format-interval-label-qhth3BMV.js → format-interval-label-D6E687rG.js} +1 -1
  25. package/dist/gateway/static/root/assets/{gateway-config-api-CaXBt-9R.js → gateway-config-api-CLt_SKTC.js} +1 -1
  26. package/dist/gateway/static/root/assets/{gateway-settings-DphpbLBc.js → gateway-settings-1PDi4lI0.js} +1 -1
  27. package/dist/gateway/static/root/assets/{gateway-startup-retry-Be1omjh3.js → gateway-startup-retry-N7Z_sz7p.js} +1 -1
  28. package/dist/gateway/static/root/assets/goal-detail-page-D_wwXL-t.js +1 -0
  29. package/dist/gateway/static/root/assets/goals-page-DZ2toZ8y.js +2 -0
  30. package/dist/gateway/static/root/assets/{goals-settings-ceEBjnfc.js → goals-settings-LFlwtIKE.js} +1 -1
  31. package/dist/gateway/static/root/assets/{heartbeat-config-api-BzO6G4fD.js → heartbeat-config-api-DGPadTw-.js} +1 -1
  32. package/dist/gateway/static/root/assets/{heartbeat-settings-DCST9-du.js → heartbeat-settings-BHHAs8ZX.js} +1 -1
  33. package/dist/gateway/static/root/assets/index-DefIdW9q.js +122 -0
  34. package/dist/gateway/static/root/assets/index-DzmWBkq-.css +1 -0
  35. package/dist/gateway/static/root/assets/{interval-presets-DJmshV8n.js → interval-presets-CEklPQ_r.js} +1 -1
  36. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-9AUUlQ5Q.js → keyboard-shortcuts-settings-Ce_aQ8Iv.js} +1 -1
  37. package/dist/gateway/static/root/assets/logs-page-j_D2QTuQ.js +2 -0
  38. package/dist/gateway/static/root/assets/{markdown-split-CYYELqAr.js → markdown-split-BMz3xPXO.js} +1 -1
  39. package/dist/gateway/static/root/assets/{media-plugins-BEcMR2M8.js → media-plugins-BalOSoz9.js} +1 -1
  40. package/dist/gateway/static/root/assets/messages-BN_WiUg0.js +3 -0
  41. package/dist/gateway/static/root/assets/models-hub-panel-CMH_TmZz.js +2 -0
  42. package/dist/gateway/static/root/assets/note-detail-page-O18BrQPq.js +2 -0
  43. package/dist/gateway/static/root/assets/{note-image-lightbox-DNAoPcmm.js → note-image-lightbox-C5D1Q5mY.js} +1 -1
  44. package/dist/gateway/static/root/assets/notes-page-B_ZYCmY9.js +1 -0
  45. package/dist/gateway/static/root/assets/{preview-open-alternatives-BPLz0AIv.js → preview-open-alternatives-CYFR5U9l.js} +1 -1
  46. package/dist/gateway/static/root/assets/remote-access-hub-CwKgWixY.js +1 -0
  47. package/dist/gateway/static/root/assets/{save-bar-controls-CrWwWRVK.js → save-bar-controls-BQ79Z5IP.js} +1 -1
  48. package/dist/gateway/static/root/assets/{schedule-field-CF2sadvN.js → schedule-field-Bgk6Emue.js} +1 -1
  49. package/dist/gateway/static/root/assets/{schema-form-Di5cKjYE.js → schema-form-DzS36TMa.js} +1 -1
  50. package/dist/gateway/static/root/assets/sessions-page-C4fcD-7B.js +1 -0
  51. package/dist/gateway/static/root/assets/settings-advanced-gate-Ad2OJ86w.js +1 -0
  52. package/dist/gateway/static/root/assets/{settings-collapsible-section-Chh282jW.js → settings-collapsible-section-CdjFnSAy.js} +1 -1
  53. package/dist/gateway/static/root/assets/{settings-form-section-Dq-83cQd.js → settings-form-section-DZLy_exu.js} +1 -1
  54. package/dist/gateway/static/root/assets/settings-page-D2BMY5wo.js +1 -0
  55. package/dist/gateway/static/root/assets/setup-status-panel-aWwxl_E1.js +1 -0
  56. package/dist/gateway/static/root/assets/{share-preview-page-0FXYnSUA.js → share-preview-page-_qRDr4g_.js} +1 -1
  57. package/dist/gateway/static/root/assets/shares-settings-DUE83uIf.js +1 -0
  58. package/dist/gateway/static/root/assets/skill-reload-api-mWKJcrog.js +1 -0
  59. package/dist/gateway/static/root/assets/skills-page-0Sx1Q58n.js +2 -0
  60. package/dist/gateway/static/root/assets/{system-settings-panel-W1pTcojB.js → system-settings-panel-hqaBF5aG.js} +1 -1
  61. package/dist/gateway/static/root/assets/{use-agent-defaults-form-BVRvkK7m.js → use-agent-defaults-form-zbVIQLOA.js} +1 -1
  62. package/dist/gateway/static/root/assets/{use-channel-catalog-CgJibfMM.js → use-channel-catalog-CbXq97rT.js} +1 -1
  63. package/dist/gateway/static/root/assets/{use-settings-tab-guard-Bw_qkRQE.js → use-settings-tab-guard-DwS3drWK.js} +1 -1
  64. package/dist/gateway/static/root/assets/voice-api-key-field-D6HR0hR2.js +1 -0
  65. package/dist/gateway/static/root/assets/{voice-player-store-C7lziaAx.js → voice-player-store-DSuOd9zy.js} +1 -1
  66. package/dist/gateway/static/root/assets/workflow-page.utils-C0Uq3xyq.js +1 -0
  67. package/dist/gateway/static/root/assets/workflows-page-DW0fmWhQ.js +18 -0
  68. package/dist/gateway/static/root/index.html +5 -5
  69. package/dist/package.js +1 -1
  70. package/dist/src/agent/agent-manager.d.ts +8 -0
  71. package/dist/src/agent/agent-manager.js +57 -22
  72. package/dist/src/agent/agent-manager.js.map +1 -1
  73. package/dist/src/agent/bootstrap/project-agents-context.d.ts +2 -0
  74. package/dist/src/agent/bootstrap/project-agents-context.js +32 -0
  75. package/dist/src/agent/bootstrap/project-agents-context.js.map +1 -0
  76. package/dist/src/agent/goals/persistent-goal-service.js +2 -4
  77. package/dist/src/agent/goals/persistent-goal-service.js.map +1 -1
  78. package/dist/src/agent/inbound/inbound-loop.js +5 -1
  79. package/dist/src/agent/inbound/inbound-loop.js.map +1 -1
  80. package/dist/src/agent/inbound/turn-dispatcher.d.ts +2 -2
  81. package/dist/src/agent/inbound/turn-dispatcher.js +2 -2
  82. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  83. package/dist/src/agent/mcp/bundle-mcp-materialize.d.ts +27 -0
  84. package/dist/src/agent/mcp/bundle-mcp-materialize.js +49 -1
  85. package/dist/src/agent/mcp/bundle-mcp-materialize.js.map +1 -1
  86. package/dist/src/agent/mcp/bundle-mcp-runtime.js +78 -3
  87. package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
  88. package/dist/src/agent/mcp/bundle-mcp-types.d.ts +19 -0
  89. package/dist/src/agent/service/btw-query.js +11 -3
  90. package/dist/src/agent/service/btw-query.js.map +1 -1
  91. package/dist/src/agent/service/parse-outbound-session-key.js +13 -8
  92. package/dist/src/agent/service/parse-outbound-session-key.js.map +1 -1
  93. package/dist/src/agent/service/process-direct-one-shot.d.ts +2 -2
  94. package/dist/src/agent/service/process-direct-one-shot.js +1 -1
  95. package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
  96. package/dist/src/agent/service/process-direct-streaming.d.ts +2 -2
  97. package/dist/src/agent/service/process-direct-streaming.js +1 -1
  98. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  99. package/dist/src/agent/service.d.ts +1 -1
  100. package/dist/src/agent/service.js +9 -5
  101. package/dist/src/agent/service.js.map +1 -1
  102. package/dist/src/agent/service.types.d.ts +5 -0
  103. package/dist/src/agent/skills/filesystem-watcher.js +4 -3
  104. package/dist/src/agent/skills/filesystem-watcher.js.map +1 -1
  105. package/dist/src/agent/skills/index.js +2 -1
  106. package/dist/src/agent/skills/index.js.map +1 -1
  107. package/dist/src/agent/skills/skill-manage-ops.js +2 -1
  108. package/dist/src/agent/skills/skill-manage-ops.js.map +1 -1
  109. package/dist/src/agent/skills/types.d.ts +1 -1
  110. package/dist/src/agent/skills/watcher.js +2 -5
  111. package/dist/src/agent/skills/watcher.js.map +1 -1
  112. package/dist/src/agent/skills/workspace-skills-dir.d.ts +1 -0
  113. package/dist/src/agent/skills/workspace-skills-dir.js +11 -0
  114. package/dist/src/agent/skills/workspace-skills-dir.js.map +1 -0
  115. package/dist/src/agent/tool-manuals/browser.d.ts +1 -1
  116. package/dist/src/agent/tool-manuals/browser.js +62 -3
  117. package/dist/src/agent/tool-manuals/browser.js.map +1 -1
  118. package/dist/src/agent/tools/browser/tool/browser-use-tool.d.ts +4 -1
  119. package/dist/src/agent/tools/browser/tool/browser-use-tool.js +13 -4
  120. package/dist/src/agent/tools/browser/tool/browser-use-tool.js.map +1 -1
  121. package/dist/src/agent/tools/browser/tool/schemas.d.ts +0 -2
  122. package/dist/src/agent/tools/browser/tool/schemas.js +1 -2
  123. package/dist/src/agent/tools/browser/tool/schemas.js.map +1 -1
  124. package/dist/src/agent/tools/factory.js +1 -10
  125. package/dist/src/agent/tools/factory.js.map +1 -1
  126. package/dist/src/agent/tools/skill-manage-tool.js +1 -1
  127. package/dist/src/agent/tools/skill-manage-tool.js.map +1 -1
  128. package/dist/src/agent/workflow/subagent-runner.js +18 -1
  129. package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
  130. package/dist/src/browser/actions/extension-registry-bridge.js +91 -7
  131. package/dist/src/browser/actions/extension-registry-bridge.js.map +1 -1
  132. package/dist/src/browser/actions/registry.js +383 -7
  133. package/dist/src/browser/actions/registry.js.map +1 -1
  134. package/dist/src/browser/actions/types.d.ts +11 -3
  135. package/dist/src/browser/index.js +2 -2
  136. package/dist/src/browser/pipeline/output.d.ts +18 -16
  137. package/dist/src/browser/pipeline/output.js +28 -20
  138. package/dist/src/browser/pipeline/output.js.map +1 -1
  139. package/dist/src/browser/pipeline/runner.d.ts +7 -14
  140. package/dist/src/browser/pipeline/runner.js +349 -102
  141. package/dist/src/browser/pipeline/runner.js.map +1 -1
  142. package/dist/src/browser/pipeline/schema.d.ts +1 -0
  143. package/dist/src/browser/pipeline/schema.js +35 -58
  144. package/dist/src/browser/pipeline/schema.js.map +1 -1
  145. package/dist/src/browser/pipeline/source.d.ts +2 -0
  146. package/dist/src/browser/pipeline/source.js +10 -1
  147. package/dist/src/browser/pipeline/source.js.map +1 -1
  148. package/dist/src/browser/pipeline/template.d.ts +10 -2
  149. package/dist/src/browser/pipeline/template.js +50 -8
  150. package/dist/src/browser/pipeline/template.js.map +1 -1
  151. package/dist/src/chat-commands/builtins/config.js +3 -5
  152. package/dist/src/chat-commands/builtins/config.js.map +1 -1
  153. package/dist/src/chat-commands/context.d.ts +5 -0
  154. package/dist/src/chat-commands/context.js +18 -9
  155. package/dist/src/chat-commands/context.js.map +1 -1
  156. package/dist/src/chat-commands/index.d.ts +1 -1
  157. package/dist/src/chat-commands/index.js +2 -2
  158. package/dist/src/chat-commands/index.js.map +1 -1
  159. package/dist/src/chat-commands/processor.js +22 -21
  160. package/dist/src/chat-commands/processor.js.map +1 -1
  161. package/dist/src/chat-commands/session-key.d.ts +3 -15
  162. package/dist/src/chat-commands/session-key.js +3 -49
  163. package/dist/src/chat-commands/session-key.js.map +1 -1
  164. package/dist/src/chat-commands/types.d.ts +4 -0
  165. package/dist/src/cli/commands/browser-cli-helpers.js +11 -5
  166. package/dist/src/cli/commands/browser-cli-helpers.js.map +1 -1
  167. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  168. package/dist/src/cli/commands/doctor/checks/session-integrity.js +8 -8
  169. package/dist/src/cli/commands/doctor/checks/session-integrity.js.map +1 -1
  170. package/dist/src/config/rules.js +5 -0
  171. package/dist/src/config/rules.js.map +1 -1
  172. package/dist/src/connectors/builtin-catalog.js +180 -1
  173. package/dist/src/connectors/builtin-catalog.js.map +1 -1
  174. package/dist/src/connectors/health.js +18 -9
  175. package/dist/src/connectors/health.js.map +1 -1
  176. package/dist/src/connectors/types.d.ts +19 -0
  177. package/dist/src/connectors/usage.js +12 -4
  178. package/dist/src/connectors/usage.js.map +1 -1
  179. package/dist/src/extensions/types/tui.d.ts +2 -0
  180. package/dist/src/gateway/heartbeat/service.js +13 -1
  181. package/dist/src/gateway/heartbeat/service.js.map +1 -1
  182. package/dist/src/gateway/hono/app.js +13 -4
  183. package/dist/src/gateway/hono/app.js.map +1 -1
  184. package/dist/src/gateway/hono/oauth-async.js +1 -1
  185. package/dist/src/gateway/hono/routes/agent-stream.js +6 -6
  186. package/dist/src/gateway/hono/routes/agent-stream.js.map +1 -1
  187. package/dist/src/gateway/hono/routes/config-patch/gateway.d.ts +1 -1
  188. package/dist/src/gateway/hono/routes/config-patch/gateway.js +6 -0
  189. package/dist/src/gateway/hono/routes/config-patch/gateway.js.map +1 -1
  190. package/dist/src/gateway/hono/routes/goals.js +16 -3
  191. package/dist/src/gateway/hono/routes/goals.js.map +1 -1
  192. package/dist/src/gateway/hono/routes/logs.js +1 -0
  193. package/dist/src/gateway/hono/routes/logs.js.map +1 -1
  194. package/dist/src/gateway/hono/routes/mcp.js +27 -8
  195. package/dist/src/gateway/hono/routes/mcp.js.map +1 -1
  196. package/dist/src/gateway/hono/routes/notes.js +14 -2
  197. package/dist/src/gateway/hono/routes/notes.js.map +1 -1
  198. package/dist/src/gateway/hono/routes/sessions.js +62 -4
  199. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  200. package/dist/src/gateway/hono/routes/workspace.js +3 -66
  201. package/dist/src/gateway/hono/routes/workspace.js.map +1 -1
  202. package/dist/src/gateway/hono/sse.d.ts +2 -2
  203. package/dist/src/gateway/hono/sse.js +7 -15
  204. package/dist/src/gateway/hono/sse.js.map +1 -1
  205. package/dist/src/gateway/resolve-webchat-session-key.d.ts +2 -6
  206. package/dist/src/gateway/resolve-webchat-session-key.js +5 -31
  207. package/dist/src/gateway/resolve-webchat-session-key.js.map +1 -1
  208. package/dist/src/gateway/service/agent-runner.d.ts +2 -3
  209. package/dist/src/gateway/service/agent-runner.js +9 -22
  210. package/dist/src/gateway/service/agent-runner.js.map +1 -1
  211. package/dist/src/gateway/service/run-gateway-agent.js +9 -7
  212. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  213. package/dist/src/gateway/service/sessions-api.d.ts +9 -0
  214. package/dist/src/gateway/service/sessions-api.js +11 -0
  215. package/dist/src/gateway/service/sessions-api.js.map +1 -1
  216. package/dist/src/gateway/service.d.ts +1 -1
  217. package/dist/src/gateway/service.js +19 -5
  218. package/dist/src/gateway/service.js.map +1 -1
  219. package/dist/src/gateway/workspace-file-search.d.ts +9 -0
  220. package/dist/src/gateway/workspace-file-search.js +76 -0
  221. package/dist/src/gateway/workspace-file-search.js.map +1 -0
  222. package/dist/src/goals/goal-notifications.d.ts +2 -0
  223. package/dist/src/goals/goal-notifications.js +16 -11
  224. package/dist/src/goals/goal-notifications.js.map +1 -1
  225. package/dist/src/goals/goal-service.js +1 -6
  226. package/dist/src/goals/goal-service.js.map +1 -1
  227. package/dist/src/project-trust/trust-store.js +2 -0
  228. package/dist/src/project-trust/trust-store.js.map +1 -1
  229. package/dist/src/session/index.d.ts +1 -1
  230. package/dist/src/session/index.js +2 -2
  231. package/dist/src/session/init-session-turn.js +6 -9
  232. package/dist/src/session/init-session-turn.js.map +1 -1
  233. package/dist/src/session/manager.d.ts +5 -1
  234. package/dist/src/session/manager.js +5 -2
  235. package/dist/src/session/manager.js.map +1 -1
  236. package/dist/src/session/reset-type.d.ts +0 -2
  237. package/dist/src/session/reset-type.js +3 -9
  238. package/dist/src/session/reset-type.js.map +1 -1
  239. package/dist/src/session/resolve-session.js +9 -20
  240. package/dist/src/session/resolve-session.js.map +1 -1
  241. package/dist/src/session/store.d.ts +11 -3
  242. package/dist/src/session/store.js +29 -13
  243. package/dist/src/session/store.js.map +1 -1
  244. package/dist/src/session/types.d.ts +2 -4
  245. package/dist/src/session/types.js.map +1 -1
  246. package/dist/src/storage/sqlite/index.d.ts +2 -2
  247. package/dist/src/storage/sqlite/index.js +3 -3
  248. package/dist/src/storage/sqlite/migrations/010_session_id_identity.sql +30 -0
  249. package/dist/src/storage/sqlite/migrations/runner.d.ts +2 -2
  250. package/dist/src/storage/sqlite/migrations/runner.js +4 -4
  251. package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
  252. package/dist/src/storage/sqlite/migrations/runner.ts +2 -2
  253. package/dist/src/storage/sqlite/row-mappers.d.ts +4 -4
  254. package/dist/src/storage/sqlite/row-mappers.js +11 -14
  255. package/dist/src/storage/sqlite/row-mappers.js.map +1 -1
  256. package/dist/src/storage/sqlite/schema.js +3 -3
  257. package/dist/src/storage/sqlite/schema.js.map +1 -1
  258. package/dist/src/storage/sqlite/schema.sql +37 -10
  259. package/dist/src/storage/sqlite/session-metadata.d.ts +2 -7
  260. package/dist/src/storage/sqlite/session-metadata.js +18 -72
  261. package/dist/src/storage/sqlite/session-metadata.js.map +1 -1
  262. package/dist/src/storage/sqlite/session-repository.d.ts +6 -5
  263. package/dist/src/storage/sqlite/session-repository.js +34 -40
  264. package/dist/src/storage/sqlite/session-repository.js.map +1 -1
  265. package/dist/src/storage/sqlite/transcript-repository.d.ts +2 -2
  266. package/dist/src/storage/sqlite/transcript-repository.js +52 -52
  267. package/dist/src/storage/sqlite/transcript-repository.js.map +1 -1
  268. package/dist/src/tui/backends/embedded-backend.d.ts +11 -4
  269. package/dist/src/tui/backends/embedded-backend.js +27 -1
  270. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  271. package/dist/src/tui/backends/gateway-sse-backend.d.ts +4 -1
  272. package/dist/src/tui/backends/gateway-sse-backend.js +21 -2
  273. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  274. package/dist/src/tui/extension-host/autocomplete.js +13 -10
  275. package/dist/src/tui/extension-host/autocomplete.js.map +1 -1
  276. package/dist/src/tui/extension-host/runtime.d.ts +7 -0
  277. package/dist/src/tui/extension-host/runtime.js +6 -4
  278. package/dist/src/tui/extension-host/runtime.js.map +1 -1
  279. package/dist/src/tui/tui-backend.d.ts +9 -0
  280. package/dist/src/tui/tui-commands.js +85 -6
  281. package/dist/src/tui/tui-commands.js.map +1 -1
  282. package/dist/src/tui/tui-file-autocomplete.d.ts +5 -0
  283. package/dist/src/tui/tui-file-autocomplete.js +26 -0
  284. package/dist/src/tui/tui-file-autocomplete.js.map +1 -0
  285. package/dist/src/tui/tui-picker-overlay.js +1 -1
  286. package/dist/src/tui/tui.js +29 -11
  287. package/dist/src/tui/tui.js.map +1 -1
  288. package/dist/src/utils/logger/context.d.ts +2 -2
  289. package/dist/src/utils/logger/context.js +2 -1
  290. package/dist/src/utils/logger/context.js.map +1 -1
  291. package/dist/src/utils/logger/log-store.js +1 -0
  292. package/dist/src/utils/logger/log-store.js.map +1 -1
  293. package/dist/src/utils/logger/pino-record.js +3 -0
  294. package/dist/src/utils/logger/pino-record.js.map +1 -1
  295. package/dist/src/utils/logger/types.d.ts +5 -0
  296. package/dist/src/utils/logger/types.js.map +1 -1
  297. package/dist/src/workflows/service/workflow-session-bridge.js +13 -1
  298. package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -1
  299. package/package.json +1 -1
  300. package/dist/gateway/static/root/assets/agent-browser-settings-page-rGbKc2Hh.js +0 -6
  301. package/dist/gateway/static/root/assets/agent-defaults-tabbed-page-CtwVbjOA.js +0 -1
  302. package/dist/gateway/static/root/assets/agents-CCBmGcwc.js +0 -71
  303. package/dist/gateway/static/root/assets/apps-page-xwCsJS15.js +0 -1
  304. package/dist/gateway/static/root/assets/channels-settings-C044DcYk.js +0 -1
  305. package/dist/gateway/static/root/assets/connectors-settings-8VEWUOTj.js +0 -2
  306. package/dist/gateway/static/root/assets/cron-page-DqmNGNOy.js +0 -3
  307. package/dist/gateway/static/root/assets/dreaming-settings-6UZE1FSK.js +0 -1
  308. package/dist/gateway/static/root/assets/goal-detail-page-bv0VRDaf.js +0 -1
  309. package/dist/gateway/static/root/assets/goals-page-1Ywnt4Cb.js +0 -2
  310. package/dist/gateway/static/root/assets/index-Cxm6xsps.js +0 -122
  311. package/dist/gateway/static/root/assets/index-b_R4tKqt.css +0 -1
  312. package/dist/gateway/static/root/assets/logs-page-B_wrxhee.js +0 -2
  313. package/dist/gateway/static/root/assets/messages-CkT-lcgP.js +0 -3
  314. package/dist/gateway/static/root/assets/models-hub-panel-D0JDSbNu.js +0 -2
  315. package/dist/gateway/static/root/assets/note-detail-page-DpFV-EO6.js +0 -2
  316. package/dist/gateway/static/root/assets/notes-page-CdkjTch8.js +0 -1
  317. package/dist/gateway/static/root/assets/remote-access-hub-DSYVAi96.js +0 -1
  318. package/dist/gateway/static/root/assets/sessions-page-CenkcVjF.js +0 -1
  319. package/dist/gateway/static/root/assets/settings-advanced-gate-D9sva86V.js +0 -1
  320. package/dist/gateway/static/root/assets/settings-page-DL35U0x0.js +0 -1
  321. package/dist/gateway/static/root/assets/setup-status-panel-Ctwbp9Er.js +0 -1
  322. package/dist/gateway/static/root/assets/shares-settings-DIM3JNsr.js +0 -1
  323. package/dist/gateway/static/root/assets/skill-reload-api-DAlNaNzL.js +0 -1
  324. package/dist/gateway/static/root/assets/skills-page-CLrHVzAt.js +0 -2
  325. package/dist/gateway/static/root/assets/voice-api-key-field-vYAO71Gn.js +0 -1
  326. package/dist/gateway/static/root/assets/workflow-page.utils-sgHjOXOm.js +0 -1
  327. package/dist/gateway/static/root/assets/workflows-page-DYyOTmFx.js +0 -18
@@ -7,7 +7,7 @@ export interface SSEHandlerConfig {
7
7
  /**
8
8
  * POST /api/agent — Send a message to the agent, stream response via SSE.
9
9
  *
10
- * Request body: { message, channel?, chatId?, attachments? }
10
+ * Request body: { message, channel?, sessionKey, attachments? }
11
11
  * Accept: text/event-stream → SSE stream
12
12
  * Accept: application/json → wait for full response, return JSON
13
13
  *
@@ -21,7 +21,7 @@ export declare function createAgentSSEHandler(config: SSEHandlerConfig): (c: Con
21
21
  /**
22
22
  * POST /api/agent/resume — Re-attach to an in-progress agent run via SSE.
23
23
  *
24
- * Request body: { runId, chatId }
24
+ * Request body: { runId, sessionKey }
25
25
  * The relay replays all buffered events from the beginning and then live-tails
26
26
  * until the run completes.
27
27
  *
@@ -23,7 +23,7 @@ function maxBase64CharsForBinary(maxBinaryBytes) {
23
23
  /**
24
24
  * POST /api/agent — Send a message to the agent, stream response via SSE.
25
25
  *
26
- * Request body: { message, channel?, chatId?, attachments? }
26
+ * Request body: { message, channel?, sessionKey, attachments? }
27
27
  * Accept: text/event-stream → SSE stream
28
28
  * Accept: application/json → wait for full response, return JSON
29
29
  *
@@ -46,14 +46,7 @@ function createAgentSSEHandler(config) {
46
46
  }, 400);
47
47
  const { message, channel = "webchat", attachments, thinking } = body;
48
48
  const clientCreatedAtMs = typeof body.clientCreatedAtMs === "number" && Number.isFinite(body.clientCreatedAtMs) ? body.clientCreatedAtMs : void 0;
49
- const newSession = Boolean(body.newSession);
50
- const cfg = service.currentConfig;
51
- const resolved = resolveWebchatSessionKey({
52
- cfg,
53
- sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0,
54
- chatId: typeof body.chatId === "string" ? body.chatId : void 0,
55
- newSession
56
- });
49
+ const resolved = resolveWebchatSessionKey({ sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0 });
57
50
  if (resolved.ok === false) return c.json({
58
51
  ok: false,
59
52
  error: {
@@ -62,7 +55,7 @@ function createAgentSSEHandler(config) {
62
55
  }
63
56
  }, 400);
64
57
  const chatId = resolved.sessionKey;
65
- updateAsyncLogContext({ sessionId: String(chatId) });
58
+ updateAsyncLogContext({ sessionKey: String(chatId) });
66
59
  if (Array.isArray(attachments)) {
67
60
  const maxDataChars = maxBase64CharsForBinary(MAX_WEBCHAT_ATTACHMENT_FILE_BYTES);
68
61
  for (const a of attachments) {
@@ -173,7 +166,7 @@ function createAgentSSEHandler(config) {
173
166
  /**
174
167
  * POST /api/agent/resume — Re-attach to an in-progress agent run via SSE.
175
168
  *
176
- * Request body: { runId, chatId }
169
+ * Request body: { runId, sessionKey }
177
170
  * The relay replays all buffered events from the beginning and then live-tails
178
171
  * until the run completes.
179
172
  *
@@ -190,8 +183,8 @@ function createAgentResumeHandler(config) {
190
183
  message: "Invalid JSON body"
191
184
  }
192
185
  }, 400);
193
- const { runId, chatId: resumeChatId } = body;
194
- if (typeof resumeChatId === "string" && resumeChatId.trim()) updateAsyncLogContext({ sessionId: resumeChatId.trim() });
186
+ const { runId, sessionKey } = body;
187
+ if (typeof sessionKey === "string" && sessionKey.trim()) updateAsyncLogContext({ sessionKey: sessionKey.trim() });
195
188
  if (!runId || typeof runId !== "string") return c.json({
196
189
  ok: false,
197
190
  error: {
@@ -226,7 +219,7 @@ function createAgentResumeHandler(config) {
226
219
  data: stringifySSEData({
227
220
  type: "error",
228
221
  runId,
229
- sessionKey: typeof resumeChatId === "string" ? resumeChatId : "",
222
+ sessionKey: typeof sessionKey === "string" ? sessionKey : "",
230
223
  timestamp: Date.now(),
231
224
  payload: {
232
225
  code: "INTERNAL_ERROR",
@@ -255,7 +248,6 @@ function createSendHandler(config) {
255
248
  message: "Missing required fields: channel, chatId, content"
256
249
  }
257
250
  }, 400);
258
- updateAsyncLogContext({ sessionId: String(chatId) });
259
251
  try {
260
252
  const result = await service.sendMessage(channel, chatId, content);
261
253
  return c.json({
@@ -1 +1 @@
1
- {"version":3,"file":"sse.js","names":[],"sources":["../../../../src/gateway/hono/sse.ts"],"sourcesContent":["import { streamSSE } from 'hono/streaming';\nimport type { Context } from 'hono';\nimport type { GatewayService } from '../service.js';\nimport { MAX_WEBCHAT_ATTACHMENT_FILE_BYTES } from '../chat-limits.js';\nimport { createLogger, updateAsyncLogContext } from '../../utils/logger.js';\nimport { stringifySSEData } from './sse-json.js';\nimport { resolveWebchatSessionKey } from '../resolve-webchat-session-key.js';\n\nconst log = createLogger('Gateway:SSE');\n\n// Active SSE connections tracking for connection limiting\nconst activeConnections = new Map<string, AbortController>();\n\nexport interface SSEHandlerConfig {\n service: GatewayService;\n maxSseConnections?: number;\n}\n\n// Type validation for agent request body\ninterface AgentRequestBody {\n message: string;\n channel?: string;\n chatId?: string;\n /** Alias for `chatId` (gateway console + extension clients). */\n sessionKey?: string;\n /** Epoch ms when the client started this send (abort cutoff / stale POST drop). */\n clientCreatedAtMs?: number;\n /** When true and `channel` is `webchat`, start a new peer id (new session). */\n newSession?: boolean;\n thinking?: string;\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n uri?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n durationSeconds?: number;\n }>;\n}\n\nfunction isValidAgentRequest(body: unknown): body is AgentRequestBody {\n if (!body || typeof body !== 'object') return false;\n const b = body as Record<string, unknown>;\n // Allow empty message if attachments are provided\n const hasMessage = typeof b.message === 'string';\n const hasAttachments = Array.isArray(b.attachments) && b.attachments.length > 0;\n return hasMessage || hasAttachments;\n}\n\n/** Max base64 character length that can decode to `MAX_WEBCHAT_ATTACHMENT_FILE_BYTES`. */\nfunction maxBase64CharsForBinary(maxBinaryBytes: number): number {\n return 4 * Math.ceil(maxBinaryBytes / 3);\n}\n\n/**\n * POST /api/agent — Send a message to the agent, stream response via SSE.\n *\n * Request body: { message, channel?, chatId?, attachments? }\n * Accept: text/event-stream → SSE stream\n * Accept: application/json → wait for full response, return JSON\n *\n * SSE events follow XOPC Chat Stream Protocol v1:\n * run_start, user_message, user_transcript, assistant_message_start,\n * assistant_delta, thinking_delta, thinking_end, tool_start, tool_update,\n * tool_end, assistant_message_end, compaction, tts_audio, clarify_request,\n * run_end, error.\n */\nexport function createAgentSSEHandler(config: SSEHandlerConfig) {\n const { service } = config;\n\n return async (c: Context) => {\n const body = await c.req.json().catch(() => null);\n\n // Input validation\n if (!isValidAgentRequest(body)) {\n return c.json({\n ok: false,\n error: { code: 'BAD_REQUEST', message: 'Missing required field: message or attachments' }\n }, 400);\n }\n\n const { message, channel = 'webchat', attachments, thinking } = body;\n const clientCreatedAtMs =\n typeof body.clientCreatedAtMs === 'number' && Number.isFinite(body.clientCreatedAtMs)\n ? body.clientCreatedAtMs\n : undefined;\n const newSession = Boolean(body.newSession);\n const cfg = service.currentConfig;\n const resolved = resolveWebchatSessionKey({\n cfg,\n sessionKey: typeof body.sessionKey === 'string' ? body.sessionKey : undefined,\n chatId: typeof body.chatId === 'string' ? body.chatId : undefined,\n newSession,\n });\n if (resolved.ok === false) {\n return c.json({\n ok: false,\n error: { code: 'BAD_REQUEST', message: resolved.error },\n }, 400);\n }\n const chatId = resolved.sessionKey;\n\n updateAsyncLogContext({ sessionId: String(chatId) });\n\n if (Array.isArray(attachments)) {\n const maxDataChars = maxBase64CharsForBinary(MAX_WEBCHAT_ATTACHMENT_FILE_BYTES);\n for (const a of attachments) {\n if (!a || typeof a !== 'object') continue;\n const data = (a as { data?: unknown }).data;\n if (typeof data === 'string' && data.length > maxDataChars) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'BAD_REQUEST',\n message: `Attachment exceeds maximum size (${MAX_WEBCHAT_ATTACHMENT_FILE_BYTES} bytes)`,\n },\n },\n 400,\n );\n }\n }\n }\n\n const accept = c.req.header('Accept') || '';\n const wantSSE = accept.includes('text/event-stream');\n\n const clientAbort = new AbortController();\n const raw = c.req.raw;\n // Keep webchat runs alive across transient disconnects (page refresh / tab route switch)\n // so the client can reattach via /api/agent/resume using runId from `run_start`.\n // Explicit cancellation still goes through /api/agent/abort.\n if (channel !== 'webchat') {\n if (raw.signal.aborted) {\n clientAbort.abort();\n } else {\n raw.signal.addEventListener('abort', () => clientAbort.abort(), { once: true });\n }\n }\n\n // --- Non-streaming fallback: collect everything, return JSON ---\n if (!wantSSE) {\n const jsonSessionKey = channel === 'webchat' ? chatId : undefined;\n\n const generator = service.runAgent(message, channel, chatId, attachments, thinking, {\n signal: clientAbort.signal,\n ...(clientCreatedAtMs !== undefined ? { clientCreatedAtMs } : {}),\n });\n try {\n let finalResult: { status: string; summary: string } | undefined;\n const tokens: string[] = [];\n\n while (true) {\n const { done, value } = await generator.next();\n if (done) {\n finalResult = value as { status: string; summary: string };\n break;\n }\n const chunk = value as { type: string; payload?: { delta?: unknown } };\n if (chunk.type === 'assistant_delta' && typeof chunk.payload?.delta === 'string') {\n tokens.push(chunk.payload.delta);\n }\n }\n\n return c.json({\n ok: true,\n payload: {\n ...finalResult,\n content: tokens.join(''),\n ...(jsonSessionKey !== undefined\n ? { sessionKey: jsonSessionKey, key: jsonSessionKey }\n : {}),\n },\n });\n } catch (error) {\n const em = error instanceof Error ? error.message : String(error);\n log.error(\n { err: error, errorMessage: em, phase: 'gateway.agent_run', sessionKey: jsonSessionKey, channel },\n `Agent run failed (JSON mode): ${em}`,\n );\n return c.json({\n ok: false,\n error: { code: 'INTERNAL_ERROR', message: error instanceof Error ? error.message : 'Unknown error' },\n }, 500);\n }\n }\n\n // --- SSE streaming ---\n c.header('X-Accel-Buffering', 'no');\n return streamSSE(c, async (stream) => {\n if (channel !== 'webchat') {\n stream.onAbort(() => {\n clientAbort.abort();\n });\n }\n\n const generator = service.runAgent(message, channel, chatId, attachments, thinking, {\n signal: clientAbort.signal,\n ...(clientCreatedAtMs !== undefined ? { clientCreatedAtMs } : {}),\n });\n\n let eventId = 0;\n\n try {\n while (true) {\n const { done, value } = await generator.next();\n\n if (done) break;\n\n const chunk = value as { type: string; seq?: unknown };\n await stream.writeSSE({\n id: typeof chunk.seq === 'number' ? String(chunk.seq) : String(++eventId),\n event: chunk.type || 'message',\n data: stringifySSEData(chunk),\n });\n }\n } catch (error) {\n log.error({ err: error }, 'Agent run failed (SSE mode)');\n await stream.writeSSE({\n id: String(++eventId),\n event: 'error',\n data: stringifySSEData({\n type: 'error',\n runId: 'unknown',\n sessionKey: chatId,\n timestamp: Date.now(),\n payload: { code: 'INTERNAL_ERROR', message: error instanceof Error ? error.message : 'Unknown error' },\n }),\n });\n }\n });\n };\n}\n\n/**\n * POST /api/agent/resume — Re-attach to an in-progress agent run via SSE.\n *\n * Request body: { runId, chatId }\n * The relay replays all buffered events from the beginning and then live-tails\n * until the run completes.\n *\n * SSE events are identical to those from POST /api/agent.\n */\nexport function createAgentResumeHandler(config: SSEHandlerConfig) {\n const { service } = config;\n\n return async (c: Context) => {\n const body = await c.req.json().catch(() => null);\n if (!body || typeof body !== 'object') {\n return c.json({ ok: false, error: { code: 'BAD_REQUEST', message: 'Invalid JSON body' } }, 400);\n }\n\n const { runId, chatId: resumeChatId } = body as { runId?: string; chatId?: string };\n if (typeof resumeChatId === 'string' && resumeChatId.trim()) {\n updateAsyncLogContext({ sessionId: resumeChatId.trim() });\n }\n if (!runId || typeof runId !== 'string') {\n return c.json({ ok: false, error: { code: 'BAD_REQUEST', message: 'Missing required field: runId' } }, 400);\n }\n\n if (!service.runRelay.hasRun(runId)) {\n return c.json({ ok: false, error: { code: 'NOT_FOUND', message: 'Run not found or already expired' } }, 404);\n }\n\n c.header('X-Accel-Buffering', 'no');\n return streamSSE(c, async (stream) => {\n let eventId = 0;\n try {\n for await (const event of service.runRelay.subscribe(runId)) {\n await stream.writeSSE({\n id: typeof event.seq === 'number' ? String(event.seq) : String(++eventId),\n event: event.type || 'message',\n data: stringifySSEData(event),\n });\n }\n } catch (error) {\n log.error({ err: error, runId }, 'Resume stream failed');\n await stream.writeSSE({\n id: String(++eventId),\n event: 'error',\n data: stringifySSEData({\n type: 'error',\n runId,\n sessionKey: typeof resumeChatId === 'string' ? resumeChatId : '',\n timestamp: Date.now(),\n payload: { code: 'INTERNAL_ERROR', message: error instanceof Error ? error.message : 'Unknown error' },\n }),\n });\n }\n });\n };\n}\n\n/**\n * POST /api/send — Send a message through a channel (non-streaming).\n */\nexport function createSendHandler(config: SSEHandlerConfig) {\n const { service } = config;\n\n return async (c: Context) => {\n const body = await c.req.json().catch(() => ({}));\n const channel = body.channel as string;\n const chatId = body.chatId as string;\n const content = body.content as string;\n\n if (!channel || !chatId || !content) {\n return c.json(\n { ok: false, error: { code: 'BAD_REQUEST', message: 'Missing required fields: channel, chatId, content' } },\n 400,\n );\n }\n\n updateAsyncLogContext({ sessionId: String(chatId) });\n\n try {\n const result = await service.sendMessage(channel, chatId, content);\n return c.json({ ok: true, payload: result });\n } catch (error) {\n log.error({ err: error }, 'Send failed');\n return c.json(\n { ok: false, error: { code: 'INTERNAL_ERROR', message: error instanceof Error ? error.message : 'Unknown error' } },\n 500,\n );\n }\n };\n}\n\n/**\n * GET /api/events — Server-pushed event stream (SSE).\n *\n * The client opens this long-lived connection to receive:\n * - channel status changes\n * - config reload notifications\n * - cron execution results\n * - any other server-initiated events\n *\n * Supports Last-Event-ID for reconnection.\n * Enforces maximum connection limit to prevent DoS.\n */\nexport function createEventsSSEHandler(config: SSEHandlerConfig) {\n const { service } = config;\n const maxConnections = config.maxSseConnections ?? 100;\n\n return async (c: Context) => {\n // Check maximum connections limit\n if (activeConnections.size >= maxConnections) {\n log.warn({ current: activeConnections.size, max: maxConnections }, 'SSE connection limit reached');\n return c.json({\n ok: false,\n error: { code: 'TOO_MANY_CONNECTIONS', message: 'Maximum SSE connections exceeded' }\n }, 503);\n }\n\n const lastEventId = c.req.header('Last-Event-ID') || undefined;\n const sessionId = c.req.header('X-Session-Id')\n || c.req.query('sessionId')\n || crypto.randomUUID();\n\n updateAsyncLogContext({ sessionId: String(sessionId) });\n\n const abortController = new AbortController();\n activeConnections.set(sessionId, abortController);\n\n return streamSSE(c, async (stream) => {\n let aborted = false;\n\n // Send a hello event so the client knows the stream is established\n await stream.writeSSE({\n id: '0',\n event: 'connected',\n data: JSON.stringify({ sessionId }),\n });\n\n // Subscribe to service events\n const cleanup = service.subscribe(sessionId, async (event) => {\n if (aborted) return;\n try {\n await stream.writeSSE({\n id: event.id,\n event: event.type,\n data: JSON.stringify(event.payload),\n });\n } catch {\n // Stream closed, will be cleaned up by onAbort\n }\n });\n\n // Replay missed events on reconnect\n if (lastEventId) {\n const missed = service.getEventsSince(sessionId, lastEventId);\n for (const event of missed) {\n await stream.writeSSE({\n id: event.id,\n event: event.type,\n data: JSON.stringify(event.payload),\n });\n }\n }\n\n // Keep alive with periodic comments (every 30s)\n const keepAlive = setInterval(async () => {\n if (aborted) { clearInterval(keepAlive); return; }\n try {\n await stream.writeSSE({ event: 'ping', data: '' });\n } catch {\n clearInterval(keepAlive);\n }\n }, 30_000);\n\n // Block until aborted — streamSSE closes when the callback returns\n await new Promise<void>((resolve) => {\n stream.onAbort(() => {\n aborted = true;\n clearInterval(keepAlive);\n cleanup();\n activeConnections.delete(sessionId);\n log.debug({ sessionId }, 'Event stream disconnected');\n resolve();\n });\n });\n });\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sse.js","names":[],"sources":["../../../../src/gateway/hono/sse.ts"],"sourcesContent":["import { streamSSE } from 'hono/streaming';\nimport type { Context } from 'hono';\nimport type { GatewayService } from '../service.js';\nimport { MAX_WEBCHAT_ATTACHMENT_FILE_BYTES } from '../chat-limits.js';\nimport { createLogger, updateAsyncLogContext } from '../../utils/logger.js';\nimport { stringifySSEData } from './sse-json.js';\nimport { resolveWebchatSessionKey } from '../resolve-webchat-session-key.js';\n\nconst log = createLogger('Gateway:SSE');\n\n// Active SSE connections tracking for connection limiting\nconst activeConnections = new Map<string, AbortController>();\n\nexport interface SSEHandlerConfig {\n service: GatewayService;\n maxSseConnections?: number;\n}\n\n// Type validation for agent request body\ninterface AgentRequestBody {\n message: string;\n channel?: string;\n sessionKey?: string;\n /** Epoch ms when the client started this send (abort cutoff / stale POST drop). */\n clientCreatedAtMs?: number;\n thinking?: string;\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n uri?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n durationSeconds?: number;\n }>;\n}\n\nfunction isValidAgentRequest(body: unknown): body is AgentRequestBody {\n if (!body || typeof body !== 'object') return false;\n const b = body as Record<string, unknown>;\n // Allow empty message if attachments are provided\n const hasMessage = typeof b.message === 'string';\n const hasAttachments = Array.isArray(b.attachments) && b.attachments.length > 0;\n return hasMessage || hasAttachments;\n}\n\n/** Max base64 character length that can decode to `MAX_WEBCHAT_ATTACHMENT_FILE_BYTES`. */\nfunction maxBase64CharsForBinary(maxBinaryBytes: number): number {\n return 4 * Math.ceil(maxBinaryBytes / 3);\n}\n\n/**\n * POST /api/agent — Send a message to the agent, stream response via SSE.\n *\n * Request body: { message, channel?, sessionKey, attachments? }\n * Accept: text/event-stream → SSE stream\n * Accept: application/json → wait for full response, return JSON\n *\n * SSE events follow XOPC Chat Stream Protocol v1:\n * run_start, user_message, user_transcript, assistant_message_start,\n * assistant_delta, thinking_delta, thinking_end, tool_start, tool_update,\n * tool_end, assistant_message_end, compaction, tts_audio, clarify_request,\n * run_end, error.\n */\nexport function createAgentSSEHandler(config: SSEHandlerConfig) {\n const { service } = config;\n\n return async (c: Context) => {\n const body = await c.req.json().catch(() => null);\n\n // Input validation\n if (!isValidAgentRequest(body)) {\n return c.json({\n ok: false,\n error: { code: 'BAD_REQUEST', message: 'Missing required field: message or attachments' }\n }, 400);\n }\n\n const { message, channel = 'webchat', attachments, thinking } = body;\n const clientCreatedAtMs =\n typeof body.clientCreatedAtMs === 'number' && Number.isFinite(body.clientCreatedAtMs)\n ? body.clientCreatedAtMs\n : undefined;\n const resolved = resolveWebchatSessionKey({\n sessionKey: typeof body.sessionKey === 'string' ? body.sessionKey : undefined,\n });\n if (resolved.ok === false) {\n return c.json({\n ok: false,\n error: { code: 'BAD_REQUEST', message: resolved.error },\n }, 400);\n }\n const chatId = resolved.sessionKey;\n\n updateAsyncLogContext({ sessionKey: String(chatId) });\n\n if (Array.isArray(attachments)) {\n const maxDataChars = maxBase64CharsForBinary(MAX_WEBCHAT_ATTACHMENT_FILE_BYTES);\n for (const a of attachments) {\n if (!a || typeof a !== 'object') continue;\n const data = (a as { data?: unknown }).data;\n if (typeof data === 'string' && data.length > maxDataChars) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'BAD_REQUEST',\n message: `Attachment exceeds maximum size (${MAX_WEBCHAT_ATTACHMENT_FILE_BYTES} bytes)`,\n },\n },\n 400,\n );\n }\n }\n }\n\n const accept = c.req.header('Accept') || '';\n const wantSSE = accept.includes('text/event-stream');\n\n const clientAbort = new AbortController();\n const raw = c.req.raw;\n // Keep webchat runs alive across transient disconnects (page refresh / tab route switch)\n // so the client can reattach via /api/agent/resume using runId from `run_start`.\n // Explicit cancellation still goes through /api/agent/abort.\n if (channel !== 'webchat') {\n if (raw.signal.aborted) {\n clientAbort.abort();\n } else {\n raw.signal.addEventListener('abort', () => clientAbort.abort(), { once: true });\n }\n }\n\n // --- Non-streaming fallback: collect everything, return JSON ---\n if (!wantSSE) {\n const jsonSessionKey = channel === 'webchat' ? chatId : undefined;\n\n const generator = service.runAgent(message, channel, chatId, attachments, thinking, {\n signal: clientAbort.signal,\n ...(clientCreatedAtMs !== undefined ? { clientCreatedAtMs } : {}),\n });\n try {\n let finalResult: { status: string; summary: string } | undefined;\n const tokens: string[] = [];\n\n while (true) {\n const { done, value } = await generator.next();\n if (done) {\n finalResult = value as { status: string; summary: string };\n break;\n }\n const chunk = value as { type: string; payload?: { delta?: unknown } };\n if (chunk.type === 'assistant_delta' && typeof chunk.payload?.delta === 'string') {\n tokens.push(chunk.payload.delta);\n }\n }\n\n return c.json({\n ok: true,\n payload: {\n ...finalResult,\n content: tokens.join(''),\n ...(jsonSessionKey !== undefined\n ? { sessionKey: jsonSessionKey, key: jsonSessionKey }\n : {}),\n },\n });\n } catch (error) {\n const em = error instanceof Error ? error.message : String(error);\n log.error(\n { err: error, errorMessage: em, phase: 'gateway.agent_run', sessionKey: jsonSessionKey, channel },\n `Agent run failed (JSON mode): ${em}`,\n );\n return c.json({\n ok: false,\n error: { code: 'INTERNAL_ERROR', message: error instanceof Error ? error.message : 'Unknown error' },\n }, 500);\n }\n }\n\n // --- SSE streaming ---\n c.header('X-Accel-Buffering', 'no');\n return streamSSE(c, async (stream) => {\n if (channel !== 'webchat') {\n stream.onAbort(() => {\n clientAbort.abort();\n });\n }\n\n const generator = service.runAgent(message, channel, chatId, attachments, thinking, {\n signal: clientAbort.signal,\n ...(clientCreatedAtMs !== undefined ? { clientCreatedAtMs } : {}),\n });\n\n let eventId = 0;\n\n try {\n while (true) {\n const { done, value } = await generator.next();\n\n if (done) break;\n\n const chunk = value as { type: string; seq?: unknown };\n await stream.writeSSE({\n id: typeof chunk.seq === 'number' ? String(chunk.seq) : String(++eventId),\n event: chunk.type || 'message',\n data: stringifySSEData(chunk),\n });\n }\n } catch (error) {\n log.error({ err: error }, 'Agent run failed (SSE mode)');\n await stream.writeSSE({\n id: String(++eventId),\n event: 'error',\n data: stringifySSEData({\n type: 'error',\n runId: 'unknown',\n sessionKey: chatId,\n timestamp: Date.now(),\n payload: { code: 'INTERNAL_ERROR', message: error instanceof Error ? error.message : 'Unknown error' },\n }),\n });\n }\n });\n };\n}\n\n/**\n * POST /api/agent/resume — Re-attach to an in-progress agent run via SSE.\n *\n * Request body: { runId, sessionKey }\n * The relay replays all buffered events from the beginning and then live-tails\n * until the run completes.\n *\n * SSE events are identical to those from POST /api/agent.\n */\nexport function createAgentResumeHandler(config: SSEHandlerConfig) {\n const { service } = config;\n\n return async (c: Context) => {\n const body = await c.req.json().catch(() => null);\n if (!body || typeof body !== 'object') {\n return c.json({ ok: false, error: { code: 'BAD_REQUEST', message: 'Invalid JSON body' } }, 400);\n }\n\n const { runId, sessionKey } = body as { runId?: string; sessionKey?: string };\n if (typeof sessionKey === 'string' && sessionKey.trim()) {\n updateAsyncLogContext({ sessionKey: sessionKey.trim() });\n }\n if (!runId || typeof runId !== 'string') {\n return c.json({ ok: false, error: { code: 'BAD_REQUEST', message: 'Missing required field: runId' } }, 400);\n }\n\n if (!service.runRelay.hasRun(runId)) {\n return c.json({ ok: false, error: { code: 'NOT_FOUND', message: 'Run not found or already expired' } }, 404);\n }\n\n c.header('X-Accel-Buffering', 'no');\n return streamSSE(c, async (stream) => {\n let eventId = 0;\n try {\n for await (const event of service.runRelay.subscribe(runId)) {\n await stream.writeSSE({\n id: typeof event.seq === 'number' ? String(event.seq) : String(++eventId),\n event: event.type || 'message',\n data: stringifySSEData(event),\n });\n }\n } catch (error) {\n log.error({ err: error, runId }, 'Resume stream failed');\n await stream.writeSSE({\n id: String(++eventId),\n event: 'error',\n data: stringifySSEData({\n type: 'error',\n runId,\n sessionKey: typeof sessionKey === 'string' ? sessionKey : '',\n timestamp: Date.now(),\n payload: { code: 'INTERNAL_ERROR', message: error instanceof Error ? error.message : 'Unknown error' },\n }),\n });\n }\n });\n };\n}\n\n/**\n * POST /api/send — Send a message through a channel (non-streaming).\n */\nexport function createSendHandler(config: SSEHandlerConfig) {\n const { service } = config;\n\n return async (c: Context) => {\n const body = await c.req.json().catch(() => ({}));\n const channel = body.channel as string;\n const chatId = body.chatId as string;\n const content = body.content as string;\n\n if (!channel || !chatId || !content) {\n return c.json(\n { ok: false, error: { code: 'BAD_REQUEST', message: 'Missing required fields: channel, chatId, content' } },\n 400,\n );\n }\n\n try {\n const result = await service.sendMessage(channel, chatId, content);\n return c.json({ ok: true, payload: result });\n } catch (error) {\n log.error({ err: error }, 'Send failed');\n return c.json(\n { ok: false, error: { code: 'INTERNAL_ERROR', message: error instanceof Error ? error.message : 'Unknown error' } },\n 500,\n );\n }\n };\n}\n\n/**\n * GET /api/events — Server-pushed event stream (SSE).\n *\n * The client opens this long-lived connection to receive:\n * - channel status changes\n * - config reload notifications\n * - cron execution results\n * - any other server-initiated events\n *\n * Supports Last-Event-ID for reconnection.\n * Enforces maximum connection limit to prevent DoS.\n */\nexport function createEventsSSEHandler(config: SSEHandlerConfig) {\n const { service } = config;\n const maxConnections = config.maxSseConnections ?? 100;\n\n return async (c: Context) => {\n // Check maximum connections limit\n if (activeConnections.size >= maxConnections) {\n log.warn({ current: activeConnections.size, max: maxConnections }, 'SSE connection limit reached');\n return c.json({\n ok: false,\n error: { code: 'TOO_MANY_CONNECTIONS', message: 'Maximum SSE connections exceeded' }\n }, 503);\n }\n\n const lastEventId = c.req.header('Last-Event-ID') || undefined;\n const sessionId = c.req.header('X-Session-Id')\n || c.req.query('sessionId')\n || crypto.randomUUID();\n\n updateAsyncLogContext({ sessionId: String(sessionId) });\n\n const abortController = new AbortController();\n activeConnections.set(sessionId, abortController);\n\n return streamSSE(c, async (stream) => {\n let aborted = false;\n\n // Send a hello event so the client knows the stream is established\n await stream.writeSSE({\n id: '0',\n event: 'connected',\n data: JSON.stringify({ sessionId }),\n });\n\n // Subscribe to service events\n const cleanup = service.subscribe(sessionId, async (event) => {\n if (aborted) return;\n try {\n await stream.writeSSE({\n id: event.id,\n event: event.type,\n data: JSON.stringify(event.payload),\n });\n } catch {\n // Stream closed, will be cleaned up by onAbort\n }\n });\n\n // Replay missed events on reconnect\n if (lastEventId) {\n const missed = service.getEventsSince(sessionId, lastEventId);\n for (const event of missed) {\n await stream.writeSSE({\n id: event.id,\n event: event.type,\n data: JSON.stringify(event.payload),\n });\n }\n }\n\n // Keep alive with periodic comments (every 30s)\n const keepAlive = setInterval(async () => {\n if (aborted) { clearInterval(keepAlive); return; }\n try {\n await stream.writeSSE({ event: 'ping', data: '' });\n } catch {\n clearInterval(keepAlive);\n }\n }, 30_000);\n\n // Block until aborted — streamSSE closes when the callback returns\n await new Promise<void>((resolve) => {\n stream.onAbort(() => {\n aborted = true;\n clearInterval(keepAlive);\n cleanup();\n activeConnections.delete(sessionId);\n log.debug({ sessionId }, 'Event stream disconnected');\n resolve();\n });\n });\n });\n };\n}\n"],"mappings":"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"}
@@ -1,9 +1,5 @@
1
- import type { Config } from '../config/schema.js';
2
1
  export type ResolveWebchatSessionKeyInput = {
3
- cfg: Config;
4
2
  sessionKey?: string;
5
- chatId?: string;
6
- newSession?: boolean;
7
3
  };
8
4
  export type ResolveWebchatSessionKeyResult = {
9
5
  ok: true;
@@ -13,7 +9,7 @@ export type ResolveWebchatSessionKeyResult = {
13
9
  error: string;
14
10
  };
15
11
  /**
16
- * Resolve the canonical `agent:` session key for webchat `/api/agent` requests.
17
- * Rejects bare legacy peer ids (e.g. `chat_*` without `agent:` prefix).
12
+ * Resolve the server-owned session key for webchat `/api/agent` requests.
13
+ * Creation is intentionally out-of-band via `POST /api/sessions`.
18
14
  */
19
15
  export declare function resolveWebchatSessionKey(input: ResolveWebchatSessionKeyInput): ResolveWebchatSessionKeyResult;
@@ -1,39 +1,13 @@
1
- import { buildAgentMainSessionKey, init_agent_session_key, normalizeMainKey } from "../routing/agent-session-key.js";
2
- import { buildSessionKey, init_session_key, parseSessionKey } from "../routing/session-key.js";
3
- import { getDefaultAgentId, init_resolve_route } from "../routing/resolve-route.js";
4
- import { randomUUID } from "node:crypto";
5
1
  //#region src/gateway/resolve-webchat-session-key.ts
6
- init_agent_session_key();
7
- init_session_key();
8
- init_resolve_route();
9
2
  /**
10
- * Resolve the canonical `agent:` session key for webchat `/api/agent` requests.
11
- * Rejects bare legacy peer ids (e.g. `chat_*` without `agent:` prefix).
3
+ * Resolve the server-owned session key for webchat `/api/agent` requests.
4
+ * Creation is intentionally out-of-band via `POST /api/sessions`.
12
5
  */
13
6
  function resolveWebchatSessionKey(input) {
14
- const agentId = getDefaultAgentId(input.cfg);
15
- const mainKey = normalizeMainKey(input.cfg.session?.mainKey);
16
- if (input.newSession) return {
17
- ok: true,
18
- sessionKey: buildSessionKey({
19
- agentId,
20
- source: "webchat",
21
- accountId: "default",
22
- peerKind: "direct",
23
- peerId: `chat_${randomUUID()}`
24
- })
25
- };
26
- const raw = (input.sessionKey?.trim() || input.chatId?.trim() || "").trim();
27
- if (!raw || raw === "default") return {
28
- ok: true,
29
- sessionKey: buildAgentMainSessionKey({
30
- agentId,
31
- mainKey
32
- })
33
- };
34
- if (!parseSessionKey(raw)) return {
7
+ const raw = input.sessionKey?.trim() ?? "";
8
+ if (!raw) return {
35
9
  ok: false,
36
- error: "sessionKey must use agent:{agentId}:{rest} format; create sessions via POST /api/sessions"
10
+ error: "Missing sessionKey; create sessions via POST /api/sessions"
37
11
  };
38
12
  return {
39
13
  ok: true,
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-webchat-session-key.js","names":[],"sources":["../../../src/gateway/resolve-webchat-session-key.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { Config } from '../config/schema.js';\nimport {\n buildAgentMainSessionKey,\n normalizeMainKey,\n} from '../routing/agent-session-key.js';\nimport { buildSessionKey, parseSessionKey } from '../routing/session-key.js';\nimport { getDefaultAgentId } from '../routing/resolve-route.js';\n\nexport type ResolveWebchatSessionKeyInput = {\n cfg: Config;\n sessionKey?: string;\n chatId?: string;\n newSession?: boolean;\n};\n\nexport type ResolveWebchatSessionKeyResult =\n | { ok: true; sessionKey: string }\n | { ok: false; error: string };\n\n/**\n * Resolve the canonical `agent:` session key for webchat `/api/agent` requests.\n * Rejects bare legacy peer ids (e.g. `chat_*` without `agent:` prefix).\n */\nexport function resolveWebchatSessionKey(\n input: ResolveWebchatSessionKeyInput,\n): ResolveWebchatSessionKeyResult {\n const agentId = getDefaultAgentId(input.cfg);\n const mainKey = normalizeMainKey(input.cfg.session?.mainKey);\n\n if (input.newSession) {\n return {\n ok: true,\n sessionKey: buildSessionKey({\n agentId,\n source: 'webchat',\n accountId: 'default',\n peerKind: 'direct',\n peerId: `chat_${randomUUID()}`,\n }),\n };\n }\n\n const raw = (input.sessionKey?.trim() || input.chatId?.trim() || '').trim();\n if (!raw || raw === 'default') {\n return { ok: true, sessionKey: buildAgentMainSessionKey({ agentId, mainKey }) };\n }\n\n if (!parseSessionKey(raw)) {\n return {\n ok: false,\n error: 'sessionKey must use agent:{agentId}:{rest} format; create sessions via POST /api/sessions',\n };\n }\n\n return { ok: true, sessionKey: raw };\n}\n"],"mappings":";;;;;wBAMyC;kBACoC;oBACb;;;;;AAiBhE,SAAgB,yBACd,OACgC;CAChC,MAAM,UAAU,kBAAkB,MAAM,IAAI;CAC5C,MAAM,UAAU,iBAAiB,MAAM,IAAI,SAAS,QAAQ;AAE5D,KAAI,MAAM,WACR,QAAO;EACL,IAAI;EACJ,YAAY,gBAAgB;GAC1B;GACA,QAAQ;GACR,WAAW;GACX,UAAU;GACV,QAAQ,QAAQ,YAAY;GAC7B,CAAC;EACH;CAGH,MAAM,OAAO,MAAM,YAAY,MAAM,IAAI,MAAM,QAAQ,MAAM,IAAI,IAAI,MAAM;AAC3E,KAAI,CAAC,OAAO,QAAQ,UAClB,QAAO;EAAE,IAAI;EAAM,YAAY,yBAAyB;GAAE;GAAS;GAAS,CAAC;EAAE;AAGjF,KAAI,CAAC,gBAAgB,IAAI,CACvB,QAAO;EACL,IAAI;EACJ,OAAO;EACR;AAGH,QAAO;EAAE,IAAI;EAAM,YAAY;EAAK"}
1
+ {"version":3,"file":"resolve-webchat-session-key.js","names":[],"sources":["../../../src/gateway/resolve-webchat-session-key.ts"],"sourcesContent":["export type ResolveWebchatSessionKeyInput = {\n sessionKey?: string;\n};\n\nexport type ResolveWebchatSessionKeyResult =\n | { ok: true; sessionKey: string }\n | { ok: false; error: string };\n\n/**\n * Resolve the server-owned session key for webchat `/api/agent` requests.\n * Creation is intentionally out-of-band via `POST /api/sessions`.\n */\nexport function resolveWebchatSessionKey(\n input: ResolveWebchatSessionKeyInput,\n): ResolveWebchatSessionKeyResult {\n const raw = input.sessionKey?.trim() ?? '';\n if (!raw) return { ok: false, error: 'Missing sessionKey; create sessions via POST /api/sessions' };\n return { ok: true, sessionKey: raw };\n}\n"],"mappings":";;;;;AAYA,SAAgB,yBACd,OACgC;CAChC,MAAM,MAAM,MAAM,YAAY,MAAM,IAAI;AACxC,KAAI,CAAC,IAAK,QAAO;EAAE,IAAI;EAAO,OAAO;EAA8D;AACnG,QAAO;EAAE,IAAI;EAAM,YAAY;EAAK"}
@@ -73,10 +73,9 @@ export declare class GatewayAgentRunner {
73
73
  abortAgentRun(runId: string): boolean;
74
74
  /**
75
75
  * Queue steering text for an active webchat run (`Agent.steer` /
76
- * tool-boundary injection). `chatId` is the same as `POST /api/agent` body
77
- * (`sessionKey` or legacy peer id).
76
+ * tool-boundary injection). The caller must pass the session key.
78
77
  */
79
- steerWebchatAgent(chatId: string, message: string): Promise<{
78
+ steerWebchatAgent(sessionKey: string, message: string): Promise<{
80
79
  ok: true;
81
80
  } | {
82
81
  ok: false;
@@ -1,13 +1,9 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
- import { buildSessionKey, init_session_key, parseSessionKey } from "../../routing/session-key.js";
4
- import { getDefaultAgentId, init_resolve_route } from "../../routing/resolve-route.js";
5
3
  import { AgentRunRelay } from "../agent-run-relay.js";
6
4
  import { ClarifyBridge } from "../clarify-bridge.js";
7
5
  import { runGatewayAgent } from "./run-gateway-agent.js";
8
6
  //#region src/gateway/service/agent-runner.ts
9
- init_session_key();
10
- init_resolve_route();
11
7
  init_logger();
12
8
  const log = createLogger("Gateway:AgentRunner");
13
9
  var GatewayAgentRunner = class {
@@ -80,23 +76,14 @@ var GatewayAgentRunner = class {
80
76
  }
81
77
  /**
82
78
  * Queue steering text for an active webchat run (`Agent.steer` /
83
- * tool-boundary injection). `chatId` is the same as `POST /api/agent` body
84
- * (`sessionKey` or legacy peer id).
79
+ * tool-boundary injection). The caller must pass the session key.
85
80
  */
86
- async steerWebchatAgent(chatId, message) {
81
+ async steerWebchatAgent(sessionKey, message) {
87
82
  const trimmed = message.trim();
88
83
  if (!trimmed) return {
89
84
  ok: false,
90
85
  code: "BAD_REQUEST"
91
86
  };
92
- const cfg = this.opts.getConfig();
93
- const sessionKey = parseSessionKey(chatId) ? chatId : buildSessionKey({
94
- agentId: getDefaultAgentId(cfg),
95
- source: "webchat",
96
- accountId: "default",
97
- peerKind: "direct",
98
- peerId: chatId
99
- });
100
87
  if (!this.activeWebchatRunBySession.has(sessionKey)) return {
101
88
  ok: false,
102
89
  code: "NO_ACTIVE_RUN"
@@ -142,11 +129,11 @@ var GatewayAgentRunner = class {
142
129
  * `turnDispatcher.enqueueWebchatSseEvent`. We take it as a callback so the
143
130
  * runner does not import AgentService statically.
144
131
  */
145
- requestClarification(opts) {
132
+ async requestClarification(opts) {
146
133
  const { sessionKey, request, publishSseFor } = opts;
147
134
  const runId = this.activeWebchatRunBySession.get(sessionKey);
148
135
  const publishSse = runId ? publishSseFor(runId) : void 0;
149
- const deliver = parseSessionKey(sessionKey)?.source === "telegram" ? async (ctx) => {
136
+ const deliver = ((await this.opts.sessionIndex.getSessionMetadata(sessionKey).catch(() => null))?.routing)?.source === "telegram" ? async (ctx) => {
150
137
  await this.deliverTelegramClarify(ctx);
151
138
  } : void 0;
152
139
  if (!runId && !deliver) return Promise.reject(/* @__PURE__ */ new Error("Clarify is not available for this session (use webchat, Telegram, or CLI)"));
@@ -160,8 +147,8 @@ var GatewayAgentRunner = class {
160
147
  });
161
148
  }
162
149
  async deliverTelegramClarify(ctx) {
163
- const parsed = parseSessionKey(ctx.sessionKey);
164
- if (!parsed || parsed.source !== "telegram") return;
150
+ const routing = (await this.opts.sessionIndex.getSessionMetadata(ctx.sessionKey).catch(() => null))?.routing;
151
+ if (!routing || routing.source !== "telegram") return;
165
152
  let body = ctx.request.question;
166
153
  if (ctx.request.default) body += `\n\nDefault if unsure: ${ctx.request.default}`;
167
154
  const choices = ctx.request.choices;
@@ -172,11 +159,11 @@ var GatewayAgentRunner = class {
172
159
  if (!buttonRows) body += "\n\nReply with your answer in this chat.";
173
160
  await this.opts.getChannelManager().send({
174
161
  channel: "telegram",
175
- chat_id: parsed.peerId,
162
+ chat_id: routing.peerId,
176
163
  content: body,
177
164
  metadata: {
178
- accountId: parsed.accountId,
179
- ...parsed.threadId ? { threadId: parsed.threadId } : {}
165
+ accountId: routing.accountId,
166
+ ...routing.threadId ? { threadId: routing.threadId } : {}
180
167
  },
181
168
  buttons: buttonRows
182
169
  });
@@ -1 +1 @@
1
- {"version":3,"file":"agent-runner.js","names":[],"sources":["../../../../src/gateway/service/agent-runner.ts"],"sourcesContent":["/**\n * GatewayAgentRunner — webchat agent invocation and the surrounding control\n * surface (abort, steer, clarify-bridge plumbing, scheduled continuations).\n *\n * Was 200 lines of `GatewayService` covering seven concerns that all hung off\n * the same handful of fields (`activeWebchatRunBySession`, `runAbortControllers`,\n * `clarifyBridge`, `runRelay`):\n *\n * - `runAgent(message, channel, chatId, ...)` — wraps {@link runGatewayAgent}\n * - `abortAgentRun(runId)` — POST /api/agent/abort + cleanup\n * - `steerWebchatAgent(chatId, message)` — Agent.steer queue at tool boundary\n * - `submitClarifyResponse(requestId, answer)` — UI answers a `clarify` call\n * - `enqueueWebchatPersistentGoalKickoff(sessionKey, goalText)` — initial\n * `/goal` kickoff posts the goal text as the next user turn\n * - `drainScheduledWebchatContinuation(sk, msg)` — background continuation\n * (extension scheduler + persistent-goal flow)\n * - `clarifyForSession({ sessionKey, request })` — clarify-bridge dispatch\n * used by `gatewayClarify.requestClarification` in AgentService\n *\n * Owns the two state maps (`activeWebchatRunBySession`, `runAbortControllers`)\n * directly so peer coordinators (sessions-api, marketplace, config) cannot\n * accidentally mutate them.\n */\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport type { AgentService } from '../../agent/service.js';\nimport type { ChannelManager } from '../../channels/manager.js';\nimport type { SessionIndex } from '../../session/index.js';\nimport { AgentRunRelay, type RelayEvent } from '../agent-run-relay.js';\nimport { ClarifyBridge, type ClarifyBridgeRequest } from '../clarify-bridge.js';\nimport { buildSessionKey, parseSessionKey } from '../../routing/session-key.js';\nimport { getDefaultAgentId } from '../../routing/resolve-route.js';\nimport { runGatewayAgent } from './run-gateway-agent.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('Gateway:AgentRunner');\n\nexport interface GatewayAgentRunnerOptions {\n bus: MessageBus;\n sessionIndex: SessionIndex;\n /** Resolved lazily — the runner is constructed before AgentService exists. */\n getAgentService: () => AgentService;\n getChannelManager: () => ChannelManager;\n getConfig: () => Config;\n /** SSE emit (re-used so `runAgent` events broadcast to subscribers). */\n emit: (type: string, payload: unknown) => void;\n}\n\nexport class GatewayAgentRunner {\n private readonly opts: GatewayAgentRunnerOptions;\n readonly runRelay = new AgentRunRelay();\n /** Per-run abort for webchat (POST /api/agent/abort or client disconnect). */\n private readonly runAbortControllers = new Map<string, AbortController>();\n private readonly clarifyBridge = new ClarifyBridge();\n /** Maps webchat session key → active `runId` for `clarify` tool routing. */\n private readonly activeWebchatRunBySession = new Map<string, string>();\n\n constructor(opts: GatewayAgentRunnerOptions) {\n this.opts = opts;\n }\n\n // ── Read-only accessors (so peers don't get a Map ref) ────────────────\n\n /** True when a webchat agent run is currently in-flight for `sessionKey`. */\n hasActiveRun(sessionKey: string): boolean {\n return this.activeWebchatRunBySession.has(sessionKey);\n }\n\n getActiveRunId(sessionKey: string): string | undefined {\n return this.activeWebchatRunBySession.get(sessionKey);\n }\n\n getClarifyBridge(): ClarifyBridge {\n return this.clarifyBridge;\n }\n\n /** Called from `GatewayService.stop()` so the bridge gets cleaned up. */\n disposeClarifyBridge(): void {\n this.clarifyBridge.dispose();\n }\n\n // ── runAgent (webchat HTTP POST) ──────────────────────────────────────\n\n async *runAgent(\n message: string,\n channel: string,\n chatId: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n }>,\n thinking?: string,\n runOptions?: { signal?: AbortSignal; clientCreatedAtMs?: number },\n ): AsyncGenerator<\n { type: string; [key: string]: unknown },\n { status: string; summary: string },\n unknown\n > {\n const iter = runGatewayAgent(\n {\n config: this.opts.getConfig(),\n agentService: this.opts.getAgentService(),\n bus: this.opts.bus,\n runRelay: this.runRelay,\n runAbortControllers: this.runAbortControllers,\n activeWebchatRunBySession: this.activeWebchatRunBySession,\n sessionIndex: this.opts.sessionIndex,\n emit: this.opts.emit,\n },\n message,\n channel,\n chatId,\n attachments,\n thinking,\n runOptions,\n );\n\n let step = await iter.next();\n while (!step.done) {\n yield step.value as unknown as { type: string; [key: string]: unknown };\n step = await iter.next();\n }\n return step.value;\n }\n\n /** Abort an in-flight webchat agent run (matches `runId` from SSE `run_start`). */\n abortAgentRun(runId: string): boolean {\n this.clarifyBridge.cancelForRun(runId);\n const keysToMark: string[] = [];\n for (const [sk, id] of this.activeWebchatRunBySession) {\n if (id === runId) {\n keysToMark.push(sk);\n }\n }\n for (const sk of keysToMark) {\n this.activeWebchatRunBySession.delete(sk);\n }\n const relaySk = this.runRelay.getSessionKey(runId);\n if (relaySk && !keysToMark.includes(relaySk)) {\n keysToMark.push(relaySk);\n }\n const c = this.runAbortControllers.get(runId);\n if (!c) {\n return false;\n }\n const cutoffTs = Date.now();\n for (const sk of keysToMark) {\n void this.opts.sessionIndex\n .updateSessionMetadata(sk, { abortCutoffTimestamp: cutoffTs })\n .catch(() => {});\n void this.opts.sessionIndex\n .appendTranscriptContextEntry(sk, {\n text: 'Webchat agent run aborted',\n data: { runId, abortCutoffTimestamp: cutoffTs },\n })\n .catch(() => {});\n }\n c.abort();\n for (const sk of keysToMark) {\n void import('../../agent/embedded/runs.js').then(({ abortEmbeddedRun }) =>\n abortEmbeddedRun(sk),\n );\n }\n return true;\n }\n\n /**\n * Queue steering text for an active webchat run (`Agent.steer` /\n * tool-boundary injection). `chatId` is the same as `POST /api/agent` body\n * (`sessionKey` or legacy peer id).\n */\n async steerWebchatAgent(\n chatId: string,\n message: string,\n ): Promise<\n { ok: true } | { ok: false; code: 'BAD_REQUEST' | 'NO_ACTIVE_RUN' | 'STEER_FAILED' }\n > {\n const trimmed = message.trim();\n if (!trimmed) {\n return { ok: false, code: 'BAD_REQUEST' };\n }\n const cfg = this.opts.getConfig();\n const parsedKey = parseSessionKey(chatId);\n const sessionKey = parsedKey\n ? chatId\n : buildSessionKey({\n agentId: getDefaultAgentId(cfg),\n source: 'webchat',\n accountId: 'default',\n peerKind: 'direct',\n peerId: chatId,\n });\n if (!this.activeWebchatRunBySession.has(sessionKey)) {\n return { ok: false, code: 'NO_ACTIVE_RUN' };\n }\n const steered = await this.opts\n .getAgentService()\n .turnDispatcher.steerWebchatSession(sessionKey, trimmed);\n if (!steered) {\n return { ok: false, code: 'STEER_FAILED' };\n }\n return { ok: true };\n }\n\n /** Deliver a user's answer to a pending `clarify` tool call. */\n submitClarifyResponse(requestId: string, answer: string): boolean {\n return this.clarifyBridge.handleResponse(requestId, answer);\n }\n\n /** Hermes-style: after HTTP sets a goal, enqueue the goal text as the next user turn. */\n enqueueWebchatPersistentGoalKickoff(sessionKey: string, goalText: string): void {\n queueMicrotask(() => {\n void this.drainScheduledWebchatContinuation(sessionKey, goalText);\n });\n }\n\n /** Same execution path as scheduled continuation, but lets callers observe failures. */\n async runScheduledWebchatContinuation(sessionKey: string, message: string): Promise<void> {\n const gen = this.runAgent(message, 'webchat', sessionKey, undefined, undefined, {\n clientCreatedAtMs: Date.now(),\n });\n for await (const _ of gen) {\n // Relay + `agent.stream` broadcast; UI attaches via pending runId + resume.\n }\n }\n\n /** Background drain for extension-initiated webchat turns (`scheduleWebchatContinuation`). */\n async drainScheduledWebchatContinuation(sessionKey: string, message: string): Promise<void> {\n try {\n await this.runScheduledWebchatContinuation(sessionKey, message);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Scheduled webchat continuation failed');\n }\n }\n\n // ── Clarify dispatch (called from AgentService.gatewayClarify) ────────\n\n /**\n * Resolve clarify-bridge config for `sessionKey`: who delivers the question\n * (webchat SSE, Telegram message, or both), then start the bridge request.\n * Rejects when neither path is available (e.g. CLI without webchat or TG).\n *\n * `publishSseFor(runId)` is the bridge into AgentService's\n * `turnDispatcher.enqueueWebchatSseEvent`. We take it as a callback so the\n * runner does not import AgentService statically.\n */\n requestClarification(opts: {\n sessionKey: string;\n request: ClarifyBridgeRequest;\n publishSseFor: (runId: string) => (e: RelayEvent) => void;\n }): Promise<string> {\n const { sessionKey, request, publishSseFor } = opts;\n const runId = this.activeWebchatRunBySession.get(sessionKey);\n const publishSse = runId ? publishSseFor(runId) : undefined;\n const parsed = parseSessionKey(sessionKey);\n const deliver =\n parsed?.source === 'telegram'\n ? async (ctx: {\n sessionKey: string;\n requestId: string;\n request: ClarifyBridgeRequest;\n }) => {\n await this.deliverTelegramClarify(ctx);\n }\n : undefined;\n if (!runId && !deliver) {\n return Promise.reject(\n new Error('Clarify is not available for this session (use webchat, Telegram, or CLI)'),\n );\n }\n return this.clarifyBridge.startRequest({\n sessionKey,\n runId,\n relay: this.runRelay,\n publishSse,\n request,\n deliver,\n });\n }\n\n private async deliverTelegramClarify(ctx: {\n sessionKey: string;\n requestId: string;\n request: ClarifyBridgeRequest;\n }): Promise<void> {\n const parsed = parseSessionKey(ctx.sessionKey);\n if (!parsed || parsed.source !== 'telegram') {\n return;\n }\n\n let body = ctx.request.question;\n if (ctx.request.default) {\n body += `\\n\\nDefault if unsure: ${ctx.request.default}`;\n }\n\n const choices = ctx.request.choices;\n const buttonRows =\n choices && choices.length >= 2\n ? choices.map((c, i) => [\n {\n text: c.length > 64 ? `${c.slice(0, 61)}…` : c,\n callback_data: `clarify:${ctx.requestId}:${i}`,\n },\n ])\n : undefined;\n\n if (!buttonRows) {\n body += '\\n\\nReply with your answer in this chat.';\n }\n\n await this.opts.getChannelManager().send({\n channel: 'telegram',\n chat_id: parsed.peerId,\n content: body,\n metadata: {\n accountId: parsed.accountId,\n ...(parsed.threadId ? { threadId: parsed.threadId } : {}),\n },\n buttons: buttonRows,\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"agent-runner.js","names":[],"sources":["../../../../src/gateway/service/agent-runner.ts"],"sourcesContent":["/**\n * GatewayAgentRunner — webchat agent invocation and the surrounding control\n * surface (abort, steer, clarify-bridge plumbing, scheduled continuations).\n *\n * Was 200 lines of `GatewayService` covering seven concerns that all hung off\n * the same handful of fields (`activeWebchatRunBySession`, `runAbortControllers`,\n * `clarifyBridge`, `runRelay`):\n *\n * - `runAgent(message, channel, chatId, ...)` — wraps {@link runGatewayAgent}\n * - `abortAgentRun(runId)` — POST /api/agent/abort + cleanup\n * - `steerWebchatAgent(chatId, message)` — Agent.steer queue at tool boundary\n * - `submitClarifyResponse(requestId, answer)` — UI answers a `clarify` call\n * - `enqueueWebchatPersistentGoalKickoff(sessionKey, goalText)` — initial\n * `/goal` kickoff posts the goal text as the next user turn\n * - `drainScheduledWebchatContinuation(sk, msg)` — background continuation\n * (extension scheduler + persistent-goal flow)\n * - `clarifyForSession({ sessionKey, request })` — clarify-bridge dispatch\n * used by `gatewayClarify.requestClarification` in AgentService\n *\n * Owns the two state maps (`activeWebchatRunBySession`, `runAbortControllers`)\n * directly so peer coordinators (sessions-api, marketplace, config) cannot\n * accidentally mutate them.\n */\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport type { AgentService } from '../../agent/service.js';\nimport type { ChannelManager } from '../../channels/manager.js';\nimport type { SessionIndex } from '../../session/index.js';\nimport { AgentRunRelay, type RelayEvent } from '../agent-run-relay.js';\nimport { ClarifyBridge, type ClarifyBridgeRequest } from '../clarify-bridge.js';\nimport { runGatewayAgent } from './run-gateway-agent.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('Gateway:AgentRunner');\n\nexport interface GatewayAgentRunnerOptions {\n bus: MessageBus;\n sessionIndex: SessionIndex;\n /** Resolved lazily — the runner is constructed before AgentService exists. */\n getAgentService: () => AgentService;\n getChannelManager: () => ChannelManager;\n getConfig: () => Config;\n /** SSE emit (re-used so `runAgent` events broadcast to subscribers). */\n emit: (type: string, payload: unknown) => void;\n}\n\nexport class GatewayAgentRunner {\n private readonly opts: GatewayAgentRunnerOptions;\n readonly runRelay = new AgentRunRelay();\n /** Per-run abort for webchat (POST /api/agent/abort or client disconnect). */\n private readonly runAbortControllers = new Map<string, AbortController>();\n private readonly clarifyBridge = new ClarifyBridge();\n /** Maps webchat session key → active `runId` for `clarify` tool routing. */\n private readonly activeWebchatRunBySession = new Map<string, string>();\n\n constructor(opts: GatewayAgentRunnerOptions) {\n this.opts = opts;\n }\n\n // ── Read-only accessors (so peers don't get a Map ref) ────────────────\n\n /** True when a webchat agent run is currently in-flight for `sessionKey`. */\n hasActiveRun(sessionKey: string): boolean {\n return this.activeWebchatRunBySession.has(sessionKey);\n }\n\n getActiveRunId(sessionKey: string): string | undefined {\n return this.activeWebchatRunBySession.get(sessionKey);\n }\n\n getClarifyBridge(): ClarifyBridge {\n return this.clarifyBridge;\n }\n\n /** Called from `GatewayService.stop()` so the bridge gets cleaned up. */\n disposeClarifyBridge(): void {\n this.clarifyBridge.dispose();\n }\n\n // ── runAgent (webchat HTTP POST) ──────────────────────────────────────\n\n async *runAgent(\n message: string,\n channel: string,\n chatId: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n }>,\n thinking?: string,\n runOptions?: { signal?: AbortSignal; clientCreatedAtMs?: number },\n ): AsyncGenerator<\n { type: string; [key: string]: unknown },\n { status: string; summary: string },\n unknown\n > {\n const iter = runGatewayAgent(\n {\n config: this.opts.getConfig(),\n agentService: this.opts.getAgentService(),\n bus: this.opts.bus,\n runRelay: this.runRelay,\n runAbortControllers: this.runAbortControllers,\n activeWebchatRunBySession: this.activeWebchatRunBySession,\n sessionIndex: this.opts.sessionIndex,\n emit: this.opts.emit,\n },\n message,\n channel,\n chatId,\n attachments,\n thinking,\n runOptions,\n );\n\n let step = await iter.next();\n while (!step.done) {\n yield step.value as unknown as { type: string; [key: string]: unknown };\n step = await iter.next();\n }\n return step.value;\n }\n\n /** Abort an in-flight webchat agent run (matches `runId` from SSE `run_start`). */\n abortAgentRun(runId: string): boolean {\n this.clarifyBridge.cancelForRun(runId);\n const keysToMark: string[] = [];\n for (const [sk, id] of this.activeWebchatRunBySession) {\n if (id === runId) {\n keysToMark.push(sk);\n }\n }\n for (const sk of keysToMark) {\n this.activeWebchatRunBySession.delete(sk);\n }\n const relaySk = this.runRelay.getSessionKey(runId);\n if (relaySk && !keysToMark.includes(relaySk)) {\n keysToMark.push(relaySk);\n }\n const c = this.runAbortControllers.get(runId);\n if (!c) {\n return false;\n }\n const cutoffTs = Date.now();\n for (const sk of keysToMark) {\n void this.opts.sessionIndex\n .updateSessionMetadata(sk, { abortCutoffTimestamp: cutoffTs })\n .catch(() => {});\n void this.opts.sessionIndex\n .appendTranscriptContextEntry(sk, {\n text: 'Webchat agent run aborted',\n data: { runId, abortCutoffTimestamp: cutoffTs },\n })\n .catch(() => {});\n }\n c.abort();\n for (const sk of keysToMark) {\n void import('../../agent/embedded/runs.js').then(({ abortEmbeddedRun }) =>\n abortEmbeddedRun(sk),\n );\n }\n return true;\n }\n\n /**\n * Queue steering text for an active webchat run (`Agent.steer` /\n * tool-boundary injection). The caller must pass the session key.\n */\n async steerWebchatAgent(\n sessionKey: string,\n message: string,\n ): Promise<\n { ok: true } | { ok: false; code: 'BAD_REQUEST' | 'NO_ACTIVE_RUN' | 'STEER_FAILED' }\n > {\n const trimmed = message.trim();\n if (!trimmed) {\n return { ok: false, code: 'BAD_REQUEST' };\n }\n if (!this.activeWebchatRunBySession.has(sessionKey)) {\n return { ok: false, code: 'NO_ACTIVE_RUN' };\n }\n const steered = await this.opts\n .getAgentService()\n .turnDispatcher.steerWebchatSession(sessionKey, trimmed);\n if (!steered) {\n return { ok: false, code: 'STEER_FAILED' };\n }\n return { ok: true };\n }\n\n /** Deliver a user's answer to a pending `clarify` tool call. */\n submitClarifyResponse(requestId: string, answer: string): boolean {\n return this.clarifyBridge.handleResponse(requestId, answer);\n }\n\n /** Hermes-style: after HTTP sets a goal, enqueue the goal text as the next user turn. */\n enqueueWebchatPersistentGoalKickoff(sessionKey: string, goalText: string): void {\n queueMicrotask(() => {\n void this.drainScheduledWebchatContinuation(sessionKey, goalText);\n });\n }\n\n /** Same execution path as scheduled continuation, but lets callers observe failures. */\n async runScheduledWebchatContinuation(sessionKey: string, message: string): Promise<void> {\n const gen = this.runAgent(message, 'webchat', sessionKey, undefined, undefined, {\n clientCreatedAtMs: Date.now(),\n });\n for await (const _ of gen) {\n // Relay + `agent.stream` broadcast; UI attaches via pending runId + resume.\n }\n }\n\n /** Background drain for extension-initiated webchat turns (`scheduleWebchatContinuation`). */\n async drainScheduledWebchatContinuation(sessionKey: string, message: string): Promise<void> {\n try {\n await this.runScheduledWebchatContinuation(sessionKey, message);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Scheduled webchat continuation failed');\n }\n }\n\n // ── Clarify dispatch (called from AgentService.gatewayClarify) ────────\n\n /**\n * Resolve clarify-bridge config for `sessionKey`: who delivers the question\n * (webchat SSE, Telegram message, or both), then start the bridge request.\n * Rejects when neither path is available (e.g. CLI without webchat or TG).\n *\n * `publishSseFor(runId)` is the bridge into AgentService's\n * `turnDispatcher.enqueueWebchatSseEvent`. We take it as a callback so the\n * runner does not import AgentService statically.\n */\n async requestClarification(opts: {\n sessionKey: string;\n request: ClarifyBridgeRequest;\n publishSseFor: (runId: string) => (e: RelayEvent) => void;\n }): Promise<string> {\n const { sessionKey, request, publishSseFor } = opts;\n const runId = this.activeWebchatRunBySession.get(sessionKey);\n const publishSse = runId ? publishSseFor(runId) : undefined;\n const metadata = await this.opts.sessionIndex.getSessionMetadata(sessionKey).catch(() => null);\n const routing = metadata?.routing;\n const deliver =\n routing?.source === 'telegram'\n ? async (ctx: {\n sessionKey: string;\n requestId: string;\n request: ClarifyBridgeRequest;\n }) => {\n await this.deliverTelegramClarify(ctx);\n }\n : undefined;\n if (!runId && !deliver) {\n return Promise.reject(\n new Error('Clarify is not available for this session (use webchat, Telegram, or CLI)'),\n );\n }\n return this.clarifyBridge.startRequest({\n sessionKey,\n runId,\n relay: this.runRelay,\n publishSse,\n request,\n deliver,\n });\n }\n\n private async deliverTelegramClarify(ctx: {\n sessionKey: string;\n requestId: string;\n request: ClarifyBridgeRequest;\n }): Promise<void> {\n const metadata = await this.opts.sessionIndex.getSessionMetadata(ctx.sessionKey).catch(() => null);\n const routing = metadata?.routing;\n if (!routing || routing.source !== 'telegram') {\n return;\n }\n\n let body = ctx.request.question;\n if (ctx.request.default) {\n body += `\\n\\nDefault if unsure: ${ctx.request.default}`;\n }\n\n const choices = ctx.request.choices;\n const buttonRows =\n choices && choices.length >= 2\n ? choices.map((c, i) => [\n {\n text: c.length > 64 ? `${c.slice(0, 61)}…` : c,\n callback_data: `clarify:${ctx.requestId}:${i}`,\n },\n ])\n : undefined;\n\n if (!buttonRows) {\n body += '\\n\\nReply with your answer in this chat.';\n }\n\n await this.opts.getChannelManager().send({\n channel: 'telegram',\n chat_id: routing.peerId,\n content: body,\n metadata: {\n accountId: routing.accountId,\n ...(routing.threadId ? { threadId: routing.threadId } : {}),\n },\n buttons: buttonRows,\n });\n }\n}\n"],"mappings":"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"}
@@ -21,19 +21,18 @@ async function* runGatewayAgent(deps, message, channel, chatId, attachments, thi
21
21
  max: 10
22
22
  }, "Attachments capped for webchat");
23
23
  const runId = crypto.randomUUID();
24
- const { config, agentService, bus, runRelay, runAbortControllers, activeWebchatRunBySession, sessionIndex: sessionIndexFromDeps, emit } = deps;
24
+ const { agentService, bus, runRelay, runAbortControllers, activeWebchatRunBySession, sessionIndex: sessionIndexFromDeps, emit } = deps;
25
25
  const sessionIndex = sessionIndexFromDeps;
26
26
  let webchatSessionKey;
27
+ let webchatSessionId;
27
28
  let webchatStaleSkip = false;
28
29
  if (channel === "webchat") {
29
- const resolved = resolveWebchatSessionKey({
30
- cfg: config,
31
- chatId,
32
- newSession: false
33
- });
30
+ const resolved = resolveWebchatSessionKey({ sessionKey: chatId });
34
31
  if (resolved.ok === false) throw new Error(resolved.error);
35
32
  webchatSessionKey = resolved.sessionKey;
36
33
  const meta = await sessionIndex.getSessionMetadata(webchatSessionKey);
34
+ if (!meta) throw new Error("Session not found; create sessions via POST /api/sessions");
35
+ webchatSessionId = meta?.sessionId;
37
36
  webchatStaleSkip = shouldSkipWebchatInboundByAbortCutoff(meta, runOptions?.clientCreatedAtMs);
38
37
  if (!webchatStaleSkip && meta?.abortCutoffTimestamp !== void 0) await sessionIndex.updateSessionMetadata(webchatSessionKey, { abortCutoffTimestamp: void 0 }).catch(() => {});
39
38
  runRelay.ensureRun(runId, webchatSessionKey);
@@ -69,7 +68,10 @@ async function* runGatewayAgent(deps, message, channel, chatId, attachments, thi
69
68
  };
70
69
  }
71
70
  const sessionKey = webchatSessionKey;
72
- updateAsyncLogContext({ sessionId: sessionKey });
71
+ updateAsyncLogContext({
72
+ sessionKey,
73
+ ...webchatSessionId ? { sessionId: webchatSessionId } : {}
74
+ });
73
75
  const timezone = agentService.resolveUserTimezoneForSession(sessionKey);
74
76
  const stampedMessage = message.trimStart().startsWith("/") ? message : prependEnvelopeTimestamp(message, timezone);
75
77
  const prepared = await agentService.prepareInboundAttachments(sessionKey, cappedAttachments);
@@ -1 +1 @@
1
- {"version":3,"file":"run-gateway-agent.js","names":[],"sources":["../../../../src/gateway/service/run-gateway-agent.ts"],"sourcesContent":["import crypto from 'crypto';\n\nimport type { AgentService } from '../../agent/service.js';\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport { prependEnvelopeTimestamp } from '../../channels/envelope-timestamp.js';\nimport { resolveWebchatSessionKey } from '../resolve-webchat-session-key.js';\nimport type { SessionIndex } from '../../session/index.js';\nimport {\n createLogger,\n inboundCorrelationMetadataFromAsyncLogContext,\n updateAsyncLogContext,\n} from '../../utils/logger.js';\nimport { shouldSkipWebchatInboundByAbortCutoff } from '../../session/abort-cutoff.js';\n\nimport { formatAgentRunErrorForClient } from '../../agent/client-error-format.js';\n\nimport { ChatStreamMapper } from '../chat-stream/mapper.js';\nimport type { ChatStreamEvent } from '../chat-stream/protocol.js';\nimport type { AgentRunRelay } from '../agent-run-relay.js';\nimport { MAX_CHAT_ATTACHMENTS } from '../chat-limits.js';\nconst log = createLogger('Gateway:Service');\n\nexport type RunGatewayAgentYield = ChatStreamEvent;\n\nexport type RunGatewayAgentDeps = {\n config: Config;\n agentService: AgentService;\n bus: MessageBus;\n runRelay: AgentRunRelay;\n runAbortControllers: Map<string, AbortController>;\n activeWebchatRunBySession: Map<string, string>;\n sessionIndex: SessionIndex;\n emit: (type: string, payload: unknown) => void;\n};\n\n/**\n * @param runOptions.signal — When set (e.g. client disconnect), aborts in-flight generation and persists partial output.\n */\nexport async function *runGatewayAgent(\n deps: RunGatewayAgentDeps,\n message: string,\n channel: string,\n chatId: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n uri?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n durationSeconds?: number;\n }>,\n thinking?: string,\n runOptions?: { signal?: AbortSignal; clientCreatedAtMs?: number },\n): AsyncGenerator<RunGatewayAgentYield, { status: string; summary: string }, unknown> {\n const cappedAttachments =\n attachments && attachments.length > MAX_CHAT_ATTACHMENTS\n ? attachments.slice(0, MAX_CHAT_ATTACHMENTS)\n : attachments;\n if (attachments && cappedAttachments && attachments.length > cappedAttachments.length) {\n log.debug(\n { dropped: attachments.length - cappedAttachments.length, max: MAX_CHAT_ATTACHMENTS },\n 'Attachments capped for webchat',\n );\n }\n\n const runId = crypto.randomUUID();\n const {\n config,\n agentService,\n bus,\n runRelay,\n runAbortControllers,\n activeWebchatRunBySession,\n sessionIndex: sessionIndexFromDeps,\n emit,\n } = deps;\n const sessionIndex = sessionIndexFromDeps;\n\n let webchatSessionKey: string | undefined;\n let webchatStaleSkip = false;\n if (channel === 'webchat') {\n const resolved = resolveWebchatSessionKey({ cfg: config, chatId, newSession: false });\n if (resolved.ok === false) {\n throw new Error(resolved.error);\n }\n webchatSessionKey = resolved.sessionKey;\n const meta = await sessionIndex.getSessionMetadata(webchatSessionKey);\n webchatStaleSkip = shouldSkipWebchatInboundByAbortCutoff(meta, runOptions?.clientCreatedAtMs);\n if (!webchatStaleSkip && meta?.abortCutoffTimestamp !== undefined) {\n await sessionIndex\n .updateSessionMetadata(webchatSessionKey, { abortCutoffTimestamp: undefined })\n .catch(() => {});\n }\n runRelay.ensureRun(runId, webchatSessionKey);\n runAbortControllers.set(runId, new AbortController());\n }\n\n const streamSessionKey = webchatSessionKey ?? chatId;\n const mapper = new ChatStreamMapper({ runId, sessionKey: streamSessionKey, channel });\n const publishStreamEvent = (event: ChatStreamEvent): ChatStreamEvent =>\n channel === 'webchat'\n ? ((runRelay.publish(runId, event as unknown as import('../agent-run-relay.js').RelayEvent) as unknown as ChatStreamEvent | undefined) ?? event)\n : event;\n const emitAndYield = function *(events: ChatStreamEvent[]): Generator<ChatStreamEvent> {\n for (const event of events) {\n const relayedEvent = publishStreamEvent(event);\n if (channel === 'webchat') emit('agent.stream', { sessionKey: streamSessionKey, event: relayedEvent });\n yield relayedEvent;\n }\n };\n\n yield* emitAndYield(mapper.start());\n\n try {\n if (channel === 'webchat' && webchatSessionKey) {\n if (webchatStaleSkip) {\n yield* emitAndYield(mapper.end('cancelled', 'Stale inbound after abort (clientCreatedAtMs before cutoff)'));\n runRelay.complete(runId);\n runAbortControllers.delete(runId);\n return {\n status: 'skipped',\n summary: 'Stale inbound after abort (clientCreatedAtMs before cutoff)',\n };\n }\n\n const sessionKey = webchatSessionKey;\n updateAsyncLogContext({ sessionId: sessionKey });\n\n const timezone = agentService.resolveUserTimezoneForSession(sessionKey);\n const stampedMessage = message.trimStart().startsWith('/')\n ? message\n : prependEnvelopeTimestamp(message, timezone);\n const prepared = await agentService.prepareInboundAttachments(sessionKey, cappedAttachments);\n\n const runAbort = runAbortControllers.get(runId);\n if (!runAbort) {\n throw new Error('run abort controller missing for webchat');\n }\n const mergedSignal = runOptions?.signal\n ? AbortSignal.any([runOptions.signal, runAbort.signal])\n : runAbort.signal;\n\n agentService.beginInboundTurn(sessionKey);\n activeWebchatRunBySession.set(sessionKey, runId);\n let streamError: string | undefined;\n try {\n const eventStream = agentService.turnDispatcher.processDirectStreaming(\n stampedMessage,\n sessionKey,\n prepared,\n thinking,\n { signal: mergedSignal, runId },\n );\n\n for await (const event of eventStream) {\n yield* emitAndYield(mapper.map(event));\n }\n\n const endStatus = mergedSignal.aborted ? 'cancelled' : 'success';\n const endSummary = mergedSignal.aborted ? 'Interrupted' : 'Message processed successfully';\n yield* emitAndYield(mapper.end(endStatus, endSummary));\n runRelay.complete(runId);\n try {\n const metaAfter = await sessionIndex.getSessionMetadata(sessionKey);\n if (metaAfter?.name) {\n emit('session.updated', { key: sessionKey, name: metaAfter.name });\n }\n } catch {\n /* ignore */\n }\n return {\n status: mergedSignal.aborted ? 'aborted' : 'ok',\n summary: mergedSignal.aborted ? 'Interrupted' : 'Message processed successfully',\n };\n } catch (error) {\n const em = error instanceof Error ? error.message : String(error);\n log.error(\n {\n err: error,\n errorMessage: em,\n phase: 'gateway.agent_run',\n sessionKey,\n runId,\n channel: 'webchat',\n },\n `Agent processing failed: ${em}`,\n );\n streamError = em;\n const errorContent = formatAgentRunErrorForClient(streamError);\n yield* emitAndYield(mapper.error(errorContent));\n yield* emitAndYield(mapper.end('error', streamError));\n runRelay.complete(runId);\n return { status: 'error', summary: streamError };\n } finally {\n activeWebchatRunBySession.delete(sessionKey);\n runAbortControllers.delete(runId);\n const assistantPlainText = agentService.getLastAssistantPlainText(sessionKey);\n const streamOutcome = agentService.persistentGoals.takeStreamOutcome(sessionKey);\n try {\n await agentService.outboundCoordinator.emitSessionTurnComplete({\n sessionKey,\n channel: 'webchat',\n chatId: sessionKey,\n inboundUserText: message,\n assistantPlainText,\n aborted: mergedSignal.aborted,\n ...(streamError !== undefined ? { streamError } : {}),\n skipPersistentGoalPostTurn: streamOutcome?.skipPersistentGoalPostTurn ?? false,\n outboundMetadata: {},\n });\n } catch (goalErr) {\n log.warn(\n { err: goalErr, sessionKey },\n `Session turn complete failed: ${goalErr instanceof Error ? goalErr.message : String(goalErr)}`,\n );\n }\n agentService.endInboundTurn(sessionKey);\n }\n }\n\n const correlationMeta = inboundCorrelationMetadataFromAsyncLogContext();\n await bus.publishInbound({\n channel,\n sender_id: 'gateway',\n chat_id: chatId,\n content: message,\n ...(correlationMeta ? { metadata: correlationMeta } : {}),\n });\n\n const messageId = `msg_${runId.replace(/[^a-zA-Z0-9_-]/g, '_')}_1`;\n yield* emitAndYield([\n {\n type: 'assistant_message_start',\n runId,\n sessionKey: streamSessionKey,\n timestamp: Date.now(),\n payload: { messageId },\n },\n {\n type: 'assistant_delta',\n runId,\n sessionKey: streamSessionKey,\n timestamp: Date.now(),\n payload: { messageId, delta: 'Processing...\\n' },\n },\n ]);\n await new Promise((resolve) => setTimeout(resolve, 1000));\n yield* emitAndYield([\n {\n type: 'assistant_delta',\n runId,\n sessionKey: streamSessionKey,\n timestamp: Date.now(),\n payload: { messageId, delta: 'Done\\n' },\n },\n {\n type: 'assistant_message_end',\n runId,\n sessionKey: streamSessionKey,\n timestamp: Date.now(),\n payload: { messageId },\n },\n ]);\n yield* emitAndYield(mapper.end('success', 'Message processed'));\n return { status: 'ok', summary: 'Message processed' };\n } catch (error) {\n const em = error instanceof Error ? error.message : String(error);\n log.error(\n {\n err: error,\n errorMessage: em,\n phase: 'gateway.agent_run',\n runId,\n channel,\n chatId,\n },\n `Agent run failed: ${em}`,\n );\n throw error;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"run-gateway-agent.js","names":[],"sources":["../../../../src/gateway/service/run-gateway-agent.ts"],"sourcesContent":["import crypto from 'crypto';\n\nimport type { AgentService } from '../../agent/service.js';\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport { prependEnvelopeTimestamp } from '../../channels/envelope-timestamp.js';\nimport { resolveWebchatSessionKey } from '../resolve-webchat-session-key.js';\nimport type { SessionIndex } from '../../session/index.js';\nimport {\n createLogger,\n inboundCorrelationMetadataFromAsyncLogContext,\n updateAsyncLogContext,\n} from '../../utils/logger.js';\nimport { shouldSkipWebchatInboundByAbortCutoff } from '../../session/abort-cutoff.js';\n\nimport { formatAgentRunErrorForClient } from '../../agent/client-error-format.js';\n\nimport { ChatStreamMapper } from '../chat-stream/mapper.js';\nimport type { ChatStreamEvent } from '../chat-stream/protocol.js';\nimport type { AgentRunRelay } from '../agent-run-relay.js';\nimport { MAX_CHAT_ATTACHMENTS } from '../chat-limits.js';\nconst log = createLogger('Gateway:Service');\n\nexport type RunGatewayAgentYield = ChatStreamEvent;\n\nexport type RunGatewayAgentDeps = {\n config: Config;\n agentService: AgentService;\n bus: MessageBus;\n runRelay: AgentRunRelay;\n runAbortControllers: Map<string, AbortController>;\n activeWebchatRunBySession: Map<string, string>;\n sessionIndex: SessionIndex;\n emit: (type: string, payload: unknown) => void;\n};\n\n/**\n * @param runOptions.signal — When set (e.g. client disconnect), aborts in-flight generation and persists partial output.\n */\nexport async function *runGatewayAgent(\n deps: RunGatewayAgentDeps,\n message: string,\n channel: string,\n chatId: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n uri?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n durationSeconds?: number;\n }>,\n thinking?: string,\n runOptions?: { signal?: AbortSignal; clientCreatedAtMs?: number },\n): AsyncGenerator<RunGatewayAgentYield, { status: string; summary: string }, unknown> {\n const cappedAttachments =\n attachments && attachments.length > MAX_CHAT_ATTACHMENTS\n ? attachments.slice(0, MAX_CHAT_ATTACHMENTS)\n : attachments;\n if (attachments && cappedAttachments && attachments.length > cappedAttachments.length) {\n log.debug(\n { dropped: attachments.length - cappedAttachments.length, max: MAX_CHAT_ATTACHMENTS },\n 'Attachments capped for webchat',\n );\n }\n\n const runId = crypto.randomUUID();\n const {\n agentService,\n bus,\n runRelay,\n runAbortControllers,\n activeWebchatRunBySession,\n sessionIndex: sessionIndexFromDeps,\n emit,\n } = deps;\n const sessionIndex = sessionIndexFromDeps;\n\n let webchatSessionKey: string | undefined;\n let webchatSessionId: string | undefined;\n let webchatStaleSkip = false;\n if (channel === 'webchat') {\n const resolved = resolveWebchatSessionKey({ sessionKey: chatId });\n if (resolved.ok === false) {\n throw new Error(resolved.error);\n }\n webchatSessionKey = resolved.sessionKey;\n const meta = await sessionIndex.getSessionMetadata(webchatSessionKey);\n if (!meta) {\n throw new Error('Session not found; create sessions via POST /api/sessions');\n }\n webchatSessionId = meta?.sessionId;\n webchatStaleSkip = shouldSkipWebchatInboundByAbortCutoff(meta, runOptions?.clientCreatedAtMs);\n if (!webchatStaleSkip && meta?.abortCutoffTimestamp !== undefined) {\n await sessionIndex\n .updateSessionMetadata(webchatSessionKey, { abortCutoffTimestamp: undefined })\n .catch(() => {});\n }\n runRelay.ensureRun(runId, webchatSessionKey);\n runAbortControllers.set(runId, new AbortController());\n }\n\n const streamSessionKey = webchatSessionKey ?? chatId;\n const mapper = new ChatStreamMapper({ runId, sessionKey: streamSessionKey, channel });\n const publishStreamEvent = (event: ChatStreamEvent): ChatStreamEvent =>\n channel === 'webchat'\n ? ((runRelay.publish(runId, event as unknown as import('../agent-run-relay.js').RelayEvent) as unknown as ChatStreamEvent | undefined) ?? event)\n : event;\n const emitAndYield = function *(events: ChatStreamEvent[]): Generator<ChatStreamEvent> {\n for (const event of events) {\n const relayedEvent = publishStreamEvent(event);\n if (channel === 'webchat') emit('agent.stream', { sessionKey: streamSessionKey, event: relayedEvent });\n yield relayedEvent;\n }\n };\n\n yield* emitAndYield(mapper.start());\n\n try {\n if (channel === 'webchat' && webchatSessionKey) {\n if (webchatStaleSkip) {\n yield* emitAndYield(mapper.end('cancelled', 'Stale inbound after abort (clientCreatedAtMs before cutoff)'));\n runRelay.complete(runId);\n runAbortControllers.delete(runId);\n return {\n status: 'skipped',\n summary: 'Stale inbound after abort (clientCreatedAtMs before cutoff)',\n };\n }\n\n const sessionKey = webchatSessionKey;\n updateAsyncLogContext({\n sessionKey,\n ...(webchatSessionId ? { sessionId: webchatSessionId } : {}),\n });\n\n const timezone = agentService.resolveUserTimezoneForSession(sessionKey);\n const stampedMessage = message.trimStart().startsWith('/')\n ? message\n : prependEnvelopeTimestamp(message, timezone);\n const prepared = await agentService.prepareInboundAttachments(sessionKey, cappedAttachments);\n\n const runAbort = runAbortControllers.get(runId);\n if (!runAbort) {\n throw new Error('run abort controller missing for webchat');\n }\n const mergedSignal = runOptions?.signal\n ? AbortSignal.any([runOptions.signal, runAbort.signal])\n : runAbort.signal;\n\n agentService.beginInboundTurn(sessionKey);\n activeWebchatRunBySession.set(sessionKey, runId);\n let streamError: string | undefined;\n try {\n const eventStream = agentService.turnDispatcher.processDirectStreaming(\n stampedMessage,\n sessionKey,\n prepared,\n thinking,\n { signal: mergedSignal, runId },\n );\n\n for await (const event of eventStream) {\n yield* emitAndYield(mapper.map(event));\n }\n\n const endStatus = mergedSignal.aborted ? 'cancelled' : 'success';\n const endSummary = mergedSignal.aborted ? 'Interrupted' : 'Message processed successfully';\n yield* emitAndYield(mapper.end(endStatus, endSummary));\n runRelay.complete(runId);\n try {\n const metaAfter = await sessionIndex.getSessionMetadata(sessionKey);\n if (metaAfter?.name) {\n emit('session.updated', { key: sessionKey, name: metaAfter.name });\n }\n } catch {\n /* ignore */\n }\n return {\n status: mergedSignal.aborted ? 'aborted' : 'ok',\n summary: mergedSignal.aborted ? 'Interrupted' : 'Message processed successfully',\n };\n } catch (error) {\n const em = error instanceof Error ? error.message : String(error);\n log.error(\n {\n err: error,\n errorMessage: em,\n phase: 'gateway.agent_run',\n sessionKey,\n runId,\n channel: 'webchat',\n },\n `Agent processing failed: ${em}`,\n );\n streamError = em;\n const errorContent = formatAgentRunErrorForClient(streamError);\n yield* emitAndYield(mapper.error(errorContent));\n yield* emitAndYield(mapper.end('error', streamError));\n runRelay.complete(runId);\n return { status: 'error', summary: streamError };\n } finally {\n activeWebchatRunBySession.delete(sessionKey);\n runAbortControllers.delete(runId);\n const assistantPlainText = agentService.getLastAssistantPlainText(sessionKey);\n const streamOutcome = agentService.persistentGoals.takeStreamOutcome(sessionKey);\n try {\n await agentService.outboundCoordinator.emitSessionTurnComplete({\n sessionKey,\n channel: 'webchat',\n chatId: sessionKey,\n inboundUserText: message,\n assistantPlainText,\n aborted: mergedSignal.aborted,\n ...(streamError !== undefined ? { streamError } : {}),\n skipPersistentGoalPostTurn: streamOutcome?.skipPersistentGoalPostTurn ?? false,\n outboundMetadata: {},\n });\n } catch (goalErr) {\n log.warn(\n { err: goalErr, sessionKey },\n `Session turn complete failed: ${goalErr instanceof Error ? goalErr.message : String(goalErr)}`,\n );\n }\n agentService.endInboundTurn(sessionKey);\n }\n }\n\n const correlationMeta = inboundCorrelationMetadataFromAsyncLogContext();\n await bus.publishInbound({\n channel,\n sender_id: 'gateway',\n chat_id: chatId,\n content: message,\n ...(correlationMeta ? { metadata: correlationMeta } : {}),\n });\n\n const messageId = `msg_${runId.replace(/[^a-zA-Z0-9_-]/g, '_')}_1`;\n yield* emitAndYield([\n {\n type: 'assistant_message_start',\n runId,\n sessionKey: streamSessionKey,\n timestamp: Date.now(),\n payload: { messageId },\n },\n {\n type: 'assistant_delta',\n runId,\n sessionKey: streamSessionKey,\n timestamp: Date.now(),\n payload: { messageId, delta: 'Processing...\\n' },\n },\n ]);\n await new Promise((resolve) => setTimeout(resolve, 1000));\n yield* emitAndYield([\n {\n type: 'assistant_delta',\n runId,\n sessionKey: streamSessionKey,\n timestamp: Date.now(),\n payload: { messageId, delta: 'Done\\n' },\n },\n {\n type: 'assistant_message_end',\n runId,\n sessionKey: streamSessionKey,\n timestamp: Date.now(),\n payload: { messageId },\n },\n ]);\n yield* emitAndYield(mapper.end('success', 'Message processed'));\n return { status: 'ok', summary: 'Message processed' };\n } catch (error) {\n const em = error instanceof Error ? error.message : String(error);\n log.error(\n {\n err: error,\n errorMessage: em,\n phase: 'gateway.agent_run',\n runId,\n channel,\n chatId,\n },\n `Agent run failed: ${em}`,\n );\n throw error;\n }\n}\n"],"mappings":"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"}
@@ -33,6 +33,15 @@ export declare class GatewaySessionsApi {
33
33
  includeTranscriptSummary?: boolean;
34
34
  includeTranscriptRows?: boolean;
35
35
  }): Promise<import("../../session/types.js").SessionDetail>;
36
+ resolveSession(input: {
37
+ key?: string;
38
+ sessionKey?: string;
39
+ sessionId?: string;
40
+ }): Promise<{
41
+ sessionKey: string;
42
+ sessionId: string;
43
+ session: Awaited<ReturnType<GatewaySessionsApi['getSession']>>;
44
+ } | null>;
36
45
  /** Read-only: in-flight webchat agent run for this session key, if any. */
37
46
  getActiveRun(sessionKey: string): {
38
47
  active: boolean;
@@ -19,6 +19,17 @@ var GatewaySessionsApi = class {
19
19
  getSession(key, options) {
20
20
  return this.opts.sessionIndex.getSession(key, options);
21
21
  }
22
+ async resolveSession(input) {
23
+ const resolvedKey = input.sessionKey?.trim() || input.key?.trim() || (input.sessionId?.trim() ? await this.opts.sessionIndex.resolveSessionKeyBySessionId(input.sessionId.trim()) : null);
24
+ if (!resolvedKey) return null;
25
+ const session = await this.getSession(resolvedKey);
26
+ if (!session?.sessionId) return null;
27
+ return {
28
+ sessionKey: resolvedKey,
29
+ sessionId: session.sessionId,
30
+ session
31
+ };
32
+ }
22
33
  /** Read-only: in-flight webchat agent run for this session key, if any. */
23
34
  getActiveRun(sessionKey) {
24
35
  const key = sessionKey.trim();