@xopcai/xopc 0.0.93 → 0.0.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (356) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  3. package/dist/extensions/feishu/src/workflow-progress.js +1 -1
  4. package/dist/extensions/telegram/src/plugin.js +1 -1
  5. package/dist/extensions/telegram/src/routing-integration.js +2 -2
  6. package/dist/extensions/telegram/src/workflow-progress.js +1 -1
  7. package/dist/extensions/telegram/xopc.extension.json +1 -1
  8. package/dist/extensions/weixin/src/api/api.js +2 -2
  9. package/dist/extensions/weixin/src/auth/accounts.js +1 -1
  10. package/dist/extensions/weixin/src/cdn/upload.js +1 -1
  11. package/dist/extensions/weixin/src/media/data-url.js +1 -1
  12. package/dist/extensions/weixin/src/messaging/debug-mode.js +1 -1
  13. package/dist/extensions/weixin/src/messaging/inbound.js +1 -1
  14. package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
  15. package/dist/extensions/weixin/src/plugin.js +1 -1
  16. package/dist/extensions/weixin/src/storage/sync-buf.js +1 -1
  17. package/dist/extensions/weixin/src/workflow-progress.js +1 -1
  18. package/dist/gateway/static/root/assets/agents-CKe2LMnz.js +222 -0
  19. package/dist/gateway/static/root/assets/apps-page-Mi9mMIZ1.js +1 -0
  20. package/dist/gateway/static/root/assets/channels-settings-BrdyC101.js +1 -0
  21. package/dist/gateway/static/root/assets/{channels-status-swr-CsGkK9h9.js → channels-status-swr-D55Bu0nn.js} +1 -1
  22. package/dist/gateway/static/root/assets/{cron-api-CyAm0xJT.js → cron-api-CPpx2l-E.js} +1 -1
  23. package/dist/gateway/static/root/assets/cron-page-Bx2jB0YN.js +1 -0
  24. package/dist/gateway/static/root/assets/{dist-DHwVV8XK.js → dist-D_AiG_Kg.js} +1 -1
  25. package/dist/gateway/static/root/assets/{extension-debug-page-BK8kcc4F.js → extension-debug-page-6ieHsxRE.js} +1 -1
  26. package/dist/gateway/static/root/assets/{extension-page-Cf8X_QUc.js → extension-page-B8nywHRO.js} +1 -1
  27. package/dist/gateway/static/root/assets/{extension-settings-page-C5-YLMmy.js → extension-settings-page-DrskdEIV.js} +1 -1
  28. package/dist/gateway/static/root/assets/{fetch-BAPnkYbC.js → fetch-B0aeeY0q.js} +1 -1
  29. package/dist/gateway/static/root/assets/{field-primitives-8p7ucXa1.js → field-primitives--9ooY8Xl.js} +1 -1
  30. package/dist/gateway/static/root/assets/{heartbeat-config-api-CpgW2sGp.js → heartbeat-config-api-DUZ_W1w-.js} +1 -1
  31. package/dist/gateway/static/root/assets/index-Bj_l8QDp.css +1 -0
  32. package/dist/gateway/static/root/assets/{index-Do52EfZK.js → index-Dj9FuxCm.js} +99 -98
  33. package/dist/gateway/static/root/assets/logs-page-CaXqhpKf.js +2 -0
  34. package/dist/gateway/static/root/assets/{note-detail-page-WLM6FUIi.js → note-detail-page-DYzym2B0.js} +3 -3
  35. package/dist/gateway/static/root/assets/{note-time-EFyIVhec.js → note-time-B-vSi2dR.js} +1 -1
  36. package/dist/gateway/static/root/assets/notes-page-BkhWdGiT.js +1 -0
  37. package/dist/gateway/static/root/assets/sessions-page-53YFokoe.js +1 -0
  38. package/dist/gateway/static/root/assets/{settings-advanced-gate-CEs8pGh6.js → settings-advanced-gate-BaZmaklx.js} +2 -2
  39. package/dist/gateway/static/root/assets/{settings-form-section-C6cGTVwK.js → settings-form-section-DIJPKpTR.js} +1 -1
  40. package/dist/gateway/static/root/assets/settings-page-Dvb230FF.js +3 -0
  41. package/dist/gateway/static/root/assets/share-preview-page-CRyjTAG6.js +2 -0
  42. package/dist/gateway/static/root/assets/skills-page-C5ZJbfAe.js +2 -0
  43. package/dist/gateway/static/root/assets/{theme-store-D6EsNTPr.js → theme-store-Cg_SuBw0.js} +1 -1
  44. package/dist/gateway/static/root/assets/{url-CTjpm0Uz.js → url-BHHmdJYc.js} +2 -2
  45. package/dist/gateway/static/root/assets/{utils-C86AVfY-.js → utils-lMYoWhqo.js} +1 -1
  46. package/dist/gateway/static/root/assets/voice-api-key-field-Dda2pcUU.js +1 -0
  47. package/dist/gateway/static/root/assets/{workflow-page.utils-DsEriMFW.js → workflow-page.utils-KIladUrU.js} +1 -1
  48. package/dist/gateway/static/root/assets/workflows-page-BTis4Z7Y.js +27 -0
  49. package/dist/gateway/static/root/index.html +5 -5
  50. package/dist/package.js +1 -1
  51. package/dist/src/agent/agent-manager.js +7 -7
  52. package/dist/src/agent/agent-scope.js +1 -1
  53. package/dist/src/agent/bootstrap/load-bootstrap-files.js +1 -1
  54. package/dist/src/agent/context/workspace-seed.js +2 -2
  55. package/dist/src/agent/goals/goal-run-store.js +4 -4
  56. package/dist/src/agent/goals/persistent-goal-service.js +1 -1
  57. package/dist/src/agent/goals/post-turn.js +2 -2
  58. package/dist/src/agent/image/load-image-media.js +2 -2
  59. package/dist/src/agent/inbound/turn-dispatcher.d.ts +1 -0
  60. package/dist/src/agent/inbound/turn-dispatcher.js +3 -0
  61. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  62. package/dist/src/agent/ipc/bus.js +1 -1
  63. package/dist/src/agent/ipc/inbox.js +2 -2
  64. package/dist/src/agent/ipc/socket.js +1 -1
  65. package/dist/src/agent/lifecycle/handlers/compaction.js +1 -1
  66. package/dist/src/agent/lifecycle/handlers/compaction.js.map +1 -1
  67. package/dist/src/agent/mcp/bundle-mcp-materialize.js +2 -2
  68. package/dist/src/agent/mcp/bundle-mcp-materialize.js.map +1 -1
  69. package/dist/src/agent/mcp/bundle-mcp-runtime.js +18 -5
  70. package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
  71. package/dist/src/agent/mcp/mcp-transport-config.js +11 -4
  72. package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
  73. package/dist/src/agent/mcp/mcp-transport.js +2 -2
  74. package/dist/src/agent/mcp/mcp-transport.js.map +1 -1
  75. package/dist/src/agent/memory/builtin-memory-store.js +1 -1
  76. package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
  77. package/dist/src/agent/memory/dreaming/events.js +1 -1
  78. package/dist/src/agent/memory/dreaming/last-run.js +1 -1
  79. package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
  80. package/dist/src/agent/memory/dreaming/preview.js +1 -1
  81. package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
  82. package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
  83. package/dist/src/agent/memory/dreaming/utils.js +1 -1
  84. package/dist/src/agent/memory/plugin-discovery.js +1 -1
  85. package/dist/src/agent/models/manager.js +1 -1
  86. package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
  87. package/dist/src/agent/reply/post-compaction-context.js +1 -1
  88. package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
  89. package/dist/src/agent/sandbox/path-policy.js +2 -2
  90. package/dist/src/agent/service/build-direct-message-content.js +1 -1
  91. package/dist/src/agent/service/process-direct-streaming.d.ts +1 -0
  92. package/dist/src/agent/service/process-direct-streaming.js +15 -12
  93. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  94. package/dist/src/agent/service.d.ts +4 -2
  95. package/dist/src/agent/service.js +24 -8
  96. package/dist/src/agent/service.js.map +1 -1
  97. package/dist/src/agent/service.types.d.ts +3 -1
  98. package/dist/src/agent/session/session-inspector.js +1 -1
  99. package/dist/src/agent/skills/config.js +1 -1
  100. package/dist/src/agent/skills/hub-hash.js +2 -2
  101. package/dist/src/agent/skills/hub-lock.js +1 -1
  102. package/dist/src/agent/skills/hub-pull.js +2 -2
  103. package/dist/src/agent/skills/index.js +1 -1
  104. package/dist/src/agent/skills/managed-store.js +1 -1
  105. package/dist/src/agent/skills/scanner.js +1 -1
  106. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  107. package/dist/src/agent/skills/skill-manager.js +1 -1
  108. package/dist/src/agent/tools/browser/tool/browser-use-tool.js +1 -1
  109. package/dist/src/agent/tools/browser/tool/browser-use-tool.js.map +1 -1
  110. package/dist/src/agent/tools/dreaming-tool.js +1 -1
  111. package/dist/src/agent/tools/factory.js +1 -1
  112. package/dist/src/agent/tools/image-generate-tool.js +1 -1
  113. package/dist/src/agent/tools/search/registry.js +1 -1
  114. package/dist/src/agent/tools/search/registry.js.map +1 -1
  115. package/dist/src/agent/tools/send-media.js +1 -1
  116. package/dist/src/agent/tools/session-search-tool.js +1 -1
  117. package/dist/src/agent/tools/session-search-tool.js.map +1 -1
  118. package/dist/src/agent/tools/skill-manage-tool.js +1 -1
  119. package/dist/src/agent/tools/workflow-tool.js +2 -2
  120. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  121. package/dist/src/agent/tools/write.js +1 -1
  122. package/dist/src/agent/workflow/catalog.js +1 -1
  123. package/dist/src/agent/workflow/progress-broker.js +1 -1
  124. package/dist/src/agent/workflow/progress-broker.js.map +1 -1
  125. package/dist/src/agent/workflow/subagent-runner.js +1 -1
  126. package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
  127. package/dist/src/auth/credentials.js +3 -3
  128. package/dist/src/auth/profiles/store.js +1 -1
  129. package/dist/src/auth/sync-provider-auth.js +1 -1
  130. package/dist/src/browser/cache-dir-policy.js +1 -1
  131. package/dist/src/browser/cdp-local-launcher.js +2 -2
  132. package/dist/src/browser/providers/browser-ext-install.js +3 -3
  133. package/dist/src/browser/providers/cloakbrowser.js +4 -4
  134. package/dist/src/browser/providers/playwright-doctor.js +1 -1
  135. package/dist/src/browser/stealth.js +1 -1
  136. package/dist/src/channels/attachments/inbound-persist.js +1 -1
  137. package/dist/src/channels/attachments/outbound-tts-persist.js +1 -1
  138. package/dist/src/channels/outbound/persist-store.js +1 -1
  139. package/dist/src/channels/pairing/allow-from-file.js +1 -1
  140. package/dist/src/channels/pairing/pairing-store.js +2 -2
  141. package/dist/src/channels/pipeline.js +3 -2
  142. package/dist/src/channels/pipeline.js.map +1 -1
  143. package/dist/src/chat-commands/agent-edit.js +2 -2
  144. package/dist/src/chat-commands/builtins/config.js +2 -2
  145. package/dist/src/chat-commands/context.js +1 -1
  146. package/dist/src/cli/cli-log-level-preset.d.ts +1 -1
  147. package/dist/src/cli/cli-log-level-preset.js +2 -2
  148. package/dist/src/cli/cli-log-level-preset.js.map +1 -1
  149. package/dist/src/cli/commands/config.js +1 -1
  150. package/dist/src/cli/commands/doctor/checks/config-health.js +1 -1
  151. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  152. package/dist/src/cli/commands/doctor/checks/session-integrity.js +2 -2
  153. package/dist/src/cli/commands/doctor/checks/state-integrity.js +1 -1
  154. package/dist/src/cli/commands/doctor/checks/workspace-status.js +1 -1
  155. package/dist/src/cli/commands/extension-dev.js +1 -1
  156. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  157. package/dist/src/cli/commands/extension-pack.js +1 -1
  158. package/dist/src/cli/commands/gateway/logs.js +1 -1
  159. package/dist/src/cli/commands/image.js +1 -1
  160. package/dist/src/cli/commands/init.js +4 -4
  161. package/dist/src/cli/commands/logs.js +3 -3
  162. package/dist/src/cli/commands/logs.js.map +1 -1
  163. package/dist/src/cli/commands/onboard.js +1 -1
  164. package/dist/src/cli/utils/init-workspace-core.js +2 -2
  165. package/dist/src/commands/agents.config.js +1 -1
  166. package/dist/src/config/agent-profile.js +1 -1
  167. package/dist/src/config/gateway-bind.js +1 -1
  168. package/dist/src/config/index.js +5 -5
  169. package/dist/src/config/loader.js +2 -2
  170. package/dist/src/config/models-json.js +2 -2
  171. package/dist/src/config/paths-state.js +1 -1
  172. package/dist/src/config/profile.js +2 -2
  173. package/dist/src/config/workspace-path.js +1 -1
  174. package/dist/src/cron/executor.js +9 -6
  175. package/dist/src/cron/executor.js.map +1 -1
  176. package/dist/src/cron/persistence.js +1 -1
  177. package/dist/src/cron/run-log-store.js +1 -1
  178. package/dist/src/daemon/constants.js +1 -1
  179. package/dist/src/daemon/install-plan.js +2 -2
  180. package/dist/src/daemon/launchd.js +2 -2
  181. package/dist/src/daemon/schtasks.js +2 -2
  182. package/dist/src/daemon/systemd.js +2 -2
  183. package/dist/src/extensions/bundle-mcp.js +1 -1
  184. package/dist/src/extensions/discover-extensions.js +1 -1
  185. package/dist/src/extensions/health.js +1 -1
  186. package/dist/src/extensions/loader.js +1 -1
  187. package/dist/src/extensions/lockfile.js +2 -2
  188. package/dist/src/extensions/update.js +1 -1
  189. package/dist/src/gateway/agents-admin.js +3 -3
  190. package/dist/src/gateway/file-path-classifier.js +2 -2
  191. package/dist/src/gateway/hono/app.js +4 -1
  192. package/dist/src/gateway/hono/app.js.map +1 -1
  193. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  194. package/dist/src/gateway/hono/lib/extension-store.js +2 -2
  195. package/dist/src/gateway/hono/lib/route-logger.d.ts +6 -0
  196. package/dist/src/gateway/hono/lib/route-logger.js +31 -0
  197. package/dist/src/gateway/hono/lib/route-logger.js.map +1 -0
  198. package/dist/src/gateway/hono/lib/static-ui.js +2 -2
  199. package/dist/src/gateway/hono/middleware/auth.js +16 -3
  200. package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
  201. package/dist/src/gateway/hono/middleware/logger.js +1 -1
  202. package/dist/src/gateway/hono/middleware/logger.js.map +1 -1
  203. package/dist/src/gateway/hono/middleware/route-errors.d.ts +5 -0
  204. package/dist/src/gateway/hono/middleware/route-errors.js +27 -0
  205. package/dist/src/gateway/hono/middleware/route-errors.js.map +1 -0
  206. package/dist/src/gateway/hono/oauth.js +1 -1
  207. package/dist/src/gateway/hono/routes/agent-stream.js +6 -0
  208. package/dist/src/gateway/hono/routes/agent-stream.js.map +1 -1
  209. package/dist/src/gateway/hono/routes/agents.js +1 -1
  210. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  211. package/dist/src/gateway/hono/routes/browser-install.js +2 -4
  212. package/dist/src/gateway/hono/routes/browser-install.js.map +1 -1
  213. package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
  214. package/dist/src/gateway/hono/routes/config.js +25 -11
  215. package/dist/src/gateway/hono/routes/config.js.map +1 -1
  216. package/dist/src/gateway/hono/routes/cron.js +5 -0
  217. package/dist/src/gateway/hono/routes/cron.js.map +1 -1
  218. package/dist/src/gateway/hono/routes/dreaming.js +1 -1
  219. package/dist/src/gateway/hono/routes/host-fs.js +4 -6
  220. package/dist/src/gateway/hono/routes/host-fs.js.map +1 -1
  221. package/dist/src/gateway/hono/routes/lazy-bundles.js +14 -1
  222. package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
  223. package/dist/src/gateway/hono/routes/lazy-fallback.js +3 -0
  224. package/dist/src/gateway/hono/routes/lazy-fallback.js.map +1 -1
  225. package/dist/src/gateway/hono/routes/logs.js +39 -7
  226. package/dist/src/gateway/hono/routes/logs.js.map +1 -1
  227. package/dist/src/gateway/hono/routes/mcp.d.ts +3 -0
  228. package/dist/src/gateway/hono/routes/mcp.js +107 -0
  229. package/dist/src/gateway/hono/routes/mcp.js.map +1 -0
  230. package/dist/src/gateway/hono/routes/models.js +1 -1
  231. package/dist/src/gateway/hono/routes/sessions.js +6 -0
  232. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  233. package/dist/src/gateway/hono/routes/shares.js +1 -1
  234. package/dist/src/gateway/hono/routes/update.js +2 -4
  235. package/dist/src/gateway/hono/routes/update.js.map +1 -1
  236. package/dist/src/gateway/hono/routes/voice.js +2 -4
  237. package/dist/src/gateway/hono/routes/voice.js.map +1 -1
  238. package/dist/src/gateway/hono/routes/workspace.js +4 -6
  239. package/dist/src/gateway/hono/routes/workspace.js.map +1 -1
  240. package/dist/src/gateway/hono/sse.js +9 -2
  241. package/dist/src/gateway/hono/sse.js.map +1 -1
  242. package/dist/src/gateway/lock.js +3 -3
  243. package/dist/src/gateway/ports.js +1 -1
  244. package/dist/src/gateway/service/agent-runner.js +3 -3
  245. package/dist/src/gateway/service/agent-runner.js.map +1 -1
  246. package/dist/src/gateway/service/config-coordinator.js +14 -6
  247. package/dist/src/gateway/service/config-coordinator.js.map +1 -1
  248. package/dist/src/gateway/service/marketplace-service.js +3 -3
  249. package/dist/src/gateway/service/marketplace-service.js.map +1 -1
  250. package/dist/src/gateway/service/run-gateway-agent.js +22 -5
  251. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  252. package/dist/src/gateway/service/sse-hub.js +1 -1
  253. package/dist/src/gateway/service/sse-hub.js.map +1 -1
  254. package/dist/src/gateway/service.js +13 -6
  255. package/dist/src/gateway/service.js.map +1 -1
  256. package/dist/src/gateway/workspace-fs-file-list.js +1 -1
  257. package/dist/src/infra/brew.js +1 -1
  258. package/dist/src/infra/package-json.js +1 -1
  259. package/dist/src/infra/package-update-steps.js +1 -1
  260. package/dist/src/infra/path-env.js +2 -2
  261. package/dist/src/infra/restart.js +2 -2
  262. package/dist/src/infra/stable-node-path.js +1 -1
  263. package/dist/src/infra/update-check.js +1 -1
  264. package/dist/src/infra/update-global.js +1 -1
  265. package/dist/src/infra/update-lock.js +3 -3
  266. package/dist/src/infra/update-runner.js +1 -1
  267. package/dist/src/infra/update-startup.js +2 -2
  268. package/dist/src/infra/write-file-atomic.js +2 -2
  269. package/dist/src/mcp/channel-bridge.js +26 -2
  270. package/dist/src/mcp/channel-bridge.js.map +1 -1
  271. package/dist/src/mcp/gateway-http-client.js +24 -2
  272. package/dist/src/mcp/gateway-http-client.js.map +1 -1
  273. package/dist/src/notes/store.js +2 -2
  274. package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
  275. package/dist/src/providers/index.js +2 -2
  276. package/dist/src/providers/model-registry.js +1 -1
  277. package/dist/src/session/config-store.js +12 -6
  278. package/dist/src/session/config-store.js.map +1 -1
  279. package/dist/src/session/index.d.ts +1 -1
  280. package/dist/src/session/index.js +2 -2
  281. package/dist/src/session/init-session-turn.js +2 -2
  282. package/dist/src/session/manager.js +8 -1
  283. package/dist/src/session/manager.js.map +1 -1
  284. package/dist/src/session/parity/jsonl-transcript-io.js +2 -2
  285. package/dist/src/session/parity/sessions-json-file.js +1 -1
  286. package/dist/src/session/parity/transcript-file-lock.js +2 -2
  287. package/dist/src/session/parity/transcript-paths.js +1 -1
  288. package/dist/src/session/resolve-session.js +4 -4
  289. package/dist/src/session/search-index-cache.js +1 -1
  290. package/dist/src/session/search-index.js +1 -1
  291. package/dist/src/session/session-title.d.ts +19 -3
  292. package/dist/src/session/session-title.js +84 -9
  293. package/dist/src/session/session-title.js.map +1 -1
  294. package/dist/src/session/store.js +6 -6
  295. package/dist/src/share/share-auto.js +2 -2
  296. package/dist/src/share/share-store.js +3 -3
  297. package/dist/src/share/share-thumbnail.js +2 -2
  298. package/dist/src/share/share-zip.js +1 -1
  299. package/dist/src/share/site-share-store.js +3 -3
  300. package/dist/src/share/site-static-serve.js +1 -1
  301. package/dist/src/tui/clipboard-image.js +3 -3
  302. package/dist/src/tui/theme-manager.js +1 -1
  303. package/dist/src/tui/tui-keybindings-file.js +1 -1
  304. package/dist/src/tui/tui-scoped-models.js +2 -2
  305. package/dist/src/tui/tui-settings.js +1 -1
  306. package/dist/src/tui/tui.js +3 -3
  307. package/dist/src/tunnel/frpc-binary.js +3 -3
  308. package/dist/src/tunnel/frpc-config.js +1 -1
  309. package/dist/src/tunnel/frpc-extract.js +1 -1
  310. package/dist/src/tunnel/tunnel-state.js +1 -1
  311. package/dist/src/utils/index.js +4 -4
  312. package/dist/src/utils/logger/audit.js +1 -1
  313. package/dist/src/utils/logger/config.js +2 -6
  314. package/dist/src/utils/logger/config.js.map +1 -1
  315. package/dist/src/utils/logger/context.d.ts +3 -22
  316. package/dist/src/utils/logger/context.js +4 -32
  317. package/dist/src/utils/logger/context.js.map +1 -1
  318. package/dist/src/utils/logger/index.d.ts +4 -7
  319. package/dist/src/utils/logger/index.js +9 -28
  320. package/dist/src/utils/logger/index.js.map +1 -1
  321. package/dist/src/utils/logger/log-store.d.ts +14 -32
  322. package/dist/src/utils/logger/log-store.js +68 -119
  323. package/dist/src/utils/logger/log-store.js.map +1 -1
  324. package/dist/src/utils/logger/log-stream.d.ts +5 -70
  325. package/dist/src/utils/logger/log-stream.js +67 -178
  326. package/dist/src/utils/logger/log-stream.js.map +1 -1
  327. package/dist/src/utils/logger/pino-record.d.ts +8 -0
  328. package/dist/src/utils/logger/pino-record.js +83 -0
  329. package/dist/src/utils/logger/pino-record.js.map +1 -0
  330. package/dist/src/utils/logger/rotation.js +1 -1
  331. package/dist/src/utils/logger/stats.d.ts +1 -1
  332. package/dist/src/utils/logger/stats.js +2 -2
  333. package/dist/src/utils/logger/stats.js.map +1 -1
  334. package/dist/src/utils/logger/streams.js +18 -0
  335. package/dist/src/utils/logger/streams.js.map +1 -1
  336. package/dist/src/utils/logger/types.d.ts +0 -9
  337. package/dist/src/utils/logger/types.js.map +1 -1
  338. package/dist/src/utils/logger.js +4 -4
  339. package/dist/src/voice/tts/audio.js +1 -1
  340. package/dist/src/voice/tts/providers/edge-speech.js +2 -2
  341. package/dist/src/workflows/store/event-store.js +1 -1
  342. package/dist/src/workflows/store/run-store.js +1 -1
  343. package/package.json +3 -2
  344. package/dist/gateway/static/root/assets/agents-C7tTJLP9.js +0 -222
  345. package/dist/gateway/static/root/assets/apps-page-BbzdMyrg.js +0 -1
  346. package/dist/gateway/static/root/assets/channels-settings-B49vG2hE.js +0 -1
  347. package/dist/gateway/static/root/assets/cron-page-Bjx7IOdR.js +0 -1
  348. package/dist/gateway/static/root/assets/index-CwDdudZM.css +0 -1
  349. package/dist/gateway/static/root/assets/logs-page-BxukQ-J-.js +0 -1
  350. package/dist/gateway/static/root/assets/notes-page-BYPVYcYn.js +0 -1
  351. package/dist/gateway/static/root/assets/sessions-page-BFD2_-Cl.js +0 -1
  352. package/dist/gateway/static/root/assets/settings-page-BiP5iH46.js +0 -2
  353. package/dist/gateway/static/root/assets/share-preview-page-tnIfJ4K6.js +0 -2
  354. package/dist/gateway/static/root/assets/skills-page-CNDctFIn.js +0 -2
  355. package/dist/gateway/static/root/assets/voice-api-key-field-CalxUkxm.js +0 -1
  356. package/dist/gateway/static/root/assets/workflows-page-D2fRxXJG.js +0 -27
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.js","names":["fsConstants"],"sources":["../../../../../src/gateway/hono/routes/workspace.ts"],"sourcesContent":["import type { Hono } from 'hono';\nimport { randomUUID } from 'node:crypto';\nimport { constants as fsConstants } from 'node:fs';\nimport { copyFile, link, mkdir, readdir, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join, resolve } from 'node:path';\n\nimport { extractProfileAgentId } from '../../../config/agent-profile.js';\nimport { type Config } from '../../../config/schema.js';\nimport { getWorkspacePath } from '../../../config/workspace-path-helpers.js';\nimport { validateWritePath } from '../../../agent/sandbox/path-policy.js';\nimport { resolveSafeInboundFilePath } from '../../../channels/attachments/inbound-persist.js';\nimport { resolveSafeTtsFilePath } from '../../../channels/attachments/outbound-tts-persist.js';\nimport {\n listAgentEntries,\n normalizeAgentId,\n resolveAgentHomeDir,\n resolveAgentWorkspaceDir,\n resolveDefaultAgentId,\n} from '../../../agent/agent-scope.js';\nimport { createLogger } from '../../../utils/logger.js';\nimport { resolveHeartbeatMdPath } from '../../workspace-heartbeat-path.js';\nimport {\n isPathUnderWorkspace,\n resolveWorkspaceSafePath,\n toWorkspaceRelativePosix,\n} from '../../workspace-editor-path.js';\nimport { listWorkspaceRelativeFilesFsFallback } from '../../workspace-fs-file-list.js';\nimport { runRipgrepInDirectory, runRipgrepListFiles } from '../../workspace-ripgrep.js';\nimport {\n buildFilePathClassifierContext,\n classifyFileLocation,\n displayNameForPath,\n fileRefSessionKeysMatch,\n resolveFileReferenceCandidate,\n} from '../../file-path-classifier.js';\nimport {\n fileReferenceRegistry,\n type FileReferenceCapability,\n type FileReferenceLocationKind,\n type FileReferenceScope,\n} from '../../file-reference-registry.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\nimport type { GatewayService } from '../../service.js';\n\nconst log = createLogger('HonoApp');\n\n/** Agent home for persisted `inbound/` and `tts/` attachments (matches `persistOutboundTtsAudio` / `prepareInboundAttachments`). */\nfunction resolvePersistedAttachmentAgentHome(cfg: Config, sessionKeyRaw: string | undefined): string {\n const sk = typeof sessionKeyRaw === 'string' ? sessionKeyRaw.trim() : '';\n const agentId = sk ? extractProfileAgentId(sk, cfg) : resolveDefaultAgentId(cfg);\n return resolveAgentHomeDir(cfg, agentId);\n}\n\nconst FILE_SEARCH_MAX_LIMIT = 50;\n\n/** Subsequence fuzzy match: all query chars appear in order in `candidate` (case-insensitive). */\nfunction fuzzySubsequenceScore(query: string, candidate: string): number | null {\n const q = query.toLowerCase();\n const c = candidate.toLowerCase();\n if (q.length === 0) return 0;\n let qi = 0;\n for (let ci = 0; ci < c.length && qi < q.length; ci++) {\n if (c[ci] === q[qi]) qi++;\n }\n if (qi < q.length) return null;\n const base = c.split('/').pop() ?? c;\n let score = 10;\n if (c.startsWith(q)) score += 40;\n if (base.startsWith(q)) score += 35;\n else if (base.includes(q)) score += 20;\n else if (c.includes(q)) score += 10;\n score -= c.length * 0.0001;\n return score;\n}\n\nasync function fuzzySearchWorkspaceFiles(\n workspaceRoot: string,\n query: string,\n limit: number,\n): Promise<Array<{ name: string; path: string; isDirectory: boolean }>> {\n let files = await runRipgrepListFiles(workspaceRoot);\n if (files.length === 0) {\n files = await listWorkspaceRelativeFilesFsFallback(workspaceRoot, 120_000);\n if (files.length > 0) {\n log.debug(\n { workspaceRoot, fileCount: files.length },\n 'workspace files/search: file list from fs walk (ripgrep unavailable or returned empty)',\n );\n }\n }\n const q = query.trim();\n const capped = Math.min(Math.max(limit, 1), FILE_SEARCH_MAX_LIMIT);\n\n type Row = { name: string; path: string; isDirectory: boolean; score: number };\n const rows: Row[] = [];\n\n if (!q) {\n const sorted = [...files].sort((a, b) => a.localeCompare(b));\n for (const rel of sorted.slice(0, capped)) {\n const name = rel.split('/').pop() ?? rel;\n rows.push({ name, path: rel, isDirectory: false, score: 0 });\n }\n return rows;\n }\n\n for (const rel of files) {\n const name = rel.split('/').pop() ?? rel;\n const scorePath = fuzzySubsequenceScore(q, rel);\n const scoreName = fuzzySubsequenceScore(q, name);\n const score = Math.max(scorePath ?? -Infinity, scoreName ?? -Infinity);\n if (score === -Infinity) continue;\n rows.push({ name, path: rel, isDirectory: false, score });\n }\n\n rows.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));\n return rows.slice(0, capped).map(({ name, path, isDirectory }) => ({ name, path, isDirectory }));\n}\n\nfunction isKnownEditorAgentId(cfg: Config, id: string): boolean {\n const n = normalizeAgentId(id);\n if (n === resolveDefaultAgentId(cfg)) return true;\n return listAgentEntries(cfg).some((e) => normalizeAgentId(e.id) === n);\n}\n\nfunction resolveEditorWorkspaceRoot(\n cfg: Config,\n agentIdRaw: string | undefined,\n): { ok: true; root: string } | { ok: false; message: string } {\n const trimmed = typeof agentIdRaw === 'string' ? agentIdRaw.trim() : '';\n if (!trimmed) {\n const root = getWorkspacePath(cfg);\n if (!root) return { ok: false, message: 'Workspace not configured' };\n return { ok: true, root };\n }\n const id = normalizeAgentId(trimmed);\n if (!isKnownEditorAgentId(cfg, id)) {\n return { ok: false, message: 'Unknown agent' };\n }\n return { ok: true, root: resolveAgentWorkspaceDir(cfg, id) };\n}\n\n/** Prefer `sessionKey` (per-session workspace override) over `agentId`. */\nasync function resolveEditorWorkspaceRootAsync(\n service: GatewayService,\n cfg: Config,\n sessionKeyRaw: string | undefined,\n agentIdRaw: string | undefined,\n): Promise<{ ok: true; root: string } | { ok: false; message: string }> {\n const sk = typeof sessionKeyRaw === 'string' ? sessionKeyRaw.trim() : '';\n if (sk) {\n try {\n const root = await service.sessions.getEffectiveWorkspacePath(sk);\n return { ok: true, root };\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, sessionKey: sk }, 'Session workspace root resolution failed');\n return { ok: false, message: em || 'Session workspace resolution failed' };\n }\n }\n return resolveEditorWorkspaceRoot(cfg, agentIdRaw);\n}\n\ninterface ResolvedWorkspaceImportConfig {\n targetDir: string;\n maxBytes: number;\n allowOverwrite: boolean;\n}\n\nfunction resolveWorkspaceImportConfig(cfg: Config): ResolvedWorkspaceImportConfig {\n const raw = cfg.workspace?.import;\n return {\n targetDir: raw?.targetDir?.trim() || 'imports',\n maxBytes: raw?.maxBytes ?? 104_857_600,\n allowOverwrite: raw?.allowOverwrite ?? true,\n };\n}\n\n/** Strip path separators, NULs and control chars from a basename so it stays in the destination dir. */\nfunction sanitizeImportBasename(name: string): string {\n return name\n .replace(/[\\\\/]/g, '')\n .replace(/[\\x00-\\x1f\\x7f]/g, '')\n .trim();\n}\n\n/**\n * Race-safe target picker for the `rename`-on-conflict strategy. Uses `link(tmp, target)`\n * which atomically fails with EEXIST when the candidate is taken; on success the tmp\n * file is left in place for the caller to unlink. Returns the linked target path.\n */\nasync function pickAvailableTargetWithLink(\n tmpAbs: string,\n initialDestAbs: string,\n maxAttempts = 1000,\n): Promise<{ ok: true; path: string; attempts: number } | { ok: false; attempts: number }> {\n const dir = dirname(initialDestAbs);\n const original = basename(initialDestAbs);\n const dotIdx = original.lastIndexOf('.');\n const stem = dotIdx > 0 ? original.slice(0, dotIdx) : original;\n const ext = dotIdx > 0 ? original.slice(dotIdx) : '';\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const candidate = attempt === 1 ? initialDestAbs : join(dir, `${stem}-${attempt}${ext}`);\n try {\n await link(tmpAbs, candidate);\n return { ok: true, path: candidate, attempts: attempt };\n } catch (err) {\n const code = (err as NodeJS.ErrnoException)?.code;\n if (code !== 'EEXIST') {\n throw err;\n }\n }\n }\n return { ok: false, attempts: maxAttempts };\n}\n\nfunction fileReferenceCapabilities(\n scope: FileReferenceScope,\n isDirectory: boolean,\n locationKind?: FileReferenceLocationKind,\n): FileReferenceCapability[] {\n if (scope === 'workspace') {\n return isDirectory\n ? ['openExternal', 'revealInFolder', 'copyPath']\n : ['preview', 'edit', 'openExternal', 'revealInFolder', 'copyPath'];\n }\n if (scope === 'external' || scope === 'agent-profile' || scope === 'session-artifact') {\n const base: FileReferenceCapability[] = ['openExternal', 'revealInFolder', 'copyPath'];\n // v1: importToWorkspace for files only; exclude xopc-config to prevent copying\n // app config into the workspace (semantically wrong).\n if (!isDirectory && locationKind !== 'xopc-config') {\n base.push('importToWorkspace');\n }\n return base;\n }\n if (scope === 'missing') return ['copyPath'];\n return [];\n}\n\nfunction isFileReferenceAction(action: unknown): action is 'openExternal' | 'revealInFolder' {\n return action === 'openExternal' || action === 'revealInFolder';\n}\n\nexport function registerWorkspaceRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service } = deps;\n\n authenticated.get('/api/workspace/inbound-file', async (c) => {\n const rel = c.req.query('rel');\n if (!rel || typeof rel !== 'string') {\n return c.json({ ok: false, error: { message: 'Missing rel' } }, 400);\n }\n const cfg = service.currentConfig;\n const agentHome = resolvePersistedAttachmentAgentHome(cfg, c.req.query('sessionKey'));\n const abs = resolveSafeInboundFilePath({ agentHome }, rel);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Forbidden' } }, 403);\n }\n try {\n const buf = await readFile(abs);\n const ext = rel.split('.').pop()?.toLowerCase() ?? '';\n const mimeByExt: Record<string, string> = {\n pdf: 'application/pdf',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n gif: 'image/gif',\n md: 'text/markdown',\n txt: 'text/plain',\n json: 'application/json',\n html: 'text/html',\n css: 'text/css',\n js: 'text/javascript',\n ts: 'text/typescript',\n webm: 'audio/webm',\n ogg: 'audio/ogg',\n opus: 'audio/ogg',\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n m4a: 'audio/mp4',\n };\n const contentType = mimeByExt[ext] || 'application/octet-stream';\n return new Response(buf, {\n headers: {\n 'Content-Type': contentType,\n 'Cache-Control': 'private, max-age=3600',\n },\n });\n } catch {\n return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n }\n });\n\n authenticated.get('/api/workspace/tts-file', async (c) => {\n const rel = c.req.query('rel');\n if (!rel || typeof rel !== 'string') {\n return c.json({ ok: false, error: { message: 'Missing rel' } }, 400);\n }\n const cfg = service.currentConfig;\n const agentHome = resolvePersistedAttachmentAgentHome(cfg, c.req.query('sessionKey'));\n const abs = resolveSafeTtsFilePath({ agentHome }, rel);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Forbidden' } }, 403);\n }\n try {\n const buf = await readFile(abs);\n const ext = rel.split('.').pop()?.toLowerCase() ?? '';\n const mimeByExt: Record<string, string> = {\n ogg: 'audio/ogg',\n opus: 'audio/ogg',\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n m4a: 'audio/mp4',\n };\n const contentType = mimeByExt[ext] || 'application/octet-stream';\n return new Response(buf, {\n headers: {\n 'Content-Type': contentType,\n 'Cache-Control': 'private, max-age=3600',\n },\n });\n } catch {\n return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n }\n });\n\n authenticated.get('/api/workspace/heartbeat-md', async (c) => {\n const abs = resolveHeartbeatMdPath(service.currentConfig);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Workspace not configured' } }, 400);\n }\n try {\n const content = await readFile(abs, 'utf-8');\n return c.json({ ok: true, payload: { content: content, file: 'HEARTBEAT.md' } });\n } catch {\n return c.json({ ok: true, payload: { content: '', file: 'HEARTBEAT.md' } });\n }\n });\n\n authenticated.put('/api/workspace/heartbeat-md', async (c) => {\n const abs = resolveHeartbeatMdPath(service.currentConfig);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Workspace not configured' } }, 400);\n }\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON' } }, 400);\n }\n const content =\n typeof body === 'object' &&\n body !== null &&\n 'content' in body &&\n typeof (body as { content: unknown }).content === 'string'\n ? (body as { content: string }).content\n : '';\n try {\n await writeFile(abs, content, 'utf-8');\n return c.json({ ok: true, payload: { file: 'HEARTBEAT.md' } });\n } catch (err) {\n log.error({ err, path: abs }, 'Failed to write HEARTBEAT.md');\n return c.json({ ok: false, error: { message: 'Write failed' } }, 500);\n }\n });\n\n authenticated.get('/api/workspace/editor/list', async (c) => {\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n const dirRel = typeof c.req.query('dir') === 'string' ? c.req.query('dir')! : '';\n const absDir = resolveWorkspaceSafePath(workspaceRoot, dirRel);\n if (!absDir) {\n return c.json({ ok: false, error: { message: 'Invalid path' } }, 400);\n }\n let st: Awaited<ReturnType<typeof stat>>;\n try {\n st = await stat(absDir);\n } catch {\n return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n }\n if (!st.isDirectory()) {\n return c.json({ ok: false, error: { message: 'Not a directory' } }, 400);\n }\n const dirents = await readdir(absDir, { withFileTypes: true });\n const entries: { name: string; path: string; absolutePath: string; isDirectory: boolean }[] = [];\n for (const entry of dirents) {\n if (entry.name.startsWith('.')) continue;\n const fullPath = join(absDir, entry.name);\n if (entry.isDirectory()) {\n entries.push({\n name: entry.name,\n path: toWorkspaceRelativePosix(workspaceRoot, fullPath),\n absolutePath: fullPath,\n isDirectory: true,\n });\n } else {\n entries.push({\n name: entry.name,\n path: toWorkspaceRelativePosix(workspaceRoot, fullPath),\n absolutePath: fullPath,\n isDirectory: false,\n });\n }\n }\n entries.sort((a, b) => {\n if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1;\n return a.name.localeCompare(b.name);\n });\n return c.json({ ok: true, payload: { entries } });\n });\n\n authenticated.get('/api/workspace/editor/read', async (c) => {\n const pathRel = typeof c.req.query('path') === 'string' ? c.req.query('path')! : '';\n if (!pathRel.trim()) {\n return c.json({ ok: false, error: { message: 'Missing path' } }, 400);\n }\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n const abs = resolveWorkspaceSafePath(workspaceRoot, pathRel);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Invalid path' } }, 400);\n }\n let st: Awaited<ReturnType<typeof stat>>;\n try {\n st = await stat(abs);\n } catch {\n return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n }\n if (!st.isFile()) {\n return c.json({ ok: false, error: { message: 'Not a file' } }, 400);\n }\n try {\n const content = await readFile(abs, 'utf-8');\n return c.json({\n ok: true,\n payload: {\n content,\n path: toWorkspaceRelativePosix(workspaceRoot, abs),\n absolutePath: abs,\n mtimeMs: st.mtimeMs,\n },\n });\n } catch {\n return c.json({ ok: false, error: { message: 'Read failed' } }, 500);\n }\n });\n\n /** Read file as raw bytes and return base64 (for PDF/images in workspace preview — avoids UTF-8 corruption). */\n authenticated.get('/api/workspace/editor/read-base64', async (c) => {\n const pathRel = typeof c.req.query('path') === 'string' ? c.req.query('path')! : '';\n if (!pathRel.trim()) {\n return c.json({ ok: false, error: { message: 'Missing path' } }, 400);\n }\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n const abs = resolveWorkspaceSafePath(workspaceRoot, pathRel);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Invalid path' } }, 400);\n }\n let st: Awaited<ReturnType<typeof stat>>;\n try {\n st = await stat(abs);\n } catch {\n return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n }\n if (!st.isFile()) {\n return c.json({ ok: false, error: { message: 'Not a file' } }, 400);\n }\n try {\n const buf = await readFile(abs);\n return c.json({\n ok: true,\n payload: {\n contentBase64: buf.toString('base64'),\n path: toWorkspaceRelativePosix(workspaceRoot, abs),\n /** Host absolute path — Electron can open with the default app (shell.openPath). */\n absolutePath: abs,\n mtimeMs: st.mtimeMs,\n },\n });\n } catch {\n return c.json({ ok: false, error: { message: 'Read failed' } }, 500);\n }\n });\n\n /** Map an absolute host path to a workspace-relative path (if under this session’s workspace). */\n authenticated.get('/api/workspace/editor/resolve-path', async (c) => {\n const raw = c.req.query('absolutePath');\n if (!raw || typeof raw !== 'string' || !raw.trim()) {\n return c.json({ ok: false, error: { message: 'Missing absolutePath' } }, 400);\n }\n const absolutePath = raw.trim();\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n const normalized = resolve(absolutePath);\n if (!isPathUnderWorkspace(workspaceRoot, normalized)) {\n return c.json({ ok: false, error: { message: 'Path not under workspace' } }, 403);\n }\n const rel = toWorkspaceRelativePosix(workspaceRoot, normalized);\n return c.json({ ok: true, payload: { workspaceRelativePath: rel } });\n });\n\n authenticated.get('/api/workspace/editor/resolve-reference', async (c) => {\n const rawPath = typeof c.req.query('path') === 'string' ? c.req.query('path')!.trim() : '';\n if (!rawPath) {\n return c.json({ ok: false, error: { code: 'INVALID_PATH', message: 'Missing path' } }, 400);\n }\n\n const sessionKey = typeof c.req.query('sessionKey') === 'string' ? c.req.query('sessionKey')!.trim() : '';\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n sessionKey,\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { code: 'WORKSPACE_RESOLUTION_FAILED', message: ws.message } }, 400);\n }\n\n const workspaceRoot = ws.root;\n const classifierCtx = { ...buildFilePathClassifierContext(service.currentConfig, sessionKey), workspaceRoot };\n const displayName = displayNameForPath(rawPath);\n const { candidate, invalid } = await resolveFileReferenceCandidate(rawPath, workspaceRoot, classifierCtx);\n\n if (!candidate || invalid) {\n return c.json({\n ok: true,\n payload: {\n inputPath: rawPath,\n displayName,\n scope: 'invalid' satisfies FileReferenceScope,\n exists: false,\n capabilities: [] as FileReferenceCapability[],\n errorCode: 'INVALID_PATH',\n },\n });\n }\n\n let st: Awaited<ReturnType<typeof stat>> | null = null;\n try {\n st = await stat(candidate);\n } catch {\n st = null;\n }\n\n if (!st) {\n // Always include the resolved candidate so the UI's \"Copy path\" yields\n // something actionable (\"I looked here, no file\"). Without this, bare\n // workspace-relative mentions fall back to the `rel:<path>` UI sentinel.\n return c.json({\n ok: true,\n payload: {\n inputPath: rawPath,\n displayName,\n scope: 'missing' satisfies FileReferenceScope,\n exists: false,\n absolutePath: candidate,\n capabilities: fileReferenceCapabilities('missing', false),\n errorCode: 'FILE_NOT_FOUND',\n },\n });\n }\n\n const classified = classifyFileLocation(candidate, classifierCtx);\n const { scope, locationKind, manageRoute } = classified;\n const inWorkspace = scope === 'workspace';\n const isDirectory = st.isDirectory();\n const capabilities = fileReferenceCapabilities(scope, isDirectory, locationKind);\n const ref = fileReferenceRegistry.register({\n absolutePath: candidate,\n sessionKey: sessionKey || undefined,\n scope,\n locationKind,\n capabilities,\n });\n\n return c.json({\n ok: true,\n payload: {\n fileRefId: ref.id,\n inputPath: rawPath,\n displayName,\n scope,\n locationKind,\n manageRoute,\n exists: true,\n isDirectory,\n absolutePath: candidate,\n workspaceRelativePath: inWorkspace ? toWorkspaceRelativePosix(workspaceRoot, candidate) : undefined,\n capabilities,\n mtimeMs: st.mtimeMs,\n },\n });\n });\n\n authenticated.post('/api/workspace/file-ref/:id/resolve-action', async (c) => {\n const id = c.req.param('id')?.trim() ?? '';\n if (!id) {\n return c.json({ ok: false, error: { code: 'INVALID_FILE_REF', message: 'Missing file reference' } }, 400);\n }\n\n const ref = fileReferenceRegistry.resolve(id);\n if (!ref) {\n return c.json({ ok: false, error: { code: 'FILE_REF_EXPIRED', message: 'File reference expired' } }, 404);\n }\n\n const sessionKey = typeof c.req.query('sessionKey') === 'string' ? c.req.query('sessionKey')!.trim() : '';\n if (!fileRefSessionKeysMatch(ref.sessionKey, sessionKey)) {\n return c.json({ ok: false, error: { code: 'FILE_REF_FORBIDDEN', message: 'File reference forbidden' } }, 403);\n }\n\n const body = (await c.req.json().catch(() => ({}))) as { action?: unknown };\n const action = body.action;\n if (!isFileReferenceAction(action)) {\n return c.json({ ok: false, error: { code: 'INVALID_ACTION', message: 'Invalid action' } }, 400);\n }\n if (!ref.capabilities.includes(action)) {\n return c.json({ ok: false, error: { code: 'ACTION_NOT_ALLOWED', message: 'Action not allowed' } }, 403);\n }\n\n let st: Awaited<ReturnType<typeof stat>>;\n try {\n st = await stat(ref.absolutePath);\n } catch {\n return c.json({ ok: false, error: { code: 'FILE_NOT_FOUND', message: 'File not found' } }, 404);\n }\n\n return c.json({\n ok: true,\n payload: {\n absolutePath: ref.absolutePath,\n isDirectory: st.isDirectory(),\n },\n });\n });\n\n authenticated.post('/api/workspace/import-file-ref/:id', async (c) => {\n const id = c.req.param('id')?.trim() ?? '';\n if (!id) {\n return c.json({ ok: false, error: { code: 'INVALID_FILE_REF', message: 'Missing file reference' } }, 400);\n }\n\n const ref = fileReferenceRegistry.resolve(id);\n if (!ref) {\n return c.json({ ok: false, error: { code: 'FILE_REF_EXPIRED', message: 'File reference expired' } }, 404);\n }\n\n const sessionKey = typeof c.req.query('sessionKey') === 'string' ? c.req.query('sessionKey')!.trim() : '';\n if (!fileRefSessionKeysMatch(ref.sessionKey, sessionKey)) {\n return c.json({ ok: false, error: { code: 'FILE_REF_FORBIDDEN', message: 'File reference forbidden' } }, 403);\n }\n\n if (!ref.capabilities.includes('importToWorkspace')) {\n return c.json({ ok: false, error: { code: 'IMPORT_NOT_ALLOWED', message: 'Import not allowed for this file' } }, 403);\n }\n\n let sourceStat: Awaited<ReturnType<typeof stat>>;\n try {\n sourceStat = await stat(ref.absolutePath);\n } catch {\n fileReferenceRegistry.expireById(id);\n return c.json({ ok: false, error: { code: 'SOURCE_NOT_FOUND', message: 'Source file no longer exists' } }, 404);\n }\n if (!sourceStat.isFile()) {\n return c.json({ ok: false, error: { code: 'SOURCE_NOT_FILE', message: 'Source is not a regular file' } }, 400);\n }\n\n const importCfg = resolveWorkspaceImportConfig(service.currentConfig);\n if (sourceStat.size > importCfg.maxBytes) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'SOURCE_TOO_LARGE',\n message: `Source exceeds maximum import size (${importCfg.maxBytes} bytes)`,\n },\n },\n 413,\n );\n }\n\n const ws = await resolveEditorWorkspaceRootAsync(service, service.currentConfig, sessionKey, undefined);\n if (ws.ok === false) {\n return c.json({ ok: false, error: { code: 'WORKSPACE_RESOLUTION_FAILED', message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n\n let body: { destination?: unknown; onConflict?: unknown };\n try {\n body = (await c.req.json().catch(() => ({}))) as typeof body;\n } catch {\n body = {};\n }\n const requestedDestRaw = typeof body.destination === 'string' ? body.destination.trim() : '';\n const onConflictRaw = typeof body.onConflict === 'string' ? body.onConflict : 'rename';\n if (onConflictRaw !== 'rename' && onConflictRaw !== 'overwrite' && onConflictRaw !== 'error') {\n return c.json({ ok: false, error: { code: 'INVALID_CONFLICT_MODE', message: 'Invalid onConflict value' } }, 400);\n }\n const onConflict = onConflictRaw as 'rename' | 'overwrite' | 'error';\n if (onConflict === 'overwrite' && !importCfg.allowOverwrite) {\n return c.json({ ok: false, error: { code: 'OVERWRITE_DISABLED', message: 'Overwrite is disabled by config' } }, 403);\n }\n\n const sourceBasename = sanitizeImportBasename(basename(ref.absolutePath)) || 'imported-file';\n let requestedRel: string;\n if (!requestedDestRaw) {\n requestedRel = `${importCfg.targetDir}/${sourceBasename}`;\n } else {\n const trimmedDest = requestedDestRaw.replace(/\\\\/g, '/');\n // Path ending with `/` is treated as a directory; append source basename.\n requestedRel = trimmedDest.endsWith('/') ? `${trimmedDest}${sourceBasename}` : trimmedDest;\n }\n\n let initialDestAbs = resolveWorkspaceSafePath(workspaceRoot, requestedRel);\n if (!initialDestAbs) {\n return c.json({ ok: false, error: { code: 'INVALID_DESTINATION', message: 'Invalid destination path' } }, 400);\n }\n\n // Sandbox: blocks `.xopc/xopc.json`, `.env*`, etc.; canonical symlink resolution included.\n const writePolicy = validateWritePath(initialDestAbs, workspaceRoot);\n if (!writePolicy.allowed) {\n return c.json({ ok: false, error: { code: 'DESTINATION_BLOCKED', message: writePolicy.reason ?? 'Destination blocked' } }, 403);\n }\n\n if (resolve(ref.absolutePath) === resolve(initialDestAbs)) {\n return c.json({ ok: false, error: { code: 'SAME_LOCATION', message: 'Destination is the same as source' } }, 400);\n }\n\n const destDir = dirname(initialDestAbs);\n try {\n await mkdir(destDir, { recursive: true });\n } catch (err) {\n log.warn({ err, destDir }, 'Failed to create import destination directory');\n return c.json({ ok: false, error: { code: 'IMPORT_FAILED', message: 'Failed to prepare destination' } }, 500);\n }\n\n // Stage source into a hidden tmp file inside the destination directory so we\n // can atomically `link` (rename strategy) or `rename` (overwrite) to land it.\n const tmpName = `.${basename(initialDestAbs)}.import-${randomUUID()}.tmp`;\n const tmpAbs = join(destDir, tmpName);\n\n const started = Date.now();\n let renamed = false;\n let overwrote = false;\n let finalDestAbs = initialDestAbs;\n\n try {\n try {\n await copyFile(ref.absolutePath, tmpAbs, fsConstants.COPYFILE_FICLONE);\n } catch (err) {\n await unlink(tmpAbs).catch(() => {});\n log.warn({ err, source: ref.absolutePath, tmpAbs }, 'Failed to copy source into staging tmp');\n return c.json({ ok: false, error: { code: 'IMPORT_FAILED', message: 'Failed to copy source file' } }, 500);\n }\n\n if (onConflict === 'overwrite') {\n // Snapshot pre-rename existence for telemetry; the actual overwrite is unconditional.\n overwrote = await stat(initialDestAbs).then(() => true).catch(() => false);\n try {\n await rename(tmpAbs, initialDestAbs);\n } catch (err) {\n await unlink(tmpAbs).catch(() => {});\n log.warn({ err, target: initialDestAbs }, 'Atomic rename failed');\n return c.json({ ok: false, error: { code: 'IMPORT_FAILED', message: 'Failed to finalize import' } }, 500);\n }\n } else if (onConflict === 'error') {\n const exists = await stat(initialDestAbs).then(() => true).catch(() => false);\n if (exists) {\n await unlink(tmpAbs).catch(() => {});\n return c.json({ ok: false, error: { code: 'DESTINATION_EXISTS', message: 'Destination already exists' } }, 409);\n }\n try {\n await link(tmpAbs, initialDestAbs);\n await unlink(tmpAbs).catch(() => {});\n } catch (err) {\n await unlink(tmpAbs).catch(() => {});\n log.warn({ err, target: initialDestAbs }, 'Hard link to destination failed');\n return c.json({ ok: false, error: { code: 'IMPORT_FAILED', message: 'Failed to finalize import' } }, 500);\n }\n } else {\n // rename: race-safe loop using O_EXCL semantics of `link`.\n const picked = await pickAvailableTargetWithLink(tmpAbs, initialDestAbs);\n if (!picked.ok) {\n await unlink(tmpAbs).catch(() => {});\n log.warn({ target: initialDestAbs, attempts: picked.attempts }, 'Failed to find free import target name');\n return c.json({ ok: false, error: { code: 'IMPORT_FAILED', message: 'No free destination name available' } }, 500);\n }\n await unlink(tmpAbs).catch(() => {});\n finalDestAbs = picked.path;\n renamed = picked.path !== initialDestAbs;\n }\n } catch (err) {\n await unlink(tmpAbs).catch(() => {});\n log.error({ err }, 'Import file unexpected failure');\n return c.json({ ok: false, error: { code: 'IMPORT_FAILED', message: 'Import failed' } }, 500);\n }\n\n let finalMtime: number;\n try {\n finalMtime = (await stat(finalDestAbs)).mtimeMs;\n } catch {\n finalMtime = Date.now();\n }\n\n const workspaceRel = toWorkspaceRelativePosix(workspaceRoot, finalDestAbs);\n\n fileReferenceRegistry.expireById(id);\n const newRef = fileReferenceRegistry.register({\n absolutePath: finalDestAbs,\n sessionKey: sessionKey || undefined,\n scope: 'workspace',\n capabilities: fileReferenceCapabilities('workspace', false),\n });\n\n const sourceScope = ref.scope;\n const sourceLocationKind = ref.locationKind;\n\n service.emit('workspace.file-imported', {\n sessionKey: sessionKey || undefined,\n workspaceRelativePath: workspaceRel,\n absolutePath: finalDestAbs,\n bytes: sourceStat.size,\n sourceScope,\n sourceLocationKind,\n });\n\n log.info(\n {\n sessionKey,\n fileRefId: id,\n sourceAbsolutePath: ref.absolutePath,\n sourceScope,\n sourceLocationKind,\n destWorkspaceRelativePath: workspaceRel,\n bytes: sourceStat.size,\n renamed,\n overwrote,\n durationMs: Date.now() - started,\n },\n 'Workspace file import succeeded',\n );\n\n return c.json({\n ok: true,\n payload: {\n workspaceRelativePath: workspaceRel,\n absolutePath: finalDestAbs,\n bytesCopied: sourceStat.size,\n sourceAbsolutePath: ref.absolutePath,\n sourceScope,\n sourceLocationKind,\n renamed,\n overwrote,\n mtimeMs: finalMtime,\n newFileRefId: newRef.id,\n },\n });\n });\n\n /**\n * Serve a workspace file as raw bytes (e.g. <img> after auth fetch + blob URL).\n * Path is workspace-relative; scope via sessionKey / agentId like other editor routes.\n */\n authenticated.get('/api/workspace/editor/raw', async (c) => {\n const pathRel = typeof c.req.query('path') === 'string' ? c.req.query('path')! : '';\n if (!pathRel.trim()) {\n return c.json({ ok: false, error: { message: 'Missing path' } }, 400);\n }\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n const abs = resolveWorkspaceSafePath(workspaceRoot, pathRel);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Invalid path' } }, 400);\n }\n let st: Awaited<ReturnType<typeof stat>>;\n try {\n st = await stat(abs);\n } catch {\n return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n }\n if (!st.isFile()) {\n return c.json({ ok: false, error: { message: 'Not a file' } }, 400);\n }\n const ext = pathRel.split('.').pop()?.toLowerCase() ?? '';\n const mimeByExt: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n bmp: 'image/bmp',\n svg: 'image/svg+xml',\n pdf: 'application/pdf',\n docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n txt: 'text/plain',\n md: 'text/markdown',\n json: 'application/json',\n html: 'text/html',\n css: 'text/css',\n js: 'text/javascript',\n ts: 'text/typescript',\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n ogg: 'audio/ogg',\n webm: 'video/webm',\n mp4: 'video/mp4',\n mov: 'video/quicktime',\n };\n const contentType = mimeByExt[ext] || 'application/octet-stream';\n try {\n const buf = await readFile(abs);\n return new Response(buf, {\n headers: {\n 'Content-Type': contentType,\n 'Cache-Control': 'private, max-age=3600',\n },\n });\n } catch {\n return c.json({ ok: false, error: { message: 'Read failed' } }, 500);\n }\n });\n\n authenticated.put('/api/workspace/editor/write', async (c) => {\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON' } }, 400);\n }\n const pathRel =\n typeof body === 'object' &&\n body !== null &&\n 'path' in body &&\n typeof (body as { path: unknown }).path === 'string'\n ? (body as { path: string }).path\n : '';\n const content =\n typeof body === 'object' &&\n body !== null &&\n 'content' in body &&\n typeof (body as { content: unknown }).content === 'string'\n ? (body as { content: string }).content\n : '';\n if (!pathRel.trim()) {\n return c.json({ ok: false, error: { message: 'Missing path' } }, 400);\n }\n const abs = resolveWorkspaceSafePath(workspaceRoot, pathRel);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Invalid path' } }, 400);\n }\n let st: Awaited<ReturnType<typeof stat>> | undefined;\n try {\n st = await stat(abs);\n } catch {\n st = undefined;\n }\n if (st && !st.isFile()) {\n return c.json({ ok: false, error: { message: 'Not a file' } }, 400);\n }\n try {\n await writeFile(abs, content, 'utf-8');\n let mtimeMs: number;\n try {\n mtimeMs = (await stat(abs)).mtimeMs;\n } catch {\n mtimeMs = Date.now();\n }\n return c.json({\n ok: true,\n payload: { path: toWorkspaceRelativePosix(workspaceRoot, abs), mtimeMs },\n });\n } catch (err) {\n log.error({ err, path: abs }, 'workspace editor write failed');\n return c.json({ ok: false, error: { message: 'Write failed' } }, 500);\n }\n });\n\n authenticated.get('/api/workspace/editor/search', async (c) => {\n const q = typeof c.req.query('q') === 'string' ? c.req.query('q')!.trim() : '';\n const dirRel = typeof c.req.query('dir') === 'string' ? c.req.query('dir')! : '';\n if (!q) {\n return c.json({\n ok: true,\n payload: { results: [] as { filePath: string; lineNumber: number; lineContent: string; matchStart: number; matchEnd: number }[] },\n });\n }\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n const absDir = resolveWorkspaceSafePath(workspaceRoot, dirRel);\n if (!absDir) {\n return c.json({ ok: false, error: { message: 'Invalid path' } }, 400);\n }\n let st: Awaited<ReturnType<typeof stat>>;\n try {\n st = await stat(absDir);\n } catch {\n return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n }\n if (!st.isDirectory()) {\n return c.json({ ok: false, error: { message: 'Not a directory' } }, 400);\n }\n const raw = await runRipgrepInDirectory(q, absDir);\n const results = raw\n .filter((r) => isPathUnderWorkspace(workspaceRoot, r.filePath))\n .map((r) => ({\n ...r,\n filePath: toWorkspaceRelativePosix(workspaceRoot, resolve(r.filePath)),\n }));\n return c.json({ ok: true, payload: { results } });\n });\n\n /** Fuzzy filename / path search over the session workspace (ripgrep `--files` + subsequence scoring). */\n authenticated.get('/api/workspace/editor/files/search', async (c) => {\n const q = typeof c.req.query('q') === 'string' ? c.req.query('q')!.trim() : '';\n const limitRaw = c.req.query('limit');\n const limit = Math.min(\n Math.max(parseInt(typeof limitRaw === 'string' ? limitRaw : '15', 10) || 15, 1),\n FILE_SEARCH_MAX_LIMIT,\n );\n\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n\n const entries = await fuzzySearchWorkspaceFiles(ws.root, q, limit);\n return c.json({ ok: true, payload: { entries } });\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"workspace.js","names":["fsConstants"],"sources":["../../../../../src/gateway/hono/routes/workspace.ts"],"sourcesContent":["import type { Hono } from 'hono';\nimport { randomUUID } from 'node:crypto';\nimport { constants as fsConstants } from 'node:fs';\nimport { copyFile, link, mkdir, readdir, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join, resolve } from 'node:path';\n\nimport { extractProfileAgentId } from '../../../config/agent-profile.js';\nimport { type Config } from '../../../config/schema.js';\nimport { getWorkspacePath } from '../../../config/workspace-path-helpers.js';\nimport { validateWritePath } from '../../../agent/sandbox/path-policy.js';\nimport { resolveSafeInboundFilePath } from '../../../channels/attachments/inbound-persist.js';\nimport { resolveSafeTtsFilePath } from '../../../channels/attachments/outbound-tts-persist.js';\nimport {\n listAgentEntries,\n normalizeAgentId,\n resolveAgentHomeDir,\n resolveAgentWorkspaceDir,\n resolveDefaultAgentId,\n} from '../../../agent/agent-scope.js';\nimport { createGatewayRouteLogger } from '../lib/route-logger.js';\nimport { resolveHeartbeatMdPath } from '../../workspace-heartbeat-path.js';\nimport {\n isPathUnderWorkspace,\n resolveWorkspaceSafePath,\n toWorkspaceRelativePosix,\n} from '../../workspace-editor-path.js';\nimport { listWorkspaceRelativeFilesFsFallback } from '../../workspace-fs-file-list.js';\nimport { runRipgrepInDirectory, runRipgrepListFiles } from '../../workspace-ripgrep.js';\nimport {\n buildFilePathClassifierContext,\n classifyFileLocation,\n displayNameForPath,\n fileRefSessionKeysMatch,\n resolveFileReferenceCandidate,\n} from '../../file-path-classifier.js';\nimport {\n fileReferenceRegistry,\n type FileReferenceCapability,\n type FileReferenceLocationKind,\n type FileReferenceScope,\n} from '../../file-reference-registry.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\nimport type { GatewayService } from '../../service.js';\n\nconst log = createGatewayRouteLogger('Workspace');\n\n/** Agent home for persisted `inbound/` and `tts/` attachments (matches `persistOutboundTtsAudio` / `prepareInboundAttachments`). */\nfunction resolvePersistedAttachmentAgentHome(cfg: Config, sessionKeyRaw: string | undefined): string {\n const sk = typeof sessionKeyRaw === 'string' ? sessionKeyRaw.trim() : '';\n const agentId = sk ? extractProfileAgentId(sk, cfg) : resolveDefaultAgentId(cfg);\n return resolveAgentHomeDir(cfg, agentId);\n}\n\nconst FILE_SEARCH_MAX_LIMIT = 50;\n\n/** Subsequence fuzzy match: all query chars appear in order in `candidate` (case-insensitive). */\nfunction fuzzySubsequenceScore(query: string, candidate: string): number | null {\n const q = query.toLowerCase();\n const c = candidate.toLowerCase();\n if (q.length === 0) return 0;\n let qi = 0;\n for (let ci = 0; ci < c.length && qi < q.length; ci++) {\n if (c[ci] === q[qi]) qi++;\n }\n if (qi < q.length) return null;\n const base = c.split('/').pop() ?? c;\n let score = 10;\n if (c.startsWith(q)) score += 40;\n if (base.startsWith(q)) score += 35;\n else if (base.includes(q)) score += 20;\n else if (c.includes(q)) score += 10;\n score -= c.length * 0.0001;\n return score;\n}\n\nasync function fuzzySearchWorkspaceFiles(\n workspaceRoot: string,\n query: string,\n limit: number,\n): Promise<Array<{ name: string; path: string; isDirectory: boolean }>> {\n let files = await runRipgrepListFiles(workspaceRoot);\n if (files.length === 0) {\n files = await listWorkspaceRelativeFilesFsFallback(workspaceRoot, 120_000);\n if (files.length > 0) {\n log.debug(\n { workspaceRoot, fileCount: files.length },\n 'workspace files/search: file list from fs walk (ripgrep unavailable or returned empty)',\n );\n }\n }\n const q = query.trim();\n const capped = Math.min(Math.max(limit, 1), FILE_SEARCH_MAX_LIMIT);\n\n type Row = { name: string; path: string; isDirectory: boolean; score: number };\n const rows: Row[] = [];\n\n if (!q) {\n const sorted = [...files].sort((a, b) => a.localeCompare(b));\n for (const rel of sorted.slice(0, capped)) {\n const name = rel.split('/').pop() ?? rel;\n rows.push({ name, path: rel, isDirectory: false, score: 0 });\n }\n return rows;\n }\n\n for (const rel of files) {\n const name = rel.split('/').pop() ?? rel;\n const scorePath = fuzzySubsequenceScore(q, rel);\n const scoreName = fuzzySubsequenceScore(q, name);\n const score = Math.max(scorePath ?? -Infinity, scoreName ?? -Infinity);\n if (score === -Infinity) continue;\n rows.push({ name, path: rel, isDirectory: false, score });\n }\n\n rows.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));\n return rows.slice(0, capped).map(({ name, path, isDirectory }) => ({ name, path, isDirectory }));\n}\n\nfunction isKnownEditorAgentId(cfg: Config, id: string): boolean {\n const n = normalizeAgentId(id);\n if (n === resolveDefaultAgentId(cfg)) return true;\n return listAgentEntries(cfg).some((e) => normalizeAgentId(e.id) === n);\n}\n\nfunction resolveEditorWorkspaceRoot(\n cfg: Config,\n agentIdRaw: string | undefined,\n): { ok: true; root: string } | { ok: false; message: string } {\n const trimmed = typeof agentIdRaw === 'string' ? agentIdRaw.trim() : '';\n if (!trimmed) {\n const root = getWorkspacePath(cfg);\n if (!root) return { ok: false, message: 'Workspace not configured' };\n return { ok: true, root };\n }\n const id = normalizeAgentId(trimmed);\n if (!isKnownEditorAgentId(cfg, id)) {\n return { ok: false, message: 'Unknown agent' };\n }\n return { ok: true, root: resolveAgentWorkspaceDir(cfg, id) };\n}\n\n/** Prefer `sessionKey` (per-session workspace override) over `agentId`. */\nasync function resolveEditorWorkspaceRootAsync(\n service: GatewayService,\n cfg: Config,\n sessionKeyRaw: string | undefined,\n agentIdRaw: string | undefined,\n): Promise<{ ok: true; root: string } | { ok: false; message: string }> {\n const sk = typeof sessionKeyRaw === 'string' ? sessionKeyRaw.trim() : '';\n if (sk) {\n try {\n const root = await service.sessions.getEffectiveWorkspacePath(sk);\n return { ok: true, root };\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, sessionKey: sk }, 'Session workspace root resolution failed');\n return { ok: false, message: em || 'Session workspace resolution failed' };\n }\n }\n return resolveEditorWorkspaceRoot(cfg, agentIdRaw);\n}\n\ninterface ResolvedWorkspaceImportConfig {\n targetDir: string;\n maxBytes: number;\n allowOverwrite: boolean;\n}\n\nfunction resolveWorkspaceImportConfig(cfg: Config): ResolvedWorkspaceImportConfig {\n const raw = cfg.workspace?.import;\n return {\n targetDir: raw?.targetDir?.trim() || 'imports',\n maxBytes: raw?.maxBytes ?? 104_857_600,\n allowOverwrite: raw?.allowOverwrite ?? true,\n };\n}\n\n/** Strip path separators, NULs and control chars from a basename so it stays in the destination dir. */\nfunction sanitizeImportBasename(name: string): string {\n return name\n .replace(/[\\\\/]/g, '')\n .replace(/[\\x00-\\x1f\\x7f]/g, '')\n .trim();\n}\n\n/**\n * Race-safe target picker for the `rename`-on-conflict strategy. Uses `link(tmp, target)`\n * which atomically fails with EEXIST when the candidate is taken; on success the tmp\n * file is left in place for the caller to unlink. Returns the linked target path.\n */\nasync function pickAvailableTargetWithLink(\n tmpAbs: string,\n initialDestAbs: string,\n maxAttempts = 1000,\n): Promise<{ ok: true; path: string; attempts: number } | { ok: false; attempts: number }> {\n const dir = dirname(initialDestAbs);\n const original = basename(initialDestAbs);\n const dotIdx = original.lastIndexOf('.');\n const stem = dotIdx > 0 ? original.slice(0, dotIdx) : original;\n const ext = dotIdx > 0 ? original.slice(dotIdx) : '';\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const candidate = attempt === 1 ? initialDestAbs : join(dir, `${stem}-${attempt}${ext}`);\n try {\n await link(tmpAbs, candidate);\n return { ok: true, path: candidate, attempts: attempt };\n } catch (err) {\n const code = (err as NodeJS.ErrnoException)?.code;\n if (code !== 'EEXIST') {\n throw err;\n }\n }\n }\n return { ok: false, attempts: maxAttempts };\n}\n\nfunction fileReferenceCapabilities(\n scope: FileReferenceScope,\n isDirectory: boolean,\n locationKind?: FileReferenceLocationKind,\n): FileReferenceCapability[] {\n if (scope === 'workspace') {\n return isDirectory\n ? ['openExternal', 'revealInFolder', 'copyPath']\n : ['preview', 'edit', 'openExternal', 'revealInFolder', 'copyPath'];\n }\n if (scope === 'external' || scope === 'agent-profile' || scope === 'session-artifact') {\n const base: FileReferenceCapability[] = ['openExternal', 'revealInFolder', 'copyPath'];\n // v1: importToWorkspace for files only; exclude xopc-config to prevent copying\n // app config into the workspace (semantically wrong).\n if (!isDirectory && locationKind !== 'xopc-config') {\n base.push('importToWorkspace');\n }\n return base;\n }\n if (scope === 'missing') return ['copyPath'];\n return [];\n}\n\nfunction isFileReferenceAction(action: unknown): action is 'openExternal' | 'revealInFolder' {\n return action === 'openExternal' || action === 'revealInFolder';\n}\n\nexport function registerWorkspaceRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service } = deps;\n\n authenticated.get('/api/workspace/inbound-file', async (c) => {\n const rel = c.req.query('rel');\n if (!rel || typeof rel !== 'string') {\n return c.json({ ok: false, error: { message: 'Missing rel' } }, 400);\n }\n const cfg = service.currentConfig;\n const agentHome = resolvePersistedAttachmentAgentHome(cfg, c.req.query('sessionKey'));\n const abs = resolveSafeInboundFilePath({ agentHome }, rel);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Forbidden' } }, 403);\n }\n try {\n const buf = await readFile(abs);\n const ext = rel.split('.').pop()?.toLowerCase() ?? '';\n const mimeByExt: Record<string, string> = {\n pdf: 'application/pdf',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n gif: 'image/gif',\n md: 'text/markdown',\n txt: 'text/plain',\n json: 'application/json',\n html: 'text/html',\n css: 'text/css',\n js: 'text/javascript',\n ts: 'text/typescript',\n webm: 'audio/webm',\n ogg: 'audio/ogg',\n opus: 'audio/ogg',\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n m4a: 'audio/mp4',\n };\n const contentType = mimeByExt[ext] || 'application/octet-stream';\n return new Response(buf, {\n headers: {\n 'Content-Type': contentType,\n 'Cache-Control': 'private, max-age=3600',\n },\n });\n } catch {\n return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n }\n });\n\n authenticated.get('/api/workspace/tts-file', async (c) => {\n const rel = c.req.query('rel');\n if (!rel || typeof rel !== 'string') {\n return c.json({ ok: false, error: { message: 'Missing rel' } }, 400);\n }\n const cfg = service.currentConfig;\n const agentHome = resolvePersistedAttachmentAgentHome(cfg, c.req.query('sessionKey'));\n const abs = resolveSafeTtsFilePath({ agentHome }, rel);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Forbidden' } }, 403);\n }\n try {\n const buf = await readFile(abs);\n const ext = rel.split('.').pop()?.toLowerCase() ?? '';\n const mimeByExt: Record<string, string> = {\n ogg: 'audio/ogg',\n opus: 'audio/ogg',\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n m4a: 'audio/mp4',\n };\n const contentType = mimeByExt[ext] || 'application/octet-stream';\n return new Response(buf, {\n headers: {\n 'Content-Type': contentType,\n 'Cache-Control': 'private, max-age=3600',\n },\n });\n } catch {\n return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n }\n });\n\n authenticated.get('/api/workspace/heartbeat-md', async (c) => {\n const abs = resolveHeartbeatMdPath(service.currentConfig);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Workspace not configured' } }, 400);\n }\n try {\n const content = await readFile(abs, 'utf-8');\n return c.json({ ok: true, payload: { content: content, file: 'HEARTBEAT.md' } });\n } catch {\n return c.json({ ok: true, payload: { content: '', file: 'HEARTBEAT.md' } });\n }\n });\n\n authenticated.put('/api/workspace/heartbeat-md', async (c) => {\n const abs = resolveHeartbeatMdPath(service.currentConfig);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Workspace not configured' } }, 400);\n }\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON' } }, 400);\n }\n const content =\n typeof body === 'object' &&\n body !== null &&\n 'content' in body &&\n typeof (body as { content: unknown }).content === 'string'\n ? (body as { content: string }).content\n : '';\n try {\n await writeFile(abs, content, 'utf-8');\n return c.json({ ok: true, payload: { file: 'HEARTBEAT.md' } });\n } catch (err) {\n log.error({ err, path: abs }, 'Failed to write HEARTBEAT.md');\n return c.json({ ok: false, error: { message: 'Write failed' } }, 500);\n }\n });\n\n authenticated.get('/api/workspace/editor/list', async (c) => {\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n const dirRel = typeof c.req.query('dir') === 'string' ? c.req.query('dir')! : '';\n const absDir = resolveWorkspaceSafePath(workspaceRoot, dirRel);\n if (!absDir) {\n return c.json({ ok: false, error: { message: 'Invalid path' } }, 400);\n }\n let st: Awaited<ReturnType<typeof stat>>;\n try {\n st = await stat(absDir);\n } catch {\n return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n }\n if (!st.isDirectory()) {\n return c.json({ ok: false, error: { message: 'Not a directory' } }, 400);\n }\n const dirents = await readdir(absDir, { withFileTypes: true });\n const entries: { name: string; path: string; absolutePath: string; isDirectory: boolean }[] = [];\n for (const entry of dirents) {\n if (entry.name.startsWith('.')) continue;\n const fullPath = join(absDir, entry.name);\n if (entry.isDirectory()) {\n entries.push({\n name: entry.name,\n path: toWorkspaceRelativePosix(workspaceRoot, fullPath),\n absolutePath: fullPath,\n isDirectory: true,\n });\n } else {\n entries.push({\n name: entry.name,\n path: toWorkspaceRelativePosix(workspaceRoot, fullPath),\n absolutePath: fullPath,\n isDirectory: false,\n });\n }\n }\n entries.sort((a, b) => {\n if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1;\n return a.name.localeCompare(b.name);\n });\n return c.json({ ok: true, payload: { entries } });\n });\n\n authenticated.get('/api/workspace/editor/read', async (c) => {\n const pathRel = typeof c.req.query('path') === 'string' ? c.req.query('path')! : '';\n if (!pathRel.trim()) {\n return c.json({ ok: false, error: { message: 'Missing path' } }, 400);\n }\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n const abs = resolveWorkspaceSafePath(workspaceRoot, pathRel);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Invalid path' } }, 400);\n }\n let st: Awaited<ReturnType<typeof stat>>;\n try {\n st = await stat(abs);\n } catch {\n return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n }\n if (!st.isFile()) {\n return c.json({ ok: false, error: { message: 'Not a file' } }, 400);\n }\n try {\n const content = await readFile(abs, 'utf-8');\n return c.json({\n ok: true,\n payload: {\n content,\n path: toWorkspaceRelativePosix(workspaceRoot, abs),\n absolutePath: abs,\n mtimeMs: st.mtimeMs,\n },\n });\n } catch {\n return c.json({ ok: false, error: { message: 'Read failed' } }, 500);\n }\n });\n\n /** Read file as raw bytes and return base64 (for PDF/images in workspace preview — avoids UTF-8 corruption). */\n authenticated.get('/api/workspace/editor/read-base64', async (c) => {\n const pathRel = typeof c.req.query('path') === 'string' ? c.req.query('path')! : '';\n if (!pathRel.trim()) {\n return c.json({ ok: false, error: { message: 'Missing path' } }, 400);\n }\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n const abs = resolveWorkspaceSafePath(workspaceRoot, pathRel);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Invalid path' } }, 400);\n }\n let st: Awaited<ReturnType<typeof stat>>;\n try {\n st = await stat(abs);\n } catch {\n return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n }\n if (!st.isFile()) {\n return c.json({ ok: false, error: { message: 'Not a file' } }, 400);\n }\n try {\n const buf = await readFile(abs);\n return c.json({\n ok: true,\n payload: {\n contentBase64: buf.toString('base64'),\n path: toWorkspaceRelativePosix(workspaceRoot, abs),\n /** Host absolute path — Electron can open with the default app (shell.openPath). */\n absolutePath: abs,\n mtimeMs: st.mtimeMs,\n },\n });\n } catch {\n return c.json({ ok: false, error: { message: 'Read failed' } }, 500);\n }\n });\n\n /** Map an absolute host path to a workspace-relative path (if under this session’s workspace). */\n authenticated.get('/api/workspace/editor/resolve-path', async (c) => {\n const raw = c.req.query('absolutePath');\n if (!raw || typeof raw !== 'string' || !raw.trim()) {\n return c.json({ ok: false, error: { message: 'Missing absolutePath' } }, 400);\n }\n const absolutePath = raw.trim();\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n const normalized = resolve(absolutePath);\n if (!isPathUnderWorkspace(workspaceRoot, normalized)) {\n return c.json({ ok: false, error: { message: 'Path not under workspace' } }, 403);\n }\n const rel = toWorkspaceRelativePosix(workspaceRoot, normalized);\n return c.json({ ok: true, payload: { workspaceRelativePath: rel } });\n });\n\n authenticated.get('/api/workspace/editor/resolve-reference', async (c) => {\n const rawPath = typeof c.req.query('path') === 'string' ? c.req.query('path')!.trim() : '';\n if (!rawPath) {\n return c.json({ ok: false, error: { code: 'INVALID_PATH', message: 'Missing path' } }, 400);\n }\n\n const sessionKey = typeof c.req.query('sessionKey') === 'string' ? c.req.query('sessionKey')!.trim() : '';\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n sessionKey,\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { code: 'WORKSPACE_RESOLUTION_FAILED', message: ws.message } }, 400);\n }\n\n const workspaceRoot = ws.root;\n const classifierCtx = { ...buildFilePathClassifierContext(service.currentConfig, sessionKey), workspaceRoot };\n const displayName = displayNameForPath(rawPath);\n const { candidate, invalid } = await resolveFileReferenceCandidate(rawPath, workspaceRoot, classifierCtx);\n\n if (!candidate || invalid) {\n return c.json({\n ok: true,\n payload: {\n inputPath: rawPath,\n displayName,\n scope: 'invalid' satisfies FileReferenceScope,\n exists: false,\n capabilities: [] as FileReferenceCapability[],\n errorCode: 'INVALID_PATH',\n },\n });\n }\n\n let st: Awaited<ReturnType<typeof stat>> | null = null;\n try {\n st = await stat(candidate);\n } catch {\n st = null;\n }\n\n if (!st) {\n // Always include the resolved candidate so the UI's \"Copy path\" yields\n // something actionable (\"I looked here, no file\"). Without this, bare\n // workspace-relative mentions fall back to the `rel:<path>` UI sentinel.\n return c.json({\n ok: true,\n payload: {\n inputPath: rawPath,\n displayName,\n scope: 'missing' satisfies FileReferenceScope,\n exists: false,\n absolutePath: candidate,\n capabilities: fileReferenceCapabilities('missing', false),\n errorCode: 'FILE_NOT_FOUND',\n },\n });\n }\n\n const classified = classifyFileLocation(candidate, classifierCtx);\n const { scope, locationKind, manageRoute } = classified;\n const inWorkspace = scope === 'workspace';\n const isDirectory = st.isDirectory();\n const capabilities = fileReferenceCapabilities(scope, isDirectory, locationKind);\n const ref = fileReferenceRegistry.register({\n absolutePath: candidate,\n sessionKey: sessionKey || undefined,\n scope,\n locationKind,\n capabilities,\n });\n\n return c.json({\n ok: true,\n payload: {\n fileRefId: ref.id,\n inputPath: rawPath,\n displayName,\n scope,\n locationKind,\n manageRoute,\n exists: true,\n isDirectory,\n absolutePath: candidate,\n workspaceRelativePath: inWorkspace ? toWorkspaceRelativePosix(workspaceRoot, candidate) : undefined,\n capabilities,\n mtimeMs: st.mtimeMs,\n },\n });\n });\n\n authenticated.post('/api/workspace/file-ref/:id/resolve-action', async (c) => {\n const id = c.req.param('id')?.trim() ?? '';\n if (!id) {\n return c.json({ ok: false, error: { code: 'INVALID_FILE_REF', message: 'Missing file reference' } }, 400);\n }\n\n const ref = fileReferenceRegistry.resolve(id);\n if (!ref) {\n return c.json({ ok: false, error: { code: 'FILE_REF_EXPIRED', message: 'File reference expired' } }, 404);\n }\n\n const sessionKey = typeof c.req.query('sessionKey') === 'string' ? c.req.query('sessionKey')!.trim() : '';\n if (!fileRefSessionKeysMatch(ref.sessionKey, sessionKey)) {\n return c.json({ ok: false, error: { code: 'FILE_REF_FORBIDDEN', message: 'File reference forbidden' } }, 403);\n }\n\n const body = (await c.req.json().catch(() => ({}))) as { action?: unknown };\n const action = body.action;\n if (!isFileReferenceAction(action)) {\n return c.json({ ok: false, error: { code: 'INVALID_ACTION', message: 'Invalid action' } }, 400);\n }\n if (!ref.capabilities.includes(action)) {\n return c.json({ ok: false, error: { code: 'ACTION_NOT_ALLOWED', message: 'Action not allowed' } }, 403);\n }\n\n let st: Awaited<ReturnType<typeof stat>>;\n try {\n st = await stat(ref.absolutePath);\n } catch {\n return c.json({ ok: false, error: { code: 'FILE_NOT_FOUND', message: 'File not found' } }, 404);\n }\n\n return c.json({\n ok: true,\n payload: {\n absolutePath: ref.absolutePath,\n isDirectory: st.isDirectory(),\n },\n });\n });\n\n authenticated.post('/api/workspace/import-file-ref/:id', async (c) => {\n const id = c.req.param('id')?.trim() ?? '';\n if (!id) {\n return c.json({ ok: false, error: { code: 'INVALID_FILE_REF', message: 'Missing file reference' } }, 400);\n }\n\n const ref = fileReferenceRegistry.resolve(id);\n if (!ref) {\n return c.json({ ok: false, error: { code: 'FILE_REF_EXPIRED', message: 'File reference expired' } }, 404);\n }\n\n const sessionKey = typeof c.req.query('sessionKey') === 'string' ? c.req.query('sessionKey')!.trim() : '';\n if (!fileRefSessionKeysMatch(ref.sessionKey, sessionKey)) {\n return c.json({ ok: false, error: { code: 'FILE_REF_FORBIDDEN', message: 'File reference forbidden' } }, 403);\n }\n\n if (!ref.capabilities.includes('importToWorkspace')) {\n return c.json({ ok: false, error: { code: 'IMPORT_NOT_ALLOWED', message: 'Import not allowed for this file' } }, 403);\n }\n\n let sourceStat: Awaited<ReturnType<typeof stat>>;\n try {\n sourceStat = await stat(ref.absolutePath);\n } catch {\n fileReferenceRegistry.expireById(id);\n return c.json({ ok: false, error: { code: 'SOURCE_NOT_FOUND', message: 'Source file no longer exists' } }, 404);\n }\n if (!sourceStat.isFile()) {\n return c.json({ ok: false, error: { code: 'SOURCE_NOT_FILE', message: 'Source is not a regular file' } }, 400);\n }\n\n const importCfg = resolveWorkspaceImportConfig(service.currentConfig);\n if (sourceStat.size > importCfg.maxBytes) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'SOURCE_TOO_LARGE',\n message: `Source exceeds maximum import size (${importCfg.maxBytes} bytes)`,\n },\n },\n 413,\n );\n }\n\n const ws = await resolveEditorWorkspaceRootAsync(service, service.currentConfig, sessionKey, undefined);\n if (ws.ok === false) {\n return c.json({ ok: false, error: { code: 'WORKSPACE_RESOLUTION_FAILED', message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n\n let body: { destination?: unknown; onConflict?: unknown };\n try {\n body = (await c.req.json().catch(() => ({}))) as typeof body;\n } catch {\n body = {};\n }\n const requestedDestRaw = typeof body.destination === 'string' ? body.destination.trim() : '';\n const onConflictRaw = typeof body.onConflict === 'string' ? body.onConflict : 'rename';\n if (onConflictRaw !== 'rename' && onConflictRaw !== 'overwrite' && onConflictRaw !== 'error') {\n return c.json({ ok: false, error: { code: 'INVALID_CONFLICT_MODE', message: 'Invalid onConflict value' } }, 400);\n }\n const onConflict = onConflictRaw as 'rename' | 'overwrite' | 'error';\n if (onConflict === 'overwrite' && !importCfg.allowOverwrite) {\n return c.json({ ok: false, error: { code: 'OVERWRITE_DISABLED', message: 'Overwrite is disabled by config' } }, 403);\n }\n\n const sourceBasename = sanitizeImportBasename(basename(ref.absolutePath)) || 'imported-file';\n let requestedRel: string;\n if (!requestedDestRaw) {\n requestedRel = `${importCfg.targetDir}/${sourceBasename}`;\n } else {\n const trimmedDest = requestedDestRaw.replace(/\\\\/g, '/');\n // Path ending with `/` is treated as a directory; append source basename.\n requestedRel = trimmedDest.endsWith('/') ? `${trimmedDest}${sourceBasename}` : trimmedDest;\n }\n\n let initialDestAbs = resolveWorkspaceSafePath(workspaceRoot, requestedRel);\n if (!initialDestAbs) {\n return c.json({ ok: false, error: { code: 'INVALID_DESTINATION', message: 'Invalid destination path' } }, 400);\n }\n\n // Sandbox: blocks `.xopc/xopc.json`, `.env*`, etc.; canonical symlink resolution included.\n const writePolicy = validateWritePath(initialDestAbs, workspaceRoot);\n if (!writePolicy.allowed) {\n return c.json({ ok: false, error: { code: 'DESTINATION_BLOCKED', message: writePolicy.reason ?? 'Destination blocked' } }, 403);\n }\n\n if (resolve(ref.absolutePath) === resolve(initialDestAbs)) {\n return c.json({ ok: false, error: { code: 'SAME_LOCATION', message: 'Destination is the same as source' } }, 400);\n }\n\n const destDir = dirname(initialDestAbs);\n try {\n await mkdir(destDir, { recursive: true });\n } catch (err) {\n log.warn({ err, destDir }, 'Failed to create import destination directory');\n return c.json({ ok: false, error: { code: 'IMPORT_FAILED', message: 'Failed to prepare destination' } }, 500);\n }\n\n // Stage source into a hidden tmp file inside the destination directory so we\n // can atomically `link` (rename strategy) or `rename` (overwrite) to land it.\n const tmpName = `.${basename(initialDestAbs)}.import-${randomUUID()}.tmp`;\n const tmpAbs = join(destDir, tmpName);\n\n const started = Date.now();\n let renamed = false;\n let overwrote = false;\n let finalDestAbs = initialDestAbs;\n\n try {\n try {\n await copyFile(ref.absolutePath, tmpAbs, fsConstants.COPYFILE_FICLONE);\n } catch (err) {\n await unlink(tmpAbs).catch(() => {});\n log.warn({ err, source: ref.absolutePath, tmpAbs }, 'Failed to copy source into staging tmp');\n return c.json({ ok: false, error: { code: 'IMPORT_FAILED', message: 'Failed to copy source file' } }, 500);\n }\n\n if (onConflict === 'overwrite') {\n // Snapshot pre-rename existence for telemetry; the actual overwrite is unconditional.\n overwrote = await stat(initialDestAbs).then(() => true).catch(() => false);\n try {\n await rename(tmpAbs, initialDestAbs);\n } catch (err) {\n await unlink(tmpAbs).catch(() => {});\n log.warn({ err, target: initialDestAbs }, 'Atomic rename failed');\n return c.json({ ok: false, error: { code: 'IMPORT_FAILED', message: 'Failed to finalize import' } }, 500);\n }\n } else if (onConflict === 'error') {\n const exists = await stat(initialDestAbs).then(() => true).catch(() => false);\n if (exists) {\n await unlink(tmpAbs).catch(() => {});\n return c.json({ ok: false, error: { code: 'DESTINATION_EXISTS', message: 'Destination already exists' } }, 409);\n }\n try {\n await link(tmpAbs, initialDestAbs);\n await unlink(tmpAbs).catch(() => {});\n } catch (err) {\n await unlink(tmpAbs).catch(() => {});\n log.warn({ err, target: initialDestAbs }, 'Hard link to destination failed');\n return c.json({ ok: false, error: { code: 'IMPORT_FAILED', message: 'Failed to finalize import' } }, 500);\n }\n } else {\n // rename: race-safe loop using O_EXCL semantics of `link`.\n const picked = await pickAvailableTargetWithLink(tmpAbs, initialDestAbs);\n if (!picked.ok) {\n await unlink(tmpAbs).catch(() => {});\n log.warn({ target: initialDestAbs, attempts: picked.attempts }, 'Failed to find free import target name');\n return c.json({ ok: false, error: { code: 'IMPORT_FAILED', message: 'No free destination name available' } }, 500);\n }\n await unlink(tmpAbs).catch(() => {});\n finalDestAbs = picked.path;\n renamed = picked.path !== initialDestAbs;\n }\n } catch (err) {\n await unlink(tmpAbs).catch(() => {});\n log.error({ err }, 'Import file unexpected failure');\n return c.json({ ok: false, error: { code: 'IMPORT_FAILED', message: 'Import failed' } }, 500);\n }\n\n let finalMtime: number;\n try {\n finalMtime = (await stat(finalDestAbs)).mtimeMs;\n } catch {\n finalMtime = Date.now();\n }\n\n const workspaceRel = toWorkspaceRelativePosix(workspaceRoot, finalDestAbs);\n\n fileReferenceRegistry.expireById(id);\n const newRef = fileReferenceRegistry.register({\n absolutePath: finalDestAbs,\n sessionKey: sessionKey || undefined,\n scope: 'workspace',\n capabilities: fileReferenceCapabilities('workspace', false),\n });\n\n const sourceScope = ref.scope;\n const sourceLocationKind = ref.locationKind;\n\n service.emit('workspace.file-imported', {\n sessionKey: sessionKey || undefined,\n workspaceRelativePath: workspaceRel,\n absolutePath: finalDestAbs,\n bytes: sourceStat.size,\n sourceScope,\n sourceLocationKind,\n });\n\n log.info(\n {\n sessionKey,\n fileRefId: id,\n sourceAbsolutePath: ref.absolutePath,\n sourceScope,\n sourceLocationKind,\n destWorkspaceRelativePath: workspaceRel,\n bytes: sourceStat.size,\n renamed,\n overwrote,\n durationMs: Date.now() - started,\n },\n 'Workspace file import succeeded',\n );\n\n return c.json({\n ok: true,\n payload: {\n workspaceRelativePath: workspaceRel,\n absolutePath: finalDestAbs,\n bytesCopied: sourceStat.size,\n sourceAbsolutePath: ref.absolutePath,\n sourceScope,\n sourceLocationKind,\n renamed,\n overwrote,\n mtimeMs: finalMtime,\n newFileRefId: newRef.id,\n },\n });\n });\n\n /**\n * Serve a workspace file as raw bytes (e.g. <img> after auth fetch + blob URL).\n * Path is workspace-relative; scope via sessionKey / agentId like other editor routes.\n */\n authenticated.get('/api/workspace/editor/raw', async (c) => {\n const pathRel = typeof c.req.query('path') === 'string' ? c.req.query('path')! : '';\n if (!pathRel.trim()) {\n return c.json({ ok: false, error: { message: 'Missing path' } }, 400);\n }\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n const abs = resolveWorkspaceSafePath(workspaceRoot, pathRel);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Invalid path' } }, 400);\n }\n let st: Awaited<ReturnType<typeof stat>>;\n try {\n st = await stat(abs);\n } catch {\n return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n }\n if (!st.isFile()) {\n return c.json({ ok: false, error: { message: 'Not a file' } }, 400);\n }\n const ext = pathRel.split('.').pop()?.toLowerCase() ?? '';\n const mimeByExt: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n bmp: 'image/bmp',\n svg: 'image/svg+xml',\n pdf: 'application/pdf',\n docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n txt: 'text/plain',\n md: 'text/markdown',\n json: 'application/json',\n html: 'text/html',\n css: 'text/css',\n js: 'text/javascript',\n ts: 'text/typescript',\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n ogg: 'audio/ogg',\n webm: 'video/webm',\n mp4: 'video/mp4',\n mov: 'video/quicktime',\n };\n const contentType = mimeByExt[ext] || 'application/octet-stream';\n try {\n const buf = await readFile(abs);\n return new Response(buf, {\n headers: {\n 'Content-Type': contentType,\n 'Cache-Control': 'private, max-age=3600',\n },\n });\n } catch {\n return c.json({ ok: false, error: { message: 'Read failed' } }, 500);\n }\n });\n\n authenticated.put('/api/workspace/editor/write', async (c) => {\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ ok: false, error: { message: 'Invalid JSON' } }, 400);\n }\n const pathRel =\n typeof body === 'object' &&\n body !== null &&\n 'path' in body &&\n typeof (body as { path: unknown }).path === 'string'\n ? (body as { path: string }).path\n : '';\n const content =\n typeof body === 'object' &&\n body !== null &&\n 'content' in body &&\n typeof (body as { content: unknown }).content === 'string'\n ? (body as { content: string }).content\n : '';\n if (!pathRel.trim()) {\n return c.json({ ok: false, error: { message: 'Missing path' } }, 400);\n }\n const abs = resolveWorkspaceSafePath(workspaceRoot, pathRel);\n if (!abs) {\n return c.json({ ok: false, error: { message: 'Invalid path' } }, 400);\n }\n let st: Awaited<ReturnType<typeof stat>> | undefined;\n try {\n st = await stat(abs);\n } catch {\n st = undefined;\n }\n if (st && !st.isFile()) {\n return c.json({ ok: false, error: { message: 'Not a file' } }, 400);\n }\n try {\n await writeFile(abs, content, 'utf-8');\n let mtimeMs: number;\n try {\n mtimeMs = (await stat(abs)).mtimeMs;\n } catch {\n mtimeMs = Date.now();\n }\n return c.json({\n ok: true,\n payload: { path: toWorkspaceRelativePosix(workspaceRoot, abs), mtimeMs },\n });\n } catch (err) {\n log.error({ err, path: abs }, 'workspace editor write failed');\n return c.json({ ok: false, error: { message: 'Write failed' } }, 500);\n }\n });\n\n authenticated.get('/api/workspace/editor/search', async (c) => {\n const q = typeof c.req.query('q') === 'string' ? c.req.query('q')!.trim() : '';\n const dirRel = typeof c.req.query('dir') === 'string' ? c.req.query('dir')! : '';\n if (!q) {\n return c.json({\n ok: true,\n payload: { results: [] as { filePath: string; lineNumber: number; lineContent: string; matchStart: number; matchEnd: number }[] },\n });\n }\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n const workspaceRoot = ws.root;\n const absDir = resolveWorkspaceSafePath(workspaceRoot, dirRel);\n if (!absDir) {\n return c.json({ ok: false, error: { message: 'Invalid path' } }, 400);\n }\n let st: Awaited<ReturnType<typeof stat>>;\n try {\n st = await stat(absDir);\n } catch {\n return c.json({ ok: false, error: { message: 'Not found' } }, 404);\n }\n if (!st.isDirectory()) {\n return c.json({ ok: false, error: { message: 'Not a directory' } }, 400);\n }\n const raw = await runRipgrepInDirectory(q, absDir);\n const results = raw\n .filter((r) => isPathUnderWorkspace(workspaceRoot, r.filePath))\n .map((r) => ({\n ...r,\n filePath: toWorkspaceRelativePosix(workspaceRoot, resolve(r.filePath)),\n }));\n return c.json({ ok: true, payload: { results } });\n });\n\n /** Fuzzy filename / path search over the session workspace (ripgrep `--files` + subsequence scoring). */\n authenticated.get('/api/workspace/editor/files/search', async (c) => {\n const q = typeof c.req.query('q') === 'string' ? c.req.query('q')!.trim() : '';\n const limitRaw = c.req.query('limit');\n const limit = Math.min(\n Math.max(parseInt(typeof limitRaw === 'string' ? limitRaw : '15', 10) || 15, 1),\n FILE_SEARCH_MAX_LIMIT,\n );\n\n const ws = await resolveEditorWorkspaceRootAsync(\n service,\n service.currentConfig,\n c.req.query('sessionKey'),\n c.req.query('agentId'),\n );\n if (ws.ok === false) {\n return c.json({ ok: false, error: { message: ws.message } }, 400);\n }\n\n const entries = await fuzzySearchWorkspaceFiles(ws.root, q, limit);\n return c.json({ ok: true, payload: { entries } });\n });\n}\n"],"mappings":"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"}
@@ -7,7 +7,7 @@ import { stringifySSEData } from "./sse-json.js";
7
7
  import { streamSSE } from "hono/streaming";
8
8
  //#region src/gateway/hono/sse.ts
9
9
  init_logger();
10
- const log = createLogger("Hono:SSE");
10
+ const log = createLogger("Gateway:SSE");
11
11
  const activeConnections = /* @__PURE__ */ new Map();
12
12
  function isValidAgentRequest(body) {
13
13
  if (!body || typeof body !== "object") return false;
@@ -114,7 +114,14 @@ function createAgentSSEHandler(config) {
114
114
  }
115
115
  });
