@mariozechner/pi-mom 0.25.3 → 0.25.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Context management for mom.\n *\n * Mom uses two files per channel:\n * - context.jsonl: Structured API messages for LLM context (same format as coding-agent sessions)\n * - log.jsonl: Human-readable channel history for grep (no tool results)\n *\n * This module provides:\n * - MomSessionManager: Adapts coding-agent's SessionManager for channel-based storage\n * - MomSettingsManager: Simple settings for mom (compaction, retry, model preferences)\n */\n\nimport type { AgentState, AppMessage } from \"@mariozechner/pi-agent-core\";\nimport {\n\ttype CompactionEntry,\n\ttype LoadedSession,\n\tloadSessionFromEntries,\n\ttype ModelChangeEntry,\n\ttype SessionEntry,\n\ttype SessionHeader,\n\ttype SessionMessageEntry,\n\ttype ThinkingLevelChangeEntry,\n} from \"@mariozechner/pi-coding-agent\";\nimport { randomBytes } from \"crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\n\nfunction uuidv4(): string {\n\tconst bytes = randomBytes(16);\n\tbytes[6] = (bytes[6] & 0x0f) | 0x40;\n\tbytes[8] = (bytes[8] & 0x3f) | 0x80;\n\tconst hex = bytes.toString(\"hex\");\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\n// ============================================================================\n// MomSessionManager - Channel-based session management\n// ============================================================================\n\n/**\n * Session manager for mom, storing context per Slack channel.\n *\n * Unlike coding-agent which creates timestamped session files, mom uses\n * a single context.jsonl per channel that persists across all @mentions.\n */\nexport class MomSessionManager {\n\tprivate sessionId: string;\n\tprivate contextFile: string;\n\tprivate logFile: string;\n\tprivate channelDir: string;\n\tprivate sessionInitialized: boolean = false;\n\tprivate inMemoryEntries: SessionEntry[] = [];\n\tprivate pendingEntries: SessionEntry[] = [];\n\n\tconstructor(channelDir: string, initialModel?: { provider: string; id: string; thinkingLevel?: string }) {\n\t\tthis.channelDir = channelDir;\n\t\tthis.contextFile = join(channelDir, \"context.jsonl\");\n\t\tthis.logFile = join(channelDir, \"log.jsonl\");\n\n\t\t// Ensure channel directory exists\n\t\tif (!existsSync(channelDir)) {\n\t\t\tmkdirSync(channelDir, { recursive: true });\n\t\t}\n\n\t\t// Load existing session or create new\n\t\tif (existsSync(this.contextFile)) {\n\t\t\tthis.inMemoryEntries = this.loadEntriesFromFile();\n\t\t\tthis.sessionId = this.extractSessionId() || uuidv4();\n\t\t\tthis.sessionInitialized = this.inMemoryEntries.length > 0;\n\t\t} else {\n\t\t\t// New session - write header immediately\n\t\t\tthis.sessionId = uuidv4();\n\t\t\tif (initialModel) {\n\t\t\t\tthis.writeSessionHeader(initialModel);\n\t\t\t}\n\t\t}\n\t\t// Note: syncFromLog() is called explicitly from agent.ts with excludeTimestamp\n\t}\n\n\t/** Write session header to file (called on new session creation) */\n\tprivate writeSessionHeader(model: { provider: string; id: string; thinkingLevel?: string }): void {\n\t\tthis.sessionInitialized = true;\n\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: this.channelDir,\n\t\t\tprovider: model.provider,\n\t\t\tmodelId: model.id,\n\t\t\tthinkingLevel: model.thinkingLevel || \"off\",\n\t\t};\n\n\t\tthis.inMemoryEntries.push(entry);\n\t\tappendFileSync(this.contextFile, JSON.stringify(entry) + \"\\n\");\n\t}\n\n\t/**\n\t * Sync user messages from log.jsonl that aren't in context.jsonl.\n\t *\n\t * log.jsonl and context.jsonl must have the same user messages.\n\t * This handles:\n\t * - Backfilled messages (mom was offline)\n\t * - Messages that arrived while mom was processing a previous turn\n\t * - Channel chatter between @mentions\n\t *\n\t * Channel chatter is formatted as \"[username]: message\" to distinguish from direct @mentions.\n\t *\n\t * Called before each agent run.\n\t *\n\t * @param excludeSlackTs Slack timestamp of current message (will be added via prompt(), not sync)\n\t */\n\tsyncFromLog(excludeSlackTs?: string): void {\n\t\tif (!existsSync(this.logFile)) return;\n\n\t\t// Build set of Slack timestamps already in context\n\t\t// We store slackTs in the message content or can extract from formatted messages\n\t\t// For messages synced from log, we use the log's date as the entry timestamp\n\t\t// For messages added via prompt(), they have different timestamps\n\t\t// So we need to match by content OR by stored slackTs\n\t\tconst contextSlackTimestamps = new Set<string>();\n\t\tconst contextMessageTexts = new Set<string>();\n\n\t\tfor (const entry of this.inMemoryEntries) {\n\t\t\tif (entry.type === \"message\") {\n\t\t\t\tconst msgEntry = entry as SessionMessageEntry;\n\t\t\t\t// Store the entry timestamp (which is the log date for synced messages)\n\t\t\t\tcontextSlackTimestamps.add(entry.timestamp);\n\n\t\t\t\t// Also store message text to catch duplicates added via prompt()\n\t\t\t\t// AppMessage has different shapes, check for content property\n\t\t\t\tconst msg = msgEntry.message as { role: string; content?: unknown };\n\t\t\t\tif (msg.role === \"user\" && msg.content !== undefined) {\n\t\t\t\t\tconst content = msg.content;\n\t\t\t\t\tif (typeof content === \"string\") {\n\t\t\t\t\t\tcontextMessageTexts.add(content);\n\t\t\t\t\t} else if (Array.isArray(content)) {\n\t\t\t\t\t\tfor (const part of content) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\ttypeof part === \"object\" &&\n\t\t\t\t\t\t\t\tpart !== null &&\n\t\t\t\t\t\t\t\t\"type\" in part &&\n\t\t\t\t\t\t\t\tpart.type === \"text\" &&\n\t\t\t\t\t\t\t\t\"text\" in part\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontextMessageTexts.add((part as { type: \"text\"; text: string }).text);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Read log.jsonl and find user messages not in context\n\t\tconst logContent = readFileSync(this.logFile, \"utf-8\");\n\t\tconst logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n\t\tinterface LogMessage {\n\t\t\tdate?: string;\n\t\t\tts?: string;\n\t\t\tuser?: string;\n\t\t\tuserName?: string;\n\t\t\ttext?: string;\n\t\t\tisBot?: boolean;\n\t\t}\n\n\t\tconst newMessages: Array<{ timestamp: string; slackTs: string; message: AppMessage }> = [];\n\n\t\tfor (const line of logLines) {\n\t\t\ttry {\n\t\t\t\tconst logMsg: LogMessage = JSON.parse(line);\n\n\t\t\t\tconst slackTs = logMsg.ts;\n\t\t\t\tconst date = logMsg.date;\n\t\t\t\tif (!slackTs || !date) continue;\n\n\t\t\t\t// Skip the current message being processed (will be added via prompt())\n\t\t\t\tif (excludeSlackTs && slackTs === excludeSlackTs) continue;\n\n\t\t\t\t// Skip bot messages - added through agent flow\n\t\t\t\tif (logMsg.isBot) continue;\n\n\t\t\t\t// Skip if this date is already in context (was synced before)\n\t\t\t\tif (contextSlackTimestamps.has(date)) continue;\n\n\t\t\t\t// Build the message text as it would appear in context\n\t\t\t\tconst messageText = `[${logMsg.userName || logMsg.user || \"unknown\"}]: ${logMsg.text || \"\"}`;\n\n\t\t\t\t// Skip if this exact message text is already in context (added via prompt())\n\t\t\t\tif (contextMessageTexts.has(messageText)) continue;\n\n\t\t\t\tconst msgTime = new Date(date).getTime() || Date.now();\n\t\t\t\tconst userMessage: AppMessage = {\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: messageText,\n\t\t\t\t\ttimestamp: msgTime,\n\t\t\t\t};\n\n\t\t\t\tnewMessages.push({ timestamp: date, slackTs, message: userMessage });\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\tif (newMessages.length === 0) return;\n\n\t\t// Sort by timestamp and add to context\n\t\tnewMessages.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());\n\n\t\tfor (const { timestamp, message } of newMessages) {\n\t\t\tconst entry: SessionMessageEntry = {\n\t\t\t\ttype: \"message\",\n\t\t\t\ttimestamp, // Use log date as entry timestamp for consistent deduplication\n\t\t\t\tmessage,\n\t\t\t};\n\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tprivate extractSessionId(): string | null {\n\t\tfor (const entry of this.inMemoryEntries) {\n\t\t\tif (entry.type === \"session\") {\n\t\t\t\treturn entry.id;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate loadEntriesFromFile(): SessionEntry[] {\n\t\tif (!existsSync(this.contextFile)) return [];\n\n\t\tconst content = readFileSync(this.contextFile, \"utf8\");\n\t\tconst entries: SessionEntry[] = [];\n\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\tfor (const line of lines) {\n\t\t\tif (!line.trim()) continue;\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line) as SessionEntry;\n\t\t\t\tentries.push(entry);\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn entries;\n\t}\n\n\t/** Initialize session with header if not already done */\n\tstartSession(state: AgentState): void {\n\t\tif (this.sessionInitialized) return;\n\t\tthis.sessionInitialized = true;\n\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: this.channelDir,\n\t\t\tprovider: state.model?.provider || \"unknown\",\n\t\t\tmodelId: state.model?.id || \"unknown\",\n\t\t\tthinkingLevel: state.thinkingLevel,\n\t\t};\n\n\t\tthis.inMemoryEntries.push(entry);\n\t\tfor (const pending of this.pendingEntries) {\n\t\t\tthis.inMemoryEntries.push(pending);\n\t\t}\n\t\tthis.pendingEntries = [];\n\n\t\t// Write to file\n\t\tappendFileSync(this.contextFile, JSON.stringify(entry) + \"\\n\");\n\t\tfor (const memEntry of this.inMemoryEntries.slice(1)) {\n\t\t\tappendFileSync(this.contextFile, JSON.stringify(memEntry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveMessage(message: AppMessage): void {\n\t\tconst entry: SessionMessageEntry = {\n\t\t\ttype: \"message\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingEntries.push(entry);\n\t\t} else {\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveThinkingLevelChange(thinkingLevel: string): void {\n\t\tconst entry: ThinkingLevelChangeEntry = {\n\t\t\ttype: \"thinking_level_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingEntries.push(entry);\n\t\t} else {\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveModelChange(provider: string, modelId: string): void {\n\t\tconst entry: ModelChangeEntry = {\n\t\t\ttype: \"model_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tprovider,\n\t\t\tmodelId,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingEntries.push(entry);\n\t\t} else {\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveCompaction(entry: CompactionEntry): void {\n\t\tthis.inMemoryEntries.push(entry);\n\t\tappendFileSync(this.contextFile, JSON.stringify(entry) + \"\\n\");\n\t}\n\n\t/** Load session with compaction support */\n\tloadSession(): LoadedSession {\n\t\tconst entries = this.loadEntries();\n\t\treturn loadSessionFromEntries(entries);\n\t}\n\n\tloadEntries(): SessionEntry[] {\n\t\t// Re-read from file to get latest state\n\t\tif (existsSync(this.contextFile)) {\n\t\t\treturn this.loadEntriesFromFile();\n\t\t}\n\t\treturn [...this.inMemoryEntries];\n\t}\n\n\tgetSessionId(): string {\n\t\treturn this.sessionId;\n\t}\n\n\tgetSessionFile(): string {\n\t\treturn this.contextFile;\n\t}\n\n\t/** Check if session should be initialized */\n\tshouldInitializeSession(messages: AppMessage[]): boolean {\n\t\tif (this.sessionInitialized) return false;\n\t\tconst userMessages = messages.filter((m) => m.role === \"user\");\n\t\tconst assistantMessages = messages.filter((m) => m.role === \"assistant\");\n\t\treturn userMessages.length >= 1 && assistantMessages.length >= 1;\n\t}\n\n\t/** Reset session (clears context.jsonl) */\n\treset(): void {\n\t\tthis.pendingEntries = [];\n\t\tthis.inMemoryEntries = [];\n\t\tthis.sessionInitialized = false;\n\t\tthis.sessionId = uuidv4();\n\t\t// Truncate the context file\n\t\tif (existsSync(this.contextFile)) {\n\t\t\twriteFileSync(this.contextFile, \"\");\n\t\t}\n\t}\n\n\t// Compatibility methods for AgentSession\n\tisEnabled(): boolean {\n\t\treturn true;\n\t}\n\n\tsetSessionFile(_path: string): void {\n\t\t// No-op for mom - we always use the channel's context.jsonl\n\t}\n\n\tloadModel(): { provider: string; modelId: string } | null {\n\t\treturn this.loadSession().model;\n\t}\n\n\tloadThinkingLevel(): string {\n\t\treturn this.loadSession().thinkingLevel;\n\t}\n\n\t/** Not used by mom but required by AgentSession interface */\n\tcreateBranchedSessionFromEntries(_entries: SessionEntry[], _branchBeforeIndex: number): string | null {\n\t\treturn null; // Mom doesn't support branching\n\t}\n}\n\n// ============================================================================\n// MomSettingsManager - Simple settings for mom\n// ============================================================================\n\nexport interface MomCompactionSettings {\n\tenabled: boolean;\n\treserveTokens: number;\n\tkeepRecentTokens: number;\n}\n\nexport interface MomRetrySettings {\n\tenabled: boolean;\n\tmaxRetries: number;\n\tbaseDelayMs: number;\n}\n\nexport interface MomSettings {\n\tdefaultProvider?: string;\n\tdefaultModel?: string;\n\tdefaultThinkingLevel?: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\";\n\tcompaction?: Partial<MomCompactionSettings>;\n\tretry?: Partial<MomRetrySettings>;\n}\n\nconst DEFAULT_COMPACTION: MomCompactionSettings = {\n\tenabled: true,\n\treserveTokens: 16384,\n\tkeepRecentTokens: 20000,\n};\n\nconst DEFAULT_RETRY: MomRetrySettings = {\n\tenabled: true,\n\tmaxRetries: 3,\n\tbaseDelayMs: 2000,\n};\n\n/**\n * Settings manager for mom.\n * Stores settings in the workspace root directory.\n */\nexport class MomSettingsManager {\n\tprivate settingsPath: string;\n\tprivate settings: MomSettings;\n\n\tconstructor(workspaceDir: string) {\n\t\tthis.settingsPath = join(workspaceDir, \"settings.json\");\n\t\tthis.settings = this.load();\n\t}\n\n\tprivate load(): MomSettings {\n\t\tif (!existsSync(this.settingsPath)) {\n\t\t\treturn {};\n\t\t}\n\n\t\ttry {\n\t\t\tconst content = readFileSync(this.settingsPath, \"utf-8\");\n\t\t\treturn JSON.parse(content);\n\t\t} catch {\n\t\t\treturn {};\n\t\t}\n\t}\n\n\tprivate save(): void {\n\t\ttry {\n\t\t\tconst dir = dirname(this.settingsPath);\n\t\t\tif (!existsSync(dir)) {\n\t\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\t}\n\t\t\twriteFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2), \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(`Warning: Could not save settings file: ${error}`);\n\t\t}\n\t}\n\n\tgetCompactionSettings(): MomCompactionSettings {\n\t\treturn {\n\t\t\t...DEFAULT_COMPACTION,\n\t\t\t...this.settings.compaction,\n\t\t};\n\t}\n\n\tgetCompactionEnabled(): boolean {\n\t\treturn this.settings.compaction?.enabled ?? DEFAULT_COMPACTION.enabled;\n\t}\n\n\tsetCompactionEnabled(enabled: boolean): void {\n\t\tthis.settings.compaction = { ...this.settings.compaction, enabled };\n\t\tthis.save();\n\t}\n\n\tgetRetrySettings(): MomRetrySettings {\n\t\treturn {\n\t\t\t...DEFAULT_RETRY,\n\t\t\t...this.settings.retry,\n\t\t};\n\t}\n\n\tgetRetryEnabled(): boolean {\n\t\treturn this.settings.retry?.enabled ?? DEFAULT_RETRY.enabled;\n\t}\n\n\tsetRetryEnabled(enabled: boolean): void {\n\t\tthis.settings.retry = { ...this.settings.retry, enabled };\n\t\tthis.save();\n\t}\n\n\tgetDefaultModel(): string | undefined {\n\t\treturn this.settings.defaultModel;\n\t}\n\n\tgetDefaultProvider(): string | undefined {\n\t\treturn this.settings.defaultProvider;\n\t}\n\n\tsetDefaultModelAndProvider(provider: string, modelId: string): void {\n\t\tthis.settings.defaultProvider = provider;\n\t\tthis.settings.defaultModel = modelId;\n\t\tthis.save();\n\t}\n\n\tgetDefaultThinkingLevel(): string {\n\t\treturn this.settings.defaultThinkingLevel || \"off\";\n\t}\n\n\tsetDefaultThinkingLevel(level: string): void {\n\t\tthis.settings.defaultThinkingLevel = level as MomSettings[\"defaultThinkingLevel\"];\n\t\tthis.save();\n\t}\n\n\t// Compatibility methods for AgentSession\n\tgetQueueMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn \"one-at-a-time\"; // Mom processes one message at a time\n\t}\n\n\tsetQueueMode(_mode: \"all\" | \"one-at-a-time\"): void {\n\t\t// No-op for mom\n\t}\n\n\tgetHookPaths(): string[] {\n\t\treturn []; // Mom doesn't use hooks\n\t}\n\n\tgetHookTimeout(): number {\n\t\treturn 30000;\n\t}\n}\n\n// ============================================================================\n// Sync log.jsonl to context.jsonl\n// ============================================================================\n\n/**\n * Sync user messages from log.jsonl to context.jsonl.\n *\n * This ensures that messages logged while mom wasn't running (channel chatter,\n * backfilled messages, messages while busy) are added to the LLM context.\n *\n * @param channelDir - Path to channel directory\n * @param excludeAfterTs - Don't sync messages with ts >= this value (they'll be handled by agent)\n * @returns Number of messages synced\n */\nexport function syncLogToContext(channelDir: string, excludeAfterTs?: string): number {\n\tconst logFile = join(channelDir, \"log.jsonl\");\n\tconst contextFile = join(channelDir, \"context.jsonl\");\n\n\tif (!existsSync(logFile)) return 0;\n\n\t// Read all user messages from log.jsonl\n\tconst logContent = readFileSync(logFile, \"utf-8\");\n\tconst logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n\tinterface LogEntry {\n\t\tts: string;\n\t\tuser: string;\n\t\tuserName?: string;\n\t\ttext: string;\n\t\tisBot: boolean;\n\t}\n\n\tconst logMessages: LogEntry[] = [];\n\tfor (const line of logLines) {\n\t\ttry {\n\t\t\tconst entry = JSON.parse(line) as LogEntry;\n\t\t\t// Only sync user messages (not bot responses)\n\t\t\tif (!entry.isBot && entry.ts && entry.text) {\n\t\t\t\t// Skip if >= excludeAfterTs\n\t\t\t\tif (excludeAfterTs && entry.ts >= excludeAfterTs) continue;\n\t\t\t\tlogMessages.push(entry);\n\t\t\t}\n\t\t} catch {}\n\t}\n\n\tif (logMessages.length === 0) return 0;\n\n\t// Read existing timestamps from context.jsonl\n\tconst existingTs = new Set<string>();\n\tif (existsSync(contextFile)) {\n\t\tconst contextContent = readFileSync(contextFile, \"utf-8\");\n\t\tconst contextLines = contextContent.trim().split(\"\\n\").filter(Boolean);\n\t\tfor (const line of contextLines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"message\" && entry.message?.role === \"user\" && entry.message?.timestamp) {\n\t\t\t\t\t// Extract ts from timestamp (ms -> slack ts format for comparison)\n\t\t\t\t\t// We store the original slack ts in a way we can recover\n\t\t\t\t\t// Actually, let's just check by content match since ts formats differ\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\n\t// For deduplication, we need to track what's already in context\n\t// Read context and extract user message content (strip attachments section for comparison)\n\tconst existingMessages = new Set<string>();\n\tif (existsSync(contextFile)) {\n\t\tconst contextContent = readFileSync(contextFile, \"utf-8\");\n\t\tconst contextLines = contextContent.trim().split(\"\\n\").filter(Boolean);\n\t\tfor (const line of contextLines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"message\" && entry.message?.role === \"user\") {\n\t\t\t\t\tlet content =\n\t\t\t\t\t\ttypeof entry.message.content === \"string\" ? entry.message.content : entry.message.content?.[0]?.text;\n\t\t\t\t\tif (content) {\n\t\t\t\t\t\t// Strip timestamp prefix for comparison (live messages have it, log messages don't)\n\t\t\t\t\t\t// Format: [YYYY-MM-DD HH:MM:SS+HH:MM] [username]: text\n\t\t\t\t\t\tcontent = content.replace(/^\\[\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}[+-]\\d{2}:\\d{2}\\] /, \"\");\n\t\t\t\t\t\t// Strip attachments section for comparison (live messages have it, log messages don't)\n\t\t\t\t\t\tconst attachmentsIdx = content.indexOf(\"\\n\\n<slack_attachments>\\n\");\n\t\t\t\t\t\tif (attachmentsIdx !== -1) {\n\t\t\t\t\t\t\tcontent = content.substring(0, attachmentsIdx);\n\t\t\t\t\t\t}\n\t\t\t\t\t\texistingMessages.add(content);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\n\t// Add missing messages to context.jsonl\n\tlet syncedCount = 0;\n\tfor (const msg of logMessages) {\n\t\tconst userName = msg.userName || msg.user;\n\t\tconst content = `[${userName}]: ${msg.text}`;\n\n\t\t// Skip if already in context\n\t\tif (existingMessages.has(content)) continue;\n\n\t\tconst timestamp = Math.floor(parseFloat(msg.ts) * 1000);\n\t\tconst entry = {\n\t\t\ttype: \"message\",\n\t\t\ttimestamp: new Date(timestamp).toISOString(),\n\t\t\tmessage: {\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent,\n\t\t\t\ttimestamp,\n\t\t\t},\n\t\t};\n\n\t\t// Ensure directory exists\n\t\tif (!existsSync(channelDir)) {\n\t\t\tmkdirSync(channelDir, { recursive: true });\n\t\t}\n\n\t\tappendFileSync(contextFile, JSON.stringify(entry) + \"\\n\");\n\t\texistingMessages.add(content); // Track to avoid duplicates within this sync\n\t\tsyncedCount++;\n\t}\n\n\treturn syncedCount;\n}\n"]}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Context management for mom.\n *\n * Mom uses two files per channel:\n * - context.jsonl: Structured API messages for LLM context (same format as coding-agent sessions)\n * - log.jsonl: Human-readable channel history for grep (no tool results)\n *\n * This module provides:\n * - MomSessionManager: Adapts coding-agent's SessionManager for channel-based storage\n * - MomSettingsManager: Simple settings for mom (compaction, retry, model preferences)\n */\n\nimport type { AgentState, AppMessage } from \"@mariozechner/pi-agent-core\";\nimport {\n\ttype CompactionEntry,\n\ttype LoadedSession,\n\tloadSessionFromEntries,\n\ttype ModelChangeEntry,\n\ttype SessionEntry,\n\ttype SessionHeader,\n\ttype SessionMessageEntry,\n\ttype ThinkingLevelChangeEntry,\n} from \"@mariozechner/pi-coding-agent\";\nimport { randomBytes } from \"crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\n\nfunction uuidv4(): string {\n\tconst bytes = randomBytes(16);\n\tbytes[6] = (bytes[6] & 0x0f) | 0x40;\n\tbytes[8] = (bytes[8] & 0x3f) | 0x80;\n\tconst hex = bytes.toString(\"hex\");\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\n// ============================================================================\n// MomSessionManager - Channel-based session management\n// ============================================================================\n\n/**\n * Session manager for mom, storing context per Slack channel.\n *\n * Unlike coding-agent which creates timestamped session files, mom uses\n * a single context.jsonl per channel that persists across all @mentions.\n */\nexport class MomSessionManager {\n\tprivate sessionId: string;\n\tprivate contextFile: string;\n\tprivate logFile: string;\n\tprivate channelDir: string;\n\tprivate sessionInitialized: boolean = false;\n\tprivate inMemoryEntries: SessionEntry[] = [];\n\tprivate pendingEntries: SessionEntry[] = [];\n\n\tconstructor(channelDir: string, initialModel?: { provider: string; id: string; thinkingLevel?: string }) {\n\t\tthis.channelDir = channelDir;\n\t\tthis.contextFile = join(channelDir, \"context.jsonl\");\n\t\tthis.logFile = join(channelDir, \"log.jsonl\");\n\n\t\t// Ensure channel directory exists\n\t\tif (!existsSync(channelDir)) {\n\t\t\tmkdirSync(channelDir, { recursive: true });\n\t\t}\n\n\t\t// Load existing session or create new\n\t\tif (existsSync(this.contextFile)) {\n\t\t\tthis.inMemoryEntries = this.loadEntriesFromFile();\n\t\t\tthis.sessionId = this.extractSessionId() || uuidv4();\n\t\t\tthis.sessionInitialized = this.inMemoryEntries.length > 0;\n\t\t} else {\n\t\t\t// New session - write header immediately\n\t\t\tthis.sessionId = uuidv4();\n\t\t\tif (initialModel) {\n\t\t\t\tthis.writeSessionHeader(initialModel);\n\t\t\t}\n\t\t}\n\t\t// Note: syncFromLog() is called explicitly from agent.ts with excludeTimestamp\n\t}\n\n\t/** Write session header to file (called on new session creation) */\n\tprivate writeSessionHeader(model: { provider: string; id: string; thinkingLevel?: string }): void {\n\t\tthis.sessionInitialized = true;\n\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: this.channelDir,\n\t\t\tprovider: model.provider,\n\t\t\tmodelId: model.id,\n\t\t\tthinkingLevel: model.thinkingLevel || \"off\",\n\t\t};\n\n\t\tthis.inMemoryEntries.push(entry);\n\t\tappendFileSync(this.contextFile, `${JSON.stringify(entry)}\\n`);\n\t}\n\n\t/**\n\t * Sync user messages from log.jsonl that aren't in context.jsonl.\n\t *\n\t * log.jsonl and context.jsonl must have the same user messages.\n\t * This handles:\n\t * - Backfilled messages (mom was offline)\n\t * - Messages that arrived while mom was processing a previous turn\n\t * - Channel chatter between @mentions\n\t *\n\t * Channel chatter is formatted as \"[username]: message\" to distinguish from direct @mentions.\n\t *\n\t * Called before each agent run.\n\t *\n\t * @param excludeSlackTs Slack timestamp of current message (will be added via prompt(), not sync)\n\t */\n\tsyncFromLog(excludeSlackTs?: string): void {\n\t\tif (!existsSync(this.logFile)) return;\n\n\t\t// Build set of Slack timestamps already in context\n\t\t// We store slackTs in the message content or can extract from formatted messages\n\t\t// For messages synced from log, we use the log's date as the entry timestamp\n\t\t// For messages added via prompt(), they have different timestamps\n\t\t// So we need to match by content OR by stored slackTs\n\t\tconst contextSlackTimestamps = new Set<string>();\n\t\tconst contextMessageTexts = new Set<string>();\n\n\t\tfor (const entry of this.inMemoryEntries) {\n\t\t\tif (entry.type === \"message\") {\n\t\t\t\tconst msgEntry = entry as SessionMessageEntry;\n\t\t\t\t// Store the entry timestamp (which is the log date for synced messages)\n\t\t\t\tcontextSlackTimestamps.add(entry.timestamp);\n\n\t\t\t\t// Also store message text to catch duplicates added via prompt()\n\t\t\t\t// AppMessage has different shapes, check for content property\n\t\t\t\tconst msg = msgEntry.message as { role: string; content?: unknown };\n\t\t\t\tif (msg.role === \"user\" && msg.content !== undefined) {\n\t\t\t\t\tconst content = msg.content;\n\t\t\t\t\tif (typeof content === \"string\") {\n\t\t\t\t\t\tcontextMessageTexts.add(content);\n\t\t\t\t\t} else if (Array.isArray(content)) {\n\t\t\t\t\t\tfor (const part of content) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\ttypeof part === \"object\" &&\n\t\t\t\t\t\t\t\tpart !== null &&\n\t\t\t\t\t\t\t\t\"type\" in part &&\n\t\t\t\t\t\t\t\tpart.type === \"text\" &&\n\t\t\t\t\t\t\t\t\"text\" in part\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontextMessageTexts.add((part as { type: \"text\"; text: string }).text);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Read log.jsonl and find user messages not in context\n\t\tconst logContent = readFileSync(this.logFile, \"utf-8\");\n\t\tconst logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n\t\tinterface LogMessage {\n\t\t\tdate?: string;\n\t\t\tts?: string;\n\t\t\tuser?: string;\n\t\t\tuserName?: string;\n\t\t\ttext?: string;\n\t\t\tisBot?: boolean;\n\t\t}\n\n\t\tconst newMessages: Array<{ timestamp: string; slackTs: string; message: AppMessage }> = [];\n\n\t\tfor (const line of logLines) {\n\t\t\ttry {\n\t\t\t\tconst logMsg: LogMessage = JSON.parse(line);\n\n\t\t\t\tconst slackTs = logMsg.ts;\n\t\t\t\tconst date = logMsg.date;\n\t\t\t\tif (!slackTs || !date) continue;\n\n\t\t\t\t// Skip the current message being processed (will be added via prompt())\n\t\t\t\tif (excludeSlackTs && slackTs === excludeSlackTs) continue;\n\n\t\t\t\t// Skip bot messages - added through agent flow\n\t\t\t\tif (logMsg.isBot) continue;\n\n\t\t\t\t// Skip if this date is already in context (was synced before)\n\t\t\t\tif (contextSlackTimestamps.has(date)) continue;\n\n\t\t\t\t// Build the message text as it would appear in context\n\t\t\t\tconst messageText = `[${logMsg.userName || logMsg.user || \"unknown\"}]: ${logMsg.text || \"\"}`;\n\n\t\t\t\t// Skip if this exact message text is already in context (added via prompt())\n\t\t\t\tif (contextMessageTexts.has(messageText)) continue;\n\n\t\t\t\tconst msgTime = new Date(date).getTime() || Date.now();\n\t\t\t\tconst userMessage: AppMessage = {\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: messageText,\n\t\t\t\t\ttimestamp: msgTime,\n\t\t\t\t};\n\n\t\t\t\tnewMessages.push({ timestamp: date, slackTs, message: userMessage });\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\tif (newMessages.length === 0) return;\n\n\t\t// Sort by timestamp and add to context\n\t\tnewMessages.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());\n\n\t\tfor (const { timestamp, message } of newMessages) {\n\t\t\tconst entry: SessionMessageEntry = {\n\t\t\t\ttype: \"message\",\n\t\t\t\ttimestamp, // Use log date as entry timestamp for consistent deduplication\n\t\t\t\tmessage,\n\t\t\t};\n\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, `${JSON.stringify(entry)}\\n`);\n\t\t}\n\t}\n\n\tprivate extractSessionId(): string | null {\n\t\tfor (const entry of this.inMemoryEntries) {\n\t\t\tif (entry.type === \"session\") {\n\t\t\t\treturn entry.id;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate loadEntriesFromFile(): SessionEntry[] {\n\t\tif (!existsSync(this.contextFile)) return [];\n\n\t\tconst content = readFileSync(this.contextFile, \"utf8\");\n\t\tconst entries: SessionEntry[] = [];\n\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\tfor (const line of lines) {\n\t\t\tif (!line.trim()) continue;\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line) as SessionEntry;\n\t\t\t\tentries.push(entry);\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn entries;\n\t}\n\n\t/** Initialize session with header if not already done */\n\tstartSession(state: AgentState): void {\n\t\tif (this.sessionInitialized) return;\n\t\tthis.sessionInitialized = true;\n\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: this.channelDir,\n\t\t\tprovider: state.model?.provider || \"unknown\",\n\t\t\tmodelId: state.model?.id || \"unknown\",\n\t\t\tthinkingLevel: state.thinkingLevel,\n\t\t};\n\n\t\tthis.inMemoryEntries.push(entry);\n\t\tfor (const pending of this.pendingEntries) {\n\t\t\tthis.inMemoryEntries.push(pending);\n\t\t}\n\t\tthis.pendingEntries = [];\n\n\t\t// Write to file\n\t\tappendFileSync(this.contextFile, `${JSON.stringify(entry)}\\n`);\n\t\tfor (const memEntry of this.inMemoryEntries.slice(1)) {\n\t\t\tappendFileSync(this.contextFile, `${JSON.stringify(memEntry)}\\n`);\n\t\t}\n\t}\n\n\tsaveMessage(message: AppMessage): void {\n\t\tconst entry: SessionMessageEntry = {\n\t\t\ttype: \"message\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingEntries.push(entry);\n\t\t} else {\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, `${JSON.stringify(entry)}\\n`);\n\t\t}\n\t}\n\n\tsaveThinkingLevelChange(thinkingLevel: string): void {\n\t\tconst entry: ThinkingLevelChangeEntry = {\n\t\t\ttype: \"thinking_level_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingEntries.push(entry);\n\t\t} else {\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, `${JSON.stringify(entry)}\\n`);\n\t\t}\n\t}\n\n\tsaveModelChange(provider: string, modelId: string): void {\n\t\tconst entry: ModelChangeEntry = {\n\t\t\ttype: \"model_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tprovider,\n\t\t\tmodelId,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingEntries.push(entry);\n\t\t} else {\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, `${JSON.stringify(entry)}\\n`);\n\t\t}\n\t}\n\n\tsaveCompaction(entry: CompactionEntry): void {\n\t\tthis.inMemoryEntries.push(entry);\n\t\tappendFileSync(this.contextFile, `${JSON.stringify(entry)}\\n`);\n\t}\n\n\t/** Load session with compaction support */\n\tloadSession(): LoadedSession {\n\t\tconst entries = this.loadEntries();\n\t\treturn loadSessionFromEntries(entries);\n\t}\n\n\tloadEntries(): SessionEntry[] {\n\t\t// Re-read from file to get latest state\n\t\tif (existsSync(this.contextFile)) {\n\t\t\treturn this.loadEntriesFromFile();\n\t\t}\n\t\treturn [...this.inMemoryEntries];\n\t}\n\n\tgetSessionId(): string {\n\t\treturn this.sessionId;\n\t}\n\n\tgetSessionFile(): string {\n\t\treturn this.contextFile;\n\t}\n\n\t/** Check if session should be initialized */\n\tshouldInitializeSession(messages: AppMessage[]): boolean {\n\t\tif (this.sessionInitialized) return false;\n\t\tconst userMessages = messages.filter((m) => m.role === \"user\");\n\t\tconst assistantMessages = messages.filter((m) => m.role === \"assistant\");\n\t\treturn userMessages.length >= 1 && assistantMessages.length >= 1;\n\t}\n\n\t/** Reset session (clears context.jsonl) */\n\treset(): void {\n\t\tthis.pendingEntries = [];\n\t\tthis.inMemoryEntries = [];\n\t\tthis.sessionInitialized = false;\n\t\tthis.sessionId = uuidv4();\n\t\t// Truncate the context file\n\t\tif (existsSync(this.contextFile)) {\n\t\t\twriteFileSync(this.contextFile, \"\");\n\t\t}\n\t}\n\n\t// Compatibility methods for AgentSession\n\tisEnabled(): boolean {\n\t\treturn true;\n\t}\n\n\tsetSessionFile(_path: string): void {\n\t\t// No-op for mom - we always use the channel's context.jsonl\n\t}\n\n\tloadModel(): { provider: string; modelId: string } | null {\n\t\treturn this.loadSession().model;\n\t}\n\n\tloadThinkingLevel(): string {\n\t\treturn this.loadSession().thinkingLevel;\n\t}\n\n\t/** Not used by mom but required by AgentSession interface */\n\tcreateBranchedSessionFromEntries(_entries: SessionEntry[], _branchBeforeIndex: number): string | null {\n\t\treturn null; // Mom doesn't support branching\n\t}\n}\n\n// ============================================================================\n// MomSettingsManager - Simple settings for mom\n// ============================================================================\n\nexport interface MomCompactionSettings {\n\tenabled: boolean;\n\treserveTokens: number;\n\tkeepRecentTokens: number;\n}\n\nexport interface MomRetrySettings {\n\tenabled: boolean;\n\tmaxRetries: number;\n\tbaseDelayMs: number;\n}\n\nexport interface MomSettings {\n\tdefaultProvider?: string;\n\tdefaultModel?: string;\n\tdefaultThinkingLevel?: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\";\n\tcompaction?: Partial<MomCompactionSettings>;\n\tretry?: Partial<MomRetrySettings>;\n}\n\nconst DEFAULT_COMPACTION: MomCompactionSettings = {\n\tenabled: true,\n\treserveTokens: 16384,\n\tkeepRecentTokens: 20000,\n};\n\nconst DEFAULT_RETRY: MomRetrySettings = {\n\tenabled: true,\n\tmaxRetries: 3,\n\tbaseDelayMs: 2000,\n};\n\n/**\n * Settings manager for mom.\n * Stores settings in the workspace root directory.\n */\nexport class MomSettingsManager {\n\tprivate settingsPath: string;\n\tprivate settings: MomSettings;\n\n\tconstructor(workspaceDir: string) {\n\t\tthis.settingsPath = join(workspaceDir, \"settings.json\");\n\t\tthis.settings = this.load();\n\t}\n\n\tprivate load(): MomSettings {\n\t\tif (!existsSync(this.settingsPath)) {\n\t\t\treturn {};\n\t\t}\n\n\t\ttry {\n\t\t\tconst content = readFileSync(this.settingsPath, \"utf-8\");\n\t\t\treturn JSON.parse(content);\n\t\t} catch {\n\t\t\treturn {};\n\t\t}\n\t}\n\n\tprivate save(): void {\n\t\ttry {\n\t\t\tconst dir = dirname(this.settingsPath);\n\t\t\tif (!existsSync(dir)) {\n\t\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\t}\n\t\t\twriteFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2), \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(`Warning: Could not save settings file: ${error}`);\n\t\t}\n\t}\n\n\tgetCompactionSettings(): MomCompactionSettings {\n\t\treturn {\n\t\t\t...DEFAULT_COMPACTION,\n\t\t\t...this.settings.compaction,\n\t\t};\n\t}\n\n\tgetCompactionEnabled(): boolean {\n\t\treturn this.settings.compaction?.enabled ?? DEFAULT_COMPACTION.enabled;\n\t}\n\n\tsetCompactionEnabled(enabled: boolean): void {\n\t\tthis.settings.compaction = { ...this.settings.compaction, enabled };\n\t\tthis.save();\n\t}\n\n\tgetRetrySettings(): MomRetrySettings {\n\t\treturn {\n\t\t\t...DEFAULT_RETRY,\n\t\t\t...this.settings.retry,\n\t\t};\n\t}\n\n\tgetRetryEnabled(): boolean {\n\t\treturn this.settings.retry?.enabled ?? DEFAULT_RETRY.enabled;\n\t}\n\n\tsetRetryEnabled(enabled: boolean): void {\n\t\tthis.settings.retry = { ...this.settings.retry, enabled };\n\t\tthis.save();\n\t}\n\n\tgetDefaultModel(): string | undefined {\n\t\treturn this.settings.defaultModel;\n\t}\n\n\tgetDefaultProvider(): string | undefined {\n\t\treturn this.settings.defaultProvider;\n\t}\n\n\tsetDefaultModelAndProvider(provider: string, modelId: string): void {\n\t\tthis.settings.defaultProvider = provider;\n\t\tthis.settings.defaultModel = modelId;\n\t\tthis.save();\n\t}\n\n\tgetDefaultThinkingLevel(): string {\n\t\treturn this.settings.defaultThinkingLevel || \"off\";\n\t}\n\n\tsetDefaultThinkingLevel(level: string): void {\n\t\tthis.settings.defaultThinkingLevel = level as MomSettings[\"defaultThinkingLevel\"];\n\t\tthis.save();\n\t}\n\n\t// Compatibility methods for AgentSession\n\tgetQueueMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn \"one-at-a-time\"; // Mom processes one message at a time\n\t}\n\n\tsetQueueMode(_mode: \"all\" | \"one-at-a-time\"): void {\n\t\t// No-op for mom\n\t}\n\n\tgetHookPaths(): string[] {\n\t\treturn []; // Mom doesn't use hooks\n\t}\n\n\tgetHookTimeout(): number {\n\t\treturn 30000;\n\t}\n}\n\n// ============================================================================\n// Sync log.jsonl to context.jsonl\n// ============================================================================\n\n/**\n * Sync user messages from log.jsonl to context.jsonl.\n *\n * This ensures that messages logged while mom wasn't running (channel chatter,\n * backfilled messages, messages while busy) are added to the LLM context.\n *\n * @param channelDir - Path to channel directory\n * @param excludeAfterTs - Don't sync messages with ts >= this value (they'll be handled by agent)\n * @returns Number of messages synced\n */\nexport function syncLogToContext(channelDir: string, excludeAfterTs?: string): number {\n\tconst logFile = join(channelDir, \"log.jsonl\");\n\tconst contextFile = join(channelDir, \"context.jsonl\");\n\n\tif (!existsSync(logFile)) return 0;\n\n\t// Read all user messages from log.jsonl\n\tconst logContent = readFileSync(logFile, \"utf-8\");\n\tconst logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n\tinterface LogEntry {\n\t\tts: string;\n\t\tuser: string;\n\t\tuserName?: string;\n\t\ttext: string;\n\t\tisBot: boolean;\n\t}\n\n\tconst logMessages: LogEntry[] = [];\n\tfor (const line of logLines) {\n\t\ttry {\n\t\t\tconst entry = JSON.parse(line) as LogEntry;\n\t\t\t// Only sync user messages (not bot responses)\n\t\t\tif (!entry.isBot && entry.ts && entry.text) {\n\t\t\t\t// Skip if >= excludeAfterTs\n\t\t\t\tif (excludeAfterTs && entry.ts >= excludeAfterTs) continue;\n\t\t\t\tlogMessages.push(entry);\n\t\t\t}\n\t\t} catch {}\n\t}\n\n\tif (logMessages.length === 0) return 0;\n\n\t// Read existing timestamps from context.jsonl\n\tif (existsSync(contextFile)) {\n\t\tconst contextContent = readFileSync(contextFile, \"utf-8\");\n\t\tconst contextLines = contextContent.trim().split(\"\\n\").filter(Boolean);\n\t\tfor (const line of contextLines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"message\" && entry.message?.role === \"user\" && entry.message?.timestamp) {\n\t\t\t\t\t// Extract ts from timestamp (ms -> slack ts format for comparison)\n\t\t\t\t\t// We store the original slack ts in a way we can recover\n\t\t\t\t\t// Actually, let's just check by content match since ts formats differ\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\n\t// For deduplication, we need to track what's already in context\n\t// Read context and extract user message content (strip attachments section for comparison)\n\tconst existingMessages = new Set<string>();\n\tif (existsSync(contextFile)) {\n\t\tconst contextContent = readFileSync(contextFile, \"utf-8\");\n\t\tconst contextLines = contextContent.trim().split(\"\\n\").filter(Boolean);\n\t\tfor (const line of contextLines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"message\" && entry.message?.role === \"user\") {\n\t\t\t\t\tlet content =\n\t\t\t\t\t\ttypeof entry.message.content === \"string\" ? entry.message.content : entry.message.content?.[0]?.text;\n\t\t\t\t\tif (content) {\n\t\t\t\t\t\t// Strip timestamp prefix for comparison (live messages have it, log messages don't)\n\t\t\t\t\t\t// Format: [YYYY-MM-DD HH:MM:SS+HH:MM] [username]: text\n\t\t\t\t\t\tcontent = content.replace(/^\\[\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}[+-]\\d{2}:\\d{2}\\] /, \"\");\n\t\t\t\t\t\t// Strip attachments section for comparison (live messages have it, log messages don't)\n\t\t\t\t\t\tconst attachmentsIdx = content.indexOf(\"\\n\\n<slack_attachments>\\n\");\n\t\t\t\t\t\tif (attachmentsIdx !== -1) {\n\t\t\t\t\t\t\tcontent = content.substring(0, attachmentsIdx);\n\t\t\t\t\t\t}\n\t\t\t\t\t\texistingMessages.add(content);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\n\t// Add missing messages to context.jsonl\n\tlet syncedCount = 0;\n\tfor (const msg of logMessages) {\n\t\tconst userName = msg.userName || msg.user;\n\t\tconst content = `[${userName}]: ${msg.text}`;\n\n\t\t// Skip if already in context\n\t\tif (existingMessages.has(content)) continue;\n\n\t\tconst timestamp = Math.floor(parseFloat(msg.ts) * 1000);\n\t\tconst entry = {\n\t\t\ttype: \"message\",\n\t\t\ttimestamp: new Date(timestamp).toISOString(),\n\t\t\tmessage: {\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent,\n\t\t\t\ttimestamp,\n\t\t\t},\n\t\t};\n\n\t\t// Ensure directory exists\n\t\tif (!existsSync(channelDir)) {\n\t\t\tmkdirSync(channelDir, { recursive: true });\n\t\t}\n\n\t\tappendFileSync(contextFile, `${JSON.stringify(entry)}\\n`);\n\t\texistingMessages.add(content); // Track to avoid duplicates within this sync\n\t\tsyncedCount++;\n\t}\n\n\treturn syncedCount;\n}\n"]}
package/dist/context.js CHANGED
@@ -73,7 +73,7 @@ export class MomSessionManager {
73
73
  thinkingLevel: model.thinkingLevel || "off",
74
74
  };
