@xopcai/xopc 0.0.27 → 0.0.29

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 (239) hide show
  1. package/dist/extensions/telegram/xopc.extension.json +1 -1
  2. package/dist/extensions/weixin/src/adapters/onboard-cli.d.ts +7 -0
  3. package/dist/extensions/weixin/src/adapters/onboard-cli.js +61 -0
  4. package/dist/extensions/weixin/src/adapters/onboard-cli.js.map +1 -0
  5. package/dist/extensions/weixin/src/cli/qr-login.d.ts +5 -0
  6. package/dist/extensions/weixin/src/cli/qr-login.js +1 -1
  7. package/dist/extensions/weixin/src/cli/qr-login.js.map +1 -1
  8. package/dist/extensions/weixin/src/index.js +1 -1
  9. package/dist/extensions/weixin/src/plugin.d.ts +1 -0
  10. package/dist/extensions/weixin/src/plugin.js +2 -0
  11. package/dist/extensions/weixin/src/plugin.js.map +1 -1
  12. package/dist/gateway/static/root/assets/agents-CkgFSiCY.js +216 -0
  13. package/dist/gateway/static/root/assets/agents-CkgFSiCY.js.map +1 -0
  14. package/dist/gateway/static/root/assets/{apps-page-CBBh_Ww8.js → apps-page-Bmq19MS-.js} +2 -2
  15. package/dist/gateway/static/root/assets/{apps-page-CBBh_Ww8.js.map → apps-page-Bmq19MS-.js.map} +1 -1
  16. package/dist/gateway/static/root/assets/channels-settings-CE7jrdkO.js +9 -0
  17. package/dist/gateway/static/root/assets/channels-settings-CE7jrdkO.js.map +1 -0
  18. package/dist/gateway/static/root/assets/cron-page-BpPPcykJ.js +2 -0
  19. package/dist/gateway/static/root/assets/cron-page-BpPPcykJ.js.map +1 -0
  20. package/dist/gateway/static/root/assets/{cron-utils-08gdQfl9.js → cron-utils-N1PqD2DB.js} +2 -2
  21. package/dist/gateway/static/root/assets/{cron-utils-08gdQfl9.js.map → cron-utils-N1PqD2DB.js.map} +1 -1
  22. package/dist/gateway/static/root/assets/{dist-C1MrygQH.js → dist--p2HQ2QF.js} +2 -2
  23. package/dist/gateway/static/root/assets/{dist-C1MrygQH.js.map → dist--p2HQ2QF.js.map} +1 -1
  24. package/dist/gateway/static/root/assets/{extension-debug-page-DN3HKUGS.js → extension-debug-page-DwHCB_6T.js} +2 -2
  25. package/dist/gateway/static/root/assets/{extension-debug-page-DN3HKUGS.js.map → extension-debug-page-DwHCB_6T.js.map} +1 -1
  26. package/dist/gateway/static/root/assets/{extension-page-CoFDHZtZ.js → extension-page-BsYwQIex.js} +2 -2
  27. package/dist/gateway/static/root/assets/{extension-page-CoFDHZtZ.js.map → extension-page-BsYwQIex.js.map} +1 -1
  28. package/dist/gateway/static/root/assets/{extension-settings-page-BcPCu_Go.js → extension-settings-page-nsisEgjB.js} +2 -2
  29. package/dist/gateway/static/root/assets/{extension-settings-page-BcPCu_Go.js.map → extension-settings-page-nsisEgjB.js.map} +1 -1
  30. package/dist/gateway/static/root/assets/index-CR8zUHGR.js +4734 -0
  31. package/dist/gateway/static/root/assets/{index-PfkB8N37.js.map → index-CR8zUHGR.js.map} +1 -1
  32. package/dist/gateway/static/root/assets/index-Dnfha4O2.css +1 -0
  33. package/dist/gateway/static/root/assets/logs-page-CQwdV_Xw.js +2 -0
  34. package/dist/gateway/static/root/assets/logs-page-CQwdV_Xw.js.map +1 -0
  35. package/dist/gateway/static/root/assets/sessions-page-Be5kIGl_.js +2 -0
  36. package/dist/gateway/static/root/assets/sessions-page-Be5kIGl_.js.map +1 -0
  37. package/dist/gateway/static/root/assets/settings-page-PodSlNwr.js +2 -0
  38. package/dist/gateway/static/root/assets/settings-page-PodSlNwr.js.map +1 -0
  39. package/dist/gateway/static/root/assets/skills-page-Clg8deH0.js +3 -0
  40. package/dist/gateway/static/root/assets/{skills-page-BmBDCEbY.js.map → skills-page-Clg8deH0.js.map} +1 -1
  41. package/dist/gateway/static/root/index.html +2 -2
  42. package/dist/package.js +1 -1
  43. package/dist/src/agent/lifecycle/hook-handler.d.ts +2 -0
  44. package/dist/src/agent/lifecycle/hook-handler.js +24 -0
  45. package/dist/src/agent/lifecycle/hook-handler.js.map +1 -1
  46. package/dist/src/agent/messaging/command-handler.js +10 -2
  47. package/dist/src/agent/messaging/command-handler.js.map +1 -1
  48. package/dist/src/agent/service/process-direct-streaming.js +77 -20
  49. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  50. package/dist/src/agent/service.d.ts +15 -0
  51. package/dist/src/agent/service.js +21 -1
  52. package/dist/src/agent/service.js.map +1 -1
  53. package/dist/src/channels/weixin/index.js +1 -1
  54. package/dist/src/cli/agent-chat-log-level-preset.d.ts +8 -0
  55. package/dist/src/cli/agent-chat-log-level-preset.js +25 -0
  56. package/dist/src/cli/agent-chat-log-level-preset.js.map +1 -0
  57. package/dist/src/cli/commands/agent/interactive.js +4 -2
  58. package/dist/src/cli/commands/agent/interactive.js.map +1 -1
  59. package/dist/src/cli/commands/agent/stream-renderer.d.ts +14 -0
  60. package/dist/src/cli/commands/agent/stream-renderer.js +99 -0
  61. package/dist/src/cli/commands/agent/stream-renderer.js.map +1 -0
  62. package/dist/src/cli/commands/agent.js +2 -2
  63. package/dist/src/cli/commands/agent.js.map +1 -1
  64. package/dist/src/cli/commands/onboard.js +77 -93
  65. package/dist/src/cli/commands/onboard.js.map +1 -1
  66. package/dist/src/cli/commands/tui.d.ts +1 -0
  67. package/dist/src/cli/commands/tui.js +40 -0
  68. package/dist/src/cli/commands/tui.js.map +1 -0
  69. package/dist/src/cli/index.d.ts +2 -0
  70. package/dist/src/cli/index.js +7 -3
  71. package/dist/src/cli/index.js.map +1 -1
  72. package/dist/src/config/schema.d.ts +6 -0
  73. package/dist/src/config/schema.js +11 -3
  74. package/dist/src/config/schema.js.map +1 -1
  75. package/dist/src/extensions/hooks.js +5 -1
  76. package/dist/src/extensions/hooks.js.map +1 -1
  77. package/dist/src/extensions/loader.d.ts +1 -0
  78. package/dist/src/extensions/loader.js +3 -1
  79. package/dist/src/extensions/loader.js.map +1 -1
  80. package/dist/src/extensions/sdk/index.d.ts +1 -1
  81. package/dist/src/extensions/sdk/index.js.map +1 -1
  82. package/dist/src/extensions/types/core.d.ts +8 -0
  83. package/dist/src/extensions/types/hooks.d.ts +16 -1
  84. package/dist/src/extensions/types/hooks.js +1 -0
  85. package/dist/src/extensions/types/hooks.js.map +1 -1
  86. package/dist/src/gateway/agents-admin.d.ts +19 -1
  87. package/dist/src/gateway/agents-admin.js +164 -3
  88. package/dist/src/gateway/agents-admin.js.map +1 -1
  89. package/dist/src/gateway/auth.d.ts +17 -3
  90. package/dist/src/gateway/auth.js +35 -16
  91. package/dist/src/gateway/auth.js.map +1 -1
  92. package/dist/src/gateway/hono/app.js +31 -1
  93. package/dist/src/gateway/hono/app.js.map +1 -1
  94. package/dist/src/gateway/hono/lib/config-payload.d.ts +1 -1
  95. package/dist/src/gateway/hono/middleware/auth.js +4 -3
  96. package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
  97. package/dist/src/gateway/hono/middleware/scopes.d.ts +15 -0
  98. package/dist/src/gateway/hono/middleware/scopes.js +41 -0
  99. package/dist/src/gateway/hono/middleware/scopes.js.map +1 -0
  100. package/dist/src/gateway/hono/routes/agents.js +59 -5
  101. package/dist/src/gateway/hono/routes/agents.js.map +1 -1
  102. package/dist/src/gateway/hono/routes/config.js +2 -2
  103. package/dist/src/gateway/hono/routes/config.js.map +1 -1
  104. package/dist/src/gateway/hono/routes/public-gateway.js +1 -0
  105. package/dist/src/gateway/hono/routes/public-gateway.js.map +1 -1
  106. package/dist/src/gateway/hono/routes/sessions.js +17 -0
  107. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  108. package/dist/src/gateway/security/audit.d.ts +18 -0
  109. package/dist/src/gateway/security/audit.js +68 -0
  110. package/dist/src/gateway/security/audit.js.map +1 -0
  111. package/dist/src/gateway/security/csp.d.ts +19 -0
  112. package/dist/src/gateway/security/csp.js +52 -0
  113. package/dist/src/gateway/security/csp.js.map +1 -0
  114. package/dist/src/gateway/security/dangerous-tools.d.ts +20 -0
  115. package/dist/src/gateway/security/dangerous-tools.js +46 -0
  116. package/dist/src/gateway/security/dangerous-tools.js.map +1 -0
  117. package/dist/src/gateway/security/flood-guard.d.ts +28 -0
  118. package/dist/src/gateway/security/flood-guard.js +42 -0
  119. package/dist/src/gateway/security/flood-guard.js.map +1 -0
  120. package/dist/src/gateway/security/index.d.ts +9 -0
  121. package/dist/src/gateway/security/index.js +10 -0
  122. package/dist/src/gateway/security/known-weak-secrets.d.ts +10 -0
  123. package/dist/src/gateway/security/known-weak-secrets.js +36 -0
  124. package/dist/src/gateway/security/known-weak-secrets.js.map +1 -0
  125. package/dist/src/gateway/security/operator-scopes.d.ts +37 -0
  126. package/dist/src/gateway/security/operator-scopes.js +137 -0
  127. package/dist/src/gateway/security/operator-scopes.js.map +1 -0
  128. package/dist/src/gateway/security/origin-check.d.ts +21 -0
  129. package/dist/src/gateway/security/origin-check.js +56 -0
  130. package/dist/src/gateway/security/origin-check.js.map +1 -0
  131. package/dist/src/gateway/security/preauth-connection-budget.d.ts +17 -0
  132. package/dist/src/gateway/security/preauth-connection-budget.js +49 -0
  133. package/dist/src/gateway/security/preauth-connection-budget.js.map +1 -0
  134. package/dist/src/gateway/security/secret-equal.d.ts +8 -0
  135. package/dist/src/gateway/security/secret-equal.js +30 -0
  136. package/dist/src/gateway/security/secret-equal.js.map +1 -0
  137. package/dist/src/gateway/service.d.ts +3 -1
  138. package/dist/src/gateway/service.js +40 -4
  139. package/dist/src/gateway/service.js.map +1 -1
  140. package/dist/src/session/client-history.d.ts +21 -0
  141. package/dist/src/session/client-history.js +89 -0
  142. package/dist/src/session/client-history.js.map +1 -0
  143. package/dist/src/session/index.d.ts +1 -0
  144. package/dist/src/session/index.js +2 -1
  145. package/dist/src/session/manager.d.ts +2 -0
  146. package/dist/src/session/manager.js +5 -0
  147. package/dist/src/session/manager.js.map +1 -1
  148. package/dist/src/session/thinking-resolve.js +1 -1
  149. package/dist/src/session/thinking-resolve.js.map +1 -1
  150. package/dist/src/tui/backends/embedded-backend.d.ts +42 -0
  151. package/dist/src/tui/backends/embedded-backend.js +173 -0
  152. package/dist/src/tui/backends/embedded-backend.js.map +1 -0
  153. package/dist/src/tui/backends/gateway-sse-backend.d.ts +53 -0
  154. package/dist/src/tui/backends/gateway-sse-backend.js +256 -0
  155. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -0
  156. package/dist/src/tui/chat-history.d.ts +4 -0
  157. package/dist/src/tui/chat-history.js +29 -0
  158. package/dist/src/tui/chat-history.js.map +1 -0
  159. package/dist/src/tui/components/assistant-message.d.ts +6 -0
  160. package/dist/src/tui/components/assistant-message.js +19 -0
  161. package/dist/src/tui/components/assistant-message.js.map +1 -0
  162. package/dist/src/tui/components/chat-log.d.ts +21 -0
  163. package/dist/src/tui/components/chat-log.js +113 -0
  164. package/dist/src/tui/components/chat-log.js.map +1 -0
  165. package/dist/src/tui/components/custom-editor.d.ts +14 -0
  166. package/dist/src/tui/components/custom-editor.js +50 -0
  167. package/dist/src/tui/components/custom-editor.js.map +1 -0
  168. package/dist/src/tui/components/fuzzy-filter.d.ts +17 -0
  169. package/dist/src/tui/components/fuzzy-filter.js +85 -0
  170. package/dist/src/tui/components/fuzzy-filter.js.map +1 -0
  171. package/dist/src/tui/components/searchable-select-list.d.ts +39 -0
  172. package/dist/src/tui/components/searchable-select-list.js +257 -0
  173. package/dist/src/tui/components/searchable-select-list.js.map +1 -0
  174. package/dist/src/tui/components/tool-execution.d.ts +16 -0
  175. package/dist/src/tui/components/tool-execution.js +76 -0
  176. package/dist/src/tui/components/tool-execution.js.map +1 -0
  177. package/dist/src/tui/components/user-message.d.ts +6 -0
  178. package/dist/src/tui/components/user-message.js +22 -0
  179. package/dist/src/tui/components/user-message.js.map +1 -0
  180. package/dist/src/tui/sse-consumer.d.ts +15 -0
  181. package/dist/src/tui/sse-consumer.js +75 -0
  182. package/dist/src/tui/sse-consumer.js.map +1 -0
  183. package/dist/src/tui/stream-assembler.d.ts +22 -0
  184. package/dist/src/tui/stream-assembler.js +63 -0
  185. package/dist/src/tui/stream-assembler.js.map +1 -0
  186. package/dist/src/tui/theme.d.ts +73 -0
  187. package/dist/src/tui/theme.js +157 -0
  188. package/dist/src/tui/theme.js.map +1 -0
  189. package/dist/src/tui/tui-agent-events.d.ts +7 -0
  190. package/dist/src/tui/tui-agent-events.js +103 -0
  191. package/dist/src/tui/tui-agent-events.js.map +1 -0
  192. package/dist/src/tui/tui-backend.d.ts +80 -0
  193. package/dist/src/tui/tui-backend.js +1 -0
  194. package/dist/src/tui/tui-commands.d.ts +23 -0
  195. package/dist/src/tui/tui-commands.js +165 -0
  196. package/dist/src/tui/tui-commands.js.map +1 -0
  197. package/dist/src/tui/tui-lifecycle.d.ts +26 -0
  198. package/dist/src/tui/tui-lifecycle.js +57 -0
  199. package/dist/src/tui/tui-lifecycle.js.map +1 -0
  200. package/dist/src/tui/tui-local-shell.d.ts +28 -0
  201. package/dist/src/tui/tui-local-shell.js +147 -0
  202. package/dist/src/tui/tui-local-shell.js.map +1 -0
  203. package/dist/src/tui/tui-overlays.d.ts +8 -0
  204. package/dist/src/tui/tui-overlays.js +22 -0
  205. package/dist/src/tui/tui-overlays.js.map +1 -0
  206. package/dist/src/tui/tui-picker-overlay.d.ts +26 -0
  207. package/dist/src/tui/tui-picker-overlay.js +69 -0
  208. package/dist/src/tui/tui-picker-overlay.js.map +1 -0
  209. package/dist/src/tui/tui-stdio-filter.d.ts +17 -0
  210. package/dist/src/tui/tui-stdio-filter.js +96 -0
  211. package/dist/src/tui/tui-stdio-filter.js.map +1 -0
  212. package/dist/src/tui/tui-submit.d.ts +25 -0
  213. package/dist/src/tui/tui-submit.js +102 -0
  214. package/dist/src/tui/tui-submit.js.map +1 -0
  215. package/dist/src/tui/tui-suspend.d.ts +10 -0
  216. package/dist/src/tui/tui-suspend.js +18 -0
  217. package/dist/src/tui/tui-suspend.js.map +1 -0
  218. package/dist/src/tui/tui-types.d.ts +86 -0
  219. package/dist/src/tui/tui-types.js +21 -0
  220. package/dist/src/tui/tui-types.js.map +1 -0
  221. package/dist/src/tui/tui.d.ts +5 -0
  222. package/dist/src/tui/tui.js +389 -0
  223. package/dist/src/tui/tui.js.map +1 -0
  224. package/package.json +5 -3
  225. package/dist/gateway/static/root/assets/agents-w8_jzuiX.js +0 -216
  226. package/dist/gateway/static/root/assets/agents-w8_jzuiX.js.map +0 -1
  227. package/dist/gateway/static/root/assets/channels-settings-DUKRPC7C.js +0 -9
  228. package/dist/gateway/static/root/assets/channels-settings-DUKRPC7C.js.map +0 -1
  229. package/dist/gateway/static/root/assets/cron-page-S18t1yG-.js +0 -2
  230. package/dist/gateway/static/root/assets/cron-page-S18t1yG-.js.map +0 -1
  231. package/dist/gateway/static/root/assets/index-OT4cGzon.css +0 -1
  232. package/dist/gateway/static/root/assets/index-PfkB8N37.js +0 -4734
  233. package/dist/gateway/static/root/assets/logs-page-DoWe1GWy.js +0 -2
  234. package/dist/gateway/static/root/assets/logs-page-DoWe1GWy.js.map +0 -1
  235. package/dist/gateway/static/root/assets/sessions-page-2uOYwEwd.js +0 -2
  236. package/dist/gateway/static/root/assets/sessions-page-2uOYwEwd.js.map +0 -1
  237. package/dist/gateway/static/root/assets/settings-page-fQWswCuq.js +0 -2
  238. package/dist/gateway/static/root/assets/settings-page-fQWswCuq.js.map +0 -1
  239. package/dist/gateway/static/root/assets/skills-page-BmBDCEbY.js +0 -3
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","names":["writeConfigToDisk"],"sources":["../../../src/gateway/service.ts"],"sourcesContent":["import crypto from 'crypto';\nimport { AgentService } from '../agent/service.js';\nimport { ChannelManager } from '../channels/manager.js';\nimport { CHAT_CHANNEL_ORDER } from '../channels/registry.js';\nimport { MessageBus, MessageBusShutdownError } from '../infra/bus/index.js';\nimport type { Config as SurfaceConfig } from '../config/config-surface.js';\nimport { loadConfig, saveConfig as writeConfigToDisk } from '../config/index.js';\nimport { getWorkspacePath } from '../config/schema.js';\nimport { CronService } from '../cron/index.js';\nimport { computeBundledExtensionExtensionsPatch } from '../extensions/bundled-extension-activation.js';\nimport { ExtensionLoader } from '../extensions/index.js';\nimport { HeartbeatService, heartbeatRunnerConfigFromConfig } from './heartbeat/index.js';\nimport { ConfigHotReloader } from '../config/reload.js';\nimport { SessionManager } from '../session/index.js';\nimport type { Config } from '../config/schema.js';\nimport type { SessionListQuery, ExportFormat } from '../session/types.js';\nimport { resolveGatewayAuth, assertGatewayAuthConfigured, validateToken, extractToken, type ResolvedGatewayAuth } from './auth.js';\nimport { getModelRegistry } from '../providers/index.js';\nimport {\n createLogger,\n getLogDir,\n getLogStats,\n inboundCorrelationMetadataFromAsyncLogContext,\n} from '../utils/logger.js';\nimport {\n resolveConfigPath,\n resolveCronJobsPath,\n resolveStateDir,\n resolveAgentDir,\n resolveWorkspaceExtensionsDir,\n} from '../config/paths.js';\nimport { AgentRunRelay, type RelayEvent } from './agent-run-relay.js';\nimport { ClarifyBridge, type ClarifyBridgeRequest } from './clarify-bridge.js';\nimport { registerClarifyBridge } from './clarify-runtime.js';\nimport {\n deleteManagedSkill as deleteManagedSkillDir,\n installSkillFromZip,\n listManagedSkillDirs,\n} from '../agent/skills/managed-store.js';\nimport {\n downloadSkillZipBuffer,\n fetchMarketplacePackageDetail,\n listSkillPackages,\n resolveSkillZipDownloadUrl,\n resolveSkillsStoreBaseUrl,\n skillIdForMarketplaceInstall,\n type MarketplacePackageDetail,\n type SkillsStoreListParams,\n type SkillsStoreListResponse,\n} from '../agent/skills/skills-store-client.js';\nimport { createSkillConfigManager } from '../agent/skills/config.js';\nimport { removeSkillsLockEntry } from '../agent/skills/hub-lock.js';\nimport type { SkillCatalogEntry } from '../agent/agent-manager.js';\nimport type { ManagedSkillListItem } from '../agent/skills/managed-store.js';\n\nconst log = createLogger('GatewayService');\nimport { PACKAGE_VERSION } from '../package-version.js';\nimport { buildSessionKey, parseSessionKey } from '../routing/session-key.js';\nimport { getDefaultAgentId } from '../routing/resolve-route.js';\nimport { prependEnvelopeTimestamp } from '../channels/envelope-timestamp.js';\nimport { scheduleGatewayUpdateCheck } from '../infra/update-startup.js';\nimport { restartGatewayProcessWithFreshPid } from './respawn.js';\nimport { MAX_CHAT_ATTACHMENTS } from './chat-limits.js';\n\n// ========== SSE Event System ==========\n\nexport interface ServiceEvent {\n id: string;\n type: string;\n payload: unknown;\n}\n\ntype EventListener = (event: ServiceEvent) => Promise<void> | void;\n\nconst EVENT_BUFFER_SIZE = 200; // ring buffer per subscriber for Last-Event-ID replay\n\nexport interface GatewayServiceConfig {\n configPath?: string;\n enableHotReload?: boolean;\n}\n\nexport class GatewayService {\n private bus: MessageBus;\n private config: Config;\n private configPath: string;\n private agentService: AgentService;\n private channelManager: ChannelManager;\n private cronService: CronService;\n private extensionLoader: ExtensionLoader | null = null;\n private heartbeatService: HeartbeatService;\n private sessionManager: SessionManager;\n private running = false;\n private configReloader: ConfigHotReloader | null = null;\n private startTime = Date.now();\n private workspacePath: string;\n\n // Authentication\n private auth: ResolvedGatewayAuth;\n\n // SSE event system\n private eventCounter = 0;\n private subscribers = new Map<string, EventListener>();\n private eventBuffers = new Map<string, ServiceEvent[]>();\n\n // Agent run relay for resuming SSE streams\n public readonly runRelay = new AgentRunRelay();\n\n /** Per-run abort for webchat (POST /api/agent/abort or client disconnect). */\n private runAbortControllers = new Map<string, AbortController>();\n\n private stopGatewayUpdateCheck: (() => void) | null = null;\n\n /** When set (e.g. by `GatewayServer`), `triggerGatewayProcessRestart` can stop HTTP then exit. */\n private gatewayShutdownForRestart: (() => Promise<void>) | null = null;\n\n private readonly clarifyBridge = new ClarifyBridge();\n\n /** Maps webchat session key → active `runId` for `clarify` tool routing. */\n private activeWebchatRunBySession = new Map<string, string>();\n\n constructor(private serviceConfig: GatewayServiceConfig = {}) {\n this.bus = new MessageBus();\n this.configPath = serviceConfig.configPath || resolveConfigPath();\n this.config = loadConfig(this.configPath);\n\n // Initialize authentication\n this.auth = resolveGatewayAuth({\n authConfig: this.config.gateway?.auth,\n });\n\n // Validate auth configuration\n assertGatewayAuthConfigured(this.auth);\n\n // Log token info (not the token itself)\n if (this.auth.mode === 'token') {\n const tokenPreview = this.auth.token ? `${this.auth.token.slice(0, 4)}***` : 'none';\n log.info({ mode: this.auth.mode, token: tokenPreview }, 'Authentication configured');\n } else {\n log.info({ mode: this.auth.mode }, 'Authentication disabled');\n }\n\n // Initialize channel manager\n this.channelManager = new ChannelManager(this.config, this.bus);\n\n // Initialize extension loader\n this.workspacePath = getWorkspacePath(this.config) || './workspace';\n this.initializeExtensionLoader();\n\n // Initialize ModelRegistry (loads from models.json)\n const registry = getModelRegistry();\n log.debug({ \n modelCount: registry.getAll().length, \n error: registry.getError() || 'none' \n }, 'ModelRegistry initialized');\n\n // Initialize agent service with extension registry\n const modelConfig = this.config.agents?.defaults?.model;\n const cronRef: { service?: CronService } = {};\n this.agentService = new AgentService(this.bus, {\n workspace: this.workspacePath,\n model: typeof modelConfig === 'string' ? modelConfig : modelConfig?.primary,\n config: this.config,\n extensionRegistry: this.extensionLoader?.getRegistry(),\n getCronService: () => cronRef.service,\n gatewayClarify: {\n requestClarification: (sessionKey, request) => {\n const runId = this.activeWebchatRunBySession.get(sessionKey);\n const publishSse = runId\n ? (e: RelayEvent) => {\n this.agentService.enqueueWebchatSseEvent(sessionKey, e);\n }\n : undefined;\n const parsed = parseSessionKey(sessionKey);\n const deliver =\n parsed?.source === 'telegram'\n ? async (ctx: { sessionKey: string; requestId: string; request: ClarifyBridgeRequest }) => {\n await this.deliverTelegramClarify(ctx);\n }\n : undefined;\n if (!runId && !deliver) {\n return Promise.reject(\n new Error('Clarify is not available for this session (use webchat, Telegram, or CLI)'),\n );\n }\n return this.clarifyBridge.startRequest({\n sessionKey,\n runId,\n relay: this.runRelay,\n publishSse,\n request,\n deliver,\n });\n },\n },\n });\n\n\n // Set channel manager reference for model switching\n this.agentService.setChannelManager(this.channelManager);\n this.channelManager.setSessionModelHooks({\n getModelForSession: (sk) => this.agentService.getModelForSession(sk),\n switchModelForSession: (sk, id) => this.agentService.switchModelForSession(sk, id),\n });\n\n // Initialize cron service\n this.cronService = new CronService({\n filePath: resolveCronJobsPath(),\n agentService: this.agentService,\n messageBus: this.bus,\n });\n cronRef.service = this.cronService;\n\n // Initialize session manager\n this.sessionManager = new SessionManager({\n config: this.config,\n });\n\n this.heartbeatService = new HeartbeatService({\n agentService: this.agentService,\n messageBus: this.bus,\n cronService: this.cronService,\n sessionStore: this.sessionManager.getStore(),\n getConfig: () => this.config,\n });\n\n this.cronService.setDeps({\n agentService: this.agentService,\n messageBus: this.bus,\n heartbeatService: this.heartbeatService,\n getDefaultCronAgentId: () => getDefaultAgentId(this.config),\n });\n }\n\n /**\n * Create extension loader and resolve configs (load runs in start() before channels).\n */\n private initializeExtensionLoader(): void {\n try {\n const aid = getDefaultAgentId(this.config);\n this.extensionLoader = new ExtensionLoader({\n workspaceDir: this.workspacePath,\n extensionsDir: resolveWorkspaceExtensionsDir(this.config, aid),\n });\n this.extensionLoader.setConfig(this.config as Parameters<ExtensionLoader['setConfig']>[0]);\n } catch (error) {\n log.warn({ error }, 'Failed to initialize extension loader');\n }\n }\n\n /**\n * Load extensions and register SDK / full ChannelPlugin instances with ChannelManager.\n */\n private async loadExtensionsAndRegisterChannels(): Promise<void> {\n if (!this.extensionLoader) {\n return;\n }\n try {\n await this.extensionLoader.loadByActivationPlan();\n const reg = this.extensionLoader.getRegistry();\n for (const plugin of reg.channelPlugins) {\n this.channelManager.registerPlugin(plugin);\n }\n log.debug(\n {\n extensionRecords: reg.extensions.size,\n channelPlugins: reg.channelPlugins.length,\n },\n 'Extensions loaded and channel plugins registered',\n );\n } catch (err) {\n log.warn({ err }, 'Failed to load extensions');\n }\n }\n\n async start(): Promise<void> {\n if (this.running) return;\n\n log.debug('Starting gateway service...');\n this.startTime = Date.now();\n this.running = true;\n\n registerClarifyBridge(this.clarifyBridge);\n\n this.channelManager.setOutboundHooks({\n runMessageSending: (to, content, channel) =>\n this.agentService.invokeOutboundMessageSending(to, content, channel),\n runMessageSent: (to, content, success, error, channel) =>\n this.agentService.invokeOutboundMessageSent(to, content, success, error, channel),\n });\n this.channelManager.enableOutboundPersistence(resolveAgentDir(this.config, getDefaultAgentId(this.config)));\n\n if (this.extensionLoader) {\n this.extensionLoader.setRuntimeContext({\n bus: this.bus,\n sessionManager: this.sessionManager,\n });\n }\n\n await this.loadExtensionsAndRegisterChannels();\n\n // Start channels (initialize first, then start)\n await this.channelManager.initialize();\n await this.channelManager.start();\n await this.channelManager.replayPendingOutboundMessages();\n\n // Initialize session manager\n await this.sessionManager.initialize();\n log.debug('Session manager initialized');\n\n this.cronService.setDeps({\n agentService: this.agentService,\n messageBus: this.bus,\n heartbeatService: this.heartbeatService,\n sessionStore: this.sessionManager.getStore(),\n getDefaultCronAgentId: () => getDefaultAgentId(this.config),\n });\n\n this.sessionManager.on('sessionUpdated', (data: { key: string; name?: string; tags?: string[] }) => {\n this.emit('session.updated', { key: data.key, name: data.name, tags: data.tags });\n });\n\n // Start cron service\n if (this.config.cron?.enabled !== false) {\n await this.cronService.initialize();\n }\n\n this.heartbeatService.start(heartbeatRunnerConfigFromConfig(this.config));\n\n // Start agent service (runs in background)\n this.agentService.start().catch((err) => {\n log.error({ err }, 'Agent service error');\n });\n\n // Start outbound message processor\n this.startOutboundProcessor().catch((err) => {\n log.error({ err }, 'Outbound processor error');\n });\n\n // Setup config hot reload\n if (this.serviceConfig.enableHotReload !== false) {\n this.setupConfigReloader();\n }\n\n this.stopGatewayUpdateCheck = scheduleGatewayUpdateCheck({\n config: this.config,\n onUpdateAvailableChange: (update) => {\n this.emit('update.available', update);\n },\n });\n\n log.debug('Gateway service started');\n }\n\n async stop(): Promise<void> {\n if (!this.running) return;\n\n log.debug('Stopping gateway service...');\n\n if (this.stopGatewayUpdateCheck) {\n this.stopGatewayUpdateCheck();\n this.stopGatewayUpdateCheck = null;\n }\n\n // Stop config reloader\n if (this.configReloader) {\n await this.configReloader.stop();\n this.configReloader = null;\n }\n\n // Stop heartbeat service\n this.heartbeatService.stop();\n\n registerClarifyBridge(null);\n this.clarifyBridge.dispose();\n this.agentService.stop();\n\n // Unblock `consumeOutbound()` / `consumeInbound()` waiters before stopping channels (CLI agent does the same).\n this.running = false;\n this.bus.shutdown();\n\n await this.channelManager.stop();\n\n // Stop cron service\n await this.cronService.stop();\n\n log.debug('Gateway service stopped');\n }\n\n /**\n * Start processing outbound messages and send through channels\n */\n private async startOutboundProcessor(): Promise<void> {\n log.debug('Starting outbound message processor');\n while (this.running) {\n try {\n const msg = await this.bus.consumeOutbound();\n await this.channelManager.send(msg);\n } catch (error) {\n if (error instanceof MessageBusShutdownError) {\n break;\n }\n const em = error instanceof Error ? error.message : String(error);\n log.error(\n { err: error, errorMessage: em, phase: 'outbound_consume' },\n `Outbound pipeline failed (will retry in 1s): ${em}`,\n );\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n }\n\n /**\n * Setup config hot reload using ConfigHotReloader\n */\n private setupConfigReloader(): void {\n this.configReloader = new ConfigHotReloader(\n this.configPath,\n this.config,\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 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.config = newConfig;\n this.emit('config.reload', { section: 'full', requiresRestart: true });\n },\n },\n {\n debounceMs: 300,\n enabled: this.serviceConfig.enableHotReload !== false,\n }\n );\n this.configReloader.start();\n }\n\n /**\n * Handle models config hot reload\n */\n private handleModelsReload(newConfig: Config): void {\n log.debug('Reloading models config...');\n this.config = newConfig;\n getModelRegistry().refresh();\n this.emit('config.reload', { section: 'models' });\n log.debug('Models config reloaded');\n }\n\n /**\n * Handle agent defaults config hot reload\n */\n private handleAgentDefaultsReload(newConfig: Config): void {\n log.debug('Reloading agent defaults...');\n this.config = newConfig;\n this.agentService.applyAgentDefaultsFromConfig(newConfig);\n this.emit('config.reload', { section: 'agents' });\n log.debug('Agent defaults reloaded');\n }\n\n /**\n * Apply `latest.channels` to every registered channel plugin (Telegram, Weixin, extensions).\n * Single runtime path for: file watcher hot reload, API saves, and Weixin QR follow-up.\n */\n private async handleChannelsReload(newConfig: Config): Promise<void> {\n log.debug('Reloading channels config...');\n this.config = newConfig;\n await this.channelManager.updateConfig(newConfig);\n this.emit('config.reload', { section: 'channels' });\n this.emit('channels.status', { channels: this.getChannelsStatus() });\n log.debug('Channels config reloaded');\n }\n\n /** After persisting config to disk: align plugins + debounced reload baseline (watchers skip duplicate diffs). */\n private async syncChannelPluginsAfterPersist(): Promise<void> {\n await this.handleChannelsReload(this.config);\n this.configReloader?.syncCurrentConfig(this.config);\n }\n\n /**\n * Handle cron config hot reload\n */\n private handleCronReload(newConfig: Config): void {\n log.debug('Reloading cron config...');\n this.config = newConfig;\n this.cronService.updateConfig(newConfig);\n this.emit('config.reload', { section: 'cron' });\n log.debug('Cron config reloaded');\n }\n\n /**\n * Handle heartbeat config hot reload\n */\n private handleHeartbeatReload(newConfig: Config): void {\n log.debug('Reloading heartbeat config...');\n this.config = newConfig;\n this.heartbeatService.updateConfig(newConfig);\n this.emit('config.reload', { section: 'heartbeat' });\n log.debug('Heartbeat config reloaded');\n }\n\n /**\n * Apply `gateway.heartbeat` from current config after PATCH /api/config (and when hot reload is off).\n * File watcher uses `handleHeartbeatReload` with the same effect when paths match.\n */\n reloadHeartbeatFromCurrentConfig(): void {\n this.handleHeartbeatReload(this.config);\n }\n\n /**\n * Handle tools config hot reload\n */\n private handleToolsReload(newConfig: Config): void {\n log.debug('Reloading tools config...');\n this.config = newConfig;\n this.emit('config.reload', { section: 'tools' });\n log.debug('Tools config reloaded');\n }\n\n /**\n * Dispatch config hot reload to extensions that registered `registerReload`, matching changed paths.\n */\n private async handleExtensionsReload(\n newConfig: Config,\n changedPaths: string[],\n ): Promise<void> {\n this.config = newConfig;\n this.extensionLoader?.setConfig(this.config as unknown as SurfaceConfig);\n\n if (!this.extensionLoader) {\n this.emit('config.reload', {\n section: 'extensions',\n source: 'extension-reload',\n changedPaths,\n });\n return;\n }\n\n const registry = this.extensionLoader.getRegistry();\n const matchingRegs = registry.getMatchingReloadRegistrations(changedPaths);\n\n if (matchingRegs.length === 0) {\n log.debug({ changedPaths }, 'No extension reload handlers matched');\n this.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.emit('config.reload', {\n section: 'extensions',\n source: 'extension-reload',\n changedPaths,\n });\n }\n\n /**\n * Reload configuration from disk (manual 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 /**\n * After Weixin QR login: token files may change without a `channels.weixin` JSON diff, so run the same\n * channel apply as an API save, then force Weixin long-poll restart (see `reloadMonitorsWithConfig`).\n */\n async afterWeixinCredentialsPersisted(): Promise<void> {\n const next = loadConfig(this.configPath);\n this.config = next;\n this.agentService.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.config, this.bus);\n log.info('Weixin monitors restarted after credential login');\n }\n\n /**\n * After Feishu WebUI QR setup: `channels.feishu` was written directly to disk; reload into memory\n * and apply channel plugins (same baseline as PATCH /api/config).\n */\n async afterFeishuCredentialsPersisted(): Promise<void> {\n const next = loadConfig(this.configPath);\n this.config = next;\n this.agentService.applyAgentDefaultsFromConfig(next);\n this.configReloader?.syncCurrentConfig(next);\n await this.handleChannelsReload(next);\n log.info('Feishu config applied after QR setup');\n }\n\n /**\n * Save current config to disk\n */\n /**\n * Persist and replace `this.config` with the validated file contents so runtime matches disk\n * (PATCH merge objects can drift from Zod-normalized output).\n */\n private async writeConfigAndReloadFromDisk(configToWrite: Config): Promise<void> {\n await writeConfigToDisk(configToWrite, this.configPath);\n this.config = loadConfig(this.configPath);\n this.agentService.applyAgentDefaultsFromConfig(this.config);\n // Hot-apply: reconcile managed dreaming cron jobs immediately after config persists.\n await this.agentService.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 }\n\n async saveConfig(config: Config): Promise<{ saved: boolean; error?: string }> {\n try {\n await this.writeConfigAndReloadFromDisk(config);\n await this.syncChannelPluginsAfterPersist();\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 /**\n * App store (phase 1): persist `extensions.enabled` / `extensions.disabled` for a bundled extension.\n * Extension modules are loaded at gateway startup; restart the gateway process to fully apply load/unload.\n */\n async setBundledExtensionActivationTarget(\n extensionId: string,\n wanted: boolean,\n ): Promise<{ ok: boolean; error?: string; requiresGatewayRestart: boolean }> {\n const loader = this.extensionLoader;\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.config, id, wanted);\n if (patch.ok === false) {\n return { ok: false, error: patch.error, requiresGatewayRestart: false };\n }\n const newConfig = { ...this.config, 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.config as unknown as SurfaceConfig);\n return { ok: true, requiresGatewayRestart: true };\n }\n\n /**\n * Update configuration and persist to disk\n */\n async updateConfig(updates: Partial<Config>): Promise<{ updated: boolean; error?: string }> {\n try {\n log.debug('Updating configuration...');\n \n // Merge updates\n this.config = { ...this.config, ...updates };\n\n await this.writeConfigAndReloadFromDisk(this.config);\n await this.syncChannelPluginsAfterPersist();\n\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 * Run agent with a message and stream events\n */\n /**\n * @param runOptions.signal — When set (e.g. client disconnect), aborts in-flight generation and persists partial output.\n */\n async *runAgent(\n message: string,\n channel: string,\n chatId: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n }>,\n thinking?: string,\n runOptions?: { signal?: AbortSignal },\n ): AsyncGenerator<{ type: string; content?: string; status?: string; runId?: string }, { 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\n // For webchat, register the run in the relay before yielding the first event\n if (channel === 'webchat') {\n const parsedKey = parseSessionKey(chatId);\n const sessionKey = parsedKey ? chatId : buildSessionKey({\n agentId: getDefaultAgentId(this.config),\n source: 'webchat',\n accountId: 'default',\n peerKind: 'direct',\n peerId: chatId,\n });\n this.runRelay.ensureRun(runId, sessionKey);\n this.runAbortControllers.set(runId, new AbortController());\n }\n\n const statusEvent = { type: 'status', status: 'accepted', runId };\n if (channel === 'webchat') this.runRelay.publish(runId, statusEvent);\n yield statusEvent;\n\n try {\n // For 'webchat' channel (web UI), process through agent service\n if (channel === 'webchat') {\n // Determine session key: if chatId is already a valid session key, use it directly\n // Otherwise, build a new session key from the chatId\n const parsedKey = parseSessionKey(chatId);\n const sessionKey = parsedKey ? chatId : buildSessionKey({\n agentId: getDefaultAgentId(this.config),\n source: 'webchat',\n accountId: 'default',\n peerKind: 'direct',\n peerId: chatId,\n });\n\n const timezone = this.agentService.resolveUserTimezoneForSession(sessionKey);\n // Keep UI clean: persist raw user text (no envelope timestamp),\n // but include a stamped variant for the model so it has a stable \"now\".\n const stampedMessage = prependEnvelopeTimestamp(message, timezone);\n const prepared = await this.agentService.prepareInboundAttachments(sessionKey, cappedAttachments);\n\n // Persist before streaming so a mid-turn refresh still sees text + attachment refs on disk.\n try {\n await this._saveUserMessage(sessionKey, message, prepared);\n } catch (err) {\n log.error({ err, sessionKey }, 'Failed to save user message');\n }\n\n const runAbort = this.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 this.activeWebchatRunBySession.set(sessionKey, runId);\n try {\n this.emit('agent.stream', { sessionKey, event: statusEvent });\n const eventStream = this.agentService.processDirectStreaming(stampedMessage, sessionKey, prepared, thinking, {\n signal: mergedSignal,\n });\n\n for await (const event of eventStream) {\n this.runRelay.publish(runId, event);\n this.emit('agent.stream', { sessionKey, event });\n yield event as { type: string; content?: string; status?: string; runId?: string };\n }\n\n this.runRelay.complete(runId);\n try {\n const metaAfter = await this.sessionManager.getSessionMetadata(sessionKey);\n if (metaAfter?.name) {\n this.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 const errorEvent = { type: 'error', content: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` };\n this.runRelay.publish(runId, errorEvent);\n this.emit('agent.stream', { sessionKey, event: errorEvent });\n this.runRelay.complete(runId);\n yield errorEvent;\n return { status: 'error', summary: error instanceof Error ? error.message : 'Unknown error' };\n } finally {\n this.activeWebchatRunBySession.delete(sessionKey);\n this.runAbortControllers.delete(runId);\n }\n }\n\n // Send message through bus for other channels (telegram, etc.)\n const correlationMeta = inboundCorrelationMetadataFromAsyncLogContext();\n await this.bus.publishInbound({\n channel,\n sender_id: 'gateway',\n chat_id: chatId,\n content: message,\n ...(correlationMeta ? { metadata: correlationMeta } : {}),\n });\n\n // Wait for and collect response\n // This is simplified - in production we'd need proper session tracking\n yield { type: 'token', content: 'Processing...\\n' };\n\n // Simulate processing delay\n await new Promise((resolve) => setTimeout(resolve, 1000));\n\n yield { type: 'token', content: 'Done\\n' };\n\n return { status: 'ok', summary: 'Message processed' };\n } catch (error) {\n log.error({ error }, 'Agent run failed');\n throw error;\n }\n }\n\n /** Abort an in-flight webchat agent run (matches `runId` from SSE `status`). */\n abortAgentRun(runId: string): boolean {\n this.clarifyBridge.cancelForRun(runId);\n for (const [sk, id] of this.activeWebchatRunBySession) {\n if (id === runId) {\n this.activeWebchatRunBySession.delete(sk);\n }\n }\n const c = this.runAbortControllers.get(runId);\n if (!c) {\n return false;\n }\n c.abort();\n return true;\n }\n\n /**\n * Queue steering text for an active webchat run (`Agent.steer` / tool-boundary injection).\n * `chatId` is the same as `POST /api/agent` body (`sessionKey` or legacy peer id).\n */\n async steerWebchatAgent(\n chatId: string,\n message: string,\n ): Promise<{ ok: true } | { ok: false; code: 'BAD_REQUEST' | 'NO_ACTIVE_RUN' | 'STEER_FAILED' }> {\n const trimmed = message.trim();\n if (!trimmed) {\n return { ok: false, code: 'BAD_REQUEST' };\n }\n const parsedKey = parseSessionKey(chatId);\n const sessionKey = parsedKey\n ? chatId\n : buildSessionKey({\n agentId: getDefaultAgentId(this.config),\n source: 'webchat',\n accountId: 'default',\n peerKind: 'direct',\n peerId: chatId,\n });\n if (!this.activeWebchatRunBySession.has(sessionKey)) {\n return { ok: false, code: 'NO_ACTIVE_RUN' };\n }\n const steered = await this.agentService.steerWebchatSession(sessionKey, trimmed);\n if (!steered) {\n return { ok: false, code: 'STEER_FAILED' };\n }\n return { ok: true };\n }\n\n private async deliverTelegramClarify(ctx: {\n sessionKey: string;\n requestId: string;\n request: ClarifyBridgeRequest;\n }): Promise<void> {\n const parsed = parseSessionKey(ctx.sessionKey);\n if (!parsed || parsed.source !== 'telegram') {\n return;\n }\n\n let body = ctx.request.question;\n if (ctx.request.default) {\n body += `\\n\\nDefault if unsure: ${ctx.request.default}`;\n }\n\n const choices = ctx.request.choices;\n const buttonRows =\n choices && choices.length >= 2\n ? choices.map((c, i) => [\n {\n text: c.length > 64 ? `${c.slice(0, 61)}…` : c,\n callback_data: `clarify:${ctx.requestId}:${i}`,\n },\n ])\n : undefined;\n\n if (!buttonRows) {\n body += '\\n\\nReply with your answer in this chat.';\n }\n\n await this.channelManager.send({\n channel: 'telegram',\n chat_id: parsed.peerId,\n content: body,\n metadata: {\n accountId: parsed.accountId,\n ...(parsed.threadId ? { threadId: parsed.threadId } : {}),\n },\n buttons: buttonRows,\n });\n }\n\n /** Deliver a user's answer to a pending `clarify` tool call. */\n submitClarifyResponse(requestId: string, answer: string): boolean {\n return this.clarifyBridge.handleResponse(requestId, answer);\n }\n\n /**\n * Send message through a channel\n */\n async sendMessage(\n channel: string,\n chatId: string,\n content: string\n ): Promise<{ sent: boolean; messageId?: string }> {\n try {\n await this.channelManager.send({\n channel,\n chat_id: chatId,\n content,\n });\n const messageId = `msg_${Date.now()}`;\n this.emit('message.sent', { channel, chatId, messageId });\n return { sent: true, messageId };\n } catch (error) {\n log.error({ channel, chatId, error }, 'Failed to send message');\n throw error;\n }\n }\n\n /**\n * Get channel statuses\n */\n getChannelsStatus(): Array<{\n name: string;\n enabled: boolean;\n connected: boolean;\n }> {\n const runningChannels = new Set(this.channelManager.getRunningChannels());\n const channels = this.config.channels as Record<string, { enabled?: boolean } | undefined> | undefined;\n const builtinOrder = CHAT_CHANNEL_ORDER as readonly string[];\n\n const rows: Array<{ name: string; enabled: boolean; connected: boolean }> = CHAT_CHANNEL_ORDER.map(\n (name) => ({\n name,\n enabled: !!channels?.[name]?.enabled,\n connected: runningChannels.has(name),\n }),\n );\n\n const extReg = this.extensionLoader?.getRegistry();\n const extraIds = extReg?.channelPlugins.map((p) => p.id).filter((id) => !builtinOrder.includes(id)) ?? [];\n if (extraIds.length === 0) {\n return rows;\n }\n\n const seen = new Set(builtinOrder);\n for (const name of extraIds) {\n if (seen.has(name)) continue;\n seen.add(name);\n rows.push({\n name,\n enabled: channels?.[name]?.enabled !== false,\n connected: runningChannels.has(name),\n });\n }\n\n return rows;\n }\n\n /**\n * Request an immediate heartbeat run (coalesced like interval/cron wakes).\n */\n requestHeartbeatNow(opts?: { reason?: string }): void {\n this.heartbeatService.requestNow({ reason: opts?.reason ?? 'manual' });\n }\n\n /**\n * Register graceful shutdown used after spawning a replacement gateway process (foreground CLI server).\n */\n registerGatewayShutdownForRestart(handler: () => Promise<void>): void {\n this.gatewayShutdownForRestart = handler;\n }\n\n /**\n * Respawn the gateway process when supported (spawn + exit, supervisor exit, or disabled when XOPC_NO_RESPAWN).\n */\n triggerGatewayProcessRestart(): { ok: boolean; mode: string; message?: string } {\n const result = restartGatewayProcessWithFreshPid();\n if (result.mode === 'failed') {\n return { ok: false, mode: result.mode, message: result.detail ?? 'spawn failed' };\n }\n if (result.mode === 'disabled') {\n return {\n ok: false,\n mode: 'disabled',\n message:\n 'Process respawn is disabled (XOPC_NO_RESPAWN). Restart the gateway manually (e.g. xopc gateway restart).',\n };\n }\n const shutdown = this.gatewayShutdownForRestart;\n if (!shutdown) {\n return {\n ok: false,\n mode: result.mode,\n message: 'Gateway restart is not available in this process.',\n };\n }\n setImmediate(() => {\n void shutdown().finally(() => {\n process.exit(0);\n });\n });\n return { ok: true, mode: result.mode };\n }\n\n /**\n * Get health status\n */\n getHealth(): {\n status: string;\n service: string;\n version: string;\n uptime: number;\n channels: { running: number; total: number };\n configPath: string;\n logs?: {\n dir: string;\n errors24h: number;\n stats: Record<string, number>;\n };\n } {\n const runningChannels = this.channelManager.getRunningChannels();\n const allChannels = this.channelManager.getAllChannels();\n const logStats = getLogStats();\n\n return {\n status: 'ok',\n service: 'xopc-gateway',\n version: PACKAGE_VERSION,\n uptime: Math.floor((Date.now() - this.startTime) / 1000),\n channels: {\n running: runningChannels.length,\n total: allChannels.length,\n },\n configPath: this.configPath,\n logs: {\n dir: getLogDir(),\n errors24h: logStats.errorsLast24h,\n stats: logStats.byLevel,\n },\n };\n }\n\n get isRunning(): boolean {\n return this.running;\n }\n\n /**\n * Get extension registry for external access (HTTP routes, gateway methods)\n */\n getExtensionRegistry() {\n return this.extensionLoader?.getRegistry();\n }\n\n /** Extension loader for discovery and frontend asset APIs (may be null if extensions failed to init). */\n getExtensionLoader(): ExtensionLoader | null {\n return this.extensionLoader;\n }\n\n /**\n * Get model registry for external access (HTTP routes)\n */\n getModelRegistry() {\n const { getModelRegistry } = require('../providers/index.js');\n return getModelRegistry();\n }\n\n /**\n * Invoke a gateway method registered by extensions\n */\n async invokeGatewayMethod(method: string, params: Record<string, unknown>): Promise<unknown> {\n const registry = this.getExtensionRegistry();\n if (!registry) {\n throw new Error('Extension registry not available');\n }\n\n const handler = registry.getGatewayMethod(method);\n if (!handler) {\n throw new Error(`Gateway method not found: ${method}`);\n }\n\n return await handler(params);\n }\n\n get currentConfig(): Config {\n return this.config;\n }\n\n get cronServiceInstance(): CronService {\n return this.cronService;\n }\n\n getSkillsApi(): { catalog: SkillCatalogEntry[]; managed: ManagedSkillListItem[] } {\n return {\n catalog: this.agentService.getSkillCatalog(),\n managed: listManagedSkillDirs(),\n };\n }\n\n getSkillMarkdownSource(skillName: string): { name: string; markdown: string } | null {\n return this.agentService.getSkillMarkdownSource(skillName);\n }\n\n deleteManagedSkill(skillId: string): void {\n removeSkillsLockEntry(skillId);\n deleteManagedSkillDir(skillId);\n this.agentService.refreshSkillsAfterDiskChange();\n }\n\n installManagedSkillZip(\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.agentService.refreshSkillsAfterDiskChange();\n return result;\n }\n\n async fetchSkillsMarketplaceCatalog(params: SkillsStoreListParams): Promise<SkillsStoreListResponse> {\n const base = resolveSkillsStoreBaseUrl(this.config);\n return listSkillPackages(base, params);\n }\n\n async fetchSkillsMarketplacePackageDetail(packageName: string): Promise<MarketplacePackageDetail> {\n const base = resolveSkillsStoreBaseUrl(this.config);\n return fetchMarketplacePackageDetail(base, packageName);\n }\n\n async installSkillFromMarketplace(opts: {\n name: string;\n version?: string;\n overwrite?: boolean;\n }): Promise<{ skillId: string; path: string }> {\n const base = resolveSkillsStoreBaseUrl(this.config);\n const { downloadUrl } = await resolveSkillZipDownloadUrl(base, opts.name, opts.version);\n const buf = await downloadSkillZipBuffer(base, downloadUrl);\n const skillId = skillIdForMarketplaceInstall(opts.name);\n return this.installManagedSkillZip(buf, { skillId, overwrite: opts.overwrite ?? false });\n }\n\n reloadSkillsFromDisk(): void {\n this.agentService.refreshSkillsAfterDiskChange();\n }\n\n patchSkillEnabled(skillName: string, enabled: boolean): void {\n createSkillConfigManager(resolveStateDir()).setSkillEnabled(skillName, enabled);\n this.agentService.refreshSkillsAfterSkillConfigChange();\n }\n\n get sessionManagerInstance(): SessionManager {\n return this.sessionManager;\n }\n\n async getSessionAgentConfig(sessionKey: string) {\n return this.agentService.getSessionAgentConfig(sessionKey);\n }\n\n /** Resolved markdown workspace for a session (after hydration / mkdir). Used by workspace file API when `sessionKey` is passed. */\n async getEffectiveWorkspacePathForSession(sessionKey: string): Promise<string> {\n return this.agentService.getEffectiveWorkspacePathForSession(sessionKey);\n }\n\n async patchSessionAgentConfig(sessionKey: string, body: {\n thinkingLevel?: string;\n model?: string | null;\n reasoningLevel?: string;\n workingDirectory?: string;\n }) {\n return this.agentService.patchSessionAgentConfig(sessionKey, body);\n }\n\n /**\n * Process a message directly through the agent (for CLI mode)\n */\n async processDirect(content: string, sessionKey = 'cli:direct'): Promise<string> {\n return this.agentService.processDirect(content, sessionKey);\n }\n\n // ========== SSE Event System ==========\n\n /**\n * Subscribe to server-pushed events.\n * Returns a cleanup function to unsubscribe.\n */\n subscribe(sessionId: string, listener: EventListener): () => 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 // Keep buffer for a while in case they reconnect\n setTimeout(() => {\n if (!this.subscribers.has(sessionId)) {\n this.eventBuffers.delete(sessionId);\n }\n }, 5 * 60_000); // 5 min grace\n log.debug({ sessionId }, 'Event subscriber removed');\n };\n }\n\n /**\n * Emit an event to all subscribers.\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 // Buffer the event\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 // Deliver\n try {\n listener(event);\n } catch (err) {\n log.warn({ sessionId, err }, 'Failed to deliver event to subscriber');\n }\n }\n }\n\n /**\n * Get events since a given event id (for Last-Event-ID reconnection).\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; // can't find cursor — send everything in buffer\n return buf.slice(idx + 1);\n }\n\n /**\n * Save user message to session for webchat (fire-and-forget).\n * Called at the start of runAgent to ensure message survives page refresh.\n */\n private async _saveUserMessage(\n sessionKey: string,\n message: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n }>,\n ): Promise<void> {\n // Load existing messages\n const existingMessages = await this.sessionManager.loadMessages(sessionKey);\n\n // Build user message (marker stripped in SessionStore.convertMessages for API clients)\n const userMessage = {\n role: 'user' as const,\n content: [{ type: 'text' as const, text: message }],\n attachments: attachments?.map((a) => ({\n type: a.type,\n mimeType: a.mimeType,\n name: a.name,\n size: a.size,\n workspaceRelativePath: a.workspaceRelativePath,\n // Note: we don't store data (base64) to keep session file small\n })),\n timestamp: Date.now(),\n /** Dropped before `agent.prompt` so we don't duplicate the turn; not exposed via GET session */\n webchatEarlySave: true as const,\n };\n\n // Append and save\n const updatedMessages = [...existingMessages, userMessage];\n await this.sessionManager.saveMessages(sessionKey, updatedMessages);\n log.debug({ sessionKey, messageCount: updatedMessages.length }, 'User message saved');\n }\n\n // ========== Session Management API ==========\n\n /**\n * List sessions with query filters\n */\n async listSessions(query?: SessionListQuery) {\n return this.sessionManager.listSessions(query);\n }\n\n /**\n * List all subagent sessions.\n * Subagent sessions have keys starting with 'subagent:'.\n */\n async listSubagents(query?: SessionListQuery) {\n return this.sessionManager.listSubagents(query);\n }\n\n /**\n * Get a single session by key\n */\n async getSession(key: string) {\n return this.sessionManager.getSession(key);\n }\n\n /**\n * Delete a session\n */\n async deleteSession(key: string): Promise<{ deleted: boolean }> {\n const result = await this.sessionManager.deleteSession(key);\n return { deleted: result };\n }\n\n /**\n * Delete multiple sessions\n */\n async deleteSessions(keys: string[]): Promise<{ success: string[]; failed: string[] }> {\n return this.sessionManager.deleteSessions(keys);\n }\n\n /**\n * Rename a session\n */\n async renameSession(key: string, name: string): Promise<{ renamed: boolean }> {\n await this.sessionManager.renameSession(key, name);\n return { renamed: true };\n }\n\n /**\n * Tag a session\n */\n async tagSession(key: string, tags: string[]): Promise<{ tagged: boolean }> {\n await this.sessionManager.tagSession(key, tags);\n return { tagged: true };\n }\n\n /**\n * Remove tags from a session\n */\n async untagSession(key: string, tags: string[]): Promise<{ untagged: boolean }> {\n await this.sessionManager.untagSession(key, tags);\n return { untagged: true };\n }\n\n /**\n * Archive a session\n */\n async archiveSession(key: string): Promise<{ archived: boolean }> {\n await this.sessionManager.archiveSession(key);\n return { archived: true };\n }\n\n /**\n * Unarchive a session\n */\n async unarchiveSession(key: string): Promise<{ unarchived: boolean }> {\n await this.sessionManager.unarchiveSession(key);\n return { unarchived: true };\n }\n\n /**\n * Pin a session\n */\n async pinSession(key: string): Promise<{ pinned: boolean }> {\n await this.sessionManager.pinSession(key);\n return { pinned: true };\n }\n\n /**\n * Unpin a session\n */\n async unpinSession(key: string): Promise<{ unpinned: boolean }> {\n await this.sessionManager.unpinSession(key);\n return { unpinned: true };\n }\n\n /**\n * Search sessions\n */\n async searchSessions(query: string) {\n return this.sessionManager.searchSessions(query);\n }\n\n /**\n * Search within a session\n */\n async searchInSession(key: string, keyword: string) {\n return this.sessionManager.searchInSession(key, keyword);\n }\n\n /**\n * Export a session\n */\n async exportSession(key: string, format: ExportFormat): Promise<{ content: string }> {\n const content = await this.sessionManager.exportSession(key, format);\n return { content };\n }\n\n /**\n * Get session statistics\n */\n async getSessionStats() {\n return this.sessionManager.getStats();\n }\n\n /**\n * Get unique chat IDs from sessions, grouped by channel\n * Returns a list of channel/chatId pairs for cron job configuration.\n * `chatId` is the session-store routing suffix (unique per bot account + peer).\n * When `routing` exists, `peerId` is the platform id (e.g. Telegram numeric chat id).\n */\n async getSessionChatIds(channel?: string): Promise<\n Array<{\n channel: string;\n chatId: string;\n lastActive: string;\n accountId?: string;\n peerKind?: string;\n peerId?: string;\n }>\n > {\n const result = await this.sessionManager.listSessions({\n limit: 1000,\n sortBy: 'lastAccessedAt',\n sortOrder: 'desc',\n ...(channel ? { channel } : {}),\n });\n\n // Group by channel:chatId to get unique pairs\n const seen = new Set<string>();\n const chatIds: Array<{\n channel: string;\n chatId: string;\n lastActive: string;\n accountId?: string;\n peerKind?: string;\n peerId?: string;\n }> = [];\n\n for (const session of result.items) {\n const key = `${session.sourceChannel}:${session.sourceChatId}`;\n if (!seen.has(key) && session.sourceChannel && session.sourceChatId) {\n seen.add(key);\n const r = session.routing;\n chatIds.push({\n channel: session.sourceChannel,\n chatId: session.sourceChatId,\n lastActive: session.lastAccessedAt,\n ...(r\n ? {\n accountId: r.accountId,\n peerKind: r.peerKind,\n peerId: r.peerId,\n }\n : {}),\n });\n }\n }\n\n return chatIds;\n }\n\n /**\n * Validate authentication token from request headers.\n * Returns true if auth is disabled (mode: 'none') or token is valid.\n */\n validateAuth(headers?: Record<string, string | string[] | undefined>): boolean {\n const token = extractToken(headers);\n return validateToken(this.auth, token);\n }\n\n /**\n * Get current auth mode.\n */\n getAuthMode(): 'none' | 'token' {\n return this.auth.mode;\n }\n\n /**\n * Get current auth token (for CLI server integration).\n * Returns undefined if mode is 'none'.\n */\n getAuthToken(): string | undefined {\n return this.auth.mode === 'token' ? this.auth.token : undefined;\n }\n\n /**\n * Refresh (regenerate) the gateway auth token.\n * Returns the new token.\n */\n async refreshAuthToken(): Promise<string> {\n if (this.auth.mode !== 'token') {\n throw new Error('Cannot refresh token: auth mode is not token');\n }\n\n // Generate new token\n const newToken = crypto.randomBytes(24).toString('hex');\n \n // Update in-memory auth\n this.auth.token = newToken;\n \n // Update config\n this.config = {\n ...this.config,\n gateway: {\n ...this.config.gateway,\n auth: {\n ...this.config.gateway?.auth,\n mode: 'token',\n token: newToken,\n },\n },\n };\n \n await this.writeConfigAndReloadFromDisk(this.config);\n\n log.info({ tokenPreview: `${newToken.slice(0, 8)}...` }, 'Gateway token refreshed');\n \n return newToken;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"service.js","names":["writeConfigToDisk"],"sources":["../../../src/gateway/service.ts"],"sourcesContent":["import crypto from 'crypto';\nimport { AgentService } from '../agent/service.js';\nimport { ChannelManager } from '../channels/manager.js';\nimport { CHAT_CHANNEL_ORDER } from '../channels/registry.js';\nimport { MessageBus, MessageBusShutdownError } from '../infra/bus/index.js';\nimport type { Config as SurfaceConfig } from '../config/config-surface.js';\nimport { loadConfig, saveConfig as writeConfigToDisk } from '../config/index.js';\nimport { getWorkspacePath } from '../config/schema.js';\nimport { CronService } from '../cron/index.js';\nimport { computeBundledExtensionExtensionsPatch } from '../extensions/bundled-extension-activation.js';\nimport { ExtensionLoader } from '../extensions/index.js';\nimport { HeartbeatService, heartbeatRunnerConfigFromConfig } from './heartbeat/index.js';\nimport { ConfigHotReloader } from '../config/reload.js';\nimport { SessionManager } from '../session/index.js';\nimport type { Config } from '../config/schema.js';\nimport type { SessionListQuery, ExportFormat } from '../session/types.js';\nimport { resolveGatewayAuth, assertGatewayAuthConfigured, validateToken, extractToken, type ResolvedGatewayAuth } from './auth.js';\nimport { assertGatewayAuthNotKnownWeak } from './security/known-weak-secrets.js';\nimport { auditGatewayConfig } from './security/audit.js';\nimport { getModelRegistry } from '../providers/index.js';\nimport {\n createLogger,\n getLogDir,\n getLogStats,\n inboundCorrelationMetadataFromAsyncLogContext,\n} from '../utils/logger.js';\nimport {\n resolveConfigPath,\n resolveCronJobsPath,\n resolveStateDir,\n resolveAgentDir,\n resolveWorkspaceExtensionsDir,\n} from '../config/paths.js';\nimport { AgentRunRelay, type RelayEvent } from './agent-run-relay.js';\nimport { ClarifyBridge, type ClarifyBridgeRequest } from './clarify-bridge.js';\nimport { registerClarifyBridge } from './clarify-runtime.js';\nimport {\n deleteManagedSkill as deleteManagedSkillDir,\n installSkillFromZip,\n listManagedSkillDirs,\n} from '../agent/skills/managed-store.js';\nimport {\n downloadSkillZipBuffer,\n fetchMarketplacePackageDetail,\n listSkillPackages,\n resolveSkillZipDownloadUrl,\n resolveSkillsStoreBaseUrl,\n skillIdForMarketplaceInstall,\n type MarketplacePackageDetail,\n type SkillsStoreListParams,\n type SkillsStoreListResponse,\n} from '../agent/skills/skills-store-client.js';\nimport { createSkillConfigManager } from '../agent/skills/config.js';\nimport { removeSkillsLockEntry } from '../agent/skills/hub-lock.js';\nimport type { SkillCatalogEntry } from '../agent/agent-manager.js';\nimport type { ManagedSkillListItem } from '../agent/skills/managed-store.js';\n\nconst log = createLogger('GatewayService');\nimport { PACKAGE_VERSION } from '../package-version.js';\nimport { buildSessionKey, parseSessionKey } from '../routing/session-key.js';\nimport { getDefaultAgentId } from '../routing/resolve-route.js';\nimport { prependEnvelopeTimestamp } from '../channels/envelope-timestamp.js';\nimport { scheduleGatewayUpdateCheck } from '../infra/update-startup.js';\nimport { restartGatewayProcessWithFreshPid } from './respawn.js';\nimport { MAX_CHAT_ATTACHMENTS } from './chat-limits.js';\n\n// ========== SSE Event System ==========\n\nexport interface ServiceEvent {\n id: string;\n type: string;\n payload: unknown;\n}\n\ntype EventListener = (event: ServiceEvent) => Promise<void> | void;\n\nconst EVENT_BUFFER_SIZE = 200; // ring buffer per subscriber for Last-Event-ID replay\n\nexport interface GatewayServiceConfig {\n configPath?: string;\n enableHotReload?: boolean;\n}\n\nexport class GatewayService {\n private bus: MessageBus;\n private config: Config;\n private configPath: string;\n private agentService: AgentService;\n private channelManager: ChannelManager;\n private cronService: CronService;\n private extensionLoader: ExtensionLoader | null = null;\n private heartbeatService: HeartbeatService;\n private sessionManager: SessionManager;\n private running = false;\n private configReloader: ConfigHotReloader | null = null;\n private startTime = Date.now();\n private workspacePath: string;\n\n // Authentication\n private auth: ResolvedGatewayAuth;\n\n // SSE event system\n private eventCounter = 0;\n private subscribers = new Map<string, EventListener>();\n private eventBuffers = new Map<string, ServiceEvent[]>();\n\n // Agent run relay for resuming SSE streams\n public readonly runRelay = new AgentRunRelay();\n\n /** Per-run abort for webchat (POST /api/agent/abort or client disconnect). */\n private runAbortControllers = new Map<string, AbortController>();\n\n private stopGatewayUpdateCheck: (() => void) | null = null;\n\n /** When set (e.g. by `GatewayServer`), `triggerGatewayProcessRestart` can stop HTTP then exit. */\n private gatewayShutdownForRestart: (() => Promise<void>) | null = null;\n\n private readonly clarifyBridge = new ClarifyBridge();\n\n /** Maps webchat session key → active `runId` for `clarify` tool routing. */\n private activeWebchatRunBySession = new Map<string, string>();\n\n constructor(private serviceConfig: GatewayServiceConfig = {}) {\n this.bus = new MessageBus();\n this.configPath = serviceConfig.configPath || resolveConfigPath();\n this.config = loadConfig(this.configPath);\n\n // Initialize authentication\n this.auth = resolveGatewayAuth({\n authConfig: this.config.gateway?.auth,\n });\n\n // Validate auth configuration\n assertGatewayAuthConfigured(this.auth);\n\n // Reject known weak / placeholder credentials at startup\n assertGatewayAuthNotKnownWeak(this.auth);\n\n // Security audit: detect dangerous configuration combinations early\n auditGatewayConfig({\n auth: this.auth,\n host: this.config.gateway?.host,\n corsOrigins: this.config.gateway?.corsOrigins,\n });\n\n // Log token info (not the token itself)\n if (this.auth.mode === 'token') {\n const tokenPreview = this.auth.token ? `${this.auth.token.slice(0, 4)}***` : 'none';\n log.info({ mode: this.auth.mode, token: tokenPreview }, 'Authentication configured');\n } else {\n log.info({ mode: this.auth.mode }, 'Authentication disabled');\n }\n\n // Initialize channel manager\n this.channelManager = new ChannelManager(this.config, this.bus);\n\n // Initialize extension loader\n this.workspacePath = getWorkspacePath(this.config) || './workspace';\n this.initializeExtensionLoader();\n\n // Initialize ModelRegistry (loads from models.json)\n const registry = getModelRegistry();\n log.debug({ \n modelCount: registry.getAll().length, \n error: registry.getError() || 'none' \n }, 'ModelRegistry initialized');\n\n // Initialize agent service with extension registry\n const modelConfig = this.config.agents?.defaults?.model;\n const cronRef: { service?: CronService } = {};\n this.agentService = new AgentService(this.bus, {\n workspace: this.workspacePath,\n model: typeof modelConfig === 'string' ? modelConfig : modelConfig?.primary,\n config: this.config,\n extensionRegistry: this.extensionLoader?.getRegistry(),\n getCronService: () => cronRef.service,\n gatewayClarify: {\n requestClarification: (sessionKey, request) => {\n const runId = this.activeWebchatRunBySession.get(sessionKey);\n const publishSse = runId\n ? (e: RelayEvent) => {\n this.agentService.enqueueWebchatSseEvent(sessionKey, e);\n }\n : undefined;\n const parsed = parseSessionKey(sessionKey);\n const deliver =\n parsed?.source === 'telegram'\n ? async (ctx: { sessionKey: string; requestId: string; request: ClarifyBridgeRequest }) => {\n await this.deliverTelegramClarify(ctx);\n }\n : undefined;\n if (!runId && !deliver) {\n return Promise.reject(\n new Error('Clarify is not available for this session (use webchat, Telegram, or CLI)'),\n );\n }\n return this.clarifyBridge.startRequest({\n sessionKey,\n runId,\n relay: this.runRelay,\n publishSse,\n request,\n deliver,\n });\n },\n },\n });\n\n\n // Set channel manager reference for model switching\n this.agentService.setChannelManager(this.channelManager);\n this.channelManager.setSessionModelHooks({\n getModelForSession: (sk) => this.agentService.getModelForSession(sk),\n switchModelForSession: (sk, id) => this.agentService.switchModelForSession(sk, id),\n });\n\n // Initialize cron service\n this.cronService = new CronService({\n filePath: resolveCronJobsPath(),\n agentService: this.agentService,\n messageBus: this.bus,\n });\n cronRef.service = this.cronService;\n\n // Initialize session manager\n this.sessionManager = new SessionManager({\n config: this.config,\n });\n\n this.heartbeatService = new HeartbeatService({\n agentService: this.agentService,\n messageBus: this.bus,\n cronService: this.cronService,\n sessionStore: this.sessionManager.getStore(),\n getConfig: () => this.config,\n });\n\n this.cronService.setDeps({\n agentService: this.agentService,\n messageBus: this.bus,\n heartbeatService: this.heartbeatService,\n getDefaultCronAgentId: () => getDefaultAgentId(this.config),\n });\n }\n\n /**\n * Create extension loader and resolve configs (load runs in start() before channels).\n */\n private initializeExtensionLoader(): void {\n try {\n const aid = getDefaultAgentId(this.config);\n this.extensionLoader = new ExtensionLoader({\n workspaceDir: this.workspacePath,\n extensionsDir: resolveWorkspaceExtensionsDir(this.config, aid),\n });\n this.extensionLoader.setConfig(this.config as Parameters<ExtensionLoader['setConfig']>[0]);\n } catch (error) {\n log.warn({ error }, 'Failed to initialize extension loader');\n }\n }\n\n /**\n * Load extensions and register SDK / full ChannelPlugin instances with ChannelManager.\n */\n private async loadExtensionsAndRegisterChannels(): Promise<void> {\n if (!this.extensionLoader) {\n return;\n }\n try {\n await this.extensionLoader.loadByActivationPlan();\n const reg = this.extensionLoader.getRegistry();\n for (const plugin of reg.channelPlugins) {\n this.channelManager.registerPlugin(plugin);\n }\n log.debug(\n {\n extensionRecords: reg.extensions.size,\n channelPlugins: reg.channelPlugins.length,\n },\n 'Extensions loaded and channel plugins registered',\n );\n } catch (err) {\n log.warn({ err }, 'Failed to load extensions');\n }\n }\n\n async start(): Promise<void> {\n if (this.running) return;\n\n log.debug('Starting gateway service...');\n this.startTime = Date.now();\n this.running = true;\n\n registerClarifyBridge(this.clarifyBridge);\n\n this.channelManager.setOutboundHooks({\n runMessageSending: (to, content, channel) =>\n this.agentService.invokeOutboundMessageSending(to, content, channel),\n runMessageSent: (to, content, success, error, channel) =>\n this.agentService.invokeOutboundMessageSent(to, content, success, error, channel),\n });\n this.channelManager.enableOutboundPersistence(resolveAgentDir(this.config, getDefaultAgentId(this.config)));\n\n if (this.extensionLoader) {\n this.extensionLoader.setRuntimeContext({\n bus: this.bus,\n sessionManager: this.sessionManager,\n scheduleWebchatContinuation: (sessionKey: string, continuationMessage: string) => {\n queueMicrotask(() => {\n void this.drainScheduledWebchatContinuation(sessionKey, continuationMessage);\n });\n },\n });\n }\n\n await this.loadExtensionsAndRegisterChannels();\n\n // Start channels (initialize first, then start)\n await this.channelManager.initialize();\n await this.channelManager.start();\n await this.channelManager.replayPendingOutboundMessages();\n\n // Initialize session manager\n await this.sessionManager.initialize();\n log.debug('Session manager initialized');\n\n this.cronService.setDeps({\n agentService: this.agentService,\n messageBus: this.bus,\n heartbeatService: this.heartbeatService,\n sessionStore: this.sessionManager.getStore(),\n getDefaultCronAgentId: () => getDefaultAgentId(this.config),\n });\n\n this.sessionManager.on('sessionUpdated', (data: { key: string; name?: string; tags?: string[] }) => {\n this.emit('session.updated', { key: data.key, name: data.name, tags: data.tags });\n });\n\n // Start cron service\n if (this.config.cron?.enabled !== false) {\n await this.cronService.initialize();\n }\n\n this.heartbeatService.start(heartbeatRunnerConfigFromConfig(this.config));\n\n // Start agent service (runs in background)\n this.agentService.start().catch((err) => {\n log.error({ err }, 'Agent service error');\n });\n\n // Start outbound message processor\n this.startOutboundProcessor().catch((err) => {\n log.error({ err }, 'Outbound processor error');\n });\n\n // Setup config hot reload\n if (this.serviceConfig.enableHotReload !== false) {\n this.setupConfigReloader();\n }\n\n this.stopGatewayUpdateCheck = scheduleGatewayUpdateCheck({\n config: this.config,\n onUpdateAvailableChange: (update) => {\n this.emit('update.available', update);\n },\n });\n\n log.debug('Gateway service started');\n }\n\n async stop(): Promise<void> {\n if (!this.running) return;\n\n log.debug('Stopping gateway service...');\n\n if (this.stopGatewayUpdateCheck) {\n this.stopGatewayUpdateCheck();\n this.stopGatewayUpdateCheck = null;\n }\n\n // Stop config reloader\n if (this.configReloader) {\n await this.configReloader.stop();\n this.configReloader = null;\n }\n\n // Stop heartbeat service\n this.heartbeatService.stop();\n\n registerClarifyBridge(null);\n this.clarifyBridge.dispose();\n this.agentService.stop();\n\n // Unblock `consumeOutbound()` / `consumeInbound()` waiters before stopping channels (CLI agent does the same).\n this.running = false;\n this.bus.shutdown();\n\n await this.channelManager.stop();\n\n // Stop cron service\n await this.cronService.stop();\n\n log.debug('Gateway service stopped');\n }\n\n /**\n * Start processing outbound messages and send through channels\n */\n private async startOutboundProcessor(): Promise<void> {\n log.debug('Starting outbound message processor');\n while (this.running) {\n try {\n const msg = await this.bus.consumeOutbound();\n await this.channelManager.send(msg);\n } catch (error) {\n if (error instanceof MessageBusShutdownError) {\n break;\n }\n const em = error instanceof Error ? error.message : String(error);\n log.error(\n { err: error, errorMessage: em, phase: 'outbound_consume' },\n `Outbound pipeline failed (will retry in 1s): ${em}`,\n );\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n }\n\n /**\n * Setup config hot reload using ConfigHotReloader\n */\n private setupConfigReloader(): void {\n this.configReloader = new ConfigHotReloader(\n this.configPath,\n this.config,\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 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.config = newConfig;\n this.emit('config.reload', { section: 'full', requiresRestart: true });\n },\n },\n {\n debounceMs: 300,\n enabled: this.serviceConfig.enableHotReload !== false,\n }\n );\n this.configReloader.start();\n }\n\n /**\n * Handle models config hot reload\n */\n private handleModelsReload(newConfig: Config): void {\n log.debug('Reloading models config...');\n this.config = newConfig;\n getModelRegistry().refresh();\n this.emit('config.reload', { section: 'models' });\n log.debug('Models config reloaded');\n }\n\n /**\n * Handle agent defaults config hot reload\n */\n private handleAgentDefaultsReload(newConfig: Config): void {\n log.debug('Reloading agent defaults...');\n this.config = newConfig;\n this.agentService.applyAgentDefaultsFromConfig(newConfig);\n this.emit('config.reload', { section: 'agents' });\n log.debug('Agent defaults reloaded');\n }\n\n /**\n * Apply `latest.channels` to every registered channel plugin (Telegram, Weixin, extensions).\n * Single runtime path for: file watcher hot reload, API saves, and Weixin QR follow-up.\n */\n private async handleChannelsReload(newConfig: Config): Promise<void> {\n log.debug('Reloading channels config...');\n this.config = newConfig;\n await this.channelManager.updateConfig(newConfig);\n this.emit('config.reload', { section: 'channels' });\n this.emit('channels.status', { channels: this.getChannelsStatus() });\n log.debug('Channels config reloaded');\n }\n\n /** After persisting config to disk: align plugins + debounced reload baseline (watchers skip duplicate diffs). */\n private async syncChannelPluginsAfterPersist(): Promise<void> {\n await this.handleChannelsReload(this.config);\n this.configReloader?.syncCurrentConfig(this.config);\n }\n\n /**\n * Handle cron config hot reload\n */\n private handleCronReload(newConfig: Config): void {\n log.debug('Reloading cron config...');\n this.config = newConfig;\n this.cronService.updateConfig(newConfig);\n this.emit('config.reload', { section: 'cron' });\n log.debug('Cron config reloaded');\n }\n\n /**\n * Handle heartbeat config hot reload\n */\n private handleHeartbeatReload(newConfig: Config): void {\n log.debug('Reloading heartbeat config...');\n this.config = newConfig;\n this.heartbeatService.updateConfig(newConfig);\n this.emit('config.reload', { section: 'heartbeat' });\n log.debug('Heartbeat config reloaded');\n }\n\n /**\n * Apply `gateway.heartbeat` from current config after PATCH /api/config (and when hot reload is off).\n * File watcher uses `handleHeartbeatReload` with the same effect when paths match.\n */\n reloadHeartbeatFromCurrentConfig(): void {\n this.handleHeartbeatReload(this.config);\n }\n\n /**\n * Handle tools config hot reload\n */\n private handleToolsReload(newConfig: Config): void {\n log.debug('Reloading tools config...');\n this.config = newConfig;\n this.emit('config.reload', { section: 'tools' });\n log.debug('Tools config reloaded');\n }\n\n /**\n * Dispatch config hot reload to extensions that registered `registerReload`, matching changed paths.\n */\n private async handleExtensionsReload(\n newConfig: Config,\n changedPaths: string[],\n ): Promise<void> {\n this.config = newConfig;\n this.extensionLoader?.setConfig(this.config as unknown as SurfaceConfig);\n\n if (!this.extensionLoader) {\n this.emit('config.reload', {\n section: 'extensions',\n source: 'extension-reload',\n changedPaths,\n });\n return;\n }\n\n const registry = this.extensionLoader.getRegistry();\n const matchingRegs = registry.getMatchingReloadRegistrations(changedPaths);\n\n if (matchingRegs.length === 0) {\n log.debug({ changedPaths }, 'No extension reload handlers matched');\n this.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.emit('config.reload', {\n section: 'extensions',\n source: 'extension-reload',\n changedPaths,\n });\n }\n\n /**\n * Reload configuration from disk (manual 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 /**\n * After Weixin QR login: token files may change without a `channels.weixin` JSON diff, so run the same\n * channel apply as an API save, then force Weixin long-poll restart (see `reloadMonitorsWithConfig`).\n */\n async afterWeixinCredentialsPersisted(): Promise<void> {\n const next = loadConfig(this.configPath);\n this.config = next;\n this.agentService.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.config, this.bus);\n log.info('Weixin monitors restarted after credential login');\n }\n\n /**\n * After Feishu WebUI QR setup: `channels.feishu` was written directly to disk; reload into memory\n * and apply channel plugins (same baseline as PATCH /api/config).\n */\n async afterFeishuCredentialsPersisted(): Promise<void> {\n const next = loadConfig(this.configPath);\n this.config = next;\n this.agentService.applyAgentDefaultsFromConfig(next);\n this.configReloader?.syncCurrentConfig(next);\n await this.handleChannelsReload(next);\n log.info('Feishu config applied after QR setup');\n }\n\n /**\n * Save current config to disk\n */\n /**\n * Persist and replace `this.config` with the validated file contents so runtime matches disk\n * (PATCH merge objects can drift from Zod-normalized output).\n */\n private async writeConfigAndReloadFromDisk(configToWrite: Config): Promise<void> {\n await writeConfigToDisk(configToWrite, this.configPath);\n this.config = loadConfig(this.configPath);\n this.agentService.applyAgentDefaultsFromConfig(this.config);\n // Hot-apply: reconcile managed dreaming cron jobs immediately after config persists.\n await this.agentService.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 }\n\n async saveConfig(config: Config): Promise<{ saved: boolean; error?: string }> {\n try {\n await this.writeConfigAndReloadFromDisk(config);\n await this.syncChannelPluginsAfterPersist();\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 /**\n * App store (phase 1): persist `extensions.enabled` / `extensions.disabled` for a bundled extension.\n * Extension modules are loaded at gateway startup; restart the gateway process to fully apply load/unload.\n */\n async setBundledExtensionActivationTarget(\n extensionId: string,\n wanted: boolean,\n ): Promise<{ ok: boolean; error?: string; requiresGatewayRestart: boolean }> {\n const loader = this.extensionLoader;\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.config, id, wanted);\n if (patch.ok === false) {\n return { ok: false, error: patch.error, requiresGatewayRestart: false };\n }\n const newConfig = { ...this.config, 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.config as unknown as SurfaceConfig);\n return { ok: true, requiresGatewayRestart: true };\n }\n\n /**\n * Update configuration and persist to disk\n */\n async updateConfig(updates: Partial<Config>): Promise<{ updated: boolean; error?: string }> {\n try {\n log.debug('Updating configuration...');\n \n // Merge updates\n this.config = { ...this.config, ...updates };\n\n await this.writeConfigAndReloadFromDisk(this.config);\n await this.syncChannelPluginsAfterPersist();\n\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 * Run agent with a message and stream events\n */\n /**\n * @param runOptions.signal — When set (e.g. client disconnect), aborts in-flight generation and persists partial output.\n */\n async *runAgent(\n message: string,\n channel: string,\n chatId: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n }>,\n thinking?: string,\n runOptions?: { signal?: AbortSignal },\n ): AsyncGenerator<{ type: string; content?: string; status?: string; runId?: string }, { 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\n // For webchat, register the run in the relay before yielding the first event\n if (channel === 'webchat') {\n const parsedKey = parseSessionKey(chatId);\n const sessionKey = parsedKey ? chatId : buildSessionKey({\n agentId: getDefaultAgentId(this.config),\n source: 'webchat',\n accountId: 'default',\n peerKind: 'direct',\n peerId: chatId,\n });\n this.runRelay.ensureRun(runId, sessionKey);\n this.runAbortControllers.set(runId, new AbortController());\n }\n\n const statusEvent = { type: 'status', status: 'accepted', runId };\n if (channel === 'webchat') this.runRelay.publish(runId, statusEvent);\n yield statusEvent;\n\n try {\n // For 'webchat' channel (web UI), process through agent service\n if (channel === 'webchat') {\n // Determine session key: if chatId is already a valid session key, use it directly\n // Otherwise, build a new session key from the chatId\n const parsedKey = parseSessionKey(chatId);\n const sessionKey = parsedKey ? chatId : buildSessionKey({\n agentId: getDefaultAgentId(this.config),\n source: 'webchat',\n accountId: 'default',\n peerKind: 'direct',\n peerId: chatId,\n });\n\n const timezone = this.agentService.resolveUserTimezoneForSession(sessionKey);\n // Keep UI clean: persist raw user text (no envelope timestamp),\n // but include a stamped variant for the model so it has a stable \"now\".\n // Skip for slash commands — parseSlashCommand requires lines starting with '/'.\n const stampedMessage = message.trimStart().startsWith('/')\n ? message\n : prependEnvelopeTimestamp(message, timezone);\n const prepared = await this.agentService.prepareInboundAttachments(sessionKey, cappedAttachments);\n\n // Persist before streaming so a mid-turn refresh still sees text + attachment refs on disk.\n try {\n await this._saveUserMessage(sessionKey, message, prepared);\n } catch (err) {\n log.error({ err, sessionKey }, 'Failed to save user message');\n }\n\n const runAbort = this.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 this.activeWebchatRunBySession.set(sessionKey, runId);\n let streamError: string | undefined;\n try {\n this.emit('agent.stream', { sessionKey, event: statusEvent });\n const eventStream = this.agentService.processDirectStreaming(stampedMessage, sessionKey, prepared, thinking, {\n signal: mergedSignal,\n });\n\n for await (const event of eventStream) {\n this.runRelay.publish(runId, event);\n this.emit('agent.stream', { sessionKey, event });\n yield event as { type: string; content?: string; status?: string; runId?: string };\n }\n\n this.runRelay.complete(runId);\n try {\n const metaAfter = await this.sessionManager.getSessionMetadata(sessionKey);\n if (metaAfter?.name) {\n this.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 errorEvent = { type: 'error', content: `Error: ${streamError}` };\n this.runRelay.publish(runId, errorEvent);\n this.emit('agent.stream', { sessionKey, event: errorEvent });\n this.runRelay.complete(runId);\n yield errorEvent;\n return { status: 'error', summary: streamError };\n } finally {\n this.activeWebchatRunBySession.delete(sessionKey);\n this.runAbortControllers.delete(runId);\n const assistantPlainText = this.agentService.getLastAssistantPlainText(sessionKey);\n await this.agentService.emitWebchatTurnComplete({\n sessionKey,\n inboundUserText: message,\n assistantPlainText,\n aborted: mergedSignal.aborted,\n ...(streamError !== undefined ? { streamError } : {}),\n });\n }\n }\n\n // Send message through bus for other channels (telegram, etc.)\n const correlationMeta = inboundCorrelationMetadataFromAsyncLogContext();\n await this.bus.publishInbound({\n channel,\n sender_id: 'gateway',\n chat_id: chatId,\n content: message,\n ...(correlationMeta ? { metadata: correlationMeta } : {}),\n });\n\n // Wait for and collect response\n // This is simplified - in production we'd need proper session tracking\n yield { type: 'token', content: 'Processing...\\n' };\n\n // Simulate processing delay\n await new Promise((resolve) => setTimeout(resolve, 1000));\n\n yield { type: 'token', content: 'Done\\n' };\n\n return { status: 'ok', summary: 'Message processed' };\n } catch (error) {\n log.error({ error }, 'Agent run failed');\n throw error;\n }\n }\n\n /** Abort an in-flight webchat agent run (matches `runId` from SSE `status`). */\n abortAgentRun(runId: string): boolean {\n this.clarifyBridge.cancelForRun(runId);\n for (const [sk, id] of this.activeWebchatRunBySession) {\n if (id === runId) {\n this.activeWebchatRunBySession.delete(sk);\n }\n }\n const c = this.runAbortControllers.get(runId);\n if (!c) {\n return false;\n }\n c.abort();\n return true;\n }\n\n /** Background drain for extension-initiated webchat turns (`scheduleWebchatContinuation`). */\n private async drainScheduledWebchatContinuation(sessionKey: string, message: string): Promise<void> {\n try {\n const gen = this.runAgent(message, 'webchat', sessionKey, undefined, undefined, undefined);\n for await (const _ of gen) {\n // Relay + persistence; no HTTP client attached.\n }\n } catch (err) {\n log.warn({ err, sessionKey }, 'Scheduled webchat continuation failed');\n }\n }\n\n /**\n * Queue steering text for an active webchat run (`Agent.steer` / tool-boundary injection).\n * `chatId` is the same as `POST /api/agent` body (`sessionKey` or legacy peer id).\n */\n async steerWebchatAgent(\n chatId: string,\n message: string,\n ): Promise<{ ok: true } | { ok: false; code: 'BAD_REQUEST' | 'NO_ACTIVE_RUN' | 'STEER_FAILED' }> {\n const trimmed = message.trim();\n if (!trimmed) {\n return { ok: false, code: 'BAD_REQUEST' };\n }\n const parsedKey = parseSessionKey(chatId);\n const sessionKey = parsedKey\n ? chatId\n : buildSessionKey({\n agentId: getDefaultAgentId(this.config),\n source: 'webchat',\n accountId: 'default',\n peerKind: 'direct',\n peerId: chatId,\n });\n if (!this.activeWebchatRunBySession.has(sessionKey)) {\n return { ok: false, code: 'NO_ACTIVE_RUN' };\n }\n const steered = await this.agentService.steerWebchatSession(sessionKey, trimmed);\n if (!steered) {\n return { ok: false, code: 'STEER_FAILED' };\n }\n return { ok: true };\n }\n\n private async deliverTelegramClarify(ctx: {\n sessionKey: string;\n requestId: string;\n request: ClarifyBridgeRequest;\n }): Promise<void> {\n const parsed = parseSessionKey(ctx.sessionKey);\n if (!parsed || parsed.source !== 'telegram') {\n return;\n }\n\n let body = ctx.request.question;\n if (ctx.request.default) {\n body += `\\n\\nDefault if unsure: ${ctx.request.default}`;\n }\n\n const choices = ctx.request.choices;\n const buttonRows =\n choices && choices.length >= 2\n ? choices.map((c, i) => [\n {\n text: c.length > 64 ? `${c.slice(0, 61)}…` : c,\n callback_data: `clarify:${ctx.requestId}:${i}`,\n },\n ])\n : undefined;\n\n if (!buttonRows) {\n body += '\\n\\nReply with your answer in this chat.';\n }\n\n await this.channelManager.send({\n channel: 'telegram',\n chat_id: parsed.peerId,\n content: body,\n metadata: {\n accountId: parsed.accountId,\n ...(parsed.threadId ? { threadId: parsed.threadId } : {}),\n },\n buttons: buttonRows,\n });\n }\n\n /** Deliver a user's answer to a pending `clarify` tool call. */\n submitClarifyResponse(requestId: string, answer: string): boolean {\n return this.clarifyBridge.handleResponse(requestId, answer);\n }\n\n /**\n * Send message through a channel\n */\n async sendMessage(\n channel: string,\n chatId: string,\n content: string\n ): Promise<{ sent: boolean; messageId?: string }> {\n try {\n await this.channelManager.send({\n channel,\n chat_id: chatId,\n content,\n });\n const messageId = `msg_${Date.now()}`;\n this.emit('message.sent', { channel, chatId, messageId });\n return { sent: true, messageId };\n } catch (error) {\n log.error({ channel, chatId, error }, 'Failed to send message');\n throw error;\n }\n }\n\n /**\n * Get channel statuses\n */\n getChannelsStatus(): Array<{\n name: string;\n enabled: boolean;\n connected: boolean;\n }> {\n const runningChannels = new Set(this.channelManager.getRunningChannels());\n const channels = this.config.channels as Record<string, { enabled?: boolean } | undefined> | undefined;\n const builtinOrder = CHAT_CHANNEL_ORDER as readonly string[];\n\n const rows: Array<{ name: string; enabled: boolean; connected: boolean }> = CHAT_CHANNEL_ORDER.map(\n (name) => ({\n name,\n enabled: !!channels?.[name]?.enabled,\n connected: runningChannels.has(name),\n }),\n );\n\n const extReg = this.extensionLoader?.getRegistry();\n const extraIds = extReg?.channelPlugins.map((p) => p.id).filter((id) => !builtinOrder.includes(id)) ?? [];\n if (extraIds.length === 0) {\n return rows;\n }\n\n const seen = new Set(builtinOrder);\n for (const name of extraIds) {\n if (seen.has(name)) continue;\n seen.add(name);\n rows.push({\n name,\n enabled: channels?.[name]?.enabled !== false,\n connected: runningChannels.has(name),\n });\n }\n\n return rows;\n }\n\n /**\n * Request an immediate heartbeat run (coalesced like interval/cron wakes).\n */\n requestHeartbeatNow(opts?: { reason?: string }): void {\n this.heartbeatService.requestNow({ reason: opts?.reason ?? 'manual' });\n }\n\n /**\n * Register graceful shutdown used after spawning a replacement gateway process (foreground CLI server).\n */\n registerGatewayShutdownForRestart(handler: () => Promise<void>): void {\n this.gatewayShutdownForRestart = handler;\n }\n\n /**\n * Respawn the gateway process when supported (spawn + exit, supervisor exit, or disabled when XOPC_NO_RESPAWN).\n */\n triggerGatewayProcessRestart(): { ok: boolean; mode: string; message?: string } {\n const result = restartGatewayProcessWithFreshPid();\n if (result.mode === 'failed') {\n return { ok: false, mode: result.mode, message: result.detail ?? 'spawn failed' };\n }\n if (result.mode === 'disabled') {\n return {\n ok: false,\n mode: 'disabled',\n message:\n 'Process respawn is disabled (XOPC_NO_RESPAWN). Restart the gateway manually (e.g. xopc gateway restart).',\n };\n }\n const shutdown = this.gatewayShutdownForRestart;\n if (!shutdown) {\n return {\n ok: false,\n mode: result.mode,\n message: 'Gateway restart is not available in this process.',\n };\n }\n setImmediate(() => {\n void shutdown().finally(() => {\n process.exit(0);\n });\n });\n return { ok: true, mode: result.mode };\n }\n\n /**\n * Get health status\n */\n getHealth(): {\n status: string;\n service: string;\n version: string;\n uptime: number;\n channels: { running: number; total: number };\n configPath: string;\n logs?: {\n dir: string;\n errors24h: number;\n stats: Record<string, number>;\n };\n } {\n const runningChannels = this.channelManager.getRunningChannels();\n const allChannels = this.channelManager.getAllChannels();\n const logStats = getLogStats();\n\n return {\n status: 'ok',\n service: 'xopc-gateway',\n version: PACKAGE_VERSION,\n uptime: Math.floor((Date.now() - this.startTime) / 1000),\n channels: {\n running: runningChannels.length,\n total: allChannels.length,\n },\n configPath: this.configPath,\n logs: {\n dir: getLogDir(),\n errors24h: logStats.errorsLast24h,\n stats: logStats.byLevel,\n },\n };\n }\n\n get isRunning(): boolean {\n return this.running;\n }\n\n /**\n * Get extension registry for external access (HTTP routes, gateway methods)\n */\n getExtensionRegistry() {\n return this.extensionLoader?.getRegistry();\n }\n\n /** Extension loader for discovery and frontend asset APIs (may be null if extensions failed to init). */\n getExtensionLoader(): ExtensionLoader | null {\n return this.extensionLoader;\n }\n\n /**\n * Get model registry for external access (HTTP routes)\n */\n getModelRegistry() {\n const { getModelRegistry } = require('../providers/index.js');\n return getModelRegistry();\n }\n\n /**\n * Invoke a gateway method registered by extensions\n */\n async invokeGatewayMethod(method: string, params: Record<string, unknown>): Promise<unknown> {\n const registry = this.getExtensionRegistry();\n if (!registry) {\n throw new Error('Extension registry not available');\n }\n\n const handler = registry.getGatewayMethod(method);\n if (!handler) {\n throw new Error(`Gateway method not found: ${method}`);\n }\n\n return await handler(params);\n }\n\n get currentConfig(): Config {\n return this.config;\n }\n\n get cronServiceInstance(): CronService {\n return this.cronService;\n }\n\n getSkillsApi(): { catalog: SkillCatalogEntry[]; managed: ManagedSkillListItem[] } {\n return {\n catalog: this.agentService.getSkillCatalog(),\n managed: listManagedSkillDirs(),\n };\n }\n\n getSkillMarkdownSource(skillName: string): { name: string; markdown: string } | null {\n return this.agentService.getSkillMarkdownSource(skillName);\n }\n\n deleteManagedSkill(skillId: string): void {\n removeSkillsLockEntry(skillId);\n deleteManagedSkillDir(skillId);\n this.agentService.refreshSkillsAfterDiskChange();\n }\n\n installManagedSkillZip(\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.agentService.refreshSkillsAfterDiskChange();\n return result;\n }\n\n async fetchSkillsMarketplaceCatalog(params: SkillsStoreListParams): Promise<SkillsStoreListResponse> {\n const base = resolveSkillsStoreBaseUrl(this.config);\n return listSkillPackages(base, params);\n }\n\n async fetchSkillsMarketplacePackageDetail(packageName: string): Promise<MarketplacePackageDetail> {\n const base = resolveSkillsStoreBaseUrl(this.config);\n return fetchMarketplacePackageDetail(base, packageName);\n }\n\n async installSkillFromMarketplace(opts: {\n name: string;\n version?: string;\n overwrite?: boolean;\n }): Promise<{ skillId: string; path: string }> {\n const base = resolveSkillsStoreBaseUrl(this.config);\n const { downloadUrl } = await resolveSkillZipDownloadUrl(base, opts.name, opts.version);\n const buf = await downloadSkillZipBuffer(base, downloadUrl);\n const skillId = skillIdForMarketplaceInstall(opts.name);\n return this.installManagedSkillZip(buf, { skillId, overwrite: opts.overwrite ?? false });\n }\n\n reloadSkillsFromDisk(): void {\n this.agentService.refreshSkillsAfterDiskChange();\n }\n\n patchSkillEnabled(skillName: string, enabled: boolean): void {\n createSkillConfigManager(resolveStateDir()).setSkillEnabled(skillName, enabled);\n this.agentService.refreshSkillsAfterSkillConfigChange();\n }\n\n get sessionManagerInstance(): SessionManager {\n return this.sessionManager;\n }\n\n async getSessionAgentConfig(sessionKey: string) {\n return this.agentService.getSessionAgentConfig(sessionKey);\n }\n\n /** Resolved markdown workspace for a session (after hydration / mkdir). Used by workspace file API when `sessionKey` is passed. */\n async getEffectiveWorkspacePathForSession(sessionKey: string): Promise<string> {\n return this.agentService.getEffectiveWorkspacePathForSession(sessionKey);\n }\n\n async patchSessionAgentConfig(sessionKey: string, body: {\n thinkingLevel?: string;\n model?: string | null;\n reasoningLevel?: string;\n workingDirectory?: string;\n }) {\n return this.agentService.patchSessionAgentConfig(sessionKey, body);\n }\n\n /**\n * Process a message directly through the agent (for CLI mode)\n */\n async processDirect(content: string, sessionKey = 'cli:direct'): Promise<string> {\n return this.agentService.processDirect(content, sessionKey);\n }\n\n // ========== SSE Event System ==========\n\n /**\n * Subscribe to server-pushed events.\n * Returns a cleanup function to unsubscribe.\n */\n subscribe(sessionId: string, listener: EventListener): () => 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 // Keep buffer for a while in case they reconnect\n setTimeout(() => {\n if (!this.subscribers.has(sessionId)) {\n this.eventBuffers.delete(sessionId);\n }\n }, 5 * 60_000); // 5 min grace\n log.debug({ sessionId }, 'Event subscriber removed');\n };\n }\n\n /**\n * Emit an event to all subscribers.\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 // Buffer the event\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 // Deliver\n try {\n listener(event);\n } catch (err) {\n log.warn({ sessionId, err }, 'Failed to deliver event to subscriber');\n }\n }\n }\n\n /**\n * Get events since a given event id (for Last-Event-ID reconnection).\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; // can't find cursor — send everything in buffer\n return buf.slice(idx + 1);\n }\n\n /**\n * Save user message to session for webchat (fire-and-forget).\n * Called at the start of runAgent to ensure message survives page refresh.\n */\n private async _saveUserMessage(\n sessionKey: string,\n message: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n }>,\n ): Promise<void> {\n // Load existing messages\n const existingMessages = await this.sessionManager.loadMessages(sessionKey);\n\n // Build user message (marker stripped in SessionStore.convertMessages for API clients)\n const userMessage = {\n role: 'user' as const,\n content: [{ type: 'text' as const, text: message }],\n attachments: attachments?.map((a) => ({\n type: a.type,\n mimeType: a.mimeType,\n name: a.name,\n size: a.size,\n workspaceRelativePath: a.workspaceRelativePath,\n // Note: we don't store data (base64) to keep session file small\n })),\n timestamp: Date.now(),\n /** Dropped before `agent.prompt` so we don't duplicate the turn; not exposed via GET session */\n webchatEarlySave: true as const,\n };\n\n // Append and save\n const updatedMessages = [...existingMessages, userMessage];\n await this.sessionManager.saveMessages(sessionKey, updatedMessages);\n log.debug({ sessionKey, messageCount: updatedMessages.length }, 'User message saved');\n }\n\n // ========== Session Management API ==========\n\n /**\n * List sessions with query filters\n */\n async listSessions(query?: SessionListQuery) {\n return this.sessionManager.listSessions(query);\n }\n\n /**\n * List all subagent sessions.\n * Subagent sessions have keys starting with 'subagent:'.\n */\n async listSubagents(query?: SessionListQuery) {\n return this.sessionManager.listSubagents(query);\n }\n\n /**\n * Get a single session by key\n */\n async getSession(key: string) {\n return this.sessionManager.getSession(key);\n }\n\n /**\n * Delete a session\n */\n async deleteSession(key: string): Promise<{ deleted: boolean }> {\n const result = await this.sessionManager.deleteSession(key);\n return { deleted: result };\n }\n\n /**\n * Delete multiple sessions\n */\n async deleteSessions(keys: string[]): Promise<{ success: string[]; failed: string[] }> {\n return this.sessionManager.deleteSessions(keys);\n }\n\n /**\n * Rename a session\n */\n async renameSession(key: string, name: string): Promise<{ renamed: boolean }> {\n await this.sessionManager.renameSession(key, name);\n return { renamed: true };\n }\n\n /**\n * Tag a session\n */\n async tagSession(key: string, tags: string[]): Promise<{ tagged: boolean }> {\n await this.sessionManager.tagSession(key, tags);\n return { tagged: true };\n }\n\n /**\n * Remove tags from a session\n */\n async untagSession(key: string, tags: string[]): Promise<{ untagged: boolean }> {\n await this.sessionManager.untagSession(key, tags);\n return { untagged: true };\n }\n\n /**\n * Archive a session\n */\n async archiveSession(key: string): Promise<{ archived: boolean }> {\n await this.sessionManager.archiveSession(key);\n return { archived: true };\n }\n\n /**\n * Unarchive a session\n */\n async unarchiveSession(key: string): Promise<{ unarchived: boolean }> {\n await this.sessionManager.unarchiveSession(key);\n return { unarchived: true };\n }\n\n /**\n * Pin a session\n */\n async pinSession(key: string): Promise<{ pinned: boolean }> {\n await this.sessionManager.pinSession(key);\n return { pinned: true };\n }\n\n /**\n * Unpin a session\n */\n async unpinSession(key: string): Promise<{ unpinned: boolean }> {\n await this.sessionManager.unpinSession(key);\n return { unpinned: true };\n }\n\n /**\n * Search sessions\n */\n async searchSessions(query: string) {\n return this.sessionManager.searchSessions(query);\n }\n\n /**\n * Search within a session\n */\n async searchInSession(key: string, keyword: string) {\n return this.sessionManager.searchInSession(key, keyword);\n }\n\n /**\n * Export a session\n */\n async exportSession(key: string, format: ExportFormat): Promise<{ content: string }> {\n const content = await this.sessionManager.exportSession(key, format);\n return { content };\n }\n\n /**\n * Get session statistics\n */\n async getSessionStats() {\n return this.sessionManager.getStats();\n }\n\n /**\n * Get unique chat IDs from sessions, grouped by channel\n * Returns a list of channel/chatId pairs for cron job configuration.\n * `chatId` is the session-store routing suffix (unique per bot account + peer).\n * When `routing` exists, `peerId` is the platform id (e.g. Telegram numeric chat id).\n */\n async getSessionChatIds(channel?: string): Promise<\n Array<{\n channel: string;\n chatId: string;\n lastActive: string;\n accountId?: string;\n peerKind?: string;\n peerId?: string;\n }>\n > {\n const result = await this.sessionManager.listSessions({\n limit: 1000,\n sortBy: 'lastAccessedAt',\n sortOrder: 'desc',\n ...(channel ? { channel } : {}),\n });\n\n // Group by channel:chatId to get unique pairs\n const seen = new Set<string>();\n const chatIds: Array<{\n channel: string;\n chatId: string;\n lastActive: string;\n accountId?: string;\n peerKind?: string;\n peerId?: string;\n }> = [];\n\n for (const session of result.items) {\n const key = `${session.sourceChannel}:${session.sourceChatId}`;\n if (!seen.has(key) && session.sourceChannel && session.sourceChatId) {\n seen.add(key);\n const r = session.routing;\n chatIds.push({\n channel: session.sourceChannel,\n chatId: session.sourceChatId,\n lastActive: session.lastAccessedAt,\n ...(r\n ? {\n accountId: r.accountId,\n peerKind: r.peerKind,\n peerId: r.peerId,\n }\n : {}),\n });\n }\n }\n\n return chatIds;\n }\n\n /**\n * Validate authentication token from request headers.\n * Returns true if auth is disabled (mode: 'none') or token is valid.\n */\n validateAuth(headers?: Record<string, string | string[] | undefined>): boolean {\n const token = extractToken(headers);\n return validateToken(this.auth, token);\n }\n\n /**\n * Get current auth mode.\n */\n getAuthMode(): 'none' | 'token' | 'password' {\n return this.auth.mode;\n }\n\n /**\n * Get current auth token (for CLI server integration).\n * Returns undefined if mode is 'none'.\n */\n getAuthToken(): string | undefined {\n return this.auth.mode === 'token' ? this.auth.token : undefined;\n }\n\n /**\n * Refresh (regenerate) the gateway auth token.\n * Returns the new token.\n */\n async refreshAuthToken(): Promise<string> {\n if (this.auth.mode !== 'token') {\n throw new Error('Cannot refresh token: auth mode is not token');\n }\n\n // Generate new token\n const newToken = crypto.randomBytes(24).toString('hex');\n \n // Update in-memory auth\n this.auth.token = newToken;\n \n // Update config\n this.config = {\n ...this.config,\n gateway: {\n ...this.config.gateway,\n auth: {\n ...this.config.gateway?.auth,\n mode: 'token',\n token: newToken,\n },\n },\n };\n \n await this.writeConfigAndReloadFromDisk(this.config);\n\n log.info({ tokenPreview: `${newToken.slice(0, 8)}...` }, 'Gateway token refreshed');\n \n return newToken;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { Message } from './types.js';
2
+ /** Transcript row for TUI and HTTP clients (flattened from persisted session messages). */
3
+ export interface ClientHistoryMessage {
4
+ role: 'user' | 'assistant' | 'system';
5
+ content: string;
6
+ timestamp?: number;
7
+ toolCalls?: Array<{
8
+ name: string;
9
+ args?: unknown;
10
+ result?: string;
11
+ isError?: boolean;
12
+ }>;
13
+ }
14
+ export declare function flattenMessageContent(content: string | unknown[]): string;
15
+ /**
16
+ * Maps gateway/session API messages into a linear chat history for clients.
17
+ * Tool / toolResult rows are merged into assistant `toolCalls` when ids match; otherwise skipped.
18
+ */
19
+ export declare function messagesToClientHistory(messages: Message[], opts?: {
20
+ limit?: number;
21
+ }): ClientHistoryMessage[];
@@ -0,0 +1,89 @@
1
+ //#region src/session/client-history.ts
2
+ function flattenMessageContent(content) {
3
+ if (typeof content === "string") return content;
4
+ if (!Array.isArray(content)) return "";
5
+ const parts = [];
6
+ for (const block of content) {
7
+ if (typeof block !== "object" || block === null) continue;
8
+ const b = block;
9
+ if (b.type === "text" && typeof b.text === "string") parts.push(b.text);
10
+ }
11
+ return parts.join("");
12
+ }
13
+ function parseTimestamp(ts) {
14
+ if (!ts) return void 0;
15
+ const ms = Date.parse(ts);
16
+ return Number.isFinite(ms) ? ms : void 0;
17
+ }
18
+ function safeJsonParseArguments(raw) {
19
+ const t = raw.trim();
20
+ if (!t) return void 0;
21
+ try {
22
+ return JSON.parse(t);
23
+ } catch {
24
+ return raw;
25
+ }
26
+ }
27
+ function collectToolResults(messages) {
28
+ const map = /* @__PURE__ */ new Map();
29
+ for (const m of messages) {
30
+ if (m.role !== "tool" && m.role !== "toolResult") continue;
31
+ const id = m.tool_call_id;
32
+ if (!id || typeof id !== "string") continue;
33
+ map.set(id, {
34
+ text: flattenMessageContent(m.content),
35
+ isError: m.isError
36
+ });
37
+ }
38
+ return map;
39
+ }
40
+ function toolCallsWithResults(tool_calls, results) {
41
+ if (!tool_calls?.length) return void 0;
42
+ const out = [];
43
+ for (const tc of tool_calls) {
44
+ const res = results.get(tc.id);
45
+ out.push({
46
+ name: tc.function.name,
47
+ args: safeJsonParseArguments(tc.function.arguments),
48
+ result: res?.text,
49
+ isError: res?.isError
50
+ });
51
+ }
52
+ return out.length ? out : void 0;
53
+ }
54
+ /**
55
+ * Maps gateway/session API messages into a linear chat history for clients.
56
+ * Tool / toolResult rows are merged into assistant `toolCalls` when ids match; otherwise skipped.
57
+ */
58
+ function messagesToClientHistory(messages, opts) {
59
+ const slice = opts?.limit !== void 0 && messages.length > opts.limit ? messages.slice(-opts.limit) : messages;
60
+ const results = collectToolResults(slice);
61
+ const out = [];
62
+ for (const m of slice) {
63
+ if (m.role === "tool" || m.role === "toolResult") continue;
64
+ if (m.role === "user" || m.role === "system") {
65
+ const text = flattenMessageContent(m.content);
66
+ out.push({
67
+ role: m.role,
68
+ content: text,
69
+ timestamp: parseTimestamp(m.timestamp)
70
+ });
71
+ continue;
72
+ }
73
+ if (m.role === "assistant") {
74
+ const text = flattenMessageContent(m.content);
75
+ const toolCalls = toolCallsWithResults(m.tool_calls, results);
76
+ out.push({
77
+ role: "assistant",
78
+ content: text,
79
+ timestamp: parseTimestamp(m.timestamp),
80
+ toolCalls
81
+ });
82
+ }
83
+ }
84
+ return out;
85
+ }
86
+ //#endregion
87
+ export { flattenMessageContent, messagesToClientHistory };
88
+
89
+ //# sourceMappingURL=client-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-history.js","names":[],"sources":["../../../src/session/client-history.ts"],"sourcesContent":["import type { Message } from './types.js';\n\n/** Transcript row for TUI and HTTP clients (flattened from persisted session messages). */\nexport interface ClientHistoryMessage {\n role: 'user' | 'assistant' | 'system';\n content: string;\n timestamp?: number;\n toolCalls?: Array<{ name: string; args?: unknown; result?: string; isError?: boolean }>;\n}\n\nexport function flattenMessageContent(content: string | unknown[]): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block !== 'object' || block === null) continue;\n const b = block as Record<string, unknown>;\n if (b.type === 'text' && typeof b.text === 'string') {\n parts.push(b.text);\n }\n }\n return parts.join('');\n}\n\nfunction parseTimestamp(ts: string | undefined): number | undefined {\n if (!ts) return undefined;\n const ms = Date.parse(ts);\n return Number.isFinite(ms) ? ms : undefined;\n}\n\nfunction safeJsonParseArguments(raw: string): unknown {\n const t = raw.trim();\n if (!t) return undefined;\n try {\n return JSON.parse(t) as unknown;\n } catch {\n return raw;\n }\n}\n\nfunction collectToolResults(messages: Message[]): Map<string, { text: string; isError?: boolean }> {\n const map = new Map<string, { text: string; isError?: boolean }>();\n for (const m of messages) {\n if (m.role !== 'tool' && m.role !== 'toolResult') continue;\n const id = m.tool_call_id;\n if (!id || typeof id !== 'string') continue;\n map.set(id, {\n text: flattenMessageContent(m.content),\n isError: m.isError,\n });\n }\n return map;\n}\n\nfunction toolCallsWithResults(\n tool_calls: Message['tool_calls'],\n results: Map<string, { text: string; isError?: boolean }>,\n): ClientHistoryMessage['toolCalls'] | undefined {\n if (!tool_calls?.length) return undefined;\n const out: NonNullable<ClientHistoryMessage['toolCalls']> = [];\n for (const tc of tool_calls) {\n const res = results.get(tc.id);\n out.push({\n name: tc.function.name,\n args: safeJsonParseArguments(tc.function.arguments),\n result: res?.text,\n isError: res?.isError,\n });\n }\n return out.length ? out : undefined;\n}\n\n/**\n * Maps gateway/session API messages into a linear chat history for clients.\n * Tool / toolResult rows are merged into assistant `toolCalls` when ids match; otherwise skipped.\n */\nexport function messagesToClientHistory(\n messages: Message[],\n opts?: { limit?: number },\n): ClientHistoryMessage[] {\n const slice =\n opts?.limit !== undefined && messages.length > opts.limit\n ? messages.slice(-opts.limit)\n : messages;\n\n const results = collectToolResults(slice);\n const out: ClientHistoryMessage[] = [];\n\n for (const m of slice) {\n if (m.role === 'tool' || m.role === 'toolResult') {\n continue;\n }\n\n if (m.role === 'user' || m.role === 'system') {\n const text = flattenMessageContent(m.content);\n out.push({\n role: m.role,\n content: text,\n timestamp: parseTimestamp(m.timestamp),\n });\n continue;\n }\n\n if (m.role === 'assistant') {\n const text = flattenMessageContent(m.content);\n const toolCalls = toolCallsWithResults(m.tool_calls, results);\n out.push({\n role: 'assistant',\n content: text,\n timestamp: parseTimestamp(m.timestamp),\n toolCalls,\n });\n }\n }\n\n return out;\n}\n"],"mappings":";AAUA,SAAgB,sBAAsB,SAAqC;AACzE,KAAI,OAAO,YAAY,SAAU,QAAO;AACxC,KAAI,CAAC,MAAM,QAAQ,QAAQ,CAAE,QAAO;CACpC,MAAM,QAAkB,EAAE;AAC1B,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM;EACjD,MAAM,IAAI;AACV,MAAI,EAAE,SAAS,UAAU,OAAO,EAAE,SAAS,SACzC,OAAM,KAAK,EAAE,KAAK;;AAGtB,QAAO,MAAM,KAAK,GAAG;;AAGvB,SAAS,eAAe,IAA4C;AAClE,KAAI,CAAC,GAAI,QAAO,KAAA;CAChB,MAAM,KAAK,KAAK,MAAM,GAAG;AACzB,QAAO,OAAO,SAAS,GAAG,GAAG,KAAK,KAAA;;AAGpC,SAAS,uBAAuB,KAAsB;CACpD,MAAM,IAAI,IAAI,MAAM;AACpB,KAAI,CAAC,EAAG,QAAO,KAAA;AACf,KAAI;AACF,SAAO,KAAK,MAAM,EAAE;SACd;AACN,SAAO;;;AAIX,SAAS,mBAAmB,UAAuE;CACjG,MAAM,sBAAM,IAAI,KAAkD;AAClE,MAAK,MAAM,KAAK,UAAU;AACxB,MAAI,EAAE,SAAS,UAAU,EAAE,SAAS,aAAc;EAClD,MAAM,KAAK,EAAE;AACb,MAAI,CAAC,MAAM,OAAO,OAAO,SAAU;AACnC,MAAI,IAAI,IAAI;GACV,MAAM,sBAAsB,EAAE,QAAQ;GACtC,SAAS,EAAE;GACZ,CAAC;;AAEJ,QAAO;;AAGT,SAAS,qBACP,YACA,SAC+C;AAC/C,KAAI,CAAC,YAAY,OAAQ,QAAO,KAAA;CAChC,MAAM,MAAsD,EAAE;AAC9D,MAAK,MAAM,MAAM,YAAY;EAC3B,MAAM,MAAM,QAAQ,IAAI,GAAG,GAAG;AAC9B,MAAI,KAAK;GACP,MAAM,GAAG,SAAS;GAClB,MAAM,uBAAuB,GAAG,SAAS,UAAU;GACnD,QAAQ,KAAK;GACb,SAAS,KAAK;GACf,CAAC;;AAEJ,QAAO,IAAI,SAAS,MAAM,KAAA;;;;;;AAO5B,SAAgB,wBACd,UACA,MACwB;CACxB,MAAM,QACJ,MAAM,UAAU,KAAA,KAAa,SAAS,SAAS,KAAK,QAChD,SAAS,MAAM,CAAC,KAAK,MAAM,GAC3B;CAEN,MAAM,UAAU,mBAAmB,MAAM;CACzC,MAAM,MAA8B,EAAE;AAEtC,MAAK,MAAM,KAAK,OAAO;AACrB,MAAI,EAAE,SAAS,UAAU,EAAE,SAAS,aAClC;AAGF,MAAI,EAAE,SAAS,UAAU,EAAE,SAAS,UAAU;GAC5C,MAAM,OAAO,sBAAsB,EAAE,QAAQ;AAC7C,OAAI,KAAK;IACP,MAAM,EAAE;IACR,SAAS;IACT,WAAW,eAAe,EAAE,UAAU;IACvC,CAAC;AACF;;AAGF,MAAI,EAAE,SAAS,aAAa;GAC1B,MAAM,OAAO,sBAAsB,EAAE,QAAQ;GAC7C,MAAM,YAAY,qBAAqB,EAAE,YAAY,QAAQ;AAC7D,OAAI,KAAK;IACP,MAAM;IACN,SAAS;IACT,WAAW,eAAe,EAAE,UAAU;IACtC;IACD,CAAC;;;AAIN,QAAO"}
@@ -13,3 +13,4 @@ export { SessionStatus, type SessionMetadata, type SessionDetail, type SessionIn
13
13
  export type { CompactionConfig, CompactionResult } from '../agent/memory/compaction.js';
14
14
  export type { WindowConfig } from '../agent/memory/window.js';
15
15
  export { maybeAutoTitleSessionStore, generateSessionTitleFromMessages, sanitizeSessionTitle, fallbackTitleFromMessages, isWebchatSessionKey, shouldAutoTitleSessionKey, } from './session-title.js';
16
+ export { messagesToClientHistory, flattenMessageContent, type ClientHistoryMessage } from './client-history.js';
@@ -9,4 +9,5 @@ import { SessionManager } from "./manager.js";
9
9
  import { SessionConfigStore, resolveReasoningLevel, resolveThinkingLevel, resolveVerboseLevel } from "./config-store.js";
10
10
  import { resolveEffectiveReasoningLevel, resolveEffectiveThinkingLevel } from "./thinking-resolve.js";
11
11
  import { effectiveWorkspacePathForSession, normalizeWorkingDirectoryInput } from "./session-workspace.js";
12
- export { SessionConfigStore, SessionManager, SessionSearchIndex, SessionStatus, SessionStore, effectiveWorkspacePathForSession, fallbackTitleFromMessages, fileStemToSessionKey, generateSessionTitleFromMessages, getOrLoadSessionSearchIndex, invalidateSessionSearchIndexCache, isWebchatSessionKey, maybeAutoTitleSessionStore, normalizeWorkingDirectoryInput, resolveEffectiveReasoningLevel, resolveEffectiveThinkingLevel, resolveReasoningLevel, resolveSessionShardRelativePath, resolveThinkingLevel, resolveVerboseLevel, sanitizeSessionPathSegment, sanitizeSessionTitle, shouldAutoTitleSessionKey };
12
+ import { flattenMessageContent, messagesToClientHistory } from "./client-history.js";
13
+ export { SessionConfigStore, SessionManager, SessionSearchIndex, SessionStatus, SessionStore, effectiveWorkspacePathForSession, fallbackTitleFromMessages, fileStemToSessionKey, flattenMessageContent, generateSessionTitleFromMessages, getOrLoadSessionSearchIndex, invalidateSessionSearchIndexCache, isWebchatSessionKey, maybeAutoTitleSessionStore, messagesToClientHistory, normalizeWorkingDirectoryInput, resolveEffectiveReasoningLevel, resolveEffectiveThinkingLevel, resolveReasoningLevel, resolveSessionShardRelativePath, resolveThinkingLevel, resolveVerboseLevel, sanitizeSessionPathSegment, sanitizeSessionTitle, shouldAutoTitleSessionKey };
@@ -32,6 +32,8 @@ export declare class SessionManager extends EventEmitter {
32
32
  failed: string[];
33
33
  }>;
34
34
  renameSession(key: string, name: string): Promise<void>;
35
+ /** Partial metadata update (caller merges nested fields like `customData` when needed). */
36
+ updateSessionMetadata(key: string, updates: Partial<SessionMetadata>): Promise<void>;
35
37
  tagSession(key: string, tags: string[]): Promise<void>;
36
38
  untagSession(key: string, tags: string[]): Promise<void>;
37
39
  setSessionTags(key: string, tags: string[]): Promise<void>;
@@ -69,6 +69,11 @@ var SessionManager = class extends EventEmitter$1 {
69
69
  name
70
70
  });
