@openacp/cli 0.6.9 → 0.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/adapters/shared/message-formatter.ts","../../src/adapters/shared/message-dispatcher.ts","../../src/adapters/shared/format-types.ts","../../src/adapters/shared/format-utils.ts","../../src/product-guide.ts"],"sourcesContent":["import type { NoiseAction, NoiseRule } from \"./format-types.js\";\nimport { STATUS_ICONS, KIND_ICONS } from \"./format-types.js\";\n\nexport function extractContentText(content: unknown, depth = 0): string {\n if (!content || depth > 5) return \"\";\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n return content\n .map((c) => extractContentText(c, depth + 1))\n .filter(Boolean)\n .join(\"\\n\");\n }\n if (typeof content !== \"object\") return String(content);\n\n const obj = content as Record<string, unknown>;\n if (obj.text && typeof obj.text === \"string\") return obj.text;\n if (obj.content) {\n if (typeof obj.content === \"string\") return obj.content;\n if (Array.isArray(obj.content)) {\n return obj.content\n .map((c) => extractContentText(c, depth + 1))\n .filter(Boolean)\n .join(\"\\n\");\n }\n return extractContentText(obj.content, depth + 1);\n }\n if (obj.input) return extractContentText(obj.input, depth + 1);\n if (obj.output) return extractContentText(obj.output, depth + 1);\n\n // Skip objects with only a 'type' key and no content fields\n const keys = Object.keys(obj).filter((k) => k !== \"type\");\n if (keys.length === 0) return \"\";\n\n // Fallback: serialize unrecognized objects so edge-case agent responses are not silently dropped\n try {\n return JSON.stringify(obj, null, 2);\n } catch {\n return \"\";\n }\n}\n\nfunction parseRawInput(rawInput: unknown): Record<string, unknown> {\n try {\n if (typeof rawInput === \"string\") {\n return JSON.parse(rawInput) as Record<string, unknown>;\n }\n if (typeof rawInput === \"object\" && rawInput !== null) {\n return rawInput as Record<string, unknown>;\n }\n } catch {\n // fall through\n }\n return {};\n}\n\n// --- Step 5: formatToolSummary with displaySummary override ---\n\nexport function formatToolSummary(\n name: string,\n rawInput: unknown,\n displaySummary?: string,\n): string {\n if (displaySummary && typeof displaySummary === \"string\") {\n return displaySummary;\n }\n\n const args = parseRawInput(rawInput);\n const lowerName = name.toLowerCase();\n\n if (lowerName === \"read\") {\n const fp = args.file_path ?? args.filePath ?? \"\";\n const limit = args.limit ? ` (${args.limit} lines)` : \"\";\n return fp ? `📖 Read ${fp}${limit}` : `🔧 ${name}`;\n }\n if (lowerName === \"edit\") {\n const fp = args.file_path ?? args.filePath ?? \"\";\n return fp ? `✏️ Edit ${fp}` : `🔧 ${name}`;\n }\n if (lowerName === \"write\") {\n const fp = args.file_path ?? args.filePath ?? \"\";\n return fp ? `📝 Write ${fp}` : `🔧 ${name}`;\n }\n if (lowerName === \"bash\") {\n const cmd = String(args.command ?? \"\").slice(0, 60);\n return cmd ? `▶️ Run: ${cmd}` : `🔧 ${name}`;\n }\n if (lowerName === \"grep\") {\n const pattern = args.pattern ?? \"\";\n const path = args.path ?? \"\";\n return pattern\n ? `🔍 Grep \"${pattern}\"${path ? ` in ${path}` : \"\"}`\n : `🔧 ${name}`;\n }\n if (lowerName === \"glob\") {\n const pattern = args.pattern ?? \"\";\n return pattern ? `🔍 Glob ${pattern}` : `🔧 ${name}`;\n }\n if (lowerName === \"agent\") {\n const desc = String(args.description ?? \"\").slice(0, 60);\n return desc ? `🧠 Agent: ${desc}` : `🔧 ${name}`;\n }\n if (lowerName === \"webfetch\" || lowerName === \"web_fetch\") {\n const url = String(args.url ?? \"\").slice(0, 60);\n return url ? `🌐 Fetch ${url}` : `🔧 ${name}`;\n }\n if (lowerName === \"websearch\" || lowerName === \"web_search\") {\n const query = String(args.query ?? \"\").slice(0, 60);\n return query ? `🌐 Search \"${query}\"` : `🔧 ${name}`;\n }\n\n return `🔧 ${name}`;\n}\n\n// --- Step 6: formatToolTitle for low verbosity ---\n\nexport function formatToolTitle(\n name: string,\n rawInput: unknown,\n displayTitle?: string,\n): string {\n if (displayTitle && typeof displayTitle === \"string\") {\n return displayTitle;\n }\n\n const args = parseRawInput(rawInput);\n const lowerName = name.toLowerCase();\n\n if ([\"read\", \"edit\", \"write\"].includes(lowerName)) {\n return String(args.file_path ?? args.filePath ?? name);\n }\n if (lowerName === \"bash\") {\n return String(args.command ?? name).slice(0, 60);\n }\n if (lowerName === \"grep\") {\n const pattern = args.pattern ?? \"\";\n const path = args.path ?? \"\";\n return pattern ? `\"${pattern}\"${path ? ` in ${path}` : \"\"}` : name;\n }\n if (lowerName === \"glob\") {\n return String(args.pattern ?? name);\n }\n if (lowerName === \"agent\") {\n return String(args.description ?? name).slice(0, 60);\n }\n if ([\"webfetch\", \"web_fetch\"].includes(lowerName)) {\n return String(args.url ?? name).slice(0, 60);\n }\n if ([\"websearch\", \"web_search\"].includes(lowerName)) {\n return String(args.query ?? name).slice(0, 60);\n }\n\n return name;\n}\n\n// --- Step 7: Noise filtering ---\n\nconst NOISE_RULES: NoiseRule[] = [\n {\n match: (name) => name.toLowerCase() === \"ls\",\n action: \"hide\",\n },\n {\n match: (_name, kind, rawInput) => {\n if (kind !== \"read\") return false;\n const args = parseRawInput(rawInput);\n const p = String(args.file_path ?? args.filePath ?? args.path ?? \"\");\n return p.endsWith(\"/\");\n },\n action: \"hide\",\n },\n {\n match: (name) => name.toLowerCase() === \"glob\",\n action: \"collapse\",\n },\n];\n\nexport function evaluateNoise(\n name: string,\n kind: string,\n rawInput: unknown,\n): NoiseAction | null {\n for (const rule of NOISE_RULES) {\n if (rule.match(name, kind, rawInput)) return rule.action;\n }\n return null;\n}\n","import type { OutgoingMessage } from \"../../core/types.js\";\nimport type { DisplayVerbosity } from \"./format-types.js\";\n\nexport interface MessageHandlers<TCtx = unknown> {\n onText(ctx: TCtx, content: OutgoingMessage): Promise<void>;\n onThought(ctx: TCtx, content: OutgoingMessage): Promise<void>;\n onToolCall(ctx: TCtx, content: OutgoingMessage): Promise<void>;\n onToolUpdate(ctx: TCtx, content: OutgoingMessage): Promise<void>;\n onPlan(ctx: TCtx, content: OutgoingMessage): Promise<void>;\n onUsage(ctx: TCtx, content: OutgoingMessage): Promise<void>;\n onSessionEnd(ctx: TCtx, content: OutgoingMessage): Promise<void>;\n onError(ctx: TCtx, content: OutgoingMessage): Promise<void>;\n onAttachment(ctx: TCtx, content: OutgoingMessage): Promise<void>;\n onSystemMessage(ctx: TCtx, content: OutgoingMessage): Promise<void>;\n}\n\nconst HIDDEN_ON_LOW: Set<string> = new Set([\"thought\", \"plan\", \"usage\"]);\n\nexport function shouldDispatch(\n type: string,\n verbosity: DisplayVerbosity,\n): boolean {\n if (verbosity === \"low\" && HIDDEN_ON_LOW.has(type)) return false;\n return true;\n}\n\nexport async function dispatchMessage<TCtx>(\n handlers: MessageHandlers<TCtx>,\n ctx: TCtx,\n content: OutgoingMessage,\n verbosity: DisplayVerbosity = \"medium\",\n): Promise<void> {\n if (!shouldDispatch(content.type, verbosity)) return;\n\n switch (content.type) {\n case \"text\":\n return handlers.onText(ctx, content);\n case \"thought\":\n return handlers.onThought(ctx, content);\n case \"tool_call\":\n return handlers.onToolCall(ctx, content);\n case \"tool_update\":\n return handlers.onToolUpdate(ctx, content);\n case \"plan\":\n return handlers.onPlan(ctx, content);\n case \"usage\":\n return handlers.onUsage(ctx, content);\n case \"session_end\":\n return handlers.onSessionEnd(ctx, content);\n case \"error\":\n return handlers.onError(ctx, content);\n case \"attachment\":\n return handlers.onAttachment(ctx, content);\n case \"system_message\":\n return handlers.onSystemMessage(ctx, content);\n default:\n return;\n }\n}\n","// src/adapters/shared/format-types.ts\n\nexport type DisplayVerbosity = \"low\" | \"medium\" | \"high\";\n\nexport type NoiseAction = \"hide\" | \"collapse\";\n\nexport interface NoiseRule {\n match: (name: string, kind: string, rawInput: unknown) => boolean;\n action: NoiseAction;\n}\n\nexport type MessageStyle =\n | \"text\"\n | \"thought\"\n | \"tool\"\n | \"plan\"\n | \"usage\"\n | \"system\"\n | \"error\"\n | \"attachment\";\n\nexport interface MessageMetadata {\n toolName?: string;\n toolStatus?: string;\n toolKind?: string;\n filePath?: string;\n command?: string;\n planEntries?: { content: string; status: string }[];\n tokens?: number;\n contextSize?: number;\n cost?: number;\n viewerLinks?: ViewerLinks;\n viewerFilePath?: string;\n}\n\n/** summary and detail are always plain text (never pre-escaped HTML/markdown) — renderers handle escaping */\nexport interface FormattedMessage {\n summary: string;\n detail?: string;\n viewerLinks?: ViewerLinks;\n icon: string;\n originalType: string;\n style: MessageStyle;\n metadata?: MessageMetadata;\n}\n\nexport const STATUS_ICONS: Record<string, string> = {\n pending: \"⏳\",\n in_progress: \"🔄\",\n completed: \"✅\",\n failed: \"❌\",\n cancelled: \"🚫\",\n running: \"🔄\",\n done: \"✅\",\n error: \"❌\",\n};\n\nexport const KIND_ICONS: Record<string, string> = {\n read: \"📖\",\n edit: \"✏️\",\n write: \"✏️\",\n delete: \"🗑️\",\n execute: \"▶️\",\n command: \"▶️\",\n bash: \"▶️\",\n search: \"🔍\",\n web: \"🌐\",\n fetch: \"🌐\",\n agent: \"🧠\",\n think: \"🧠\",\n install: \"📦\",\n move: \"📦\",\n other: \"🛠️\",\n};\n\nexport interface ViewerLinks {\n file?: string;\n diff?: string;\n}\n\nexport interface ToolCallMeta {\n id: string;\n name: string;\n kind?: string;\n status?: string;\n content?: unknown;\n rawInput?: unknown;\n viewerLinks?: ViewerLinks;\n viewerFilePath?: string;\n}\n\nexport interface ToolUpdateMeta extends ToolCallMeta {\n status: string;\n}\n","export function progressBar(ratio: number, length = 10): string {\n const filled = Math.round(Math.min(ratio, 1) * length);\n return \"▓\".repeat(filled) + \"░\".repeat(length - filled);\n}\n\nexport function formatTokens(n: number): string {\n return n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);\n}\n\nexport function stripCodeFences(text: string): string {\n return text\n .replace(/```\\w*\\n?/g, \"\")\n .replace(/```$/gm, \"\")\n .trim();\n}\n\nexport function truncateContent(text: string, maxLen: number): string {\n if (text.length <= maxLen) return text;\n return text.slice(0, maxLen) + \"\\n… (truncated)\";\n}\n\nexport function splitMessage(text: string, maxLength: number): string[] {\n if (text.length <= maxLength) return [text];\n const chunks: string[] = [];\n let remaining = text;\n while (remaining.length > 0) {\n if (remaining.length <= maxLength) {\n chunks.push(remaining);\n break;\n }\n\n const wouldLeaveSmall = remaining.length < maxLength * 1.3;\n const searchLimit = wouldLeaveSmall\n ? Math.floor(remaining.length / 2) + 300\n : maxLength;\n\n const threshold = maxLength * 0.2;\n let splitAt = remaining.lastIndexOf(\"\\n\\n\", searchLimit);\n if (splitAt === -1 || splitAt < threshold) {\n splitAt = remaining.lastIndexOf(\"\\n\", searchLimit);\n }\n if (splitAt === -1 || splitAt < threshold) {\n splitAt = searchLimit;\n }\n\n const candidate = remaining.slice(0, splitAt);\n const fences = candidate.match(/```/g);\n if (fences && fences.length % 2 !== 0) {\n const closingFence = remaining.indexOf(\"```\", splitAt);\n if (closingFence !== -1) {\n const afterFence = remaining.indexOf(\"\\n\", closingFence + 3);\n const fenceSplit =\n afterFence !== -1 ? afterFence + 1 : closingFence + 3;\n // Only extend to include the closing fence if it doesn't exceed 2x maxLength\n if (fenceSplit <= maxLength * 2) {\n splitAt = fenceSplit;\n }\n }\n }\n\n chunks.push(remaining.slice(0, splitAt));\n remaining = remaining.slice(splitAt).replace(/^\\n+/, \"\");\n }\n return chunks;\n}\n","/**\n * OpenACP Product Guide — comprehensive reference for the AI assistant.\n * The assistant reads this at runtime to answer user questions about features.\n */\nexport const PRODUCT_GUIDE = `\n# OpenACP — Product Guide\n\nOpenACP lets you chat with AI coding agents (like Claude Code) through messaging platforms (Telegram, Discord).\nYou type messages in your chat platform, the agent reads/writes/runs code in your project folder, and results stream back in real time.\n\n---\n\n## Quick Start\n\n1. Start OpenACP: \\`openacp\\` (or \\`openacp start\\` for background daemon)\n2. Open your messaging platform (Telegram group or Discord server) — you'll see the Assistant topic/thread\n3. Tap/click 🆕 New Session or type /new\n4. Pick an agent and a project folder\n5. Chat in the session topic/thread — the agent works on your code\n\n---\n\n## Core Concepts\n\n### Sessions\nA session = one conversation with one AI agent working in one project folder.\nEach session gets its own topic (Telegram) or forum thread (Discord). Chat there to give instructions to the agent.\n\n### Agents\nAn agent is an AI coding tool (e.g., Claude Code, Gemini, Cursor, Codex, etc.).\nOpenACP supports 28+ agents from the official ACP Registry (agentclientprotocol.com).\nYou can install multiple agents and choose which one to use per session.\nThe default agent is used when you don't specify one.\n\n### Agent Management\n- Browse agents: \\`/agents\\` in your chat platform or \\`openacp agents\\` in CLI\n- Install: tap the install button in /agents, or \\`openacp agents install <name>\\`\n- Uninstall: \\`openacp agents uninstall <name>\\`\n- Setup/login: \\`openacp agents run <name> -- <args>\\` (e.g., \\`openacp agents run gemini -- auth login\\`)\n- Details: \\`openacp agents info <name>\\` shows version, dependencies, and setup steps\n\nSome agents need additional setup before they can be used:\n- Claude: requires \\`claude login\\`\n- Gemini: requires \\`openacp agents run gemini -- auth login\\`\n- Codex: requires setting \\`OPENAI_API_KEY\\` environment variable\n- GitHub Copilot: requires \\`openacp agents run copilot -- auth login\\`\n\nAgents are installed in three ways depending on the agent:\n- **npx** — Node.js agents, downloaded automatically on first use\n- **uvx** — Python agents, downloaded automatically on first use\n- **binary** — Platform-specific binaries, downloaded to \\`~/.openacp/agents/\\`\n\n### Project Folder (Workspace)\nThe directory where the agent reads, writes, and runs code.\nWhen creating a session, you choose which folder the agent works in.\nYou can type a full path like \\`~/code/my-project\\` or just a name like \\`my-project\\` (it becomes \\`<base-dir>/my-project\\`).\n\n### System Topics\n- **Assistant** — Always-on helper that can answer questions, create sessions, check status, troubleshoot\n- **Notifications** — System alerts (permission requests, session errors, completions)\n\n---\n\n## Creating Sessions\n\n### From menu\nTap 🆕 New Session → choose agent (if multiple) → choose project folder → confirm\n\n### From command\n- \\`/new\\` — Interactive flow (asks agent + folder)\n- \\`/new claude ~/code/my-project\\` — Create directly with specific agent and folder\n\n### From Assistant topic\nJust ask: \"Create a session for my-project with claude\" — the assistant handles it\n\n### Quick new chat\n\\`/newchat\\` in a session topic — creates new session with same agent and folder as current one\n\n---\n\n## Working with Sessions\n\n### Chat\nType messages in the session topic. The agent responds with code changes, explanations, tool outputs.\n\n### What you see while the agent works\n- **💭 Thinking indicator** — Shows when the agent is reasoning, with elapsed time\n- **Text responses** — Streamed in real time, updated every few seconds\n- **Tool calls** — When the agent runs commands or edits files, you see tool name, input, status, and output\n- **📋 Plan card** — Visual task progress with completed/in-progress/pending items and progress bar\n- **\"View File\" / \"View Diff\" buttons** — Opens in browser with Monaco editor (requires tunnel)\n\n### Session lifecycle\n1. **Creating** — Topic created, agent spawning\n2. **Warming up** — Agent primes its cache (happens automatically, invisible to you)\n3. **Active** — Ready for your messages\n4. **Auto-naming** — After your first message, the session gets a descriptive name (agent summarizes in ~5 words). The topic title updates automatically.\n5. **Finished/Error** — Session completed or hit an error\n\n### Agent skills\nSome agents provide slash commands (e.g., /compact, /review). Available skills are pinned in the session topic.\n\n### Permission requests\nWhen the agent wants to run a command, it asks for permission.\nYou see buttons: ✅ Allow, ❌ Reject (and sometimes \"Always Allow\").\nA notification also appears in the Notifications topic with a link to the request.\n\n### Dangerous mode\nAuto-approves ALL permission requests — the agent runs any command without asking.\n- Enable: \\`/enable_dangerous\\` or tap the ☠️ button in the session\n- Disable: \\`/disable_dangerous\\` or tap the 🔐 button\n- ⚠️ Use with caution — the agent can execute anything\n\n### Session timeout\nIdle sessions are automatically cancelled after a configurable timeout (default: 60 minutes).\nConfigure via \\`security.sessionTimeoutMinutes\\` in config.\n\n---\n\n## Session Transfer (Handoff)\n\n### Chat → Terminal\n1. Type \\`/handoff\\` in a session topic/thread\n2. You get a command like \\`claude --resume <SESSION_ID>\\`\n3. Copy and run it in your terminal — the session continues there with full conversation history\n\n### Terminal → Chat\n1. First time: run \\`openacp integrate claude\\` to install the handoff skill (one-time setup)\n2. In Claude Code, use the /openacp:handoff slash command\n3. The session appears as a new topic/thread and you can continue chatting there\n\n### How it works\n- The agent session ID is shared between platforms\n- Conversation history is preserved — pick up where you left off\n- The agent that supports resume (e.g., Claude with \\`--resume\\`) handles the actual transfer\n\n---\n\n## Managing Sessions\n\n### Status\n- \\`/status\\` — Shows active sessions count and details\n- Ask the Assistant: \"What sessions are running?\"\n\n### List all sessions\n- \\`/sessions\\` — Shows all sessions with status (active, finished, error)\n\n### Cancel\n- \\`/cancel\\` in a session topic — cancels that session\n- Ask the Assistant: \"Cancel the stuck session\"\n\n### Cleanup\n- From \\`/sessions\\` → tap cleanup buttons (finished, errors, all)\n- Ask the Assistant: \"Clean up old sessions\"\n\n---\n\n## Assistant Topic\n\nThe Assistant is an always-on AI helper in its own topic. It can:\n- Answer questions about OpenACP\n- Create sessions for you\n- Check status and health\n- Cancel sessions\n- Clean up old sessions\n- Troubleshoot issues\n- Manage configuration\n\nJust chat naturally: \"How do I create a session?\", \"What's the status?\", \"Something is stuck\"\n\n### Clear history\n\\`/clear\\` in the Assistant topic — resets the conversation\n\n---\n\n## System Commands\n\n| Command | Where | What it does |\n|---------|-------|-------------|\n| \\`/new [agent] [path]\\` | Anywhere | Create new session |\n| \\`/newchat\\` | Session topic | New session, same agent + folder |\n| \\`/cancel\\` | Session topic | Cancel current session |\n| \\`/status\\` | Anywhere | Show status |\n| \\`/sessions\\` | Anywhere | List all sessions |\n| \\`/agents\\` | Anywhere | Browse & install agents from ACP Registry |\n| \\`/install <name>\\` | Anywhere | Install an agent |\n| \\`/enable_dangerous\\` | Session topic | Auto-approve all permissions |\n| \\`/disable_dangerous\\` | Session topic | Restore permission prompts |\n| \\`/handoff\\` | Session topic | Transfer session to terminal |\n| \\`/clear\\` | Assistant topic | Clear assistant history |\n| \\`/menu\\` | Anywhere | Show action menu |\n| \\`/help\\` | Anywhere | Show help |\n| \\`/restart\\` | Anywhere | Restart OpenACP |\n| \\`/update\\` | Anywhere | Update to latest version |\n| \\`/integrate\\` | Anywhere | Manage agent integrations |\n\n---\n\n## Menu Buttons\n\n| Button | Action |\n|--------|--------|\n| 🆕 New Session | Create new session (interactive) |\n| 📋 Sessions | List all sessions with cleanup options |\n| 📊 Status | Show active/total session count |\n| 🤖 Agents | List available agents |\n| 🔗 Integrate | Manage agent integrations |\n| ❓ Help | Show help text |\n| 🔄 Restart | Restart OpenACP |\n| ⬆️ Update | Check and install updates |\n\n---\n\n## CLI Commands\n\n### Server\n- \\`openacp\\` — Start (uses configured mode: foreground or daemon)\n- \\`openacp start\\` — Start as background daemon\n- \\`openacp stop\\` — Stop daemon\n- \\`openacp status\\` — Show daemon status\n- \\`openacp logs\\` — Tail daemon logs\n- \\`openacp --foreground\\` — Force foreground mode (useful for debugging or containers)\n\n### Auto-start (run on boot)\n- macOS: installs a LaunchAgent in \\`~/Library/LaunchAgents/\\`\n- Linux: installs a systemd user service in \\`~/.config/systemd/user/\\`\n- Enabled automatically when you start the daemon. Remove with \\`openacp stop\\`.\n\n### Configuration\n- \\`openacp config\\` — Interactive config editor\n- \\`openacp reset\\` — Delete all data and start fresh\n\n### Agent Management (CLI)\n- \\`openacp agents\\` — List all agents (installed + available from ACP Registry)\n- \\`openacp agents install <name>\\` — Install an agent\n- \\`openacp agents uninstall <name>\\` — Remove an agent\n- \\`openacp agents info <name>\\` — Show details, dependencies, and setup guide\n- \\`openacp agents run <name> [-- args]\\` — Run agent CLI directly (for login, config, etc.)\n- \\`openacp agents refresh\\` — Force-refresh registry cache\n\n### Plugins\n- \\`openacp install <package>\\` — Install adapter plugin\n- \\`openacp uninstall <package>\\` — Remove adapter plugin\n- \\`openacp plugins\\` — List installed plugins\n\n### Integration\n- \\`openacp integrate <agent>\\` — Install agent integration (e.g., Claude handoff skill)\n- \\`openacp integrate <agent> --uninstall\\` — Remove integration\n\n### API (requires running daemon)\n\\`openacp api <command>\\` — Interact with running daemon:\n\n| Command | Description |\n|---------|-------------|\n| \\`status\\` | List active sessions |\n| \\`session <id>\\` | Session details |\n| \\`new <agent> <path>\\` | Create session |\n| \\`send <id> \"text\"\\` | Send prompt |\n| \\`cancel <id>\\` | Cancel session |\n| \\`dangerous <id> on/off\\` | Toggle dangerous mode |\n| \\`topics [--status x,y]\\` | List topics |\n| \\`delete-topic <id> [--force]\\` | Delete topic |\n| \\`cleanup [--status x,y]\\` | Cleanup old topics |\n| \\`agents\\` | List agents |\n| \\`health\\` | System health |\n| \\`config\\` | Show config |\n| \\`config set <key> <value>\\` | Update config |\n| \\`adapters\\` | List adapters |\n| \\`tunnel\\` | Tunnel status |\n| \\`notify \"message\"\\` | Send notification |\n| \\`version\\` | Daemon version |\n| \\`restart\\` | Restart daemon |\n\n---\n\n## File Viewer (Tunnel)\n\nWhen tunnel is enabled, file edits and diffs get \"View\" buttons that open in your browser:\n- **Monaco Editor** — Full VS Code editor with syntax highlighting\n- **Diff viewer** — Side-by-side or inline comparison\n- **Line highlighting** — Click lines to highlight\n- Dark/light theme toggle\n\n### Setup\nEnable in config: set \\`tunnel.enabled\\` to \\`true\\`.\nProviders: Cloudflare (default, free), ngrok, bore, Tailscale Funnel.\n\n### Port Tunneling\n\nExpose any local port (dev servers, APIs, etc.) to the internet:\n\n**CLI commands** (agent can call these directly):\n- \\`openacp tunnel add <port> --label <name>\\` — Create tunnel to a local port\n- \\`openacp tunnel list\\` — List active tunnels\n- \\`openacp tunnel stop <port>\\` — Stop a tunnel\n- \\`openacp tunnel stop-all\\` — Stop all user tunnels\n\n**Telegram commands**:\n- \\`/tunnel <port> [label]\\` — Create tunnel\n- \\`/tunnels\\` — List active tunnels\n- \\`/tunnel stop <port>\\` — Stop tunnel\n\nExample: after starting a dev server on port 3000, run \\`openacp tunnel add 3000 --label my-app\\` to get a public URL.\n\n---\n\n## Configuration\n\nConfig file: \\`~/.openacp/config.json\\`\n\n### Channels\n- **channels.telegram.botToken** — Your Telegram bot token\n- **channels.telegram.chatId** — Your Telegram supergroup ID\n- **channels.discord.botToken** — Your Discord bot token\n- **channels.discord.guildId** — Your Discord server (guild) ID\n\n### Agents\n- **defaultAgent** — Which agent to use by default\n- Agents are managed via \\`/agents\\` (Telegram) or \\`openacp agents\\` (CLI)\n- Installed agents are stored in \\`~/.openacp/agents.json\\`\n- Agent list is fetched from the ACP Registry CDN and cached locally (24h)\n\n### Workspace\n- **workspace.baseDir** — Base directory for project folders (default: \\`~/openacp-workspace\\`)\n\n### Security\n- **security.allowedUserIds** — Restrict who can use the bot (empty = everyone)\n- **security.maxConcurrentSessions** — Max parallel sessions (default: 5)\n- **security.sessionTimeoutMinutes** — Auto-cancel idle sessions (default: 60)\n\n### Tunnel / File Viewer\n- **tunnel.enabled** — Enable file viewer tunnel\n- **tunnel.provider** — Tunnel provider: cloudflare (default, free), ngrok, bore, tailscale\n- **tunnel.port** — Local port for tunnel server (default: 3100)\n- **tunnel.auth.enabled** — Enable authentication for tunnel URLs\n- **tunnel.auth.token** — Auth token for tunnel access\n- **tunnel.storeTtlMinutes** — How long viewer links stay cached (default: 60)\n\n### Logging\n- **logging.level** — Log level: silent, debug, info, warn, error, fatal (default: info)\n- **logging.logDir** — Log directory (default: \\`~/.openacp/logs\\`)\n- **logging.maxFileSize** — Max log file size before rotation\n- **logging.maxFiles** — Max number of rotated log files\n- **logging.sessionLogRetentionDays** — Auto-delete old session logs (default: 30)\n\n### Data Retention\n- **sessionStore.ttlDays** — How long session records persist (default: 30). Old records are cleaned up automatically.\n\n### Environment variables\nOverride config with env vars:\n- \\`OPENACP_TELEGRAM_BOT_TOKEN\\`\n- \\`OPENACP_TELEGRAM_CHAT_ID\\`\n- \\`OPENACP_DISCORD_BOT_TOKEN\\`\n- \\`OPENACP_DISCORD_GUILD_ID\\`\n- \\`OPENACP_DEFAULT_AGENT\\`\n- \\`OPENACP_RUN_MODE\\` — foreground or daemon\n- \\`OPENACP_API_PORT\\` — API server port (default: 21420)\n- \\`OPENACP_TUNNEL_ENABLED\\`\n- \\`OPENACP_TUNNEL_PORT\\`\n- \\`OPENACP_TUNNEL_PROVIDER\\`\n- \\`OPENACP_LOG_LEVEL\\`\n- \\`OPENACP_LOG_DIR\\`\n- \\`OPENACP_DEBUG\\` — Sets log level to debug\n\n---\n\n## Troubleshooting\n\n### Session stuck / not responding\n- Check status: ask Assistant \"Is anything stuck?\"\n- Cancel and create new: \\`/cancel\\` then \\`/new\\`\n- Check system health: Assistant can run health check\n\n### Agent not found\n- Check available agents: \\`/agents\\` or \\`openacp agents\\`\n- Install missing agent: \\`openacp agents install <name>\\`\n- Some agents need login first: \\`openacp agents info <name>\\` to see setup steps\n- Run agent CLI for setup: \\`openacp agents run <name> -- <args>\\`\n\n### Permission request not showing\n- Check Notifications topic for the alert\n- Try \\`/enable_dangerous\\` to auto-approve (if you trust the agent)\n\n### Session disappeared after restart\n- Sessions persist across restarts\n- Send a message in the old topic — it auto-resumes\n- If topic was deleted, the session record may still exist in status\n\n### Bot not responding at all\n- Check daemon: \\`openacp status\\`\n- Check logs: \\`openacp logs\\`\n- Restart: \\`openacp start\\` or \\`/restart\\`\n\n### Messages going to wrong topic\n- Each session is bound to a specific topic/thread\n- If you see messages appearing in the Assistant topic instead of the session topic, try creating a new session\n\n### Viewing logs\n- Session-specific logs: \\`~/.openacp/logs/sessions/\\`\n- System logs: \\`openacp logs\\` to tail live\n- Set \\`OPENACP_DEBUG=true\\` for verbose output\n\n---\n\n## Data & Storage\n\nAll data is stored in \\`~/.openacp/\\`:\n- \\`config.json\\` — Configuration\n- \\`agents.json\\` — Installed agents (managed by AgentCatalog)\n- \\`registry-cache.json\\` — Cached ACP Registry data (refreshes every 24h)\n- \\`agents/\\` — Downloaded binary agents\n- \\`sessions/\\` — Session records and state\n- \\`topics/\\` — Topic-to-session mappings\n- \\`logs/\\` — System and session logs\n- \\`plugins/\\` — Installed adapter plugins\n- \\`openacp.pid\\` — Daemon PID file\n\nSession records auto-cleanup: 30 days (configurable via \\`sessionStore.ttlDays\\`).\nSession logs auto-cleanup: 30 days (configurable via \\`logging.sessionLogRetentionDays\\`).\n`;\n"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/adapters/discord/adapter.ts","../../src/adapters/discord/send-queue.ts","../../src/adapters/discord/formatting.ts","../../src/adapters/discord/tool-call-tracker.ts","../../src/adapters/discord/streaming.ts","../../src/adapters/discord/draft-manager.ts","../../src/adapters/discord/activity.ts","../../src/adapters/discord/skill-command-manager.ts","../../src/adapters/discord/permissions.ts","../../src/adapters/discord/commands/index.ts","../../src/adapters/discord/commands/menu.ts","../../src/adapters/discord/commands/integrate.ts","../../src/adapters/discord/commands/router.ts","../../src/adapters/discord/assistant.ts","../../src/adapters/discord/media.ts"],"sourcesContent":["import { Client, GatewayIntentBits, MessageFlags, type Guild, type ForumChannel, type TextChannel, type ThreadChannel } from 'discord.js'\nimport { ChannelAdapter } from '../../core/channel.js'\nimport type { OutgoingMessage, PermissionRequest, NotificationMessage, AgentCommand, PlanEntry } from '../../core/types.js'\nimport type { OpenACPCore } from '../../core/core.js'\nimport type { Session } from '../../core/session.js'\nimport { log } from '../../core/log.js'\nimport type { DisplayVerbosity } from '../shared/format-types.js'\nimport { evaluateNoise } from '../shared/message-formatter.js'\nimport { dispatchMessage, type MessageHandlers } from '../shared/message-dispatcher.js'\nimport type { DiscordChannelConfig } from './types.js'\nimport { DiscordSendQueue } from './send-queue.js'\nimport { ToolCallTracker } from './tool-call-tracker.js'\nimport { DraftManager } from './draft-manager.js'\nimport { ActivityTracker } from './activity.js'\nimport { SkillCommandManager } from './skill-command-manager.js'\nimport { PermissionHandler } from './permissions.js'\nimport {\n ensureForums,\n createSessionThread as forumsCreateThread,\n renameSessionThread as forumsRenameThread,\n deleteSessionThread as forumsDeleteThread,\n ensureUnarchived,\n buildDeepLink,\n} from './forums.js'\nimport {\n registerSlashCommands,\n handleSlashCommand,\n setupButtonCallbacks,\n} from './commands/index.js'\nimport {\n spawnAssistant,\n buildWelcomeMessage,\n} from './assistant.js'\nimport type { Attachment } from '../../core/types.js'\nimport type { FileService } from '../../core/file-service.js'\nimport { buildFallbackText, downloadDiscordAttachment, isAttachmentTooLarge } from './media.js'\n\ninterface DiscordMessageCtx {\n sessionId: string\n thread: ThreadChannel\n isAssistant: boolean\n}\n\nexport class DiscordAdapter extends ChannelAdapter<OpenACPCore> {\n private client: Client\n private discordConfig: DiscordChannelConfig\n private sendQueue: DiscordSendQueue\n private toolTracker: ToolCallTracker\n private draftManager: DraftManager\n private skillManager!: SkillCommandManager\n private permissionHandler!: PermissionHandler\n private sessionTrackers: Map<string, ActivityTracker> = new Map()\n\n private get verbosity(): DisplayVerbosity {\n return (this.discordConfig as Record<string, unknown>).displayVerbosity as DisplayVerbosity ?? 'medium'\n }\n\n private guild!: Guild\n private forumChannel!: ForumChannel | TextChannel\n private notificationChannel!: TextChannel\n private assistantSession: Session | null = null\n private assistantInitializing = false\n private fileService: FileService\n\n constructor(core: OpenACPCore, config: DiscordChannelConfig) {\n super(core, config)\n this.discordConfig = config\n\n this.client = new Client({\n intents: [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n ],\n })\n\n this.sendQueue = new DiscordSendQueue()\n this.toolTracker = new ToolCallTracker(this.sendQueue)\n this.draftManager = new DraftManager(this.sendQueue)\n this.fileService = core.fileService\n\n // Wire discord.js rate limit events to send queue\n this.client.rest.on('rateLimited', (info) => {\n log.warn({ route: info.route, timeToReset: info.timeToReset }, '[DiscordAdapter] Rate limited')\n this.sendQueue.onRateLimited()\n })\n }\n\n // ─── start ────────────────────────────────────────────────────────────────\n\n start(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.client.once('ready', async () => {\n try {\n log.info({ guildId: this.discordConfig.guildId }, '[DiscordAdapter] Client ready, initializing...')\n\n // Fetch guild\n const guild = this.client.guilds.cache.get(this.discordConfig.guildId)\n ?? await this.client.guilds.fetch(this.discordConfig.guildId).catch(() => null)\n if (!guild) {\n throw new Error(`Guild not found: ${this.discordConfig.guildId}`)\n }\n this.guild = guild\n\n // Ensure forum + notification channels exist\n const saveConfig = (updates: Record<string, unknown>) =>\n this.core.configManager.save(updates as Parameters<typeof this.core.configManager.save>[0])\n const { forumChannel, notificationChannel } = await ensureForums(\n guild,\n {\n forumChannelId: this.discordConfig.forumChannelId,\n notificationChannelId: this.discordConfig.notificationChannelId,\n },\n saveConfig,\n )\n this.forumChannel = forumChannel\n this.notificationChannel = notificationChannel\n\n // Init managers that need guild/guildId\n this.skillManager = new SkillCommandManager(this.sendQueue, this.core.sessionManager)\n this.permissionHandler = new PermissionHandler(\n guild.id,\n (sessionId) => this.core.sessionManager.getSession(sessionId),\n (notification) => this.sendNotification(notification),\n )\n\n // Register slash commands\n await registerSlashCommands(guild)\n\n // Wire interaction + message handlers\n this.setupInteractionHandler()\n this.setupMessageHandler()\n\n // Welcome message\n const welcomeMsg = buildWelcomeMessage(this.core)\n try {\n await this.notificationChannel.send(welcomeMsg)\n } catch (err) {\n log.warn({ err }, '[DiscordAdapter] Failed to send welcome message')\n }\n\n // Spawn assistant session\n await this.setupAssistant()\n\n log.info('[DiscordAdapter] Initialization complete')\n resolve()\n } catch (err) {\n log.error({ err }, '[DiscordAdapter] Initialization failed')\n reject(err)\n }\n })\n\n this.client.login(this.discordConfig.botToken).catch(reject)\n })\n }\n\n // ─── stop ─────────────────────────────────────────────────────────────────\n\n async stop(): Promise<void> {\n if (this.assistantSession) {\n try {\n await this.assistantSession.destroy()\n } catch (err) {\n log.warn({ err }, '[DiscordAdapter] Failed to destroy assistant session')\n }\n this.assistantSession = null\n }\n this.client.destroy()\n log.info('[DiscordAdapter] Stopped')\n }\n\n // ─── Interaction handler ──────────────────────────────────────────────────\n\n private setupInteractionHandler(): void {\n this.client.on('interactionCreate', async (interaction) => {\n try {\n if (interaction.isChatInputCommand()) {\n await handleSlashCommand(interaction, this)\n return\n }\n\n if (interaction.isButton()) {\n // Permission buttons take priority\n const handled = await this.permissionHandler.handleButtonInteraction(interaction)\n if (!handled) {\n await setupButtonCallbacks(interaction, this)\n }\n }\n } catch (err) {\n log.error({ err }, '[DiscordAdapter] interactionCreate handler error')\n }\n })\n }\n\n // ─── Message handler ──────────────────────────────────────────────────────\n\n private setupMessageHandler(): void {\n this.client.on('messageCreate', async (message) => {\n try {\n // Ignore bots and self\n if (message.author.bot) return\n\n // Ignore DMs\n if (!message.guild) return\n\n // Ignore messages from the wrong guild\n if (message.guild.id !== this.guild.id) return\n\n // Only process messages in threads\n if (!message.channel.isThread()) return\n\n const threadId = message.channel.id\n const userId = message.author.id\n let text = message.content\n\n log.debug(\n { threadId, userId, text: text.slice(0, 50), attachmentCount: message.attachments.size },\n '[DiscordAdapter] messageCreate received',\n )\n\n // Ignore messages with no text and no attachments\n if (!text && message.attachments.size === 0) return\n\n // Resolve sessionId for file storage (fallback to \"unknown\" for new sessions)\n const sessionId =\n this.core.sessionManager.getSessionByThread('discord', threadId)?.id ?? 'unknown'\n\n // Process attachments\n if (message.attachments.size > 0) {\n log.info(\n {\n sessionId,\n attachments: message.attachments.map((a) => ({\n name: a.name, size: a.size, contentType: a.contentType, url: a.url?.slice(0, 80),\n })),\n },\n '[discord-media] Processing incoming attachments',\n )\n }\n const attachments = await this.processIncomingAttachments(message, sessionId)\n\n // Generate fallback text if message has attachments but no text\n if (!text && attachments.length > 0) {\n text = buildFallbackText(attachments)\n }\n\n // If all attachment downloads failed and no text, notify user\n if (!text && attachments.length === 0 && message.attachments.size > 0) {\n try {\n await message.reply('Failed to process attachment(s)')\n } catch { /* best effort */ }\n return\n }\n\n // Route assistant thread messages to assistant\n if (\n this.discordConfig.assistantThreadId &&\n threadId === this.discordConfig.assistantThreadId\n ) {\n if (this.assistantSession && text) {\n await this.assistantSession.enqueuePrompt(text, attachments.length > 0 ? attachments : undefined)\n }\n return\n }\n\n // Route to core for session dispatch\n await this.core.handleMessage({\n channelId: 'discord',\n threadId,\n userId,\n text,\n ...(attachments.length > 0 ? { attachments } : {}),\n })\n } catch (err) {\n log.error({ err }, '[DiscordAdapter] messageCreate handler error')\n }\n })\n }\n\n // ─── Assistant ────────────────────────────────────────────────────────────\n\n private async setupAssistant(): Promise<void> {\n let threadId = this.discordConfig.assistantThreadId\n\n // Verify existing thread is still accessible\n if (threadId) {\n try {\n const existing = this.guild.channels.cache.get(threadId)\n ?? await this.guild.channels.fetch(threadId)\n if (existing && existing.isThread()) {\n await ensureUnarchived(existing as import('discord.js').ThreadChannel)\n log.info({ threadId }, '[DiscordAdapter] Reusing existing assistant thread')\n } else {\n log.warn({ threadId }, '[DiscordAdapter] Assistant thread not found, recreating...')\n threadId = null\n }\n } catch {\n log.warn({ threadId }, '[DiscordAdapter] Assistant thread inaccessible, recreating...')\n threadId = null\n }\n }\n\n if (!threadId) {\n // Create a new thread for the assistant\n const thread = await forumsCreateThread(this.forumChannel, 'Assistant')\n threadId = thread.id\n await this.core.configManager.save({\n channels: { discord: { assistantThreadId: thread.id } },\n } as Parameters<typeof this.core.configManager.save>[0])\n log.info({ threadId }, '[DiscordAdapter] Created assistant thread')\n }\n\n this.assistantInitializing = true\n try {\n const { session, ready } = await spawnAssistant(this.core, threadId)\n this.assistantSession = session\n ready.finally(() => {\n this.assistantInitializing = false\n })\n } catch (err) {\n this.assistantInitializing = false\n log.error({ err }, '[DiscordAdapter] Failed to spawn assistant')\n }\n }\n\n async respawnAssistant(): Promise<void> {\n if (this.assistantSession) {\n try {\n await this.assistantSession.destroy()\n } catch { /* ignore */ }\n this.assistantSession = null\n }\n await this.setupAssistant()\n }\n\n // ─── Incoming media ──────────────────────────────────────────────────\n\n private async processIncomingAttachments(\n message: import('discord.js').Message,\n sessionId: string,\n ): Promise<Attachment[]> {\n if (message.attachments.size === 0) return []\n\n const isVoiceMessage = message.flags.has(MessageFlags.IsVoiceMessage)\n\n const results = await Promise.allSettled(\n message.attachments.map(async (discordAtt) => {\n const buffer = await downloadDiscordAttachment(\n discordAtt.url,\n discordAtt.name ?? 'attachment',\n )\n if (!buffer) return null\n\n let data = buffer\n let fileName = discordAtt.name ?? 'attachment'\n let mimeType = discordAtt.contentType ?? 'application/octet-stream'\n\n // Convert voice messages from OGG Opus to WAV\n if (isVoiceMessage && mimeType.includes('ogg')) {\n try {\n data = await this.fileService.convertOggToWav(buffer)\n fileName = 'voice.wav'\n mimeType = 'audio/wav'\n } catch (err) {\n log.warn({ err }, '[discord-media] OGG→WAV conversion failed, saving original')\n }\n }\n\n return this.fileService.saveFile(sessionId, fileName, data, mimeType)\n }),\n )\n\n const rejected = results.filter((r) => r.status === 'rejected')\n if (rejected.length > 0) {\n log.warn({ rejected: rejected.map((r) => (r as PromiseRejectedResult).reason) }, '[discord-media] Some attachments failed')\n }\n\n const saved = results\n .filter((r): r is PromiseFulfilledResult<Attachment | null> => r.status === 'fulfilled')\n .map((r) => r.value)\n .filter((att): att is Attachment => att !== null)\n\n log.info({ count: saved.length, files: saved.map((a) => a.fileName) }, '[discord-media] Attachments processed')\n return saved\n }\n\n // ─── Helper: resolve thread ───────────────────────────────────────────────\n\n private async getThread(sessionId: string): Promise<ThreadChannel | null> {\n const session = this.core.sessionManager.getSession(sessionId)\n const threadId = session?.threadId\n if (!threadId) {\n log.warn({ sessionId }, '[DiscordAdapter] No threadId for session')\n return null\n }\n try {\n const channel = this.guild.channels.cache.get(threadId)\n ?? await this.guild.channels.fetch(threadId)\n if (channel && channel.isThread()) return channel as ThreadChannel\n log.warn({ sessionId, threadId }, '[DiscordAdapter] Channel is not a thread')\n return null\n } catch (err) {\n log.warn({ err, sessionId, threadId }, '[DiscordAdapter] Failed to fetch thread')\n return null\n }\n }\n\n // ─── Helper: get or create activity tracker ──────────────────────────────\n\n private getOrCreateTracker(sessionId: string, thread: ThreadChannel): ActivityTracker {\n if (!this.sessionTrackers.has(sessionId)) {\n this.sessionTrackers.set(sessionId, new ActivityTracker(thread, this.sendQueue))\n }\n return this.sessionTrackers.get(sessionId)!\n }\n\n // --- MessageHandlers for dispatchMessage ---\n\n private messageHandlers: MessageHandlers<DiscordMessageCtx> = {\n onThought: async (ctx, _content) => {\n const tracker = this.getOrCreateTracker(ctx.sessionId, ctx.thread)\n await tracker.onThought()\n },\n\n onText: async (ctx, content) => {\n const tracker = this.getOrCreateTracker(ctx.sessionId, ctx.thread)\n await tracker.onTextStart()\n const draft = this.draftManager.getOrCreate(ctx.sessionId, ctx.thread)\n draft.append(content.text)\n this.draftManager.appendText(ctx.sessionId, content.text)\n },\n\n onToolCall: async (ctx, content) => {\n const meta = content.metadata ?? {}\n const toolName = String(meta.name ?? content.text ?? 'Tool')\n const toolKind = String(meta.kind ?? 'other')\n const noiseAction = evaluateNoise(toolName, toolKind, meta.rawInput)\n if (noiseAction === 'hide' && this.verbosity !== 'high') return\n if (noiseAction === 'collapse' && this.verbosity === 'low') return\n\n const tracker = this.getOrCreateTracker(ctx.sessionId, ctx.thread)\n await tracker.onToolCall()\n await this.draftManager.finalize(ctx.sessionId, ctx.thread, ctx.isAssistant)\n await this.toolTracker.trackNewCall(ctx.sessionId, ctx.thread, {\n id: String(meta.id ?? ''),\n name: toolName,\n kind: meta.kind as string | undefined,\n status: String(meta.status ?? 'running'),\n content: meta.content,\n viewerLinks: meta.viewerLinks as { file?: string; diff?: string } | undefined,\n viewerFilePath: meta.viewerFilePath as string | undefined,\n }, this.verbosity)\n },\n\n onToolUpdate: async (ctx, content) => {\n const meta = content.metadata ?? {}\n await this.toolTracker.updateCall(ctx.sessionId, {\n id: String(meta.id ?? ''),\n name: content.text || String(meta.name ?? ''),\n kind: meta.kind as string | undefined,\n status: String(meta.status ?? 'completed'),\n content: meta.content,\n viewerLinks: meta.viewerLinks as { file?: string; diff?: string } | undefined,\n viewerFilePath: meta.viewerFilePath as string | undefined,\n }, this.verbosity)\n },\n\n onPlan: async (ctx, content) => {\n const entries = (content.metadata?.entries ?? []) as PlanEntry[]\n const tracker = this.getOrCreateTracker(ctx.sessionId, ctx.thread)\n await tracker.onPlan(entries)\n },\n\n onUsage: async (ctx, content) => {\n await this.draftManager.finalize(ctx.sessionId, ctx.thread, ctx.isAssistant)\n const meta = content.metadata ?? {}\n const tracker = this.getOrCreateTracker(ctx.sessionId, ctx.thread)\n await tracker.sendUsage({\n tokensUsed: meta.tokensUsed as number | undefined,\n contextSize: meta.contextSize as number | undefined,\n })\n // Send usage notification to notification channel\n try {\n const deepLink = buildDeepLink(this.guild.id, ctx.thread.id)\n await this.sendNotification({\n sessionId: ctx.sessionId,\n type: 'completed',\n summary: content.text || 'Session completed',\n deepLink,\n })\n } catch { /* best effort */ }\n },\n\n onSessionEnd: async (ctx, _content) => {\n await this.draftManager.finalize(ctx.sessionId, ctx.thread, ctx.isAssistant)\n const tracker = this.getOrCreateTracker(ctx.sessionId, ctx.thread)\n await tracker.cleanup()\n this.toolTracker.cleanup(ctx.sessionId)\n this.sessionTrackers.delete(ctx.sessionId)\n await this.skillManager.cleanup(ctx.sessionId)\n try {\n await this.sendQueue.enqueue(\n () => ctx.thread.send({ content: '✅ Done' }),\n { type: 'other' },\n )\n } catch { /* best effort */ }\n },\n\n onError: async (ctx, content) => {\n await this.draftManager.finalize(ctx.sessionId, ctx.thread, ctx.isAssistant)\n const tracker = this.getOrCreateTracker(ctx.sessionId, ctx.thread)\n await tracker.cleanup()\n this.toolTracker.cleanup(ctx.sessionId)\n this.sessionTrackers.delete(ctx.sessionId)\n try {\n await this.sendQueue.enqueue(\n () => ctx.thread.send({ content: `❌ Error: ${content.text}` }),\n { type: 'other' },\n )\n } catch { /* best effort */ }\n },\n\n onAttachment: async (ctx, content) => {\n if (!content.attachment) return\n const { attachment } = content\n await this.draftManager.finalize(ctx.sessionId, ctx.thread, ctx.isAssistant)\n\n // Discord free tier limit: 25MB\n if (isAttachmentTooLarge(attachment.size)) {\n log.warn({ sessionId: ctx.sessionId, fileName: attachment.fileName, size: attachment.size }, '[discord-media] File too large (>25MB)')\n try {\n await this.sendQueue.enqueue(\n () => ctx.thread.send({ content: `⚠️ File too large to send (${Math.round(attachment.size / 1024 / 1024)}MB): ${attachment.fileName}` }),\n { type: 'other' },\n )\n } catch { /* best effort */ }\n return\n }\n\n try {\n await this.sendQueue.enqueue(\n () => ctx.thread.send({ files: [{ attachment: attachment.filePath, name: attachment.fileName }] }),\n { type: 'other' },\n )\n\n // Strip [TTS]...[/TTS] block from the text message after audio is sent.\n // This fires after sendQueue completes, so the draft message already exists.\n // stripPattern is best-effort and handles missing/finalized drafts gracefully.\n if (attachment.type === 'audio') {\n const draft = this.draftManager.getDraft(ctx.sessionId)\n if (draft) {\n draft.stripPattern(/\\[TTS\\][\\s\\S]*?\\[\\/TTS\\]/g).catch(() => {})\n }\n }\n } catch (err) {\n log.error({ err, sessionId: ctx.sessionId, fileName: attachment.fileName }, '[discord-media] Failed to send attachment')\n }\n },\n\n onSystemMessage: async (ctx, content) => {\n try {\n await this.sendQueue.enqueue(\n () => ctx.thread.send({ content: content.text }),\n { type: 'other' },\n )\n } catch { /* best effort */ }\n },\n }\n\n // ─── sendMessage ──────────────────────────────────────────────────────────\n\n async sendMessage(sessionId: string, content: OutgoingMessage): Promise<void> {\n // Suppress output while assistant is initializing its system prompt\n if (\n this.assistantInitializing &&\n this.assistantSession &&\n sessionId === this.assistantSession.id\n ) {\n return\n }\n\n const thread = await this.getThread(sessionId)\n if (!thread) return\n\n await ensureUnarchived(thread)\n\n const isAssistant =\n this.assistantSession != null && sessionId === this.assistantSession.id\n\n const ctx: DiscordMessageCtx = { sessionId, thread, isAssistant }\n await dispatchMessage(this.messageHandlers, ctx, content, this.verbosity)\n }\n\n // ─── sendPermissionRequest ────────────────────────────────────────────────\n\n async sendPermissionRequest(sessionId: string, request: PermissionRequest): Promise<void> {\n const session = this.core.sessionManager.getSession(sessionId)\n if (!session) {\n log.warn({ sessionId }, '[DiscordAdapter] sendPermissionRequest: session not found')\n return\n }\n\n const thread = await this.getThread(sessionId)\n if (!thread) return\n\n await this.permissionHandler.sendPermissionRequest(session, request, thread)\n }\n\n // ─── sendNotification ─────────────────────────────────────────────────────\n\n async sendNotification(notification: NotificationMessage): Promise<void> {\n if (!this.notificationChannel) return\n\n const typeIcon: Record<string, string> = {\n completed: '✅',\n error: '❌',\n permission: '🔐',\n input_required: '💬',\n }\n\n const icon = typeIcon[notification.type] ?? 'ℹ️'\n const name = notification.sessionName ? ` **${notification.sessionName}**` : ''\n let text = `${icon}${name}: ${notification.summary}`\n if (notification.deepLink) {\n text += `\\n${notification.deepLink}`\n }\n\n try {\n await this.sendQueue.enqueue(\n () => this.notificationChannel.send({ content: text }),\n { type: 'other' },\n )\n } catch (err) {\n log.warn({ err }, '[DiscordAdapter] Failed to send notification')\n }\n }\n\n // ─── createSessionThread ─────────────────────────────────────────────────\n\n async createSessionThread(sessionId: string, name: string): Promise<string> {\n const thread = await forumsCreateThread(this.forumChannel, name)\n\n // Persist threadId on session record\n const session = this.core.sessionManager.getSession(sessionId)\n if (session) {\n session.threadId = thread.id\n }\n\n const record = this.core.sessionManager.getSessionRecord(sessionId)\n if (record) {\n await this.core.sessionManager.patchRecord(sessionId, {\n platform: { ...record.platform, threadId: thread.id },\n })\n }\n\n return thread.id\n }\n\n // ─── renameSessionThread ──────────────────────────────────────────────────\n\n async renameSessionThread(sessionId: string, newName: string): Promise<void> {\n const session = this.core.sessionManager.getSession(sessionId)\n const threadId = session?.threadId\n if (!threadId) return\n await forumsRenameThread(this.guild, threadId, newName)\n }\n\n // ─── deleteSessionThread ──────────────────────────────────────────────────\n\n override async deleteSessionThread(sessionId: string): Promise<void> {\n const session = this.core.sessionManager.getSession(sessionId)\n const threadId = session?.threadId\n if (!threadId) return\n await forumsDeleteThread(this.guild, threadId)\n }\n\n // ─── sendSkillCommands ────────────────────────────────────────────────────\n\n override async sendSkillCommands(sessionId: string, commands: AgentCommand[]): Promise<void> {\n const thread = await this.getThread(sessionId)\n if (!thread) return\n await this.skillManager.send(sessionId, thread, commands)\n }\n\n // ─── cleanupSkillCommands ─────────────────────────────────────────────────\n\n override async cleanupSkillCommands(sessionId: string): Promise<void> {\n await this.skillManager.cleanup(sessionId)\n }\n\n // ─── Public helpers (for slash commands) ─────────────────────────────────\n\n getForumChannel(): ForumChannel | TextChannel {\n return this.forumChannel\n }\n\n getGuild(): Guild {\n return this.guild\n }\n\n getGuildId(): string {\n return this.guild.id\n }\n\n getAssistantSessionId(): string | null {\n return this.assistantSession?.id ?? null\n }\n\n getAssistantThreadId(): string | null {\n return this.discordConfig.assistantThreadId\n }\n}\n","import { log } from '../../core/log.js'\n\nexport type QueueItemType = 'text' | 'other'\n\ninterface QueueItem<T = unknown> {\n fn: () => Promise<T>\n type: QueueItemType\n key?: string\n resolve: (value: T | undefined) => void\n reject: (err: unknown) => void\n}\n\nexport class DiscordSendQueue {\n private items: QueueItem[] = []\n private processing = false\n private lastExec = 0\n private minInterval: number\n\n constructor(minInterval = 1000) {\n this.minInterval = minInterval\n }\n\n enqueue<T>(\n fn: () => Promise<T>,\n opts: { type: 'text' | 'other'; key?: string },\n ): Promise<T | undefined> {\n const type = opts.type\n const key = opts.key\n\n return new Promise<T | undefined>((resolve, reject) => {\n if (type === 'text' && key) {\n const idx = this.items.findIndex(\n (item) => item.type === 'text' && item.key === key,\n )\n if (idx !== -1) {\n // Resolve old pending item with undefined (dedup: replace with newer)\n this.items[idx].resolve(undefined)\n this.items[idx] = { fn, type, key, resolve, reject } as QueueItem\n this.scheduleProcess()\n return\n }\n }\n\n this.items.push({ fn, type, key, resolve, reject } as QueueItem)\n this.scheduleProcess()\n })\n }\n\n onRateLimited(): void {\n log.warn('[DiscordSendQueue] Rate limited — dropping queued text items')\n const remaining: QueueItem[] = []\n for (const item of this.items) {\n if (item.type === 'text') {\n item.resolve(undefined)\n } else {\n remaining.push(item)\n }\n }\n this.items = remaining\n }\n\n private scheduleProcess(): void {\n if (this.processing) return\n if (this.items.length === 0) return\n\n const elapsed = Date.now() - this.lastExec\n const delay = Math.max(0, this.minInterval - elapsed)\n\n this.processing = true\n setTimeout(() => void this.processNext(), delay)\n }\n\n private async processNext(): Promise<void> {\n const item = this.items.shift()\n if (!item) {\n this.processing = false\n return\n }\n\n try {\n const result = await item.fn()\n item.resolve(result)\n } catch (err) {\n item.reject(err)\n } finally {\n this.lastExec = Date.now()\n this.processing = false\n this.scheduleProcess()\n }\n }\n}\n","import type { PlanEntry } from \"../../core/types.js\";\nimport type {\n ToolCallMeta,\n ToolUpdateMeta,\n ViewerLinks,\n} from \"../shared/format-types.js\";\nimport { STATUS_ICONS } from \"../shared/format-types.js\";\nimport {\n progressBar,\n formatTokens,\n truncateContent,\n stripCodeFences,\n splitMessage as sharedSplitMessage,\n} from \"../shared/format-utils.js\";\nimport {\n extractContentText,\n formatToolSummary,\n formatToolTitle,\n} from \"../shared/message-formatter.js\";\nimport type { DisplayVerbosity } from \"../shared/format-types.js\";\n\nfunction formatViewerLinks(links?: ViewerLinks, filePath?: string): string {\n if (!links) return \"\";\n const fileName = filePath ? filePath.split(\"/\").pop() || filePath : \"\";\n let text = \"\\n\";\n if (links.file) text += `\\n[View ${fileName || \"file\"}](${links.file})`;\n if (links.diff)\n text += `\\n[View diff${fileName ? ` — ${fileName}` : \"\"}](${links.diff})`;\n return text;\n}\n\nexport function formatToolCall(\n tool: ToolCallMeta,\n verbosity: DisplayVerbosity = \"medium\",\n): string {\n const si = STATUS_ICONS[tool.status || \"\"] || \"🔧\";\n const name = tool.name || \"Tool\";\n const label =\n verbosity === \"low\"\n ? formatToolTitle(name, tool.rawInput)\n : formatToolSummary(name, tool.rawInput);\n let text = `${si} **${label}**`;\n text += formatViewerLinks(tool.viewerLinks, tool.viewerFilePath);\n if (verbosity === \"high\" || (verbosity === \"medium\" && !tool.viewerLinks)) {\n const details = stripCodeFences(extractContentText(tool.content));\n if (details) {\n text += `\\n\\`\\`\\`\\n${truncateContent(details, 500)}\\n\\`\\`\\``;\n }\n }\n return text;\n}\n\nexport function formatToolUpdate(\n update: ToolUpdateMeta,\n verbosity: DisplayVerbosity = \"medium\",\n): string {\n return formatToolCall(update, verbosity);\n}\n\nexport function formatPlan(entries: PlanEntry[]): string {\n const statusIcon: Record<string, string> = {\n pending: \"⏳\",\n in_progress: \"🔄\",\n completed: \"✅\",\n };\n const lines = entries.map(\n (e, i) => `${statusIcon[e.status] || \"⬜\"} ${i + 1}. ${e.content}`,\n );\n return `**Plan:**\\n${lines.join(\"\\n\")}`;\n}\n\nexport function formatUsage(usage: {\n tokensUsed?: number;\n contextSize?: number;\n}): string {\n const { tokensUsed, contextSize } = usage;\n if (tokensUsed == null) return \"📊 Usage data unavailable\";\n if (contextSize == null) return `📊 ${formatTokens(tokensUsed)} tokens`;\n\n const ratio = tokensUsed / contextSize;\n const pct = Math.round(ratio * 100);\n const bar = progressBar(ratio);\n const emoji = pct >= 85 ? \"⚠️\" : \"📊\";\n return `${emoji} ${formatTokens(tokensUsed)} / ${formatTokens(contextSize)} tokens\\n${bar} ${pct}%`;\n}\n\nexport function splitMessage(text: string, maxLength = 1800): string[] {\n return sharedSplitMessage(text, maxLength);\n}\n","import type { TextChannel, ThreadChannel, Message } from \"discord.js\";\nimport { log } from \"../../core/log.js\";\nimport { formatToolCall, formatToolUpdate } from \"./formatting.js\";\nimport type { DiscordSendQueue } from \"./send-queue.js\";\nimport type {\n ToolCallMeta,\n ViewerLinks,\n DisplayVerbosity,\n} from \"../shared/format-types.js\";\n\ninterface ToolCallState {\n message?: Message;\n name: string;\n kind?: string;\n rawInput?: unknown;\n viewerLinks?: ViewerLinks;\n viewerFilePath?: string;\n ready: Promise<void>;\n}\n\nexport class ToolCallTracker {\n sessions: Map<string, Map<string, ToolCallState>> = new Map();\n\n constructor(private sendQueue: DiscordSendQueue) {}\n\n async trackNewCall(\n sessionId: string,\n thread: TextChannel | ThreadChannel,\n tool: ToolCallMeta,\n verbosity: DisplayVerbosity = \"medium\",\n ): Promise<void> {\n if (!this.sessions.has(sessionId)) {\n this.sessions.set(sessionId, new Map());\n }\n\n let resolveReady!: () => void;\n const ready = new Promise<void>((r) => {\n resolveReady = r;\n });\n\n const state: ToolCallState = {\n message: undefined,\n name: tool.name,\n kind: tool.kind,\n rawInput: tool.rawInput,\n viewerLinks: tool.viewerLinks,\n viewerFilePath: tool.viewerFilePath,\n ready,\n };\n\n this.sessions.get(sessionId)!.set(tool.id, state);\n\n const content = formatToolCall(tool, verbosity);\n\n try {\n const msg = await this.sendQueue.enqueue(() => thread.send({ content }), {\n type: \"other\",\n });\n if (msg) state.message = msg;\n } catch (err) {\n log.warn(\n { err, toolId: tool.id },\n \"[ToolCallTracker] trackNewCall() send failed\",\n );\n } finally {\n resolveReady();\n }\n }\n\n async updateCall(\n sessionId: string,\n update: ToolCallMeta & { status: string },\n verbosity: DisplayVerbosity = \"medium\",\n ): Promise<void> {\n const toolState = this.sessions.get(sessionId)?.get(update.id);\n if (!toolState) return;\n\n // Accumulate fields from intermediate updates\n if (update.viewerLinks) toolState.viewerLinks = update.viewerLinks;\n if (update.viewerFilePath) toolState.viewerFilePath = update.viewerFilePath;\n if (update.name) toolState.name = update.name;\n if (update.kind) toolState.kind = update.kind;\n\n // Only edit on terminal status — minimizes API calls to avoid rate limits\n const isTerminal =\n update.status === \"completed\" || update.status === \"failed\";\n if (!isTerminal) return;\n\n // Wait for initial send to complete before editing\n await toolState.ready;\n\n if (!toolState.message) return;\n\n log.debug(\n {\n toolId: update.id,\n status: update.status,\n hasViewerLinks: !!toolState.viewerLinks,\n name: toolState.name,\n msgId: toolState.message.id,\n },\n \"[ToolCallTracker] Tool completed, preparing edit\",\n );\n\n const merged = {\n ...update,\n name: toolState.name,\n kind: toolState.kind,\n rawInput: toolState.rawInput,\n viewerLinks: toolState.viewerLinks,\n viewerFilePath: toolState.viewerFilePath,\n };\n const content = formatToolUpdate(merged, verbosity);\n\n try {\n await this.sendQueue.enqueue(() => toolState.message!.edit({ content }), {\n type: \"other\",\n });\n } catch (err) {\n log.warn(\n {\n err,\n msgId: toolState.message.id,\n contentLen: content.length,\n hasViewerLinks: !!merged.viewerLinks,\n },\n \"[ToolCallTracker] Tool update edit failed\",\n );\n }\n }\n\n cleanup(sessionId: string): void {\n this.sessions.delete(sessionId);\n }\n}\n","import type { TextChannel, ThreadChannel, Message } from 'discord.js'\nimport { splitMessage } from './formatting.js'\nimport type { DiscordSendQueue } from './send-queue.js'\n\nconst FLUSH_INTERVAL = 5000\nconst MAX_DISPLAY_LENGTH = 1900\n\nexport class MessageDraft {\n private buffer: string = ''\n private message?: Message\n private flushTimer?: ReturnType<typeof setTimeout>\n private flushPromise: Promise<void> = Promise.resolve()\n private lastSentBuffer: string = ''\n private displayTruncated = false\n private firstFlushPending = false\n\n constructor(\n private thread: TextChannel | ThreadChannel,\n private sendQueue: DiscordSendQueue,\n private sessionId: string,\n ) {}\n\n append(text: string): void {\n if (!text) return\n this.buffer += text\n this.scheduleFlush()\n }\n\n getBuffer(): string {\n return this.buffer\n }\n\n private scheduleFlush(): void {\n if (this.flushTimer) return\n this.flushTimer = setTimeout(() => {\n this.flushTimer = undefined\n this.flushPromise = this.flushPromise\n .then(() => this.flush())\n .catch(() => {})\n }, FLUSH_INTERVAL)\n }\n\n async flush(): Promise<void> {\n if (!this.buffer) return\n if (this.firstFlushPending) return\n\n // Snapshot buffer before any await — append() can be called concurrently\n const snapshot = this.buffer\n\n let content = snapshot\n let truncated = false\n if (content.length > MAX_DISPLAY_LENGTH) {\n content = snapshot.slice(0, MAX_DISPLAY_LENGTH) + '…'\n truncated = true\n }\n\n if (!content) return\n\n if (!this.message) {\n this.firstFlushPending = true\n try {\n const result = await this.sendQueue.enqueue(\n () => this.thread.send({ content }),\n { type: 'other' },\n )\n if (result) {\n this.message = result\n if (!truncated) {\n this.lastSentBuffer = snapshot\n this.displayTruncated = false\n } else {\n this.displayTruncated = true\n }\n }\n } catch {\n // send failed — next flush will retry\n } finally {\n this.firstFlushPending = false\n }\n } else {\n // Skip if content hasn't changed since last send\n if (!truncated && snapshot === this.lastSentBuffer) return\n\n try {\n const result = await this.sendQueue.enqueue(\n () => this.message!.edit({ content }),\n { type: 'text', key: this.sessionId },\n )\n // Only mark as sent if the edit was actually executed (not deduped/dropped)\n if (result !== undefined) {\n if (!truncated) {\n this.lastSentBuffer = snapshot\n this.displayTruncated = false\n } else {\n this.displayTruncated = true\n }\n }\n } catch {\n // Don't reset message — transient errors should not cause duplicate sends\n }\n }\n }\n\n async stripPattern(pattern: RegExp): Promise<void> {\n if (!this.message || !this.buffer) return\n\n const stripped = this.buffer.replace(pattern, '').trim()\n if (stripped === this.buffer.trim()) return\n\n this.buffer = stripped\n this.lastSentBuffer = stripped\n\n if (!stripped) return\n\n try {\n await this.sendQueue.enqueue(\n () => this.message!.edit({ content: stripped }),\n { type: 'other' },\n )\n } catch {\n // Best effort — non-critical edit\n }\n }\n\n async finalize(): Promise<void> {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer)\n this.flushTimer = undefined\n }\n\n // Wait for any in-progress flush to complete\n await this.flushPromise\n\n if (!this.buffer) return\n\n // Skip if buffer was already fully sent and nothing new appended\n if (this.message && this.buffer === this.lastSentBuffer && !this.displayTruncated) {\n return\n }\n\n // Try to send full buffer as a single message (most common case)\n if (this.buffer.length <= MAX_DISPLAY_LENGTH) {\n const content = this.buffer\n try {\n if (this.message) {\n await this.sendQueue.enqueue(\n () => this.message!.edit({ content }),\n { type: 'other' },\n )\n } else {\n await this.sendQueue.enqueue(\n () => this.thread.send({ content }),\n { type: 'other' },\n )\n }\n return\n } catch {\n // Fall through to split approach\n }\n }\n\n // Buffer exceeds limit or single send failed — split and send chunks\n const chunks = splitMessage(this.buffer, MAX_DISPLAY_LENGTH)\n\n for (let i = 0; i < chunks.length; i++) {\n const content = chunks[i]\n try {\n if (i === 0 && this.message) {\n await this.sendQueue.enqueue(\n () => this.message!.edit({ content }),\n { type: 'other' },\n )\n } else {\n const msg = await this.sendQueue.enqueue(\n () => this.thread.send({ content }),\n { type: 'other' },\n )\n if (msg) {\n this.message = msg\n }\n }\n } catch {\n // Skip this chunk — best effort\n }\n }\n }\n}\n","import type { TextChannel, ThreadChannel } from 'discord.js'\nimport { MessageDraft } from './streaming.js'\nimport type { DiscordSendQueue } from './send-queue.js'\nimport { detectAction, storeAction, buildActionKeyboard } from './action-detect.js'\n\nexport class DraftManager {\n drafts: Map<string, MessageDraft> = new Map()\n textBuffers: Map<string, string> = new Map()\n\n constructor(\n private sendQueue: DiscordSendQueue,\n ) {}\n\n getOrCreate(sessionId: string, thread: TextChannel | ThreadChannel): MessageDraft {\n let draft = this.drafts.get(sessionId)\n if (!draft) {\n draft = new MessageDraft(thread, this.sendQueue, sessionId)\n this.drafts.set(sessionId, draft)\n }\n return draft\n }\n\n hasDraft(sessionId: string): boolean {\n return this.drafts.has(sessionId)\n }\n\n getDraft(sessionId: string): MessageDraft | undefined {\n return this.drafts.get(sessionId)\n }\n\n appendText(sessionId: string, text: string): void {\n this.textBuffers.set(sessionId, (this.textBuffers.get(sessionId) ?? '') + text)\n }\n\n /**\n * Finalize the current draft.\n * If isAssistant is true, detects action patterns in the accumulated text and sends\n * action buttons as a follow-up message.\n */\n async finalize(\n sessionId: string,\n thread?: TextChannel | ThreadChannel,\n isAssistant?: boolean,\n ): Promise<void> {\n const draft = this.drafts.get(sessionId)\n if (!draft) return\n\n // Delete BEFORE awaiting to prevent concurrent finalize() calls\n // from double-finalizing the same draft\n this.drafts.delete(sessionId)\n await draft.finalize()\n\n // Detect actions in assistant responses and attach action buttons\n if (isAssistant && thread) {\n const fullText = this.textBuffers.get(sessionId)\n this.textBuffers.delete(sessionId)\n if (fullText) {\n const detected = detectAction(fullText)\n if (detected) {\n const actionId = storeAction(detected)\n const components = [buildActionKeyboard(actionId, detected)]\n try {\n await this.sendQueue.enqueue(\n () => thread.send({ components }),\n { type: 'other' },\n )\n } catch {\n // Best effort — action buttons are non-critical\n }\n }\n }\n } else {\n this.textBuffers.delete(sessionId)\n }\n }\n\n cleanup(sessionId: string): void {\n this.drafts.delete(sessionId)\n this.textBuffers.delete(sessionId)\n }\n}\n","import type { TextChannel, ThreadChannel, Message } from 'discord.js'\nimport { EmbedBuilder } from 'discord.js'\nimport { log } from '../../core/log.js'\nimport type { PlanEntry } from '../../core/types.js'\nimport { formatUsage, formatPlan } from './formatting.js'\nimport type { DiscordSendQueue } from './send-queue.js'\n\n// ─── ThinkingIndicator ────────────────────────────────────────────────────────\n\nconst TYPING_REFRESH_MS = 8_000\n\nexport class ThinkingIndicator {\n private dismissed = false\n private refreshTimer?: ReturnType<typeof setInterval>\n\n constructor(\n private channel: TextChannel | ThreadChannel,\n ) {}\n\n async show(): Promise<void> {\n if (this.dismissed) return\n try {\n await this.channel.sendTyping()\n this.startRefreshTimer()\n } catch (err) {\n log.warn({ err }, '[ThinkingIndicator] sendTyping() failed')\n }\n }\n\n dismiss(): void {\n this.dismissed = true\n this.stopRefreshTimer()\n }\n\n reset(): void {\n this.dismissed = false\n }\n\n private startRefreshTimer(): void {\n this.stopRefreshTimer()\n this.refreshTimer = setInterval(() => {\n if (this.dismissed) {\n this.stopRefreshTimer()\n return\n }\n this.channel.sendTyping().catch(() => {})\n }, TYPING_REFRESH_MS)\n }\n\n private stopRefreshTimer(): void {\n if (this.refreshTimer) {\n clearInterval(this.refreshTimer)\n this.refreshTimer = undefined\n }\n }\n}\n\n// ─── UsageMessage ─────────────────────────────────────────────────────────────\n\nexport class UsageMessage {\n private message?: Message\n\n constructor(\n private thread: TextChannel | ThreadChannel,\n private sendQueue: DiscordSendQueue,\n ) {}\n\n async send(usage: { tokensUsed?: number; contextSize?: number }): Promise<void> {\n const text = formatUsage(usage)\n const embed = new EmbedBuilder().setDescription(text)\n try {\n if (this.message) {\n await this.sendQueue.enqueue(\n () => this.message!.edit({ embeds: [embed] }),\n { type: 'other' },\n )\n } else {\n const result = await this.sendQueue.enqueue(\n () => this.thread.send({ embeds: [embed] }),\n { type: 'other' },\n )\n if (result) this.message = result\n }\n } catch (err) {\n log.warn({ err }, '[UsageMessage] send() failed')\n }\n }\n\n async delete(): Promise<void> {\n if (!this.message) return\n const msg = this.message\n this.message = undefined\n try {\n await this.sendQueue.enqueue(\n () => msg.delete(),\n { type: 'other' },\n )\n } catch (err) {\n log.warn({ err }, '[UsageMessage] delete() failed')\n }\n }\n}\n\n// ─── PlanCard ─────────────────────────────────────────────────────────────────\n\nconst PLAN_DEBOUNCE_MS = 3_500\n\nexport class PlanCard {\n private message?: Message\n private flushPromise: Promise<void> = Promise.resolve()\n private latestEntries?: PlanEntry[]\n private lastSentText?: string\n private flushTimer?: ReturnType<typeof setTimeout>\n\n constructor(\n private thread: TextChannel | ThreadChannel,\n private sendQueue: DiscordSendQueue,\n ) {}\n\n update(entries: PlanEntry[]): void {\n this.latestEntries = entries\n if (this.flushTimer) clearTimeout(this.flushTimer)\n this.flushTimer = setTimeout(() => {\n this.flushTimer = undefined\n this.flushPromise = this.flushPromise\n .then(() => this._flush())\n .catch(() => {})\n }, PLAN_DEBOUNCE_MS)\n }\n\n async finalize(): Promise<void> {\n if (!this.latestEntries) return\n if (this.flushTimer) {\n clearTimeout(this.flushTimer)\n this.flushTimer = undefined\n }\n await this.flushPromise\n this.flushPromise = this.flushPromise\n .then(() => this._flush())\n .catch(() => {})\n await this.flushPromise\n }\n\n destroy(): void {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer)\n this.flushTimer = undefined\n }\n }\n\n private async _flush(): Promise<void> {\n if (!this.latestEntries) return\n const text = formatPlan(this.latestEntries)\n if (this.message && text === this.lastSentText) return\n this.lastSentText = text\n const embed = new EmbedBuilder().setDescription(text)\n try {\n if (this.message) {\n await this.sendQueue.enqueue(\n () => this.message!.edit({ embeds: [embed] }),\n { type: 'other' },\n )\n } else {\n const result = await this.sendQueue.enqueue(\n () => this.thread.send({ embeds: [embed] }),\n { type: 'other' },\n )\n if (result) this.message = result\n }\n } catch (err) {\n log.warn({ err }, '[PlanCard] flush failed')\n }\n }\n}\n\n// ─── ActivityTracker ──────────────────────────────────────────────────────────\n\nexport class ActivityTracker {\n private isFirstEvent = true\n private hasPlanCard = false\n private thinking: ThinkingIndicator\n private planCard: PlanCard\n private usage: UsageMessage\n\n constructor(\n private thread: TextChannel | ThreadChannel,\n private sendQueue: DiscordSendQueue,\n ) {\n this.thinking = new ThinkingIndicator(thread)\n this.planCard = new PlanCard(thread, sendQueue)\n this.usage = new UsageMessage(thread, sendQueue)\n }\n\n async onNewPrompt(): Promise<void> {\n this.isFirstEvent = true\n this.hasPlanCard = false\n this.thinking.dismiss()\n this.thinking.reset()\n }\n\n async onThought(): Promise<void> {\n await this._firstEventGuard()\n await this.thinking.show()\n }\n\n async onTextStart(): Promise<void> {\n await this._firstEventGuard()\n this.thinking.dismiss()\n }\n\n async onToolCall(): Promise<void> {\n await this._firstEventGuard()\n this.thinking.dismiss()\n this.thinking.reset()\n }\n\n async onPlan(entries: PlanEntry[]): Promise<void> {\n await this._firstEventGuard()\n this.thinking.dismiss()\n this.hasPlanCard = true\n this.planCard.update(entries)\n }\n\n async sendUsage(usage: { tokensUsed?: number; contextSize?: number }): Promise<void> {\n await this.usage.send(usage)\n }\n\n async cleanup(): Promise<void> {\n this.thinking.dismiss()\n this.planCard.destroy()\n if (this.hasPlanCard) {\n await this.planCard.finalize()\n }\n }\n\n private async _firstEventGuard(): Promise<void> {\n if (!this.isFirstEvent) return\n this.isFirstEvent = false\n await this.usage.delete()\n }\n}\n","import type { Message, TextChannel, ThreadChannel } from 'discord.js'\nimport { log } from '../../core/log.js'\nimport type { AgentCommand } from '../../core/types.js'\nimport type { SessionManager } from '../../core/session-manager.js'\nimport type { DiscordPlatformData } from '../../core/types.js'\nimport type { DiscordSendQueue } from './send-queue.js'\n\nconst DISCORD_MSG_LIMIT = 1900\n\nfunction buildSkillContent(commands: AgentCommand[]): string {\n const sorted = [...commands].sort((a, b) => a.name.localeCompare(b.name))\n const header = '**Available Skills**\\n'\n const lines = sorted.map((c) => `\\`/${c.name}\\``)\n let content = header\n for (const line of lines) {\n const candidate = content + '\\n' + line\n if (candidate.length > DISCORD_MSG_LIMIT) break\n content = candidate\n }\n return content\n}\n\nexport class SkillCommandManager {\n private messages: Map<string, Message> = new Map()\n\n constructor(\n private sendQueue: DiscordSendQueue,\n private sessionManager: SessionManager,\n ) {}\n\n async send(\n sessionId: string,\n thread: TextChannel | ThreadChannel,\n commands: AgentCommand[],\n ): Promise<void> {\n // Restore from persisted platform data if not in memory\n if (!this.messages.has(sessionId)) {\n const record = this.sessionManager.getSessionRecord(sessionId)\n const platform = record?.platform as DiscordPlatformData | undefined\n if (platform?.skillMsgId) {\n try {\n const msg = await thread.messages.fetch(platform.skillMsgId)\n if (msg) this.messages.set(sessionId, msg)\n } catch {\n // Message may no longer exist — will send a new one\n }\n }\n }\n\n // Empty commands → remove pinned message\n if (commands.length === 0) {\n await this.cleanup(sessionId)\n return\n }\n\n const content = buildSkillContent(commands)\n const existingMsg = this.messages.get(sessionId)\n\n if (existingMsg) {\n try {\n await existingMsg.edit({ content })\n return\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : ''\n if (msg.includes('Unknown Message') || msg.includes('10008')) {\n // Message no longer exists — fall through to send a new one\n this.messages.delete(sessionId)\n } else {\n // Transient error or not-modified — just return\n return\n }\n }\n }\n\n // Send new message and pin it\n try {\n const msg = await this.sendQueue.enqueue(\n () => thread.send({ content }),\n { type: 'other' },\n )\n\n if (!msg) return\n\n this.messages.set(sessionId, msg)\n\n // Persist skillMsgId so it survives restarts\n const record = this.sessionManager.getSessionRecord(sessionId)\n if (record) {\n await this.sessionManager.patchRecord(sessionId, {\n platform: { ...record.platform, skillMsgId: msg.id },\n })\n }\n\n // Pin the message\n try {\n await msg.pin()\n } catch (err) {\n log.warn({ err, sessionId }, '[SkillCommandManager] Failed to pin skill message')\n }\n } catch (err) {\n log.error({ err, sessionId }, '[SkillCommandManager] Failed to send skill commands')\n }\n }\n\n async cleanup(sessionId: string): Promise<void> {\n const msg = this.messages.get(sessionId)\n this.messages.delete(sessionId)\n\n if (msg) {\n try {\n await msg.edit({ content: '*Session ended*' })\n await msg.unpin()\n } catch {\n // Message may already be deleted\n }\n }\n\n // Clear persisted skillMsgId\n const record = this.sessionManager.getSessionRecord(sessionId)\n if (record) {\n const platform = record.platform\n if (platform && typeof platform === 'object' && 'threadId' in platform) {\n const { skillMsgId: _removed, ...rest } = platform as unknown as DiscordPlatformData\n await this.sessionManager.patchRecord(sessionId, { platform: rest })\n }\n }\n }\n}\n","import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'\nimport type { ButtonInteraction, TextChannel, ThreadChannel } from 'discord.js'\nimport { nanoid } from 'nanoid'\nimport type { PermissionRequest, NotificationMessage } from '../../core/types.js'\nimport type { Session } from '../../core/session.js'\nimport { log } from '../../core/log.js'\nimport { buildDeepLink } from './forums.js'\n\ninterface PendingPermission {\n sessionId: string\n requestId: string\n options: { id: string; isAllow: boolean }[]\n guildId: string\n channelId: string\n messageId?: string\n}\n\nexport class PermissionHandler {\n private pending: Map<string, PendingPermission> = new Map()\n\n constructor(\n private guildId: string,\n private getSession: (sessionId: string) => Session | undefined,\n private sendNotification: (notification: NotificationMessage) => Promise<void>,\n ) {}\n\n async sendPermissionRequest(\n session: Session,\n request: PermissionRequest,\n thread: TextChannel | ThreadChannel,\n ): Promise<void> {\n // Short callback key (Discord 100-char customId limit)\n const callbackKey = nanoid(8)\n this.pending.set(callbackKey, {\n sessionId: session.id,\n requestId: request.id,\n options: request.options.map((o) => ({ id: o.id, isAllow: o.isAllow })),\n guildId: this.guildId,\n channelId: thread.id,\n })\n\n // Build action row with buttons\n const row = new ActionRowBuilder<ButtonBuilder>()\n for (const option of request.options) {\n const emoji = option.isAllow ? '✅' : '❌'\n row.addComponents(\n new ButtonBuilder()\n .setCustomId(`p:${callbackKey}:${option.id}`)\n .setLabel(`${emoji} ${option.label}`)\n .setStyle(option.isAllow ? ButtonStyle.Success : ButtonStyle.Danger),\n )\n }\n\n // Send permission message in session thread\n let messageId: string | undefined\n try {\n const msg = await thread.send({\n content: `🔐 **Permission request:**\\n\\n${request.description}`,\n components: [row],\n })\n messageId = msg.id\n // Store messageId for deep link\n const pendingEntry = this.pending.get(callbackKey)\n if (pendingEntry) pendingEntry.messageId = messageId\n } catch (err) {\n log.warn({ err, sessionId: session.id }, '[PermissionHandler] Failed to send permission request')\n return\n }\n\n // Build deep link for notification\n const deepLink = buildDeepLink(this.guildId, thread.id, messageId)\n\n // Fire-and-forget notification to avoid sendQueue deadlock\n void this.sendNotification({\n sessionId: session.id,\n sessionName: session.name,\n type: 'permission',\n summary: request.description,\n deepLink,\n })\n }\n\n async handleButtonInteraction(interaction: ButtonInteraction): Promise<boolean> {\n const data = interaction.customId\n if (!data.startsWith('p:')) return false\n\n const parts = data.split(':')\n if (parts.length < 3) return false\n\n const [, callbackKey, optionId] = parts\n\n const pending = this.pending.get(callbackKey)\n if (!pending) {\n try {\n await interaction.reply({ content: '❌ Permission request expired', ephemeral: true })\n } catch { /* ignore */ }\n return true\n }\n\n const session = this.getSession(pending.sessionId)\n const option = pending.options.find((o) => o.id === optionId)\n const isAllow = option?.isAllow ?? false\n\n log.info(\n { requestId: pending.requestId, optionId, isAllow },\n '[PermissionHandler] Permission responded',\n )\n\n if (session?.permissionGate.requestId === pending.requestId) {\n session.permissionGate.resolve(optionId)\n }\n\n this.pending.delete(callbackKey)\n\n try {\n await interaction.reply({ content: '✅ Responded', ephemeral: true })\n } catch { /* ignore */ }\n\n // Remove buttons from the original message\n try {\n await interaction.message.edit({ components: [] })\n } catch { /* ignore */ }\n\n return true\n }\n}\n","import { SlashCommandBuilder } from \"discord.js\";\nimport type { Guild } from \"discord.js\";\n\nexport const SLASH_COMMANDS = [\n new SlashCommandBuilder()\n .setName(\"new\")\n .setDescription(\"Create a new agent session\")\n .addStringOption((o) =>\n o.setName(\"agent\").setDescription(\"Agent to use\").setRequired(false),\n )\n .addStringOption((o) =>\n o\n .setName(\"workspace\")\n .setDescription(\"Workspace directory\")\n .setRequired(false),\n ),\n\n new SlashCommandBuilder()\n .setName(\"newchat\")\n .setDescription(\n \"New chat in current thread, inheriting agent and workspace\",\n ),\n\n new SlashCommandBuilder()\n .setName(\"cancel\")\n .setDescription(\"Cancel the current session\"),\n\n new SlashCommandBuilder()\n .setName(\"status\")\n .setDescription(\"Show session or global status\"),\n\n new SlashCommandBuilder()\n .setName(\"sessions\")\n .setDescription(\"List all sessions\"),\n\n new SlashCommandBuilder()\n .setName(\"agents\")\n .setDescription(\"List available agents\"),\n\n new SlashCommandBuilder()\n .setName(\"install\")\n .setDescription(\"Install an agent by name\")\n .addStringOption((o) =>\n o\n .setName(\"name\")\n .setDescription(\"Agent name to install\")\n .setRequired(true),\n ),\n\n new SlashCommandBuilder()\n .setName(\"menu\")\n .setDescription(\"Show the action menu\"),\n\n new SlashCommandBuilder().setName(\"help\").setDescription(\"Show help\"),\n\n new SlashCommandBuilder()\n .setName(\"dangerous\")\n .setDescription(\"Toggle dangerous mode for the current session\"),\n\n new SlashCommandBuilder()\n .setName(\"restart\")\n .setDescription(\"Restart OpenACP\"),\n\n new SlashCommandBuilder()\n .setName(\"update\")\n .setDescription(\"Update to the latest version\"),\n\n new SlashCommandBuilder()\n .setName(\"integrate\")\n .setDescription(\"Manage agent integrations\"),\n\n new SlashCommandBuilder()\n .setName(\"settings\")\n .setDescription(\"Show configuration settings\"),\n\n new SlashCommandBuilder()\n .setName(\"doctor\")\n .setDescription(\"Run system diagnostics\"),\n\n new SlashCommandBuilder()\n .setName(\"handoff\")\n .setDescription(\"Generate a terminal resume command for this session\"),\n\n new SlashCommandBuilder()\n .setName(\"clear\")\n .setDescription(\"Reset the assistant session\"),\n\n new SlashCommandBuilder()\n .setName(\"tts\")\n .setDescription(\"Toggle Text to Speech for the current session\")\n .addStringOption((o) =>\n o\n .setName(\"mode\")\n .setDescription(\n \"on = persistent, off = disable, empty = next message only\",\n )\n .setRequired(false)\n .addChoices({ name: \"on\", value: \"on\" }, { name: \"off\", value: \"off\" }),\n ),\n\n new SlashCommandBuilder()\n .setName(\"verbosity\")\n .setDescription(\"Set display verbosity level\")\n .addStringOption((o) =>\n o\n .setName(\"level\")\n .setDescription(\"Verbosity level\")\n .setRequired(true)\n .addChoices(\n { name: \"low — minimal, title only\", value: \"low\" },\n { name: \"medium — balanced (default)\", value: \"medium\" },\n { name: \"high — full detail with content\", value: \"high\" },\n ),\n ),\n];\n\nexport async function registerSlashCommands(guild: Guild): Promise<void> {\n await guild.commands.set(SLASH_COMMANDS.map((cmd) => cmd.toJSON()));\n}\n\nexport { handleSlashCommand, setupButtonCallbacks } from \"./router.js\";\n","import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n} from 'discord.js'\nimport type { ChatInputCommandInteraction, ButtonInteraction } from 'discord.js'\nimport { log } from '../../../core/log.js'\nimport type { DiscordAdapter } from '../adapter.js'\n\nexport function buildMenuKeyboard(): ActionRowBuilder<ButtonBuilder>[] {\n const row1 = new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder().setCustomId('m:new').setLabel('🆕 New Session').setStyle(ButtonStyle.Primary),\n new ButtonBuilder().setCustomId('m:sessions').setLabel('📋 Sessions').setStyle(ButtonStyle.Secondary),\n new ButtonBuilder().setCustomId('m:status').setLabel('📊 Status').setStyle(ButtonStyle.Secondary),\n )\n const row2 = new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder().setCustomId('m:agents').setLabel('🤖 Agents').setStyle(ButtonStyle.Secondary),\n new ButtonBuilder().setCustomId('m:settings').setLabel('⚙️ Settings').setStyle(ButtonStyle.Secondary),\n new ButtonBuilder().setCustomId('m:integrate').setLabel('🔗 Integrate').setStyle(ButtonStyle.Secondary),\n )\n const row3 = new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder().setCustomId('m:tts').setLabel('🔊 TTS').setStyle(ButtonStyle.Secondary),\n new ButtonBuilder().setCustomId('m:restart').setLabel('🔄 Restart').setStyle(ButtonStyle.Secondary),\n new ButtonBuilder().setCustomId('m:update').setLabel('⬆️ Update').setStyle(ButtonStyle.Secondary),\n new ButtonBuilder().setCustomId('m:help').setLabel('❓ Help').setStyle(ButtonStyle.Secondary),\n new ButtonBuilder().setCustomId('m:doctor').setLabel('🩺 Doctor').setStyle(ButtonStyle.Secondary),\n )\n return [row1, row2, row3]\n}\n\nexport async function handleMenu(\n interaction: ChatInputCommandInteraction,\n _adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.reply({\n content: '**OpenACP Menu**\\nChoose an action:',\n components: buildMenuKeyboard(),\n ephemeral: true,\n })\n}\n\nexport async function handleHelp(\n interaction: ChatInputCommandInteraction,\n _adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.reply({\n content:\n `📖 **OpenACP Help**\\n\\n` +\n `🚀 **Getting Started**\\n` +\n `Use 🆕 New Session or \\`/new\\` to start coding with AI.\\n` +\n `Each session gets its own forum thread — chat there to work with the agent.\\n\\n` +\n `💡 **Common Commands**\\n` +\n `\\`/new [agent] [workspace]\\` — Create new session\\n` +\n `\\`/newchat\\` — New chat, same agent & workspace\\n` +\n `\\`/cancel\\` — Cancel current session\\n` +\n `\\`/status\\` — Show session or system status\\n` +\n `\\`/sessions\\` — List all sessions\\n` +\n `\\`/agents\\` — Browse & install agents\\n` +\n `\\`/install <name>\\` — Install an agent\\n\\n` +\n `⚙️ **System**\\n` +\n `\\`/restart\\` — Restart OpenACP\\n` +\n `\\`/update\\` — Update to latest version\\n` +\n `\\`/integrate\\` — Manage agent integrations\\n` +\n `\\`/settings\\` — View configuration\\n` +\n `\\`/menu\\` — Show action menu\\n\\n` +\n `🔒 **Session Options**\\n` +\n `\\`/dangerous\\` — Toggle dangerous mode (auto-approve permissions)\\n` +\n `\\`/tts\\` — Toggle Text to Speech (on/off/next message)\\n` +\n `\\`/handoff\\` — Continue session in your terminal\\n` +\n `\\`/clear\\` — Clear assistant session history\\n\\n` +\n `🩺 **Diagnostics**\\n` +\n `\\`/doctor\\` — Run system diagnostics`,\n ephemeral: true,\n })\n}\n\nexport async function handleClear(\n interaction: ChatInputCommandInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n if (!adapter.getAssistantSessionId()) {\n await interaction.editReply('⚠️ Assistant is not available.')\n return\n }\n\n try {\n await adapter.respawnAssistant()\n await interaction.editReply('✅ Assistant history cleared.')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n await interaction.editReply(`❌ Failed to clear: \\`${message}\\``)\n }\n}\n\nexport async function handleMenuButton(\n interaction: ButtonInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n const { customId } = interaction\n\n try {\n await interaction.deferUpdate()\n } catch { /* expired */ }\n\n try {\n switch (customId) {\n case 'm:new': {\n // Delegate to new-session handler\n const { handleNew } = await import('./new-session.js')\n // Create a fake slash command interaction proxy for the button context\n // We just show the menu inline instead\n await interaction.followUp({ content: 'Use `/new` to create a new session.', ephemeral: true })\n break\n }\n case 'm:sessions': {\n const { handleSessions } = await import('./session.js')\n // Use followUp to show the sessions list\n await showSessionsList(interaction, adapter)\n break\n }\n case 'm:status': {\n await showGlobalStatus(interaction, adapter)\n break\n }\n case 'm:agents': {\n const { showAgentsList } = await import('./agents.js')\n await showAgentsList(interaction, adapter)\n break\n }\n case 'm:settings': {\n const { showSettingsInfo } = await import('./settings.js')\n await showSettingsInfo(interaction, adapter)\n break\n }\n case 'm:integrate': {\n await interaction.followUp({ content: 'Use `/integrate` to manage integrations.', ephemeral: true })\n break\n }\n case 'm:restart': {\n const { handleRestart } = await import('./admin.js')\n if (!adapter.core.requestRestart) {\n await interaction.followUp({ content: '⚠️ Restart not available.', ephemeral: true })\n } else {\n await interaction.followUp({ content: '🔄 Restarting OpenACP...', ephemeral: true })\n await new Promise((r) => setTimeout(r, 500))\n await adapter.core.requestRestart()\n }\n break\n }\n case 'm:update': {\n await interaction.followUp({ content: '⚠️ Update not implemented yet. Run `npm install -g @openacp/cli@latest` in your terminal.', ephemeral: true })\n break\n }\n case 'm:help': {\n await interaction.followUp({ content: 'Use `/help` for command reference.', ephemeral: true })\n break\n }\n case 'm:doctor': {\n const { runDoctorInline } = await import('./doctor.js')\n await runDoctorInline(interaction, adapter)\n break\n }\n case 'm:tts': {\n const channelId = interaction.channelId\n const session = adapter.core.sessionManager.getSessionByThread('discord', channelId)\n if (!session) {\n await interaction.followUp({ content: '⚠️ No active session in this channel. Use `/tts` inside a session thread.', ephemeral: true })\n } else {\n const newMode = session.voiceMode === 'on' ? 'off' : 'on'\n session.setVoiceMode(newMode)\n const msg = newMode === 'on'\n ? '🔊 Text to Speech enabled for this session.'\n : '🔇 Text to Speech disabled.'\n await interaction.followUp({ content: msg, ephemeral: true })\n }\n break\n }\n default:\n log.warn({ customId }, '[discord-menu] Unhandled menu button')\n }\n } catch (err) {\n log.error({ err, customId }, '[discord-menu] Menu button handler failed')\n try {\n await interaction.followUp({ content: `❌ Action failed: ${err instanceof Error ? err.message : String(err)}`, ephemeral: true })\n } catch { /* ignore */ }\n }\n}\n\nasync function showGlobalStatus(interaction: ButtonInteraction, adapter: DiscordAdapter): Promise<void> {\n const sessions = adapter.core.sessionManager.listSessions('discord')\n const active = sessions.filter((s: any) => s.status === 'active' || s.status === 'initializing')\n await interaction.followUp({\n content:\n `**OpenACP Status**\\n` +\n `Active sessions: ${active.length}\\n` +\n `Total sessions: ${sessions.length}`,\n ephemeral: true,\n })\n}\n\nasync function showSessionsList(interaction: ButtonInteraction, adapter: DiscordAdapter): Promise<void> {\n const allRecords = adapter.core.sessionManager.listRecords()\n if (allRecords.length === 0) {\n await interaction.followUp({ content: 'No sessions found.', ephemeral: true })\n return\n }\n\n const STATUS_EMOJI: Record<string, string> = {\n active: '🟢', initializing: '🟡', finished: '✅', error: '❌', cancelled: '⛔',\n }\n const STATUS_ORDER: Record<string, number> = {\n active: 0, initializing: 1, error: 2, finished: 3, cancelled: 4,\n }\n\n allRecords.sort(\n (a: any, b: any) => (STATUS_ORDER[a.status] ?? 5) - (STATUS_ORDER[b.status] ?? 5),\n )\n\n const lines = allRecords.slice(0, 20).map((r: any) => {\n const emoji = STATUS_EMOJI[r.status] || '⚪'\n const name = r.name?.trim() || `${r.agentName} session`\n return `${emoji} **${name}** \\`[${r.status}]\\``\n })\n\n const truncated = allRecords.length > 20 ? `\\n\\n*...and ${allRecords.length - 20} more*` : ''\n\n await interaction.followUp({\n content: `**Sessions: ${allRecords.length}**\\n\\n${lines.join('\\n')}${truncated}`,\n ephemeral: true,\n })\n}\n","import type { ChatInputCommandInteraction, ButtonInteraction } from 'discord.js'\nimport { log } from '../../../core/log.js'\nimport type { DiscordAdapter } from '../adapter.js'\n\nexport async function handleIntegrate(\n interaction: ChatInputCommandInteraction,\n _adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n // Stub: integration management not yet implemented for Discord\n await interaction.editReply(\n '🔗 **Integrations**\\n\\nIntegration management via Discord is not yet implemented.\\n\\nUse the CLI: `openacp integrate`',\n )\n}\n\nexport async function handleIntegrateButton(\n interaction: ButtonInteraction,\n _adapter: DiscordAdapter,\n): Promise<void> {\n // Stub: integration button callbacks not yet implemented for Discord\n log.debug({ customId: interaction.customId }, '[discord-integrate] Button stub called')\n try {\n await interaction.reply({\n content: '🔗 Integration management via Discord is not yet implemented. Use the CLI: `openacp integrate`',\n ephemeral: true,\n })\n } catch { /* ignore */ }\n}\n","import type {\n ChatInputCommandInteraction,\n ButtonInteraction,\n} from \"discord.js\";\nimport { log } from \"../../../core/log.js\";\nimport type { DiscordAdapter } from \"../adapter.js\";\n\nimport {\n handleNew,\n handleNewChat,\n handleNewSessionButton,\n} from \"./new-session.js\";\nimport {\n handleCancel,\n handleStatus,\n handleSessions,\n handleHandoff,\n handleCleanupButton,\n} from \"./session.js\";\nimport {\n handleDangerous,\n handleDangerousButton,\n handleTTS,\n handleTTSButton,\n handleRestart,\n handleUpdate,\n handleVerbosity,\n} from \"./admin.js\";\nimport {\n handleMenu,\n handleHelp,\n handleClear,\n handleMenuButton,\n} from \"./menu.js\";\nimport { handleAgents, handleInstall, handleAgentButton } from \"./agents.js\";\nimport { handleDoctor, handleDoctorButton } from \"./doctor.js\";\nimport { handleIntegrate, handleIntegrateButton } from \"./integrate.js\";\nimport { handleSettings, handleSettingsButton } from \"./settings.js\";\n\nexport async function handleSlashCommand(\n interaction: ChatInputCommandInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n const { commandName } = interaction;\n\n try {\n switch (commandName) {\n case \"new\":\n await handleNew(interaction, adapter);\n break;\n case \"newchat\":\n await handleNewChat(interaction, adapter);\n break;\n case \"cancel\":\n await handleCancel(interaction, adapter);\n break;\n case \"status\":\n await handleStatus(interaction, adapter);\n break;\n case \"sessions\":\n await handleSessions(interaction, adapter);\n break;\n case \"agents\":\n await handleAgents(interaction, adapter);\n break;\n case \"install\":\n await handleInstall(interaction, adapter);\n break;\n case \"menu\":\n await handleMenu(interaction, adapter);\n break;\n case \"help\":\n await handleHelp(interaction, adapter);\n break;\n case \"dangerous\":\n await handleDangerous(interaction, adapter);\n break;\n case \"restart\":\n await handleRestart(interaction, adapter);\n break;\n case \"update\":\n await handleUpdate(interaction, adapter);\n break;\n case \"integrate\":\n await handleIntegrate(interaction, adapter);\n break;\n case \"settings\":\n await handleSettings(interaction, adapter);\n break;\n case \"doctor\":\n await handleDoctor(interaction, adapter);\n break;\n case \"handoff\":\n await handleHandoff(interaction, adapter);\n break;\n case \"clear\":\n await handleClear(interaction, adapter);\n break;\n case \"tts\":\n await handleTTS(interaction, adapter);\n break;\n case \"verbosity\":\n await handleVerbosity(interaction, adapter);\n break;\n default:\n log.warn({ commandName }, \"[discord-router] Unknown slash command\");\n if (!interaction.replied && !interaction.deferred) {\n await interaction.reply({\n content: `Unknown command: /${commandName}`,\n ephemeral: true,\n });\n }\n }\n } catch (err) {\n log.error(\n { err, commandName },\n \"[discord-router] Slash command handler failed\",\n );\n try {\n const errMsg = `❌ Command failed: ${err instanceof Error ? err.message : String(err)}`;\n if (interaction.deferred) {\n await interaction.editReply(errMsg);\n } else if (!interaction.replied) {\n await interaction.reply({ content: errMsg, ephemeral: true });\n }\n } catch {\n /* ignore reply errors */\n }\n }\n}\n\nexport async function setupButtonCallbacks(\n interaction: ButtonInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n const { customId } = interaction;\n\n try {\n // Ordered from most specific to least specific\n if (customId.startsWith(\"a:dismiss:\")) {\n // Action dismiss button — remove buttons from the message\n try {\n await interaction.update({ components: [] });\n } catch {\n /* ignore */\n }\n return;\n }\n\n if (customId.startsWith(\"a:\")) {\n // Action confirm button (new session or cancel from assistant suggestions)\n const { getAction, removeAction } = await import(\"../action-detect.js\");\n const actionId = customId.slice(2);\n const action = getAction(actionId);\n if (!action) {\n await interaction.reply({\n content: \"❌ Action expired.\",\n ephemeral: true,\n });\n return;\n }\n removeAction(actionId);\n if (action.type === \"new_session\") {\n await executeNewSession(\n interaction,\n adapter,\n action.agent,\n action.workspace,\n );\n } else if (action.type === \"cancel_session\") {\n await executeCancelSession(interaction, adapter);\n }\n return;\n }\n\n if (customId.startsWith(\"d:\")) {\n await handleDangerousButton(interaction, adapter);\n return;\n }\n\n if (customId.startsWith(\"v:\")) {\n await handleTTSButton(interaction, adapter);\n return;\n }\n\n if (customId.startsWith(\"m:new:\")) {\n await handleNewSessionButton(interaction, adapter);\n return;\n }\n\n if (customId === \"m:cleanup\" || customId.startsWith(\"m:cleanup:\")) {\n await handleCleanupButton(interaction, adapter);\n return;\n }\n\n if (customId === \"m:doctor\" || customId.startsWith(\"m:doctor:\")) {\n await handleDoctorButton(interaction, adapter);\n return;\n }\n\n if (customId.startsWith(\"ag:\")) {\n await handleAgentButton(interaction, adapter);\n return;\n }\n\n if (customId.startsWith(\"na:\")) {\n // New session with specific agent (from install confirmation)\n const agentKey = customId.slice(3);\n try {\n await interaction.deferReply({ ephemeral: true });\n } catch {\n /* ignore */\n }\n await executeNewSession(interaction, adapter, agentKey, undefined);\n return;\n }\n\n if (customId.startsWith(\"s:\")) {\n await handleSettingsButton(interaction, adapter);\n return;\n }\n\n if (customId.startsWith(\"i:\")) {\n await handleIntegrateButton(interaction, adapter);\n return;\n }\n\n // Catch-all m: handler\n if (customId.startsWith(\"m:\")) {\n await handleMenuButton(interaction, adapter);\n return;\n }\n\n log.warn({ customId }, \"[discord-router] Unhandled button interaction\");\n } catch (err) {\n log.error({ err, customId }, \"[discord-router] Button callback failed\");\n try {\n const errMsg = `❌ Action failed: ${err instanceof Error ? err.message : String(err)}`;\n if (!interaction.replied && !interaction.deferred) {\n await interaction.reply({ content: errMsg, ephemeral: true });\n } else {\n await interaction.followUp({ content: errMsg, ephemeral: true });\n }\n } catch {\n /* ignore reply errors */\n }\n }\n}\n\n// Helper: execute new session from button interaction\nasync function executeNewSession(\n interaction: ButtonInteraction,\n adapter: DiscordAdapter,\n agentName?: string,\n workspace?: string,\n): Promise<void> {\n const { executeNewSession: doExecute } = await import(\"./new-session.js\");\n await doExecute(interaction, adapter, agentName, workspace);\n}\n\n// Helper: execute cancel session from button interaction\nasync function executeCancelSession(\n interaction: ButtonInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n const { executeCancelSession: doCancel } = await import(\"./session.js\");\n await doCancel(interaction, adapter);\n}\n","import type { OpenACPCore } from '../../core/core.js'\nimport type { Session } from '../../core/session.js'\nimport { log } from '../../core/log.js'\nimport { PRODUCT_GUIDE } from '../../product-guide.js'\n\nexport interface SpawnAssistantResult {\n session: Session\n /** Resolves when the background system prompt completes (or fails). */\n ready: Promise<void>\n}\n\n/**\n * Spawns an assistant session for the Discord adapter.\n * Creates a session with the default agent, sets the threadId to the given Discord thread/channel,\n * and fires a system prompt in the background.\n */\nexport async function spawnAssistant(\n core: OpenACPCore,\n threadId: string,\n): Promise<SpawnAssistantResult> {\n const config = core.configManager.get()\n\n log.info({ agent: config.defaultAgent, threadId }, '[discord-assistant] Creating assistant session...')\n\n const session = await core.createSession({\n channelId: 'discord',\n agentName: config.defaultAgent,\n workingDirectory: core.configManager.resolveWorkspace(),\n initialName: 'Assistant', // Prevent auto-naming from triggering after system prompt\n })\n session.threadId = threadId\n\n log.info({ sessionId: session.id, threadId }, '[discord-assistant] Assistant agent spawned')\n\n // Build dynamic context for system prompt\n const systemPrompt = buildAssistantSystemPrompt(core)\n\n // Fire system prompt in background — don't block startup\n const ready = session\n .enqueuePrompt(systemPrompt)\n .then(() => {\n log.info({ sessionId: session.id }, '[discord-assistant] System prompt completed')\n })\n .catch((err) => {\n log.warn({ err }, '[discord-assistant] System prompt failed')\n })\n\n return { session, ready }\n}\n\n/**\n * Build a welcome message summarising active sessions and available agents.\n */\nexport function buildWelcomeMessage(core: OpenACPCore): string {\n const allRecords = core.sessionManager.listRecords()\n const activeCount = allRecords.filter(\n (r) => r.status === 'active' || r.status === 'initializing',\n ).length\n const errorCount = allRecords.filter((r) => r.status === 'error').length\n const totalCount = allRecords.length\n\n const installedEntries = core.agentCatalog.getInstalledEntries()\n const agents = Object.keys(installedEntries)\n const config = core.configManager.get()\n const defaultAgent = config.defaultAgent\n\n const agentList = agents\n .map((a) => `${a}${a === defaultAgent ? ' (default)' : ''}`)\n .join(', ')\n\n if (totalCount === 0) {\n return `👋 **OpenACP is ready!**\\n\\nNo sessions yet. Use \\`/new\\` to start, or ask me anything!\\n\\nAgents: ${agentList}`\n }\n\n if (errorCount > 0) {\n return (\n `👋 **OpenACP is ready!**\\n\\n` +\n `📊 ${activeCount} active, ${errorCount} errors / ${totalCount} total\\n` +\n `⚠️ ${errorCount} session${errorCount > 1 ? 's have' : ' has'} errors — ask me to check.\\n\\n` +\n `Agents: ${agentList}`\n )\n }\n\n return (\n `👋 **OpenACP is ready!**\\n\\n` +\n `📊 ${activeCount} active / ${totalCount} total\\n` +\n `Agents: ${agentList}`\n )\n}\n\n/**\n * Build the system prompt for the Discord assistant session.\n * Includes current state, available agents, and action playbook.\n */\nexport function buildAssistantSystemPrompt(core: OpenACPCore): string {\n const config = core.configManager.get()\n\n const allRecords = core.sessionManager.listRecords()\n const activeCount = allRecords.filter(\n (r) => r.status === 'active' || r.status === 'initializing',\n ).length\n\n const statusCounts = new Map<string, number>()\n for (const r of allRecords) {\n statusCounts.set(r.status, (statusCounts.get(r.status) ?? 0) + 1)\n }\n const topicBreakdown =\n Array.from(statusCounts.entries())\n .map(([status, count]) => `${status}: ${count}`)\n .join(', ') || 'none'\n\n const installedEntries = core.agentCatalog.getInstalledEntries()\n const installedAgents = Object.keys(installedEntries)\n const agentNames = installedAgents.length ? installedAgents.join(', ') : Object.keys(config.agents).join(', ')\n\n const availableItems = core.agentCatalog.getAvailable()\n const availableAgentCount = availableItems.filter((i) => !i.installed).length\n\n return `You are the OpenACP Assistant — a helpful guide for managing AI coding sessions on Discord.\n\n## Current State\n- Active sessions: ${activeCount} / ${allRecords.length} total\n- Sessions by status: ${topicBreakdown}\n- Installed agents: ${agentNames}\n- Available in ACP Registry: ${availableAgentCount} more agents (use \\`/agents\\` to browse)\n- Default agent: ${config.defaultAgent}\n- Workspace base directory: ${config.workspace.baseDir}\n- Platform: Discord\n\n## Discord Context\n- Each session gets its own forum thread in the OpenACP sessions channel\n- Users interact with sessions by chatting in those threads\n- Slash commands: /new, /cancel, /status, /sessions, /agents, /install, /menu, /help, /dangerous, /restart, /update, /integrate, /settings, /doctor, /handoff, /clear\n\n## Action Playbook\n\n### Create Session\n- The workspace is the project directory where the agent will work (read, write, execute code). It should be a specific project folder like \\`~/code/my-project\\` or \\`${config.workspace.baseDir}/my-app\\`.\n- Ask which agent to use (if multiple are installed). Installed: ${agentNames}\n- Ask which project directory to use as workspace. Suggest \\`${config.workspace.baseDir}\\` as the base.\n- Create via: \\`openacp api new <agent> <workspace>\\`\n\n### Browse & Install Agents\n- Guide users to \\`/agents\\` command to see all available agents\n- For CLI users: \\`openacp agents install <name>\\`\n- Some agents need login/setup after install — guide users to \\`openacp agents info <name>\\`\n- To run agent CLI for login: \\`openacp agents run <name> -- <args>\\`\n\n### Check Status / List Sessions\n- Run \\`openacp api status\\` for active sessions overview\n- Run \\`openacp api topics\\` for full list with statuses\n\n### Cancel Session\n- Run \\`openacp api status\\` to see what's active\n- If 1 active session → ask user to confirm → \\`openacp api cancel <id>\\`\n- If multiple → list them, ask user which one to cancel\n\n### Troubleshoot\n- Run \\`openacp api health\\` + \\`openacp api status\\` to diagnose\n- Small issue (stuck session) → suggest cancel + create new\n- Big issue (system-level) → suggest restart, ask for confirmation first\n\n### Cleanup Old Sessions\n- Run \\`openacp api topics --status finished,error\\` to see what can be cleaned\n- Report the count, ask user to confirm\n- Execute: \\`openacp api cleanup --status <statuses>\\`\n\n### Configuration\n- View: \\`openacp config\\`\n- Update: \\`openacp config set <key> <value>\\`\n\n### Restart / Update\n- Always ask for confirmation — these are disruptive actions\n\n### Toggle Dangerous Mode\n- Run \\`openacp api dangerous <id> on|off\\`\n- Explain: dangerous mode auto-approves all permission requests\n\n## CLI Commands Reference\n\\`\\`\\`bash\n# Session management\nopenacp api status # List active sessions\nopenacp api session <id> # Session detail\nopenacp api new <agent> <workspace> # Create new session\nopenacp api send <id> \"prompt text\" # Send prompt to session\nopenacp api cancel <id> # Cancel session\nopenacp api dangerous <id> on|off # Toggle dangerous mode\n\n# Topic management\nopenacp api topics # List all topics\nopenacp api cleanup # Cleanup finished topics\n\n# Agent management\nopenacp agents # List installed + available agents\nopenacp agents install <name> # Install agent from ACP Registry\nopenacp agents uninstall <name> # Remove agent\nopenacp agents info <name> # Show details & setup guide\nopenacp agents run <name> -- <args> # Run agent CLI (for login, etc.)\n\n# System\nopenacp api health # System health\nopenacp config # Edit config (interactive)\nopenacp config set <key> <value> # Update config value\nopenacp api restart # Restart daemon\n\\`\\`\\`\n\n## Guidelines\n- NEVER show \\`openacp api ...\\` commands to users. These are internal tools for YOU to run silently.\n- Run \\`openacp api ...\\` commands yourself for everything you can. Only guide users to Discord slash commands/buttons when needed.\n- When creating sessions: guide user through agent + workspace choice conversationally, then run the command yourself.\n- Destructive actions (cancel active session, restart, cleanup) → always ask user to confirm first.\n- Respond in the same language the user uses.\n- Format responses for Discord: use **bold**, \\`code\\`, keep it concise.\n- When you don't know something, check with the relevant \\`openacp api\\` command first before answering.\n\n## Product Reference\n${PRODUCT_GUIDE}`\n}\n\n/**\n * Enqueue a prompt to the assistant session.\n */\nexport async function handleAssistantMessage(\n session: Session | null,\n text: string,\n): Promise<void> {\n if (!session) return\n await session.enqueuePrompt(text)\n}\n","import type { Attachment } from '../../core/types.js'\nimport { log } from '../../core/log.js'\n\nconst MAX_DOWNLOAD_SIZE = 100 * 1024 * 1024 // 100MB safety cap for downloads\nconst DISCORD_UPLOAD_LIMIT = 25 * 1024 * 1024 // 25MB — Discord free tier\n\n/**\n * Check if an attachment exceeds Discord's upload limit.\n */\nexport function isAttachmentTooLarge(size: number): boolean {\n return size > DISCORD_UPLOAD_LIMIT\n}\n\n/**\n * Classify a MIME contentType string into an Attachment type.\n */\nexport function classifyAttachmentType(\n contentType: string | null | undefined,\n): Attachment['type'] {\n if (!contentType) return 'file'\n if (contentType.startsWith('image/')) return 'image'\n if (contentType.startsWith('audio/')) return 'audio'\n return 'file'\n}\n\n/**\n * Build fallback text when message.content is empty but attachments exist.\n * Mirrors Telegram adapter's pattern: [Photo: filename], [Audio: filename], [File: filename]\n */\nexport function buildFallbackText(\n attachments: Array<{ type: Attachment['type']; fileName: string }>,\n): string {\n return attachments\n .map((att) => {\n const label = att.type === 'image' ? 'Photo' : att.type === 'audio' ? 'Audio' : 'File'\n return `[${label}: ${att.fileName}]`\n })\n .join(' ')\n}\n\n/**\n * Download a file from a Discord attachment URL.\n * Returns the buffer or null on failure.\n */\nexport async function downloadDiscordAttachment(\n url: string,\n fileName: string,\n): Promise<Buffer | null> {\n try {\n const response = await fetch(url)\n if (!response.ok) {\n log.warn({ url, status: response.status, fileName }, '[discord-media] Download failed')\n return null\n }\n const buffer = Buffer.from(await response.arrayBuffer())\n if (buffer.length > MAX_DOWNLOAD_SIZE) {\n log.warn({ fileName, size: buffer.length }, '[discord-media] File exceeds download size cap')\n return null\n }\n return buffer\n } catch (err) {\n log.error({ err, url, fileName }, '[discord-media] Download error')\n return null\n }\n}\n"],"mappings":"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","names":["splitMessage","splitMessage","ActionRowBuilder","ButtonBuilder","ButtonStyle","ActionRowBuilder","ButtonBuilder","ButtonStyle","handleNew","handleSessions","handleRestart"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/main.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { ConfigManager } from './core/config.js'\nimport { OpenACPCore } from './core/core.js'\nimport { loadAdapterFactory } from './core/plugin-manager.js'\nimport { initLogger, shutdownLogger, cleanupOldSessionLogs, log, muteLogger, unmuteLogger } from './core/log.js'\nimport { TelegramAdapter } from './adapters/telegram/index.js'\nimport type { TelegramChannelConfig } from './adapters/telegram/index.js'\nimport { ApiServer } from './core/api-server.js'\nimport { TopicManager } from './core/topic-manager.js'\n\nexport const RESTART_EXIT_CODE = 75\nlet shuttingDown = false\n\nexport async function startServer() {\n // 0. If running as daemon child, check state and write PID file\n if (process.argv.includes('--daemon-child')) {\n const { writePidFile, readPidFile, getPidPath, shouldAutoStart } = await import('./core/daemon.js')\n\n // Only auto-start if the daemon was previously running (user started it)\n if (!shouldAutoStart()) {\n process.exit(0)\n }\n\n const pidPath = getPidPath()\n const existingPid = readPidFile(pidPath)\n if (existingPid !== null && existingPid !== process.pid) {\n try {\n process.kill(existingPid, 0)\n console.error(`Another OpenACP instance is already running (PID ${existingPid}). Exiting.`)\n process.exit(1)\n } catch {\n // Stale PID file — safe to overwrite\n }\n }\n writePidFile(pidPath, process.pid)\n }\n\n // 1. Check config exists, run setup if not\n const configManager = new ConfigManager()\n const configExists = await configManager.exists()\n\n if (!configExists) {\n const { runSetup } = await import('./core/setup.js')\n const shouldStart = await runSetup(configManager)\n if (!shouldStart) process.exit(0)\n }\n\n // 2. Load config (validates with Zod)\n await configManager.load()\n const config = configManager.get()\n initLogger(config.logging)\n log.debug({ configPath: configManager.getConfigPath() }, 'Config loaded')\n\n // Show banner in foreground TTY mode (not daemon, not piped)\n const isForegroundTTY = !!(process.stdout.isTTY && !process.env.NO_COLOR && config.runMode !== 'daemon')\n if (isForegroundTTY) {\n const { printStartBanner } = await import('./core/setup.js')\n await printStartBanner()\n }\n\n // Mute pino during startup, show spinner instead\n let spinner: ReturnType<typeof import('ora').default> | undefined\n if (isForegroundTTY) {\n muteLogger()\n const ora = (await import('ora')).default\n spinner = ora({ text: 'Starting OpenACP...', spinner: 'dots' }).start()\n }\n\n // Post-upgrade dependency check (blocking — must complete before server start)\n try {\n const { runPostUpgradeChecks } = await import('./core/post-upgrade.js')\n await runPostUpgradeChecks(config)\n } catch (err) {\n log.warn({ err }, 'Post-upgrade check failed')\n }\n\n // Async cleanup of old session logs (non-blocking)\n cleanupOldSessionLogs(config.logging.sessionLogRetentionDays).catch(err =>\n log.warn({ err }, 'Session log cleanup failed')\n )\n\n // 3. Create core\n const core = new OpenACPCore(configManager)\n\n // 3.5 Start tunnel if configured\n let tunnelService: import('./tunnel/tunnel-service.js').TunnelService | undefined\n if (config.tunnel.enabled) {\n const { TunnelService } = await import('./tunnel/tunnel-service.js')\n tunnelService = new TunnelService(config.tunnel)\n const publicUrl = await tunnelService.start()\n core.tunnelService = tunnelService\n log.info({ publicUrl }, 'Tunnel started')\n }\n\n // 4. Register adapters from config\n for (const [channelName, channelConfig] of Object.entries(config.channels)) {\n if (!channelConfig.enabled) continue\n\n if (channelName === 'telegram') {\n core.registerAdapter('telegram', new TelegramAdapter(core, channelConfig as TelegramChannelConfig))\n log.info({ adapter: 'telegram' }, 'Adapter registered')\n } else if (channelName === 'slack') {\n const { SlackAdapter } = await import('./adapters/slack/adapter.js')\n const slackConfig = channelConfig as import('./adapters/slack/types.js').SlackChannelConfig\n core.registerAdapter('slack', new SlackAdapter(core, slackConfig))\n log.info({ adapter: 'slack' }, 'Adapter registered')\n } else if (channelName === 'discord') {\n const { DiscordAdapter } = await import('./adapters/discord/index.js')\n const discordConfig = channelConfig as import('./adapters/discord/types.js').DiscordChannelConfig\n core.registerAdapter('discord', new DiscordAdapter(core, discordConfig))\n log.info({ adapter: 'discord' }, 'Adapter registered')\n } else if (channelConfig.adapter) {\n // Plugin adapter\n const factory = await loadAdapterFactory(channelConfig.adapter)\n if (factory) {\n const adapter = factory.createAdapter(core, channelConfig)\n core.registerAdapter(channelName, adapter)\n log.info({ adapter: channelName, plugin: channelConfig.adapter }, 'Adapter registered')\n } else {\n const name = channelName\n const err = channelConfig.adapter\n log.error({ adapter: name, err }, 'Failed to load adapter')\n }\n } else {\n log.error({ adapter: channelName }, 'Channel has no built-in adapter; set \"adapter\" field to a plugin package')\n }\n }\n\n if (core.adapters.size === 0) {\n log.error('No channels enabled. Enable at least one channel in config.')\n process.exit(1)\n }\n\n // 5. Start\n let apiServer: ApiServer | undefined\n\n const shutdown = async (signal: string, exitCode = 0) => {\n if (shuttingDown) return\n shuttingDown = true\n log.info({ signal, exitCode }, 'Signal received, shutting down')\n\n try {\n if (apiServer) await apiServer.stop()\n await core.stop()\n if (tunnelService) await tunnelService.stop()\n } catch (err) {\n log.error({ err }, 'Error during shutdown')\n }\n\n const isDaemon = process.argv.includes('--daemon-child')\n\n // Clean up PID file if running as daemon\n if (isDaemon) {\n const { removePidFile, getPidPath } = await import('./core/daemon.js')\n removePidFile(getPidPath())\n }\n\n // Self-respawn on restart\n if (exitCode === RESTART_EXIT_CODE) {\n if (isDaemon) {\n // Daemon mode: spawn detached child writing to log file\n const { spawn: spawnChild } = await import('node:child_process')\n const { expandHome } = await import('./core/config.js')\n const fs = await import('node:fs')\n const pathMod = await import('node:path')\n\n const cliPath = pathMod.resolve(process.argv[1])\n const resolvedLogDir = expandHome(config.logging.logDir)\n fs.mkdirSync(resolvedLogDir, { recursive: true })\n const logFile = pathMod.join(resolvedLogDir, 'openacp.log')\n const out = fs.openSync(logFile, 'a')\n const err = fs.openSync(logFile, 'a')\n\n const child = spawnChild(process.execPath, [cliPath, '--daemon-child'], {\n detached: true,\n stdio: ['ignore', out, err],\n env: { ...process.env, OPENACP_SKIP_UPDATE_CHECK: '1' },\n })\n fs.closeSync(out)\n fs.closeSync(err)\n child.unref()\n log.info({ newPid: child.pid }, 'Respawned daemon for restart')\n } else if (!process.env.OPENACP_DEV_LOOP) {\n // Foreground production mode: spawn replacement process with inherited stdio\n const { spawn: spawnChild } = await import('node:child_process')\n const child = spawnChild(process.execPath, process.argv.slice(1), {\n stdio: 'inherit',\n env: { ...process.env, OPENACP_SKIP_UPDATE_CHECK: '1' },\n })\n await shutdownLogger()\n child.on('exit', (code) => process.exit(code ?? 0))\n return\n }\n }\n\n await shutdownLogger()\n process.exit(exitCode)\n }\n\n // Expose restart trigger for adapters (e.g. /restart command)\n core.requestRestart = () => shutdown('restart', RESTART_EXIT_CODE)\n\n process.on('SIGINT', () => shutdown('SIGINT'))\n process.on('SIGTERM', () => shutdown('SIGTERM'))\n\n process.on('uncaughtException', (err) => {\n log.error({ err }, 'Uncaught exception')\n })\n\n process.on('unhandledRejection', (err) => {\n log.error({ err }, 'Unhandled rejection')\n })\n\n await core.start()\n\n const updatedConfig = core.configManager.get()\n const telegramAdapter = core.adapters.get('telegram') ?? null\n let topicManager: TopicManager | undefined\n if (telegramAdapter) {\n const telegramCfg = updatedConfig.channels?.telegram as TelegramChannelConfig | undefined\n topicManager = new TopicManager(\n core.sessionManager,\n telegramAdapter,\n {\n notificationTopicId: telegramCfg?.notificationTopicId ?? null,\n assistantTopicId: telegramCfg?.assistantTopicId ?? null,\n },\n )\n }\n\n apiServer = new ApiServer(core, config.api, undefined, topicManager)\n await apiServer.start()\n\n // 6. Log ready\n if (isForegroundTTY) {\n if (spinner) spinner.stop()\n const ok = (msg: string) => console.log(`\\x1b[32m✓\\x1b[0m ${msg}`)\n ok('Config loaded')\n ok('Dependencies checked')\n if (tunnelService) ok(`Tunnel ready → ${tunnelService.getPublicUrl()}`)\n for (const [name] of core.adapters) ok(`${name.charAt(0).toUpperCase() + name.slice(1)} connected`)\n if (apiServer) ok(`API server on port ${config.api.port}`)\n console.log(`\\nOpenACP is running. Press Ctrl+C to stop.\\n`)\n unmuteLogger()\n }\n log.debug({ agents: Object.keys(config.agents) }, 'OpenACP started')\n}\n\n// Direct execution for dev (node dist/main.js)\nconst isDirectExecution = process.argv[1]?.endsWith('main.js')\nif (isDirectExecution) {\n startServer().catch((err) => {\n log.error({ err }, 'Fatal error')\n process.exit(1)\n })\n}\n"],"mappings":"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","names":[]}