acpx 0.18.0 → 0.19.1

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-DWlx5BQ4.js} +5 -3
  2. package/dist/agent-registry-DWlx5BQ4.js.map +1 -0
  3. package/dist/agent-registry.js +1 -1
  4. package/dist/{cli-8mVHeUXh.js → cli-BerngYEz.js} +5 -5
  5. package/dist/{cli-8mVHeUXh.js.map → cli-BerngYEz.js.map} +1 -1
  6. package/dist/cli.js +77 -53
  7. package/dist/cli.js.map +1 -1
  8. package/dist/{client-DeNKZZcu.d.ts → client-BchvQRpz.d.ts} +17 -4
  9. package/dist/client-BchvQRpz.d.ts.map +1 -0
  10. package/dist/{flows-WOQNzCMC.js → flows-DimX0it5.js} +693 -475
  11. package/dist/flows-DimX0it5.js.map +1 -0
  12. package/dist/flows.d.ts +20 -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-vobVApfO.js} +32 -11
  16. package/dist/invocation-options-vobVApfO.js.map +1 -0
  17. package/dist/{ipc-BJrvfNXr.js → ipc-DAApJ5mi.js} +1041 -263
  18. package/dist/ipc-DAApJ5mi.js.map +1 -0
  19. package/dist/{output-CL18YmsG.js → output-Cnuji8Bo.js} +41 -20
  20. package/dist/output-Cnuji8Bo.js.map +1 -0
  21. package/dist/{queue-owner-runtime-Cr0dWJuL.js → queue-owner-runtime-C4OOosbo.js} +732 -507
  22. package/dist/queue-owner-runtime-C4OOosbo.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 +48 -29
  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-Bj9LoYUq.js} +5 -5
  29. package/dist/session-watch-Bj9LoYUq.js.map +1 -0
  30. package/dist/{watch-Dp73ph8h.js → watch-Cz6jbrRs.js} +2 -2
  31. package/dist/{watch-Dp73ph8h.js.map → watch-Cz6jbrRs.js.map} +1 -1
  32. package/package.json +1 -1
  33. package/skills/acpx/SKILL.md +45 -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 ProcessDescendants, xt as formatUnknownErrorMessage, y as createLockOwner, yt as isProcessAlive, z as resolveSessionRecord, zt as GeminiAcpStartupTimeoutError } from "./ipc-DAApJ5mi.js";
