acp-extension-claude 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/LICENSE +222 -0
- package/README.md +53 -0
- package/dist/acp-agent.d.ts +103 -0
- package/dist/acp-agent.d.ts.map +1 -0
- package/dist/acp-agent.js +944 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/lib.d.ts +7 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +6 -0
- package/dist/mcp-server.d.ts +21 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +782 -0
- package/dist/settings.d.ts +123 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +422 -0
- package/dist/tests/acp-agent.test.d.ts +2 -0
- package/dist/tests/acp-agent.test.d.ts.map +1 -0
- package/dist/tests/acp-agent.test.js +753 -0
- package/dist/tests/extract-lines.test.d.ts +2 -0
- package/dist/tests/extract-lines.test.d.ts.map +1 -0
- package/dist/tests/extract-lines.test.js +79 -0
- package/dist/tests/replace-and-calculate-location.test.d.ts +2 -0
- package/dist/tests/replace-and-calculate-location.test.d.ts.map +1 -0
- package/dist/tests/replace-and-calculate-location.test.js +266 -0
- package/dist/tests/settings.test.d.ts +2 -0
- package/dist/tests/settings.test.d.ts.map +1 -0
- package/dist/tests/settings.test.js +462 -0
- package/dist/tests/typescript-declarations.test.d.ts +2 -0
- package/dist/tests/typescript-declarations.test.d.ts.map +1 -0
- package/dist/tests/typescript-declarations.test.js +473 -0
- package/dist/tools.d.ts +50 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +555 -0
- package/dist/utils.d.ts +32 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +150 -0
- package/package.json +71 -0
package/dist/tools.js
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import { SYSTEM_REMINDER } from "./mcp-server.js";
|
|
2
|
+
import * as diff from "diff";
|
|
3
|
+
const acpUnqualifiedToolNames = {
|
|
4
|
+
read: "Read",
|
|
5
|
+
edit: "Edit",
|
|
6
|
+
write: "Write",
|
|
7
|
+
bash: "Bash",
|
|
8
|
+
killShell: "KillShell",
|
|
9
|
+
bashOutput: "BashOutput",
|
|
10
|
+
};
|
|
11
|
+
export const ACP_TOOL_NAME_PREFIX = "mcp__acp__";
|
|
12
|
+
export const acpToolNames = {
|
|
13
|
+
read: ACP_TOOL_NAME_PREFIX + acpUnqualifiedToolNames.read,
|
|
14
|
+
edit: ACP_TOOL_NAME_PREFIX + acpUnqualifiedToolNames.edit,
|
|
15
|
+
write: ACP_TOOL_NAME_PREFIX + acpUnqualifiedToolNames.write,
|
|
16
|
+
bash: ACP_TOOL_NAME_PREFIX + acpUnqualifiedToolNames.bash,
|
|
17
|
+
killShell: ACP_TOOL_NAME_PREFIX + acpUnqualifiedToolNames.killShell,
|
|
18
|
+
bashOutput: ACP_TOOL_NAME_PREFIX + acpUnqualifiedToolNames.bashOutput,
|
|
19
|
+
};
|
|
20
|
+
export const EDIT_TOOL_NAMES = [acpToolNames.edit, acpToolNames.write];
|
|
21
|
+
export function toolInfoFromToolUse(toolUse) {
|
|
22
|
+
const name = toolUse.name;
|
|
23
|
+
const input = toolUse.input;
|
|
24
|
+
switch (name) {
|
|
25
|
+
case "Task":
|
|
26
|
+
return {
|
|
27
|
+
title: input?.description ? input.description : "Task",
|
|
28
|
+
kind: "think",
|
|
29
|
+
content: input && input.prompt
|
|
30
|
+
? [
|
|
31
|
+
{
|
|
32
|
+
type: "content",
|
|
33
|
+
content: { type: "text", text: input.prompt },
|
|
34
|
+
},
|
|
35
|
+
]
|
|
36
|
+
: [],
|
|
37
|
+
};
|
|
38
|
+
case "NotebookRead":
|
|
39
|
+
return {
|
|
40
|
+
title: input?.notebook_path ? `Read Notebook ${input.notebook_path}` : "Read Notebook",
|
|
41
|
+
kind: "read",
|
|
42
|
+
content: [],
|
|
43
|
+
locations: input?.notebook_path ? [{ path: input.notebook_path }] : [],
|
|
44
|
+
};
|
|
45
|
+
case "NotebookEdit":
|
|
46
|
+
return {
|
|
47
|
+
title: input?.notebook_path ? `Edit Notebook ${input.notebook_path}` : "Edit Notebook",
|
|
48
|
+
kind: "edit",
|
|
49
|
+
content: input && input.new_source
|
|
50
|
+
? [
|
|
51
|
+
{
|
|
52
|
+
type: "content",
|
|
53
|
+
content: { type: "text", text: input.new_source },
|
|
54
|
+
},
|
|
55
|
+
]
|
|
56
|
+
: [],
|
|
57
|
+
locations: input?.notebook_path ? [{ path: input.notebook_path }] : [],
|
|
58
|
+
};
|
|
59
|
+
case "Bash":
|
|
60
|
+
case acpToolNames.bash:
|
|
61
|
+
return {
|
|
62
|
+
title: input?.command ? "`" + input.command.replaceAll("`", "\\`") + "`" : "Terminal",
|
|
63
|
+
kind: "execute",
|
|
64
|
+
content: input && input.description
|
|
65
|
+
? [
|
|
66
|
+
{
|
|
67
|
+
type: "content",
|
|
68
|
+
content: { type: "text", text: input.description },
|
|
69
|
+
},
|
|
70
|
+
]
|
|
71
|
+
: [],
|
|
72
|
+
};
|
|
73
|
+
case "BashOutput":
|
|
74
|
+
case acpToolNames.bashOutput:
|
|
75
|
+
return {
|
|
76
|
+
title: "Tail Logs",
|
|
77
|
+
kind: "execute",
|
|
78
|
+
content: [],
|
|
79
|
+
};
|
|
80
|
+
case "KillShell":
|
|
81
|
+
case acpToolNames.killShell:
|
|
82
|
+
return {
|
|
83
|
+
title: "Kill Process",
|
|
84
|
+
kind: "execute",
|
|
85
|
+
content: [],
|
|
86
|
+
};
|
|
87
|
+
case acpToolNames.read: {
|
|
88
|
+
let limit = "";
|
|
89
|
+
if (input.limit) {
|
|
90
|
+
limit =
|
|
91
|
+
" (" + ((input.offset ?? 0) + 1) + " - " + ((input.offset ?? 0) + input.limit) + ")";
|
|
92
|
+
}
|
|
93
|
+
else if (input.offset) {
|
|
94
|
+
limit = " (from line " + (input.offset + 1) + ")";
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
title: "Read " + (input.file_path ?? "File") + limit,
|
|
98
|
+
kind: "read",
|
|
99
|
+
locations: input.file_path
|
|
100
|
+
? [
|
|
101
|
+
{
|
|
102
|
+
path: input.file_path,
|
|
103
|
+
line: input.offset ?? 0,
|
|
104
|
+
},
|
|
105
|
+
]
|
|
106
|
+
: [],
|
|
107
|
+
content: [],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
case "Read":
|
|
111
|
+
return {
|
|
112
|
+
title: "Read File",
|
|
113
|
+
kind: "read",
|
|
114
|
+
content: [],
|
|
115
|
+
locations: input.file_path
|
|
116
|
+
? [
|
|
117
|
+
{
|
|
118
|
+
path: input.file_path,
|
|
119
|
+
line: input.offset ?? 0,
|
|
120
|
+
},
|
|
121
|
+
]
|
|
122
|
+
: [],
|
|
123
|
+
};
|
|
124
|
+
case "LS":
|
|
125
|
+
return {
|
|
126
|
+
title: `List the ${input?.path ? "`" + input.path + "`" : "current"} directory's contents`,
|
|
127
|
+
kind: "search",
|
|
128
|
+
content: [],
|
|
129
|
+
locations: [],
|
|
130
|
+
};
|
|
131
|
+
case acpToolNames.edit:
|
|
132
|
+
case "Edit": {
|
|
133
|
+
const path = input?.file_path ?? input?.file_path;
|
|
134
|
+
return {
|
|
135
|
+
title: path ? `Edit \`${path}\`` : "Edit",
|
|
136
|
+
kind: "edit",
|
|
137
|
+
content: input && path
|
|
138
|
+
? [
|
|
139
|
+
{
|
|
140
|
+
type: "diff",
|
|
141
|
+
path,
|
|
142
|
+
oldText: input.old_string ?? null,
|
|
143
|
+
newText: input.new_string ?? "",
|
|
144
|
+
},
|
|
145
|
+
]
|
|
146
|
+
: [],
|
|
147
|
+
locations: path ? [{ path }] : undefined,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
case acpToolNames.write: {
|
|
151
|
+
let content = [];
|
|
152
|
+
if (input && input.file_path) {
|
|
153
|
+
content = [
|
|
154
|
+
{
|
|
155
|
+
type: "diff",
|
|
156
|
+
path: input.file_path,
|
|
157
|
+
oldText: null,
|
|
158
|
+
newText: input.content,
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
else if (input && input.content) {
|
|
163
|
+
content = [
|
|
164
|
+
{
|
|
165
|
+
type: "content",
|
|
166
|
+
content: { type: "text", text: input.content },
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
title: input?.file_path ? `Write ${input.file_path}` : "Write",
|
|
172
|
+
kind: "edit",
|
|
173
|
+
content,
|
|
174
|
+
locations: input?.file_path ? [{ path: input.file_path }] : [],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
case "Write":
|
|
178
|
+
return {
|
|
179
|
+
title: input?.file_path ? `Write ${input.file_path}` : "Write",
|
|
180
|
+
kind: "edit",
|
|
181
|
+
content: input && input.file_path
|
|
182
|
+
? [
|
|
183
|
+
{
|
|
184
|
+
type: "diff",
|
|
185
|
+
path: input.file_path,
|
|
186
|
+
oldText: null,
|
|
187
|
+
newText: input.content,
|
|
188
|
+
},
|
|
189
|
+
]
|
|
190
|
+
: [],
|
|
191
|
+
locations: input?.file_path ? [{ path: input.file_path }] : [],
|
|
192
|
+
};
|
|
193
|
+
case "Glob": {
|
|
194
|
+
let label = "Find";
|
|
195
|
+
if (input.path) {
|
|
196
|
+
label += ` \`${input.path}\``;
|
|
197
|
+
}
|
|
198
|
+
if (input.pattern) {
|
|
199
|
+
label += ` \`${input.pattern}\``;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
title: label,
|
|
203
|
+
kind: "search",
|
|
204
|
+
content: [],
|
|
205
|
+
locations: input.path ? [{ path: input.path }] : [],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
case "Grep": {
|
|
209
|
+
let label = "grep";
|
|
210
|
+
if (input["-i"]) {
|
|
211
|
+
label += " -i";
|
|
212
|
+
}
|
|
213
|
+
if (input["-n"]) {
|
|
214
|
+
label += " -n";
|
|
215
|
+
}
|
|
216
|
+
if (input["-A"] !== undefined) {
|
|
217
|
+
label += ` -A ${input["-A"]}`;
|
|
218
|
+
}
|
|
219
|
+
if (input["-B"] !== undefined) {
|
|
220
|
+
label += ` -B ${input["-B"]}`;
|
|
221
|
+
}
|
|
222
|
+
if (input["-C"] !== undefined) {
|
|
223
|
+
label += ` -C ${input["-C"]}`;
|
|
224
|
+
}
|
|
225
|
+
if (input.output_mode) {
|
|
226
|
+
switch (input.output_mode) {
|
|
227
|
+
case "FilesWithMatches":
|
|
228
|
+
label += " -l";
|
|
229
|
+
break;
|
|
230
|
+
case "Count":
|
|
231
|
+
label += " -c";
|
|
232
|
+
break;
|
|
233
|
+
case "Content":
|
|
234
|
+
default:
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (input.head_limit !== undefined) {
|
|
239
|
+
label += ` | head -${input.head_limit}`;
|
|
240
|
+
}
|
|
241
|
+
if (input.glob) {
|
|
242
|
+
label += ` --include="${input.glob}"`;
|
|
243
|
+
}
|
|
244
|
+
if (input.type) {
|
|
245
|
+
label += ` --type=${input.type}`;
|
|
246
|
+
}
|
|
247
|
+
if (input.multiline) {
|
|
248
|
+
label += " -P";
|
|
249
|
+
}
|
|
250
|
+
if (input.pattern) {
|
|
251
|
+
label += ` "${input.pattern}"`;
|
|
252
|
+
}
|
|
253
|
+
if (input.path) {
|
|
254
|
+
label += ` ${input.path}`;
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
title: label,
|
|
258
|
+
kind: "search",
|
|
259
|
+
content: [],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
case "WebFetch":
|
|
263
|
+
return {
|
|
264
|
+
title: input?.url ? `Fetch ${input.url}` : "Fetch",
|
|
265
|
+
kind: "fetch",
|
|
266
|
+
content: input && input.prompt
|
|
267
|
+
? [
|
|
268
|
+
{
|
|
269
|
+
type: "content",
|
|
270
|
+
content: { type: "text", text: input.prompt },
|
|
271
|
+
},
|
|
272
|
+
]
|
|
273
|
+
: [],
|
|
274
|
+
};
|
|
275
|
+
case "WebSearch": {
|
|
276
|
+
let label = `"${input.query}"`;
|
|
277
|
+
if (input.allowed_domains && input.allowed_domains.length > 0) {
|
|
278
|
+
label += ` (allowed: ${input.allowed_domains.join(", ")})`;
|
|
279
|
+
}
|
|
280
|
+
if (input.blocked_domains && input.blocked_domains.length > 0) {
|
|
281
|
+
label += ` (blocked: ${input.blocked_domains.join(", ")})`;
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
title: label,
|
|
285
|
+
kind: "fetch",
|
|
286
|
+
content: [],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
case "TodoWrite":
|
|
290
|
+
return {
|
|
291
|
+
title: Array.isArray(input?.todos)
|
|
292
|
+
? `Update TODOs: ${input.todos.map((todo) => todo.content).join(", ")}`
|
|
293
|
+
: "Update TODOs",
|
|
294
|
+
kind: "think",
|
|
295
|
+
content: [],
|
|
296
|
+
};
|
|
297
|
+
case "ExitPlanMode":
|
|
298
|
+
return {
|
|
299
|
+
title: "Ready to code?",
|
|
300
|
+
kind: "switch_mode",
|
|
301
|
+
content: input && input.plan
|
|
302
|
+
? [{ type: "content", content: { type: "text", text: input.plan } }]
|
|
303
|
+
: [],
|
|
304
|
+
};
|
|
305
|
+
case "Other": {
|
|
306
|
+
let output;
|
|
307
|
+
try {
|
|
308
|
+
output = JSON.stringify(input, null, 2);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
output = typeof input === "string" ? input : "{}";
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
title: name || "Unknown Tool",
|
|
315
|
+
kind: "other",
|
|
316
|
+
content: [
|
|
317
|
+
{
|
|
318
|
+
type: "content",
|
|
319
|
+
content: {
|
|
320
|
+
type: "text",
|
|
321
|
+
text: `\`\`\`json\n${output}\`\`\``,
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
default:
|
|
328
|
+
return {
|
|
329
|
+
title: name || "Unknown Tool",
|
|
330
|
+
kind: "other",
|
|
331
|
+
content: [],
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
export function toolUpdateFromToolResult(toolResult, toolUse) {
|
|
336
|
+
if ("is_error" in toolResult &&
|
|
337
|
+
toolResult.is_error &&
|
|
338
|
+
toolResult.content &&
|
|
339
|
+
toolResult.content.length > 0) {
|
|
340
|
+
// Only return errors
|
|
341
|
+
return toAcpContentUpdate(toolResult.content, true);
|
|
342
|
+
}
|
|
343
|
+
switch (toolUse?.name) {
|
|
344
|
+
case "Read":
|
|
345
|
+
case acpToolNames.read:
|
|
346
|
+
if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
|
|
347
|
+
return {
|
|
348
|
+
content: toolResult.content.map((content) => ({
|
|
349
|
+
type: "content",
|
|
350
|
+
content: content.type === "text"
|
|
351
|
+
? {
|
|
352
|
+
type: "text",
|
|
353
|
+
text: markdownEscape(content.text.replace(SYSTEM_REMINDER, "")),
|
|
354
|
+
}
|
|
355
|
+
: content,
|
|
356
|
+
})),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
else if (typeof toolResult.content === "string" && toolResult.content.length > 0) {
|
|
360
|
+
return {
|
|
361
|
+
content: [
|
|
362
|
+
{
|
|
363
|
+
type: "content",
|
|
364
|
+
content: {
|
|
365
|
+
type: "text",
|
|
366
|
+
text: markdownEscape(toolResult.content.replace(SYSTEM_REMINDER, "")),
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
],
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
return {};
|
|
373
|
+
case acpToolNames.edit: {
|
|
374
|
+
const content = [];
|
|
375
|
+
const locations = [];
|
|
376
|
+
if (Array.isArray(toolResult.content) &&
|
|
377
|
+
toolResult.content.length > 0 &&
|
|
378
|
+
"text" in toolResult.content[0] &&
|
|
379
|
+
typeof toolResult.content[0].text === "string") {
|
|
380
|
+
const patches = diff.parsePatch(toolResult.content[0].text);
|
|
381
|
+
console.error(JSON.stringify(patches));
|
|
382
|
+
for (const { oldFileName, newFileName, hunks } of patches) {
|
|
383
|
+
for (const { lines, newStart } of hunks) {
|
|
384
|
+
const oldText = [];
|
|
385
|
+
const newText = [];
|
|
386
|
+
for (const line of lines) {
|
|
387
|
+
if (line.startsWith("-")) {
|
|
388
|
+
oldText.push(line.slice(1));
|
|
389
|
+
}
|
|
390
|
+
else if (line.startsWith("+")) {
|
|
391
|
+
newText.push(line.slice(1));
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
oldText.push(line.slice(1));
|
|
395
|
+
newText.push(line.slice(1));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (oldText.length > 0 || newText.length > 0) {
|
|
399
|
+
locations.push({ path: newFileName || oldFileName, line: newStart });
|
|
400
|
+
content.push({
|
|
401
|
+
type: "diff",
|
|
402
|
+
path: newFileName || oldFileName,
|
|
403
|
+
oldText: oldText.join("\n") || null,
|
|
404
|
+
newText: newText.join("\n"),
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const result = {};
|
|
411
|
+
if (content.length > 0) {
|
|
412
|
+
result.content = content;
|
|
413
|
+
}
|
|
414
|
+
if (locations.length > 0) {
|
|
415
|
+
result.locations = locations;
|
|
416
|
+
}
|
|
417
|
+
return result;
|
|
418
|
+
}
|
|
419
|
+
case acpToolNames.bash:
|
|
420
|
+
case "edit":
|
|
421
|
+
case "Edit":
|
|
422
|
+
case acpToolNames.write:
|
|
423
|
+
case "Write": {
|
|
424
|
+
return {};
|
|
425
|
+
}
|
|
426
|
+
case "ExitPlanMode": {
|
|
427
|
+
return { title: "Exited Plan Mode" };
|
|
428
|
+
}
|
|
429
|
+
case "Task":
|
|
430
|
+
case "NotebookEdit":
|
|
431
|
+
case "NotebookRead":
|
|
432
|
+
case "TodoWrite":
|
|
433
|
+
case "exit_plan_mode":
|
|
434
|
+
case "Bash":
|
|
435
|
+
case "BashOutput":
|
|
436
|
+
case "KillBash":
|
|
437
|
+
case "LS":
|
|
438
|
+
case "Glob":
|
|
439
|
+
case "Grep":
|
|
440
|
+
case "WebFetch":
|
|
441
|
+
case "WebSearch":
|
|
442
|
+
case "Other":
|
|
443
|
+
default: {
|
|
444
|
+
return toAcpContentUpdate(toolResult.content, "is_error" in toolResult ? toolResult.is_error : false);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function toAcpContentUpdate(content, isError = false) {
|
|
449
|
+
if (Array.isArray(content) && content.length > 0) {
|
|
450
|
+
return {
|
|
451
|
+
content: content.map((content) => ({
|
|
452
|
+
type: "content",
|
|
453
|
+
content: isError && content.type === "text"
|
|
454
|
+
? {
|
|
455
|
+
...content,
|
|
456
|
+
text: `\`\`\`\n${content.text}\n\`\`\``,
|
|
457
|
+
}
|
|
458
|
+
: content,
|
|
459
|
+
})),
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
else if (typeof content === "string" && content.length > 0) {
|
|
463
|
+
return {
|
|
464
|
+
content: [
|
|
465
|
+
{
|
|
466
|
+
type: "content",
|
|
467
|
+
content: {
|
|
468
|
+
type: "text",
|
|
469
|
+
text: isError ? `\`\`\`\n${content}\n\`\`\`` : content,
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
return {};
|
|
476
|
+
}
|
|
477
|
+
export function planEntries(input) {
|
|
478
|
+
return input.todos.map((input) => ({
|
|
479
|
+
content: input.content,
|
|
480
|
+
status: input.status,
|
|
481
|
+
priority: "medium",
|
|
482
|
+
}));
|
|
483
|
+
}
|
|
484
|
+
export function markdownEscape(text) {
|
|
485
|
+
let escape = "```";
|
|
486
|
+
for (const [m] of text.matchAll(/^```+/gm)) {
|
|
487
|
+
while (m.length >= escape.length) {
|
|
488
|
+
escape += "`";
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return escape + "\n" + text + (text.endsWith("\n") ? "" : "\n") + escape;
|
|
492
|
+
}
|
|
493
|
+
/* A global variable to store callbacks that should be executed when receiving hooks from Claude Code */
|
|
494
|
+
const toolUseCallbacks = {};
|
|
495
|
+
/* Setup callbacks that will be called when receiving hooks from Claude Code */
|
|
496
|
+
export const registerHookCallback = (toolUseID, { onPostToolUseHook, }) => {
|
|
497
|
+
toolUseCallbacks[toolUseID] = {
|
|
498
|
+
onPostToolUseHook,
|
|
499
|
+
};
|
|
500
|
+
};
|
|
501
|
+
/* A callback for Claude Code that is called when receiving a PostToolUse hook */
|
|
502
|
+
export const createPostToolUseHook = (logger = console) => async (input, toolUseID) => {
|
|
503
|
+
if (input.hook_event_name === "PostToolUse" && toolUseID) {
|
|
504
|
+
const onPostToolUseHook = toolUseCallbacks[toolUseID]?.onPostToolUseHook;
|
|
505
|
+
if (onPostToolUseHook) {
|
|
506
|
+
await onPostToolUseHook(toolUseID, input.tool_input, input.tool_response);
|
|
507
|
+
delete toolUseCallbacks[toolUseID]; // Cleanup after execution
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
logger.error(`No onPostToolUseHook found for tool use ID: ${toolUseID}`);
|
|
511
|
+
delete toolUseCallbacks[toolUseID];
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return { continue: true };
|
|
515
|
+
};
|
|
516
|
+
/**
|
|
517
|
+
* Creates a PreToolUse hook that checks permissions using the SettingsManager.
|
|
518
|
+
* This runs before the SDK's built-in permission rules, allowing us to enforce
|
|
519
|
+
* our own permission settings for ACP-prefixed tools.
|
|
520
|
+
*/
|
|
521
|
+
export const createPreToolUseHook = (settingsManager, logger = console) => async (input, _toolUseID) => {
|
|
522
|
+
if (input.hook_event_name !== "PreToolUse") {
|
|
523
|
+
return { continue: true };
|
|
524
|
+
}
|
|
525
|
+
const toolName = input.tool_name;
|
|
526
|
+
const toolInput = input.tool_input;
|
|
527
|
+
const permissionCheck = settingsManager.checkPermission(toolName, toolInput);
|
|
528
|
+
if (permissionCheck.decision !== "ask") {
|
|
529
|
+
logger.log(`[PreToolUseHook] Tool: ${toolName}, Decision: ${permissionCheck.decision}, Rule: ${permissionCheck.rule}`);
|
|
530
|
+
}
|
|
531
|
+
switch (permissionCheck.decision) {
|
|
532
|
+
case "allow":
|
|
533
|
+
return {
|
|
534
|
+
continue: true,
|
|
535
|
+
hookSpecificOutput: {
|
|
536
|
+
hookEventName: "PreToolUse",
|
|
537
|
+
permissionDecision: "allow",
|
|
538
|
+
permissionDecisionReason: `Allowed by settings rule: ${permissionCheck.rule}`,
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
case "deny":
|
|
542
|
+
return {
|
|
543
|
+
continue: true,
|
|
544
|
+
hookSpecificOutput: {
|
|
545
|
+
hookEventName: "PreToolUse",
|
|
546
|
+
permissionDecision: "deny",
|
|
547
|
+
permissionDecisionReason: `Denied by settings rule: ${permissionCheck.rule}`,
|
|
548
|
+
},
|
|
549
|
+
};
|
|
550
|
+
case "ask":
|
|
551
|
+
default:
|
|
552
|
+
// Let the normal permission flow continue
|
|
553
|
+
return { continue: true };
|
|
554
|
+
}
|
|
555
|
+
};
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Readable, Writable } from "node:stream";
|
|
2
|
+
import { WritableStream, ReadableStream } from "node:stream/web";
|
|
3
|
+
import { Logger } from "./acp-agent.js";
|
|
4
|
+
import { ClaudeCodeSettings } from "./settings.js";
|
|
5
|
+
export declare class Pushable<T> implements AsyncIterable<T> {
|
|
6
|
+
private queue;
|
|
7
|
+
private resolvers;
|
|
8
|
+
private done;
|
|
9
|
+
push(item: T): void;
|
|
10
|
+
end(): void;
|
|
11
|
+
[Symbol.asyncIterator](): AsyncIterator<T>;
|
|
12
|
+
}
|
|
13
|
+
export declare function nodeToWebWritable(nodeStream: Writable): WritableStream<Uint8Array>;
|
|
14
|
+
export declare function nodeToWebReadable(nodeStream: Readable): ReadableStream<Uint8Array>;
|
|
15
|
+
export declare function unreachable(value: never, logger?: Logger): void;
|
|
16
|
+
export declare function sleep(time: number): Promise<void>;
|
|
17
|
+
export declare function loadManagedSettings(): ClaudeCodeSettings | null;
|
|
18
|
+
export declare function applyEnvironmentSettings(settings: ClaudeCodeSettings): void;
|
|
19
|
+
export interface ExtractLinesResult {
|
|
20
|
+
content: string;
|
|
21
|
+
wasLimited: boolean;
|
|
22
|
+
linesRead: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Extracts lines from file content with byte limit enforcement.
|
|
26
|
+
*
|
|
27
|
+
* @param fullContent - The complete file content
|
|
28
|
+
* @param maxContentLength - Maximum number of UTF-16 Code Units to return
|
|
29
|
+
* @returns Object containing extracted content and metadata
|
|
30
|
+
*/
|
|
31
|
+
export declare function extractLinesWithByteLimit(fullContent: string, maxContentLength: number): ExtractLinesResult;
|
|
32
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +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;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,MAAM,GACvB,kBAAkB,CA8CpB"}
|