acp-discord 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/daemon.js CHANGED
@@ -23,7 +23,8 @@ import {
23
23
  ButtonStyle as ButtonStyle2,
24
24
  EmbedBuilder as EmbedBuilder2
25
25
  } from "discord.js";
26
- import { resolve as resolvePath, dirname as dirname2 } from "path";
26
+ import { resolve as resolvePath, dirname as dirname3 } from "path";
27
+ import { existsSync as existsSync2 } from "fs";
27
28
  import { fileURLToPath } from "url";
28
29
 
29
30
  // src/daemon/channel-router.ts
@@ -55,6 +56,8 @@ var ChannelRouter = class {
55
56
 
56
57
  // src/daemon/session-manager.ts
57
58
  import { spawn } from "child_process";
59
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
60
+ import { dirname } from "path";
58
61
  import { Readable, Writable } from "stream";
59
62
  import { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
60
63
 
@@ -142,11 +145,51 @@ function extractDiffs(content) {
142
145
  var SessionManager = class {
143
146
  sessions = /* @__PURE__ */ new Map();
144
147
  handlers;
145
- constructor(handlers) {
148
+ sessionsPath;
149
+ pendingResumes = /* @__PURE__ */ new Map();
150
+ constructor(handlers, sessionsPath) {
146
151
  this.handlers = handlers;
152
+ this.sessionsPath = sessionsPath;
153
+ this.loadSessionMap();
154
+ }
155
+ loadSessionMap() {
156
+ try {
157
+ const data = readFileSync(this.sessionsPath, "utf-8");
158
+ const map = JSON.parse(data);
159
+ for (const [channelId, entry] of Object.entries(map)) {
160
+ this.pendingResumes.set(channelId, entry);
161
+ }
162
+ if (this.pendingResumes.size > 0) {
163
+ console.log(`Loaded ${this.pendingResumes.size} session(s) for lazy resume`);
164
+ }
165
+ } catch (err) {
166
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") return;
167
+ if (err instanceof SyntaxError) {
168
+ console.warn("Corrupt sessions.json, starting fresh:", err.message);
169
+ }
170
+ }
147
171
  }
148
- async prompt(channelId, text, agentConfig, requestorId, mcpServers) {
149
- const session = await this.getOrCreate(channelId, agentConfig, requestorId, mcpServers);
172
+ saveSessionMap() {
173
+ const map = {};
174
+ for (const [channelId, entry] of this.pendingResumes) {
175
+ map[channelId] = entry;
176
+ }
177
+ for (const [channelId, session] of this.sessions) {
178
+ map[channelId] = {
179
+ sessionId: session.sessionId,
180
+ agentName: session.agentName
181
+ };
182
+ }
183
+ try {
184
+ mkdirSync(dirname(this.sessionsPath), { recursive: true });
185
+ writeFileSync(this.sessionsPath, JSON.stringify(map, null, 2));
186
+ } catch (err) {
187
+ console.error("Failed to save session map:", err);
188
+ }
189
+ }
190
+ async prompt(channelId, text, agentName, agentConfig, requestorId, mcpServers) {
191
+ console.log(`[MCP] prompt: channel=${channelId} mcpServers=${mcpServers ? `[${mcpServers.length} server(s)]` : "undefined"}`);
192
+ const session = await this.getOrCreate(channelId, agentName, agentConfig, requestorId, mcpServers);
150
193
  session.lastActivity = Date.now();
151
194
  this.resetIdleTimer(session, agentConfig.idle_timeout);
152
195
  if (session.prompting) {
@@ -181,12 +224,27 @@ var SessionManager = class {
181
224
  session.connection.cancel({ sessionId: session.sessionId });
182
225
  }
183
226
  }
184
- async getOrCreate(channelId, agentConfig, requestorId, mcpServers) {
227
+ async getOrCreate(channelId, agentName, agentConfig, requestorId, mcpServers) {
185
228
  const existing = this.sessions.get(channelId);
186
- if (existing) return existing;
187
- return this.createSession(channelId, agentConfig, requestorId, mcpServers);
229
+ if (existing) {
230
+ console.log(`[MCP] getOrCreate: reusing existing session for channel=${channelId} (mcpServers passed but ignored: ${mcpServers ? mcpServers.length : 0} server(s))`);
231
+ return existing;
232
+ }
233
+ const pending = this.pendingResumes.get(channelId);
234
+ if (pending && pending.agentName === agentName) {
235
+ this.pendingResumes.delete(channelId);
236
+ try {
237
+ return await this.resumeSession(channelId, agentName, agentConfig, requestorId, pending.sessionId, mcpServers);
238
+ } catch (err) {
239
+ console.warn(`Session resume failed for channel ${channelId}, creating new session:`, err);
240
+ }
241
+ } else if (pending) {
242
+ this.pendingResumes.delete(channelId);
243
+ }
244
+ console.log(`[MCP] getOrCreate: creating new session for channel=${channelId} with ${mcpServers?.length ?? 0} MCP server(s)`);
245
+ return this.createSession(channelId, agentName, agentConfig, requestorId, mcpServers);
188
246
  }
189
- async createSession(channelId, config, requestorId, mcpServers) {
247
+ async createSession(channelId, agentName, config, requestorId, mcpServers) {
190
248
  const proc = spawn(config.command, config.args, {
191
249
  stdio: ["pipe", "pipe", "inherit"],
192
250
  cwd: config.cwd
@@ -232,17 +290,102 @@ var SessionManager = class {
232
290
  version: "0.1.0"
233
291
  }
234
292
  });
235
- const result = await connection.newSession({
293
+ const newSessionPayload = {
236
294
  cwd: config.cwd,
237
295
  mcpServers: mcpServers ?? []
238
- });
296
+ };
297
+ console.log(`[MCP] createSession: calling newSession for channel=${channelId}`, JSON.stringify({
298
+ cwd: newSessionPayload.cwd,
299
+ mcpServerCount: newSessionPayload.mcpServers.length,
300
+ mcpServerNames: newSessionPayload.mcpServers.map((s) => s.name)
301
+ }));
302
+ const result = await connection.newSession(newSessionPayload);
239
303
  sessionId = result.sessionId;
304
+ console.log(`[MCP] createSession: newSession succeeded, sessionId=${sessionId}`);
240
305
  } catch (err) {
241
306
  proc.kill();
242
307
  throw err;
243
308
  }
244
309
  const managed = {
245
310
  channelId,
311
+ agentName,
312
+ process: proc,
313
+ connection,
314
+ sessionId,
315
+ lastActivity: Date.now(),
316
+ idleTimer: this.startIdleTimer(channelId, config.idle_timeout),
317
+ prompting: false,
318
+ queue: [],
319
+ activePromptRequestorId: requestorId
320
+ };
321
+ this.sessions.set(channelId, managed);
322
+ return managed;
323
+ }
324
+ async resumeSession(channelId, agentName, config, requestorId, previousSessionId, mcpServers) {
325
+ const proc = spawn(config.command, config.args, {
326
+ stdio: ["pipe", "pipe", "inherit"],
327
+ cwd: config.cwd
328
+ });
329
+ proc.on("error", (err) => {
330
+ console.error(`Agent process error for channel ${channelId}:`, err);
331
+ const session = this.sessions.get(channelId);
332
+ if (session?.process === proc) {
333
+ clearTimeout(session.idleTimer);
334
+ this.sessions.delete(channelId);
335
+ }
336
+ });
337
+ proc.on("exit", (code) => {
338
+ const session = this.sessions.get(channelId);
339
+ if (session?.process === proc) {
340
+ clearTimeout(session.idleTimer);
341
+ this.sessions.delete(channelId);
342
+ if (code !== 0 && code !== null) {
343
+ console.warn(`Agent process for channel ${channelId} exited with code ${code}`);
344
+ }
345
+ }
346
+ });
347
+ let connection;
348
+ let sessionId;
349
+ try {
350
+ const stream = ndJsonStream(
351
+ Writable.toWeb(proc.stdin),
352
+ Readable.toWeb(proc.stdout)
353
+ );
354
+ const client = createAcpClient(channelId, this.handlers, () => {
355
+ return this.sessions.get(channelId)?.activePromptRequestorId ?? requestorId;
356
+ });
357
+ connection = new ClientSideConnection((_agent) => client, stream);
358
+ const initResult = await connection.initialize({
359
+ protocolVersion: PROTOCOL_VERSION,
360
+ clientCapabilities: {
361
+ fs: { readTextFile: true, writeTextFile: true },
362
+ terminal: true
363
+ },
364
+ clientInfo: {
365
+ name: "acp-discord",
366
+ title: "ACP Discord Bot",
367
+ version: "0.1.0"
368
+ }
369
+ });
370
+ const supportsResume = !!initResult.agentCapabilities?.sessionCapabilities?.resume;
371
+ if (!supportsResume) {
372
+ proc.kill();
373
+ throw new Error("Agent does not support session resume");
374
+ }
375
+ await connection.unstable_resumeSession({
376
+ sessionId: previousSessionId,
377
+ cwd: config.cwd,
378
+ mcpServers: mcpServers ?? []
379
+ });
380
+ sessionId = previousSessionId;
381
+ console.log(`Resumed session ${sessionId} for channel ${channelId}`);
382
+ } catch (err) {
383
+ proc.kill();
384
+ throw err;
385
+ }
386
+ const managed = {
387
+ channelId,
388
+ agentName,
246
389
  process: proc,
247
390
  connection,
248
391
  sessionId,
@@ -263,6 +406,7 @@ var SessionManager = class {
263
406
  session.idleTimer = this.startIdleTimer(session.channelId, timeoutSec);
264
407
  }
265
408
  teardown(channelId) {
409
+ this.pendingResumes.delete(channelId);
266
410
  const session = this.sessions.get(channelId);
267
411
  if (!session) return;
268
412
  clearTimeout(session.idleTimer);
@@ -270,6 +414,7 @@ var SessionManager = class {
270
414
  this.sessions.delete(channelId);
271
415
  }
272
416
  teardownAll() {
417
+ this.saveSessionMap();
273
418
  for (const channelId of this.sessions.keys()) {
274
419
  this.teardown(channelId);
275
420
  }
@@ -481,8 +626,8 @@ async function sendPermissionRequest(channel, toolTitle, toolKind, options, requ
481
626
 
482
627
  // src/daemon/ipc-server.ts
483
628
  import { createServer } from "net";
484
- import { existsSync, unlinkSync, mkdirSync, chmodSync } from "fs";
485
- import { dirname } from "path";
629
+ import { existsSync, unlinkSync, mkdirSync as mkdirSync2, chmodSync } from "fs";
630
+ import { dirname as dirname2 } from "path";
486
631
  import { homedir } from "os";
487
632
  import { join } from "path";
488
633
  var DEFAULT_IPC_SOCKET_PATH = join(homedir(), ".acp-discord", "ipc.sock");
@@ -499,7 +644,7 @@ var IpcServer = class {
499
644
  if (existsSync(this.socketPath)) {
500
645
  unlinkSync(this.socketPath);
501
646
  }
502
- mkdirSync(dirname(this.socketPath), { recursive: true });
647
+ mkdirSync2(dirname2(this.socketPath), { recursive: true });
503
648
  return new Promise((resolve, reject) => {
504
649
  this.server = createServer((socket) => this.handleConnection(socket));
505
650
  this.server.on("error", (err) => {
@@ -590,7 +735,7 @@ var IpcServer = class {
590
735
  };
591
736
 
592
737
  // src/daemon/discord-bot.ts
593
- async function startDiscordBot(config) {
738
+ async function startDiscordBot(config, sessionsPath) {
594
739
  const router = new ChannelRouter(config);
595
740
  const toolStates = /* @__PURE__ */ new Map();
596
741
  const toolSummaryMessages = /* @__PURE__ */ new Map();
@@ -690,13 +835,23 @@ async function startDiscordBot(config) {
690
835
  permissionDiffShown.delete(channelId);
691
836
  }
692
837
  };
693
- const sessionManager = new SessionManager(handlers);
838
+ const sessionManager = new SessionManager(handlers, sessionsPath);
694
839
  function buildMcpServers(channelId, agentName, guildId) {
695
840
  const resolved = router.resolve(channelId);
696
- if (!resolved?.agent.discord_tools) return [];
697
- const currentDir = import.meta.dirname ?? dirname2(fileURLToPath(import.meta.url));
841
+ const discordToolsEnabled = resolved?.agent.discord_tools ?? false;
842
+ console.log(`[MCP] buildMcpServers: channel=${channelId} agent=${agentName} discord_tools=${discordToolsEnabled}`);
843
+ if (!discordToolsEnabled) {
844
+ console.log(`[MCP] buildMcpServers: discord_tools is falsy, returning empty array`);
845
+ return [];
846
+ }
847
+ const currentDir = import.meta.dirname ?? dirname3(fileURLToPath(import.meta.url));
698
848
  const mcpScriptPath = resolvePath(currentDir, "mcp-discord-channels.js");
699
- return [
849
+ const scriptExists = existsSync2(mcpScriptPath);
850
+ console.log(`[MCP] buildMcpServers: mcpScriptPath=${mcpScriptPath} exists=${scriptExists}`);
851
+ if (!scriptExists) {
852
+ console.warn(`[MCP] WARNING: MCP script not found at ${mcpScriptPath} \u2014 Discord tools will not work`);
853
+ }
854
+ const mcpConfig = [
700
855
  {
701
856
  name: "discord-channels",
702
857
  command: "node",
@@ -710,6 +865,8 @@ async function startDiscordBot(config) {
710
865
  ]
711
866
  }
712
867
  ];
868
+ console.log(`[MCP] buildMcpServers: returning ${mcpConfig.length} MCP server(s):`, JSON.stringify(mcpConfig.map((s) => ({ name: s.name, command: s.command, args: s.args }))));
869
+ return mcpConfig;
713
870
  }
714
871
  function accumulateDiffs(channelId, toolCallId, diffs) {
715
872
  if (diffs.length === 0) return;
@@ -821,7 +978,7 @@ async function startDiscordBot(config) {
821
978
  }
822
979
  async function promptWithMcp(channelId, text, agentName, guildId, agentConfig, requestorId) {
823
980
  const mcpServers = guildId ? buildMcpServers(channelId, agentName, guildId) : void 0;
824
- await sessionManager.prompt(channelId, text, agentConfig, requestorId, mcpServers);
981
+ await sessionManager.prompt(channelId, text, agentName, agentConfig, requestorId, mcpServers);
825
982
  }
826
983
  discordClient = new Client({
827
984
  intents: [
@@ -982,6 +1139,7 @@ async function startDiscordBot(config) {
982
1139
  var CONFIG_DIR = join2(homedir2(), ".acp-discord");
983
1140
  var CONFIG_PATH = join2(CONFIG_DIR, "config.toml");
984
1141
  var PID_PATH = join2(CONFIG_DIR, "daemon.pid");
1142
+ var SESSIONS_PATH = join2(CONFIG_DIR, "sessions.json");
985
1143
  async function runDaemon() {
986
1144
  process.on("uncaughtException", (err) => {
987
1145
  console.error("Uncaught exception:", err);
@@ -995,7 +1153,7 @@ async function runDaemon() {
995
1153
  process.on("exit", () => removePid(PID_PATH));
996
1154
  console.log(`acp-discord daemon started (PID: ${process.pid})`);
997
1155
  console.log(`Loaded config: ${Object.keys(config.channels).length} channel(s)`);
998
- await startDiscordBot(config);
1156
+ await startDiscordBot(config, SESSIONS_PATH);
999
1157
  }
1000
1158
  if (process.env.ACP_DISCORD_DAEMON === "1") {
1001
1159
  runDaemon().catch((err) => {
@@ -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"],"sourcesContent":["import { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { loadConfig } from \"../shared/config.js\";\nimport { writePid, removePid } from \"../cli/pid.js\";\nimport { startDiscordBot } from \"./discord-bot.js\";\n\nconst CONFIG_DIR = join(homedir(), \".acp-discord\");\nconst CONFIG_PATH = join(CONFIG_DIR, \"config.toml\");\nconst PID_PATH = join(CONFIG_DIR, \"daemon.pid\");\n\nexport async function runDaemon(): Promise<void> {\n // 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);\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 { fileURLToPath } from \"node:url\";\nimport type { AppConfig } from \"../shared/types.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\";\n\nexport async function startDiscordBot(config: AppConfig): Promise<void> {\n const router = new ChannelRouter(config);\n\n // Per-channel state for display\n const toolStates = new Map<string, Map<string, { title: string; status: ToolStatus; 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 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 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 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 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 // 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);\n\n // --- MCP server config builder ---\n\n function buildMcpServers(channelId: string, agentName: string, guildId: string): McpServerConfig[] {\n const resolved = router.resolve(channelId);\n if (!resolved?.agent.discord_tools) return [];\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 const mcpScriptPath = resolvePath(currentDir, \"mcp-discord-channels.js\");\n\n return [\n {\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\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, 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 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 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 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 ---\n await ipcServer.start();\n\n // Graceful shutdown\n process.on(\"SIGTERM\", () => {\n ipcServer.stop();\n sessionManager.teardownAll();\n discordClient.destroy();\n });\n\n process.on(\"SIGINT\", () => {\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\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.config.channels[channelId] = {\n agent: agentName,\n auto_reply: autoReply,\n };\n }\n\n unregisterDynamic(channelId: string): void {\n delete this.config.channels[channelId];\n }\n}\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport { Readable, Writable } from \"node:stream\";\nimport { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } from \"@agentclientprotocol/sdk\";\nimport type { AgentConfig } from \"../shared/types.js\";\nimport { createAcpClient, type AcpEventHandlers } from \"./acp-client.js\";\n\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 process: ChildProcess;\n connection: ClientSideConnection;\n sessionId: string;\n lastActivity: number;\n idleTimer: NodeJS.Timeout;\n prompting: boolean;\n queue: Array<{ text: string; requestorId: string }>;\n /** Set only when executePrompt begins — stable for the duration of the prompt */\n activePromptRequestorId: string;\n}\n\nexport class SessionManager {\n private sessions = new Map<string, ManagedSession>();\n private handlers: AcpEventHandlers;\n\n constructor(handlers: AcpEventHandlers) {\n this.handlers = handlers;\n }\n\n async prompt(channelId: string, text: string, agentConfig: AgentConfig, requestorId: string, mcpServers?: McpServerConfig[]): Promise<string> {\n const session = await this.getOrCreate(channelId, 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, agentConfig: AgentConfig, requestorId: string, mcpServers?: McpServerConfig[]): Promise<ManagedSession> {\n const existing = this.sessions.get(channelId);\n if (existing) return existing;\n return this.createSession(channelId, agentConfig, requestorId, mcpServers);\n }\n\n private async createSession(channelId: 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 result = await connection.newSession({\n cwd: config.cwd,\n mcpServers: mcpServers ?? [],\n });\n sessionId = result.sessionId;\n } catch (err) {\n proc.kill();\n throw err;\n }\n\n const managed: ManagedSession = {\n channelId,\n process: proc,\n connection,\n sessionId,\n lastActivity: Date.now(),\n idleTimer: this.startIdleTimer(channelId, config.idle_timeout),\n prompting: false,\n queue: [],\n activePromptRequestorId: requestorId,\n };\n\n this.sessions.set(channelId, managed);\n return managed;\n }\n\n private startIdleTimer(channelId: string, timeoutSec: number): NodeJS.Timeout {\n return setTimeout(() => this.teardown(channelId), timeoutSec * 1000);\n }\n\n private resetIdleTimer(session: ManagedSession, timeoutSec: number): void {\n clearTimeout(session.idleTimer);\n session.idleTimer = this.startIdleTimer(session.channelId, timeoutSec);\n }\n\n teardown(channelId: string): void {\n const session = this.sessions.get(channelId);\n if (!session) return;\n clearTimeout(session.idleTimer);\n session.process.kill();\n this.sessions.delete(channelId);\n }\n\n teardownAll(): void {\n for (const channelId of this.sessions.keys()) {\n this.teardown(channelId);\n }\n }\n\n isPrompting(channelId: string): boolean {\n return this.sessions.get(channelId)?.prompting ?? false;\n }\n\n getActiveRequestorId(channelId: string): string | null {\n const session = this.sessions.get(channelId);\n if (!session?.prompting) return null;\n return session.activePromptRequestorId;\n }\n\n getActiveChannels(): string[] {\n return Array.from(this.sessions.keys());\n }\n}\n","import type {\n Client,\n RequestPermissionRequest,\n RequestPermissionResponse,\n SessionNotification,\n} from \"@agentclientprotocol/sdk\";\n\nexport interface 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 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 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);\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\nfunction extractToolDetail(rawInput?: Record<string, unknown>): string | null {\n if (!rawInput) return null;\n\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 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 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}\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}\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 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"],"mappings":"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","names":["join","homedir","ActionRowBuilder","ButtonBuilder","ButtonStyle","EmbedBuilder","dirname","EmbedBuilder","ActionRowBuilder","ButtonBuilder","ButtonStyle","dirname","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"],"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);\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 { 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\";\n\nexport async function startDiscordBot(config: AppConfig, sessionsPath: string): Promise<void> {\n const router = new ChannelRouter(config);\n\n // Per-channel state for display\n const toolStates = new Map<string, Map<string, { title: string; status: ToolStatus; 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 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 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 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 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 // 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 console.log(`[MCP] buildMcpServers: channel=${channelId} agent=${agentName} discord_tools=${discordToolsEnabled}`);\n\n if (!discordToolsEnabled) {\n console.log(`[MCP] buildMcpServers: discord_tools is falsy, returning empty array`);\n return [];\n }\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 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 const mcpConfig: McpServerConfig[] = [\n {\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 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 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 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 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 ---\n await ipcServer.start();\n\n // Graceful shutdown\n process.on(\"SIGTERM\", () => {\n ipcServer.stop();\n sessionManager.teardownAll();\n discordClient.destroy();\n });\n\n process.on(\"SIGINT\", () => {\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\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.config.channels[channelId] = {\n agent: agentName,\n auto_reply: autoReply,\n };\n }\n\n unregisterDynamic(channelId: string): void {\n delete this.config.channels[channelId];\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 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 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);\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\nfunction extractToolDetail(rawInput?: Record<string, unknown>): string | null {\n if (!rawInput) return null;\n\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 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 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}\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}\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 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"],"mappings":"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","names":["join","homedir","ActionRowBuilder","ButtonBuilder","ButtonStyle","EmbedBuilder","dirname","existsSync","mkdirSync","dirname","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.6.2",
3
+ "version": "0.7.0",
4
4
  "description": "Discord bot that wraps ACP protocol for coding agents",
5
5
  "type": "module",
6
6
  "bin": {