2
+ import { A as requireAgentStdio, C as PROCESS_HELPER_TIMEOUT_MS, D as isoNow$2, E as isChildProcessRunning, F as waitForChildExit, I as waitForSpawn, M as resolveAgentSessionCwd, N as runTimedExecFile, P as splitCommandLine, T as basenameToken, _ as buildSpawnCommandOptions, b as readWindowsEnvValue, f as resolveBuiltInAgentLaunch, g as buildAgentSpawnCommand, j as resolveAgentCommandParts, v as buildTerminalShellSpawnCommand, w as asAbsoluteCwd, x as resolveWindowsExecutablePath, y as buildTerminalSpawnCommand } from "./agent-registry-DWlx5BQ4.js";
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))}`;
@@ -1078,63 +1080,22 @@ function compareVersionParts(left, right) {
1078
1080
  }
1079
1081
  return 0;
1080
1082
  }
1081
- async function detectGeminiVersion(command) {
1082
- const versionLine = (await readCommandOutput(command, ["--version"], GEMINI_VERSION_TIMEOUT_MS))?.split(/\r?\n/).map((line) => line.trim()).find((line) => /\d+\.\d+\.\d+/.test(line));
1083
+ async function detectGeminiVersion(command, context, diagnostic = false) {
1084
+ const versionLine = (await context.readOutput(command, ["--version"], GEMINI_VERSION_TIMEOUT_MS, diagnostic))?.split(/\r?\n/).map((line) => line.trim()).find((line) => /\d+\.\d+\.\d+/.test(line));
1083
1085
  return parseGeminiVersion(versionLine);
1084
1086
  }
1085
- async function resolveGeminiCommandArgs(command, args) {
1087
+ async function resolveGeminiCommandArgs(command, args, context) {
1086
1088
  if (basenameToken(command) !== "gemini" || !args.includes("--acp")) return [...args];
1087
- const version = await detectGeminiVersion(command);
1089
+ const version = await detectGeminiVersion(command, context);
1088
1090
  if (version && compareVersionParts(version.parts, GEMINI_ACP_FLAG_VERSION) < 0) return args.map((arg) => arg === "--acp" ? "--experimental-acp" : arg);
1089
1091
  return [...args];
1090
1092
  }
1091
- async function readCommandOutput(command, args, timeoutMs) {
1092
- return await new Promise((resolve) => {
1093
- const child = spawn(command, [...args], buildSpawnCommandOptions(command, {
1094
- stdio: [
1095
- "ignore",
1096
- "pipe",
1097
- "pipe"
1098
- ],
1099
- windowsHide: true
1100
- }));
1101
- let stdout = "";
1102
- let stderr = "";
1103
- let settled = false;
1104
- const finish = (value) => {
1105
- if (settled) return;
1106
- settled = true;
1107
- clearTimeout(timer);
1108
- child.removeAllListeners();
1109
- child.stdout?.removeAllListeners();
1110
- child.stderr?.removeAllListeners();
1111
- resolve(value);
1112
- };
1113
- const timer = setTimeout(() => {
1114
- child.kill("SIGKILL");
1115
- finish(void 0);
1116
- }, timeoutMs);
1117
- child.stdout?.setEncoding("utf8");
1118
- child.stderr?.setEncoding("utf8");
1119
- child.stdout?.on("data", (chunk) => {
1120
- stdout += chunk;
1121
- });
1122
- child.stderr?.on("data", (chunk) => {
1123
- stderr += chunk;
1124
- });
1125
- child.once("error", () => {
1126
- finish(void 0);
1127
- });
1128
- child.once("close", () => {
1129
- finish(`${stdout}\n${stderr}`);
1130
- });
1131
- });
1132
- }
1133
- async function buildGeminiAcpStartupTimeoutMessage(command) {
1093
+ async function buildGeminiAcpStartupTimeoutMessage(command, context) {
1134
1094
  const parts = ["Gemini CLI ACP startup timed out before initialize completed.", "This usually means the local Gemini CLI is waiting on interactive OAuth or has incompatible ACP subprocess behavior."];
1135
- const version = await detectGeminiVersion(command);
1095
+ const version = await detectGeminiVersion(command, context, true);
1136
1096
  if (version) parts.push(`Detected Gemini CLI version: ${version.raw}.`);
1137
- if (!process.env.GEMINI_API_KEY && !process.env.GOOGLE_API_KEY) parts.push("No GEMINI_API_KEY or GOOGLE_API_KEY was set for non-interactive auth.");
1097
+ const env = context.env;
1098
+ if (!env.GEMINI_API_KEY && !env.GOOGLE_API_KEY) parts.push("No GEMINI_API_KEY or GOOGLE_API_KEY was set for non-interactive auth.");
1138
1099
  parts.push("Try upgrading Gemini CLI and using API-key-based auth for non-interactive ACP runs.");
1139
1100
  return parts.join(" ");
1140
1101
  }
@@ -1145,16 +1106,17 @@ function buildClaudeAcpSessionCreateTimeoutMessage() {
1145
1106
  "In harnessed or non-interactive runs, prefer --approve-all with nonInteractivePermissions=deny, upgrade Claude Code and the Claude ACP adapter, or use acpx claude exec as a one-shot fallback."
1146
1107
  ].join(" ");
1147
1108
  }
1148
- async function buildCopilotAcpUnsupportedMessage(command) {
1149
- const parts = ["GitHub Copilot CLI ACP stdio mode is not available in the installed copilot binary.", "acpx copilot expects a Copilot CLI release that supports --acp --stdio."];
1150
- const helpOutput = await readCommandOutput(command, ["--help"], COPILOT_HELP_TIMEOUT_MS);
1151
- if (typeof helpOutput === "string" && !helpOutput.includes("--acp")) parts.push("Detected copilot --help output without --acp support.");
1152
- parts.push("Upgrade GitHub Copilot CLI to a release with ACP stdio support, or use --agent with another ACP-compatible adapter in the meantime.");
1153
- return parts.join(" ");
1109
+ function buildCopilotAcpUnsupportedMessage() {
1110
+ return [
1111
+ "GitHub Copilot CLI ACP stdio mode is not available in the installed copilot binary.",
1112
+ "acpx copilot expects a Copilot CLI release that supports --acp --stdio.",
1113
+ "Detected copilot --help output without --acp support.",
1114
+ "Upgrade GitHub Copilot CLI to a release with ACP stdio support, or use --agent with another ACP-compatible adapter in the meantime."
1115
+ ].join(" ");
1154
1116
  }
1155
- async function ensureCopilotAcpSupport(command) {
1156
- const helpOutput = await readCommandOutput(command, ["--help"], COPILOT_HELP_TIMEOUT_MS);
1157
- if (typeof helpOutput === "string" && !helpOutput.includes("--acp")) throw new CopilotAcpUnsupportedError(await buildCopilotAcpUnsupportedMessage(command), { retryable: false });
1117
+ async function ensureCopilotAcpSupport(command, context) {
1118
+ const helpOutput = await context.readOutput(command, ["--help"], COPILOT_HELP_TIMEOUT_MS);
1119
+ if (typeof helpOutput === "string" && !helpOutput.includes("--acp")) throw new CopilotAcpUnsupportedError(buildCopilotAcpUnsupportedMessage(), { retryable: false });
1158
1120
  }
1159
1121
  function buildClaudeCodeOptionsMeta(options, isolateUserSettings = false) {
1160
1122
  const claudeCodeOptions = {};
@@ -1375,6 +1337,73 @@ function resolveClientCapabilities(params) {
1375
1337
  };
1376
1338
  }
1377
1339
  //#endregion
1340
+ //#region src/acp/command-probe.ts
1341
+ var CommandProbeAdmissionError = class extends Error {
1342
+ reason;
1343
+ constructor(reason) {
1344
+ super("Agent command admission rejected", { cause: reason });
1345
+ this.reason = reason;
1346
+ this.name = "CommandProbeAdmissionError";
1347
+ }
1348
+ };
1349
+ async function admitCommandProbe(admit) {
1350
+ try {
1351
+ return await admit();
1352
+ } catch (error) {
1353
+ throw new CommandProbeAdmissionError(error);
1354
+ }
1355
+ }
1356
+ /** Collect only this short adapter command; its client owns process retirement. */
1357
+ function captureCommandProbeOutput(child, timeoutMs, retire) {
1358
+ let stdout = "";
1359
+ let stderr = "";
1360
+ let settled = false;
1361
+ let disposed = false;
1362
+ let timer;
1363
+ let resolveResult;
1364
+ const result = new Promise((resolve) => {
1365
+ resolveResult = resolve;
1366
+ });
1367
+ const finish = (value) => {
1368
+ if (settled) return;
1369
+ settled = true;
1370
+ clearTimeout(timer);
1371
+ resolveResult(value);
1372
+ };
1373
+ const fail = () => {
1374
+ if (disposed) return;
1375
+ finish(void 0);
1376
+ retire().catch(() => {});
1377
+ };
1378
+ const onStdout = (chunk) => {
1379
+ if (!settled) stdout += chunk;
1380
+ };
1381
+ const onStderr = (chunk) => {
1382
+ if (!settled) stderr += chunk;
1383
+ };
1384
+ const onClose = () => finish(`${stdout}\n${stderr}`);
1385
+ child.stdout?.setEncoding("utf8");
1386
+ child.stderr?.setEncoding("utf8");
1387
+ child.stdout?.on("data", onStdout);
1388
+ child.stderr?.on("data", onStderr);
1389
+ child.on("error", fail);
1390
+ child.stdout?.on("error", fail);
1391
+ child.stderr?.on("error", fail);
1392
+ child.once("close", onClose);
1393
+ timer = setTimeout(fail, timeoutMs);
1394
+ return {
1395
+ result,
1396
+ dispose: () => {
1397
+ disposed = true;
1398
+ finish(void 0);
1399
+ clearTimeout(timer);
1400
+ child.stdout?.off("data", onStdout);
1401
+ child.stderr?.off("data", onStderr);
1402
+ child.off("close", onClose);
1403
+ }
1404
+ };
1405
+ }
1406
+ //#endregion
1378
1407
  //#region src/acp/model-support.ts
1379
1408
  const REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE = "ACP_MODEL_UNSUPPORTED";
1380
1409
  const REQUESTED_MODEL_UNSUPPORTED_REASONS = ["missing-capability", "unadvertised-model"];
@@ -1556,15 +1585,22 @@ function enqueueNdJsonLine(agentCommand, line, controller) {
1556
1585
  console.error("Failed to parse JSON message:", trimmedLine, err);
1557
1586
  }
1558
1587
  }
1559
- function enqueueNdJsonLines(agentCommand, lines, controller) {
1588
+ function enqueueNdJsonChunk(agentCommand, chunk, fragments, controller) {
1589
+ const lines = chunk.split("\n");
1590
+ const suffix = lines.pop() || "";
1591
+ if (lines.length > 0 && fragments.length > 0) {
1592
+ lines[0] = fragments.join("") + lines[0];
1593
+ fragments.length = 0;
1594
+ }
1560
1595
  for (const line of lines) enqueueNdJsonLine(agentCommand, line, controller);
1596
+ if (suffix) fragments.push(suffix);
1561
1597
  }
1562
1598
  function createNdJsonMessageStream(agentCommand, output, input, maxMessageBytes, onReadError) {
1563
1599
  const textEncoder = new TextEncoder();
1564
1600
  const textDecoder = new TextDecoder();
1565
1601
  return {
1566
1602
  readable: new ReadableStream({ async start(controller) {
1567
- let content = "";
1603
+ const fragments = [];
1568
1604
  let retainedBytes = 0;
1569
1605
  const reader = input.getReader();
1570
1606
  try {
@@ -1572,10 +1608,7 @@ function createNdJsonMessageStream(agentCommand, output, input, maxMessageBytes,
1572
1608
  const { value, done } = await reader.read();
1573
1609
  if (done) break;
1574
1610
  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);
1611
+ enqueueNdJsonChunk(agentCommand, textDecoder.decode(value, { stream: true }), fragments, controller);
1579
1612
  }
1580
1613
  controller.close();
1581
1614
  } catch (err) {
@@ -1666,89 +1699,6 @@ function promptRequestOwner(message) {
1666
1699
  } : void 0;
1667
1700
  }
1668
1701
  //#endregion
1669
- //#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
- function includeDescendants(table, owned) {
1686
- let expanded;
1687
- do {
1688
- 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;
1692
- }
1693
- } while (expanded);
1694
- }
1695
- /** Best-effort POSIX cleanup for descendants witnessed during this child launch. */
1696
- var ProcessDescendants = class {
1697
- child;
1698
- identities = /* @__PURE__ */ new Map();
1699
- pending;
1700
- retired = false;
1701
- constructor(child) {
1702
- this.child = child;
1703
- }
1704
- capture(timeoutMs = PROCESS_HELPER_TIMEOUT_MS) {
1705
- if (this.retired || process.platform === "win32") return Promise.resolve(true);
1706
- this.pending ??= this.readSnapshot(timeoutMs).finally(() => {
1707
- this.pending = void 0;
1708
- });
1709
- return this.pending;
1710
- }
1711
- async readSnapshot(timeoutMs) {
1712
- try {
1713
- const output = await runTimedExecFile("ps", ["-eo", "pid=,ppid=,stat=,lstart="], { timeoutMs });
1714
- if (!this.retired) this.refresh(parseProcessTable(output));
1715
- return true;
1716
- } catch {
1717
- return false;
1718
- }
1719
- }
1720
- refresh(table) {
1721
- const owned = /* @__PURE__ */ new Set();
1722
- 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);
1725
- includeDescendants(table, owned);
1726
- owned.delete(root ?? 0);
1727
- this.identities = new Map([...table].filter(([pid]) => owned.has(pid)));
1728
- }
1729
- async signal(signal, timeoutMs) {
1730
- if (!await this.capture(timeoutMs) || this.retired) return;
1731
- for (const pid of this.identities.keys()) try {
1732
- process.kill(pid, signal);
1733
- } catch {
1734
- this.identities.delete(pid);
1735
- }
1736
- }
1737
- async waitForExit(timeoutMs) {
1738
- const deadline = Date.now() + timeoutMs;
1739
- do {
1740
- if (!await this.capture(Math.max(1, deadline - Date.now()))) return false;
1741
- if (this.identities.size === 0) return true;
1742
- await setTimeout$1(Math.min(100, Math.max(0, deadline - Date.now())));
1743
- } while (Date.now() < deadline);
1744
- return false;
1745
- }
1746
- retire() {
1747
- this.retired = true;
1748
- this.identities.clear();
1749
- }
1750
- };
1751
- //#endregion
1752
1702
  //#region src/acp/session-control-errors.ts
1753
1703
  const SESSION_CONTROL_UNSUPPORTED_ACP_CODES = /* @__PURE__ */ new Set([-32601, -32602]);
1754
1704
  function asRecord$1(value) {
@@ -1812,12 +1762,20 @@ function readTerminalOutputCeiling() {
1812
1762
  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
1763
  return bytes === 0 ? void 0 : bytes;
1814
1764
  }
1765
+ function resolveTerminalOutputLimit(requested, ceiling) {
1766
+ return Math.min(Math.max(0, Math.round(requested ?? DEFAULT_TERMINAL_OUTPUT_LIMIT_BYTES)), ceiling ?? Number.POSITIVE_INFINITY);
1767
+ }
1768
+ function tracksTerminalDescendants(terminal) {
1769
+ return terminal.killProcessGroup || terminal.descendants !== void 0;
1770
+ }
1771
+ function createTerminalDescendants(proc, killProcessGroup) {
1772
+ return process.platform === "win32" && killProcessGroup ? void 0 : new ProcessDescendants(proc, { ownProcessGroup: true });
1773
+ }
1815
1774
  function trimToUtf8Boundary(buffer, limit) {
1816
1775
  if (limit <= 0) return Buffer.alloc(0);
1817
- if (buffer.length <= limit) return buffer;
1818
- let start = buffer.length - limit;
1776
+ let start = Math.max(0, buffer.length - limit);
1819
1777
  while (start < buffer.length && (buffer[start] & 192) === 128) start += 1;
1820
- if (start >= buffer.length) start = buffer.length - limit;
1778
+ if (start >= buffer.length) return Buffer.alloc(0);
1821
1779
  return buffer.subarray(start);
1822
1780
  }
1823
1781
  async function defaultConfirmExecute(commandLine, signal) {
@@ -1854,7 +1812,8 @@ var TerminalManager = class {
1854
1812
  this.onOperation = options.onOperation;
1855
1813
  this.usesDefaultConfirmExecute = options.confirmExecute == null;
1856
1814
  this.confirmExecute = options.confirmExecute ?? defaultConfirmExecute;
1857
- this.killGraceMs = Math.max(0, Math.round(options.killGraceMs ?? DEFAULT_KILL_GRACE_MS));
1815
+ const killGraceMs = Math.max(0, Math.round(options.killGraceMs ?? DEFAULT_KILL_GRACE_MS));
1816
+ this.killGraceMs = Number.isFinite(killGraceMs) && killGraceMs <= 2147483647 ? killGraceMs : 1;
1858
1817
  this.processHelperTimeoutMs = Math.max(1, Math.round(options.processHelperTimeoutMs ?? 8e3));
1859
1818
  }
1860
1819
  updatePermissionPolicy(permissionMode, nonInteractivePermissions) {
@@ -1875,8 +1834,7 @@ var TerminalManager = class {
1875
1834
  const approved = await this.isExecuteApproved(commandLine, authority?.signal);
1876
1835
  assertControlAuthority(authority);
1877
1836
  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);
1837
+ const outputByteLimit = resolveTerminalOutputLimit(params.outputByteLimit, this.outputCeilingBytes);
1880
1838
  const { proc, spawnCommand } = await spawnTerminalProcess(params, this.cwd, authority);
1881
1839
  let resolveExit = () => {};
1882
1840
  const exitPromise = new Promise((resolve) => {
@@ -1886,6 +1844,7 @@ var TerminalManager = class {
1886
1844
  process: proc,
1887
1845
  killProcessGroup: spawnCommand.killProcessGroup,
1888
1846
  descendantPids: /* @__PURE__ */ new Set(),
1847
+ descendants: createTerminalDescendants(proc, spawnCommand.killProcessGroup),
1889
1848
  processHelperTimeoutMs: this.processHelperTimeoutMs,
1890
1849
  output: Buffer.alloc(0),
1891
1850
  truncated: false,
@@ -1899,10 +1858,8 @@ var TerminalManager = class {
1899
1858
  const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1900
1859
  if (bytes.length === 0) return;
1901
1860
  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
- }
1861
+ terminal.truncated ||= terminal.output.length > terminal.outputByteLimit;
1862
+ if (terminal.truncated) terminal.output = trimToUtf8Boundary(terminal.output, terminal.outputByteLimit);
1906
1863
  };
1907
1864
  proc.stdout.on("data", appendOutput);
1908
1865
  proc.stderr.on("data", appendOutput);
@@ -1911,9 +1868,10 @@ var TerminalManager = class {
1911
1868
  proc.once("exit", (exitCode, signal) => {
1912
1869
  terminal.exitCode = exitCode;
1913
1870
  terminal.signal = signal;
1914
- terminal.processGroupSnapshotPromise = rememberProcessGroupPids(terminal);
1871
+ terminal.processGroupSnapshotPromise = terminal.descendants ? terminal.descendants.capture(terminal.processHelperTimeoutMs).then(() => {}) : rememberProcessGroupPids(terminal);
1915
1872
  (async () => {
1916
1873
  await terminal.processGroupSnapshotPromise;
1874
+ terminal.processGroupSnapshotPromise = void 0;
1917
1875
  terminal.resolveExit({
1918
1876
  exitCode: exitCode ?? null,
1919
1877
  signal: signal ?? null
@@ -1923,6 +1881,7 @@ var TerminalManager = class {
1923
1881
  const terminalId = randomUUID();
1924
1882
  this.terminals.set(terminalId, terminal);
1925
1883
  try {
1884
+ await terminal.descendants?.capture(terminal.processHelperTimeoutMs);
1926
1885
  assertControlAuthority(authority);
1927
1886
  } catch (error) {
1928
1887
  await this.releaseTerminal({
@@ -2036,6 +1995,9 @@ var TerminalManager = class {
2036
1995
  try {
2037
1996
  await this.killProcess(terminal);
2038
1997
  await terminal.exitPromise.catch(() => {});
1998
+ terminal.descendants?.retire();
1999
+ terminal.process.stdout.destroy();
2000
+ terminal.process.stderr.destroy();
2039
2001
  terminal.output = Buffer.alloc(0);
2040
2002
  this.terminals.delete(params.terminalId);
2041
2003
  this.emitOperation({
@@ -2058,10 +2020,13 @@ var TerminalManager = class {
2058
2020
  }
2059
2021
  }
2060
2022
  async shutdown() {
2061
- for (const terminalId of Array.from(this.terminals.keys())) await this.releaseTerminal({
2023
+ const results = await Promise.allSettled(Array.from(this.terminals.keys(), (terminalId) => this.releaseTerminal({
2062
2024
  terminalId,
2063
2025
  sessionId: "shutdown"
2064
- });
2026
+ })));
2027
+ const failures = [];
2028
+ for (const result of results) if (result.status === "rejected") failures.push(result.reason);
2029
+ if (failures.length > 0) throw new AggregateError(failures, "Terminal shutdown failed", { cause: failures[0] });
2065
2030
  }
2066
2031
  getTerminal(terminalId) {
2067
2032
  return this.terminals.get(terminalId);
@@ -2079,13 +2044,13 @@ var TerminalManager = class {
2079
2044
  return terminal.exitCode === void 0 && terminal.signal === void 0;
2080
2045
  }
2081
2046
  async killProcess(terminal) {
2082
- if (!this.isRunning(terminal) && !terminal.killProcessGroup) return;
2047
+ if (!this.isRunning(terminal) && !tracksTerminalDescendants(terminal)) return;
2083
2048
  try {
2084
2049
  await this.signalProcess(terminal, "SIGTERM");
2085
2050
  } catch {
2086
2051
  return;
2087
2052
  }
2088
- if (await this.waitForCleanupAfterSignal(terminal) && !terminal.killProcessGroup) return;
2053
+ if (await this.waitForCleanupAfterSignal(terminal) && (!terminal.killProcessGroup || terminal.descendants)) return;
2089
2054
  try {
2090
2055
  await this.signalProcess(terminal, "SIGKILL");
2091
2056
  } catch {
@@ -2094,15 +2059,16 @@ var TerminalManager = class {
2094
2059
  await this.waitForFinalCleanup(terminal);
2095
2060
  }
2096
2061
  async signalProcess(terminal, signal) {
2062
+ if (terminal.descendants) {
2063
+ await terminal.descendants.signal(signal, terminal.processHelperTimeoutMs);
2064
+ if (this.isRunning(terminal)) terminal.process.kill(signal);
2065
+ return;
2066
+ }
2097
2067
  const pid = terminal.process.pid;
2098
2068
  if (terminal.killProcessGroup && pid && process.platform === "win32") {
2099
2069
  await this.signalWindowsProcessGroup(terminal, pid, signal);
2100
2070
  return;
2101
2071
  }
2102
- if (terminal.killProcessGroup && pid) {
2103
- await this.signalPosixProcessGroup(terminal, pid, signal);
2104
- return;
2105
- }
2106
2072
  terminal.process.kill(signal);
2107
2073
  }
2108
2074
  async signalWindowsProcessGroup(terminal, pid, signal) {
@@ -2113,14 +2079,6 @@ var TerminalManager = class {
2113
2079
  }
2114
2080
  for (const descendantPid of terminal.descendantPids) await killWindowsProcessTree(descendantPid, signal, terminal.processHelperTimeoutMs);
2115
2081
  }
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
2082
  async captureDescendantPids(terminal, pid) {
2125
2083
  if (!this.isRunning(terminal)) await terminal.processGroupSnapshotPromise?.catch(() => {});
2126
2084
  for (const descendantPid of await listDescendantPids(pid, terminal.processHelperTimeoutMs)) terminal.descendantPids.add(descendantPid);
@@ -2129,12 +2087,13 @@ var TerminalManager = class {
2129
2087
  if (!await this.waitForCleanupAfterSignal(terminal) && process.platform === "win32") throw new Error("Terminal process cleanup did not finish after SIGKILL");
2130
2088
  }
2131
2089
  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);
2090
+ const deadline = performance.now() + this.killGraceMs;
2091
+ while (this.isRunning(terminal) || terminal.processGroupSnapshotPromise || await hasLiveTerminalDescendants(terminal, deadline - performance.now()) || hasLivePid(terminal.descendantPids)) {
2092
+ const remaining = deadline - performance.now();
2093
+ if (remaining <= 0) return false;
2094
+ await waitMs(Math.min(25, remaining));
2095
+ }
2096
+ return true;
2138
2097
  }
2139
2098
  };
2140
2099
  async function spawnTerminalProcess(params, defaultCwd, authority) {
@@ -2155,7 +2114,7 @@ async function spawnTerminalProcess(params, defaultCwd, authority) {
2155
2114
  }
2156
2115
  async function spawnAndWait(spawnCommand, params, defaultCwd, authority) {
2157
2116
  const spawnOptions = buildTerminalSpawnOptions(spawnCommand.command, params.cwd ?? defaultCwd, params.env);
2158
- if (spawnCommand.killProcessGroup) spawnOptions.detached = true;
2117
+ if (spawnCommand.killProcessGroup || process.platform !== "win32") spawnOptions.detached = true;
2159
2118
  assertControlAuthority(authority);
2160
2119
  const proc = spawn(spawnCommand.command, spawnCommand.args, spawnOptions);
2161
2120
  await waitForSpawn(proc);
@@ -2183,7 +2142,7 @@ function commandPathExists(command, cwd) {
2183
2142
  async function listDescendantPids(rootPid, timeoutMs) {
2184
2143
  let output;
2185
2144
  try {
2186
- output = await runProcessListCommand(timeoutMs);
2145
+ output = await runWindowsProcessListCommand(timeoutMs);
2187
2146
  } catch {
2188
2147
  return [];
2189
2148
  }
@@ -2216,38 +2175,10 @@ function parseProcessListLine(line) {
2216
2175
  parentPid
2217
2176
  };
2218
2177
  }
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
2178
  async function rememberProcessGroupPids(terminal) {
2224
2179
  const processGroupId = terminal.process.pid;
2225
2180
  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 });
2181
+ for (const pid of await listDescendantPids(processGroupId, terminal.processHelperTimeoutMs)) terminal.descendantPids.add(pid);
2251
2182
  }
2252
2183
  async function runWindowsProcessListCommand(timeoutMs) {
2253
2184
  const command = ["Get-CimInstance Win32_Process |", "ForEach-Object { \"$($_.ProcessId) $($_.ParentProcessId)\" }"].join(" ");
@@ -2275,22 +2206,11 @@ async function killWindowsProcessTree(pid, signal, timeoutMs = PROCESS_HELPER_TI
2275
2206
  });
2276
2207
  } catch {}
2277
2208
  }
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));
2209
+ async function hasLiveTerminalDescendants(terminal, timeoutMs) {
2210
+ const descendants = terminal.descendants;
2211
+ if (!descendants) return false;
2212
+ if (timeoutMs > 0) await descendants.capture(Math.min(terminal.processHelperTimeoutMs, timeoutMs));
2213
+ return descendants.hasTrackedProcesses();
2294
2214
  }
2295
2215
  function hasLivePid(pids) {
2296
2216
  for (const pid of pids) try {
@@ -2400,26 +2320,13 @@ function childProcessIsRunning(agent) {
2400
2320
  function cancelledPermissionResponse() {
2401
2321
  return { outcome: { outcome: "cancelled" } };
2402
2322
  }
2403
- function shouldSuppressSdkConsoleError(args) {
2404
- if (args.length === 0) return false;
2405
- return typeof args[0] === "string" && args[0] === "Error handling request";
2406
- }
2407
- function installSdkConsoleErrorSuppression() {
2408
- const originalConsoleError = console.error;
2409
- console.error = (...args) => {
2410
- if (shouldSuppressSdkConsoleError(args)) return;
2411
- originalConsoleError(...args);
2412
- };
2413
- return () => {
2414
- console.error = originalConsoleError;
2415
- };
2416
- }
2417
2323
  var AcpClient = class {
2418
2324
  options;
2419
2325
  connection;
2420
2326
  agent;
2421
2327
  agentDescendants = /* @__PURE__ */ new WeakMap();
2422
2328
  agentCleanups = /* @__PURE__ */ new WeakMap();
2329
+ commandProbes = /* @__PURE__ */ new Set();
2423
2330
  initResult;
2424
2331
  loadedSessionId;
2425
2332
  eventHandlers;
@@ -2441,6 +2348,7 @@ var AcpClient = class {
2441
2348
  cancellingSessionIds = /* @__PURE__ */ new Set();
2442
2349
  permissionAbortControllers = /* @__PURE__ */ new Map();
2443
2350
  closing = false;
2351
+ closeTask;
2444
2352
  closeEpoch = 0;
2445
2353
  agentStartedAt;
2446
2354
  lastAgentExit;
@@ -2537,7 +2445,7 @@ var AcpClient = class {
2537
2445
  this.terminalManager.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
2538
2446
  }
2539
2447
  hasLiveConnection() {
2540
- return this.connection != null && !this.connection.signal.aborted && this.agent != null && isChildProcessRunning(this.agent);
2448
+ return !this.closing && this.connection != null && !this.connection.signal.aborted && this.agent != null && isChildProcessRunning(this.agent);
2541
2449
  }
2542
2450
  hasReusableSession(sessionId) {
2543
2451
  return this.hasLiveConnection() && this.loadedSessionId === sessionId;
@@ -2560,15 +2468,17 @@ var AcpClient = class {
2560
2468
  }
2561
2469
  fallback?.abort();
2562
2470
  }
2563
- async start() {
2471
+ async start(authority) {
2472
+ assertControlAuthority(authority);
2564
2473
  if (this.hasLiveConnection()) return;
2565
- if (this.connection || this.agent) await this.close();
2566
- const epoch = this.closeEpoch;
2474
+ const epoch = await this.waitForPreviousClose();
2567
2475
  const maxMessageBytes = readMaxAcpMessageBytes();
2568
- const launch = await this.resolveAgentLaunchPlan();
2476
+ const launch = await this.resolveAgentLaunchPlan(epoch, authority);
2477
+ this.assertCommandProbeActive(epoch, authority);
2569
2478
  this.logAgentLaunch(launch);
2570
2479
  await this.ensureLaunchSupport(launch);
2571
- const { child, process: startedProcess } = await this.spawnAgentProcess(launch);
2480
+ this.assertCommandProbeActive(epoch, authority);
2481
+ const { child, process: startedProcess } = await this.spawnAgentProcess(launch, authority);
2572
2482
  if (this.closeEpoch === epoch) {
2573
2483
  this.agent = child;
2574
2484
  this.closing = false;
@@ -2591,7 +2501,7 @@ var AcpClient = class {
2591
2501
  startupFailure.dispose();
2592
2502
  throw error;
2593
2503
  }
2594
- await this.stopIfClosedDuringLaunch(epoch, child, startupFailure);
2504
+ await this.stopIfClosedDuringLaunch(epoch, child, startupFailure, authority);
2595
2505
  const input = Writable.toWeb(child.stdin);
2596
2506
  const output = Readable.toWeb(child.stdout);
2597
2507
  let connection;
@@ -2606,9 +2516,10 @@ var AcpClient = class {
2606
2516
  elicitationModes: this.options.elicitationModes
2607
2517
  });
2608
2518
  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 });
2519
+ const onConnectionClose = () => {
2520
+ this.handleAgentDisconnect(child, "connection_close", child.exitCode ?? null, child.signalCode ?? null);
2521
+ };
2522
+ connection.signal.addEventListener("abort", onConnectionClose, { once: true });
2612
2523
  await this.initializeAgentConnection({
2613
2524
  child,
2614
2525
  connection,
@@ -2617,13 +2528,38 @@ var AcpClient = class {
2617
2528
  launch,
2618
2529
  capabilities
2619
2530
  });
2531
+ if (connection.signal.aborted) onConnectionClose();
2620
2532
  }
2621
- async resolveAgentLaunchPlan() {
2533
+ async waitForPreviousClose() {
2534
+ const cleanup = this.closeTask ?? (this.connection || this.agent ? this.close() : void 0);
2535
+ const epoch = this.closeEpoch;
2536
+ await cleanup;
2537
+ if (this.closeEpoch !== epoch) throw new Error("ACP client was closed while the agent was starting");
2538
+ return epoch;
2539
+ }
2540
+ async resolveAgentLaunchPlan(epoch, authority) {
2622
2541
  const configuredCommand = resolveAgentCommandParts(this.options.agentCommand, this.options.agentArgv);
2623
2542
  const resolvedBuiltInLaunch = resolveBuiltInAgentLaunch(this.options.agentCommand);
2624
2543
  const spawnCommand = resolvedBuiltInLaunch?.command ?? configuredCommand.command;
2544
+ const spawnOptions = buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials, this.options.sessionOptions?.env, this.options.agentProcessEnv);
2545
+ const commandContext = {
2546
+ env: spawnOptions.env,
2547
+ readOutput: async (command, args, timeoutMs, diagnostic = false) => {
2548
+ try {
2549
+ const output = await this.readAgentCommandOutput(command, args, timeoutMs, spawnOptions, epoch, authority);
2550
+ await admitCommandProbe(() => this.assertCommandProbeActive(epoch, authority));
2551
+ return output;
2552
+ } catch (error) {
2553
+ if (error instanceof CommandProbeAdmissionError) {
2554
+ if (diagnostic) return;
2555
+ throw error.reason;
2556
+ }
2557
+ throw error;
2558
+ }
2559
+ }
2560
+ };
2625
2561
  let args = resolvedBuiltInLaunch?.args ?? configuredCommand.args;
2626
- args = await resolveGeminiCommandArgs(spawnCommand, args);
2562
+ args = await resolveGeminiCommandArgs(spawnCommand, args, commandContext);
2627
2563
  if (isQoderAcpCommand(spawnCommand, args)) args = buildQoderAcpCommandArgs(args, this.options);
2628
2564
  return {
2629
2565
  spawnCommand,
@@ -2633,7 +2569,8 @@ var AcpClient = class {
2633
2569
  geminiAcp: isGeminiAcpCommand(spawnCommand, args),
2634
2570
  copilotAcp: isCopilotAcpCommand(spawnCommand, args),
2635
2571
  claudeAcp: isClaudeAcpCommand(spawnCommand, args),
2636
- spawnOptions: buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials, this.options.sessionOptions?.env, this.options.agentProcessEnv)
2572
+ spawnOptions,
2573
+ commandContext
2637
2574
  };
2638
2575
  }
2639
2576
  logAgentLaunch(plan) {
@@ -2649,7 +2586,7 @@ var AcpClient = class {
2649
2586
  this.log(`spawning agent: ${plan.spawnCommand} ${plan.args.join(" ")}`);
2650
2587
  }
2651
2588
  async ensureLaunchSupport(plan) {
2652
- if (plan.copilotAcp) await ensureCopilotAcpSupport(plan.spawnCommand);
2589
+ if (plan.copilotAcp) await ensureCopilotAcpSupport(plan.spawnCommand, plan.commandContext);
2653
2590
  if (!plan.claudeAcp) return;
2654
2591
  const claudeExe = resolveClaudeCodeExecutable(process.platform, plan.spawnOptions.env);
2655
2592
  if (claudeExe) {
@@ -2657,21 +2594,36 @@ var AcpClient = class {
2657
2594
  this.log(`resolved system Claude Code executable: ${claudeExe}`);
2658
2595
  }
2659
2596
  }
2660
- async spawnAgentProcess(plan) {
2661
- const spawnCommand = buildAgentSpawnCommand(plan.spawnCommand, plan.args, process.platform, plan.spawnOptions.env);
2662
- const launch = Object.freeze({
2663
- launchId: randomUUID(),
2664
- scope: snapshotProcessLaunchScope(this.options.processLaunchScope),
2665
- command: spawnCommand.command,
2666
- args: Object.freeze([...spawnCommand.args]),
2667
- cwd: this.options.cwd
2597
+ prepareProcessLaunch(command, args, options) {
2598
+ const resolved = buildAgentSpawnCommand(command, args, process.platform, options.env);
2599
+ return {
2600
+ launch: Object.freeze({
2601
+ launchId: randomUUID(),
2602
+ scope: snapshotProcessLaunchScope(this.options.processLaunchScope),
2603
+ command: resolved.command,
2604
+ args: Object.freeze([...resolved.args]),
2605
+ cwd: options.cwd
2606
+ }),
2607
+ windowsVerbatimArguments: resolved.windowsVerbatimArguments
2608
+ };
2609
+ }
2610
+ startedProcess(launch, pid) {
2611
+ return Object.freeze({
2612
+ ...launch,
2613
+ pid,
2614
+ startedAt: isoNow$2()
2668
2615
  });
2616
+ }
2617
+ async spawnAgentProcess(plan, authority) {
2618
+ const prepared = this.prepareProcessLaunch(plan.spawnCommand, plan.args, plan.spawnOptions);
2619
+ const { launch } = prepared;
2669
2620
  await this.options.processLifecycle?.onBeforeSpawn?.(launch);
2621
+ assertControlAuthority(authority);
2670
2622
  let spawnedChild;
2671
2623
  try {
2672
- spawnedChild = spawn(spawnCommand.command, spawnCommand.args, {
2624
+ spawnedChild = spawn(launch.command, [...launch.args], {
2673
2625
  ...plan.spawnOptions,
2674
- windowsVerbatimArguments: spawnCommand.windowsVerbatimArguments
2626
+ windowsVerbatimArguments: prepared.windowsVerbatimArguments
2675
2627
  });
2676
2628
  await waitForSpawn(spawnedChild);
2677
2629
  } catch (error) {
@@ -2690,21 +2642,86 @@ var AcpClient = class {
2690
2642
  }
2691
2643
  return {
2692
2644
  child,
2693
- process: Object.freeze({
2694
- ...launch,
2695
- pid,
2696
- startedAt: isoNow$2()
2697
- })
2645
+ process: this.startedProcess(launch, pid)
2698
2646
  };
2699
2647
  }
2648
+ assertCommandProbeActive(epoch, authority) {
2649
+ assertControlAuthority(authority);
2650
+ if (this.closeEpoch !== epoch) throw new Error("ACP client was closed while the agent was starting");
2651
+ }
2652
+ async readAgentCommandOutput(command, args, timeoutMs, options, epoch, authority) {
2653
+ const prepared = this.prepareProcessLaunch(command, args, options);
2654
+ const { launch } = prepared;
2655
+ await admitCommandProbe(async () => {
2656
+ this.assertCommandProbeActive(epoch, authority);
2657
+ await this.options.processLifecycle?.onBeforeSpawn?.(launch);
2658
+ });
2659
+ try {
2660
+ this.assertCommandProbeActive(epoch, authority);
2661
+ } catch (error) {
2662
+ throw new CommandProbeAdmissionError(error);
2663
+ }
2664
+ let child;
2665
+ try {
2666
+ child = spawn(launch.command, [...launch.args], {
2667
+ cwd: options.cwd,
2668
+ env: options.env,
2669
+ stdio: [
2670
+ "ignore",
2671
+ "pipe",
2672
+ "pipe"
2673
+ ],
2674
+ windowsHide: true,
2675
+ windowsVerbatimArguments: prepared.windowsVerbatimArguments,
2676
+ detached: process.platform !== "win32"
2677
+ });
2678
+ } catch (error) {
2679
+ this.notifyProcessSpawnFailure(launch, new AgentSpawnError(command, error));
2680
+ return;
2681
+ }
2682
+ this.commandProbes.add(child);
2683
+ this.agentDescendants.set(child, new ProcessDescendants(child, { ownProcessGroup: true }));
2684
+ const capture = captureCommandProbeOutput(child, timeoutMs, () => this.terminateAgentProcess(child));
2685
+ try {
2686
+ try {
2687
+ await waitForSpawn(child);
2688
+ } catch (error) {
2689
+ this.notifyProcessSpawnFailure(launch, new AgentSpawnError(command, error));
2690
+ return;
2691
+ }
2692
+ if (child.pid === void 0) {
2693
+ this.notifyProcessSpawnFailure(launch, new AgentSpawnError(command, /* @__PURE__ */ new Error("spawned probe did not expose a PID")));
2694
+ return;
2695
+ }
2696
+ const started = this.startedProcess(launch, child.pid);
2697
+ await this.admitSpawnedProcess(child, (barrier) => this.attachProcessExitObserver(child, started, barrier), () => admitCommandProbe(async () => {
2698
+ await this.options.processLifecycle?.onSpawned?.(started);
2699
+ this.assertCommandProbeActive(epoch, authority);
2700
+ }));
2701
+ const output = await capture.result;
2702
+ await admitCommandProbe(() => this.assertCommandProbeActive(epoch, authority));
2703
+ return output;
2704
+ } finally {
2705
+ try {
2706
+ await this.terminateAgentProcess(child);
2707
+ } finally {
2708
+ capture.dispose();
2709
+ this.commandProbes.delete(child);
2710
+ }
2711
+ }
2712
+ }
2700
2713
  async admitAndObserveSpawnedProcess(child, process) {
2714
+ await this.admitSpawnedProcess(child, (barrier) => this.attachAgentLifecycleObservers(child, process, barrier), async () => {
2715
+ await this.options.processLifecycle?.onSpawned?.(process);
2716
+ });
2717
+ }
2718
+ async admitSpawnedProcess(child, observe, admit) {
2701
2719
  let releaseExitNotification = () => {};
2702
- const exitNotificationBarrier = new Promise((resolve) => {
2720
+ observe(new Promise((resolve) => {
2703
2721
  releaseExitNotification = resolve;
2704
- });
2705
- this.attachAgentLifecycleObservers(child, process, exitNotificationBarrier);
2722
+ }));
2706
2723
  try {
2707
- await this.options.processLifecycle?.onSpawned?.(process);
2724
+ await admit();
2708
2725
  } catch (error) {
2709
2726
  await this.terminateAgentProcess(child);
2710
2727
  throw error;
@@ -2770,7 +2787,7 @@ var AcpClient = class {
2770
2787
  await this.captureAgentDescendants(params.child);
2771
2788
  const normalizedError = error instanceof AcpMessageLimitError ? error : await this.normalizeInitializeError(error, params.child, params.startupStderr);
2772
2789
  await this.terminateAgentProcess(params.child);
2773
- if (params.launch.geminiAcp && error instanceof TimeoutError) throw new GeminiAcpStartupTimeoutError(await buildGeminiAcpStartupTimeoutMessage(params.launch.spawnCommand), {
2790
+ if (params.launch.geminiAcp && error instanceof TimeoutError) throw new GeminiAcpStartupTimeoutError(await buildGeminiAcpStartupTimeoutMessage(params.launch.spawnCommand, params.launch.commandContext), {
2774
2791
  cause: error,
2775
2792
  retryable: true
2776
2793
  });
@@ -2788,7 +2805,8 @@ var AcpClient = class {
2788
2805
  onPromptRequestWritten: (active, owner) => this.onPromptRequestWritten(active, owner)
2789
2806
  });
2790
2807
  }
2791
- async createSession(cwd = this.options.cwd) {
2808
+ async createSession(cwd = this.options.cwd, authority) {
2809
+ assertControlAuthority(authority);
2792
2810
  const connection = this.getConnection();
2793
2811
  const { command, args } = resolveAgentCommandParts(this.options.agentCommand, this.options.agentArgv);
2794
2812
  const claudeAcp = isClaudeAcpCommand(command, args);
@@ -2799,7 +2817,7 @@ var AcpClient = class {
2799
2817
  cwd: sessionCwd,
2800
2818
  mcpServers: this.options.mcpServers ?? [],
2801
2819
  _meta: buildClaudeCodeOptionsMeta(this.options.sessionOptions, claudeAcp)
2802
- }));
2820
+ }), authority);
2803
2821
  result = claudeAcp ? await withTimeout(createPromise, resolveClaudeAcpSessionCreateTimeoutMs()) : await createPromise;
2804
2822
  } catch (error) {
2805
2823
  if (claudeAcp && error instanceof TimeoutError) throw new ClaudeAcpSessionCreateTimeoutError(buildClaudeAcpSessionCreateTimeoutMessage(), {
@@ -2831,6 +2849,7 @@ var AcpClient = class {
2831
2849
  async loadSessionWithOptions(sessionId, cwd = this.options.cwd, options = {}) {
2832
2850
  const connection = this.getConnection();
2833
2851
  const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
2852
+ assertControlAuthority(options.authority);
2834
2853
  const previousSuppression = this.applySessionUpdateSuppression(Boolean(options.suppressReplayUpdates));
2835
2854
  let response;
2836
2855
  try {
@@ -2838,7 +2857,7 @@ var AcpClient = class {
2838
2857
  sessionId,
2839
2858
  cwd: sessionCwd,
2840
2859
  mcpServers: this.options.mcpServers ?? []
2841
- }));
2860
+ }), options.authority);
2842
2861
  await this.waitForSessionUpdateDrain(options.replayIdleMs ?? REPLAY_IDLE_MS, options.replayDrainTimeoutMs ?? REPLAY_DRAIN_TIMEOUT_MS);
2843
2862
  } finally {
2844
2863
  this.restoreSessionUpdateSuppression(previousSuppression);
@@ -2848,14 +2867,15 @@ var AcpClient = class {
2848
2867
  await this.captureAgentDescendants(this.agent);
2849
2868
  return result;
2850
2869
  }
2851
- async resumeSession(sessionId, cwd = this.options.cwd) {
2870
+ async resumeSession(sessionId, cwd = this.options.cwd, authority) {
2871
+ assertControlAuthority(authority);
2852
2872
  const connection = this.getConnection();
2853
2873
  const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
2854
2874
  const response = await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.resume, {
2855
2875
  sessionId,
2856
2876
  cwd: sessionCwd,
2857
2877
  mcpServers: this.options.mcpServers ?? []
2858
- }));
2878
+ }), authority);
2859
2879
  this.loadedSessionId = sessionId;
2860
2880
  const result = toReconnectedSessionResult(response);
2861
2881
  await this.captureAgentDescendants(this.agent);
@@ -2877,7 +2897,6 @@ var AcpClient = class {
2877
2897
  async prompt(sessionId, prompt, onRequestWritten, onElicitation, onPermissionRequest, authority) {
2878
2898
  const connection = this.getConnection();
2879
2899
  const normalizedPrompt = this.normalizePromptForAgent(prompt);
2880
- const restoreConsoleError = this.options.suppressSdkConsoleErrors ? installSdkConsoleErrorSuppression() : void 0;
2881
2900
  const previousActivePrompt = this.activePrompt;
2882
2901
  const replacedOwners = this.pendingPromptOwners.filter((owner) => owner.sessionId === sessionId);
2883
2902
  const activePrompt = this.beginActivePrompt(sessionId, onRequestWritten, onElicitation, onPermissionRequest, authority);
@@ -2895,7 +2914,6 @@ var AcpClient = class {
2895
2914
  this.throwPromptPermissionFailureIfPresent(sessionId);
2896
2915
  throw error;
2897
2916
  } finally {
2898
- restoreConsoleError?.();
2899
2917
  this.clearActivePrompt(activePrompt);
2900
2918
  }
2901
2919
  }
@@ -3080,20 +3098,23 @@ var AcpClient = class {
3080
3098
  if (timer) clearTimeout(timer);
3081
3099
  }
3082
3100
  }
3083
- async close() {
3101
+ close() {
3084
3102
  this.closing = true;
3085
3103
  this.closeEpoch += 1;
3104
+ if (this.closeTask) return this.closeTask;
3105
+ this.closeTask = Promise.resolve().then(() => this.finishClose()).finally(() => {
3106
+ this.closeTask = void 0;
3107
+ });
3108
+ return this.closeTask;
3109
+ }
3110
+ async finishClose() {
3086
3111
  const permissionControllers = [...this.permissionAbortControllers.values()];
3087
3112
  this.permissionAbortControllers.clear();
3088
3113
  const owners = [...this.pendingPromptOwners];
3089
3114
  this.abortActiveElicitation();
3090
3115
  for (const owner of owners) owner.requestController.abort();
3091
3116
  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) }));
3117
+ await this.retireNativeResources();
3097
3118
  this.sessionUpdateChain = Promise.resolve();
3098
3119
  this.observedSessionUpdates = 0;
3099
3120
  this.processedSessionUpdates = 0;
@@ -3110,10 +3131,28 @@ var AcpClient = class {
3110
3131
  this.connection = void 0;
3111
3132
  this.agent = void 0;
3112
3133
  }
3113
- async stopIfClosedDuringLaunch(epoch, child, startupFailure) {
3134
+ async retireNativeResources() {
3135
+ const owned = [...this.commandProbes, ...this.agent ? [this.agent] : []];
3136
+ const failures = [];
3137
+ try {
3138
+ await this.terminalManager.shutdown();
3139
+ } catch (error) {
3140
+ failures.push(error);
3141
+ }
3142
+ const retired = await Promise.allSettled(owned.map((child) => this.terminateAgentProcess(child)));
3143
+ for (const result of retired) if (result.status === "rejected") failures.push(result.reason);
3144
+ try {
3145
+ this.closeConnection();
3146
+ } finally {
3147
+ 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) }));
3148
+ }
3149
+ if (failures.length > 0) throw new AggregateError(failures, "ACP client cleanup failed", { cause: failures[0] });
3150
+ }
3151
+ async stopIfClosedDuringLaunch(epoch, child, startupFailure, authority) {
3114
3152
  if (this.closeEpoch === epoch) return;
3115
3153
  startupFailure.dispose();
3116
3154
  await this.terminateAgentProcess(child);
3155
+ assertControlAuthority(authority);
3117
3156
  throw new Error("ACP client was closed while the agent was starting");
3118
3157
  }
3119
3158
  abortActiveElicitation() {
@@ -3127,6 +3166,11 @@ var AcpClient = class {
3127
3166
  if (descendants && !await descendants.capture()) this.log("could not verify agent descendants; skipping unverified process cleanup");
3128
3167
  }
3129
3168
  terminateAgentProcess(child) {
3169
+ if (child.pid === void 0) {
3170
+ this.agentDescendants.get(child)?.retire();
3171
+ this.detachAgentHandles(child, true);
3172
+ return Promise.resolve();
3173
+ }
3130
3174
  let cleanup = this.agentCleanups.get(child);
3131
3175
  if (!cleanup) {
3132
3176
  cleanup = Promise.resolve().then(() => this.cleanupAgentProcess(child));
@@ -3136,12 +3180,12 @@ var AcpClient = class {
3136
3180
  }
3137
3181
  async cleanupAgentProcess(child) {
3138
3182
  const descendants = this.agentDescendants.get(child);
3139
- const deadline = Date.now() + AGENT_CLEANUP_BUDGET_MS;
3183
+ const deadline = performance.now() + AGENT_CLEANUP_BUDGET_MS;
3140
3184
  const stdinCloseGraceMs = resolveAgentCloseAfterStdinEndMs(this.options.agentCommand);
3141
3185
  try {
3142
- if (descendants) await descendants.capture(Math.max(1, deadline - Date.now()));
3186
+ if (descendants) await descendants.capture(Math.max(1, deadline - performance.now()));
3143
3187
  this.endAgentStdin(child);
3144
- await waitForChildExit(child, Math.min(stdinCloseGraceMs, Math.max(0, deadline - Date.now())));
3188
+ await waitForChildExit(child, Math.min(stdinCloseGraceMs, Math.max(0, deadline - performance.now())));
3145
3189
  if (!await this.signalAgentAndDescendants(child, "SIGTERM", AGENT_CLOSE_TERM_GRACE_MS, deadline)) {
3146
3190
  this.log("agent processes did not exit after SIGTERM; forcing SIGKILL");
3147
3191
  await this.signalAgentAndDescendants(child, "SIGKILL", AGENT_CLOSE_KILL_GRACE_MS, deadline);
@@ -3152,18 +3196,18 @@ var AcpClient = class {
3152
3196
  }
3153
3197
  }
3154
3198
  endAgentStdin(child) {
3155
- if (child.stdin.destroyed) return;
3199
+ if (!child.stdin || child.stdin.destroyed) return;
3156
3200
  try {
3157
3201
  child.stdin.end();
3158
3202
  } catch {}
3159
3203
  }
3160
3204
  async signalAgentAndDescendants(child, signal, waitMs, deadline) {
3161
3205
  const descendants = this.agentDescendants.get(child);
3162
- if (descendants && Date.now() < deadline) await descendants.signal(signal, deadline - Date.now());
3206
+ if (descendants && performance.now() < deadline) await descendants.signal(signal, deadline - performance.now());
3163
3207
  if (isChildProcessRunning(child)) try {
3164
3208
  child.kill(signal);
3165
3209
  } catch {}
3166
- const remaining = Math.min(waitMs, Math.max(0, deadline - Date.now()));
3210
+ const remaining = Math.min(waitMs, Math.max(0, deadline - performance.now()));
3167
3211
  const [exited, descendantsExited] = await Promise.all([waitForChildExit(child, remaining), descendants ? descendants.waitForExit(remaining) : Promise.resolve(true)]);
3168
3212
  return exited && descendantsExited;
3169
3213
  }
@@ -3473,24 +3517,34 @@ var AcpClient = class {
3473
3517
  return cancelledPermissionResponse();
3474
3518
  }
3475
3519
  attachAgentLifecycleObservers(child, startedProcess, exitNotificationBarrier) {
3520
+ this.attachProcessExitObserver(child, startedProcess, exitNotificationBarrier, (exitCode, signal) => {
3521
+ this.handleAgentDisconnect(child, "process_exit", exitCode, signal);
3522
+ this.terminateAgentProcess(child);
3523
+ });
3524
+ child.once("close", (exitCode, signal) => {
3525
+ this.handleAgentDisconnect(child, "process_close", exitCode, signal);
3526
+ });
3527
+ child.stdout.once("close", () => {
3528
+ this.handleAgentDisconnect(child, "pipe_close", child.exitCode ?? null, child.signalCode ?? null);
3529
+ });
3530
+ }
3531
+ attachProcessExitObserver(child, started, barrier, onProcessExit) {
3476
3532
  const onExit = (exitCode, signal) => {
3477
3533
  const exitedAt = isoNow$2();
3478
- this.recordAgentExit(child, "process_exit", exitCode, signal);
3479
- this.terminateAgentProcess(child);
3480
- exitNotificationBarrier.then(() => {
3481
- this.notifyProcessExit(startedProcess, exitCode, signal, exitedAt);
3482
- });
3534
+ onProcessExit?.(exitCode, signal);
3535
+ barrier.then(() => this.notifyProcessExit(started, exitCode, signal, exitedAt));
3483
3536
  };
3484
3537
  child.once("exit", onExit);
3485
3538
  if (child.exitCode !== null || child.signalCode !== null) {
3486
3539
  child.off("exit", onExit);
3487
3540
  onExit(child.exitCode, child.signalCode);
3488
3541
  }
3489
- child.once("close", (exitCode, signal) => {
3490
- this.recordAgentExit(child, "process_close", exitCode, signal);
3491
- });
3492
- child.stdout.once("close", () => {
3493
- this.recordAgentExit(child, "pipe_close", child.exitCode ?? null, child.signalCode ?? null);
3542
+ }
3543
+ handleAgentDisconnect(child, reason, exitCode, signal) {
3544
+ this.recordAgentExit(child, reason, exitCode, signal);
3545
+ if (this.agent !== child || this.closing || !this.connection) return;
3546
+ this.close().catch((error) => {
3547
+ this.log(`disconnected agent cleanup failed: ${String(error)}`);
3494
3548
  });
3495
3549
  }
3496
3550
  notifyProcessSpawnFailure(launch, error) {
@@ -3854,7 +3908,6 @@ function ensureAgentMessage(conversation) {
3854
3908
  return created;
3855
3909
  }
3856
3910
  function appendAgentText(agent, text) {
3857
- if (!text.trim()) return;
3858
3911
  const last = agent.content.at(-1);
3859
3912
  if (last && isAgentTextContent(last)) {
3860
3913
  last.Text = trimRuntimeText(`${last.Text}${text}`, MAX_RUNTIME_AGENT_TEXT_CHARS);
@@ -3864,7 +3917,6 @@ function appendAgentText(agent, text) {
3864
3917
  agent.content.push(next);
3865
3918
  }
3866
3919
  function appendAgentThinking(agent, text) {
3867
- if (!text.trim()) return;
3868
3920
  const last = agent.content.at(-1);
3869
3921
  if (last && isAgentThinkingContent(last)) {
3870
3922
  last.Thinking.text = trimRuntimeText(`${last.Thinking.text}${text}`, MAX_RUNTIME_THINKING_CHARS);
@@ -3921,8 +3973,7 @@ function ensureToolUseContent(agent, toolCallId) {
3921
3973
  return created;
3922
3974
  }
3923
3975
  function upsertToolResult(agent, toolCallId, patch) {
3924
- const existing = agent.tool_results[toolCallId];
3925
- const fallback = existingToolResultValues(existing);
3976
+ const fallback = existingToolResultValues(hasOwn(agent.tool_results, toolCallId) ? agent.tool_results[toolCallId] : void 0);
3926
3977
  const next = {
3927
3978
  tool_use_id: toolCallId,
3928
3979
  tool_name: patch.tool_name ?? fallback.tool_name,
@@ -3930,7 +3981,12 @@ function upsertToolResult(agent, toolCallId, patch) {
3930
3981
  content: patch.content ?? fallback.content,
3931
3982
  output: patch.output ?? fallback.output
3932
3983
  };
3933
- agent.tool_results[toolCallId] = next;
3984
+ Object.defineProperty(agent.tool_results, toolCallId, {
3985
+ value: next,
3986
+ writable: true,
3987
+ enumerable: true,
3988
+ configurable: true
3989
+ });
3934
3990
  }
3935
3991
  function existingToolResultValues(existing) {
3936
3992
  if (existing) return existing;
@@ -3971,7 +4027,7 @@ function applyToolResultUpdate(agent, tool, update) {
3971
4027
  const output = hasOwn(update, "rawOutput") ? deepClone(update.rawOutput) : void 0;
3972
4028
  upsertToolResult(agent, update.toolCallId, {
3973
4029
  tool_name: tool.name,
3974
- is_error: statusIndicatesError(status),
4030
+ is_error: status === void 0 ? void 0 : statusIndicatesError(status),
3975
4031
  content: output === void 0 ? void 0 : toToolResultContent(output),
3976
4032
  output
3977
4033
  });
@@ -4301,6 +4357,11 @@ function mergeSessionOptions(preferred, fallback) {
4301
4357
  assignDefinedOption(merged, "env", mergeEnvRecords(fallback?.env, preferred?.env));
4302
4358
  return Object.keys(merged).length > 0 ? merged : void 0;
4303
4359
  }
4360
+ function normalizeSessionOptionShape(options) {
4361
+ const normalized = mergeSessionOptions(options, void 0);
4362
+ if (normalized?.env !== void 0 && Object.keys(normalized.env).length === 0) delete normalized.env;
4363
+ return normalized && Object.keys(normalized).length > 0 ? normalized : void 0;
4364
+ }
4304
4365
  function mergeEnvRecords(fallback, preferred) {
4305
4366
  if (!fallback && !preferred) return;
4306
4367
  return {
@@ -4397,10 +4458,7 @@ function getDesiredModeId(state) {
4397
4458
  function getDesiredConfigOptions(state) {
4398
4459
  const desired = state?.desired_config_options;
4399
4460
  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
- }));
4461
+ return Object.fromEntries(Object.entries(desired).filter(([, value]) => typeof value === "string"));
4404
4462
  }
4405
4463
  function setDesiredModeId(record, modeId) {
4406
4464
  const acpx = ensureAcpxState(record.acpx);
@@ -4410,10 +4468,9 @@ function setDesiredModeId(record, modeId) {
4410
4468
  record.acpx = acpx;
4411
4469
  }
4412
4470
  function clearDesiredConfigOption(state, configId) {
4413
- const normalizedConfigId = normalizeModeId(configId);
4414
- if (!normalizedConfigId || !state.desired_config_options) return;
4471
+ if (typeof configId !== "string" || !state.desired_config_options) return;
4415
4472
  const desired = { ...state.desired_config_options };
4416
- delete desired[normalizedConfigId];
4473
+ delete desired[configId];
4417
4474
  if (Object.keys(desired).length > 0) state.desired_config_options = desired;
4418
4475
  else delete state.desired_config_options;
4419
4476
  }
@@ -4452,7 +4509,7 @@ async function applyRequestedModelIfAdvertised(params) {
4452
4509
  if (params.models.currentModelId === requestedModel) return { applied: true };
4453
4510
  return {
4454
4511
  applied: true,
4455
- response: await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel, params.models), params.timeoutMs)
4512
+ response: await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel, params.models, params.authority), params.timeoutMs)
4456
4513
  };
4457
4514
  }
4458
4515
  //#endregion
@@ -4477,9 +4534,9 @@ function applyAcceptedConfigOptions(state, response) {
4477
4534
  if (!response) return next;
4478
4535
  applyConfigOptionsModelState(next, response.configOptions);
4479
4536
  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;
4537
+ const desiredEntries = [];
4538
+ for (const option of response.configOptions) if (typeof option.currentValue === "string" && Object.hasOwn(next.desired_config_options, option.id)) desiredEntries.push([option.id, option.currentValue]);
4539
+ if (desiredEntries.length > 0) next.desired_config_options = Object.fromEntries(desiredEntries);
4483
4540
  else delete next.desired_config_options;
4484
4541
  return next;
4485
4542
  }
@@ -4497,8 +4554,7 @@ function applyModelSelection(state, modelId, response) {
4497
4554
  function applyConfigOptionSelection(state, configId, value, response, modelConfigId = advertisedModelState(state)?.configId) {
4498
4555
  if (configId === modelConfigId || configId === modelStateFromConfigOptions(response.configOptions)?.configId) return applyModelSelection(state, value, response);
4499
4556
  const next = cloneSessionAcpxState(state) ?? {};
4500
- if (configId === "mode") next.desired_mode_id = value;
4501
- else next.desired_config_options = {
4557
+ next.desired_config_options = {
4502
4558
  ...next.desired_config_options,
4503
4559
  [configId]: value
4504
4560
  };
@@ -4509,6 +4565,7 @@ function applyConfigOptionSelection(state, configId, value, response, modelConfi
4509
4565
  const LOCK_RETRY_MS = 15;
4510
4566
  const INCOMPLETE_RESERVATION_GRACE_MS = 15e3;
4511
4567
  let lastCreatedAt = 0;
4568
+ const localTurns = /* @__PURE__ */ new Map();
4512
4569
  async function readLock(filePath) {
4513
4570
  try {
4514
4571
  const stat = await fs$1.lstat(filePath, { bigint: true });
@@ -4522,11 +4579,6 @@ async function readLock(filePath) {
4522
4579
  throw error;
4523
4580
  }
4524
4581
  }
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
4582
  function parseLock(payload) {
4531
4583
  try {
4532
4584
  return JSON.parse(payload);
@@ -4534,7 +4586,7 @@ function parseLock(payload) {
4534
4586
  return;
4535
4587
  }
4536
4588
  }
4537
- async function tryAcquireGuard(filePath) {
4589
+ async function tryAcquireGuard(filePath, owner, signal) {
4538
4590
  try {
4539
4591
  return await acquireFileLock(filePath, {
4540
4592
  managerKey: "acpx.session-turn",
@@ -4549,9 +4601,9 @@ async function tryAcquireGuard(filePath) {
4549
4601
  randomize: false
4550
4602
  },
4551
4603
  staleRecovery: "remove-if-unchanged",
4552
- payload: () => ({ pid: process.pid }),
4553
- shouldReclaim: ({ payload }) => isProcessDefinitelyDead(lockPid(payload)),
4554
- shouldRemoveStaleLock: ({ payload }) => isProcessDefinitelyDead(lockPid(payload))
4604
+ payload: () => owner.payload,
4605
+ shouldReclaim: ({ payload }) => owner.hasExited(payload, false, signal),
4606
+ shouldRemoveStaleLock: ({ payload }) => owner.hasExited(payload, true, signal)
4555
4607
  });
4556
4608
  } catch (error) {
4557
4609
  if (error.code === "file_lock_timeout") return;
@@ -4567,11 +4619,12 @@ async function removeObservedLock(filePath, observed) {
4567
4619
  });
4568
4620
  return true;
4569
4621
  }
4570
- async function recoverAbandonedLock(filePath) {
4622
+ async function recoverAbandonedLock(filePath, owner, signal) {
4571
4623
  const observed = await readLock(filePath);
4572
4624
  if (!observed) return true;
4573
- const pid = lockPid(parseLock(observed.payload));
4574
- if (pid && !isProcessDefinitelyDead(pid)) return false;
4625
+ const payload = parseLock(observed.payload);
4626
+ const pid = lockOwnerPid(payload);
4627
+ if (pid && (!await owner.hasExited(payload, false, signal) || !await owner.hasExited(payload, true, signal))) return false;
4575
4628
  if (!pid && Date.now() - Number(observed.stat.mtimeMs) <= INCOMPLETE_RESERVATION_GRACE_MS) return false;
4576
4629
  if (await removeObservedLock(filePath, observed)) {
4577
4630
  incrementPerfCounter("session.events.stale_lock_recovered");
@@ -4607,58 +4660,112 @@ async function removePublishedLock(filePath, payload) {
4607
4660
  const current = await readLock(filePath);
4608
4661
  if (current?.payload === payload) await removeObservedLock(filePath, current);
4609
4662
  }
4610
- async function rejectAcquisition(filePath, payload, guard, error) {
4663
+ async function rejectAcquisition(filePath, receipt, error) {
4611
4664
  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);
4665
+ await disposeTurn(filePath, receipt).catch((cleanupError) => {
4666
+ failures.push(...cleanupError instanceof AggregateError ? cleanupError.errors : [cleanupError]);
4617
4667
  });
4618
4668
  if (failures.length > 1) throw new AggregateError(failures, "Session turn acquisition and cleanup failed");
4619
4669
  throw error;
4620
4670
  }
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
- })();
4671
+ async function releaseTurnGuard(receipt) {
4672
+ await receipt.guard?.release();
4673
+ receipt.guard = void 0;
4674
+ }
4675
+ async function removeTurnMarker(filePath, receipt) {
4676
+ if (!receipt.removeMarker) return true;
4677
+ if (receipt.guard && !await receipt.guard.verifyStillHeld()) await releaseTurnGuard(receipt);
4678
+ receipt.guard ??= await tryAcquireGuard(filePath, receipt.owner);
4679
+ if (!receipt.guard) return false;
4680
+ await receipt.removeMarker();
4681
+ receipt.removeMarker = void 0;
4682
+ return true;
4683
+ }
4684
+ async function settleTurn(filePath, receipt) {
4685
+ if (receipt.settled) return true;
4686
+ const pending = receipt.pending ??= (async () => {
4687
+ receipt.failed = false;
4688
+ const failures = [];
4631
4689
  try {
4632
- await pending;
4633
- } finally {
4634
- if (disposal === pending) disposal = void 0;
4690
+ if (!await removeTurnMarker(filePath, receipt)) return false;
4691
+ } catch (error) {
4692
+ failures.push(error);
4635
4693
  }
4636
- } };
4694
+ if (failures.length === 0 || !receipt.admitted) try {
4695
+ await releaseTurnGuard(receipt);
4696
+ } catch (error) {
4697
+ failures.push(error);
4698
+ }
4699
+ if (failures.length > 1) throw new AggregateError(failures, "Session turn cleanup failed");
4700
+ if (failures.length === 1) throw failures[0];
4701
+ receipt.settled = true;
4702
+ localTurns.delete(filePath);
4703
+ return true;
4704
+ })();
4705
+ try {
4706
+ return await pending;
4707
+ } finally {
4708
+ if (receipt.pending === pending) {
4709
+ receipt.pending = void 0;
4710
+ receipt.failed = !receipt.settled;
4711
+ }
4712
+ }
4637
4713
  }
4638
- async function tryPublishLock(filePath, payload, signal) {
4714
+ async function disposeTurn(filePath, receipt) {
4715
+ if (!await settleTurn(filePath, receipt)) throw new Error(`Session turn cleanup is waiting for its guard: ${filePath}`);
4716
+ }
4717
+ async function tryPublishLock(filePath, payload, owner, signal) {
4639
4718
  for (;;) try {
4640
4719
  await publishLock(filePath, payload, signal);
4641
4720
  return true;
4642
4721
  } catch (error) {
4643
4722
  if (error.code !== "EEXIST") throw error;
4644
- if (!await recoverAbandonedLock(filePath)) return false;
4723
+ if (!await recoverAbandonedLock(filePath, owner, signal)) return false;
4645
4724
  }
4646
4725
  }
4647
- async function tryAcquireTurn(filePath, payload, signal) {
4648
- const guard = await tryAcquireGuard(filePath);
4649
- if (!guard) return;
4726
+ async function tryAcquireTurnCleanup(filePath, payload, owner, signal) {
4727
+ const previous = localTurns.get(filePath);
4728
+ if (previous) {
4729
+ if (previous.failed) await settleTurn(filePath, previous);
4730
+ return;
4731
+ }
4732
+ const receipt = {
4733
+ owner,
4734
+ removeMarker: async () => await removePublishedLock(filePath, payload),
4735
+ admitted: false,
4736
+ failed: false,
4737
+ settled: false
4738
+ };
4739
+ localTurns.set(filePath, receipt);
4740
+ try {
4741
+ receipt.guard = await tryAcquireGuard(filePath, owner, signal);
4742
+ } catch (error) {
4743
+ localTurns.delete(filePath);
4744
+ throw error;
4745
+ }
4746
+ if (!receipt.guard) {
4747
+ localTurns.delete(filePath);
4748
+ return;
4749
+ }
4750
+ return receipt;
4751
+ }
4752
+ async function tryAcquireTurn(filePath, payload, owner, signal) {
4753
+ const receipt = await tryAcquireTurnCleanup(filePath, payload, owner, signal);
4754
+ if (!receipt) return;
4650
4755
  try {
4651
4756
  signal?.throwIfAborted();
4652
- if (!await tryPublishLock(filePath, payload, signal)) {
4653
- await guard.release();
4757
+ if (!await tryPublishLock(filePath, payload, owner, signal)) {
4758
+ await disposeTurn(filePath, receipt);
4654
4759
  return;
4655
4760
  }
4656
4761
  const observed = await readLock(filePath);
4657
4762
  if (observed?.payload !== payload) throw new Error(`Session turn ownership changed before admission: ${filePath}`);
4658
4763
  signal?.throwIfAborted();
4659
- return turnReceipt(filePath, observed, guard);
4764
+ receipt.admitted = true;
4765
+ receipt.removeMarker = async () => await removeObservedLock(filePath, observed);
4766
+ return { [Symbol.asyncDispose]: async () => await disposeTurn(filePath, receipt) };
4660
4767
  } catch (error) {
4661
- return await rejectAcquisition(filePath, payload, guard, error);
4768
+ return await rejectAcquisition(filePath, receipt, error);
4662
4769
  }
4663
4770
  }
4664
4771
  async function acquireSessionTurn(recordId, signal) {
@@ -4668,19 +4775,44 @@ async function acquireSessionTurn(recordId, signal) {
4668
4775
  mode: 448
4669
4776
  });
4670
4777
  const filePath = path.join(await fs$1.realpath(path.dirname(requestedPath)), path.basename(requestedPath));
4778
+ const owner = await createLockOwner({ signal });
4671
4779
  lastCreatedAt = Math.max(Date.now(), lastCreatedAt + 1);
4672
4780
  const payload = `${JSON.stringify({
4673
- pid: process.pid,
4781
+ ...owner.payload,
4674
4782
  created_at: new Date(lastCreatedAt).toISOString()
4675
4783
  }, null, 2)}\n`;
4676
4784
  for (;;) {
4677
4785
  signal?.throwIfAborted();
4678
- const turn = await tryAcquireTurn(filePath, payload, signal);
4786
+ const turn = await tryAcquireTurn(filePath, payload, owner, signal);
4679
4787
  if (turn) return turn;
4680
4788
  await setTimeout$1(LOCK_RETRY_MS, void 0, { signal });
4681
4789
  }
4682
4790
  }
4683
4791
  //#endregion
4792
+ //#region src/session/execution/direct-lifetime.ts
4793
+ function ownDirectClient(client, signal) {
4794
+ let closing;
4795
+ const cancel = async () => {
4796
+ await withTimeout(client.cancelActivePrompt(2500), 2500).catch(() => {});
4797
+ await client.close();
4798
+ };
4799
+ const onAbort = () => {
4800
+ closing ??= cancel();
4801
+ closing.catch(() => {});
4802
+ };
4803
+ signal?.addEventListener("abort", onAbort, { once: true });
4804
+ if (signal?.aborted) onAbort();
4805
+ const release = () => signal?.removeEventListener("abort", onAbort);
4806
+ return Object.assign(() => {
4807
+ release();
4808
+ closing ??= client.close();
4809
+ return closing;
4810
+ }, { release });
4811
+ }
4812
+ function directExecutionError(error, signal) {
4813
+ return signal?.aborted && error instanceof AgentDisconnectedError ? signal.reason : error;
4814
+ }
4815
+ //#endregion
4684
4816
  //#region src/session/queue/ipc-server.ts
4685
4817
  const QUEUE_SOCKET_DRAIN_TIMEOUT_MS = 1e3;
4686
4818
  function makeQueueOwnerError(requestId, message, detailCode, options = {}) {
@@ -4713,9 +4845,12 @@ function makeQueueOwnerErrorFromUnknown(requestId, error, detailCode, options =
4713
4845
  acp: normalized.acp
4714
4846
  };
4715
4847
  }
4848
+ function armSocketDrainTimeout(socket) {
4849
+ if (process.platform !== "win32") socket.setTimeout(QUEUE_SOCKET_DRAIN_TIMEOUT_MS);
4850
+ }
4716
4851
  function writeQueueMessage(socket, message) {
4717
4852
  if (socket.destroyed || !socket.writable) return;
4718
- socket.write(`${JSON.stringify(message)}\n`);
4853
+ if (!socket.write(`${JSON.stringify(message)}\n`) && !socket.timeout) armSocketDrainTimeout(socket);
4719
4854
  }
4720
4855
  var SessionQueueOwner = class SessionQueueOwner {
4721
4856
  server;
@@ -4883,6 +5018,7 @@ var SessionQueueOwner = class SessionQueueOwner {
4883
5018
  if (socket.destroyed || this.drainingSockets.has(socket)) return;
4884
5019
  this.taskSockets.delete(socket);
4885
5020
  this.drainingSockets.add(socket);
5021
+ armSocketDrainTimeout(socket);
4886
5022
  socket.end(() => {
4887
5023
  this.drainingSockets.delete(socket);
4888
5024
  socket.destroy();
@@ -5032,6 +5168,10 @@ var SessionQueueOwner = class SessionQueueOwner {
5032
5168
  handleConnection(socket) {
5033
5169
  this.sockets.add(socket);
5034
5170
  socket.setEncoding("utf8");
5171
+ socket.on("timeout", () => socket.destroy());
5172
+ socket.on("drain", () => {
5173
+ if (!socket.writableEnded) socket.setTimeout(0);
5174
+ });
5035
5175
  socket.once("close", () => {
5036
5176
  this.sockets.delete(socket);
5037
5177
  this.taskSockets.delete(socket);
@@ -5120,6 +5260,10 @@ async function replayDesiredMode(params) {
5120
5260
  await withTimeout(params.client.setSessionMode(params.sessionId, params.desiredModeId, params.replay.authority), params.timeoutMs);
5121
5261
  params.replay.acknowledged = true;
5122
5262
  if (params.verbose) process.stderr.write(`[acpx] replayed desired mode ${params.desiredModeId} on fresh ACP session ${params.sessionId} (previous ${params.previousSessionId})\n`);
5263
+ return {
5264
+ models: advertisedModelState(params.record.acpx),
5265
+ configOptionsPresent: params.record.acpx?.config_options !== void 0
5266
+ };
5123
5267
  } catch (error) {
5124
5268
  rethrowAuthorityFailure(params.replay, error);
5125
5269
  throw new SessionModeReplayError(`Failed to replay saved session mode ${params.desiredModeId} on fresh ACP session ${params.sessionId}: ${formatErrorMessage(error)}`, {
@@ -5198,18 +5342,20 @@ function restoreOriginalSessionState(params) {
5198
5342
  async function connectAndLoadSession(options) {
5199
5343
  const record = options.record;
5200
5344
  const client = options.client;
5345
+ assertControlAuthority(options.authority);
5201
5346
  const sameSessionOnly = requiresSameSession(options.resumePolicy) || Boolean(record.importedFrom);
5202
5347
  const originalSessionId = record.acpSessionId;
5203
5348
  const originalAgentSessionId = record.agentSessionId;
5204
5349
  const originalAcpx = cloneSessionAcpxState(record.acpx);
5205
- const desiredModeId = getDesiredModeId(record.acpx);
5350
+ const desiredModeId = options.replacingMode ? void 0 : getDesiredModeId(record.acpx);
5206
5351
  const desiredModelId = getDesiredModelId(record.acpx);
5207
5352
  const desiredConfigOptions = getDesiredConfigOptions(record.acpx);
5208
5353
  const storedProcessAlive = isProcessAlive(record.pid);
5209
5354
  logReconnectAttempt(record, storedProcessAlive, Boolean(record.pid) && !storedProcessAlive, options.verbose);
5210
5355
  const reusingLoadedSession = client.hasReusableSession(record.acpSessionId);
5211
5356
  if (reusingLoadedSession) incrementPerfCounter("runtime.connect_and_load.reused_session");
5212
- else await withTimeout(client.start(), options.timeoutMs);
5357
+ else await withTimeout(client.start(options.authority), options.timeoutMs);
5358
+ assertControlAuthority(options.authority);
5213
5359
  options.onClientAvailable?.(options.activeController);
5214
5360
  applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
5215
5361
  record.closed = false;
@@ -5220,7 +5366,8 @@ async function connectAndLoadSession(options) {
5220
5366
  record,
5221
5367
  reusingLoadedSession,
5222
5368
  sameSessionOnly,
5223
- timeoutMs: options.timeoutMs
5369
+ timeoutMs: options.timeoutMs,
5370
+ authority: options.authority
5224
5371
  });
5225
5372
  const { resumed, loadError, sessionId, createdFreshSession, pendingAgentSessionId, sessionModels } = loadState;
5226
5373
  applyReconnectedModelState(record, sessionModels, loadState.configOptionsPresent, loadState.legacyModelMetadataPresent, createdFreshSession);
@@ -5254,14 +5401,18 @@ async function connectAndLoadSession(options) {
5254
5401
  };
5255
5402
  }
5256
5403
  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;
5404
+ const models = resolveModeReplayModels(replay.modeMetadata, initialModels);
5405
+ if (replay.configReplay.replayed) return replay.configReplay.models ?? preserveLegacyModels(replay.modelReplay.replayed ? replay.modelReplay.models : models);
5406
+ return replay.modelReplay.replayed ? replay.modelReplay.models : models;
5407
+ }
5408
+ function resolveModeReplayModels(modeMetadata, initialModels) {
5409
+ return modeMetadata ? modeMetadata.models : initialModels;
5259
5410
  }
5260
5411
  function preserveLegacyModels(models) {
5261
5412
  return models && !models.configId ? models : void 0;
5262
5413
  }
5263
5414
  function resolveConfigOptionsPresenceAfterReplay(replay, initiallyPresent) {
5264
- return initiallyPresent || replay.configReplay.replayed || replay.modelReplay.configOptions !== void 0;
5415
+ return initiallyPresent || replay.modeMetadata?.configOptionsPresent === true || replay.configReplay.replayed || replay.modelReplay.configOptions !== void 0;
5265
5416
  }
5266
5417
  function applyReconnectedModelState(record, sessionModels, configOptionsPresent, legacyModelMetadataPresent, createdFreshSession) {
5267
5418
  clearOmittedFreshSessionConfigOptions(record, createdFreshSession, configOptionsPresent);
@@ -5302,12 +5453,13 @@ async function replaySessionPreferences(params) {
5302
5453
  };
5303
5454
  let modelReplay = { replayed: false };
5304
5455
  let configReplay = { replayed: false };
5456
+ let modeMetadata;
5305
5457
  const replay = createReplayState(params.authority);
5306
- const replacingModel = replacesModel(params.replacingConfigOption, params.sessionModels, params.originalAcpx);
5307
5458
  try {
5308
5459
  assertControlAuthority(replay.authority);
5309
- await replayDesiredMode({
5460
+ modeMetadata = await replayDesiredMode({
5310
5461
  client: params.client,
5462
+ record: params.record,
5311
5463
  sessionId: params.sessionId,
5312
5464
  desiredModeId: params.createdFreshSession ? params.desiredModeId : void 0,
5313
5465
  replay,
@@ -5315,6 +5467,8 @@ async function replaySessionPreferences(params) {
5315
5467
  timeoutMs: params.timeoutMs,
5316
5468
  verbose: params.verbose
5317
5469
  });
5470
+ const effectiveModels = resolveModeReplayModels(modeMetadata, params.sessionModels);
5471
+ const replacingModel = replacesModel(params.replacingConfigOption, effectiveModels, params.originalAcpx);
5318
5472
  modelReplay = await replayDesiredModel({
5319
5473
  client: params.client,
5320
5474
  sessionId: params.sessionId,
@@ -5322,7 +5476,7 @@ async function replaySessionPreferences(params) {
5322
5476
  replay,
5323
5477
  previousSessionId: params.originalSessionId,
5324
5478
  record: params.record,
5325
- models: params.sessionModels,
5479
+ models: effectiveModels,
5326
5480
  createdFreshSession: params.createdFreshSession,
5327
5481
  timeoutMs: params.timeoutMs,
5328
5482
  verbose: params.verbose,
@@ -5349,7 +5503,8 @@ async function replaySessionPreferences(params) {
5349
5503
  reconcileAgentSessionId(params.record, params.pendingAgentSessionId);
5350
5504
  return {
5351
5505
  modelReplay,
5352
- configReplay
5506
+ configReplay,
5507
+ modeMetadata
5353
5508
  };
5354
5509
  }
5355
5510
  function settleFailedReplay(params, replay, error) {
@@ -5381,11 +5536,14 @@ async function loadOrCreateRuntimeSession(params) {
5381
5536
  record: params.record,
5382
5537
  reason: "agent does not support session/resume or session/load"
5383
5538
  });
5384
- return await createFreshRuntimeSession(params.client, params.record, params.timeoutMs);
5539
+ return await createFreshRuntimeSession(params.client, params.record, params.timeoutMs, params.authority);
5385
5540
  }
5386
5541
  async function loadRuntimeSession(params, resume) {
5387
5542
  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);
5543
+ 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, {
5544
+ suppressReplayUpdates: true,
5545
+ ...params.authority ? { authority: params.authority } : {}
5546
+ }), params.timeoutMs);
5389
5547
  reconcileAgentSessionId(params.record, loadResult.agentSessionId);
5390
5548
  applyConfigOptionsToRecord(params.record, loadResult);
5391
5549
  return {
@@ -5398,9 +5556,14 @@ async function loadRuntimeSession(params, resume) {
5398
5556
  createdFreshSession: false
5399
5557
  };
5400
5558
  } catch (error) {
5559
+ rethrowCancelledLoad(error, params.authority);
5401
5560
  return await recoverRuntimeSessionLoadFailure(params, error);
5402
5561
  }
5403
5562
  }
5563
+ function rethrowCancelledLoad(error, authority) {
5564
+ const signal = authority?.signal;
5565
+ if (signal?.aborted && (error instanceof AgentDisconnectedError || error === signal.reason)) throw signal.reason;
5566
+ }
5404
5567
  async function recoverRuntimeSessionLoadFailure(params, error) {
5405
5568
  const loadError = formatErrorMessage(error);
5406
5569
  if (params.sameSessionOnly) throw makeSessionResumeRequiredError({
@@ -5410,12 +5573,12 @@ async function recoverRuntimeSessionLoadFailure(params, error) {
5410
5573
  });
5411
5574
  if (!shouldFallbackToNewSession(error, params.record)) throw error;
5412
5575
  return {
5413
- ...await createFreshRuntimeSession(params.client, params.record, params.timeoutMs),
5576
+ ...await createFreshRuntimeSession(params.client, params.record, params.timeoutMs, params.authority),
5414
5577
  loadError
5415
5578
  };
5416
5579
  }
5417
- async function createFreshRuntimeSession(client, record, timeoutMs) {
5418
- const createdSession = await withTimeout(client.createSession(record.cwd), timeoutMs);
5580
+ async function createFreshRuntimeSession(client, record, timeoutMs, authority) {
5581
+ const createdSession = await withTimeout(client.createSession(record.cwd, authority), timeoutMs);
5419
5582
  applyConfigOptionsToRecord(record, createdSession);
5420
5583
  return {
5421
5584
  sessionId: createdSession.sessionId,
@@ -5482,6 +5645,7 @@ async function withConnectedSession(options) {
5482
5645
  client,
5483
5646
  record,
5484
5647
  resumePolicy: options.resumePolicy,
5648
+ replacingMode: options.replacingMode,
5485
5649
  replacingConfigOption: options.replacingConfigOption,
5486
5650
  authority: options.authority,
5487
5651
  timeoutMs: options.timeoutMs,
@@ -5542,6 +5706,7 @@ async function withConnectedSession(options) {
5542
5706
  function buildDirectConnectedSessionOptions(options, run) {
5543
5707
  return {
5544
5708
  sessionRecordId: options.sessionRecordId,
5709
+ replacingMode: options.replacingMode,
5545
5710
  replacingConfigOption: options.replacingConfigOption,
5546
5711
  loadRecord: resolveSessionRecord,
5547
5712
  saveRecord: writeSessionRecord,
@@ -5592,7 +5757,10 @@ async function withOwnedDirectSession(options) {
5592
5757
  }
5593
5758
  }
5594
5759
  async function runSessionSetModeDirect(options) {
5595
- return toSessionMutationResult(await withOwnedDirectSession(buildDirectConnectedSessionOptions(options, async ({ client, sessionId, record }) => {
5760
+ return toSessionMutationResult(await withOwnedDirectSession(buildDirectConnectedSessionOptions({
5761
+ ...options,
5762
+ replacingMode: true
5763
+ }, async ({ client, sessionId, record }) => {
5596
5764
  await withTimeout(client.setSessionMode(sessionId, options.modeId), options.timeoutMs);
5597
5765
  setDesiredModeId(record, options.modeId);
5598
5766
  })));
@@ -5806,8 +5974,14 @@ const sessionControlTestInternals = {
5806
5974
  };
5807
5975
  async function closeSession(sessionId) {
5808
5976
  const record = await resolveSessionRecord(sessionId);
5809
- await tryCloseSessionOnRunningOwner({ sessionId: record.acpxRecordId }).catch(() => {});
5810
- await terminateQueueOwnerForSession(record.acpxRecordId);
5977
+ let selectedOwner;
5978
+ await tryCloseSessionOnRunningOwner({
5979
+ sessionId: record.acpxRecordId,
5980
+ onOwnerSelected: (owner) => {
5981
+ selectedOwner = owner;
5982
+ }
5983
+ }).catch(() => {});
5984
+ if (selectedOwner) await terminateQueueOwnerForSession(record.acpxRecordId, selectedOwner);
5811
5985
  if (record.pid != null && isProcessAlive(record.pid) && await isLikelyMatchingProcess(record.pid, record.agentCommand)) await terminateProcess(record.pid);
5812
5986
  const ownership = await acquireSessionTurn(record.acpxRecordId);
5813
5987
  try {
@@ -5825,9 +5999,11 @@ async function closeSession(sessionId) {
5825
5999
  //#region src/session/execution/session-management.ts
5826
6000
  async function createSessionRecordWithClient(client, options) {
5827
6001
  const cwd = absolutePath(options.cwd);
5828
- await withTimeout(client.start(), options.timeoutMs);
6002
+ await withTimeout(client.start({ signal: options.signal }), options.timeoutMs);
6003
+ assertControlAuthority({ signal: options.signal });
5829
6004
  const createdState = options.resumeSessionId ? await resumeSessionRecordWithClient(client, options, cwd) : await createFreshSessionState(client, options, cwd);
5830
6005
  const { sessionId, agentSessionId } = createdState;
6006
+ assertControlAuthority({ signal: options.signal });
5831
6007
  const lifecycle = client.getAgentLifecycleSnapshot();
5832
6008
  const record = {
5833
6009
  ...createInitialSessionRecord({
@@ -5849,10 +6025,12 @@ async function createSessionRecordWithClient(client, options) {
5849
6025
  applyConfigOptionsToRecord(record, createdState.sessionResult);
5850
6026
  applyInitialModelSelection(record, createdState.sessionResult.models, options.sessionOptions?.model, createdState.modelApplication);
5851
6027
  await writeSessionRecord(record);
6028
+ assertControlAuthority({ signal: options.signal });
5852
6029
  return record;
5853
6030
  }
5854
6031
  async function createFreshSessionState(client, options, cwd) {
5855
- const createdSession = await withTimeout(client.createSession(cwd), options.timeoutMs);
6032
+ const createdSession = await withTimeout(client.createSession(cwd, { signal: options.signal }), options.timeoutMs);
6033
+ assertControlAuthority({ signal: options.signal });
5856
6034
  const modelApplication = await applyRequestedModelIfAdvertised({
5857
6035
  client,
5858
6036
  sessionId: createdSession.sessionId,
@@ -5860,7 +6038,8 @@ async function createFreshSessionState(client, options, cwd) {
5860
6038
  models: createdSession.models,
5861
6039
  agentCommand: options.agentCommand,
5862
6040
  timeoutMs: options.timeoutMs,
5863
- onWarning: options.onModelWarning
6041
+ onWarning: options.onModelWarning,
6042
+ authority: { signal: options.signal }
5864
6043
  });
5865
6044
  return {
5866
6045
  sessionId: createdSession.sessionId,
@@ -5874,8 +6053,9 @@ async function resumeSessionRecordWithClient(client, options, cwd) {
5874
6053
  const resumeMethod = client.supportsResumeSession() ? "session/resume" : client.supportsLoadSession() ? "session/load" : void 0;
5875
6054
  if (!resumeMethod) throw new Error(`Agent command "${options.agentCommand}" does not support session/resume or session/load; cannot resume session ${options.resumeSessionId}`);
5876
6055
  try {
5877
- const resumedSession = await withTimeout(resumeMethod === "session/resume" ? client.resumeSession(options.resumeSessionId, cwd) : client.loadSession(options.resumeSessionId, cwd), options.timeoutMs);
6056
+ 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
6057
  const sessionModels = resumedSession.models;
6058
+ assertControlAuthority({ signal: options.signal });
5879
6059
  const modelApplication = await applyRequestedModelIfAdvertised({
5880
6060
  client,
5881
6061
  sessionId: options.resumeSessionId,
@@ -5883,7 +6063,8 @@ async function resumeSessionRecordWithClient(client, options, cwd) {
5883
6063
  models: sessionModels,
5884
6064
  agentCommand: options.agentCommand,
5885
6065
  timeoutMs: options.timeoutMs,
5886
- onWarning: options.onModelWarning
6066
+ onWarning: options.onModelWarning,
6067
+ authority: { signal: options.signal }
5887
6068
  });
5888
6069
  return {
5889
6070
  sessionId: options.resumeSessionId,
@@ -5896,7 +6077,7 @@ async function resumeSessionRecordWithClient(client, options, cwd) {
5896
6077
  }
5897
6078
  }
5898
6079
  async function createSessionWithClient(options) {
5899
- options.signal?.throwIfAborted();
6080
+ assertControlAuthority({ signal: options.signal });
5900
6081
  const client = new AcpClient({
5901
6082
  agentCommand: options.agentCommand,
5902
6083
  agentArgv: options.agentArgv,
@@ -5912,28 +6093,25 @@ async function createSessionWithClient(options) {
5912
6093
  verbose: options.verbose,
5913
6094
  sessionOptions: options.sessionOptions
5914
6095
  });
5915
- const onAbort = () => {
5916
- client.close().catch(() => {});
5917
- };
5918
- options.signal?.addEventListener("abort", onAbort, { once: true });
6096
+ const closeOwnedClient = ownDirectClient(client, options.signal);
5919
6097
  try {
5920
6098
  const create = async () => {
5921
- options.signal?.throwIfAborted();
6099
+ assertControlAuthority({ signal: options.signal });
5922
6100
  const record = await createSessionRecordWithClient(client, options);
5923
- options.signal?.throwIfAborted();
6101
+ assertControlAuthority({ signal: options.signal });
5924
6102
  return record;
5925
6103
  };
5926
6104
  return {
5927
6105
  record: options.handleProcessInterrupts === false ? await create() : await withInterrupt(create, async () => {
5928
- await client.close();
6106
+ await closeOwnedClient();
5929
6107
  }),
5930
6108
  client
5931
6109
  };
5932
6110
  } catch (error) {
5933
- await client.close();
5934
- throw error;
6111
+ await closeOwnedClient();
6112
+ throw directExecutionError(error, options.signal);
5935
6113
  } finally {
5936
- options.signal?.removeEventListener("abort", onAbort);
6114
+ closeOwnedClient.release();
5937
6115
  }
5938
6116
  }
5939
6117
  async function createSession(options) {
@@ -5999,7 +6177,7 @@ async function ensureSession(options) {
5999
6177
  }
6000
6178
  }
6001
6179
  async function ensureSessionWithOwnership(options, cwd) {
6002
- options.signal?.throwIfAborted();
6180
+ assertControlAuthority({ signal: options.signal });
6003
6181
  const gitRoot = findGitRepositoryRoot(cwd);
6004
6182
  const walkBoundary = options.walkBoundary ?? gitRoot ?? cwd;
6005
6183
  const existing = await findSessionByDirectoryWalk({
@@ -6509,27 +6687,27 @@ const NODE_TEST_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
6509
6687
  "--test-reporter",
6510
6688
  "--test-reporter-destination"
6511
6689
  ]);
6512
- const INSPECTOR_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
6690
+ const INSPECTOR_FLAGS = /* @__PURE__ */ new Set([
6513
6691
  "--inspect",
6514
6692
  "--inspect-brk",
6693
+ "--inspect-wait"
6694
+ ]);
6695
+ const INSPECTOR_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
6515
6696
  "--inspect-port",
6516
6697
  "--inspect-publish-uid",
6517
6698
  "--debug-port"
6518
6699
  ]);
6519
- const INSPECTOR_FLAG_PREFIXES = [
6520
- "--inspect=",
6521
- "--inspect-brk=",
6522
- "--inspect-port=",
6523
- "--inspect-publish-uid=",
6524
- "--debug-port="
6525
- ];
6526
6700
  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";
6701
+ if (NODE_TEST_FLAGS_WITH_VALUE.has(value)) return "drop-with-value";
6702
+ if (NODE_TEST_FLAGS.has(value) || value.startsWith("--test-")) return "drop";
6703
+ return classifyInspectorArgv(value);
6530
6704
  }
6531
- function dropSingleExecArgv(value) {
6532
- return NODE_TEST_FLAGS.has(value) || value.startsWith("--test-") || INSPECTOR_FLAG_PREFIXES.some((prefix) => value.startsWith(prefix));
6705
+ function classifyInspectorArgv(value) {
6706
+ const [name] = value.split("=", 1);
6707
+ if (!name?.startsWith("--")) return "keep";
6708
+ const flag = name.replaceAll("_", "-");
6709
+ if (INSPECTOR_FLAGS_WITH_VALUE.has(flag)) return value.includes("=") ? "drop" : "drop-with-value";
6710
+ return INSPECTOR_FLAGS.has(flag) ? "drop" : "keep";
6533
6711
  }
6534
6712
  function sanitizeQueueOwnerExecArgv(execArgv = process.execArgv) {
6535
6713
  const sanitized = [];
@@ -6795,10 +6973,11 @@ var QueueTaskOutputFormatter = class {
6795
6973
  this.send = task.send;
6796
6974
  }
6797
6975
  setContext(_context) {}
6798
- onAcpMessage(message) {
6976
+ onAcpMessage(message, direction) {
6799
6977
  this.send({
6800
6978
  type: "event",
6801
6979
  requestId: this.requestId,
6980
+ ...direction === void 0 ? {} : { direction },
6802
6981
  message
6803
6982
  });
6804
6983
  }
@@ -6855,7 +7034,7 @@ var AcpErrorTracker = class {
6855
7034
  this.outbound.length = 0;
6856
7035
  }
6857
7036
  observe(output, direction, message) {
6858
- output.onAcpMessage(message);
7037
+ output.onAcpMessage(message, direction);
6859
7038
  const acp = extractAcpError(message);
6860
7039
  if (!acp) return;
6861
7040
  if (direction === "inbound") {
@@ -6889,6 +7068,7 @@ async function applyPromptModelIfAdvertised(params) {
6889
7068
  models: advertisedModelState(params.record.acpx),
6890
7069
  agentCommand: params.record.agentCommand,
6891
7070
  timeoutMs: params.timeoutMs,
7071
+ authority: { signal: params.signal },
6892
7072
  onWarning: params.suppressWarnings ? void 0 : (message) => process.stderr.write(`[acpx] warning: ${message}\n`)
6893
7073
  });
6894
7074
  if (result.applied) params.record.acpx = applyModelSelection(params.record.acpx, requestedModel, result.response);
@@ -7073,7 +7253,7 @@ async function waitForSessionTurn(options) {
7073
7253
  "SIGTERM",
7074
7254
  "SIGHUP"
7075
7255
  ];
7076
- for (const signal of signals) process.once(signal, onInterrupt);
7256
+ if (options.handleProcessInterrupts !== false) for (const signal of signals) process.once(signal, onInterrupt);
7077
7257
  const timeoutMs = options.timeoutMs;
7078
7258
  const timeout = timeoutMs != null && timeoutMs > 0 ? setTimeout(() => waiting.abort(new TimeoutError(timeoutMs)), timeoutMs) : void 0;
7079
7259
  const signal = options.waitSignal ? AbortSignal.any([waiting.signal, options.waitSignal]) : waiting.signal;
@@ -7084,18 +7264,22 @@ async function waitForSessionTurn(options) {
7084
7264
  ownership: await acquireSessionTurn(acpxRecordId, signal)
7085
7265
  };
7086
7266
  } catch (error) {
7087
- signal.throwIfAborted();
7088
- throw error;
7267
+ return throwTurnAcquisitionError(error, signal);
7089
7268
  } finally {
7090
7269
  clearTimeout(timeout);
7091
7270
  for (const signal of signals) process.off(signal, onInterrupt);
7092
7271
  }
7093
7272
  }
7273
+ function throwTurnAcquisitionError(error, signal) {
7274
+ if (signal.aborted && error instanceof Error && error.name === "AbortError" && error.cause === signal.reason) throw signal.reason;
7275
+ throw error;
7276
+ }
7094
7277
  async function runSessionPrompt(options) {
7095
7278
  let turn;
7096
7279
  try {
7097
7280
  turn = await waitForSessionTurn(options);
7098
7281
  } catch (error) {
7282
+ if (options.ownedSignal) throw error;
7099
7283
  if (options.waitSignal?.aborted && error === options.waitSignal.reason) {
7100
7284
  const record = await resolveSessionRecord(options.sessionRecordId);
7101
7285
  return {
@@ -7215,8 +7399,10 @@ async function runOwnedSessionPrompt(options) {
7215
7399
  const record = await measurePerf("session.resolve_prompt_record", async () => {
7216
7400
  return await resolveSessionRecord(options.sessionRecordId);
7217
7401
  });
7402
+ options.ownedSignal?.throwIfAborted();
7218
7403
  const { conversation, promptMessageId } = preparePromptConversation(record, options);
7219
7404
  await writeSessionRecord(record);
7405
+ options.ownedSignal?.throwIfAborted();
7220
7406
  output.setContext({ sessionId: record.acpxRecordId });
7221
7407
  const eventWriter = await measurePerf("session.events.open", async () => {
7222
7408
  return await SessionEventWriter.open(record);
@@ -7259,7 +7445,7 @@ async function runOwnedSessionPrompt(options) {
7259
7445
  if (options.verbose) process.stderr.write("[acpx] live session checkpoint failed: " + formatErrorMessage(error) + "\n");
7260
7446
  }
7261
7447
  });
7262
- let closeClientOnExit = options.client == null;
7448
+ let closeClientOnExit = options.client == null || options.ownedSignal !== void 0;
7263
7449
  const client = options.client ?? new AcpClient({
7264
7450
  agentCommand: record.agentCommand,
7265
7451
  agentArgv: record.agentArgv,
@@ -7285,6 +7471,7 @@ async function runOwnedSessionPrompt(options) {
7285
7471
  suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
7286
7472
  verbose: options.verbose
7287
7473
  });
7474
+ const closeOwnedClient = options.closeProvidedClient ?? ownDirectClient(client, options.ownedSignal);
7288
7475
  client.setEventHandlers({
7289
7476
  onAcpMessage: (direction, message) => {
7290
7477
  pendingMessages.push(message);
@@ -7358,6 +7545,7 @@ async function runOwnedSessionPrompt(options) {
7358
7545
  verbose: options.verbose,
7359
7546
  suppressWarnings: options.suppressSdkConsoleErrors,
7360
7547
  activeController,
7548
+ authority: { signal: options.ownedSignal },
7361
7549
  onClientAvailable: (controller) => {
7362
7550
  options.onClientAvailable?.(controller);
7363
7551
  notifiedClientAvailable = true;
@@ -7464,15 +7652,19 @@ async function runOwnedSessionPrompt(options) {
7464
7652
  return response;
7465
7653
  };
7466
7654
  const runPrompt = async () => {
7655
+ options.ownedSignal?.throwIfAborted();
7467
7656
  const { sessionId: activeSessionId, resumed, loadError } = await connectForPrompt();
7657
+ options.ownedSignal?.throwIfAborted();
7468
7658
  await applyPromptModelIfAdvertised({
7469
7659
  client,
7470
7660
  sessionId: activeSessionId,
7471
7661
  requestedModel: options.sessionOptions?.model,
7472
7662
  record,
7473
7663
  timeoutMs: options.timeoutMs,
7474
- suppressWarnings: options.suppressSdkConsoleErrors
7664
+ suppressWarnings: options.suppressSdkConsoleErrors,
7665
+ signal: options.ownedSignal
7475
7666
  });
7667
+ options.ownedSignal?.throwIfAborted();
7476
7668
  output.setContext({ sessionId: record.acpxRecordId });
7477
7669
  await liveCheckpoint.checkpoint();
7478
7670
  const response = await savePromptSuccess(await runPromptWithRetries(activeSessionId));
@@ -7490,20 +7682,23 @@ async function runOwnedSessionPrompt(options) {
7490
7682
  record.lastUsedAt = isoNow$1();
7491
7683
  applyConversation(record, conversation);
7492
7684
  await flushPendingMessages(false).catch(() => {});
7493
- if (closeClientOnExit) await client.close();
7685
+ if (closeClientOnExit) await closeOwnedClient();
7494
7686
  };
7495
7687
  const runObservedPrompt = async () => {
7496
7688
  try {
7497
- return await runWithOptionalInterrupt({
7689
+ const result = await runWithOptionalInterrupt({
7498
7690
  handleProcessInterrupts: options.handleProcessInterrupts,
7499
7691
  run: runPrompt,
7500
7692
  handleInterrupt
7501
7693
  });
7694
+ options.ownedSignal?.throwIfAborted();
7695
+ return result;
7502
7696
  } catch (error) {
7503
- const matchedAcpError = acpErrors.match(error);
7504
- attachAcpErrorPayload(error, matchedAcpError);
7505
- markOutputAlreadyEmitted(error, matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted);
7506
- throw error;
7697
+ const failure = directExecutionError(error, options.ownedSignal);
7698
+ const matchedAcpError = acpErrors.match(failure);
7699
+ attachAcpErrorPayload(failure, matchedAcpError);
7700
+ markOutputAlreadyEmitted(failure, matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted);
7701
+ throw failure;
7507
7702
  }
7508
7703
  };
7509
7704
  const cleanupPrompt = async (attempt) => {
@@ -7516,7 +7711,7 @@ async function runOwnedSessionPrompt(options) {
7516
7711
  async () => {
7517
7712
  const journalFailed = attempt.status === "rejected" && attempt.error instanceof AcpxOperationalError && attempt.error.detailCode === "SESSION_JOURNAL_WRITE_FAILED";
7518
7713
  if (unresolvedPrompt || closeClientOnExit || journalFailed) try {
7519
- await client.close();
7714
+ await closeOwnedClient();
7520
7715
  } catch (error) {
7521
7716
  options.onClientCloseFailure?.();
7522
7717
  throw error;
@@ -7554,7 +7749,8 @@ async function runOwnedSessionPrompt(options) {
7554
7749
  cleanup: cleanupPrompt
7555
7750
  });
7556
7751
  }
7557
- async function runOnce(options) {
7752
+ async function runOnce(options, control) {
7753
+ const authority = { signal: control?.signal };
7558
7754
  const output = options.outputFormatter;
7559
7755
  const shouldMarkAcpErrorsEmitted = rendersAcpErrors(options.errorEmissionPolicy);
7560
7756
  let promptTurnActive = false;
@@ -7594,10 +7790,12 @@ async function runOnce(options) {
7594
7790
  },
7595
7791
  sessionOptions: options.sessionOptions
7596
7792
  });
7793
+ const closeOwnedClient = ownDirectClient(client, control?.signal);
7597
7794
  const runExecPromptAttempt = async (sessionId) => {
7795
+ assertControlAuthority(authority);
7598
7796
  acpErrors.reset();
7599
7797
  return await measurePerf("runtime.exec.prompt", async () => {
7600
- return await withTimeout(client.prompt(sessionId, options.prompt), options.timeoutMs);
7798
+ return await withTimeout(client.prompt(sessionId, options.prompt, void 0, void 0, void 0, { signal: control?.signal }), options.timeoutMs);
7601
7799
  });
7602
7800
  };
7603
7801
  const runExecPromptWithRetries = async (sessionId) => {
@@ -7606,78 +7804,105 @@ async function runOnce(options) {
7606
7804
  for (let attempt = 0;; attempt++) try {
7607
7805
  return await runExecPromptAttempt(sessionId);
7608
7806
  } catch (error) {
7609
- if (await preparePromptRetry(error, attempt, maxRetries, () => promptTurnHadSideEffects, options.suppressSdkConsoleErrors)) continue;
7807
+ if (await preparePromptRetry(error, attempt, maxRetries, () => promptTurnHadSideEffects, options.suppressSdkConsoleErrors, control?.signal)) continue;
7610
7808
  promptTurnActive = false;
7611
7809
  throw error;
7612
7810
  }
7613
7811
  };
7614
7812
  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);
7813
+ return await runWithOptionalInterrupt({
7814
+ handleProcessInterrupts: control?.handleProcessInterrupts,
7815
+ run: async () => {
7816
+ assertControlAuthority(authority);
7817
+ await measurePerf("runtime.exec.start", async () => {
7818
+ await withTimeout(client.start(authority), options.timeoutMs);
7819
+ });
7820
+ assertControlAuthority(authority);
7821
+ const createdSession = await measurePerf("runtime.exec.create_session", async () => {
7822
+ return await withTimeout(client.createSession(absolutePath(options.cwd), authority), options.timeoutMs);
7823
+ });
7824
+ assertControlAuthority(authority);
7825
+ const sessionId = createdSession.sessionId;
7826
+ if (createdSession.models) applyAdvertisedModelState(controlState, createdSession.models);
7827
+ controlState = applyConfigOptionsToState(controlState, createdSession.configOptions ?? []);
7828
+ const modelApplication = await applyRequestedModelIfAdvertised({
7829
+ client,
7830
+ sessionId,
7831
+ requestedModel: options.sessionOptions?.model,
7832
+ models: createdSession.models,
7833
+ agentCommand: options.agentCommand,
7834
+ timeoutMs: options.timeoutMs,
7835
+ authority,
7836
+ onWarning: options.suppressSdkConsoleErrors ? void 0 : (message) => process.stderr.write(`[acpx] warning: ${message}\n`)
7837
+ });
7838
+ if (modelApplication.response) controlState = applyConfigOptionsToState(controlState, modelApplication.response.configOptions);
7839
+ for (const configOption of options.configOptions ?? []) {
7840
+ assertControlAuthority(authority);
7841
+ const response = await withTimeout(client.setSessionConfigOption(sessionId, configOption.configId, configOption.value, advertisedModelState(controlState), authority), options.timeoutMs);
7842
+ controlState = applyConfigOptionsToState(controlState, response.configOptions);
7843
+ }
7844
+ assertControlAuthority(authority);
7845
+ output.setContext({ sessionId });
7846
+ const response = await runExecPromptWithRetries(sessionId);
7847
+ promptTurnActive = false;
7848
+ return toPromptResult(response.stopReason, sessionId, client, response._meta);
7849
+ },
7850
+ handleInterrupt: async () => {
7851
+ await client.cancelActivePrompt(INTERRUPT_CANCEL_WAIT_MS);
7852
+ await closeOwnedClient();
7638
7853
  }
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
7854
  });
7648
7855
  } catch (error) {
7649
- const matchedAcpError = acpErrors.match(error);
7650
- attachAcpErrorPayload(error, matchedAcpError);
7651
- markOutputAlreadyEmitted(error, matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted);
7652
- throw error;
7856
+ const failure = directExecutionError(error, authority.signal);
7857
+ const matchedAcpError = acpErrors.match(failure);
7858
+ attachAcpErrorPayload(failure, matchedAcpError);
7859
+ markOutputAlreadyEmitted(failure, matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted);
7860
+ throw failure;
7653
7861
  } finally {
7654
- await client.close();
7862
+ try {
7863
+ await closeOwnedClient();
7864
+ } finally {
7865
+ try {
7866
+ options.onPermissionStats?.(client.getPermissionStats());
7867
+ } catch {}
7868
+ output.flush();
7869
+ }
7655
7870
  }
7656
7871
  }
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
- });
7872
+ async function sendSessionDirect(options, control) {
7873
+ const closeProvidedClient = control && options.client ? ownDirectClient(options.client, control.signal) : void 0;
7874
+ try {
7875
+ control?.signal.throwIfAborted();
7876
+ return await runSessionPrompt({
7877
+ ownedSignal: control?.signal,
7878
+ closeProvidedClient,
7879
+ waitSignal: control?.signal,
7880
+ handleProcessInterrupts: control?.handleProcessInterrupts,
7881
+ sessionRecordId: options.sessionId,
7882
+ prompt: options.prompt,
7883
+ mcpServers: options.mcpServers,
7884
+ permissionMode: options.permissionMode,
7885
+ resumePolicy: options.resumePolicy,
7886
+ nonInteractivePermissions: options.nonInteractivePermissions,
7887
+ permissionPolicy: options.permissionPolicy,
7888
+ authCredentials: options.authCredentials,
7889
+ authPolicy: options.authPolicy,
7890
+ fs: options.fs,
7891
+ terminal: options.terminal,
7892
+ outputFormatter: options.outputFormatter,
7893
+ errorEmissionPolicy: options.errorEmissionPolicy,
7894
+ onAcpMessage: options.onAcpMessage,
7895
+ onSessionUpdate: options.onSessionUpdate,
7896
+ onClientOperation: options.onClientOperation,
7897
+ onPermissionEscalation: options.onPermissionEscalation,
7898
+ timeoutMs: options.timeoutMs,
7899
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
7900
+ verbose: options.verbose,
7901
+ client: options.client
7902
+ });
7903
+ } finally {
7904
+ await closeProvidedClient?.();
7905
+ }
7681
7906
  }
7682
7907
  //#endregion
7683
7908
  //#region src/session/execution/queue-owner-runtime.ts
@@ -8079,6 +8304,6 @@ async function sendSession(options) {
8079
8304
  }
8080
8305
  const queueOwnerRuntimeTestInternals = { queueOwnerExitIsFatal };
8081
8306
  //#endregion
8082
- 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 };
8307
+ export { isRequestedModelUnsupportedError as $, applyInitialModelSelection as A, reconcileAgentSessionId as B, setSessionModeOnOwner as C, connectAndLoadSession as D, withConnectedSession as E, persistSessionOptions as F, recordPromptSubmission as G, cloneSessionConversation as H, sessionOptionsFromRecord as I, advertisedModelState as J, recordSessionUpdate as K, applyConversation as L, applyRequestedModelIfAdvertised as M, setDesiredModeId as N, applyConfigOptionSelection as O, normalizeSessionOptionShape as P, RequestedModelUnsupportedError as Q, applyLifecycleSnapshotToRecord as R, setSessionMode as S, setSessionModelOnOwner as T, createSessionConversation as U, cloneSessionAcpxState as V, recordClientOperation as W, REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE as X, AcpClient as Y, REQUESTED_MODEL_UNSUPPORTED_REASONS as Z, cancelSessionPrompt as _, runQueuedTask as a, isLegacyZedCodexAcpInvocation as at, setSessionConfigOption as b, LiveSessionCheckpoint as c, assertControlAuthority as ct, flushPerfMetricsCapture as d, exitCodeForOutputErrorCode as dt, getAcpxVersion as et, installPerfMetricsCapture as f, normalizeOutputError as ft, listAgentSessions as g, ensureSession as h, runOnce as i, listSessionEvents as it, applyModelSelection as j, applyConfigOptionsToRecord as k, runPromptTurn as l, withInterrupt as lt, createSessionWithClient as m, runSessionQueueOwner as n, DEFAULT_QUEUE_OWNER_TTL_MS as nt, sendSessionDirect as o, InterruptedError as ot, createSession as p, trimConversationForRuntime as q, sendSession as r, normalizeQueueOwnerTtlMs as rt, DISCARD_OUTPUT_FORMATTER as s, TimeoutError as st, queueOwnerRuntimeTestInternals as t, permissionModeSatisfies as tt, buildQueueOwnerArgOverride as u, withTimeout as ut, closeSession as v, setSessionModel as w, setSessionConfigOptionOnOwner as x, sessionControlTestInternals as y, createInitialSessionRecord as z };
8083
8308
 
8084
- //# sourceMappingURL=queue-owner-runtime-Cr0dWJuL.js.map
8309
+ //# sourceMappingURL=queue-owner-runtime-C4OOosbo.js.map