@xopcai/xopc 0.0.30 → 0.0.31

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 (105) hide show
  1. package/dist/extensions/telegram/xopc.extension.json +1 -1
  2. package/dist/gateway/static/root/assets/agents-3u63Fw2Y.js +216 -0
  3. package/dist/gateway/static/root/assets/agents-3u63Fw2Y.js.map +1 -0
  4. package/dist/gateway/static/root/assets/{apps-page-CTChHQAu.js → apps-page-CWegY6Kp.js} +2 -2
  5. package/dist/gateway/static/root/assets/{apps-page-CTChHQAu.js.map → apps-page-CWegY6Kp.js.map} +1 -1
  6. package/dist/gateway/static/root/assets/channels-settings-CiyeXcTK.js +9 -0
  7. package/dist/gateway/static/root/assets/channels-settings-CiyeXcTK.js.map +1 -0
  8. package/dist/gateway/static/root/assets/cron-api-_j_79Zf5.js +3 -0
  9. package/dist/gateway/static/root/assets/cron-api-_j_79Zf5.js.map +1 -0
  10. package/dist/gateway/static/root/assets/cron-page-S86YNTtI.js +2 -0
  11. package/dist/gateway/static/root/assets/cron-page-S86YNTtI.js.map +1 -0
  12. package/dist/gateway/static/root/assets/dist-D0jxbvuz.js +2 -0
  13. package/dist/gateway/static/root/assets/{dist-UWGUW3x8.js.map → dist-D0jxbvuz.js.map} +1 -1
  14. package/dist/gateway/static/root/assets/{extension-debug-page-BwB4a4cK.js → extension-debug-page-DB630cW8.js} +2 -2
  15. package/dist/gateway/static/root/assets/{extension-debug-page-BwB4a4cK.js.map → extension-debug-page-DB630cW8.js.map} +1 -1
  16. package/dist/gateway/static/root/assets/{extension-page-CSWu2PHZ.js → extension-page-CnoPUBul.js} +2 -2
  17. package/dist/gateway/static/root/assets/{extension-page-CSWu2PHZ.js.map → extension-page-CnoPUBul.js.map} +1 -1
  18. package/dist/gateway/static/root/assets/{extension-settings-page-B12K2a13.js → extension-settings-page-BsiOkvBe.js} +2 -2
  19. package/dist/gateway/static/root/assets/{extension-settings-page-B12K2a13.js.map → extension-settings-page-BsiOkvBe.js.map} +1 -1
  20. package/dist/gateway/static/root/assets/{index-D0pFZ0OE.js → index-DHLmAIQl.js} +81 -81
  21. package/dist/gateway/static/root/assets/{index-D0pFZ0OE.js.map → index-DHLmAIQl.js.map} +1 -1
  22. package/dist/gateway/static/root/assets/index-DoPwy4aU.css +1 -0
  23. package/dist/gateway/static/root/assets/logs-page-Bndhenn2.js +2 -0
  24. package/dist/gateway/static/root/assets/logs-page-Bndhenn2.js.map +1 -0
  25. package/dist/gateway/static/root/assets/sessions-page-Q201-_lP.js +2 -0
  26. package/dist/gateway/static/root/assets/{sessions-page-DJkuWpOT.js.map → sessions-page-Q201-_lP.js.map} +1 -1
  27. package/dist/gateway/static/root/assets/settings-page-Cw75fpc6.js +2 -0
  28. package/dist/gateway/static/root/assets/settings-page-Cw75fpc6.js.map +1 -0
  29. package/dist/gateway/static/root/assets/skills-page-CVwEzD_J.js +3 -0
  30. package/dist/gateway/static/root/assets/skills-page-CVwEzD_J.js.map +1 -0
  31. package/dist/gateway/static/root/index.html +2 -2
  32. package/dist/package.js +1 -1
  33. package/dist/src/agent/orchestration/agent-orchestrator.js +1 -1
  34. package/dist/src/agent/service/process-direct-streaming.js +12 -1
  35. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  36. package/dist/src/agent/service.d.ts +4 -0
  37. package/dist/src/agent/service.js +7 -1
  38. package/dist/src/agent/service.js.map +1 -1
  39. package/dist/src/agent/skills/marketplace/resolve-adapter.js +1 -1
  40. package/dist/src/agent/skills/marketplace/resolve-adapter.js.map +1 -1
  41. package/dist/src/config/schema.js +1 -0
  42. package/dist/src/config/schema.js.map +1 -1
  43. package/dist/src/cron/validation.js +1 -1
  44. package/dist/src/cron/validation.js.map +1 -1
  45. package/dist/src/gateway/hono/routes/sessions.js +124 -2
  46. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  47. package/dist/src/gateway/hono/sse.js +9 -2
  48. package/dist/src/gateway/hono/sse.js.map +1 -1
  49. package/dist/src/gateway/service/run-gateway-agent.d.ts +1 -0
  50. package/dist/src/gateway/service/run-gateway-agent.js +18 -10
  51. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  52. package/dist/src/gateway/service.d.ts +23 -1
  53. package/dist/src/gateway/service.js +47 -3
  54. package/dist/src/gateway/service.js.map +1 -1
  55. package/dist/src/session/abort-cutoff.d.ts +6 -0
  56. package/dist/src/session/abort-cutoff.js +10 -0
  57. package/dist/src/session/abort-cutoff.js.map +1 -0
  58. package/dist/src/session/compaction-checkpoints.d.ts +8 -0
  59. package/dist/src/session/compaction-checkpoints.js +21 -0
  60. package/dist/src/session/compaction-checkpoints.js.map +1 -0
  61. package/dist/src/session/index.d.ts +8 -1
  62. package/dist/src/session/index.js +7 -1
  63. package/dist/src/session/manager.d.ts +26 -1
  64. package/dist/src/session/manager.js +39 -2
  65. package/dist/src/session/manager.js.map +1 -1
  66. package/dist/src/session/patch-metadata.d.ts +12 -0
  67. package/dist/src/session/patch-metadata.js +23 -0
  68. package/dist/src/session/patch-metadata.js.map +1 -0
  69. package/dist/src/session/search-index.d.ts +2 -0
  70. package/dist/src/session/search-index.js +30 -2
  71. package/dist/src/session/search-index.js.map +1 -1
  72. package/dist/src/session/session-context-for-llm.d.ts +32 -0
  73. package/dist/src/session/session-context-for-llm.js +60 -0
  74. package/dist/src/session/session-context-for-llm.js.map +1 -0
  75. package/dist/src/session/store.d.ts +36 -2
  76. package/dist/src/session/store.js +200 -28
  77. package/dist/src/session/store.js.map +1 -1
  78. package/dist/src/session/strip-webchat-early-save.d.ts +5 -0
  79. package/dist/src/session/strip-webchat-early-save.js +17 -0
  80. package/dist/src/session/strip-webchat-early-save.js.map +1 -0
  81. package/dist/src/session/transcript-format.d.ts +46 -0
  82. package/dist/src/session/transcript-format.js +88 -0
  83. package/dist/src/session/transcript-format.js.map +1 -0
  84. package/dist/src/session/types.d.ts +37 -0
  85. package/dist/src/session/types.js.map +1 -1
  86. package/dist/src/utils/logger/log-store.js +4 -3
  87. package/dist/src/utils/logger/log-store.js.map +1 -1
  88. package/package.json +1 -1
  89. package/dist/gateway/static/root/assets/agents-BfwtJOPK.js +0 -216
  90. package/dist/gateway/static/root/assets/agents-BfwtJOPK.js.map +0 -1
  91. package/dist/gateway/static/root/assets/channels-settings-BpwVOvvf.js +0 -9
  92. package/dist/gateway/static/root/assets/channels-settings-BpwVOvvf.js.map +0 -1
  93. package/dist/gateway/static/root/assets/cron-page-C_6AbVRf.js +0 -2
  94. package/dist/gateway/static/root/assets/cron-page-C_6AbVRf.js.map +0 -1
  95. package/dist/gateway/static/root/assets/cron-utils-DZ7pabh5.js +0 -3
  96. package/dist/gateway/static/root/assets/cron-utils-DZ7pabh5.js.map +0 -1
  97. package/dist/gateway/static/root/assets/dist-UWGUW3x8.js +0 -2
  98. package/dist/gateway/static/root/assets/index-C6itMrqR.css +0 -1
  99. package/dist/gateway/static/root/assets/logs-page-BXqha2gI.js +0 -2
  100. package/dist/gateway/static/root/assets/logs-page-BXqha2gI.js.map +0 -1
  101. package/dist/gateway/static/root/assets/sessions-page-DJkuWpOT.js +0 -2
  102. package/dist/gateway/static/root/assets/settings-page-CleZrGHy.js +0 -2
  103. package/dist/gateway/static/root/assets/settings-page-CleZrGHy.js.map +0 -1
  104. package/dist/gateway/static/root/assets/skills-page-D7NiIOzA.js +0 -3
  105. package/dist/gateway/static/root/assets/skills-page-D7NiIOzA.js.map +0 -1
