acpx 0.7.0 → 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 +7 -2
- package/dist/{cli-T-Z-9x6a.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-COPilhO_.d.ts → client-FzXPdgP7.d.ts} +7 -3
- package/dist/client-FzXPdgP7.d.ts.map +1 -0
- package/dist/{flags-Dj-IXgo9.js → flags-D706STfk.js} +46 -6
- package/dist/flags-D706STfk.js.map +1 -0
- package/dist/{flows-CF8w1rPI.js → flows-hcjHmU7P.js} +8 -5
- package/dist/flows-hcjHmU7P.js.map +1 -0
- package/dist/flows.d.ts +3 -2
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +1 -1
- package/dist/{prompt-turn-CVPMWdj1.js → live-checkpoint-B9ctAuqV.js} +1301 -78
- 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 +28 -4
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +153 -24
- 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 +23 -21
- package/skills/acpx/SKILL.md +200 -10
- package/dist/cli-T-Z-9x6a.js.map +0 -1
- package/dist/client-COPilhO_.d.ts.map +0 -1
- package/dist/flags-Dj-IXgo9.js.map +0 -1
- package/dist/flows-CF8w1rPI.js.map +0 -1
- package/dist/ipc-ABXlXzGP.js +0 -1290
- package/dist/ipc-ABXlXzGP.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-CVPMWdj1.js.map +0 -1
- package/dist/render-N5YwotCy.js +0 -172
- package/dist/render-N5YwotCy.js.map +0 -1
- package/dist/rolldown-runtime-CiIaOW0V.js +0 -13
- package/dist/session-CDaQe6BH.js +0 -1538
- package/dist/session-CDaQe6BH.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);
|
|
@@ -1564,6 +2315,71 @@ function buildAgentSpawnOptions(cwd, authCredentials) {
|
|
|
1564
2315
|
};
|
|
1565
2316
|
}
|
|
1566
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
|
|
1567
2383
|
//#region src/acp/session-control-errors.ts
|
|
1568
2384
|
const SESSION_CONTROL_UNSUPPORTED_ACP_CODES = new Set([-32601, -32602]);
|
|
1569
2385
|
function asRecord$1(value) {
|
|
@@ -1687,14 +2503,15 @@ var TerminalManager = class {
|
|
|
1687
2503
|
try {
|
|
1688
2504
|
if (!await this.isExecuteApproved(commandLine)) throw new PermissionDeniedError("Permission denied for terminal/create");
|
|
1689
2505
|
const outputByteLimit = Math.max(0, Math.round(params.outputByteLimit ?? DEFAULT_TERMINAL_OUTPUT_LIMIT_BYTES));
|
|
1690
|
-
const proc
|
|
1691
|
-
await waitForSpawn(proc);
|
|
2506
|
+
const { proc, spawnCommand } = await spawnTerminalProcess(params, this.cwd);
|
|
1692
2507
|
let resolveExit = () => {};
|
|
1693
2508
|
const exitPromise = new Promise((resolve) => {
|
|
1694
2509
|
resolveExit = resolve;
|
|
1695
2510
|
});
|
|
1696
2511
|
const terminal = {
|
|
1697
2512
|
process: proc,
|
|
2513
|
+
killProcessGroup: spawnCommand.killProcessGroup,
|
|
2514
|
+
descendantPids: /* @__PURE__ */ new Set(),
|
|
1698
2515
|
output: Buffer.alloc(0),
|
|
1699
2516
|
truncated: false,
|
|
1700
2517
|
outputByteLimit,
|
|
@@ -1717,10 +2534,14 @@ var TerminalManager = class {
|
|
|
1717
2534
|
proc.once("exit", (exitCode, signal) => {
|
|
1718
2535
|
terminal.exitCode = exitCode;
|
|
1719
2536
|
terminal.signal = signal;
|
|
1720
|
-
terminal.
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
2537
|
+
terminal.processGroupSnapshotPromise = rememberProcessGroupPids(terminal);
|
|
2538
|
+
(async () => {
|
|
2539
|
+
await terminal.processGroupSnapshotPromise;
|
|
2540
|
+
terminal.resolveExit({
|
|
2541
|
+
exitCode: exitCode ?? null,
|
|
2542
|
+
signal: signal ?? null
|
|
2543
|
+
});
|
|
2544
|
+
})();
|
|
1724
2545
|
});
|
|
1725
2546
|
const terminalId = randomUUID();
|
|
1726
2547
|
this.terminals.set(terminalId, terminal);
|
|
@@ -1872,21 +2693,284 @@ var TerminalManager = class {
|
|
|
1872
2693
|
return terminal.exitCode === void 0 && terminal.signal === void 0;
|
|
1873
2694
|
}
|
|
1874
2695
|
async killProcess(terminal) {
|
|
1875
|
-
if (!this.isRunning(terminal)) return;
|
|
2696
|
+
if (!this.isRunning(terminal) && !terminal.killProcessGroup) return;
|
|
1876
2697
|
try {
|
|
1877
|
-
|
|
2698
|
+
await this.signalProcess(terminal, "SIGTERM");
|
|
1878
2699
|
} catch {
|
|
1879
2700
|
return;
|
|
1880
2701
|
}
|
|
1881
|
-
if (await
|
|
2702
|
+
if (await this.waitForCleanupAfterSignal(terminal) && !terminal.killProcessGroup) return;
|
|
1882
2703
|
try {
|
|
1883
|
-
|
|
2704
|
+
await this.signalProcess(terminal, "SIGKILL");
|
|
1884
2705
|
} catch {
|
|
1885
2706
|
return;
|
|
1886
2707
|
}
|
|
1887
|
-
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);
|
|
1888
2741
|
}
|
|
1889
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
|
+
}
|
|
1890
2974
|
//#endregion
|
|
1891
2975
|
//#region src/acp/client.ts
|
|
1892
2976
|
const REPLAY_IDLE_MS = 80;
|
|
@@ -1973,6 +3057,7 @@ var AcpClient = class {
|
|
|
1973
3057
|
suppressReplaySessionUpdateMessages = false;
|
|
1974
3058
|
activePrompt;
|
|
1975
3059
|
cancellingSessionIds = /* @__PURE__ */ new Set();
|
|
3060
|
+
permissionAbortControllers = /* @__PURE__ */ new Map();
|
|
1976
3061
|
closing = false;
|
|
1977
3062
|
agentStartedAt;
|
|
1978
3063
|
lastAgentExit;
|
|
@@ -1989,7 +3074,8 @@ var AcpClient = class {
|
|
|
1989
3074
|
onAcpMessage: this.options.onAcpMessage,
|
|
1990
3075
|
onAcpOutputMessage: this.options.onAcpOutputMessage,
|
|
1991
3076
|
onSessionUpdate: this.options.onSessionUpdate,
|
|
1992
|
-
onClientOperation: this.options.onClientOperation
|
|
3077
|
+
onClientOperation: this.options.onClientOperation,
|
|
3078
|
+
onPermissionEscalation: this.options.onPermissionEscalation
|
|
1993
3079
|
};
|
|
1994
3080
|
this.filesystem = new FileSystemHandlers({
|
|
1995
3081
|
cwd: this.options.cwd,
|
|
@@ -2042,6 +3128,7 @@ var AcpClient = class {
|
|
|
2042
3128
|
updateRuntimeOptions(options) {
|
|
2043
3129
|
if (options.permissionMode) this.options.permissionMode = options.permissionMode;
|
|
2044
3130
|
if (options.nonInteractivePermissions !== void 0) this.options.nonInteractivePermissions = options.nonInteractivePermissions;
|
|
3131
|
+
if (Object.prototype.hasOwnProperty.call(options, "permissionPolicy")) this.options.permissionPolicy = options.permissionPolicy;
|
|
2045
3132
|
if (options.terminal !== void 0) this.options.terminal = options.terminal;
|
|
2046
3133
|
if (options.permissionMode || options.nonInteractivePermissions !== void 0) {
|
|
2047
3134
|
this.filesystem.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
|
|
@@ -2296,6 +3383,7 @@ var AcpClient = class {
|
|
|
2296
3383
|
restoreConsoleError?.();
|
|
2297
3384
|
if (this.activePrompt?.promise === promptPromise) this.activePrompt = void 0;
|
|
2298
3385
|
this.cancellingSessionIds.delete(sessionId);
|
|
3386
|
+
this.abortAndDropPermissionSignal(sessionId);
|
|
2299
3387
|
this.promptPermissionFailures.delete(sessionId);
|
|
2300
3388
|
}
|
|
2301
3389
|
}
|
|
@@ -2341,6 +3429,7 @@ var AcpClient = class {
|
|
|
2341
3429
|
async cancel(sessionId) {
|
|
2342
3430
|
const connection = this.getConnection();
|
|
2343
3431
|
this.cancellingSessionIds.add(sessionId);
|
|
3432
|
+
this.abortAndDropPermissionSignal(sessionId);
|
|
2344
3433
|
await this.runConnectionRequest(() => connection.cancel({ sessionId }));
|
|
2345
3434
|
}
|
|
2346
3435
|
async closeSession(sessionId) {
|
|
@@ -2387,6 +3476,8 @@ var AcpClient = class {
|
|
|
2387
3476
|
this.suppressReplaySessionUpdateMessages = false;
|
|
2388
3477
|
this.activePrompt = void 0;
|
|
2389
3478
|
this.cancellingSessionIds.clear();
|
|
3479
|
+
for (const controller of this.permissionAbortControllers.values()) controller.abort();
|
|
3480
|
+
this.permissionAbortControllers.clear();
|
|
2390
3481
|
this.promptPermissionFailures.clear();
|
|
2391
3482
|
this.loadedSessionId = void 0;
|
|
2392
3483
|
this.initResult = void 0;
|
|
@@ -2535,9 +3626,36 @@ var AcpClient = class {
|
|
|
2535
3626
|
}
|
|
2536
3627
|
async handlePermissionRequest(params) {
|
|
2537
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
|
+
}
|
|
2538
3654
|
let response;
|
|
2539
3655
|
try {
|
|
2540
|
-
|
|
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);
|
|
2541
3659
|
} catch (error) {
|
|
2542
3660
|
if (error instanceof PermissionPromptUnavailableError) {
|
|
2543
3661
|
this.notePromptPermissionFailure(params.sessionId, error);
|
|
@@ -2643,6 +3761,21 @@ var AcpClient = class {
|
|
|
2643
3761
|
async handleReleaseTerminal(params) {
|
|
2644
3762
|
return await this.terminalManager.releaseTerminal(params);
|
|
2645
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
|
+
}
|
|
2646
3779
|
recordPermissionDecision(decision) {
|
|
2647
3780
|
this.permissionStats.requested += 1;
|
|
2648
3781
|
if (decision === "approved") {
|
|
@@ -2704,6 +3837,47 @@ var AcpClient = class {
|
|
|
2704
3837
|
}
|
|
2705
3838
|
};
|
|
2706
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
|
|
2707
3881
|
//#region src/session/conversation-model.ts
|
|
2708
3882
|
const MAX_RUNTIME_MESSAGES = 200;
|
|
2709
3883
|
const MAX_RUNTIME_AGENT_TEXT_CHARS = 8e3;
|
|
@@ -3183,6 +4357,22 @@ function assertRequestedModelSupported(params) {
|
|
|
3183
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)}.`);
|
|
3184
4358
|
}
|
|
3185
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
|
|
3186
4376
|
//#region src/runtime/engine/lifecycle.ts
|
|
3187
4377
|
function applyLifecycleSnapshotToRecord(record, snapshot) {
|
|
3188
4378
|
if (!snapshot) return;
|
|
@@ -3404,28 +4594,6 @@ async function connectAndLoadSession(options) {
|
|
|
3404
4594
|
};
|
|
3405
4595
|
}
|
|
3406
4596
|
//#endregion
|
|
3407
|
-
//#region src/runtime/engine/session-options.ts
|
|
3408
|
-
function mergeSessionOptions(preferred, fallback) {
|
|
3409
|
-
const merged = { ...fallback };
|
|
3410
|
-
if (preferred?.model !== void 0) merged.model = preferred.model;
|
|
3411
|
-
if (preferred?.allowedTools !== void 0) merged.allowedTools = preferred.allowedTools;
|
|
3412
|
-
if (preferred?.maxTurns !== void 0) merged.maxTurns = preferred.maxTurns;
|
|
3413
|
-
if (preferred?.systemPrompt !== void 0) merged.systemPrompt = preferred.systemPrompt;
|
|
3414
|
-
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
3415
|
-
}
|
|
3416
|
-
function sessionOptionsFromRecord(record) {
|
|
3417
|
-
const stored = record.acpx?.session_options;
|
|
3418
|
-
if (!stored) return;
|
|
3419
|
-
const sessionOptions = {};
|
|
3420
|
-
if (typeof stored.model === "string" && stored.model.trim().length > 0) sessionOptions.model = stored.model;
|
|
3421
|
-
if (Array.isArray(stored.allowed_tools)) sessionOptions.allowedTools = [...stored.allowed_tools];
|
|
3422
|
-
if (typeof stored.max_turns === "number") sessionOptions.maxTurns = stored.max_turns;
|
|
3423
|
-
const storedSystemPrompt = stored.system_prompt;
|
|
3424
|
-
if (typeof storedSystemPrompt === "string" && storedSystemPrompt.length > 0) sessionOptions.systemPrompt = storedSystemPrompt;
|
|
3425
|
-
else if (storedSystemPrompt && typeof storedSystemPrompt === "object" && typeof storedSystemPrompt.append === "string" && storedSystemPrompt.append.length > 0) sessionOptions.systemPrompt = { append: storedSystemPrompt.append };
|
|
3426
|
-
return Object.keys(sessionOptions).length > 0 ? sessionOptions : void 0;
|
|
3427
|
-
}
|
|
3428
|
-
//#endregion
|
|
3429
4597
|
//#region src/runtime/engine/connected-session.ts
|
|
3430
4598
|
function createActiveSessionController(params) {
|
|
3431
4599
|
const getActiveSessionId = () => params.getActiveSessionId();
|
|
@@ -3451,6 +4619,7 @@ async function withConnectedSession(options) {
|
|
|
3451
4619
|
mcpServers: options.mcpServers,
|
|
3452
4620
|
permissionMode: options.permissionMode ?? "approve-reads",
|
|
3453
4621
|
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
4622
|
+
onPermissionRequest: options.onPermissionRequest,
|
|
3454
4623
|
authCredentials: options.authCredentials,
|
|
3455
4624
|
authPolicy: options.authPolicy,
|
|
3456
4625
|
terminal: options.terminal,
|
|
@@ -3462,6 +4631,7 @@ async function withConnectedSession(options) {
|
|
|
3462
4631
|
mcpServers: options.mcpServers,
|
|
3463
4632
|
permissionMode: options.permissionMode ?? "approve-reads",
|
|
3464
4633
|
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
4634
|
+
onPermissionRequest: options.onPermissionRequest,
|
|
3465
4635
|
authCredentials: options.authCredentials,
|
|
3466
4636
|
authPolicy: options.authPolicy,
|
|
3467
4637
|
terminal: options.terminal,
|
|
@@ -3562,6 +4732,59 @@ async function runPromptTurn(params) {
|
|
|
3562
4732
|
}
|
|
3563
4733
|
}
|
|
3564
4734
|
//#endregion
|
|
3565
|
-
|
|
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 };
|
|
3566
4789
|
|
|
3567
|
-
//# sourceMappingURL=
|
|
4790
|
+
//# sourceMappingURL=live-checkpoint-B9ctAuqV.js.map
|