@xopcai/xopc 0.0.86 → 0.0.88

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 (658) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/adapters/cli-login.js +3 -3
  3. package/dist/extensions/feishu/src/adapters/cli-login.js.map +1 -1
  4. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  5. package/dist/extensions/feishu/src/workflow-progress.js +1 -1
  6. package/dist/extensions/telegram/src/delivery-chat-id.d.ts +1 -1
  7. package/dist/extensions/telegram/src/delivery-chat-id.js +1 -1
  8. package/dist/extensions/telegram/src/delivery-chat-id.js.map +1 -1
  9. package/dist/extensions/telegram/src/plugin.js +1 -1
  10. package/dist/extensions/telegram/src/routing-integration.js +3 -2
  11. package/dist/extensions/telegram/src/routing-integration.js.map +1 -1
  12. package/dist/extensions/telegram/src/workflow-progress.js +1 -1
  13. package/dist/extensions/telegram/xopc.extension.json +1 -1
  14. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js +2 -2
  15. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js.map +1 -1
  16. package/dist/extensions/weixin/src/api/api.js +3 -3
  17. package/dist/extensions/weixin/src/api/api.js.map +1 -1
  18. package/dist/extensions/weixin/src/auth/accounts.js +12 -12
  19. package/dist/extensions/weixin/src/auth/accounts.js.map +1 -1
  20. package/dist/extensions/weixin/src/cdn/upload.js +1 -1
  21. package/dist/extensions/weixin/src/delivery-to.js +2 -2
  22. package/dist/extensions/weixin/src/delivery-to.js.map +1 -1
  23. package/dist/extensions/weixin/src/media/data-url.js +1 -1
  24. package/dist/extensions/weixin/src/messaging/debug-mode.js +5 -5
  25. package/dist/extensions/weixin/src/messaging/debug-mode.js.map +1 -1
  26. package/dist/extensions/weixin/src/messaging/inbound.js +11 -11
  27. package/dist/extensions/weixin/src/messaging/inbound.js.map +1 -1
  28. package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
  29. package/dist/extensions/weixin/src/plugin.js +1 -1
  30. package/dist/extensions/weixin/src/storage/sync-buf.js +4 -4
  31. package/dist/extensions/weixin/src/storage/sync-buf.js.map +1 -1
  32. package/dist/extensions/weixin/src/workflow-progress.d.ts +1 -1
  33. package/dist/extensions/weixin/src/workflow-progress.js +1 -1
  34. package/dist/extensions/weixin/src/workflow-progress.js.map +1 -1
  35. package/dist/gateway/static/root/assets/agents-CRxETUZx.js +222 -0
  36. package/dist/gateway/static/root/assets/{apps-page-DrfytjOb.js → apps-page-wKWf3l57.js} +1 -1
  37. package/dist/gateway/static/root/assets/channels-settings-DDbqVNkx.js +1 -0
  38. package/dist/gateway/static/root/assets/{channels-status-swr-Bs5kMCMI.js → channels-status-swr-DIsl75Y3.js} +1 -1
  39. package/dist/gateway/static/root/assets/copy-SxMW6Xpc.js +1 -0
  40. package/dist/gateway/static/root/assets/{cron-api-BuVcZ5zR.js → cron-api-N9hvuRrn.js} +1 -1
  41. package/dist/gateway/static/root/assets/{cron-page-BMrloeFH.js → cron-page-tlNGNxhP.js} +1 -1
  42. package/dist/gateway/static/root/assets/{dist-CKU1OOTf.js → dist-CJwfHYvT.js} +1 -1
  43. package/dist/gateway/static/root/assets/{extension-debug-page-BdW_46sN.js → extension-debug-page-BVJohZoZ.js} +1 -1
  44. package/dist/gateway/static/root/assets/{extension-page-DW47KI82.js → extension-page-BT2tmElC.js} +1 -1
  45. package/dist/gateway/static/root/assets/extension-settings-page-BSS47c2j.js +1 -0
  46. package/dist/gateway/static/root/assets/{fetch-B2MYHbWg.js → fetch-BaFNUtkE.js} +1 -1
  47. package/dist/gateway/static/root/assets/{field-primitives-DPG-oJmx.js → field-primitives-QwYEq6Hz.js} +1 -1
  48. package/dist/gateway/static/root/assets/{heartbeat-config-api-C8dNts9i.js → heartbeat-config-api-BVSidEDJ.js} +1 -1
  49. package/dist/gateway/static/root/assets/index-CqZzHNEg.css +1 -0
  50. package/dist/gateway/static/root/assets/{index-BmVYculr.js → index-qNrVJp-y.js} +97 -95
  51. package/dist/gateway/static/root/assets/{logs-page-sTsVWz0X.js → logs-page-DDonPVLn.js} +1 -1
  52. package/dist/gateway/static/root/assets/sessions-page-DKt-Wmib.js +1 -0
  53. package/dist/gateway/static/root/assets/{settings-form-section-DuvRQW--.js → settings-form-section-B8N3A3Zo.js} +1 -1
  54. package/dist/gateway/static/root/assets/settings-page-DcJjvvw4.js +3 -0
  55. package/dist/gateway/static/root/assets/{share-preview-page-BtG2kLDh.js → share-preview-page-Q7KqkO-u.js} +1 -1
  56. package/dist/gateway/static/root/assets/skills-page-DuJ4BTO3.js +2 -0
  57. package/dist/gateway/static/root/assets/{theme-store-DryYl3qD.js → theme-store-BbRc5ugR.js} +1 -1
  58. package/dist/gateway/static/root/assets/url-D6jvVYIA.js +7 -0
  59. package/dist/gateway/static/root/assets/{utils-BY7bU1DT.js → utils-CxDGduqK.js} +1 -1
  60. package/dist/gateway/static/root/assets/voice-api-key-field-CTyHz7L_.js +1 -0
  61. package/dist/gateway/static/root/assets/workflows-page-GacJ41Fv.js +27 -0
  62. package/dist/gateway/static/root/index.html +6 -5
  63. package/dist/package.js +1 -1
  64. package/dist/src/agent/agent-manager.js +7 -7
  65. package/dist/src/agent/agent-scope.d.ts +4 -0
  66. package/dist/src/agent/agent-scope.js +53 -10
  67. package/dist/src/agent/agent-scope.js.map +1 -1
  68. package/dist/src/agent/bootstrap/filter-bootstrap-files.js +2 -1
  69. package/dist/src/agent/bootstrap/filter-bootstrap-files.js.map +1 -1
  70. package/dist/src/agent/bootstrap/load-bootstrap-files.js +1 -1
  71. package/dist/src/agent/child-agent-factory.d.ts +15 -0
  72. package/dist/src/agent/child-agent-factory.js +35 -2
  73. package/dist/src/agent/child-agent-factory.js.map +1 -1
  74. package/dist/src/agent/client-error-format.d.ts +20 -0
  75. package/dist/src/agent/client-error-format.js +97 -0
  76. package/dist/src/agent/client-error-format.js.map +1 -0
  77. package/dist/src/agent/context/workspace-seed.js +2 -2
  78. package/dist/src/agent/embedded/run-turn.js +23 -4
  79. package/dist/src/agent/embedded/run-turn.js.map +1 -1
  80. package/dist/src/agent/embedded/session-tool-result-guard.js +2 -1
  81. package/dist/src/agent/embedded/session-tool-result-guard.js.map +1 -1
  82. package/dist/src/agent/embedded/tool-result-truncation.js +2 -1
  83. package/dist/src/agent/embedded/tool-result-truncation.js.map +1 -1
  84. package/dist/src/agent/fallback/candidates.js +2 -2
  85. package/dist/src/agent/fallback/candidates.js.map +1 -1
  86. package/dist/src/agent/goals/goal-locale.d.ts +1 -1
  87. package/dist/src/agent/goals/goal-run-store.js +4 -4
  88. package/dist/src/agent/goals/persistent-goal-apis.d.ts +0 -2
  89. package/dist/src/agent/goals/persistent-goal-service.js +1 -2
  90. package/dist/src/agent/goals/persistent-goal-service.js.map +1 -1
  91. package/dist/src/agent/goals/post-turn.js +2 -2
  92. package/dist/src/agent/image/generation/normalization.js +2 -12
  93. package/dist/src/agent/image/generation/normalization.js.map +1 -1
  94. package/dist/src/agent/image/generation/provider-registry.d.ts +4 -8
  95. package/dist/src/agent/image/generation/provider-registry.js.map +1 -1
  96. package/dist/src/agent/image/generation/runtime.d.ts +2 -2
  97. package/dist/src/agent/image/generation/runtime.js.map +1 -1
  98. package/dist/src/agent/image/generation/types.d.ts +0 -18
  99. package/dist/src/agent/image/image-helpers.js +6 -1
  100. package/dist/src/agent/image/image-helpers.js.map +1 -1
  101. package/dist/src/agent/image/index.d.ts +1 -1
  102. package/dist/src/agent/image/load-image-media.js +2 -2
  103. package/dist/src/agent/inbound/inbound-loop.d.ts +5 -0
  104. package/dist/src/agent/inbound/inbound-loop.js +41 -10
  105. package/dist/src/agent/inbound/inbound-loop.js.map +1 -1
  106. package/dist/src/agent/inbound/turn-dispatcher.d.ts +4 -0
  107. package/dist/src/agent/inbound/turn-dispatcher.js +7 -5
  108. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  109. package/dist/src/agent/ipc/bus.js +1 -1
  110. package/dist/src/agent/ipc/inbox.js +2 -2
  111. package/dist/src/agent/ipc/socket.js +1 -1
  112. package/dist/src/agent/mcp/bundle-mcp-materialize.js +2 -1
  113. package/dist/src/agent/mcp/bundle-mcp-materialize.js.map +1 -1
  114. package/dist/src/agent/mcp/bundle-mcp-names.js +2 -1
  115. package/dist/src/agent/mcp/bundle-mcp-names.js.map +1 -1
  116. package/dist/src/agent/mcp/bundle-mcp-runtime.js +2 -1
  117. package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
  118. package/dist/src/agent/mcp/mcp-transport-config.js +2 -1
  119. package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
  120. package/dist/src/agent/mcp/mcp-transport.js +2 -1
  121. package/dist/src/agent/mcp/mcp-transport.js.map +1 -1
  122. package/dist/src/agent/media-generation/runtime-shared.js +2 -9
  123. package/dist/src/agent/media-generation/runtime-shared.js.map +1 -1
  124. package/dist/src/agent/memory/builtin-memory-store.js +1 -1
  125. package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
  126. package/dist/src/agent/memory/dreaming/events.js +1 -1
  127. package/dist/src/agent/memory/dreaming/last-run.js +1 -1
  128. package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
  129. package/dist/src/agent/memory/dreaming/preview.js +1 -1
  130. package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
  131. package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
  132. package/dist/src/agent/memory/dreaming/utils.js +1 -1
  133. package/dist/src/agent/memory/plugin-discovery.js +1 -1
  134. package/dist/src/agent/messaging/command-handler.d.ts +6 -0
  135. package/dist/src/agent/messaging/command-handler.js +5 -0
  136. package/dist/src/agent/messaging/command-handler.js.map +1 -1
  137. package/dist/src/agent/models/manager.js +1 -1
  138. package/dist/src/agent/orchestration/llm-turn-retry.d.ts +2 -0
  139. package/dist/src/agent/orchestration/llm-turn-retry.js +9 -1
  140. package/dist/src/agent/orchestration/llm-turn-retry.js.map +1 -1
  141. package/dist/src/agent/prompt/safety.d.ts +0 -7
  142. package/dist/src/agent/prompt/safety.js +1 -20
  143. package/dist/src/agent/prompt/safety.js.map +1 -1
  144. package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
  145. package/dist/src/agent/reply/post-compaction-context.js +1 -1
  146. package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
  147. package/dist/src/agent/sandbox/path-policy.js +2 -2
  148. package/dist/src/agent/service/build-direct-message-content.js +2 -2
  149. package/dist/src/agent/service/build-direct-message-content.js.map +1 -1
  150. package/dist/src/agent/service/direct-turn-helpers.d.ts +3 -1
  151. package/dist/src/agent/service/direct-turn-helpers.js +6 -1
  152. package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
  153. package/dist/src/agent/service/process-direct-one-shot.d.ts +4 -0
  154. package/dist/src/agent/service/process-direct-one-shot.js +15 -2
  155. package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
  156. package/dist/src/agent/service/process-direct-streaming.d.ts +4 -0
  157. package/dist/src/agent/service/process-direct-streaming.js +53 -7
  158. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  159. package/dist/src/agent/service/webchat-tts.d.ts +1 -2
  160. package/dist/src/agent/service/webchat-tts.js +2 -2
  161. package/dist/src/agent/service/webchat-tts.js.map +1 -1
  162. package/dist/src/agent/service.d.ts +8 -0
  163. package/dist/src/agent/service.js +25 -5
  164. package/dist/src/agent/service.js.map +1 -1
  165. package/dist/src/agent/session/session-inspector.js +1 -1
  166. package/dist/src/agent/skills/config.js +1 -1
  167. package/dist/src/agent/skills/hub-hash.js +2 -2
  168. package/dist/src/agent/skills/hub-lock.js +1 -1
  169. package/dist/src/agent/skills/hub-pull.js +2 -2
  170. package/dist/src/agent/skills/index.js +1 -1
  171. package/dist/src/agent/skills/managed-store.js +1 -1
  172. package/dist/src/agent/skills/scanner.js +1 -1
  173. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  174. package/dist/src/agent/skills/skill-manager.js +1 -1
  175. package/dist/src/agent/tools/create-share-tool.js +27 -20
  176. package/dist/src/agent/tools/create-share-tool.js.map +1 -1
  177. package/dist/src/agent/tools/dreaming-tool.js +1 -1
  178. package/dist/src/agent/tools/factory.js +2 -2
  179. package/dist/src/agent/tools/image-generate-tool.js +1 -1
  180. package/dist/src/agent/tools/index.d.ts +0 -1
  181. package/dist/src/agent/tools/index.js +4 -5
  182. package/dist/src/agent/tools/send-media.js +1 -1
  183. package/dist/src/agent/tools/shell.js +0 -13
  184. package/dist/src/agent/tools/shell.js.map +1 -1
  185. package/dist/src/agent/tools/skill-manage-tool.js +1 -1
  186. package/dist/src/agent/tools/workflow-tool.js +70 -16
  187. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  188. package/dist/src/agent/tools/write.js +1 -1
  189. package/dist/src/agent/workflow/agent-progress.d.ts +5 -0
  190. package/dist/src/agent/workflow/agent-progress.js +65 -0
  191. package/dist/src/agent/workflow/agent-progress.js.map +1 -0
  192. package/dist/src/agent/workflow/builtins/audit-repo.d.ts +1 -1
  193. package/dist/src/agent/workflow/builtins/audit-repo.js +14 -0
  194. package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -1
  195. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +1 -1
  196. package/dist/src/agent/workflow/builtins/debug-incident.js +14 -0
  197. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -1
  198. package/dist/src/agent/workflow/builtins/implementation-plan.d.ts +12 -0
  199. package/dist/src/agent/workflow/builtins/implementation-plan.js +175 -0
  200. package/dist/src/agent/workflow/builtins/implementation-plan.js.map +1 -0
  201. package/dist/src/agent/workflow/builtins/index.d.ts +3 -1
  202. package/dist/src/agent/workflow/builtins/index.js +11 -1
  203. package/dist/src/agent/workflow/builtins/index.js.map +1 -1
  204. package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +1 -1
  205. package/dist/src/agent/workflow/builtins/multi-perspective-review.js +14 -0
  206. package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -1
  207. package/dist/src/agent/workflow/builtins/pr-review.d.ts +1 -1
  208. package/dist/src/agent/workflow/builtins/pr-review.js +14 -0
  209. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -1
  210. package/dist/src/agent/workflow/builtins/release-check.d.ts +11 -0
  211. package/dist/src/agent/workflow/builtins/release-check.js +165 -0
  212. package/dist/src/agent/workflow/builtins/release-check.js.map +1 -0
  213. package/dist/src/agent/workflow/builtins/research.d.ts +1 -1
  214. package/dist/src/agent/workflow/builtins/research.js +14 -0
  215. package/dist/src/agent/workflow/builtins/research.js.map +1 -1
  216. package/dist/src/agent/workflow/catalog.js +1 -1
  217. package/dist/src/agent/workflow/channel-capability.d.ts +3 -3
  218. package/dist/src/agent/workflow/index.d.ts +2 -1
  219. package/dist/src/agent/workflow/index.js +3 -2
  220. package/dist/src/agent/workflow/lint.d.ts +38 -0
  221. package/dist/src/agent/workflow/lint.js +74 -0
  222. package/dist/src/agent/workflow/lint.js.map +1 -0
  223. package/dist/src/agent/workflow/meta-locale.d.ts +12 -0
  224. package/dist/src/agent/workflow/meta-locale.js +62 -0
  225. package/dist/src/agent/workflow/meta-locale.js.map +1 -0
  226. package/dist/src/agent/workflow/parser.js +7 -1
  227. package/dist/src/agent/workflow/parser.js.map +1 -1
  228. package/dist/src/agent/workflow/runtime.d.ts +4 -1
  229. package/dist/src/agent/workflow/runtime.js +88 -8
  230. package/dist/src/agent/workflow/runtime.js.map +1 -1
  231. package/dist/src/agent/workflow/snapshot.js +2 -12
  232. package/dist/src/agent/workflow/snapshot.js.map +1 -1
  233. package/dist/src/agent/workflow/step-labels.d.ts +8 -0
  234. package/dist/src/agent/workflow/step-labels.js +48 -0
  235. package/dist/src/agent/workflow/step-labels.js.map +1 -0
  236. package/dist/src/agent/workflow/subagent-runner.js +46 -1
  237. package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
  238. package/dist/src/agent/workflow/types.d.ts +76 -1
  239. package/dist/src/auth/credentials.d.ts +5 -0
  240. package/dist/src/auth/credentials.js +12 -3
  241. package/dist/src/auth/credentials.js.map +1 -1
  242. package/dist/src/auth/profiles/store.js +1 -1
  243. package/dist/src/auth/sync-provider-auth.js +1 -1
  244. package/dist/src/browser/cache-dir-policy.js +1 -1
  245. package/dist/src/browser/cdp-local-launcher.js +2 -2
  246. package/dist/src/browser/index.js +4 -4
  247. package/dist/src/browser/manager.d.ts +1 -3
  248. package/dist/src/browser/manager.js +0 -6
  249. package/dist/src/browser/manager.js.map +1 -1
  250. package/dist/src/browser/providers/browser-ext-install.d.ts +4 -4
  251. package/dist/src/browser/providers/browser-ext-install.js +41 -88
  252. package/dist/src/browser/providers/browser-ext-install.js.map +1 -1
  253. package/dist/src/browser/providers/cloakbrowser.d.ts +0 -5
  254. package/dist/src/browser/providers/cloakbrowser.js +6 -59
  255. package/dist/src/browser/providers/cloakbrowser.js.map +1 -1
  256. package/dist/src/browser/providers/playwright-doctor.js +1 -1
  257. package/dist/src/browser/stealth.js +1 -1
  258. package/dist/src/channels/attachments/inbound-persist.js +1 -1
  259. package/dist/src/channels/attachments/outbound-tts-persist.js +1 -1
  260. package/dist/src/channels/attachments/voice-stt-webchat.js +10 -8
  261. package/dist/src/channels/attachments/voice-stt-webchat.js.map +1 -1
  262. package/dist/src/channels/outbound/persist-store.js +1 -1
  263. package/dist/src/channels/pairing/allow-from-file.js +9 -9
  264. package/dist/src/channels/pairing/allow-from-file.js.map +1 -1
  265. package/dist/src/channels/pairing/pairing-store.js +7 -7
  266. package/dist/src/channels/pairing/pairing-store.js.map +1 -1
  267. package/dist/src/chat-commands/builtins/config.js +2 -2
  268. package/dist/src/chat-commands/builtins/session.js +1 -1
  269. package/dist/src/chat-commands/builtins/session.js.map +1 -1
  270. package/dist/src/chat-commands/builtins/tts.js +2 -2
  271. package/dist/src/chat-commands/builtins/tts.js.map +1 -1
  272. package/dist/src/chat-commands/context.d.ts +3 -0
  273. package/dist/src/chat-commands/context.js +22 -4
  274. package/dist/src/chat-commands/context.js.map +1 -1
  275. package/dist/src/chat-commands/session-key.d.ts +4 -37
  276. package/dist/src/chat-commands/session-key.js +49 -85
  277. package/dist/src/chat-commands/session-key.js.map +1 -1
  278. package/dist/src/chat-commands/types.d.ts +2 -0
  279. package/dist/src/cli/commands/agent/interactive.js +2 -2
  280. package/dist/src/cli/commands/agent/interactive.js.map +1 -1
  281. package/dist/src/cli/commands/agent/sessions.js +2 -2
  282. package/dist/src/cli/commands/agent/sessions.js.map +1 -1
  283. package/dist/src/cli/commands/agent.js +4 -5
  284. package/dist/src/cli/commands/agent.js.map +1 -1
  285. package/dist/src/cli/commands/channels.js +1 -5
  286. package/dist/src/cli/commands/channels.js.map +1 -1
  287. package/dist/src/cli/commands/config.js +1 -1
  288. package/dist/src/cli/commands/doctor/checks/config-health.js +1 -1
  289. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  290. package/dist/src/cli/commands/doctor/checks/session-integrity.js +1 -1
  291. package/dist/src/cli/commands/doctor/checks/state-integrity.js +1 -1
  292. package/dist/src/cli/commands/doctor/checks/workspace-status.js +1 -1
  293. package/dist/src/cli/commands/extension-dev.js +1 -1
  294. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  295. package/dist/src/cli/commands/extension-pack.js +1 -1
  296. package/dist/src/cli/commands/gateway/lifecycle-core.js +1 -1
  297. package/dist/src/cli/commands/gateway/lifecycle-core.js.map +1 -1
  298. package/dist/src/cli/commands/gateway/logs.d.ts +9 -0
  299. package/dist/src/cli/commands/gateway/logs.js +50 -17
  300. package/dist/src/cli/commands/gateway/logs.js.map +1 -1
  301. package/dist/src/cli/commands/image.js +23 -22
  302. package/dist/src/cli/commands/image.js.map +1 -1
  303. package/dist/src/cli/commands/init.js +4 -4
  304. package/dist/src/cli/commands/onboard.js +1 -1
  305. package/dist/src/cli/commands/session/utils.js +2 -2
  306. package/dist/src/cli/commands/session/utils.js.map +1 -1
  307. package/dist/src/cli/commands/update.js +26 -46
  308. package/dist/src/cli/commands/update.js.map +1 -1
  309. package/dist/src/cli/utils/init-workspace-core.js +2 -2
  310. package/dist/src/cli/utils/session.d.ts +0 -5
  311. package/dist/src/cli/utils/session.js +1 -6
  312. package/dist/src/cli/utils/session.js.map +1 -1
  313. package/dist/src/commands/agents.config.js +1 -1
  314. package/dist/src/commands/agents.config.js.map +1 -1
  315. package/dist/src/config/agent-profile.js +6 -28
  316. package/dist/src/config/agent-profile.js.map +1 -1
  317. package/dist/src/config/agent-typed-models.d.ts +18 -0
  318. package/dist/src/config/agent-typed-models.js +53 -0
  319. package/dist/src/config/agent-typed-models.js.map +1 -0
  320. package/dist/src/config/gateway-bind.js +1 -1
  321. package/dist/src/config/index.js +6 -6
  322. package/dist/src/config/loader.js +2 -2
  323. package/dist/src/config/model-input.js +2 -5
  324. package/dist/src/config/model-input.js.map +1 -1
  325. package/dist/src/config/models-json.js +2 -2
  326. package/dist/src/config/paths-state.js +1 -1
  327. package/dist/src/config/profile.js +2 -2
  328. package/dist/src/config/schema.d.ts +253 -217
  329. package/dist/src/config/schema.js +91 -40
  330. package/dist/src/config/schema.js.map +1 -1
  331. package/dist/src/config/voice.d.ts +3 -28
  332. package/dist/src/config/voice.js +27 -261
  333. package/dist/src/config/voice.js.map +1 -1
  334. package/dist/src/config/workspace-path-helpers.d.ts +1 -2
  335. package/dist/src/config/workspace-path-helpers.js.map +1 -1
  336. package/dist/src/config/workspace-path.js +1 -1
  337. package/dist/src/cron/executor.js +2 -2
  338. package/dist/src/cron/persistence.js +1 -1
  339. package/dist/src/cron/run-log-store.js +1 -1
  340. package/dist/src/daemon/constants.js +1 -1
  341. package/dist/src/daemon/install-plan.js +27 -3
  342. package/dist/src/daemon/install-plan.js.map +1 -1
  343. package/dist/src/daemon/launchd.d.ts +8 -0
  344. package/dist/src/daemon/launchd.js +7 -14
  345. package/dist/src/daemon/launchd.js.map +1 -1
  346. package/dist/src/daemon/schtasks.d.ts +25 -0
  347. package/dist/src/daemon/schtasks.js +168 -48
  348. package/dist/src/daemon/schtasks.js.map +1 -1
  349. package/dist/src/daemon/service.js +5 -4
  350. package/dist/src/daemon/service.js.map +1 -1
  351. package/dist/src/daemon/systemd.d.ts +6 -0
  352. package/dist/src/daemon/systemd.js +20 -5
  353. package/dist/src/daemon/systemd.js.map +1 -1
  354. package/dist/src/extensions/activation-context.js +0 -1
  355. package/dist/src/extensions/activation-context.js.map +1 -1
  356. package/dist/src/extensions/bundle-mcp.js +1 -1
  357. package/dist/src/extensions/discover-extensions.js +1 -1
  358. package/dist/src/extensions/health.js +1 -1
  359. package/dist/src/extensions/loader.js +1 -1
  360. package/dist/src/extensions/lockfile.js +2 -2
  361. package/dist/src/extensions/normalize-manifest.js +0 -1
  362. package/dist/src/extensions/normalize-manifest.js.map +1 -1
  363. package/dist/src/extensions/types/manifest.d.ts +0 -2
  364. package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
  365. package/dist/src/gateway/agent-builtin-tools.js +1 -0
  366. package/dist/src/gateway/agent-builtin-tools.js.map +1 -1
  367. package/dist/src/gateway/agents-admin.d.ts +9 -0
  368. package/dist/src/gateway/agents-admin.js +28 -4
  369. package/dist/src/gateway/agents-admin.js.map +1 -1
  370. package/dist/src/gateway/config-tools-web.js +3 -2
  371. package/dist/src/gateway/config-tools-web.js.map +1 -1
  372. package/dist/src/gateway/file-path-classifier.js +2 -2
  373. package/dist/src/gateway/heartbeat/service.js +2 -2
  374. package/dist/src/gateway/heartbeat/service.js.map +1 -1
  375. package/dist/src/gateway/hono/app.js +1 -1
  376. package/dist/src/gateway/hono/lib/agent-model.d.ts +25 -10
  377. package/dist/src/gateway/hono/lib/agent-model.js +60 -36
  378. package/dist/src/gateway/hono/lib/agent-model.js.map +1 -1
  379. package/dist/src/gateway/hono/lib/config-payload.js +29 -6
  380. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  381. package/dist/src/gateway/hono/lib/extension-store.js +2 -2
  382. package/dist/src/gateway/hono/lib/mask-secret-length.d.ts +6 -0
  383. package/dist/src/gateway/hono/lib/mask-secret-length.js +16 -0
  384. package/dist/src/gateway/hono/lib/mask-secret-length.js.map +1 -0
  385. package/dist/src/gateway/hono/lib/safe-providers-config.d.ts +1 -1
  386. package/dist/src/gateway/hono/lib/safe-providers-config.js +2 -1
  387. package/dist/src/gateway/hono/lib/safe-providers-config.js.map +1 -1
  388. package/dist/src/gateway/hono/lib/safe-voice-config.js +16 -54
  389. package/dist/src/gateway/hono/lib/safe-voice-config.js.map +1 -1
  390. package/dist/src/gateway/hono/lib/static-ui.js +2 -2
  391. package/dist/src/gateway/hono/oauth.js +1 -1
  392. package/dist/src/gateway/hono/routes/agents.js +2 -2
  393. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  394. package/dist/src/gateway/hono/routes/config-patch/agents.js +25 -7
  395. package/dist/src/gateway/hono/routes/config-patch/agents.js.map +1 -1
  396. package/dist/src/gateway/hono/routes/config-patch/channels.js +0 -11
  397. package/dist/src/gateway/hono/routes/config-patch/channels.js.map +1 -1
  398. package/dist/src/gateway/hono/routes/config-patch/gateway.js +3 -2
  399. package/dist/src/gateway/hono/routes/config-patch/gateway.js.map +1 -1
  400. package/dist/src/gateway/hono/routes/config-patch/misc.js +8 -3
  401. package/dist/src/gateway/hono/routes/config-patch/misc.js.map +1 -1
  402. package/dist/src/gateway/hono/routes/config.js +59 -0
  403. package/dist/src/gateway/hono/routes/config.js.map +1 -1
  404. package/dist/src/gateway/hono/routes/dreaming.js +1 -1
  405. package/dist/src/gateway/hono/routes/goals.js +1 -1
  406. package/dist/src/gateway/hono/routes/goals.js.map +1 -1
  407. package/dist/src/gateway/hono/routes/host-fs.js +2 -2
  408. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  409. package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
  410. package/dist/src/gateway/hono/routes/models.js +75 -12
  411. package/dist/src/gateway/hono/routes/models.js.map +1 -1
  412. package/dist/src/gateway/hono/routes/sessions.js +28 -7
  413. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  414. package/dist/src/gateway/hono/routes/shares.js +15 -13
  415. package/dist/src/gateway/hono/routes/shares.js.map +1 -1
  416. package/dist/src/gateway/hono/routes/tunnel.js +1 -1
  417. package/dist/src/gateway/hono/routes/update.js +4 -2
  418. package/dist/src/gateway/hono/routes/update.js.map +1 -1
  419. package/dist/src/gateway/hono/routes/voice.js +75 -0
  420. package/dist/src/gateway/hono/routes/voice.js.map +1 -1
  421. package/dist/src/gateway/hono/routes/workflows.d.ts +3 -0
  422. package/dist/src/gateway/hono/routes/workflows.js +347 -0
  423. package/dist/src/gateway/hono/routes/workflows.js.map +1 -0
  424. package/dist/src/gateway/hono/routes/workspace.js +4 -4
  425. package/dist/src/gateway/hono/sse.js +16 -33
  426. package/dist/src/gateway/hono/sse.js.map +1 -1
  427. package/dist/src/gateway/lock.js +11 -11
  428. package/dist/src/gateway/lock.js.map +1 -1
  429. package/dist/src/gateway/ports.js +6 -6
  430. package/dist/src/gateway/ports.js.map +1 -1
  431. package/dist/src/gateway/resolve-webchat-session-key.d.ts +19 -0
  432. package/dist/src/gateway/resolve-webchat-session-key.js +46 -0
  433. package/dist/src/gateway/resolve-webchat-session-key.js.map +1 -0
  434. package/dist/src/gateway/service/agent-runner.js +2 -2
  435. package/dist/src/gateway/service/marketplace-service.js +2 -2
  436. package/dist/src/gateway/service/run-gateway-agent.js +9 -11
  437. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  438. package/dist/src/gateway/service/sessions-api.d.ts +3 -0
  439. package/dist/src/gateway/service/sessions-api.js +8 -0
  440. package/dist/src/gateway/service/sessions-api.js.map +1 -1
  441. package/dist/src/gateway/service.d.ts +3 -2
  442. package/dist/src/gateway/service.js +9 -8
  443. package/dist/src/gateway/service.js.map +1 -1
  444. package/dist/src/gateway/session-reset-service.d.ts +20 -0
  445. package/dist/src/gateway/session-reset-service.js +54 -0
  446. package/dist/src/gateway/session-reset-service.js.map +1 -0
  447. package/dist/src/gateway/startup-readiness.d.ts +1 -1
  448. package/dist/src/gateway/startup-readiness.js +1 -0
  449. package/dist/src/gateway/startup-readiness.js.map +1 -1
  450. package/dist/src/gateway/workspace-fs-file-list.js +1 -1
  451. package/dist/src/heartbeat/index.js +1 -1
  452. package/dist/src/infra/gateway-processes.js +2 -2
  453. package/dist/src/infra/gateway-processes.js.map +1 -1
  454. package/dist/src/infra/restart.js +2 -2
  455. package/dist/src/infra/run-command.d.ts +16 -0
  456. package/dist/src/infra/run-command.js +67 -0
  457. package/dist/src/infra/run-command.js.map +1 -0
  458. package/dist/src/infra/update-check.js +1 -1
  459. package/dist/src/infra/update-global.d.ts +45 -0
  460. package/dist/src/infra/update-global.js +224 -0
  461. package/dist/src/infra/update-global.js.map +1 -0
  462. package/dist/src/infra/update-lock.js +3 -3
  463. package/dist/src/infra/update-runner.js +1 -1
  464. package/dist/src/infra/update-startup.js +2 -2
  465. package/dist/src/infra/write-file-atomic.js +2 -2
  466. package/dist/src/mcp/channel-shared.js +2 -1
  467. package/dist/src/mcp/channel-shared.js.map +1 -1
  468. package/dist/src/providers/auth-runtime/auth-profile-store.js +2 -2
  469. package/dist/src/providers/auth-runtime/auth-profile-store.js.map +1 -1
  470. package/dist/src/providers/auth-runtime/resolve-auth.js +1 -12
  471. package/dist/src/providers/auth-runtime/resolve-auth.js.map +1 -1
  472. package/dist/src/providers/auth-runtime/types.d.ts +6 -12
  473. package/dist/src/providers/index.js +2 -2
  474. package/dist/src/providers/model-registry.js +1 -1
  475. package/dist/src/routing/agent-session-key.d.ts +58 -0
  476. package/dist/src/routing/agent-session-key.js +164 -0
  477. package/dist/src/routing/agent-session-key.js.map +1 -0
  478. package/dist/src/routing/index.d.ts +1 -1
  479. package/dist/src/routing/index.js +4 -2
  480. package/dist/src/routing/index.js.map +1 -1
  481. package/dist/src/routing/resolve-route.d.ts +15 -0
  482. package/dist/src/routing/resolve-route.js +41 -20
  483. package/dist/src/routing/resolve-route.js.map +1 -1
  484. package/dist/src/routing/resolve-tui-session-key.d.ts +25 -0
  485. package/dist/src/routing/resolve-tui-session-key.js +54 -0
  486. package/dist/src/routing/resolve-tui-session-key.js.map +1 -0
  487. package/dist/src/routing/session-key-utils.d.ts +24 -0
  488. package/dist/src/routing/session-key-utils.js +92 -0
  489. package/dist/src/routing/session-key-utils.js.map +1 -0
  490. package/dist/src/routing/session-key.d.ts +19 -49
  491. package/dist/src/routing/session-key.js +143 -116
  492. package/dist/src/routing/session-key.js.map +1 -1
  493. package/dist/src/session/config-store.js +2 -2
  494. package/dist/src/session/index.d.ts +6 -0
  495. package/dist/src/session/index.js +7 -1
  496. package/dist/src/session/init-session-turn.d.ts +30 -0
  497. package/dist/src/session/init-session-turn.js +102 -0
  498. package/dist/src/session/init-session-turn.js.map +1 -0
  499. package/dist/src/session/lifecycle-timestamps.d.ts +8 -0
  500. package/dist/src/session/lifecycle-timestamps.js +16 -0
  501. package/dist/src/session/lifecycle-timestamps.js.map +1 -0
  502. package/dist/src/session/manager.d.ts +7 -1
  503. package/dist/src/session/manager.js +8 -1
  504. package/dist/src/session/manager.js.map +1 -1
  505. package/dist/src/session/parity/jsonl-transcript-io.js +2 -2
  506. package/dist/src/session/parity/sessions-json-file.js +1 -1
  507. package/dist/src/session/parity/transcript-file-lock.js +2 -2
  508. package/dist/src/session/parity/transcript-paths.js +2 -2
  509. package/dist/src/session/parity/transcript-paths.js.map +1 -1
  510. package/dist/src/session/parity/xopc-session-disk-entry.d.ts +6 -0
  511. package/dist/src/session/reset-policy.d.ts +32 -0
  512. package/dist/src/session/reset-policy.js +65 -0
  513. package/dist/src/session/reset-policy.js.map +1 -0
  514. package/dist/src/session/reset-triggers.d.ts +20 -0
  515. package/dist/src/session/reset-triggers.js +63 -0
  516. package/dist/src/session/reset-triggers.js.map +1 -0
  517. package/dist/src/session/reset-type.d.ts +12 -0
  518. package/dist/src/session/reset-type.js +25 -0
  519. package/dist/src/session/reset-type.js.map +1 -0
  520. package/dist/src/session/resolve-session.d.ts +30 -0
  521. package/dist/src/session/resolve-session.js +93 -0
  522. package/dist/src/session/resolve-session.js.map +1 -0
  523. package/dist/src/session/search-index-cache.js +1 -1
  524. package/dist/src/session/search-index.js +1 -1
  525. package/dist/src/session/session-title.js +3 -2
  526. package/dist/src/session/session-title.js.map +1 -1
  527. package/dist/src/session/store.d.ts +11 -4
  528. package/dist/src/session/store.js +62 -11
  529. package/dist/src/session/store.js.map +1 -1
  530. package/dist/src/session/transcript-events.js +2 -1
  531. package/dist/src/session/transcript-events.js.map +1 -1
  532. package/dist/src/share/share-auto.js +2 -2
  533. package/dist/src/share/share-store.js +3 -3
  534. package/dist/src/share/share-thumbnail.js +2 -2
  535. package/dist/src/share/share-url.d.ts +33 -0
  536. package/dist/src/share/share-url.js +56 -14
  537. package/dist/src/share/share-url.js.map +1 -1
  538. package/dist/src/share/share-zip.js +1 -1
  539. package/dist/src/share/site-share-store.js +3 -3
  540. package/dist/src/share/site-static-serve.js +1 -1
  541. package/dist/src/tui/backends/embedded-backend.js +4 -9
  542. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  543. package/dist/src/tui/backends/gateway-sse-backend.js +1 -1
  544. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  545. package/dist/src/tui/clipboard-image.js +3 -3
  546. package/dist/src/tui/components/chat-log.js +3 -3
  547. package/dist/src/tui/components/chat-log.js.map +1 -1
  548. package/dist/src/tui/theme-manager.js +1 -1
  549. package/dist/src/tui/theme.d.ts +0 -2
  550. package/dist/src/tui/theme.js +1 -3
  551. package/dist/src/tui/theme.js.map +1 -1
  552. package/dist/src/tui/tui-agent-events.js +2 -1
  553. package/dist/src/tui/tui-agent-events.js.map +1 -1
  554. package/dist/src/tui/tui-commands.d.ts +3 -0
  555. package/dist/src/tui/tui-commands.js +45 -10
  556. package/dist/src/tui/tui-commands.js.map +1 -1
  557. package/dist/src/tui/tui-keybindings-file.js +2 -22
  558. package/dist/src/tui/tui-keybindings-file.js.map +1 -1
  559. package/dist/src/tui/tui-scoped-models.js +2 -2
  560. package/dist/src/tui/tui-session-actions.d.ts +28 -0
  561. package/dist/src/tui/tui-session-actions.js +88 -0
  562. package/dist/src/tui/tui-session-actions.js.map +1 -0
  563. package/dist/src/tui/tui-settings.js +1 -1
  564. package/dist/src/tui/tui.js +54 -49
  565. package/dist/src/tui/tui.js.map +1 -1
  566. package/dist/src/tunnel/frpc-binary.js +3 -3
  567. package/dist/src/tunnel/frpc-config.js +1 -1
  568. package/dist/src/tunnel/frpc-extract.js +1 -1
  569. package/dist/src/tunnel/tunnel-state.js +1 -1
  570. package/dist/src/utils/logger/audit.js +1 -1
  571. package/dist/src/utils/logger/log-store.js +1 -1
  572. package/dist/src/utils/logger/rotation.js +1 -1
  573. package/dist/src/utils/string-coerce.d.ts +2 -0
  574. package/dist/src/utils/string-coerce.js +10 -1
  575. package/dist/src/utils/string-coerce.js.map +1 -1
  576. package/dist/src/voice/metadata/builtin.d.ts +2 -0
  577. package/dist/src/voice/metadata/builtin.js +420 -0
  578. package/dist/src/voice/metadata/builtin.js.map +1 -0
  579. package/dist/src/voice/metadata/index.d.ts +4 -0
  580. package/dist/src/voice/metadata/index.js +3 -0
  581. package/dist/src/voice/metadata/registry.d.ts +5 -0
  582. package/dist/src/voice/metadata/registry.js +34 -0
  583. package/dist/src/voice/metadata/registry.js.map +1 -0
  584. package/dist/src/voice/metadata/types.d.ts +41 -0
  585. package/dist/src/voice/metadata/types.js +1 -0
  586. package/dist/src/voice/stt/config-slice.d.ts +2 -5
  587. package/dist/src/voice/stt/config-slice.js +5 -26
  588. package/dist/src/voice/stt/config-slice.js.map +1 -1
  589. package/dist/src/voice/stt/list-providers.d.ts +3 -3
  590. package/dist/src/voice/stt/list-providers.js +41 -6
  591. package/dist/src/voice/stt/list-providers.js.map +1 -1
  592. package/dist/src/voice/stt/types.d.ts +1 -18
  593. package/dist/src/voice/stt/types.js +4 -2
  594. package/dist/src/voice/stt/types.js.map +1 -1
  595. package/dist/src/voice/tts/audio.js +1 -1
  596. package/dist/src/voice/tts/config-slice.d.ts +3 -7
  597. package/dist/src/voice/tts/config-slice.js +7 -38
  598. package/dist/src/voice/tts/config-slice.js.map +1 -1
  599. package/dist/src/voice/tts/list-providers.d.ts +3 -3
  600. package/dist/src/voice/tts/list-providers.js +41 -6
  601. package/dist/src/voice/tts/list-providers.js.map +1 -1
  602. package/dist/src/voice/tts/merge-config.js +2 -48
  603. package/dist/src/voice/tts/merge-config.js.map +1 -1
  604. package/dist/src/voice/tts/providers/alibaba-speech.js +1 -1
  605. package/dist/src/voice/tts/providers/alibaba-speech.js.map +1 -1
  606. package/dist/src/voice/tts/providers/edge-speech.js +2 -2
  607. package/dist/src/voice/tts/types.d.ts +1 -29
  608. package/dist/src/voice/tts/types.js +19 -17
  609. package/dist/src/voice/tts/types.js.map +1 -1
  610. package/dist/src/workflows/domain/command.d.ts +18 -0
  611. package/dist/src/workflows/domain/command.js +1 -0
  612. package/dist/src/workflows/domain/definition.d.ts +62 -0
  613. package/dist/src/workflows/domain/definition.js +1 -0
  614. package/dist/src/workflows/domain/event.d.ts +67 -0
  615. package/dist/src/workflows/domain/event.js +1 -0
  616. package/dist/src/workflows/domain/index.d.ts +5 -0
  617. package/dist/src/workflows/domain/index.js +2 -0
  618. package/dist/src/workflows/domain/result.d.ts +65 -0
  619. package/dist/src/workflows/domain/result.js +1 -0
  620. package/dist/src/workflows/domain/run.d.ts +120 -0
  621. package/dist/src/workflows/domain/run.js +14 -0
  622. package/dist/src/workflows/domain/run.js.map +1 -0
  623. package/dist/src/workflows/engine/index.d.ts +2 -0
  624. package/dist/src/workflows/engine/index.js +3 -0
  625. package/dist/src/workflows/engine/projector.d.ts +3 -0
  626. package/dist/src/workflows/engine/projector.js +205 -0
  627. package/dist/src/workflows/engine/projector.js.map +1 -0
  628. package/dist/src/workflows/engine/workflow-engine.d.ts +31 -0
  629. package/dist/src/workflows/engine/workflow-engine.js +188 -0
  630. package/dist/src/workflows/engine/workflow-engine.js.map +1 -0
  631. package/dist/src/workflows/index.d.ts +6 -0
  632. package/dist/src/workflows/index.js +11 -0
  633. package/dist/src/workflows/runtime/index.d.ts +1 -0
  634. package/dist/src/workflows/runtime/index.js +4 -0
  635. package/dist/src/workflows/runtime/script-runtime.d.ts +3 -0
  636. package/dist/src/workflows/runtime/script-runtime.js +3 -0
  637. package/dist/src/workflows/store/event-store.d.ts +17 -0
  638. package/dist/src/workflows/store/event-store.js +83 -0
  639. package/dist/src/workflows/store/event-store.js.map +1 -0
  640. package/dist/src/workflows/store/paths.d.ts +7 -0
  641. package/dist/src/workflows/store/paths.js +26 -0
  642. package/dist/src/workflows/store/paths.js.map +1 -0
  643. package/dist/src/workflows/store/run-store.d.ts +13 -0
  644. package/dist/src/workflows/store/run-store.js +68 -0
  645. package/dist/src/workflows/store/run-store.js.map +1 -0
  646. package/package.json +5 -8
  647. package/dist/gateway/static/root/assets/agents-mS3_HpRI.js +0 -222
  648. package/dist/gateway/static/root/assets/channels-settings-BG6b9KrW.js +0 -1
  649. package/dist/gateway/static/root/assets/extension-settings-page-B-W4x2xP.js +0 -1
  650. package/dist/gateway/static/root/assets/index-ew_2L2We.css +0 -1
  651. package/dist/gateway/static/root/assets/sessions-page-FaG_Vlkb.js +0 -1
  652. package/dist/gateway/static/root/assets/settings-page-Bet1OerL.js +0 -3
  653. package/dist/gateway/static/root/assets/skills-page-DhUO235y.js +0 -2
  654. package/dist/gateway/static/root/assets/url-BwNL6Rgk.js +0 -3
  655. package/dist/gateway/static/root/assets/voice-api-key-field-CGEydndO.js +0 -1
  656. package/dist/src/agent/tools/browser-legacy-tools.d.ts +0 -17
  657. package/dist/src/agent/tools/browser-legacy-tools.js +0 -766
  658. package/dist/src/agent/tools/browser-legacy-tools.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflows.js","names":["resolveModelById"],"sources":["../../../../../src/gateway/hono/routes/workflows.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { Hono } from 'hono';\n\nimport { resolveDefaultAgentId } from '../../../agent/agent-scope.js';\nimport type { BuildChildToolsOptions } from '../../../agent/child-agent-factory.js';\nimport { AgentToolsFactory } from '../../../agent/tools/factory.js';\nimport { createWorkflowCatalog } from '../../../agent/workflow/catalog.js';\nimport { DelegateSubagentRunner } from '../../../agent/workflow/subagent-runner.js';\nimport type { WorkflowDefinition, WorkflowRunSource, WorkflowRunSummary } from '../../../workflows/domain/index.js';\nimport { isTerminalWorkflowRunStatus } from '../../../workflows/domain/run.js';\nimport { WorkflowEngine, WorkflowEventStore, WorkflowRunStore } from '../../../workflows/index.js';\nimport { extractProfileAgentId } from '../../../config/agent-profile.js';\nimport { resolveModelRef } from '../../../config/agent-typed-models.js';\nimport { resolveModel as resolveModelById } from '../../../providers/index.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst DEFAULT_WORKFLOW_CONCURRENCY = 4;\nconst DEFAULT_WORKFLOW_TIMEOUT_SEC = 30 * 60;\nconst DEFAULT_WORKFLOW_MAX_SUBAGENTS = 100;\n\nconst activeWorkflowRuns = new Map<string, AbortController>();\n\ninterface StartWorkflowRunRequestBody {\n definitionId?: string;\n input?: unknown;\n goal?: string;\n agentId?: string;\n sessionKey?: string;\n source?: WorkflowRunSource;\n concurrency?: number;\n maxSubagents?: number;\n tokenBudget?: number | null;\n}\n\ninterface SaveWorkflowDefinitionRequestBody {\n name?: string;\n script?: string;\n}\n\nexport function registerWorkflowRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service } = deps;\n\n authenticated.get('/api/workflows/definitions', (c) => {\n const catalog = createWorkflowCatalog();\n const definitions = catalog.list().map((entry) => {\n try {\n return toWorkflowDefinition(catalog.load(entry.name));\n } catch {\n return null;\n }\n }).filter((definition): definition is WorkflowDefinition => Boolean(definition));\n\n return c.json({ definitions });\n });\n\n authenticated.get('/api/workflows/definitions/:id', (c) => {\n const id = c.req.param('id');\n const catalog = createWorkflowCatalog();\n try {\n const definition = toWorkflowDefinition(catalog.load(id));\n return c.json({ definition });\n } catch (err) {\n return c.json({ error: err instanceof Error ? err.message : 'Workflow definition not found' }, 404);\n }\n });\n\n authenticated.post('/api/workflows/definitions', async (c) => {\n const body = await readJsonBody<SaveWorkflowDefinitionRequestBody>(c.req.raw);\n const name = body.name?.trim();\n const script = body.script?.trim();\n if (!name) {\n return c.json({ error: 'name is required' }, 400);\n }\n if (!script) {\n return c.json({ error: 'script is required' }, 400);\n }\n\n const catalog = createWorkflowCatalog();\n try {\n catalog.save(name, script);\n const definition = toWorkflowDefinition(catalog.load(name));\n return c.json({ definition }, 201);\n } catch (err) {\n return c.json({ error: err instanceof Error ? err.message : 'Failed to save workflow' }, 400);\n }\n });\n\n authenticated.delete('/api/workflows/definitions/:id', (c) => {\n const id = c.req.param('id').trim();\n if (!id) {\n return c.json({ error: 'id is required' }, 400);\n }\n\n const catalog = createWorkflowCatalog();\n try {\n const removed = catalog.remove(id);\n if (!removed) {\n return c.json({ error: 'User workflow not found or cannot delete built-in workflow' }, 404);\n }\n return c.json({ removed: true });\n } catch (err) {\n return c.json({ error: err instanceof Error ? err.message : 'Failed to delete workflow' }, 400);\n }\n });\n\n authenticated.get('/api/workflows/stats', async (c) => {\n const agentId = getAgentId(c.req.query('agentId'), service.currentConfig);\n const runStore = createRunStore(service.currentConfig, agentId);\n const runs = await runStore.listRunSummaries(500);\n return c.json({ stats: buildWorkflowStats(runs) });\n });\n\n authenticated.post('/api/workflows/runs', async (c) => {\n const body = await readJsonBody<StartWorkflowRunRequestBody>(c.req.raw);\n const definitionId = body.definitionId?.trim();\n if (!definitionId) {\n return c.json({ error: 'definitionId is required' }, 400);\n }\n\n const agentId = getAgentId(body.agentId ?? c.req.query('agentId'), service.currentConfig);\n const sessionKey = body.sessionKey?.trim() || `workflow:${agentId}`;\n const runId = await queueWorkflowRun({\n deps,\n agentId,\n sessionKey,\n definitionId,\n input: body.input,\n goal: body.goal,\n source: body.source ?? { kind: 'webui' },\n concurrency: normalizePositiveInteger(body.concurrency),\n maxSubagents: normalizePositiveInteger(body.maxSubagents),\n tokenBudget: body.tokenBudget,\n });\n\n if (!runId) {\n return c.json({ error: 'Workflow definition not found' }, 404);\n }\n\n return c.json({ runId }, 202);\n });\n\n authenticated.get('/api/workflows/runs', async (c) => {\n const agentId = getAgentId(c.req.query('agentId'), service.currentConfig);\n const rawLimit = c.req.query('limit');\n const limit = rawLimit ? Number.parseInt(rawLimit, 10) : 50;\n const runStore = createRunStore(service.currentConfig, agentId);\n const runs = await runStore.listRunSummaries(Number.isFinite(limit) ? limit : 50);\n return c.json({ runs });\n });\n\n authenticated.post('/api/workflows/runs/:runId/cancel', async (c) => {\n const runId = c.req.param('runId');\n const agentId = getAgentId(c.req.query('agentId'), service.currentConfig);\n const runStore = createRunStore(service.currentConfig, agentId);\n\n const controller = activeWorkflowRuns.get(runId);\n if (controller) {\n controller.abort();\n activeWorkflowRuns.delete(runId);\n return c.json({ cancelled: true });\n }\n\n const view = await runStore.readRunView(runId);\n if (!view) {\n return c.json({ error: 'Workflow run not found' }, 404);\n }\n\n if (isTerminalWorkflowRunStatus(view.run.status)) {\n return c.json({ cancelled: true, alreadyFinished: true });\n }\n\n const eventStore = new WorkflowEventStore(service.currentConfig, agentId);\n await eventStore.append({\n runId,\n type: 'run_cancelled',\n payload: { reason: 'Cancelled by user' },\n });\n const updated = await runStore.rebuildRunView(runId);\n if (updated) {\n service.emit('workflow.run.updated', { runId, view: updated });\n }\n return c.json({ cancelled: true });\n });\n\n authenticated.get('/api/workflows/runs/:runId', async (c) => {\n const agentId = getAgentId(c.req.query('agentId'), service.currentConfig);\n const runId = c.req.param('runId');\n const runStore = createRunStore(service.currentConfig, agentId);\n const view = await runStore.readRunView(runId);\n if (!view) {\n return c.json({ error: 'Workflow run not found' }, 404);\n }\n return c.json({ view });\n });\n\n authenticated.post('/api/workflows/runs/:runId/rebuild', async (c) => {\n const agentId = getAgentId(c.req.query('agentId'), service.currentConfig);\n const runId = c.req.param('runId');\n const runStore = createRunStore(service.currentConfig, agentId);\n const view = await runStore.rebuildRunView(runId);\n if (!view) {\n return c.json({ error: 'Workflow run not found' }, 404);\n }\n service.emit('workflow.run.updated', { runId, view });\n return c.json({ view });\n });\n\n authenticated.post('/api/workflows/runs/:runId/retry', async (c) => {\n const agentId = getAgentId(c.req.query('agentId'), service.currentConfig);\n const runId = c.req.param('runId');\n const runStore = createRunStore(service.currentConfig, agentId);\n const existing = await runStore.readRunView(runId);\n if (!existing) {\n return c.json({ error: 'Workflow run not found' }, 404);\n }\n\n const sessionKey = `workflow:${agentId}`;\n const newRunId = await queueWorkflowRun({\n deps,\n agentId,\n sessionKey,\n definitionId: existing.run.definitionId,\n input: existing.run.input,\n goal: existing.run.goal,\n source: { kind: 'webui' },\n });\n\n if (!newRunId) {\n return c.json({ error: 'Workflow definition not found' }, 404);\n }\n\n return c.json({ runId: newRunId }, 202);\n });\n}\n\nfunction createRunStore(config: AuthenticatedRouteDeps['service']['currentConfig'], agentId: string): WorkflowRunStore {\n const eventStore = new WorkflowEventStore(config, agentId);\n return new WorkflowRunStore(config, agentId, eventStore);\n}\n\nfunction getAgentId(rawAgentId: string | undefined, config: AuthenticatedRouteDeps['service']['currentConfig']): string {\n const trimmed = rawAgentId?.trim();\n if (trimmed) {\n return trimmed;\n }\n return resolveDefaultAgentId(config);\n}\n\nfunction toWorkflowDefinition(loaded: ReturnType<ReturnType<typeof createWorkflowCatalog>['load']>): WorkflowDefinition {\n const nowMs = Date.now();\n const phases = loaded.meta.phases?.map((phase, index) => ({\n id: normalizeId(phase.title) || `phase-${index + 1}`,\n title: phase.title,\n description: phase.detail,\n })) ?? [];\n\n return {\n id: loaded.name,\n name: loaded.name,\n title: toTitle(loaded.name),\n description: loaded.meta.description,\n version: '1.0.0',\n phases,\n runtime: {\n kind: 'script',\n source: loaded.script,\n },\n defaults: {\n concurrency: DEFAULT_WORKFLOW_CONCURRENCY,\n timeoutSec: DEFAULT_WORKFLOW_TIMEOUT_SEC,\n maxSubagents: loaded.meta.estimatedAgents?.max ?? DEFAULT_WORKFLOW_MAX_SUBAGENTS,\n },\n metadata: {\n tags: loaded.meta.tags ?? [],\n builtIn: loaded.source === 'builtin',\n source: loaded.source,\n whenToUse: loaded.meta.whenToUse,\n estimatedAgents: loaded.meta.estimatedAgents,\n examplePrompts: loaded.meta.examplePrompts,\n i18n: loaded.meta.i18n,\n createdAtMs: nowMs,\n updatedAtMs: nowMs,\n },\n };\n}\n\ninterface QueueWorkflowRunParams {\n deps: AuthenticatedRouteDeps;\n agentId: string;\n sessionKey: string;\n definitionId: string;\n input?: unknown;\n goal?: string;\n source: WorkflowRunSource;\n concurrency?: number;\n maxSubagents?: number;\n tokenBudget?: number | null;\n}\n\nasync function queueWorkflowRun(params: QueueWorkflowRunParams): Promise<string | null> {\n const { deps, agentId, sessionKey, definitionId } = params;\n const { service } = deps;\n const catalog = createWorkflowCatalog();\n let definition: WorkflowDefinition;\n try {\n definition = toWorkflowDefinition(catalog.load(definitionId));\n } catch {\n return null;\n }\n\n const eventStore = new WorkflowEventStore(service.currentConfig, agentId);\n const runStore = new WorkflowRunStore(service.currentConfig, agentId, eventStore);\n const runId = randomUUID();\n const abortController = new AbortController();\n const engine = createWorkflowEngine({\n deps,\n eventStore,\n runStore,\n sessionKey,\n });\n\n activeWorkflowRuns.set(runId, abortController);\n void engine.startRun(definition, {\n runId,\n input: params.input,\n goal: params.goal,\n source: params.source,\n signal: abortController.signal,\n concurrency: params.concurrency,\n maxSubagents: params.maxSubagents,\n tokenBudget: params.tokenBudget,\n }).catch((err) => {\n service.emit('workflow.run.error', {\n runId,\n error: err instanceof Error ? err.message : String(err),\n });\n }).finally(() => {\n activeWorkflowRuns.delete(runId);\n });\n\n return runId;\n}\n\nfunction buildWorkflowStats(runs: WorkflowRunSummary[]): {\n totalRuns: number;\n activeRuns: number;\n succeededRuns: number;\n failedRuns: number;\n averageDurationMs: number | null;\n topDefinitions: Array<{ definitionId: string; count: number }>;\n} {\n const activeStatuses = new Set(['queued', 'running']);\n const succeededStatuses = new Set(['succeeded']);\n const failedStatuses = new Set(['failed', 'timeout', 'cancelled']);\n\n let durationTotal = 0;\n let durationCount = 0;\n const definitionCounts = new Map<string, number>();\n\n for (const run of runs) {\n definitionCounts.set(run.definitionId, (definitionCounts.get(run.definitionId) ?? 0) + 1);\n if (run.metrics.durationMs != null && Number.isFinite(run.metrics.durationMs)) {\n durationTotal += run.metrics.durationMs;\n durationCount += 1;\n }\n }\n\n const topDefinitions = [...definitionCounts.entries()]\n .sort((left, right) => right[1] - left[1])\n .slice(0, 5)\n .map(([definitionId, count]) => ({ definitionId, count }));\n\n return {\n totalRuns: runs.length,\n activeRuns: runs.filter((run) => activeStatuses.has(run.status)).length,\n succeededRuns: runs.filter((run) => succeededStatuses.has(run.status)).length,\n failedRuns: runs.filter((run) => failedStatuses.has(run.status)).length,\n averageDurationMs: durationCount > 0 ? Math.round(durationTotal / durationCount) : null,\n topDefinitions,\n };\n}\n\nfunction createWorkflowEngine(params: {\n deps: AuthenticatedRouteDeps;\n eventStore: WorkflowEventStore;\n runStore: WorkflowRunStore;\n sessionKey: string;\n}): WorkflowEngine {\n const { service } = params.deps;\n const runner = new DelegateSubagentRunner({\n workspace: service.currentWorkspacePath,\n bus: service.messageBusInstance,\n getDefaultModel: () => resolveModelById(service.agentService.getModelForSession(params.sessionKey)),\n getConfig: () => service.currentConfig,\n buildChildTools: (childOptions) => buildWorkflowChildTools(childOptions),\n });\n\n return new WorkflowEngine({\n cwd: service.currentWorkspacePath,\n eventStore: params.eventStore,\n runStore: params.runStore,\n runner,\n resolveModelId: (modelId) => {\n const agentId = extractProfileAgentId(params.sessionKey, service.currentConfig);\n return resolveModelById(resolveModelRef(service.currentConfig, agentId, modelId));\n },\n onEventAppended: (event) => {\n service.emit('workflow.event.appended', { runId: event.runId, event });\n },\n onRunViewUpdated: (view) => {\n service.emit('workflow.run.updated', { runId: view.run.id, view });\n },\n });\n}\n\nfunction buildWorkflowChildTools(childOptions: BuildChildToolsOptions) {\n const childFactory = new AgentToolsFactory({\n workspace: childOptions.workspace,\n bus: childOptions.bus,\n getCurrentContext: () => null,\n getConfig: childOptions.getConfig,\n getPrimaryModel: () => childOptions.model,\n toolExecutorConfig: childOptions.toolExecutorConfig,\n });\n return childFactory.createAllTools({\n workspace: childOptions.workspace,\n getPrimaryModel: () => childOptions.model,\n disabledTools: new Set(['extensions']),\n });\n}\n\nasync function readJsonBody<T>(request: Request): Promise<T> {\n try {\n return await request.json() as T;\n } catch {\n return {} as T;\n }\n}\n\nfunction normalizePositiveInteger(value: number | undefined): number | undefined {\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 1) {\n return undefined;\n }\n return Math.floor(value);\n}\n\nfunction normalizeId(value: string): string {\n return value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\nfunction toTitle(value: string): string {\n return value\n .split(/[_-]+/g)\n .filter(Boolean)\n .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)\n .join(' ');\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { init_agent_scope, listAgentEntries, normalizeAgentId, resolveAgentHomeDir, resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../../agent/agent-scope.js";
