acp-discord 0.1.0 → 0.2.0

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.
@@ -81,9 +81,13 @@ function parseConfig(toml) {
81
81
  if (ch.cwd !== void 0 && typeof ch.cwd !== "string") {
82
82
  throw new Error(`channels.${id}.cwd must be a string`);
83
83
  }
84
+ if (ch.auto_reply !== void 0 && typeof ch.auto_reply !== "boolean") {
85
+ throw new Error(`channels.${id}.auto_reply must be a boolean`);
86
+ }
84
87
  parsedChannels[id] = {
85
88
  agent: agentRef,
86
- cwd: ch.cwd ? String(ch.cwd) : void 0
89
+ cwd: ch.cwd ? String(ch.cwd) : void 0,
90
+ auto_reply: ch.auto_reply === true
87
91
  };
88
92
  }
89
93
  return {
@@ -106,7 +110,8 @@ function resolveChannelConfig(config, channelId) {
106
110
  agent: {
107
111
  ...agentConf,
108
112
  cwd: channelConf.cwd ?? agentConf.cwd
109
- }
113
+ },
114
+ autoReply: channelConf.auto_reply === true
110
115
  };
111
116
  }
112
117
 
@@ -119,4 +124,4 @@ export {
119
124
  loadConfig,
120
125
  resolveChannelConfig
121
126
  };
122
- //# sourceMappingURL=chunk-SITRIFHO.js.map
127
+ //# sourceMappingURL=chunk-QRVSGBED.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/pid.ts","../src/shared/config.ts"],"sourcesContent":["import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport function writePid(pidPath: string, pid: number): void {\n mkdirSync(dirname(pidPath), { recursive: true });\n writeFileSync(pidPath, String(pid), \"utf-8\");\n}\n\nexport function readPid(pidPath: string): number | null {\n if (!existsSync(pidPath)) return null;\n const content = readFileSync(pidPath, \"utf-8\").trim();\n const pid = parseInt(content, 10);\n return isNaN(pid) ? null : pid;\n}\n\nexport function removePid(pidPath: string): void {\n if (existsSync(pidPath)) unlinkSync(pidPath);\n}\n\nexport function isDaemonRunning(pidPath: string): boolean {\n const pid = readPid(pidPath);\n if (pid === null) return false;\n try {\n process.kill(pid, 0); // signal 0 = check if process exists\n return true;\n } catch {\n return false;\n }\n}\n","import { parse } from \"smol-toml\";\nimport { readFileSync } from \"node:fs\";\nimport type { AppConfig, ResolvedChannelConfig } from \"./types.js\";\n\nexport function parseConfig(toml: string): AppConfig {\n const raw = parse(toml) as Record<string, unknown>;\n\n const discord = raw.discord as Record<string, unknown> | undefined;\n if (!discord?.token || typeof discord.token !== \"string\") {\n throw new Error(\"Missing required: discord.token\");\n }\n if (discord.token.trim().length === 0) {\n throw new Error(\"discord.token must not be empty\");\n }\n\n const agents = raw.agents as Record<string, Record<string, unknown>> | undefined;\n if (!agents || Object.keys(agents).length === 0) {\n throw new Error(\"Missing required: at least one agent in [agents.*]\");\n }\n\n const parsedAgents: AppConfig[\"agents\"] = {};\n for (const [name, agent] of Object.entries(agents)) {\n // Validate command is a non-empty string\n if (!agent.command || typeof agent.command !== \"string\") {\n throw new Error(`agents.${name}.command must be a non-empty string`);\n }\n\n // Validate args is an array of strings\n if (agent.args !== undefined) {\n if (!Array.isArray(agent.args) || !agent.args.every((a: unknown) => typeof a === \"string\")) {\n throw new Error(`agents.${name}.args must be an array of strings`);\n }\n }\n\n // Validate idle_timeout is a positive number\n if (agent.idle_timeout !== undefined) {\n if (typeof agent.idle_timeout !== \"number\" || agent.idle_timeout <= 0) {\n throw new Error(`agents.${name}.idle_timeout must be a positive number`);\n }\n }\n\n // Validate cwd is a string if provided\n if (agent.cwd !== undefined && typeof agent.cwd !== \"string\") {\n throw new Error(`agents.${name}.cwd must be a string`);\n }\n\n parsedAgents[name] = {\n command: agent.command,\n args: (agent.args as string[]) ?? [],\n cwd: typeof agent.cwd === \"string\" ? agent.cwd : process.cwd(),\n idle_timeout: typeof agent.idle_timeout === \"number\" ? agent.idle_timeout : 600,\n };\n }\n\n const channels = (raw.channels ?? {}) as Record<string, Record<string, unknown>>;\n const parsedChannels: AppConfig[\"channels\"] = {};\n for (const [id, ch] of Object.entries(channels)) {\n const agentRef = ch.agent ?? \"default\";\n if (typeof agentRef !== \"string\") {\n throw new Error(`channels.${id}.agent must be a string`);\n }\n if (!parsedAgents[agentRef]) {\n throw new Error(`channels.${id}.agent references unknown agent \"${agentRef}\"`);\n }\n if (ch.cwd !== undefined && typeof ch.cwd !== \"string\") {\n throw new Error(`channels.${id}.cwd must be a string`);\n }\n if (ch.auto_reply !== undefined && typeof ch.auto_reply !== \"boolean\") {\n throw new Error(`channels.${id}.auto_reply must be a boolean`);\n }\n\n parsedChannels[id] = {\n agent: agentRef,\n cwd: ch.cwd ? String(ch.cwd) : undefined,\n auto_reply: ch.auto_reply === true,\n };\n }\n\n return {\n discord: { token: String(discord.token) },\n agents: parsedAgents,\n channels: parsedChannels,\n };\n}\n\nexport function loadConfig(configPath: string): AppConfig {\n const content = readFileSync(configPath, \"utf-8\");\n return parseConfig(content);\n}\n\nexport function resolveChannelConfig(\n config: AppConfig,\n channelId: string,\n): ResolvedChannelConfig | null {\n const channelConf = config.channels[channelId];\n if (!channelConf) return null;\n\n const agentConf = config.agents[channelConf.agent];\n if (!agentConf) return null;\n\n return {\n channelId,\n agent: {\n ...agentConf,\n cwd: channelConf.cwd ?? agentConf.cwd,\n },\n autoReply: channelConf.auto_reply === true,\n };\n}\n"],"mappings":"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","names":["readFileSync"]}
package/dist/daemon.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  removePid,
5
5
  resolveChannelConfig,
6
6
  writePid
7
- } from "./chunk-SITRIFHO.js";
7
+ } from "./chunk-QRVSGBED.js";
8
8
 
9
9
  // src/daemon/index.ts
10
10
  import { join } from "path";
