@mariozechner/pi-mom 0.10.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/agent.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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","sourcesContent":["import { Agent, type AgentEvent, ProviderTransport } from \"@mariozechner/pi-agent-core\";\nimport { getModel } from \"@mariozechner/pi-ai\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { mkdir } from \"fs/promises\";\nimport { join } from \"path\";\nimport * as log from \"./log.js\";\nimport { createExecutor, type SandboxConfig } from \"./sandbox.js\";\nimport type { SlackContext } from \"./slack.js\";\nimport type { ChannelStore } from \"./store.js\";\nimport { createMomTools, setUploadFunction } from \"./tools/index.js\";\n\n// Hardcoded model for now\nconst model = getModel(\"anthropic\", \"claude-sonnet-4-5\");\n\nexport interface AgentRunner {\n\trun(ctx: SlackContext, channelDir: string, store: ChannelStore): Promise<{ stopReason: string }>;\n\tabort(): void;\n}\n\nfunction getAnthropicApiKey(): string {\n\tconst key = process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;\n\tif (!key) {\n\t\tthrow new Error(\"ANTHROPIC_OAUTH_TOKEN or ANTHROPIC_API_KEY must be set\");\n\t}\n\treturn key;\n}\n\nfunction getRecentMessages(channelDir: string, count: number): string {\n\tconst logPath = join(channelDir, \"log.jsonl\");\n\tif (!existsSync(logPath)) {\n\t\treturn \"(no message history yet)\";\n\t}\n\n\tconst content = readFileSync(logPath, \"utf-8\");\n\tconst lines = content.trim().split(\"\\n\").filter(Boolean);\n\tconst recentLines = lines.slice(-count);\n\n\tif (recentLines.length === 0) {\n\t\treturn \"(no message history yet)\";\n\t}\n\n\t// Format as TSV for more concise system prompt\n\tconst formatted: string[] = [];\n\tfor (const line of recentLines) {\n\t\ttry {\n\t\t\tconst msg = JSON.parse(line);\n\t\t\tconst date = (msg.date || \"\").substring(0, 19);\n\t\t\tconst user = msg.userName || msg.user;\n\t\t\tconst text = msg.text || \"\";\n\t\t\tconst attachments = (msg.attachments || []).map((a: { local: string }) => a.local).join(\",\");\n\t\t\tformatted.push(`${date}\\t${user}\\t${text}\\t${attachments}`);\n\t\t} catch (error) {}\n\t}\n\n\treturn formatted.join(\"\\n\");\n}\n\nfunction getMemory(channelDir: string): string {\n\tconst parts: string[] = [];\n\n\t// Read workspace-level memory (shared across all channels)\n\tconst workspaceMemoryPath = join(channelDir, \"..\", \"MEMORY.md\");\n\tif (existsSync(workspaceMemoryPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(workspaceMemoryPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(\"### Global Workspace Memory\\n\" + content);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read workspace memory\", `${workspaceMemoryPath}: ${error}`);\n\t\t}\n\t}\n\n\t// Read channel-specific memory\n\tconst channelMemoryPath = join(channelDir, \"MEMORY.md\");\n\tif (existsSync(channelMemoryPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(channelMemoryPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(\"### Channel-Specific Memory\\n\" + content);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read channel memory\", `${channelMemoryPath}: ${error}`);\n\t\t}\n\t}\n\n\tif (parts.length === 0) {\n\t\treturn \"(no working memory yet)\";\n\t}\n\n\treturn parts.join(\"\\n\\n\");\n}\n\nfunction buildSystemPrompt(\n\tworkspacePath: string,\n\tchannelId: string,\n\tmemory: string,\n\tsandboxConfig: SandboxConfig,\n): string {\n\tconst channelPath = `${workspacePath}/${channelId}`;\n\tconst isDocker = sandboxConfig.type === \"docker\";\n\n\tconst envDescription = isDocker\n\t\t? `You are running inside a Docker container (Alpine Linux).\n- Install tools with: apk add <package>\n- Your changes persist across sessions\n- You have full control over this container`\n\t\t: `You are running directly on the host machine.\n- Be careful with system modifications\n- Use the system's package manager if needed`;\n\n\tconst currentDate = new Date().toISOString().split(\"T\")[0]; // YYYY-MM-DD\n\tconst currentDateTime = new Date().toISOString(); // Full ISO 8601\n\n\treturn `You are mom, a helpful Slack bot assistant.\n\n## Current Date and Time\n- Date: ${currentDate}\n- Full timestamp: ${currentDateTime}\n- Use this when working with dates or searching logs\n\n## Communication Style\n- Be concise and professional\n- Do not use emojis unless the user communicates informally with you\n- Get to the point quickly\n- If you need clarification, ask directly\n- Use Slack's mrkdwn format (NOT standard Markdown):\n - Bold: *text* (single asterisks)\n - Italic: _text_\n - Strikethrough: ~text~\n - Code: \\`code\\`\n - Code block: \\`\\`\\`code\\`\\`\\`\n - Links: <url|text>\n - Do NOT use **double asterisks** or [markdown](links)\n\n## Your Environment\n${envDescription}\n\n## Your Workspace\nYour working directory is: ${channelPath}\n\n### Directory Structure\n- ${workspacePath}/ - Root workspace (shared across all channels)\n - MEMORY.md - GLOBAL memory visible to all channels (write global info here)\n - ${channelId}/ - This channel's directory\n - MEMORY.md - CHANNEL-SPECIFIC memory (only visible in this channel)\n - scratch/ - Your working directory for files, repos, etc.\n - log.jsonl - Message history in JSONL format (one JSON object per line)\n - attachments/ - Files shared by users (managed by system, read-only)\n\n### Message History Format\nEach line in log.jsonl contains:\n{\n \"date\": \"2025-11-26T10:44:00.123Z\", // ISO 8601 - easy to grep by date!\n \"ts\": \"1732619040.123456\", // Slack timestamp or epoch ms\n \"user\": \"U123ABC\", // User ID or \"bot\"\n \"userName\": \"mario\", // User handle (optional)\n \"text\": \"message text\",\n \"isBot\": false\n}\n\n**⚠️ CRITICAL: Efficient Log Queries (Avoid Context Overflow)**\n\nLog files can be VERY LARGE (100K+ lines). The problem is getting too MANY messages, not message length.\nEach message can be up to 10k chars - that's fine. Use head/tail to LIMIT NUMBER OF MESSAGES (10-50 at a time).\n\n**Install jq first (if not already):**\n\\`\\`\\`bash\n${isDocker ? \"apk add jq\" : \"# jq should be available, or install via package manager\"}\n\\`\\`\\`\n\n**Essential query patterns:**\n\\`\\`\\`bash\n# Last N messages (compact JSON output)\ntail -20 log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text, attachments: [(.attachments // [])[].local]}'\n\n# Or TSV format (easier to read)\ntail -20 log.jsonl | jq -r '[.date[0:19], (.userName // .user), .text, ((.attachments // []) | map(.local) | join(\",\"))] | @tsv'\n\n# Search by date (LIMIT with head/tail!)\ngrep '\"date\":\"2025-11-26' log.jsonl | tail -30 | jq -c '{date: .date[0:19], user: (.userName // .user), text, attachments: [(.attachments // [])[].local]}'\n\n# Messages from specific user (count first, then limit)\ngrep '\"userName\":\"mario\"' log.jsonl | wc -l # Check count first\ngrep '\"userName\":\"mario\"' log.jsonl | tail -20 | jq -c '{date: .date[0:19], user: .userName, text, attachments: [(.attachments // [])[].local]}'\n\n# Only count (when you just need the number)\ngrep '\"isBot\":false' log.jsonl | wc -l\n\n# Messages with attachments only (limit!)\ngrep '\"attachments\":[{' log.jsonl | tail -10 | jq -r '[.date[0:16], (.userName // .user), .text, (.attachments | map(.local) | join(\",\"))] | @tsv'\n\\`\\`\\`\n\n**KEY RULE:** Always pipe through 'head -N' or 'tail -N' to limit results BEFORE parsing with jq!\n\\`\\`\\`\n\n**Date filtering:**\n- Today: grep '\"date\":\"${currentDate}' log.jsonl\n- Yesterday: grep '\"date\":\"2025-11-25' log.jsonl\n- Date range: grep '\"date\":\"2025-11-(26|27|28)' log.jsonl\n- Time range: grep -E '\"date\":\"2025-11-26T(09|10|11):' log.jsonl\n\n### Working Memory System\nYou can maintain working memory across conversations by writing MEMORY.md files.\n\n**IMPORTANT PATH RULES:**\n- Global memory (all channels): ${workspacePath}/MEMORY.md\n- Channel memory (this channel only): ${channelPath}/MEMORY.md\n\n**What to remember:**\n- Project details and architecture → Global memory\n- User preferences and coding style → Global memory\n- Channel-specific context → Channel memory\n- Recurring tasks and patterns → Appropriate memory file\n- Credentials locations (never actual secrets) → Global memory\n- Decisions made and their rationale → Appropriate memory file\n\n**When to update:**\n- After learning something important that will help in future conversations\n- When user asks you to remember something\n- When you discover project structure or conventions\n\n### Current Working Memory\n${memory}\n\n## Tools\nYou have access to: bash, read, edit, write, attach tools.\n- bash: Run shell commands (this is your main tool)\n- read: Read files\n- edit: Edit files surgically\n- write: Create/overwrite files\n- attach: Share a file with the user in Slack\n\nEach tool requires a \"label\" parameter - brief description shown to the user.\n\n## Guidelines\n- Be concise and helpful\n- Use bash for most operations\n- If you need a tool, install it\n- If you need credentials, ask the user\n\n## CRITICAL\n- DO NOT USE EMOJIS. KEEP YOUR RESPONSES AS SHORT AS POSSIBLE.\n`;\n}\n\nfunction truncate(text: string, maxLen: number): string {\n\tif (text.length <= maxLen) return text;\n\treturn text.substring(0, maxLen - 3) + \"...\";\n}\n\nfunction extractToolResultText(result: unknown): string {\n\t// If it's already a string, return it\n\tif (typeof result === \"string\") {\n\t\treturn result;\n\t}\n\n\t// If it's an object with content array (tool result format)\n\tif (\n\t\tresult &&\n\t\ttypeof result === \"object\" &&\n\t\t\"content\" in result &&\n\t\tArray.isArray((result as { content: unknown }).content)\n\t) {\n\t\tconst content = (result as { content: Array<{ type: string; text?: string }> }).content;\n\t\tconst textParts: string[] = [];\n\t\tfor (const part of content) {\n\t\t\tif (part.type === \"text\" && part.text) {\n\t\t\t\ttextParts.push(part.text);\n\t\t\t}\n\t\t}\n\t\tif (textParts.length > 0) {\n\t\t\treturn textParts.join(\"\\n\");\n\t\t}\n\t}\n\n\t// Fallback to JSON\n\treturn JSON.stringify(result);\n}\n\nfunction formatToolArgsForSlack(_toolName: string, 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\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\tlines.push(value);\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\nexport function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner {\n\tlet agent: Agent | null = null;\n\tconst executor = createExecutor(sandboxConfig);\n\n\treturn {\n\t\tasync run(ctx: SlackContext, channelDir: string, store: ChannelStore): Promise<{ stopReason: string }> {\n\t\t\t// Ensure channel directory exists\n\t\t\tawait mkdir(channelDir, { recursive: true });\n\n\t\t\tconst channelId = ctx.message.channel;\n\t\t\tconst workspacePath = executor.getWorkspacePath(channelDir.replace(`/${channelId}`, \"\"));\n\t\t\tconst recentMessages = getRecentMessages(channelDir, 50);\n\t\t\tconst memory = getMemory(channelDir);\n\t\t\tconst systemPrompt = buildSystemPrompt(workspacePath, channelId, memory, sandboxConfig);\n\n\t\t\t// Set up file upload function for the attach tool\n\t\t\t// For Docker, we need to translate paths back to host\n\t\t\tsetUploadFunction(async (filePath: string, title?: string) => {\n\t\t\t\tconst hostPath = translateToHostPath(filePath, channelDir, workspacePath, channelId);\n\t\t\t\tawait ctx.uploadFile(hostPath, title);\n\t\t\t});\n\n\t\t\t// Create tools with executor\n\t\t\tconst tools = createMomTools(executor);\n\n\t\t\t// Create ephemeral agent\n\t\t\tagent = new Agent({\n\t\t\t\tinitialState: {\n\t\t\t\t\tsystemPrompt,\n\t\t\t\t\tmodel,\n\t\t\t\t\tthinkingLevel: \"off\",\n\t\t\t\t\ttools,\n\t\t\t\t},\n\t\t\t\ttransport: new ProviderTransport({\n\t\t\t\t\tgetApiKey: async () => getAnthropicApiKey(),\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\t// Create logging context\n\t\t\tconst logCtx = {\n\t\t\t\tchannelId: ctx.message.channel,\n\t\t\t\tuserName: ctx.message.userName,\n\t\t\t\tchannelName: ctx.channelName,\n\t\t\t};\n\n\t\t\t// Track pending tool calls to pair args with results and timing\n\t\t\tconst pendingTools = new Map<string, { toolName: string; args: unknown; startTime: number }>();\n\n\t\t\t// Track usage across all assistant messages in this run\n\t\t\tconst totalUsage = {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\tcost: {\n\t\t\t\t\tinput: 0,\n\t\t\t\t\toutput: 0,\n\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\ttotal: 0,\n\t\t\t\t},\n\t\t\t};\n\n\t\t\t// Track stop reason\n\t\t\tlet stopReason = \"stop\";\n\n\t\t\t// Promise queue to ensure ctx.respond/respondInThread calls execute in order\n\t\t\tconst queue = {\n\t\t\t\tchain: Promise.resolve(),\n\t\t\t\tenqueue<T>(fn: () => Promise<T>): Promise<T> {\n\t\t\t\t\tconst result = this.chain.then(fn);\n\t\t\t\t\tthis.chain = result.then(\n\t\t\t\t\t\t() => {},\n\t\t\t\t\t\t() => {},\n\t\t\t\t\t); // swallow errors for chain\n\t\t\t\t\treturn result;\n\t\t\t\t},\n\t\t\t\tflush(): Promise<void> {\n\t\t\t\t\treturn this.chain;\n\t\t\t\t},\n\t\t\t};\n\n\t\t\t// Subscribe to events\n\t\t\tagent.subscribe(async (event: AgentEvent) => {\n\t\t\t\tswitch (event.type) {\n\t\t\t\t\tcase \"tool_execution_start\": {\n\t\t\t\t\t\tconst args = event.args as { label?: string };\n\t\t\t\t\t\tconst label = args.label || event.toolName;\n\n\t\t\t\t\t\t// Store args to pair with result later\n\t\t\t\t\t\tpendingTools.set(event.toolCallId, {\n\t\t\t\t\t\t\ttoolName: event.toolName,\n\t\t\t\t\t\t\targs: event.args,\n\t\t\t\t\t\t\tstartTime: Date.now(),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Log to console\n\t\t\t\t\t\tlog.logToolStart(logCtx, event.toolName, label, event.args as Record<string, unknown>);\n\n\t\t\t\t\t\t// Log to jsonl\n\t\t\t\t\t\tawait store.logMessage(ctx.message.channel, {\n\t\t\t\t\t\t\tdate: new Date().toISOString(),\n\t\t\t\t\t\t\tts: Date.now().toString(),\n\t\t\t\t\t\t\tuser: \"bot\",\n\t\t\t\t\t\t\ttext: `[Tool] ${event.toolName}: ${JSON.stringify(event.args)}`,\n\t\t\t\t\t\t\tattachments: [],\n\t\t\t\t\t\t\tisBot: true,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Show label in main message only\n\t\t\t\t\t\tqueue.enqueue(() => ctx.respond(`_→ ${label}_`));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase \"tool_execution_end\": {\n\t\t\t\t\t\tconst resultStr = extractToolResultText(event.result);\n\t\t\t\t\t\tconst pending = pendingTools.get(event.toolCallId);\n\t\t\t\t\t\tpendingTools.delete(event.toolCallId);\n\n\t\t\t\t\t\tconst durationMs = pending ? Date.now() - pending.startTime : 0;\n\n\t\t\t\t\t\t// Log to console\n\t\t\t\t\t\tif (event.isError) {\n\t\t\t\t\t\t\tlog.logToolError(logCtx, event.toolName, durationMs, resultStr);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlog.logToolSuccess(logCtx, event.toolName, durationMs, resultStr);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Log to jsonl\n\t\t\t\t\t\tawait store.logMessage(ctx.message.channel, {\n\t\t\t\t\t\t\tdate: new Date().toISOString(),\n\t\t\t\t\t\t\tts: Date.now().toString(),\n\t\t\t\t\t\t\tuser: \"bot\",\n\t\t\t\t\t\t\ttext: `[Tool Result] ${event.toolName}: ${event.isError ? \"ERROR: \" : \"\"}${truncate(resultStr, 1000)}`,\n\t\t\t\t\t\t\tattachments: [],\n\t\t\t\t\t\t\tisBot: true,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Post args + result together in thread\n\t\t\t\t\t\tconst label = pending?.args ? (pending.args as { label?: string }).label : undefined;\n\t\t\t\t\t\tconst argsFormatted = pending\n\t\t\t\t\t\t\t? formatToolArgsForSlack(event.toolName, pending.args as Record<string, unknown>)\n\t\t\t\t\t\t\t: \"(args not found)\";\n\t\t\t\t\t\tconst duration = (durationMs / 1000).toFixed(1);\n\t\t\t\t\t\tconst threadResult = truncate(resultStr, 2000);\n\n\t\t\t\t\t\tlet threadMessage = `*${event.isError ? \"✗\" : \"✓\"} ${event.toolName}*`;\n\t\t\t\t\t\tif (label) {\n\t\t\t\t\t\t\tthreadMessage += `: ${label}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthreadMessage += ` (${duration}s)\\n`;\n\n\t\t\t\t\t\tif (argsFormatted) {\n\t\t\t\t\t\t\tthreadMessage += \"```\\n\" + argsFormatted + \"\\n```\\n\";\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthreadMessage += \"*Result:*\\n```\\n\" + threadResult + \"\\n```\";\n\n\t\t\t\t\t\tqueue.enqueue(() => ctx.respondInThread(threadMessage));\n\n\t\t\t\t\t\t// Show brief error in main message if failed\n\t\t\t\t\t\tif (event.isError) {\n\t\t\t\t\t\t\tqueue.enqueue(() => ctx.respond(`_Error: ${truncate(resultStr, 200)}_`));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase \"message_update\": {\n\t\t\t\t\t\t// No longer stream to console - just track that we're streaming\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase \"message_start\":\n\t\t\t\t\t\tif (event.message.role === \"assistant\") {\n\t\t\t\t\t\t\tlog.logResponseStart(logCtx);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase \"message_end\":\n\t\t\t\t\t\tif (event.message.role === \"assistant\") {\n\t\t\t\t\t\t\tconst assistantMsg = event.message as any; // AssistantMessage type\n\n\t\t\t\t\t\t\t// Track stop reason\n\t\t\t\t\t\t\tif (assistantMsg.stopReason) {\n\t\t\t\t\t\t\t\tstopReason = assistantMsg.stopReason;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Accumulate usage\n\t\t\t\t\t\t\tif (assistantMsg.usage) {\n\t\t\t\t\t\t\t\ttotalUsage.input += assistantMsg.usage.input;\n\t\t\t\t\t\t\t\ttotalUsage.output += assistantMsg.usage.output;\n\t\t\t\t\t\t\t\ttotalUsage.cacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\t\t\t\t\ttotalUsage.cacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\t\t\t\t\ttotalUsage.cost.input += assistantMsg.usage.cost.input;\n\t\t\t\t\t\t\t\ttotalUsage.cost.output += assistantMsg.usage.cost.output;\n\t\t\t\t\t\t\t\ttotalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;\n\t\t\t\t\t\t\t\ttotalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;\n\t\t\t\t\t\t\t\ttotalUsage.cost.total += assistantMsg.usage.cost.total;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Extract thinking and text from assistant message\n\t\t\t\t\t\t\tconst content = event.message.content;\n\t\t\t\t\t\t\tconst thinkingParts: string[] = [];\n\t\t\t\t\t\t\tconst textParts: string[] = [];\n\t\t\t\t\t\t\tfor (const part of content) {\n\t\t\t\t\t\t\t\tif (part.type === \"thinking\") {\n\t\t\t\t\t\t\t\t\tthinkingParts.push(part.thinking);\n\t\t\t\t\t\t\t\t} else if (part.type === \"text\") {\n\t\t\t\t\t\t\t\t\ttextParts.push(part.text);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst text = textParts.join(\"\\n\");\n\n\t\t\t\t\t\t\t// Post thinking to main message and thread\n\t\t\t\t\t\t\tfor (const thinking of thinkingParts) {\n\t\t\t\t\t\t\t\tlog.logThinking(logCtx, thinking);\n\t\t\t\t\t\t\t\tqueue.enqueue(() => ctx.respond(`_${thinking}_`));\n\t\t\t\t\t\t\t\tqueue.enqueue(() => ctx.respondInThread(`_${thinking}_`));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Post text to main message and thread\n\t\t\t\t\t\t\tif (text.trim()) {\n\t\t\t\t\t\t\t\tlog.logResponse(logCtx, text);\n\t\t\t\t\t\t\t\tqueue.enqueue(() => ctx.respond(text));\n\t\t\t\t\t\t\t\tqueue.enqueue(() => ctx.respondInThread(text));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Run the agent with user's message\n\t\t\t// Prepend recent messages to the user prompt (not system prompt) for better caching\n\t\t\tconst userPrompt =\n\t\t\t\t`Recent conversation history (last 50 messages):\\n` +\n\t\t\t\t`Format: date TAB user TAB text TAB attachments\\n\\n` +\n\t\t\t\t`${recentMessages}\\n\\n` +\n\t\t\t\t`---\\n\\n` +\n\t\t\t\t`Current message: ${ctx.message.text || \"(attached files)\"}`;\n\n\t\t\tawait agent.prompt(userPrompt);\n\n\t\t\t// Wait for all queued respond calls to complete\n\t\t\tawait queue.flush();\n\n\t\t\t// Get final assistant message text from agent state and replace main message\n\t\t\tconst messages = agent.state.messages;\n\t\t\tconst lastAssistant = messages.filter((m) => m.role === \"assistant\").pop();\n\t\t\tconst finalText =\n\t\t\t\tlastAssistant?.content\n\t\t\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t\t\t.map((c) => c.text)\n\t\t\t\t\t.join(\"\\n\") || \"\";\n\t\t\tif (finalText.trim()) {\n\t\t\t\tawait ctx.replaceMessage(finalText);\n\t\t\t}\n\n\t\t\t// Log usage summary if there was any usage\n\t\t\tif (totalUsage.cost.total > 0) {\n\t\t\t\tconst summary = log.logUsageSummary(logCtx, totalUsage);\n\t\t\t\tqueue.enqueue(() => ctx.respondInThread(summary));\n\t\t\t\tawait queue.flush();\n\t\t\t}\n\n\t\t\treturn { stopReason };\n\t\t},\n\n\t\tabort(): void {\n\t\t\tagent?.abort();\n\t\t},\n\t};\n}\n\n/**\n * Translate container path back to host path for file operations\n */\nfunction translateToHostPath(\n\tcontainerPath: string,\n\tchannelDir: string,\n\tworkspacePath: string,\n\tchannelId: string,\n): string {\n\tif (workspacePath === \"/workspace\") {\n\t\t// Docker mode - translate /workspace/channelId/... to host path\n\t\tconst prefix = `/workspace/${channelId}/`;\n\t\tif (containerPath.startsWith(prefix)) {\n\t\t\treturn join(channelDir, containerPath.slice(prefix.length));\n\t\t}\n\t\t// Maybe it's just /workspace/...\n\t\tif (containerPath.startsWith(\"/workspace/\")) {\n\t\t\treturn join(channelDir, \"..\", containerPath.slice(\"/workspace/\".length));\n\t\t}\n\t}\n\t// Host mode or already a host path\n\treturn containerPath;\n}\n"]}
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAmB,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACxF,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,cAAc,EAAsB,MAAM,cAAc,CAAC;AAGlE,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErE,0BAA0B;AAC1B,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;AAEzD;;;GAGG;AACH,IAAI,QAAQ,GAAG,CAAC,CAAC;AACjB,IAAI,SAAS,GAAG,CAAC,CAAC;AAElB,SAAS,SAAS,GAAW;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;QACtB,2DAA2D;QAC3D,SAAS,EAAE,CAAC;IACb,CAAC;SAAM,CAAC;QACP,kCAAkC;QAClC,QAAQ,GAAG,GAAG,CAAC;QACf,SAAS,GAAG,CAAC,CAAC;IACf,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,yBAAyB;IACzE,OAAO,GAAG,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AAAA,CAC1D;AAOD,SAAS,kBAAkB,GAAW;IACrC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAC/E,IAAI,CAAC,GAAG,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,GAAG,CAAC;AAAA,CACX;AAYD,SAAS,iBAAiB,CAAC,UAAkB,EAAE,SAAiB,EAAU;IACzE,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAC9C,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1B,OAAO,0BAA0B,CAAC;IACnC,CAAC;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEzD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,0BAA0B,CAAC;IACnC,CAAC;IAED,iDAAiD;IACjD,wDAAwD;IACxD,MAAM,QAAQ,GAAiB,EAAE,CAAC;IAClC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC;YACJ,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACX,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC;QACpC,OAAO,GAAG,GAAG,GAAG,CAAC;IAAA,CACjB,CAAC,CAAC;IAEH,yCAAyC;IACzC,yCAAyC;IACzC,iFAAiF;IACjF,4CAA4C;IAC5C,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,IAAI,WAAW,GAAiB,EAAE,CAAC;IACnC,IAAI,UAAU,GAAmB,IAAI,CAAC;IAEtC,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/C,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACxB,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,KAAK,IAAI,CAAC;QAEjC,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACzB,gBAAgB;YAChB,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACzB,UAAU,GAAG,KAAK,CAAC;QACpB,CAAC;aAAM,IAAI,KAAK,IAAI,UAAU,EAAE,CAAC;YAChC,uCAAuC;YACvC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;aAAM,CAAC;YACP,mDAAmD;YACnD,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YAC3B,WAAW,GAAG,CAAC,GAAG,CAAC,CAAC;YACpB,UAAU,GAAG,KAAK,CAAC;YAEnB,+BAA+B;YAC/B,IAAI,KAAK,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;gBAC/B,MAAM;YACP,CAAC;QACF,CAAC;IACF,CAAC;IAED,8CAA8C;IAC9C,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;QACxD,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC5B,CAAC;IAED,mDAAmD;IACnD,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACxB,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC/C,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;YAC5B,MAAM,WAAW,GAAG,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC1E,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,WAAW,EAAE,CAAC,CAAC;QAC7D,CAAC;IACF,CAAC;IAED,OAAO,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CAC5B;AAED,SAAS,SAAS,CAAC,UAAkB,EAAU;IAC9C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,2DAA2D;IAC3D,MAAM,mBAAmB,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IAChE,IAAI,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,YAAY,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;YAClE,IAAI,OAAO,EAAE,CAAC;gBACb,KAAK,CAAC,IAAI,CAAC,+BAA+B,GAAG,OAAO,CAAC,CAAC;YACvD,CAAC;QACF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,GAAG,CAAC,UAAU,CAAC,iCAAiC,EAAE,GAAG,mBAAmB,KAAK,KAAK,EAAE,CAAC,CAAC;QACvF,CAAC;IACF,CAAC;IAED,+BAA+B;IAC/B,MAAM,iBAAiB,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IACxD,IAAI,UAAU,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACnC,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,YAAY,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;YAChE,IAAI,OAAO,EAAE,CAAC;gBACb,KAAK,CAAC,IAAI,CAAC,+BAA+B,GAAG,OAAO,CAAC,CAAC;YACvD,CAAC;QACF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,GAAG,CAAC,UAAU,CAAC,+BAA+B,EAAE,GAAG,iBAAiB,KAAK,KAAK,EAAE,CAAC,CAAC;QACnF,CAAC;IACF,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,yBAAyB,CAAC;IAClC,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAAA,CAC1B;AAED,SAAS,iBAAiB,CACzB,aAAqB,EACrB,SAAiB,EACjB,MAAc,EACd,aAA4B,EAC5B,QAAuB,EACvB,KAAiB,EACR;IACT,MAAM,WAAW,GAAG,GAAG,aAAa,IAAI,SAAS,EAAE,CAAC;IACpD,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,KAAK,QAAQ,CAAC;IAEjD,0BAA0B;IAC1B,MAAM,eAAe,GACpB,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC;IAEtG,uBAAuB;IACvB,MAAM,YAAY,GACjB,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC;IAEnH,MAAM,cAAc,GAAG,QAAQ;QAC9B,CAAC,CAAC;;;uCAGmC;QACrC,CAAC,CAAC;4BACwB,OAAO,CAAC,GAAG,EAAE;uCACF,CAAC;IAEvC,MAAM,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa;IACzE,MAAM,eAAe,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,gBAAgB;IAElE,OAAO;;;UAGE,WAAW,KAAK,eAAe;;;;;;;;YAQ7B,eAAe;;SAElB,YAAY;;;;;EAKnB,cAAc;;;EAGd,aAAa;;;YAGT,SAAS;;;;;;;;;aASF,aAAa,0BAA0B,WAAW;;;;;;YAMnD,aAAa;aACZ,WAAW;;;;EAItB,MAAM;;;;;EAKN,QAAQ,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BzC,CAAC;AAAA,CACD;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,MAAc,EAAU;IACvD,IAAI,IAAI,CAAC,MAAM,IAAI,MAAM;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;AAAA,CAC7C;AAED,SAAS,qBAAqB,CAAC,MAAe,EAAU;IACvD,sCAAsC;IACtC,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC;IACf,CAAC;IAED,4DAA4D;IAC5D,IACC,MAAM;QACN,OAAO,MAAM,KAAK,QAAQ;QAC1B,SAAS,IAAI,MAAM;QACnB,KAAK,CAAC,OAAO,CAAE,MAA+B,CAAC,OAAO,CAAC,EACtD,CAAC;QACF,MAAM,OAAO,GAAI,MAA8D,CAAC,OAAO,CAAC;QACxF,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBACvC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC3B,CAAC;QACF,CAAC;QACD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,OAAO,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;IACF,CAAC;IAED,mBAAmB;IACnB,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AAAA,CAC9B;AAED,SAAS,sBAAsB,CAAC,SAAiB,EAAE,IAA6B,EAAU;IACzF,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,sCAAsC;QACtC,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,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,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,MAAM,UAAU,iBAAiB,CAAC,aAA4B,EAAe;IAC5E,IAAI,KAAK,GAAiB,IAAI,CAAC;IAC/B,MAAM,QAAQ,GAAG,cAAc,CAAC,aAAa,CAAC,CAAC;IAE/C,OAAO;QACN,KAAK,CAAC,GAAG,CAAC,GAAiB,EAAE,UAAkB,EAAE,KAAmB,EAAmC;YACtG,kCAAkC;YAClC,MAAM,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAE7C,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;YACtC,MAAM,aAAa,GAAG,QAAQ,CAAC,gBAAgB,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YACzF,MAAM,cAAc,GAAG,iBAAiB,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;YACzD,MAAM,MAAM,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;YACrC,MAAM,YAAY,GAAG,iBAAiB,CACrC,aAAa,EACb,SAAS,EACT,MAAM,EACN,aAAa,EACb,GAAG,CAAC,QAAQ,EACZ,GAAG,CAAC,KAAK,CACT,CAAC;YAEF,2BAA2B;YAC3B,GAAG,CAAC,OAAO,CACV,2BAA2B,YAAY,CAAC,MAAM,qBAAqB,cAAc,CAAC,MAAM,mBAAmB,MAAM,CAAC,MAAM,QAAQ,CAChI,CAAC;YACF,GAAG,CAAC,OAAO,CAAC,aAAa,GAAG,CAAC,QAAQ,CAAC,MAAM,YAAY,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;YAE5E,kDAAkD;YAClD,sDAAsD;YACtD,iBAAiB,CAAC,KAAK,EAAE,QAAgB,EAAE,KAAc,EAAE,EAAE,CAAC;gBAC7D,MAAM,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;gBACrF,MAAM,GAAG,CAAC,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YAAA,CACtC,CAAC,CAAC;YAEH,6BAA6B;YAC7B,MAAM,KAAK,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;YAEvC,yBAAyB;YACzB,KAAK,GAAG,IAAI,KAAK,CAAC;gBACjB,YAAY,EAAE;oBACb,YAAY;oBACZ,KAAK;oBACL,aAAa,EAAE,KAAK;oBACpB,KAAK;iBACL;gBACD,SAAS,EAAE,IAAI,iBAAiB,CAAC;oBAChC,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,kBAAkB,EAAE;iBAC3C,CAAC;aACF,CAAC,CAAC;YAEH,yBAAyB;YACzB,MAAM,MAAM,GAAG;gBACd,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO;gBAC9B,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,QAAQ;gBAC9B,WAAW,EAAE,GAAG,CAAC,WAAW;aAC5B,CAAC;YAEF,gEAAgE;YAChE,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkE,CAAC;YAE/F,wDAAwD;YACxD,MAAM,UAAU,GAAG;gBAClB,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,CAAC;gBACT,SAAS,EAAE,CAAC;gBACZ,UAAU,EAAE,CAAC;gBACb,IAAI,EAAE;oBACL,KAAK,EAAE,CAAC;oBACR,MAAM,EAAE,CAAC;oBACT,SAAS,EAAE,CAAC;oBACZ,UAAU,EAAE,CAAC;oBACb,KAAK,EAAE,CAAC;iBACR;aACD,CAAC;YAEF,oBAAoB;YACpB,IAAI,UAAU,GAAG,MAAM,CAAC;YAExB,oFAAoF;YACpF,MAAM,gBAAgB,GAAG,KAAK,CAAC;YAC/B,MAAM,aAAa,GAAG,CAAC,IAAY,EAAY,EAAE,CAAC;gBACjD,IAAI,IAAI,CAAC,MAAM,IAAI,gBAAgB;oBAAE,OAAO,CAAC,IAAI,CAAC,CAAC;gBACnD,MAAM,KAAK,GAAa,EAAE,CAAC;gBAC3B,IAAI,SAAS,GAAG,IAAI,CAAC;gBACrB,IAAI,OAAO,GAAG,CAAC,CAAC;gBAChB,OAAO,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC7B,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,gBAAgB,GAAG,EAAE,CAAC,CAAC;oBAC5D,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC,gBAAgB,GAAG,EAAE,CAAC,CAAC;oBACvD,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC3E,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC;oBAC3B,OAAO,EAAE,CAAC;gBACX,CAAC;gBACD,OAAO,KAAK,CAAC;YAAA,CACb,CAAC;YAEF,6EAA6E;YAC7E,qEAAqE;YACrE,MAAM,KAAK,GAAG;gBACb,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE;gBACxB,OAAO,CAAC,EAAuB,EAAE,YAAoB,EAAQ;oBAC5D,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;wBACxC,IAAI,CAAC;4BACJ,MAAM,EAAE,EAAE,CAAC;wBACZ,CAAC;wBAAC,OAAO,GAAG,EAAE,CAAC;4BACd,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;4BAChE,GAAG,CAAC,UAAU,CAAC,oBAAoB,YAAY,GAAG,EAAE,MAAM,CAAC,CAAC;4BAC5D,iEAAiE;4BACjE,IAAI,CAAC;gCACJ,MAAM,GAAG,CAAC,eAAe,CAAC,WAAW,MAAM,GAAG,CAAC,CAAC;4BACjD,CAAC;4BAAC,MAAM,CAAC;gCACR,6BAA6B;4BAC9B,CAAC;wBACF,CAAC;oBAAA,CACD,CAAC,CAAC;gBAAA,CACH;gBACD,4CAA4C;gBAC5C,cAAc,CAAC,IAAY,EAAE,MAAyB,EAAE,YAAoB,EAAE,GAAG,GAAG,IAAI,EAAQ;oBAC/F,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;oBAClC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;wBAC1B,IAAI,CAAC,OAAO,CACX,GAAG,EAAE,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,EAC9E,YAAY,CACZ,CAAC;oBACH,CAAC;gBAAA,CACD;gBACD,KAAK,GAAkB;oBACtB,OAAO,IAAI,CAAC,KAAK,CAAC;gBAAA,CAClB;aACD,CAAC;YAEF,sBAAsB;YACtB,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,KAAiB,EAAE,EAAE,CAAC;gBAC5C,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;oBACpB,KAAK,sBAAsB,EAAE,CAAC;wBAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,IAA0B,CAAC;wBAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,QAAQ,CAAC;wBAE3C,uCAAuC;wBACvC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE;4BAClC,QAAQ,EAAE,KAAK,CAAC,QAAQ;4BACxB,IAAI,EAAE,KAAK,CAAC,IAAI;4BAChB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;yBACrB,CAAC,CAAC;wBAEH,iBAAiB;wBACjB,GAAG,CAAC,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,IAA+B,CAAC,CAAC;wBAEvF,eAAe;wBACf,MAAM,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE;4BAC3C,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;4BAC9B,EAAE,EAAE,SAAS,EAAE;4BACf,IAAI,EAAE,KAAK;4BACX,IAAI,EAAE,UAAU,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE;4BAC/D,WAAW,EAAE,EAAE;4BACf,KAAK,EAAE,IAAI;yBACX,CAAC,CAAC;wBAEH,kCAAkC;wBAClC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,QAAM,KAAK,GAAG,EAAE,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC;wBACtE,MAAM;oBACP,CAAC;oBAED,KAAK,oBAAoB,EAAE,CAAC;wBAC3B,MAAM,SAAS,GAAG,qBAAqB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;wBACtD,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;wBACnD,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;wBAEtC,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;wBAEhE,iBAAiB;wBACjB,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;4BACnB,GAAG,CAAC,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;wBACjE,CAAC;6BAAM,CAAC;4BACP,GAAG,CAAC,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;wBACnE,CAAC;wBAED,eAAe;wBACf,MAAM,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE;4BAC3C,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;4BAC9B,EAAE,EAAE,SAAS,EAAE;4BACf,IAAI,EAAE,KAAK;4BACX,IAAI,EAAE,iBAAiB,KAAK,CAAC,QAAQ,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE;4BACtG,WAAW,EAAE,EAAE;4BACf,KAAK,EAAE,IAAI;yBACX,CAAC,CAAC;wBAEH,wCAAwC;wBACxC,MAAM,KAAK,GAAG,OAAO,EAAE,IAAI,CAAC,CAAC,CAAE,OAAO,CAAC,IAA2B,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;wBACrF,MAAM,aAAa,GAAG,OAAO;4BAC5B,CAAC,CAAC,sBAAsB,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,IAA+B,CAAC;4BACjF,CAAC,CAAC,kBAAkB,CAAC;wBACtB,MAAM,QAAQ,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;wBAChD,IAAI,aAAa,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAG,CAAC,CAAC,CAAC,KAAG,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC;wBACvE,IAAI,KAAK,EAAE,CAAC;4BACX,aAAa,IAAI,KAAK,KAAK,EAAE,CAAC;wBAC/B,CAAC;wBACD,aAAa,IAAI,KAAK,QAAQ,MAAM,CAAC;wBAErC,IAAI,aAAa,EAAE,CAAC;4BACnB,aAAa,IAAI,OAAO,GAAG,aAAa,GAAG,SAAS,CAAC;wBACtD,CAAC;wBAED,aAAa,IAAI,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAAC;wBAE1D,KAAK,CAAC,cAAc,CAAC,aAAa,EAAE,QAAQ,EAAE,oBAAoB,EAAE,KAAK,CAAC,CAAC;wBAE3E,6CAA6C;wBAC7C,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;4BACnB,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC;wBAC/F,CAAC;wBACD,MAAM;oBACP,CAAC;oBAED,KAAK,gBAAgB,EAAE,CAAC;wBACvB,gEAAgE;wBAChE,MAAM;oBACP,CAAC;oBAED,KAAK,eAAe;wBACnB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;4BACxC,GAAG,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;wBAC9B,CAAC;wBACD,MAAM;oBAEP,KAAK,aAAa;wBACjB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;4BACxC,MAAM,YAAY,GAAG,KAAK,CAAC,OAAc,CAAC,CAAC,wBAAwB;4BAEnE,oBAAoB;4BACpB,IAAI,YAAY,CAAC,UAAU,EAAE,CAAC;gCAC7B,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC;4BACtC,CAAC;4BAED,mBAAmB;4BACnB,IAAI,YAAY,CAAC,KAAK,EAAE,CAAC;gCACxB,UAAU,CAAC,KAAK,IAAI,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC;gCAC7C,UAAU,CAAC,MAAM,IAAI,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC;gCAC/C,UAAU,CAAC,SAAS,IAAI,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC;gCACrD,UAAU,CAAC,UAAU,IAAI,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC;gCACvD,UAAU,CAAC,IAAI,CAAC,KAAK,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;gCACvD,UAAU,CAAC,IAAI,CAAC,MAAM,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;gCACzD,UAAU,CAAC,IAAI,CAAC,SAAS,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;gCAC/D,UAAU,CAAC,IAAI,CAAC,UAAU,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC;gCACjE,UAAU,CAAC,IAAI,CAAC,KAAK,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;4BACxD,CAAC;4BAED,mDAAmD;4BACnD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;4BACtC,MAAM,aAAa,GAAa,EAAE,CAAC;4BACnC,MAAM,SAAS,GAAa,EAAE,CAAC;4BAC/B,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;gCAC5B,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;oCAC9B,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gCACnC,CAAC;qCAAM,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oCACjC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gCAC3B,CAAC;4BACF,CAAC;4BAED,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;4BAElC,2CAA2C;4BAC3C,KAAK,MAAM,QAAQ,IAAI,aAAa,EAAE,CAAC;gCACtC,GAAG,CAAC,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;gCAClC,KAAK,CAAC,cAAc,CAAC,IAAI,QAAQ,GAAG,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;gCAC/D,KAAK,CAAC,cAAc,CAAC,IAAI,QAAQ,GAAG,EAAE,QAAQ,EAAE,iBAAiB,EAAE,KAAK,CAAC,CAAC;4BAC3E,CAAC;4BAED,uCAAuC;4BACvC,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gCACjB,GAAG,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gCAC9B,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;gCACpD,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,iBAAiB,EAAE,KAAK,CAAC,CAAC;4BAChE,CAAC;wBACF,CAAC;wBACD,MAAM;gBACR,CAAC;YAAA,CACD,CAAC,CAAC;YAEH,oCAAoC;YACpC,oFAAoF;YACpF,kEAAkE;YAClE,MAAM,UAAU,GACf,sEAAsE;gBACtE,oDAAoD;gBACpD,cAAc,CAAC;YAChB,oCAAoC;YACpC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;YAC5G,MAAM,WAAW,GAChB,sBAAsB,YAAY,CAAC,MAAM,kBAAkB,YAAY,MAAM;gBAC7E,yBAAyB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,MAAM,kBAAkB,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM;gBACjH,oBAAoB,UAAU,CAAC,MAAM,kBAAkB,UAAU,EAAE,CAAC;YACrE,MAAM,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,iBAAiB,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;YAE3E,MAAM,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAE/B,gDAAgD;YAChD,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;YAEpB,6EAA6E;YAC7E,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC;YACtC,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,GAAG,EAAE,CAAC;YAC3E,MAAM,SAAS,GACd,aAAa,EAAE,OAAO;iBACpB,MAAM,CAAC,CAAC,CAAC,EAAuC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;iBACrE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;iBAClB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACpB,IAAI,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC;gBACtB,IAAI,CAAC;oBACJ,sEAAsE;oBACtE,MAAM,QAAQ,GACb,SAAS,CAAC,MAAM,GAAG,gBAAgB;wBAClC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,gBAAgB,GAAG,EAAE,CAAC,GAAG,sCAAsC;wBACxF,CAAC,CAAC,SAAS,CAAC;oBACd,MAAM,GAAG,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;gBACpC,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;oBAChE,GAAG,CAAC,UAAU,CAAC,2CAA2C,EAAE,MAAM,CAAC,CAAC;gBACrE,CAAC;YACF,CAAC;YAED,2CAA2C;YAC3C,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;gBAC/B,MAAM,OAAO,GAAG,GAAG,CAAC,eAAe,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;gBACxD,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC,CAAC;gBACnE,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;YACrB,CAAC;YAED,OAAO,EAAE,UAAU,EAAE,CAAC;QAAA,CACtB;QAED,KAAK,GAAS;YACb,KAAK,EAAE,KAAK,EAAE,CAAC;QAAA,CACf;KACD,CAAC;AAAA,CACF;AAED;;GAEG;AACH,SAAS,mBAAmB,CAC3B,aAAqB,EACrB,UAAkB,EAClB,aAAqB,EACrB,SAAiB,EACR;IACT,IAAI,aAAa,KAAK,YAAY,EAAE,CAAC;QACpC,gEAAgE;QAChE,MAAM,MAAM,GAAG,cAAc,SAAS,GAAG,CAAC;QAC1C,IAAI,aAAa,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7D,CAAC;QACD,iCAAiC;QACjC,IAAI,aAAa,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;YAC7C,OAAO,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,aAAa,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;QAC1E,CAAC;IACF,CAAC;IACD,mCAAmC;IACnC,OAAO,aAAa,CAAC;AAAA,CACrB","sourcesContent":["import { Agent, type AgentEvent, ProviderTransport } from \"@mariozechner/pi-agent-core\";\nimport { getModel } from \"@mariozechner/pi-ai\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { mkdir, writeFile } from \"fs/promises\";\nimport { join } from \"path\";\nimport * as log from \"./log.js\";\nimport { createExecutor, type SandboxConfig } from \"./sandbox.js\";\nimport type { ChannelInfo, SlackContext, UserInfo } from \"./slack.js\";\nimport type { ChannelStore } from \"./store.js\";\nimport { createMomTools, setUploadFunction } from \"./tools/index.js\";\n\n// Hardcoded model for now\nconst model = getModel(\"anthropic\", \"claude-sonnet-4-5\");\n\n/**\n * Convert Date.now() to Slack timestamp format (seconds.microseconds)\n * Uses a monotonic counter to ensure ordering even within the same millisecond\n */\nlet lastTsMs = 0;\nlet tsCounter = 0;\n\nfunction toSlackTs(): string {\n\tconst now = Date.now();\n\tif (now === lastTsMs) {\n\t\t// Same millisecond - increment counter for sub-ms ordering\n\t\ttsCounter++;\n\t} else {\n\t\t// New millisecond - reset counter\n\t\tlastTsMs = now;\n\t\ttsCounter = 0;\n\t}\n\tconst seconds = Math.floor(now / 1000);\n\tconst micros = (now % 1000) * 1000 + tsCounter; // ms to micros + counter\n\treturn `${seconds}.${micros.toString().padStart(6, \"0\")}`;\n}\n\nexport interface AgentRunner {\n\trun(ctx: SlackContext, channelDir: string, store: ChannelStore): Promise<{ stopReason: string }>;\n\tabort(): void;\n}\n\nfunction getAnthropicApiKey(): string {\n\tconst key = process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;\n\tif (!key) {\n\t\tthrow new Error(\"ANTHROPIC_OAUTH_TOKEN or ANTHROPIC_API_KEY must be set\");\n\t}\n\treturn key;\n}\n\ninterface LogMessage {\n\tdate?: string;\n\tts?: string;\n\tuser?: string;\n\tuserName?: string;\n\ttext?: string;\n\tattachments?: Array<{ local: string }>;\n\tisBot?: boolean;\n}\n\nfunction getRecentMessages(channelDir: string, turnCount: number): string {\n\tconst logPath = join(channelDir, \"log.jsonl\");\n\tif (!existsSync(logPath)) {\n\t\treturn \"(no message history yet)\";\n\t}\n\n\tconst content = readFileSync(logPath, \"utf-8\");\n\tconst lines = content.trim().split(\"\\n\").filter(Boolean);\n\n\tif (lines.length === 0) {\n\t\treturn \"(no message history yet)\";\n\t}\n\n\t// Parse all messages and sort by Slack timestamp\n\t// (attachment downloads can cause out-of-order logging)\n\tconst messages: LogMessage[] = [];\n\tfor (const line of lines) {\n\t\ttry {\n\t\t\tmessages.push(JSON.parse(line));\n\t\t} catch {}\n\t}\n\tmessages.sort((a, b) => {\n\t\tconst tsA = parseFloat(a.ts || \"0\");\n\t\tconst tsB = parseFloat(b.ts || \"0\");\n\t\treturn tsA - tsB;\n\t});\n\n\t// Group into \"turns\" - a turn is either:\n\t// - A single user message (isBot: false)\n\t// - A sequence of consecutive bot messages (isBot: true) coalesced into one turn\n\t// We walk backwards to get the last N turns\n\tconst turns: LogMessage[][] = [];\n\tlet currentTurn: LogMessage[] = [];\n\tlet lastWasBot: boolean | null = null;\n\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst msg = messages[i];\n\t\tconst isBot = msg.isBot === true;\n\n\t\tif (lastWasBot === null) {\n\t\t\t// First message\n\t\t\tcurrentTurn.unshift(msg);\n\t\t\tlastWasBot = isBot;\n\t\t} else if (isBot && lastWasBot) {\n\t\t\t// Consecutive bot messages - same turn\n\t\t\tcurrentTurn.unshift(msg);\n\t\t} else {\n\t\t\t// Transition - save current turn and start new one\n\t\t\tturns.unshift(currentTurn);\n\t\t\tcurrentTurn = [msg];\n\t\t\tlastWasBot = isBot;\n\n\t\t\t// Stop if we have enough turns\n\t\t\tif (turns.length >= turnCount) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Don't forget the last turn we were building\n\tif (currentTurn.length > 0 && turns.length < turnCount) {\n\t\tturns.unshift(currentTurn);\n\t}\n\n\t// Flatten turns back to messages and format as TSV\n\tconst formatted: string[] = [];\n\tfor (const turn of turns) {\n\t\tfor (const msg of turn) {\n\t\t\tconst date = (msg.date || \"\").substring(0, 19);\n\t\t\tconst user = msg.userName || msg.user || \"\";\n\t\t\tconst text = msg.text || \"\";\n\t\t\tconst attachments = (msg.attachments || []).map((a) => a.local).join(\",\");\n\t\t\tformatted.push(`${date}\\t${user}\\t${text}\\t${attachments}`);\n\t\t}\n\t}\n\n\treturn formatted.join(\"\\n\");\n}\n\nfunction getMemory(channelDir: string): string {\n\tconst parts: string[] = [];\n\n\t// Read workspace-level memory (shared across all channels)\n\tconst workspaceMemoryPath = join(channelDir, \"..\", \"MEMORY.md\");\n\tif (existsSync(workspaceMemoryPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(workspaceMemoryPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(\"### Global Workspace Memory\\n\" + content);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read workspace memory\", `${workspaceMemoryPath}: ${error}`);\n\t\t}\n\t}\n\n\t// Read channel-specific memory\n\tconst channelMemoryPath = join(channelDir, \"MEMORY.md\");\n\tif (existsSync(channelMemoryPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(channelMemoryPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(\"### Channel-Specific Memory\\n\" + content);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read channel memory\", `${channelMemoryPath}: ${error}`);\n\t\t}\n\t}\n\n\tif (parts.length === 0) {\n\t\treturn \"(no working memory yet)\";\n\t}\n\n\treturn parts.join(\"\\n\\n\");\n}\n\nfunction buildSystemPrompt(\n\tworkspacePath: string,\n\tchannelId: string,\n\tmemory: string,\n\tsandboxConfig: SandboxConfig,\n\tchannels: ChannelInfo[],\n\tusers: UserInfo[],\n): string {\n\tconst channelPath = `${workspacePath}/${channelId}`;\n\tconst isDocker = sandboxConfig.type === \"docker\";\n\n\t// Format channel mappings\n\tconst channelMappings =\n\t\tchannels.length > 0 ? channels.map((c) => `${c.id}\\t#${c.name}`).join(\"\\n\") : \"(no channels loaded)\";\n\n\t// Format user mappings\n\tconst userMappings =\n\t\tusers.length > 0 ? users.map((u) => `${u.id}\\t@${u.userName}\\t${u.displayName}`).join(\"\\n\") : \"(no users loaded)\";\n\n\tconst envDescription = isDocker\n\t\t? `You are running inside a Docker container (Alpine Linux).\n- Bash working directory: / (use cd or absolute paths)\n- Install tools with: apk add <package>\n- Your changes persist across sessions`\n\t\t: `You are running directly on the host machine.\n- Bash working directory: ${process.cwd()}\n- Be careful with system modifications`;\n\n\tconst currentDate = new Date().toISOString().split(\"T\")[0]; // YYYY-MM-DD\n\tconst currentDateTime = new Date().toISOString(); // Full ISO 8601\n\n\treturn `You are mom, a Slack bot assistant. Be concise. No emojis.\n\n## Context\n- Date: ${currentDate} (${currentDateTime})\n- You receive the last 50 conversation turns. If you need older context, search log.jsonl.\n\n## Slack Formatting (mrkdwn, NOT Markdown)\nBold: *text*, Italic: _text_, Code: \\`code\\`, Block: \\`\\`\\`code\\`\\`\\`, Links: <url|text>\nDo NOT use **double asterisks** or [markdown](links).\n\n## Slack IDs\nChannels: ${channelMappings}\n\nUsers: ${userMappings}\n\nWhen mentioning users, use <@username> format (e.g., <@mario>).\n\n## Environment\n${envDescription}\n\n## Workspace Layout\n${workspacePath}/\n├── MEMORY.md # Global memory (all channels)\n├── skills/ # Global CLI tools you create\n└── ${channelId}/ # This channel\n ├── MEMORY.md # Channel-specific memory\n ├── log.jsonl # Full message history\n ├── attachments/ # User-shared files\n ├── scratch/ # Your working directory\n └── skills/ # Channel-specific tools\n\n## Skills (Custom CLI Tools)\nYou can create reusable CLI tools for recurring tasks (email, APIs, data processing, etc.).\nStore in \\`${workspacePath}/skills/<name>/\\` or \\`${channelPath}/skills/<name>/\\`.\nEach skill needs a \\`SKILL.md\\` documenting usage. Read it before using a skill.\nList skills in global memory so you remember them.\n\n## Memory\nWrite to MEMORY.md files to persist context across conversations.\n- Global (${workspacePath}/MEMORY.md): skills, preferences, project info\n- Channel (${channelPath}/MEMORY.md): channel-specific decisions, ongoing work\nUpdate when you learn something important or when asked to remember something.\n\n### Current Memory\n${memory}\n\n## Log Queries (CRITICAL: limit output to avoid context overflow)\nFormat: \\`{\"date\":\"...\",\"ts\":\"...\",\"user\":\"...\",\"userName\":\"...\",\"text\":\"...\",\"isBot\":false}\\`\nThe log contains user messages AND your tool calls/results. Filter appropriately.\n${isDocker ? \"Install jq: apk add jq\" : \"\"}\n\n**Conversation only (excludes tool calls/results) - use for summaries:**\n\\`\\`\\`bash\n# Recent conversation (no [Tool] or [Tool Result] lines)\ngrep -v '\"text\":\"\\\\[Tool' log.jsonl | tail -30 | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Yesterday's conversation\ngrep '\"date\":\"2025-11-26' log.jsonl | grep -v '\"text\":\"\\\\[Tool' | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Specific user's messages\ngrep '\"userName\":\"mario\"' log.jsonl | grep -v '\"text\":\"\\\\[Tool' | tail -20 | jq -c '{date: .date[0:19], text}'\n\\`\\`\\`\n\n**Full details (includes tool calls) - use when you need technical context:**\n\\`\\`\\`bash\n# Raw recent entries\ntail -20 log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Count all messages\nwc -l log.jsonl\n\\`\\`\\`\n\n## Tools\n- bash: Run shell commands (primary tool). Install packages as needed.\n- read: Read files\n- write: Create/overwrite files \n- edit: Surgical file edits\n- attach: Share files to Slack\n\nEach tool requires a \"label\" parameter (shown to user).\n`;\n}\n\nfunction truncate(text: string, maxLen: number): string {\n\tif (text.length <= maxLen) return text;\n\treturn text.substring(0, maxLen - 3) + \"...\";\n}\n\nfunction extractToolResultText(result: unknown): string {\n\t// If it's already a string, return it\n\tif (typeof result === \"string\") {\n\t\treturn result;\n\t}\n\n\t// If it's an object with content array (tool result format)\n\tif (\n\t\tresult &&\n\t\ttypeof result === \"object\" &&\n\t\t\"content\" in result &&\n\t\tArray.isArray((result as { content: unknown }).content)\n\t) {\n\t\tconst content = (result as { content: Array<{ type: string; text?: string }> }).content;\n\t\tconst textParts: string[] = [];\n\t\tfor (const part of content) {\n\t\t\tif (part.type === \"text\" && part.text) {\n\t\t\t\ttextParts.push(part.text);\n\t\t\t}\n\t\t}\n\t\tif (textParts.length > 0) {\n\t\t\treturn textParts.join(\"\\n\");\n\t\t}\n\t}\n\n\t// Fallback to JSON\n\treturn JSON.stringify(result);\n}\n\nfunction formatToolArgsForSlack(_toolName: string, 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\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\tlines.push(value);\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\nexport function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner {\n\tlet agent: Agent | null = null;\n\tconst executor = createExecutor(sandboxConfig);\n\n\treturn {\n\t\tasync run(ctx: SlackContext, channelDir: string, store: ChannelStore): Promise<{ stopReason: string }> {\n\t\t\t// Ensure channel directory exists\n\t\t\tawait mkdir(channelDir, { recursive: true });\n\n\t\t\tconst channelId = ctx.message.channel;\n\t\t\tconst workspacePath = executor.getWorkspacePath(channelDir.replace(`/${channelId}`, \"\"));\n\t\t\tconst recentMessages = getRecentMessages(channelDir, 50);\n\t\t\tconst memory = getMemory(channelDir);\n\t\t\tconst systemPrompt = buildSystemPrompt(\n\t\t\t\tworkspacePath,\n\t\t\t\tchannelId,\n\t\t\t\tmemory,\n\t\t\t\tsandboxConfig,\n\t\t\t\tctx.channels,\n\t\t\t\tctx.users,\n\t\t\t);\n\n\t\t\t// Debug: log context sizes\n\t\t\tlog.logInfo(\n\t\t\t\t`Context sizes - system: ${systemPrompt.length} chars, messages: ${recentMessages.length} chars, memory: ${memory.length} chars`,\n\t\t\t);\n\t\t\tlog.logInfo(`Channels: ${ctx.channels.length}, Users: ${ctx.users.length}`);\n\n\t\t\t// Set up file upload function for the attach tool\n\t\t\t// For Docker, we need to translate paths back to host\n\t\t\tsetUploadFunction(async (filePath: string, title?: string) => {\n\t\t\t\tconst hostPath = translateToHostPath(filePath, channelDir, workspacePath, channelId);\n\t\t\t\tawait ctx.uploadFile(hostPath, title);\n\t\t\t});\n\n\t\t\t// Create tools with executor\n\t\t\tconst tools = createMomTools(executor);\n\n\t\t\t// Create ephemeral agent\n\t\t\tagent = new Agent({\n\t\t\t\tinitialState: {\n\t\t\t\t\tsystemPrompt,\n\t\t\t\t\tmodel,\n\t\t\t\t\tthinkingLevel: \"off\",\n\t\t\t\t\ttools,\n\t\t\t\t},\n\t\t\t\ttransport: new ProviderTransport({\n\t\t\t\t\tgetApiKey: async () => getAnthropicApiKey(),\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\t// Create logging context\n\t\t\tconst logCtx = {\n\t\t\t\tchannelId: ctx.message.channel,\n\t\t\t\tuserName: ctx.message.userName,\n\t\t\t\tchannelName: ctx.channelName,\n\t\t\t};\n\n\t\t\t// Track pending tool calls to pair args with results and timing\n\t\t\tconst pendingTools = new Map<string, { toolName: string; args: unknown; startTime: number }>();\n\n\t\t\t// Track usage across all assistant messages in this run\n\t\t\tconst totalUsage = {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\tcost: {\n\t\t\t\t\tinput: 0,\n\t\t\t\t\toutput: 0,\n\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\ttotal: 0,\n\t\t\t\t},\n\t\t\t};\n\n\t\t\t// Track stop reason\n\t\t\tlet stopReason = \"stop\";\n\n\t\t\t// Slack message limit is 40,000 characters - split into multiple messages if needed\n\t\t\tconst SLACK_MAX_LENGTH = 40000;\n\t\t\tconst splitForSlack = (text: string): string[] => {\n\t\t\t\tif (text.length <= SLACK_MAX_LENGTH) return [text];\n\t\t\t\tconst parts: string[] = [];\n\t\t\t\tlet remaining = text;\n\t\t\t\tlet partNum = 1;\n\t\t\t\twhile (remaining.length > 0) {\n\t\t\t\t\tconst chunk = remaining.substring(0, SLACK_MAX_LENGTH - 50);\n\t\t\t\t\tremaining = remaining.substring(SLACK_MAX_LENGTH - 50);\n\t\t\t\t\tconst suffix = remaining.length > 0 ? `\\n_(continued ${partNum}...)_` : \"\";\n\t\t\t\t\tparts.push(chunk + suffix);\n\t\t\t\t\tpartNum++;\n\t\t\t\t}\n\t\t\t\treturn parts;\n\t\t\t};\n\n\t\t\t// Promise queue to ensure ctx.respond/respondInThread calls execute in order\n\t\t\t// Handles errors gracefully by posting to thread instead of crashing\n\t\t\tconst queue = {\n\t\t\t\tchain: Promise.resolve(),\n\t\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void {\n\t\t\t\t\tthis.chain = this.chain.then(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait fn();\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(`Slack API error (${errorContext})`, errMsg);\n\t\t\t\t\t\t\t// Try to post error to thread, but don't crash if that fails too\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait ctx.respondInThread(`_Error: ${errMsg}_`);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// Ignore - we tried our best\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\t// Enqueue a message that may need splitting\n\t\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, log = true): void {\n\t\t\t\t\tconst parts = splitForSlack(text);\n\t\t\t\t\tfor (const part of parts) {\n\t\t\t\t\t\tthis.enqueue(\n\t\t\t\t\t\t\t() => (target === \"main\" ? ctx.respond(part, log) : ctx.respondInThread(part)),\n\t\t\t\t\t\t\terrorContext,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tflush(): Promise<void> {\n\t\t\t\t\treturn this.chain;\n\t\t\t\t},\n\t\t\t};\n\n\t\t\t// Subscribe to events\n\t\t\tagent.subscribe(async (event: AgentEvent) => {\n\t\t\t\tswitch (event.type) {\n\t\t\t\t\tcase \"tool_execution_start\": {\n\t\t\t\t\t\tconst args = event.args as { label?: string };\n\t\t\t\t\t\tconst label = args.label || event.toolName;\n\n\t\t\t\t\t\t// Store args to pair with result later\n\t\t\t\t\t\tpendingTools.set(event.toolCallId, {\n\t\t\t\t\t\t\ttoolName: event.toolName,\n\t\t\t\t\t\t\targs: event.args,\n\t\t\t\t\t\t\tstartTime: Date.now(),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Log to console\n\t\t\t\t\t\tlog.logToolStart(logCtx, event.toolName, label, event.args as Record<string, unknown>);\n\n\t\t\t\t\t\t// Log to jsonl\n\t\t\t\t\t\tawait store.logMessage(ctx.message.channel, {\n\t\t\t\t\t\t\tdate: new Date().toISOString(),\n\t\t\t\t\t\t\tts: toSlackTs(),\n\t\t\t\t\t\t\tuser: \"bot\",\n\t\t\t\t\t\t\ttext: `[Tool] ${event.toolName}: ${JSON.stringify(event.args)}`,\n\t\t\t\t\t\t\tattachments: [],\n\t\t\t\t\t\t\tisBot: true,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Show label in main message only\n\t\t\t\t\t\tqueue.enqueue(() => ctx.respond(`_→ ${label}_`, false), \"tool label\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase \"tool_execution_end\": {\n\t\t\t\t\t\tconst resultStr = extractToolResultText(event.result);\n\t\t\t\t\t\tconst pending = pendingTools.get(event.toolCallId);\n\t\t\t\t\t\tpendingTools.delete(event.toolCallId);\n\n\t\t\t\t\t\tconst durationMs = pending ? Date.now() - pending.startTime : 0;\n\n\t\t\t\t\t\t// Log to console\n\t\t\t\t\t\tif (event.isError) {\n\t\t\t\t\t\t\tlog.logToolError(logCtx, event.toolName, durationMs, resultStr);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlog.logToolSuccess(logCtx, event.toolName, durationMs, resultStr);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Log to jsonl\n\t\t\t\t\t\tawait store.logMessage(ctx.message.channel, {\n\t\t\t\t\t\t\tdate: new Date().toISOString(),\n\t\t\t\t\t\t\tts: toSlackTs(),\n\t\t\t\t\t\t\tuser: \"bot\",\n\t\t\t\t\t\t\ttext: `[Tool Result] ${event.toolName}: ${event.isError ? \"ERROR: \" : \"\"}${truncate(resultStr, 1000)}`,\n\t\t\t\t\t\t\tattachments: [],\n\t\t\t\t\t\t\tisBot: true,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Post args + result together in thread\n\t\t\t\t\t\tconst label = pending?.args ? (pending.args as { label?: string }).label : undefined;\n\t\t\t\t\t\tconst argsFormatted = pending\n\t\t\t\t\t\t\t? formatToolArgsForSlack(event.toolName, pending.args as Record<string, unknown>)\n\t\t\t\t\t\t\t: \"(args not found)\";\n\t\t\t\t\t\tconst duration = (durationMs / 1000).toFixed(1);\n\t\t\t\t\t\tlet threadMessage = `*${event.isError ? \"✗\" : \"✓\"} ${event.toolName}*`;\n\t\t\t\t\t\tif (label) {\n\t\t\t\t\t\t\tthreadMessage += `: ${label}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthreadMessage += ` (${duration}s)\\n`;\n\n\t\t\t\t\t\tif (argsFormatted) {\n\t\t\t\t\t\t\tthreadMessage += \"```\\n\" + argsFormatted + \"\\n```\\n\";\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthreadMessage += \"*Result:*\\n```\\n\" + resultStr + \"\\n```\";\n\n\t\t\t\t\t\tqueue.enqueueMessage(threadMessage, \"thread\", \"tool result thread\", false);\n\n\t\t\t\t\t\t// Show brief error in main message if failed\n\t\t\t\t\t\tif (event.isError) {\n\t\t\t\t\t\t\tqueue.enqueue(() => ctx.respond(`_Error: ${truncate(resultStr, 200)}_`, false), \"tool error\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase \"message_update\": {\n\t\t\t\t\t\t// No longer stream to console - just track that we're streaming\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase \"message_start\":\n\t\t\t\t\t\tif (event.message.role === \"assistant\") {\n\t\t\t\t\t\t\tlog.logResponseStart(logCtx);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase \"message_end\":\n\t\t\t\t\t\tif (event.message.role === \"assistant\") {\n\t\t\t\t\t\t\tconst assistantMsg = event.message as any; // AssistantMessage type\n\n\t\t\t\t\t\t\t// Track stop reason\n\t\t\t\t\t\t\tif (assistantMsg.stopReason) {\n\t\t\t\t\t\t\t\tstopReason = assistantMsg.stopReason;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Accumulate usage\n\t\t\t\t\t\t\tif (assistantMsg.usage) {\n\t\t\t\t\t\t\t\ttotalUsage.input += assistantMsg.usage.input;\n\t\t\t\t\t\t\t\ttotalUsage.output += assistantMsg.usage.output;\n\t\t\t\t\t\t\t\ttotalUsage.cacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\t\t\t\t\ttotalUsage.cacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\t\t\t\t\ttotalUsage.cost.input += assistantMsg.usage.cost.input;\n\t\t\t\t\t\t\t\ttotalUsage.cost.output += assistantMsg.usage.cost.output;\n\t\t\t\t\t\t\t\ttotalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;\n\t\t\t\t\t\t\t\ttotalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;\n\t\t\t\t\t\t\t\ttotalUsage.cost.total += assistantMsg.usage.cost.total;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Extract thinking and text from assistant message\n\t\t\t\t\t\t\tconst content = event.message.content;\n\t\t\t\t\t\t\tconst thinkingParts: string[] = [];\n\t\t\t\t\t\t\tconst textParts: string[] = [];\n\t\t\t\t\t\t\tfor (const part of content) {\n\t\t\t\t\t\t\t\tif (part.type === \"thinking\") {\n\t\t\t\t\t\t\t\t\tthinkingParts.push(part.thinking);\n\t\t\t\t\t\t\t\t} else if (part.type === \"text\") {\n\t\t\t\t\t\t\t\t\ttextParts.push(part.text);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst text = textParts.join(\"\\n\");\n\n\t\t\t\t\t\t\t// Post thinking to main message and thread\n\t\t\t\t\t\t\tfor (const thinking of thinkingParts) {\n\t\t\t\t\t\t\t\tlog.logThinking(logCtx, thinking);\n\t\t\t\t\t\t\t\tqueue.enqueueMessage(`_${thinking}_`, \"main\", \"thinking main\");\n\t\t\t\t\t\t\t\tqueue.enqueueMessage(`_${thinking}_`, \"thread\", \"thinking thread\", false);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Post text to main message and thread\n\t\t\t\t\t\t\tif (text.trim()) {\n\t\t\t\t\t\t\t\tlog.logResponse(logCtx, text);\n\t\t\t\t\t\t\t\tqueue.enqueueMessage(text, \"main\", \"response main\");\n\t\t\t\t\t\t\t\tqueue.enqueueMessage(text, \"thread\", \"response thread\", false);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Run the agent with user's message\n\t\t\t// Prepend recent messages to the user prompt (not system prompt) for better caching\n\t\t\t// The current message is already the last entry in recentMessages\n\t\t\tconst userPrompt =\n\t\t\t\t`Conversation history (last 50 turns). Respond to the last message.\\n` +\n\t\t\t\t`Format: date TAB user TAB text TAB attachments\\n\\n` +\n\t\t\t\trecentMessages;\n\t\t\t// Debug: write full context to file\n\t\t\tconst toolDefs = tools.map((t) => ({ name: t.name, description: t.description, parameters: t.parameters }));\n\t\t\tconst debugPrompt =\n\t\t\t\t`=== SYSTEM PROMPT (${systemPrompt.length} chars) ===\\n\\n${systemPrompt}\\n\\n` +\n\t\t\t\t`=== TOOL DEFINITIONS (${JSON.stringify(toolDefs).length} chars) ===\\n\\n${JSON.stringify(toolDefs, null, 2)}\\n\\n` +\n\t\t\t\t`=== USER PROMPT (${userPrompt.length} chars) ===\\n\\n${userPrompt}`;\n\t\t\tawait writeFile(join(channelDir, \"last_prompt.txt\"), debugPrompt, \"utf-8\");\n\n\t\t\tawait agent.prompt(userPrompt);\n\n\t\t\t// Wait for all queued respond calls to complete\n\t\t\tawait queue.flush();\n\n\t\t\t// Get final assistant message text from agent state and replace main message\n\t\t\tconst messages = agent.state.messages;\n\t\t\tconst lastAssistant = messages.filter((m) => m.role === \"assistant\").pop();\n\t\t\tconst finalText =\n\t\t\t\tlastAssistant?.content\n\t\t\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t\t\t.map((c) => c.text)\n\t\t\t\t\t.join(\"\\n\") || \"\";\n\t\t\tif (finalText.trim()) {\n\t\t\t\ttry {\n\t\t\t\t\t// For the main message, truncate if too long (full text is in thread)\n\t\t\t\t\tconst mainText =\n\t\t\t\t\t\tfinalText.length > SLACK_MAX_LENGTH\n\t\t\t\t\t\t\t? finalText.substring(0, SLACK_MAX_LENGTH - 50) + \"\\n\\n_(see thread for full response)_\"\n\t\t\t\t\t\t\t: finalText;\n\t\t\t\t\tawait ctx.replaceMessage(mainText);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tlog.logWarning(\"Failed to replace message with final text\", errMsg);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Log usage summary if there was any usage\n\t\t\tif (totalUsage.cost.total > 0) {\n\t\t\t\tconst summary = log.logUsageSummary(logCtx, totalUsage);\n\t\t\t\tqueue.enqueue(() => ctx.respondInThread(summary), \"usage summary\");\n\t\t\t\tawait queue.flush();\n\t\t\t}\n\n\t\t\treturn { stopReason };\n\t\t},\n\n\t\tabort(): void {\n\t\t\tagent?.abort();\n\t\t},\n\t};\n}\n\n/**\n * Translate container path back to host path for file operations\n */\nfunction translateToHostPath(\n\tcontainerPath: string,\n\tchannelDir: string,\n\tworkspacePath: string,\n\tchannelId: string,\n): string {\n\tif (workspacePath === \"/workspace\") {\n\t\t// Docker mode - translate /workspace/channelId/... to host path\n\t\tconst prefix = `/workspace/${channelId}/`;\n\t\tif (containerPath.startsWith(prefix)) {\n\t\t\treturn join(channelDir, containerPath.slice(prefix.length));\n\t\t}\n\t\t// Maybe it's just /workspace/...\n\t\tif (containerPath.startsWith(\"/workspace/\")) {\n\t\t\treturn join(channelDir, \"..\", containerPath.slice(\"/workspace/\".length));\n\t\t}\n\t}\n\t// Host mode or already a host path\n\treturn containerPath;\n}\n"]}
package/dist/log.d.ts CHANGED
@@ -14,6 +14,7 @@ export declare function logDownloadStart(ctx: LogContext, filename: string, loca
14
14
  export declare function logDownloadSuccess(ctx: LogContext, sizeKB: number): void;
15
15
  export declare function logDownloadError(ctx: LogContext, filename: string, error: string): void;
16
16
  export declare function logStopRequest(ctx: LogContext): void;
17
+ export declare function logInfo(message: string): void;
17
18
  export declare function logWarning(message: string, details?: string): void;
18
19
  export declare function logAgentError(ctx: LogContext | "system", error: string): void;
19
20
  export declare function logUsageSummary(ctx: LogContext, usage: {
package/dist/log.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAiED,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAElE;AAGD,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAWlH;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAY1G;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAUvG;AAGD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAEtD;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAQnE;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAQ/D;AAGD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAG3F;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAExE;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAGvF;AAGD,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAGpD;AAGD,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CASlE;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAQ7E;AAGD,wBAAgB,eAAe,CAC9B,GAAG,EAAE,UAAU,EACf,KAAK,EAAE;IACN,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9F,GACC,MAAM,CA8BR;AAGD,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAIpE;AAED,wBAAgB,YAAY,IAAI,IAAI,CAGnC;AAED,wBAAgB,eAAe,IAAI,IAAI,CAEtC","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 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): string {\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\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"]}
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAiED,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAElE;AAGD,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAWlH;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAY1G;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAUvG;AAGD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAEtD;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAQnE;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAQ/D;AAGD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAG3F;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAExE;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAGvF;AAGD,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAGpD;AAGD,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CASlE;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAQ7E;AAGD,wBAAgB,eAAe,CAC9B,GAAG,EAAE,UAAU,EACf,KAAK,EAAE;IACN,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9F,GACC,MAAM,CA8BR;AAGD,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAIpE;AAED,wBAAgB,YAAY,IAAI,IAAI,CAGnC;AAED,wBAAgB,eAAe,IAAI,IAAI,CAEtC","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): string {\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\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"]}
package/dist/log.js CHANGED
@@ -137,6 +137,9 @@ export function logStopRequest(ctx) {
137
137
  console.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} ⊗ Stop requested - aborting`));
138
138
  }
139
139
  // System
140
+ export function logInfo(message) {
141
+ console.log(chalk.blue(`${timestamp()} [system] ${message}`));
142
+ }
140
143
  export function logWarning(message, details) {
141
144
  console.log(chalk.yellow(`${timestamp()} [system] ⚠ ${message}`));
142
145
  if (details) {
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 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): string {\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\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"]}
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): string {\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\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"]}
package/dist/slack.d.ts CHANGED
@@ -12,8 +12,12 @@ export interface SlackContext {
12
12
  message: SlackMessage;
13
13
  channelName?: string;
14
14
  store: ChannelStore;
15
- /** Send/update the main message (accumulates text) */
16
- respond(text: string): Promise<void>;
15
+ /** All channels the bot is a member of */
16
+ channels: ChannelInfo[];
17
+ /** All known users in the workspace */
18
+ users: UserInfo[];
19
+ /** Send/update the main message (accumulates text). Set log=false to skip logging. */
20
+ respond(text: string, log?: boolean): Promise<void>;
17
21
  /** Replace the entire message text (not append) */
18
22
  replaceMessage(text: string): Promise<void>;
19
23
  /** Post a message in the thread under the main message (for verbose details) */
@@ -34,6 +38,15 @@ export interface MomBotConfig {
34
38
  botToken: string;
35
39
  workingDir: string;
36
40
  }
41
+ export interface ChannelInfo {
42
+ id: string;
43
+ name: string;
44
+ }
45
+ export interface UserInfo {
46
+ id: string;
47
+ userName: string;
48
+ displayName: string;
49
+ }
37
50
  export declare class MomBot {
38
51
  private socketClient;
39
52
  private webClient;
@@ -41,7 +54,23 @@ export declare class MomBot {
41
54
  private botUserId;
42
55
  readonly store: ChannelStore;
43
56
  private userCache;
57
+ private channelCache;
44
58
  constructor(handler: MomHandler, config: MomBotConfig);
59
+ private fetchChannels;
60
+ private fetchUsers;
61
+ /**
62
+ * Get all known channels (id -> name)
63
+ */
64
+ getChannels(): ChannelInfo[];
65
+ /**
66
+ * Get all known users
67
+ */
68
+ getUsers(): UserInfo[];
69
+ /**
70
+ * Obfuscate usernames and user IDs in text to prevent pinging people
71
+ * e.g., "nate" -> "n_a_t_e", "@mario" -> "@m_a_r_i_o", "<@U123>" -> "<@U_1_2_3>"
72
+ */
73
+ private obfuscateUsernames;
45
74
  private getUserInfo;
46
75
  private setupEventHandlers;
47
76
  private logMessage;
@@ -1 +1 @@
1
- {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../src/slack.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,UAAU,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,YAAY,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,sDAAsD;IACtD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,mDAAmD;IACnD,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,gFAAgF;IAChF,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,iCAAiC;IACjC,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,mCAAmC;IACnC,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,+DAA+D;IAC/D,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,UAAU;IAC1B,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,eAAe,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,MAAM;IAClB,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,SAAS,CAAuB;IACxC,SAAgB,KAAK,EAAE,YAAY,CAAC;IACpC,OAAO,CAAC,SAAS,CAAqE;IAEtF,YAAY,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAUpD;YAEa,WAAW;IAmBzB,OAAO,CAAC,kBAAkB;YAqEZ,UAAU;YAsBV,aAAa;IAsKrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAK3B;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1B;CACD","sourcesContent":["import { SocketModeClient } from \"@slack/socket-mode\";\nimport { WebClient } from \"@slack/web-api\";\nimport { readFileSync } from \"fs\";\nimport { basename } from \"path\";\nimport * as log from \"./log.js\";\nimport { type Attachment, ChannelStore } from \"./store.js\";\n\nexport interface SlackMessage {\n\ttext: string; // message content (mentions stripped)\n\trawText: string; // original text with mentions\n\tuser: string; // user ID\n\tuserName?: string; // user handle\n\tchannel: string; // channel ID\n\tts: string; // timestamp (for threading)\n\tattachments: Attachment[]; // file attachments\n}\n\nexport interface SlackContext {\n\tmessage: SlackMessage;\n\tchannelName?: string; // channel name for logging (e.g., #dev-team)\n\tstore: ChannelStore;\n\t/** Send/update the main message (accumulates text) */\n\trespond(text: string): Promise<void>;\n\t/** Replace the entire message text (not append) */\n\treplaceMessage(text: string): Promise<void>;\n\t/** Post a message in the thread under the main message (for verbose details) */\n\trespondInThread(text: string): Promise<void>;\n\t/** Show/hide typing indicator */\n\tsetTyping(isTyping: boolean): Promise<void>;\n\t/** Upload a file to the channel */\n\tuploadFile(filePath: string, title?: string): Promise<void>;\n\t/** Set working state (adds/removes working indicator emoji) */\n\tsetWorking(working: boolean): Promise<void>;\n}\n\nexport interface MomHandler {\n\tonChannelMention(ctx: SlackContext): Promise<void>;\n\tonDirectMessage(ctx: SlackContext): Promise<void>;\n}\n\nexport interface MomBotConfig {\n\tappToken: string;\n\tbotToken: string;\n\tworkingDir: string; // directory for channel data and attachments\n}\n\nexport class MomBot {\n\tprivate socketClient: SocketModeClient;\n\tprivate webClient: WebClient;\n\tprivate handler: MomHandler;\n\tprivate botUserId: string | null = null;\n\tpublic readonly store: ChannelStore;\n\tprivate userCache: Map<string, { userName: string; displayName: string }> = new Map();\n\n\tconstructor(handler: MomHandler, config: MomBotConfig) {\n\t\tthis.handler = handler;\n\t\tthis.socketClient = new SocketModeClient({ appToken: config.appToken });\n\t\tthis.webClient = new WebClient(config.botToken);\n\t\tthis.store = new ChannelStore({\n\t\t\tworkingDir: config.workingDir,\n\t\t\tbotToken: config.botToken,\n\t\t});\n\n\t\tthis.setupEventHandlers();\n\t}\n\n\tprivate async getUserInfo(userId: string): Promise<{ userName: string; displayName: string }> {\n\t\tif (this.userCache.has(userId)) {\n\t\t\treturn this.userCache.get(userId)!;\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await this.webClient.users.info({ user: userId });\n\t\t\tconst user = result.user as { name?: string; real_name?: string };\n\t\t\tconst info = {\n\t\t\t\tuserName: user?.name || userId,\n\t\t\t\tdisplayName: user?.real_name || user?.name || userId,\n\t\t\t};\n\t\t\tthis.userCache.set(userId, info);\n\t\t\treturn info;\n\t\t} catch {\n\t\t\treturn { userName: userId, displayName: userId };\n\t\t}\n\t}\n\n\tprivate setupEventHandlers(): void {\n\t\t// Handle @mentions in channels\n\t\tthis.socketClient.on(\"app_mention\", async ({ event, ack }) => {\n\t\t\tawait ack();\n\n\t\t\tconst slackEvent = event as {\n\t\t\t\ttext: string;\n\t\t\t\tchannel: string;\n\t\t\t\tuser: string;\n\t\t\t\tts: string;\n\t\t\t\tfiles?: Array<{ name: string; url_private_download?: string; url_private?: string }>;\n\t\t\t};\n\n\t\t\t// Log the mention (message event may not fire for app_mention)\n\t\t\tawait this.logMessage(slackEvent);\n\n\t\t\tconst ctx = await this.createContext(slackEvent);\n\t\t\tawait this.handler.onChannelMention(ctx);\n\t\t});\n\n\t\t// Handle all messages (for logging) and DMs (for triggering handler)\n\t\tthis.socketClient.on(\"message\", async ({ event, ack }) => {\n\t\t\tawait ack();\n\n\t\t\tconst slackEvent = event as {\n\t\t\t\ttext?: string;\n\t\t\t\tchannel: string;\n\t\t\t\tuser?: string;\n\t\t\t\tts: string;\n\t\t\t\tchannel_type?: string;\n\t\t\t\tsubtype?: string;\n\t\t\t\tbot_id?: string;\n\t\t\t\tfiles?: Array<{ name: string; url_private_download?: string; url_private?: string }>;\n\t\t\t};\n\n\t\t\t// Ignore bot messages\n\t\t\tif (slackEvent.bot_id) return;\n\t\t\t// Ignore message edits, etc. (but allow file_share)\n\t\t\tif (slackEvent.subtype !== undefined && slackEvent.subtype !== \"file_share\") return;\n\t\t\t// Ignore if no user\n\t\t\tif (!slackEvent.user) return;\n\t\t\t// Ignore messages from the bot itself\n\t\t\tif (slackEvent.user === this.botUserId) return;\n\t\t\t// Ignore if no text AND no files\n\t\t\tif (!slackEvent.text && (!slackEvent.files || slackEvent.files.length === 0)) return;\n\n\t\t\t// Log ALL messages (channel and DM)\n\t\t\tawait this.logMessage({\n\t\t\t\ttext: slackEvent.text || \"\",\n\t\t\t\tchannel: slackEvent.channel,\n\t\t\t\tuser: slackEvent.user,\n\t\t\t\tts: slackEvent.ts,\n\t\t\t\tfiles: slackEvent.files,\n\t\t\t});\n\n\t\t\t// Only trigger handler for DMs (channel mentions are handled by app_mention event)\n\t\t\tif (slackEvent.channel_type === \"im\") {\n\t\t\t\tconst ctx = await this.createContext({\n\t\t\t\t\ttext: slackEvent.text || \"\",\n\t\t\t\t\tchannel: slackEvent.channel,\n\t\t\t\t\tuser: slackEvent.user,\n\t\t\t\t\tts: slackEvent.ts,\n\t\t\t\t\tfiles: slackEvent.files,\n\t\t\t\t});\n\t\t\t\tawait this.handler.onDirectMessage(ctx);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate async logMessage(event: {\n\t\ttext: string;\n\t\tchannel: string;\n\t\tuser: string;\n\t\tts: string;\n\t\tfiles?: Array<{ name: string; url_private_download?: string; url_private?: string }>;\n\t}): Promise<void> {\n\t\tconst attachments = event.files ? this.store.processAttachments(event.channel, event.files, event.ts) : [];\n\t\tconst { userName, displayName } = await this.getUserInfo(event.user);\n\n\t\tawait this.store.logMessage(event.channel, {\n\t\t\tdate: new Date(parseFloat(event.ts) * 1000).toISOString(),\n\t\t\tts: event.ts,\n\t\t\tuser: event.user,\n\t\t\tuserName,\n\t\t\tdisplayName,\n\t\t\ttext: event.text,\n\t\t\tattachments,\n\t\t\tisBot: false,\n\t\t});\n\t}\n\n\tprivate async createContext(event: {\n\t\ttext: string;\n\t\tchannel: string;\n\t\tuser: string;\n\t\tts: string;\n\t\tfiles?: Array<{ name: string; url_private_download?: string; url_private?: string }>;\n\t}): Promise<SlackContext> {\n\t\tconst rawText = event.text;\n\t\tconst text = rawText.replace(/<@[A-Z0-9]+>/gi, \"\").trim();\n\n\t\t// Get user info for logging\n\t\tconst { userName } = await this.getUserInfo(event.user);\n\n\t\t// Get channel name for logging (best effort)\n\t\tlet channelName: string | undefined;\n\t\ttry {\n\t\t\tif (event.channel.startsWith(\"C\")) {\n\t\t\t\tconst result = await this.webClient.conversations.info({ channel: event.channel });\n\t\t\t\tchannelName = result.channel?.name ? `#${result.channel.name}` : undefined;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore errors - we'll just use the channel ID\n\t\t}\n\n\t\t// Process attachments (for context, already logged by message handler)\n\t\tconst attachments = event.files ? this.store.processAttachments(event.channel, event.files, event.ts) : [];\n\n\t\t// Track the single message for this run\n\t\tlet messageTs: string | null = null;\n\t\tlet accumulatedText = \"\";\n\t\tlet isThinking = true; // Track if we're still in \"thinking\" state\n\t\tlet isWorking = true; // Track if still processing\n\t\tconst workingIndicator = \" ...\";\n\t\tlet updatePromise: Promise<void> = Promise.resolve();\n\n\t\treturn {\n\t\t\tmessage: {\n\t\t\t\ttext,\n\t\t\t\trawText,\n\t\t\t\tuser: event.user,\n\t\t\t\tuserName,\n\t\t\t\tchannel: event.channel,\n\t\t\t\tts: event.ts,\n\t\t\t\tattachments,\n\t\t\t},\n\t\t\tchannelName,\n\t\t\tstore: this.store,\n\t\t\trespond: async (responseText: string) => {\n\t\t\t\t// Queue updates to avoid race conditions\n\t\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\t\tif (isThinking) {\n\t\t\t\t\t\t// First real response replaces \"Thinking...\"\n\t\t\t\t\t\taccumulatedText = responseText;\n\t\t\t\t\t\tisThinking = false;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Subsequent responses get appended\n\t\t\t\t\t\taccumulatedText += \"\\n\" + responseText;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Add working indicator if still working\n\t\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\n\t\t\t\t\tif (messageTs) {\n\t\t\t\t\t\t// Update existing message\n\t\t\t\t\t\tawait this.webClient.chat.update({\n\t\t\t\t\t\t\tchannel: event.channel,\n\t\t\t\t\t\t\tts: messageTs,\n\t\t\t\t\t\t\ttext: displayText,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Post initial message\n\t\t\t\t\t\tconst result = await this.webClient.chat.postMessage({\n\t\t\t\t\t\t\tchannel: event.channel,\n\t\t\t\t\t\t\ttext: displayText,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tmessageTs = result.ts as string;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Log the response\n\t\t\t\t\tawait this.store.logBotResponse(event.channel, responseText, messageTs!);\n\t\t\t\t});\n\n\t\t\t\tawait updatePromise;\n\t\t\t},\n\t\t\trespondInThread: async (threadText: string) => {\n\t\t\t\t// Queue thread posts to maintain order\n\t\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\t\tif (!messageTs) {\n\t\t\t\t\t\t// No main message yet, just skip\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\t// Post in thread under the main message\n\t\t\t\t\tawait this.webClient.chat.postMessage({\n\t\t\t\t\t\tchannel: event.channel,\n\t\t\t\t\t\tthread_ts: messageTs,\n\t\t\t\t\t\ttext: threadText,\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tawait updatePromise;\n\t\t\t},\n\t\t\tsetTyping: async (isTyping: boolean) => {\n\t\t\t\tif (isTyping && !messageTs) {\n\t\t\t\t\t// Post initial \"thinking\" message (... auto-appended by working indicator)\n\t\t\t\t\taccumulatedText = \"_Thinking_\";\n\t\t\t\t\tconst result = await this.webClient.chat.postMessage({\n\t\t\t\t\t\tchannel: event.channel,\n\t\t\t\t\t\ttext: accumulatedText,\n\t\t\t\t\t});\n\t\t\t\t\tmessageTs = result.ts as string;\n\t\t\t\t}\n\t\t\t\t// We don't delete/clear anymore - message persists and gets updated\n\t\t\t},\n\t\t\tuploadFile: async (filePath: string, title?: string) => {\n\t\t\t\tconst fileName = title || basename(filePath);\n\t\t\t\tconst fileContent = readFileSync(filePath);\n\n\t\t\t\tawait this.webClient.files.uploadV2({\n\t\t\t\t\tchannel_id: event.channel,\n\t\t\t\t\tfile: fileContent,\n\t\t\t\t\tfilename: fileName,\n\t\t\t\t\ttitle: fileName,\n\t\t\t\t});\n\t\t\t},\n\t\t\treplaceMessage: async (text: string) => {\n\t\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\t\t// Replace the accumulated text entirely\n\t\t\t\t\taccumulatedText = text;\n\n\t\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\n\t\t\t\t\tif (messageTs) {\n\t\t\t\t\t\tawait this.webClient.chat.update({\n\t\t\t\t\t\t\tchannel: event.channel,\n\t\t\t\t\t\t\tts: messageTs,\n\t\t\t\t\t\t\ttext: displayText,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Post initial message\n\t\t\t\t\t\tconst result = await this.webClient.chat.postMessage({\n\t\t\t\t\t\t\tchannel: event.channel,\n\t\t\t\t\t\t\ttext: displayText,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tmessageTs = result.ts as string;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tawait updatePromise;\n\t\t\t},\n\t\t\tsetWorking: async (working: boolean) => {\n\t\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\t\tisWorking = working;\n\n\t\t\t\t\t// If we have a message, update it to add/remove indicator\n\t\t\t\t\tif (messageTs) {\n\t\t\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\t\t\t\t\t\tawait this.webClient.chat.update({\n\t\t\t\t\t\t\tchannel: event.channel,\n\t\t\t\t\t\t\tts: messageTs,\n\t\t\t\t\t\t\ttext: displayText,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tawait updatePromise;\n\t\t\t},\n\t\t};\n\t}\n\n\tasync start(): Promise<void> {\n\t\tconst auth = await this.webClient.auth.test();\n\t\tthis.botUserId = auth.user_id as string;\n\t\tawait this.socketClient.start();\n\t\tlog.logConnected();\n\t}\n\n\tasync stop(): Promise<void> {\n\t\tawait this.socketClient.disconnect();\n\t\tlog.logDisconnected();\n\t}\n}\n"]}
1
+ {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../src/slack.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,UAAU,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,YAAY,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,0CAA0C;IAC1C,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,uCAAuC;IACvC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,sFAAsF;IACtF,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,mDAAmD;IACnD,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,gFAAgF;IAChF,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,iCAAiC;IACjC,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,mCAAmC;IACnC,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,+DAA+D;IAC/D,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,UAAU;IAC1B,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,eAAe,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,MAAM;IAClB,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,SAAS,CAAuB;IACxC,SAAgB,KAAK,EAAE,YAAY,CAAC;IACpC,OAAO,CAAC,SAAS,CAAqE;IACtF,OAAO,CAAC,YAAY,CAAkC;IAEtD,YAAY,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAUpD;YAKa,aAAa;YA8Bb,UAAU;IA8BxB;;OAEG;IACH,WAAW,IAAI,WAAW,EAAE,CAE3B;IAED;;OAEG;IACH,QAAQ,IAAI,QAAQ,EAAE,CAMrB;IAED;;;OAGG;IACH,OAAO,CAAC,kBAAkB;YAsBZ,WAAW;IAmBzB,OAAO,CAAC,kBAAkB;YAmEZ,UAAU;YAsBV,aAAa;IA4KrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAU3B;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1B;CACD","sourcesContent":["import { SocketModeClient } from \"@slack/socket-mode\";\nimport { WebClient } from \"@slack/web-api\";\nimport { readFileSync } from \"fs\";\nimport { basename } from \"path\";\nimport * as log from \"./log.js\";\nimport { type Attachment, ChannelStore } from \"./store.js\";\n\nexport interface SlackMessage {\n\ttext: string; // message content (mentions stripped)\n\trawText: string; // original text with mentions\n\tuser: string; // user ID\n\tuserName?: string; // user handle\n\tchannel: string; // channel ID\n\tts: string; // timestamp (for threading)\n\tattachments: Attachment[]; // file attachments\n}\n\nexport interface SlackContext {\n\tmessage: SlackMessage;\n\tchannelName?: string; // channel name for logging (e.g., #dev-team)\n\tstore: ChannelStore;\n\t/** All channels the bot is a member of */\n\tchannels: ChannelInfo[];\n\t/** All known users in the workspace */\n\tusers: UserInfo[];\n\t/** Send/update the main message (accumulates text). Set log=false to skip logging. */\n\trespond(text: string, log?: boolean): Promise<void>;\n\t/** Replace the entire message text (not append) */\n\treplaceMessage(text: string): Promise<void>;\n\t/** Post a message in the thread under the main message (for verbose details) */\n\trespondInThread(text: string): Promise<void>;\n\t/** Show/hide typing indicator */\n\tsetTyping(isTyping: boolean): Promise<void>;\n\t/** Upload a file to the channel */\n\tuploadFile(filePath: string, title?: string): Promise<void>;\n\t/** Set working state (adds/removes working indicator emoji) */\n\tsetWorking(working: boolean): Promise<void>;\n}\n\nexport interface MomHandler {\n\tonChannelMention(ctx: SlackContext): Promise<void>;\n\tonDirectMessage(ctx: SlackContext): Promise<void>;\n}\n\nexport interface MomBotConfig {\n\tappToken: string;\n\tbotToken: string;\n\tworkingDir: string; // directory for channel data and attachments\n}\n\nexport interface ChannelInfo {\n\tid: string;\n\tname: string;\n}\n\nexport interface UserInfo {\n\tid: string;\n\tuserName: string;\n\tdisplayName: string;\n}\n\nexport class MomBot {\n\tprivate socketClient: SocketModeClient;\n\tprivate webClient: WebClient;\n\tprivate handler: MomHandler;\n\tprivate botUserId: string | null = null;\n\tpublic readonly store: ChannelStore;\n\tprivate userCache: Map<string, { userName: string; displayName: string }> = new Map();\n\tprivate channelCache: Map<string, string> = new Map(); // id -> name\n\n\tconstructor(handler: MomHandler, config: MomBotConfig) {\n\t\tthis.handler = handler;\n\t\tthis.socketClient = new SocketModeClient({ appToken: config.appToken });\n\t\tthis.webClient = new WebClient(config.botToken);\n\t\tthis.store = new ChannelStore({\n\t\t\tworkingDir: config.workingDir,\n\t\t\tbotToken: config.botToken,\n\t\t});\n\n\t\tthis.setupEventHandlers();\n\t}\n\n\t/**\n\t * Fetch all channels the bot is a member of\n\t */\n\tprivate async fetchChannels(): Promise<void> {\n\t\ttry {\n\t\t\tlet cursor: string | undefined;\n\t\t\tdo {\n\t\t\t\tconst result = await this.webClient.conversations.list({\n\t\t\t\t\ttypes: \"public_channel,private_channel\",\n\t\t\t\t\texclude_archived: true,\n\t\t\t\t\tlimit: 200,\n\t\t\t\t\tcursor,\n\t\t\t\t});\n\n\t\t\t\tconst channels = result.channels as Array<{ id?: string; name?: string; is_member?: boolean }> | undefined;\n\t\t\t\tif (channels) {\n\t\t\t\t\tfor (const channel of channels) {\n\t\t\t\t\t\tif (channel.id && channel.name && channel.is_member) {\n\t\t\t\t\t\t\tthis.channelCache.set(channel.id, channel.name);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tcursor = result.response_metadata?.next_cursor;\n\t\t\t} while (cursor);\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to fetch channels\", String(error));\n\t\t}\n\t}\n\n\t/**\n\t * Fetch all workspace users\n\t */\n\tprivate async fetchUsers(): Promise<void> {\n\t\ttry {\n\t\t\tlet cursor: string | undefined;\n\t\t\tdo {\n\t\t\t\tconst result = await this.webClient.users.list({\n\t\t\t\t\tlimit: 200,\n\t\t\t\t\tcursor,\n\t\t\t\t});\n\n\t\t\t\tconst members = result.members as\n\t\t\t\t\t| Array<{ id?: string; name?: string; real_name?: string; deleted?: boolean }>\n\t\t\t\t\t| undefined;\n\t\t\t\tif (members) {\n\t\t\t\t\tfor (const user of members) {\n\t\t\t\t\t\tif (user.id && user.name && !user.deleted) {\n\t\t\t\t\t\t\tthis.userCache.set(user.id, {\n\t\t\t\t\t\t\t\tuserName: user.name,\n\t\t\t\t\t\t\t\tdisplayName: user.real_name || user.name,\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\n\t\t\t\tcursor = result.response_metadata?.next_cursor;\n\t\t\t} while (cursor);\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to fetch users\", String(error));\n\t\t}\n\t}\n\n\t/**\n\t * Get all known channels (id -> name)\n\t */\n\tgetChannels(): ChannelInfo[] {\n\t\treturn Array.from(this.channelCache.entries()).map(([id, name]) => ({ id, name }));\n\t}\n\n\t/**\n\t * Get all known users\n\t */\n\tgetUsers(): UserInfo[] {\n\t\treturn Array.from(this.userCache.entries()).map(([id, { userName, displayName }]) => ({\n\t\t\tid,\n\t\t\tuserName,\n\t\t\tdisplayName,\n\t\t}));\n\t}\n\n\t/**\n\t * Obfuscate usernames and user IDs in text to prevent pinging people\n\t * e.g., \"nate\" -> \"n_a_t_e\", \"@mario\" -> \"@m_a_r_i_o\", \"<@U123>\" -> \"<@U_1_2_3>\"\n\t */\n\tprivate obfuscateUsernames(text: string): string {\n\t\tlet result = text;\n\n\t\t// Obfuscate user IDs like <@U16LAL8LS>\n\t\tresult = result.replace(/<@([A-Z0-9]+)>/gi, (_match, id) => {\n\t\t\treturn `<@${id.split(\"\").join(\"_\")}>`;\n\t\t});\n\n\t\t// Obfuscate usernames\n\t\tfor (const { userName } of this.userCache.values()) {\n\t\t\t// Escape special regex characters in username\n\t\t\tconst escaped = userName.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\t\t\t// Match @username, <@username>, or bare username (case insensitive, word boundary)\n\t\t\tconst pattern = new RegExp(`(<@|@)?(\\\\b${escaped}\\\\b)`, \"gi\");\n\t\t\tresult = result.replace(pattern, (_match, prefix, name) => {\n\t\t\t\tconst obfuscated = name.split(\"\").join(\"_\");\n\t\t\t\treturn (prefix || \"\") + obfuscated;\n\t\t\t});\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate async getUserInfo(userId: string): Promise<{ userName: string; displayName: string }> {\n\t\tif (this.userCache.has(userId)) {\n\t\t\treturn this.userCache.get(userId)!;\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await this.webClient.users.info({ user: userId });\n\t\t\tconst user = result.user as { name?: string; real_name?: string };\n\t\t\tconst info = {\n\t\t\t\tuserName: user?.name || userId,\n\t\t\t\tdisplayName: user?.real_name || user?.name || userId,\n\t\t\t};\n\t\t\tthis.userCache.set(userId, info);\n\t\t\treturn info;\n\t\t} catch {\n\t\t\treturn { userName: userId, displayName: userId };\n\t\t}\n\t}\n\n\tprivate setupEventHandlers(): void {\n\t\t// Handle @mentions in channels\n\t\t// Note: We don't log here - the message event handler logs all messages\n\t\tthis.socketClient.on(\"app_mention\", async ({ event, ack }) => {\n\t\t\tawait ack();\n\n\t\t\tconst slackEvent = event as {\n\t\t\t\ttext: string;\n\t\t\t\tchannel: string;\n\t\t\t\tuser: string;\n\t\t\t\tts: string;\n\t\t\t\tfiles?: Array<{ name: string; url_private_download?: string; url_private?: string }>;\n\t\t\t};\n\n\t\t\tconst ctx = await this.createContext(slackEvent);\n\t\t\tawait this.handler.onChannelMention(ctx);\n\t\t});\n\n\t\t// Handle all messages (for logging) and DMs (for triggering handler)\n\t\tthis.socketClient.on(\"message\", async ({ event, ack }) => {\n\t\t\tawait ack();\n\n\t\t\tconst slackEvent = event as {\n\t\t\t\ttext?: string;\n\t\t\t\tchannel: string;\n\t\t\t\tuser?: string;\n\t\t\t\tts: string;\n\t\t\t\tchannel_type?: string;\n\t\t\t\tsubtype?: string;\n\t\t\t\tbot_id?: string;\n\t\t\t\tfiles?: Array<{ name: string; url_private_download?: string; url_private?: string }>;\n\t\t\t};\n\n\t\t\t// Ignore bot messages\n\t\t\tif (slackEvent.bot_id) return;\n\t\t\t// Ignore message edits, etc. (but allow file_share)\n\t\t\tif (slackEvent.subtype !== undefined && slackEvent.subtype !== \"file_share\") return;\n\t\t\t// Ignore if no user\n\t\t\tif (!slackEvent.user) return;\n\t\t\t// Ignore messages from the bot itself\n\t\t\tif (slackEvent.user === this.botUserId) return;\n\t\t\t// Ignore if no text AND no files\n\t\t\tif (!slackEvent.text && (!slackEvent.files || slackEvent.files.length === 0)) return;\n\n\t\t\t// Log ALL messages (channel and DM)\n\t\t\tawait this.logMessage({\n\t\t\t\ttext: slackEvent.text || \"\",\n\t\t\t\tchannel: slackEvent.channel,\n\t\t\t\tuser: slackEvent.user,\n\t\t\t\tts: slackEvent.ts,\n\t\t\t\tfiles: slackEvent.files,\n\t\t\t});\n\n\t\t\t// Only trigger handler for DMs (channel mentions are handled by app_mention event)\n\t\t\tif (slackEvent.channel_type === \"im\") {\n\t\t\t\tconst ctx = await this.createContext({\n\t\t\t\t\ttext: slackEvent.text || \"\",\n\t\t\t\t\tchannel: slackEvent.channel,\n\t\t\t\t\tuser: slackEvent.user,\n\t\t\t\t\tts: slackEvent.ts,\n\t\t\t\t\tfiles: slackEvent.files,\n\t\t\t\t});\n\t\t\t\tawait this.handler.onDirectMessage(ctx);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate async logMessage(event: {\n\t\ttext: string;\n\t\tchannel: string;\n\t\tuser: string;\n\t\tts: string;\n\t\tfiles?: Array<{ name: string; url_private_download?: string; url_private?: string }>;\n\t}): Promise<void> {\n\t\tconst attachments = event.files ? this.store.processAttachments(event.channel, event.files, event.ts) : [];\n\t\tconst { userName, displayName } = await this.getUserInfo(event.user);\n\n\t\tawait this.store.logMessage(event.channel, {\n\t\t\tdate: new Date(parseFloat(event.ts) * 1000).toISOString(),\n\t\t\tts: event.ts,\n\t\t\tuser: event.user,\n\t\t\tuserName,\n\t\t\tdisplayName,\n\t\t\ttext: event.text,\n\t\t\tattachments,\n\t\t\tisBot: false,\n\t\t});\n\t}\n\n\tprivate async createContext(event: {\n\t\ttext: string;\n\t\tchannel: string;\n\t\tuser: string;\n\t\tts: string;\n\t\tfiles?: Array<{ name: string; url_private_download?: string; url_private?: string }>;\n\t}): Promise<SlackContext> {\n\t\tconst rawText = event.text;\n\t\tconst text = rawText.replace(/<@[A-Z0-9]+>/gi, \"\").trim();\n\n\t\t// Get user info for logging\n\t\tconst { userName } = await this.getUserInfo(event.user);\n\n\t\t// Get channel name for logging (best effort)\n\t\tlet channelName: string | undefined;\n\t\ttry {\n\t\t\tif (event.channel.startsWith(\"C\")) {\n\t\t\t\tconst result = await this.webClient.conversations.info({ channel: event.channel });\n\t\t\t\tchannelName = result.channel?.name ? `#${result.channel.name}` : undefined;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore errors - we'll just use the channel ID\n\t\t}\n\n\t\t// Process attachments (for context, already logged by message handler)\n\t\tconst attachments = event.files ? this.store.processAttachments(event.channel, event.files, event.ts) : [];\n\n\t\t// Track the single message for this run\n\t\tlet messageTs: string | null = null;\n\t\tlet accumulatedText = \"\";\n\t\tlet isThinking = true; // Track if we're still in \"thinking\" state\n\t\tlet isWorking = true; // Track if still processing\n\t\tconst workingIndicator = \" ...\";\n\t\tlet updatePromise: Promise<void> = Promise.resolve();\n\n\t\treturn {\n\t\t\tmessage: {\n\t\t\t\ttext,\n\t\t\t\trawText,\n\t\t\t\tuser: event.user,\n\t\t\t\tuserName,\n\t\t\t\tchannel: event.channel,\n\t\t\t\tts: event.ts,\n\t\t\t\tattachments,\n\t\t\t},\n\t\t\tchannelName,\n\t\t\tstore: this.store,\n\t\t\tchannels: this.getChannels(),\n\t\t\tusers: this.getUsers(),\n\t\t\trespond: async (responseText: string, log = true) => {\n\t\t\t\t// Queue updates to avoid race conditions\n\t\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\t\tif (isThinking) {\n\t\t\t\t\t\t// First real response replaces \"Thinking...\"\n\t\t\t\t\t\taccumulatedText = responseText;\n\t\t\t\t\t\tisThinking = false;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Subsequent responses get appended\n\t\t\t\t\t\taccumulatedText += \"\\n\" + responseText;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Add working indicator if still working\n\t\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\n\t\t\t\t\tif (messageTs) {\n\t\t\t\t\t\t// Update existing message\n\t\t\t\t\t\tawait this.webClient.chat.update({\n\t\t\t\t\t\t\tchannel: event.channel,\n\t\t\t\t\t\t\tts: messageTs,\n\t\t\t\t\t\t\ttext: displayText,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Post initial message\n\t\t\t\t\t\tconst result = await this.webClient.chat.postMessage({\n\t\t\t\t\t\t\tchannel: event.channel,\n\t\t\t\t\t\t\ttext: displayText,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tmessageTs = result.ts as string;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Log the response if requested\n\t\t\t\t\tif (log) {\n\t\t\t\t\t\tawait this.store.logBotResponse(event.channel, responseText, messageTs!);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tawait updatePromise;\n\t\t\t},\n\t\t\trespondInThread: async (threadText: string) => {\n\t\t\t\t// Queue thread posts to maintain order\n\t\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\t\tif (!messageTs) {\n\t\t\t\t\t\t// No main message yet, just skip\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\t// Obfuscate usernames to avoid pinging people in thread details\n\t\t\t\t\tconst obfuscatedText = this.obfuscateUsernames(threadText);\n\t\t\t\t\t// Post in thread under the main message\n\t\t\t\t\tawait this.webClient.chat.postMessage({\n\t\t\t\t\t\tchannel: event.channel,\n\t\t\t\t\t\tthread_ts: messageTs,\n\t\t\t\t\t\ttext: obfuscatedText,\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tawait updatePromise;\n\t\t\t},\n\t\t\tsetTyping: async (isTyping: boolean) => {\n\t\t\t\tif (isTyping && !messageTs) {\n\t\t\t\t\t// Post initial \"thinking\" message (... auto-appended by working indicator)\n\t\t\t\t\taccumulatedText = \"_Thinking_\";\n\t\t\t\t\tconst result = await this.webClient.chat.postMessage({\n\t\t\t\t\t\tchannel: event.channel,\n\t\t\t\t\t\ttext: accumulatedText,\n\t\t\t\t\t});\n\t\t\t\t\tmessageTs = result.ts as string;\n\t\t\t\t}\n\t\t\t\t// We don't delete/clear anymore - message persists and gets updated\n\t\t\t},\n\t\t\tuploadFile: async (filePath: string, title?: string) => {\n\t\t\t\tconst fileName = title || basename(filePath);\n\t\t\t\tconst fileContent = readFileSync(filePath);\n\n\t\t\t\tawait this.webClient.files.uploadV2({\n\t\t\t\t\tchannel_id: event.channel,\n\t\t\t\t\tfile: fileContent,\n\t\t\t\t\tfilename: fileName,\n\t\t\t\t\ttitle: fileName,\n\t\t\t\t});\n\t\t\t},\n\t\t\treplaceMessage: async (text: string) => {\n\t\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\t\t// Replace the accumulated text entirely\n\t\t\t\t\taccumulatedText = text;\n\n\t\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\n\t\t\t\t\tif (messageTs) {\n\t\t\t\t\t\tawait this.webClient.chat.update({\n\t\t\t\t\t\t\tchannel: event.channel,\n\t\t\t\t\t\t\tts: messageTs,\n\t\t\t\t\t\t\ttext: displayText,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Post initial message\n\t\t\t\t\t\tconst result = await this.webClient.chat.postMessage({\n\t\t\t\t\t\t\tchannel: event.channel,\n\t\t\t\t\t\t\ttext: displayText,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tmessageTs = result.ts as string;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tawait updatePromise;\n\t\t\t},\n\t\t\tsetWorking: async (working: boolean) => {\n\t\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\t\tisWorking = working;\n\n\t\t\t\t\t// If we have a message, update it to add/remove indicator\n\t\t\t\t\tif (messageTs) {\n\t\t\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\t\t\t\t\t\tawait this.webClient.chat.update({\n\t\t\t\t\t\t\tchannel: event.channel,\n\t\t\t\t\t\t\tts: messageTs,\n\t\t\t\t\t\t\ttext: displayText,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tawait updatePromise;\n\t\t\t},\n\t\t};\n\t}\n\n\tasync start(): Promise<void> {\n\t\tconst auth = await this.webClient.auth.test();\n\t\tthis.botUserId = auth.user_id as string;\n\n\t\t// Fetch channels and users in parallel\n\t\tawait Promise.all([this.fetchChannels(), this.fetchUsers()]);\n\t\tlog.logInfo(`Loaded ${this.channelCache.size} channels, ${this.userCache.size} users`);\n\n\t\tawait this.socketClient.start();\n\t\tlog.logConnected();\n\t}\n\n\tasync stop(): Promise<void> {\n\t\tawait this.socketClient.disconnect();\n\t\tlog.logDisconnected();\n\t}\n}\n"]}