@sudocode-ai/claude-code-acp 0.12.10 → 0.13.2
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 +4 -10
- package/dist/acp-agent.d.ts +205 -0
- package/dist/acp-agent.d.ts.map +1 -0
- package/dist/acp-agent.js +103 -33
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/lib.d.ts +7 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/mcp-server.d.ts +21 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +214 -158
- package/dist/settings.d.ts +123 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/tools.d.ts +50 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +3 -1
- package/dist/utils.d.ts +32 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +22 -12
- package/dist/tests/acp-agent-fork.test.js +0 -338
- package/dist/tests/acp-agent.test.js +0 -753
- package/dist/tests/extract-lines.test.js +0 -79
- package/dist/tests/fork-session.test.js +0 -83
- package/dist/tests/replace-and-calculate-location.test.js +0 -266
- package/dist/tests/settings.test.js +0 -462
package/README.md
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
Use [Claude Code](https://www.anthropic.com/claude-code) from [ACP-compatible](https://agentclientprotocol.com) clients such as [Zed](https://zed.dev)!
|
|
6
6
|
|
|
7
|
-
This tool implements an ACP agent by using the official [Claude
|
|
7
|
+
This tool implements an ACP agent by using the official [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview), supporting:
|
|
8
8
|
|
|
9
9
|
- Context @-mentions
|
|
10
10
|
- Images
|
|
11
11
|
- Tool calls (with permission requests)
|
|
12
12
|
- Following
|
|
13
|
-
- Edit
|
|
13
|
+
- Edit reviewddddd
|
|
14
14
|
- TODO lists
|
|
15
15
|
- Interactive (and background) terminals
|
|
16
16
|
- Custom [Slash commands](https://docs.anthropic.com/en/docs/claude-code/slash-commands)
|
|
@@ -32,20 +32,14 @@ Read the docs on [External Agent](https://zed.dev/docs/ai/external-agents) suppo
|
|
|
32
32
|
|
|
33
33
|
### Other clients
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
- [marimo notebook](https://github.com/marimo-team/marimo)
|
|
37
|
-
- Neovim
|
|
38
|
-
- via [CodeCompanion.nvim](https://codecompanion.olimorris.dev/configuration/adapters#setup-claude-code-via-acp)
|
|
39
|
-
- via [yetone/avante.nvim](https://github.com/yetone/avante.nvim)
|
|
40
|
-
|
|
41
|
-
[Submit a PR](https://github.com/zed-industries/claude-code-acp/pulls) to add yours!
|
|
35
|
+
Or try it with any of the other [ACP compatible clients](https://agentclientprotocol.com/overview/clients)!
|
|
42
36
|
|
|
43
37
|
#### Installation
|
|
44
38
|
|
|
45
39
|
Install the adapter from `npm`:
|
|
46
40
|
|
|
47
41
|
```bash
|
|
48
|
-
npm install @zed-industries/claude-code-acp
|
|
42
|
+
npm install -g @zed-industries/claude-code-acp
|
|
49
43
|
```
|
|
50
44
|
|
|
51
45
|
You can then use `claude-code-acp` as a regular ACP agent:
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { Agent, AgentSideConnection, AuthenticateRequest, CancelNotification, ClientCapabilities, ForkSessionRequest, ForkSessionResponse, InitializeRequest, InitializeResponse, LoadSessionRequest, LoadSessionResponse, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, ReadTextFileRequest, ReadTextFileResponse, ResumeSessionRequest, ResumeSessionResponse, SessionNotification, SetSessionModelRequest, SetSessionModelResponse, SetSessionModeRequest, SetSessionModeResponse, TerminalHandle, TerminalOutputResponse, WriteTextFileRequest, WriteTextFileResponse } from "@agentclientprotocol/sdk";
|
|
2
|
+
import { SettingsManager } from "./settings.js";
|
|
3
|
+
import { CanUseTool, Options, PermissionMode, Query, SDKPartialAssistantMessage, SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
4
|
+
import { Pushable } from "./utils.js";
|
|
5
|
+
import { ContentBlockParam } from "@anthropic-ai/sdk/resources";
|
|
6
|
+
import { BetaContentBlock, BetaRawContentBlockDelta } from "@anthropic-ai/sdk/resources/beta.mjs";
|
|
7
|
+
export declare const CLAUDE_CONFIG_DIR: string;
|
|
8
|
+
/**
|
|
9
|
+
* Logger interface for customizing logging output
|
|
10
|
+
*/
|
|
11
|
+
export interface Logger {
|
|
12
|
+
log: (...args: any[]) => void;
|
|
13
|
+
error: (...args: any[]) => void;
|
|
14
|
+
}
|
|
15
|
+
type Session = {
|
|
16
|
+
query: Query;
|
|
17
|
+
input: Pushable<SDKUserMessage>;
|
|
18
|
+
cancelled: boolean;
|
|
19
|
+
permissionMode: PermissionMode;
|
|
20
|
+
settingsManager: SettingsManager;
|
|
21
|
+
abortController: AbortController;
|
|
22
|
+
cwd: string;
|
|
23
|
+
/** Optional: the actual session file path (for forked sessions where filename differs from sessionId) */
|
|
24
|
+
sessionFilePath?: string;
|
|
25
|
+
};
|
|
26
|
+
type BackgroundTerminal = {
|
|
27
|
+
handle: TerminalHandle;
|
|
28
|
+
status: "started";
|
|
29
|
+
lastOutput: TerminalOutputResponse | null;
|
|
30
|
+
} | {
|
|
31
|
+
status: "aborted" | "exited" | "killed" | "timedOut";
|
|
32
|
+
pendingOutput: TerminalOutputResponse;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Extra metadata that can be given to Claude Code when creating a new session.
|
|
36
|
+
*/
|
|
37
|
+
export type NewSessionMeta = {
|
|
38
|
+
claudeCode?: {
|
|
39
|
+
/**
|
|
40
|
+
* Options forwarded to Claude Code when starting a new session.
|
|
41
|
+
* Those parameters will be ignored and managed by ACP:
|
|
42
|
+
* - cwd
|
|
43
|
+
* - includePartialMessages
|
|
44
|
+
* - allowDangerouslySkipPermissions
|
|
45
|
+
* - permissionMode
|
|
46
|
+
* - canUseTool
|
|
47
|
+
* - executable
|
|
48
|
+
* Those parameters will be used and updated to work with ACP:
|
|
49
|
+
* - hooks (merged with ACP's hooks)
|
|
50
|
+
* - mcpServers (merged with ACP's mcpServers)
|
|
51
|
+
*/
|
|
52
|
+
options?: Options;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Extra metadata that the agent provides for each tool_call / tool_update update.
|
|
57
|
+
*/
|
|
58
|
+
export type ToolUpdateMeta = {
|
|
59
|
+
claudeCode?: {
|
|
60
|
+
toolName: string;
|
|
61
|
+
toolResponse?: unknown;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
type ToolUseCache = {
|
|
65
|
+
[key: string]: {
|
|
66
|
+
type: "tool_use" | "server_tool_use" | "mcp_tool_use";
|
|
67
|
+
id: string;
|
|
68
|
+
name: string;
|
|
69
|
+
input: any;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
export declare class ClaudeAcpAgent implements Agent {
|
|
73
|
+
sessions: {
|
|
74
|
+
[key: string]: Session;
|
|
75
|
+
};
|
|
76
|
+
client: AgentSideConnection;
|
|
77
|
+
toolUseCache: ToolUseCache;
|
|
78
|
+
backgroundTerminals: {
|
|
79
|
+
[key: string]: BackgroundTerminal;
|
|
80
|
+
};
|
|
81
|
+
clientCapabilities?: ClientCapabilities;
|
|
82
|
+
logger: Logger;
|
|
83
|
+
constructor(client: AgentSideConnection, logger?: Logger);
|
|
84
|
+
initialize(request: InitializeRequest): Promise<InitializeResponse>;
|
|
85
|
+
newSession(params: NewSessionRequest): Promise<NewSessionResponse>;
|
|
86
|
+
/**
|
|
87
|
+
* Fork an existing session to create a new independent session.
|
|
88
|
+
* This is the ACP protocol method handler for session/fork.
|
|
89
|
+
* Named unstable_forkSession to match SDK expectations (session/fork routes to this method).
|
|
90
|
+
*/
|
|
91
|
+
unstable_forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse>;
|
|
92
|
+
/**
|
|
93
|
+
* Get the directory where session files are stored for a given cwd.
|
|
94
|
+
*/
|
|
95
|
+
private getSessionDirPath;
|
|
96
|
+
/**
|
|
97
|
+
* Extract the internal sessionId from a session JSONL file.
|
|
98
|
+
* The CLI stores the actual session ID inside the file, which may differ from the filename.
|
|
99
|
+
* For forked sessions, the filename is "agent-xxx" but the internal sessionId is a UUID.
|
|
100
|
+
*/
|
|
101
|
+
private extractInternalSessionId;
|
|
102
|
+
/**
|
|
103
|
+
* Promote a sidechain session to a regular session by modifying the session file.
|
|
104
|
+
* Forked sessions have "isSidechain": true which prevents them from being resumed.
|
|
105
|
+
* This method changes it to false so the session can be resumed/forked again.
|
|
106
|
+
*/
|
|
107
|
+
private promoteToFullSession;
|
|
108
|
+
/**
|
|
109
|
+
* Update the sessionId in all lines of a session JSONL file.
|
|
110
|
+
* This is used when we need to assign a new unique session ID to avoid collisions.
|
|
111
|
+
*/
|
|
112
|
+
private updateSessionIdInFile;
|
|
113
|
+
/**
|
|
114
|
+
* Discover the CLI-assigned session ID by looking for new session files.
|
|
115
|
+
* Returns the CLI's session ID if found, or the original sessionId if not.
|
|
116
|
+
*/
|
|
117
|
+
private discoverCliSessionId;
|
|
118
|
+
/**
|
|
119
|
+
* Alias for unstable_forkSession for convenience.
|
|
120
|
+
*/
|
|
121
|
+
forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse>;
|
|
122
|
+
/**
|
|
123
|
+
* Load an existing session to resume a previous conversation.
|
|
124
|
+
* This is the ACP protocol method handler for session/load.
|
|
125
|
+
*/
|
|
126
|
+
loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse>;
|
|
127
|
+
/**
|
|
128
|
+
* Resume an existing session. This delegates to loadSession for enhanced functionality.
|
|
129
|
+
*/
|
|
130
|
+
unstable_resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse>;
|
|
131
|
+
authenticate(_params: AuthenticateRequest): Promise<void>;
|
|
132
|
+
prompt(params: PromptRequest): Promise<PromptResponse>;
|
|
133
|
+
cancel(params: CancelNotification): Promise<void>;
|
|
134
|
+
/**
|
|
135
|
+
* Handle extension methods from the client.
|
|
136
|
+
*
|
|
137
|
+
* Currently supports:
|
|
138
|
+
* - `_session/inject`: Inject a message into an active session mid-execution
|
|
139
|
+
* - `_session/flush`: Flush a session to disk for fork-with-flush support
|
|
140
|
+
*/
|
|
141
|
+
extMethod(method: string, params: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
142
|
+
/**
|
|
143
|
+
* Inject a message into an active session.
|
|
144
|
+
*
|
|
145
|
+
* This allows sending additional user input while a prompt() call is actively
|
|
146
|
+
* processing. The injected message will be queued and processed by the agent
|
|
147
|
+
* as part of the current conversation turn.
|
|
148
|
+
*
|
|
149
|
+
* Use cases:
|
|
150
|
+
* - Providing clarification while the agent is working
|
|
151
|
+
* - Adding context mid-execution
|
|
152
|
+
* - Sending corrections before a tool completes
|
|
153
|
+
*
|
|
154
|
+
* @param params.sessionId - The session to inject the message into
|
|
155
|
+
* @param params.message - Either a string or an array of ContentBlocks (same format as prompt)
|
|
156
|
+
* @returns Success status and any error message
|
|
157
|
+
*/
|
|
158
|
+
private handleSessionInject;
|
|
159
|
+
/**
|
|
160
|
+
* Flush a session to disk by aborting its query subprocess.
|
|
161
|
+
*
|
|
162
|
+
* This is used by the fork-with-flush mechanism to ensure session data
|
|
163
|
+
* is persisted to disk before forking. When the Claude SDK subprocess
|
|
164
|
+
* exits (via abort), it writes the session data to:
|
|
165
|
+
* ~/.claude/projects/<cwd-hash>/<sessionId>.jsonl
|
|
166
|
+
*
|
|
167
|
+
* After this method completes, the session is removed from memory and
|
|
168
|
+
* must be reloaded via loadSession() to continue using it.
|
|
169
|
+
*/
|
|
170
|
+
private handleSessionFlush;
|
|
171
|
+
/**
|
|
172
|
+
* Get the file path where Claude Code stores session data.
|
|
173
|
+
*
|
|
174
|
+
* Claude Code stores sessions at:
|
|
175
|
+
* ~/.claude/projects/<cwd-hash>/<sessionId>.jsonl
|
|
176
|
+
*
|
|
177
|
+
* Where <cwd-hash> is the cwd with `/` replaced by `-`
|
|
178
|
+
* Note: We resolve the real path to handle macOS symlinks like /var -> /private/var
|
|
179
|
+
*/
|
|
180
|
+
private getSessionFilePath;
|
|
181
|
+
/**
|
|
182
|
+
* Wait for a session file to appear on disk.
|
|
183
|
+
*
|
|
184
|
+
* @param filePath - Path to the session file
|
|
185
|
+
* @param timeout - Maximum time to wait in milliseconds
|
|
186
|
+
* @returns true if file appears, false if timeout
|
|
187
|
+
*/
|
|
188
|
+
private waitForSessionFile;
|
|
189
|
+
unstable_setSessionModel(params: SetSessionModelRequest): Promise<SetSessionModelResponse | void>;
|
|
190
|
+
setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse>;
|
|
191
|
+
readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse>;
|
|
192
|
+
writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse>;
|
|
193
|
+
canUseTool(sessionId: string): CanUseTool;
|
|
194
|
+
private createSession;
|
|
195
|
+
}
|
|
196
|
+
export declare function promptToClaude(prompt: PromptRequest): SDKUserMessage;
|
|
197
|
+
/**
|
|
198
|
+
* Convert an SDKAssistantMessage (Claude) to a SessionNotification (ACP).
|
|
199
|
+
* Only handles text, image, and thinking chunks for now.
|
|
200
|
+
*/
|
|
201
|
+
export declare function toAcpNotifications(content: string | ContentBlockParam[] | BetaContentBlock[] | BetaRawContentBlockDelta[], role: "assistant" | "user", sessionId: string, toolUseCache: ToolUseCache, client: AgentSideConnection, logger: Logger): SessionNotification[];
|
|
202
|
+
export declare function streamEventToAcpNotifications(message: SDKPartialAssistantMessage, sessionId: string, toolUseCache: ToolUseCache, client: AgentSideConnection, logger: Logger): SessionNotification[];
|
|
203
|
+
export declare function runAcp(): void;
|
|
204
|
+
export {};
|
|
205
|
+
//# sourceMappingURL=acp-agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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
|
@@ -54,6 +54,7 @@ export class ClaudeAcpAgent {
|
|
|
54
54
|
},
|
|
55
55
|
sessionCapabilities: {
|
|
56
56
|
fork: {},
|
|
57
|
+
resume: {},
|
|
57
58
|
},
|
|
58
59
|
loadSession: true,
|
|
59
60
|
},
|
|
@@ -84,7 +85,7 @@ export class ClaudeAcpAgent {
|
|
|
84
85
|
// Get the session directory to track new files
|
|
85
86
|
const sessionDir = this.getSessionDirPath(params.cwd);
|
|
86
87
|
const beforeFiles = new Set(fs.existsSync(sessionDir)
|
|
87
|
-
? fs.readdirSync(sessionDir).filter(f => f.endsWith(
|
|
88
|
+
? fs.readdirSync(sessionDir).filter((f) => f.endsWith(".jsonl"))
|
|
88
89
|
: []);
|
|
89
90
|
const result = await this.createSession({
|
|
90
91
|
cwd: params.cwd,
|
|
@@ -95,7 +96,7 @@ export class ClaudeAcpAgent {
|
|
|
95
96
|
forkSession: true,
|
|
96
97
|
});
|
|
97
98
|
// Wait briefly for CLI to create the session file
|
|
98
|
-
await new Promise(resolve => setTimeout(resolve, 200));
|
|
99
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
99
100
|
// Find the CLI-assigned session ID by looking for new session files
|
|
100
101
|
const cliSessionId = await this.discoverCliSessionId(sessionDir, beforeFiles, result.sessionId);
|
|
101
102
|
if (cliSessionId && cliSessionId !== result.sessionId) {
|
|
@@ -167,13 +168,13 @@ export class ClaudeAcpAgent {
|
|
|
167
168
|
if (!fs.existsSync(filePath)) {
|
|
168
169
|
return null;
|
|
169
170
|
}
|
|
170
|
-
const content = fs.readFileSync(filePath,
|
|
171
|
-
const firstLine = content.split(
|
|
171
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
172
|
+
const firstLine = content.split("\n").find((line) => line.trim().length > 0);
|
|
172
173
|
if (!firstLine) {
|
|
173
174
|
return null;
|
|
174
175
|
}
|
|
175
176
|
const parsed = JSON.parse(firstLine);
|
|
176
|
-
if (parsed.sessionId && typeof parsed.sessionId ===
|
|
177
|
+
if (parsed.sessionId && typeof parsed.sessionId === "string") {
|
|
177
178
|
// Verify it's a UUID format
|
|
178
179
|
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(parsed.sessionId);
|
|
179
180
|
if (isUuid) {
|
|
@@ -197,8 +198,8 @@ export class ClaudeAcpAgent {
|
|
|
197
198
|
if (!fs.existsSync(filePath)) {
|
|
198
199
|
return false;
|
|
199
200
|
}
|
|
200
|
-
const content = fs.readFileSync(filePath,
|
|
201
|
-
const lines = content.split(
|
|
201
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
202
|
+
const lines = content.split("\n");
|
|
202
203
|
const modifiedLines = [];
|
|
203
204
|
for (const line of lines) {
|
|
204
205
|
if (!line.trim()) {
|
|
@@ -218,7 +219,7 @@ export class ClaudeAcpAgent {
|
|
|
218
219
|
modifiedLines.push(line);
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
|
-
fs.writeFileSync(filePath, modifiedLines.join(
|
|
222
|
+
fs.writeFileSync(filePath, modifiedLines.join("\n"), "utf-8");
|
|
222
223
|
this.logger.log(`[claude-code-acp] Promoted sidechain to full session: ${filePath}`);
|
|
223
224
|
return true;
|
|
224
225
|
}
|
|
@@ -236,8 +237,8 @@ export class ClaudeAcpAgent {
|
|
|
236
237
|
if (!fs.existsSync(filePath)) {
|
|
237
238
|
return false;
|
|
238
239
|
}
|
|
239
|
-
const content = fs.readFileSync(filePath,
|
|
240
|
-
const lines = content.split(
|
|
240
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
241
|
+
const lines = content.split("\n");
|
|
241
242
|
const modifiedLines = [];
|
|
242
243
|
for (const line of lines) {
|
|
243
244
|
if (!line.trim()) {
|
|
@@ -247,7 +248,7 @@ export class ClaudeAcpAgent {
|
|
|
247
248
|
try {
|
|
248
249
|
const parsed = JSON.parse(line);
|
|
249
250
|
// Update the sessionId in each line
|
|
250
|
-
if (parsed.sessionId && typeof parsed.sessionId ===
|
|
251
|
+
if (parsed.sessionId && typeof parsed.sessionId === "string") {
|
|
251
252
|
parsed.sessionId = newSessionId;
|
|
252
253
|
}
|
|
253
254
|
modifiedLines.push(JSON.stringify(parsed));
|
|
@@ -257,7 +258,7 @@ export class ClaudeAcpAgent {
|
|
|
257
258
|
modifiedLines.push(line);
|
|
258
259
|
}
|
|
259
260
|
}
|
|
260
|
-
fs.writeFileSync(filePath, modifiedLines.join(
|
|
261
|
+
fs.writeFileSync(filePath, modifiedLines.join("\n"), "utf-8");
|
|
261
262
|
this.logger.log(`[claude-code-acp] Updated session ID in file: ${filePath}`);
|
|
262
263
|
return true;
|
|
263
264
|
}
|
|
@@ -276,18 +277,18 @@ export class ClaudeAcpAgent {
|
|
|
276
277
|
const agentPattern = /^agent-[a-f0-9]+\.jsonl$/;
|
|
277
278
|
while (Date.now() - start < timeout) {
|
|
278
279
|
if (fs.existsSync(sessionDir)) {
|
|
279
|
-
const currentFiles = fs.readdirSync(sessionDir).filter(f => f.endsWith(
|
|
280
|
+
const currentFiles = fs.readdirSync(sessionDir).filter((f) => f.endsWith(".jsonl"));
|
|
280
281
|
// Only look for new files that match the agent-xxx pattern
|
|
281
282
|
// This prevents picking up renamed UUID files from previous forks
|
|
282
|
-
const newFiles = currentFiles.filter(f => !beforeFiles.has(f) && agentPattern.test(f));
|
|
283
|
+
const newFiles = currentFiles.filter((f) => !beforeFiles.has(f) && agentPattern.test(f));
|
|
283
284
|
if (newFiles.length === 1) {
|
|
284
285
|
// Found exactly one new agent session file - this is our fork
|
|
285
286
|
this.logger.log(`[claude-code-acp] Discovered fork session file: ${newFiles[0]}`);
|
|
286
|
-
return newFiles[0].replace(
|
|
287
|
+
return newFiles[0].replace(".jsonl", "");
|
|
287
288
|
}
|
|
288
289
|
else if (newFiles.length > 1) {
|
|
289
290
|
// Multiple new agent files - try to find the most recent one
|
|
290
|
-
let newestFile =
|
|
291
|
+
let newestFile = "";
|
|
291
292
|
let newestMtime = 0;
|
|
292
293
|
for (const file of newFiles) {
|
|
293
294
|
const filePath = path.join(sessionDir, file);
|
|
@@ -299,11 +300,11 @@ export class ClaudeAcpAgent {
|
|
|
299
300
|
}
|
|
300
301
|
if (newestFile) {
|
|
301
302
|
this.logger.log(`[claude-code-acp] Discovered fork session file (newest of ${newFiles.length}): ${newestFile}`);
|
|
302
|
-
return newestFile.replace(
|
|
303
|
+
return newestFile.replace(".jsonl", "");
|
|
303
304
|
}
|
|
304
305
|
}
|
|
305
306
|
}
|
|
306
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
307
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
307
308
|
}
|
|
308
309
|
// Timeout - return fallback
|
|
309
310
|
this.logger.log(`[claude-code-acp] Could not discover CLI session ID, using fallback: ${fallbackId}`);
|
|
@@ -330,16 +331,17 @@ export class ClaudeAcpAgent {
|
|
|
330
331
|
return response;
|
|
331
332
|
}
|
|
332
333
|
/**
|
|
333
|
-
*
|
|
334
|
+
* Resume an existing session. This delegates to loadSession for enhanced functionality.
|
|
334
335
|
*/
|
|
335
336
|
async unstable_resumeSession(params) {
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
cwd: meta?.cwd ?? process.cwd(),
|
|
340
|
-
mcpServers: meta?.mcpServers ?? [],
|
|
337
|
+
const response = await this.createSession({
|
|
338
|
+
cwd: params.cwd,
|
|
339
|
+
mcpServers: params.mcpServers ?? [],
|
|
341
340
|
_meta: params._meta,
|
|
341
|
+
}, {
|
|
342
|
+
resume: params.sessionId,
|
|
342
343
|
});
|
|
344
|
+
return response;
|
|
343
345
|
}
|
|
344
346
|
async authenticate(_params) {
|
|
345
347
|
throw new Error("Method not implemented.");
|
|
@@ -476,14 +478,62 @@ export class ClaudeAcpAgent {
|
|
|
476
478
|
* Handle extension methods from the client.
|
|
477
479
|
*
|
|
478
480
|
* Currently supports:
|
|
481
|
+
* - `_session/inject`: Inject a message into an active session mid-execution
|
|
479
482
|
* - `_session/flush`: Flush a session to disk for fork-with-flush support
|
|
480
483
|
*/
|
|
481
484
|
async extMethod(method, params) {
|
|
485
|
+
if (method === "_session/inject") {
|
|
486
|
+
return this.handleSessionInject(params);
|
|
487
|
+
}
|
|
482
488
|
if (method === "_session/flush") {
|
|
483
489
|
return this.handleSessionFlush(params);
|
|
484
490
|
}
|
|
485
491
|
throw RequestError.methodNotFound(method);
|
|
486
492
|
}
|
|
493
|
+
/**
|
|
494
|
+
* Inject a message into an active session.
|
|
495
|
+
*
|
|
496
|
+
* This allows sending additional user input while a prompt() call is actively
|
|
497
|
+
* processing. The injected message will be queued and processed by the agent
|
|
498
|
+
* as part of the current conversation turn.
|
|
499
|
+
*
|
|
500
|
+
* Use cases:
|
|
501
|
+
* - Providing clarification while the agent is working
|
|
502
|
+
* - Adding context mid-execution
|
|
503
|
+
* - Sending corrections before a tool completes
|
|
504
|
+
*
|
|
505
|
+
* @param params.sessionId - The session to inject the message into
|
|
506
|
+
* @param params.message - Either a string or an array of ContentBlocks (same format as prompt)
|
|
507
|
+
* @returns Success status and any error message
|
|
508
|
+
*/
|
|
509
|
+
handleSessionInject(params) {
|
|
510
|
+
const { sessionId, message } = params;
|
|
511
|
+
const session = this.sessions[sessionId];
|
|
512
|
+
if (!session) {
|
|
513
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
514
|
+
}
|
|
515
|
+
if (session.cancelled) {
|
|
516
|
+
return { success: false, error: `Session ${sessionId} is cancelled` };
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
// Convert string to ContentBlock array if needed
|
|
520
|
+
const prompt = typeof message === "string" ? [{ type: "text", text: message }] : message;
|
|
521
|
+
// Create a PromptRequest-like object to reuse promptToClaude
|
|
522
|
+
const promptRequest = {
|
|
523
|
+
sessionId,
|
|
524
|
+
prompt,
|
|
525
|
+
};
|
|
526
|
+
// Convert to SDK format and push to the session's input queue
|
|
527
|
+
const sdkMessage = promptToClaude(promptRequest);
|
|
528
|
+
session.input.push(sdkMessage);
|
|
529
|
+
this.logger.log(`[claude-code-acp] Injected message into session ${sessionId}`);
|
|
530
|
+
return { success: true };
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
this.logger.error(`[claude-code-acp] Failed to inject message into session ${sessionId}:`, error);
|
|
534
|
+
return { success: false, error: String(error) };
|
|
535
|
+
}
|
|
536
|
+
}
|
|
487
537
|
/**
|
|
488
538
|
* Flush a session to disk by aborting its query subprocess.
|
|
489
539
|
*
|
|
@@ -568,7 +618,7 @@ export class ClaudeAcpAgent {
|
|
|
568
618
|
if (fs.existsSync(filePath)) {
|
|
569
619
|
return true;
|
|
570
620
|
}
|
|
571
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
621
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
572
622
|
}
|
|
573
623
|
return false;
|
|
574
624
|
}
|
|
@@ -728,9 +778,18 @@ export class ClaudeAcpAgent {
|
|
|
728
778
|
};
|
|
729
779
|
}
|
|
730
780
|
async createSession(params, creationOpts = {}) {
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
|
|
781
|
+
// We want to create a new session id unless it is resume,
|
|
782
|
+
// but not resume + forkSession.
|
|
783
|
+
let sessionId;
|
|
784
|
+
if (creationOpts.forkSession) {
|
|
785
|
+
sessionId = randomUUID();
|
|
786
|
+
}
|
|
787
|
+
else if (creationOpts.resume) {
|
|
788
|
+
sessionId = creationOpts.resume;
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
sessionId = randomUUID();
|
|
792
|
+
}
|
|
734
793
|
const input = new Pushable();
|
|
735
794
|
const settingsManager = new SettingsManager(params.cwd, {
|
|
736
795
|
logger: this.logger,
|
|
@@ -785,16 +844,19 @@ export class ClaudeAcpAgent {
|
|
|
785
844
|
// Extract options from _meta if provided
|
|
786
845
|
const userProvidedOptions = params._meta?.claudeCode?.options;
|
|
787
846
|
const extraArgs = { ...userProvidedOptions?.extraArgs };
|
|
788
|
-
if (creationOpts?.resume === undefined) {
|
|
847
|
+
if (creationOpts?.resume === undefined || creationOpts?.forkSession) {
|
|
789
848
|
// Set our own session id if not resuming an existing session.
|
|
790
|
-
// Note: For forked sessions (resume + fork), Claude CLI assigns its own session ID
|
|
791
|
-
// which means chain forking (fork of a fork) is not currently supported.
|
|
792
849
|
extraArgs["session-id"] = sessionId;
|
|
793
850
|
}
|
|
851
|
+
// Configure thinking tokens from environment variable
|
|
852
|
+
const maxThinkingTokens = process.env.MAX_THINKING_TOKENS
|
|
853
|
+
? parseInt(process.env.MAX_THINKING_TOKENS, 10)
|
|
854
|
+
: undefined;
|
|
794
855
|
const options = {
|
|
795
856
|
systemPrompt,
|
|
796
857
|
settingSources: ["user", "project", "local"],
|
|
797
858
|
stderr: (err) => this.logger.error(err),
|
|
859
|
+
...(maxThinkingTokens !== undefined && { maxThinkingTokens }),
|
|
798
860
|
...userProvidedOptions,
|
|
799
861
|
// Override certain fields that must be controlled by ACP
|
|
800
862
|
cwd: params.cwd,
|
|
@@ -812,6 +874,7 @@ export class ClaudeAcpAgent {
|
|
|
812
874
|
...(process.env.CLAUDE_CODE_EXECUTABLE && {
|
|
813
875
|
pathToClaudeCodeExecutable: process.env.CLAUDE_CODE_EXECUTABLE,
|
|
814
876
|
}),
|
|
877
|
+
tools: { type: "preset", preset: "claude_code" },
|
|
815
878
|
hooks: {
|
|
816
879
|
...userProvidedOptions?.hooks,
|
|
817
880
|
PreToolUse: [
|
|
@@ -830,7 +893,8 @@ export class ClaudeAcpAgent {
|
|
|
830
893
|
...creationOpts,
|
|
831
894
|
};
|
|
832
895
|
const allowedTools = [];
|
|
833
|
-
|
|
896
|
+
// Disable this for now, not a great way to expose this over ACP at the moment (in progress work so we can revisit)
|
|
897
|
+
const disallowedTools = ["AskUserQuestion"];
|
|
834
898
|
// Check if built-in tools should be disabled
|
|
835
899
|
const disableBuiltInTools = params._meta?.disableBuiltInTools === true;
|
|
836
900
|
if (!disableBuiltInTools) {
|
|
@@ -958,7 +1022,13 @@ async function getAvailableSlashCommands(query) {
|
|
|
958
1022
|
const commands = await query.supportedCommands();
|
|
959
1023
|
return commands
|
|
960
1024
|
.map((command) => {
|
|
961
|
-
const input = command.argumentHint
|
|
1025
|
+
const input = command.argumentHint
|
|
1026
|
+
? {
|
|
1027
|
+
hint: Array.isArray(command.argumentHint)
|
|
1028
|
+
? command.argumentHint.join(" ")
|
|
1029
|
+
: command.argumentHint,
|
|
1030
|
+
}
|
|
1031
|
+
: null;
|
|
962
1032
|
let name = command.name;
|
|
963
1033
|
if (command.name.endsWith(" (MCP)")) {
|
|
964
1034
|
name = `mcp:${name.replace(" (MCP)", "")}`;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ClaudeAcpAgent, runAcp, toAcpNotifications, streamEventToAcpNotifications, type ToolUpdateMeta, type NewSessionMeta, } from "./acp-agent.js";
|
|
2
|
+
export { loadManagedSettings, applyEnvironmentSettings, nodeToWebReadable, nodeToWebWritable, Pushable, unreachable, } from "./utils.js";
|
|
3
|
+
export { createMcpServer } from "./mcp-server.js";
|
|
4
|
+
export { toolInfoFromToolUse, planEntries, toolUpdateFromToolResult, createPreToolUseHook, acpToolNames as toolNames, } from "./tools.js";
|
|
5
|
+
export { SettingsManager, type ClaudeCodeSettings, type PermissionSettings, type PermissionDecision, type PermissionCheckResult, type SettingsManagerOptions, } from "./settings.js";
|
|
6
|
+
export type { ClaudePlanEntry } from "./tools.js";
|
|
7
|
+
//# sourceMappingURL=lib.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,MAAM,EACN,kBAAkB,EAClB,6BAA6B,EAC7B,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,wBAAwB,EACxB,oBAAoB,EACpB,YAAY,IAAI,SAAS,GAC1B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,eAAe,CAAC;AAGvB,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ClaudeAcpAgent } from "./acp-agent.js";
|
|
3
|
+
import { ClientCapabilities } from "@agentclientprotocol/sdk";
|
|
4
|
+
export declare const SYSTEM_REMINDER = "\n\n<system-reminder>\nWhenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.\n</system-reminder>";
|
|
5
|
+
export declare function createMcpServer(agent: ClaudeAcpAgent, sessionId: string, clientCapabilities: ClientCapabilities | undefined): McpServer;
|
|
6
|
+
/**
|
|
7
|
+
* Replace text in a file and calculate the line numbers where the edits occurred.
|
|
8
|
+
*
|
|
9
|
+
* @param fileContent - The full file content
|
|
10
|
+
* @param edits - Array of edit operations to apply sequentially
|
|
11
|
+
* @returns the new content and the line numbers where replacements occurred in the final content
|
|
12
|
+
*/
|
|
13
|
+
export declare function replaceAndCalculateLocation(fileContent: string, edits: Array<{
|
|
14
|
+
oldText: string;
|
|
15
|
+
newText: string;
|
|
16
|
+
replaceAll?: boolean;
|
|
17
|
+
}>): {
|
|
18
|
+
newContent: string;
|
|
19
|
+
lineNumbers: number[];
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=mcp-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AASpE,OAAO,EAAqB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EACL,kBAAkB,EAGnB,MAAM,0BAA0B,CAAC;AAQlC,eAAO,MAAM,eAAe,iSAIT,CAAC;AA2BpB,wBAAgB,eAAe,CAC7B,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,kBAAkB,GAAG,SAAS,GACjD,SAAS,CA2nBX;AA+DD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,KAAK,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC,GACD;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CAyF/C"}
|