@@ -504,7 +504,7 @@ async function startDiscordBot(config) {
504
504
  const resolved = router.resolve(channelId);
505
505
  if (!resolved) return;
506
506
  const isMention = message.mentions.has(discordClient.user);
507
- if (!isMention) return;
507
+ if (!resolved.autoReply && !isMention) return;
508
508
  const text = message.content.replace(/<@!?\d+>/g, "").trim();
509
509
  if (!text) {
510
510
  await message.reply("Please provide a message.");
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/daemon/index.ts","../src/daemon/discord-bot.ts","../src/daemon/channel-router.ts","../src/daemon/session-manager.ts","../src/daemon/acp-client.ts","../src/daemon/permission-ui.ts","../src/daemon/message-bridge.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { loadConfig } from \"../shared/config.js\";\nimport { writePid, removePid } from \"../cli/pid.js\";\nimport { startDiscordBot } from \"./discord-bot.js\";\n\nconst CONFIG_DIR = join(homedir(), \".acp-discord\");\nconst CONFIG_PATH = join(CONFIG_DIR, \"config.toml\");\nconst PID_PATH = join(CONFIG_DIR, \"daemon.pid\");\n\nexport async function runDaemon(): Promise<void> {\n // Load config first — if it fails, no stale PID file is left behind (#12)\n const config = loadConfig(CONFIG_PATH);\n\n writePid(PID_PATH, process.pid);\n process.on(\"exit\", () => removePid(PID_PATH));\n\n console.log(`acp-discord daemon started (PID: ${process.pid})`);\n console.log(`Loaded config: ${Object.keys(config.channels).length} channel(s)`);\n\n await startDiscordBot(config);\n}\n\nif (process.env.ACP_DISCORD_DAEMON === \"1\") {\n runDaemon().catch((err) => {\n console.error(\"Daemon failed:\", err);\n process.exit(1);\n });\n}\n","import {\n Client,\n GatewayIntentBits,\n Events,\n REST,\n Routes,\n SlashCommandBuilder,\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n type Message,\n type TextChannel,\n} from \"discord.js\";\nimport type { AppConfig } from \"../shared/types.js\";\nimport { ChannelRouter } from \"./channel-router.js\";\nimport { SessionManager } from \"./session-manager.js\";\nimport { sendPermissionRequest } from \"./permission-ui.js\";\nimport { splitMessage, formatToolSummary, type ToolStatus } from \"./message-bridge.js\";\nimport type { AcpEventHandlers } from \"./acp-client.js\";\n\nexport async function startDiscordBot(config: AppConfig): Promise<void> {\n const router = new ChannelRouter(config);\n\n // Per-channel state for display\n const toolStates = new Map<string, Map<string, { title: string; status: ToolStatus }>>();\n const toolSummaryMessages = new Map<string, Message>();\n const replyBuffers = new Map<string, string>();\n const replyMessages = new Map<string, Message>();\n const flushTimers = new Map<string, NodeJS.Timeout>();\n\n let discordClient: Client;\n\n const handlers: AcpEventHandlers = {\n onToolCall(channelId, toolCallId, title, _kind, status) {\n if (!toolStates.has(channelId)) toolStates.set(channelId, new Map());\n toolStates.get(channelId)!.set(toolCallId, { title, status: status as ToolStatus });\n updateToolSummaryMessage(channelId);\n },\n\n onToolCallUpdate(channelId, toolCallId, status) {\n const tools = toolStates.get(channelId);\n const tool = tools?.get(toolCallId);\n if (tool) {\n tool.status = status as ToolStatus;\n updateToolSummaryMessage(channelId);\n }\n },\n\n onAgentMessageChunk(channelId, text) {\n const current = replyBuffers.get(channelId) ?? \"\";\n replyBuffers.set(channelId, current + text);\n scheduleFlushReply(channelId);\n },\n\n async onPermissionRequest(channelId, requestorId, toolCall, options) {\n const channel = await fetchChannel(channelId);\n if (!channel) return { outcome: \"cancelled\" as const };\n return sendPermissionRequest(channel, toolCall.title, toolCall.kind, options, requestorId);\n },\n\n onPromptComplete(channelId, _stopReason) {\n // Final flush\n flushReply(channelId, true);\n // Remove stop button from tool summary\n removeStopButton(channelId);\n // Clear state for next turn\n toolStates.delete(channelId);\n toolSummaryMessages.delete(channelId);\n replyBuffers.delete(channelId);\n replyMessages.delete(channelId);\n },\n };\n\n const sessionManager = new SessionManager(handlers);\n\n // --- Display helpers ---\n\n async function fetchChannel(channelId: string): Promise<TextChannel | null> {\n const cached = discordClient.channels.cache.get(channelId) as TextChannel | undefined;\n if (cached) return cached;\n try {\n const fetched = await discordClient.channels.fetch(channelId);\n return fetched as TextChannel;\n } catch {\n return null;\n }\n }\n\n async function updateToolSummaryMessage(channelId: string) {\n const tools = toolStates.get(channelId);\n if (!tools) return;\n\n const content = formatToolSummary(tools);\n const channel = await fetchChannel(channelId);\n if (!channel) return;\n\n const stopButton = new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder()\n .setCustomId(`stop_${channelId}`)\n .setLabel(\"\\u23F9 Stop\")\n .setStyle(ButtonStyle.Secondary),\n );\n\n const existing = toolSummaryMessages.get(channelId);\n if (existing) {\n await existing.edit({ content, components: [stopButton] }).catch(() => {});\n } else {\n const msg = await channel.send({ content, components: [stopButton] });\n toolSummaryMessages.set(channelId, msg);\n }\n }\n\n async function removeStopButton(channelId: string) {\n const msg = toolSummaryMessages.get(channelId);\n if (msg) {\n const tools = toolStates.get(channelId);\n const content = tools ? formatToolSummary(tools) : msg.content;\n await msg.edit({ content, components: [] }).catch(() => {});\n }\n }\n\n function scheduleFlushReply(channelId: string) {\n if (flushTimers.has(channelId)) return;\n flushTimers.set(\n channelId,\n setTimeout(() => {\n flushTimers.delete(channelId);\n flushReply(channelId, false);\n }, 500),\n );\n }\n\n async function flushReply(channelId: string, final: boolean) {\n const timer = flushTimers.get(channelId);\n if (timer) {\n clearTimeout(timer);\n flushTimers.delete(channelId);\n }\n\n const buffer = replyBuffers.get(channelId);\n if (!buffer) return;\n\n const channel = await fetchChannel(channelId);\n if (!channel) return;\n\n if (final) {\n // Send final reply as new message(s), delete streaming message\n const existing = replyMessages.get(channelId);\n if (existing) await existing.delete().catch(() => {});\n replyMessages.delete(channelId);\n\n const chunks = splitMessage(buffer);\n for (const chunk of chunks) {\n await channel.send(chunk);\n }\n replyBuffers.delete(channelId);\n } else {\n // Streaming update: edit existing message\n const truncated = buffer.length > 2000 ? buffer.slice(buffer.length - 1900) + \"...\" : buffer;\n const existing = replyMessages.get(channelId);\n if (existing) {\n await existing.edit(truncated).catch(() => {});\n } else {\n const msg = await channel.send(truncated);\n replyMessages.set(channelId, msg);\n }\n }\n }\n\n // --- Discord client setup ---\n\n discordClient = new Client({\n intents: [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n ],\n });\n\n discordClient.on(Events.ClientReady, async (c) => {\n console.log(`Discord bot ready: ${c.user.tag}`);\n\n // Register /ask slash command\n const askCommand = new SlashCommandBuilder()\n .setName(\"ask\")\n .setDescription(\"Ask the coding agent a question\")\n .addStringOption((opt) =>\n opt.setName(\"message\").setDescription(\"Your message\").setRequired(true),\n );\n\n const rest = new REST().setToken(config.discord.token);\n try {\n await rest.put(Routes.applicationCommands(c.application.id), {\n body: [askCommand.toJSON()],\n });\n console.log(\"Registered /ask command\");\n } catch (err) {\n console.error(\"Failed to register commands:\", err);\n }\n });\n\n // Handle @mention messages in configured channels\n discordClient.on(Events.MessageCreate, async (message: Message) => {\n if (message.author.bot) return;\n\n const channelId = message.channelId;\n const resolved = router.resolve(channelId);\n if (!resolved) return;\n\n const isMention = message.mentions.has(discordClient.user!);\n if (!isMention) return;\n\n // Strip the mention prefix\n const text = message.content.replace(/<@!?\\d+>/g, \"\").trim();\n\n if (!text) {\n await message.reply(\"Please provide a message.\");\n return;\n }\n\n if (sessionManager.isPrompting(channelId)) {\n await message.reply(\"\\u23F3 Agent is working. Your message has been queued.\");\n }\n\n try {\n await sessionManager.prompt(channelId, text, resolved.agent, message.author.id);\n } catch (err) {\n console.error(`Prompt failed for channel ${channelId}:`, err);\n await message.reply(\"An error occurred while processing your request.\").catch(() => {});\n }\n });\n\n // Handle stop button clicks\n discordClient.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isButton()) return;\n\n if (interaction.customId.startsWith(\"stop_\")) {\n const channelId = interaction.customId.replace(\"stop_\", \"\");\n const activeRequestor = sessionManager.getActiveRequestorId(channelId);\n\n // Only the user who triggered the current prompt can stop it\n if (activeRequestor && interaction.user.id !== activeRequestor) {\n await interaction.reply({ content: \"Only the user who started this prompt can stop it.\", ephemeral: true });\n return;\n }\n\n sessionManager.cancel(channelId);\n await interaction.update({ components: [] });\n }\n });\n\n // Handle /ask command\n discordClient.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isChatInputCommand()) return;\n if (interaction.commandName !== \"ask\") return;\n\n const channelId = interaction.channelId;\n const resolved = router.resolve(channelId);\n if (!resolved) {\n await interaction.reply({ content: \"This channel is not configured for ACP.\", ephemeral: true });\n return;\n }\n\n const text = interaction.options.getString(\"message\", true);\n await interaction.deferReply();\n\n if (sessionManager.isPrompting(channelId)) {\n await interaction.editReply(\"\\u23F3 Agent is working. Your message has been queued.\");\n } else {\n await interaction.editReply(`\\uD83D\\uDCAC Processing: ${text.slice(0, 100)}...`);\n }\n\n try {\n await sessionManager.prompt(channelId, text, resolved.agent, interaction.user.id);\n } catch (err) {\n console.error(`Prompt failed for channel ${channelId}:`, err);\n await interaction.followUp({ content: \"An error occurred while processing your request.\", ephemeral: true }).catch(() => {});\n }\n });\n\n // Graceful shutdown\n process.on(\"SIGTERM\", () => {\n sessionManager.teardownAll();\n discordClient.destroy();\n });\n\n process.on(\"SIGINT\", () => {\n sessionManager.teardownAll();\n discordClient.destroy();\n });\n\n try {\n await discordClient.login(config.discord.token);\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes(\"TOKEN_INVALID\") || message.includes(\"An invalid token was provided\")) {\n console.error(\"Error: Invalid Discord bot token. Check your config.toml.\");\n } else if (message.includes(\"ConnectTimeout\") || message.includes(\"ETIMEDOUT\") || message.includes(\"ECONNREFUSED\")) {\n console.error(\"Error: Cannot connect to Discord API. Check your network or proxy settings.\");\n console.error(\"Hint: Set HTTPS_PROXY=http://127.0.0.1:7890 if you need a proxy.\");\n } else {\n console.error(\"Error: Failed to connect to Discord:\", message);\n }\n process.exit(1);\n }\n}\n","import type { AppConfig, ResolvedChannelConfig } from \"../shared/types.js\";\nimport { resolveChannelConfig } from \"../shared/config.js\";\n\nexport class ChannelRouter {\n private config: AppConfig;\n\n constructor(config: AppConfig) {\n this.config = config;\n }\n\n resolve(channelId: string): ResolvedChannelConfig | null {\n return resolveChannelConfig(this.config, channelId);\n }\n\n isConfigured(channelId: string): boolean {\n return this.resolve(channelId) !== null;\n }\n}\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport { Readable, Writable } from \"node:stream\";\nimport { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } from \"@agentclientprotocol/sdk\";\nimport type { AgentConfig } from \"../shared/types.js\";\nimport { createAcpClient, type AcpEventHandlers } from \"./acp-client.js\";\n\ninterface ManagedSession {\n channelId: string;\n process: ChildProcess;\n connection: ClientSideConnection;\n sessionId: string;\n lastActivity: number;\n idleTimer: NodeJS.Timeout;\n prompting: boolean;\n queue: Array<{ text: string; requestorId: string }>;\n /** Set only when executePrompt begins — stable for the duration of the prompt */\n activePromptRequestorId: string;\n}\n\nexport class SessionManager {\n private sessions = new Map<string, ManagedSession>();\n private handlers: AcpEventHandlers;\n\n constructor(handlers: AcpEventHandlers) {\n this.handlers = handlers;\n }\n\n async prompt(channelId: string, text: string, agentConfig: AgentConfig, requestorId: string): Promise<string> {\n const session = await this.getOrCreate(channelId, agentConfig, requestorId);\n session.lastActivity = Date.now();\n this.resetIdleTimer(session, agentConfig.idle_timeout);\n\n if (session.prompting) {\n session.queue.push({ text, requestorId });\n return \"queued\";\n }\n\n return this.executePrompt(session, text, requestorId, agentConfig);\n }\n\n private async executePrompt(session: ManagedSession, text: string, requestorId: string, agentConfig: AgentConfig): Promise<string> {\n session.prompting = true;\n session.activePromptRequestorId = requestorId;\n try {\n const result = await session.connection.prompt({\n sessionId: session.sessionId,\n prompt: [{ type: \"text\", text }],\n });\n this.handlers.onPromptComplete(session.channelId, result.stopReason);\n return result.stopReason;\n } finally {\n session.prompting = false;\n // Process queue — await and catch to prevent unhandled rejections (#3)\n const next = session.queue.shift();\n if (next) {\n this.executePrompt(session, next.text, next.requestorId, agentConfig).catch((err) => {\n console.error(`Queued prompt failed for channel ${session.channelId}:`, err);\n });\n }\n }\n }\n\n cancel(channelId: string): void {\n const session = this.sessions.get(channelId);\n if (session) {\n session.connection.cancel({ sessionId: session.sessionId });\n }\n }\n\n private async getOrCreate(channelId: string, agentConfig: AgentConfig, requestorId: string): Promise<ManagedSession> {\n const existing = this.sessions.get(channelId);\n if (existing) return existing;\n return this.createSession(channelId, agentConfig, requestorId);\n }\n\n private async createSession(channelId: string, config: AgentConfig, requestorId: string): Promise<ManagedSession> {\n const proc = spawn(config.command, config.args, {\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n cwd: config.cwd,\n });\n\n // Handle spawn errors (ENOENT, permission denied, etc.) (#4)\n proc.on(\"error\", (err) => {\n console.error(`Agent process error for channel ${channelId}:`, err);\n const session = this.sessions.get(channelId);\n if (session?.process === proc) {\n clearTimeout(session.idleTimer);\n this.sessions.delete(channelId);\n }\n });\n\n proc.on(\"exit\", () => {\n const session = this.sessions.get(channelId);\n if (session?.process === proc) {\n this.sessions.delete(channelId);\n clearTimeout(session.idleTimer);\n }\n });\n\n // Wrap initialize/newSession in try/catch to clean up process on failure (#5)\n let connection: ClientSideConnection;\n let sessionId: string;\n try {\n const stream = ndJsonStream(\n Writable.toWeb(proc.stdin!) as WritableStream<Uint8Array>,\n Readable.toWeb(proc.stdout!) as ReadableStream<Uint8Array>,\n );\n\n const client = createAcpClient(channelId, this.handlers, () => {\n return this.sessions.get(channelId)?.activePromptRequestorId ?? requestorId;\n });\n connection = new ClientSideConnection((_agent) => client, stream);\n\n await connection.initialize({\n protocolVersion: PROTOCOL_VERSION,\n clientCapabilities: {\n fs: { readTextFile: true, writeTextFile: true },\n terminal: true,\n },\n clientInfo: {\n name: \"acp-discord\",\n title: \"ACP Discord Bot\",\n version: \"0.1.0\",\n },\n });\n\n const result = await connection.newSession({\n cwd: config.cwd,\n mcpServers: [],\n });\n sessionId = result.sessionId;\n } catch (err) {\n proc.kill();\n throw err;\n }\n\n const managed: ManagedSession = {\n channelId,\n process: proc,\n connection,\n sessionId,\n lastActivity: Date.now(),\n idleTimer: this.startIdleTimer(channelId, config.idle_timeout),\n prompting: false,\n queue: [],\n activePromptRequestorId: requestorId,\n };\n\n this.sessions.set(channelId, managed);\n return managed;\n }\n\n private startIdleTimer(channelId: string, timeoutSec: number): NodeJS.Timeout {\n return setTimeout(() => this.teardown(channelId), timeoutSec * 1000);\n }\n\n private resetIdleTimer(session: ManagedSession, timeoutSec: number): void {\n clearTimeout(session.idleTimer);\n session.idleTimer = this.startIdleTimer(session.channelId, timeoutSec);\n }\n\n teardown(channelId: string): void {\n const session = this.sessions.get(channelId);\n if (!session) return;\n clearTimeout(session.idleTimer);\n session.process.kill();\n this.sessions.delete(channelId);\n }\n\n teardownAll(): void {\n for (const channelId of this.sessions.keys()) {\n this.teardown(channelId);\n }\n }\n\n isPrompting(channelId: string): boolean {\n return this.sessions.get(channelId)?.prompting ?? false;\n }\n\n getActiveRequestorId(channelId: string): string | null {\n const session = this.sessions.get(channelId);\n if (!session?.prompting) return null;\n return session.activePromptRequestorId;\n }\n\n getActiveChannels(): string[] {\n return Array.from(this.sessions.keys());\n }\n}\n","import type {\n Client,\n RequestPermissionRequest,\n RequestPermissionResponse,\n SessionNotification,\n} from \"@agentclientprotocol/sdk\";\n\nexport interface AcpEventHandlers {\n onToolCall(channelId: string, toolCallId: string, title: string, kind: string, status: string): void;\n onToolCallUpdate(channelId: string, toolCallId: string, status: string): void;\n onAgentMessageChunk(channelId: string, text: string): void;\n onPermissionRequest(\n channelId: string,\n requestorId: string,\n toolCall: { toolCallId: string; title: string; kind: string },\n options: Array<{ optionId: string; name: string; kind: string }>,\n ): Promise<{ outcome: \"selected\"; optionId: string } | { outcome: \"cancelled\" }>;\n onPromptComplete(channelId: string, stopReason: string): void;\n}\n\nexport function createAcpClient(\n channelId: string,\n handlers: AcpEventHandlers,\n getRequestorId: () => string,\n): Client {\n return {\n async requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {\n const result = await handlers.onPermissionRequest(\n channelId,\n getRequestorId(),\n {\n toolCallId: params.toolCall.toolCallId,\n title: params.toolCall.title ?? \"Unknown\",\n kind: params.toolCall.kind ?? \"other\",\n },\n params.options.map((o: { optionId: string; name: string; kind: string }) => ({\n optionId: o.optionId,\n name: o.name,\n kind: o.kind,\n })),\n );\n\n if (result.outcome === \"selected\") {\n return { outcome: { outcome: \"selected\", optionId: result.optionId } };\n }\n return { outcome: { outcome: \"cancelled\" } };\n },\n\n async sessionUpdate(params: SessionNotification): Promise<void> {\n const update = params.update;\n switch (update.sessionUpdate) {\n case \"agent_message_chunk\": {\n if (update.content.type === \"text\") {\n handlers.onAgentMessageChunk(channelId, update.content.text);\n }\n break;\n }\n case \"tool_call\": {\n handlers.onToolCall(\n channelId,\n update.toolCallId,\n update.title ?? \"Unknown\",\n update.kind ?? \"other\",\n update.status ?? \"pending\",\n );\n break;\n }\n case \"tool_call_update\": {\n handlers.onToolCallUpdate(\n channelId,\n update.toolCallId,\n update.status ?? \"in_progress\",\n );\n break;\n }\n }\n },\n };\n}\n","import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n EmbedBuilder,\n type TextChannel,\n} from \"discord.js\";\n\nconst KIND_LABELS: Record<string, string> = {\n allow_once: \"\\u2705 Allow\",\n allow_always: \"\\u2705 Always Allow\",\n reject_once: \"\\u274C Reject\",\n reject_always: \"\\u274C Never Allow\",\n};\n\nconst KIND_STYLES: Record<string, ButtonStyle> = {\n allow_once: ButtonStyle.Success,\n allow_always: ButtonStyle.Success,\n reject_once: ButtonStyle.Danger,\n reject_always: ButtonStyle.Danger,\n};\n\nexport interface PermissionOption {\n optionId: string;\n name: string;\n kind: string;\n}\n\nexport async function sendPermissionRequest(\n channel: TextChannel,\n toolTitle: string,\n toolKind: string,\n options: PermissionOption[],\n requestorId: string,\n timeoutMs = 14 * 60 * 1000,\n): Promise<{ outcome: \"selected\"; optionId: string } | { outcome: \"cancelled\" }> {\n if (options.length === 0) {\n return { outcome: \"cancelled\" };\n }\n\n const embed = new EmbedBuilder()\n .setColor(0xffa500)\n .setTitle(`Permission: ${toolTitle}`)\n .setDescription(`Tool type: \\`${toolKind}\\``)\n .setTimestamp();\n\n const buttons = options.map((opt) =>\n new ButtonBuilder()\n .setCustomId(`perm_${opt.optionId}`)\n .setLabel(KIND_LABELS[opt.kind] ?? opt.name)\n .setStyle(KIND_STYLES[opt.kind] ?? ButtonStyle.Secondary),\n );\n\n // Discord allows max 5 buttons per ActionRow\n const rows: ActionRowBuilder<ButtonBuilder>[] = [];\n for (let i = 0; i < buttons.length; i += 5) {\n rows.push(new ActionRowBuilder<ButtonBuilder>().addComponents(buttons.slice(i, i + 5)));\n }\n\n const msg = await channel.send({ embeds: [embed], components: rows });\n\n return new Promise((resolve) => {\n const collector = msg.createMessageComponentCollector({\n filter: (i) => i.user.id === requestorId,\n time: timeoutMs,\n });\n\n collector.on(\"collect\", async (interaction) => {\n const optionId = interaction.customId.replace(\"perm_\", \"\");\n await interaction.update({ components: [] });\n collector.stop(\"selected\");\n resolve({ outcome: \"selected\", optionId });\n });\n\n collector.on(\"end\", (_collected, reason) => {\n if (reason === \"time\") {\n msg.edit({ components: [] }).catch(() => {});\n resolve({ outcome: \"cancelled\" });\n }\n });\n });\n}\n","const DISCORD_MAX_LENGTH = 2000;\n\nexport function splitMessage(text: string, maxLength = DISCORD_MAX_LENGTH): string[] {\n if (text.length <= maxLength) return [text];\n\n const chunks: string[] = [];\n let remaining = text;\n let inCodeBlock = false;\n let codeFence = \"\";\n\n while (remaining.length > 0) {\n if (remaining.length <= maxLength) {\n chunks.push(remaining);\n break;\n }\n\n // Find split point: prefer newline before maxLength\n let splitAt = maxLength;\n const lastNewline = remaining.lastIndexOf(\"\\n\", maxLength);\n if (lastNewline > maxLength * 0.5) {\n splitAt = lastNewline + 1;\n }\n\n let chunk = remaining.slice(0, splitAt);\n remaining = remaining.slice(splitAt);\n\n // Handle code blocks: count fences in this chunk\n const fenceMatches = chunk.match(/```\\w*/g) || [];\n for (const fence of fenceMatches) {\n if (!inCodeBlock) {\n inCodeBlock = true;\n codeFence = fence;\n } else {\n inCodeBlock = false;\n codeFence = \"\";\n }\n }\n\n // If we're inside a code block at the split, close and reopen\n if (inCodeBlock) {\n chunk += \"\\n```\";\n remaining = codeFence + \"\\n\" + remaining;\n inCodeBlock = false;\n codeFence = \"\";\n }\n\n chunks.push(chunk);\n }\n\n return chunks;\n}\n\nexport type ToolStatus = \"pending\" | \"in_progress\" | \"completed\" | \"failed\";\n\nconst STATUS_ICONS: Record<ToolStatus, string> = {\n pending: \"\\u23F3\", // ⏳\n in_progress: \"\\uD83D\\uDD04\", // 🔄\n completed: \"\\u2705\", // ✅\n failed: \"\\u274C\", // ❌\n};\n\nexport function formatToolSummary(\n tools: Map<string, { title: string; status: ToolStatus }>,\n): string {\n const lines: string[] = [];\n for (const [, tool] of tools) {\n lines.push(`${STATUS_ICONS[tool.status]} ${tool.title}`);\n }\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":["ActionRowBuilder","ButtonBuilder","ButtonStyle","ActionRowBuilder","ButtonBuilder","ButtonStyle"]}
1
+ {"version":3,"sources":["../src/daemon/index.ts","../src/daemon/discord-bot.ts","../src/daemon/channel-router.ts","../src/daemon/session-manager.ts","../src/daemon/acp-client.ts","../src/daemon/permission-ui.ts","../src/daemon/message-bridge.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { loadConfig } from \"../shared/config.js\";\nimport { writePid, removePid } from \"../cli/pid.js\";\nimport { startDiscordBot } from \"./discord-bot.js\";\n\nconst CONFIG_DIR = join(homedir(), \".acp-discord\");\nconst CONFIG_PATH = join(CONFIG_DIR, \"config.toml\");\nconst PID_PATH = join(CONFIG_DIR, \"daemon.pid\");\n\nexport async function runDaemon(): Promise<void> {\n // Load config first — if it fails, no stale PID file is left behind (#12)\n const config = loadConfig(CONFIG_PATH);\n\n writePid(PID_PATH, process.pid);\n process.on(\"exit\", () => removePid(PID_PATH));\n\n console.log(`acp-discord daemon started (PID: ${process.pid})`);\n console.log(`Loaded config: ${Object.keys(config.channels).length} channel(s)`);\n\n await startDiscordBot(config);\n}\n\nif (process.env.ACP_DISCORD_DAEMON === \"1\") {\n runDaemon().catch((err) => {\n console.error(\"Daemon failed:\", err);\n process.exit(1);\n });\n}\n","import {\n Client,\n GatewayIntentBits,\n Events,\n REST,\n Routes,\n SlashCommandBuilder,\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n type Message,\n type TextChannel,\n} from \"discord.js\";\nimport type { AppConfig } from \"../shared/types.js\";\nimport { ChannelRouter } from \"./channel-router.js\";\nimport { SessionManager } from \"./session-manager.js\";\nimport { sendPermissionRequest } from \"./permission-ui.js\";\nimport { splitMessage, formatToolSummary, type ToolStatus } from \"./message-bridge.js\";\nimport type { AcpEventHandlers } from \"./acp-client.js\";\n\nexport async function startDiscordBot(config: AppConfig): Promise<void> {\n const router = new ChannelRouter(config);\n\n // Per-channel state for display\n const toolStates = new Map<string, Map<string, { title: string; status: ToolStatus }>>();\n const toolSummaryMessages = new Map<string, Message>();\n const replyBuffers = new Map<string, string>();\n const replyMessages = new Map<string, Message>();\n const flushTimers = new Map<string, NodeJS.Timeout>();\n\n let discordClient: Client;\n\n const handlers: AcpEventHandlers = {\n onToolCall(channelId, toolCallId, title, _kind, status) {\n if (!toolStates.has(channelId)) toolStates.set(channelId, new Map());\n toolStates.get(channelId)!.set(toolCallId, { title, status: status as ToolStatus });\n updateToolSummaryMessage(channelId);\n },\n\n onToolCallUpdate(channelId, toolCallId, status) {\n const tools = toolStates.get(channelId);\n const tool = tools?.get(toolCallId);\n if (tool) {\n tool.status = status as ToolStatus;\n updateToolSummaryMessage(channelId);\n }\n },\n\n onAgentMessageChunk(channelId, text) {\n const current = replyBuffers.get(channelId) ?? \"\";\n replyBuffers.set(channelId, current + text);\n scheduleFlushReply(channelId);\n },\n\n async onPermissionRequest(channelId, requestorId, toolCall, options) {\n const channel = await fetchChannel(channelId);\n if (!channel) return { outcome: \"cancelled\" as const };\n return sendPermissionRequest(channel, toolCall.title, toolCall.kind, options, requestorId);\n },\n\n onPromptComplete(channelId, _stopReason) {\n // Final flush\n flushReply(channelId, true);\n // Remove stop button from tool summary\n removeStopButton(channelId);\n // Clear state for next turn\n toolStates.delete(channelId);\n toolSummaryMessages.delete(channelId);\n replyBuffers.delete(channelId);\n replyMessages.delete(channelId);\n },\n };\n\n const sessionManager = new SessionManager(handlers);\n\n // --- Display helpers ---\n\n async function fetchChannel(channelId: string): Promise<TextChannel | null> {\n const cached = discordClient.channels.cache.get(channelId) as TextChannel | undefined;\n if (cached) return cached;\n try {\n const fetched = await discordClient.channels.fetch(channelId);\n return fetched as TextChannel;\n } catch {\n return null;\n }\n }\n\n async function updateToolSummaryMessage(channelId: string) {\n const tools = toolStates.get(channelId);\n if (!tools) return;\n\n const content = formatToolSummary(tools);\n const channel = await fetchChannel(channelId);\n if (!channel) return;\n\n const stopButton = new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder()\n .setCustomId(`stop_${channelId}`)\n .setLabel(\"\\u23F9 Stop\")\n .setStyle(ButtonStyle.Secondary),\n );\n\n const existing = toolSummaryMessages.get(channelId);\n if (existing) {\n await existing.edit({ content, components: [stopButton] }).catch(() => {});\n } else {\n const msg = await channel.send({ content, components: [stopButton] });\n toolSummaryMessages.set(channelId, msg);\n }\n }\n\n async function removeStopButton(channelId: string) {\n const msg = toolSummaryMessages.get(channelId);\n if (msg) {\n const tools = toolStates.get(channelId);\n const content = tools ? formatToolSummary(tools) : msg.content;\n await msg.edit({ content, components: [] }).catch(() => {});\n }\n }\n\n function scheduleFlushReply(channelId: string) {\n if (flushTimers.has(channelId)) return;\n flushTimers.set(\n channelId,\n setTimeout(() => {\n flushTimers.delete(channelId);\n flushReply(channelId, false);\n }, 500),\n );\n }\n\n async function flushReply(channelId: string, final: boolean) {\n const timer = flushTimers.get(channelId);\n if (timer) {\n clearTimeout(timer);\n flushTimers.delete(channelId);\n }\n\n const buffer = replyBuffers.get(channelId);\n if (!buffer) return;\n\n const channel = await fetchChannel(channelId);\n if (!channel) return;\n\n if (final) {\n // Send final reply as new message(s), delete streaming message\n const existing = replyMessages.get(channelId);\n if (existing) await existing.delete().catch(() => {});\n replyMessages.delete(channelId);\n\n const chunks = splitMessage(buffer);\n for (const chunk of chunks) {\n await channel.send(chunk);\n }\n replyBuffers.delete(channelId);\n } else {\n // Streaming update: edit existing message\n const truncated = buffer.length > 2000 ? buffer.slice(buffer.length - 1900) + \"...\" : buffer;\n const existing = replyMessages.get(channelId);\n if (existing) {\n await existing.edit(truncated).catch(() => {});\n } else {\n const msg = await channel.send(truncated);\n replyMessages.set(channelId, msg);\n }\n }\n }\n\n // --- Discord client setup ---\n\n discordClient = new Client({\n intents: [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n ],\n });\n\n discordClient.on(Events.ClientReady, async (c) => {\n console.log(`Discord bot ready: ${c.user.tag}`);\n\n // Register /ask slash command\n const askCommand = new SlashCommandBuilder()\n .setName(\"ask\")\n .setDescription(\"Ask the coding agent a question\")\n .addStringOption((opt) =>\n opt.setName(\"message\").setDescription(\"Your message\").setRequired(true),\n );\n\n const rest = new REST().setToken(config.discord.token);\n try {\n await rest.put(Routes.applicationCommands(c.application.id), {\n body: [askCommand.toJSON()],\n });\n console.log(\"Registered /ask command\");\n } catch (err) {\n console.error(\"Failed to register commands:\", err);\n }\n });\n\n // Handle @mention messages in configured channels\n discordClient.on(Events.MessageCreate, async (message: Message) => {\n if (message.author.bot) return;\n\n const channelId = message.channelId;\n const resolved = router.resolve(channelId);\n if (!resolved) return;\n\n const isMention = message.mentions.has(discordClient.user!);\n if (!resolved.autoReply && !isMention) return;\n\n // Strip mention prefix if present\n const text = message.content.replace(/<@!?\\d+>/g, \"\").trim();\n\n if (!text) {\n await message.reply(\"Please provide a message.\");\n return;\n }\n\n if (sessionManager.isPrompting(channelId)) {\n await message.reply(\"\\u23F3 Agent is working. Your message has been queued.\");\n }\n\n try {\n await sessionManager.prompt(channelId, text, resolved.agent, message.author.id);\n } catch (err) {\n console.error(`Prompt failed for channel ${channelId}:`, err);\n await message.reply(\"An error occurred while processing your request.\").catch(() => {});\n }\n });\n\n // Handle stop button clicks\n discordClient.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isButton()) return;\n\n if (interaction.customId.startsWith(\"stop_\")) {\n const channelId = interaction.customId.replace(\"stop_\", \"\");\n const activeRequestor = sessionManager.getActiveRequestorId(channelId);\n\n // Only the user who triggered the current prompt can stop it\n if (activeRequestor && interaction.user.id !== activeRequestor) {\n await interaction.reply({ content: \"Only the user who started this prompt can stop it.\", ephemeral: true });\n return;\n }\n\n sessionManager.cancel(channelId);\n await interaction.update({ components: [] });\n }\n });\n\n // Handle /ask command\n discordClient.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isChatInputCommand()) return;\n if (interaction.commandName !== \"ask\") return;\n\n const channelId = interaction.channelId;\n const resolved = router.resolve(channelId);\n if (!resolved) {\n await interaction.reply({ content: \"This channel is not configured for ACP.\", ephemeral: true });\n return;\n }\n\n const text = interaction.options.getString(\"message\", true);\n await interaction.deferReply();\n\n if (sessionManager.isPrompting(channelId)) {\n await interaction.editReply(\"\\u23F3 Agent is working. Your message has been queued.\");\n } else {\n await interaction.editReply(`\\uD83D\\uDCAC Processing: ${text.slice(0, 100)}...`);\n }\n\n try {\n await sessionManager.prompt(channelId, text, resolved.agent, interaction.user.id);\n } catch (err) {\n console.error(`Prompt failed for channel ${channelId}:`, err);\n await interaction.followUp({ content: \"An error occurred while processing your request.\", ephemeral: true }).catch(() => {});\n }\n });\n\n // Graceful shutdown\n process.on(\"SIGTERM\", () => {\n sessionManager.teardownAll();\n discordClient.destroy();\n });\n\n process.on(\"SIGINT\", () => {\n sessionManager.teardownAll();\n discordClient.destroy();\n });\n\n try {\n await discordClient.login(config.discord.token);\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes(\"TOKEN_INVALID\") || message.includes(\"An invalid token was provided\")) {\n console.error(\"Error: Invalid Discord bot token. Check your config.toml.\");\n } else if (message.includes(\"ConnectTimeout\") || message.includes(\"ETIMEDOUT\") || message.includes(\"ECONNREFUSED\")) {\n console.error(\"Error: Cannot connect to Discord API. Check your network or proxy settings.\");\n console.error(\"Hint: Set HTTPS_PROXY=http://127.0.0.1:7890 if you need a proxy.\");\n } else {\n console.error(\"Error: Failed to connect to Discord:\", message);\n }\n process.exit(1);\n }\n}\n","import type { AppConfig, ResolvedChannelConfig } from \"../shared/types.js\";\nimport { resolveChannelConfig } from \"../shared/config.js\";\n\nexport class ChannelRouter {\n private config: AppConfig;\n\n constructor(config: AppConfig) {\n this.config = config;\n }\n\n resolve(channelId: string): ResolvedChannelConfig | null {\n return resolveChannelConfig(this.config, channelId);\n }\n\n isConfigured(channelId: string): boolean {\n return this.resolve(channelId) !== null;\n }\n}\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport { Readable, Writable } from \"node:stream\";\nimport { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } from \"@agentclientprotocol/sdk\";\nimport type { AgentConfig } from \"../shared/types.js\";\nimport { createAcpClient, type AcpEventHandlers } from \"./acp-client.js\";\n\ninterface ManagedSession {\n channelId: string;\n process: ChildProcess;\n connection: ClientSideConnection;\n sessionId: string;\n lastActivity: number;\n idleTimer: NodeJS.Timeout;\n prompting: boolean;\n queue: Array<{ text: string; requestorId: string }>;\n /** Set only when executePrompt begins — stable for the duration of the prompt */\n activePromptRequestorId: string;\n}\n\nexport class SessionManager {\n private sessions = new Map<string, ManagedSession>();\n private handlers: AcpEventHandlers;\n\n constructor(handlers: AcpEventHandlers) {\n this.handlers = handlers;\n }\n\n async prompt(channelId: string, text: string, agentConfig: AgentConfig, requestorId: string): Promise<string> {\n const session = await this.getOrCreate(channelId, agentConfig, requestorId);\n session.lastActivity = Date.now();\n this.resetIdleTimer(session, agentConfig.idle_timeout);\n\n if (session.prompting) {\n session.queue.push({ text, requestorId });\n return \"queued\";\n }\n\n return this.executePrompt(session, text, requestorId, agentConfig);\n }\n\n private async executePrompt(session: ManagedSession, text: string, requestorId: string, agentConfig: AgentConfig): Promise<string> {\n session.prompting = true;\n session.activePromptRequestorId = requestorId;\n try {\n const result = await session.connection.prompt({\n sessionId: session.sessionId,\n prompt: [{ type: \"text\", text }],\n });\n this.handlers.onPromptComplete(session.channelId, result.stopReason);\n return result.stopReason;\n } finally {\n session.prompting = false;\n // Process queue — await and catch to prevent unhandled rejections (#3)\n const next = session.queue.shift();\n if (next) {\n this.executePrompt(session, next.text, next.requestorId, agentConfig).catch((err) => {\n console.error(`Queued prompt failed for channel ${session.channelId}:`, err);\n });\n }\n }\n }\n\n cancel(channelId: string): void {\n const session = this.sessions.get(channelId);\n if (session) {\n session.connection.cancel({ sessionId: session.sessionId });\n }\n }\n\n private async getOrCreate(channelId: string, agentConfig: AgentConfig, requestorId: string): Promise<ManagedSession> {\n const existing = this.sessions.get(channelId);\n if (existing) return existing;\n return this.createSession(channelId, agentConfig, requestorId);\n }\n\n private async createSession(channelId: string, config: AgentConfig, requestorId: string): Promise<ManagedSession> {\n const proc = spawn(config.command, config.args, {\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n cwd: config.cwd,\n });\n\n // Handle spawn errors (ENOENT, permission denied, etc.) (#4)\n proc.on(\"error\", (err) => {\n console.error(`Agent process error for channel ${channelId}:`, err);\n const session = this.sessions.get(channelId);\n if (session?.process === proc) {\n clearTimeout(session.idleTimer);\n this.sessions.delete(channelId);\n }\n });\n\n proc.on(\"exit\", () => {\n const session = this.sessions.get(channelId);\n if (session?.process === proc) {\n this.sessions.delete(channelId);\n clearTimeout(session.idleTimer);\n }\n });\n\n // Wrap initialize/newSession in try/catch to clean up process on failure (#5)\n let connection: ClientSideConnection;\n let sessionId: string;\n try {\n const stream = ndJsonStream(\n Writable.toWeb(proc.stdin!) as WritableStream<Uint8Array>,\n Readable.toWeb(proc.stdout!) as ReadableStream<Uint8Array>,\n );\n\n const client = createAcpClient(channelId, this.handlers, () => {\n return this.sessions.get(channelId)?.activePromptRequestorId ?? requestorId;\n });\n connection = new ClientSideConnection((_agent) => client, stream);\n\n await connection.initialize({\n protocolVersion: PROTOCOL_VERSION,\n clientCapabilities: {\n fs: { readTextFile: true, writeTextFile: true },\n terminal: true,\n },\n clientInfo: {\n name: \"acp-discord\",\n title: \"ACP Discord Bot\",\n version: \"0.1.0\",\n },\n });\n\n const result = await connection.newSession({\n cwd: config.cwd,\n mcpServers: [],\n });\n sessionId = result.sessionId;\n } catch (err) {\n proc.kill();\n throw err;\n }\n\n const managed: ManagedSession = {\n channelId,\n process: proc,\n connection,\n sessionId,\n lastActivity: Date.now(),\n idleTimer: this.startIdleTimer(channelId, config.idle_timeout),\n prompting: false,\n queue: [],\n activePromptRequestorId: requestorId,\n };\n\n this.sessions.set(channelId, managed);\n return managed;\n }\n\n private startIdleTimer(channelId: string, timeoutSec: number): NodeJS.Timeout {\n return setTimeout(() => this.teardown(channelId), timeoutSec * 1000);\n }\n\n private resetIdleTimer(session: ManagedSession, timeoutSec: number): void {\n clearTimeout(session.idleTimer);\n session.idleTimer = this.startIdleTimer(session.channelId, timeoutSec);\n }\n\n teardown(channelId: string): void {\n const session = this.sessions.get(channelId);\n if (!session) return;\n clearTimeout(session.idleTimer);\n session.process.kill();\n this.sessions.delete(channelId);\n }\n\n teardownAll(): void {\n for (const channelId of this.sessions.keys()) {\n this.teardown(channelId);\n }\n }\n\n isPrompting(channelId: string): boolean {\n return this.sessions.get(channelId)?.prompting ?? false;\n }\n\n getActiveRequestorId(channelId: string): string | null {\n const session = this.sessions.get(channelId);\n if (!session?.prompting) return null;\n return session.activePromptRequestorId;\n }\n\n getActiveChannels(): string[] {\n return Array.from(this.sessions.keys());\n }\n}\n","import type {\n Client,\n RequestPermissionRequest,\n RequestPermissionResponse,\n SessionNotification,\n} from \"@agentclientprotocol/sdk\";\n\nexport interface AcpEventHandlers {\n onToolCall(channelId: string, toolCallId: string, title: string, kind: string, status: string): void;\n onToolCallUpdate(channelId: string, toolCallId: string, status: string): void;\n onAgentMessageChunk(channelId: string, text: string): void;\n onPermissionRequest(\n channelId: string,\n requestorId: string,\n toolCall: { toolCallId: string; title: string; kind: string },\n options: Array<{ optionId: string; name: string; kind: string }>,\n ): Promise<{ outcome: \"selected\"; optionId: string } | { outcome: \"cancelled\" }>;\n onPromptComplete(channelId: string, stopReason: string): void;\n}\n\nexport function createAcpClient(\n channelId: string,\n handlers: AcpEventHandlers,\n getRequestorId: () => string,\n): Client {\n return {\n async requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {\n const result = await handlers.onPermissionRequest(\n channelId,\n getRequestorId(),\n {\n toolCallId: params.toolCall.toolCallId,\n title: params.toolCall.title ?? \"Unknown\",\n kind: params.toolCall.kind ?? \"other\",\n },\n params.options.map((o: { optionId: string; name: string; kind: string }) => ({\n optionId: o.optionId,\n name: o.name,\n kind: o.kind,\n })),\n );\n\n if (result.outcome === \"selected\") {\n return { outcome: { outcome: \"selected\", optionId: result.optionId } };\n }\n return { outcome: { outcome: \"cancelled\" } };\n },\n\n async sessionUpdate(params: SessionNotification): Promise<void> {\n const update = params.update;\n switch (update.sessionUpdate) {\n case \"agent_message_chunk\": {\n if (update.content.type === \"text\") {\n handlers.onAgentMessageChunk(channelId, update.content.text);\n }\n break;\n }\n case \"tool_call\": {\n handlers.onToolCall(\n channelId,\n update.toolCallId,\n update.title ?? \"Unknown\",\n update.kind ?? \"other\",\n update.status ?? \"pending\",\n );\n break;\n }\n case \"tool_call_update\": {\n handlers.onToolCallUpdate(\n channelId,\n update.toolCallId,\n update.status ?? \"in_progress\",\n );\n break;\n }\n }\n },\n };\n}\n","import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n EmbedBuilder,\n type TextChannel,\n} from \"discord.js\";\n\nconst KIND_LABELS: Record<string, string> = {\n allow_once: \"\\u2705 Allow\",\n allow_always: \"\\u2705 Always Allow\",\n reject_once: \"\\u274C Reject\",\n reject_always: \"\\u274C Never Allow\",\n};\n\nconst KIND_STYLES: Record<string, ButtonStyle> = {\n allow_once: ButtonStyle.Success,\n allow_always: ButtonStyle.Success,\n reject_once: ButtonStyle.Danger,\n reject_always: ButtonStyle.Danger,\n};\n\nexport interface PermissionOption {\n optionId: string;\n name: string;\n kind: string;\n}\n\nexport async function sendPermissionRequest(\n channel: TextChannel,\n toolTitle: string,\n toolKind: string,\n options: PermissionOption[],\n requestorId: string,\n timeoutMs = 14 * 60 * 1000,\n): Promise<{ outcome: \"selected\"; optionId: string } | { outcome: \"cancelled\" }> {\n if (options.length === 0) {\n return { outcome: \"cancelled\" };\n }\n\n const embed = new EmbedBuilder()\n .setColor(0xffa500)\n .setTitle(`Permission: ${toolTitle}`)\n .setDescription(`Tool type: \\`${toolKind}\\``)\n .setTimestamp();\n\n const buttons = options.map((opt) =>\n new ButtonBuilder()\n .setCustomId(`perm_${opt.optionId}`)\n .setLabel(KIND_LABELS[opt.kind] ?? opt.name)\n .setStyle(KIND_STYLES[opt.kind] ?? ButtonStyle.Secondary),\n );\n\n // Discord allows max 5 buttons per ActionRow\n const rows: ActionRowBuilder<ButtonBuilder>[] = [];\n for (let i = 0; i < buttons.length; i += 5) {\n rows.push(new ActionRowBuilder<ButtonBuilder>().addComponents(buttons.slice(i, i + 5)));\n }\n\n const msg = await channel.send({ embeds: [embed], components: rows });\n\n return new Promise((resolve) => {\n const collector = msg.createMessageComponentCollector({\n filter: (i) => i.user.id === requestorId,\n time: timeoutMs,\n });\n\n collector.on(\"collect\", async (interaction) => {\n const optionId = interaction.customId.replace(\"perm_\", \"\");\n await interaction.update({ components: [] });\n collector.stop(\"selected\");\n resolve({ outcome: \"selected\", optionId });\n });\n\n collector.on(\"end\", (_collected, reason) => {\n if (reason === \"time\") {\n msg.edit({ components: [] }).catch(() => {});\n resolve({ outcome: \"cancelled\" });\n }\n });\n });\n}\n","const DISCORD_MAX_LENGTH = 2000;\n\nexport function splitMessage(text: string, maxLength = DISCORD_MAX_LENGTH): string[] {\n if (text.length <= maxLength) return [text];\n\n const chunks: string[] = [];\n let remaining = text;\n let inCodeBlock = false;\n let codeFence = \"\";\n\n while (remaining.length > 0) {\n if (remaining.length <= maxLength) {\n chunks.push(remaining);\n break;\n }\n\n // Find split point: prefer newline before maxLength\n let splitAt = maxLength;\n const lastNewline = remaining.lastIndexOf(\"\\n\", maxLength);\n if (lastNewline > maxLength * 0.5) {\n splitAt = lastNewline + 1;\n }\n\n let chunk = remaining.slice(0, splitAt);\n remaining = remaining.slice(splitAt);\n\n // Handle code blocks: count fences in this chunk\n const fenceMatches = chunk.match(/```\\w*/g) || [];\n for (const fence of fenceMatches) {\n if (!inCodeBlock) {\n inCodeBlock = true;\n codeFence = fence;\n } else {\n inCodeBlock = false;\n codeFence = \"\";\n }\n }\n\n // If we're inside a code block at the split, close and reopen\n if (inCodeBlock) {\n chunk += \"\\n```\";\n remaining = codeFence + \"\\n\" + remaining;\n inCodeBlock = false;\n codeFence = \"\";\n }\n\n chunks.push(chunk);\n }\n\n return chunks;\n}\n\nexport type ToolStatus = \"pending\" | \"in_progress\" | \"completed\" | \"failed\";\n\nconst STATUS_ICONS: Record<ToolStatus, string> = {\n pending: \"\\u23F3\", // ⏳\n in_progress: \"\\uD83D\\uDD04\", // 🔄\n completed: \"\\u2705\", // ✅\n failed: \"\\u274C\", // ❌\n};\n\nexport function formatToolSummary(\n tools: Map<string, { title: string; status: ToolStatus }>,\n): string {\n const lines: string[] = [];\n for (const [, tool] of tools) {\n lines.push(`${STATUS_ICONS[tool.status]} ${tool.title}`);\n }\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":["ActionRowBuilder","ButtonBuilder","ButtonStyle","ActionRowBuilder","ButtonBuilder","ButtonStyle"]}
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  parseConfig,
5
5
  readPid,
6
6
  removePid
7
- } from "./chunk-SITRIFHO.js";
7
+ } from "./chunk-QRVSGBED.js";
8
8
 
