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.
Files changed (50) hide show
  1. package/README.md +14 -6
  2. package/dist/{cli-T-Z-9x6a.js → cli-Bf3yjqzE.js} +35 -10
  3. package/dist/cli-Bf3yjqzE.js.map +1 -0
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +724 -241
  7. package/dist/cli.js.map +1 -1
  8. package/dist/{client-COPilhO_.d.ts → client-BssohYqM.d.ts} +35 -4
  9. package/dist/client-BssohYqM.d.ts.map +1 -0
  10. package/dist/flags-C-rwARqg.js +260 -0
  11. package/dist/flags-C-rwARqg.js.map +1 -0
  12. package/dist/{flows-CF8w1rPI.js → flows-WLs26_5Y.js} +405 -337
  13. package/dist/flows-WLs26_5Y.js.map +1 -0
  14. package/dist/flows.d.ts +23 -2
  15. package/dist/flows.d.ts.map +1 -1
  16. package/dist/flows.js +1 -1
  17. package/dist/{prompt-turn-CVPMWdj1.js → live-checkpoint-D5d-K9s1.js} +2487 -609
  18. package/dist/live-checkpoint-D5d-K9s1.js.map +1 -0
  19. package/dist/output-DPg20dvn.js +4146 -0
  20. package/dist/output-DPg20dvn.js.map +1 -0
  21. package/dist/runtime.d.ts +56 -4
  22. package/dist/runtime.d.ts.map +1 -1
  23. package/dist/runtime.js +676 -393
  24. package/dist/runtime.js.map +1 -1
  25. package/dist/{types-CVBeQyi3.d.ts → session-options-CFudjdkU.d.ts} +62 -3
  26. package/dist/session-options-CFudjdkU.d.ts.map +1 -0
  27. package/package.json +30 -25
  28. package/skills/acpx/SKILL.md +211 -13
  29. package/dist/cli-T-Z-9x6a.js.map +0 -1
  30. package/dist/client-COPilhO_.d.ts.map +0 -1
  31. package/dist/flags-Dj-IXgo9.js +0 -163
  32. package/dist/flags-Dj-IXgo9.js.map +0 -1
  33. package/dist/flows-CF8w1rPI.js.map +0 -1
  34. package/dist/ipc-ABXlXzGP.js +0 -1290
  35. package/dist/ipc-ABXlXzGP.js.map +0 -1
  36. package/dist/jsonrpc-DSxh2w5R.js +0 -68
  37. package/dist/jsonrpc-DSxh2w5R.js.map +0 -1
  38. package/dist/output-DmHvT8vm.js +0 -807
  39. package/dist/output-DmHvT8vm.js.map +0 -1
  40. package/dist/perf-metrics-C2pXfxvR.js +0 -598
  41. package/dist/perf-metrics-C2pXfxvR.js.map +0 -1
  42. package/dist/prompt-turn-CVPMWdj1.js.map +0 -1
  43. package/dist/render-N5YwotCy.js +0 -172
  44. package/dist/render-N5YwotCy.js.map +0 -1
  45. package/dist/rolldown-runtime-CiIaOW0V.js +0 -13
  46. package/dist/session-CDaQe6BH.js +0 -1538
  47. package/dist/session-CDaQe6BH.js.map +0 -1
  48. package/dist/session-options-pCbHn_n7.d.ts +0 -13
  49. package/dist/session-options-pCbHn_n7.d.ts.map +0 -1
  50. 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.12.0",
