acpx 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/dist/{cli-Ddxpnz9X.js → cli-BGYGVo3b.js} +35 -10
- package/dist/cli-BGYGVo3b.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +204 -19
- package/dist/cli.js.map +1 -1
- package/dist/{client-2fTFutRH.d.ts → client-FzXPdgP7.d.ts} +10 -4
- package/dist/client-FzXPdgP7.d.ts.map +1 -0
- package/dist/{flags-yXzUm7Aq.js → flags-D706STfk.js} +46 -6
- package/dist/flags-D706STfk.js.map +1 -0
- package/dist/{flows-CDsfbaA2.js → flows-hcjHmU7P.js} +117 -11
- package/dist/flows-hcjHmU7P.js.map +1 -0
- package/dist/flows.d.ts +19 -3
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +2 -2
- package/dist/{prompt-turn-BY5SwU1F.js → live-checkpoint-B9ctAuqV.js} +1335 -82
- package/dist/live-checkpoint-B9ctAuqV.js.map +1 -0
- package/dist/output-BL9XRWzS.js +3712 -0
- package/dist/output-BL9XRWzS.js.map +1 -0
- package/dist/runtime.d.ts +32 -6
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +169 -32
- package/dist/runtime.js.map +1 -1
- package/dist/{types-CVBeQyi3.d.ts → session-options-BJyG6zEH.d.ts} +56 -3
- package/dist/session-options-BJyG6zEH.d.ts.map +1 -0
- package/package.json +27 -25
- package/skills/acpx/SKILL.md +200 -9
- package/dist/cli-Ddxpnz9X.js.map +0 -1
- package/dist/client-2fTFutRH.d.ts.map +0 -1
- package/dist/flags-yXzUm7Aq.js.map +0 -1
- package/dist/flows-CDsfbaA2.js.map +0 -1
- package/dist/ipc-BruTG5Fb.js +0 -1241
- package/dist/ipc-BruTG5Fb.js.map +0 -1
- package/dist/jsonrpc-DSxh2w5R.js +0 -68
- package/dist/jsonrpc-DSxh2w5R.js.map +0 -1
- package/dist/output-DmHvT8vm.js +0 -807
- package/dist/output-DmHvT8vm.js.map +0 -1
- package/dist/perf-metrics-C2pXfxvR.js +0 -598
- package/dist/perf-metrics-C2pXfxvR.js.map +0 -1
- package/dist/prompt-turn-BY5SwU1F.js.map +0 -1
- package/dist/render-yqwtaOX4.js +0 -172
- package/dist/render-yqwtaOX4.js.map +0 -1
- package/dist/rolldown-runtime-CiIaOW0V.js +0 -13
- package/dist/session-BwgaPK8-.js +0 -1526
- package/dist/session-BwgaPK8-.js.map +0 -1
- package/dist/session-options-pCbHn_n7.d.ts +0 -13
- package/dist/session-options-pCbHn_n7.d.ts.map +0 -1
- package/dist/types-CVBeQyi3.d.ts.map +0 -1
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { B as PermissionDeniedError, C as isAcpResourceNotFoundError, F as AgentStartupError, G as SessionModeReplayError, I as AuthPolicyError, J as SessionResolutionError, K as SessionModelReplayError, L as ClaudeAcpSessionCreateTimeoutError, M as SESSION_RECORD_SCHEMA, N as AgentDisconnectedError, P as AgentSpawnError, R as CopilotAcpUnsupportedError, S as extractAcpError, V as PermissionPromptUnavailableError, W as SessionConfigOptionReplayError, Y as SessionResumeRequiredError, g as textPrompt, i as measurePerf, l as extractRuntimeSessionId, q as SessionNotFoundError, r as incrementPerfCounter, u as normalizeRuntimeSessionId, v as formatErrorMessage, y as isAcpQueryClosedBeforeResponseError, z as GeminiAcpStartupTimeoutError } from "./perf-metrics-C2pXfxvR.js";
|
|
2
|
-
import { r as isSessionUpdateNotification } from "./jsonrpc-DSxh2w5R.js";
|
|
3
1
|
import fs, { statSync } from "node:fs";
|
|
4
2
|
import { fileURLToPath } from "node:url";
|
|
5
3
|
import path from "node:path";
|
|
@@ -11,6 +9,397 @@ import { ClientSideConnection, PROTOCOL_VERSION } from "@agentclientprotocol/sdk
|
|
|
11
9
|
import readline from "node:readline/promises";
|
|
12
10
|
import { promisify } from "node:util";
|
|
13
11
|
import { randomUUID } from "node:crypto";
|
|
12
|
+
//#region src/errors.ts
|
|
13
|
+
var AcpxOperationalError = class extends Error {
|
|
14
|
+
outputCode;
|
|
15
|
+
detailCode;
|
|
16
|
+
origin;
|
|
17
|
+
retryable;
|
|
18
|
+
acp;
|
|
19
|
+
outputAlreadyEmitted;
|
|
20
|
+
constructor(message, options) {
|
|
21
|
+
super(message, options);
|
|
22
|
+
this.name = new.target.name;
|
|
23
|
+
this.outputCode = options?.outputCode;
|
|
24
|
+
this.detailCode = options?.detailCode;
|
|
25
|
+
this.origin = options?.origin;
|
|
26
|
+
this.retryable = options?.retryable;
|
|
27
|
+
this.acp = options?.acp;
|
|
28
|
+
this.outputAlreadyEmitted = options?.outputAlreadyEmitted;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var SessionNotFoundError = class extends AcpxOperationalError {
|
|
32
|
+
sessionId;
|
|
33
|
+
constructor(sessionId) {
|
|
34
|
+
super(`Session not found: ${sessionId}`);
|
|
35
|
+
this.sessionId = sessionId;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var SessionResolutionError = class extends AcpxOperationalError {};
|
|
39
|
+
var AgentSpawnError = class extends AcpxOperationalError {
|
|
40
|
+
agentCommand;
|
|
41
|
+
constructor(agentCommand, cause) {
|
|
42
|
+
super(`Failed to spawn agent command: ${agentCommand}`, { cause: cause instanceof Error ? cause : void 0 });
|
|
43
|
+
this.agentCommand = agentCommand;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var AgentStartupError = class extends AcpxOperationalError {
|
|
47
|
+
agentCommand;
|
|
48
|
+
exitCode;
|
|
49
|
+
signal;
|
|
50
|
+
stderrSummary;
|
|
51
|
+
constructor(params) {
|
|
52
|
+
const exitSummary = `exit=${params.exitCode ?? "null"}, signal=${params.signal ?? "null"}`;
|
|
53
|
+
const stderrSuffix = typeof params.stderrSummary === "string" && params.stderrSummary.trim().length > 0 ? `: ${params.stderrSummary.trim()}` : "";
|
|
54
|
+
super(`ACP agent exited before initialize completed (${exitSummary})${stderrSuffix}`, {
|
|
55
|
+
cause: params.cause instanceof Error ? params.cause : void 0,
|
|
56
|
+
outputCode: "RUNTIME",
|
|
57
|
+
detailCode: "AGENT_STARTUP_FAILED",
|
|
58
|
+
origin: "acp"
|
|
59
|
+
});
|
|
60
|
+
this.agentCommand = params.agentCommand;
|
|
61
|
+
this.exitCode = params.exitCode;
|
|
62
|
+
this.signal = params.signal;
|
|
63
|
+
this.stderrSummary = params.stderrSummary?.trim() || void 0;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var AgentDisconnectedError = class extends AcpxOperationalError {
|
|
67
|
+
reason;
|
|
68
|
+
exitCode;
|
|
69
|
+
signal;
|
|
70
|
+
constructor(reason, exitCode, signal, options) {
|
|
71
|
+
super(`ACP agent disconnected during request (${reason}, exit=${exitCode ?? "null"}, signal=${signal ?? "null"})`, {
|
|
72
|
+
outputCode: "RUNTIME",
|
|
73
|
+
detailCode: "AGENT_DISCONNECTED",
|
|
74
|
+
origin: "acp",
|
|
75
|
+
...options
|
|
76
|
+
});
|
|
77
|
+
this.reason = reason;
|
|
78
|
+
this.exitCode = exitCode;
|
|
79
|
+
this.signal = signal;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var SessionResumeRequiredError = class extends AcpxOperationalError {
|
|
83
|
+
constructor(message, options) {
|
|
84
|
+
super(message, {
|
|
85
|
+
outputCode: "RUNTIME",
|
|
86
|
+
detailCode: "SESSION_RESUME_REQUIRED",
|
|
87
|
+
origin: "acp",
|
|
88
|
+
retryable: true,
|
|
89
|
+
...options
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var GeminiAcpStartupTimeoutError = class extends AcpxOperationalError {
|
|
94
|
+
constructor(message, options) {
|
|
95
|
+
super(message, {
|
|
96
|
+
outputCode: "TIMEOUT",
|
|
97
|
+
detailCode: "GEMINI_ACP_STARTUP_TIMEOUT",
|
|
98
|
+
origin: "acp",
|
|
99
|
+
...options
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var SessionModeReplayError = class extends AcpxOperationalError {
|
|
104
|
+
constructor(message, options) {
|
|
105
|
+
super(message, {
|
|
106
|
+
outputCode: "RUNTIME",
|
|
107
|
+
detailCode: "SESSION_MODE_REPLAY_FAILED",
|
|
108
|
+
origin: "acp",
|
|
109
|
+
...options
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var SessionModelReplayError = class extends AcpxOperationalError {
|
|
114
|
+
constructor(message, options) {
|
|
115
|
+
super(message, {
|
|
116
|
+
outputCode: "RUNTIME",
|
|
117
|
+
detailCode: "SESSION_MODEL_REPLAY_FAILED",
|
|
118
|
+
origin: "acp",
|
|
119
|
+
...options
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var SessionConfigOptionReplayError = class extends AcpxOperationalError {
|
|
124
|
+
constructor(message, options) {
|
|
125
|
+
super(message, {
|
|
126
|
+
outputCode: "RUNTIME",
|
|
127
|
+
detailCode: "SESSION_CONFIG_OPTION_REPLAY_FAILED",
|
|
128
|
+
origin: "acp",
|
|
129
|
+
...options
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
var ClaudeAcpSessionCreateTimeoutError = class extends AcpxOperationalError {
|
|
134
|
+
constructor(message, options) {
|
|
135
|
+
super(message, {
|
|
136
|
+
outputCode: "TIMEOUT",
|
|
137
|
+
detailCode: "CLAUDE_ACP_SESSION_CREATE_TIMEOUT",
|
|
138
|
+
origin: "acp",
|
|
139
|
+
...options
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var CopilotAcpUnsupportedError = class extends AcpxOperationalError {
|
|
144
|
+
constructor(message, options) {
|
|
145
|
+
super(message, {
|
|
146
|
+
outputCode: "RUNTIME",
|
|
147
|
+
detailCode: "COPILOT_ACP_UNSUPPORTED",
|
|
148
|
+
origin: "acp",
|
|
149
|
+
...options
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
var AuthPolicyError = class extends AcpxOperationalError {
|
|
154
|
+
constructor(message, options) {
|
|
155
|
+
super(message, {
|
|
156
|
+
outputCode: "RUNTIME",
|
|
157
|
+
detailCode: "AUTH_REQUIRED",
|
|
158
|
+
origin: "acp",
|
|
159
|
+
...options
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
var QueueConnectionError = class extends AcpxOperationalError {};
|
|
164
|
+
var QueueProtocolError = class extends AcpxOperationalError {};
|
|
165
|
+
var PermissionDeniedError = class extends AcpxOperationalError {};
|
|
166
|
+
var PermissionPromptUnavailableError = class extends AcpxOperationalError {
|
|
167
|
+
constructor() {
|
|
168
|
+
super("Permission prompt unavailable in non-interactive mode");
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/types.ts
|
|
173
|
+
const EXIT_CODES = {
|
|
174
|
+
SUCCESS: 0,
|
|
175
|
+
ERROR: 1,
|
|
176
|
+
USAGE: 2,
|
|
177
|
+
TIMEOUT: 3,
|
|
178
|
+
NO_SESSION: 4,
|
|
179
|
+
PERMISSION_DENIED: 5,
|
|
180
|
+
INTERRUPTED: 130
|
|
181
|
+
};
|
|
182
|
+
const OUTPUT_FORMATS = [
|
|
183
|
+
"text",
|
|
184
|
+
"json",
|
|
185
|
+
"quiet"
|
|
186
|
+
];
|
|
187
|
+
const PERMISSION_MODES = [
|
|
188
|
+
"approve-all",
|
|
189
|
+
"approve-reads",
|
|
190
|
+
"deny-all"
|
|
191
|
+
];
|
|
192
|
+
const AUTH_POLICIES = ["skip", "fail"];
|
|
193
|
+
const NON_INTERACTIVE_PERMISSION_POLICIES = ["deny", "fail"];
|
|
194
|
+
const PERMISSION_POLICY_ACTIONS = [
|
|
195
|
+
"approve",
|
|
196
|
+
"deny",
|
|
197
|
+
"escalate"
|
|
198
|
+
];
|
|
199
|
+
const OUTPUT_ERROR_CODES = [
|
|
200
|
+
"NO_SESSION",
|
|
201
|
+
"TIMEOUT",
|
|
202
|
+
"PERMISSION_DENIED",
|
|
203
|
+
"PERMISSION_PROMPT_UNAVAILABLE",
|
|
204
|
+
"RUNTIME",
|
|
205
|
+
"USAGE"
|
|
206
|
+
];
|
|
207
|
+
const OUTPUT_ERROR_ORIGINS = [
|
|
208
|
+
"cli",
|
|
209
|
+
"runtime",
|
|
210
|
+
"queue",
|
|
211
|
+
"acp"
|
|
212
|
+
];
|
|
213
|
+
const SESSION_RECORD_SCHEMA = "acpx.session.v1";
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region src/acp/error-shapes.ts
|
|
216
|
+
const RESOURCE_NOT_FOUND_ACP_CODES = new Set([-32001, -32002]);
|
|
217
|
+
function asRecord$7(value) {
|
|
218
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
219
|
+
return value;
|
|
220
|
+
}
|
|
221
|
+
function toAcpErrorPayload(value) {
|
|
222
|
+
const record = asRecord$7(value);
|
|
223
|
+
if (!record) return;
|
|
224
|
+
if (typeof record.code !== "number" || !Number.isFinite(record.code)) return;
|
|
225
|
+
if (typeof record.message !== "string" || record.message.length === 0) return;
|
|
226
|
+
return {
|
|
227
|
+
code: record.code,
|
|
228
|
+
message: record.message,
|
|
229
|
+
data: record.data
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function extractAcpErrorInternal(value, depth) {
|
|
233
|
+
if (depth > 5) return;
|
|
234
|
+
const direct = toAcpErrorPayload(value);
|
|
235
|
+
if (direct) return direct;
|
|
236
|
+
const record = asRecord$7(value);
|
|
237
|
+
if (!record) return;
|
|
238
|
+
if ("error" in record) {
|
|
239
|
+
const nested = extractAcpErrorInternal(record.error, depth + 1);
|
|
240
|
+
if (nested) return nested;
|
|
241
|
+
}
|
|
242
|
+
if ("acp" in record) {
|
|
243
|
+
const nested = extractAcpErrorInternal(record.acp, depth + 1);
|
|
244
|
+
if (nested) return nested;
|
|
245
|
+
}
|
|
246
|
+
if ("cause" in record) {
|
|
247
|
+
const nested = extractAcpErrorInternal(record.cause, depth + 1);
|
|
248
|
+
if (nested) return nested;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function formatUnknownErrorMessage(error) {
|
|
252
|
+
if (error instanceof Error) return error.message;
|
|
253
|
+
if (error && typeof error === "object") {
|
|
254
|
+
const maybeMessage = error.message;
|
|
255
|
+
if (typeof maybeMessage === "string" && maybeMessage.length > 0) return maybeMessage;
|
|
256
|
+
try {
|
|
257
|
+
return JSON.stringify(error);
|
|
258
|
+
} catch {}
|
|
259
|
+
}
|
|
260
|
+
return String(error);
|
|
261
|
+
}
|
|
262
|
+
const SESSION_NOT_FOUND_PATTERN = /session\s+["'\w-]+\s+not found/i;
|
|
263
|
+
function isSessionNotFoundText(value) {
|
|
264
|
+
if (typeof value !== "string") return false;
|
|
265
|
+
const normalized = value.toLowerCase();
|
|
266
|
+
return normalized.includes("resource_not_found") || normalized.includes("resource not found") || normalized.includes("session not found") || normalized.includes("unknown session") || normalized.includes("invalid session identifier") || SESSION_NOT_FOUND_PATTERN.test(value);
|
|
267
|
+
}
|
|
268
|
+
function hasSessionNotFoundHint(value, depth = 0) {
|
|
269
|
+
if (depth > 4) return false;
|
|
270
|
+
if (isSessionNotFoundText(value)) return true;
|
|
271
|
+
if (Array.isArray(value)) return value.some((entry) => hasSessionNotFoundHint(entry, depth + 1));
|
|
272
|
+
const record = asRecord$7(value);
|
|
273
|
+
if (!record) return false;
|
|
274
|
+
return Object.values(record).some((entry) => hasSessionNotFoundHint(entry, depth + 1));
|
|
275
|
+
}
|
|
276
|
+
function extractAcpError(error) {
|
|
277
|
+
return extractAcpErrorInternal(error, 0);
|
|
278
|
+
}
|
|
279
|
+
function isAcpResourceNotFoundError(error) {
|
|
280
|
+
const acp = extractAcpError(error);
|
|
281
|
+
if (acp && RESOURCE_NOT_FOUND_ACP_CODES.has(acp.code)) return true;
|
|
282
|
+
if (acp) {
|
|
283
|
+
if (isSessionNotFoundText(acp.message)) return true;
|
|
284
|
+
if (hasSessionNotFoundHint(acp.data)) return true;
|
|
285
|
+
}
|
|
286
|
+
return isSessionNotFoundText(formatUnknownErrorMessage(error));
|
|
287
|
+
}
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/acp/error-normalization.ts
|
|
290
|
+
const AUTH_REQUIRED_ACP_CODES = new Set([-32e3]);
|
|
291
|
+
const QUERY_CLOSED_BEFORE_RESPONSE_DETAIL = "query closed before response received";
|
|
292
|
+
function asRecord$6(value) {
|
|
293
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
294
|
+
return value;
|
|
295
|
+
}
|
|
296
|
+
function isAuthRequiredMessage(value) {
|
|
297
|
+
if (!value) return false;
|
|
298
|
+
const normalized = value.toLowerCase();
|
|
299
|
+
return normalized.includes("auth required") || normalized.includes("authentication required") || normalized.includes("authorization required") || normalized.includes("credential required") || normalized.includes("credentials required") || normalized.includes("token required") || normalized.includes("login required");
|
|
300
|
+
}
|
|
301
|
+
function isAcpAuthRequiredPayload(acp) {
|
|
302
|
+
if (!acp) return false;
|
|
303
|
+
if (!AUTH_REQUIRED_ACP_CODES.has(acp.code)) return false;
|
|
304
|
+
if (isAuthRequiredMessage(acp.message)) return true;
|
|
305
|
+
const data = asRecord$6(acp.data);
|
|
306
|
+
if (!data) return false;
|
|
307
|
+
if (data.authRequired === true) return true;
|
|
308
|
+
const methodId = data.methodId;
|
|
309
|
+
if (typeof methodId === "string" && methodId.trim().length > 0) return true;
|
|
310
|
+
const methods = data.methods;
|
|
311
|
+
if (Array.isArray(methods) && methods.length > 0) return true;
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
function isOutputErrorCode(value) {
|
|
315
|
+
return typeof value === "string" && OUTPUT_ERROR_CODES.includes(value);
|
|
316
|
+
}
|
|
317
|
+
function isOutputErrorOrigin(value) {
|
|
318
|
+
return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
|
|
319
|
+
}
|
|
320
|
+
function readOutputErrorMeta(error) {
|
|
321
|
+
const record = asRecord$6(error);
|
|
322
|
+
if (!record) return {};
|
|
323
|
+
return {
|
|
324
|
+
outputCode: isOutputErrorCode(record.outputCode) ? record.outputCode : void 0,
|
|
325
|
+
detailCode: typeof record.detailCode === "string" && record.detailCode.trim().length > 0 ? record.detailCode : void 0,
|
|
326
|
+
origin: isOutputErrorOrigin(record.origin) ? record.origin : void 0,
|
|
327
|
+
retryable: typeof record.retryable === "boolean" ? record.retryable : void 0,
|
|
328
|
+
acp: extractAcpError(record.acp)
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function isTimeoutLike(error) {
|
|
332
|
+
return error instanceof Error && error.name === "TimeoutError";
|
|
333
|
+
}
|
|
334
|
+
function isNoSessionLike(error) {
|
|
335
|
+
return error instanceof Error && error.name === "NoSessionError";
|
|
336
|
+
}
|
|
337
|
+
function isUsageLike(error) {
|
|
338
|
+
if (!(error instanceof Error)) return false;
|
|
339
|
+
return error.name === "CommanderError" || error.name === "InvalidArgumentError" || asRecord$6(error)?.code === "commander.invalidArgument";
|
|
340
|
+
}
|
|
341
|
+
function formatErrorMessage(error) {
|
|
342
|
+
return formatUnknownErrorMessage(error);
|
|
343
|
+
}
|
|
344
|
+
function isAcpQueryClosedBeforeResponseError(error) {
|
|
345
|
+
const acp = extractAcpError(error);
|
|
346
|
+
if (!acp || acp.code !== -32603) return false;
|
|
347
|
+
const details = asRecord$6(acp.data)?.details;
|
|
348
|
+
if (typeof details !== "string") return false;
|
|
349
|
+
return details.toLowerCase().includes(QUERY_CLOSED_BEFORE_RESPONSE_DETAIL);
|
|
350
|
+
}
|
|
351
|
+
function mapErrorCode(error) {
|
|
352
|
+
if (error instanceof PermissionPromptUnavailableError) return "PERMISSION_PROMPT_UNAVAILABLE";
|
|
353
|
+
if (error instanceof PermissionDeniedError) return "PERMISSION_DENIED";
|
|
354
|
+
if (isTimeoutLike(error)) return "TIMEOUT";
|
|
355
|
+
if (isNoSessionLike(error) || isAcpResourceNotFoundError(error)) return "NO_SESSION";
|
|
356
|
+
if (isUsageLike(error)) return "USAGE";
|
|
357
|
+
}
|
|
358
|
+
function normalizeOutputError(error, options = {}) {
|
|
359
|
+
const meta = readOutputErrorMeta(error);
|
|
360
|
+
let code = mapErrorCode(error) ?? options.defaultCode ?? "RUNTIME";
|
|
361
|
+
if (meta.outputCode) code = meta.outputCode;
|
|
362
|
+
if (code === "RUNTIME" && isAcpResourceNotFoundError(error)) code = "NO_SESSION";
|
|
363
|
+
const acp = options.acp ?? meta.acp ?? extractAcpError(error);
|
|
364
|
+
const detailCode = meta.detailCode ?? options.detailCode ?? (error instanceof AuthPolicyError || isAcpAuthRequiredPayload(acp) ? "AUTH_REQUIRED" : void 0);
|
|
365
|
+
return {
|
|
366
|
+
code,
|
|
367
|
+
message: formatErrorMessage(error),
|
|
368
|
+
detailCode,
|
|
369
|
+
origin: meta.origin ?? options.origin,
|
|
370
|
+
retryable: meta.retryable ?? options.retryable,
|
|
371
|
+
acp
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Returns true when an error from `client.prompt()` looks transient and
|
|
376
|
+
* can reasonably be retried (e.g. model-API 400/500, network hiccups that
|
|
377
|
+
* surface as ACP internal errors).
|
|
378
|
+
*
|
|
379
|
+
* Errors that are definitively non-recoverable (auth, missing session,
|
|
380
|
+
* invalid params, timeout, permission) return false.
|
|
381
|
+
*/
|
|
382
|
+
function isRetryablePromptError(error) {
|
|
383
|
+
if (error instanceof PermissionDeniedError || error instanceof PermissionPromptUnavailableError) return false;
|
|
384
|
+
if (isTimeoutLike(error) || isNoSessionLike(error) || isUsageLike(error)) return false;
|
|
385
|
+
const acp = extractAcpError(error);
|
|
386
|
+
if (!acp) return false;
|
|
387
|
+
if (acp.code === -32001 || acp.code === -32002) return false;
|
|
388
|
+
if (isAcpAuthRequiredPayload(acp)) return false;
|
|
389
|
+
if (acp.code === -32601 || acp.code === -32602) return false;
|
|
390
|
+
return acp.code === -32603 || acp.code === -32700;
|
|
391
|
+
}
|
|
392
|
+
function exitCodeForOutputErrorCode(code) {
|
|
393
|
+
switch (code) {
|
|
394
|
+
case "USAGE": return EXIT_CODES.USAGE;
|
|
395
|
+
case "TIMEOUT": return EXIT_CODES.TIMEOUT;
|
|
396
|
+
case "NO_SESSION": return EXIT_CODES.NO_SESSION;
|
|
397
|
+
case "PERMISSION_DENIED":
|
|
398
|
+
case "PERMISSION_PROMPT_UNAVAILABLE": return EXIT_CODES.PERMISSION_DENIED;
|
|
399
|
+
default: return EXIT_CODES.ERROR;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
//#endregion
|
|
14
403
|
//#region src/agent-registry.ts
|
|
15
404
|
const ACP_ADAPTER_PACKAGE_RANGES = {
|
|
16
405
|
pi: "^0.0.26",
|
|
@@ -223,6 +612,149 @@ async function withInterrupt(run, onInterrupt) {
|
|
|
223
612
|
});
|
|
224
613
|
}
|
|
225
614
|
//#endregion
|
|
615
|
+
//#region src/prompt-content.ts
|
|
616
|
+
var PromptInputValidationError = class extends Error {
|
|
617
|
+
constructor(message) {
|
|
618
|
+
super(message);
|
|
619
|
+
this.name = "PromptInputValidationError";
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
function asRecord$5(value) {
|
|
623
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
624
|
+
return value;
|
|
625
|
+
}
|
|
626
|
+
function isNonEmptyString(value) {
|
|
627
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
628
|
+
}
|
|
629
|
+
function isBase64Data(value) {
|
|
630
|
+
if (value.length === 0 || value.length % 4 !== 0) return false;
|
|
631
|
+
return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
|
|
632
|
+
}
|
|
633
|
+
function isImageMimeType(value) {
|
|
634
|
+
return /^image\/[A-Za-z0-9.+-]+$/i.test(value);
|
|
635
|
+
}
|
|
636
|
+
function isTextBlock(value) {
|
|
637
|
+
const record = asRecord$5(value);
|
|
638
|
+
return record?.type === "text" && typeof record.text === "string";
|
|
639
|
+
}
|
|
640
|
+
function isImageBlock(value) {
|
|
641
|
+
const record = asRecord$5(value);
|
|
642
|
+
return record?.type === "image" && isNonEmptyString(record.mimeType) && isImageMimeType(record.mimeType) && typeof record.data === "string" && isBase64Data(record.data);
|
|
643
|
+
}
|
|
644
|
+
function isResourceLinkBlock(value) {
|
|
645
|
+
const record = asRecord$5(value);
|
|
646
|
+
return record?.type === "resource_link" && isNonEmptyString(record.uri) && (record.title === void 0 || typeof record.title === "string") && (record.name === void 0 || typeof record.name === "string");
|
|
647
|
+
}
|
|
648
|
+
function isResourcePayload(value) {
|
|
649
|
+
const record = asRecord$5(value);
|
|
650
|
+
if (!record || !isNonEmptyString(record.uri)) return false;
|
|
651
|
+
return record.text === void 0 || typeof record.text === "string";
|
|
652
|
+
}
|
|
653
|
+
function isResourceBlock(value) {
|
|
654
|
+
const record = asRecord$5(value);
|
|
655
|
+
return record?.type === "resource" && isResourcePayload(record.resource);
|
|
656
|
+
}
|
|
657
|
+
function isContentBlock(value) {
|
|
658
|
+
return isTextBlock(value) || isImageBlock(value) || isResourceLinkBlock(value) || isResourceBlock(value);
|
|
659
|
+
}
|
|
660
|
+
function getContentBlockValidationError(value, index) {
|
|
661
|
+
const record = asRecord$5(value);
|
|
662
|
+
if (!record || typeof record.type !== "string") return `prompt[${index}] must be an ACP content block object`;
|
|
663
|
+
switch (record.type) {
|
|
664
|
+
case "text": return typeof record.text === "string" ? void 0 : `prompt[${index}] text block must include a string text field`;
|
|
665
|
+
case "image":
|
|
666
|
+
if (!isNonEmptyString(record.mimeType)) return `prompt[${index}] image block must include a non-empty mimeType`;
|
|
667
|
+
if (!isImageMimeType(record.mimeType)) return `prompt[${index}] image block mimeType must start with image/`;
|
|
668
|
+
if (typeof record.data !== "string" || record.data.length === 0) return `prompt[${index}] image block must include non-empty base64 data`;
|
|
669
|
+
if (!isBase64Data(record.data)) return `prompt[${index}] image block data must be valid base64`;
|
|
670
|
+
return;
|
|
671
|
+
case "resource_link":
|
|
672
|
+
if (!isNonEmptyString(record.uri)) return `prompt[${index}] resource_link block must include a non-empty uri`;
|
|
673
|
+
if (record.title !== void 0 && typeof record.title !== "string") return `prompt[${index}] resource_link block title must be a string when present`;
|
|
674
|
+
if (record.name !== void 0 && typeof record.name !== "string") return `prompt[${index}] resource_link block name must be a string when present`;
|
|
675
|
+
return;
|
|
676
|
+
case "resource":
|
|
677
|
+
if (!asRecord$5(record.resource)) return `prompt[${index}] resource block must include a resource object`;
|
|
678
|
+
if (!isResourcePayload(record.resource)) return `prompt[${index}] resource block resource must include a non-empty uri and optional text`;
|
|
679
|
+
return;
|
|
680
|
+
default: return `prompt[${index}] has unsupported content block type ${JSON.stringify(record.type)}`;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function isPromptInput(value) {
|
|
684
|
+
return Array.isArray(value) && value.every((entry) => isContentBlock(entry));
|
|
685
|
+
}
|
|
686
|
+
function textPrompt(text) {
|
|
687
|
+
return [{
|
|
688
|
+
type: "text",
|
|
689
|
+
text
|
|
690
|
+
}];
|
|
691
|
+
}
|
|
692
|
+
function parseStructuredPrompt(source) {
|
|
693
|
+
if (!source.startsWith("[")) return;
|
|
694
|
+
try {
|
|
695
|
+
const parsed = JSON.parse(source);
|
|
696
|
+
if (isPromptInput(parsed)) return parsed;
|
|
697
|
+
if (Array.isArray(parsed)) throw new PromptInputValidationError(parsed.map((entry, index) => getContentBlockValidationError(entry, index)).find((message) => message !== void 0) ?? "Structured prompt JSON must be an array of valid ACP content blocks");
|
|
698
|
+
return;
|
|
699
|
+
} catch (error) {
|
|
700
|
+
if (error instanceof PromptInputValidationError) throw error;
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
function parsePromptSource(source) {
|
|
705
|
+
const trimmed = source.trim();
|
|
706
|
+
const structured = parseStructuredPrompt(trimmed);
|
|
707
|
+
if (structured) return structured;
|
|
708
|
+
if (!trimmed) return [];
|
|
709
|
+
return textPrompt(trimmed);
|
|
710
|
+
}
|
|
711
|
+
function mergePromptSourceWithText(source, suffixText) {
|
|
712
|
+
const prompt = parsePromptSource(source);
|
|
713
|
+
const appended = suffixText.trim();
|
|
714
|
+
if (!appended) return prompt;
|
|
715
|
+
if (prompt.length === 0) return textPrompt(appended);
|
|
716
|
+
return [...prompt, ...textPrompt(appended)];
|
|
717
|
+
}
|
|
718
|
+
function promptToDisplayText(prompt) {
|
|
719
|
+
return prompt.map((block) => {
|
|
720
|
+
switch (block.type) {
|
|
721
|
+
case "text": return block.text;
|
|
722
|
+
case "resource_link": return block.title ?? block.name ?? block.uri;
|
|
723
|
+
case "resource": return "text" in block.resource && typeof block.resource.text === "string" ? block.resource.text : block.resource.uri;
|
|
724
|
+
case "image": return `[image] ${block.mimeType}`;
|
|
725
|
+
default: return "";
|
|
726
|
+
}
|
|
727
|
+
}).filter((entry) => entry.trim().length > 0).join("\n\n").trim();
|
|
728
|
+
}
|
|
729
|
+
//#endregion
|
|
730
|
+
//#region src/acp/agent-session-id.ts
|
|
731
|
+
const AGENT_SESSION_ID_META_KEYS = ["agentSessionId", "sessionId"];
|
|
732
|
+
function normalizeAgentSessionId(value) {
|
|
733
|
+
if (typeof value !== "string") return;
|
|
734
|
+
const trimmed = value.trim();
|
|
735
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
736
|
+
}
|
|
737
|
+
function asMetaRecord(meta) {
|
|
738
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) return;
|
|
739
|
+
return meta;
|
|
740
|
+
}
|
|
741
|
+
function extractAgentSessionId(meta) {
|
|
742
|
+
const record = asMetaRecord(meta);
|
|
743
|
+
if (!record) return;
|
|
744
|
+
for (const key of AGENT_SESSION_ID_META_KEYS) {
|
|
745
|
+
const normalized = normalizeAgentSessionId(record[key]);
|
|
746
|
+
if (normalized) return normalized;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
//#endregion
|
|
750
|
+
//#region src/session/runtime-session-id.ts
|
|
751
|
+
function normalizeRuntimeSessionId(value) {
|
|
752
|
+
return normalizeAgentSessionId(value);
|
|
753
|
+
}
|
|
754
|
+
function extractRuntimeSessionId(meta) {
|
|
755
|
+
return extractAgentSessionId(meta);
|
|
756
|
+
}
|
|
757
|
+
//#endregion
|
|
226
758
|
//#region src/session/persistence/serialize.ts
|
|
227
759
|
function serializeSessionRecordForDisk(record) {
|
|
228
760
|
const canonical = {
|
|
@@ -262,6 +794,72 @@ function serializeSessionRecordForDisk(record) {
|
|
|
262
794
|
};
|
|
263
795
|
}
|
|
264
796
|
//#endregion
|
|
797
|
+
//#region src/perf-metrics.ts
|
|
798
|
+
const counters = /* @__PURE__ */ new Map();
|
|
799
|
+
const gauges = /* @__PURE__ */ new Map();
|
|
800
|
+
const timings = /* @__PURE__ */ new Map();
|
|
801
|
+
function hrNow() {
|
|
802
|
+
return process.hrtime.bigint();
|
|
803
|
+
}
|
|
804
|
+
function durationMs(start) {
|
|
805
|
+
return Number(process.hrtime.bigint() - start) / 1e6;
|
|
806
|
+
}
|
|
807
|
+
function roundMetric(value) {
|
|
808
|
+
return Number(value.toFixed(3));
|
|
809
|
+
}
|
|
810
|
+
function incrementPerfCounter(name, delta = 1) {
|
|
811
|
+
counters.set(name, (counters.get(name) ?? 0) + delta);
|
|
812
|
+
}
|
|
813
|
+
function setPerfGauge(name, value) {
|
|
814
|
+
gauges.set(name, value);
|
|
815
|
+
}
|
|
816
|
+
function recordPerfDuration(name, durationMsValue) {
|
|
817
|
+
const next = timings.get(name) ?? {
|
|
818
|
+
count: 0,
|
|
819
|
+
totalMs: 0,
|
|
820
|
+
maxMs: 0
|
|
821
|
+
};
|
|
822
|
+
next.count += 1;
|
|
823
|
+
next.totalMs += durationMsValue;
|
|
824
|
+
next.maxMs = Math.max(next.maxMs, durationMsValue);
|
|
825
|
+
timings.set(name, next);
|
|
826
|
+
}
|
|
827
|
+
async function measurePerf(name, run) {
|
|
828
|
+
const startedAt = hrNow();
|
|
829
|
+
try {
|
|
830
|
+
return await run();
|
|
831
|
+
} finally {
|
|
832
|
+
recordPerfDuration(name, durationMs(startedAt));
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
function startPerfTimer(name) {
|
|
836
|
+
const startedAt = hrNow();
|
|
837
|
+
return () => {
|
|
838
|
+
const elapsedMs = durationMs(startedAt);
|
|
839
|
+
recordPerfDuration(name, elapsedMs);
|
|
840
|
+
return elapsedMs;
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
function getPerfMetricsSnapshot() {
|
|
844
|
+
return {
|
|
845
|
+
counters: Object.fromEntries(counters.entries()),
|
|
846
|
+
gauges: Object.fromEntries(gauges.entries()),
|
|
847
|
+
timings: Object.fromEntries([...timings.entries()].map(([name, bucket]) => [name, {
|
|
848
|
+
count: bucket.count,
|
|
849
|
+
totalMs: roundMetric(bucket.totalMs),
|
|
850
|
+
maxMs: roundMetric(bucket.maxMs)
|
|
851
|
+
}]))
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
function resetPerfMetrics() {
|
|
855
|
+
counters.clear();
|
|
856
|
+
gauges.clear();
|
|
857
|
+
timings.clear();
|
|
858
|
+
}
|
|
859
|
+
function formatPerfMetric(name, durationMsValue) {
|
|
860
|
+
return `${name}=${roundMetric(durationMsValue)}ms`;
|
|
861
|
+
}
|
|
862
|
+
//#endregion
|
|
265
863
|
//#region src/persisted-key-policy.ts
|
|
266
864
|
const SNAKE_CASE_KEY = /^[a-z][a-z0-9_]*$/;
|
|
267
865
|
const ZED_TAG_KEYS = new Set([
|
|
@@ -358,7 +956,7 @@ function defaultSessionEventLog(sessionId) {
|
|
|
358
956
|
}
|
|
359
957
|
//#endregion
|
|
360
958
|
//#region src/session/persistence/parse.ts
|
|
361
|
-
function asRecord$
|
|
959
|
+
function asRecord$4(value) {
|
|
362
960
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
363
961
|
return value;
|
|
364
962
|
}
|
|
@@ -370,7 +968,7 @@ function isStringArray(value) {
|
|
|
370
968
|
}
|
|
371
969
|
function parseTokenUsage(raw) {
|
|
372
970
|
if (raw === void 0 || raw === null) return;
|
|
373
|
-
const record = asRecord$
|
|
971
|
+
const record = asRecord$4(raw);
|
|
374
972
|
if (!record) return null;
|
|
375
973
|
const usage = {};
|
|
376
974
|
for (const field of [
|
|
@@ -388,7 +986,7 @@ function parseTokenUsage(raw) {
|
|
|
388
986
|
}
|
|
389
987
|
function parseRequestTokenUsage(raw) {
|
|
390
988
|
if (raw === void 0 || raw === null) return;
|
|
391
|
-
const record = asRecord$
|
|
989
|
+
const record = asRecord$4(raw);
|
|
392
990
|
if (!record) return null;
|
|
393
991
|
const usage = {};
|
|
394
992
|
for (const [key, value] of Object.entries(record)) {
|
|
@@ -399,44 +997,44 @@ function parseRequestTokenUsage(raw) {
|
|
|
399
997
|
return usage;
|
|
400
998
|
}
|
|
401
999
|
function isSessionMessageImage(raw) {
|
|
402
|
-
const record = asRecord$
|
|
1000
|
+
const record = asRecord$4(raw);
|
|
403
1001
|
if (!record || typeof record.source !== "string") return false;
|
|
404
1002
|
if (record.size === void 0 || record.size === null) return true;
|
|
405
|
-
const size = asRecord$
|
|
1003
|
+
const size = asRecord$4(record.size);
|
|
406
1004
|
return !!size && typeof size.width === "number" && Number.isFinite(size.width) && typeof size.height === "number" && Number.isFinite(size.height);
|
|
407
1005
|
}
|
|
408
1006
|
function isUserContent(raw) {
|
|
409
|
-
const record = asRecord$
|
|
1007
|
+
const record = asRecord$4(raw);
|
|
410
1008
|
if (!record) return false;
|
|
411
1009
|
if (typeof record.Text === "string") return true;
|
|
412
1010
|
if (record.Mention !== void 0) {
|
|
413
|
-
const mention = asRecord$
|
|
1011
|
+
const mention = asRecord$4(record.Mention);
|
|
414
1012
|
return !!mention && typeof mention.uri === "string" && typeof mention.content === "string";
|
|
415
1013
|
}
|
|
416
1014
|
if (record.Image !== void 0) return isSessionMessageImage(record.Image);
|
|
417
1015
|
return false;
|
|
418
1016
|
}
|
|
419
1017
|
function isToolUse(raw) {
|
|
420
|
-
const record = asRecord$
|
|
1018
|
+
const record = asRecord$4(raw);
|
|
421
1019
|
return !!record && typeof record.id === "string" && typeof record.name === "string" && typeof record.raw_input === "string" && hasOwn$1(record, "input") && typeof record.is_input_complete === "boolean" && (record.thought_signature === void 0 || record.thought_signature === null || typeof record.thought_signature === "string");
|
|
422
1020
|
}
|
|
423
1021
|
function isToolResultContent(raw) {
|
|
424
|
-
const record = asRecord$
|
|
1022
|
+
const record = asRecord$4(raw);
|
|
425
1023
|
if (!record) return false;
|
|
426
1024
|
if (typeof record.Text === "string") return true;
|
|
427
1025
|
if (record.Image !== void 0) return isSessionMessageImage(record.Image);
|
|
428
1026
|
return false;
|
|
429
1027
|
}
|
|
430
1028
|
function isToolResult(raw) {
|
|
431
|
-
const record = asRecord$
|
|
1029
|
+
const record = asRecord$4(raw);
|
|
432
1030
|
return !!record && typeof record.tool_use_id === "string" && typeof record.tool_name === "string" && typeof record.is_error === "boolean" && isToolResultContent(record.content);
|
|
433
1031
|
}
|
|
434
1032
|
function isAgentContent(raw) {
|
|
435
|
-
const record = asRecord$
|
|
1033
|
+
const record = asRecord$4(raw);
|
|
436
1034
|
if (!record) return false;
|
|
437
1035
|
if (typeof record.Text === "string") return true;
|
|
438
1036
|
if (record.Thinking !== void 0) {
|
|
439
|
-
const thinking = asRecord$
|
|
1037
|
+
const thinking = asRecord$4(record.Thinking);
|
|
440
1038
|
return !!thinking && typeof thinking.text === "string" && (thinking.signature === void 0 || thinking.signature === null || typeof thinking.signature === "string");
|
|
441
1039
|
}
|
|
442
1040
|
if (typeof record.RedactedThinking === "string") return true;
|
|
@@ -444,17 +1042,17 @@ function isAgentContent(raw) {
|
|
|
444
1042
|
return false;
|
|
445
1043
|
}
|
|
446
1044
|
function isUserMessage$1(raw) {
|
|
447
|
-
const record = asRecord$
|
|
1045
|
+
const record = asRecord$4(raw);
|
|
448
1046
|
if (!record || record.User === void 0) return false;
|
|
449
|
-
const user = asRecord$
|
|
1047
|
+
const user = asRecord$4(record.User);
|
|
450
1048
|
return !!user && typeof user.id === "string" && Array.isArray(user.content) && user.content.every((entry) => isUserContent(entry));
|
|
451
1049
|
}
|
|
452
1050
|
function isAgentMessage$1(raw) {
|
|
453
|
-
const record = asRecord$
|
|
1051
|
+
const record = asRecord$4(raw);
|
|
454
1052
|
if (!record || record.Agent === void 0) return false;
|
|
455
|
-
const agent = asRecord$
|
|
1053
|
+
const agent = asRecord$4(record.Agent);
|
|
456
1054
|
if (!agent || !Array.isArray(agent.content) || !agent.content.every(isAgentContent)) return false;
|
|
457
|
-
const toolResults = asRecord$
|
|
1055
|
+
const toolResults = asRecord$4(agent.tool_results);
|
|
458
1056
|
if (!toolResults) return false;
|
|
459
1057
|
return Object.values(toolResults).every(isToolResult);
|
|
460
1058
|
}
|
|
@@ -476,13 +1074,13 @@ function parseConversationRecord(record) {
|
|
|
476
1074
|
};
|
|
477
1075
|
}
|
|
478
1076
|
function parseAcpxState(raw) {
|
|
479
|
-
const record = asRecord$
|
|
1077
|
+
const record = asRecord$4(raw);
|
|
480
1078
|
if (!record) return;
|
|
481
1079
|
const state = {};
|
|
482
1080
|
if (record.reset_on_next_ensure === true) state.reset_on_next_ensure = true;
|
|
483
1081
|
if (typeof record.current_mode_id === "string") state.current_mode_id = record.current_mode_id;
|
|
484
1082
|
if (typeof record.desired_mode_id === "string") state.desired_mode_id = record.desired_mode_id;
|
|
485
|
-
const desiredConfigOptions = asRecord$
|
|
1083
|
+
const desiredConfigOptions = asRecord$4(record.desired_config_options);
|
|
486
1084
|
if (desiredConfigOptions) {
|
|
487
1085
|
const parsed = {};
|
|
488
1086
|
for (const [key, value] of Object.entries(desiredConfigOptions)) if (typeof key === "string" && typeof value === "string") parsed[key] = value;
|
|
@@ -492,7 +1090,7 @@ function parseAcpxState(raw) {
|
|
|
492
1090
|
if (isStringArray(record.available_models)) state.available_models = [...record.available_models];
|
|
493
1091
|
if (isStringArray(record.available_commands)) state.available_commands = [...record.available_commands];
|
|
494
1092
|
if (Array.isArray(record.config_options)) state.config_options = record.config_options;
|
|
495
|
-
const sessionOptions = asRecord$
|
|
1093
|
+
const sessionOptions = asRecord$4(record.session_options);
|
|
496
1094
|
if (sessionOptions) {
|
|
497
1095
|
const parsedSessionOptions = {};
|
|
498
1096
|
if (typeof sessionOptions.model === "string") parsedSessionOptions.model = sessionOptions.model;
|
|
@@ -501,7 +1099,7 @@ function parseAcpxState(raw) {
|
|
|
501
1099
|
const rawSystemPrompt = sessionOptions.system_prompt;
|
|
502
1100
|
if (typeof rawSystemPrompt === "string" && rawSystemPrompt.length > 0) parsedSessionOptions.system_prompt = rawSystemPrompt;
|
|
503
1101
|
else {
|
|
504
|
-
const appendRecord = asRecord$
|
|
1102
|
+
const appendRecord = asRecord$4(rawSystemPrompt);
|
|
505
1103
|
if (appendRecord && typeof appendRecord.append === "string" && appendRecord.append.length > 0) parsedSessionOptions.system_prompt = { append: appendRecord.append };
|
|
506
1104
|
}
|
|
507
1105
|
if (Object.keys(parsedSessionOptions).length > 0) state.session_options = parsedSessionOptions;
|
|
@@ -509,7 +1107,7 @@ function parseAcpxState(raw) {
|
|
|
509
1107
|
return state;
|
|
510
1108
|
}
|
|
511
1109
|
function parseEventLog(raw, sessionId) {
|
|
512
|
-
const record = asRecord$
|
|
1110
|
+
const record = asRecord$4(raw);
|
|
513
1111
|
if (!record) return defaultSessionEventLog(sessionId);
|
|
514
1112
|
if (typeof record.active_path !== "string" || typeof record.segment_count !== "number" || !Number.isInteger(record.segment_count) || record.segment_count < 1 || typeof record.max_segment_bytes !== "number" || !Number.isInteger(record.max_segment_bytes) || record.max_segment_bytes < 1 || typeof record.max_segments !== "number" || !Number.isInteger(record.max_segments) || record.max_segments < 1) return defaultSessionEventLog(sessionId);
|
|
515
1113
|
return {
|
|
@@ -553,7 +1151,7 @@ function normalizeOptionalSignal(value) {
|
|
|
553
1151
|
return Symbol("invalid");
|
|
554
1152
|
}
|
|
555
1153
|
function parseSessionRecord(raw) {
|
|
556
|
-
const record = asRecord$
|
|
1154
|
+
const record = asRecord$4(raw);
|
|
557
1155
|
if (!record) return null;
|
|
558
1156
|
if (record.schema !== "acpx.session.v1") return null;
|
|
559
1157
|
const name = normalizeOptionalName(record.name);
|
|
@@ -595,7 +1193,7 @@ function parseSessionRecord(raw) {
|
|
|
595
1193
|
lastAgentExitAt,
|
|
596
1194
|
lastAgentDisconnectReason,
|
|
597
1195
|
protocolVersion: typeof record.protocol_version === "number" ? record.protocol_version : void 0,
|
|
598
|
-
agentCapabilities: asRecord$
|
|
1196
|
+
agentCapabilities: asRecord$4(record.agent_capabilities),
|
|
599
1197
|
title: conversation.title,
|
|
600
1198
|
messages: conversation.messages,
|
|
601
1199
|
updated_at: conversation.updated_at,
|
|
@@ -607,12 +1205,12 @@ function parseSessionRecord(raw) {
|
|
|
607
1205
|
//#endregion
|
|
608
1206
|
//#region src/session/persistence/index.ts
|
|
609
1207
|
const SESSION_INDEX_SCHEMA = "acpx.session-index.v1";
|
|
610
|
-
function asRecord$
|
|
1208
|
+
function asRecord$3(value) {
|
|
611
1209
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
612
1210
|
return value;
|
|
613
1211
|
}
|
|
614
1212
|
function parseIndexEntry(raw) {
|
|
615
|
-
const record = asRecord$
|
|
1213
|
+
const record = asRecord$3(raw);
|
|
616
1214
|
if (!record) return;
|
|
617
1215
|
if (typeof record.file !== "string" || typeof record.acpxRecordId !== "string" || typeof record.acpSessionId !== "string" || typeof record.agentCommand !== "string" || typeof record.cwd !== "string" || typeof record.lastUsedAt !== "string" || typeof record.closed !== "boolean") return;
|
|
618
1216
|
if (record.name !== void 0 && typeof record.name !== "string") return;
|
|
@@ -646,7 +1244,7 @@ async function readSessionIndex(sessionDir) {
|
|
|
646
1244
|
const filePath = sessionIndexPath(sessionDir);
|
|
647
1245
|
try {
|
|
648
1246
|
const payload = await fs$1.readFile(filePath, "utf8");
|
|
649
|
-
const record = asRecord$
|
|
1247
|
+
const record = asRecord$3(JSON.parse(payload));
|
|
650
1248
|
if (!record || record.schema !== SESSION_INDEX_SCHEMA || !Array.isArray(record.files)) return;
|
|
651
1249
|
const files = record.files.filter((entry) => typeof entry === "string");
|
|
652
1250
|
if (files.length !== record.files.length || !Array.isArray(record.entries)) return;
|
|
@@ -1076,6 +1674,18 @@ function selected(optionId) {
|
|
|
1076
1674
|
function cancelled() {
|
|
1077
1675
|
return { outcome: { outcome: "cancelled" } };
|
|
1078
1676
|
}
|
|
1677
|
+
function withEscalationMetadata(response, event) {
|
|
1678
|
+
return {
|
|
1679
|
+
...response,
|
|
1680
|
+
_meta: {
|
|
1681
|
+
...response._meta,
|
|
1682
|
+
acpx: {
|
|
1683
|
+
...response._meta?.acpx && typeof response._meta.acpx === "object" && !Array.isArray(response._meta.acpx) ? response._meta.acpx : {},
|
|
1684
|
+
permissionEscalation: event
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1079
1689
|
function pickOption(options, kinds) {
|
|
1080
1690
|
for (const kind of kinds) {
|
|
1081
1691
|
const match = options.find((option) => option.kind === kind);
|
|
@@ -1107,32 +1717,149 @@ async function promptForToolPermission(params) {
|
|
|
1107
1717
|
function canPromptForPermission$1() {
|
|
1108
1718
|
return process.stdin.isTTY && process.stderr.isTTY;
|
|
1109
1719
|
}
|
|
1720
|
+
function readStringProperty(value, keys) {
|
|
1721
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
1722
|
+
const record = value;
|
|
1723
|
+
for (const key of keys) {
|
|
1724
|
+
const entry = record[key];
|
|
1725
|
+
if (typeof entry === "string" && entry.trim().length > 0) return entry.trim();
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
function readToolName(params) {
|
|
1729
|
+
const rawInputName = readStringProperty(params.toolCall.rawInput, [
|
|
1730
|
+
"name",
|
|
1731
|
+
"tool",
|
|
1732
|
+
"toolName"
|
|
1733
|
+
]);
|
|
1734
|
+
if (rawInputName) return rawInputName;
|
|
1735
|
+
const head = (params.toolCall.title?.trim())?.split(/[:\s]/, 1)[0]?.trim();
|
|
1736
|
+
return head && head.length > 0 ? head : void 0;
|
|
1737
|
+
}
|
|
1738
|
+
function normalizeMatcher(value) {
|
|
1739
|
+
return value.trim().toLowerCase();
|
|
1740
|
+
}
|
|
1741
|
+
function permissionMatchTokens(params) {
|
|
1742
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
1743
|
+
const kind = inferToolKind(params);
|
|
1744
|
+
const rawKind = params.toolCall.kind;
|
|
1745
|
+
const title = params.toolCall.title?.trim();
|
|
1746
|
+
const toolName = readToolName(params);
|
|
1747
|
+
for (const value of [
|
|
1748
|
+
kind,
|
|
1749
|
+
rawKind,
|
|
1750
|
+
title,
|
|
1751
|
+
toolName
|
|
1752
|
+
]) if (typeof value === "string" && value.trim().length > 0) tokens.add(normalizeMatcher(value));
|
|
1753
|
+
if (title) {
|
|
1754
|
+
const head = title.split(/[:\s]/, 1)[0]?.trim();
|
|
1755
|
+
if (head) tokens.add(normalizeMatcher(head));
|
|
1756
|
+
}
|
|
1757
|
+
return [...tokens];
|
|
1758
|
+
}
|
|
1759
|
+
function findPolicyRule(rules, params) {
|
|
1760
|
+
if (!rules || rules.length === 0) return;
|
|
1761
|
+
const tokens = permissionMatchTokens(params);
|
|
1762
|
+
for (const rule of rules) {
|
|
1763
|
+
const normalized = normalizeMatcher(rule);
|
|
1764
|
+
if (normalized === "*" || tokens.includes(normalized)) return rule;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
function matchPermissionPolicy(params, policy) {
|
|
1768
|
+
if (!policy) return;
|
|
1769
|
+
const denyRule = findPolicyRule(policy.autoDeny, params);
|
|
1770
|
+
if (denyRule) return {
|
|
1771
|
+
action: "deny",
|
|
1772
|
+
matchedRule: denyRule
|
|
1773
|
+
};
|
|
1774
|
+
const approveRule = findPolicyRule(policy.autoApprove, params);
|
|
1775
|
+
if (approveRule) return {
|
|
1776
|
+
action: "approve",
|
|
1777
|
+
matchedRule: approveRule
|
|
1778
|
+
};
|
|
1779
|
+
const escalateRule = findPolicyRule(policy.escalate, params);
|
|
1780
|
+
if (escalateRule) return {
|
|
1781
|
+
action: "escalate",
|
|
1782
|
+
matchedRule: escalateRule
|
|
1783
|
+
};
|
|
1784
|
+
return policy.defaultAction ? { action: policy.defaultAction } : void 0;
|
|
1785
|
+
}
|
|
1786
|
+
function buildEscalationEvent(params, matchedRule) {
|
|
1787
|
+
const toolKind = inferToolKind(params);
|
|
1788
|
+
const toolTitle = params.toolCall.title?.trim() || "tool";
|
|
1789
|
+
const toolName = readToolName(params);
|
|
1790
|
+
return {
|
|
1791
|
+
type: "permission_escalation",
|
|
1792
|
+
sessionId: params.sessionId,
|
|
1793
|
+
toolCallId: params.toolCall.toolCallId,
|
|
1794
|
+
...toolName ? { toolName } : {},
|
|
1795
|
+
toolTitle,
|
|
1796
|
+
...params.toolCall.rawInput !== void 0 ? { toolInput: params.toolCall.rawInput } : {},
|
|
1797
|
+
...toolKind ? { toolKind } : {},
|
|
1798
|
+
action: "escalate",
|
|
1799
|
+
...matchedRule ? { matchedRule } : {},
|
|
1800
|
+
message: `Permission escalation required for ${toolTitle}`,
|
|
1801
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1110
1804
|
function permissionModeSatisfies(actual, required) {
|
|
1111
1805
|
return PERMISSION_MODE_RANK[actual] >= PERMISSION_MODE_RANK[required];
|
|
1112
1806
|
}
|
|
1113
|
-
async function
|
|
1807
|
+
async function resolvePermissionRequestWithDetails(params, mode, nonInteractivePolicy = "deny", policy) {
|
|
1114
1808
|
const options = params.options ?? [];
|
|
1115
|
-
if (options.length === 0) return cancelled();
|
|
1809
|
+
if (options.length === 0) return { response: cancelled() };
|
|
1116
1810
|
const allowOption = pickOption(options, ["allow_once", "allow_always"]);
|
|
1117
1811
|
const rejectOption = pickOption(options, ["reject_once", "reject_always"]);
|
|
1812
|
+
const policyMatch = matchPermissionPolicy(params, policy);
|
|
1813
|
+
if (policyMatch?.action === "approve") {
|
|
1814
|
+
if (allowOption) return { response: selected(allowOption.optionId) };
|
|
1815
|
+
return { response: selected(options[0].optionId) };
|
|
1816
|
+
}
|
|
1817
|
+
if (policyMatch?.action === "deny") {
|
|
1818
|
+
if (rejectOption) return { response: selected(rejectOption.optionId) };
|
|
1819
|
+
return { response: cancelled() };
|
|
1820
|
+
}
|
|
1821
|
+
if (policyMatch?.action === "escalate") {
|
|
1822
|
+
if (canPromptForPermission$1()) {
|
|
1823
|
+
const approved = await promptForToolPermission(params);
|
|
1824
|
+
if (approved && allowOption) return { response: selected(allowOption.optionId) };
|
|
1825
|
+
if (!approved && rejectOption) return { response: selected(rejectOption.optionId) };
|
|
1826
|
+
return { response: cancelled() };
|
|
1827
|
+
}
|
|
1828
|
+
const escalation = buildEscalationEvent(params, policyMatch.matchedRule);
|
|
1829
|
+
return {
|
|
1830
|
+
response: withEscalationMetadata(rejectOption ? selected(rejectOption.optionId) : cancelled(), escalation),
|
|
1831
|
+
escalation
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1118
1834
|
if (mode === "approve-all") {
|
|
1119
|
-
if (allowOption) return selected(allowOption.optionId);
|
|
1120
|
-
return selected(options[0].optionId);
|
|
1835
|
+
if (allowOption) return { response: selected(allowOption.optionId) };
|
|
1836
|
+
return { response: selected(options[0].optionId) };
|
|
1121
1837
|
}
|
|
1122
1838
|
if (mode === "deny-all") {
|
|
1123
|
-
if (rejectOption) return selected(rejectOption.optionId);
|
|
1124
|
-
return cancelled();
|
|
1839
|
+
if (rejectOption) return { response: selected(rejectOption.optionId) };
|
|
1840
|
+
return { response: cancelled() };
|
|
1125
1841
|
}
|
|
1126
|
-
if (isAutoApprovedReadKind(inferToolKind(params)) && allowOption) return selected(allowOption.optionId);
|
|
1842
|
+
if (isAutoApprovedReadKind(inferToolKind(params)) && allowOption) return { response: selected(allowOption.optionId) };
|
|
1127
1843
|
if (!canPromptForPermission$1()) {
|
|
1128
1844
|
if (nonInteractivePolicy === "fail") throw new PermissionPromptUnavailableError();
|
|
1129
|
-
if (rejectOption) return selected(rejectOption.optionId);
|
|
1130
|
-
return cancelled();
|
|
1845
|
+
if (rejectOption) return { response: selected(rejectOption.optionId) };
|
|
1846
|
+
return { response: cancelled() };
|
|
1131
1847
|
}
|
|
1132
1848
|
const approved = await promptForToolPermission(params);
|
|
1133
|
-
if (approved && allowOption) return selected(allowOption.optionId);
|
|
1134
|
-
if (!approved && rejectOption) return selected(rejectOption.optionId);
|
|
1135
|
-
return cancelled();
|
|
1849
|
+
if (approved && allowOption) return { response: selected(allowOption.optionId) };
|
|
1850
|
+
if (!approved && rejectOption) return { response: selected(rejectOption.optionId) };
|
|
1851
|
+
return { response: cancelled() };
|
|
1852
|
+
}
|
|
1853
|
+
const DECISION_FALLBACK_ORDER = {
|
|
1854
|
+
allow_once: ["allow_once", "allow_always"],
|
|
1855
|
+
allow_always: ["allow_always", "allow_once"],
|
|
1856
|
+
reject_once: ["reject_once", "reject_always"],
|
|
1857
|
+
reject_always: ["reject_always", "reject_once"]
|
|
1858
|
+
};
|
|
1859
|
+
function decisionToResponse(params, decision) {
|
|
1860
|
+
if (decision.outcome === "cancel") return cancelled();
|
|
1861
|
+
const matched = pickOption(params.options ?? [], DECISION_FALLBACK_ORDER[decision.outcome]);
|
|
1862
|
+
return matched ? selected(matched.optionId) : cancelled();
|
|
1136
1863
|
}
|
|
1137
1864
|
function classifyPermissionDecision(params, response) {
|
|
1138
1865
|
if (response.outcome.outcome !== "selected") return "cancelled";
|
|
@@ -1176,6 +1903,30 @@ function buildSpawnCommandOptions(command, options, platform = process.platform,
|
|
|
1176
1903
|
shell: true
|
|
1177
1904
|
};
|
|
1178
1905
|
}
|
|
1906
|
+
function buildTerminalSpawnCommand(command, args) {
|
|
1907
|
+
return {
|
|
1908
|
+
command,
|
|
1909
|
+
args: args ?? [],
|
|
1910
|
+
killProcessGroup: false
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
function buildTerminalShellSpawnCommand(command, platform = process.platform) {
|
|
1914
|
+
if (platform === "win32") return {
|
|
1915
|
+
command: "cmd.exe",
|
|
1916
|
+
args: [
|
|
1917
|
+
"/d",
|
|
1918
|
+
"/s",
|
|
1919
|
+
"/c",
|
|
1920
|
+
command
|
|
1921
|
+
],
|
|
1922
|
+
killProcessGroup: true
|
|
1923
|
+
};
|
|
1924
|
+
return {
|
|
1925
|
+
command: "/bin/sh",
|
|
1926
|
+
args: ["-c", command],
|
|
1927
|
+
killProcessGroup: true
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1179
1930
|
//#endregion
|
|
1180
1931
|
//#region src/acp/client-process.ts
|
|
1181
1932
|
const execFileAsync = promisify(execFile);
|
|
@@ -1290,9 +2041,10 @@ function isWsl(options) {
|
|
|
1290
2041
|
if ((options.platform ?? process.platform) !== "linux") return false;
|
|
1291
2042
|
return (options.existsSync ?? fs.existsSync)("/proc/sys/fs/binfmt_misc/WSLInterop");
|
|
1292
2043
|
}
|
|
2044
|
+
const WINDOWS_EXECUTABLE_EXTENSION_RE = /\.(?:exe|cmd|bat)$/u;
|
|
1293
2045
|
function isWindowsExecutableCommand(command) {
|
|
1294
|
-
const normalized = command.
|
|
1295
|
-
return
|
|
2046
|
+
const normalized = command.toLowerCase();
|
|
2047
|
+
return WINDOWS_EXECUTABLE_EXTENSION_RE.test(normalized);
|
|
1296
2048
|
}
|
|
1297
2049
|
async function runWslpath(cwd) {
|
|
1298
2050
|
const { stdout } = await execFileAsync("wslpath", ["-w", cwd], { encoding: "utf8" });
|
|
@@ -1490,6 +2242,13 @@ function buildClaudeCodeOptionsMeta(options) {
|
|
|
1490
2242
|
if (Object.keys(meta).length === 0) return;
|
|
1491
2243
|
return meta;
|
|
1492
2244
|
}
|
|
2245
|
+
function resolveClaudeCodeExecutable(platform = process.platform, env = process.env) {
|
|
2246
|
+
if (platform !== "win32") return;
|
|
2247
|
+
if (readWindowsEnvValue(env, "CLAUDE_CODE_EXECUTABLE")) return;
|
|
2248
|
+
const resolved = resolveWindowsCommand("claude", env);
|
|
2249
|
+
if (!resolved) return;
|
|
2250
|
+
return path.resolve(resolved);
|
|
2251
|
+
}
|
|
1493
2252
|
//#endregion
|
|
1494
2253
|
//#region src/acp/auth-env.ts
|
|
1495
2254
|
const AUTH_ENV_PREFIX = "ACPX_AUTH_";
|
|
@@ -1556,6 +2315,71 @@ function buildAgentSpawnOptions(cwd, authCredentials) {
|
|
|
1556
2315
|
};
|
|
1557
2316
|
}
|
|
1558
2317
|
//#endregion
|
|
2318
|
+
//#region src/acp/jsonrpc.ts
|
|
2319
|
+
function asRecord$2(value) {
|
|
2320
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
2321
|
+
return value;
|
|
2322
|
+
}
|
|
2323
|
+
function hasValidId(value) {
|
|
2324
|
+
return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
2325
|
+
}
|
|
2326
|
+
function isErrorObject(value) {
|
|
2327
|
+
const record = asRecord$2(value);
|
|
2328
|
+
return !!record && typeof record.code === "number" && Number.isFinite(record.code) && typeof record.message === "string";
|
|
2329
|
+
}
|
|
2330
|
+
function hasResultOrError(value) {
|
|
2331
|
+
const hasResult = Object.hasOwn(value, "result");
|
|
2332
|
+
const hasError = Object.hasOwn(value, "error");
|
|
2333
|
+
if (hasResult && hasError) return false;
|
|
2334
|
+
if (!hasResult && !hasError) return false;
|
|
2335
|
+
if (hasError && !isErrorObject(value.error)) return false;
|
|
2336
|
+
return true;
|
|
2337
|
+
}
|
|
2338
|
+
function isAcpJsonRpcMessage(value) {
|
|
2339
|
+
const record = asRecord$2(value);
|
|
2340
|
+
if (!record || record.jsonrpc !== "2.0") return false;
|
|
2341
|
+
const hasMethod = typeof record.method === "string" && record.method.length > 0;
|
|
2342
|
+
const hasId = Object.hasOwn(record, "id");
|
|
2343
|
+
if (hasMethod && !hasId) return true;
|
|
2344
|
+
if (hasMethod && hasId) return hasValidId(record.id);
|
|
2345
|
+
if (!hasMethod && hasId) {
|
|
2346
|
+
if (!hasValidId(record.id)) return false;
|
|
2347
|
+
return hasResultOrError(record);
|
|
2348
|
+
}
|
|
2349
|
+
return false;
|
|
2350
|
+
}
|
|
2351
|
+
function isJsonRpcNotification(message) {
|
|
2352
|
+
return Object.hasOwn(message, "method") && typeof message.method === "string" && !Object.hasOwn(message, "id");
|
|
2353
|
+
}
|
|
2354
|
+
function isSessionUpdateNotification(message) {
|
|
2355
|
+
return isJsonRpcNotification(message) && message.method === "session/update";
|
|
2356
|
+
}
|
|
2357
|
+
function extractSessionUpdateNotification(message) {
|
|
2358
|
+
if (!isSessionUpdateNotification(message)) return;
|
|
2359
|
+
const params = asRecord$2(message.params);
|
|
2360
|
+
if (!params) return;
|
|
2361
|
+
const sessionId = typeof params.sessionId === "string" ? params.sessionId : null;
|
|
2362
|
+
if (!sessionId) return;
|
|
2363
|
+
const update = asRecord$2(params.update);
|
|
2364
|
+
if (!update || typeof update.sessionUpdate !== "string") return;
|
|
2365
|
+
return {
|
|
2366
|
+
sessionId,
|
|
2367
|
+
update
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
function parsePromptStopReason(message) {
|
|
2371
|
+
if (!Object.hasOwn(message, "id") || !Object.hasOwn(message, "result")) return;
|
|
2372
|
+
const record = asRecord$2(message.result);
|
|
2373
|
+
if (!record) return;
|
|
2374
|
+
return typeof record.stopReason === "string" ? record.stopReason : void 0;
|
|
2375
|
+
}
|
|
2376
|
+
function parseJsonRpcErrorMessage(message) {
|
|
2377
|
+
if (!Object.hasOwn(message, "error")) return;
|
|
2378
|
+
const errorRecord = asRecord$2(message.error);
|
|
2379
|
+
if (!errorRecord || typeof errorRecord.message !== "string") return;
|
|
2380
|
+
return errorRecord.message;
|
|
2381
|
+
}
|
|
2382
|
+
//#endregion
|
|
1559
2383
|
//#region src/acp/session-control-errors.ts
|
|
1560
2384
|
const SESSION_CONTROL_UNSUPPORTED_ACP_CODES = new Set([-32601, -32602]);
|
|
1561
2385
|
function asRecord$1(value) {
|
|
@@ -1679,14 +2503,15 @@ var TerminalManager = class {
|
|
|
1679
2503
|
try {
|
|
1680
2504
|
if (!await this.isExecuteApproved(commandLine)) throw new PermissionDeniedError("Permission denied for terminal/create");
|
|
1681
2505
|
const outputByteLimit = Math.max(0, Math.round(params.outputByteLimit ?? DEFAULT_TERMINAL_OUTPUT_LIMIT_BYTES));
|
|
1682
|
-
const proc
|
|
1683
|
-
await waitForSpawn(proc);
|
|
2506
|
+
const { proc, spawnCommand } = await spawnTerminalProcess(params, this.cwd);
|
|
1684
2507
|
let resolveExit = () => {};
|
|
1685
2508
|
const exitPromise = new Promise((resolve) => {
|
|
1686
2509
|
resolveExit = resolve;
|
|
1687
2510
|
});
|
|
1688
2511
|
const terminal = {
|
|
1689
2512
|
process: proc,
|
|
2513
|
+
killProcessGroup: spawnCommand.killProcessGroup,
|
|
2514
|
+
descendantPids: /* @__PURE__ */ new Set(),
|
|
1690
2515
|
output: Buffer.alloc(0),
|
|
1691
2516
|
truncated: false,
|
|
1692
2517
|
outputByteLimit,
|
|
@@ -1709,10 +2534,14 @@ var TerminalManager = class {
|
|
|
1709
2534
|
proc.once("exit", (exitCode, signal) => {
|
|
1710
2535
|
terminal.exitCode = exitCode;
|
|
1711
2536
|
terminal.signal = signal;
|
|
1712
|
-
terminal.
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
2537
|
+
terminal.processGroupSnapshotPromise = rememberProcessGroupPids(terminal);
|
|
2538
|
+
(async () => {
|
|
2539
|
+
await terminal.processGroupSnapshotPromise;
|
|
2540
|
+
terminal.resolveExit({
|
|
2541
|
+
exitCode: exitCode ?? null,
|
|
2542
|
+
signal: signal ?? null
|
|
2543
|
+
});
|
|
2544
|
+
})();
|
|
1716
2545
|
});
|
|
1717
2546
|
const terminalId = randomUUID();
|
|
1718
2547
|
this.terminals.set(terminalId, terminal);
|
|
@@ -1864,21 +2693,284 @@ var TerminalManager = class {
|
|
|
1864
2693
|
return terminal.exitCode === void 0 && terminal.signal === void 0;
|
|
1865
2694
|
}
|
|
1866
2695
|
async killProcess(terminal) {
|
|
1867
|
-
if (!this.isRunning(terminal)) return;
|
|
2696
|
+
if (!this.isRunning(terminal) && !terminal.killProcessGroup) return;
|
|
1868
2697
|
try {
|
|
1869
|
-
|
|
2698
|
+
await this.signalProcess(terminal, "SIGTERM");
|
|
1870
2699
|
} catch {
|
|
1871
2700
|
return;
|
|
1872
2701
|
}
|
|
1873
|
-
if (await
|
|
2702
|
+
if (await this.waitForCleanupAfterSignal(terminal) && !terminal.killProcessGroup) return;
|
|
1874
2703
|
try {
|
|
1875
|
-
|
|
2704
|
+
await this.signalProcess(terminal, "SIGKILL");
|
|
1876
2705
|
} catch {
|
|
1877
2706
|
return;
|
|
1878
2707
|
}
|
|
1879
|
-
await
|
|
2708
|
+
await this.waitForCleanupAfterSignal(terminal);
|
|
2709
|
+
}
|
|
2710
|
+
async signalProcess(terminal, signal) {
|
|
2711
|
+
const pid = terminal.process.pid;
|
|
2712
|
+
if (terminal.killProcessGroup && pid && process.platform === "win32") {
|
|
2713
|
+
if (!this.isRunning(terminal)) await terminal.processGroupSnapshotPromise?.catch(() => {});
|
|
2714
|
+
for (const descendantPid of await listDescendantPids(pid)) terminal.descendantPids.add(descendantPid);
|
|
2715
|
+
if (this.isRunning(terminal)) {
|
|
2716
|
+
await killWindowsProcessTree(pid, signal);
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
for (const descendantPid of terminal.descendantPids) await killWindowsProcessTree(descendantPid, signal);
|
|
2720
|
+
return;
|
|
2721
|
+
}
|
|
2722
|
+
if (terminal.killProcessGroup && pid) {
|
|
2723
|
+
if (!this.isRunning(terminal)) await terminal.processGroupSnapshotPromise?.catch(() => {});
|
|
2724
|
+
for (const descendantPid of await listDescendantPids(pid)) terminal.descendantPids.add(descendantPid);
|
|
2725
|
+
if (process.platform !== "win32" && hasLiveProcessGroup(pid)) {
|
|
2726
|
+
sendSignal(-pid, signal);
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
for (const descendantPid of terminal.descendantPids) sendSignal(descendantPid, signal);
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
terminal.process.kill(signal);
|
|
2733
|
+
}
|
|
2734
|
+
async waitForCleanupAfterSignal(terminal) {
|
|
2735
|
+
return await Promise.race([this.waitForTerminalAndTrackedDescendants(terminal).then(() => true), waitMs(this.killGraceMs).then(() => false)]);
|
|
2736
|
+
}
|
|
2737
|
+
async waitForTerminalAndTrackedDescendants(terminal) {
|
|
2738
|
+
await terminal.exitPromise;
|
|
2739
|
+
while (hasLiveTerminalProcessGroup(terminal)) await waitMs(25);
|
|
2740
|
+
while (hasLivePid(terminal.descendantPids)) await waitMs(25);
|
|
1880
2741
|
}
|
|
1881
2742
|
};
|
|
2743
|
+
async function spawnTerminalProcess(params, defaultCwd) {
|
|
2744
|
+
const directCommand = buildTerminalSpawnCommand(params.command, params.args);
|
|
2745
|
+
try {
|
|
2746
|
+
return {
|
|
2747
|
+
proc: await spawnAndWait(directCommand, params, defaultCwd),
|
|
2748
|
+
spawnCommand: directCommand
|
|
2749
|
+
};
|
|
2750
|
+
} catch (error) {
|
|
2751
|
+
const fallbackCommand = params.args === void 0 && isNotFoundSpawnError(error) ? buildTerminalFallbackSpawnCommand(params.command, params.cwd ?? defaultCwd) : void 0;
|
|
2752
|
+
if (!fallbackCommand) throw error;
|
|
2753
|
+
return {
|
|
2754
|
+
proc: await spawnAndWait(fallbackCommand, params, defaultCwd),
|
|
2755
|
+
spawnCommand: fallbackCommand
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
async function spawnAndWait(spawnCommand, params, defaultCwd) {
|
|
2760
|
+
const spawnOptions = buildTerminalSpawnOptions(spawnCommand.command, params.cwd ?? defaultCwd, params.env);
|
|
2761
|
+
if (spawnCommand.killProcessGroup) spawnOptions.detached = true;
|
|
2762
|
+
const proc = spawn(spawnCommand.command, spawnCommand.args, spawnOptions);
|
|
2763
|
+
await waitForSpawn(proc);
|
|
2764
|
+
return proc;
|
|
2765
|
+
}
|
|
2766
|
+
function isNotFoundSpawnError(error) {
|
|
2767
|
+
return error instanceof Error && error.code === "ENOENT";
|
|
2768
|
+
}
|
|
2769
|
+
function buildTerminalFallbackSpawnCommand(command, cwd, platform = process.platform) {
|
|
2770
|
+
if (commandPathExists(command, cwd)) return;
|
|
2771
|
+
if (platform === "win32") return hasWindowsShellSyntax(command) || /\s/u.test(command) ? buildTerminalShellSpawnCommand(command, platform) : void 0;
|
|
2772
|
+
if (hasShellSyntax(command) || /\s/u.test(command)) return buildTerminalShellSpawnCommand(command, platform);
|
|
2773
|
+
}
|
|
2774
|
+
function hasShellSyntax(command) {
|
|
2775
|
+
return /[|&;<>()>$`*?[\]{}'"\\\r\n]/u.test(command);
|
|
2776
|
+
}
|
|
2777
|
+
function hasWindowsShellSyntax(command) {
|
|
2778
|
+
return /[|&;<>()>$`*?[\]{}'"\r\n]/u.test(command);
|
|
2779
|
+
}
|
|
2780
|
+
function commandPathExists(command, cwd) {
|
|
2781
|
+
if (!/[\\/]/u.test(command)) return false;
|
|
2782
|
+
const resolvedPath = path.isAbsolute(command) ? command : path.resolve(cwd, command);
|
|
2783
|
+
return fs.existsSync(resolvedPath);
|
|
2784
|
+
}
|
|
2785
|
+
async function listDescendantPids(rootPid) {
|
|
2786
|
+
let output;
|
|
2787
|
+
try {
|
|
2788
|
+
output = await runProcessListCommand();
|
|
2789
|
+
} catch {
|
|
2790
|
+
return [];
|
|
2791
|
+
}
|
|
2792
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
2793
|
+
for (const line of output.split("\n")) {
|
|
2794
|
+
const match = line.trim().match(/^(\d+)\s+(\d+)$/);
|
|
2795
|
+
if (!match) continue;
|
|
2796
|
+
const pid = Number(match[1]);
|
|
2797
|
+
const parentPid = Number(match[2]);
|
|
2798
|
+
if (!Number.isInteger(pid) || !Number.isInteger(parentPid) || pid <= 0 || parentPid <= 0) continue;
|
|
2799
|
+
const children = childrenByParent.get(parentPid);
|
|
2800
|
+
if (children) children.push(pid);
|
|
2801
|
+
else childrenByParent.set(parentPid, [pid]);
|
|
2802
|
+
}
|
|
2803
|
+
const descendants = [];
|
|
2804
|
+
const queue = [...childrenByParent.get(rootPid) ?? []];
|
|
2805
|
+
for (let index = 0; index < queue.length; index += 1) {
|
|
2806
|
+
const pid = queue[index];
|
|
2807
|
+
descendants.push(pid);
|
|
2808
|
+
queue.push(...childrenByParent.get(pid) ?? []);
|
|
2809
|
+
}
|
|
2810
|
+
return descendants;
|
|
2811
|
+
}
|
|
2812
|
+
async function runProcessListCommand() {
|
|
2813
|
+
if (process.platform === "win32") return await runWindowsProcessListCommand();
|
|
2814
|
+
return await new Promise((resolve, reject) => {
|
|
2815
|
+
const child = spawn("ps", ["-eo", "pid=,ppid="], { stdio: [
|
|
2816
|
+
"ignore",
|
|
2817
|
+
"pipe",
|
|
2818
|
+
"pipe"
|
|
2819
|
+
] });
|
|
2820
|
+
let stdout = "";
|
|
2821
|
+
let stderr = "";
|
|
2822
|
+
child.stdout.setEncoding("utf8");
|
|
2823
|
+
child.stderr.setEncoding("utf8");
|
|
2824
|
+
child.stdout.on("data", (chunk) => {
|
|
2825
|
+
stdout += chunk;
|
|
2826
|
+
});
|
|
2827
|
+
child.stderr.on("data", (chunk) => {
|
|
2828
|
+
stderr += chunk;
|
|
2829
|
+
});
|
|
2830
|
+
child.once("error", reject);
|
|
2831
|
+
child.once("close", (code, signal) => {
|
|
2832
|
+
if (code === 0) {
|
|
2833
|
+
resolve(stdout);
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2836
|
+
reject(/* @__PURE__ */ new Error(`ps exited with code ${code ?? "null"} signal ${signal ?? "null"}: ${stderr}`));
|
|
2837
|
+
});
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
async function rememberProcessGroupPids(terminal) {
|
|
2841
|
+
const processGroupId = terminal.process.pid;
|
|
2842
|
+
if (!terminal.killProcessGroup || !processGroupId) return;
|
|
2843
|
+
if (process.platform === "win32") {
|
|
2844
|
+
for (const pid of await listDescendantPids(processGroupId)) terminal.descendantPids.add(pid);
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
for (const pid of await listProcessGroupPids(processGroupId)) if (pid !== processGroupId) terminal.descendantPids.add(pid);
|
|
2848
|
+
}
|
|
2849
|
+
async function listProcessGroupPids(processGroupId) {
|
|
2850
|
+
let output;
|
|
2851
|
+
try {
|
|
2852
|
+
output = await runProcessGroupListCommand();
|
|
2853
|
+
} catch {
|
|
2854
|
+
return [];
|
|
2855
|
+
}
|
|
2856
|
+
const pids = [];
|
|
2857
|
+
for (const line of output.split("\n")) {
|
|
2858
|
+
const match = line.trim().match(/^(\d+)\s+(\d+)$/);
|
|
2859
|
+
if (!match) continue;
|
|
2860
|
+
const pid = Number(match[1]);
|
|
2861
|
+
const pgid = Number(match[2]);
|
|
2862
|
+
if (Number.isInteger(pid) && Number.isInteger(pgid) && pid > 0 && pgid === processGroupId) pids.push(pid);
|
|
2863
|
+
}
|
|
2864
|
+
return pids;
|
|
2865
|
+
}
|
|
2866
|
+
async function runProcessGroupListCommand() {
|
|
2867
|
+
return await new Promise((resolve, reject) => {
|
|
2868
|
+
const child = spawn("ps", ["-eo", "pid=,pgid="], { stdio: [
|
|
2869
|
+
"ignore",
|
|
2870
|
+
"pipe",
|
|
2871
|
+
"pipe"
|
|
2872
|
+
] });
|
|
2873
|
+
let stdout = "";
|
|
2874
|
+
let stderr = "";
|
|
2875
|
+
child.stdout.setEncoding("utf8");
|
|
2876
|
+
child.stderr.setEncoding("utf8");
|
|
2877
|
+
child.stdout.on("data", (chunk) => {
|
|
2878
|
+
stdout += chunk;
|
|
2879
|
+
});
|
|
2880
|
+
child.stderr.on("data", (chunk) => {
|
|
2881
|
+
stderr += chunk;
|
|
2882
|
+
});
|
|
2883
|
+
child.once("error", reject);
|
|
2884
|
+
child.once("close", (code, signal) => {
|
|
2885
|
+
if (code === 0) {
|
|
2886
|
+
resolve(stdout);
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
reject(/* @__PURE__ */ new Error(`ps exited with code ${code ?? "null"} signal ${signal ?? "null"}: ${stderr}`));
|
|
2890
|
+
});
|
|
2891
|
+
});
|
|
2892
|
+
}
|
|
2893
|
+
async function runWindowsProcessListCommand() {
|
|
2894
|
+
return await new Promise((resolve, reject) => {
|
|
2895
|
+
const child = spawn("powershell.exe", [
|
|
2896
|
+
"-NoProfile",
|
|
2897
|
+
"-NonInteractive",
|
|
2898
|
+
"-Command",
|
|
2899
|
+
["Get-CimInstance Win32_Process |", "ForEach-Object { \"$($_.ProcessId) $($_.ParentProcessId)\" }"].join(" ")
|
|
2900
|
+
], {
|
|
2901
|
+
stdio: [
|
|
2902
|
+
"ignore",
|
|
2903
|
+
"pipe",
|
|
2904
|
+
"pipe"
|
|
2905
|
+
],
|
|
2906
|
+
windowsHide: true
|
|
2907
|
+
});
|
|
2908
|
+
let stdout = "";
|
|
2909
|
+
let stderr = "";
|
|
2910
|
+
child.stdout.setEncoding("utf8");
|
|
2911
|
+
child.stderr.setEncoding("utf8");
|
|
2912
|
+
child.stdout.on("data", (chunk) => {
|
|
2913
|
+
stdout += chunk;
|
|
2914
|
+
});
|
|
2915
|
+
child.stderr.on("data", (chunk) => {
|
|
2916
|
+
stderr += chunk;
|
|
2917
|
+
});
|
|
2918
|
+
child.once("error", reject);
|
|
2919
|
+
child.once("close", (code, signal) => {
|
|
2920
|
+
if (code === 0) {
|
|
2921
|
+
resolve(stdout);
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
reject(/* @__PURE__ */ new Error(`powershell process list exited with code ${code ?? "null"} signal ${signal ?? "null"}: ${stderr}`));
|
|
2925
|
+
});
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
async function killWindowsProcessTree(pid, signal) {
|
|
2929
|
+
const args = [
|
|
2930
|
+
"/pid",
|
|
2931
|
+
String(pid),
|
|
2932
|
+
"/t"
|
|
2933
|
+
];
|
|
2934
|
+
if (signal === "SIGKILL") args.push("/f");
|
|
2935
|
+
await new Promise((resolve) => {
|
|
2936
|
+
const child = spawn("taskkill", args, {
|
|
2937
|
+
stdio: [
|
|
2938
|
+
"ignore",
|
|
2939
|
+
"ignore",
|
|
2940
|
+
"ignore"
|
|
2941
|
+
],
|
|
2942
|
+
windowsHide: true
|
|
2943
|
+
});
|
|
2944
|
+
child.once("error", () => resolve());
|
|
2945
|
+
child.once("close", () => resolve());
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
function sendSignal(pid, signal) {
|
|
2949
|
+
try {
|
|
2950
|
+
process.kill(pid, signal);
|
|
2951
|
+
} catch {}
|
|
2952
|
+
}
|
|
2953
|
+
function hasLiveProcessGroup(processGroupId) {
|
|
2954
|
+
try {
|
|
2955
|
+
process.kill(-processGroupId, 0);
|
|
2956
|
+
return true;
|
|
2957
|
+
} catch {
|
|
2958
|
+
return false;
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
function hasLiveTerminalProcessGroup(terminal) {
|
|
2962
|
+
const pid = terminal.process.pid;
|
|
2963
|
+
return Boolean(terminal.killProcessGroup && pid && process.platform !== "win32" && hasLiveProcessGroup(pid));
|
|
2964
|
+
}
|
|
2965
|
+
function hasLivePid(pids) {
|
|
2966
|
+
for (const pid of pids) try {
|
|
2967
|
+
process.kill(pid, 0);
|
|
2968
|
+
return true;
|
|
2969
|
+
} catch {
|
|
2970
|
+
pids.delete(pid);
|
|
2971
|
+
}
|
|
2972
|
+
return false;
|
|
2973
|
+
}
|
|
1882
2974
|
//#endregion
|
|
1883
2975
|
//#region src/acp/client.ts
|
|
1884
2976
|
const REPLAY_IDLE_MS = 80;
|
|
@@ -1965,6 +3057,7 @@ var AcpClient = class {
|
|
|
1965
3057
|
suppressReplaySessionUpdateMessages = false;
|
|
1966
3058
|
activePrompt;
|
|
1967
3059
|
cancellingSessionIds = /* @__PURE__ */ new Set();
|
|
3060
|
+
permissionAbortControllers = /* @__PURE__ */ new Map();
|
|
1968
3061
|
closing = false;
|
|
1969
3062
|
agentStartedAt;
|
|
1970
3063
|
lastAgentExit;
|
|
@@ -1981,7 +3074,8 @@ var AcpClient = class {
|
|
|
1981
3074
|
onAcpMessage: this.options.onAcpMessage,
|
|
1982
3075
|
onAcpOutputMessage: this.options.onAcpOutputMessage,
|
|
1983
3076
|
onSessionUpdate: this.options.onSessionUpdate,
|
|
1984
|
-
onClientOperation: this.options.onClientOperation
|
|
3077
|
+
onClientOperation: this.options.onClientOperation,
|
|
3078
|
+
onPermissionEscalation: this.options.onPermissionEscalation
|
|
1985
3079
|
};
|
|
1986
3080
|
this.filesystem = new FileSystemHandlers({
|
|
1987
3081
|
cwd: this.options.cwd,
|
|
@@ -2034,6 +3128,7 @@ var AcpClient = class {
|
|
|
2034
3128
|
updateRuntimeOptions(options) {
|
|
2035
3129
|
if (options.permissionMode) this.options.permissionMode = options.permissionMode;
|
|
2036
3130
|
if (options.nonInteractivePermissions !== void 0) this.options.nonInteractivePermissions = options.nonInteractivePermissions;
|
|
3131
|
+
if (Object.prototype.hasOwnProperty.call(options, "permissionPolicy")) this.options.permissionPolicy = options.permissionPolicy;
|
|
2037
3132
|
if (options.terminal !== void 0) this.options.terminal = options.terminal;
|
|
2038
3133
|
if (options.permissionMode || options.nonInteractivePermissions !== void 0) {
|
|
2039
3134
|
this.filesystem.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
|
|
@@ -2064,7 +3159,15 @@ var AcpClient = class {
|
|
|
2064
3159
|
else this.log(`spawning agent: ${spawnCommand} ${args.join(" ")}`);
|
|
2065
3160
|
const geminiAcp = isGeminiAcpCommand(spawnCommand, args);
|
|
2066
3161
|
if (isCopilotAcpCommand(spawnCommand, args)) await ensureCopilotAcpSupport(spawnCommand);
|
|
2067
|
-
const
|
|
3162
|
+
const agentSpawnOptions = buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials);
|
|
3163
|
+
if (isClaudeAcpCommand(spawnCommand, args)) {
|
|
3164
|
+
const claudeExe = resolveClaudeCodeExecutable(process.platform, agentSpawnOptions.env);
|
|
3165
|
+
if (claudeExe) {
|
|
3166
|
+
agentSpawnOptions.env.CLAUDE_CODE_EXECUTABLE = claudeExe;
|
|
3167
|
+
this.log(`resolved system Claude Code executable: ${claudeExe}`);
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
const spawnedChild = spawn(spawnCommand, args, buildSpawnCommandOptions(spawnCommand, agentSpawnOptions));
|
|
2068
3171
|
try {
|
|
2069
3172
|
await waitForSpawn$1(spawnedChild);
|
|
2070
3173
|
} catch (error) {
|
|
@@ -2216,6 +3319,7 @@ var AcpClient = class {
|
|
|
2216
3319
|
return {
|
|
2217
3320
|
sessionId: result.sessionId,
|
|
2218
3321
|
agentSessionId: extractRuntimeSessionId(result._meta),
|
|
3322
|
+
configOptions: result.configOptions ?? void 0,
|
|
2219
3323
|
models: result.models ?? void 0
|
|
2220
3324
|
};
|
|
2221
3325
|
}
|
|
@@ -2245,6 +3349,7 @@ var AcpClient = class {
|
|
|
2245
3349
|
this.loadedSessionId = sessionId;
|
|
2246
3350
|
return {
|
|
2247
3351
|
agentSessionId: extractRuntimeSessionId(response?._meta),
|
|
3352
|
+
configOptions: response?.configOptions ?? void 0,
|
|
2248
3353
|
models: response?.models ?? void 0
|
|
2249
3354
|
};
|
|
2250
3355
|
}
|
|
@@ -2278,6 +3383,7 @@ var AcpClient = class {
|
|
|
2278
3383
|
restoreConsoleError?.();
|
|
2279
3384
|
if (this.activePrompt?.promise === promptPromise) this.activePrompt = void 0;
|
|
2280
3385
|
this.cancellingSessionIds.delete(sessionId);
|
|
3386
|
+
this.abortAndDropPermissionSignal(sessionId);
|
|
2281
3387
|
this.promptPermissionFailures.delete(sessionId);
|
|
2282
3388
|
}
|
|
2283
3389
|
}
|
|
@@ -2323,11 +3429,12 @@ var AcpClient = class {
|
|
|
2323
3429
|
async cancel(sessionId) {
|
|
2324
3430
|
const connection = this.getConnection();
|
|
2325
3431
|
this.cancellingSessionIds.add(sessionId);
|
|
3432
|
+
this.abortAndDropPermissionSignal(sessionId);
|
|
2326
3433
|
await this.runConnectionRequest(() => connection.cancel({ sessionId }));
|
|
2327
3434
|
}
|
|
2328
3435
|
async closeSession(sessionId) {
|
|
2329
3436
|
const connection = this.getConnection();
|
|
2330
|
-
await this.runConnectionRequest(() => connection.
|
|
3437
|
+
await this.runConnectionRequest(() => connection.closeSession({ sessionId }));
|
|
2331
3438
|
if (this.loadedSessionId === sessionId) this.loadedSessionId = void 0;
|
|
2332
3439
|
}
|
|
2333
3440
|
async requestCancelActivePrompt() {
|
|
@@ -2369,6 +3476,8 @@ var AcpClient = class {
|
|
|
2369
3476
|
this.suppressReplaySessionUpdateMessages = false;
|
|
2370
3477
|
this.activePrompt = void 0;
|
|
2371
3478
|
this.cancellingSessionIds.clear();
|
|
3479
|
+
for (const controller of this.permissionAbortControllers.values()) controller.abort();
|
|
3480
|
+
this.permissionAbortControllers.clear();
|
|
2372
3481
|
this.promptPermissionFailures.clear();
|
|
2373
3482
|
this.loadedSessionId = void 0;
|
|
2374
3483
|
this.initResult = void 0;
|
|
@@ -2517,9 +3626,36 @@ var AcpClient = class {
|
|
|
2517
3626
|
}
|
|
2518
3627
|
async handlePermissionRequest(params) {
|
|
2519
3628
|
if (this.cancellingSessionIds.has(params.sessionId)) return { outcome: { outcome: "cancelled" } };
|
|
3629
|
+
if (this.options.onPermissionRequest) {
|
|
3630
|
+
const signal = this.cancellationSignalForSession(params.sessionId);
|
|
3631
|
+
try {
|
|
3632
|
+
const decision = await this.options.onPermissionRequest({
|
|
3633
|
+
sessionId: params.sessionId,
|
|
3634
|
+
raw: params,
|
|
3635
|
+
inferredKind: inferToolKind(params)
|
|
3636
|
+
}, { signal });
|
|
3637
|
+
if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
|
|
3638
|
+
this.recordPermissionDecision("cancelled");
|
|
3639
|
+
return { outcome: { outcome: "cancelled" } };
|
|
3640
|
+
}
|
|
3641
|
+
if (decision) {
|
|
3642
|
+
const response = decisionToResponse(params, decision);
|
|
3643
|
+
this.recordPermissionDecision(classifyPermissionDecision(params, response));
|
|
3644
|
+
return response;
|
|
3645
|
+
}
|
|
3646
|
+
} catch (error) {
|
|
3647
|
+
if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
|
|
3648
|
+
this.recordPermissionDecision("cancelled");
|
|
3649
|
+
return { outcome: { outcome: "cancelled" } };
|
|
3650
|
+
}
|
|
3651
|
+
this.log(`onPermissionRequest threw, falling through to mode-based resolver: ${error instanceof Error ? error.message : String(error)}`);
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
2520
3654
|
let response;
|
|
2521
3655
|
try {
|
|
2522
|
-
|
|
3656
|
+
const result = await resolvePermissionRequestWithDetails(params, this.options.permissionMode, this.options.nonInteractivePermissions ?? "deny", this.options.permissionPolicy);
|
|
3657
|
+
response = result.response;
|
|
3658
|
+
if (result.escalation) this.eventHandlers.onPermissionEscalation?.(result.escalation);
|
|
2523
3659
|
} catch (error) {
|
|
2524
3660
|
if (error instanceof PermissionPromptUnavailableError) {
|
|
2525
3661
|
this.notePromptPermissionFailure(params.sessionId, error);
|
|
@@ -2625,6 +3761,21 @@ var AcpClient = class {
|
|
|
2625
3761
|
async handleReleaseTerminal(params) {
|
|
2626
3762
|
return await this.terminalManager.releaseTerminal(params);
|
|
2627
3763
|
}
|
|
3764
|
+
cancellationSignalForSession(sessionId) {
|
|
3765
|
+
let controller = this.permissionAbortControllers.get(sessionId);
|
|
3766
|
+
if (!controller) {
|
|
3767
|
+
controller = new AbortController();
|
|
3768
|
+
this.permissionAbortControllers.set(sessionId, controller);
|
|
3769
|
+
}
|
|
3770
|
+
return controller.signal;
|
|
3771
|
+
}
|
|
3772
|
+
abortAndDropPermissionSignal(sessionId) {
|
|
3773
|
+
const controller = this.permissionAbortControllers.get(sessionId);
|
|
3774
|
+
if (controller) {
|
|
3775
|
+
controller.abort();
|
|
3776
|
+
this.permissionAbortControllers.delete(sessionId);
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
2628
3779
|
recordPermissionDecision(decision) {
|
|
2629
3780
|
this.permissionStats.requested += 1;
|
|
2630
3781
|
if (decision === "approved") {
|
|
@@ -2686,6 +3837,47 @@ var AcpClient = class {
|
|
|
2686
3837
|
}
|
|
2687
3838
|
};
|
|
2688
3839
|
//#endregion
|
|
3840
|
+
//#region src/runtime/engine/session-options.ts
|
|
3841
|
+
function mergeSessionOptions(preferred, fallback) {
|
|
3842
|
+
const merged = { ...fallback };
|
|
3843
|
+
if (preferred?.model !== void 0) merged.model = preferred.model;
|
|
3844
|
+
if (preferred?.allowedTools !== void 0) merged.allowedTools = preferred.allowedTools;
|
|
3845
|
+
if (preferred?.maxTurns !== void 0) merged.maxTurns = preferred.maxTurns;
|
|
3846
|
+
if (preferred?.systemPrompt !== void 0) merged.systemPrompt = preferred.systemPrompt;
|
|
3847
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
3848
|
+
}
|
|
3849
|
+
function persistSessionOptions(record, options) {
|
|
3850
|
+
const systemPromptOption = options?.systemPrompt;
|
|
3851
|
+
const normalizedSystemPrompt = typeof systemPromptOption === "string" && systemPromptOption.length > 0 ? systemPromptOption : systemPromptOption && typeof systemPromptOption === "object" && typeof systemPromptOption.append === "string" && systemPromptOption.append.length > 0 ? { append: systemPromptOption.append } : void 0;
|
|
3852
|
+
const next = options && {
|
|
3853
|
+
model: typeof options.model === "string" ? options.model : void 0,
|
|
3854
|
+
allowed_tools: Array.isArray(options.allowedTools) ? [...options.allowedTools] : void 0,
|
|
3855
|
+
max_turns: typeof options.maxTurns === "number" ? options.maxTurns : void 0,
|
|
3856
|
+
system_prompt: normalizedSystemPrompt
|
|
3857
|
+
};
|
|
3858
|
+
if (Boolean(next && (typeof next.model === "string" && next.model.trim().length > 0 || Array.isArray(next.allowed_tools) || typeof next.max_turns === "number" || next.system_prompt !== void 0)) && next) {
|
|
3859
|
+
record.acpx = {
|
|
3860
|
+
...record.acpx,
|
|
3861
|
+
session_options: next
|
|
3862
|
+
};
|
|
3863
|
+
return;
|
|
3864
|
+
}
|
|
3865
|
+
if (!record.acpx) return;
|
|
3866
|
+
delete record.acpx.session_options;
|
|
3867
|
+
}
|
|
3868
|
+
function sessionOptionsFromRecord(record) {
|
|
3869
|
+
const stored = record.acpx?.session_options;
|
|
3870
|
+
if (!stored) return;
|
|
3871
|
+
const sessionOptions = {};
|
|
3872
|
+
if (typeof stored.model === "string" && stored.model.trim().length > 0) sessionOptions.model = stored.model;
|
|
3873
|
+
if (Array.isArray(stored.allowed_tools)) sessionOptions.allowedTools = [...stored.allowed_tools];
|
|
3874
|
+
if (typeof stored.max_turns === "number") sessionOptions.maxTurns = stored.max_turns;
|
|
3875
|
+
const storedSystemPrompt = stored.system_prompt;
|
|
3876
|
+
if (typeof storedSystemPrompt === "string" && storedSystemPrompt.length > 0) sessionOptions.systemPrompt = storedSystemPrompt;
|
|
3877
|
+
else if (storedSystemPrompt && typeof storedSystemPrompt === "object" && typeof storedSystemPrompt.append === "string" && storedSystemPrompt.append.length > 0) sessionOptions.systemPrompt = { append: storedSystemPrompt.append };
|
|
3878
|
+
return Object.keys(sessionOptions).length > 0 ? sessionOptions : void 0;
|
|
3879
|
+
}
|
|
3880
|
+
//#endregion
|
|
2689
3881
|
//#region src/session/conversation-model.ts
|
|
2690
3882
|
const MAX_RUNTIME_MESSAGES = 200;
|
|
2691
3883
|
const MAX_RUNTIME_AGENT_TEXT_CHARS = 8e3;
|
|
@@ -3061,6 +4253,15 @@ function trimConversationForRuntime(conversation) {
|
|
|
3061
4253
|
if (requestUsageEntries.length > MAX_RUNTIME_REQUEST_TOKEN_USAGE) conversation.request_token_usage = Object.fromEntries(requestUsageEntries.slice(-MAX_RUNTIME_REQUEST_TOKEN_USAGE));
|
|
3062
4254
|
}
|
|
3063
4255
|
//#endregion
|
|
4256
|
+
//#region src/session/config-options.ts
|
|
4257
|
+
function applyConfigOptionsToRecord(record, result) {
|
|
4258
|
+
const configOptions = result?.configOptions;
|
|
4259
|
+
if (!configOptions) return;
|
|
4260
|
+
const acpxState = cloneSessionAcpxState(record.acpx) ?? {};
|
|
4261
|
+
acpxState.config_options = structuredClone(configOptions);
|
|
4262
|
+
record.acpx = acpxState;
|
|
4263
|
+
}
|
|
4264
|
+
//#endregion
|
|
3064
4265
|
//#region src/session/mode-preference.ts
|
|
3065
4266
|
function ensureAcpxState(state) {
|
|
3066
4267
|
return state ?? {};
|
|
@@ -3156,6 +4357,22 @@ function assertRequestedModelSupported(params) {
|
|
|
3156
4357
|
if (!new Set(params.models.availableModels.map((model) => model.modelId)).has(params.requestedModel)) throw new RequestedModelUnsupportedError(`Cannot ${params.context === "replay" ? "replay saved model" : "apply --model"} "${params.requestedModel}": the ACP agent did not advertise that model. Available models: ${formatAvailableModelIds(params.models)}.`);
|
|
3157
4358
|
}
|
|
3158
4359
|
//#endregion
|
|
4360
|
+
//#region src/session/model-application.ts
|
|
4361
|
+
async function applyRequestedModelIfAdvertised(params) {
|
|
4362
|
+
const requestedModel = typeof params.requestedModel === "string" ? params.requestedModel.trim() : "";
|
|
4363
|
+
if (!requestedModel) return false;
|
|
4364
|
+
assertRequestedModelSupported({
|
|
4365
|
+
requestedModel,
|
|
4366
|
+
models: params.models,
|
|
4367
|
+
agentCommand: params.agentCommand,
|
|
4368
|
+
context: "apply"
|
|
4369
|
+
});
|
|
4370
|
+
if (!params.models) return false;
|
|
4371
|
+
if (params.models.currentModelId === requestedModel) return true;
|
|
4372
|
+
await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel), params.timeoutMs);
|
|
4373
|
+
return true;
|
|
4374
|
+
}
|
|
4375
|
+
//#endregion
|
|
3159
4376
|
//#region src/runtime/engine/lifecycle.ts
|
|
3160
4377
|
function applyLifecycleSnapshotToRecord(record, snapshot) {
|
|
3161
4378
|
if (!snapshot) return;
|
|
@@ -3296,6 +4513,7 @@ async function connectAndLoadSession(options) {
|
|
|
3296
4513
|
else if (client.supportsLoadSession()) try {
|
|
3297
4514
|
const loadResult = await withTimeout(client.loadSessionWithOptions(record.acpSessionId, record.cwd, { suppressReplayUpdates: true }), options.timeoutMs);
|
|
3298
4515
|
reconcileAgentSessionId(record, loadResult.agentSessionId);
|
|
4516
|
+
applyConfigOptionsToRecord(record, loadResult);
|
|
3299
4517
|
sessionModels = loadResult.models;
|
|
3300
4518
|
resumed = true;
|
|
3301
4519
|
} catch (error) {
|
|
@@ -3310,6 +4528,7 @@ async function connectAndLoadSession(options) {
|
|
|
3310
4528
|
sessionId = createdSession.sessionId;
|
|
3311
4529
|
createdFreshSession = true;
|
|
3312
4530
|
pendingAgentSessionId = createdSession.agentSessionId;
|
|
4531
|
+
applyConfigOptionsToRecord(record, createdSession);
|
|
3313
4532
|
sessionModels = createdSession.models;
|
|
3314
4533
|
}
|
|
3315
4534
|
else {
|
|
@@ -3321,6 +4540,7 @@ async function connectAndLoadSession(options) {
|
|
|
3321
4540
|
sessionId = createdSession.sessionId;
|
|
3322
4541
|
createdFreshSession = true;
|
|
3323
4542
|
pendingAgentSessionId = createdSession.agentSessionId;
|
|
4543
|
+
applyConfigOptionsToRecord(record, createdSession);
|
|
3324
4544
|
sessionModels = createdSession.models;
|
|
3325
4545
|
}
|
|
3326
4546
|
if (createdFreshSession) {
|
|
@@ -3374,28 +4594,6 @@ async function connectAndLoadSession(options) {
|
|
|
3374
4594
|
};
|
|
3375
4595
|
}
|
|
3376
4596
|
//#endregion
|
|
3377
|
-
//#region src/runtime/engine/session-options.ts
|
|
3378
|
-
function mergeSessionOptions(preferred, fallback) {
|
|
3379
|
-
const merged = { ...fallback };
|
|
3380
|
-
if (preferred?.model !== void 0) merged.model = preferred.model;
|
|
3381
|
-
if (preferred?.allowedTools !== void 0) merged.allowedTools = preferred.allowedTools;
|
|
3382
|
-
if (preferred?.maxTurns !== void 0) merged.maxTurns = preferred.maxTurns;
|
|
3383
|
-
if (preferred?.systemPrompt !== void 0) merged.systemPrompt = preferred.systemPrompt;
|
|
3384
|
-
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
3385
|
-
}
|
|
3386
|
-
function sessionOptionsFromRecord(record) {
|
|
3387
|
-
const stored = record.acpx?.session_options;
|
|
3388
|
-
if (!stored) return;
|
|
3389
|
-
const sessionOptions = {};
|
|
3390
|
-
if (typeof stored.model === "string" && stored.model.trim().length > 0) sessionOptions.model = stored.model;
|
|
3391
|
-
if (Array.isArray(stored.allowed_tools)) sessionOptions.allowedTools = [...stored.allowed_tools];
|
|
3392
|
-
if (typeof stored.max_turns === "number") sessionOptions.maxTurns = stored.max_turns;
|
|
3393
|
-
const storedSystemPrompt = stored.system_prompt;
|
|
3394
|
-
if (typeof storedSystemPrompt === "string" && storedSystemPrompt.length > 0) sessionOptions.systemPrompt = storedSystemPrompt;
|
|
3395
|
-
else if (storedSystemPrompt && typeof storedSystemPrompt === "object" && typeof storedSystemPrompt.append === "string" && storedSystemPrompt.append.length > 0) sessionOptions.systemPrompt = { append: storedSystemPrompt.append };
|
|
3396
|
-
return Object.keys(sessionOptions).length > 0 ? sessionOptions : void 0;
|
|
3397
|
-
}
|
|
3398
|
-
//#endregion
|
|
3399
4597
|
//#region src/runtime/engine/connected-session.ts
|
|
3400
4598
|
function createActiveSessionController(params) {
|
|
3401
4599
|
const getActiveSessionId = () => params.getActiveSessionId();
|
|
@@ -3421,6 +4619,7 @@ async function withConnectedSession(options) {
|
|
|
3421
4619
|
mcpServers: options.mcpServers,
|
|
3422
4620
|
permissionMode: options.permissionMode ?? "approve-reads",
|
|
3423
4621
|
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
4622
|
+
onPermissionRequest: options.onPermissionRequest,
|
|
3424
4623
|
authCredentials: options.authCredentials,
|
|
3425
4624
|
authPolicy: options.authPolicy,
|
|
3426
4625
|
terminal: options.terminal,
|
|
@@ -3432,6 +4631,7 @@ async function withConnectedSession(options) {
|
|
|
3432
4631
|
mcpServers: options.mcpServers,
|
|
3433
4632
|
permissionMode: options.permissionMode ?? "approve-reads",
|
|
3434
4633
|
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
4634
|
+
onPermissionRequest: options.onPermissionRequest,
|
|
3435
4635
|
authCredentials: options.authCredentials,
|
|
3436
4636
|
authPolicy: options.authPolicy,
|
|
3437
4637
|
terminal: options.terminal,
|
|
@@ -3532,6 +4732,59 @@ async function runPromptTurn(params) {
|
|
|
3532
4732
|
}
|
|
3533
4733
|
}
|
|
3534
4734
|
//#endregion
|
|
3535
|
-
|
|
4735
|
+
//#region src/session/live-checkpoint.ts
|
|
4736
|
+
const DEFAULT_LIVE_CHECKPOINT_INTERVAL_MS = 500;
|
|
4737
|
+
var LiveSessionCheckpoint = class {
|
|
4738
|
+
save;
|
|
4739
|
+
intervalMs;
|
|
4740
|
+
onError;
|
|
4741
|
+
dirty = false;
|
|
4742
|
+
flushing;
|
|
4743
|
+
timer;
|
|
4744
|
+
constructor(options) {
|
|
4745
|
+
this.save = options.save;
|
|
4746
|
+
this.intervalMs = options.intervalMs ?? DEFAULT_LIVE_CHECKPOINT_INTERVAL_MS;
|
|
4747
|
+
this.onError = options.onError;
|
|
4748
|
+
}
|
|
4749
|
+
request() {
|
|
4750
|
+
this.dirty = true;
|
|
4751
|
+
if (this.timer) return;
|
|
4752
|
+
this.timer = setTimeout(() => {
|
|
4753
|
+
this.timer = void 0;
|
|
4754
|
+
this.flush().catch((error) => {
|
|
4755
|
+
this.onError?.(error);
|
|
4756
|
+
});
|
|
4757
|
+
}, this.intervalMs);
|
|
4758
|
+
this.timer.unref?.();
|
|
4759
|
+
}
|
|
4760
|
+
async checkpoint() {
|
|
4761
|
+
this.dirty = true;
|
|
4762
|
+
await this.flush();
|
|
4763
|
+
}
|
|
4764
|
+
async flush() {
|
|
4765
|
+
if (this.timer) {
|
|
4766
|
+
clearTimeout(this.timer);
|
|
4767
|
+
this.timer = void 0;
|
|
4768
|
+
}
|
|
4769
|
+
if (this.flushing) {
|
|
4770
|
+
await this.flushing;
|
|
4771
|
+
if (!this.dirty) return;
|
|
4772
|
+
}
|
|
4773
|
+
this.flushing = this.flushDirty();
|
|
4774
|
+
try {
|
|
4775
|
+
await this.flushing;
|
|
4776
|
+
} finally {
|
|
4777
|
+
this.flushing = void 0;
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4780
|
+
async flushDirty() {
|
|
4781
|
+
while (this.dirty) {
|
|
4782
|
+
this.dirty = false;
|
|
4783
|
+
await this.save();
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4786
|
+
};
|
|
4787
|
+
//#endregion
|
|
4788
|
+
export { getPerfMetricsSnapshot as $, parsePromptStopReason as A, EXIT_CODES as At, normalizeName as B, mergeSessionOptions as C, formatErrorMessage as Ct, extractSessionUpdateNotification as D, isAcpResourceNotFoundError as Dt, AcpClient as E, extractAcpError as Et, findSession as F, PERMISSION_MODES as Ft, DEFAULT_EVENT_SEGMENT_MAX_BYTES as G, resolveSessionRecord as H, findSessionByDirectoryWalk as I, PERMISSION_POLICY_ACTIONS as It, sessionEventActivePath as J, defaultSessionEventLog as K, isoNow$2 as L, SESSION_RECORD_SCHEMA as Lt, DEFAULT_HISTORY_LIMIT as M, OUTPUT_ERROR_CODES as Mt, absolutePath as N, OUTPUT_ERROR_ORIGINS as Nt, isAcpJsonRpcMessage as O, toAcpErrorPayload as Ot, findGitRepositoryRoot as P, OUTPUT_FORMATS as Pt, formatPerfMetric as Q, listSessions as R, QueueConnectionError as Rt, trimConversationForRuntime as S, exitCodeForOutputErrorCode as St, sessionOptionsFromRecord as T, normalizeOutputError as Tt, writeSessionRecord as U, pruneSessions as V, parseSessionRecord as W, sessionEventSegmentPath as X, sessionEventLockPath as Y, assertPersistedKeyPolicy as Z, cloneSessionConversation as _, withTimeout as _t, applyConversation as a, startPerfTimer as at, recordPromptSubmission as b, normalizeAgentName$1 as bt, applyRequestedModelIfAdvertised as c, PromptInputValidationError as ct, setDesiredConfigOption as d, parsePromptSource as dt, incrementPerfCounter as et, setDesiredModeId as f, promptToDisplayText as ft, cloneSessionAcpxState as g, withInterrupt as gt, applyConfigOptionsToRecord as h, TimeoutError as ht, connectAndLoadSession as i, setPerfGauge as it, permissionModeSatisfies as j, NON_INTERACTIVE_PERMISSION_POLICIES as jt, parseJsonRpcErrorMessage as k, AUTH_POLICIES as kt, assertRequestedModelSupported as l, isPromptInput as lt, syncAdvertisedModelState as m, InterruptedError as mt, runPromptTurn as n, recordPerfDuration as nt, applyLifecycleSnapshotToRecord as o, serializeSessionRecordForDisk as ot, setDesiredModelId as p, textPrompt as pt, sessionBaseDir$1 as q, withConnectedSession as r, resetPerfMetrics as rt, reconcileAgentSessionId as s, normalizeRuntimeSessionId as st, LiveSessionCheckpoint as t, measurePerf as tt, setCurrentModelId as u, mergePromptSourceWithText as ut, createSessionConversation as v, DEFAULT_AGENT_NAME as vt, persistSessionOptions as w, isRetryablePromptError as wt, recordSessionUpdate as x, resolveAgentCommand as xt, recordClientOperation as y, listBuiltInAgents as yt, listSessionsForAgent as z, QueueProtocolError as zt };
|
|
3536
4789
|
|
|
3537
|
-
//# sourceMappingURL=
|
|
4790
|
+
//# sourceMappingURL=live-checkpoint-B9ctAuqV.js.map
|