116
116
  } catch (error) {
117
- log.error({ err: error }, "Agent run failed (JSON mode)");
117
+ const em = error instanceof Error ? error.message : String(error);
118
+ log.error({
119
+ err: error,
120
+ errorMessage: em,
121
+ phase: "gateway.agent_run",
122
+ sessionKey: jsonSessionKey,
123
+ channel
124
+ }, `Agent run failed (JSON mode): ${em}`);
118
125
  return c.json({
119
126
  ok: false,
120
127
  error: {
@@ -1 +1 @@
1
- {"version":3,"file":"sse.js","names":[],"sources":["../../../../src/gateway/hono/sse.ts"],"sourcesContent":["import { streamSSE } from 'hono/streaming';\nimport type { Context } from 'hono';\nimport type { GatewayService } from '../service.js';\nimport { MAX_WEBCHAT_ATTACHMENT_FILE_BYTES } from '../chat-limits.js';\nimport { createLogger, updateAsyncLogContext } from '../../utils/logger.js';\nimport { stringifySSEData } from './sse-json.js';\nimport { resolveWebchatSessionKey } from '../resolve-webchat-session-key.js';\n\nconst log = createLogger('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
+ {"version":3,"file":"sse.js","names":[],"sources":["../../../../src/gateway/hono/sse.ts"],"sourcesContent":["import { streamSSE } from 'hono/streaming';\nimport type { Context } from 'hono';\nimport type { GatewayService } from '../service.js';\nimport { MAX_WEBCHAT_ATTACHMENT_FILE_BYTES } from '../chat-limits.js';\nimport { createLogger, updateAsyncLogContext } from '../../utils/logger.js';\nimport { stringifySSEData } from './sse-json.js';\nimport { resolveWebchatSessionKey } from '../resolve-webchat-session-key.js';\n\nconst log = createLogger('Gateway:SSE');\n\n// Active SSE connections tracking for connection limiting\nconst activeConnections = new Map<string, AbortController>();\n\nexport interface SSEHandlerConfig {\n service: GatewayService;\n maxSseConnections?: number;\n}\n\n// Type validation for agent request body\ninterface AgentRequestBody {\n message: string;\n channel?: string;\n chatId?: string;\n /** Alias for `chatId` (gateway console + extension clients). */\n sessionKey?: string;\n /** Epoch ms when the client started this send (abort cutoff / stale POST drop). */\n clientCreatedAtMs?: number;\n /** When true and `channel` is `webchat`, start a new peer id (new session). */\n newSession?: boolean;\n thinking?: string;\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n 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 const em = error instanceof Error ? error.message : String(error);\n log.error(\n { err: error, errorMessage: em, phase: 'gateway.agent_run', sessionKey: jsonSessionKey, channel },\n `Agent run failed (JSON mode): ${em}`,\n );\n return c.json({\n ok: false,\n error: { code: 'INTERNAL_ERROR', message: error instanceof Error ? error.message : 'Unknown error' },\n }, 500);\n }\n }\n\n // --- SSE streaming ---\n c.header('X-Accel-Buffering', 'no');\n return streamSSE(c, async (stream) => {\n if (channel !== 'webchat') {\n stream.onAbort(() => {\n clientAbort.abort();\n });\n }\n\n const generator = service.runAgent(message, channel, chatId, attachments, thinking, {\n signal: clientAbort.signal,\n ...(clientCreatedAtMs !== undefined ? { clientCreatedAtMs } : {}),\n });\n\n let eventId = 0;\n\n try {\n while (true) {\n const { done, value } = await generator.next();\n\n if (done) {\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";
4
1
  import path from "node:path";
5
2
  import net from "node:net";
3
+ import fs from "node:fs";
4
+ import { createHash } from "node:crypto";
5
+ import fs$1 from "node:fs/promises";
6
6
  import { homedir } from "os";
7
7
  //#region src/gateway/lock.ts
8
8
  /**
@@ -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";
4
3
  import net from "node:net";
4
+ import fs from "node:fs";
5
5
  import { execFileSync } from "node:child_process";
6
6
  //#region src/gateway/ports.ts
7
7
  /**
@@ -1,7 +1,7 @@
1
- import { createLogger } from "../../utils/logger/index.js";
2
- import { init_logger } from "../../utils/logger.js";
3
1
  import { buildSessionKey, init_session_key, parseSessionKey } from "../../routing/session-key.js";
4
2
  import { getDefaultAgentId, init_resolve_route } from "../../routing/resolve-route.js";
3
+ import { createLogger } from "../../utils/logger/index.js";
4
+ import { init_logger } from "../../utils/logger.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";
@@ -9,7 +9,7 @@ import { runGatewayAgent } from "./run-gateway-agent.js";
9
9
  init_session_key();
10
10
  init_resolve_route();
11
11
  init_logger();
12
- const log = createLogger("GatewayAgentRunner");
12
+ const log = createLogger("Gateway:AgentRunner");
13
13
  var GatewayAgentRunner = class {
14
14
  opts;
15
15
  runRelay = new AgentRunRelay();
@@ -1 +1 @@
1
- {"version":3,"file":"agent-runner.js","names":[],"sources":["../../../../src/gateway/service/agent-runner.ts"],"sourcesContent":["/**\n * GatewayAgentRunner — webchat agent invocation and the surrounding control\n * surface (abort, steer, clarify-bridge plumbing, scheduled continuations).\n *\n * Was 200 lines of `GatewayService` covering seven concerns that all hung off\n * the same handful of fields (`activeWebchatRunBySession`, `runAbortControllers`,\n * `clarifyBridge`, `runRelay`):\n *\n * - `runAgent(message, channel, chatId, ...)` — wraps {@link runGatewayAgent}\n * - `abortAgentRun(runId)` — POST /api/agent/abort + cleanup\n * - `steerWebchatAgent(chatId, message)` — Agent.steer queue at tool boundary\n * - `submitClarifyResponse(requestId, answer)` — UI answers a `clarify` call\n * - `enqueueWebchatPersistentGoalKickoff(sessionKey, goalText)` — initial\n * `/goal` kickoff posts the goal text as the next user turn\n * - `drainScheduledWebchatContinuation(sk, msg)` — background continuation\n * (extension scheduler + persistent-goal flow)\n * - `clarifyForSession({ sessionKey, request })` — clarify-bridge dispatch\n * used by `gatewayClarify.requestClarification` in AgentService\n *\n * Owns the two state maps (`activeWebchatRunBySession`, `runAbortControllers`)\n * directly so peer coordinators (sessions-api, marketplace, config) cannot\n * accidentally mutate them.\n */\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport type { AgentService } from '../../agent/service.js';\nimport type { ChannelManager } from '../../channels/manager.js';\nimport type { SessionIndex } from '../../session/index.js';\nimport { AgentRunRelay, type RelayEvent } from '../agent-run-relay.js';\nimport { ClarifyBridge, type ClarifyBridgeRequest } from '../clarify-bridge.js';\nimport { buildSessionKey, parseSessionKey } from '../../routing/session-key.js';\nimport { getDefaultAgentId } from '../../routing/resolve-route.js';\nimport { runGatewayAgent } from './run-gateway-agent.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('GatewayAgentRunner');\n\nexport interface GatewayAgentRunnerOptions {\n bus: MessageBus;\n sessionIndex: SessionIndex;\n /** Resolved lazily — the runner is constructed before AgentService exists. */\n getAgentService: () => AgentService;\n getChannelManager: () => ChannelManager;\n getConfig: () => Config;\n /** SSE emit (re-used so `runAgent` events broadcast to subscribers). */\n emit: (type: string, payload: unknown) => void;\n}\n\nexport class GatewayAgentRunner {\n private readonly opts: GatewayAgentRunnerOptions;\n readonly runRelay = new AgentRunRelay();\n /** Per-run abort for webchat (POST /api/agent/abort or client disconnect). */\n private readonly runAbortControllers = new Map<string, AbortController>();\n private readonly clarifyBridge = new ClarifyBridge();\n /** Maps webchat session key → active `runId` for `clarify` tool routing. */\n private readonly activeWebchatRunBySession = new Map<string, string>();\n\n constructor(opts: GatewayAgentRunnerOptions) {\n this.opts = opts;\n }\n\n // ── Read-only accessors (so peers don't get a Map ref) ────────────────\n\n /** True when a webchat agent run is currently in-flight for `sessionKey`. */\n hasActiveRun(sessionKey: string): boolean {\n return this.activeWebchatRunBySession.has(sessionKey);\n }\n\n getActiveRunId(sessionKey: string): string | undefined {\n return this.activeWebchatRunBySession.get(sessionKey);\n }\n\n getClarifyBridge(): ClarifyBridge {\n return this.clarifyBridge;\n }\n\n /** Called from `GatewayService.stop()` so the bridge gets cleaned up. */\n disposeClarifyBridge(): void {\n this.clarifyBridge.dispose();\n }\n\n // ── runAgent (webchat HTTP POST) ──────────────────────────────────────\n\n async *runAgent(\n message: string,\n channel: string,\n chatId: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n }>,\n thinking?: string,\n runOptions?: { signal?: AbortSignal; clientCreatedAtMs?: number },\n ): AsyncGenerator<\n { type: string; content?: string; status?: string; runId?: string },\n { status: string; summary: string },\n unknown\n > {\n const iter = runGatewayAgent(\n {\n config: this.opts.getConfig(),\n agentService: this.opts.getAgentService(),\n bus: this.opts.bus,\n runRelay: this.runRelay,\n runAbortControllers: this.runAbortControllers,\n activeWebchatRunBySession: this.activeWebchatRunBySession,\n sessionIndex: this.opts.sessionIndex,\n emit: this.opts.emit,\n },\n message,\n channel,\n chatId,\n attachments,\n thinking,\n runOptions,\n );\n\n let step = await iter.next();\n while (!step.done) {\n yield step.value as { type: string; content?: string; status?: string; runId?: string };\n step = await iter.next();\n }\n return step.value;\n }\n\n /** Abort an in-flight webchat agent run (matches `runId` from SSE `status`). */\n abortAgentRun(runId: string): boolean {\n this.clarifyBridge.cancelForRun(runId);\n const keysToMark: string[] = [];\n for (const [sk, id] of this.activeWebchatRunBySession) {\n if (id === runId) {\n keysToMark.push(sk);\n }\n }\n for (const sk of keysToMark) {\n this.activeWebchatRunBySession.delete(sk);\n }\n const relaySk = this.runRelay.getSessionKey(runId);\n if (relaySk && !keysToMark.includes(relaySk)) {\n keysToMark.push(relaySk);\n }\n const c = this.runAbortControllers.get(runId);\n if (!c) {\n return false;\n }\n const cutoffTs = Date.now();\n for (const sk of keysToMark) {\n void this.opts.sessionIndex\n .updateSessionMetadata(sk, { abortCutoffTimestamp: cutoffTs })\n .catch(() => {});\n void this.opts.sessionIndex\n .appendTranscriptContextEntry(sk, {\n text: 'Webchat agent run aborted',\n data: { runId, abortCutoffTimestamp: cutoffTs },\n })\n .catch(() => {});\n }\n c.abort();\n for (const sk of keysToMark) {\n void import('../../agent/embedded/runs.js').then(({ abortEmbeddedRun }) =>\n abortEmbeddedRun(sk),\n );\n }\n return true;\n }\n\n /**\n * Queue steering text for an active webchat run (`Agent.steer` /\n * tool-boundary injection). `chatId` is the same as `POST /api/agent` body\n * (`sessionKey` or legacy peer id).\n */\n async steerWebchatAgent(\n chatId: string,\n message: string,\n ): Promise<\n { ok: true } | { ok: false; code: 'BAD_REQUEST' | 'NO_ACTIVE_RUN' | 'STEER_FAILED' }\n > {\n const trimmed = message.trim();\n if (!trimmed) {\n return { ok: false, code: 'BAD_REQUEST' };\n }\n const cfg = this.opts.getConfig();\n const parsedKey = parseSessionKey(chatId);\n const sessionKey = parsedKey\n ? chatId\n : buildSessionKey({\n agentId: getDefaultAgentId(cfg),\n source: 'webchat',\n accountId: 'default',\n peerKind: 'direct',\n peerId: chatId,\n });\n if (!this.activeWebchatRunBySession.has(sessionKey)) {\n return { ok: false, code: 'NO_ACTIVE_RUN' };\n }\n const steered = await this.opts\n .getAgentService()\n .turnDispatcher.steerWebchatSession(sessionKey, trimmed);\n if (!steered) {\n return { ok: false, code: 'STEER_FAILED' };\n }\n return { ok: true };\n }\n\n /** Deliver a user's answer to a pending `clarify` tool call. */\n submitClarifyResponse(requestId: string, answer: string): boolean {\n return this.clarifyBridge.handleResponse(requestId, answer);\n }\n\n /** Hermes-style: after HTTP sets a goal, enqueue the goal text as the next user turn. */\n enqueueWebchatPersistentGoalKickoff(sessionKey: string, goalText: string): void {\n queueMicrotask(() => {\n void this.drainScheduledWebchatContinuation(sessionKey, goalText);\n });\n }\n\n /** Background drain for extension-initiated webchat turns (`scheduleWebchatContinuation`). */\n async drainScheduledWebchatContinuation(sessionKey: string, message: string): Promise<void> {\n try {\n const gen = this.runAgent(message, 'webchat', sessionKey, undefined, undefined, {\n clientCreatedAtMs: Date.now(),\n });\n for await (const _ of gen) {\n // Relay + `agent.stream` broadcast; UI attaches via pending runId + resume.\n }\n } catch (err) {\n log.warn({ err, sessionKey }, 'Scheduled webchat continuation failed');\n }\n }\n\n // ── Clarify dispatch (called from AgentService.gatewayClarify) ────────\n\n /**\n * Resolve clarify-bridge config for `sessionKey`: who delivers the question\n * (webchat SSE, Telegram message, or both), then start the bridge request.\n * Rejects when neither path is available (e.g. CLI without webchat or TG).\n *\n * `publishSseFor(runId)` is the bridge into AgentService's\n * `turnDispatcher.enqueueWebchatSseEvent`. We take it as a callback so the\n * runner does not import AgentService statically.\n */\n requestClarification(opts: {\n sessionKey: string;\n request: ClarifyBridgeRequest;\n publishSseFor: (runId: string) => (e: RelayEvent) => void;\n }): Promise<string> {\n const { sessionKey, request, publishSseFor } = opts;\n const runId = this.activeWebchatRunBySession.get(sessionKey);\n const publishSse = runId ? publishSseFor(runId) : undefined;\n const parsed = parseSessionKey(sessionKey);\n const deliver =\n parsed?.source === 'telegram'\n ? async (ctx: {\n sessionKey: string;\n requestId: string;\n request: ClarifyBridgeRequest;\n }) => {\n await this.deliverTelegramClarify(ctx);\n }\n : undefined;\n if (!runId && !deliver) {\n return Promise.reject(\n new Error('Clarify is not available for this session (use webchat, Telegram, or CLI)'),\n );\n }\n return this.clarifyBridge.startRequest({\n sessionKey,\n runId,\n relay: this.runRelay,\n publishSse,\n request,\n deliver,\n });\n }\n\n private async deliverTelegramClarify(ctx: {\n sessionKey: string;\n requestId: string;\n request: ClarifyBridgeRequest;\n }): Promise<void> {\n const parsed = parseSessionKey(ctx.sessionKey);\n if (!parsed || parsed.source !== 'telegram') {\n return;\n }\n\n let body = ctx.request.question;\n if (ctx.request.default) {\n body += `\\n\\nDefault if unsure: ${ctx.request.default}`;\n }\n\n const choices = ctx.request.choices;\n const buttonRows =\n choices && choices.length >= 2\n ? choices.map((c, i) => [\n {\n text: c.length > 64 ? `${c.slice(0, 61)}…` : c,\n callback_data: `clarify:${ctx.requestId}:${i}`,\n },\n ])\n : undefined;\n\n if (!buttonRows) {\n body += '\\n\\nReply with your answer in this chat.';\n }\n\n await this.opts.getChannelManager().send({\n channel: 'telegram',\n chat_id: parsed.peerId,\n content: body,\n metadata: {\n accountId: parsed.accountId,\n ...(parsed.threadId ? { threadId: parsed.threadId } : {}),\n },\n buttons: buttonRows,\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"agent-runner.js","names":[],"sources":["../../../../src/gateway/service/agent-runner.ts"],"sourcesContent":["/**\n * GatewayAgentRunner — webchat agent invocation and the surrounding control\n * surface (abort, steer, clarify-bridge plumbing, scheduled continuations).\n *\n * Was 200 lines of `GatewayService` covering seven concerns that all hung off\n * the same handful of fields (`activeWebchatRunBySession`, `runAbortControllers`,\n * `clarifyBridge`, `runRelay`):\n *\n * - `runAgent(message, channel, chatId, ...)` — wraps {@link runGatewayAgent}\n * - `abortAgentRun(runId)` — POST /api/agent/abort + cleanup\n * - `steerWebchatAgent(chatId, message)` — Agent.steer queue at tool boundary\n * - `submitClarifyResponse(requestId, answer)` — UI answers a `clarify` call\n * - `enqueueWebchatPersistentGoalKickoff(sessionKey, goalText)` — initial\n * `/goal` kickoff posts the goal text as the next user turn\n * - `drainScheduledWebchatContinuation(sk, msg)` — background continuation\n * (extension scheduler + persistent-goal flow)\n * - `clarifyForSession({ sessionKey, request })` — clarify-bridge dispatch\n * used by `gatewayClarify.requestClarification` in AgentService\n *\n * Owns the two state maps (`activeWebchatRunBySession`, `runAbortControllers`)\n * directly so peer coordinators (sessions-api, marketplace, config) cannot\n * accidentally mutate them.\n */\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport type { AgentService } from '../../agent/service.js';\nimport type { ChannelManager } from '../../channels/manager.js';\nimport type { SessionIndex } from '../../session/index.js';\nimport { AgentRunRelay, type RelayEvent } from '../agent-run-relay.js';\nimport { ClarifyBridge, type ClarifyBridgeRequest } from '../clarify-bridge.js';\nimport { buildSessionKey, parseSessionKey } from '../../routing/session-key.js';\nimport { getDefaultAgentId } from '../../routing/resolve-route.js';\nimport { runGatewayAgent } from './run-gateway-agent.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('Gateway:AgentRunner');\n\nexport interface GatewayAgentRunnerOptions {\n bus: MessageBus;\n sessionIndex: SessionIndex;\n /** Resolved lazily — the runner is constructed before AgentService exists. */\n getAgentService: () => AgentService;\n getChannelManager: () => ChannelManager;\n getConfig: () => Config;\n /** SSE emit (re-used so `runAgent` events broadcast to subscribers). */\n emit: (type: string, payload: unknown) => void;\n}\n\nexport class GatewayAgentRunner {\n private readonly opts: GatewayAgentRunnerOptions;\n readonly runRelay = new AgentRunRelay();\n /** Per-run abort for webchat (POST /api/agent/abort or client disconnect). */\n private readonly runAbortControllers = new Map<string, AbortController>();\n private readonly clarifyBridge = new ClarifyBridge();\n /** Maps webchat session key → active `runId` for `clarify` tool routing. */\n private readonly activeWebchatRunBySession = new Map<string, string>();\n\n constructor(opts: GatewayAgentRunnerOptions) {\n this.opts = opts;\n }\n\n // ── Read-only accessors (so peers don't get a Map ref) ────────────────\n\n /** True when a webchat agent run is currently in-flight for `sessionKey`. */\n hasActiveRun(sessionKey: string): boolean {\n return this.activeWebchatRunBySession.has(sessionKey);\n }\n\n getActiveRunId(sessionKey: string): string | undefined {\n return this.activeWebchatRunBySession.get(sessionKey);\n }\n\n getClarifyBridge(): ClarifyBridge {\n return this.clarifyBridge;\n }\n\n /** Called from `GatewayService.stop()` so the bridge gets cleaned up. */\n disposeClarifyBridge(): void {\n this.clarifyBridge.dispose();\n }\n\n // ── runAgent (webchat HTTP POST) ──────────────────────────────────────\n\n async *runAgent(\n message: string,\n channel: string,\n chatId: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n }>,\n thinking?: string,\n runOptions?: { signal?: AbortSignal; clientCreatedAtMs?: number },\n ): AsyncGenerator<\n { type: string; content?: string; status?: string; runId?: string },\n { status: string; summary: string },\n unknown\n > {\n const iter = runGatewayAgent(\n {\n config: this.opts.getConfig(),\n agentService: this.opts.getAgentService(),\n bus: this.opts.bus,\n runRelay: this.runRelay,\n runAbortControllers: this.runAbortControllers,\n activeWebchatRunBySession: this.activeWebchatRunBySession,\n sessionIndex: this.opts.sessionIndex,\n emit: this.opts.emit,\n },\n message,\n channel,\n chatId,\n attachments,\n thinking,\n runOptions,\n );\n\n let step = await iter.next();\n while (!step.done) {\n yield step.value as { type: string; content?: string; status?: string; runId?: string };\n step = await iter.next();\n }\n return step.value;\n }\n\n /** Abort an in-flight webchat agent run (matches `runId` from SSE `status`). */\n abortAgentRun(runId: string): boolean {\n this.clarifyBridge.cancelForRun(runId);\n const keysToMark: string[] = [];\n for (const [sk, id] of this.activeWebchatRunBySession) {\n if (id === runId) {\n keysToMark.push(sk);\n }\n }\n for (const sk of keysToMark) {\n this.activeWebchatRunBySession.delete(sk);\n }\n const relaySk = this.runRelay.getSessionKey(runId);\n if (relaySk && !keysToMark.includes(relaySk)) {\n keysToMark.push(relaySk);\n }\n const c = this.runAbortControllers.get(runId);\n if (!c) {\n return false;\n }\n const cutoffTs = Date.now();\n for (const sk of keysToMark) {\n void this.opts.sessionIndex\n .updateSessionMetadata(sk, { abortCutoffTimestamp: cutoffTs })\n .catch(() => {});\n void this.opts.sessionIndex\n .appendTranscriptContextEntry(sk, {\n text: 'Webchat agent run aborted',\n data: { runId, abortCutoffTimestamp: cutoffTs },\n })\n .catch(() => {});\n }\n c.abort();\n for (const sk of keysToMark) {\n void import('../../agent/embedded/runs.js').then(({ abortEmbeddedRun }) =>\n abortEmbeddedRun(sk),\n );\n }\n return true;\n }\n\n /**\n * Queue steering text for an active webchat run (`Agent.steer` /\n * tool-boundary injection). `chatId` is the same as `POST /api/agent` body\n * (`sessionKey` or legacy peer id).\n */\n async steerWebchatAgent(\n chatId: string,\n message: string,\n ): Promise<\n { ok: true } | { ok: false; code: 'BAD_REQUEST' | 'NO_ACTIVE_RUN' | 'STEER_FAILED' }\n > {\n const trimmed = message.trim();\n if (!trimmed) {\n return { ok: false, code: 'BAD_REQUEST' };\n }\n const cfg = this.opts.getConfig();\n const parsedKey = parseSessionKey(chatId);\n const sessionKey = parsedKey\n ? chatId\n : buildSessionKey({\n agentId: getDefaultAgentId(cfg),\n source: 'webchat',\n accountId: 'default',\n peerKind: 'direct',\n peerId: chatId,\n });\n if (!this.activeWebchatRunBySession.has(sessionKey)) {\n return { ok: false, code: 'NO_ACTIVE_RUN' };\n }\n const steered = await this.opts\n .getAgentService()\n .turnDispatcher.steerWebchatSession(sessionKey, trimmed);\n if (!steered) {\n return { ok: false, code: 'STEER_FAILED' };\n }\n return { ok: true };\n }\n\n /** Deliver a user's answer to a pending `clarify` tool call. */\n submitClarifyResponse(requestId: string, answer: string): boolean {\n return this.clarifyBridge.handleResponse(requestId, answer);\n }\n\n /** Hermes-style: after HTTP sets a goal, enqueue the goal text as the next user turn. */\n enqueueWebchatPersistentGoalKickoff(sessionKey: string, goalText: string): void {\n queueMicrotask(() => {\n void this.drainScheduledWebchatContinuation(sessionKey, goalText);\n });\n }\n\n /** Background drain for extension-initiated webchat turns (`scheduleWebchatContinuation`). */\n async drainScheduledWebchatContinuation(sessionKey: string, message: string): Promise<void> {\n try {\n const gen = this.runAgent(message, 'webchat', sessionKey, undefined, undefined, {\n clientCreatedAtMs: Date.now(),\n });\n for await (const _ of gen) {\n // Relay + `agent.stream` broadcast; UI attaches via pending runId + resume.\n }\n } catch (err) {\n log.warn({ err, sessionKey }, 'Scheduled webchat continuation failed');\n }\n }\n\n // ── Clarify dispatch (called from AgentService.gatewayClarify) ────────\n\n /**\n * Resolve clarify-bridge config for `sessionKey`: who delivers the question\n * (webchat SSE, Telegram message, or both), then start the bridge request.\n * Rejects when neither path is available (e.g. CLI without webchat or TG).\n *\n * `publishSseFor(runId)` is the bridge into AgentService's\n * `turnDispatcher.enqueueWebchatSseEvent`. We take it as a callback so the\n * runner does not import AgentService statically.\n */\n requestClarification(opts: {\n sessionKey: string;\n request: ClarifyBridgeRequest;\n publishSseFor: (runId: string) => (e: RelayEvent) => void;\n }): Promise<string> {\n const { sessionKey, request, publishSseFor } = opts;\n const runId = this.activeWebchatRunBySession.get(sessionKey);\n const publishSse = runId ? publishSseFor(runId) : undefined;\n const parsed = parseSessionKey(sessionKey);\n const deliver =\n parsed?.source === 'telegram'\n ? async (ctx: {\n sessionKey: string;\n requestId: string;\n request: ClarifyBridgeRequest;\n }) => {\n await this.deliverTelegramClarify(ctx);\n }\n : undefined;\n if (!runId && !deliver) {\n return Promise.reject(\n new Error('Clarify is not available for this session (use webchat, Telegram, or CLI)'),\n );\n }\n return this.clarifyBridge.startRequest({\n sessionKey,\n runId,\n relay: this.runRelay,\n publishSse,\n request,\n deliver,\n });\n }\n\n private async deliverTelegramClarify(ctx: {\n sessionKey: string;\n requestId: string;\n request: ClarifyBridgeRequest;\n }): Promise<void> {\n const parsed = parseSessionKey(ctx.sessionKey);\n if (!parsed || parsed.source !== 'telegram') {\n return;\n }\n\n let body = ctx.request.question;\n if (ctx.request.default) {\n body += `\\n\\nDefault if unsure: ${ctx.request.default}`;\n }\n\n const choices = ctx.request.choices;\n const buttonRows =\n choices && choices.length >= 2\n ? choices.map((c, i) => [\n {\n text: c.length > 64 ? `${c.slice(0, 61)}…` : c,\n callback_data: `clarify:${ctx.requestId}:${i}`,\n },\n ])\n : undefined;\n\n if (!buttonRows) {\n body += '\\n\\nReply with your answer in this chat.';\n }\n\n await this.opts.getChannelManager().send({\n channel: 'telegram',\n chat_id: parsed.peerId,\n content: body,\n metadata: {\n accountId: parsed.accountId,\n ...(parsed.threadId ? { threadId: parsed.threadId } : {}),\n },\n buttons: buttonRows,\n });\n }\n}\n"],"mappings":"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"}