acpx 0.19.1 → 0.19.3

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 (41) hide show
  1. package/dist/{agent-registry-DWlx5BQ4.js → agent-registry-DWnqUICY.js} +20 -17
  2. package/dist/agent-registry-DWnqUICY.js.map +1 -0
  3. package/dist/agent-registry.js +1 -1
  4. package/dist/{cli-BerngYEz.js → cli-C1uofM8S.js} +5 -5
  5. package/dist/{cli-BerngYEz.js.map → cli-C1uofM8S.js.map} +1 -1
  6. package/dist/cli.d.ts +1 -1
  7. package/dist/cli.js +56 -40
  8. package/dist/cli.js.map +1 -1
  9. package/dist/{client-BchvQRpz.d.ts → client-CdG6nVDG.d.ts} +4 -3
  10. package/dist/client-CdG6nVDG.d.ts.map +1 -0
  11. package/dist/{flows-DimX0it5.js → flows-hz_Pfsp-.js} +9 -5
  12. package/dist/flows-hz_Pfsp-.js.map +1 -0
  13. package/dist/flows.d.ts +2 -2
  14. package/dist/flows.js +1 -1
  15. package/dist/{invocation-options-vobVApfO.js → invocation-options-DbZzMvKM.js} +5 -5
  16. package/dist/{invocation-options-vobVApfO.js.map → invocation-options-DbZzMvKM.js.map} +1 -1
  17. package/dist/{ipc-DAApJ5mi.js → ipc-BW06Pbb8.js} +1246 -369
  18. package/dist/ipc-BW06Pbb8.js.map +1 -0
  19. package/dist/{output-Cnuji8Bo.js → output-BjeeG-cj.js} +3 -3
  20. package/dist/{output-Cnuji8Bo.js.map → output-BjeeG-cj.js.map} +1 -1
  21. package/dist/{queue-owner-runtime-C4OOosbo.js → queue-owner-runtime-Dhr4gxNE.js} +542 -929
  22. package/dist/queue-owner-runtime-Dhr4gxNE.js.map +1 -0
  23. package/dist/runtime.d.ts +7 -2
  24. package/dist/runtime.d.ts.map +1 -1
  25. package/dist/runtime.js +24 -17
  26. package/dist/runtime.js.map +1 -1
  27. package/dist/{session-options-Dp6mzNSy.d.ts → session-options-X7LhX9BF.d.ts} +2 -1
  28. package/dist/{session-options-Dp6mzNSy.d.ts.map → session-options-X7LhX9BF.d.ts.map} +1 -1
  29. package/dist/{session-watch-Bj9LoYUq.js → session-watch-CgfkUKLA.js} +4 -4
  30. package/dist/{session-watch-Bj9LoYUq.js.map → session-watch-CgfkUKLA.js.map} +1 -1
  31. package/dist/watch-BGdqBiNX.js +85 -0
  32. package/dist/watch-BGdqBiNX.js.map +1 -0
  33. package/package.json +11 -11
  34. package/skills/acpx/SKILL.md +7 -2
  35. package/dist/agent-registry-DWlx5BQ4.js.map +0 -1
  36. package/dist/client-BchvQRpz.d.ts.map +0 -1
  37. package/dist/flows-DimX0it5.js.map +0 -1
  38. package/dist/ipc-DAApJ5mi.js.map +0 -1
  39. package/dist/queue-owner-runtime-C4OOosbo.js.map +0 -1
  40. package/dist/watch-Cz6jbrRs.js +0 -32
  41. package/dist/watch-Cz6jbrRs.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { $ as measurePerf, A as findGitRepositoryRoot, B as writeSessionRecord, Bt as PermissionDeniedError, C as getUnsupportedPromptContentMessage, D as textPrompt, Dt as OUTPUT_ERROR_ORIGINS, E as promptToDisplayText, Et as OUTPUT_ERROR_CODES, Ft as AgentStartupError, G as normalizeAgentSessionId, Gt as SessionModelReplayError, Ht as QueueConnectionError, I as normalizeName, It as AuthPolicyError, Jt as UnsupportedPromptContentError, K as SESSION_JOURNAL_SCHEMA, Lt as ClaudeAcpSessionCreateTimeoutError, M as findSessionByDirectoryWalk, Mt as AcpxOperationalError, N as isoNow$1, Nt as AgentDisconnectedError, Pt as AgentSpawnError, Q as incrementPerfCounter, Rt as CopilotAcpUnsupportedError, St as isAcpResourceNotFoundError, Ut as SessionConfigOptionReplayError, Vt as PermissionPromptUnavailableError, W as extractAgentSessionId, Wt as SessionModeReplayError, X as formatPerfMetric, Z as getPerfMetricsSnapshot, _ as waitMs$1, a as trySetModelOnRunningOwner, at as isAcpJsonRpcMessage, b as lockOwnerPid, bt as extractAcpError, c as queueRequestExceedsLimit, et as recordPerfDuration, f as refreshQueueOwnerLease, ft as defaultSessionEventLog, g as tryAcquireQueueOwnerLease, gt as sessionEventSegmentPath, h as terminateQueueOwnerForSession, ht as sessionEventLockPath, i as trySetModeOnRunningOwner, k as absolutePath, l as parseQueueRequest, m as terminateProcess, mt as sessionEventActivePath, n as tryCloseSessionOnRunningOwner, nt as setPerfGauge, o as trySubmitToRunningOwner, ot as isAcpMessageObject, p as releaseQueueOwnerLease, pt as sessionBaseDir, q as SessionJournalReader, qt as SessionResumeRequiredError, r as trySetConfigOptionOnRunningOwner, rt as startPerfTimer, s as queueRequestByteLimit, st as isSessionUpdateNotification, t as tryCancelOnRunningOwner, tt as resetPerfMetrics, v as QueueLeaseGuardSettlementError, wt as EXIT_CODES, x as ProcessDescendants, xt as formatUnknownErrorMessage, y as createLockOwner, yt as isProcessAlive, z as resolveSessionRecord, zt as GeminiAcpStartupTimeoutError } from "./ipc-DAApJ5mi.js";
2
- import { A as requireAgentStdio, C as PROCESS_HELPER_TIMEOUT_MS, D as isoNow$2, E as isChildProcessRunning, F as waitForChildExit, I as waitForSpawn, M as resolveAgentSessionCwd, N as runTimedExecFile, P as splitCommandLine, T as basenameToken, _ as buildSpawnCommandOptions, b as readWindowsEnvValue, f as resolveBuiltInAgentLaunch, g as buildAgentSpawnCommand, j as resolveAgentCommandParts, v as buildTerminalShellSpawnCommand, w as asAbsoluteCwd, x as resolveWindowsExecutablePath, y as buildTerminalSpawnCommand } from "./agent-registry-DWlx5BQ4.js";
1
+ import { $ as resetPerfMetrics, Bt as GeminiAcpStartupTimeoutError, C as textPrompt, Ct as normalizeOutputError, F as normalizeName, Ft as AgentSpawnError, G as SESSION_JOURNAL_SCHEMA, Gt as SessionModeReplayError, Ht as PermissionPromptUnavailableError, It as AgentStartupError, Jt as SessionResumeRequiredError, K as SessionJournalReader, Kt as SessionModelReplayError, L as readSessionRecord, Lt as AuthPolicyError, M as isoNow$1, Nt as AcpxOperationalError, O as absolutePath, Pt as AgentDisconnectedError, Q as measurePerf, R as resolveSessionRecord, Rt as ClaudeAcpSessionCreateTimeoutError, S as promptToDisplayText, St as isRetryablePromptError, T as lockOwnerPid, Tt as isAcpResourceNotFoundError, U as extractAgentSessionId, Ut as QueueConnectionError, Vt as PermissionDeniedError, W as normalizeAgentSessionId, Wt as SessionConfigOptionReplayError, X as getPerfMetricsSnapshot, Y as formatPerfMetric, Yt as UnsupportedPromptContentError, Z as incrementPerfCounter, _ as ProcessDescendants, a as trySetModelOnRunningOwner, at as isSessionUpdateNotification, bt as formatErrorMessage, d as releaseQueueOwnerLease, dt as sessionBaseDir, et as setPerfGauge, f as terminateProcess, ft as sessionEventActivePath, g as QueueLeaseGuardSettlementError, h as waitMs$1, i as trySetModeOnRunningOwner, it as isAcpMessageObject, j as findSessionByDirectoryWalk, k as findGitRepositoryRoot, m as tryAcquireQueueOwnerLease, mt as sessionEventSegmentPath, n as tryCloseSessionOnRunningOwner, o as trySubmitToRunningOwner, p as terminateQueueOwnerForSession, pt as sessionEventLockPath, r as trySetConfigOptionOnRunningOwner, rt as isAcpJsonRpcMessage, s as SessionQueueOwner, t as tryCancelOnRunningOwner, tt as startPerfTimer, u as refreshQueueOwnerLease, ut as defaultSessionEventLog, vt as isProcessAlive, w as createLockOwner, wt as extractAcpError, xt as isAcpQueryClosedBeforeResponseError, y as getUnsupportedPromptContentMessage, z as writeSessionRecord, zt as CopilotAcpUnsupportedError } from "./ipc-BW06Pbb8.js";
2
+ import { A as requireAgentStdio, C as PROCESS_HELPER_TIMEOUT_MS, D as isoNow$2, E as isChildProcessRunning, F as waitForChildExit, I as waitForSpawn, M as resolveAgentSessionCwd, N as runTimedExecFile, P as splitCommandLine, T as basenameToken, _ as buildSpawnCommandOptions, b as readWindowsEnvValue, f as resolveBuiltInAgentLaunch, g as buildAgentSpawnCommand, j as resolveAgentCommandParts, v as buildTerminalShellSpawnCommand, w as asAbsoluteCwd, x as resolveWindowsExecutablePath, y as buildTerminalSpawnCommand } from "./agent-registry-DWnqUICY.js";
3
3
  import fs, { readFileSync, realpathSync } from "node:fs";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import path from "node:path";
@@ -8,145 +8,14 @@ import fs$1 from "node:fs/promises";
8
8
  import { appendRegularFile, appendRegularFileSync, withTempFile } from "@openclaw/fs-safe/advanced";
9
9
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
10
10
  import { createHash, randomUUID } from "node:crypto";
11
+ import { isHardlinkFallbackError } from "@openclaw/fs-safe/durability";
12
+ import { acquireFileLock } from "@openclaw/fs-safe/file-lock";
11
13
  import { Readable, Writable } from "node:stream";
12
14
  import { PROTOCOL_VERSION, RequestError, client, methods } from "@agentclientprotocol/sdk";
13
15
  import { FsSafeError } from "@openclaw/fs-safe/errors";
14
16
  import { isPathInside } from "@openclaw/fs-safe/path";
15
17
  import { root } from "@openclaw/fs-safe/root";
16
18
  import readline from "node:readline/promises";
