@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
@@ -75,11 +75,15 @@ var GatewayConfigCoordinator = class {
75
75
  this.scheduleChannelPluginsAfterPersist();
76
76
  return { saved: true };
77
77
  } catch (err) {
78
- const error = err instanceof Error ? err.message : String(err);
79
- log.error({ error }, "Failed to save config");
78
+ const em = err instanceof Error ? err.message : String(err);
79
+ log.error({
80
+ err,
81
+ errorMessage: em,
82
+ phase: "infra.config"
83
+ }, `Failed to save config: ${em}`);
80
84
  return {
81
85
  saved: false,
82
- error
86
+ error: em
83
87
  };
84
88
  }
85
89
  }
@@ -97,11 +101,15 @@ var GatewayConfigCoordinator = class {
97
101
  log.debug("Configuration updated successfully");
98
102
  return { updated: true };
99
103
  } catch (err) {
100
- const error = err instanceof Error ? err.message : String(err);
101
- log.error({ error }, "Failed to update config");
104
+ const em = err instanceof Error ? err.message : String(err);
105
+ log.error({
106
+ err,
107
+ errorMessage: em,
108
+ phase: "infra.config"
109
+ }, `Failed to update config: ${em}`);
102
110
  return {
103
111
  updated: false,
104
- error
112
+ error: em
105
113
  };
106
114
  }
107
115
  }