75
75
  this.inMemoryEntries.push(entry);
76
- appendFileSync(this.contextFile, JSON.stringify(entry) + "\n");
76
+ appendFileSync(this.contextFile, `${JSON.stringify(entry)}\n`);
77
77
  }
78
78
  /**
79
79
  * Sync user messages from log.jsonl that aren't in context.jsonl.
@@ -175,7 +175,7 @@ export class MomSessionManager {
175
175
  message,
176
176
  };
177
177
  this.inMemoryEntries.push(entry);
178
- appendFileSync(this.contextFile, JSON.stringify(entry) + "\n");
178
+ appendFileSync(this.contextFile, `${JSON.stringify(entry)}\n`);
179
179
  }
180
180
  }
181
181
  extractSessionId() {
@@ -225,9 +225,9 @@ export class MomSessionManager {
225
225
  }
226
226
  this.pendingEntries = [];
227
227
  // Write to file
228
- appendFileSync(this.contextFile, JSON.stringify(entry) + "\n");
228
+ appendFileSync(this.contextFile, `${JSON.stringify(entry)}\n`);
229
229
  for (const memEntry of this.inMemoryEntries.slice(1)) {
230
- appendFileSync(this.contextFile, JSON.stringify(memEntry) + "\n");
230
+ appendFileSync(this.contextFile, `${JSON.stringify(memEntry)}\n`);
231
231
  }
232
232
  }
233
233
  saveMessage(message) {
@@ -241,7 +241,7 @@ export class MomSessionManager {
241
241
  }
242
242
  else {
243
243
  this.inMemoryEntries.push(entry);
244
- appendFileSync(this.contextFile, JSON.stringify(entry) + "\n");
244
+ appendFileSync(this.contextFile, `${JSON.stringify(entry)}\n`);
245
245
  }
246
246
  }
247
247
  saveThinkingLevelChange(thinkingLevel) {
@@ -255,7 +255,7 @@ export class MomSessionManager {
255
255
  }
256
256
  else {
257
257
  this.inMemoryEntries.push(entry);
258
- appendFileSync(this.contextFile, JSON.stringify(entry) + "\n");
258
+ appendFileSync(this.contextFile, `${JSON.stringify(entry)}\n`);
259
259
  }
260
260
  }
261
261
  saveModelChange(provider, modelId) {
@@ -270,12 +270,12 @@ export class MomSessionManager {
270
270
  }
271
271
  else {
272
272
  this.inMemoryEntries.push(entry);
273
- appendFileSync(this.contextFile, JSON.stringify(entry) + "\n");
273
+ appendFileSync(this.contextFile, `${JSON.stringify(entry)}\n`);
274
274
  }
275
275
  }
276
276
  saveCompaction(entry) {
277
277
  this.inMemoryEntries.push(entry);
278
- appendFileSync(this.contextFile, JSON.stringify(entry) + "\n");
278
+ appendFileSync(this.contextFile, `${JSON.stringify(entry)}\n`);
279
279
  }
280
280
  /** Load session with compaction support */
