@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/client.js
CHANGED
|
@@ -117,6 +117,9 @@ var ClientTransport = class {
|
|
|
117
117
|
};
|
|
118
118
|
const waitForMarker = (chunk) => {
|
|
119
119
|
this.buffer += chunk;
|
|
120
|
+
if (this.buffer.length > this.maxFrameChars) {
|
|
121
|
+
this.buffer = this.buffer.slice(-this.maxFrameChars);
|
|
122
|
+
}
|
|
120
123
|
const idx = this.buffer.indexOf("[wstack-acp]\n");
|
|
121
124
|
if (idx !== -1) {
|
|
122
125
|
this.buffer = this.buffer.slice(idx + "[wstack-acp]\n".length);
|
|
@@ -233,6 +236,39 @@ function verbatimOptions(invocation) {
|
|
|
233
236
|
// src/types/acp-v1.ts
|
|
234
237
|
var ACP_PROTOCOL_VERSION = 1;
|
|
235
238
|
|
|
239
|
+
// src/client/acp-session-content.ts
|
|
240
|
+
function textContent(text) {
|
|
241
|
+
return { type: "text", text };
|
|
242
|
+
}
|
|
243
|
+
function imageContent(mimeType, data) {
|
|
244
|
+
return { type: "image", mimeType, data };
|
|
245
|
+
}
|
|
246
|
+
function audioContent(mimeType, data) {
|
|
247
|
+
return { type: "audio", mimeType, data };
|
|
248
|
+
}
|
|
249
|
+
function extractText(block) {
|
|
250
|
+
if (typeof block !== "object" || block === null) return "";
|
|
251
|
+
const b = block;
|
|
252
|
+
if (b.type === "text" && typeof b.text === "string") return b.text;
|
|
253
|
+
if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
|
|
254
|
+
return b.resource.text;
|
|
255
|
+
}
|
|
256
|
+
return "";
|
|
257
|
+
}
|
|
258
|
+
function isRecord(v) {
|
|
259
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
260
|
+
}
|
|
261
|
+
function emptyRunResult(stopReason) {
|
|
262
|
+
return {
|
|
263
|
+
text: "",
|
|
264
|
+
stopReason,
|
|
265
|
+
hasText: false,
|
|
266
|
+
toolCalls: [],
|
|
267
|
+
diffs: [],
|
|
268
|
+
thoughts: ""
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
236
272
|
// src/client/file-server.ts
|
|
237
273
|
import { randomBytes } from "node:crypto";
|
|
238
274
|
import { realpathSync } from "node:fs";
|
|
@@ -908,7 +944,7 @@ function finitePositiveLimit(value, fallback) {
|
|
|
908
944
|
return value !== void 0 && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
909
945
|
}
|
|
910
946
|
|
|
911
|
-
// src/client/acp-session.ts
|
|
947
|
+
// src/client/acp-session-errors.ts
|
|
912
948
|
var ACPSessionError = class extends Error {
|
|
913
949
|
kind;
|
|
914
950
|
cause;
|
|
@@ -922,6 +958,264 @@ var ACPSessionError = class extends Error {
|
|
|
922
958
|
function isJsonRpcError(v) {
|
|
923
959
|
return typeof v === "object" && v !== null && typeof v.code === "number" && typeof v.message === "string";
|
|
924
960
|
}
|
|
961
|
+
|
|
962
|
+
// src/client/acp-session-updates.ts
|
|
963
|
+
function createSessionScratch() {
|
|
964
|
+
return { text: "", thoughts: "", toolCalls: /* @__PURE__ */ new Map(), diffs: [] };
|
|
965
|
+
}
|
|
966
|
+
function handleAcpSessionUpdate(msg, scratch, emitProgress) {
|
|
967
|
+
const update = msg.params?.update;
|
|
968
|
+
if (typeof update !== "object" || update === null) return;
|
|
969
|
+
const u = update;
|
|
970
|
+
emitProgress({ type: "raw", update: u });
|
|
971
|
+
switch (u.sessionUpdate) {
|
|
972
|
+
case "agent_message_chunk": {
|
|
973
|
+
const text = extractText(u.content);
|
|
974
|
+
if (text) {
|
|
975
|
+
scratch.text += text;
|
|
976
|
+
emitProgress({ type: "message", text });
|
|
977
|
+
}
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
case "thought_chunk": {
|
|
981
|
+
const text = extractText(u.content);
|
|
982
|
+
if (text) {
|
|
983
|
+
scratch.thoughts += text;
|
|
984
|
+
emitProgress({ type: "thought", text });
|
|
985
|
+
}
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
case "tool_call":
|
|
989
|
+
case "tool_call_update":
|
|
990
|
+
captureToolCall(u, u.sessionUpdate === "tool_call", scratch, emitProgress);
|
|
991
|
+
return;
|
|
992
|
+
case "plan":
|
|
993
|
+
if (Array.isArray(u.entries)) {
|
|
994
|
+
scratch.plan = u.entries;
|
|
995
|
+
emitProgress({ type: "plan", entries: u.entries });
|
|
996
|
+
}
|
|
997
|
+
return;
|
|
998
|
+
case "usage_update":
|
|
999
|
+
if (typeof u.used === "number" && typeof u.size === "number") {
|
|
1000
|
+
const usage = {
|
|
1001
|
+
used: u.used,
|
|
1002
|
+
size: u.size,
|
|
1003
|
+
...typeof u.cost === "object" && u.cost !== null ? { cost: u.cost } : {}
|
|
1004
|
+
};
|
|
1005
|
+
scratch.usage = usage;
|
|
1006
|
+
emitProgress({ type: "usage", usage });
|
|
1007
|
+
}
|
|
1008
|
+
return;
|
|
1009
|
+
case "available_commands_update":
|
|
1010
|
+
case "current_mode_update":
|
|
1011
|
+
case "config_option_update":
|
|
1012
|
+
case "session_info_update":
|
|
1013
|
+
case "user_message_chunk":
|
|
1014
|
+
case "next_edit_suggestions":
|
|
1015
|
+
case "elicitation":
|
|
1016
|
+
return;
|
|
1017
|
+
default:
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
function captureToolCall(u, isNew, scratch, emitProgress) {
|
|
1022
|
+
const toolCallId = typeof u.toolCallId === "string" ? u.toolCallId : "";
|
|
1023
|
+
if (!toolCallId) return;
|
|
1024
|
+
const prev = scratch.toolCalls.get(toolCallId);
|
|
1025
|
+
const record = {
|
|
1026
|
+
toolCallId,
|
|
1027
|
+
title: typeof u.title === "string" ? u.title : prev?.title ?? toolCallId,
|
|
1028
|
+
kind: typeof u.kind === "string" ? u.kind : prev?.kind,
|
|
1029
|
+
status: typeof u.status === "string" ? u.status : prev?.status ?? (isNew ? "pending" : "in_progress"),
|
|
1030
|
+
rawInput: isRecord(u.rawInput) ? u.rawInput : prev?.rawInput,
|
|
1031
|
+
rawOutput: isRecord(u.rawOutput) ? u.rawOutput : prev?.rawOutput
|
|
1032
|
+
};
|
|
1033
|
+
scratch.toolCalls.set(toolCallId, record);
|
|
1034
|
+
if (Array.isArray(u.content)) {
|
|
1035
|
+
for (const c of u.content) {
|
|
1036
|
+
if (c && typeof c === "object" && c.type === "diff") {
|
|
1037
|
+
const diff = {
|
|
1038
|
+
path: c.path,
|
|
1039
|
+
oldText: c.oldText,
|
|
1040
|
+
newText: c.newText
|
|
1041
|
+
};
|
|
1042
|
+
scratch.diffs.push(diff);
|
|
1043
|
+
emitProgress({ type: "diff", diff });
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
emitProgress({
|
|
1048
|
+
type: isNew ? "tool_call" : "tool_call_update",
|
|
1049
|
+
toolCall: record
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// src/client/acp-session-callbacks.ts
|
|
1054
|
+
async function handleAcpPermissionRequest(msg, permissionPolicy, sender) {
|
|
1055
|
+
const id = msg.id;
|
|
1056
|
+
if (id === void 0) return;
|
|
1057
|
+
const params = msg.params;
|
|
1058
|
+
const toolCall = params?.toolCall;
|
|
1059
|
+
const options = Array.isArray(params?.options) ? params.options : [];
|
|
1060
|
+
if (!toolCall) {
|
|
1061
|
+
await sender.sendErrorResponse(id, -32602, "toolCall is required");
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
const policyAbort = new AbortController();
|
|
1065
|
+
try {
|
|
1066
|
+
const outcome = await permissionPolicy({
|
|
1067
|
+
toolCall,
|
|
1068
|
+
options,
|
|
1069
|
+
signal: policyAbort.signal
|
|
1070
|
+
});
|
|
1071
|
+
await sender.sendResult(id, { outcome });
|
|
1072
|
+
} catch (err) {
|
|
1073
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1074
|
+
await sender.sendErrorResponse(id, -32603, `permission policy failed: ${message}`);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender) {
|
|
1078
|
+
const id = msg.id;
|
|
1079
|
+
if (id === void 0) return;
|
|
1080
|
+
const params = msg.params;
|
|
1081
|
+
if (!params?.path) {
|
|
1082
|
+
await sender.sendErrorResponse(id, -32602, "path is required");
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
if (msg.method === "fs/write_text_file") {
|
|
1086
|
+
const allowed = await authorizeAcpCallback(permissionPolicy, {
|
|
1087
|
+
toolCallId: `acp-fs-write-${id}`,
|
|
1088
|
+
title: `Write file: ${params.path}`,
|
|
1089
|
+
kind: "edit",
|
|
1090
|
+
rawInput: { path: params.path, sessionId: params.sessionId }
|
|
1091
|
+
});
|
|
1092
|
+
if (!allowed) {
|
|
1093
|
+
await sender.sendErrorResponse(id, -32602, "filesystem write denied by permission policy");
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
try {
|
|
1098
|
+
if (msg.method === "fs/read_text_file") {
|
|
1099
|
+
const result = await fileServer.readTextFile({
|
|
1100
|
+
sessionId: params.sessionId ?? "",
|
|
1101
|
+
path: params.path
|
|
1102
|
+
});
|
|
1103
|
+
await sender.sendResult(id, result);
|
|
1104
|
+
} else {
|
|
1105
|
+
await fileServer.writeTextFile({
|
|
1106
|
+
sessionId: params.sessionId ?? "",
|
|
1107
|
+
path: params.path,
|
|
1108
|
+
content: params.content ?? ""
|
|
1109
|
+
});
|
|
1110
|
+
await sender.sendResult(id, {});
|
|
1111
|
+
}
|
|
1112
|
+
} catch (err) {
|
|
1113
|
+
const code = err instanceof FsError ? -32602 : -32603;
|
|
1114
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1115
|
+
await sender.sendErrorResponse(id, code, message);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
async function handleAcpTerminalRequest(msg, terminalServer, permissionPolicy, sender) {
|
|
1119
|
+
const id = msg.id;
|
|
1120
|
+
if (id === void 0) return;
|
|
1121
|
+
const params = msg.params ?? {};
|
|
1122
|
+
try {
|
|
1123
|
+
switch (msg.method) {
|
|
1124
|
+
case "terminal/create": {
|
|
1125
|
+
const allowed = await authorizeAcpCallback(permissionPolicy, {
|
|
1126
|
+
toolCallId: `acp-terminal-create-${id}`,
|
|
1127
|
+
title: `Run command: ${String(params.command ?? "")} ${(Array.isArray(params.args) ? params.args : []).join(" ")}`.trim(),
|
|
1128
|
+
kind: "execute",
|
|
1129
|
+
rawInput: {
|
|
1130
|
+
command: params.command,
|
|
1131
|
+
args: params.args,
|
|
1132
|
+
cwd: params.cwd,
|
|
1133
|
+
sessionId: params.sessionId
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
if (!allowed) {
|
|
1137
|
+
await sender.sendErrorResponse(id, -32602, "terminal create denied by permission policy");
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
const createOpts = {
|
|
1141
|
+
sessionId: String(params.sessionId ?? ""),
|
|
1142
|
+
command: String(params.command ?? ""),
|
|
1143
|
+
args: Array.isArray(params.args) ? params.args : []
|
|
1144
|
+
};
|
|
1145
|
+
if (Array.isArray(params.env)) {
|
|
1146
|
+
createOpts.env = params.env;
|
|
1147
|
+
}
|
|
1148
|
+
if (typeof params.cwd === "string") {
|
|
1149
|
+
createOpts.cwd = params.cwd;
|
|
1150
|
+
}
|
|
1151
|
+
if (typeof params.outputByteLimit === "number") {
|
|
1152
|
+
createOpts.outputByteLimit = params.outputByteLimit;
|
|
1153
|
+
}
|
|
1154
|
+
const result = terminalServer.create(createOpts);
|
|
1155
|
+
await sender.sendResult(id, result);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
case "terminal/output": {
|
|
1159
|
+
const terminalId = String(params.terminalId ?? "");
|
|
1160
|
+
const out = terminalServer.output(terminalId);
|
|
1161
|
+
await sender.sendResult(id, out);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
case "terminal/wait_for_exit": {
|
|
1165
|
+
const terminalId = String(params.terminalId ?? "");
|
|
1166
|
+
const exit = await terminalServer.waitForExit(terminalId);
|
|
1167
|
+
await sender.sendResult(id, exit);
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
case "terminal/kill": {
|
|
1171
|
+
const terminalId = String(params.terminalId ?? "");
|
|
1172
|
+
terminalServer.kill(terminalId);
|
|
1173
|
+
await sender.sendResult(id, {});
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
case "terminal/release": {
|
|
1177
|
+
const terminalId = String(params.terminalId ?? "");
|
|
1178
|
+
terminalServer.release(terminalId);
|
|
1179
|
+
await sender.sendResult(id, {});
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
default:
|
|
1183
|
+
await sender.sendErrorResponse(id, -32601, `unknown method: ${msg.method}`);
|
|
1184
|
+
}
|
|
1185
|
+
} catch (err) {
|
|
1186
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1187
|
+
await sender.sendErrorResponse(id, -32603, message);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
async function authorizeAcpCallback(permissionPolicy, partial) {
|
|
1191
|
+
try {
|
|
1192
|
+
const outcome = await permissionPolicy({
|
|
1193
|
+
toolCall: {
|
|
1194
|
+
sessionUpdate: "tool_call_update",
|
|
1195
|
+
toolCallId: partial.toolCallId,
|
|
1196
|
+
title: partial.title,
|
|
1197
|
+
kind: partial.kind,
|
|
1198
|
+
status: "pending",
|
|
1199
|
+
...partial.rawInput ? { rawInput: partial.rawInput } : {}
|
|
1200
|
+
},
|
|
1201
|
+
options: [
|
|
1202
|
+
{ optionId: "allow", name: "Allow", kind: "allow_once" },
|
|
1203
|
+
{ optionId: "reject", name: "Reject", kind: "reject_once" }
|
|
1204
|
+
],
|
|
1205
|
+
signal: new AbortController().signal
|
|
1206
|
+
});
|
|
1207
|
+
return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always";
|
|
1208
|
+
} catch {
|
|
1209
|
+
return false;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// src/client/acp-message-routing.ts
|
|
1214
|
+
function isBestEffortAckMethod(method) {
|
|
1215
|
+
return method === "mcp/connect" || method === "mcp/message" || method === "mcp/disconnect" || method === "elicitation/create" || method === "elicitation/complete";
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// src/client/acp-session.ts
|
|
925
1219
|
var ACPSession = class _ACPSession {
|
|
926
1220
|
transport;
|
|
927
1221
|
fileServer;
|
|
@@ -1615,6 +1909,12 @@ var ACPSession = class _ACPSession {
|
|
|
1615
1909
|
error: { code, message }
|
|
1616
1910
|
});
|
|
1617
1911
|
}
|
|
1912
|
+
responseSender() {
|
|
1913
|
+
return {
|
|
1914
|
+
sendResult: (id, result) => this.sendResult(id, result),
|
|
1915
|
+
sendErrorResponse: (id, code, message) => this.sendErrorResponse(id, code, message)
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1618
1918
|
handleMessage(msg) {
|
|
1619
1919
|
if (msg.id !== void 0 && (msg.result !== void 0 || msg.error !== void 0)) {
|
|
1620
1920
|
const pending = this.pending.get(msg.id);
|
|
@@ -1629,29 +1929,32 @@ var ACPSession = class _ACPSession {
|
|
|
1629
1929
|
return;
|
|
1630
1930
|
}
|
|
1631
1931
|
if (msg.method === "session/update") {
|
|
1632
|
-
this.
|
|
1932
|
+
handleAcpSessionUpdate(msg, this.scratch, (event) => this.emitProgress(event));
|
|
1633
1933
|
return;
|
|
1634
1934
|
}
|
|
1635
1935
|
if (msg.method === "session/request_permission") {
|
|
1636
|
-
void this.
|
|
1936
|
+
void handleAcpPermissionRequest(msg, this.permissionPolicy, this.responseSender());
|
|
1637
1937
|
return;
|
|
1638
1938
|
}
|
|
1639
1939
|
if (msg.method === "fs/read_text_file" || msg.method === "fs/write_text_file") {
|
|
1640
|
-
void
|
|
1940
|
+
void handleAcpFsRequest(
|
|
1941
|
+
msg,
|
|
1942
|
+
this.fileServer,
|
|
1943
|
+
this.permissionPolicy,
|
|
1944
|
+
this.responseSender()
|
|
1945
|
+
);
|
|
1641
1946
|
return;
|
|
1642
1947
|
}
|
|
1643
1948
|
if (msg.method?.startsWith("terminal/")) {
|
|
1644
|
-
void
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
});
|
|
1651
|
-
}
|
|
1949
|
+
void handleAcpTerminalRequest(
|
|
1950
|
+
msg,
|
|
1951
|
+
this.terminalServer,
|
|
1952
|
+
this.permissionPolicy,
|
|
1953
|
+
this.responseSender()
|
|
1954
|
+
);
|
|
1652
1955
|
return;
|
|
1653
1956
|
}
|
|
1654
|
-
if (msg.method
|
|
1957
|
+
if (isBestEffortAckMethod(msg.method)) {
|
|
1655
1958
|
if (msg.id !== void 0) {
|
|
1656
1959
|
this.sendResult(msg.id, {}).catch(() => {
|
|
1657
1960
|
});
|
|
@@ -1672,98 +1975,6 @@ var ACPSession = class _ACPSession {
|
|
|
1672
1975
|
);
|
|
1673
1976
|
}
|
|
1674
1977
|
}
|
|
1675
|
-
handleUpdate(msg) {
|
|
1676
|
-
const update = msg.params?.update;
|
|
1677
|
-
if (typeof update !== "object" || update === null) return;
|
|
1678
|
-
const u = update;
|
|
1679
|
-
this.emitProgress({ type: "raw", update: u });
|
|
1680
|
-
switch (u.sessionUpdate) {
|
|
1681
|
-
case "agent_message_chunk": {
|
|
1682
|
-
const text = extractText(u.content);
|
|
1683
|
-
if (text) {
|
|
1684
|
-
this.scratch.text += text;
|
|
1685
|
-
this.emitProgress({ type: "message", text });
|
|
1686
|
-
}
|
|
1687
|
-
return;
|
|
1688
|
-
}
|
|
1689
|
-
case "thought_chunk": {
|
|
1690
|
-
const text = extractText(u.content);
|
|
1691
|
-
if (text) {
|
|
1692
|
-
this.scratch.thoughts += text;
|
|
1693
|
-
this.emitProgress({ type: "thought", text });
|
|
1694
|
-
}
|
|
1695
|
-
return;
|
|
1696
|
-
}
|
|
1697
|
-
case "tool_call":
|
|
1698
|
-
case "tool_call_update": {
|
|
1699
|
-
this.captureToolCall(u, u.sessionUpdate === "tool_call");
|
|
1700
|
-
return;
|
|
1701
|
-
}
|
|
1702
|
-
case "plan":
|
|
1703
|
-
if (Array.isArray(u.entries)) {
|
|
1704
|
-
this.scratch.plan = u.entries;
|
|
1705
|
-
this.emitProgress({ type: "plan", entries: u.entries });
|
|
1706
|
-
}
|
|
1707
|
-
return;
|
|
1708
|
-
case "usage_update":
|
|
1709
|
-
if (typeof u.used === "number" && typeof u.size === "number") {
|
|
1710
|
-
const usage = {
|
|
1711
|
-
used: u.used,
|
|
1712
|
-
size: u.size,
|
|
1713
|
-
...typeof u.cost === "object" && u.cost !== null ? { cost: u.cost } : {}
|
|
1714
|
-
};
|
|
1715
|
-
this.scratch.usage = usage;
|
|
1716
|
-
this.emitProgress({ type: "usage", usage });
|
|
1717
|
-
}
|
|
1718
|
-
return;
|
|
1719
|
-
case "available_commands_update":
|
|
1720
|
-
case "current_mode_update":
|
|
1721
|
-
case "config_option_update":
|
|
1722
|
-
case "session_info_update":
|
|
1723
|
-
case "user_message_chunk":
|
|
1724
|
-
case "next_edit_suggestions":
|
|
1725
|
-
case "elicitation":
|
|
1726
|
-
return;
|
|
1727
|
-
default:
|
|
1728
|
-
return;
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
/**
|
|
1732
|
-
* Fold a `tool_call` / `tool_call_update` notification into the scratch
|
|
1733
|
-
* tool-call map (deduped by toolCallId), extract any `diff` content into
|
|
1734
|
-
* the diffs list, and emit live progress.
|
|
1735
|
-
*/
|
|
1736
|
-
captureToolCall(u, isNew) {
|
|
1737
|
-
const toolCallId = typeof u.toolCallId === "string" ? u.toolCallId : "";
|
|
1738
|
-
if (!toolCallId) return;
|
|
1739
|
-
const prev = this.scratch.toolCalls.get(toolCallId);
|
|
1740
|
-
const record = {
|
|
1741
|
-
toolCallId,
|
|
1742
|
-
title: typeof u.title === "string" ? u.title : prev?.title ?? toolCallId,
|
|
1743
|
-
kind: typeof u.kind === "string" ? u.kind : prev?.kind,
|
|
1744
|
-
status: typeof u.status === "string" ? u.status : prev?.status ?? (isNew ? "pending" : "in_progress"),
|
|
1745
|
-
rawInput: isRecord(u.rawInput) ? u.rawInput : prev?.rawInput,
|
|
1746
|
-
rawOutput: isRecord(u.rawOutput) ? u.rawOutput : prev?.rawOutput
|
|
1747
|
-
};
|
|
1748
|
-
this.scratch.toolCalls.set(toolCallId, record);
|
|
1749
|
-
if (Array.isArray(u.content)) {
|
|
1750
|
-
for (const c of u.content) {
|
|
1751
|
-
if (c && typeof c === "object" && c.type === "diff") {
|
|
1752
|
-
const diff = {
|
|
1753
|
-
path: c.path,
|
|
1754
|
-
oldText: c.oldText,
|
|
1755
|
-
newText: c.newText
|
|
1756
|
-
};
|
|
1757
|
-
this.scratch.diffs.push(diff);
|
|
1758
|
-
this.emitProgress({ type: "diff", diff });
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
this.emitProgress({
|
|
1763
|
-
type: isNew ? "tool_call" : "tool_call_update",
|
|
1764
|
-
toolCall: record
|
|
1765
|
-
});
|
|
1766
|
-
}
|
|
1767
1978
|
emitProgress(event) {
|
|
1768
1979
|
if (!this.progressHandler) return;
|
|
1769
1980
|
try {
|
|
@@ -1774,217 +1985,11 @@ var ACPSession = class _ACPSession {
|
|
|
1774
1985
|
/** Live progress handler installed for the duration of a `prompt()` turn. */
|
|
1775
1986
|
progressHandler = null;
|
|
1776
1987
|
// Per-prompt scratch state
|
|
1777
|
-
scratch =
|
|
1988
|
+
scratch = createSessionScratch();
|
|
1778
1989
|
resetScratch() {
|
|
1779
|
-
this.scratch =
|
|
1780
|
-
}
|
|
1781
|
-
async handlePermissionRequest(msg) {
|
|
1782
|
-
const id = msg.id;
|
|
1783
|
-
if (id === void 0) return;
|
|
1784
|
-
const params = msg.params;
|
|
1785
|
-
const toolCall = params?.toolCall;
|
|
1786
|
-
const options = Array.isArray(params?.options) ? params.options : [];
|
|
1787
|
-
if (!toolCall) {
|
|
1788
|
-
await this.sendErrorResponse(id, -32602, "toolCall is required");
|
|
1789
|
-
return;
|
|
1790
|
-
}
|
|
1791
|
-
const policyAbort = new AbortController();
|
|
1792
|
-
try {
|
|
1793
|
-
const outcome = await this.permissionPolicy({
|
|
1794
|
-
toolCall,
|
|
1795
|
-
options,
|
|
1796
|
-
signal: policyAbort.signal
|
|
1797
|
-
});
|
|
1798
|
-
await this.sendResult(id, { outcome });
|
|
1799
|
-
} catch (err) {
|
|
1800
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1801
|
-
await this.sendErrorResponse(id, -32603, `permission policy failed: ${message}`);
|
|
1802
|
-
}
|
|
1803
|
-
}
|
|
1804
|
-
/**
|
|
1805
|
-
* Enforce authorization at privileged callback sinks (fs/write,
|
|
1806
|
-
* terminal/create). Unlike `handlePermissionRequest` which responds to
|
|
1807
|
-
* agent-initiated `session/request_permission` messages, this method is
|
|
1808
|
-
* called by the handler BEFORE dispatching to FileServer/TerminalServer,
|
|
1809
|
-
* closing the gap where the agent simply skips the voluntary permission
|
|
1810
|
-
* request and sends the privileged callback directly.
|
|
1811
|
-
*
|
|
1812
|
-
* Uses the session's permission policy. The default
|
|
1813
|
-
* (`readOnlyPermissionPolicy`) auto-approves only side-effect-free tool
|
|
1814
|
-
* calls (read/search/fetch/think) and rejects everything else — this is
|
|
1815
|
-
* the safe-by-default posture. For trusted local agents (CLI `acp spawn`,
|
|
1816
|
-
* Director fan-out), inject `defaultPermissionPolicy` to grant
|
|
1817
|
-
* write/execute access.
|
|
1818
|
-
*
|
|
1819
|
-
* Returns true if the callback is authorized, false if denied.
|
|
1820
|
-
*/
|
|
1821
|
-
async authorizeCallback(partial) {
|
|
1822
|
-
try {
|
|
1823
|
-
const outcome = await this.permissionPolicy({
|
|
1824
|
-
toolCall: {
|
|
1825
|
-
sessionUpdate: "tool_call_update",
|
|
1826
|
-
toolCallId: partial.toolCallId,
|
|
1827
|
-
title: partial.title,
|
|
1828
|
-
kind: partial.kind,
|
|
1829
|
-
status: "pending",
|
|
1830
|
-
...partial.rawInput ? { rawInput: partial.rawInput } : {}
|
|
1831
|
-
},
|
|
1832
|
-
options: [
|
|
1833
|
-
{ optionId: "allow", name: "Allow", kind: "allow_once" },
|
|
1834
|
-
{ optionId: "reject", name: "Reject", kind: "reject_once" }
|
|
1835
|
-
],
|
|
1836
|
-
signal: new AbortController().signal
|
|
1837
|
-
});
|
|
1838
|
-
return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always";
|
|
1839
|
-
} catch {
|
|
1840
|
-
return false;
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1843
|
-
async handleFsRequest(msg) {
|
|
1844
|
-
const id = msg.id;
|
|
1845
|
-
if (id === void 0) return;
|
|
1846
|
-
const params = msg.params;
|
|
1847
|
-
if (!params?.path) {
|
|
1848
|
-
await this.sendErrorResponse(id, -32602, "path is required");
|
|
1849
|
-
return;
|
|
1850
|
-
}
|
|
1851
|
-
if (msg.method === "fs/write_text_file") {
|
|
1852
|
-
const allowed = await this.authorizeCallback({
|
|
1853
|
-
toolCallId: `acp-fs-write-${id}`,
|
|
1854
|
-
title: `Write file: ${params.path}`,
|
|
1855
|
-
kind: "edit",
|
|
1856
|
-
rawInput: { path: params.path, sessionId: params.sessionId }
|
|
1857
|
-
});
|
|
1858
|
-
if (!allowed) {
|
|
1859
|
-
await this.sendErrorResponse(id, -32602, "filesystem write denied by permission policy");
|
|
1860
|
-
return;
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
try {
|
|
1864
|
-
if (msg.method === "fs/read_text_file") {
|
|
1865
|
-
const result = await this.fileServer.readTextFile({
|
|
1866
|
-
sessionId: params.sessionId ?? "",
|
|
1867
|
-
path: params.path
|
|
1868
|
-
});
|
|
1869
|
-
await this.sendResult(id, result);
|
|
1870
|
-
} else {
|
|
1871
|
-
await this.fileServer.writeTextFile({
|
|
1872
|
-
sessionId: params.sessionId ?? "",
|
|
1873
|
-
path: params.path,
|
|
1874
|
-
content: params.content ?? ""
|
|
1875
|
-
});
|
|
1876
|
-
await this.sendResult(id, {});
|
|
1877
|
-
}
|
|
1878
|
-
} catch (err) {
|
|
1879
|
-
const code = err instanceof FsError ? -32602 : -32603;
|
|
1880
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1881
|
-
await this.sendErrorResponse(id, code, message);
|
|
1882
|
-
}
|
|
1883
|
-
}
|
|
1884
|
-
async handleTerminalRequest(msg) {
|
|
1885
|
-
const id = msg.id;
|
|
1886
|
-
if (id === void 0) return;
|
|
1887
|
-
const params = msg.params ?? {};
|
|
1888
|
-
try {
|
|
1889
|
-
switch (msg.method) {
|
|
1890
|
-
case "terminal/create": {
|
|
1891
|
-
const allowed = await this.authorizeCallback({
|
|
1892
|
-
toolCallId: `acp-terminal-create-${id}`,
|
|
1893
|
-
title: `Run command: ${String(params.command ?? "")} ${(Array.isArray(params.args) ? params.args : []).join(" ")}`.trim(),
|
|
1894
|
-
kind: "execute",
|
|
1895
|
-
rawInput: {
|
|
1896
|
-
command: params.command,
|
|
1897
|
-
args: params.args,
|
|
1898
|
-
cwd: params.cwd,
|
|
1899
|
-
sessionId: params.sessionId
|
|
1900
|
-
}
|
|
1901
|
-
});
|
|
1902
|
-
if (!allowed) {
|
|
1903
|
-
await this.sendErrorResponse(id, -32602, "terminal create denied by permission policy");
|
|
1904
|
-
return;
|
|
1905
|
-
}
|
|
1906
|
-
const createOpts = {
|
|
1907
|
-
sessionId: String(params.sessionId ?? ""),
|
|
1908
|
-
command: String(params.command ?? ""),
|
|
1909
|
-
args: Array.isArray(params.args) ? params.args : []
|
|
1910
|
-
};
|
|
1911
|
-
if (Array.isArray(params.env)) {
|
|
1912
|
-
createOpts.env = params.env;
|
|
1913
|
-
}
|
|
1914
|
-
if (typeof params.cwd === "string") {
|
|
1915
|
-
createOpts.cwd = params.cwd;
|
|
1916
|
-
}
|
|
1917
|
-
if (typeof params.outputByteLimit === "number") {
|
|
1918
|
-
createOpts.outputByteLimit = params.outputByteLimit;
|
|
1919
|
-
}
|
|
1920
|
-
const result = this.terminalServer.create(createOpts);
|
|
1921
|
-
await this.sendResult(id, result);
|
|
1922
|
-
return;
|
|
1923
|
-
}
|
|
1924
|
-
case "terminal/output": {
|
|
1925
|
-
const terminalId = String(params.terminalId ?? "");
|
|
1926
|
-
const out = this.terminalServer.output(terminalId);
|
|
1927
|
-
await this.sendResult(id, out);
|
|
1928
|
-
return;
|
|
1929
|
-
}
|
|
1930
|
-
case "terminal/wait_for_exit": {
|
|
1931
|
-
const terminalId = String(params.terminalId ?? "");
|
|
1932
|
-
const exit = await this.terminalServer.waitForExit(terminalId);
|
|
1933
|
-
await this.sendResult(id, exit);
|
|
1934
|
-
return;
|
|
1935
|
-
}
|
|
1936
|
-
case "terminal/kill": {
|
|
1937
|
-
const terminalId = String(params.terminalId ?? "");
|
|
1938
|
-
this.terminalServer.kill(terminalId);
|
|
1939
|
-
await this.sendResult(id, {});
|
|
1940
|
-
return;
|
|
1941
|
-
}
|
|
1942
|
-
case "terminal/release": {
|
|
1943
|
-
const terminalId = String(params.terminalId ?? "");
|
|
1944
|
-
this.terminalServer.release(terminalId);
|
|
1945
|
-
await this.sendResult(id, {});
|
|
1946
|
-
return;
|
|
1947
|
-
}
|
|
1948
|
-
default:
|
|
1949
|
-
await this.sendErrorResponse(id, -32601, `unknown method: ${msg.method}`);
|
|
1950
|
-
}
|
|
1951
|
-
} catch (err) {
|
|
1952
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1953
|
-
await this.sendErrorResponse(id, -32603, message);
|
|
1954
|
-
}
|
|
1990
|
+
this.scratch = createSessionScratch();
|
|
1955
1991
|
}
|
|
1956
1992
|
};
|
|
1957
|
-
function textContent(text) {
|
|
1958
|
-
return { type: "text", text };
|
|
1959
|
-
}
|
|
1960
|
-
function imageContent(mimeType, data) {
|
|
1961
|
-
return { type: "image", mimeType, data };
|
|
1962
|
-
}
|
|
1963
|
-
function audioContent(mimeType, data) {
|
|
1964
|
-
return { type: "audio", mimeType, data };
|
|
1965
|
-
}
|
|
1966
|
-
function extractText(block) {
|
|
1967
|
-
if (typeof block !== "object" || block === null) return "";
|
|
1968
|
-
const b = block;
|
|
1969
|
-
if (b.type === "text" && typeof b.text === "string") return b.text;
|
|
1970
|
-
if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
|
|
1971
|
-
return b.resource.text;
|
|
1972
|
-
}
|
|
1973
|
-
return "";
|
|
1974
|
-
}
|
|
1975
|
-
function isRecord(v) {
|
|
1976
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1977
|
-
}
|
|
1978
|
-
function emptyRunResult(stopReason) {
|
|
1979
|
-
return {
|
|
1980
|
-
text: "",
|
|
1981
|
-
stopReason,
|
|
1982
|
-
hasText: false,
|
|
1983
|
-
toolCalls: [],
|
|
1984
|
-
diffs: [],
|
|
1985
|
-
thoughts: ""
|
|
1986
|
-
};
|
|
1987
|
-
}
|
|
1988
1993
|
|
|
1989
1994
|
// src/integration/acp-subagent-runner.ts
|
|
1990
1995
|
async function makeACPSubagentRunner(options) {
|