claude-code-acp-ts 0.24.3 → 0.37.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/tools.js CHANGED
@@ -52,17 +52,17 @@ export function toolInfoFromToolUse(toolUse, supportsTerminalOutput = false, cwd
52
52
  case "Read": {
53
53
  const input = toolUse.input;
54
54
  let limit = "";
55
- if (input.limit && input.limit > 0) {
55
+ if (input?.limit && input.limit > 0) {
56
56
  limit = " (" + (input.offset ?? 1) + " - " + ((input.offset ?? 1) + input.limit - 1) + ")";
57
57
  }
58
- else if (input.offset) {
58
+ else if (input?.offset) {
59
59
  limit = " (from line " + input.offset + ")";
60
60
  }
61
- const displayPath = input.file_path ? toDisplayPath(input.file_path, cwd) : "File";
61
+ const displayPath = input?.file_path ? toDisplayPath(input.file_path, cwd) : "File";
62
62
  return {
63
63
  title: "Read " + displayPath + limit,
64
64
  kind: "read",
65
- locations: input.file_path
65
+ locations: input?.file_path
66
66
  ? [
67
67
  {
68
68
  path: input.file_path,
@@ -126,38 +126,38 @@ export function toolInfoFromToolUse(toolUse, supportsTerminalOutput = false, cwd
126
126
  case "Glob": {
127
127
  const input = toolUse.input;
128
128
  let label = "Find";
129
- if (input.path) {
129
+ if (input?.path) {
130
130
  label += ` \`${input.path}\``;
131
131
  }
132
- if (input.pattern) {
132
+ if (input?.pattern) {
133
133
  label += ` \`${input.pattern}\``;
134
134
  }
135
135
  return {
136
136
  title: label,
137
137
  kind: "search",
138
138
  content: [],
139
- locations: input.path ? [{ path: input.path }] : [],
139
+ locations: input?.path ? [{ path: input.path }] : [],
140
140
  };
141
141
  }
142
142
  case "Grep": {
143
143
  const input = toolUse.input;
144
144
  let label = "grep";
145
- if (input["-i"]) {
145
+ if (input?.["-i"]) {
146
146
  label += " -i";
147
147
  }
148
- if (input["-n"]) {
148
+ if (input?.["-n"]) {
149
149
  label += " -n";
150
150
  }
151
- if (input["-A"] !== undefined) {
151
+ if (input?.["-A"] !== undefined) {
152
152
  label += ` -A ${input["-A"]}`;
153
153
  }
154
- if (input["-B"] !== undefined) {
154
+ if (input?.["-B"] !== undefined) {
155
155
  label += ` -B ${input["-B"]}`;
156
156
  }
157
- if (input["-C"] !== undefined) {
157
+ if (input?.["-C"] !== undefined) {
158
158
  label += ` -C ${input["-C"]}`;
159
159
  }
160
- if (input.output_mode) {
160
+ if (input?.output_mode) {
161
161
  switch (input.output_mode) {
162
162
  case "files_with_matches":
163
163
  label += " -l";
@@ -170,22 +170,22 @@ export function toolInfoFromToolUse(toolUse, supportsTerminalOutput = false, cwd
170
170
  break;
171
171
  }
172
172
  }
173
- if (input.head_limit !== undefined) {
173
+ if (input?.head_limit !== undefined) {
174
174
  label += ` | head -${input.head_limit}`;
175
175
  }
176
- if (input.glob) {
176
+ if (input?.glob) {
177
177
  label += ` --include="${input.glob}"`;
178
178
  }
179
- if (input.type) {
179
+ if (input?.type) {
180
180
  label += ` --type=${input.type}`;
181
181
  }
182
- if (input.multiline) {
182
+ if (input?.multiline) {
183
183
  label += " -P";
184
184
  }
185
- if (input.pattern) {
185
+ if (input?.pattern) {
186
186
  label += ` "${input.pattern}"`;
187
187
  }
188
- if (input.path) {
188
+ if (input?.path) {
189
189
  label += ` ${input.path}`;
190
190
  }
191
191
  return {
@@ -211,11 +211,11 @@ export function toolInfoFromToolUse(toolUse, supportsTerminalOutput = false, cwd
211
211
  }
212
212
  case "WebSearch": {
213
213
  const input = toolUse.input;
214
- let label = `"${input.query}"`;
215
- if (input.allowed_domains && input.allowed_domains.length > 0) {
214
+ let label = input?.query ? `"${input.query}"` : "Web search";
215
+ if (input?.allowed_domains && input.allowed_domains.length > 0) {
216
216
  label += ` (allowed: ${input.allowed_domains.join(", ")})`;
217
217
  }
218
- if (input.blocked_domains && input.blocked_domains.length > 0) {
218
+ if (input?.blocked_domains && input.blocked_domains.length > 0) {
219
219
  label += ` (blocked: ${input.blocked_domains.join(", ")})`;
220
220
  }
221
221
  return {
@@ -234,6 +234,36 @@ export function toolInfoFromToolUse(toolUse, supportsTerminalOutput = false, cwd
234
234
  content: [],
235
235
  };
236
236
  }
237
+ case "TaskCreate": {
238
+ const input = toolUse.input;
239
+ return {
240
+ title: input?.subject ? `Create task: ${input.subject}` : "Create task",
241
+ kind: "think",
242
+ content: [],
243
+ };
244
+ }
245
+ case "TaskUpdate": {
246
+ const input = toolUse.input;
247
+ return {
248
+ title: input?.subject ? `Update task: ${input.subject}` : "Update task",
249
+ kind: "think",
250
+ content: [],
251
+ };
252
+ }
253
+ case "TaskList": {
254
+ return {
255
+ title: "List tasks",
256
+ kind: "think",
257
+ content: [],
258
+ };
259
+ }
260
+ case "TaskGet": {
261
+ return {
262
+ title: "Get task",
263
+ kind: "think",
264
+ content: [],
265
+ };
266
+ }
237
267
  case "ExitPlanMode": {
238
268
  const planInput = toolUse.input;
239
269
  return {
@@ -477,9 +507,85 @@ function toAcpContentBlock(content, isError) {
477
507
  }
478
508
  }
479
509
  export function planEntries(input) {
480
- return input.todos.map((input) => ({
481
- content: input.content,
482
- status: input.status,
510
+ return (input?.todos ?? []).map((todo) => ({
511
+ content: todo.content,
512
+ status: todo.status,
513
+ priority: "medium",
514
+ }));
515
+ }
516
+ /**
517
+ * Best-effort parse of a TaskCreate tool_result content into the structured
518
+ * TaskCreateOutput. The SDK delivers tool outputs either as a string or as
519
+ * an array of TextBlockParam-like blocks containing JSON text; try both.
520
+ */
521
+ export function parseTaskCreateOutput(content) {
522
+ const tryParse = (text) => {
523
+ try {
524
+ const parsed = JSON.parse(text);
525
+ if (parsed &&
526
+ typeof parsed === "object" &&
527
+ parsed.task &&
528
+ typeof parsed.task.id === "string") {
529
+ return parsed;
530
+ }
531
+ }
532
+ catch {
533
+ // ignore
534
+ }
535
+ return undefined;
536
+ };
537
+ if (typeof content === "string") {
538
+ return tryParse(content);
539
+ }
540
+ if (Array.isArray(content)) {
541
+ for (const block of content) {
542
+ if (block && typeof block === "object" && "type" in block && block.type === "text") {
543
+ const text = block.text;
544
+ if (typeof text === "string") {
545
+ const parsed = tryParse(text);
546
+ if (parsed)
547
+ return parsed;
548
+ }
549
+ }
550
+ }
551
+ }
552
+ return undefined;
553
+ }
554
+ export function applyTaskCreate(state, input, output) {
555
+ const taskId = output?.task?.id;
556
+ if (!taskId || !input)
557
+ return;
558
+ state.set(taskId, {
559
+ subject: input.subject,
560
+ status: "pending",
561
+ activeForm: input.activeForm,
562
+ description: input.description,
563
+ });
564
+ }
565
+ export function applyTaskUpdate(state, input) {
566
+ if (!input?.taskId)
567
+ return;
568
+ if (input.status === "deleted") {
569
+ state.delete(input.taskId);
570
+ return;
571
+ }
572
+ const existing = state.get(input.taskId);
573
+ // Without a subject from either the existing entry or the update payload,
574
+ // we'd produce a plan entry with empty `content` — drop the update.
575
+ const subject = input.subject ?? existing?.subject;
576
+ if (!subject)
577
+ return;
578
+ state.set(input.taskId, {
579
+ subject,
580
+ status: input.status ?? existing?.status ?? "pending",
581
+ activeForm: input.activeForm ?? existing?.activeForm,
582
+ description: input.description ?? existing?.description,
583
+ });
584
+ }
585
+ export function taskStateToPlanEntries(state) {
586
+ return Array.from(state.values()).map((task) => ({
587
+ content: task.subject,
588
+ status: task.status,
483
589
  priority: "medium",
484
590
  }));
485
591
  }
@@ -493,12 +599,13 @@ export function markdownEscape(text) {
493
599
  return escape + "\n" + text + (text.endsWith("\n") ? "" : "\n") + escape;
494
600
  }
495
601
  /**
496
- * Builds diff ToolUpdate content from the structured Edit toolResponse provided
497
- * by the PostToolUse hook. Unlike parsing the plain unified diff string, this uses
498
- * the pre-parsed structuredPatch which supports multiple replacement sites (replaceAll)
499
- * and always includes context lines for better readability.
602
+ * Builds diff ToolUpdate content from the structured toolResponse provided by
603
+ * the PostToolUse hook for diff-producing tools (Edit, Write). Unlike parsing
604
+ * the plain unified diff string, this uses the pre-parsed structuredPatch
605
+ * which supports multiple replacement sites (replaceAll) and always includes
606
+ * context lines for better readability.
500
607
  */
501
- export function toolUpdateFromEditToolResponse(toolResponse) {
608
+ export function toolUpdateFromDiffToolResponse(toolResponse) {
502
609
  if (!toolResponse || typeof toolResponse !== "object")
503
610
  return {};
504
611
  const response = toolResponse;
@@ -567,3 +674,40 @@ export const createPostToolUseHook = (logger = console, options) => async (input
567
674
  }
568
675
  return { continue: true };
569
676
  };
677
+ /**
678
+ * Hook callback for `TaskCreated` / `TaskCompleted` events. The SDK fires
679
+ * these for both user-facing TaskCreate tool calls and subagent task
680
+ * creation, giving us `task_id` + `task_subject` without having to parse
681
+ * tool_result payloads.
682
+ *
683
+ * Populating `taskState` from the hook means a later `TaskUpdate` (which
684
+ * typically only carries `taskId` + `status`) finds an existing entry with
685
+ * a real subject, instead of synthesizing a placeholder with empty content.
686
+ */
687
+ export const createTaskHook = (options) => async (input) => {
688
+ const taskId = "task_id" in input && typeof input.task_id === "string" ? input.task_id : undefined;
689
+ if (!taskId)
690
+ return { continue: true };
691
+ if (input.hook_event_name === "TaskCreated") {
692
+ if (!input.task_subject)
693
+ return { continue: true };
694
+ if (options.taskState.has(taskId))
695
+ return { continue: true };
696
+ options.taskState.set(taskId, {
697
+ subject: input.task_subject,
698
+ status: "pending",
699
+ description: input.task_description,
700
+ });
701
+ if (options.onChange)
702
+ await options.onChange();
703
+ }
704
+ else if (input.hook_event_name === "TaskCompleted") {
705
+ const existing = options.taskState.get(taskId);
706
+ if (!existing || existing.status === "completed")
707
+ return { continue: true };
708
+ options.taskState.set(taskId, { ...existing, status: "completed" });
709
+ if (options.onChange)
710
+ await options.onChange();
711
+ }
712
+ return { continue: true };
713
+ };
package/dist/utils.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { Readable, Writable } from "node:stream";
2
2
  import { WritableStream, ReadableStream } from "node:stream/web";
3
3
  import { Logger } from "./acp-agent.js";
4
- import { ClaudeCodeSettings } from "./settings.js";
5
4
  export declare class Pushable<T> implements AsyncIterable<T> {
6
5
  private queue;
7
6
  private resolvers;
@@ -14,6 +13,4 @@ export declare function nodeToWebWritable(nodeStream: Writable): WritableStream<
14
13
  export declare function nodeToWebReadable(nodeStream: Readable): ReadableStream<Uint8Array>;
15
14
  export declare function unreachable(value: never, logger?: Logger): void;
16
15
  export declare function sleep(time: number): Promise<void>;
17
- export declare function loadManagedSettings(): ClaudeCodeSettings | null;
18
- export declare function applyEnvironmentSettings(settings: ClaudeCodeSettings): void;
19
16
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjE,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAA0B,MAAM,eAAe,CAAC;AAG3E,qBAAa,QAAQ,CAAC,CAAC,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAClD,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,IAAI,CAAS;IAErB,IAAI,CAAC,IAAI,EAAE,CAAC;IASZ,GAAG;IAQH,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC;CAgB3C;AAGD,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAclF;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAUlF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,GAAE,MAAgB,QAQjE;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjD;AAED,wBAAgB,mBAAmB,IAAI,kBAAkB,GAAG,IAAI,CAM/D;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAM3E"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAGxC,qBAAa,QAAQ,CAAC,CAAC,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAClD,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,IAAI,CAAS;IAErB,IAAI,CAAC,IAAI,EAAE,CAAC;IASZ,GAAG;IAQH,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC;CAgB3C;AAGD,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAclF;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAUlF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,GAAE,MAAgB,QAQjE;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjD"}
package/dist/utils.js CHANGED
@@ -1,7 +1,5 @@
1
1
  // A pushable async iterable: allows you to push items and consume them with for-await.
2
2
  import { WritableStream, ReadableStream } from "node:stream/web";
3
- import { readFileSync } from "node:fs";
4
- import { getManagedSettingsPath } from "./settings.js";
5
3
  // Useful for bridging push-based and async-iterator-based code.
6
4
  export class Pushable {
7
5
  constructor() {
@@ -83,18 +81,3 @@ export function unreachable(value, logger = console) {
83
81
  export function sleep(time) {
84
82
  return new Promise((resolve) => setTimeout(resolve, time));
85
83
  }
86
- export function loadManagedSettings() {
87
- try {
88
- return JSON.parse(readFileSync(getManagedSettingsPath(), "utf8"));
89
- }
90
- catch {
91
- return null;
92
- }
93
- }
94
- export function applyEnvironmentSettings(settings) {
95
- if (settings.env) {
96
- for (const [key, value] of Object.entries(settings.env)) {
97
- process.env[key] = value;
98
- }
99
- }
100
- }
package/package.json CHANGED
@@ -3,8 +3,8 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.24.3",
7
- "description": "An ACP-compatible coding agent powered by the Claude Code SDK (TypeScript)",
6
+ "version": "0.37.0",
7
+ "description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
8
8
  "main": "dist/lib.js",
9
9
  "types": "dist/lib.d.ts",
10
10
  "bin": {
@@ -26,7 +26,7 @@
26
26
  "package.json"
27
27
  ],
28
28
  "scripts": {
29
- "build": "./node_modules/.bin/tsc",
29
+ "build": "tsc",
30
30
  "start": "node dist/index.js",
31
31
  "dev": "npm run build && npm run start",
32
32
  "lint": "eslint src --ext .ts",
@@ -60,22 +60,22 @@
60
60
  "author": "Zed Industries",
61
61
  "license": "Apache-2.0",
62
62
  "dependencies": {
63
- "@agentclientprotocol/sdk": "0.17.0",
64
- "@anthropic-ai/claude-agent-sdk": "0.2.84",
63
+ "@agentclientprotocol/sdk": "0.22.1",
64
+ "@anthropic-ai/claude-agent-sdk": "0.3.146",
65
65
  "zod": "^3.25.0 || ^4.0.0"
66
66
  },
67
67
  "devDependencies": {
68
- "@anthropic-ai/sdk": "0.81.0",
68
+ "@anthropic-ai/sdk": "0.97.1",
69
69
  "@eslint/js": "10.0.1",
70
- "@types/node": "25.5.0",
71
- "@typescript-eslint/eslint-plugin": "8.57.2",
72
- "@typescript-eslint/parser": "8.57.2",
73
- "eslint": "10.1.0",
70
+ "@types/node": "25.9.1",
71
+ "@typescript-eslint/eslint-plugin": "8.59.4",
72
+ "@typescript-eslint/parser": "8.59.4",
73
+ "eslint": "10.4.0",
74
74
  "eslint-config-prettier": "10.1.8",
75
- "globals": "17.4.0",
76
- "prettier": "3.8.1",
75
+ "globals": "17.6.0",
76
+ "prettier": "3.8.3",
77
77
  "ts-node": "10.9.2",
78
- "typescript": "5.9.3",
79
- "vitest": "4.1.1"
78
+ "typescript": "6.0.3",
79
+ "vitest": "4.1.7"
80
80
  }
81
81
  }
@@ -1,21 +0,0 @@
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
@@ -1 +0,0 @@
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,CA+qBX;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"}