@mariozechner/pi-mom 0.9.4 → 0.10.1

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 CHANGED
@@ -3,10 +3,11 @@ import { getModel } from "@mariozechner/pi-ai";
3
3
  import { existsSync, readFileSync } from "fs";
4
4
  import { mkdir } from "fs/promises";
5
5
  import { join } from "path";
6
+ import * as log from "./log.js";
6
7
  import { createExecutor } from "./sandbox.js";
7
8
  import { createMomTools, setUploadFunction } from "./tools/index.js";
8
9
  // Hardcoded model for now
9
- const model = getModel("anthropic", "claude-opus-4-5");
10
+ const model = getModel("anthropic", "claude-sonnet-4-5");
10
11
  function getAnthropicApiKey() {
11
12
  const key = process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;
12
13
  if (!key) {
@@ -25,9 +26,55 @@ function getRecentMessages(channelDir, count) {
25
26
  if (recentLines.length === 0) {
26
27
  return "(no message history yet)";
27
28
  }
28
- return recentLines.join("\n");
29
+ // Format as TSV for more concise system prompt
30
+ const formatted = [];
31
+ for (const line of recentLines) {
32
+ try {
33
+ const msg = JSON.parse(line);
34
+ const date = (msg.date || "").substring(0, 19);
35
+ const user = msg.userName || msg.user;
36
+ const text = msg.text || "";
37
+ const attachments = (msg.attachments || []).map((a) => a.local).join(",");
38
+ formatted.push(`${date}\t${user}\t${text}\t${attachments}`);
39
+ }
40
+ catch (error) { }
41
+ }
42
+ return formatted.join("\n");
29
43
  }
30
- function buildSystemPrompt(workspacePath, channelId, recentMessages, sandboxConfig) {
44
+ function getMemory(channelDir) {
45
+ const parts = [];
46
+ // Read workspace-level memory (shared across all channels)
47
+ const workspaceMemoryPath = join(channelDir, "..", "MEMORY.md");
48
+ if (existsSync(workspaceMemoryPath)) {
49
+ try {
50
+ const content = readFileSync(workspaceMemoryPath, "utf-8").trim();
51
+ if (content) {
52
+ parts.push("### Global Workspace Memory\n" + content);
53
+ }
54
+ }
55
+ catch (error) {
56
+ log.logWarning("Failed to read workspace memory", `${workspaceMemoryPath}: ${error}`);
57
+ }
58
+ }
59
+ // Read channel-specific memory
60
+ const channelMemoryPath = join(channelDir, "MEMORY.md");
61
+ if (existsSync(channelMemoryPath)) {
62
+ try {
63
+ const content = readFileSync(channelMemoryPath, "utf-8").trim();
64
+ if (content) {
65
+ parts.push("### Channel-Specific Memory\n" + content);
66
+ }
67
+ }
68
+ catch (error) {
69
+ log.logWarning("Failed to read channel memory", `${channelMemoryPath}: ${error}`);
70
+ }
71
+ }
72
+ if (parts.length === 0) {
73
+ return "(no working memory yet)";
74
+ }
75
+ return parts.join("\n\n");
76
+ }
77
+ function buildSystemPrompt(workspacePath, channelId, memory, sandboxConfig) {
31
78
  const channelPath = `${workspacePath}/${channelId}`;
32
79
  const isDocker = sandboxConfig.type === "docker";
33
80
  const envDescription = isDocker
@@ -38,8 +85,15 @@ function buildSystemPrompt(workspacePath, channelId, recentMessages, sandboxConf
38
85
  : `You are running directly on the host machine.
39
86
  - Be careful with system modifications
40
87
  - Use the system's package manager if needed`;
88
+ const currentDate = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
89
+ const currentDateTime = new Date().toISOString(); // Full ISO 8601
41
90
  return `You are mom, a helpful Slack bot assistant.
42
91
 
92
+ ## Current Date and Time
93
+ - Date: ${currentDate}
94
+ - Full timestamp: ${currentDateTime}
95
+ - Use this when working with dates or searching logs
96
+
43
97
  ## Communication Style
44
98
  - Be concise and professional
45
99
  - Do not use emojis unless the user communicates informally with you
@@ -60,20 +114,89 @@ ${envDescription}
60
114
  ## Your Workspace
61
115
  Your working directory is: ${channelPath}
62
116
 
63
- ### Scratchpad
64
- Use ${channelPath}/scratch/ for temporary work like cloning repos, generating files, etc.
65
- This directory persists across conversations, so you can reference previous work.
117
+ ### Directory Structure
118
+ - ${workspacePath}/ - Root workspace (shared across all channels)
119
+ - MEMORY.md - GLOBAL memory visible to all channels (write global info here)
120
+ - ${channelId}/ - This channel's directory
121
+ - MEMORY.md - CHANNEL-SPECIFIC memory (only visible in this channel)
122
+ - scratch/ - Your working directory for files, repos, etc.
123
+ - log.jsonl - Message history in JSONL format (one JSON object per line)
124
+ - attachments/ - Files shared by users (managed by system, read-only)
125
+
126
+ ### Message History Format
127
+ Each line in log.jsonl contains:
128
+ {
129
+ "date": "2025-11-26T10:44:00.123Z", // ISO 8601 - easy to grep by date!
130
+ "ts": "1732619040.123456", // Slack timestamp or epoch ms
131
+ "user": "U123ABC", // User ID or "bot"
132
+ "userName": "mario", // User handle (optional)
133
+ "text": "message text",
134
+ "isBot": false
135
+ }
136
+
137
+ **⚠️ CRITICAL: Efficient Log Queries (Avoid Context Overflow)**
138
+
139
+ Log files can be VERY LARGE (100K+ lines). The problem is getting too MANY messages, not message length.
140
+ Each message can be up to 10k chars - that's fine. Use head/tail to LIMIT NUMBER OF MESSAGES (10-50 at a time).
141
+
142
+ **Install jq first (if not already):**
143
+ \`\`\`bash
144
+ ${isDocker ? "apk add jq" : "# jq should be available, or install via package manager"}
145
+ \`\`\`
146
+
147
+ **Essential query patterns:**
148
+ \`\`\`bash
149
+ # Last N messages (compact JSON output)
150
+ tail -20 log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text, attachments: [(.attachments // [])[].local]}'
151
+
152
+ # Or TSV format (easier to read)
153
+ tail -20 log.jsonl | jq -r '[.date[0:19], (.userName // .user), .text, ((.attachments // []) | map(.local) | join(","))] | @tsv'
154
+
155
+ # Search by date (LIMIT with head/tail!)
156
+ grep '"date":"2025-11-26' log.jsonl | tail -30 | jq -c '{date: .date[0:19], user: (.userName // .user), text, attachments: [(.attachments // [])[].local]}'
157
+
158
+ # Messages from specific user (count first, then limit)
159
+ grep '"userName":"mario"' log.jsonl | wc -l # Check count first
160
+ grep '"userName":"mario"' log.jsonl | tail -20 | jq -c '{date: .date[0:19], user: .userName, text, attachments: [(.attachments // [])[].local]}'
161
+
162
+ # Only count (when you just need the number)
163
+ grep '"isBot":false' log.jsonl | wc -l
164
+
165
+ # Messages with attachments only (limit!)
166
+ grep '"attachments":[{' log.jsonl | tail -10 | jq -r '[.date[0:16], (.userName // .user), .text, (.attachments | map(.local) | join(","))] | @tsv'
167
+ \`\`\`
168
+
169
+ **KEY RULE:** Always pipe through 'head -N' or 'tail -N' to limit results BEFORE parsing with jq!
170
+ \`\`\`
171
+
172
+ **Date filtering:**
173
+ - Today: grep '"date":"${currentDate}' log.jsonl
174
+ - Yesterday: grep '"date":"2025-11-25' log.jsonl
175
+ - Date range: grep '"date":"2025-11-(26|27|28)' log.jsonl
176
+ - Time range: grep -E '"date":"2025-11-26T(09|10|11):' log.jsonl
177
+
178
+ ### Working Memory System
179
+ You can maintain working memory across conversations by writing MEMORY.md files.
180
+
181
+ **IMPORTANT PATH RULES:**
182
+ - Global memory (all channels): ${workspacePath}/MEMORY.md
183
+ - Channel memory (this channel only): ${channelPath}/MEMORY.md
66
184
 
67
- ### Channel Data (read-only, managed by the system)
68
- - Message history: ${channelPath}/log.jsonl
69
- - Attachments from users: ${channelPath}/attachments/
185
+ **What to remember:**
186
+ - Project details and architecture → Global memory
187
+ - User preferences and coding style → Global memory
188
+ - Channel-specific context → Channel memory
189
+ - Recurring tasks and patterns → Appropriate memory file
190
+ - Credentials locations (never actual secrets) → Global memory
191
+ - Decisions made and their rationale → Appropriate memory file
70
192
 
71
- You can:
72
- - Configure tools and save credentials in your home directory
73
- - Create files and directories in your scratchpad
193
+ **When to update:**
194
+ - After learning something important that will help in future conversations
195
+ - When user asks you to remember something
196
+ - When you discover project structure or conventions
74
197
 
75
- ### Recent Messages (last 50)
76
- ${recentMessages}
198
+ ### Current Working Memory
199
+ ${memory}
77
200
 
78
201
  ## Tools
79
202
  You have access to: bash, read, edit, write, attach tools.
@@ -100,6 +223,61 @@ function truncate(text, maxLen) {
100
223
  return text;
101
224
  return text.substring(0, maxLen - 3) + "...";
102
225
  }
226
+ function extractToolResultText(result) {
227
+ // If it's already a string, return it
228
+ if (typeof result === "string") {
229
+ return result;
230
+ }
231
+ // If it's an object with content array (tool result format)
232
+ if (result &&
233
+ typeof result === "object" &&
234
+ "content" in result &&
235
+ Array.isArray(result.content)) {
236
+ const content = result.content;
237
+ const textParts = [];
238
+ for (const part of content) {
239
+ if (part.type === "text" && part.text) {
240
+ textParts.push(part.text);
241
+ }
242
+ }
243
+ if (textParts.length > 0) {
244
+ return textParts.join("\n");
245
+ }
246
+ }
247
+ // Fallback to JSON
248
+ return JSON.stringify(result);
249
+ }
250
+ function formatToolArgsForSlack(_toolName, args) {
251
+ const lines = [];
252
+ for (const [key, value] of Object.entries(args)) {
253
+ // Skip the label - it's already shown
254
+ if (key === "label")
255
+ continue;
256
+ // For read tool, format path with offset/limit
257
+ if (key === "path" && typeof value === "string") {
258
+ const offset = args.offset;
259
+ const limit = args.limit;
260
+ if (offset !== undefined && limit !== undefined) {
261
+ lines.push(`${value}:${offset}-${offset + limit}`);
262
+ }
263
+ else {
264
+ lines.push(value);
265
+ }
266
+ continue;
267
+ }
268
+ // Skip offset/limit since we already handled them
269
+ if (key === "offset" || key === "limit")
270
+ continue;
271
+ // For other values, format them
272
+ if (typeof value === "string") {
273
+ lines.push(value);
274
+ }
275
+ else {
276
+ lines.push(JSON.stringify(value));
277
+ }
278
+ }
279
+ return lines.join("\n");
280
+ }
103
281
  export function createAgentRunner(sandboxConfig) {
104
282
  let agent = null;
105
283
  const executor = createExecutor(sandboxConfig);
@@ -110,7 +288,8 @@ export function createAgentRunner(sandboxConfig) {
110
288
  const channelId = ctx.message.channel;
111
289
  const workspacePath = executor.getWorkspacePath(channelDir.replace(`/${channelId}`, ""));
112
290
  const recentMessages = getRecentMessages(channelDir, 50);
113
- const systemPrompt = buildSystemPrompt(workspacePath, channelId, recentMessages, sandboxConfig);
291
+ const memory = getMemory(channelDir);
292
+ const systemPrompt = buildSystemPrompt(workspacePath, channelId, memory, sandboxConfig);
114
293
  // Set up file upload function for the attach tool
115
294
  // For Docker, we need to translate paths back to host
116
295
  setUploadFunction(async (filePath, title) => {
@@ -131,8 +310,42 @@ export function createAgentRunner(sandboxConfig) {
131
310
  getApiKey: async () => getAnthropicApiKey(),
132
311
  }),
133
312
  });
134
- // Track pending tool calls to pair args with results
313
+ // Create logging context
314
+ const logCtx = {
315
+ channelId: ctx.message.channel,
316
+ userName: ctx.message.userName,
317
+ channelName: ctx.channelName,
318
+ };
319
+ // Track pending tool calls to pair args with results and timing
135
320
  const pendingTools = new Map();
321
+ // Track usage across all assistant messages in this run
322
+ const totalUsage = {
323
+ input: 0,
324
+ output: 0,
325
+ cacheRead: 0,
326
+ cacheWrite: 0,
327
+ cost: {
328
+ input: 0,
329
+ output: 0,
330
+ cacheRead: 0,
331
+ cacheWrite: 0,
332
+ total: 0,
333
+ },
334
+ };
335
+ // Track stop reason
336
+ let stopReason = "stop";
337
+ // Promise queue to ensure ctx.respond/respondInThread calls execute in order
338
+ const queue = {
339
+ chain: Promise.resolve(),
340
+ enqueue(fn) {
341
+ const result = this.chain.then(fn);
342
+ this.chain = result.then(() => { }, () => { }); // swallow errors for chain
343
+ return result;
344
+ },
345
+ flush() {
346
+ return this.chain;
347
+ },
348
+ };
136
349
  // Subscribe to events
137
350
  agent.subscribe(async (event) => {
138
351
  switch (event.type) {
@@ -140,11 +353,16 @@ export function createAgentRunner(sandboxConfig) {
140
353
  const args = event.args;
141
354
  const label = args.label || event.toolName;
142
355
  // Store args to pair with result later
143
- pendingTools.set(event.toolCallId, { toolName: event.toolName, args: event.args });
356
+ pendingTools.set(event.toolCallId, {
357
+ toolName: event.toolName,
358
+ args: event.args,
359
+ startTime: Date.now(),
360
+ });
144
361
  // Log to console
145
- console.log(`\n[Tool] ${event.toolName}: ${JSON.stringify(event.args)}`);
362
+ log.logToolStart(logCtx, event.toolName, label, event.args);
146
363
  // Log to jsonl
147
364
  await store.logMessage(ctx.message.channel, {
365
+ date: new Date().toISOString(),
148
366
  ts: Date.now().toString(),
149
367
  user: "bot",
150
368
  text: `[Tool] ${event.toolName}: ${JSON.stringify(event.args)}`,
@@ -152,17 +370,24 @@ export function createAgentRunner(sandboxConfig) {
152
370
  isBot: true,
153
371
  });
154
372
  // Show label in main message only
155
- await ctx.respond(`_${label}_`);
373
+ queue.enqueue(() => ctx.respond(`_${label}_`));
156
374
  break;
157
375
  }
158
376
  case "tool_execution_end": {
159
- const resultStr = typeof event.result === "string" ? event.result : JSON.stringify(event.result);
377
+ const resultStr = extractToolResultText(event.result);
160
378
  const pending = pendingTools.get(event.toolCallId);
161
379
  pendingTools.delete(event.toolCallId);
380
+ const durationMs = pending ? Date.now() - pending.startTime : 0;
162
381
  // Log to console
163
- console.log(`[Tool Result] ${event.isError ? "ERROR: " : ""}${truncate(resultStr, 1000)}\n`);
382
+ if (event.isError) {
383
+ log.logToolError(logCtx, event.toolName, durationMs, resultStr);
384
+ }
385
+ else {
386
+ log.logToolSuccess(logCtx, event.toolName, durationMs, resultStr);
387
+ }
164
388
  // Log to jsonl
165
389
  await store.logMessage(ctx.message.channel, {
390
+ date: new Date().toISOString(),
166
391
  ts: Date.now().toString(),
167
392
  user: "bot",
168
393
  text: `[Tool Result] ${event.toolName}: ${event.isError ? "ERROR: " : ""}${truncate(resultStr, 1000)}`,
@@ -170,57 +395,112 @@ export function createAgentRunner(sandboxConfig) {
170
395
  isBot: true,
171
396
  });
172
397
  // Post args + result together in thread
173
- const argsStr = pending ? JSON.stringify(pending.args, null, 2) : "(args not found)";
398
+ const label = pending?.args ? pending.args.label : undefined;
399
+ const argsFormatted = pending
400
+ ? formatToolArgsForSlack(event.toolName, pending.args)
401
+ : "(args not found)";
402
+ const duration = (durationMs / 1000).toFixed(1);
174
403
  const threadResult = truncate(resultStr, 2000);
175
- await ctx.respondInThread(`*[${event.toolName}]* ${event.isError ? "" : "✓"}\n` +
176
- "```\n" +
177
- argsStr +
178
- "\n```\n" +
179
- "*Result:*\n```\n" +
180
- threadResult +
181
- "\n```");
404
+ let threadMessage = `*${event.isError ? "" : "✓"} ${event.toolName}*`;
405
+ if (label) {
406
+ threadMessage += `: ${label}`;
407
+ }
408
+ threadMessage += ` (${duration}s)\n`;
409
+ if (argsFormatted) {
410
+ threadMessage += "```\n" + argsFormatted + "\n```\n";
411
+ }
412
+ threadMessage += "*Result:*\n```\n" + threadResult + "\n```";
413
+ queue.enqueue(() => ctx.respondInThread(threadMessage));
182
414
  // Show brief error in main message if failed
183
415
  if (event.isError) {
184
- await ctx.respond(`_Error: ${truncate(resultStr, 200)}_`);
416
+ queue.enqueue(() => ctx.respond(`_Error: ${truncate(resultStr, 200)}_`));
185
417
  }
186
418
  break;
187
419
  }
188
420
  case "message_update": {
189
- const ev = event.assistantMessageEvent;
190
- // Stream deltas to console
191
- if (ev.type === "text_delta") {
192
- process.stdout.write(ev.delta);
193
- }
194
- else if (ev.type === "thinking_delta") {
195
- process.stdout.write(ev.delta);
196
- }
421
+ // No longer stream to console - just track that we're streaming
197
422
  break;
198
423
  }
199
424
  case "message_start":
200
425
  if (event.message.role === "assistant") {
201
- process.stdout.write("\n");
426
+ log.logResponseStart(logCtx);
202
427
  }
203
428
  break;
204
429
  case "message_end":
205
430
  if (event.message.role === "assistant") {
206
- process.stdout.write("\n");
207
- // Extract text from assistant message
431
+ const assistantMsg = event.message; // AssistantMessage type
432
+ // Track stop reason
433
+ if (assistantMsg.stopReason) {
434
+ stopReason = assistantMsg.stopReason;
435
+ }
436
+ // Accumulate usage
437
+ if (assistantMsg.usage) {
438
+ totalUsage.input += assistantMsg.usage.input;
439
+ totalUsage.output += assistantMsg.usage.output;
440
+ totalUsage.cacheRead += assistantMsg.usage.cacheRead;
441
+ totalUsage.cacheWrite += assistantMsg.usage.cacheWrite;
442
+ totalUsage.cost.input += assistantMsg.usage.cost.input;
443
+ totalUsage.cost.output += assistantMsg.usage.cost.output;
444
+ totalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;
445
+ totalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;
446
+ totalUsage.cost.total += assistantMsg.usage.cost.total;
447
+ }
448
+ // Extract thinking and text from assistant message
208
449
  const content = event.message.content;
209
- let text = "";
450
+ const thinkingParts = [];
451
+ const textParts = [];
210
452
  for (const part of content) {
211
- if (part.type === "text") {
212
- text += part.text;
453
+ if (part.type === "thinking") {
454
+ thinkingParts.push(part.thinking);
455
+ }
456
+ else if (part.type === "text") {
457
+ textParts.push(part.text);
213
458
  }
214
459
  }
460
+ const text = textParts.join("\n");
461
+ // Post thinking to main message and thread
462
+ for (const thinking of thinkingParts) {
463
+ log.logThinking(logCtx, thinking);
464
+ queue.enqueue(() => ctx.respond(`_${thinking}_`));
465
+ queue.enqueue(() => ctx.respondInThread(`_${thinking}_`));
466
+ }
467
+ // Post text to main message and thread
215
468
  if (text.trim()) {
216
- await ctx.respond(text);
469
+ log.logResponse(logCtx, text);
470
+ queue.enqueue(() => ctx.respond(text));
471
+ queue.enqueue(() => ctx.respondInThread(text));
217
472
  }
218
473
  }
219
474
  break;
220
475
  }
221
476
  });
222
477
  // Run the agent with user's message
223
- await agent.prompt(ctx.message.text || "(attached files)");
478
+ // Prepend recent messages to the user prompt (not system prompt) for better caching
479
+ const userPrompt = `Recent conversation history (last 50 messages):\n` +
480
+ `Format: date TAB user TAB text TAB attachments\n\n` +
481
+ `${recentMessages}\n\n` +
482
+ `---\n\n` +
483
+ `Current message: ${ctx.message.text || "(attached files)"}`;
484
+ await agent.prompt(userPrompt);
485
+ // Wait for all queued respond calls to complete
486
+ await queue.flush();
487
+ // Get final assistant message text from agent state and replace main message
488
+ const messages = agent.state.messages;
489
+ const lastAssistant = messages.filter((m) => m.role === "assistant").pop();
490
+ const finalText = lastAssistant?.content
491
+ .filter((c) => c.type === "text")
492
+ .map((c) => c.text)
493
+ .join("\n") || "";
494
+ if (finalText.trim()) {
495
+ await ctx.replaceMessage(finalText);
496
+ }
497
+ // Log usage summary if there was any usage
498
+ if (totalUsage.cost.total > 0) {
499
+ const summary = log.logUsageSummary(logCtx, totalUsage);
500
+ queue.enqueue(() => ctx.respondInThread(summary));
501
+ await queue.flush();
502
+ }
503
+ return { stopReason };
224
504
  },
