claude-code-acp-ts 0.13.1 → 0.16.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/acp-agent.d.ts +15 -4
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +337 -109
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +42 -3
- package/dist/settings.d.ts +1 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +6 -2
- package/dist/tools.d.ts +5 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +83 -16
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +13 -0
- package/package.json +12 -12
package/dist/acp-agent.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Agent, AgentSideConnection, AuthenticateRequest, CancelNotification, ClientCapabilities, ForkSessionRequest, ForkSessionResponse, InitializeRequest, InitializeResponse, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, ReadTextFileRequest, ReadTextFileResponse, ResumeSessionRequest, ResumeSessionResponse, SessionNotification, SetSessionModelRequest, SetSessionModelResponse, SetSessionModeRequest, SetSessionModeResponse, TerminalHandle, TerminalOutputResponse, WriteTextFileRequest, WriteTextFileResponse } from "@agentclientprotocol/sdk";
|
|
1
|
+
import { Agent, AgentSideConnection, AuthenticateRequest, CancelNotification, ClientCapabilities, ForkSessionRequest, ForkSessionResponse, InitializeRequest, InitializeResponse, LoadSessionRequest, LoadSessionResponse, ListSessionsRequest, ListSessionsResponse, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, ReadTextFileRequest, ReadTextFileResponse, ResumeSessionRequest, ResumeSessionResponse, SessionNotification, SetSessionModelRequest, SetSessionModelResponse, SetSessionModeRequest, SetSessionModeResponse, TerminalHandle, TerminalOutputResponse, WriteTextFileRequest, WriteTextFileResponse } from "@agentclientprotocol/sdk";
|
|
2
2
|
import { SettingsManager } from "./settings.js";
|
|
3
3
|
import { CanUseTool, Options, PermissionMode, Query, SDKPartialAssistantMessage, SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
4
4
|
import { Pushable } from "./utils.js";
|
|
@@ -44,6 +44,7 @@ export type NewSessionMeta = {
|
|
|
44
44
|
* Those parameters will be used and updated to work with ACP:
|
|
45
45
|
* - hooks (merged with ACP's hooks)
|
|
46
46
|
* - mcpServers (merged with ACP's mcpServers)
|
|
47
|
+
* - disallowedTools (merged with ACP's disallowedTools)
|
|
47
48
|
*/
|
|
48
49
|
options?: Options;
|
|
49
50
|
};
|
|
@@ -57,12 +58,12 @@ export type ToolUpdateMeta = {
|
|
|
57
58
|
toolResponse?: unknown;
|
|
58
59
|
};
|
|
59
60
|
};
|
|
60
|
-
type ToolUseCache = {
|
|
61
|
+
export type ToolUseCache = {
|
|
61
62
|
[key: string]: {
|
|
62
63
|
type: "tool_use" | "server_tool_use" | "mcp_tool_use";
|
|
63
64
|
id: string;
|
|
64
65
|
name: string;
|
|
65
|
-
input:
|
|
66
|
+
input: unknown;
|
|
66
67
|
};
|
|
67
68
|
};
|
|
68
69
|
export declare class ClaudeAcpAgent implements Agent {
|
|
@@ -81,11 +82,19 @@ export declare class ClaudeAcpAgent implements Agent {
|
|
|
81
82
|
newSession(params: NewSessionRequest): Promise<NewSessionResponse>;
|
|
82
83
|
unstable_forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse>;
|
|
83
84
|
unstable_resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse>;
|
|
85
|
+
loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse>;
|
|
86
|
+
/**
|
|
87
|
+
* List Claude Code sessions by parsing JSONL files
|
|
88
|
+
* Sessions are stored in ~/.claude/projects/<path-encoded>/
|
|
89
|
+
* Implements the draft session/list RFD spec
|
|
90
|
+
*/
|
|
91
|
+
unstable_listSessions(params: ListSessionsRequest): Promise<ListSessionsResponse>;
|
|
84
92
|
authenticate(_params: AuthenticateRequest): Promise<void>;
|
|
85
93
|
prompt(params: PromptRequest): Promise<PromptResponse>;
|
|
86
94
|
cancel(params: CancelNotification): Promise<void>;
|
|
87
95
|
unstable_setSessionModel(params: SetSessionModelRequest): Promise<SetSessionModelResponse | void>;
|
|
88
96
|
setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse>;
|
|
97
|
+
private replaySessionHistory;
|
|
89
98
|
readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse>;
|
|
90
99
|
writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse>;
|
|
91
100
|
canUseTool(sessionId: string): CanUseTool;
|
|
@@ -96,7 +105,9 @@ export declare function promptToClaude(prompt: PromptRequest): SDKUserMessage;
|
|
|
96
105
|
* Convert an SDKAssistantMessage (Claude) to a SessionNotification (ACP).
|
|
97
106
|
* Only handles text, image, and thinking chunks for now.
|
|
98
107
|
*/
|
|
99
|
-
export declare function toAcpNotifications(content: string | ContentBlockParam[] | BetaContentBlock[] | BetaRawContentBlockDelta[], role: "assistant" | "user", sessionId: string, toolUseCache: ToolUseCache, client: AgentSideConnection, logger: Logger
|
|
108
|
+
export declare function toAcpNotifications(content: string | ContentBlockParam[] | BetaContentBlock[] | BetaRawContentBlockDelta[], role: "assistant" | "user", sessionId: string, toolUseCache: ToolUseCache, client: AgentSideConnection, logger: Logger, options?: {
|
|
109
|
+
registerHooks?: boolean;
|
|
110
|
+
}): SessionNotification[];
|
|
100
111
|
export declare function streamEventToAcpNotifications(message: SDKPartialAssistantMessage, sessionId: string, toolUseCache: ToolUseCache, client: AgentSideConnection, logger: Logger): SessionNotification[];
|
|
101
112
|
export declare function runAcp(): void;
|
|
102
113
|
export {};
|
package/dist/acp-agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acp-agent.d.ts","sourceRoot":"","sources":["../src/acp-agent.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,mBAAmB,EACnB,mBAAmB,EAEnB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"acp-agent.d.ts","sourceRoot":"","sources":["../src/acp-agent.ts"],"names":[],"mappings":"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"}
|
package/dist/acp-agent.js
CHANGED
|
@@ -3,14 +3,31 @@ import { SettingsManager } from "./settings.js";
|
|
|
3
3
|
import { query, } from "@anthropic-ai/claude-agent-sdk";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
+
import * as readline from "node:readline";
|
|
6
7
|
import * as os from "node:os";
|
|
7
|
-
import { nodeToWebReadable, nodeToWebWritable, Pushable, unreachable } from "./utils.js";
|
|
8
|
+
import { encodeProjectPath, nodeToWebReadable, nodeToWebWritable, Pushable, unreachable, } from "./utils.js";
|
|
8
9
|
import { createMcpServer } from "./mcp-server.js";
|
|
9
10
|
import { EDIT_TOOL_NAMES, acpToolNames } from "./tools.js";
|
|
10
11
|
import { toolInfoFromToolUse, planEntries, toolUpdateFromToolResult, registerHookCallback, createPostToolUseHook, createPreToolUseHook, } from "./tools.js";
|
|
11
12
|
import packageJson from "../package.json" with { type: "json" };
|
|
12
13
|
import { randomUUID } from "node:crypto";
|
|
13
|
-
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
export const CLAUDE_CONFIG_DIR = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
|
|
16
|
+
function sessionFilePath(cwd, sessionId) {
|
|
17
|
+
return path.join(CLAUDE_CONFIG_DIR, "projects", encodeProjectPath(cwd), `${sessionId}.jsonl`);
|
|
18
|
+
}
|
|
19
|
+
const MAX_TITLE_LENGTH = 128;
|
|
20
|
+
function sanitizeTitle(text) {
|
|
21
|
+
// Replace newlines and collapse whitespace
|
|
22
|
+
const sanitized = text
|
|
23
|
+
.replace(/[\r\n]+/g, " ")
|
|
24
|
+
.replace(/\s+/g, " ")
|
|
25
|
+
.trim();
|
|
26
|
+
if (sanitized.length <= MAX_TITLE_LENGTH) {
|
|
27
|
+
return sanitized;
|
|
28
|
+
}
|
|
29
|
+
return sanitized.slice(0, MAX_TITLE_LENGTH - 1) + "…";
|
|
30
|
+
}
|
|
14
31
|
// ==================== 超时配置 ====================
|
|
15
32
|
// 可通过环境变量覆盖默认值
|
|
16
33
|
/** 获取模型列表超时时间(毫秒),默认 30 秒 */
|
|
@@ -53,6 +70,7 @@ async function withTimeout(promise, timeoutMs, operationName, logger = console)
|
|
|
53
70
|
}
|
|
54
71
|
// Bypass Permissions doesn't work if we are a root/sudo user
|
|
55
72
|
const IS_ROOT = (process.geteuid?.() ?? process.getuid?.()) === 0;
|
|
73
|
+
const ALLOW_BYPASS = !IS_ROOT || !!process.env.IS_SANDBOX;
|
|
56
74
|
// Implement the ACP Agent interface
|
|
57
75
|
export class ClaudeAcpAgent {
|
|
58
76
|
constructor(client, logger) {
|
|
@@ -71,16 +89,16 @@ export class ClaudeAcpAgent {
|
|
|
71
89
|
id: "claude-login",
|
|
72
90
|
};
|
|
73
91
|
// If client supports terminal-auth capability, use that instead.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
if (request.clientCapabilities?._meta?.["terminal-auth"] === true) {
|
|
93
|
+
const cliPath = fileURLToPath(import.meta.resolve("@anthropic-ai/claude-agent-sdk/cli.js"));
|
|
94
|
+
authMethod._meta = {
|
|
95
|
+
"terminal-auth": {
|
|
96
|
+
command: "node",
|
|
97
|
+
args: [cliPath, "/login"],
|
|
98
|
+
label: "Claude Code Login",
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
84
102
|
return {
|
|
85
103
|
protocolVersion: 1,
|
|
86
104
|
agentCapabilities: {
|
|
@@ -92,8 +110,10 @@ export class ClaudeAcpAgent {
|
|
|
92
110
|
http: true,
|
|
93
111
|
sse: true,
|
|
94
112
|
},
|
|
113
|
+
loadSession: true,
|
|
95
114
|
sessionCapabilities: {
|
|
96
115
|
fork: {},
|
|
116
|
+
list: {},
|
|
97
117
|
resume: {},
|
|
98
118
|
},
|
|
99
119
|
},
|
|
@@ -135,6 +155,171 @@ export class ClaudeAcpAgent {
|
|
|
135
155
|
});
|
|
136
156
|
return response;
|
|
137
157
|
}
|
|
158
|
+
async loadSession(params) {
|
|
159
|
+
try {
|
|
160
|
+
await fs.promises.access(sessionFilePath(params.cwd, params.sessionId));
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
throw new Error("Session not found");
|
|
164
|
+
}
|
|
165
|
+
const response = await this.createSession({
|
|
166
|
+
cwd: params.cwd,
|
|
167
|
+
mcpServers: params.mcpServers ?? [],
|
|
168
|
+
_meta: params._meta,
|
|
169
|
+
}, {
|
|
170
|
+
resume: params.sessionId,
|
|
171
|
+
});
|
|
172
|
+
await this.replaySessionHistory(params.sessionId, params.cwd);
|
|
173
|
+
return {
|
|
174
|
+
modes: response.modes,
|
|
175
|
+
models: response.models,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* List Claude Code sessions by parsing JSONL files
|
|
180
|
+
* Sessions are stored in ~/.claude/projects/<path-encoded>/
|
|
181
|
+
* Implements the draft session/list RFD spec
|
|
182
|
+
*/
|
|
183
|
+
async unstable_listSessions(params) {
|
|
184
|
+
// Note: We load all sessions into memory for sorting, so pagination here is for
|
|
185
|
+
// API response size limits rather than memory efficiency. This matches the RFD spec.
|
|
186
|
+
const PAGE_SIZE = 50;
|
|
187
|
+
const claudeDir = path.join(CLAUDE_CONFIG_DIR, "projects");
|
|
188
|
+
try {
|
|
189
|
+
await fs.promises.access(claudeDir);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return { sessions: [] };
|
|
193
|
+
}
|
|
194
|
+
// Collect all sessions across all project directories
|
|
195
|
+
const allSessions = [];
|
|
196
|
+
const encodedCwdFilter = params.cwd ? encodeProjectPath(params.cwd) : null;
|
|
197
|
+
try {
|
|
198
|
+
const projectDirs = await fs.promises.readdir(claudeDir);
|
|
199
|
+
for (const encodedPath of projectDirs) {
|
|
200
|
+
const projectDir = path.join(claudeDir, encodedPath);
|
|
201
|
+
const stat = await fs.promises.stat(projectDir);
|
|
202
|
+
if (!stat.isDirectory())
|
|
203
|
+
continue;
|
|
204
|
+
// Path encoding is not always reversible (hyphens can be separators or literals),
|
|
205
|
+
// so only use encoded value as a coarse pre-filter.
|
|
206
|
+
if (encodedCwdFilter && encodedPath !== encodedCwdFilter)
|
|
207
|
+
continue;
|
|
208
|
+
const files = await fs.promises.readdir(projectDir);
|
|
209
|
+
// Filter to user session files only. Skip agent-*.jsonl files which contain
|
|
210
|
+
// internal agent metadata and system logs, not user-visible conversation sessions.
|
|
211
|
+
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
212
|
+
for (const file of jsonlFiles) {
|
|
213
|
+
const filePath = path.join(projectDir, file);
|
|
214
|
+
try {
|
|
215
|
+
const content = await fs.promises.readFile(filePath, "utf-8");
|
|
216
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
217
|
+
const sessionId = file.replace(".jsonl", "");
|
|
218
|
+
let parsedAnyEntry = false;
|
|
219
|
+
let sessionCwd;
|
|
220
|
+
// Find first user message for title
|
|
221
|
+
let title;
|
|
222
|
+
for (const line of lines) {
|
|
223
|
+
try {
|
|
224
|
+
const entry = JSON.parse(line);
|
|
225
|
+
parsedAnyEntry = true;
|
|
226
|
+
if (entry.isSidechain === true) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const entrySessionId = typeof entry.sessionId === "string" ? entry.sessionId : undefined;
|
|
230
|
+
if (typeof entry.sessionId === "string" && entry.sessionId !== entrySessionId) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (typeof entry.cwd === "string") {
|
|
234
|
+
sessionCwd = entry.cwd;
|
|
235
|
+
}
|
|
236
|
+
if (!title && entry.type === "user" && entry.message?.content) {
|
|
237
|
+
const msgContent = entry.message.content;
|
|
238
|
+
if (typeof msgContent === "string") {
|
|
239
|
+
title = sanitizeTitle(msgContent);
|
|
240
|
+
}
|
|
241
|
+
if (Array.isArray(msgContent) && msgContent.length > 0) {
|
|
242
|
+
const first = msgContent[0];
|
|
243
|
+
const text = typeof first === "string"
|
|
244
|
+
? first
|
|
245
|
+
: first && typeof first === "object" && typeof first.text === "string"
|
|
246
|
+
? first.text
|
|
247
|
+
: undefined;
|
|
248
|
+
if (text) {
|
|
249
|
+
title = sanitizeTitle(text);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Continue scanning until we have both fields, since cwd can appear
|
|
254
|
+
// in later entries even after the first user title-bearing message.
|
|
255
|
+
if (title && sessionCwd) {
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// Skip malformed lines
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (!parsedAnyEntry)
|
|
264
|
+
continue;
|
|
265
|
+
// SessionInfo.cwd is currently required. For entries that do not
|
|
266
|
+
// include an explicit cwd in the session JSONL (typically metadata-only files),
|
|
267
|
+
// we skip them instead of decoding folder names because path encoding is lossy.
|
|
268
|
+
if (!sessionCwd)
|
|
269
|
+
continue;
|
|
270
|
+
// Even after encoded-path pre-filtering, verify per-entry cwd to disambiguate
|
|
271
|
+
// collisions such as "/a-b" and "/a/b" that map to the same encoded folder name.
|
|
272
|
+
if (params.cwd && sessionCwd !== params.cwd)
|
|
273
|
+
continue;
|
|
274
|
+
// Get file modification time as updatedAt
|
|
275
|
+
const fileStat = await fs.promises.stat(filePath);
|
|
276
|
+
const updatedAt = fileStat.mtime.toISOString();
|
|
277
|
+
allSessions.push({
|
|
278
|
+
sessionId,
|
|
279
|
+
cwd: sessionCwd,
|
|
280
|
+
title: title ?? null,
|
|
281
|
+
updatedAt,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
this.logger.error(`[unstable_listSessions] Failed to parse session file: ${filePath}`, err);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
this.logger.error("[unstable_listSessions] Failed to list sessions", err);
|
|
292
|
+
return { sessions: [] };
|
|
293
|
+
}
|
|
294
|
+
// Sort by updatedAt descending (most recent first)
|
|
295
|
+
allSessions.sort((a, b) => {
|
|
296
|
+
const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
297
|
+
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
298
|
+
return timeB - timeA;
|
|
299
|
+
});
|
|
300
|
+
// Handle pagination with cursor
|
|
301
|
+
let startIndex = 0;
|
|
302
|
+
if (params.cursor) {
|
|
303
|
+
try {
|
|
304
|
+
const decoded = Buffer.from(params.cursor, "base64").toString("utf-8");
|
|
305
|
+
const cursorData = JSON.parse(decoded);
|
|
306
|
+
startIndex = cursorData.offset ?? 0;
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// Invalid cursor, start from beginning
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const pageOfSessions = allSessions.slice(startIndex, startIndex + PAGE_SIZE);
|
|
313
|
+
const hasMore = startIndex + PAGE_SIZE < allSessions.length;
|
|
314
|
+
const response = {
|
|
315
|
+
sessions: pageOfSessions,
|
|
316
|
+
};
|
|
317
|
+
if (hasMore) {
|
|
318
|
+
const nextCursor = Buffer.from(JSON.stringify({ offset: startIndex + PAGE_SIZE })).toString("base64");
|
|
319
|
+
response.nextCursor = nextCursor;
|
|
320
|
+
}
|
|
321
|
+
return response;
|
|
322
|
+
}
|
|
138
323
|
async authenticate(_params) {
|
|
139
324
|
throw new Error("Method not implemented.");
|
|
140
325
|
}
|
|
@@ -159,8 +344,12 @@ export class ClaudeAcpAgent {
|
|
|
159
344
|
case "init":
|
|
160
345
|
break;
|
|
161
346
|
case "compact_boundary":
|
|
347
|
+
case "hook_started":
|
|
348
|
+
case "task_notification":
|
|
349
|
+
case "hook_progress":
|
|
162
350
|
case "hook_response":
|
|
163
351
|
case "status":
|
|
352
|
+
case "files_persisted":
|
|
164
353
|
// Todo: process via status api: https://docs.claude.com/en/docs/claude-code/hooks#hook-output
|
|
165
354
|
break;
|
|
166
355
|
default:
|
|
@@ -215,6 +404,14 @@ export class ClaudeAcpAgent {
|
|
|
215
404
|
// their own docs: https://docs.anthropic.com/en/docs/claude-code/sdk/sdk-slash-commands#%2Fcompact-compact-conversation-history
|
|
216
405
|
if (typeof message.message.content === "string" &&
|
|
217
406
|
message.message.content.includes("<local-command-stdout>")) {
|
|
407
|
+
// Handle /context by sending its reply as regular agent message.
|
|
408
|
+
if (message.message.content.includes("Context Usage")) {
|
|
409
|
+
for (const notification of toAcpNotifications(message.message.content
|
|
410
|
+
.replace("<local-command-stdout>", "")
|
|
411
|
+
.replace("</local-command-stdout>", ""), "assistant", params.sessionId, this.toolUseCache, this.client, this.logger)) {
|
|
412
|
+
await this.client.sessionUpdate(notification);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
218
415
|
this.logger.log(message.message.content);
|
|
219
416
|
break;
|
|
220
417
|
}
|
|
@@ -249,6 +446,7 @@ export class ClaudeAcpAgent {
|
|
|
249
446
|
break;
|
|
250
447
|
}
|
|
251
448
|
case "tool_progress":
|
|
449
|
+
case "tool_use_summary":
|
|
252
450
|
break;
|
|
253
451
|
case "auth_status":
|
|
254
452
|
break;
|
|
@@ -295,6 +493,54 @@ export class ClaudeAcpAgent {
|
|
|
295
493
|
throw new Error("Invalid Mode");
|
|
296
494
|
}
|
|
297
495
|
}
|
|
496
|
+
async replaySessionHistory(sessionId, cwd) {
|
|
497
|
+
const filePath = sessionFilePath(cwd, sessionId);
|
|
498
|
+
const toolUseCache = {};
|
|
499
|
+
const stream = fs.createReadStream(filePath, { encoding: "utf-8" });
|
|
500
|
+
const reader = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
501
|
+
try {
|
|
502
|
+
for await (const line of reader) {
|
|
503
|
+
const trimmed = line.trim();
|
|
504
|
+
if (!trimmed) {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
let entry;
|
|
508
|
+
try {
|
|
509
|
+
entry = JSON.parse(trimmed);
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
if (entry.type !== "user" && entry.type !== "assistant") {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (entry.isSidechain) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
if (entry.sessionId && entry.sessionId !== sessionId) {
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
const message = entry.message;
|
|
524
|
+
if (!message) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const role = message.role === "assistant" ? "assistant" : message.role === "user" ? "user" : null;
|
|
528
|
+
if (!role) {
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
const content = message.content;
|
|
532
|
+
if (typeof content !== "string" && !Array.isArray(content)) {
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
for (const notification of toAcpNotifications(content, role, sessionId, toolUseCache, this.client, this.logger, { registerHooks: false })) {
|
|
536
|
+
await this.client.sessionUpdate(notification);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
finally {
|
|
541
|
+
reader.close();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
298
544
|
async readTextFile(params) {
|
|
299
545
|
const response = await this.client.readTextFile(params);
|
|
300
546
|
return response;
|
|
@@ -487,11 +733,6 @@ export class ClaudeAcpAgent {
|
|
|
487
733
|
const permissionMode = "default";
|
|
488
734
|
// Extract options from _meta if provided
|
|
489
735
|
const userProvidedOptions = params._meta?.claudeCode?.options;
|
|
490
|
-
const extraArgs = { ...userProvidedOptions?.extraArgs };
|
|
491
|
-
if (creationOpts?.resume === undefined || creationOpts?.forkSession) {
|
|
492
|
-
// Set our own session id if not resuming an existing session.
|
|
493
|
-
extraArgs["session-id"] = sessionId;
|
|
494
|
-
}
|
|
495
736
|
// Configure thinking tokens from environment variable
|
|
496
737
|
const maxThinkingTokens = process.env.MAX_THINKING_TOKENS
|
|
497
738
|
? parseInt(process.env.MAX_THINKING_TOKENS, 10)
|
|
@@ -506,10 +747,9 @@ export class ClaudeAcpAgent {
|
|
|
506
747
|
cwd: params.cwd,
|
|
507
748
|
includePartialMessages: true,
|
|
508
749
|
mcpServers: { ...(userProvidedOptions?.mcpServers || {}), ...mcpServers },
|
|
509
|
-
extraArgs,
|
|
510
750
|
// If we want bypassPermissions to be an option, we have to allow it here.
|
|
511
751
|
// But it doesn't work in root mode, so we only activate it if it will work.
|
|
512
|
-
allowDangerouslySkipPermissions:
|
|
752
|
+
allowDangerouslySkipPermissions: ALLOW_BYPASS,
|
|
513
753
|
permissionMode,
|
|
514
754
|
canUseTool: this.canUseTool(sessionId),
|
|
515
755
|
// note: although not documented by the types, passing an absolute path
|
|
@@ -530,12 +770,32 @@ export class ClaudeAcpAgent {
|
|
|
530
770
|
PostToolUse: [
|
|
531
771
|
...(userProvidedOptions?.hooks?.PostToolUse || []),
|
|
532
772
|
{
|
|
533
|
-
hooks: [
|
|
773
|
+
hooks: [
|
|
774
|
+
createPostToolUseHook(this.logger, {
|
|
775
|
+
onEnterPlanMode: async () => {
|
|
776
|
+
const session = this.sessions[sessionId];
|
|
777
|
+
if (session) {
|
|
778
|
+
session.permissionMode = "plan";
|
|
779
|
+
}
|
|
780
|
+
await this.client.sessionUpdate({
|
|
781
|
+
sessionId,
|
|
782
|
+
update: {
|
|
783
|
+
sessionUpdate: "current_mode_update",
|
|
784
|
+
currentModeId: "plan",
|
|
785
|
+
},
|
|
786
|
+
});
|
|
787
|
+
},
|
|
788
|
+
}),
|
|
789
|
+
],
|
|
534
790
|
},
|
|
535
791
|
],
|
|
536
792
|
},
|
|
537
793
|
...creationOpts,
|
|
538
794
|
};
|
|
795
|
+
if (creationOpts?.resume === undefined || creationOpts?.forkSession) {
|
|
796
|
+
// Set our own session id if not resuming an existing session.
|
|
797
|
+
options.sessionId = sessionId;
|
|
798
|
+
}
|
|
539
799
|
const allowedTools = [];
|
|
540
800
|
// Disable this for now, not a great way to expose this over ACP at the moment (in progress work so we can revisit)
|
|
541
801
|
const disallowedTools = ["AskUserQuestion"];
|
|
@@ -562,7 +822,7 @@ export class ClaudeAcpAgent {
|
|
|
562
822
|
options.allowedTools = allowedTools;
|
|
563
823
|
}
|
|
564
824
|
if (disallowedTools.length > 0) {
|
|
565
|
-
options.disallowedTools = disallowedTools;
|
|
825
|
+
options.disallowedTools = [...(options.disallowedTools || []), ...disallowedTools];
|
|
566
826
|
}
|
|
567
827
|
// Handle abort controller from meta options
|
|
568
828
|
const abortController = userProvidedOptions?.abortController;
|
|
@@ -589,47 +849,10 @@ export class ClaudeAcpAgent {
|
|
|
589
849
|
permissionMode,
|
|
590
850
|
settingsManager,
|
|
591
851
|
};
|
|
592
|
-
this.logger.log(`📋 [ACP]
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
// availableCommands = await withTimeout(
|
|
597
|
-
// getAvailableSlashCommands(q, this.logger),
|
|
598
|
-
// COMMAND_FETCH_TIMEOUT_MS,
|
|
599
|
-
// "getAvailableSlashCommands",
|
|
600
|
-
// this.logger,
|
|
601
|
-
// );
|
|
602
|
-
// } catch (error) {
|
|
603
|
-
// if (USE_DEFAULTS_ON_TIMEOUT) {
|
|
604
|
-
// this.logger.log(`⚠️ [ACP] 获取命令失败,使用空列表: ${error}`);
|
|
605
|
-
// availableCommands = [];
|
|
606
|
-
// } else {
|
|
607
|
-
// throw error;
|
|
608
|
-
// }
|
|
609
|
-
// }
|
|
610
|
-
// 使用超时保护包装模型获取
|
|
611
|
-
let models = {
|
|
612
|
-
availableModels: [],
|
|
613
|
-
currentModelId: "",
|
|
614
|
-
};
|
|
615
|
-
// try {
|
|
616
|
-
// models = await withTimeout(
|
|
617
|
-
// getAvailableModels(q, this.logger),
|
|
618
|
-
// MODEL_FETCH_TIMEOUT_MS,
|
|
619
|
-
// "getAvailableModels",
|
|
620
|
-
// this.logger,
|
|
621
|
-
// );
|
|
622
|
-
// } catch (error) {
|
|
623
|
-
// if (USE_DEFAULTS_ON_TIMEOUT) {
|
|
624
|
-
// this.logger.log(`⚠️ [ACP] 获取模型失败,使用默认值: ${error}`);
|
|
625
|
-
// models = {
|
|
626
|
-
// availableModels: [],
|
|
627
|
-
// currentModelId: "",
|
|
628
|
-
// };
|
|
629
|
-
// } else {
|
|
630
|
-
// throw error;
|
|
631
|
-
// }
|
|
632
|
-
// }
|
|
852
|
+
this.logger.log(`📋 [ACP] 等待初始化结果 (模型+命令)...`);
|
|
853
|
+
const initializationResult = await withTimeout(q.initializationResult(), MODEL_FETCH_TIMEOUT_MS, "initializationResult", this.logger);
|
|
854
|
+
this.logger.log(`📋 [ACP] 初始化完成, 获取到 ${initializationResult.models.length} 个模型, ${initializationResult.commands.length} 个命令`);
|
|
855
|
+
const models = await getAvailableModels(q, initializationResult.models, settingsManager);
|
|
633
856
|
const sessionCreateElapsed = Date.now() - sessionCreateStartTime;
|
|
634
857
|
this.logger.log(`✅ [ACP] 会话创建完成, 总耗时: ${sessionCreateElapsed}ms`);
|
|
635
858
|
// Needs to happen after we return the session
|
|
@@ -638,7 +861,7 @@ export class ClaudeAcpAgent {
|
|
|
638
861
|
sessionId,
|
|
639
862
|
update: {
|
|
640
863
|
sessionUpdate: "available_commands_update",
|
|
641
|
-
availableCommands,
|
|
864
|
+
availableCommands: getAvailableSlashCommands(initializationResult.commands),
|
|
642
865
|
},
|
|
643
866
|
});
|
|
644
867
|
}, 0);
|
|
@@ -665,7 +888,7 @@ export class ClaudeAcpAgent {
|
|
|
665
888
|
},
|
|
666
889
|
];
|
|
667
890
|
// Only works in non-root mode
|
|
668
|
-
if (
|
|
891
|
+
if (ALLOW_BYPASS) {
|
|
669
892
|
availableModes.push({
|
|
670
893
|
id: "bypassPermissions",
|
|
671
894
|
name: "Bypass Permissions",
|
|
@@ -682,42 +905,42 @@ export class ClaudeAcpAgent {
|
|
|
682
905
|
};
|
|
683
906
|
}
|
|
684
907
|
}
|
|
685
|
-
async function getAvailableModels(query,
|
|
686
|
-
const startTime = Date.now();
|
|
687
|
-
logger.log(` 📋 [ACP] 开始获取可用模型列表...`);
|
|
688
|
-
const models = await query.supportedModels();
|
|
689
|
-
logger.log(` 📋 [ACP] 获取到 ${models.length} 个模型, 耗时: ${Date.now() - startTime}ms`);
|
|
690
|
-
// Query doesn't give us access to the currently selected model, so we just choose the first model in the list.
|
|
908
|
+
async function getAvailableModels(query, models, settingsManager) {
|
|
691
909
|
if (models.length === 0) {
|
|
692
|
-
logger.log(` ⚠️ [ACP] 没有可用模型`);
|
|
693
910
|
return { availableModels: [], currentModelId: "" };
|
|
694
911
|
}
|
|
695
|
-
const
|
|
912
|
+
const settings = settingsManager.getSettings();
|
|
913
|
+
let currentModel = models[0];
|
|
914
|
+
if (settings.model) {
|
|
915
|
+
const match = models.find((m) => m.value === settings.model ||
|
|
916
|
+
m.value.includes(settings.model) ||
|
|
917
|
+
settings.model.includes(m.value) ||
|
|
918
|
+
m.displayName.toLowerCase() === settings.model.toLowerCase() ||
|
|
919
|
+
m.displayName.toLowerCase().includes(settings.model.toLowerCase()));
|
|
920
|
+
if (match) {
|
|
921
|
+
currentModel = match;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
696
924
|
await query.setModel(currentModel.value);
|
|
697
|
-
const availableModels = models.map((model) => ({
|
|
698
|
-
modelId: model.value,
|
|
699
|
-
name: model.displayName,
|
|
700
|
-
description: model.description,
|
|
701
|
-
}));
|
|
702
925
|
return {
|
|
703
|
-
availableModels
|
|
926
|
+
availableModels: models.map((model) => ({
|
|
927
|
+
modelId: model.value,
|
|
928
|
+
name: model.displayName,
|
|
929
|
+
description: model.description,
|
|
930
|
+
})),
|
|
704
931
|
currentModelId: currentModel.value,
|
|
705
932
|
};
|
|
706
933
|
}
|
|
707
|
-
|
|
708
|
-
const startTime = Date.now();
|
|
709
|
-
logger.log(` 📋 [ACP] 开始获取可用斜杠命令...`);
|
|
934
|
+
function getAvailableSlashCommands(commands) {
|
|
710
935
|
const UNSUPPORTED_COMMANDS = [
|
|
711
|
-
"context",
|
|
712
936
|
"cost",
|
|
937
|
+
"keybindings-help",
|
|
713
938
|
"login",
|
|
714
939
|
"logout",
|
|
715
940
|
"output-style:new",
|
|
716
941
|
"release-notes",
|
|
717
942
|
"todos",
|
|
718
943
|
];
|
|
719
|
-
const commands = await query.supportedCommands();
|
|
720
|
-
logger.log(` 📋 [ACP] 获取到 ${commands.length} 个斜杠命令, 耗时: ${Date.now() - startTime}ms`);
|
|
721
944
|
return commands
|
|
722
945
|
.map((command) => {
|
|
723
946
|
const input = command.argumentHint
|
|
@@ -837,7 +1060,8 @@ export function promptToClaude(prompt) {
|
|
|
837
1060
|
* Convert an SDKAssistantMessage (Claude) to a SessionNotification (ACP).
|
|
838
1061
|
* Only handles text, image, and thinking chunks for now.
|
|
839
1062
|
*/
|
|
840
|
-
export function toAcpNotifications(content, role, sessionId, toolUseCache, client, logger) {
|
|
1063
|
+
export function toAcpNotifications(content, role, sessionId, toolUseCache, client, logger, options) {
|
|
1064
|
+
const registerHooks = options?.registerHooks !== false;
|
|
841
1065
|
if (typeof content === "string") {
|
|
842
1066
|
return [
|
|
843
1067
|
{
|
|
@@ -902,31 +1126,32 @@ export function toAcpNotifications(content, role, sessionId, toolUseCache, clien
|
|
|
902
1126
|
}
|
|
903
1127
|
}
|
|
904
1128
|
else {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1129
|
+
if (registerHooks) {
|
|
1130
|
+
registerHookCallback(chunk.id, {
|
|
1131
|
+
onPostToolUseHook: async (toolUseId, toolInput, toolResponse) => {
|
|
1132
|
+
const toolUse = toolUseCache[toolUseId];
|
|
1133
|
+
if (toolUse) {
|
|
1134
|
+
const update = {
|
|
1135
|
+
_meta: {
|
|
1136
|
+
claudeCode: {
|
|
1137
|
+
toolResponse,
|
|
1138
|
+
toolName: toolUse.name,
|
|
1139
|
+
},
|
|
915
1140
|
},
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
}
|
|
1141
|
+
toolCallId: toolUseId,
|
|
1142
|
+
sessionUpdate: "tool_call_update",
|
|
1143
|
+
};
|
|
1144
|
+
await client.sessionUpdate({
|
|
1145
|
+
sessionId,
|
|
1146
|
+
update,
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
else {
|
|
1150
|
+
logger.error(`[claude-code-acp] Got a tool response for tool use that wasn't tracked: ${toolUseId}`);
|
|
1151
|
+
}
|
|
1152
|
+
},
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
930
1155
|
let rawInput;
|
|
931
1156
|
try {
|
|
932
1157
|
rawInput = JSON.parse(JSON.stringify(chunk.input));
|
|
@@ -972,6 +1197,7 @@ export function toAcpNotifications(content, role, sessionId, toolUseCache, clien
|
|
|
972
1197
|
toolCallId: chunk.tool_use_id,
|
|
973
1198
|
sessionUpdate: "tool_call_update",
|
|
974
1199
|
status: "is_error" in chunk && chunk.is_error ? "failed" : "completed",
|
|
1200
|
+
rawOutput: chunk.content,
|
|
975
1201
|
...toolUpdateFromToolResult(chunk, toolUseCache[chunk.tool_use_id]),
|
|
976
1202
|
};
|
|
977
1203
|
}
|
|
@@ -984,6 +1210,8 @@ export function toAcpNotifications(content, role, sessionId, toolUseCache, clien
|
|
|
984
1210
|
case "citations_delta":
|
|
985
1211
|
case "signature_delta":
|
|
986
1212
|
case "container_upload":
|
|
1213
|
+
case "compaction":
|
|
1214
|
+
case "compaction_delta":
|
|
987
1215
|
break;
|
|
988
1216
|
default:
|
|
989
1217
|
unreachable(chunk, logger);
|
package/dist/mcp-server.d.ts.map
CHANGED
|
@@ -1 +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,
|
|
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"}
|
package/dist/mcp-server.js
CHANGED
|
@@ -535,15 +535,54 @@ Output: Create directory 'foo'`),
|
|
|
535
535
|
|
|
536
536
|
In sessions with ${acpToolNames.bashOutput} always use it for output from Bash commands instead of TaskOutput.`,
|
|
537
537
|
inputSchema: {
|
|
538
|
-
|
|
538
|
+
task_id: z
|
|
539
539
|
.string()
|
|
540
540
|
.describe(`The id of the background bash command as returned by \`${acpToolNames.bash}\``),
|
|
541
|
+
block: z.boolean().describe("Whether to wait for completion"),
|
|
542
|
+
timeout: z.number().describe("Max wait time in ms"),
|
|
541
543
|
},
|
|
542
544
|
}, async (input) => {
|
|
543
545
|
try {
|
|
544
|
-
const bgTerm = agent.backgroundTerminals[input.
|
|
546
|
+
const bgTerm = agent.backgroundTerminals[input.task_id];
|
|
545
547
|
if (!bgTerm) {
|
|
546
|
-
throw new Error(`Unknown shell ${input.
|
|
548
|
+
throw new Error(`Unknown shell ${input.task_id}`);
|
|
549
|
+
}
|
|
550
|
+
if (input.block && bgTerm.status === "started") {
|
|
551
|
+
const statusPromise = Promise.race([
|
|
552
|
+
bgTerm.handle
|
|
553
|
+
.waitForExit()
|
|
554
|
+
.then((exitStatus) => ({ status: "exited", exitStatus })),
|
|
555
|
+
sleep(input.timeout ?? 2 * 60 * 1000).then(async () => {
|
|
556
|
+
if (bgTerm.status === "started") {
|
|
557
|
+
await bgTerm.handle.kill();
|
|
558
|
+
}
|
|
559
|
+
return { status: "timedOut", exitStatus: null };
|
|
560
|
+
}),
|
|
561
|
+
]);
|
|
562
|
+
const { status, exitStatus } = await statusPromise;
|
|
563
|
+
const currentOutput = await bgTerm.handle.currentOutput();
|
|
564
|
+
const strippedOutput = stripCommonPrefix(bgTerm.lastOutput?.output ?? "", currentOutput.output);
|
|
565
|
+
agent.backgroundTerminals[input.task_id] = {
|
|
566
|
+
status,
|
|
567
|
+
pendingOutput: {
|
|
568
|
+
...currentOutput,
|
|
569
|
+
output: strippedOutput,
|
|
570
|
+
exitStatus: exitStatus ?? currentOutput.exitStatus,
|
|
571
|
+
},
|
|
572
|
+
};
|
|
573
|
+
await bgTerm.handle.release();
|
|
574
|
+
return {
|
|
575
|
+
content: [
|
|
576
|
+
{
|
|
577
|
+
type: "text",
|
|
578
|
+
text: toolCommandOutput(status, {
|
|
579
|
+
...currentOutput,
|
|
580
|
+
output: strippedOutput,
|
|
581
|
+
exitStatus: exitStatus ?? currentOutput.exitStatus,
|
|
582
|
+
}),
|
|
583
|
+
},
|
|
584
|
+
],
|
|
585
|
+
};
|
|
547
586
|
}
|
|
548
587
|
if (bgTerm.status === "started") {
|
|
549
588
|
const newOutput = await bgTerm.handle.currentOutput();
|
package/dist/settings.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface PermissionSettings {
|
|
|
19
19
|
export interface ClaudeCodeSettings {
|
|
20
20
|
permissions?: PermissionSettings;
|
|
21
21
|
env?: Record<string, string>;
|
|
22
|
+
model?: string;
|
|
22
23
|
}
|
|
23
24
|
export type PermissionDecision = "allow" | "deny" | "ask";
|
|
24
25
|
export interface PermissionCheckResult {
|
package/dist/settings.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;AAE1D,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;CACnC;AAwLD;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAW/C;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CAC7E;AAED;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,YAAY,CAA0B;IAC9C,OAAO,CAAC,eAAe,CAA0B;IACjD,OAAO,CAAC,aAAa,CAA0B;IAC/C,OAAO,CAAC,kBAAkB,CAA0B;IACpD,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,MAAM,CAAqE;IACnF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAA8C;gBAEvD,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAMzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAUjC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAI3B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAI9B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAI5B;;OAEG;YACW,eAAe;IAgB7B;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAkDrB;;OAEG;IACH,OAAO,CAAC,aAAa;IAkCrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAgB5B;;;;;;OAMG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,qBAAqB;IAuC5E;;OAEG;IACH,WAAW,IAAI,kBAAkB;IAIjC;;OAEG;IACH,MAAM,IAAI,MAAM;IAIhB;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxC;;OAEG;IACH,OAAO,IAAI,IAAI;CAYhB"}
|
package/dist/settings.js
CHANGED
|
@@ -81,7 +81,8 @@ function normalizePath(filePath, cwd) {
|
|
|
81
81
|
else if (!path.isAbsolute(filePath)) {
|
|
82
82
|
filePath = path.join(cwd, filePath);
|
|
83
83
|
}
|
|
84
|
-
|
|
84
|
+
// Convert backslashes to forward slashes for minimatch compatibility on Windows
|
|
85
|
+
return path.normalize(filePath).replace(/\\/g, "/");
|
|
85
86
|
}
|
|
86
87
|
/**
|
|
87
88
|
* Checks if a file path matches a glob pattern
|
|
@@ -103,7 +104,7 @@ function matchesRule(rule, toolName, toolInput, cwd) {
|
|
|
103
104
|
// - "Edit rules apply to all built-in tools that edit files."
|
|
104
105
|
// - "Claude will make a best-effort attempt to apply Read rules to all built-in tools
|
|
105
106
|
// that read files like Grep, Glob, and LS."
|
|
106
|
-
const ruleAppliesToTool = rule.toolName === "Bash" ||
|
|
107
|
+
const ruleAppliesToTool = (rule.toolName === "Bash" && toolName === acpToolNames.bash) ||
|
|
107
108
|
(rule.toolName === "Edit" && FILE_EDITING_TOOLS.includes(toolName)) ||
|
|
108
109
|
(rule.toolName === "Read" && FILE_READING_TOOLS.includes(toolName));
|
|
109
110
|
if (!ruleAppliesToTool) {
|
|
@@ -288,6 +289,9 @@ export class SettingsManager {
|
|
|
288
289
|
if (settings.env) {
|
|
289
290
|
merged.env = { ...merged.env, ...settings.env };
|
|
290
291
|
}
|
|
292
|
+
if (settings.model) {
|
|
293
|
+
merged.model = settings.model;
|
|
294
|
+
}
|
|
291
295
|
}
|
|
292
296
|
this.mergedSettings = merged;
|
|
293
297
|
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PlanEntry, ToolCallContent, ToolCallLocation, ToolKind } from "@agentclientprotocol/sdk";
|
|
2
2
|
import { ToolResultBlockParam, WebSearchToolResultBlockParam } from "@anthropic-ai/sdk/resources";
|
|
3
|
+
import { BetaBashCodeExecutionToolResultBlockParam, BetaCodeExecutionToolResultBlockParam, BetaRequestMCPToolResultBlockParam, BetaTextEditorCodeExecutionToolResultBlockParam, BetaToolResultBlockParam, BetaToolSearchToolResultBlockParam, BetaWebFetchToolResultBlockParam, BetaWebSearchToolResultBlockParam } from "@anthropic-ai/sdk/resources/beta.mjs";
|
|
3
4
|
export declare const ACP_TOOL_NAME_PREFIX = "mcp__acp__";
|
|
4
5
|
export declare const acpToolNames: {
|
|
5
6
|
read: string;
|
|
@@ -10,7 +11,6 @@ export declare const acpToolNames: {
|
|
|
10
11
|
bashOutput: string;
|
|
11
12
|
};
|
|
12
13
|
export declare const EDIT_TOOL_NAMES: string[];
|
|
13
|
-
import { BetaBashCodeExecutionToolResultBlockParam, BetaCodeExecutionToolResultBlockParam, BetaRequestMCPToolResultBlockParam, BetaTextEditorCodeExecutionToolResultBlockParam, BetaToolSearchToolResultBlockParam, BetaWebFetchToolResultBlockParam, BetaWebSearchToolResultBlockParam } from "@anthropic-ai/sdk/resources/beta.mjs";
|
|
14
14
|
import { HookCallback } from "@anthropic-ai/claude-agent-sdk";
|
|
15
15
|
import { Logger } from "./acp-agent.js";
|
|
16
16
|
import { SettingsManager } from "./settings.js";
|
|
@@ -26,7 +26,7 @@ interface ToolUpdate {
|
|
|
26
26
|
locations?: ToolCallLocation[];
|
|
27
27
|
}
|
|
28
28
|
export declare function toolInfoFromToolUse(toolUse: any): ToolInfo;
|
|
29
|
-
export declare function toolUpdateFromToolResult(toolResult: ToolResultBlockParam | BetaWebSearchToolResultBlockParam | BetaWebFetchToolResultBlockParam | WebSearchToolResultBlockParam | BetaCodeExecutionToolResultBlockParam | BetaBashCodeExecutionToolResultBlockParam | BetaTextEditorCodeExecutionToolResultBlockParam | BetaRequestMCPToolResultBlockParam | BetaToolSearchToolResultBlockParam, toolUse: any | undefined): ToolUpdate;
|
|
29
|
+
export declare function toolUpdateFromToolResult(toolResult: ToolResultBlockParam | BetaToolResultBlockParam | BetaWebSearchToolResultBlockParam | BetaWebFetchToolResultBlockParam | WebSearchToolResultBlockParam | BetaCodeExecutionToolResultBlockParam | BetaBashCodeExecutionToolResultBlockParam | BetaTextEditorCodeExecutionToolResultBlockParam | BetaRequestMCPToolResultBlockParam | BetaToolSearchToolResultBlockParam, toolUse: any | undefined): ToolUpdate;
|
|
30
30
|
export type ClaudePlanEntry = {
|
|
31
31
|
content: string;
|
|
32
32
|
status: "pending" | "in_progress" | "completed";
|
|
@@ -39,7 +39,9 @@ export declare function markdownEscape(text: string): string;
|
|
|
39
39
|
export declare const registerHookCallback: (toolUseID: string, { onPostToolUseHook, }: {
|
|
40
40
|
onPostToolUseHook?: (toolUseID: string, toolInput: unknown, toolResponse: unknown) => Promise<void>;
|
|
41
41
|
}) => void;
|
|
42
|
-
export declare const createPostToolUseHook: (logger?: Logger
|
|
42
|
+
export declare const createPostToolUseHook: (logger?: Logger, options?: {
|
|
43
|
+
onEnterPlanMode?: () => Promise<void>;
|
|
44
|
+
}) => HookCallback;
|
|
43
45
|
/**
|
|
44
46
|
* Creates a PreToolUse hook that checks permissions using the SettingsManager.
|
|
45
47
|
* This runs before the SDK's built-in permission rules, allowing us to enforce
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACT,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAGL,oBAAoB,EAEpB,6BAA6B,EAE9B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,yCAAyC,EAGzC,qCAAqC,EAGrC,kCAAkC,EAClC,+CAA+C,EAK/C,wBAAwB,EACxB,kCAAkC,EAIlC,gCAAgC,EAGhC,iCAAiC,EAElC,MAAM,sCAAsC,CAAC;AAW9C,eAAO,MAAM,oBAAoB,eAAe,CAAC;AACjD,eAAO,MAAM,YAAY;;;;;;;CAOxB,CAAC;AAEF,eAAO,MAAM,eAAe,UAA0C,CAAC;AAyBvE,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,UAAU,QAAQ;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAChC;AAED,UAAU,UAAU;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAChC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,GAAG,GAAG,QAAQ,CA+V1D;AAED,wBAAgB,wBAAwB,CACtC,UAAU,EACN,oBAAoB,GACpB,wBAAwB,GACxB,iCAAiC,GACjC,gCAAgC,GAChC,6BAA6B,GAC7B,qCAAqC,GACrC,yCAAyC,GACzC,+CAA+C,GAC/C,kCAAkC,GAClC,kCAAkC,EACtC,OAAO,EAAE,GAAG,GAAG,SAAS,GACvB,UAAU,CA4HZ;AA0GD,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;IAChD,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,WAAW,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,eAAe,EAAE,CAAA;CAAE,GAAG,SAAS,EAAE,CAM5E;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQnD;AAcD,eAAO,MAAM,oBAAoB,GAC/B,WAAW,MAAM,EACjB,wBAEG;IACD,iBAAiB,CAAC,EAAE,CAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,EAClB,YAAY,EAAE,OAAO,KAClB,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,SAKF,CAAC;AAGF,eAAO,MAAM,qBAAqB,GAE9B,SAAQ,MAAgB,EACxB,UAAU;IACR,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,KACA,YAoBF,CAAC;AAEJ;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC9B,iBAAiB,eAAe,EAAE,SAAQ,MAAgB,KAAG,YA2C7D,CAAC"}
|
package/dist/tools.js
CHANGED
|
@@ -448,17 +448,22 @@ export function toolUpdateFromToolResult(toolResult, toolUse) {
|
|
|
448
448
|
function toAcpContentUpdate(content, isError = false) {
|
|
449
449
|
if (Array.isArray(content) && content.length > 0) {
|
|
450
450
|
return {
|
|
451
|
-
content: content.map((
|
|
451
|
+
content: content.map((c) => ({
|
|
452
452
|
type: "content",
|
|
453
|
-
content: isError
|
|
454
|
-
? {
|
|
455
|
-
...content,
|
|
456
|
-
text: `\`\`\`\n${content.text}\n\`\`\``,
|
|
457
|
-
}
|
|
458
|
-
: content,
|
|
453
|
+
content: toAcpContentBlock(c, isError),
|
|
459
454
|
})),
|
|
460
455
|
};
|
|
461
456
|
}
|
|
457
|
+
else if (typeof content === "object" && content !== null && "type" in content) {
|
|
458
|
+
return {
|
|
459
|
+
content: [
|
|
460
|
+
{
|
|
461
|
+
type: "content",
|
|
462
|
+
content: toAcpContentBlock(content, isError),
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
};
|
|
466
|
+
}
|
|
462
467
|
else if (typeof content === "string" && content.length > 0) {
|
|
463
468
|
return {
|
|
464
469
|
content: [
|
|
@@ -474,6 +479,62 @@ function toAcpContentUpdate(content, isError = false) {
|
|
|
474
479
|
}
|
|
475
480
|
return {};
|
|
476
481
|
}
|
|
482
|
+
function toAcpContentBlock(content, isError) {
|
|
483
|
+
const wrapText = (text) => ({
|
|
484
|
+
type: "text",
|
|
485
|
+
text: isError ? `\`\`\`\n${text}\n\`\`\`` : text,
|
|
486
|
+
});
|
|
487
|
+
switch (content.type) {
|
|
488
|
+
case "text":
|
|
489
|
+
return {
|
|
490
|
+
type: "text",
|
|
491
|
+
text: isError ? `\`\`\`\n${content.text}\n\`\`\`` : content.text,
|
|
492
|
+
};
|
|
493
|
+
case "image":
|
|
494
|
+
if (content.source.type === "base64") {
|
|
495
|
+
return {
|
|
496
|
+
type: "image",
|
|
497
|
+
data: content.source.data,
|
|
498
|
+
mimeType: content.source.media_type,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
// URL and file-based images can't be converted to ACP format (requires data)
|
|
502
|
+
return wrapText(content.source.type === "url"
|
|
503
|
+
? `[image: ${content.source.url}]`
|
|
504
|
+
: "[image: file reference]");
|
|
505
|
+
case "tool_reference":
|
|
506
|
+
return wrapText(`Tool: ${content.tool_name}`);
|
|
507
|
+
case "tool_search_tool_search_result":
|
|
508
|
+
return wrapText(`Tools found: ${content.tool_references.map((r) => r.tool_name).join(", ") || "none"}`);
|
|
509
|
+
case "tool_search_tool_result_error":
|
|
510
|
+
return wrapText(`Error: ${content.error_code}${content.error_message ? ` - ${content.error_message}` : ""}`);
|
|
511
|
+
case "web_search_result":
|
|
512
|
+
return wrapText(`${content.title} (${content.url})`);
|
|
513
|
+
case "web_search_tool_result_error":
|
|
514
|
+
return wrapText(`Error: ${content.error_code}`);
|
|
515
|
+
case "web_fetch_result":
|
|
516
|
+
return wrapText(`Fetched: ${content.url}`);
|
|
517
|
+
case "web_fetch_tool_result_error":
|
|
518
|
+
return wrapText(`Error: ${content.error_code}`);
|
|
519
|
+
case "code_execution_result":
|
|
520
|
+
return wrapText(`Output: ${content.stdout || content.stderr || ""}`);
|
|
521
|
+
case "bash_code_execution_result":
|
|
522
|
+
return wrapText(`Output: ${content.stdout || content.stderr || ""}`);
|
|
523
|
+
case "code_execution_tool_result_error":
|
|
524
|
+
case "bash_code_execution_tool_result_error":
|
|
525
|
+
return wrapText(`Error: ${content.error_code}`);
|
|
526
|
+
case "text_editor_code_execution_view_result":
|
|
527
|
+
return wrapText(content.content);
|
|
528
|
+
case "text_editor_code_execution_create_result":
|
|
529
|
+
return wrapText(content.is_file_update ? "File updated" : "File created");
|
|
530
|
+
case "text_editor_code_execution_str_replace_result":
|
|
531
|
+
return wrapText(content.lines?.join("\n") || "");
|
|
532
|
+
case "text_editor_code_execution_tool_result_error":
|
|
533
|
+
return wrapText(`Error: ${content.error_code}${content.error_message ? ` - ${content.error_message}` : ""}`);
|
|
534
|
+
default:
|
|
535
|
+
return wrapText(JSON.stringify(content));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
477
538
|
export function planEntries(input) {
|
|
478
539
|
return input.todos.map((input) => ({
|
|
479
540
|
content: input.content,
|
|
@@ -499,16 +560,22 @@ export const registerHookCallback = (toolUseID, { onPostToolUseHook, }) => {
|
|
|
499
560
|
};
|
|
500
561
|
};
|
|
501
562
|
/* 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"
|
|
504
|
-
|
|
505
|
-
if (
|
|
506
|
-
await
|
|
507
|
-
delete toolUseCallbacks[toolUseID]; // Cleanup after execution
|
|
563
|
+
export const createPostToolUseHook = (logger = console, options) => async (input, toolUseID) => {
|
|
564
|
+
if (input.hook_event_name === "PostToolUse") {
|
|
565
|
+
// Handle EnterPlanMode tool - notify client of mode change after successful execution
|
|
566
|
+
if (input.tool_name === "EnterPlanMode" && options?.onEnterPlanMode) {
|
|
567
|
+
await options.onEnterPlanMode();
|
|
508
568
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
569
|
+
if (toolUseID) {
|
|
570
|
+
const onPostToolUseHook = toolUseCallbacks[toolUseID]?.onPostToolUseHook;
|
|
571
|
+
if (onPostToolUseHook) {
|
|
572
|
+
await onPostToolUseHook(toolUseID, input.tool_input, input.tool_response);
|
|
573
|
+
delete toolUseCallbacks[toolUseID]; // Cleanup after execution
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
logger.error(`No onPostToolUseHook found for tool use ID: ${toolUseID}`);
|
|
577
|
+
delete toolUseCallbacks[toolUseID];
|
|
578
|
+
}
|
|
512
579
|
}
|
|
513
580
|
}
|
|
514
581
|
return { continue: true };
|
package/dist/utils.d.ts
CHANGED
|
@@ -29,4 +29,5 @@ export interface ExtractLinesResult {
|
|
|
29
29
|
* @returns Object containing extracted content and metadata
|
|
30
30
|
*/
|
|
31
31
|
export declare function extractLinesWithByteLimit(fullContent: string, maxContentLength: number): ExtractLinesResult;
|
|
32
|
+
export declare function encodeProjectPath(cwd: string): string;
|
|
32
33
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -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;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"}
|
|
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;AAKD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUrD"}
|
package/dist/utils.js
CHANGED
|
@@ -148,3 +148,16 @@ export function extractLinesWithByteLimit(fullContent, maxContentLength) {
|
|
|
148
148
|
linesRead: linesSeen,
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
|
+
// Helper to encode a path like Claude does:
|
|
152
|
+
// - Unix: "/Users/test" -> "-Users-test"
|
|
153
|
+
// - Windows: "C:\Users\test" -> "C-Users-test"
|
|
154
|
+
export function encodeProjectPath(cwd) {
|
|
155
|
+
const windowsPathMatch = cwd.match(/^([A-Za-z]):[\\/]/);
|
|
156
|
+
if (windowsPathMatch) {
|
|
157
|
+
const driveLetter = windowsPathMatch[1];
|
|
158
|
+
const rest = cwd.slice(2);
|
|
159
|
+
return `${driveLetter}${rest.replace(/[\\/]/g, "-")}`;
|
|
160
|
+
}
|
|
161
|
+
// Unix paths
|
|
162
|
+
return cwd.replace(/\//g, "-");
|
|
163
|
+
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.16.0",
|
|
7
7
|
"description": "An ACP-compatible coding agent powered by the Claude Code SDK (TypeScript)",
|
|
8
8
|
"main": "dist/lib.js",
|
|
9
9
|
"types": "dist/lib.d.ts",
|
|
@@ -60,23 +60,23 @@
|
|
|
60
60
|
"author": "Zed Industries",
|
|
61
61
|
"license": "Apache-2.0",
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@agentclientprotocol/sdk": "0.
|
|
64
|
-
"@anthropic-ai/claude-agent-sdk": "0.2.
|
|
65
|
-
"@modelcontextprotocol/sdk": "1.
|
|
63
|
+
"@agentclientprotocol/sdk": "0.14.1",
|
|
64
|
+
"@anthropic-ai/claude-agent-sdk": "0.2.34",
|
|
65
|
+
"@modelcontextprotocol/sdk": "1.26.0",
|
|
66
66
|
"diff": "8.0.3",
|
|
67
|
-
"minimatch": "10.1.
|
|
67
|
+
"minimatch": "10.1.2"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@anthropic-ai/sdk": "0.
|
|
71
|
-
"@types/node": "25.
|
|
72
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
73
|
-
"@typescript-eslint/parser": "8.
|
|
70
|
+
"@anthropic-ai/sdk": "0.73.0",
|
|
71
|
+
"@types/node": "25.2.1",
|
|
72
|
+
"@typescript-eslint/eslint-plugin": "8.54.0",
|
|
73
|
+
"@typescript-eslint/parser": "8.54.0",
|
|
74
74
|
"eslint": "9.39.2",
|
|
75
75
|
"eslint-config-prettier": "10.1.8",
|
|
76
|
-
"globals": "17.
|
|
77
|
-
"prettier": "3.8.
|
|
76
|
+
"globals": "17.3.0",
|
|
77
|
+
"prettier": "3.8.1",
|
|
78
78
|
"ts-node": "10.9.2",
|
|
79
79
|
"typescript": "5.9.3",
|
|
80
|
-
"vitest": "4.0.
|
|
80
|
+
"vitest": "4.0.18"
|
|
81
81
|
}
|
|
82
82
|
}
|