@mariozechner/pi-mom 0.18.4 → 0.18.5
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/CHANGELOG.md +10 -0
- package/dist/agent.d.ts +1 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +32 -14
- package/dist/agent.js.map +1 -1
- package/dist/download.d.ts +2 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/download.js +89 -0
- package/dist/download.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +28 -4
- package/dist/main.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.18.5] - 2025-12-12
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `--download <channel-id>` flag to download a channel's full history including thread replies as plain text
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Error handling: when agent returns `stopReason: "error"`, main message is updated to "Sorry, something went wrong" and error details are posted to the thread
|
|
12
|
+
|
|
3
13
|
## [0.18.4] - 2025-12-11
|
|
4
14
|
|
|
5
15
|
### Fixed
|
package/dist/agent.d.ts
CHANGED
package/dist/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAQA,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EAAe,YAAY,EAAY,MAAM,YAAY,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA0B/C,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC3B,GAAG,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,eAAe,CAAC,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjH,KAAK,IAAI,IAAI,CAAC;CACd;AA4ND;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CAOlH","sourcesContent":["import { Agent, type AgentEvent, ProviderTransport } from \"@mariozechner/pi-agent-core\";\nimport { getModel } from \"@mariozechner/pi-ai\";\nimport { AgentSession, messageTransformer } from \"@mariozechner/pi-coding-agent\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { mkdir, writeFile } from \"fs/promises\";\nimport { join } from \"path\";\nimport { MomSessionManager, MomSettingsManager } from \"./context.js\";\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 - TODO: make configurable (issue #63)\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\ttsCounter++;\n\t} else {\n\t\tlastTsMs = now;\n\t\ttsCounter = 0;\n\t}\n\tconst seconds = Math.floor(now / 1000);\n\tconst micros = (now % 1000) * 1000 + tsCounter;\n\treturn `${seconds}.${micros.toString().padStart(6, \"0\")}`;\n}\n\nexport interface PendingMessage {\n\tuserName: string;\n\ttext: string;\n\tattachments: { local: string }[];\n\ttimestamp: number;\n}\n\nexport interface AgentRunner {\n\trun(ctx: SlackContext, store: ChannelStore, pendingMessages?: PendingMessage[]): 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 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\treturn `You are mom, a Slack bot assistant. Be concise. No emojis.\n\n## Context\n- For current date/time, use: date\n- You have access to previous conversation context including tool results from prior turns.\n- For older history beyond your context, search log.jsonl (contains user messages and your final responses, but not tool results).\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 # Message history (no tool results)\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## System Configuration Log\nMaintain ${workspacePath}/SYSTEM.md to log all environment modifications:\n- Installed packages (apk add, npm install, pip install)\n- Environment variables set\n- Config files modified (~/.gitconfig, cron jobs, etc.)\n- Skill dependencies installed\n\nUpdate this file whenever you modify the environment. On fresh container, read it first to restore your setup.\n\n## Log Queries (for older history)\nFormat: \\`{\"date\":\"...\",\"ts\":\"...\",\"user\":\"...\",\"userName\":\"...\",\"text\":\"...\",\"isBot\":false}\\`\nThe log contains user messages and your final responses (not tool calls/results).\n${isDocker ? \"Install jq: apk add jq\" : \"\"}\n\n\\`\\`\\`bash\n# Recent messages\ntail -30 log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Search for specific topic\ngrep -i \"topic\" log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Messages from specific user\ngrep '\"userName\":\"mario\"' log.jsonl | tail -20 | jq -c '{date: .date[0:19], text}'\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\tif (typeof result === \"string\") {\n\t\treturn result;\n\t}\n\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\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\tif (key === \"label\") continue;\n\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\tif (key === \"offset\" || key === \"limit\") continue;\n\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\n// Cache runners per channel\nconst channelRunners = new Map<string, AgentRunner>();\n\n/**\n * Get or create an AgentRunner for a channel.\n * Runners are cached - one per channel, persistent across messages.\n */\nexport function getOrCreateRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst existing = channelRunners.get(channelId);\n\tif (existing) return existing;\n\n\tconst runner = createRunner(sandboxConfig, channelId, channelDir);\n\tchannelRunners.set(channelId, runner);\n\treturn runner;\n}\n\n/**\n * Create a new AgentRunner for a channel.\n * Sets up the session and subscribes to events once.\n */\nfunction createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst executor = createExecutor(sandboxConfig);\n\tconst workspacePath = executor.getWorkspacePath(channelDir.replace(`/${channelId}`, \"\"));\n\n\t// Create tools\n\tconst tools = createMomTools(executor);\n\n\t// Initial system prompt (will be updated each run with fresh memory/channels/users)\n\tconst memory = getMemory(channelDir);\n\tconst systemPrompt = buildSystemPrompt(workspacePath, channelId, memory, sandboxConfig, [], []);\n\n\t// Create session manager and settings manager\n\t// Pass model info so new sessions get a header written immediately\n\tconst sessionManager = new MomSessionManager(channelDir, {\n\t\tprovider: model.provider,\n\t\tid: model.id,\n\t\tthinkingLevel: \"off\",\n\t});\n\tconst settingsManager = new MomSettingsManager(join(channelDir, \"..\"));\n\n\t// Create agent\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel,\n\t\t\tthinkingLevel: \"off\",\n\t\t\ttools,\n\t\t},\n\t\tmessageTransformer,\n\t\ttransport: new ProviderTransport({\n\t\t\tgetApiKey: async () => getAnthropicApiKey(),\n\t\t}),\n\t});\n\n\t// Load existing messages\n\tconst loadedSession = sessionManager.loadSession();\n\tif (loadedSession.messages.length > 0) {\n\t\tagent.replaceMessages(loadedSession.messages);\n\t\tlog.logInfo(`[${channelId}] Loaded ${loadedSession.messages.length} messages from context.jsonl`);\n\t}\n\n\t// Create AgentSession wrapper\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager: sessionManager as any,\n\t\tsettingsManager: settingsManager as any,\n\t});\n\n\t// Mutable per-run state - event handler references this\n\tconst runState = {\n\t\tctx: null as SlackContext | null,\n\t\tlogCtx: null as { channelId: string; userName?: string; channelName?: string } | null,\n\t\tqueue: null as {\n\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void;\n\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog?: boolean): void;\n\t\t} | null,\n\t\tpendingTools: new Map<string, { toolName: string; args: unknown; startTime: number }>(),\n\t\ttotalUsage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t};\n\n\t// Subscribe to events ONCE\n\tsession.subscribe(async (event) => {\n\t\t// Skip if no active run\n\t\tif (!runState.ctx || !runState.logCtx || !runState.queue) return;\n\n\t\tconst { ctx, logCtx, queue, pendingTools } = runState;\n\n\t\tif (event.type === \"tool_execution_start\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"tool_execution_start\" };\n\t\t\tconst args = agentEvent.args as { label?: string };\n\t\t\tconst label = args.label || agentEvent.toolName;\n\n\t\t\tpendingTools.set(agentEvent.toolCallId, {\n\t\t\t\ttoolName: agentEvent.toolName,\n\t\t\t\targs: agentEvent.args,\n\t\t\t\tstartTime: Date.now(),\n\t\t\t});\n\n\t\t\tlog.logToolStart(logCtx, agentEvent.toolName, label, agentEvent.args as Record<string, unknown>);\n\t\t\tqueue.enqueue(() => ctx.respond(`_→ ${label}_`, false), \"tool label\");\n\t\t} else if (event.type === \"tool_execution_end\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"tool_execution_end\" };\n\t\t\tconst resultStr = extractToolResultText(agentEvent.result);\n\t\t\tconst pending = pendingTools.get(agentEvent.toolCallId);\n\t\t\tpendingTools.delete(agentEvent.toolCallId);\n\n\t\t\tconst durationMs = pending ? Date.now() - pending.startTime : 0;\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tlog.logToolError(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t} else {\n\t\t\t\tlog.logToolSuccess(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t}\n\n\t\t\t// Post args + result to thread\n\t\t\tconst label = pending?.args ? (pending.args as { label?: string }).label : undefined;\n\t\t\tconst argsFormatted = pending\n\t\t\t\t? formatToolArgsForSlack(agentEvent.toolName, pending.args as Record<string, unknown>)\n\t\t\t\t: \"(args not found)\";\n\t\t\tconst duration = (durationMs / 1000).toFixed(1);\n\t\t\tlet threadMessage = `*${agentEvent.isError ? \"✗\" : \"✓\"} ${agentEvent.toolName}*`;\n\t\t\tif (label) threadMessage += `: ${label}`;\n\t\t\tthreadMessage += ` (${duration}s)\\n`;\n\t\t\tif (argsFormatted) threadMessage += \"```\\n\" + argsFormatted + \"\\n```\\n\";\n\t\t\tthreadMessage += \"*Result:*\\n```\\n\" + resultStr + \"\\n```\";\n\n\t\t\tqueue.enqueueMessage(threadMessage, \"thread\", \"tool result thread\", false);\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tqueue.enqueue(() => ctx.respond(`_Error: ${truncate(resultStr, 200)}_`, false), \"tool error\");\n\t\t\t}\n\t\t} else if (event.type === \"message_start\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"message_start\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tlog.logResponseStart(logCtx);\n\t\t\t}\n\t\t} else if (event.type === \"message_end\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"message_end\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = agentEvent.message as any;\n\n\t\t\t\tif (assistantMsg.stopReason) {\n\t\t\t\t\trunState.stopReason = assistantMsg.stopReason;\n\t\t\t\t}\n\n\t\t\t\tif (assistantMsg.usage) {\n\t\t\t\t\trunState.totalUsage.input += assistantMsg.usage.input;\n\t\t\t\t\trunState.totalUsage.output += assistantMsg.usage.output;\n\t\t\t\t\trunState.totalUsage.cacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\t\trunState.totalUsage.cacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.input += assistantMsg.usage.cost.input;\n\t\t\t\t\trunState.totalUsage.cost.output += assistantMsg.usage.cost.output;\n\t\t\t\t\trunState.totalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;\n\t\t\t\t\trunState.totalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.total += assistantMsg.usage.cost.total;\n\t\t\t\t}\n\n\t\t\t\tconst content = agentEvent.message.content;\n\t\t\t\tconst thinkingParts: string[] = [];\n\t\t\t\tconst textParts: string[] = [];\n\t\t\t\tfor (const part of content) {\n\t\t\t\t\tif (part.type === \"thinking\") {\n\t\t\t\t\t\tthinkingParts.push((part as any).thinking);\n\t\t\t\t\t} else if (part.type === \"text\") {\n\t\t\t\t\t\ttextParts.push((part as any).text);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst text = textParts.join(\"\\n\");\n\n\t\t\t\tfor (const thinking of thinkingParts) {\n\t\t\t\t\tlog.logThinking(logCtx, thinking);\n\t\t\t\t\tqueue.enqueueMessage(`_${thinking}_`, \"main\", \"thinking main\");\n\t\t\t\t\tqueue.enqueueMessage(`_${thinking}_`, \"thread\", \"thinking thread\", false);\n\t\t\t\t}\n\n\t\t\t\tif (text.trim()) {\n\t\t\t\t\tlog.logResponse(logCtx, text);\n\t\t\t\t\tqueue.enqueueMessage(text, \"main\", \"response main\");\n\t\t\t\t\tqueue.enqueueMessage(text, \"thread\", \"response thread\", false);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"auto_compaction_start\") {\n\t\t\tlog.logInfo(`Auto-compaction started (reason: ${(event as any).reason})`);\n\t\t\tqueue.enqueue(() => ctx.respond(\"_Compacting context..._\", false), \"compaction start\");\n\t\t} else if (event.type === \"auto_compaction_end\") {\n\t\t\tconst compEvent = event as any;\n\t\t\tif (compEvent.result) {\n\t\t\t\tlog.logInfo(`Auto-compaction complete: ${compEvent.result.tokensBefore} tokens compacted`);\n\t\t\t} else if (compEvent.aborted) {\n\t\t\t\tlog.logInfo(\"Auto-compaction aborted\");\n\t\t\t}\n\t\t} else if (event.type === \"auto_retry_start\") {\n\t\t\tconst retryEvent = event as any;\n\t\t\tlog.logWarning(`Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})`, retryEvent.errorMessage);\n\t\t\tqueue.enqueue(\n\t\t\t\t() => ctx.respond(`_Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})..._`, false),\n\t\t\t\t\"retry\",\n\t\t\t);\n\t\t}\n\t});\n\n\t// Slack message limit\n\tconst SLACK_MAX_LENGTH = 40000;\n\tconst splitForSlack = (text: string): string[] => {\n\t\tif (text.length <= SLACK_MAX_LENGTH) return [text];\n\t\tconst parts: string[] = [];\n\t\tlet remaining = text;\n\t\tlet partNum = 1;\n\t\twhile (remaining.length > 0) {\n\t\t\tconst chunk = remaining.substring(0, SLACK_MAX_LENGTH - 50);\n\t\t\tremaining = remaining.substring(SLACK_MAX_LENGTH - 50);\n\t\t\tconst suffix = remaining.length > 0 ? `\\n_(continued ${partNum}...)_` : \"\";\n\t\t\tparts.push(chunk + suffix);\n\t\t\tpartNum++;\n\t\t}\n\t\treturn parts;\n\t};\n\n\treturn {\n\t\tasync run(\n\t\t\tctx: SlackContext,\n\t\t\t_store: ChannelStore,\n\t\t\t_pendingMessages?: PendingMessage[],\n\t\t): Promise<{ stopReason: string }> {\n\t\t\t// Ensure channel directory exists\n\t\t\tawait mkdir(channelDir, { recursive: true });\n\n\t\t\t// Reload messages from context.jsonl\n\t\t\t// This picks up any messages synced from log.jsonl before this run\n\t\t\tconst reloadedSession = sessionManager.loadSession();\n\t\t\tif (reloadedSession.messages.length > 0) {\n\t\t\t\tagent.replaceMessages(reloadedSession.messages);\n\t\t\t\tlog.logInfo(`[${channelId}] Reloaded ${reloadedSession.messages.length} messages from context`);\n\t\t\t}\n\n\t\t\t// Update system prompt with fresh memory and channel/user info\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\t\t\tsession.agent.setSystemPrompt(systemPrompt);\n\n\t\t\t// Set up file upload function\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// Reset per-run state\n\t\t\trunState.ctx = ctx;\n\t\t\trunState.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\t\t\trunState.pendingTools.clear();\n\t\t\trunState.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: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t};\n\t\t\trunState.stopReason = \"stop\";\n\n\t\t\t// Create queue for this run\n\t\t\tlet queueChain = Promise.resolve();\n\t\t\trunState.queue = {\n\t\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void {\n\t\t\t\t\tqueueChain = queueChain.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\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\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\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog = 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, doLog) : 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};\n\n\t\t\t// Log context info\n\t\t\tlog.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);\n\t\t\tlog.logInfo(`Channels: ${ctx.channels.length}, Users: ${ctx.users.length}`);\n\n\t\t\t// Build user message with username prefix\n\t\t\t// Format: \"[username]: message\" so LLM knows who's talking\n\t\t\tlet userMessage = `[${ctx.message.userName || \"unknown\"}]: ${ctx.message.text}`;\n\n\t\t\t// Add attachment paths if any (convert to absolute paths in execution environment)\n\t\t\tif (ctx.message.attachments && ctx.message.attachments.length > 0) {\n\t\t\t\tconst attachmentPaths = ctx.message.attachments.map((a) => `${workspacePath}/${a.local}`).join(\"\\n\");\n\t\t\t\tuserMessage += `\\n\\nAttachments:\\n${attachmentPaths}`;\n\t\t\t}\n\n\t\t\t// Debug: write context to last_prompt.jsonl\n\t\t\tconst debugContext = {\n\t\t\t\tsystemPrompt,\n\t\t\t\tmessages: session.messages,\n\t\t\t\tnewUserMessage: userMessage,\n\t\t\t};\n\t\t\tawait writeFile(join(channelDir, \"last_prompt.jsonl\"), JSON.stringify(debugContext, null, 2));\n\n\t\t\tawait session.prompt(userMessage);\n\n\t\t\t// Wait for queued messages\n\t\t\tawait queueChain;\n\n\t\t\t// Final message update\n\t\t\tconst messages = session.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\n\t\t\tif (finalText.trim()) {\n\t\t\t\ttry {\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 with context info\n\t\t\tif (runState.totalUsage.cost.total > 0) {\n\t\t\t\t// Get last non-aborted assistant message for context calculation\n\t\t\t\tconst messages = session.messages;\n\t\t\t\tconst lastAssistantMessage = messages\n\t\t\t\t\t.slice()\n\t\t\t\t\t.reverse()\n\t\t\t\t\t.find((m) => m.role === \"assistant\" && (m as any).stopReason !== \"aborted\") as any;\n\n\t\t\t\tconst contextTokens = lastAssistantMessage\n\t\t\t\t\t? lastAssistantMessage.usage.input +\n\t\t\t\t\t\tlastAssistantMessage.usage.output +\n\t\t\t\t\t\tlastAssistantMessage.usage.cacheRead +\n\t\t\t\t\t\tlastAssistantMessage.usage.cacheWrite\n\t\t\t\t\t: 0;\n\t\t\t\tconst contextWindow = model.contextWindow || 200000;\n\n\t\t\t\tconst summary = log.logUsageSummary(runState.logCtx!, runState.totalUsage, contextTokens, contextWindow);\n\t\t\t\trunState.queue.enqueue(() => ctx.respondInThread(summary), \"usage summary\");\n\t\t\t\tawait queueChain;\n\t\t\t}\n\n\t\t\t// Clear run state\n\t\t\trunState.ctx = null;\n\t\t\trunState.logCtx = null;\n\t\t\trunState.queue = null;\n\n\t\t\treturn { stopReason: runState.stopReason };\n\t\t},\n\n\t\tabort(): void {\n\t\t\tsession.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\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\tif (containerPath.startsWith(\"/workspace/\")) {\n\t\t\treturn join(channelDir, \"..\", containerPath.slice(\"/workspace/\".length));\n\t\t}\n\t}\n\treturn containerPath;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAQA,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EAAe,YAAY,EAAY,MAAM,YAAY,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA0B/C,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC3B,GAAG,CACF,GAAG,EAAE,YAAY,EACjB,KAAK,EAAE,YAAY,EACnB,eAAe,CAAC,EAAE,cAAc,EAAE,GAChC,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D,KAAK,IAAI,IAAI,CAAC;CACd;AA4ND;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CAOlH","sourcesContent":["import { Agent, type AgentEvent, ProviderTransport } from \"@mariozechner/pi-agent-core\";\nimport { getModel } from \"@mariozechner/pi-ai\";\nimport { AgentSession, messageTransformer } from \"@mariozechner/pi-coding-agent\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { mkdir, writeFile } from \"fs/promises\";\nimport { join } from \"path\";\nimport { MomSessionManager, MomSettingsManager } from \"./context.js\";\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 - TODO: make configurable (issue #63)\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\ttsCounter++;\n\t} else {\n\t\tlastTsMs = now;\n\t\ttsCounter = 0;\n\t}\n\tconst seconds = Math.floor(now / 1000);\n\tconst micros = (now % 1000) * 1000 + tsCounter;\n\treturn `${seconds}.${micros.toString().padStart(6, \"0\")}`;\n}\n\nexport interface PendingMessage {\n\tuserName: string;\n\ttext: string;\n\tattachments: { local: string }[];\n\ttimestamp: number;\n}\n\nexport interface AgentRunner {\n\trun(\n\t\tctx: SlackContext,\n\t\tstore: ChannelStore,\n\t\tpendingMessages?: PendingMessage[],\n\t): Promise<{ stopReason: string; errorMessage?: 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 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\treturn `You are mom, a Slack bot assistant. Be concise. No emojis.\n\n## Context\n- For current date/time, use: date\n- You have access to previous conversation context including tool results from prior turns.\n- For older history beyond your context, search log.jsonl (contains user messages and your final responses, but not tool results).\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 # Message history (no tool results)\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## System Configuration Log\nMaintain ${workspacePath}/SYSTEM.md to log all environment modifications:\n- Installed packages (apk add, npm install, pip install)\n- Environment variables set\n- Config files modified (~/.gitconfig, cron jobs, etc.)\n- Skill dependencies installed\n\nUpdate this file whenever you modify the environment. On fresh container, read it first to restore your setup.\n\n## Log Queries (for older history)\nFormat: \\`{\"date\":\"...\",\"ts\":\"...\",\"user\":\"...\",\"userName\":\"...\",\"text\":\"...\",\"isBot\":false}\\`\nThe log contains user messages and your final responses (not tool calls/results).\n${isDocker ? \"Install jq: apk add jq\" : \"\"}\n\n\\`\\`\\`bash\n# Recent messages\ntail -30 log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Search for specific topic\ngrep -i \"topic\" log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Messages from specific user\ngrep '\"userName\":\"mario\"' log.jsonl | tail -20 | jq -c '{date: .date[0:19], text}'\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\tif (typeof result === \"string\") {\n\t\treturn result;\n\t}\n\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\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\tif (key === \"label\") continue;\n\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\tif (key === \"offset\" || key === \"limit\") continue;\n\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\n// Cache runners per channel\nconst channelRunners = new Map<string, AgentRunner>();\n\n/**\n * Get or create an AgentRunner for a channel.\n * Runners are cached - one per channel, persistent across messages.\n */\nexport function getOrCreateRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst existing = channelRunners.get(channelId);\n\tif (existing) return existing;\n\n\tconst runner = createRunner(sandboxConfig, channelId, channelDir);\n\tchannelRunners.set(channelId, runner);\n\treturn runner;\n}\n\n/**\n * Create a new AgentRunner for a channel.\n * Sets up the session and subscribes to events once.\n */\nfunction createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst executor = createExecutor(sandboxConfig);\n\tconst workspacePath = executor.getWorkspacePath(channelDir.replace(`/${channelId}`, \"\"));\n\n\t// Create tools\n\tconst tools = createMomTools(executor);\n\n\t// Initial system prompt (will be updated each run with fresh memory/channels/users)\n\tconst memory = getMemory(channelDir);\n\tconst systemPrompt = buildSystemPrompt(workspacePath, channelId, memory, sandboxConfig, [], []);\n\n\t// Create session manager and settings manager\n\t// Pass model info so new sessions get a header written immediately\n\tconst sessionManager = new MomSessionManager(channelDir, {\n\t\tprovider: model.provider,\n\t\tid: model.id,\n\t\tthinkingLevel: \"off\",\n\t});\n\tconst settingsManager = new MomSettingsManager(join(channelDir, \"..\"));\n\n\t// Create agent\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel,\n\t\t\tthinkingLevel: \"off\",\n\t\t\ttools,\n\t\t},\n\t\tmessageTransformer,\n\t\ttransport: new ProviderTransport({\n\t\t\tgetApiKey: async () => getAnthropicApiKey(),\n\t\t}),\n\t});\n\n\t// Load existing messages\n\tconst loadedSession = sessionManager.loadSession();\n\tif (loadedSession.messages.length > 0) {\n\t\tagent.replaceMessages(loadedSession.messages);\n\t\tlog.logInfo(`[${channelId}] Loaded ${loadedSession.messages.length} messages from context.jsonl`);\n\t}\n\n\t// Create AgentSession wrapper\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager: sessionManager as any,\n\t\tsettingsManager: settingsManager as any,\n\t});\n\n\t// Mutable per-run state - event handler references this\n\tconst runState = {\n\t\tctx: null as SlackContext | null,\n\t\tlogCtx: null as { channelId: string; userName?: string; channelName?: string } | null,\n\t\tqueue: null as {\n\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void;\n\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog?: boolean): void;\n\t\t} | null,\n\t\tpendingTools: new Map<string, { toolName: string; args: unknown; startTime: number }>(),\n\t\ttotalUsage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t\terrorMessage: undefined as string | undefined,\n\t};\n\n\t// Subscribe to events ONCE\n\tsession.subscribe(async (event) => {\n\t\t// Skip if no active run\n\t\tif (!runState.ctx || !runState.logCtx || !runState.queue) return;\n\n\t\tconst { ctx, logCtx, queue, pendingTools } = runState;\n\n\t\tif (event.type === \"tool_execution_start\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"tool_execution_start\" };\n\t\t\tconst args = agentEvent.args as { label?: string };\n\t\t\tconst label = args.label || agentEvent.toolName;\n\n\t\t\tpendingTools.set(agentEvent.toolCallId, {\n\t\t\t\ttoolName: agentEvent.toolName,\n\t\t\t\targs: agentEvent.args,\n\t\t\t\tstartTime: Date.now(),\n\t\t\t});\n\n\t\t\tlog.logToolStart(logCtx, agentEvent.toolName, label, agentEvent.args as Record<string, unknown>);\n\t\t\tqueue.enqueue(() => ctx.respond(`_→ ${label}_`, false), \"tool label\");\n\t\t} else if (event.type === \"tool_execution_end\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"tool_execution_end\" };\n\t\t\tconst resultStr = extractToolResultText(agentEvent.result);\n\t\t\tconst pending = pendingTools.get(agentEvent.toolCallId);\n\t\t\tpendingTools.delete(agentEvent.toolCallId);\n\n\t\t\tconst durationMs = pending ? Date.now() - pending.startTime : 0;\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tlog.logToolError(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t} else {\n\t\t\t\tlog.logToolSuccess(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t}\n\n\t\t\t// Post args + result to thread\n\t\t\tconst label = pending?.args ? (pending.args as { label?: string }).label : undefined;\n\t\t\tconst argsFormatted = pending\n\t\t\t\t? formatToolArgsForSlack(agentEvent.toolName, pending.args as Record<string, unknown>)\n\t\t\t\t: \"(args not found)\";\n\t\t\tconst duration = (durationMs / 1000).toFixed(1);\n\t\t\tlet threadMessage = `*${agentEvent.isError ? \"✗\" : \"✓\"} ${agentEvent.toolName}*`;\n\t\t\tif (label) threadMessage += `: ${label}`;\n\t\t\tthreadMessage += ` (${duration}s)\\n`;\n\t\t\tif (argsFormatted) threadMessage += \"```\\n\" + argsFormatted + \"\\n```\\n\";\n\t\t\tthreadMessage += \"*Result:*\\n```\\n\" + resultStr + \"\\n```\";\n\n\t\t\tqueue.enqueueMessage(threadMessage, \"thread\", \"tool result thread\", false);\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tqueue.enqueue(() => ctx.respond(`_Error: ${truncate(resultStr, 200)}_`, false), \"tool error\");\n\t\t\t}\n\t\t} else if (event.type === \"message_start\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"message_start\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tlog.logResponseStart(logCtx);\n\t\t\t}\n\t\t} else if (event.type === \"message_end\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"message_end\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = agentEvent.message as any;\n\n\t\t\t\tif (assistantMsg.stopReason) {\n\t\t\t\t\trunState.stopReason = assistantMsg.stopReason;\n\t\t\t\t}\n\t\t\t\tif (assistantMsg.errorMessage) {\n\t\t\t\t\trunState.errorMessage = assistantMsg.errorMessage;\n\t\t\t\t}\n\n\t\t\t\tif (assistantMsg.usage) {\n\t\t\t\t\trunState.totalUsage.input += assistantMsg.usage.input;\n\t\t\t\t\trunState.totalUsage.output += assistantMsg.usage.output;\n\t\t\t\t\trunState.totalUsage.cacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\t\trunState.totalUsage.cacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.input += assistantMsg.usage.cost.input;\n\t\t\t\t\trunState.totalUsage.cost.output += assistantMsg.usage.cost.output;\n\t\t\t\t\trunState.totalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;\n\t\t\t\t\trunState.totalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.total += assistantMsg.usage.cost.total;\n\t\t\t\t}\n\n\t\t\t\tconst content = agentEvent.message.content;\n\t\t\t\tconst thinkingParts: string[] = [];\n\t\t\t\tconst textParts: string[] = [];\n\t\t\t\tfor (const part of content) {\n\t\t\t\t\tif (part.type === \"thinking\") {\n\t\t\t\t\t\tthinkingParts.push((part as any).thinking);\n\t\t\t\t\t} else if (part.type === \"text\") {\n\t\t\t\t\t\ttextParts.push((part as any).text);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst text = textParts.join(\"\\n\");\n\n\t\t\t\tfor (const thinking of thinkingParts) {\n\t\t\t\t\tlog.logThinking(logCtx, thinking);\n\t\t\t\t\tqueue.enqueueMessage(`_${thinking}_`, \"main\", \"thinking main\");\n\t\t\t\t\tqueue.enqueueMessage(`_${thinking}_`, \"thread\", \"thinking thread\", false);\n\t\t\t\t}\n\n\t\t\t\tif (text.trim()) {\n\t\t\t\t\tlog.logResponse(logCtx, text);\n\t\t\t\t\tqueue.enqueueMessage(text, \"main\", \"response main\");\n\t\t\t\t\tqueue.enqueueMessage(text, \"thread\", \"response thread\", false);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"auto_compaction_start\") {\n\t\t\tlog.logInfo(`Auto-compaction started (reason: ${(event as any).reason})`);\n\t\t\tqueue.enqueue(() => ctx.respond(\"_Compacting context..._\", false), \"compaction start\");\n\t\t} else if (event.type === \"auto_compaction_end\") {\n\t\t\tconst compEvent = event as any;\n\t\t\tif (compEvent.result) {\n\t\t\t\tlog.logInfo(`Auto-compaction complete: ${compEvent.result.tokensBefore} tokens compacted`);\n\t\t\t} else if (compEvent.aborted) {\n\t\t\t\tlog.logInfo(\"Auto-compaction aborted\");\n\t\t\t}\n\t\t} else if (event.type === \"auto_retry_start\") {\n\t\t\tconst retryEvent = event as any;\n\t\t\tlog.logWarning(`Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})`, retryEvent.errorMessage);\n\t\t\tqueue.enqueue(\n\t\t\t\t() => ctx.respond(`_Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})..._`, false),\n\t\t\t\t\"retry\",\n\t\t\t);\n\t\t}\n\t});\n\n\t// Slack message limit\n\tconst SLACK_MAX_LENGTH = 40000;\n\tconst splitForSlack = (text: string): string[] => {\n\t\tif (text.length <= SLACK_MAX_LENGTH) return [text];\n\t\tconst parts: string[] = [];\n\t\tlet remaining = text;\n\t\tlet partNum = 1;\n\t\twhile (remaining.length > 0) {\n\t\t\tconst chunk = remaining.substring(0, SLACK_MAX_LENGTH - 50);\n\t\t\tremaining = remaining.substring(SLACK_MAX_LENGTH - 50);\n\t\t\tconst suffix = remaining.length > 0 ? `\\n_(continued ${partNum}...)_` : \"\";\n\t\t\tparts.push(chunk + suffix);\n\t\t\tpartNum++;\n\t\t}\n\t\treturn parts;\n\t};\n\n\treturn {\n\t\tasync run(\n\t\t\tctx: SlackContext,\n\t\t\t_store: ChannelStore,\n\t\t\t_pendingMessages?: PendingMessage[],\n\t\t): Promise<{ stopReason: string; errorMessage?: string }> {\n\t\t\t// Ensure channel directory exists\n\t\t\tawait mkdir(channelDir, { recursive: true });\n\n\t\t\t// Reload messages from context.jsonl\n\t\t\t// This picks up any messages synced from log.jsonl before this run\n\t\t\tconst reloadedSession = sessionManager.loadSession();\n\t\t\tif (reloadedSession.messages.length > 0) {\n\t\t\t\tagent.replaceMessages(reloadedSession.messages);\n\t\t\t\tlog.logInfo(`[${channelId}] Reloaded ${reloadedSession.messages.length} messages from context`);\n\t\t\t}\n\n\t\t\t// Update system prompt with fresh memory and channel/user info\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\t\t\tsession.agent.setSystemPrompt(systemPrompt);\n\n\t\t\t// Set up file upload function\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// Reset per-run state\n\t\t\trunState.ctx = ctx;\n\t\t\trunState.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\t\t\trunState.pendingTools.clear();\n\t\t\trunState.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: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t};\n\t\t\trunState.stopReason = \"stop\";\n\t\t\trunState.errorMessage = undefined;\n\n\t\t\t// Create queue for this run\n\t\t\tlet queueChain = Promise.resolve();\n\t\t\trunState.queue = {\n\t\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void {\n\t\t\t\t\tqueueChain = queueChain.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\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\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\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog = 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, doLog) : 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};\n\n\t\t\t// Log context info\n\t\t\tlog.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);\n\t\t\tlog.logInfo(`Channels: ${ctx.channels.length}, Users: ${ctx.users.length}`);\n\n\t\t\t// Build user message with username prefix\n\t\t\t// Format: \"[username]: message\" so LLM knows who's talking\n\t\t\tlet userMessage = `[${ctx.message.userName || \"unknown\"}]: ${ctx.message.text}`;\n\n\t\t\t// Add attachment paths if any (convert to absolute paths in execution environment)\n\t\t\tif (ctx.message.attachments && ctx.message.attachments.length > 0) {\n\t\t\t\tconst attachmentPaths = ctx.message.attachments.map((a) => `${workspacePath}/${a.local}`).join(\"\\n\");\n\t\t\t\tuserMessage += `\\n\\nAttachments:\\n${attachmentPaths}`;\n\t\t\t}\n\n\t\t\t// Debug: write context to last_prompt.jsonl\n\t\t\tconst debugContext = {\n\t\t\t\tsystemPrompt,\n\t\t\t\tmessages: session.messages,\n\t\t\t\tnewUserMessage: userMessage,\n\t\t\t};\n\t\t\tawait writeFile(join(channelDir, \"last_prompt.jsonl\"), JSON.stringify(debugContext, null, 2));\n\n\t\t\tawait session.prompt(userMessage);\n\n\t\t\t// Wait for queued messages\n\t\t\tawait queueChain;\n\n\t\t\t// Handle error case - update main message and post error to thread\n\t\t\tif (runState.stopReason === \"error\" && runState.errorMessage) {\n\t\t\t\ttry {\n\t\t\t\t\tawait ctx.replaceMessage(\"_Sorry, something went wrong_\");\n\t\t\t\t\tawait ctx.respondInThread(`_Error: ${runState.errorMessage}_`);\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 post error message\", errMsg);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Final message update\n\t\t\t\tconst messages = session.messages;\n\t\t\t\tconst lastAssistant = messages.filter((m) => m.role === \"assistant\").pop();\n\t\t\t\tconst finalText =\n\t\t\t\t\tlastAssistant?.content\n\t\t\t\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t\t\t\t.map((c) => c.text)\n\t\t\t\t\t\t.join(\"\\n\") || \"\";\n\n\t\t\t\tif (finalText.trim()) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst mainText =\n\t\t\t\t\t\t\tfinalText.length > SLACK_MAX_LENGTH\n\t\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\t: finalText;\n\t\t\t\t\t\tawait ctx.replaceMessage(mainText);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\tlog.logWarning(\"Failed to replace message with final text\", errMsg);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Log usage summary with context info\n\t\t\tif (runState.totalUsage.cost.total > 0) {\n\t\t\t\t// Get last non-aborted assistant message for context calculation\n\t\t\t\tconst messages = session.messages;\n\t\t\t\tconst lastAssistantMessage = messages\n\t\t\t\t\t.slice()\n\t\t\t\t\t.reverse()\n\t\t\t\t\t.find((m) => m.role === \"assistant\" && (m as any).stopReason !== \"aborted\") as any;\n\n\t\t\t\tconst contextTokens = lastAssistantMessage\n\t\t\t\t\t? lastAssistantMessage.usage.input +\n\t\t\t\t\t\tlastAssistantMessage.usage.output +\n\t\t\t\t\t\tlastAssistantMessage.usage.cacheRead +\n\t\t\t\t\t\tlastAssistantMessage.usage.cacheWrite\n\t\t\t\t\t: 0;\n\t\t\t\tconst contextWindow = model.contextWindow || 200000;\n\n\t\t\t\tconst summary = log.logUsageSummary(runState.logCtx!, runState.totalUsage, contextTokens, contextWindow);\n\t\t\t\trunState.queue.enqueue(() => ctx.respondInThread(summary), \"usage summary\");\n\t\t\t\tawait queueChain;\n\t\t\t}\n\n\t\t\t// Clear run state\n\t\t\trunState.ctx = null;\n\t\t\trunState.logCtx = null;\n\t\t\trunState.queue = null;\n\n\t\t\treturn { stopReason: runState.stopReason, errorMessage: runState.errorMessage };\n\t\t},\n\n\t\tabort(): void {\n\t\t\tsession.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\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\tif (containerPath.startsWith(\"/workspace/\")) {\n\t\t\treturn join(channelDir, \"..\", containerPath.slice(\"/workspace/\".length));\n\t\t}\n\t}\n\treturn containerPath;\n}\n"]}
|
package/dist/agent.js
CHANGED
|
@@ -292,6 +292,7 @@ function createRunner(sandboxConfig, channelId, channelDir) {
|
|
|
292
292
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
293
293
|
},
|
|
294
294
|
stopReason: "stop",
|
|
295
|
+
errorMessage: undefined,
|
|
295
296
|
};
|
|
296
297
|
// Subscribe to events ONCE
|
|
297
298
|
session.subscribe(async (event) => {
|
|
@@ -354,6 +355,9 @@ function createRunner(sandboxConfig, channelId, channelDir) {
|
|
|
354
355
|
if (assistantMsg.stopReason) {
|
|
355
356
|
runState.stopReason = assistantMsg.stopReason;
|
|
356
357
|
}
|
|
358
|
+
if (assistantMsg.errorMessage) {
|
|
359
|
+
runState.errorMessage = assistantMsg.errorMessage;
|
|
360
|
+
}
|
|
357
361
|
if (assistantMsg.usage) {
|
|
358
362
|
runState.totalUsage.input += assistantMsg.usage.input;
|
|
359
363
|
runState.totalUsage.output += assistantMsg.usage.output;
|
|
@@ -461,6 +465,7 @@ function createRunner(sandboxConfig, channelId, channelDir) {
|
|
|
461
465
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
462
466
|
};
|
|
463
467
|
runState.stopReason = "stop";
|
|
468
|
+
runState.errorMessage = undefined;
|
|
464
469
|
// Create queue for this run
|
|
465
470
|
let queueChain = Promise.resolve();
|
|
466
471
|
runState.queue = {
|
|
@@ -509,23 +514,36 @@ function createRunner(sandboxConfig, channelId, channelDir) {
|
|
|
509
514
|
await session.prompt(userMessage);
|
|
510
515
|
// Wait for queued messages
|
|
511
516
|
await queueChain;
|
|
512
|
-
//
|
|
513
|
-
|
|
514
|
-
const lastAssistant = messages.filter((m) => m.role === "assistant").pop();
|
|
515
|
-
const finalText = lastAssistant?.content
|
|
516
|
-
.filter((c) => c.type === "text")
|
|
517
|
-
.map((c) => c.text)
|
|
518
|
-
.join("\n") || "";
|
|
519
|
-
if (finalText.trim()) {
|
|
517
|
+
// Handle error case - update main message and post error to thread
|
|
518
|
+
if (runState.stopReason === "error" && runState.errorMessage) {
|
|
520
519
|
try {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
: finalText;
|
|
524
|
-
await ctx.replaceMessage(mainText);
|
|
520
|
+
await ctx.replaceMessage("_Sorry, something went wrong_");
|
|
521
|
+
await ctx.respondInThread(`_Error: ${runState.errorMessage}_`);
|
|
525
522
|
}
|
|
526
523
|
catch (err) {
|
|
527
524
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
528
|
-
log.logWarning("Failed to
|
|
525
|
+
log.logWarning("Failed to post error message", errMsg);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
// Final message update
|
|
530
|
+
const messages = session.messages;
|
|
531
|
+
const lastAssistant = messages.filter((m) => m.role === "assistant").pop();
|
|
532
|
+
const finalText = lastAssistant?.content
|
|
533
|
+
.filter((c) => c.type === "text")
|
|
534
|
+
.map((c) => c.text)
|
|
535
|
+
.join("\n") || "";
|
|
536
|
+
if (finalText.trim()) {
|
|
537
|
+
try {
|
|
538
|
+
const mainText = finalText.length > SLACK_MAX_LENGTH
|
|
539
|
+
? finalText.substring(0, SLACK_MAX_LENGTH - 50) + "\n\n_(see thread for full response)_"
|
|
540
|
+
: finalText;
|
|
541
|
+
await ctx.replaceMessage(mainText);
|
|
542
|
+
}
|
|
543
|
+
catch (err) {
|
|
544
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
545
|
+
log.logWarning("Failed to replace message with final text", errMsg);
|
|
546
|
+
}
|
|
529
547
|
}
|
|
530
548
|
}
|
|
531
549
|
// Log usage summary with context info
|
|
@@ -551,7 +569,7 @@ function createRunner(sandboxConfig, channelId, channelDir) {
|
|
|
551
569
|
runState.ctx = null;
|
|
552
570
|
runState.logCtx = null;
|
|
553
571
|
runState.queue = null;
|
|
554
|
-
return { stopReason: runState.stopReason };
|
|
572
|
+
return { stopReason: runState.stopReason, errorMessage: runState.errorMessage };
|
|
555
573
|
},
|
|
556
574
|
abort() {
|
|
557
575
|
session.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 { AgentSession, messageTransformer } from \"@mariozechner/pi-coding-agent\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { mkdir, writeFile } from \"fs/promises\";\nimport { join } from \"path\";\nimport { MomSessionManager, MomSettingsManager } from \"./context.js\";\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 - TODO: make configurable (issue #63)\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\ttsCounter++;\n\t} else {\n\t\tlastTsMs = now;\n\t\ttsCounter = 0;\n\t}\n\tconst seconds = Math.floor(now / 1000);\n\tconst micros = (now % 1000) * 1000 + tsCounter;\n\treturn `${seconds}.${micros.toString().padStart(6, \"0\")}`;\n}\n\nexport interface PendingMessage {\n\tuserName: string;\n\ttext: string;\n\tattachments: { local: string }[];\n\ttimestamp: number;\n}\n\nexport interface AgentRunner {\n\trun(ctx: SlackContext, store: ChannelStore, pendingMessages?: PendingMessage[]): 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 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\treturn `You are mom, a Slack bot assistant. Be concise. No emojis.\n\n## Context\n- For current date/time, use: date\n- You have access to previous conversation context including tool results from prior turns.\n- For older history beyond your context, search log.jsonl (contains user messages and your final responses, but not tool results).\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 # Message history (no tool results)\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## System Configuration Log\nMaintain ${workspacePath}/SYSTEM.md to log all environment modifications:\n- Installed packages (apk add, npm install, pip install)\n- Environment variables set\n- Config files modified (~/.gitconfig, cron jobs, etc.)\n- Skill dependencies installed\n\nUpdate this file whenever you modify the environment. On fresh container, read it first to restore your setup.\n\n## Log Queries (for older history)\nFormat: \\`{\"date\":\"...\",\"ts\":\"...\",\"user\":\"...\",\"userName\":\"...\",\"text\":\"...\",\"isBot\":false}\\`\nThe log contains user messages and your final responses (not tool calls/results).\n${isDocker ? \"Install jq: apk add jq\" : \"\"}\n\n\\`\\`\\`bash\n# Recent messages\ntail -30 log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Search for specific topic\ngrep -i \"topic\" log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Messages from specific user\ngrep '\"userName\":\"mario\"' log.jsonl | tail -20 | jq -c '{date: .date[0:19], text}'\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\tif (typeof result === \"string\") {\n\t\treturn result;\n\t}\n\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\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\tif (key === \"label\") continue;\n\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\tif (key === \"offset\" || key === \"limit\") continue;\n\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\n// Cache runners per channel\nconst channelRunners = new Map<string, AgentRunner>();\n\n/**\n * Get or create an AgentRunner for a channel.\n * Runners are cached - one per channel, persistent across messages.\n */\nexport function getOrCreateRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst existing = channelRunners.get(channelId);\n\tif (existing) return existing;\n\n\tconst runner = createRunner(sandboxConfig, channelId, channelDir);\n\tchannelRunners.set(channelId, runner);\n\treturn runner;\n}\n\n/**\n * Create a new AgentRunner for a channel.\n * Sets up the session and subscribes to events once.\n */\nfunction createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst executor = createExecutor(sandboxConfig);\n\tconst workspacePath = executor.getWorkspacePath(channelDir.replace(`/${channelId}`, \"\"));\n\n\t// Create tools\n\tconst tools = createMomTools(executor);\n\n\t// Initial system prompt (will be updated each run with fresh memory/channels/users)\n\tconst memory = getMemory(channelDir);\n\tconst systemPrompt = buildSystemPrompt(workspacePath, channelId, memory, sandboxConfig, [], []);\n\n\t// Create session manager and settings manager\n\t// Pass model info so new sessions get a header written immediately\n\tconst sessionManager = new MomSessionManager(channelDir, {\n\t\tprovider: model.provider,\n\t\tid: model.id,\n\t\tthinkingLevel: \"off\",\n\t});\n\tconst settingsManager = new MomSettingsManager(join(channelDir, \"..\"));\n\n\t// Create agent\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel,\n\t\t\tthinkingLevel: \"off\",\n\t\t\ttools,\n\t\t},\n\t\tmessageTransformer,\n\t\ttransport: new ProviderTransport({\n\t\t\tgetApiKey: async () => getAnthropicApiKey(),\n\t\t}),\n\t});\n\n\t// Load existing messages\n\tconst loadedSession = sessionManager.loadSession();\n\tif (loadedSession.messages.length > 0) {\n\t\tagent.replaceMessages(loadedSession.messages);\n\t\tlog.logInfo(`[${channelId}] Loaded ${loadedSession.messages.length} messages from context.jsonl`);\n\t}\n\n\t// Create AgentSession wrapper\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager: sessionManager as any,\n\t\tsettingsManager: settingsManager as any,\n\t});\n\n\t// Mutable per-run state - event handler references this\n\tconst runState = {\n\t\tctx: null as SlackContext | null,\n\t\tlogCtx: null as { channelId: string; userName?: string; channelName?: string } | null,\n\t\tqueue: null as {\n\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void;\n\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog?: boolean): void;\n\t\t} | null,\n\t\tpendingTools: new Map<string, { toolName: string; args: unknown; startTime: number }>(),\n\t\ttotalUsage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t};\n\n\t// Subscribe to events ONCE\n\tsession.subscribe(async (event) => {\n\t\t// Skip if no active run\n\t\tif (!runState.ctx || !runState.logCtx || !runState.queue) return;\n\n\t\tconst { ctx, logCtx, queue, pendingTools } = runState;\n\n\t\tif (event.type === \"tool_execution_start\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"tool_execution_start\" };\n\t\t\tconst args = agentEvent.args as { label?: string };\n\t\t\tconst label = args.label || agentEvent.toolName;\n\n\t\t\tpendingTools.set(agentEvent.toolCallId, {\n\t\t\t\ttoolName: agentEvent.toolName,\n\t\t\t\targs: agentEvent.args,\n\t\t\t\tstartTime: Date.now(),\n\t\t\t});\n\n\t\t\tlog.logToolStart(logCtx, agentEvent.toolName, label, agentEvent.args as Record<string, unknown>);\n\t\t\tqueue.enqueue(() => ctx.respond(`_→ ${label}_`, false), \"tool label\");\n\t\t} else if (event.type === \"tool_execution_end\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"tool_execution_end\" };\n\t\t\tconst resultStr = extractToolResultText(agentEvent.result);\n\t\t\tconst pending = pendingTools.get(agentEvent.toolCallId);\n\t\t\tpendingTools.delete(agentEvent.toolCallId);\n\n\t\t\tconst durationMs = pending ? Date.now() - pending.startTime : 0;\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tlog.logToolError(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t} else {\n\t\t\t\tlog.logToolSuccess(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t}\n\n\t\t\t// Post args + result to thread\n\t\t\tconst label = pending?.args ? (pending.args as { label?: string }).label : undefined;\n\t\t\tconst argsFormatted = pending\n\t\t\t\t? formatToolArgsForSlack(agentEvent.toolName, pending.args as Record<string, unknown>)\n\t\t\t\t: \"(args not found)\";\n\t\t\tconst duration = (durationMs / 1000).toFixed(1);\n\t\t\tlet threadMessage = `*${agentEvent.isError ? \"✗\" : \"✓\"} ${agentEvent.toolName}*`;\n\t\t\tif (label) threadMessage += `: ${label}`;\n\t\t\tthreadMessage += ` (${duration}s)\\n`;\n\t\t\tif (argsFormatted) threadMessage += \"```\\n\" + argsFormatted + \"\\n```\\n\";\n\t\t\tthreadMessage += \"*Result:*\\n```\\n\" + resultStr + \"\\n```\";\n\n\t\t\tqueue.enqueueMessage(threadMessage, \"thread\", \"tool result thread\", false);\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tqueue.enqueue(() => ctx.respond(`_Error: ${truncate(resultStr, 200)}_`, false), \"tool error\");\n\t\t\t}\n\t\t} else if (event.type === \"message_start\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"message_start\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tlog.logResponseStart(logCtx);\n\t\t\t}\n\t\t} else if (event.type === \"message_end\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"message_end\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = agentEvent.message as any;\n\n\t\t\t\tif (assistantMsg.stopReason) {\n\t\t\t\t\trunState.stopReason = assistantMsg.stopReason;\n\t\t\t\t}\n\n\t\t\t\tif (assistantMsg.usage) {\n\t\t\t\t\trunState.totalUsage.input += assistantMsg.usage.input;\n\t\t\t\t\trunState.totalUsage.output += assistantMsg.usage.output;\n\t\t\t\t\trunState.totalUsage.cacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\t\trunState.totalUsage.cacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.input += assistantMsg.usage.cost.input;\n\t\t\t\t\trunState.totalUsage.cost.output += assistantMsg.usage.cost.output;\n\t\t\t\t\trunState.totalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;\n\t\t\t\t\trunState.totalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.total += assistantMsg.usage.cost.total;\n\t\t\t\t}\n\n\t\t\t\tconst content = agentEvent.message.content;\n\t\t\t\tconst thinkingParts: string[] = [];\n\t\t\t\tconst textParts: string[] = [];\n\t\t\t\tfor (const part of content) {\n\t\t\t\t\tif (part.type === \"thinking\") {\n\t\t\t\t\t\tthinkingParts.push((part as any).thinking);\n\t\t\t\t\t} else if (part.type === \"text\") {\n\t\t\t\t\t\ttextParts.push((part as any).text);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst text = textParts.join(\"\\n\");\n\n\t\t\t\tfor (const thinking of thinkingParts) {\n\t\t\t\t\tlog.logThinking(logCtx, thinking);\n\t\t\t\t\tqueue.enqueueMessage(`_${thinking}_`, \"main\", \"thinking main\");\n\t\t\t\t\tqueue.enqueueMessage(`_${thinking}_`, \"thread\", \"thinking thread\", false);\n\t\t\t\t}\n\n\t\t\t\tif (text.trim()) {\n\t\t\t\t\tlog.logResponse(logCtx, text);\n\t\t\t\t\tqueue.enqueueMessage(text, \"main\", \"response main\");\n\t\t\t\t\tqueue.enqueueMessage(text, \"thread\", \"response thread\", false);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"auto_compaction_start\") {\n\t\t\tlog.logInfo(`Auto-compaction started (reason: ${(event as any).reason})`);\n\t\t\tqueue.enqueue(() => ctx.respond(\"_Compacting context..._\", false), \"compaction start\");\n\t\t} else if (event.type === \"auto_compaction_end\") {\n\t\t\tconst compEvent = event as any;\n\t\t\tif (compEvent.result) {\n\t\t\t\tlog.logInfo(`Auto-compaction complete: ${compEvent.result.tokensBefore} tokens compacted`);\n\t\t\t} else if (compEvent.aborted) {\n\t\t\t\tlog.logInfo(\"Auto-compaction aborted\");\n\t\t\t}\n\t\t} else if (event.type === \"auto_retry_start\") {\n\t\t\tconst retryEvent = event as any;\n\t\t\tlog.logWarning(`Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})`, retryEvent.errorMessage);\n\t\t\tqueue.enqueue(\n\t\t\t\t() => ctx.respond(`_Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})..._`, false),\n\t\t\t\t\"retry\",\n\t\t\t);\n\t\t}\n\t});\n\n\t// Slack message limit\n\tconst SLACK_MAX_LENGTH = 40000;\n\tconst splitForSlack = (text: string): string[] => {\n\t\tif (text.length <= SLACK_MAX_LENGTH) return [text];\n\t\tconst parts: string[] = [];\n\t\tlet remaining = text;\n\t\tlet partNum = 1;\n\t\twhile (remaining.length > 0) {\n\t\t\tconst chunk = remaining.substring(0, SLACK_MAX_LENGTH - 50);\n\t\t\tremaining = remaining.substring(SLACK_MAX_LENGTH - 50);\n\t\t\tconst suffix = remaining.length > 0 ? `\\n_(continued ${partNum}...)_` : \"\";\n\t\t\tparts.push(chunk + suffix);\n\t\t\tpartNum++;\n\t\t}\n\t\treturn parts;\n\t};\n\n\treturn {\n\t\tasync run(\n\t\t\tctx: SlackContext,\n\t\t\t_store: ChannelStore,\n\t\t\t_pendingMessages?: PendingMessage[],\n\t\t): Promise<{ stopReason: string }> {\n\t\t\t// Ensure channel directory exists\n\t\t\tawait mkdir(channelDir, { recursive: true });\n\n\t\t\t// Reload messages from context.jsonl\n\t\t\t// This picks up any messages synced from log.jsonl before this run\n\t\t\tconst reloadedSession = sessionManager.loadSession();\n\t\t\tif (reloadedSession.messages.length > 0) {\n\t\t\t\tagent.replaceMessages(reloadedSession.messages);\n\t\t\t\tlog.logInfo(`[${channelId}] Reloaded ${reloadedSession.messages.length} messages from context`);\n\t\t\t}\n\n\t\t\t// Update system prompt with fresh memory and channel/user info\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\t\t\tsession.agent.setSystemPrompt(systemPrompt);\n\n\t\t\t// Set up file upload function\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// Reset per-run state\n\t\t\trunState.ctx = ctx;\n\t\t\trunState.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\t\t\trunState.pendingTools.clear();\n\t\t\trunState.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: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t};\n\t\t\trunState.stopReason = \"stop\";\n\n\t\t\t// Create queue for this run\n\t\t\tlet queueChain = Promise.resolve();\n\t\t\trunState.queue = {\n\t\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void {\n\t\t\t\t\tqueueChain = queueChain.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\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\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\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog = 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, doLog) : 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};\n\n\t\t\t// Log context info\n\t\t\tlog.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);\n\t\t\tlog.logInfo(`Channels: ${ctx.channels.length}, Users: ${ctx.users.length}`);\n\n\t\t\t// Build user message with username prefix\n\t\t\t// Format: \"[username]: message\" so LLM knows who's talking\n\t\t\tlet userMessage = `[${ctx.message.userName || \"unknown\"}]: ${ctx.message.text}`;\n\n\t\t\t// Add attachment paths if any (convert to absolute paths in execution environment)\n\t\t\tif (ctx.message.attachments && ctx.message.attachments.length > 0) {\n\t\t\t\tconst attachmentPaths = ctx.message.attachments.map((a) => `${workspacePath}/${a.local}`).join(\"\\n\");\n\t\t\t\tuserMessage += `\\n\\nAttachments:\\n${attachmentPaths}`;\n\t\t\t}\n\n\t\t\t// Debug: write context to last_prompt.jsonl\n\t\t\tconst debugContext = {\n\t\t\t\tsystemPrompt,\n\t\t\t\tmessages: session.messages,\n\t\t\t\tnewUserMessage: userMessage,\n\t\t\t};\n\t\t\tawait writeFile(join(channelDir, \"last_prompt.jsonl\"), JSON.stringify(debugContext, null, 2));\n\n\t\t\tawait session.prompt(userMessage);\n\n\t\t\t// Wait for queued messages\n\t\t\tawait queueChain;\n\n\t\t\t// Final message update\n\t\t\tconst messages = session.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\n\t\t\tif (finalText.trim()) {\n\t\t\t\ttry {\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 with context info\n\t\t\tif (runState.totalUsage.cost.total > 0) {\n\t\t\t\t// Get last non-aborted assistant message for context calculation\n\t\t\t\tconst messages = session.messages;\n\t\t\t\tconst lastAssistantMessage = messages\n\t\t\t\t\t.slice()\n\t\t\t\t\t.reverse()\n\t\t\t\t\t.find((m) => m.role === \"assistant\" && (m as any).stopReason !== \"aborted\") as any;\n\n\t\t\t\tconst contextTokens = lastAssistantMessage\n\t\t\t\t\t? lastAssistantMessage.usage.input +\n\t\t\t\t\t\tlastAssistantMessage.usage.output +\n\t\t\t\t\t\tlastAssistantMessage.usage.cacheRead +\n\t\t\t\t\t\tlastAssistantMessage.usage.cacheWrite\n\t\t\t\t\t: 0;\n\t\t\t\tconst contextWindow = model.contextWindow || 200000;\n\n\t\t\t\tconst summary = log.logUsageSummary(runState.logCtx!, runState.totalUsage, contextTokens, contextWindow);\n\t\t\t\trunState.queue.enqueue(() => ctx.respondInThread(summary), \"usage summary\");\n\t\t\t\tawait queueChain;\n\t\t\t}\n\n\t\t\t// Clear run state\n\t\t\trunState.ctx = null;\n\t\t\trunState.logCtx = null;\n\t\t\trunState.queue = null;\n\n\t\t\treturn { stopReason: runState.stopReason };\n\t\t},\n\n\t\tabort(): void {\n\t\t\tsession.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\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\tif (containerPath.startsWith(\"/workspace/\")) {\n\t\t\treturn join(channelDir, \"..\", containerPath.slice(\"/workspace/\".length));\n\t\t}\n\t}\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 { AgentSession, messageTransformer } from \"@mariozechner/pi-coding-agent\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { mkdir, writeFile } from \"fs/promises\";\nimport { join } from \"path\";\nimport { MomSessionManager, MomSettingsManager } from \"./context.js\";\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 - TODO: make configurable (issue #63)\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\ttsCounter++;\n\t} else {\n\t\tlastTsMs = now;\n\t\ttsCounter = 0;\n\t}\n\tconst seconds = Math.floor(now / 1000);\n\tconst micros = (now % 1000) * 1000 + tsCounter;\n\treturn `${seconds}.${micros.toString().padStart(6, \"0\")}`;\n}\n\nexport interface PendingMessage {\n\tuserName: string;\n\ttext: string;\n\tattachments: { local: string }[];\n\ttimestamp: number;\n}\n\nexport interface AgentRunner {\n\trun(\n\t\tctx: SlackContext,\n\t\tstore: ChannelStore,\n\t\tpendingMessages?: PendingMessage[],\n\t): Promise<{ stopReason: string; errorMessage?: 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 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\treturn `You are mom, a Slack bot assistant. Be concise. No emojis.\n\n## Context\n- For current date/time, use: date\n- You have access to previous conversation context including tool results from prior turns.\n- For older history beyond your context, search log.jsonl (contains user messages and your final responses, but not tool results).\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 # Message history (no tool results)\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## System Configuration Log\nMaintain ${workspacePath}/SYSTEM.md to log all environment modifications:\n- Installed packages (apk add, npm install, pip install)\n- Environment variables set\n- Config files modified (~/.gitconfig, cron jobs, etc.)\n- Skill dependencies installed\n\nUpdate this file whenever you modify the environment. On fresh container, read it first to restore your setup.\n\n## Log Queries (for older history)\nFormat: \\`{\"date\":\"...\",\"ts\":\"...\",\"user\":\"...\",\"userName\":\"...\",\"text\":\"...\",\"isBot\":false}\\`\nThe log contains user messages and your final responses (not tool calls/results).\n${isDocker ? \"Install jq: apk add jq\" : \"\"}\n\n\\`\\`\\`bash\n# Recent messages\ntail -30 log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Search for specific topic\ngrep -i \"topic\" log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Messages from specific user\ngrep '\"userName\":\"mario\"' log.jsonl | tail -20 | jq -c '{date: .date[0:19], text}'\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\tif (typeof result === \"string\") {\n\t\treturn result;\n\t}\n\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\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\tif (key === \"label\") continue;\n\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\tif (key === \"offset\" || key === \"limit\") continue;\n\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\n// Cache runners per channel\nconst channelRunners = new Map<string, AgentRunner>();\n\n/**\n * Get or create an AgentRunner for a channel.\n * Runners are cached - one per channel, persistent across messages.\n */\nexport function getOrCreateRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst existing = channelRunners.get(channelId);\n\tif (existing) return existing;\n\n\tconst runner = createRunner(sandboxConfig, channelId, channelDir);\n\tchannelRunners.set(channelId, runner);\n\treturn runner;\n}\n\n/**\n * Create a new AgentRunner for a channel.\n * Sets up the session and subscribes to events once.\n */\nfunction createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst executor = createExecutor(sandboxConfig);\n\tconst workspacePath = executor.getWorkspacePath(channelDir.replace(`/${channelId}`, \"\"));\n\n\t// Create tools\n\tconst tools = createMomTools(executor);\n\n\t// Initial system prompt (will be updated each run with fresh memory/channels/users)\n\tconst memory = getMemory(channelDir);\n\tconst systemPrompt = buildSystemPrompt(workspacePath, channelId, memory, sandboxConfig, [], []);\n\n\t// Create session manager and settings manager\n\t// Pass model info so new sessions get a header written immediately\n\tconst sessionManager = new MomSessionManager(channelDir, {\n\t\tprovider: model.provider,\n\t\tid: model.id,\n\t\tthinkingLevel: \"off\",\n\t});\n\tconst settingsManager = new MomSettingsManager(join(channelDir, \"..\"));\n\n\t// Create agent\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel,\n\t\t\tthinkingLevel: \"off\",\n\t\t\ttools,\n\t\t},\n\t\tmessageTransformer,\n\t\ttransport: new ProviderTransport({\n\t\t\tgetApiKey: async () => getAnthropicApiKey(),\n\t\t}),\n\t});\n\n\t// Load existing messages\n\tconst loadedSession = sessionManager.loadSession();\n\tif (loadedSession.messages.length > 0) {\n\t\tagent.replaceMessages(loadedSession.messages);\n\t\tlog.logInfo(`[${channelId}] Loaded ${loadedSession.messages.length} messages from context.jsonl`);\n\t}\n\n\t// Create AgentSession wrapper\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager: sessionManager as any,\n\t\tsettingsManager: settingsManager as any,\n\t});\n\n\t// Mutable per-run state - event handler references this\n\tconst runState = {\n\t\tctx: null as SlackContext | null,\n\t\tlogCtx: null as { channelId: string; userName?: string; channelName?: string } | null,\n\t\tqueue: null as {\n\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void;\n\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog?: boolean): void;\n\t\t} | null,\n\t\tpendingTools: new Map<string, { toolName: string; args: unknown; startTime: number }>(),\n\t\ttotalUsage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t\terrorMessage: undefined as string | undefined,\n\t};\n\n\t// Subscribe to events ONCE\n\tsession.subscribe(async (event) => {\n\t\t// Skip if no active run\n\t\tif (!runState.ctx || !runState.logCtx || !runState.queue) return;\n\n\t\tconst { ctx, logCtx, queue, pendingTools } = runState;\n\n\t\tif (event.type === \"tool_execution_start\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"tool_execution_start\" };\n\t\t\tconst args = agentEvent.args as { label?: string };\n\t\t\tconst label = args.label || agentEvent.toolName;\n\n\t\t\tpendingTools.set(agentEvent.toolCallId, {\n\t\t\t\ttoolName: agentEvent.toolName,\n\t\t\t\targs: agentEvent.args,\n\t\t\t\tstartTime: Date.now(),\n\t\t\t});\n\n\t\t\tlog.logToolStart(logCtx, agentEvent.toolName, label, agentEvent.args as Record<string, unknown>);\n\t\t\tqueue.enqueue(() => ctx.respond(`_→ ${label}_`, false), \"tool label\");\n\t\t} else if (event.type === \"tool_execution_end\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"tool_execution_end\" };\n\t\t\tconst resultStr = extractToolResultText(agentEvent.result);\n\t\t\tconst pending = pendingTools.get(agentEvent.toolCallId);\n\t\t\tpendingTools.delete(agentEvent.toolCallId);\n\n\t\t\tconst durationMs = pending ? Date.now() - pending.startTime : 0;\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tlog.logToolError(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t} else {\n\t\t\t\tlog.logToolSuccess(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t}\n\n\t\t\t// Post args + result to thread\n\t\t\tconst label = pending?.args ? (pending.args as { label?: string }).label : undefined;\n\t\t\tconst argsFormatted = pending\n\t\t\t\t? formatToolArgsForSlack(agentEvent.toolName, pending.args as Record<string, unknown>)\n\t\t\t\t: \"(args not found)\";\n\t\t\tconst duration = (durationMs / 1000).toFixed(1);\n\t\t\tlet threadMessage = `*${agentEvent.isError ? \"✗\" : \"✓\"} ${agentEvent.toolName}*`;\n\t\t\tif (label) threadMessage += `: ${label}`;\n\t\t\tthreadMessage += ` (${duration}s)\\n`;\n\t\t\tif (argsFormatted) threadMessage += \"```\\n\" + argsFormatted + \"\\n```\\n\";\n\t\t\tthreadMessage += \"*Result:*\\n```\\n\" + resultStr + \"\\n```\";\n\n\t\t\tqueue.enqueueMessage(threadMessage, \"thread\", \"tool result thread\", false);\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tqueue.enqueue(() => ctx.respond(`_Error: ${truncate(resultStr, 200)}_`, false), \"tool error\");\n\t\t\t}\n\t\t} else if (event.type === \"message_start\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"message_start\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tlog.logResponseStart(logCtx);\n\t\t\t}\n\t\t} else if (event.type === \"message_end\") {\n\t\t\tconst agentEvent = event as AgentEvent & { type: \"message_end\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = agentEvent.message as any;\n\n\t\t\t\tif (assistantMsg.stopReason) {\n\t\t\t\t\trunState.stopReason = assistantMsg.stopReason;\n\t\t\t\t}\n\t\t\t\tif (assistantMsg.errorMessage) {\n\t\t\t\t\trunState.errorMessage = assistantMsg.errorMessage;\n\t\t\t\t}\n\n\t\t\t\tif (assistantMsg.usage) {\n\t\t\t\t\trunState.totalUsage.input += assistantMsg.usage.input;\n\t\t\t\t\trunState.totalUsage.output += assistantMsg.usage.output;\n\t\t\t\t\trunState.totalUsage.cacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\t\trunState.totalUsage.cacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.input += assistantMsg.usage.cost.input;\n\t\t\t\t\trunState.totalUsage.cost.output += assistantMsg.usage.cost.output;\n\t\t\t\t\trunState.totalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;\n\t\t\t\t\trunState.totalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.total += assistantMsg.usage.cost.total;\n\t\t\t\t}\n\n\t\t\t\tconst content = agentEvent.message.content;\n\t\t\t\tconst thinkingParts: string[] = [];\n\t\t\t\tconst textParts: string[] = [];\n\t\t\t\tfor (const part of content) {\n\t\t\t\t\tif (part.type === \"thinking\") {\n\t\t\t\t\t\tthinkingParts.push((part as any).thinking);\n\t\t\t\t\t} else if (part.type === \"text\") {\n\t\t\t\t\t\ttextParts.push((part as any).text);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst text = textParts.join(\"\\n\");\n\n\t\t\t\tfor (const thinking of thinkingParts) {\n\t\t\t\t\tlog.logThinking(logCtx, thinking);\n\t\t\t\t\tqueue.enqueueMessage(`_${thinking}_`, \"main\", \"thinking main\");\n\t\t\t\t\tqueue.enqueueMessage(`_${thinking}_`, \"thread\", \"thinking thread\", false);\n\t\t\t\t}\n\n\t\t\t\tif (text.trim()) {\n\t\t\t\t\tlog.logResponse(logCtx, text);\n\t\t\t\t\tqueue.enqueueMessage(text, \"main\", \"response main\");\n\t\t\t\t\tqueue.enqueueMessage(text, \"thread\", \"response thread\", false);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"auto_compaction_start\") {\n\t\t\tlog.logInfo(`Auto-compaction started (reason: ${(event as any).reason})`);\n\t\t\tqueue.enqueue(() => ctx.respond(\"_Compacting context..._\", false), \"compaction start\");\n\t\t} else if (event.type === \"auto_compaction_end\") {\n\t\t\tconst compEvent = event as any;\n\t\t\tif (compEvent.result) {\n\t\t\t\tlog.logInfo(`Auto-compaction complete: ${compEvent.result.tokensBefore} tokens compacted`);\n\t\t\t} else if (compEvent.aborted) {\n\t\t\t\tlog.logInfo(\"Auto-compaction aborted\");\n\t\t\t}\n\t\t} else if (event.type === \"auto_retry_start\") {\n\t\t\tconst retryEvent = event as any;\n\t\t\tlog.logWarning(`Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})`, retryEvent.errorMessage);\n\t\t\tqueue.enqueue(\n\t\t\t\t() => ctx.respond(`_Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})..._`, false),\n\t\t\t\t\"retry\",\n\t\t\t);\n\t\t}\n\t});\n\n\t// Slack message limit\n\tconst SLACK_MAX_LENGTH = 40000;\n\tconst splitForSlack = (text: string): string[] => {\n\t\tif (text.length <= SLACK_MAX_LENGTH) return [text];\n\t\tconst parts: string[] = [];\n\t\tlet remaining = text;\n\t\tlet partNum = 1;\n\t\twhile (remaining.length > 0) {\n\t\t\tconst chunk = remaining.substring(0, SLACK_MAX_LENGTH - 50);\n\t\t\tremaining = remaining.substring(SLACK_MAX_LENGTH - 50);\n\t\t\tconst suffix = remaining.length > 0 ? `\\n_(continued ${partNum}...)_` : \"\";\n\t\t\tparts.push(chunk + suffix);\n\t\t\tpartNum++;\n\t\t}\n\t\treturn parts;\n\t};\n\n\treturn {\n\t\tasync run(\n\t\t\tctx: SlackContext,\n\t\t\t_store: ChannelStore,\n\t\t\t_pendingMessages?: PendingMessage[],\n\t\t): Promise<{ stopReason: string; errorMessage?: string }> {\n\t\t\t// Ensure channel directory exists\n\t\t\tawait mkdir(channelDir, { recursive: true });\n\n\t\t\t// Reload messages from context.jsonl\n\t\t\t// This picks up any messages synced from log.jsonl before this run\n\t\t\tconst reloadedSession = sessionManager.loadSession();\n\t\t\tif (reloadedSession.messages.length > 0) {\n\t\t\t\tagent.replaceMessages(reloadedSession.messages);\n\t\t\t\tlog.logInfo(`[${channelId}] Reloaded ${reloadedSession.messages.length} messages from context`);\n\t\t\t}\n\n\t\t\t// Update system prompt with fresh memory and channel/user info\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\t\t\tsession.agent.setSystemPrompt(systemPrompt);\n\n\t\t\t// Set up file upload function\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// Reset per-run state\n\t\t\trunState.ctx = ctx;\n\t\t\trunState.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\t\t\trunState.pendingTools.clear();\n\t\t\trunState.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: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t};\n\t\t\trunState.stopReason = \"stop\";\n\t\t\trunState.errorMessage = undefined;\n\n\t\t\t// Create queue for this run\n\t\t\tlet queueChain = Promise.resolve();\n\t\t\trunState.queue = {\n\t\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void {\n\t\t\t\t\tqueueChain = queueChain.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\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\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\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog = 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, doLog) : 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};\n\n\t\t\t// Log context info\n\t\t\tlog.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);\n\t\t\tlog.logInfo(`Channels: ${ctx.channels.length}, Users: ${ctx.users.length}`);\n\n\t\t\t// Build user message with username prefix\n\t\t\t// Format: \"[username]: message\" so LLM knows who's talking\n\t\t\tlet userMessage = `[${ctx.message.userName || \"unknown\"}]: ${ctx.message.text}`;\n\n\t\t\t// Add attachment paths if any (convert to absolute paths in execution environment)\n\t\t\tif (ctx.message.attachments && ctx.message.attachments.length > 0) {\n\t\t\t\tconst attachmentPaths = ctx.message.attachments.map((a) => `${workspacePath}/${a.local}`).join(\"\\n\");\n\t\t\t\tuserMessage += `\\n\\nAttachments:\\n${attachmentPaths}`;\n\t\t\t}\n\n\t\t\t// Debug: write context to last_prompt.jsonl\n\t\t\tconst debugContext = {\n\t\t\t\tsystemPrompt,\n\t\t\t\tmessages: session.messages,\n\t\t\t\tnewUserMessage: userMessage,\n\t\t\t};\n\t\t\tawait writeFile(join(channelDir, \"last_prompt.jsonl\"), JSON.stringify(debugContext, null, 2));\n\n\t\t\tawait session.prompt(userMessage);\n\n\t\t\t// Wait for queued messages\n\t\t\tawait queueChain;\n\n\t\t\t// Handle error case - update main message and post error to thread\n\t\t\tif (runState.stopReason === \"error\" && runState.errorMessage) {\n\t\t\t\ttry {\n\t\t\t\t\tawait ctx.replaceMessage(\"_Sorry, something went wrong_\");\n\t\t\t\t\tawait ctx.respondInThread(`_Error: ${runState.errorMessage}_`);\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 post error message\", errMsg);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Final message update\n\t\t\t\tconst messages = session.messages;\n\t\t\t\tconst lastAssistant = messages.filter((m) => m.role === \"assistant\").pop();\n\t\t\t\tconst finalText =\n\t\t\t\t\tlastAssistant?.content\n\t\t\t\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t\t\t\t.map((c) => c.text)\n\t\t\t\t\t\t.join(\"\\n\") || \"\";\n\n\t\t\t\tif (finalText.trim()) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst mainText =\n\t\t\t\t\t\t\tfinalText.length > SLACK_MAX_LENGTH\n\t\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\t: finalText;\n\t\t\t\t\t\tawait ctx.replaceMessage(mainText);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\tlog.logWarning(\"Failed to replace message with final text\", errMsg);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Log usage summary with context info\n\t\t\tif (runState.totalUsage.cost.total > 0) {\n\t\t\t\t// Get last non-aborted assistant message for context calculation\n\t\t\t\tconst messages = session.messages;\n\t\t\t\tconst lastAssistantMessage = messages\n\t\t\t\t\t.slice()\n\t\t\t\t\t.reverse()\n\t\t\t\t\t.find((m) => m.role === \"assistant\" && (m as any).stopReason !== \"aborted\") as any;\n\n\t\t\t\tconst contextTokens = lastAssistantMessage\n\t\t\t\t\t? lastAssistantMessage.usage.input +\n\t\t\t\t\t\tlastAssistantMessage.usage.output +\n\t\t\t\t\t\tlastAssistantMessage.usage.cacheRead +\n\t\t\t\t\t\tlastAssistantMessage.usage.cacheWrite\n\t\t\t\t\t: 0;\n\t\t\t\tconst contextWindow = model.contextWindow || 200000;\n\n\t\t\t\tconst summary = log.logUsageSummary(runState.logCtx!, runState.totalUsage, contextTokens, contextWindow);\n\t\t\t\trunState.queue.enqueue(() => ctx.respondInThread(summary), \"usage summary\");\n\t\t\t\tawait queueChain;\n\t\t\t}\n\n\t\t\t// Clear run state\n\t\t\trunState.ctx = null;\n\t\t\trunState.logCtx = null;\n\t\t\trunState.queue = null;\n\n\t\t\treturn { stopReason: runState.stopReason, errorMessage: runState.errorMessage };\n\t\t},\n\n\t\tabort(): void {\n\t\t\tsession.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\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\tif (containerPath.startsWith(\"/workspace/\")) {\n\t\t\treturn join(channelDir, \"..\", containerPath.slice(\"/workspace/\".length));\n\t\t}\n\t}\n\treturn containerPath;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"AA6BA,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuFxF","sourcesContent":["import { LogLevel, WebClient } from \"@slack/web-api\";\n\ninterface Message {\n\tts: string;\n\tuser?: string;\n\ttext?: string;\n\tthread_ts?: string;\n\treply_count?: number;\n\tfiles?: Array<{ name: string; url_private?: string }>;\n}\n\nfunction formatTs(ts: string): string {\n\tconst date = new Date(parseFloat(ts) * 1000);\n\treturn date\n\t\t.toISOString()\n\t\t.replace(\"T\", \" \")\n\t\t.replace(/\\.\\d+Z$/, \"\");\n}\n\nfunction formatMessage(ts: string, user: string, text: string, indent = \"\"): string {\n\tconst prefix = `[${formatTs(ts)}] ${user}: `;\n\tconst lines = text.split(\"\\n\");\n\tconst firstLine = `${indent}${prefix}${lines[0]}`;\n\tif (lines.length === 1) return firstLine;\n\t// All continuation lines get same indent as content start\n\tconst contentIndent = indent + \" \".repeat(prefix.length);\n\treturn [firstLine, ...lines.slice(1).map((l) => contentIndent + l)].join(\"\\n\");\n}\n\nexport async function downloadChannel(channelId: string, botToken: string): Promise<void> {\n\tconst client = new WebClient(botToken, { logLevel: LogLevel.ERROR });\n\n\tconsole.error(`Fetching channel info for ${channelId}...`);\n\n\t// Get channel info\n\tlet channelName = channelId;\n\ttry {\n\t\tconst info = await client.conversations.info({ channel: channelId });\n\t\tchannelName = (info.channel as any)?.name || channelId;\n\t} catch {\n\t\t// DM channels don't have names, that's fine\n\t}\n\n\tconsole.error(`Downloading history for #${channelName} (${channelId})...`);\n\n\t// Fetch all messages\n\tconst messages: Message[] = [];\n\tlet cursor: string | undefined;\n\n\tdo {\n\t\tconst response = await client.conversations.history({\n\t\t\tchannel: channelId,\n\t\t\tlimit: 200,\n\t\t\tcursor,\n\t\t});\n\n\t\tif (response.messages) {\n\t\t\tmessages.push(...(response.messages as Message[]));\n\t\t}\n\n\t\tcursor = response.response_metadata?.next_cursor;\n\t\tconsole.error(` Fetched ${messages.length} messages...`);\n\t} while (cursor);\n\n\t// Reverse to chronological order\n\tmessages.reverse();\n\n\t// Build map of thread replies\n\tconst threadReplies = new Map<string, Message[]>();\n\tconst threadsToFetch = messages.filter((m) => m.reply_count && m.reply_count > 0);\n\n\tconsole.error(`Fetching ${threadsToFetch.length} threads...`);\n\n\tfor (let i = 0; i < threadsToFetch.length; i++) {\n\t\tconst parent = threadsToFetch[i];\n\t\tconsole.error(` Thread ${i + 1}/${threadsToFetch.length} (${parent.reply_count} replies)...`);\n\n\t\tconst replies: Message[] = [];\n\t\tlet threadCursor: string | undefined;\n\n\t\tdo {\n\t\t\tconst response = await client.conversations.replies({\n\t\t\t\tchannel: channelId,\n\t\t\t\tts: parent.ts,\n\t\t\t\tlimit: 200,\n\t\t\t\tcursor: threadCursor,\n\t\t\t});\n\n\t\t\tif (response.messages) {\n\t\t\t\t// Skip the first message (it's the parent)\n\t\t\t\treplies.push(...(response.messages as Message[]).slice(1));\n\t\t\t}\n\n\t\t\tthreadCursor = response.response_metadata?.next_cursor;\n\t\t} while (threadCursor);\n\n\t\tthreadReplies.set(parent.ts, replies);\n\t}\n\n\t// Output messages with thread replies interleaved\n\tlet totalReplies = 0;\n\tfor (const msg of messages) {\n\t\t// Output the message\n\t\tconsole.log(formatMessage(msg.ts, msg.user || \"unknown\", msg.text || \"\"));\n\n\t\t// Output thread replies right after parent (indented)\n\t\tconst replies = threadReplies.get(msg.ts);\n\t\tif (replies) {\n\t\t\tfor (const reply of replies) {\n\t\t\t\tconsole.log(formatMessage(reply.ts, reply.user || \"unknown\", reply.text || \"\", \" \"));\n\t\t\t\ttotalReplies++;\n\t\t\t}\n\t\t}\n\t}\n\n\tconsole.error(`Done! ${messages.length} messages, ${totalReplies} thread replies`);\n}\n"]}
|
package/dist/download.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { LogLevel, WebClient } from "@slack/web-api";
|
|
2
|
+
function formatTs(ts) {
|
|
3
|
+
const date = new Date(parseFloat(ts) * 1000);
|
|
4
|
+
return date
|
|
5
|
+
.toISOString()
|
|
6
|
+
.replace("T", " ")
|
|
7
|
+
.replace(/\.\d+Z$/, "");
|
|
8
|
+
}
|
|
9
|
+
function formatMessage(ts, user, text, indent = "") {
|
|
10
|
+
const prefix = `[${formatTs(ts)}] ${user}: `;
|
|
11
|
+
const lines = text.split("\n");
|
|
12
|
+
const firstLine = `${indent}${prefix}${lines[0]}`;
|
|
13
|
+
if (lines.length === 1)
|
|
14
|
+
return firstLine;
|
|
15
|
+
// All continuation lines get same indent as content start
|
|
16
|
+
const contentIndent = indent + " ".repeat(prefix.length);
|
|
17
|
+
return [firstLine, ...lines.slice(1).map((l) => contentIndent + l)].join("\n");
|
|
18
|
+
}
|
|
19
|
+
export async function downloadChannel(channelId, botToken) {
|
|
20
|
+
const client = new WebClient(botToken, { logLevel: LogLevel.ERROR });
|
|
21
|
+
console.error(`Fetching channel info for ${channelId}...`);
|
|
22
|
+
// Get channel info
|
|
23
|
+
let channelName = channelId;
|
|
24
|
+
try {
|
|
25
|
+
const info = await client.conversations.info({ channel: channelId });
|
|
26
|
+
channelName = info.channel?.name || channelId;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// DM channels don't have names, that's fine
|
|
30
|
+
}
|
|
31
|
+
console.error(`Downloading history for #${channelName} (${channelId})...`);
|
|
32
|
+
// Fetch all messages
|
|
33
|
+
const messages = [];
|
|
34
|
+
let cursor;
|
|
35
|
+
do {
|
|
36
|
+
const response = await client.conversations.history({
|
|
37
|
+
channel: channelId,
|
|
38
|
+
limit: 200,
|
|
39
|
+
cursor,
|
|
40
|
+
});
|
|
41
|
+
if (response.messages) {
|
|
42
|
+
messages.push(...response.messages);
|
|
43
|
+
}
|
|
44
|
+
cursor = response.response_metadata?.next_cursor;
|
|
45
|
+
console.error(` Fetched ${messages.length} messages...`);
|
|
46
|
+
} while (cursor);
|
|
47
|
+
// Reverse to chronological order
|
|
48
|
+
messages.reverse();
|
|
49
|
+
// Build map of thread replies
|
|
50
|
+
const threadReplies = new Map();
|
|
51
|
+
const threadsToFetch = messages.filter((m) => m.reply_count && m.reply_count > 0);
|
|
52
|
+
console.error(`Fetching ${threadsToFetch.length} threads...`);
|
|
53
|
+
for (let i = 0; i < threadsToFetch.length; i++) {
|
|
54
|
+
const parent = threadsToFetch[i];
|
|
55
|
+
console.error(` Thread ${i + 1}/${threadsToFetch.length} (${parent.reply_count} replies)...`);
|
|
56
|
+
const replies = [];
|
|
57
|
+
let threadCursor;
|
|
58
|
+
do {
|
|
59
|
+
const response = await client.conversations.replies({
|
|
60
|
+
channel: channelId,
|
|
61
|
+
ts: parent.ts,
|
|
62
|
+
limit: 200,
|
|
63
|
+
cursor: threadCursor,
|
|
64
|
+
});
|
|
65
|
+
if (response.messages) {
|
|
66
|
+
// Skip the first message (it's the parent)
|
|
67
|
+
replies.push(...response.messages.slice(1));
|
|
68
|
+
}
|
|
69
|
+
threadCursor = response.response_metadata?.next_cursor;
|
|
70
|
+
} while (threadCursor);
|
|
71
|
+
threadReplies.set(parent.ts, replies);
|
|
72
|
+
}
|
|
73
|
+
// Output messages with thread replies interleaved
|
|
74
|
+
let totalReplies = 0;
|
|
75
|
+
for (const msg of messages) {
|
|
76
|
+
// Output the message
|
|
77
|
+
console.log(formatMessage(msg.ts, msg.user || "unknown", msg.text || ""));
|
|
78
|
+
// Output thread replies right after parent (indented)
|
|
79
|
+
const replies = threadReplies.get(msg.ts);
|
|
80
|
+
if (replies) {
|
|
81
|
+
for (const reply of replies) {
|
|
82
|
+
console.log(formatMessage(reply.ts, reply.user || "unknown", reply.text || "", " "));
|
|
83
|
+
totalReplies++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
console.error(`Done! ${messages.length} messages, ${totalReplies} thread replies`);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=download.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download.js","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAWrD,SAAS,QAAQ,CAAC,EAAU,EAAU;IACrC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IAC7C,OAAO,IAAI;SACT,WAAW,EAAE;SACb,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC;SACjB,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;AAAA,CACzB;AAED,SAAS,aAAa,CAAC,EAAU,EAAE,IAAY,EAAE,IAAY,EAAE,MAAM,GAAG,EAAE,EAAU;IACnF,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,EAAE,CAAC,KAAK,IAAI,IAAI,CAAC;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,SAAS,GAAG,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAClD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,0DAA0D;IAC1D,MAAM,aAAa,GAAG,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACzD,OAAO,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CAC/E;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,SAAiB,EAAE,QAAgB,EAAiB;IACzF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAErE,OAAO,CAAC,KAAK,CAAC,6BAA6B,SAAS,KAAK,CAAC,CAAC;IAE3D,mBAAmB;IACnB,IAAI,WAAW,GAAG,SAAS,CAAC;IAC5B,IAAI,CAAC;QACJ,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;QACrE,WAAW,GAAI,IAAI,CAAC,OAAe,EAAE,IAAI,IAAI,SAAS,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACR,4CAA4C;IAC7C,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,4BAA4B,WAAW,KAAK,SAAS,MAAM,CAAC,CAAC;IAE3E,qBAAqB;IACrB,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,IAAI,MAA0B,CAAC;IAE/B,GAAG,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC;YACnD,OAAO,EAAE,SAAS;YAClB,KAAK,EAAE,GAAG;YACV,MAAM;SACN,CAAC,CAAC;QAEH,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACvB,QAAQ,CAAC,IAAI,CAAC,GAAI,QAAQ,CAAC,QAAsB,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,GAAG,QAAQ,CAAC,iBAAiB,EAAE,WAAW,CAAC;QACjD,OAAO,CAAC,KAAK,CAAC,aAAa,QAAQ,CAAC,MAAM,cAAc,CAAC,CAAC;IAC3D,CAAC,QAAQ,MAAM,EAAE;IAEjB,iCAAiC;IACjC,QAAQ,CAAC,OAAO,EAAE,CAAC;IAEnB,8BAA8B;IAC9B,MAAM,aAAa,GAAG,IAAI,GAAG,EAAqB,CAAC;IACnD,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;IAElF,OAAO,CAAC,KAAK,CAAC,YAAY,cAAc,CAAC,MAAM,aAAa,CAAC,CAAC;IAE9D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;QACjC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,cAAc,CAAC,MAAM,KAAK,MAAM,CAAC,WAAW,cAAc,CAAC,CAAC;QAE/F,MAAM,OAAO,GAAc,EAAE,CAAC;QAC9B,IAAI,YAAgC,CAAC;QAErC,GAAG,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC;gBACnD,OAAO,EAAE,SAAS;gBAClB,EAAE,EAAE,MAAM,CAAC,EAAE;gBACb,KAAK,EAAE,GAAG;gBACV,MAAM,EAAE,YAAY;aACpB,CAAC,CAAC;YAEH,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;gBACvB,2CAA2C;gBAC3C,OAAO,CAAC,IAAI,CAAC,GAAI,QAAQ,CAAC,QAAsB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5D,CAAC;YAED,YAAY,GAAG,QAAQ,CAAC,iBAAiB,EAAE,WAAW,CAAC;QACxD,CAAC,QAAQ,YAAY,EAAE;QAEvB,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC;IAED,kDAAkD;IAClD,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC5B,qBAAqB;QACrB,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,IAAI,SAAS,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;QAE1E,sDAAsD;QACtD,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1C,IAAI,OAAO,EAAE,CAAC;YACb,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC7B,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,IAAI,SAAS,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;gBACtF,YAAY,EAAE,CAAC;YAChB,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,SAAS,QAAQ,CAAC,MAAM,cAAc,YAAY,iBAAiB,CAAC,CAAC;AAAA,CACnF","sourcesContent":["import { LogLevel, WebClient } from \"@slack/web-api\";\n\ninterface Message {\n\tts: string;\n\tuser?: string;\n\ttext?: string;\n\tthread_ts?: string;\n\treply_count?: number;\n\tfiles?: Array<{ name: string; url_private?: string }>;\n}\n\nfunction formatTs(ts: string): string {\n\tconst date = new Date(parseFloat(ts) * 1000);\n\treturn date\n\t\t.toISOString()\n\t\t.replace(\"T\", \" \")\n\t\t.replace(/\\.\\d+Z$/, \"\");\n}\n\nfunction formatMessage(ts: string, user: string, text: string, indent = \"\"): string {\n\tconst prefix = `[${formatTs(ts)}] ${user}: `;\n\tconst lines = text.split(\"\\n\");\n\tconst firstLine = `${indent}${prefix}${lines[0]}`;\n\tif (lines.length === 1) return firstLine;\n\t// All continuation lines get same indent as content start\n\tconst contentIndent = indent + \" \".repeat(prefix.length);\n\treturn [firstLine, ...lines.slice(1).map((l) => contentIndent + l)].join(\"\\n\");\n}\n\nexport async function downloadChannel(channelId: string, botToken: string): Promise<void> {\n\tconst client = new WebClient(botToken, { logLevel: LogLevel.ERROR });\n\n\tconsole.error(`Fetching channel info for ${channelId}...`);\n\n\t// Get channel info\n\tlet channelName = channelId;\n\ttry {\n\t\tconst info = await client.conversations.info({ channel: channelId });\n\t\tchannelName = (info.channel as any)?.name || channelId;\n\t} catch {\n\t\t// DM channels don't have names, that's fine\n\t}\n\n\tconsole.error(`Downloading history for #${channelName} (${channelId})...`);\n\n\t// Fetch all messages\n\tconst messages: Message[] = [];\n\tlet cursor: string | undefined;\n\n\tdo {\n\t\tconst response = await client.conversations.history({\n\t\t\tchannel: channelId,\n\t\t\tlimit: 200,\n\t\t\tcursor,\n\t\t});\n\n\t\tif (response.messages) {\n\t\t\tmessages.push(...(response.messages as Message[]));\n\t\t}\n\n\t\tcursor = response.response_metadata?.next_cursor;\n\t\tconsole.error(` Fetched ${messages.length} messages...`);\n\t} while (cursor);\n\n\t// Reverse to chronological order\n\tmessages.reverse();\n\n\t// Build map of thread replies\n\tconst threadReplies = new Map<string, Message[]>();\n\tconst threadsToFetch = messages.filter((m) => m.reply_count && m.reply_count > 0);\n\n\tconsole.error(`Fetching ${threadsToFetch.length} threads...`);\n\n\tfor (let i = 0; i < threadsToFetch.length; i++) {\n\t\tconst parent = threadsToFetch[i];\n\t\tconsole.error(` Thread ${i + 1}/${threadsToFetch.length} (${parent.reply_count} replies)...`);\n\n\t\tconst replies: Message[] = [];\n\t\tlet threadCursor: string | undefined;\n\n\t\tdo {\n\t\t\tconst response = await client.conversations.replies({\n\t\t\t\tchannel: channelId,\n\t\t\t\tts: parent.ts,\n\t\t\t\tlimit: 200,\n\t\t\t\tcursor: threadCursor,\n\t\t\t});\n\n\t\t\tif (response.messages) {\n\t\t\t\t// Skip the first message (it's the parent)\n\t\t\t\treplies.push(...(response.messages as Message[]).slice(1));\n\t\t\t}\n\n\t\t\tthreadCursor = response.response_metadata?.next_cursor;\n\t\t} while (threadCursor);\n\n\t\tthreadReplies.set(parent.ts, replies);\n\t}\n\n\t// Output messages with thread replies interleaved\n\tlet totalReplies = 0;\n\tfor (const msg of messages) {\n\t\t// Output the message\n\t\tconsole.log(formatMessage(msg.ts, msg.user || \"unknown\", msg.text || \"\"));\n\n\t\t// Output thread replies right after parent (indented)\n\t\tconst replies = threadReplies.get(msg.ts);\n\t\tif (replies) {\n\t\t\tfor (const reply of replies) {\n\t\t\t\tconsole.log(formatMessage(reply.ts, reply.user || \"unknown\", reply.text || \"\", \" \"));\n\t\t\t\ttotalReplies++;\n\t\t\t}\n\t\t}\n\t}\n\n\tconsole.error(`Done! ${messages.length} messages, ${totalReplies} thread replies`);\n}\n"]}
|
package/dist/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"","sourcesContent":["#!/usr/bin/env node\n\nimport { join, resolve } from \"path\";\nimport { type AgentRunner, getOrCreateRunner } from \"./agent.js\";\nimport { syncLogToContext } from \"./context.js\";\nimport * as log from \"./log.js\";\nimport { parseSandboxArg, type SandboxConfig, validateSandbox } from \"./sandbox.js\";\nimport { type MomHandler, type SlackBot, SlackBot as SlackBotClass, type SlackEvent } from \"./slack.js\";\nimport { ChannelStore } from \"./store.js\";\n\n// ============================================================================\n// Config\n// ============================================================================\n\nconst MOM_SLACK_APP_TOKEN = process.env.MOM_SLACK_APP_TOKEN;\nconst MOM_SLACK_BOT_TOKEN = process.env.MOM_SLACK_BOT_TOKEN;\nconst ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;\nconst ANTHROPIC_OAUTH_TOKEN = process.env.ANTHROPIC_OAUTH_TOKEN;\n\nfunction parseArgs(): { workingDir: string; sandbox: SandboxConfig } {\n\tconst args = process.argv.slice(2);\n\tlet sandbox: SandboxConfig = { type: \"host\" };\n\tlet workingDir: string | undefined;\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\t\tif (arg.startsWith(\"--sandbox=\")) {\n\t\t\tsandbox = parseSandboxArg(arg.slice(\"--sandbox=\".length));\n\t\t} else if (arg === \"--sandbox\") {\n\t\t\tsandbox = parseSandboxArg(args[++i] || \"\");\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tworkingDir = arg;\n\t\t}\n\t}\n\n\tif (!workingDir) {\n\t\tconsole.error(\"Usage: mom [--sandbox=host|docker:<name>] <working-directory>\");\n\t\tprocess.exit(1);\n\t}\n\n\treturn { workingDir: resolve(workingDir), sandbox };\n}\n\nconst { workingDir, sandbox } = parseArgs();\n\nif (!MOM_SLACK_APP_TOKEN || !MOM_SLACK_BOT_TOKEN || (!ANTHROPIC_API_KEY && !ANTHROPIC_OAUTH_TOKEN)) {\n\tconsole.error(\"Missing env: MOM_SLACK_APP_TOKEN, MOM_SLACK_BOT_TOKEN, ANTHROPIC_API_KEY or ANTHROPIC_OAUTH_TOKEN\");\n\tprocess.exit(1);\n}\n\nawait validateSandbox(sandbox);\n\n// ============================================================================\n// State (per channel)\n// ============================================================================\n\ninterface ChannelState {\n\trunning: boolean;\n\trunner: AgentRunner;\n\tstore: ChannelStore;\n\tstopRequested: boolean;\n\tstopMessageTs?: string;\n}\n\nconst channelStates = new Map<string, ChannelState>();\n\nfunction getState(channelId: string): ChannelState {\n\tlet state = channelStates.get(channelId);\n\tif (!state) {\n\t\tconst channelDir = join(workingDir, channelId);\n\t\tstate = {\n\t\t\trunning: false,\n\t\t\trunner: getOrCreateRunner(sandbox, channelId, channelDir),\n\t\t\tstore: new ChannelStore({ workingDir, botToken: MOM_SLACK_BOT_TOKEN! }),\n\t\t\tstopRequested: false,\n\t\t};\n\t\tchannelStates.set(channelId, state);\n\t}\n\treturn state;\n}\n\n// ============================================================================\n// Create SlackContext adapter\n// ============================================================================\n\nfunction createSlackContext(event: SlackEvent, slack: SlackBot, state: ChannelState) {\n\tlet messageTs: string | null = null;\n\tlet accumulatedText = \"\";\n\tlet isWorking = true;\n\tconst workingIndicator = \" ...\";\n\tlet updatePromise = Promise.resolve();\n\n\tconst user = slack.getUser(event.user);\n\n\treturn {\n\t\tmessage: {\n\t\t\ttext: event.text,\n\t\t\trawText: event.text,\n\t\t\tuser: event.user,\n\t\t\tuserName: user?.userName,\n\t\t\tchannel: event.channel,\n\t\t\tts: event.ts,\n\t\t\tattachments: (event.attachments || []).map((a) => ({ local: a.local })),\n\t\t},\n\t\tchannelName: slack.getChannel(event.channel)?.name,\n\t\tstore: state.store,\n\t\tchannels: slack.getAllChannels().map((c) => ({ id: c.id, name: c.name })),\n\t\tusers: slack.getAllUsers().map((u) => ({ id: u.id, userName: u.userName, displayName: u.displayName })),\n\n\t\trespond: async (text: string, shouldLog = true) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\taccumulatedText = accumulatedText ? accumulatedText + \"\\n\" + text : text;\n\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tawait slack.updateMessage(event.channel, messageTs, displayText);\n\t\t\t\t} else {\n\t\t\t\t\tmessageTs = await slack.postMessage(event.channel, displayText);\n\t\t\t\t}\n\n\t\t\t\tif (shouldLog && messageTs) {\n\t\t\t\t\tslack.logBotResponse(event.channel, text, messageTs);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\n\t\treplaceMessage: async (text: string) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\taccumulatedText = text;\n\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tawait slack.updateMessage(event.channel, messageTs, displayText);\n\t\t\t\t} else {\n\t\t\t\t\tmessageTs = await slack.postMessage(event.channel, displayText);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\n\t\trespondInThread: async (text: string) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tawait slack.postInThread(event.channel, messageTs, text);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\n\t\tsetTyping: async (isTyping: boolean) => {\n\t\t\tif (isTyping && !messageTs) {\n\t\t\t\taccumulatedText = \"_Thinking_\";\n\t\t\t\tmessageTs = await slack.postMessage(event.channel, accumulatedText + workingIndicator);\n\t\t\t}\n\t\t},\n\n\t\tuploadFile: async (filePath: string, title?: string) => {\n\t\t\tawait slack.uploadFile(event.channel, filePath, title);\n\t\t},\n\n\t\tsetWorking: async (working: boolean) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\tisWorking = working;\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\t\t\t\t\tawait slack.updateMessage(event.channel, messageTs, displayText);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\t};\n}\n\n// ============================================================================\n// Handler\n// ============================================================================\n\nconst handler: MomHandler = {\n\tisRunning(channelId: string): boolean {\n\t\tconst state = channelStates.get(channelId);\n\t\treturn state?.running ?? false;\n\t},\n\n\tasync handleStop(channelId: string, slack: SlackBot): Promise<void> {\n\t\tconst state = channelStates.get(channelId);\n\t\tif (state?.running) {\n\t\t\tstate.stopRequested = true;\n\t\t\tstate.runner.abort();\n\t\t\tconst ts = await slack.postMessage(channelId, \"_Stopping..._\");\n\t\t\tstate.stopMessageTs = ts; // Save for updating later\n\t\t} else {\n\t\t\tawait slack.postMessage(channelId, \"_Nothing running_\");\n\t\t}\n\t},\n\n\tasync handleEvent(event: SlackEvent, slack: SlackBot): Promise<void> {\n\t\tconst state = getState(event.channel);\n\t\tconst channelDir = join(workingDir, event.channel);\n\n\t\t// Start run\n\t\tstate.running = true;\n\t\tstate.stopRequested = false;\n\n\t\tlog.logInfo(`[${event.channel}] Starting run: ${event.text.substring(0, 50)}`);\n\n\t\ttry {\n\t\t\t// SYNC context from log.jsonl BEFORE processing\n\t\t\t// This adds any messages that were logged while mom wasn't running\n\t\t\t// Exclude messages >= current ts (will be handled by agent)\n\t\t\tconst syncedCount = syncLogToContext(channelDir, event.ts);\n\t\t\tif (syncedCount > 0) {\n\t\t\t\tlog.logInfo(`[${event.channel}] Synced ${syncedCount} messages from log to context`);\n\t\t\t}\n\n\t\t\t// Create context adapter\n\t\t\tconst ctx = createSlackContext(event, slack, state);\n\n\t\t\t// Run the agent\n\t\t\tawait ctx.setTyping(true);\n\t\t\tawait ctx.setWorking(true);\n\t\t\tconst result = await state.runner.run(ctx as any, state.store);\n\t\t\tawait ctx.setWorking(false);\n\n\t\t\tif (result.stopReason === \"aborted\" && state.stopRequested) {\n\t\t\t\tif (state.stopMessageTs) {\n\t\t\t\t\tawait slack.updateMessage(event.channel, state.stopMessageTs, \"_Stopped_\");\n\t\t\t\t\tstate.stopMessageTs = undefined;\n\t\t\t\t} else {\n\t\t\t\t\tawait slack.postMessage(event.channel, \"_Stopped_\");\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tlog.logWarning(`[${event.channel}] Run error`, err instanceof Error ? err.message : String(err));\n\t\t} finally {\n\t\t\tstate.running = false;\n\t\t}\n\t},\n};\n\n// ============================================================================\n// Start\n// ============================================================================\n\nlog.logStartup(workingDir, sandbox.type === \"host\" ? \"host\" : `docker:${sandbox.container}`);\n\n// Shared store for attachment downloads (also used per-channel in getState)\nconst sharedStore = new ChannelStore({ workingDir, botToken: MOM_SLACK_BOT_TOKEN! });\n\nconst bot = new SlackBotClass(handler, {\n\tappToken: MOM_SLACK_APP_TOKEN,\n\tbotToken: MOM_SLACK_BOT_TOKEN,\n\tworkingDir,\n\tstore: sharedStore,\n});\n\nbot.start();\n"]}
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"","sourcesContent":["#!/usr/bin/env node\n\nimport { join, resolve } from \"path\";\nimport { type AgentRunner, getOrCreateRunner } from \"./agent.js\";\nimport { syncLogToContext } from \"./context.js\";\nimport { downloadChannel } from \"./download.js\";\nimport * as log from \"./log.js\";\nimport { parseSandboxArg, type SandboxConfig, validateSandbox } from \"./sandbox.js\";\nimport { type MomHandler, type SlackBot, SlackBot as SlackBotClass, type SlackEvent } from \"./slack.js\";\nimport { ChannelStore } from \"./store.js\";\n\n// ============================================================================\n// Config\n// ============================================================================\n\nconst MOM_SLACK_APP_TOKEN = process.env.MOM_SLACK_APP_TOKEN;\nconst MOM_SLACK_BOT_TOKEN = process.env.MOM_SLACK_BOT_TOKEN;\nconst ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;\nconst ANTHROPIC_OAUTH_TOKEN = process.env.ANTHROPIC_OAUTH_TOKEN;\n\ninterface ParsedArgs {\n\tworkingDir?: string;\n\tsandbox: SandboxConfig;\n\tdownloadChannel?: string;\n}\n\nfunction parseArgs(): ParsedArgs {\n\tconst args = process.argv.slice(2);\n\tlet sandbox: SandboxConfig = { type: \"host\" };\n\tlet workingDir: string | undefined;\n\tlet downloadChannelId: string | undefined;\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\t\tif (arg.startsWith(\"--sandbox=\")) {\n\t\t\tsandbox = parseSandboxArg(arg.slice(\"--sandbox=\".length));\n\t\t} else if (arg === \"--sandbox\") {\n\t\t\tsandbox = parseSandboxArg(args[++i] || \"\");\n\t\t} else if (arg.startsWith(\"--download=\")) {\n\t\t\tdownloadChannelId = arg.slice(\"--download=\".length);\n\t\t} else if (arg === \"--download\") {\n\t\t\tdownloadChannelId = args[++i];\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tworkingDir = arg;\n\t\t}\n\t}\n\n\treturn {\n\t\tworkingDir: workingDir ? resolve(workingDir) : undefined,\n\t\tsandbox,\n\t\tdownloadChannel: downloadChannelId,\n\t};\n}\n\nconst parsedArgs = parseArgs();\n\n// Handle --download mode\nif (parsedArgs.downloadChannel) {\n\tif (!MOM_SLACK_BOT_TOKEN) {\n\t\tconsole.error(\"Missing env: MOM_SLACK_BOT_TOKEN\");\n\t\tprocess.exit(1);\n\t}\n\tawait downloadChannel(parsedArgs.downloadChannel, MOM_SLACK_BOT_TOKEN);\n\tprocess.exit(0);\n}\n\n// Normal bot mode - require working dir\nif (!parsedArgs.workingDir) {\n\tconsole.error(\"Usage: mom [--sandbox=host|docker:<name>] <working-directory>\");\n\tconsole.error(\" mom --download <channel-id>\");\n\tprocess.exit(1);\n}\n\nconst { workingDir, sandbox } = { workingDir: parsedArgs.workingDir, sandbox: parsedArgs.sandbox };\n\nif (!MOM_SLACK_APP_TOKEN || !MOM_SLACK_BOT_TOKEN || (!ANTHROPIC_API_KEY && !ANTHROPIC_OAUTH_TOKEN)) {\n\tconsole.error(\"Missing env: MOM_SLACK_APP_TOKEN, MOM_SLACK_BOT_TOKEN, ANTHROPIC_API_KEY or ANTHROPIC_OAUTH_TOKEN\");\n\tprocess.exit(1);\n}\n\nawait validateSandbox(sandbox);\n\n// ============================================================================\n// State (per channel)\n// ============================================================================\n\ninterface ChannelState {\n\trunning: boolean;\n\trunner: AgentRunner;\n\tstore: ChannelStore;\n\tstopRequested: boolean;\n\tstopMessageTs?: string;\n}\n\nconst channelStates = new Map<string, ChannelState>();\n\nfunction getState(channelId: string): ChannelState {\n\tlet state = channelStates.get(channelId);\n\tif (!state) {\n\t\tconst channelDir = join(workingDir, channelId);\n\t\tstate = {\n\t\t\trunning: false,\n\t\t\trunner: getOrCreateRunner(sandbox, channelId, channelDir),\n\t\t\tstore: new ChannelStore({ workingDir, botToken: MOM_SLACK_BOT_TOKEN! }),\n\t\t\tstopRequested: false,\n\t\t};\n\t\tchannelStates.set(channelId, state);\n\t}\n\treturn state;\n}\n\n// ============================================================================\n// Create SlackContext adapter\n// ============================================================================\n\nfunction createSlackContext(event: SlackEvent, slack: SlackBot, state: ChannelState) {\n\tlet messageTs: string | null = null;\n\tlet accumulatedText = \"\";\n\tlet isWorking = true;\n\tconst workingIndicator = \" ...\";\n\tlet updatePromise = Promise.resolve();\n\n\tconst user = slack.getUser(event.user);\n\n\treturn {\n\t\tmessage: {\n\t\t\ttext: event.text,\n\t\t\trawText: event.text,\n\t\t\tuser: event.user,\n\t\t\tuserName: user?.userName,\n\t\t\tchannel: event.channel,\n\t\t\tts: event.ts,\n\t\t\tattachments: (event.attachments || []).map((a) => ({ local: a.local })),\n\t\t},\n\t\tchannelName: slack.getChannel(event.channel)?.name,\n\t\tstore: state.store,\n\t\tchannels: slack.getAllChannels().map((c) => ({ id: c.id, name: c.name })),\n\t\tusers: slack.getAllUsers().map((u) => ({ id: u.id, userName: u.userName, displayName: u.displayName })),\n\n\t\trespond: async (text: string, shouldLog = true) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\taccumulatedText = accumulatedText ? accumulatedText + \"\\n\" + text : text;\n\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tawait slack.updateMessage(event.channel, messageTs, displayText);\n\t\t\t\t} else {\n\t\t\t\t\tmessageTs = await slack.postMessage(event.channel, displayText);\n\t\t\t\t}\n\n\t\t\t\tif (shouldLog && messageTs) {\n\t\t\t\t\tslack.logBotResponse(event.channel, text, messageTs);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\n\t\treplaceMessage: async (text: string) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\taccumulatedText = text;\n\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tawait slack.updateMessage(event.channel, messageTs, displayText);\n\t\t\t\t} else {\n\t\t\t\t\tmessageTs = await slack.postMessage(event.channel, displayText);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\n\t\trespondInThread: async (text: string) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tawait slack.postInThread(event.channel, messageTs, text);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\n\t\tsetTyping: async (isTyping: boolean) => {\n\t\t\tif (isTyping && !messageTs) {\n\t\t\t\taccumulatedText = \"_Thinking_\";\n\t\t\t\tmessageTs = await slack.postMessage(event.channel, accumulatedText + workingIndicator);\n\t\t\t}\n\t\t},\n\n\t\tuploadFile: async (filePath: string, title?: string) => {\n\t\t\tawait slack.uploadFile(event.channel, filePath, title);\n\t\t},\n\n\t\tsetWorking: async (working: boolean) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\tisWorking = working;\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\t\t\t\t\tawait slack.updateMessage(event.channel, messageTs, displayText);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\t};\n}\n\n// ============================================================================\n// Handler\n// ============================================================================\n\nconst handler: MomHandler = {\n\tisRunning(channelId: string): boolean {\n\t\tconst state = channelStates.get(channelId);\n\t\treturn state?.running ?? false;\n\t},\n\n\tasync handleStop(channelId: string, slack: SlackBot): Promise<void> {\n\t\tconst state = channelStates.get(channelId);\n\t\tif (state?.running) {\n\t\t\tstate.stopRequested = true;\n\t\t\tstate.runner.abort();\n\t\t\tconst ts = await slack.postMessage(channelId, \"_Stopping..._\");\n\t\t\tstate.stopMessageTs = ts; // Save for updating later\n\t\t} else {\n\t\t\tawait slack.postMessage(channelId, \"_Nothing running_\");\n\t\t}\n\t},\n\n\tasync handleEvent(event: SlackEvent, slack: SlackBot): Promise<void> {\n\t\tconst state = getState(event.channel);\n\t\tconst channelDir = join(workingDir, event.channel);\n\n\t\t// Start run\n\t\tstate.running = true;\n\t\tstate.stopRequested = false;\n\n\t\tlog.logInfo(`[${event.channel}] Starting run: ${event.text.substring(0, 50)}`);\n\n\t\ttry {\n\t\t\t// SYNC context from log.jsonl BEFORE processing\n\t\t\t// This adds any messages that were logged while mom wasn't running\n\t\t\t// Exclude messages >= current ts (will be handled by agent)\n\t\t\tconst syncedCount = syncLogToContext(channelDir, event.ts);\n\t\t\tif (syncedCount > 0) {\n\t\t\t\tlog.logInfo(`[${event.channel}] Synced ${syncedCount} messages from log to context`);\n\t\t\t}\n\n\t\t\t// Create context adapter\n\t\t\tconst ctx = createSlackContext(event, slack, state);\n\n\t\t\t// Run the agent\n\t\t\tawait ctx.setTyping(true);\n\t\t\tawait ctx.setWorking(true);\n\t\t\tconst result = await state.runner.run(ctx as any, state.store);\n\t\t\tawait ctx.setWorking(false);\n\n\t\t\tif (result.stopReason === \"aborted\" && state.stopRequested) {\n\t\t\t\tif (state.stopMessageTs) {\n\t\t\t\t\tawait slack.updateMessage(event.channel, state.stopMessageTs, \"_Stopped_\");\n\t\t\t\t\tstate.stopMessageTs = undefined;\n\t\t\t\t} else {\n\t\t\t\t\tawait slack.postMessage(event.channel, \"_Stopped_\");\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tlog.logWarning(`[${event.channel}] Run error`, err instanceof Error ? err.message : String(err));\n\t\t} finally {\n\t\t\tstate.running = false;\n\t\t}\n\t},\n};\n\n// ============================================================================\n// Start\n// ============================================================================\n\nlog.logStartup(workingDir, sandbox.type === \"host\" ? \"host\" : `docker:${sandbox.container}`);\n\n// Shared store for attachment downloads (also used per-channel in getState)\nconst sharedStore = new ChannelStore({ workingDir, botToken: MOM_SLACK_BOT_TOKEN! });\n\nconst bot = new SlackBotClass(handler, {\n\tappToken: MOM_SLACK_APP_TOKEN,\n\tbotToken: MOM_SLACK_BOT_TOKEN,\n\tworkingDir,\n\tstore: sharedStore,\n});\n\nbot.start();\n"]}
|
package/dist/main.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { join, resolve } from "path";
|
|
3
3
|
import { getOrCreateRunner } from "./agent.js";
|
|
4
4
|
import { syncLogToContext } from "./context.js";
|
|
5
|
+
import { downloadChannel } from "./download.js";
|
|
5
6
|
import * as log from "./log.js";
|
|
6
7
|
import { parseSandboxArg, validateSandbox } from "./sandbox.js";
|
|
7
8
|
import { SlackBot as SlackBotClass } from "./slack.js";
|
|
@@ -17,6 +18,7 @@ function parseArgs() {
|
|
|
17
18
|
const args = process.argv.slice(2);
|
|
18
19
|
let sandbox = { type: "host" };
|
|
19
20
|
let workingDir;
|
|
21
|
+
let downloadChannelId;
|
|
20
22
|
for (let i = 0; i < args.length; i++) {
|
|
21
23
|
const arg = args[i];
|
|
22
24
|
if (arg.startsWith("--sandbox=")) {
|
|
@@ -25,17 +27,39 @@ function parseArgs() {
|
|
|
25
27
|
else if (arg === "--sandbox") {
|
|
26
28
|
sandbox = parseSandboxArg(args[++i] || "");
|
|
27
29
|
}
|
|
30
|
+
else if (arg.startsWith("--download=")) {
|
|
31
|
+
downloadChannelId = arg.slice("--download=".length);
|
|
32
|
+
}
|
|
33
|
+
else if (arg === "--download") {
|
|
34
|
+
downloadChannelId = args[++i];
|
|
35
|
+
}
|
|
28
36
|
else if (!arg.startsWith("-")) {
|
|
29
37
|
workingDir = arg;
|
|
30
38
|
}
|
|
31
39
|
}
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
return {
|
|
41
|
+
workingDir: workingDir ? resolve(workingDir) : undefined,
|
|
42
|
+
sandbox,
|
|
43
|
+
downloadChannel: downloadChannelId,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const parsedArgs = parseArgs();
|
|
47
|
+
// Handle --download mode
|
|
48
|
+
if (parsedArgs.downloadChannel) {
|
|
49
|
+
if (!MOM_SLACK_BOT_TOKEN) {
|
|
50
|
+
console.error("Missing env: MOM_SLACK_BOT_TOKEN");
|
|
34
51
|
process.exit(1);
|
|
35
52
|
}
|
|
36
|
-
|
|
53
|
+
await downloadChannel(parsedArgs.downloadChannel, MOM_SLACK_BOT_TOKEN);
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
// Normal bot mode - require working dir
|
|
57
|
+
if (!parsedArgs.workingDir) {
|
|
58
|
+
console.error("Usage: mom [--sandbox=host|docker:<name>] <working-directory>");
|
|
59
|
+
console.error(" mom --download <channel-id>");
|
|
60
|
+
process.exit(1);
|
|
37
61
|
}
|
|
38
|
-
const { workingDir, sandbox } =
|
|
62
|
+
const { workingDir, sandbox } = { workingDir: parsedArgs.workingDir, sandbox: parsedArgs.sandbox };
|
|
39
63
|
if (!MOM_SLACK_APP_TOKEN || !MOM_SLACK_BOT_TOKEN || (!ANTHROPIC_API_KEY && !ANTHROPIC_OAUTH_TOKEN)) {
|
|
40
64
|
console.error("Missing env: MOM_SLACK_APP_TOKEN, MOM_SLACK_BOT_TOKEN, ANTHROPIC_API_KEY or ANTHROPIC_OAUTH_TOKEN");
|
|
41
65
|
process.exit(1);
|
package/dist/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { join, resolve } from \"path\";\nimport { type AgentRunner, getOrCreateRunner } from \"./agent.js\";\nimport { syncLogToContext } from \"./context.js\";\nimport * as log from \"./log.js\";\nimport { parseSandboxArg, type SandboxConfig, validateSandbox } from \"./sandbox.js\";\nimport { type MomHandler, type SlackBot, SlackBot as SlackBotClass, type SlackEvent } from \"./slack.js\";\nimport { ChannelStore } from \"./store.js\";\n\n// ============================================================================\n// Config\n// ============================================================================\n\nconst MOM_SLACK_APP_TOKEN = process.env.MOM_SLACK_APP_TOKEN;\nconst MOM_SLACK_BOT_TOKEN = process.env.MOM_SLACK_BOT_TOKEN;\nconst ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;\nconst ANTHROPIC_OAUTH_TOKEN = process.env.ANTHROPIC_OAUTH_TOKEN;\n\nfunction parseArgs(): { workingDir: string; sandbox: SandboxConfig } {\n\tconst args = process.argv.slice(2);\n\tlet sandbox: SandboxConfig = { type: \"host\" };\n\tlet workingDir: string | undefined;\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\t\tif (arg.startsWith(\"--sandbox=\")) {\n\t\t\tsandbox = parseSandboxArg(arg.slice(\"--sandbox=\".length));\n\t\t} else if (arg === \"--sandbox\") {\n\t\t\tsandbox = parseSandboxArg(args[++i] || \"\");\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tworkingDir = arg;\n\t\t}\n\t}\n\n\tif (!workingDir) {\n\t\tconsole.error(\"Usage: mom [--sandbox=host|docker:<name>] <working-directory>\");\n\t\tprocess.exit(1);\n\t}\n\n\treturn { workingDir: resolve(workingDir), sandbox };\n}\n\nconst { workingDir, sandbox } = parseArgs();\n\nif (!MOM_SLACK_APP_TOKEN || !MOM_SLACK_BOT_TOKEN || (!ANTHROPIC_API_KEY && !ANTHROPIC_OAUTH_TOKEN)) {\n\tconsole.error(\"Missing env: MOM_SLACK_APP_TOKEN, MOM_SLACK_BOT_TOKEN, ANTHROPIC_API_KEY or ANTHROPIC_OAUTH_TOKEN\");\n\tprocess.exit(1);\n}\n\nawait validateSandbox(sandbox);\n\n// ============================================================================\n// State (per channel)\n// ============================================================================\n\ninterface ChannelState {\n\trunning: boolean;\n\trunner: AgentRunner;\n\tstore: ChannelStore;\n\tstopRequested: boolean;\n\tstopMessageTs?: string;\n}\n\nconst channelStates = new Map<string, ChannelState>();\n\nfunction getState(channelId: string): ChannelState {\n\tlet state = channelStates.get(channelId);\n\tif (!state) {\n\t\tconst channelDir = join(workingDir, channelId);\n\t\tstate = {\n\t\t\trunning: false,\n\t\t\trunner: getOrCreateRunner(sandbox, channelId, channelDir),\n\t\t\tstore: new ChannelStore({ workingDir, botToken: MOM_SLACK_BOT_TOKEN! }),\n\t\t\tstopRequested: false,\n\t\t};\n\t\tchannelStates.set(channelId, state);\n\t}\n\treturn state;\n}\n\n// ============================================================================\n// Create SlackContext adapter\n// ============================================================================\n\nfunction createSlackContext(event: SlackEvent, slack: SlackBot, state: ChannelState) {\n\tlet messageTs: string | null = null;\n\tlet accumulatedText = \"\";\n\tlet isWorking = true;\n\tconst workingIndicator = \" ...\";\n\tlet updatePromise = Promise.resolve();\n\n\tconst user = slack.getUser(event.user);\n\n\treturn {\n\t\tmessage: {\n\t\t\ttext: event.text,\n\t\t\trawText: event.text,\n\t\t\tuser: event.user,\n\t\t\tuserName: user?.userName,\n\t\t\tchannel: event.channel,\n\t\t\tts: event.ts,\n\t\t\tattachments: (event.attachments || []).map((a) => ({ local: a.local })),\n\t\t},\n\t\tchannelName: slack.getChannel(event.channel)?.name,\n\t\tstore: state.store,\n\t\tchannels: slack.getAllChannels().map((c) => ({ id: c.id, name: c.name })),\n\t\tusers: slack.getAllUsers().map((u) => ({ id: u.id, userName: u.userName, displayName: u.displayName })),\n\n\t\trespond: async (text: string, shouldLog = true) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\taccumulatedText = accumulatedText ? accumulatedText + \"\\n\" + text : text;\n\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tawait slack.updateMessage(event.channel, messageTs, displayText);\n\t\t\t\t} else {\n\t\t\t\t\tmessageTs = await slack.postMessage(event.channel, displayText);\n\t\t\t\t}\n\n\t\t\t\tif (shouldLog && messageTs) {\n\t\t\t\t\tslack.logBotResponse(event.channel, text, messageTs);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\n\t\treplaceMessage: async (text: string) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\taccumulatedText = text;\n\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tawait slack.updateMessage(event.channel, messageTs, displayText);\n\t\t\t\t} else {\n\t\t\t\t\tmessageTs = await slack.postMessage(event.channel, displayText);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\n\t\trespondInThread: async (text: string) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tawait slack.postInThread(event.channel, messageTs, text);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\n\t\tsetTyping: async (isTyping: boolean) => {\n\t\t\tif (isTyping && !messageTs) {\n\t\t\t\taccumulatedText = \"_Thinking_\";\n\t\t\t\tmessageTs = await slack.postMessage(event.channel, accumulatedText + workingIndicator);\n\t\t\t}\n\t\t},\n\n\t\tuploadFile: async (filePath: string, title?: string) => {\n\t\t\tawait slack.uploadFile(event.channel, filePath, title);\n\t\t},\n\n\t\tsetWorking: async (working: boolean) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\tisWorking = working;\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\t\t\t\t\tawait slack.updateMessage(event.channel, messageTs, displayText);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\t};\n}\n\n// ============================================================================\n// Handler\n// ============================================================================\n\nconst handler: MomHandler = {\n\tisRunning(channelId: string): boolean {\n\t\tconst state = channelStates.get(channelId);\n\t\treturn state?.running ?? false;\n\t},\n\n\tasync handleStop(channelId: string, slack: SlackBot): Promise<void> {\n\t\tconst state = channelStates.get(channelId);\n\t\tif (state?.running) {\n\t\t\tstate.stopRequested = true;\n\t\t\tstate.runner.abort();\n\t\t\tconst ts = await slack.postMessage(channelId, \"_Stopping..._\");\n\t\t\tstate.stopMessageTs = ts; // Save for updating later\n\t\t} else {\n\t\t\tawait slack.postMessage(channelId, \"_Nothing running_\");\n\t\t}\n\t},\n\n\tasync handleEvent(event: SlackEvent, slack: SlackBot): Promise<void> {\n\t\tconst state = getState(event.channel);\n\t\tconst channelDir = join(workingDir, event.channel);\n\n\t\t// Start run\n\t\tstate.running = true;\n\t\tstate.stopRequested = false;\n\n\t\tlog.logInfo(`[${event.channel}] Starting run: ${event.text.substring(0, 50)}`);\n\n\t\ttry {\n\t\t\t// SYNC context from log.jsonl BEFORE processing\n\t\t\t// This adds any messages that were logged while mom wasn't running\n\t\t\t// Exclude messages >= current ts (will be handled by agent)\n\t\t\tconst syncedCount = syncLogToContext(channelDir, event.ts);\n\t\t\tif (syncedCount > 0) {\n\t\t\t\tlog.logInfo(`[${event.channel}] Synced ${syncedCount} messages from log to context`);\n\t\t\t}\n\n\t\t\t// Create context adapter\n\t\t\tconst ctx = createSlackContext(event, slack, state);\n\n\t\t\t// Run the agent\n\t\t\tawait ctx.setTyping(true);\n\t\t\tawait ctx.setWorking(true);\n\t\t\tconst result = await state.runner.run(ctx as any, state.store);\n\t\t\tawait ctx.setWorking(false);\n\n\t\t\tif (result.stopReason === \"aborted\" && state.stopRequested) {\n\t\t\t\tif (state.stopMessageTs) {\n\t\t\t\t\tawait slack.updateMessage(event.channel, state.stopMessageTs, \"_Stopped_\");\n\t\t\t\t\tstate.stopMessageTs = undefined;\n\t\t\t\t} else {\n\t\t\t\t\tawait slack.postMessage(event.channel, \"_Stopped_\");\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tlog.logWarning(`[${event.channel}] Run error`, err instanceof Error ? err.message : String(err));\n\t\t} finally {\n\t\t\tstate.running = false;\n\t\t}\n\t},\n};\n\n// ============================================================================\n// Start\n// ============================================================================\n\nlog.logStartup(workingDir, sandbox.type === \"host\" ? \"host\" : `docker:${sandbox.container}`);\n\n// Shared store for attachment downloads (also used per-channel in getState)\nconst sharedStore = new ChannelStore({ workingDir, botToken: MOM_SLACK_BOT_TOKEN! });\n\nconst bot = new SlackBotClass(handler, {\n\tappToken: MOM_SLACK_APP_TOKEN,\n\tbotToken: MOM_SLACK_BOT_TOKEN,\n\tworkingDir,\n\tstore: sharedStore,\n});\n\nbot.start();\n"]}
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { join, resolve } from \"path\";\nimport { type AgentRunner, getOrCreateRunner } from \"./agent.js\";\nimport { syncLogToContext } from \"./context.js\";\nimport { downloadChannel } from \"./download.js\";\nimport * as log from \"./log.js\";\nimport { parseSandboxArg, type SandboxConfig, validateSandbox } from \"./sandbox.js\";\nimport { type MomHandler, type SlackBot, SlackBot as SlackBotClass, type SlackEvent } from \"./slack.js\";\nimport { ChannelStore } from \"./store.js\";\n\n// ============================================================================\n// Config\n// ============================================================================\n\nconst MOM_SLACK_APP_TOKEN = process.env.MOM_SLACK_APP_TOKEN;\nconst MOM_SLACK_BOT_TOKEN = process.env.MOM_SLACK_BOT_TOKEN;\nconst ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;\nconst ANTHROPIC_OAUTH_TOKEN = process.env.ANTHROPIC_OAUTH_TOKEN;\n\ninterface ParsedArgs {\n\tworkingDir?: string;\n\tsandbox: SandboxConfig;\n\tdownloadChannel?: string;\n}\n\nfunction parseArgs(): ParsedArgs {\n\tconst args = process.argv.slice(2);\n\tlet sandbox: SandboxConfig = { type: \"host\" };\n\tlet workingDir: string | undefined;\n\tlet downloadChannelId: string | undefined;\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\t\tif (arg.startsWith(\"--sandbox=\")) {\n\t\t\tsandbox = parseSandboxArg(arg.slice(\"--sandbox=\".length));\n\t\t} else if (arg === \"--sandbox\") {\n\t\t\tsandbox = parseSandboxArg(args[++i] || \"\");\n\t\t} else if (arg.startsWith(\"--download=\")) {\n\t\t\tdownloadChannelId = arg.slice(\"--download=\".length);\n\t\t} else if (arg === \"--download\") {\n\t\t\tdownloadChannelId = args[++i];\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tworkingDir = arg;\n\t\t}\n\t}\n\n\treturn {\n\t\tworkingDir: workingDir ? resolve(workingDir) : undefined,\n\t\tsandbox,\n\t\tdownloadChannel: downloadChannelId,\n\t};\n}\n\nconst parsedArgs = parseArgs();\n\n// Handle --download mode\nif (parsedArgs.downloadChannel) {\n\tif (!MOM_SLACK_BOT_TOKEN) {\n\t\tconsole.error(\"Missing env: MOM_SLACK_BOT_TOKEN\");\n\t\tprocess.exit(1);\n\t}\n\tawait downloadChannel(parsedArgs.downloadChannel, MOM_SLACK_BOT_TOKEN);\n\tprocess.exit(0);\n}\n\n// Normal bot mode - require working dir\nif (!parsedArgs.workingDir) {\n\tconsole.error(\"Usage: mom [--sandbox=host|docker:<name>] <working-directory>\");\n\tconsole.error(\" mom --download <channel-id>\");\n\tprocess.exit(1);\n}\n\nconst { workingDir, sandbox } = { workingDir: parsedArgs.workingDir, sandbox: parsedArgs.sandbox };\n\nif (!MOM_SLACK_APP_TOKEN || !MOM_SLACK_BOT_TOKEN || (!ANTHROPIC_API_KEY && !ANTHROPIC_OAUTH_TOKEN)) {\n\tconsole.error(\"Missing env: MOM_SLACK_APP_TOKEN, MOM_SLACK_BOT_TOKEN, ANTHROPIC_API_KEY or ANTHROPIC_OAUTH_TOKEN\");\n\tprocess.exit(1);\n}\n\nawait validateSandbox(sandbox);\n\n// ============================================================================\n// State (per channel)\n// ============================================================================\n\ninterface ChannelState {\n\trunning: boolean;\n\trunner: AgentRunner;\n\tstore: ChannelStore;\n\tstopRequested: boolean;\n\tstopMessageTs?: string;\n}\n\nconst channelStates = new Map<string, ChannelState>();\n\nfunction getState(channelId: string): ChannelState {\n\tlet state = channelStates.get(channelId);\n\tif (!state) {\n\t\tconst channelDir = join(workingDir, channelId);\n\t\tstate = {\n\t\t\trunning: false,\n\t\t\trunner: getOrCreateRunner(sandbox, channelId, channelDir),\n\t\t\tstore: new ChannelStore({ workingDir, botToken: MOM_SLACK_BOT_TOKEN! }),\n\t\t\tstopRequested: false,\n\t\t};\n\t\tchannelStates.set(channelId, state);\n\t}\n\treturn state;\n}\n\n// ============================================================================\n// Create SlackContext adapter\n// ============================================================================\n\nfunction createSlackContext(event: SlackEvent, slack: SlackBot, state: ChannelState) {\n\tlet messageTs: string | null = null;\n\tlet accumulatedText = \"\";\n\tlet isWorking = true;\n\tconst workingIndicator = \" ...\";\n\tlet updatePromise = Promise.resolve();\n\n\tconst user = slack.getUser(event.user);\n\n\treturn {\n\t\tmessage: {\n\t\t\ttext: event.text,\n\t\t\trawText: event.text,\n\t\t\tuser: event.user,\n\t\t\tuserName: user?.userName,\n\t\t\tchannel: event.channel,\n\t\t\tts: event.ts,\n\t\t\tattachments: (event.attachments || []).map((a) => ({ local: a.local })),\n\t\t},\n\t\tchannelName: slack.getChannel(event.channel)?.name,\n\t\tstore: state.store,\n\t\tchannels: slack.getAllChannels().map((c) => ({ id: c.id, name: c.name })),\n\t\tusers: slack.getAllUsers().map((u) => ({ id: u.id, userName: u.userName, displayName: u.displayName })),\n\n\t\trespond: async (text: string, shouldLog = true) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\taccumulatedText = accumulatedText ? accumulatedText + \"\\n\" + text : text;\n\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tawait slack.updateMessage(event.channel, messageTs, displayText);\n\t\t\t\t} else {\n\t\t\t\t\tmessageTs = await slack.postMessage(event.channel, displayText);\n\t\t\t\t}\n\n\t\t\t\tif (shouldLog && messageTs) {\n\t\t\t\t\tslack.logBotResponse(event.channel, text, messageTs);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\n\t\treplaceMessage: async (text: string) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\taccumulatedText = text;\n\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tawait slack.updateMessage(event.channel, messageTs, displayText);\n\t\t\t\t} else {\n\t\t\t\t\tmessageTs = await slack.postMessage(event.channel, displayText);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\n\t\trespondInThread: async (text: string) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tawait slack.postInThread(event.channel, messageTs, text);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\n\t\tsetTyping: async (isTyping: boolean) => {\n\t\t\tif (isTyping && !messageTs) {\n\t\t\t\taccumulatedText = \"_Thinking_\";\n\t\t\t\tmessageTs = await slack.postMessage(event.channel, accumulatedText + workingIndicator);\n\t\t\t}\n\t\t},\n\n\t\tuploadFile: async (filePath: string, title?: string) => {\n\t\t\tawait slack.uploadFile(event.channel, filePath, title);\n\t\t},\n\n\t\tsetWorking: async (working: boolean) => {\n\t\t\tupdatePromise = updatePromise.then(async () => {\n\t\t\t\tisWorking = working;\n\t\t\t\tif (messageTs) {\n\t\t\t\t\tconst displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\t\t\t\t\tawait slack.updateMessage(event.channel, messageTs, displayText);\n\t\t\t\t}\n\t\t\t});\n\t\t\tawait updatePromise;\n\t\t},\n\t};\n}\n\n// ============================================================================\n// Handler\n// ============================================================================\n\nconst handler: MomHandler = {\n\tisRunning(channelId: string): boolean {\n\t\tconst state = channelStates.get(channelId);\n\t\treturn state?.running ?? false;\n\t},\n\n\tasync handleStop(channelId: string, slack: SlackBot): Promise<void> {\n\t\tconst state = channelStates.get(channelId);\n\t\tif (state?.running) {\n\t\t\tstate.stopRequested = true;\n\t\t\tstate.runner.abort();\n\t\t\tconst ts = await slack.postMessage(channelId, \"_Stopping..._\");\n\t\t\tstate.stopMessageTs = ts; // Save for updating later\n\t\t} else {\n\t\t\tawait slack.postMessage(channelId, \"_Nothing running_\");\n\t\t}\n\t},\n\n\tasync handleEvent(event: SlackEvent, slack: SlackBot): Promise<void> {\n\t\tconst state = getState(event.channel);\n\t\tconst channelDir = join(workingDir, event.channel);\n\n\t\t// Start run\n\t\tstate.running = true;\n\t\tstate.stopRequested = false;\n\n\t\tlog.logInfo(`[${event.channel}] Starting run: ${event.text.substring(0, 50)}`);\n\n\t\ttry {\n\t\t\t// SYNC context from log.jsonl BEFORE processing\n\t\t\t// This adds any messages that were logged while mom wasn't running\n\t\t\t// Exclude messages >= current ts (will be handled by agent)\n\t\t\tconst syncedCount = syncLogToContext(channelDir, event.ts);\n\t\t\tif (syncedCount > 0) {\n\t\t\t\tlog.logInfo(`[${event.channel}] Synced ${syncedCount} messages from log to context`);\n\t\t\t}\n\n\t\t\t// Create context adapter\n\t\t\tconst ctx = createSlackContext(event, slack, state);\n\n\t\t\t// Run the agent\n\t\t\tawait ctx.setTyping(true);\n\t\t\tawait ctx.setWorking(true);\n\t\t\tconst result = await state.runner.run(ctx as any, state.store);\n\t\t\tawait ctx.setWorking(false);\n\n\t\t\tif (result.stopReason === \"aborted\" && state.stopRequested) {\n\t\t\t\tif (state.stopMessageTs) {\n\t\t\t\t\tawait slack.updateMessage(event.channel, state.stopMessageTs, \"_Stopped_\");\n\t\t\t\t\tstate.stopMessageTs = undefined;\n\t\t\t\t} else {\n\t\t\t\t\tawait slack.postMessage(event.channel, \"_Stopped_\");\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tlog.logWarning(`[${event.channel}] Run error`, err instanceof Error ? err.message : String(err));\n\t\t} finally {\n\t\t\tstate.running = false;\n\t\t}\n\t},\n};\n\n// ============================================================================\n// Start\n// ============================================================================\n\nlog.logStartup(workingDir, sandbox.type === \"host\" ? \"host\" : `docker:${sandbox.container}`);\n\n// Shared store for attachment downloads (also used per-channel in getState)\nconst sharedStore = new ChannelStore({ workingDir, botToken: MOM_SLACK_BOT_TOKEN! });\n\nconst bot = new SlackBotClass(handler, {\n\tappToken: MOM_SLACK_APP_TOKEN,\n\tbotToken: MOM_SLACK_BOT_TOKEN,\n\tworkingDir,\n\tstore: sharedStore,\n});\n\nbot.start();\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mariozechner/pi-mom",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.5",
|
|
4
4
|
"description": "Slack bot that delegates messages to the pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@anthropic-ai/sandbox-runtime": "^0.0.16",
|
|
24
|
-
"@mariozechner/pi-agent-core": "^0.18.
|
|
25
|
-
"@mariozechner/pi-ai": "^0.18.
|
|
26
|
-
"@mariozechner/pi-coding-agent": "^0.18.
|
|
24
|
+
"@mariozechner/pi-agent-core": "^0.18.5",
|
|
25
|
+
"@mariozechner/pi-ai": "^0.18.5",
|
|
26
|
+
"@mariozechner/pi-coding-agent": "^0.18.5",
|
|
27
27
|
"@sinclair/typebox": "^0.34.0",
|
|
28
28
|
"@slack/socket-mode": "^2.0.0",
|
|
29
29
|
"@slack/web-api": "^7.0.0",
|