acpx 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/{agent-registry-DdKB-zia.js → agent-registry-Ct2yWPW7.js} +3 -2
  2. package/dist/agent-registry-Ct2yWPW7.js.map +1 -0
  3. package/dist/agent-registry.js +1 -1
  4. package/dist/{cli-8mVHeUXh.js → cli-Bmki9cj5.js} +5 -5
  5. package/dist/{cli-8mVHeUXh.js.map → cli-Bmki9cj5.js.map} +1 -1
  6. package/dist/cli.js +55 -47
  7. package/dist/cli.js.map +1 -1
  8. package/dist/{client-DeNKZZcu.d.ts → client-DzaUKazo.d.ts} +10 -4
  9. package/dist/client-DzaUKazo.d.ts.map +1 -0
  10. package/dist/{flows-WOQNzCMC.js → flows--Ewxn_fQ.js} +665 -460
  11. package/dist/flows--Ewxn_fQ.js.map +1 -0
  12. package/dist/flows.d.ts +19 -7
  13. package/dist/flows.d.ts.map +1 -1
  14. package/dist/flows.js +1 -1
  15. package/dist/{invocation-options-B6d7TI-y.js → invocation-options-DRzxHZmr.js} +32 -11
  16. package/dist/invocation-options-DRzxHZmr.js.map +1 -0
  17. package/dist/{ipc-BJrvfNXr.js → ipc-fMgj5vrO.js} +540 -241
  18. package/dist/ipc-fMgj5vrO.js.map +1 -0
  19. package/dist/{output-CL18YmsG.js → output-D6kDs8BN.js} +24 -11
  20. package/dist/output-D6kDs8BN.js.map +1 -0
  21. package/dist/{queue-owner-runtime-Cr0dWJuL.js → queue-owner-runtime-CLwjMYNL.js} +549 -337
  22. package/dist/queue-owner-runtime-CLwjMYNL.js.map +1 -0
  23. package/dist/runtime.d.ts +19 -1
  24. package/dist/runtime.d.ts.map +1 -1
  25. package/dist/runtime.js +47 -28
  26. package/dist/runtime.js.map +1 -1
  27. package/dist/session-options-Dp6mzNSy.d.ts.map +1 -1
  28. package/dist/{session-watch-B8Q_TsGf.js → session-watch-BfdIo-n4.js} +5 -5
  29. package/dist/session-watch-BfdIo-n4.js.map +1 -0
  30. package/dist/{watch-Dp73ph8h.js → watch-DH-zXQH2.js} +2 -2
  31. package/dist/{watch-Dp73ph8h.js.map → watch-DH-zXQH2.js.map} +1 -1
  32. package/package.json +1 -1
  33. package/skills/acpx/SKILL.md +43 -23
  34. package/dist/agent-registry-DdKB-zia.js.map +0 -1
  35. package/dist/client-DeNKZZcu.d.ts.map +0 -1
  36. package/dist/flows-WOQNzCMC.js.map +0 -1
  37. package/dist/invocation-options-B6d7TI-y.js.map +0 -1
  38. package/dist/ipc-BJrvfNXr.js.map +0 -1
  39. package/dist/output-CL18YmsG.js.map +0 -1
  40. package/dist/queue-owner-runtime-Cr0dWJuL.js.map +0 -1
  41. package/dist/session-watch-B8Q_TsGf.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { $ as setPerfGauge, A as isoNow$1, At as AcpxOperationalError, Bt as QueueConnectionError, C as promptToDisplayText, D as findGitRepositoryRoot, E as absolutePath, Ft as ClaudeAcpSessionCreateTimeoutError, Gt as SessionResumeRequiredError, H as normalizeAgentSessionId, Ht as SessionModeReplayError, I as resolveSessionRecord, It as CopilotAcpUnsupportedError, J as getPerfMetricsSnapshot, Kt as UnsupportedPromptContentError, L as writeSessionRecord, Lt as GeminiAcpStartupTimeoutError, Mt as AgentSpawnError, N as normalizeName, Nt as AgentStartupError, Pt as AuthPolicyError, Q as resetPerfMetrics, Rt as PermissionDeniedError, St as EXIT_CODES, Tt as OUTPUT_ERROR_ORIGINS, U as SESSION_JOURNAL_SCHEMA, Ut as SessionModelReplayError, V as extractAgentSessionId, Vt as SessionConfigOptionReplayError, W as SessionJournalReader, X as measurePerf, Y as incrementPerfCounter, Z as recordPerfDuration, _ as waitMs$1, _t as isProcessDefinitelyDead, a as trySetModelOnRunningOwner, b as getUnsupportedPromptContentMessage, bt as isAcpResourceNotFoundError, c as queueRequestExceedsLimit, dt as sessionEventActivePath, et as startPerfTimer, f as refreshQueueOwnerLease, ft as sessionEventLockPath, g as tryAcquireQueueOwnerLease, gt as isProcessAlive, h as terminateQueueOwnerForSession, i as trySetModeOnRunningOwner, it as isSessionUpdateNotification, jt as AgentDisconnectedError, k as findSessionByDirectoryWalk, l as parseQueueRequest, lt as defaultSessionEventLog, m as terminateProcess, n as tryCloseSessionOnRunningOwner, nt as isAcpJsonRpcMessage, o as trySubmitToRunningOwner, p as releaseQueueOwnerLease, pt as sessionEventSegmentPath, q as formatPerfMetric, r as trySetConfigOptionOnRunningOwner, rt as isAcpMessageObject, s as queueRequestByteLimit, t as tryCancelOnRunningOwner, ut as sessionBaseDir, v as QueueLeaseGuardSettlementError, vt as extractAcpError, w as textPrompt, wt as OUTPUT_ERROR_CODES, yt as formatUnknownErrorMessage, zt as PermissionPromptUnavailableError } from "./ipc-BJrvfNXr.js";
2
- import { A as resolveAgentCommandParts, C as asAbsoluteCwd, E as isoNow$2, F as waitForSpawn, M as runTimedExecFile, N as splitCommandLine, P as waitForChildExit, S as PROCESS_HELPER_TIMEOUT_MS, T as isChildProcessRunning, _ as buildSpawnCommandOptions, b as readWindowsEnvValue, f as resolveBuiltInAgentLaunch, g as buildAgentSpawnCommand, j as resolveAgentSessionCwd, k as requireAgentStdio, v as buildTerminalShellSpawnCommand, w as basenameToken, x as resolveWindowsExecutablePath, y as buildTerminalSpawnCommand } from "./agent-registry-DdKB-zia.js";
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 readProcessTable, xt as formatUnknownErrorMessage, y as createLockOwner, yt as isProcessAlive, z as resolveSessionRecord, zt as GeminiAcpStartupTimeoutError } from "./ipc-fMgj5vrO.js";
2
+ import { A as resolveAgentCommandParts, C as asAbsoluteCwd, E as isoNow$2, F as waitForSpawn, M as runTimedExecFile, N as splitCommandLine, P as waitForChildExit, S as PROCESS_HELPER_TIMEOUT_MS, T as isChildProcessRunning, _ as buildSpawnCommandOptions, b as readWindowsEnvValue, f as resolveBuiltInAgentLaunch, g as buildAgentSpawnCommand, j as resolveAgentSessionCwd, k as requireAgentStdio, v as buildTerminalShellSpawnCommand, w as basenameToken, x as resolveWindowsExecutablePath, y as buildTerminalSpawnCommand } from "./agent-registry-Ct2yWPW7.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";
@@ -485,6 +485,17 @@ function normalizeQueueOwnerTtlMs(ttlMs) {
485
485
  return Math.round(ttlMs);
486
486
  }
487
487
  //#endregion
488
+ //#region src/file-read-window.ts
489
+ function sliceReadWindow(content, line, limit) {
490
+ if (line == null && limit == null) return content;
491
+ const lines = content.split("\n");
492
+ const startIndex = Math.max(0, (line == null ? 1 : Math.max(1, Math.trunc(line))) - 1);
493
+ const maxLines = limit == null ? void 0 : Math.max(0, Math.trunc(limit));
494
+ if (maxLines === 0) return "";
495
+ const endIndex = maxLines == null ? lines.length : Math.min(lines.length, startIndex + maxLines);
496
+ return lines.slice(startIndex, endIndex).join("\n");
497
+ }
498
+ //#endregion
488
499
  //#region src/permission-prompt.ts
489
500
  let promptQueue = Promise.resolve();