9
9
  // src/cli/index.ts
10
10
  import { Command as Command3 } from "commander";
@@ -270,6 +270,7 @@ You need to collect:
270
270
  1. Discord Bot Token (guide them to https://discord.com/developers/applications if needed)
271
271
  2. Default working directory (the project path the agent will work on)
272
272
  3. Channel IDs to bind (explain how to get channel IDs: right-click channel \u2192 Copy Channel ID)
273
+ 4. Reply mode per channel: ask whether the bot should respond to ALL messages in the channel (auto_reply = true) or only when @mentioned (auto_reply = false, the default)
273
274
 
274
275
  Once you have all info, write the config file using the write_text_file tool to ${CONFIG_PATH}.
275
276
 
@@ -286,6 +287,7 @@ idle_timeout = 600
286
287
 
287
288
  [channels.<channel-id>]
288
289
  agent = "default"
290
+ auto_reply = false # true = respond to all messages; false = only @mentions
289
291
  \`\`\`
290
292
 
291
293
  Be friendly and concise. Ask one question at a time.`;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli/index.ts","../src/cli/daemon.ts","../src/cli/autostart.ts","../src/cli/init.ts","../src/shared/detect-agents.ts","../src/index.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { makeDaemonCommand } from \"./daemon.js\";\nimport { makeInitCommand } from \"./init.js\";\n\nexport function createCli(): Command {\n const program = new Command();\n\n program\n .name(\"acp-discord\")\n .description(\"Discord bot for ACP coding agents\")\n .version(\"0.1.0\");\n\n program.addCommand(makeInitCommand());\n program.addCommand(makeDaemonCommand());\n\n return program;\n}\n","import { Command } from \"commander\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { spawn } from \"node:child_process\";\nimport { openSync, closeSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { isDaemonRunning, readPid, removePid } from \"./pid.js\";\nimport { enableAutostart, disableAutostart } from \"./autostart.js\";\n\nconst CONFIG_DIR = join(homedir(), \".acp-discord\");\nconst PID_PATH = join(CONFIG_DIR, \"daemon.pid\");\nconst LOG_PATH = join(CONFIG_DIR, \"daemon.log\");\nconst ERR_LOG_PATH = join(CONFIG_DIR, \"daemon.error.log\");\n\nexport function makeDaemonCommand(): Command {\n const daemon = new Command(\"daemon\").description(\"Manage the acp-discord daemon\");\n\n daemon\n .command(\"start\")\n .description(\"Start the daemon (background)\")\n .action(async () => {\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon already running (PID: ${pid})`);\n process.exit(1);\n }\n removePid(PID_PATH); // clean stale\n\n // In the bundled output, both index.js and daemon.js are in dist/\n const thisDir = fileURLToPath(new URL(\".\", import.meta.url));\n const daemonEntry = join(thisDir, \"daemon.js\");\n const outFd = openSync(LOG_PATH, \"a\");\n const errFd = openSync(ERR_LOG_PATH, \"a\");\n const child = spawn(process.execPath, [daemonEntry], {\n detached: true,\n stdio: [\"ignore\", outFd, errFd],\n env: { ...process.env, ACP_DISCORD_DAEMON: \"1\" },\n });\n\n child.unref();\n closeSync(outFd);\n closeSync(errFd);\n\n // Wait briefly and verify the daemon wrote its PID file (#11)\n await new Promise((resolve) => setTimeout(resolve, 1500));\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon started (PID: ${pid})`);\n process.exit(0);\n } else {\n console.error(`Daemon failed to start (forked PID: ${child.pid}).`);\n console.error(`Check logs: ${ERR_LOG_PATH}`);\n process.exit(1);\n }\n });\n\n daemon\n .command(\"run\")\n .description(\"Run the daemon in foreground (for service managers)\")\n .action(async () => {\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon already running (PID: ${pid})`);\n process.exit(1);\n }\n // Import and run directly in this process\n const { runDaemon } = await import(\"../daemon/index.js\");\n await runDaemon();\n });\n\n daemon\n .command(\"stop\")\n .description(\"Stop the daemon\")\n .action(async () => {\n const pid = readPid(PID_PATH);\n if (pid === null) {\n console.log(\"Daemon is not running\");\n return;\n }\n try {\n process.kill(pid, \"SIGTERM\");\n removePid(PID_PATH);\n console.log(`Daemon stopped (PID: ${pid})`);\n } catch {\n removePid(PID_PATH);\n console.log(\"Daemon was not running (stale PID removed)\");\n }\n });\n\n daemon\n .command(\"status\")\n .description(\"Show daemon status\")\n .action(async () => {\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon is running (PID: ${pid})`);\n } else {\n removePid(PID_PATH);\n console.log(\"Daemon is not running\");\n }\n });\n\n daemon\n .command(\"enable\")\n .description(\"Enable auto-start on boot\")\n .action(async () => {\n enableAutostart();\n });\n\n daemon\n .command(\"disable\")\n .description(\"Disable auto-start on boot\")\n .action(async () => {\n disableAutostart();\n });\n\n return daemon;\n}\n","import { writeFileSync, unlinkSync, existsSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir, platform } from \"node:os\";\nimport { execSync } from \"node:child_process\";\n\nconst SYSTEMD_DIR = join(homedir(), \".config\", \"systemd\", \"user\");\nconst SYSTEMD_SERVICE = \"acp-discord.service\";\nconst LAUNCHD_DIR = join(homedir(), \"Library\", \"LaunchAgents\");\nconst LAUNCHD_PLIST = \"com.acp-discord.plist\";\n\nfunction getNpxPath(): string {\n try {\n return execSync(\"which npx\", { encoding: \"utf-8\" }).trim();\n } catch {\n throw new Error(\"npx not found in PATH. Ensure Node.js is installed.\");\n }\n}\n\nexport function enableAutostart(): void {\n const os = platform();\n\n if (os === \"linux\") {\n mkdirSync(SYSTEMD_DIR, { recursive: true });\n const npx = getNpxPath();\n // Use \"daemon run\" for foreground mode — correct for systemd lifecycle (#10)\n const service = `[Unit]\nDescription=acp-discord daemon\nAfter=network.target\n\n[Service]\nExecStart=${npx} acp-discord daemon run\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=default.target\n`;\n const servicePath = join(SYSTEMD_DIR, SYSTEMD_SERVICE);\n writeFileSync(servicePath, service);\n try {\n execSync(\"systemctl --user daemon-reload\");\n execSync(`systemctl --user enable ${SYSTEMD_SERVICE}`);\n } catch (err) {\n console.error(\"Failed to enable systemd service:\", err instanceof Error ? err.message : err);\n return;\n }\n console.log(`Enabled systemd service: ${servicePath}`);\n console.log(\"Run: systemctl --user start acp-discord\");\n } else if (os === \"darwin\") {\n mkdirSync(LAUNCHD_DIR, { recursive: true });\n const npx = getNpxPath();\n // Use \"daemon run\" for foreground mode — correct for launchd lifecycle (#10)\n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>com.acp-discord</string>\n <key>ProgramArguments</key>\n <array>\n <string>${npx}</string>\n <string>acp-discord</string>\n <string>daemon</string>\n <string>run</string>\n </array>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>StandardOutPath</key>\n <string>${join(homedir(), \".acp-discord\", \"daemon.log\")}</string>\n <key>StandardErrorPath</key>\n <string>${join(homedir(), \".acp-discord\", \"daemon.error.log\")}</string>\n</dict>\n</plist>`;\n const plistPath = join(LAUNCHD_DIR, LAUNCHD_PLIST);\n writeFileSync(plistPath, plist);\n console.log(`Enabled launchd service: ${plistPath}`);\n console.log(\"Run: launchctl load \" + plistPath);\n } else {\n console.error(`Auto-start not supported on ${os}. Use your OS service manager manually.`);\n }\n}\n\nexport function disableAutostart(): void {\n const os = platform();\n\n if (os === \"linux\") {\n const servicePath = join(SYSTEMD_DIR, SYSTEMD_SERVICE);\n try {\n execSync(`systemctl --user disable ${SYSTEMD_SERVICE}`);\n } catch {\n // may not be enabled\n }\n if (existsSync(servicePath)) unlinkSync(servicePath);\n try {\n execSync(\"systemctl --user daemon-reload\");\n } catch (err) {\n console.error(\"Failed to reload systemd:\", err instanceof Error ? err.message : err);\n }\n console.log(\"Disabled systemd auto-start\");\n } else if (os === \"darwin\") {\n const plistPath = join(LAUNCHD_DIR, LAUNCHD_PLIST);\n try {\n execSync(`launchctl unload ${plistPath}`);\n } catch {\n // may not be loaded\n }\n if (existsSync(plistPath)) unlinkSync(plistPath);\n console.log(\"Disabled launchd auto-start\");\n } else {\n console.error(`Auto-start not supported on ${os}.`);\n }\n}\n","import { Command } from \"commander\";\nimport { join, resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { spawn } from \"node:child_process\";\nimport { Readable, Writable } from \"node:stream\";\nimport { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } from \"@agentclientprotocol/sdk\";\nimport type { Client } from \"@agentclientprotocol/sdk\";\nimport { detectInstalledAgents } from \"../shared/detect-agents.js\";\nimport { parseConfig } from \"../shared/config.js\";\n\nconst CONFIG_DIR = join(homedir(), \".acp-discord\");\nconst CONFIG_PATH = join(CONFIG_DIR, \"config.toml\");\n\n// Only auto-allow writes to the config directory during setup\nconst SAFE_WRITE_PREFIX = CONFIG_DIR;\n\nconst INIT_SYSTEM_PROMPT = `You are a setup assistant for acp-discord, a Discord bot that connects Discord channels to ACP coding agents.\n\nYour job is to help the user configure ~/.acp-discord/config.toml interactively.\n\nYou need to collect:\n1. Discord Bot Token (guide them to https://discord.com/developers/applications if needed)\n2. Default working directory (the project path the agent will work on)\n3. Channel IDs to bind (explain how to get channel IDs: right-click channel → Copy Channel ID)\n\nOnce you have all info, write the config file using the write_text_file tool to ${CONFIG_PATH}.\n\nConfig format (TOML):\n\\`\\`\\`toml\n[discord]\ntoken = \"<token>\"\n\n[agents.default]\ncommand = \"npx\"\nargs = [\"<acp-package>\"]\ncwd = \"<working-directory>\"\nidle_timeout = 600\n\n[channels.<channel-id>]\nagent = \"default\"\n\\`\\`\\`\n\nBe friendly and concise. Ask one question at a time.`;\n\nexport function makeInitCommand(): Command {\n return new Command(\"init\")\n .description(\"Interactive setup wizard\")\n .action(async () => {\n console.log(\"Welcome to acp-discord setup!\\n\");\n\n // Detect agents\n console.log(\"Detecting ACP-compatible agents...\");\n const agents = detectInstalledAgents();\n\n if (agents.length === 0) {\n console.error(\"No ACP-compatible agents found.\");\n console.error(\"Install one of: claude-code, codex, opencode, pi\");\n process.exit(1);\n }\n\n for (const agent of agents) {\n console.log(` \\u2713 ${agent.name} (found)`);\n }\n\n const selected = agents[0];\n console.log(`\\nUsing: ${selected.name}\\n`);\n console.log(\"Starting setup agent...\\n\");\n\n // Spawn ACP agent for interactive setup\n const proc = spawn(selected.command, [selected.acpPackage], {\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n });\n\n const stream = ndJsonStream(\n Writable.toWeb(proc.stdin!) as WritableStream<Uint8Array>,\n Readable.toWeb(proc.stdout!) as ReadableStream<Uint8Array>,\n );\n\n // Simple readline for user input\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const askUser = (prompt: string): Promise<string> =>\n new Promise((resolve) => rl.question(prompt, resolve));\n\n const client: Client = {\n async requestPermission(params) {\n const title = params.toolCall.title ?? \"Unknown\";\n const kind = params.toolCall.kind ?? \"other\";\n\n // Auto-allow only safe file writes within the config directory,\n // validated against actual tool locations (not spoofable title) (#2)\n const isSafeWrite = kind === \"write_text_file\" || kind === \"fs\";\n if (isSafeWrite && params.toolCall.locations?.length) {\n const allPathsSafe = params.toolCall.locations.every(\n (loc: { path: string }) => {\n const resolved = resolve(loc.path);\n return resolved.startsWith(SAFE_WRITE_PREFIX + \"/\") || resolved === SAFE_WRITE_PREFIX;\n },\n );\n if (allPathsSafe) {\n const allowOption = params.options.find((o: { kind: string }) => o.kind === \"allow_once\");\n if (allowOption) {\n return { outcome: { outcome: \"selected\" as const, optionId: allowOption.optionId } };\n }\n }\n }\n\n // For all other operations, ask the user\n console.log(`\\n--- Permission Request ---`);\n console.log(`Tool: ${title}`);\n console.log(`Type: ${kind}`);\n console.log(`Options:`);\n for (let i = 0; i < params.options.length; i++) {\n const opt = params.options[i];\n console.log(` ${i + 1}. ${opt.name} (${opt.kind})`);\n }\n\n const answer = await askUser(`Choose option (1-${params.options.length}, or 'c' to cancel): `);\n if (answer.toLowerCase() === \"c\") {\n const rejectOption = params.options.find((o: { kind: string }) => o.kind === \"reject_once\");\n if (rejectOption) {\n return { outcome: { outcome: \"selected\" as const, optionId: rejectOption.optionId } };\n }\n return { outcome: { outcome: \"cancelled\" as const } };\n }\n\n const idx = parseInt(answer, 10) - 1;\n if (idx >= 0 && idx < params.options.length) {\n return { outcome: { outcome: \"selected\" as const, optionId: params.options[idx].optionId } };\n }\n\n // Invalid input — default to reject\n const rejectOption = params.options.find((o: { kind: string }) => o.kind === \"reject_once\");\n if (rejectOption) {\n return { outcome: { outcome: \"selected\" as const, optionId: rejectOption.optionId } };\n }\n return { outcome: { outcome: \"cancelled\" as const } };\n },\n async sessionUpdate(params) {\n const update = params.update;\n if (update.sessionUpdate === \"agent_message_chunk\" && update.content.type === \"text\") {\n process.stdout.write(update.content.text);\n }\n },\n };\n\n const connection = new ClientSideConnection((_agent) => client, stream);\n\n await connection.initialize({\n protocolVersion: PROTOCOL_VERSION,\n clientCapabilities: {\n fs: { readTextFile: true, writeTextFile: true },\n terminal: false,\n },\n clientInfo: { name: \"acp-discord-init\", title: \"ACP Discord Init\", version: \"0.1.0\" },\n });\n\n mkdirSync(CONFIG_DIR, { recursive: true });\n\n const { sessionId } = await connection.newSession({\n cwd: CONFIG_DIR,\n mcpServers: [],\n });\n\n // Initial prompt\n await connection.prompt({\n sessionId,\n prompt: [\n { type: \"text\", text: INIT_SYSTEM_PROMPT },\n { type: \"text\", text: `The ACP agent package is: ${selected.acpPackage}\\nPlease start the setup.` },\n ],\n });\n\n // Interactive loop\n while (true) {\n const input = await askUser(\"\\n> \");\n if (input.toLowerCase() === \"exit\" || input.toLowerCase() === \"quit\") break;\n\n const result = await connection.prompt({\n sessionId,\n prompt: [{ type: \"text\", text: input }],\n });\n\n if (result.stopReason === \"end_turn\" && existsSync(CONFIG_PATH)) {\n // Validate the written config is structurally valid (#9)\n try {\n const content = readFileSync(CONFIG_PATH, \"utf-8\");\n parseConfig(content); // throws if invalid\n console.log(\"\\n\\nSetup complete! Config written to\", CONFIG_PATH);\n console.log(\"Run `npx acp-discord daemon start` to begin.\");\n break;\n } catch {\n // config not valid yet, continue\n }\n }\n }\n\n rl.close();\n proc.kill();\n });\n}\n","import { execFileSync } from \"node:child_process\";\n\nexport interface AgentInfo {\n name: string;\n command: string;\n acpPackage: string;\n detectCommand: string;\n detectArgs: string[];\n}\n\nexport const KNOWN_AGENTS: AgentInfo[] = [\n {\n name: \"claude-code\",\n command: \"npx\",\n acpPackage: \"@zed-industries/claude-agent-acp\",\n detectCommand: \"claude\",\n detectArgs: [\"--version\"],\n },\n {\n name: \"codex\",\n command: \"npx\",\n acpPackage: \"@openai/codex-acp\",\n detectCommand: \"codex\",\n detectArgs: [\"--version\"],\n },\n {\n name: \"opencode\",\n command: \"npx\",\n acpPackage: \"@opencode/acp\",\n detectCommand: \"opencode\",\n detectArgs: [\"--version\"],\n },\n {\n name: \"pi\",\n command: \"npx\",\n acpPackage: \"@anthropic-ai/pi-acp\",\n detectCommand: \"pi\",\n detectArgs: [\"--version\"],\n },\n];\n\nexport function detectInstalledAgents(): AgentInfo[] {\n const found: AgentInfo[] = [];\n for (const agent of KNOWN_AGENTS) {\n try {\n execFileSync(agent.detectCommand, agent.detectArgs, {\n stdio: \"ignore\",\n timeout: 5000,\n });\n found.push(agent);\n } catch {\n // not installed\n }\n }\n return found;\n}\n","import { createCli } from \"./cli/index.js\";\n\ncreateCli().parse();\n"],"mappings":";;;;;;;;;AAAA,SAAS,WAAAA,gBAAe;;;ACAxB,SAAS,eAAe;AACxB,SAAS,QAAAC,aAAY;AACrB,SAAS,WAAAC,gBAAe;AACxB,SAAS,aAAa;AACtB,SAAS,UAAU,iBAAiB;AACpC,SAAS,qBAAqB;;;ACL9B,SAAS,eAAe,YAAY,YAAY,iBAAiB;AACjE,SAAS,YAAY;AACrB,SAAS,SAAS,gBAAgB;AAClC,SAAS,gBAAgB;AAEzB,IAAM,cAAc,KAAK,QAAQ,GAAG,WAAW,WAAW,MAAM;AAChE,IAAM,kBAAkB;AACxB,IAAM,cAAc,KAAK,QAAQ,GAAG,WAAW,cAAc;AAC7D,IAAM,gBAAgB;AAEtB,SAAS,aAAqB;AAC5B,MAAI;AACF,WAAO,SAAS,aAAa,EAAE,UAAU,QAAQ,CAAC,EAAE,KAAK;AAAA,EAC3D,QAAQ;AACN,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AACF;AAEO,SAAS,kBAAwB;AACtC,QAAM,KAAK,SAAS;AAEpB,MAAI,OAAO,SAAS;AAClB,cAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAC1C,UAAM,MAAM,WAAW;AAEvB,UAAM,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,YAKR,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOX,UAAM,cAAc,KAAK,aAAa,eAAe;AACrD,kBAAc,aAAa,OAAO;AAClC,QAAI;AACF,eAAS,gCAAgC;AACzC,eAAS,2BAA2B,eAAe,EAAE;AAAA,IACvD,SAAS,KAAK;AACZ,cAAQ,MAAM,qCAAqC,eAAe,QAAQ,IAAI,UAAU,GAAG;AAC3F;AAAA,IACF;AACA,YAAQ,IAAI,4BAA4B,WAAW,EAAE;AACrD,YAAQ,IAAI,yCAAyC;AAAA,EACvD,WAAW,OAAO,UAAU;AAC1B,cAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAC1C,UAAM,MAAM,WAAW;AAEvB,UAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAQJ,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAUL,KAAK,QAAQ,GAAG,gBAAgB,YAAY,CAAC;AAAA;AAAA,YAE7C,KAAK,QAAQ,GAAG,gBAAgB,kBAAkB,CAAC;AAAA;AAAA;AAG3D,UAAM,YAAY,KAAK,aAAa,aAAa;AACjD,kBAAc,WAAW,KAAK;AAC9B,YAAQ,IAAI,4BAA4B,SAAS,EAAE;AACnD,YAAQ,IAAI,yBAAyB,SAAS;AAAA,EAChD,OAAO;AACL,YAAQ,MAAM,+BAA+B,EAAE,yCAAyC;AAAA,EAC1F;AACF;AAEO,SAAS,mBAAyB;AACvC,QAAM,KAAK,SAAS;AAEpB,MAAI,OAAO,SAAS;AAClB,UAAM,cAAc,KAAK,aAAa,eAAe;AACrD,QAAI;AACF,eAAS,4BAA4B,eAAe,EAAE;AAAA,IACxD,QAAQ;AAAA,IAER;AACA,QAAI,WAAW,WAAW,EAAG,YAAW,WAAW;AACnD,QAAI;AACF,eAAS,gCAAgC;AAAA,IAC3C,SAAS,KAAK;AACZ,cAAQ,MAAM,6BAA6B,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,IACrF;AACA,YAAQ,IAAI,6BAA6B;AAAA,EAC3C,WAAW,OAAO,UAAU;AAC1B,UAAM,YAAY,KAAK,aAAa,aAAa;AACjD,QAAI;AACF,eAAS,oBAAoB,SAAS,EAAE;AAAA,IAC1C,QAAQ;AAAA,IAER;AACA,QAAI,WAAW,SAAS,EAAG,YAAW,SAAS;AAC/C,YAAQ,IAAI,6BAA6B;AAAA,EAC3C,OAAO;AACL,YAAQ,MAAM,+BAA+B,EAAE,GAAG;AAAA,EACpD;AACF;;;ADxGA,IAAM,aAAaC,MAAKC,SAAQ,GAAG,cAAc;AACjD,IAAM,WAAWD,MAAK,YAAY,YAAY;AAC9C,IAAM,WAAWA,MAAK,YAAY,YAAY;AAC9C,IAAM,eAAeA,MAAK,YAAY,kBAAkB;AAEjD,SAAS,oBAA6B;AAC3C,QAAM,SAAS,IAAI,QAAQ,QAAQ,EAAE,YAAY,+BAA+B;AAEhF,SACG,QAAQ,OAAO,EACf,YAAY,+BAA+B,EAC3C,OAAO,YAAY;AAClB,QAAI,gBAAgB,QAAQ,GAAG;AAC7B,YAAM,MAAM,QAAQ,QAAQ;AAC5B,cAAQ,IAAI,gCAAgC,GAAG,GAAG;AAClD,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,cAAU,QAAQ;AAGlB,UAAM,UAAU,cAAc,IAAI,IAAI,KAAK,YAAY,GAAG,CAAC;AAC3D,UAAM,cAAcA,MAAK,SAAS,WAAW;AAC7C,UAAM,QAAQ,SAAS,UAAU,GAAG;AACpC,UAAM,QAAQ,SAAS,cAAc,GAAG;AACxC,UAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,WAAW,GAAG;AAAA,MACnD,UAAU;AAAA,MACV,OAAO,CAAC,UAAU,OAAO,KAAK;AAAA,MAC9B,KAAK,EAAE,GAAG,QAAQ,KAAK,oBAAoB,IAAI;AAAA,IACjD,CAAC;AAED,UAAM,MAAM;AACZ,cAAU,KAAK;AACf,cAAU,KAAK;AAGf,UAAM,IAAI,QAAQ,CAACE,aAAY,WAAWA,UAAS,IAAI,CAAC;AACxD,QAAI,gBAAgB,QAAQ,GAAG;AAC7B,YAAM,MAAM,QAAQ,QAAQ;AAC5B,cAAQ,IAAI,wBAAwB,GAAG,GAAG;AAC1C,cAAQ,KAAK,CAAC;AAAA,IAChB,OAAO;AACL,cAAQ,MAAM,uCAAuC,MAAM,GAAG,IAAI;AAClE,cAAQ,MAAM,eAAe,YAAY,EAAE;AAC3C,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,CAAC;AAEH,SACG,QAAQ,KAAK,EACb,YAAY,qDAAqD,EACjE,OAAO,YAAY;AAClB,QAAI,gBAAgB,QAAQ,GAAG;AAC7B,YAAM,MAAM,QAAQ,QAAQ;AAC5B,cAAQ,IAAI,gCAAgC,GAAG,GAAG;AAClD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,EAAE,UAAU,IAAI,MAAM,OAAO,aAAoB;AACvD,UAAM,UAAU;AAAA,EAClB,CAAC;AAEH,SACG,QAAQ,MAAM,EACd,YAAY,iBAAiB,EAC7B,OAAO,YAAY;AAClB,UAAM,MAAM,QAAQ,QAAQ;AAC5B,QAAI,QAAQ,MAAM;AAChB,cAAQ,IAAI,uBAAuB;AACnC;AAAA,IACF;AACA,QAAI;AACF,cAAQ,KAAK,KAAK,SAAS;AAC3B,gBAAU,QAAQ;AAClB,cAAQ,IAAI,wBAAwB,GAAG,GAAG;AAAA,IAC5C,QAAQ;AACN,gBAAU,QAAQ;AAClB,cAAQ,IAAI,4CAA4C;AAAA,IAC1D;AAAA,EACF,CAAC;AAEH,SACG,QAAQ,QAAQ,EAChB,YAAY,oBAAoB,EAChC,OAAO,YAAY;AAClB,QAAI,gBAAgB,QAAQ,GAAG;AAC7B,YAAM,MAAM,QAAQ,QAAQ;AAC5B,cAAQ,IAAI,2BAA2B,GAAG,GAAG;AAAA,IAC/C,OAAO;AACL,gBAAU,QAAQ;AAClB,cAAQ,IAAI,uBAAuB;AAAA,IACrC;AAAA,EACF,CAAC;AAEH,SACG,QAAQ,QAAQ,EAChB,YAAY,2BAA2B,EACvC,OAAO,YAAY;AAClB,oBAAgB;AAAA,EAClB,CAAC;AAEH,SACG,QAAQ,SAAS,EACjB,YAAY,4BAA4B,EACxC,OAAO,YAAY;AAClB,qBAAiB;AAAA,EACnB,CAAC;AAEH,SAAO;AACT;;;AErHA,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAAC,OAAM,eAAe;AAC9B,SAAS,WAAAC,gBAAe;AACxB,SAAS,cAAAC,aAAY,aAAAC,YAAW,oBAAoB;AACpD,SAAS,SAAAC,cAAa;AACtB,SAAS,UAAU,gBAAgB;AACnC,SAAS,sBAAsB,cAAc,wBAAwB;;;ACNrE,SAAS,oBAAoB;AAUtB,IAAM,eAA4B;AAAA,EACvC;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,YAAY,CAAC,WAAW;AAAA,EAC1B;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,YAAY,CAAC,WAAW;AAAA,EAC1B;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,YAAY,CAAC,WAAW;AAAA,EAC1B;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,YAAY,CAAC,WAAW;AAAA,EAC1B;AACF;AAEO,SAAS,wBAAqC;AACnD,QAAM,QAAqB,CAAC;AAC5B,aAAW,SAAS,cAAc;AAChC,QAAI;AACF,mBAAa,MAAM,eAAe,MAAM,YAAY;AAAA,QAClD,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AACD,YAAM,KAAK,KAAK;AAAA,IAClB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;;;AD5CA,IAAMC,cAAaC,MAAKC,SAAQ,GAAG,cAAc;AACjD,IAAM,cAAcD,MAAKD,aAAY,aAAa;AAGlD,IAAM,oBAAoBA;AAE1B,IAAM,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kFASuD,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBtF,SAAS,kBAA2B;AACzC,SAAO,IAAIG,SAAQ,MAAM,EACtB,YAAY,0BAA0B,EACtC,OAAO,YAAY;AAClB,YAAQ,IAAI,iCAAiC;AAG7C,YAAQ,IAAI,oCAAoC;AAChD,UAAM,SAAS,sBAAsB;AAErC,QAAI,OAAO,WAAW,GAAG;AACvB,cAAQ,MAAM,iCAAiC;AAC/C,cAAQ,MAAM,kDAAkD;AAChE,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,eAAW,SAAS,QAAQ;AAC1B,cAAQ,IAAI,YAAY,MAAM,IAAI,UAAU;AAAA,IAC9C;AAEA,UAAM,WAAW,OAAO,CAAC;AACzB,YAAQ,IAAI;AAAA,SAAY,SAAS,IAAI;AAAA,CAAI;AACzC,YAAQ,IAAI,2BAA2B;AAGvC,UAAM,OAAOC,OAAM,SAAS,SAAS,CAAC,SAAS,UAAU,GAAG;AAAA,MAC1D,OAAO,CAAC,QAAQ,QAAQ,SAAS;AAAA,IACnC,CAAC;AAED,UAAM,SAAS;AAAA,MACb,SAAS,MAAM,KAAK,KAAM;AAAA,MAC1B,SAAS,MAAM,KAAK,MAAO;AAAA,IAC7B;AAGA,UAAM,WAAW,MAAM,OAAO,UAAe;AAC7C,UAAM,KAAK,SAAS,gBAAgB;AAAA,MAClC,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,IAClB,CAAC;AAED,UAAM,UAAU,CAAC,WACf,IAAI,QAAQ,CAACC,aAAY,GAAG,SAAS,QAAQA,QAAO,CAAC;AAEvD,UAAM,SAAiB;AAAA,MACrB,MAAM,kBAAkB,QAAQ;AAC9B,cAAM,QAAQ,OAAO,SAAS,SAAS;AACvC,cAAM,OAAO,OAAO,SAAS,QAAQ;AAIrC,cAAM,cAAc,SAAS,qBAAqB,SAAS;AAC3D,YAAI,eAAe,OAAO,SAAS,WAAW,QAAQ;AACpD,gBAAM,eAAe,OAAO,SAAS,UAAU;AAAA,YAC7C,CAAC,QAA0B;AACzB,oBAAM,WAAW,QAAQ,IAAI,IAAI;AACjC,qBAAO,SAAS,WAAW,oBAAoB,GAAG,KAAK,aAAa;AAAA,YACtE;AAAA,UACF;AACA,cAAI,cAAc;AAChB,kBAAM,cAAc,OAAO,QAAQ,KAAK,CAAC,MAAwB,EAAE,SAAS,YAAY;AACxF,gBAAI,aAAa;AACf,qBAAO,EAAE,SAAS,EAAE,SAAS,YAAqB,UAAU,YAAY,SAAS,EAAE;AAAA,YACrF;AAAA,UACF;AAAA,QACF;AAGA,gBAAQ,IAAI;AAAA,2BAA8B;AAC1C,gBAAQ,IAAI,SAAS,KAAK,EAAE;AAC5B,gBAAQ,IAAI,SAAS,IAAI,EAAE;AAC3B,gBAAQ,IAAI,UAAU;AACtB,iBAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,KAAK;AAC9C,gBAAM,MAAM,OAAO,QAAQ,CAAC;AAC5B,kBAAQ,IAAI,KAAK,IAAI,CAAC,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,GAAG;AAAA,QACrD;AAEA,cAAM,SAAS,MAAM,QAAQ,oBAAoB,OAAO,QAAQ,MAAM,uBAAuB;AAC7F,YAAI,OAAO,YAAY,MAAM,KAAK;AAChC,gBAAMC,gBAAe,OAAO,QAAQ,KAAK,CAAC,MAAwB,EAAE,SAAS,aAAa;AAC1F,cAAIA,eAAc;AAChB,mBAAO,EAAE,SAAS,EAAE,SAAS,YAAqB,UAAUA,cAAa,SAAS,EAAE;AAAA,UACtF;AACA,iBAAO,EAAE,SAAS,EAAE,SAAS,YAAqB,EAAE;AAAA,QACtD;AAEA,cAAM,MAAM,SAAS,QAAQ,EAAE,IAAI;AACnC,YAAI,OAAO,KAAK,MAAM,OAAO,QAAQ,QAAQ;AAC3C,iBAAO,EAAE,SAAS,EAAE,SAAS,YAAqB,UAAU,OAAO,QAAQ,GAAG,EAAE,SAAS,EAAE;AAAA,QAC7F;AAGA,cAAM,eAAe,OAAO,QAAQ,KAAK,CAAC,MAAwB,EAAE,SAAS,aAAa;AAC1F,YAAI,cAAc;AAChB,iBAAO,EAAE,SAAS,EAAE,SAAS,YAAqB,UAAU,aAAa,SAAS,EAAE;AAAA,QACtF;AACA,eAAO,EAAE,SAAS,EAAE,SAAS,YAAqB,EAAE;AAAA,MACtD;AAAA,MACA,MAAM,cAAc,QAAQ;AAC1B,cAAM,SAAS,OAAO;AACtB,YAAI,OAAO,kBAAkB,yBAAyB,OAAO,QAAQ,SAAS,QAAQ;AACpF,kBAAQ,OAAO,MAAM,OAAO,QAAQ,IAAI;AAAA,QAC1C;AAAA,MACF;AAAA,IACF;AAEA,UAAM,aAAa,IAAI,qBAAqB,CAAC,WAAW,QAAQ,MAAM;AAEtE,UAAM,WAAW,WAAW;AAAA,MAC1B,iBAAiB;AAAA,MACjB,oBAAoB;AAAA,QAClB,IAAI,EAAE,cAAc,MAAM,eAAe,KAAK;AAAA,QAC9C,UAAU;AAAA,MACZ;AAAA,MACA,YAAY,EAAE,MAAM,oBAAoB,OAAO,oBAAoB,SAAS,QAAQ;AAAA,IACtF,CAAC;AAED,IAAAC,WAAUP,aAAY,EAAE,WAAW,KAAK,CAAC;AAEzC,UAAM,EAAE,UAAU,IAAI,MAAM,WAAW,WAAW;AAAA,MAChD,KAAKA;AAAA,MACL,YAAY,CAAC;AAAA,IACf,CAAC;AAGD,UAAM,WAAW,OAAO;AAAA,MACtB;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,MAAM,QAAQ,MAAM,mBAAmB;AAAA,QACzC,EAAE,MAAM,QAAQ,MAAM,6BAA6B,SAAS,UAAU;AAAA,yBAA4B;AAAA,MACpG;AAAA,IACF,CAAC;AAGD,WAAO,MAAM;AACX,YAAM,QAAQ,MAAM,QAAQ,MAAM;AAClC,UAAI,MAAM,YAAY,MAAM,UAAU,MAAM,YAAY,MAAM,OAAQ;AAEtE,YAAM,SAAS,MAAM,WAAW,OAAO;AAAA,QACrC;AAAA,QACA,QAAQ,CAAC,EAAE,MAAM,QAAQ,MAAM,MAAM,CAAC;AAAA,MACxC,CAAC;AAED,UAAI,OAAO,eAAe,cAAcQ,YAAW,WAAW,GAAG;AAE/D,YAAI;AACF,gBAAM,UAAU,aAAa,aAAa,OAAO;AACjD,sBAAY,OAAO;AACnB,kBAAQ,IAAI,yCAAyC,WAAW;AAChE,kBAAQ,IAAI,8CAA8C;AAC1D;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAEA,OAAG,MAAM;AACT,SAAK,KAAK;AAAA,EACZ,CAAC;AACL;;;AHzMO,SAAS,YAAqB;AACnC,QAAM,UAAU,IAAIC,SAAQ;AAE5B,UACG,KAAK,aAAa,EAClB,YAAY,mCAAmC,EAC/C,QAAQ,OAAO;AAElB,UAAQ,WAAW,gBAAgB,CAAC;AACpC,UAAQ,WAAW,kBAAkB,CAAC;AAEtC,SAAO;AACT;;;AKdA,UAAU,EAAE,MAAM;","names":["Command","join","homedir","join","homedir","resolve","Command","join","homedir","existsSync","mkdirSync","spawn","CONFIG_DIR","join","homedir","Command","spawn","resolve","rejectOption","mkdirSync","existsSync","Command"]}