281
281
  loadSession() {
@@ -473,7 +473,6 @@ export function syncLogToContext(channelDir, excludeAfterTs) {
473
473
  if (logMessages.length === 0)
474
474
  return 0;
475
475
  // Read existing timestamps from context.jsonl
476
- const existingTs = new Set();
477
476
  if (existsSync(contextFile)) {
478
477
  const contextContent = readFileSync(contextFile, "utf-8");
479
478
  const contextLines = contextContent.trim().split("\n").filter(Boolean);
@@ -538,7 +537,7 @@ export function syncLogToContext(channelDir, excludeAfterTs) {
538
537
  if (!existsSync(channelDir)) {
539
538
  mkdirSync(channelDir, { recursive: true });
540
539
  }
541
- appendFileSync(contextFile, JSON.stringify(entry) + "\n");
540
+ appendFileSync(contextFile, `${JSON.stringify(entry)}\n`);
542
541
  existingMessages.add(content); // Track to avoid duplicates within this sync
543
542
  syncedCount++;
544
543
  }
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAGN,sBAAsB,GAMtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACxF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAErC,SAAS,MAAM,GAAW;IACzB,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC9B,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACpC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACpC,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;AAAA,CAC/G;AAED,+EAA+E;AAC/E,uDAAuD;AACvD,+EAA+E;AAE/E;;;;;GAKG;AACH,MAAM,OAAO,iBAAiB;IACrB,SAAS,CAAS;IAClB,WAAW,CAAS;IACpB,OAAO,CAAS;IAChB,UAAU,CAAS;IACnB,kBAAkB,GAAY,KAAK,CAAC;IACpC,eAAe,GAAmB,EAAE,CAAC;IACrC,cAAc,GAAmB,EAAE,CAAC;IAE5C,YAAY,UAAkB,EAAE,YAAuE,EAAE;QACxG,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QACrD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QAE7C,kCAAkC;QAClC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7B,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,CAAC;QAED,sCAAsC;QACtC,IAAI,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAClD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,MAAM,EAAE,CAAC;YACrD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC;QAC3D,CAAC;aAAM,CAAC;YACP,yCAAyC;YACzC,IAAI,CAAC,SAAS,GAAG,MAAM,EAAE,CAAC;YAC1B,IAAI,YAAY,EAAE,CAAC;gBAClB,IAAI,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;YACvC,CAAC;QACF,CAAC;QACD,+EAA+E;IAD9E,CAED;IAED,oEAAoE;IAC5D,kBAAkB,CAAC,KAA+D,EAAQ;QACjG,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAE/B,MAAM,KAAK,GAAkB;YAC5B,IAAI,EAAE,SAAS;YACf,EAAE,EAAE,IAAI,CAAC,SAAS;YAClB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,GAAG,EAAE,IAAI,CAAC,UAAU;YACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,OAAO,EAAE,KAAK,CAAC,EAAE;YACjB,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,KAAK;SAC3C,CAAC;QAEF,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAAA,CAC/D;IAED;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,cAAuB,EAAQ;QAC1C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO;QAEtC,mDAAmD;QACnD,iFAAiF;QACjF,6EAA6E;QAC7E,kEAAkE;QAClE,sDAAsD;QACtD,MAAM,sBAAsB,GAAG,IAAI,GAAG,EAAU,CAAC;QACjD,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAU,CAAC;QAE9C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC1C,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC9B,MAAM,QAAQ,GAAG,KAA4B,CAAC;gBAC9C,wEAAwE;gBACxE,sBAAsB,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;gBAE5C,iEAAiE;gBACjE,8DAA8D;gBAC9D,MAAM,GAAG,GAAG,QAAQ,CAAC,OAA8C,CAAC;gBACpE,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;oBACtD,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;oBAC5B,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;wBACjC,mBAAmB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBAClC,CAAC;yBAAM,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;wBACnC,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;4BAC5B,IACC,OAAO,IAAI,KAAK,QAAQ;gCACxB,IAAI,KAAK,IAAI;gCACb,MAAM,IAAI,IAAI;gCACd,IAAI,CAAC,IAAI,KAAK,MAAM;gCACpB,MAAM,IAAI,IAAI,EACb,CAAC;gCACF,mBAAmB,CAAC,GAAG,CAAE,IAAuC,CAAC,IAAI,CAAC,CAAC;4BACxE,CAAC;wBACF,CAAC;oBACF,CAAC;gBACF,CAAC;YACF,CAAC;QACF,CAAC;QAED,uDAAuD;QACvD,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAW/D,MAAM,WAAW,GAAuE,EAAE,CAAC;QAE3F,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACJ,MAAM,MAAM,GAAe,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAE5C,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC;gBAC1B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;gBACzB,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI;oBAAE,SAAS;gBAEhC,wEAAwE;gBACxE,IAAI,cAAc,IAAI,OAAO,KAAK,cAAc;oBAAE,SAAS;gBAE3D,+CAA+C;gBAC/C,IAAI,MAAM,CAAC,KAAK;oBAAE,SAAS;gBAE3B,8DAA8D;gBAC9D,IAAI,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAE/C,uDAAuD;gBACvD,MAAM,WAAW,GAAG,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,IAAI,IAAI,SAAS,MAAM,MAAM,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;gBAE7F,6EAA6E;gBAC7E,IAAI,mBAAmB,CAAC,GAAG,CAAC,WAAW,CAAC;oBAAE,SAAS;gBAEnD,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;gBACvD,MAAM,WAAW,GAAe;oBAC/B,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,WAAW;oBACpB,SAAS,EAAE,OAAO;iBAClB,CAAC;gBAEF,WAAW,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC;YACtE,CAAC;YAAC,MAAM,CAAC;gBACR,uBAAuB;YACxB,CAAC;QACF,CAAC;QAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAErC,uCAAuC;QACvC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAE9F,KAAK,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,WAAW,EAAE,CAAC;YAClD,MAAM,KAAK,GAAwB;gBAClC,IAAI,EAAE,SAAS;gBACf,SAAS,EAAE,+DAA+D;gBAC1E,OAAO;aACP,CAAC;YAEF,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAChE,CAAC;IAAA,CACD;IAEO,gBAAgB,GAAkB;QACzC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC1C,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC9B,OAAO,KAAK,CAAC,EAAE,CAAC;YACjB,CAAC;QACF,CAAC;QACD,OAAO,IAAI,CAAC;IAAA,CACZ;IAEO,mBAAmB,GAAmB;QAC7C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC;YAAE,OAAO,EAAE,CAAC;QAE7C,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACvD,MAAM,OAAO,GAAmB,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;gBAAE,SAAS;YAC3B,IAAI,CAAC;gBACJ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAiB,CAAC;gBAC/C,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACR,uBAAuB;YACxB,CAAC;QACF,CAAC;QAED,OAAO,OAAO,CAAC;IAAA,CACf;IAED,yDAAyD;IACzD,YAAY,CAAC,KAAiB,EAAQ;QACrC,IAAI,IAAI,CAAC,kBAAkB;YAAE,OAAO;QACpC,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAE/B,MAAM,KAAK,GAAkB;YAC5B,IAAI,EAAE,SAAS;YACf,EAAE,EAAE,IAAI,CAAC,SAAS;YAClB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,GAAG,EAAE,IAAI,CAAC,UAAU;YACpB,QAAQ,EAAE,KAAK,CAAC,KAAK,EAAE,QAAQ,IAAI,SAAS;YAC5C,OAAO,EAAE,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,SAAS;YACrC,aAAa,EAAE,KAAK,CAAC,aAAa;SAClC,CAAC;QAEF,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YAC3C,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;QAEzB,gBAAgB;QAChB,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAC/D,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YACtD,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC;QACnE,CAAC;IAAA,CACD;IAED,WAAW,CAAC,OAAmB,EAAQ;QACtC,MAAM,KAAK,GAAwB;YAClC,IAAI,EAAE,SAAS;YACf,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,OAAO;SACP,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAChE,CAAC;IAAA,CACD;IAED,uBAAuB,CAAC,aAAqB,EAAQ;QACpD,MAAM,KAAK,GAA6B;YACvC,IAAI,EAAE,uBAAuB;YAC7B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,aAAa;SACb,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAChE,CAAC;IAAA,CACD;IAED,eAAe,CAAC,QAAgB,EAAE,OAAe,EAAQ;QACxD,MAAM,KAAK,GAAqB;YAC/B,IAAI,EAAE,cAAc;YACpB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,QAAQ;YACR,OAAO;SACP,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAChE,CAAC;IAAA,CACD;IAED,cAAc,CAAC,KAAsB,EAAQ;QAC5C,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAAA,CAC/D;IAED,2CAA2C;IAC3C,WAAW,GAAkB;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACnC,OAAO,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAAA,CACvC;IAED,WAAW,GAAmB;QAC7B,wCAAwC;QACxC,IAAI,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACnC,CAAC;QACD,OAAO,CAAC,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC;IAAA,CACjC;IAED,YAAY,GAAW;QACtB,OAAO,IAAI,CAAC,SAAS,CAAC;IAAA,CACtB;IAED,cAAc,GAAW;QACxB,OAAO,IAAI,CAAC,WAAW,CAAC;IAAA,CACxB;IAED,6CAA6C;IAC7C,uBAAuB,CAAC,QAAsB,EAAW;QACxD,IAAI,IAAI,CAAC,kBAAkB;YAAE,OAAO,KAAK,CAAC;QAC1C,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;QAC/D,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;QACzE,OAAO,YAAY,CAAC,MAAM,IAAI,CAAC,IAAI,iBAAiB,CAAC,MAAM,IAAI,CAAC,CAAC;IAAA,CACjE;IAED,2CAA2C;IAC3C,KAAK,GAAS;QACb,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;QACzB,IAAI,CAAC,eAAe,GAAG,EAAE,CAAC;QAC1B,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;QAChC,IAAI,CAAC,SAAS,GAAG,MAAM,EAAE,CAAC;QAC1B,4BAA4B;QAC5B,IAAI,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,aAAa,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACrC,CAAC;IAAA,CACD;IAED,yCAAyC;IACzC,SAAS,GAAY;QACpB,OAAO,IAAI,CAAC;IAAA,CACZ;IAED,cAAc,CAAC,KAAa,EAAQ;QACnC,4DAA4D;IADxB,CAEpC;IAED,SAAS,GAAiD;QACzD,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC;IAAA,CAChC;IAED,iBAAiB,GAAW;QAC3B,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,aAAa,CAAC;IAAA,CACxC;IAED,6DAA6D;IAC7D,gCAAgC,CAAC,QAAwB,EAAE,kBAA0B,EAAiB;QACrG,OAAO,IAAI,CAAC,CAAC,gCAAgC;IAAjC,CACZ;CACD;AA0BD,MAAM,kBAAkB,GAA0B;IACjD,OAAO,EAAE,IAAI;IACb,aAAa,EAAE,KAAK;IACpB,gBAAgB,EAAE,KAAK;CACvB,CAAC;AAEF,MAAM,aAAa,GAAqB;IACvC,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,CAAC;IACb,WAAW,EAAE,IAAI;CACjB,CAAC;AAEF;;;GAGG;AACH,MAAM,OAAO,kBAAkB;IACtB,YAAY,CAAS;IACrB,QAAQ,CAAc;IAE9B,YAAY,YAAoB,EAAE;QACjC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;QACxD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CAC5B;IAEO,IAAI,GAAgB;QAC3B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YACpC,OAAO,EAAE,CAAC;QACX,CAAC;QAED,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YACzD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,EAAE,CAAC;QACX,CAAC;IAAA,CACD;IAEO,IAAI,GAAS;QACpB,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACvC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACrC,CAAC;YACD,aAAa,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QACnF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,0CAA0C,KAAK,EAAE,CAAC,CAAC;QAClE,CAAC;IAAA,CACD;IAED,qBAAqB,GAA0B;QAC9C,OAAO;YACN,GAAG,kBAAkB;YACrB,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU;SAC3B,CAAC;IAAA,CACF;IAED,oBAAoB,GAAY;QAC/B,OAAO,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,IAAI,kBAAkB,CAAC,OAAO,CAAC;IAAA,CACvE;IAED,oBAAoB,CAAC,OAAgB,EAAQ;QAC5C,IAAI,CAAC,QAAQ,CAAC,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC;QACpE,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CACZ;IAED,gBAAgB,GAAqB;QACpC,OAAO;YACN,GAAG,aAAa;YAChB,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK;SACtB,CAAC;IAAA,CACF;IAED,eAAe,GAAY;QAC1B,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,IAAI,aAAa,CAAC,OAAO,CAAC;IAAA,CAC7D;IAED,eAAe,CAAC,OAAgB,EAAQ;QACvC,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;QAC1D,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CACZ;IAED,eAAe,GAAuB;QACrC,OAAO,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;IAAA,CAClC;IAED,kBAAkB,GAAuB;QACxC,OAAO,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC;IAAA,CACrC;IAED,0BAA0B,CAAC,QAAgB,EAAE,OAAe,EAAQ;QACnE,IAAI,CAAC,QAAQ,CAAC,eAAe,GAAG,QAAQ,CAAC;QACzC,IAAI,CAAC,QAAQ,CAAC,YAAY,GAAG,OAAO,CAAC;QACrC,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CACZ;IAED,uBAAuB,GAAW;QACjC,OAAO,IAAI,CAAC,QAAQ,CAAC,oBAAoB,IAAI,KAAK,CAAC;IAAA,CACnD;IAED,uBAAuB,CAAC,KAAa,EAAQ;QAC5C,IAAI,CAAC,QAAQ,CAAC,oBAAoB,GAAG,KAA4C,CAAC;QAClF,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CACZ;IAED,yCAAyC;IACzC,YAAY,GAA4B;QACvC,OAAO,eAAe,CAAC,CAAC,sCAAsC;IAAvC,CACvB;IAED,YAAY,CAAC,KAA8B,EAAQ;QAClD,gBAAgB;IADmC,CAEnD;IAED,YAAY,GAAa;QACxB,OAAO,EAAE,CAAC,CAAC,wBAAwB;IAAzB,CACV;IAED,cAAc,GAAW;QACxB,OAAO,KAAK,CAAC;IAAA,CACb;CACD;AAED,+EAA+E;AAC/E,kCAAkC;AAClC,+EAA+E;AAE/E;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAkB,EAAE,cAAuB,EAAU;IACrF,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;IAEtD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,CAAC,CAAC;IAEnC,wCAAwC;IACxC,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAU/D,MAAM,WAAW,GAAe,EAAE,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC;YACJ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAa,CAAC;YAC3C,8CAA8C;YAC9C,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;gBAC5C,4BAA4B;gBAC5B,IAAI,cAAc,IAAI,KAAK,CAAC,EAAE,IAAI,cAAc;oBAAE,SAAS;gBAC3D,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;QACF,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACX,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAEvC,8CAA8C;IAC9C,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,MAAM,cAAc,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC1D,MAAM,YAAY,GAAG,cAAc,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACvE,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YACjC,IAAI,CAAC;gBACJ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC/B,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC;oBAC5F,mEAAmE;oBACnE,yDAAyD;oBACzD,sEAAsE;gBACvE,CAAC;YACF,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;QACX,CAAC;IACF,CAAC;IAED,gEAAgE;IAChE,2FAA2F;IAC3F,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAC;IAC3C,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,MAAM,cAAc,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC1D,MAAM,YAAY,GAAG,cAAc,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACvE,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YACjC,IAAI,CAAC;gBACJ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC/B,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;oBAChE,IAAI,OAAO,GACV,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;oBACtG,IAAI,OAAO,EAAE,CAAC;wBACb,oFAAoF;wBACpF,uDAAuD;wBACvD,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,0DAA0D,EAAE,EAAE,CAAC,CAAC;wBAC1F,uFAAuF;wBACvF,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;wBACpE,IAAI,cAAc,KAAK,CAAC,CAAC,EAAE,CAAC;4BAC3B,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;wBAChD,CAAC;wBACD,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBAC/B,CAAC;gBACF,CAAC;YACF,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;QACX,CAAC;IACF,CAAC;IAED,wCAAwC;IACxC,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC;QAC1C,MAAM,OAAO,GAAG,IAAI,QAAQ,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAE7C,6BAA6B;QAC7B,IAAI,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,SAAS;QAE5C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;QACxD,MAAM,KAAK,GAAG;YACb,IAAI,EAAE,SAAS;YACf,SAAS,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE;YAC5C,OAAO,EAAE;gBACR,IAAI,EAAE,MAAM;gBACZ,OAAO;gBACP,SAAS;aACT;SACD,CAAC;QAEF,0BAA0B;QAC1B,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7B,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,CAAC;QAED,cAAc,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAC1D,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,6CAA6C;QAC5E,WAAW,EAAE,CAAC;IACf,CAAC;IAED,OAAO,WAAW,CAAC;AAAA,CACnB","sourcesContent":["/**\n * Context management for mom.\n *\n * Mom uses two files per channel:\n * - context.jsonl: Structured API messages for LLM context (same format as coding-agent sessions)\n * - log.jsonl: Human-readable channel history for grep (no tool results)\n *\n * This module provides:\n * - MomSessionManager: Adapts coding-agent's SessionManager for channel-based storage\n * - MomSettingsManager: Simple settings for mom (compaction, retry, model preferences)\n */\n\nimport type { AgentState, AppMessage } from \"@mariozechner/pi-agent-core\";\nimport {\n\ttype CompactionEntry,\n\ttype LoadedSession,\n\tloadSessionFromEntries,\n\ttype ModelChangeEntry,\n\ttype SessionEntry,\n\ttype SessionHeader,\n\ttype SessionMessageEntry,\n\ttype ThinkingLevelChangeEntry,\n} from \"@mariozechner/pi-coding-agent\";\nimport { randomBytes } from \"crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\n\nfunction uuidv4(): string {\n\tconst bytes = randomBytes(16);\n\tbytes[6] = (bytes[6] & 0x0f) | 0x40;\n\tbytes[8] = (bytes[8] & 0x3f) | 0x80;\n\tconst hex = bytes.toString(\"hex\");\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\n// ============================================================================\n// MomSessionManager - Channel-based session management\n// ============================================================================\n\n/**\n * Session manager for mom, storing context per Slack channel.\n *\n * Unlike coding-agent which creates timestamped session files, mom uses\n * a single context.jsonl per channel that persists across all @mentions.\n */\nexport class MomSessionManager {\n\tprivate sessionId: string;\n\tprivate contextFile: string;\n\tprivate logFile: string;\n\tprivate channelDir: string;\n\tprivate sessionInitialized: boolean = false;\n\tprivate inMemoryEntries: SessionEntry[] = [];\n\tprivate pendingEntries: SessionEntry[] = [];\n\n\tconstructor(channelDir: string, initialModel?: { provider: string; id: string; thinkingLevel?: string }) {\n\t\tthis.channelDir = channelDir;\n\t\tthis.contextFile = join(channelDir, \"context.jsonl\");\n\t\tthis.logFile = join(channelDir, \"log.jsonl\");\n\n\t\t// Ensure channel directory exists\n\t\tif (!existsSync(channelDir)) {\n\t\t\tmkdirSync(channelDir, { recursive: true });\n\t\t}\n\n\t\t// Load existing session or create new\n\t\tif (existsSync(this.contextFile)) {\n\t\t\tthis.inMemoryEntries = this.loadEntriesFromFile();\n\t\t\tthis.sessionId = this.extractSessionId() || uuidv4();\n\t\t\tthis.sessionInitialized = this.inMemoryEntries.length > 0;\n\t\t} else {\n\t\t\t// New session - write header immediately\n\t\t\tthis.sessionId = uuidv4();\n\t\t\tif (initialModel) {\n\t\t\t\tthis.writeSessionHeader(initialModel);\n\t\t\t}\n\t\t}\n\t\t// Note: syncFromLog() is called explicitly from agent.ts with excludeTimestamp\n\t}\n\n\t/** Write session header to file (called on new session creation) */\n\tprivate writeSessionHeader(model: { provider: string; id: string; thinkingLevel?: string }): void {\n\t\tthis.sessionInitialized = true;\n\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: this.channelDir,\n\t\t\tprovider: model.provider,\n\t\t\tmodelId: model.id,\n\t\t\tthinkingLevel: model.thinkingLevel || \"off\",\n\t\t};\n\n\t\tthis.inMemoryEntries.push(entry);\n\t\tappendFileSync(this.contextFile, JSON.stringify(entry) + \"\\n\");\n\t}\n\n\t/**\n\t * Sync user messages from log.jsonl that aren't in context.jsonl.\n\t *\n\t * log.jsonl and context.jsonl must have the same user messages.\n\t * This handles:\n\t * - Backfilled messages (mom was offline)\n\t * - Messages that arrived while mom was processing a previous turn\n\t * - Channel chatter between @mentions\n\t *\n\t * Channel chatter is formatted as \"[username]: message\" to distinguish from direct @mentions.\n\t *\n\t * Called before each agent run.\n\t *\n\t * @param excludeSlackTs Slack timestamp of current message (will be added via prompt(), not sync)\n\t */\n\tsyncFromLog(excludeSlackTs?: string): void {\n\t\tif (!existsSync(this.logFile)) return;\n\n\t\t// Build set of Slack timestamps already in context\n\t\t// We store slackTs in the message content or can extract from formatted messages\n\t\t// For messages synced from log, we use the log's date as the entry timestamp\n\t\t// For messages added via prompt(), they have different timestamps\n\t\t// So we need to match by content OR by stored slackTs\n\t\tconst contextSlackTimestamps = new Set<string>();\n\t\tconst contextMessageTexts = new Set<string>();\n\n\t\tfor (const entry of this.inMemoryEntries) {\n\t\t\tif (entry.type === \"message\") {\n\t\t\t\tconst msgEntry = entry as SessionMessageEntry;\n\t\t\t\t// Store the entry timestamp (which is the log date for synced messages)\n\t\t\t\tcontextSlackTimestamps.add(entry.timestamp);\n\n\t\t\t\t// Also store message text to catch duplicates added via prompt()\n\t\t\t\t// AppMessage has different shapes, check for content property\n\t\t\t\tconst msg = msgEntry.message as { role: string; content?: unknown };\n\t\t\t\tif (msg.role === \"user\" && msg.content !== undefined) {\n\t\t\t\t\tconst content = msg.content;\n\t\t\t\t\tif (typeof content === \"string\") {\n\t\t\t\t\t\tcontextMessageTexts.add(content);\n\t\t\t\t\t} else if (Array.isArray(content)) {\n\t\t\t\t\t\tfor (const part of content) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\ttypeof part === \"object\" &&\n\t\t\t\t\t\t\t\tpart !== null &&\n\t\t\t\t\t\t\t\t\"type\" in part &&\n\t\t\t\t\t\t\t\tpart.type === \"text\" &&\n\t\t\t\t\t\t\t\t\"text\" in part\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontextMessageTexts.add((part as { type: \"text\"; text: string }).text);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Read log.jsonl and find user messages not in context\n\t\tconst logContent = readFileSync(this.logFile, \"utf-8\");\n\t\tconst logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n\t\tinterface LogMessage {\n\t\t\tdate?: string;\n\t\t\tts?: string;\n\t\t\tuser?: string;\n\t\t\tuserName?: string;\n\t\t\ttext?: string;\n\t\t\tisBot?: boolean;\n\t\t}\n\n\t\tconst newMessages: Array<{ timestamp: string; slackTs: string; message: AppMessage }> = [];\n\n\t\tfor (const line of logLines) {\n\t\t\ttry {\n\t\t\t\tconst logMsg: LogMessage = JSON.parse(line);\n\n\t\t\t\tconst slackTs = logMsg.ts;\n\t\t\t\tconst date = logMsg.date;\n\t\t\t\tif (!slackTs || !date) continue;\n\n\t\t\t\t// Skip the current message being processed (will be added via prompt())\n\t\t\t\tif (excludeSlackTs && slackTs === excludeSlackTs) continue;\n\n\t\t\t\t// Skip bot messages - added through agent flow\n\t\t\t\tif (logMsg.isBot) continue;\n\n\t\t\t\t// Skip if this date is already in context (was synced before)\n\t\t\t\tif (contextSlackTimestamps.has(date)) continue;\n\n\t\t\t\t// Build the message text as it would appear in context\n\t\t\t\tconst messageText = `[${logMsg.userName || logMsg.user || \"unknown\"}]: ${logMsg.text || \"\"}`;\n\n\t\t\t\t// Skip if this exact message text is already in context (added via prompt())\n\t\t\t\tif (contextMessageTexts.has(messageText)) continue;\n\n\t\t\t\tconst msgTime = new Date(date).getTime() || Date.now();\n\t\t\t\tconst userMessage: AppMessage = {\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: messageText,\n\t\t\t\t\ttimestamp: msgTime,\n\t\t\t\t};\n\n\t\t\t\tnewMessages.push({ timestamp: date, slackTs, message: userMessage });\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\tif (newMessages.length === 0) return;\n\n\t\t// Sort by timestamp and add to context\n\t\tnewMessages.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());\n\n\t\tfor (const { timestamp, message } of newMessages) {\n\t\t\tconst entry: SessionMessageEntry = {\n\t\t\t\ttype: \"message\",\n\t\t\t\ttimestamp, // Use log date as entry timestamp for consistent deduplication\n\t\t\t\tmessage,\n\t\t\t};\n\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tprivate extractSessionId(): string | null {\n\t\tfor (const entry of this.inMemoryEntries) {\n\t\t\tif (entry.type === \"session\") {\n\t\t\t\treturn entry.id;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate loadEntriesFromFile(): SessionEntry[] {\n\t\tif (!existsSync(this.contextFile)) return [];\n\n\t\tconst content = readFileSync(this.contextFile, \"utf8\");\n\t\tconst entries: SessionEntry[] = [];\n\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\tfor (const line of lines) {\n\t\t\tif (!line.trim()) continue;\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line) as SessionEntry;\n\t\t\t\tentries.push(entry);\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn entries;\n\t}\n\n\t/** Initialize session with header if not already done */\n\tstartSession(state: AgentState): void {\n\t\tif (this.sessionInitialized) return;\n\t\tthis.sessionInitialized = true;\n\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: this.channelDir,\n\t\t\tprovider: state.model?.provider || \"unknown\",\n\t\t\tmodelId: state.model?.id || \"unknown\",\n\t\t\tthinkingLevel: state.thinkingLevel,\n\t\t};\n\n\t\tthis.inMemoryEntries.push(entry);\n\t\tfor (const pending of this.pendingEntries) {\n\t\t\tthis.inMemoryEntries.push(pending);\n\t\t}\n\t\tthis.pendingEntries = [];\n\n\t\t// Write to file\n\t\tappendFileSync(this.contextFile, JSON.stringify(entry) + \"\\n\");\n\t\tfor (const memEntry of this.inMemoryEntries.slice(1)) {\n\t\t\tappendFileSync(this.contextFile, JSON.stringify(memEntry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveMessage(message: AppMessage): void {\n\t\tconst entry: SessionMessageEntry = {\n\t\t\ttype: \"message\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingEntries.push(entry);\n\t\t} else {\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveThinkingLevelChange(thinkingLevel: string): void {\n\t\tconst entry: ThinkingLevelChangeEntry = {\n\t\t\ttype: \"thinking_level_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingEntries.push(entry);\n\t\t} else {\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveModelChange(provider: string, modelId: string): void {\n\t\tconst entry: ModelChangeEntry = {\n\t\t\ttype: \"model_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tprovider,\n\t\t\tmodelId,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingEntries.push(entry);\n\t\t} else {\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveCompaction(entry: CompactionEntry): void {\n\t\tthis.inMemoryEntries.push(entry);\n\t\tappendFileSync(this.contextFile, JSON.stringify(entry) + \"\\n\");\n\t}\n\n\t/** Load session with compaction support */\n\tloadSession(): LoadedSession {\n\t\tconst entries = this.loadEntries();\n\t\treturn loadSessionFromEntries(entries);\n\t}\n\n\tloadEntries(): SessionEntry[] {\n\t\t// Re-read from file to get latest state\n\t\tif (existsSync(this.contextFile)) {\n\t\t\treturn this.loadEntriesFromFile();\n\t\t}\n\t\treturn [...this.inMemoryEntries];\n\t}\n\n\tgetSessionId(): string {\n\t\treturn this.sessionId;\n\t}\n\n\tgetSessionFile(): string {\n\t\treturn this.contextFile;\n\t}\n\n\t/** Check if session should be initialized */\n\tshouldInitializeSession(messages: AppMessage[]): boolean {\n\t\tif (this.sessionInitialized) return false;\n\t\tconst userMessages = messages.filter((m) => m.role === \"user\");\n\t\tconst assistantMessages = messages.filter((m) => m.role === \"assistant\");\n\t\treturn userMessages.length >= 1 && assistantMessages.length >= 1;\n\t}\n\n\t/** Reset session (clears context.jsonl) */\n\treset(): void {\n\t\tthis.pendingEntries = [];\n\t\tthis.inMemoryEntries = [];\n\t\tthis.sessionInitialized = false;\n\t\tthis.sessionId = uuidv4();\n\t\t// Truncate the context file\n\t\tif (existsSync(this.contextFile)) {\n\t\t\twriteFileSync(this.contextFile, \"\");\n\t\t}\n\t}\n\n\t// Compatibility methods for AgentSession\n\tisEnabled(): boolean {\n\t\treturn true;\n\t}\n\n\tsetSessionFile(_path: string): void {\n\t\t// No-op for mom - we always use the channel's context.jsonl\n\t}\n\n\tloadModel(): { provider: string; modelId: string } | null {\n\t\treturn this.loadSession().model;\n\t}\n\n\tloadThinkingLevel(): string {\n\t\treturn this.loadSession().thinkingLevel;\n\t}\n\n\t/** Not used by mom but required by AgentSession interface */\n\tcreateBranchedSessionFromEntries(_entries: SessionEntry[], _branchBeforeIndex: number): string | null {\n\t\treturn null; // Mom doesn't support branching\n\t}\n}\n\n// ============================================================================\n// MomSettingsManager - Simple settings for mom\n// ============================================================================\n\nexport interface MomCompactionSettings {\n\tenabled: boolean;\n\treserveTokens: number;\n\tkeepRecentTokens: number;\n}\n\nexport interface MomRetrySettings {\n\tenabled: boolean;\n\tmaxRetries: number;\n\tbaseDelayMs: number;\n}\n\nexport interface MomSettings {\n\tdefaultProvider?: string;\n\tdefaultModel?: string;\n\tdefaultThinkingLevel?: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\";\n\tcompaction?: Partial<MomCompactionSettings>;\n\tretry?: Partial<MomRetrySettings>;\n}\n\nconst DEFAULT_COMPACTION: MomCompactionSettings = {\n\tenabled: true,\n\treserveTokens: 16384,\n\tkeepRecentTokens: 20000,\n};\n\nconst DEFAULT_RETRY: MomRetrySettings = {\n\tenabled: true,\n\tmaxRetries: 3,\n\tbaseDelayMs: 2000,\n};\n\n/**\n * Settings manager for mom.\n * Stores settings in the workspace root directory.\n */\nexport class MomSettingsManager {\n\tprivate settingsPath: string;\n\tprivate settings: MomSettings;\n\n\tconstructor(workspaceDir: string) {\n\t\tthis.settingsPath = join(workspaceDir, \"settings.json\");\n\t\tthis.settings = this.load();\n\t}\n\n\tprivate load(): MomSettings {\n\t\tif (!existsSync(this.settingsPath)) {\n\t\t\treturn {};\n\t\t}\n\n\t\ttry {\n\t\t\tconst content = readFileSync(this.settingsPath, \"utf-8\");\n\t\t\treturn JSON.parse(content);\n\t\t} catch {\n\t\t\treturn {};\n\t\t}\n\t}\n\n\tprivate save(): void {\n\t\ttry {\n\t\t\tconst dir = dirname(this.settingsPath);\n\t\t\tif (!existsSync(dir)) {\n\t\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\t}\n\t\t\twriteFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2), \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(`Warning: Could not save settings file: ${error}`);\n\t\t}\n\t}\n\n\tgetCompactionSettings(): MomCompactionSettings {\n\t\treturn {\n\t\t\t...DEFAULT_COMPACTION,\n\t\t\t...this.settings.compaction,\n\t\t};\n\t}\n\n\tgetCompactionEnabled(): boolean {\n\t\treturn this.settings.compaction?.enabled ?? DEFAULT_COMPACTION.enabled;\n\t}\n\n\tsetCompactionEnabled(enabled: boolean): void {\n\t\tthis.settings.compaction = { ...this.settings.compaction, enabled };\n\t\tthis.save();\n\t}\n\n\tgetRetrySettings(): MomRetrySettings {\n\t\treturn {\n\t\t\t...DEFAULT_RETRY,\n\t\t\t...this.settings.retry,\n\t\t};\n\t}\n\n\tgetRetryEnabled(): boolean {\n\t\treturn this.settings.retry?.enabled ?? DEFAULT_RETRY.enabled;\n\t}\n\n\tsetRetryEnabled(enabled: boolean): void {\n\t\tthis.settings.retry = { ...this.settings.retry, enabled };\n\t\tthis.save();\n\t}\n\n\tgetDefaultModel(): string | undefined {\n\t\treturn this.settings.defaultModel;\n\t}\n\n\tgetDefaultProvider(): string | undefined {\n\t\treturn this.settings.defaultProvider;\n\t}\n\n\tsetDefaultModelAndProvider(provider: string, modelId: string): void {\n\t\tthis.settings.defaultProvider = provider;\n\t\tthis.settings.defaultModel = modelId;\n\t\tthis.save();\n\t}\n\n\tgetDefaultThinkingLevel(): string {\n\t\treturn this.settings.defaultThinkingLevel || \"off\";\n\t}\n\n\tsetDefaultThinkingLevel(level: string): void {\n\t\tthis.settings.defaultThinkingLevel = level as MomSettings[\"defaultThinkingLevel\"];\n\t\tthis.save();\n\t}\n\n\t// Compatibility methods for AgentSession\n\tgetQueueMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn \"one-at-a-time\"; // Mom processes one message at a time\n\t}\n\n\tsetQueueMode(_mode: \"all\" | \"one-at-a-time\"): void {\n\t\t// No-op for mom\n\t}\n\n\tgetHookPaths(): string[] {\n\t\treturn []; // Mom doesn't use hooks\n\t}\n\n\tgetHookTimeout(): number {\n\t\treturn 30000;\n\t}\n}\n\n// ============================================================================\n// Sync log.jsonl to context.jsonl\n// ============================================================================\n\n/**\n * Sync user messages from log.jsonl to context.jsonl.\n *\n * This ensures that messages logged while mom wasn't running (channel chatter,\n * backfilled messages, messages while busy) are added to the LLM context.\n *\n * @param channelDir - Path to channel directory\n * @param excludeAfterTs - Don't sync messages with ts >= this value (they'll be handled by agent)\n * @returns Number of messages synced\n */\nexport function syncLogToContext(channelDir: string, excludeAfterTs?: string): number {\n\tconst logFile = join(channelDir, \"log.jsonl\");\n\tconst contextFile = join(channelDir, \"context.jsonl\");\n\n\tif (!existsSync(logFile)) return 0;\n\n\t// Read all user messages from log.jsonl\n\tconst logContent = readFileSync(logFile, \"utf-8\");\n\tconst logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n\tinterface LogEntry {\n\t\tts: string;\n\t\tuser: string;\n\t\tuserName?: string;\n\t\ttext: string;\n\t\tisBot: boolean;\n\t}\n\n\tconst logMessages: LogEntry[] = [];\n\tfor (const line of logLines) {\n\t\ttry {\n\t\t\tconst entry = JSON.parse(line) as LogEntry;\n\t\t\t// Only sync user messages (not bot responses)\n\t\t\tif (!entry.isBot && entry.ts && entry.text) {\n\t\t\t\t// Skip if >= excludeAfterTs\n\t\t\t\tif (excludeAfterTs && entry.ts >= excludeAfterTs) continue;\n\t\t\t\tlogMessages.push(entry);\n\t\t\t}\n\t\t} catch {}\n\t}\n\n\tif (logMessages.length === 0) return 0;\n\n\t// Read existing timestamps from context.jsonl\n\tconst existingTs = new Set<string>();\n\tif (existsSync(contextFile)) {\n\t\tconst contextContent = readFileSync(contextFile, \"utf-8\");\n\t\tconst contextLines = contextContent.trim().split(\"\\n\").filter(Boolean);\n\t\tfor (const line of contextLines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"message\" && entry.message?.role === \"user\" && entry.message?.timestamp) {\n\t\t\t\t\t// Extract ts from timestamp (ms -> slack ts format for comparison)\n\t\t\t\t\t// We store the original slack ts in a way we can recover\n\t\t\t\t\t// Actually, let's just check by content match since ts formats differ\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\n\t// For deduplication, we need to track what's already in context\n\t// Read context and extract user message content (strip attachments section for comparison)\n\tconst existingMessages = new Set<string>();\n\tif (existsSync(contextFile)) {\n\t\tconst contextContent = readFileSync(contextFile, \"utf-8\");\n\t\tconst contextLines = contextContent.trim().split(\"\\n\").filter(Boolean);\n\t\tfor (const line of contextLines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"message\" && entry.message?.role === \"user\") {\n\t\t\t\t\tlet content =\n\t\t\t\t\t\ttypeof entry.message.content === \"string\" ? entry.message.content : entry.message.content?.[0]?.text;\n\t\t\t\t\tif (content) {\n\t\t\t\t\t\t// Strip timestamp prefix for comparison (live messages have it, log messages don't)\n\t\t\t\t\t\t// Format: [YYYY-MM-DD HH:MM:SS+HH:MM] [username]: text\n\t\t\t\t\t\tcontent = content.replace(/^\\[\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}[+-]\\d{2}:\\d{2}\\] /, \"\");\n\t\t\t\t\t\t// Strip attachments section for comparison (live messages have it, log messages don't)\n\t\t\t\t\t\tconst attachmentsIdx = content.indexOf(\"\\n\\n<slack_attachments>\\n\");\n\t\t\t\t\t\tif (attachmentsIdx !== -1) {\n\t\t\t\t\t\t\tcontent = content.substring(0, attachmentsIdx);\n\t\t\t\t\t\t}\n\t\t\t\t\t\texistingMessages.add(content);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\n\t// Add missing messages to context.jsonl\n\tlet syncedCount = 0;\n\tfor (const msg of logMessages) {\n\t\tconst userName = msg.userName || msg.user;\n\t\tconst content = `[${userName}]: ${msg.text}`;\n\n\t\t// Skip if already in context\n\t\tif (existingMessages.has(content)) continue;\n\n\t\tconst timestamp = Math.floor(parseFloat(msg.ts) * 1000);\n\t\tconst entry = {\n\t\t\ttype: \"message\",\n\t\t\ttimestamp: new Date(timestamp).toISOString(),\n\t\t\tmessage: {\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent,\n\t\t\t\ttimestamp,\n\t\t\t},\n\t\t};\n\n\t\t// Ensure directory exists\n\t\tif (!existsSync(channelDir)) {\n\t\t\tmkdirSync(channelDir, { recursive: true });\n\t\t}\n\n\t\tappendFileSync(contextFile, JSON.stringify(entry) + \"\\n\");\n\t\texistingMessages.add(content); // Track to avoid duplicates within this sync\n\t\tsyncedCount++;\n\t}\n\n\treturn syncedCount;\n}\n"]}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAGN,sBAAsB,GAMtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACxF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAErC,SAAS,MAAM,GAAW;IACzB,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC9B,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACpC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACpC,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;AAAA,CAC/G;AAED,+EAA+E;AAC/E,uDAAuD;AACvD,+EAA+E;AAE/E;;;;;GAKG;AACH,MAAM,OAAO,iBAAiB;IACrB,SAAS,CAAS;IAClB,WAAW,CAAS;IACpB,OAAO,CAAS;IAChB,UAAU,CAAS;IACnB,kBAAkB,GAAY,KAAK,CAAC;IACpC,eAAe,GAAmB,EAAE,CAAC;IACrC,cAAc,GAAmB,EAAE,CAAC;IAE5C,YAAY,UAAkB,EAAE,YAAuE,EAAE;QACxG,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QACrD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QAE7C,kCAAkC;QAClC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7B,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,CAAC;QAED,sCAAsC;QACtC,IAAI,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAClD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,MAAM,EAAE,CAAC;YACrD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC;QAC3D,CAAC;aAAM,CAAC;YACP,yCAAyC;YACzC,IAAI,CAAC,SAAS,GAAG,MAAM,EAAE,CAAC;YAC1B,IAAI,YAAY,EAAE,CAAC;gBAClB,IAAI,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;YACvC,CAAC;QACF,CAAC;QACD,+EAA+E;IAD9E,CAED;IAED,oEAAoE;IAC5D,kBAAkB,CAAC,KAA+D,EAAQ;QACjG,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAE/B,MAAM,KAAK,GAAkB;YAC5B,IAAI,EAAE,SAAS;YACf,EAAE,EAAE,IAAI,CAAC,SAAS;YAClB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,GAAG,EAAE,IAAI,CAAC,UAAU;YACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,OAAO,EAAE,KAAK,CAAC,EAAE;YACjB,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,KAAK;SAC3C,CAAC;QAEF,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAAA,CAC/D;IAED;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,cAAuB,EAAQ;QAC1C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO;QAEtC,mDAAmD;QACnD,iFAAiF;QACjF,6EAA6E;QAC7E,kEAAkE;QAClE,sDAAsD;QACtD,MAAM,sBAAsB,GAAG,IAAI,GAAG,EAAU,CAAC;QACjD,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAU,CAAC;QAE9C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC1C,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC9B,MAAM,QAAQ,GAAG,KAA4B,CAAC;gBAC9C,wEAAwE;gBACxE,sBAAsB,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;gBAE5C,iEAAiE;gBACjE,8DAA8D;gBAC9D,MAAM,GAAG,GAAG,QAAQ,CAAC,OAA8C,CAAC;gBACpE,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;oBACtD,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;oBAC5B,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;wBACjC,mBAAmB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBAClC,CAAC;yBAAM,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;wBACnC,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;4BAC5B,IACC,OAAO,IAAI,KAAK,QAAQ;gCACxB,IAAI,KAAK,IAAI;gCACb,MAAM,IAAI,IAAI;gCACd,IAAI,CAAC,IAAI,KAAK,MAAM;gCACpB,MAAM,IAAI,IAAI,EACb,CAAC;gCACF,mBAAmB,CAAC,GAAG,CAAE,IAAuC,CAAC,IAAI,CAAC,CAAC;4BACxE,CAAC;wBACF,CAAC;oBACF,CAAC;gBACF,CAAC;YACF,CAAC;QACF,CAAC;QAED,uDAAuD;QACvD,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAW/D,MAAM,WAAW,GAAuE,EAAE,CAAC;QAE3F,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACJ,MAAM,MAAM,GAAe,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAE5C,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC;gBAC1B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;gBACzB,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI;oBAAE,SAAS;gBAEhC,wEAAwE;gBACxE,IAAI,cAAc,IAAI,OAAO,KAAK,cAAc;oBAAE,SAAS;gBAE3D,+CAA+C;gBAC/C,IAAI,MAAM,CAAC,KAAK;oBAAE,SAAS;gBAE3B,8DAA8D;gBAC9D,IAAI,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAE/C,uDAAuD;gBACvD,MAAM,WAAW,GAAG,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,IAAI,IAAI,SAAS,MAAM,MAAM,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;gBAE7F,6EAA6E;gBAC7E,IAAI,mBAAmB,CAAC,GAAG,CAAC,WAAW,CAAC;oBAAE,SAAS;gBAEnD,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;gBACvD,MAAM,WAAW,GAAe;oBAC/B,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,WAAW;oBACpB,SAAS,EAAE,OAAO;iBAClB,CAAC;gBAEF,WAAW,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC;YACtE,CAAC;YAAC,MAAM,CAAC;gBACR,uBAAuB;YACxB,CAAC;QACF,CAAC;QAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAErC,uCAAuC;QACvC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAE9F,KAAK,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,WAAW,EAAE,CAAC;YAClD,MAAM,KAAK,GAAwB;gBAClC,IAAI,EAAE,SAAS;gBACf,SAAS,EAAE,+DAA+D;gBAC1E,OAAO;aACP,CAAC;YAEF,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChE,CAAC;IAAA,CACD;IAEO,gBAAgB,GAAkB;QACzC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC1C,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC9B,OAAO,KAAK,CAAC,EAAE,CAAC;YACjB,CAAC;QACF,CAAC;QACD,OAAO,IAAI,CAAC;IAAA,CACZ;IAEO,mBAAmB,GAAmB;QAC7C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC;YAAE,OAAO,EAAE,CAAC;QAE7C,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACvD,MAAM,OAAO,GAAmB,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;gBAAE,SAAS;YAC3B,IAAI,CAAC;gBACJ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAiB,CAAC;gBAC/C,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACR,uBAAuB;YACxB,CAAC;QACF,CAAC;QAED,OAAO,OAAO,CAAC;IAAA,CACf;IAED,yDAAyD;IACzD,YAAY,CAAC,KAAiB,EAAQ;QACrC,IAAI,IAAI,CAAC,kBAAkB;YAAE,OAAO;QACpC,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAE/B,MAAM,KAAK,GAAkB;YAC5B,IAAI,EAAE,SAAS;YACf,EAAE,EAAE,IAAI,CAAC,SAAS;YAClB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,GAAG,EAAE,IAAI,CAAC,UAAU;YACpB,QAAQ,EAAE,KAAK,CAAC,KAAK,EAAE,QAAQ,IAAI,SAAS;YAC5C,OAAO,EAAE,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,SAAS;YACrC,aAAa,EAAE,KAAK,CAAC,aAAa;SAClC,CAAC;QAEF,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YAC3C,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;QAEzB,gBAAgB;QAChB,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/D,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YACtD,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC;IAAA,CACD;IAED,WAAW,CAAC,OAAmB,EAAQ;QACtC,MAAM,KAAK,GAAwB;YAClC,IAAI,EAAE,SAAS;YACf,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,OAAO;SACP,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChE,CAAC;IAAA,CACD;IAED,uBAAuB,CAAC,aAAqB,EAAQ;QACpD,MAAM,KAAK,GAA6B;YACvC,IAAI,EAAE,uBAAuB;YAC7B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,aAAa;SACb,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChE,CAAC;IAAA,CACD;IAED,eAAe,CAAC,QAAgB,EAAE,OAAe,EAAQ;QACxD,MAAM,KAAK,GAAqB;YAC/B,IAAI,EAAE,cAAc;YACpB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,QAAQ;YACR,OAAO;SACP,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChE,CAAC;IAAA,CACD;IAED,cAAc,CAAC,KAAsB,EAAQ;QAC5C,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAAA,CAC/D;IAED,2CAA2C;IAC3C,WAAW,GAAkB;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACnC,OAAO,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAAA,CACvC;IAED,WAAW,GAAmB;QAC7B,wCAAwC;QACxC,IAAI,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACnC,CAAC;QACD,OAAO,CAAC,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC;IAAA,CACjC;IAED,YAAY,GAAW;QACtB,OAAO,IAAI,CAAC,SAAS,CAAC;IAAA,CACtB;IAED,cAAc,GAAW;QACxB,OAAO,IAAI,CAAC,WAAW,CAAC;IAAA,CACxB;IAED,6CAA6C;IAC7C,uBAAuB,CAAC,QAAsB,EAAW;QACxD,IAAI,IAAI,CAAC,kBAAkB;YAAE,OAAO,KAAK,CAAC;QAC1C,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;QAC/D,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;QACzE,OAAO,YAAY,CAAC,MAAM,IAAI,CAAC,IAAI,iBAAiB,CAAC,MAAM,IAAI,CAAC,CAAC;IAAA,CACjE;IAED,2CAA2C;IAC3C,KAAK,GAAS;QACb,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;QACzB,IAAI,CAAC,eAAe,GAAG,EAAE,CAAC;QAC1B,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;QAChC,IAAI,CAAC,SAAS,GAAG,MAAM,EAAE,CAAC;QAC1B,4BAA4B;QAC5B,IAAI,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,aAAa,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACrC,CAAC;IAAA,CACD;IAED,yCAAyC;IACzC,SAAS,GAAY;QACpB,OAAO,IAAI,CAAC;IAAA,CACZ;IAED,cAAc,CAAC,KAAa,EAAQ;QACnC,4DAA4D;IADxB,CAEpC;IAED,SAAS,GAAiD;QACzD,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC;IAAA,CAChC;IAED,iBAAiB,GAAW;QAC3B,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,aAAa,CAAC;IAAA,CACxC;IAED,6DAA6D;IAC7D,gCAAgC,CAAC,QAAwB,EAAE,kBAA0B,EAAiB;QACrG,OAAO,IAAI,CAAC,CAAC,gCAAgC;IAAjC,CACZ;CACD;AA0BD,MAAM,kBAAkB,GAA0B;IACjD,OAAO,EAAE,IAAI;IACb,aAAa,EAAE,KAAK;IACpB,gBAAgB,EAAE,KAAK;CACvB,CAAC;AAEF,MAAM,aAAa,GAAqB;IACvC,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,CAAC;IACb,WAAW,EAAE,IAAI;CACjB,CAAC;AAEF;;;GAGG;AACH,MAAM,OAAO,kBAAkB;IACtB,YAAY,CAAS;IACrB,QAAQ,CAAc;IAE9B,YAAY,YAAoB,EAAE;QACjC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;QACxD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CAC5B;IAEO,IAAI,GAAgB;QAC3B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YACpC,OAAO,EAAE,CAAC;QACX,CAAC;QAED,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YACzD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,EAAE,CAAC;QACX,CAAC;IAAA,CACD;IAEO,IAAI,GAAS;QACpB,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACvC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACrC,CAAC;YACD,aAAa,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QACnF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,0CAA0C,KAAK,EAAE,CAAC,CAAC;QAClE,CAAC;IAAA,CACD;IAED,qBAAqB,GAA0B;QAC9C,OAAO;YACN,GAAG,kBAAkB;YACrB,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU;SAC3B,CAAC;IAAA,CACF;IAED,oBAAoB,GAAY;QAC/B,OAAO,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,IAAI,kBAAkB,CAAC,OAAO,CAAC;IAAA,CACvE;IAED,oBAAoB,CAAC,OAAgB,EAAQ;QAC5C,IAAI,CAAC,QAAQ,CAAC,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC;QACpE,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CACZ;IAED,gBAAgB,GAAqB;QACpC,OAAO;YACN,GAAG,aAAa;YAChB,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK;SACtB,CAAC;IAAA,CACF;IAED,eAAe,GAAY;QAC1B,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,IAAI,aAAa,CAAC,OAAO,CAAC;IAAA,CAC7D;IAED,eAAe,CAAC,OAAgB,EAAQ;QACvC,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;QAC1D,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CACZ;IAED,eAAe,GAAuB;QACrC,OAAO,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;IAAA,CAClC;IAED,kBAAkB,GAAuB;QACxC,OAAO,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC;IAAA,CACrC;IAED,0BAA0B,CAAC,QAAgB,EAAE,OAAe,EAAQ;QACnE,IAAI,CAAC,QAAQ,CAAC,eAAe,GAAG,QAAQ,CAAC;QACzC,IAAI,CAAC,QAAQ,CAAC,YAAY,GAAG,OAAO,CAAC;QACrC,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CACZ;IAED,uBAAuB,GAAW;QACjC,OAAO,IAAI,CAAC,QAAQ,CAAC,oBAAoB,IAAI,KAAK,CAAC;IAAA,CACnD;IAED,uBAAuB,CAAC,KAAa,EAAQ;QAC5C,IAAI,CAAC,QAAQ,CAAC,oBAAoB,GAAG,KAA4C,CAAC;QAClF,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CACZ;IAED,yCAAyC;IACzC,YAAY,GAA4B;QACvC,OAAO,eAAe,CAAC,CAAC,sCAAsC;IAAvC,CACvB;IAED,YAAY,CAAC,KAA8B,EAAQ;QAClD,gBAAgB;IADmC,CAEnD;IAED,YAAY,GAAa;QACxB,OAAO,EAAE,CAAC,CAAC,wBAAwB;IAAzB,CACV;IAED,cAAc,GAAW;QACxB,OAAO,KAAK,CAAC;IAAA,CACb;CACD;AAED,+EAA+E;AAC/E,kCAAkC;AAClC,+EAA+E;AAE/E;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAkB,EAAE,cAAuB,EAAU;IACrF,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;IAEtD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,CAAC,CAAC;IAEnC,wCAAwC;IACxC,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAU/D,MAAM,WAAW,GAAe,EAAE,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC;YACJ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAa,CAAC;YAC3C,8CAA8C;YAC9C,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;gBAC5C,4BAA4B;gBAC5B,IAAI,cAAc,IAAI,KAAK,CAAC,EAAE,IAAI,cAAc;oBAAE,SAAS;gBAC3D,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;QACF,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACX,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAEvC,8CAA8C;IAC9C,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,MAAM,cAAc,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC1D,MAAM,YAAY,GAAG,cAAc,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACvE,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YACjC,IAAI,CAAC;gBACJ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC/B,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC;oBAC5F,mEAAmE;oBACnE,yDAAyD;oBACzD,sEAAsE;gBACvE,CAAC;YACF,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;QACX,CAAC;IACF,CAAC;IAED,gEAAgE;IAChE,2FAA2F;IAC3F,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAC;IAC3C,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,MAAM,cAAc,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC1D,MAAM,YAAY,GAAG,cAAc,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACvE,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YACjC,IAAI,CAAC;gBACJ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC/B,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;oBAChE,IAAI,OAAO,GACV,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;oBACtG,IAAI,OAAO,EAAE,CAAC;wBACb,oFAAoF;wBACpF,uDAAuD;wBACvD,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,0DAA0D,EAAE,EAAE,CAAC,CAAC;wBAC1F,uFAAuF;wBACvF,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;wBACpE,IAAI,cAAc,KAAK,CAAC,CAAC,EAAE,CAAC;4BAC3B,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;wBAChD,CAAC;wBACD,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBAC/B,CAAC;gBACF,CAAC;YACF,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;QACX,CAAC;IACF,CAAC;IAED,wCAAwC;IACxC,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC;QAC1C,MAAM,OAAO,GAAG,IAAI,QAAQ,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAE7C,6BAA6B;QAC7B,IAAI,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,SAAS;QAE5C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;QACxD,MAAM,KAAK,GAAG;YACb,IAAI,EAAE,SAAS;YACf,SAAS,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE;YAC5C,OAAO,EAAE;gBACR,IAAI,EAAE,MAAM;gBACZ,OAAO;gBACP,SAAS;aACT;SACD,CAAC;QAEF,0BAA0B;QAC1B,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7B,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,CAAC;QAED,cAAc,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1D,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,6CAA6C;QAC5E,WAAW,EAAE,CAAC;IACf,CAAC;IAED,OAAO,WAAW,CAAC;AAAA,CACnB","sourcesContent":["/**\n * Context management for mom.\n *\n * Mom uses two files per channel:\n * - context.jsonl: Structured API messages for LLM context (same format as coding-agent sessions)\n * - log.jsonl: Human-readable channel history for grep (no tool results)\n *\n * This module provides:\n * - MomSessionManager: Adapts coding-agent's SessionManager for channel-based storage\n * - MomSettingsManager: Simple settings for mom (compaction, retry, model preferences)\n */\n\nimport type { AgentState, AppMessage } from \"@mariozechner/pi-agent-core\";\nimport {\n\ttype CompactionEntry,\n\ttype LoadedSession,\n\tloadSessionFromEntries,\n\ttype ModelChangeEntry,\n\ttype SessionEntry,\n\ttype SessionHeader,\n\ttype SessionMessageEntry,\n\ttype ThinkingLevelChangeEntry,\n} from \"@mariozechner/pi-coding-agent\";\nimport { randomBytes } from \"crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\n\nfunction uuidv4(): string {\n\tconst bytes = randomBytes(16);\n\tbytes[6] = (bytes[6] & 0x0f) | 0x40;\n\tbytes[8] = (bytes[8] & 0x3f) | 0x80;\n\tconst hex = bytes.toString(\"hex\");\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\n// ============================================================================\n// MomSessionManager - Channel-based session management\n// ============================================================================\n\n/**\n * Session manager for mom, storing context per Slack channel.\n *\n * Unlike coding-agent which creates timestamped session files, mom uses\n * a single context.jsonl per channel that persists across all @mentions.\n */\nexport class MomSessionManager {\n\tprivate sessionId: string;\n\tprivate contextFile: string;\n\tprivate logFile: string;\n\tprivate channelDir: string;\n\tprivate sessionInitialized: boolean = false;\n\tprivate inMemoryEntries: SessionEntry[] = [];\n\tprivate pendingEntries: SessionEntry[] = [];\n\n\tconstructor(channelDir: string, initialModel?: { provider: string; id: string; thinkingLevel?: string }) {\n\t\tthis.channelDir = channelDir;\n\t\tthis.contextFile = join(channelDir, \"context.jsonl\");\n\t\tthis.logFile = join(channelDir, \"log.jsonl\");\n\n\t\t// Ensure channel directory exists\n\t\tif (!existsSync(channelDir)) {\n\t\t\tmkdirSync(channelDir, { recursive: true });\n\t\t}\n\n\t\t// Load existing session or create new\n\t\tif (existsSync(this.contextFile)) {\n\t\t\tthis.inMemoryEntries = this.loadEntriesFromFile();\n\t\t\tthis.sessionId = this.extractSessionId() || uuidv4();\n\t\t\tthis.sessionInitialized = this.inMemoryEntries.length > 0;\n\t\t} else {\n\t\t\t// New session - write header immediately\n\t\t\tthis.sessionId = uuidv4();\n\t\t\tif (initialModel) {\n\t\t\t\tthis.writeSessionHeader(initialModel);\n\t\t\t}\n\t\t}\n\t\t// Note: syncFromLog() is called explicitly from agent.ts with excludeTimestamp\n\t}\n\n\t/** Write session header to file (called on new session creation) */\n\tprivate writeSessionHeader(model: { provider: string; id: string; thinkingLevel?: string }): void {\n\t\tthis.sessionInitialized = true;\n\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: this.channelDir,\n\t\t\tprovider: model.provider,\n\t\t\tmodelId: model.id,\n\t\t\tthinkingLevel: model.thinkingLevel || \"off\",\n\t\t};\n\n\t\tthis.inMemoryEntries.push(entry);\n\t\tappendFileSync(this.contextFile, `${JSON.stringify(entry)}\\n`);\n\t}\n\n\t/**\n\t * Sync user messages from log.jsonl that aren't in context.jsonl.\n\t *\n\t * log.jsonl and context.jsonl must have the same user messages.\n\t * This handles:\n\t * - Backfilled messages (mom was offline)\n\t * - Messages that arrived while mom was processing a previous turn\n\t * - Channel chatter between @mentions\n\t *\n\t * Channel chatter is formatted as \"[username]: message\" to distinguish from direct @mentions.\n\t *\n\t * Called before each agent run.\n\t *\n\t * @param excludeSlackTs Slack timestamp of current message (will be added via prompt(), not sync)\n\t */\n\tsyncFromLog(excludeSlackTs?: string): void {\n\t\tif (!existsSync(this.logFile)) return;\n\n\t\t// Build set of Slack timestamps already in context\n\t\t// We store slackTs in the message content or can extract from formatted messages\n\t\t// For messages synced from log, we use the log's date as the entry timestamp\n\t\t// For messages added via prompt(), they have different timestamps\n\t\t// So we need to match by content OR by stored slackTs\n\t\tconst contextSlackTimestamps = new Set<string>();\n\t\tconst contextMessageTexts = new Set<string>();\n\n\t\tfor (const entry of this.inMemoryEntries) {\n\t\t\tif (entry.type === \"message\") {\n\t\t\t\tconst msgEntry = entry as SessionMessageEntry;\n\t\t\t\t// Store the entry timestamp (which is the log date for synced messages)\n\t\t\t\tcontextSlackTimestamps.add(entry.timestamp);\n\n\t\t\t\t// Also store message text to catch duplicates added via prompt()\n\t\t\t\t// AppMessage has different shapes, check for content property\n\t\t\t\tconst msg = msgEntry.message as { role: string; content?: unknown };\n\t\t\t\tif (msg.role === \"user\" && msg.content !== undefined) {\n\t\t\t\t\tconst content = msg.content;\n\t\t\t\t\tif (typeof content === \"string\") {\n\t\t\t\t\t\tcontextMessageTexts.add(content);\n\t\t\t\t\t} else if (Array.isArray(content)) {\n\t\t\t\t\t\tfor (const part of content) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\ttypeof part === \"object\" &&\n\t\t\t\t\t\t\t\tpart !== null &&\n\t\t\t\t\t\t\t\t\"type\" in part &&\n\t\t\t\t\t\t\t\tpart.type === \"text\" &&\n\t\t\t\t\t\t\t\t\"text\" in part\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontextMessageTexts.add((part as { type: \"text\"; text: string }).text);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Read log.jsonl and find user messages not in context\n\t\tconst logContent = readFileSync(this.logFile, \"utf-8\");\n\t\tconst logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n\t\tinterface LogMessage {\n\t\t\tdate?: string;\n\t\t\tts?: string;\n\t\t\tuser?: string;\n\t\t\tuserName?: string;\n\t\t\ttext?: string;\n\t\t\tisBot?: boolean;\n\t\t}\n\n\t\tconst newMessages: Array<{ timestamp: string; slackTs: string; message: AppMessage }> = [];\n\n\t\tfor (const line of logLines) {\n\t\t\ttry {\n\t\t\t\tconst logMsg: LogMessage = JSON.parse(line);\n\n\t\t\t\tconst slackTs = logMsg.ts;\n\t\t\t\tconst date = logMsg.date;\n\t\t\t\tif (!slackTs || !date) continue;\n\n\t\t\t\t// Skip the current message being processed (will be added via prompt())\n\t\t\t\tif (excludeSlackTs && slackTs === excludeSlackTs) continue;\n\n\t\t\t\t// Skip bot messages - added through agent flow\n\t\t\t\tif (logMsg.isBot) continue;\n\n\t\t\t\t// Skip if this date is already in context (was synced before)\n\t\t\t\tif (contextSlackTimestamps.has(date)) continue;\n\n\t\t\t\t// Build the message text as it would appear in context\n\t\t\t\tconst messageText = `[${logMsg.userName || logMsg.user || \"unknown\"}]: ${logMsg.text || \"\"}`;\n\n\t\t\t\t// Skip if this exact message text is already in context (added via prompt())\n\t\t\t\tif (contextMessageTexts.has(messageText)) continue;\n\n\t\t\t\tconst msgTime = new Date(date).getTime() || Date.now();\n\t\t\t\tconst userMessage: AppMessage = {\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: messageText,\n\t\t\t\t\ttimestamp: msgTime,\n\t\t\t\t};\n\n\t\t\t\tnewMessages.push({ timestamp: date, slackTs, message: userMessage });\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\tif (newMessages.length === 0) return;\n\n\t\t// Sort by timestamp and add to context\n\t\tnewMessages.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());\n\n\t\tfor (const { timestamp, message } of newMessages) {\n\t\t\tconst entry: SessionMessageEntry = {\n\t\t\t\ttype: \"message\",\n\t\t\t\ttimestamp, // Use log date as entry timestamp for consistent deduplication\n\t\t\t\tmessage,\n\t\t\t};\n\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, `${JSON.stringify(entry)}\\n`);\n\t\t}\n\t}\n\n\tprivate extractSessionId(): string | null {\n\t\tfor (const entry of this.inMemoryEntries) {\n\t\t\tif (entry.type === \"session\") {\n\t\t\t\treturn entry.id;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate loadEntriesFromFile(): SessionEntry[] {\n\t\tif (!existsSync(this.contextFile)) return [];\n\n\t\tconst content = readFileSync(this.contextFile, \"utf8\");\n\t\tconst entries: SessionEntry[] = [];\n\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\tfor (const line of lines) {\n\t\t\tif (!line.trim()) continue;\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line) as SessionEntry;\n\t\t\t\tentries.push(entry);\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn entries;\n\t}\n\n\t/** Initialize session with header if not already done */\n\tstartSession(state: AgentState): void {\n\t\tif (this.sessionInitialized) return;\n\t\tthis.sessionInitialized = true;\n\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: this.channelDir,\n\t\t\tprovider: state.model?.provider || \"unknown\",\n\t\t\tmodelId: state.model?.id || \"unknown\",\n\t\t\tthinkingLevel: state.thinkingLevel,\n\t\t};\n\n\t\tthis.inMemoryEntries.push(entry);\n\t\tfor (const pending of this.pendingEntries) {\n\t\t\tthis.inMemoryEntries.push(pending);\n\t\t}\n\t\tthis.pendingEntries = [];\n\n\t\t// Write to file\n\t\tappendFileSync(this.contextFile, `${JSON.stringify(entry)}\\n`);\n\t\tfor (const memEntry of this.inMemoryEntries.slice(1)) {\n\t\t\tappendFileSync(this.contextFile, `${JSON.stringify(memEntry)}\\n`);\n\t\t}\n\t}\n\n\tsaveMessage(message: AppMessage): void {\n\t\tconst entry: SessionMessageEntry = {\n\t\t\ttype: \"message\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingEntries.push(entry);\n\t\t} else {\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, `${JSON.stringify(entry)}\\n`);\n\t\t}\n\t}\n\n\tsaveThinkingLevelChange(thinkingLevel: string): void {\n\t\tconst entry: ThinkingLevelChangeEntry = {\n\t\t\ttype: \"thinking_level_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingEntries.push(entry);\n\t\t} else {\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, `${JSON.stringify(entry)}\\n`);\n\t\t}\n\t}\n\n\tsaveModelChange(provider: string, modelId: string): void {\n\t\tconst entry: ModelChangeEntry = {\n\t\t\ttype: \"model_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tprovider,\n\t\t\tmodelId,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingEntries.push(entry);\n\t\t} else {\n\t\t\tthis.inMemoryEntries.push(entry);\n\t\t\tappendFileSync(this.contextFile, `${JSON.stringify(entry)}\\n`);\n\t\t}\n\t}\n\n\tsaveCompaction(entry: CompactionEntry): void {\n\t\tthis.inMemoryEntries.push(entry);\n\t\tappendFileSync(this.contextFile, `${JSON.stringify(entry)}\\n`);\n\t}\n\n\t/** Load session with compaction support */\n\tloadSession(): LoadedSession {\n\t\tconst entries = this.loadEntries();\n\t\treturn loadSessionFromEntries(entries);\n\t}\n\n\tloadEntries(): SessionEntry[] {\n\t\t// Re-read from file to get latest state\n\t\tif (existsSync(this.contextFile)) {\n\t\t\treturn this.loadEntriesFromFile();\n\t\t}\n\t\treturn [...this.inMemoryEntries];\n\t}\n\n\tgetSessionId(): string {\n\t\treturn this.sessionId;\n\t}\n\n\tgetSessionFile(): string {\n\t\treturn this.contextFile;\n\t}\n\n\t/** Check if session should be initialized */\n\tshouldInitializeSession(messages: AppMessage[]): boolean {\n\t\tif (this.sessionInitialized) return false;\n\t\tconst userMessages = messages.filter((m) => m.role === \"user\");\n\t\tconst assistantMessages = messages.filter((m) => m.role === \"assistant\");\n\t\treturn userMessages.length >= 1 && assistantMessages.length >= 1;\n\t}\n\n\t/** Reset session (clears context.jsonl) */\n\treset(): void {\n\t\tthis.pendingEntries = [];\n\t\tthis.inMemoryEntries = [];\n\t\tthis.sessionInitialized = false;\n\t\tthis.sessionId = uuidv4();\n\t\t// Truncate the context file\n\t\tif (existsSync(this.contextFile)) {\n\t\t\twriteFileSync(this.contextFile, \"\");\n\t\t}\n\t}\n\n\t// Compatibility methods for AgentSession\n\tisEnabled(): boolean {\n\t\treturn true;\n\t}\n\n\tsetSessionFile(_path: string): void {\n\t\t// No-op for mom - we always use the channel's context.jsonl\n\t}\n\n\tloadModel(): { provider: string; modelId: string } | null {\n\t\treturn this.loadSession().model;\n\t}\n\n\tloadThinkingLevel(): string {\n\t\treturn this.loadSession().thinkingLevel;\n\t}\n\n\t/** Not used by mom but required by AgentSession interface */\n\tcreateBranchedSessionFromEntries(_entries: SessionEntry[], _branchBeforeIndex: number): string | null {\n\t\treturn null; // Mom doesn't support branching\n\t}\n}\n\n// ============================================================================\n// MomSettingsManager - Simple settings for mom\n// ============================================================================\n\nexport interface MomCompactionSettings {\n\tenabled: boolean;\n\treserveTokens: number;\n\tkeepRecentTokens: number;\n}\n\nexport interface MomRetrySettings {\n\tenabled: boolean;\n\tmaxRetries: number;\n\tbaseDelayMs: number;\n}\n\nexport interface MomSettings {\n\tdefaultProvider?: string;\n\tdefaultModel?: string;\n\tdefaultThinkingLevel?: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\";\n\tcompaction?: Partial<MomCompactionSettings>;\n\tretry?: Partial<MomRetrySettings>;\n}\n\nconst DEFAULT_COMPACTION: MomCompactionSettings = {\n\tenabled: true,\n\treserveTokens: 16384,\n\tkeepRecentTokens: 20000,\n};\n\nconst DEFAULT_RETRY: MomRetrySettings = {\n\tenabled: true,\n\tmaxRetries: 3,\n\tbaseDelayMs: 2000,\n};\n\n/**\n * Settings manager for mom.\n * Stores settings in the workspace root directory.\n */\nexport class MomSettingsManager {\n\tprivate settingsPath: string;\n\tprivate settings: MomSettings;\n\n\tconstructor(workspaceDir: string) {\n\t\tthis.settingsPath = join(workspaceDir, \"settings.json\");\n\t\tthis.settings = this.load();\n\t}\n\n\tprivate load(): MomSettings {\n\t\tif (!existsSync(this.settingsPath)) {\n\t\t\treturn {};\n\t\t}\n\n\t\ttry {\n\t\t\tconst content = readFileSync(this.settingsPath, \"utf-8\");\n\t\t\treturn JSON.parse(content);\n\t\t} catch {\n\t\t\treturn {};\n\t\t}\n\t}\n\n\tprivate save(): void {\n\t\ttry {\n\t\t\tconst dir = dirname(this.settingsPath);\n\t\t\tif (!existsSync(dir)) {\n\t\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\t}\n\t\t\twriteFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2), \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(`Warning: Could not save settings file: ${error}`);\n\t\t}\n\t}\n\n\tgetCompactionSettings(): MomCompactionSettings {\n\t\treturn {\n\t\t\t...DEFAULT_COMPACTION,\n\t\t\t...this.settings.compaction,\n\t\t};\n\t}\n\n\tgetCompactionEnabled(): boolean {\n\t\treturn this.settings.compaction?.enabled ?? DEFAULT_COMPACTION.enabled;\n\t}\n\n\tsetCompactionEnabled(enabled: boolean): void {\n\t\tthis.settings.compaction = { ...this.settings.compaction, enabled };\n\t\tthis.save();\n\t}\n\n\tgetRetrySettings(): MomRetrySettings {\n\t\treturn {\n\t\t\t...DEFAULT_RETRY,\n\t\t\t...this.settings.retry,\n\t\t};\n\t}\n\n\tgetRetryEnabled(): boolean {\n\t\treturn this.settings.retry?.enabled ?? DEFAULT_RETRY.enabled;\n\t}\n\n\tsetRetryEnabled(enabled: boolean): void {\n\t\tthis.settings.retry = { ...this.settings.retry, enabled };\n\t\tthis.save();\n\t}\n\n\tgetDefaultModel(): string | undefined {\n\t\treturn this.settings.defaultModel;\n\t}\n\n\tgetDefaultProvider(): string | undefined {\n\t\treturn this.settings.defaultProvider;\n\t}\n\n\tsetDefaultModelAndProvider(provider: string, modelId: string): void {\n\t\tthis.settings.defaultProvider = provider;\n\t\tthis.settings.defaultModel = modelId;\n\t\tthis.save();\n\t}\n\n\tgetDefaultThinkingLevel(): string {\n\t\treturn this.settings.defaultThinkingLevel || \"off\";\n\t}\n\n\tsetDefaultThinkingLevel(level: string): void {\n\t\tthis.settings.defaultThinkingLevel = level as MomSettings[\"defaultThinkingLevel\"];\n\t\tthis.save();\n\t}\n\n\t// Compatibility methods for AgentSession\n\tgetQueueMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn \"one-at-a-time\"; // Mom processes one message at a time\n\t}\n\n\tsetQueueMode(_mode: \"all\" | \"one-at-a-time\"): void {\n\t\t// No-op for mom\n\t}\n\n\tgetHookPaths(): string[] {\n\t\treturn []; // Mom doesn't use hooks\n\t}\n\n\tgetHookTimeout(): number {\n\t\treturn 30000;\n\t}\n}\n\n// ============================================================================\n// Sync log.jsonl to context.jsonl\n// ============================================================================\n\n/**\n * Sync user messages from log.jsonl to context.jsonl.\n *\n * This ensures that messages logged while mom wasn't running (channel chatter,\n * backfilled messages, messages while busy) are added to the LLM context.\n *\n * @param channelDir - Path to channel directory\n * @param excludeAfterTs - Don't sync messages with ts >= this value (they'll be handled by agent)\n * @returns Number of messages synced\n */\nexport function syncLogToContext(channelDir: string, excludeAfterTs?: string): number {\n\tconst logFile = join(channelDir, \"log.jsonl\");\n\tconst contextFile = join(channelDir, \"context.jsonl\");\n\n\tif (!existsSync(logFile)) return 0;\n\n\t// Read all user messages from log.jsonl\n\tconst logContent = readFileSync(logFile, \"utf-8\");\n\tconst logLines = logContent.trim().split(\"\\n\").filter(Boolean);\n\n\tinterface LogEntry {\n\t\tts: string;\n\t\tuser: string;\n\t\tuserName?: string;\n\t\ttext: string;\n\t\tisBot: boolean;\n\t}\n\n\tconst logMessages: LogEntry[] = [];\n\tfor (const line of logLines) {\n\t\ttry {\n\t\t\tconst entry = JSON.parse(line) as LogEntry;\n\t\t\t// Only sync user messages (not bot responses)\n\t\t\tif (!entry.isBot && entry.ts && entry.text) {\n\t\t\t\t// Skip if >= excludeAfterTs\n\t\t\t\tif (excludeAfterTs && entry.ts >= excludeAfterTs) continue;\n\t\t\t\tlogMessages.push(entry);\n\t\t\t}\n\t\t} catch {}\n\t}\n\n\tif (logMessages.length === 0) return 0;\n\n\t// Read existing timestamps from context.jsonl\n\tif (existsSync(contextFile)) {\n\t\tconst contextContent = readFileSync(contextFile, \"utf-8\");\n\t\tconst contextLines = contextContent.trim().split(\"\\n\").filter(Boolean);\n\t\tfor (const line of contextLines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"message\" && entry.message?.role === \"user\" && entry.message?.timestamp) {\n\t\t\t\t\t// Extract ts from timestamp (ms -> slack ts format for comparison)\n\t\t\t\t\t// We store the original slack ts in a way we can recover\n\t\t\t\t\t// Actually, let's just check by content match since ts formats differ\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\n\t// For deduplication, we need to track what's already in context\n\t// Read context and extract user message content (strip attachments section for comparison)\n\tconst existingMessages = new Set<string>();\n\tif (existsSync(contextFile)) {\n\t\tconst contextContent = readFileSync(contextFile, \"utf-8\");\n\t\tconst contextLines = contextContent.trim().split(\"\\n\").filter(Boolean);\n\t\tfor (const line of contextLines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"message\" && entry.message?.role === \"user\") {\n\t\t\t\t\tlet content =\n\t\t\t\t\t\ttypeof entry.message.content === \"string\" ? entry.message.content : entry.message.content?.[0]?.text;\n\t\t\t\t\tif (content) {\n\t\t\t\t\t\t// Strip timestamp prefix for comparison (live messages have it, log messages don't)\n\t\t\t\t\t\t// Format: [YYYY-MM-DD HH:MM:SS+HH:MM] [username]: text\n\t\t\t\t\t\tcontent = content.replace(/^\\[\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}[+-]\\d{2}:\\d{2}\\] /, \"\");\n\t\t\t\t\t\t// Strip attachments section for comparison (live messages have it, log messages don't)\n\t\t\t\t\t\tconst attachmentsIdx = content.indexOf(\"\\n\\n<slack_attachments>\\n\");\n\t\t\t\t\t\tif (attachmentsIdx !== -1) {\n\t\t\t\t\t\t\tcontent = content.substring(0, attachmentsIdx);\n\t\t\t\t\t\t}\n\t\t\t\t\t\texistingMessages.add(content);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\n\t// Add missing messages to context.jsonl\n\tlet syncedCount = 0;\n\tfor (const msg of logMessages) {\n\t\tconst userName = msg.userName || msg.user;\n\t\tconst content = `[${userName}]: ${msg.text}`;\n\n\t\t// Skip if already in context\n\t\tif (existingMessages.has(content)) continue;\n\n\t\tconst timestamp = Math.floor(parseFloat(msg.ts) * 1000);\n\t\tconst entry = {\n\t\t\ttype: \"message\",\n\t\t\ttimestamp: new Date(timestamp).toISOString(),\n\t\t\tmessage: {\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent,\n\t\t\t\ttimestamp,\n\t\t\t},\n\t\t};\n\n\t\t// Ensure directory exists\n\t\tif (!existsSync(channelDir)) {\n\t\t\tmkdirSync(channelDir, { recursive: true });\n\t\t}\n\n\t\tappendFileSync(contextFile, `${JSON.stringify(entry)}\\n`);\n\t\texistingMessages.add(content); // Track to avoid duplicates within this sync\n\t\tsyncedCount++;\n\t}\n\n\treturn syncedCount;\n}\n"]}
package/dist/log.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from \"chalk\";\n\nexport interface LogContext {\n\tchannelId: string;\n\tuserName?: string;\n\tchannelName?: string; // For display like #dev-team vs C16HET4EQ\n}\n\nfunction timestamp(): string {\n\tconst now = new Date();\n\tconst hh = String(now.getHours()).padStart(2, \"0\");\n\tconst mm = String(now.getMinutes()).padStart(2, \"0\");\n\tconst ss = String(now.getSeconds()).padStart(2, \"0\");\n\treturn `[${hh}:${mm}:${ss}]`;\n}\n\nfunction formatContext(ctx: LogContext): string {\n\t// DMs: [DM:username]\n\t// Channels: [#channel-name:username] or [C16HET4EQ:username] if no name\n\tif (ctx.channelId.startsWith(\"D\")) {\n\t\treturn `[DM:${ctx.userName || ctx.channelId}]`;\n\t}\n\tconst channel = ctx.channelName || ctx.channelId;\n\tconst user = ctx.userName || \"unknown\";\n\treturn `[${channel.startsWith(\"#\") ? channel : `#${channel}`}:${user}]`;\n}\n\nfunction truncate(text: string, maxLen: number): string {\n\tif (text.length <= maxLen) return text;\n\treturn text.substring(0, maxLen) + `\\n(truncated at ${maxLen} chars)`;\n}\n\nfunction formatToolArgs(args: Record<string, unknown>): string {\n\tconst lines: string[] = [];\n\n\tfor (const [key, value] of Object.entries(args)) {\n\t\t// Skip the label - it's already shown in the tool name\n\t\tif (key === \"label\") continue;\n\n\t\t// For read tool, format path with offset/limit\n\t\tif (key === \"path\" && typeof value === \"string\") {\n\t\t\tconst offset = args.offset as number | undefined;\n\t\t\tconst limit = args.limit as number | undefined;\n\t\t\tif (offset !== undefined && limit !== undefined) {\n\t\t\t\tlines.push(`${value}:${offset}-${offset + limit}`);\n\t\t\t} else {\n\t\t\t\tlines.push(value);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Skip offset/limit since we already handled them\n\t\tif (key === \"offset\" || key === \"limit\") continue;\n\n\t\t// For other values, format them\n\t\tif (typeof value === \"string\") {\n\t\t\t// Multi-line strings get indented\n\t\t\tif (value.includes(\"\\n\")) {\n\t\t\t\tlines.push(value);\n\t\t\t} else {\n\t\t\t\tlines.push(value);\n\t\t\t}\n\t\t} else {\n\t\t\tlines.push(JSON.stringify(value));\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\n// User messages\nexport function logUserMessage(ctx: LogContext, text: string): void {\n\tconsole.log(chalk.green(`${timestamp()} ${formatContext(ctx)} ${text}`));\n}\n\n// Tool execution\nexport function logToolStart(ctx: LogContext, toolName: string, label: string, args: Record<string, unknown>): void {\n\tconst formattedArgs = formatToolArgs(args);\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ↳ ${toolName}: ${label}`));\n\tif (formattedArgs) {\n\t\t// Indent the args\n\t\tconst indented = formattedArgs\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => ` ${line}`)\n\t\t\t.join(\"\\n\");\n\t\tconsole.log(chalk.dim(indented));\n\t}\n}\n\nexport function logToolSuccess(ctx: LogContext, toolName: string, durationMs: number, result: string): void {\n\tconst duration = (durationMs / 1000).toFixed(1);\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✓ ${toolName} (${duration}s)`));\n\n\tconst truncated = truncate(result, 1000);\n\tif (truncated) {\n\t\tconst indented = truncated\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => ` ${line}`)\n\t\t\t.join(\"\\n\");\n\t\tconsole.log(chalk.dim(indented));\n\t}\n}\n\nexport function logToolError(ctx: LogContext, toolName: string, durationMs: number, error: string): void {\n\tconst duration = (durationMs / 1000).toFixed(1);\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✗ ${toolName} (${duration}s)`));\n\n\tconst truncated = truncate(error, 1000);\n\tconst indented = truncated\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\n// Response streaming\nexport function logResponseStart(ctx: LogContext): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} → Streaming response...`));\n}\n\nexport function logThinking(ctx: LogContext, thinking: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💭 Thinking`));\n\tconst truncated = truncate(thinking, 1000);\n\tconst indented = truncated\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\nexport function logResponse(ctx: LogContext, text: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💬 Response`));\n\tconst truncated = truncate(text, 1000);\n\tconst indented = truncated\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\n// Attachments\nexport function logDownloadStart(ctx: LogContext, filename: string, localPath: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ↓ Downloading attachment`));\n\tconsole.log(chalk.dim(` ${filename} → ${localPath}`));\n}\n\nexport function logDownloadSuccess(ctx: LogContext, sizeKB: number): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✓ Downloaded (${sizeKB.toLocaleString()} KB)`));\n}\n\nexport function logDownloadError(ctx: LogContext, filename: string, error: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✗ Download failed`));\n\tconsole.log(chalk.dim(` ${filename}: ${error}`));\n}\n\n// Control\nexport function logStopRequest(ctx: LogContext): void {\n\tconsole.log(chalk.green(`${timestamp()} ${formatContext(ctx)} stop`));\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ⊗ Stop requested - aborting`));\n}\n\n// System\nexport function logInfo(message: string): void {\n\tconsole.log(chalk.blue(`${timestamp()} [system] ${message}`));\n}\n\nexport function logWarning(message: string, details?: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} [system] ⚠ ${message}`));\n\tif (details) {\n\t\tconst indented = details\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => ` ${line}`)\n\t\t\t.join(\"\\n\");\n\t\tconsole.log(chalk.dim(indented));\n\t}\n}\n\nexport function logAgentError(ctx: LogContext | \"system\", error: string): void {\n\tconst context = ctx === \"system\" ? \"[system]\" : formatContext(ctx);\n\tconsole.log(chalk.yellow(`${timestamp()} ${context} ✗ Agent error`));\n\tconst indented = error\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\n// Usage summary\nexport function logUsageSummary(\n\tctx: LogContext,\n\tusage: {\n\t\tinput: number;\n\t\toutput: number;\n\t\tcacheRead: number;\n\t\tcacheWrite: number;\n\t\tcost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };\n\t},\n\tcontextTokens?: number,\n\tcontextWindow?: number,\n): string {\n\tconst formatTokens = (count: number): string => {\n\t\tif (count < 1000) return count.toString();\n\t\tif (count < 10000) return (count / 1000).toFixed(1) + \"k\";\n\t\tif (count < 1000000) return Math.round(count / 1000) + \"k\";\n\t\treturn (count / 1000000).toFixed(1) + \"M\";\n\t};\n\n\tconst lines: string[] = [];\n\tlines.push(\"*Usage Summary*\");\n\tlines.push(`Tokens: ${usage.input.toLocaleString()} in, ${usage.output.toLocaleString()} out`);\n\tif (usage.cacheRead > 0 || usage.cacheWrite > 0) {\n\t\tlines.push(`Cache: ${usage.cacheRead.toLocaleString()} read, ${usage.cacheWrite.toLocaleString()} write`);\n\t}\n\tif (contextTokens && contextWindow) {\n\t\tconst contextPercent = ((contextTokens / contextWindow) * 100).toFixed(1);\n\t\tlines.push(`Context: ${formatTokens(contextTokens)} / ${formatTokens(contextWindow)} (${contextPercent}%)`);\n\t}\n\tlines.push(\n\t\t`Cost: $${usage.cost.input.toFixed(4)} in, $${usage.cost.output.toFixed(4)} out` +\n\t\t\t(usage.cacheRead > 0 || usage.cacheWrite > 0\n\t\t\t\t? `, $${usage.cost.cacheRead.toFixed(4)} cache read, $${usage.cost.cacheWrite.toFixed(4)} cache write`\n\t\t\t\t: \"\"),\n\t);\n\tlines.push(`*Total: $${usage.cost.total.toFixed(4)}*`);\n\n\tconst summary = lines.join(\"\\n\");\n\n\t// Log to console\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💰 Usage`));\n\tconsole.log(\n\t\tchalk.dim(\n\t\t\t` ${usage.input.toLocaleString()} in + ${usage.output.toLocaleString()} out` +\n\t\t\t\t(usage.cacheRead > 0 || usage.cacheWrite > 0\n\t\t\t\t\t? ` (${usage.cacheRead.toLocaleString()} cache read, ${usage.cacheWrite.toLocaleString()} cache write)`\n\t\t\t\t\t: \"\") +\n\t\t\t\t` = $${usage.cost.total.toFixed(4)}`,\n\t\t),\n\t);\n\n\treturn summary;\n}\n\n// Startup (no context needed)\nexport function logStartup(workingDir: string, sandbox: string): void {\n\tconsole.log(\"Starting mom bot...\");\n\tconsole.log(` Working directory: ${workingDir}`);\n\tconsole.log(` Sandbox: ${sandbox}`);\n}\n\nexport function logConnected(): void {\n\tconsole.log(\"⚡️ Mom bot connected and listening!\");\n\tconsole.log(\"\");\n}\n\nexport function logDisconnected(): void {\n\tconsole.log(\"Mom bot disconnected.\");\n}\n\n// Backfill\nexport function logBackfillStart(channelCount: number): void {\n\tconsole.log(chalk.blue(`${timestamp()} [system] Backfilling ${channelCount} channels...`));\n}\n\nexport function logBackfillChannel(channelName: string, messageCount: number): void {\n\tconsole.log(chalk.blue(`${timestamp()} [system] #${channelName}: ${messageCount} messages`));\n}\n\nexport function logBackfillComplete(totalMessages: number, durationMs: number): void {\n\tconst duration = (durationMs / 1000).toFixed(1);\n\tconsole.log(chalk.blue(`${timestamp()} [system] Backfill complete: ${totalMessages} messages in ${duration}s`));\n}\n"]}
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from \"chalk\";\n\nexport interface LogContext {\n\tchannelId: string;\n\tuserName?: string;\n\tchannelName?: string; // For display like #dev-team vs C16HET4EQ\n}\n\nfunction timestamp(): string {\n\tconst now = new Date();\n\tconst hh = String(now.getHours()).padStart(2, \"0\");\n\tconst mm = String(now.getMinutes()).padStart(2, \"0\");\n\tconst ss = String(now.getSeconds()).padStart(2, \"0\");\n\treturn `[${hh}:${mm}:${ss}]`;\n}\n\nfunction formatContext(ctx: LogContext): string {\n\t// DMs: [DM:username]\n\t// Channels: [#channel-name:username] or [C16HET4EQ:username] if no name\n\tif (ctx.channelId.startsWith(\"D\")) {\n\t\treturn `[DM:${ctx.userName || ctx.channelId}]`;\n\t}\n\tconst channel = ctx.channelName || ctx.channelId;\n\tconst user = ctx.userName || \"unknown\";\n\treturn `[${channel.startsWith(\"#\") ? channel : `#${channel}`}:${user}]`;\n}\n\nfunction truncate(text: string, maxLen: number): string {\n\tif (text.length <= maxLen) return text;\n\treturn `${text.substring(0, maxLen)}\\n(truncated at ${maxLen} chars)`;\n}\n\nfunction formatToolArgs(args: Record<string, unknown>): string {\n\tconst lines: string[] = [];\n\n\tfor (const [key, value] of Object.entries(args)) {\n\t\t// Skip the label - it's already shown in the tool name\n\t\tif (key === \"label\") continue;\n\n\t\t// For read tool, format path with offset/limit\n\t\tif (key === \"path\" && typeof value === \"string\") {\n\t\t\tconst offset = args.offset as number | undefined;\n\t\t\tconst limit = args.limit as number | undefined;\n\t\t\tif (offset !== undefined && limit !== undefined) {\n\t\t\t\tlines.push(`${value}:${offset}-${offset + limit}`);\n\t\t\t} else {\n\t\t\t\tlines.push(value);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Skip offset/limit since we already handled them\n\t\tif (key === \"offset\" || key === \"limit\") continue;\n\n\t\t// For other values, format them\n\t\tif (typeof value === \"string\") {\n\t\t\t// Multi-line strings get indented\n\t\t\tif (value.includes(\"\\n\")) {\n\t\t\t\tlines.push(value);\n\t\t\t} else {\n\t\t\t\tlines.push(value);\n\t\t\t}\n\t\t} else {\n\t\t\tlines.push(JSON.stringify(value));\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\n// User messages\nexport function logUserMessage(ctx: LogContext, text: string): void {\n\tconsole.log(chalk.green(`${timestamp()} ${formatContext(ctx)} ${text}`));\n}\n\n// Tool execution\nexport function logToolStart(ctx: LogContext, toolName: string, label: string, args: Record<string, unknown>): void {\n\tconst formattedArgs = formatToolArgs(args);\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ↳ ${toolName}: ${label}`));\n\tif (formattedArgs) {\n\t\t// Indent the args\n\t\tconst indented = formattedArgs\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => ` ${line}`)\n\t\t\t.join(\"\\n\");\n\t\tconsole.log(chalk.dim(indented));\n\t}\n}\n\nexport function logToolSuccess(ctx: LogContext, toolName: string, durationMs: number, result: string): void {\n\tconst duration = (durationMs / 1000).toFixed(1);\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✓ ${toolName} (${duration}s)`));\n\n\tconst truncated = truncate(result, 1000);\n\tif (truncated) {\n\t\tconst indented = truncated\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => ` ${line}`)\n\t\t\t.join(\"\\n\");\n\t\tconsole.log(chalk.dim(indented));\n\t}\n}\n\nexport function logToolError(ctx: LogContext, toolName: string, durationMs: number, error: string): void {\n\tconst duration = (durationMs / 1000).toFixed(1);\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✗ ${toolName} (${duration}s)`));\n\n\tconst truncated = truncate(error, 1000);\n\tconst indented = truncated\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\n// Response streaming\nexport function logResponseStart(ctx: LogContext): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} → Streaming response...`));\n}\n\nexport function logThinking(ctx: LogContext, thinking: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💭 Thinking`));\n\tconst truncated = truncate(thinking, 1000);\n\tconst indented = truncated\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\nexport function logResponse(ctx: LogContext, text: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💬 Response`));\n\tconst truncated = truncate(text, 1000);\n\tconst indented = truncated\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\n// Attachments\nexport function logDownloadStart(ctx: LogContext, filename: string, localPath: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ↓ Downloading attachment`));\n\tconsole.log(chalk.dim(` ${filename} → ${localPath}`));\n}\n\nexport function logDownloadSuccess(ctx: LogContext, sizeKB: number): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✓ Downloaded (${sizeKB.toLocaleString()} KB)`));\n}\n\nexport function logDownloadError(ctx: LogContext, filename: string, error: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✗ Download failed`));\n\tconsole.log(chalk.dim(` ${filename}: ${error}`));\n}\n\n// Control\nexport function logStopRequest(ctx: LogContext): void {\n\tconsole.log(chalk.green(`${timestamp()} ${formatContext(ctx)} stop`));\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ⊗ Stop requested - aborting`));\n}\n\n// System\nexport function logInfo(message: string): void {\n\tconsole.log(chalk.blue(`${timestamp()} [system] ${message}`));\n}\n\nexport function logWarning(message: string, details?: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} [system] ⚠ ${message}`));\n\tif (details) {\n\t\tconst indented = details\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => ` ${line}`)\n\t\t\t.join(\"\\n\");\n\t\tconsole.log(chalk.dim(indented));\n\t}\n}\n\nexport function logAgentError(ctx: LogContext | \"system\", error: string): void {\n\tconst context = ctx === \"system\" ? \"[system]\" : formatContext(ctx);\n\tconsole.log(chalk.yellow(`${timestamp()} ${context} ✗ Agent error`));\n\tconst indented = error\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\n// Usage summary\nexport function logUsageSummary(\n\tctx: LogContext,\n\tusage: {\n\t\tinput: number;\n\t\toutput: number;\n\t\tcacheRead: number;\n\t\tcacheWrite: number;\n\t\tcost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };\n\t},\n\tcontextTokens?: number,\n\tcontextWindow?: number,\n): string {\n\tconst formatTokens = (count: number): string => {\n\t\tif (count < 1000) return count.toString();\n\t\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\t\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\t\treturn `${(count / 1000000).toFixed(1)}M`;\n\t};\n\n\tconst lines: string[] = [];\n\tlines.push(\"*Usage Summary*\");\n\tlines.push(`Tokens: ${usage.input.toLocaleString()} in, ${usage.output.toLocaleString()} out`);\n\tif (usage.cacheRead > 0 || usage.cacheWrite > 0) {\n\t\tlines.push(`Cache: ${usage.cacheRead.toLocaleString()} read, ${usage.cacheWrite.toLocaleString()} write`);\n\t}\n\tif (contextTokens && contextWindow) {\n\t\tconst contextPercent = ((contextTokens / contextWindow) * 100).toFixed(1);\n\t\tlines.push(`Context: ${formatTokens(contextTokens)} / ${formatTokens(contextWindow)} (${contextPercent}%)`);\n\t}\n\tlines.push(\n\t\t`Cost: $${usage.cost.input.toFixed(4)} in, $${usage.cost.output.toFixed(4)} out` +\n\t\t\t(usage.cacheRead > 0 || usage.cacheWrite > 0\n\t\t\t\t? `, $${usage.cost.cacheRead.toFixed(4)} cache read, $${usage.cost.cacheWrite.toFixed(4)} cache write`\n\t\t\t\t: \"\"),\n\t);\n\tlines.push(`*Total: $${usage.cost.total.toFixed(4)}*`);\n\n\tconst summary = lines.join(\"\\n\");\n\n\t// Log to console\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💰 Usage`));\n\tconsole.log(\n\t\tchalk.dim(\n\t\t\t` ${usage.input.toLocaleString()} in + ${usage.output.toLocaleString()} out` +\n\t\t\t\t(usage.cacheRead > 0 || usage.cacheWrite > 0\n\t\t\t\t\t? ` (${usage.cacheRead.toLocaleString()} cache read, ${usage.cacheWrite.toLocaleString()} cache write)`\n\t\t\t\t\t: \"\") +\n\t\t\t\t` = $${usage.cost.total.toFixed(4)}`,\n\t\t),\n\t);\n\n\treturn summary;\n}\n\n// Startup (no context needed)\nexport function logStartup(workingDir: string, sandbox: string): void {\n\tconsole.log(\"Starting mom bot...\");\n\tconsole.log(` Working directory: ${workingDir}`);\n\tconsole.log(` Sandbox: ${sandbox}`);\n}\n\nexport function logConnected(): void {\n\tconsole.log(\"⚡️ Mom bot connected and listening!\");\n\tconsole.log(\"\");\n}\n\nexport function logDisconnected(): void {\n\tconsole.log(\"Mom bot disconnected.\");\n}\n\n// Backfill\nexport function logBackfillStart(channelCount: number): void {\n\tconsole.log(chalk.blue(`${timestamp()} [system] Backfilling ${channelCount} channels...`));\n}\n\nexport function logBackfillChannel(channelName: string, messageCount: number): void {\n\tconsole.log(chalk.blue(`${timestamp()} [system] #${channelName}: ${messageCount} messages`));\n}\n\nexport function logBackfillComplete(totalMessages: number, durationMs: number): void {\n\tconst duration = (durationMs / 1000).toFixed(1);\n\tconsole.log(chalk.blue(`${timestamp()} [system] Backfill complete: ${totalMessages} messages in ${duration}s`));\n}\n"]}
package/dist/log.js CHANGED
@@ -19,7 +19,7 @@ function formatContext(ctx) {
19
19
  function truncate(text, maxLen) {
20
20
  if (text.length <= maxLen)
21
21
  return text;
22
- return text.substring(0, maxLen) + `\n(truncated at ${maxLen} chars)`;
22
+ return `${text.substring(0, maxLen)}\n(truncated at ${maxLen} chars)`;
23
23
  }
24
24
  function formatToolArgs(args) {
25
25
  const lines = [];
@@ -165,10 +165,10 @@ export function logUsageSummary(ctx, usage, contextTokens, contextWindow) {
165
165
  if (count < 1000)
166
166
  return count.toString();
167
167
  if (count < 10000)
168
- return (count / 1000).toFixed(1) + "k";
168
+ return `${(count / 1000).toFixed(1)}k`;
169
169
  if (count < 1000000)
170
- return Math.round(count / 1000) + "k";
171
- return (count / 1000000).toFixed(1) + "M";
170
+ return `${Math.round(count / 1000)}k`;
171
+ return `${(count / 1000000).toFixed(1)}M`;
172
172
  };
173
173
  const lines = [];
174
174
  lines.push("*Usage Summary*");
package/dist/log.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from \"chalk\";\n\nexport interface LogContext {\n\tchannelId: string;\n\tuserName?: string;\n\tchannelName?: string; // For display like #dev-team vs C16HET4EQ\n}\n\nfunction timestamp(): string {\n\tconst now = new Date();\n\tconst hh = String(now.getHours()).padStart(2, \"0\");\n\tconst mm = String(now.getMinutes()).padStart(2, \"0\");\n\tconst ss = String(now.getSeconds()).padStart(2, \"0\");\n\treturn `[${hh}:${mm}:${ss}]`;\n}\n\nfunction formatContext(ctx: LogContext): string {\n\t// DMs: [DM:username]\n\t// Channels: [#channel-name:username] or [C16HET4EQ:username] if no name\n\tif (ctx.channelId.startsWith(\"D\")) {\n\t\treturn `[DM:${ctx.userName || ctx.channelId}]`;\n\t}\n\tconst channel = ctx.channelName || ctx.channelId;\n\tconst user = ctx.userName || \"unknown\";\n\treturn `[${channel.startsWith(\"#\") ? channel : `#${channel}`}:${user}]`;\n}\n\nfunction truncate(text: string, maxLen: number): string {\n\tif (text.length <= maxLen) return text;\n\treturn text.substring(0, maxLen) + `\\n(truncated at ${maxLen} chars)`;\n}\n\nfunction formatToolArgs(args: Record<string, unknown>): string {\n\tconst lines: string[] = [];\n\n\tfor (const [key, value] of Object.entries(args)) {\n\t\t// Skip the label - it's already shown in the tool name\n\t\tif (key === \"label\") continue;\n\n\t\t// For read tool, format path with offset/limit\n\t\tif (key === \"path\" && typeof value === \"string\") {\n\t\t\tconst offset = args.offset as number | undefined;\n\t\t\tconst limit = args.limit as number | undefined;\n\t\t\tif (offset !== undefined && limit !== undefined) {\n\t\t\t\tlines.push(`${value}:${offset}-${offset + limit}`);\n\t\t\t} else {\n\t\t\t\tlines.push(value);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Skip offset/limit since we already handled them\n\t\tif (key === \"offset\" || key === \"limit\") continue;\n\n\t\t// For other values, format them\n\t\tif (typeof value === \"string\") {\n\t\t\t// Multi-line strings get indented\n\t\t\tif (value.includes(\"\\n\")) {\n\t\t\t\tlines.push(value);\n\t\t\t} else {\n\t\t\t\tlines.push(value);\n\t\t\t}\n\t\t} else {\n\t\t\tlines.push(JSON.stringify(value));\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\n// User messages\nexport function logUserMessage(ctx: LogContext, text: string): void {\n\tconsole.log(chalk.green(`${timestamp()} ${formatContext(ctx)} ${text}`));\n}\n\n// Tool execution\nexport function logToolStart(ctx: LogContext, toolName: string, label: string, args: Record<string, unknown>): void {\n\tconst formattedArgs = formatToolArgs(args);\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ↳ ${toolName}: ${label}`));\n\tif (formattedArgs) {\n\t\t// Indent the args\n\t\tconst indented = formattedArgs\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => ` ${line}`)\n\t\t\t.join(\"\\n\");\n\t\tconsole.log(chalk.dim(indented));\n\t}\n}\n\nexport function logToolSuccess(ctx: LogContext, toolName: string, durationMs: number, result: string): void {\n\tconst duration = (durationMs / 1000).toFixed(1);\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✓ ${toolName} (${duration}s)`));\n\n\tconst truncated = truncate(result, 1000);\n\tif (truncated) {\n\t\tconst indented = truncated\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => ` ${line}`)\n\t\t\t.join(\"\\n\");\n\t\tconsole.log(chalk.dim(indented));\n\t}\n}\n\nexport function logToolError(ctx: LogContext, toolName: string, durationMs: number, error: string): void {\n\tconst duration = (durationMs / 1000).toFixed(1);\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✗ ${toolName} (${duration}s)`));\n\n\tconst truncated = truncate(error, 1000);\n\tconst indented = truncated\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\n// Response streaming\nexport function logResponseStart(ctx: LogContext): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} → Streaming response...`));\n}\n\nexport function logThinking(ctx: LogContext, thinking: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💭 Thinking`));\n\tconst truncated = truncate(thinking, 1000);\n\tconst indented = truncated\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\nexport function logResponse(ctx: LogContext, text: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💬 Response`));\n\tconst truncated = truncate(text, 1000);\n\tconst indented = truncated\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\n// Attachments\nexport function logDownloadStart(ctx: LogContext, filename: string, localPath: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ↓ Downloading attachment`));\n\tconsole.log(chalk.dim(` ${filename} → ${localPath}`));\n}\n\nexport function logDownloadSuccess(ctx: LogContext, sizeKB: number): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✓ Downloaded (${sizeKB.toLocaleString()} KB)`));\n}\n\nexport function logDownloadError(ctx: LogContext, filename: string, error: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✗ Download failed`));\n\tconsole.log(chalk.dim(` ${filename}: ${error}`));\n}\n\n// Control\nexport function logStopRequest(ctx: LogContext): void {\n\tconsole.log(chalk.green(`${timestamp()} ${formatContext(ctx)} stop`));\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ⊗ Stop requested - aborting`));\n}\n\n// System\nexport function logInfo(message: string): void {\n\tconsole.log(chalk.blue(`${timestamp()} [system] ${message}`));\n}\n\nexport function logWarning(message: string, details?: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} [system] ⚠ ${message}`));\n\tif (details) {\n\t\tconst indented = details\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => ` ${line}`)\n\t\t\t.join(\"\\n\");\n\t\tconsole.log(chalk.dim(indented));\n\t}\n}\n\nexport function logAgentError(ctx: LogContext | \"system\", error: string): void {\n\tconst context = ctx === \"system\" ? \"[system]\" : formatContext(ctx);\n\tconsole.log(chalk.yellow(`${timestamp()} ${context} ✗ Agent error`));\n\tconst indented = error\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\n// Usage summary\nexport function logUsageSummary(\n\tctx: LogContext,\n\tusage: {\n\t\tinput: number;\n\t\toutput: number;\n\t\tcacheRead: number;\n\t\tcacheWrite: number;\n\t\tcost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };\n\t},\n\tcontextTokens?: number,\n\tcontextWindow?: number,\n): string {\n\tconst formatTokens = (count: number): string => {\n\t\tif (count < 1000) return count.toString();\n\t\tif (count < 10000) return (count / 1000).toFixed(1) + \"k\";\n\t\tif (count < 1000000) return Math.round(count / 1000) + \"k\";\n\t\treturn (count / 1000000).toFixed(1) + \"M\";\n\t};\n\n\tconst lines: string[] = [];\n\tlines.push(\"*Usage Summary*\");\n\tlines.push(`Tokens: ${usage.input.toLocaleString()} in, ${usage.output.toLocaleString()} out`);\n\tif (usage.cacheRead > 0 || usage.cacheWrite > 0) {\n\t\tlines.push(`Cache: ${usage.cacheRead.toLocaleString()} read, ${usage.cacheWrite.toLocaleString()} write`);\n\t}\n\tif (contextTokens && contextWindow) {\n\t\tconst contextPercent = ((contextTokens / contextWindow) * 100).toFixed(1);\n\t\tlines.push(`Context: ${formatTokens(contextTokens)} / ${formatTokens(contextWindow)} (${contextPercent}%)`);\n\t}\n\tlines.push(\n\t\t`Cost: $${usage.cost.input.toFixed(4)} in, $${usage.cost.output.toFixed(4)} out` +\n\t\t\t(usage.cacheRead > 0 || usage.cacheWrite > 0\n\t\t\t\t? `, $${usage.cost.cacheRead.toFixed(4)} cache read, $${usage.cost.cacheWrite.toFixed(4)} cache write`\n\t\t\t\t: \"\"),\n\t);\n\tlines.push(`*Total: $${usage.cost.total.toFixed(4)}*`);\n\n\tconst summary = lines.join(\"\\n\");\n\n\t// Log to console\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💰 Usage`));\n\tconsole.log(\n\t\tchalk.dim(\n\t\t\t` ${usage.input.toLocaleString()} in + ${usage.output.toLocaleString()} out` +\n\t\t\t\t(usage.cacheRead > 0 || usage.cacheWrite > 0\n\t\t\t\t\t? ` (${usage.cacheRead.toLocaleString()} cache read, ${usage.cacheWrite.toLocaleString()} cache write)`\n\t\t\t\t\t: \"\") +\n\t\t\t\t` = $${usage.cost.total.toFixed(4)}`,\n\t\t),\n\t);\n\n\treturn summary;\n}\n\n// Startup (no context needed)\nexport function logStartup(workingDir: string, sandbox: string): void {\n\tconsole.log(\"Starting mom bot...\");\n\tconsole.log(` Working directory: ${workingDir}`);\n\tconsole.log(` Sandbox: ${sandbox}`);\n}\n\nexport function logConnected(): void {\n\tconsole.log(\"⚡️ Mom bot connected and listening!\");\n\tconsole.log(\"\");\n}\n\nexport function logDisconnected(): void {\n\tconsole.log(\"Mom bot disconnected.\");\n}\n\n// Backfill\nexport function logBackfillStart(channelCount: number): void {\n\tconsole.log(chalk.blue(`${timestamp()} [system] Backfilling ${channelCount} channels...`));\n}\n\nexport function logBackfillChannel(channelName: string, messageCount: number): void {\n\tconsole.log(chalk.blue(`${timestamp()} [system] #${channelName}: ${messageCount} messages`));\n}\n\nexport function logBackfillComplete(totalMessages: number, durationMs: number): void {\n\tconst duration = (durationMs / 1000).toFixed(1);\n\tconsole.log(chalk.blue(`${timestamp()} [system] Backfill complete: ${totalMessages} messages in ${duration}s`));\n}\n"]}
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,SAAS,SAAS,GAAW;IAC5B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACrD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACrD,OAAO,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC;AAAA,CAC7B;AAED,SAAS,aAAa,CAAC,GAAe,EAAU;IAC/C,qBAAqB;IACrB,wEAAwE;IACxE,IAAI,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,OAAO,OAAO,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,SAAS,GAAG,CAAC;IAChD,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,SAAS,CAAC;IACjD,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,IAAI,SAAS,CAAC;IACvC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,IAAI,IAAI,GAAG,CAAC;AAAA,CACxE;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,MAAc,EAAU;IACvD,IAAI,IAAI,CAAC,MAAM,IAAI,MAAM;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,mBAAmB,MAAM,SAAS,CAAC;AAAA,CACtE;AAED,SAAS,cAAc,CAAC,IAA6B,EAAU;IAC9D,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,uDAAuD;QACvD,IAAI,GAAG,KAAK,OAAO;YAAE,SAAS;QAE9B,+CAA+C;QAC/C,IAAI,GAAG,KAAK,MAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACjD,MAAM,MAAM,GAAG,IAAI,CAAC,MAA4B,CAAC;YACjD,MAAM,KAAK,GAAG,IAAI,CAAC,KAA2B,CAAC;YAC/C,IAAI,MAAM,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACjD,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,MAAM,IAAI,MAAM,GAAG,KAAK,EAAE,CAAC,CAAC;YACpD,CAAC;iBAAM,CAAC;gBACP,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnB,CAAC;YACD,SAAS;QACV,CAAC;QAED,kDAAkD;QAClD,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,OAAO;YAAE,SAAS;QAElD,gCAAgC;QAChC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC/B,kCAAkC;YAClC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnB,CAAC;iBAAM,CAAC;gBACP,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnB,CAAC;QACF,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACnC,CAAC;IACF,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACxB;AAED,gBAAgB;AAChB,MAAM,UAAU,cAAc,CAAC,GAAe,EAAE,IAAY,EAAQ;IACnE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,SAAS,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;AAAA,CACzE;AAED,iBAAiB;AACjB,MAAM,UAAU,YAAY,CAAC,GAAe,EAAE,QAAgB,EAAE,KAAa,EAAE,IAA6B,EAAQ;IACnH,MAAM,aAAa,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,QAAM,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC;IAC1F,IAAI,aAAa,EAAE,CAAC;QACnB,kBAAkB;QAClB,MAAM,QAAQ,GAAG,aAAa;aAC5B,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,IAAI,EAAE,CAAC;aACnC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClC,CAAC;AAAA,CACD;AAED,MAAM,UAAU,cAAc,CAAC,GAAe,EAAE,QAAgB,EAAE,UAAkB,EAAE,MAAc,EAAQ;IAC3G,MAAM,QAAQ,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,QAAM,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC;IAE/F,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACzC,IAAI,SAAS,EAAE,CAAC;QACf,MAAM,QAAQ,GAAG,SAAS;aACxB,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,IAAI,EAAE,CAAC;aACnC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClC,CAAC;AAAA,CACD;AAED,MAAM,UAAU,YAAY,CAAC,GAAe,EAAE,QAAgB,EAAE,UAAkB,EAAE,KAAa,EAAQ;IACxG,MAAM,QAAQ,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,QAAM,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC;IAE/F,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,SAAS;SACxB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,IAAI,EAAE,CAAC;SACnC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;AAAA,CACjC;AAED,qBAAqB;AACrB,MAAM,UAAU,gBAAgB,CAAC,GAAe,EAAQ;IACvD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,4BAA0B,CAAC,CAAC,CAAC;AAAA,CAC1F;AAED,MAAM,UAAU,WAAW,CAAC,GAAe,EAAE,QAAgB,EAAQ;IACpE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,gBAAa,CAAC,CAAC,CAAC;IAC7E,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,SAAS;SACxB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,IAAI,EAAE,CAAC;SACnC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;AAAA,CACjC;AAED,MAAM,UAAU,WAAW,CAAC,GAAe,EAAE,IAAY,EAAQ;IAChE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,gBAAa,CAAC,CAAC,CAAC;IAC7E,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,SAAS;SACxB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,IAAI,EAAE,CAAC;SACnC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;AAAA,CACjC;AAED,cAAc;AACd,MAAM,UAAU,gBAAgB,CAAC,GAAe,EAAE,QAAgB,EAAE,SAAiB,EAAQ;IAC5F,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,6BAA2B,CAAC,CAAC,CAAC;IAC3F,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,QAAQ,QAAM,SAAS,EAAE,CAAC,CAAC,CAAC;AAAA,CAChE;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAe,EAAE,MAAc,EAAQ;IACzE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,oBAAkB,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;AAAA,CAC/G;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAe,EAAE,QAAgB,EAAE,KAAa,EAAQ;IACxF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,sBAAoB,CAAC,CAAC,CAAC;IACpF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC;AAAA,CAC3D;AAED,UAAU;AACV,MAAM,UAAU,cAAc,CAAC,GAAe,EAAQ;IACrD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,SAAS,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,gCAA8B,CAAC,CAAC,CAAC;AAAA,CAC9F;AAED,SAAS;AACT,MAAM,UAAU,OAAO,CAAC,OAAe,EAAQ;IAC9C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,SAAS,EAAE,aAAa,OAAO,EAAE,CAAC,CAAC,CAAC;AAAA,CAC9D;AAED,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,OAAgB,EAAQ;IACnE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,iBAAe,OAAO,EAAE,CAAC,CAAC,CAAC;IAClE,IAAI,OAAO,EAAE,CAAC;QACb,MAAM,QAAQ,GAAG,OAAO;aACtB,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,IAAI,EAAE,CAAC;aACnC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClC,CAAC;AAAA,CACD;AAED,MAAM,UAAU,aAAa,CAAC,GAA0B,EAAE,KAAa,EAAQ;IAC9E,MAAM,OAAO,GAAG,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,IAAI,OAAO,kBAAgB,CAAC,CAAC,CAAC;IACrE,MAAM,QAAQ,GAAG,KAAK;SACpB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,IAAI,EAAE,CAAC;SACnC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;AAAA,CACjC;AAED,gBAAgB;AAChB,MAAM,UAAU,eAAe,CAC9B,GAAe,EACf,KAMC,EACD,aAAsB,EACtB,aAAsB,EACb;IACT,MAAM,YAAY,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC;QAC/C,IAAI,KAAK,GAAG,IAAI;YAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC1C,IAAI,KAAK,GAAG,KAAK;YAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;QAC1D,IAAI,KAAK,GAAG,OAAO;YAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC;QAC3D,OAAO,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAAA,CAC1C,CAAC;IAEF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAC9B,KAAK,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,KAAK,CAAC,cAAc,EAAE,QAAQ,KAAK,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAC/F,IAAI,KAAK,CAAC,SAAS,GAAG,CAAC,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;QACjD,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,SAAS,CAAC,cAAc,EAAE,UAAU,KAAK,CAAC,UAAU,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;IAC3G,CAAC;IACD,IAAI,aAAa,IAAI,aAAa,EAAE,CAAC;QACpC,MAAM,cAAc,GAAG,CAAC,CAAC,aAAa,GAAG,aAAa,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC1E,KAAK,CAAC,IAAI,CAAC,YAAY,YAAY,CAAC,aAAa,CAAC,MAAM,YAAY,CAAC,aAAa,CAAC,KAAK,cAAc,IAAI,CAAC,CAAC;IAC7G,CAAC;IACD,KAAK,CAAC,IAAI,CACT,UAAU,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;QAC/E,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC;YAC3C,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc;YACtG,CAAC,CAAC,EAAE,CAAC,CACP,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,YAAY,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAEvD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEjC,iBAAiB;IACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,aAAU,CAAC,CAAC,CAAC;IAC1E,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,GAAG,CACR,cAAc,KAAK,CAAC,KAAK,CAAC,cAAc,EAAE,SAAS,KAAK,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM;QACrF,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC;YAC3C,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC,cAAc,EAAE,gBAAgB,KAAK,CAAC,UAAU,CAAC,cAAc,EAAE,eAAe;YACvG,CAAC,CAAC,EAAE,CAAC;QACN,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CACrC,CACD,CAAC;IAEF,OAAO,OAAO,CAAC;AAAA,CACf;AAED,8BAA8B;AAC9B,MAAM,UAAU,UAAU,CAAC,UAAkB,EAAE,OAAe,EAAQ;IACrE,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,wBAAwB,UAAU,EAAE,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,CAAC,cAAc,OAAO,EAAE,CAAC,CAAC;AAAA,CACrC;AAED,MAAM,UAAU,YAAY,GAAS;IACpC,OAAO,CAAC,GAAG,CAAC,yCAAqC,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAAA,CAChB;AAED,MAAM,UAAU,eAAe,GAAS;IACvC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;AAAA,CACrC;AAED,WAAW;AACX,MAAM,UAAU,gBAAgB,CAAC,YAAoB,EAAQ;IAC5D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,SAAS,EAAE,yBAAyB,YAAY,cAAc,CAAC,CAAC,CAAC;AAAA,CAC3F;AAED,MAAM,UAAU,kBAAkB,CAAC,WAAmB,EAAE,YAAoB,EAAQ;IACnF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,SAAS,EAAE,gBAAgB,WAAW,KAAK,YAAY,WAAW,CAAC,CAAC,CAAC;AAAA,CAC/F;AAED,MAAM,UAAU,mBAAmB,CAAC,aAAqB,EAAE,UAAkB,EAAQ;IACpF,MAAM,QAAQ,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,SAAS,EAAE,gCAAgC,aAAa,gBAAgB,QAAQ,GAAG,CAAC,CAAC,CAAC;AAAA,CAChH","sourcesContent":["import chalk from \"chalk\";\n\nexport interface LogContext {\n\tchannelId: string;\n\tuserName?: string;\n\tchannelName?: string; // For display like #dev-team vs C16HET4EQ\n}\n\nfunction timestamp(): string {\n\tconst now = new Date();\n\tconst hh = String(now.getHours()).padStart(2, \"0\");\n\tconst mm = String(now.getMinutes()).padStart(2, \"0\");\n\tconst ss = String(now.getSeconds()).padStart(2, \"0\");\n\treturn `[${hh}:${mm}:${ss}]`;\n}\n\nfunction formatContext(ctx: LogContext): string {\n\t// DMs: [DM:username]\n\t// Channels: [#channel-name:username] or [C16HET4EQ:username] if no name\n\tif (ctx.channelId.startsWith(\"D\")) {\n\t\treturn `[DM:${ctx.userName || ctx.channelId}]`;\n\t}\n\tconst channel = ctx.channelName || ctx.channelId;\n\tconst user = ctx.userName || \"unknown\";\n\treturn `[${channel.startsWith(\"#\") ? channel : `#${channel}`}:${user}]`;\n}\n\nfunction truncate(text: string, maxLen: number): string {\n\tif (text.length <= maxLen) return text;\n\treturn `${text.substring(0, maxLen)}\\n(truncated at ${maxLen} chars)`;\n}\n\nfunction formatToolArgs(args: Record<string, unknown>): string {\n\tconst lines: string[] = [];\n\n\tfor (const [key, value] of Object.entries(args)) {\n\t\t// Skip the label - it's already shown in the tool name\n\t\tif (key === \"label\") continue;\n\n\t\t// For read tool, format path with offset/limit\n\t\tif (key === \"path\" && typeof value === \"string\") {\n\t\t\tconst offset = args.offset as number | undefined;\n\t\t\tconst limit = args.limit as number | undefined;\n\t\t\tif (offset !== undefined && limit !== undefined) {\n\t\t\t\tlines.push(`${value}:${offset}-${offset + limit}`);\n\t\t\t} else {\n\t\t\t\tlines.push(value);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Skip offset/limit since we already handled them\n\t\tif (key === \"offset\" || key === \"limit\") continue;\n\n\t\t// For other values, format them\n\t\tif (typeof value === \"string\") {\n\t\t\t// Multi-line strings get indented\n\t\t\tif (value.includes(\"\\n\")) {\n\t\t\t\tlines.push(value);\n\t\t\t} else {\n\t\t\t\tlines.push(value);\n\t\t\t}\n\t\t} else {\n\t\t\tlines.push(JSON.stringify(value));\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\n// User messages\nexport function logUserMessage(ctx: LogContext, text: string): void {\n\tconsole.log(chalk.green(`${timestamp()} ${formatContext(ctx)} ${text}`));\n}\n\n// Tool execution\nexport function logToolStart(ctx: LogContext, toolName: string, label: string, args: Record<string, unknown>): void {\n\tconst formattedArgs = formatToolArgs(args);\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ↳ ${toolName}: ${label}`));\n\tif (formattedArgs) {\n\t\t// Indent the args\n\t\tconst indented = formattedArgs\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => ` ${line}`)\n\t\t\t.join(\"\\n\");\n\t\tconsole.log(chalk.dim(indented));\n\t}\n}\n\nexport function logToolSuccess(ctx: LogContext, toolName: string, durationMs: number, result: string): void {\n\tconst duration = (durationMs / 1000).toFixed(1);\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✓ ${toolName} (${duration}s)`));\n\n\tconst truncated = truncate(result, 1000);\n\tif (truncated) {\n\t\tconst indented = truncated\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => ` ${line}`)\n\t\t\t.join(\"\\n\");\n\t\tconsole.log(chalk.dim(indented));\n\t}\n}\n\nexport function logToolError(ctx: LogContext, toolName: string, durationMs: number, error: string): void {\n\tconst duration = (durationMs / 1000).toFixed(1);\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✗ ${toolName} (${duration}s)`));\n\n\tconst truncated = truncate(error, 1000);\n\tconst indented = truncated\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\n// Response streaming\nexport function logResponseStart(ctx: LogContext): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} → Streaming response...`));\n}\n\nexport function logThinking(ctx: LogContext, thinking: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💭 Thinking`));\n\tconst truncated = truncate(thinking, 1000);\n\tconst indented = truncated\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\nexport function logResponse(ctx: LogContext, text: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💬 Response`));\n\tconst truncated = truncate(text, 1000);\n\tconst indented = truncated\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\n// Attachments\nexport function logDownloadStart(ctx: LogContext, filename: string, localPath: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ↓ Downloading attachment`));\n\tconsole.log(chalk.dim(` ${filename} → ${localPath}`));\n}\n\nexport function logDownloadSuccess(ctx: LogContext, sizeKB: number): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✓ Downloaded (${sizeKB.toLocaleString()} KB)`));\n}\n\nexport function logDownloadError(ctx: LogContext, filename: string, error: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✗ Download failed`));\n\tconsole.log(chalk.dim(` ${filename}: ${error}`));\n}\n\n// Control\nexport function logStopRequest(ctx: LogContext): void {\n\tconsole.log(chalk.green(`${timestamp()} ${formatContext(ctx)} stop`));\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ⊗ Stop requested - aborting`));\n}\n\n// System\nexport function logInfo(message: string): void {\n\tconsole.log(chalk.blue(`${timestamp()} [system] ${message}`));\n}\n\nexport function logWarning(message: string, details?: string): void {\n\tconsole.log(chalk.yellow(`${timestamp()} [system] ⚠ ${message}`));\n\tif (details) {\n\t\tconst indented = details\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => ` ${line}`)\n\t\t\t.join(\"\\n\");\n\t\tconsole.log(chalk.dim(indented));\n\t}\n}\n\nexport function logAgentError(ctx: LogContext | \"system\", error: string): void {\n\tconst context = ctx === \"system\" ? \"[system]\" : formatContext(ctx);\n\tconsole.log(chalk.yellow(`${timestamp()} ${context} ✗ Agent error`));\n\tconst indented = error\n\t\t.split(\"\\n\")\n\t\t.map((line) => ` ${line}`)\n\t\t.join(\"\\n\");\n\tconsole.log(chalk.dim(indented));\n}\n\n// Usage summary\nexport function logUsageSummary(\n\tctx: LogContext,\n\tusage: {\n\t\tinput: number;\n\t\toutput: number;\n\t\tcacheRead: number;\n\t\tcacheWrite: number;\n\t\tcost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };\n\t},\n\tcontextTokens?: number,\n\tcontextWindow?: number,\n): string {\n\tconst formatTokens = (count: number): string => {\n\t\tif (count < 1000) return count.toString();\n\t\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\t\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\t\treturn `${(count / 1000000).toFixed(1)}M`;\n\t};\n\n\tconst lines: string[] = [];\n\tlines.push(\"*Usage Summary*\");\n\tlines.push(`Tokens: ${usage.input.toLocaleString()} in, ${usage.output.toLocaleString()} out`);\n\tif (usage.cacheRead > 0 || usage.cacheWrite > 0) {\n\t\tlines.push(`Cache: ${usage.cacheRead.toLocaleString()} read, ${usage.cacheWrite.toLocaleString()} write`);\n\t}\n\tif (contextTokens && contextWindow) {\n\t\tconst contextPercent = ((contextTokens / contextWindow) * 100).toFixed(1);\n\t\tlines.push(`Context: ${formatTokens(contextTokens)} / ${formatTokens(contextWindow)} (${contextPercent}%)`);\n\t}\n\tlines.push(\n\t\t`Cost: $${usage.cost.input.toFixed(4)} in, $${usage.cost.output.toFixed(4)} out` +\n\t\t\t(usage.cacheRead > 0 || usage.cacheWrite > 0\n\t\t\t\t? `, $${usage.cost.cacheRead.toFixed(4)} cache read, $${usage.cost.cacheWrite.toFixed(4)} cache write`\n\t\t\t\t: \"\"),\n\t);\n\tlines.push(`*Total: $${usage.cost.total.toFixed(4)}*`);\n\n\tconst summary = lines.join(\"\\n\");\n\n\t// Log to console\n\tconsole.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💰 Usage`));\n\tconsole.log(\n\t\tchalk.dim(\n\t\t\t` ${usage.input.toLocaleString()} in + ${usage.output.toLocaleString()} out` +\n\t\t\t\t(usage.cacheRead > 0 || usage.cacheWrite > 0\n\t\t\t\t\t? ` (${usage.cacheRead.toLocaleString()} cache read, ${usage.cacheWrite.toLocaleString()} cache write)`\n\t\t\t\t\t: \"\") +\n\t\t\t\t` = $${usage.cost.total.toFixed(4)}`,\n\t\t),\n\t);\n\n\treturn summary;\n}\n\n// Startup (no context needed)\nexport function logStartup(workingDir: string, sandbox: string): void {\n\tconsole.log(\"Starting mom bot...\");\n\tconsole.log(` Working directory: ${workingDir}`);\n\tconsole.log(` Sandbox: ${sandbox}`);\n}\n\nexport function logConnected(): void {\n\tconsole.log(\"⚡️ Mom bot connected and listening!\");\n\tconsole.log(\"\");\n}\n\nexport function logDisconnected(): void {\n\tconsole.log(\"Mom bot disconnected.\");\n}\n\n// Backfill\nexport function logBackfillStart(channelCount: number): void {\n\tconsole.log(chalk.blue(`${timestamp()} [system] Backfilling ${channelCount} channels...`));\n}\n\nexport function logBackfillChannel(channelName: string, messageCount: number): void {\n\tconsole.log(chalk.blue(`${timestamp()} [system] #${channelName}: ${messageCount} messages`));\n}\n\nexport function logBackfillComplete(totalMessages: number, durationMs: number): void {\n\tconst duration = (durationMs / 1000).toFixed(1);\n\tconsole.log(chalk.blue(`${timestamp()} [system] Backfill complete: ${totalMessages} messages in ${duration}s`));\n}\n"]}