225
505
  abort() {
226
506
  agent?.abort();
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\";\n\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-opus-4-5\");\n\nexport interface AgentRunner {\n\trun(ctx: SlackContext, channelDir: string, store: ChannelStore): Promise<void>;\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\treturn recentLines.join(\"\\n\");\n}\n\nfunction buildSystemPrompt(\n\tworkspacePath: string,\n\tchannelId: string,\n\trecentMessages: 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\treturn `You are mom, a helpful Slack bot assistant.\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### Scratchpad\nUse ${channelPath}/scratch/ for temporary work like cloning repos, generating files, etc.\nThis directory persists across conversations, so you can reference previous work.\n\n### Channel Data (read-only, managed by the system)\n- Message history: ${channelPath}/log.jsonl\n- Attachments from users: ${channelPath}/attachments/\n\nYou can:\n- Configure tools and save credentials in your home directory\n- Create files and directories in your scratchpad\n\n### Recent Messages (last 50)\n${recentMessages}\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\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<void> {\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 systemPrompt = buildSystemPrompt(workspacePath, channelId, recentMessages, 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// Track pending tool calls to pair args with results\n\t\t\tconst pendingTools = new Map<string, { toolName: string; args: unknown }>();\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, { toolName: event.toolName, args: event.args });\n\n\t\t\t\t\t\t// Log to console\n\t\t\t\t\t\tconsole.log(`\\n[Tool] ${event.toolName}: ${JSON.stringify(event.args)}`);\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\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\tawait 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 = typeof event.result === \"string\" ? event.result : JSON.stringify(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\t// Log to console\n\t\t\t\t\t\tconsole.log(`[Tool Result] ${event.isError ? \"ERROR: \" : \"\"}${truncate(resultStr, 1000)}\\n`);\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\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 argsStr = pending ? JSON.stringify(pending.args, null, 2) : \"(args not found)\";\n\t\t\t\t\t\tconst threadResult = truncate(resultStr, 2000);\n\t\t\t\t\t\tawait ctx.respondInThread(\n\t\t\t\t\t\t\t`*[${event.toolName}]* ${event.isError ? \"❌\" : \"✓\"}\\n` +\n\t\t\t\t\t\t\t\t\"```\\n\" +\n\t\t\t\t\t\t\t\targsStr +\n\t\t\t\t\t\t\t\t\"\\n```\\n\" +\n\t\t\t\t\t\t\t\t\"*Result:*\\n```\\n\" +\n\t\t\t\t\t\t\t\tthreadResult +\n\t\t\t\t\t\t\t\t\"\\n```\",\n\t\t\t\t\t\t);\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\tawait 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\tconst ev = event.assistantMessageEvent;\n\t\t\t\t\t\t// Stream deltas to console\n\t\t\t\t\t\tif (ev.type === \"text_delta\") {\n\t\t\t\t\t\t\tprocess.stdout.write(ev.delta);\n\t\t\t\t\t\t} else if (ev.type === \"thinking_delta\") {\n\t\t\t\t\t\t\tprocess.stdout.write(ev.delta);\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_start\":\n\t\t\t\t\t\tif (event.message.role === \"assistant\") {\n\t\t\t\t\t\t\tprocess.stdout.write(\"\\n\");\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\tprocess.stdout.write(\"\\n\");\n\t\t\t\t\t\t\t// Extract text from assistant message\n\t\t\t\t\t\t\tconst content = event.message.content;\n\t\t\t\t\t\t\tlet text = \"\";\n\t\t\t\t\t\t\tfor (const part of content) {\n\t\t\t\t\t\t\t\tif (part.type === \"text\") {\n\t\t\t\t\t\t\t\t\ttext += part.text;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (text.trim()) {\n\t\t\t\t\t\t\t\tawait ctx.respond(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\tawait agent.prompt(ctx.message.text || \"(attached files)\");\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":"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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"]}