@@ -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 { assertGatewayAuthNotKnownWeak } from './security/known-weak-secrets.js';\nimport { auditGatewayConfig } from './security/audit.js';\nimport { getModelRegistry } from '../providers/index.js';\nimport { createLogger, getLogDir, getLogStats } 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 downloadFromMarketplace,\n getMarketplacePackageDetail,\n getMarketplaceProviderDisplayName,\n listMarketplaceCategories,\n listMarketplacePackages,\n resolveSkillsMarketplaceProvider,\n type MarketplaceCategoryOption,\n type MarketplacePackageDetail,\n type SkillsStoreListParams,\n type UnifiedMarketplaceListResponse,\n type UnifiedMarketplacePackageDetail,\n} from '../agent/skills/skills-marketplace.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 { SkillMarkdownPreviewPayload } from '../agent/skills/types.js';\nimport type { ManagedSkillListItem } from '../agent/skills/managed-store.js';\n\nimport { PACKAGE_VERSION } from '../package-version.js';\nimport { buildSessionKey, parseSessionKey } from '../routing/session-key.js';\nimport { getDefaultAgentId } from '../routing/resolve-route.js';\nimport { scheduleGatewayUpdateCheck } from '../infra/update-startup.js';\nimport { restartGatewayProcessWithFreshPid } from './respawn.js';\nimport { getDistinctSessionChatIds } from './service/session-chat-ids.js';\nimport { runGatewayAgent } from './service/run-gateway-agent.js';\nimport { GatewaySseHub } from './service/sse-hub.js';\nimport type { GatewayServiceConfig, ServiceEvent } from './service/types.js';\n\nexport type { GatewayServiceConfig, ServiceEvent } from './service/types.js';\n\nconst log = createLogger('GatewayService');\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 private readonly sse = new GatewaySseHub();\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 * `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 iter = runGatewayAgent(\n {\n config: this.config,\n agentService: this.agentService,\n bus: this.bus,\n runRelay: this.runRelay,\n runAbortControllers: this.runAbortControllers,\n activeWebchatRunBySession: this.activeWebchatRunBySession,\n sessionManager: this.sessionManager,\n emit: (type, payload) => this.sse.emit(type, payload),\n },\n message,\n channel,\n chatId,\n attachments,\n thinking,\n runOptions,\n );\n\n let step = await iter.next();\n while (!step.done) {\n yield step.value as { type: string; content?: string; status?: string; runId?: string };\n step = await iter.next();\n }\n return step.value;\n }\n\n /** Abort an in-flight webchat agent run (matches `runId` from SSE `status`). */\n abortAgentRun(runId: string): boolean {\n this.clarifyBridge.cancelForRun(runId);\n 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(lang?: string): { catalog: SkillCatalogEntry[]; managed: ManagedSkillListItem[] } {\n return {\n catalog: this.agentService.getSkillCatalog(lang),\n managed: listManagedSkillDirs(),\n };\n }\n\n getSkillMarkdownSource(skillName: string, lang?: string): SkillMarkdownPreviewPayload | null {\n return this.agentService.getSkillMarkdownSource(skillName, lang);\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<UnifiedMarketplaceListResponse> {\n return listMarketplacePackages(this.config, params);\n }\n\n async fetchSkillsMarketplaceCategories(): Promise<{ items: MarketplaceCategoryOption[] }> {\n return listMarketplaceCategories(this.config);\n }\n\n async fetchSkillsMarketplacePackageDetail(packageName: string): Promise<UnifiedMarketplacePackageDetail> {\n return getMarketplacePackageDetail(this.config, packageName);\n }\n\n async installSkillFromMarketplace(opts: {\n name: string;\n version?: string;\n overwrite?: boolean;\n }): Promise<{ skillId: string; path: string }> {\n const { buffer, skillId } = await downloadFromMarketplace(this.config, opts.name, opts.version);\n return this.installManagedSkillZip(buffer, { skillId, overwrite: opts.overwrite ?? false });\n }\n\n getSkillsMarketplaceProvider(): { provider: string; displayName: string } {\n const provider = resolveSkillsMarketplaceProvider(this.config);\n return {\n provider,\n displayName: getMarketplaceProviderDisplayName(provider),\n };\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: (event: ServiceEvent) => Promise<void> | void): () => void {\n return this.sse.subscribe(sessionId, listener);\n }\n\n /**\n * Emit an event to all subscribers.\n */\n emit(type: string, payload: unknown): void {\n this.sse.emit(type, payload);\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 return this.sse.getEventsSince(sessionId, lastEventId);\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 return getDistinctSessionChatIds(this.sessionManager, channel);\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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"}
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 type { SessionPatchBody } from '../session/patch-metadata.js';\nimport type { CompactionResult } from '../agent/memory/compaction.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 { createLogger, getLogDir, getLogStats } 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 downloadFromMarketplace,\n getMarketplacePackageDetail,\n getMarketplaceProviderDisplayName,\n listMarketplaceCategories,\n listMarketplacePackages,\n resolveSkillsMarketplaceProvider,\n type MarketplaceCategoryOption,\n type MarketplacePackageDetail,\n type SkillsStoreListParams,\n type UnifiedMarketplaceListResponse,\n type UnifiedMarketplacePackageDetail,\n} from '../agent/skills/skills-marketplace.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 { SkillMarkdownPreviewPayload } from '../agent/skills/types.js';\nimport type { ManagedSkillListItem } from '../agent/skills/managed-store.js';\n\nimport { PACKAGE_VERSION } from '../package-version.js';\nimport { buildSessionKey, parseSessionKey } from '../routing/session-key.js';\nimport { getDefaultAgentId } from '../routing/resolve-route.js';\nimport { scheduleGatewayUpdateCheck } from '../infra/update-startup.js';\nimport { restartGatewayProcessWithFreshPid } from './respawn.js';\nimport { getDistinctSessionChatIds } from './service/session-chat-ids.js';\nimport { runGatewayAgent } from './service/run-gateway-agent.js';\nimport { GatewaySseHub } from './service/sse-hub.js';\nimport type { GatewayServiceConfig, ServiceEvent } from './service/types.js';\n\nexport type { GatewayServiceConfig, ServiceEvent } from './service/types.js';\n\nconst log = createLogger('GatewayService');\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 private readonly sse = new GatewaySseHub();\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 * `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; clientCreatedAtMs?: number },\n ): AsyncGenerator<{ type: string; content?: string; status?: string; runId?: string }, { status: string; summary: string }, unknown> {\n const iter = runGatewayAgent(\n {\n config: this.config,\n agentService: this.agentService,\n bus: this.bus,\n runRelay: this.runRelay,\n runAbortControllers: this.runAbortControllers,\n activeWebchatRunBySession: this.activeWebchatRunBySession,\n sessionManager: this.sessionManager,\n emit: (type, payload) => this.sse.emit(type, payload),\n },\n message,\n channel,\n chatId,\n attachments,\n thinking,\n runOptions,\n );\n\n let step = await iter.next();\n while (!step.done) {\n yield step.value as { type: string; content?: string; status?: string; runId?: string };\n step = await iter.next();\n }\n return step.value;\n }\n\n /** Abort an in-flight webchat agent run (matches `runId` from SSE `status`). */\n abortAgentRun(runId: string): boolean {\n this.clarifyBridge.cancelForRun(runId);\n const keysToMark: string[] = [];\n for (const [sk, id] of this.activeWebchatRunBySession) {\n if (id === runId) {\n keysToMark.push(sk);\n }\n }\n for (const sk of keysToMark) {\n this.activeWebchatRunBySession.delete(sk);\n }\n const relaySk = this.runRelay.getSessionKey(runId);\n if (relaySk && !keysToMark.includes(relaySk)) {\n keysToMark.push(relaySk);\n }\n const c = this.runAbortControllers.get(runId);\n if (!c) {\n return false;\n }\n const cutoffTs = Date.now();\n for (const sk of keysToMark) {\n void this.sessionManager\n .updateSessionMetadata(sk, { abortCutoffTimestamp: cutoffTs })\n .catch(() => {});\n void this.sessionManager\n .appendTranscriptContextEntry(sk, {\n text: 'Webchat agent run aborted',\n data: { runId, abortCutoffTimestamp: cutoffTs },\n })\n .catch(() => {});\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(lang?: string): { catalog: SkillCatalogEntry[]; managed: ManagedSkillListItem[] } {\n return {\n catalog: this.agentService.getSkillCatalog(lang),\n managed: listManagedSkillDirs(),\n };\n }\n\n getSkillMarkdownSource(skillName: string, lang?: string): SkillMarkdownPreviewPayload | null {\n return this.agentService.getSkillMarkdownSource(skillName, lang);\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<UnifiedMarketplaceListResponse> {\n return listMarketplacePackages(this.config, params);\n }\n\n async fetchSkillsMarketplaceCategories(): Promise<{ items: MarketplaceCategoryOption[] }> {\n return listMarketplaceCategories(this.config);\n }\n\n async fetchSkillsMarketplacePackageDetail(packageName: string): Promise<UnifiedMarketplacePackageDetail> {\n return getMarketplacePackageDetail(this.config, packageName);\n }\n\n async installSkillFromMarketplace(opts: {\n name: string;\n version?: string;\n overwrite?: boolean;\n }): Promise<{ skillId: string; path: string }> {\n const { buffer, skillId } = await downloadFromMarketplace(this.config, opts.name, opts.version);\n return this.installManagedSkillZip(buffer, { skillId, overwrite: opts.overwrite ?? false });\n }\n\n getSkillsMarketplaceProvider(): { provider: string; displayName: string } {\n const provider = resolveSkillsMarketplaceProvider(this.config);\n return {\n provider,\n displayName: getMarketplaceProviderDisplayName(provider),\n };\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: (event: ServiceEvent) => Promise<void> | void): () => void {\n return this.sse.subscribe(sessionId, listener);\n }\n\n /**\n * Emit an event to all subscribers.\n */\n emit(type: string, payload: unknown): void {\n this.sse.emit(type, payload);\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 return this.sse.getEventsSince(sessionId, lastEventId);\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(\n key: string,\n options?: { includeTranscriptSummary?: boolean; includeTranscriptRows?: boolean },\n ) {\n return this.sessionManager.getSession(key, options);\n }\n\n /**\n * Partial session metadata update (OpenClaw-style sessions.patch subset).\n */\n async patchSession(\n key: string,\n body: SessionPatchBody,\n ): Promise<{ ok: true } | { ok: false; error: string }> {\n return this.sessionManager.patchSession(key, body);\n }\n\n async listSessionCompactionCheckpoints(key: string) {\n return this.sessionManager.listCompactionCheckpoints(key);\n }\n\n async getSessionCompactionCheckpoint(key: string, checkpointId: string) {\n return this.sessionManager.getCompactionCheckpointDetail(key, checkpointId);\n }\n\n async restoreSessionCompactionCheckpoint(key: string, checkpointId: string): Promise<void> {\n await this.sessionManager.restoreCompactionCheckpoint(key, checkpointId);\n this.agentService.evictSessionAgent(key);\n }\n\n async runSessionCompaction(\n key: string,\n options?: { instructions?: string; force?: boolean },\n ): Promise<CompactionResult> {\n const result = await this.agentService.compactSession(key, options);\n if (result.compacted) {\n void this.sessionManager\n .appendTranscriptContextEntry(key, {\n text: 'Session transcript compacted',\n data: {\n firstKeptIndex: result.firstKeptIndex,\n tokensBefore: result.tokensBefore,\n tokensAfter: result.tokensAfter,\n summaryPreview: result.summary.slice(0, 500),\n },\n })\n .catch(() => {});\n }\n return result;\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 return getDistinctSessionChatIds(this.sessionManager, channel);\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,6 @@
1
+ /**
2
+ * Webchat abort cutoff (OpenClaw-style): stale in-flight POSTs after /api/agent/abort
3
+ * are ignored when the client sends `clientCreatedAtMs` from send time.
4
+ */
5
+ import type { SessionMetadata } from './types.js';
6
+ export declare function shouldSkipWebchatInboundByAbortCutoff(meta: SessionMetadata | null | undefined, clientCreatedAtMs?: number): boolean;
@@ -0,0 +1,10 @@
1
+ //#region src/session/abort-cutoff.ts
2
+ function shouldSkipWebchatInboundByAbortCutoff(meta, clientCreatedAtMs) {
3
+ if (!meta?.abortCutoffTimestamp) return false;
4
+ if (typeof clientCreatedAtMs !== "number" || !Number.isFinite(clientCreatedAtMs)) return false;
5
+ return clientCreatedAtMs <= meta.abortCutoffTimestamp;
6
+ }
7
+ //#endregion
8
+ export { shouldSkipWebchatInboundByAbortCutoff };
9
+
10
+ //# sourceMappingURL=abort-cutoff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abort-cutoff.js","names":[],"sources":["../../../src/session/abort-cutoff.ts"],"sourcesContent":["/**\n * Webchat abort cutoff (OpenClaw-style): stale in-flight POSTs after /api/agent/abort\n * are ignored when the client sends `clientCreatedAtMs` from send time.\n */\n\nimport type { SessionMetadata } from './types.js';\n\nexport function shouldSkipWebchatInboundByAbortCutoff(\n meta: SessionMetadata | null | undefined,\n clientCreatedAtMs?: number,\n): boolean {\n if (!meta?.abortCutoffTimestamp) {\n return false;\n }\n if (typeof clientCreatedAtMs !== 'number' || !Number.isFinite(clientCreatedAtMs)) {\n return false;\n }\n return clientCreatedAtMs <= meta.abortCutoffTimestamp;\n}\n"],"mappings":";AAOA,SAAgB,sCACd,MACA,mBACS;AACT,KAAI,CAAC,MAAM,qBACT,QAAO;AAET,KAAI,OAAO,sBAAsB,YAAY,CAAC,OAAO,SAAS,kBAAkB,CAC9E,QAAO;AAET,QAAO,qBAAqB,KAAK"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Pre-compaction transcript snapshots ({safeKey}.compaction-backup.{uuid}.json).
3
+ * OpenClaw-style listing and id normalization for gateway APIs.
4
+ */
5
+ /**
6
+ * Accept raw uuid or full backup filename stem; returns lowercase uuid or null.
7
+ */
8
+ export declare function normalizeCompactionCheckpointId(raw: string): string | null;
@@ -0,0 +1,21 @@
1
+ //#region src/session/compaction-checkpoints.ts
2
+ /**
3
+ * Pre-compaction transcript snapshots ({safeKey}.compaction-backup.{uuid}.json).
4
+ * OpenClaw-style listing and id normalization for gateway APIs.
5
+ */
6
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
7
+ /**
8
+ * Accept raw uuid or full backup filename stem; returns lowercase uuid or null.
9
+ */
10
+ function normalizeCompactionCheckpointId(raw) {
11
+ const t = raw.trim();
12
+ if (!t) return null;
13
+ if (UUID_RE.test(t)) return t.toLowerCase();
14
+ const m = t.match(/\.compaction-backup\.([0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\.json$/i);
15
+ if (m?.[1] && UUID_RE.test(m[1])) return m[1].toLowerCase();
16
+ return null;
17
+ }
18
+ //#endregion
19
+ export { normalizeCompactionCheckpointId };
20
+
21
+ //# sourceMappingURL=compaction-checkpoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction-checkpoints.js","names":[],"sources":["../../../src/session/compaction-checkpoints.ts"],"sourcesContent":["/**\n * Pre-compaction transcript snapshots ({safeKey}.compaction-backup.{uuid}.json).\n * OpenClaw-style listing and id normalization for gateway APIs.\n */\n\nconst UUID_RE =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\n/**\n * Accept raw uuid or full backup filename stem; returns lowercase uuid or null.\n */\nexport function normalizeCompactionCheckpointId(raw: string): string | null {\n const t = raw.trim();\n if (!t) return null;\n if (UUID_RE.test(t)) {\n return t.toLowerCase();\n }\n const m = t.match(/\\.compaction-backup\\.([0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\\.json$/i);\n if (m?.[1] && UUID_RE.test(m[1])) {\n return m[1].toLowerCase();\n }\n return null;\n}\n"],"mappings":";;;;;AAKA,MAAM,UACJ;;;;AAKF,SAAgB,gCAAgC,KAA4B;CAC1E,MAAM,IAAI,IAAI,MAAM;AACpB,KAAI,CAAC,EAAG,QAAO;AACf,KAAI,QAAQ,KAAK,EAAE,CACjB,QAAO,EAAE,aAAa;CAExB,MAAM,IAAI,EAAE,MAAM,yGAAyG;AAC3H,KAAI,IAAI,MAAM,QAAQ,KAAK,EAAE,GAAG,CAC9B,QAAO,EAAE,GAAG,aAAa;AAE3B,QAAO"}
@@ -9,8 +9,15 @@ export { SessionConfigStore, resolveThinkingLevel, resolveReasoningLevel, resolv
9
9
  export { resolveEffectiveThinkingLevel, resolveEffectiveReasoningLevel } from './thinking-resolve.js';
10
10
  export type { SessionAgentConfig } from './config-store.js';
11
11
  export { normalizeWorkingDirectoryInput, effectiveWorkspacePathForSession, } from './session-workspace.js';
12
- export { SessionStatus, type SessionMetadata, type SessionDetail, type SessionIndex, type SessionListQuery, type PaginatedResult, type SessionStats, type ExportFormat, type SessionExport, } from './types.js';
12
+ export { SessionStatus, type SessionMetadata, type SessionDetail, type SessionIndex, type SessionListQuery, type PaginatedResult, type SessionStats, type ExportFormat, type SessionExport, type SessionTranscriptSummary, type CompactionCheckpointSummary, type CompactionCheckpointDetail, } from './types.js';
13
+ export { normalizeCompactionCheckpointId } from './compaction-checkpoints.js';
14
+ export { shouldSkipWebchatInboundByAbortCutoff } from './abort-cutoff.js';
15
+ export { stripTrailingWebchatEarlySaveUserIfPresent } from './strip-webchat-early-save.js';
13
16
  export type { CompactionConfig, CompactionResult } from '../agent/memory/compaction.js';
14
17
  export type { WindowConfig } from '../agent/memory/window.js';
15
18
  export { maybeAutoTitleSessionStore, generateSessionTitleFromMessages, sanitizeSessionTitle, fallbackTitleFromMessages, isWebchatSessionKey, shouldAutoTitleSessionKey, } from './session-title.js';
16
19
  export { messagesToClientHistory, flattenMessageContent, type ClientHistoryMessage } from './client-history.js';
20
+ export { XOPC_SESSION_TRANSCRIPT_TYPE, CURRENT_SESSION_TRANSCRIPT_VERSION, parseStoredTranscriptJson, buildTranscriptEnvelope, } from './transcript-format.js';
21
+ export { buildSessionContextForLlm, isTranscriptContextEntry, mergeLlmMessagesPreservingContextRows, transcriptRowsFromJsonArray, type TranscriptStoredRow, type XopcTranscriptContextEntry, } from './session-context-for-llm.js';
22
+ export type { XopcSessionTranscriptV1, TranscriptCompactionRecord, } from './transcript-format.js';
23
+ export { applySessionPatchToMetadata, type SessionPatchBody } from './patch-metadata.js';
@@ -1,13 +1,19 @@
1
1
  import { fileStemToSessionKey } from "./session-file-key.js";
2
+ import { buildSessionContextForLlm, isTranscriptContextEntry, mergeLlmMessagesPreservingContextRows, transcriptRowsFromJsonArray } from "./session-context-for-llm.js";
3
+ import { CURRENT_SESSION_TRANSCRIPT_VERSION, XOPC_SESSION_TRANSCRIPT_TYPE, buildTranscriptEnvelope, parseStoredTranscriptJson } from "./transcript-format.js";
2
4
  import { SessionSearchIndex } from "./search-index.js";
3
5
  import { getOrLoadSessionSearchIndex, invalidateSessionSearchIndexCache } from "./search-index-cache.js";
4
6
  import { fallbackTitleFromMessages, generateSessionTitleFromMessages, isWebchatSessionKey, maybeAutoTitleSessionStore, sanitizeSessionTitle, shouldAutoTitleSessionKey } from "./session-title.js";
5
7
  import { resolveSessionShardRelativePath, sanitizeSessionPathSegment } from "./shard-path.js";
6
8
  import { SessionStatus } from "./types.js";
9
+ import { normalizeCompactionCheckpointId } from "./compaction-checkpoints.js";
7
10
  import { SessionStore } from "./store.js";
11
+ import { applySessionPatchToMetadata } from "./patch-metadata.js";
8
12
  import { SessionManager } from "./manager.js";
9
13
  import { SessionConfigStore, resolveReasoningLevel, resolveThinkingLevel, resolveVerboseLevel } from "./config-store.js";
10
14
  import { resolveEffectiveReasoningLevel, resolveEffectiveThinkingLevel } from "./thinking-resolve.js";
11
15
  import { effectiveWorkspacePathForSession, normalizeWorkingDirectoryInput } from "./session-workspace.js";
16
+ import { shouldSkipWebchatInboundByAbortCutoff } from "./abort-cutoff.js";
17
+ import { stripTrailingWebchatEarlySaveUserIfPresent } from "./strip-webchat-early-save.js";
12
18
  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 };
19
+ export { CURRENT_SESSION_TRANSCRIPT_VERSION, SessionConfigStore, SessionManager, SessionSearchIndex, SessionStatus, SessionStore, XOPC_SESSION_TRANSCRIPT_TYPE, applySessionPatchToMetadata, buildSessionContextForLlm, buildTranscriptEnvelope, effectiveWorkspacePathForSession, fallbackTitleFromMessages, fileStemToSessionKey, flattenMessageContent, generateSessionTitleFromMessages, getOrLoadSessionSearchIndex, invalidateSessionSearchIndexCache, isTranscriptContextEntry, isWebchatSessionKey, maybeAutoTitleSessionStore, mergeLlmMessagesPreservingContextRows, messagesToClientHistory, normalizeCompactionCheckpointId, normalizeWorkingDirectoryInput, parseStoredTranscriptJson, resolveEffectiveReasoningLevel, resolveEffectiveThinkingLevel, resolveReasoningLevel, resolveSessionShardRelativePath, resolveThinkingLevel, resolveVerboseLevel, sanitizeSessionPathSegment, sanitizeSessionTitle, shouldAutoTitleSessionKey, shouldSkipWebchatInboundByAbortCutoff, stripTrailingWebchatEarlySaveUserIfPresent, transcriptRowsFromJsonArray };
@@ -3,6 +3,9 @@ import { SessionStore } from './store.js';
3
3
  import type { SessionMetadata, SessionDetail, SessionListQuery, PaginatedResult, GlobalSessionStats, ExportFormat, SessionStatus } from './types.js';
4
4
  import type { Message } from './types.js';
5
5
  import type { CompactionConfig, CompactionResult } from '../agent/memory/compaction.js';
6
+ import type { XopcSessionTranscriptV1 } from './transcript-format.js';
7
+ import type { XopcTranscriptContextEntry } from './session-context-for-llm.js';
8
+ import { type SessionPatchBody } from './patch-metadata.js';
6
9
  import type { WindowConfig } from '../agent/memory/window.js';
7
10
  import type { Config } from '../config/schema.js';
8
11
  export interface SessionManagerConfig {
@@ -24,7 +27,19 @@ export declare class SessionManager extends EventEmitter {
24
27
  * Subagent sessions have keys starting with 'subagent:'.
25
28
  */
26
29
  listSubagents(query?: SessionListQuery): Promise<PaginatedResult<SessionMetadata>>;
27
- getSession(key: string): Promise<SessionDetail | null>;
30
+ getSession(key: string, options?: {
31
+ includeTranscriptSummary?: boolean;
32
+ includeTranscriptRows?: boolean;
33
+ }): Promise<SessionDetail | null>;
34
+ /**
35
+ * OpenClaw-style `sessions.patch`: partial metadata (name, tags, customData shallow merge).
36
+ */
37
+ patchSession(key: string, patch: SessionPatchBody): Promise<{
38
+ ok: true;
39
+ } | {
40
+ ok: false;
41
+ error: string;
42
+ }>;
28
43
  getSessionMetadata(key: string): Promise<SessionMetadata | null>;
29
44
  deleteSession(key: string): Promise<boolean>;
30
45
  deleteSessions(keys: string[]): Promise<{
@@ -77,8 +92,14 @@ export declare class SessionManager extends EventEmitter {
77
92
  }) => void): void;
78
93
  /** Load messages for a session key */
79
94
  loadMessages(key: string): Promise<import("@mariozechner/pi-agent-core/dist/types.js").AgentMessage[]>;
95
+ /** Wrapped transcript document (stable id, compaction history); null if missing or not a valid envelope. */
96
+ loadTranscriptDocument(key: string): Promise<XopcSessionTranscriptV1 | null>;
80
97
  /** Save messages for a session key */
81
98
  saveMessages(key: string, messages: any[]): Promise<void>;
99
+ /**
100
+ * Append `kind: 'context'` transcript row (persisted, excluded from {@link loadMessages} / LLM).
101
+ */
102
+ appendTranscriptContextEntry(key: string, entry: Omit<XopcTranscriptContextEntry, 'kind'> & Partial<Pick<XopcTranscriptContextEntry, 'kind'>>): Promise<void>;
82
103
  /** Delete session data */
83
104
  delete(key: string): Promise<void>;
84
105
  /** Token/window stats for a message list */
@@ -105,6 +126,10 @@ export declare class SessionManager extends EventEmitter {
105
126
  totalTokensAfter: number;
106
127
  lastCompactionAt: any;
107
128
  }>;
129
+ /** List pre-compaction transcript snapshots (newest first). */
130
+ listCompactionCheckpoints(key: string): Promise<import("./types.js").CompactionCheckpointSummary[]>;
131
+ getCompactionCheckpointDetail(key: string, checkpointId: string): Promise<import("./types.js").CompactionCheckpointDetail>;
132
+ restoreCompactionCheckpoint(key: string, checkpointId: string): Promise<void>;
108
133
  /** Estimate token usage for messages */
109
134
  estimateTokenUsage(key: string, messages: any[]): Promise<number>;
110
135
  }
@@ -1,6 +1,7 @@
1
1
  import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
3
  import { SessionStore } from "./store.js";
4
+ import { applySessionPatchToMetadata } from "./patch-metadata.js";
4
5
  import EventEmitter$1 from "events";
5
6
  //#region src/session/manager.ts
6
7
  init_logger();
@@ -44,11 +45,26 @@ var SessionManager = class extends EventEmitter$1 {
44
45
  hasMore: false
45
46
  };
46
47
  }
47
- async getSession(key) {
48
- const session = await this.store.get(key);
48
+ async getSession(key, options) {
49
+ const session = await this.store.get(key, options);
49
50
  if (session) this.emit("sessionAccessed", { key });
50
51
  return session;
51
52
  }
53
+ /**
54
+ * OpenClaw-style `sessions.patch`: partial metadata (name, tags, customData shallow merge).
55
+ */
56
+ async patchSession(key, patch) {
57
+ const meta = await this.store.getMetadata(key);
58
+ if (!meta) return {
59
+ ok: false,
60
+ error: "Session not found"
61
+ };
62
+ const updates = applySessionPatchToMetadata(meta, patch);
63
+ if (Object.keys(updates).length === 0) return { ok: true };
64
+ await this.store.updateMetadata(key, updates);
65
+ this.emit("sessionUpdated", { key });
66
+ return { ok: true };
67
+ }
52
68
  async getSessionMetadata(key) {
53
69
  return this.store.getMetadata(key);
54
70
  }
@@ -178,10 +194,21 @@ var SessionManager = class extends EventEmitter$1 {
178
194
  async loadMessages(key) {
179
195
  return this.store.loadMessages(key);
180
196
  }
197
+ /** Wrapped transcript document (stable id, compaction history); null if missing or not a valid envelope. */
198
+ async loadTranscriptDocument(key) {
199
+ return this.store.loadTranscriptDocument(key);
200
+ }
181
201
  /** Save messages for a session key */
182
202
  async saveMessages(key, messages) {
183
203
  return this.store.saveMessages(key, messages);
184
204
  }
205
+ /**
206
+ * Append `kind: 'context'` transcript row (persisted, excluded from {@link loadMessages} / LLM).
207
+ */
208
+ async appendTranscriptContextEntry(key, entry) {
209
+ await this.store.appendTranscriptContextEntry(key, entry);
210
+ this.emit("sessionUpdated", { key });
211
+ }
185
212
  /** Delete session data */
186
213
  async delete(key) {
187
214
  await this.store.delete(key);
@@ -202,6 +229,16 @@ var SessionManager = class extends EventEmitter$1 {
202
229
  async getCompactionStats(key) {
203
230
  return this.store.getCompactionStats(key);
204
231
  }
232
+ /** List pre-compaction transcript snapshots (newest first). */
233
+ listCompactionCheckpoints(key) {
234
+ return this.store.listCompactionCheckpoints(key);
235
+ }
236
+ getCompactionCheckpointDetail(key, checkpointId) {
237
+ return this.store.getCompactionCheckpointDetail(key, checkpointId);
238
+ }
239
+ restoreCompactionCheckpoint(key, checkpointId) {
240
+ return this.store.restoreCompactionCheckpoint(key, checkpointId);
241
+ }
205
242
  /** Estimate token usage for messages */
206
243
  async estimateTokenUsage(key, messages) {
207
244
  return this.store.estimateTokens(messages);