acp-discord 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/daemon.js CHANGED
@@ -171,9 +171,11 @@ var SessionManager = class {
171
171
  handlers;
172
172
  sessionsPath;
173
173
  pendingResumes = /* @__PURE__ */ new Map();
174
- constructor(handlers, sessionsPath) {
174
+ maxConcurrentSessions;
175
+ constructor(handlers, sessionsPath, maxConcurrentSessions = 1) {
175
176
  this.handlers = handlers;
176
177
  this.sessionsPath = sessionsPath;
178
+ this.maxConcurrentSessions = maxConcurrentSessions;
177
179
  this.loadSessionMap();
178
180
  }
179
181
  loadSessionMap() {
@@ -232,13 +234,20 @@ var SessionManager = class {
232
234
  });
233
235
  this.handlers.onPromptComplete(session.channelId, result.stopReason);
234
236
  return result.stopReason;
237
+ } catch (err) {
238
+ console.error(`Prompt error for channel ${session.channelId}, tearing down session:`, err);
239
+ this.handlers.onPromptComplete(session.channelId, "error");
240
+ this.teardown(session.channelId);
241
+ throw err;
235
242
  } finally {
236
243
  session.prompting = false;
237
- const next = session.queue.shift();
238
- if (next) {
239
- this.executePrompt(session, next.text, next.requestorId, agentConfig).catch((err) => {
240
- console.error(`Queued prompt failed for channel ${session.channelId}:`, err);
241
- });
244
+ if (this.sessions.has(session.channelId)) {
245
+ const next = session.queue.shift();
246
+ if (next) {
247
+ this.executePrompt(session, next.text, next.requestorId, agentConfig).catch((err) => {
248
+ console.error(`Queued prompt failed for channel ${session.channelId}:`, err);
249
+ });
250
+ }
242
251
  }
243
252
  }
244
253
  }
@@ -265,13 +274,40 @@ var SessionManager = class {
265
274
  } else if (pending) {
266
275
  this.pendingResumes.delete(channelId);
267
276
  }
277
+ this.evictIfNeeded();
268
278
  console.log(`[MCP] getOrCreate: creating new session for channel=${channelId} with ${mcpServers?.length ?? 0} MCP server(s)`);
269
279
  return this.createSession(channelId, agentName, agentConfig, requestorId, mcpServers);
270
280
  }
