@agentclientprotocol/claude-agent-acp 0.70.0 → 0.72.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/README.md +10 -9
- package/dist/acp-agent.d.ts +136 -49
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +1304 -724
- package/dist/acp-subagents.d.ts +82 -0
- package/dist/acp-subagents.d.ts.map +1 -0
- package/dist/acp-subagents.js +13 -0
- package/dist/air-extension.d.ts +23 -0
- package/dist/air-extension.d.ts.map +1 -0
- package/dist/air-extension.js +63 -0
- package/dist/async-tasks.d.ts +109 -0
- package/dist/async-tasks.d.ts.map +1 -0
- package/dist/async-tasks.js +613 -0
- package/dist/clear-context-coordinator.d.ts +60 -0
- package/dist/clear-context-coordinator.d.ts.map +1 -0
- package/dist/clear-context-coordinator.js +80 -0
- package/dist/exit-plan.d.ts +42 -0
- package/dist/exit-plan.d.ts.map +1 -0
- package/dist/exit-plan.js +154 -0
- package/dist/file-change-audit.d.ts.map +1 -1
- package/dist/file-change-audit.js +4 -33
- package/dist/fork-session.d.ts +12 -0
- package/dist/fork-session.d.ts.map +1 -0
- package/dist/fork-session.js +41 -0
- package/dist/native-subagents.d.ts +71 -0
- package/dist/native-subagents.d.ts.map +1 -0
- package/dist/native-subagents.js +422 -0
- package/dist/permissions/effects.d.ts +19 -0
- package/dist/permissions/effects.d.ts.map +1 -0
- package/dist/permissions/effects.js +166 -0
- package/dist/permissions/modes.d.ts +8 -0
- package/dist/permissions/modes.d.ts.map +1 -0
- package/dist/permissions/modes.js +41 -0
- package/dist/permissions/normalization.d.ts +6 -0
- package/dist/permissions/normalization.d.ts.map +1 -0
- package/dist/permissions/normalization.js +100 -0
- package/dist/permissions/options/filesystem.d.ts +9 -0
- package/dist/permissions/options/filesystem.d.ts.map +1 -0
- package/dist/permissions/options/filesystem.js +124 -0
- package/dist/permissions/options/shared.d.ts +39 -0
- package/dist/permissions/options/shared.d.ts.map +1 -0
- package/dist/permissions/options/shared.js +64 -0
- package/dist/permissions/options/shell.d.ts +5 -0
- package/dist/permissions/options/shell.d.ts.map +1 -0
- package/dist/permissions/options/shell.js +100 -0
- package/dist/permissions/options/tools.d.ts +11 -0
- package/dist/permissions/options/tools.d.ts.map +1 -0
- package/dist/permissions/options/tools.js +135 -0
- package/dist/permissions/options.d.ts +5 -0
- package/dist/permissions/options.d.ts.map +1 -0
- package/dist/permissions/options.js +60 -0
- package/dist/permissions/presentation.d.ts +15 -0
- package/dist/permissions/presentation.d.ts.map +1 -0
- package/dist/permissions/presentation.js +83 -0
- package/dist/permissions/response.d.ts +11 -0
- package/dist/permissions/response.d.ts.map +1 -0
- package/dist/permissions/response.js +19 -0
- package/dist/session-config-ids.d.ts +5 -0
- package/dist/session-config-ids.d.ts.map +1 -0
- package/dist/session-config-ids.js +4 -0
- package/dist/session-failure-extension.d.ts +2 -24
- package/dist/session-failure-extension.d.ts.map +1 -1
- package/dist/session-failure-extension.js +13 -40
- package/dist/session-mode.d.ts +66 -0
- package/dist/session-mode.d.ts.map +1 -0
- package/dist/session-mode.js +234 -0
- package/dist/session-titles.d.ts +77 -0
- package/dist/session-titles.d.ts.map +1 -0
- package/dist/session-titles.js +199 -0
- package/dist/tool-result-meta.d.ts +8 -0
- package/dist/tool-result-meta.d.ts.map +1 -0
- package/dist/tool-result-meta.js +19 -0
- package/dist/tools.d.ts +6 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +144 -20
- package/package.json +12 -12
package/dist/tools.js
CHANGED
|
@@ -211,15 +211,8 @@ export function toolInfoFromToolUse(toolUse, supportsTerminalOutput = false, cwd
|
|
|
211
211
|
}
|
|
212
212
|
case "WebSearch": {
|
|
213
213
|
const input = toolUse.input;
|
|
214
|
-
let label = input?.query ? `"${input.query}"` : "Web search";
|
|
215
|
-
if (input?.allowed_domains && input.allowed_domains.length > 0) {
|
|
216
|
-
label += ` (allowed: ${input.allowed_domains.join(", ")})`;
|
|
217
|
-
}
|
|
218
|
-
if (input?.blocked_domains && input.blocked_domains.length > 0) {
|
|
219
|
-
label += ` (blocked: ${input.blocked_domains.join(", ")})`;
|
|
220
|
-
}
|
|
221
214
|
return {
|
|
222
|
-
title:
|
|
215
|
+
title: input?.query ? `Search "${input.query}"` : "Web search",
|
|
223
216
|
kind: "fetch",
|
|
224
217
|
content: [],
|
|
225
218
|
};
|
|
@@ -284,7 +277,7 @@ export function toolInfoFromToolUse(toolUse, supportsTerminalOutput = false, cwd
|
|
|
284
277
|
case "ExitPlanMode": {
|
|
285
278
|
const planInput = toolUse.input;
|
|
286
279
|
return {
|
|
287
|
-
title: "
|
|
280
|
+
title: "Approve Plan",
|
|
288
281
|
kind: "switch_mode",
|
|
289
282
|
content: planInput?.plan
|
|
290
283
|
? [{ type: "content", content: { type: "text", text: planInput.plan } }]
|
|
@@ -420,6 +413,45 @@ function stripAgentTrailerFromContent(content) {
|
|
|
420
413
|
}
|
|
421
414
|
return content;
|
|
422
415
|
}
|
|
416
|
+
/** Leading model-directed note the CLI prepends to a subagent's report when
|
|
417
|
+
* the agent stopped at its maxTurns limit (CLI 2.1.246+); the result still
|
|
418
|
+
* ships as `status: "completed"`. Two body variants follow this prefix, and
|
|
419
|
+
* the trailing "Send the agent a message (SendMessage) …" sentence is
|
|
420
|
+
* omitted for some agent types — anchor only the stable prefix so a format
|
|
421
|
+
* change makes the replacement stop matching rather than mangle a report. */
|
|
422
|
+
const PARTIAL_OUTPUT_NOTE = /^NOTE: this agent stopped at its \d+-turn limit before finishing\./;
|
|
423
|
+
/** Client-facing replacement: the partial-output fact matters to the user,
|
|
424
|
+
* but the SendMessage continuation instruction is model-directed and
|
|
425
|
+
* meaningless over ACP. */
|
|
426
|
+
const PARTIAL_OUTPUT_LABEL = "[Agent stopped at its turn limit — the output below is partial]";
|
|
427
|
+
/** Replace a leading partial-output note paragraph with the concise
|
|
428
|
+
* client-facing label, leaving the report that follows intact. */
|
|
429
|
+
function replacePartialNoteInText(text) {
|
|
430
|
+
if (!PARTIAL_OUTPUT_NOTE.test(text))
|
|
431
|
+
return text;
|
|
432
|
+
const paragraphEnd = text.indexOf("\n\n");
|
|
433
|
+
const report = paragraphEnd === -1 ? "" : text.slice(paragraphEnd + 2).trimStart();
|
|
434
|
+
return report ? `${PARTIAL_OUTPUT_LABEL}\n\n${report}` : PARTIAL_OUTPUT_LABEL;
|
|
435
|
+
}
|
|
436
|
+
/** Apply {@link replacePartialNoteInText} to an Agent/Task result `content`.
|
|
437
|
+
* In the structured AgentOutput lane the note is its own leading text block;
|
|
438
|
+
* in the raw lane it is the first paragraph of the text — both reduce to
|
|
439
|
+
* transforming the first text block (or the plain string). */
|
|
440
|
+
function replacePartialOutputNote(content) {
|
|
441
|
+
if (typeof content === "string") {
|
|
442
|
+
return replacePartialNoteInText(content);
|
|
443
|
+
}
|
|
444
|
+
if (Array.isArray(content) && content.length > 0) {
|
|
445
|
+
const [first, ...rest] = content;
|
|
446
|
+
if (first !== null &&
|
|
447
|
+
typeof first === "object" &&
|
|
448
|
+
first.type === "text" &&
|
|
449
|
+
typeof first.text === "string") {
|
|
450
|
+
return [{ ...first, text: replacePartialNoteInText(first.text) }, ...rest];
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return content;
|
|
454
|
+
}
|
|
423
455
|
export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOutput = false, toolUseResult) {
|
|
424
456
|
if ("is_error" in toolResult &&
|
|
425
457
|
toolResult.is_error &&
|
|
@@ -656,7 +688,11 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu
|
|
|
656
688
|
// A completed subagent can end with zero text blocks; an empty
|
|
657
689
|
// structured render would beat the raw fallback for no benefit.
|
|
658
690
|
structured.content.length > 0) {
|
|
659
|
-
return toAcpContentUpdate(
|
|
691
|
+
return toAcpContentUpdate(
|
|
692
|
+
// A maxTurns-stopped subagent still completes, with a model-directed
|
|
693
|
+
// partial-output note prepended to its report — swap it for a
|
|
694
|
+
// client-facing label (see PARTIAL_OUTPUT_NOTE).
|
|
695
|
+
replacePartialOutputNote(structured.content), "is_error" in toolResult ? toolResult.is_error : false);
|
|
660
696
|
}
|
|
661
697
|
// No structured report to render from (replayed sessions —
|
|
662
698
|
// getSessionMessages doesn't expose the transcript's toolUseResult —
|
|
@@ -665,7 +701,10 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu
|
|
|
665
701
|
// tail-anchored strip is the only cleanup available; if the trailer
|
|
666
702
|
// format changes it simply stops matching and the full raw text
|
|
667
703
|
// renders, no worse than before.
|
|
668
|
-
return toAcpContentUpdate(
|
|
704
|
+
return toAcpContentUpdate(
|
|
705
|
+
// Head and tail cleanups are independent: the partial-output note
|
|
706
|
+
// leads the raw text the same way it leads the structured content.
|
|
707
|
+
replacePartialOutputNote(stripAgentTrailerFromContent(toolResult.content)), "is_error" in toolResult ? toolResult.is_error : false);
|
|
669
708
|
}
|
|
670
709
|
case "Skill": {
|
|
671
710
|
return {};
|
|
@@ -718,6 +757,15 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu
|
|
|
718
757
|
function formatWebSearchHit(hit) {
|
|
719
758
|
return `${hit.title} (${hit.url})`;
|
|
720
759
|
}
|
|
760
|
+
/** Human-readable size for the document placeholder ("312 B", "2.4 KB",
|
|
761
|
+
* "1.3 MB"). */
|
|
762
|
+
function formatByteSize(bytes) {
|
|
763
|
+
if (bytes < 1024)
|
|
764
|
+
return `${bytes} B`;
|
|
765
|
+
if (bytes < 1024 * 1024)
|
|
766
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
767
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
768
|
+
}
|
|
721
769
|
function toAcpContentUpdate(content, isError = false) {
|
|
722
770
|
if (Array.isArray(content) && content.length > 0) {
|
|
723
771
|
return {
|
|
@@ -775,6 +823,28 @@ function toAcpContentBlock(content, isError) {
|
|
|
775
823
|
return wrapText(content.source.type === "url"
|
|
776
824
|
? `[image: ${content.source.url}]`
|
|
777
825
|
: "[image: file reference]");
|
|
826
|
+
case "document": {
|
|
827
|
+
// A PDF Read delivers its raw `document` block inside the tool_result
|
|
828
|
+
// content (SDK 0.3.243 moved it here from a separate follow-up user
|
|
829
|
+
// message; the MultiRead documents lane always lived here). ACP has no
|
|
830
|
+
// document block and the base64 payload can be megabytes — render a
|
|
831
|
+
// compact placeholder, never the data, matching the CLI's own compact
|
|
832
|
+
// "Read PDF (size)" rendering.
|
|
833
|
+
const title = typeof content.title === "string" && content.title.length > 0 ? ` "${content.title}"` : "";
|
|
834
|
+
const source = content.source;
|
|
835
|
+
switch (source.type) {
|
|
836
|
+
case "url":
|
|
837
|
+
return wrapText(`[document${title}: ${source.url}]`);
|
|
838
|
+
case "base64":
|
|
839
|
+
case "text":
|
|
840
|
+
// base64 inflates the byte count by 4/3; plain text is 1:1.
|
|
841
|
+
return wrapText(`[document${title}: ${source.media_type}, ${formatByteSize(source.type === "base64"
|
|
842
|
+
? Math.floor((source.data.length * 3) / 4)
|
|
843
|
+
: source.data.length)}]`);
|
|
844
|
+
default:
|
|
845
|
+
return wrapText(`[document${title}]`);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
778
848
|
case "tool_reference":
|
|
779
849
|
return wrapText(`Tool: ${content.tool_name}`);
|
|
780
850
|
case "tool_search_tool_search_result":
|
|
@@ -1040,6 +1110,32 @@ export function toolUpdateFromDiffToolResponse(toolResponse) {
|
|
|
1040
1110
|
});
|
|
1041
1111
|
}
|
|
1042
1112
|
}
|
|
1113
|
+
// A Write `update` can arrive with an empty structuredPatch — nothing
|
|
1114
|
+
// changed, the diff timed out, or the previous content was too large to
|
|
1115
|
+
// diff (originalFile null; SDK 0.3.252 documents the lane). Returning `{}`
|
|
1116
|
+
// would leave Write's optimistic tool_use-time content standing, and that
|
|
1117
|
+
// was built with `oldText: null` — "creation" semantics — so an overwrite
|
|
1118
|
+
// of a large existing file would render as creating it. Emit a truthful
|
|
1119
|
+
// replacement instead. Gated on `type` so Edit (whose output carries no
|
|
1120
|
+
// `type` and whose optimistic old/new diff is already truthful) keeps the
|
|
1121
|
+
// empty-return behavior.
|
|
1122
|
+
if (content.length === 0 && response.type === "update" && typeof response.content === "string") {
|
|
1123
|
+
locations.push({ path: response.filePath });
|
|
1124
|
+
content.push(typeof response.originalFile === "string"
|
|
1125
|
+
? {
|
|
1126
|
+
type: "diff",
|
|
1127
|
+
path: response.filePath,
|
|
1128
|
+
oldText: response.originalFile,
|
|
1129
|
+
newText: response.content,
|
|
1130
|
+
}
|
|
1131
|
+
: {
|
|
1132
|
+
type: "content",
|
|
1133
|
+
content: {
|
|
1134
|
+
type: "text",
|
|
1135
|
+
text: `Updated \`${response.filePath}\` (previous content too large to diff)`,
|
|
1136
|
+
},
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1043
1139
|
const result = {};
|
|
1044
1140
|
if (content.length > 0)
|
|
1045
1141
|
result.content = content;
|
|
@@ -1047,14 +1143,38 @@ export function toolUpdateFromDiffToolResponse(toolResponse) {
|
|
|
1047
1143
|
result.locations = locations;
|
|
1048
1144
|
return result;
|
|
1049
1145
|
}
|
|
1050
|
-
/*
|
|
1051
|
-
|
|
1146
|
+
/* Callbacks are keyed globally because the SDK hook is process-wide, but each
|
|
1147
|
+
* entry retains its owning ACP session so cancellation/teardown can release it. */
|
|
1148
|
+
const toolUseCallbacks = new Map();
|
|
1052
1149
|
/* Setup callbacks that will be called when receiving hooks from Claude Code */
|
|
1053
|
-
export const registerHookCallback = (toolUseID, { onPostToolUseHook, }) => {
|
|
1054
|
-
|
|
1150
|
+
export const registerHookCallback = (toolUseID, { onPostToolUseHook, }, ownerId) => {
|
|
1151
|
+
unregisterHookCallback(toolUseID);
|
|
1152
|
+
toolUseCallbacks.set(toolUseID, {
|
|
1153
|
+
ownerId,
|
|
1055
1154
|
onPostToolUseHook,
|
|
1056
|
-
};
|
|
1155
|
+
});
|
|
1057
1156
|
};
|
|
1157
|
+
export function unregisterHookCallback(toolUseID) {
|
|
1158
|
+
const callback = toolUseCallbacks.get(toolUseID);
|
|
1159
|
+
if (callback?.cleanupTimer)
|
|
1160
|
+
clearTimeout(callback.cleanupTimer);
|
|
1161
|
+
toolUseCallbacks.delete(toolUseID);
|
|
1162
|
+
}
|
|
1163
|
+
/** PostToolUse normally follows tool_result, so keep the callback for a short
|
|
1164
|
+
* grace period while still bounding retention when the hook never arrives. */
|
|
1165
|
+
export function completeHookCallback(toolUseID) {
|
|
1166
|
+
const callback = toolUseCallbacks.get(toolUseID);
|
|
1167
|
+
if (!callback || callback.cleanupTimer)
|
|
1168
|
+
return;
|
|
1169
|
+
callback.cleanupTimer = setTimeout(() => unregisterHookCallback(toolUseID), 30_000);
|
|
1170
|
+
callback.cleanupTimer.unref?.();
|
|
1171
|
+
}
|
|
1172
|
+
export function clearHookCallbacks(ownerId) {
|
|
1173
|
+
for (const [toolUseID, callback] of toolUseCallbacks) {
|
|
1174
|
+
if (callback.ownerId === ownerId)
|
|
1175
|
+
unregisterHookCallback(toolUseID);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1058
1178
|
/* A callback for Claude Code that is called when receiving a PostToolUse hook */
|
|
1059
1179
|
export const createPostToolUseHook = (options) => async (input, toolUseID) => {
|
|
1060
1180
|
if (input.hook_event_name === "PostToolUse") {
|
|
@@ -1063,11 +1183,15 @@ export const createPostToolUseHook = (options) => async (input, toolUseID) => {
|
|
|
1063
1183
|
await options.onEnterPlanMode();
|
|
1064
1184
|
}
|
|
1065
1185
|
if (toolUseID) {
|
|
1066
|
-
const onPostToolUseHook = toolUseCallbacks
|
|
1067
|
-
|
|
1068
|
-
|
|
1186
|
+
const onPostToolUseHook = toolUseCallbacks.get(toolUseID)?.onPostToolUseHook;
|
|
1187
|
+
try {
|
|
1188
|
+
if (onPostToolUseHook) {
|
|
1189
|
+
await onPostToolUseHook(toolUseID, input.tool_input, input.tool_response);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
finally {
|
|
1193
|
+
unregisterHookCallback(toolUseID);
|
|
1069
1194
|
}
|
|
1070
|
-
delete toolUseCallbacks[toolUseID]; // Cleanup after execution
|
|
1071
1195
|
}
|
|
1072
1196
|
}
|
|
1073
1197
|
return { continue: true };
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.72.0",
|
|
7
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",
|
|
@@ -65,23 +65,23 @@
|
|
|
65
65
|
"node": ">=22"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@agentclientprotocol/sdk": "1.
|
|
69
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
70
|
-
"zod": "^
|
|
68
|
+
"@agentclientprotocol/sdk": "1.4.0",
|
|
69
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.252",
|
|
70
|
+
"zod": "^4.0.0"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
|
-
"@anthropic-ai/sdk": "0.
|
|
73
|
+
"@anthropic-ai/sdk": "0.122.0",
|
|
74
74
|
"@eslint/js": "10.0.1",
|
|
75
|
-
"@tsconfig/node22": "22.0.
|
|
76
|
-
"@types/node": "26.
|
|
77
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
78
|
-
"@typescript-eslint/parser": "8.
|
|
79
|
-
"eslint": "10.
|
|
75
|
+
"@tsconfig/node22": "22.0.6",
|
|
76
|
+
"@types/node": "26.4.0",
|
|
77
|
+
"@typescript-eslint/eslint-plugin": "8.69.0",
|
|
78
|
+
"@typescript-eslint/parser": "8.69.0",
|
|
79
|
+
"eslint": "10.9.1",
|
|
80
80
|
"eslint-config-prettier": "10.1.8",
|
|
81
|
-
"globals": "17.
|
|
81
|
+
"globals": "17.12.0",
|
|
82
82
|
"prettier": "3.9.6",
|
|
83
83
|
"ts-node": "10.9.2",
|
|
84
84
|
"typescript": "6.0.3",
|
|
85
|
-
"vitest": "4.1.
|
|
85
|
+
"vitest": "4.1.11"
|
|
86
86
|
}
|
|
87
87
|
}
|