acpx 0.3.1 → 0.4.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 (35) hide show
  1. package/README.md +65 -16
  2. package/dist/{acp-jsonrpc-BNHXq7qK.js → acp-jsonrpc-BbBgC5gO.js} +15 -2
  3. package/dist/acp-jsonrpc-BbBgC5gO.js.map +1 -0
  4. package/dist/cli-idpWyCOs.js +176 -0
  5. package/dist/cli-idpWyCOs.js.map +1 -0
  6. package/dist/cli.d.ts +1 -118
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +235 -336
  9. package/dist/cli.js.map +1 -1
  10. package/dist/flags-CCcX9fZj.js +194 -0
  11. package/dist/flags-CCcX9fZj.js.map +1 -0
  12. package/dist/flows-BL1tSvZT.js +1551 -0
  13. package/dist/flows-BL1tSvZT.js.map +1 -0
  14. package/dist/flows.d.ts +292 -0
  15. package/dist/flows.d.ts.map +1 -0
  16. package/dist/flows.js +2 -0
  17. package/dist/{output-BmkPP7qE.js → output-Du3m6oPQ.js} +139 -30
  18. package/dist/output-Du3m6oPQ.js.map +1 -0
  19. package/dist/{output-render-DEAaMxg8.js → output-render-Bz58qaQn.js} +10 -10
  20. package/dist/output-render-Bz58qaQn.js.map +1 -0
  21. package/dist/{queue-ipc-EQLpBMKv.js → queue-ipc-CE8_QGX3.js} +258 -95
  22. package/dist/queue-ipc-CE8_QGX3.js.map +1 -0
  23. package/dist/{session-C2Q8ktsN.js → session-RO_LZUnv.js} +687 -138
  24. package/dist/session-RO_LZUnv.js.map +1 -0
  25. package/dist/types-CeRKmEQ1.d.ts +137 -0
  26. package/dist/types-CeRKmEQ1.d.ts.map +1 -0
  27. package/package.json +44 -16
  28. package/skills/acpx/SKILL.md +22 -5
  29. package/dist/acp-jsonrpc-BNHXq7qK.js.map +0 -1
  30. package/dist/output-BmkPP7qE.js.map +0 -1
  31. package/dist/output-render-DEAaMxg8.js.map +0 -1
  32. package/dist/queue-ipc-EQLpBMKv.js.map +0 -1
  33. package/dist/runtime-session-id-C544sPPL.js +0 -31
  34. package/dist/runtime-session-id-C544sPPL.js.map +0 -1
  35. package/dist/session-C2Q8ktsN.js.map +0 -1
@@ -1,16 +1,15 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-CiIaOW0V.js";
2
- import { B as CopilotAcpUnsupportedError, E as normalizeOutputError, F as promptToDisplayText, G as SessionModeReplayError, H as PermissionDeniedError, I as textPrompt, J as extractAcpError, K as SessionNotFoundError, L as AgentSpawnError, R as AuthPolicyError, S as startPerfTimer, T as isAcpQueryClosedBeforeResponseError, U as PermissionPromptUnavailableError, V as GeminiAcpStartupTimeoutError, W as QueueConnectionError, Y as isAcpResourceNotFoundError, _ as getPerfMetricsSnapshot, a as trySetConfigOptionOnRunningOwner, b as resetPerfMetrics, c as SessionQueueOwner, d as releaseQueueOwnerLease, f as terminateProcess, g as formatPerfMetric, h as waitMs$1, i as tryCancelOnRunningOwner, j as SESSION_RECORD_SCHEMA, l as isProcessAlive, m as tryAcquireQueueOwnerLease, o as trySetModeOnRunningOwner, p as terminateQueueOwnerForSession, q as SessionResolutionError, s as trySubmitToRunningOwner, u as refreshQueueOwnerLease, v as incrementPerfCounter, w as formatErrorMessage, x as setPerfGauge, y as measurePerf, z as ClaudeAcpSessionCreateTimeoutError } from "./queue-ipc-EQLpBMKv.js";
3
- import { n as isSessionUpdateNotification, t as isAcpJsonRpcMessage } from "./acp-jsonrpc-BNHXq7qK.js";
4
- import { n as normalizeRuntimeSessionId, t as extractRuntimeSessionId } from "./runtime-session-id-C544sPPL.js";
2
+ import { $ as extractAcpError, B as AgentSpawnError, C as formatErrorMessage, E as normalizeOutputError, G as PermissionDeniedError, H as ClaudeAcpSessionCreateTimeoutError, J as SessionModeReplayError, K as PermissionPromptUnavailableError, L as promptToDisplayText, M as extractRuntimeSessionId, N as normalizeRuntimeSessionId, Q as SessionResumeRequiredError, R as textPrompt, T as isRetryablePromptError, U as CopilotAcpUnsupportedError, V as AuthPolicyError, W as GeminiAcpStartupTimeoutError, X as SessionNotFoundError, Y as SessionModelReplayError, Z as SessionResolutionError, _ as incrementPerfCounter, a as trySubmitToRunningOwner, b as setPerfGauge, c as isProcessAlive, d as terminateProcess, et as isAcpResourceNotFoundError, f as terminateQueueOwnerForSession, g as getPerfMetricsSnapshot, h as formatPerfMetric, i as trySetModelOnRunningOwner, j as SESSION_RECORD_SCHEMA, l as refreshQueueOwnerLease, m as waitMs$1, n as trySetConfigOptionOnRunningOwner, o as SessionQueueOwner, p as tryAcquireQueueOwnerLease, q as QueueConnectionError, r as trySetModeOnRunningOwner, t as tryCancelOnRunningOwner, u as releaseQueueOwnerLease, v as measurePerf, w as isAcpQueryClosedBeforeResponseError, x as startPerfTimer, y as resetPerfMetrics, z as AgentDisconnectedError } from "./queue-ipc-CE8_QGX3.js";
3
+ import { n as isAcpJsonRpcMessage, r as isSessionUpdateNotification } from "./acp-jsonrpc-BbBgC5gO.js";
5
4
  import fs, { realpathSync, statSync } from "node:fs";
6
5
  import fs$1 from "node:fs/promises";
7
6
  import path from "node:path";
7
+ import os from "node:os";
8
8
  import { spawn } from "node:child_process";
9
9
  import { Readable, Writable } from "node:stream";