17
- import { isHardlinkFallbackError } from "@openclaw/fs-safe/durability";
18
- import { acquireFileLock } from "@openclaw/fs-safe/file-lock";
19
- import net from "node:net";
20
- //#region src/acp/error-normalization.ts
21
- const AUTH_REQUIRED_ACP_CODES = /* @__PURE__ */ new Set([-32e3]);
22
- const QUERY_CLOSED_BEFORE_RESPONSE_DETAIL = "query closed before response received";
23
- function asRecord$3(value) {
24
- if (!value || typeof value !== "object" || Array.isArray(value)) return;
25
- return value;
26
- }
27
- function isAuthRequiredMessage(value) {
28
- if (!value) return false;
29
- const normalized = value.toLowerCase();
30
- return [
31
- "auth required",
32
- "authentication required",
33
- "authorization required",
34
- "credential required",
35
- "credentials required",
36
- "token required",
37
- "login required"
38
- ].some((needle) => normalized.includes(needle));
39
- }
40
- function isAcpAuthRequiredPayload(acp) {
41
- if (!acp) return false;
42
- if (!AUTH_REQUIRED_ACP_CODES.has(acp.code)) return false;
43
- if (isAuthRequiredMessage(acp.message)) return true;
44
- const data = asRecord$3(acp.data);
45
- if (!data) return false;
46
- return hasAuthRequiredData(data);
47
- }
48
- function hasAuthRequiredData(data) {
49
- return data.authRequired === true || hasNonEmptyString(data.methodId) || hasNonEmptyArray(data.methods);
50
- }
51
- function hasNonEmptyString(value) {
52
- return typeof value === "string" && value.trim().length > 0;
53
- }
54
- function hasNonEmptyArray(value) {
55
- return Array.isArray(value) && value.length > 0;
56
- }
57
- function isOutputErrorCode(value) {
58
- return typeof value === "string" && OUTPUT_ERROR_CODES.includes(value);
59
- }
60
- function isOutputErrorOrigin(value) {
61
- return typeof value === "string" && OUTPUT_ERROR_ORIGINS.includes(value);
62
- }
63
- function readOutputErrorMeta(error) {
64
- const record = asRecord$3(error);
65
- if (!record) return {};
66
- return {
67
- outputCode: isOutputErrorCode(record.outputCode) ? record.outputCode : void 0,
68
- detailCode: typeof record.detailCode === "string" && record.detailCode.trim().length > 0 ? record.detailCode : void 0,
69
- origin: isOutputErrorOrigin(record.origin) ? record.origin : void 0,
70
- retryable: typeof record.retryable === "boolean" ? record.retryable : void 0,
71
- acp: extractAcpError(record.acp)
72
- };
73
- }
74
- function isTimeoutLike(error) {
75
- return error instanceof Error && error.name === "TimeoutError";
76
- }
77
- function isNoSessionLike(error) {
78
- return error instanceof Error && error.name === "NoSessionError";
79
- }
80
- function isUsageLike(error) {
81
- if (!(error instanceof Error)) return false;
82
- return error.name === "CommanderError" || error.name === "InvalidArgumentError" || asRecord$3(error)?.code === "commander.invalidArgument";
83
- }
84
- function formatErrorMessage(error) {
85
- return formatUnknownErrorMessage(error);
86
- }
87
- function isAcpQueryClosedBeforeResponseError(error) {
88
- const acp = extractAcpError(error);
89
- if (!acp || acp.code !== -32603) return false;
90
- const details = asRecord$3(acp.data)?.details;
91
- if (typeof details !== "string") return false;
92
- return details.toLowerCase().includes(QUERY_CLOSED_BEFORE_RESPONSE_DETAIL);
93
- }
94
- function mapErrorCode(error) {
95
- if (error instanceof PermissionPromptUnavailableError) return "PERMISSION_PROMPT_UNAVAILABLE";
96
- if (error instanceof PermissionDeniedError) return "PERMISSION_DENIED";
97
- if (isTimeoutLike(error)) return "TIMEOUT";
98
- if (isNoSessionLike(error) || isAcpResourceNotFoundError(error)) return "NO_SESSION";
99
- if (isUsageLike(error)) return "USAGE";
100
- }
101
- function normalizeOutputError(error, options = {}) {
102
- const meta = readOutputErrorMeta(error);
103
- const code = resolveOutputErrorCode(error, options, meta);
104
- const acp = options.acp ?? meta.acp ?? extractAcpError(error);
105
- return {
106
- code,
107
- message: formatErrorMessage(error),
108
- detailCode: resolveDetailCode(error, acp, options, meta),
109
- origin: meta.origin ?? options.origin,
110
- retryable: meta.retryable ?? options.retryable,
111
- acp
112
- };
113
- }
114
- function resolveOutputErrorCode(error, options, meta) {
115
- const code = meta.outputCode ?? mapErrorCode(error) ?? options.defaultCode ?? "RUNTIME";
116
- if (code === "RUNTIME" && isAcpResourceNotFoundError(error)) return "NO_SESSION";
117
- return code;
118
- }
119
- function resolveDetailCode(error, acp, options, meta) {
120
- return meta.detailCode ?? options.detailCode ?? (error instanceof AuthPolicyError || isAcpAuthRequiredPayload(acp) ? "AUTH_REQUIRED" : void 0);
121
- }
122
- /**
123
- * Returns true when an error from `client.prompt()` looks transient and
124
- * can reasonably be retried (e.g. model-API 400/500, network hiccups that
125
- * surface as ACP internal errors).
126
- *
127
- * Errors that are definitively non-recoverable (auth, missing session,
128
- * invalid params, timeout, permission) return false.
129
- */
130
- function isRetryablePromptError(error) {
131
- if (isNonRetryablePromptError(error)) return false;
132
- const acp = extractAcpError(error);
133
- if (!acp) return false;
134
- return acp.code === -32603 || acp.code === -32700;
135
- }
136
- function isNonRetryablePromptError(error) {
137
- return error instanceof PermissionDeniedError || error instanceof PermissionPromptUnavailableError || isTimeoutLike(error) || isNoSessionLike(error) || isUsageLike(error);
138
- }
139
- function exitCodeForOutputErrorCode(code) {
140
- switch (code) {
141
- case "USAGE": return EXIT_CODES.USAGE;
142
- case "TIMEOUT": return EXIT_CODES.TIMEOUT;
143
- case "NO_SESSION": return EXIT_CODES.NO_SESSION;
144
- case "PERMISSION_DENIED":
145
- case "PERMISSION_PROMPT_UNAVAILABLE": return EXIT_CODES.PERMISSION_DENIED;
146
- default: return EXIT_CODES.ERROR;
147
- }
148
- }
149
- //#endregion
150
19
  //#region src/async-control.ts
151
20
  var TimeoutError = class extends Error {
152
21
  constructor(timeoutMs) {
@@ -477,6 +346,246 @@ async function listSessionEvents(sessionId, maxSegments) {
477
346
  }).readAcpMessages();
478
347
  }
479
348
  //#endregion
349
+ //#region src/session/turn-ownership.ts
350
+ const LOCK_RETRY_MS = 15;
351
+ const INCOMPLETE_RESERVATION_GRACE_MS = 15e3;
352
+ let lastCreatedAt = 0;
353
+ const localTurns = /* @__PURE__ */ new Map();
354
+ async function readLock(filePath) {
355
+ try {
356
+ const stat = await fs$1.lstat(filePath, { bigint: true });
357
+ if (!stat.isFile()) throw new Error(`Session turn lock is not a regular file: ${filePath}`);
358
+ return {
359
+ stat,
360
+ payload: await fs$1.readFile(filePath, "utf8")
361
+ };
362
+ } catch (error) {
363
+ if (error.code === "ENOENT") return;
364
+ throw error;
365
+ }
366
+ }
367
+ function parseLock(payload) {
368
+ try {
369
+ return JSON.parse(payload);
370
+ } catch {
371
+ return;
372
+ }
373
+ }
374
+ async function tryAcquireGuard(filePath, owner, signal) {
375
+ try {
376
+ return await acquireFileLock(filePath, {
377
+ managerKey: "acpx.session-turn",
378
+ lockPath: `${filePath}.guard`,
379
+ staleMs: Infinity,
380
+ timeoutMs: LOCK_RETRY_MS,
381
+ retry: {
382
+ retries: 8,
383
+ minTimeout: 1,
384
+ maxTimeout: 2,
385
+ factor: 1,
386
+ randomize: false
387
+ },
388
+ staleRecovery: "remove-if-unchanged",
389
+ payload: () => owner.payload,
390
+ shouldReclaim: ({ payload }) => owner.hasExited(payload, false, signal),
391
+ shouldRemoveStaleLock: ({ payload }) => owner.hasExited(payload, true, signal)
392
+ });
393
+ } catch (error) {
394
+ if (error.code === "file_lock_timeout") return;
395
+ throw error;
396
+ }
397
+ }
398
+ async function removeObservedLock(filePath, observed) {
399
+ const current = await readLock(filePath);
400
+ if (!current) return true;
401
+ if (current.payload !== observed.payload || current.stat.mtimeNs !== observed.stat.mtimeNs || current.stat.size !== observed.stat.size) return false;
402
+ await fs$1.unlink(filePath).catch((error) => {
403
+ if (error.code !== "ENOENT") throw error;
404
+ });
405
+ return true;
406
+ }
407
+ async function recoverAbandonedLock(filePath, owner, signal) {
408
+ const observed = await readLock(filePath);
409
+ if (!observed) return true;
410
+ const payload = parseLock(observed.payload);
411
+ const pid = lockOwnerPid(payload);
412
+ if (pid && (!await owner.hasExited(payload, false, signal) || !await owner.hasExited(payload, true, signal))) return false;
413
+ if (!pid && Date.now() - Number(observed.stat.mtimeMs) <= INCOMPLETE_RESERVATION_GRACE_MS) return false;
414
+ if (await removeObservedLock(filePath, observed)) {
415
+ incrementPerfCounter("session.events.stale_lock_recovered");
416
+ return true;
417
+ }
418
+ return false;
419
+ }
420
+ async function publishLock(filePath, payload, signal) {
421
+ await withTempFile({
422
+ rootDir: path.dirname(filePath),
423
+ prefix: "session-turn",
424
+ fileName: "lock"
425
+ }, async (temporaryPath) => {
426
+ await fs$1.writeFile(temporaryPath, payload, {
427
+ encoding: "utf8",
428
+ flag: "wx",
429
+ mode: 384
430
+ });
431
+ signal?.throwIfAborted();
432
+ try {
433
+ await fs$1.link(temporaryPath, filePath);
434
+ } catch (error) {
435
+ if (!isHardlinkFallbackError(error)) throw error;
436
+ await fs$1.writeFile(filePath, payload, {
437
+ encoding: "utf8",
438
+ flag: "wx",
439
+ mode: 384
440
+ });
441
+ }
442
+ });
443
+ }
444
+ async function removePublishedLock(filePath, payload) {
445
+ const current = await readLock(filePath);
446
+ if (current?.payload === payload) await removeObservedLock(filePath, current);
447
+ }
448
+ async function rejectAcquisition(filePath, receipt, error) {
449
+ const failures = [error];
450
+ await disposeTurn(filePath, receipt).catch((cleanupError) => {
451
+ failures.push(...cleanupError instanceof AggregateError ? cleanupError.errors : [cleanupError]);
452
+ });
453
+ if (failures.length > 1) throw new AggregateError(failures, "Session turn acquisition and cleanup failed");
454
+ throw error;
455
+ }
456
+ async function releaseTurnGuard(receipt) {
457
+ await receipt.guard?.release();
458
+ receipt.guard = void 0;
459
+ }
460
+ async function removeTurnMarker(filePath, receipt) {
461
+ if (!receipt.removeMarker) return true;
462
+ if (receipt.guard && !await receipt.guard.verifyStillHeld()) await releaseTurnGuard(receipt);
463
+ receipt.guard ??= await tryAcquireGuard(filePath, receipt.owner);
464
+ if (!receipt.guard) return false;
465
+ await receipt.removeMarker();
466
+ receipt.removeMarker = void 0;
467
+ return true;
468
+ }
469
+ async function settleTurn(filePath, receipt) {
470
+ if (receipt.settled) return true;
471
+ const pending = receipt.pending ??= (async () => {
472
+ receipt.failed = false;
473
+ const failures = [];
474
+ try {
475
+ if (!await removeTurnMarker(filePath, receipt)) return false;
476
+ } catch (error) {
477
+ failures.push(error);
478
+ }
479
+ if (failures.length === 0 || !receipt.admitted) try {
480
+ await releaseTurnGuard(receipt);
481
+ } catch (error) {
482
+ failures.push(error);
483
+ }
484
+ if (failures.length > 1) throw new AggregateError(failures, "Session turn cleanup failed");
485
+ if (failures.length === 1) throw failures[0];
486
+ receipt.settled = true;
487
+ localTurns.delete(filePath);
488
+ return true;
489
+ })();
490
+ try {
491
+ return await pending;
492
+ } finally {
493
+ if (receipt.pending === pending) {
494
+ receipt.pending = void 0;
495
+ receipt.failed = !receipt.settled;
496
+ }
497
+ }
498
+ }
499
+ async function disposeTurn(filePath, receipt) {
500
+ if (!await settleTurn(filePath, receipt)) throw new Error(`Session turn cleanup is waiting for its guard: ${filePath}`);
501
+ }
502
+ async function tryPublishLock(filePath, payload, owner, signal) {
503
+ for (;;) try {
504
+ await publishLock(filePath, payload, signal);
505
+ return true;
506
+ } catch (error) {
507
+ if (error.code !== "EEXIST") throw error;
508
+ if (!await recoverAbandonedLock(filePath, owner, signal)) return false;
509
+ }
510
+ }
511
+ async function tryAcquireTurnCleanup(filePath, payload, owner, signal) {
512
+ const previous = localTurns.get(filePath);
513
+ if (previous) {
514
+ if (previous.failed) await settleTurn(filePath, previous);
515
+ return;
516
+ }
517
+ const receipt = {
518
+ owner,
519
+ removeMarker: async () => await removePublishedLock(filePath, payload),
520
+ admitted: false,
521
+ failed: false,
522
+ settled: false
523
+ };
524
+ localTurns.set(filePath, receipt);
525
+ try {
526
+ receipt.guard = await tryAcquireGuard(filePath, owner, signal);
527
+ } catch (error) {
528
+ localTurns.delete(filePath);
529
+ throw error;
530
+ }
531
+ if (!receipt.guard) {
532
+ localTurns.delete(filePath);
533
+ return;
534
+ }
535
+ return receipt;
536
+ }
537
+ async function tryAcquireTurn(filePath, payload, owner, signal) {
538
+ const receipt = await tryAcquireTurnCleanup(filePath, payload, owner, signal);
539
+ if (!receipt) return;
540
+ try {
541
+ signal?.throwIfAborted();
542
+ if (!await tryPublishLock(filePath, payload, owner, signal)) {
543
+ await disposeTurn(filePath, receipt);
544
+ return;
545
+ }
546
+ const observed = await readLock(filePath);
547
+ if (observed?.payload !== payload) throw new Error(`Session turn ownership changed before admission: ${filePath}`);
548
+ signal?.throwIfAborted();
549
+ receipt.admitted = true;
550
+ receipt.removeMarker = async () => await removeObservedLock(filePath, observed);
551
+ return { [Symbol.asyncDispose]: async () => await disposeTurn(filePath, receipt) };
552
+ } catch (error) {
553
+ return await rejectAcquisition(filePath, receipt, error);
554
+ }
555
+ }
556
+ async function acquireSessionTurn(recordId, signal) {
557
+ return await acquireSessionOwnership(sessionEventLockPath(recordId), signal);
558
+ }
559
+ async function acquireSessionScope(scope, signal) {
560
+ return await acquireSessionTurn(`ensure:${createHash("sha256").update(JSON.stringify([
561
+ scope.agentCommand,
562
+ scope.cwd,
563
+ scope.name
564
+ ])).digest("hex")}`, signal);
565
+ }
566
+ async function acquireSessionImport(signal) {
567
+ return await acquireSessionOwnership(path.join(sessionBaseDir(), ".import-admission.lock"), signal);
568
+ }
569
+ async function acquireSessionOwnership(requestedPath, signal) {
570
+ await fs$1.mkdir(path.dirname(requestedPath), {
571
+ recursive: true,
572
+ mode: 448
573
+ });
574
+ const filePath = path.join(await fs$1.realpath(path.dirname(requestedPath)), path.basename(requestedPath));
575
+ const owner = await createLockOwner({ signal });
576
+ lastCreatedAt = Math.max(Date.now(), lastCreatedAt + 1);
577
+ const payload = `${JSON.stringify({
578
+ ...owner.payload,
579
+ created_at: new Date(lastCreatedAt).toISOString()
580
+ }, null, 2)}\n`;
581
+ for (;;) {
582
+ signal?.throwIfAborted();
583
+ const turn = await tryAcquireTurn(filePath, payload, owner, signal);
584
+ if (turn) return turn;
585
+ await setTimeout$1(LOCK_RETRY_MS, void 0, { signal });
586
+ }
587
+ }
588
+ //#endregion
480
589
  //#region src/session/execution/contracts.ts
