@xopcai/xopc 0.0.77 → 0.0.78
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.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agents-DN3vr8pb.js → agents-Bh_9-1KB.js} +2 -2
- package/dist/gateway/static/root/assets/{agents-DN3vr8pb.js.map → agents-Bh_9-1KB.js.map} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-BUn41aPi.js → apps-page-CB5anZpc.js} +2 -2
- package/dist/gateway/static/root/assets/{apps-page-BUn41aPi.js.map → apps-page-CB5anZpc.js.map} +1 -1
- package/dist/gateway/static/root/assets/{channels-settings-CYMmWDtP.js → channels-settings-Bt1sprhC.js} +2 -2
- package/dist/gateway/static/root/assets/{channels-settings-CYMmWDtP.js.map → channels-settings-Bt1sprhC.js.map} +1 -1
- package/dist/gateway/static/root/assets/{channels-status-swr-sJj4ueTp.js → channels-status-swr-Crgak3fg.js} +2 -2
- package/dist/gateway/static/root/assets/{channels-status-swr-sJj4ueTp.js.map → channels-status-swr-Crgak3fg.js.map} +1 -1
- package/dist/gateway/static/root/assets/{cron-api-CLxnaHdq.js → cron-api-CzJGvQQ7.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-api-CLxnaHdq.js.map → cron-api-CzJGvQQ7.js.map} +1 -1
- package/dist/gateway/static/root/assets/{cron-page-BAQ8xSnJ.js → cron-page-BoNRJNVV.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-page-BAQ8xSnJ.js.map → cron-page-BoNRJNVV.js.map} +1 -1
- package/dist/gateway/static/root/assets/{dist-BfJYxiK5.js → dist-a-eaOUvs.js} +2 -2
- package/dist/gateway/static/root/assets/{dist-BfJYxiK5.js.map → dist-a-eaOUvs.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-bgvVs-Sy.js → extension-debug-page-D-O1XjAa.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-debug-page-bgvVs-Sy.js.map → extension-debug-page-D-O1XjAa.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-SG4TVv-u.js → extension-page-B2VpqBTH.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-page-SG4TVv-u.js.map → extension-page-B2VpqBTH.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-CJZRTsjF.js → extension-settings-page-CmBcQfeO.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-settings-page-CJZRTsjF.js.map → extension-settings-page-CmBcQfeO.js.map} +1 -1
- package/dist/gateway/static/root/assets/{fetch-K_0JRCXU.js → fetch-EGO9T3MN.js} +3 -3
- package/dist/gateway/static/root/assets/{fetch-K_0JRCXU.js.map → fetch-EGO9T3MN.js.map} +1 -1
- package/dist/gateway/static/root/assets/{field-primitives-Z76hyBYS.js → field-primitives-Bh7G1y4D.js} +2 -2
- package/dist/gateway/static/root/assets/{field-primitives-Z76hyBYS.js.map → field-primitives-Bh7G1y4D.js.map} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-config-api-BqfDabSI.js → heartbeat-config-api-DxpIEZNs.js} +2 -2
- package/dist/gateway/static/root/assets/{heartbeat-config-api-BqfDabSI.js.map → heartbeat-config-api-DxpIEZNs.js.map} +1 -1
- package/dist/gateway/static/root/assets/{index-ChiUhJAs.js → index-Dxy9ZCtC.js} +5 -5
- package/dist/gateway/static/root/assets/{index-ChiUhJAs.js.map → index-Dxy9ZCtC.js.map} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-DrIMhDE2.js → logs-page-Dw58E2GE.js} +2 -2
- package/dist/gateway/static/root/assets/{logs-page-DrIMhDE2.js.map → logs-page-Dw58E2GE.js.map} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-B-RGO3N0.js → sessions-page-CPkhCy57.js} +2 -2
- package/dist/gateway/static/root/assets/{sessions-page-B-RGO3N0.js.map → sessions-page-CPkhCy57.js.map} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-Csvl1iL6.js → settings-form-section-DLZDVMEf.js} +2 -2
- package/dist/gateway/static/root/assets/{settings-form-section-Csvl1iL6.js.map → settings-form-section-DLZDVMEf.js.map} +1 -1
- package/dist/gateway/static/root/assets/settings-page-CVPCa0PE.js +4 -0
- package/dist/gateway/static/root/assets/settings-page-CVPCa0PE.js.map +1 -0
- package/dist/gateway/static/root/assets/{skills-page-dHwx2vh0.js → skills-page-DueZ9Qfg.js} +2 -2
- package/dist/gateway/static/root/assets/{skills-page-dHwx2vh0.js.map → skills-page-DueZ9Qfg.js.map} +1 -1
- package/dist/gateway/static/root/assets/{theme-store-Bl5A2Fd_.js → theme-store-CWPq9gW1.js} +2 -2
- package/dist/gateway/static/root/assets/{theme-store-Bl5A2Fd_.js.map → theme-store-CWPq9gW1.js.map} +1 -1
- package/dist/gateway/static/root/assets/{utils-COYrNFF7.js → utils-Cnix55r9.js} +2 -2
- package/dist/gateway/static/root/assets/{utils-COYrNFF7.js.map → utils-Cnix55r9.js.map} +1 -1
- package/dist/gateway/static/root/assets/{voice-api-key-field-5WZZaxH3.js → voice-api-key-field-BR3Ut06g.js} +2 -2
- package/dist/gateway/static/root/assets/{voice-api-key-field-5WZZaxH3.js.map → voice-api-key-field-BR3Ut06g.js.map} +1 -1
- package/dist/gateway/static/root/index.html +3 -3
- package/dist/package.js +1 -1
- package/dist/src/browser/providers/browser-ext-install.js +23 -4
- package/dist/src/browser/providers/browser-ext-install.js.map +1 -1
- package/dist/src/cli/commands/tunnel.js +4 -5
- package/dist/src/cli/commands/tunnel.js.map +1 -1
- package/dist/src/config/index.js +2 -2
- package/dist/src/config/rules.js +0 -5
- package/dist/src/config/rules.js.map +1 -1
- package/dist/src/config/schema.d.ts +0 -27
- package/dist/src/config/schema.js +4 -18
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/gateway/auth-rate-limit.d.ts +2 -0
- package/dist/src/gateway/auth-rate-limit.js +9 -3
- package/dist/src/gateway/auth-rate-limit.js.map +1 -1
- package/dist/src/gateway/hono/app.js +19 -13
- package/dist/src/gateway/hono/app.js.map +1 -1
- package/dist/src/gateway/hono/lib/config-payload.d.ts +3 -1
- package/dist/src/gateway/hono/lib/config-payload.js +1 -2
- package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
- package/dist/src/gateway/hono/routes/tunnel.js +32 -30
- package/dist/src/gateway/hono/routes/tunnel.js.map +1 -1
- package/dist/src/gateway/host.d.ts +24 -0
- package/dist/src/gateway/host.js +33 -1
- package/dist/src/gateway/host.js.map +1 -1
- package/dist/src/gateway/index.d.ts +1 -1
- package/dist/src/gateway/index.js +2 -2
- package/dist/src/gateway/runtime-config.js +1 -8
- package/dist/src/gateway/runtime-config.js.map +1 -1
- package/dist/src/gateway/security/audit.js +4 -4
- package/dist/src/gateway/security/audit.js.map +1 -1
- package/dist/src/gateway/server.js +2 -3
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service/types.d.ts +2 -0
- package/dist/src/gateway/service.d.ts +2 -0
- package/dist/src/gateway/service.js +7 -2
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/tunnel/frp-subdomain-host.d.ts +2 -0
- package/dist/src/tunnel/frp-subdomain-host.js +15 -0
- package/dist/src/tunnel/frp-subdomain-host.js.map +1 -0
- package/dist/src/tunnel/gateway-lifecycle.d.ts +0 -4
- package/dist/src/tunnel/gateway-lifecycle.js +9 -11
- package/dist/src/tunnel/gateway-lifecycle.js.map +1 -1
- package/dist/src/tunnel/index.d.ts +2 -4
- package/dist/src/tunnel/index.js +2 -4
- package/dist/src/tunnel/pair-url.js +7 -1
- package/dist/src/tunnel/pair-url.js.map +1 -1
- package/dist/src/tunnel/pairing.d.ts +13 -0
- package/dist/src/tunnel/pairing.js +48 -1
- package/dist/src/tunnel/pairing.js.map +1 -1
- package/dist/src/tunnel/tunnel-config.js +2 -16
- package/dist/src/tunnel/tunnel-config.js.map +1 -1
- package/dist/src/tunnel/tunnel-service.d.ts +1 -10
- package/dist/src/tunnel/tunnel-service.js +7 -60
- package/dist/src/tunnel/tunnel-service.js.map +1 -1
- package/dist/src/tunnel/tunnel-types.d.ts +3 -18
- package/dist/src/tunnel/well-known.d.ts +5 -0
- package/dist/src/tunnel/well-known.js +2 -1
- package/dist/src/tunnel/well-known.js.map +1 -1
- package/package.json +2 -2
- package/dist/gateway/static/root/assets/settings-page-nxAc0ta1.js +0 -4
- package/dist/gateway/static/root/assets/settings-page-nxAc0ta1.js.map +0 -1
- package/dist/src/tunnel/acme-cert-store.d.ts +0 -34
- package/dist/src/tunnel/acme-cert-store.js +0 -184
- package/dist/src/tunnel/acme-cert-store.js.map +0 -1
- package/dist/src/tunnel/acme-client.d.ts +0 -50
- package/dist/src/tunnel/acme-client.js +0 -473
- package/dist/src/tunnel/acme-client.js.map +0 -1
- package/dist/src/tunnel/acme-crypto.d.ts +0 -25
- package/dist/src/tunnel/acme-crypto.js +0 -58
- package/dist/src/tunnel/acme-crypto.js.map +0 -1
- package/dist/src/tunnel/acme-csr.d.ts +0 -5
- package/dist/src/tunnel/acme-csr.js +0 -48
- package/dist/src/tunnel/acme-csr.js.map +0 -1
- package/dist/src/tunnel/tls-server.d.ts +0 -14
- package/dist/src/tunnel/tls-server.js +0 -126
- package/dist/src/tunnel/tls-server.js.map +0 -1
- package/dist/src/tunnel/tunnel-e2e-config.d.ts +0 -11
- package/dist/src/tunnel/tunnel-e2e-config.js +0 -29
- package/dist/src/tunnel/tunnel-e2e-config.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 { existsSync, mkdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { AgentService } from '../agent/service.js';\nimport { ChannelManager } from '../channels/manager.js';\nimport { CHAT_CHANNEL_ORDER, getChatChannelMeta } from '../channels/registry.js';\nimport { setPairingBroadcastSink } from '../channels/pairing/pairing-events.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 { installExtensionFromStoreZip, peekExtensionIdFromStoreZip } from '../extensions/install.js';\nimport { getExtensionLockfileManager } from '../extensions/lockfile.js';\nimport { HeartbeatService, heartbeatRunnerConfigFromConfig } from './heartbeat/index.js';\nimport { ConfigHotReloader } from '../config/reload.js';\nimport { SessionIndex } 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 { wireTunnelEventsToGateway } from '../tunnel/gateway-lifecycle.js';\nimport {\n stopTailscaleExposure,\n} from './tailscale-lifecycle.js';\nimport { getExposureManager } from '../remote-access/exposure-manager.js';\nimport { sanitizeTunnelConfig } from '../tunnel/tunnel-config.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 { assertGatewayRuntimeConfig } from './runtime-config.js';\nimport { isGatewayStrictSecurityEnabled } from './auth-rate-limit.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 resolveExtensionsDir,\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 listRegisteredProviders,\n resolveSkillsMarketplaceProvider,\n type MarketplaceCategoryOption,\n type SkillsStoreListParams,\n type UnifiedMarketplaceListResponse,\n type UnifiedMarketplacePackageDetail,\n} from '../agent/skills/skills-marketplace.js';\nimport {\n downloadExtensionStoreZipBuffer,\n fetchMarketplacePackageDetail,\n resolveExtensionZipDownloadUrl,\n resolveExtensionsStoreBaseUrl,\n type MarketplacePackageDetail,\n} from '../agent/skills/marketplace/adapters/store/store-api-client.js';\nimport { createSkillConfigManager } from '../agent/skills/config.js';\nimport { removeSkillsLockEntry } from '../agent/skills/hub-lock.js';\nimport type { SkillCatalogEntry } from '../agent/agent-manager.js';\nimport type { SkillMarkdownPreviewPayload } from '../agent/skills/types.js';\nimport type { ManagedSkillListItem } from '../agent/skills/managed-store.js';\nimport { PACKAGE_VERSION } from '../package-version.js';\n\nimport {\n disposeAllSessionMcpRuntimes,\n retireSessionMcpRuntimeForSessionKey,\n} from '../agent/mcp/bundle-mcp-tools.js';\nimport { buildSessionKey, parseSessionKey } from '../routing/session-key.js';\nimport { getDefaultAgentId } from '../routing/resolve-route.js';\nimport { scheduleGatewayUpdateCheck } from '../infra/update-startup.js';\nimport { resolveChannelConnectDeferSet } from './resolve-channel-connect-defer.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 {\n GatewayChannelStartupPhase1Metrics,\n GatewayChannelStartupPhase2Metrics,\n GatewayServiceConfig,\n ServiceEvent,\n} from './service/types.js';\n\nexport type {\n GatewayChannelStartupPhase1Metrics,\n GatewayChannelStartupPhase2Metrics,\n GatewayServiceConfig,\n ServiceEvent,\n} 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 browserExtensionProvider: import('../browser/providers/extension.js').ExtensionBrowserProvider | null = null;\n private browserExtensionRelease: (() => Promise<void>) | null = null;\n /** `${host}:${port}` when the gateway holds the extension bridge listener. */\n private browserExtensionBindKey: string | null = null;\n private heartbeatService: HeartbeatService;\n private sessionIndex: SessionIndex;\n private running = false;\n private configReloader: ConfigHotReloader | null = null;\n /** In-flight coalesced apply after PATCH/save (Telegram `getMe` must not block HTTP). */\n private channelReloadFlushPromise: Promise<void> | null = null;\n private channelReloadPending = false;\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 /** Snapshot for phase-2 metrics / logs (ids deferred at phase-1 `start()`). */\n private lastDeferredChannelConnectIds: string[] = [];\n private lastChannelConnectDeferMode: 'auto' | 'off' | 'explicit' = 'auto';\n private lastChannelConnectDeferSource: 'off' | 'explicit' | 'meta' = 'off';\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 if (sanitizeTunnelConfig(this.config)) {\n void writeConfigToDisk(this.config, this.configPath).catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, phase: 'tunnel_sanitize', errorMessage: em }, `Tunnel config sanitize persist failed: ${em}`);\n });\n }\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 const gatewayPort = this.config.gateway?.port ?? 18790;\n const runtimeConfig = assertGatewayRuntimeConfig({\n cfg: this.config,\n auth: this.auth,\n bindOverride: serviceConfig.listenBind,\n port: gatewayPort,\n });\n\n // Security audit: non-blocking warnings for remaining risk signals\n auditGatewayConfig({\n auth: this.auth,\n bindHost: runtimeConfig.bindHost,\n corsOrigins: runtimeConfig.corsOrigins,\n rateLimitEnabled: runtimeConfig.rateLimitEnabled,\n tlsEnabled: runtimeConfig.tlsEnabled,\n trustedProxies: this.config.gateway?.trustedProxies,\n allowRealIpFallback: this.config.gateway?.allowRealIpFallback === true,\n dangerouslyAllowHostHeaderOriginFallback: runtimeConfig.dangerouslyAllowHostHeaderOriginFallback,\n strictSecurityEnabled: isGatewayStrictSecurityEnabled(this.config),\n rateLimitConfigured: this.config.gateway?.auth?.rateLimit !== undefined,\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 if (this.auth.mode === 'trusted-proxy') {\n log.info(\n {\n mode: this.auth.mode,\n userHeader: this.auth.trustedProxy?.userHeader,\n trustedProxyCount: this.config.gateway?.trustedProxies?.length ?? 0,\n },\n 'Trusted-proxy authentication configured',\n );\n } else {\n log.info({ mode: this.auth.mode }, 'Authentication configured');\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 // Session index + files shared with AgentService (webchat `/goal` metadata must match GET /api/goals/webchat).\n this.sessionIndex = new SessionIndex({\n config: this.config,\n });\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 sessionStore: this.sessionIndex.getStore(),\n onSessionMetadataUpdated: (sessionKey) => {\n this.sessionIndex.emit('sessionUpdated', { key: sessionKey });\n },\n onSessionTranscriptUpdated: (sessionKey) => {\n this.emit('session.transcript_updated', { key: sessionKey });\n },\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 this.agentService.setPersistentGoalWebchatContinuationScheduler((sessionKey, message) => {\n const scheduleWhenIdle = () => {\n if (this.agentService.getInboundTurnDepth(sessionKey) > 0) {\n setTimeout(scheduleWhenIdle, 50);\n return;\n }\n if (this.activeWebchatRunBySession.has(sessionKey)) {\n setTimeout(scheduleWhenIdle, 50);\n return;\n }\n void this.drainScheduledWebchatContinuation(sessionKey, message);\n };\n queueMicrotask(scheduleWhenIdle);\n });\n\n this.heartbeatService = new HeartbeatService({\n agentService: this.agentService,\n messageBus: this.bus,\n cronService: this.cronService,\n sessionStore: this.sessionIndex.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 /** Hermes-style: after HTTP sets a goal, enqueue the goal text as the next user turn. */\n enqueueWebchatPersistentGoalKickoff(sessionKey: string, goalText: string): void {\n queueMicrotask(() => {\n void this.drainScheduledWebchatContinuation(sessionKey, goalText);\n });\n }\n\n /**\n * Create extension loader and resolve configs (load runs in start() before channels).\n */\n private initializeExtensionLoader(): void {\n try {\n this.extensionLoader = new ExtensionLoader({\n workspaceDir: this.workspacePath,\n extensionsDir: resolveExtensionsDir(),\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 setPairingBroadcastSink((type, payload) => {\n this.emit(type, payload);\n });\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.sessionIndex,\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: init all; optional defer for meta.deferConnectUntilAfterListen (GatewayServer)\n const phase1StartedAt = performance.now();\n const t0 = performance.now();\n await this.channelManager.initialize();\n const channelInitMs = performance.now() - t0;\n\n const t1 = performance.now();\n const deferResolution = resolveChannelConnectDeferSet({\n config: this.config,\n channelManager: this.channelManager,\n deferChannelConnectUntilAfterHttp: this.serviceConfig.deferChannelConnectUntilAfterHttp === true,\n });\n const deferConnect = deferResolution.deferPluginIds;\n const deferPlanMs = performance.now() - t1;\n this.lastDeferredChannelConnectIds = [...deferConnect];\n this.lastChannelConnectDeferMode = deferResolution.mode;\n this.lastChannelConnectDeferSource = deferResolution.source;\n\n if (deferConnect.size > 0) {\n log.info({ channels: [...deferConnect] }, 'Deferring channel outbound connect until HTTP listen');\n }\n\n const t2 = performance.now();\n await this.channelManager.start(\n deferConnect.size > 0 ? { deferConnectPluginIds: deferConnect } : undefined,\n );\n const channelPhase1StartMs = performance.now() - t2;\n\n let replayOutboundMs: number | null = null;\n if (this.serviceConfig.deferChannelConnectUntilAfterHttp !== true) {\n const tr = performance.now();\n await this.channelManager.replayPendingOutboundMessages();\n replayOutboundMs = performance.now() - tr;\n }\n\n const channelStartupPhase1TotalMs = performance.now() - phase1StartedAt;\n const gwDeferMode = this.config.gateway?.channelConnectDeferMode ?? 'auto';\n const phase1Metrics: GatewayChannelStartupPhase1Metrics = {\n deferChannelConnectUntilAfterHttp: this.serviceConfig.deferChannelConnectUntilAfterHttp === true,\n channelConnectDeferMode: this.serviceConfig.deferChannelConnectUntilAfterHttp\n ? deferResolution.mode\n : gwDeferMode,\n channelConnectDeferSource: deferResolution.source,\n deferredChannelIds: this.lastDeferredChannelConnectIds,\n deferredChannelCount: this.lastDeferredChannelConnectIds.length,\n channelInitMs: Math.round(channelInitMs),\n deferPlanMs: Math.round(deferPlanMs),\n channelPhase1StartMs: Math.round(channelPhase1StartMs),\n replayOutboundMs: replayOutboundMs === null ? null : Math.round(replayOutboundMs),\n channelStartupPhase1TotalMs: Math.round(channelStartupPhase1TotalMs),\n };\n log.info(\n { phase: 'gateway.channel_startup', stage: 'phase1', ...phase1Metrics },\n 'Gateway channel startup phase-1 complete',\n );\n\n // Initialize session manager\n await this.sessionIndex.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.sessionIndex.getStore(),\n getDefaultCronAgentId: () => getDefaultAgentId(this.config),\n });\n\n this.sessionIndex.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 void import('../browser/providers/browser-ext-install.js')\n .then(({ ensureBrowserExtensionOnStartup }) => ensureBrowserExtensionOnStartup(this.config))\n .catch((err) => log.warn({ err }, 'Browser extension artifact ensure failed'));\n\n // Start browser extension WS server if configured\n await this.startBrowserExtensionServerIfNeeded();\n\n // Start agent service (runs in background)\n this.agentService.start().catch((err) => {\n log.error({ err }, 'Agent service error');\n });\n\n // Outbound drain: after deferred channel connects when using HTTP lifecycle (avoid racing Telegram).\n if (this.serviceConfig.deferChannelConnectUntilAfterHttp !== true) {\n this.startOutboundProcessor().catch((err) => {\n log.error({ err }, 'Outbound processor error');\n });\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 wireTunnelEventsToGateway(this);\n\n log.debug('Gateway service started');\n }\n\n /** After HTTP is listening: exposure auto-start (Tailscale, then FRP tunnel). */\n private async runExposureAutoStartIfConfigured(): Promise<void> {\n const port = this.config.gateway?.port ?? 18790;\n await getExposureManager().autoStart(this.config, port, this.getAuthToken());\n }\n\n /**\n * Called by `GatewayServer` when the HTTP listener is bound. Starts channels that\n * opted into `meta.deferConnectUntilAfterListen`, then replays outbound queue.\n */\n async onHttpListening(): Promise<void> {\n await this.runExposureAutoStartIfConfigured();\n\n if (this.serviceConfig.deferChannelConnectUntilAfterHttp !== true) {\n return;\n }\n const listenStartedAt = performance.now();\n try {\n const tDef = performance.now();\n await this.channelManager.startDeferredConnects();\n const channelPhase2DeferredMs = performance.now() - tDef;\n\n const tr = performance.now();\n await this.channelManager.replayPendingOutboundMessages();\n const replayOutboundMs = performance.now() - tr;\n\n this.startOutboundProcessor().catch((err) => {\n log.error({ err }, 'Outbound processor error');\n });\n this.emit('channels.status', { channels: this.getChannelsStatus() });\n\n const onHttpListeningTotalMs = performance.now() - listenStartedAt;\n const phase2Metrics: GatewayChannelStartupPhase2Metrics = {\n channelConnectDeferMode: this.lastChannelConnectDeferMode,\n channelConnectDeferSource: this.lastChannelConnectDeferSource,\n deferredChannelIds: this.lastDeferredChannelConnectIds,\n channelPhase2DeferredMs: Math.round(channelPhase2DeferredMs),\n replayOutboundMs: Math.round(replayOutboundMs),\n onHttpListeningTotalMs: Math.round(onHttpListeningTotalMs),\n };\n log.info(\n { phase: 'gateway.channel_startup', stage: 'phase2', ...phase2Metrics },\n 'Gateway channel startup phase-2 complete (HTTP listening)',\n );\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.error(\n {\n err,\n errorMessage: em,\n phase: 'gateway.channel_startup',\n stage: 'phase2',\n deferredChannelIds: this.lastDeferredChannelConnectIds,\n elapsedMs: Math.round(performance.now() - listenStartedAt),\n },\n `Deferred channel startup after HTTP listen failed: ${em}`,\n );\n }\n }\n\n async stop(): Promise<void> {\n if (!this.running) return;\n\n setPairingBroadcastSink(null);\n\n log.debug('Stopping gateway service...');\n\n await stopTailscaleExposure().catch((err) => {\n log.warn({ err }, 'Tailscale exposure shutdown failed');\n });\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 if (this.channelReloadFlushPromise) {\n await this.channelReloadFlushPromise.catch(() => {});\n this.channelReloadFlushPromise = null;\n }\n\n // Stop heartbeat service\n this.heartbeatService.stop();\n\n // Stop browser extension WS server (shared acquire/release with BrowserManager)\n if (this.browserExtensionRelease) {\n await this.browserExtensionRelease();\n this.browserExtensionRelease = null;\n }\n this.browserExtensionProvider = null;\n this.browserExtensionBindKey = null;\n\n registerClarifyBridge(null);\n this.clarifyBridge.dispose();\n await disposeAllSessionMcpRuntimes().catch((err) => {\n log.warn({ err }, 'MCP runtime shutdown failed');\n });\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 this.lastDeferredChannelConnectIds = [];\n this.lastChannelConnectDeferMode = 'auto';\n this.lastChannelConnectDeferSource = 'off';\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 /** Start the browser extension WS server when backend is 'extension'. */\n private async startBrowserExtensionServerIfNeeded(): Promise<void> {\n await this.reconcileBrowserExtensionServer();\n }\n\n /**\n * Start/stop/rebind the Chrome extension bridge when `agents.defaults.browser` changes.\n * PATCH saves update config in memory without re-running gateway startup, so this must run on save too.\n */\n async reconcileBrowserExtensionServer(): Promise<void> {\n const browser = (this.config.agents?.defaults as Record<string, unknown> | undefined)?.browser as\n | Record<string, unknown>\n | undefined;\n const wantsExtension = browser?.backend === 'extension';\n\n if (!wantsExtension) {\n if (this.browserExtensionRelease) {\n await this.browserExtensionRelease();\n this.browserExtensionRelease = null;\n this.browserExtensionProvider = null;\n this.browserExtensionBindKey = null;\n log.debug('Browser extension WS server stopped (backend is not extension)');\n }\n return;\n }\n\n const ext = browser.extension as Record<string, unknown> | undefined;\n const port = typeof ext?.port === 'number' ? ext.port : 19820;\n const host = typeof ext?.host === 'string' && ext.host ? ext.host : '127.0.0.1';\n const bindKey = `${host}:${port}`;\n\n if (this.browserExtensionRelease && this.browserExtensionBindKey === bindKey) {\n return;\n }\n\n if (this.browserExtensionRelease) {\n await this.browserExtensionRelease();\n this.browserExtensionRelease = null;\n this.browserExtensionProvider = null;\n this.browserExtensionBindKey = null;\n }\n\n try {\n const { acquireExtensionBrowserServer } = await import('../browser/providers/extension-ws-acquire.js');\n const { provider, release } = await acquireExtensionBrowserServer({ port, host });\n this.browserExtensionProvider = provider;\n this.browserExtensionRelease = release;\n this.browserExtensionBindKey = bindKey;\n log.info({ port, host }, 'Browser extension WS server started');\n } catch (err) {\n const code = err && typeof err === 'object' && 'code' in err ? (err as { code: unknown }).code : undefined;\n log.error(\n {\n err,\n phase: 'browser_extension_ws',\n ...(code === 'EADDRINUSE'\n ? {\n bindPort: port,\n bindHost: host,\n hint: 'Another process holds this port (default 19820). Stop it or set agents.defaults.browser.extension.port.',\n }\n : {}),\n },\n `Failed to start browser extension WS server: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\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 onMcpReload: (newConfig) => this.handleMcpReload(newConfig),\n onExtensionsReload: async (newConfig, changedPaths) => {\n await this.handleExtensionsReload(newConfig, changedPaths);\n },\n onFullRestart: (newConfig) => {\n log.warn(\n { requiresProcessRestart: true, hint: 'Restart the gateway process (hot reload cannot apply this change).' },\n 'Config reload: full gateway restart required — see prior \"restartPaths\" info log',\n );\n this.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 void this.reconcileBrowserExtensionServer();\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 /**\n * Apply channel plugins for the latest persisted `this.config` without blocking `saveConfig` HTTP handlers.\n * Coalesces rapid saves so Telegram/Weixin do not stop/start repeatedly.\n */\n private scheduleChannelPluginsAfterPersist(): void {\n this.channelReloadPending = true;\n if (this.channelReloadFlushPromise) return;\n this.channelReloadFlushPromise = (async () => {\n try {\n while (this.channelReloadPending) {\n this.channelReloadPending = false;\n await this.handleChannelsReload(this.config);\n }\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.error({ err, errorMessage: em }, `Channel reload after persist failed: ${em}`);\n } finally {\n this.channelReloadFlushPromise = null;\n if (this.channelReloadPending) {\n this.scheduleChannelPluginsAfterPersist();\n }\n }\n })();\n }\n\n /**\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 private handleMcpReload(newConfig: Config): void {\n log.debug('Reloading MCP config...');\n this.config = newConfig;\n void disposeAllSessionMcpRuntimes().catch((err) => {\n log.warn({ err }, 'MCP runtime dispose on config reload failed');\n });\n this.emit('config.reload', { section: 'mcp' });\n log.debug('MCP 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 * 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 if (sanitizeTunnelConfig(this.config)) {\n await writeConfigToDisk(this.config, this.configPath);\n }\n this.agentService.applyAgentDefaultsFromConfig(this.config);\n await this.reconcileBrowserExtensionServer();\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 // Align watcher baseline before channel hooks run so fs `change` does not re-apply the same diff concurrently.\n this.configReloader?.syncCurrentConfig(this.config);\n }\n\n async saveConfig(config: Config): Promise<{ saved: boolean; error?: string }> {\n try {\n await this.writeConfigAndReloadFromDisk(config);\n this.scheduleChannelPluginsAfterPersist();\n return { saved: true };\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n log.error({ error }, 'Failed to save config');\n return { saved: false, error };\n }\n }\n\n /**\n * App store (phase 1): persist `extensions.enabled` / `extensions.disabled` for a bundled extension.\n * Marketplace-only extensions hot-load on enable; disable still needs a gateway restart to unload.\n */\n async setBundledExtensionActivationTarget(\n extensionId: string,\n wanted: boolean,\n ): Promise<{ ok: boolean; error?: string; requiresGatewayRestart: boolean }> {\n const loader = this.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\n let requiresGatewayRestart = true;\n if (wanted) {\n try {\n loader.invalidateManifestCache();\n await loader.loadByActivationPlan();\n requiresGatewayRestart = false;\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, extensionId: id, errorMessage: em },\n `Extension hot-load after bundled activation failed: ${em}`,\n );\n requiresGatewayRestart = true;\n }\n }\n\n this.emit('config.reload', { section: 'extensions', source: 'bundled-activation' });\n return { ok: true, requiresGatewayRestart };\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 this.scheduleChannelPluginsAfterPersist();\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 sessionIndex: this.sessionIndex,\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.sessionIndex\n .updateSessionMetadata(sk, { abortCutoffTimestamp: cutoffTs })\n .catch(() => {});\n void this.sessionIndex\n .appendTranscriptContextEntry(sk, {\n text: 'Webchat agent run aborted',\n data: { runId, abortCutoffTimestamp: cutoffTs },\n })\n .catch(() => {});\n }\n c.abort();\n for (const sk of keysToMark) {\n void import('../agent/embedded/runs.js').then(({ abortEmbeddedRun }) => abortEmbeddedRun(sk));\n }\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, {\n clientCreatedAtMs: Date.now(),\n });\n for await (const _ of gen) {\n // Relay + `agent.stream` broadcast; UI attaches via pending runId + resume.\n }\n } catch (err) {\n log.warn({ err, sessionKey }, 'Scheduled webchat continuation failed');\n }\n }\n\n /**\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 * Hub metadata for gateway console (built-in registry + registered channel plugins).\n */\n getChannelsHubMeta(): Array<{\n id: string;\n label: string;\n description: string;\n manageable: boolean;\n order: number;\n }> {\n const manageableIds = new Set<string>(['telegram', 'weixin', 'feishu']);\n const byId = new Map<\n string,\n { id: string; label: string; description: string; manageable: boolean; order: number }\n >();\n\n for (const plugin of this.channelManager.getAllPlugins()) {\n byId.set(plugin.id, {\n id: plugin.id,\n label: plugin.meta.label,\n description: plugin.meta.blurb,\n manageable: manageableIds.has(plugin.id),\n order: plugin.meta.order ?? 999,\n });\n }\n\n CHAT_CHANNEL_ORDER.forEach((id, index) => {\n if (byId.has(id)) return;\n const meta = getChatChannelMeta(id);\n byId.set(id, {\n id,\n label: meta.label,\n description: meta.description,\n manageable: true,\n order: index,\n });\n });\n\n return Array.from(byId.values()).toSorted((a, b) => {\n if (a.order !== b.order) return a.order - b.order;\n return a.id.localeCompare(b.id);\n });\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\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(\n params: SkillsStoreListParams,\n provider?: string,\n ): Promise<UnifiedMarketplaceListResponse> {\n return listMarketplacePackages(this.config, params, provider);\n }\n\n async fetchSkillsMarketplaceCategories(\n provider?: string,\n ): Promise<{ items: MarketplaceCategoryOption[] }> {\n return listMarketplaceCategories(this.config, provider);\n }\n\n async fetchSkillsMarketplacePackageDetail(\n packageName: string,\n provider?: string,\n ): Promise<UnifiedMarketplacePackageDetail> {\n return getMarketplacePackageDetail(this.config, packageName, provider);\n }\n\n async installSkillFromMarketplace(opts: {\n name: string;\n version?: string;\n overwrite?: boolean;\n provider?: string;\n }): Promise<{ skillId: string; path: string }> {\n const { buffer, skillId } = await downloadFromMarketplace(\n this.config,\n opts.name,\n opts.version,\n opts.provider,\n );\n return this.installManagedSkillZip(buffer, { skillId, overwrite: opts.overwrite ?? false });\n }\n\n /**\n * xopc-store extension package preview (type must be `extension`).\n */\n async fetchExtensionMarketplacePackageDetail(packageName: string): Promise<MarketplacePackageDetail> {\n const base = resolveExtensionsStoreBaseUrl(this.config);\n const detail = await fetchMarketplacePackageDetail(base, packageName.trim());\n if (detail.type !== 'extension') {\n throw new Error(\n `Package \"${packageName}\" is not an extension (store type: ${detail.type}).`,\n );\n }\n return detail;\n }\n\n private mergeExtensionEnabledIntoConfig(extensionId: string): Config {\n const id = extensionId.trim();\n const prevExt = this.config.extensions;\n const baseExt =\n prevExt && typeof prevExt === 'object' && !Array.isArray(prevExt)\n ? { ...(prevExt as Record<string, unknown>) }\n : {};\n const enabledRaw = baseExt.enabled;\n const enabled = Array.isArray(enabledRaw)\n ? [...enabledRaw.filter((x): x is string => typeof x === 'string')]\n : [];\n if (!enabled.includes(id)) enabled.push(id);\n\n const disabledRaw = baseExt.disabled;\n const nextExt: Record<string, unknown> = { ...baseExt, enabled };\n if (Array.isArray(disabledRaw)) {\n const next = disabledRaw.filter((x): x is string => typeof x === 'string' && x !== id);\n if (next.length > 0) nextExt.disabled = next;\n else delete nextExt.disabled;\n }\n\n return {\n ...this.config,\n extensions: nextExt,\n } as Config;\n }\n\n private mergeExtensionRemovedFromEnabledConfig(extensionId: string): Config {\n const id = extensionId.trim();\n const prevExt = this.config.extensions;\n const baseExt =\n prevExt && typeof prevExt === 'object' && !Array.isArray(prevExt)\n ? { ...(prevExt as Record<string, unknown>) }\n : {};\n const enabledRaw = baseExt.enabled;\n const enabled = Array.isArray(enabledRaw)\n ? enabledRaw.filter((x): x is string => typeof x === 'string' && x !== id)\n : [];\n return {\n ...this.config,\n extensions: { ...baseExt, enabled },\n } as Config;\n }\n\n /**\n * Install an extension from xopc-store into the global extensions directory (`~/.xopc/extensions`),\n * append its id to `extensions.enabled`, refresh the loader, and emit `config.reload`.\n */\n async installExtensionFromMarketplace(opts: {\n name: string;\n version?: string;\n overwrite?: boolean;\n }): Promise<{ extensionId: string; version: string; requiresGatewayRestart: boolean }> {\n const packageName = opts.name.trim();\n if (!packageName) {\n throw new Error('Package name is required');\n }\n const storeBase = resolveExtensionsStoreBaseUrl(this.config);\n const targetDir = resolveExtensionsDir();\n mkdirSync(targetDir, { recursive: true });\n\n const { downloadUrl, version } = await resolveExtensionZipDownloadUrl(\n storeBase,\n packageName,\n opts.version,\n );\n const buf = await downloadExtensionStoreZipBuffer(storeBase, downloadUrl);\n\n if (opts.overwrite) {\n const peekId = peekExtensionIdFromStoreZip(buf);\n if (peekId && existsSync(join(targetDir, peekId))) {\n rmSync(join(targetDir, peekId), { recursive: true, force: true });\n }\n }\n\n const result = await installExtensionFromStoreZip(buf, targetDir);\n if (!result.ok || !result.extensionId) {\n throw new Error(result.error ?? 'Extension install failed');\n }\n\n const lock = getExtensionLockfileManager();\n await lock.upsert(result.extensionId, {\n name: result.extensionId,\n version,\n resolved: packageName,\n source: 'store',\n });\n\n const nextConfig = this.mergeExtensionEnabledIntoConfig(result.extensionId);\n const saved = await this.saveConfig(nextConfig);\n if (!saved.saved) {\n throw new Error(saved.error ?? 'Failed to save config after extension install');\n }\n\n const channelIdsBefore = new Set(this.channelManager.getAllPlugins().map((p) => p.id));\n let requiresGatewayRestart = false;\n try {\n if (this.extensionLoader) {\n this.extensionLoader.invalidateManifestCache();\n await this.extensionLoader.loadByActivationPlan();\n const reg = this.extensionLoader.getRegistry();\n for (const p of reg.channelPlugins) {\n if (!channelIdsBefore.has(p.id)) {\n requiresGatewayRestart = true;\n break;\n }\n }\n }\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, errorMessage: em }, `Extension loader refresh after marketplace install failed: ${em}`);\n requiresGatewayRestart = true;\n }\n\n this.emit('config.reload', { section: 'extensions', source: 'marketplace-install' });\n return { extensionId: result.extensionId, version, requiresGatewayRestart };\n }\n\n /**\n * Remove a user-installed (global or per-agent extensions dir) extension from disk and config.\n */\n async uninstallUserExtension(extensionId: string): Promise<{ requiresGatewayRestart: boolean }> {\n const id = extensionId.trim();\n if (!id) {\n throw new Error('extensionId is required');\n }\n const loader = this.extensionLoader;\n if (!loader) {\n throw new Error('Extensions unavailable');\n }\n const discovered = loader.discoverExtensions();\n const ext = discovered.find((e) => e.id === id);\n if (!ext) {\n throw new Error(`Extension not found: ${id}`);\n }\n if (ext.source === 'bundled') {\n throw new Error('Built-in extensions cannot be uninstalled from the marketplace UI');\n }\n if (existsSync(ext.path)) {\n rmSync(ext.path, { recursive: true, force: true });\n }\n await getExtensionLockfileManager().remove(id);\n const nextConfig = this.mergeExtensionRemovedFromEnabledConfig(id);\n const saved = await this.saveConfig(nextConfig);\n if (!saved.saved) {\n throw new Error(saved.error ?? 'Failed to save config after extension uninstall');\n }\n try {\n loader.invalidateManifestCache();\n await loader.loadByActivationPlan();\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, errorMessage: em }, `Extension loader refresh after uninstall failed: ${em}`);\n }\n this.emit('config.reload', { section: 'extensions', source: 'marketplace-uninstall' });\n return { requiresGatewayRestart: true };\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 /** All registered marketplace providers (built-in + extension-contributed). */\n getSkillsMarketplaceProviders(): Array<{ id: string; displayName: string }> {\n return listRegisteredProviders();\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 sessionIndexInstance(): SessionIndex {\n return this.sessionIndex;\n }\n\n /** @deprecated Use {@link sessionIndexInstance}. */\n get sessionManagerInstance(): SessionIndex {\n return this.sessionIndex;\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.sessionIndex.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.sessionIndex.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.sessionIndex.getSession(key, options);\n }\n\n async getSessionMessagePage(\n key: string,\n options?: {\n offset?: number;\n limit?: number;\n before?: string;\n includeTranscriptSummary?: boolean;\n includeTranscriptRows?: boolean;\n },\n ) {\n return this.sessionIndex.getSessionMessagePage(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.sessionIndex.patchSession(key, body);\n }\n\n async listSessionCompactionCheckpoints(key: string) {\n return this.sessionIndex.listCompactionCheckpoints(key);\n }\n\n async getSessionCompactionCheckpoint(key: string, checkpointId: string) {\n return this.sessionIndex.getCompactionCheckpointDetail(key, checkpointId);\n }\n\n async restoreSessionCompactionCheckpoint(key: string, checkpointId: string): Promise<void> {\n await this.sessionIndex.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.sessionIndex\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.sessionIndex.deleteSession(key);\n if (result) {\n this.agentService.evictSessionAgent(key);\n await retireSessionMcpRuntimeForSessionKey({ sessionKey: key, reason: 'session-delete' });\n }\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.sessionIndex.deleteSessions(keys);\n }\n\n /**\n * Rename a session\n */\n async renameSession(key: string, name: string): Promise<{ renamed: boolean }> {\n await this.sessionIndex.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.sessionIndex.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.sessionIndex.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.sessionIndex.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.sessionIndex.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.sessionIndex.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.sessionIndex.unpinSession(key);\n return { unpinned: true };\n }\n\n /**\n * Search sessions\n */\n async searchSessions(query: string) {\n return this.sessionIndex.searchSessions(query);\n }\n\n /**\n * Search within a session\n */\n async searchInSession(key: string, keyword: string) {\n return this.sessionIndex.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.sessionIndex.exportSession(key, format);\n return { content };\n }\n\n /**\n * Get session statistics\n */\n async getSessionStats() {\n return this.sessionIndex.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.sessionIndex, 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' | 'trusted-proxy' {\n return this.auth.mode;\n }\n\n /** Resolved gateway auth (mode, credentials, trusted-proxy config). */\n getResolvedAuth(): ResolvedGatewayAuth {\n return this.auth;\n }\n\n /**\n * Get current auth token (for CLI server integration).\n * Returns undefined if mode is not token.\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 { existsSync, mkdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { AgentService } from '../agent/service.js';\nimport { ChannelManager } from '../channels/manager.js';\nimport { CHAT_CHANNEL_ORDER, getChatChannelMeta } from '../channels/registry.js';\nimport { setPairingBroadcastSink } from '../channels/pairing/pairing-events.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 { installExtensionFromStoreZip, peekExtensionIdFromStoreZip } from '../extensions/install.js';\nimport { getExtensionLockfileManager } from '../extensions/lockfile.js';\nimport { HeartbeatService, heartbeatRunnerConfigFromConfig } from './heartbeat/index.js';\nimport { ConfigHotReloader } from '../config/reload.js';\nimport { SessionIndex } 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 { wireTunnelEventsToGateway } from '../tunnel/gateway-lifecycle.js';\nimport {\n stopTailscaleExposure,\n} from './tailscale-lifecycle.js';\nimport { getExposureManager } from '../remote-access/exposure-manager.js';\nimport { sanitizeTunnelConfig } from '../tunnel/tunnel-config.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 { assertGatewayRuntimeConfig } from './runtime-config.js';\nimport { resolveEffectiveGatewayPort } from './host.js';\nimport { isGatewayStrictSecurityEnabled } from './auth-rate-limit.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 resolveExtensionsDir,\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 listRegisteredProviders,\n resolveSkillsMarketplaceProvider,\n type MarketplaceCategoryOption,\n type SkillsStoreListParams,\n type UnifiedMarketplaceListResponse,\n type UnifiedMarketplacePackageDetail,\n} from '../agent/skills/skills-marketplace.js';\nimport {\n downloadExtensionStoreZipBuffer,\n fetchMarketplacePackageDetail,\n resolveExtensionZipDownloadUrl,\n resolveExtensionsStoreBaseUrl,\n type MarketplacePackageDetail,\n} from '../agent/skills/marketplace/adapters/store/store-api-client.js';\nimport { createSkillConfigManager } from '../agent/skills/config.js';\nimport { removeSkillsLockEntry } from '../agent/skills/hub-lock.js';\nimport type { SkillCatalogEntry } from '../agent/agent-manager.js';\nimport type { SkillMarkdownPreviewPayload } from '../agent/skills/types.js';\nimport type { ManagedSkillListItem } from '../agent/skills/managed-store.js';\nimport { PACKAGE_VERSION } from '../package-version.js';\n\nimport {\n disposeAllSessionMcpRuntimes,\n retireSessionMcpRuntimeForSessionKey,\n} from '../agent/mcp/bundle-mcp-tools.js';\nimport { buildSessionKey, parseSessionKey } from '../routing/session-key.js';\nimport { getDefaultAgentId } from '../routing/resolve-route.js';\nimport { scheduleGatewayUpdateCheck } from '../infra/update-startup.js';\nimport { resolveChannelConnectDeferSet } from './resolve-channel-connect-defer.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 {\n GatewayChannelStartupPhase1Metrics,\n GatewayChannelStartupPhase2Metrics,\n GatewayServiceConfig,\n ServiceEvent,\n} from './service/types.js';\n\nexport type {\n GatewayChannelStartupPhase1Metrics,\n GatewayChannelStartupPhase2Metrics,\n GatewayServiceConfig,\n ServiceEvent,\n} 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 browserExtensionProvider: import('../browser/providers/extension.js').ExtensionBrowserProvider | null = null;\n private browserExtensionRelease: (() => Promise<void>) | null = null;\n /** `${host}:${port}` when the gateway holds the extension bridge listener. */\n private browserExtensionBindKey: string | null = null;\n private heartbeatService: HeartbeatService;\n private sessionIndex: SessionIndex;\n private running = false;\n private configReloader: ConfigHotReloader | null = null;\n /** In-flight coalesced apply after PATCH/save (Telegram `getMe` must not block HTTP). */\n private channelReloadFlushPromise: Promise<void> | null = null;\n private channelReloadPending = false;\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 /** Snapshot for phase-2 metrics / logs (ids deferred at phase-1 `start()`). */\n private lastDeferredChannelConnectIds: string[] = [];\n private lastChannelConnectDeferMode: 'auto' | 'off' | 'explicit' = 'auto';\n private lastChannelConnectDeferSource: 'off' | 'explicit' | 'meta' = 'off';\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 if (sanitizeTunnelConfig(this.config)) {\n void writeConfigToDisk(this.config, this.configPath).catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, phase: 'tunnel_sanitize', errorMessage: em }, `Tunnel config sanitize persist failed: ${em}`);\n });\n }\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 const gatewayPort = this.getEffectiveListenPort();\n const runtimeConfig = assertGatewayRuntimeConfig({\n cfg: this.config,\n auth: this.auth,\n bindOverride: serviceConfig.listenBind,\n port: gatewayPort,\n });\n\n // Security audit: non-blocking warnings for remaining risk signals\n auditGatewayConfig({\n auth: this.auth,\n bindHost: runtimeConfig.bindHost,\n corsOrigins: runtimeConfig.corsOrigins,\n rateLimitEnabled: runtimeConfig.rateLimitEnabled,\n tlsEnabled: runtimeConfig.tlsEnabled,\n trustedProxies: this.config.gateway?.trustedProxies,\n allowRealIpFallback: this.config.gateway?.allowRealIpFallback === true,\n dangerouslyAllowHostHeaderOriginFallback: runtimeConfig.dangerouslyAllowHostHeaderOriginFallback,\n strictSecurityEnabled: isGatewayStrictSecurityEnabled(this.config),\n rateLimitConfigured: this.config.gateway?.auth?.rateLimit !== undefined,\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 if (this.auth.mode === 'trusted-proxy') {\n log.info(\n {\n mode: this.auth.mode,\n userHeader: this.auth.trustedProxy?.userHeader,\n trustedProxyCount: this.config.gateway?.trustedProxies?.length ?? 0,\n },\n 'Trusted-proxy authentication configured',\n );\n } else {\n log.info({ mode: this.auth.mode }, 'Authentication configured');\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 // Session index + files shared with AgentService (webchat `/goal` metadata must match GET /api/goals/webchat).\n this.sessionIndex = new SessionIndex({\n config: this.config,\n });\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 sessionStore: this.sessionIndex.getStore(),\n onSessionMetadataUpdated: (sessionKey) => {\n this.sessionIndex.emit('sessionUpdated', { key: sessionKey });\n },\n onSessionTranscriptUpdated: (sessionKey) => {\n this.emit('session.transcript_updated', { key: sessionKey });\n },\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 this.agentService.setPersistentGoalWebchatContinuationScheduler((sessionKey, message) => {\n const scheduleWhenIdle = () => {\n if (this.agentService.getInboundTurnDepth(sessionKey) > 0) {\n setTimeout(scheduleWhenIdle, 50);\n return;\n }\n if (this.activeWebchatRunBySession.has(sessionKey)) {\n setTimeout(scheduleWhenIdle, 50);\n return;\n }\n void this.drainScheduledWebchatContinuation(sessionKey, message);\n };\n queueMicrotask(scheduleWhenIdle);\n });\n\n this.heartbeatService = new HeartbeatService({\n agentService: this.agentService,\n messageBus: this.bus,\n cronService: this.cronService,\n sessionStore: this.sessionIndex.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 /** Hermes-style: after HTTP sets a goal, enqueue the goal text as the next user turn. */\n enqueueWebchatPersistentGoalKickoff(sessionKey: string, goalText: string): void {\n queueMicrotask(() => {\n void this.drainScheduledWebchatContinuation(sessionKey, goalText);\n });\n }\n\n /**\n * Create extension loader and resolve configs (load runs in start() before channels).\n */\n private initializeExtensionLoader(): void {\n try {\n this.extensionLoader = new ExtensionLoader({\n workspaceDir: this.workspacePath,\n extensionsDir: resolveExtensionsDir(),\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 setPairingBroadcastSink((type, payload) => {\n this.emit(type, payload);\n });\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.sessionIndex,\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: init all; optional defer for meta.deferConnectUntilAfterListen (GatewayServer)\n const phase1StartedAt = performance.now();\n const t0 = performance.now();\n await this.channelManager.initialize();\n const channelInitMs = performance.now() - t0;\n\n const t1 = performance.now();\n const deferResolution = resolveChannelConnectDeferSet({\n config: this.config,\n channelManager: this.channelManager,\n deferChannelConnectUntilAfterHttp: this.serviceConfig.deferChannelConnectUntilAfterHttp === true,\n });\n const deferConnect = deferResolution.deferPluginIds;\n const deferPlanMs = performance.now() - t1;\n this.lastDeferredChannelConnectIds = [...deferConnect];\n this.lastChannelConnectDeferMode = deferResolution.mode;\n this.lastChannelConnectDeferSource = deferResolution.source;\n\n if (deferConnect.size > 0) {\n log.info({ channels: [...deferConnect] }, 'Deferring channel outbound connect until HTTP listen');\n }\n\n const t2 = performance.now();\n await this.channelManager.start(\n deferConnect.size > 0 ? { deferConnectPluginIds: deferConnect } : undefined,\n );\n const channelPhase1StartMs = performance.now() - t2;\n\n let replayOutboundMs: number | null = null;\n if (this.serviceConfig.deferChannelConnectUntilAfterHttp !== true) {\n const tr = performance.now();\n await this.channelManager.replayPendingOutboundMessages();\n replayOutboundMs = performance.now() - tr;\n }\n\n const channelStartupPhase1TotalMs = performance.now() - phase1StartedAt;\n const gwDeferMode = this.config.gateway?.channelConnectDeferMode ?? 'auto';\n const phase1Metrics: GatewayChannelStartupPhase1Metrics = {\n deferChannelConnectUntilAfterHttp: this.serviceConfig.deferChannelConnectUntilAfterHttp === true,\n channelConnectDeferMode: this.serviceConfig.deferChannelConnectUntilAfterHttp\n ? deferResolution.mode\n : gwDeferMode,\n channelConnectDeferSource: deferResolution.source,\n deferredChannelIds: this.lastDeferredChannelConnectIds,\n deferredChannelCount: this.lastDeferredChannelConnectIds.length,\n channelInitMs: Math.round(channelInitMs),\n deferPlanMs: Math.round(deferPlanMs),\n channelPhase1StartMs: Math.round(channelPhase1StartMs),\n replayOutboundMs: replayOutboundMs === null ? null : Math.round(replayOutboundMs),\n channelStartupPhase1TotalMs: Math.round(channelStartupPhase1TotalMs),\n };\n log.info(\n { phase: 'gateway.channel_startup', stage: 'phase1', ...phase1Metrics },\n 'Gateway channel startup phase-1 complete',\n );\n\n // Initialize session manager\n await this.sessionIndex.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.sessionIndex.getStore(),\n getDefaultCronAgentId: () => getDefaultAgentId(this.config),\n });\n\n this.sessionIndex.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 void import('../browser/providers/browser-ext-install.js')\n .then(({ ensureBrowserExtensionOnStartup }) => ensureBrowserExtensionOnStartup(this.config))\n .catch((err) => log.warn({ err }, 'Browser extension artifact ensure failed'));\n\n // Start browser extension WS server if configured\n await this.startBrowserExtensionServerIfNeeded();\n\n // Start agent service (runs in background)\n this.agentService.start().catch((err) => {\n log.error({ err }, 'Agent service error');\n });\n\n // Outbound drain: after deferred channel connects when using HTTP lifecycle (avoid racing Telegram).\n if (this.serviceConfig.deferChannelConnectUntilAfterHttp !== true) {\n this.startOutboundProcessor().catch((err) => {\n log.error({ err }, 'Outbound processor error');\n });\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 wireTunnelEventsToGateway(this);\n\n log.debug('Gateway service started');\n }\n\n /** After HTTP is listening: exposure auto-start (Tailscale, then FRP tunnel). */\n private async runExposureAutoStartIfConfigured(): Promise<void> {\n const port = this.getEffectiveListenPort();\n await getExposureManager().autoStart(this.config, port, this.getAuthToken());\n }\n\n /**\n * Called by `GatewayServer` when the HTTP listener is bound. Starts channels that\n * opted into `meta.deferConnectUntilAfterListen`, then replays outbound queue.\n */\n async onHttpListening(): Promise<void> {\n await this.runExposureAutoStartIfConfigured();\n\n if (this.serviceConfig.deferChannelConnectUntilAfterHttp !== true) {\n return;\n }\n const listenStartedAt = performance.now();\n try {\n const tDef = performance.now();\n await this.channelManager.startDeferredConnects();\n const channelPhase2DeferredMs = performance.now() - tDef;\n\n const tr = performance.now();\n await this.channelManager.replayPendingOutboundMessages();\n const replayOutboundMs = performance.now() - tr;\n\n this.startOutboundProcessor().catch((err) => {\n log.error({ err }, 'Outbound processor error');\n });\n this.emit('channels.status', { channels: this.getChannelsStatus() });\n\n const onHttpListeningTotalMs = performance.now() - listenStartedAt;\n const phase2Metrics: GatewayChannelStartupPhase2Metrics = {\n channelConnectDeferMode: this.lastChannelConnectDeferMode,\n channelConnectDeferSource: this.lastChannelConnectDeferSource,\n deferredChannelIds: this.lastDeferredChannelConnectIds,\n channelPhase2DeferredMs: Math.round(channelPhase2DeferredMs),\n replayOutboundMs: Math.round(replayOutboundMs),\n onHttpListeningTotalMs: Math.round(onHttpListeningTotalMs),\n };\n log.info(\n { phase: 'gateway.channel_startup', stage: 'phase2', ...phase2Metrics },\n 'Gateway channel startup phase-2 complete (HTTP listening)',\n );\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.error(\n {\n err,\n errorMessage: em,\n phase: 'gateway.channel_startup',\n stage: 'phase2',\n deferredChannelIds: this.lastDeferredChannelConnectIds,\n elapsedMs: Math.round(performance.now() - listenStartedAt),\n },\n `Deferred channel startup after HTTP listen failed: ${em}`,\n );\n }\n }\n\n async stop(): Promise<void> {\n if (!this.running) return;\n\n setPairingBroadcastSink(null);\n\n log.debug('Stopping gateway service...');\n\n await stopTailscaleExposure().catch((err) => {\n log.warn({ err }, 'Tailscale exposure shutdown failed');\n });\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 if (this.channelReloadFlushPromise) {\n await this.channelReloadFlushPromise.catch(() => {});\n this.channelReloadFlushPromise = null;\n }\n\n // Stop heartbeat service\n this.heartbeatService.stop();\n\n // Stop browser extension WS server (shared acquire/release with BrowserManager)\n if (this.browserExtensionRelease) {\n await this.browserExtensionRelease();\n this.browserExtensionRelease = null;\n }\n this.browserExtensionProvider = null;\n this.browserExtensionBindKey = null;\n\n registerClarifyBridge(null);\n this.clarifyBridge.dispose();\n await disposeAllSessionMcpRuntimes().catch((err) => {\n log.warn({ err }, 'MCP runtime shutdown failed');\n });\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 this.lastDeferredChannelConnectIds = [];\n this.lastChannelConnectDeferMode = 'auto';\n this.lastChannelConnectDeferSource = 'off';\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 /** Start the browser extension WS server when backend is 'extension'. */\n private async startBrowserExtensionServerIfNeeded(): Promise<void> {\n await this.reconcileBrowserExtensionServer();\n }\n\n /**\n * Start/stop/rebind the Chrome extension bridge when `agents.defaults.browser` changes.\n * PATCH saves update config in memory without re-running gateway startup, so this must run on save too.\n */\n async reconcileBrowserExtensionServer(): Promise<void> {\n const browser = (this.config.agents?.defaults as Record<string, unknown> | undefined)?.browser as\n | Record<string, unknown>\n | undefined;\n const wantsExtension = browser?.backend === 'extension';\n\n if (!wantsExtension) {\n if (this.browserExtensionRelease) {\n await this.browserExtensionRelease();\n this.browserExtensionRelease = null;\n this.browserExtensionProvider = null;\n this.browserExtensionBindKey = null;\n log.debug('Browser extension WS server stopped (backend is not extension)');\n }\n return;\n }\n\n const ext = browser.extension as Record<string, unknown> | undefined;\n const port = typeof ext?.port === 'number' ? ext.port : 19820;\n const host = typeof ext?.host === 'string' && ext.host ? ext.host : '127.0.0.1';\n const bindKey = `${host}:${port}`;\n\n if (this.browserExtensionRelease && this.browserExtensionBindKey === bindKey) {\n return;\n }\n\n if (this.browserExtensionRelease) {\n await this.browserExtensionRelease();\n this.browserExtensionRelease = null;\n this.browserExtensionProvider = null;\n this.browserExtensionBindKey = null;\n }\n\n try {\n const { acquireExtensionBrowserServer } = await import('../browser/providers/extension-ws-acquire.js');\n const { provider, release } = await acquireExtensionBrowserServer({ port, host });\n this.browserExtensionProvider = provider;\n this.browserExtensionRelease = release;\n this.browserExtensionBindKey = bindKey;\n log.info({ port, host }, 'Browser extension WS server started');\n } catch (err) {\n const code = err && typeof err === 'object' && 'code' in err ? (err as { code: unknown }).code : undefined;\n log.error(\n {\n err,\n phase: 'browser_extension_ws',\n ...(code === 'EADDRINUSE'\n ? {\n bindPort: port,\n bindHost: host,\n hint: 'Another process holds this port (default 19820). Stop it or set agents.defaults.browser.extension.port.',\n }\n : {}),\n },\n `Failed to start browser extension WS server: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\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 onMcpReload: (newConfig) => this.handleMcpReload(newConfig),\n onExtensionsReload: async (newConfig, changedPaths) => {\n await this.handleExtensionsReload(newConfig, changedPaths);\n },\n onFullRestart: (newConfig) => {\n log.warn(\n { requiresProcessRestart: true, hint: 'Restart the gateway process (hot reload cannot apply this change).' },\n 'Config reload: full gateway restart required — see prior \"restartPaths\" info log',\n );\n this.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 void this.reconcileBrowserExtensionServer();\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 /**\n * Apply channel plugins for the latest persisted `this.config` without blocking `saveConfig` HTTP handlers.\n * Coalesces rapid saves so Telegram/Weixin do not stop/start repeatedly.\n */\n private scheduleChannelPluginsAfterPersist(): void {\n this.channelReloadPending = true;\n if (this.channelReloadFlushPromise) return;\n this.channelReloadFlushPromise = (async () => {\n try {\n while (this.channelReloadPending) {\n this.channelReloadPending = false;\n await this.handleChannelsReload(this.config);\n }\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.error({ err, errorMessage: em }, `Channel reload after persist failed: ${em}`);\n } finally {\n this.channelReloadFlushPromise = null;\n if (this.channelReloadPending) {\n this.scheduleChannelPluginsAfterPersist();\n }\n }\n })();\n }\n\n /**\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 private handleMcpReload(newConfig: Config): void {\n log.debug('Reloading MCP config...');\n this.config = newConfig;\n void disposeAllSessionMcpRuntimes().catch((err) => {\n log.warn({ err }, 'MCP runtime dispose on config reload failed');\n });\n this.emit('config.reload', { section: 'mcp' });\n log.debug('MCP 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 * 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 if (sanitizeTunnelConfig(this.config)) {\n await writeConfigToDisk(this.config, this.configPath);\n }\n this.agentService.applyAgentDefaultsFromConfig(this.config);\n await this.reconcileBrowserExtensionServer();\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 // Align watcher baseline before channel hooks run so fs `change` does not re-apply the same diff concurrently.\n this.configReloader?.syncCurrentConfig(this.config);\n }\n\n async saveConfig(config: Config): Promise<{ saved: boolean; error?: string }> {\n try {\n await this.writeConfigAndReloadFromDisk(config);\n this.scheduleChannelPluginsAfterPersist();\n return { saved: true };\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n log.error({ error }, 'Failed to save config');\n return { saved: false, error };\n }\n }\n\n /**\n * App store (phase 1): persist `extensions.enabled` / `extensions.disabled` for a bundled extension.\n * Marketplace-only extensions hot-load on enable; disable still needs a gateway restart to unload.\n */\n async setBundledExtensionActivationTarget(\n extensionId: string,\n wanted: boolean,\n ): Promise<{ ok: boolean; error?: string; requiresGatewayRestart: boolean }> {\n const loader = this.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\n let requiresGatewayRestart = true;\n if (wanted) {\n try {\n loader.invalidateManifestCache();\n await loader.loadByActivationPlan();\n requiresGatewayRestart = false;\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, extensionId: id, errorMessage: em },\n `Extension hot-load after bundled activation failed: ${em}`,\n );\n requiresGatewayRestart = true;\n }\n }\n\n this.emit('config.reload', { section: 'extensions', source: 'bundled-activation' });\n return { ok: true, requiresGatewayRestart };\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 this.scheduleChannelPluginsAfterPersist();\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 sessionIndex: this.sessionIndex,\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.sessionIndex\n .updateSessionMetadata(sk, { abortCutoffTimestamp: cutoffTs })\n .catch(() => {});\n void this.sessionIndex\n .appendTranscriptContextEntry(sk, {\n text: 'Webchat agent run aborted',\n data: { runId, abortCutoffTimestamp: cutoffTs },\n })\n .catch(() => {});\n }\n c.abort();\n for (const sk of keysToMark) {\n void import('../agent/embedded/runs.js').then(({ abortEmbeddedRun }) => abortEmbeddedRun(sk));\n }\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, {\n clientCreatedAtMs: Date.now(),\n });\n for await (const _ of gen) {\n // Relay + `agent.stream` broadcast; UI attaches via pending runId + resume.\n }\n } catch (err) {\n log.warn({ err, sessionKey }, 'Scheduled webchat continuation failed');\n }\n }\n\n /**\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 * Hub metadata for gateway console (built-in registry + registered channel plugins).\n */\n getChannelsHubMeta(): Array<{\n id: string;\n label: string;\n description: string;\n manageable: boolean;\n order: number;\n }> {\n const manageableIds = new Set<string>(['telegram', 'weixin', 'feishu']);\n const byId = new Map<\n string,\n { id: string; label: string; description: string; manageable: boolean; order: number }\n >();\n\n for (const plugin of this.channelManager.getAllPlugins()) {\n byId.set(plugin.id, {\n id: plugin.id,\n label: plugin.meta.label,\n description: plugin.meta.blurb,\n manageable: manageableIds.has(plugin.id),\n order: plugin.meta.order ?? 999,\n });\n }\n\n CHAT_CHANNEL_ORDER.forEach((id, index) => {\n if (byId.has(id)) return;\n const meta = getChatChannelMeta(id);\n byId.set(id, {\n id,\n label: meta.label,\n description: meta.description,\n manageable: true,\n order: index,\n });\n });\n\n return Array.from(byId.values()).toSorted((a, b) => {\n if (a.order !== b.order) return a.order - b.order;\n return a.id.localeCompare(b.id);\n });\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 /** Effective HTTP listen port (CLI `--port` override or config default). */\n getEffectiveListenPort(): number {\n return resolveEffectiveGatewayPort(this.config, this.serviceConfig.listenPort);\n }\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(\n params: SkillsStoreListParams,\n provider?: string,\n ): Promise<UnifiedMarketplaceListResponse> {\n return listMarketplacePackages(this.config, params, provider);\n }\n\n async fetchSkillsMarketplaceCategories(\n provider?: string,\n ): Promise<{ items: MarketplaceCategoryOption[] }> {\n return listMarketplaceCategories(this.config, provider);\n }\n\n async fetchSkillsMarketplacePackageDetail(\n packageName: string,\n provider?: string,\n ): Promise<UnifiedMarketplacePackageDetail> {\n return getMarketplacePackageDetail(this.config, packageName, provider);\n }\n\n async installSkillFromMarketplace(opts: {\n name: string;\n version?: string;\n overwrite?: boolean;\n provider?: string;\n }): Promise<{ skillId: string; path: string }> {\n const { buffer, skillId } = await downloadFromMarketplace(\n this.config,\n opts.name,\n opts.version,\n opts.provider,\n );\n return this.installManagedSkillZip(buffer, { skillId, overwrite: opts.overwrite ?? false });\n }\n\n /**\n * xopc-store extension package preview (type must be `extension`).\n */\n async fetchExtensionMarketplacePackageDetail(packageName: string): Promise<MarketplacePackageDetail> {\n const base = resolveExtensionsStoreBaseUrl(this.config);\n const detail = await fetchMarketplacePackageDetail(base, packageName.trim());\n if (detail.type !== 'extension') {\n throw new Error(\n `Package \"${packageName}\" is not an extension (store type: ${detail.type}).`,\n );\n }\n return detail;\n }\n\n private mergeExtensionEnabledIntoConfig(extensionId: string): Config {\n const id = extensionId.trim();\n const prevExt = this.config.extensions;\n const baseExt =\n prevExt && typeof prevExt === 'object' && !Array.isArray(prevExt)\n ? { ...(prevExt as Record<string, unknown>) }\n : {};\n const enabledRaw = baseExt.enabled;\n const enabled = Array.isArray(enabledRaw)\n ? [...enabledRaw.filter((x): x is string => typeof x === 'string')]\n : [];\n if (!enabled.includes(id)) enabled.push(id);\n\n const disabledRaw = baseExt.disabled;\n const nextExt: Record<string, unknown> = { ...baseExt, enabled };\n if (Array.isArray(disabledRaw)) {\n const next = disabledRaw.filter((x): x is string => typeof x === 'string' && x !== id);\n if (next.length > 0) nextExt.disabled = next;\n else delete nextExt.disabled;\n }\n\n return {\n ...this.config,\n extensions: nextExt,\n } as Config;\n }\n\n private mergeExtensionRemovedFromEnabledConfig(extensionId: string): Config {\n const id = extensionId.trim();\n const prevExt = this.config.extensions;\n const baseExt =\n prevExt && typeof prevExt === 'object' && !Array.isArray(prevExt)\n ? { ...(prevExt as Record<string, unknown>) }\n : {};\n const enabledRaw = baseExt.enabled;\n const enabled = Array.isArray(enabledRaw)\n ? enabledRaw.filter((x): x is string => typeof x === 'string' && x !== id)\n : [];\n return {\n ...this.config,\n extensions: { ...baseExt, enabled },\n } as Config;\n }\n\n /**\n * Install an extension from xopc-store into the global extensions directory (`~/.xopc/extensions`),\n * append its id to `extensions.enabled`, refresh the loader, and emit `config.reload`.\n */\n async installExtensionFromMarketplace(opts: {\n name: string;\n version?: string;\n overwrite?: boolean;\n }): Promise<{ extensionId: string; version: string; requiresGatewayRestart: boolean }> {\n const packageName = opts.name.trim();\n if (!packageName) {\n throw new Error('Package name is required');\n }\n const storeBase = resolveExtensionsStoreBaseUrl(this.config);\n const targetDir = resolveExtensionsDir();\n mkdirSync(targetDir, { recursive: true });\n\n const { downloadUrl, version } = await resolveExtensionZipDownloadUrl(\n storeBase,\n packageName,\n opts.version,\n );\n const buf = await downloadExtensionStoreZipBuffer(storeBase, downloadUrl);\n\n if (opts.overwrite) {\n const peekId = peekExtensionIdFromStoreZip(buf);\n if (peekId && existsSync(join(targetDir, peekId))) {\n rmSync(join(targetDir, peekId), { recursive: true, force: true });\n }\n }\n\n const result = await installExtensionFromStoreZip(buf, targetDir);\n if (!result.ok || !result.extensionId) {\n throw new Error(result.error ?? 'Extension install failed');\n }\n\n const lock = getExtensionLockfileManager();\n await lock.upsert(result.extensionId, {\n name: result.extensionId,\n version,\n resolved: packageName,\n source: 'store',\n });\n\n const nextConfig = this.mergeExtensionEnabledIntoConfig(result.extensionId);\n const saved = await this.saveConfig(nextConfig);\n if (!saved.saved) {\n throw new Error(saved.error ?? 'Failed to save config after extension install');\n }\n\n const channelIdsBefore = new Set(this.channelManager.getAllPlugins().map((p) => p.id));\n let requiresGatewayRestart = false;\n try {\n if (this.extensionLoader) {\n this.extensionLoader.invalidateManifestCache();\n await this.extensionLoader.loadByActivationPlan();\n const reg = this.extensionLoader.getRegistry();\n for (const p of reg.channelPlugins) {\n if (!channelIdsBefore.has(p.id)) {\n requiresGatewayRestart = true;\n break;\n }\n }\n }\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, errorMessage: em }, `Extension loader refresh after marketplace install failed: ${em}`);\n requiresGatewayRestart = true;\n }\n\n this.emit('config.reload', { section: 'extensions', source: 'marketplace-install' });\n return { extensionId: result.extensionId, version, requiresGatewayRestart };\n }\n\n /**\n * Remove a user-installed (global or per-agent extensions dir) extension from disk and config.\n */\n async uninstallUserExtension(extensionId: string): Promise<{ requiresGatewayRestart: boolean }> {\n const id = extensionId.trim();\n if (!id) {\n throw new Error('extensionId is required');\n }\n const loader = this.extensionLoader;\n if (!loader) {\n throw new Error('Extensions unavailable');\n }\n const discovered = loader.discoverExtensions();\n const ext = discovered.find((e) => e.id === id);\n if (!ext) {\n throw new Error(`Extension not found: ${id}`);\n }\n if (ext.source === 'bundled') {\n throw new Error('Built-in extensions cannot be uninstalled from the marketplace UI');\n }\n if (existsSync(ext.path)) {\n rmSync(ext.path, { recursive: true, force: true });\n }\n await getExtensionLockfileManager().remove(id);\n const nextConfig = this.mergeExtensionRemovedFromEnabledConfig(id);\n const saved = await this.saveConfig(nextConfig);\n if (!saved.saved) {\n throw new Error(saved.error ?? 'Failed to save config after extension uninstall');\n }\n try {\n loader.invalidateManifestCache();\n await loader.loadByActivationPlan();\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, errorMessage: em }, `Extension loader refresh after uninstall failed: ${em}`);\n }\n this.emit('config.reload', { section: 'extensions', source: 'marketplace-uninstall' });\n return { requiresGatewayRestart: true };\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 /** All registered marketplace providers (built-in + extension-contributed). */\n getSkillsMarketplaceProviders(): Array<{ id: string; displayName: string }> {\n return listRegisteredProviders();\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 sessionIndexInstance(): SessionIndex {\n return this.sessionIndex;\n }\n\n /** @deprecated Use {@link sessionIndexInstance}. */\n get sessionManagerInstance(): SessionIndex {\n return this.sessionIndex;\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.sessionIndex.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.sessionIndex.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.sessionIndex.getSession(key, options);\n }\n\n async getSessionMessagePage(\n key: string,\n options?: {\n offset?: number;\n limit?: number;\n before?: string;\n includeTranscriptSummary?: boolean;\n includeTranscriptRows?: boolean;\n },\n ) {\n return this.sessionIndex.getSessionMessagePage(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.sessionIndex.patchSession(key, body);\n }\n\n async listSessionCompactionCheckpoints(key: string) {\n return this.sessionIndex.listCompactionCheckpoints(key);\n }\n\n async getSessionCompactionCheckpoint(key: string, checkpointId: string) {\n return this.sessionIndex.getCompactionCheckpointDetail(key, checkpointId);\n }\n\n async restoreSessionCompactionCheckpoint(key: string, checkpointId: string): Promise<void> {\n await this.sessionIndex.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.sessionIndex\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.sessionIndex.deleteSession(key);\n if (result) {\n this.agentService.evictSessionAgent(key);\n await retireSessionMcpRuntimeForSessionKey({ sessionKey: key, reason: 'session-delete' });\n }\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.sessionIndex.deleteSessions(keys);\n }\n\n /**\n * Rename a session\n */\n async renameSession(key: string, name: string): Promise<{ renamed: boolean }> {\n await this.sessionIndex.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.sessionIndex.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.sessionIndex.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.sessionIndex.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.sessionIndex.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.sessionIndex.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.sessionIndex.unpinSession(key);\n return { unpinned: true };\n }\n\n /**\n * Search sessions\n */\n async searchSessions(query: string) {\n return this.sessionIndex.searchSessions(query);\n }\n\n /**\n * Search within a session\n */\n async searchInSession(key: string, keyword: string) {\n return this.sessionIndex.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.sessionIndex.exportSession(key, format);\n return { content };\n }\n\n /**\n * Get session statistics\n */\n async getSessionStats() {\n return this.sessionIndex.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.sessionIndex, 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' | 'trusted-proxy' {\n return this.auth.mode;\n }\n\n /** Resolved gateway auth (mode, credentials, trusted-proxy config). */\n getResolvedAuth(): ResolvedGatewayAuth {\n return this.auth;\n }\n\n /**\n * Get current auth token (for CLI server integration).\n * Returns undefined if mode is not token.\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,15 @@
|
|
|
1
|
+
//#region src/tunnel/frp-subdomain-host.ts
|
|
2
|
+
/** Broker subdomain host derived from broker URL. */
|
|
3
|
+
function resolveFrpSubdomainHost(brokerUrl, override) {
|
|
4
|
+
if (override?.trim()) return override.trim();
|
|
5
|
+
try {
|
|
6
|
+
const host = new URL(brokerUrl.replace(/\/api\/?$/, "")).hostname;
|
|
7
|
+
if (host === "frp.xopc.ai" || host.endsWith(".frp.xopc.ai")) return "frp.xopc.ai";
|
|
8
|
+
if (host.includes(".")) return host;
|
|
9
|
+
} catch {}
|
|
10
|
+
return "frp.xopc.ai";
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { resolveFrpSubdomainHost };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=frp-subdomain-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frp-subdomain-host.js","names":[],"sources":["../../../src/tunnel/frp-subdomain-host.ts"],"sourcesContent":["/** Broker subdomain host derived from broker URL. */\nexport function resolveFrpSubdomainHost(brokerUrl: string, override?: string): string {\n if (override?.trim()) return override.trim();\n try {\n const host = new URL(brokerUrl.replace(/\\/api\\/?$/, '')).hostname;\n if (host === 'frp.xopc.ai' || host.endsWith('.frp.xopc.ai')) return 'frp.xopc.ai';\n if (host.includes('.')) return host;\n } catch {\n /* fall through */\n }\n return 'frp.xopc.ai';\n}\n"],"mappings":";;AACA,SAAgB,wBAAwB,WAAmB,UAA2B;AACpF,KAAI,UAAU,MAAM,CAAE,QAAO,SAAS,MAAM;AAC5C,KAAI;EACF,MAAM,OAAO,IAAI,IAAI,UAAU,QAAQ,aAAa,GAAG,CAAC,CAAC;AACzD,MAAI,SAAS,iBAAiB,KAAK,SAAS,eAAe,CAAE,QAAO;AACpE,MAAI,KAAK,SAAS,IAAI,CAAE,QAAO;SACzB;AAGR,QAAO"}
|
|
@@ -6,12 +6,8 @@ type TunnelEventSink = {
|
|
|
6
6
|
export declare function wireTunnelEventsToGateway(service: TunnelEventSink): void;
|
|
7
7
|
export type ConfigureTunnelFromGatewayConfigOptions = {
|
|
8
8
|
force?: boolean;
|
|
9
|
-
/** Apply config/env broker URL immediately; refresh from well-known in the background. */
|
|
10
9
|
deferWellKnownFetch?: boolean;
|
|
11
10
|
};
|
|
12
11
|
export declare function configureTunnelFromGatewayConfig(config: Config, opts?: ConfigureTunnelFromGatewayConfigOptions): Promise<void>;
|
|
13
|
-
/**
|
|
14
|
-
* Start FRP tunnel when `tunnel.autoStart` is set (CLI gateway / GatewayServer after HTTP listen).
|
|
15
|
-
*/
|
|
16
12
|
export declare function maybeAutoStartTunnelFromConfig(config: Config, gatewayToken: string | undefined): Promise<void>;
|
|
17
13
|
export {};
|
|
@@ -2,10 +2,9 @@ import { createLogger } from "../utils/logger/index.js";
|
|
|
2
2
|
import { init_logger } from "../utils/logger.js";
|
|
3
3
|
import { resolveGatewayEffectiveHost } from "../config/gateway-bind.js";
|
|
4
4
|
import { hasValidTunnelConsent } from "./consent.js";
|
|
5
|
-
import { subscribeCertStatus } from "./acme-cert-store.js";
|
|
6
5
|
import { resolveTunnelBrokerUrl, resolveTunnelRegistrationSecret } from "./env.js";
|
|
7
6
|
import { getTunnelService } from "./tunnel-service.js";
|
|
8
|
-
import { resolveFrpSubdomainHost
|
|
7
|
+
import { resolveFrpSubdomainHost } from "./frp-subdomain-host.js";
|
|
9
8
|
import { fetchTunnelWellKnown } from "./well-known.js";
|
|
10
9
|
//#region src/tunnel/gateway-lifecycle.ts
|
|
11
10
|
init_logger();
|
|
@@ -24,13 +23,8 @@ function wireTunnelEventsToGateway(service) {
|
|
|
24
23
|
tunnel.on("tunnel:disconnected", publish);
|
|
25
24
|
tunnel.on("tunnel:error", publish);
|
|
26
25
|
tunnel.on("tunnel:progress", publish);
|
|
27
|
-
subscribeCertStatus((cert) => {
|
|
28
|
-
service.emit("tunnel.cert.status", cert);
|
|
29
|
-
publish();
|
|
30
|
-
});
|
|
31
26
|
}
|
|
32
27
|
function applyTunnelServiceFromGatewayConfig(config, brokerUrl) {
|
|
33
|
-
const gatewayPort = (config.gateway ?? {}).port ?? 18790;
|
|
34
28
|
let registrationSecret;
|
|
35
29
|
try {
|
|
36
30
|
registrationSecret = resolveTunnelRegistrationSecret(process.env, brokerUrl, config.tunnel?.registrationSecret);
|
|
@@ -47,7 +41,6 @@ function applyTunnelServiceFromGatewayConfig(config, brokerUrl) {
|
|
|
47
41
|
registrationSecret,
|
|
48
42
|
autoStart: config.tunnel?.autoStart ?? false,
|
|
49
43
|
gatewayHost: resolveGatewayEffectiveHost(config),
|
|
50
|
-
e2e: resolveTunnelE2eConfig(config.tunnel, gatewayPort),
|
|
51
44
|
frpSubdomainHost: resolveFrpSubdomainHost(brokerUrl)
|
|
52
45
|
});
|
|
53
46
|
}
|
|
@@ -56,6 +49,14 @@ async function resolveBrokerUrlFromWellKnown(initialBrokerUrl) {
|
|
|
56
49
|
try {
|
|
57
50
|
const wellKnown = await fetchTunnelWellKnown(brokerUrl);
|
|
58
51
|
if (wellKnown.brokerUrl?.trim()) brokerUrl = wellKnown.brokerUrl.trim();
|
|
52
|
+
if (wellKnown.transport?.tls === "broker_terminated") log.debug({
|
|
53
|
+
brokerUrl,
|
|
54
|
+
phase: "tunnel_well_known"
|
|
55
|
+
}, "Broker uses wildcard TLS termination");
|
|
56
|
+
else if (wellKnown.transport?.tls) log.warn({
|
|
57
|
+
tls: wellKnown.transport.tls,
|
|
58
|
+
phase: "tunnel_well_known"
|
|
59
|
+
}, "Unexpected broker transport mode — expect broker_terminated");
|
|
59
60
|
} catch (err) {
|
|
60
61
|
log.debug({
|
|
61
62
|
err,
|
|
@@ -93,9 +94,6 @@ async function configureTunnelFromGatewayConfig(config, opts) {
|
|
|
93
94
|
if (deferredWellKnownRefresh) await deferredWellKnownRefresh;
|
|
94
95
|
applyTunnelServiceFromGatewayConfig(config, await resolveBrokerUrlFromWellKnown(initialBrokerUrl));
|
|
95
96
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Start FRP tunnel when `tunnel.autoStart` is set (CLI gateway / GatewayServer after HTTP listen).
|
|
98
|
-
*/
|
|
99
97
|
async function maybeAutoStartTunnelFromConfig(config, gatewayToken) {
|
|
100
98
|
if (!config.tunnel?.autoStart) return;
|
|
101
99
|
if (!hasValidTunnelConsent(config)) {
|