@sudocode-ai/claude-code-acp 0.12.9 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/acp-agent.js CHANGED
@@ -20,7 +20,6 @@ export class ClaudeAcpAgent {
20
20
  this.sessions = {};
21
21
  this.client = client;
22
22
  this.toolUseCache = {};
23
- this.fileContentCache = {};
24
23
  this.logger = logger ?? console;
25
24
  }
26
25
  async initialize(request) {
@@ -55,6 +54,7 @@ export class ClaudeAcpAgent {
55
54
  },
56
55
  sessionCapabilities: {
57
56
  fork: {},
57
+ resume: {},
58
58
  },
59
59
  loadSession: true,
60
60
  },
@@ -82,16 +82,233 @@ export class ClaudeAcpAgent {
82
82
  * Named unstable_forkSession to match SDK expectations (session/fork routes to this method).
83
83
  */
84
84
  async unstable_forkSession(params) {
85
- // cwd and mcpServers are passed via _meta since they're not in the SDK type yet
86
- const meta = params._meta;
87
- return await this.createSession({
88
- cwd: meta?.cwd ?? process.cwd(),
89
- mcpServers: meta?.mcpServers ?? [],
85
+ // Get the session directory to track new files
86
+ const sessionDir = this.getSessionDirPath(params.cwd);
87
+ const beforeFiles = new Set(fs.existsSync(sessionDir)
88
+ ? fs.readdirSync(sessionDir).filter((f) => f.endsWith(".jsonl"))
89
+ : []);
90
+ const result = await this.createSession({
91
+ cwd: params.cwd,
92
+ mcpServers: params.mcpServers ?? [],
90
93
  _meta: params._meta,
91
94
  }, {
92
95
  resume: params.sessionId,
93
96
  forkSession: true,
94
97
  });
98
+ // Wait briefly for CLI to create the session file
99
+ await new Promise((resolve) => setTimeout(resolve, 200));
100
+ // Find the CLI-assigned session ID by looking for new session files
101
+ const cliSessionId = await this.discoverCliSessionId(sessionDir, beforeFiles, result.sessionId);
102
+ if (cliSessionId && cliSessionId !== result.sessionId) {
103
+ // Check if the CLI assigned a non-UUID session ID (e.g., "agent-xxx")
104
+ // If so, we need to extract the internal sessionId from the file
105
+ const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(cliSessionId);
106
+ if (!isUuid) {
107
+ // Read the session file to extract the internal sessionId
108
+ const oldFilePath = path.join(sessionDir, `${cliSessionId}.jsonl`);
109
+ const internalSessionId = this.extractInternalSessionId(oldFilePath);
110
+ if (internalSessionId) {
111
+ this.logger.log(`[claude-code-acp] Fork: extracted internal sessionId ${internalSessionId} from ${cliSessionId}`);
112
+ // Check if target file already exists (CLI reuses session IDs for forks from same parent)
113
+ // If so, generate a new unique session ID to avoid collisions
114
+ let finalSessionId = internalSessionId;
115
+ let newFilePath = path.join(sessionDir, `${finalSessionId}.jsonl`);
116
+ if (fs.existsSync(newFilePath)) {
117
+ // Session ID collision - CLI created a fork with the same internal ID
118
+ // Generate a new UUID and update the file's internal session ID
119
+ finalSessionId = randomUUID();
120
+ newFilePath = path.join(sessionDir, `${finalSessionId}.jsonl`);
121
+ this.logger.log(`[claude-code-acp] Fork: session ID collision detected, using new ID: ${finalSessionId}`);
122
+ // Update the internal session ID in the file before renaming
123
+ this.updateSessionIdInFile(oldFilePath, finalSessionId);
124
+ }
125
+ // Rename the file to match the session ID so CLI can find it
126
+ try {
127
+ fs.renameSync(oldFilePath, newFilePath);
128
+ this.logger.log(`[claude-code-acp] Fork: renamed ${cliSessionId}.jsonl -> ${finalSessionId}.jsonl`);
129
+ // Promote sidechain to full session so it can be resumed/forked again
130
+ this.promoteToFullSession(newFilePath);
131
+ }
132
+ catch (err) {
133
+ this.logger.error(`[claude-code-acp] Failed to rename session file: ${err}`);
134
+ // Continue anyway - the session might still work
135
+ }
136
+ // Re-register session with the final session ID
137
+ const session = this.sessions[result.sessionId];
138
+ this.sessions[finalSessionId] = session;
139
+ delete this.sessions[result.sessionId];
140
+ return { ...result, sessionId: finalSessionId };
141
+ }
142
+ // Fall through if we couldn't extract the internal ID
143
+ this.logger.error(`[claude-code-acp] Could not extract internal sessionId from ${oldFilePath}`);
144
+ }
145
+ // Re-register session with the CLI's session ID (if it's already a UUID or extraction failed)
146
+ this.logger.log(`[claude-code-acp] Fork: remapping session ${result.sessionId} -> ${cliSessionId}`);
147
+ this.sessions[cliSessionId] = this.sessions[result.sessionId];
148
+ delete this.sessions[result.sessionId];
149
+ return { ...result, sessionId: cliSessionId };
150
+ }
151
+ return result;
152
+ }
153
+ /**
154
+ * Get the directory where session files are stored for a given cwd.
155
+ */
156
+ getSessionDirPath(cwd) {
157
+ const realCwd = fs.realpathSync(cwd);
158
+ const cwdHash = realCwd.replace(/[/_]/g, "-");
159
+ return path.join(os.homedir(), ".claude", "projects", cwdHash);
160
+ }
161
+ /**
162
+ * Extract the internal sessionId from a session JSONL file.
163
+ * The CLI stores the actual session ID inside the file, which may differ from the filename.
164
+ * For forked sessions, the filename is "agent-xxx" but the internal sessionId is a UUID.
165
+ */
166
+ extractInternalSessionId(filePath) {
167
+ try {
168
+ if (!fs.existsSync(filePath)) {
169
+ return null;
170
+ }
171
+ const content = fs.readFileSync(filePath, "utf-8");
172
+ const firstLine = content.split("\n").find((line) => line.trim().length > 0);
173
+ if (!firstLine) {
174
+ return null;
175
+ }
176
+ const parsed = JSON.parse(firstLine);
177
+ if (parsed.sessionId && typeof parsed.sessionId === "string") {
178
+ // Verify it's a UUID format
179
+ const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(parsed.sessionId);
180
+ if (isUuid) {
181
+ return parsed.sessionId;
182
+ }
183
+ }
184
+ return null;
185
+ }
186
+ catch (err) {
187
+ this.logger.error(`[claude-code-acp] Failed to extract sessionId from ${filePath}: ${err}`);
188
+ return null;
189
+ }
190
+ }
191
+ /**
192
+ * Promote a sidechain session to a regular session by modifying the session file.
193
+ * Forked sessions have "isSidechain": true which prevents them from being resumed.
194
+ * This method changes it to false so the session can be resumed/forked again.
195
+ */
196
+ promoteToFullSession(filePath) {
197
+ try {
198
+ if (!fs.existsSync(filePath)) {
199
+ return false;
200
+ }
201
+ const content = fs.readFileSync(filePath, "utf-8");
202
+ const lines = content.split("\n");
203
+ const modifiedLines = [];
204
+ for (const line of lines) {
205
+ if (!line.trim()) {
206
+ modifiedLines.push(line);
207
+ continue;
208
+ }
209
+ try {
210
+ const parsed = JSON.parse(line);
211
+ // Change isSidechain from true to false
212
+ if (parsed.isSidechain === true) {
213
+ parsed.isSidechain = false;
214
+ }
215
+ modifiedLines.push(JSON.stringify(parsed));
216
+ }
217
+ catch {
218
+ // Keep line as-is if it can't be parsed
219
+ modifiedLines.push(line);
220
+ }
221
+ }
222
+ fs.writeFileSync(filePath, modifiedLines.join("\n"), "utf-8");
223
+ this.logger.log(`[claude-code-acp] Promoted sidechain to full session: ${filePath}`);
224
+ return true;
225
+ }
226
+ catch (err) {
227
+ this.logger.error(`[claude-code-acp] Failed to promote session: ${err}`);
228
+ return false;
229
+ }
230
+ }
231
+ /**
232
+ * Update the sessionId in all lines of a session JSONL file.
233
+ * This is used when we need to assign a new unique session ID to avoid collisions.
234
+ */
235
+ updateSessionIdInFile(filePath, newSessionId) {
236
+ try {
237
+ if (!fs.existsSync(filePath)) {
238
+ return false;
239
+ }
240
+ const content = fs.readFileSync(filePath, "utf-8");
241
+ const lines = content.split("\n");
242
+ const modifiedLines = [];
243
+ for (const line of lines) {
244
+ if (!line.trim()) {
245
+ modifiedLines.push(line);
246
+ continue;
247
+ }
248
+ try {
249
+ const parsed = JSON.parse(line);
250
+ // Update the sessionId in each line
251
+ if (parsed.sessionId && typeof parsed.sessionId === "string") {
252
+ parsed.sessionId = newSessionId;
253
+ }
254
+ modifiedLines.push(JSON.stringify(parsed));
255
+ }
256
+ catch {
257
+ // Keep line as-is if it can't be parsed
258
+ modifiedLines.push(line);
259
+ }
260
+ }
261
+ fs.writeFileSync(filePath, modifiedLines.join("\n"), "utf-8");
262
+ this.logger.log(`[claude-code-acp] Updated session ID in file: ${filePath}`);
263
+ return true;
264
+ }
265
+ catch (err) {
266
+ this.logger.error(`[claude-code-acp] Failed to update session ID in file: ${err}`);
267
+ return false;
268
+ }
269
+ }
270
+ /**
271
+ * Discover the CLI-assigned session ID by looking for new session files.
272
+ * Returns the CLI's session ID if found, or the original sessionId if not.
273
+ */
274
+ async discoverCliSessionId(sessionDir, beforeFiles, fallbackId, timeout = 2000) {
275
+ const start = Date.now();
276
+ // Pattern for CLI-assigned fork session IDs (agent-xxxxxxx)
277
+ const agentPattern = /^agent-[a-f0-9]+\.jsonl$/;
278
+ while (Date.now() - start < timeout) {
279
+ if (fs.existsSync(sessionDir)) {
280
+ const currentFiles = fs.readdirSync(sessionDir).filter((f) => f.endsWith(".jsonl"));
281
+ // Only look for new files that match the agent-xxx pattern
282
+ // This prevents picking up renamed UUID files from previous forks
283
+ const newFiles = currentFiles.filter((f) => !beforeFiles.has(f) && agentPattern.test(f));
284
+ if (newFiles.length === 1) {
285
+ // Found exactly one new agent session file - this is our fork
286
+ this.logger.log(`[claude-code-acp] Discovered fork session file: ${newFiles[0]}`);
287
+ return newFiles[0].replace(".jsonl", "");
288
+ }
289
+ else if (newFiles.length > 1) {
290
+ // Multiple new agent files - try to find the most recent one
291
+ let newestFile = "";
292
+ let newestMtime = 0;
293
+ for (const file of newFiles) {
294
+ const filePath = path.join(sessionDir, file);
295
+ const stat = fs.statSync(filePath);
296
+ if (stat.mtimeMs > newestMtime) {
297
+ newestMtime = stat.mtimeMs;
298
+ newestFile = file;
299
+ }
300
+ }
301
+ if (newestFile) {
302
+ this.logger.log(`[claude-code-acp] Discovered fork session file (newest of ${newFiles.length}): ${newestFile}`);
303
+ return newestFile.replace(".jsonl", "");
304
+ }
305
+ }
306
+ }
307
+ await new Promise((resolve) => setTimeout(resolve, 100));
308
+ }
309
+ // Timeout - return fallback
310
+ this.logger.log(`[claude-code-acp] Could not discover CLI session ID, using fallback: ${fallbackId}`);
311
+ return fallbackId;
95
312
  }
96
313
  /**
97
314
  * Alias for unstable_forkSession for convenience.
@@ -191,7 +408,7 @@ export class ClaudeAcpAgent {
191
408
  break;
192
409
  }
193
410
  case "stream_event": {
194
- for (const notification of streamEventToAcpNotifications(message, params.sessionId, this.toolUseCache, this.fileContentCache, this.client, this.logger)) {
411
+ for (const notification of streamEventToAcpNotifications(message, params.sessionId, this.toolUseCache, this.client, this.logger)) {
195
412
  await this.client.sessionUpdate(notification);
196
413
  }
197
414
  break;
@@ -233,7 +450,7 @@ export class ClaudeAcpAgent {
233
450
  ? // Handled by stream events above
234
451
  message.message.content.filter((item) => !["text", "thinking"].includes(item.type))
235
452
  : message.message.content;
236
- for (const notification of toAcpNotifications(content, message.message.role, params.sessionId, this.toolUseCache, this.fileContentCache, this.client, this.logger)) {
453
+ for (const notification of toAcpNotifications(content, message.message.role, params.sessionId, this.toolUseCache, this.client, this.logger)) {
237
454
  await this.client.sessionUpdate(notification);
238
455
  }
239
456
  break;
@@ -256,6 +473,106 @@ export class ClaudeAcpAgent {
256
473
  this.sessions[params.sessionId].cancelled = true;
257
474
  await this.sessions[params.sessionId].query.interrupt();
258
475
  }
476
+ /**
477
+ * Handle extension methods from the client.
478
+ *
479
+ * Currently supports:
480
+ * - `_session/flush`: Flush a session to disk for fork-with-flush support
481
+ */
482
+ async extMethod(method, params) {
483
+ if (method === "_session/flush") {
484
+ return this.handleSessionFlush(params);
485
+ }
486
+ throw RequestError.methodNotFound(method);
487
+ }
488
+ /**
489
+ * Flush a session to disk by aborting its query subprocess.
490
+ *
491
+ * This is used by the fork-with-flush mechanism to ensure session data
492
+ * is persisted to disk before forking. When the Claude SDK subprocess
493
+ * exits (via abort), it writes the session data to:
494
+ * ~/.claude/projects/<cwd-hash>/<sessionId>.jsonl
495
+ *
496
+ * After this method completes, the session is removed from memory and
497
+ * must be reloaded via loadSession() to continue using it.
498
+ */
499
+ async handleSessionFlush(params) {
500
+ const { sessionId, persistTimeout = 5000 } = params;
501
+ const session = this.sessions[sessionId];
502
+ if (!session) {
503
+ return { success: false, error: `Session ${sessionId} not found` };
504
+ }
505
+ try {
506
+ // Step 1: Mark session as cancelled to stop processing
507
+ session.cancelled = true;
508
+ // Step 2: Interrupt any ongoing query work
509
+ await session.query.interrupt();
510
+ // Step 3: End the input stream to signal no more input
511
+ session.input.end();
512
+ // Step 4: Abort the session using the AbortController
513
+ // This forces the Claude SDK subprocess to exit, which triggers disk persistence
514
+ session.abortController.abort();
515
+ // Step 5: Wait for the session file to appear on disk
516
+ // Use stored sessionFilePath for forked sessions (where filename differs from sessionId)
517
+ const sessionFilePath = session.sessionFilePath ?? this.getSessionFilePath(sessionId, session.cwd);
518
+ this.logger.log(`[claude-code-acp] Waiting for session file at: ${sessionFilePath}`);
519
+ this.logger.log(`[claude-code-acp] Session cwd: ${session.cwd}`);
520
+ const persisted = await this.waitForSessionFile(sessionFilePath, persistTimeout);
521
+ if (!persisted) {
522
+ this.logger.error(`[claude-code-acp] Session file not found at ${sessionFilePath} after ${persistTimeout}ms`);
523
+ // Check if file exists at the path
524
+ const exists = fs.existsSync(sessionFilePath);
525
+ this.logger.error(`[claude-code-acp] File exists check: ${exists}`);
526
+ // Still remove the session from memory
527
+ delete this.sessions[sessionId];
528
+ return { success: false, error: `Session file not created within timeout` };
529
+ }
530
+ // Step 6: Remove session from our map
531
+ // The client will call loadSession() to reload it from disk
532
+ delete this.sessions[sessionId];
533
+ this.logger.log(`[claude-code-acp] Session ${sessionId} flushed to disk at ${sessionFilePath}`);
534
+ return { success: true, filePath: sessionFilePath };
535
+ }
536
+ catch (error) {
537
+ this.logger.error(`[claude-code-acp] Failed to flush session ${sessionId}:`, error);
538
+ // Clean up session on error
539
+ delete this.sessions[sessionId];
540
+ return { success: false, error: String(error) };
541
+ }
542
+ }
543
+ /**
544
+ * Get the file path where Claude Code stores session data.
545
+ *
546
+ * Claude Code stores sessions at:
547
+ * ~/.claude/projects/<cwd-hash>/<sessionId>.jsonl
548
+ *
549
+ * Where <cwd-hash> is the cwd with `/` replaced by `-`
550
+ * Note: We resolve the real path to handle macOS symlinks like /var -> /private/var
551
+ */
552
+ getSessionFilePath(sessionId, cwd) {
553
+ // Resolve the real path to handle macOS symlinks like /var -> /private/var
554
+ const realCwd = fs.realpathSync(cwd);
555
+ // Claude Code replaces both / and _ with - in the cwd hash
556
+ const cwdHash = realCwd.replace(/[/_]/g, "-");
557
+ return path.join(os.homedir(), ".claude", "projects", cwdHash, `${sessionId}.jsonl`);
558
+ }
559
+ /**
560
+ * Wait for a session file to appear on disk.
561
+ *
562
+ * @param filePath - Path to the session file
563
+ * @param timeout - Maximum time to wait in milliseconds
564
+ * @returns true if file appears, false if timeout
565
+ */
566
+ async waitForSessionFile(filePath, timeout) {
567
+ const start = Date.now();
568
+ while (Date.now() - start < timeout) {
569
+ if (fs.existsSync(filePath)) {
570
+ return true;
571
+ }
572
+ await new Promise((resolve) => setTimeout(resolve, 100));
573
+ }
574
+ return false;
575
+ }
259
576
  async unstable_setSessionModel(params) {
260
577
  if (!this.sessions[params.sessionId]) {
261
578
  throw new Error("Session not found");
@@ -287,14 +604,10 @@ export class ClaudeAcpAgent {
287
604
  }
288
605
  async readTextFile(params) {
289
606
  const response = await this.client.readTextFile(params);
290
- if (!params.limit && !params.line) {
291
- this.fileContentCache[params.path] = response.content;
292
- }
293
607
  return response;
294
608
  }
295
609
  async writeTextFile(params) {
296
610
  const response = await this.client.writeTextFile(params);
297
- this.fileContentCache[params.path] = params.content;
298
611
  return response;
299
612
  }
300
613
  canUseTool(sessionId) {
@@ -322,7 +635,7 @@ export class ClaudeAcpAgent {
322
635
  toolCall: {
323
636
  toolCallId: toolUseID,
324
637
  rawInput: toolInput,
325
- title: toolInfoFromToolUse({ name: toolName, input: toolInput }, this.fileContentCache, this.logger).title,
638
+ title: toolInfoFromToolUse({ name: toolName, input: toolInput }).title,
326
639
  },
327
640
  });
328
641
  if (signal.aborted || response.outcome?.outcome === "cancelled") {
@@ -378,7 +691,7 @@ export class ClaudeAcpAgent {
378
691
  toolCall: {
379
692
  toolCallId: toolUseID,
380
693
  rawInput: toolInput,
381
- title: toolInfoFromToolUse({ name: toolName, input: toolInput }, this.fileContentCache, this.logger).title,
694
+ title: toolInfoFromToolUse({ name: toolName, input: toolInput }).title,
382
695
  },
383
696
  });
384
697
  if (signal.aborted || response.outcome?.outcome === "cancelled") {
@@ -416,9 +729,18 @@ export class ClaudeAcpAgent {
416
729
  };
417
730
  }
418
731
  async createSession(params, creationOpts = {}) {
419
- // When forking, generate a new session ID (fork creates a new independent session)
420
- // When resuming (not fork), use the original session ID
421
- const sessionId = creationOpts.forkSession ? randomUUID() : (creationOpts.resume ?? randomUUID());
732
+ // We want to create a new session id unless it is resume,
733
+ // but not resume + forkSession.
734
+ let sessionId;
735
+ if (creationOpts.forkSession) {
736
+ sessionId = randomUUID();
737
+ }
738
+ else if (creationOpts.resume) {
739
+ sessionId = creationOpts.resume;
740
+ }
741
+ else {
742
+ sessionId = randomUUID();
743
+ }
422
744
  const input = new Pushable();
423
745
  const settingsManager = new SettingsManager(params.cwd, {
424
746
  logger: this.logger,
@@ -473,15 +795,21 @@ export class ClaudeAcpAgent {
473
795
  // Extract options from _meta if provided
474
796
  const userProvidedOptions = params._meta?.claudeCode?.options;
475
797
  const extraArgs = { ...userProvidedOptions?.extraArgs };
476
- if (creationOpts?.resume === undefined) {
798
+ if (creationOpts?.resume === undefined || creationOpts?.forkSession) {
477
799
  // Set our own session id if not resuming an existing session.
478
- // TODO: find a way to make this work for fork
800
+ // Note: For forked sessions (resume + fork), Claude CLI assigns its own session ID
801
+ // which means chain forking (fork of a fork) is not currently supported.
479
802
  extraArgs["session-id"] = sessionId;
480
803
  }
804
+ // Configure thinking tokens from environment variable
805
+ const maxThinkingTokens = process.env.MAX_THINKING_TOKENS
806
+ ? parseInt(process.env.MAX_THINKING_TOKENS, 10)
807
+ : undefined;
481
808
  const options = {
482
809
  systemPrompt,
483
810
  settingSources: ["user", "project", "local"],
484
811
  stderr: (err) => this.logger.error(err),
812
+ ...(maxThinkingTokens !== undefined && { maxThinkingTokens }),
485
813
  ...userProvidedOptions,
486
814
  // Override certain fields that must be controlled by ACP
487
815
  cwd: params.cwd,
@@ -499,6 +827,7 @@ export class ClaudeAcpAgent {
499
827
  ...(process.env.CLAUDE_CODE_EXECUTABLE && {
500
828
  pathToClaudeCodeExecutable: process.env.CLAUDE_CODE_EXECUTABLE,
501
829
  }),
830
+ tools: { type: "preset", preset: "claude_code" },
502
831
  hooks: {
503
832
  ...userProvidedOptions?.hooks,
504
833
  PreToolUse: [
@@ -517,7 +846,8 @@ export class ClaudeAcpAgent {
517
846
  ...creationOpts,
518
847
  };
519
848
  const allowedTools = [];
520
- const disallowedTools = [];
849
+ // Disable this for now, not a great way to expose this over ACP at the moment (in progress work so we can revisit)
850
+ const disallowedTools = ["AskUserQuestion"];
521
851
  // Check if built-in tools should be disabled
522
852
  const disableBuiltInTools = params._meta?.disableBuiltInTools === true;
523
853
  if (!disableBuiltInTools) {
@@ -543,11 +873,15 @@ export class ClaudeAcpAgent {
543
873
  if (disallowedTools.length > 0) {
544
874
  options.disallowedTools = disallowedTools;
545
875
  }
546
- // Handle abort controller from meta options
547
- const abortController = userProvidedOptions?.abortController;
548
- if (abortController?.signal.aborted) {
876
+ // Create our own AbortController for session management
877
+ const sessionAbortController = new AbortController();
878
+ // Handle abort controller from meta options (user can still provide one)
879
+ const userAbortController = userProvidedOptions?.abortController;
880
+ if (userAbortController?.signal.aborted) {
549
881
  throw new Error("Cancelled");
550
882
  }
883
+ // Pass the abort controller to the query options
884
+ options.abortController = sessionAbortController;
551
885
  const q = query({
552
886
  prompt: input,
553
887
  options,
@@ -558,6 +892,8 @@ export class ClaudeAcpAgent {
558
892
  cancelled: false,
559
893
  permissionMode,
560
894
  settingsManager,
895
+ abortController: sessionAbortController,
896
+ cwd: params.cwd,
561
897
  };
562
898
  const availableCommands = await getAvailableSlashCommands(q);
563
899
  const models = await getAvailableModels(q);
@@ -639,7 +975,13 @@ async function getAvailableSlashCommands(query) {
639
975
  const commands = await query.supportedCommands();
640
976
  return commands
641
977
  .map((command) => {
642
- const input = command.argumentHint ? { hint: command.argumentHint } : null;
978
+ const input = command.argumentHint
979
+ ? {
980
+ hint: Array.isArray(command.argumentHint)
981
+ ? command.argumentHint.join(" ")
982
+ : command.argumentHint,
983
+ }
984
+ : null;
643
985
  let name = command.name;
644
986
  if (command.name.endsWith(" (MCP)")) {
645
987
  name = `mcp:${name.replace(" (MCP)", "")}`;
@@ -750,7 +1092,7 @@ export function promptToClaude(prompt) {
750
1092
  * Convert an SDKAssistantMessage (Claude) to a SessionNotification (ACP).
751
1093
  * Only handles text, image, and thinking chunks for now.
752
1094
  */
753
- export function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentCache, client, logger) {
1095
+ export function toAcpNotifications(content, role, sessionId, toolUseCache, client, logger) {
754
1096
  if (typeof content === "string") {
755
1097
  return [
756
1098
  {
@@ -857,7 +1199,7 @@ export function toAcpNotifications(content, role, sessionId, toolUseCache, fileC
857
1199
  sessionUpdate: "tool_call",
858
1200
  rawInput,
859
1201
  status: "pending",
860
- ...toolInfoFromToolUse(chunk, fileContentCache, logger),
1202
+ ...toolInfoFromToolUse(chunk),
861
1203
  };
862
1204
  }
863
1205
  break;
@@ -908,13 +1250,13 @@ export function toAcpNotifications(content, role, sessionId, toolUseCache, fileC
908
1250
  }
909
1251
  return output;
910
1252
  }
911
- export function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileContentCache, client, logger) {
1253
+ export function streamEventToAcpNotifications(message, sessionId, toolUseCache, client, logger) {
912
1254
  const event = message.event;
913
1255
  switch (event.type) {
914
1256
  case "content_block_start":
915
- return toAcpNotifications([event.content_block], "assistant", sessionId, toolUseCache, fileContentCache, client, logger);
1257
+ return toAcpNotifications([event.content_block], "assistant", sessionId, toolUseCache, client, logger);
916
1258
  case "content_block_delta":
917
- return toAcpNotifications([event.delta], "assistant", sessionId, toolUseCache, fileContentCache, client, logger);
1259
+ return toAcpNotifications([event.delta], "assistant", sessionId, toolUseCache, client, logger);
918
1260
  // No content
919
1261
  case "message_start":
920
1262
  case "message_delta":
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/lib.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { ClaudeAcpAgent, runAcp, toAcpNotifications, streamEventToAcpNotifications, type ToolUpdateMeta, type NewSessionMeta, } from "./acp-agent.js";
2
+ export { loadManagedSettings, applyEnvironmentSettings, nodeToWebReadable, nodeToWebWritable, Pushable, unreachable, } from "./utils.js";
3
+ export { createMcpServer } from "./mcp-server.js";
4
+ export { toolInfoFromToolUse, planEntries, toolUpdateFromToolResult, createPreToolUseHook, acpToolNames as toolNames, } from "./tools.js";
5
+ export { SettingsManager, type ClaudeCodeSettings, type PermissionSettings, type PermissionDecision, type PermissionCheckResult, type SettingsManagerOptions, } from "./settings.js";
6
+ export type { ClaudePlanEntry } from "./tools.js";
7
+ //# sourceMappingURL=lib.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,MAAM,EACN,kBAAkB,EAClB,6BAA6B,EAC7B,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,wBAAwB,EACxB,oBAAoB,EACpB,YAAY,IAAI,SAAS,GAC1B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,eAAe,CAAC;AAGvB,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ClaudeAcpAgent } from "./acp-agent.js";
3
+ import { ClientCapabilities } from "@agentclientprotocol/sdk";
4
+ export declare const SYSTEM_REMINDER = "\n\n<system-reminder>\nWhenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.\n</system-reminder>";
5
+ export declare function createMcpServer(agent: ClaudeAcpAgent, sessionId: string, clientCapabilities: ClientCapabilities | undefined): McpServer;
6
+ /**
7
+ * Replace text in a file and calculate the line numbers where the edits occurred.
8
+ *
9
+ * @param fileContent - The full file content
10
+ * @param edits - Array of edit operations to apply sequentially
11
+ * @returns the new content and the line numbers where replacements occurred in the final content
12
+ */
13
+ export declare function replaceAndCalculateLocation(fileContent: string, edits: Array<{
14
+ oldText: string;
15
+ newText: string;
16
+ replaceAll?: boolean;
17
+ }>): {
18
+ newContent: string;
19
+ lineNumbers: number[];
20
+ };
21
+ //# sourceMappingURL=mcp-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AASpE,OAAO,EAAqB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EACL,kBAAkB,EAGnB,MAAM,0BAA0B,CAAC;AAQlC,eAAO,MAAM,eAAe,iSAIT,CAAC;AA2BpB,wBAAgB,eAAe,CAC7B,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,kBAAkB,GAAG,SAAS,GACjD,SAAS,CA2nBX;AA+DD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,KAAK,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC,GACD;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CAyF/C"}