71
71
  }
72
+ /** Partial metadata update (caller merges nested fields like `customData` when needed). */
73
+ async updateSessionMetadata(key, updates) {
74
+ await this.store.updateMetadata(key, updates);
75
+ this.emit("sessionUpdated", { key });
76
+ }
72
77
  async tagSession(key, tags) {
73
78
  const existing = await this.store.getMetadata(key);
74
79
  if (!existing) throw new Error(`Session not found: ${key}`);
@@ -1 +1 @@
1
- {"version":3,"file":"manager.js","names":["EventEmitter"],"sources":["../../../src/session/manager.ts"],"sourcesContent":["// Session manager - high-level session management service\n\nimport EventEmitter from 'events';\nimport { createLogger } from '../utils/logger.js';\nimport { SessionStore } from './store.js';\nimport type {\n SessionMetadata,\n SessionDetail,\n SessionListQuery,\n PaginatedResult,\n GlobalSessionStats,\n ExportFormat,\n SessionStatus,\n} from './types.js';\nimport type { Message } from './types.js';\nimport type { CompactionConfig, CompactionResult } from '../agent/memory/compaction.js';\nimport type { WindowConfig } from '../agent/memory/window.js';\nimport type { Config } from '../config/schema.js';\n\nconst log = createLogger('SessionManager');\n\nexport interface SessionManagerConfig {\n config: Config;\n agentId?: string;\n sessionsDir?: string;\n windowConfig?: Partial<WindowConfig>;\n compactionConfig?: Partial<CompactionConfig>;\n}\n\nexport class SessionManager extends EventEmitter {\n private store: SessionStore;\n\n constructor(config: SessionManagerConfig) {\n super();\n this.store = new SessionStore(\n {\n config: config.config,\n agentId: config.agentId,\n sessionsDir: config.sessionsDir,\n },\n config.windowConfig,\n config.compactionConfig\n );\n }\n\n async initialize(): Promise<void> {\n await this.store.initialize();\n this.emit('ready');\n }\n\n /** Low-level store (e.g. cron resolving weixin delivery from session index). */\n getStore(): SessionStore {\n return this.store;\n }\n\n // ========== CRUD Operations ==========\n\n async listSessions(query?: SessionListQuery): Promise<PaginatedResult<SessionMetadata>> {\n return this.store.list(query);\n }\n\n /**\n * List all subagent sessions.\n * Subagent sessions have keys starting with 'subagent:'.\n */\n async listSubagents(query: SessionListQuery = {}): Promise<PaginatedResult<SessionMetadata>> {\n // Filter for subagent sessions only\n const subagentQuery: SessionListQuery = {\n ...query,\n search: query.search ? `subagent:${query.search}` : 'subagent:',\n };\n \n const result = await this.store.list(subagentQuery);\n \n // Additional filtering to ensure only subagent sessions\n const subagentSessions = result.items.filter((s) => s.key.startsWith('subagent:'));\n \n return {\n ...result,\n items: subagentSessions,\n total: subagentSessions.length,\n hasMore: false, // Simplified for now\n };\n }\n\n async getSession(key: string): Promise<SessionDetail | null> {\n const session = await this.store.get(key);\n if (session) {\n this.emit('sessionAccessed', { key });\n }\n return session;\n }\n\n async getSessionMetadata(key: string): Promise<SessionMetadata | null> {\n return this.store.getMetadata(key);\n }\n\n async deleteSession(key: string): Promise<boolean> {\n const result = await this.store.delete(key);\n if (result) {\n this.emit('sessionDeleted', { key });\n }\n return result;\n }\n\n async deleteSessions(keys: string[]): Promise<{ success: string[]; failed: string[] }> {\n const result = await this.store.deleteMany(keys);\n for (const key of result.success) {\n this.emit('sessionDeleted', { key });\n }\n return result;\n }\n\n // ========== Metadata Updates ==========\n\n async renameSession(key: string, name: string): Promise<void> {\n await this.store.updateMetadata(key, { name });\n this.emit('sessionUpdated', { key, name });\n }\n\n async tagSession(key: string, tags: string[]): Promise<void> {\n const existing = await this.store.getMetadata(key);\n if (!existing) {\n throw new Error(`Session not found: ${key}`);\n }\n\n // Merge tags, remove duplicates\n const mergedTags = [...new Set([...existing.tags, ...tags])];\n await this.store.updateMetadata(key, { tags: mergedTags });\n this.emit('sessionUpdated', { key, tags: mergedTags });\n }\n\n async untagSession(key: string, tags: string[]): Promise<void> {\n const existing = await this.store.getMetadata(key);\n if (!existing) {\n throw new Error(`Session not found: ${key}`);\n }\n\n const filteredTags = existing.tags.filter((t) => !tags.includes(t));\n await this.store.updateMetadata(key, { tags: filteredTags });\n this.emit('sessionUpdated', { key, tags: filteredTags });\n }\n\n async setSessionTags(key: string, tags: string[]): Promise<void> {\n await this.store.updateMetadata(key, { tags: [...new Set(tags)] });\n this.emit('sessionUpdated', { key, tags });\n }\n\n // ========== Status Management ==========\n\n async archiveSession(key: string): Promise<void> {\n await this.store.archive(key);\n this.emit('sessionArchived', { key });\n }\n\n async unarchiveSession(key: string): Promise<void> {\n await this.store.unarchive(key);\n this.emit('sessionRestored', { key });\n }\n\n async pinSession(key: string): Promise<void> {\n await this.store.pin(key);\n this.emit('sessionPinned', { key });\n }\n\n async unpinSession(key: string): Promise<void> {\n await this.store.unpin(key);\n this.emit('sessionUnpinned', { key });\n }\n\n async setSessionStatus(key: string, status: SessionStatus): Promise<void> {\n await this.store.setStatus(key, status);\n this.emit('sessionStatusChanged', { key, status });\n }\n\n // ========== Search ==========\n\n async searchSessions(query: string): Promise<SessionMetadata[]> {\n const result = await this.store.list({ search: query, limit: 100 });\n return result.items;\n }\n\n async searchInSession(key: string, keyword: string): Promise<Message[]> {\n return this.store.searchInSession(key, keyword);\n }\n\n // ========== Export/Import ==========\n\n async exportSession(key: string, format: ExportFormat): Promise<string> {\n return this.store.exportSession(key, format);\n }\n\n // ========== Statistics ==========\n\n async getStats(): Promise<GlobalSessionStats> {\n return this.store.getStats();\n }\n\n // ========== Maintenance ==========\n\n async archiveOldSessions(olderThanDays: number): Promise<number> {\n const count = await this.store.archiveOld(olderThanDays);\n log.info({ count, olderThanDays }, 'Archived old sessions');\n return count;\n }\n\n // ========== Event Helpers ==========\n\n onSessionCreated(callback: (metadata: SessionMetadata) => void): void {\n this.on('sessionCreated', callback);\n }\n\n onSessionUpdated(callback: (data: { key: string; name?: string; tags?: string[] }) => void): void {\n this.on('sessionUpdated', callback);\n }\n\n onSessionDeleted(callback: (data: { key: string }) => void): void {\n this.on('sessionDeleted', callback);\n }\n\n onSessionArchived(callback: (data: { key: string }) => void): void {\n this.on('sessionArchived', callback);\n }\n\n onSessionRestored(callback: (data: { key: string }) => void): void {\n this.on('sessionRestored', callback);\n }\n\n onSessionPinned(callback: (data: { key: string }) => void): void {\n this.on('sessionPinned', callback);\n }\n\n onSessionUnpinned(callback: (data: { key: string }) => void): void {\n this.on('sessionUnpinned', callback);\n }\n\n onSessionStatusChanged(callback: (data: { key: string; status: SessionStatus }) => void): void {\n this.on('sessionStatusChanged', callback);\n }\n\n onSessionAccessed(callback: (data: { key: string }) => void): void {\n this.on('sessionAccessed', callback);\n }\n\n // ========== Store delegation (messages, compaction) ==========\n\n /** Load messages for a session key */\n async loadMessages(key: string) {\n return this.store.loadMessages(key);\n }\n\n /** Save messages for a session key */\n async saveMessages(key: string, messages: any[]) {\n return this.store.saveMessages(key, messages);\n }\n\n /** Delete session data */\n async delete(key: string): Promise<void> {\n await this.store.delete(key);\n }\n\n /** Token/window stats for a message list */\n getWindowStats(messages: any[]) {\n return this.store.getWindowStats(messages);\n }\n\n /** Prepare compaction run */\n prepareCompaction(key: string, messages: any[], contextWindow: number) {\n return this.store.prepareCompaction(key, messages, contextWindow);\n }\n\n /** Compact session messages */\n compact(\n key: string,\n messages: any[],\n contextWindow: number,\n instructions?: string,\n force?: boolean,\n ): Promise<CompactionResult> {\n return this.store.compact(key, messages, contextWindow, instructions, force);\n }\n\n /** Compaction stats for a session */\n async getCompactionStats(key: string) {\n return this.store.getCompactionStats(key);\n }\n\n /** Estimate token usage for messages */\n async estimateTokenUsage(key: string, messages: any[]): Promise<number> {\n return this.store.estimateTokens(messages);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"manager.js","names":["EventEmitter"],"sources":["../../../src/session/manager.ts"],"sourcesContent":["// Session manager - high-level session management service\n\nimport EventEmitter from 'events';\nimport { createLogger } from '../utils/logger.js';\nimport { SessionStore } from './store.js';\nimport type {\n SessionMetadata,\n SessionDetail,\n SessionListQuery,\n PaginatedResult,\n GlobalSessionStats,\n ExportFormat,\n SessionStatus,\n} from './types.js';\nimport type { Message } from './types.js';\nimport type { CompactionConfig, CompactionResult } from '../agent/memory/compaction.js';\nimport type { WindowConfig } from '../agent/memory/window.js';\nimport type { Config } from '../config/schema.js';\n\nconst log = createLogger('SessionManager');\n\nexport interface SessionManagerConfig {\n config: Config;\n agentId?: string;\n sessionsDir?: string;\n windowConfig?: Partial<WindowConfig>;\n compactionConfig?: Partial<CompactionConfig>;\n}\n\nexport class SessionManager extends EventEmitter {\n private store: SessionStore;\n\n constructor(config: SessionManagerConfig) {\n super();\n this.store = new SessionStore(\n {\n config: config.config,\n agentId: config.agentId,\n sessionsDir: config.sessionsDir,\n },\n config.windowConfig,\n config.compactionConfig\n );\n }\n\n async initialize(): Promise<void> {\n await this.store.initialize();\n this.emit('ready');\n }\n\n /** Low-level store (e.g. cron resolving weixin delivery from session index). */\n getStore(): SessionStore {\n return this.store;\n }\n\n // ========== CRUD Operations ==========\n\n async listSessions(query?: SessionListQuery): Promise<PaginatedResult<SessionMetadata>> {\n return this.store.list(query);\n }\n\n /**\n * List all subagent sessions.\n * Subagent sessions have keys starting with 'subagent:'.\n */\n async listSubagents(query: SessionListQuery = {}): Promise<PaginatedResult<SessionMetadata>> {\n // Filter for subagent sessions only\n const subagentQuery: SessionListQuery = {\n ...query,\n search: query.search ? `subagent:${query.search}` : 'subagent:',\n };\n \n const result = await this.store.list(subagentQuery);\n \n // Additional filtering to ensure only subagent sessions\n const subagentSessions = result.items.filter((s) => s.key.startsWith('subagent:'));\n \n return {\n ...result,\n items: subagentSessions,\n total: subagentSessions.length,\n hasMore: false, // Simplified for now\n };\n }\n\n async getSession(key: string): Promise<SessionDetail | null> {\n const session = await this.store.get(key);\n if (session) {\n this.emit('sessionAccessed', { key });\n }\n return session;\n }\n\n async getSessionMetadata(key: string): Promise<SessionMetadata | null> {\n return this.store.getMetadata(key);\n }\n\n async deleteSession(key: string): Promise<boolean> {\n const result = await this.store.delete(key);\n if (result) {\n this.emit('sessionDeleted', { key });\n }\n return result;\n }\n\n async deleteSessions(keys: string[]): Promise<{ success: string[]; failed: string[] }> {\n const result = await this.store.deleteMany(keys);\n for (const key of result.success) {\n this.emit('sessionDeleted', { key });\n }\n return result;\n }\n\n // ========== Metadata Updates ==========\n\n async renameSession(key: string, name: string): Promise<void> {\n await this.store.updateMetadata(key, { name });\n this.emit('sessionUpdated', { key, name });\n }\n\n /** Partial metadata update (caller merges nested fields like `customData` when needed). */\n async updateSessionMetadata(key: string, updates: Partial<SessionMetadata>): Promise<void> {\n await this.store.updateMetadata(key, updates);\n this.emit('sessionUpdated', { key });\n }\n\n async tagSession(key: string, tags: string[]): Promise<void> {\n const existing = await this.store.getMetadata(key);\n if (!existing) {\n throw new Error(`Session not found: ${key}`);\n }\n\n // Merge tags, remove duplicates\n const mergedTags = [...new Set([...existing.tags, ...tags])];\n await this.store.updateMetadata(key, { tags: mergedTags });\n this.emit('sessionUpdated', { key, tags: mergedTags });\n }\n\n async untagSession(key: string, tags: string[]): Promise<void> {\n const existing = await this.store.getMetadata(key);\n if (!existing) {\n throw new Error(`Session not found: ${key}`);\n }\n\n const filteredTags = existing.tags.filter((t) => !tags.includes(t));\n await this.store.updateMetadata(key, { tags: filteredTags });\n this.emit('sessionUpdated', { key, tags: filteredTags });\n }\n\n async setSessionTags(key: string, tags: string[]): Promise<void> {\n await this.store.updateMetadata(key, { tags: [...new Set(tags)] });\n this.emit('sessionUpdated', { key, tags });\n }\n\n // ========== Status Management ==========\n\n async archiveSession(key: string): Promise<void> {\n await this.store.archive(key);\n this.emit('sessionArchived', { key });\n }\n\n async unarchiveSession(key: string): Promise<void> {\n await this.store.unarchive(key);\n this.emit('sessionRestored', { key });\n }\n\n async pinSession(key: string): Promise<void> {\n await this.store.pin(key);\n this.emit('sessionPinned', { key });\n }\n\n async unpinSession(key: string): Promise<void> {\n await this.store.unpin(key);\n this.emit('sessionUnpinned', { key });\n }\n\n async setSessionStatus(key: string, status: SessionStatus): Promise<void> {\n await this.store.setStatus(key, status);\n this.emit('sessionStatusChanged', { key, status });\n }\n\n // ========== Search ==========\n\n async searchSessions(query: string): Promise<SessionMetadata[]> {\n const result = await this.store.list({ search: query, limit: 100 });\n return result.items;\n }\n\n async searchInSession(key: string, keyword: string): Promise<Message[]> {\n return this.store.searchInSession(key, keyword);\n }\n\n // ========== Export/Import ==========\n\n async exportSession(key: string, format: ExportFormat): Promise<string> {\n return this.store.exportSession(key, format);\n }\n\n // ========== Statistics ==========\n\n async getStats(): Promise<GlobalSessionStats> {\n return this.store.getStats();\n }\n\n // ========== Maintenance ==========\n\n async archiveOldSessions(olderThanDays: number): Promise<number> {\n const count = await this.store.archiveOld(olderThanDays);\n log.info({ count, olderThanDays }, 'Archived old sessions');\n return count;\n }\n\n // ========== Event Helpers ==========\n\n onSessionCreated(callback: (metadata: SessionMetadata) => void): void {\n this.on('sessionCreated', callback);\n }\n\n onSessionUpdated(callback: (data: { key: string; name?: string; tags?: string[] }) => void): void {\n this.on('sessionUpdated', callback);\n }\n\n onSessionDeleted(callback: (data: { key: string }) => void): void {\n this.on('sessionDeleted', callback);\n }\n\n onSessionArchived(callback: (data: { key: string }) => void): void {\n this.on('sessionArchived', callback);\n }\n\n onSessionRestored(callback: (data: { key: string }) => void): void {\n this.on('sessionRestored', callback);\n }\n\n onSessionPinned(callback: (data: { key: string }) => void): void {\n this.on('sessionPinned', callback);\n }\n\n onSessionUnpinned(callback: (data: { key: string }) => void): void {\n this.on('sessionUnpinned', callback);\n }\n\n onSessionStatusChanged(callback: (data: { key: string; status: SessionStatus }) => void): void {\n this.on('sessionStatusChanged', callback);\n }\n\n onSessionAccessed(callback: (data: { key: string }) => void): void {\n this.on('sessionAccessed', callback);\n }\n\n // ========== Store delegation (messages, compaction) ==========\n\n /** Load messages for a session key */\n async loadMessages(key: string) {\n return this.store.loadMessages(key);\n }\n\n /** Save messages for a session key */\n async saveMessages(key: string, messages: any[]) {\n return this.store.saveMessages(key, messages);\n }\n\n /** Delete session data */\n async delete(key: string): Promise<void> {\n await this.store.delete(key);\n }\n\n /** Token/window stats for a message list */\n getWindowStats(messages: any[]) {\n return this.store.getWindowStats(messages);\n }\n\n /** Prepare compaction run */\n prepareCompaction(key: string, messages: any[], contextWindow: number) {\n return this.store.prepareCompaction(key, messages, contextWindow);\n }\n\n /** Compact session messages */\n compact(\n key: string,\n messages: any[],\n contextWindow: number,\n instructions?: string,\n force?: boolean,\n ): Promise<CompactionResult> {\n return this.store.compact(key, messages, contextWindow, instructions, force);\n }\n\n /** Compaction stats for a session */\n async getCompactionStats(key: string) {\n return this.store.getCompactionStats(key);\n }\n\n /** Estimate token usage for messages */\n async estimateTokenUsage(key: string, messages: any[]): Promise<number> {\n return this.store.estimateTokens(messages);\n }\n}\n"],"mappings":"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"}