490
501
  async function promptForPermission(options) {
@@ -598,7 +609,7 @@ var FileSystemHandlers = class {
598
609
  if (this.permissionMode === "deny-all") throw new PermissionDeniedError("Permission denied for fs/read_text_file (--deny-all)");
599
610
  const content = await (await this.getWorkspace()).readText(filePath);
600
611
  assertControlAuthority(authority);
601
- const sliced = this.sliceContent(content, params.line, params.limit);
612
+ const sliced = sliceReadWindow(content, params.line, params.limit);
602
613
  this.emitOperation({
603
614
  method: "fs/read_text_file",
604
615
  status: "completed",
@@ -686,15 +697,6 @@ var FileSystemHandlers = class {
686
697
  maxBytes: Infinity
687
698
  });
688
699
  }
689
- sliceContent(content, line, limit) {
690
- if (line == null && limit == null) return content;
691
- const lines = content.split("\n");
692
- const startIndex = Math.max(0, (line == null ? 1 : Math.max(1, Math.trunc(line))) - 1);
693
- const maxLines = limit == null ? void 0 : Math.max(0, Math.trunc(limit));
694
- if (maxLines === 0) return "";
695
- const endIndex = maxLines == null ? lines.length : Math.min(lines.length, startIndex + maxLines);
696
- return lines.slice(startIndex, endIndex).join("\n");
697
- }
698
700
  readWindowDetails(line, limit) {
699
701
  if (line == null && limit == null) return;
700
702
  return `line=${line == null ? 1 : Math.max(1, Math.trunc(line))}, limit=${limit == null ? "all" : Math.max(0, Math.trunc(limit))}`;
@@ -1556,15 +1558,22 @@ function enqueueNdJsonLine(agentCommand, line, controller) {
1556
1558
  console.error("Failed to parse JSON message:", trimmedLine, err);
1557
1559
  }
1558
1560
  }
1559
- function enqueueNdJsonLines(agentCommand, lines, controller) {
1561
+ function enqueueNdJsonChunk(agentCommand, chunk, fragments, controller) {
1562
+ const lines = chunk.split("\n");
1563
+ const suffix = lines.pop() || "";
1564
+ if (lines.length > 0 && fragments.length > 0) {
1565
+ lines[0] = fragments.join("") + lines[0];
1566
+ fragments.length = 0;
1567
+ }
1560
1568
  for (const line of lines) enqueueNdJsonLine(agentCommand, line, controller);
1569
+ if (suffix) fragments.push(suffix);
1561
1570
  }
1562
1571
  function createNdJsonMessageStream(agentCommand, output, input, maxMessageBytes, onReadError) {
1563
1572
  const textEncoder = new TextEncoder();
1564
1573
  const textDecoder = new TextDecoder();
1565
1574
  return {
1566
1575
  readable: new ReadableStream({ async start(controller) {
1567
- let content = "";
1576
+ const fragments = [];
1568
1577
  let retainedBytes = 0;
1569
1578
  const reader = input.getReader();
1570
1579
  try {
@@ -1572,10 +1581,7 @@ function createNdJsonMessageStream(agentCommand, output, input, maxMessageBytes,
1572
1581
  const { value, done } = await reader.read();
1573
1582
  if (done) break;
1574
1583
  if (maxMessageBytes !== void 0) retainedBytes = countLineBytes(value, retainedBytes, maxMessageBytes);
1575
- content += textDecoder.decode(value, { stream: true });
1576
- const lines = content.split("\n");
1577
- content = lines.pop() || "";
1578
- enqueueNdJsonLines(agentCommand, lines, controller);
1584
+ enqueueNdJsonChunk(agentCommand, textDecoder.decode(value, { stream: true }), fragments, controller);
1579
1585
  }
1580
1586
  controller.close();
1581
1587
  } catch (err) {
@@ -1667,65 +1673,88 @@ function promptRequestOwner(message) {
1667
1673
  }
1668
1674
  //#endregion
1669
1675
  //#region src/acp/process-descendants.ts
1670
- function parseProcessTable(output) {
1671
- const table = /* @__PURE__ */ new Map();
1672
- for (const line of output.split("\n")) {
1673
- const match = /^\s*(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(line);
1674
- if (match && !match[3].startsWith("Z")) {
1675
- const pid = Number(match[1]);
1676
- if (Number.isSafeInteger(pid) && pid > 1 && pid !== process.pid) table.set(pid, {
1677
- pid,
1678
- parentPid: Number(match[2]),
1679
- birth: match[4]
1680
- });
1681
- }
1682
- }
1683
- return table;
1684
- }
1685
1676
  function includeDescendants(table, owned) {
1686
1677
  let expanded;
1687
1678
  do {
1688
1679
  expanded = false;
1689
- for (const identity of table.values()) if (owned.has(identity.parentPid) && !owned.has(identity.pid)) {
1690
- owned.add(identity.pid);
1691
- expanded = true;
1680
+ for (const identity of table.values()) {
1681
+ const parent = table.get(identity.parentPid);
1682
+ if (process.platform === "win32" && (!parent || parent.birth > identity.birth)) continue;
1683
+ if (owned.has(identity.parentPid) && !owned.has(identity.pid)) {
1684
+ owned.add(identity.pid);
1685
+ expanded = true;
1686
+ }
1692
1687
  }
1693
1688
  } while (expanded);
1694
1689
  }
1695
- /** Best-effort POSIX cleanup for descendants witnessed during this child launch. */
1690
+ /** Best-effort cleanup for descendants witnessed during this child launch. */
1696
1691
  var ProcessDescendants = class {
1697
1692
  child;
1698
1693
  identities = /* @__PURE__ */ new Map();
1694
+ rootBirth;
1699
1695
  pending;
1700
1696
  retired = false;
1701
- constructor(child) {
1697
+ ownProcessGroup;
1698
+ captureGroupAfterExit;
1699
+ groupExitedAt;
1700
+ onRootExit = () => {
1701
+ this.groupExitedAt = Date.now();
1702
+ };
1703
+ constructor(child, options = {}) {
1702
1704
  this.child = child;
1705
+ this.ownProcessGroup = process.platform !== "win32" && Boolean(options.ownProcessGroup);
1706
+ this.captureGroupAfterExit = this.ownProcessGroup && isChildProcessRunning(child);
1707
+ if (this.ownProcessGroup) child.once("exit", this.onRootExit);
1703
1708
  }
1704
1709
  capture(timeoutMs = PROCESS_HELPER_TIMEOUT_MS) {
1705
- if (this.retired || process.platform === "win32") return Promise.resolve(true);
1710
+ if (this.retired) return Promise.resolve(true);
1706
1711
  this.pending ??= this.readSnapshot(timeoutMs).finally(() => {
1707
1712
  this.pending = void 0;
1708
1713
  });
1709
1714
  return this.pending;
1710
1715
  }
1711
1716
  async readSnapshot(timeoutMs) {
1717
+ const deadline = performance.now() + timeoutMs;
1718
+ const rootWasRunning = isChildProcessRunning(this.child);
1719
+ const captureRootGroup = rootWasRunning || this.captureGroupAfterExit;
1720
+ if (!rootWasRunning) this.captureGroupAfterExit = false;
1712
1721
  try {
1713
- const output = await runTimedExecFile("ps", ["-eo", "pid=,ppid=,stat=,lstart="], { timeoutMs });
1714
- if (!this.retired) this.refresh(parseProcessTable(output));
1722
+ const table = await readProcessTable(timeoutMs);
1723
+ if (!this.retired) {
1724
+ table.delete(1);
1725
+ table.delete(process.pid);
1726
+ this.refresh(table, captureRootGroup);
1727
+ if (this.captureGroupAfterExit && !isChildProcessRunning(this.child)) return await this.readSnapshot(Math.max(1, deadline - performance.now()));
1728
+ }
1715
1729
  return true;
1716
1730
  } catch {
1731
+ if (!isChildProcessRunning(this.child)) this.captureGroupAfterExit = false;
1717
1732
  return false;
1718
1733
  }
1719
1734
  }
1720
- refresh(table) {
1735
+ refresh(table, rootWasRunning) {
1721
1736
  const owned = /* @__PURE__ */ new Set();
1722
1737
  for (const [pid, identity] of this.identities) if (table.get(pid)?.birth === identity.birth) owned.add(pid);
1723
- const root = this.child.pid;
1724
- if (root && isChildProcessRunning(this.child)) owned.add(root);
1738
+ this.includeRoot(table, owned);
1739
+ if (this.ownProcessGroup) this.includeProcessGroup(table, owned, rootWasRunning);
1725
1740
  includeDescendants(table, owned);
1726
- owned.delete(root ?? 0);
1741
+ owned.delete(this.child.pid ?? 0);
1727
1742
  this.identities = new Map([...table].filter(([pid]) => owned.has(pid)));
1728
1743
  }
1744
+ includeRoot(table, owned) {
1745
+ const root = this.child.pid;
1746
+ const rootIdentity = root && table.get(root);
1747
+ if (rootIdentity && isChildProcessRunning(this.child)) {
1748
+ this.rootBirth ??= rootIdentity.birth;
1749
+ if (rootIdentity.birth === this.rootBirth) owned.add(rootIdentity.pid);
1750
+ }
1751
+ }
1752
+ includeProcessGroup(table, owned, rootWasRunning) {
1753
+ const root = this.child.pid;
1754
+ if (root && (rootWasRunning || [...owned].some((pid) => table.get(pid)?.groupPid === root))) {
1755
+ for (const identity of table.values()) if (identity.groupPid === root && (this.groupExitedAt === void 0 || Date.parse(identity.birth) <= this.groupExitedAt)) owned.add(identity.pid);
1756
+ }
1757
+ }
1729
1758
  async signal(signal, timeoutMs) {
1730
1759
  if (!await this.capture(timeoutMs) || this.retired) return;
1731
1760
  for (const pid of this.identities.keys()) try {
@@ -1734,6 +1763,9 @@ var ProcessDescendants = class {
1734
1763
  this.identities.delete(pid);
1735
1764
  }
1736
1765
  }
1766
+ hasTrackedProcesses() {
1767
+ return this.identities.size > 0;
1768
+ }
1737
1769
  async waitForExit(timeoutMs) {
1738
1770
  const deadline = Date.now() + timeoutMs;
1739
1771
  do {
@@ -1745,6 +1777,7 @@ var ProcessDescendants = class {
1745
1777
  }
1746
1778
  retire() {
1747
1779
  this.retired = true;
1780
+ this.child.off("exit", this.onRootExit);
1748
1781
  this.identities.clear();
1749
1782
  }
1750
1783
  };
@@ -1812,12 +1845,20 @@ function readTerminalOutputCeiling() {
1812
1845
  if (!/^\d+$/.test(raw) || !Number.isSafeInteger(bytes)) throw new Error("ACPX_TERMINAL_MAX_OUTPUT_BYTES must be a non-negative safe integer; zero disables the host ceiling");
1813
1846
  return bytes === 0 ? void 0 : bytes;
1814
1847
  }
1848
+ function resolveTerminalOutputLimit(requested, ceiling) {
1849
+ return Math.min(Math.max(0, Math.round(requested ?? DEFAULT_TERMINAL_OUTPUT_LIMIT_BYTES)), ceiling ?? Number.POSITIVE_INFINITY);
1850
+ }
1851
+ function tracksTerminalDescendants(terminal) {
1852
+ return terminal.killProcessGroup || terminal.descendants !== void 0;
1853
+ }
1854
+ function createTerminalDescendants(proc, killProcessGroup) {
1855
+ return process.platform === "win32" && killProcessGroup ? void 0 : new ProcessDescendants(proc, { ownProcessGroup: true });
1856
+ }
1815
1857
  function trimToUtf8Boundary(buffer, limit) {
1816
1858
  if (limit <= 0) return Buffer.alloc(0);
1817
- if (buffer.length <= limit) return buffer;
1818
- let start = buffer.length - limit;
1859
+ let start = Math.max(0, buffer.length - limit);
1819
1860
  while (start < buffer.length && (buffer[start] & 192) === 128) start += 1;
1820
- if (start >= buffer.length) start = buffer.length - limit;
1861
+ if (start >= buffer.length) return Buffer.alloc(0);
1821
1862
  return buffer.subarray(start);
1822
1863
  }
1823
1864
  async function defaultConfirmExecute(commandLine, signal) {
@@ -1854,7 +1895,8 @@ var TerminalManager = class {
1854
1895
  this.onOperation = options.onOperation;
1855
1896
  this.usesDefaultConfirmExecute = options.confirmExecute == null;
1856
1897
  this.confirmExecute = options.confirmExecute ?? defaultConfirmExecute;
1857
- this.killGraceMs = Math.max(0, Math.round(options.killGraceMs ?? DEFAULT_KILL_GRACE_MS));
1898
+ const killGraceMs = Math.max(0, Math.round(options.killGraceMs ?? DEFAULT_KILL_GRACE_MS));
1899
+ this.killGraceMs = Number.isFinite(killGraceMs) && killGraceMs <= 2147483647 ? killGraceMs : 1;
1858
1900
  this.processHelperTimeoutMs = Math.max(1, Math.round(options.processHelperTimeoutMs ?? 8e3));
1859
1901
  }
1860
1902
  updatePermissionPolicy(permissionMode, nonInteractivePermissions) {
@@ -1875,8 +1917,7 @@ var TerminalManager = class {
1875
1917
  const approved = await this.isExecuteApproved(commandLine, authority?.signal);
1876
1918
  assertControlAuthority(authority);
1877
1919
  if (!approved) throw new PermissionDeniedError("Permission denied for terminal/create");
1878
- const requestedLimit = Math.max(0, Math.round(params.outputByteLimit ?? DEFAULT_TERMINAL_OUTPUT_LIMIT_BYTES));
1879
- const outputByteLimit = Math.min(requestedLimit, this.outputCeilingBytes ?? Number.POSITIVE_INFINITY);
1920
+ const outputByteLimit = resolveTerminalOutputLimit(params.outputByteLimit, this.outputCeilingBytes);
1880
1921
  const { proc, spawnCommand } = await spawnTerminalProcess(params, this.cwd, authority);
1881
1922
  let resolveExit = () => {};
1882
1923
  const exitPromise = new Promise((resolve) => {
@@ -1886,6 +1927,7 @@ var TerminalManager = class {
1886
1927
  process: proc,
1887
1928
  killProcessGroup: spawnCommand.killProcessGroup,
1888
1929
  descendantPids: /* @__PURE__ */ new Set(),
1930
+ descendants: createTerminalDescendants(proc, spawnCommand.killProcessGroup),
1889
1931
  processHelperTimeoutMs: this.processHelperTimeoutMs,
1890
1932
  output: Buffer.alloc(0),
1891
1933
  truncated: false,
@@ -1899,10 +1941,8 @@ var TerminalManager = class {
1899
1941
  const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1900
1942
  if (bytes.length === 0) return;
1901
1943
  terminal.output = Buffer.concat([terminal.output, bytes]);
1902
- if (terminal.output.length > terminal.outputByteLimit) {
1903
- terminal.output = trimToUtf8Boundary(terminal.output, terminal.outputByteLimit);
1904
- terminal.truncated = true;
1905
- }
1944
+ terminal.truncated ||= terminal.output.length > terminal.outputByteLimit;
1945
+ if (terminal.truncated) terminal.output = trimToUtf8Boundary(terminal.output, terminal.outputByteLimit);
1906
1946
  };
1907
1947
  proc.stdout.on("data", appendOutput);
1908
1948
  proc.stderr.on("data", appendOutput);
@@ -1911,9 +1951,10 @@ var TerminalManager = class {
1911
1951
  proc.once("exit", (exitCode, signal) => {
1912
1952
  terminal.exitCode = exitCode;
1913
1953
  terminal.signal = signal;
1914
- terminal.processGroupSnapshotPromise = rememberProcessGroupPids(terminal);
1954
+ terminal.processGroupSnapshotPromise = terminal.descendants ? terminal.descendants.capture(terminal.processHelperTimeoutMs).then(() => {}) : rememberProcessGroupPids(terminal);
1915
1955
  (async () => {
1916
1956
  await terminal.processGroupSnapshotPromise;
1957
+ terminal.processGroupSnapshotPromise = void 0;
1917
1958
  terminal.resolveExit({
1918
1959
  exitCode: exitCode ?? null,
1919
1960
  signal: signal ?? null
@@ -1923,6 +1964,7 @@ var TerminalManager = class {
1923
1964
  const terminalId = randomUUID();
1924
1965
  this.terminals.set(terminalId, terminal);
1925
1966
  try {
1967
+ await terminal.descendants?.capture(terminal.processHelperTimeoutMs);
1926
1968
  assertControlAuthority(authority);
1927
1969
  } catch (error) {
1928
1970
  await this.releaseTerminal({
@@ -2036,6 +2078,9 @@ var TerminalManager = class {
2036
2078
  try {
2037
2079
  await this.killProcess(terminal);
2038
2080
  await terminal.exitPromise.catch(() => {});
2081
+ terminal.descendants?.retire();
2082
+ terminal.process.stdout.destroy();
2083
+ terminal.process.stderr.destroy();
2039
2084
  terminal.output = Buffer.alloc(0);
2040
2085
  this.terminals.delete(params.terminalId);
2041
2086
  this.emitOperation({
@@ -2058,10 +2103,13 @@ var TerminalManager = class {
2058
2103
  }
2059
2104
  }
2060
2105
  async shutdown() {
2061
- for (const terminalId of Array.from(this.terminals.keys())) await this.releaseTerminal({
2106
+ const results = await Promise.allSettled(Array.from(this.terminals.keys(), (terminalId) => this.releaseTerminal({
2062
2107
  terminalId,
2063
2108
  sessionId: "shutdown"
2064
- });
2109
+ })));
2110
+ const failures = [];
2111
+ for (const result of results) if (result.status === "rejected") failures.push(result.reason);
2112
+ if (failures.length > 0) throw new AggregateError(failures, "Terminal shutdown failed", { cause: failures[0] });
2065
2113
  }
2066
2114
  getTerminal(terminalId) {
2067
2115
  return this.terminals.get(terminalId);
@@ -2079,13 +2127,13 @@ var TerminalManager = class {
2079
2127
  return terminal.exitCode === void 0 && terminal.signal === void 0;
2080
2128
  }
2081
2129
  async killProcess(terminal) {
2082
- if (!this.isRunning(terminal) && !terminal.killProcessGroup) return;
2130
+ if (!this.isRunning(terminal) && !tracksTerminalDescendants(terminal)) return;
2083
2131
  try {
2084
2132
  await this.signalProcess(terminal, "SIGTERM");
2085
2133
  } catch {
2086
2134
  return;
2087
2135
  }
2088
- if (await this.waitForCleanupAfterSignal(terminal) && !terminal.killProcessGroup) return;
2136
+ if (await this.waitForCleanupAfterSignal(terminal) && (!terminal.killProcessGroup || terminal.descendants)) return;
2089
2137
  try {
2090
2138
  await this.signalProcess(terminal, "SIGKILL");
2091
2139
  } catch {
@@ -2094,15 +2142,16 @@ var TerminalManager = class {
2094
2142
  await this.waitForFinalCleanup(terminal);
2095
2143
  }
2096
2144
  async signalProcess(terminal, signal) {
2145
+ if (terminal.descendants) {
2146
+ await terminal.descendants.signal(signal, terminal.processHelperTimeoutMs);
2147
+ if (this.isRunning(terminal)) terminal.process.kill(signal);
2148
+ return;
2149
+ }
2097
2150
  const pid = terminal.process.pid;
2098
2151
  if (terminal.killProcessGroup && pid && process.platform === "win32") {
2099
2152
  await this.signalWindowsProcessGroup(terminal, pid, signal);
2100
2153
  return;
2101
2154
  }
2102
- if (terminal.killProcessGroup && pid) {
2103
- await this.signalPosixProcessGroup(terminal, pid, signal);
2104
- return;
2105
- }
2106
2155
  terminal.process.kill(signal);
2107
2156
  }
2108
2157
  async signalWindowsProcessGroup(terminal, pid, signal) {
@@ -2113,14 +2162,6 @@ var TerminalManager = class {
2113
2162
  }
2114
2163
  for (const descendantPid of terminal.descendantPids) await killWindowsProcessTree(descendantPid, signal, terminal.processHelperTimeoutMs);
2115
2164
  }
2116
- async signalPosixProcessGroup(terminal, pid, signal) {
2117
- await this.captureDescendantPids(terminal, pid);
2118
- if (hasLiveProcessGroup(pid)) {
2119
- sendSignal(-pid, signal);
2120
- return;
2121
- }
2122
- for (const descendantPid of terminal.descendantPids) sendSignal(descendantPid, signal);
2123
- }
2124
2165
  async captureDescendantPids(terminal, pid) {
2125
2166
  if (!this.isRunning(terminal)) await terminal.processGroupSnapshotPromise?.catch(() => {});
2126
2167
  for (const descendantPid of await listDescendantPids(pid, terminal.processHelperTimeoutMs)) terminal.descendantPids.add(descendantPid);
@@ -2129,12 +2170,13 @@ var TerminalManager = class {
2129
2170
  if (!await this.waitForCleanupAfterSignal(terminal) && process.platform === "win32") throw new Error("Terminal process cleanup did not finish after SIGKILL");
2130
2171
  }
2131
2172
  async waitForCleanupAfterSignal(terminal) {
2132
- return await Promise.race([this.waitForTerminalAndTrackedDescendants(terminal).then(() => true), waitMs(this.killGraceMs).then(() => false)]);
2133
- }
2134
- async waitForTerminalAndTrackedDescendants(terminal) {
2135
- await terminal.exitPromise;
2136
- while (hasLiveTerminalProcessGroup(terminal)) await waitMs(25);
2137
- while (hasLivePid(terminal.descendantPids)) await waitMs(25);
2173
+ const deadline = performance.now() + this.killGraceMs;
2174
+ while (this.isRunning(terminal) || terminal.processGroupSnapshotPromise || await hasLiveTerminalDescendants(terminal, deadline - performance.now()) || hasLivePid(terminal.descendantPids)) {
2175
+ const remaining = deadline - performance.now();
2176
+ if (remaining <= 0) return false;
2177
+ await waitMs(Math.min(25, remaining));
2178
+ }
2179
+ return true;
2138
2180
  }
2139
2181
  };
2140
2182
  async function spawnTerminalProcess(params, defaultCwd, authority) {
@@ -2155,7 +2197,7 @@ async function spawnTerminalProcess(params, defaultCwd, authority) {
2155
2197
  }
2156
2198
  async function spawnAndWait(spawnCommand, params, defaultCwd, authority) {
2157
2199
  const spawnOptions = buildTerminalSpawnOptions(spawnCommand.command, params.cwd ?? defaultCwd, params.env);
2158
- if (spawnCommand.killProcessGroup) spawnOptions.detached = true;
2200
+ if (spawnCommand.killProcessGroup || process.platform !== "win32") spawnOptions.detached = true;
2159
2201
  assertControlAuthority(authority);
2160
2202
  const proc = spawn(spawnCommand.command, spawnCommand.args, spawnOptions);
2161
2203
  await waitForSpawn(proc);
@@ -2183,7 +2225,7 @@ function commandPathExists(command, cwd) {
2183
2225
  async function listDescendantPids(rootPid, timeoutMs) {
2184
2226
  let output;
2185
2227
  try {
2186
- output = await runProcessListCommand(timeoutMs);
2228
+ output = await runWindowsProcessListCommand(timeoutMs);
2187
2229
  } catch {
2188
2230
  return [];
2189
2231
  }
@@ -2216,38 +2258,10 @@ function parseProcessListLine(line) {
2216
2258
  parentPid
2217
2259
  };
2218
2260
  }
2219
- async function runProcessListCommand(timeoutMs) {
2220
- if (process.platform === "win32") return await runWindowsProcessListCommand(timeoutMs);
2221
- return await runTimedExecFile("ps", ["-eo", "pid=,ppid="], { timeoutMs });
2222
- }
2223
2261
  async function rememberProcessGroupPids(terminal) {
2224
2262
  const processGroupId = terminal.process.pid;
2225
2263
  if (!terminal.killProcessGroup || !processGroupId) return;
2226
- if (process.platform === "win32") {
2227
- for (const pid of await listDescendantPids(processGroupId, terminal.processHelperTimeoutMs)) terminal.descendantPids.add(pid);
2228
- return;
2229
- }
2230
- for (const pid of await listProcessGroupPids(processGroupId, terminal.processHelperTimeoutMs)) if (pid !== processGroupId) terminal.descendantPids.add(pid);
2231
- }
2232
- async function listProcessGroupPids(processGroupId, timeoutMs) {
2233
- let output;
2234
- try {
2235
- output = await runProcessGroupListCommand(timeoutMs);
2236
- } catch {
2237
- return [];
2238
- }
2239
- const pids = [];
2240
- for (const line of output.split("\n")) {
2241
- const match = line.trim().match(/^(\d+)\s+(\d+)$/);
2242
- if (!match) continue;
2243
- const pid = Number(match[1]);
2244
- const pgid = Number(match[2]);
2245
- if (Number.isInteger(pid) && Number.isInteger(pgid) && pid > 0 && pgid === processGroupId) pids.push(pid);
2246
- }
2247
- return pids;
2248
- }
2249
- async function runProcessGroupListCommand(timeoutMs) {
2250
- return await runTimedExecFile("ps", ["-eo", "pid=,pgid="], { timeoutMs });
2264
+ for (const pid of await listDescendantPids(processGroupId, terminal.processHelperTimeoutMs)) terminal.descendantPids.add(pid);
2251
2265
  }
2252
2266
  async function runWindowsProcessListCommand(timeoutMs) {
2253
2267
  const command = ["Get-CimInstance Win32_Process |", "ForEach-Object { \"$($_.ProcessId) $($_.ParentProcessId)\" }"].join(" ");
@@ -2275,22 +2289,11 @@ async function killWindowsProcessTree(pid, signal, timeoutMs = PROCESS_HELPER_TI
2275
2289
  });
2276
2290
  } catch {}
2277
2291
  }
2278
- function sendSignal(pid, signal) {
2279
- try {
2280
- process.kill(pid, signal);
2281
- } catch {}
2282
- }
2283
- function hasLiveProcessGroup(processGroupId) {
2284
- try {
2285
- process.kill(-processGroupId, 0);
2286
- return true;
2287
- } catch {
2288
- return false;
2289
- }
2290
- }
2291
- function hasLiveTerminalProcessGroup(terminal) {
2292
- const pid = terminal.process.pid;
2293
- return Boolean(terminal.killProcessGroup && pid && process.platform !== "win32" && hasLiveProcessGroup(pid));
2292
+ async function hasLiveTerminalDescendants(terminal, timeoutMs) {
2293
+ const descendants = terminal.descendants;
2294
+ if (!descendants) return false;
2295
+ if (timeoutMs > 0) await descendants.capture(Math.min(terminal.processHelperTimeoutMs, timeoutMs));
2296
+ return descendants.hasTrackedProcesses();
2294
2297
  }
2295
2298
  function hasLivePid(pids) {
2296
2299
  for (const pid of pids) try {
@@ -2441,6 +2444,7 @@ var AcpClient = class {
2441
2444
  cancellingSessionIds = /* @__PURE__ */ new Set();
2442
2445
  permissionAbortControllers = /* @__PURE__ */ new Map();
2443
2446
  closing = false;
2447
+ closeTask;
2444
2448
  closeEpoch = 0;
2445
2449
  agentStartedAt;
2446
2450
  lastAgentExit;
@@ -2537,7 +2541,7 @@ var AcpClient = class {
2537
2541
  this.terminalManager.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
2538
2542
  }
2539
2543
  hasLiveConnection() {
2540
- return this.connection != null && !this.connection.signal.aborted && this.agent != null && isChildProcessRunning(this.agent);
2544
+ return !this.closing && this.connection != null && !this.connection.signal.aborted && this.agent != null && isChildProcessRunning(this.agent);
2541
2545
  }
2542
2546
  hasReusableSession(sessionId) {
2543
2547
  return this.hasLiveConnection() && this.loadedSessionId === sessionId;
@@ -2560,15 +2564,17 @@ var AcpClient = class {
2560
2564
  }
2561
2565
  fallback?.abort();
2562
2566
  }
2563
- async start() {
2567
+ async start(authority) {
2568
+ assertControlAuthority(authority);
2564
2569
  if (this.hasLiveConnection()) return;
2565
- if (this.connection || this.agent) await this.close();
2566
- const epoch = this.closeEpoch;
2570
+ const epoch = await this.waitForPreviousClose();
2567
2571
  const maxMessageBytes = readMaxAcpMessageBytes();
2568
2572
  const launch = await this.resolveAgentLaunchPlan();
2573
+ assertControlAuthority(authority);
2569
2574
  this.logAgentLaunch(launch);
2570
2575
  await this.ensureLaunchSupport(launch);
2571
- const { child, process: startedProcess } = await this.spawnAgentProcess(launch);
2576
+ assertControlAuthority(authority);
2577
+ const { child, process: startedProcess } = await this.spawnAgentProcess(launch, authority);
2572
2578
  if (this.closeEpoch === epoch) {
2573
2579
  this.agent = child;
2574
2580
  this.closing = false;
@@ -2591,7 +2597,7 @@ var AcpClient = class {
2591
2597
  startupFailure.dispose();
2592
2598
  throw error;
2593
2599
  }
2594
- await this.stopIfClosedDuringLaunch(epoch, child, startupFailure);
2600
+ await this.stopIfClosedDuringLaunch(epoch, child, startupFailure, authority);
2595
2601
  const input = Writable.toWeb(child.stdin);
2596
2602
  const output = Readable.toWeb(child.stdout);
2597
2603
  let connection;
@@ -2606,9 +2612,10 @@ var AcpClient = class {
2606
2612
  elicitationModes: this.options.elicitationModes
2607
2613
  });
2608
2614
  connection = this.createConnection(stream, launch, capabilities);
2609
- connection.signal.addEventListener("abort", () => {
2610
- this.recordAgentExit(child, "connection_close", child.exitCode ?? null, child.signalCode ?? null);
2611
- }, { once: true });
2615
+ const onConnectionClose = () => {
2616
+ this.handleAgentDisconnect(child, "connection_close", child.exitCode ?? null, child.signalCode ?? null);
2617
+ };
2618
+ connection.signal.addEventListener("abort", onConnectionClose, { once: true });
2612
2619
  await this.initializeAgentConnection({
2613
2620
  child,
2614
2621
  connection,
@@ -2617,6 +2624,14 @@ var AcpClient = class {
2617
2624
  launch,
2618
2625
  capabilities
2619
2626
  });
2627
+ if (connection.signal.aborted) onConnectionClose();
2628
+ }
2629
+ async waitForPreviousClose() {
2630
+ const cleanup = this.closeTask ?? (this.connection || this.agent ? this.close() : void 0);
2631
+ const epoch = this.closeEpoch;
2632
+ await cleanup;
2633
+ if (this.closeEpoch !== epoch) throw new Error("ACP client was closed while the agent was starting");
2634
+ return epoch;
2620
2635
  }
2621
2636
  async resolveAgentLaunchPlan() {
2622
2637
  const configuredCommand = resolveAgentCommandParts(this.options.agentCommand, this.options.agentArgv);
@@ -2657,7 +2672,7 @@ var AcpClient = class {
2657
2672
  this.log(`resolved system Claude Code executable: ${claudeExe}`);
2658
2673
  }
2659
2674
  }
2660
- async spawnAgentProcess(plan) {
2675
+ async spawnAgentProcess(plan, authority) {
2661
2676
  const spawnCommand = buildAgentSpawnCommand(plan.spawnCommand, plan.args, process.platform, plan.spawnOptions.env);
2662
2677
  const launch = Object.freeze({
2663
2678
  launchId: randomUUID(),
@@ -2667,6 +2682,7 @@ var AcpClient = class {
2667
2682
  cwd: this.options.cwd
2668
2683
  });
2669
2684
  await this.options.processLifecycle?.onBeforeSpawn?.(launch);
2685
+ assertControlAuthority(authority);
2670
2686
  let spawnedChild;
2671
2687
  try {
2672
2688
  spawnedChild = spawn(spawnCommand.command, spawnCommand.args, {
@@ -2788,7 +2804,8 @@ var AcpClient = class {
2788
2804
  onPromptRequestWritten: (active, owner) => this.onPromptRequestWritten(active, owner)
2789
2805
  });
2790
2806
  }
2791
- async createSession(cwd = this.options.cwd) {
2807
+ async createSession(cwd = this.options.cwd, authority) {
2808
+ assertControlAuthority(authority);
2792
2809
  const connection = this.getConnection();
2793
2810
  const { command, args } = resolveAgentCommandParts(this.options.agentCommand, this.options.agentArgv);
2794
2811
  const claudeAcp = isClaudeAcpCommand(command, args);
@@ -2799,7 +2816,7 @@ var AcpClient = class {
2799
2816
  cwd: sessionCwd,
2800
2817
  mcpServers: this.options.mcpServers ?? [],
2801
2818
  _meta: buildClaudeCodeOptionsMeta(this.options.sessionOptions, claudeAcp)
2802
- }));
2819
+ }), authority);
2803
2820
  result = claudeAcp ? await withTimeout(createPromise, resolveClaudeAcpSessionCreateTimeoutMs()) : await createPromise;
2804
2821
  } catch (error) {
2805
2822
  if (claudeAcp && error instanceof TimeoutError) throw new ClaudeAcpSessionCreateTimeoutError(buildClaudeAcpSessionCreateTimeoutMessage(), {
@@ -2831,6 +2848,7 @@ var AcpClient = class {
2831
2848
  async loadSessionWithOptions(sessionId, cwd = this.options.cwd, options = {}) {
2832
2849
  const connection = this.getConnection();
2833
2850
  const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
2851
+ assertControlAuthority(options.authority);
2834
2852
  const previousSuppression = this.applySessionUpdateSuppression(Boolean(options.suppressReplayUpdates));
2835
2853
  let response;
2836
2854
  try {
@@ -2838,7 +2856,7 @@ var AcpClient = class {
2838
2856
  sessionId,
2839
2857
  cwd: sessionCwd,
2840
2858
  mcpServers: this.options.mcpServers ?? []
2841
- }));
2859
+ }), options.authority);
2842
2860
  await this.waitForSessionUpdateDrain(options.replayIdleMs ?? REPLAY_IDLE_MS, options.replayDrainTimeoutMs ?? REPLAY_DRAIN_TIMEOUT_MS);
2843
2861
  } finally {
2844
2862
  this.restoreSessionUpdateSuppression(previousSuppression);
@@ -2848,14 +2866,15 @@ var AcpClient = class {
2848
2866
  await this.captureAgentDescendants(this.agent);
2849
2867
  return result;
2850
2868
  }
2851
- async resumeSession(sessionId, cwd = this.options.cwd) {
2869
+ async resumeSession(sessionId, cwd = this.options.cwd, authority) {
2870
+ assertControlAuthority(authority);
2852
2871
  const connection = this.getConnection();
2853
2872
  const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
2854
2873
  const response = await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.resume, {
2855
2874
  sessionId,
2856
2875
  cwd: sessionCwd,
2857
2876
  mcpServers: this.options.mcpServers ?? []
2858
- }));
2877
+ }), authority);
2859
2878
  this.loadedSessionId = sessionId;
2860
2879
  const result = toReconnectedSessionResult(response);
2861
2880
  await this.captureAgentDescendants(this.agent);
@@ -3080,20 +3099,23 @@ var AcpClient = class {
3080
3099
  if (timer) clearTimeout(timer);
3081
3100
  }
3082
3101
  }
3083
- async close() {
3102
+ close() {
3084
3103
  this.closing = true;
3085
3104
  this.closeEpoch += 1;
3105
+ if (this.closeTask) return this.closeTask;
3106
+ this.closeTask = Promise.resolve().then(() => this.finishClose()).finally(() => {
3107
+ this.closeTask = void 0;
3108
+ });
3109
+ return this.closeTask;
3110
+ }
3111
+ async finishClose() {
3086
3112
  const permissionControllers = [...this.permissionAbortControllers.values()];
3087
3113
  this.permissionAbortControllers.clear();
3088
3114
  const owners = [...this.pendingPromptOwners];
3089
3115
  this.abortActiveElicitation();
3090
3116
  for (const owner of owners) owner.requestController.abort();
3091
3117
  for (const controller of permissionControllers) controller.abort();
3092
- await this.terminalManager.shutdown();
3093
- const agent = this.agent;
3094
- if (agent) await this.terminateAgentProcess(agent);
3095
- this.closeConnection();
3096
- if (this.pendingConnectionRequests.size > 0) this.rejectPendingConnectionRequests(this.lastAgentExit ? new AgentDisconnectedError(this.lastAgentExit.reason, this.lastAgentExit.exitCode, this.lastAgentExit.signal, { outputAlreadyEmitted: Boolean(this.activePrompt) }) : new AgentDisconnectedError("connection_close", null, null, { outputAlreadyEmitted: Boolean(this.activePrompt) }));
3118
+ await this.retireNativeResources();
3097
3119
  this.sessionUpdateChain = Promise.resolve();
3098
3120
  this.observedSessionUpdates = 0;
3099
3121
  this.processedSessionUpdates = 0;
@@ -3110,10 +3132,31 @@ var AcpClient = class {
3110
3132
  this.connection = void 0;
3111
3133
  this.agent = void 0;
3112
3134
  }
3113
- async stopIfClosedDuringLaunch(epoch, child, startupFailure) {
3135
+ async retireNativeResources() {
3136
+ const agent = this.agent;
3137
+ const failures = [];
3138
+ try {
3139
+ await this.terminalManager.shutdown();
3140
+ } catch (error) {
3141
+ failures.push(error);
3142
+ }
3143
+ try {
3144
+ if (agent) await this.terminateAgentProcess(agent);
3145
+ } catch (error) {
3146
+ failures.push(error);
3147
+ }
3148
+ try {
3149
+ this.closeConnection();
3150
+ } finally {
3151
+ this.rejectPendingConnectionRequests(this.lastAgentExit ? new AgentDisconnectedError(this.lastAgentExit.reason, this.lastAgentExit.exitCode, this.lastAgentExit.signal, { outputAlreadyEmitted: Boolean(this.activePrompt) }) : new AgentDisconnectedError("connection_close", null, null, { outputAlreadyEmitted: Boolean(this.activePrompt) }));
3152
+ }
3153
+ if (failures.length > 0) throw new AggregateError(failures, "ACP client cleanup failed", { cause: failures[0] });
3154
+ }
3155
+ async stopIfClosedDuringLaunch(epoch, child, startupFailure, authority) {
3114
3156
  if (this.closeEpoch === epoch) return;
3115
3157
  startupFailure.dispose();
3116
3158
  await this.terminateAgentProcess(child);
3159
+ assertControlAuthority(authority);
3117
3160
  throw new Error("ACP client was closed while the agent was starting");
3118
3161
  }
3119
3162
  abortActiveElicitation() {
@@ -3475,7 +3518,7 @@ var AcpClient = class {
3475
3518
  attachAgentLifecycleObservers(child, startedProcess, exitNotificationBarrier) {
3476
3519
  const onExit = (exitCode, signal) => {
3477
3520
  const exitedAt = isoNow$2();
3478
- this.recordAgentExit(child, "process_exit", exitCode, signal);
3521
+ this.handleAgentDisconnect(child, "process_exit", exitCode, signal);
3479
3522
  this.terminateAgentProcess(child);
3480
3523
  exitNotificationBarrier.then(() => {
3481
3524
  this.notifyProcessExit(startedProcess, exitCode, signal, exitedAt);
@@ -3487,10 +3530,17 @@ var AcpClient = class {
3487
3530
  onExit(child.exitCode, child.signalCode);
3488
3531
  }
3489
3532
  child.once("close", (exitCode, signal) => {
3490
- this.recordAgentExit(child, "process_close", exitCode, signal);
3533
+ this.handleAgentDisconnect(child, "process_close", exitCode, signal);
3491
3534
  });
3492
3535
  child.stdout.once("close", () => {
3493
- this.recordAgentExit(child, "pipe_close", child.exitCode ?? null, child.signalCode ?? null);
3536
+ this.handleAgentDisconnect(child, "pipe_close", child.exitCode ?? null, child.signalCode ?? null);
3537
+ });
3538
+ }
3539
+ handleAgentDisconnect(child, reason, exitCode, signal) {
3540
+ this.recordAgentExit(child, reason, exitCode, signal);
3541
+ if (this.agent !== child || this.closing || !this.connection) return;
3542
+ this.close().catch((error) => {
3543
+ this.log(`disconnected agent cleanup failed: ${String(error)}`);
3494
3544
  });
3495
3545
  }
3496
3546
  notifyProcessSpawnFailure(launch, error) {
@@ -3854,7 +3904,6 @@ function ensureAgentMessage(conversation) {
3854
3904
  return created;
3855
3905
  }
3856
3906
  function appendAgentText(agent, text) {
3857
- if (!text.trim()) return;
3858
3907
  const last = agent.content.at(-1);
3859
3908
  if (last && isAgentTextContent(last)) {
3860
3909
  last.Text = trimRuntimeText(`${last.Text}${text}`, MAX_RUNTIME_AGENT_TEXT_CHARS);
@@ -3864,7 +3913,6 @@ function appendAgentText(agent, text) {
3864
3913
  agent.content.push(next);
3865
3914
  }
3866
3915
  function appendAgentThinking(agent, text) {
3867
- if (!text.trim()) return;
3868
3916
  const last = agent.content.at(-1);
3869
3917
  if (last && isAgentThinkingContent(last)) {
3870
3918
  last.Thinking.text = trimRuntimeText(`${last.Thinking.text}${text}`, MAX_RUNTIME_THINKING_CHARS);
@@ -3921,8 +3969,7 @@ function ensureToolUseContent(agent, toolCallId) {
3921
3969
  return created;
3922
3970
  }
3923
3971
  function upsertToolResult(agent, toolCallId, patch) {
3924
- const existing = agent.tool_results[toolCallId];
3925
- const fallback = existingToolResultValues(existing);
3972
+ const fallback = existingToolResultValues(hasOwn(agent.tool_results, toolCallId) ? agent.tool_results[toolCallId] : void 0);
3926
3973
  const next = {
3927
3974
  tool_use_id: toolCallId,
3928
3975
  tool_name: patch.tool_name ?? fallback.tool_name,
@@ -3930,7 +3977,12 @@ function upsertToolResult(agent, toolCallId, patch) {
3930
3977
  content: patch.content ?? fallback.content,
3931
3978
  output: patch.output ?? fallback.output
3932
3979
  };
3933
- agent.tool_results[toolCallId] = next;
3980
+ Object.defineProperty(agent.tool_results, toolCallId, {
3981
+ value: next,
3982
+ writable: true,
3983
+ enumerable: true,
3984
+ configurable: true
3985
+ });
3934
3986
  }
3935
3987
  function existingToolResultValues(existing) {
3936
3988
  if (existing) return existing;
@@ -3971,7 +4023,7 @@ function applyToolResultUpdate(agent, tool, update) {
3971
4023
  const output = hasOwn(update, "rawOutput") ? deepClone(update.rawOutput) : void 0;
3972
4024
  upsertToolResult(agent, update.toolCallId, {
3973
4025
  tool_name: tool.name,
3974
- is_error: statusIndicatesError(status),
4026
+ is_error: status === void 0 ? void 0 : statusIndicatesError(status),
3975
4027
  content: output === void 0 ? void 0 : toToolResultContent(output),
3976
4028
  output
3977
4029
  });
@@ -4397,10 +4449,7 @@ function getDesiredModeId(state) {
4397
4449
  function getDesiredConfigOptions(state) {
4398
4450
  const desired = state?.desired_config_options;
4399
4451
  if (!desired) return {};
4400
- return Object.fromEntries(Object.entries(desired).flatMap(([configId, value]) => {
4401
- const normalizedConfigId = normalizeModeId(configId);
4402
- return normalizedConfigId && typeof value === "string" ? [[normalizedConfigId, value]] : [];
4403
- }));
4452
+ return Object.fromEntries(Object.entries(desired).filter(([, value]) => typeof value === "string"));
4404
4453
  }
4405
4454
  function setDesiredModeId(record, modeId) {
4406
4455
  const acpx = ensureAcpxState(record.acpx);
@@ -4410,10 +4459,9 @@ function setDesiredModeId(record, modeId) {
4410
4459
  record.acpx = acpx;
4411
4460
  }
4412
4461
  function clearDesiredConfigOption(state, configId) {
4413
- const normalizedConfigId = normalizeModeId(configId);
4414
- if (!normalizedConfigId || !state.desired_config_options) return;
4462
+ if (typeof configId !== "string" || !state.desired_config_options) return;
4415
4463
  const desired = { ...state.desired_config_options };
4416
- delete desired[normalizedConfigId];
4464
+ delete desired[configId];
4417
4465
  if (Object.keys(desired).length > 0) state.desired_config_options = desired;
4418
4466
  else delete state.desired_config_options;
4419
4467
  }
@@ -4452,7 +4500,7 @@ async function applyRequestedModelIfAdvertised(params) {
4452
4500
  if (params.models.currentModelId === requestedModel) return { applied: true };
4453
4501
  return {
4454
4502
  applied: true,
4455
- response: await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel, params.models), params.timeoutMs)
4503
+ response: await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel, params.models, params.authority), params.timeoutMs)
4456
4504
  };
4457
4505
  }
4458
4506
  //#endregion
@@ -4477,9 +4525,9 @@ function applyAcceptedConfigOptions(state, response) {
4477
4525
  if (!response) return next;
4478
4526
  applyConfigOptionsModelState(next, response.configOptions);
4479
4527
  if (!next.desired_config_options) return next;
4480
- const desired = {};
4481
- for (const option of response.configOptions) if (typeof option.currentValue === "string" && Object.hasOwn(next.desired_config_options, option.id)) desired[option.id] = option.currentValue;
4482
- if (Object.keys(desired).length > 0) next.desired_config_options = desired;
4528
+ const desiredEntries = [];
4529
+ 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]);
4530
+ if (desiredEntries.length > 0) next.desired_config_options = Object.fromEntries(desiredEntries);
4483
4531
  else delete next.desired_config_options;
4484
4532
  return next;
4485
4533
  }
@@ -4497,8 +4545,7 @@ function applyModelSelection(state, modelId, response) {
4497
4545
  function applyConfigOptionSelection(state, configId, value, response, modelConfigId = advertisedModelState(state)?.configId) {
4498
4546
  if (configId === modelConfigId || configId === modelStateFromConfigOptions(response.configOptions)?.configId) return applyModelSelection(state, value, response);
4499
4547
  const next = cloneSessionAcpxState(state) ?? {};
4500
- if (configId === "mode") next.desired_mode_id = value;
4501
- else next.desired_config_options = {
4548
+ next.desired_config_options = {
4502
4549
  ...next.desired_config_options,
4503
4550
  [configId]: value
4504
4551
  };
@@ -4509,6 +4556,7 @@ function applyConfigOptionSelection(state, configId, value, response, modelConfi
4509
4556
  const LOCK_RETRY_MS = 15;
4510
4557
  const INCOMPLETE_RESERVATION_GRACE_MS = 15e3;
4511
4558
  let lastCreatedAt = 0;
4559
+ const localTurns = /* @__PURE__ */ new Map();
4512
4560
  async function readLock(filePath) {
4513
4561
  try {
4514
4562
  const stat = await fs$1.lstat(filePath, { bigint: true });
@@ -4522,11 +4570,6 @@ async function readLock(filePath) {
4522
4570
  throw error;
4523
4571
  }
4524
4572
  }
4525
- function lockPid(value) {
4526
- if (!value || typeof value !== "object" || !("pid" in value)) return;
4527
- const pid = value.pid;
4528
- return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
4529
- }
4530
4573
  function parseLock(payload) {
4531
4574
  try {
4532
4575
  return JSON.parse(payload);
@@ -4534,7 +4577,7 @@ function parseLock(payload) {
4534
4577
  return;
4535
4578
  }
4536
4579
  }
4537
- async function tryAcquireGuard(filePath) {
4580
+ async function tryAcquireGuard(filePath, owner, signal) {
4538
4581
  try {
4539
4582
  return await acquireFileLock(filePath, {
4540
4583
  managerKey: "acpx.session-turn",
@@ -4549,9 +4592,9 @@ async function tryAcquireGuard(filePath) {
4549
4592
  randomize: false
4550
4593
  },
4551
4594
  staleRecovery: "remove-if-unchanged",
4552
- payload: () => ({ pid: process.pid }),
4553
- shouldReclaim: ({ payload }) => isProcessDefinitelyDead(lockPid(payload)),
4554
- shouldRemoveStaleLock: ({ payload }) => isProcessDefinitelyDead(lockPid(payload))
4595
+ payload: () => owner.payload,
4596
+ shouldReclaim: ({ payload }) => owner.hasExited(payload, false, signal),
4597
+ shouldRemoveStaleLock: ({ payload }) => owner.hasExited(payload, true, signal)
4555
4598
  });
4556
4599
  } catch (error) {
4557
4600
  if (error.code === "file_lock_timeout") return;
@@ -4567,11 +4610,12 @@ async function removeObservedLock(filePath, observed) {
4567
4610
  });
4568
4611
  return true;
4569
4612
  }
4570
- async function recoverAbandonedLock(filePath) {
4613
+ async function recoverAbandonedLock(filePath, owner, signal) {
4571
4614
  const observed = await readLock(filePath);
4572
4615
  if (!observed) return true;
4573
- const pid = lockPid(parseLock(observed.payload));
4574
- if (pid && !isProcessDefinitelyDead(pid)) return false;
4616
+ const payload = parseLock(observed.payload);
4617
+ const pid = lockOwnerPid(payload);
4618
+ if (pid && (!await owner.hasExited(payload, false, signal) || !await owner.hasExited(payload, true, signal))) return false;
4575
4619
  if (!pid && Date.now() - Number(observed.stat.mtimeMs) <= INCOMPLETE_RESERVATION_GRACE_MS) return false;
4576
4620
  if (await removeObservedLock(filePath, observed)) {
4577
4621
  incrementPerfCounter("session.events.stale_lock_recovered");
@@ -4607,58 +4651,112 @@ async function removePublishedLock(filePath, payload) {
4607
4651
  const current = await readLock(filePath);
4608
4652
  if (current?.payload === payload) await removeObservedLock(filePath, current);
4609
4653
  }
4610
- async function rejectAcquisition(filePath, payload, guard, error) {
4654
+ async function rejectAcquisition(filePath, receipt, error) {
4611
4655
  const failures = [error];
4612
- await removePublishedLock(filePath, payload).catch((cleanupError) => {
4613
- failures.push(cleanupError);
4614
- });
4615
- await guard.release().catch((releaseError) => {
4616
- failures.push(releaseError);
4656
+ await disposeTurn(filePath, receipt).catch((cleanupError) => {
4657
+ failures.push(...cleanupError instanceof AggregateError ? cleanupError.errors : [cleanupError]);
4617
4658
  });
4618
4659
  if (failures.length > 1) throw new AggregateError(failures, "Session turn acquisition and cleanup failed");
4619
4660
  throw error;
4620
4661
  }
4621
- function turnReceipt(filePath, observed, guard) {
4622
- let disposed = false;
4623
- let disposal;
4624
- return { [Symbol.asyncDispose]: async () => {
4625
- if (disposed) return;
4626
- const pending = disposal ??= (async () => {
4627
- await removeObservedLock(filePath, observed);
4628
- await guard.release();
4629
- disposed = true;
4630
- })();
4662
+ async function releaseTurnGuard(receipt) {
4663
+ await receipt.guard?.release();
4664
+ receipt.guard = void 0;
4665
+ }
4666
+ async function removeTurnMarker(filePath, receipt) {
4667
+ if (!receipt.removeMarker) return true;
4668
+ if (receipt.guard && !await receipt.guard.verifyStillHeld()) await releaseTurnGuard(receipt);
4669
+ receipt.guard ??= await tryAcquireGuard(filePath, receipt.owner);
4670
+ if (!receipt.guard) return false;
4671
+ await receipt.removeMarker();
4672
+ receipt.removeMarker = void 0;
4673
+ return true;
4674
+ }
4675
+ async function settleTurn(filePath, receipt) {
4676
+ if (receipt.settled) return true;
4677
+ const pending = receipt.pending ??= (async () => {
4678
+ receipt.failed = false;
4679
+ const failures = [];
4631
4680
  try {
4632
- await pending;
4633
- } finally {
4634
- if (disposal === pending) disposal = void 0;
4681
+ if (!await removeTurnMarker(filePath, receipt)) return false;
4682
+ } catch (error) {
4683
+ failures.push(error);
4635
4684
  }
4636
- } };
4685
+ if (failures.length === 0 || !receipt.admitted) try {
4686
+ await releaseTurnGuard(receipt);
4687
+ } catch (error) {
4688
+ failures.push(error);
4689
+ }
4690
+ if (failures.length > 1) throw new AggregateError(failures, "Session turn cleanup failed");
4691
+ if (failures.length === 1) throw failures[0];
4692
+ receipt.settled = true;
4693
+ localTurns.delete(filePath);
4694
+ return true;
4695
+ })();
4696
+ try {
4697
+ return await pending;
4698
+ } finally {
4699
+ if (receipt.pending === pending) {
4700
+ receipt.pending = void 0;
4701
+ receipt.failed = !receipt.settled;
4702
+ }
4703
+ }
4704
+ }
4705
+ async function disposeTurn(filePath, receipt) {
4706
+ if (!await settleTurn(filePath, receipt)) throw new Error(`Session turn cleanup is waiting for its guard: ${filePath}`);
4637
4707
  }
4638
- async function tryPublishLock(filePath, payload, signal) {
4708
+ async function tryPublishLock(filePath, payload, owner, signal) {
4639
4709
  for (;;) try {
4640
4710
  await publishLock(filePath, payload, signal);
4641
4711
  return true;
4642
4712
  } catch (error) {
4643
4713
  if (error.code !== "EEXIST") throw error;
4644
- if (!await recoverAbandonedLock(filePath)) return false;
4714
+ if (!await recoverAbandonedLock(filePath, owner, signal)) return false;
4645
4715
  }
4646
4716
  }
4647
- async function tryAcquireTurn(filePath, payload, signal) {
4648
- const guard = await tryAcquireGuard(filePath);
4649
- if (!guard) return;
4717
+ async function tryAcquireTurnCleanup(filePath, payload, owner, signal) {
4718
+ const previous = localTurns.get(filePath);
4719
+ if (previous) {
4720
+ if (previous.failed) await settleTurn(filePath, previous);
4721
+ return;
4722
+ }
4723
+ const receipt = {
4724
+ owner,
4725
+ removeMarker: async () => await removePublishedLock(filePath, payload),
4726
+ admitted: false,
4727
+ failed: false,
4728
+ settled: false
4729
+ };
4730
+ localTurns.set(filePath, receipt);
4731
+ try {
4732
+ receipt.guard = await tryAcquireGuard(filePath, owner, signal);
4733
+ } catch (error) {
4734
+ localTurns.delete(filePath);
4735
+ throw error;
4736
+ }
4737
+ if (!receipt.guard) {
4738
+ localTurns.delete(filePath);
4739
+ return;
4740
+ }
4741
+ return receipt;
4742
+ }
4743
+ async function tryAcquireTurn(filePath, payload, owner, signal) {
4744
+ const receipt = await tryAcquireTurnCleanup(filePath, payload, owner, signal);
4745
+ if (!receipt) return;
4650
4746
  try {
4651
4747
  signal?.throwIfAborted();
4652
- if (!await tryPublishLock(filePath, payload, signal)) {
4653
- await guard.release();
4748
+ if (!await tryPublishLock(filePath, payload, owner, signal)) {
4749
+ await disposeTurn(filePath, receipt);
4654
4750
  return;
4655
4751
  }
4656
4752
  const observed = await readLock(filePath);
4657
4753
  if (observed?.payload !== payload) throw new Error(`Session turn ownership changed before admission: ${filePath}`);
4658
4754
  signal?.throwIfAborted();
4659
- return turnReceipt(filePath, observed, guard);
4755
+ receipt.admitted = true;
4756
+ receipt.removeMarker = async () => await removeObservedLock(filePath, observed);
4757
+ return { [Symbol.asyncDispose]: async () => await disposeTurn(filePath, receipt) };
4660
4758
  } catch (error) {
4661
- return await rejectAcquisition(filePath, payload, guard, error);
4759
+ return await rejectAcquisition(filePath, receipt, error);
4662
4760
  }
4663
4761
  }
4664
4762
  async function acquireSessionTurn(recordId, signal) {
@@ -4668,19 +4766,44 @@ async function acquireSessionTurn(recordId, signal) {
4668
4766
  mode: 448
4669
4767
  });
4670
4768
  const filePath = path.join(await fs$1.realpath(path.dirname(requestedPath)), path.basename(requestedPath));
4769
+ const owner = await createLockOwner({ signal });
4671
4770
  lastCreatedAt = Math.max(Date.now(), lastCreatedAt + 1);
4672
4771
  const payload = `${JSON.stringify({
4673
- pid: process.pid,
4772
+ ...owner.payload,
4674
4773
  created_at: new Date(lastCreatedAt).toISOString()
4675
4774
  }, null, 2)}\n`;
4676
4775
  for (;;) {
4677
4776
  signal?.throwIfAborted();
4678
- const turn = await tryAcquireTurn(filePath, payload, signal);
4777
+ const turn = await tryAcquireTurn(filePath, payload, owner, signal);
4679
4778
  if (turn) return turn;
4680
4779
  await setTimeout$1(LOCK_RETRY_MS, void 0, { signal });
4681
4780
  }
4682
4781
  }
4683
4782
  //#endregion
4783
+ //#region src/session/execution/direct-lifetime.ts
4784
+ function ownDirectClient(client, signal) {
4785
+ let closing;
4786
+ const cancel = async () => {
4787
+ await withTimeout(client.cancelActivePrompt(2500), 2500).catch(() => {});
4788
+ await client.close();
4789
+ };
4790
+ const onAbort = () => {
4791
+ closing ??= cancel();
4792
+ closing.catch(() => {});
4793
+ };
4794
+ signal?.addEventListener("abort", onAbort, { once: true });
4795
+ if (signal?.aborted) onAbort();
4796
+ const release = () => signal?.removeEventListener("abort", onAbort);
4797
+ return Object.assign(() => {
4798
+ release();
4799
+ closing ??= client.close();
4800
+ return closing;
4801
+ }, { release });
4802
+ }
4803
+ function directExecutionError(error, signal) {
4804
+ return signal?.aborted && error instanceof AgentDisconnectedError ? signal.reason : error;
4805
+ }
4806
+ //#endregion
4684
4807
  //#region src/session/queue/ipc-server.ts
4685
4808
  const QUEUE_SOCKET_DRAIN_TIMEOUT_MS = 1e3;
4686
4809
  function makeQueueOwnerError(requestId, message, detailCode, options = {}) {
@@ -4713,9 +4836,12 @@ function makeQueueOwnerErrorFromUnknown(requestId, error, detailCode, options =
4713
4836
  acp: normalized.acp
4714
4837
  };
4715
4838
  }
4839
+ function armSocketDrainTimeout(socket) {
4840
+ if (process.platform !== "win32") socket.setTimeout(QUEUE_SOCKET_DRAIN_TIMEOUT_MS);
4841
+ }
4716
4842
  function writeQueueMessage(socket, message) {
4717
4843
  if (socket.destroyed || !socket.writable) return;
4718
- socket.write(`${JSON.stringify(message)}\n`);
4844
+ if (!socket.write(`${JSON.stringify(message)}\n`) && !socket.timeout) armSocketDrainTimeout(socket);
4719
4845
  }
4720
4846
  var SessionQueueOwner = class SessionQueueOwner {
4721
4847
  server;
@@ -4883,6 +5009,7 @@ var SessionQueueOwner = class SessionQueueOwner {
4883
5009
  if (socket.destroyed || this.drainingSockets.has(socket)) return;
4884
5010
  this.taskSockets.delete(socket);
4885
5011
  this.drainingSockets.add(socket);
5012
+ armSocketDrainTimeout(socket);
4886
5013
  socket.end(() => {
4887
5014
  this.drainingSockets.delete(socket);
4888
5015
  socket.destroy();
@@ -5032,6 +5159,10 @@ var SessionQueueOwner = class SessionQueueOwner {
5032
5159
  handleConnection(socket) {
5033
5160
  this.sockets.add(socket);
5034
5161
  socket.setEncoding("utf8");
5162
+ socket.on("timeout", () => socket.destroy());
5163
+ socket.on("drain", () => {
5164
+ if (!socket.writableEnded) socket.setTimeout(0);
5165
+ });
5035
5166
  socket.once("close", () => {
5036
5167
  this.sockets.delete(socket);
5037
5168
  this.taskSockets.delete(socket);
@@ -5120,6 +5251,10 @@ async function replayDesiredMode(params) {
5120
5251
  await withTimeout(params.client.setSessionMode(params.sessionId, params.desiredModeId, params.replay.authority), params.timeoutMs);
5121
5252
  params.replay.acknowledged = true;
5122
5253
  if (params.verbose) process.stderr.write(`[acpx] replayed desired mode ${params.desiredModeId} on fresh ACP session ${params.sessionId} (previous ${params.previousSessionId})\n`);
5254
+ return {
5255
+ models: advertisedModelState(params.record.acpx),
5256
+ configOptionsPresent: params.record.acpx?.config_options !== void 0
5257
+ };
5123
5258
  } catch (error) {
5124
5259
  rethrowAuthorityFailure(params.replay, error);
5125
5260
  throw new SessionModeReplayError(`Failed to replay saved session mode ${params.desiredModeId} on fresh ACP session ${params.sessionId}: ${formatErrorMessage(error)}`, {
@@ -5198,18 +5333,20 @@ function restoreOriginalSessionState(params) {
5198
5333
  async function connectAndLoadSession(options) {
5199
5334
  const record = options.record;
5200
5335
  const client = options.client;
5336
+ assertControlAuthority(options.authority);
5201
5337
  const sameSessionOnly = requiresSameSession(options.resumePolicy) || Boolean(record.importedFrom);
5202
5338
  const originalSessionId = record.acpSessionId;
5203
5339
  const originalAgentSessionId = record.agentSessionId;
5204
5340
  const originalAcpx = cloneSessionAcpxState(record.acpx);
5205
- const desiredModeId = getDesiredModeId(record.acpx);
5341
+ const desiredModeId = options.replacingMode ? void 0 : getDesiredModeId(record.acpx);
5206
5342
  const desiredModelId = getDesiredModelId(record.acpx);
5207
5343
  const desiredConfigOptions = getDesiredConfigOptions(record.acpx);
5208
5344
  const storedProcessAlive = isProcessAlive(record.pid);
5209
5345
  logReconnectAttempt(record, storedProcessAlive, Boolean(record.pid) && !storedProcessAlive, options.verbose);
5210
5346
  const reusingLoadedSession = client.hasReusableSession(record.acpSessionId);
5211
5347
  if (reusingLoadedSession) incrementPerfCounter("runtime.connect_and_load.reused_session");
5212
- else await withTimeout(client.start(), options.timeoutMs);
5348
+ else await withTimeout(client.start(options.authority), options.timeoutMs);
5349
+ assertControlAuthority(options.authority);
5213
5350
  options.onClientAvailable?.(options.activeController);
5214
5351
  applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
5215
5352
  record.closed = false;
@@ -5220,7 +5357,8 @@ async function connectAndLoadSession(options) {
5220
5357
  record,
5221
5358
  reusingLoadedSession,
5222
5359
  sameSessionOnly,
5223
- timeoutMs: options.timeoutMs
5360
+ timeoutMs: options.timeoutMs,
5361
+ authority: options.authority
5224
5362
  });
5225
5363
  const { resumed, loadError, sessionId, createdFreshSession, pendingAgentSessionId, sessionModels } = loadState;
5226
5364
  applyReconnectedModelState(record, sessionModels, loadState.configOptionsPresent, loadState.legacyModelMetadataPresent, createdFreshSession);
@@ -5254,14 +5392,18 @@ async function connectAndLoadSession(options) {
5254
5392
  };
5255
5393
  }
5256
5394
  function resolveModelsAfterReplay(replay, initialModels) {
5257
- if (replay.configReplay.replayed) return replay.configReplay.models ?? preserveLegacyModels(replay.modelReplay.replayed ? replay.modelReplay.models : initialModels);
5258
- return replay.modelReplay.replayed ? replay.modelReplay.models : initialModels;
5395
+ const models = resolveModeReplayModels(replay.modeMetadata, initialModels);
5396
+ if (replay.configReplay.replayed) return replay.configReplay.models ?? preserveLegacyModels(replay.modelReplay.replayed ? replay.modelReplay.models : models);
5397
+ return replay.modelReplay.replayed ? replay.modelReplay.models : models;
5398
+ }
5399
+ function resolveModeReplayModels(modeMetadata, initialModels) {
5400
+ return modeMetadata ? modeMetadata.models : initialModels;
5259
5401
  }
5260
5402
  function preserveLegacyModels(models) {
5261
5403
  return models && !models.configId ? models : void 0;
5262
5404
  }
5263
5405
  function resolveConfigOptionsPresenceAfterReplay(replay, initiallyPresent) {
5264
- return initiallyPresent || replay.configReplay.replayed || replay.modelReplay.configOptions !== void 0;
5406
+ return initiallyPresent || replay.modeMetadata?.configOptionsPresent === true || replay.configReplay.replayed || replay.modelReplay.configOptions !== void 0;
5265
5407
  }
5266
5408
  function applyReconnectedModelState(record, sessionModels, configOptionsPresent, legacyModelMetadataPresent, createdFreshSession) {
5267
5409
  clearOmittedFreshSessionConfigOptions(record, createdFreshSession, configOptionsPresent);
@@ -5302,12 +5444,13 @@ async function replaySessionPreferences(params) {
5302
5444
  };
5303
5445
  let modelReplay = { replayed: false };
5304
5446
  let configReplay = { replayed: false };
5447
+ let modeMetadata;
5305
5448
  const replay = createReplayState(params.authority);
5306
- const replacingModel = replacesModel(params.replacingConfigOption, params.sessionModels, params.originalAcpx);
5307
5449
  try {
5308
5450
  assertControlAuthority(replay.authority);
5309
- await replayDesiredMode({
5451
+ modeMetadata = await replayDesiredMode({
5310
5452
  client: params.client,
5453
+ record: params.record,
5311
5454
  sessionId: params.sessionId,
5312
5455
  desiredModeId: params.createdFreshSession ? params.desiredModeId : void 0,
5313
5456
  replay,
@@ -5315,6 +5458,8 @@ async function replaySessionPreferences(params) {
5315
5458
  timeoutMs: params.timeoutMs,
5316
5459
  verbose: params.verbose
5317
5460
  });
5461
+ const effectiveModels = resolveModeReplayModels(modeMetadata, params.sessionModels);
5462
+ const replacingModel = replacesModel(params.replacingConfigOption, effectiveModels, params.originalAcpx);
5318
5463
  modelReplay = await replayDesiredModel({
5319
5464
  client: params.client,
5320
5465
  sessionId: params.sessionId,
@@ -5322,7 +5467,7 @@ async function replaySessionPreferences(params) {
5322
5467
  replay,
5323
5468
  previousSessionId: params.originalSessionId,
5324
5469
  record: params.record,
5325
- models: params.sessionModels,
5470
+ models: effectiveModels,
5326
5471
  createdFreshSession: params.createdFreshSession,
5327
5472
  timeoutMs: params.timeoutMs,
5328
5473
  verbose: params.verbose,
@@ -5349,7 +5494,8 @@ async function replaySessionPreferences(params) {
5349
5494
  reconcileAgentSessionId(params.record, params.pendingAgentSessionId);
5350
5495
  return {
5351
5496
  modelReplay,
5352
- configReplay
5497
+ configReplay,
5498
+ modeMetadata
5353
5499
  };
5354
5500
  }
5355
5501
  function settleFailedReplay(params, replay, error) {
@@ -5381,11 +5527,14 @@ async function loadOrCreateRuntimeSession(params) {
5381
5527
  record: params.record,
5382
5528
  reason: "agent does not support session/resume or session/load"
5383
5529
  });
5384
- return await createFreshRuntimeSession(params.client, params.record, params.timeoutMs);
5530
+ return await createFreshRuntimeSession(params.client, params.record, params.timeoutMs, params.authority);
5385
5531
  }
5386
5532
  async function loadRuntimeSession(params, resume) {
5387
5533
  try {
5388
- const loadResult = await withTimeout(resume ? params.client.resumeSession(params.record.acpSessionId, params.record.cwd) : params.client.loadSessionWithOptions(params.record.acpSessionId, params.record.cwd, { suppressReplayUpdates: true }), params.timeoutMs);
5534
+ const loadResult = await withTimeout(resume ? params.client.resumeSession(params.record.acpSessionId, params.record.cwd, params.authority) : params.client.loadSessionWithOptions(params.record.acpSessionId, params.record.cwd, {
5535
+ suppressReplayUpdates: true,
5536
+ ...params.authority ? { authority: params.authority } : {}
5537
+ }), params.timeoutMs);
5389
5538
  reconcileAgentSessionId(params.record, loadResult.agentSessionId);
5390
5539
  applyConfigOptionsToRecord(params.record, loadResult);
5391
5540
  return {
@@ -5398,9 +5547,14 @@ async function loadRuntimeSession(params, resume) {
5398
5547
  createdFreshSession: false
5399
5548
  };
5400
5549
  } catch (error) {
5550
+ rethrowCancelledLoad(error, params.authority);
5401
5551
  return await recoverRuntimeSessionLoadFailure(params, error);
5402
5552
  }
5403
5553
  }
5554
+ function rethrowCancelledLoad(error, authority) {
5555
+ const signal = authority?.signal;
5556
+ if (signal?.aborted && (error instanceof AgentDisconnectedError || error === signal.reason)) throw signal.reason;
5557
+ }
5404
5558
  async function recoverRuntimeSessionLoadFailure(params, error) {
5405
5559
  const loadError = formatErrorMessage(error);
5406
5560
  if (params.sameSessionOnly) throw makeSessionResumeRequiredError({
@@ -5410,12 +5564,12 @@ async function recoverRuntimeSessionLoadFailure(params, error) {
5410
5564
  });
5411
5565
  if (!shouldFallbackToNewSession(error, params.record)) throw error;
5412
5566
  return {
5413
- ...await createFreshRuntimeSession(params.client, params.record, params.timeoutMs),
5567
+ ...await createFreshRuntimeSession(params.client, params.record, params.timeoutMs, params.authority),
5414
5568
  loadError
5415
5569
  };
5416
5570
  }
5417
- async function createFreshRuntimeSession(client, record, timeoutMs) {
5418
- const createdSession = await withTimeout(client.createSession(record.cwd), timeoutMs);
5571
+ async function createFreshRuntimeSession(client, record, timeoutMs, authority) {
5572
+ const createdSession = await withTimeout(client.createSession(record.cwd, authority), timeoutMs);
5419
5573
  applyConfigOptionsToRecord(record, createdSession);
5420
5574
  return {
5421
5575
  sessionId: createdSession.sessionId,
@@ -5482,6 +5636,7 @@ async function withConnectedSession(options) {
5482
5636
  client,
5483
5637
  record,
5484
5638
  resumePolicy: options.resumePolicy,
5639
+ replacingMode: options.replacingMode,
5485
5640
  replacingConfigOption: options.replacingConfigOption,
5486
5641
  authority: options.authority,
5487
5642
  timeoutMs: options.timeoutMs,
@@ -5542,6 +5697,7 @@ async function withConnectedSession(options) {
5542
5697
  function buildDirectConnectedSessionOptions(options, run) {
5543
5698
  return {
5544
5699
  sessionRecordId: options.sessionRecordId,
5700
+ replacingMode: options.replacingMode,
5545
5701
  replacingConfigOption: options.replacingConfigOption,
5546
5702
  loadRecord: resolveSessionRecord,
5547
5703
  saveRecord: writeSessionRecord,
@@ -5592,7 +5748,10 @@ async function withOwnedDirectSession(options) {
5592
5748
  }
5593
5749
  }
5594
5750
  async function runSessionSetModeDirect(options) {
5595
- return toSessionMutationResult(await withOwnedDirectSession(buildDirectConnectedSessionOptions(options, async ({ client, sessionId, record }) => {
5751
+ return toSessionMutationResult(await withOwnedDirectSession(buildDirectConnectedSessionOptions({
5752
+ ...options,
5753
+ replacingMode: true
5754
+ }, async ({ client, sessionId, record }) => {
5596
5755
  await withTimeout(client.setSessionMode(sessionId, options.modeId), options.timeoutMs);
5597
5756
  setDesiredModeId(record, options.modeId);
5598
5757
  })));
@@ -5806,8 +5965,14 @@ const sessionControlTestInternals = {
5806
5965
  };
5807
5966
  async function closeSession(sessionId) {
5808
5967
  const record = await resolveSessionRecord(sessionId);
5809
- await tryCloseSessionOnRunningOwner({ sessionId: record.acpxRecordId }).catch(() => {});
5810
- await terminateQueueOwnerForSession(record.acpxRecordId);
5968
+ let selectedOwner;
5969
+ await tryCloseSessionOnRunningOwner({
5970
+ sessionId: record.acpxRecordId,
5971
+ onOwnerSelected: (owner) => {
5972
+ selectedOwner = owner;
5973
+ }
5974
+ }).catch(() => {});
5975
+ if (selectedOwner) await terminateQueueOwnerForSession(record.acpxRecordId, selectedOwner);
5811
5976
  if (record.pid != null && isProcessAlive(record.pid) && await isLikelyMatchingProcess(record.pid, record.agentCommand)) await terminateProcess(record.pid);
5812
5977
  const ownership = await acquireSessionTurn(record.acpxRecordId);
5813
5978
  try {
@@ -5825,9 +5990,11 @@ async function closeSession(sessionId) {
5825
5990
  //#region src/session/execution/session-management.ts
5826
5991
  async function createSessionRecordWithClient(client, options) {
5827
5992
  const cwd = absolutePath(options.cwd);
5828
- await withTimeout(client.start(), options.timeoutMs);
5993
+ await withTimeout(client.start({ signal: options.signal }), options.timeoutMs);
5994
+ assertControlAuthority({ signal: options.signal });
5829
5995
  const createdState = options.resumeSessionId ? await resumeSessionRecordWithClient(client, options, cwd) : await createFreshSessionState(client, options, cwd);
5830
5996
  const { sessionId, agentSessionId } = createdState;
5997
+ assertControlAuthority({ signal: options.signal });
5831
5998
  const lifecycle = client.getAgentLifecycleSnapshot();
5832
5999
  const record = {
5833
6000
  ...createInitialSessionRecord({
@@ -5849,10 +6016,12 @@ async function createSessionRecordWithClient(client, options) {
5849
6016
  applyConfigOptionsToRecord(record, createdState.sessionResult);
5850
6017
  applyInitialModelSelection(record, createdState.sessionResult.models, options.sessionOptions?.model, createdState.modelApplication);
5851
6018
  await writeSessionRecord(record);
6019
+ assertControlAuthority({ signal: options.signal });
5852
6020
  return record;
5853
6021
  }
5854
6022
  async function createFreshSessionState(client, options, cwd) {
5855
- const createdSession = await withTimeout(client.createSession(cwd), options.timeoutMs);
6023
+ const createdSession = await withTimeout(client.createSession(cwd, { signal: options.signal }), options.timeoutMs);
6024
+ assertControlAuthority({ signal: options.signal });
5856
6025
  const modelApplication = await applyRequestedModelIfAdvertised({
5857
6026
  client,
5858
6027
  sessionId: createdSession.sessionId,
@@ -5860,7 +6029,8 @@ async function createFreshSessionState(client, options, cwd) {
5860
6029
  models: createdSession.models,
5861
6030
  agentCommand: options.agentCommand,
5862
6031
  timeoutMs: options.timeoutMs,
5863
- onWarning: options.onModelWarning
6032
+ onWarning: options.onModelWarning,
6033
+ authority: { signal: options.signal }
5864
6034
  });
5865
6035
  return {
5866
6036
  sessionId: createdSession.sessionId,
@@ -5874,8 +6044,9 @@ async function resumeSessionRecordWithClient(client, options, cwd) {
5874
6044
  const resumeMethod = client.supportsResumeSession() ? "session/resume" : client.supportsLoadSession() ? "session/load" : void 0;
5875
6045
  if (!resumeMethod) throw new Error(`Agent command "${options.agentCommand}" does not support session/resume or session/load; cannot resume session ${options.resumeSessionId}`);
5876
6046
  try {
5877
- const resumedSession = await withTimeout(resumeMethod === "session/resume" ? client.resumeSession(options.resumeSessionId, cwd) : client.loadSession(options.resumeSessionId, cwd), options.timeoutMs);
6047
+ const resumedSession = await withTimeout(resumeMethod === "session/resume" ? client.resumeSession(options.resumeSessionId, cwd, { signal: options.signal }) : client.loadSessionWithOptions(options.resumeSessionId, cwd, { authority: { signal: options.signal } }), options.timeoutMs);
5878
6048
  const sessionModels = resumedSession.models;
6049
+ assertControlAuthority({ signal: options.signal });
5879
6050
  const modelApplication = await applyRequestedModelIfAdvertised({
5880
6051
  client,
5881
6052
  sessionId: options.resumeSessionId,
@@ -5883,7 +6054,8 @@ async function resumeSessionRecordWithClient(client, options, cwd) {
5883
6054
  models: sessionModels,
5884
6055
  agentCommand: options.agentCommand,
5885
6056
  timeoutMs: options.timeoutMs,
5886
- onWarning: options.onModelWarning
6057
+ onWarning: options.onModelWarning,
6058
+ authority: { signal: options.signal }
5887
6059
  });
5888
6060
  return {
5889
6061
  sessionId: options.resumeSessionId,
@@ -5896,7 +6068,7 @@ async function resumeSessionRecordWithClient(client, options, cwd) {
5896
6068
  }
5897
6069
  }
5898
6070
  async function createSessionWithClient(options) {
5899
- options.signal?.throwIfAborted();
6071
+ assertControlAuthority({ signal: options.signal });
5900
6072
  const client = new AcpClient({
5901
6073
  agentCommand: options.agentCommand,
5902
6074
  agentArgv: options.agentArgv,
@@ -5912,28 +6084,25 @@ async function createSessionWithClient(options) {
5912
6084
  verbose: options.verbose,
5913
6085
  sessionOptions: options.sessionOptions
5914
6086
  });
5915
- const onAbort = () => {
5916
- client.close().catch(() => {});
5917
- };
5918
- options.signal?.addEventListener("abort", onAbort, { once: true });
6087
+ const closeOwnedClient = ownDirectClient(client, options.signal);
5919
6088
  try {
5920
6089
  const create = async () => {
5921
- options.signal?.throwIfAborted();
6090
+ assertControlAuthority({ signal: options.signal });
5922
6091
  const record = await createSessionRecordWithClient(client, options);
5923
- options.signal?.throwIfAborted();
6092
+ assertControlAuthority({ signal: options.signal });
5924
6093
  return record;
5925
6094
  };
5926
6095
  return {
5927
6096
  record: options.handleProcessInterrupts === false ? await create() : await withInterrupt(create, async () => {
5928
- await client.close();
6097
+ await closeOwnedClient();
5929
6098
  }),
5930
6099
  client
5931
6100
  };
5932
6101
  } catch (error) {
5933
- await client.close();
5934
- throw error;
6102
+ await closeOwnedClient();
6103
+ throw directExecutionError(error, options.signal);
5935
6104
  } finally {
5936
- options.signal?.removeEventListener("abort", onAbort);
6105
+ closeOwnedClient.release();
5937
6106
  }
5938
6107
  }
5939
6108
  async function createSession(options) {
@@ -5999,7 +6168,7 @@ async function ensureSession(options) {
5999
6168
  }
6000
6169
  }
6001
6170
  async function ensureSessionWithOwnership(options, cwd) {
6002
- options.signal?.throwIfAborted();
6171
+ assertControlAuthority({ signal: options.signal });
6003
6172
  const gitRoot = findGitRepositoryRoot(cwd);
6004
6173
  const walkBoundary = options.walkBoundary ?? gitRoot ?? cwd;
6005
6174
  const existing = await findSessionByDirectoryWalk({
@@ -6509,27 +6678,27 @@ const NODE_TEST_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
6509
6678
  "--test-reporter",
6510
6679
  "--test-reporter-destination"
6511
6680
  ]);
6512
- const INSPECTOR_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
6681
+ const INSPECTOR_FLAGS = /* @__PURE__ */ new Set([
6513
6682
  "--inspect",
6514
6683
  "--inspect-brk",
6684
+ "--inspect-wait"
6685
+ ]);
6686
+ const INSPECTOR_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
6515
6687
  "--inspect-port",
6516
6688
  "--inspect-publish-uid",
6517
6689
  "--debug-port"
6518
6690
  ]);
6519
- const INSPECTOR_FLAG_PREFIXES = [
6520
- "--inspect=",
6521
- "--inspect-brk=",
6522
- "--inspect-port=",
6523
- "--inspect-publish-uid=",
6524
- "--debug-port="
6525
- ];
6526
6691
  function classifyExecArgv(value) {
6527
- if (value === void 0) return "drop";
6528
- if (NODE_TEST_FLAGS_WITH_VALUE.has(value) || INSPECTOR_FLAGS_WITH_VALUE.has(value)) return "drop-with-value";
6529
- return dropSingleExecArgv(value) ? "drop" : "keep";
6692
+ if (NODE_TEST_FLAGS_WITH_VALUE.has(value)) return "drop-with-value";
6693
+ if (NODE_TEST_FLAGS.has(value) || value.startsWith("--test-")) return "drop";
6694
+ return classifyInspectorArgv(value);
6530
6695
  }
6531
- function dropSingleExecArgv(value) {
6532
- return NODE_TEST_FLAGS.has(value) || value.startsWith("--test-") || INSPECTOR_FLAG_PREFIXES.some((prefix) => value.startsWith(prefix));
6696
+ function classifyInspectorArgv(value) {
6697
+ const [name] = value.split("=", 1);
6698
+ if (!name?.startsWith("--")) return "keep";
6699
+ const flag = name.replaceAll("_", "-");
6700
+ if (INSPECTOR_FLAGS_WITH_VALUE.has(flag)) return value.includes("=") ? "drop" : "drop-with-value";
6701
+ return INSPECTOR_FLAGS.has(flag) ? "drop" : "keep";
6533
6702
  }
6534
6703
  function sanitizeQueueOwnerExecArgv(execArgv = process.execArgv) {
6535
6704
  const sanitized = [];
@@ -6889,6 +7058,7 @@ async function applyPromptModelIfAdvertised(params) {
6889
7058
  models: advertisedModelState(params.record.acpx),
6890
7059
  agentCommand: params.record.agentCommand,
6891
7060
  timeoutMs: params.timeoutMs,
7061
+ authority: { signal: params.signal },
6892
7062
  onWarning: params.suppressWarnings ? void 0 : (message) => process.stderr.write(`[acpx] warning: ${message}\n`)
6893
7063
  });
6894
7064
  if (result.applied) params.record.acpx = applyModelSelection(params.record.acpx, requestedModel, result.response);
@@ -7073,7 +7243,7 @@ async function waitForSessionTurn(options) {
7073
7243
  "SIGTERM",
7074
7244
  "SIGHUP"
7075
7245
  ];
7076
- for (const signal of signals) process.once(signal, onInterrupt);
7246
+ if (options.handleProcessInterrupts !== false) for (const signal of signals) process.once(signal, onInterrupt);
7077
7247
  const timeoutMs = options.timeoutMs;
7078
7248
  const timeout = timeoutMs != null && timeoutMs > 0 ? setTimeout(() => waiting.abort(new TimeoutError(timeoutMs)), timeoutMs) : void 0;
7079
7249
  const signal = options.waitSignal ? AbortSignal.any([waiting.signal, options.waitSignal]) : waiting.signal;
@@ -7084,18 +7254,22 @@ async function waitForSessionTurn(options) {
7084
7254
  ownership: await acquireSessionTurn(acpxRecordId, signal)
7085
7255
  };
7086
7256
  } catch (error) {
7087
- signal.throwIfAborted();
7088
- throw error;
7257
+ return throwTurnAcquisitionError(error, signal);
7089
7258
  } finally {
7090
7259
  clearTimeout(timeout);
7091
7260
  for (const signal of signals) process.off(signal, onInterrupt);
7092
7261
  }
7093
7262
  }
7263
+ function throwTurnAcquisitionError(error, signal) {
7264
+ if (signal.aborted && error instanceof Error && error.name === "AbortError" && error.cause === signal.reason) throw signal.reason;
7265
+ throw error;
7266
+ }
7094
7267
  async function runSessionPrompt(options) {
7095
7268
  let turn;
7096
7269
  try {
7097
7270
  turn = await waitForSessionTurn(options);
7098
7271
  } catch (error) {
7272
+ if (options.ownedSignal) throw error;
7099
7273
  if (options.waitSignal?.aborted && error === options.waitSignal.reason) {
7100
7274
  const record = await resolveSessionRecord(options.sessionRecordId);
7101
7275
  return {
@@ -7215,8 +7389,10 @@ async function runOwnedSessionPrompt(options) {
7215
7389
  const record = await measurePerf("session.resolve_prompt_record", async () => {
7216
7390
  return await resolveSessionRecord(options.sessionRecordId);
7217
7391
  });
7392
+ options.ownedSignal?.throwIfAborted();
7218
7393
  const { conversation, promptMessageId } = preparePromptConversation(record, options);
7219
7394
  await writeSessionRecord(record);
7395
+ options.ownedSignal?.throwIfAborted();
7220
7396
  output.setContext({ sessionId: record.acpxRecordId });
7221
7397
  const eventWriter = await measurePerf("session.events.open", async () => {
7222
7398
  return await SessionEventWriter.open(record);
@@ -7259,7 +7435,7 @@ async function runOwnedSessionPrompt(options) {
7259
7435
  if (options.verbose) process.stderr.write("[acpx] live session checkpoint failed: " + formatErrorMessage(error) + "\n");
7260
7436
  }
7261
7437
  });
7262
- let closeClientOnExit = options.client == null;
7438
+ let closeClientOnExit = options.client == null || options.ownedSignal !== void 0;
7263
7439
  const client = options.client ?? new AcpClient({
7264
7440
  agentCommand: record.agentCommand,
7265
7441
  agentArgv: record.agentArgv,
@@ -7285,6 +7461,7 @@ async function runOwnedSessionPrompt(options) {
7285
7461
  suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
7286
7462
  verbose: options.verbose
7287
7463
  });
7464
+ const closeOwnedClient = options.closeProvidedClient ?? ownDirectClient(client, options.ownedSignal);
7288
7465
  client.setEventHandlers({
7289
7466
  onAcpMessage: (direction, message) => {
7290
7467
  pendingMessages.push(message);
@@ -7358,6 +7535,7 @@ async function runOwnedSessionPrompt(options) {
7358
7535
  verbose: options.verbose,
7359
7536
  suppressWarnings: options.suppressSdkConsoleErrors,
7360
7537
  activeController,
7538
+ authority: { signal: options.ownedSignal },
7361
7539
  onClientAvailable: (controller) => {
7362
7540
  options.onClientAvailable?.(controller);
7363
7541
  notifiedClientAvailable = true;
@@ -7464,15 +7642,19 @@ async function runOwnedSessionPrompt(options) {
7464
7642
  return response;
7465
7643
  };
7466
7644
  const runPrompt = async () => {
7645
+ options.ownedSignal?.throwIfAborted();
7467
7646
  const { sessionId: activeSessionId, resumed, loadError } = await connectForPrompt();
7647
+ options.ownedSignal?.throwIfAborted();
7468
7648
  await applyPromptModelIfAdvertised({
7469
7649
  client,
7470
7650
  sessionId: activeSessionId,
7471
7651
  requestedModel: options.sessionOptions?.model,
7472
7652
  record,
7473
7653
  timeoutMs: options.timeoutMs,
7474
- suppressWarnings: options.suppressSdkConsoleErrors
7654
+ suppressWarnings: options.suppressSdkConsoleErrors,
7655
+ signal: options.ownedSignal
7475
7656
  });
7657
+ options.ownedSignal?.throwIfAborted();
7476
7658
  output.setContext({ sessionId: record.acpxRecordId });
7477
7659
  await liveCheckpoint.checkpoint();
7478
7660
  const response = await savePromptSuccess(await runPromptWithRetries(activeSessionId));
@@ -7490,20 +7672,23 @@ async function runOwnedSessionPrompt(options) {
7490
7672
  record.lastUsedAt = isoNow$1();
7491
7673
  applyConversation(record, conversation);
7492
7674
  await flushPendingMessages(false).catch(() => {});
7493
- if (closeClientOnExit) await client.close();
7675
+ if (closeClientOnExit) await closeOwnedClient();
7494
7676
  };
7495
7677
  const runObservedPrompt = async () => {
7496
7678
  try {
7497
- return await runWithOptionalInterrupt({
7679
+ const result = await runWithOptionalInterrupt({
7498
7680
  handleProcessInterrupts: options.handleProcessInterrupts,
7499
7681
  run: runPrompt,
7500
7682
  handleInterrupt
7501
7683
  });
7684
+ options.ownedSignal?.throwIfAborted();
7685
+ return result;
7502
7686
  } catch (error) {
7503
- const matchedAcpError = acpErrors.match(error);
7504
- attachAcpErrorPayload(error, matchedAcpError);
7505
- markOutputAlreadyEmitted(error, matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted);
7506
- throw error;
7687
+ const failure = directExecutionError(error, options.ownedSignal);
7688
+ const matchedAcpError = acpErrors.match(failure);
7689
+ attachAcpErrorPayload(failure, matchedAcpError);
7690
+ markOutputAlreadyEmitted(failure, matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted);
7691
+ throw failure;
7507
7692
  }
7508
7693
  };
7509
7694
  const cleanupPrompt = async (attempt) => {
@@ -7516,7 +7701,7 @@ async function runOwnedSessionPrompt(options) {
7516
7701
  async () => {
7517
7702
  const journalFailed = attempt.status === "rejected" && attempt.error instanceof AcpxOperationalError && attempt.error.detailCode === "SESSION_JOURNAL_WRITE_FAILED";
7518
7703
  if (unresolvedPrompt || closeClientOnExit || journalFailed) try {
7519
- await client.close();
7704
+ await closeOwnedClient();
7520
7705
  } catch (error) {
7521
7706
  options.onClientCloseFailure?.();
7522
7707
  throw error;
@@ -7554,7 +7739,8 @@ async function runOwnedSessionPrompt(options) {
7554
7739
  cleanup: cleanupPrompt
7555
7740
  });
7556
7741
  }
7557
- async function runOnce(options) {
7742
+ async function runOnce(options, control) {
7743
+ const authority = { signal: control?.signal };
7558
7744
  const output = options.outputFormatter;
7559
7745
  const shouldMarkAcpErrorsEmitted = rendersAcpErrors(options.errorEmissionPolicy);
7560
7746
  let promptTurnActive = false;
@@ -7594,10 +7780,12 @@ async function runOnce(options) {
7594
7780
  },
7595
7781
  sessionOptions: options.sessionOptions
7596
7782
  });
7783
+ const closeOwnedClient = ownDirectClient(client, control?.signal);
7597
7784
  const runExecPromptAttempt = async (sessionId) => {
7785
+ assertControlAuthority(authority);
7598
7786
  acpErrors.reset();
7599
7787
  return await measurePerf("runtime.exec.prompt", async () => {
7600
- return await withTimeout(client.prompt(sessionId, options.prompt), options.timeoutMs);
7788
+ return await withTimeout(client.prompt(sessionId, options.prompt, void 0, void 0, void 0, { signal: control?.signal }), options.timeoutMs);
7601
7789
  });
7602
7790
  };
7603
7791
  const runExecPromptWithRetries = async (sessionId) => {
@@ -7606,78 +7794,102 @@ async function runOnce(options) {
7606
7794
  for (let attempt = 0;; attempt++) try {
7607
7795
  return await runExecPromptAttempt(sessionId);
7608
7796
  } catch (error) {
7609
- if (await preparePromptRetry(error, attempt, maxRetries, () => promptTurnHadSideEffects, options.suppressSdkConsoleErrors)) continue;
7797
+ if (await preparePromptRetry(error, attempt, maxRetries, () => promptTurnHadSideEffects, options.suppressSdkConsoleErrors, control?.signal)) continue;
7610
7798
  promptTurnActive = false;
7611
7799
  throw error;
7612
7800
  }
7613
7801
  };
7614
7802
  try {
7615
- return await withInterrupt(async () => {
7616
- await measurePerf("runtime.exec.start", async () => {
7617
- await withTimeout(client.start(), options.timeoutMs);
7618
- });
7619
- const createdSession = await measurePerf("runtime.exec.create_session", async () => {
7620
- return await withTimeout(client.createSession(absolutePath(options.cwd)), options.timeoutMs);
7621
- });
7622
- const sessionId = createdSession.sessionId;
7623
- if (createdSession.models) applyAdvertisedModelState(controlState, createdSession.models);
7624
- controlState = applyConfigOptionsToState(controlState, createdSession.configOptions ?? []);
7625
- const modelApplication = await applyRequestedModelIfAdvertised({
7626
- client,
7627
- sessionId,
7628
- requestedModel: options.sessionOptions?.model,
7629
- models: createdSession.models,
7630
- agentCommand: options.agentCommand,
7631
- timeoutMs: options.timeoutMs,
7632
- onWarning: options.suppressSdkConsoleErrors ? void 0 : (message) => process.stderr.write(`[acpx] warning: ${message}\n`)
7633
- });
7634
- if (modelApplication.response) controlState = applyConfigOptionsToState(controlState, modelApplication.response.configOptions);
7635
- for (const configOption of options.configOptions ?? []) {
7636
- const response = await withTimeout(client.setSessionConfigOption(sessionId, configOption.configId, configOption.value, advertisedModelState(controlState)), options.timeoutMs);
7637
- controlState = applyConfigOptionsToState(controlState, response.configOptions);
7803
+ return await runWithOptionalInterrupt({
7804
+ handleProcessInterrupts: control?.handleProcessInterrupts,
7805
+ run: async () => {
7806
+ assertControlAuthority(authority);
7807
+ await measurePerf("runtime.exec.start", async () => {
7808
+ await withTimeout(client.start(authority), options.timeoutMs);
7809
+ });
7810
+ assertControlAuthority(authority);
7811
+ const createdSession = await measurePerf("runtime.exec.create_session", async () => {
7812
+ return await withTimeout(client.createSession(absolutePath(options.cwd), authority), options.timeoutMs);
7813
+ });
7814
+ assertControlAuthority(authority);
7815
+ const sessionId = createdSession.sessionId;
7816
+ if (createdSession.models) applyAdvertisedModelState(controlState, createdSession.models);
7817
+ controlState = applyConfigOptionsToState(controlState, createdSession.configOptions ?? []);
7818
+ const modelApplication = await applyRequestedModelIfAdvertised({
7819
+ client,
7820
+ sessionId,
7821
+ requestedModel: options.sessionOptions?.model,
7822
+ models: createdSession.models,
7823
+ agentCommand: options.agentCommand,
7824
+ timeoutMs: options.timeoutMs,
7825
+ authority,
7826
+ onWarning: options.suppressSdkConsoleErrors ? void 0 : (message) => process.stderr.write(`[acpx] warning: ${message}\n`)
7827
+ });
7828
+ if (modelApplication.response) controlState = applyConfigOptionsToState(controlState, modelApplication.response.configOptions);
7829
+ for (const configOption of options.configOptions ?? []) {
7830
+ assertControlAuthority(authority);
7831
+ const response = await withTimeout(client.setSessionConfigOption(sessionId, configOption.configId, configOption.value, advertisedModelState(controlState), authority), options.timeoutMs);
7832
+ controlState = applyConfigOptionsToState(controlState, response.configOptions);
7833
+ }
7834
+ assertControlAuthority(authority);
7835
+ output.setContext({ sessionId });
7836
+ const response = await runExecPromptWithRetries(sessionId);
7837
+ promptTurnActive = false;
7838
+ return toPromptResult(response.stopReason, sessionId, client, response._meta);
7839
+ },
7840
+ handleInterrupt: async () => {
7841
+ await client.cancelActivePrompt(INTERRUPT_CANCEL_WAIT_MS);
7842
+ await closeOwnedClient();
7638
7843
  }
7639
- output.setContext({ sessionId });
7640
- const response = await runExecPromptWithRetries(sessionId);
7641
- promptTurnActive = false;
7642
- output.flush();
7643
- return toPromptResult(response.stopReason, sessionId, client, response._meta);
7644
- }, async () => {
7645
- await client.cancelActivePrompt(INTERRUPT_CANCEL_WAIT_MS);
7646
- await client.close();
7647
7844
  });
7648
7845
  } catch (error) {
7649
- const matchedAcpError = acpErrors.match(error);
7650
- attachAcpErrorPayload(error, matchedAcpError);
7651
- markOutputAlreadyEmitted(error, matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted);
7652
- throw error;
7846
+ const failure = directExecutionError(error, authority.signal);
7847
+ const matchedAcpError = acpErrors.match(failure);
7848
+ attachAcpErrorPayload(failure, matchedAcpError);
7849
+ markOutputAlreadyEmitted(failure, matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted);
7850
+ throw failure;
7653
7851
  } finally {
7654
- await client.close();
7852
+ try {
7853
+ await closeOwnedClient();
7854
+ } finally {
7855
+ output.flush();
7856
+ }
7655
7857
  }
7656
7858
  }
7657
- async function sendSessionDirect(options) {
7658
- return await runSessionPrompt({
7659
- sessionRecordId: options.sessionId,
7660
- prompt: options.prompt,
7661
- mcpServers: options.mcpServers,
7662
- permissionMode: options.permissionMode,
7663
- resumePolicy: options.resumePolicy,
7664
- nonInteractivePermissions: options.nonInteractivePermissions,
7665
- permissionPolicy: options.permissionPolicy,
7666
- authCredentials: options.authCredentials,
7667
- authPolicy: options.authPolicy,
7668
- fs: options.fs,
7669
- terminal: options.terminal,
7670
- outputFormatter: options.outputFormatter,
7671
- errorEmissionPolicy: options.errorEmissionPolicy,
7672
- onAcpMessage: options.onAcpMessage,
7673
- onSessionUpdate: options.onSessionUpdate,
7674
- onClientOperation: options.onClientOperation,
7675
- onPermissionEscalation: options.onPermissionEscalation,
7676
- timeoutMs: options.timeoutMs,
7677
- suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
7678
- verbose: options.verbose,
7679
- client: options.client
7680
- });
7859
+ async function sendSessionDirect(options, control) {
7860
+ const closeProvidedClient = control && options.client ? ownDirectClient(options.client, control.signal) : void 0;
7861
+ try {
7862
+ control?.signal.throwIfAborted();
7863
+ return await runSessionPrompt({
7864
+ ownedSignal: control?.signal,
7865
+ closeProvidedClient,
7866
+ waitSignal: control?.signal,
7867
+ handleProcessInterrupts: control?.handleProcessInterrupts,
7868
+ sessionRecordId: options.sessionId,
7869
+ prompt: options.prompt,
7870
+ mcpServers: options.mcpServers,
7871
+ permissionMode: options.permissionMode,
7872
+ resumePolicy: options.resumePolicy,
7873
+ nonInteractivePermissions: options.nonInteractivePermissions,
7874
+ permissionPolicy: options.permissionPolicy,
7875
+ authCredentials: options.authCredentials,
7876
+ authPolicy: options.authPolicy,
7877
+ fs: options.fs,
7878
+ terminal: options.terminal,
7879
+ outputFormatter: options.outputFormatter,
7880
+ errorEmissionPolicy: options.errorEmissionPolicy,
7881
+ onAcpMessage: options.onAcpMessage,
7882
+ onSessionUpdate: options.onSessionUpdate,
7883
+ onClientOperation: options.onClientOperation,
7884
+ onPermissionEscalation: options.onPermissionEscalation,
7885
+ timeoutMs: options.timeoutMs,
7886
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
7887
+ verbose: options.verbose,
7888
+ client: options.client
7889
+ });
7890
+ } finally {
7891
+ await closeProvidedClient?.();
7892
+ }
7681
7893
  }
7682
7894
  //#endregion
7683
7895
  //#region src/session/execution/queue-owner-runtime.ts
@@ -8081,4 +8293,4 @@ const queueOwnerRuntimeTestInternals = { queueOwnerExitIsFatal };
8081
8293
  //#endregion
8082
8294
  export { getAcpxVersion as $, applyInitialModelSelection as A, cloneSessionAcpxState as B, setSessionModeOnOwner as C, connectAndLoadSession as D, withConnectedSession as E, sessionOptionsFromRecord as F, recordSessionUpdate as G, createSessionConversation as H, applyConversation as I, AcpClient as J, trimConversationForRuntime as K, applyLifecycleSnapshotToRecord as L, applyRequestedModelIfAdvertised as M, setDesiredModeId as N, applyConfigOptionSelection as O, persistSessionOptions as P, isRequestedModelUnsupportedError as Q, createInitialSessionRecord as R, setSessionMode as S, setSessionModelOnOwner as T, recordClientOperation as U, cloneSessionConversation as V, recordPromptSubmission as W, REQUESTED_MODEL_UNSUPPORTED_REASONS as X, REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE as Y, RequestedModelUnsupportedError as Z, cancelSessionPrompt as _, runQueuedTask as a, InterruptedError as at, setSessionConfigOption as b, LiveSessionCheckpoint as c, withInterrupt as ct, flushPerfMetricsCapture as d, normalizeOutputError as dt, permissionModeSatisfies as et, installPerfMetricsCapture as f, listAgentSessions as g, ensureSession as h, runOnce as i, isLegacyZedCodexAcpInvocation as it, applyModelSelection as j, applyConfigOptionsToRecord as k, runPromptTurn as l, withTimeout as lt, createSessionWithClient as m, runSessionQueueOwner as n, normalizeQueueOwnerTtlMs as nt, sendSessionDirect as o, TimeoutError as ot, createSession as p, advertisedModelState as q, sendSession as r, listSessionEvents as rt, DISCARD_OUTPUT_FORMATTER as s, assertControlAuthority as st, queueOwnerRuntimeTestInternals as t, DEFAULT_QUEUE_OWNER_TTL_MS as tt, buildQueueOwnerArgOverride as u, exitCodeForOutputErrorCode as ut, closeSession as v, setSessionModel as w, setSessionConfigOptionOnOwner as x, sessionControlTestInternals as y, reconcileAgentSessionId as z };
8083
8295
 
8084
- //# sourceMappingURL=queue-owner-runtime-Cr0dWJuL.js.map
8296
+ //# sourceMappingURL=queue-owner-runtime-CLwjMYNL.js.map