2
- import { extractProfileAgentId } from "../../../config/agent-profile.js";
3
1
  import { createLogger } from "../../../utils/logger/index.js";
4
2
  import { init_logger } from "../../../utils/logger.js";
3
+ import { init_agent_scope, listAgentEntries, normalizeAgentId, resolveAgentHomeDir, resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../../agent/agent-scope.js";
4
+ import { extractProfileAgentId } from "../../../config/agent-profile.js";
5
5
  import { validateWritePath } from "../../../agent/sandbox/path-policy.js";
6
6
  import { isPathUnderWorkspace, resolveWorkspaceSafePath, toWorkspaceRelativePosix } from "../../workspace-editor-path.js";
7
7
  import { resolveSafeInboundFilePath } from "../../../channels/attachments/inbound-persist.js";
@@ -12,10 +12,10 @@ import { fileReferenceRegistry } from "../../file-reference-registry.js";
12
12
  import { resolveHeartbeatMdPath } from "../../workspace-heartbeat-path.js";
13
13
  import { listWorkspaceRelativeFilesFsFallback } from "../../workspace-fs-file-list.js";
14
14
  import { runRipgrepInDirectory, runRipgrepListFiles } from "../../workspace-ripgrep.js";
15
- import { basename, dirname, join, resolve } from "node:path";
16
- import { constants } from "node:fs";
17
15
  import { randomUUID } from "node:crypto";
16
+ import { constants } from "node:fs";
18
17
  import { copyFile, link, mkdir, readFile, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
18
+ import { basename, dirname, join, resolve } from "node:path";
19
19
  //#region src/gateway/hono/routes/workspace.ts
20
20
  init_agent_scope();
21
21
  init_logger();
@@ -1,16 +1,12 @@
1
- import { buildSessionKey, init_session_key, parseSessionKey } from "../../routing/session-key.js";
2
- import { getDefaultAgentId, init_resolve_route } from "../../routing/resolve-route.js";
3
1
  import { updateAsyncLogContext } from "../../utils/logger/context.js";
4
2
  import { createLogger } from "../../utils/logger/index.js";
5
3
  import { init_logger } from "../../utils/logger.js";
6
4
  import { MAX_WEBCHAT_ATTACHMENT_FILE_BYTES } from "../chat-limits.js";
5
+ import { resolveWebchatSessionKey } from "../resolve-webchat-session-key.js";
7
6
  import { stringifySSEData } from "./sse-json.js";
8
- import { randomUUID } from "node:crypto";
9
7
  import { streamSSE } from "hono/streaming";
10
8
  //#region src/gateway/hono/sse.ts
11
9
  init_logger();
12
- init_session_key();
13
- init_resolve_route();
14
10
  const log = createLogger("Hono:SSE");
15
11
  const activeConnections = /* @__PURE__ */ new Map();
16
12
  function isValidAgentRequest(body) {
@@ -53,24 +49,21 @@ function createAgentSSEHandler(config) {
53
49
  const { message, channel = "webchat", attachments, thinking } = body;
54
50
  const clientCreatedAtMs = typeof body.clientCreatedAtMs === "number" && Number.isFinite(body.clientCreatedAtMs) ? body.clientCreatedAtMs : void 0;
55
51
  const newSession = Boolean(body.newSession);
56
- let chatId = "default";
57
- if (newSession && channel === "webchat") chatId = `chat_${randomUUID()}`;
58
- else {
59
- const sk = typeof body.sessionKey === "string" && body.sessionKey.trim() ? body.sessionKey.trim() : "";
60
- const cid = typeof body.chatId === "string" && body.chatId.trim() ? body.chatId.trim() : "";
61
- const rawChatId = sk || cid || "default";
62
- if (rawChatId !== "default" && !/^[a-zA-Z0-9][a-zA-Z0-9._:@\-]{0,255}$/.test(rawChatId)) {
63
- log.warn({ rawChatId: rawChatId.slice(0, 64) }, "Rejected invalid chatId format");
64
- return c.json({
65
- ok: false,
66
- error: {
67
- code: "BAD_REQUEST",
68
- message: "Invalid session key format"
69
- }
70
- }, 400);
52
+ const cfg = service.currentConfig;
53
+ const resolved = resolveWebchatSessionKey({
54
+ cfg,
55
+ sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0,
56
+ chatId: typeof body.chatId === "string" ? body.chatId : void 0,
57
+ newSession
58
+ });
59
+ if (resolved.ok === false) return c.json({
60
+ ok: false,
61
+ error: {
62
+ code: "BAD_REQUEST",
63
+ message: resolved.error
71
64
  }
72
- chatId = rawChatId;
73
- }
65
+ }, 400);
66
+ const chatId = resolved.sessionKey;
74
67
  updateAsyncLogContext({ sessionId: String(chatId) });
75
68
  if (Array.isArray(attachments)) {
76
69
  const maxDataChars = maxBase64CharsForBinary(MAX_WEBCHAT_ATTACHMENT_FILE_BYTES);
@@ -92,17 +85,7 @@ function createAgentSSEHandler(config) {
92
85
  if (channel !== "webchat") if (raw.signal.aborted) clientAbort.abort();
93
86
  else raw.signal.addEventListener("abort", () => clientAbort.abort(), { once: true });
94
87
  if (!wantSSE) {
95
- let jsonSessionKey;
96
- if (channel === "webchat") {
97
- const cfg = service.currentConfig;
98
- jsonSessionKey = parseSessionKey(chatId) ? chatId : buildSessionKey({
99
- agentId: getDefaultAgentId(cfg),
100
- source: "webchat",
101
- accountId: "default",
102
- peerKind: "direct",
103
- peerId: chatId
104
- });
105
- }
88
+ const jsonSessionKey = channel === "webchat" ? chatId : void 0;
106
89
  const generator = service.runAgent(message, channel, chatId, attachments, thinking, {
107
90
  signal: clientAbort.signal,
108
91
  ...clientCreatedAtMs !== void 0 ? { clientCreatedAtMs } : {}
@@ -1 +1 @@
1
- {"version":3,"file":"sse.js","names":[],"sources":["../../../../src/gateway/hono/sse.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { 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 { buildSessionKey, parseSessionKey } from '../../routing/session-key.js';\nimport { getDefaultAgentId } from '../../routing/resolve-route.js';\n\nconst log = createLogger('Hono: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 name?: string;\n size?: 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:\n * event: status — { status, runId }\n * event: user_message — { timestamp, content?, attachments? } (user turn accepted, before agent tokens)\n * event: user_transcript — { text, attachments? } (voice STT complete, before agent tokens)\n * event: token — { content }\n * event: error — { content }\n * event: result — { ok, payload: { status, summary } }\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 let chatId = 'default';\n if (newSession && channel === 'webchat') {\n chatId = `chat_${randomUUID()}`;\n } else {\n const sk = typeof body.sessionKey === 'string' && body.sessionKey.trim() ? body.sessionKey.trim() : '';\n const cid = typeof body.chatId === 'string' && body.chatId.trim() ? body.chatId.trim() : '';\n const rawChatId = sk || cid || 'default';\n\n // Validate sessionKey / chatId format to prevent cross-session access\n if (rawChatId !== 'default' && !/^[a-zA-Z0-9][a-zA-Z0-9._:@\\-]{0,255}$/.test(rawChatId)) {\n log.warn({ rawChatId: rawChatId.slice(0, 64) }, 'Rejected invalid chatId format');\n return c.json({\n ok: false,\n error: { code: 'BAD_REQUEST', message: 'Invalid session key format' },\n }, 400);\n }\n chatId = rawChatId;\n }\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 `status`.\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 let jsonSessionKey: string | undefined;\n if (channel === 'webchat') {\n const cfg = service.currentConfig;\n const parsedKey = parseSessionKey(chatId);\n jsonSessionKey = parsedKey\n ? chatId\n : buildSessionKey({\n agentId: getDefaultAgentId(cfg),\n source: 'webchat',\n accountId: 'default',\n peerKind: 'direct',\n peerId: chatId,\n });\n }\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; content?: string; status?: string; runId?: string };\n if (chunk.type === 'token' && chunk.content) {\n tokens.push(chunk.content);\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 log.error({ err: error }, 'Agent run failed (JSON mode)');\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) {\n // Final result\n await stream.writeSSE({\n id: String(++eventId),\n event: 'result',\n data: JSON.stringify({ ok: true, payload: value }),\n });\n break;\n }\n\n const chunk = value as { type: string; content?: string; status?: string; runId?: string };\n\n // Intermediate events: status / token / error\n await stream.writeSSE({\n id: 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: JSON.stringify({\n ok: false,\n error: { 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: String(++eventId),\n event: event.type || 'message',\n data: stringifySSEData(event),\n });\n }\n // Run completed — send a final result event\n await stream.writeSSE({\n id: String(++eventId),\n event: 'result',\n data: JSON.stringify({ ok: true, payload: { status: 'ok', summary: 'Resumed run completed' } }),\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: JSON.stringify({\n ok: false,\n error: { 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('Hono: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 name?: string;\n size?: 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:\n * event: status — { status, runId }\n * event: user_message — { timestamp, content?, attachments? } (user turn accepted, before agent tokens)\n * event: user_transcript — { text, attachments? } (voice STT complete, before agent tokens)\n * event: token — { content }\n * event: error — { content }\n * event: result — { ok, payload: { status, summary } }\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 `status`.\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; content?: string; status?: string; runId?: string };\n if (chunk.type === 'token' && chunk.content) {\n tokens.push(chunk.content);\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 log.error({ err: error }, 'Agent run failed (JSON mode)');\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) {\n // Final result\n await stream.writeSSE({\n id: String(++eventId),\n event: 'result',\n data: JSON.stringify({ ok: true, payload: value }),\n });\n break;\n }\n\n const chunk = value as { type: string; content?: string; status?: string; runId?: string };\n\n // Intermediate events: status / token / error\n await stream.writeSSE({\n id: 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: JSON.stringify({\n ok: false,\n error: { 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: String(++eventId),\n event: event.type || 'message',\n data: stringifySSEData(event),\n });\n }\n // Run completed — send a final result event\n await stream.writeSSE({\n id: String(++eventId),\n event: 'result',\n data: JSON.stringify({ ok: true, payload: { status: 'ok', summary: 'Resumed run completed' } }),\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: JSON.stringify({\n ok: false,\n error: { 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,8 +1,8 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import fs$1 from "node:fs/promises";
1
4
  import path from "node:path";
2
5
  import net from "node:net";
3
- import fsSync from "node:fs";
4
- import { createHash } from "node:crypto";
5
- import fs from "node:fs/promises";
6
6
  import { homedir } from "os";
7
7
  //#region src/gateway/lock.ts
8
8
  /**
@@ -57,7 +57,7 @@ function isPidAlive(pid) {
57
57
  function readLinuxStartTime(pid) {
58
58
  if (process.platform !== "linux") return null;
59
59
  try {
60
- const raw = fsSync.readFileSync(`/proc/${pid}/stat`, "utf8").trim();
60
+ const raw = fs.readFileSync(`/proc/${pid}/stat`, "utf8").trim();
61
61
  const closeParen = raw.lastIndexOf(")");
62
62
  if (closeParen < 0) return null;
63
63
  const fields = raw.slice(closeParen + 1).trim().split(/\s+/);
@@ -69,7 +69,7 @@ function readLinuxStartTime(pid) {
69
69
  }
70
70
  async function readLockPayload(lockPath) {
71
71
  try {
72
- const raw = await fs.readFile(lockPath, "utf8");
72
+ const raw = await fs$1.readFile(lockPath, "utf8");
73
73
  const parsed = JSON.parse(raw);
74
74
  if (typeof parsed.pid !== "number") return null;
75
75
  if (typeof parsed.createdAt !== "string") return null;
@@ -105,11 +105,11 @@ async function acquireGatewayLock(configPath, opts = {}) {
105
105
  const staleMs = opts.staleMs ?? DEFAULT_STALE_MS;
106
106
  const port = opts.port;
107
107
  const lockPath = resolveLockPath(configPath);
108
- await fs.mkdir(path.dirname(lockPath), { recursive: true });
108
+ await fs$1.mkdir(path.dirname(lockPath), { recursive: true });
109
109
  const startedAt = Date.now();
110
110
  let lastPayload = null;
111
111
  while (Date.now() - startedAt < timeoutMs) try {
112
- const handle = await fs.open(lockPath, "wx");
112
+ const handle = await fs$1.open(lockPath, "wx");
113
113
  const startTime = process.platform === "linux" ? readLinuxStartTime(process.pid) : null;
114
114
  const payload = {
115
115
  pid: process.pid,
@@ -124,7 +124,7 @@ async function acquireGatewayLock(configPath, opts = {}) {
124
124
  configPath,
125
125
  release: async () => {
126
126
  await handle.close().catch(() => void 0);
127
- await fs.rm(lockPath, { force: true });
127
+ await fs$1.rm(lockPath, { force: true });
128
128
  console.log(`[GatewayLock] Lock released: lockPath=${lockPath}`);
129
129
  }
130
130
  };
@@ -135,7 +135,7 @@ async function acquireGatewayLock(configPath, opts = {}) {
135
135
  const ownerStatus = ownerPid ? await resolveOwnerStatus(ownerPid, lastPayload, port) : "unknown";
136
136
  if (ownerStatus === "dead" && ownerPid) {
137
137
  console.warn(`[GatewayLock] Cleaning up stale gateway lock: pid=${ownerPid}`);
138
- await fs.rm(lockPath, { force: true });
138
+ await fs$1.rm(lockPath, { force: true });
139
139
  continue;
140
140
  }
141
141
  if (ownerStatus !== "alive") {
@@ -145,14 +145,14 @@ async function acquireGatewayLock(configPath, opts = {}) {
145
145
  stale = Number.isFinite(createdAt) ? Date.now() - createdAt > staleMs : false;
146
146
  }
147
147
  if (!stale) try {
148
- const st = await fs.stat(lockPath);
148
+ const st = await fs$1.stat(lockPath);
149
149
  stale = Date.now() - st.mtimeMs > staleMs;
150
150
  } catch {
151
151
  stale = false;
152
152
  }
153
153
  if (stale) {
154
154
  console.warn(`[GatewayLock] Removing stale lock file: lockPath=${lockPath}`);
155
- await fs.rm(lockPath, { force: true });
155
+ await fs$1.rm(lockPath, { force: true });
156
156
  continue;
157
157
  }
158
158
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lock.js","names":[],"sources":["../../../src/gateway/lock.ts"],"sourcesContent":["/**\n * Gateway Lock - Prevents multiple gateway instances from running simultaneously\n */\n\nimport { createHash } from \"node:crypto\";\nimport fsSync from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport net from \"node:net\";\nimport path from \"node:path\";\nimport { homedir } from \"os\";\n\nconst DEFAULT_TIMEOUT_MS = 5000;\nconst DEFAULT_POLL_INTERVAL_MS = 100;\nconst DEFAULT_STALE_MS = 30_000;\nconst DEFAULT_PORT_PROBE_TIMEOUT_MS = 1000;\n\ntype LockPayload = {\n pid: number;\n createdAt: string;\n configPath: string;\n startTime?: number;\n};\n\nexport type GatewayLockHandle = {\n lockPath: string;\n configPath: string;\n release: () => Promise<void>;\n};\n\nexport type GatewayLockOptions = {\n timeoutMs?: number;\n pollIntervalMs?: number;\n staleMs?: number;\n port?: number;\n};\n\nexport class GatewayLockError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = \"GatewayLockError\";\n }\n}\n\n// Get lock file directory\nfunction resolveLockDir(): string {\n return path.join(homedir(), \".xopc\", \"locks\");\n}\n\n// Generate lock file path based on config path\nfunction resolveLockPath(configPath: string): string {\n const hash = createHash(\"sha256\").update(configPath).digest(\"hex\").slice(0, 8);\n return path.join(resolveLockDir(), `gateway.${hash}.lock`);\n}\n\n// Check if port is available\nasync function checkPortFree(port: number, host = \"127.0.0.1\"): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = net.createConnection({ port, host });\n let settled = false;\n\n const finish = (result: boolean) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n socket.removeAllListeners();\n socket.destroy();\n resolve(result);\n };\n\n const timer = setTimeout(() => finish(true), DEFAULT_PORT_PROBE_TIMEOUT_MS);\n socket.once(\"connect\", () => finish(false));\n socket.once(\"error\", () => finish(true));\n });\n}\n\n// Check if PID is alive\nfunction isPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n// Linux: read process start time to prevent PID reuse\nfunction readLinuxStartTime(pid: number): number | null {\n if (process.platform !== \"linux\") return null;\n try {\n const raw = fsSync.readFileSync(`/proc/${pid}/stat`, \"utf8\").trim();\n const closeParen = raw.lastIndexOf(\")\");\n if (closeParen < 0) return null;\n const rest = raw.slice(closeParen + 1).trim();\n const fields = rest.split(/\\s+/);\n const startTime = parseInt(fields[19] ?? \"\", 10);\n return Number.isFinite(startTime) ? startTime : null;\n } catch {\n return null;\n }\n}\n\n// Parse lock file content\nasync function readLockPayload(lockPath: string): Promise<LockPayload | null> {\n try {\n const raw = await fs.readFile(lockPath, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<LockPayload>;\n if (typeof parsed.pid !== \"number\") return null;\n if (typeof parsed.createdAt !== \"string\") return null;\n if (typeof parsed.configPath !== \"string\") return null;\n return {\n pid: parsed.pid,\n createdAt: parsed.createdAt,\n configPath: parsed.configPath,\n startTime: typeof parsed.startTime === \"number\" ? parsed.startTime : undefined,\n };\n } catch {\n return null;\n }\n}\n\n// Get lock owner status\nasync function resolveOwnerStatus(\n pid: number,\n payload: LockPayload | null,\n port: number | undefined\n): Promise<\"alive\" | \"dead\" | \"unknown\"> {\n // 1. Check port\n if (port != null) {\n const portFree = await checkPortFree(port);\n if (portFree) return \"dead\";\n }\n\n // 2. Check PID alive\n if (!isPidAlive(pid)) return \"dead\";\n\n // 3. Linux: check start time to prevent PID reuse\n if (process.platform === \"linux\") {\n const payloadStartTime = payload?.startTime;\n if (Number.isFinite(payloadStartTime)) {\n const currentStartTime = readLinuxStartTime(pid);\n if (currentStartTime == null) return \"unknown\";\n return currentStartTime === payloadStartTime ? \"alive\" : \"dead\";\n }\n }\n\n return \"alive\";\n}\n\n// Acquire lock\nexport async function acquireGatewayLock(\n configPath: string,\n opts: GatewayLockOptions = {}\n): Promise<GatewayLockHandle> {\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const staleMs = opts.staleMs ?? DEFAULT_STALE_MS;\n const port = opts.port;\n\n const lockPath = resolveLockPath(configPath);\n await fs.mkdir(path.dirname(lockPath), { recursive: true });\n\n const startedAt = Date.now();\n let lastPayload: LockPayload | null = null;\n\n while (Date.now() - startedAt < timeoutMs) {\n try {\n // Try to create lock file (wx = exclusive creation)\n const handle = await fs.open(lockPath, \"wx\");\n\n const startTime = process.platform === \"linux\" ? readLinuxStartTime(process.pid) : null;\n const payload: LockPayload = {\n pid: process.pid,\n createdAt: new Date().toISOString(),\n configPath,\n ...(Number.isFinite(startTime) ? { startTime } : {}),\n };\n\n await handle.writeFile(JSON.stringify(payload), \"utf8\");\n\n console.log(`[GatewayLock] Lock acquired: pid=${process.pid}, lockPath=${lockPath}`);\n\n return {\n lockPath,\n configPath,\n release: async () => {\n await handle.close().catch(() => undefined);\n await fs.rm(lockPath, { force: true });\n console.log(`[GatewayLock] Lock released: lockPath=${lockPath}`);\n },\n };\n } catch (err) {\n const code = (err as { code?: unknown }).code;\n if (code !== \"EEXIST\") {\n throw new GatewayLockError(`Failed to acquire lock at ${lockPath}`, err);\n }\n\n // Lock exists, check owner status\n lastPayload = await readLockPayload(lockPath);\n const ownerPid = lastPayload?.pid;\n const ownerStatus = ownerPid\n ? await resolveOwnerStatus(ownerPid, lastPayload, port)\n : \"unknown\";\n\n // Owner dead, clean up lock file\n if (ownerStatus === \"dead\" && ownerPid) {\n console.warn(`[GatewayLock] Cleaning up stale gateway lock: pid=${ownerPid}`);\n await fs.rm(lockPath, { force: true });\n continue;\n }\n\n // Unknown status, check if expired\n if (ownerStatus !== \"alive\") {\n let stale = false;\n if (lastPayload?.createdAt) {\n const createdAt = Date.parse(lastPayload.createdAt);\n stale = Number.isFinite(createdAt) ? Date.now() - createdAt > staleMs : false;\n }\n if (!stale) {\n try {\n const st = await fs.stat(lockPath);\n stale = Date.now() - st.mtimeMs > staleMs;\n } catch {\n stale = false;\n }\n }\n if (stale) {\n console.warn(`[GatewayLock] Removing stale lock file: lockPath=${lockPath}`);\n await fs.rm(lockPath, { force: true });\n continue;\n }\n }\n\n // Wait and retry\n await new Promise((r) => setTimeout(r, pollIntervalMs));\n }\n }\n\n const owner = lastPayload?.pid ? ` (pid ${lastPayload.pid})` : \"\";\n throw new GatewayLockError(`Gateway already running${owner}; lock timeout after ${timeoutMs}ms`);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"lock.js","names":["fsSync","fs"],"sources":["../../../src/gateway/lock.ts"],"sourcesContent":["/**\n * Gateway Lock - Prevents multiple gateway instances from running simultaneously\n */\n\nimport { createHash } from \"node:crypto\";\nimport fsSync from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport net from \"node:net\";\nimport path from \"node:path\";\nimport { homedir } from \"os\";\n\nconst DEFAULT_TIMEOUT_MS = 5000;\nconst DEFAULT_POLL_INTERVAL_MS = 100;\nconst DEFAULT_STALE_MS = 30_000;\nconst DEFAULT_PORT_PROBE_TIMEOUT_MS = 1000;\n\ntype LockPayload = {\n pid: number;\n createdAt: string;\n configPath: string;\n startTime?: number;\n};\n\nexport type GatewayLockHandle = {\n lockPath: string;\n configPath: string;\n release: () => Promise<void>;\n};\n\nexport type GatewayLockOptions = {\n timeoutMs?: number;\n pollIntervalMs?: number;\n staleMs?: number;\n port?: number;\n};\n\nexport class GatewayLockError extends Error {\n constructor(message: string, public readonly cause?: unknown) {\n super(message);\n this.name = \"GatewayLockError\";\n }\n}\n\n// Get lock file directory\nfunction resolveLockDir(): string {\n return path.join(homedir(), \".xopc\", \"locks\");\n}\n\n// Generate lock file path based on config path\nfunction resolveLockPath(configPath: string): string {\n const hash = createHash(\"sha256\").update(configPath).digest(\"hex\").slice(0, 8);\n return path.join(resolveLockDir(), `gateway.${hash}.lock`);\n}\n\n// Check if port is available\nasync function checkPortFree(port: number, host = \"127.0.0.1\"): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = net.createConnection({ port, host });\n let settled = false;\n\n const finish = (result: boolean) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n socket.removeAllListeners();\n socket.destroy();\n resolve(result);\n };\n\n const timer = setTimeout(() => finish(true), DEFAULT_PORT_PROBE_TIMEOUT_MS);\n socket.once(\"connect\", () => finish(false));\n socket.once(\"error\", () => finish(true));\n });\n}\n\n// Check if PID is alive\nfunction isPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n// Linux: read process start time to prevent PID reuse\nfunction readLinuxStartTime(pid: number): number | null {\n if (process.platform !== \"linux\") return null;\n try {\n const raw = fsSync.readFileSync(`/proc/${pid}/stat`, \"utf8\").trim();\n const closeParen = raw.lastIndexOf(\")\");\n if (closeParen < 0) return null;\n const rest = raw.slice(closeParen + 1).trim();\n const fields = rest.split(/\\s+/);\n const startTime = parseInt(fields[19] ?? \"\", 10);\n return Number.isFinite(startTime) ? startTime : null;\n } catch {\n return null;\n }\n}\n\n// Parse lock file content\nasync function readLockPayload(lockPath: string): Promise<LockPayload | null> {\n try {\n const raw = await fs.readFile(lockPath, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<LockPayload>;\n if (typeof parsed.pid !== \"number\") return null;\n if (typeof parsed.createdAt !== \"string\") return null;\n if (typeof parsed.configPath !== \"string\") return null;\n return {\n pid: parsed.pid,\n createdAt: parsed.createdAt,\n configPath: parsed.configPath,\n startTime: typeof parsed.startTime === \"number\" ? parsed.startTime : undefined,\n };\n } catch {\n return null;\n }\n}\n\n// Get lock owner status\nasync function resolveOwnerStatus(\n pid: number,\n payload: LockPayload | null,\n port: number | undefined\n): Promise<\"alive\" | \"dead\" | \"unknown\"> {\n // 1. Check port\n if (port != null) {\n const portFree = await checkPortFree(port);\n if (portFree) return \"dead\";\n }\n\n // 2. Check PID alive\n if (!isPidAlive(pid)) return \"dead\";\n\n // 3. Linux: check start time to prevent PID reuse\n if (process.platform === \"linux\") {\n const payloadStartTime = payload?.startTime;\n if (Number.isFinite(payloadStartTime)) {\n const currentStartTime = readLinuxStartTime(pid);\n if (currentStartTime == null) return \"unknown\";\n return currentStartTime === payloadStartTime ? \"alive\" : \"dead\";\n }\n }\n\n return \"alive\";\n}\n\n// Acquire lock\nexport async function acquireGatewayLock(\n configPath: string,\n opts: GatewayLockOptions = {}\n): Promise<GatewayLockHandle> {\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const staleMs = opts.staleMs ?? DEFAULT_STALE_MS;\n const port = opts.port;\n\n const lockPath = resolveLockPath(configPath);\n await fs.mkdir(path.dirname(lockPath), { recursive: true });\n\n const startedAt = Date.now();\n let lastPayload: LockPayload | null = null;\n\n while (Date.now() - startedAt < timeoutMs) {\n try {\n // Try to create lock file (wx = exclusive creation)\n const handle = await fs.open(lockPath, \"wx\");\n\n const startTime = process.platform === \"linux\" ? readLinuxStartTime(process.pid) : null;\n const payload: LockPayload = {\n pid: process.pid,\n createdAt: new Date().toISOString(),\n configPath,\n ...(Number.isFinite(startTime) ? { startTime } : {}),\n };\n\n await handle.writeFile(JSON.stringify(payload), \"utf8\");\n\n console.log(`[GatewayLock] Lock acquired: pid=${process.pid}, lockPath=${lockPath}`);\n\n return {\n lockPath,\n configPath,\n release: async () => {\n await handle.close().catch(() => undefined);\n await fs.rm(lockPath, { force: true });\n console.log(`[GatewayLock] Lock released: lockPath=${lockPath}`);\n },\n };\n } catch (err) {\n const code = (err as { code?: unknown }).code;\n if (code !== \"EEXIST\") {\n throw new GatewayLockError(`Failed to acquire lock at ${lockPath}`, err);\n }\n\n // Lock exists, check owner status\n lastPayload = await readLockPayload(lockPath);\n const ownerPid = lastPayload?.pid;\n const ownerStatus = ownerPid\n ? await resolveOwnerStatus(ownerPid, lastPayload, port)\n : \"unknown\";\n\n // Owner dead, clean up lock file\n if (ownerStatus === \"dead\" && ownerPid) {\n console.warn(`[GatewayLock] Cleaning up stale gateway lock: pid=${ownerPid}`);\n await fs.rm(lockPath, { force: true });\n continue;\n }\n\n // Unknown status, check if expired\n if (ownerStatus !== \"alive\") {\n let stale = false;\n if (lastPayload?.createdAt) {\n const createdAt = Date.parse(lastPayload.createdAt);\n stale = Number.isFinite(createdAt) ? Date.now() - createdAt > staleMs : false;\n }\n if (!stale) {\n try {\n const st = await fs.stat(lockPath);\n stale = Date.now() - st.mtimeMs > staleMs;\n } catch {\n stale = false;\n }\n }\n if (stale) {\n console.warn(`[GatewayLock] Removing stale lock file: lockPath=${lockPath}`);\n await fs.rm(lockPath, { force: true });\n continue;\n }\n }\n\n // Wait and retry\n await new Promise((r) => setTimeout(r, pollIntervalMs));\n }\n }\n\n const owner = lastPayload?.pid ? ` (pid ${lastPayload.pid})` : \"\";\n throw new GatewayLockError(`Gateway already running${owner}; lock timeout after ${timeoutMs}ms`);\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
+ import fs from "node:fs";
3
4
  import net from "node:net";
4
- import fsSync from "node:fs";
5
5
  import { execFileSync } from "node:child_process";
6
6
  //#region src/gateway/ports.ts
7
7
  /**
@@ -54,7 +54,7 @@ function listPortListenersViaProc(port) {
54
54
  for (const procFile of ["/proc/net/tcp", "/proc/net/tcp6"]) {
55
55
  let content;
56
56
  try {
57
- content = fsSync.readFileSync(procFile, "utf-8");
57
+ content = fs.readFileSync(procFile, "utf-8");
58
58
  } catch {
59
59
  continue;
60
60
  }
@@ -68,7 +68,7 @@ function listPortListenersViaProc(port) {
68
68
  if (inodeSet.size === 0) return results;
69
69
  let pidDirs;
70
70
  try {
71
- pidDirs = fsSync.readdirSync("/proc").filter((name) => /^\d+$/.test(name));
71
+ pidDirs = fs.readdirSync("/proc").filter((name) => /^\d+$/.test(name));
72
72
  } catch {
73
73
  return results;
74
74
  }
@@ -76,14 +76,14 @@ function listPortListenersViaProc(port) {
76
76
  const fdDir = `/proc/${pidStr}/fd`;
77
77
  let fds;
78
78
  try {
79
- fds = fsSync.readdirSync(fdDir);
79
+ fds = fs.readdirSync(fdDir);
80
80
  } catch {
81
81
  continue;
82
82
  }
83
83
  for (const fd of fds) {
84
84
  let target;
85
85
  try {
86
- target = fsSync.readlinkSync(`${fdDir}/${fd}`);
86
+ target = fs.readlinkSync(`${fdDir}/${fd}`);
87
87
  } catch {
88
88
  continue;
89
89
  }
@@ -93,7 +93,7 @@ function listPortListenersViaProc(port) {
93
93
  if (!results.some((p) => p.pid === pid)) {
94
94
  let command;
95
95
  try {
96
- command = fsSync.readFileSync(`/proc/${pidStr}/comm`, "utf-8").trim();
96
+ command = fs.readFileSync(`/proc/${pidStr}/comm`, "utf-8").trim();
97
97
  } catch {}
98
98
  results.push({
99
99
  pid,
@@ -1 +1 @@
1
- {"version":3,"file":"ports.js","names":["execErr","fs"],"sources":["../../../src/gateway/ports.ts"],"sourcesContent":["/**\n * Ports Management - Port management utilities\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport net from \"node:net\";\nimport { createLogger } from \"../utils/logger.js\";\n\nconst log = createLogger(\"Ports\");\n\nexport type PortProcess = { pid: number; command?: string };\n\nexport type ForceFreePortResult = {\n killed: PortProcess[];\n waitedMs: number;\n escalatedToSigkill: boolean;\n};\n\n// Parse lsof output\nexport function parseLsofOutput(output: string): PortProcess[] {\n const lines = output.split(/\\r?\\n/).filter(Boolean);\n const results: PortProcess[] = [];\n let current: Partial<PortProcess> = {};\n\n for (const line of lines) {\n if (line.startsWith(\"p\")) {\n if (current.pid) {\n results.push(current as PortProcess);\n }\n current = { pid: parseInt(line.slice(1), 10) };\n } else if (line.startsWith(\"c\")) {\n current.command = line.slice(1);\n }\n }\n\n if (current.pid) {\n results.push(current as PortProcess);\n }\n\n return results;\n}\n\n/**\n * Parse `ss -tlnp` output to find PIDs listening on a given port.\n * Example line:\n * LISTEN 0 128 0.0.0.0:3000 0.0.0.0:* users:((\"node\",pid=1234,fd=18))\n */\nfunction listPortListenersViaSs(port: number): PortProcess[] {\n let out: string;\n try {\n out = execFileSync(\"ss\", [\"-tlnp\", `sport = :${port}`], { encoding: \"utf-8\" });\n } catch (err: unknown) {\n const execErr = err as { status?: number; code?: string };\n if (execErr.status === 1) {\n return []; // No matching sockets\n }\n throw err instanceof Error ? err : new Error(String(err));\n }\n const results: PortProcess[] = [];\n\n for (const line of out.split(/\\r?\\n/)) {\n if (!line.includes(\"LISTEN\")) continue;\n for (const match of line.matchAll(/pid=(\\d+)/g)) {\n const pid = parseInt(match[1], 10);\n if (!results.some((p) => p.pid === pid)) {\n results.push({ pid });\n }\n }\n }\n\n return results;\n}\n\n/**\n * Read /proc/net/tcp (and /proc/net/tcp6) to find PIDs listening on a given port.\n * Falls back to an empty list if /proc is unavailable (non-Linux).\n */\nfunction listPortListenersViaProc(port: number): PortProcess[] {\n const hexPort = port.toString(16).toUpperCase().padStart(4, \"0\");\n const results: PortProcess[] = [];\n const inodeSet = new Set<string>();\n\n for (const procFile of [\"/proc/net/tcp\", \"/proc/net/tcp6\"]) {\n let content: string;\n try {\n content = fs.readFileSync(procFile, \"utf-8\");\n } catch {\n continue;\n }\n\n for (const line of content.split(\"\\n\").slice(1)) {\n const parts = line.trim().split(/\\s+/);\n // state 0A = TCP_LISTEN\n if (parts.length < 10 || parts[3] !== \"0A\") continue;\n const localAddress = parts[1];\n const portHex = localAddress.split(\":\")[1];\n if (portHex?.toUpperCase() !== hexPort) continue;\n inodeSet.add(parts[9]);\n }\n }\n\n if (inodeSet.size === 0) return results;\n\n // Walk /proc/<pid>/fd to match socket inodes to PIDs\n let pidDirs: string[];\n try {\n pidDirs = fs.readdirSync(\"/proc\").filter((name) => /^\\d+$/.test(name));\n } catch {\n return results;\n }\n\n for (const pidStr of pidDirs) {\n const fdDir = `/proc/${pidStr}/fd`;\n let fds: string[];\n try {\n fds = fs.readdirSync(fdDir);\n } catch {\n continue;\n }\n\n for (const fd of fds) {\n let target: string;\n try {\n target = fs.readlinkSync(`${fdDir}/${fd}`);\n } catch {\n continue;\n }\n\n // symlink target looks like \"socket:[12345]\"\n const inodeMatch = /^socket:\\[(\\d+)\\]$/.exec(target);\n if (!inodeMatch || !inodeSet.has(inodeMatch[1])) continue;\n\n const pid = parseInt(pidStr, 10);\n if (!results.some((p) => p.pid === pid)) {\n let command: string | undefined;\n try {\n command = fs.readFileSync(`/proc/${pidStr}/comm`, \"utf-8\").trim();\n } catch {\n // comm not readable — leave undefined\n }\n results.push({ pid, command });\n }\n break;\n }\n }\n\n return results;\n}\n\n/**\n * Parse `netstat -ano` output (Windows) to find PIDs listening on a given port.\n * Example line:\n * TCP 0.0.0.0:18790 0.0.0.0:0 LISTENING 1234\n */\nexport function parseNetstatOutput(output: string, port: number): PortProcess[] {\n const portSuffix = `:${port}`;\n const results: PortProcess[] = [];\n\n for (const line of output.split(/\\r?\\n/)) {\n if (!line.includes(\"LISTENING\")) continue;\n const parts = line.trim().split(/\\s+/);\n // Format: TCP <local addr> <foreign addr> LISTENING <pid>\n if (parts.length < 5) continue;\n const localAddr = parts[1];\n if (!localAddr.endsWith(portSuffix)) continue;\n const pid = parseInt(parts[parts.length - 1], 10);\n if (Number.isFinite(pid) && pid > 0 && !results.some((p) => p.pid === pid)) {\n results.push({ pid });\n }\n }\n\n return results;\n}\n\nfunction listPortListenersViaNetstat(port: number): PortProcess[] {\n let out: string;\n try {\n out = execFileSync(\"netstat\", [\"-ano\", \"-p\", \"TCP\"], {\n encoding: \"utf-8\",\n shell: true,\n timeout: 5000,\n });\n } catch {\n return [];\n }\n\n return parseNetstatOutput(out, port);\n}\n\n// List processes listening on port\nexport function listPortListeners(port: number): PortProcess[] {\n // Windows: use netstat -ano\n if (process.platform === \"win32\") {\n return listPortListenersViaNetstat(port);\n }\n\n // Try lsof first (macOS + most Linux distros)\n try {\n const out = execFileSync(\"lsof\", [\"-nP\", `-iTCP:${port}`, \"-sTCP:LISTEN\", \"-FpFc\"], {\n encoding: \"utf-8\",\n });\n return parseLsofOutput(out);\n } catch (err: unknown) {\n const execErr = err as { status?: number; code?: string };\n\n if (execErr.code !== \"ENOENT\") {\n if (execErr.status === 1) return []; // No listeners\n throw err instanceof Error ? err : new Error(String(err));\n }\n // lsof not available — fall through to Linux alternatives\n log.debug({ port }, \"lsof not found; trying ss fallback\");\n }\n\n // Try ss (iproute2, available on most modern Linux systems)\n try {\n return listPortListenersViaSs(port);\n } catch (err: unknown) {\n const execErr = err as { code?: string };\n if (execErr.code !== \"ENOENT\") {\n throw err instanceof Error ? err : new Error(String(err));\n }\n log.debug({ port }, \"ss not found; trying /proc/net/tcp fallback\");\n }\n\n // Last resort: parse /proc/net/tcp directly (no external tools required)\n return listPortListenersViaProc(port);\n}\n\n// Force free port\nexport async function forceFreePortAndWait(\n port: number,\n opts: {\n timeoutMs?: number;\n intervalMs?: number;\n sigtermTimeoutMs?: number;\n } = {}\n): Promise<ForceFreePortResult> {\n const timeoutMs = Math.max(opts.timeoutMs ?? 2000, 0);\n const intervalMs = Math.max(opts.intervalMs ?? 100, 1);\n const sigtermTimeoutMs = Math.min(Math.max(opts.sigtermTimeoutMs ?? 700, 0), timeoutMs);\n\n // 1. Get listener list\n const listeners = listPortListeners(port);\n const killed: PortProcess[] = [...listeners];\n\n // 2. Send SIGTERM\n for (const proc of listeners) {\n try {\n process.kill(proc.pid, \"SIGTERM\");\n log.info({ pid: proc.pid }, \"Sent SIGTERM\");\n } catch (err) {\n log.warn({ pid: proc.pid, err }, \"Failed to send SIGTERM\");\n }\n }\n\n // 3. Wait for processes to exit\n let waitedMs = 0;\n const checkInterval = () => new Promise<void>((r) => setTimeout(r, intervalMs));\n\n // Wait for SIGTERM to take effect\n const sigtermTries = Math.ceil(sigtermTimeoutMs / intervalMs);\n for (let i = 0; i < sigtermTries; i++) {\n await checkInterval();\n waitedMs += intervalMs;\n\n const remaining = listPortListeners(port);\n if (remaining.length === 0) {\n return { killed, waitedMs, escalatedToSigkill: false };\n }\n }\n\n // 4. SIGTERM timeout, send SIGKILL\n const remaining = listPortListeners(port);\n for (const proc of remaining) {\n try {\n process.kill(proc.pid, \"SIGKILL\");\n log.info({ pid: proc.pid }, \"Sent SIGKILL\");\n } catch (err) {\n log.warn({ pid: proc.pid, err }, \"Failed to send SIGKILL\");\n }\n }\n\n // 5. Wait for SIGKILL to take effect\n const remainingBudget = Math.max(timeoutMs - waitedMs, 0);\n const sigkillTries = Math.ceil(remainingBudget / intervalMs);\n\n for (let i = 0; i < sigkillTries; i++) {\n await checkInterval();\n waitedMs += intervalMs;\n\n const stillRemaining = listPortListeners(port);\n if (stillRemaining.length === 0) {\n return { killed, waitedMs, escalatedToSigkill: true };\n }\n }\n\n throw new Error(`Port ${port} still has listeners after force free`);\n}\n\n// Check if port is available\nexport async function checkPortAvailable(port: number, host = \"0.0.0.0\"): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer();\n\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n resolve(false);\n } else {\n resolve(true);\n }\n });\n\n server.once(\"listening\", () => {\n server.close();\n resolve(true);\n });\n\n server.listen(port, host);\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ports.js","names":["execErr"],"sources":["../../../src/gateway/ports.ts"],"sourcesContent":["/**\n * Ports Management - Port management utilities\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport net from \"node:net\";\nimport { createLogger } from \"../utils/logger.js\";\n\nconst log = createLogger(\"Ports\");\n\nexport type PortProcess = { pid: number; command?: string };\n\nexport type ForceFreePortResult = {\n killed: PortProcess[];\n waitedMs: number;\n escalatedToSigkill: boolean;\n};\n\n// Parse lsof output\nexport function parseLsofOutput(output: string): PortProcess[] {\n const lines = output.split(/\\r?\\n/).filter(Boolean);\n const results: PortProcess[] = [];\n let current: Partial<PortProcess> = {};\n\n for (const line of lines) {\n if (line.startsWith(\"p\")) {\n if (current.pid) {\n results.push(current as PortProcess);\n }\n current = { pid: parseInt(line.slice(1), 10) };\n } else if (line.startsWith(\"c\")) {\n current.command = line.slice(1);\n }\n }\n\n if (current.pid) {\n results.push(current as PortProcess);\n }\n\n return results;\n}\n\n/**\n * Parse `ss -tlnp` output to find PIDs listening on a given port.\n * Example line:\n * LISTEN 0 128 0.0.0.0:3000 0.0.0.0:* users:((\"node\",pid=1234,fd=18))\n */\nfunction listPortListenersViaSs(port: number): PortProcess[] {\n let out: string;\n try {\n out = execFileSync(\"ss\", [\"-tlnp\", `sport = :${port}`], { encoding: \"utf-8\" });\n } catch (err: unknown) {\n const execErr = err as { status?: number; code?: string };\n if (execErr.status === 1) {\n return []; // No matching sockets\n }\n throw err instanceof Error ? err : new Error(String(err));\n }\n const results: PortProcess[] = [];\n\n for (const line of out.split(/\\r?\\n/)) {\n if (!line.includes(\"LISTEN\")) continue;\n for (const match of line.matchAll(/pid=(\\d+)/g)) {\n const pid = parseInt(match[1], 10);\n if (!results.some((p) => p.pid === pid)) {\n results.push({ pid });\n }\n }\n }\n\n return results;\n}\n\n/**\n * Read /proc/net/tcp (and /proc/net/tcp6) to find PIDs listening on a given port.\n * Falls back to an empty list if /proc is unavailable (non-Linux).\n */\nfunction listPortListenersViaProc(port: number): PortProcess[] {\n const hexPort = port.toString(16).toUpperCase().padStart(4, \"0\");\n const results: PortProcess[] = [];\n const inodeSet = new Set<string>();\n\n for (const procFile of [\"/proc/net/tcp\", \"/proc/net/tcp6\"]) {\n let content: string;\n try {\n content = fs.readFileSync(procFile, \"utf-8\");\n } catch {\n continue;\n }\n\n for (const line of content.split(\"\\n\").slice(1)) {\n const parts = line.trim().split(/\\s+/);\n // state 0A = TCP_LISTEN\n if (parts.length < 10 || parts[3] !== \"0A\") continue;\n const localAddress = parts[1];\n const portHex = localAddress.split(\":\")[1];\n if (portHex?.toUpperCase() !== hexPort) continue;\n inodeSet.add(parts[9]);\n }\n }\n\n if (inodeSet.size === 0) return results;\n\n // Walk /proc/<pid>/fd to match socket inodes to PIDs\n let pidDirs: string[];\n try {\n pidDirs = fs.readdirSync(\"/proc\").filter((name) => /^\\d+$/.test(name));\n } catch {\n return results;\n }\n\n for (const pidStr of pidDirs) {\n const fdDir = `/proc/${pidStr}/fd`;\n let fds: string[];\n try {\n fds = fs.readdirSync(fdDir);\n } catch {\n continue;\n }\n\n for (const fd of fds) {\n let target: string;\n try {\n target = fs.readlinkSync(`${fdDir}/${fd}`);\n } catch {\n continue;\n }\n\n // symlink target looks like \"socket:[12345]\"\n const inodeMatch = /^socket:\\[(\\d+)\\]$/.exec(target);\n if (!inodeMatch || !inodeSet.has(inodeMatch[1])) continue;\n\n const pid = parseInt(pidStr, 10);\n if (!results.some((p) => p.pid === pid)) {\n let command: string | undefined;\n try {\n command = fs.readFileSync(`/proc/${pidStr}/comm`, \"utf-8\").trim();\n } catch {\n // comm not readable — leave undefined\n }\n results.push({ pid, command });\n }\n break;\n }\n }\n\n return results;\n}\n\n/**\n * Parse `netstat -ano` output (Windows) to find PIDs listening on a given port.\n * Example line:\n * TCP 0.0.0.0:18790 0.0.0.0:0 LISTENING 1234\n */\nexport function parseNetstatOutput(output: string, port: number): PortProcess[] {\n const portSuffix = `:${port}`;\n const results: PortProcess[] = [];\n\n for (const line of output.split(/\\r?\\n/)) {\n if (!line.includes(\"LISTENING\")) continue;\n const parts = line.trim().split(/\\s+/);\n // Format: TCP <local addr> <foreign addr> LISTENING <pid>\n if (parts.length < 5) continue;\n const localAddr = parts[1];\n if (!localAddr.endsWith(portSuffix)) continue;\n const pid = parseInt(parts[parts.length - 1], 10);\n if (Number.isFinite(pid) && pid > 0 && !results.some((p) => p.pid === pid)) {\n results.push({ pid });\n }\n }\n\n return results;\n}\n\nfunction listPortListenersViaNetstat(port: number): PortProcess[] {\n let out: string;\n try {\n out = execFileSync(\"netstat\", [\"-ano\", \"-p\", \"TCP\"], {\n encoding: \"utf-8\",\n shell: true,\n timeout: 5000,\n });\n } catch {\n return [];\n }\n\n return parseNetstatOutput(out, port);\n}\n\n// List processes listening on port\nexport function listPortListeners(port: number): PortProcess[] {\n // Windows: use netstat -ano\n if (process.platform === \"win32\") {\n return listPortListenersViaNetstat(port);\n }\n\n // Try lsof first (macOS + most Linux distros)\n try {\n const out = execFileSync(\"lsof\", [\"-nP\", `-iTCP:${port}`, \"-sTCP:LISTEN\", \"-FpFc\"], {\n encoding: \"utf-8\",\n });\n return parseLsofOutput(out);\n } catch (err: unknown) {\n const execErr = err as { status?: number; code?: string };\n\n if (execErr.code !== \"ENOENT\") {\n if (execErr.status === 1) return []; // No listeners\n throw err instanceof Error ? err : new Error(String(err));\n }\n // lsof not available — fall through to Linux alternatives\n log.debug({ port }, \"lsof not found; trying ss fallback\");\n }\n\n // Try ss (iproute2, available on most modern Linux systems)\n try {\n return listPortListenersViaSs(port);\n } catch (err: unknown) {\n const execErr = err as { code?: string };\n if (execErr.code !== \"ENOENT\") {\n throw err instanceof Error ? err : new Error(String(err));\n }\n log.debug({ port }, \"ss not found; trying /proc/net/tcp fallback\");\n }\n\n // Last resort: parse /proc/net/tcp directly (no external tools required)\n return listPortListenersViaProc(port);\n}\n\n// Force free port\nexport async function forceFreePortAndWait(\n port: number,\n opts: {\n timeoutMs?: number;\n intervalMs?: number;\n sigtermTimeoutMs?: number;\n } = {}\n): Promise<ForceFreePortResult> {\n const timeoutMs = Math.max(opts.timeoutMs ?? 2000, 0);\n const intervalMs = Math.max(opts.intervalMs ?? 100, 1);\n const sigtermTimeoutMs = Math.min(Math.max(opts.sigtermTimeoutMs ?? 700, 0), timeoutMs);\n\n // 1. Get listener list\n const listeners = listPortListeners(port);\n const killed: PortProcess[] = [...listeners];\n\n // 2. Send SIGTERM\n for (const proc of listeners) {\n try {\n process.kill(proc.pid, \"SIGTERM\");\n log.info({ pid: proc.pid }, \"Sent SIGTERM\");\n } catch (err) {\n log.warn({ pid: proc.pid, err }, \"Failed to send SIGTERM\");\n }\n }\n\n // 3. Wait for processes to exit\n let waitedMs = 0;\n const checkInterval = () => new Promise<void>((r) => setTimeout(r, intervalMs));\n\n // Wait for SIGTERM to take effect\n const sigtermTries = Math.ceil(sigtermTimeoutMs / intervalMs);\n for (let i = 0; i < sigtermTries; i++) {\n await checkInterval();\n waitedMs += intervalMs;\n\n const remaining = listPortListeners(port);\n if (remaining.length === 0) {\n return { killed, waitedMs, escalatedToSigkill: false };\n }\n }\n\n // 4. SIGTERM timeout, send SIGKILL\n const remaining = listPortListeners(port);\n for (const proc of remaining) {\n try {\n process.kill(proc.pid, \"SIGKILL\");\n log.info({ pid: proc.pid }, \"Sent SIGKILL\");\n } catch (err) {\n log.warn({ pid: proc.pid, err }, \"Failed to send SIGKILL\");\n }\n }\n\n // 5. Wait for SIGKILL to take effect\n const remainingBudget = Math.max(timeoutMs - waitedMs, 0);\n const sigkillTries = Math.ceil(remainingBudget / intervalMs);\n\n for (let i = 0; i < sigkillTries; i++) {\n await checkInterval();\n waitedMs += intervalMs;\n\n const stillRemaining = listPortListeners(port);\n if (stillRemaining.length === 0) {\n return { killed, waitedMs, escalatedToSigkill: true };\n }\n }\n\n throw new Error(`Port ${port} still has listeners after force free`);\n}\n\n// Check if port is available\nexport async function checkPortAvailable(port: number, host = \"0.0.0.0\"): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer();\n\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n resolve(false);\n } else {\n resolve(true);\n }\n });\n\n server.once(\"listening\", () => {\n server.close();\n resolve(true);\n });\n\n server.listen(port, host);\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { Config } from '../config/schema.js';
2
+ export type ResolveWebchatSessionKeyInput = {
3
+ cfg: Config;
4
+ sessionKey?: string;
5
+ chatId?: string;
6
+ newSession?: boolean;
7
+ };
8
+ export type ResolveWebchatSessionKeyResult = {
9
+ ok: true;
10
+ sessionKey: string;
11
+ } | {
12
+ ok: false;
13
+ error: string;
14
+ };
15
+ /**
16
+ * Resolve the canonical `agent:` session key for webchat `/api/agent` requests.
17
+ * Rejects bare legacy peer ids (e.g. `chat_*` without `agent:` prefix).
18
+ */
19
+ export declare function resolveWebchatSessionKey(input: ResolveWebchatSessionKeyInput): ResolveWebchatSessionKeyResult;
@@ -0,0 +1,46 @@
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
+ //#region src/gateway/resolve-webchat-session-key.ts
6
+ init_agent_session_key();
7
+ init_session_key();
8
+ init_resolve_route();
9
+ /**
10
+ * Resolve the canonical `agent:` session key for webchat `/api/agent` requests.
11
+ * Rejects bare legacy peer ids (e.g. `chat_*` without `agent:` prefix).
12
+ */
13
+ 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 {
35
+ ok: false,
36
+ error: "sessionKey must use agent:{agentId}:{rest} format; create sessions via POST /api/sessions"
37
+ };
38
+ return {
39
+ ok: true,
40
+ sessionKey: raw
41
+ };
42
+ }
43
+ //#endregion
44
+ export { resolveWebchatSessionKey };
45
+
46
+ //# sourceMappingURL=resolve-webchat-session-key.js.map
@@ -0,0 +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,7 +1,7 @@
1
- import { buildSessionKey, init_session_key, parseSessionKey } from "../../routing/session-key.js";
2
- import { getDefaultAgentId, init_resolve_route } from "../../routing/resolve-route.js";
3
1
  import { createLogger } from "../../utils/logger/index.js";
4
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
5
  import { AgentRunRelay } from "../agent-run-relay.js";
6
6
  import { ClarifyBridge } from "../clarify-bridge.js";
7
7
  import { runGatewayAgent } from "./run-gateway-agent.js";