acpx 0.7.0 → 0.9.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 +14 -6
- package/dist/{cli-T-Z-9x6a.js → cli-Bf3yjqzE.js} +35 -10
- package/dist/cli-Bf3yjqzE.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +724 -241
- package/dist/cli.js.map +1 -1
- package/dist/{client-COPilhO_.d.ts → client-BssohYqM.d.ts} +35 -4
- package/dist/client-BssohYqM.d.ts.map +1 -0
- package/dist/flags-C-rwARqg.js +260 -0
- package/dist/flags-C-rwARqg.js.map +1 -0
- package/dist/{flows-CF8w1rPI.js → flows-WLs26_5Y.js} +405 -337
- package/dist/flows-WLs26_5Y.js.map +1 -0
- package/dist/flows.d.ts +23 -2
- package/dist/flows.d.ts.map +1 -1
- package/dist/flows.js +1 -1
- package/dist/{prompt-turn-CVPMWdj1.js → live-checkpoint-D5d-K9s1.js} +2487 -609
- package/dist/live-checkpoint-D5d-K9s1.js.map +1 -0
- package/dist/output-DPg20dvn.js +4146 -0
- package/dist/output-DPg20dvn.js.map +1 -0
- package/dist/runtime.d.ts +56 -4
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +676 -393
- package/dist/runtime.js.map +1 -1
- package/dist/{types-CVBeQyi3.d.ts → session-options-CFudjdkU.d.ts} +62 -3
- package/dist/session-options-CFudjdkU.d.ts.map +1 -0
- package/package.json +30 -25
- package/skills/acpx/SKILL.md +211 -13
- 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 +0 -163
- 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,16 +9,435 @@ 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 UnsupportedPromptContentError = class extends AcpxOperationalError {
|
|
83
|
+
constructor(message) {
|
|
84
|
+
super(message, {
|
|
85
|
+
outputCode: "USAGE",
|
|
86
|
+
detailCode: "UNSUPPORTED_PROMPT_CONTENT",
|
|
87
|
+
origin: "acp"
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var SessionResumeRequiredError = class extends AcpxOperationalError {
|
|
92
|
+
constructor(message, options) {
|
|
93
|
+
super(message, {
|
|
94
|
+
outputCode: "RUNTIME",
|
|
95
|
+
detailCode: "SESSION_RESUME_REQUIRED",
|
|
96
|
+
origin: "acp",
|
|
97
|
+
retryable: true,
|
|
98
|
+
...options
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var GeminiAcpStartupTimeoutError = class extends AcpxOperationalError {
|
|
103
|
+
constructor(message, options) {
|
|
104
|
+
super(message, {
|
|
105
|
+
outputCode: "TIMEOUT",
|
|
106
|
+
detailCode: "GEMINI_ACP_STARTUP_TIMEOUT",
|
|
107
|
+
origin: "acp",
|
|
108
|
+
...options
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
var SessionModeReplayError = class extends AcpxOperationalError {
|
|
113
|
+
constructor(message, options) {
|
|
114
|
+
super(message, {
|
|
115
|
+
outputCode: "RUNTIME",
|
|
116
|
+
detailCode: "SESSION_MODE_REPLAY_FAILED",
|
|
117
|
+
origin: "acp",
|
|
118
|
+
...options
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var SessionModelReplayError = class extends AcpxOperationalError {
|
|
123
|
+
constructor(message, options) {
|
|
124
|
+
super(message, {
|
|
125
|
+
outputCode: "RUNTIME",
|
|
126
|
+
detailCode: "SESSION_MODEL_REPLAY_FAILED",
|
|
127
|
+
origin: "acp",
|
|
128
|
+
...options
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
var SessionConfigOptionReplayError = class extends AcpxOperationalError {
|
|
133
|
+
constructor(message, options) {
|
|
134
|
+
super(message, {
|
|
135
|
+
outputCode: "RUNTIME",
|
|
136
|
+
detailCode: "SESSION_CONFIG_OPTION_REPLAY_FAILED",
|
|
137
|
+
origin: "acp",
|
|
138
|
+
...options
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
var ClaudeAcpSessionCreateTimeoutError = class extends AcpxOperationalError {
|
|
143
|
+
constructor(message, options) {
|
|
144
|
+
super(message, {
|
|
145
|
+
outputCode: "TIMEOUT",
|
|
146
|
+
detailCode: "CLAUDE_ACP_SESSION_CREATE_TIMEOUT",
|
|
147
|
+
origin: "acp",
|
|
148
|
+
...options
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
var CopilotAcpUnsupportedError = class extends AcpxOperationalError {
|
|
153
|
+
constructor(message, options) {
|
|
154
|
+
super(message, {
|
|
155
|
+
outputCode: "RUNTIME",
|
|
156
|
+
detailCode: "COPILOT_ACP_UNSUPPORTED",
|
|
157
|
+
origin: "acp",
|
|
158
|
+
...options
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
var AuthPolicyError = class extends AcpxOperationalError {
|
|
163
|
+
constructor(message, options) {
|
|
164
|
+
super(message, {
|
|
165
|
+
outputCode: "RUNTIME",
|
|
166
|
+
detailCode: "AUTH_REQUIRED",
|
|
167
|
+
origin: "acp",
|
|
168
|
+
...options
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
var QueueConnectionError = class extends AcpxOperationalError {};
|
|
173
|
+
var QueueProtocolError = class extends AcpxOperationalError {};
|
|
174
|
+
var PermissionDeniedError = class extends AcpxOperationalError {};
|
|
175
|
+
var PermissionPromptUnavailableError = class extends AcpxOperationalError {
|
|
176
|
+
constructor() {
|
|
177
|
+
super("Permission prompt unavailable in non-interactive mode");
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/types.ts
|
|
182
|
+
const EXIT_CODES = {
|
|
183
|
+
SUCCESS: 0,
|
|
184
|
+
ERROR: 1,
|
|
185
|
+
USAGE: 2,
|
|
186
|
+
TIMEOUT: 3,
|
|
187
|
+
NO_SESSION: 4,
|
|
188
|
+
PERMISSION_DENIED: 5,
|
|
189
|
+
INTERRUPTED: 130
|
|
190
|
+
};
|
|
191
|
+
const OUTPUT_FORMATS = [
|
|
192
|
+
"text",
|
|
193
|
+
"json",
|
|
194
|
+
"quiet"
|
|
195
|
+
];
|
|
196
|
+
const PERMISSION_MODES = [
|
|
197
|
+
"approve-all",
|
|
198
|
+
"approve-reads",
|
|
199
|
+
"deny-all"
|
|
200
|
+
];
|
|
201
|
+
const AUTH_POLICIES = ["skip", "fail"];
|
|
202
|
+
const NON_INTERACTIVE_PERMISSION_POLICIES = ["deny", "fail"];
|
|
203
|
+
const PERMISSION_POLICY_ACTIONS = [
|
|
204
|
+
"approve",
|
|
205
|
+
"deny",
|
|
206
|
+
"escalate"
|
|
207
|
+
];
|
|
208
|
+
const OUTPUT_ERROR_CODES = [
|
|
209
|
+
"NO_SESSION",
|
|
210
|
+
"TIMEOUT",
|
|
211
|
+
"PERMISSION_DENIED",
|
|
212
|
+
"PERMISSION_PROMPT_UNAVAILABLE",
|
|
213
|
+
"RUNTIME",
|
|
214
|
+
"USAGE"
|
|
215
|
+
];
|
|
216
|
+
const OUTPUT_ERROR_ORIGINS = [
|
|
217
|
+
"cli",
|
|
218
|
+
"runtime",
|
|
219
|
+
"queue",
|
|
220
|
+
"acp"
|
|
221
|
+
];
|
|
222
|
+
const SESSION_RECORD_SCHEMA = "acpx.session.v1";
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/acp/error-shapes.ts
|
|
225
|
+
const RESOURCE_NOT_FOUND_ACP_CODES = new Set([-32001, -32002]);
|
|
226
|
+
function asRecord$7(value) {
|
|
227
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
function toAcpErrorPayload(value) {
|
|
231
|
+
const record = asRecord$7(value);
|
|
232
|
+
if (!record) return;
|
|
233
|
+
if (typeof record.code !== "number" || !Number.isFinite(record.code)) return;
|
|
234
|
+
if (typeof record.message !== "string" || record.message.length === 0) return;
|
|
235
|
+
return {
|
|
236
|
+
code: record.code,
|
|
237
|
+
message: record.message,
|
|
238
|
+
data: record.data
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function extractAcpErrorInternal(value, depth) {
|
|
242
|
+
if (depth > 5) return;
|
|
243
|
+
const direct = toAcpErrorPayload(value);
|
|
244
|
+
if (direct) return direct;
|
|
245
|
+
const record = asRecord$7(value);
|
|
246
|
+
if (!record) return;
|
|
247
|
+
return extractNestedAcpError(record, depth);
|
|
248
|
+
}
|
|
249
|
+
function extractNestedAcpError(record, depth) {
|
|
250
|
+
for (const key of [
|
|
251
|
+
"error",
|
|
252
|
+
"acp",
|
|
253
|
+
"cause"
|
|
254
|
+
]) if (key in record) {
|
|
255
|
+
const nested = extractAcpErrorInternal(record[key], depth + 1);
|
|
256
|
+
if (nested) return nested;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function formatUnknownErrorMessage(error) {
|
|
260
|
+
if (error instanceof Error) return error.message;
|
|
261
|
+
if (error && typeof error === "object") {
|
|
262
|
+
const maybeMessage = error.message;
|
|
263
|
+
if (typeof maybeMessage === "string" && maybeMessage.length > 0) return maybeMessage;
|
|
264
|
+
try {
|
|
265
|
+
return JSON.stringify(error);
|
|
266
|
+
} catch {}
|
|
267
|
+
}
|
|
268
|
+
return String(error);
|
|
269
|
+
}
|
|
270
|
+
const SESSION_NOT_FOUND_PATTERN = /session\s+["'\w-]+\s+not found/i;
|
|
271
|
+
function isSessionNotFoundText(value) {
|
|
272
|
+
if (typeof value !== "string") return false;
|
|
273
|
+
const normalized = value.toLowerCase();
|
|
274
|
+
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);
|
|
275
|
+
}
|
|
276
|
+
function hasSessionNotFoundHint(value, depth = 0) {
|
|
277
|
+
if (depth > 4) return false;
|
|
278
|
+
if (isSessionNotFoundText(value)) return true;
|
|
279
|
+
if (Array.isArray(value)) return value.some((entry) => hasSessionNotFoundHint(entry, depth + 1));
|
|
280
|
+
const record = asRecord$7(value);
|
|
281
|
+
if (!record) return false;
|
|
282
|
+
return Object.values(record).some((entry) => hasSessionNotFoundHint(entry, depth + 1));
|
|
283
|
+
}
|
|
284
|
+
function extractAcpError(error) {
|
|
285
|
+
return extractAcpErrorInternal(error, 0);
|
|
286
|
+
}
|
|
287
|
+
function isAcpResourceNotFoundError(error) {
|
|
288
|
+
const acp = extractAcpError(error);
|
|
289
|
+
if (acp && RESOURCE_NOT_FOUND_ACP_CODES.has(acp.code)) return true;
|
|
290
|
+
if (acp) {
|
|
291
|
+
if (isSessionNotFoundText(acp.message)) return true;
|
|
292
|
+
if (hasSessionNotFoundHint(acp.data)) return true;
|
|
293
|
+
}
|
|
294
|
+
return isSessionNotFoundText(formatUnknownErrorMessage(error));
|
|
295
|
+
}
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region src/acp/error-normalization.ts
|
|
298
|
+
const AUTH_REQUIRED_ACP_CODES = new Set([-32e3]);
|
|
299
|
+
const QUERY_CLOSED_BEFORE_RESPONSE_DETAIL = "query closed before response received";
|
|
300
|
+
function asRecord$6(value) {
|
|
301
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
302
|
+
return value;
|
|
303
|
+
}
|
|
304
|
+
function isAuthRequiredMessage(value) {
|
|
305
|
+
if (!value) return false;
|
|
306
|
+
const normalized = value.toLowerCase();
|
|
307
|
+
return [
|
|
308
|
+
"auth required",
|
|
309
|
+
"authentication required",
|
|
310
|
+
"authorization required",
|
|
311
|
+
"credential required",
|
|
312
|
+
"credentials required",
|
|
313
|
+
"token required",
|
|
314
|
+
"login required"
|
|
315
|
+
].some((needle) => normalized.includes(needle));
|
|
316
|
+
}
|
|
317
|
+
function isAcpAuthRequiredPayload(acp) {
|
|
318
|
+
if (!acp) return false;
|
|
319
|
+
if (!AUTH_REQUIRED_ACP_CODES.has(acp.code)) return false;
|
|
320
|
+
if (isAuthRequiredMessage(acp.message)) return true;
|
|
321
|
+
const data = asRecord$6(acp.data);
|
|
322
|
+
if (!data) return false;
|
|
323
|
+
return hasAuthRequiredData(data);
|
|
324
|
+
}
|
|
325
|
+
function hasAuthRequiredData(data) {
|
|
326
|
+
return data.authRequired === true || hasNonEmptyString(data.methodId) || hasNonEmptyArray(data.methods);
|
|
327
|
+
}
|
|
328
|
+
function hasNonEmptyString(value) {
|
|
329
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
330
|
+
}
|
|
331
|
+
function hasNonEmptyArray(value) {
|
|
332
|
+
return Array.isArray(value) && value.length > 0;
|
|
333
|
+
}
|
|
334
|
+
function isOutputErrorCode(value) {
|
|
335
|
+
return typeof value === "string" && OUTPUT_ERROR_CODES.includes(value);
|
|
336
|
+
}
|
|
337
|
+
function isOutputErrorOrigin(value) {
|
|
338
|
+
return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
|
|
339
|
+
}
|
|
340
|
+
function readOutputErrorMeta(error) {
|
|
341
|
+
const record = asRecord$6(error);
|
|
342
|
+
if (!record) return {};
|
|
343
|
+
return {
|
|
344
|
+
outputCode: isOutputErrorCode(record.outputCode) ? record.outputCode : void 0,
|
|
345
|
+
detailCode: typeof record.detailCode === "string" && record.detailCode.trim().length > 0 ? record.detailCode : void 0,
|
|
346
|
+
origin: isOutputErrorOrigin(record.origin) ? record.origin : void 0,
|
|
347
|
+
retryable: typeof record.retryable === "boolean" ? record.retryable : void 0,
|
|
348
|
+
acp: extractAcpError(record.acp)
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function isTimeoutLike(error) {
|
|
352
|
+
return error instanceof Error && error.name === "TimeoutError";
|
|
353
|
+
}
|
|
354
|
+
function isNoSessionLike(error) {
|
|
355
|
+
return error instanceof Error && error.name === "NoSessionError";
|
|
356
|
+
}
|
|
357
|
+
function isUsageLike(error) {
|
|
358
|
+
if (!(error instanceof Error)) return false;
|
|
359
|
+
return error.name === "CommanderError" || error.name === "InvalidArgumentError" || asRecord$6(error)?.code === "commander.invalidArgument";
|
|
360
|
+
}
|
|
361
|
+
function formatErrorMessage(error) {
|
|
362
|
+
return formatUnknownErrorMessage(error);
|
|
363
|
+
}
|
|
364
|
+
function isAcpQueryClosedBeforeResponseError(error) {
|
|
365
|
+
const acp = extractAcpError(error);
|
|
366
|
+
if (!acp || acp.code !== -32603) return false;
|
|
367
|
+
const details = asRecord$6(acp.data)?.details;
|
|
368
|
+
if (typeof details !== "string") return false;
|
|
369
|
+
return details.toLowerCase().includes(QUERY_CLOSED_BEFORE_RESPONSE_DETAIL);
|
|
370
|
+
}
|
|
371
|
+
function mapErrorCode(error) {
|
|
372
|
+
if (error instanceof PermissionPromptUnavailableError) return "PERMISSION_PROMPT_UNAVAILABLE";
|
|
373
|
+
if (error instanceof PermissionDeniedError) return "PERMISSION_DENIED";
|
|
374
|
+
if (isTimeoutLike(error)) return "TIMEOUT";
|
|
375
|
+
if (isNoSessionLike(error) || isAcpResourceNotFoundError(error)) return "NO_SESSION";
|
|
376
|
+
if (isUsageLike(error)) return "USAGE";
|
|
377
|
+
}
|
|
378
|
+
function normalizeOutputError(error, options = {}) {
|
|
379
|
+
const meta = readOutputErrorMeta(error);
|
|
380
|
+
const code = resolveOutputErrorCode(error, options, meta);
|
|
381
|
+
const acp = options.acp ?? meta.acp ?? extractAcpError(error);
|
|
382
|
+
return {
|
|
383
|
+
code,
|
|
384
|
+
message: formatErrorMessage(error),
|
|
385
|
+
detailCode: resolveDetailCode(error, acp, options, meta),
|
|
386
|
+
origin: meta.origin ?? options.origin,
|
|
387
|
+
retryable: meta.retryable ?? options.retryable,
|
|
388
|
+
acp
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function resolveOutputErrorCode(error, options, meta) {
|
|
392
|
+
const code = meta.outputCode ?? mapErrorCode(error) ?? options.defaultCode ?? "RUNTIME";
|
|
393
|
+
if (code === "RUNTIME" && isAcpResourceNotFoundError(error)) return "NO_SESSION";
|
|
394
|
+
return code;
|
|
395
|
+
}
|
|
396
|
+
function resolveDetailCode(error, acp, options, meta) {
|
|
397
|
+
return meta.detailCode ?? options.detailCode ?? (error instanceof AuthPolicyError || isAcpAuthRequiredPayload(acp) ? "AUTH_REQUIRED" : void 0);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Returns true when an error from `client.prompt()` looks transient and
|
|
401
|
+
* can reasonably be retried (e.g. model-API 400/500, network hiccups that
|
|
402
|
+
* surface as ACP internal errors).
|
|
403
|
+
*
|
|
404
|
+
* Errors that are definitively non-recoverable (auth, missing session,
|
|
405
|
+
* invalid params, timeout, permission) return false.
|
|
406
|
+
*/
|
|
407
|
+
function isRetryablePromptError(error) {
|
|
408
|
+
if (isNonRetryablePromptError(error)) return false;
|
|
409
|
+
const acp = extractAcpError(error);
|
|
410
|
+
if (!acp) return false;
|
|
411
|
+
if (isPermanentPromptAcpError(acp)) return false;
|
|
412
|
+
return acp.code === -32603 || acp.code === -32700;
|
|
413
|
+
}
|
|
414
|
+
function isNonRetryablePromptError(error) {
|
|
415
|
+
return error instanceof PermissionDeniedError || error instanceof PermissionPromptUnavailableError || isTimeoutLike(error) || isNoSessionLike(error) || isUsageLike(error);
|
|
416
|
+
}
|
|
417
|
+
function isPermanentPromptAcpError(acp) {
|
|
418
|
+
return acp.code === -32001 || acp.code === -32002 || acp.code === -32601 || acp.code === -32602 || isAcpAuthRequiredPayload(acp);
|
|
419
|
+
}
|
|
420
|
+
function exitCodeForOutputErrorCode(code) {
|
|
421
|
+
switch (code) {
|
|
422
|
+
case "USAGE": return EXIT_CODES.USAGE;
|
|
423
|
+
case "TIMEOUT": return EXIT_CODES.TIMEOUT;
|
|
424
|
+
case "NO_SESSION": return EXIT_CODES.NO_SESSION;
|
|
425
|
+
case "PERMISSION_DENIED":
|
|
426
|
+
case "PERMISSION_PROMPT_UNAVAILABLE": return EXIT_CODES.PERMISSION_DENIED;
|
|
427
|
+
default: return EXIT_CODES.ERROR;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
//#endregion
|
|
14
431
|
//#region src/agent-registry.ts
|
|
15
432
|
const ACP_ADAPTER_PACKAGE_RANGES = {
|
|
16
433
|
pi: "^0.0.26",
|
|
17
|
-
codex: "^0.
|
|
18
|
-
claude: "^0.
|
|
434
|
+
codex: "^0.0.44",
|
|
435
|
+
claude: "^0.36.1"
|
|
19
436
|
};
|
|
20
437
|
const AGENT_REGISTRY = {
|
|
21
438
|
pi: `npx pi-acp@${ACP_ADAPTER_PACKAGE_RANGES.pi}`,
|
|
22
439
|
openclaw: "openclaw acp",
|
|
23
|
-
codex: `npx @
|
|
440
|
+
codex: `npx -y @agentclientprotocol/codex-acp@${ACP_ADAPTER_PACKAGE_RANGES.codex}`,
|
|
24
441
|
claude: `npx -y @agentclientprotocol/claude-agent-acp@${ACP_ADAPTER_PACKAGE_RANGES.claude}`,
|
|
25
442
|
gemini: "gemini --acp",
|
|
26
443
|
cursor: "cursor-agent acp",
|
|
@@ -37,7 +454,7 @@ const AGENT_REGISTRY = {
|
|
|
37
454
|
};
|
|
38
455
|
const BUILT_IN_AGENT_PACKAGES = {
|
|
39
456
|
codex: {
|
|
40
|
-
packageName: "@
|
|
457
|
+
packageName: "@agentclientprotocol/codex-acp",
|
|
41
458
|
packageRange: ACP_ADAPTER_PACKAGE_RANGES.codex,
|
|
42
459
|
preferredBinName: "codex-acp",
|
|
43
460
|
fallbackCommand: AGENT_REGISTRY.codex,
|
|
@@ -109,26 +526,37 @@ function resolveInstalledBuiltInAgentLaunch(agentCommand, options = {}) {
|
|
|
109
526
|
const existsSync = options.existsSync ?? fs.existsSync;
|
|
110
527
|
const resolvePackageRoot = options.resolvePackageRoot ?? defaultResolvePackageRoot;
|
|
111
528
|
try {
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (!existsSync(binPath)) return;
|
|
529
|
+
const resolved = resolveInstalledBuiltInAgentPackage(spec, {
|
|
530
|
+
readFileSync,
|
|
531
|
+
existsSync,
|
|
532
|
+
resolvePackageRoot
|
|
533
|
+
});
|
|
534
|
+
if (!resolved) return;
|
|
119
535
|
return {
|
|
120
536
|
source: "installed",
|
|
121
537
|
command: process.execPath,
|
|
122
|
-
args: [binPath],
|
|
538
|
+
args: [resolved.binPath],
|
|
123
539
|
packageName: spec.packageName,
|
|
124
540
|
packageRange: spec.packageRange,
|
|
125
|
-
packageVersion:
|
|
126
|
-
binPath
|
|
541
|
+
packageVersion: resolved.packageVersion,
|
|
542
|
+
binPath: resolved.binPath
|
|
127
543
|
};
|
|
128
544
|
} catch {
|
|
129
545
|
return;
|
|
130
546
|
}
|
|
131
547
|
}
|
|
548
|
+
function resolveInstalledBuiltInAgentPackage(spec, options) {
|
|
549
|
+
const packageRoot = options.resolvePackageRoot(spec.packageName);
|
|
550
|
+
const manifest = JSON.parse(options.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
551
|
+
if (manifest.name !== spec.packageName) return;
|
|
552
|
+
const relativeBinPath = resolvePackageBin(spec, manifest);
|
|
553
|
+
if (!relativeBinPath) return;
|
|
554
|
+
const binPath = path.resolve(packageRoot, relativeBinPath);
|
|
555
|
+
return options.existsSync(binPath) ? {
|
|
556
|
+
packageVersion: manifest.version,
|
|
557
|
+
binPath
|
|
558
|
+
} : void 0;
|
|
559
|
+
}
|
|
132
560
|
function resolvePackageExecBuiltInAgentLaunch(agentCommand, options = {}) {
|
|
133
561
|
const spec = findBuiltInAgentPackage(agentCommand);
|
|
134
562
|
if (!spec) return;
|
|
@@ -223,6 +651,210 @@ async function withInterrupt(run, onInterrupt) {
|
|
|
223
651
|
});
|
|
224
652
|
}
|
|
225
653
|
//#endregion
|
|
654
|
+
//#region src/prompt-content.ts
|
|
655
|
+
var PromptInputValidationError = class extends Error {
|
|
656
|
+
constructor(message) {
|
|
657
|
+
super(message);
|
|
658
|
+
this.name = "PromptInputValidationError";
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
function asRecord$5(value) {
|
|
662
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
663
|
+
return value;
|
|
664
|
+
}
|
|
665
|
+
function isNonEmptyString(value) {
|
|
666
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
667
|
+
}
|
|
668
|
+
function isBase64Data(value) {
|
|
669
|
+
if (value.length === 0 || value.length % 4 !== 0) return false;
|
|
670
|
+
return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
|
|
671
|
+
}
|
|
672
|
+
function isImageMimeType(value) {
|
|
673
|
+
return /^image\/[A-Za-z0-9.+-]+$/i.test(value);
|
|
674
|
+
}
|
|
675
|
+
function isAudioMimeType(value) {
|
|
676
|
+
return /^audio\/[A-Za-z0-9.+-]+$/i.test(value);
|
|
677
|
+
}
|
|
678
|
+
function isTextBlock(value) {
|
|
679
|
+
const record = asRecord$5(value);
|
|
680
|
+
return record?.type === "text" && typeof record.text === "string";
|
|
681
|
+
}
|
|
682
|
+
function isImageBlock(value) {
|
|
683
|
+
const record = asRecord$5(value);
|
|
684
|
+
return record?.type === "image" && isNonEmptyString(record.mimeType) && isImageMimeType(record.mimeType) && typeof record.data === "string" && isBase64Data(record.data);
|
|
685
|
+
}
|
|
686
|
+
function isAudioBlock(value) {
|
|
687
|
+
const record = asRecord$5(value);
|
|
688
|
+
return record?.type === "audio" && isNonEmptyString(record.mimeType) && isAudioMimeType(record.mimeType) && typeof record.data === "string" && isBase64Data(record.data);
|
|
689
|
+
}
|
|
690
|
+
function isResourceLinkBlock(value) {
|
|
691
|
+
const record = asRecord$5(value);
|
|
692
|
+
return record?.type === "resource_link" && isNonEmptyString(record.uri) && (record.title === void 0 || typeof record.title === "string") && (record.name === void 0 || typeof record.name === "string");
|
|
693
|
+
}
|
|
694
|
+
function isResourcePayload(value) {
|
|
695
|
+
const record = asRecord$5(value);
|
|
696
|
+
if (!record || !isNonEmptyString(record.uri)) return false;
|
|
697
|
+
return record.text === void 0 || typeof record.text === "string";
|
|
698
|
+
}
|
|
699
|
+
function isResourceBlock(value) {
|
|
700
|
+
const record = asRecord$5(value);
|
|
701
|
+
return record?.type === "resource" && isResourcePayload(record.resource);
|
|
702
|
+
}
|
|
703
|
+
const CONTENT_BLOCK_VALIDATORS = [
|
|
704
|
+
isTextBlock,
|
|
705
|
+
isImageBlock,
|
|
706
|
+
isAudioBlock,
|
|
707
|
+
isResourceLinkBlock,
|
|
708
|
+
isResourceBlock
|
|
709
|
+
];
|
|
710
|
+
function isContentBlock(value) {
|
|
711
|
+
return CONTENT_BLOCK_VALIDATORS.some((validator) => validator(value));
|
|
712
|
+
}
|
|
713
|
+
const CONTENT_BLOCK_ERROR_VALIDATORS = {
|
|
714
|
+
text: validateTextContentBlock,
|
|
715
|
+
image: validateImageContentBlock,
|
|
716
|
+
audio: validateAudioContentBlock,
|
|
717
|
+
resource_link: validateResourceLinkContentBlock,
|
|
718
|
+
resource: validateResourceContentBlock
|
|
719
|
+
};
|
|
720
|
+
function contentBlockErrorValidator(type) {
|
|
721
|
+
return Object.hasOwn(CONTENT_BLOCK_ERROR_VALIDATORS, type) ? CONTENT_BLOCK_ERROR_VALIDATORS[type] : void 0;
|
|
722
|
+
}
|
|
723
|
+
function validateTextContentBlock(record, index) {
|
|
724
|
+
return typeof record.text === "string" ? void 0 : `prompt[${index}] text block must include a string text field`;
|
|
725
|
+
}
|
|
726
|
+
function validateImageContentBlock(record, index) {
|
|
727
|
+
if (!isNonEmptyString(record.mimeType)) return `prompt[${index}] image block must include a non-empty mimeType`;
|
|
728
|
+
if (!isImageMimeType(record.mimeType)) return `prompt[${index}] image block mimeType must start with image/`;
|
|
729
|
+
if (typeof record.data !== "string" || record.data.length === 0) return `prompt[${index}] image block must include non-empty base64 data`;
|
|
730
|
+
return isBase64Data(record.data) ? void 0 : `prompt[${index}] image block data must be valid base64`;
|
|
731
|
+
}
|
|
732
|
+
function validateAudioContentBlock(record, index) {
|
|
733
|
+
if (!isNonEmptyString(record.mimeType)) return `prompt[${index}] audio block must include a non-empty mimeType`;
|
|
734
|
+
if (!isAudioMimeType(record.mimeType)) return `prompt[${index}] audio block mimeType must start with audio/`;
|
|
735
|
+
if (typeof record.data !== "string" || record.data.length === 0) return `prompt[${index}] audio block must include non-empty base64 data`;
|
|
736
|
+
return isBase64Data(record.data) ? void 0 : `prompt[${index}] audio block data must be valid base64`;
|
|
737
|
+
}
|
|
738
|
+
function validateResourceLinkContentBlock(record, index) {
|
|
739
|
+
if (!isNonEmptyString(record.uri)) return `prompt[${index}] resource_link block must include a non-empty uri`;
|
|
740
|
+
if (record.title !== void 0 && typeof record.title !== "string") return `prompt[${index}] resource_link block title must be a string when present`;
|
|
741
|
+
if (record.name !== void 0 && typeof record.name !== "string") return `prompt[${index}] resource_link block name must be a string when present`;
|
|
742
|
+
}
|
|
743
|
+
function validateResourceContentBlock(record, index) {
|
|
744
|
+
if (!asRecord$5(record.resource)) return `prompt[${index}] resource block must include a resource object`;
|
|
745
|
+
return isResourcePayload(record.resource) ? void 0 : `prompt[${index}] resource block resource must include a non-empty uri and optional text`;
|
|
746
|
+
}
|
|
747
|
+
function getContentBlockValidationError(value, index) {
|
|
748
|
+
const record = asRecord$5(value);
|
|
749
|
+
if (!record || typeof record.type !== "string") return `prompt[${index}] must be an ACP content block object`;
|
|
750
|
+
const validator = contentBlockErrorValidator(record.type);
|
|
751
|
+
return validator ? validator(record, index) : `prompt[${index}] has unsupported content block type ${JSON.stringify(record.type)}`;
|
|
752
|
+
}
|
|
753
|
+
function isPromptInput(value) {
|
|
754
|
+
return Array.isArray(value) && value.every((entry) => isContentBlock(entry));
|
|
755
|
+
}
|
|
756
|
+
function promptCapabilityRequirement(block) {
|
|
757
|
+
switch (block.type) {
|
|
758
|
+
case "image": return {
|
|
759
|
+
blockType: "image",
|
|
760
|
+
capability: "image"
|
|
761
|
+
};
|
|
762
|
+
case "audio": return {
|
|
763
|
+
blockType: "audio",
|
|
764
|
+
capability: "audio"
|
|
765
|
+
};
|
|
766
|
+
case "resource": return {
|
|
767
|
+
blockType: "resource",
|
|
768
|
+
capability: "embeddedContext"
|
|
769
|
+
};
|
|
770
|
+
default: return;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function getUnsupportedPromptContentMessage(prompt, agentCapabilities) {
|
|
774
|
+
for (const [index, block] of prompt.entries()) {
|
|
775
|
+
const requirement = promptCapabilityRequirement(block);
|
|
776
|
+
if (!requirement) continue;
|
|
777
|
+
if (agentCapabilities?.promptCapabilities?.[requirement.capability] === true) continue;
|
|
778
|
+
return `prompt[${index}] ${requirement.blockType} content requires agentCapabilities.promptCapabilities.${requirement.capability}`;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
function textPrompt(text) {
|
|
782
|
+
return [{
|
|
783
|
+
type: "text",
|
|
784
|
+
text
|
|
785
|
+
}];
|
|
786
|
+
}
|
|
787
|
+
function parseStructuredPrompt(source) {
|
|
788
|
+
if (!source.startsWith("[")) return;
|
|
789
|
+
try {
|
|
790
|
+
const parsed = JSON.parse(source);
|
|
791
|
+
if (isPromptInput(parsed)) return parsed;
|
|
792
|
+
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");
|
|
793
|
+
return;
|
|
794
|
+
} catch (error) {
|
|
795
|
+
if (error instanceof PromptInputValidationError) throw error;
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function parsePromptSource(source) {
|
|
800
|
+
const trimmed = source.trim();
|
|
801
|
+
const structured = parseStructuredPrompt(trimmed);
|
|
802
|
+
if (structured) return structured;
|
|
803
|
+
if (!trimmed) return [];
|
|
804
|
+
return textPrompt(trimmed);
|
|
805
|
+
}
|
|
806
|
+
function mergePromptSourceWithText(source, suffixText) {
|
|
807
|
+
const prompt = parsePromptSource(source);
|
|
808
|
+
const appended = suffixText.trim();
|
|
809
|
+
if (!appended) return prompt;
|
|
810
|
+
if (prompt.length === 0) return textPrompt(appended);
|
|
811
|
+
return [...prompt, ...textPrompt(appended)];
|
|
812
|
+
}
|
|
813
|
+
function promptToDisplayText(prompt) {
|
|
814
|
+
return prompt.map((block) => contentBlockDisplayText(block)).filter((entry) => entry.trim().length > 0).join("\n\n").trim();
|
|
815
|
+
}
|
|
816
|
+
function contentBlockDisplayText(block) {
|
|
817
|
+
switch (block.type) {
|
|
818
|
+
case "text": return block.text;
|
|
819
|
+
case "resource_link": return block.title ?? block.name ?? block.uri;
|
|
820
|
+
case "resource": return resourceBlockDisplayText(block);
|
|
821
|
+
case "image": return `[image] ${block.mimeType}`;
|
|
822
|
+
case "audio": return `[audio] ${block.mimeType}`;
|
|
823
|
+
default: return "";
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
function resourceBlockDisplayText(block) {
|
|
827
|
+
return "text" in block.resource && typeof block.resource.text === "string" ? block.resource.text : block.resource.uri;
|
|
828
|
+
}
|
|
829
|
+
//#endregion
|
|
830
|
+
//#region src/acp/agent-session-id.ts
|
|
831
|
+
const AGENT_SESSION_ID_META_KEYS = ["agentSessionId", "sessionId"];
|
|
832
|
+
function normalizeAgentSessionId(value) {
|
|
833
|
+
if (typeof value !== "string") return;
|
|
834
|
+
const trimmed = value.trim();
|
|
835
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
836
|
+
}
|
|
837
|
+
function asMetaRecord(meta) {
|
|
838
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) return;
|
|
839
|
+
return meta;
|
|
840
|
+
}
|
|
841
|
+
function extractAgentSessionId(meta) {
|
|
842
|
+
const record = asMetaRecord(meta);
|
|
843
|
+
if (!record) return;
|
|
844
|
+
for (const key of AGENT_SESSION_ID_META_KEYS) {
|
|
845
|
+
const normalized = normalizeAgentSessionId(record[key]);
|
|
846
|
+
if (normalized) return normalized;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
//#endregion
|
|
850
|
+
//#region src/session/runtime-session-id.ts
|
|
851
|
+
function normalizeRuntimeSessionId(value) {
|
|
852
|
+
return normalizeAgentSessionId(value);
|
|
853
|
+
}
|
|
854
|
+
function extractRuntimeSessionId(meta) {
|
|
855
|
+
return extractAgentSessionId(meta);
|
|
856
|
+
}
|
|
857
|
+
//#endregion
|
|
226
858
|
//#region src/session/persistence/serialize.ts
|
|
227
859
|
function serializeSessionRecordForDisk(record) {
|
|
228
860
|
const canonical = {
|
|
@@ -262,6 +894,72 @@ function serializeSessionRecordForDisk(record) {
|
|
|
262
894
|
};
|
|
263
895
|
}
|
|
264
896
|
//#endregion
|
|
897
|
+
//#region src/perf-metrics.ts
|
|
898
|
+
const counters = /* @__PURE__ */ new Map();
|
|
899
|
+
const gauges = /* @__PURE__ */ new Map();
|
|
900
|
+
const timings = /* @__PURE__ */ new Map();
|
|
901
|
+
function hrNow() {
|
|
902
|
+
return process.hrtime.bigint();
|
|
903
|
+
}
|
|
904
|
+
function durationMs(start) {
|
|
905
|
+
return Number(process.hrtime.bigint() - start) / 1e6;
|
|
906
|
+
}
|
|
907
|
+
function roundMetric(value) {
|
|
908
|
+
return Number(value.toFixed(3));
|
|
909
|
+
}
|
|
910
|
+
function incrementPerfCounter(name, delta = 1) {
|
|
911
|
+
counters.set(name, (counters.get(name) ?? 0) + delta);
|
|
912
|
+
}
|
|
913
|
+
function setPerfGauge(name, value) {
|
|
914
|
+
gauges.set(name, value);
|
|
915
|
+
}
|
|
916
|
+
function recordPerfDuration(name, durationMsValue) {
|
|
917
|
+
const next = timings.get(name) ?? {
|
|
918
|
+
count: 0,
|
|
919
|
+
totalMs: 0,
|
|
920
|
+
maxMs: 0
|
|
921
|
+
};
|
|
922
|
+
next.count += 1;
|
|
923
|
+
next.totalMs += durationMsValue;
|
|
924
|
+
next.maxMs = Math.max(next.maxMs, durationMsValue);
|
|
925
|
+
timings.set(name, next);
|
|
926
|
+
}
|
|
927
|
+
async function measurePerf(name, run) {
|
|
928
|
+
const startedAt = hrNow();
|
|
929
|
+
try {
|
|
930
|
+
return await run();
|
|
931
|
+
} finally {
|
|
932
|
+
recordPerfDuration(name, durationMs(startedAt));
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
function startPerfTimer(name) {
|
|
936
|
+
const startedAt = hrNow();
|
|
937
|
+
return () => {
|
|
938
|
+
const elapsedMs = durationMs(startedAt);
|
|
939
|
+
recordPerfDuration(name, elapsedMs);
|
|
940
|
+
return elapsedMs;
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
function getPerfMetricsSnapshot() {
|
|
944
|
+
return {
|
|
945
|
+
counters: Object.fromEntries(counters.entries()),
|
|
946
|
+
gauges: Object.fromEntries(gauges.entries()),
|
|
947
|
+
timings: Object.fromEntries([...timings.entries()].map(([name, bucket]) => [name, {
|
|
948
|
+
count: bucket.count,
|
|
949
|
+
totalMs: roundMetric(bucket.totalMs),
|
|
950
|
+
maxMs: roundMetric(bucket.maxMs)
|
|
951
|
+
}]))
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
function resetPerfMetrics() {
|
|
955
|
+
counters.clear();
|
|
956
|
+
gauges.clear();
|
|
957
|
+
timings.clear();
|
|
958
|
+
}
|
|
959
|
+
function formatPerfMetric(name, durationMsValue) {
|
|
960
|
+
return `${name}=${roundMetric(durationMsValue)}ms`;
|
|
961
|
+
}
|
|
962
|
+
//#endregion
|
|
265
963
|
//#region src/persisted-key-policy.ts
|
|
266
964
|
const SNAKE_CASE_KEY = /^[a-z][a-z0-9_]*$/;
|
|
267
965
|
const ZED_TAG_KEYS = new Set([
|
|
@@ -271,6 +969,7 @@ const ZED_TAG_KEYS = new Set([
|
|
|
271
969
|
"Text",
|
|
272
970
|
"Mention",
|
|
273
971
|
"Image",
|
|
972
|
+
"Audio",
|
|
274
973
|
"Thinking",
|
|
275
974
|
"RedactedThinking",
|
|
276
975
|
"ToolUse"
|
|
@@ -298,10 +997,13 @@ function shouldSkipKeyRule(path) {
|
|
|
298
997
|
function shouldSkipDescend(path) {
|
|
299
998
|
return OPAQUE_VALUE_PATHS.has(joinPath(path)) || isToolResultOutputPath(path);
|
|
300
999
|
}
|
|
1000
|
+
function isToolResultOutputTail(path, toolResultsIndex) {
|
|
1001
|
+
return toolResultsIndex !== -1 && toolResultsIndex + 2 === path.length - 1;
|
|
1002
|
+
}
|
|
301
1003
|
function isToolResultOutputPath(path) {
|
|
302
1004
|
if (path.length < 5 || path[path.length - 1] !== "output") return false;
|
|
303
1005
|
const toolResultsIndex = path.lastIndexOf("tool_results");
|
|
304
|
-
if (
|
|
1006
|
+
if (!isToolResultOutputTail(path, toolResultsIndex)) return false;
|
|
305
1007
|
return path.slice(0, toolResultsIndex + 1).join(".") === "messages.Agent.tool_results";
|
|
306
1008
|
}
|
|
307
1009
|
function collectViolations(value, path, violations) {
|
|
@@ -311,12 +1013,12 @@ function collectViolations(value, path, violations) {
|
|
|
311
1013
|
}
|
|
312
1014
|
if (!isRecord(value)) return;
|
|
313
1015
|
const skipKeyRule = shouldSkipKeyRule(path);
|
|
314
|
-
for (const [key, child] of Object.entries(value))
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
1016
|
+
for (const [key, child] of Object.entries(value)) collectKeyViolation(child, key, path, skipKeyRule, violations);
|
|
1017
|
+
}
|
|
1018
|
+
function collectKeyViolation(child, key, path, skipKeyRule, violations) {
|
|
1019
|
+
if (!skipKeyRule && !SNAKE_CASE_KEY.test(key) && !isAllowedKey(path, key)) violations.push(`${joinPath(path)}.${key}`.replace(/^\./, ""));
|
|
1020
|
+
const childPath = [...path, key];
|
|
1021
|
+
if (!shouldSkipDescend(childPath)) collectViolations(child, childPath, violations);
|
|
320
1022
|
}
|
|
321
1023
|
function findPersistedKeyPolicyViolations(value) {
|
|
322
1024
|
const violations = [];
|
|
@@ -358,7 +1060,7 @@ function defaultSessionEventLog(sessionId) {
|
|
|
358
1060
|
}
|
|
359
1061
|
//#endregion
|
|
360
1062
|
//#region src/session/persistence/parse.ts
|
|
361
|
-
function asRecord$
|
|
1063
|
+
function asRecord$4(value) {
|
|
362
1064
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
363
1065
|
return value;
|
|
364
1066
|
}
|
|
@@ -370,7 +1072,7 @@ function isStringArray(value) {
|
|
|
370
1072
|
}
|
|
371
1073
|
function parseTokenUsage(raw) {
|
|
372
1074
|
if (raw === void 0 || raw === null) return;
|
|
373
|
-
const record = asRecord$
|
|
1075
|
+
const record = asRecord$4(raw);
|
|
374
1076
|
if (!record) return null;
|
|
375
1077
|
const usage = {};
|
|
376
1078
|
for (const field of [
|
|
@@ -381,14 +1083,17 @@ function parseTokenUsage(raw) {
|
|
|
381
1083
|
]) {
|
|
382
1084
|
const value = record[field];
|
|
383
1085
|
if (value === void 0) continue;
|
|
384
|
-
if (
|
|
1086
|
+
if (!isNonNegativeFiniteNumber(value)) return null;
|
|
385
1087
|
usage[field] = value;
|
|
386
1088
|
}
|
|
387
1089
|
return usage;
|
|
388
1090
|
}
|
|
1091
|
+
function isNonNegativeFiniteNumber(value) {
|
|
1092
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
1093
|
+
}
|
|
389
1094
|
function parseRequestTokenUsage(raw) {
|
|
390
1095
|
if (raw === void 0 || raw === null) return;
|
|
391
|
-
const record = asRecord$
|
|
1096
|
+
const record = asRecord$4(raw);
|
|
392
1097
|
if (!record) return null;
|
|
393
1098
|
const usage = {};
|
|
394
1099
|
for (const [key, value] of Object.entries(record)) {
|
|
@@ -399,62 +1104,81 @@ function parseRequestTokenUsage(raw) {
|
|
|
399
1104
|
return usage;
|
|
400
1105
|
}
|
|
401
1106
|
function isSessionMessageImage(raw) {
|
|
402
|
-
const record = asRecord$
|
|
1107
|
+
const record = asRecord$4(raw);
|
|
403
1108
|
if (!record || typeof record.source !== "string") return false;
|
|
404
1109
|
if (record.size === void 0 || record.size === null) return true;
|
|
405
|
-
const size = asRecord$
|
|
406
|
-
return !!size &&
|
|
1110
|
+
const size = asRecord$4(record.size);
|
|
1111
|
+
return !!size && isFiniteNumber(size.width) && isFiniteNumber(size.height);
|
|
1112
|
+
}
|
|
1113
|
+
function isSessionMessageAudio(raw) {
|
|
1114
|
+
const record = asRecord$4(raw);
|
|
1115
|
+
return !!record && typeof record.source === "string" && typeof record.mime_type === "string";
|
|
1116
|
+
}
|
|
1117
|
+
function isFiniteNumber(value) {
|
|
1118
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
407
1119
|
}
|
|
408
1120
|
function isUserContent(raw) {
|
|
409
|
-
const record = asRecord$
|
|
1121
|
+
const record = asRecord$4(raw);
|
|
410
1122
|
if (!record) return false;
|
|
411
1123
|
if (typeof record.Text === "string") return true;
|
|
412
1124
|
if (record.Mention !== void 0) {
|
|
413
|
-
const mention = asRecord$
|
|
1125
|
+
const mention = asRecord$4(record.Mention);
|
|
414
1126
|
return !!mention && typeof mention.uri === "string" && typeof mention.content === "string";
|
|
415
1127
|
}
|
|
416
1128
|
if (record.Image !== void 0) return isSessionMessageImage(record.Image);
|
|
1129
|
+
if (record.Audio !== void 0) return isSessionMessageAudio(record.Audio);
|
|
417
1130
|
return false;
|
|
418
1131
|
}
|
|
419
1132
|
function isToolUse(raw) {
|
|
420
|
-
const record = asRecord$
|
|
421
|
-
return !!record &&
|
|
1133
|
+
const record = asRecord$4(raw);
|
|
1134
|
+
return !!record && hasStringFields(record, [
|
|
1135
|
+
"id",
|
|
1136
|
+
"name",
|
|
1137
|
+
"raw_input"
|
|
1138
|
+
]) && hasOwn$1(record, "input") && typeof record.is_input_complete === "boolean" && isOptionalString(record.thought_signature);
|
|
1139
|
+
}
|
|
1140
|
+
function hasStringFields(record, keys) {
|
|
1141
|
+
return keys.every((key) => typeof record[key] === "string");
|
|
1142
|
+
}
|
|
1143
|
+
function isOptionalString(value) {
|
|
1144
|
+
return value === void 0 || value === null || typeof value === "string";
|
|
422
1145
|
}
|
|
423
1146
|
function isToolResultContent(raw) {
|
|
424
|
-
const record = asRecord$
|
|
1147
|
+
const record = asRecord$4(raw);
|
|
425
1148
|
if (!record) return false;
|
|
426
1149
|
if (typeof record.Text === "string") return true;
|
|
427
1150
|
if (record.Image !== void 0) return isSessionMessageImage(record.Image);
|
|
428
1151
|
return false;
|
|
429
1152
|
}
|
|
430
1153
|
function isToolResult(raw) {
|
|
431
|
-
const record = asRecord$
|
|
1154
|
+
const record = asRecord$4(raw);
|
|
432
1155
|
return !!record && typeof record.tool_use_id === "string" && typeof record.tool_name === "string" && typeof record.is_error === "boolean" && isToolResultContent(record.content);
|
|
433
1156
|
}
|
|
434
1157
|
function isAgentContent(raw) {
|
|
435
|
-
const record = asRecord$
|
|
1158
|
+
const record = asRecord$4(raw);
|
|
436
1159
|
if (!record) return false;
|
|
437
1160
|
if (typeof record.Text === "string") return true;
|
|
438
|
-
if (record.Thinking !== void 0)
|
|
439
|
-
const thinking = asRecord$3(record.Thinking);
|
|
440
|
-
return !!thinking && typeof thinking.text === "string" && (thinking.signature === void 0 || thinking.signature === null || typeof thinking.signature === "string");
|
|
441
|
-
}
|
|
1161
|
+
if (record.Thinking !== void 0) return isThinkingContent(record.Thinking);
|
|
442
1162
|
if (typeof record.RedactedThinking === "string") return true;
|
|
443
1163
|
if (record.ToolUse !== void 0) return isToolUse(record.ToolUse);
|
|
444
1164
|
return false;
|
|
445
1165
|
}
|
|
1166
|
+
function isThinkingContent(raw) {
|
|
1167
|
+
const thinking = asRecord$4(raw);
|
|
1168
|
+
return !!thinking && typeof thinking.text === "string" && isOptionalString(thinking.signature);
|
|
1169
|
+
}
|
|
446
1170
|
function isUserMessage$1(raw) {
|
|
447
|
-
const record = asRecord$
|
|
1171
|
+
const record = asRecord$4(raw);
|
|
448
1172
|
if (!record || record.User === void 0) return false;
|
|
449
|
-
const user = asRecord$
|
|
1173
|
+
const user = asRecord$4(record.User);
|
|
450
1174
|
return !!user && typeof user.id === "string" && Array.isArray(user.content) && user.content.every((entry) => isUserContent(entry));
|
|
451
1175
|
}
|
|
452
1176
|
function isAgentMessage$1(raw) {
|
|
453
|
-
const record = asRecord$
|
|
1177
|
+
const record = asRecord$4(raw);
|
|
454
1178
|
if (!record || record.Agent === void 0) return false;
|
|
455
|
-
const agent = asRecord$
|
|
1179
|
+
const agent = asRecord$4(record.Agent);
|
|
456
1180
|
if (!agent || !Array.isArray(agent.content) || !agent.content.every(isAgentContent)) return false;
|
|
457
|
-
const toolResults = asRecord$
|
|
1181
|
+
const toolResults = asRecord$4(agent.tool_results);
|
|
458
1182
|
if (!toolResults) return false;
|
|
459
1183
|
return Object.values(toolResults).every(isToolResult);
|
|
460
1184
|
}
|
|
@@ -462,56 +1186,88 @@ function isConversationMessage(raw) {
|
|
|
462
1186
|
return raw === "Resume" || isUserMessage$1(raw) || isAgentMessage$1(raw);
|
|
463
1187
|
}
|
|
464
1188
|
function parseConversationRecord(record) {
|
|
465
|
-
if (!
|
|
466
|
-
|
|
1189
|
+
if (!hasValidConversationCore(record)) return;
|
|
1190
|
+
const title = parseConversationTitle(record.title);
|
|
1191
|
+
if (title === INVALID_VALUE) return;
|
|
467
1192
|
const cumulativeTokenUsage = parseTokenUsage(record.cumulative_token_usage);
|
|
468
1193
|
const requestTokenUsage = parseRequestTokenUsage(record.request_token_usage);
|
|
469
1194
|
if (cumulativeTokenUsage === null || requestTokenUsage === null) return;
|
|
470
1195
|
return {
|
|
471
|
-
title
|
|
1196
|
+
title,
|
|
472
1197
|
messages: record.messages,
|
|
473
1198
|
updated_at: record.updated_at,
|
|
474
1199
|
cumulative_token_usage: cumulativeTokenUsage ?? {},
|
|
475
1200
|
request_token_usage: requestTokenUsage ?? {}
|
|
476
1201
|
};
|
|
477
1202
|
}
|
|
1203
|
+
const INVALID_VALUE = Symbol("invalid");
|
|
1204
|
+
function parseConversationTitle(value) {
|
|
1205
|
+
if (value === void 0 || value === null || typeof value === "string") return value;
|
|
1206
|
+
return INVALID_VALUE;
|
|
1207
|
+
}
|
|
1208
|
+
function hasValidConversationCore(record) {
|
|
1209
|
+
return Array.isArray(record.messages) && record.messages.every(isConversationMessage) && typeof record.updated_at === "string";
|
|
1210
|
+
}
|
|
478
1211
|
function parseAcpxState(raw) {
|
|
479
|
-
const record = asRecord$
|
|
1212
|
+
const record = asRecord$4(raw);
|
|
480
1213
|
if (!record) return;
|
|
481
1214
|
const state = {};
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const parsed = {};
|
|
488
|
-
for (const [key, value] of Object.entries(desiredConfigOptions)) if (typeof key === "string" && typeof value === "string") parsed[key] = value;
|
|
489
|
-
if (Object.keys(parsed).length > 0) state.desired_config_options = parsed;
|
|
490
|
-
}
|
|
491
|
-
if (typeof record.current_model_id === "string") state.current_model_id = record.current_model_id;
|
|
1215
|
+
assignBooleanTrue(state, "reset_on_next_ensure", record.reset_on_next_ensure);
|
|
1216
|
+
assignStringState(state, "current_mode_id", record.current_mode_id);
|
|
1217
|
+
assignStringState(state, "desired_mode_id", record.desired_mode_id);
|
|
1218
|
+
assignDesiredConfigOptions(state, record.desired_config_options);
|
|
1219
|
+
assignStringState(state, "current_model_id", record.current_model_id);
|
|
492
1220
|
if (isStringArray(record.available_models)) state.available_models = [...record.available_models];
|
|
493
1221
|
if (isStringArray(record.available_commands)) state.available_commands = [...record.available_commands];
|
|
494
1222
|
if (Array.isArray(record.config_options)) state.config_options = record.config_options;
|
|
495
|
-
|
|
496
|
-
if (sessionOptions) {
|
|
497
|
-
const parsedSessionOptions = {};
|
|
498
|
-
if (typeof sessionOptions.model === "string") parsedSessionOptions.model = sessionOptions.model;
|
|
499
|
-
if (isStringArray(sessionOptions.allowed_tools)) parsedSessionOptions.allowed_tools = [...sessionOptions.allowed_tools];
|
|
500
|
-
if (typeof sessionOptions.max_turns === "number" && Number.isInteger(sessionOptions.max_turns) && sessionOptions.max_turns > 0) parsedSessionOptions.max_turns = sessionOptions.max_turns;
|
|
501
|
-
const rawSystemPrompt = sessionOptions.system_prompt;
|
|
502
|
-
if (typeof rawSystemPrompt === "string" && rawSystemPrompt.length > 0) parsedSessionOptions.system_prompt = rawSystemPrompt;
|
|
503
|
-
else {
|
|
504
|
-
const appendRecord = asRecord$3(rawSystemPrompt);
|
|
505
|
-
if (appendRecord && typeof appendRecord.append === "string" && appendRecord.append.length > 0) parsedSessionOptions.system_prompt = { append: appendRecord.append };
|
|
506
|
-
}
|
|
507
|
-
if (Object.keys(parsedSessionOptions).length > 0) state.session_options = parsedSessionOptions;
|
|
508
|
-
}
|
|
1223
|
+
assignParsedSessionOptions(state, record.session_options);
|
|
509
1224
|
return state;
|
|
510
1225
|
}
|
|
1226
|
+
function assignBooleanTrue(state, key, value) {
|
|
1227
|
+
if (value === true) state[key] = true;
|
|
1228
|
+
}
|
|
1229
|
+
function assignStringState(state, key, value) {
|
|
1230
|
+
if (typeof value === "string") state[key] = value;
|
|
1231
|
+
}
|
|
1232
|
+
function assignDesiredConfigOptions(state, raw) {
|
|
1233
|
+
const desiredConfigOptions = asRecord$4(raw);
|
|
1234
|
+
if (!desiredConfigOptions) return;
|
|
1235
|
+
const parsed = Object.fromEntries(Object.entries(desiredConfigOptions).filter((entry) => {
|
|
1236
|
+
const [, value] = entry;
|
|
1237
|
+
return typeof value === "string";
|
|
1238
|
+
}));
|
|
1239
|
+
if (Object.keys(parsed).length > 0) state.desired_config_options = parsed;
|
|
1240
|
+
}
|
|
1241
|
+
function assignParsedSessionOptions(state, raw) {
|
|
1242
|
+
const sessionOptions = asRecord$4(raw);
|
|
1243
|
+
if (!sessionOptions) return;
|
|
1244
|
+
const parsedSessionOptions = {};
|
|
1245
|
+
assignSessionOptionModel(parsedSessionOptions, sessionOptions.model);
|
|
1246
|
+
assignSessionOptionAllowedTools(parsedSessionOptions, sessionOptions.allowed_tools);
|
|
1247
|
+
assignSessionOptionMaxTurns(parsedSessionOptions, sessionOptions.max_turns);
|
|
1248
|
+
assignSessionOptionSystemPrompt(parsedSessionOptions, sessionOptions.system_prompt);
|
|
1249
|
+
if (Object.keys(parsedSessionOptions).length > 0) state.session_options = parsedSessionOptions;
|
|
1250
|
+
}
|
|
1251
|
+
function assignSessionOptionModel(options, value) {
|
|
1252
|
+
if (typeof value === "string") options.model = value;
|
|
1253
|
+
}
|
|
1254
|
+
function assignSessionOptionAllowedTools(options, value) {
|
|
1255
|
+
if (isStringArray(value)) options.allowed_tools = [...value];
|
|
1256
|
+
}
|
|
1257
|
+
function assignSessionOptionMaxTurns(options, value) {
|
|
1258
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0) options.max_turns = value;
|
|
1259
|
+
}
|
|
1260
|
+
function assignSessionOptionSystemPrompt(options, value) {
|
|
1261
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1262
|
+
options.system_prompt = value;
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
const appendRecord = asRecord$4(value);
|
|
1266
|
+
if (appendRecord && typeof appendRecord.append === "string" && appendRecord.append.length > 0) options.system_prompt = { append: appendRecord.append };
|
|
1267
|
+
}
|
|
511
1268
|
function parseEventLog(raw, sessionId) {
|
|
512
|
-
const record = asRecord$
|
|
513
|
-
if (!record) return defaultSessionEventLog(sessionId);
|
|
514
|
-
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);
|
|
1269
|
+
const record = asRecord$4(raw);
|
|
1270
|
+
if (!record || !hasValidEventLogCore(record)) return defaultSessionEventLog(sessionId);
|
|
515
1271
|
return {
|
|
516
1272
|
active_path: record.active_path,
|
|
517
1273
|
segment_count: record.segment_count,
|
|
@@ -521,6 +1277,12 @@ function parseEventLog(raw, sessionId) {
|
|
|
521
1277
|
last_write_error: record.last_write_error == null || typeof record.last_write_error === "string" ? record.last_write_error : null
|
|
522
1278
|
};
|
|
523
1279
|
}
|
|
1280
|
+
function hasValidEventLogCore(record) {
|
|
1281
|
+
return typeof record.active_path === "string" && isPositiveInteger(record.segment_count) && isPositiveInteger(record.max_segment_bytes) && isPositiveInteger(record.max_segments);
|
|
1282
|
+
}
|
|
1283
|
+
function isPositiveInteger(value) {
|
|
1284
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
1285
|
+
}
|
|
524
1286
|
function normalizeOptionalName(value) {
|
|
525
1287
|
if (value == null) return;
|
|
526
1288
|
if (typeof value !== "string") return null;
|
|
@@ -553,20 +1315,22 @@ function normalizeOptionalSignal(value) {
|
|
|
553
1315
|
return Symbol("invalid");
|
|
554
1316
|
}
|
|
555
1317
|
function parseSessionRecord(raw) {
|
|
556
|
-
const record = asRecord$
|
|
1318
|
+
const record = asRecord$4(raw);
|
|
557
1319
|
if (!record) return null;
|
|
558
1320
|
if (record.schema !== "acpx.session.v1") return null;
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
1321
|
+
const optionals = validSessionOptionals({
|
|
1322
|
+
name: normalizeOptionalName(record.name),
|
|
1323
|
+
pid: normalizeOptionalPid(record.pid),
|
|
1324
|
+
closed: normalizeOptionalBoolean(record.closed, false),
|
|
1325
|
+
closedAt: normalizeOptionalString(record.closed_at),
|
|
1326
|
+
agentStartedAt: normalizeOptionalString(record.agent_started_at),
|
|
1327
|
+
lastPromptAt: normalizeOptionalString(record.last_prompt_at),
|
|
1328
|
+
lastAgentExitCode: normalizeOptionalExitCode(record.last_agent_exit_code),
|
|
1329
|
+
lastAgentExitSignal: normalizeOptionalSignal(record.last_agent_exit_signal),
|
|
1330
|
+
lastAgentExitAt: normalizeOptionalString(record.last_agent_exit_at),
|
|
1331
|
+
lastAgentDisconnectReason: normalizeOptionalString(record.last_agent_disconnect_reason)
|
|
1332
|
+
});
|
|
1333
|
+
if (!hasValidSessionRecordCore(record) || !optionals) return null;
|
|
570
1334
|
const conversation = parseConversationRecord(record);
|
|
571
1335
|
if (!conversation) return null;
|
|
572
1336
|
const eventLog = parseEventLog(record.event_log, record.acpx_record_id);
|
|
@@ -579,23 +1343,23 @@ function parseSessionRecord(raw) {
|
|
|
579
1343
|
agentSessionId: normalizeRuntimeSessionId(record.agent_session_id),
|
|
580
1344
|
agentCommand: record.agent_command,
|
|
581
1345
|
cwd: record.cwd,
|
|
582
|
-
name,
|
|
1346
|
+
name: optionals.name,
|
|
583
1347
|
createdAt: record.created_at,
|
|
584
1348
|
lastUsedAt: record.last_used_at,
|
|
585
1349
|
lastSeq: record.last_seq,
|
|
586
1350
|
lastRequestId,
|
|
587
1351
|
eventLog,
|
|
588
|
-
closed,
|
|
589
|
-
closedAt,
|
|
590
|
-
pid,
|
|
591
|
-
agentStartedAt,
|
|
592
|
-
lastPromptAt,
|
|
593
|
-
lastAgentExitCode,
|
|
594
|
-
lastAgentExitSignal,
|
|
595
|
-
lastAgentExitAt,
|
|
596
|
-
lastAgentDisconnectReason,
|
|
1352
|
+
closed: optionals.closed,
|
|
1353
|
+
closedAt: optionals.closedAt,
|
|
1354
|
+
pid: optionals.pid,
|
|
1355
|
+
agentStartedAt: optionals.agentStartedAt,
|
|
1356
|
+
lastPromptAt: optionals.lastPromptAt,
|
|
1357
|
+
lastAgentExitCode: optionals.lastAgentExitCode,
|
|
1358
|
+
lastAgentExitSignal: optionals.lastAgentExitSignal,
|
|
1359
|
+
lastAgentExitAt: optionals.lastAgentExitAt,
|
|
1360
|
+
lastAgentDisconnectReason: optionals.lastAgentDisconnectReason,
|
|
597
1361
|
protocolVersion: typeof record.protocol_version === "number" ? record.protocol_version : void 0,
|
|
598
|
-
agentCapabilities: asRecord$
|
|
1362
|
+
agentCapabilities: asRecord$4(record.agent_capabilities),
|
|
599
1363
|
title: conversation.title,
|
|
600
1364
|
messages: conversation.messages,
|
|
601
1365
|
updated_at: conversation.updated_at,
|
|
@@ -604,17 +1368,46 @@ function parseSessionRecord(raw) {
|
|
|
604
1368
|
acpx: parseAcpxState(record.acpx)
|
|
605
1369
|
};
|
|
606
1370
|
}
|
|
1371
|
+
function hasValidSessionRecordCore(record) {
|
|
1372
|
+
return hasStringFields(record, [
|
|
1373
|
+
"acpx_record_id",
|
|
1374
|
+
"acp_session_id",
|
|
1375
|
+
"agent_command",
|
|
1376
|
+
"cwd",
|
|
1377
|
+
"created_at",
|
|
1378
|
+
"last_used_at"
|
|
1379
|
+
]) && typeof record.last_seq === "number" && Number.isInteger(record.last_seq) && record.last_seq >= 0;
|
|
1380
|
+
}
|
|
1381
|
+
function validSessionOptionals(options) {
|
|
1382
|
+
if (hasNullOptionalSessionFields(options) || hasInvalidExitStatus(options)) return null;
|
|
1383
|
+
return options;
|
|
1384
|
+
}
|
|
1385
|
+
function hasNullOptionalSessionFields(options) {
|
|
1386
|
+
return [
|
|
1387
|
+
options.name,
|
|
1388
|
+
options.pid,
|
|
1389
|
+
options.closed,
|
|
1390
|
+
options.closedAt,
|
|
1391
|
+
options.agentStartedAt,
|
|
1392
|
+
options.lastPromptAt,
|
|
1393
|
+
options.lastAgentExitAt,
|
|
1394
|
+
options.lastAgentDisconnectReason
|
|
1395
|
+
].some((value) => value === null);
|
|
1396
|
+
}
|
|
1397
|
+
function hasInvalidExitStatus(options) {
|
|
1398
|
+
return typeof options.lastAgentExitCode === "symbol" || typeof options.lastAgentExitSignal === "symbol";
|
|
1399
|
+
}
|
|
607
1400
|
//#endregion
|
|
608
1401
|
//#region src/session/persistence/index.ts
|
|
609
1402
|
const SESSION_INDEX_SCHEMA = "acpx.session-index.v1";
|
|
610
|
-
function asRecord$
|
|
1403
|
+
function asRecord$3(value) {
|
|
611
1404
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
612
1405
|
return value;
|
|
613
1406
|
}
|
|
614
1407
|
function parseIndexEntry(raw) {
|
|
615
|
-
const record = asRecord$
|
|
1408
|
+
const record = asRecord$3(raw);
|
|
616
1409
|
if (!record) return;
|
|
617
|
-
if (
|
|
1410
|
+
if (!hasRequiredIndexEntryFields(record)) return;
|
|
618
1411
|
if (record.name !== void 0 && typeof record.name !== "string") return;
|
|
619
1412
|
return {
|
|
620
1413
|
file: record.file,
|
|
@@ -627,6 +1420,16 @@ function parseIndexEntry(raw) {
|
|
|
627
1420
|
lastUsedAt: record.lastUsedAt
|
|
628
1421
|
};
|
|
629
1422
|
}
|
|
1423
|
+
function hasRequiredIndexEntryFields(record) {
|
|
1424
|
+
return [
|
|
1425
|
+
"file",
|
|
1426
|
+
"acpxRecordId",
|
|
1427
|
+
"acpSessionId",
|
|
1428
|
+
"agentCommand",
|
|
1429
|
+
"cwd",
|
|
1430
|
+
"lastUsedAt"
|
|
1431
|
+
].every((key) => typeof record[key] === "string") && typeof record.closed === "boolean";
|
|
1432
|
+
}
|
|
630
1433
|
function sessionIndexPath(sessionDir) {
|
|
631
1434
|
return path.join(sessionDir, "index.json");
|
|
632
1435
|
}
|
|
@@ -646,7 +1449,7 @@ async function readSessionIndex(sessionDir) {
|
|
|
646
1449
|
const filePath = sessionIndexPath(sessionDir);
|
|
647
1450
|
try {
|
|
648
1451
|
const payload = await fs$1.readFile(filePath, "utf8");
|
|
649
|
-
const record = asRecord$
|
|
1452
|
+
const record = asRecord$3(JSON.parse(payload));
|
|
650
1453
|
if (!record || record.schema !== SESSION_INDEX_SCHEMA || !Array.isArray(record.files)) return;
|
|
651
1454
|
const files = record.files.filter((entry) => typeof entry === "string");
|
|
652
1455
|
if (files.length !== record.files.length || !Array.isArray(record.entries)) return;
|
|
@@ -838,13 +1641,17 @@ async function findSessionByDirectoryWalk(options) {
|
|
|
838
1641
|
for (;;) {
|
|
839
1642
|
const match = sessions.find((session) => matchesSessionEntry(session, current, normalizedName));
|
|
840
1643
|
if (match) return await loadRecordFromIndexEntry(match);
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
if (parent === current) return;
|
|
1644
|
+
const parent = nextWalkParent(current, walkBoundary, walkRoot);
|
|
1645
|
+
if (!parent) return;
|
|
844
1646
|
current = parent;
|
|
845
|
-
if (!isWithinBoundary(walkBoundary, current)) return;
|
|
846
1647
|
}
|
|
847
1648
|
}
|
|
1649
|
+
function nextWalkParent(current, walkBoundary, walkRoot) {
|
|
1650
|
+
if (current === walkBoundary || current === walkRoot) return;
|
|
1651
|
+
const parent = path.dirname(current);
|
|
1652
|
+
if (parent === current || !isWithinBoundary(walkBoundary, parent)) return;
|
|
1653
|
+
return parent;
|
|
1654
|
+
}
|
|
848
1655
|
function closedAtOrLastUsedAt(record) {
|
|
849
1656
|
return record.closedAt ?? record.lastUsedAt;
|
|
850
1657
|
}
|
|
@@ -853,14 +1660,7 @@ function isSessionStreamFile(fileName, safeId) {
|
|
|
853
1660
|
}
|
|
854
1661
|
async function pruneSessions(options = {}) {
|
|
855
1662
|
await ensureSessionDir();
|
|
856
|
-
|
|
857
|
-
if (options.agentCommand) eligible = eligible.filter((entry) => entry.agentCommand === options.agentCommand);
|
|
858
|
-
const cutoff = options.before ?? (options.olderThanMs != null ? new Date(Date.now() - options.olderThanMs) : void 0);
|
|
859
|
-
const records = [];
|
|
860
|
-
for (const entry of eligible) {
|
|
861
|
-
const record = await loadRecordFromIndexEntry(entry);
|
|
862
|
-
if (record && (!cutoff || closedAtOrLastUsedAt(record) < cutoff.toISOString())) records.push(record);
|
|
863
|
-
}
|
|
1663
|
+
const records = await loadPrunableRecords(filterPruneCandidates(await loadSessionIndexEntries(), options.agentCommand), options.before ?? (options.olderThanMs != null ? new Date(Date.now() - options.olderThanMs) : void 0));
|
|
864
1664
|
if (options.dryRun) return {
|
|
865
1665
|
pruned: records,
|
|
866
1666
|
bytesFreed: 0,
|
|
@@ -872,24 +1672,7 @@ async function pruneSessions(options = {}) {
|
|
|
872
1672
|
if (options.includeHistory) try {
|
|
873
1673
|
dirEntries = await fs$1.readdir(sessionDir);
|
|
874
1674
|
} catch {}
|
|
875
|
-
for (const record of records)
|
|
876
|
-
const safeId = encodeURIComponent(record.acpxRecordId);
|
|
877
|
-
const jsonFile = path.join(sessionDir, `${safeId}.json`);
|
|
878
|
-
try {
|
|
879
|
-
const stat = await fs$1.stat(jsonFile);
|
|
880
|
-
bytesFreed += stat.size;
|
|
881
|
-
} catch {}
|
|
882
|
-
await fs$1.unlink(jsonFile).catch(() => void 0);
|
|
883
|
-
if (options.includeHistory) for (const name of dirEntries) {
|
|
884
|
-
if (!isSessionStreamFile(name, safeId)) continue;
|
|
885
|
-
const filePath = path.join(sessionDir, name);
|
|
886
|
-
try {
|
|
887
|
-
const stat = await fs$1.stat(filePath);
|
|
888
|
-
bytesFreed += stat.size;
|
|
889
|
-
} catch {}
|
|
890
|
-
await fs$1.unlink(filePath).catch(() => void 0);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
1675
|
+
for (const record of records) bytesFreed += await pruneSessionFiles(record, sessionDir, dirEntries, options.includeHistory === true);
|
|
893
1676
|
await rebuildSessionIndex(sessionDir).catch(() => {});
|
|
894
1677
|
return {
|
|
895
1678
|
pruned: records,
|
|
@@ -897,6 +1680,35 @@ async function pruneSessions(options = {}) {
|
|
|
897
1680
|
dryRun: false
|
|
898
1681
|
};
|
|
899
1682
|
}
|
|
1683
|
+
function filterPruneCandidates(entries, agentCommand) {
|
|
1684
|
+
return entries.filter((entry) => entry.closed && (!agentCommand || entry.agentCommand === agentCommand));
|
|
1685
|
+
}
|
|
1686
|
+
async function loadPrunableRecords(entries, cutoff) {
|
|
1687
|
+
const records = [];
|
|
1688
|
+
const cutoffIso = cutoff?.toISOString();
|
|
1689
|
+
for (const entry of entries) {
|
|
1690
|
+
const record = await loadRecordFromIndexEntry(entry);
|
|
1691
|
+
if (record && isBeforeCutoff(record, cutoffIso)) records.push(record);
|
|
1692
|
+
}
|
|
1693
|
+
return records;
|
|
1694
|
+
}
|
|
1695
|
+
function isBeforeCutoff(record, cutoffIso) {
|
|
1696
|
+
return !cutoffIso || closedAtOrLastUsedAt(record) < cutoffIso;
|
|
1697
|
+
}
|
|
1698
|
+
async function pruneSessionFiles(record, sessionDir, dirEntries, includeHistory) {
|
|
1699
|
+
const safeId = encodeURIComponent(record.acpxRecordId);
|
|
1700
|
+
let bytesFreed = await unlinkCountingBytes(path.join(sessionDir, `${safeId}.json`));
|
|
1701
|
+
if (includeHistory) for (const name of dirEntries.filter((entry) => isSessionStreamFile(entry, safeId))) bytesFreed += await unlinkCountingBytes(path.join(sessionDir, name));
|
|
1702
|
+
return bytesFreed;
|
|
1703
|
+
}
|
|
1704
|
+
async function unlinkCountingBytes(filePath) {
|
|
1705
|
+
let bytes = 0;
|
|
1706
|
+
try {
|
|
1707
|
+
bytes = (await fs$1.stat(filePath)).size;
|
|
1708
|
+
} catch {}
|
|
1709
|
+
await fs$1.unlink(filePath).catch(() => void 0);
|
|
1710
|
+
return bytes;
|
|
1711
|
+
}
|
|
900
1712
|
//#endregion
|
|
901
1713
|
//#region src/permission-prompt.ts
|
|
902
1714
|
async function promptForPermission(options) {
|
|
@@ -1076,27 +1888,84 @@ function selected(optionId) {
|
|
|
1076
1888
|
function cancelled() {
|
|
1077
1889
|
return { outcome: { outcome: "cancelled" } };
|
|
1078
1890
|
}
|
|
1891
|
+
function withEscalationMetadata(response, event) {
|
|
1892
|
+
return {
|
|
1893
|
+
...response,
|
|
1894
|
+
_meta: {
|
|
1895
|
+
...response._meta,
|
|
1896
|
+
acpx: {
|
|
1897
|
+
...response._meta?.acpx && typeof response._meta.acpx === "object" && !Array.isArray(response._meta.acpx) ? response._meta.acpx : {},
|
|
1898
|
+
permissionEscalation: event
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1079
1903
|
function pickOption(options, kinds) {
|
|
1080
1904
|
for (const kind of kinds) {
|
|
1081
1905
|
const match = options.find((option) => option.kind === kind);
|
|
1082
1906
|
if (match) return match;
|
|
1083
1907
|
}
|
|
1084
1908
|
}
|
|
1909
|
+
const TOOL_KIND_TITLE_MATCHERS = [
|
|
1910
|
+
{
|
|
1911
|
+
kind: "read",
|
|
1912
|
+
needles: ["read", "cat"]
|
|
1913
|
+
},
|
|
1914
|
+
{
|
|
1915
|
+
kind: "search",
|
|
1916
|
+
needles: [
|
|
1917
|
+
"search",
|
|
1918
|
+
"find",
|
|
1919
|
+
"grep"
|
|
1920
|
+
]
|
|
1921
|
+
},
|
|
1922
|
+
{
|
|
1923
|
+
kind: "edit",
|
|
1924
|
+
needles: [
|
|
1925
|
+
"write",
|
|
1926
|
+
"edit",
|
|
1927
|
+
"patch"
|
|
1928
|
+
]
|
|
1929
|
+
},
|
|
1930
|
+
{
|
|
1931
|
+
kind: "delete",
|
|
1932
|
+
needles: ["delete", "remove"]
|
|
1933
|
+
},
|
|
1934
|
+
{
|
|
1935
|
+
kind: "move",
|
|
1936
|
+
needles: ["move", "rename"]
|
|
1937
|
+
},
|
|
1938
|
+
{
|
|
1939
|
+
kind: "execute",
|
|
1940
|
+
needles: [
|
|
1941
|
+
"run",
|
|
1942
|
+
"execute",
|
|
1943
|
+
"bash"
|
|
1944
|
+
]
|
|
1945
|
+
},
|
|
1946
|
+
{
|
|
1947
|
+
kind: "fetch",
|
|
1948
|
+
needles: [
|
|
1949
|
+
"fetch",
|
|
1950
|
+
"http",
|
|
1951
|
+
"url"
|
|
1952
|
+
]
|
|
1953
|
+
},
|
|
1954
|
+
{
|
|
1955
|
+
kind: "think",
|
|
1956
|
+
needles: ["think"]
|
|
1957
|
+
}
|
|
1958
|
+
];
|
|
1085
1959
|
function inferToolKind(params) {
|
|
1086
1960
|
if (params.toolCall.kind) return params.toolCall.kind;
|
|
1087
1961
|
const title = params.toolCall.title?.trim().toLowerCase();
|
|
1088
1962
|
if (!title) return;
|
|
1089
1963
|
const head = title.split(":", 1)[0]?.trim();
|
|
1090
1964
|
if (!head) return;
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
if (head.includes("move") || head.includes("rename")) return "move";
|
|
1096
|
-
if (head.includes("run") || head.includes("execute") || head.includes("bash")) return "execute";
|
|
1097
|
-
if (head.includes("fetch") || head.includes("http") || head.includes("url")) return "fetch";
|
|
1098
|
-
if (head.includes("think")) return "think";
|
|
1099
|
-
return "other";
|
|
1965
|
+
return titleHeadToolKind(head) ?? "other";
|
|
1966
|
+
}
|
|
1967
|
+
function titleHeadToolKind(head) {
|
|
1968
|
+
return TOOL_KIND_TITLE_MATCHERS.find(({ needles }) => needles.some((needle) => head.includes(needle)))?.kind;
|
|
1100
1969
|
}
|
|
1101
1970
|
function isAutoApprovedReadKind(kind) {
|
|
1102
1971
|
return kind === "read" || kind === "search";
|
|
@@ -1107,32 +1976,152 @@ async function promptForToolPermission(params) {
|
|
|
1107
1976
|
function canPromptForPermission$1() {
|
|
1108
1977
|
return process.stdin.isTTY && process.stderr.isTTY;
|
|
1109
1978
|
}
|
|
1110
|
-
function
|
|
1111
|
-
|
|
1979
|
+
function readStringProperty(value, keys) {
|
|
1980
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
1981
|
+
const record = value;
|
|
1982
|
+
for (const key of keys) {
|
|
1983
|
+
const entry = record[key];
|
|
1984
|
+
if (typeof entry === "string" && entry.trim().length > 0) return entry.trim();
|
|
1985
|
+
}
|
|
1112
1986
|
}
|
|
1113
|
-
|
|
1114
|
-
const
|
|
1115
|
-
|
|
1987
|
+
function readToolName(params) {
|
|
1988
|
+
const rawInputName = readStringProperty(params.toolCall.rawInput, [
|
|
1989
|
+
"name",
|
|
1990
|
+
"tool",
|
|
1991
|
+
"toolName"
|
|
1992
|
+
]);
|
|
1993
|
+
if (rawInputName) return rawInputName;
|
|
1994
|
+
const head = (params.toolCall.title?.trim())?.split(/[:\s]/, 1)[0]?.trim();
|
|
1995
|
+
return head && head.length > 0 ? head : void 0;
|
|
1996
|
+
}
|
|
1997
|
+
function normalizeMatcher(value) {
|
|
1998
|
+
return value.trim().toLowerCase();
|
|
1999
|
+
}
|
|
2000
|
+
function permissionMatchTokens(params) {
|
|
2001
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
2002
|
+
const kind = inferToolKind(params);
|
|
2003
|
+
const rawKind = params.toolCall.kind;
|
|
2004
|
+
const title = params.toolCall.title?.trim();
|
|
2005
|
+
const toolName = readToolName(params);
|
|
2006
|
+
for (const value of [
|
|
2007
|
+
kind,
|
|
2008
|
+
rawKind,
|
|
2009
|
+
title,
|
|
2010
|
+
toolName
|
|
2011
|
+
]) if (typeof value === "string" && value.trim().length > 0) tokens.add(normalizeMatcher(value));
|
|
2012
|
+
if (title) {
|
|
2013
|
+
const head = title.split(/[:\s]/, 1)[0]?.trim();
|
|
2014
|
+
if (head) tokens.add(normalizeMatcher(head));
|
|
2015
|
+
}
|
|
2016
|
+
return [...tokens];
|
|
2017
|
+
}
|
|
2018
|
+
function findPolicyRule(rules, params) {
|
|
2019
|
+
if (!rules || rules.length === 0) return;
|
|
2020
|
+
const tokens = permissionMatchTokens(params);
|
|
2021
|
+
for (const rule of rules) {
|
|
2022
|
+
const normalized = normalizeMatcher(rule);
|
|
2023
|
+
if (normalized === "*" || tokens.includes(normalized)) return rule;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
function matchPermissionPolicy(params, policy) {
|
|
2027
|
+
if (!policy) return;
|
|
2028
|
+
const denyRule = findPolicyRule(policy.autoDeny, params);
|
|
2029
|
+
if (denyRule) return {
|
|
2030
|
+
action: "deny",
|
|
2031
|
+
matchedRule: denyRule
|
|
2032
|
+
};
|
|
2033
|
+
const approveRule = findPolicyRule(policy.autoApprove, params);
|
|
2034
|
+
if (approveRule) return {
|
|
2035
|
+
action: "approve",
|
|
2036
|
+
matchedRule: approveRule
|
|
2037
|
+
};
|
|
2038
|
+
const escalateRule = findPolicyRule(policy.escalate, params);
|
|
2039
|
+
if (escalateRule) return {
|
|
2040
|
+
action: "escalate",
|
|
2041
|
+
matchedRule: escalateRule
|
|
2042
|
+
};
|
|
2043
|
+
return policy.defaultAction ? { action: policy.defaultAction } : void 0;
|
|
2044
|
+
}
|
|
2045
|
+
function buildEscalationEvent(params, matchedRule) {
|
|
2046
|
+
const toolKind = inferToolKind(params);
|
|
2047
|
+
const toolTitle = params.toolCall.title?.trim() || "tool";
|
|
2048
|
+
const toolName = readToolName(params);
|
|
2049
|
+
return {
|
|
2050
|
+
type: "permission_escalation",
|
|
2051
|
+
sessionId: params.sessionId,
|
|
2052
|
+
toolCallId: params.toolCall.toolCallId,
|
|
2053
|
+
...toolName ? { toolName } : {},
|
|
2054
|
+
toolTitle,
|
|
2055
|
+
...params.toolCall.rawInput !== void 0 ? { toolInput: params.toolCall.rawInput } : {},
|
|
2056
|
+
...toolKind ? { toolKind } : {},
|
|
2057
|
+
action: "escalate",
|
|
2058
|
+
...matchedRule ? { matchedRule } : {},
|
|
2059
|
+
message: `Permission escalation required for ${toolTitle}`,
|
|
2060
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
function selectedOrFirst(options, allowOption) {
|
|
2064
|
+
return { response: selected((allowOption ?? options[0]).optionId) };
|
|
2065
|
+
}
|
|
2066
|
+
function selectedOrCancelled(option) {
|
|
2067
|
+
return { response: option ? selected(option.optionId) : cancelled() };
|
|
2068
|
+
}
|
|
2069
|
+
async function resolveEscalatingPermissionRequest(params, policyMatch, allowOption, rejectOption) {
|
|
2070
|
+
if (canPromptForPermission$1()) return resolveInteractivePromptResult(params, allowOption, rejectOption);
|
|
2071
|
+
const escalation = buildEscalationEvent(params, policyMatch.matchedRule);
|
|
2072
|
+
return {
|
|
2073
|
+
response: withEscalationMetadata(rejectOption ? selected(rejectOption.optionId) : cancelled(), escalation),
|
|
2074
|
+
escalation
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
async function resolveInteractivePromptResult(params, allowOption, rejectOption) {
|
|
2078
|
+
const approved = await promptForToolPermission(params);
|
|
2079
|
+
if (approved && allowOption) return { response: selected(allowOption.optionId) };
|
|
2080
|
+
if (!approved && rejectOption) return { response: selected(rejectOption.optionId) };
|
|
2081
|
+
return { response: cancelled() };
|
|
2082
|
+
}
|
|
2083
|
+
function resolvePolicyMatch(params, policyMatch, options, allowOption, rejectOption) {
|
|
2084
|
+
if (policyMatch?.action === "approve") return selectedOrFirst(options, allowOption);
|
|
2085
|
+
if (policyMatch?.action === "deny") return selectedOrCancelled(rejectOption);
|
|
2086
|
+
if (policyMatch?.action === "escalate") return resolveEscalatingPermissionRequest(params, policyMatch, allowOption, rejectOption);
|
|
2087
|
+
}
|
|
2088
|
+
function resolveModeMatch(options, mode, allowOption, rejectOption) {
|
|
2089
|
+
if (mode === "approve-all") return selectedOrFirst(options, allowOption);
|
|
2090
|
+
if (mode === "deny-all") return selectedOrCancelled(rejectOption);
|
|
2091
|
+
}
|
|
2092
|
+
function resolveNonInteractivePermission(nonInteractivePolicy, rejectOption) {
|
|
2093
|
+
if (nonInteractivePolicy === "fail") throw new PermissionPromptUnavailableError();
|
|
2094
|
+
return selectedOrCancelled(rejectOption);
|
|
2095
|
+
}
|
|
2096
|
+
async function resolveReadOrPromptPermission(params, nonInteractivePolicy, allowOption, rejectOption) {
|
|
2097
|
+
if (isAutoApprovedReadKind(inferToolKind(params)) && allowOption) return { response: selected(allowOption.optionId) };
|
|
2098
|
+
if (!canPromptForPermission$1()) return resolveNonInteractivePermission(nonInteractivePolicy, rejectOption);
|
|
2099
|
+
return resolveInteractivePromptResult(params, allowOption, rejectOption);
|
|
2100
|
+
}
|
|
2101
|
+
function permissionModeSatisfies(actual, required) {
|
|
2102
|
+
return PERMISSION_MODE_RANK[actual] >= PERMISSION_MODE_RANK[required];
|
|
2103
|
+
}
|
|
2104
|
+
async function resolvePermissionRequestWithDetails(params, mode, nonInteractivePolicy = "deny", policy) {
|
|
2105
|
+
const options = params.options ?? [];
|
|
2106
|
+
if (options.length === 0) return { response: cancelled() };
|
|
1116
2107
|
const allowOption = pickOption(options, ["allow_once", "allow_always"]);
|
|
1117
2108
|
const rejectOption = pickOption(options, ["reject_once", "reject_always"]);
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
if (!approved && rejectOption) return selected(rejectOption.optionId);
|
|
1135
|
-
return cancelled();
|
|
2109
|
+
const resolvedByPolicy = await resolvePolicyMatch(params, matchPermissionPolicy(params, policy), options, allowOption, rejectOption);
|
|
2110
|
+
if (resolvedByPolicy) return resolvedByPolicy;
|
|
2111
|
+
const resolvedByMode = resolveModeMatch(options, mode, allowOption, rejectOption);
|
|
2112
|
+
if (resolvedByMode) return resolvedByMode;
|
|
2113
|
+
return resolveReadOrPromptPermission(params, nonInteractivePolicy, allowOption, rejectOption);
|
|
2114
|
+
}
|
|
2115
|
+
const DECISION_FALLBACK_ORDER = {
|
|
2116
|
+
allow_once: ["allow_once", "allow_always"],
|
|
2117
|
+
allow_always: ["allow_always", "allow_once"],
|
|
2118
|
+
reject_once: ["reject_once", "reject_always"],
|
|
2119
|
+
reject_always: ["reject_always", "reject_once"]
|
|
2120
|
+
};
|
|
2121
|
+
function decisionToResponse(params, decision) {
|
|
2122
|
+
if (decision.outcome === "cancel") return cancelled();
|
|
2123
|
+
const matched = pickOption(params.options ?? [], DECISION_FALLBACK_ORDER[decision.outcome]);
|
|
2124
|
+
return matched ? selected(matched.optionId) : cancelled();
|
|
1136
2125
|
}
|
|
1137
2126
|
function classifyPermissionDecision(params, response) {
|
|
1138
2127
|
if (response.outcome.outcome !== "selected") return "cancelled";
|
|
@@ -1148,21 +2137,35 @@ function readWindowsEnvValue(env, key) {
|
|
|
1148
2137
|
const matchedKey = Object.keys(env).find((entry) => entry.toUpperCase() === key);
|
|
1149
2138
|
return matchedKey ? env[matchedKey] : void 0;
|
|
1150
2139
|
}
|
|
1151
|
-
function
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
2140
|
+
function windowsExecutableExtensions(env) {
|
|
2141
|
+
return (readWindowsEnvValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
|
|
2142
|
+
}
|
|
2143
|
+
function commandCandidates(command, env) {
|
|
2144
|
+
if (path.extname(command).length > 0) return [command];
|
|
2145
|
+
return windowsExecutableExtensions(env).map((extension) => `${command}${extension}`);
|
|
2146
|
+
}
|
|
2147
|
+
function commandHasPath(command) {
|
|
2148
|
+
return command.includes("/") || command.includes("\\") || path.isAbsolute(command);
|
|
2149
|
+
}
|
|
2150
|
+
function resolveWindowsPathCommand(command, env) {
|
|
2151
|
+
const candidates = commandCandidates(command, env);
|
|
1155
2152
|
const pathValue = readWindowsEnvValue(env, "PATH");
|
|
1156
2153
|
if (!pathValue) return;
|
|
1157
2154
|
for (const directory of pathValue.split(";")) {
|
|
1158
|
-
const
|
|
1159
|
-
if (
|
|
1160
|
-
for (const candidate of candidates) {
|
|
1161
|
-
const resolved = path.join(trimmedDirectory, candidate);
|
|
1162
|
-
if (fs.existsSync(resolved)) return resolved;
|
|
1163
|
-
}
|
|
2155
|
+
const resolved = findExistingCommandInDirectory(directory, candidates);
|
|
2156
|
+
if (resolved) return resolved;
|
|
1164
2157
|
}
|
|
1165
2158
|
}
|
|
2159
|
+
function findExistingCommandInDirectory(directory, candidates) {
|
|
2160
|
+
const trimmedDirectory = directory.trim();
|
|
2161
|
+
if (trimmedDirectory.length === 0) return;
|
|
2162
|
+
return candidates.map((candidate) => path.join(trimmedDirectory, candidate)).find((resolved) => fs.existsSync(resolved));
|
|
2163
|
+
}
|
|
2164
|
+
function resolveWindowsCommand(command, env = process.env) {
|
|
2165
|
+
const candidates = commandCandidates(command, env);
|
|
2166
|
+
if (commandHasPath(command)) return candidates.find((candidate) => fs.existsSync(candidate));
|
|
2167
|
+
return resolveWindowsPathCommand(command, env);
|
|
2168
|
+
}
|
|
1166
2169
|
function shouldUseWindowsBatchShell(command, platform = process.platform, env = process.env) {
|
|
1167
2170
|
if (platform !== "win32") return false;
|
|
1168
2171
|
const resolvedCommand = resolveWindowsCommand(command, env) ?? command;
|
|
@@ -1176,6 +2179,30 @@ function buildSpawnCommandOptions(command, options, platform = process.platform,
|
|
|
1176
2179
|
shell: true
|
|
1177
2180
|
};
|
|
1178
2181
|
}
|
|
2182
|
+
function buildTerminalSpawnCommand(command, args) {
|
|
2183
|
+
return {
|
|
2184
|
+
command,
|
|
2185
|
+
args: args ?? [],
|
|
2186
|
+
killProcessGroup: false
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
function buildTerminalShellSpawnCommand(command, platform = process.platform) {
|
|
2190
|
+
if (platform === "win32") return {
|
|
2191
|
+
command: "cmd.exe",
|
|
2192
|
+
args: [
|
|
2193
|
+
"/d",
|
|
2194
|
+
"/s",
|
|
2195
|
+
"/c",
|
|
2196
|
+
command
|
|
2197
|
+
],
|
|
2198
|
+
killProcessGroup: true
|
|
2199
|
+
};
|
|
2200
|
+
return {
|
|
2201
|
+
command: "/bin/sh",
|
|
2202
|
+
args: ["-c", command],
|
|
2203
|
+
killProcessGroup: true
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
1179
2206
|
//#endregion
|
|
1180
2207
|
//#region src/acp/client-process.ts
|
|
1181
2208
|
const execFileAsync = promisify(execFile);
|
|
@@ -1231,32 +2258,16 @@ function splitCommandLine(value) {
|
|
|
1231
2258
|
let quote = null;
|
|
1232
2259
|
let escaping = false;
|
|
1233
2260
|
for (const ch of value) {
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
if (ch === quote) quote = null;
|
|
1245
|
-
else current += ch;
|
|
1246
|
-
continue;
|
|
1247
|
-
}
|
|
1248
|
-
if (ch === "'" || ch === "\"") {
|
|
1249
|
-
quote = ch;
|
|
1250
|
-
continue;
|
|
1251
|
-
}
|
|
1252
|
-
if (/\s/.test(ch)) {
|
|
1253
|
-
if (current.length > 0) {
|
|
1254
|
-
parts.push(current);
|
|
1255
|
-
current = "";
|
|
1256
|
-
}
|
|
1257
|
-
continue;
|
|
1258
|
-
}
|
|
1259
|
-
current += ch;
|
|
2261
|
+
const next = readCommandLineChar({
|
|
2262
|
+
ch,
|
|
2263
|
+
current,
|
|
2264
|
+
quote,
|
|
2265
|
+
escaping,
|
|
2266
|
+
parts
|
|
2267
|
+
});
|
|
2268
|
+
current = next.current;
|
|
2269
|
+
quote = next.quote;
|
|
2270
|
+
escaping = next.escaping;
|
|
1260
2271
|
}
|
|
1261
2272
|
if (escaping) current += "\\";
|
|
1262
2273
|
if (quote) throw new Error("Invalid --agent command: unterminated quote");
|
|
@@ -1267,6 +2278,59 @@ function splitCommandLine(value) {
|
|
|
1267
2278
|
args: parts.slice(1)
|
|
1268
2279
|
};
|
|
1269
2280
|
}
|
|
2281
|
+
function readCommandLineChar(state) {
|
|
2282
|
+
if (state.escaping) return {
|
|
2283
|
+
current: state.current + state.ch,
|
|
2284
|
+
quote: state.quote,
|
|
2285
|
+
escaping: false
|
|
2286
|
+
};
|
|
2287
|
+
if (state.ch === "\\" && state.quote !== "'") return {
|
|
2288
|
+
current: state.current,
|
|
2289
|
+
quote: state.quote,
|
|
2290
|
+
escaping: true
|
|
2291
|
+
};
|
|
2292
|
+
if (state.quote) return readQuotedCommandLineChar({
|
|
2293
|
+
ch: state.ch,
|
|
2294
|
+
current: state.current,
|
|
2295
|
+
quote: state.quote
|
|
2296
|
+
});
|
|
2297
|
+
return readUnquotedCommandLineChar(state);
|
|
2298
|
+
}
|
|
2299
|
+
function readQuotedCommandLineChar(state) {
|
|
2300
|
+
if (state.ch === state.quote) return {
|
|
2301
|
+
current: state.current,
|
|
2302
|
+
quote: null,
|
|
2303
|
+
escaping: false
|
|
2304
|
+
};
|
|
2305
|
+
return {
|
|
2306
|
+
current: state.current + state.ch,
|
|
2307
|
+
quote: state.quote,
|
|
2308
|
+
escaping: false
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
function readUnquotedCommandLineChar(state) {
|
|
2312
|
+
if (state.ch === "'" || state.ch === "\"") return {
|
|
2313
|
+
current: state.current,
|
|
2314
|
+
quote: state.ch,
|
|
2315
|
+
escaping: false
|
|
2316
|
+
};
|
|
2317
|
+
if (/\s/.test(state.ch)) {
|
|
2318
|
+
flushCommandLinePart(state.parts, state.current);
|
|
2319
|
+
return {
|
|
2320
|
+
current: "",
|
|
2321
|
+
quote: null,
|
|
2322
|
+
escaping: false
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
return {
|
|
2326
|
+
current: state.current + state.ch,
|
|
2327
|
+
quote: null,
|
|
2328
|
+
escaping: false
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
function flushCommandLinePart(parts, current) {
|
|
2332
|
+
if (current.length > 0) parts.push(current);
|
|
2333
|
+
}
|
|
1270
2334
|
function asAbsoluteCwd(cwd) {
|
|
1271
2335
|
return path.resolve(cwd);
|
|
1272
2336
|
}
|
|
@@ -1480,17 +2544,28 @@ async function ensureCopilotAcpSupport(command) {
|
|
|
1480
2544
|
function buildClaudeCodeOptionsMeta(options) {
|
|
1481
2545
|
if (!options) return;
|
|
1482
2546
|
const claudeCodeOptions = {};
|
|
1483
|
-
|
|
1484
|
-
if (Array.isArray(options.allowedTools)) claudeCodeOptions.allowedTools = [...options.allowedTools];
|
|
1485
|
-
if (typeof options.maxTurns === "number") claudeCodeOptions.maxTurns = options.maxTurns;
|
|
2547
|
+
assignClaudeCodeOptions(claudeCodeOptions, options);
|
|
1486
2548
|
const meta = {};
|
|
1487
2549
|
if (Object.keys(claudeCodeOptions).length > 0) meta.claudeCode = { options: claudeCodeOptions };
|
|
1488
|
-
|
|
1489
|
-
if (typeof systemPrompt === "string" && systemPrompt.length > 0) meta.systemPrompt = systemPrompt;
|
|
1490
|
-
else if (systemPrompt && typeof systemPrompt === "object" && typeof systemPrompt.append === "string" && systemPrompt.append.length > 0) meta.systemPrompt = { append: systemPrompt.append };
|
|
2550
|
+
assignClaudeCodeSystemPrompt(meta, options.systemPrompt);
|
|
1491
2551
|
if (Object.keys(meta).length === 0) return;
|
|
1492
2552
|
return meta;
|
|
1493
2553
|
}
|
|
2554
|
+
function assignClaudeCodeOptions(target, options) {
|
|
2555
|
+
if (typeof options.model === "string" && options.model.trim().length > 0) target.model = options.model;
|
|
2556
|
+
if (Array.isArray(options.allowedTools)) target.allowedTools = [...options.allowedTools];
|
|
2557
|
+
if (typeof options.maxTurns === "number") target.maxTurns = options.maxTurns;
|
|
2558
|
+
}
|
|
2559
|
+
function assignClaudeCodeSystemPrompt(target, systemPrompt) {
|
|
2560
|
+
if (typeof systemPrompt === "string" && systemPrompt.length > 0) {
|
|
2561
|
+
target.systemPrompt = systemPrompt;
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
2564
|
+
if (isAppendSystemPrompt(systemPrompt)) target.systemPrompt = { append: systemPrompt.append };
|
|
2565
|
+
}
|
|
2566
|
+
function isAppendSystemPrompt(value) {
|
|
2567
|
+
return !!value && typeof value === "object" && typeof value.append === "string" && value.append.length > 0;
|
|
2568
|
+
}
|
|
1494
2569
|
function resolveClaudeCodeExecutable(platform = process.platform, env = process.env) {
|
|
1495
2570
|
if (platform !== "win32") return;
|
|
1496
2571
|
if (readWindowsEnvValue(env, "CLAUDE_CODE_EXECUTABLE")) return;
|
|
@@ -1535,18 +2610,21 @@ function buildAgentEnvironment(authCredentials) {
|
|
|
1535
2610
|
const env = { ...process.env };
|
|
1536
2611
|
promotePrefixedAuthEnvironment(env);
|
|
1537
2612
|
if (!authCredentials) return env;
|
|
1538
|
-
for (const [methodId, credential] of Object.entries(authCredentials))
|
|
1539
|
-
if (typeof credential !== "string" || credential.trim().length === 0) continue;
|
|
1540
|
-
if (!methodId.includes("=") && !methodId.includes("\0") && env[methodId] == null) env[methodId] = credential;
|
|
1541
|
-
const normalized = toEnvToken(methodId);
|
|
1542
|
-
if (normalized) {
|
|
1543
|
-
const prefixed = `${AUTH_ENV_PREFIX}${normalized}`;
|
|
1544
|
-
if (env[prefixed] == null) env[prefixed] = credential;
|
|
1545
|
-
if (env[normalized] == null) env[normalized] = credential;
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
2613
|
+
for (const [methodId, credential] of Object.entries(authCredentials)) assignAuthCredentialEnv(env, methodId, credential);
|
|
1548
2614
|
return env;
|
|
1549
2615
|
}
|
|
2616
|
+
function assignAuthCredentialEnv(env, methodId, credential) {
|
|
2617
|
+
if (typeof credential !== "string" || credential.trim().length === 0) return;
|
|
2618
|
+
if (!methodId.includes("=") && !methodId.includes("\0") && env[methodId] == null) env[methodId] = credential;
|
|
2619
|
+
const normalized = toEnvToken(methodId);
|
|
2620
|
+
if (normalized) {
|
|
2621
|
+
assignIfMissing(env, `${AUTH_ENV_PREFIX}${normalized}`, credential);
|
|
2622
|
+
assignIfMissing(env, normalized, credential);
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
function assignIfMissing(env, key, value) {
|
|
2626
|
+
if (env[key] == null) env[key] = value;
|
|
2627
|
+
}
|
|
1550
2628
|
function resolveConfiguredAuthCredential(methodId, authCredentials) {
|
|
1551
2629
|
const configCredentials = authCredentials ?? {};
|
|
1552
2630
|
return configCredentials[methodId] ?? configCredentials[toEnvToken(methodId)];
|
|
@@ -1564,6 +2642,76 @@ function buildAgentSpawnOptions(cwd, authCredentials) {
|
|
|
1564
2642
|
};
|
|
1565
2643
|
}
|
|
1566
2644
|
//#endregion
|
|
2645
|
+
//#region src/acp/jsonrpc.ts
|
|
2646
|
+
function asRecord$2(value) {
|
|
2647
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
2648
|
+
return value;
|
|
2649
|
+
}
|
|
2650
|
+
function hasValidId(value) {
|
|
2651
|
+
return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
2652
|
+
}
|
|
2653
|
+
function isErrorObject(value) {
|
|
2654
|
+
const record = asRecord$2(value);
|
|
2655
|
+
return !!record && typeof record.code === "number" && Number.isFinite(record.code) && typeof record.message === "string";
|
|
2656
|
+
}
|
|
2657
|
+
function hasResultOrError(value) {
|
|
2658
|
+
const hasResult = Object.hasOwn(value, "result");
|
|
2659
|
+
const hasError = Object.hasOwn(value, "error");
|
|
2660
|
+
if (hasResult && hasError) return false;
|
|
2661
|
+
if (!hasResult && !hasError) return false;
|
|
2662
|
+
if (hasError && !isErrorObject(value.error)) return false;
|
|
2663
|
+
return true;
|
|
2664
|
+
}
|
|
2665
|
+
function hasMethod(value) {
|
|
2666
|
+
return typeof value.method === "string" && value.method.length > 0;
|
|
2667
|
+
}
|
|
2668
|
+
function isJsonRpcRequest(value) {
|
|
2669
|
+
return hasMethod(value) && Object.hasOwn(value, "id") && hasValidId(value.id);
|
|
2670
|
+
}
|
|
2671
|
+
function isJsonRpcNotificationRecord(value) {
|
|
2672
|
+
return hasMethod(value) && !Object.hasOwn(value, "id");
|
|
2673
|
+
}
|
|
2674
|
+
function isJsonRpcResponse(value) {
|
|
2675
|
+
if (hasMethod(value) || !Object.hasOwn(value, "id") || !hasValidId(value.id)) return false;
|
|
2676
|
+
return hasResultOrError(value);
|
|
2677
|
+
}
|
|
2678
|
+
function isAcpJsonRpcMessage(value) {
|
|
2679
|
+
const record = asRecord$2(value);
|
|
2680
|
+
if (!record || record.jsonrpc !== "2.0") return false;
|
|
2681
|
+
return isJsonRpcNotificationRecord(record) || isJsonRpcRequest(record) || isJsonRpcResponse(record);
|
|
2682
|
+
}
|
|
2683
|
+
function isJsonRpcNotification(message) {
|
|
2684
|
+
return Object.hasOwn(message, "method") && typeof message.method === "string" && !Object.hasOwn(message, "id");
|
|
2685
|
+
}
|
|
2686
|
+
function isSessionUpdateNotification(message) {
|
|
2687
|
+
return isJsonRpcNotification(message) && message.method === "session/update";
|
|
2688
|
+
}
|
|
2689
|
+
function extractSessionUpdateNotification(message) {
|
|
2690
|
+
if (!isSessionUpdateNotification(message)) return;
|
|
2691
|
+
const params = asRecord$2(message.params);
|
|
2692
|
+
if (!params) return;
|
|
2693
|
+
const sessionId = typeof params.sessionId === "string" ? params.sessionId : null;
|
|
2694
|
+
if (!sessionId) return;
|
|
2695
|
+
const update = asRecord$2(params.update);
|
|
2696
|
+
if (!update || typeof update.sessionUpdate !== "string") return;
|
|
2697
|
+
return {
|
|
2698
|
+
sessionId,
|
|
2699
|
+
update
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
function parsePromptStopReason(message) {
|
|
2703
|
+
if (!Object.hasOwn(message, "id") || !Object.hasOwn(message, "result")) return;
|
|
2704
|
+
const record = asRecord$2(message.result);
|
|
2705
|
+
if (!record) return;
|
|
2706
|
+
return typeof record.stopReason === "string" ? record.stopReason : void 0;
|
|
2707
|
+
}
|
|
2708
|
+
function parseJsonRpcErrorMessage(message) {
|
|
2709
|
+
if (!Object.hasOwn(message, "error")) return;
|
|
2710
|
+
const errorRecord = asRecord$2(message.error);
|
|
2711
|
+
if (!errorRecord || typeof errorRecord.message !== "string") return;
|
|
2712
|
+
return errorRecord.message;
|
|
2713
|
+
}
|
|
2714
|
+
//#endregion
|
|
1567
2715
|
//#region src/acp/session-control-errors.ts
|
|
1568
2716
|
const SESSION_CONTROL_UNSUPPORTED_ACP_CODES = new Set([-32601, -32602]);
|
|
1569
2717
|
function asRecord$1(value) {
|
|
@@ -1687,14 +2835,15 @@ var TerminalManager = class {
|
|
|
1687
2835
|
try {
|
|
1688
2836
|
if (!await this.isExecuteApproved(commandLine)) throw new PermissionDeniedError("Permission denied for terminal/create");
|
|
1689
2837
|
const outputByteLimit = Math.max(0, Math.round(params.outputByteLimit ?? DEFAULT_TERMINAL_OUTPUT_LIMIT_BYTES));
|
|
1690
|
-
const proc
|
|
1691
|
-
await waitForSpawn(proc);
|
|
2838
|
+
const { proc, spawnCommand } = await spawnTerminalProcess(params, this.cwd);
|
|
1692
2839
|
let resolveExit = () => {};
|
|
1693
2840
|
const exitPromise = new Promise((resolve) => {
|
|
1694
2841
|
resolveExit = resolve;
|
|
1695
2842
|
});
|
|
1696
2843
|
const terminal = {
|
|
1697
2844
|
process: proc,
|
|
2845
|
+
killProcessGroup: spawnCommand.killProcessGroup,
|
|
2846
|
+
descendantPids: /* @__PURE__ */ new Set(),
|
|
1698
2847
|
output: Buffer.alloc(0),
|
|
1699
2848
|
truncated: false,
|
|
1700
2849
|
outputByteLimit,
|
|
@@ -1717,10 +2866,14 @@ var TerminalManager = class {
|
|
|
1717
2866
|
proc.once("exit", (exitCode, signal) => {
|
|
1718
2867
|
terminal.exitCode = exitCode;
|
|
1719
2868
|
terminal.signal = signal;
|
|
1720
|
-
terminal.
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
2869
|
+
terminal.processGroupSnapshotPromise = rememberProcessGroupPids(terminal);
|
|
2870
|
+
(async () => {
|
|
2871
|
+
await terminal.processGroupSnapshotPromise;
|
|
2872
|
+
terminal.resolveExit({
|
|
2873
|
+
exitCode: exitCode ?? null,
|
|
2874
|
+
signal: signal ?? null
|
|
2875
|
+
});
|
|
2876
|
+
})();
|
|
1724
2877
|
});
|
|
1725
2878
|
const terminalId = randomUUID();
|
|
1726
2879
|
this.terminals.set(terminalId, terminal);
|
|
@@ -1872,21 +3025,301 @@ var TerminalManager = class {
|
|
|
1872
3025
|
return terminal.exitCode === void 0 && terminal.signal === void 0;
|
|
1873
3026
|
}
|
|
1874
3027
|
async killProcess(terminal) {
|
|
1875
|
-
if (!this.isRunning(terminal)) return;
|
|
3028
|
+
if (!this.isRunning(terminal) && !terminal.killProcessGroup) return;
|
|
1876
3029
|
try {
|
|
1877
|
-
|
|
3030
|
+
await this.signalProcess(terminal, "SIGTERM");
|
|
1878
3031
|
} catch {
|
|
1879
3032
|
return;
|
|
1880
3033
|
}
|
|
1881
|
-
if (await
|
|
3034
|
+
if (await this.waitForCleanupAfterSignal(terminal) && !terminal.killProcessGroup) return;
|
|
1882
3035
|
try {
|
|
1883
|
-
|
|
3036
|
+
await this.signalProcess(terminal, "SIGKILL");
|
|
1884
3037
|
} catch {
|
|
1885
3038
|
return;
|
|
1886
3039
|
}
|
|
1887
|
-
await
|
|
3040
|
+
await this.waitForCleanupAfterSignal(terminal);
|
|
3041
|
+
}
|
|
3042
|
+
async signalProcess(terminal, signal) {
|
|
3043
|
+
const pid = terminal.process.pid;
|
|
3044
|
+
if (terminal.killProcessGroup && pid && process.platform === "win32") {
|
|
3045
|
+
await this.signalWindowsProcessGroup(terminal, pid, signal);
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
3048
|
+
if (terminal.killProcessGroup && pid) {
|
|
3049
|
+
await this.signalPosixProcessGroup(terminal, pid, signal);
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
terminal.process.kill(signal);
|
|
3053
|
+
}
|
|
3054
|
+
async signalWindowsProcessGroup(terminal, pid, signal) {
|
|
3055
|
+
await this.captureDescendantPids(terminal, pid);
|
|
3056
|
+
if (this.isRunning(terminal)) {
|
|
3057
|
+
await killWindowsProcessTree(pid, signal);
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
for (const descendantPid of terminal.descendantPids) await killWindowsProcessTree(descendantPid, signal);
|
|
3061
|
+
}
|
|
3062
|
+
async signalPosixProcessGroup(terminal, pid, signal) {
|
|
3063
|
+
await this.captureDescendantPids(terminal, pid);
|
|
3064
|
+
if (hasLiveProcessGroup(pid)) {
|
|
3065
|
+
sendSignal(-pid, signal);
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
3068
|
+
for (const descendantPid of terminal.descendantPids) sendSignal(descendantPid, signal);
|
|
3069
|
+
}
|
|
3070
|
+
async captureDescendantPids(terminal, pid) {
|
|
3071
|
+
if (!this.isRunning(terminal)) await terminal.processGroupSnapshotPromise?.catch(() => {});
|
|
3072
|
+
for (const descendantPid of await listDescendantPids(pid)) terminal.descendantPids.add(descendantPid);
|
|
3073
|
+
}
|
|
3074
|
+
async waitForCleanupAfterSignal(terminal) {
|
|
3075
|
+
return await Promise.race([this.waitForTerminalAndTrackedDescendants(terminal).then(() => true), waitMs(this.killGraceMs).then(() => false)]);
|
|
3076
|
+
}
|
|
3077
|
+
async waitForTerminalAndTrackedDescendants(terminal) {
|
|
3078
|
+
await terminal.exitPromise;
|
|
3079
|
+
while (hasLiveTerminalProcessGroup(terminal)) await waitMs(25);
|
|
3080
|
+
while (hasLivePid(terminal.descendantPids)) await waitMs(25);
|
|
1888
3081
|
}
|
|
1889
3082
|
};
|
|
3083
|
+
async function spawnTerminalProcess(params, defaultCwd) {
|
|
3084
|
+
const directCommand = buildTerminalSpawnCommand(params.command, params.args);
|
|
3085
|
+
try {
|
|
3086
|
+
return {
|
|
3087
|
+
proc: await spawnAndWait(directCommand, params, defaultCwd),
|
|
3088
|
+
spawnCommand: directCommand
|
|
3089
|
+
};
|
|
3090
|
+
} catch (error) {
|
|
3091
|
+
const fallbackCommand = params.args === void 0 && isNotFoundSpawnError(error) ? buildTerminalFallbackSpawnCommand(params.command, params.cwd ?? defaultCwd) : void 0;
|
|
3092
|
+
if (!fallbackCommand) throw error;
|
|
3093
|
+
return {
|
|
3094
|
+
proc: await spawnAndWait(fallbackCommand, params, defaultCwd),
|
|
3095
|
+
spawnCommand: fallbackCommand
|
|
3096
|
+
};
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
async function spawnAndWait(spawnCommand, params, defaultCwd) {
|
|
3100
|
+
const spawnOptions = buildTerminalSpawnOptions(spawnCommand.command, params.cwd ?? defaultCwd, params.env);
|
|
3101
|
+
if (spawnCommand.killProcessGroup) spawnOptions.detached = true;
|
|
3102
|
+
const proc = spawn(spawnCommand.command, spawnCommand.args, spawnOptions);
|
|
3103
|
+
await waitForSpawn(proc);
|
|
3104
|
+
return proc;
|
|
3105
|
+
}
|
|
3106
|
+
function isNotFoundSpawnError(error) {
|
|
3107
|
+
return error instanceof Error && error.code === "ENOENT";
|
|
3108
|
+
}
|
|
3109
|
+
function buildTerminalFallbackSpawnCommand(command, cwd, platform = process.platform) {
|
|
3110
|
+
if (commandPathExists(command, cwd)) return;
|
|
3111
|
+
if (platform === "win32") return hasWindowsShellSyntax(command) || /\s/u.test(command) ? buildTerminalShellSpawnCommand(command, platform) : void 0;
|
|
3112
|
+
if (hasShellSyntax(command) || /\s/u.test(command)) return buildTerminalShellSpawnCommand(command, platform);
|
|
3113
|
+
}
|
|
3114
|
+
function hasShellSyntax(command) {
|
|
3115
|
+
return /[|&;<>()>$`*?[\]{}'"\\\r\n]/u.test(command);
|
|
3116
|
+
}
|
|
3117
|
+
function hasWindowsShellSyntax(command) {
|
|
3118
|
+
return /[|&;<>()>$`*?[\]{}'"\r\n]/u.test(command);
|
|
3119
|
+
}
|
|
3120
|
+
function commandPathExists(command, cwd) {
|
|
3121
|
+
if (!/[\\/]/u.test(command)) return false;
|
|
3122
|
+
const resolvedPath = path.isAbsolute(command) ? command : path.resolve(cwd, command);
|
|
3123
|
+
return fs.existsSync(resolvedPath);
|
|
3124
|
+
}
|
|
3125
|
+
async function listDescendantPids(rootPid) {
|
|
3126
|
+
let output;
|
|
3127
|
+
try {
|
|
3128
|
+
output = await runProcessListCommand();
|
|
3129
|
+
} catch {
|
|
3130
|
+
return [];
|
|
3131
|
+
}
|
|
3132
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
3133
|
+
for (const line of output.split("\n")) addProcessListLine(childrenByParent, line);
|
|
3134
|
+
const descendants = [];
|
|
3135
|
+
const queue = [...childrenByParent.get(rootPid) ?? []];
|
|
3136
|
+
for (let index = 0; index < queue.length; index += 1) {
|
|
3137
|
+
const pid = queue[index];
|
|
3138
|
+
descendants.push(pid);
|
|
3139
|
+
queue.push(...childrenByParent.get(pid) ?? []);
|
|
3140
|
+
}
|
|
3141
|
+
return descendants;
|
|
3142
|
+
}
|
|
3143
|
+
function addProcessListLine(childrenByParent, line) {
|
|
3144
|
+
const parsed = parseProcessListLine(line);
|
|
3145
|
+
if (!parsed) return;
|
|
3146
|
+
const children = childrenByParent.get(parsed.parentPid);
|
|
3147
|
+
if (children) children.push(parsed.pid);
|
|
3148
|
+
else childrenByParent.set(parsed.parentPid, [parsed.pid]);
|
|
3149
|
+
}
|
|
3150
|
+
function parseProcessListLine(line) {
|
|
3151
|
+
const match = line.trim().match(/^(\d+)\s+(\d+)$/);
|
|
3152
|
+
if (!match) return;
|
|
3153
|
+
const pid = Number(match[1]);
|
|
3154
|
+
const parentPid = Number(match[2]);
|
|
3155
|
+
if (!Number.isInteger(pid) || !Number.isInteger(parentPid) || pid <= 0 || parentPid <= 0) return;
|
|
3156
|
+
return {
|
|
3157
|
+
pid,
|
|
3158
|
+
parentPid
|
|
3159
|
+
};
|
|
3160
|
+
}
|
|
3161
|
+
async function runProcessListCommand() {
|
|
3162
|
+
if (process.platform === "win32") return await runWindowsProcessListCommand();
|
|
3163
|
+
return await new Promise((resolve, reject) => {
|
|
3164
|
+
const child = spawn("ps", ["-eo", "pid=,ppid="], { stdio: [
|
|
3165
|
+
"ignore",
|
|
3166
|
+
"pipe",
|
|
3167
|
+
"pipe"
|
|
3168
|
+
] });
|
|
3169
|
+
let stdout = "";
|
|
3170
|
+
let stderr = "";
|
|
3171
|
+
child.stdout.setEncoding("utf8");
|
|
3172
|
+
child.stderr.setEncoding("utf8");
|
|
3173
|
+
child.stdout.on("data", (chunk) => {
|
|
3174
|
+
stdout += chunk;
|
|
3175
|
+
});
|
|
3176
|
+
child.stderr.on("data", (chunk) => {
|
|
3177
|
+
stderr += chunk;
|
|
3178
|
+
});
|
|
3179
|
+
child.once("error", reject);
|
|
3180
|
+
child.once("close", (code, signal) => {
|
|
3181
|
+
if (code === 0) {
|
|
3182
|
+
resolve(stdout);
|
|
3183
|
+
return;
|
|
3184
|
+
}
|
|
3185
|
+
reject(/* @__PURE__ */ new Error(`ps exited with code ${code ?? "null"} signal ${signal ?? "null"}: ${stderr}`));
|
|
3186
|
+
});
|
|
3187
|
+
});
|
|
3188
|
+
}
|
|
3189
|
+
async function rememberProcessGroupPids(terminal) {
|
|
3190
|
+
const processGroupId = terminal.process.pid;
|
|
3191
|
+
if (!terminal.killProcessGroup || !processGroupId) return;
|
|
3192
|
+
if (process.platform === "win32") {
|
|
3193
|
+
for (const pid of await listDescendantPids(processGroupId)) terminal.descendantPids.add(pid);
|
|
3194
|
+
return;
|
|
3195
|
+
}
|
|
3196
|
+
for (const pid of await listProcessGroupPids(processGroupId)) if (pid !== processGroupId) terminal.descendantPids.add(pid);
|
|
3197
|
+
}
|
|
3198
|
+
async function listProcessGroupPids(processGroupId) {
|
|
3199
|
+
let output;
|
|
3200
|
+
try {
|
|
3201
|
+
output = await runProcessGroupListCommand();
|
|
3202
|
+
} catch {
|
|
3203
|
+
return [];
|
|
3204
|
+
}
|
|
3205
|
+
const pids = [];
|
|
3206
|
+
for (const line of output.split("\n")) {
|
|
3207
|
+
const match = line.trim().match(/^(\d+)\s+(\d+)$/);
|
|
3208
|
+
if (!match) continue;
|
|
3209
|
+
const pid = Number(match[1]);
|
|
3210
|
+
const pgid = Number(match[2]);
|
|
3211
|
+
if (Number.isInteger(pid) && Number.isInteger(pgid) && pid > 0 && pgid === processGroupId) pids.push(pid);
|
|
3212
|
+
}
|
|
3213
|
+
return pids;
|
|
3214
|
+
}
|
|
3215
|
+
async function runProcessGroupListCommand() {
|
|
3216
|
+
return await new Promise((resolve, reject) => {
|
|
3217
|
+
const child = spawn("ps", ["-eo", "pid=,pgid="], { stdio: [
|
|
3218
|
+
"ignore",
|
|
3219
|
+
"pipe",
|
|
3220
|
+
"pipe"
|
|
3221
|
+
] });
|
|
3222
|
+
let stdout = "";
|
|
3223
|
+
let stderr = "";
|
|
3224
|
+
child.stdout.setEncoding("utf8");
|
|
3225
|
+
child.stderr.setEncoding("utf8");
|
|
3226
|
+
child.stdout.on("data", (chunk) => {
|
|
3227
|
+
stdout += chunk;
|
|
3228
|
+
});
|
|
3229
|
+
child.stderr.on("data", (chunk) => {
|
|
3230
|
+
stderr += chunk;
|
|
3231
|
+
});
|
|
3232
|
+
child.once("error", reject);
|
|
3233
|
+
child.once("close", (code, signal) => {
|
|
3234
|
+
if (code === 0) {
|
|
3235
|
+
resolve(stdout);
|
|
3236
|
+
return;
|
|
3237
|
+
}
|
|
3238
|
+
reject(/* @__PURE__ */ new Error(`ps exited with code ${code ?? "null"} signal ${signal ?? "null"}: ${stderr}`));
|
|
3239
|
+
});
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
3242
|
+
async function runWindowsProcessListCommand() {
|
|
3243
|
+
return await new Promise((resolve, reject) => {
|
|
3244
|
+
const child = spawn("powershell.exe", [
|
|
3245
|
+
"-NoProfile",
|
|
3246
|
+
"-NonInteractive",
|
|
3247
|
+
"-Command",
|
|
3248
|
+
["Get-CimInstance Win32_Process |", "ForEach-Object { \"$($_.ProcessId) $($_.ParentProcessId)\" }"].join(" ")
|
|
3249
|
+
], {
|
|
3250
|
+
stdio: [
|
|
3251
|
+
"ignore",
|
|
3252
|
+
"pipe",
|
|
3253
|
+
"pipe"
|
|
3254
|
+
],
|
|
3255
|
+
windowsHide: true
|
|
3256
|
+
});
|
|
3257
|
+
let stdout = "";
|
|
3258
|
+
let stderr = "";
|
|
3259
|
+
child.stdout.setEncoding("utf8");
|
|
3260
|
+
child.stderr.setEncoding("utf8");
|
|
3261
|
+
child.stdout.on("data", (chunk) => {
|
|
3262
|
+
stdout += chunk;
|
|
3263
|
+
});
|
|
3264
|
+
child.stderr.on("data", (chunk) => {
|
|
3265
|
+
stderr += chunk;
|
|
3266
|
+
});
|
|
3267
|
+
child.once("error", reject);
|
|
3268
|
+
child.once("close", (code, signal) => {
|
|
3269
|
+
if (code === 0) {
|
|
3270
|
+
resolve(stdout);
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
reject(/* @__PURE__ */ new Error(`powershell process list exited with code ${code ?? "null"} signal ${signal ?? "null"}: ${stderr}`));
|
|
3274
|
+
});
|
|
3275
|
+
});
|
|
3276
|
+
}
|
|
3277
|
+
async function killWindowsProcessTree(pid, signal) {
|
|
3278
|
+
const args = [
|
|
3279
|
+
"/pid",
|
|
3280
|
+
String(pid),
|
|
3281
|
+
"/t"
|
|
3282
|
+
];
|
|
3283
|
+
if (signal === "SIGKILL") args.push("/f");
|
|
3284
|
+
await new Promise((resolve) => {
|
|
3285
|
+
const child = spawn("taskkill", args, {
|
|
3286
|
+
stdio: [
|
|
3287
|
+
"ignore",
|
|
3288
|
+
"ignore",
|
|
3289
|
+
"ignore"
|
|
3290
|
+
],
|
|
3291
|
+
windowsHide: true
|
|
3292
|
+
});
|
|
3293
|
+
child.once("error", () => resolve());
|
|
3294
|
+
child.once("close", () => resolve());
|
|
3295
|
+
});
|
|
3296
|
+
}
|
|
3297
|
+
function sendSignal(pid, signal) {
|
|
3298
|
+
try {
|
|
3299
|
+
process.kill(pid, signal);
|
|
3300
|
+
} catch {}
|
|
3301
|
+
}
|
|
3302
|
+
function hasLiveProcessGroup(processGroupId) {
|
|
3303
|
+
try {
|
|
3304
|
+
process.kill(-processGroupId, 0);
|
|
3305
|
+
return true;
|
|
3306
|
+
} catch {
|
|
3307
|
+
return false;
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
function hasLiveTerminalProcessGroup(terminal) {
|
|
3311
|
+
const pid = terminal.process.pid;
|
|
3312
|
+
return Boolean(terminal.killProcessGroup && pid && process.platform !== "win32" && hasLiveProcessGroup(pid));
|
|
3313
|
+
}
|
|
3314
|
+
function hasLivePid(pids) {
|
|
3315
|
+
for (const pid of pids) try {
|
|
3316
|
+
process.kill(pid, 0);
|
|
3317
|
+
return true;
|
|
3318
|
+
} catch {
|
|
3319
|
+
pids.delete(pid);
|
|
3320
|
+
}
|
|
3321
|
+
return false;
|
|
3322
|
+
}
|
|
1890
3323
|
//#endregion
|
|
1891
3324
|
//#region src/acp/client.ts
|
|
1892
3325
|
const REPLAY_IDLE_MS = 80;
|
|
@@ -1895,6 +3328,20 @@ const DRAIN_POLL_INTERVAL_MS = 20;
|
|
|
1895
3328
|
const AGENT_CLOSE_TERM_GRACE_MS = 1500;
|
|
1896
3329
|
const AGENT_CLOSE_KILL_GRACE_MS = 1e3;
|
|
1897
3330
|
const STARTUP_STDERR_MAX_CHARS = 8192;
|
|
3331
|
+
function toReconnectedSessionResult(response) {
|
|
3332
|
+
return {
|
|
3333
|
+
agentSessionId: extractRuntimeSessionId(response?._meta),
|
|
3334
|
+
configOptions: response?.configOptions ?? void 0,
|
|
3335
|
+
models: response?.models ?? void 0
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
function childProcessIsRunning(agent) {
|
|
3339
|
+
if (!agent) return false;
|
|
3340
|
+
return agent.exitCode == null && agent.signalCode == null && !agent.killed;
|
|
3341
|
+
}
|
|
3342
|
+
function cancelledPermissionResponse() {
|
|
3343
|
+
return { outcome: { outcome: "cancelled" } };
|
|
3344
|
+
}
|
|
1898
3345
|
function shouldSuppressSdkConsoleError(args) {
|
|
1899
3346
|
if (args.length === 0) return false;
|
|
1900
3347
|
return typeof args[0] === "string" && args[0] === "Error handling request";
|
|
@@ -1909,6 +3356,19 @@ function installSdkConsoleErrorSuppression() {
|
|
|
1909
3356
|
console.error = originalConsoleError;
|
|
1910
3357
|
};
|
|
1911
3358
|
}
|
|
3359
|
+
function enqueueNdJsonLine(agentCommand, line, controller) {
|
|
3360
|
+
const trimmedLine = line.trim();
|
|
3361
|
+
if (!trimmedLine || shouldIgnoreNonJsonAgentOutputLine(agentCommand, trimmedLine)) return;
|
|
3362
|
+
try {
|
|
3363
|
+
const message = JSON.parse(trimmedLine);
|
|
3364
|
+
controller.enqueue(message);
|
|
3365
|
+
} catch (err) {
|
|
3366
|
+
console.error("Failed to parse JSON message:", trimmedLine, err);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
function enqueueNdJsonLines(agentCommand, lines, controller) {
|
|
3370
|
+
for (const line of lines) enqueueNdJsonLine(agentCommand, line, controller);
|
|
3371
|
+
}
|
|
1912
3372
|
function createNdJsonMessageStream(agentCommand, output, input) {
|
|
1913
3373
|
const textEncoder = new TextEncoder();
|
|
1914
3374
|
const textDecoder = new TextDecoder();
|
|
@@ -1924,16 +3384,7 @@ function createNdJsonMessageStream(agentCommand, output, input) {
|
|
|
1924
3384
|
content += textDecoder.decode(value, { stream: true });
|
|
1925
3385
|
const lines = content.split("\n");
|
|
1926
3386
|
content = lines.pop() || "";
|
|
1927
|
-
|
|
1928
|
-
const trimmedLine = line.trim();
|
|
1929
|
-
if (!trimmedLine || shouldIgnoreNonJsonAgentOutputLine(agentCommand, trimmedLine)) continue;
|
|
1930
|
-
try {
|
|
1931
|
-
const message = JSON.parse(trimmedLine);
|
|
1932
|
-
controller.enqueue(message);
|
|
1933
|
-
} catch (err) {
|
|
1934
|
-
console.error("Failed to parse JSON message:", trimmedLine, err);
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
3387
|
+
enqueueNdJsonLines(agentCommand, lines, controller);
|
|
1937
3388
|
}
|
|
1938
3389
|
} finally {
|
|
1939
3390
|
reader.releaseLock();
|
|
@@ -1973,6 +3424,7 @@ var AcpClient = class {
|
|
|
1973
3424
|
suppressReplaySessionUpdateMessages = false;
|
|
1974
3425
|
activePrompt;
|
|
1975
3426
|
cancellingSessionIds = /* @__PURE__ */ new Set();
|
|
3427
|
+
permissionAbortControllers = /* @__PURE__ */ new Map();
|
|
1976
3428
|
closing = false;
|
|
1977
3429
|
agentStartedAt;
|
|
1978
3430
|
lastAgentExit;
|
|
@@ -1989,7 +3441,8 @@ var AcpClient = class {
|
|
|
1989
3441
|
onAcpMessage: this.options.onAcpMessage,
|
|
1990
3442
|
onAcpOutputMessage: this.options.onAcpOutputMessage,
|
|
1991
3443
|
onSessionUpdate: this.options.onSessionUpdate,
|
|
1992
|
-
onClientOperation: this.options.onClientOperation
|
|
3444
|
+
onClientOperation: this.options.onClientOperation,
|
|
3445
|
+
onPermissionEscalation: this.options.onPermissionEscalation
|
|
1993
3446
|
};
|
|
1994
3447
|
this.filesystem = new FileSystemHandlers({
|
|
1995
3448
|
cwd: this.options.cwd,
|
|
@@ -2019,7 +3472,7 @@ var AcpClient = class {
|
|
|
2019
3472
|
}
|
|
2020
3473
|
getAgentLifecycleSnapshot() {
|
|
2021
3474
|
const pid = this.agent?.pid ?? this.lastKnownPid;
|
|
2022
|
-
const running =
|
|
3475
|
+
const running = childProcessIsRunning(this.agent);
|
|
2023
3476
|
return {
|
|
2024
3477
|
pid,
|
|
2025
3478
|
startedAt: this.agentStartedAt,
|
|
@@ -2030,9 +3483,15 @@ var AcpClient = class {
|
|
|
2030
3483
|
supportsLoadSession() {
|
|
2031
3484
|
return Boolean(this.initResult?.agentCapabilities?.loadSession);
|
|
2032
3485
|
}
|
|
3486
|
+
supportsResumeSession() {
|
|
3487
|
+
return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.resume);
|
|
3488
|
+
}
|
|
2033
3489
|
supportsCloseSession() {
|
|
2034
3490
|
return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.close);
|
|
2035
3491
|
}
|
|
3492
|
+
supportsListSessions() {
|
|
3493
|
+
return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.list);
|
|
3494
|
+
}
|
|
2036
3495
|
setEventHandlers(handlers) {
|
|
2037
3496
|
this.eventHandlers = { ...handlers };
|
|
2038
3497
|
}
|
|
@@ -2040,16 +3499,20 @@ var AcpClient = class {
|
|
|
2040
3499
|
this.eventHandlers = {};
|
|
2041
3500
|
}
|
|
2042
3501
|
updateRuntimeOptions(options) {
|
|
3502
|
+
const shouldRefreshPermissionPolicy = options.permissionMode !== void 0 || options.nonInteractivePermissions !== void 0;
|
|
2043
3503
|
if (options.permissionMode) this.options.permissionMode = options.permissionMode;
|
|
2044
3504
|
if (options.nonInteractivePermissions !== void 0) this.options.nonInteractivePermissions = options.nonInteractivePermissions;
|
|
3505
|
+
if (Object.prototype.hasOwnProperty.call(options, "permissionPolicy")) this.options.permissionPolicy = options.permissionPolicy;
|
|
2045
3506
|
if (options.terminal !== void 0) this.options.terminal = options.terminal;
|
|
2046
|
-
|
|
2047
|
-
this.filesystem.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
|
|
2048
|
-
this.terminalManager.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
|
|
2049
|
-
}
|
|
3507
|
+
this.refreshRuntimePermissionPolicy(shouldRefreshPermissionPolicy);
|
|
2050
3508
|
if (options.suppressSdkConsoleErrors !== void 0) this.options.suppressSdkConsoleErrors = options.suppressSdkConsoleErrors;
|
|
2051
3509
|
if (options.verbose !== void 0) this.options.verbose = options.verbose;
|
|
2052
3510
|
}
|
|
3511
|
+
refreshRuntimePermissionPolicy(enabled) {
|
|
3512
|
+
if (!enabled) return;
|
|
3513
|
+
this.filesystem.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
|
|
3514
|
+
this.terminalManager.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
|
|
3515
|
+
}
|
|
2053
3516
|
hasReusableSession(sessionId) {
|
|
2054
3517
|
return this.connection != null && this.agent != null && isChildProcessRunning(this.agent) && this.loadedSessionId === sessionId;
|
|
2055
3518
|
}
|
|
@@ -2061,32 +3524,10 @@ var AcpClient = class {
|
|
|
2061
3524
|
async start() {
|
|
2062
3525
|
if (this.connection && this.agent && isChildProcessRunning(this.agent)) return;
|
|
2063
3526
|
if (this.connection || this.agent) await this.close();
|
|
2064
|
-
const
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
args = await resolveGeminiCommandArgs(spawnCommand, args);
|
|
2069
|
-
if (isQoderAcpCommand(spawnCommand, args)) args = buildQoderAcpCommandArgs(args, this.options);
|
|
2070
|
-
if (resolvedBuiltInLaunch?.source === "installed") this.log(`spawning installed built-in agent ${resolvedBuiltInLaunch.packageName}${resolvedBuiltInLaunch.packageVersion ? `@${resolvedBuiltInLaunch.packageVersion}` : ""} via ${spawnCommand} ${args.join(" ")}`);
|
|
2071
|
-
else if (resolvedBuiltInLaunch?.source === "package-exec") this.log(`spawning built-in agent ${resolvedBuiltInLaunch.packageName}@${resolvedBuiltInLaunch.packageRange} via current Node package exec bridge ${spawnCommand} ${args.join(" ")}`);
|
|
2072
|
-
else this.log(`spawning agent: ${spawnCommand} ${args.join(" ")}`);
|
|
2073
|
-
const geminiAcp = isGeminiAcpCommand(spawnCommand, args);
|
|
2074
|
-
if (isCopilotAcpCommand(spawnCommand, args)) await ensureCopilotAcpSupport(spawnCommand);
|
|
2075
|
-
const agentSpawnOptions = buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials);
|
|
2076
|
-
if (isClaudeAcpCommand(spawnCommand, args)) {
|
|
2077
|
-
const claudeExe = resolveClaudeCodeExecutable(process.platform, agentSpawnOptions.env);
|
|
2078
|
-
if (claudeExe) {
|
|
2079
|
-
agentSpawnOptions.env.CLAUDE_CODE_EXECUTABLE = claudeExe;
|
|
2080
|
-
this.log(`resolved system Claude Code executable: ${claudeExe}`);
|
|
2081
|
-
}
|
|
2082
|
-
}
|
|
2083
|
-
const spawnedChild = spawn(spawnCommand, args, buildSpawnCommandOptions(spawnCommand, agentSpawnOptions));
|
|
2084
|
-
try {
|
|
2085
|
-
await waitForSpawn$1(spawnedChild);
|
|
2086
|
-
} catch (error) {
|
|
2087
|
-
throw new AgentSpawnError(this.options.agentCommand, error);
|
|
2088
|
-
}
|
|
2089
|
-
const child = requireAgentStdio(spawnedChild);
|
|
3527
|
+
const launch = await this.resolveAgentLaunchPlan();
|
|
3528
|
+
this.logAgentLaunch(launch);
|
|
3529
|
+
await this.ensureLaunchSupport(launch);
|
|
3530
|
+
const child = await this.spawnAgentProcess(launch);
|
|
2090
3531
|
this.closing = false;
|
|
2091
3532
|
this.agentStartedAt = isoNow$1();
|
|
2092
3533
|
this.lastAgentExit = void 0;
|
|
@@ -2100,22 +3541,84 @@ var AcpClient = class {
|
|
|
2100
3541
|
});
|
|
2101
3542
|
const input = Writable.toWeb(child.stdin);
|
|
2102
3543
|
const output = Readable.toWeb(child.stdout);
|
|
2103
|
-
const
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
3544
|
+
const stream = this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output));
|
|
3545
|
+
const connection = this.createConnection(stream);
|
|
3546
|
+
connection.signal.addEventListener("abort", () => {
|
|
3547
|
+
this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null);
|
|
3548
|
+
}, { once: true });
|
|
3549
|
+
const startupFailure = this.createStartupFailureWatcher(child, startupStderr);
|
|
3550
|
+
await this.initializeAgentConnection({
|
|
3551
|
+
child,
|
|
3552
|
+
connection,
|
|
3553
|
+
startupFailure,
|
|
3554
|
+
startupStderr,
|
|
3555
|
+
launch
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3558
|
+
async resolveAgentLaunchPlan() {
|
|
3559
|
+
const configuredCommand = splitCommandLine(this.options.agentCommand);
|
|
3560
|
+
const resolvedBuiltInLaunch = resolveBuiltInAgentLaunch(this.options.agentCommand);
|
|
3561
|
+
const spawnCommand = resolvedBuiltInLaunch?.command ?? configuredCommand.command;
|
|
3562
|
+
let args = resolvedBuiltInLaunch?.args ?? configuredCommand.args;
|
|
3563
|
+
args = await resolveGeminiCommandArgs(spawnCommand, args);
|
|
3564
|
+
if (isQoderAcpCommand(spawnCommand, args)) args = buildQoderAcpCommandArgs(args, this.options);
|
|
3565
|
+
return {
|
|
3566
|
+
spawnCommand,
|
|
3567
|
+
args,
|
|
3568
|
+
resolvedBuiltInLaunch,
|
|
3569
|
+
geminiAcp: isGeminiAcpCommand(spawnCommand, args),
|
|
3570
|
+
copilotAcp: isCopilotAcpCommand(spawnCommand, args),
|
|
3571
|
+
claudeAcp: isClaudeAcpCommand(spawnCommand, args),
|
|
3572
|
+
spawnOptions: buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials)
|
|
3573
|
+
};
|
|
3574
|
+
}
|
|
3575
|
+
logAgentLaunch(plan) {
|
|
3576
|
+
const launch = plan.resolvedBuiltInLaunch;
|
|
3577
|
+
if (launch?.source === "installed") {
|
|
3578
|
+
this.log(`spawning installed built-in agent ${launch.packageName}${launch.packageVersion ? `@${launch.packageVersion}` : ""} via ${plan.spawnCommand} ${plan.args.join(" ")}`);
|
|
3579
|
+
return;
|
|
3580
|
+
}
|
|
3581
|
+
if (launch?.source === "package-exec") {
|
|
3582
|
+
this.log(`spawning built-in agent ${launch.packageName}@${launch.packageRange} via current Node package exec bridge ${plan.spawnCommand} ${plan.args.join(" ")}`);
|
|
3583
|
+
return;
|
|
3584
|
+
}
|
|
3585
|
+
this.log(`spawning agent: ${plan.spawnCommand} ${plan.args.join(" ")}`);
|
|
3586
|
+
}
|
|
3587
|
+
async ensureLaunchSupport(plan) {
|
|
3588
|
+
if (plan.copilotAcp) await ensureCopilotAcpSupport(plan.spawnCommand);
|
|
3589
|
+
if (!plan.claudeAcp) return;
|
|
3590
|
+
const claudeExe = resolveClaudeCodeExecutable(process.platform, plan.spawnOptions.env);
|
|
3591
|
+
if (claudeExe) {
|
|
3592
|
+
plan.spawnOptions.env.CLAUDE_CODE_EXECUTABLE = claudeExe;
|
|
3593
|
+
this.log(`resolved system Claude Code executable: ${claudeExe}`);
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
async spawnAgentProcess(plan) {
|
|
3597
|
+
const spawnedChild = spawn(plan.spawnCommand, plan.args, buildSpawnCommandOptions(plan.spawnCommand, plan.spawnOptions));
|
|
3598
|
+
try {
|
|
3599
|
+
await waitForSpawn$1(spawnedChild);
|
|
3600
|
+
} catch (error) {
|
|
3601
|
+
throw new AgentSpawnError(this.options.agentCommand, error);
|
|
3602
|
+
}
|
|
3603
|
+
return requireAgentStdio(spawnedChild);
|
|
3604
|
+
}
|
|
3605
|
+
createConnection(stream) {
|
|
3606
|
+
return new ClientSideConnection(() => ({
|
|
3607
|
+
sessionUpdate: async (params) => {
|
|
3608
|
+
await this.handleSessionUpdate(params);
|
|
3609
|
+
},
|
|
3610
|
+
requestPermission: async (params) => {
|
|
3611
|
+
return this.handlePermissionRequest(params);
|
|
3612
|
+
},
|
|
3613
|
+
readTextFile: async (params) => {
|
|
3614
|
+
return this.handleReadTextFile(params);
|
|
3615
|
+
},
|
|
3616
|
+
writeTextFile: async (params) => {
|
|
3617
|
+
return this.handleWriteTextFile(params);
|
|
3618
|
+
},
|
|
3619
|
+
createTerminal: async (params) => {
|
|
3620
|
+
return this.handleCreateTerminal(params);
|
|
3621
|
+
},
|
|
2119
3622
|
terminalOutput: async (params) => {
|
|
2120
3623
|
return this.handleTerminalOutput(params);
|
|
2121
3624
|
},
|
|
@@ -2128,49 +3631,51 @@ var AcpClient = class {
|
|
|
2128
3631
|
releaseTerminal: async (params) => {
|
|
2129
3632
|
return this.handleReleaseTerminal(params);
|
|
2130
3633
|
}
|
|
2131
|
-
}),
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
}, { once: true });
|
|
2135
|
-
const startupFailure = this.createStartupFailureWatcher(child, startupStderr);
|
|
3634
|
+
}), stream);
|
|
3635
|
+
}
|
|
3636
|
+
async initializeAgentConnection(params) {
|
|
2136
3637
|
try {
|
|
2137
|
-
const initResult = await Promise.race([(
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
fs: {
|
|
2142
|
-
readTextFile: true,
|
|
2143
|
-
writeTextFile: true
|
|
2144
|
-
},
|
|
2145
|
-
terminal: this.options.terminal !== false
|
|
2146
|
-
},
|
|
2147
|
-
clientInfo: {
|
|
2148
|
-
name: "acpx",
|
|
2149
|
-
version: "0.1.0"
|
|
2150
|
-
}
|
|
2151
|
-
});
|
|
2152
|
-
const initialized = geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise;
|
|
2153
|
-
await this.authenticateIfRequired(connection, initialized.authMethods ?? []);
|
|
2154
|
-
return initialized;
|
|
2155
|
-
})(), startupFailure.promise]);
|
|
2156
|
-
startupFailure.dispose();
|
|
2157
|
-
this.connection = connection;
|
|
2158
|
-
this.agent = child;
|
|
3638
|
+
const initResult = await Promise.race([this.initializeProtocolConnection(params.connection, params.launch.geminiAcp), params.startupFailure.promise]);
|
|
3639
|
+
params.startupFailure.dispose();
|
|
3640
|
+
this.connection = params.connection;
|
|
3641
|
+
this.agent = params.child;
|
|
2159
3642
|
this.initResult = initResult;
|
|
2160
3643
|
this.log(`initialized protocol version ${initResult.protocolVersion}`);
|
|
2161
3644
|
} catch (error) {
|
|
2162
|
-
|
|
2163
|
-
const normalizedError = await this.normalizeInitializeError(error, child, startupStderr);
|
|
2164
|
-
try {
|
|
2165
|
-
child.kill();
|
|
2166
|
-
} catch {}
|
|
2167
|
-
if (geminiAcp && error instanceof TimeoutError) throw new GeminiAcpStartupTimeoutError(await buildGeminiAcpStartupTimeoutMessage(spawnCommand), {
|
|
2168
|
-
cause: error,
|
|
2169
|
-
retryable: true
|
|
2170
|
-
});
|
|
2171
|
-
throw normalizedError;
|
|
3645
|
+
await this.handleInitializeFailure(params, error);
|
|
2172
3646
|
}
|
|
2173
3647
|
}
|
|
3648
|
+
async initializeProtocolConnection(connection, geminiAcp) {
|
|
3649
|
+
const initializePromise = connection.initialize({
|
|
3650
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
3651
|
+
clientCapabilities: {
|
|
3652
|
+
fs: {
|
|
3653
|
+
readTextFile: true,
|
|
3654
|
+
writeTextFile: true
|
|
3655
|
+
},
|
|
3656
|
+
terminal: this.options.terminal !== false
|
|
3657
|
+
},
|
|
3658
|
+
clientInfo: {
|
|
3659
|
+
name: "acpx",
|
|
3660
|
+
version: "0.1.0"
|
|
3661
|
+
}
|
|
3662
|
+
});
|
|
3663
|
+
const initialized = geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise;
|
|
3664
|
+
await this.authenticateIfRequired(connection, initialized.authMethods ?? []);
|
|
3665
|
+
return initialized;
|
|
3666
|
+
}
|
|
3667
|
+
async handleInitializeFailure(params, error) {
|
|
3668
|
+
params.startupFailure.dispose();
|
|
3669
|
+
const normalizedError = await this.normalizeInitializeError(error, params.child, params.startupStderr);
|
|
3670
|
+
try {
|
|
3671
|
+
params.child.kill();
|
|
3672
|
+
} catch {}
|
|
3673
|
+
if (params.launch.geminiAcp && error instanceof TimeoutError) throw new GeminiAcpStartupTimeoutError(await buildGeminiAcpStartupTimeoutMessage(params.launch.spawnCommand), {
|
|
3674
|
+
cause: error,
|
|
3675
|
+
retryable: true
|
|
3676
|
+
});
|
|
3677
|
+
throw normalizedError;
|
|
3678
|
+
}
|
|
2174
3679
|
createTappedStream(base) {
|
|
2175
3680
|
const onAcpMessage = () => this.eventHandlers.onAcpMessage;
|
|
2176
3681
|
const onAcpOutputMessage = () => this.eventHandlers.onAcpOutputMessage;
|
|
@@ -2243,10 +3748,7 @@ var AcpClient = class {
|
|
|
2243
3748
|
async loadSessionWithOptions(sessionId, cwd = this.options.cwd, options = {}) {
|
|
2244
3749
|
const connection = this.getConnection();
|
|
2245
3750
|
const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
|
|
2246
|
-
const previousSuppression = this.
|
|
2247
|
-
const previousReplaySuppression = this.suppressReplaySessionUpdateMessages;
|
|
2248
|
-
this.suppressSessionUpdates = previousSuppression || Boolean(options.suppressReplayUpdates);
|
|
2249
|
-
this.suppressReplaySessionUpdateMessages = previousReplaySuppression || Boolean(options.suppressReplayUpdates);
|
|
3751
|
+
const previousSuppression = this.applySessionUpdateSuppression(Boolean(options.suppressReplayUpdates));
|
|
2250
3752
|
let response;
|
|
2251
3753
|
try {
|
|
2252
3754
|
response = await this.runConnectionRequest(() => connection.loadSession({
|
|
@@ -2256,24 +3758,44 @@ var AcpClient = class {
|
|
|
2256
3758
|
}));
|
|
2257
3759
|
await this.waitForSessionUpdateDrain(options.replayIdleMs ?? REPLAY_IDLE_MS, options.replayDrainTimeoutMs ?? REPLAY_DRAIN_TIMEOUT_MS);
|
|
2258
3760
|
} finally {
|
|
2259
|
-
this.
|
|
2260
|
-
this.suppressReplaySessionUpdateMessages = previousReplaySuppression;
|
|
3761
|
+
this.restoreSessionUpdateSuppression(previousSuppression);
|
|
2261
3762
|
}
|
|
2262
3763
|
this.loadedSessionId = sessionId;
|
|
2263
|
-
return
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
3764
|
+
return toReconnectedSessionResult(response);
|
|
3765
|
+
}
|
|
3766
|
+
async resumeSession(sessionId, cwd = this.options.cwd) {
|
|
3767
|
+
const connection = this.getConnection();
|
|
3768
|
+
const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
|
|
3769
|
+
const response = await this.runConnectionRequest(() => connection.resumeSession({
|
|
3770
|
+
sessionId,
|
|
3771
|
+
cwd: sessionCwd,
|
|
3772
|
+
mcpServers: this.options.mcpServers ?? []
|
|
3773
|
+
}));
|
|
3774
|
+
this.loadedSessionId = sessionId;
|
|
3775
|
+
return toReconnectedSessionResult(response);
|
|
3776
|
+
}
|
|
3777
|
+
applySessionUpdateSuppression(enabled) {
|
|
3778
|
+
const previous = {
|
|
3779
|
+
suppressSessionUpdates: this.suppressSessionUpdates,
|
|
3780
|
+
suppressReplaySessionUpdateMessages: this.suppressReplaySessionUpdateMessages
|
|
2267
3781
|
};
|
|
3782
|
+
this.suppressSessionUpdates = previous.suppressSessionUpdates || enabled;
|
|
3783
|
+
this.suppressReplaySessionUpdateMessages = previous.suppressReplaySessionUpdateMessages || enabled;
|
|
3784
|
+
return previous;
|
|
3785
|
+
}
|
|
3786
|
+
restoreSessionUpdateSuppression(previous) {
|
|
3787
|
+
this.suppressSessionUpdates = previous.suppressSessionUpdates;
|
|
3788
|
+
this.suppressReplaySessionUpdateMessages = previous.suppressReplaySessionUpdateMessages;
|
|
2268
3789
|
}
|
|
2269
3790
|
async prompt(sessionId, prompt) {
|
|
2270
3791
|
const connection = this.getConnection();
|
|
3792
|
+
const normalizedPrompt = this.normalizePromptForAgent(prompt);
|
|
2271
3793
|
const restoreConsoleError = this.options.suppressSdkConsoleErrors ? installSdkConsoleErrorSuppression() : void 0;
|
|
2272
3794
|
let promptPromise;
|
|
2273
3795
|
try {
|
|
2274
3796
|
promptPromise = this.runConnectionRequest(() => connection.prompt({
|
|
2275
3797
|
sessionId,
|
|
2276
|
-
prompt:
|
|
3798
|
+
prompt: normalizedPrompt
|
|
2277
3799
|
}));
|
|
2278
3800
|
} catch (error) {
|
|
2279
3801
|
restoreConsoleError?.();
|
|
@@ -2284,21 +3806,32 @@ var AcpClient = class {
|
|
|
2284
3806
|
promise: promptPromise
|
|
2285
3807
|
};
|
|
2286
3808
|
try {
|
|
2287
|
-
|
|
2288
|
-
const permissionFailure = this.consumePromptPermissionFailure(sessionId);
|
|
2289
|
-
if (permissionFailure) throw permissionFailure;
|
|
2290
|
-
return response;
|
|
3809
|
+
return this.returnPromptResponseOrPermissionFailure(sessionId, await promptPromise);
|
|
2291
3810
|
} catch (error) {
|
|
2292
|
-
|
|
2293
|
-
if (permissionFailure) throw permissionFailure;
|
|
3811
|
+
this.throwPromptPermissionFailureIfPresent(sessionId);
|
|
2294
3812
|
throw error;
|
|
2295
3813
|
} finally {
|
|
2296
3814
|
restoreConsoleError?.();
|
|
2297
3815
|
if (this.activePrompt?.promise === promptPromise) this.activePrompt = void 0;
|
|
2298
3816
|
this.cancellingSessionIds.delete(sessionId);
|
|
3817
|
+
this.abortAndDropPermissionSignal(sessionId);
|
|
2299
3818
|
this.promptPermissionFailures.delete(sessionId);
|
|
2300
3819
|
}
|
|
2301
3820
|
}
|
|
3821
|
+
normalizePromptForAgent(prompt) {
|
|
3822
|
+
const normalizedPrompt = typeof prompt === "string" ? textPrompt(prompt) : prompt;
|
|
3823
|
+
const unsupportedPromptContent = getUnsupportedPromptContentMessage(normalizedPrompt, this.initResult?.agentCapabilities);
|
|
3824
|
+
if (unsupportedPromptContent) throw new UnsupportedPromptContentError(unsupportedPromptContent);
|
|
3825
|
+
return normalizedPrompt;
|
|
3826
|
+
}
|
|
3827
|
+
returnPromptResponseOrPermissionFailure(sessionId, response) {
|
|
3828
|
+
this.throwPromptPermissionFailureIfPresent(sessionId);
|
|
3829
|
+
return response;
|
|
3830
|
+
}
|
|
3831
|
+
throwPromptPermissionFailureIfPresent(sessionId) {
|
|
3832
|
+
const permissionFailure = this.consumePromptPermissionFailure(sessionId);
|
|
3833
|
+
if (permissionFailure) throw permissionFailure;
|
|
3834
|
+
}
|
|
2302
3835
|
async setSessionMode(sessionId, modeId) {
|
|
2303
3836
|
const connection = this.getConnection();
|
|
2304
3837
|
try {
|
|
@@ -2341,6 +3874,7 @@ var AcpClient = class {
|
|
|
2341
3874
|
async cancel(sessionId) {
|
|
2342
3875
|
const connection = this.getConnection();
|
|
2343
3876
|
this.cancellingSessionIds.add(sessionId);
|
|
3877
|
+
this.abortAndDropPermissionSignal(sessionId);
|
|
2344
3878
|
await this.runConnectionRequest(() => connection.cancel({ sessionId }));
|
|
2345
3879
|
}
|
|
2346
3880
|
async closeSession(sessionId) {
|
|
@@ -2348,6 +3882,10 @@ var AcpClient = class {
|
|
|
2348
3882
|
await this.runConnectionRequest(() => connection.closeSession({ sessionId }));
|
|
2349
3883
|
if (this.loadedSessionId === sessionId) this.loadedSessionId = void 0;
|
|
2350
3884
|
}
|
|
3885
|
+
async listSessions(params = {}) {
|
|
3886
|
+
const connection = this.getConnection();
|
|
3887
|
+
return await this.runConnectionRequest(() => connection.listSessions(params));
|
|
3888
|
+
}
|
|
2351
3889
|
async requestCancelActivePrompt() {
|
|
2352
3890
|
const active = this.activePrompt;
|
|
2353
3891
|
if (!active) return false;
|
|
@@ -2387,6 +3925,8 @@ var AcpClient = class {
|
|
|
2387
3925
|
this.suppressReplaySessionUpdateMessages = false;
|
|
2388
3926
|
this.activePrompt = void 0;
|
|
2389
3927
|
this.cancellingSessionIds.clear();
|
|
3928
|
+
for (const controller of this.permissionAbortControllers.values()) controller.abort();
|
|
3929
|
+
this.permissionAbortControllers.clear();
|
|
2390
3930
|
this.promptPermissionFailures.clear();
|
|
2391
3931
|
this.loadedSessionId = void 0;
|
|
2392
3932
|
this.initResult = void 0;
|
|
@@ -2395,25 +3935,28 @@ var AcpClient = class {
|
|
|
2395
3935
|
}
|
|
2396
3936
|
async terminateAgentProcess(child) {
|
|
2397
3937
|
const stdinCloseGraceMs = resolveAgentCloseAfterStdinEndMs(this.options.agentCommand);
|
|
2398
|
-
|
|
2399
|
-
child.stdin.end();
|
|
2400
|
-
} catch {}
|
|
3938
|
+
this.endAgentStdin(child);
|
|
2401
3939
|
let exited = await waitForChildExit(child, stdinCloseGraceMs);
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
child.kill("SIGTERM");
|
|
2405
|
-
} catch {}
|
|
2406
|
-
exited = await waitForChildExit(child, AGENT_CLOSE_TERM_GRACE_MS);
|
|
2407
|
-
}
|
|
2408
|
-
if (!exited && isChildProcessRunning(child)) {
|
|
3940
|
+
exited = await this.killAgentIfRunning(child, exited, "SIGTERM", AGENT_CLOSE_TERM_GRACE_MS);
|
|
3941
|
+
if (!exited) {
|
|
2409
3942
|
this.log(`agent did not exit after ${AGENT_CLOSE_TERM_GRACE_MS}ms; forcing SIGKILL`);
|
|
2410
|
-
|
|
2411
|
-
child.kill("SIGKILL");
|
|
2412
|
-
} catch {}
|
|
2413
|
-
exited = await waitForChildExit(child, AGENT_CLOSE_KILL_GRACE_MS);
|
|
3943
|
+
exited = await this.killAgentIfRunning(child, exited, "SIGKILL", AGENT_CLOSE_KILL_GRACE_MS);
|
|
2414
3944
|
}
|
|
2415
3945
|
this.detachAgentHandles(child, !exited);
|
|
2416
3946
|
}
|
|
3947
|
+
endAgentStdin(child) {
|
|
3948
|
+
if (child.stdin.destroyed) return;
|
|
3949
|
+
try {
|
|
3950
|
+
child.stdin.end();
|
|
3951
|
+
} catch {}
|
|
3952
|
+
}
|
|
3953
|
+
async killAgentIfRunning(child, alreadyExited, signal, waitMs) {
|
|
3954
|
+
if (alreadyExited || !isChildProcessRunning(child)) return alreadyExited;
|
|
3955
|
+
try {
|
|
3956
|
+
child.kill(signal);
|
|
3957
|
+
} catch {}
|
|
3958
|
+
return await waitForChildExit(child, waitMs);
|
|
3959
|
+
}
|
|
2417
3960
|
detachAgentHandles(agent, unref) {
|
|
2418
3961
|
const stdin = agent.stdin;
|
|
2419
3962
|
const stdout = agent.stdout;
|
|
@@ -2534,22 +4077,71 @@ var AcpClient = class {
|
|
|
2534
4077
|
this.log(`authenticated with method ${selected.methodId} (${selected.source})`);
|
|
2535
4078
|
}
|
|
2536
4079
|
async handlePermissionRequest(params) {
|
|
2537
|
-
if (this.cancellingSessionIds.has(params.sessionId)) return
|
|
2538
|
-
|
|
4080
|
+
if (this.cancellingSessionIds.has(params.sessionId)) return cancelledPermissionResponse();
|
|
4081
|
+
const hostResponse = await this.tryHandlePermissionRequestWithHost(params);
|
|
4082
|
+
if (hostResponse) return hostResponse;
|
|
4083
|
+
const { response, recorded } = await this.resolvePermissionRequestFromMode(params);
|
|
4084
|
+
if (!recorded) {
|
|
4085
|
+
const decision = classifyPermissionDecision(params, response);
|
|
4086
|
+
this.recordPermissionDecision(decision);
|
|
4087
|
+
}
|
|
4088
|
+
return response;
|
|
4089
|
+
}
|
|
4090
|
+
async tryHandlePermissionRequestWithHost(params) {
|
|
4091
|
+
if (!this.options.onPermissionRequest) return;
|
|
4092
|
+
const signal = this.cancellationSignalForSession(params.sessionId);
|
|
2539
4093
|
try {
|
|
2540
|
-
|
|
4094
|
+
const decision = await this.options.onPermissionRequest({
|
|
4095
|
+
sessionId: params.sessionId,
|
|
4096
|
+
raw: params,
|
|
4097
|
+
inferredKind: inferToolKind(params)
|
|
4098
|
+
}, { signal });
|
|
4099
|
+
return this.hostPermissionDecisionResponse(params, signal, decision);
|
|
2541
4100
|
} catch (error) {
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
4101
|
+
return this.hostPermissionErrorResponse(params, signal, error);
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
hostPermissionDecisionResponse(params, signal, decision) {
|
|
4105
|
+
if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
|
|
4106
|
+
this.recordPermissionDecision("cancelled");
|
|
4107
|
+
return cancelledPermissionResponse();
|
|
2548
4108
|
}
|
|
2549
|
-
|
|
2550
|
-
|
|
4109
|
+
if (!decision) return;
|
|
4110
|
+
const response = decisionToResponse(params, decision);
|
|
4111
|
+
this.recordPermissionDecision(classifyPermissionDecision(params, response));
|
|
2551
4112
|
return response;
|
|
2552
4113
|
}
|
|
4114
|
+
hostPermissionErrorResponse(params, signal, error) {
|
|
4115
|
+
if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
|
|
4116
|
+
this.recordPermissionDecision("cancelled");
|
|
4117
|
+
return cancelledPermissionResponse();
|
|
4118
|
+
}
|
|
4119
|
+
this.log(`onPermissionRequest threw, falling through to mode-based resolver: ${error instanceof Error ? error.message : String(error)}`);
|
|
4120
|
+
}
|
|
4121
|
+
async resolvePermissionRequestFromMode(params) {
|
|
4122
|
+
try {
|
|
4123
|
+
const result = await resolvePermissionRequestWithDetails(params, this.options.permissionMode, this.options.nonInteractivePermissions ?? "deny", this.options.permissionPolicy);
|
|
4124
|
+
this.emitPermissionEscalation(result.escalation);
|
|
4125
|
+
return {
|
|
4126
|
+
response: result.response,
|
|
4127
|
+
recorded: false
|
|
4128
|
+
};
|
|
4129
|
+
} catch (error) {
|
|
4130
|
+
return this.handleModePermissionError(params.sessionId, error);
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
emitPermissionEscalation(escalation) {
|
|
4134
|
+
if (escalation) this.eventHandlers.onPermissionEscalation?.(escalation);
|
|
4135
|
+
}
|
|
4136
|
+
handleModePermissionError(sessionId, error) {
|
|
4137
|
+
if (!(error instanceof PermissionPromptUnavailableError)) throw error;
|
|
4138
|
+
this.notePromptPermissionFailure(sessionId, error);
|
|
4139
|
+
this.recordPermissionDecision("cancelled");
|
|
4140
|
+
return {
|
|
4141
|
+
response: cancelledPermissionResponse(),
|
|
4142
|
+
recorded: true
|
|
4143
|
+
};
|
|
4144
|
+
}
|
|
2553
4145
|
attachAgentLifecycleObservers(child) {
|
|
2554
4146
|
child.once("exit", (exitCode, signal) => {
|
|
2555
4147
|
this.recordAgentExit("process_exit", exitCode, signal);
|
|
@@ -2643,6 +4235,21 @@ var AcpClient = class {
|
|
|
2643
4235
|
async handleReleaseTerminal(params) {
|
|
2644
4236
|
return await this.terminalManager.releaseTerminal(params);
|
|
2645
4237
|
}
|
|
4238
|
+
cancellationSignalForSession(sessionId) {
|
|
4239
|
+
let controller = this.permissionAbortControllers.get(sessionId);
|
|
4240
|
+
if (!controller) {
|
|
4241
|
+
controller = new AbortController();
|
|
4242
|
+
this.permissionAbortControllers.set(sessionId, controller);
|
|
4243
|
+
}
|
|
4244
|
+
return controller.signal;
|
|
4245
|
+
}
|
|
4246
|
+
abortAndDropPermissionSignal(sessionId) {
|
|
4247
|
+
const controller = this.permissionAbortControllers.get(sessionId);
|
|
4248
|
+
if (controller) {
|
|
4249
|
+
controller.abort();
|
|
4250
|
+
this.permissionAbortControllers.delete(sessionId);
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
2646
4253
|
recordPermissionDecision(decision) {
|
|
2647
4254
|
this.permissionStats.requested += 1;
|
|
2648
4255
|
if (decision === "approved") {
|
|
@@ -2704,6 +4311,111 @@ var AcpClient = class {
|
|
|
2704
4311
|
}
|
|
2705
4312
|
};
|
|
2706
4313
|
//#endregion
|
|
4314
|
+
//#region src/runtime/engine/lifecycle.ts
|
|
4315
|
+
function applyLifecycleSnapshotToRecord(record, snapshot) {
|
|
4316
|
+
if (!snapshot) return;
|
|
4317
|
+
record.pid = snapshot.running ? snapshot.pid : void 0;
|
|
4318
|
+
record.agentStartedAt = snapshot.startedAt;
|
|
4319
|
+
if (snapshot.lastExit) {
|
|
4320
|
+
record.lastAgentExitCode = snapshot.lastExit.exitCode;
|
|
4321
|
+
record.lastAgentExitSignal = snapshot.lastExit.signal;
|
|
4322
|
+
record.lastAgentExitAt = snapshot.lastExit.exitedAt;
|
|
4323
|
+
record.lastAgentDisconnectReason = snapshot.lastExit.reason;
|
|
4324
|
+
return;
|
|
4325
|
+
}
|
|
4326
|
+
record.lastAgentExitCode = void 0;
|
|
4327
|
+
record.lastAgentExitSignal = void 0;
|
|
4328
|
+
record.lastAgentExitAt = void 0;
|
|
4329
|
+
record.lastAgentDisconnectReason = void 0;
|
|
4330
|
+
}
|
|
4331
|
+
function reconcileAgentSessionId(record, agentSessionId) {
|
|
4332
|
+
const normalized = normalizeRuntimeSessionId(agentSessionId);
|
|
4333
|
+
if (!normalized) return;
|
|
4334
|
+
record.agentSessionId = normalized;
|
|
4335
|
+
}
|
|
4336
|
+
function sessionHasAgentMessages(recordOrConversation) {
|
|
4337
|
+
return recordOrConversation.messages.some((message) => typeof message === "object" && message !== null && "Agent" in message);
|
|
4338
|
+
}
|
|
4339
|
+
function applyConversation(record, conversation) {
|
|
4340
|
+
record.title = conversation.title;
|
|
4341
|
+
record.updated_at = conversation.updated_at;
|
|
4342
|
+
record.messages = conversation.messages;
|
|
4343
|
+
record.cumulative_token_usage = conversation.cumulative_token_usage;
|
|
4344
|
+
record.request_token_usage = conversation.request_token_usage;
|
|
4345
|
+
}
|
|
4346
|
+
//#endregion
|
|
4347
|
+
//#region src/runtime/engine/session-options.ts
|
|
4348
|
+
function mergeSessionOptions(preferred, fallback) {
|
|
4349
|
+
const merged = { ...fallback };
|
|
4350
|
+
assignDefinedOption(merged, "model", preferred?.model);
|
|
4351
|
+
assignDefinedOption(merged, "allowedTools", preferred?.allowedTools);
|
|
4352
|
+
assignDefinedOption(merged, "maxTurns", preferred?.maxTurns);
|
|
4353
|
+
assignDefinedOption(merged, "systemPrompt", preferred?.systemPrompt);
|
|
4354
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
4355
|
+
}
|
|
4356
|
+
function assignDefinedOption(target, key, value) {
|
|
4357
|
+
if (value !== void 0) target[key] = value;
|
|
4358
|
+
}
|
|
4359
|
+
function persistSessionOptions(record, options) {
|
|
4360
|
+
const next = options === void 0 ? void 0 : persistedSessionOptions(options);
|
|
4361
|
+
if (next !== void 0) {
|
|
4362
|
+
record.acpx = {
|
|
4363
|
+
...record.acpx,
|
|
4364
|
+
session_options: next
|
|
4365
|
+
};
|
|
4366
|
+
return;
|
|
4367
|
+
}
|
|
4368
|
+
if (!record.acpx) return;
|
|
4369
|
+
delete record.acpx.session_options;
|
|
4370
|
+
}
|
|
4371
|
+
function sessionOptionsFromRecord(record) {
|
|
4372
|
+
const stored = record.acpx?.session_options;
|
|
4373
|
+
if (!stored) return;
|
|
4374
|
+
const sessionOptions = {};
|
|
4375
|
+
assignStoredOption(sessionOptions, "model", nonEmptyString(stored.model));
|
|
4376
|
+
assignStoredOption(sessionOptions, "allowedTools", storedAllowedTools(stored.allowed_tools));
|
|
4377
|
+
assignStoredOption(sessionOptions, "maxTurns", storedMaxTurns(stored.max_turns));
|
|
4378
|
+
assignStoredOption(sessionOptions, "systemPrompt", storedSystemPromptOption(stored.system_prompt));
|
|
4379
|
+
return Object.keys(sessionOptions).length > 0 ? sessionOptions : void 0;
|
|
4380
|
+
}
|
|
4381
|
+
function persistedSessionOptions(options) {
|
|
4382
|
+
const next = {
|
|
4383
|
+
model: nonEmptyString(options.model),
|
|
4384
|
+
allowed_tools: Array.isArray(options.allowedTools) ? [...options.allowedTools] : void 0,
|
|
4385
|
+
max_turns: typeof options.maxTurns === "number" ? options.maxTurns : void 0,
|
|
4386
|
+
system_prompt: normalizeSystemPromptOption(options.systemPrompt)
|
|
4387
|
+
};
|
|
4388
|
+
return hasPersistedSessionOptions(next) ? next : void 0;
|
|
4389
|
+
}
|
|
4390
|
+
function hasPersistedSessionOptions(options) {
|
|
4391
|
+
return options.model !== void 0 || options.allowed_tools !== void 0 || options.max_turns !== void 0 || options.system_prompt !== void 0;
|
|
4392
|
+
}
|
|
4393
|
+
function normalizeSystemPromptOption(value) {
|
|
4394
|
+
const prompt = nonEmptyString(value);
|
|
4395
|
+
if (prompt !== void 0) return prompt;
|
|
4396
|
+
const append = appendedSystemPrompt(value);
|
|
4397
|
+
return append === void 0 ? void 0 : { append };
|
|
4398
|
+
}
|
|
4399
|
+
function appendedSystemPrompt(value) {
|
|
4400
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return;
|
|
4401
|
+
return nonEmptyString(value.append);
|
|
4402
|
+
}
|
|
4403
|
+
function assignStoredOption(target, key, value) {
|
|
4404
|
+
assignDefinedOption(target, key, value);
|
|
4405
|
+
}
|
|
4406
|
+
function storedAllowedTools(value) {
|
|
4407
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string") ? [...value] : void 0;
|
|
4408
|
+
}
|
|
4409
|
+
function storedMaxTurns(value) {
|
|
4410
|
+
return typeof value === "number" ? value : void 0;
|
|
4411
|
+
}
|
|
4412
|
+
function storedSystemPromptOption(value) {
|
|
4413
|
+
return normalizeSystemPromptOption(value);
|
|
4414
|
+
}
|
|
4415
|
+
function nonEmptyString(value) {
|
|
4416
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
4417
|
+
}
|
|
4418
|
+
//#endregion
|
|
2707
4419
|
//#region src/session/conversation-model.ts
|
|
2708
4420
|
const MAX_RUNTIME_MESSAGES = 200;
|
|
2709
4421
|
const MAX_RUNTIME_AGENT_TEXT_CHARS = 8e3;
|
|
@@ -2729,13 +4441,17 @@ function normalizeAgentName(value) {
|
|
|
2729
4441
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
2730
4442
|
}
|
|
2731
4443
|
function extractText(content) {
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
return content.
|
|
4444
|
+
switch (content.type) {
|
|
4445
|
+
case "text": return content.text;
|
|
4446
|
+
case "resource_link": return content.title ?? content.name ?? content.uri;
|
|
4447
|
+
case "resource": return extractResourceText(content);
|
|
4448
|
+
case "audio": return `[audio] ${content.mimeType}`;
|
|
4449
|
+
default: return;
|
|
2737
4450
|
}
|
|
2738
4451
|
}
|
|
4452
|
+
function extractResourceText(content) {
|
|
4453
|
+
return "text" in content.resource && typeof content.resource.text === "string" ? content.resource.text : content.resource.uri;
|
|
4454
|
+
}
|
|
2739
4455
|
function contentToUserContent(content) {
|
|
2740
4456
|
if (content.type === "text") return { Text: content.text };
|
|
2741
4457
|
if (content.type === "resource_link") {
|
|
@@ -2745,17 +4461,22 @@ function contentToUserContent(content) {
|
|
|
2745
4461
|
content: value
|
|
2746
4462
|
} };
|
|
2747
4463
|
}
|
|
2748
|
-
if (content.type === "resource")
|
|
2749
|
-
if ("text" in content.resource && typeof content.resource.text === "string") return { Text: content.resource.text };
|
|
2750
|
-
return { Mention: {
|
|
2751
|
-
uri: content.resource.uri,
|
|
2752
|
-
content: content.resource.uri
|
|
2753
|
-
} };
|
|
2754
|
-
}
|
|
4464
|
+
if (content.type === "resource") return resourceToUserContent(content);
|
|
2755
4465
|
if (content.type === "image") return { Image: {
|
|
2756
4466
|
source: content.data,
|
|
2757
4467
|
size: null
|
|
2758
4468
|
} };
|
|
4469
|
+
if (content.type === "audio") return { Audio: {
|
|
4470
|
+
source: content.data,
|
|
4471
|
+
mime_type: content.mimeType
|
|
4472
|
+
} };
|
|
4473
|
+
}
|
|
4474
|
+
function resourceToUserContent(content) {
|
|
4475
|
+
if ("text" in content.resource && typeof content.resource.text === "string") return { Text: content.resource.text };
|
|
4476
|
+
return { Mention: {
|
|
4477
|
+
uri: content.resource.uri,
|
|
4478
|
+
content: content.resource.uri
|
|
4479
|
+
} };
|
|
2759
4480
|
}
|
|
2760
4481
|
function nextUserMessageId() {
|
|
2761
4482
|
return randomUUID();
|
|
@@ -2857,38 +4578,67 @@ function ensureToolUseContent(agent, toolCallId) {
|
|
|
2857
4578
|
}
|
|
2858
4579
|
function upsertToolResult(agent, toolCallId, patch) {
|
|
2859
4580
|
const existing = agent.tool_results[toolCallId];
|
|
4581
|
+
const fallback = existingToolResultValues(existing);
|
|
2860
4582
|
const next = {
|
|
2861
4583
|
tool_use_id: toolCallId,
|
|
2862
|
-
tool_name: patch.tool_name ??
|
|
2863
|
-
is_error: patch.is_error ??
|
|
2864
|
-
content: patch.content ??
|
|
2865
|
-
output: patch.output ??
|
|
4584
|
+
tool_name: patch.tool_name ?? fallback.tool_name,
|
|
4585
|
+
is_error: patch.is_error ?? fallback.is_error,
|
|
4586
|
+
content: patch.content ?? fallback.content,
|
|
4587
|
+
output: patch.output ?? fallback.output
|
|
2866
4588
|
};
|
|
2867
4589
|
agent.tool_results[toolCallId] = next;
|
|
2868
4590
|
}
|
|
4591
|
+
function existingToolResultValues(existing) {
|
|
4592
|
+
if (existing) return existing;
|
|
4593
|
+
return {
|
|
4594
|
+
tool_use_id: "",
|
|
4595
|
+
tool_name: "tool_call",
|
|
4596
|
+
is_error: false,
|
|
4597
|
+
content: { Text: "" },
|
|
4598
|
+
output: void 0
|
|
4599
|
+
};
|
|
4600
|
+
}
|
|
2869
4601
|
function applyToolCallUpdate(agent, update) {
|
|
2870
4602
|
const tool = ensureToolUseContent(agent, update.toolCallId);
|
|
4603
|
+
applyToolIdentityUpdate(tool, update);
|
|
4604
|
+
applyToolInputUpdate(tool, update);
|
|
4605
|
+
applyToolStatusUpdate(tool, update);
|
|
4606
|
+
applyToolResultUpdate(agent, tool, update);
|
|
4607
|
+
}
|
|
4608
|
+
function applyToolIdentityUpdate(tool, update) {
|
|
2871
4609
|
if (hasOwn(update, "title")) tool.name = normalizeAgentName(update.title) ?? tool.name ?? "tool_call";
|
|
2872
4610
|
if (hasOwn(update, "kind")) {
|
|
2873
4611
|
const kindName = normalizeAgentName(update.kind);
|
|
2874
4612
|
if (!tool.name || tool.name === "tool_call") tool.name = kindName ?? tool.name;
|
|
2875
4613
|
}
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
}
|
|
4614
|
+
}
|
|
4615
|
+
function applyToolInputUpdate(tool, update) {
|
|
4616
|
+
if (!hasOwn(update, "rawInput")) return;
|
|
4617
|
+
const rawInput = deepClone(update.rawInput);
|
|
4618
|
+
tool.input = rawInput ?? {};
|
|
4619
|
+
tool.raw_input = toRawInput(rawInput);
|
|
4620
|
+
}
|
|
4621
|
+
function applyToolStatusUpdate(tool, update) {
|
|
2881
4622
|
if (hasOwn(update, "status")) tool.is_input_complete = statusIndicatesComplete(update.status);
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
4623
|
+
}
|
|
4624
|
+
function applyToolResultUpdate(agent, tool, update) {
|
|
4625
|
+
if (!hasToolResultPatch(update)) return;
|
|
4626
|
+
const status = update.status;
|
|
4627
|
+
const output = hasOwn(update, "rawOutput") ? deepClone(update.rawOutput) : void 0;
|
|
4628
|
+
upsertToolResult(agent, update.toolCallId, {
|
|
4629
|
+
tool_name: tool.name,
|
|
4630
|
+
is_error: statusIndicatesError(status),
|
|
4631
|
+
content: output === void 0 ? void 0 : toToolResultContent(output),
|
|
4632
|
+
output
|
|
4633
|
+
});
|
|
4634
|
+
}
|
|
4635
|
+
function hasToolResultPatch(update) {
|
|
4636
|
+
return [
|
|
4637
|
+
"rawOutput",
|
|
4638
|
+
"status",
|
|
4639
|
+
"title",
|
|
4640
|
+
"kind"
|
|
4641
|
+
].some((key) => hasOwn(update, key));
|
|
2892
4642
|
}
|
|
2893
4643
|
function asRecord(value) {
|
|
2894
4644
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
@@ -2919,9 +4669,12 @@ function usageToTokenUsage(update) {
|
|
|
2919
4669
|
"cachedReadTokens"
|
|
2920
4670
|
])
|
|
2921
4671
|
};
|
|
2922
|
-
if (normalized
|
|
4672
|
+
if (!hasTokenUsageValue(normalized)) return;
|
|
2923
4673
|
return normalized;
|
|
2924
4674
|
}
|
|
4675
|
+
function hasTokenUsageValue(usage) {
|
|
4676
|
+
return Object.values(usage).some((value) => value !== void 0);
|
|
4677
|
+
}
|
|
2925
4678
|
function ensureAcpxState$1(state) {
|
|
2926
4679
|
return state ?? {};
|
|
2927
4680
|
}
|
|
@@ -2960,14 +4713,21 @@ function cloneSessionAcpxState(state) {
|
|
|
2960
4713
|
available_models: state.available_models ? [...state.available_models] : void 0,
|
|
2961
4714
|
available_commands: state.available_commands ? [...state.available_commands] : void 0,
|
|
2962
4715
|
config_options: state.config_options ? deepClone(state.config_options) : void 0,
|
|
2963
|
-
session_options: state.session_options
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
4716
|
+
session_options: cloneSessionOptions(state.session_options)
|
|
4717
|
+
};
|
|
4718
|
+
}
|
|
4719
|
+
function cloneSessionOptions(options) {
|
|
4720
|
+
if (!options) return;
|
|
4721
|
+
return {
|
|
4722
|
+
model: options.model,
|
|
4723
|
+
allowed_tools: options.allowed_tools ? [...options.allowed_tools] : void 0,
|
|
4724
|
+
max_turns: options.max_turns,
|
|
4725
|
+
...options.system_prompt !== void 0 ? { system_prompt: cloneSystemPromptOption(options.system_prompt) } : {}
|
|
2969
4726
|
};
|
|
2970
4727
|
}
|
|
4728
|
+
function cloneSystemPromptOption(option) {
|
|
4729
|
+
return typeof option === "string" ? option : { append: option.append };
|
|
4730
|
+
}
|
|
2971
4731
|
function recordPromptSubmission(conversation, prompt, timestamp = isoNow()) {
|
|
2972
4732
|
const userContent = (typeof prompt === "string" ? textPrompt(prompt) : prompt).map((content) => contentToUserContent(content)).filter((content) => content !== void 0);
|
|
2973
4733
|
if (userContent.length === 0) return;
|
|
@@ -3000,57 +4760,70 @@ function hasAgentReplyAfterPrompt(conversation, promptMessageId) {
|
|
|
3000
4760
|
function recordSessionUpdate(conversation, state, notification, timestamp = isoNow()) {
|
|
3001
4761
|
const acpx = ensureAcpxState$1(state);
|
|
3002
4762
|
const update = notification.update;
|
|
3003
|
-
|
|
3004
|
-
case "user_message_chunk": {
|
|
3005
|
-
const userContent = contentToUserContent(update.content);
|
|
3006
|
-
if (userContent) conversation.messages.push({ User: {
|
|
3007
|
-
id: nextUserMessageId(),
|
|
3008
|
-
content: [userContent]
|
|
3009
|
-
} });
|
|
3010
|
-
break;
|
|
3011
|
-
}
|
|
3012
|
-
case "agent_message_chunk": {
|
|
3013
|
-
const text = extractText(update.content);
|
|
3014
|
-
if (text) appendAgentText(ensureAgentMessage(conversation), text);
|
|
3015
|
-
break;
|
|
3016
|
-
}
|
|
3017
|
-
case "agent_thought_chunk": {
|
|
3018
|
-
const text = extractText(update.content);
|
|
3019
|
-
if (text) appendAgentThinking(ensureAgentMessage(conversation), text);
|
|
3020
|
-
break;
|
|
3021
|
-
}
|
|
3022
|
-
case "tool_call":
|
|
3023
|
-
case "tool_call_update":
|
|
3024
|
-
applyToolCallUpdate(ensureAgentMessage(conversation), update);
|
|
3025
|
-
break;
|
|
3026
|
-
case "usage_update": {
|
|
3027
|
-
const usage = usageToTokenUsage(update);
|
|
3028
|
-
if (usage) {
|
|
3029
|
-
conversation.cumulative_token_usage = usage;
|
|
3030
|
-
const userId = lastUserMessageId(conversation);
|
|
3031
|
-
if (userId) conversation.request_token_usage[userId] = usage;
|
|
3032
|
-
}
|
|
3033
|
-
break;
|
|
3034
|
-
}
|
|
3035
|
-
case "session_info_update":
|
|
3036
|
-
if (hasOwn(update, "title")) conversation.title = update.title ?? null;
|
|
3037
|
-
if (hasOwn(update, "updatedAt")) conversation.updated_at = update.updatedAt ?? conversation.updated_at;
|
|
3038
|
-
break;
|
|
3039
|
-
case "available_commands_update":
|
|
3040
|
-
acpx.available_commands = update.availableCommands.map((entry) => entry.name).filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
3041
|
-
break;
|
|
3042
|
-
case "current_mode_update":
|
|
3043
|
-
acpx.current_mode_id = update.currentModeId;
|
|
3044
|
-
break;
|
|
3045
|
-
case "config_option_update":
|
|
3046
|
-
acpx.config_options = deepClone(update.configOptions);
|
|
3047
|
-
break;
|
|
3048
|
-
default: break;
|
|
3049
|
-
}
|
|
4763
|
+
applySessionUpdate(conversation, acpx, update);
|
|
3050
4764
|
updateConversationTimestamp(conversation, timestamp);
|
|
3051
4765
|
trimConversationForRuntime(conversation);
|
|
3052
4766
|
return acpx;
|
|
3053
4767
|
}
|
|
4768
|
+
function applySessionUpdate(conversation, acpx, update) {
|
|
4769
|
+
const handler = SESSION_UPDATE_HANDLERS[update.sessionUpdate];
|
|
4770
|
+
handler?.(conversation, acpx, update);
|
|
4771
|
+
}
|
|
4772
|
+
const SESSION_UPDATE_HANDLERS = {
|
|
4773
|
+
user_message_chunk: (conversation, _acpx, update) => {
|
|
4774
|
+
if (update.sessionUpdate === "user_message_chunk") appendUserMessageChunk(conversation, update.content);
|
|
4775
|
+
},
|
|
4776
|
+
agent_message_chunk: (conversation, _acpx, update) => {
|
|
4777
|
+
if (update.sessionUpdate === "agent_message_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentText);
|
|
4778
|
+
},
|
|
4779
|
+
agent_thought_chunk: (conversation, _acpx, update) => {
|
|
4780
|
+
if (update.sessionUpdate === "agent_thought_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentThinking);
|
|
4781
|
+
},
|
|
4782
|
+
tool_call: (conversation, _acpx, update) => {
|
|
4783
|
+
if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") applyToolCallUpdate(ensureAgentMessage(conversation), update);
|
|
4784
|
+
},
|
|
4785
|
+
tool_call_update: (conversation, _acpx, update) => {
|
|
4786
|
+
if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") applyToolCallUpdate(ensureAgentMessage(conversation), update);
|
|
4787
|
+
},
|
|
4788
|
+
usage_update: (conversation, _acpx, update) => {
|
|
4789
|
+
if (update.sessionUpdate === "usage_update") applyUsageUpdate(conversation, update);
|
|
4790
|
+
},
|
|
4791
|
+
session_info_update: (conversation, _acpx, update) => {
|
|
4792
|
+
if (update.sessionUpdate === "session_info_update") applySessionInfoUpdate(conversation, update);
|
|
4793
|
+
},
|
|
4794
|
+
available_commands_update: (_conversation, acpx, update) => {
|
|
4795
|
+
if (update.sessionUpdate === "available_commands_update") acpx.available_commands = update.availableCommands.map((entry) => entry.name).filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
4796
|
+
},
|
|
4797
|
+
current_mode_update: (_conversation, acpx, update) => {
|
|
4798
|
+
if (update.sessionUpdate === "current_mode_update") acpx.current_mode_id = update.currentModeId;
|
|
4799
|
+
},
|
|
4800
|
+
config_option_update: (_conversation, acpx, update) => {
|
|
4801
|
+
if (update.sessionUpdate === "config_option_update") acpx.config_options = deepClone(update.configOptions);
|
|
4802
|
+
}
|
|
4803
|
+
};
|
|
4804
|
+
function appendUserMessageChunk(conversation, content) {
|
|
4805
|
+
const userContent = contentToUserContent(content);
|
|
4806
|
+
if (!userContent) return;
|
|
4807
|
+
conversation.messages.push({ User: {
|
|
4808
|
+
id: nextUserMessageId(),
|
|
4809
|
+
content: [userContent]
|
|
4810
|
+
} });
|
|
4811
|
+
}
|
|
4812
|
+
function appendAgentMessageChunk(conversation, content, append) {
|
|
4813
|
+
const text = extractText(content);
|
|
4814
|
+
if (text) append(ensureAgentMessage(conversation), text);
|
|
4815
|
+
}
|
|
4816
|
+
function applyUsageUpdate(conversation, update) {
|
|
4817
|
+
const usage = usageToTokenUsage(update);
|
|
4818
|
+
if (!usage) return;
|
|
4819
|
+
conversation.cumulative_token_usage = usage;
|
|
4820
|
+
const userId = lastUserMessageId(conversation);
|
|
4821
|
+
if (userId) conversation.request_token_usage[userId] = usage;
|
|
4822
|
+
}
|
|
4823
|
+
function applySessionInfoUpdate(conversation, update) {
|
|
4824
|
+
if (hasOwn(update, "title")) conversation.title = update.title ?? null;
|
|
4825
|
+
if (hasOwn(update, "updatedAt")) conversation.updated_at = update.updatedAt ?? conversation.updated_at;
|
|
4826
|
+
}
|
|
3054
4827
|
function recordClientOperation(conversation, state, operation, timestamp = isoNow()) {
|
|
3055
4828
|
const acpx = ensureAcpxState$1(state);
|
|
3056
4829
|
updateConversationTimestamp(conversation, timestamp);
|
|
@@ -3059,25 +4832,36 @@ function recordClientOperation(conversation, state, operation, timestamp = isoNo
|
|
|
3059
4832
|
}
|
|
3060
4833
|
function trimConversationForRuntime(conversation) {
|
|
3061
4834
|
if (conversation.messages.length > MAX_RUNTIME_MESSAGES) conversation.messages = conversation.messages.slice(-MAX_RUNTIME_MESSAGES);
|
|
3062
|
-
for (const message of conversation.messages)
|
|
3063
|
-
if (!isAgentMessage(message)) {
|
|
3064
|
-
if (isUserMessage(message)) message.User.content = message.User.content.map((content) => {
|
|
3065
|
-
if ("Text" in content) return { Text: trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS) };
|
|
3066
|
-
return content;
|
|
3067
|
-
});
|
|
3068
|
-
continue;
|
|
3069
|
-
}
|
|
3070
|
-
for (const content of message.Agent.content) if ("Text" in content) content.Text = trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS);
|
|
3071
|
-
else if ("Thinking" in content) content.Thinking.text = trimRuntimeText(content.Thinking.text, MAX_RUNTIME_THINKING_CHARS);
|
|
3072
|
-
else if ("ToolUse" in content) content.ToolUse.raw_input = trimRuntimeText(content.ToolUse.raw_input, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
3073
|
-
for (const result of Object.values(message.Agent.tool_results)) {
|
|
3074
|
-
if ("Text" in result.content) result.content.Text = trimRuntimeText(result.content.Text, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
3075
|
-
if (typeof result.output === "string") result.output = trimRuntimeText(result.output, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
3076
|
-
}
|
|
3077
|
-
}
|
|
4835
|
+
for (const message of conversation.messages) trimRuntimeMessage(message);
|
|
3078
4836
|
const requestUsageEntries = Object.entries(conversation.request_token_usage);
|
|
3079
4837
|
if (requestUsageEntries.length > MAX_RUNTIME_REQUEST_TOKEN_USAGE) conversation.request_token_usage = Object.fromEntries(requestUsageEntries.slice(-MAX_RUNTIME_REQUEST_TOKEN_USAGE));
|
|
3080
4838
|
}
|
|
4839
|
+
function trimRuntimeMessage(message) {
|
|
4840
|
+
if (isUserMessage(message)) {
|
|
4841
|
+
trimRuntimeUserMessage(message.User);
|
|
4842
|
+
return;
|
|
4843
|
+
}
|
|
4844
|
+
if (isAgentMessage(message)) trimRuntimeAgentMessage(message.Agent);
|
|
4845
|
+
}
|
|
4846
|
+
function trimRuntimeUserMessage(message) {
|
|
4847
|
+
message.content = message.content.map((content) => {
|
|
4848
|
+
if ("Text" in content) return { Text: trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS) };
|
|
4849
|
+
return content;
|
|
4850
|
+
});
|
|
4851
|
+
}
|
|
4852
|
+
function trimRuntimeAgentMessage(message) {
|
|
4853
|
+
for (const content of message.content) trimRuntimeAgentContent(content);
|
|
4854
|
+
for (const result of Object.values(message.tool_results)) trimRuntimeToolResult(result);
|
|
4855
|
+
}
|
|
4856
|
+
function trimRuntimeAgentContent(content) {
|
|
4857
|
+
if ("Text" in content) content.Text = trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS);
|
|
4858
|
+
else if ("Thinking" in content) content.Thinking.text = trimRuntimeText(content.Thinking.text, MAX_RUNTIME_THINKING_CHARS);
|
|
4859
|
+
else if ("ToolUse" in content) content.ToolUse.raw_input = trimRuntimeText(content.ToolUse.raw_input, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
4860
|
+
}
|
|
4861
|
+
function trimRuntimeToolResult(result) {
|
|
4862
|
+
if ("Text" in result.content) result.content.Text = trimRuntimeText(result.content.Text, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
4863
|
+
if (typeof result.output === "string") result.output = trimRuntimeText(result.output, MAX_RUNTIME_TOOL_IO_CHARS);
|
|
4864
|
+
}
|
|
3081
4865
|
//#endregion
|
|
3082
4866
|
//#region src/session/config-options.ts
|
|
3083
4867
|
function applyConfigOptionsToRecord(record, result) {
|
|
@@ -3183,37 +4967,20 @@ function assertRequestedModelSupported(params) {
|
|
|
3183
4967
|
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
4968
|
}
|
|
3185
4969
|
//#endregion
|
|
3186
|
-
//#region src/
|
|
3187
|
-
function
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
record.lastAgentExitAt = void 0;
|
|
3201
|
-
record.lastAgentDisconnectReason = void 0;
|
|
3202
|
-
}
|
|
3203
|
-
function reconcileAgentSessionId(record, agentSessionId) {
|
|
3204
|
-
const normalized = normalizeRuntimeSessionId(agentSessionId);
|
|
3205
|
-
if (!normalized) return;
|
|
3206
|
-
record.agentSessionId = normalized;
|
|
3207
|
-
}
|
|
3208
|
-
function sessionHasAgentMessages(recordOrConversation) {
|
|
3209
|
-
return recordOrConversation.messages.some((message) => typeof message === "object" && message !== null && "Agent" in message);
|
|
3210
|
-
}
|
|
3211
|
-
function applyConversation(record, conversation) {
|
|
3212
|
-
record.title = conversation.title;
|
|
3213
|
-
record.updated_at = conversation.updated_at;
|
|
3214
|
-
record.messages = conversation.messages;
|
|
3215
|
-
record.cumulative_token_usage = conversation.cumulative_token_usage;
|
|
3216
|
-
record.request_token_usage = conversation.request_token_usage;
|
|
4970
|
+
//#region src/session/model-application.ts
|
|
4971
|
+
async function applyRequestedModelIfAdvertised(params) {
|
|
4972
|
+
const requestedModel = typeof params.requestedModel === "string" ? params.requestedModel.trim() : "";
|
|
4973
|
+
if (!requestedModel) return false;
|
|
4974
|
+
assertRequestedModelSupported({
|
|
4975
|
+
requestedModel,
|
|
4976
|
+
models: params.models,
|
|
4977
|
+
agentCommand: params.agentCommand,
|
|
4978
|
+
context: "apply"
|
|
4979
|
+
});
|
|
4980
|
+
if (!params.models) return false;
|
|
4981
|
+
if (params.models.currentModelId === requestedModel) return true;
|
|
4982
|
+
await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel), params.timeoutMs);
|
|
4983
|
+
return true;
|
|
3217
4984
|
}
|
|
3218
4985
|
//#endregion
|
|
3219
4986
|
//#region src/runtime/engine/reconnect.ts
|
|
@@ -3228,15 +4995,19 @@ function isProcessAlive(pid) {
|
|
|
3228
4995
|
}
|
|
3229
4996
|
const SESSION_LOAD_UNSUPPORTED_CODES = new Set([-32601, -32602]);
|
|
3230
4997
|
function shouldFallbackToNewSession(error, record) {
|
|
3231
|
-
if (error
|
|
3232
|
-
if (isAcpResourceNotFoundError(error)) return true;
|
|
4998
|
+
if (isHardReconnectFailure(error)) return false;
|
|
3233
4999
|
const acp = extractAcpError(error);
|
|
3234
|
-
if (
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
5000
|
+
if (isAcpResourceNotFoundError(error) || isUnsupportedSessionLoadAcpError(acp)) return true;
|
|
5001
|
+
return !sessionHasAgentMessages(record) && isFallbackSafeEmptySessionError(error, acp);
|
|
5002
|
+
}
|
|
5003
|
+
function isHardReconnectFailure(error) {
|
|
5004
|
+
return error instanceof TimeoutError || error instanceof InterruptedError;
|
|
5005
|
+
}
|
|
5006
|
+
function isUnsupportedSessionLoadAcpError(acp) {
|
|
5007
|
+
return !!acp && SESSION_LOAD_UNSUPPORTED_CODES.has(acp.code);
|
|
5008
|
+
}
|
|
5009
|
+
function isFallbackSafeEmptySessionError(error, acp) {
|
|
5010
|
+
return isAcpQueryClosedBeforeResponseError(error) || acp?.code === -32603;
|
|
3240
5011
|
}
|
|
3241
5012
|
function requiresSameSession(resumePolicy) {
|
|
3242
5013
|
return resumePolicy === "same-session-only";
|
|
@@ -3300,11 +5071,7 @@ async function connectAndLoadSession(options) {
|
|
|
3300
5071
|
const desiredModelId = getDesiredModelId(record.acpx);
|
|
3301
5072
|
const desiredConfigOptions = getDesiredConfigOptions(record.acpx);
|
|
3302
5073
|
const storedProcessAlive = isProcessAlive(record.pid);
|
|
3303
|
-
|
|
3304
|
-
if (options.verbose) {
|
|
3305
|
-
if (storedProcessAlive) process.stderr.write(`[acpx] saved session pid ${record.pid} is running; reconnecting with loadSession\n`);
|
|
3306
|
-
else if (shouldReconnect) process.stderr.write(`[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session/load\n`);
|
|
3307
|
-
}
|
|
5074
|
+
logReconnectAttempt(record, storedProcessAlive, Boolean(record.pid) && !storedProcessAlive, options.verbose);
|
|
3308
5075
|
const reusingLoadedSession = client.hasReusableSession(record.acpSessionId);
|
|
3309
5076
|
if (reusingLoadedSession) incrementPerfCounter("runtime.connect_and_load.reused_session");
|
|
3310
5077
|
else await withTimeout(client.start(), options.timeoutMs);
|
|
@@ -3319,82 +5086,35 @@ async function connectAndLoadSession(options) {
|
|
|
3319
5086
|
let createdFreshSession = false;
|
|
3320
5087
|
let pendingAgentSessionId = record.agentSessionId;
|
|
3321
5088
|
let sessionModels;
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
pendingAgentSessionId
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
createdFreshSession = true;
|
|
3352
|
-
pendingAgentSessionId = createdSession.agentSessionId;
|
|
3353
|
-
applyConfigOptionsToRecord(record, createdSession);
|
|
3354
|
-
sessionModels = createdSession.models;
|
|
3355
|
-
}
|
|
3356
|
-
if (createdFreshSession) {
|
|
3357
|
-
try {
|
|
3358
|
-
await replayDesiredMode({
|
|
3359
|
-
client,
|
|
3360
|
-
sessionId,
|
|
3361
|
-
desiredModeId,
|
|
3362
|
-
previousSessionId: originalSessionId,
|
|
3363
|
-
timeoutMs: options.timeoutMs,
|
|
3364
|
-
verbose: options.verbose
|
|
3365
|
-
});
|
|
3366
|
-
await replayDesiredModel({
|
|
3367
|
-
client,
|
|
3368
|
-
sessionId,
|
|
3369
|
-
desiredModelId,
|
|
3370
|
-
previousSessionId: originalSessionId,
|
|
3371
|
-
record,
|
|
3372
|
-
models: sessionModels,
|
|
3373
|
-
timeoutMs: options.timeoutMs,
|
|
3374
|
-
verbose: options.verbose
|
|
3375
|
-
});
|
|
3376
|
-
await replayDesiredConfigOptions({
|
|
3377
|
-
client,
|
|
3378
|
-
sessionId,
|
|
3379
|
-
desiredConfigOptions,
|
|
3380
|
-
previousSessionId: originalSessionId,
|
|
3381
|
-
timeoutMs: options.timeoutMs,
|
|
3382
|
-
verbose: options.verbose
|
|
3383
|
-
});
|
|
3384
|
-
} catch (error) {
|
|
3385
|
-
restoreOriginalSessionState({
|
|
3386
|
-
record,
|
|
3387
|
-
sessionId: originalSessionId,
|
|
3388
|
-
agentSessionId: originalAgentSessionId
|
|
3389
|
-
});
|
|
3390
|
-
if (options.verbose) process.stderr.write(`[acpx] ${formatErrorMessage(error)}\n`);
|
|
3391
|
-
throw error;
|
|
3392
|
-
}
|
|
3393
|
-
record.acpSessionId = sessionId;
|
|
3394
|
-
reconcileAgentSessionId(record, pendingAgentSessionId);
|
|
3395
|
-
}
|
|
3396
|
-
syncAdvertisedModelState(record, sessionModels);
|
|
3397
|
-
if (createdFreshSession && desiredModelId && sessionModels) setCurrentModelId(record, desiredModelId);
|
|
5089
|
+
const loadState = await loadOrCreateRuntimeSession({
|
|
5090
|
+
client,
|
|
5091
|
+
record,
|
|
5092
|
+
reusingLoadedSession,
|
|
5093
|
+
sameSessionOnly,
|
|
5094
|
+
timeoutMs: options.timeoutMs
|
|
5095
|
+
});
|
|
5096
|
+
resumed = loadState.resumed;
|
|
5097
|
+
loadError = loadState.loadError;
|
|
5098
|
+
sessionId = loadState.sessionId;
|
|
5099
|
+
createdFreshSession = loadState.createdFreshSession;
|
|
5100
|
+
pendingAgentSessionId = loadState.pendingAgentSessionId;
|
|
5101
|
+
sessionModels = loadState.sessionModels;
|
|
5102
|
+
await replayFreshSessionPreferences({
|
|
5103
|
+
client,
|
|
5104
|
+
record,
|
|
5105
|
+
createdFreshSession,
|
|
5106
|
+
sessionId,
|
|
5107
|
+
pendingAgentSessionId,
|
|
5108
|
+
originalSessionId,
|
|
5109
|
+
originalAgentSessionId,
|
|
5110
|
+
desiredModeId,
|
|
5111
|
+
desiredModelId,
|
|
5112
|
+
desiredConfigOptions,
|
|
5113
|
+
sessionModels,
|
|
5114
|
+
timeoutMs: options.timeoutMs,
|
|
5115
|
+
verbose: options.verbose
|
|
5116
|
+
});
|
|
5117
|
+
applyReconnectedModelState(record, sessionModels, createdFreshSession, desiredModelId);
|
|
3398
5118
|
options.onSessionIdResolved?.(sessionId);
|
|
3399
5119
|
return {
|
|
3400
5120
|
sessionId,
|
|
@@ -3403,27 +5123,130 @@ async function connectAndLoadSession(options) {
|
|
|
3403
5123
|
loadError
|
|
3404
5124
|
};
|
|
3405
5125
|
}
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
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;
|
|
5126
|
+
function applyReconnectedModelState(record, sessionModels, createdFreshSession, desiredModelId) {
|
|
5127
|
+
syncAdvertisedModelState(record, sessionModels);
|
|
5128
|
+
if (createdFreshSession && desiredModelId && sessionModels) setCurrentModelId(record, desiredModelId);
|
|
3415
5129
|
}
|
|
3416
|
-
function
|
|
3417
|
-
|
|
3418
|
-
if (
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
if (
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
5130
|
+
function logReconnectAttempt(record, storedProcessAlive, shouldReconnect, verbose) {
|
|
5131
|
+
if (!verbose) return;
|
|
5132
|
+
if (storedProcessAlive) {
|
|
5133
|
+
process.stderr.write(`[acpx] saved session pid ${record.pid} is running; reconnecting to saved ACP session\n`);
|
|
5134
|
+
return;
|
|
5135
|
+
}
|
|
5136
|
+
if (shouldReconnect) process.stderr.write(`[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session reconnect\n`);
|
|
5137
|
+
}
|
|
5138
|
+
async function replayFreshSessionPreferences(params) {
|
|
5139
|
+
if (!params.createdFreshSession) return;
|
|
5140
|
+
try {
|
|
5141
|
+
await replayDesiredMode({
|
|
5142
|
+
client: params.client,
|
|
5143
|
+
sessionId: params.sessionId,
|
|
5144
|
+
desiredModeId: params.desiredModeId,
|
|
5145
|
+
previousSessionId: params.originalSessionId,
|
|
5146
|
+
timeoutMs: params.timeoutMs,
|
|
5147
|
+
verbose: params.verbose
|
|
5148
|
+
});
|
|
5149
|
+
await replayDesiredModel({
|
|
5150
|
+
client: params.client,
|
|
5151
|
+
sessionId: params.sessionId,
|
|
5152
|
+
desiredModelId: params.desiredModelId,
|
|
5153
|
+
previousSessionId: params.originalSessionId,
|
|
5154
|
+
record: params.record,
|
|
5155
|
+
models: params.sessionModels,
|
|
5156
|
+
timeoutMs: params.timeoutMs,
|
|
5157
|
+
verbose: params.verbose
|
|
5158
|
+
});
|
|
5159
|
+
await replayDesiredConfigOptions({
|
|
5160
|
+
client: params.client,
|
|
5161
|
+
sessionId: params.sessionId,
|
|
5162
|
+
desiredConfigOptions: params.desiredConfigOptions,
|
|
5163
|
+
previousSessionId: params.originalSessionId,
|
|
5164
|
+
timeoutMs: params.timeoutMs,
|
|
5165
|
+
verbose: params.verbose
|
|
5166
|
+
});
|
|
5167
|
+
} catch (error) {
|
|
5168
|
+
restoreOriginalSessionState({
|
|
5169
|
+
record: params.record,
|
|
5170
|
+
sessionId: params.originalSessionId,
|
|
5171
|
+
agentSessionId: params.originalAgentSessionId
|
|
5172
|
+
});
|
|
5173
|
+
if (params.verbose) process.stderr.write(`[acpx] ${formatErrorMessage(error)}\n`);
|
|
5174
|
+
throw error;
|
|
5175
|
+
}
|
|
5176
|
+
params.record.acpSessionId = params.sessionId;
|
|
5177
|
+
reconcileAgentSessionId(params.record, params.pendingAgentSessionId);
|
|
5178
|
+
}
|
|
5179
|
+
async function loadOrCreateRuntimeSession(params) {
|
|
5180
|
+
if (params.reusingLoadedSession) return {
|
|
5181
|
+
sessionId: params.record.acpSessionId,
|
|
5182
|
+
pendingAgentSessionId: params.record.agentSessionId,
|
|
5183
|
+
sessionModels: void 0,
|
|
5184
|
+
resumed: true,
|
|
5185
|
+
createdFreshSession: false
|
|
5186
|
+
};
|
|
5187
|
+
if (params.client.supportsResumeSession()) return await resumeRuntimeSession(params);
|
|
5188
|
+
if (params.client.supportsLoadSession()) return await loadRuntimeSession(params);
|
|
5189
|
+
if (params.sameSessionOnly) throw makeSessionResumeRequiredError({
|
|
5190
|
+
record: params.record,
|
|
5191
|
+
reason: "agent does not support session/resume or session/load"
|
|
5192
|
+
});
|
|
5193
|
+
return await createFreshRuntimeSession(params.client, params.record, params.timeoutMs);
|
|
5194
|
+
}
|
|
5195
|
+
async function resumeRuntimeSession(params) {
|
|
5196
|
+
try {
|
|
5197
|
+
const resumeResult = await withTimeout(params.client.resumeSession(params.record.acpSessionId, params.record.cwd), params.timeoutMs);
|
|
5198
|
+
reconcileAgentSessionId(params.record, resumeResult.agentSessionId);
|
|
5199
|
+
applyConfigOptionsToRecord(params.record, resumeResult);
|
|
5200
|
+
return {
|
|
5201
|
+
sessionId: params.record.acpSessionId,
|
|
5202
|
+
pendingAgentSessionId: params.record.agentSessionId,
|
|
5203
|
+
sessionModels: resumeResult.models,
|
|
5204
|
+
resumed: true,
|
|
5205
|
+
createdFreshSession: false
|
|
5206
|
+
};
|
|
5207
|
+
} catch (error) {
|
|
5208
|
+
return await recoverRuntimeSessionLoadFailure(params, error);
|
|
5209
|
+
}
|
|
5210
|
+
}
|
|
5211
|
+
async function loadRuntimeSession(params) {
|
|
5212
|
+
try {
|
|
5213
|
+
const loadResult = await withTimeout(params.client.loadSessionWithOptions(params.record.acpSessionId, params.record.cwd, { suppressReplayUpdates: true }), params.timeoutMs);
|
|
5214
|
+
reconcileAgentSessionId(params.record, loadResult.agentSessionId);
|
|
5215
|
+
applyConfigOptionsToRecord(params.record, loadResult);
|
|
5216
|
+
return {
|
|
5217
|
+
sessionId: params.record.acpSessionId,
|
|
5218
|
+
pendingAgentSessionId: params.record.agentSessionId,
|
|
5219
|
+
sessionModels: loadResult.models,
|
|
5220
|
+
resumed: true,
|
|
5221
|
+
createdFreshSession: false
|
|
5222
|
+
};
|
|
5223
|
+
} catch (error) {
|
|
5224
|
+
return await recoverRuntimeSessionLoadFailure(params, error);
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5227
|
+
async function recoverRuntimeSessionLoadFailure(params, error) {
|
|
5228
|
+
const loadError = formatErrorMessage(error);
|
|
5229
|
+
if (params.sameSessionOnly) throw makeSessionResumeRequiredError({
|
|
5230
|
+
record: params.record,
|
|
5231
|
+
reason: loadError,
|
|
5232
|
+
cause: error
|
|
5233
|
+
});
|
|
5234
|
+
if (!shouldFallbackToNewSession(error, params.record)) throw error;
|
|
5235
|
+
return {
|
|
5236
|
+
...await createFreshRuntimeSession(params.client, params.record, params.timeoutMs),
|
|
5237
|
+
loadError
|
|
5238
|
+
};
|
|
5239
|
+
}
|
|
5240
|
+
async function createFreshRuntimeSession(client, record, timeoutMs) {
|
|
5241
|
+
const createdSession = await withTimeout(client.createSession(record.cwd), timeoutMs);
|
|
5242
|
+
applyConfigOptionsToRecord(record, createdSession);
|
|
5243
|
+
return {
|
|
5244
|
+
sessionId: createdSession.sessionId,
|
|
5245
|
+
pendingAgentSessionId: createdSession.agentSessionId,
|
|
5246
|
+
sessionModels: createdSession.models,
|
|
5247
|
+
resumed: false,
|
|
5248
|
+
createdFreshSession: true
|
|
5249
|
+
};
|
|
3427
5250
|
}
|
|
3428
5251
|
//#endregion
|
|
3429
5252
|
//#region src/runtime/engine/connected-session.ts
|
|
@@ -3451,6 +5274,7 @@ async function withConnectedSession(options) {
|
|
|
3451
5274
|
mcpServers: options.mcpServers,
|
|
3452
5275
|
permissionMode: options.permissionMode ?? "approve-reads",
|
|
3453
5276
|
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
5277
|
+
onPermissionRequest: options.onPermissionRequest,
|
|
3454
5278
|
authCredentials: options.authCredentials,
|
|
3455
5279
|
authPolicy: options.authPolicy,
|
|
3456
5280
|
terminal: options.terminal,
|
|
@@ -3462,6 +5286,7 @@ async function withConnectedSession(options) {
|
|
|
3462
5286
|
mcpServers: options.mcpServers,
|
|
3463
5287
|
permissionMode: options.permissionMode ?? "approve-reads",
|
|
3464
5288
|
nonInteractivePermissions: options.nonInteractivePermissions,
|
|
5289
|
+
onPermissionRequest: options.onPermissionRequest,
|
|
3465
5290
|
authCredentials: options.authCredentials,
|
|
3466
5291
|
authPolicy: options.authPolicy,
|
|
3467
5292
|
terminal: options.terminal,
|
|
@@ -3562,6 +5387,59 @@ async function runPromptTurn(params) {
|
|
|
3562
5387
|
}
|
|
3563
5388
|
}
|
|
3564
5389
|
//#endregion
|
|
3565
|
-
|
|
5390
|
+
//#region src/session/live-checkpoint.ts
|
|
5391
|
+
const DEFAULT_LIVE_CHECKPOINT_INTERVAL_MS = 500;
|
|
5392
|
+
var LiveSessionCheckpoint = class {
|
|
5393
|
+
save;
|
|
5394
|
+
intervalMs;
|
|
5395
|
+
onError;
|
|
5396
|
+
dirty = false;
|
|
5397
|
+
flushing;
|
|
5398
|
+
timer;
|
|
5399
|
+
constructor(options) {
|
|
5400
|
+
this.save = options.save;
|
|
5401
|
+
this.intervalMs = options.intervalMs ?? DEFAULT_LIVE_CHECKPOINT_INTERVAL_MS;
|
|
5402
|
+
this.onError = options.onError;
|
|
5403
|
+
}
|
|
5404
|
+
request() {
|
|
5405
|
+
this.dirty = true;
|
|
5406
|
+
if (this.timer) return;
|
|
5407
|
+
this.timer = setTimeout(() => {
|
|
5408
|
+
this.timer = void 0;
|
|
5409
|
+
this.flush().catch((error) => {
|
|
5410
|
+
this.onError?.(error);
|
|
5411
|
+
});
|
|
5412
|
+
}, this.intervalMs);
|
|
5413
|
+
this.timer.unref?.();
|
|
5414
|
+
}
|
|
5415
|
+
async checkpoint() {
|
|
5416
|
+
this.dirty = true;
|
|
5417
|
+
await this.flush();
|
|
5418
|
+
}
|
|
5419
|
+
async flush() {
|
|
5420
|
+
if (this.timer) {
|
|
5421
|
+
clearTimeout(this.timer);
|
|
5422
|
+
this.timer = void 0;
|
|
5423
|
+
}
|
|
5424
|
+
if (this.flushing) {
|
|
5425
|
+
await this.flushing;
|
|
5426
|
+
if (!this.dirty) return;
|
|
5427
|
+
}
|
|
5428
|
+
this.flushing = this.flushDirty();
|
|
5429
|
+
try {
|
|
5430
|
+
await this.flushing;
|
|
5431
|
+
} finally {
|
|
5432
|
+
this.flushing = void 0;
|
|
5433
|
+
}
|
|
5434
|
+
}
|
|
5435
|
+
async flushDirty() {
|
|
5436
|
+
while (this.dirty) {
|
|
5437
|
+
this.dirty = false;
|
|
5438
|
+
await this.save();
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
};
|
|
5442
|
+
//#endregion
|
|
5443
|
+
export { getPerfMetricsSnapshot as $, parsePromptStopReason as A, EXIT_CODES as At, normalizeName as B, QueueProtocolError as Bt, applyConversation 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, AgentSpawnError as Rt, sessionOptionsFromRecord as S, exitCodeForOutputErrorCode as St, reconcileAgentSessionId as T, normalizeOutputError as Tt, writeSessionRecord as U, pruneSessions as V, parseSessionRecord as W, sessionEventSegmentPath as X, sessionEventLockPath as Y, assertPersistedKeyPolicy as Z, recordPromptSubmission as _, withTimeout as _t, applyRequestedModelIfAdvertised as a, startPerfTimer as at, mergeSessionOptions as b, normalizeAgentName$1 as bt, setDesiredConfigOption as c, PromptInputValidationError as ct, syncAdvertisedModelState as d, parsePromptSource as dt, incrementPerfCounter as et, applyConfigOptionsToRecord as f, promptToDisplayText as ft, recordClientOperation as g, withInterrupt as gt, createSessionConversation 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, setDesiredModeId as l, isPromptInput as lt, cloneSessionConversation as m, InterruptedError as mt, runPromptTurn as n, recordPerfDuration as nt, assertRequestedModelSupported as o, serializeSessionRecordForDisk as ot, cloneSessionAcpxState as p, textPrompt as pt, sessionBaseDir$1 as q, withConnectedSession as r, resetPerfMetrics as rt, setCurrentModelId as s, normalizeRuntimeSessionId as st, LiveSessionCheckpoint as t, measurePerf as tt, setDesiredModelId as u, mergePromptSourceWithText as ut, recordSessionUpdate as v, DEFAULT_AGENT_NAME as vt, applyLifecycleSnapshotToRecord as w, isRetryablePromptError as wt, persistSessionOptions as x, resolveAgentCommand as xt, trimConversationForRuntime as y, listBuiltInAgents as yt, listSessionsForAgent as z, QueueConnectionError as zt };
|
|
3566
5444
|
|
|
3567
|
-
//# sourceMappingURL=
|
|
5445
|
+
//# sourceMappingURL=live-checkpoint-D5d-K9s1.js.map
|