281
+ evictIfNeeded() {
282
+ while (this.sessions.size >= this.maxConcurrentSessions) {
283
+ let oldest = null;
284
+ for (const session of this.sessions.values()) {
285
+ if (session.prompting) continue;
286
+ if (!oldest || session.lastActivity < oldest.lastActivity) {
287
+ oldest = session;
288
+ }
289
+ }
290
+ if (!oldest) {
291
+ for (const session of this.sessions.values()) {
292
+ if (!oldest || session.lastActivity < oldest.lastActivity) {
293
+ oldest = session;
294
+ }
295
+ }
296
+ }
297
+ if (oldest) {
298
+ console.log(`Evicting session for channel ${oldest.channelId} (lastActivity=${new Date(oldest.lastActivity).toISOString()}) to make room`);
299
+ this.teardown(oldest.channelId);
300
+ } else {
301
+ break;
302
+ }
303
+ }
304
+ }
271
305
  async createSession(channelId, agentName, config, requestorId, mcpServers) {
272
306
  const proc = spawn(config.command, config.args, {
273
307
  stdio: ["pipe", "pipe", "inherit"],
274
- cwd: config.cwd
308
+ cwd: config.cwd,
309
+ detached: true
310
+ // Create new process group so we can kill the entire tree
275
311
  });
276
312
  proc.on("error", (err) => {
277
313
  console.error(`Agent process error for channel ${channelId}:`, err);
@@ -327,7 +363,7 @@ var SessionManager = class {
327
363
  sessionId = result.sessionId;
328
364
  console.log(`[MCP] createSession: newSession succeeded, sessionId=${sessionId}`);
329
365
  } catch (err) {
330
- proc.kill();
366
+ this.killProcessTree(proc);
331
367
  throw err;
332
368
  }
333
369
  const managed = {
@@ -348,7 +384,9 @@ var SessionManager = class {
348
384
  async resumeSession(channelId, agentName, config, requestorId, previousSessionId, mcpServers) {
349
385
  const proc = spawn(config.command, config.args, {
350
386
  stdio: ["pipe", "pipe", "inherit"],
351
- cwd: config.cwd
387
+ cwd: config.cwd,
388
+ detached: true
389
+ // Create new process group so we can kill the entire tree
352
390
  });
353
391
  proc.on("error", (err) => {
354
392
  console.error(`Agent process error for channel ${channelId}:`, err);
@@ -393,7 +431,7 @@ var SessionManager = class {
393
431
  });
394
432
  const supportsResume = !!initResult.agentCapabilities?.sessionCapabilities?.resume;
395
433
  if (!supportsResume) {
396
- proc.kill();
434
+ this.killProcessTree(proc);
397
435
  throw new Error("Agent does not support session resume");
398
436
  }
399
437
  await connection.unstable_resumeSession({
@@ -404,7 +442,7 @@ var SessionManager = class {
404
442
  sessionId = previousSessionId;
405
443
  console.log(`Resumed session ${sessionId} for channel ${channelId}`);
406
444
  } catch (err) {
407
- proc.kill();
445
+ this.killProcessTree(proc);
408
446
  throw err;
409
447
  }
410
448
  const managed = {
@@ -434,9 +472,33 @@ var SessionManager = class {
434
472
  const session = this.sessions.get(channelId);
435
473
  if (!session) return;
436
474
  clearTimeout(session.idleTimer);
437
- session.process.kill();
475
+ this.killProcessTree(session.process);
438
476
  this.sessions.delete(channelId);
439
477
  }
478
+ killProcessTree(proc) {
479
+ if (!proc.pid) {
480
+ proc.kill();
481
+ return;
482
+ }
483
+ try {
484
+ process.kill(-proc.pid, "SIGTERM");
485
+ } catch {
486
+ try {
487
+ proc.kill("SIGTERM");
488
+ } catch {
489
+ }
490
+ }
491
+ setTimeout(() => {
492
+ try {
493
+ process.kill(-proc.pid, "SIGKILL");
494
+ } catch {
495
+ }
496
+ try {
497
+ proc.kill("SIGKILL");
498
+ } catch {
499
+ }
500
+ }, 5e3).unref();
501
+ }
440
502
  teardownAll() {
441
503
  this.saveSessionMap();
442
504
  for (const channelId of this.sessions.keys()) {
@@ -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","../src/daemon/ipc-server.ts","../src/daemon/task-scheduler.ts","../src/daemon/task-runner.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\");\nconst SESSIONS_PATH = join(CONFIG_DIR, \"sessions.json\");\n\nexport async function runDaemon(): Promise<void> {\n // Global error handlers — graceful shutdown on fatal errors, continue on rejections\n process.on(\"uncaughtException\", (err) => {\n console.error(\"Uncaught exception:\", err);\n // Allow event loop to flush logs, then exit for restart by service manager\n setTimeout(() => process.exit(1), 1000);\n });\n\n process.on(\"unhandledRejection\", (reason) => {\n console.error(\"Unhandled rejection:\", reason);\n // Unhandled rejections are less severe — log but continue\n });\n\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, SESSIONS_PATH, CONFIG_PATH);\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 EmbedBuilder,\n type Message,\n type TextChannel,\n} from \"discord.js\";\nimport { resolve as resolvePath, dirname } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport type { AppConfig } from \"../shared/types.js\";\nimport { saveConfig, resolveChannelConfig } from \"../shared/config.js\";\nimport { ChannelRouter } from \"./channel-router.js\";\nimport { SessionManager, type McpServerConfig } from \"./session-manager.js\";\nimport { sendPermissionRequest } from \"./permission-ui.js\";\nimport { splitMessage, formatToolSummary, formatDiff, type ToolStatus } from \"./message-bridge.js\";\nimport type { AcpEventHandlers, DiffContent } from \"./acp-client.js\";\nimport { IpcServer, DEFAULT_IPC_SOCKET_PATH } from \"./ipc-server.js\";\nimport { TaskScheduler } from \"./task-scheduler.js\";\nimport { runTask } from \"./task-runner.js\";\n\nexport async function startDiscordBot(config: AppConfig, sessionsPath: string, configPath: string): Promise<void> {\n let currentConfig = config;\n const router = new ChannelRouter(currentConfig);\n\n // Per-channel state for display\n const toolStates = new Map<string, Map<string, { title: string; status: ToolStatus; rawInput?: Record<string, unknown> }>>();\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 // channelId -> toolCallId -> DiffContent[]\n const pendingDiffs = new Map<string, Map<string, DiffContent[]>>();\n // channelId -> Set of toolCallIds whose diffs were already shown at permission-request time\n const permissionDiffShown = new Map<string, Set<string>>();\n\n // Typing indicator state: channelId -> interval timer\n const typingIntervals = new Map<string, NodeJS.Timeout>();\n\n function startTyping(channelId: string) {\n if (typingIntervals.has(channelId)) return;\n // Set a placeholder immediately to prevent concurrent calls from creating duplicate intervals\n const placeholder = setTimeout(() => {}, 0);\n typingIntervals.set(channelId, placeholder);\n clearTimeout(placeholder);\n\n fetchChannel(channelId).then((channel) => {\n if (!channel) { typingIntervals.delete(channelId); return; }\n // Re-check: stopTyping may have been called while we awaited\n if (!typingIntervals.has(channelId)) return;\n channel.sendTyping().catch(() => {});\n const interval = setInterval(() => {\n channel.sendTyping().catch(() => {});\n }, 8000);\n typingIntervals.set(channelId, interval);\n }).catch(() => {\n typingIntervals.delete(channelId);\n });\n }\n\n function stopTyping(channelId: string) {\n const interval = typingIntervals.get(channelId);\n if (interval) {\n clearInterval(interval);\n typingIntervals.delete(channelId);\n }\n }\n\n let discordClient: Client;\n\n // --- Confirmation UI for MCP tool actions ---\n\n // Pending confirmation requests from MCP servers (requestId -> { resolver, allowedUserId })\n const pendingConfirmations = new Map<string, { resolve: (approved: boolean) => void; allowedUserId: string | null }>();\n\n async function handleConfirmAction(sourceChannelId: string, description: string, details: string): Promise<boolean> {\n const channel = await fetchChannel(sourceChannelId);\n if (!channel) return false;\n\n // Only the user who triggered the current prompt can approve\n const allowedUserId = sessionManager.getActiveRequestorId(sourceChannelId);\n\n const requestId = `mcp_confirm_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n\n const embed = new EmbedBuilder()\n .setColor(0xffa500)\n .setTitle(`Channel Action: ${description}`)\n .setDescription(details || \"No additional details\")\n .setTimestamp();\n\n const row = new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder()\n .setCustomId(`mcp_approve_${requestId}`)\n .setLabel(\"\\u2705 Approve\")\n .setStyle(ButtonStyle.Success),\n new ButtonBuilder()\n .setCustomId(`mcp_reject_${requestId}`)\n .setLabel(\"\\u274C Reject\")\n .setStyle(ButtonStyle.Danger),\n );\n\n const msg = await channel.send({ embeds: [embed], components: [row] });\n\n return new Promise<boolean>((resolve) => {\n const timeout = setTimeout(() => {\n pendingConfirmations.delete(requestId);\n msg.delete().catch(() => msg.edit({ components: [] }).catch(() => {}));\n resolve(false);\n }, 5 * 60 * 1000); // 5 minute timeout\n\n pendingConfirmations.set(requestId, {\n resolve: (approved: boolean) => {\n clearTimeout(timeout);\n pendingConfirmations.delete(requestId);\n msg.delete().catch(() => msg.edit({ components: [] }).catch(() => {}));\n resolve(approved);\n },\n allowedUserId,\n });\n });\n }\n\n // --- IPC Server ---\n\n // --- Task Scheduler ---\n\n const taskScheduler = new TaskScheduler(async (task) => {\n const resolved = router.resolve(task.channel_id);\n if (!resolved) {\n console.error(`TaskScheduler: no resolved config for channel ${task.channel_id}`);\n taskScheduler.logRun(task.id, {\n startedAt: new Date(),\n completedAt: new Date(),\n durationMs: 0,\n status: \"error\",\n output: \"\",\n error: `No resolved config for channel ${task.channel_id}`,\n });\n return;\n }\n\n const channel = await fetchChannel(task.channel_id);\n const guildId = channel?.guild?.id ?? null;\n const mcpServers = guildId ? buildMcpServers(task.channel_id, task.agent_name, guildId) : [];\n\n const startedAt = new Date();\n const result = await runTask(resolved.agent, task.prompt, mcpServers);\n const completedAt = new Date();\n const durationMs = completedAt.getTime() - startedAt.getTime();\n\n taskScheduler.logRun(task.id, {\n startedAt,\n completedAt,\n durationMs,\n status: result.error ? \"error\" : \"success\",\n output: result.output,\n error: result.error,\n });\n\n const shouldNotify =\n task.notify === \"always\" ||\n (task.notify === \"on_error\" && result.error);\n\n if (shouldNotify && channel) {\n const text = result.error ?? result.output;\n const chunks = splitMessage(text || \"(no output)\");\n for (const chunk of chunks) {\n await channel.send(chunk);\n }\n }\n });\n\n const ipcServer = new IpcServer(\n {\n registerChannel(channelId, agentName, autoReply) {\n router.registerDynamic(channelId, agentName, autoReply);\n console.log(`IPC: registered dynamic channel ${channelId} -> agent ${agentName}`);\n },\n unregisterChannel(channelId) {\n router.unregisterDynamic(channelId);\n console.log(`IPC: unregistered dynamic channel ${channelId}`);\n },\n confirmAction: handleConfirmAction,\n\n async bindChannel(channelId, agentName, opts, guildId) {\n // Validate guild ownership if guildId provided\n if (guildId) {\n try {\n const channel = await discordClient.channels.fetch(channelId);\n const channelGuildId = channel && \"guildId\" in channel ? (channel as { guildId: string }).guildId : null;\n if (channelGuildId !== guildId) {\n return { success: false, error: `Channel ${channelId} does not belong to guild ${guildId}` };\n }\n } catch {\n return { success: false, error: `Cannot verify channel ${channelId}` };\n }\n }\n\n // Validate agent exists\n if (!currentConfig.agents[agentName]) {\n return { success: false, error: `Unknown agent \"${agentName}\"` };\n }\n\n // Update in-memory config\n currentConfig.channels[channelId] = {\n agent: agentName,\n cwd: opts.cwd,\n auto_reply: opts.autoReply ?? true,\n discord_tools: opts.discordTools,\n };\n\n // Register in router\n router.registerDynamic(channelId, agentName, opts.autoReply ?? true);\n\n // Persist to config.toml\n try {\n saveConfig(configPath, currentConfig);\n } catch (err) {\n // Rollback in-memory state on write failure\n delete currentConfig.channels[channelId];\n router.unregisterDynamic(channelId);\n const message = err instanceof Error ? err.message : String(err);\n return { success: false, error: `Failed to save config: ${message}` };\n }\n\n console.log(`IPC: bound channel ${channelId} -> agent ${agentName}`);\n return { success: true };\n },\n\n async unbindChannel(channelId, guildId) {\n // Validate guild ownership if guildId provided\n if (guildId) {\n try {\n const channel = await discordClient.channels.fetch(channelId);\n const channelGuildId = channel && \"guildId\" in channel ? (channel as { guildId: string }).guildId : null;\n if (channelGuildId !== guildId) {\n return { success: false, error: `Channel ${channelId} does not belong to guild ${guildId}` };\n }\n } catch {\n // Channel may have been deleted — allow unbinding if it exists in config\n }\n }\n\n if (!currentConfig.channels[channelId]) {\n return { success: false, error: `Channel ${channelId} is not bound` };\n }\n\n // Save for rollback\n const previous = currentConfig.channels[channelId];\n\n // Update in-memory\n delete currentConfig.channels[channelId];\n router.unregisterDynamic(channelId);\n\n // Persist\n try {\n saveConfig(configPath, currentConfig);\n } catch (err) {\n // Rollback\n currentConfig.channels[channelId] = previous;\n router.registerDynamic(channelId, previous.agent, previous.auto_reply ?? false);\n const message = err instanceof Error ? err.message : String(err);\n return { success: false, error: `Failed to save config: ${message}` };\n }\n\n console.log(`IPC: unbound channel ${channelId}`);\n return { success: true };\n },\n\n async listBindings(guildId) {\n const entries = Object.entries(currentConfig.channels);\n const bindings = [];\n for (const [channelId, ch] of entries) {\n // Filter by guild if specified\n if (guildId) {\n try {\n const channel = discordClient.channels.cache.get(channelId) ?? await discordClient.channels.fetch(channelId);\n const channelGuildId = channel && \"guildId\" in channel ? (channel as { guildId: string }).guildId : null;\n if (channelGuildId !== guildId) continue;\n } catch {\n continue;\n }\n }\n const resolved = resolveChannelConfig(currentConfig, channelId);\n bindings.push({\n channelId,\n agent: ch.agent,\n cwd: ch.cwd ?? currentConfig.agents[ch.agent]?.cwd,\n autoReply: ch.auto_reply ?? false,\n discordTools: resolved?.agent.discord_tools ?? false,\n });\n }\n return { success: true, bindings };\n },\n createTask(params) {\n try {\n const task = taskScheduler.createTask(params as Parameters<typeof taskScheduler.createTask>[0]);\n return { task };\n } catch (err) {\n return { error: err instanceof Error ? err.message : String(err) };\n }\n },\n listTasks(channelId) {\n return { tasks: taskScheduler.listTasks(channelId) };\n },\n updateTask(taskId, updates, channelId) {\n const task = taskScheduler.updateTask(taskId, updates, channelId);\n if (!task) return { error: `Task ${taskId} not found` };\n return { task };\n },\n deleteTask(taskId, channelId) {\n const deleted = taskScheduler.deleteTask(taskId, channelId);\n if (!deleted) return { error: `Task ${taskId} not found` };\n return { deleted: true };\n },\n getTaskLogs(taskId, channelId) {\n return { logs: taskScheduler.getTaskLogs(taskId, channelId) };\n },\n },\n DEFAULT_IPC_SOCKET_PATH,\n );\n\n const handlers: AcpEventHandlers = {\n onToolCall(channelId, toolCallId, title, _kind, status, diffs, rawInput) {\n if (!toolStates.has(channelId)) toolStates.set(channelId, new Map());\n toolStates.get(channelId)!.set(toolCallId, { title, status: status as ToolStatus, rawInput });\n accumulateDiffs(channelId, toolCallId, diffs);\n updateToolSummaryMessage(channelId);\n if (status === \"completed\") sendDiffsForTool(channelId, toolCallId);\n },\n\n onToolCallUpdate(channelId, toolCallId, status, diffs, rawInput) {\n const tools = toolStates.get(channelId);\n const tool = tools?.get(toolCallId);\n if (tool) {\n tool.status = status as ToolStatus;\n if (rawInput && !tool.rawInput) tool.rawInput = rawInput;\n accumulateDiffs(channelId, toolCallId, diffs);\n updateToolSummaryMessage(channelId);\n if (status === \"completed\") sendDiffsForTool(channelId, toolCallId);\n }\n },\n\n onAgentMessageChunk(channelId, text) {\n startTyping(channelId);\n const current = replyBuffers.get(channelId) ?? \"\";\n replyBuffers.set(channelId, current + text);\n scheduleFlushReply(channelId);\n },\n\n async onPermissionRequest(channelId, requestorId, toolCall, options, diffs) {\n const channel = await fetchChannel(channelId);\n if (!channel) return { outcome: \"cancelled\" as const };\n const result = await sendPermissionRequest(channel, toolCall.title, toolCall.kind, options, requestorId, diffs);\n if (result.diffsSent) {\n if (!permissionDiffShown.has(channelId)) permissionDiffShown.set(channelId, new Set());\n permissionDiffShown.get(channelId)!.add(toolCall.toolCallId);\n }\n return result;\n },\n\n onPromptComplete(channelId, _stopReason) {\n stopTyping(channelId);\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 pendingDiffs.delete(channelId);\n permissionDiffShown.delete(channelId);\n },\n };\n\n const sessionManager = new SessionManager(handlers, sessionsPath);\n\n // --- MCP server config builder ---\n\n function buildMcpServers(channelId: string, agentName: string, guildId: string): McpServerConfig[] {\n const resolved = router.resolve(channelId);\n const discordToolsEnabled = resolved?.agent.discord_tools ?? false;\n const scheduledTasksEnabled = resolved?.agent.scheduled_tasks ?? false;\n console.log(`[MCP] buildMcpServers: channel=${channelId} agent=${agentName} discord_tools=${discordToolsEnabled} scheduled_tasks=${scheduledTasksEnabled}`);\n\n const mcpConfig: McpServerConfig[] = [];\n\n // Resolve the built MCP server script path relative to this package\n // import.meta.dirname is available in Node 21.2+; fall back to fileURLToPath for Node 18\n const currentDir = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url));\n\n if (discordToolsEnabled) {\n const mcpScriptPath = resolvePath(currentDir, \"mcp-discord-channels.js\");\n const scriptExists = existsSync(mcpScriptPath);\n\n console.log(`[MCP] buildMcpServers: mcpScriptPath=${mcpScriptPath} exists=${scriptExists}`);\n if (!scriptExists) {\n console.warn(`[MCP] WARNING: MCP script not found at ${mcpScriptPath} — Discord tools will not work`);\n }\n\n mcpConfig.push({\n name: \"discord-channels\",\n command: \"node\",\n args: [mcpScriptPath],\n env: [\n { name: \"DISCORD_TOKEN\", value: config.discord.token },\n { name: \"GUILD_ID\", value: guildId },\n { name: \"IPC_SOCKET_PATH\", value: DEFAULT_IPC_SOCKET_PATH },\n { name: \"AGENT_NAME\", value: agentName },\n { name: \"SOURCE_CHANNEL_ID\", value: channelId },\n ],\n });\n }\n\n if (scheduledTasksEnabled) {\n const tasksMcpPath = resolvePath(currentDir, \"mcp-scheduled-tasks.js\");\n const tasksScriptExists = existsSync(tasksMcpPath);\n\n console.log(`[MCP] buildMcpServers: tasksMcpPath=${tasksMcpPath} exists=${tasksScriptExists}`);\n if (!tasksScriptExists) {\n console.warn(`[MCP] WARNING: MCP script not found at ${tasksMcpPath} — Scheduled tasks tools will not work`);\n } else {\n mcpConfig.push({\n name: \"scheduled-tasks\",\n command: \"node\",\n args: [tasksMcpPath],\n env: [\n { name: \"IPC_SOCKET_PATH\", value: DEFAULT_IPC_SOCKET_PATH },\n { name: \"AGENT_NAME\", value: agentName },\n { name: \"SOURCE_CHANNEL_ID\", value: channelId },\n ],\n });\n }\n }\n\n console.log(`[MCP] buildMcpServers: returning ${mcpConfig.length} MCP server(s):`, JSON.stringify(mcpConfig.map(s => ({ name: s.name, command: s.command, args: s.args }))));\n return mcpConfig;\n }\n\n // --- Display helpers ---\n\n function accumulateDiffs(channelId: string, toolCallId: string, diffs: DiffContent[]) {\n if (diffs.length === 0) return;\n if (!pendingDiffs.has(channelId)) pendingDiffs.set(channelId, new Map());\n const channelDiffs = pendingDiffs.get(channelId)!;\n const existing = channelDiffs.get(toolCallId) ?? [];\n channelDiffs.set(toolCallId, existing.concat(diffs));\n }\n\n async function sendDiffsForTool(channelId: string, toolCallId: string) {\n // Skip if diffs were already shown at permission-request time\n const shownSet = permissionDiffShown.get(channelId);\n if (shownSet?.has(toolCallId)) {\n shownSet.delete(toolCallId);\n pendingDiffs.get(channelId)?.delete(toolCallId);\n return;\n }\n\n const channelDiffs = pendingDiffs.get(channelId);\n const diffs = channelDiffs?.get(toolCallId);\n if (!diffs || diffs.length === 0) return;\n\n const channel = await fetchChannel(channelId);\n if (!channel) return;\n\n const messages = formatDiff(diffs);\n for (const msg of messages) {\n await channel.send({ content: msg, allowedMentions: { parse: [] as const } });\n }\n\n channelDiffs!.delete(toolCallId);\n }\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 noMentions = { parse: [] as const };\n const existing = toolSummaryMessages.get(channelId);\n if (existing) {\n await existing.edit({ content, components: [stopButton], allowedMentions: noMentions }).catch(() => {});\n } else {\n const msg = await channel.send({ content, components: [stopButton], allowedMentions: noMentions });\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: [], allowedMentions: { parse: [] as const } }).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 // --- Helper: resolve guild ID from a channel ---\n\n function getGuildId(message: Message): string | null {\n return message.guildId ?? null;\n }\n\n // --- Helper: prompt with MCP servers ---\n\n async function promptWithMcp(channelId: string, text: string, agentName: string, guildId: string | null, agentConfig: typeof config.agents[string], requestorId: string): Promise<void> {\n const mcpServers = guildId ? buildMcpServers(channelId, agentName, guildId) : undefined;\n await sessionManager.prompt(channelId, text, agentName, agentConfig, requestorId, mcpServers);\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(\"error\", (err) => {\n console.error(\"Discord client error:\", err);\n });\n\n discordClient.on(\"warn\", (msg) => {\n console.warn(\"Discord client warning:\", msg);\n });\n\n discordClient.on(\"shardDisconnect\", (event, shardId) => {\n console.warn(`Shard ${shardId} disconnected (code: ${event.code})`);\n });\n\n discordClient.on(\"shardReconnecting\", (shardId) => {\n console.log(`Shard ${shardId} reconnecting...`);\n });\n\n discordClient.on(Events.ClientReady, async (c) => {\n console.log(`Discord bot ready: ${c.user.tag}`);\n\n // Register slash commands\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 clearCommand = new SlashCommandBuilder()\n .setName(\"clear\")\n .setDescription(\"Clear the agent session and start fresh\");\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(), clearCommand.toJSON()],\n });\n console.log(\"Registered /ask and /clear commands\");\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 promptWithMcp(channelId, text, resolved.agentName, getGuildId(message), resolved.agent, message.author.id);\n } catch (err) {\n stopTyping(channelId);\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 and MCP confirmation buttons\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 // Handle MCP confirmation buttons\n if (interaction.customId.startsWith(\"mcp_approve_\") || interaction.customId.startsWith(\"mcp_reject_\")) {\n const approved = interaction.customId.startsWith(\"mcp_approve_\");\n const requestId = interaction.customId.replace(/^mcp_(approve|reject)_/, \"\");\n const pending = pendingConfirmations.get(requestId);\n if (pending) {\n // Only the user who triggered the prompt can approve/reject\n if (pending.allowedUserId && interaction.user.id !== pending.allowedUserId) {\n await interaction.reply({ content: \"Only the user who started this prompt can approve or reject.\", ephemeral: true });\n return;\n }\n await interaction.deferUpdate();\n pending.resolve(approved);\n } else {\n await interaction.reply({ content: \"This confirmation has expired.\", ephemeral: true });\n }\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 const guildId = interaction.guildId ?? null;\n await promptWithMcp(channelId, text, resolved.agentName, guildId, resolved.agent, interaction.user.id);\n } catch (err) {\n stopTyping(channelId);\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 // Handle /clear command\n discordClient.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isChatInputCommand()) return;\n if (interaction.commandName !== \"clear\") return;\n\n const channelId = interaction.channelId;\n sessionManager.teardown(channelId);\n\n // Clean up display state\n stopTyping(channelId);\n toolStates.delete(channelId);\n toolSummaryMessages.delete(channelId);\n replyBuffers.delete(channelId);\n replyMessages.delete(channelId);\n pendingDiffs.delete(channelId);\n permissionDiffShown.delete(channelId);\n const timer = flushTimers.get(channelId);\n if (timer) clearTimeout(timer);\n flushTimers.delete(channelId);\n\n await interaction.reply(\"Session cleared. Next message will start a fresh agent.\");\n });\n\n // --- Start IPC server and task scheduler ---\n await ipcServer.start();\n taskScheduler.start();\n\n // Graceful shutdown\n process.on(\"SIGTERM\", () => {\n for (const channelId of typingIntervals.keys()) stopTyping(channelId);\n taskScheduler.stop();\n ipcServer.stop();\n sessionManager.teardownAll();\n discordClient.destroy();\n });\n\n process.on(\"SIGINT\", () => {\n for (const channelId of typingIntervals.keys()) stopTyping(channelId);\n taskScheduler.stop();\n ipcServer.stop();\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 ipcServer.stop();\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 private dynamicChannels = new Map<string, { agentName: string; autoReply: boolean }>();\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 registerDynamic(channelId: string, agentName: string, autoReply: boolean): void {\n if (!this.config.agents[agentName]) {\n console.error(`Cannot register dynamic channel: unknown agent \"${agentName}\"`);\n return;\n }\n this.dynamicChannels.set(channelId, { agentName, autoReply });\n // Merge into existing channel config to preserve cwd/discord_tools overrides\n const existing = this.config.channels[channelId];\n this.config.channels[channelId] = {\n ...existing,\n agent: agentName,\n auto_reply: autoReply,\n };\n }\n\n unregisterDynamic(channelId: string): void {\n this.dynamicChannels.delete(channelId);\n delete this.config.channels[channelId];\n }\n\n updateConfig(newConfig: AppConfig): void {\n this.config = newConfig;\n // Re-inject dynamic channels that aren't already in the new config\n for (const [channelId, { agentName, autoReply }] of this.dynamicChannels) {\n if (!this.config.channels[channelId] && this.config.agents[agentName]) {\n this.config.channels[channelId] = {\n agent: agentName,\n auto_reply: autoReply,\n };\n }\n }\n }\n\n getConfig(): AppConfig {\n return this.config;\n }\n}\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport { readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\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\nexport interface McpServerConfig {\n name: string;\n command: string;\n args: string[];\n env: Array<{ name: string; value: string }>;\n}\n\ninterface ManagedSession {\n channelId: string;\n agentName: 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\ninterface PersistedSession {\n sessionId: string;\n agentName: string;\n}\n\nexport class SessionManager {\n private sessions = new Map<string, ManagedSession>();\n private handlers: AcpEventHandlers;\n private sessionsPath: string;\n private pendingResumes = new Map<string, PersistedSession>();\n\n constructor(handlers: AcpEventHandlers, sessionsPath: string) {\n this.handlers = handlers;\n this.sessionsPath = sessionsPath;\n this.loadSessionMap();\n }\n\n private loadSessionMap(): void {\n try {\n const data = readFileSync(this.sessionsPath, \"utf-8\");\n const map = JSON.parse(data) as Record<string, PersistedSession>;\n for (const [channelId, entry] of Object.entries(map)) {\n this.pendingResumes.set(channelId, entry);\n }\n if (this.pendingResumes.size > 0) {\n console.log(`Loaded ${this.pendingResumes.size} session(s) for lazy resume`);\n }\n } catch (err: unknown) {\n // ENOENT is expected on first run; log other errors for diagnosability\n if (err instanceof Error && \"code\" in err && (err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n if (err instanceof SyntaxError) {\n console.warn(\"Corrupt sessions.json, starting fresh:\", err.message);\n }\n }\n }\n\n saveSessionMap(): void {\n const map: Record<string, PersistedSession> = {};\n // Include unresumed pending sessions so they survive daemon restarts\n // where no messages were received for that channel\n for (const [channelId, entry] of this.pendingResumes) {\n map[channelId] = entry;\n }\n // Active sessions override any pending resume for the same channel\n for (const [channelId, session] of this.sessions) {\n map[channelId] = {\n sessionId: session.sessionId,\n agentName: session.agentName,\n };\n }\n try {\n mkdirSync(dirname(this.sessionsPath), { recursive: true });\n writeFileSync(this.sessionsPath, JSON.stringify(map, null, 2));\n } catch (err) {\n console.error(\"Failed to save session map:\", err);\n }\n }\n\n async prompt(channelId: string, text: string, agentName: string, agentConfig: AgentConfig, requestorId: string, mcpServers?: McpServerConfig[]): Promise<string> {\n console.log(`[MCP] prompt: channel=${channelId} mcpServers=${mcpServers ? `[${mcpServers.length} server(s)]` : \"undefined\"}`);\n const session = await this.getOrCreate(channelId, agentName, agentConfig, requestorId, mcpServers);\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, agentName: string, agentConfig: AgentConfig, requestorId: string, mcpServers?: McpServerConfig[]): Promise<ManagedSession> {\n const existing = this.sessions.get(channelId);\n if (existing) {\n console.log(`[MCP] getOrCreate: reusing existing session for channel=${channelId} (mcpServers passed but ignored: ${mcpServers ? mcpServers.length : 0} server(s))`);\n return existing;\n }\n\n // Check for a pending resume from a previous daemon run\n const pending = this.pendingResumes.get(channelId);\n if (pending && pending.agentName === agentName) {\n this.pendingResumes.delete(channelId);\n try {\n return await this.resumeSession(channelId, agentName, agentConfig, requestorId, pending.sessionId, mcpServers);\n } catch (err) {\n console.warn(`Session resume failed for channel ${channelId}, creating new session:`, err);\n // Fall through to create a new session\n }\n } else if (pending) {\n // Agent name changed since last run — discard stale resume\n this.pendingResumes.delete(channelId);\n }\n\n console.log(`[MCP] getOrCreate: creating new session for channel=${channelId} with ${mcpServers?.length ?? 0} MCP server(s)`);\n return this.createSession(channelId, agentName, agentConfig, requestorId, mcpServers);\n }\n\n private async createSession(channelId: string, agentName: string, config: AgentConfig, requestorId: string, mcpServers?: McpServerConfig[]): 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\", (code) => {\n const session = this.sessions.get(channelId);\n if (session?.process === proc) {\n clearTimeout(session.idleTimer);\n this.sessions.delete(channelId);\n if (code !== 0 && code !== null) {\n console.warn(`Agent process for channel ${channelId} exited with code ${code}`);\n }\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 newSessionPayload = {\n cwd: config.cwd,\n mcpServers: mcpServers ?? [],\n };\n console.log(`[MCP] createSession: calling newSession for channel=${channelId}`, JSON.stringify({\n cwd: newSessionPayload.cwd,\n mcpServerCount: newSessionPayload.mcpServers.length,\n mcpServerNames: newSessionPayload.mcpServers.map(s => s.name),\n }));\n const result = await connection.newSession(newSessionPayload);\n sessionId = result.sessionId;\n console.log(`[MCP] createSession: newSession succeeded, sessionId=${sessionId}`);\n } catch (err) {\n proc.kill();\n throw err;\n }\n\n const managed: ManagedSession = {\n channelId,\n agentName,\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 async resumeSession(channelId: string, agentName: string, config: AgentConfig, requestorId: string, previousSessionId: string, mcpServers?: McpServerConfig[]): Promise<ManagedSession> {\n const proc = spawn(config.command, config.args, {\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n cwd: config.cwd,\n });\n\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\", (code) => {\n const session = this.sessions.get(channelId);\n if (session?.process === proc) {\n clearTimeout(session.idleTimer);\n this.sessions.delete(channelId);\n if (code !== 0 && code !== null) {\n console.warn(`Agent process for channel ${channelId} exited with code ${code}`);\n }\n }\n });\n\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 const initResult = 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 // Check if agent supports session resume\n const supportsResume = !!initResult.agentCapabilities?.sessionCapabilities?.resume;\n if (!supportsResume) {\n proc.kill();\n throw new Error(\"Agent does not support session resume\");\n }\n\n await connection.unstable_resumeSession({\n sessionId: previousSessionId,\n cwd: config.cwd,\n mcpServers: mcpServers ?? [],\n });\n sessionId = previousSessionId;\n console.log(`Resumed session ${sessionId} for channel ${channelId}`);\n } catch (err) {\n proc.kill();\n throw err;\n }\n\n const managed: ManagedSession = {\n channelId,\n agentName,\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 this.pendingResumes.delete(channelId);\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 this.saveSessionMap();\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 DiffContent {\n path: string;\n oldText?: string | null;\n newText: string;\n}\n\nexport interface AcpEventHandlers {\n onToolCall(channelId: string, toolCallId: string, title: string, kind: string, status: string, diffs: DiffContent[], rawInput?: Record<string, unknown>): void;\n onToolCallUpdate(channelId: string, toolCallId: string, status: string, diffs: DiffContent[], rawInput?: Record<string, unknown>): 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 diffs: DiffContent[],\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 diffs = extractDiffs((params.toolCall as { content?: unknown }).content);\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 diffs,\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 const rawKeys = (update as Record<string, unknown>).rawInput;\n console.log(`[DEBUG tool_call] toolCallId=${update.toolCallId} title=${update.title} rawInputKeys=${rawKeys && typeof rawKeys === \"object\" ? Object.keys(rawKeys).join(\",\") : String(rawKeys)} updateKeys=${Object.keys(update).join(\",\")}`);\n const toolCallDiffs = extractDiffs(update.content);\n const rawVal = (update as Record<string, unknown>).rawInput;\n const rawInput = typeof rawVal === \"object\" && rawVal !== null && !Array.isArray(rawVal)\n ? (rawVal as Record<string, unknown>)\n : undefined;\n handlers.onToolCall(\n channelId,\n update.toolCallId,\n update.title ?? \"Unknown\",\n update.kind ?? \"other\",\n update.status ?? \"pending\",\n toolCallDiffs,\n rawInput,\n );\n break;\n }\n case \"tool_call_update\": {\n const updateRawKeys = (update as Record<string, unknown>).rawInput;\n console.log(`[DEBUG tool_call_update] toolCallId=${update.toolCallId} rawInputKeys=${updateRawKeys && typeof updateRawKeys === \"object\" ? Object.keys(updateRawKeys).join(\",\") : String(updateRawKeys)} updateKeys=${Object.keys(update).join(\",\")}`);\n const updateDiffs = extractDiffs(update.content);\n const updateRawVal = (update as Record<string, unknown>).rawInput;\n const updateRawInput = typeof updateRawVal === \"object\" && updateRawVal !== null && !Array.isArray(updateRawVal)\n ? (updateRawVal as Record<string, unknown>)\n : undefined;\n handlers.onToolCallUpdate(\n channelId,\n update.toolCallId,\n update.status ?? \"in_progress\",\n updateDiffs,\n updateRawInput,\n );\n break;\n }\n }\n },\n };\n}\n\nfunction extractDiffs(content: unknown): DiffContent[] {\n if (!Array.isArray(content)) return [];\n const diffs: DiffContent[] = [];\n for (const item of content) {\n if (item && typeof item === \"object\" && \"type\" in item && item.type === \"diff\") {\n const { path, oldText, newText } = item as Record<string, unknown>;\n if (typeof path !== \"string\" || typeof newText !== \"string\") continue;\n if (oldText !== undefined && oldText !== null && typeof oldText !== \"string\") continue;\n diffs.push({ path, oldText: (oldText as string | null) ?? null, newText });\n }\n }\n return diffs;\n}\n","import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n EmbedBuilder,\n type Message,\n type TextChannel,\n} from \"discord.js\";\nimport type { DiffContent } from \"./acp-client.js\";\nimport { formatDiff } from \"./message-bridge.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 diffs: DiffContent[] = [],\n timeoutMs = 14 * 60 * 1000,\n): Promise<{ outcome: \"selected\"; optionId: string; diffsSent?: boolean } | { outcome: \"cancelled\"; diffsSent?: boolean }> {\n if (options.length === 0) {\n return { outcome: \"cancelled\" };\n }\n\n // Send diffs before the permission embed so the user can review changes\n let diffsSent = false;\n const diffMsgs: Message[] = [];\n if (diffs.length > 0) {\n try {\n const diffMessages = formatDiff(diffs);\n for (const content of diffMessages) {\n diffMsgs.push(await channel.send(content));\n }\n diffsSent = true;\n } catch (err) {\n console.error(\"Failed to send permission diffs:\", err);\n }\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 const cleanup = () => {\n for (const dm of diffMsgs) dm.delete().catch(() => {});\n msg.delete().catch(() => msg.edit({ components: [] }).catch(() => {}));\n };\n\n collector.on(\"collect\", async (interaction) => {\n const optionId = interaction.customId.replace(\"perm_\", \"\");\n await interaction.deferUpdate();\n cleanup();\n collector.stop(\"selected\");\n resolve({ outcome: \"selected\", optionId, diffsSent });\n });\n\n collector.on(\"end\", (_collected, reason) => {\n if (reason !== \"selected\") {\n cleanup();\n resolve({ outcome: \"cancelled\", diffsSent });\n }\n });\n });\n}\n","import { createTwoFilesPatch } from \"diff\";\nimport type { DiffContent } from \"./acp-client.js\";\n\nconst DISCORD_MAX_LENGTH = 2000;\nconst MAX_DIFF_LINES = 150;\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; rawInput?: Record<string, unknown> }>,\n): string {\n const lines: string[] = [];\n for (const [, tool] of tools) {\n const detail = extractToolDetail(tool.rawInput) ?? extractDetailFromTitle(tool.title);\n const suffix = detail ? ` · \\`${detail}\\`` : \"\";\n lines.push(`${STATUS_ICONS[tool.status]} ${tool.title}${suffix}`);\n }\n return lines.join(\"\\n\");\n}\n\nconst MAX_DETAIL_LENGTH = 80;\n\n// Only display values from known-safe fields to avoid leaking secrets\nconst SAFE_FIELDS = [\"command\", \"file_path\", \"pattern\", \"query\", \"path\", \"url\", \"description\"];\n\n// Substrings that mark a field as sensitive — matches \"secret_key\", \"api_key\", \"access_token\", etc.\nconst BLOCKED_SUBSTRINGS = [\"token\", \"secret\", \"password\", \"key\", \"content\", \"new_string\", \"old_string\", \"credential\", \"auth\"];\n\nfunction isBlockedField(name: string): boolean {\n const lower = name.toLowerCase();\n return BLOCKED_SUBSTRINGS.some((sub) => lower.includes(sub));\n}\n\nfunction extractToolDetail(rawInput?: Record<string, unknown>): string | null {\n if (!rawInput) return null;\n\n // Try known safe fields first\n for (const field of SAFE_FIELDS) {\n if (typeof rawInput[field] === \"string\" && rawInput[field]) {\n return truncate(sanitizeDetail(rawInput[field] as string), MAX_DETAIL_LENGTH);\n }\n }\n\n // Fallback: pick the first short string value from non-blocked fields\n for (const [fieldName, value] of Object.entries(rawInput)) {\n if (isBlockedField(fieldName)) continue;\n if (SAFE_FIELDS.includes(fieldName)) continue; // already checked\n if (typeof value === \"string\" && value.length > 0 && value.length < 100) {\n return truncate(sanitizeDetail(value), MAX_DETAIL_LENGTH);\n }\n }\n\n return null;\n}\n\n/**\n * Extract useful detail from the tool title when rawInput is unavailable.\n * Titles often look like \"Read /path/to/file\" or \"Bash: ls -la\" —\n * we extract the part after the first space or colon.\n */\nfunction extractDetailFromTitle(title: string): string | null {\n if (!title) return null;\n\n // Pattern: \"ToolName /path/or/arg\" or \"ToolName: something\"\n const colonMatch = title.match(/^[^:]+:\\s*(.+)/);\n if (colonMatch) {\n return truncate(sanitizeDetail(colonMatch[1].trim()), MAX_DETAIL_LENGTH);\n }\n\n // Pattern: \"ToolName /some/path\" — extract if second part looks like a path or command\n const spaceIdx = title.indexOf(\" \");\n if (spaceIdx > 0 && spaceIdx < title.length - 1) {\n const rest = title.slice(spaceIdx + 1).trim();\n // Only extract if it looks like a path, URL, or meaningful argument\n if (rest.startsWith(\"/\") || rest.startsWith(\"./\") || rest.startsWith(\"http\") || rest.includes(\".\")) {\n return truncate(sanitizeDetail(rest), MAX_DETAIL_LENGTH);\n }\n }\n\n return null;\n}\n\nfunction sanitizeDetail(text: string): string {\n return text.replace(/`/g, \"'\");\n}\n\nfunction truncate(text: string, max: number): string {\n // Use first line only for multiline values\n const firstLine = text.split(\"\\n\")[0];\n if (firstLine.length <= max) return firstLine;\n return firstLine.slice(0, max - 1) + \"\\u2026\";\n}\n\nexport function formatDiff(diffs: DiffContent[], maxLines = MAX_DIFF_LINES): string[] {\n if (diffs.length === 0) return [];\n\n const parts: string[] = [];\n\n for (const d of diffs) {\n const fileName = d.path.split(\"/\").pop() ?? d.path;\n const oldText = d.oldText ?? \"\";\n const patch = createTwoFilesPatch(\n d.oldText == null ? \"/dev/null\" : d.path,\n d.path,\n oldText,\n d.newText,\n undefined,\n undefined,\n { context: 3 },\n );\n\n // Remove the first two header lines (Index: and ===) if present, keep ---/+++ and hunks\n const patchLines = patch.split(\"\\n\");\n // Find the first --- line to start from\n const startIdx = patchLines.findIndex((l) => l.startsWith(\"---\"));\n const diffLines = startIdx >= 0 ? patchLines.slice(startIdx) : patchLines;\n\n let truncated = false;\n let displayLines = diffLines;\n if (diffLines.length > maxLines) {\n displayLines = diffLines.slice(0, maxLines);\n truncated = true;\n }\n\n let block = `**${fileName}**\\n\\`\\`\\`diff\\n${displayLines.join(\"\\n\")}\\n\\`\\`\\``;\n if (truncated) {\n block += `\\n*... ${diffLines.length - maxLines} more lines*`;\n }\n\n parts.push(block);\n }\n\n // Join all diff blocks and split for Discord's message limit\n const fullMessage = parts.join(\"\\n\\n\");\n return splitMessage(fullMessage);\n}\n","import { createServer, type Server, type Socket } from \"node:net\";\nimport { existsSync, unlinkSync, mkdirSync, chmodSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport const DEFAULT_IPC_SOCKET_PATH = join(homedir(), \".acp-discord\", \"ipc.sock\");\n\nexport interface BindChannelOpts {\n cwd?: string;\n autoReply?: boolean;\n discordTools?: boolean;\n}\n\nexport interface BindingInfo {\n channelId: string;\n agent: string;\n cwd?: string;\n autoReply: boolean;\n discordTools: boolean;\n}\n\nexport interface TaskCrudResult {\n task?: unknown;\n tasks?: unknown[];\n logs?: unknown[];\n error?: string;\n deleted?: boolean;\n}\n\nexport interface IpcHandler {\n registerChannel(channelId: string, agentName: string, autoReply: boolean): void;\n unregisterChannel(channelId: string): void;\n confirmAction(sourceChannelId: string, description: string, details: string): Promise<boolean>;\n bindChannel(channelId: string, agentName: string, opts: BindChannelOpts, guildId?: string): Promise<{ success: boolean; error?: string }>;\n unbindChannel(channelId: string, guildId?: string): Promise<{ success: boolean; error?: string }>;\n listBindings(guildId?: string): Promise<{ success: boolean; bindings: BindingInfo[] }>;\n createTask?(params: Record<string, unknown>): TaskCrudResult;\n listTasks?(channelId?: string): TaskCrudResult;\n updateTask?(taskId: string, updates: Record<string, unknown>, channelId?: string): TaskCrudResult;\n deleteTask?(taskId: string, channelId?: string): TaskCrudResult;\n getTaskLogs?(taskId?: string, channelId?: string): TaskCrudResult;\n}\n\ninterface IpcMessage {\n action: string;\n requestId?: string;\n channelId?: string;\n agentName?: string;\n autoReply?: boolean;\n sourceChannelId?: string;\n description?: string;\n details?: string;\n cwd?: string;\n discordTools?: boolean;\n guildId?: string;\n // Task-related fields\n taskId?: string;\n prompt?: string;\n scheduleType?: string;\n scheduleValue?: string;\n notify?: string;\n updates?: Record<string, unknown>;\n}\n\nexport class IpcServer {\n private server: Server | null = null;\n private socketPath: string;\n private handler: IpcHandler;\n private connections = new Set<Socket>();\n\n constructor(handler: IpcHandler, socketPath = DEFAULT_IPC_SOCKET_PATH) {\n this.handler = handler;\n this.socketPath = socketPath;\n }\n\n async start(): Promise<void> {\n // Clean up stale socket\n if (existsSync(this.socketPath)) {\n unlinkSync(this.socketPath);\n }\n mkdirSync(dirname(this.socketPath), { recursive: true });\n\n return new Promise((resolve, reject) => {\n this.server = createServer((socket) => this.handleConnection(socket));\n\n this.server.on(\"error\", (err) => {\n console.error(\"IPC server error:\", err);\n reject(err);\n });\n\n this.server.listen(this.socketPath, () => {\n // Restrict socket to owner-only access\n chmodSync(this.socketPath, 0o600);\n console.log(`IPC server listening on ${this.socketPath}`);\n resolve();\n });\n });\n }\n\n private handleConnection(socket: Socket): void {\n this.connections.add(socket);\n let buffer = \"\";\n\n socket.on(\"data\", (data) => {\n buffer += data.toString();\n // Process newline-delimited JSON messages\n let newlineIdx: number;\n while ((newlineIdx = buffer.indexOf(\"\\n\")) !== -1) {\n const line = buffer.slice(0, newlineIdx).trim();\n buffer = buffer.slice(newlineIdx + 1);\n if (line) {\n this.processMessage(socket, line).catch((err) => {\n console.error(\"IPC message processing error:\", err);\n });\n }\n }\n });\n\n socket.on(\"close\", () => {\n this.connections.delete(socket);\n });\n\n socket.on(\"error\", (err) => {\n console.error(\"IPC connection error:\", err);\n this.connections.delete(socket);\n });\n }\n\n private async processMessage(socket: Socket, raw: string): Promise<void> {\n let msg: IpcMessage;\n try {\n msg = JSON.parse(raw);\n } catch {\n console.error(\"IPC: invalid JSON:\", raw);\n return;\n }\n\n switch (msg.action) {\n case \"register_channel\":\n if (msg.channelId && msg.agentName) {\n this.handler.registerChannel(msg.channelId, msg.agentName, msg.autoReply ?? true);\n }\n break;\n\n case \"unregister_channel\":\n if (msg.channelId) {\n this.handler.unregisterChannel(msg.channelId);\n }\n break;\n\n case \"confirm_action\":\n if (msg.requestId && msg.sourceChannelId && msg.description) {\n const approved = await this.handler.confirmAction(\n msg.sourceChannelId,\n msg.description,\n msg.details ?? \"\",\n );\n const response = JSON.stringify({ requestId: msg.requestId, approved }) + \"\\n\";\n socket.write(response);\n }\n break;\n\n case \"bind_channel\": {\n if (msg.requestId && msg.channelId && msg.agentName) {\n const result = await this.handler.bindChannel(msg.channelId, msg.agentName, {\n cwd: msg.cwd,\n autoReply: msg.autoReply,\n discordTools: msg.discordTools,\n }, msg.guildId);\n socket.write(JSON.stringify({ requestId: msg.requestId, ...result }) + \"\\n\");\n }\n break;\n }\n\n case \"unbind_channel\": {\n if (msg.requestId && msg.channelId) {\n const result = await this.handler.unbindChannel(msg.channelId, msg.guildId);\n socket.write(JSON.stringify({ requestId: msg.requestId, ...result }) + \"\\n\");\n }\n break;\n }\n\n case \"list_bindings\": {\n if (msg.requestId) {\n const result = await this.handler.listBindings(msg.guildId);\n socket.write(JSON.stringify({ requestId: msg.requestId, ...result }) + \"\\n\");\n }\n break;\n }\n\n case \"create_task\": {\n if (!msg.requestId || !this.handler.createTask) break;\n try {\n const result = this.handler.createTask({\n channel_id: msg.channelId ?? msg.sourceChannelId ?? \"\",\n agent_name: msg.agentName ?? \"unknown\",\n prompt: msg.prompt ?? \"\",\n schedule_type: msg.scheduleType ?? \"once\",\n schedule_value: msg.scheduleValue ?? \"\",\n description: msg.description,\n notify: msg.notify,\n created_by: \"agent\",\n });\n socket.write(JSON.stringify({ requestId: msg.requestId, ...result }) + \"\\n\");\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n socket.write(JSON.stringify({ requestId: msg.requestId, error: errMsg }) + \"\\n\");\n }\n break;\n }\n\n case \"list_tasks\": {\n if (!msg.requestId || !this.handler.listTasks) break;\n const listResult = this.handler.listTasks(msg.channelId);\n socket.write(JSON.stringify({ requestId: msg.requestId, ...listResult }) + \"\\n\");\n break;\n }\n\n case \"update_task\": {\n if (!msg.requestId || !msg.taskId || !this.handler.updateTask) break;\n try {\n const updateResult = this.handler.updateTask(msg.taskId, msg.updates ?? {}, msg.channelId);\n socket.write(JSON.stringify({ requestId: msg.requestId, ...updateResult }) + \"\\n\");\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n socket.write(JSON.stringify({ requestId: msg.requestId, error: errMsg }) + \"\\n\");\n }\n break;\n }\n\n case \"delete_task\": {\n if (!msg.requestId || !msg.taskId || !this.handler.deleteTask) break;\n const deleteResult = this.handler.deleteTask(msg.taskId, msg.channelId);\n socket.write(JSON.stringify({ requestId: msg.requestId, ...deleteResult }) + \"\\n\");\n break;\n }\n\n case \"get_task_logs\": {\n if (!msg.requestId || !this.handler.getTaskLogs) break;\n const logsResult = this.handler.getTaskLogs(msg.taskId, msg.channelId);\n socket.write(JSON.stringify({ requestId: msg.requestId, ...logsResult }) + \"\\n\");\n break;\n }\n\n default:\n console.error(\"IPC: unknown action:\", msg.action);\n }\n }\n\n stop(): void {\n for (const conn of this.connections) {\n conn.destroy();\n }\n this.connections.clear();\n if (this.server) {\n this.server.close();\n this.server = null;\n }\n // Clean up socket file\n if (existsSync(this.socketPath)) {\n try {\n unlinkSync(this.socketPath);\n } catch {\n // ignore\n }\n }\n }\n}\n","import { readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { randomUUID } from \"node:crypto\";\nimport { CronExpressionParser } from \"cron-parser\";\n\nconst DEFAULT_DATA_DIR = join(homedir(), \".acp-discord\");\nconst MAX_LOGS_PER_TASK = 50;\n\nexport interface ScheduledTask {\n id: string;\n channel_id: string;\n agent_name: string;\n prompt: string;\n description: string;\n schedule_type: \"once\" | \"cron\" | \"interval\";\n schedule_value: string;\n status: \"active\" | \"paused\" | \"completed\";\n notify: \"always\" | \"on_error\" | \"never\";\n next_run: string | null;\n last_run: string | null;\n created_by: string;\n created_at: string;\n}\n\nexport interface TaskRunLog {\n id: string;\n task_id: string;\n task_description: string;\n started_at: string;\n completed_at: string;\n duration_ms: number;\n status: \"success\" | \"error\";\n output: string;\n error: string | null;\n}\n\nexport interface TaskRunInput {\n startedAt: Date;\n completedAt: Date;\n durationMs: number;\n status: \"success\" | \"error\";\n output: string;\n error: string | null;\n}\n\nexport interface CreateTaskParams {\n channel_id: string;\n agent_name: string;\n prompt: string;\n description?: string;\n schedule_type: \"once\" | \"cron\" | \"interval\";\n schedule_value: string;\n notify?: \"always\" | \"on_error\" | \"never\";\n created_by: string;\n}\n\nexport type OnTaskFire = (task: ScheduledTask) => Promise<void>;\n\nexport class TaskScheduler {\n private tasks: ScheduledTask[] = [];\n private logs: TaskRunLog[] = [];\n private interval: NodeJS.Timeout | null = null;\n private onTaskFire: OnTaskFire;\n private tasksPath: string;\n private logsPath: string;\n private dataDir: string;\n private inFlight = new Set<string>(); // task IDs currently running\n\n constructor(onTaskFire: OnTaskFire, dataDir?: string) {\n this.onTaskFire = onTaskFire;\n this.dataDir = dataDir ?? DEFAULT_DATA_DIR;\n this.tasksPath = join(this.dataDir, \"scheduled-tasks.json\");\n this.logsPath = join(this.dataDir, \"task-run-logs.json\");\n }\n\n start(): void {\n this.load();\n this.interval = setInterval(() => {\n this.poll().catch((err) => {\n console.error(\"TaskScheduler poll error:\", err);\n });\n }, 15_000);\n console.log(`TaskScheduler started with ${this.tasks.length} task(s)`);\n }\n\n stop(): void {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = null;\n }\n this.save();\n }\n\n createTask(params: CreateTaskParams): ScheduledTask {\n const now = new Date().toISOString();\n const task: ScheduledTask = {\n id: randomUUID(),\n channel_id: params.channel_id,\n agent_name: params.agent_name,\n prompt: params.prompt,\n description: params.description ?? params.prompt.slice(0, 80),\n schedule_type: params.schedule_type,\n schedule_value: params.schedule_value,\n status: \"active\",\n notify: params.notify ?? \"on_error\",\n next_run: null,\n last_run: null,\n created_by: params.created_by,\n created_at: now,\n };\n\n task.next_run = this.computeNextRun(task);\n if (!task.next_run) {\n throw new Error(`Invalid schedule: cannot compute next run for ${params.schedule_type} \"${params.schedule_value}\"`);\n }\n\n this.tasks.push(task);\n this.save();\n return task;\n }\n\n listTasks(channelId?: string): ScheduledTask[] {\n if (channelId) {\n return this.tasks.filter((t) => t.channel_id === channelId);\n }\n return [...this.tasks];\n }\n\n updateTask(\n id: string,\n updates: Partial<Pick<ScheduledTask, \"status\" | \"prompt\" | \"schedule_type\" | \"schedule_value\" | \"notify\" | \"description\">>,\n channelId?: string,\n ): ScheduledTask | null {\n const task = this.tasks.find((t) => t.id === id);\n if (!task) return null;\n if (channelId && task.channel_id !== channelId) return null;\n\n if (updates.status !== undefined) task.status = updates.status;\n if (updates.prompt !== undefined) task.prompt = updates.prompt;\n if (updates.description !== undefined) task.description = updates.description;\n if (updates.notify !== undefined) task.notify = updates.notify;\n\n if (updates.schedule_type !== undefined || updates.schedule_value !== undefined) {\n // Save originals for rollback on validation failure\n const origType = task.schedule_type;\n const origValue = task.schedule_value;\n const origNextRun = task.next_run;\n\n if (updates.schedule_type !== undefined) task.schedule_type = updates.schedule_type;\n if (updates.schedule_value !== undefined) task.schedule_value = updates.schedule_value;\n const nextRun = this.computeNextRun(task);\n if (!nextRun) {\n // Rollback\n task.schedule_type = origType;\n task.schedule_value = origValue;\n task.next_run = origNextRun;\n throw new Error(`Invalid schedule: cannot compute next run for ${task.schedule_type} \"${task.schedule_value}\"`);\n }\n task.next_run = nextRun;\n }\n\n this.save();\n return task;\n }\n\n deleteTask(id: string, channelId?: string): boolean {\n const idx = this.tasks.findIndex((t) => t.id === id);\n if (idx === -1) return false;\n if (channelId && this.tasks[idx].channel_id !== channelId) return false;\n this.tasks.splice(idx, 1);\n this.save();\n return true;\n }\n\n getTaskLogs(taskId?: string, channelId?: string): TaskRunLog[] {\n let logs = this.logs;\n if (taskId) {\n // Verify the task belongs to the channel if channelId specified\n if (channelId) {\n const task = this.tasks.find((t) => t.id === taskId);\n if (task && task.channel_id !== channelId) return [];\n }\n logs = logs.filter((l) => l.task_id === taskId);\n } else if (channelId) {\n // Only return logs for tasks belonging to this channel\n const channelTaskIds = new Set(\n this.tasks.filter((t) => t.channel_id === channelId).map((t) => t.id),\n );\n logs = logs.filter((l) => channelTaskIds.has(l.task_id));\n }\n return [...logs];\n }\n\n logRun(taskId: string, result: TaskRunInput): void {\n const task = this.tasks.find((t) => t.id === taskId);\n const log: TaskRunLog = {\n id: randomUUID(),\n task_id: taskId,\n task_description: task?.description ?? \"unknown\",\n started_at: result.startedAt.toISOString(),\n completed_at: result.completedAt.toISOString(),\n duration_ms: result.durationMs,\n status: result.status,\n output: result.output.slice(0, 4000),\n error: result.error,\n };\n this.logs.push(log);\n\n // Trim logs per task\n const taskLogs = this.logs.filter((l) => l.task_id === taskId);\n if (taskLogs.length > MAX_LOGS_PER_TASK) {\n const idsToRemove = new Set(\n taskLogs\n .slice(0, taskLogs.length - MAX_LOGS_PER_TASK)\n .map((l) => l.id),\n );\n this.logs = this.logs.filter((l) => !idsToRemove.has(l.id));\n }\n\n this.saveLogs();\n }\n\n computeNextRun(task: ScheduledTask): string | null {\n const now = new Date();\n\n switch (task.schedule_type) {\n case \"once\": {\n const date = new Date(task.schedule_value);\n if (isNaN(date.getTime())) return null;\n return date > now ? date.toISOString() : date.toISOString();\n }\n\n case \"cron\": {\n try {\n const expr = CronExpressionParser.parse(task.schedule_value, { currentDate: now });\n const next = expr.next();\n return next.toDate().toISOString();\n } catch {\n return null;\n }\n }\n\n case \"interval\": {\n const seconds = parseInt(task.schedule_value, 10);\n if (isNaN(seconds) || seconds <= 0) return null;\n // Anchor to last_run to prevent drift\n const anchor = task.last_run ? new Date(task.last_run) : now;\n const next = new Date(anchor.getTime() + seconds * 1000);\n // If next is in the past, advance forward\n if (next <= now) {\n const elapsed = now.getTime() - anchor.getTime();\n const intervals = Math.ceil(elapsed / (seconds * 1000));\n return new Date(anchor.getTime() + intervals * seconds * 1000).toISOString();\n }\n return next.toISOString();\n }\n\n default:\n return null;\n }\n }\n\n private async poll(): Promise<void> {\n const now = new Date();\n\n for (const task of this.tasks) {\n if (task.status !== \"active\") continue;\n if (!task.next_run) continue;\n if (this.inFlight.has(task.id)) continue; // skip if already running\n\n const nextRun = new Date(task.next_run);\n if (nextRun > now) continue;\n\n // Mark as firing\n task.last_run = now.toISOString();\n\n if (task.schedule_type === \"once\") {\n task.status = \"completed\";\n } else {\n task.next_run = this.computeNextRun(task);\n }\n this.save();\n\n // Fire asynchronously with in-flight guard\n this.inFlight.add(task.id);\n this.onTaskFire(task)\n .catch((err) => {\n console.error(`Task fire error for ${task.id}:`, err);\n })\n .finally(() => {\n this.inFlight.delete(task.id);\n });\n }\n }\n\n private load(): void {\n mkdirSync(this.dataDir, { recursive: true });\n try {\n const data = readFileSync(this.tasksPath, \"utf-8\");\n this.tasks = JSON.parse(data);\n } catch {\n this.tasks = [];\n }\n try {\n const data = readFileSync(this.logsPath, \"utf-8\");\n this.logs = JSON.parse(data);\n } catch {\n this.logs = [];\n }\n }\n\n private save(): void {\n mkdirSync(this.dataDir, { recursive: true });\n writeFileSync(this.tasksPath, JSON.stringify(this.tasks, null, 2));\n }\n\n private saveLogs(): void {\n mkdirSync(this.dataDir, { recursive: true });\n writeFileSync(this.logsPath, JSON.stringify(this.logs, null, 2));\n }\n}\n","import { spawn } from \"node:child_process\";\nimport { Readable, Writable } from \"node:stream\";\nimport { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } from \"@agentclientprotocol/sdk\";\nimport type { Client, RequestPermissionResponse, SessionNotification } from \"@agentclientprotocol/sdk\";\nimport type { AgentConfig } from \"../shared/types.js\";\nimport type { McpServerConfig } from \"./session-manager.js\";\n\nexport interface TaskRunResult {\n output: string;\n stopReason: string;\n error: string | null;\n}\n\nexport async function runTask(\n agentConfig: AgentConfig,\n prompt: string,\n mcpServers: McpServerConfig[],\n): Promise<TaskRunResult> {\n const proc = spawn(agentConfig.command, agentConfig.args, {\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n cwd: agentConfig.cwd,\n });\n\n let output = \"\";\n let error: string | null = null;\n\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: Client = {\n async requestPermission(): Promise<RequestPermissionResponse> {\n // Auto-cancel permission requests in scheduled tasks\n return { outcome: { outcome: \"cancelled\" } };\n },\n async sessionUpdate(params: SessionNotification): Promise<void> {\n const update = params.update;\n if (update.sessionUpdate === \"agent_message_chunk\") {\n if (update.content.type === \"text\") {\n output += update.content.text;\n }\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: true,\n },\n clientInfo: {\n name: \"acp-discord-task-runner\",\n title: \"ACP Discord Task Runner\",\n version: \"0.1.0\",\n },\n });\n\n const { sessionId } = await connection.newSession({\n cwd: agentConfig.cwd,\n mcpServers,\n });\n\n const result = await connection.prompt({\n sessionId,\n prompt: [{ type: \"text\", text: prompt }],\n });\n\n proc.kill();\n return { output, stopReason: result.stopReason, error: null };\n } catch (err) {\n proc.kill();\n error = err instanceof Error ? err.message : String(err);\n return { output, stopReason: \"error\", error };\n }\n}\n"],"mappings":"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","names":["join","homedir","ActionRowBuilder","ButtonBuilder","ButtonStyle","EmbedBuilder","dirname","existsSync","mkdirSync","dirname","readFileSync","writeFileSync","mkdirSync","join","homedir","spawn","Readable","Writable","ClientSideConnection","ndJsonStream","PROTOCOL_VERSION","EmbedBuilder","ActionRowBuilder","ButtonBuilder","ButtonStyle","dirname","existsSync","join","homedir"]}
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","../src/daemon/ipc-server.ts","../src/daemon/task-scheduler.ts","../src/daemon/task-runner.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\");\nconst SESSIONS_PATH = join(CONFIG_DIR, \"sessions.json\");\n\nexport async function runDaemon(): Promise<void> {\n // Global error handlers — graceful shutdown on fatal errors, continue on rejections\n process.on(\"uncaughtException\", (err) => {\n console.error(\"Uncaught exception:\", err);\n // Allow event loop to flush logs, then exit for restart by service manager\n setTimeout(() => process.exit(1), 1000);\n });\n\n process.on(\"unhandledRejection\", (reason) => {\n console.error(\"Unhandled rejection:\", reason);\n // Unhandled rejections are less severe — log but continue\n });\n\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, SESSIONS_PATH, CONFIG_PATH);\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 EmbedBuilder,\n type Message,\n type TextChannel,\n} from \"discord.js\";\nimport { resolve as resolvePath, dirname } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport type { AppConfig } from \"../shared/types.js\";\nimport { saveConfig, resolveChannelConfig } from \"../shared/config.js\";\nimport { ChannelRouter } from \"./channel-router.js\";\nimport { SessionManager, type McpServerConfig } from \"./session-manager.js\";\nimport { sendPermissionRequest } from \"./permission-ui.js\";\nimport { splitMessage, formatToolSummary, formatDiff, type ToolStatus } from \"./message-bridge.js\";\nimport type { AcpEventHandlers, DiffContent } from \"./acp-client.js\";\nimport { IpcServer, DEFAULT_IPC_SOCKET_PATH } from \"./ipc-server.js\";\nimport { TaskScheduler } from \"./task-scheduler.js\";\nimport { runTask } from \"./task-runner.js\";\n\nexport async function startDiscordBot(config: AppConfig, sessionsPath: string, configPath: string): Promise<void> {\n let currentConfig = config;\n const router = new ChannelRouter(currentConfig);\n\n // Per-channel state for display\n const toolStates = new Map<string, Map<string, { title: string; status: ToolStatus; rawInput?: Record<string, unknown> }>>();\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 // channelId -> toolCallId -> DiffContent[]\n const pendingDiffs = new Map<string, Map<string, DiffContent[]>>();\n // channelId -> Set of toolCallIds whose diffs were already shown at permission-request time\n const permissionDiffShown = new Map<string, Set<string>>();\n\n // Typing indicator state: channelId -> interval timer\n const typingIntervals = new Map<string, NodeJS.Timeout>();\n\n function startTyping(channelId: string) {\n if (typingIntervals.has(channelId)) return;\n // Set a placeholder immediately to prevent concurrent calls from creating duplicate intervals\n const placeholder = setTimeout(() => {}, 0);\n typingIntervals.set(channelId, placeholder);\n clearTimeout(placeholder);\n\n fetchChannel(channelId).then((channel) => {\n if (!channel) { typingIntervals.delete(channelId); return; }\n // Re-check: stopTyping may have been called while we awaited\n if (!typingIntervals.has(channelId)) return;\n channel.sendTyping().catch(() => {});\n const interval = setInterval(() => {\n channel.sendTyping().catch(() => {});\n }, 8000);\n typingIntervals.set(channelId, interval);\n }).catch(() => {\n typingIntervals.delete(channelId);\n });\n }\n\n function stopTyping(channelId: string) {\n const interval = typingIntervals.get(channelId);\n if (interval) {\n clearInterval(interval);\n typingIntervals.delete(channelId);\n }\n }\n\n let discordClient: Client;\n\n // --- Confirmation UI for MCP tool actions ---\n\n // Pending confirmation requests from MCP servers (requestId -> { resolver, allowedUserId })\n const pendingConfirmations = new Map<string, { resolve: (approved: boolean) => void; allowedUserId: string | null }>();\n\n async function handleConfirmAction(sourceChannelId: string, description: string, details: string): Promise<boolean> {\n const channel = await fetchChannel(sourceChannelId);\n if (!channel) return false;\n\n // Only the user who triggered the current prompt can approve\n const allowedUserId = sessionManager.getActiveRequestorId(sourceChannelId);\n\n const requestId = `mcp_confirm_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n\n const embed = new EmbedBuilder()\n .setColor(0xffa500)\n .setTitle(`Channel Action: ${description}`)\n .setDescription(details || \"No additional details\")\n .setTimestamp();\n\n const row = new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder()\n .setCustomId(`mcp_approve_${requestId}`)\n .setLabel(\"\\u2705 Approve\")\n .setStyle(ButtonStyle.Success),\n new ButtonBuilder()\n .setCustomId(`mcp_reject_${requestId}`)\n .setLabel(\"\\u274C Reject\")\n .setStyle(ButtonStyle.Danger),\n );\n\n const msg = await channel.send({ embeds: [embed], components: [row] });\n\n return new Promise<boolean>((resolve) => {\n const timeout = setTimeout(() => {\n pendingConfirmations.delete(requestId);\n msg.delete().catch(() => msg.edit({ components: [] }).catch(() => {}));\n resolve(false);\n }, 5 * 60 * 1000); // 5 minute timeout\n\n pendingConfirmations.set(requestId, {\n resolve: (approved: boolean) => {\n clearTimeout(timeout);\n pendingConfirmations.delete(requestId);\n msg.delete().catch(() => msg.edit({ components: [] }).catch(() => {}));\n resolve(approved);\n },\n allowedUserId,\n });\n });\n }\n\n // --- IPC Server ---\n\n // --- Task Scheduler ---\n\n const taskScheduler = new TaskScheduler(async (task) => {\n const resolved = router.resolve(task.channel_id);\n if (!resolved) {\n console.error(`TaskScheduler: no resolved config for channel ${task.channel_id}`);\n taskScheduler.logRun(task.id, {\n startedAt: new Date(),\n completedAt: new Date(),\n durationMs: 0,\n status: \"error\",\n output: \"\",\n error: `No resolved config for channel ${task.channel_id}`,\n });\n return;\n }\n\n const channel = await fetchChannel(task.channel_id);\n const guildId = channel?.guild?.id ?? null;\n const mcpServers = guildId ? buildMcpServers(task.channel_id, task.agent_name, guildId) : [];\n\n const startedAt = new Date();\n const result = await runTask(resolved.agent, task.prompt, mcpServers);\n const completedAt = new Date();\n const durationMs = completedAt.getTime() - startedAt.getTime();\n\n taskScheduler.logRun(task.id, {\n startedAt,\n completedAt,\n durationMs,\n status: result.error ? \"error\" : \"success\",\n output: result.output,\n error: result.error,\n });\n\n const shouldNotify =\n task.notify === \"always\" ||\n (task.notify === \"on_error\" && result.error);\n\n if (shouldNotify && channel) {\n const text = result.error ?? result.output;\n const chunks = splitMessage(text || \"(no output)\");\n for (const chunk of chunks) {\n await channel.send(chunk);\n }\n }\n });\n\n const ipcServer = new IpcServer(\n {\n registerChannel(channelId, agentName, autoReply) {\n router.registerDynamic(channelId, agentName, autoReply);\n console.log(`IPC: registered dynamic channel ${channelId} -> agent ${agentName}`);\n },\n unregisterChannel(channelId) {\n router.unregisterDynamic(channelId);\n console.log(`IPC: unregistered dynamic channel ${channelId}`);\n },\n confirmAction: handleConfirmAction,\n\n async bindChannel(channelId, agentName, opts, guildId) {\n // Validate guild ownership if guildId provided\n if (guildId) {\n try {\n const channel = await discordClient.channels.fetch(channelId);\n const channelGuildId = channel && \"guildId\" in channel ? (channel as { guildId: string }).guildId : null;\n if (channelGuildId !== guildId) {\n return { success: false, error: `Channel ${channelId} does not belong to guild ${guildId}` };\n }\n } catch {\n return { success: false, error: `Cannot verify channel ${channelId}` };\n }\n }\n\n // Validate agent exists\n if (!currentConfig.agents[agentName]) {\n return { success: false, error: `Unknown agent \"${agentName}\"` };\n }\n\n // Update in-memory config\n currentConfig.channels[channelId] = {\n agent: agentName,\n cwd: opts.cwd,\n auto_reply: opts.autoReply ?? true,\n discord_tools: opts.discordTools,\n };\n\n // Register in router\n router.registerDynamic(channelId, agentName, opts.autoReply ?? true);\n\n // Persist to config.toml\n try {\n saveConfig(configPath, currentConfig);\n } catch (err) {\n // Rollback in-memory state on write failure\n delete currentConfig.channels[channelId];\n router.unregisterDynamic(channelId);\n const message = err instanceof Error ? err.message : String(err);\n return { success: false, error: `Failed to save config: ${message}` };\n }\n\n console.log(`IPC: bound channel ${channelId} -> agent ${agentName}`);\n return { success: true };\n },\n\n async unbindChannel(channelId, guildId) {\n // Validate guild ownership if guildId provided\n if (guildId) {\n try {\n const channel = await discordClient.channels.fetch(channelId);\n const channelGuildId = channel && \"guildId\" in channel ? (channel as { guildId: string }).guildId : null;\n if (channelGuildId !== guildId) {\n return { success: false, error: `Channel ${channelId} does not belong to guild ${guildId}` };\n }\n } catch {\n // Channel may have been deleted — allow unbinding if it exists in config\n }\n }\n\n if (!currentConfig.channels[channelId]) {\n return { success: false, error: `Channel ${channelId} is not bound` };\n }\n\n // Save for rollback\n const previous = currentConfig.channels[channelId];\n\n // Update in-memory\n delete currentConfig.channels[channelId];\n router.unregisterDynamic(channelId);\n\n // Persist\n try {\n saveConfig(configPath, currentConfig);\n } catch (err) {\n // Rollback\n currentConfig.channels[channelId] = previous;\n router.registerDynamic(channelId, previous.agent, previous.auto_reply ?? false);\n const message = err instanceof Error ? err.message : String(err);\n return { success: false, error: `Failed to save config: ${message}` };\n }\n\n console.log(`IPC: unbound channel ${channelId}`);\n return { success: true };\n },\n\n async listBindings(guildId) {\n const entries = Object.entries(currentConfig.channels);\n const bindings = [];\n for (const [channelId, ch] of entries) {\n // Filter by guild if specified\n if (guildId) {\n try {\n const channel = discordClient.channels.cache.get(channelId) ?? await discordClient.channels.fetch(channelId);\n const channelGuildId = channel && \"guildId\" in channel ? (channel as { guildId: string }).guildId : null;\n if (channelGuildId !== guildId) continue;\n } catch {\n continue;\n }\n }\n const resolved = resolveChannelConfig(currentConfig, channelId);\n bindings.push({\n channelId,\n agent: ch.agent,\n cwd: ch.cwd ?? currentConfig.agents[ch.agent]?.cwd,\n autoReply: ch.auto_reply ?? false,\n discordTools: resolved?.agent.discord_tools ?? false,\n });\n }\n return { success: true, bindings };\n },\n createTask(params) {\n try {\n const task = taskScheduler.createTask(params as Parameters<typeof taskScheduler.createTask>[0]);\n return { task };\n } catch (err) {\n return { error: err instanceof Error ? err.message : String(err) };\n }\n },\n listTasks(channelId) {\n return { tasks: taskScheduler.listTasks(channelId) };\n },\n updateTask(taskId, updates, channelId) {\n const task = taskScheduler.updateTask(taskId, updates, channelId);\n if (!task) return { error: `Task ${taskId} not found` };\n return { task };\n },\n deleteTask(taskId, channelId) {\n const deleted = taskScheduler.deleteTask(taskId, channelId);\n if (!deleted) return { error: `Task ${taskId} not found` };\n return { deleted: true };\n },\n getTaskLogs(taskId, channelId) {\n return { logs: taskScheduler.getTaskLogs(taskId, channelId) };\n },\n },\n DEFAULT_IPC_SOCKET_PATH,\n );\n\n const handlers: AcpEventHandlers = {\n onToolCall(channelId, toolCallId, title, _kind, status, diffs, rawInput) {\n if (!toolStates.has(channelId)) toolStates.set(channelId, new Map());\n toolStates.get(channelId)!.set(toolCallId, { title, status: status as ToolStatus, rawInput });\n accumulateDiffs(channelId, toolCallId, diffs);\n updateToolSummaryMessage(channelId);\n if (status === \"completed\") sendDiffsForTool(channelId, toolCallId);\n },\n\n onToolCallUpdate(channelId, toolCallId, status, diffs, rawInput) {\n const tools = toolStates.get(channelId);\n const tool = tools?.get(toolCallId);\n if (tool) {\n tool.status = status as ToolStatus;\n if (rawInput && !tool.rawInput) tool.rawInput = rawInput;\n accumulateDiffs(channelId, toolCallId, diffs);\n updateToolSummaryMessage(channelId);\n if (status === \"completed\") sendDiffsForTool(channelId, toolCallId);\n }\n },\n\n onAgentMessageChunk(channelId, text) {\n startTyping(channelId);\n const current = replyBuffers.get(channelId) ?? \"\";\n replyBuffers.set(channelId, current + text);\n scheduleFlushReply(channelId);\n },\n\n async onPermissionRequest(channelId, requestorId, toolCall, options, diffs) {\n const channel = await fetchChannel(channelId);\n if (!channel) return { outcome: \"cancelled\" as const };\n const result = await sendPermissionRequest(channel, toolCall.title, toolCall.kind, options, requestorId, diffs);\n if (result.diffsSent) {\n if (!permissionDiffShown.has(channelId)) permissionDiffShown.set(channelId, new Set());\n permissionDiffShown.get(channelId)!.add(toolCall.toolCallId);\n }\n return result;\n },\n\n onPromptComplete(channelId, _stopReason) {\n stopTyping(channelId);\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 pendingDiffs.delete(channelId);\n permissionDiffShown.delete(channelId);\n },\n };\n\n const sessionManager = new SessionManager(handlers, sessionsPath);\n\n // --- MCP server config builder ---\n\n function buildMcpServers(channelId: string, agentName: string, guildId: string): McpServerConfig[] {\n const resolved = router.resolve(channelId);\n const discordToolsEnabled = resolved?.agent.discord_tools ?? false;\n const scheduledTasksEnabled = resolved?.agent.scheduled_tasks ?? false;\n console.log(`[MCP] buildMcpServers: channel=${channelId} agent=${agentName} discord_tools=${discordToolsEnabled} scheduled_tasks=${scheduledTasksEnabled}`);\n\n const mcpConfig: McpServerConfig[] = [];\n\n // Resolve the built MCP server script path relative to this package\n // import.meta.dirname is available in Node 21.2+; fall back to fileURLToPath for Node 18\n const currentDir = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url));\n\n if (discordToolsEnabled) {\n const mcpScriptPath = resolvePath(currentDir, \"mcp-discord-channels.js\");\n const scriptExists = existsSync(mcpScriptPath);\n\n console.log(`[MCP] buildMcpServers: mcpScriptPath=${mcpScriptPath} exists=${scriptExists}`);\n if (!scriptExists) {\n console.warn(`[MCP] WARNING: MCP script not found at ${mcpScriptPath} — Discord tools will not work`);\n }\n\n mcpConfig.push({\n name: \"discord-channels\",\n command: \"node\",\n args: [mcpScriptPath],\n env: [\n { name: \"DISCORD_TOKEN\", value: config.discord.token },\n { name: \"GUILD_ID\", value: guildId },\n { name: \"IPC_SOCKET_PATH\", value: DEFAULT_IPC_SOCKET_PATH },\n { name: \"AGENT_NAME\", value: agentName },\n { name: \"SOURCE_CHANNEL_ID\", value: channelId },\n ],\n });\n }\n\n if (scheduledTasksEnabled) {\n const tasksMcpPath = resolvePath(currentDir, \"mcp-scheduled-tasks.js\");\n const tasksScriptExists = existsSync(tasksMcpPath);\n\n console.log(`[MCP] buildMcpServers: tasksMcpPath=${tasksMcpPath} exists=${tasksScriptExists}`);\n if (!tasksScriptExists) {\n console.warn(`[MCP] WARNING: MCP script not found at ${tasksMcpPath} — Scheduled tasks tools will not work`);\n } else {\n mcpConfig.push({\n name: \"scheduled-tasks\",\n command: \"node\",\n args: [tasksMcpPath],\n env: [\n { name: \"IPC_SOCKET_PATH\", value: DEFAULT_IPC_SOCKET_PATH },\n { name: \"AGENT_NAME\", value: agentName },\n { name: \"SOURCE_CHANNEL_ID\", value: channelId },\n ],\n });\n }\n }\n\n console.log(`[MCP] buildMcpServers: returning ${mcpConfig.length} MCP server(s):`, JSON.stringify(mcpConfig.map(s => ({ name: s.name, command: s.command, args: s.args }))));\n return mcpConfig;\n }\n\n // --- Display helpers ---\n\n function accumulateDiffs(channelId: string, toolCallId: string, diffs: DiffContent[]) {\n if (diffs.length === 0) return;\n if (!pendingDiffs.has(channelId)) pendingDiffs.set(channelId, new Map());\n const channelDiffs = pendingDiffs.get(channelId)!;\n const existing = channelDiffs.get(toolCallId) ?? [];\n channelDiffs.set(toolCallId, existing.concat(diffs));\n }\n\n async function sendDiffsForTool(channelId: string, toolCallId: string) {\n // Skip if diffs were already shown at permission-request time\n const shownSet = permissionDiffShown.get(channelId);\n if (shownSet?.has(toolCallId)) {\n shownSet.delete(toolCallId);\n pendingDiffs.get(channelId)?.delete(toolCallId);\n return;\n }\n\n const channelDiffs = pendingDiffs.get(channelId);\n const diffs = channelDiffs?.get(toolCallId);\n if (!diffs || diffs.length === 0) return;\n\n const channel = await fetchChannel(channelId);\n if (!channel) return;\n\n const messages = formatDiff(diffs);\n for (const msg of messages) {\n await channel.send({ content: msg, allowedMentions: { parse: [] as const } });\n }\n\n channelDiffs!.delete(toolCallId);\n }\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 noMentions = { parse: [] as const };\n const existing = toolSummaryMessages.get(channelId);\n if (existing) {\n await existing.edit({ content, components: [stopButton], allowedMentions: noMentions }).catch(() => {});\n } else {\n const msg = await channel.send({ content, components: [stopButton], allowedMentions: noMentions });\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: [], allowedMentions: { parse: [] as const } }).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 // --- Helper: resolve guild ID from a channel ---\n\n function getGuildId(message: Message): string | null {\n return message.guildId ?? null;\n }\n\n // --- Helper: prompt with MCP servers ---\n\n async function promptWithMcp(channelId: string, text: string, agentName: string, guildId: string | null, agentConfig: typeof config.agents[string], requestorId: string): Promise<void> {\n const mcpServers = guildId ? buildMcpServers(channelId, agentName, guildId) : undefined;\n await sessionManager.prompt(channelId, text, agentName, agentConfig, requestorId, mcpServers);\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(\"error\", (err) => {\n console.error(\"Discord client error:\", err);\n });\n\n discordClient.on(\"warn\", (msg) => {\n console.warn(\"Discord client warning:\", msg);\n });\n\n discordClient.on(\"shardDisconnect\", (event, shardId) => {\n console.warn(`Shard ${shardId} disconnected (code: ${event.code})`);\n });\n\n discordClient.on(\"shardReconnecting\", (shardId) => {\n console.log(`Shard ${shardId} reconnecting...`);\n });\n\n discordClient.on(Events.ClientReady, async (c) => {\n console.log(`Discord bot ready: ${c.user.tag}`);\n\n // Register slash commands\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 clearCommand = new SlashCommandBuilder()\n .setName(\"clear\")\n .setDescription(\"Clear the agent session and start fresh\");\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(), clearCommand.toJSON()],\n });\n console.log(\"Registered /ask and /clear commands\");\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 promptWithMcp(channelId, text, resolved.agentName, getGuildId(message), resolved.agent, message.author.id);\n } catch (err) {\n stopTyping(channelId);\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 and MCP confirmation buttons\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 // Handle MCP confirmation buttons\n if (interaction.customId.startsWith(\"mcp_approve_\") || interaction.customId.startsWith(\"mcp_reject_\")) {\n const approved = interaction.customId.startsWith(\"mcp_approve_\");\n const requestId = interaction.customId.replace(/^mcp_(approve|reject)_/, \"\");\n const pending = pendingConfirmations.get(requestId);\n if (pending) {\n // Only the user who triggered the prompt can approve/reject\n if (pending.allowedUserId && interaction.user.id !== pending.allowedUserId) {\n await interaction.reply({ content: \"Only the user who started this prompt can approve or reject.\", ephemeral: true });\n return;\n }\n await interaction.deferUpdate();\n pending.resolve(approved);\n } else {\n await interaction.reply({ content: \"This confirmation has expired.\", ephemeral: true });\n }\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 const guildId = interaction.guildId ?? null;\n await promptWithMcp(channelId, text, resolved.agentName, guildId, resolved.agent, interaction.user.id);\n } catch (err) {\n stopTyping(channelId);\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 // Handle /clear command\n discordClient.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isChatInputCommand()) return;\n if (interaction.commandName !== \"clear\") return;\n\n const channelId = interaction.channelId;\n sessionManager.teardown(channelId);\n\n // Clean up display state\n stopTyping(channelId);\n toolStates.delete(channelId);\n toolSummaryMessages.delete(channelId);\n replyBuffers.delete(channelId);\n replyMessages.delete(channelId);\n pendingDiffs.delete(channelId);\n permissionDiffShown.delete(channelId);\n const timer = flushTimers.get(channelId);\n if (timer) clearTimeout(timer);\n flushTimers.delete(channelId);\n\n await interaction.reply(\"Session cleared. Next message will start a fresh agent.\");\n });\n\n // --- Start IPC server and task scheduler ---\n await ipcServer.start();\n taskScheduler.start();\n\n // Graceful shutdown\n process.on(\"SIGTERM\", () => {\n for (const channelId of typingIntervals.keys()) stopTyping(channelId);\n taskScheduler.stop();\n ipcServer.stop();\n sessionManager.teardownAll();\n discordClient.destroy();\n });\n\n process.on(\"SIGINT\", () => {\n for (const channelId of typingIntervals.keys()) stopTyping(channelId);\n taskScheduler.stop();\n ipcServer.stop();\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 ipcServer.stop();\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 private dynamicChannels = new Map<string, { agentName: string; autoReply: boolean }>();\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 registerDynamic(channelId: string, agentName: string, autoReply: boolean): void {\n if (!this.config.agents[agentName]) {\n console.error(`Cannot register dynamic channel: unknown agent \"${agentName}\"`);\n return;\n }\n this.dynamicChannels.set(channelId, { agentName, autoReply });\n // Merge into existing channel config to preserve cwd/discord_tools overrides\n const existing = this.config.channels[channelId];\n this.config.channels[channelId] = {\n ...existing,\n agent: agentName,\n auto_reply: autoReply,\n };\n }\n\n unregisterDynamic(channelId: string): void {\n this.dynamicChannels.delete(channelId);\n delete this.config.channels[channelId];\n }\n\n updateConfig(newConfig: AppConfig): void {\n this.config = newConfig;\n // Re-inject dynamic channels that aren't already in the new config\n for (const [channelId, { agentName, autoReply }] of this.dynamicChannels) {\n if (!this.config.channels[channelId] && this.config.agents[agentName]) {\n this.config.channels[channelId] = {\n agent: agentName,\n auto_reply: autoReply,\n };\n }\n }\n }\n\n getConfig(): AppConfig {\n return this.config;\n }\n}\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport { readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\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\nexport interface McpServerConfig {\n name: string;\n command: string;\n args: string[];\n env: Array<{ name: string; value: string }>;\n}\n\ninterface ManagedSession {\n channelId: string;\n agentName: 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\ninterface PersistedSession {\n sessionId: string;\n agentName: string;\n}\n\nexport class SessionManager {\n private sessions = new Map<string, ManagedSession>();\n private handlers: AcpEventHandlers;\n private sessionsPath: string;\n private pendingResumes = new Map<string, PersistedSession>();\n private maxConcurrentSessions: number;\n\n constructor(handlers: AcpEventHandlers, sessionsPath: string, maxConcurrentSessions = 1) {\n this.handlers = handlers;\n this.sessionsPath = sessionsPath;\n this.maxConcurrentSessions = maxConcurrentSessions;\n this.loadSessionMap();\n }\n\n private loadSessionMap(): void {\n try {\n const data = readFileSync(this.sessionsPath, \"utf-8\");\n const map = JSON.parse(data) as Record<string, PersistedSession>;\n for (const [channelId, entry] of Object.entries(map)) {\n this.pendingResumes.set(channelId, entry);\n }\n if (this.pendingResumes.size > 0) {\n console.log(`Loaded ${this.pendingResumes.size} session(s) for lazy resume`);\n }\n } catch (err: unknown) {\n // ENOENT is expected on first run; log other errors for diagnosability\n if (err instanceof Error && \"code\" in err && (err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n if (err instanceof SyntaxError) {\n console.warn(\"Corrupt sessions.json, starting fresh:\", err.message);\n }\n }\n }\n\n saveSessionMap(): void {\n const map: Record<string, PersistedSession> = {};\n // Include unresumed pending sessions so they survive daemon restarts\n // where no messages were received for that channel\n for (const [channelId, entry] of this.pendingResumes) {\n map[channelId] = entry;\n }\n // Active sessions override any pending resume for the same channel\n for (const [channelId, session] of this.sessions) {\n map[channelId] = {\n sessionId: session.sessionId,\n agentName: session.agentName,\n };\n }\n try {\n mkdirSync(dirname(this.sessionsPath), { recursive: true });\n writeFileSync(this.sessionsPath, JSON.stringify(map, null, 2));\n } catch (err) {\n console.error(\"Failed to save session map:\", err);\n }\n }\n\n async prompt(channelId: string, text: string, agentName: string, agentConfig: AgentConfig, requestorId: string, mcpServers?: McpServerConfig[]): Promise<string> {\n console.log(`[MCP] prompt: channel=${channelId} mcpServers=${mcpServers ? `[${mcpServers.length} server(s)]` : \"undefined\"}`);\n const session = await this.getOrCreate(channelId, agentName, agentConfig, requestorId, mcpServers);\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 } catch (err) {\n // Connection broken or agent crashed — teardown to kill orphaned processes\n console.error(`Prompt error for channel ${session.channelId}, tearing down session:`, err);\n this.handlers.onPromptComplete(session.channelId, \"error\");\n this.teardown(session.channelId);\n throw err;\n } finally {\n session.prompting = false;\n // Process queue only if session is still alive (not torn down above)\n if (this.sessions.has(session.channelId)) {\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\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, agentName: string, agentConfig: AgentConfig, requestorId: string, mcpServers?: McpServerConfig[]): Promise<ManagedSession> {\n const existing = this.sessions.get(channelId);\n if (existing) {\n console.log(`[MCP] getOrCreate: reusing existing session for channel=${channelId} (mcpServers passed but ignored: ${mcpServers ? mcpServers.length : 0} server(s))`);\n return existing;\n }\n\n // Check for a pending resume from a previous daemon run\n const pending = this.pendingResumes.get(channelId);\n if (pending && pending.agentName === agentName) {\n this.pendingResumes.delete(channelId);\n try {\n return await this.resumeSession(channelId, agentName, agentConfig, requestorId, pending.sessionId, mcpServers);\n } catch (err) {\n console.warn(`Session resume failed for channel ${channelId}, creating new session:`, err);\n // Fall through to create a new session\n }\n } else if (pending) {\n // Agent name changed since last run — discard stale resume\n this.pendingResumes.delete(channelId);\n }\n\n // Evict oldest idle session(s) if at capacity\n this.evictIfNeeded();\n\n console.log(`[MCP] getOrCreate: creating new session for channel=${channelId} with ${mcpServers?.length ?? 0} MCP server(s)`);\n return this.createSession(channelId, agentName, agentConfig, requestorId, mcpServers);\n }\n\n private evictIfNeeded(): void {\n while (this.sessions.size >= this.maxConcurrentSessions) {\n // Find the least-recently-active non-prompting session\n let oldest: ManagedSession | null = null;\n for (const session of this.sessions.values()) {\n if (session.prompting) continue;\n if (!oldest || session.lastActivity < oldest.lastActivity) {\n oldest = session;\n }\n }\n if (!oldest) {\n // All sessions are actively prompting — evict the oldest anyway\n for (const session of this.sessions.values()) {\n if (!oldest || session.lastActivity < oldest.lastActivity) {\n oldest = session;\n }\n }\n }\n if (oldest) {\n console.log(`Evicting session for channel ${oldest.channelId} (lastActivity=${new Date(oldest.lastActivity).toISOString()}) to make room`);\n this.teardown(oldest.channelId);\n } else {\n break;\n }\n }\n }\n\n private async createSession(channelId: string, agentName: string, config: AgentConfig, requestorId: string, mcpServers?: McpServerConfig[]): Promise<ManagedSession> {\n const proc = spawn(config.command, config.args, {\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n cwd: config.cwd,\n detached: true, // Create new process group so we can kill the entire tree\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\", (code) => {\n const session = this.sessions.get(channelId);\n if (session?.process === proc) {\n clearTimeout(session.idleTimer);\n this.sessions.delete(channelId);\n if (code !== 0 && code !== null) {\n console.warn(`Agent process for channel ${channelId} exited with code ${code}`);\n }\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 newSessionPayload = {\n cwd: config.cwd,\n mcpServers: mcpServers ?? [],\n };\n console.log(`[MCP] createSession: calling newSession for channel=${channelId}`, JSON.stringify({\n cwd: newSessionPayload.cwd,\n mcpServerCount: newSessionPayload.mcpServers.length,\n mcpServerNames: newSessionPayload.mcpServers.map(s => s.name),\n }));\n const result = await connection.newSession(newSessionPayload);\n sessionId = result.sessionId;\n console.log(`[MCP] createSession: newSession succeeded, sessionId=${sessionId}`);\n } catch (err) {\n this.killProcessTree(proc);\n throw err;\n }\n\n const managed: ManagedSession = {\n channelId,\n agentName,\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 async resumeSession(channelId: string, agentName: string, config: AgentConfig, requestorId: string, previousSessionId: string, mcpServers?: McpServerConfig[]): Promise<ManagedSession> {\n const proc = spawn(config.command, config.args, {\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n cwd: config.cwd,\n detached: true, // Create new process group so we can kill the entire tree\n });\n\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\", (code) => {\n const session = this.sessions.get(channelId);\n if (session?.process === proc) {\n clearTimeout(session.idleTimer);\n this.sessions.delete(channelId);\n if (code !== 0 && code !== null) {\n console.warn(`Agent process for channel ${channelId} exited with code ${code}`);\n }\n }\n });\n\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 const initResult = 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 // Check if agent supports session resume\n const supportsResume = !!initResult.agentCapabilities?.sessionCapabilities?.resume;\n if (!supportsResume) {\n this.killProcessTree(proc);\n throw new Error(\"Agent does not support session resume\");\n }\n\n await connection.unstable_resumeSession({\n sessionId: previousSessionId,\n cwd: config.cwd,\n mcpServers: mcpServers ?? [],\n });\n sessionId = previousSessionId;\n console.log(`Resumed session ${sessionId} for channel ${channelId}`);\n } catch (err) {\n this.killProcessTree(proc);\n throw err;\n }\n\n const managed: ManagedSession = {\n channelId,\n agentName,\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 this.pendingResumes.delete(channelId);\n const session = this.sessions.get(channelId);\n if (!session) return;\n clearTimeout(session.idleTimer);\n // Kill process group to ensure MCP child processes are also terminated\n this.killProcessTree(session.process);\n this.sessions.delete(channelId);\n }\n\n private killProcessTree(proc: ChildProcess): void {\n if (!proc.pid) {\n proc.kill();\n return;\n }\n try {\n // Kill the entire process group (negative PID) to clean up MCP children\n process.kill(-proc.pid, \"SIGTERM\");\n } catch {\n // Process group kill failed (e.g. not a group leader) — fall back to direct kill\n try { proc.kill(\"SIGTERM\"); } catch { /* already dead */ }\n }\n // Force-kill after 5s if still alive\n setTimeout(() => {\n try { process.kill(-proc.pid!, \"SIGKILL\"); } catch { /* already dead */ }\n try { proc.kill(\"SIGKILL\"); } catch { /* already dead */ }\n }, 5000).unref();\n }\n\n teardownAll(): void {\n this.saveSessionMap();\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 DiffContent {\n path: string;\n oldText?: string | null;\n newText: string;\n}\n\nexport interface AcpEventHandlers {\n onToolCall(channelId: string, toolCallId: string, title: string, kind: string, status: string, diffs: DiffContent[], rawInput?: Record<string, unknown>): void;\n onToolCallUpdate(channelId: string, toolCallId: string, status: string, diffs: DiffContent[], rawInput?: Record<string, unknown>): 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 diffs: DiffContent[],\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 diffs = extractDiffs((params.toolCall as { content?: unknown }).content);\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 diffs,\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 const rawKeys = (update as Record<string, unknown>).rawInput;\n console.log(`[DEBUG tool_call] toolCallId=${update.toolCallId} title=${update.title} rawInputKeys=${rawKeys && typeof rawKeys === \"object\" ? Object.keys(rawKeys).join(\",\") : String(rawKeys)} updateKeys=${Object.keys(update).join(\",\")}`);\n const toolCallDiffs = extractDiffs(update.content);\n const rawVal = (update as Record<string, unknown>).rawInput;\n const rawInput = typeof rawVal === \"object\" && rawVal !== null && !Array.isArray(rawVal)\n ? (rawVal as Record<string, unknown>)\n : undefined;\n handlers.onToolCall(\n channelId,\n update.toolCallId,\n update.title ?? \"Unknown\",\n update.kind ?? \"other\",\n update.status ?? \"pending\",\n toolCallDiffs,\n rawInput,\n );\n break;\n }\n case \"tool_call_update\": {\n const updateRawKeys = (update as Record<string, unknown>).rawInput;\n console.log(`[DEBUG tool_call_update] toolCallId=${update.toolCallId} rawInputKeys=${updateRawKeys && typeof updateRawKeys === \"object\" ? Object.keys(updateRawKeys).join(\",\") : String(updateRawKeys)} updateKeys=${Object.keys(update).join(\",\")}`);\n const updateDiffs = extractDiffs(update.content);\n const updateRawVal = (update as Record<string, unknown>).rawInput;\n const updateRawInput = typeof updateRawVal === \"object\" && updateRawVal !== null && !Array.isArray(updateRawVal)\n ? (updateRawVal as Record<string, unknown>)\n : undefined;\n handlers.onToolCallUpdate(\n channelId,\n update.toolCallId,\n update.status ?? \"in_progress\",\n updateDiffs,\n updateRawInput,\n );\n break;\n }\n }\n },\n };\n}\n\nfunction extractDiffs(content: unknown): DiffContent[] {\n if (!Array.isArray(content)) return [];\n const diffs: DiffContent[] = [];\n for (const item of content) {\n if (item && typeof item === \"object\" && \"type\" in item && item.type === \"diff\") {\n const { path, oldText, newText } = item as Record<string, unknown>;\n if (typeof path !== \"string\" || typeof newText !== \"string\") continue;\n if (oldText !== undefined && oldText !== null && typeof oldText !== \"string\") continue;\n diffs.push({ path, oldText: (oldText as string | null) ?? null, newText });\n }\n }\n return diffs;\n}\n","import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n EmbedBuilder,\n type Message,\n type TextChannel,\n} from \"discord.js\";\nimport type { DiffContent } from \"./acp-client.js\";\nimport { formatDiff } from \"./message-bridge.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 diffs: DiffContent[] = [],\n timeoutMs = 14 * 60 * 1000,\n): Promise<{ outcome: \"selected\"; optionId: string; diffsSent?: boolean } | { outcome: \"cancelled\"; diffsSent?: boolean }> {\n if (options.length === 0) {\n return { outcome: \"cancelled\" };\n }\n\n // Send diffs before the permission embed so the user can review changes\n let diffsSent = false;\n const diffMsgs: Message[] = [];\n if (diffs.length > 0) {\n try {\n const diffMessages = formatDiff(diffs);\n for (const content of diffMessages) {\n diffMsgs.push(await channel.send(content));\n }\n diffsSent = true;\n } catch (err) {\n console.error(\"Failed to send permission diffs:\", err);\n }\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 const cleanup = () => {\n for (const dm of diffMsgs) dm.delete().catch(() => {});\n msg.delete().catch(() => msg.edit({ components: [] }).catch(() => {}));\n };\n\n collector.on(\"collect\", async (interaction) => {\n const optionId = interaction.customId.replace(\"perm_\", \"\");\n await interaction.deferUpdate();\n cleanup();\n collector.stop(\"selected\");\n resolve({ outcome: \"selected\", optionId, diffsSent });\n });\n\n collector.on(\"end\", (_collected, reason) => {\n if (reason !== \"selected\") {\n cleanup();\n resolve({ outcome: \"cancelled\", diffsSent });\n }\n });\n });\n}\n","import { createTwoFilesPatch } from \"diff\";\nimport type { DiffContent } from \"./acp-client.js\";\n\nconst DISCORD_MAX_LENGTH = 2000;\nconst MAX_DIFF_LINES = 150;\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; rawInput?: Record<string, unknown> }>,\n): string {\n const lines: string[] = [];\n for (const [, tool] of tools) {\n const detail = extractToolDetail(tool.rawInput) ?? extractDetailFromTitle(tool.title);\n const suffix = detail ? ` · \\`${detail}\\`` : \"\";\n lines.push(`${STATUS_ICONS[tool.status]} ${tool.title}${suffix}`);\n }\n return lines.join(\"\\n\");\n}\n\nconst MAX_DETAIL_LENGTH = 80;\n\n// Only display values from known-safe fields to avoid leaking secrets\nconst SAFE_FIELDS = [\"command\", \"file_path\", \"pattern\", \"query\", \"path\", \"url\", \"description\"];\n\n// Substrings that mark a field as sensitive — matches \"secret_key\", \"api_key\", \"access_token\", etc.\nconst BLOCKED_SUBSTRINGS = [\"token\", \"secret\", \"password\", \"key\", \"content\", \"new_string\", \"old_string\", \"credential\", \"auth\"];\n\nfunction isBlockedField(name: string): boolean {\n const lower = name.toLowerCase();\n return BLOCKED_SUBSTRINGS.some((sub) => lower.includes(sub));\n}\n\nfunction extractToolDetail(rawInput?: Record<string, unknown>): string | null {\n if (!rawInput) return null;\n\n // Try known safe fields first\n for (const field of SAFE_FIELDS) {\n if (typeof rawInput[field] === \"string\" && rawInput[field]) {\n return truncate(sanitizeDetail(rawInput[field] as string), MAX_DETAIL_LENGTH);\n }\n }\n\n // Fallback: pick the first short string value from non-blocked fields\n for (const [fieldName, value] of Object.entries(rawInput)) {\n if (isBlockedField(fieldName)) continue;\n if (SAFE_FIELDS.includes(fieldName)) continue; // already checked\n if (typeof value === \"string\" && value.length > 0 && value.length < 100) {\n return truncate(sanitizeDetail(value), MAX_DETAIL_LENGTH);\n }\n }\n\n return null;\n}\n\n/**\n * Extract useful detail from the tool title when rawInput is unavailable.\n * Titles often look like \"Read /path/to/file\" or \"Bash: ls -la\" —\n * we extract the part after the first space or colon.\n */\nfunction extractDetailFromTitle(title: string): string | null {\n if (!title) return null;\n\n // Pattern: \"ToolName /path/or/arg\" or \"ToolName: something\"\n const colonMatch = title.match(/^[^:]+:\\s*(.+)/);\n if (colonMatch) {\n return truncate(sanitizeDetail(colonMatch[1].trim()), MAX_DETAIL_LENGTH);\n }\n\n // Pattern: \"ToolName /some/path\" — extract if second part looks like a path or command\n const spaceIdx = title.indexOf(\" \");\n if (spaceIdx > 0 && spaceIdx < title.length - 1) {\n const rest = title.slice(spaceIdx + 1).trim();\n // Only extract if it looks like a path, URL, or meaningful argument\n if (rest.startsWith(\"/\") || rest.startsWith(\"./\") || rest.startsWith(\"http\") || rest.includes(\".\")) {\n return truncate(sanitizeDetail(rest), MAX_DETAIL_LENGTH);\n }\n }\n\n return null;\n}\n\nfunction sanitizeDetail(text: string): string {\n return text.replace(/`/g, \"'\");\n}\n\nfunction truncate(text: string, max: number): string {\n // Use first line only for multiline values\n const firstLine = text.split(\"\\n\")[0];\n if (firstLine.length <= max) return firstLine;\n return firstLine.slice(0, max - 1) + \"\\u2026\";\n}\n\nexport function formatDiff(diffs: DiffContent[], maxLines = MAX_DIFF_LINES): string[] {\n if (diffs.length === 0) return [];\n\n const parts: string[] = [];\n\n for (const d of diffs) {\n const fileName = d.path.split(\"/\").pop() ?? d.path;\n const oldText = d.oldText ?? \"\";\n const patch = createTwoFilesPatch(\n d.oldText == null ? \"/dev/null\" : d.path,\n d.path,\n oldText,\n d.newText,\n undefined,\n undefined,\n { context: 3 },\n );\n\n // Remove the first two header lines (Index: and ===) if present, keep ---/+++ and hunks\n const patchLines = patch.split(\"\\n\");\n // Find the first --- line to start from\n const startIdx = patchLines.findIndex((l) => l.startsWith(\"---\"));\n const diffLines = startIdx >= 0 ? patchLines.slice(startIdx) : patchLines;\n\n let truncated = false;\n let displayLines = diffLines;\n if (diffLines.length > maxLines) {\n displayLines = diffLines.slice(0, maxLines);\n truncated = true;\n }\n\n let block = `**${fileName}**\\n\\`\\`\\`diff\\n${displayLines.join(\"\\n\")}\\n\\`\\`\\``;\n if (truncated) {\n block += `\\n*... ${diffLines.length - maxLines} more lines*`;\n }\n\n parts.push(block);\n }\n\n // Join all diff blocks and split for Discord's message limit\n const fullMessage = parts.join(\"\\n\\n\");\n return splitMessage(fullMessage);\n}\n","import { createServer, type Server, type Socket } from \"node:net\";\nimport { existsSync, unlinkSync, mkdirSync, chmodSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport const DEFAULT_IPC_SOCKET_PATH = join(homedir(), \".acp-discord\", \"ipc.sock\");\n\nexport interface BindChannelOpts {\n cwd?: string;\n autoReply?: boolean;\n discordTools?: boolean;\n}\n\nexport interface BindingInfo {\n channelId: string;\n agent: string;\n cwd?: string;\n autoReply: boolean;\n discordTools: boolean;\n}\n\nexport interface TaskCrudResult {\n task?: unknown;\n tasks?: unknown[];\n logs?: unknown[];\n error?: string;\n deleted?: boolean;\n}\n\nexport interface IpcHandler {\n registerChannel(channelId: string, agentName: string, autoReply: boolean): void;\n unregisterChannel(channelId: string): void;\n confirmAction(sourceChannelId: string, description: string, details: string): Promise<boolean>;\n bindChannel(channelId: string, agentName: string, opts: BindChannelOpts, guildId?: string): Promise<{ success: boolean; error?: string }>;\n unbindChannel(channelId: string, guildId?: string): Promise<{ success: boolean; error?: string }>;\n listBindings(guildId?: string): Promise<{ success: boolean; bindings: BindingInfo[] }>;\n createTask?(params: Record<string, unknown>): TaskCrudResult;\n listTasks?(channelId?: string): TaskCrudResult;\n updateTask?(taskId: string, updates: Record<string, unknown>, channelId?: string): TaskCrudResult;\n deleteTask?(taskId: string, channelId?: string): TaskCrudResult;\n getTaskLogs?(taskId?: string, channelId?: string): TaskCrudResult;\n}\n\ninterface IpcMessage {\n action: string;\n requestId?: string;\n channelId?: string;\n agentName?: string;\n autoReply?: boolean;\n sourceChannelId?: string;\n description?: string;\n details?: string;\n cwd?: string;\n discordTools?: boolean;\n guildId?: string;\n // Task-related fields\n taskId?: string;\n prompt?: string;\n scheduleType?: string;\n scheduleValue?: string;\n notify?: string;\n updates?: Record<string, unknown>;\n}\n\nexport class IpcServer {\n private server: Server | null = null;\n private socketPath: string;\n private handler: IpcHandler;\n private connections = new Set<Socket>();\n\n constructor(handler: IpcHandler, socketPath = DEFAULT_IPC_SOCKET_PATH) {\n this.handler = handler;\n this.socketPath = socketPath;\n }\n\n async start(): Promise<void> {\n // Clean up stale socket\n if (existsSync(this.socketPath)) {\n unlinkSync(this.socketPath);\n }\n mkdirSync(dirname(this.socketPath), { recursive: true });\n\n return new Promise((resolve, reject) => {\n this.server = createServer((socket) => this.handleConnection(socket));\n\n this.server.on(\"error\", (err) => {\n console.error(\"IPC server error:\", err);\n reject(err);\n });\n\n this.server.listen(this.socketPath, () => {\n // Restrict socket to owner-only access\n chmodSync(this.socketPath, 0o600);\n console.log(`IPC server listening on ${this.socketPath}`);\n resolve();\n });\n });\n }\n\n private handleConnection(socket: Socket): void {\n this.connections.add(socket);\n let buffer = \"\";\n\n socket.on(\"data\", (data) => {\n buffer += data.toString();\n // Process newline-delimited JSON messages\n let newlineIdx: number;\n while ((newlineIdx = buffer.indexOf(\"\\n\")) !== -1) {\n const line = buffer.slice(0, newlineIdx).trim();\n buffer = buffer.slice(newlineIdx + 1);\n if (line) {\n this.processMessage(socket, line).catch((err) => {\n console.error(\"IPC message processing error:\", err);\n });\n }\n }\n });\n\n socket.on(\"close\", () => {\n this.connections.delete(socket);\n });\n\n socket.on(\"error\", (err) => {\n console.error(\"IPC connection error:\", err);\n this.connections.delete(socket);\n });\n }\n\n private async processMessage(socket: Socket, raw: string): Promise<void> {\n let msg: IpcMessage;\n try {\n msg = JSON.parse(raw);\n } catch {\n console.error(\"IPC: invalid JSON:\", raw);\n return;\n }\n\n switch (msg.action) {\n case \"register_channel\":\n if (msg.channelId && msg.agentName) {\n this.handler.registerChannel(msg.channelId, msg.agentName, msg.autoReply ?? true);\n }\n break;\n\n case \"unregister_channel\":\n if (msg.channelId) {\n this.handler.unregisterChannel(msg.channelId);\n }\n break;\n\n case \"confirm_action\":\n if (msg.requestId && msg.sourceChannelId && msg.description) {\n const approved = await this.handler.confirmAction(\n msg.sourceChannelId,\n msg.description,\n msg.details ?? \"\",\n );\n const response = JSON.stringify({ requestId: msg.requestId, approved }) + \"\\n\";\n socket.write(response);\n }\n break;\n\n case \"bind_channel\": {\n if (msg.requestId && msg.channelId && msg.agentName) {\n const result = await this.handler.bindChannel(msg.channelId, msg.agentName, {\n cwd: msg.cwd,\n autoReply: msg.autoReply,\n discordTools: msg.discordTools,\n }, msg.guildId);\n socket.write(JSON.stringify({ requestId: msg.requestId, ...result }) + \"\\n\");\n }\n break;\n }\n\n case \"unbind_channel\": {\n if (msg.requestId && msg.channelId) {\n const result = await this.handler.unbindChannel(msg.channelId, msg.guildId);\n socket.write(JSON.stringify({ requestId: msg.requestId, ...result }) + \"\\n\");\n }\n break;\n }\n\n case \"list_bindings\": {\n if (msg.requestId) {\n const result = await this.handler.listBindings(msg.guildId);\n socket.write(JSON.stringify({ requestId: msg.requestId, ...result }) + \"\\n\");\n }\n break;\n }\n\n case \"create_task\": {\n if (!msg.requestId || !this.handler.createTask) break;\n try {\n const result = this.handler.createTask({\n channel_id: msg.channelId ?? msg.sourceChannelId ?? \"\",\n agent_name: msg.agentName ?? \"unknown\",\n prompt: msg.prompt ?? \"\",\n schedule_type: msg.scheduleType ?? \"once\",\n schedule_value: msg.scheduleValue ?? \"\",\n description: msg.description,\n notify: msg.notify,\n created_by: \"agent\",\n });\n socket.write(JSON.stringify({ requestId: msg.requestId, ...result }) + \"\\n\");\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n socket.write(JSON.stringify({ requestId: msg.requestId, error: errMsg }) + \"\\n\");\n }\n break;\n }\n\n case \"list_tasks\": {\n if (!msg.requestId || !this.handler.listTasks) break;\n const listResult = this.handler.listTasks(msg.channelId);\n socket.write(JSON.stringify({ requestId: msg.requestId, ...listResult }) + \"\\n\");\n break;\n }\n\n case \"update_task\": {\n if (!msg.requestId || !msg.taskId || !this.handler.updateTask) break;\n try {\n const updateResult = this.handler.updateTask(msg.taskId, msg.updates ?? {}, msg.channelId);\n socket.write(JSON.stringify({ requestId: msg.requestId, ...updateResult }) + \"\\n\");\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n socket.write(JSON.stringify({ requestId: msg.requestId, error: errMsg }) + \"\\n\");\n }\n break;\n }\n\n case \"delete_task\": {\n if (!msg.requestId || !msg.taskId || !this.handler.deleteTask) break;\n const deleteResult = this.handler.deleteTask(msg.taskId, msg.channelId);\n socket.write(JSON.stringify({ requestId: msg.requestId, ...deleteResult }) + \"\\n\");\n break;\n }\n\n case \"get_task_logs\": {\n if (!msg.requestId || !this.handler.getTaskLogs) break;\n const logsResult = this.handler.getTaskLogs(msg.taskId, msg.channelId);\n socket.write(JSON.stringify({ requestId: msg.requestId, ...logsResult }) + \"\\n\");\n break;\n }\n\n default:\n console.error(\"IPC: unknown action:\", msg.action);\n }\n }\n\n stop(): void {\n for (const conn of this.connections) {\n conn.destroy();\n }\n this.connections.clear();\n if (this.server) {\n this.server.close();\n this.server = null;\n }\n // Clean up socket file\n if (existsSync(this.socketPath)) {\n try {\n unlinkSync(this.socketPath);\n } catch {\n // ignore\n }\n }\n }\n}\n","import { readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { randomUUID } from \"node:crypto\";\nimport { CronExpressionParser } from \"cron-parser\";\n\nconst DEFAULT_DATA_DIR = join(homedir(), \".acp-discord\");\nconst MAX_LOGS_PER_TASK = 50;\n\nexport interface ScheduledTask {\n id: string;\n channel_id: string;\n agent_name: string;\n prompt: string;\n description: string;\n schedule_type: \"once\" | \"cron\" | \"interval\";\n schedule_value: string;\n status: \"active\" | \"paused\" | \"completed\";\n notify: \"always\" | \"on_error\" | \"never\";\n next_run: string | null;\n last_run: string | null;\n created_by: string;\n created_at: string;\n}\n\nexport interface TaskRunLog {\n id: string;\n task_id: string;\n task_description: string;\n started_at: string;\n completed_at: string;\n duration_ms: number;\n status: \"success\" | \"error\";\n output: string;\n error: string | null;\n}\n\nexport interface TaskRunInput {\n startedAt: Date;\n completedAt: Date;\n durationMs: number;\n status: \"success\" | \"error\";\n output: string;\n error: string | null;\n}\n\nexport interface CreateTaskParams {\n channel_id: string;\n agent_name: string;\n prompt: string;\n description?: string;\n schedule_type: \"once\" | \"cron\" | \"interval\";\n schedule_value: string;\n notify?: \"always\" | \"on_error\" | \"never\";\n created_by: string;\n}\n\nexport type OnTaskFire = (task: ScheduledTask) => Promise<void>;\n\nexport class TaskScheduler {\n private tasks: ScheduledTask[] = [];\n private logs: TaskRunLog[] = [];\n private interval: NodeJS.Timeout | null = null;\n private onTaskFire: OnTaskFire;\n private tasksPath: string;\n private logsPath: string;\n private dataDir: string;\n private inFlight = new Set<string>(); // task IDs currently running\n\n constructor(onTaskFire: OnTaskFire, dataDir?: string) {\n this.onTaskFire = onTaskFire;\n this.dataDir = dataDir ?? DEFAULT_DATA_DIR;\n this.tasksPath = join(this.dataDir, \"scheduled-tasks.json\");\n this.logsPath = join(this.dataDir, \"task-run-logs.json\");\n }\n\n start(): void {\n this.load();\n this.interval = setInterval(() => {\n this.poll().catch((err) => {\n console.error(\"TaskScheduler poll error:\", err);\n });\n }, 15_000);\n console.log(`TaskScheduler started with ${this.tasks.length} task(s)`);\n }\n\n stop(): void {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = null;\n }\n this.save();\n }\n\n createTask(params: CreateTaskParams): ScheduledTask {\n const now = new Date().toISOString();\n const task: ScheduledTask = {\n id: randomUUID(),\n channel_id: params.channel_id,\n agent_name: params.agent_name,\n prompt: params.prompt,\n description: params.description ?? params.prompt.slice(0, 80),\n schedule_type: params.schedule_type,\n schedule_value: params.schedule_value,\n status: \"active\",\n notify: params.notify ?? \"on_error\",\n next_run: null,\n last_run: null,\n created_by: params.created_by,\n created_at: now,\n };\n\n task.next_run = this.computeNextRun(task);\n if (!task.next_run) {\n throw new Error(`Invalid schedule: cannot compute next run for ${params.schedule_type} \"${params.schedule_value}\"`);\n }\n\n this.tasks.push(task);\n this.save();\n return task;\n }\n\n listTasks(channelId?: string): ScheduledTask[] {\n if (channelId) {\n return this.tasks.filter((t) => t.channel_id === channelId);\n }\n return [...this.tasks];\n }\n\n updateTask(\n id: string,\n updates: Partial<Pick<ScheduledTask, \"status\" | \"prompt\" | \"schedule_type\" | \"schedule_value\" | \"notify\" | \"description\">>,\n channelId?: string,\n ): ScheduledTask | null {\n const task = this.tasks.find((t) => t.id === id);\n if (!task) return null;\n if (channelId && task.channel_id !== channelId) return null;\n\n if (updates.status !== undefined) task.status = updates.status;\n if (updates.prompt !== undefined) task.prompt = updates.prompt;\n if (updates.description !== undefined) task.description = updates.description;\n if (updates.notify !== undefined) task.notify = updates.notify;\n\n if (updates.schedule_type !== undefined || updates.schedule_value !== undefined) {\n // Save originals for rollback on validation failure\n const origType = task.schedule_type;\n const origValue = task.schedule_value;\n const origNextRun = task.next_run;\n\n if (updates.schedule_type !== undefined) task.schedule_type = updates.schedule_type;\n if (updates.schedule_value !== undefined) task.schedule_value = updates.schedule_value;\n const nextRun = this.computeNextRun(task);\n if (!nextRun) {\n // Rollback\n task.schedule_type = origType;\n task.schedule_value = origValue;\n task.next_run = origNextRun;\n throw new Error(`Invalid schedule: cannot compute next run for ${task.schedule_type} \"${task.schedule_value}\"`);\n }\n task.next_run = nextRun;\n }\n\n this.save();\n return task;\n }\n\n deleteTask(id: string, channelId?: string): boolean {\n const idx = this.tasks.findIndex((t) => t.id === id);\n if (idx === -1) return false;\n if (channelId && this.tasks[idx].channel_id !== channelId) return false;\n this.tasks.splice(idx, 1);\n this.save();\n return true;\n }\n\n getTaskLogs(taskId?: string, channelId?: string): TaskRunLog[] {\n let logs = this.logs;\n if (taskId) {\n // Verify the task belongs to the channel if channelId specified\n if (channelId) {\n const task = this.tasks.find((t) => t.id === taskId);\n if (task && task.channel_id !== channelId) return [];\n }\n logs = logs.filter((l) => l.task_id === taskId);\n } else if (channelId) {\n // Only return logs for tasks belonging to this channel\n const channelTaskIds = new Set(\n this.tasks.filter((t) => t.channel_id === channelId).map((t) => t.id),\n );\n logs = logs.filter((l) => channelTaskIds.has(l.task_id));\n }\n return [...logs];\n }\n\n logRun(taskId: string, result: TaskRunInput): void {\n const task = this.tasks.find((t) => t.id === taskId);\n const log: TaskRunLog = {\n id: randomUUID(),\n task_id: taskId,\n task_description: task?.description ?? \"unknown\",\n started_at: result.startedAt.toISOString(),\n completed_at: result.completedAt.toISOString(),\n duration_ms: result.durationMs,\n status: result.status,\n output: result.output.slice(0, 4000),\n error: result.error,\n };\n this.logs.push(log);\n\n // Trim logs per task\n const taskLogs = this.logs.filter((l) => l.task_id === taskId);\n if (taskLogs.length > MAX_LOGS_PER_TASK) {\n const idsToRemove = new Set(\n taskLogs\n .slice(0, taskLogs.length - MAX_LOGS_PER_TASK)\n .map((l) => l.id),\n );\n this.logs = this.logs.filter((l) => !idsToRemove.has(l.id));\n }\n\n this.saveLogs();\n }\n\n computeNextRun(task: ScheduledTask): string | null {\n const now = new Date();\n\n switch (task.schedule_type) {\n case \"once\": {\n const date = new Date(task.schedule_value);\n if (isNaN(date.getTime())) return null;\n return date > now ? date.toISOString() : date.toISOString();\n }\n\n case \"cron\": {\n try {\n const expr = CronExpressionParser.parse(task.schedule_value, { currentDate: now });\n const next = expr.next();\n return next.toDate().toISOString();\n } catch {\n return null;\n }\n }\n\n case \"interval\": {\n const seconds = parseInt(task.schedule_value, 10);\n if (isNaN(seconds) || seconds <= 0) return null;\n // Anchor to last_run to prevent drift\n const anchor = task.last_run ? new Date(task.last_run) : now;\n const next = new Date(anchor.getTime() + seconds * 1000);\n // If next is in the past, advance forward\n if (next <= now) {\n const elapsed = now.getTime() - anchor.getTime();\n const intervals = Math.ceil(elapsed / (seconds * 1000));\n return new Date(anchor.getTime() + intervals * seconds * 1000).toISOString();\n }\n return next.toISOString();\n }\n\n default:\n return null;\n }\n }\n\n private async poll(): Promise<void> {\n const now = new Date();\n\n for (const task of this.tasks) {\n if (task.status !== \"active\") continue;\n if (!task.next_run) continue;\n if (this.inFlight.has(task.id)) continue; // skip if already running\n\n const nextRun = new Date(task.next_run);\n if (nextRun > now) continue;\n\n // Mark as firing\n task.last_run = now.toISOString();\n\n if (task.schedule_type === \"once\") {\n task.status = \"completed\";\n } else {\n task.next_run = this.computeNextRun(task);\n }\n this.save();\n\n // Fire asynchronously with in-flight guard\n this.inFlight.add(task.id);\n this.onTaskFire(task)\n .catch((err) => {\n console.error(`Task fire error for ${task.id}:`, err);\n })\n .finally(() => {\n this.inFlight.delete(task.id);\n });\n }\n }\n\n private load(): void {\n mkdirSync(this.dataDir, { recursive: true });\n try {\n const data = readFileSync(this.tasksPath, \"utf-8\");\n this.tasks = JSON.parse(data);\n } catch {\n this.tasks = [];\n }\n try {\n const data = readFileSync(this.logsPath, \"utf-8\");\n this.logs = JSON.parse(data);\n } catch {\n this.logs = [];\n }\n }\n\n private save(): void {\n mkdirSync(this.dataDir, { recursive: true });\n writeFileSync(this.tasksPath, JSON.stringify(this.tasks, null, 2));\n }\n\n private saveLogs(): void {\n mkdirSync(this.dataDir, { recursive: true });\n writeFileSync(this.logsPath, JSON.stringify(this.logs, null, 2));\n }\n}\n","import { spawn } from \"node:child_process\";\nimport { Readable, Writable } from \"node:stream\";\nimport { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } from \"@agentclientprotocol/sdk\";\nimport type { Client, RequestPermissionResponse, SessionNotification } from \"@agentclientprotocol/sdk\";\nimport type { AgentConfig } from \"../shared/types.js\";\nimport type { McpServerConfig } from \"./session-manager.js\";\n\nexport interface TaskRunResult {\n output: string;\n stopReason: string;\n error: string | null;\n}\n\nexport async function runTask(\n agentConfig: AgentConfig,\n prompt: string,\n mcpServers: McpServerConfig[],\n): Promise<TaskRunResult> {\n const proc = spawn(agentConfig.command, agentConfig.args, {\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n cwd: agentConfig.cwd,\n });\n\n let output = \"\";\n let error: string | null = null;\n\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: Client = {\n async requestPermission(): Promise<RequestPermissionResponse> {\n // Auto-cancel permission requests in scheduled tasks\n return { outcome: { outcome: \"cancelled\" } };\n },\n async sessionUpdate(params: SessionNotification): Promise<void> {\n const update = params.update;\n if (update.sessionUpdate === \"agent_message_chunk\") {\n if (update.content.type === \"text\") {\n output += update.content.text;\n }\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: true,\n },\n clientInfo: {\n name: \"acp-discord-task-runner\",\n title: \"ACP Discord Task Runner\",\n version: \"0.1.0\",\n },\n });\n\n const { sessionId } = await connection.newSession({\n cwd: agentConfig.cwd,\n mcpServers,\n });\n\n const result = await connection.prompt({\n sessionId,\n prompt: [{ type: \"text\", text: prompt }],\n });\n\n proc.kill();\n return { output, stopReason: result.stopReason, error: null };\n } catch (err) {\n proc.kill();\n error = err instanceof Error ? err.message : String(err);\n return { output, stopReason: \"error\", error };\n }\n}\n"],"mappings":"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","names":["join","homedir","ActionRowBuilder","ButtonBuilder","ButtonStyle","EmbedBuilder","dirname","existsSync","mkdirSync","dirname","readFileSync","writeFileSync","mkdirSync","join","homedir","spawn","Readable","Writable","ClientSideConnection","ndJsonStream","PROTOCOL_VERSION","EmbedBuilder","ActionRowBuilder","ButtonBuilder","ButtonStyle","dirname","existsSync","join","homedir"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acp-discord",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Discord bot that wraps ACP protocol for coding agents",
5
5
  "type": "module",
6
6
  "bin": {