18
- claude: "^0.31.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 @zed-industries/codex-acp@${ACP_ADAPTER_PACKAGE_RANGES.codex}`,
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: "@zed-industries/codex-acp",
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 packageRoot = resolvePackageRoot(spec.packageName);
113
- const manifest = JSON.parse(readFileSync(path.join(packageRoot, "package.json"), "utf8"));
114
- if (manifest.name !== spec.packageName) return;
115
- const relativeBinPath = resolvePackageBin(spec, manifest);
116
- if (!relativeBinPath) return;
117
- const binPath = path.resolve(packageRoot, relativeBinPath);
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: manifest.version,
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 (toolResultsIndex === -1 || toolResultsIndex + 2 !== path.length - 1) return false;
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
- if (!skipKeyRule && !SNAKE_CASE_KEY.test(key) && !isAllowedKey(path, key)) violations.push(`${joinPath(path)}.${key}`.replace(/^\./, ""));
316
- const childPath = [...path, key];
317
- if (shouldSkipDescend(childPath)) continue;
318
- collectViolations(child, childPath, violations);
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$3(value) {
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$3(raw);
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 (typeof value !== "number" || !Number.isFinite(value) || value < 0) return null;
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$3(raw);
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$3(raw);
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$3(record.size);
406
- return !!size && typeof size.width === "number" && Number.isFinite(size.width) && typeof size.height === "number" && Number.isFinite(size.height);
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$3(raw);
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$3(record.Mention);
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$3(raw);
421
- return !!record && typeof record.id === "string" && typeof record.name === "string" && typeof record.raw_input === "string" && hasOwn$1(record, "input") && typeof record.is_input_complete === "boolean" && (record.thought_signature === void 0 || record.thought_signature === null || typeof record.thought_signature === "string");
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$3(raw);
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$3(raw);
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$3(raw);
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$3(raw);
1171
+ const record = asRecord$4(raw);
448
1172
  if (!record || record.User === void 0) return false;
449
- const user = asRecord$3(record.User);
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$3(raw);
1177
+ const record = asRecord$4(raw);
454
1178
  if (!record || record.Agent === void 0) return false;
455
- const agent = asRecord$3(record.Agent);
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$3(agent.tool_results);
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 (!Array.isArray(record.messages) || !record.messages.every(isConversationMessage) || typeof record.updated_at !== "string") return;
466
- if (record.title !== void 0 && record.title !== null && typeof record.title !== "string") return;
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: record.title === void 0 || record.title === null || typeof record.title === "string" ? record.title : null,
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$3(raw);
1212
+ const record = asRecord$4(raw);
480
1213
  if (!record) return;
481
1214
  const state = {};
482
- if (record.reset_on_next_ensure === true) state.reset_on_next_ensure = true;
483
- if (typeof record.current_mode_id === "string") state.current_mode_id = record.current_mode_id;
484
- if (typeof record.desired_mode_id === "string") state.desired_mode_id = record.desired_mode_id;
485
- const desiredConfigOptions = asRecord$3(record.desired_config_options);
486
- if (desiredConfigOptions) {
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
- const sessionOptions = asRecord$3(record.session_options);
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$3(raw);
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$3(raw);
1318
+ const record = asRecord$4(raw);
557
1319
  if (!record) return null;
558
1320
  if (record.schema !== "acpx.session.v1") return null;
559
- const name = normalizeOptionalName(record.name);
560
- const pid = normalizeOptionalPid(record.pid);
561
- const closed = normalizeOptionalBoolean(record.closed, false);
562
- const closedAt = normalizeOptionalString(record.closed_at);
563
- const agentStartedAt = normalizeOptionalString(record.agent_started_at);
564
- const lastPromptAt = normalizeOptionalString(record.last_prompt_at);
565
- const lastAgentExitCode = normalizeOptionalExitCode(record.last_agent_exit_code);
566
- const lastAgentExitSignal = normalizeOptionalSignal(record.last_agent_exit_signal);
567
- const lastAgentExitAt = normalizeOptionalString(record.last_agent_exit_at);
568
- const lastAgentDisconnectReason = normalizeOptionalString(record.last_agent_disconnect_reason);
569
- if (typeof record.acpx_record_id !== "string" || typeof record.acp_session_id !== "string" || typeof record.agent_command !== "string" || typeof record.cwd !== "string" || typeof record.created_at !== "string" || typeof record.last_used_at !== "string" || typeof record.last_seq !== "number" || !Number.isInteger(record.last_seq) || record.last_seq < 0 || name === null || pid === null || closed === null || closedAt === null || agentStartedAt === null || lastPromptAt === null || typeof lastAgentExitCode === "symbol" || typeof lastAgentExitSignal === "symbol" || lastAgentExitAt === null || lastAgentDisconnectReason === null) return null;
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$3(record.agent_capabilities),
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$2(value) {
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$2(raw);
1408
+ const record = asRecord$3(raw);
616
1409
  if (!record) return;
617
- if (typeof record.file !== "string" || typeof record.acpxRecordId !== "string" || typeof record.acpSessionId !== "string" || typeof record.agentCommand !== "string" || typeof record.cwd !== "string" || typeof record.lastUsedAt !== "string" || typeof record.closed !== "boolean") return;
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$2(JSON.parse(payload));
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
- if (current === walkBoundary || current === walkRoot) return;
842
- const parent = path.dirname(current);
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
- let eligible = (await loadSessionIndexEntries()).filter((entry) => entry.closed);
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
- if (head.includes("read") || head.includes("cat")) return "read";
1092
- if (head.includes("search") || head.includes("find") || head.includes("grep")) return "search";
1093
- if (head.includes("write") || head.includes("edit") || head.includes("patch")) return "edit";
1094
- if (head.includes("delete") || head.includes("remove")) return "delete";
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 permissionModeSatisfies(actual, required) {
1111
- return PERMISSION_MODE_RANK[actual] >= PERMISSION_MODE_RANK[required];
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
- async function resolvePermissionRequest(params, mode, nonInteractivePolicy = "deny") {
1114
- const options = params.options ?? [];
1115
- if (options.length === 0) return cancelled();
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
- if (mode === "approve-all") {
1119
- if (allowOption) return selected(allowOption.optionId);
1120
- return selected(options[0].optionId);
1121
- }
1122
- if (mode === "deny-all") {
1123
- if (rejectOption) return selected(rejectOption.optionId);
1124
- return cancelled();
1125
- }
1126
- if (isAutoApprovedReadKind(inferToolKind(params)) && allowOption) return selected(allowOption.optionId);
1127
- if (!canPromptForPermission$1()) {
1128
- if (nonInteractivePolicy === "fail") throw new PermissionPromptUnavailableError();
1129
- if (rejectOption) return selected(rejectOption.optionId);
1130
- return cancelled();
1131
- }
1132
- const approved = await promptForToolPermission(params);
1133
- if (approved && allowOption) return selected(allowOption.optionId);
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 resolveWindowsCommand(command, env = process.env) {
1152
- const extensions = (readWindowsEnvValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
1153
- const candidates = path.extname(command).length > 0 ? [command] : extensions.map((extension) => `${command}${extension}`);
1154
- if (command.includes("/") || command.includes("\\") || path.isAbsolute(command)) return candidates.find((candidate) => fs.existsSync(candidate));
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 trimmedDirectory = directory.trim();
1159
- if (trimmedDirectory.length === 0) continue;
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
- if (escaping) {
1235
- current += ch;
1236
- escaping = false;
1237
- continue;
1238
- }
1239
- if (ch === "\\" && quote !== "'") {
1240
- escaping = true;
1241
- continue;
1242
- }
1243
- if (quote) {
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
- if (typeof options.model === "string" && options.model.trim().length > 0) claudeCodeOptions.model = options.model;
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
- const systemPrompt = options.systemPrompt;
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 = spawn(params.command, params.args ?? [], buildTerminalSpawnOptions(params.command, params.cwd ?? this.cwd, params.env));
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.resolveExit({
1721
- exitCode: exitCode ?? null,
1722
- signal: signal ?? null
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
- terminal.process.kill("SIGTERM");
3030
+ await this.signalProcess(terminal, "SIGTERM");
1878
3031
  } catch {
1879
3032
  return;
1880
3033
  }
1881
- if (await Promise.race([terminal.exitPromise.then(() => true), waitMs(this.killGraceMs).then(() => false)]) || !this.isRunning(terminal)) return;
3034
+ if (await this.waitForCleanupAfterSignal(terminal) && !terminal.killProcessGroup) return;
1882
3035
  try {
1883
- terminal.process.kill("SIGKILL");
3036
+ await this.signalProcess(terminal, "SIGKILL");
1884
3037
  } catch {
1885
3038
  return;
1886
3039
  }
1887
- await Promise.race([terminal.exitPromise.then(() => void 0), waitMs(this.killGraceMs)]);
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
- for (const line of lines) {
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 = Boolean(this.agent) && this.agent?.exitCode == null && this.agent?.signalCode == null && !this.agent?.killed;
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
- if (options.permissionMode || options.nonInteractivePermissions !== void 0) {
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 configuredCommand = splitCommandLine(this.options.agentCommand);
2065
- const resolvedBuiltInLaunch = resolveBuiltInAgentLaunch(this.options.agentCommand);
2066
- const spawnCommand = resolvedBuiltInLaunch?.command ?? configuredCommand.command;
2067
- let args = resolvedBuiltInLaunch?.args ?? configuredCommand.args;
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 connection = new ClientSideConnection(() => ({
2104
- sessionUpdate: async (params) => {
2105
- await this.handleSessionUpdate(params);
2106
- },
2107
- requestPermission: async (params) => {
2108
- return this.handlePermissionRequest(params);
2109
- },
2110
- readTextFile: async (params) => {
2111
- return this.handleReadTextFile(params);
2112
- },
2113
- writeTextFile: async (params) => {
2114
- return this.handleWriteTextFile(params);
2115
- },
2116
- createTerminal: async (params) => {
2117
- return this.handleCreateTerminal(params);
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
- }), this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output)));
2132
- connection.signal.addEventListener("abort", () => {
2133
- this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null);
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([(async () => {
2138
- const initializePromise = connection.initialize({
2139
- protocolVersion: PROTOCOL_VERSION,
2140
- clientCapabilities: {
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
- startupFailure.dispose();
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.suppressSessionUpdates;
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.suppressSessionUpdates = previousSuppression;
2260
- this.suppressReplaySessionUpdateMessages = previousReplaySuppression;
3761
+ this.restoreSessionUpdateSuppression(previousSuppression);
2261
3762
  }
2262
3763
  this.loadedSessionId = sessionId;
2263
- return {
2264
- agentSessionId: extractRuntimeSessionId(response?._meta),
2265
- configOptions: response?.configOptions ?? void 0,
2266
- models: response?.models ?? void 0
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: typeof prompt === "string" ? textPrompt(prompt) : 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
- const response = await promptPromise;
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
- const permissionFailure = this.consumePromptPermissionFailure(sessionId);
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
- if (!child.stdin.destroyed) try {
2399
- child.stdin.end();
2400
- } catch {}
3938
+ this.endAgentStdin(child);
2401
3939
  let exited = await waitForChildExit(child, stdinCloseGraceMs);
2402
- if (!exited && isChildProcessRunning(child)) {
2403
- try {
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
- try {
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 { outcome: { outcome: "cancelled" } };
2538
- let response;
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
- response = await resolvePermissionRequest(params, this.options.permissionMode, this.options.nonInteractivePermissions ?? "deny");
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
- if (error instanceof PermissionPromptUnavailableError) {
2543
- this.notePromptPermissionFailure(params.sessionId, error);
2544
- this.recordPermissionDecision("cancelled");
2545
- return { outcome: { outcome: "cancelled" } };
2546
- }
2547
- throw error;
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
- const decision = classifyPermissionDecision(params, response);
2550
- this.recordPermissionDecision(decision);
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
- if (content.type === "text") return content.text;
2733
- if (content.type === "resource_link") return content.title ?? content.name ?? content.uri;
2734
- if (content.type === "resource") {
2735
- if ("text" in content.resource && typeof content.resource.text === "string") return content.resource.text;
2736
- return content.resource.uri;
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 ?? existing?.tool_name ?? "tool_call",
2863
- is_error: patch.is_error ?? existing?.is_error ?? false,
2864
- content: patch.content ?? existing?.content ?? { Text: "" },
2865
- output: patch.output ?? existing?.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
- if (hasOwn(update, "rawInput")) {
2877
- const rawInput = deepClone(update.rawInput);
2878
- tool.input = rawInput ?? {};
2879
- tool.raw_input = toRawInput(rawInput);
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
- if (hasOwn(update, "rawOutput") || hasOwn(update, "status") || hasOwn(update, "title") || hasOwn(update, "kind")) {
2883
- const status = update.status;
2884
- const output = hasOwn(update, "rawOutput") ? deepClone(update.rawOutput) : void 0;
2885
- upsertToolResult(agent, update.toolCallId, {
2886
- tool_name: tool.name,
2887
- is_error: statusIndicatesError(status),
2888
- content: output === void 0 ? void 0 : toToolResultContent(output),
2889
- output
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.input_tokens === void 0 && normalized.output_tokens === void 0 && normalized.cache_creation_input_tokens === void 0 && normalized.cache_read_input_tokens === void 0) return;
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
- model: state.session_options.model,
2965
- allowed_tools: state.session_options.allowed_tools ? [...state.session_options.allowed_tools] : void 0,
2966
- max_turns: state.session_options.max_turns,
2967
- ...state.session_options.system_prompt !== void 0 ? { system_prompt: typeof state.session_options.system_prompt === "string" ? state.session_options.system_prompt : { append: state.session_options.system_prompt.append } } : {}
2968
- } : void 0
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
- switch (update.sessionUpdate) {
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/runtime/engine/lifecycle.ts
3187
- function applyLifecycleSnapshotToRecord(record, snapshot) {
3188
- if (!snapshot) return;
3189
- record.pid = snapshot.pid;
3190
- record.agentStartedAt = snapshot.startedAt;
3191
- if (snapshot.lastExit) {
3192
- record.lastAgentExitCode = snapshot.lastExit.exitCode;
3193
- record.lastAgentExitSignal = snapshot.lastExit.signal;
3194
- record.lastAgentExitAt = snapshot.lastExit.exitedAt;
3195
- record.lastAgentDisconnectReason = snapshot.lastExit.reason;
3196
- return;
3197
- }
3198
- record.lastAgentExitCode = void 0;
3199
- record.lastAgentExitSignal = void 0;
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 instanceof TimeoutError || error instanceof InterruptedError) return false;
3232
- if (isAcpResourceNotFoundError(error)) return true;
4998
+ if (isHardReconnectFailure(error)) return false;
3233
4999
  const acp = extractAcpError(error);
3234
- if (acp && SESSION_LOAD_UNSUPPORTED_CODES.has(acp.code)) return true;
3235
- if (!sessionHasAgentMessages(record)) {
3236
- if (isAcpQueryClosedBeforeResponseError(error)) return true;
3237
- if (acp?.code === -32603) return true;
3238
- }
3239
- return false;
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
- const shouldReconnect = Boolean(record.pid) && !storedProcessAlive;
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
- if (reusingLoadedSession) resumed = true;
3323
- else if (client.supportsLoadSession()) try {
3324
- const loadResult = await withTimeout(client.loadSessionWithOptions(record.acpSessionId, record.cwd, { suppressReplayUpdates: true }), options.timeoutMs);
3325
- reconcileAgentSessionId(record, loadResult.agentSessionId);
3326
- applyConfigOptionsToRecord(record, loadResult);
3327
- sessionModels = loadResult.models;
3328
- resumed = true;
3329
- } catch (error) {
3330
- loadError = formatErrorMessage(error);
3331
- if (sameSessionOnly) throw makeSessionResumeRequiredError({
3332
- record,
3333
- reason: loadError,
3334
- cause: error
3335
- });
3336
- if (!shouldFallbackToNewSession(error, record)) throw error;
3337
- const createdSession = await withTimeout(client.createSession(record.cwd), options.timeoutMs);
3338
- sessionId = createdSession.sessionId;
3339
- createdFreshSession = true;
3340
- pendingAgentSessionId = createdSession.agentSessionId;
3341
- applyConfigOptionsToRecord(record, createdSession);
3342
- sessionModels = createdSession.models;
3343
- }
3344
- else {
3345
- if (sameSessionOnly) throw makeSessionResumeRequiredError({
3346
- record,
3347
- reason: "agent does not support session/load"
3348
- });
3349
- const createdSession = await withTimeout(client.createSession(record.cwd), options.timeoutMs);
3350
- sessionId = createdSession.sessionId;
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
- //#endregion
3407
- //#region src/runtime/engine/session-options.ts
3408
- function mergeSessionOptions(preferred, fallback) {
3409
- const merged = { ...fallback };
3410
- if (preferred?.model !== void 0) merged.model = preferred.model;
3411
- if (preferred?.allowedTools !== void 0) merged.allowedTools = preferred.allowedTools;
3412
- if (preferred?.maxTurns !== void 0) merged.maxTurns = preferred.maxTurns;
3413
- if (preferred?.systemPrompt !== void 0) merged.systemPrompt = preferred.systemPrompt;
3414
- return Object.keys(merged).length > 0 ? merged : void 0;
5126
+ function applyReconnectedModelState(record, sessionModels, createdFreshSession, desiredModelId) {
5127
+ syncAdvertisedModelState(record, sessionModels);
5128
+ if (createdFreshSession && desiredModelId && sessionModels) setCurrentModelId(record, desiredModelId);
3415
5129
  }
3416
- function sessionOptionsFromRecord(record) {
3417
- const stored = record.acpx?.session_options;
3418
- if (!stored) return;
3419
- const sessionOptions = {};
3420
- if (typeof stored.model === "string" && stored.model.trim().length > 0) sessionOptions.model = stored.model;
3421
- if (Array.isArray(stored.allowed_tools)) sessionOptions.allowedTools = [...stored.allowed_tools];
3422
- if (typeof stored.max_turns === "number") sessionOptions.maxTurns = stored.max_turns;
3423
- const storedSystemPrompt = stored.system_prompt;
3424
- if (typeof storedSystemPrompt === "string" && storedSystemPrompt.length > 0) sessionOptions.systemPrompt = storedSystemPrompt;
3425
- else if (storedSystemPrompt && typeof storedSystemPrompt === "object" && typeof storedSystemPrompt.append === "string" && storedSystemPrompt.append.length > 0) sessionOptions.systemPrompt = { append: storedSystemPrompt.append };
3426
- return Object.keys(sessionOptions).length > 0 ? sessionOptions : void 0;
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
- export { resolveAgentCommand as $, isoNow$2 as A, sessionBaseDir$1 as B, AcpClient as C, findGitRepositoryRoot as D, absolutePath as E, resolveSessionRecord as F, serializeSessionRecordForDisk as G, sessionEventLockPath as H, writeSessionRecord as I, withInterrupt as J, InterruptedError as K, parseSessionRecord as L, listSessionsForAgent as M, normalizeName as N, findSession as O, pruneSessions as P, normalizeAgentName$1 as Q, DEFAULT_EVENT_SEGMENT_MAX_BYTES as R, trimConversationForRuntime as S, DEFAULT_HISTORY_LIMIT as T, sessionEventSegmentPath as U, sessionEventActivePath as V, assertPersistedKeyPolicy as W, DEFAULT_AGENT_NAME as X, withTimeout as Y, listBuiltInAgents as Z, cloneSessionConversation as _, connectAndLoadSession as a, recordPromptSubmission as b, reconcileAgentSessionId as c, setDesiredConfigOption as d, setDesiredModeId as f, cloneSessionAcpxState as g, applyConfigOptionsToRecord as h, sessionOptionsFromRecord as i, listSessions as j, findSessionByDirectoryWalk as k, assertRequestedModelSupported as l, syncAdvertisedModelState as m, withConnectedSession as n, applyConversation as o, setDesiredModelId as p, TimeoutError as q, mergeSessionOptions as r, applyLifecycleSnapshotToRecord as s, runPromptTurn as t, setCurrentModelId as u, createSessionConversation as v, permissionModeSatisfies as w, recordSessionUpdate as x, recordClientOperation as y, defaultSessionEventLog as z };
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=prompt-turn-CVPMWdj1.js.map
5445
+ //# sourceMappingURL=live-checkpoint-D5d-K9s1.js.map