@@ -1 +1 @@
1
- {"version":3,"file":"config-coordinator.js","names":["writeConfigToDisk"],"sources":["../../../../src/gateway/service/config-coordinator.ts"],"sourcesContent":["/**\n * GatewayConfigCoordinator — owns config persistence, hot-reload, and the\n * per-section reload handlers.\n *\n * Was 350 lines of `GatewayService` covering nine concerns:\n * - manual `reloadConfig()` (CLI/UI trigger)\n * - `saveConfig()` / `updateConfig()` (PATCH /api/config)\n * - `setBundledExtensionActivationTarget` (extension store install)\n * - `afterWeixinCredentialsPersisted` / `afterFeishuCredentialsPersisted`\n * (QR-login follow-ups that bypass the watcher)\n * - `ConfigHotReloader` (fs.watch → debounced per-section dispatch)\n * - Eight section reload handlers (models / agents / channels / cron /\n * heartbeat / tools / mcp / extensions)\n * - `scheduleChannelPluginsAfterPersist` (coalesces rapid saves so\n * Telegram/Weixin do not stop/start repeatedly)\n *\n * Pulled out so the gateway composition root stays focused on lifecycle, and\n * each handler is reachable from one place when adding a new config section.\n *\n * **Config state ownership.** `GatewayService.config` is still the single\n * source of truth — this coordinator reads it via `getConfig()` and writes it\n * back via `setConfig()` after every reload / persist. We pass through rather\n * than holding our own copy so other coordinators (sessions, marketplace,\n * agent runner) see the latest config the moment a reload commits.\n */\nimport type { Config } from '../../config/schema.js';\nimport type { Config as SurfaceConfig } from '../../config/config-surface.js';\nimport type { AgentService } from '../../agent/service.js';\nimport type { ChannelManager } from '../../channels/manager.js';\nimport type { CronService } from '../../cron/index.js';\nimport type { HeartbeatService } from '../heartbeat/index.js';\nimport type { ExtensionLoader } from '../../extensions/loader.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport { ConfigHotReloader } from '../../config/reload.js';\nimport { loadConfig, saveConfig as writeConfigToDisk } from '../../config/index.js';\nimport { sanitizeTunnelConfig } from '../../tunnel/tunnel-config.js';\nimport { getModelRegistry } from '../../providers/index.js';\nimport { disposeAllSessionMcpRuntimes } from '../../agent/mcp/bundle-mcp-tools.js';\nimport { computeBundledExtensionExtensionsPatch } from '../../extensions/bundled-extension-activation.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('GatewayConfigCoordinator');\n\nexport interface GatewayConfigCoordinatorOptions {\n configPath: string;\n bus: MessageBus;\n /** Hot reload (fs.watch) — disabled in tests / certain CLI modes. */\n enableHotReload: boolean;\n getConfig: () => Config;\n /** Writes the new config back into `GatewayService.config`. */\n setConfig: (next: Config) => void;\n getAgentService: () => AgentService;\n getChannelManager: () => ChannelManager;\n getCronService: () => CronService;\n getHeartbeatService: () => HeartbeatService | null;\n getExtensionLoader: () => ExtensionLoader | null;\n /** Re-evaluate browser-extension server attachment after agent defaults change. */\n reconcileBrowserExtensionServer: () => Promise<void>;\n /** Latest channel status snapshot for the `channels.status` SSE event. */\n getChannelsStatus: () => unknown;\n /** SSE emit (used for `config.reload` + `channels.status`). */\n emit: (type: string, payload: unknown) => void;\n}\n\nexport class GatewayConfigCoordinator {\n private readonly opts: GatewayConfigCoordinatorOptions;\n private configReloader: ConfigHotReloader | null = null;\n private channelReloadFlushPromise: Promise<void> | null = null;\n private channelReloadPending = false;\n\n constructor(opts: GatewayConfigCoordinatorOptions) {\n this.opts = opts;\n }\n\n // ── Lifecycle ──────────────────────────────────────────────────────────\n\n /** Start the fs.watch-based reloader (idempotent — only starts once). */\n startHotReloader(): void {\n if (this.configReloader) return;\n this.configReloader = new ConfigHotReloader(\n this.opts.configPath,\n this.opts.getConfig(),\n {\n onModelsReload: (newConfig) => this.handleModelsReload(newConfig),\n onAgentDefaultsReload: (newConfig) => this.handleAgentDefaultsReload(newConfig),\n onChannelsReload: (newConfig) => this.handleChannelsReload(newConfig),\n onCronReload: (newConfig) => this.handleCronReload(newConfig),\n onHeartbeatReload: (newConfig) => this.handleHeartbeatReload(newConfig),\n onToolsReload: (newConfig) => this.handleToolsReload(newConfig),\n onMcpReload: (newConfig) => this.handleMcpReload(newConfig),\n onExtensionsReload: async (newConfig, changedPaths) => {\n await this.handleExtensionsReload(newConfig, changedPaths);\n },\n onFullRestart: (newConfig) => {\n log.warn(\n { requiresProcessRestart: true, hint: 'Restart the gateway process (hot reload cannot apply this change).' },\n 'Config reload: full gateway restart required — see prior \"restartPaths\" info log',\n );\n this.opts.setConfig(newConfig);\n this.opts.emit('config.reload', { section: 'full', requiresRestart: true });\n },\n },\n {\n debounceMs: 300,\n enabled: this.opts.enableHotReload,\n }\n );\n this.configReloader.start();\n }\n\n async stopHotReloader(): Promise<void> {\n if (this.configReloader) {\n await this.configReloader.stop();\n this.configReloader = null;\n }\n }\n\n // ── Manual reload (UI trigger) ─────────────────────────────────────────\n\n async reloadConfig(): Promise<{ reloaded: boolean; error?: string }> {\n if (!this.configReloader) {\n return { reloaded: false, error: 'Config reloader not initialized' };\n }\n const result = await this.configReloader.triggerReload();\n return { reloaded: result.success, error: result.error };\n }\n\n // ── Persist (PATCH /api/config, marketplace install, etc.) ─────────────\n\n async saveConfig(config: Config): Promise<{ saved: boolean; error?: string }> {\n try {\n await this.writeConfigAndReloadFromDisk(config);\n this.scheduleChannelPluginsAfterPersist();\n return { saved: true };\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n log.error({ error }, 'Failed to save config');\n return { saved: false, error };\n }\n }\n\n /** Merge partial updates into `currentConfig` and persist. */\n async updateConfig(updates: Partial<Config>): Promise<{ updated: boolean; error?: string }> {\n try {\n log.debug('Updating configuration...');\n const merged = { ...this.opts.getConfig(), ...updates };\n this.opts.setConfig(merged);\n await this.writeConfigAndReloadFromDisk(merged);\n this.scheduleChannelPluginsAfterPersist();\n log.debug('Configuration updated successfully');\n return { updated: true };\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n log.error({ error }, 'Failed to update config');\n return { updated: false, error };\n }\n }\n\n /**\n * App store (phase 1): persist `extensions.enabled` / `extensions.disabled`\n * for a bundled extension. Marketplace-only extensions hot-load on enable;\n * disable still needs a gateway restart to unload.\n */\n async setBundledExtensionActivationTarget(\n extensionId: string,\n wanted: boolean,\n ): Promise<{ ok: boolean; error?: string; requiresGatewayRestart: boolean }> {\n const loader = this.opts.getExtensionLoader();\n if (!loader) {\n return { ok: false, error: 'Extension loader unavailable', requiresGatewayRestart: false };\n }\n const id = extensionId.trim();\n if (!id) {\n return { ok: false, error: 'Invalid extension id', requiresGatewayRestart: false };\n }\n const patch = computeBundledExtensionExtensionsPatch(loader, this.opts.getConfig(), id, wanted);\n if (patch.ok === false) {\n return { ok: false, error: patch.error, requiresGatewayRestart: false };\n }\n const newConfig = { ...this.opts.getConfig(), extensions: patch.extensions } as Config;\n const saved = await this.saveConfig(newConfig);\n if (!saved.saved) {\n return { ok: false, error: saved.error ?? 'Failed to save config', requiresGatewayRestart: false };\n }\n loader.setConfig(this.opts.getConfig() as unknown as SurfaceConfig);\n\n let requiresGatewayRestart = true;\n if (wanted) {\n try {\n loader.invalidateManifestCache();\n await loader.loadByActivationPlan();\n requiresGatewayRestart = false;\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, extensionId: id, errorMessage: em },\n `Extension hot-load after bundled activation failed: ${em}`,\n );\n requiresGatewayRestart = true;\n }\n }\n\n this.opts.emit('config.reload', { section: 'extensions', source: 'bundled-activation' });\n return { ok: true, requiresGatewayRestart };\n }\n\n // ── QR-login follow-ups (bypass fs watcher) ────────────────────────────\n\n async afterWeixinCredentialsPersisted(): Promise<void> {\n const next = loadConfig(this.opts.configPath);\n this.opts.setConfig(next);\n this.opts.getAgentService().applyAgentDefaultsFromConfig(next);\n this.configReloader?.syncCurrentConfig(next);\n await this.handleChannelsReload(next);\n const { weixinPlugin } = await import('../../channels/weixin/index.js');\n await weixinPlugin.reloadMonitorsWithConfig(this.opts.getConfig(), this.opts.bus);\n log.info('Weixin monitors restarted after credential login');\n }\n\n async afterFeishuCredentialsPersisted(): Promise<void> {\n const next = loadConfig(this.opts.configPath);\n this.opts.setConfig(next);\n this.opts.getAgentService().applyAgentDefaultsFromConfig(next);\n this.configReloader?.syncCurrentConfig(next);\n await this.handleChannelsReload(next);\n log.info('Feishu config applied after QR setup');\n }\n\n // ── Section reload handlers (also used by manual triggers) ─────────────\n\n /**\n * Apply `latest.channels` to every registered channel plugin (Telegram,\n * Weixin, extensions). Single runtime path for: file watcher hot reload, API\n * saves, and Weixin QR follow-up.\n */\n async handleChannelsReload(newConfig: Config): Promise<void> {\n log.debug('Reloading channels config...');\n this.opts.setConfig(newConfig);\n await this.opts.getChannelManager().updateConfig(newConfig);\n this.opts.emit('config.reload', { section: 'channels' });\n this.opts.emit('channels.status', { channels: this.opts.getChannelsStatus() });\n log.debug('Channels config reloaded');\n }\n\n /**\n * Apply `gateway.heartbeat` from current config after PATCH /api/config (and\n * when hot reload is off). File watcher uses `handleHeartbeatReload` with\n * the same effect when paths match.\n */\n reloadHeartbeatFromCurrentConfig(): void {\n this.handleHeartbeatReload(this.opts.getConfig());\n }\n\n // ── Internals ──────────────────────────────────────────────────────────\n\n private handleModelsReload(newConfig: Config): void {\n log.debug('Reloading models config...');\n this.opts.setConfig(newConfig);\n getModelRegistry().refresh();\n this.opts.emit('config.reload', { section: 'models' });\n log.debug('Models config reloaded');\n }\n\n private handleAgentDefaultsReload(newConfig: Config): void {\n log.debug('Reloading agent defaults...');\n this.opts.setConfig(newConfig);\n this.opts.getAgentService().applyAgentDefaultsFromConfig(newConfig);\n void this.opts.reconcileBrowserExtensionServer();\n this.opts.emit('config.reload', { section: 'agents' });\n log.debug('Agent defaults reloaded');\n }\n\n /**\n * Coalesces rapid saves so Telegram/Weixin do not stop/start repeatedly.\n * The persist path schedules the channel apply; the same coalescer absorbs\n * follow-up saves until the first flush settles.\n */\n private scheduleChannelPluginsAfterPersist(): void {\n this.channelReloadPending = true;\n if (this.channelReloadFlushPromise) return;\n this.channelReloadFlushPromise = (async () => {\n try {\n while (this.channelReloadPending) {\n this.channelReloadPending = false;\n await this.handleChannelsReload(this.opts.getConfig());\n }\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.error({ err, errorMessage: em }, `Channel reload after persist failed: ${em}`);\n } finally {\n this.channelReloadFlushPromise = null;\n if (this.channelReloadPending) {\n this.scheduleChannelPluginsAfterPersist();\n }\n }\n })();\n }\n\n private handleCronReload(newConfig: Config): void {\n log.debug('Reloading cron config...');\n this.opts.setConfig(newConfig);\n this.opts.getCronService().updateConfig(newConfig);\n this.opts.emit('config.reload', { section: 'cron' });\n log.debug('Cron config reloaded');\n }\n\n private handleHeartbeatReload(newConfig: Config): void {\n log.debug('Reloading heartbeat config...');\n this.opts.setConfig(newConfig);\n this.opts.getHeartbeatService()?.updateConfig(newConfig);\n this.opts.emit('config.reload', { section: 'heartbeat' });\n log.debug('Heartbeat config reloaded');\n }\n\n private handleToolsReload(newConfig: Config): void {\n log.debug('Reloading tools config...');\n this.opts.setConfig(newConfig);\n this.opts.emit('config.reload', { section: 'tools' });\n log.debug('Tools config reloaded');\n }\n\n private handleMcpReload(newConfig: Config): void {\n log.debug('Reloading MCP config...');\n this.opts.setConfig(newConfig);\n void disposeAllSessionMcpRuntimes().catch((err) => {\n log.warn({ err }, 'MCP runtime dispose on config reload failed');\n });\n this.opts.emit('config.reload', { section: 'mcp' });\n log.debug('MCP config reloaded');\n }\n\n /** Dispatch config hot reload to extensions that registered `registerReload`. */\n private async handleExtensionsReload(\n newConfig: Config,\n changedPaths: string[],\n ): Promise<void> {\n this.opts.setConfig(newConfig);\n const loader = this.opts.getExtensionLoader();\n loader?.setConfig(newConfig as unknown as SurfaceConfig);\n\n if (!loader) {\n this.opts.emit('config.reload', {\n section: 'extensions',\n source: 'extension-reload',\n changedPaths,\n });\n return;\n }\n\n const registry = loader.getRegistry();\n const matchingRegs = registry.getMatchingReloadRegistrations(changedPaths);\n\n if (matchingRegs.length === 0) {\n log.debug({ changedPaths }, 'No extension reload handlers matched');\n this.opts.emit('config.reload', {\n section: 'extensions',\n source: 'extension-reload',\n changedPaths,\n });\n return;\n }\n\n for (const reg of matchingRegs) {\n const relevantPaths = changedPaths.filter(\n (p) =>\n reg.configPrefixes.length === 0 ||\n reg.configPrefixes.some(\n (prefix) => p === prefix || p.startsWith(`${prefix}.`),\n ),\n );\n\n log.info(\n { extensionId: reg.extensionId, relevantPaths },\n 'Calling extension reload handler',\n );\n\n try {\n const result = await reg.handler(newConfig, relevantPaths);\n if (result.success) {\n log.info({ extensionId: reg.extensionId }, 'Extension reload succeeded');\n } else {\n log.warn(\n { extensionId: reg.extensionId, error: result.error },\n `Extension reload reported failure: ${result.error ?? 'unknown'}`,\n );\n }\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n log.error(\n { err, extensionId: reg.extensionId, errorMessage },\n `Extension reload handler threw: ${errorMessage}`,\n );\n }\n }\n\n this.opts.emit('config.reload', {\n section: 'extensions',\n source: 'extension-reload',\n changedPaths,\n });\n }\n\n /**\n * Persist and replace `currentConfig` with the validated file contents so\n * runtime matches disk (PATCH merge objects can drift from Zod-normalized\n * output).\n */\n private async writeConfigAndReloadFromDisk(configToWrite: Config): Promise<void> {\n await writeConfigToDisk(configToWrite, this.opts.configPath);\n const reloaded = loadConfig(this.opts.configPath);\n this.opts.setConfig(reloaded);\n if (sanitizeTunnelConfig(reloaded)) {\n await writeConfigToDisk(reloaded, this.opts.configPath);\n }\n this.opts.getAgentService().applyAgentDefaultsFromConfig(reloaded);\n await this.opts.reconcileBrowserExtensionServer();\n // Hot-apply: reconcile managed dreaming cron jobs immediately after config persists.\n await this.opts.getAgentService().reconcileDreamingNow().catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, errorMessage: em }, `Dreaming cron reconcile after save failed: ${em}`);\n });\n // Align watcher baseline before channel hooks run so fs `change` does not\n // re-apply the same diff concurrently.\n this.configReloader?.syncCurrentConfig(reloaded);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config-coordinator.js","names":["writeConfigToDisk"],"sources":["../../../../src/gateway/service/config-coordinator.ts"],"sourcesContent":["/**\n * GatewayConfigCoordinator — owns config persistence, hot-reload, and the\n * per-section reload handlers.\n *\n * Was 350 lines of `GatewayService` covering nine concerns:\n * - manual `reloadConfig()` (CLI/UI trigger)\n * - `saveConfig()` / `updateConfig()` (PATCH /api/config)\n * - `setBundledExtensionActivationTarget` (extension store install)\n * - `afterWeixinCredentialsPersisted` / `afterFeishuCredentialsPersisted`\n * (QR-login follow-ups that bypass the watcher)\n * - `ConfigHotReloader` (fs.watch → debounced per-section dispatch)\n * - Eight section reload handlers (models / agents / channels / cron /\n * heartbeat / tools / mcp / extensions)\n * - `scheduleChannelPluginsAfterPersist` (coalesces rapid saves so\n * Telegram/Weixin do not stop/start repeatedly)\n *\n * Pulled out so the gateway composition root stays focused on lifecycle, and\n * each handler is reachable from one place when adding a new config section.\n *\n * **Config state ownership.** `GatewayService.config` is still the single\n * source of truth — this coordinator reads it via `getConfig()` and writes it\n * back via `setConfig()` after every reload / persist. We pass through rather\n * than holding our own copy so other coordinators (sessions, marketplace,\n * agent runner) see the latest config the moment a reload commits.\n */\nimport type { Config } from '../../config/schema.js';\nimport type { Config as SurfaceConfig } from '../../config/config-surface.js';\nimport type { AgentService } from '../../agent/service.js';\nimport type { ChannelManager } from '../../channels/manager.js';\nimport type { CronService } from '../../cron/index.js';\nimport type { HeartbeatService } from '../heartbeat/index.js';\nimport type { ExtensionLoader } from '../../extensions/loader.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport { ConfigHotReloader } from '../../config/reload.js';\nimport { loadConfig, saveConfig as writeConfigToDisk } from '../../config/index.js';\nimport { sanitizeTunnelConfig } from '../../tunnel/tunnel-config.js';\nimport { getModelRegistry } from '../../providers/index.js';\nimport { disposeAllSessionMcpRuntimes } from '../../agent/mcp/bundle-mcp-tools.js';\nimport { computeBundledExtensionExtensionsPatch } from '../../extensions/bundled-extension-activation.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('GatewayConfigCoordinator');\n\nexport interface GatewayConfigCoordinatorOptions {\n configPath: string;\n bus: MessageBus;\n /** Hot reload (fs.watch) — disabled in tests / certain CLI modes. */\n enableHotReload: boolean;\n getConfig: () => Config;\n /** Writes the new config back into `GatewayService.config`. */\n setConfig: (next: Config) => void;\n getAgentService: () => AgentService;\n getChannelManager: () => ChannelManager;\n getCronService: () => CronService;\n getHeartbeatService: () => HeartbeatService | null;\n getExtensionLoader: () => ExtensionLoader | null;\n /** Re-evaluate browser-extension server attachment after agent defaults change. */\n reconcileBrowserExtensionServer: () => Promise<void>;\n /** Latest channel status snapshot for the `channels.status` SSE event. */\n getChannelsStatus: () => unknown;\n /** SSE emit (used for `config.reload` + `channels.status`). */\n emit: (type: string, payload: unknown) => void;\n}\n\nexport class GatewayConfigCoordinator {\n private readonly opts: GatewayConfigCoordinatorOptions;\n private configReloader: ConfigHotReloader | null = null;\n private channelReloadFlushPromise: Promise<void> | null = null;\n private channelReloadPending = false;\n\n constructor(opts: GatewayConfigCoordinatorOptions) {\n this.opts = opts;\n }\n\n // ── Lifecycle ──────────────────────────────────────────────────────────\n\n /** Start the fs.watch-based reloader (idempotent — only starts once). */\n startHotReloader(): void {\n if (this.configReloader) return;\n this.configReloader = new ConfigHotReloader(\n this.opts.configPath,\n this.opts.getConfig(),\n {\n onModelsReload: (newConfig) => this.handleModelsReload(newConfig),\n onAgentDefaultsReload: (newConfig) => this.handleAgentDefaultsReload(newConfig),\n onChannelsReload: (newConfig) => this.handleChannelsReload(newConfig),\n onCronReload: (newConfig) => this.handleCronReload(newConfig),\n onHeartbeatReload: (newConfig) => this.handleHeartbeatReload(newConfig),\n onToolsReload: (newConfig) => this.handleToolsReload(newConfig),\n onMcpReload: (newConfig) => this.handleMcpReload(newConfig),\n onExtensionsReload: async (newConfig, changedPaths) => {\n await this.handleExtensionsReload(newConfig, changedPaths);\n },\n onFullRestart: (newConfig) => {\n log.warn(\n { requiresProcessRestart: true, hint: 'Restart the gateway process (hot reload cannot apply this change).' },\n 'Config reload: full gateway restart required — see prior \"restartPaths\" info log',\n );\n this.opts.setConfig(newConfig);\n this.opts.emit('config.reload', { section: 'full', requiresRestart: true });\n },\n },\n {\n debounceMs: 300,\n enabled: this.opts.enableHotReload,\n }\n );\n this.configReloader.start();\n }\n\n async stopHotReloader(): Promise<void> {\n if (this.configReloader) {\n await this.configReloader.stop();\n this.configReloader = null;\n }\n }\n\n // ── Manual reload (UI trigger) ─────────────────────────────────────────\n\n async reloadConfig(): Promise<{ reloaded: boolean; error?: string }> {\n if (!this.configReloader) {\n return { reloaded: false, error: 'Config reloader not initialized' };\n }\n const result = await this.configReloader.triggerReload();\n return { reloaded: result.success, error: result.error };\n }\n\n // ── Persist (PATCH /api/config, marketplace install, etc.) ─────────────\n\n async saveConfig(config: Config): Promise<{ saved: boolean; error?: string }> {\n try {\n await this.writeConfigAndReloadFromDisk(config);\n this.scheduleChannelPluginsAfterPersist();\n return { saved: true };\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.error({ err, errorMessage: em, phase: 'infra.config' }, `Failed to save config: ${em}`);\n return { saved: false, error: em };\n }\n }\n\n /** Merge partial updates into `currentConfig` and persist. */\n async updateConfig(updates: Partial<Config>): Promise<{ updated: boolean; error?: string }> {\n try {\n log.debug('Updating configuration...');\n const merged = { ...this.opts.getConfig(), ...updates };\n this.opts.setConfig(merged);\n await this.writeConfigAndReloadFromDisk(merged);\n this.scheduleChannelPluginsAfterPersist();\n log.debug('Configuration updated successfully');\n return { updated: true };\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.error({ err, errorMessage: em, phase: 'infra.config' }, `Failed to update config: ${em}`);\n return { updated: false, error: em };\n }\n }\n\n /**\n * App store (phase 1): persist `extensions.enabled` / `extensions.disabled`\n * for a bundled extension. Marketplace-only extensions hot-load on enable;\n * disable still needs a gateway restart to unload.\n */\n async setBundledExtensionActivationTarget(\n extensionId: string,\n wanted: boolean,\n ): Promise<{ ok: boolean; error?: string; requiresGatewayRestart: boolean }> {\n const loader = this.opts.getExtensionLoader();\n if (!loader) {\n return { ok: false, error: 'Extension loader unavailable', requiresGatewayRestart: false };\n }\n const id = extensionId.trim();\n if (!id) {\n return { ok: false, error: 'Invalid extension id', requiresGatewayRestart: false };\n }\n const patch = computeBundledExtensionExtensionsPatch(loader, this.opts.getConfig(), id, wanted);\n if (patch.ok === false) {\n return { ok: false, error: patch.error, requiresGatewayRestart: false };\n }\n const newConfig = { ...this.opts.getConfig(), extensions: patch.extensions } as Config;\n const saved = await this.saveConfig(newConfig);\n if (!saved.saved) {\n return { ok: false, error: saved.error ?? 'Failed to save config', requiresGatewayRestart: false };\n }\n loader.setConfig(this.opts.getConfig() as unknown as SurfaceConfig);\n\n let requiresGatewayRestart = true;\n if (wanted) {\n try {\n loader.invalidateManifestCache();\n await loader.loadByActivationPlan();\n requiresGatewayRestart = false;\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, extensionId: id, errorMessage: em },\n `Extension hot-load after bundled activation failed: ${em}`,\n );\n requiresGatewayRestart = true;\n }\n }\n\n this.opts.emit('config.reload', { section: 'extensions', source: 'bundled-activation' });\n return { ok: true, requiresGatewayRestart };\n }\n\n // ── QR-login follow-ups (bypass fs watcher) ────────────────────────────\n\n async afterWeixinCredentialsPersisted(): Promise<void> {\n const next = loadConfig(this.opts.configPath);\n this.opts.setConfig(next);\n this.opts.getAgentService().applyAgentDefaultsFromConfig(next);\n this.configReloader?.syncCurrentConfig(next);\n await this.handleChannelsReload(next);\n const { weixinPlugin } = await import('../../channels/weixin/index.js');\n await weixinPlugin.reloadMonitorsWithConfig(this.opts.getConfig(), this.opts.bus);\n log.info('Weixin monitors restarted after credential login');\n }\n\n async afterFeishuCredentialsPersisted(): Promise<void> {\n const next = loadConfig(this.opts.configPath);\n this.opts.setConfig(next);\n this.opts.getAgentService().applyAgentDefaultsFromConfig(next);\n this.configReloader?.syncCurrentConfig(next);\n await this.handleChannelsReload(next);\n log.info('Feishu config applied after QR setup');\n }\n\n // ── Section reload handlers (also used by manual triggers) ─────────────\n\n /**\n * Apply `latest.channels` to every registered channel plugin (Telegram,\n * Weixin, extensions). Single runtime path for: file watcher hot reload, API\n * saves, and Weixin QR follow-up.\n */\n async handleChannelsReload(newConfig: Config): Promise<void> {\n log.debug('Reloading channels config...');\n this.opts.setConfig(newConfig);\n await this.opts.getChannelManager().updateConfig(newConfig);\n this.opts.emit('config.reload', { section: 'channels' });\n this.opts.emit('channels.status', { channels: this.opts.getChannelsStatus() });\n log.debug('Channels config reloaded');\n }\n\n /**\n * Apply `gateway.heartbeat` from current config after PATCH /api/config (and\n * when hot reload is off). File watcher uses `handleHeartbeatReload` with\n * the same effect when paths match.\n */\n reloadHeartbeatFromCurrentConfig(): void {\n this.handleHeartbeatReload(this.opts.getConfig());\n }\n\n // ── Internals ──────────────────────────────────────────────────────────\n\n private handleModelsReload(newConfig: Config): void {\n log.debug('Reloading models config...');\n this.opts.setConfig(newConfig);\n getModelRegistry().refresh();\n this.opts.emit('config.reload', { section: 'models' });\n log.debug('Models config reloaded');\n }\n\n private handleAgentDefaultsReload(newConfig: Config): void {\n log.debug('Reloading agent defaults...');\n this.opts.setConfig(newConfig);\n this.opts.getAgentService().applyAgentDefaultsFromConfig(newConfig);\n void this.opts.reconcileBrowserExtensionServer();\n this.opts.emit('config.reload', { section: 'agents' });\n log.debug('Agent defaults reloaded');\n }\n\n /**\n * Coalesces rapid saves so Telegram/Weixin do not stop/start repeatedly.\n * The persist path schedules the channel apply; the same coalescer absorbs\n * follow-up saves until the first flush settles.\n */\n private scheduleChannelPluginsAfterPersist(): void {\n this.channelReloadPending = true;\n if (this.channelReloadFlushPromise) return;\n this.channelReloadFlushPromise = (async () => {\n try {\n while (this.channelReloadPending) {\n this.channelReloadPending = false;\n await this.handleChannelsReload(this.opts.getConfig());\n }\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.error({ err, errorMessage: em }, `Channel reload after persist failed: ${em}`);\n } finally {\n this.channelReloadFlushPromise = null;\n if (this.channelReloadPending) {\n this.scheduleChannelPluginsAfterPersist();\n }\n }\n })();\n }\n\n private handleCronReload(newConfig: Config): void {\n log.debug('Reloading cron config...');\n this.opts.setConfig(newConfig);\n this.opts.getCronService().updateConfig(newConfig);\n this.opts.emit('config.reload', { section: 'cron' });\n log.debug('Cron config reloaded');\n }\n\n private handleHeartbeatReload(newConfig: Config): void {\n log.debug('Reloading heartbeat config...');\n this.opts.setConfig(newConfig);\n this.opts.getHeartbeatService()?.updateConfig(newConfig);\n this.opts.emit('config.reload', { section: 'heartbeat' });\n log.debug('Heartbeat config reloaded');\n }\n\n private handleToolsReload(newConfig: Config): void {\n log.debug('Reloading tools config...');\n this.opts.setConfig(newConfig);\n this.opts.emit('config.reload', { section: 'tools' });\n log.debug('Tools config reloaded');\n }\n\n private handleMcpReload(newConfig: Config): void {\n log.debug('Reloading MCP config...');\n this.opts.setConfig(newConfig);\n void disposeAllSessionMcpRuntimes().catch((err) => {\n log.warn({ err }, 'MCP runtime dispose on config reload failed');\n });\n this.opts.emit('config.reload', { section: 'mcp' });\n log.debug('MCP config reloaded');\n }\n\n /** Dispatch config hot reload to extensions that registered `registerReload`. */\n private async handleExtensionsReload(\n newConfig: Config,\n changedPaths: string[],\n ): Promise<void> {\n this.opts.setConfig(newConfig);\n const loader = this.opts.getExtensionLoader();\n loader?.setConfig(newConfig as unknown as SurfaceConfig);\n\n if (!loader) {\n this.opts.emit('config.reload', {\n section: 'extensions',\n source: 'extension-reload',\n changedPaths,\n });\n return;\n }\n\n const registry = loader.getRegistry();\n const matchingRegs = registry.getMatchingReloadRegistrations(changedPaths);\n\n if (matchingRegs.length === 0) {\n log.debug({ changedPaths }, 'No extension reload handlers matched');\n this.opts.emit('config.reload', {\n section: 'extensions',\n source: 'extension-reload',\n changedPaths,\n });\n return;\n }\n\n for (const reg of matchingRegs) {\n const relevantPaths = changedPaths.filter(\n (p) =>\n reg.configPrefixes.length === 0 ||\n reg.configPrefixes.some(\n (prefix) => p === prefix || p.startsWith(`${prefix}.`),\n ),\n );\n\n log.info(\n { extensionId: reg.extensionId, relevantPaths },\n 'Calling extension reload handler',\n );\n\n try {\n const result = await reg.handler(newConfig, relevantPaths);\n if (result.success) {\n log.info({ extensionId: reg.extensionId }, 'Extension reload succeeded');\n } else {\n log.warn(\n { extensionId: reg.extensionId, error: result.error },\n `Extension reload reported failure: ${result.error ?? 'unknown'}`,\n );\n }\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n log.error(\n { err, extensionId: reg.extensionId, errorMessage },\n `Extension reload handler threw: ${errorMessage}`,\n );\n }\n }\n\n this.opts.emit('config.reload', {\n section: 'extensions',\n source: 'extension-reload',\n changedPaths,\n });\n }\n\n /**\n * Persist and replace `currentConfig` with the validated file contents so\n * runtime matches disk (PATCH merge objects can drift from Zod-normalized\n * output).\n */\n private async writeConfigAndReloadFromDisk(configToWrite: Config): Promise<void> {\n await writeConfigToDisk(configToWrite, this.opts.configPath);\n const reloaded = loadConfig(this.opts.configPath);\n this.opts.setConfig(reloaded);\n if (sanitizeTunnelConfig(reloaded)) {\n await writeConfigToDisk(reloaded, this.opts.configPath);\n }\n this.opts.getAgentService().applyAgentDefaultsFromConfig(reloaded);\n await this.opts.reconcileBrowserExtensionServer();\n // Hot-apply: reconcile managed dreaming cron jobs immediately after config persists.\n await this.opts.getAgentService().reconcileDreamingNow().catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, errorMessage: em }, `Dreaming cron reconcile after save failed: ${em}`);\n });\n // Align watcher baseline before channel hooks run so fs `change` does not\n // re-apply the same diff concurrently.\n this.configReloader?.syncCurrentConfig(reloaded);\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
+ import { resolveStateDir } from "../../config/paths-state.js";
1
2
  import { createLogger } from "../../utils/logger/index.js";
2
3
  import { init_logger } from "../../utils/logger.js";
3
- import { resolveStateDir } from "../../config/paths-state.js";
4
4
  import { init_paths, resolveExtensionsDir } from "../../config/paths.js";
5
5
  import { createSkillConfigManager } from "../../agent/skills/config.js";
6
6
  import { removeSkillsLockEntry } from "../../agent/skills/hub-lock.js";
@@ -11,8 +11,8 @@ import { installExtensionFromStoreZip, peekExtensionIdFromStoreZip } from "../..
11
11
  import { downloadExtensionStoreZipBuffer, fetchMarketplacePackageDetail, resolveExtensionZipDownloadUrl, resolveExtensionsStoreBaseUrl } from "../../agent/skills/marketplace/adapters/store/store-api-client.js";
12
12
  import { getMarketplaceProviderDisplayName, resolveSkillsMarketplaceProvider } from "../../agent/skills/marketplace/resolve-adapter.js";
13
13
  import { downloadFromMarketplace, getMarketplacePackageDetail, listMarketplaceCategories, listMarketplacePackages } from "../../agent/skills/skills-marketplace.js";
14
- import { existsSync, mkdirSync, rmSync } from "node:fs";
15
14
  import { join } from "node:path";
15
+ import { existsSync, mkdirSync, rmSync } from "node:fs";
16
16
  //#region src/gateway/service/marketplace-service.ts
17
17
  /**
18
18
  * GatewayMarketplaceService — install / browse / remove for the two marketplaces
@@ -33,7 +33,7 @@ import { join } from "node:path";
33
33
  */
34
34
  init_paths();
35
35
  init_logger();
36
- const log = createLogger("GatewayMarketplace");
36
+ const log = createLogger("Gateway:Marketplace");
37
37
  var GatewayMarketplaceService = class {
38
38
  opts;
39
39
  constructor(opts) {
@@ -1 +1 @@
1
- {"version":3,"file":"marketplace-service.js","names":[],"sources":["../../../../src/gateway/service/marketplace-service.ts"],"sourcesContent":["/**\n * GatewayMarketplaceService — install / browse / remove for the two marketplaces\n * the gateway exposes:\n *\n * • **Skills** (`~/.xopc/skills/managed/<id>`) — zip-bundled markdown skills\n * pulled from a provider catalog (`agent/skills/skills-marketplace.ts`).\n * • **Extensions** (`~/.xopc/extensions/<id>`) — full extension packages from\n * the xopc-store.\n *\n * Owns the install/uninstall composite operations:\n * - download zip → unpack → upsert lockfile → refresh loader → emit reload\n * - rm dir → remove from `extensions.enabled` → refresh loader → emit reload\n *\n * Local-only skill management (install-from-zip, delete, enable/disable, list)\n * lives here too so callers (commands-skills routes) depend on a single narrow\n * service instead of the full `GatewayService`.\n */\nimport { existsSync, mkdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport type { Config } from '../../config/schema.js';\nimport type { AgentService } from '../../agent/service.js';\nimport type { ChannelManager } from '../../channels/manager.js';\nimport type { ExtensionLoader } from '../../extensions/loader.js';\nimport type { SkillCatalogEntry } from '../../agent/agent-manager.js';\nimport type {\n ManagedSkillListItem,\n} from '../../agent/skills/managed-store.js';\nimport type { SkillMarkdownPreviewPayload } from '../../agent/skills/types.js';\nimport type {\n MarketplaceCategoryOption,\n SkillsStoreListParams,\n UnifiedMarketplaceListResponse,\n UnifiedMarketplacePackageDetail,\n} from '../../agent/skills/skills-marketplace.js';\nimport type { MarketplacePackageDetail } from '../../agent/skills/marketplace/adapters/store/store-api-client.js';\nimport {\n deleteManagedSkill as deleteManagedSkillDir,\n installSkillFromZip,\n listManagedSkillDirs,\n} from '../../agent/skills/managed-store.js';\nimport {\n downloadFromMarketplace,\n getMarketplacePackageDetail,\n getMarketplaceProviderDisplayName,\n listMarketplaceCategories,\n listMarketplacePackages,\n listRegisteredProviders,\n resolveSkillsMarketplaceProvider,\n} from '../../agent/skills/skills-marketplace.js';\nimport {\n downloadExtensionStoreZipBuffer,\n fetchMarketplacePackageDetail,\n resolveExtensionZipDownloadUrl,\n resolveExtensionsStoreBaseUrl,\n} from '../../agent/skills/marketplace/adapters/store/store-api-client.js';\nimport { installExtensionFromStoreZip, peekExtensionIdFromStoreZip } from '../../extensions/install.js';\nimport { createSkillConfigManager } from '../../agent/skills/config.js';\nimport { removeSkillsLockEntry } from '../../agent/skills/hub-lock.js';\nimport { getExtensionLockfileManager } from '../../extensions/lockfile.js';\nimport { resolveExtensionsDir, resolveStateDir } from '../../config/paths.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('GatewayMarketplace');\n\nexport interface GatewayMarketplaceServiceOptions {\n getConfig: () => Config;\n getAgentService: () => AgentService;\n getExtensionLoader: () => ExtensionLoader | null;\n getChannelManager: () => ChannelManager;\n saveConfig: (config: Config) => Promise<{ saved: boolean; error?: string }>;\n emit: (type: string, payload: unknown) => void;\n}\n\nexport class GatewayMarketplaceService {\n private readonly opts: GatewayMarketplaceServiceOptions;\n\n constructor(opts: GatewayMarketplaceServiceOptions) {\n this.opts = opts;\n }\n\n // ── Local skills (managed dir) ────────────────────────────────────────\n\n getSkillsApi(lang?: string): {\n catalog: SkillCatalogEntry[];\n managed: ManagedSkillListItem[];\n } {\n return {\n catalog: this.opts.getAgentService().getSkillCatalog(lang),\n managed: listManagedSkillDirs(),\n };\n }\n\n getSkillMarkdownSource(skillName: string, lang?: string): SkillMarkdownPreviewPayload | null {\n return this.opts.getAgentService().getSkillMarkdownSource(skillName, lang);\n }\n\n deleteSkill(skillId: string): void {\n removeSkillsLockEntry(skillId);\n deleteManagedSkillDir(skillId);\n this.opts.getAgentService().refreshSkillsAfterDiskChange();\n }\n\n installSkillZip(\n buffer: Buffer,\n opts: { skillId?: string; overwrite?: boolean },\n ): { skillId: string; path: string } {\n const result = installSkillFromZip(buffer, opts);\n removeSkillsLockEntry(result.skillId);\n this.opts.getAgentService().refreshSkillsAfterDiskChange();\n return result;\n }\n\n reloadSkills(): void {\n this.opts.getAgentService().refreshSkillsAfterDiskChange();\n }\n\n patchSkillEnabled(skillName: string, enabled: boolean): void {\n createSkillConfigManager(resolveStateDir()).setSkillEnabled(skillName, enabled);\n this.opts.getAgentService().refreshSkillsAfterSkillConfigChange();\n }\n\n // ── Skills marketplace catalog ────────────────────────────────────────\n\n async fetchSkillsCatalog(\n params: SkillsStoreListParams,\n provider?: string,\n ): Promise<UnifiedMarketplaceListResponse> {\n return listMarketplacePackages(this.opts.getConfig(), params, provider);\n }\n\n async fetchSkillsCategories(\n provider?: string,\n ): Promise<{ items: MarketplaceCategoryOption[] }> {\n return listMarketplaceCategories(this.opts.getConfig(), provider);\n }\n\n async fetchSkillsPackageDetail(\n packageName: string,\n provider?: string,\n ): Promise<UnifiedMarketplacePackageDetail> {\n return getMarketplacePackageDetail(this.opts.getConfig(), packageName, provider);\n }\n\n async installSkill(opts: {\n name: string;\n version?: string;\n overwrite?: boolean;\n provider?: string;\n }): Promise<{ skillId: string; path: string }> {\n const { buffer, skillId } = await downloadFromMarketplace(\n this.opts.getConfig(),\n opts.name,\n opts.version,\n opts.provider,\n );\n return this.installSkillZip(buffer, { skillId, overwrite: opts.overwrite ?? false });\n }\n\n getSkillsProvider(): { provider: string; displayName: string } {\n const provider = resolveSkillsMarketplaceProvider(this.opts.getConfig());\n return {\n provider,\n displayName: getMarketplaceProviderDisplayName(provider),\n };\n }\n\n /** All registered marketplace providers (built-in + extension-contributed). */\n getSkillsProviders(): Array<{ id: string; displayName: string }> {\n return listRegisteredProviders();\n }\n\n // ── Extension marketplace ─────────────────────────────────────────────\n\n /** xopc-store extension package preview (type must be `extension`). */\n async fetchExtensionPackageDetail(packageName: string): Promise<MarketplacePackageDetail> {\n const base = resolveExtensionsStoreBaseUrl(this.opts.getConfig());\n const detail = await fetchMarketplacePackageDetail(base, packageName.trim());\n if (detail.type !== 'extension') {\n throw new Error(\n `Package \"${packageName}\" is not an extension (store type: ${detail.type}).`,\n );\n }\n return detail;\n }\n\n /**\n * Install an extension from xopc-store into `~/.xopc/extensions`, append id\n * to `extensions.enabled`, refresh the loader, and emit `config.reload`.\n * Returns `requiresGatewayRestart=true` when a new channel plugin would have\n * to wire into the running gateway (channel registration cannot hot-patch).\n */\n async installExtension(opts: {\n name: string;\n version?: string;\n overwrite?: boolean;\n }): Promise<{ extensionId: string; version: string; requiresGatewayRestart: boolean }> {\n const packageName = opts.name.trim();\n if (!packageName) {\n throw new Error('Package name is required');\n }\n const cfg = this.opts.getConfig();\n const storeBase = resolveExtensionsStoreBaseUrl(cfg);\n const targetDir = resolveExtensionsDir();\n mkdirSync(targetDir, { recursive: true });\n\n const { downloadUrl, version } = await resolveExtensionZipDownloadUrl(\n storeBase,\n packageName,\n opts.version,\n );\n const buf = await downloadExtensionStoreZipBuffer(storeBase, downloadUrl);\n\n if (opts.overwrite) {\n const peekId = peekExtensionIdFromStoreZip(buf);\n if (peekId && existsSync(join(targetDir, peekId))) {\n rmSync(join(targetDir, peekId), { recursive: true, force: true });\n }\n }\n\n const result = await installExtensionFromStoreZip(buf, targetDir);\n if (!result.ok || !result.extensionId) {\n throw new Error(result.error ?? 'Extension install failed');\n }\n\n const lock = getExtensionLockfileManager();\n await lock.upsert(result.extensionId, {\n name: result.extensionId,\n version,\n resolved: packageName,\n source: 'store',\n });\n\n const nextConfig = this.mergeExtensionEnabledIntoConfig(cfg, result.extensionId);\n const saved = await this.opts.saveConfig(nextConfig);\n if (!saved.saved) {\n throw new Error(saved.error ?? 'Failed to save config after extension install');\n }\n\n const channelIdsBefore = new Set(this.opts.getChannelManager().getAllPlugins().map((p) => p.id));\n let requiresGatewayRestart = false;\n const loader = this.opts.getExtensionLoader();\n try {\n if (loader) {\n loader.invalidateManifestCache();\n await loader.loadByActivationPlan();\n const reg = loader.getRegistry();\n for (const p of reg.channelPlugins) {\n if (!channelIdsBefore.has(p.id)) {\n requiresGatewayRestart = true;\n break;\n }\n }\n }\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, errorMessage: em }, `Extension loader refresh after marketplace install failed: ${em}`);\n requiresGatewayRestart = true;\n }\n\n this.opts.emit('config.reload', { section: 'extensions', source: 'marketplace-install' });\n return { extensionId: result.extensionId, version, requiresGatewayRestart };\n }\n\n /** Remove a user-installed extension (global or per-agent dir) from disk and config. */\n async uninstallExtension(extensionId: string): Promise<{ requiresGatewayRestart: boolean }> {\n const id = extensionId.trim();\n if (!id) {\n throw new Error('extensionId is required');\n }\n const loader = this.opts.getExtensionLoader();\n if (!loader) {\n throw new Error('Extensions unavailable');\n }\n const discovered = loader.discoverExtensions();\n const ext = discovered.find((e) => e.id === id);\n if (!ext) {\n throw new Error(`Extension not found: ${id}`);\n }\n if (ext.source === 'bundled') {\n throw new Error('Built-in extensions cannot be uninstalled from the marketplace UI');\n }\n if (existsSync(ext.path)) {\n rmSync(ext.path, { recursive: true, force: true });\n }\n await getExtensionLockfileManager().remove(id);\n\n const nextConfig = this.mergeExtensionRemovedFromEnabledConfig(this.opts.getConfig(), id);\n const saved = await this.opts.saveConfig(nextConfig);\n if (!saved.saved) {\n throw new Error(saved.error ?? 'Failed to save config after extension uninstall');\n }\n try {\n loader.invalidateManifestCache();\n await loader.loadByActivationPlan();\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, errorMessage: em }, `Extension loader refresh after uninstall failed: ${em}`);\n }\n this.opts.emit('config.reload', { section: 'extensions', source: 'marketplace-uninstall' });\n return { requiresGatewayRestart: true };\n }\n\n // ── Internals ─────────────────────────────────────────────────────────\n\n private mergeExtensionEnabledIntoConfig(currentConfig: Config, extensionId: string): Config {\n const id = extensionId.trim();\n const prevExt = currentConfig.extensions;\n const baseExt =\n prevExt && typeof prevExt === 'object' && !Array.isArray(prevExt)\n ? { ...(prevExt as Record<string, unknown>) }\n : {};\n const enabledRaw = baseExt.enabled;\n const enabled = Array.isArray(enabledRaw)\n ? [...enabledRaw.filter((x): x is string => typeof x === 'string')]\n : [];\n if (!enabled.includes(id)) enabled.push(id);\n\n const disabledRaw = baseExt.disabled;\n const nextExt: Record<string, unknown> = { ...baseExt, enabled };\n if (Array.isArray(disabledRaw)) {\n const next = disabledRaw.filter((x): x is string => typeof x === 'string' && x !== id);\n if (next.length > 0) nextExt.disabled = next;\n else delete nextExt.disabled;\n }\n\n return {\n ...currentConfig,\n extensions: nextExt,\n } as Config;\n }\n\n private mergeExtensionRemovedFromEnabledConfig(currentConfig: Config, extensionId: string): Config {\n const id = extensionId.trim();\n const prevExt = currentConfig.extensions;\n const baseExt =\n prevExt && typeof prevExt === 'object' && !Array.isArray(prevExt)\n ? { ...(prevExt as Record<string, unknown>) }\n : {};\n const enabledRaw = baseExt.enabled;\n const enabled = Array.isArray(enabledRaw)\n ? enabledRaw.filter((x): x is string => typeof x === 'string' && x !== id)\n : [];\n return {\n ...currentConfig,\n extensions: { ...baseExt, enabled },\n } as Config;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YA4D8E;aACzB;AAErD,MAAM,MAAM,aAAa,qBAAqB;AAW9C,IAAa,4BAAb,MAAuC;CACrC;CAEA,YAAY,MAAwC;AAClD,OAAK,OAAO;;CAKd,aAAa,MAGX;AACA,SAAO;GACL,SAAS,KAAK,KAAK,iBAAiB,CAAC,gBAAgB,KAAK;GAC1D,SAAS,sBAAsB;GAChC;;CAGH,uBAAuB,WAAmB,MAAmD;AAC3F,SAAO,KAAK,KAAK,iBAAiB,CAAC,uBAAuB,WAAW,KAAK;;CAG5E,YAAY,SAAuB;AACjC,wBAAsB,QAAQ;AAC9B,qBAAsB,QAAQ;AAC9B,OAAK,KAAK,iBAAiB,CAAC,8BAA8B;;CAG5D,gBACE,QACA,MACmC;EACnC,MAAM,SAAS,oBAAoB,QAAQ,KAAK;AAChD,wBAAsB,OAAO,QAAQ;AACrC,OAAK,KAAK,iBAAiB,CAAC,8BAA8B;AAC1D,SAAO;;CAGT,eAAqB;AACnB,OAAK,KAAK,iBAAiB,CAAC,8BAA8B;;CAG5D,kBAAkB,WAAmB,SAAwB;AAC3D,2BAAyB,iBAAiB,CAAC,CAAC,gBAAgB,WAAW,QAAQ;AAC/E,OAAK,KAAK,iBAAiB,CAAC,qCAAqC;;CAKnE,MAAM,mBACJ,QACA,UACyC;AACzC,SAAO,wBAAwB,KAAK,KAAK,WAAW,EAAE,QAAQ,SAAS;;CAGzE,MAAM,sBACJ,UACiD;AACjD,SAAO,0BAA0B,KAAK,KAAK,WAAW,EAAE,SAAS;;CAGnE,MAAM,yBACJ,aACA,UAC0C;AAC1C,SAAO,4BAA4B,KAAK,KAAK,WAAW,EAAE,aAAa,SAAS;;CAGlF,MAAM,aAAa,MAK4B;EAC7C,MAAM,EAAE,QAAQ,YAAY,MAAM,wBAChC,KAAK,KAAK,WAAW,EACrB,KAAK,MACL,KAAK,SACL,KAAK,SACN;AACD,SAAO,KAAK,gBAAgB,QAAQ;GAAE;GAAS,WAAW,KAAK,aAAa;GAAO,CAAC;;CAGtF,oBAA+D;EAC7D,MAAM,WAAW,iCAAiC,KAAK,KAAK,WAAW,CAAC;AACxE,SAAO;GACL;GACA,aAAa,kCAAkC,SAAS;GACzD;;;CAIH,qBAAiE;AAC/D,SAAO,yBAAyB;;;CAMlC,MAAM,4BAA4B,aAAwD;EAExF,MAAM,SAAS,MAAM,8BADR,8BAA8B,KAAK,KAAK,WAAW,CACT,EAAE,YAAY,MAAM,CAAC;AAC5E,MAAI,OAAO,SAAS,YAClB,OAAM,IAAI,MACR,YAAY,YAAY,qCAAqC,OAAO,KAAK,IAC1E;AAEH,SAAO;;;;;;;;CAST,MAAM,iBAAiB,MAIgE;EACrF,MAAM,cAAc,KAAK,KAAK,MAAM;AACpC,MAAI,CAAC,YACH,OAAM,IAAI,MAAM,2BAA2B;EAE7C,MAAM,MAAM,KAAK,KAAK,WAAW;EACjC,MAAM,YAAY,8BAA8B,IAAI;EACpD,MAAM,YAAY,sBAAsB;AACxC,YAAU,WAAW,EAAE,WAAW,MAAM,CAAC;EAEzC,MAAM,EAAE,aAAa,YAAY,MAAM,+BACrC,WACA,aACA,KAAK,QACN;EACD,MAAM,MAAM,MAAM,gCAAgC,WAAW,YAAY;AAEzE,MAAI,KAAK,WAAW;GAClB,MAAM,SAAS,4BAA4B,IAAI;AAC/C,OAAI,UAAU,WAAW,KAAK,WAAW,OAAO,CAAC,CAC/C,QAAO,KAAK,WAAW,OAAO,EAAE;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;;EAIrE,MAAM,SAAS,MAAM,6BAA6B,KAAK,UAAU;AACjE,MAAI,CAAC,OAAO,MAAM,CAAC,OAAO,YACxB,OAAM,IAAI,MAAM,OAAO,SAAS,2BAA2B;AAI7D,QADa,6BACH,CAAC,OAAO,OAAO,aAAa;GACpC,MAAM,OAAO;GACb;GACA,UAAU;GACV,QAAQ;GACT,CAAC;EAEF,MAAM,aAAa,KAAK,gCAAgC,KAAK,OAAO,YAAY;EAChF,MAAM,QAAQ,MAAM,KAAK,KAAK,WAAW,WAAW;AACpD,MAAI,CAAC,MAAM,MACT,OAAM,IAAI,MAAM,MAAM,SAAS,gDAAgD;EAGjF,MAAM,mBAAmB,IAAI,IAAI,KAAK,KAAK,mBAAmB,CAAC,eAAe,CAAC,KAAK,MAAM,EAAE,GAAG,CAAC;EAChG,IAAI,yBAAyB;EAC7B,MAAM,SAAS,KAAK,KAAK,oBAAoB;AAC7C,MAAI;AACF,OAAI,QAAQ;AACV,WAAO,yBAAyB;AAChC,UAAM,OAAO,sBAAsB;IACnC,MAAM,MAAM,OAAO,aAAa;AAChC,SAAK,MAAM,KAAK,IAAI,eAClB,KAAI,CAAC,iBAAiB,IAAI,EAAE,GAAG,EAAE;AAC/B,8BAAyB;AACzB;;;WAIC,KAAK;GACZ,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAI,KAAK;IAAE;IAAK,cAAc;IAAI,EAAE,8DAA8D,KAAK;AACvG,4BAAyB;;AAG3B,OAAK,KAAK,KAAK,iBAAiB;GAAE,SAAS;GAAc,QAAQ;GAAuB,CAAC;AACzF,SAAO;GAAE,aAAa,OAAO;GAAa;GAAS;GAAwB;;;CAI7E,MAAM,mBAAmB,aAAmE;EAC1F,MAAM,KAAK,YAAY,MAAM;AAC7B,MAAI,CAAC,GACH,OAAM,IAAI,MAAM,0BAA0B;EAE5C,MAAM,SAAS,KAAK,KAAK,oBAAoB;AAC7C,MAAI,CAAC,OACH,OAAM,IAAI,MAAM,yBAAyB;EAG3C,MAAM,MADa,OAAO,oBACJ,CAAC,MAAM,MAAM,EAAE,OAAO,GAAG;AAC/C,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,wBAAwB,KAAK;AAE/C,MAAI,IAAI,WAAW,UACjB,OAAM,IAAI,MAAM,oEAAoE;AAEtF,MAAI,WAAW,IAAI,KAAK,CACtB,QAAO,IAAI,MAAM;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;AAEpD,QAAM,6BAA6B,CAAC,OAAO,GAAG;EAE9C,MAAM,aAAa,KAAK,uCAAuC,KAAK,KAAK,WAAW,EAAE,GAAG;EACzF,MAAM,QAAQ,MAAM,KAAK,KAAK,WAAW,WAAW;AACpD,MAAI,CAAC,MAAM,MACT,OAAM,IAAI,MAAM,MAAM,SAAS,kDAAkD;AAEnF,MAAI;AACF,UAAO,yBAAyB;AAChC,SAAM,OAAO,sBAAsB;WAC5B,KAAK;GACZ,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAI,KAAK;IAAE;IAAK,cAAc;IAAI,EAAE,oDAAoD,KAAK;;AAE/F,OAAK,KAAK,KAAK,iBAAiB;GAAE,SAAS;GAAc,QAAQ;GAAyB,CAAC;AAC3F,SAAO,EAAE,wBAAwB,MAAM;;CAKzC,gCAAwC,eAAuB,aAA6B;EAC1F,MAAM,KAAK,YAAY,MAAM;EAC7B,MAAM,UAAU,cAAc;EAC9B,MAAM,UACJ,WAAW,OAAO,YAAY,YAAY,CAAC,MAAM,QAAQ,QAAQ,GAC7D,EAAE,GAAI,SAAqC,GAC3C,EAAE;EACR,MAAM,aAAa,QAAQ;EAC3B,MAAM,UAAU,MAAM,QAAQ,WAAW,GACrC,CAAC,GAAG,WAAW,QAAQ,MAAmB,OAAO,MAAM,SAAS,CAAC,GACjE,EAAE;AACN,MAAI,CAAC,QAAQ,SAAS,GAAG,CAAE,SAAQ,KAAK,GAAG;EAE3C,MAAM,cAAc,QAAQ;EAC5B,MAAM,UAAmC;GAAE,GAAG;GAAS;GAAS;AAChE,MAAI,MAAM,QAAQ,YAAY,EAAE;GAC9B,MAAM,OAAO,YAAY,QAAQ,MAAmB,OAAO,MAAM,YAAY,MAAM,GAAG;AACtF,OAAI,KAAK,SAAS,EAAG,SAAQ,WAAW;OACnC,QAAO,QAAQ;;AAGtB,SAAO;GACL,GAAG;GACH,YAAY;GACb;;CAGH,uCAA+C,eAAuB,aAA6B;EACjG,MAAM,KAAK,YAAY,MAAM;EAC7B,MAAM,UAAU,cAAc;EAC9B,MAAM,UACJ,WAAW,OAAO,YAAY,YAAY,CAAC,MAAM,QAAQ,QAAQ,GAC7D,EAAE,GAAI,SAAqC,GAC3C,EAAE;EACR,MAAM,aAAa,QAAQ;EAC3B,MAAM,UAAU,MAAM,QAAQ,WAAW,GACrC,WAAW,QAAQ,MAAmB,OAAO,MAAM,YAAY,MAAM,GAAG,GACxE,EAAE;AACN,SAAO;GACL,GAAG;GACH,YAAY;IAAE,GAAG;IAAS;IAAS;GACpC"}
1
+ {"version":3,"file":"marketplace-service.js","names":[],"sources":["../../../../src/gateway/service/marketplace-service.ts"],"sourcesContent":["/**\n * GatewayMarketplaceService — install / browse / remove for the two marketplaces\n * the gateway exposes:\n *\n * • **Skills** (`~/.xopc/skills/managed/<id>`) — zip-bundled markdown skills\n * pulled from a provider catalog (`agent/skills/skills-marketplace.ts`).\n * • **Extensions** (`~/.xopc/extensions/<id>`) — full extension packages from\n * the xopc-store.\n *\n * Owns the install/uninstall composite operations:\n * - download zip → unpack → upsert lockfile → refresh loader → emit reload\n * - rm dir → remove from `extensions.enabled` → refresh loader → emit reload\n *\n * Local-only skill management (install-from-zip, delete, enable/disable, list)\n * lives here too so callers (commands-skills routes) depend on a single narrow\n * service instead of the full `GatewayService`.\n */\nimport { existsSync, mkdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport type { Config } from '../../config/schema.js';\nimport type { AgentService } from '../../agent/service.js';\nimport type { ChannelManager } from '../../channels/manager.js';\nimport type { ExtensionLoader } from '../../extensions/loader.js';\nimport type { SkillCatalogEntry } from '../../agent/agent-manager.js';\nimport type {\n ManagedSkillListItem,\n} from '../../agent/skills/managed-store.js';\nimport type { SkillMarkdownPreviewPayload } from '../../agent/skills/types.js';\nimport type {\n MarketplaceCategoryOption,\n SkillsStoreListParams,\n UnifiedMarketplaceListResponse,\n UnifiedMarketplacePackageDetail,\n} from '../../agent/skills/skills-marketplace.js';\nimport type { MarketplacePackageDetail } from '../../agent/skills/marketplace/adapters/store/store-api-client.js';\nimport {\n deleteManagedSkill as deleteManagedSkillDir,\n installSkillFromZip,\n listManagedSkillDirs,\n} from '../../agent/skills/managed-store.js';\nimport {\n downloadFromMarketplace,\n getMarketplacePackageDetail,\n getMarketplaceProviderDisplayName,\n listMarketplaceCategories,\n listMarketplacePackages,\n listRegisteredProviders,\n resolveSkillsMarketplaceProvider,\n} from '../../agent/skills/skills-marketplace.js';\nimport {\n downloadExtensionStoreZipBuffer,\n fetchMarketplacePackageDetail,\n resolveExtensionZipDownloadUrl,\n resolveExtensionsStoreBaseUrl,\n} from '../../agent/skills/marketplace/adapters/store/store-api-client.js';\nimport { installExtensionFromStoreZip, peekExtensionIdFromStoreZip } from '../../extensions/install.js';\nimport { createSkillConfigManager } from '../../agent/skills/config.js';\nimport { removeSkillsLockEntry } from '../../agent/skills/hub-lock.js';\nimport { getExtensionLockfileManager } from '../../extensions/lockfile.js';\nimport { resolveExtensionsDir, resolveStateDir } from '../../config/paths.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('Gateway:Marketplace');\n\nexport interface GatewayMarketplaceServiceOptions {\n getConfig: () => Config;\n getAgentService: () => AgentService;\n getExtensionLoader: () => ExtensionLoader | null;\n getChannelManager: () => ChannelManager;\n saveConfig: (config: Config) => Promise<{ saved: boolean; error?: string }>;\n emit: (type: string, payload: unknown) => void;\n}\n\nexport class GatewayMarketplaceService {\n private readonly opts: GatewayMarketplaceServiceOptions;\n\n constructor(opts: GatewayMarketplaceServiceOptions) {\n this.opts = opts;\n }\n\n // ── Local skills (managed dir) ────────────────────────────────────────\n\n getSkillsApi(lang?: string): {\n catalog: SkillCatalogEntry[];\n managed: ManagedSkillListItem[];\n } {\n return {\n catalog: this.opts.getAgentService().getSkillCatalog(lang),\n managed: listManagedSkillDirs(),\n };\n }\n\n getSkillMarkdownSource(skillName: string, lang?: string): SkillMarkdownPreviewPayload | null {\n return this.opts.getAgentService().getSkillMarkdownSource(skillName, lang);\n }\n\n deleteSkill(skillId: string): void {\n removeSkillsLockEntry(skillId);\n deleteManagedSkillDir(skillId);\n this.opts.getAgentService().refreshSkillsAfterDiskChange();\n }\n\n installSkillZip(\n buffer: Buffer,\n opts: { skillId?: string; overwrite?: boolean },\n ): { skillId: string; path: string } {\n const result = installSkillFromZip(buffer, opts);\n removeSkillsLockEntry(result.skillId);\n this.opts.getAgentService().refreshSkillsAfterDiskChange();\n return result;\n }\n\n reloadSkills(): void {\n this.opts.getAgentService().refreshSkillsAfterDiskChange();\n }\n\n patchSkillEnabled(skillName: string, enabled: boolean): void {\n createSkillConfigManager(resolveStateDir()).setSkillEnabled(skillName, enabled);\n this.opts.getAgentService().refreshSkillsAfterSkillConfigChange();\n }\n\n // ── Skills marketplace catalog ────────────────────────────────────────\n\n async fetchSkillsCatalog(\n params: SkillsStoreListParams,\n provider?: string,\n ): Promise<UnifiedMarketplaceListResponse> {\n return listMarketplacePackages(this.opts.getConfig(), params, provider);\n }\n\n async fetchSkillsCategories(\n provider?: string,\n ): Promise<{ items: MarketplaceCategoryOption[] }> {\n return listMarketplaceCategories(this.opts.getConfig(), provider);\n }\n\n async fetchSkillsPackageDetail(\n packageName: string,\n provider?: string,\n ): Promise<UnifiedMarketplacePackageDetail> {\n return getMarketplacePackageDetail(this.opts.getConfig(), packageName, provider);\n }\n\n async installSkill(opts: {\n name: string;\n version?: string;\n overwrite?: boolean;\n provider?: string;\n }): Promise<{ skillId: string; path: string }> {\n const { buffer, skillId } = await downloadFromMarketplace(\n this.opts.getConfig(),\n opts.name,\n opts.version,\n opts.provider,\n );\n return this.installSkillZip(buffer, { skillId, overwrite: opts.overwrite ?? false });\n }\n\n getSkillsProvider(): { provider: string; displayName: string } {\n const provider = resolveSkillsMarketplaceProvider(this.opts.getConfig());\n return {\n provider,\n displayName: getMarketplaceProviderDisplayName(provider),\n };\n }\n\n /** All registered marketplace providers (built-in + extension-contributed). */\n getSkillsProviders(): Array<{ id: string; displayName: string }> {\n return listRegisteredProviders();\n }\n\n // ── Extension marketplace ─────────────────────────────────────────────\n\n /** xopc-store extension package preview (type must be `extension`). */\n async fetchExtensionPackageDetail(packageName: string): Promise<MarketplacePackageDetail> {\n const base = resolveExtensionsStoreBaseUrl(this.opts.getConfig());\n const detail = await fetchMarketplacePackageDetail(base, packageName.trim());\n if (detail.type !== 'extension') {\n throw new Error(\n `Package \"${packageName}\" is not an extension (store type: ${detail.type}).`,\n );\n }\n return detail;\n }\n\n /**\n * Install an extension from xopc-store into `~/.xopc/extensions`, append id\n * to `extensions.enabled`, refresh the loader, and emit `config.reload`.\n * Returns `requiresGatewayRestart=true` when a new channel plugin would have\n * to wire into the running gateway (channel registration cannot hot-patch).\n */\n async installExtension(opts: {\n name: string;\n version?: string;\n overwrite?: boolean;\n }): Promise<{ extensionId: string; version: string; requiresGatewayRestart: boolean }> {\n const packageName = opts.name.trim();\n if (!packageName) {\n throw new Error('Package name is required');\n }\n const cfg = this.opts.getConfig();\n const storeBase = resolveExtensionsStoreBaseUrl(cfg);\n const targetDir = resolveExtensionsDir();\n mkdirSync(targetDir, { recursive: true });\n\n const { downloadUrl, version } = await resolveExtensionZipDownloadUrl(\n storeBase,\n packageName,\n opts.version,\n );\n const buf = await downloadExtensionStoreZipBuffer(storeBase, downloadUrl);\n\n if (opts.overwrite) {\n const peekId = peekExtensionIdFromStoreZip(buf);\n if (peekId && existsSync(join(targetDir, peekId))) {\n rmSync(join(targetDir, peekId), { recursive: true, force: true });\n }\n }\n\n const result = await installExtensionFromStoreZip(buf, targetDir);\n if (!result.ok || !result.extensionId) {\n throw new Error(result.error ?? 'Extension install failed');\n }\n\n const lock = getExtensionLockfileManager();\n await lock.upsert(result.extensionId, {\n name: result.extensionId,\n version,\n resolved: packageName,\n source: 'store',\n });\n\n const nextConfig = this.mergeExtensionEnabledIntoConfig(cfg, result.extensionId);\n const saved = await this.opts.saveConfig(nextConfig);\n if (!saved.saved) {\n throw new Error(saved.error ?? 'Failed to save config after extension install');\n }\n\n const channelIdsBefore = new Set(this.opts.getChannelManager().getAllPlugins().map((p) => p.id));\n let requiresGatewayRestart = false;\n const loader = this.opts.getExtensionLoader();\n try {\n if (loader) {\n loader.invalidateManifestCache();\n await loader.loadByActivationPlan();\n const reg = loader.getRegistry();\n for (const p of reg.channelPlugins) {\n if (!channelIdsBefore.has(p.id)) {\n requiresGatewayRestart = true;\n break;\n }\n }\n }\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, errorMessage: em }, `Extension loader refresh after marketplace install failed: ${em}`);\n requiresGatewayRestart = true;\n }\n\n this.opts.emit('config.reload', { section: 'extensions', source: 'marketplace-install' });\n return { extensionId: result.extensionId, version, requiresGatewayRestart };\n }\n\n /** Remove a user-installed extension (global or per-agent dir) from disk and config. */\n async uninstallExtension(extensionId: string): Promise<{ requiresGatewayRestart: boolean }> {\n const id = extensionId.trim();\n if (!id) {\n throw new Error('extensionId is required');\n }\n const loader = this.opts.getExtensionLoader();\n if (!loader) {\n throw new Error('Extensions unavailable');\n }\n const discovered = loader.discoverExtensions();\n const ext = discovered.find((e) => e.id === id);\n if (!ext) {\n throw new Error(`Extension not found: ${id}`);\n }\n if (ext.source === 'bundled') {\n throw new Error('Built-in extensions cannot be uninstalled from the marketplace UI');\n }\n if (existsSync(ext.path)) {\n rmSync(ext.path, { recursive: true, force: true });\n }\n await getExtensionLockfileManager().remove(id);\n\n const nextConfig = this.mergeExtensionRemovedFromEnabledConfig(this.opts.getConfig(), id);\n const saved = await this.opts.saveConfig(nextConfig);\n if (!saved.saved) {\n throw new Error(saved.error ?? 'Failed to save config after extension uninstall');\n }\n try {\n loader.invalidateManifestCache();\n await loader.loadByActivationPlan();\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, errorMessage: em }, `Extension loader refresh after uninstall failed: ${em}`);\n }\n this.opts.emit('config.reload', { section: 'extensions', source: 'marketplace-uninstall' });\n return { requiresGatewayRestart: true };\n }\n\n // ── Internals ─────────────────────────────────────────────────────────\n\n private mergeExtensionEnabledIntoConfig(currentConfig: Config, extensionId: string): Config {\n const id = extensionId.trim();\n const prevExt = currentConfig.extensions;\n const baseExt =\n prevExt && typeof prevExt === 'object' && !Array.isArray(prevExt)\n ? { ...(prevExt as Record<string, unknown>) }\n : {};\n const enabledRaw = baseExt.enabled;\n const enabled = Array.isArray(enabledRaw)\n ? [...enabledRaw.filter((x): x is string => typeof x === 'string')]\n : [];\n if (!enabled.includes(id)) enabled.push(id);\n\n const disabledRaw = baseExt.disabled;\n const nextExt: Record<string, unknown> = { ...baseExt, enabled };\n if (Array.isArray(disabledRaw)) {\n const next = disabledRaw.filter((x): x is string => typeof x === 'string' && x !== id);\n if (next.length > 0) nextExt.disabled = next;\n else delete nextExt.disabled;\n }\n\n return {\n ...currentConfig,\n extensions: nextExt,\n } as Config;\n }\n\n private mergeExtensionRemovedFromEnabledConfig(currentConfig: Config, extensionId: string): Config {\n const id = extensionId.trim();\n const prevExt = currentConfig.extensions;\n const baseExt =\n prevExt && typeof prevExt === 'object' && !Array.isArray(prevExt)\n ? { ...(prevExt as Record<string, unknown>) }\n : {};\n const enabledRaw = baseExt.enabled;\n const enabled = Array.isArray(enabledRaw)\n ? enabledRaw.filter((x): x is string => typeof x === 'string' && x !== id)\n : [];\n return {\n ...currentConfig,\n extensions: { ...baseExt, enabled },\n } as Config;\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { inboundCorrelationMetadataFromAsyncLogContext } from "../../utils/logger/context.js";
1
+ import { inboundCorrelationMetadataFromAsyncLogContext, updateAsyncLogContext } from "../../utils/logger/context.js";
2
2
  import { createLogger } from "../../utils/logger/index.js";
3
3
  import { init_logger } from "../../utils/logger.js";
4
4
  import { formatAgentRunErrorForClient } from "../../agent/client-error-format.js";
@@ -9,7 +9,7 @@ import { resolveWebchatSessionKey } from "../resolve-webchat-session-key.js";
9
9
  import crypto from "crypto";
10
10
  //#region src/gateway/service/run-gateway-agent.ts
11
11
  init_logger();
12
- const log = createLogger("GatewayService");
12
+ const log = createLogger("Gateway:Service");
13
13
  /**
14
14
  * @param runOptions.signal — When set (e.g. client disconnect), aborts in-flight generation and persists partial output.
15
15
  */
@@ -56,6 +56,7 @@ async function* runGatewayAgent(deps, message, channel, chatId, attachments, thi
56
56
  };
57
57
  }
58
58
  const sessionKey = webchatSessionKey;
59
+ updateAsyncLogContext({ sessionId: sessionKey });
59
60
  const timezone = agentService.resolveUserTimezoneForSession(sessionKey);
60
61
  const stampedMessage = message.trimStart().startsWith("/") ? message : prependEnvelopeTimestamp(message, timezone);
61
62
  const prepared = await agentService.prepareInboundAttachments(sessionKey, cappedAttachments);
@@ -92,8 +93,16 @@ async function* runGatewayAgent(deps, message, channel, chatId, attachments, thi
92
93
  summary: mergedSignal.aborted ? "Interrupted" : "Message processed successfully"
93
94
  };
94
95
  } catch (error) {
95
- log.error({ error }, "Agent processing failed");
96
- streamError = error instanceof Error ? error.message : "Unknown error";
96
+ const em = error instanceof Error ? error.message : String(error);
97
+ log.error({
98
+ err: error,
99
+ errorMessage: em,
100
+ phase: "gateway.agent_run",
101
+ sessionKey,
102
+ runId,
103
+ channel: "webchat"
104
+ }, `Agent processing failed: ${em}`);
105
+ streamError = em;
97
106
  const errorEvent = {
98
107
  type: "error",
99
108
  content: formatAgentRunErrorForClient(streamError)
@@ -157,7 +166,15 @@ async function* runGatewayAgent(deps, message, channel, chatId, attachments, thi
157
166
  summary: "Message processed"
158
167
  };
159
168
  } catch (error) {
160
- log.error({ error }, "Agent run failed");
169
+ const em = error instanceof Error ? error.message : String(error);
170
+ log.error({
171
+ err: error,
172
+ errorMessage: em,
173
+ phase: "gateway.agent_run",
174
+ runId,
175
+ channel,
176
+ chatId
177
+ }, `Agent run failed: ${em}`);
161
178
  throw error;
162
179
  }
163
180
  }
@@ -1 +1 @@
1
- {"version":3,"file":"run-gateway-agent.js","names":[],"sources":["../../../../src/gateway/service/run-gateway-agent.ts"],"sourcesContent":["import crypto from 'crypto';\n\nimport type { AgentService } from '../../agent/service.js';\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport { prependEnvelopeTimestamp } from '../../channels/envelope-timestamp.js';\nimport { resolveWebchatSessionKey } from '../resolve-webchat-session-key.js';\nimport type { SessionIndex } from '../../session/index.js';\nimport {\n createLogger,\n inboundCorrelationMetadataFromAsyncLogContext,\n} from '../../utils/logger.js';\nimport { shouldSkipWebchatInboundByAbortCutoff } from '../../session/abort-cutoff.js';\n\nimport { formatAgentRunErrorForClient } from '../../agent/client-error-format.js';\n\nimport type { AgentRunRelay } from '../agent-run-relay.js';\nimport { MAX_CHAT_ATTACHMENTS } from '../chat-limits.js';\nconst log = createLogger('GatewayService');\n\nexport type RunGatewayAgentYield = {\n type: string;\n content?: string;\n status?: string;\n runId?: string;\n};\n\nexport type RunGatewayAgentDeps = {\n config: Config;\n agentService: AgentService;\n bus: MessageBus;\n runRelay: AgentRunRelay;\n runAbortControllers: Map<string, AbortController>;\n activeWebchatRunBySession: Map<string, string>;\n sessionIndex: SessionIndex;\n emit: (type: string, payload: unknown) => void;\n};\n\n/**\n * @param runOptions.signal — When set (e.g. client disconnect), aborts in-flight generation and persists partial output.\n */\nexport async function *runGatewayAgent(\n deps: RunGatewayAgentDeps,\n message: string,\n channel: string,\n chatId: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n }>,\n thinking?: string,\n runOptions?: { signal?: AbortSignal; clientCreatedAtMs?: number },\n): AsyncGenerator<RunGatewayAgentYield, { status: string; summary: string }, unknown> {\n const cappedAttachments =\n attachments && attachments.length > MAX_CHAT_ATTACHMENTS\n ? attachments.slice(0, MAX_CHAT_ATTACHMENTS)\n : attachments;\n if (attachments && cappedAttachments && attachments.length > cappedAttachments.length) {\n log.debug(\n { dropped: attachments.length - cappedAttachments.length, max: MAX_CHAT_ATTACHMENTS },\n 'Attachments capped for webchat',\n );\n }\n\n const runId = crypto.randomUUID();\n const {\n config,\n agentService,\n bus,\n runRelay,\n runAbortControllers,\n activeWebchatRunBySession,\n sessionIndex: sessionIndexFromDeps,\n emit,\n } = deps;\n const sessionIndex = sessionIndexFromDeps;\n\n let webchatSessionKey: string | undefined;\n let webchatStaleSkip = false;\n if (channel === 'webchat') {\n const resolved = resolveWebchatSessionKey({ cfg: config, chatId, newSession: false });\n if (resolved.ok === false) {\n throw new Error(resolved.error);\n }\n webchatSessionKey = resolved.sessionKey;\n const meta = await sessionIndex.getSessionMetadata(webchatSessionKey);\n webchatStaleSkip = shouldSkipWebchatInboundByAbortCutoff(meta, runOptions?.clientCreatedAtMs);\n if (!webchatStaleSkip && meta?.abortCutoffTimestamp !== undefined) {\n await sessionIndex\n .updateSessionMetadata(webchatSessionKey, { abortCutoffTimestamp: undefined })\n .catch(() => {});\n }\n runRelay.ensureRun(runId, webchatSessionKey);\n runAbortControllers.set(runId, new AbortController());\n }\n\n const statusEvent = { type: 'status', status: 'accepted', runId };\n if (channel === 'webchat') runRelay.publish(runId, statusEvent);\n yield statusEvent;\n\n try {\n if (channel === 'webchat' && webchatSessionKey) {\n if (webchatStaleSkip) {\n runRelay.complete(runId);\n runAbortControllers.delete(runId);\n return {\n status: 'skipped',\n summary: 'Stale inbound after abort (clientCreatedAtMs before cutoff)',\n };\n }\n\n const sessionKey = webchatSessionKey;\n\n const timezone = agentService.resolveUserTimezoneForSession(sessionKey);\n const stampedMessage = message.trimStart().startsWith('/')\n ? message\n : prependEnvelopeTimestamp(message, timezone);\n const prepared = await agentService.prepareInboundAttachments(sessionKey, cappedAttachments);\n\n const runAbort = runAbortControllers.get(runId);\n if (!runAbort) {\n throw new Error('run abort controller missing for webchat');\n }\n const mergedSignal = runOptions?.signal\n ? AbortSignal.any([runOptions.signal, runAbort.signal])\n : runAbort.signal;\n\n agentService.beginInboundTurn(sessionKey);\n activeWebchatRunBySession.set(sessionKey, runId);\n let streamError: string | undefined;\n try {\n emit('agent.stream', { sessionKey, event: statusEvent });\n const eventStream = agentService.turnDispatcher.processDirectStreaming(\n stampedMessage,\n sessionKey,\n prepared,\n thinking,\n { signal: mergedSignal },\n );\n\n for await (const event of eventStream) {\n runRelay.publish(runId, event);\n emit('agent.stream', { sessionKey, event });\n yield event as RunGatewayAgentYield;\n }\n\n runRelay.complete(runId);\n try {\n const metaAfter = await sessionIndex.getSessionMetadata(sessionKey);\n if (metaAfter?.name) {\n emit('session.updated', { key: sessionKey, name: metaAfter.name });\n }\n } catch {\n /* ignore */\n }\n return {\n status: mergedSignal.aborted ? 'aborted' : 'ok',\n summary: mergedSignal.aborted ? 'Interrupted' : 'Message processed successfully',\n };\n } catch (error) {\n log.error({ error }, 'Agent processing failed');\n streamError = error instanceof Error ? error.message : 'Unknown error';\n const errorContent = formatAgentRunErrorForClient(streamError);\n const errorEvent = { type: 'error', content: errorContent };\n runRelay.publish(runId, errorEvent);\n emit('agent.stream', { sessionKey, event: errorEvent });\n runRelay.complete(runId);\n yield errorEvent;\n return { status: 'error', summary: streamError };\n } finally {\n activeWebchatRunBySession.delete(sessionKey);\n runAbortControllers.delete(runId);\n const assistantPlainText = agentService.getLastAssistantPlainText(sessionKey);\n const streamOutcome = agentService.persistentGoals.takeStreamOutcome(sessionKey);\n try {\n await agentService.outboundCoordinator.emitSessionTurnComplete({\n sessionKey,\n channel: 'webchat',\n chatId: sessionKey,\n inboundUserText: message,\n assistantPlainText,\n aborted: mergedSignal.aborted,\n ...(streamError !== undefined ? { streamError } : {}),\n skipPersistentGoalPostTurn: streamOutcome?.skipPersistentGoalPostTurn ?? false,\n outboundMetadata: {},\n });\n } catch (goalErr) {\n log.warn(\n { err: goalErr, sessionKey },\n `Session turn complete failed: ${goalErr instanceof Error ? goalErr.message : String(goalErr)}`,\n );\n }\n agentService.endInboundTurn(sessionKey);\n }\n }\n\n const correlationMeta = inboundCorrelationMetadataFromAsyncLogContext();\n await bus.publishInbound({\n channel,\n sender_id: 'gateway',\n chat_id: chatId,\n content: message,\n ...(correlationMeta ? { metadata: correlationMeta } : {}),\n });\n\n yield { type: 'token', content: 'Processing...\\n' };\n await new Promise((resolve) => setTimeout(resolve, 1000));\n yield { type: 'token', content: 'Done\\n' };\n return { status: 'ok', summary: 'Message processed' };\n } catch (error) {\n log.error({ error }, 'Agent run failed');\n throw error;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"run-gateway-agent.js","names":[],"sources":["../../../../src/gateway/service/run-gateway-agent.ts"],"sourcesContent":["import crypto from 'crypto';\n\nimport type { AgentService } from '../../agent/service.js';\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport { prependEnvelopeTimestamp } from '../../channels/envelope-timestamp.js';\nimport { resolveWebchatSessionKey } from '../resolve-webchat-session-key.js';\nimport type { SessionIndex } from '../../session/index.js';\nimport {\n createLogger,\n inboundCorrelationMetadataFromAsyncLogContext,\n updateAsyncLogContext,\n} from '../../utils/logger.js';\nimport { shouldSkipWebchatInboundByAbortCutoff } from '../../session/abort-cutoff.js';\n\nimport { formatAgentRunErrorForClient } from '../../agent/client-error-format.js';\n\nimport type { AgentRunRelay } from '../agent-run-relay.js';\nimport { MAX_CHAT_ATTACHMENTS } from '../chat-limits.js';\nconst log = createLogger('Gateway:Service');\n\nexport type RunGatewayAgentYield = {\n type: string;\n content?: string;\n status?: string;\n runId?: string;\n};\n\nexport type RunGatewayAgentDeps = {\n config: Config;\n agentService: AgentService;\n bus: MessageBus;\n runRelay: AgentRunRelay;\n runAbortControllers: Map<string, AbortController>;\n activeWebchatRunBySession: Map<string, string>;\n sessionIndex: SessionIndex;\n emit: (type: string, payload: unknown) => void;\n};\n\n/**\n * @param runOptions.signal — When set (e.g. client disconnect), aborts in-flight generation and persists partial output.\n */\nexport async function *runGatewayAgent(\n deps: RunGatewayAgentDeps,\n message: string,\n channel: string,\n chatId: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n }>,\n thinking?: string,\n runOptions?: { signal?: AbortSignal; clientCreatedAtMs?: number },\n): AsyncGenerator<RunGatewayAgentYield, { status: string; summary: string }, unknown> {\n const cappedAttachments =\n attachments && attachments.length > MAX_CHAT_ATTACHMENTS\n ? attachments.slice(0, MAX_CHAT_ATTACHMENTS)\n : attachments;\n if (attachments && cappedAttachments && attachments.length > cappedAttachments.length) {\n log.debug(\n { dropped: attachments.length - cappedAttachments.length, max: MAX_CHAT_ATTACHMENTS },\n 'Attachments capped for webchat',\n );\n }\n\n const runId = crypto.randomUUID();\n const {\n config,\n agentService,\n bus,\n runRelay,\n runAbortControllers,\n activeWebchatRunBySession,\n sessionIndex: sessionIndexFromDeps,\n emit,\n } = deps;\n const sessionIndex = sessionIndexFromDeps;\n\n let webchatSessionKey: string | undefined;\n let webchatStaleSkip = false;\n if (channel === 'webchat') {\n const resolved = resolveWebchatSessionKey({ cfg: config, chatId, newSession: false });\n if (resolved.ok === false) {\n throw new Error(resolved.error);\n }\n webchatSessionKey = resolved.sessionKey;\n const meta = await sessionIndex.getSessionMetadata(webchatSessionKey);\n webchatStaleSkip = shouldSkipWebchatInboundByAbortCutoff(meta, runOptions?.clientCreatedAtMs);\n if (!webchatStaleSkip && meta?.abortCutoffTimestamp !== undefined) {\n await sessionIndex\n .updateSessionMetadata(webchatSessionKey, { abortCutoffTimestamp: undefined })\n .catch(() => {});\n }\n runRelay.ensureRun(runId, webchatSessionKey);\n runAbortControllers.set(runId, new AbortController());\n }\n\n const statusEvent = { type: 'status', status: 'accepted', runId };\n if (channel === 'webchat') runRelay.publish(runId, statusEvent);\n yield statusEvent;\n\n try {\n if (channel === 'webchat' && webchatSessionKey) {\n if (webchatStaleSkip) {\n runRelay.complete(runId);\n runAbortControllers.delete(runId);\n return {\n status: 'skipped',\n summary: 'Stale inbound after abort (clientCreatedAtMs before cutoff)',\n };\n }\n\n const sessionKey = webchatSessionKey;\n updateAsyncLogContext({ sessionId: sessionKey });\n\n const timezone = agentService.resolveUserTimezoneForSession(sessionKey);\n const stampedMessage = message.trimStart().startsWith('/')\n ? message\n : prependEnvelopeTimestamp(message, timezone);\n const prepared = await agentService.prepareInboundAttachments(sessionKey, cappedAttachments);\n\n const runAbort = runAbortControllers.get(runId);\n if (!runAbort) {\n throw new Error('run abort controller missing for webchat');\n }\n const mergedSignal = runOptions?.signal\n ? AbortSignal.any([runOptions.signal, runAbort.signal])\n : runAbort.signal;\n\n agentService.beginInboundTurn(sessionKey);\n activeWebchatRunBySession.set(sessionKey, runId);\n let streamError: string | undefined;\n try {\n emit('agent.stream', { sessionKey, event: statusEvent });\n const eventStream = agentService.turnDispatcher.processDirectStreaming(\n stampedMessage,\n sessionKey,\n prepared,\n thinking,\n { signal: mergedSignal },\n );\n\n for await (const event of eventStream) {\n runRelay.publish(runId, event);\n emit('agent.stream', { sessionKey, event });\n yield event as RunGatewayAgentYield;\n }\n\n runRelay.complete(runId);\n try {\n const metaAfter = await sessionIndex.getSessionMetadata(sessionKey);\n if (metaAfter?.name) {\n emit('session.updated', { key: sessionKey, name: metaAfter.name });\n }\n } catch {\n /* ignore */\n }\n return {\n status: mergedSignal.aborted ? 'aborted' : 'ok',\n summary: mergedSignal.aborted ? 'Interrupted' : 'Message processed successfully',\n };\n } catch (error) {\n const em = error instanceof Error ? error.message : String(error);\n log.error(\n {\n err: error,\n errorMessage: em,\n phase: 'gateway.agent_run',\n sessionKey,\n runId,\n channel: 'webchat',\n },\n `Agent processing failed: ${em}`,\n );\n streamError = em;\n const errorContent = formatAgentRunErrorForClient(streamError);\n const errorEvent = { type: 'error', content: errorContent };\n runRelay.publish(runId, errorEvent);\n emit('agent.stream', { sessionKey, event: errorEvent });\n runRelay.complete(runId);\n yield errorEvent;\n return { status: 'error', summary: streamError };\n } finally {\n activeWebchatRunBySession.delete(sessionKey);\n runAbortControllers.delete(runId);\n const assistantPlainText = agentService.getLastAssistantPlainText(sessionKey);\n const streamOutcome = agentService.persistentGoals.takeStreamOutcome(sessionKey);\n try {\n await agentService.outboundCoordinator.emitSessionTurnComplete({\n sessionKey,\n channel: 'webchat',\n chatId: sessionKey,\n inboundUserText: message,\n assistantPlainText,\n aborted: mergedSignal.aborted,\n ...(streamError !== undefined ? { streamError } : {}),\n skipPersistentGoalPostTurn: streamOutcome?.skipPersistentGoalPostTurn ?? false,\n outboundMetadata: {},\n });\n } catch (goalErr) {\n log.warn(\n { err: goalErr, sessionKey },\n `Session turn complete failed: ${goalErr instanceof Error ? goalErr.message : String(goalErr)}`,\n );\n }\n agentService.endInboundTurn(sessionKey);\n }\n }\n\n const correlationMeta = inboundCorrelationMetadataFromAsyncLogContext();\n await bus.publishInbound({\n channel,\n sender_id: 'gateway',\n chat_id: chatId,\n content: message,\n ...(correlationMeta ? { metadata: correlationMeta } : {}),\n });\n\n yield { type: 'token', content: 'Processing...\\n' };\n await new Promise((resolve) => setTimeout(resolve, 1000));\n yield { type: 'token', content: 'Done\\n' };\n return { status: 'ok', summary: 'Message processed' };\n } catch (error) {\n const em = error instanceof Error ? error.message : String(error);\n log.error(\n {\n err: error,\n errorMessage: em,\n phase: 'gateway.agent_run',\n runId,\n channel,\n chatId,\n },\n `Agent run failed: ${em}`,\n );\n throw error;\n }\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  //#region src/gateway/service/sse-hub.ts
4
4
  init_logger();
5
- const log = createLogger("GatewayService");
5
+ const log = createLogger("Gateway:Service");
6
6
  const EVENT_BUFFER_SIZE = 200;
7
7
  var GatewaySseHub = class {
8
8
  eventCounter = 0;
@@ -1 +1 @@
1
- {"version":3,"file":"sse-hub.js","names":[],"sources":["../../../../src/gateway/service/sse-hub.ts"],"sourcesContent":["import { createLogger } from '../../utils/logger.js';\n\nimport type { ServiceEvent } from './types.js';\n\nconst log = createLogger('GatewayService');\n\nconst EVENT_BUFFER_SIZE = 200;\n\nexport type GatewaySseListener = (event: ServiceEvent) => Promise<void> | void;\n\nexport class GatewaySseHub {\n private eventCounter = 0;\n private subscribers = new Map<string, GatewaySseListener>();\n private eventBuffers = new Map<string, ServiceEvent[]>();\n\n subscribe(sessionId: string, listener: GatewaySseListener): () => void {\n this.subscribers.set(sessionId, listener);\n if (!this.eventBuffers.has(sessionId)) {\n this.eventBuffers.set(sessionId, []);\n }\n log.debug({ sessionId }, 'Event subscriber added');\n\n return () => {\n this.subscribers.delete(sessionId);\n setTimeout(() => {\n if (!this.subscribers.has(sessionId)) {\n this.eventBuffers.delete(sessionId);\n }\n }, 5 * 60_000);\n log.debug({ sessionId }, 'Event subscriber removed');\n };\n }\n\n emit(type: string, payload: unknown): void {\n const id = String(++this.eventCounter);\n const event: ServiceEvent = { id, type, payload };\n\n for (const [sessionId, listener] of this.subscribers) {\n const buf = this.eventBuffers.get(sessionId) || [];\n buf.push(event);\n if (buf.length > EVENT_BUFFER_SIZE) buf.shift();\n this.eventBuffers.set(sessionId, buf);\n\n try {\n listener(event);\n } catch (err) {\n log.warn({ sessionId, err }, 'Failed to deliver event to subscriber');\n }\n }\n }\n\n getEventsSince(sessionId: string, lastEventId: string): ServiceEvent[] {\n const buf = this.eventBuffers.get(sessionId);\n if (!buf) return [];\n\n const idx = buf.findIndex((e) => e.id === lastEventId);\n if (idx === -1) return buf;\n return buf.slice(idx + 1);\n }\n}\n"],"mappings":";;;aAAqD;AAIrD,MAAM,MAAM,aAAa,iBAAiB;AAE1C,MAAM,oBAAoB;AAI1B,IAAa,gBAAb,MAA2B;CACzB,eAAuB;CACvB,8BAAsB,IAAI,KAAiC;CAC3D,+BAAuB,IAAI,KAA6B;CAExD,UAAU,WAAmB,UAA0C;AACrE,OAAK,YAAY,IAAI,WAAW,SAAS;AACzC,MAAI,CAAC,KAAK,aAAa,IAAI,UAAU,CACnC,MAAK,aAAa,IAAI,WAAW,EAAE,CAAC;AAEtC,MAAI,MAAM,EAAE,WAAW,EAAE,yBAAyB;AAElD,eAAa;AACX,QAAK,YAAY,OAAO,UAAU;AAClC,oBAAiB;AACf,QAAI,CAAC,KAAK,YAAY,IAAI,UAAU,CAClC,MAAK,aAAa,OAAO,UAAU;MAEpC,IAAI,IAAO;AACd,OAAI,MAAM,EAAE,WAAW,EAAE,2BAA2B;;;CAIxD,KAAK,MAAc,SAAwB;EAEzC,MAAM,QAAsB;GAAE,IADnB,OAAO,EAAE,KAAK,aACO;GAAE;GAAM;GAAS;AAEjD,OAAK,MAAM,CAAC,WAAW,aAAa,KAAK,aAAa;GACpD,MAAM,MAAM,KAAK,aAAa,IAAI,UAAU,IAAI,EAAE;AAClD,OAAI,KAAK,MAAM;AACf,OAAI,IAAI,SAAS,kBAAmB,KAAI,OAAO;AAC/C,QAAK,aAAa,IAAI,WAAW,IAAI;AAErC,OAAI;AACF,aAAS,MAAM;YACR,KAAK;AACZ,QAAI,KAAK;KAAE;KAAW;KAAK,EAAE,wCAAwC;;;;CAK3E,eAAe,WAAmB,aAAqC;EACrE,MAAM,MAAM,KAAK,aAAa,IAAI,UAAU;AAC5C,MAAI,CAAC,IAAK,QAAO,EAAE;EAEnB,MAAM,MAAM,IAAI,WAAW,MAAM,EAAE,OAAO,YAAY;AACtD,MAAI,QAAQ,GAAI,QAAO;AACvB,SAAO,IAAI,MAAM,MAAM,EAAE"}
1
+ {"version":3,"file":"sse-hub.js","names":[],"sources":["../../../../src/gateway/service/sse-hub.ts"],"sourcesContent":["import { createLogger } from '../../utils/logger.js';\n\nimport type { ServiceEvent } from './types.js';\n\nconst log = createLogger('Gateway:Service');\n\nconst EVENT_BUFFER_SIZE = 200;\n\nexport type GatewaySseListener = (event: ServiceEvent) => Promise<void> | void;\n\nexport class GatewaySseHub {\n private eventCounter = 0;\n private subscribers = new Map<string, GatewaySseListener>();\n private eventBuffers = new Map<string, ServiceEvent[]>();\n\n subscribe(sessionId: string, listener: GatewaySseListener): () => void {\n this.subscribers.set(sessionId, listener);\n if (!this.eventBuffers.has(sessionId)) {\n this.eventBuffers.set(sessionId, []);\n }\n log.debug({ sessionId }, 'Event subscriber added');\n\n return () => {\n this.subscribers.delete(sessionId);\n setTimeout(() => {\n if (!this.subscribers.has(sessionId)) {\n this.eventBuffers.delete(sessionId);\n }\n }, 5 * 60_000);\n log.debug({ sessionId }, 'Event subscriber removed');\n };\n }\n\n emit(type: string, payload: unknown): void {\n const id = String(++this.eventCounter);\n const event: ServiceEvent = { id, type, payload };\n\n for (const [sessionId, listener] of this.subscribers) {\n const buf = this.eventBuffers.get(sessionId) || [];\n buf.push(event);\n if (buf.length > EVENT_BUFFER_SIZE) buf.shift();\n this.eventBuffers.set(sessionId, buf);\n\n try {\n listener(event);\n } catch (err) {\n log.warn({ sessionId, err }, 'Failed to deliver event to subscriber');\n }\n }\n }\n\n getEventsSince(sessionId: string, lastEventId: string): ServiceEvent[] {\n const buf = this.eventBuffers.get(sessionId);\n if (!buf) return [];\n\n const idx = buf.findIndex((e) => e.id === lastEventId);\n if (idx === -1) return buf;\n return buf.slice(idx + 1);\n }\n}\n"],"mappings":";;;aAAqD;AAIrD,MAAM,MAAM,aAAa,kBAAkB;AAE3C,MAAM,oBAAoB;AAI1B,IAAa,gBAAb,MAA2B;CACzB,eAAuB;CACvB,8BAAsB,IAAI,KAAiC;CAC3D,+BAAuB,IAAI,KAA6B;CAExD,UAAU,WAAmB,UAA0C;AACrE,OAAK,YAAY,IAAI,WAAW,SAAS;AACzC,MAAI,CAAC,KAAK,aAAa,IAAI,UAAU,CACnC,MAAK,aAAa,IAAI,WAAW,EAAE,CAAC;AAEtC,MAAI,MAAM,EAAE,WAAW,EAAE,yBAAyB;AAElD,eAAa;AACX,QAAK,YAAY,OAAO,UAAU;AAClC,oBAAiB;AACf,QAAI,CAAC,KAAK,YAAY,IAAI,UAAU,CAClC,MAAK,aAAa,OAAO,UAAU;MAEpC,IAAI,IAAO;AACd,OAAI,MAAM,EAAE,WAAW,EAAE,2BAA2B;;;CAIxD,KAAK,MAAc,SAAwB;EAEzC,MAAM,QAAsB;GAAE,IADnB,OAAO,EAAE,KAAK,aACO;GAAE;GAAM;GAAS;AAEjD,OAAK,MAAM,CAAC,WAAW,aAAa,KAAK,aAAa;GACpD,MAAM,MAAM,KAAK,aAAa,IAAI,UAAU,IAAI,EAAE;AAClD,OAAI,KAAK,MAAM;AACf,OAAI,IAAI,SAAS,kBAAmB,KAAI,OAAO;AAC/C,QAAK,aAAa,IAAI,WAAW,IAAI;AAErC,OAAI;AACF,aAAS,MAAM;YACR,KAAK;AACZ,QAAI,KAAK;KAAE;KAAW;KAAK,EAAE,wCAAwC;;;;CAK3E,eAAe,WAAmB,aAAqC;EACrE,MAAM,MAAM,KAAK,aAAa,IAAI,UAAU;AAC5C,MAAI,CAAC,IAAK,QAAO,EAAE;EAEnB,MAAM,MAAM,IAAI,WAAW,MAAM,EAAE,OAAO,YAAY;AACtD,MAAI,QAAQ,GAAI,QAAO;AACvB,SAAO,IAAI,MAAM,MAAM,EAAE"}
@@ -1,12 +1,12 @@
1
1
  import { __toCommonJS } from "../../_virtual/_rolldown/runtime.js";
2
2
  import { PACKAGE_VERSION, init_package_version } from "../package-version.js";
3
+ import { getDefaultAgentId, init_resolve_route } from "../routing/resolve-route.js";
3
4
  import { getLogDir } from "../utils/logger/config.js";
4
- import { getLogStats } from "../utils/logger/stats.js";
5
+ import { getRuntimeLogStats } from "../utils/logger/stats.js";
5
6
  import { createLogger } from "../utils/logger/index.js";
6
7
  import { init_logger } from "../utils/logger.js";
7
8
  import { init_paths, resolveAgentDir, resolveConfigPath, resolveCronJobsPath, resolveExtensionsDir } from "../config/paths.js";
8
9
  import { loadConfig, saveConfig } from "../config/loader.js";
9
- import { getDefaultAgentId, init_resolve_route } from "../routing/resolve-route.js";
10
10
  import { prewarmModelRegistry } from "../providers/model-registry.js";
11
11
  import { init_providers, providers_exports } from "../providers/index.js";
12
12
  import { resolveEffectiveGatewayPort } from "./host.js";
@@ -65,7 +65,7 @@ init_logger();
65
65
  init_paths();
66
66
  init_package_version();
67
67
  init_resolve_route();
68
- const log = createLogger("GatewayService");
68
+ const log = createLogger("Gateway:Service");
69
69
  var GatewayService = class {
70
70
  bus;
71
71
  config;
@@ -224,8 +224,15 @@ var GatewayService = class {
224
224
  model: this.config.agents?.defaults?.model?.primary,
225
225
  config: this.config,
226
226
  sessionStore: this.sessionIndex.getStore(),
227
- onSessionMetadataUpdated: (sessionKey) => {
228
- this.sessionIndex.emit("sessionUpdated", { key: sessionKey });
227
+ onSessionMetadataUpdated: (sessionKey, patch) => {
228
+ this.sessionIndex.emit("sessionUpdated", {
229
+ key: sessionKey,
230
+ name: patch?.name
231
+ });
232
+ this.emit("session.updated", {
233
+ key: sessionKey,
234
+ name: patch?.name
235
+ });
229
236
  },
230
237
  onSessionTranscriptUpdated: (sessionKey) => {
231
238
  this.emit("session.transcript_updated", { key: sessionKey });
@@ -865,7 +872,7 @@ var GatewayService = class {
865
872
  getHealth() {
866
873
  const runningChannels = this.channelManager.getRunningChannels();
867
874
  const allChannels = this.channelManager.getAllChannels();
868
- const logStats = getLogStats();
875
+ const logStats = getRuntimeLogStats();
869
876
  const readiness = this.readiness.getSnapshot();
870
877
  return {
871
878
  status: "ok",