10
- import { ClientSideConnection, PROTOCOL_VERSION, ndJsonStream } from "@agentclientprotocol/sdk";
10
+ import { ClientSideConnection, PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
11
11
  import readline from "node:readline/promises";
12
12
  import { randomUUID } from "node:crypto";
13
- import os from "node:os";
14
13
  //#region src/permission-prompt.ts
15
14
  async function promptForPermission(options) {
16
15
  if (!process.stdin.isTTY || !process.stderr.isTTY) return false;
@@ -159,8 +158,7 @@ var FileSystemHandlers = class {
159
158
  sliceContent(content, line, limit) {
160
159
  if (line == null && limit == null) return content;
161
160
  const lines = content.split("\n");
162
- const startLine = line == null ? 1 : Math.max(1, Math.trunc(line));
163
- const startIndex = Math.max(0, startLine - 1);
161
+ const startIndex = Math.max(0, (line == null ? 1 : Math.max(1, Math.trunc(line))) - 1);
164
162
  const maxLines = limit == null ? void 0 : Math.max(0, Math.trunc(limit));
165
163
  if (maxLines === 0) return "";
166
164
  const endIndex = maxLines == null ? lines.length : Math.min(lines.length, startIndex + maxLines);
@@ -176,6 +174,11 @@ var FileSystemHandlers = class {
176
174
  };
177
175
  //#endregion
178
176
  //#region src/permissions.ts
177
+ const PERMISSION_MODE_RANK = {
178
+ "deny-all": 0,
179
+ "approve-reads": 1,
180
+ "approve-all": 2
181
+ };
179
182
  function selected(optionId) {
180
183
  return { outcome: {
181
184
  outcome: "selected",
@@ -216,6 +219,9 @@ async function promptForToolPermission(params) {
216
219
  function canPromptForPermission$1() {
217
220
  return Boolean(process.stdin.isTTY && process.stderr.isTTY);
218
221
  }
222
+ function permissionModeSatisfies(actual, required) {
223
+ return PERMISSION_MODE_RANK[actual] >= PERMISSION_MODE_RANK[required];
224
+ }
219
225
  async function resolvePermissionRequest(params, mode, nonInteractivePolicy = "deny") {
220
226
  const options = params.options ?? [];
221
227
  if (options.length === 0) return cancelled();
@@ -284,24 +290,64 @@ async function withInterrupt(run, onInterrupt) {
284
290
  settled = true;
285
291
  process.off("SIGINT", onSigint);
286
292
  process.off("SIGTERM", onSigterm);
293
+ process.off("SIGHUP", onSighup);
287
294
  cb();
288
295
  };
289
- const onSigint = () => {
296
+ const rejectInterrupted = () => {
290
297
  onInterrupt().finally(() => {
291
298
  finish(() => reject(new InterruptedError()));
292
299
  });
293
300
  };
301
+ const onSigint = () => {
302
+ rejectInterrupted();
303
+ };
294
304
  const onSigterm = () => {
295
- onInterrupt().finally(() => {
296
- finish(() => reject(new InterruptedError()));
297
- });
305
+ rejectInterrupted();
306
+ };
307
+ const onSighup = () => {
308
+ rejectInterrupted();
298
309
  };
299
310
  process.once("SIGINT", onSigint);
300
311
  process.once("SIGTERM", onSigterm);
312
+ process.once("SIGHUP", onSighup);
301
313
  run().then((result) => finish(() => resolve(result)), (error) => finish(() => reject(error)));
302
314
  });
303
315
  }
304
316
  //#endregion
317
+ //#region src/spawn-command-options.ts
318
+ function readWindowsEnvValue(env, key) {
319
+ const matchedKey = Object.keys(env).find((entry) => entry.toUpperCase() === key);
320
+ return matchedKey ? env[matchedKey] : void 0;
321
+ }
322
+ function resolveWindowsCommand(command, env = process.env) {
323
+ const extensions = (readWindowsEnvValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
324
+ const candidates = path.extname(command).length > 0 ? [command] : extensions.map((extension) => `${command}${extension}`);
325
+ if (command.includes("/") || command.includes("\\") || path.isAbsolute(command)) return candidates.find((candidate) => fs.existsSync(candidate));
326
+ const pathValue = readWindowsEnvValue(env, "PATH");
327
+ if (!pathValue) return;
328
+ for (const directory of pathValue.split(";")) {
329
+ const trimmedDirectory = directory.trim();
330
+ if (trimmedDirectory.length === 0) continue;
331
+ for (const candidate of candidates) {
332
+ const resolved = path.join(trimmedDirectory, candidate);
333
+ if (fs.existsSync(resolved)) return resolved;
334
+ }
335
+ }
336
+ }
337
+ function shouldUseWindowsBatchShell(command, platform = process.platform, env = process.env) {
338
+ if (platform !== "win32") return false;
339
+ const resolvedCommand = resolveWindowsCommand(command, env) ?? command;
340
+ const ext = path.extname(resolvedCommand).toLowerCase();
341
+ return ext === ".cmd" || ext === ".bat";
342
+ }
343
+ function buildSpawnCommandOptions(command, options, platform = process.platform, env = process.env) {
344
+ if (!shouldUseWindowsBatchShell(command, platform, env)) return options;
345
+ return {
346
+ ...options,
347
+ shell: true
348
+ };
349
+ }
350
+ //#endregion
305
351
  //#region src/terminal.ts
306
352
  const DEFAULT_TERMINAL_OUTPUT_LIMIT_BYTES = 64 * 1024;
307
353
  const DEFAULT_KILL_GRACE_MS = 1500;
@@ -318,17 +364,18 @@ function toEnvObject(env) {
318
364
  for (const entry of env) merged[entry.name] = entry.value;
319
365
  return merged;
320
366
  }
321
- function buildTerminalSpawnOptions(cwd, env) {
322
- return {
367
+ function buildTerminalSpawnOptions(command, cwd, env, platform = process.platform) {
368
+ const resolvedEnv = toEnvObject(env);
369
+ return buildSpawnCommandOptions(command, {
323
370
  cwd,
324
- env: toEnvObject(env),
371
+ env: resolvedEnv,
325
372
  stdio: [
326
373
  "ignore",
327
374
  "pipe",
328
375
  "pipe"
329
376
  ],
330
377
  windowsHide: true
331
- };
378
+ }, platform, resolvedEnv ?? process.env);
332
379
  }
333
380
  function trimToUtf8Boundary(buffer, limit) {
334
381
  if (limit <= 0) return Buffer.alloc(0);
@@ -397,7 +444,7 @@ var TerminalManager = class {
397
444
  try {
398
445
  if (!await this.isExecuteApproved(commandLine)) throw new PermissionDeniedError("Permission denied for terminal/create");
399
446
  const outputByteLimit = Math.max(0, Math.round(params.outputByteLimit ?? DEFAULT_TERMINAL_OUTPUT_LIMIT_BYTES));
400
- const proc = spawn(params.command, params.args ?? [], buildTerminalSpawnOptions(params.cwd ?? this.cwd, params.env));
447
+ const proc = spawn(params.command, params.args ?? [], buildTerminalSpawnOptions(params.command, params.cwd ?? this.cwd, params.env));
401
448
  await waitForSpawn$1(proc);
402
449
  let resolveExit = () => {};
403
450
  const exitPromise = new Promise((resolve) => {
@@ -602,7 +649,8 @@ var TerminalManager = class {
602
649
  const REPLAY_IDLE_MS = 80;
603
650
  const REPLAY_DRAIN_TIMEOUT_MS = 5e3;
604
651
  const DRAIN_POLL_INTERVAL_MS = 20;
605
- const AGENT_CLOSE_AFTER_STDIN_END_MS = 100;
652
+ const DEFAULT_AGENT_CLOSE_AFTER_STDIN_END_MS = 100;
653
+ const QODER_AGENT_CLOSE_AFTER_STDIN_END_MS = 750;
606
654
  const AGENT_CLOSE_TERM_GRACE_MS = 1500;
607
655
  const AGENT_CLOSE_KILL_GRACE_MS = 1e3;
608
656
  const GEMINI_ACP_STARTUP_TIMEOUT_MS = 15e3;
@@ -615,6 +663,7 @@ const GEMINI_ACP_FLAG_VERSION = [
615
663
  ];
616
664
  const COPILOT_HELP_TIMEOUT_MS = 2e3;
617
665
  const SESSION_CONTROL_UNSUPPORTED_ACP_CODES = new Set([-32601, -32602]);
666
+ const QODER_BENIGN_STDOUT_LINES = new Set(["Received interrupt signal. Cleaning up resources...", "Cleanup completed. Exiting..."]);
618
667
  function shouldSuppressSdkConsoleError(args) {
619
668
  if (args.length === 0) return false;
620
669
  return typeof args[0] === "string" && args[0] === "Error handling request";
@@ -723,6 +772,56 @@ function asAbsoluteCwd(cwd) {
723
772
  function basenameToken(value) {
724
773
  return path.basename(value).toLowerCase().replace(/\.(cmd|exe|bat)$/u, "");
725
774
  }
775
+ function resolveAgentCloseAfterStdinEndMs(agentCommand) {
776
+ const { command } = splitCommandLine(agentCommand);
777
+ return basenameToken(command) === "qodercli" ? QODER_AGENT_CLOSE_AFTER_STDIN_END_MS : DEFAULT_AGENT_CLOSE_AFTER_STDIN_END_MS;
778
+ }
779
+ function shouldIgnoreNonJsonAgentOutputLine(agentCommand, trimmedLine) {
780
+ const { command } = splitCommandLine(agentCommand);
781
+ return basenameToken(command) === "qodercli" && QODER_BENIGN_STDOUT_LINES.has(trimmedLine);
782
+ }
783
+ function createNdJsonMessageStream(agentCommand, output, input) {
784
+ const textEncoder = new TextEncoder();
785
+ const textDecoder = new TextDecoder();
786
+ return {
787
+ readable: new ReadableStream({ async start(controller) {
788
+ let content = "";
789
+ const reader = input.getReader();
790
+ try {
791
+ while (true) {
792
+ const { value, done } = await reader.read();
793
+ if (done) break;
794
+ if (!value) continue;
795
+ content += textDecoder.decode(value, { stream: true });
796
+ const lines = content.split("\n");
797
+ content = lines.pop() || "";
798
+ for (const line of lines) {
799
+ const trimmedLine = line.trim();
800
+ if (!trimmedLine || shouldIgnoreNonJsonAgentOutputLine(agentCommand, trimmedLine)) continue;
801
+ try {
802
+ const message = JSON.parse(trimmedLine);
803
+ controller.enqueue(message);
804
+ } catch (err) {
805
+ console.error("Failed to parse JSON message:", trimmedLine, err);
806
+ }
807
+ }
808
+ }
809
+ } finally {
810
+ reader.releaseLock();
811
+ controller.close();
812
+ }
813
+ } }),
814
+ writable: new WritableStream({ async write(message) {
815
+ const content = JSON.stringify(message) + "\n";
816
+ const writer = output.getWriter();
817
+ try {
818
+ await writer.write(textEncoder.encode(content));
819
+ } finally {
820
+ writer.releaseLock();
821
+ }
822
+ } })
823
+ };
824
+ }
726
825
  function isGeminiAcpCommand(command, args) {
727
826
  return basenameToken(command) === "gemini" && (args.includes("--acp") || args.includes("--experimental-acp"));
728
827
  }
@@ -733,37 +832,32 @@ function isClaudeAcpCommand(command, args) {
733
832
  function isCopilotAcpCommand(command, args) {
734
833
  return basenameToken(command) === "copilot" && args.includes("--acp");
735
834
  }
736
- function readWindowsEnvValue(env, key) {
737
- const matchedKey = Object.keys(env).find((entry) => entry.toUpperCase() === key);
738
- return matchedKey ? env[matchedKey] : void 0;
835
+ function isQoderAcpCommand(command, args) {
836
+ return basenameToken(command) === "qodercli" && args.includes("--acp");
739
837
  }
740
- function resolveWindowsCommand(command, env = process.env) {
741
- const extensions = (readWindowsEnvValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
742
- const candidates = path.extname(command).length > 0 ? [command] : extensions.map((extension) => `${command}${extension}`);
743
- if (command.includes("/") || command.includes("\\") || path.isAbsolute(command)) return candidates.find((candidate) => fs.existsSync(candidate));
744
- const pathValue = readWindowsEnvValue(env, "PATH");
745
- if (!pathValue) return;
746
- for (const directory of pathValue.split(";")) {
747
- const trimmedDirectory = directory.trim();
748
- if (trimmedDirectory.length === 0) continue;
749
- for (const candidate of candidates) {
750
- const resolved = path.join(trimmedDirectory, candidate);
751
- if (fs.existsSync(resolved)) return resolved;
752
- }
753
- }
838
+ function hasCommandFlag(args, flagName) {
839
+ return args.some((arg) => arg === flagName || arg.startsWith(`${flagName}=`));
754
840
  }
755
- function shouldUseWindowsBatchShell(command, platform = process.platform, env = process.env) {
756
- if (platform !== "win32") return false;
757
- const resolvedCommand = resolveWindowsCommand(command, env) ?? command;
758
- const ext = path.extname(resolvedCommand).toLowerCase();
759
- return ext === ".cmd" || ext === ".bat";
841
+ function normalizeQoderAllowedToolName(tool) {
842
+ switch (tool.trim().toLowerCase()) {
843
+ case "bash":
844
+ case "glob":
845
+ case "grep":
846
+ case "ls":
847
+ case "read":
848
+ case "write": return tool.trim().toUpperCase();
849
+ default: return tool.trim();
850
+ }
760
851
  }
761
- function buildSpawnCommandOptions(command, options, platform = process.platform, env = process.env) {
762
- if (!shouldUseWindowsBatchShell(command, platform, env)) return options;
763
- return {
764
- ...options,
765
- shell: true
766
- };
852
+ function buildQoderAcpCommandArgs(initialArgs, options) {
853
+ const args = [...initialArgs];
854
+ const sessionOptions = options.sessionOptions;
855
+ if (typeof sessionOptions?.maxTurns === "number" && !hasCommandFlag(args, "--max-turns")) args.push(`--max-turns=${sessionOptions.maxTurns}`);
856
+ if (Array.isArray(sessionOptions?.allowedTools) && !hasCommandFlag(args, "--allowed-tools") && !hasCommandFlag(args, "--disallowed-tools")) {
857
+ const encodedTools = sessionOptions.allowedTools.map(normalizeQoderAllowedToolName).join(",");
858
+ args.push(`--allowed-tools=${encodedTools}`);
859
+ }
860
+ return args;
767
861
  }
768
862
  function resolveGeminiAcpStartupTimeoutMs() {
769
863
  const raw = process.env.ACPX_GEMINI_ACP_STARTUP_TIMEOUT_MS;
@@ -904,7 +998,7 @@ async function buildGeminiAcpStartupTimeoutMessage(command) {
904
998
  function buildClaudeAcpSessionCreateTimeoutMessage() {
905
999
  return [
906
1000
  "Claude ACP session creation timed out before session/new completed.",
907
- "This matches the known persistent-session stall seen with some Claude Code and @zed-industries/claude-agent-acp combinations.",
1001
+ "This matches the known persistent-session stall seen with some Claude Code and @agentclientprotocol/claude-agent-acp combinations.",
908
1002
  "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."
909
1003
  ].join(" ");
910
1004
  }
@@ -922,7 +1016,7 @@ async function ensureCopilotAcpSupport(command) {
922
1016
  function toEnvToken(value) {
923
1017
  return value.trim().replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
924
1018
  }
925
- function authEnvKeys(methodId) {
1019
+ function buildAuthEnvKeys(methodId) {
926
1020
  const token = toEnvToken(methodId);
927
1021
  const keys = new Set([methodId]);
928
1022
  if (token) {
@@ -931,6 +1025,14 @@ function authEnvKeys(methodId) {
931
1025
  }
932
1026
  return [...keys];
933
1027
  }
1028
+ const authEnvKeysCache = /* @__PURE__ */ new Map();
1029
+ function authEnvKeys(methodId) {
1030
+ const cached = authEnvKeysCache.get(methodId);
1031
+ if (cached) return cached;
1032
+ const keys = buildAuthEnvKeys(methodId);
1033
+ authEnvKeysCache.set(methodId, keys);
1034
+ return keys;
1035
+ }
934
1036
  function readEnvCredential(methodId) {
935
1037
  for (const key of authEnvKeys(methodId)) {
936
1038
  const value = process.env[key];
@@ -1024,6 +1126,7 @@ var AcpClient = class {
1024
1126
  lastAgentExit;
1025
1127
  lastKnownPid;
1026
1128
  promptPermissionFailures = /* @__PURE__ */ new Map();
1129
+ pendingConnectionRequests = /* @__PURE__ */ new Set();
1027
1130
  constructor(options) {
1028
1131
  this.options = {
1029
1132
  ...options,
@@ -1103,7 +1206,8 @@ var AcpClient = class {
1103
1206
  if (this.connection && this.agent && isChildProcessRunning(this.agent)) return;
1104
1207
  if (this.connection || this.agent) await this.close();
1105
1208
  const { command, args: initialArgs } = splitCommandLine(this.options.agentCommand);
1106
- const args = await resolveGeminiCommandArgs(command, initialArgs);
1209
+ let args = await resolveGeminiCommandArgs(command, initialArgs);
1210
+ if (isQoderAcpCommand(command, args)) args = buildQoderAcpCommandArgs(args, this.options);
1107
1211
  this.log(`spawning agent: ${command} ${args.join(" ")}`);
1108
1212
  const geminiAcp = isGeminiAcpCommand(command, args);
1109
1213
  if (isCopilotAcpCommand(command, args)) await ensureCopilotAcpSupport(command);
@@ -1153,7 +1257,7 @@ var AcpClient = class {
1153
1257
  releaseTerminal: async (params) => {
1154
1258
  return this.handleReleaseTerminal(params);
1155
1259
  }
1156
- }), this.createTappedStream(ndJsonStream(input, output)));
1260
+ }), this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output)));
1157
1261
  connection.signal.addEventListener("abort", () => {
1158
1262
  this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null);
1159
1263
  }, { once: true });
@@ -1230,11 +1334,11 @@ var AcpClient = class {
1230
1334
  const claudeAcp = isClaudeAcpCommand(command, args);
1231
1335
  let result;
1232
1336
  try {
1233
- const createPromise = connection.newSession({
1337
+ const createPromise = this.runConnectionRequest(() => connection.newSession({
1234
1338
  cwd: asAbsoluteCwd(cwd),
1235
1339
  mcpServers: this.options.mcpServers ?? [],
1236
1340
  _meta: buildClaudeCodeOptionsMeta(this.options.sessionOptions)
1237
- });
1341
+ }));
1238
1342
  result = claudeAcp ? await withTimeout(createPromise, resolveClaudeAcpSessionCreateTimeoutMs()) : await createPromise;
1239
1343
  } catch (error) {
1240
1344
  if (claudeAcp && error instanceof TimeoutError) throw new ClaudeAcpSessionCreateTimeoutError(buildClaudeAcpSessionCreateTimeoutMessage(), {
@@ -1246,7 +1350,8 @@ var AcpClient = class {
1246
1350
  this.loadedSessionId = result.sessionId;
1247
1351
  return {
1248
1352
  sessionId: result.sessionId,
1249
- agentSessionId: extractRuntimeSessionId(result._meta)
1353
+ agentSessionId: extractRuntimeSessionId(result._meta),
1354
+ models: result.models ?? void 0
1250
1355
  };
1251
1356
  }
1252
1357
  async loadSession(sessionId, cwd = this.options.cwd) {
@@ -1261,28 +1366,31 @@ var AcpClient = class {
1261
1366
  this.suppressReplaySessionUpdateMessages = previousReplaySuppression || Boolean(options.suppressReplayUpdates);
1262
1367
  let response;
1263
1368
  try {
1264
- response = await connection.loadSession({
1369
+ response = await this.runConnectionRequest(() => connection.loadSession({
1265
1370
  sessionId,
1266
1371
  cwd: asAbsoluteCwd(cwd),
1267
1372
  mcpServers: this.options.mcpServers ?? []
1268
- });
1373
+ }));
1269
1374
  await this.waitForSessionUpdateDrain(options.replayIdleMs ?? REPLAY_IDLE_MS, options.replayDrainTimeoutMs ?? REPLAY_DRAIN_TIMEOUT_MS);
1270
1375
  } finally {
1271
1376
  this.suppressSessionUpdates = previousSuppression;
1272
1377
  this.suppressReplaySessionUpdateMessages = previousReplaySuppression;
1273
1378
  }
1274
1379
  this.loadedSessionId = sessionId;
1275
- return { agentSessionId: extractRuntimeSessionId(response?._meta) };
1380
+ return {
1381
+ agentSessionId: extractRuntimeSessionId(response?._meta),
1382
+ models: response?.models ?? void 0
1383
+ };
1276
1384
  }
1277
1385
  async prompt(sessionId, prompt) {
1278
1386
  const connection = this.getConnection();
1279
1387
  const restoreConsoleError = this.options.suppressSdkConsoleErrors ? installSdkConsoleErrorSuppression() : void 0;
1280
1388
  let promptPromise;
1281
1389
  try {
1282
- promptPromise = connection.prompt({
1390
+ promptPromise = this.runConnectionRequest(() => connection.prompt({
1283
1391
  sessionId,
1284
1392
  prompt: typeof prompt === "string" ? textPrompt(prompt) : prompt
1285
- });
1393
+ }));
1286
1394
  } catch (error) {
1287
1395
  restoreConsoleError?.();
1288
1396
  throw error;
@@ -1310,10 +1418,10 @@ var AcpClient = class {
1310
1418
  async setSessionMode(sessionId, modeId) {
1311
1419
  const connection = this.getConnection();
1312
1420
  try {
1313
- await connection.setSessionMode({
1421
+ await this.runConnectionRequest(() => connection.setSessionMode({
1314
1422
  sessionId,
1315
1423
  modeId
1316
- });
1424
+ }));
1317
1425
  } catch (error) {
1318
1426
  throw maybeWrapSessionControlError("session/set_mode", error, `for mode "${modeId}"`);
1319
1427
  }
@@ -1321,19 +1429,35 @@ var AcpClient = class {
1321
1429
  async setSessionConfigOption(sessionId, configId, value) {
1322
1430
  const connection = this.getConnection();
1323
1431
  try {
1324
- return await connection.setSessionConfigOption({
1432
+ return await this.runConnectionRequest(() => connection.setSessionConfigOption({
1325
1433
  sessionId,
1326
1434
  configId,
1327
1435
  value
1328
- });
1436
+ }));
1329
1437
  } catch (error) {
1330
1438
  throw maybeWrapSessionControlError("session/set_config_option", error, `for "${configId}"="${value}"`);
1331
1439
  }
1332
1440
  }
1441
+ async setSessionModel(sessionId, modelId) {
1442
+ const connection = this.getConnection();
1443
+ try {
1444
+ await this.runConnectionRequest(() => connection.unstable_setSessionModel({
1445
+ sessionId,
1446
+ modelId
1447
+ }));
1448
+ } catch (error) {
1449
+ const wrapped = maybeWrapSessionControlError("session/set_model", error, `for model "${modelId}"`);
1450
+ if (wrapped !== error) throw wrapped;
1451
+ const acp = extractAcpError(error);
1452
+ const summary = acp ? formatSessionControlAcpSummary(acp) : error instanceof Error ? error.message : String(error);
1453
+ if (error instanceof Error) throw new Error(`Failed session/set_model for model "${modelId}": ${summary}`, { cause: error });
1454
+ throw new Error(`Failed session/set_model for model "${modelId}": ${summary}`, { cause: error });
1455
+ }
1456
+ }
1333
1457
  async cancel(sessionId) {
1334
1458
  const connection = this.getConnection();
1335
1459
  this.cancellingSessionIds.add(sessionId);
1336
- await connection.cancel({ sessionId });
1460
+ await this.runConnectionRequest(() => connection.cancel({ sessionId }));
1337
1461
  }
1338
1462
  async requestCancelActivePrompt() {
1339
1463
  const active = this.activePrompt;
@@ -1366,6 +1490,7 @@ var AcpClient = class {
1366
1490
  await this.terminalManager.shutdown();
1367
1491
  const agent = this.agent;
1368
1492
  if (agent) await this.terminateAgentProcess(agent);
1493
+ 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) }));
1369
1494
  this.sessionUpdateChain = Promise.resolve();
1370
1495
  this.observedSessionUpdates = 0;
1371
1496
  this.processedSessionUpdates = 0;
@@ -1380,10 +1505,11 @@ var AcpClient = class {
1380
1505
  this.agent = void 0;
1381
1506
  }
1382
1507
  async terminateAgentProcess(child) {
1508
+ const stdinCloseGraceMs = resolveAgentCloseAfterStdinEndMs(this.options.agentCommand);
1383
1509
  if (!child.stdin.destroyed) try {
1384
1510
  child.stdin.end();
1385
1511
  } catch {}
1386
- let exited = await waitForChildExit(child, AGENT_CLOSE_AFTER_STDIN_END_MS);
1512
+ let exited = await waitForChildExit(child, stdinCloseGraceMs);
1387
1513
  if (!exited && isChildProcessRunning(child)) {
1388
1514
  try {
1389
1515
  child.kill("SIGTERM");
@@ -1483,6 +1609,7 @@ var AcpClient = class {
1483
1609
  reason,
1484
1610
  unexpectedDuringPrompt: !this.closing && Boolean(this.activePrompt)
1485
1611
  };
1612
+ this.rejectPendingConnectionRequests(new AgentDisconnectedError(reason, exitCode, signal, { outputAlreadyEmitted: Boolean(this.activePrompt) }));
1486
1613
  }
1487
1614
  notePromptPermissionFailure(sessionId, error) {
1488
1615
  if (!this.promptPermissionFailures.has(sessionId)) this.promptPermissionFailures.set(sessionId, error);
@@ -1492,6 +1619,33 @@ var AcpClient = class {
1492
1619
  if (error) this.promptPermissionFailures.delete(sessionId);
1493
1620
  return error;
1494
1621
  }
1622
+ async runConnectionRequest(run) {
1623
+ return await new Promise((resolve, reject) => {
1624
+ const pending = {
1625
+ settled: false,
1626
+ reject
1627
+ };
1628
+ const finish = (cb) => {
1629
+ if (pending.settled) return;
1630
+ pending.settled = true;
1631
+ this.pendingConnectionRequests.delete(pending);
1632
+ cb();
1633
+ };
1634
+ this.pendingConnectionRequests.add(pending);
1635
+ Promise.resolve().then(run).then((value) => finish(() => resolve(value)), (error) => finish(() => reject(error)));
1636
+ });
1637
+ }
1638
+ rejectPendingConnectionRequests(error) {
1639
+ for (const pending of this.pendingConnectionRequests) {
1640
+ if (pending.settled) {
1641
+ this.pendingConnectionRequests.delete(pending);
1642
+ continue;
1643
+ }
1644
+ pending.settled = true;
1645
+ this.pendingConnectionRequests.delete(pending);
1646
+ pending.reject(error);
1647
+ }
1648
+ }
1495
1649
  async handleReadTextFile(params) {
1496
1650
  try {
1497
1651
  return await this.filesystem.readTextFile(params);
@@ -1908,8 +2062,15 @@ function cloneSessionAcpxState(state) {
1908
2062
  return {
1909
2063
  current_mode_id: state.current_mode_id,
1910
2064
  desired_mode_id: state.desired_mode_id,
2065
+ current_model_id: state.current_model_id,
2066
+ available_models: state.available_models ? [...state.available_models] : void 0,
1911
2067
  available_commands: state.available_commands ? [...state.available_commands] : void 0,
1912
- config_options: state.config_options ? deepClone(state.config_options) : void 0
2068
+ config_options: state.config_options ? deepClone(state.config_options) : void 0,
2069
+ session_options: state.session_options ? {
2070
+ model: state.session_options.model,
2071
+ allowed_tools: state.session_options.allowed_tools ? [...state.session_options.allowed_tools] : void 0,
2072
+ max_turns: state.session_options.max_turns
2073
+ } : void 0
1913
2074
  };
1914
2075
  }
1915
2076
  function recordPromptSubmission(conversation, prompt, timestamp = isoNow$1()) {
@@ -2264,8 +2425,18 @@ function parseAcpxState(raw) {
2264
2425
  const state = {};
2265
2426
  if (typeof record.current_mode_id === "string") state.current_mode_id = record.current_mode_id;
2266
2427
  if (typeof record.desired_mode_id === "string") state.desired_mode_id = record.desired_mode_id;
2428
+ if (typeof record.current_model_id === "string") state.current_model_id = record.current_model_id;
2429
+ if (isStringArray(record.available_models)) state.available_models = [...record.available_models];
2267
2430
  if (isStringArray(record.available_commands)) state.available_commands = [...record.available_commands];
2268
2431
  if (Array.isArray(record.config_options)) state.config_options = record.config_options;
2432
+ const sessionOptions = asRecord$1(record.session_options);
2433
+ if (sessionOptions) {
2434
+ const parsedSessionOptions = {};
2435
+ if (typeof sessionOptions.model === "string") parsedSessionOptions.model = sessionOptions.model;
2436
+ if (isStringArray(sessionOptions.allowed_tools)) parsedSessionOptions.allowed_tools = [...sessionOptions.allowed_tools];
2437
+ if (typeof sessionOptions.max_turns === "number" && Number.isInteger(sessionOptions.max_turns) && sessionOptions.max_turns > 0) parsedSessionOptions.max_turns = sessionOptions.max_turns;
2438
+ if (Object.keys(parsedSessionOptions).length > 0) state.session_options = parsedSessionOptions;
2439
+ }
2269
2440
  return state;
2270
2441
  }
2271
2442
  function parseEventLog(raw, sessionId) {
@@ -2865,6 +3036,15 @@ var QueueOwnerTurnController = class {
2865
3036
  }
2866
3037
  await this.options.setSessionModeFallback(modeId, timeoutMs);
2867
3038
  }
3039
+ async setSessionModel(modelId, timeoutMs) {
3040
+ this.assertCanHandleControlRequest();
3041
+ const activeController = this.activeController;
3042
+ if (activeController) {
3043
+ await this.options.withTimeout(async () => await activeController.setSessionModel(modelId), timeoutMs);
3044
+ return;
3045
+ }
3046
+ await this.options.setSessionModelFallback(modelId, timeoutMs);
3047
+ }
2868
3048
  async setSessionConfigOption(configId, value, timeoutMs) {
2869
3049
  this.assertCanHandleControlRequest();
2870
3050
  const activeController = this.activeController;
@@ -2882,6 +3062,11 @@ function normalizeModeId(modeId) {
2882
3062
  const trimmed = modeId.trim();
2883
3063
  return trimmed.length > 0 ? trimmed : void 0;
2884
3064
  }
3065
+ function normalizeModelId(modelId) {
3066
+ if (typeof modelId !== "string") return;
3067
+ const trimmed = modelId.trim();
3068
+ return trimmed.length > 0 ? trimmed : void 0;
3069
+ }
2885
3070
  function getDesiredModeId(state) {
2886
3071
  return normalizeModeId(state?.desired_mode_id);
2887
3072
  }
@@ -2892,6 +3077,33 @@ function setDesiredModeId(record, modeId) {
2892
3077
  else delete acpx.desired_mode_id;
2893
3078
  record.acpx = acpx;
2894
3079
  }
3080
+ function getDesiredModelId(state) {
3081
+ return normalizeModelId(state?.session_options?.model);
3082
+ }
3083
+ function setDesiredModelId(record, modelId) {
3084
+ const acpx = ensureAcpxState(record.acpx);
3085
+ const normalized = normalizeModelId(modelId);
3086
+ const sessionOptions = { ...acpx.session_options };
3087
+ if (normalized) sessionOptions.model = normalized;
3088
+ else delete sessionOptions.model;
3089
+ if (typeof sessionOptions.model === "string" || Array.isArray(sessionOptions.allowed_tools) || typeof sessionOptions.max_turns === "number") acpx.session_options = sessionOptions;
3090
+ else delete acpx.session_options;
3091
+ record.acpx = acpx;
3092
+ }
3093
+ function setCurrentModelId(record, modelId) {
3094
+ const acpx = ensureAcpxState(record.acpx);
3095
+ const normalized = normalizeModelId(modelId);
3096
+ if (normalized) acpx.current_model_id = normalized;
3097
+ else delete acpx.current_model_id;
3098
+ record.acpx = acpx;
3099
+ }
3100
+ function syncAdvertisedModelState(record, models) {
3101
+ if (!models) return;
3102
+ const acpx = ensureAcpxState(record.acpx);
3103
+ acpx.current_model_id = models.currentModelId;
3104
+ acpx.available_models = models.availableModels.map((model) => model.modelId);
3105
+ record.acpx = acpx;
3106
+ }
2895
3107
  //#endregion
2896
3108
  //#region src/session-runtime/lifecycle.ts
2897
3109
  function applyLifecycleSnapshotToRecord(record, snapshot) {
@@ -2926,21 +3138,32 @@ function applyConversation(record, conversation) {
2926
3138
  }
2927
3139
  //#endregion
2928
3140
  //#region src/session-runtime/connect-load.ts
3141
+ const SESSION_LOAD_UNSUPPORTED_CODES = new Set([-32601, -32602]);
2929
3142
  function shouldFallbackToNewSession(error, record) {
2930
3143
  if (error instanceof TimeoutError || error instanceof InterruptedError) return false;
2931
3144
  if (isAcpResourceNotFoundError(error)) return true;
3145
+ const acp = extractAcpError(error);
3146
+ if (acp && SESSION_LOAD_UNSUPPORTED_CODES.has(acp.code)) return true;
2932
3147
  if (!sessionHasAgentMessages(record)) {
2933
3148
  if (isAcpQueryClosedBeforeResponseError(error)) return true;
2934
- if (extractAcpError(error)?.code === -32603) return true;
3149
+ if (acp?.code === -32603) return true;
2935
3150
  }
2936
3151
  return false;
2937
3152
  }
3153
+ function requiresSameSession(resumePolicy) {
3154
+ return resumePolicy === "same-session-only";
3155
+ }
3156
+ function makeSessionResumeRequiredError(params) {
3157
+ return new SessionResumeRequiredError(`Persistent ACP session ${params.record.acpSessionId} could not be resumed: ${params.reason}`, { cause: params.cause instanceof Error ? params.cause : void 0 });
3158
+ }
2938
3159
  async function connectAndLoadSession(options) {
2939
3160
  const record = options.record;
2940
3161
  const client = options.client;
3162
+ const sameSessionOnly = requiresSameSession(options.resumePolicy);
2941
3163
  const originalSessionId = record.acpSessionId;
2942
3164
  const originalAgentSessionId = record.agentSessionId;
2943
3165
  const desiredModeId = getDesiredModeId(record.acpx);
3166
+ const desiredModelId = getDesiredModelId(record.acpx);
2944
3167
  const storedProcessAlive = isProcessAlive(record.pid);
2945
3168
  const shouldReconnect = Boolean(record.pid) && !storedProcessAlive;
2946
3169
  if (options.verbose) {
@@ -2960,23 +3183,37 @@ async function connectAndLoadSession(options) {
2960
3183
  let sessionId = record.acpSessionId;
2961
3184
  let createdFreshSession = false;
2962
3185
  let pendingAgentSessionId = record.agentSessionId;
3186
+ let sessionModels;
2963
3187
  if (reusingLoadedSession) resumed = true;
2964
3188
  else if (client.supportsLoadSession()) try {
2965
- reconcileAgentSessionId(record, (await withTimeout(client.loadSessionWithOptions(record.acpSessionId, record.cwd, { suppressReplayUpdates: true }), options.timeoutMs)).agentSessionId);
3189
+ const loadResult = await withTimeout(client.loadSessionWithOptions(record.acpSessionId, record.cwd, { suppressReplayUpdates: true }), options.timeoutMs);
3190
+ reconcileAgentSessionId(record, loadResult.agentSessionId);
3191
+ sessionModels = loadResult.models;
2966
3192
  resumed = true;
2967
3193
  } catch (error) {
2968
3194
  loadError = formatErrorMessage(error);
3195
+ if (sameSessionOnly) throw makeSessionResumeRequiredError({
3196
+ record,
3197
+ reason: loadError,
3198
+ cause: error
3199
+ });
2969
3200
  if (!shouldFallbackToNewSession(error, record)) throw error;
2970
3201
  const createdSession = await withTimeout(client.createSession(record.cwd), options.timeoutMs);
2971
3202
  sessionId = createdSession.sessionId;
2972
3203
  createdFreshSession = true;
2973
3204
  pendingAgentSessionId = createdSession.agentSessionId;
3205
+ sessionModels = createdSession.models;
2974
3206
  }
2975
3207
  else {
3208
+ if (sameSessionOnly) throw makeSessionResumeRequiredError({
3209
+ record,
3210
+ reason: "agent does not support session/load"
3211
+ });
2976
3212
  const createdSession = await withTimeout(client.createSession(record.cwd), options.timeoutMs);
2977
3213
  sessionId = createdSession.sessionId;
2978
3214
  createdFreshSession = true;
2979
3215
  pendingAgentSessionId = createdSession.agentSessionId;
3216
+ sessionModels = createdSession.models;
2980
3217
  }
2981
3218
  if (createdFreshSession && desiredModeId) try {
2982
3219
  await withTimeout(client.setSessionMode(sessionId, desiredModeId), options.timeoutMs);
@@ -2991,10 +3228,26 @@ async function connectAndLoadSession(options) {
2991
3228
  retryable: true
2992
3229
  });
2993
3230
  }
3231
+ if (createdFreshSession && desiredModelId && sessionModels && desiredModelId !== sessionModels.currentModelId) try {
3232
+ await withTimeout(client.setSessionModel(sessionId, desiredModelId), options.timeoutMs);
3233
+ setCurrentModelId(record, desiredModelId);
3234
+ if (options.verbose) process.stderr.write(`[acpx] replayed desired model ${desiredModelId} on fresh ACP session ${sessionId} (previous ${originalSessionId})\n`);
3235
+ } catch (error) {
3236
+ const message = `Failed to replay saved session model ${desiredModelId} on fresh ACP session ${sessionId}: ` + formatErrorMessage(error);
3237
+ record.acpSessionId = originalSessionId;
3238
+ record.agentSessionId = originalAgentSessionId;
3239
+ if (options.verbose) process.stderr.write(`[acpx] ${message}\n`);
3240
+ throw new SessionModelReplayError(message, {
3241
+ cause: error instanceof Error ? error : void 0,
3242
+ retryable: true
3243
+ });
3244
+ }
2994
3245
  if (createdFreshSession) {
2995
3246
  record.acpSessionId = sessionId;
2996
3247
  reconcileAgentSessionId(record, pendingAgentSessionId);
2997
3248
  }
3249
+ syncAdvertisedModelState(record, sessionModels);
3250
+ if (createdFreshSession && desiredModelId && sessionModels) setCurrentModelId(record, desiredModelId);
2998
3251
  options.onSessionIdResolved?.(sessionId);
2999
3252
  return {
3000
3253
  sessionId,
@@ -3005,6 +3258,15 @@ async function connectAndLoadSession(options) {
3005
3258
  }
3006
3259
  //#endregion
3007
3260
  //#region src/session-runtime/prompt-runner.ts
3261
+ function sessionOptionsFromRecord$1(record) {
3262
+ const stored = record.acpx?.session_options;
3263
+ if (!stored) return;
3264
+ const sessionOptions = {};
3265
+ if (typeof stored.model === "string" && stored.model.trim().length > 0) sessionOptions.model = stored.model;
3266
+ if (Array.isArray(stored.allowed_tools)) sessionOptions.allowedTools = [...stored.allowed_tools];
3267
+ if (typeof stored.max_turns === "number") sessionOptions.maxTurns = stored.max_turns;
3268
+ return Object.keys(sessionOptions).length > 0 ? sessionOptions : void 0;
3269
+ }
3008
3270
  async function withConnectedSession(options) {
3009
3271
  const record = await resolveSessionRecord(options.sessionRecordId);
3010
3272
  const client = new AcpClient({
@@ -3015,7 +3277,8 @@ async function withConnectedSession(options) {
3015
3277
  nonInteractivePermissions: options.nonInteractivePermissions,
3016
3278
  authCredentials: options.authCredentials,
3017
3279
  authPolicy: options.authPolicy,
3018
- verbose: options.verbose
3280
+ verbose: options.verbose,
3281
+ sessionOptions: sessionOptionsFromRecord$1(record)
3019
3282
  });
3020
3283
  let activeSessionIdForControl = record.acpSessionId;
3021
3284
  let notifiedClientAvailable = false;
@@ -3025,6 +3288,9 @@ async function withConnectedSession(options) {
3025
3288
  setSessionMode: async (modeId) => {
3026
3289
  await client.setSessionMode(activeSessionIdForControl, modeId);
3027
3290
  },
3291
+ setSessionModel: async (modelId) => {
3292
+ await client.setSessionModel(activeSessionIdForControl, modelId);
3293
+ },
3028
3294
  setSessionConfigOption: async (configId, value) => {
3029
3295
  return await client.setSessionConfigOption(activeSessionIdForControl, configId, value);
3030
3296
  }
@@ -3095,6 +3361,29 @@ async function runSessionSetModeDirect(options) {
3095
3361
  loadError: result.loadError
3096
3362
  };
3097
3363
  }
3364
+ async function runSessionSetModelDirect(options) {
3365
+ const result = await withConnectedSession({
3366
+ sessionRecordId: options.sessionRecordId,
3367
+ mcpServers: options.mcpServers,
3368
+ nonInteractivePermissions: options.nonInteractivePermissions,
3369
+ authCredentials: options.authCredentials,
3370
+ authPolicy: options.authPolicy,
3371
+ timeoutMs: options.timeoutMs,
3372
+ verbose: options.verbose,
3373
+ onClientAvailable: options.onClientAvailable,
3374
+ onClientClosed: options.onClientClosed,
3375
+ run: async (client, sessionId, record) => {
3376
+ await withTimeout(client.setSessionModel(sessionId, options.modelId), options.timeoutMs);
3377
+ setDesiredModelId(record, options.modelId);
3378
+ setCurrentModelId(record, options.modelId);
3379
+ }
3380
+ });
3381
+ return {
3382
+ record: result.record,
3383
+ resumed: result.resumed,
3384
+ loadError: result.loadError
3385
+ };
3386
+ }
3098
3387
  async function runSessionSetConfigOptionDirect(options) {
3099
3388
  const result = await withConnectedSession({
3100
3389
  sessionRecordId: options.sessionRecordId,
@@ -3121,7 +3410,40 @@ async function runSessionSetConfigOptionDirect(options) {
3121
3410
  }
3122
3411
  //#endregion
3123
3412
  //#region src/session-runtime/queue-owner-process.ts
3413
+ function sanitizeQueueOwnerExecArgv(execArgv = process.execArgv) {
3414
+ const sanitized = [];
3415
+ for (let index = 0; index < execArgv.length; index += 1) {
3416
+ const value = execArgv[index];
3417
+ if (value === "--experimental-test-coverage" || value === "--test") continue;
3418
+ if (value === "--test-name-pattern" || value === "--test-reporter" || value === "--test-reporter-destination") {
3419
+ index += 1;
3420
+ continue;
3421
+ }
3422
+ if (value.startsWith("--test-")) continue;
3423
+ if (value === "--inspect" || value === "--inspect-brk" || value === "--inspect-port" || value === "--inspect-publish-uid" || value.startsWith("--inspect=") || value.startsWith("--inspect-brk=") || value.startsWith("--inspect-port=") || value.startsWith("--inspect-publish-uid=") || value === "--debug-port" || value.startsWith("--debug-port=")) {
3424
+ if (value === "--inspect" || value === "--inspect-brk" || value === "--inspect-port" || value === "--inspect-publish-uid" || value === "--debug-port") index += 1;
3425
+ continue;
3426
+ }
3427
+ sanitized.push(value);
3428
+ }
3429
+ return sanitized;
3430
+ }
3431
+ function buildQueueOwnerArgOverride(entryPath, execArgv = process.execArgv) {
3432
+ const sanitized = sanitizeQueueOwnerExecArgv(execArgv);
3433
+ if (sanitized.length === 0) return null;
3434
+ return JSON.stringify([
3435
+ ...sanitized,
3436
+ entryPath,
3437
+ "__queue-owner"
3438
+ ]);
3439
+ }
3124
3440
  function resolveQueueOwnerSpawnArgs(argv = process.argv) {
3441
+ const override = process.env.ACPX_QUEUE_OWNER_ARGS;
3442
+ if (override) {
3443
+ const parsed = JSON.parse(override);
3444
+ if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((value) => typeof value === "string" && value.length > 0)) return [...parsed];
3445
+ throw new Error("acpx self-spawn failed: invalid ACPX_QUEUE_OWNER_ARGS");
3446
+ }
3125
3447
  const entry = argv[1];
3126
3448
  if (!entry || entry.trim().length === 0) throw new Error("acpx self-spawn failed: missing CLI entry path");
3127
3449
  return [realpathSync(entry), "__queue-owner"];
@@ -3137,7 +3459,8 @@ function queueOwnerRuntimeOptionsFromSend(options) {
3137
3459
  suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
3138
3460
  verbose: options.verbose,
3139
3461
  ttlMs: options.ttlMs,
3140
- maxQueueDepth: options.maxQueueDepth
3462
+ maxQueueDepth: options.maxQueueDepth,
3463
+ promptRetries: options.promptRetries
3141
3464
  };
3142
3465
  }
3143
3466
  function buildQueueOwnerSpawnOptions(payload) {
@@ -3161,6 +3484,31 @@ const DEFAULT_QUEUE_OWNER_TTL_MS = 3e5;
3161
3484
  const INTERRUPT_CANCEL_WAIT_MS = 2500;
3162
3485
  const QUEUE_OWNER_STARTUP_MAX_ATTEMPTS = 120;
3163
3486
  const QUEUE_OWNER_HEARTBEAT_INTERVAL_MS = 5e3;
3487
+ function sessionOptionsFromRecord(record) {
3488
+ const stored = record.acpx?.session_options;
3489
+ if (!stored) return;
3490
+ const sessionOptions = {};
3491
+ if (typeof stored.model === "string" && stored.model.trim().length > 0) sessionOptions.model = stored.model;
3492
+ if (Array.isArray(stored.allowed_tools)) sessionOptions.allowedTools = [...stored.allowed_tools];
3493
+ if (typeof stored.max_turns === "number") sessionOptions.maxTurns = stored.max_turns;
3494
+ return Object.keys(sessionOptions).length > 0 ? sessionOptions : void 0;
3495
+ }
3496
+ function persistSessionOptions(record, options) {
3497
+ const next = options && {
3498
+ model: typeof options.model === "string" ? options.model : void 0,
3499
+ allowed_tools: Array.isArray(options.allowedTools) ? [...options.allowedTools] : void 0,
3500
+ max_turns: typeof options.maxTurns === "number" ? options.maxTurns : void 0
3501
+ };
3502
+ if (Boolean(next && (typeof next.model === "string" && next.model.trim().length > 0 || Array.isArray(next.allowed_tools) && next.allowed_tools.length > 0 || typeof next.max_turns === "number")) && next) {
3503
+ record.acpx = {
3504
+ ...record.acpx,
3505
+ session_options: next
3506
+ };
3507
+ return;
3508
+ }
3509
+ if (!record.acpx) return;
3510
+ delete record.acpx.session_options;
3511
+ }
3164
3512
  function toPromptResult(stopReason, sessionId, client) {
3165
3513
  return {
3166
3514
  stopReason,
@@ -3168,6 +3516,13 @@ function toPromptResult(stopReason, sessionId, client) {
3168
3516
  permissionStats: client.getPermissionStats()
3169
3517
  };
3170
3518
  }
3519
+ async function applyRequestedModelIfAdvertised(params) {
3520
+ const requestedModel = typeof params.requestedModel === "string" ? params.requestedModel.trim() : "";
3521
+ if (!requestedModel || !params.models) return false;
3522
+ if (params.models.currentModelId === requestedModel) return true;
3523
+ await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel), params.timeoutMs);
3524
+ return true;
3525
+ }
3171
3526
  var QueueTaskOutputFormatter = class {
3172
3527
  requestId;
3173
3528
  send;
@@ -3255,6 +3610,10 @@ function normalizeQueueOwnerTtlMs(ttlMs) {
3255
3610
  if (!Number.isFinite(ttlMs) || ttlMs < 0) return DEFAULT_QUEUE_OWNER_TTL_MS;
3256
3611
  return Math.round(ttlMs);
3257
3612
  }
3613
+ function emitPromptRetryNotice(params) {
3614
+ if (params.suppressSdkConsoleErrors) return;
3615
+ process.stderr.write(`[acpx] prompt failed (${formatErrorMessage(params.error)}), retrying in ${params.delayMs}ms (attempt ${params.attempt}/${params.maxRetries})\n`);
3616
+ }
3258
3617
  async function runQueuedTask(sessionRecordId, task, options) {
3259
3618
  const outputFormatter = task.waitForCompletion ? new QueueTaskOutputFormatter(task) : DISCARD_OUTPUT_FORMATTER;
3260
3619
  try {
@@ -3263,6 +3622,7 @@ async function runQueuedTask(sessionRecordId, task, options) {
3263
3622
  mcpServers: options.mcpServers,
3264
3623
  prompt: task.prompt ?? textPrompt(task.message),
3265
3624
  permissionMode: task.permissionMode,
3625
+ resumePolicy: task.resumePolicy,
3266
3626
  nonInteractivePermissions: task.nonInteractivePermissions ?? options.nonInteractivePermissions,
3267
3627
  authCredentials: options.authCredentials,
3268
3628
  authPolicy: options.authPolicy,
@@ -3270,6 +3630,7 @@ async function runQueuedTask(sessionRecordId, task, options) {
3270
3630
  timeoutMs: task.timeoutMs,
3271
3631
  suppressSdkConsoleErrors: task.suppressSdkConsoleErrors ?? options.suppressSdkConsoleErrors,
3272
3632
  verbose: options.verbose,
3633
+ promptRetries: options.promptRetries,
3273
3634
  onClientAvailable: options.onClientAvailable,
3274
3635
  onClientClosed: options.onClientClosed,
3275
3636
  onPromptActive: options.onPromptActive,
@@ -3318,6 +3679,8 @@ async function runSessionPrompt(options) {
3318
3679
  const pendingMessages = [];
3319
3680
  const pendingConnectOutputMessages = [];
3320
3681
  let bufferingConnectOutput = true;
3682
+ let promptTurnActive = false;
3683
+ let promptTurnHadSideEffects = false;
3321
3684
  let sawAcpMessage = false;
3322
3685
  let eventWriterClosed = false;
3323
3686
  const closeEventWriter = async (checkpoint) => {
@@ -3342,7 +3705,8 @@ async function runSessionPrompt(options) {
3342
3705
  authCredentials: options.authCredentials,
3343
3706
  authPolicy: options.authPolicy,
3344
3707
  suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
3345
- verbose: options.verbose
3708
+ verbose: options.verbose,
3709
+ sessionOptions: sessionOptionsFromRecord(record)
3346
3710
  });
3347
3711
  client.updateRuntimeOptions({
3348
3712
  permissionMode: options.permissionMode,
@@ -3351,9 +3715,10 @@ async function runSessionPrompt(options) {
3351
3715
  verbose: options.verbose
3352
3716
  });
3353
3717
  client.setEventHandlers({
3354
- onAcpMessage: (_direction, message) => {
3718
+ onAcpMessage: (direction, message) => {
3355
3719
  sawAcpMessage = true;
3356
3720
  pendingMessages.push(message);
3721
+ options.onAcpMessage?.(direction, message);
3357
3722
  },
3358
3723
  onAcpOutputMessage: (_direction, message) => {
3359
3724
  if (bufferingConnectOutput) {
@@ -3363,12 +3728,16 @@ async function runSessionPrompt(options) {
3363
3728
  output.onAcpMessage(message);
3364
3729
  },
3365
3730
  onSessionUpdate: (notification) => {
3731
+ if (promptTurnActive) promptTurnHadSideEffects = true;
3366
3732
  acpxState = recordSessionUpdate(conversation, acpxState, notification);
3367
3733
  trimConversationForRuntime(conversation);
3734
+ options.onSessionUpdate?.(notification);
3368
3735
  },
3369
3736
  onClientOperation: (operation) => {
3737
+ if (promptTurnActive) promptTurnHadSideEffects = true;
3370
3738
  acpxState = recordClientOperation(conversation, acpxState, operation);
3371
3739
  trimConversationForRuntime(conversation);
3740
+ options.onClientOperation?.(operation);
3372
3741
  }
3373
3742
  });
3374
3743
  let activeSessionIdForControl = record.acpSessionId;
@@ -3379,6 +3748,9 @@ async function runSessionPrompt(options) {
3379
3748
  setSessionMode: async (modeId) => {
3380
3749
  await client.setSessionMode(activeSessionIdForControl, modeId);
3381
3750
  },
3751
+ setSessionModel: async (modelId) => {
3752
+ await client.setSessionModel(activeSessionIdForControl, modelId);
3753
+ },
3382
3754
  setSessionConfigOption: async (configId, value) => {
3383
3755
  return await client.setSessionConfigOption(activeSessionIdForControl, configId, value);
3384
3756
  }
@@ -3391,6 +3763,7 @@ async function runSessionPrompt(options) {
3391
3763
  return await connectAndLoadSession({
3392
3764
  client,
3393
3765
  record,
3766
+ resumePolicy: options.resumePolicy,
3394
3767
  timeoutMs: options.timeoutMs,
3395
3768
  verbose: options.verbose,
3396
3769
  activeController,
@@ -3419,11 +3792,13 @@ async function runSessionPrompt(options) {
3419
3792
  if (options.verbose) process.stderr.write(`[acpx] ${formatPerfMetric("prompt.connect_and_load", Date.now() - connectStartedAt)}\n`);
3420
3793
  output.setContext({ sessionId: record.acpxRecordId });
3421
3794
  await flushPendingMessages(false);
3795
+ const maxRetries = options.promptRetries ?? 0;
3422
3796
  let response;
3423
- try {
3797
+ promptTurnActive = true;
3798
+ for (let attempt = 0;; attempt++) try {
3424
3799
  const promptStartedAt = Date.now();
3425
3800
  const promptPromise = client.prompt(activeSessionId, options.prompt);
3426
- if (options.onPromptActive) try {
3801
+ if (attempt === 0 && options.onPromptActive) try {
3427
3802
  await options.onPromptActive();
3428
3803
  } catch (error) {
3429
3804
  if (options.verbose) process.stderr.write("[acpx] onPromptActive hook failed: " + formatErrorMessage(error) + "\n");
@@ -3432,10 +3807,26 @@ async function runSessionPrompt(options) {
3432
3807
  return await withTimeout(promptPromise, options.timeoutMs);
3433
3808
  });
3434
3809
  if (options.verbose) process.stderr.write(`[acpx] ${formatPerfMetric("prompt.agent_turn", Date.now() - promptStartedAt)}\n`);
3810
+ break;
3435
3811
  } catch (error) {
3436
3812
  const snapshot = client.getAgentLifecycleSnapshot();
3813
+ const agentCrashed = snapshot.lastExit?.unexpectedDuringPrompt === true;
3814
+ if (attempt < maxRetries && !agentCrashed && !promptTurnHadSideEffects && isRetryablePromptError(error)) {
3815
+ const delayMs = Math.min(1e3 * 2 ** attempt, 1e4);
3816
+ emitPromptRetryNotice({
3817
+ error,
3818
+ delayMs,
3819
+ attempt: attempt + 1,
3820
+ maxRetries,
3821
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors
3822
+ });
3823
+ await waitMs$1(delayMs);
3824
+ if (!promptTurnHadSideEffects) continue;
3825
+ }
3826
+ promptTurnActive = false;
3437
3827
  applyLifecycleSnapshotToRecord(record, snapshot);
3438
- if (snapshot.lastExit?.unexpectedDuringPrompt && options.verbose) process.stderr.write("[acpx] agent disconnected during prompt (" + snapshot.lastExit.reason + ", exit=" + snapshot.lastExit.exitCode + ", signal=" + (snapshot.lastExit.signal ?? "none") + ")\n");
3828
+ const lastExit = snapshot.lastExit;
3829
+ if (lastExit?.unexpectedDuringPrompt && options.verbose) process.stderr.write("[acpx] agent disconnected during prompt (" + lastExit.reason + ", exit=" + lastExit.exitCode + ", signal=" + (lastExit.signal ?? "none") + ")\n");
3439
3830
  const normalizedError = normalizeOutputError(error, { origin: "runtime" });
3440
3831
  await flushPendingMessages(false).catch(() => {});
3441
3832
  output.flush();
@@ -3447,6 +3838,7 @@ async function runSessionPrompt(options) {
3447
3838
  propagated.normalizedOutputError = normalizedError;
3448
3839
  throw propagated;
3449
3840
  }
3841
+ promptTurnActive = false;
3450
3842
  await flushPendingMessages(false);
3451
3843
  output.flush();
3452
3844
  record.lastUsedAt = isoNow();
@@ -3488,6 +3880,8 @@ async function runSessionPrompt(options) {
3488
3880
  }
3489
3881
  async function runOnce(options) {
3490
3882
  const output = options.outputFormatter;
3883
+ let promptTurnActive = false;
3884
+ let promptTurnHadSideEffects = false;
3491
3885
  const client = new AcpClient({
3492
3886
  agentCommand: options.agentCommand,
3493
3887
  cwd: absolutePath(options.cwd),
@@ -3498,7 +3892,16 @@ async function runOnce(options) {
3498
3892
  authPolicy: options.authPolicy,
3499
3893
  suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
3500
3894
  verbose: options.verbose,
3895
+ onAcpMessage: options.onAcpMessage,
3501
3896
  onAcpOutputMessage: (_direction, message) => output.onAcpMessage(message),
3897
+ onSessionUpdate: (notification) => {
3898
+ if (promptTurnActive) promptTurnHadSideEffects = true;
3899
+ options.onSessionUpdate?.(notification);
3900
+ },
3901
+ onClientOperation: (operation) => {
3902
+ if (promptTurnActive) promptTurnHadSideEffects = true;
3903
+ options.onClientOperation?.(operation);
3904
+ },
3502
3905
  sessionOptions: options.sessionOptions
3503
3906
  });
3504
3907
  try {
@@ -3506,13 +3909,43 @@ async function runOnce(options) {
3506
3909
  await measurePerf("runtime.exec.start", async () => {
3507
3910
  await withTimeout(client.start(), options.timeoutMs);
3508
3911
  });
3509
- const sessionId = (await measurePerf("runtime.exec.create_session", async () => {
3912
+ const createdSession = await measurePerf("runtime.exec.create_session", async () => {
3510
3913
  return await withTimeout(client.createSession(absolutePath(options.cwd)), options.timeoutMs);
3511
- })).sessionId;
3512
- output.setContext({ sessionId });
3513
- const response = await measurePerf("runtime.exec.prompt", async () => {
3514
- return await withTimeout(client.prompt(sessionId, options.prompt), options.timeoutMs);
3515
3914
  });
3915
+ const sessionId = createdSession.sessionId;
3916
+ await applyRequestedModelIfAdvertised({
3917
+ client,
3918
+ sessionId,
3919
+ requestedModel: options.sessionOptions?.model,
3920
+ models: createdSession.models,
3921
+ timeoutMs: options.timeoutMs
3922
+ });
3923
+ output.setContext({ sessionId });
3924
+ const maxRetries = options.promptRetries ?? 0;
3925
+ let response;
3926
+ promptTurnActive = true;
3927
+ for (let attempt = 0;; attempt++) try {
3928
+ response = await measurePerf("runtime.exec.prompt", async () => {
3929
+ return await withTimeout(client.prompt(sessionId, options.prompt), options.timeoutMs);
3930
+ });
3931
+ break;
3932
+ } catch (error) {
3933
+ if (attempt < maxRetries && !promptTurnHadSideEffects && isRetryablePromptError(error)) {
3934
+ const delayMs = Math.min(1e3 * 2 ** attempt, 1e4);
3935
+ emitPromptRetryNotice({
3936
+ error,
3937
+ delayMs,
3938
+ attempt: attempt + 1,
3939
+ maxRetries,
3940
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors
3941
+ });
3942
+ await waitMs$1(delayMs);
3943
+ if (!promptTurnHadSideEffects) continue;
3944
+ }
3945
+ promptTurnActive = false;
3946
+ throw error;
3947
+ }
3948
+ promptTurnActive = false;
3516
3949
  output.flush();
3517
3950
  return toPromptResult(response.stopReason, sessionId, client);
3518
3951
  }, async () => {
@@ -3523,7 +3956,78 @@ async function runOnce(options) {
3523
3956
  await client.close();
3524
3957
  }
3525
3958
  }
3526
- async function createSession(options) {
3959
+ async function createSessionRecordWithClient(client, options) {
3960
+ const cwd = absolutePath(options.cwd);
3961
+ await measurePerf("runtime.session_create.start", async () => {
3962
+ await withTimeout(client.start(), options.timeoutMs);
3963
+ });
3964
+ let sessionId;
3965
+ let agentSessionId;
3966
+ let sessionModels;
3967
+ let requestedModelApplied = false;
3968
+ if (options.resumeSessionId) {
3969
+ if (!client.supportsLoadSession()) throw new Error(`Agent command "${options.agentCommand}" does not support session/load; cannot resume session ${options.resumeSessionId}`);
3970
+ try {
3971
+ const loadedSession = await withTimeout(client.loadSession(options.resumeSessionId, cwd), options.timeoutMs);
3972
+ sessionId = options.resumeSessionId;
3973
+ agentSessionId = normalizeRuntimeSessionId(loadedSession.agentSessionId);
3974
+ sessionModels = loadedSession.models;
3975
+ requestedModelApplied = await applyRequestedModelIfAdvertised({
3976
+ client,
3977
+ sessionId,
3978
+ requestedModel: options.sessionOptions?.model,
3979
+ models: loadedSession.models,
3980
+ timeoutMs: options.timeoutMs
3981
+ });
3982
+ } catch (error) {
3983
+ throw new Error(`Failed to resume ACP session ${options.resumeSessionId}: ${formatErrorMessage(error)}`, { cause: error });
3984
+ }
3985
+ } else {
3986
+ const createdSession = await measurePerf("runtime.session_create.create_session", async () => {
3987
+ return await withTimeout(client.createSession(cwd), options.timeoutMs);
3988
+ });
3989
+ sessionId = createdSession.sessionId;
3990
+ agentSessionId = normalizeRuntimeSessionId(createdSession.agentSessionId);
3991
+ sessionModels = createdSession.models;
3992
+ requestedModelApplied = await applyRequestedModelIfAdvertised({
3993
+ client,
3994
+ sessionId,
3995
+ requestedModel: options.sessionOptions?.model,
3996
+ models: createdSession.models,
3997
+ timeoutMs: options.timeoutMs
3998
+ });
3999
+ }
4000
+ const lifecycle = client.getAgentLifecycleSnapshot();
4001
+ const now = isoNow();
4002
+ const record = {
4003
+ schema: SESSION_RECORD_SCHEMA,
4004
+ acpxRecordId: sessionId,
4005
+ acpSessionId: sessionId,
4006
+ agentSessionId,
4007
+ agentCommand: options.agentCommand,
4008
+ cwd,
4009
+ name: normalizeName(options.name),
4010
+ createdAt: now,
4011
+ lastUsedAt: now,
4012
+ lastSeq: 0,
4013
+ lastRequestId: void 0,
4014
+ eventLog: defaultSessionEventLog(sessionId),
4015
+ closed: false,
4016
+ closedAt: void 0,
4017
+ pid: lifecycle.pid,
4018
+ agentStartedAt: lifecycle.startedAt,
4019
+ protocolVersion: client.initializeResult?.protocolVersion,
4020
+ agentCapabilities: client.initializeResult?.agentCapabilities,
4021
+ ...createSessionConversation(now),
4022
+ acpx: {}
4023
+ };
4024
+ persistSessionOptions(record, options.sessionOptions);
4025
+ syncAdvertisedModelState(record, sessionModels);
4026
+ if (requestedModelApplied) setCurrentModelId(record, options.sessionOptions?.model);
4027
+ await writeSessionRecord(record);
4028
+ return record;
4029
+ }
4030
+ async function createSessionWithClient(options) {
3527
4031
  const client = new AcpClient({
3528
4032
  agentCommand: options.agentCommand,
3529
4033
  cwd: absolutePath(options.cwd),
@@ -3536,56 +4040,23 @@ async function createSession(options) {
3536
4040
  sessionOptions: options.sessionOptions
3537
4041
  });
3538
4042
  try {
3539
- return await withInterrupt(async () => {
3540
- const cwd = absolutePath(options.cwd);
3541
- await measurePerf("runtime.session_create.start", async () => {
3542
- await withTimeout(client.start(), options.timeoutMs);
3543
- });
3544
- let sessionId;
3545
- let agentSessionId;
3546
- if (options.resumeSessionId) {
3547
- if (!client.supportsLoadSession()) throw new Error(`Agent command "${options.agentCommand}" does not support session/load; cannot resume session ${options.resumeSessionId}`);
3548
- try {
3549
- const loadedSession = await withTimeout(client.loadSession(options.resumeSessionId, cwd), options.timeoutMs);
3550
- sessionId = options.resumeSessionId;
3551
- agentSessionId = normalizeRuntimeSessionId(loadedSession.agentSessionId);
3552
- } catch (error) {
3553
- throw new Error(`Failed to resume ACP session ${options.resumeSessionId}: ${formatErrorMessage(error)}`, { cause: error });
3554
- }
3555
- } else {
3556
- const createdSession = await measurePerf("runtime.session_create.create_session", async () => await withTimeout(client.createSession(cwd), options.timeoutMs));
3557
- sessionId = createdSession.sessionId;
3558
- agentSessionId = normalizeRuntimeSessionId(createdSession.agentSessionId);
3559
- }
3560
- const lifecycle = client.getAgentLifecycleSnapshot();
3561
- const now = isoNow();
3562
- const record = {
3563
- schema: SESSION_RECORD_SCHEMA,
3564
- acpxRecordId: sessionId,
3565
- acpSessionId: sessionId,
3566
- agentSessionId,
3567
- agentCommand: options.agentCommand,
3568
- cwd,
3569
- name: normalizeName(options.name),
3570
- createdAt: now,
3571
- lastUsedAt: now,
3572
- lastSeq: 0,
3573
- lastRequestId: void 0,
3574
- eventLog: defaultSessionEventLog(sessionId),
3575
- closed: false,
3576
- closedAt: void 0,
3577
- pid: lifecycle.pid,
3578
- agentStartedAt: lifecycle.startedAt,
3579
- protocolVersion: client.initializeResult?.protocolVersion,
3580
- agentCapabilities: client.initializeResult?.agentCapabilities,
3581
- ...createSessionConversation(now),
3582
- acpx: {}
3583
- };
3584
- await writeSessionRecord(record);
3585
- return record;
3586
- }, async () => {
3587
- await client.close();
3588
- });
4043
+ return {
4044
+ record: await withInterrupt(async () => {
4045
+ return await createSessionRecordWithClient(client, options);
4046
+ }, async () => {
4047
+ await client.close();
4048
+ }),
4049
+ client
4050
+ };
4051
+ } catch (error) {
4052
+ await client.close();
4053
+ throw error;
4054
+ }
4055
+ }
4056
+ async function createSession(options) {
4057
+ const { record, client } = await createSessionWithClient(options);
4058
+ try {
4059
+ return record;
3589
4060
  } finally {
3590
4061
  await client.close();
3591
4062
  }
@@ -3600,10 +4071,26 @@ async function ensureSession(options) {
3600
4071
  name: options.name,
3601
4072
  boundary: walkBoundary
3602
4073
  });
3603
- if (existing) return {
3604
- record: existing,
3605
- created: false
3606
- };
4074
+ if (existing) {
4075
+ const requestedModel = options.sessionOptions?.model;
4076
+ if (requestedModel) return {
4077
+ record: (await setSessionModel({
4078
+ sessionId: existing.acpxRecordId,
4079
+ modelId: requestedModel,
4080
+ mcpServers: options.mcpServers,
4081
+ nonInteractivePermissions: options.nonInteractivePermissions,
4082
+ authCredentials: options.authCredentials,
4083
+ authPolicy: options.authPolicy,
4084
+ timeoutMs: options.timeoutMs,
4085
+ verbose: options.verbose
4086
+ })).record,
4087
+ created: false
4088
+ };
4089
+ return {
4090
+ record: existing,
4091
+ created: false
4092
+ };
4093
+ }
3607
4094
  return {
3608
4095
  record: await createSession({
3609
4096
  agentCommand: options.agentCommand,
@@ -3647,12 +4134,13 @@ async function runSessionQueueOwner(options) {
3647
4134
  agentCommand: sessionRecord.agentCommand,
3648
4135
  cwd: absolutePath(sessionRecord.cwd),
3649
4136
  mcpServers: options.mcpServers,
3650
- permissionMode: "approve-reads",
4137
+ permissionMode: options.permissionMode,
3651
4138
  nonInteractivePermissions: options.nonInteractivePermissions,
3652
4139
  authCredentials: options.authCredentials,
3653
4140
  authPolicy: options.authPolicy,
3654
4141
  suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
3655
- verbose: options.verbose
4142
+ verbose: options.verbose,
4143
+ sessionOptions: sessionOptionsFromRecord(sessionRecord)
3656
4144
  });
3657
4145
  const ttlMs = normalizeQueueOwnerTtlMs(options.ttlMs);
3658
4146
  const maxQueueDepth = Math.max(1, Math.round(options.maxQueueDepth ?? 16));
@@ -3672,6 +4160,18 @@ async function runSessionQueueOwner(options) {
3672
4160
  verbose: options.verbose
3673
4161
  });
3674
4162
  },
4163
+ setSessionModelFallback: async (modelId, timeoutMs) => {
4164
+ await runSessionSetModelDirect({
4165
+ sessionRecordId: options.sessionId,
4166
+ modelId,
4167
+ mcpServers: options.mcpServers,
4168
+ nonInteractivePermissions: options.nonInteractivePermissions,
4169
+ authCredentials: options.authCredentials,
4170
+ authPolicy: options.authPolicy,
4171
+ timeoutMs,
4172
+ verbose: options.verbose
4173
+ });
4174
+ },
3675
4175
  setSessionConfigOptionFallback: async (configId, value, timeoutMs) => {
3676
4176
  return (await runSessionSetConfigOptionDirect({
3677
4177
  sessionRecordId: options.sessionId,
@@ -3719,6 +4219,9 @@ async function runSessionQueueOwner(options) {
3719
4219
  setSessionMode: async (modeId, timeoutMs) => {
3720
4220
  await turnController.setSessionMode(modeId, timeoutMs);
3721
4221
  },
4222
+ setSessionModel: async (modelId, timeoutMs) => {
4223
+ await turnController.setSessionModel(modelId, timeoutMs);
4224
+ },
3722
4225
  setSessionConfigOption: async (configId, value, timeoutMs) => {
3723
4226
  return await turnController.setSessionConfigOption(configId, value, timeoutMs);
3724
4227
  }
@@ -3750,6 +4253,7 @@ async function runSessionQueueOwner(options) {
3750
4253
  authCredentials: options.authCredentials,
3751
4254
  authPolicy: options.authPolicy,
3752
4255
  suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
4256
+ promptRetries: options.promptRetries,
3753
4257
  onClientAvailable: setActiveController,
3754
4258
  onClientClosed: clearActiveController,
3755
4259
  onPromptActive: async () => {
@@ -3788,6 +4292,26 @@ async function sendSession(options) {
3788
4292
  }
3789
4293
  throw new Error(`Session queue owner failed to start for session ${options.sessionId}`);
3790
4294
  }
4295
+ async function sendSessionDirect(options) {
4296
+ return await runSessionPrompt({
4297
+ sessionRecordId: options.sessionId,
4298
+ prompt: options.prompt,
4299
+ mcpServers: options.mcpServers,
4300
+ permissionMode: options.permissionMode,
4301
+ resumePolicy: options.resumePolicy,
4302
+ nonInteractivePermissions: options.nonInteractivePermissions,
4303
+ authCredentials: options.authCredentials,
4304
+ authPolicy: options.authPolicy,
4305
+ outputFormatter: options.outputFormatter,
4306
+ onAcpMessage: options.onAcpMessage,
4307
+ onSessionUpdate: options.onSessionUpdate,
4308
+ onClientOperation: options.onClientOperation,
4309
+ timeoutMs: options.timeoutMs,
4310
+ suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
4311
+ verbose: options.verbose,
4312
+ client: options.client
4313
+ });
4314
+ }
3791
4315
  async function cancelSessionPrompt(options) {
3792
4316
  const cancelled = await tryCancelOnRunningOwner(options);
3793
4317
  return {
@@ -3816,6 +4340,28 @@ async function setSessionMode(options) {
3816
4340
  verbose: options.verbose
3817
4341
  });
3818
4342
  }
4343
+ async function setSessionModel(options) {
4344
+ if (await trySetModelOnRunningOwner(options.sessionId, options.modelId, options.timeoutMs, options.verbose)) {
4345
+ const record = await resolveSessionRecord(options.sessionId);
4346
+ setDesiredModelId(record, options.modelId);
4347
+ setCurrentModelId(record, options.modelId);
4348
+ await writeSessionRecord(record);
4349
+ return {
4350
+ record,
4351
+ resumed: false
4352
+ };
4353
+ }
4354
+ return await runSessionSetModelDirect({
4355
+ sessionRecordId: options.sessionId,
4356
+ modelId: options.modelId,
4357
+ mcpServers: options.mcpServers,
4358
+ nonInteractivePermissions: options.nonInteractivePermissions,
4359
+ authCredentials: options.authCredentials,
4360
+ authPolicy: options.authPolicy,
4361
+ timeoutMs: options.timeoutMs,
4362
+ verbose: options.verbose
4363
+ });
4364
+ }
3819
4365
  async function setSessionConfigOption(options) {
3820
4366
  const ownerResponse = await trySetConfigOptionOnRunningOwner(options.sessionId, options.configId, options.value, options.timeoutMs, options.verbose);
3821
4367
  if (ownerResponse) {
@@ -3882,6 +4428,7 @@ var session_exports = /* @__PURE__ */ __exportAll({
3882
4428
  cancelSessionPrompt: () => cancelSessionPrompt,
3883
4429
  closeSession: () => closeSession,
3884
4430
  createSession: () => createSession,
4431
+ createSessionWithClient: () => createSessionWithClient,
3885
4432
  ensureSession: () => ensureSession,
3886
4433
  findGitRepositoryRoot: () => findGitRepositoryRoot,
3887
4434
  findSession: () => findSession,
@@ -3893,10 +4440,12 @@ var session_exports = /* @__PURE__ */ __exportAll({
3893
4440
  runOnce: () => runOnce,
3894
4441
  runSessionQueueOwner: () => runSessionQueueOwner,
3895
4442
  sendSession: () => sendSession,
4443
+ sendSessionDirect: () => sendSessionDirect,
3896
4444
  setSessionConfigOption: () => setSessionConfigOption,
3897
- setSessionMode: () => setSessionMode
4445
+ setSessionMode: () => setSessionMode,
4446
+ setSessionModel: () => setSessionModel
3898
4447
  });
3899
4448
  //#endregion
3900
- export { findGitRepositoryRoot as a, flushPerfMetricsCapture as c, DEFAULT_HISTORY_LIMIT as i, installPerfMetricsCapture as l, DEFAULT_QUEUE_OWNER_TTL_MS as n, findSession as o, runSessionQueueOwner as r, findSessionByDirectoryWalk as s, session_exports as t, InterruptedError as u };
4449
+ export { TimeoutError as C, permissionModeSatisfies as E, InterruptedError as S, withTimeout as T, recordClientOperation as _, runOnce as a, flushPerfMetricsCapture as b, buildQueueOwnerArgOverride as c, findSession as d, findSessionByDirectoryWalk as f, createSessionConversation as g, cloneSessionAcpxState as h, createSessionWithClient as i, DEFAULT_HISTORY_LIMIT as l, defaultSessionEventLog as m, DEFAULT_QUEUE_OWNER_TTL_MS as n, runSessionQueueOwner as o, resolveSessionRecord as p, cancelSessionPrompt as r, sendSessionDirect as s, session_exports as t, findGitRepositoryRoot as u, recordPromptSubmission as v, withInterrupt as w, installPerfMetricsCapture as x, recordSessionUpdate as y };
3901
4450
 
3902
- //# sourceMappingURL=session-C2Q8ktsN.js.map
4451
+ //# sourceMappingURL=session-RO_LZUnv.js.map