@xopcai/xopc 0.0.48 → 0.0.49
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/extensions/dingtalk/src/accounts.js +1 -1
- package/dist/extensions/dingtalk/src/accounts.js.map +1 -1
- package/dist/extensions/dingtalk/src/merge-config.js +1 -1
- package/dist/extensions/dingtalk/src/merge-config.js.map +1 -1
- package/dist/extensions/dingtalk/src/plugin.js +1 -1
- package/dist/extensions/dingtalk/src/plugin.js.map +1 -1
- package/dist/extensions/feishu/src/adapters/cli-login.js +8 -8
- package/dist/extensions/feishu/src/adapters/cli-login.js.map +1 -1
- package/dist/extensions/feishu/src/adapters/onboard-cli.js +8 -8
- package/dist/extensions/feishu/src/adapters/onboard-cli.js.map +1 -1
- package/dist/extensions/feishu/src/plugin.js +1 -1
- package/dist/extensions/feishu/src/plugin.js.map +1 -1
- package/dist/extensions/feishu/src/schema/config-schema.js +2 -2
- package/dist/extensions/feishu/src/schema/config-schema.js.map +1 -1
- package/dist/extensions/feishu/src/state/accounts.js +1 -1
- package/dist/extensions/feishu/src/state/accounts.js.map +1 -1
- package/dist/extensions/feishu/src/streaming/streaming-adapter.js +82 -36
- package/dist/extensions/feishu/src/streaming/streaming-adapter.js.map +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/auth/accounts.js +1 -1
- package/dist/extensions/weixin/src/auth/accounts.js.map +1 -1
- package/dist/extensions/weixin/src/config-schema.js +2 -2
- package/dist/extensions/weixin/src/config-schema.js.map +1 -1
- package/dist/extensions/weixin/src/config-surface.js +1 -1
- package/dist/extensions/weixin/src/config-surface.js.map +1 -1
- package/dist/extensions/weixin/src/messaging/process-message.js +2 -2
- package/dist/extensions/weixin/src/messaging/process-message.js.map +1 -1
- package/dist/extensions/weixin/src/plugin.js +1 -1
- package/dist/extensions/weixin/src/plugin.js.map +1 -1
- package/dist/gateway/static/root/assets/{agents-BdISC5UA.js → agents-CQllyJhj.js} +2 -2
- package/dist/gateway/static/root/assets/{agents-BdISC5UA.js.map → agents-CQllyJhj.js.map} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-CXU_Jg95.js → apps-page-BWI3RVMh.js} +2 -2
- package/dist/gateway/static/root/assets/{apps-page-CXU_Jg95.js.map → apps-page-BWI3RVMh.js.map} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-rfmhXfC4.js +2 -0
- package/dist/gateway/static/root/assets/channels-settings-rfmhXfC4.js.map +1 -0
- package/dist/gateway/static/root/assets/{cron-dreaming-jobs-C-V4_3vz.js → cron-dreaming-jobs-BzCQy56Z.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-dreaming-jobs-C-V4_3vz.js.map → cron-dreaming-jobs-BzCQy56Z.js.map} +1 -1
- package/dist/gateway/static/root/assets/{cron-page-CIIy81K6.js → cron-page-CjTuH_av.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-page-CIIy81K6.js.map → cron-page-CjTuH_av.js.map} +1 -1
- package/dist/gateway/static/root/assets/{dist-VW7dXc5X.js → dist-DIeOihYP.js} +2 -2
- package/dist/gateway/static/root/assets/{dist-VW7dXc5X.js.map → dist-DIeOihYP.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-Cslwx62-.js → extension-debug-page-CR-4lZOn.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-debug-page-Cslwx62-.js.map → extension-debug-page-CR-4lZOn.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-Dzyebr6T.js → extension-page-BNwcYj2Q.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-page-Dzyebr6T.js.map → extension-page-BNwcYj2Q.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-B07uetL_.js → extension-settings-page-CDpiZPV4.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-settings-page-B07uetL_.js.map → extension-settings-page-CDpiZPV4.js.map} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-config-api-kmvGNriW.js → heartbeat-config-api-CSbqK-L_.js} +2 -2
- package/dist/gateway/static/root/assets/{heartbeat-config-api-kmvGNriW.js.map → heartbeat-config-api-CSbqK-L_.js.map} +1 -1
- package/dist/gateway/static/root/assets/{index-DW6JvymK.js → index-D3sMd_aw.js} +4 -4
- package/dist/gateway/static/root/assets/{index-DW6JvymK.js.map → index-D3sMd_aw.js.map} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-BiRnV2Ex.js → logs-page-DLcWAXU5.js} +2 -2
- package/dist/gateway/static/root/assets/{logs-page-BiRnV2Ex.js.map → logs-page-DLcWAXU5.js.map} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-DQq0QaQh.js → sessions-page-Ck3lS3N_.js} +2 -2
- package/dist/gateway/static/root/assets/{sessions-page-DQq0QaQh.js.map → sessions-page-Ck3lS3N_.js.map} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-BV_l8nJ3.js → settings-page-Dp_eFgub.js} +2 -2
- package/dist/gateway/static/root/assets/{settings-page-BV_l8nJ3.js.map → settings-page-Dp_eFgub.js.map} +1 -1
- package/dist/gateway/static/root/assets/{skills-page-Bv0pphDM.js → skills-page-ClRj9e6K.js} +2 -2
- package/dist/gateway/static/root/assets/{skills-page-Bv0pphDM.js.map → skills-page-ClRj9e6K.js.map} +1 -1
- package/dist/gateway/static/root/assets/{use-image-provider-credentials-BPcW1K0N.js → use-image-provider-credentials-DAi5Iu8c.js} +2 -2
- package/dist/gateway/static/root/assets/{use-image-provider-credentials-BPcW1K0N.js.map → use-image-provider-credentials-DAi5Iu8c.js.map} +1 -1
- package/dist/gateway/static/root/index.html +1 -1
- package/dist/package.js +1 -1
- package/dist/src/agent/service.js +2 -0
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/gateway/hono/routes/channels.js +2 -2
- package/dist/src/gateway/hono/routes/channels.js.map +1 -1
- package/dist/src/gateway/hono/routes/config.js +3 -3
- package/dist/src/gateway/hono/routes/config.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/channels-settings-Doe1ciOW.js +0 -2
- package/dist/gateway/static/root/assets/channels-settings-Doe1ciOW.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","names":["parseRoutingSessionKey"],"sources":["../../../src/agent/service.ts"],"sourcesContent":["import type { AgentEvent, AgentMessage, ThinkingLevel } from '@earendil-works/pi-agent-core';\nimport { MessageBusShutdownError, type MessageBus, type InboundMessage } from '../infra/bus/index.js';\nimport { type Config, getAgentDefaultModelRef } from '../config/schema.js';\nimport { maybeAutoTitleSessionStore } from '../session/session-title.js';\nimport type { ChannelManager } from '../channels/manager.js';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport {\n SessionStore,\n SessionConfigStore,\n resolveEffectiveThinkingLevel,\n resolveEffectiveReasoningLevel,\n effectiveWorkspacePathForSession,\n normalizeWorkingDirectoryInput,\n type CompactionConfig,\n type WindowConfig,\n} from '../session/index.js';\nimport type { SessionDetail } from '../session/types.js';\nimport {\n normalizeThinkLevel,\n normalizeReasoningLevel,\n type ThinkLevel,\n type ReasoningLevel,\n} from './transcript/thinking-types.js';\nimport { createLogger, runWithLogContext, updateAsyncLogContext } from '../utils/logger.js';\nimport { ExtensionHookRunner } from '../extensions/index.js';\nimport { loadBootstrapFiles, extractTextContent } from './context/workspace.js';\nimport { SessionTracker } from './session/tracker.js';\nimport { ModelManager } from './models/index.js';\nimport { initializeCommands } from '../chat-commands/index.js';\nimport { ProgressFeedbackManager } from './lifecycle/progress.js';\nimport { HookHandler } from './lifecycle/hook-handler.js';\nimport { ToolErrorTracker } from './tools/error-tracker.js';\nimport { RequestLimiter } from './models/request-limiter.js';\nimport { SystemReminder } from './prompt/system-reminder.js';\nimport { ToolUsageAnalyzer } from './tools/usage-analyzer.js';\nimport { ToolChainTracker } from './tools/chain-tracker.js';\nimport { ErrorPatternMatcher } from './tools/error-pattern-matcher.js';\nimport { ContextMiddleware, SelfVerifyMiddleware } from './middleware/index.js';\nimport { LifecycleManager } from './lifecycle/index.js';\nimport { CompactionLifecycleHandler } from './lifecycle/handlers/compaction.js';\n\nimport { MessageRouter, CommandHandler, StreamManager } from './messaging/index.js';\nimport { SessionContextManager, SessionLifecycleManager, type SessionContext } from './session/index.js';\nimport { AgentOrchestrator, AgentEventHandler } from './orchestration/index.js';\nimport { FeedbackCoordinator } from './feedback/index.js';\nimport { AgentManager, type SkillCatalogEntry } from './agent-manager.js';\nimport type { SkillMarkdownPreviewPayload } from './skills/types.js';\nimport { inboundMessageLogRequestId } from './service-inbound-utils.js';\nimport type { AgentServiceConfig, StreamHandle } from './service.types.js';\nimport {\n runProcessDirectStreaming,\n type ProcessDirectStreamingDeps,\n} from './service/process-direct-streaming.js';\nimport { parseSessionKey as parseRoutingSessionKey } from '../routing/session-key.js';\nimport { handlePersistentGoalPostTurn } from './goals/post-turn.js';\nimport type { PersistentGoalApis } from './goals/persistent-goal-apis.js';\nimport { reconcileManagedDreamingCronJobs } from './service/reconcile-dreaming-cron.js';\nimport { parseOutboundSessionKey } from './service/parse-outbound-session-key.js';\nimport { runBtwQuery } from './service/btw-query.js';\nimport { formatSessionContextReport } from './service/session-context-report.js';\nimport { buildDirectUserMessageContent } from './service/build-direct-message-content.js';\nimport { maybeEmitWebchatTts } from './service/webchat-tts.js';\nimport { runProcessDirect, type RunProcessDirectDeps } from './service/process-direct-one-shot.js';\n\nimport {\n resolveAgentHomeDir,\n resolveDefaultAgentId,\n} from './agent-scope.js';\nimport { extractProfileAgentId, resolveAgentBootstrapDir } from '../config/agent-profile.js';\nimport { DEFAULT_ACK_MAX_CHARS, NO_REPLY, shouldSilence } from '../heartbeat/tokens.js';\nimport { createTypingController, type TypingController } from './lifecycle/typing.js';\nimport { cleanTrailingErrors, sanitizeMessages } from './memory/message-sanitizer.js';\nimport {\n tryApplySessionTranscriptHygiene,\n tryApplySessionTranscriptHygieneForPersistence,\n} from './transcript/transcript-hygiene.js';\nimport {\n persistInboundAttachmentsToWorkspace,\n type InternalAttachmentRoots,\n} from '../channels/attachments/inbound-persist.js';\nimport { resolveEffectiveAgentProfileForSession } from '../config/agent-profile.js';\nimport { applyConfigOverrides } from '../config/runtime-overrides.js';\nimport type { CompactionResult } from './memory/compaction.js';\n\nexport type { AgentServiceConfig, AgentContext, StreamHandle } from './service.types.js';\n\nconst log = createLogger('AgentService');\n\nexport class AgentService {\n private sessionStore: SessionStore;\n private sessionConfigStore: SessionConfigStore;\n private hookRunner?: ExtensionHookRunner;\n private running = false;\n private agentId: string;\n private workspaceDir: string;\n private bootstrapFiles: ReturnType<typeof loadBootstrapFiles> = [];\n private channelManagerRef: ChannelManager | null = null;\n private bus: MessageBus;\n private config: AgentServiceConfig;\n\n private sessionTracker: SessionTracker;\n private modelManager: ModelManager;\n private progressManager: ProgressFeedbackManager;\n private hookHandler: HookHandler;\n private lifecycleManager: LifecycleManager;\n private errorTracker: ToolErrorTracker;\n private requestLimiter: RequestLimiter;\n private systemReminder: SystemReminder;\n private toolUsageAnalyzer: ToolUsageAnalyzer;\n private toolChainTracker: ToolChainTracker;\n private errorPatternMatcher: ErrorPatternMatcher;\n private selfVerifyMiddleware: SelfVerifyMiddleware;\n private contextMiddleware: ContextMiddleware;\n\n private messageRouter: MessageRouter;\n private commandHandler: CommandHandler;\n private streamManager: StreamManager;\n private sessionContextManager: SessionContextManager;\n private sessionLifecycleManager: SessionLifecycleManager;\n private agentOrchestrator: AgentOrchestrator;\n private agentEventHandler: AgentEventHandler;\n private feedbackCoordinator: FeedbackCoordinator;\n private agentManager: AgentManager;\n\n /** Webchat SSE queue pushers for `clarify_request` and similar mid-turn UI events. */\n private webchatSseEnqueueBySession = new Map<\n string,\n (event: { type: string; [key: string]: unknown }) => void\n >();\n\n /** Gateway: drain `processDirectStreaming` for webchat continuations (Hermes FIFO-style). */\n private persistentGoalWebchatContinuationScheduler?: (sessionKey: string, message: string) => void;\n private directStreamOutcomeBySession = new Map<string, { skipPersistentGoalPostTurn: boolean }>();\n /** Concurrent inbound / direct-stream turns per session (Hermes-style /goal mid-flight guard). */\n private inboundTurnDepthBySession = new Map<string, number>();\n\n /** Gateway: notify UI after direct `SessionStore.updateMetadata` (no SessionManager emit). */\n private onSessionMetadataUpdated?: (sessionKey: string) => void;\n\n // Track event unsubscribers per session\n private sessionUnsubscribers: Map<string, () => void> = new Map();\n\n private effectiveAppConfig(): Config | undefined {\n const base = this.config.config;\n return base ? applyConfigOverrides(base) : undefined;\n }\n\n constructor(bus: MessageBus, config: AgentServiceConfig) {\n this.bus = bus;\n this.config = config;\n this.onSessionMetadataUpdated = config.onSessionMetadataUpdated;\n this.agentId = `agent-${Date.now()}`;\n this.workspaceDir = config.workspace;\n\n if (config.config) {\n const aid = resolveDefaultAgentId(config.config);\n this.bootstrapFiles = loadBootstrapFiles(resolveAgentBootstrapDir(config.config, aid));\n } else {\n this.bootstrapFiles = [];\n }\n\n this.sessionTracker = new SessionTracker();\n this.modelManager = new ModelManager({\n defaultModel: config.model,\n config: config.config,\n });\n\n initializeCommands();\n log.debug('Command system initialized');\n\n this.sessionStore = config.sessionStore ?? this.createSessionStore();\n const appCfgForPaths = this.config.config;\n if (!appCfgForPaths) {\n throw new Error('AgentService requires config.config for session paths');\n }\n const defaultAid = resolveDefaultAgentId(appCfgForPaths);\n const defaultAgentHome = resolveAgentHomeDir(appCfgForPaths, defaultAid);\n this.sessionConfigStore = new SessionConfigStore(defaultAgentHome);\n\n this.hookRunner = this.createHookRunner();\n this.hookHandler = new HookHandler({\n hookRunner: this.hookRunner,\n agentId: this.agentId,\n get sessionKey() { return this.currentContext?.sessionKey; },\n });\n\n this.progressManager = this.createProgressManager();\n this.initializeReliabilityModules();\n\n this.lifecycleManager = new LifecycleManager();\n this.initializeLifecycleHandlers();\n\n this.streamManager = new StreamManager();\n this.sessionContextManager = new SessionContextManager();\n this.feedbackCoordinator = new FeedbackCoordinator({\n progressManager: this.progressManager,\n bus,\n });\n\n // Initialize AgentManager\n this.agentManager = new AgentManager({\n workspace: config.workspace,\n model: config.model,\n config: config.config,\n extensionRegistry: config.extensionRegistry,\n hookRunner: this.hookRunner,\n bus,\n getCurrentContext: () => this.sessionContextManager.getContext(),\n getSessionStore: () => this.sessionStore,\n getModelManager: () => this.modelManager,\n thinkingLevel: config.thinkingLevel,\n reasoningLevel: config.reasoningLevel,\n verboseLevel: config.verboseLevel,\n gatewayClarify: config.gatewayClarify,\n getCronService: config.getCronService,\n });\n\n this.agentEventHandler = new AgentEventHandler({\n progressManager: this.progressManager,\n errorTracker: this.errorTracker,\n requestLimiter: this.requestLimiter,\n lifecycleManager: this.lifecycleManager,\n toolChainTracker: this.toolChainTracker,\n selfVerifyMiddleware: this.selfVerifyMiddleware,\n systemReminder: this.systemReminder,\n toolUsageAnalyzer: this.toolUsageAnalyzer,\n errorPatternMatcher: this.errorPatternMatcher,\n modelManager: this.modelManager,\n });\n\n this.agentOrchestrator = new AgentOrchestrator({\n agentManager: this.agentManager,\n sessionStore: this.sessionStore,\n modelManager: this.modelManager,\n eventHandler: this.agentEventHandler,\n feedbackCoordinator: this.feedbackCoordinator,\n sessionConfigStore: this.sessionConfigStore,\n hydrateSessionWorkspaceFromStore: (sessionKey) => this.hydrateSessionWorkspaceFromStore(sessionKey),\n getConfig: () => this.effectiveAppConfig(),\n getThinkingDefault: () => this.effectiveAppConfig()?.agents?.defaults?.thinkingDefault,\n getThinkingDefaultForSession: (sessionKey: string) =>\n this.agentManager.getThinkingDefaultForSession(sessionKey),\n workspaceRoot: this.workspaceDir,\n getWorkspaceRootForSession: (sessionKey: string) =>\n this.agentManager.getResolvedWorkspaceForSession(sessionKey),\n getAgentInternalStorageRootForSession: (sessionKey: string) =>\n resolveAgentHomeDir(this.config.config!, extractProfileAgentId(sessionKey, this.config.config!)),\n enqueueAutoTitle: (sessionKey: string) => this.enqueueMaybeAutoTitleAfterPersist(sessionKey),\n });\n\n this.messageRouter = new MessageRouter();\n this.commandHandler = new CommandHandler({\n config: config.config!,\n bus,\n sessionStore: this.sessionStore,\n sessionConfigStore: this.sessionConfigStore,\n getPersistentGoalApisForCommand: (routing) => this.getPersistentGoalApisForCommand(routing),\n applySessionThinkingLevel: (sessionKey: string, level: ThinkLevel) => {\n this.agentManager.setThinkingLevel(sessionKey, level as ThinkingLevel);\n },\n getCurrentModel: () => this.agentOrchestrator.getCurrentModel(),\n switchModelForSession: (sessionKey: string, modelId: string) =>\n this.switchModelForSession(sessionKey, modelId),\n invalidateAgentSession: (sessionKey: string) => {\n this.agentManager.removeAgent(sessionKey);\n },\n abortSessionTurn: async (sessionKey: string) => {\n await this.streamManager.abort();\n this.agentOrchestrator.abort(sessionKey);\n },\n compactSession: (sessionKey, options) => this.compactSession(sessionKey, options),\n btwQuery: (sessionKey, question) => this.btwQuery(sessionKey, question),\n getSessionContextReport: (sessionKey, mode) => this.getSessionContextReport(sessionKey, mode),\n });\n\n this.sessionLifecycleManager = new SessionLifecycleManager(\n this.sessionStore,\n this.sessionTracker,\n this.lifecycleManager\n );\n\n // Register signal handlers only if not running as an Electron subprocess.\n // In Electron, the parent process manages the lifecycle and signals should not trigger disposal.\n const isElectronSubprocess = !!process.env.ELECTRON_RUN_AS_NODE;\n if (!isElectronSubprocess) {\n process.on('SIGINT', () => this.dispose());\n process.on('SIGTERM', () => this.dispose());\n }\n\n log.info('AgentService initialized');\n }\n\n private attachmentRootsForSession(sessionKey: string): InternalAttachmentRoots {\n const cfg = this.config.config!;\n return {\n agentHome: resolveAgentHomeDir(cfg, extractProfileAgentId(sessionKey, cfg)),\n };\n }\n\n private createSessionStore(): SessionStore {\n const sessionStoreDefaults = this.config.agentDefaults || this.config.config?.agents?.defaults;\n const windowConfig: Partial<WindowConfig> = {\n maxMessages: 100,\n keepRecentMessages: sessionStoreDefaults?.maxToolIterations || 20,\n preserveSystemMessages: true,\n };\n const compactionConfig: Partial<CompactionConfig> = {\n enabled: sessionStoreDefaults?.compaction?.enabled ?? true,\n mode: (sessionStoreDefaults?.compaction?.mode as 'extractive' | 'abstractive' | 'structured') || 'abstractive',\n reserveTokens: sessionStoreDefaults?.compaction?.reserveTokens || 8000,\n triggerThreshold: sessionStoreDefaults?.compaction?.triggerThreshold || 0.8,\n minMessagesBeforeCompact: sessionStoreDefaults?.compaction?.minMessagesBeforeCompact || 10,\n keepRecentMessages: sessionStoreDefaults?.compaction?.keepRecentMessages || 10,\n evictionWindow: sessionStoreDefaults?.compaction?.evictionWindow || 0.2,\n retentionWindow: sessionStoreDefaults?.compaction?.retentionWindow || 6,\n };\n const appCfg = this.config.config;\n if (!appCfg) {\n throw new Error('AgentService requires config.config for session store paths');\n }\n return new SessionStore(\n {\n config: appCfg,\n agentId: resolveDefaultAgentId(appCfg),\n },\n windowConfig,\n compactionConfig,\n );\n }\n\n private createHookRunner(): ExtensionHookRunner | undefined {\n if (!this.config.extensionRegistry) return undefined;\n\n return new ExtensionHookRunner(this.config.extensionRegistry, {\n catchErrors: true,\n logger: {\n info: (msg: string) => log.info({ hook: true }, msg),\n warn: (msg: string) => log.warn({ hook: true }, msg),\n error: (msg: string) => log.error({ hook: true }, msg),\n },\n });\n }\n\n private createProgressManager(): ProgressFeedbackManager {\n return new ProgressFeedbackManager({\n level: 'normal',\n showThinking: true,\n streamToolProgress: true,\n heartbeatEnabled: true,\n heartbeatIntervalMs: 20000,\n longTaskThresholdMs: 30000,\n });\n }\n\n private initializeReliabilityModules(): void {\n const defaults = this.config.agentDefaults || this.config.config?.agents?.defaults;\n\n this.errorTracker = new ToolErrorTracker({\n maxFailuresPerTool: defaults?.maxToolFailuresPerTurn || 3,\n maxTotalFailures: defaults?.maxToolFailuresPerTurn ? defaults.maxToolFailuresPerTurn + 2 : 5,\n resetOnTurnEnd: true,\n });\n\n this.selfVerifyMiddleware = new SelfVerifyMiddleware({\n maxEditsPerFile: 5,\n enablePreCompletionCheck: true,\n minTurnsForVerification: 4,\n resetOnVerification: true,\n });\n\n this.requestLimiter = new RequestLimiter({\n maxRequestsPerTurn: defaults?.maxRequestsPerTurn || 50,\n warnThreshold: 0.8,\n softLimit: false,\n });\n\n this.systemReminder = new SystemReminder({\n enabled: true,\n appendToToolResults: true,\n maxRemindersPerTurn: 3,\n });\n\n this.toolUsageAnalyzer = new ToolUsageAnalyzer({\n enabled: true,\n lowUsageThreshold: 5,\n veryLowUsageThreshold: 1,\n minCallsForAnalysis: 100,\n reportIntervalMs: 60 * 60 * 1000,\n });\n\n this.toolChainTracker = new ToolChainTracker({\n enabled: true,\n maxChainsPerSession: 10,\n maxNodesPerChain: 100,\n trackParams: true,\n trackResults: true,\n autoPrune: true,\n });\n\n this.errorPatternMatcher = new ErrorPatternMatcher({\n enabled: true,\n defaultMaxRetries: 1,\n logMatches: true,\n });\n\n // Initialize context middleware for automatic request tracking\n this.contextMiddleware = new ContextMiddleware();\n }\n\n private initializeLifecycleHandlers(): void {\n this.lifecycleManager.on('llm_response', new CompactionLifecycleHandler({\n minMessages: 20,\n maxTokens: 8000,\n preserveReasoning: true,\n accumulateUsage: true,\n }));\n\n log.debug(\n { handlers: this.lifecycleManager.getRegisteredHandlers() },\n 'Lifecycle handlers initialized'\n );\n }\n\n setChannelManager(channelManager: ChannelManager): void {\n this.modelManager.setChannelManager(channelManager);\n this.channelManagerRef = channelManager;\n }\n\n /**\n * Apply config after save or hot reload so the default model updates without restarting the gateway.\n */\n applyAgentDefaultsFromConfig(config: Config): void {\n this.config.config = config;\n const ref = getAgentDefaultModelRef(config);\n this.config.model = ref;\n this.modelManager.updateFromConfig(config);\n this.agentManager.updateAgentDefaults(config);\n this.commandHandler.updateAgentConfig(config);\n }\n\n getSkillCatalog(lang?: string): SkillCatalogEntry[] {\n return this.agentManager.getSkillCatalog(lang);\n }\n\n getSkillMarkdownSource(skillName: string, lang?: string): SkillMarkdownPreviewPayload | null {\n return this.agentManager.getSkillMarkdownSource(skillName, lang);\n }\n\n refreshSkillsAfterDiskChange(): void {\n this.agentManager.refreshSkillsAfterDiskChange();\n }\n\n refreshSkillsAfterSkillConfigChange(): void {\n this.agentManager.refreshSkillsAfterSkillConfigChange();\n }\n\n getModelForSession(sessionKey: string): string {\n return this.modelManager.getModelForSession(sessionKey);\n }\n\n async switchModelForSession(sessionKey: string, modelId: string): Promise<boolean> {\n const ok = await this.modelManager.switchModelForSession(sessionKey, modelId);\n if (!ok) return false;\n await this.sessionConfigStore.update(sessionKey, { modelOverride: modelId });\n const result = this.agentManager.setModelForSession(sessionKey, modelId);\n if (result) {\n this.sessionTracker.touchSession(sessionKey);\n }\n return true;\n }\n\n private async clearSessionModelOverride(sessionKey: string): Promise<void> {\n this.modelManager.clearSessionModelOverride(sessionKey);\n await this.sessionConfigStore.update(sessionKey, { modelOverride: undefined });\n const agent = this.agentManager.getAgent(sessionKey);\n if (agent) {\n await this.modelManager.applyModelForSession(agent, sessionKey);\n }\n }\n\n /**\n * Clears per-session model override so the next turn uses the configured agent default\n * (e.g. cron isolated job with no explicit model).\n */\n async resetSessionModelToAgentDefault(sessionKey: string): Promise<void> {\n await this.clearSessionModelOverride(sessionKey);\n }\n\n private async hydrateSessionModelFromStore(sessionKey: string): Promise<void> {\n const cfg = await this.sessionConfigStore.get(sessionKey);\n if (cfg?.modelOverride) {\n await this.modelManager.switchModelForSession(sessionKey, cfg.modelOverride);\n }\n }\n\n setStreamHandle(handle: StreamHandle): void {\n this.streamManager.setHandle(handle);\n this.feedbackCoordinator.setStreamHandle(handle);\n }\n\n clearStreamHandle(): void {\n this.streamManager.clearHandle();\n this.feedbackCoordinator.endTask();\n }\n\n /** Last assistant visible plain text for a session (e.g. after a webchat stream). */\n getLastAssistantPlainText(sessionKey: string): string {\n return this.agentManager.getLastAssistantContent(sessionKey) ?? '';\n }\n\n /** Gateway only: webchat continuations bypass the bus and reuse `runGatewayAgent`. */\n setPersistentGoalWebchatContinuationScheduler(\n fn: ((sessionKey: string, message: string) => void) | undefined,\n ): void {\n this.persistentGoalWebchatContinuationScheduler = fn;\n }\n\n beginInboundTurn(sessionKey: string): void {\n this.inboundTurnDepthBySession.set(\n sessionKey,\n (this.inboundTurnDepthBySession.get(sessionKey) ?? 0) + 1,\n );\n }\n\n endInboundTurn(sessionKey: string): void {\n const n = (this.inboundTurnDepthBySession.get(sessionKey) ?? 1) - 1;\n if (n <= 0) {\n this.inboundTurnDepthBySession.delete(sessionKey);\n } else {\n this.inboundTurnDepthBySession.set(sessionKey, n);\n }\n }\n\n getInboundTurnDepth(sessionKey: string): number {\n return this.inboundTurnDepthBySession.get(sessionKey) ?? 0;\n }\n\n schedulePersistentGoalContinuation(\n sessionKey: string,\n message: string,\n routing: { channel: string; chatId: string; inboundMetadata?: Record<string, unknown> },\n ): void {\n const parsed = parseRoutingSessionKey(sessionKey);\n if (parsed?.source === 'webchat' && this.persistentGoalWebchatContinuationScheduler) {\n this.persistentGoalWebchatContinuationScheduler(sessionKey, message);\n return;\n }\n queueMicrotask(() => {\n void this.bus\n .publishInbound({\n channel: routing.channel,\n chat_id: routing.chatId,\n sender_id: 'persistent-goal',\n content: message,\n metadata: { sessionKey, ...routing.inboundMetadata },\n })\n .catch((err) => {\n log.warn({ err, sessionKey }, 'Persistent goal: publishInbound failed');\n });\n });\n }\n\n getPersistentGoalApisForCommand(routing: {\n sessionKey: string;\n channel: string;\n chatId: string;\n inboundMetadata?: Record<string, unknown>;\n }): PersistentGoalApis {\n return {\n getSessionMetadata: (k) => this.sessionStore.getMetadata(k),\n updateSessionMetadata: async (k, u) => {\n await this.sessionStore.updateMetadata(k, u);\n this.onSessionMetadataUpdated?.(k);\n },\n loadMessages: (k) => this.sessionStore.loadMessages(k),\n saveMessages: (k, m) => this.sessionStore.saveMessages(k, m),\n scheduleContinuation: (sk, msg) => {\n this.schedulePersistentGoalContinuation(sk, msg, {\n channel: routing.channel,\n chatId: routing.chatId,\n inboundMetadata: routing.inboundMetadata,\n });\n },\n inboundConcurrentDepth: (sk) => this.getInboundTurnDepth(sk),\n };\n }\n\n recordPersistentGoalStreamOutcome(\n sessionKey: string,\n outcome: { skipPersistentGoalPostTurn: boolean },\n ): void {\n this.directStreamOutcomeBySession.set(sessionKey, outcome);\n }\n\n takePersistentGoalStreamOutcome(sessionKey: string): { skipPersistentGoalPostTurn: boolean } | undefined {\n const v = this.directStreamOutcomeBySession.get(sessionKey);\n this.directStreamOutcomeBySession.delete(sessionKey);\n return v;\n }\n\n /**\n * After any assistant-visible turn (webchat direct stream or bus-driven channels): extension hook + built-in `/goal` post-turn.\n */\n async emitSessionTurnComplete(payload: {\n sessionKey: string;\n channel: string;\n chatId: string;\n inboundUserText: string;\n assistantPlainText: string;\n aborted: boolean;\n streamError?: string;\n skipPersistentGoalPostTurn?: boolean;\n outboundMetadata?: Record<string, unknown>;\n }): Promise<void> {\n await this.hookHandler.triggerWithSessionKey(payload.sessionKey, 'webchat_turn_complete', {\n sessionKey: payload.sessionKey,\n channel: payload.channel,\n chatId: payload.chatId,\n inboundUserText: payload.inboundUserText,\n assistantPlainText: payload.assistantPlainText,\n aborted: payload.aborted,\n ...(payload.streamError !== undefined ? { streamError: payload.streamError } : {}),\n });\n\n const apis = this.getPersistentGoalApisForCommand({\n sessionKey: payload.sessionKey,\n channel: payload.channel,\n chatId: payload.chatId,\n inboundMetadata: payload.outboundMetadata,\n });\n\n const src = parseRoutingSessionKey(payload.sessionKey)?.source;\n const isWebchat = src === 'webchat';\n const publishVerdict =\n !isWebchat && payload.channel !== 'cli'\n ? async (text: string) => {\n await this.bus.publishOutbound({\n channel: payload.channel,\n chat_id: payload.chatId,\n content: text,\n type: 'message',\n metadata: {\n accountId: payload.outboundMetadata?.accountId,\n threadId: payload.outboundMetadata?.threadId,\n },\n });\n }\n : undefined;\n\n await handlePersistentGoalPostTurn({\n apis,\n sessionKey: payload.sessionKey,\n assistantPlainText: payload.assistantPlainText,\n aborted: payload.aborted,\n ...(payload.streamError !== undefined ? { streamError: payload.streamError } : {}),\n skipPersistentGoalPostTurn: payload.skipPersistentGoalPostTurn ?? false,\n config: this.effectiveAppConfig(),\n publishVerdictToChannel: publishVerdict,\n });\n }\n\n async start(): Promise<void> {\n this.running = true;\n await this.sessionConfigStore.initialize();\n await this.hookHandler.trigger('gateway_start', { port: 0, host: 'cli' });\n await this.reconcileDreamingCronJob().catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, errorMessage: em }, `Dreaming cron reconcile failed: ${em}`);\n });\n log.debug('Agent service started');\n await this.hookHandler.trigger('session_start', { sessionId: this.agentId });\n\n while (this.running) {\n try {\n const msg = await this.bus.consumeInbound();\n await this.handleInboundMessage(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: 'inbound_consume' },\n `Agent loop failed (will retry in 1s): ${em}`,\n );\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n\n await this.hookHandler.trigger('session_end', {\n sessionId: this.agentId,\n messageCount: 0, // No longer tracking single agent messages\n });\n }\n\n stop(): Promise<void> {\n this.running = false;\n this.agentManager.dispose();\n this.dispose();\n\n this.hookHandler.trigger('gateway_stop', { reason: 'stopped' });\n log.debug('Agent service stopped');\n return Promise.resolve();\n }\n\n /**\n * Reconcile managed Dreaming cron job against the current effective config.\n * Safe to call after config saves to apply changes without restarting the process.\n */\n async reconcileDreamingNow(): Promise<void> {\n await this.reconcileDreamingCronJob();\n }\n\n private async reconcileDreamingCronJob(): Promise<void> {\n const cron = this.config.getCronService?.();\n if (!cron) {\n return;\n }\n await reconcileManagedDreamingCronJobs(cron, this.effectiveAppConfig());\n }\n\n /**\n * Persist agent messages with the same sanitizer + transcript hygiene as AgentOrchestrator.\n * Uses persistence hygiene so `thinking` blocks remain on disk for the web UI (LLM load path still drops them).\n */\n private async persistAgentSessionMessages(sessionKey: string): Promise<void> {\n const raw = this.agentManager.getMessages(sessionKey);\n if (!raw) {\n return;\n }\n const { messages } = sanitizeMessages(raw);\n let toSave = messages;\n try {\n const model = this.modelManager.getResolvedModelForSession(sessionKey);\n toSave = tryApplySessionTranscriptHygieneForPersistence(messages, model);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Transcript hygiene on save skipped');\n }\n await this.sessionStore.save(sessionKey, toSave);\n this.enqueueMaybeAutoTitleAfterPersist(sessionKey);\n }\n\n /**\n * Fire-and-forget: `maybeAutoTitleSessionStore` no-ops for cron/heartbeat keys.\n * Runs after persist so the store has the latest transcript; does not block SSE / callers.\n */\n private enqueueMaybeAutoTitleAfterPersist(sessionKey: string): void {\n void (async () => {\n try {\n let modelRef =\n getAgentDefaultModelRef(this.config.config ?? ({} as Config)) ?? this.config.model;\n if (!modelRef?.trim()) {\n try {\n modelRef = this.modelManager.getModelForSession(sessionKey);\n } catch {\n modelRef = undefined;\n }\n }\n await maybeAutoTitleSessionStore(this.sessionStore, sessionKey, modelRef?.trim() || undefined);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Auto session title failed');\n }\n })();\n }\n\n private prepareLoadedSessionMessages(sessionKey: string, messages: AgentMessage[]): AgentMessage[] {\n let out = cleanTrailingErrors(messages);\n try {\n const model = this.modelManager.getResolvedModelForSession(sessionKey);\n out = tryApplySessionTranscriptHygiene(out, model);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Transcript hygiene on load skipped');\n }\n return out;\n }\n\n private parseSessionKey(sessionKey: string): { channel: string; chatId: string } {\n return parseOutboundSessionKey(sessionKey, this.config.config);\n }\n\n private initSessionContext(\n sessionKey: string,\n channel: string,\n chatId: string,\n senderId = '',\n ): SessionContext {\n const context: SessionContext = {\n sessionKey,\n channel,\n chatId,\n senderId,\n isGroup: false,\n };\n\n this.contextMiddleware.onRequest({\n sessionKey,\n userId: context.senderId,\n channel,\n chatId,\n });\n\n this.sessionContextManager.setContext(context);\n this.feedbackCoordinator.setContext(context);\n this.setupSessionEventHandling(sessionKey);\n\n return context;\n }\n\n /**\n * Persist inbound file attachments under agent home `inbound/` (non-images with data).\n * Idempotent if `workspaceRelativePath` is already set on an attachment.\n */\n async prepareInboundAttachments(\n sessionKey: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n }>,\n ): Promise<\n | Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n }>\n | undefined\n > {\n const cfg = this.config.config!;\n const storageRoot = resolveAgentHomeDir(cfg, extractProfileAgentId(sessionKey, cfg));\n return persistInboundAttachmentsToWorkspace(storageRoot, sessionKey, attachments);\n }\n\n private endDirectRequestContext(): void {\n this.sessionContextManager.clearContext();\n this.feedbackCoordinator.clearContext();\n this.contextMiddleware.onResponse();\n }\n\n /** Full session snapshot (metadata + API-shaped messages), e.g. embedded TUI history. */\n async loadSessionDetail(sessionKey: string): Promise<SessionDetail | null> {\n return this.sessionStore.get(sessionKey);\n }\n\n async compactSession(\n sessionKey: string,\n options?: { instructions?: string; force?: boolean },\n ): Promise<CompactionResult> {\n const messages = await this.sessionStore.load(sessionKey);\n const contextWindow = this.getContextWindow();\n const result = await this.sessionStore.compact(\n sessionKey,\n messages,\n contextWindow,\n options?.instructions,\n options?.force ?? true,\n );\n if (result.compacted) {\n await this.sessionStore.save(sessionKey, await this.sessionStore.load(sessionKey));\n this.agentManager.removeAgent(sessionKey);\n }\n log.info({ sessionKey, result }, 'Manual compaction complete');\n return result;\n }\n\n /**\n * Drop in-memory agent so the next turn reloads transcript from disk (e.g. after checkpoint restore).\n */\n evictSessionAgent(sessionKey: string): void {\n this.agentManager.removeAgent(sessionKey);\n }\n\n /**\n * One-shot LLM answer for /btw: uses transcript as background only; does not persist to session.\n */\n async btwQuery(sessionKey: string, question: string): Promise<{ text: string; error?: string }> {\n return runBtwQuery({\n sessionKey,\n question,\n sessionStore: this.sessionStore,\n modelForSession: this.modelManager.getModelForSession(sessionKey),\n log,\n });\n }\n\n /** Markdown or JSON summary for /context (prompt assembly is approximated from config + transcript stats). */\n async getSessionContextReport(\n sessionKey: string,\n mode: 'list' | 'detail' | 'json',\n ): Promise<string> {\n const messages = await this.sessionStore.load(sessionKey);\n const cw = this.getContextWindow();\n const stats = this.getSessionStats(sessionKey, messages);\n const cfg = this.effectiveAppConfig() ?? this.config.config!;\n const model = this.modelManager.getModelForSession(sessionKey);\n const sc = await this.sessionConfigStore.get(sessionKey);\n const workspace = effectiveWorkspacePathForSession(cfg, sessionKey, sc);\n const estTokens = await this.sessionStore.estimateTokenUsage(sessionKey, messages);\n const profile = resolveEffectiveAgentProfileForSession(cfg, sessionKey);\n const defaults = cfg.agents?.defaults;\n const compaction = defaults?.compaction;\n const tools = defaults?.tools;\n\n const toolsSummary =\n tools && typeof tools === 'object'\n ? Object.entries(tools as Record<string, unknown>)\n .filter(([, v]) => v === true)\n .map(([k]) => k)\n .slice(0, 16)\n .join(', ') || '(none explicitly true)'\n : '(see agents.defaults.tools in config)';\n\n return formatSessionContextReport({\n sessionKey,\n mode,\n model,\n workspacePath: workspace,\n agentId: profile.agentId,\n messageCount: messages.length,\n contextWindowNominal: cw,\n estimatedTranscriptTokens: estTokens,\n thinkingDefault: defaults?.thinkingDefault,\n reasoningDefault: defaults?.reasoningDefault,\n verboseDefault: defaults?.verboseDefault,\n compaction,\n toolsFlagsSummary: toolsSummary,\n windowStats: stats.windowStats,\n compactionRunStats: stats.compactionStats,\n });\n }\n\n getSessionStats(sessionKey: string, messages: AgentMessage[]) {\n return {\n windowStats: this.sessionStore.getWindowStats(messages),\n compactionStats: this.sessionStore.getCompactionStats(sessionKey),\n tokenEstimate: this.sessionStore.estimateTokenUsage(sessionKey, messages),\n };\n }\n\n private async applyResolvedThinkingLevel(sessionKey: string, requestOverride?: string | null): Promise<void> {\n const def = this.effectiveAppConfig()?.agents?.defaults?.thinkingDefault;\n const level = await resolveEffectiveThinkingLevel(\n this.sessionConfigStore,\n sessionKey,\n requestOverride,\n def,\n );\n this.agentManager.setThinkingLevel(sessionKey, level);\n }\n\n /** Resolved thinking level and effective model ref for a session (Web UI). */\n async getSessionAgentConfig(sessionKey: string): Promise<{\n thinkingLevel: ThinkingLevel;\n model: string;\n reasoningLevel: ReasoningLevel;\n effectiveWorkspacePath: string;\n workingDirectoryLocked: boolean;\n }> {\n await this.hydrateSessionModelFromStore(sessionKey);\n const cfg = this.effectiveAppConfig()!;\n const sc = await this.sessionConfigStore.get(sessionKey);\n\n // Ensure model display matches the effective agent profile even before an Agent instance exists.\n // Otherwise, `ModelManager.getModelForSession()` falls back to the global default until the first turn creates the agent.\n const profile = resolveEffectiveAgentProfileForSession(cfg, sessionKey);\n const profileModelRef = profile.primaryModelRef?.trim();\n if (profileModelRef) {\n this.modelManager.setSessionProfileDefault(sessionKey, profileModelRef);\n }\n\n const defThink = cfg.agents?.defaults?.thinkingDefault ?? 'medium';\n const level = await resolveEffectiveThinkingLevel(this.sessionConfigStore, sessionKey, null, defThink);\n const defReason = (cfg.agents?.defaults?.reasoningDefault ?? 'stream') as ReasoningLevel;\n const reasoningLevel = await resolveEffectiveReasoningLevel(this.sessionConfigStore, sessionKey, defReason);\n const model = this.modelManager.getModelForSession(sessionKey);\n return {\n thinkingLevel: level,\n model,\n reasoningLevel,\n effectiveWorkspacePath: effectiveWorkspacePathForSession(cfg, sessionKey, sc),\n workingDirectoryLocked: Boolean(sc?.workingDirectoryOverride?.trim()),\n };\n }\n\n /**\n * Load session working directory override into AgentManager, ensure directory exists.\n * Call before AgentManager.getOrCreateAgent for this session.\n */\n async hydrateSessionWorkspaceFromStore(sessionKey: string): Promise<void> {\n const cfg = this.config.config;\n if (!cfg) {\n return;\n }\n const loaded = await this.sessionConfigStore.get(sessionKey);\n if (loaded?.workingDirectoryOverride?.trim()) {\n const wdStored = normalizeWorkingDirectoryInput(loaded.workingDirectoryOverride);\n if (wdStored.ok) {\n this.agentManager.setSessionWorkspaceOverride(sessionKey, wdStored.path);\n } else {\n log.warn({ sessionKey }, 'Invalid stored workingDirectoryOverride; ignoring');\n this.agentManager.setSessionWorkspaceOverride(sessionKey, null);\n }\n } else {\n this.agentManager.setSessionWorkspaceOverride(sessionKey, null);\n }\n const effective = effectiveWorkspacePathForSession(cfg, sessionKey, loaded);\n await mkdir(effective, { recursive: true });\n }\n\n /**\n * Sync persisted session workspace override for an isolated cron run (runs may change when the job is edited).\n * Omit or pass empty `workingDirectory` to use the effective agent default workspace for this session key.\n */\n async applyCronJobWorkingDirectory(sessionKey: string, workingDirectory: string | undefined): Promise<void> {\n const raw = workingDirectory?.trim();\n if (raw) {\n const wdNorm = normalizeWorkingDirectoryInput(raw);\n if (wdNorm.ok === false) {\n log.warn({ sessionKey, error: wdNorm.error }, 'Cron job working directory invalid; using agent default');\n await this.clearCronSessionWorkingDirectoryOverride(sessionKey);\n return;\n }\n await mkdir(wdNorm.path, { recursive: true });\n await this.sessionConfigStore.update(sessionKey, { workingDirectoryOverride: wdNorm.path });\n this.agentManager.setSessionWorkspaceOverride(sessionKey, wdNorm.path);\n return;\n }\n await this.clearCronSessionWorkingDirectoryOverride(sessionKey);\n }\n\n private async clearCronSessionWorkingDirectoryOverride(sessionKey: string): Promise<void> {\n const existing = await this.sessionConfigStore.get(sessionKey);\n if (existing?.workingDirectoryOverride) {\n const { workingDirectoryOverride: _removed, ...rest } = existing;\n await this.sessionConfigStore.set(sessionKey, rest);\n }\n this.agentManager.setSessionWorkspaceOverride(sessionKey, null);\n }\n\n /** Workspace root for UI file tree / editor (same as agent tools after hydration). */\n async getEffectiveWorkspacePathForSession(sessionKey: string): Promise<string> {\n await this.hydrateSessionWorkspaceFromStore(sessionKey);\n const cfg = this.config.config!;\n const sc = await this.sessionConfigStore.get(sessionKey);\n return effectiveWorkspacePathForSession(cfg, sessionKey, sc);\n }\n\n async patchSessionAgentConfig(\n sessionKey: string,\n partial: {\n thinkingLevel?: string;\n model?: string | null;\n reasoningLevel?: string;\n workingDirectory?: string;\n },\n ): Promise<{ ok: boolean; error?: string }> {\n if (partial.model !== undefined) {\n if (partial.model === null || partial.model === '') {\n await this.clearSessionModelOverride(sessionKey);\n } else {\n const ok = await this.modelManager.switchModelForSession(sessionKey, partial.model);\n if (!ok) {\n return { ok: false, error: 'Invalid model' };\n }\n await this.sessionConfigStore.update(sessionKey, { modelOverride: partial.model });\n this.agentManager.setModelForSession(sessionKey, partial.model);\n }\n }\n\n if (partial.thinkingLevel !== undefined) {\n const normalized = normalizeThinkLevel(partial.thinkingLevel);\n if (!normalized) {\n return { ok: false, error: 'Invalid thinking level' };\n }\n await this.sessionConfigStore.update(sessionKey, { thinkingLevel: normalized });\n this.agentManager.setThinkingLevel(sessionKey, normalized as ThinkingLevel);\n }\n\n if (partial.reasoningLevel !== undefined) {\n const normalized = normalizeReasoningLevel(partial.reasoningLevel);\n if (!normalized) {\n return { ok: false, error: 'Invalid reasoning level' };\n }\n await this.sessionConfigStore.update(sessionKey, { reasoningLevel: normalized });\n }\n\n if (partial.workingDirectory !== undefined) {\n const cfg = this.config.config;\n if (!cfg) {\n return { ok: false, error: 'Config not loaded' };\n }\n const existing = await this.sessionConfigStore.get(sessionKey);\n const existingRaw = existing?.workingDirectoryOverride?.trim();\n const incoming = partial.workingDirectory.trim();\n\n const priorMessages = await this.sessionStore.load(sessionKey);\n\n if (priorMessages.length > 0) {\n if (!incoming) {\n return { ok: false, error: 'workingDirectory is empty' };\n }\n if (!existingRaw) {\n return {\n ok: false,\n error: 'Working directory can only be set before the first message in this conversation',\n };\n }\n const prev = normalizeWorkingDirectoryInput(existingRaw);\n const next = normalizeWorkingDirectoryInput(incoming);\n if (prev.ok && next.ok && prev.path === next.path) {\n /* idempotent */\n } else {\n return { ok: false, error: 'Working directory is already set for this session' };\n }\n } else {\n if (!incoming) {\n return { ok: false, error: 'workingDirectory is empty' };\n }\n const wdNorm = normalizeWorkingDirectoryInput(incoming);\n switch (wdNorm.ok) {\n case true:\n if (existingRaw) {\n const prev = normalizeWorkingDirectoryInput(existingRaw);\n if (prev.ok && prev.path === wdNorm.path) {\n break;\n }\n }\n await mkdir(wdNorm.path, { recursive: true });\n await this.sessionConfigStore.update(sessionKey, { workingDirectoryOverride: wdNorm.path });\n this.agentManager.setSessionWorkspaceOverride(sessionKey, wdNorm.path);\n this.agentManager.removeAgent(sessionKey);\n break;\n case false:\n return { ok: false, error: wdNorm.error };\n default:\n return { ok: false, error: 'Invalid working directory' };\n }\n }\n }\n\n return { ok: true };\n }\n\n async *processDirectStreaming(\n content: string,\n sessionKey = 'cli:direct',\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n }>,\n thinking?: string,\n options?: { signal?: AbortSignal },\n ): AsyncGenerator<{ type: string; [key: string]: unknown }, void, unknown> {\n yield* runProcessDirectStreaming(this.createProcessDirectStreamingDeps(), {\n content,\n sessionKey,\n attachments,\n thinking,\n signal: options?.signal,\n });\n }\n\n /**\n * Best-effort timezone resolution for webchat envelope timestamps.\n * Reads `USER.md` in the resolved workspace and extracts a `Timezone:` line.\n */\n resolveUserTimezoneForSession(sessionKey: string): string | undefined {\n try {\n const workspace = this.agentManager.getResolvedWorkspaceForSession(sessionKey);\n const userPath = join(workspace, 'USER.md');\n if (!existsSync(userPath)) return undefined;\n const raw = readFileSync(userPath, 'utf-8');\n const match = raw.match(/Timezone:\\s*(.+)/i);\n const tz = match?.[1]?.trim();\n return tz || undefined;\n } catch {\n return undefined;\n }\n }\n\n private createProcessDirectStreamingDeps(): ProcessDirectStreamingDeps {\n return {\n log,\n parseSessionKey: (sk) => this.parseSessionKey(sk),\n initDirectStreamingSession: (sk, channel, chatId) => this.initSessionContext(sk, channel, chatId),\n registerWebchatSsePublisher: (sk, publisher) => {\n this.webchatSseEnqueueBySession.set(sk, publisher);\n },\n unregisterWebchatSsePublisher: (sk) => {\n this.webchatSseEnqueueBySession.delete(sk);\n },\n agentManager: this.agentManager,\n hydrateSessionWorkspaceFromStore: (sk) => this.hydrateSessionWorkspaceFromStore(sk),\n hydrateSessionModelFromStore: (sk) => this.hydrateSessionModelFromStore(sk),\n agentEventHandler: this.agentEventHandler,\n sessionStore: this.sessionStore,\n prepareLoadedSessionMessages: (sk, msgs) => this.prepareLoadedSessionMessages(sk, msgs),\n modelManager: this.modelManager,\n applyResolvedThinkingLevel: (sk, t) => this.applyResolvedThinkingLevel(sk, t),\n getConfig: () => this.effectiveAppConfig(),\n sessionConfigStore: this.sessionConfigStore,\n attachmentRootsForSession: (sk) => this.attachmentRootsForSession(sk),\n agentOrchestrator: this.agentOrchestrator,\n commandHandler: this.commandHandler,\n prepareInboundAttachments: (sk, att) => this.prepareInboundAttachments(sk, att),\n buildMessageContent: (text, prepared, sk) =>\n buildDirectUserMessageContent({\n content: text,\n attachments: prepared,\n sessionKey: sk,\n config: this.config.config!,\n agentManager: this.agentManager,\n modelManager: this.modelManager,\n }),\n persistAgentSessionMessages: (sk) => this.persistAgentSessionMessages(sk),\n recordPersistentGoalStreamOutcome: (sk, o) => this.recordPersistentGoalStreamOutcome(sk, o),\n maybeEmitWebchatTts: (sk, hadVoice) =>\n maybeEmitWebchatTts(\n {\n config: this.config.config,\n agentManager: this.agentManager,\n sessionStore: this.sessionStore,\n log,\n },\n sk,\n hadVoice,\n ),\n endDirectRequestContext: () => this.endDirectRequestContext(),\n };\n }\n\n /**\n * Inject an SSE event into an in-flight webchat stream (same queue as tokens/tools).\n */\n enqueueWebchatSseEvent(sessionKey: string, event: { type: string; [key: string]: unknown }): void {\n const pub = this.webchatSseEnqueueBySession.get(sessionKey);\n if (pub) {\n pub(event);\n }\n }\n\n /**\n * Queue a steering user message into pi-agent's in-flight run (delivered after current tool work, before the next LLM call).\n * See `Agent.steer` in `@earendil-works/pi-agent-core`.\n */\n async steerWebchatSession(sessionKey: string, text: string): Promise<boolean> {\n const trimmed = text.trim();\n if (!trimmed) return false;\n try {\n await this.hydrateSessionWorkspaceFromStore(sessionKey);\n const agent = this.agentManager.getOrCreateAgent(sessionKey);\n const msg: AgentMessage = {\n role: 'user',\n content: [{ type: 'text', text: trimmed }],\n timestamp: Date.now(),\n };\n agent.steer(msg);\n return true;\n } catch (err) {\n log.warn({ err, sessionKey }, 'steerWebchatSession failed');\n return false;\n }\n }\n\n private createRunProcessDirectDeps(): RunProcessDirectDeps {\n const cfg = this.config.config;\n if (!cfg) {\n throw new Error('AgentService requires config.config');\n }\n return {\n log,\n config: cfg,\n parseSessionKey: (sk) => this.parseSessionKey(sk),\n initSessionContext: (sk, channel, chatId) => {\n void this.initSessionContext(sk, channel, chatId);\n },\n hydrateSessionWorkspaceFromStore: (sk) => this.hydrateSessionWorkspaceFromStore(sk),\n hydrateSessionModelFromStore: (sk) => this.hydrateSessionModelFromStore(sk),\n agentManager: this.agentManager,\n sessionStore: this.sessionStore,\n prepareLoadedSessionMessages: (sk, msgs) => this.prepareLoadedSessionMessages(sk, msgs),\n modelManager: this.modelManager,\n applyResolvedThinkingLevel: (sk, t) => this.applyResolvedThinkingLevel(sk, t),\n prepareInboundAttachments: (sk, att) => this.prepareInboundAttachments(sk, att),\n commandHandler: this.commandHandler,\n persistAgentSessionMessages: (sk) => this.persistAgentSessionMessages(sk),\n endDirectRequestContext: () => this.endDirectRequestContext(),\n };\n }\n\n async processDirect(\n content: string,\n sessionKey = 'cli:direct',\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n }>,\n thinking?: string,\n ): Promise<string> {\n return runProcessDirect(this.createRunProcessDirectDeps(), {\n content,\n sessionKey,\n attachments,\n thinking,\n });\n }\n\n private async handleInboundMessage(msg: InboundMessage): Promise<void> {\n const requestId = inboundMessageLogRequestId(msg);\n\n await runWithLogContext({ requestId }, async () => {\n const routing = await this.messageRouter.routeMessage(msg);\n const { context, isCommand, command, commandArgs } = routing;\n\n const sessionContext: SessionContext = {\n sessionKey: context.sessionKey,\n channel: context.channel,\n chatId: context.chatId,\n senderId: context.senderId || '',\n isGroup: context.isGroup || false,\n metadata: {\n transcribedVoice: msg.metadata?.transcribedVoice === true,\n },\n };\n\n updateAsyncLogContext({ sessionId: sessionContext.sessionKey });\n\n this.sessionContextManager.setContext(sessionContext);\n this.feedbackCoordinator.setContext(sessionContext);\n\n // Setup event handling for this session\n this.setupSessionEventHandling(sessionContext.sessionKey);\n\n await this.sessionLifecycleManager.startSession(sessionContext);\n\n /** Declared on the function so `finally` can clear typing after outbound (TTS + send). */\n let typingController: TypingController | null = null;\n let inboundTurnArmed = false;\n let busProcessFailed: string | undefined;\n\n try {\n if (msg.channel === 'system') {\n await this.handleSystemMessage(msg, sessionContext);\n return;\n }\n\n if (isCommand && command) {\n const handled = await this.commandHandler.executeCommand(command, commandArgs || '', {\n sessionKey: sessionContext.sessionKey,\n channel: sessionContext.channel,\n chatId: sessionContext.chatId,\n senderId: sessionContext.senderId,\n isGroup: sessionContext.isGroup,\n inboundMetadata: msg.metadata,\n });\n\n if (handled) {\n return;\n }\n }\n\n // Start continuous typing indicator (renews every 5 seconds)\n if (msg.channel !== 'cli') {\n typingController = createTypingController({\n intervalSeconds: 5,\n onStart: async () => {\n await this.bus.publishOutbound({\n channel: msg.channel,\n chat_id: msg.chat_id,\n content: '',\n type: 'typing_on',\n metadata: {\n accountId: msg.metadata?.accountId,\n threadId: msg.metadata?.threadId,\n sessionWebhook: msg.metadata?.sessionWebhook,\n conversationId: msg.metadata?.conversationId,\n },\n });\n },\n onStop: async () => {\n await this.bus.publishOutbound({\n channel: msg.channel,\n chat_id: msg.chat_id,\n content: '',\n type: 'typing_off',\n metadata: {\n accountId: msg.metadata?.accountId,\n threadId: msg.metadata?.threadId,\n sessionWebhook: msg.metadata?.sessionWebhook,\n conversationId: msg.metadata?.conversationId,\n },\n });\n },\n });\n typingController.start();\n }\n\n if (this.channelManagerRef && msg.channel !== 'cli') {\n const meta = msg.metadata as Record<string, unknown> | undefined;\n const streamHandle = this.channelManagerRef.startStream(\n msg.channel,\n msg.chat_id,\n meta?.accountId as string | undefined,\n {\n threadId: meta?.threadId as string | undefined,\n replyToMessageId: meta?.messageId as string | undefined,\n },\n );\n\n if (streamHandle) {\n this.setStreamHandle(streamHandle as StreamHandle);\n }\n }\n\n this.beginInboundTurn(sessionContext.sessionKey);\n inboundTurnArmed = true;\n try {\n await this.agentOrchestrator.process(msg, sessionContext);\n } catch (procErr) {\n busProcessFailed = procErr instanceof Error ? procErr.message : String(procErr);\n throw procErr;\n }\n } finally {\n await this.sessionLifecycleManager.endSession(sessionContext);\n await this.streamManager.end();\n try {\n await this.sendFinalResponse(msg, sessionContext);\n } finally {\n // After outbound (incl. TTS); previously we cleared typing right after LLM finished, so Weixin showed typing_off before the message.\n await typingController?.stop();\n }\n if (inboundTurnArmed) {\n const meta = msg.metadata as Record<string, unknown> | undefined;\n const assistantPlainText = this.getLastAssistantPlainText(sessionContext.sessionKey) ?? '';\n try {\n await this.emitSessionTurnComplete({\n sessionKey: sessionContext.sessionKey,\n channel: sessionContext.channel,\n chatId: sessionContext.chatId,\n inboundUserText: msg.content,\n assistantPlainText,\n aborted: false,\n ...(busProcessFailed !== undefined ? { streamError: busProcessFailed } : {}),\n skipPersistentGoalPostTurn: false,\n outboundMetadata: {\n accountId: meta?.accountId,\n threadId: meta?.threadId,\n },\n });\n } catch (turnErr) {\n const em = turnErr instanceof Error ? turnErr.message : String(turnErr);\n log.warn(\n { err: turnErr, sessionKey: sessionContext.sessionKey },\n `Session turn complete failed: ${em}`,\n );\n }\n this.endInboundTurn(sessionContext.sessionKey);\n }\n this.feedbackCoordinator.endTask();\n this.sessionContextManager.clearContext();\n this.feedbackCoordinator.clearContext();\n }\n });\n }\n\n private async handleSystemMessage(msg: InboundMessage, context: SessionContext): Promise<void> {\n log.debug({ sessionKey: context.sessionKey }, 'Processing system message');\n\n await this.hydrateSessionWorkspaceFromStore(context.sessionKey);\n\n // Get or create agent for this session\n const agent = this.agentManager.getOrCreateAgent(context.sessionKey);\n\n const messages = await this.sessionStore.load(context.sessionKey);\n await this.checkAndCompact(context.sessionKey, messages);\n const refreshedMessages = await this.sessionStore.load(context.sessionKey);\n agent.state.messages = this.prepareLoadedSessionMessages(context.sessionKey, refreshedMessages);\n\n const systemMessage: AgentMessage = {\n role: 'user',\n content: [{ type: 'text', text: `[System: ${msg.sender_id}] ${msg.content}` }],\n timestamp: Date.now(),\n };\n\n try {\n await agent.prompt(systemMessage);\n await agent.waitForIdle();\n\n const finalContent = this.agentManager.getLastAssistantContent(context.sessionKey);\n if (finalContent) {\n const hookResult = await this.hookHandler.runMessageSending(\n context.chatId,\n finalContent,\n context.channel,\n );\n if (hookResult.send) {\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: hookResult.content || finalContent,\n type: 'message',\n });\n }\n }\n\n await this.persistAgentSessionMessages(context.sessionKey);\n } catch (error) {\n const em = error instanceof Error ? error.message : String(error);\n log.error(\n {\n err: error,\n errorMessage: em,\n sessionKey: context.sessionKey,\n channel: context.channel,\n chatId: context.chatId,\n senderId: msg.sender_id,\n },\n `System message handling failed: ${em}`,\n );\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: '❌ An error occurred while processing the system message.',\n type: 'message',\n });\n }\n }\n\n /**\n * Setup event handling for a specific session\n */\n private setupSessionEventHandling(sessionKey: string): void {\n // If already subscribed, skip\n if (this.sessionUnsubscribers.has(sessionKey)) {\n return;\n }\n\n const unsubscribe = this.agentManager.subscribeToSession(sessionKey, (event) => {\n this.handleSessionEvent(sessionKey, event);\n });\n\n if (unsubscribe) {\n this.sessionUnsubscribers.set(sessionKey, unsubscribe);\n }\n }\n\n /**\n * Handle events from a specific session's agent\n */\n private handleSessionEvent(sessionKey: string, event: AgentEvent): void {\n const currentContext = this.sessionContextManager.getContext();\n if (!currentContext) {\n // Inbound `finally` clears context before trailing agent `message_update` events finish — ignore (not a bug).\n return;\n }\n\n if (currentContext.sessionKey !== sessionKey) {\n // Event from a different session — still process with current context where applicable\n this.agentEventHandler.handle(event, currentContext);\n return;\n }\n\n // Handle streaming updates for the current session\n if (event.type === 'message_update') {\n const msgEvent = event as Extract<AgentEvent, { type: 'message_update' }>;\n if (msgEvent.message?.role === 'assistant') {\n const content = msgEvent.message.content;\n const text = Array.isArray(content)\n ? extractTextContent(content as Array<{ type: string; text?: string }>)\n : String(content);\n\n this.streamManager.update(text);\n }\n }\n\n this.agentEventHandler.handle(event, currentContext);\n }\n\n private async checkAndCompact(sessionKey: string, messages: AgentMessage[]): Promise<void> {\n const contextWindow = this.getContextWindow();\n const prep = this.sessionStore.prepareCompaction(sessionKey, messages, contextWindow);\n if (!prep.needsCompaction) return;\n\n log.info({ sessionKey, reason: prep.stats?.reason, usagePercent: prep.stats?.usagePercent }, 'Session needs compaction');\n\n const result = await this.sessionStore.compact(sessionKey, messages, contextWindow, undefined, false);\n await this.hookHandler.trigger('after_compaction', {\n messageCount: messages.length,\n tokenCount: result.tokensBefore,\n compactedCount: messages.length - result.firstKeptIndex,\n });\n log.info({ sessionKey, tokensBefore: result.tokensBefore, tokensAfter: result.tokensAfter }, 'Session compacted');\n }\n\n private getContextWindow(): number {\n const defaults = this.config.agentDefaults || this.config.config?.agents?.defaults;\n return defaults?.maxTokens ? defaults.maxTokens * 4 : 128000;\n }\n\n private async sendFinalResponse(\n msg: InboundMessage,\n sessionContext: SessionContext\n ): Promise<void> {\n if (this.streamManager.consumeSkipFinalOutbound()) {\n return;\n }\n\n const finalContent = this.agentManager.getLastAssistantContent(sessionContext.sessionKey);\n if (!finalContent?.trim()) return;\n\n const ackMax =\n this.config.config?.gateway?.heartbeat?.ackMaxChars ?? DEFAULT_ACK_MAX_CHARS;\n if (shouldSilence(finalContent, ackMax) || finalContent.trim() === NO_REPLY) {\n log.debug(\n { sessionKey: sessionContext.sessionKey },\n 'Silent reply — skipping outbound',\n );\n return;\n }\n\n const hookResult = await this.hookHandler.runMessageSending(\n sessionContext.chatId,\n finalContent,\n sessionContext.channel,\n );\n if (!hookResult.send) return;\n\n // TTS is handled by ChannelManager, just send text message here\n await this.bus.publishOutbound({\n channel: sessionContext.channel,\n chat_id: sessionContext.chatId,\n content: hookResult.content || finalContent,\n type: 'message',\n metadata: {\n accountId: msg.metadata?.accountId,\n threadId: msg.metadata?.threadId,\n transcribedVoice: sessionContext.metadata?.transcribedVoice,\n sessionWebhook: msg.metadata?.sessionWebhook,\n conversationId: msg.metadata?.conversationId,\n },\n });\n }\n\n /** Extension hooks for ChannelManager outbound pipeline (Gateway). */\n async invokeOutboundMessageSending(\n to: string,\n content: string,\n channel: string,\n ): Promise<{ send: boolean; content?: string; reason?: string }> {\n return this.hookHandler.runMessageSending(to, content, channel);\n }\n\n async invokeOutboundMessageSent(\n to: string,\n content: string,\n success: boolean,\n error: string | undefined,\n channel: string,\n ): Promise<void> {\n return this.hookHandler.runMessageSent(to, content, success, error, channel);\n }\n\n private dispose(): void {\n this.sessionTracker.dispose();\n\n // Unsubscribe from all session agents\n for (const unsubscribe of this.sessionUnsubscribers.values()) {\n unsubscribe();\n }\n this.sessionUnsubscribers.clear();\n\n // Dispose all agent instances\n this.agentManager.dispose();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"service.js","names":["parseRoutingSessionKey"],"sources":["../../../src/agent/service.ts"],"sourcesContent":["import type { AgentEvent, AgentMessage, ThinkingLevel } from '@earendil-works/pi-agent-core';\nimport { MessageBusShutdownError, type MessageBus, type InboundMessage } from '../infra/bus/index.js';\nimport { type Config, getAgentDefaultModelRef } from '../config/schema.js';\nimport { maybeAutoTitleSessionStore } from '../session/session-title.js';\nimport type { ChannelManager } from '../channels/manager.js';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport {\n SessionStore,\n SessionConfigStore,\n resolveEffectiveThinkingLevel,\n resolveEffectiveReasoningLevel,\n effectiveWorkspacePathForSession,\n normalizeWorkingDirectoryInput,\n type CompactionConfig,\n type WindowConfig,\n} from '../session/index.js';\nimport type { SessionDetail } from '../session/types.js';\nimport {\n normalizeThinkLevel,\n normalizeReasoningLevel,\n type ThinkLevel,\n type ReasoningLevel,\n} from './transcript/thinking-types.js';\nimport { createLogger, runWithLogContext, updateAsyncLogContext } from '../utils/logger.js';\nimport { ExtensionHookRunner } from '../extensions/index.js';\nimport { loadBootstrapFiles, extractTextContent } from './context/workspace.js';\nimport { SessionTracker } from './session/tracker.js';\nimport { ModelManager } from './models/index.js';\nimport { initializeCommands } from '../chat-commands/index.js';\nimport { ProgressFeedbackManager } from './lifecycle/progress.js';\nimport { HookHandler } from './lifecycle/hook-handler.js';\nimport { ToolErrorTracker } from './tools/error-tracker.js';\nimport { RequestLimiter } from './models/request-limiter.js';\nimport { SystemReminder } from './prompt/system-reminder.js';\nimport { ToolUsageAnalyzer } from './tools/usage-analyzer.js';\nimport { ToolChainTracker } from './tools/chain-tracker.js';\nimport { ErrorPatternMatcher } from './tools/error-pattern-matcher.js';\nimport { ContextMiddleware, SelfVerifyMiddleware } from './middleware/index.js';\nimport { LifecycleManager } from './lifecycle/index.js';\nimport { CompactionLifecycleHandler } from './lifecycle/handlers/compaction.js';\n\nimport { MessageRouter, CommandHandler, StreamManager } from './messaging/index.js';\nimport { SessionContextManager, SessionLifecycleManager, type SessionContext } from './session/index.js';\nimport { AgentOrchestrator, AgentEventHandler } from './orchestration/index.js';\nimport { FeedbackCoordinator } from './feedback/index.js';\nimport { AgentManager, type SkillCatalogEntry } from './agent-manager.js';\nimport type { SkillMarkdownPreviewPayload } from './skills/types.js';\nimport { inboundMessageLogRequestId } from './service-inbound-utils.js';\nimport type { AgentServiceConfig, StreamHandle } from './service.types.js';\nimport {\n runProcessDirectStreaming,\n type ProcessDirectStreamingDeps,\n} from './service/process-direct-streaming.js';\nimport { parseSessionKey as parseRoutingSessionKey } from '../routing/session-key.js';\nimport { handlePersistentGoalPostTurn } from './goals/post-turn.js';\nimport type { PersistentGoalApis } from './goals/persistent-goal-apis.js';\nimport { reconcileManagedDreamingCronJobs } from './service/reconcile-dreaming-cron.js';\nimport { parseOutboundSessionKey } from './service/parse-outbound-session-key.js';\nimport { runBtwQuery } from './service/btw-query.js';\nimport { formatSessionContextReport } from './service/session-context-report.js';\nimport { buildDirectUserMessageContent } from './service/build-direct-message-content.js';\nimport { maybeEmitWebchatTts } from './service/webchat-tts.js';\nimport { runProcessDirect, type RunProcessDirectDeps } from './service/process-direct-one-shot.js';\n\nimport {\n resolveAgentHomeDir,\n resolveDefaultAgentId,\n} from './agent-scope.js';\nimport { extractProfileAgentId, resolveAgentBootstrapDir } from '../config/agent-profile.js';\nimport { DEFAULT_ACK_MAX_CHARS, NO_REPLY, shouldSilence } from '../heartbeat/tokens.js';\nimport { createTypingController, type TypingController } from './lifecycle/typing.js';\nimport { cleanTrailingErrors, sanitizeMessages } from './memory/message-sanitizer.js';\nimport {\n tryApplySessionTranscriptHygiene,\n tryApplySessionTranscriptHygieneForPersistence,\n} from './transcript/transcript-hygiene.js';\nimport {\n persistInboundAttachmentsToWorkspace,\n type InternalAttachmentRoots,\n} from '../channels/attachments/inbound-persist.js';\nimport { resolveEffectiveAgentProfileForSession } from '../config/agent-profile.js';\nimport { applyConfigOverrides } from '../config/runtime-overrides.js';\nimport type { CompactionResult } from './memory/compaction.js';\n\nexport type { AgentServiceConfig, AgentContext, StreamHandle } from './service.types.js';\n\nconst log = createLogger('AgentService');\n\nexport class AgentService {\n private sessionStore: SessionStore;\n private sessionConfigStore: SessionConfigStore;\n private hookRunner?: ExtensionHookRunner;\n private running = false;\n private agentId: string;\n private workspaceDir: string;\n private bootstrapFiles: ReturnType<typeof loadBootstrapFiles> = [];\n private channelManagerRef: ChannelManager | null = null;\n private bus: MessageBus;\n private config: AgentServiceConfig;\n\n private sessionTracker: SessionTracker;\n private modelManager: ModelManager;\n private progressManager: ProgressFeedbackManager;\n private hookHandler: HookHandler;\n private lifecycleManager: LifecycleManager;\n private errorTracker: ToolErrorTracker;\n private requestLimiter: RequestLimiter;\n private systemReminder: SystemReminder;\n private toolUsageAnalyzer: ToolUsageAnalyzer;\n private toolChainTracker: ToolChainTracker;\n private errorPatternMatcher: ErrorPatternMatcher;\n private selfVerifyMiddleware: SelfVerifyMiddleware;\n private contextMiddleware: ContextMiddleware;\n\n private messageRouter: MessageRouter;\n private commandHandler: CommandHandler;\n private streamManager: StreamManager;\n private sessionContextManager: SessionContextManager;\n private sessionLifecycleManager: SessionLifecycleManager;\n private agentOrchestrator: AgentOrchestrator;\n private agentEventHandler: AgentEventHandler;\n private feedbackCoordinator: FeedbackCoordinator;\n private agentManager: AgentManager;\n\n /** Webchat SSE queue pushers for `clarify_request` and similar mid-turn UI events. */\n private webchatSseEnqueueBySession = new Map<\n string,\n (event: { type: string; [key: string]: unknown }) => void\n >();\n\n /** Gateway: drain `processDirectStreaming` for webchat continuations (Hermes FIFO-style). */\n private persistentGoalWebchatContinuationScheduler?: (sessionKey: string, message: string) => void;\n private directStreamOutcomeBySession = new Map<string, { skipPersistentGoalPostTurn: boolean }>();\n /** Concurrent inbound / direct-stream turns per session (Hermes-style /goal mid-flight guard). */\n private inboundTurnDepthBySession = new Map<string, number>();\n\n /** Gateway: notify UI after direct `SessionStore.updateMetadata` (no SessionManager emit). */\n private onSessionMetadataUpdated?: (sessionKey: string) => void;\n\n // Track event unsubscribers per session\n private sessionUnsubscribers: Map<string, () => void> = new Map();\n\n private effectiveAppConfig(): Config | undefined {\n const base = this.config.config;\n return base ? applyConfigOverrides(base) : undefined;\n }\n\n constructor(bus: MessageBus, config: AgentServiceConfig) {\n this.bus = bus;\n this.config = config;\n this.onSessionMetadataUpdated = config.onSessionMetadataUpdated;\n this.agentId = `agent-${Date.now()}`;\n this.workspaceDir = config.workspace;\n\n if (config.config) {\n const aid = resolveDefaultAgentId(config.config);\n this.bootstrapFiles = loadBootstrapFiles(resolveAgentBootstrapDir(config.config, aid));\n } else {\n this.bootstrapFiles = [];\n }\n\n this.sessionTracker = new SessionTracker();\n this.modelManager = new ModelManager({\n defaultModel: config.model,\n config: config.config,\n });\n\n initializeCommands();\n log.debug('Command system initialized');\n\n this.sessionStore = config.sessionStore ?? this.createSessionStore();\n const appCfgForPaths = this.config.config;\n if (!appCfgForPaths) {\n throw new Error('AgentService requires config.config for session paths');\n }\n const defaultAid = resolveDefaultAgentId(appCfgForPaths);\n const defaultAgentHome = resolveAgentHomeDir(appCfgForPaths, defaultAid);\n this.sessionConfigStore = new SessionConfigStore(defaultAgentHome);\n\n this.hookRunner = this.createHookRunner();\n this.hookHandler = new HookHandler({\n hookRunner: this.hookRunner,\n agentId: this.agentId,\n get sessionKey() { return this.currentContext?.sessionKey; },\n });\n\n this.progressManager = this.createProgressManager();\n this.initializeReliabilityModules();\n\n this.lifecycleManager = new LifecycleManager();\n this.initializeLifecycleHandlers();\n\n this.streamManager = new StreamManager();\n this.sessionContextManager = new SessionContextManager();\n this.feedbackCoordinator = new FeedbackCoordinator({\n progressManager: this.progressManager,\n bus,\n });\n\n // Initialize AgentManager\n this.agentManager = new AgentManager({\n workspace: config.workspace,\n model: config.model,\n config: config.config,\n extensionRegistry: config.extensionRegistry,\n hookRunner: this.hookRunner,\n bus,\n getCurrentContext: () => this.sessionContextManager.getContext(),\n getSessionStore: () => this.sessionStore,\n getModelManager: () => this.modelManager,\n thinkingLevel: config.thinkingLevel,\n reasoningLevel: config.reasoningLevel,\n verboseLevel: config.verboseLevel,\n gatewayClarify: config.gatewayClarify,\n getCronService: config.getCronService,\n });\n\n this.agentEventHandler = new AgentEventHandler({\n progressManager: this.progressManager,\n errorTracker: this.errorTracker,\n requestLimiter: this.requestLimiter,\n lifecycleManager: this.lifecycleManager,\n toolChainTracker: this.toolChainTracker,\n selfVerifyMiddleware: this.selfVerifyMiddleware,\n systemReminder: this.systemReminder,\n toolUsageAnalyzer: this.toolUsageAnalyzer,\n errorPatternMatcher: this.errorPatternMatcher,\n modelManager: this.modelManager,\n });\n\n this.agentOrchestrator = new AgentOrchestrator({\n agentManager: this.agentManager,\n sessionStore: this.sessionStore,\n modelManager: this.modelManager,\n eventHandler: this.agentEventHandler,\n feedbackCoordinator: this.feedbackCoordinator,\n sessionConfigStore: this.sessionConfigStore,\n hydrateSessionWorkspaceFromStore: (sessionKey) => this.hydrateSessionWorkspaceFromStore(sessionKey),\n getConfig: () => this.effectiveAppConfig(),\n getThinkingDefault: () => this.effectiveAppConfig()?.agents?.defaults?.thinkingDefault,\n getThinkingDefaultForSession: (sessionKey: string) =>\n this.agentManager.getThinkingDefaultForSession(sessionKey),\n workspaceRoot: this.workspaceDir,\n getWorkspaceRootForSession: (sessionKey: string) =>\n this.agentManager.getResolvedWorkspaceForSession(sessionKey),\n getAgentInternalStorageRootForSession: (sessionKey: string) =>\n resolveAgentHomeDir(this.config.config!, extractProfileAgentId(sessionKey, this.config.config!)),\n enqueueAutoTitle: (sessionKey: string) => this.enqueueMaybeAutoTitleAfterPersist(sessionKey),\n });\n\n this.messageRouter = new MessageRouter();\n this.commandHandler = new CommandHandler({\n config: config.config!,\n bus,\n sessionStore: this.sessionStore,\n sessionConfigStore: this.sessionConfigStore,\n getPersistentGoalApisForCommand: (routing) => this.getPersistentGoalApisForCommand(routing),\n applySessionThinkingLevel: (sessionKey: string, level: ThinkLevel) => {\n this.agentManager.setThinkingLevel(sessionKey, level as ThinkingLevel);\n },\n getCurrentModel: () => this.agentOrchestrator.getCurrentModel(),\n switchModelForSession: (sessionKey: string, modelId: string) =>\n this.switchModelForSession(sessionKey, modelId),\n invalidateAgentSession: (sessionKey: string) => {\n this.agentManager.removeAgent(sessionKey);\n },\n abortSessionTurn: async (sessionKey: string) => {\n await this.streamManager.abort();\n this.agentOrchestrator.abort(sessionKey);\n },\n compactSession: (sessionKey, options) => this.compactSession(sessionKey, options),\n btwQuery: (sessionKey, question) => this.btwQuery(sessionKey, question),\n getSessionContextReport: (sessionKey, mode) => this.getSessionContextReport(sessionKey, mode),\n });\n\n this.sessionLifecycleManager = new SessionLifecycleManager(\n this.sessionStore,\n this.sessionTracker,\n this.lifecycleManager\n );\n\n // Register signal handlers only if not running as an Electron subprocess.\n // In Electron, the parent process manages the lifecycle and signals should not trigger disposal.\n const isElectronSubprocess = !!process.env.ELECTRON_RUN_AS_NODE;\n if (!isElectronSubprocess) {\n process.on('SIGINT', () => this.dispose());\n process.on('SIGTERM', () => this.dispose());\n }\n\n log.info('AgentService initialized');\n }\n\n private attachmentRootsForSession(sessionKey: string): InternalAttachmentRoots {\n const cfg = this.config.config!;\n return {\n agentHome: resolveAgentHomeDir(cfg, extractProfileAgentId(sessionKey, cfg)),\n };\n }\n\n private createSessionStore(): SessionStore {\n const sessionStoreDefaults = this.config.agentDefaults || this.config.config?.agents?.defaults;\n const windowConfig: Partial<WindowConfig> = {\n maxMessages: 100,\n keepRecentMessages: sessionStoreDefaults?.maxToolIterations || 20,\n preserveSystemMessages: true,\n };\n const compactionConfig: Partial<CompactionConfig> = {\n enabled: sessionStoreDefaults?.compaction?.enabled ?? true,\n mode: (sessionStoreDefaults?.compaction?.mode as 'extractive' | 'abstractive' | 'structured') || 'abstractive',\n reserveTokens: sessionStoreDefaults?.compaction?.reserveTokens || 8000,\n triggerThreshold: sessionStoreDefaults?.compaction?.triggerThreshold || 0.8,\n minMessagesBeforeCompact: sessionStoreDefaults?.compaction?.minMessagesBeforeCompact || 10,\n keepRecentMessages: sessionStoreDefaults?.compaction?.keepRecentMessages || 10,\n evictionWindow: sessionStoreDefaults?.compaction?.evictionWindow || 0.2,\n retentionWindow: sessionStoreDefaults?.compaction?.retentionWindow || 6,\n };\n const appCfg = this.config.config;\n if (!appCfg) {\n throw new Error('AgentService requires config.config for session store paths');\n }\n return new SessionStore(\n {\n config: appCfg,\n agentId: resolveDefaultAgentId(appCfg),\n },\n windowConfig,\n compactionConfig,\n );\n }\n\n private createHookRunner(): ExtensionHookRunner | undefined {\n if (!this.config.extensionRegistry) return undefined;\n\n return new ExtensionHookRunner(this.config.extensionRegistry, {\n catchErrors: true,\n logger: {\n info: (msg: string) => log.info({ hook: true }, msg),\n warn: (msg: string) => log.warn({ hook: true }, msg),\n error: (msg: string) => log.error({ hook: true }, msg),\n },\n });\n }\n\n private createProgressManager(): ProgressFeedbackManager {\n return new ProgressFeedbackManager({\n level: 'normal',\n showThinking: true,\n streamToolProgress: true,\n heartbeatEnabled: true,\n heartbeatIntervalMs: 20000,\n longTaskThresholdMs: 30000,\n });\n }\n\n private initializeReliabilityModules(): void {\n const defaults = this.config.agentDefaults || this.config.config?.agents?.defaults;\n\n this.errorTracker = new ToolErrorTracker({\n maxFailuresPerTool: defaults?.maxToolFailuresPerTurn || 3,\n maxTotalFailures: defaults?.maxToolFailuresPerTurn ? defaults.maxToolFailuresPerTurn + 2 : 5,\n resetOnTurnEnd: true,\n });\n\n this.selfVerifyMiddleware = new SelfVerifyMiddleware({\n maxEditsPerFile: 5,\n enablePreCompletionCheck: true,\n minTurnsForVerification: 4,\n resetOnVerification: true,\n });\n\n this.requestLimiter = new RequestLimiter({\n maxRequestsPerTurn: defaults?.maxRequestsPerTurn || 50,\n warnThreshold: 0.8,\n softLimit: false,\n });\n\n this.systemReminder = new SystemReminder({\n enabled: true,\n appendToToolResults: true,\n maxRemindersPerTurn: 3,\n });\n\n this.toolUsageAnalyzer = new ToolUsageAnalyzer({\n enabled: true,\n lowUsageThreshold: 5,\n veryLowUsageThreshold: 1,\n minCallsForAnalysis: 100,\n reportIntervalMs: 60 * 60 * 1000,\n });\n\n this.toolChainTracker = new ToolChainTracker({\n enabled: true,\n maxChainsPerSession: 10,\n maxNodesPerChain: 100,\n trackParams: true,\n trackResults: true,\n autoPrune: true,\n });\n\n this.errorPatternMatcher = new ErrorPatternMatcher({\n enabled: true,\n defaultMaxRetries: 1,\n logMatches: true,\n });\n\n // Initialize context middleware for automatic request tracking\n this.contextMiddleware = new ContextMiddleware();\n }\n\n private initializeLifecycleHandlers(): void {\n this.lifecycleManager.on('llm_response', new CompactionLifecycleHandler({\n minMessages: 20,\n maxTokens: 8000,\n preserveReasoning: true,\n accumulateUsage: true,\n }));\n\n log.debug(\n { handlers: this.lifecycleManager.getRegisteredHandlers() },\n 'Lifecycle handlers initialized'\n );\n }\n\n setChannelManager(channelManager: ChannelManager): void {\n this.modelManager.setChannelManager(channelManager);\n this.channelManagerRef = channelManager;\n }\n\n /**\n * Apply config after save or hot reload so the default model updates without restarting the gateway.\n */\n applyAgentDefaultsFromConfig(config: Config): void {\n this.config.config = config;\n const ref = getAgentDefaultModelRef(config);\n this.config.model = ref;\n this.modelManager.updateFromConfig(config);\n this.agentManager.updateAgentDefaults(config);\n this.commandHandler.updateAgentConfig(config);\n }\n\n getSkillCatalog(lang?: string): SkillCatalogEntry[] {\n return this.agentManager.getSkillCatalog(lang);\n }\n\n getSkillMarkdownSource(skillName: string, lang?: string): SkillMarkdownPreviewPayload | null {\n return this.agentManager.getSkillMarkdownSource(skillName, lang);\n }\n\n refreshSkillsAfterDiskChange(): void {\n this.agentManager.refreshSkillsAfterDiskChange();\n }\n\n refreshSkillsAfterSkillConfigChange(): void {\n this.agentManager.refreshSkillsAfterSkillConfigChange();\n }\n\n getModelForSession(sessionKey: string): string {\n return this.modelManager.getModelForSession(sessionKey);\n }\n\n async switchModelForSession(sessionKey: string, modelId: string): Promise<boolean> {\n const ok = await this.modelManager.switchModelForSession(sessionKey, modelId);\n if (!ok) return false;\n await this.sessionConfigStore.update(sessionKey, { modelOverride: modelId });\n const result = this.agentManager.setModelForSession(sessionKey, modelId);\n if (result) {\n this.sessionTracker.touchSession(sessionKey);\n }\n return true;\n }\n\n private async clearSessionModelOverride(sessionKey: string): Promise<void> {\n this.modelManager.clearSessionModelOverride(sessionKey);\n await this.sessionConfigStore.update(sessionKey, { modelOverride: undefined });\n const agent = this.agentManager.getAgent(sessionKey);\n if (agent) {\n await this.modelManager.applyModelForSession(agent, sessionKey);\n }\n }\n\n /**\n * Clears per-session model override so the next turn uses the configured agent default\n * (e.g. cron isolated job with no explicit model).\n */\n async resetSessionModelToAgentDefault(sessionKey: string): Promise<void> {\n await this.clearSessionModelOverride(sessionKey);\n }\n\n private async hydrateSessionModelFromStore(sessionKey: string): Promise<void> {\n const cfg = await this.sessionConfigStore.get(sessionKey);\n if (cfg?.modelOverride) {\n await this.modelManager.switchModelForSession(sessionKey, cfg.modelOverride);\n }\n }\n\n setStreamHandle(handle: StreamHandle): void {\n this.streamManager.setHandle(handle);\n this.feedbackCoordinator.setStreamHandle(handle);\n }\n\n clearStreamHandle(): void {\n this.streamManager.clearHandle();\n this.feedbackCoordinator.endTask();\n }\n\n /** Last assistant visible plain text for a session (e.g. after a webchat stream). */\n getLastAssistantPlainText(sessionKey: string): string {\n return this.agentManager.getLastAssistantContent(sessionKey) ?? '';\n }\n\n /** Gateway only: webchat continuations bypass the bus and reuse `runGatewayAgent`. */\n setPersistentGoalWebchatContinuationScheduler(\n fn: ((sessionKey: string, message: string) => void) | undefined,\n ): void {\n this.persistentGoalWebchatContinuationScheduler = fn;\n }\n\n beginInboundTurn(sessionKey: string): void {\n this.inboundTurnDepthBySession.set(\n sessionKey,\n (this.inboundTurnDepthBySession.get(sessionKey) ?? 0) + 1,\n );\n }\n\n endInboundTurn(sessionKey: string): void {\n const n = (this.inboundTurnDepthBySession.get(sessionKey) ?? 1) - 1;\n if (n <= 0) {\n this.inboundTurnDepthBySession.delete(sessionKey);\n } else {\n this.inboundTurnDepthBySession.set(sessionKey, n);\n }\n }\n\n getInboundTurnDepth(sessionKey: string): number {\n return this.inboundTurnDepthBySession.get(sessionKey) ?? 0;\n }\n\n schedulePersistentGoalContinuation(\n sessionKey: string,\n message: string,\n routing: { channel: string; chatId: string; inboundMetadata?: Record<string, unknown> },\n ): void {\n const parsed = parseRoutingSessionKey(sessionKey);\n if (parsed?.source === 'webchat' && this.persistentGoalWebchatContinuationScheduler) {\n this.persistentGoalWebchatContinuationScheduler(sessionKey, message);\n return;\n }\n queueMicrotask(() => {\n void this.bus\n .publishInbound({\n channel: routing.channel,\n chat_id: routing.chatId,\n sender_id: 'persistent-goal',\n content: message,\n metadata: { sessionKey, ...routing.inboundMetadata },\n })\n .catch((err) => {\n log.warn({ err, sessionKey }, 'Persistent goal: publishInbound failed');\n });\n });\n }\n\n getPersistentGoalApisForCommand(routing: {\n sessionKey: string;\n channel: string;\n chatId: string;\n inboundMetadata?: Record<string, unknown>;\n }): PersistentGoalApis {\n return {\n getSessionMetadata: (k) => this.sessionStore.getMetadata(k),\n updateSessionMetadata: async (k, u) => {\n await this.sessionStore.updateMetadata(k, u);\n this.onSessionMetadataUpdated?.(k);\n },\n loadMessages: (k) => this.sessionStore.loadMessages(k),\n saveMessages: (k, m) => this.sessionStore.saveMessages(k, m),\n scheduleContinuation: (sk, msg) => {\n this.schedulePersistentGoalContinuation(sk, msg, {\n channel: routing.channel,\n chatId: routing.chatId,\n inboundMetadata: routing.inboundMetadata,\n });\n },\n inboundConcurrentDepth: (sk) => this.getInboundTurnDepth(sk),\n };\n }\n\n recordPersistentGoalStreamOutcome(\n sessionKey: string,\n outcome: { skipPersistentGoalPostTurn: boolean },\n ): void {\n this.directStreamOutcomeBySession.set(sessionKey, outcome);\n }\n\n takePersistentGoalStreamOutcome(sessionKey: string): { skipPersistentGoalPostTurn: boolean } | undefined {\n const v = this.directStreamOutcomeBySession.get(sessionKey);\n this.directStreamOutcomeBySession.delete(sessionKey);\n return v;\n }\n\n /**\n * After any assistant-visible turn (webchat direct stream or bus-driven channels): extension hook + built-in `/goal` post-turn.\n */\n async emitSessionTurnComplete(payload: {\n sessionKey: string;\n channel: string;\n chatId: string;\n inboundUserText: string;\n assistantPlainText: string;\n aborted: boolean;\n streamError?: string;\n skipPersistentGoalPostTurn?: boolean;\n outboundMetadata?: Record<string, unknown>;\n }): Promise<void> {\n await this.hookHandler.triggerWithSessionKey(payload.sessionKey, 'webchat_turn_complete', {\n sessionKey: payload.sessionKey,\n channel: payload.channel,\n chatId: payload.chatId,\n inboundUserText: payload.inboundUserText,\n assistantPlainText: payload.assistantPlainText,\n aborted: payload.aborted,\n ...(payload.streamError !== undefined ? { streamError: payload.streamError } : {}),\n });\n\n const apis = this.getPersistentGoalApisForCommand({\n sessionKey: payload.sessionKey,\n channel: payload.channel,\n chatId: payload.chatId,\n inboundMetadata: payload.outboundMetadata,\n });\n\n const src = parseRoutingSessionKey(payload.sessionKey)?.source;\n const isWebchat = src === 'webchat';\n const publishVerdict =\n !isWebchat && payload.channel !== 'cli'\n ? async (text: string) => {\n await this.bus.publishOutbound({\n channel: payload.channel,\n chat_id: payload.chatId,\n content: text,\n type: 'message',\n metadata: {\n accountId: payload.outboundMetadata?.accountId,\n threadId: payload.outboundMetadata?.threadId,\n },\n });\n }\n : undefined;\n\n await handlePersistentGoalPostTurn({\n apis,\n sessionKey: payload.sessionKey,\n assistantPlainText: payload.assistantPlainText,\n aborted: payload.aborted,\n ...(payload.streamError !== undefined ? { streamError: payload.streamError } : {}),\n skipPersistentGoalPostTurn: payload.skipPersistentGoalPostTurn ?? false,\n config: this.effectiveAppConfig(),\n publishVerdictToChannel: publishVerdict,\n });\n }\n\n async start(): Promise<void> {\n this.running = true;\n await this.sessionConfigStore.initialize();\n await this.hookHandler.trigger('gateway_start', { port: 0, host: 'cli' });\n await this.reconcileDreamingCronJob().catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn({ err, errorMessage: em }, `Dreaming cron reconcile failed: ${em}`);\n });\n log.debug('Agent service started');\n await this.hookHandler.trigger('session_start', { sessionId: this.agentId });\n\n while (this.running) {\n try {\n const msg = await this.bus.consumeInbound();\n await this.handleInboundMessage(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: 'inbound_consume' },\n `Agent loop failed (will retry in 1s): ${em}`,\n );\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n\n await this.hookHandler.trigger('session_end', {\n sessionId: this.agentId,\n messageCount: 0, // No longer tracking single agent messages\n });\n }\n\n stop(): Promise<void> {\n this.running = false;\n this.agentManager.dispose();\n this.dispose();\n\n this.hookHandler.trigger('gateway_stop', { reason: 'stopped' });\n log.debug('Agent service stopped');\n return Promise.resolve();\n }\n\n /**\n * Reconcile managed Dreaming cron job against the current effective config.\n * Safe to call after config saves to apply changes without restarting the process.\n */\n async reconcileDreamingNow(): Promise<void> {\n await this.reconcileDreamingCronJob();\n }\n\n private async reconcileDreamingCronJob(): Promise<void> {\n const cron = this.config.getCronService?.();\n if (!cron) {\n return;\n }\n await reconcileManagedDreamingCronJobs(cron, this.effectiveAppConfig());\n }\n\n /**\n * Persist agent messages with the same sanitizer + transcript hygiene as AgentOrchestrator.\n * Uses persistence hygiene so `thinking` blocks remain on disk for the web UI (LLM load path still drops them).\n */\n private async persistAgentSessionMessages(sessionKey: string): Promise<void> {\n const raw = this.agentManager.getMessages(sessionKey);\n if (!raw) {\n return;\n }\n const { messages } = sanitizeMessages(raw);\n let toSave = messages;\n try {\n const model = this.modelManager.getResolvedModelForSession(sessionKey);\n toSave = tryApplySessionTranscriptHygieneForPersistence(messages, model);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Transcript hygiene on save skipped');\n }\n await this.sessionStore.save(sessionKey, toSave);\n this.enqueueMaybeAutoTitleAfterPersist(sessionKey);\n }\n\n /**\n * Fire-and-forget: `maybeAutoTitleSessionStore` no-ops for cron/heartbeat keys.\n * Runs after persist so the store has the latest transcript; does not block SSE / callers.\n */\n private enqueueMaybeAutoTitleAfterPersist(sessionKey: string): void {\n void (async () => {\n try {\n let modelRef =\n getAgentDefaultModelRef(this.config.config ?? ({} as Config)) ?? this.config.model;\n if (!modelRef?.trim()) {\n try {\n modelRef = this.modelManager.getModelForSession(sessionKey);\n } catch {\n modelRef = undefined;\n }\n }\n await maybeAutoTitleSessionStore(this.sessionStore, sessionKey, modelRef?.trim() || undefined);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Auto session title failed');\n }\n })();\n }\n\n private prepareLoadedSessionMessages(sessionKey: string, messages: AgentMessage[]): AgentMessage[] {\n let out = cleanTrailingErrors(messages);\n try {\n const model = this.modelManager.getResolvedModelForSession(sessionKey);\n out = tryApplySessionTranscriptHygiene(out, model);\n } catch (err) {\n log.warn({ err, sessionKey }, 'Transcript hygiene on load skipped');\n }\n return out;\n }\n\n private parseSessionKey(sessionKey: string): { channel: string; chatId: string } {\n return parseOutboundSessionKey(sessionKey, this.config.config);\n }\n\n private initSessionContext(\n sessionKey: string,\n channel: string,\n chatId: string,\n senderId = '',\n ): SessionContext {\n const context: SessionContext = {\n sessionKey,\n channel,\n chatId,\n senderId,\n isGroup: false,\n };\n\n this.contextMiddleware.onRequest({\n sessionKey,\n userId: context.senderId,\n channel,\n chatId,\n });\n\n this.sessionContextManager.setContext(context);\n this.feedbackCoordinator.setContext(context);\n this.agentManager.getOrCreateAgent(sessionKey);\n this.setupSessionEventHandling(sessionKey);\n\n return context;\n }\n\n /**\n * Persist inbound file attachments under agent home `inbound/` (non-images with data).\n * Idempotent if `workspaceRelativePath` is already set on an attachment.\n */\n async prepareInboundAttachments(\n sessionKey: string,\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n }>,\n ): Promise<\n | Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n }>\n | undefined\n > {\n const cfg = this.config.config!;\n const storageRoot = resolveAgentHomeDir(cfg, extractProfileAgentId(sessionKey, cfg));\n return persistInboundAttachmentsToWorkspace(storageRoot, sessionKey, attachments);\n }\n\n private endDirectRequestContext(): void {\n this.sessionContextManager.clearContext();\n this.feedbackCoordinator.clearContext();\n this.contextMiddleware.onResponse();\n }\n\n /** Full session snapshot (metadata + API-shaped messages), e.g. embedded TUI history. */\n async loadSessionDetail(sessionKey: string): Promise<SessionDetail | null> {\n return this.sessionStore.get(sessionKey);\n }\n\n async compactSession(\n sessionKey: string,\n options?: { instructions?: string; force?: boolean },\n ): Promise<CompactionResult> {\n const messages = await this.sessionStore.load(sessionKey);\n const contextWindow = this.getContextWindow();\n const result = await this.sessionStore.compact(\n sessionKey,\n messages,\n contextWindow,\n options?.instructions,\n options?.force ?? true,\n );\n if (result.compacted) {\n await this.sessionStore.save(sessionKey, await this.sessionStore.load(sessionKey));\n this.agentManager.removeAgent(sessionKey);\n }\n log.info({ sessionKey, result }, 'Manual compaction complete');\n return result;\n }\n\n /**\n * Drop in-memory agent so the next turn reloads transcript from disk (e.g. after checkpoint restore).\n */\n evictSessionAgent(sessionKey: string): void {\n this.agentManager.removeAgent(sessionKey);\n }\n\n /**\n * One-shot LLM answer for /btw: uses transcript as background only; does not persist to session.\n */\n async btwQuery(sessionKey: string, question: string): Promise<{ text: string; error?: string }> {\n return runBtwQuery({\n sessionKey,\n question,\n sessionStore: this.sessionStore,\n modelForSession: this.modelManager.getModelForSession(sessionKey),\n log,\n });\n }\n\n /** Markdown or JSON summary for /context (prompt assembly is approximated from config + transcript stats). */\n async getSessionContextReport(\n sessionKey: string,\n mode: 'list' | 'detail' | 'json',\n ): Promise<string> {\n const messages = await this.sessionStore.load(sessionKey);\n const cw = this.getContextWindow();\n const stats = this.getSessionStats(sessionKey, messages);\n const cfg = this.effectiveAppConfig() ?? this.config.config!;\n const model = this.modelManager.getModelForSession(sessionKey);\n const sc = await this.sessionConfigStore.get(sessionKey);\n const workspace = effectiveWorkspacePathForSession(cfg, sessionKey, sc);\n const estTokens = await this.sessionStore.estimateTokenUsage(sessionKey, messages);\n const profile = resolveEffectiveAgentProfileForSession(cfg, sessionKey);\n const defaults = cfg.agents?.defaults;\n const compaction = defaults?.compaction;\n const tools = defaults?.tools;\n\n const toolsSummary =\n tools && typeof tools === 'object'\n ? Object.entries(tools as Record<string, unknown>)\n .filter(([, v]) => v === true)\n .map(([k]) => k)\n .slice(0, 16)\n .join(', ') || '(none explicitly true)'\n : '(see agents.defaults.tools in config)';\n\n return formatSessionContextReport({\n sessionKey,\n mode,\n model,\n workspacePath: workspace,\n agentId: profile.agentId,\n messageCount: messages.length,\n contextWindowNominal: cw,\n estimatedTranscriptTokens: estTokens,\n thinkingDefault: defaults?.thinkingDefault,\n reasoningDefault: defaults?.reasoningDefault,\n verboseDefault: defaults?.verboseDefault,\n compaction,\n toolsFlagsSummary: toolsSummary,\n windowStats: stats.windowStats,\n compactionRunStats: stats.compactionStats,\n });\n }\n\n getSessionStats(sessionKey: string, messages: AgentMessage[]) {\n return {\n windowStats: this.sessionStore.getWindowStats(messages),\n compactionStats: this.sessionStore.getCompactionStats(sessionKey),\n tokenEstimate: this.sessionStore.estimateTokenUsage(sessionKey, messages),\n };\n }\n\n private async applyResolvedThinkingLevel(sessionKey: string, requestOverride?: string | null): Promise<void> {\n const def = this.effectiveAppConfig()?.agents?.defaults?.thinkingDefault;\n const level = await resolveEffectiveThinkingLevel(\n this.sessionConfigStore,\n sessionKey,\n requestOverride,\n def,\n );\n this.agentManager.setThinkingLevel(sessionKey, level);\n }\n\n /** Resolved thinking level and effective model ref for a session (Web UI). */\n async getSessionAgentConfig(sessionKey: string): Promise<{\n thinkingLevel: ThinkingLevel;\n model: string;\n reasoningLevel: ReasoningLevel;\n effectiveWorkspacePath: string;\n workingDirectoryLocked: boolean;\n }> {\n await this.hydrateSessionModelFromStore(sessionKey);\n const cfg = this.effectiveAppConfig()!;\n const sc = await this.sessionConfigStore.get(sessionKey);\n\n // Ensure model display matches the effective agent profile even before an Agent instance exists.\n // Otherwise, `ModelManager.getModelForSession()` falls back to the global default until the first turn creates the agent.\n const profile = resolveEffectiveAgentProfileForSession(cfg, sessionKey);\n const profileModelRef = profile.primaryModelRef?.trim();\n if (profileModelRef) {\n this.modelManager.setSessionProfileDefault(sessionKey, profileModelRef);\n }\n\n const defThink = cfg.agents?.defaults?.thinkingDefault ?? 'medium';\n const level = await resolveEffectiveThinkingLevel(this.sessionConfigStore, sessionKey, null, defThink);\n const defReason = (cfg.agents?.defaults?.reasoningDefault ?? 'stream') as ReasoningLevel;\n const reasoningLevel = await resolveEffectiveReasoningLevel(this.sessionConfigStore, sessionKey, defReason);\n const model = this.modelManager.getModelForSession(sessionKey);\n return {\n thinkingLevel: level,\n model,\n reasoningLevel,\n effectiveWorkspacePath: effectiveWorkspacePathForSession(cfg, sessionKey, sc),\n workingDirectoryLocked: Boolean(sc?.workingDirectoryOverride?.trim()),\n };\n }\n\n /**\n * Load session working directory override into AgentManager, ensure directory exists.\n * Call before AgentManager.getOrCreateAgent for this session.\n */\n async hydrateSessionWorkspaceFromStore(sessionKey: string): Promise<void> {\n const cfg = this.config.config;\n if (!cfg) {\n return;\n }\n const loaded = await this.sessionConfigStore.get(sessionKey);\n if (loaded?.workingDirectoryOverride?.trim()) {\n const wdStored = normalizeWorkingDirectoryInput(loaded.workingDirectoryOverride);\n if (wdStored.ok) {\n this.agentManager.setSessionWorkspaceOverride(sessionKey, wdStored.path);\n } else {\n log.warn({ sessionKey }, 'Invalid stored workingDirectoryOverride; ignoring');\n this.agentManager.setSessionWorkspaceOverride(sessionKey, null);\n }\n } else {\n this.agentManager.setSessionWorkspaceOverride(sessionKey, null);\n }\n const effective = effectiveWorkspacePathForSession(cfg, sessionKey, loaded);\n await mkdir(effective, { recursive: true });\n }\n\n /**\n * Sync persisted session workspace override for an isolated cron run (runs may change when the job is edited).\n * Omit or pass empty `workingDirectory` to use the effective agent default workspace for this session key.\n */\n async applyCronJobWorkingDirectory(sessionKey: string, workingDirectory: string | undefined): Promise<void> {\n const raw = workingDirectory?.trim();\n if (raw) {\n const wdNorm = normalizeWorkingDirectoryInput(raw);\n if (wdNorm.ok === false) {\n log.warn({ sessionKey, error: wdNorm.error }, 'Cron job working directory invalid; using agent default');\n await this.clearCronSessionWorkingDirectoryOverride(sessionKey);\n return;\n }\n await mkdir(wdNorm.path, { recursive: true });\n await this.sessionConfigStore.update(sessionKey, { workingDirectoryOverride: wdNorm.path });\n this.agentManager.setSessionWorkspaceOverride(sessionKey, wdNorm.path);\n return;\n }\n await this.clearCronSessionWorkingDirectoryOverride(sessionKey);\n }\n\n private async clearCronSessionWorkingDirectoryOverride(sessionKey: string): Promise<void> {\n const existing = await this.sessionConfigStore.get(sessionKey);\n if (existing?.workingDirectoryOverride) {\n const { workingDirectoryOverride: _removed, ...rest } = existing;\n await this.sessionConfigStore.set(sessionKey, rest);\n }\n this.agentManager.setSessionWorkspaceOverride(sessionKey, null);\n }\n\n /** Workspace root for UI file tree / editor (same as agent tools after hydration). */\n async getEffectiveWorkspacePathForSession(sessionKey: string): Promise<string> {\n await this.hydrateSessionWorkspaceFromStore(sessionKey);\n const cfg = this.config.config!;\n const sc = await this.sessionConfigStore.get(sessionKey);\n return effectiveWorkspacePathForSession(cfg, sessionKey, sc);\n }\n\n async patchSessionAgentConfig(\n sessionKey: string,\n partial: {\n thinkingLevel?: string;\n model?: string | null;\n reasoningLevel?: string;\n workingDirectory?: string;\n },\n ): Promise<{ ok: boolean; error?: string }> {\n if (partial.model !== undefined) {\n if (partial.model === null || partial.model === '') {\n await this.clearSessionModelOverride(sessionKey);\n } else {\n const ok = await this.modelManager.switchModelForSession(sessionKey, partial.model);\n if (!ok) {\n return { ok: false, error: 'Invalid model' };\n }\n await this.sessionConfigStore.update(sessionKey, { modelOverride: partial.model });\n this.agentManager.setModelForSession(sessionKey, partial.model);\n }\n }\n\n if (partial.thinkingLevel !== undefined) {\n const normalized = normalizeThinkLevel(partial.thinkingLevel);\n if (!normalized) {\n return { ok: false, error: 'Invalid thinking level' };\n }\n await this.sessionConfigStore.update(sessionKey, { thinkingLevel: normalized });\n this.agentManager.setThinkingLevel(sessionKey, normalized as ThinkingLevel);\n }\n\n if (partial.reasoningLevel !== undefined) {\n const normalized = normalizeReasoningLevel(partial.reasoningLevel);\n if (!normalized) {\n return { ok: false, error: 'Invalid reasoning level' };\n }\n await this.sessionConfigStore.update(sessionKey, { reasoningLevel: normalized });\n }\n\n if (partial.workingDirectory !== undefined) {\n const cfg = this.config.config;\n if (!cfg) {\n return { ok: false, error: 'Config not loaded' };\n }\n const existing = await this.sessionConfigStore.get(sessionKey);\n const existingRaw = existing?.workingDirectoryOverride?.trim();\n const incoming = partial.workingDirectory.trim();\n\n const priorMessages = await this.sessionStore.load(sessionKey);\n\n if (priorMessages.length > 0) {\n if (!incoming) {\n return { ok: false, error: 'workingDirectory is empty' };\n }\n if (!existingRaw) {\n return {\n ok: false,\n error: 'Working directory can only be set before the first message in this conversation',\n };\n }\n const prev = normalizeWorkingDirectoryInput(existingRaw);\n const next = normalizeWorkingDirectoryInput(incoming);\n if (prev.ok && next.ok && prev.path === next.path) {\n /* idempotent */\n } else {\n return { ok: false, error: 'Working directory is already set for this session' };\n }\n } else {\n if (!incoming) {\n return { ok: false, error: 'workingDirectory is empty' };\n }\n const wdNorm = normalizeWorkingDirectoryInput(incoming);\n switch (wdNorm.ok) {\n case true:\n if (existingRaw) {\n const prev = normalizeWorkingDirectoryInput(existingRaw);\n if (prev.ok && prev.path === wdNorm.path) {\n break;\n }\n }\n await mkdir(wdNorm.path, { recursive: true });\n await this.sessionConfigStore.update(sessionKey, { workingDirectoryOverride: wdNorm.path });\n this.agentManager.setSessionWorkspaceOverride(sessionKey, wdNorm.path);\n this.agentManager.removeAgent(sessionKey);\n break;\n case false:\n return { ok: false, error: wdNorm.error };\n default:\n return { ok: false, error: 'Invalid working directory' };\n }\n }\n }\n\n return { ok: true };\n }\n\n async *processDirectStreaming(\n content: string,\n sessionKey = 'cli:direct',\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n }>,\n thinking?: string,\n options?: { signal?: AbortSignal },\n ): AsyncGenerator<{ type: string; [key: string]: unknown }, void, unknown> {\n yield* runProcessDirectStreaming(this.createProcessDirectStreamingDeps(), {\n content,\n sessionKey,\n attachments,\n thinking,\n signal: options?.signal,\n });\n }\n\n /**\n * Best-effort timezone resolution for webchat envelope timestamps.\n * Reads `USER.md` in the resolved workspace and extracts a `Timezone:` line.\n */\n resolveUserTimezoneForSession(sessionKey: string): string | undefined {\n try {\n const workspace = this.agentManager.getResolvedWorkspaceForSession(sessionKey);\n const userPath = join(workspace, 'USER.md');\n if (!existsSync(userPath)) return undefined;\n const raw = readFileSync(userPath, 'utf-8');\n const match = raw.match(/Timezone:\\s*(.+)/i);\n const tz = match?.[1]?.trim();\n return tz || undefined;\n } catch {\n return undefined;\n }\n }\n\n private createProcessDirectStreamingDeps(): ProcessDirectStreamingDeps {\n return {\n log,\n parseSessionKey: (sk) => this.parseSessionKey(sk),\n initDirectStreamingSession: (sk, channel, chatId) => this.initSessionContext(sk, channel, chatId),\n registerWebchatSsePublisher: (sk, publisher) => {\n this.webchatSseEnqueueBySession.set(sk, publisher);\n },\n unregisterWebchatSsePublisher: (sk) => {\n this.webchatSseEnqueueBySession.delete(sk);\n },\n agentManager: this.agentManager,\n hydrateSessionWorkspaceFromStore: (sk) => this.hydrateSessionWorkspaceFromStore(sk),\n hydrateSessionModelFromStore: (sk) => this.hydrateSessionModelFromStore(sk),\n agentEventHandler: this.agentEventHandler,\n sessionStore: this.sessionStore,\n prepareLoadedSessionMessages: (sk, msgs) => this.prepareLoadedSessionMessages(sk, msgs),\n modelManager: this.modelManager,\n applyResolvedThinkingLevel: (sk, t) => this.applyResolvedThinkingLevel(sk, t),\n getConfig: () => this.effectiveAppConfig(),\n sessionConfigStore: this.sessionConfigStore,\n attachmentRootsForSession: (sk) => this.attachmentRootsForSession(sk),\n agentOrchestrator: this.agentOrchestrator,\n commandHandler: this.commandHandler,\n prepareInboundAttachments: (sk, att) => this.prepareInboundAttachments(sk, att),\n buildMessageContent: (text, prepared, sk) =>\n buildDirectUserMessageContent({\n content: text,\n attachments: prepared,\n sessionKey: sk,\n config: this.config.config!,\n agentManager: this.agentManager,\n modelManager: this.modelManager,\n }),\n persistAgentSessionMessages: (sk) => this.persistAgentSessionMessages(sk),\n recordPersistentGoalStreamOutcome: (sk, o) => this.recordPersistentGoalStreamOutcome(sk, o),\n maybeEmitWebchatTts: (sk, hadVoice) =>\n maybeEmitWebchatTts(\n {\n config: this.config.config,\n agentManager: this.agentManager,\n sessionStore: this.sessionStore,\n log,\n },\n sk,\n hadVoice,\n ),\n endDirectRequestContext: () => this.endDirectRequestContext(),\n };\n }\n\n /**\n * Inject an SSE event into an in-flight webchat stream (same queue as tokens/tools).\n */\n enqueueWebchatSseEvent(sessionKey: string, event: { type: string; [key: string]: unknown }): void {\n const pub = this.webchatSseEnqueueBySession.get(sessionKey);\n if (pub) {\n pub(event);\n }\n }\n\n /**\n * Queue a steering user message into pi-agent's in-flight run (delivered after current tool work, before the next LLM call).\n * See `Agent.steer` in `@earendil-works/pi-agent-core`.\n */\n async steerWebchatSession(sessionKey: string, text: string): Promise<boolean> {\n const trimmed = text.trim();\n if (!trimmed) return false;\n try {\n await this.hydrateSessionWorkspaceFromStore(sessionKey);\n const agent = this.agentManager.getOrCreateAgent(sessionKey);\n const msg: AgentMessage = {\n role: 'user',\n content: [{ type: 'text', text: trimmed }],\n timestamp: Date.now(),\n };\n agent.steer(msg);\n return true;\n } catch (err) {\n log.warn({ err, sessionKey }, 'steerWebchatSession failed');\n return false;\n }\n }\n\n private createRunProcessDirectDeps(): RunProcessDirectDeps {\n const cfg = this.config.config;\n if (!cfg) {\n throw new Error('AgentService requires config.config');\n }\n return {\n log,\n config: cfg,\n parseSessionKey: (sk) => this.parseSessionKey(sk),\n initSessionContext: (sk, channel, chatId) => {\n void this.initSessionContext(sk, channel, chatId);\n },\n hydrateSessionWorkspaceFromStore: (sk) => this.hydrateSessionWorkspaceFromStore(sk),\n hydrateSessionModelFromStore: (sk) => this.hydrateSessionModelFromStore(sk),\n agentManager: this.agentManager,\n sessionStore: this.sessionStore,\n prepareLoadedSessionMessages: (sk, msgs) => this.prepareLoadedSessionMessages(sk, msgs),\n modelManager: this.modelManager,\n applyResolvedThinkingLevel: (sk, t) => this.applyResolvedThinkingLevel(sk, t),\n prepareInboundAttachments: (sk, att) => this.prepareInboundAttachments(sk, att),\n commandHandler: this.commandHandler,\n persistAgentSessionMessages: (sk) => this.persistAgentSessionMessages(sk),\n endDirectRequestContext: () => this.endDirectRequestContext(),\n };\n }\n\n async processDirect(\n content: string,\n sessionKey = 'cli:direct',\n attachments?: Array<{\n type: string;\n mimeType?: string;\n data?: string;\n name?: string;\n size?: number;\n workspaceRelativePath?: string;\n }>,\n thinking?: string,\n ): Promise<string> {\n return runProcessDirect(this.createRunProcessDirectDeps(), {\n content,\n sessionKey,\n attachments,\n thinking,\n });\n }\n\n private async handleInboundMessage(msg: InboundMessage): Promise<void> {\n const requestId = inboundMessageLogRequestId(msg);\n\n await runWithLogContext({ requestId }, async () => {\n const routing = await this.messageRouter.routeMessage(msg);\n const { context, isCommand, command, commandArgs } = routing;\n\n const sessionContext: SessionContext = {\n sessionKey: context.sessionKey,\n channel: context.channel,\n chatId: context.chatId,\n senderId: context.senderId || '',\n isGroup: context.isGroup || false,\n metadata: {\n transcribedVoice: msg.metadata?.transcribedVoice === true,\n },\n };\n\n updateAsyncLogContext({ sessionId: sessionContext.sessionKey });\n\n this.sessionContextManager.setContext(sessionContext);\n this.feedbackCoordinator.setContext(sessionContext);\n\n // `subscribeToSession` requires an Agent instance; without this the first inbound never\n // registers `message_update` streaming (second turn behaved differently).\n this.agentManager.getOrCreateAgent(sessionContext.sessionKey);\n\n // Setup event handling for this session\n this.setupSessionEventHandling(sessionContext.sessionKey);\n\n await this.sessionLifecycleManager.startSession(sessionContext);\n\n /** Declared on the function so `finally` can clear typing after outbound (TTS + send). */\n let typingController: TypingController | null = null;\n let inboundTurnArmed = false;\n let busProcessFailed: string | undefined;\n\n try {\n if (msg.channel === 'system') {\n await this.handleSystemMessage(msg, sessionContext);\n return;\n }\n\n if (isCommand && command) {\n const handled = await this.commandHandler.executeCommand(command, commandArgs || '', {\n sessionKey: sessionContext.sessionKey,\n channel: sessionContext.channel,\n chatId: sessionContext.chatId,\n senderId: sessionContext.senderId,\n isGroup: sessionContext.isGroup,\n inboundMetadata: msg.metadata,\n });\n\n if (handled) {\n return;\n }\n }\n\n // Start continuous typing indicator (renews every 5 seconds)\n if (msg.channel !== 'cli') {\n typingController = createTypingController({\n intervalSeconds: 5,\n onStart: async () => {\n await this.bus.publishOutbound({\n channel: msg.channel,\n chat_id: msg.chat_id,\n content: '',\n type: 'typing_on',\n metadata: {\n accountId: msg.metadata?.accountId,\n threadId: msg.metadata?.threadId,\n sessionWebhook: msg.metadata?.sessionWebhook,\n conversationId: msg.metadata?.conversationId,\n },\n });\n },\n onStop: async () => {\n await this.bus.publishOutbound({\n channel: msg.channel,\n chat_id: msg.chat_id,\n content: '',\n type: 'typing_off',\n metadata: {\n accountId: msg.metadata?.accountId,\n threadId: msg.metadata?.threadId,\n sessionWebhook: msg.metadata?.sessionWebhook,\n conversationId: msg.metadata?.conversationId,\n },\n });\n },\n });\n typingController.start();\n }\n\n if (this.channelManagerRef && msg.channel !== 'cli') {\n const meta = msg.metadata as Record<string, unknown> | undefined;\n const streamHandle = this.channelManagerRef.startStream(\n msg.channel,\n msg.chat_id,\n meta?.accountId as string | undefined,\n {\n threadId: meta?.threadId as string | undefined,\n replyToMessageId: meta?.messageId as string | undefined,\n },\n );\n\n if (streamHandle) {\n this.setStreamHandle(streamHandle as StreamHandle);\n }\n }\n\n this.beginInboundTurn(sessionContext.sessionKey);\n inboundTurnArmed = true;\n try {\n await this.agentOrchestrator.process(msg, sessionContext);\n } catch (procErr) {\n busProcessFailed = procErr instanceof Error ? procErr.message : String(procErr);\n throw procErr;\n }\n } finally {\n await this.sessionLifecycleManager.endSession(sessionContext);\n await this.streamManager.end();\n try {\n await this.sendFinalResponse(msg, sessionContext);\n } finally {\n // After outbound (incl. TTS); previously we cleared typing right after LLM finished, so Weixin showed typing_off before the message.\n await typingController?.stop();\n }\n if (inboundTurnArmed) {\n const meta = msg.metadata as Record<string, unknown> | undefined;\n const assistantPlainText = this.getLastAssistantPlainText(sessionContext.sessionKey) ?? '';\n try {\n await this.emitSessionTurnComplete({\n sessionKey: sessionContext.sessionKey,\n channel: sessionContext.channel,\n chatId: sessionContext.chatId,\n inboundUserText: msg.content,\n assistantPlainText,\n aborted: false,\n ...(busProcessFailed !== undefined ? { streamError: busProcessFailed } : {}),\n skipPersistentGoalPostTurn: false,\n outboundMetadata: {\n accountId: meta?.accountId,\n threadId: meta?.threadId,\n },\n });\n } catch (turnErr) {\n const em = turnErr instanceof Error ? turnErr.message : String(turnErr);\n log.warn(\n { err: turnErr, sessionKey: sessionContext.sessionKey },\n `Session turn complete failed: ${em}`,\n );\n }\n this.endInboundTurn(sessionContext.sessionKey);\n }\n this.feedbackCoordinator.endTask();\n this.sessionContextManager.clearContext();\n this.feedbackCoordinator.clearContext();\n }\n });\n }\n\n private async handleSystemMessage(msg: InboundMessage, context: SessionContext): Promise<void> {\n log.debug({ sessionKey: context.sessionKey }, 'Processing system message');\n\n await this.hydrateSessionWorkspaceFromStore(context.sessionKey);\n\n // Get or create agent for this session\n const agent = this.agentManager.getOrCreateAgent(context.sessionKey);\n\n const messages = await this.sessionStore.load(context.sessionKey);\n await this.checkAndCompact(context.sessionKey, messages);\n const refreshedMessages = await this.sessionStore.load(context.sessionKey);\n agent.state.messages = this.prepareLoadedSessionMessages(context.sessionKey, refreshedMessages);\n\n const systemMessage: AgentMessage = {\n role: 'user',\n content: [{ type: 'text', text: `[System: ${msg.sender_id}] ${msg.content}` }],\n timestamp: Date.now(),\n };\n\n try {\n await agent.prompt(systemMessage);\n await agent.waitForIdle();\n\n const finalContent = this.agentManager.getLastAssistantContent(context.sessionKey);\n if (finalContent) {\n const hookResult = await this.hookHandler.runMessageSending(\n context.chatId,\n finalContent,\n context.channel,\n );\n if (hookResult.send) {\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: hookResult.content || finalContent,\n type: 'message',\n });\n }\n }\n\n await this.persistAgentSessionMessages(context.sessionKey);\n } catch (error) {\n const em = error instanceof Error ? error.message : String(error);\n log.error(\n {\n err: error,\n errorMessage: em,\n sessionKey: context.sessionKey,\n channel: context.channel,\n chatId: context.chatId,\n senderId: msg.sender_id,\n },\n `System message handling failed: ${em}`,\n );\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: '❌ An error occurred while processing the system message.',\n type: 'message',\n });\n }\n }\n\n /**\n * Setup event handling for a specific session\n */\n private setupSessionEventHandling(sessionKey: string): void {\n // If already subscribed, skip\n if (this.sessionUnsubscribers.has(sessionKey)) {\n return;\n }\n\n const unsubscribe = this.agentManager.subscribeToSession(sessionKey, (event) => {\n this.handleSessionEvent(sessionKey, event);\n });\n\n if (unsubscribe) {\n this.sessionUnsubscribers.set(sessionKey, unsubscribe);\n }\n }\n\n /**\n * Handle events from a specific session's agent\n */\n private handleSessionEvent(sessionKey: string, event: AgentEvent): void {\n const currentContext = this.sessionContextManager.getContext();\n if (!currentContext) {\n // Inbound `finally` clears context before trailing agent `message_update` events finish — ignore (not a bug).\n return;\n }\n\n if (currentContext.sessionKey !== sessionKey) {\n // Event from a different session — still process with current context where applicable\n this.agentEventHandler.handle(event, currentContext);\n return;\n }\n\n // Handle streaming updates for the current session\n if (event.type === 'message_update') {\n const msgEvent = event as Extract<AgentEvent, { type: 'message_update' }>;\n if (msgEvent.message?.role === 'assistant') {\n const content = msgEvent.message.content;\n const text = Array.isArray(content)\n ? extractTextContent(content as Array<{ type: string; text?: string }>)\n : String(content);\n\n this.streamManager.update(text);\n }\n }\n\n this.agentEventHandler.handle(event, currentContext);\n }\n\n private async checkAndCompact(sessionKey: string, messages: AgentMessage[]): Promise<void> {\n const contextWindow = this.getContextWindow();\n const prep = this.sessionStore.prepareCompaction(sessionKey, messages, contextWindow);\n if (!prep.needsCompaction) return;\n\n log.info({ sessionKey, reason: prep.stats?.reason, usagePercent: prep.stats?.usagePercent }, 'Session needs compaction');\n\n const result = await this.sessionStore.compact(sessionKey, messages, contextWindow, undefined, false);\n await this.hookHandler.trigger('after_compaction', {\n messageCount: messages.length,\n tokenCount: result.tokensBefore,\n compactedCount: messages.length - result.firstKeptIndex,\n });\n log.info({ sessionKey, tokensBefore: result.tokensBefore, tokensAfter: result.tokensAfter }, 'Session compacted');\n }\n\n private getContextWindow(): number {\n const defaults = this.config.agentDefaults || this.config.config?.agents?.defaults;\n return defaults?.maxTokens ? defaults.maxTokens * 4 : 128000;\n }\n\n private async sendFinalResponse(\n msg: InboundMessage,\n sessionContext: SessionContext\n ): Promise<void> {\n if (this.streamManager.consumeSkipFinalOutbound()) {\n return;\n }\n\n const finalContent = this.agentManager.getLastAssistantContent(sessionContext.sessionKey);\n if (!finalContent?.trim()) return;\n\n const ackMax =\n this.config.config?.gateway?.heartbeat?.ackMaxChars ?? DEFAULT_ACK_MAX_CHARS;\n if (shouldSilence(finalContent, ackMax) || finalContent.trim() === NO_REPLY) {\n log.debug(\n { sessionKey: sessionContext.sessionKey },\n 'Silent reply — skipping outbound',\n );\n return;\n }\n\n const hookResult = await this.hookHandler.runMessageSending(\n sessionContext.chatId,\n finalContent,\n sessionContext.channel,\n );\n if (!hookResult.send) return;\n\n // TTS is handled by ChannelManager, just send text message here\n await this.bus.publishOutbound({\n channel: sessionContext.channel,\n chat_id: sessionContext.chatId,\n content: hookResult.content || finalContent,\n type: 'message',\n metadata: {\n accountId: msg.metadata?.accountId,\n threadId: msg.metadata?.threadId,\n transcribedVoice: sessionContext.metadata?.transcribedVoice,\n sessionWebhook: msg.metadata?.sessionWebhook,\n conversationId: msg.metadata?.conversationId,\n },\n });\n }\n\n /** Extension hooks for ChannelManager outbound pipeline (Gateway). */\n async invokeOutboundMessageSending(\n to: string,\n content: string,\n channel: string,\n ): Promise<{ send: boolean; content?: string; reason?: string }> {\n return this.hookHandler.runMessageSending(to, content, channel);\n }\n\n async invokeOutboundMessageSent(\n to: string,\n content: string,\n success: boolean,\n error: string | undefined,\n channel: string,\n ): Promise<void> {\n return this.hookHandler.runMessageSent(to, content, success, error, channel);\n }\n\n private dispose(): void {\n this.sessionTracker.dispose();\n\n // Unsubscribe from all session agents\n for (const unsubscribe of this.sessionUnsubscribers.values()) {\n unsubscribe();\n }\n this.sessionUnsubscribers.clear();\n\n // Dispose all agent instances\n this.agentManager.dispose();\n }\n}\n"],"mappings":"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"}
|
|
@@ -23,7 +23,7 @@ async function startDingtalkSetupPolling(sessionKey, service) {
|
|
|
23
23
|
const raw = readFileSync(configPath, "utf8");
|
|
24
24
|
const config = JSON.parse(raw);
|
|
25
25
|
const existing = config.channels?.dingtalk ?? {};
|
|
26
|
-
const dmPolicy = typeof existing.dmPolicy === "string" && existing.dmPolicy.trim() ? existing.dmPolicy : "
|
|
26
|
+
const dmPolicy = typeof existing.dmPolicy === "string" && existing.dmPolicy.trim() ? existing.dmPolicy : "open";
|
|
27
27
|
const allowFrom = Array.isArray(existing.allowFrom) ? existing.allowFrom : [];
|
|
28
28
|
config.channels = {
|
|
29
29
|
...config.channels,
|
|
@@ -64,7 +64,7 @@ async function startFeishuSetupPolling(sessionKey, service) {
|
|
|
64
64
|
const raw = readFileSync(configPath, "utf8");
|
|
65
65
|
const config = JSON.parse(raw);
|
|
66
66
|
const existingFeishu = config.channels?.feishu ?? {};
|
|
67
|
-
const dmPolicy = typeof existingFeishu.dmPolicy === "string" && existingFeishu.dmPolicy.trim() ? existingFeishu.dmPolicy : "
|
|
67
|
+
const dmPolicy = typeof existingFeishu.dmPolicy === "string" && existingFeishu.dmPolicy.trim() ? existingFeishu.dmPolicy : "open";
|
|
68
68
|
const preseedOpenId = outcome.result.openId?.trim();
|
|
69
69
|
const allowFrom = mergeDistinctSenderIds(existingFeishu.allowFrom, preseedOpenId ? [preseedOpenId] : []);
|
|
70
70
|
config.channels = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channels.js","names":[],"sources":["../../../../../src/gateway/hono/routes/channels.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\n\nimport type { Hono } from 'hono';\n\nimport { mergeDistinctSenderIds } from '../../../channels/pairing/index.js';\nimport { writeTextAtomic } from '../../../infra/write-file-atomic.js';\nimport type { GatewayService } from '../../service.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\ntype FeishuSetupDomain = 'feishu' | 'lark';\n\ninterface FeishuSetupSession {\n deviceCode: string;\n domain: FeishuSetupDomain;\n intervalSec: number;\n expireInSec: number;\n createdAt: number;\n phase: 'idle' | 'polling' | 'scanned' | 'done' | 'error';\n result?: {\n appId: string;\n appSecret: string;\n domain: FeishuSetupDomain;\n openId?: string;\n };\n error?: string;\n}\n\nconst feishuSetupSessions = new Map<string, FeishuSetupSession>();\n\ninterface DingtalkSetupSession {\n deviceCode: string;\n intervalSec: number;\n expireInSec: number;\n createdAt: number;\n phase: 'idle' | 'polling' | 'done' | 'error';\n result?: { clientId: string; clientSecret: string };\n error?: string;\n}\n\nconst dingtalkSetupSessions = new Map<string, DingtalkSetupSession>();\n\nasync function startDingtalkSetupPolling(sessionKey: string, service: GatewayService): Promise<void> {\n const session = dingtalkSetupSessions.get(sessionKey);\n if (!session) return;\n\n const { waitForDingtalkRegistrationSuccess } = await import(\n '../../../../extensions/dingtalk/src/auth/device-auth.js'\n );\n\n session.phase = 'polling';\n\n try {\n const creds = await waitForDingtalkRegistrationSuccess({\n deviceCode: session.deviceCode,\n intervalSeconds: session.intervalSec,\n expiresInSeconds: session.expireInSec,\n });\n session.phase = 'done';\n session.result = creds;\n try {\n const configPath = service.getHealth().configPath;\n const raw = readFileSync(configPath, 'utf8');\n const config = JSON.parse(raw) as {\n channels?: Record<string, unknown>;\n };\n const existing = (config.channels?.dingtalk ?? {}) as Record<string, unknown>;\n const dmPolicy =\n typeof existing.dmPolicy === 'string' && existing.dmPolicy.trim() ? existing.dmPolicy : 'pairing';\n const allowFrom = Array.isArray(existing.allowFrom) ? existing.allowFrom : [];\n\n config.channels = {\n ...config.channels,\n dingtalk: {\n ...existing,\n enabled: true,\n clientId: creds.clientId,\n clientSecret: creds.clientSecret,\n dmPolicy,\n allowFrom,\n },\n };\n\n await writeTextAtomic(configPath, `${JSON.stringify(config, null, 2)}\\n`);\n await service.afterDingtalkCredentialsPersisted();\n } catch {\n /* non-blocking */\n }\n } catch (err) {\n session.phase = 'error';\n session.error = err instanceof Error ? err.message : String(err);\n }\n\n setTimeout(() => dingtalkSetupSessions.delete(sessionKey), 30_000);\n}\n\nasync function startFeishuSetupPolling(sessionKey: string, service: GatewayService): Promise<void> {\n const session = feishuSetupSessions.get(sessionKey);\n if (!session) return;\n\n const { pollAppRegistration } = await import(\n '../../../../extensions/feishu/src/auth/app-registration.js'\n );\n\n session.phase = 'polling';\n\n const outcome = await pollAppRegistration({\n deviceCode: session.deviceCode,\n intervalSec: session.intervalSec,\n expireInSec: session.expireInSec,\n initialDomain: session.domain,\n });\n\n if (outcome.status === 'success') {\n session.phase = 'done';\n session.result = outcome.result;\n try {\n const configPath = service.getHealth().configPath;\n const raw = readFileSync(configPath, 'utf8');\n const config = JSON.parse(raw) as {\n channels?: Record<string, unknown>;\n };\n const existingFeishu = (config.channels?.feishu ?? {}) as Record<string, unknown>;\n const dmPolicy =\n typeof existingFeishu.dmPolicy === 'string' && existingFeishu.dmPolicy.trim()\n ? existingFeishu.dmPolicy\n : 'pairing';\n const preseedOpenId = outcome.result.openId?.trim();\n const allowFrom = mergeDistinctSenderIds(existingFeishu.allowFrom, preseedOpenId ? [preseedOpenId] : []);\n\n config.channels = {\n ...config.channels,\n feishu: {\n ...existingFeishu,\n enabled: true,\n appId: outcome.result.appId,\n appSecret: outcome.result.appSecret,\n domain: outcome.result.domain,\n connectionMode: (existingFeishu.connectionMode as string) || 'websocket',\n dmPolicy,\n allowFrom,\n },\n };\n\n await writeTextAtomic(configPath, `${JSON.stringify(config, null, 2)}\\n`);\n await service.afterFeishuCredentialsPersisted();\n } catch {\n // Config write / reload failure is non-blocking; session still carries credentials for debugging.\n }\n } else {\n session.phase = 'error';\n session.error =\n outcome.status === 'access_denied'\n ? 'User denied authorization.'\n : outcome.status === 'expired'\n ? 'Session expired.'\n : outcome.status === 'timeout'\n ? 'Scan timed out.'\n : 'message' in outcome\n ? outcome.message\n : 'Unknown error.';\n }\n\n setTimeout(() => feishuSetupSessions.delete(sessionKey), 30_000);\n}\n\nexport function registerChannelRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware } = deps;\n\n authenticated.get('/api/channels/status', (c) => {\n const channels = service.getChannelsStatus();\n return c.json({ ok: true, payload: { channels } });\n });\n\n authenticated.post('/api/channels/weixin/login/start', strictRateLimitMiddleware, async (c) => {\n const body = await c.req.json().catch(() => ({}));\n const account =\n body && typeof body === 'object' && typeof (body as { account?: unknown }).account === 'string'\n ? (body as { account: string }).account.trim() || undefined\n : undefined;\n const rawTimeout =\n body && typeof body === 'object' ? (body as { timeoutMs?: unknown }).timeoutMs : undefined;\n const timeoutMs =\n typeof rawTimeout === 'number' && Number.isFinite(rawTimeout) ? Math.max(60_000, rawTimeout) : undefined;\n\n const { startWeixinGatewayQrLogin } = await import('../../../channels/weixin/index.js');\n const result = await startWeixinGatewayQrLogin({\n configPath: service.getHealth().configPath,\n account,\n timeoutMs,\n onPersisted: async (r) => {\n if (r.ok) {\n await service.afterWeixinCredentialsPersisted();\n }\n },\n });\n\n if (result.ok === false) {\n return c.json(\n { ok: false, error: { code: 'WEIXIN_LOGIN_FAILED', message: result.message } },\n 400,\n );\n }\n return c.json({\n ok: true,\n payload: { sessionKey: result.sessionKey, qrcodeUrl: result.qrcodeUrl },\n });\n });\n\n authenticated.get('/api/channels/weixin/login/:sessionKey', async (c) => {\n const sessionKey = c.req.param('sessionKey')?.trim() ?? '';\n if (!sessionKey) {\n return c.json({ ok: false, error: { code: 'BAD_REQUEST', message: 'Missing sessionKey' } }, 400);\n }\n const { getWeixinGatewayQrLoginStatus } = await import('../../../channels/weixin/index.js');\n const status = getWeixinGatewayQrLoginStatus(sessionKey);\n return c.json({ ok: true, payload: { status } });\n });\n\n authenticated.post('/api/channels/feishu/setup/start', strictRateLimitMiddleware, async (c) => {\n const body = await c.req.json().catch(() => ({}));\n const rawDomain =\n body && typeof body === 'object' && typeof (body as { domain?: unknown }).domain === 'string'\n ? (body as { domain: string }).domain.trim().toLowerCase()\n : '';\n const domain: FeishuSetupDomain = rawDomain === 'lark' ? 'lark' : 'feishu';\n\n const { initAppRegistration, beginAppRegistration } = await import(\n '../../../../extensions/feishu/src/auth/app-registration.js'\n );\n\n const supported = await initAppRegistration(domain);\n if (!supported) {\n return c.json(\n {\n ok: false,\n error: { code: 'FEISHU_SCAN_NOT_SUPPORTED', message: 'Scan-to-create is not available.' },\n },\n 400,\n );\n }\n\n const begin = await beginAppRegistration(domain);\n\n const sessionKey = `feishu-setup-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n\n feishuSetupSessions.set(sessionKey, {\n deviceCode: begin.deviceCode,\n domain,\n intervalSec: begin.intervalSec,\n expireInSec: begin.expireInSec,\n createdAt: Date.now(),\n phase: 'idle',\n });\n\n void startFeishuSetupPolling(sessionKey, service);\n\n return c.json({\n ok: true,\n payload: { sessionKey, qrUrl: begin.qrUrl },\n });\n });\n\n authenticated.get('/api/channels/feishu/setup/:sessionKey', async (c) => {\n const sessionKey = c.req.param('sessionKey')?.trim() ?? '';\n if (!sessionKey) {\n return c.json({ ok: false, error: { code: 'BAD_REQUEST', message: 'Missing sessionKey' } }, 400);\n }\n\n const session = feishuSetupSessions.get(sessionKey);\n if (!session) {\n return c.json({\n ok: true,\n payload: {\n status: { phase: 'unknown' as const, message: 'Session not found or expired.' },\n },\n });\n }\n\n if (session.phase === 'done' && session.result) {\n return c.json({\n ok: true,\n payload: {\n status: {\n phase: 'done' as const,\n ok: true,\n appId: session.result.appId,\n domain: session.result.domain,\n openId: session.result.openId,\n },\n },\n });\n }\n\n if (session.phase === 'error') {\n return c.json({\n ok: true,\n payload: {\n status: {\n phase: 'done' as const,\n ok: false,\n message: session.error ?? 'Setup failed.',\n },\n },\n });\n }\n\n return c.json({\n ok: true,\n payload: {\n status: { phase: 'polling' as const },\n },\n });\n });\n\n authenticated.post('/api/channels/dingtalk/setup/start', strictRateLimitMiddleware, async (c) => {\n const { beginDingtalkRegistration } = await import('../../../../extensions/dingtalk/src/auth/device-auth.js');\n\n let begin;\n try {\n begin = await beginDingtalkRegistration();\n } catch (err) {\n const message = err instanceof Error ? err.message : 'DingTalk registration start failed';\n return c.json({ ok: false, error: { code: 'DINGTALK_SETUP_START_FAILED', message } }, 400);\n }\n\n const sessionKey = `dingtalk-setup-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n\n dingtalkSetupSessions.set(sessionKey, {\n deviceCode: begin.deviceCode,\n intervalSec: begin.intervalSeconds,\n expireInSec: begin.expiresInSeconds,\n createdAt: Date.now(),\n phase: 'idle',\n });\n\n void startDingtalkSetupPolling(sessionKey, service);\n\n return c.json({\n ok: true,\n payload: { sessionKey, qrUrl: begin.verificationUriComplete },\n });\n });\n\n authenticated.get('/api/channels/dingtalk/setup/:sessionKey', async (c) => {\n const sessionKey = c.req.param('sessionKey')?.trim() ?? '';\n if (!sessionKey) {\n return c.json({ ok: false, error: { code: 'BAD_REQUEST', message: 'Missing sessionKey' } }, 400);\n }\n\n const session = dingtalkSetupSessions.get(sessionKey);\n if (!session) {\n return c.json({\n ok: true,\n payload: {\n status: { phase: 'unknown' as const, message: 'Session not found or expired.' },\n },\n });\n }\n\n if (session.phase === 'done' && session.result) {\n return c.json({\n ok: true,\n payload: {\n status: {\n phase: 'done' as const,\n ok: true,\n clientId: session.result.clientId,\n },\n },\n });\n }\n\n if (session.phase === 'error') {\n return c.json({\n ok: true,\n payload: {\n status: {\n phase: 'done' as const,\n ok: false,\n message: session.error ?? 'Setup failed.',\n },\n },\n });\n }\n\n return c.json({\n ok: true,\n payload: {\n status: { phase: 'polling' as const },\n },\n });\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"channels.js","names":[],"sources":["../../../../../src/gateway/hono/routes/channels.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\n\nimport type { Hono } from 'hono';\n\nimport { mergeDistinctSenderIds } from '../../../channels/pairing/index.js';\nimport { writeTextAtomic } from '../../../infra/write-file-atomic.js';\nimport type { GatewayService } from '../../service.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\ntype FeishuSetupDomain = 'feishu' | 'lark';\n\ninterface FeishuSetupSession {\n deviceCode: string;\n domain: FeishuSetupDomain;\n intervalSec: number;\n expireInSec: number;\n createdAt: number;\n phase: 'idle' | 'polling' | 'scanned' | 'done' | 'error';\n result?: {\n appId: string;\n appSecret: string;\n domain: FeishuSetupDomain;\n openId?: string;\n };\n error?: string;\n}\n\nconst feishuSetupSessions = new Map<string, FeishuSetupSession>();\n\ninterface DingtalkSetupSession {\n deviceCode: string;\n intervalSec: number;\n expireInSec: number;\n createdAt: number;\n phase: 'idle' | 'polling' | 'done' | 'error';\n result?: { clientId: string; clientSecret: string };\n error?: string;\n}\n\nconst dingtalkSetupSessions = new Map<string, DingtalkSetupSession>();\n\nasync function startDingtalkSetupPolling(sessionKey: string, service: GatewayService): Promise<void> {\n const session = dingtalkSetupSessions.get(sessionKey);\n if (!session) return;\n\n const { waitForDingtalkRegistrationSuccess } = await import(\n '../../../../extensions/dingtalk/src/auth/device-auth.js'\n );\n\n session.phase = 'polling';\n\n try {\n const creds = await waitForDingtalkRegistrationSuccess({\n deviceCode: session.deviceCode,\n intervalSeconds: session.intervalSec,\n expiresInSeconds: session.expireInSec,\n });\n session.phase = 'done';\n session.result = creds;\n try {\n const configPath = service.getHealth().configPath;\n const raw = readFileSync(configPath, 'utf8');\n const config = JSON.parse(raw) as {\n channels?: Record<string, unknown>;\n };\n const existing = (config.channels?.dingtalk ?? {}) as Record<string, unknown>;\n const dmPolicy =\n typeof existing.dmPolicy === 'string' && existing.dmPolicy.trim() ? existing.dmPolicy : 'open';\n const allowFrom = Array.isArray(existing.allowFrom) ? existing.allowFrom : [];\n\n config.channels = {\n ...config.channels,\n dingtalk: {\n ...existing,\n enabled: true,\n clientId: creds.clientId,\n clientSecret: creds.clientSecret,\n dmPolicy,\n allowFrom,\n },\n };\n\n await writeTextAtomic(configPath, `${JSON.stringify(config, null, 2)}\\n`);\n await service.afterDingtalkCredentialsPersisted();\n } catch {\n /* non-blocking */\n }\n } catch (err) {\n session.phase = 'error';\n session.error = err instanceof Error ? err.message : String(err);\n }\n\n setTimeout(() => dingtalkSetupSessions.delete(sessionKey), 30_000);\n}\n\nasync function startFeishuSetupPolling(sessionKey: string, service: GatewayService): Promise<void> {\n const session = feishuSetupSessions.get(sessionKey);\n if (!session) return;\n\n const { pollAppRegistration } = await import(\n '../../../../extensions/feishu/src/auth/app-registration.js'\n );\n\n session.phase = 'polling';\n\n const outcome = await pollAppRegistration({\n deviceCode: session.deviceCode,\n intervalSec: session.intervalSec,\n expireInSec: session.expireInSec,\n initialDomain: session.domain,\n });\n\n if (outcome.status === 'success') {\n session.phase = 'done';\n session.result = outcome.result;\n try {\n const configPath = service.getHealth().configPath;\n const raw = readFileSync(configPath, 'utf8');\n const config = JSON.parse(raw) as {\n channels?: Record<string, unknown>;\n };\n const existingFeishu = (config.channels?.feishu ?? {}) as Record<string, unknown>;\n const dmPolicy =\n typeof existingFeishu.dmPolicy === 'string' && existingFeishu.dmPolicy.trim()\n ? existingFeishu.dmPolicy\n : 'open';\n const preseedOpenId = outcome.result.openId?.trim();\n const allowFrom = mergeDistinctSenderIds(existingFeishu.allowFrom, preseedOpenId ? [preseedOpenId] : []);\n\n config.channels = {\n ...config.channels,\n feishu: {\n ...existingFeishu,\n enabled: true,\n appId: outcome.result.appId,\n appSecret: outcome.result.appSecret,\n domain: outcome.result.domain,\n connectionMode: (existingFeishu.connectionMode as string) || 'websocket',\n dmPolicy,\n allowFrom,\n },\n };\n\n await writeTextAtomic(configPath, `${JSON.stringify(config, null, 2)}\\n`);\n await service.afterFeishuCredentialsPersisted();\n } catch {\n // Config write / reload failure is non-blocking; session still carries credentials for debugging.\n }\n } else {\n session.phase = 'error';\n session.error =\n outcome.status === 'access_denied'\n ? 'User denied authorization.'\n : outcome.status === 'expired'\n ? 'Session expired.'\n : outcome.status === 'timeout'\n ? 'Scan timed out.'\n : 'message' in outcome\n ? outcome.message\n : 'Unknown error.';\n }\n\n setTimeout(() => feishuSetupSessions.delete(sessionKey), 30_000);\n}\n\nexport function registerChannelRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service, strictRateLimitMiddleware } = deps;\n\n authenticated.get('/api/channels/status', (c) => {\n const channels = service.getChannelsStatus();\n return c.json({ ok: true, payload: { channels } });\n });\n\n authenticated.post('/api/channels/weixin/login/start', strictRateLimitMiddleware, async (c) => {\n const body = await c.req.json().catch(() => ({}));\n const account =\n body && typeof body === 'object' && typeof (body as { account?: unknown }).account === 'string'\n ? (body as { account: string }).account.trim() || undefined\n : undefined;\n const rawTimeout =\n body && typeof body === 'object' ? (body as { timeoutMs?: unknown }).timeoutMs : undefined;\n const timeoutMs =\n typeof rawTimeout === 'number' && Number.isFinite(rawTimeout) ? Math.max(60_000, rawTimeout) : undefined;\n\n const { startWeixinGatewayQrLogin } = await import('../../../channels/weixin/index.js');\n const result = await startWeixinGatewayQrLogin({\n configPath: service.getHealth().configPath,\n account,\n timeoutMs,\n onPersisted: async (r) => {\n if (r.ok) {\n await service.afterWeixinCredentialsPersisted();\n }\n },\n });\n\n if (result.ok === false) {\n return c.json(\n { ok: false, error: { code: 'WEIXIN_LOGIN_FAILED', message: result.message } },\n 400,\n );\n }\n return c.json({\n ok: true,\n payload: { sessionKey: result.sessionKey, qrcodeUrl: result.qrcodeUrl },\n });\n });\n\n authenticated.get('/api/channels/weixin/login/:sessionKey', async (c) => {\n const sessionKey = c.req.param('sessionKey')?.trim() ?? '';\n if (!sessionKey) {\n return c.json({ ok: false, error: { code: 'BAD_REQUEST', message: 'Missing sessionKey' } }, 400);\n }\n const { getWeixinGatewayQrLoginStatus } = await import('../../../channels/weixin/index.js');\n const status = getWeixinGatewayQrLoginStatus(sessionKey);\n return c.json({ ok: true, payload: { status } });\n });\n\n authenticated.post('/api/channels/feishu/setup/start', strictRateLimitMiddleware, async (c) => {\n const body = await c.req.json().catch(() => ({}));\n const rawDomain =\n body && typeof body === 'object' && typeof (body as { domain?: unknown }).domain === 'string'\n ? (body as { domain: string }).domain.trim().toLowerCase()\n : '';\n const domain: FeishuSetupDomain = rawDomain === 'lark' ? 'lark' : 'feishu';\n\n const { initAppRegistration, beginAppRegistration } = await import(\n '../../../../extensions/feishu/src/auth/app-registration.js'\n );\n\n const supported = await initAppRegistration(domain);\n if (!supported) {\n return c.json(\n {\n ok: false,\n error: { code: 'FEISHU_SCAN_NOT_SUPPORTED', message: 'Scan-to-create is not available.' },\n },\n 400,\n );\n }\n\n const begin = await beginAppRegistration(domain);\n\n const sessionKey = `feishu-setup-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n\n feishuSetupSessions.set(sessionKey, {\n deviceCode: begin.deviceCode,\n domain,\n intervalSec: begin.intervalSec,\n expireInSec: begin.expireInSec,\n createdAt: Date.now(),\n phase: 'idle',\n });\n\n void startFeishuSetupPolling(sessionKey, service);\n\n return c.json({\n ok: true,\n payload: { sessionKey, qrUrl: begin.qrUrl },\n });\n });\n\n authenticated.get('/api/channels/feishu/setup/:sessionKey', async (c) => {\n const sessionKey = c.req.param('sessionKey')?.trim() ?? '';\n if (!sessionKey) {\n return c.json({ ok: false, error: { code: 'BAD_REQUEST', message: 'Missing sessionKey' } }, 400);\n }\n\n const session = feishuSetupSessions.get(sessionKey);\n if (!session) {\n return c.json({\n ok: true,\n payload: {\n status: { phase: 'unknown' as const, message: 'Session not found or expired.' },\n },\n });\n }\n\n if (session.phase === 'done' && session.result) {\n return c.json({\n ok: true,\n payload: {\n status: {\n phase: 'done' as const,\n ok: true,\n appId: session.result.appId,\n domain: session.result.domain,\n openId: session.result.openId,\n },\n },\n });\n }\n\n if (session.phase === 'error') {\n return c.json({\n ok: true,\n payload: {\n status: {\n phase: 'done' as const,\n ok: false,\n message: session.error ?? 'Setup failed.',\n },\n },\n });\n }\n\n return c.json({\n ok: true,\n payload: {\n status: { phase: 'polling' as const },\n },\n });\n });\n\n authenticated.post('/api/channels/dingtalk/setup/start', strictRateLimitMiddleware, async (c) => {\n const { beginDingtalkRegistration } = await import('../../../../extensions/dingtalk/src/auth/device-auth.js');\n\n let begin;\n try {\n begin = await beginDingtalkRegistration();\n } catch (err) {\n const message = err instanceof Error ? err.message : 'DingTalk registration start failed';\n return c.json({ ok: false, error: { code: 'DINGTALK_SETUP_START_FAILED', message } }, 400);\n }\n\n const sessionKey = `dingtalk-setup-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n\n dingtalkSetupSessions.set(sessionKey, {\n deviceCode: begin.deviceCode,\n intervalSec: begin.intervalSeconds,\n expireInSec: begin.expiresInSeconds,\n createdAt: Date.now(),\n phase: 'idle',\n });\n\n void startDingtalkSetupPolling(sessionKey, service);\n\n return c.json({\n ok: true,\n payload: { sessionKey, qrUrl: begin.verificationUriComplete },\n });\n });\n\n authenticated.get('/api/channels/dingtalk/setup/:sessionKey', async (c) => {\n const sessionKey = c.req.param('sessionKey')?.trim() ?? '';\n if (!sessionKey) {\n return c.json({ ok: false, error: { code: 'BAD_REQUEST', message: 'Missing sessionKey' } }, 400);\n }\n\n const session = dingtalkSetupSessions.get(sessionKey);\n if (!session) {\n return c.json({\n ok: true,\n payload: {\n status: { phase: 'unknown' as const, message: 'Session not found or expired.' },\n },\n });\n }\n\n if (session.phase === 'done' && session.result) {\n return c.json({\n ok: true,\n payload: {\n status: {\n phase: 'done' as const,\n ok: true,\n clientId: session.result.clientId,\n },\n },\n });\n }\n\n if (session.phase === 'error') {\n return c.json({\n ok: true,\n payload: {\n status: {\n phase: 'done' as const,\n ok: false,\n message: session.error ?? 'Setup failed.',\n },\n },\n });\n }\n\n return c.json({\n ok: true,\n payload: {\n status: { phase: 'polling' as const },\n },\n });\n });\n}\n"],"mappings":"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"}
|
|
@@ -426,7 +426,7 @@ function registerConfigRoutes(authenticated, deps) {
|
|
|
426
426
|
if (!config.channels) config.channels = {};
|
|
427
427
|
if (!config.channels.weixin) config.channels.weixin = {
|
|
428
428
|
enabled: false,
|
|
429
|
-
dmPolicy: "
|
|
429
|
+
dmPolicy: "open",
|
|
430
430
|
allowFrom: [],
|
|
431
431
|
debug: false,
|
|
432
432
|
historyLimit: 50,
|
|
@@ -461,7 +461,7 @@ function registerConfigRoutes(authenticated, deps) {
|
|
|
461
461
|
appSecret: "",
|
|
462
462
|
domain: "feishu",
|
|
463
463
|
connectionMode: "websocket",
|
|
464
|
-
dmPolicy: "
|
|
464
|
+
dmPolicy: "open",
|
|
465
465
|
groupPolicy: "allowlist",
|
|
466
466
|
allowFrom: [],
|
|
467
467
|
groupAllowFrom: [],
|
|
@@ -532,7 +532,7 @@ function registerConfigRoutes(authenticated, deps) {
|
|
|
532
532
|
enabled: false,
|
|
533
533
|
clientId: "",
|
|
534
534
|
clientSecret: "",
|
|
535
|
-
dmPolicy: "
|
|
535
|
+
dmPolicy: "open",
|
|
536
536
|
groupPolicy: "open",
|
|
537
537
|
allowFrom: [],
|
|
538
538
|
groupAllowFrom: [],
|