481
590
  const DEFAULT_QUEUE_OWNER_TTL_MS = 3e5;
482
591
  function normalizeQueueOwnerTtlMs(ttlMs) {
@@ -1147,10 +1256,10 @@ function assignClaudeCodeSystemPrompt(target, systemPrompt) {
1147
1256
  function isAppendSystemPrompt(value) {
1148
1257
  return !!value && typeof value === "object" && typeof value.append === "string" && value.append.length > 0;
1149
1258
  }
1150
- function resolveClaudeCodeExecutable(platform = process.platform, env = process.env) {
1259
+ function resolveClaudeCodeExecutable(platform = process.platform, env = process.env, cwd) {
1151
1260
  if (platform !== "win32") return;
1152
1261
  if (readWindowsEnvValue(env, "CLAUDE_CODE_EXECUTABLE")) return;
1153
- return resolveWindowsExecutablePath("claude", env);
1262
+ return resolveWindowsExecutablePath("claude", env, cwd);
1154
1263
  }
1155
1264
  //#endregion
1156
1265
  //#region src/acp/auth-env.ts
@@ -1197,7 +1306,7 @@ function promotePrefixedAuthEnvironment(env) {
1197
1306
  if (!normalized) continue;
1198
1307
  protectEnvKey(protectedKeys, key);
1199
1308
  protectEnvKey(protectedKeys, normalized);
1200
- if (env[normalized] == null) env[normalized] = value;
1309
+ assignIfMissing(env, normalized, value);
1201
1310
  }
1202
1311
  return protectedKeys;
1203
1312
  }
@@ -1234,7 +1343,7 @@ function addAuthCredentialEnvKeys(protectedKeys, methodId, credential) {
1234
1343
  }
1235
1344
  function assignAuthCredentialEnv(env, methodId, credential) {
1236
1345
  if (typeof credential !== "string" || credential.trim().length === 0) return;
1237
- if (!methodId.includes("=") && !methodId.includes("\0") && env[methodId] == null) env[methodId] = credential;
1346
+ if (!methodId.includes("=") && !methodId.includes("\0")) assignIfMissing(env, methodId, credential);
1238
1347
  const normalized = toEnvToken(methodId);
1239
1348
  if (normalized) {
1240
1349
  assignIfMissing(env, `${AUTH_ENV_PREFIX}${normalized}`, credential);
@@ -1242,7 +1351,10 @@ function assignAuthCredentialEnv(env, methodId, credential) {
1242
1351
  }
1243
1352
  }
1244
1353
  function assignIfMissing(env, key, value) {
1245
- if (env[key] == null) env[key] = value;
1354
+ if (env[key] != null) return;
1355
+ const normalizedKey = protectedEnvKey(key);
1356
+ if (process.platform === "win32" && Object.entries(env).some(([existingKey, existingValue]) => existingValue != null && protectedEnvKey(existingKey) === normalizedKey)) return;
1357
+ env[key] = value;
1246
1358
  }
1247
1359
  function resolveConfiguredAuthCredential(methodId, authCredentials) {
1248
1360
  const configCredentials = authCredentials ?? {};
@@ -1753,7 +1865,7 @@ function buildTerminalSpawnOptions(command, cwd, env, platform = process.platfor
1753
1865
  "pipe"
1754
1866
  ],
1755
1867
  windowsHide: true
1756
- }, platform, resolvedEnv ?? process.env);
1868
+ }, platform, resolvedEnv ?? process.env, cwd);
1757
1869
  }