1
+ {"version":3,"sources":["../src/cli/index.ts","../src/cli/daemon.ts","../src/cli/autostart.ts","../src/cli/init.ts","../src/shared/detect-agents.ts","../src/index.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { makeDaemonCommand } from \"./daemon.js\";\nimport { makeInitCommand } from \"./init.js\";\n\nexport function createCli(): Command {\n const program = new Command();\n\n program\n .name(\"acp-discord\")\n .description(\"Discord bot for ACP coding agents\")\n .version(\"0.1.0\");\n\n program.addCommand(makeInitCommand());\n program.addCommand(makeDaemonCommand());\n\n return program;\n}\n","import { Command } from \"commander\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { spawn } from \"node:child_process\";\nimport { openSync, closeSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { isDaemonRunning, readPid, removePid } from \"./pid.js\";\nimport { enableAutostart, disableAutostart } from \"./autostart.js\";\n\nconst CONFIG_DIR = join(homedir(), \".acp-discord\");\nconst PID_PATH = join(CONFIG_DIR, \"daemon.pid\");\nconst LOG_PATH = join(CONFIG_DIR, \"daemon.log\");\nconst ERR_LOG_PATH = join(CONFIG_DIR, \"daemon.error.log\");\n\nexport function makeDaemonCommand(): Command {\n const daemon = new Command(\"daemon\").description(\"Manage the acp-discord daemon\");\n\n daemon\n .command(\"start\")\n .description(\"Start the daemon (background)\")\n .action(async () => {\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon already running (PID: ${pid})`);\n process.exit(1);\n }\n removePid(PID_PATH); // clean stale\n\n // In the bundled output, both index.js and daemon.js are in dist/\n const thisDir = fileURLToPath(new URL(\".\", import.meta.url));\n const daemonEntry = join(thisDir, \"daemon.js\");\n const outFd = openSync(LOG_PATH, \"a\");\n const errFd = openSync(ERR_LOG_PATH, \"a\");\n const child = spawn(process.execPath, [daemonEntry], {\n detached: true,\n stdio: [\"ignore\", outFd, errFd],\n env: { ...process.env, ACP_DISCORD_DAEMON: \"1\" },\n });\n\n child.unref();\n closeSync(outFd);\n closeSync(errFd);\n\n // Wait briefly and verify the daemon wrote its PID file (#11)\n await new Promise((resolve) => setTimeout(resolve, 1500));\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon started (PID: ${pid})`);\n process.exit(0);\n } else {\n console.error(`Daemon failed to start (forked PID: ${child.pid}).`);\n console.error(`Check logs: ${ERR_LOG_PATH}`);\n process.exit(1);\n }\n });\n\n daemon\n .command(\"run\")\n .description(\"Run the daemon in foreground (for service managers)\")\n .action(async () => {\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon already running (PID: ${pid})`);\n process.exit(1);\n }\n // Import and run directly in this process\n const { runDaemon } = await import(\"../daemon/index.js\");\n await runDaemon();\n });\n\n daemon\n .command(\"stop\")\n .description(\"Stop the daemon\")\n .action(async () => {\n const pid = readPid(PID_PATH);\n if (pid === null) {\n console.log(\"Daemon is not running\");\n return;\n }\n try {\n process.kill(pid, \"SIGTERM\");\n removePid(PID_PATH);\n console.log(`Daemon stopped (PID: ${pid})`);\n } catch {\n removePid(PID_PATH);\n console.log(\"Daemon was not running (stale PID removed)\");\n }\n });\n\n daemon\n .command(\"status\")\n .description(\"Show daemon status\")\n .action(async () => {\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon is running (PID: ${pid})`);\n } else {\n removePid(PID_PATH);\n console.log(\"Daemon is not running\");\n }\n });\n\n daemon\n .command(\"enable\")\n .description(\"Enable auto-start on boot\")\n .action(async () => {\n enableAutostart();\n });\n\n daemon\n .command(\"disable\")\n .description(\"Disable auto-start on boot\")\n .action(async () => {\n disableAutostart();\n });\n\n return daemon;\n}\n","import { writeFileSync, unlinkSync, existsSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir, platform } from \"node:os\";\nimport { execSync } from \"node:child_process\";\n\nconst SYSTEMD_DIR = join(homedir(), \".config\", \"systemd\", \"user\");\nconst SYSTEMD_SERVICE = \"acp-discord.service\";\nconst LAUNCHD_DIR = join(homedir(), \"Library\", \"LaunchAgents\");\nconst LAUNCHD_PLIST = \"com.acp-discord.plist\";\n\nfunction getNpxPath(): string {\n try {\n return execSync(\"which npx\", { encoding: \"utf-8\" }).trim();\n } catch {\n throw new Error(\"npx not found in PATH. Ensure Node.js is installed.\");\n }\n}\n\nexport function enableAutostart(): void {\n const os = platform();\n\n if (os === \"linux\") {\n mkdirSync(SYSTEMD_DIR, { recursive: true });\n const npx = getNpxPath();\n // Use \"daemon run\" for foreground mode — correct for systemd lifecycle (#10)\n const service = `[Unit]\nDescription=acp-discord daemon\nAfter=network.target\n\n[Service]\nExecStart=${npx} acp-discord daemon run\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=default.target\n`;\n const servicePath = join(SYSTEMD_DIR, SYSTEMD_SERVICE);\n writeFileSync(servicePath, service);\n try {\n execSync(\"systemctl --user daemon-reload\");\n execSync(`systemctl --user enable ${SYSTEMD_SERVICE}`);\n } catch (err) {\n console.error(\"Failed to enable systemd service:\", err instanceof Error ? err.message : err);\n return;\n }\n console.log(`Enabled systemd service: ${servicePath}`);\n console.log(\"Run: systemctl --user start acp-discord\");\n } else if (os === \"darwin\") {\n mkdirSync(LAUNCHD_DIR, { recursive: true });\n const npx = getNpxPath();\n // Use \"daemon run\" for foreground mode — correct for launchd lifecycle (#10)\n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>com.acp-discord</string>\n <key>ProgramArguments</key>\n <array>\n <string>${npx}</string>\n <string>acp-discord</string>\n <string>daemon</string>\n <string>run</string>\n </array>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>StandardOutPath</key>\n <string>${join(homedir(), \".acp-discord\", \"daemon.log\")}</string>\n <key>StandardErrorPath</key>\n <string>${join(homedir(), \".acp-discord\", \"daemon.error.log\")}</string>\n</dict>\n</plist>`;\n const plistPath = join(LAUNCHD_DIR, LAUNCHD_PLIST);\n writeFileSync(plistPath, plist);\n console.log(`Enabled launchd service: ${plistPath}`);\n console.log(\"Run: launchctl load \" + plistPath);\n } else {\n console.error(`Auto-start not supported on ${os}. Use your OS service manager manually.`);\n }\n}\n\nexport function disableAutostart(): void {\n const os = platform();\n\n if (os === \"linux\") {\n const servicePath = join(SYSTEMD_DIR, SYSTEMD_SERVICE);\n try {\n execSync(`systemctl --user disable ${SYSTEMD_SERVICE}`);\n } catch {\n // may not be enabled\n }\n if (existsSync(servicePath)) unlinkSync(servicePath);\n try {\n execSync(\"systemctl --user daemon-reload\");\n } catch (err) {\n console.error(\"Failed to reload systemd:\", err instanceof Error ? err.message : err);\n }\n console.log(\"Disabled systemd auto-start\");\n } else if (os === \"darwin\") {\n const plistPath = join(LAUNCHD_DIR, LAUNCHD_PLIST);\n try {\n execSync(`launchctl unload ${plistPath}`);\n } catch {\n // may not be loaded\n }\n if (existsSync(plistPath)) unlinkSync(plistPath);\n console.log(\"Disabled launchd auto-start\");\n } else {\n console.error(`Auto-start not supported on ${os}.`);\n }\n}\n","import { Command } from \"commander\";\nimport { join, resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { spawn } from \"node:child_process\";\nimport { Readable, Writable } from \"node:stream\";\nimport { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } from \"@agentclientprotocol/sdk\";\nimport type { Client } from \"@agentclientprotocol/sdk\";\nimport { detectInstalledAgents } from \"../shared/detect-agents.js\";\nimport { parseConfig } from \"../shared/config.js\";\n\nconst CONFIG_DIR = join(homedir(), \".acp-discord\");\nconst CONFIG_PATH = join(CONFIG_DIR, \"config.toml\");\n\n// Only auto-allow writes to the config directory during setup\nconst SAFE_WRITE_PREFIX = CONFIG_DIR;\n\nconst INIT_SYSTEM_PROMPT = `You are a setup assistant for acp-discord, a Discord bot that connects Discord channels to ACP coding agents.\n\nYour job is to help the user configure ~/.acp-discord/config.toml interactively.\n\nYou need to collect:\n1. Discord Bot Token (guide them to https://discord.com/developers/applications if needed)\n2. Default working directory (the project path the agent will work on)\n3. Channel IDs to bind (explain how to get channel IDs: right-click channel → Copy Channel ID)\n4. Reply mode per channel: ask whether the bot should respond to ALL messages in the channel (auto_reply = true) or only when @mentioned (auto_reply = false, the default)\n\nOnce you have all info, write the config file using the write_text_file tool to ${CONFIG_PATH}.\n\nConfig format (TOML):\n\\`\\`\\`toml\n[discord]\ntoken = \"<token>\"\n\n[agents.default]\ncommand = \"npx\"\nargs = [\"<acp-package>\"]\ncwd = \"<working-directory>\"\nidle_timeout = 600\n\n[channels.<channel-id>]\nagent = \"default\"\nauto_reply = false # true = respond to all messages; false = only @mentions\n\\`\\`\\`\n\nBe friendly and concise. Ask one question at a time.`;\n\nexport function makeInitCommand(): Command {\n return new Command(\"init\")\n .description(\"Interactive setup wizard\")\n .action(async () => {\n console.log(\"Welcome to acp-discord setup!\\n\");\n\n // Detect agents\n console.log(\"Detecting ACP-compatible agents...\");\n const agents = detectInstalledAgents();\n\n if (agents.length === 0) {\n console.error(\"No ACP-compatible agents found.\");\n console.error(\"Install one of: claude-code, codex, opencode, pi\");\n process.exit(1);\n }\n\n for (const agent of agents) {\n console.log(` \\u2713 ${agent.name} (found)`);\n }\n\n const selected = agents[0];\n console.log(`\\nUsing: ${selected.name}\\n`);\n console.log(\"Starting setup agent...\\n\");\n\n // Spawn ACP agent for interactive setup\n const proc = spawn(selected.command, [selected.acpPackage], {\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n });\n\n const stream = ndJsonStream(\n Writable.toWeb(proc.stdin!) as WritableStream<Uint8Array>,\n Readable.toWeb(proc.stdout!) as ReadableStream<Uint8Array>,\n );\n\n // Simple readline for user input\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const askUser = (prompt: string): Promise<string> =>\n new Promise((resolve) => rl.question(prompt, resolve));\n\n const client: Client = {\n async requestPermission(params) {\n const title = params.toolCall.title ?? \"Unknown\";\n const kind = params.toolCall.kind ?? \"other\";\n\n // Auto-allow only safe file writes within the config directory,\n // validated against actual tool locations (not spoofable title) (#2)\n const isSafeWrite = kind === \"write_text_file\" || kind === \"fs\";\n if (isSafeWrite && params.toolCall.locations?.length) {\n const allPathsSafe = params.toolCall.locations.every(\n (loc: { path: string }) => {\n const resolved = resolve(loc.path);\n return resolved.startsWith(SAFE_WRITE_PREFIX + \"/\") || resolved === SAFE_WRITE_PREFIX;\n },\n );\n if (allPathsSafe) {\n const allowOption = params.options.find((o: { kind: string }) => o.kind === \"allow_once\");\n if (allowOption) {\n return { outcome: { outcome: \"selected\" as const, optionId: allowOption.optionId } };\n }\n }\n }\n\n // For all other operations, ask the user\n console.log(`\\n--- Permission Request ---`);\n console.log(`Tool: ${title}`);\n console.log(`Type: ${kind}`);\n console.log(`Options:`);\n for (let i = 0; i < params.options.length; i++) {\n const opt = params.options[i];\n console.log(` ${i + 1}. ${opt.name} (${opt.kind})`);\n }\n\n const answer = await askUser(`Choose option (1-${params.options.length}, or 'c' to cancel): `);\n if (answer.toLowerCase() === \"c\") {\n const rejectOption = params.options.find((o: { kind: string }) => o.kind === \"reject_once\");\n if (rejectOption) {\n return { outcome: { outcome: \"selected\" as const, optionId: rejectOption.optionId } };\n }\n return { outcome: { outcome: \"cancelled\" as const } };\n }\n\n const idx = parseInt(answer, 10) - 1;\n if (idx >= 0 && idx < params.options.length) {\n return { outcome: { outcome: \"selected\" as const, optionId: params.options[idx].optionId } };\n }\n\n // Invalid input — default to reject\n const rejectOption = params.options.find((o: { kind: string }) => o.kind === \"reject_once\");\n if (rejectOption) {\n return { outcome: { outcome: \"selected\" as const, optionId: rejectOption.optionId } };\n }\n return { outcome: { outcome: \"cancelled\" as const } };\n },\n async sessionUpdate(params) {\n const update = params.update;\n if (update.sessionUpdate === \"agent_message_chunk\" && update.content.type === \"text\") {\n process.stdout.write(update.content.text);\n }\n },\n };\n\n const connection = new ClientSideConnection((_agent) => client, stream);\n\n await connection.initialize({\n protocolVersion: PROTOCOL_VERSION,\n clientCapabilities: {\n fs: { readTextFile: true, writeTextFile: true },\n terminal: false,\n },\n clientInfo: { name: \"acp-discord-init\", title: \"ACP Discord Init\", version: \"0.1.0\" },\n });\n\n mkdirSync(CONFIG_DIR, { recursive: true });\n\n const { sessionId } = await connection.newSession({\n cwd: CONFIG_DIR,\n mcpServers: [],\n });\n\n // Initial prompt\n await connection.prompt({\n sessionId,\n prompt: [\n { type: \"text\", text: INIT_SYSTEM_PROMPT },\n { type: \"text\", text: `The ACP agent package is: ${selected.acpPackage}\\nPlease start the setup.` },\n ],\n });\n\n // Interactive loop\n while (true) {\n const input = await askUser(\"\\n> \");\n if (input.toLowerCase() === \"exit\" || input.toLowerCase() === \"quit\") break;\n\n const result = await connection.prompt({\n sessionId,\n prompt: [{ type: \"text\", text: input }],\n });\n\n if (result.stopReason === \"end_turn\" && existsSync(CONFIG_PATH)) {\n // Validate the written config is structurally valid (#9)\n try {\n const content = readFileSync(CONFIG_PATH, \"utf-8\");\n parseConfig(content); // throws if invalid\n console.log(\"\\n\\nSetup complete! Config written to\", CONFIG_PATH);\n console.log(\"Run `npx acp-discord daemon start` to begin.\");\n break;\n } catch {\n // config not valid yet, continue\n }\n }\n }\n\n rl.close();\n proc.kill();\n });\n}\n","import { execFileSync } from \"node:child_process\";\n\nexport interface AgentInfo {\n name: string;\n command: string;\n acpPackage: string;\n detectCommand: string;\n detectArgs: string[];\n}\n\nexport const KNOWN_AGENTS: AgentInfo[] = [\n {\n name: \"claude-code\",\n command: \"npx\",\n acpPackage: \"@zed-industries/claude-agent-acp\",\n detectCommand: \"claude\",\n detectArgs: [\"--version\"],\n },\n {\n name: \"codex\",\n command: \"npx\",\n acpPackage: \"@openai/codex-acp\",\n detectCommand: \"codex\",\n detectArgs: [\"--version\"],\n },\n {\n name: \"opencode\",\n command: \"npx\",\n acpPackage: \"@opencode/acp\",\n detectCommand: \"opencode\",\n detectArgs: [\"--version\"],\n },\n {\n name: \"pi\",\n command: \"npx\",\n acpPackage: \"@anthropic-ai/pi-acp\",\n detectCommand: \"pi\",\n detectArgs: [\"--version\"],\n },\n];\n\nexport function detectInstalledAgents(): AgentInfo[] {\n const found: AgentInfo[] = [];\n for (const agent of KNOWN_AGENTS) {\n try {\n execFileSync(agent.detectCommand, agent.detectArgs, {\n stdio: \"ignore\",\n timeout: 5000,\n });\n found.push(agent);\n } catch {\n // not installed\n }\n }\n return found;\n}\n","import { createCli } from \"./cli/index.js\";\n\ncreateCli().parse();\n"],"mappings":"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","names":["Command","join","homedir","join","homedir","resolve","Command","join","homedir","existsSync","mkdirSync","spawn","CONFIG_DIR","join","homedir","Command","spawn","resolve","rejectOption","mkdirSync","existsSync","Command"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acp-discord",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Discord bot that wraps ACP protocol for coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,34 +20,32 @@
20
20
  "claude",
21
21
  "coding-agent"
22
22
  ],
23
- "scripts": {
24
- "dev": "tsx src/index.ts",
25
- "build": "tsup",
26
- "start": "node dist/index.js",
27
- "test": "vitest run",
28
- "test:watch": "vitest"
29
- },
30
23
  "engines": {
31
24
  "node": ">=18"
32
25
  },
26
+ "bugs": {
27
+ "url": "https://github.com/broven/acp-discord/issues"
28
+ },
29
+ "homepage": "https://github.com/broven/acp-discord#readme",
33
30
  "license": "MIT",
34
- "packageManager": "pnpm@10.14.0",
35
31
  "dependencies": {
36
32
  "@agentclientprotocol/sdk": "^0.15.0",
37
33
  "commander": "^14.0.3",
38
34
  "discord.js": "^14.25.1",
39
35
  "smol-toml": "^1.6.0"
40
36
  },
41
- "pnpm": {
42
- "onlyBuiltDependencies": [
43
- "esbuild"
44
- ]
45
- },
46
37
  "devDependencies": {
47
38
  "@types/node": "^25.3.5",
48
39
  "tsup": "^8.5.1",
49
40
  "tsx": "^4.21.0",
50
41
  "typescript": "^5.9.3",
51
42
  "vitest": "^4.0.18"
43
+ },
44
+ "scripts": {
45
+ "dev": "tsx src/index.ts",
46
+ "build": "tsup",
47
+ "start": "node dist/index.js",
48
+ "test": "vitest run",
49
+ "test:watch": "vitest"
52
50
  }
53
- }
51
+ }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/pid.ts","../src/shared/config.ts"],"sourcesContent":["import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport function writePid(pidPath: string, pid: number): void {\n mkdirSync(dirname(pidPath), { recursive: true });\n writeFileSync(pidPath, String(pid), \"utf-8\");\n}\n\nexport function readPid(pidPath: string): number | null {\n if (!existsSync(pidPath)) return null;\n const content = readFileSync(pidPath, \"utf-8\").trim();\n const pid = parseInt(content, 10);\n return isNaN(pid) ? null : pid;\n}\n\nexport function removePid(pidPath: string): void {\n if (existsSync(pidPath)) unlinkSync(pidPath);\n}\n\nexport function isDaemonRunning(pidPath: string): boolean {\n const pid = readPid(pidPath);\n if (pid === null) return false;\n try {\n process.kill(pid, 0); // signal 0 = check if process exists\n return true;\n } catch {\n return false;\n }\n}\n","import { parse } from \"smol-toml\";\nimport { readFileSync } from \"node:fs\";\nimport type { AppConfig, ResolvedChannelConfig } from \"./types.js\";\n\nexport function parseConfig(toml: string): AppConfig {\n const raw = parse(toml) as Record<string, unknown>;\n\n const discord = raw.discord as Record<string, unknown> | undefined;\n if (!discord?.token || typeof discord.token !== \"string\") {\n throw new Error(\"Missing required: discord.token\");\n }\n if (discord.token.trim().length === 0) {\n throw new Error(\"discord.token must not be empty\");\n }\n\n const agents = raw.agents as Record<string, Record<string, unknown>> | undefined;\n if (!agents || Object.keys(agents).length === 0) {\n throw new Error(\"Missing required: at least one agent in [agents.*]\");\n }\n\n const parsedAgents: AppConfig[\"agents\"] = {};\n for (const [name, agent] of Object.entries(agents)) {\n // Validate command is a non-empty string\n if (!agent.command || typeof agent.command !== \"string\") {\n throw new Error(`agents.${name}.command must be a non-empty string`);\n }\n\n // Validate args is an array of strings\n if (agent.args !== undefined) {\n if (!Array.isArray(agent.args) || !agent.args.every((a: unknown) => typeof a === \"string\")) {\n throw new Error(`agents.${name}.args must be an array of strings`);\n }\n }\n\n // Validate idle_timeout is a positive number\n if (agent.idle_timeout !== undefined) {\n if (typeof agent.idle_timeout !== \"number\" || agent.idle_timeout <= 0) {\n throw new Error(`agents.${name}.idle_timeout must be a positive number`);\n }\n }\n\n // Validate cwd is a string if provided\n if (agent.cwd !== undefined && typeof agent.cwd !== \"string\") {\n throw new Error(`agents.${name}.cwd must be a string`);\n }\n\n parsedAgents[name] = {\n command: agent.command,\n args: (agent.args as string[]) ?? [],\n cwd: typeof agent.cwd === \"string\" ? agent.cwd : process.cwd(),\n idle_timeout: typeof agent.idle_timeout === \"number\" ? agent.idle_timeout : 600,\n };\n }\n\n const channels = (raw.channels ?? {}) as Record<string, Record<string, unknown>>;\n const parsedChannels: AppConfig[\"channels\"] = {};\n for (const [id, ch] of Object.entries(channels)) {\n const agentRef = ch.agent ?? \"default\";\n if (typeof agentRef !== \"string\") {\n throw new Error(`channels.${id}.agent must be a string`);\n }\n if (!parsedAgents[agentRef]) {\n throw new Error(`channels.${id}.agent references unknown agent \"${agentRef}\"`);\n }\n if (ch.cwd !== undefined && typeof ch.cwd !== \"string\") {\n throw new Error(`channels.${id}.cwd must be a string`);\n }\n\n parsedChannels[id] = {\n agent: agentRef,\n cwd: ch.cwd ? String(ch.cwd) : undefined,\n };\n }\n\n return {\n discord: { token: String(discord.token) },\n agents: parsedAgents,\n channels: parsedChannels,\n };\n}\n\nexport function loadConfig(configPath: string): AppConfig {\n const content = readFileSync(configPath, \"utf-8\");\n return parseConfig(content);\n}\n\nexport function resolveChannelConfig(\n config: AppConfig,\n channelId: string,\n): ResolvedChannelConfig | null {\n const channelConf = config.channels[channelId];\n if (!channelConf) return null;\n\n const agentConf = config.agents[channelConf.agent];\n if (!agentConf) return null;\n\n return {\n channelId,\n agent: {\n ...agentConf,\n cwd: channelConf.cwd ?? agentConf.cwd,\n },\n };\n}\n"],"mappings":"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","names":["readFileSync"]}