@wrongstack/acp 0.295.1 → 0.296.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/dist/agent/stdio-transport.d.ts.map +1 -1
- package/dist/agent.js.map +1 -1
- package/dist/client/acp-message-routing.d.ts +2 -0
- package/dist/client/acp-message-routing.d.ts.map +1 -0
- package/dist/client/acp-session-callbacks.d.ts +12 -0
- package/dist/client/acp-session-callbacks.d.ts.map +1 -0
- package/dist/client/acp-session-content.d.ts +24 -0
- package/dist/client/acp-session-content.d.ts.map +1 -0
- package/dist/client/acp-session-errors.d.ts +13 -0
- package/dist/client/acp-session-errors.d.ts.map +1 -0
- package/dist/client/acp-session-types.d.ts +81 -0
- package/dist/client/acp-session-types.d.ts.map +1 -0
- package/dist/client/acp-session-updates.d.ts +18 -0
- package/dist/client/acp-session-updates.d.ts.map +1 -0
- package/dist/client/acp-session.d.ts +6 -171
- package/dist/client/acp-session.d.ts.map +1 -1
- package/dist/client.js +318 -313
- package/dist/client.js.map +3 -3
- package/dist/index.js +318 -313
- package/dist/index.js.map +3 -3
- package/dist/wrongstack-acp-agent.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -974,6 +974,9 @@ var ClientTransport = class {
|
|
|
974
974
|
};
|
|
975
975
|
const waitForMarker = (chunk) => {
|
|
976
976
|
this.buffer += chunk;
|
|
977
|
+
if (this.buffer.length > this.maxFrameChars) {
|
|
978
|
+
this.buffer = this.buffer.slice(-this.maxFrameChars);
|
|
979
|
+
}
|
|
977
980
|
const idx = this.buffer.indexOf("[wstack-acp]\n");
|
|
978
981
|
if (idx !== -1) {
|
|
979
982
|
this.buffer = this.buffer.slice(idx + "[wstack-acp]\n".length);
|
|
@@ -1401,6 +1404,39 @@ if (isEntrypoint) {
|
|
|
1401
1404
|
});
|
|
1402
1405
|
}
|
|
1403
1406
|
|
|
1407
|
+
// src/client/acp-session-content.ts
|
|
1408
|
+
function textContent(text) {
|
|
1409
|
+
return { type: "text", text };
|
|
1410
|
+
}
|
|
1411
|
+
function imageContent(mimeType, data) {
|
|
1412
|
+
return { type: "image", mimeType, data };
|
|
1413
|
+
}
|
|
1414
|
+
function audioContent(mimeType, data) {
|
|
1415
|
+
return { type: "audio", mimeType, data };
|
|
1416
|
+
}
|
|
1417
|
+
function extractText(block) {
|
|
1418
|
+
if (typeof block !== "object" || block === null) return "";
|
|
1419
|
+
const b = block;
|
|
1420
|
+
if (b.type === "text" && typeof b.text === "string") return b.text;
|
|
1421
|
+
if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
|
|
1422
|
+
return b.resource.text;
|
|
1423
|
+
}
|
|
1424
|
+
return "";
|
|
1425
|
+
}
|
|
1426
|
+
function isRecord(v) {
|
|
1427
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1428
|
+
}
|
|
1429
|
+
function emptyRunResult(stopReason) {
|
|
1430
|
+
return {
|
|
1431
|
+
text: "",
|
|
1432
|
+
stopReason,
|
|
1433
|
+
hasText: false,
|
|
1434
|
+
toolCalls: [],
|
|
1435
|
+
diffs: [],
|
|
1436
|
+
thoughts: ""
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1404
1440
|
// src/client/file-server.ts
|
|
1405
1441
|
import { randomBytes } from "node:crypto";
|
|
1406
1442
|
import { realpathSync } from "node:fs";
|
|
@@ -2076,7 +2112,7 @@ function finitePositiveLimit(value, fallback) {
|
|
|
2076
2112
|
return value !== void 0 && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
2077
2113
|
}
|
|
2078
2114
|
|
|
2079
|
-
// src/client/acp-session.ts
|
|
2115
|
+
// src/client/acp-session-errors.ts
|
|
2080
2116
|
var ACPSessionError = class extends Error {
|
|
2081
2117
|
kind;
|
|
2082
2118
|
cause;
|
|
@@ -2090,6 +2126,264 @@ var ACPSessionError = class extends Error {
|
|
|
2090
2126
|
function isJsonRpcError(v) {
|
|
2091
2127
|
return typeof v === "object" && v !== null && typeof v.code === "number" && typeof v.message === "string";
|
|
2092
2128
|
}
|
|
2129
|
+
|
|
2130
|
+
// src/client/acp-session-updates.ts
|
|
2131
|
+
function createSessionScratch() {
|
|
2132
|
+
return { text: "", thoughts: "", toolCalls: /* @__PURE__ */ new Map(), diffs: [] };
|
|
2133
|
+
}
|
|
2134
|
+
function handleAcpSessionUpdate(msg, scratch, emitProgress) {
|
|
2135
|
+
const update = msg.params?.update;
|
|
2136
|
+
if (typeof update !== "object" || update === null) return;
|
|
2137
|
+
const u = update;
|
|
2138
|
+
emitProgress({ type: "raw", update: u });
|
|
2139
|
+
switch (u.sessionUpdate) {
|
|
2140
|
+
case "agent_message_chunk": {
|
|
2141
|
+
const text = extractText(u.content);
|
|
2142
|
+
if (text) {
|
|
2143
|
+
scratch.text += text;
|
|
2144
|
+
emitProgress({ type: "message", text });
|
|
2145
|
+
}
|
|
2146
|
+
return;
|
|
2147
|
+
}
|
|
2148
|
+
case "thought_chunk": {
|
|
2149
|
+
const text = extractText(u.content);
|
|
2150
|
+
if (text) {
|
|
2151
|
+
scratch.thoughts += text;
|
|
2152
|
+
emitProgress({ type: "thought", text });
|
|
2153
|
+
}
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
case "tool_call":
|
|
2157
|
+
case "tool_call_update":
|
|
2158
|
+
captureToolCall(u, u.sessionUpdate === "tool_call", scratch, emitProgress);
|
|
2159
|
+
return;
|
|
2160
|
+
case "plan":
|
|
2161
|
+
if (Array.isArray(u.entries)) {
|
|
2162
|
+
scratch.plan = u.entries;
|
|
2163
|
+
emitProgress({ type: "plan", entries: u.entries });
|
|
2164
|
+
}
|
|
2165
|
+
return;
|
|
2166
|
+
case "usage_update":
|
|
2167
|
+
if (typeof u.used === "number" && typeof u.size === "number") {
|
|
2168
|
+
const usage = {
|
|
2169
|
+
used: u.used,
|
|
2170
|
+
size: u.size,
|
|
2171
|
+
...typeof u.cost === "object" && u.cost !== null ? { cost: u.cost } : {}
|
|
2172
|
+
};
|
|
2173
|
+
scratch.usage = usage;
|
|
2174
|
+
emitProgress({ type: "usage", usage });
|
|
2175
|
+
}
|
|
2176
|
+
return;
|
|
2177
|
+
case "available_commands_update":
|
|
2178
|
+
case "current_mode_update":
|
|
2179
|
+
case "config_option_update":
|
|
2180
|
+
case "session_info_update":
|
|
2181
|
+
case "user_message_chunk":
|
|
2182
|
+
case "next_edit_suggestions":
|
|
2183
|
+
case "elicitation":
|
|
2184
|
+
return;
|
|
2185
|
+
default:
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
function captureToolCall(u, isNew, scratch, emitProgress) {
|
|
2190
|
+
const toolCallId = typeof u.toolCallId === "string" ? u.toolCallId : "";
|
|
2191
|
+
if (!toolCallId) return;
|
|
2192
|
+
const prev = scratch.toolCalls.get(toolCallId);
|
|
2193
|
+
const record = {
|
|
2194
|
+
toolCallId,
|
|
2195
|
+
title: typeof u.title === "string" ? u.title : prev?.title ?? toolCallId,
|
|
2196
|
+
kind: typeof u.kind === "string" ? u.kind : prev?.kind,
|
|
2197
|
+
status: typeof u.status === "string" ? u.status : prev?.status ?? (isNew ? "pending" : "in_progress"),
|
|
2198
|
+
rawInput: isRecord(u.rawInput) ? u.rawInput : prev?.rawInput,
|
|
2199
|
+
rawOutput: isRecord(u.rawOutput) ? u.rawOutput : prev?.rawOutput
|
|
2200
|
+
};
|
|
2201
|
+
scratch.toolCalls.set(toolCallId, record);
|
|
2202
|
+
if (Array.isArray(u.content)) {
|
|
2203
|
+
for (const c of u.content) {
|
|
2204
|
+
if (c && typeof c === "object" && c.type === "diff") {
|
|
2205
|
+
const diff = {
|
|
2206
|
+
path: c.path,
|
|
2207
|
+
oldText: c.oldText,
|
|
2208
|
+
newText: c.newText
|
|
2209
|
+
};
|
|
2210
|
+
scratch.diffs.push(diff);
|
|
2211
|
+
emitProgress({ type: "diff", diff });
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
emitProgress({
|
|
2216
|
+
type: isNew ? "tool_call" : "tool_call_update",
|
|
2217
|
+
toolCall: record
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
// src/client/acp-session-callbacks.ts
|
|
2222
|
+
async function handleAcpPermissionRequest(msg, permissionPolicy, sender) {
|
|
2223
|
+
const id = msg.id;
|
|
2224
|
+
if (id === void 0) return;
|
|
2225
|
+
const params = msg.params;
|
|
2226
|
+
const toolCall = params?.toolCall;
|
|
2227
|
+
const options = Array.isArray(params?.options) ? params.options : [];
|
|
2228
|
+
if (!toolCall) {
|
|
2229
|
+
await sender.sendErrorResponse(id, -32602, "toolCall is required");
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
const policyAbort = new AbortController();
|
|
2233
|
+
try {
|
|
2234
|
+
const outcome = await permissionPolicy({
|
|
2235
|
+
toolCall,
|
|
2236
|
+
options,
|
|
2237
|
+
signal: policyAbort.signal
|
|
2238
|
+
});
|
|
2239
|
+
await sender.sendResult(id, { outcome });
|
|
2240
|
+
} catch (err) {
|
|
2241
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2242
|
+
await sender.sendErrorResponse(id, -32603, `permission policy failed: ${message}`);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender) {
|
|
2246
|
+
const id = msg.id;
|
|
2247
|
+
if (id === void 0) return;
|
|
2248
|
+
const params = msg.params;
|
|
2249
|
+
if (!params?.path) {
|
|
2250
|
+
await sender.sendErrorResponse(id, -32602, "path is required");
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
if (msg.method === "fs/write_text_file") {
|
|
2254
|
+
const allowed = await authorizeAcpCallback(permissionPolicy, {
|
|
2255
|
+
toolCallId: `acp-fs-write-${id}`,
|
|
2256
|
+
title: `Write file: ${params.path}`,
|
|
2257
|
+
kind: "edit",
|
|
2258
|
+
rawInput: { path: params.path, sessionId: params.sessionId }
|
|
2259
|
+
});
|
|
2260
|
+
if (!allowed) {
|
|
2261
|
+
await sender.sendErrorResponse(id, -32602, "filesystem write denied by permission policy");
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
try {
|
|
2266
|
+
if (msg.method === "fs/read_text_file") {
|
|
2267
|
+
const result = await fileServer.readTextFile({
|
|
2268
|
+
sessionId: params.sessionId ?? "",
|
|
2269
|
+
path: params.path
|
|
2270
|
+
});
|
|
2271
|
+
await sender.sendResult(id, result);
|
|
2272
|
+
} else {
|
|
2273
|
+
await fileServer.writeTextFile({
|
|
2274
|
+
sessionId: params.sessionId ?? "",
|
|
2275
|
+
path: params.path,
|
|
2276
|
+
content: params.content ?? ""
|
|
2277
|
+
});
|
|
2278
|
+
await sender.sendResult(id, {});
|
|
2279
|
+
}
|
|
2280
|
+
} catch (err) {
|
|
2281
|
+
const code = err instanceof FsError ? -32602 : -32603;
|
|
2282
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2283
|
+
await sender.sendErrorResponse(id, code, message);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
async function handleAcpTerminalRequest(msg, terminalServer, permissionPolicy, sender) {
|
|
2287
|
+
const id = msg.id;
|
|
2288
|
+
if (id === void 0) return;
|
|
2289
|
+
const params = msg.params ?? {};
|
|
2290
|
+
try {
|
|
2291
|
+
switch (msg.method) {
|
|
2292
|
+
case "terminal/create": {
|
|
2293
|
+
const allowed = await authorizeAcpCallback(permissionPolicy, {
|
|
2294
|
+
toolCallId: `acp-terminal-create-${id}`,
|
|
2295
|
+
title: `Run command: ${String(params.command ?? "")} ${(Array.isArray(params.args) ? params.args : []).join(" ")}`.trim(),
|
|
2296
|
+
kind: "execute",
|
|
2297
|
+
rawInput: {
|
|
2298
|
+
command: params.command,
|
|
2299
|
+
args: params.args,
|
|
2300
|
+
cwd: params.cwd,
|
|
2301
|
+
sessionId: params.sessionId
|
|
2302
|
+
}
|
|
2303
|
+
});
|
|
2304
|
+
if (!allowed) {
|
|
2305
|
+
await sender.sendErrorResponse(id, -32602, "terminal create denied by permission policy");
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
const createOpts = {
|
|
2309
|
+
sessionId: String(params.sessionId ?? ""),
|
|
2310
|
+
command: String(params.command ?? ""),
|
|
2311
|
+
args: Array.isArray(params.args) ? params.args : []
|
|
2312
|
+
};
|
|
2313
|
+
if (Array.isArray(params.env)) {
|
|
2314
|
+
createOpts.env = params.env;
|
|
2315
|
+
}
|
|
2316
|
+
if (typeof params.cwd === "string") {
|
|
2317
|
+
createOpts.cwd = params.cwd;
|
|
2318
|
+
}
|
|
2319
|
+
if (typeof params.outputByteLimit === "number") {
|
|
2320
|
+
createOpts.outputByteLimit = params.outputByteLimit;
|
|
2321
|
+
}
|
|
2322
|
+
const result = terminalServer.create(createOpts);
|
|
2323
|
+
await sender.sendResult(id, result);
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
case "terminal/output": {
|
|
2327
|
+
const terminalId = String(params.terminalId ?? "");
|
|
2328
|
+
const out = terminalServer.output(terminalId);
|
|
2329
|
+
await sender.sendResult(id, out);
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
2332
|
+
case "terminal/wait_for_exit": {
|
|
2333
|
+
const terminalId = String(params.terminalId ?? "");
|
|
2334
|
+
const exit = await terminalServer.waitForExit(terminalId);
|
|
2335
|
+
await sender.sendResult(id, exit);
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
case "terminal/kill": {
|
|
2339
|
+
const terminalId = String(params.terminalId ?? "");
|
|
2340
|
+
terminalServer.kill(terminalId);
|
|
2341
|
+
await sender.sendResult(id, {});
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
case "terminal/release": {
|
|
2345
|
+
const terminalId = String(params.terminalId ?? "");
|
|
2346
|
+
terminalServer.release(terminalId);
|
|
2347
|
+
await sender.sendResult(id, {});
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
default:
|
|
2351
|
+
await sender.sendErrorResponse(id, -32601, `unknown method: ${msg.method}`);
|
|
2352
|
+
}
|
|
2353
|
+
} catch (err) {
|
|
2354
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2355
|
+
await sender.sendErrorResponse(id, -32603, message);
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
async function authorizeAcpCallback(permissionPolicy, partial) {
|
|
2359
|
+
try {
|
|
2360
|
+
const outcome = await permissionPolicy({
|
|
2361
|
+
toolCall: {
|
|
2362
|
+
sessionUpdate: "tool_call_update",
|
|
2363
|
+
toolCallId: partial.toolCallId,
|
|
2364
|
+
title: partial.title,
|
|
2365
|
+
kind: partial.kind,
|
|
2366
|
+
status: "pending",
|
|
2367
|
+
...partial.rawInput ? { rawInput: partial.rawInput } : {}
|
|
2368
|
+
},
|
|
2369
|
+
options: [
|
|
2370
|
+
{ optionId: "allow", name: "Allow", kind: "allow_once" },
|
|
2371
|
+
{ optionId: "reject", name: "Reject", kind: "reject_once" }
|
|
2372
|
+
],
|
|
2373
|
+
signal: new AbortController().signal
|
|
2374
|
+
});
|
|
2375
|
+
return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always";
|
|
2376
|
+
} catch {
|
|
2377
|
+
return false;
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
// src/client/acp-message-routing.ts
|
|
2382
|
+
function isBestEffortAckMethod(method) {
|
|
2383
|
+
return method === "mcp/connect" || method === "mcp/message" || method === "mcp/disconnect" || method === "elicitation/create" || method === "elicitation/complete";
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
// src/client/acp-session.ts
|
|
2093
2387
|
var ACPSession = class _ACPSession {
|
|
2094
2388
|
transport;
|
|
2095
2389
|
fileServer;
|
|
@@ -2783,6 +3077,12 @@ var ACPSession = class _ACPSession {
|
|
|
2783
3077
|
error: { code, message }
|
|
2784
3078
|
});
|
|
2785
3079
|
}
|
|
3080
|
+
responseSender() {
|
|
3081
|
+
return {
|
|
3082
|
+
sendResult: (id, result) => this.sendResult(id, result),
|
|
3083
|
+
sendErrorResponse: (id, code, message) => this.sendErrorResponse(id, code, message)
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
2786
3086
|
handleMessage(msg) {
|
|
2787
3087
|
if (msg.id !== void 0 && (msg.result !== void 0 || msg.error !== void 0)) {
|
|
2788
3088
|
const pending = this.pending.get(msg.id);
|
|
@@ -2797,29 +3097,32 @@ var ACPSession = class _ACPSession {
|
|
|
2797
3097
|
return;
|
|
2798
3098
|
}
|
|
2799
3099
|
if (msg.method === "session/update") {
|
|
2800
|
-
this.
|
|
3100
|
+
handleAcpSessionUpdate(msg, this.scratch, (event) => this.emitProgress(event));
|
|
2801
3101
|
return;
|
|
2802
3102
|
}
|
|
2803
3103
|
if (msg.method === "session/request_permission") {
|
|
2804
|
-
void this.
|
|
3104
|
+
void handleAcpPermissionRequest(msg, this.permissionPolicy, this.responseSender());
|
|
2805
3105
|
return;
|
|
2806
3106
|
}
|
|
2807
3107
|
if (msg.method === "fs/read_text_file" || msg.method === "fs/write_text_file") {
|
|
2808
|
-
void
|
|
3108
|
+
void handleAcpFsRequest(
|
|
3109
|
+
msg,
|
|
3110
|
+
this.fileServer,
|
|
3111
|
+
this.permissionPolicy,
|
|
3112
|
+
this.responseSender()
|
|
3113
|
+
);
|
|
2809
3114
|
return;
|
|
2810
3115
|
}
|
|
2811
3116
|
if (msg.method?.startsWith("terminal/")) {
|
|
2812
|
-
void
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
});
|
|
2819
|
-
}
|
|
3117
|
+
void handleAcpTerminalRequest(
|
|
3118
|
+
msg,
|
|
3119
|
+
this.terminalServer,
|
|
3120
|
+
this.permissionPolicy,
|
|
3121
|
+
this.responseSender()
|
|
3122
|
+
);
|
|
2820
3123
|
return;
|
|
2821
3124
|
}
|
|
2822
|
-
if (msg.method
|
|
3125
|
+
if (isBestEffortAckMethod(msg.method)) {
|
|
2823
3126
|
if (msg.id !== void 0) {
|
|
2824
3127
|
this.sendResult(msg.id, {}).catch(() => {
|
|
2825
3128
|
});
|
|
@@ -2840,98 +3143,6 @@ var ACPSession = class _ACPSession {
|
|
|
2840
3143
|
);
|
|
2841
3144
|
}
|
|
2842
3145
|
}
|
|
2843
|
-
handleUpdate(msg) {
|
|
2844
|
-
const update = msg.params?.update;
|
|
2845
|
-
if (typeof update !== "object" || update === null) return;
|
|
2846
|
-
const u = update;
|
|
2847
|
-
this.emitProgress({ type: "raw", update: u });
|
|
2848
|
-
switch (u.sessionUpdate) {
|
|
2849
|
-
case "agent_message_chunk": {
|
|
2850
|
-
const text = extractText(u.content);
|
|
2851
|
-
if (text) {
|
|
2852
|
-
this.scratch.text += text;
|
|
2853
|
-
this.emitProgress({ type: "message", text });
|
|
2854
|
-
}
|
|
2855
|
-
return;
|
|
2856
|
-
}
|
|
2857
|
-
case "thought_chunk": {
|
|
2858
|
-
const text = extractText(u.content);
|
|
2859
|
-
if (text) {
|
|
2860
|
-
this.scratch.thoughts += text;
|
|
2861
|
-
this.emitProgress({ type: "thought", text });
|
|
2862
|
-
}
|
|
2863
|
-
return;
|
|
2864
|
-
}
|
|
2865
|
-
case "tool_call":
|
|
2866
|
-
case "tool_call_update": {
|
|
2867
|
-
this.captureToolCall(u, u.sessionUpdate === "tool_call");
|
|
2868
|
-
return;
|
|
2869
|
-
}
|
|
2870
|
-
case "plan":
|
|
2871
|
-
if (Array.isArray(u.entries)) {
|
|
2872
|
-
this.scratch.plan = u.entries;
|
|
2873
|
-
this.emitProgress({ type: "plan", entries: u.entries });
|
|
2874
|
-
}
|
|
2875
|
-
return;
|
|
2876
|
-
case "usage_update":
|
|
2877
|
-
if (typeof u.used === "number" && typeof u.size === "number") {
|
|
2878
|
-
const usage = {
|
|
2879
|
-
used: u.used,
|
|
2880
|
-
size: u.size,
|
|
2881
|
-
...typeof u.cost === "object" && u.cost !== null ? { cost: u.cost } : {}
|
|
2882
|
-
};
|
|
2883
|
-
this.scratch.usage = usage;
|
|
2884
|
-
this.emitProgress({ type: "usage", usage });
|
|
2885
|
-
}
|
|
2886
|
-
return;
|
|
2887
|
-
case "available_commands_update":
|
|
2888
|
-
case "current_mode_update":
|
|
2889
|
-
case "config_option_update":
|
|
2890
|
-
case "session_info_update":
|
|
2891
|
-
case "user_message_chunk":
|
|
2892
|
-
case "next_edit_suggestions":
|
|
2893
|
-
case "elicitation":
|
|
2894
|
-
return;
|
|
2895
|
-
default:
|
|
2896
|
-
return;
|
|
2897
|
-
}
|
|
2898
|
-
}
|
|
2899
|
-
/**
|
|
2900
|
-
* Fold a `tool_call` / `tool_call_update` notification into the scratch
|
|
2901
|
-
* tool-call map (deduped by toolCallId), extract any `diff` content into
|
|
2902
|
-
* the diffs list, and emit live progress.
|
|
2903
|
-
*/
|
|
2904
|
-
captureToolCall(u, isNew) {
|
|
2905
|
-
const toolCallId = typeof u.toolCallId === "string" ? u.toolCallId : "";
|
|
2906
|
-
if (!toolCallId) return;
|
|
2907
|
-
const prev = this.scratch.toolCalls.get(toolCallId);
|
|
2908
|
-
const record = {
|
|
2909
|
-
toolCallId,
|
|
2910
|
-
title: typeof u.title === "string" ? u.title : prev?.title ?? toolCallId,
|
|
2911
|
-
kind: typeof u.kind === "string" ? u.kind : prev?.kind,
|
|
2912
|
-
status: typeof u.status === "string" ? u.status : prev?.status ?? (isNew ? "pending" : "in_progress"),
|
|
2913
|
-
rawInput: isRecord(u.rawInput) ? u.rawInput : prev?.rawInput,
|
|
2914
|
-
rawOutput: isRecord(u.rawOutput) ? u.rawOutput : prev?.rawOutput
|
|
2915
|
-
};
|
|
2916
|
-
this.scratch.toolCalls.set(toolCallId, record);
|
|
2917
|
-
if (Array.isArray(u.content)) {
|
|
2918
|
-
for (const c of u.content) {
|
|
2919
|
-
if (c && typeof c === "object" && c.type === "diff") {
|
|
2920
|
-
const diff = {
|
|
2921
|
-
path: c.path,
|
|
2922
|
-
oldText: c.oldText,
|
|
2923
|
-
newText: c.newText
|
|
2924
|
-
};
|
|
2925
|
-
this.scratch.diffs.push(diff);
|
|
2926
|
-
this.emitProgress({ type: "diff", diff });
|
|
2927
|
-
}
|
|
2928
|
-
}
|
|
2929
|
-
}
|
|
2930
|
-
this.emitProgress({
|
|
2931
|
-
type: isNew ? "tool_call" : "tool_call_update",
|
|
2932
|
-
toolCall: record
|
|
2933
|
-
});
|
|
2934
|
-
}
|
|
2935
3146
|
emitProgress(event) {
|
|
2936
3147
|
if (!this.progressHandler) return;
|
|
2937
3148
|
try {
|
|
@@ -2942,217 +3153,11 @@ var ACPSession = class _ACPSession {
|
|
|
2942
3153
|
/** Live progress handler installed for the duration of a `prompt()` turn. */
|
|
2943
3154
|
progressHandler = null;
|
|
2944
3155
|
// Per-prompt scratch state
|
|
2945
|
-
scratch =
|
|
3156
|
+
scratch = createSessionScratch();
|
|
2946
3157
|
resetScratch() {
|
|
2947
|
-
this.scratch =
|
|
2948
|
-
}
|
|
2949
|
-
async handlePermissionRequest(msg) {
|
|
2950
|
-
const id = msg.id;
|
|
2951
|
-
if (id === void 0) return;
|
|
2952
|
-
const params = msg.params;
|
|
2953
|
-
const toolCall = params?.toolCall;
|
|
2954
|
-
const options = Array.isArray(params?.options) ? params.options : [];
|
|
2955
|
-
if (!toolCall) {
|
|
2956
|
-
await this.sendErrorResponse(id, -32602, "toolCall is required");
|
|
2957
|
-
return;
|
|
2958
|
-
}
|
|
2959
|
-
const policyAbort = new AbortController();
|
|
2960
|
-
try {
|
|
2961
|
-
const outcome = await this.permissionPolicy({
|
|
2962
|
-
toolCall,
|
|
2963
|
-
options,
|
|
2964
|
-
signal: policyAbort.signal
|
|
2965
|
-
});
|
|
2966
|
-
await this.sendResult(id, { outcome });
|
|
2967
|
-
} catch (err) {
|
|
2968
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2969
|
-
await this.sendErrorResponse(id, -32603, `permission policy failed: ${message}`);
|
|
2970
|
-
}
|
|
2971
|
-
}
|
|
2972
|
-
/**
|
|
2973
|
-
* Enforce authorization at privileged callback sinks (fs/write,
|
|
2974
|
-
* terminal/create). Unlike `handlePermissionRequest` which responds to
|
|
2975
|
-
* agent-initiated `session/request_permission` messages, this method is
|
|
2976
|
-
* called by the handler BEFORE dispatching to FileServer/TerminalServer,
|
|
2977
|
-
* closing the gap where the agent simply skips the voluntary permission
|
|
2978
|
-
* request and sends the privileged callback directly.
|
|
2979
|
-
*
|
|
2980
|
-
* Uses the session's permission policy. The default
|
|
2981
|
-
* (`readOnlyPermissionPolicy`) auto-approves only side-effect-free tool
|
|
2982
|
-
* calls (read/search/fetch/think) and rejects everything else — this is
|
|
2983
|
-
* the safe-by-default posture. For trusted local agents (CLI `acp spawn`,
|
|
2984
|
-
* Director fan-out), inject `defaultPermissionPolicy` to grant
|
|
2985
|
-
* write/execute access.
|
|
2986
|
-
*
|
|
2987
|
-
* Returns true if the callback is authorized, false if denied.
|
|
2988
|
-
*/
|
|
2989
|
-
async authorizeCallback(partial) {
|
|
2990
|
-
try {
|
|
2991
|
-
const outcome = await this.permissionPolicy({
|
|
2992
|
-
toolCall: {
|
|
2993
|
-
sessionUpdate: "tool_call_update",
|
|
2994
|
-
toolCallId: partial.toolCallId,
|
|
2995
|
-
title: partial.title,
|
|
2996
|
-
kind: partial.kind,
|
|
2997
|
-
status: "pending",
|
|
2998
|
-
...partial.rawInput ? { rawInput: partial.rawInput } : {}
|
|
2999
|
-
},
|
|
3000
|
-
options: [
|
|
3001
|
-
{ optionId: "allow", name: "Allow", kind: "allow_once" },
|
|
3002
|
-
{ optionId: "reject", name: "Reject", kind: "reject_once" }
|
|
3003
|
-
],
|
|
3004
|
-
signal: new AbortController().signal
|
|
3005
|
-
});
|
|
3006
|
-
return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always";
|
|
3007
|
-
} catch {
|
|
3008
|
-
return false;
|
|
3009
|
-
}
|
|
3010
|
-
}
|
|
3011
|
-
async handleFsRequest(msg) {
|
|
3012
|
-
const id = msg.id;
|
|
3013
|
-
if (id === void 0) return;
|
|
3014
|
-
const params = msg.params;
|
|
3015
|
-
if (!params?.path) {
|
|
3016
|
-
await this.sendErrorResponse(id, -32602, "path is required");
|
|
3017
|
-
return;
|
|
3018
|
-
}
|
|
3019
|
-
if (msg.method === "fs/write_text_file") {
|
|
3020
|
-
const allowed = await this.authorizeCallback({
|
|
3021
|
-
toolCallId: `acp-fs-write-${id}`,
|
|
3022
|
-
title: `Write file: ${params.path}`,
|
|
3023
|
-
kind: "edit",
|
|
3024
|
-
rawInput: { path: params.path, sessionId: params.sessionId }
|
|
3025
|
-
});
|
|
3026
|
-
if (!allowed) {
|
|
3027
|
-
await this.sendErrorResponse(id, -32602, "filesystem write denied by permission policy");
|
|
3028
|
-
return;
|
|
3029
|
-
}
|
|
3030
|
-
}
|
|
3031
|
-
try {
|
|
3032
|
-
if (msg.method === "fs/read_text_file") {
|
|
3033
|
-
const result = await this.fileServer.readTextFile({
|
|
3034
|
-
sessionId: params.sessionId ?? "",
|
|
3035
|
-
path: params.path
|
|
3036
|
-
});
|
|
3037
|
-
await this.sendResult(id, result);
|
|
3038
|
-
} else {
|
|
3039
|
-
await this.fileServer.writeTextFile({
|
|
3040
|
-
sessionId: params.sessionId ?? "",
|
|
3041
|
-
path: params.path,
|
|
3042
|
-
content: params.content ?? ""
|
|
3043
|
-
});
|
|
3044
|
-
await this.sendResult(id, {});
|
|
3045
|
-
}
|
|
3046
|
-
} catch (err) {
|
|
3047
|
-
const code = err instanceof FsError ? -32602 : -32603;
|
|
3048
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3049
|
-
await this.sendErrorResponse(id, code, message);
|
|
3050
|
-
}
|
|
3051
|
-
}
|
|
3052
|
-
async handleTerminalRequest(msg) {
|
|
3053
|
-
const id = msg.id;
|
|
3054
|
-
if (id === void 0) return;
|
|
3055
|
-
const params = msg.params ?? {};
|
|
3056
|
-
try {
|
|
3057
|
-
switch (msg.method) {
|
|
3058
|
-
case "terminal/create": {
|
|
3059
|
-
const allowed = await this.authorizeCallback({
|
|
3060
|
-
toolCallId: `acp-terminal-create-${id}`,
|
|
3061
|
-
title: `Run command: ${String(params.command ?? "")} ${(Array.isArray(params.args) ? params.args : []).join(" ")}`.trim(),
|
|
3062
|
-
kind: "execute",
|
|
3063
|
-
rawInput: {
|
|
3064
|
-
command: params.command,
|
|
3065
|
-
args: params.args,
|
|
3066
|
-
cwd: params.cwd,
|
|
3067
|
-
sessionId: params.sessionId
|
|
3068
|
-
}
|
|
3069
|
-
});
|
|
3070
|
-
if (!allowed) {
|
|
3071
|
-
await this.sendErrorResponse(id, -32602, "terminal create denied by permission policy");
|
|
3072
|
-
return;
|
|
3073
|
-
}
|
|
3074
|
-
const createOpts = {
|
|
3075
|
-
sessionId: String(params.sessionId ?? ""),
|
|
3076
|
-
command: String(params.command ?? ""),
|
|
3077
|
-
args: Array.isArray(params.args) ? params.args : []
|
|
3078
|
-
};
|
|
3079
|
-
if (Array.isArray(params.env)) {
|
|
3080
|
-
createOpts.env = params.env;
|
|
3081
|
-
}
|
|
3082
|
-
if (typeof params.cwd === "string") {
|
|
3083
|
-
createOpts.cwd = params.cwd;
|
|
3084
|
-
}
|
|
3085
|
-
if (typeof params.outputByteLimit === "number") {
|
|
3086
|
-
createOpts.outputByteLimit = params.outputByteLimit;
|
|
3087
|
-
}
|
|
3088
|
-
const result = this.terminalServer.create(createOpts);
|
|
3089
|
-
await this.sendResult(id, result);
|
|
3090
|
-
return;
|
|
3091
|
-
}
|
|
3092
|
-
case "terminal/output": {
|
|
3093
|
-
const terminalId = String(params.terminalId ?? "");
|
|
3094
|
-
const out = this.terminalServer.output(terminalId);
|
|
3095
|
-
await this.sendResult(id, out);
|
|
3096
|
-
return;
|
|
3097
|
-
}
|
|
3098
|
-
case "terminal/wait_for_exit": {
|
|
3099
|
-
const terminalId = String(params.terminalId ?? "");
|
|
3100
|
-
const exit = await this.terminalServer.waitForExit(terminalId);
|
|
3101
|
-
await this.sendResult(id, exit);
|
|
3102
|
-
return;
|
|
3103
|
-
}
|
|
3104
|
-
case "terminal/kill": {
|
|
3105
|
-
const terminalId = String(params.terminalId ?? "");
|
|
3106
|
-
this.terminalServer.kill(terminalId);
|
|
3107
|
-
await this.sendResult(id, {});
|
|
3108
|
-
return;
|
|
3109
|
-
}
|
|
3110
|
-
case "terminal/release": {
|
|
3111
|
-
const terminalId = String(params.terminalId ?? "");
|
|
3112
|
-
this.terminalServer.release(terminalId);
|
|
3113
|
-
await this.sendResult(id, {});
|
|
3114
|
-
return;
|
|
3115
|
-
}
|
|
3116
|
-
default:
|
|
3117
|
-
await this.sendErrorResponse(id, -32601, `unknown method: ${msg.method}`);
|
|
3118
|
-
}
|
|
3119
|
-
} catch (err) {
|
|
3120
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
3121
|
-
await this.sendErrorResponse(id, -32603, message);
|
|
3122
|
-
}
|
|
3158
|
+
this.scratch = createSessionScratch();
|
|
3123
3159
|
}
|
|
3124
3160
|
};
|
|
3125
|
-
function textContent(text) {
|
|
3126
|
-
return { type: "text", text };
|
|
3127
|
-
}
|
|
3128
|
-
function imageContent(mimeType, data) {
|
|
3129
|
-
return { type: "image", mimeType, data };
|
|
3130
|
-
}
|
|
3131
|
-
function audioContent(mimeType, data) {
|
|
3132
|
-
return { type: "audio", mimeType, data };
|
|
3133
|
-
}
|
|
3134
|
-
function extractText(block) {
|
|
3135
|
-
if (typeof block !== "object" || block === null) return "";
|
|
3136
|
-
const b = block;
|
|
3137
|
-
if (b.type === "text" && typeof b.text === "string") return b.text;
|
|
3138
|
-
if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
|
|
3139
|
-
return b.resource.text;
|
|
3140
|
-
}
|
|
3141
|
-
return "";
|
|
3142
|
-
}
|
|
3143
|
-
function isRecord(v) {
|
|
3144
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3145
|
-
}
|
|
3146
|
-
function emptyRunResult(stopReason) {
|
|
3147
|
-
return {
|
|
3148
|
-
text: "",
|
|
3149
|
-
stopReason,
|
|
3150
|
-
hasText: false,
|
|
3151
|
-
toolCalls: [],
|
|
3152
|
-
diffs: [],
|
|
3153
|
-
thoughts: ""
|
|
3154
|
-
};
|
|
3155
|
-
}
|
|
3156
3161
|
|
|
3157
3162
|
// src/client/tool-translator.ts
|
|
3158
3163
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|