1758
1870
  function readTerminalOutputCeiling() {
1759
1871
  const raw = process.env.ACPX_TERMINAL_MAX_OUTPUT_BYTES?.trim();
@@ -1804,6 +1916,7 @@ var TerminalManager = class {
1804
1916
  processHelperTimeoutMs;
1805
1917
  outputCeilingBytes;
1806
1918
  terminals = /* @__PURE__ */ new Map();
1919
+ creations = /* @__PURE__ */ new Map();
1807
1920
  constructor(options) {
1808
1921
  this.outputCeilingBytes = readTerminalOutputCeiling();
1809
1922
  this.cwd = options.cwd;
@@ -1820,7 +1933,15 @@ var TerminalManager = class {
1820
1933
  this.permissionMode = permissionMode;
1821
1934
  this.nonInteractivePermissions = nonInteractivePermissions ?? "deny";
1822
1935
  }
1823
- async createTerminal(params, authority) {
1936
+ createTerminal(params, authority) {
1937
+ const creation = {};
1938
+ const pending = Promise.resolve().then(() => this.create(params, authority, creation));
1939
+ this.creations.set(pending, creation);
1940
+ const remove = () => this.creations.delete(pending);
1941
+ pending.then(remove, remove);
1942
+ return pending;
1943
+ }
1944
+ async create(params, authority, creation) {
1824
1945
  assertControlAuthority(authority);
1825
1946
  const commandLine = toCommandLine(params.command, params.args);
1826
1947
  const summary = `terminal/create: ${commandLine}`;
@@ -1884,10 +2005,15 @@ var TerminalManager = class {
1884
2005
  await terminal.descendants?.capture(terminal.processHelperTimeoutMs);
1885
2006
  assertControlAuthority(authority);
1886
2007
  } catch (error) {
1887
- await this.releaseTerminal({
1888
- terminalId,
1889
- sessionId: params.sessionId
1890
- });
2008
+ try {
2009
+ await this.releaseTerminal({
2010
+ terminalId,
2011
+ sessionId: params.sessionId
2012
+ });
2013
+ } catch (cleanupError) {
2014
+ creation.cleanupFailure = { error: cleanupError };
2015
+ throw cleanupError;
2016
+ }
1891
2017
  throw error;
1892
2018
  }
1893
2019
  this.emitOperation({
@@ -2020,11 +2146,13 @@ var TerminalManager = class {
2020
2146
  }
2021
2147
  }
2022
2148
  async shutdown() {
2149
+ const creations = [...this.creations];
2150
+ await Promise.allSettled(creations.map(([pending]) => pending));
2023
2151
  const results = await Promise.allSettled(Array.from(this.terminals.keys(), (terminalId) => this.releaseTerminal({
2024
2152
  terminalId,
2025
2153
  sessionId: "shutdown"
2026
2154
  })));
2027
- const failures = [];
2155
+ const failures = creations.flatMap(([, creation]) => creation.cleanupFailure ? [creation.cleanupFailure.error] : []);
2028
2156
  for (const result of results) if (result.status === "rejected") failures.push(result.reason);
2029
2157
  if (failures.length > 0) throw new AggregateError(failures, "Terminal shutdown failed", { cause: failures[0] });
2030
2158
  }
@@ -2468,8 +2596,9 @@ var AcpClient = class {
2468
2596
  }
2469
2597
  fallback?.abort();
2470
2598
  }
2471
- async start(authority) {
2599
+ async start(authority, options) {
2472
2600
  assertControlAuthority(authority);
2601
+ if (options) this.options.sessionOptions = structuredClone(options.sessionOptions);
2473
2602
  if (this.hasLiveConnection()) return;
2474
2603
  const epoch = await this.waitForPreviousClose();
2475
2604
  const maxMessageBytes = readMaxAcpMessageBytes();
@@ -2588,14 +2717,14 @@ var AcpClient = class {
2588
2717
  async ensureLaunchSupport(plan) {
2589
2718
  if (plan.copilotAcp) await ensureCopilotAcpSupport(plan.spawnCommand, plan.commandContext);
2590
2719
  if (!plan.claudeAcp) return;
2591
- const claudeExe = resolveClaudeCodeExecutable(process.platform, plan.spawnOptions.env);
2720
+ const claudeExe = resolveClaudeCodeExecutable(process.platform, plan.spawnOptions.env, plan.spawnOptions.cwd);
2592
2721
  if (claudeExe) {
2593
2722
  plan.spawnOptions.env.CLAUDE_CODE_EXECUTABLE = claudeExe;
2594
2723
  this.log(`resolved system Claude Code executable: ${claudeExe}`);
2595
2724
  }
2596
2725
  }
2597
2726
  prepareProcessLaunch(command, args, options) {
2598
- const resolved = buildAgentSpawnCommand(command, args, process.platform, options.env);
2727
+ const resolved = buildAgentSpawnCommand(command, args, process.platform, options.env, options.cwd);
2599
2728
  return {
2600
2729
  launch: Object.freeze({
2601
2730
  launchId: randomUUID(),
@@ -2805,6 +2934,10 @@ var AcpClient = class {
2805
2934
  onPromptRequestWritten: (active, owner) => this.onPromptRequestWritten(active, owner)
2806
2935
  });
2807
2936
  }
2937
+ sessionMeta() {
2938
+ const { command, args } = resolveAgentCommandParts(this.options.agentCommand, this.options.agentArgv);
2939
+ return buildClaudeCodeOptionsMeta(this.options.sessionOptions, isClaudeAcpCommand(command, args));
2940
+ }
2808
2941
  async createSession(cwd = this.options.cwd, authority) {
2809
2942
  assertControlAuthority(authority);
2810
2943
  const connection = this.getConnection();
@@ -2816,7 +2949,7 @@ var AcpClient = class {
2816
2949
  const createPromise = this.runConnectionRequest(() => connection.agent.request(methods.agent.session.new, {
2817
2950
  cwd: sessionCwd,
2818
2951
  mcpServers: this.options.mcpServers ?? [],
2819
- _meta: buildClaudeCodeOptionsMeta(this.options.sessionOptions, claudeAcp)
2952
+ _meta: this.sessionMeta()
2820
2953
  }), authority);
2821
2954
  result = claudeAcp ? await withTimeout(createPromise, resolveClaudeAcpSessionCreateTimeoutMs()) : await createPromise;
2822
2955
  } catch (error) {
@@ -2856,7 +2989,8 @@ var AcpClient = class {
2856
2989
  response = await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.load, {
2857
2990
  sessionId,
2858
2991
  cwd: sessionCwd,
2859
- mcpServers: this.options.mcpServers ?? []
2992
+ mcpServers: this.options.mcpServers ?? [],
2993
+ _meta: this.sessionMeta()
2860
2994
  }), options.authority);
2861
2995
  await this.waitForSessionUpdateDrain(options.replayIdleMs ?? REPLAY_IDLE_MS, options.replayDrainTimeoutMs ?? REPLAY_DRAIN_TIMEOUT_MS);
2862
2996
  } finally {
@@ -2874,7 +3008,8 @@ var AcpClient = class {
2874
3008
  const response = await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.resume, {
2875
3009
  sessionId,
2876
3010
  cwd: sessionCwd,
2877
- mcpServers: this.options.mcpServers ?? []
3011
+ mcpServers: this.options.mcpServers ?? [],
3012
+ _meta: this.sessionMeta()
2878
3013
  }), authority);
2879
3014
  this.loadedSessionId = sessionId;
2880
3015
  const result = toReconnectedSessionResult(response);
@@ -3862,6 +3997,7 @@ function contentToUserContent(content) {
3862
3997
  if (content.type === "resource") return resourceToUserContent(content);
3863
3998
  if (content.type === "image") return { Image: {
3864
3999
  source: content.data,
4000
+ mime_type: content.mimeType,
3865
4001
  size: null
3866
4002
  } };
3867
4003
  if (content.type === "audio") return { Audio: {
@@ -3930,7 +4066,10 @@ function appendAgentThinking(agent, text) {
3930
4066
  }
3931
4067
  function trimRuntimeText(value, maxChars) {
3932
4068
  if (value.length <= maxChars) return value;
3933
- return `${value.slice(0, Math.max(0, maxChars - 3))}...`;
4069
+ let end = Math.max(0, maxChars - 3);
4070
+ const last = value.charCodeAt(end - 1);
4071
+ if (last >= 55296 && last <= 56319) end -= 1;
4072
+ return `${value.slice(0, end)}...`;
3934
4073
  }
3935
4074
  function statusIndicatesComplete(status) {
3936
4075
  if (typeof status !== "string") return false;
@@ -4477,13 +4616,6 @@ function clearDesiredConfigOption(state, configId) {
4477
4616
  function getDesiredModelId(state) {
4478
4617
  return normalizeModelId(state?.session_options?.model);
4479
4618
  }
4480
- function setCurrentModelId(record, modelId) {
4481
- const acpx = ensureAcpxState(record.acpx);
4482
- const normalized = normalizeModelId(modelId);
4483
- if (normalized) acpx.current_model_id = normalized;
4484
- else delete acpx.current_model_id;
4485
- record.acpx = acpx;
4486
- }
4487
4619
  function syncAdvertisedModelState(record, models) {
4488
4620
  if (!models) return;
4489
4621
  const acpx = ensureAcpxState(record.acpx);
@@ -4506,9 +4638,13 @@ async function applyRequestedModelIfAdvertised(params) {
4506
4638
  });
4507
4639
  if (warning) params.onWarning?.(warning);
4508
4640
  if (!params.models) return { applied: false };
4509
- if (params.models.currentModelId === requestedModel) return { applied: true };
4641
+ if (params.models.currentModelId === requestedModel) return {
4642
+ applied: true,
4643
+ modelId: requestedModel
4644
+ };
4510
4645
  return {
4511
4646
  applied: true,
4647
+ modelId: requestedModel,
4512
4648
  response: await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel, params.models, params.authority), params.timeoutMs)
4513
4649
  };
4514
4650
  }
@@ -4517,276 +4653,61 @@ async function applyRequestedModelIfAdvertised(params) {
4517
4653
  function applyConfigOptionsToState(state, configOptions) {
4518
4654
  const acpxState = cloneSessionAcpxState(state) ?? {};
4519
4655
  applyConfigOptionsModelState(acpxState, configOptions);
4520
- return acpxState;
4521
- }
4522
- function applyConfigOptionsToRecord(record, result) {
4523
- const configOptions = result?.configOptions;
4524
- if (!configOptions) return;
4525
- record.acpx = applyConfigOptionsToState(record.acpx, configOptions);
4526
- }
4527
- function applyInitialModelSelection(record, originalModels, requestedModel, modelApplication) {
4528
- applyConfigOptionsToRecord(record, modelApplication.response);
4529
- syncAdvertisedModelState(record, modelApplication.response ? modelStateFromConfigOptions(modelApplication.response.configOptions) : originalModels);
4530
- if (modelApplication.applied) setCurrentModelId(record, currentModelIdFromSetModelResponse(modelApplication.response, requestedModel));
4531
- }
4532
- function applyAcceptedConfigOptions(state, response) {
4533
- const next = cloneSessionAcpxState(state) ?? {};
4534
- if (!response) return next;
4535
- applyConfigOptionsModelState(next, response.configOptions);
4536
- if (!next.desired_config_options) return next;
4537
- const desiredEntries = [];
4538
- for (const option of response.configOptions) if (typeof option.currentValue === "string" && Object.hasOwn(next.desired_config_options, option.id)) desiredEntries.push([option.id, option.currentValue]);
4539
- if (desiredEntries.length > 0) next.desired_config_options = Object.fromEntries(desiredEntries);
4540
- else delete next.desired_config_options;
4541
- return next;
4542
- }
4543
- function applyModelSelection(state, modelId, response) {
4544
- const modelConfigId = advertisedModelState(state)?.configId;
4545
- const next = applyAcceptedConfigOptions(state, response);
4546
- next.session_options = {
4547
- ...next.session_options,
4548
- model: modelId
4549
- };
4550
- next.current_model_id = currentModelIdFromSetModelResponse(response, modelId);
4551
- clearDesiredConfigOption(next, modelConfigId ?? advertisedModelState(next)?.configId);
4552
- return next;
4553
- }
4554
- function applyConfigOptionSelection(state, configId, value, response, modelConfigId = advertisedModelState(state)?.configId) {
4555
- if (configId === modelConfigId || configId === modelStateFromConfigOptions(response.configOptions)?.configId) return applyModelSelection(state, value, response);
4556
- const next = cloneSessionAcpxState(state) ?? {};
4557
- next.desired_config_options = {
4558
- ...next.desired_config_options,
4559
- [configId]: value
4560
- };
4561
- return applyAcceptedConfigOptions(next, response);
4562
- }
4563
- //#endregion
4564
- //#region src/session/turn-ownership.ts
4565
- const LOCK_RETRY_MS = 15;
4566
- const INCOMPLETE_RESERVATION_GRACE_MS = 15e3;
4567
- let lastCreatedAt = 0;
4568
- const localTurns = /* @__PURE__ */ new Map();
4569
- async function readLock(filePath) {
4570
- try {
4571
- const stat = await fs$1.lstat(filePath, { bigint: true });
4572
- if (!stat.isFile()) throw new Error(`Session turn lock is not a regular file: ${filePath}`);
4573
- return {
4574
- stat,
4575
- payload: await fs$1.readFile(filePath, "utf8")
4576
- };
4577
- } catch (error) {
4578
- if (error.code === "ENOENT") return;
4579
- throw error;
4580
- }
4581
- }
4582
- function parseLock(payload) {
4583
- try {
4584
- return JSON.parse(payload);
4585
- } catch {
4586
- return;
4587
- }
4588
- }
4589
- async function tryAcquireGuard(filePath, owner, signal) {
4590
- try {
4591
- return await acquireFileLock(filePath, {
4592
- managerKey: "acpx.session-turn",
4593
- lockPath: `${filePath}.guard`,
4594
- staleMs: Infinity,
4595
- timeoutMs: LOCK_RETRY_MS,
4596
- retry: {
4597
- retries: 8,
4598
- minTimeout: 1,
4599
- maxTimeout: 2,
4600
- factor: 1,
4601
- randomize: false
4602
- },
4603
- staleRecovery: "remove-if-unchanged",
4604
- payload: () => owner.payload,
4605
- shouldReclaim: ({ payload }) => owner.hasExited(payload, false, signal),
4606
- shouldRemoveStaleLock: ({ payload }) => owner.hasExited(payload, true, signal)
4607
- });
4608
- } catch (error) {
4609
- if (error.code === "file_lock_timeout") return;
4610
- throw error;
4611
- }
4612
- }
4613
- async function removeObservedLock(filePath, observed) {
4614
- const current = await readLock(filePath);
4615
- if (!current) return true;
4616
- if (current.payload !== observed.payload || current.stat.mtimeNs !== observed.stat.mtimeNs || current.stat.size !== observed.stat.size) return false;
4617
- await fs$1.unlink(filePath).catch((error) => {
4618
- if (error.code !== "ENOENT") throw error;
4619
- });
4620
- return true;
4621
- }
4622
- async function recoverAbandonedLock(filePath, owner, signal) {
4623
- const observed = await readLock(filePath);
4624
- if (!observed) return true;
4625
- const payload = parseLock(observed.payload);
4626
- const pid = lockOwnerPid(payload);
4627
- if (pid && (!await owner.hasExited(payload, false, signal) || !await owner.hasExited(payload, true, signal))) return false;
4628
- if (!pid && Date.now() - Number(observed.stat.mtimeMs) <= INCOMPLETE_RESERVATION_GRACE_MS) return false;
4629
- if (await removeObservedLock(filePath, observed)) {
4630
- incrementPerfCounter("session.events.stale_lock_recovered");
4631
- return true;
4632
- }
4633
- return false;
4634
- }
4635
- async function publishLock(filePath, payload, signal) {
4636
- await withTempFile({
4637
- rootDir: path.dirname(filePath),
4638
- prefix: "session-turn",
4639
- fileName: "lock"
4640
- }, async (temporaryPath) => {
4641
- await fs$1.writeFile(temporaryPath, payload, {
4642
- encoding: "utf8",
4643
- flag: "wx",
4644
- mode: 384
4645
- });
4646
- signal?.throwIfAborted();
4647
- try {
4648
- await fs$1.link(temporaryPath, filePath);
4649
- } catch (error) {
4650
- if (!isHardlinkFallbackError(error)) throw error;
4651
- await fs$1.writeFile(filePath, payload, {
4652
- encoding: "utf8",
4653
- flag: "wx",
4654
- mode: 384
4655
- });
4656
- }
4657
- });
4658
- }
4659
- async function removePublishedLock(filePath, payload) {
4660
- const current = await readLock(filePath);
4661
- if (current?.payload === payload) await removeObservedLock(filePath, current);
4662
- }
4663
- async function rejectAcquisition(filePath, receipt, error) {
4664
- const failures = [error];
4665
- await disposeTurn(filePath, receipt).catch((cleanupError) => {
4666
- failures.push(...cleanupError instanceof AggregateError ? cleanupError.errors : [cleanupError]);
4667
- });
4668
- if (failures.length > 1) throw new AggregateError(failures, "Session turn acquisition and cleanup failed");
4669
- throw error;
4670
- }
4671
- async function releaseTurnGuard(receipt) {
4672
- await receipt.guard?.release();
4673
- receipt.guard = void 0;
4674
- }
4675
- async function removeTurnMarker(filePath, receipt) {
4676
- if (!receipt.removeMarker) return true;
4677
- if (receipt.guard && !await receipt.guard.verifyStillHeld()) await releaseTurnGuard(receipt);
4678
- receipt.guard ??= await tryAcquireGuard(filePath, receipt.owner);
4679
- if (!receipt.guard) return false;
4680
- await receipt.removeMarker();
4681
- receipt.removeMarker = void 0;
4682
- return true;
4683
- }
4684
- async function settleTurn(filePath, receipt) {
4685
- if (receipt.settled) return true;
4686
- const pending = receipt.pending ??= (async () => {
4687
- receipt.failed = false;
4688
- const failures = [];
4689
- try {
4690
- if (!await removeTurnMarker(filePath, receipt)) return false;
4691
- } catch (error) {
4692
- failures.push(error);
4693
- }
4694
- if (failures.length === 0 || !receipt.admitted) try {
4695
- await releaseTurnGuard(receipt);
4696
- } catch (error) {
4697
- failures.push(error);
4698
- }
4699
- if (failures.length > 1) throw new AggregateError(failures, "Session turn cleanup failed");
4700
- if (failures.length === 1) throw failures[0];
4701
- receipt.settled = true;
4702
- localTurns.delete(filePath);
4703
- return true;
4704
- })();
4705
- try {
4706
- return await pending;
4707
- } finally {
4708
- if (receipt.pending === pending) {
4709
- receipt.pending = void 0;
4710
- receipt.failed = !receipt.settled;
4711
- }
4712
- }
4656
+ return acpxState;
4713
4657
  }
4714
- async function disposeTurn(filePath, receipt) {
4715
- if (!await settleTurn(filePath, receipt)) throw new Error(`Session turn cleanup is waiting for its guard: ${filePath}`);
4658
+ function applyConfigOptionsToRecord(record, result) {
4659
+ const configOptions = result?.configOptions;
4660
+ if (!configOptions) return;
4661
+ record.acpx = applyConfigOptionsToState(record.acpx, configOptions);
4716
4662
  }
4717
- async function tryPublishLock(filePath, payload, owner, signal) {
4718
- for (;;) try {
4719
- await publishLock(filePath, payload, signal);
4720
- return true;
4721
- } catch (error) {
4722
- if (error.code !== "EEXIST") throw error;
4723
- if (!await recoverAbandonedLock(filePath, owner, signal)) return false;
4724
- }
4663
+ function applyInitialModelSelection(record, originalModels, modelApplication) {
4664
+ applyConfigOptionsToRecord(record, modelApplication.response);
4665
+ syncAdvertisedModelState(record, modelApplication.response?.configOptions !== void 0 ? modelStateFromConfigOptions(modelApplication.response.configOptions) : originalModels);
4666
+ if (modelApplication.applied) record.acpx = applyModelSelection(record.acpx, modelApplication.modelId, modelApplication.response);
4725
4667
  }
4726
- async function tryAcquireTurnCleanup(filePath, payload, owner, signal) {
4727
- const previous = localTurns.get(filePath);
4728
- if (previous) {
4729
- if (previous.failed) await settleTurn(filePath, previous);
4730
- return;
4731
- }
4732
- const receipt = {
4733
- owner,
4734
- removeMarker: async () => await removePublishedLock(filePath, payload),
4735
- admitted: false,
4736
- failed: false,
4737
- settled: false
4738
- };
4739
- localTurns.set(filePath, receipt);
4740
- try {
4741
- receipt.guard = await tryAcquireGuard(filePath, owner, signal);
4742
- } catch (error) {
4743
- localTurns.delete(filePath);
4744
- throw error;
4745
- }
4746
- if (!receipt.guard) {
4747
- localTurns.delete(filePath);
4748
- return;
4668
+ function applyAcceptedConfigOptions(state, response, selection) {
4669
+ const next = cloneSessionAcpxState(state) ?? {};
4670
+ if (response?.configOptions === void 0) {
4671
+ const option = next.config_options?.find((entry) => entry.id === selection.configId);
4672
+ if (option) option.currentValue = selection.value;
4673
+ return next;
4749
4674
  }
4750
- return receipt;
4675
+ applyConfigOptionsModelState(next, response.configOptions);
4676
+ reconcileDesiredConfigOptions(next, response.configOptions);
4677
+ return next;
4751
4678
  }
4752
- async function tryAcquireTurn(filePath, payload, owner, signal) {
4753
- const receipt = await tryAcquireTurnCleanup(filePath, payload, owner, signal);
4754
- if (!receipt) return;
4755
- try {
4756
- signal?.throwIfAborted();
4757
- if (!await tryPublishLock(filePath, payload, owner, signal)) {
4758
- await disposeTurn(filePath, receipt);
4759
- return;
4760
- }
4761
- const observed = await readLock(filePath);
4762
- if (observed?.payload !== payload) throw new Error(`Session turn ownership changed before admission: ${filePath}`);
4763
- signal?.throwIfAborted();
4764
- receipt.admitted = true;
4765
- receipt.removeMarker = async () => await removeObservedLock(filePath, observed);
4766
- return { [Symbol.asyncDispose]: async () => await disposeTurn(filePath, receipt) };
4767
- } catch (error) {
4768
- return await rejectAcquisition(filePath, receipt, error);
4769
- }
4679
+ function reconcileDesiredConfigOptions(state, configOptions) {
4680
+ if (!state.desired_config_options) return;
4681
+ const desiredEntries = [];
4682
+ for (const option of configOptions) if (typeof option.currentValue === "string" && Object.hasOwn(state.desired_config_options, option.id)) desiredEntries.push([option.id, option.currentValue]);
4683
+ if (desiredEntries.length > 0) state.desired_config_options = Object.fromEntries(desiredEntries);
4684
+ else delete state.desired_config_options;
4770
4685
  }
4771
- async function acquireSessionTurn(recordId, signal) {
4772
- const requestedPath = sessionEventLockPath(recordId);
4773
- await fs$1.mkdir(path.dirname(requestedPath), {
4774
- recursive: true,
4775
- mode: 448
4686
+ function applyModelSelection(state, modelId, response) {
4687
+ const modelConfigId = advertisedModelState(state)?.configId;
4688
+ const next = applyAcceptedConfigOptions(state, response, {
4689
+ configId: modelConfigId,
4690
+ value: modelId
4691
+ });
4692
+ next.session_options = {
4693
+ ...next.session_options,
4694
+ model: modelId
4695
+ };
4696
+ next.current_model_id = currentModelIdFromSetModelResponse(response, modelId);
4697
+ clearDesiredConfigOption(next, modelConfigId ?? advertisedModelState(next)?.configId);
4698
+ return next;
4699
+ }
4700
+ function applyConfigOptionSelection(state, configId, value, response, modelConfigId = advertisedModelState(state)?.configId) {
4701
+ if (configId === modelConfigId || configId === modelStateFromConfigOptions(response.configOptions)?.configId) return applyModelSelection(state, value, response);
4702
+ const next = cloneSessionAcpxState(state) ?? {};
4703
+ next.desired_config_options = {
4704
+ ...next.desired_config_options,
4705
+ [configId]: value
4706
+ };
4707
+ return applyAcceptedConfigOptions(next, response, {
4708
+ configId,
4709
+ value
4776
4710
  });
4777
- const filePath = path.join(await fs$1.realpath(path.dirname(requestedPath)), path.basename(requestedPath));
4778
- const owner = await createLockOwner({ signal });
4779
- lastCreatedAt = Math.max(Date.now(), lastCreatedAt + 1);
4780
- const payload = `${JSON.stringify({
4781
- ...owner.payload,
4782
- created_at: new Date(lastCreatedAt).toISOString()
4783
- }, null, 2)}\n`;
4784
- for (;;) {
4785
- signal?.throwIfAborted();
4786
- const turn = await tryAcquireTurn(filePath, payload, owner, signal);
4787
- if (turn) return turn;
4788
- await setTimeout$1(LOCK_RETRY_MS, void 0, { signal });
4789
- }
4790
4711
  }
4791
4712
  //#endregion
4792
4713
  //#region src/session/execution/direct-lifetime.ts
@@ -4813,406 +4734,6 @@ function directExecutionError(error, signal) {
4813
4734
  return signal?.aborted && error instanceof AgentDisconnectedError ? signal.reason : error;
4814
4735
  }
4815
4736
  //#endregion
4816
- //#region src/session/queue/ipc-server.ts
4817
- const QUEUE_SOCKET_DRAIN_TIMEOUT_MS = 1e3;
4818
- function makeQueueOwnerError(requestId, message, detailCode, options = {}) {
4819
- return {
4820
- type: "error",
4821
- requestId,
4822
- ownerGeneration: void 0,
4823
- code: "RUNTIME",
4824
- detailCode,
4825
- origin: "queue",
4826
- retryable: options.retryable,
4827
- message
4828
- };
4829
- }
4830
- function makeQueueOwnerErrorFromUnknown(requestId, error, detailCode, options = {}) {
4831
- const normalized = normalizeOutputError(error, {
4832
- defaultCode: "RUNTIME",
4833
- origin: "queue",
4834
- detailCode,
4835
- retryable: options.retryable
4836
- });
4837
- return {
4838
- type: "error",
4839
- requestId,
4840
- code: normalized.code,
4841
- detailCode: normalized.detailCode,
4842
- origin: normalized.origin,
4843
- message: normalized.message,
4844
- retryable: normalized.retryable,
4845
- acp: normalized.acp
4846
- };
4847
- }
4848
- function armSocketDrainTimeout(socket) {
4849
- if (process.platform !== "win32") socket.setTimeout(QUEUE_SOCKET_DRAIN_TIMEOUT_MS);
4850
- }
4851
- function writeQueueMessage(socket, message) {
4852
- if (socket.destroyed || !socket.writable) return;
4853
- if (!socket.write(`${JSON.stringify(message)}\n`) && !socket.timeout) armSocketDrainTimeout(socket);
4854
- }
4855
- var SessionQueueOwner = class SessionQueueOwner {
4856
- server;
4857
- controlHandlers;
4858
- ownerGeneration;
4859
- maxQueueDepth;
4860
- maxRequestBytes;
4861
- onQueueDepthChanged;
4862
- pending = [];
4863
- activeTask;
4864
- waiters = [];
4865
- sockets = /* @__PURE__ */ new Set();
4866
- taskSockets = /* @__PURE__ */ new Set();
4867
- drainingSockets = /* @__PURE__ */ new Set();
4868
- closed = false;
4869
- constructor(server, controlHandlers, lease, options) {
4870
- this.server = server;
4871
- this.controlHandlers = controlHandlers;
4872
- this.ownerGeneration = lease.ownerGeneration;
4873
- this.maxQueueDepth = Math.max(1, Math.round(options.maxQueueDepth));
4874
- this.maxRequestBytes = queueRequestByteLimit(options.maxRequestBytes?.toString());
4875
- this.onQueueDepthChanged = options.onQueueDepthChanged;
4876
- }
4877
- static async start(lease, controlHandlers, options = { maxQueueDepth: 16 }) {
4878
- const ownerRef = { current: void 0 };
4879
- const server = net.createServer((socket) => {
4880
- ownerRef.current?.handleConnection(socket);
4881
- });
4882
- ownerRef.current = new SessionQueueOwner(server, controlHandlers, lease, options);
4883
- await new Promise((resolve, reject) => {
4884
- const onListening = () => {
4885
- server.off("error", onError);
4886
- resolve();
4887
- };
4888
- const onError = (error) => {
4889
- server.off("listening", onListening);
4890
- reject(error);
4891
- };
4892
- server.once("listening", onListening);
4893
- server.once("error", onError);
4894
- server.listen(lease.socketPath);
4895
- });
4896
- return ownerRef.current;
4897
- }
4898
- beginShutdown() {
4899
- if (this.closed) return;
4900
- this.closed = true;
4901
- for (const waiter of this.waiters.splice(0)) waiter(void 0);
4902
- for (const task of this.pending.splice(0)) {
4903
- if (task.waitForCompletion) task.send(makeQueueOwnerError(task.requestId, "Queue owner shutting down before prompt execution", "QUEUE_OWNER_SHUTTING_DOWN", { retryable: true }));
4904
- task.close();
4905
- }
4906
- for (const socket of this.sockets) if (!this.taskSockets.has(socket) && !this.drainingSockets.has(socket)) socket.destroy();
4907
- this.emitQueueDepth();
4908
- }
4909
- async close() {
4910
- this.beginShutdown();
4911
- await new Promise((resolve) => {
4912
- const forceCloseTimer = setTimeout(() => {
4913
- for (const socket of this.sockets) socket.destroy();
4914
- }, QUEUE_SOCKET_DRAIN_TIMEOUT_MS);
4915
- forceCloseTimer.unref();
4916
- this.server.close(() => {
4917
- clearTimeout(forceCloseTimer);
4918
- resolve();
4919
- });
4920
- });
4921
- }
4922
- async nextTask(timeoutMs) {
4923
- if (this.pending.length > 0) {
4924
- const task = this.pending.shift();
4925
- this.emitQueueDepth();
4926
- if (task) {
4927
- this.activeTask = task;
4928
- recordPerfDuration("queue.owner.wait_ms", Date.now() - task.enqueuedAt);
4929
- }
4930
- return task;
4931
- }
4932
- if (this.closed) return;
4933
- return await new Promise((resolve) => {
4934
- const timer = timeoutMs != null && setTimeout(() => {
4935
- const index = this.waiters.indexOf(waiter);
4936
- if (index >= 0) this.waiters.splice(index, 1);
4937
- resolve(void 0);
4938
- }, Math.max(0, timeoutMs));
4939
- const waiter = (task) => {
4940
- if (timer) clearTimeout(timer);
4941
- resolve(task);
4942
- };
4943
- this.waiters.push(waiter);
4944
- });
4945
- }
4946
- queueDepth() {
4947
- return this.pending.length;
4948
- }
4949
- completeTask(task) {
4950
- if (this.activeTask === task) this.activeTask = void 0;
4951
- }
4952
- async cancelPrompt(targetRequestId) {
4953
- if (targetRequestId === void 0) return await this.controlHandlers.cancelPrompt();
4954
- const index = this.pending.findIndex((task) => task.requestId === targetRequestId);
4955
- if (index >= 0) {
4956
- const [task] = this.pending.splice(index, 1);
4957
- task.send(makeQueueOwnerError(task.requestId, "Prompt cancelled before execution", "QUEUE_REQUEST_CANCELLED", { retryable: false }));
4958
- task.close();
4959
- this.emitQueueDepth();
4960
- return true;
4961
- }
4962
- return this.activeTask?.requestId === targetRequestId ? await this.controlHandlers.cancelPrompt() : false;
4963
- }
4964
- emitQueueDepth() {
4965
- this.onQueueDepthChanged?.(this.pending.length);
4966
- }
4967
- enqueue(task) {
4968
- if (this.closed) {
4969
- task.send(makeQueueOwnerError(task.requestId, "Queue owner is shutting down", "QUEUE_OWNER_SHUTTING_DOWN", { retryable: true }));
4970
- task.close();
4971
- return false;
4972
- }
4973
- if (this.activeTask?.requestId === task.requestId || this.pending.some((queued) => queued.requestId === task.requestId)) {
4974
- task.send(makeQueueOwnerError(task.requestId, "Request is already queued or active", "QUEUE_REQUEST_DUPLICATE", { retryable: false }));
4975
- task.close();
4976
- return false;
4977
- }
4978
- const waiter = this.waiters.shift();
4979
- if (waiter) {
4980
- this.activeTask = task;
4981
- waiter(task);
4982
- return true;
4983
- }
4984
- if (this.pending.length >= this.maxQueueDepth) {
4985
- task.send({
4986
- ...makeQueueOwnerError(task.requestId, `Queue owner is overloaded (${this.pending.length}/${this.maxQueueDepth} queued)`, "QUEUE_OWNER_OVERLOADED", { retryable: true }),
4987
- ownerGeneration: this.ownerGeneration
4988
- });
4989
- task.close();
4990
- return false;
4991
- }
4992
- this.pending.push(task);
4993
- this.emitQueueDepth();
4994
- return true;
4995
- }
4996
- handleControlRequest(options) {
4997
- this.taskSockets.add(options.socket);
4998
- writeQueueMessage(options.socket, {
4999
- type: "accepted",
5000
- requestId: options.requestId,
5001
- ownerGeneration: this.ownerGeneration
5002
- });
5003
- options.run().then((message) => {
5004
- writeQueueMessage(options.socket, {
5005
- ...message,
5006
- ownerGeneration: this.ownerGeneration
5007
- });
5008
- }).catch((error) => {
5009
- writeQueueMessage(options.socket, {
5010
- ...makeQueueOwnerErrorFromUnknown(options.requestId, error, "QUEUE_CONTROL_REQUEST_FAILED"),
5011
- ownerGeneration: this.ownerGeneration
5012
- });
5013
- }).finally(() => {
5014
- this.endSocket(options.socket);
5015
- });
5016
- }
5017
- endSocket(socket) {
5018
- if (socket.destroyed || this.drainingSockets.has(socket)) return;
5019
- this.taskSockets.delete(socket);
5020
- this.drainingSockets.add(socket);
5021
- armSocketDrainTimeout(socket);
5022
- socket.end(() => {
5023
- this.drainingSockets.delete(socket);
5024
- socket.destroy();
5025
- });
5026
- }
5027
- failRequest(socket, requestId, message, detailCode) {
5028
- writeQueueMessage(socket, {
5029
- ...makeQueueOwnerError(requestId, message, detailCode, { retryable: false }),
5030
- ownerGeneration: this.ownerGeneration
5031
- });
5032
- this.endSocket(socket);
5033
- }
5034
- parseRequestLine(socket, line) {
5035
- let parsed;
5036
- try {
5037
- parsed = JSON.parse(line);
5038
- } catch {
5039
- this.failRequest(socket, "unknown", "Invalid queue request payload", "QUEUE_REQUEST_PAYLOAD_INVALID_JSON");
5040
- return;
5041
- }
5042
- const request = parseQueueRequest(parsed);
5043
- if (!request) this.failRequest(socket, "unknown", "Invalid queue request", "QUEUE_REQUEST_INVALID");
5044
- return request ?? void 0;
5045
- }
5046
- rejectStaleOwnerGeneration(socket, request) {
5047
- if (request.ownerGeneration === void 0 || this.ownerGeneration === void 0 || request.ownerGeneration === this.ownerGeneration) return false;
5048
- this.failRequest(socket, request.requestId, "Queue request targeted a stale queue owner generation", "QUEUE_OWNER_GENERATION_MISMATCH");
5049
- return true;
5050
- }
5051
- rejectClosedOwner(socket, request) {
5052
- if (!this.closed) return false;
5053
- writeQueueMessage(socket, {
5054
- ...makeQueueOwnerError(request.requestId, "Queue owner is shutting down", "QUEUE_OWNER_CLOSED", { retryable: true }),
5055
- ownerGeneration: this.ownerGeneration
5056
- });
5057
- this.endSocket(socket);
5058
- return true;
5059
- }
5060
- handleControlQueueRequest(socket, request) {
5061
- if (request.type === "cancel_prompt") {
5062
- this.handleControlRequest({
5063
- socket,
5064
- requestId: request.requestId,
5065
- run: async () => ({
5066
- type: "cancel_result",
5067
- requestId: request.requestId,
5068
- cancelled: await this.cancelPrompt(request.targetRequestId)
5069
- })
5070
- });
5071
- return true;
5072
- }
5073
- if (request.type === "close_session") {
5074
- this.handleControlRequest({
5075
- socket,
5076
- requestId: request.requestId,
5077
- run: async () => ({
5078
- type: "close_session_result",
5079
- requestId: request.requestId,
5080
- closed: await this.controlHandlers.closeSession(request.timeoutMs)
5081
- })
5082
- });
5083
- return true;
5084
- }
5085
- return this.handleSessionControlQueueRequest(socket, request);
5086
- }
5087
- handleSessionControlQueueRequest(socket, request) {
5088
- if (request.type === "set_mode") {
5089
- this.handleControlRequest({
5090
- socket,
5091
- requestId: request.requestId,
5092
- run: async () => {
5093
- await this.controlHandlers.setSessionMode(request.modeId, request.timeoutMs);
5094
- return {
5095
- type: "set_mode_result",
5096
- requestId: request.requestId,
5097
- modeId: request.modeId
5098
- };
5099
- }
5100
- });
5101
- return true;
5102
- }
5103
- if (request.type === "set_model") {
5104
- this.handleControlRequest({
5105
- socket,
5106
- requestId: request.requestId,
5107
- run: async () => {
5108
- const response = await this.controlHandlers.setSessionModel(request.modelId, request.timeoutMs);
5109
- return {
5110
- type: "set_model_result",
5111
- requestId: request.requestId,
5112
- modelId: request.modelId,
5113
- response
5114
- };
5115
- }
5116
- });
5117
- return true;
5118
- }
5119
- if (request.type === "set_config_option") {
5120
- this.handleControlRequest({
5121
- socket,
5122
- requestId: request.requestId,
5123
- run: async () => ({
5124
- type: "set_config_option_result",
5125
- requestId: request.requestId,
5126
- response: await this.controlHandlers.setSessionConfigOption(request.configId, request.value, request.timeoutMs)
5127
- })
5128
- });
5129
- return true;
5130
- }
5131
- return false;
5132
- }
5133
- enqueuePromptRequest(socket, request) {
5134
- this.taskSockets.add(socket);
5135
- const task = {
5136
- requestId: request.requestId,
5137
- message: request.message,
5138
- prompt: request.prompt ?? textPrompt(request.message),
5139
- permissionMode: request.permissionMode,
5140
- resumePolicy: request.resumePolicy,
5141
- nonInteractivePermissions: request.nonInteractivePermissions,
5142
- permissionPolicy: request.permissionPolicy,
5143
- timeoutMs: request.timeoutMs,
5144
- suppressSdkConsoleErrors: request.suppressSdkConsoleErrors,
5145
- promptRetries: request.promptRetries,
5146
- sessionOptions: request.sessionOptions,
5147
- waitForCompletion: request.waitForCompletion,
5148
- reportPromptStarted: request.reportPromptStarted,
5149
- enqueuedAt: Date.now(),
5150
- send: (message) => {
5151
- writeQueueMessage(socket, {
5152
- ...message,
5153
- ownerGeneration: this.ownerGeneration
5154
- });
5155
- },
5156
- close: () => {
5157
- this.endSocket(socket);
5158
- }
5159
- };
5160
- if (!this.enqueue(task)) return;
5161
- writeQueueMessage(socket, {
5162
- type: "accepted",
5163
- requestId: request.requestId,
5164
- ownerGeneration: this.ownerGeneration
5165
- });
5166
- if (!request.waitForCompletion) task.close();
5167
- }
5168
- handleConnection(socket) {
5169
- this.sockets.add(socket);
5170
- socket.setEncoding("utf8");
5171
- socket.on("timeout", () => socket.destroy());
5172
- socket.on("drain", () => {
5173
- if (!socket.writableEnded) socket.setTimeout(0);
5174
- });
5175
- socket.once("close", () => {
5176
- this.sockets.delete(socket);
5177
- this.taskSockets.delete(socket);
5178
- this.drainingSockets.delete(socket);
5179
- });
5180
- let buffer = "";
5181
- let handled = false;
5182
- const processLine = (line) => {
5183
- if (handled) return;
5184
- handled = true;
5185
- const request = this.parseRequestLine(socket, line);
5186
- if (!request || this.rejectClosedOwner(socket, request) || this.rejectStaleOwnerGeneration(socket, request)) return;
5187
- if (this.handleControlQueueRequest(socket, request)) return;
5188
- if (request.type !== "submit_prompt") {
5189
- this.failRequest(socket, request.requestId, "Invalid queue request", "QUEUE_REQUEST_INVALID");
5190
- return;
5191
- }
5192
- this.enqueuePromptRequest(socket, request);
5193
- };
5194
- socket.on("data", (chunk) => {
5195
- buffer += chunk;
5196
- let index = buffer.indexOf("\n");
5197
- while (index >= 0) {
5198
- const rawLine = buffer.slice(0, index);
5199
- if (this.rejectOversizedRequest(socket, rawLine)) return;
5200
- const line = rawLine.trim();
5201
- buffer = buffer.slice(index + 1);
5202
- if (line.length > 0) processLine(line);
5203
- index = buffer.indexOf("\n");
5204
- }
5205
- this.rejectOversizedRequest(socket, buffer);
5206
- });
5207
- socket.on("error", () => {});
5208
- }
5209
- rejectOversizedRequest(socket, line) {
5210
- if (!queueRequestExceedsLimit(line, this.maxRequestBytes)) return false;
5211
- socket.destroy();
5212
- return true;
5213
- }
5214
- };
5215
- //#endregion
5216
4737
  //#region src/runtime/engine/reconnect.ts
5217
4738
  const SESSION_LOAD_UNSUPPORTED_CODES = /* @__PURE__ */ new Set([-32601, -32602]);
5218
4739
  function createReplayState(authority) {
@@ -5288,10 +4809,7 @@ async function replayDesiredModel(params) {
5288
4809
  const response = await withTimeout(params.client.setSessionModel(params.sessionId, params.desiredModelId, params.models, params.replay.authority), params.timeoutMs);
5289
4810
  params.replay.acknowledged = true;
5290
4811
  params.record.acpx = applyModelSelection(params.record.acpx, params.desiredModelId, response);
5291
- const models = response ? modelStateFromConfigOptions(response.configOptions) : {
5292
- ...params.models,
5293
- currentModelId: params.desiredModelId
5294
- };
4812
+ const models = advertisedModelState(params.record.acpx);
5295
4813
  if (params.verbose) process.stderr.write(`[acpx] replayed desired model ${params.desiredModelId} on ACP session ${params.sessionId} (previous ${params.previousSessionId})\n`);
5296
4814
  return {
5297
4815
  replayed: true,
@@ -5319,10 +4837,10 @@ async function replayDesiredConfigOptions(params) {
5319
4837
  const response = await withTimeout(params.client.setSessionConfigOption(params.sessionId, configId, value, advertisedModelState(params.record.acpx), params.replay.authority), params.timeoutMs);
5320
4838
  params.replay.acknowledged = true;
5321
4839
  params.record.acpx = applyConfigOptionSelection(params.record.acpx, configId, value, response);
5322
- acceptedConfigOptions = response.configOptions;
4840
+ acceptedConfigOptions = params.record.acpx.config_options;
5323
4841
  result = {
5324
4842
  replayed: true,
5325
- models: modelStateFromConfigOptions(response.configOptions)
4843
+ models: advertisedModelState(params.record.acpx)
5326
4844
  };
5327
4845
  if (params.verbose) process.stderr.write(`[acpx] replayed desired config option ${configId} on ACP session ${params.sessionId} (previous ${params.previousSessionId})\n`);
5328
4846
  } catch (error) {
@@ -5354,7 +4872,7 @@ async function connectAndLoadSession(options) {
5354
4872
  logReconnectAttempt(record, storedProcessAlive, Boolean(record.pid) && !storedProcessAlive, options.verbose);
5355
4873
  const reusingLoadedSession = client.hasReusableSession(record.acpSessionId);
5356
4874
  if (reusingLoadedSession) incrementPerfCounter("runtime.connect_and_load.reused_session");
5357
- else await withTimeout(client.start(options.authority), options.timeoutMs);
4875
+ else await withTimeout(client.start(options.authority, { sessionOptions: mergeSessionOptions(options.sessionOptions, sessionOptionsFromRecord(record)) }), options.timeoutMs);
5358
4876
  assertControlAuthority(options.authority);
5359
4877
  options.onClientAvailable?.(options.activeController);
5360
4878
  applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
@@ -5798,6 +5316,89 @@ async function runSessionSetConfigOptionDirect(options) {
5798
5316
  };
5799
5317
  }
5800
5318
  //#endregion
5319
+ //#region src/session/execution/windows-process-command.ts
5320
+ function skipSeparators(value) {
5321
+ return value.replace(/^[ \t]+/u, "");
5322
+ }
5323
+ function readQuote(commandLine, state, executable) {
5324
+ if (!executable && state.quoted && commandLine[state.index + 1] === "\"") {
5325
+ state.value += "\"";
5326
+ state.index += 2;
5327
+ } else {
5328
+ state.quoted = !state.quoted;
5329
+ state.index += 1;
5330
+ }
5331
+ }
5332
+ function readBackslashes(commandLine, state, executable) {
5333
+ const start = state.index;
5334
+ while (commandLine[state.index] === "\\") state.index += 1;
5335
+ const count = state.index - start;
5336
+ if (executable || commandLine[state.index] !== "\"") state.value += "\\".repeat(count);
5337
+ else {
5338
+ state.value += "\\".repeat(Math.floor(count / 2));
5339
+ if (count % 2 === 1) {
5340
+ state.value += "\"";
5341
+ state.index += 1;
5342
+ } else readQuote(commandLine, state, false);
5343
+ }
5344
+ }
5345
+ function readArgument(commandLine, start, executable) {
5346
+ const state = {
5347
+ value: "",
5348
+ index: start,
5349
+ quoted: false
5350
+ };
5351
+ while (state.index < commandLine.length) {
5352
+ const char = commandLine[state.index];
5353
+ if (!state.quoted && /[ \t]/u.test(char)) break;
5354
+ if (char === "\\") readBackslashes(commandLine, state, executable);
5355
+ else if (char === "\"") readQuote(commandLine, state, executable);
5356
+ else {
5357
+ state.value += char;
5358
+ state.index += 1;
5359
+ }
5360
+ }
5361
+ return state.quoted ? void 0 : state;
5362
+ }
5363
+ function readBatchCommand(commandLine) {
5364
+ const body = commandLine.match(/^[ \t]+\/d[ \t]+\/s[ \t]+\/c[ \t]+"([\s\S]*)"$/iu)?.[1];
5365
+ if (!body) return;
5366
+ return readBatchExecutable(body);
5367
+ }
5368
+ function readBatchExecutable(body) {
5369
+ let command = "";
5370
+ for (let index = 0; index < body.length; index += 1) {
5371
+ const char = body[index];
5372
+ if (char === "^") {
5373
+ index += 1;
5374
+ if (index === body.length) return;
5375
+ command += body[index];
5376
+ } else if (/[ \t]/u.test(char)) break;
5377
+ else if (/["\r\n]/u.test(char)) return;
5378
+ else command += char;
5379
+ }
5380
+ return /\.(?:cmd|bat)$/iu.test(command) ? command : void 0;
5381
+ }
5382
+ /** Decode process observations, not saved command identities or POSIX launch strings. */
5383
+ function splitWindowsProcessCommandLine(commandLine) {
5384
+ const line = skipSeparators(commandLine);
5385
+ const executable = readArgument(line, 0, true);
5386
+ if (!executable?.value) return [];
5387
+ const argv = [executable.value];
5388
+ if (path.win32.basename(executable.value).toLowerCase() === "cmd.exe") {
5389
+ const batch = readBatchCommand(line.slice(executable.index));
5390
+ return batch ? [...argv, batch] : argv;
5391
+ }
5392
+ let rest = skipSeparators(line.slice(executable.index));
5393
+ while (rest) {
5394
+ const argument = readArgument(rest, 0, false);
5395
+ if (!argument) return [];
5396
+ argv.push(argument.value);
5397
+ rest = skipSeparators(rest.slice(argument.index));
5398
+ }
5399
+ return argv;
5400
+ }
5401
+ //#endregion
5801
5402
  //#region src/session/execution/session-control.ts
5802
5403
  async function cancelSessionPrompt(options) {
5803
5404
  const cancelled = await tryCancelOnRunningOwner(options);
@@ -5907,15 +5508,16 @@ async function setSessionConfigOption(options) {
5907
5508
  verbose: options.verbose
5908
5509
  });
5909
5510
  }
5910
- function firstAgentCommandToken(command) {
5511
+ function firstAgentCommandToken(command, argv) {
5512
+ if (argv) return argv[0] || void 0;
5911
5513
  try {
5912
5514
  return splitCommandLine(command).command || void 0;
5913
5515
  } catch {
5914
5516
  return;
5915
5517
  }
5916
5518
  }
5917
- async function isLikelyMatchingProcess(pid, agentCommand) {
5918
- const expectedToken = firstAgentCommandToken(agentCommand);
5519
+ async function isLikelyMatchingProcess(pid, record) {
5520
+ const expectedToken = firstAgentCommandToken(record.agentCommand, record.agentArgv);
5919
5521
  if (!expectedToken) return false;
5920
5522
  const argv = await readProcessArgv(pid);
5921
5523
  if (argv.length === 0) return false;
@@ -5954,13 +5556,14 @@ async function readWindowsCommandLine(pid) {
5954
5556
  "-NonInteractive",
5955
5557
  "-Command",
5956
5558
  `(Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}").CommandLine`
5957
- ], { windowsHide: true })).trim() || void 0;
5559
+ ], { windowsHide: true })).replace(/\r?\n$/u, "") || void 0;
5958
5560
  } catch {
5959
5561
  return;
5960
5562
  }
5961
5563
  }
5962
- function splitCommandLineLike(commandLine) {
5564
+ function splitCommandLineLike(commandLine, platform = process.platform) {
5963
5565
  if (!commandLine) return [];
5566
+ if (platform === "win32") return splitWindowsProcessCommandLine(commandLine);
5964
5567
  try {
5965
5568
  const parsed = splitCommandLine(commandLine);
5966
5569
  return [parsed.command, ...parsed.args];
@@ -5982,7 +5585,7 @@ async function closeSession(sessionId) {
5982
5585
  }
5983
5586
  }).catch(() => {});
5984
5587
  if (selectedOwner) await terminateQueueOwnerForSession(record.acpxRecordId, selectedOwner);
5985
- if (record.pid != null && isProcessAlive(record.pid) && await isLikelyMatchingProcess(record.pid, record.agentCommand)) await terminateProcess(record.pid);
5588
+ if (record.pid != null && isProcessAlive(record.pid) && await isLikelyMatchingProcess(record.pid, record)) await terminateProcess(record.pid);
5986
5589
  const ownership = await acquireSessionTurn(record.acpxRecordId);
5987
5590
  try {
5988
5591
  const current = await resolveSessionRecord(record.acpxRecordId);
@@ -6023,7 +5626,7 @@ async function createSessionRecordWithClient(client, options) {
6023
5626
  };
6024
5627
  persistSessionOptions(record, options.sessionOptions);
6025
5628
  applyConfigOptionsToRecord(record, createdState.sessionResult);
6026
- applyInitialModelSelection(record, createdState.sessionResult.models, options.sessionOptions?.model, createdState.modelApplication);
5629
+ applyInitialModelSelection(record, createdState.sessionResult.models, createdState.modelApplication);
6027
5630
  await writeSessionRecord(record);
6028
5631
  assertControlAuthority({ signal: options.signal });
6029
5632
  return record;
@@ -6078,6 +5681,14 @@ async function resumeSessionRecordWithClient(client, options, cwd) {
6078
5681
  }
6079
5682
  async function createSessionWithClient(options) {
6080
5683
  assertControlAuthority({ signal: options.signal });
5684
+ if (options.resumeSessionId) {
5685
+ const resumedRecord = await readSessionRecord(options.resumeSessionId);
5686
+ assertControlAuthority({ signal: options.signal });
5687
+ if (resumedRecord && !resumedRecord.closed && resumedRecord.agentCommand === options.agentCommand) {
5688
+ await closeSession(resumedRecord.acpxRecordId);
5689
+ assertControlAuthority({ signal: options.signal });
5690
+ }
5691
+ }
6081
5692
  const client = new AcpClient({
6082
5693
  agentCommand: options.agentCommand,
6083
5694
  agentArgv: options.agentArgv,
@@ -6165,11 +5776,11 @@ async function listAgentSessions(options) {
6165
5776
  }
6166
5777
  async function ensureSession(options) {
6167
5778
  const cwd = absolutePath(options.cwd);
6168
- const ownership = await acquireSessionTurn(`ensure:${createHash("sha256").update(JSON.stringify([
6169
- options.agentCommand,
5779
+ const ownership = await acquireSessionScope({
5780
+ agentCommand: options.agentCommand,
6170
5781
  cwd,
6171
- normalizeName(options.name)
6172
- ])).digest("hex")}`, options.signal);
5782
+ name: normalizeName(options.name)
5783
+ }, options.signal);
6173
5784
  try {
6174
5785
  return await ensureSessionWithOwnership(options, cwd);
6175
5786
  } finally {
@@ -6949,7 +6560,12 @@ var LiveSessionCheckpoint = class {
6949
6560
  async flushDirty() {
6950
6561
  while (this.dirty) {
6951
6562
  this.dirty = false;
6952
- await this.save();
6563
+ try {
6564
+ await this.save();
6565
+ } catch (error) {
6566
+ this.dirty = true;
6567
+ throw error;
6568
+ }
6953
6569
  }
6954
6570
  }
6955
6571
  };
@@ -7003,8 +6619,8 @@ var QueueTaskOutputFormatter = class {
7003
6619
  flush() {}
7004
6620
  };
7005
6621
  function markOutputAlreadyEmitted(error, outputAlreadyEmitted) {
7006
- if (!outputAlreadyEmitted || !error || typeof error !== "object") return;
7007
- error.outputAlreadyEmitted = true;
6622
+ if (!error || typeof error !== "object") return;
6623
+ error.outputAlreadyEmitted = outputAlreadyEmitted;
7008
6624
  }
7009
6625
  async function runWithOptionalInterrupt(options) {
7010
6626
  if (options.handleProcessInterrupts === false) return await options.run();
@@ -7047,11 +6663,17 @@ var AcpErrorTracker = class {
7047
6663
  return this.latestInbound ?? this.outbound.findLast((acp) => outboundAcpErrorMatches(error, acp));
7048
6664
  }
7049
6665
  };
7050
- function toPromptResult(stopReason, sessionId, client, meta) {
6666
+ function toPromptResult(stopReason, sessionId, client, before, meta) {
6667
+ const after = client.getPermissionStats();
7051
6668
  return {
7052
6669
  stopReason,
7053
6670
  sessionId,
7054
- permissionStats: client.getPermissionStats(),
6671
+ permissionStats: {
6672
+ requested: after.requested - before.requested,
6673
+ approved: after.approved - before.approved,
6674
+ denied: after.denied - before.denied,
6675
+ cancelled: after.cancelled - before.cancelled
6676
+ },
7055
6677
  ...meta === void 0 ? {} : { _meta: meta }
7056
6678
  };
7057
6679
  }
@@ -7073,60 +6695,37 @@ async function applyPromptModelIfAdvertised(params) {
7073
6695
  });
7074
6696
  if (result.applied) params.record.acpx = applyModelSelection(params.record.acpx, requestedModel, result.response);
7075
6697
  }
7076
- function jsonRpcIdKey(value) {
6698
+ function jsonRpcIdKey(message) {
6699
+ if (!("id" in message)) return;
6700
+ const value = message.id;
7077
6701
  if (typeof value === "string") return `s:${value}`;
7078
6702
  if (typeof value === "number" && Number.isFinite(value)) return `n:${value}`;
7079
6703
  }
7080
- function extractJsonRpcRequestInfo(message) {
7081
- const candidate = message;
7082
- if (typeof candidate.method !== "string") return;
7083
- const idKey = jsonRpcIdKey(candidate.id);
7084
- if (!idKey) return;
7085
- return {
7086
- idKey,
7087
- method: candidate.method
7088
- };
7089
- }
7090
- function extractJsonRpcResponseInfo(message) {
7091
- const candidate = message;
7092
- const idKey = jsonRpcIdKey(candidate.id);
7093
- if (!idKey) return;
7094
- const hasError = Object.hasOwn(candidate, "error");
7095
- const hasResult = Object.hasOwn(candidate, "result");
7096
- if (!hasError && !hasResult) return;
7097
- return {
7098
- idKey,
7099
- hasError
7100
- };
7101
- }
7102
6704
  const SESSION_RECONNECT_METHODS = /* @__PURE__ */ new Set(["session/load", "session/resume"]);
7103
- function filterRecoverableLoadFallbackOutput(messages) {
7104
- const requestMethodById = /* @__PURE__ */ new Map();
7105
- const failedLoadRequestIds = /* @__PURE__ */ new Set();
7106
- for (const message of messages) {
7107
- const request = extractJsonRpcRequestInfo(message);
7108
- if (request) {
7109
- requestMethodById.set(request.idKey, request.method);
7110
- continue;
7111
- }
7112
- const response = extractJsonRpcResponseInfo(message);
7113
- if (!response || !response.hasError) continue;
7114
- const requestMethod = requestMethodById.get(response.idKey);
7115
- if (requestMethod && SESSION_RECONNECT_METHODS.has(requestMethod)) failedLoadRequestIds.add(response.idKey);
7116
- }
7117
- if (failedLoadRequestIds.size === 0) return messages;
7118
- return messages.filter((message) => {
7119
- const request = extractJsonRpcRequestInfo(message);
7120
- if (request && SESSION_RECONNECT_METHODS.has(request.method) && failedLoadRequestIds.has(request.idKey)) return false;
7121
- const response = extractJsonRpcResponseInfo(message);
7122
- if (response && failedLoadRequestIds.has(response.idKey)) return false;
7123
- return true;
7124
- });
6705
+ function isReconnectRequest(direction, message) {
6706
+ return direction === "outbound" && "method" in message && typeof message.method === "string" && SESSION_RECONNECT_METHODS.has(message.method);
6707
+ }
6708
+ function isInboundResponse(direction, message) {
6709
+ return direction === "inbound" && ("result" in message || "error" in message);
7125
6710
  }
7126
6711
  function filterBufferedConnectOutput(messages, loadError) {
7127
6712
  if (loadError == null) return messages;
7128
- const filteredMessages = new Set(filterRecoverableLoadFallbackOutput(messages.map(({ message }) => message)));
7129
- return messages.filter(({ message }) => filteredMessages.has(message));
6713
+ const pending = /* @__PURE__ */ new Map();
6714
+ const suppressed = /* @__PURE__ */ new Set();
6715
+ for (const [index, { direction, message }] of messages.entries()) {
6716
+ const id = jsonRpcIdKey(message);
6717
+ if (!id) continue;
6718
+ if (isReconnectRequest(direction, message)) pending.set(id, index);
6719
+ else if (isInboundResponse(direction, message)) {
6720
+ const requestIndex = pending.get(id);
6721
+ if (requestIndex !== void 0 && "error" in message) {
6722
+ suppressed.add(requestIndex);
6723
+ suppressed.add(index);
6724
+ }
6725
+ pending.delete(id);
6726
+ }
6727
+ }
6728
+ return messages.filter((_message, index) => !suppressed.has(index));
7130
6729
  }
7131
6730
  function emitPromptRetryNotice(params) {
7132
6731
  if (params.suppressSdkConsoleErrors) return;
@@ -7190,7 +6789,7 @@ function buildQueuedTaskRunOptions(sessionRecordId, task, options, outputFormatt
7190
6789
  suppressSdkConsoleErrors: task.suppressSdkConsoleErrors ?? options.suppressSdkConsoleErrors,
7191
6790
  verbose: options.verbose,
7192
6791
  promptRetries: task.promptRetries ?? options.promptRetries ?? 0,
7193
- sessionOptions: mergeSessionOptions(task.sessionOptions, options.sessionOptions),
6792
+ sessionOptions: task.sessionOptions,
7194
6793
  onClientAvailable: options.onClientAvailable,
7195
6794
  onClientClosed: options.onClientClosed,
7196
6795
  onClientCloseFailure: options.onClientCloseFailure,
@@ -7462,6 +7061,7 @@ async function runOwnedSessionPrompt(options) {
7462
7061
  verbose: options.verbose,
7463
7062
  sessionOptions
7464
7063
  });
7064
+ const permissionStatsBefore = client.getPermissionStats();
7465
7065
  client.updateRuntimeOptions({
7466
7066
  permissionMode: options.permissionMode,
7467
7067
  nonInteractivePermissions: options.nonInteractivePermissions,
@@ -7540,6 +7140,7 @@ async function runOwnedSessionPrompt(options) {
7540
7140
  client,
7541
7141
  record,
7542
7142
  resumePolicy: options.resumePolicy,
7143
+ sessionOptions: options.sessionOptions,
7543
7144
  replacingConfigOption: requestedModelId(options.sessionOptions?.model) ? { key: "model" } : void 0,
7544
7145
  timeoutMs: options.timeoutMs,
7545
7146
  verbose: options.verbose,
@@ -7617,7 +7218,7 @@ async function runOwnedSessionPrompt(options) {
7617
7218
  applyConversation(record, conversation);
7618
7219
  const propagated = error instanceof Error ? error : new Error(formatErrorMessage(error));
7619
7220
  attachAcpErrorPayload(propagated, normalizedError.acp);
7620
- propagated.outputAlreadyEmitted = matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted;
7221
+ markOutputAlreadyEmitted(propagated, matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted);
7621
7222
  propagated.normalizedOutputError = normalizedError;
7622
7223
  throw propagated;
7623
7224
  };
@@ -7670,7 +7271,7 @@ async function runOwnedSessionPrompt(options) {
7670
7271
  const response = await savePromptSuccess(await runPromptWithRetries(activeSessionId));
7671
7272
  promptTurnActive = false;
7672
7273
  return {
7673
- ...toPromptResult(response.stopReason, record.acpxRecordId, client, response._meta),
7274
+ ...toPromptResult(response.stopReason, record.acpxRecordId, client, permissionStatsBefore, response._meta),
7674
7275
  record,
7675
7276
  resumed,
7676
7277
  loadError
@@ -7790,6 +7391,7 @@ async function runOnce(options, control) {
7790
7391
  },
7791
7392
  sessionOptions: options.sessionOptions
7792
7393
  });
7394
+ const permissionStatsBefore = client.getPermissionStats();
7793
7395
  const closeOwnedClient = ownDirectClient(client, control?.signal);
7794
7396
  const runExecPromptAttempt = async (sessionId) => {
7795
7397
  assertControlAuthority(authority);
@@ -7835,17 +7437,17 @@ async function runOnce(options, control) {
7835
7437
  authority,
7836
7438
  onWarning: options.suppressSdkConsoleErrors ? void 0 : (message) => process.stderr.write(`[acpx] warning: ${message}\n`)
7837
7439
  });
7838
- if (modelApplication.response) controlState = applyConfigOptionsToState(controlState, modelApplication.response.configOptions);
7440
+ if (modelApplication.applied) controlState = applyModelSelection(controlState, modelApplication.modelId, modelApplication.response);
7839
7441
  for (const configOption of options.configOptions ?? []) {
7840
7442
  assertControlAuthority(authority);
7841
7443
  const response = await withTimeout(client.setSessionConfigOption(sessionId, configOption.configId, configOption.value, advertisedModelState(controlState), authority), options.timeoutMs);
7842
- controlState = applyConfigOptionsToState(controlState, response.configOptions);
7444
+ controlState = applyConfigOptionSelection(controlState, configOption.configId, configOption.value, response);
7843
7445
  }
7844
7446
  assertControlAuthority(authority);
7845
7447
  output.setContext({ sessionId });
7846
7448
  const response = await runExecPromptWithRetries(sessionId);
7847
7449
  promptTurnActive = false;
7848
- return toPromptResult(response.stopReason, sessionId, client, response._meta);
7450
+ return toPromptResult(response.stopReason, sessionId, client, permissionStatsBefore, response._meta);
7849
7451
  },
7850
7452
  handleInterrupt: async () => {
7851
7453
  await client.cancelActivePrompt(INTERRUPT_CANCEL_WAIT_MS);
@@ -8072,31 +7674,50 @@ function createLeaseHeartbeat(lease, shutdown) {
8072
7674
  };
8073
7675
  }
8074
7676
  async function runSessionQueueOwner(options) {
8075
- const lease = await tryAcquireQueueOwnerLease(options.sessionId, {
8076
- path: options.mcpConfigPath,
8077
- fingerprint: options.mcpConfigFingerprint
8078
- });
8079
- if (!lease) return;
8080
- let runtimeFailure;
8081
- try {
8082
- await runQueueOwnerRuntime(options, lease);
8083
- } catch (error) {
8084
- runtimeFailure = { error };
8085
- }
7677
+ const signals = [
7678
+ "SIGINT",
7679
+ "SIGTERM",
7680
+ "SIGHUP"
7681
+ ];
7682
+ let stopRequested = false;
7683
+ let requestRuntimeStop;
7684
+ const onSignal = () => {
7685
+ stopRequested = true;
7686
+ requestRuntimeStop?.();
7687
+ };
8086
7688
  try {
8087
- await releaseQueueOwnerLease(lease);
8088
- } catch (error) {
8089
- if (runtimeFailure) throw new AggregateError([runtimeFailure.error, error], "Queue owner shutdown failed", { cause: error });
8090
- throw error;
7689
+ for (const signal of signals) process.on(signal, onSignal);
7690
+ const lease = await tryAcquireQueueOwnerLease(options.sessionId, {
7691
+ path: options.mcpConfigPath,
7692
+ fingerprint: options.mcpConfigFingerprint
7693
+ });
7694
+ if (!lease) return;
7695
+ let runtimeFailure;
7696
+ try {
7697
+ await runQueueOwnerRuntime(options, lease, (requestStop) => {
7698
+ requestRuntimeStop = requestStop;
7699
+ if (stopRequested) requestStop();
7700
+ });
7701
+ } catch (error) {
7702
+ runtimeFailure = { error };
7703
+ }
7704
+ try {
7705
+ await releaseQueueOwnerLease(lease);
7706
+ } catch (error) {
7707
+ if (runtimeFailure) throw new AggregateError([runtimeFailure.error, error], "Queue owner shutdown failed", { cause: error });
7708
+ throw error;
7709
+ }
7710
+ if (runtimeFailure) throw runtimeFailure.error;
7711
+ } finally {
7712
+ for (const signal of signals) process.off(signal, onSignal);
8091
7713
  }
8092
- if (runtimeFailure) throw runtimeFailure.error;
8093
7714
  }
8094
7715
  async function keepOwnerForPendingControls(controls, shutdown) {
8095
7716
  if (shutdown.requested || !controls.hasPending) return false;
8096
7717
  await controls.drainAdmitted();
8097
7718
  return !shutdown.requested;
8098
7719
  }
8099
- async function runQueueOwnerRuntime(options, lease) {
7720
+ async function runQueueOwnerRuntime(options, lease, bindStopRequest) {
8100
7721
  const sessionRecord = await resolveSessionRecord(options.sessionId);
8101
7722
  let owner;
8102
7723
  let heartbeatTimer;
@@ -8170,13 +7791,8 @@ async function runQueueOwnerRuntime(options, lease) {
8170
7791
  sessionId: options.sessionId,
8171
7792
  verbose: options.verbose
8172
7793
  });
8173
- const onSignal = () => {
8174
- shutdown.request();
8175
- };
7794
+ bindStopRequest(shutdown.request);
8176
7795
  const heartbeat = createLeaseHeartbeat(lease, shutdown);
8177
- process.once("SIGINT", onSignal);
8178
- process.once("SIGTERM", onSignal);
8179
- process.once("SIGHUP", onSignal);
8180
7796
  try {
8181
7797
  owner = await SessionQueueOwner.start(lease, {
8182
7798
  cancelPrompt: async () => {
@@ -8258,9 +7874,6 @@ async function runQueueOwnerRuntime(options, lease) {
8258
7874
  }
8259
7875
  }
8260
7876
  } finally {
8261
- process.off("SIGINT", onSignal);
8262
- process.off("SIGTERM", onSignal);
8263
- process.off("SIGHUP", onSignal);
8264
7877
  await shutdown.shutdown();
8265
7878
  await lease.updates;
8266
7879
  }
@@ -8304,6 +7917,6 @@ async function sendSession(options) {
8304
7917
  }
8305
7918
  const queueOwnerRuntimeTestInternals = { queueOwnerExitIsFatal };
8306
7919
  //#endregion
8307
- export { isRequestedModelUnsupportedError as $, applyInitialModelSelection as A, reconcileAgentSessionId as B, setSessionModeOnOwner as C, connectAndLoadSession as D, withConnectedSession as E, persistSessionOptions as F, recordPromptSubmission as G, cloneSessionConversation as H, sessionOptionsFromRecord as I, advertisedModelState as J, recordSessionUpdate as K, applyConversation as L, applyRequestedModelIfAdvertised as M, setDesiredModeId as N, applyConfigOptionSelection as O, normalizeSessionOptionShape as P, RequestedModelUnsupportedError as Q, applyLifecycleSnapshotToRecord as R, setSessionMode as S, setSessionModelOnOwner as T, createSessionConversation as U, cloneSessionAcpxState as V, recordClientOperation as W, REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE as X, AcpClient as Y, REQUESTED_MODEL_UNSUPPORTED_REASONS as Z, cancelSessionPrompt as _, runQueuedTask as a, isLegacyZedCodexAcpInvocation as at, setSessionConfigOption as b, LiveSessionCheckpoint as c, assertControlAuthority as ct, flushPerfMetricsCapture as d, exitCodeForOutputErrorCode as dt, getAcpxVersion as et, installPerfMetricsCapture as f, normalizeOutputError as ft, listAgentSessions as g, ensureSession as h, runOnce as i, listSessionEvents as it, applyModelSelection as j, applyConfigOptionsToRecord as k, runPromptTurn as l, withInterrupt as lt, createSessionWithClient as m, runSessionQueueOwner as n, DEFAULT_QUEUE_OWNER_TTL_MS as nt, sendSessionDirect as o, InterruptedError as ot, createSession as p, trimConversationForRuntime as q, sendSession as r, normalizeQueueOwnerTtlMs as rt, DISCARD_OUTPUT_FORMATTER as s, TimeoutError as st, queueOwnerRuntimeTestInternals as t, permissionModeSatisfies as tt, buildQueueOwnerArgOverride as u, withTimeout as ut, closeSession as v, setSessionModel as w, setSessionConfigOptionOnOwner as x, sessionControlTestInternals as y, createInitialSessionRecord as z };
7920
+ export { isRequestedModelUnsupportedError as $, applyInitialModelSelection as A, reconcileAgentSessionId as B, setSessionModeOnOwner as C, connectAndLoadSession as D, withConnectedSession as E, persistSessionOptions as F, recordPromptSubmission as G, cloneSessionConversation as H, sessionOptionsFromRecord as I, advertisedModelState as J, recordSessionUpdate as K, applyConversation as L, applyRequestedModelIfAdvertised as M, setDesiredModeId as N, applyConfigOptionSelection as O, normalizeSessionOptionShape as P, RequestedModelUnsupportedError as Q, applyLifecycleSnapshotToRecord as R, setSessionMode as S, setSessionModelOnOwner as T, createSessionConversation as U, cloneSessionAcpxState as V, recordClientOperation as W, REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE as X, AcpClient as Y, REQUESTED_MODEL_UNSUPPORTED_REASONS as Z, cancelSessionPrompt as _, runQueuedTask as a, acquireSessionScope as at, setSessionConfigOption as b, LiveSessionCheckpoint as c, InterruptedError as ct, flushPerfMetricsCapture as d, withInterrupt as dt, getAcpxVersion as et, installPerfMetricsCapture as f, withTimeout as ft, listAgentSessions as g, ensureSession as h, runOnce as i, acquireSessionImport as it, applyModelSelection as j, applyConfigOptionsToRecord as k, runPromptTurn as l, TimeoutError as lt, createSessionWithClient as m, runSessionQueueOwner as n, DEFAULT_QUEUE_OWNER_TTL_MS as nt, sendSessionDirect as o, listSessionEvents as ot, createSession as p, trimConversationForRuntime as q, sendSession as r, normalizeQueueOwnerTtlMs as rt, DISCARD_OUTPUT_FORMATTER as s, isLegacyZedCodexAcpInvocation as st, queueOwnerRuntimeTestInternals as t, permissionModeSatisfies as tt, buildQueueOwnerArgOverride as u, assertControlAuthority as ut, closeSession as v, setSessionModel as w, setSessionConfigOptionOnOwner as x, sessionControlTestInternals as y, createInitialSessionRecord as z };
8308
7921
 
8309
- //# sourceMappingURL=queue-owner-runtime-C4OOosbo.js.map
7922
+ //# sourceMappingURL=queue-owner-runtime-Dhr4gxNE.js.map