acpx 0.19.2 → 0.19.4

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 (43) hide show
  1. package/dist/{agent-registry-DWlx5BQ4.js → agent-registry-kHdpsK9F.js} +35 -21
  2. package/dist/agent-registry-kHdpsK9F.js.map +1 -0
  3. package/dist/agent-registry.d.ts.map +1 -1
  4. package/dist/agent-registry.js +1 -1
  5. package/dist/{cli-D-s_eTUy.js → cli-DCtHeMEi.js} +34 -46
  6. package/dist/cli-DCtHeMEi.js.map +1 -0
  7. package/dist/cli.d.ts +1 -1
  8. package/dist/cli.js +57 -40
  9. package/dist/cli.js.map +1 -1
  10. package/dist/{client-BchvQRpz.d.ts → client-DqCXdF0Q.d.ts} +6 -18
  11. package/dist/client-DqCXdF0Q.d.ts.map +1 -0
  12. package/dist/{flows-DdxEaeZo.js → flows--1zpaEh9.js} +38 -14
  13. package/dist/flows--1zpaEh9.js.map +1 -0
  14. package/dist/flows.d.ts +2 -2
  15. package/dist/flows.js +1 -1
  16. package/dist/{invocation-options-HjxSBsK0.js → invocation-options-CvkenzCc.js} +6 -12
  17. package/dist/invocation-options-CvkenzCc.js.map +1 -0
  18. package/dist/{ipc-D0ZMQ0q9.js → ipc-D7zT86b9.js} +430 -307
  19. package/dist/ipc-D7zT86b9.js.map +1 -0
  20. package/dist/{output-GQnhkJMU.js → output-lylSoEBd.js} +12 -4
  21. package/dist/output-lylSoEBd.js.map +1 -0
  22. package/dist/{queue-owner-runtime-W0lly6ap.js → queue-owner-runtime-C0wIYowh.js} +766 -547
  23. package/dist/queue-owner-runtime-C0wIYowh.js.map +1 -0
  24. package/dist/runtime.d.ts +23 -3
  25. package/dist/runtime.d.ts.map +1 -1
  26. package/dist/runtime.js +52 -22
  27. package/dist/runtime.js.map +1 -1
  28. package/dist/{session-options-Dp6mzNSy.d.ts → session-options-X7LhX9BF.d.ts} +2 -1
  29. package/dist/{session-options-Dp6mzNSy.d.ts.map → session-options-X7LhX9BF.d.ts.map} +1 -1
  30. package/dist/{session-watch-_8yxQ6ba.js → session-watch-DPOwDRf8.js} +4 -4
  31. package/dist/{session-watch-_8yxQ6ba.js.map → session-watch-DPOwDRf8.js.map} +1 -1
  32. package/dist/{watch-C0mwpJcN.js → watch-H7rNOJtv.js} +2 -2
  33. package/dist/{watch-C0mwpJcN.js.map → watch-H7rNOJtv.js.map} +1 -1
  34. package/package.json +17 -16
  35. package/skills/acpx/SKILL.md +13 -3
  36. package/dist/agent-registry-DWlx5BQ4.js.map +0 -1
  37. package/dist/cli-D-s_eTUy.js.map +0 -1
  38. package/dist/client-BchvQRpz.d.ts.map +0 -1
  39. package/dist/flows-DdxEaeZo.js.map +0 -1
  40. package/dist/invocation-options-HjxSBsK0.js.map +0 -1
  41. package/dist/ipc-D0ZMQ0q9.js.map +0 -1
  42. package/dist/output-GQnhkJMU.js.map +0 -1
  43. package/dist/queue-owner-runtime-W0lly6ap.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { $ as resetPerfMetrics, Bt as PermissionDeniedError, Ct as extractAcpError, E as textPrompt, F as normalizeName, Ft as AgentStartupError, G as SESSION_JOURNAL_SCHEMA, Gt as SessionModelReplayError, Ht as QueueConnectionError, It as AuthPolicyError, Jt as UnsupportedPromptContentError, K as SessionJournalReader, Lt as ClaudeAcpSessionCreateTimeoutError, M as isoNow$1, Mt as AcpxOperationalError, Nt as AgentDisconnectedError, O as absolutePath, Pt as AgentSpawnError, Q as measurePerf, R as resolveSessionRecord, Rt as CopilotAcpUnsupportedError, S as getUnsupportedPromptContentMessage, St as normalizeOutputError, T as promptToDisplayText, U as extractAgentSessionId, Ut as SessionConfigOptionReplayError, Vt as PermissionPromptUnavailableError, W as normalizeAgentSessionId, Wt as SessionModeReplayError, X as getPerfMetricsSnapshot, Y as formatPerfMetric, Z as incrementPerfCounter, _ as createLockOwner, _t as isProcessAlive, a as trySetModelOnRunningOwner, at as isSessionUpdateNotification, bt as isAcpQueryClosedBeforeResponseError, d as releaseQueueOwnerLease, dt as sessionBaseDir, et as setPerfGauge, f as terminateProcess, ft as sessionEventActivePath, g as QueueLeaseGuardSettlementError, h as waitMs$1, i as trySetModeOnRunningOwner, it as isAcpMessageObject, j as findSessionByDirectoryWalk, k as findGitRepositoryRoot, m as tryAcquireQueueOwnerLease, mt as sessionEventSegmentPath, n as tryCloseSessionOnRunningOwner, o as trySubmitToRunningOwner, p as terminateQueueOwnerForSession, pt as sessionEventLockPath, qt as SessionResumeRequiredError, r as trySetConfigOptionOnRunningOwner, rt as isAcpJsonRpcMessage, s as SessionQueueOwner, t as tryCancelOnRunningOwner, tt as startPerfTimer, u as refreshQueueOwnerLease, ut as defaultSessionEventLog, v as lockOwnerPid, wt as isAcpResourceNotFoundError, xt as isRetryablePromptError, y as ProcessDescendants, yt as formatErrorMessage, z as writeSessionRecord, zt as GeminiAcpStartupTimeoutError } from "./ipc-D0ZMQ0q9.js";
2
- import { A as requireAgentStdio, C as PROCESS_HELPER_TIMEOUT_MS, D as isoNow$2, E as isChildProcessRunning, F as waitForChildExit, I as waitForSpawn, M as resolveAgentSessionCwd, N as runTimedExecFile, P as splitCommandLine, T as basenameToken, _ as buildSpawnCommandOptions, b as readWindowsEnvValue, f as resolveBuiltInAgentLaunch, g as buildAgentSpawnCommand, j as resolveAgentCommandParts, v as buildTerminalShellSpawnCommand, w as asAbsoluteCwd, x as resolveWindowsExecutablePath, y as buildTerminalSpawnCommand } from "./agent-registry-DWlx5BQ4.js";
1
+ import { $ as resetPerfMetrics, Bt as GeminiAcpStartupTimeoutError, C as textPrompt, Ct as normalizeOutputError, F as normalizeName, Ft as AgentSpawnError, G as SESSION_JOURNAL_SCHEMA, Gt as SessionModeReplayError, Ht as PermissionPromptUnavailableError, It as AgentStartupError, Jt as SessionResumeRequiredError, K as SessionJournalReader, Kt as SessionModelReplayError, L as readSessionRecord, Lt as AuthPolicyError, M as isoNow$1, Nt as AcpxOperationalError, O as absolutePath, Pt as AgentDisconnectedError, Q as measurePerf, R as resolveSessionRecord, Rt as ClaudeAcpSessionCreateTimeoutError, S as promptToDisplayText, St as isRetryablePromptError, T as lockOwnerPid, Tt as isAcpResourceNotFoundError, U as extractAgentSessionId, Ut as QueueConnectionError, Vt as PermissionDeniedError, W as normalizeAgentSessionId, Wt as SessionConfigOptionReplayError, X as getPerfMetricsSnapshot, Y as formatPerfMetric, Yt as UnsupportedPromptContentError, Z as incrementPerfCounter, _ as ProcessDescendants, a as trySetModelOnRunningOwner, at as isSessionUpdateNotification, bt as formatErrorMessage, d as releaseQueueOwnerLease, dt as sessionBaseDir, et as setPerfGauge, f as terminateProcess, ft as sessionEventActivePath, g as QueueLeaseGuardSettlementError, h as waitMs$1, i as trySetModeOnRunningOwner, it as isAcpMessageObject, j as findSessionByDirectoryWalk, k as findGitRepositoryRoot, m as tryAcquireQueueOwnerLease, mt as sessionEventSegmentPath, n as tryCloseSessionOnRunningOwner, o as trySubmitToRunningOwner, p as terminateQueueOwnerForSession, pt as sessionEventLockPath, r as trySetConfigOptionOnRunningOwner, rt as isAcpJsonRpcMessage, s as SessionQueueOwner, t as tryCancelOnRunningOwner, tt as startPerfTimer, u as refreshQueueOwnerLease, ut as defaultSessionEventLog, vt as isProcessAlive, w as createLockOwner, wt as extractAcpError, xt as isAcpQueryClosedBeforeResponseError, y as getUnsupportedPromptContentMessage, z as writeSessionRecord, zt as CopilotAcpUnsupportedError } from "./ipc-D7zT86b9.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-kHdpsK9F.js";
3
3
  import fs, { readFileSync, realpathSync } from "node:fs";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import path from "node:path";
@@ -8,14 +8,14 @@ import fs$1 from "node:fs/promises";
8
8
  import { appendRegularFile, appendRegularFileSync, withTempFile } from "@openclaw/fs-safe/advanced";
9
9
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
10
10
  import { createHash, randomUUID } from "node:crypto";
11
+ import { isHardlinkFallbackError } from "@openclaw/fs-safe/durability";
12
+ import { acquireFileLock } from "@openclaw/fs-safe/file-lock";
11
13
  import { Readable, Writable } from "node:stream";
12
14
  import { PROTOCOL_VERSION, RequestError, client, methods } from "@agentclientprotocol/sdk";
13
15
  import { FsSafeError } from "@openclaw/fs-safe/errors";
14
16
  import { isPathInside } from "@openclaw/fs-safe/path";
15
17
  import { root } from "@openclaw/fs-safe/root";
16
18
  import readline from "node:readline/promises";
17
- import { isHardlinkFallbackError } from "@openclaw/fs-safe/durability";
18
- import { acquireFileLock } from "@openclaw/fs-safe/file-lock";
19
19
  //#region src/async-control.ts
20
20
  var TimeoutError = class extends Error {
21
21
  constructor(timeoutMs) {
@@ -346,6 +346,246 @@ async function listSessionEvents(sessionId, maxSegments) {
346
346
  }).readAcpMessages();
347
347
  }
348
348
  //#endregion
349
+ //#region src/session/turn-ownership.ts
350
+ const LOCK_RETRY_MS = 15;
351
+ const INCOMPLETE_RESERVATION_GRACE_MS = 15e3;
352
+ let lastCreatedAt = 0;
353
+ const localTurns = /* @__PURE__ */ new Map();
354
+ async function readLock(filePath) {
355
+ try {
356
+ const stat = await fs$1.lstat(filePath, { bigint: true });
357
+ if (!stat.isFile()) throw new Error(`Session turn lock is not a regular file: ${filePath}`);
358
+ return {
359
+ stat,
360
+ payload: await fs$1.readFile(filePath, "utf8")
361
+ };
362
+ } catch (error) {
363
+ if (error.code === "ENOENT") return;
364
+ throw error;
365
+ }
366
+ }
367
+ function parseLock(payload) {
368
+ try {
369
+ return JSON.parse(payload);
370
+ } catch {
371
+ return;
372
+ }
373
+ }
374
+ async function tryAcquireGuard(filePath, owner, signal) {
375
+ try {
376
+ return await acquireFileLock(filePath, {
377
+ managerKey: "acpx.session-turn",
378
+ lockPath: `${filePath}.guard`,
379
+ staleMs: Infinity,
380
+ timeoutMs: LOCK_RETRY_MS,
381
+ retry: {
382
+ retries: 8,
383
+ minTimeout: 1,
384
+ maxTimeout: 2,
385
+ factor: 1,
386
+ randomize: false
387
+ },
388
+ staleRecovery: "remove-if-unchanged",
389
+ payload: () => owner.payload,
390
+ shouldReclaim: ({ payload }) => owner.hasExited(payload, false, signal),
391
+ shouldRemoveStaleLock: ({ payload }) => owner.hasExited(payload, true, signal)
392
+ });
393
+ } catch (error) {
394
+ if (error.code === "file_lock_timeout") return;
395
+ throw error;
396
+ }
397
+ }
398
+ async function removeObservedLock(filePath, observed) {
399
+ const current = await readLock(filePath);
400
+ if (!current) return true;
401
+ if (current.payload !== observed.payload || current.stat.mtimeNs !== observed.stat.mtimeNs || current.stat.size !== observed.stat.size) return false;
402
+ await fs$1.unlink(filePath).catch((error) => {
403
+ if (error.code !== "ENOENT") throw error;
404
+ });
405
+ return true;
406
+ }
407
+ async function recoverAbandonedLock(filePath, owner, signal) {
408
+ const observed = await readLock(filePath);
409
+ if (!observed) return true;
410
+ const payload = parseLock(observed.payload);
411
+ const pid = lockOwnerPid(payload);
412
+ if (pid && (!await owner.hasExited(payload, false, signal) || !await owner.hasExited(payload, true, signal))) return false;
413
+ if (!pid && Date.now() - Number(observed.stat.mtimeMs) <= INCOMPLETE_RESERVATION_GRACE_MS) return false;
414
+ if (await removeObservedLock(filePath, observed)) {
415
+ incrementPerfCounter("session.events.stale_lock_recovered");
416
+ return true;
417
+ }
418
+ return false;
419
+ }
420
+ async function publishLock(filePath, payload, signal) {
421
+ await withTempFile({
422
+ rootDir: path.dirname(filePath),
423
+ prefix: "session-turn",
424
+ fileName: "lock"
425
+ }, async (temporaryPath) => {
426
+ await fs$1.writeFile(temporaryPath, payload, {
427
+ encoding: "utf8",
428
+ flag: "wx",
429
+ mode: 384
430
+ });
431
+ signal?.throwIfAborted();
432
+ try {
433
+ await fs$1.link(temporaryPath, filePath);
434
+ } catch (error) {
435
+ if (!isHardlinkFallbackError(error)) throw error;
436
+ await fs$1.writeFile(filePath, payload, {
437
+ encoding: "utf8",
438
+ flag: "wx",
439
+ mode: 384
440
+ });
441
+ }
442
+ });
443
+ }
444
+ async function removePublishedLock(filePath, payload) {
445
+ const current = await readLock(filePath);
446
+ if (current?.payload === payload) await removeObservedLock(filePath, current);
447
+ }
448
+ async function rejectAcquisition(filePath, receipt, error) {
449
+ const failures = [error];
450
+ await disposeTurn(filePath, receipt).catch((cleanupError) => {
451
+ failures.push(...cleanupError instanceof AggregateError ? cleanupError.errors : [cleanupError]);
452
+ });
453
+ if (failures.length > 1) throw new AggregateError(failures, "Session turn acquisition and cleanup failed");
454
+ throw error;
455
+ }
456
+ async function releaseTurnGuard(receipt) {
457
+ await receipt.guard?.release();
458
+ receipt.guard = void 0;
459
+ }
460
+ async function removeTurnMarker(filePath, receipt) {
461
+ if (!receipt.removeMarker) return true;
462
+ if (receipt.guard && !await receipt.guard.verifyStillHeld()) await releaseTurnGuard(receipt);
463
+ receipt.guard ??= await tryAcquireGuard(filePath, receipt.owner);
464
+ if (!receipt.guard) return false;
465
+ await receipt.removeMarker();
466
+ receipt.removeMarker = void 0;
467
+ return true;
468
+ }
469
+ async function settleTurn(filePath, receipt) {
470
+ if (receipt.settled) return true;
471
+ const pending = receipt.pending ??= (async () => {
472
+ receipt.failed = false;
473
+ const failures = [];
474
+ try {
475
+ if (!await removeTurnMarker(filePath, receipt)) return false;
476
+ } catch (error) {
477
+ failures.push(error);
478
+ }
479
+ if (failures.length === 0 || !receipt.admitted) try {
480
+ await releaseTurnGuard(receipt);
481
+ } catch (error) {
482
+ failures.push(error);
483
+ }
484
+ if (failures.length > 1) throw new AggregateError(failures, "Session turn cleanup failed");
485
+ if (failures.length === 1) throw failures[0];
486
+ receipt.settled = true;
487
+ localTurns.delete(filePath);
488
+ return true;
489
+ })();
490
+ try {
491
+ return await pending;
492
+ } finally {
493
+ if (receipt.pending === pending) {
494
+ receipt.pending = void 0;
495
+ receipt.failed = !receipt.settled;
496
+ }
497
+ }
498
+ }
499
+ async function disposeTurn(filePath, receipt) {
500
+ if (!await settleTurn(filePath, receipt)) throw new Error(`Session turn cleanup is waiting for its guard: ${filePath}`);
501
+ }
502
+ async function tryPublishLock(filePath, payload, owner, signal) {
503
+ for (;;) try {
504
+ await publishLock(filePath, payload, signal);
505
+ return true;
506
+ } catch (error) {
507
+ if (error.code !== "EEXIST") throw error;
508
+ if (!await recoverAbandonedLock(filePath, owner, signal)) return false;
509
+ }
510
+ }
511
+ async function tryAcquireTurnCleanup(filePath, payload, owner, signal) {
512
+ const previous = localTurns.get(filePath);
513
+ if (previous) {
514
+ if (previous.failed) await settleTurn(filePath, previous);
515
+ return;
516
+ }
517
+ const receipt = {
518
+ owner,
519
+ removeMarker: async () => await removePublishedLock(filePath, payload),
520
+ admitted: false,
521
+ failed: false,
522
+ settled: false
523
+ };
524
+ localTurns.set(filePath, receipt);
525
+ try {
526
+ receipt.guard = await tryAcquireGuard(filePath, owner, signal);
527
+ } catch (error) {
528
+ localTurns.delete(filePath);
529
+ throw error;
530
+ }
531
+ if (!receipt.guard) {
532
+ localTurns.delete(filePath);
533
+ return;
534
+ }
535
+ return receipt;
536
+ }
537
+ async function tryAcquireTurn(filePath, payload, owner, signal) {
538
+ const receipt = await tryAcquireTurnCleanup(filePath, payload, owner, signal);
539
+ if (!receipt) return;
540
+ try {
541
+ signal?.throwIfAborted();
542
+ if (!await tryPublishLock(filePath, payload, owner, signal)) {
543
+ await disposeTurn(filePath, receipt);
544
+ return;
545
+ }
546
+ const observed = await readLock(filePath);
547
+ if (observed?.payload !== payload) throw new Error(`Session turn ownership changed before admission: ${filePath}`);
548
+ signal?.throwIfAborted();
549
+ receipt.admitted = true;
550
+ receipt.removeMarker = async () => await removeObservedLock(filePath, observed);
551
+ return { [Symbol.asyncDispose]: async () => await disposeTurn(filePath, receipt) };
552
+ } catch (error) {
553
+ return await rejectAcquisition(filePath, receipt, error);
554
+ }
555
+ }
556
+ async function acquireSessionTurn(recordId, signal) {
557
+ return await acquireSessionOwnership(sessionEventLockPath(recordId), signal);
558
+ }
559
+ async function acquireSessionScope(scope, signal) {
560
+ return await acquireSessionTurn(`ensure:${createHash("sha256").update(JSON.stringify([
561
+ scope.agentCommand,
562
+ scope.cwd,
563
+ scope.name
564
+ ])).digest("hex")}`, signal);
565
+ }
566
+ async function acquireSessionImport(signal) {
567
+ return await acquireSessionOwnership(path.join(sessionBaseDir(), ".import-admission.lock"), signal);
568
+ }
569
+ async function acquireSessionOwnership(requestedPath, signal) {
570
+ await fs$1.mkdir(path.dirname(requestedPath), {
571
+ recursive: true,
572
+ mode: 448
573
+ });
574
+ const filePath = path.join(await fs$1.realpath(path.dirname(requestedPath)), path.basename(requestedPath));
575
+ const owner = await createLockOwner({ signal });
576
+ lastCreatedAt = Math.max(Date.now(), lastCreatedAt + 1);
577
+ const payload = `${JSON.stringify({
578
+ ...owner.payload,
579
+ created_at: new Date(lastCreatedAt).toISOString()
580
+ }, null, 2)}\n`;
581
+ for (;;) {
582
+ signal?.throwIfAborted();
583
+ const turn = await tryAcquireTurn(filePath, payload, owner, signal);
584
+ if (turn) return turn;
585
+ await setTimeout$1(LOCK_RETRY_MS, void 0, { signal });
586
+ }
587
+ }
588
+ //#endregion
349
589
  //#region src/session/execution/contracts.ts
350
590
  const DEFAULT_QUEUE_OWNER_TTL_MS = 3e5;
351
591
  function normalizeQueueOwnerTtlMs(ttlMs) {
@@ -366,6 +606,18 @@ function sliceReadWindow(content, line, limit) {
366
606
  }
367
607
  //#endregion
368
608
  //#region src/permission-prompt.ts
609
+ function visiblePromptText(value) {
610
+ let rendered = "";
611
+ for (const character of value) {
612
+ const codePoint = character.codePointAt(0);
613
+ if (codePoint !== void 0 && (codePoint <= 31 || codePoint >= 127 && codePoint <= 159)) {
614
+ rendered += `\\x${codePoint.toString(16).padStart(2, "0")}`;
615
+ continue;
616
+ }
617
+ rendered += character;
618
+ }
619
+ return rendered;
620
+ }
369
621
  let promptQueue = Promise.resolve();
370
622
  async function promptForPermission(options) {
371
623
  options.signal?.throwIfAborted();
@@ -384,6 +636,10 @@ async function promptForPermission(options) {
384
636
  signal.removeEventListener("abort", onAbort);
385
637
  }
386
638
  }
639
+ function promptForDisplay(prompt) {
640
+ if (prompt.startsWith("\n")) return `\n${visiblePromptText(prompt.slice(1))}`;
641
+ return visiblePromptText(prompt);
642
+ }
387
643
  async function askPermission(options) {
388
644
  options.signal?.throwIfAborted();
389
645
  if (!canPrompt()) return false;
@@ -400,7 +656,7 @@ async function askPermission(options) {
400
656
  rl.once("close", onClose);
401
657
  });
402
658
  try {
403
- const answer = await Promise.race([rl.question(options.prompt, { signal }), closed]);
659
+ const answer = await Promise.race([rl.question(promptForDisplay(options.prompt), { signal }), closed]);
404
660
  options.signal?.throwIfAborted();
405
661
  const normalized = answer?.trim().toLowerCase();
406
662
  return normalized === "y" || normalized === "yes";
@@ -414,8 +670,8 @@ function canPrompt() {
414
670
  return process.stdin.isTTY && process.stderr.isTTY && !process.stdin.readableEnded && !process.stdin.destroyed;
415
671
  }
416
672
  function writePromptDetails(options) {
417
- if (options.header) process.stderr.write(`\n${options.header}\n`);
418
- if (options.details && options.details.trim().length > 0) process.stderr.write(`${options.details}\n`);
673
+ if (options.header) process.stderr.write(`\n${visiblePromptText(options.header)}\n`);
674
+ if (options.details && options.details.trim().length > 0) process.stderr.write(`${options.details.split("\n").map(visiblePromptText).join("\n")}\n`);
419
675
  }
420
676
  //#endregion
421
677
  //#region src/filesystem.ts
@@ -1016,10 +1272,10 @@ function assignClaudeCodeSystemPrompt(target, systemPrompt) {
1016
1272
  function isAppendSystemPrompt(value) {
1017
1273
  return !!value && typeof value === "object" && typeof value.append === "string" && value.append.length > 0;
1018
1274
  }
1019
- function resolveClaudeCodeExecutable(platform = process.platform, env = process.env) {
1275
+ function resolveClaudeCodeExecutable(platform = process.platform, env = process.env, cwd) {
1020
1276
  if (platform !== "win32") return;
1021
1277
  if (readWindowsEnvValue(env, "CLAUDE_CODE_EXECUTABLE")) return;
1022
- return resolveWindowsExecutablePath("claude", env);
1278
+ return resolveWindowsExecutablePath("claude", env, cwd);
1023
1279
  }
1024
1280
  //#endregion
1025
1281
  //#region src/acp/auth-env.ts
@@ -1066,7 +1322,7 @@ function promotePrefixedAuthEnvironment(env) {
1066
1322
  if (!normalized) continue;
1067
1323
  protectEnvKey(protectedKeys, key);
1068
1324
  protectEnvKey(protectedKeys, normalized);
1069
- if (env[normalized] == null) env[normalized] = value;
1325
+ assignIfMissing(env, normalized, value);
1070
1326
  }
1071
1327
  return protectedKeys;
1072
1328
  }
@@ -1090,7 +1346,12 @@ function buildAgentEnvironment(authCredentials, sessionEnv, agentProcessEnv) {
1090
1346
  function assignSessionEnv(env, key, value) {
1091
1347
  const normalizedKey = protectedEnvKey(key);
1092
1348
  for (const existingKey of Object.keys(env)) if (protectedEnvKey(existingKey) === normalizedKey) delete env[existingKey];
1093
- env[key] = value;
1349
+ Object.defineProperty(env, key, {
1350
+ value,
1351
+ writable: true,
1352
+ enumerable: true,
1353
+ configurable: true
1354
+ });
1094
1355
  }
1095
1356
  function addAuthCredentialEnvKeys(protectedKeys, methodId, credential) {
1096
1357
  if (typeof credential !== "string" || credential.trim().length === 0) return;
@@ -1103,7 +1364,7 @@ function addAuthCredentialEnvKeys(protectedKeys, methodId, credential) {
1103
1364
  }
1104
1365
  function assignAuthCredentialEnv(env, methodId, credential) {
1105
1366
  if (typeof credential !== "string" || credential.trim().length === 0) return;
1106
- if (!methodId.includes("=") && !methodId.includes("\0") && env[methodId] == null) env[methodId] = credential;
1367
+ if (!methodId.includes("=") && !methodId.includes("\0")) assignIfMissing(env, methodId, credential);
1107
1368
  const normalized = toEnvToken(methodId);
1108
1369
  if (normalized) {
1109
1370
  assignIfMissing(env, `${AUTH_ENV_PREFIX}${normalized}`, credential);
@@ -1111,7 +1372,10 @@ function assignAuthCredentialEnv(env, methodId, credential) {
1111
1372
  }
1112
1373
  }
1113
1374
  function assignIfMissing(env, key, value) {
1114
- if (env[key] == null) env[key] = value;
1375
+ if (env[key] != null) return;
1376
+ const normalizedKey = protectedEnvKey(key);
1377
+ if (process.platform === "win32" && Object.entries(env).some(([existingKey, existingValue]) => existingValue != null && protectedEnvKey(existingKey) === normalizedKey)) return;
1378
+ env[key] = value;
1115
1379
  }
1116
1380
  function resolveConfiguredAuthCredential(methodId, authCredentials) {
1117
1381
  const configCredentials = authCredentials ?? {};
@@ -1273,34 +1537,7 @@ function captureCommandProbeOutput(child, timeoutMs, retire) {
1273
1537
  };
1274
1538
  }
1275
1539
  //#endregion
1276
- //#region src/acp/model-support.ts
1277
- const REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE = "ACP_MODEL_UNSUPPORTED";
1278
- const REQUESTED_MODEL_UNSUPPORTED_REASONS = ["missing-capability", "unadvertised-model"];
1279
- var RequestedModelUnsupportedError = class extends Error {
1280
- code = REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE;
1281
- reason;
1282
- ambiguous;
1283
- constructor(message, reason, details) {
1284
- super(message);
1285
- this.name = "RequestedModelUnsupportedError";
1286
- this.reason = reason;
1287
- if (details?.ambiguous) this.ambiguous = true;
1288
- }
1289
- };
1290
- function isRequestedModelUnsupportedReason(value) {
1291
- return typeof value === "string" && REQUESTED_MODEL_UNSUPPORTED_REASONS.includes(value);
1292
- }
1293
- function isRequestedModelUnsupportedError(value) {
1294
- if (value instanceof RequestedModelUnsupportedError) return true;
1295
- if (!value || typeof value !== "object") return false;
1296
- const candidate = value;
1297
- return candidate.name === "RequestedModelUnsupportedError" && candidate.code === "ACP_MODEL_UNSUPPORTED" && isRequestedModelUnsupportedReason(candidate.reason);
1298
- }
1299
- function supportsLegacyClaudeCodeModelMetadata(agentCommand) {
1300
- if (!agentCommand) return false;
1301
- const { command, args } = splitCommandLine(agentCommand);
1302
- return isClaudeAcpCommand(command, args);
1303
- }
1540
+ //#region src/acp/model-config-state.ts
1304
1541
  function asRecord$2(value) {
1305
1542
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
1306
1543
  return value;
@@ -1359,6 +1596,35 @@ function modelStateFromConfigOptions(configOptions) {
1359
1596
  }
1360
1597
  return selected;
1361
1598
  }
1599
+ //#endregion
1600
+ //#region src/acp/model-support.ts
1601
+ const REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE = "ACP_MODEL_UNSUPPORTED";
1602
+ const REQUESTED_MODEL_UNSUPPORTED_REASONS = ["missing-capability", "unadvertised-model"];
1603
+ var RequestedModelUnsupportedError = class extends Error {
1604
+ code = REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE;
1605
+ reason;
1606
+ ambiguous;
1607
+ constructor(message, reason, details) {
1608
+ super(message);
1609
+ this.name = "RequestedModelUnsupportedError";
1610
+ this.reason = reason;
1611
+ if (details?.ambiguous) this.ambiguous = true;
1612
+ }
1613
+ };
1614
+ function isRequestedModelUnsupportedReason(value) {
1615
+ return typeof value === "string" && REQUESTED_MODEL_UNSUPPORTED_REASONS.includes(value);
1616
+ }
1617
+ function isRequestedModelUnsupportedError(value) {
1618
+ if (value instanceof RequestedModelUnsupportedError) return true;
1619
+ if (!value || typeof value !== "object") return false;
1620
+ const candidate = value;
1621
+ return candidate.name === "RequestedModelUnsupportedError" && candidate.code === "ACP_MODEL_UNSUPPORTED" && isRequestedModelUnsupportedReason(candidate.reason);
1622
+ }
1623
+ function supportsLegacyClaudeCodeModelMetadata(agentCommand) {
1624
+ if (!agentCommand) return false;
1625
+ const { command, args } = splitCommandLine(agentCommand);
1626
+ return isClaudeAcpCommand(command, args);
1627
+ }
1362
1628
  function modelStateFromLegacyResponse(response) {
1363
1629
  if (!response || typeof response !== "object") return;
1364
1630
  const models = response.models;
@@ -1390,6 +1656,16 @@ function resolveRequestedModelId(params) {
1390
1656
  if (candidates.length > 1) throw new RequestedModelUnsupportedError(`Cannot select model "${params.requestedModel}": multiple advertised Cursor models match (${candidates.join(", ")}). Use an exact advertised model ID.`, "unadvertised-model", { ambiguous: true });
1391
1657
  return candidates.length === 1 ? candidates[0] : params.requestedModel;
1392
1658
  }
1659
+ function resolveRequestedConfigOption(params) {
1660
+ const modelConfigId = params.models?.configId;
1661
+ return {
1662
+ modelConfigId,
1663
+ resolvedValue: modelConfigId === params.configId ? resolveRequestedModelId({
1664
+ ...params,
1665
+ requestedModel: params.value
1666
+ }) : params.value
1667
+ };
1668
+ }
1393
1669
  function isCursorAcpCommandForModelAlias(agentCommand) {
1394
1670
  if (!agentCommand) return false;
1395
1671
  const { command, args } = splitCommandLine(agentCommand);
@@ -1622,7 +1898,7 @@ function buildTerminalSpawnOptions(command, cwd, env, platform = process.platfor
1622
1898
  "pipe"
1623
1899
  ],
1624
1900
  windowsHide: true
1625
- }, platform, resolvedEnv ?? process.env);
1901
+ }, platform, resolvedEnv ?? process.env, cwd);
1626
1902
  }
1627
1903
  function readTerminalOutputCeiling() {
1628
1904
  const raw = process.env.ACPX_TERMINAL_MAX_OUTPUT_BYTES?.trim();
@@ -1673,6 +1949,7 @@ var TerminalManager = class {
1673
1949
  processHelperTimeoutMs;
1674
1950
  outputCeilingBytes;
1675
1951
  terminals = /* @__PURE__ */ new Map();
1952
+ creations = /* @__PURE__ */ new Map();
1676
1953
  constructor(options) {
1677
1954
  this.outputCeilingBytes = readTerminalOutputCeiling();
1678
1955
  this.cwd = options.cwd;
@@ -1689,7 +1966,15 @@ var TerminalManager = class {
1689
1966
  this.permissionMode = permissionMode;
1690
1967
  this.nonInteractivePermissions = nonInteractivePermissions ?? "deny";
1691
1968
  }
1692
- async createTerminal(params, authority) {
1969
+ createTerminal(params, authority) {
1970
+ const creation = {};
1971
+ const pending = Promise.resolve().then(() => this.create(params, authority, creation));
1972
+ this.creations.set(pending, creation);
1973
+ const remove = () => this.creations.delete(pending);
1974
+ pending.then(remove, remove);
1975
+ return pending;
1976
+ }
1977
+ async create(params, authority, creation) {
1693
1978
  assertControlAuthority(authority);
1694
1979
  const commandLine = toCommandLine(params.command, params.args);
1695
1980
  const summary = `terminal/create: ${commandLine}`;
@@ -1753,10 +2038,15 @@ var TerminalManager = class {
1753
2038
  await terminal.descendants?.capture(terminal.processHelperTimeoutMs);
1754
2039
  assertControlAuthority(authority);
1755
2040
  } catch (error) {
1756
- await this.releaseTerminal({
1757
- terminalId,
1758
- sessionId: params.sessionId
1759
- });
2041
+ try {
2042
+ await this.releaseTerminal({
2043
+ terminalId,
2044
+ sessionId: params.sessionId
2045
+ });
2046
+ } catch (cleanupError) {
2047
+ creation.cleanupFailure = { error: cleanupError };
2048
+ throw cleanupError;
2049
+ }
1760
2050
  throw error;
1761
2051
  }
1762
2052
  this.emitOperation({
@@ -1889,11 +2179,13 @@ var TerminalManager = class {
1889
2179
  }
1890
2180
  }
1891
2181
  async shutdown() {
2182
+ const creations = [...this.creations];
2183
+ await Promise.allSettled(creations.map(([pending]) => pending));
1892
2184
  const results = await Promise.allSettled(Array.from(this.terminals.keys(), (terminalId) => this.releaseTerminal({
1893
2185
  terminalId,
1894
2186
  sessionId: "shutdown"
1895
2187
  })));
1896
- const failures = [];
2188
+ const failures = creations.flatMap(([, creation]) => creation.cleanupFailure ? [creation.cleanupFailure.error] : []);
1897
2189
  for (const result of results) if (result.status === "rejected") failures.push(result.reason);
1898
2190
  if (failures.length > 0) throw new AggregateError(failures, "Terminal shutdown failed", { cause: failures[0] });
1899
2191
  }
@@ -1983,7 +2275,7 @@ async function spawnTerminalProcess(params, defaultCwd, authority) {
1983
2275
  }
1984
2276
  async function spawnAndWait(spawnCommand, params, defaultCwd, authority) {
1985
2277
  const spawnOptions = buildTerminalSpawnOptions(spawnCommand.command, params.cwd ?? defaultCwd, params.env);
1986
- if (spawnCommand.killProcessGroup || process.platform !== "win32") spawnOptions.detached = true;
2278
+ if (process.platform !== "win32") spawnOptions.detached = true;
1987
2279
  assertControlAuthority(authority);
1988
2280
  const proc = spawn(spawnCommand.command, spawnCommand.args, spawnOptions);
1989
2281
  await waitForSpawn(proc);
@@ -2147,7 +2439,14 @@ function hasResponseField(response, field) {
2147
2439
  }
2148
2440
  function normalizeResponseConfigOptions(response) {
2149
2441
  if (!response || !("configOptions" in response)) return;
2150
- return response.configOptions ?? [];
2442
+ return Array.isArray(response.configOptions) ? response.configOptions : void 0;
2443
+ }
2444
+ function normalizeConfigOptionAcknowledgement(response) {
2445
+ if (!response || typeof response !== "object" || Array.isArray(response)) return {};
2446
+ if (Array.isArray(response.configOptions)) return response;
2447
+ const acknowledgement = { ...response };
2448
+ delete acknowledgement.configOptions;
2449
+ return acknowledgement;
2151
2450
  }
2152
2451
  function toReconnectedSessionResult(response) {
2153
2452
  const configOptions = normalizeResponseConfigOptions(response);
@@ -2158,7 +2457,7 @@ function toReconnectedSessionResult(response) {
2158
2457
  configOptions,
2159
2458
  response
2160
2459
  }),
2161
- configOptionsPresent: hasResponseField(response, "configOptions"),
2460
+ configOptionsPresent: configOptions !== void 0,
2162
2461
  legacyModelMetadataPresent: hasResponseField(response, "models")
2163
2462
  };
2164
2463
  }
@@ -2337,8 +2636,9 @@ var AcpClient = class {
2337
2636
  }
2338
2637
  fallback?.abort();
2339
2638
  }
2340
- async start(authority) {
2639
+ async start(authority, options) {
2341
2640
  assertControlAuthority(authority);
2641
+ if (options) this.options.sessionOptions = structuredClone(options.sessionOptions);
2342
2642
  if (this.hasLiveConnection()) return;
2343
2643
  const epoch = await this.waitForPreviousClose();
2344
2644
  const maxMessageBytes = readMaxAcpMessageBytes();
@@ -2457,14 +2757,14 @@ var AcpClient = class {
2457
2757
  async ensureLaunchSupport(plan) {
2458
2758
  if (plan.copilotAcp) await ensureCopilotAcpSupport(plan.spawnCommand, plan.commandContext);
2459
2759
  if (!plan.claudeAcp) return;
2460
- const claudeExe = resolveClaudeCodeExecutable(process.platform, plan.spawnOptions.env);
2760
+ const claudeExe = resolveClaudeCodeExecutable(process.platform, plan.spawnOptions.env, plan.spawnOptions.cwd);
2461
2761
  if (claudeExe) {
2462
2762
  plan.spawnOptions.env.CLAUDE_CODE_EXECUTABLE = claudeExe;
2463
2763
  this.log(`resolved system Claude Code executable: ${claudeExe}`);
2464
2764
  }
2465
2765
  }
2466
2766
  prepareProcessLaunch(command, args, options) {
2467
- const resolved = buildAgentSpawnCommand(command, args, process.platform, options.env);
2767
+ const resolved = buildAgentSpawnCommand(command, args, process.platform, options.env, options.cwd);
2468
2768
  return {
2469
2769
  launch: Object.freeze({
2470
2770
  launchId: randomUUID(),
@@ -2674,6 +2974,10 @@ var AcpClient = class {
2674
2974
  onPromptRequestWritten: (active, owner) => this.onPromptRequestWritten(active, owner)
2675
2975
  });
2676
2976
  }
2977
+ sessionMeta() {
2978
+ const { command, args } = resolveAgentCommandParts(this.options.agentCommand, this.options.agentArgv);
2979
+ return buildClaudeCodeOptionsMeta(this.options.sessionOptions, isClaudeAcpCommand(command, args));
2980
+ }
2677
2981
  async createSession(cwd = this.options.cwd, authority) {
2678
2982
  assertControlAuthority(authority);
2679
2983
  const connection = this.getConnection();
@@ -2685,7 +2989,7 @@ var AcpClient = class {
2685
2989
  const createPromise = this.runConnectionRequest(() => connection.agent.request(methods.agent.session.new, {
2686
2990
  cwd: sessionCwd,
2687
2991
  mcpServers: this.options.mcpServers ?? [],
2688
- _meta: buildClaudeCodeOptionsMeta(this.options.sessionOptions, claudeAcp)
2992
+ _meta: this.sessionMeta()
2689
2993
  }), authority);
2690
2994
  result = claudeAcp ? await withTimeout(createPromise, resolveClaudeAcpSessionCreateTimeoutMs()) : await createPromise;
2691
2995
  } catch (error) {
@@ -2707,7 +3011,7 @@ var AcpClient = class {
2707
3011
  agentSessionId: extractAgentSessionId(result._meta),
2708
3012
  configOptions,
2709
3013
  models,
2710
- configOptionsPresent: hasResponseField(result, "configOptions"),
3014
+ configOptionsPresent: configOptions !== void 0,
2711
3015
  legacyModelMetadataPresent: hasResponseField(result, "models")
2712
3016
  };
2713
3017
  }
@@ -2725,7 +3029,8 @@ var AcpClient = class {
2725
3029
  response = await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.load, {
2726
3030
  sessionId,
2727
3031
  cwd: sessionCwd,
2728
- mcpServers: this.options.mcpServers ?? []
3032
+ mcpServers: this.options.mcpServers ?? [],
3033
+ _meta: this.sessionMeta()
2729
3034
  }), options.authority);
2730
3035
  await this.waitForSessionUpdateDrain(options.replayIdleMs ?? REPLAY_IDLE_MS, options.replayDrainTimeoutMs ?? REPLAY_DRAIN_TIMEOUT_MS);
2731
3036
  } finally {
@@ -2743,7 +3048,8 @@ var AcpClient = class {
2743
3048
  const response = await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.resume, {
2744
3049
  sessionId,
2745
3050
  cwd: sessionCwd,
2746
- mcpServers: this.options.mcpServers ?? []
3051
+ mcpServers: this.options.mcpServers ?? [],
3052
+ _meta: this.sessionMeta()
2747
3053
  }), authority);
2748
3054
  this.loadedSessionId = sessionId;
2749
3055
  const result = toReconnectedSessionResult(response);
@@ -2862,11 +3168,11 @@ var AcpClient = class {
2862
3168
  assertControlAuthority(authority);
2863
3169
  const resolvedValue = models?.configId === configId ? this.resolveSessionModelId(value, models) : value;
2864
3170
  const connection = this.getConnection();
2865
- return await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.setConfigOption, {
3171
+ return normalizeConfigOptionAcknowledgement(await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.setConfigOption, {
2866
3172
  sessionId,
2867
3173
  configId,
2868
3174
  value: resolvedValue
2869
- }), authority, (error) => maybeWrapSessionControlError("session/set_config_option", error, `for "${configId}"="${value}"`));
3175
+ }), authority, (error) => maybeWrapSessionControlError("session/set_config_option", error, `for "${configId}"="${value}"`)));
2870
3176
  }
2871
3177
  async setSessionModel(sessionId, modelId, models, authority) {
2872
3178
  assertControlAuthority(authority);
@@ -2888,11 +3194,11 @@ var AcpClient = class {
2888
3194
  }
2889
3195
  async setSessionModelThroughConfig(sessionId, modelId, configId, authority) {
2890
3196
  const connection = this.getConnection();
2891
- return await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.setConfigOption, {
3197
+ return normalizeConfigOptionAcknowledgement(await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.setConfigOption, {
2892
3198
  sessionId,
2893
3199
  configId,
2894
3200
  value: modelId
2895
- }), authority, (error) => this.throwSessionModelError("session/set_config_option", modelId, error));
3201
+ }), authority, (error) => this.throwSessionModelError("session/set_config_option", modelId, error)));
2896
3202
  }
2897
3203
  async setSessionModelThroughLegacyMethod(sessionId, modelId, authority) {
2898
3204
  const connection = this.getConnection();
@@ -3659,6 +3965,7 @@ function removeModelConfigOptions(state) {
3659
3965
  state.config_options = state.config_options.filter((option) => option.category !== "model" && option.id !== "model");
3660
3966
  }
3661
3967
  function applyConfigOptionsModelState(state, configOptions) {
3968
+ if (!Array.isArray(configOptions)) return;
3662
3969
  const previousConfigModels = modelStateFromConfigOptions(state.config_options);
3663
3970
  const preservesLegacyControl = state.model_control === "legacy_set_model" || state.model_control === void 0 && previousConfigModels === void 0 && legacyModelState(state) !== void 0;
3664
3971
  state.config_options = structuredClone(configOptions);
@@ -3668,14 +3975,9 @@ function applyConfigOptionsModelState(state, configOptions) {
3668
3975
  else clearAdvertisedModelState(state);
3669
3976
  }
3670
3977
  //#endregion
3671
- //#region src/session/conversation-model.ts
3672
- const MAX_RUNTIME_MESSAGES = 200;
3673
- const MAX_RUNTIME_AGENT_TEXT_CHARS = 8e3;
3674
- const MAX_RUNTIME_THINKING_CHARS = 4e3;
3675
- const MAX_RUNTIME_TOOL_IO_CHARS = 4e3;
3676
- const MAX_RUNTIME_REQUEST_TOKEN_USAGE = 100;
3677
- function isoNow() {
3678
- return (/* @__PURE__ */ new Date()).toISOString();
3978
+ //#region src/session/conversation-reducer.ts
3979
+ function resolveUserMessageId(value) {
3980
+ return typeof value === "string" ? value : value();
3679
3981
  }
3680
3982
  function deepClone(value) {
3681
3983
  try {
@@ -3731,6 +4033,7 @@ function contentToUserContent(content) {
3731
4033
  if (content.type === "resource") return resourceToUserContent(content);
3732
4034
  if (content.type === "image") return { Image: {
3733
4035
  source: content.data,
4036
+ mime_type: content.mimeType,
3734
4037
  size: null
3735
4038
  } };
3736
4039
  if (content.type === "audio") return { Audio: {
@@ -3745,9 +4048,6 @@ function resourceToUserContent(content) {
3745
4048
  content: content.resource.uri
3746
4049
  } };
3747
4050
  }
3748
- function nextUserMessageId() {
3749
- return randomUUID();
3750
- }
3751
4051
  function isUserMessage(message) {
3752
4052
  return typeof message === "object" && message !== null && hasOwn(message, "User");
3753
4053
  }
@@ -3779,7 +4079,7 @@ function ensureAgentMessage(conversation) {
3779
4079
  function appendAgentText(agent, text) {
3780
4080
  const last = agent.content.at(-1);
3781
4081
  if (last && isAgentTextContent(last)) {
3782
- last.Text = trimRuntimeText(`${last.Text}${text}`, MAX_RUNTIME_AGENT_TEXT_CHARS);
4082
+ last.Text = `${last.Text}${text}`;
3783
4083
  return;
3784
4084
  }
3785
4085
  const next = { Text: text };
@@ -3788,7 +4088,7 @@ function appendAgentText(agent, text) {
3788
4088
  function appendAgentThinking(agent, text) {
3789
4089
  const last = agent.content.at(-1);
3790
4090
  if (last && isAgentThinkingContent(last)) {
3791
- last.Thinking.text = trimRuntimeText(`${last.Thinking.text}${text}`, MAX_RUNTIME_THINKING_CHARS);
4091
+ last.Thinking.text = `${last.Thinking.text}${text}`;
3792
4092
  return;
3793
4093
  }
3794
4094
  const next = { Thinking: {
@@ -3797,10 +4097,6 @@ function appendAgentThinking(agent, text) {
3797
4097
  } };
3798
4098
  agent.content.push(next);
3799
4099
  }
3800
- function trimRuntimeText(value, maxChars) {
3801
- if (value.length <= maxChars) return value;
3802
- return `${value.slice(0, Math.max(0, maxChars - 3))}...`;
3803
- }
3804
4100
  function statusIndicatesComplete(status) {
3805
4101
  if (typeof status !== "string") return false;
3806
4102
  const normalized = status.toLowerCase();
@@ -3812,18 +4108,18 @@ function statusIndicatesError(status) {
3812
4108
  return normalized.includes("fail") || normalized.includes("error");
3813
4109
  }
3814
4110
  function toToolResultContent(value) {
3815
- if (typeof value === "string") return { Text: trimRuntimeText(value, MAX_RUNTIME_TOOL_IO_CHARS) };
4111
+ if (typeof value === "string") return { Text: value };
3816
4112
  if (value != null) try {
3817
- return { Text: trimRuntimeText(JSON.stringify(value), MAX_RUNTIME_TOOL_IO_CHARS) };
4113
+ return { Text: JSON.stringify(value) ?? "[Unserializable value]" };
3818
4114
  } catch {
3819
4115
  return { Text: "[Unserializable value]" };
3820
4116
  }
3821
4117
  return { Text: "" };
3822
4118
  }
3823
4119
  function toRawInput(value) {
3824
- if (typeof value === "string") return trimRuntimeText(value, MAX_RUNTIME_TOOL_IO_CHARS);
4120
+ if (typeof value === "string") return value;
3825
4121
  try {
3826
- return trimRuntimeText(JSON.stringify(value ?? {}), MAX_RUNTIME_TOOL_IO_CHARS);
4122
+ return JSON.stringify(value ?? {}) ?? "[Unserializable input]";
3827
4123
  } catch {
3828
4124
  return value == null ? "" : "[Unserializable input]";
3829
4125
  }
@@ -3868,11 +4164,13 @@ function existingToolResultValues(existing) {
3868
4164
  };
3869
4165
  }
3870
4166
  function applyToolCallUpdate(agent, update) {
4167
+ const contentCount = agent.content.length;
3871
4168
  const tool = ensureToolUseContent(agent, update.toolCallId);
3872
4169
  applyToolIdentityUpdate(tool, update);
3873
4170
  applyToolInputUpdate(tool, update);
3874
4171
  applyToolStatusUpdate(tool, update);
3875
4172
  applyToolResultUpdate(agent, tool, update);
4173
+ return agent.content.length !== contentCount || hasOwn(update, "rawInput") || hasToolResultPatch(update);
3876
4174
  }
3877
4175
  function applyToolIdentityUpdate(tool, update) {
3878
4176
  if (hasOwn(update, "title")) tool.name = normalizeAgentName(update.title) ?? tool.name ?? "tool_call";
@@ -3975,7 +4273,7 @@ function lastUserMessageId(conversation) {
3975
4273
  if (message && isUserMessage(message)) return message.User.id;
3976
4274
  }
3977
4275
  }
3978
- function createSessionConversation(timestamp = isoNow()) {
4276
+ function createConversation(timestamp) {
3979
4277
  return {
3980
4278
  title: null,
3981
4279
  messages: [],
@@ -3985,8 +4283,7 @@ function createSessionConversation(timestamp = isoNow()) {
3985
4283
  request_token_usage: {}
3986
4284
  };
3987
4285
  }
3988
- function cloneSessionConversation(conversation) {
3989
- if (!conversation) return createSessionConversation();
4286
+ function cloneConversation(conversation) {
3990
4287
  return {
3991
4288
  title: conversation.title,
3992
4289
  messages: deepClone(conversation.messages ?? []),
@@ -4027,57 +4324,47 @@ function cloneSessionOptions(options) {
4027
4324
  function cloneSystemPromptOption(option) {
4028
4325
  return typeof option === "string" ? option : { append: option.append };
4029
4326
  }
4030
- function recordPromptSubmission(conversation, prompt, timestamp = isoNow(), messageId) {
4327
+ function reducePromptSubmission(conversation, prompt, timestamp, messageId) {
4031
4328
  const userContent = (typeof prompt === "string" ? textPrompt(prompt) : prompt).map((content) => contentToUserContent(content)).filter((content) => content !== void 0);
4032
- if (userContent.length === 0) return;
4033
- const promptMessageId = messageId ?? nextUserMessageId();
4329
+ if (userContent.length === 0) return { messageIndex: void 0 };
4034
4330
  conversation.messages.push({ User: {
4035
- id: promptMessageId,
4036
- content: userContent.map((content) => {
4037
- if ("Text" in content) return { Text: trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS) };
4038
- return content;
4039
- })
4331
+ id: resolveUserMessageId(messageId),
4332
+ content: userContent
4040
4333
  } });
4041
4334
  updateConversationTimestamp(conversation, timestamp);
4042
- trimConversationForRuntime(conversation);
4043
- return promptMessageId;
4335
+ return { messageIndex: conversation.messages.length - 1 };
4044
4336
  }
4045
- function recordSessionUpdate(conversation, state, notification, timestamp = isoNow(), userMessageId) {
4337
+ function reduceSessionUpdate(conversation, state, notification, timestamp, userMessageId) {
4046
4338
  const acpx = ensureAcpxState$1(state);
4047
- const update = notification.update;
4048
- applySessionUpdate(conversation, acpx, update, userMessageId);
4339
+ const messageIndex = applySessionUpdate(conversation, acpx, notification.update, userMessageId);
4049
4340
  updateConversationTimestamp(conversation, timestamp);
4050
- trimConversationForRuntime(conversation);
4051
- return acpx;
4341
+ return {
4342
+ acpx,
4343
+ messageIndex
4344
+ };
4052
4345
  }
4053
- function recordPromptResponseUsage(conversation, usage, promptMessageId, timestamp = isoNow()) {
4346
+ function reducePromptResponseUsage(conversation, usage, promptMessageId, timestamp) {
4054
4347
  const tokenUsage = sourceToTokenUsage(usage);
4055
4348
  if (!tokenUsage) return false;
4056
4349
  applyTokenUsage(conversation, tokenUsage, promptMessageId);
4057
4350
  updateConversationTimestamp(conversation, timestamp);
4058
- trimConversationForRuntime(conversation);
4059
4351
  return true;
4060
4352
  }
4061
4353
  function applySessionUpdate(conversation, acpx, update, userMessageId) {
4062
- const handler = SESSION_UPDATE_HANDLERS[update.sessionUpdate];
4063
- handler?.(conversation, acpx, update, userMessageId);
4354
+ return (Object.hasOwn(SESSION_UPDATE_HANDLERS, update.sessionUpdate) ? SESSION_UPDATE_HANDLERS[update.sessionUpdate] : void 0)?.(conversation, acpx, update, userMessageId);
4064
4355
  }
4065
4356
  const SESSION_UPDATE_HANDLERS = {
4066
4357
  user_message_chunk: (conversation, _acpx, update, userMessageId) => {
4067
- if (update.sessionUpdate === "user_message_chunk") appendUserMessageChunk(conversation, update.content, userMessageId);
4358
+ if (update.sessionUpdate === "user_message_chunk") return appendUserMessageChunk(conversation, update.content, userMessageId);
4068
4359
  },
4069
4360
  agent_message_chunk: (conversation, _acpx, update) => {
4070
- if (update.sessionUpdate === "agent_message_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentText);
4361
+ if (update.sessionUpdate === "agent_message_chunk") return appendAgentMessageChunk(conversation, update.content, appendAgentText);
4071
4362
  },
4072
4363
  agent_thought_chunk: (conversation, _acpx, update) => {
4073
- if (update.sessionUpdate === "agent_thought_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentThinking);
4074
- },
4075
- tool_call: (conversation, _acpx, update) => {
4076
- if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") applyToolCallUpdate(ensureAgentMessage(conversation), update);
4077
- },
4078
- tool_call_update: (conversation, _acpx, update) => {
4079
- if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") applyToolCallUpdate(ensureAgentMessage(conversation), update);
4364
+ if (update.sessionUpdate === "agent_thought_chunk") return appendAgentMessageChunk(conversation, update.content, appendAgentThinking);
4080
4365
  },
4366
+ tool_call: applyToolUpdate,
4367
+ tool_call_update: applyToolUpdate,
4081
4368
  usage_update: (conversation, _acpx, update) => {
4082
4369
  if (update.sessionUpdate === "usage_update") applyUsageUpdate(conversation, update);
4083
4370
  },
@@ -4094,17 +4381,24 @@ const SESSION_UPDATE_HANDLERS = {
4094
4381
  if (update.sessionUpdate === "config_option_update") applyConfigOptionsModelState(acpx, deepClone(update.configOptions));
4095
4382
  }
4096
4383
  };
4384
+ function applyToolUpdate(conversation, _acpx, update) {
4385
+ if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") return;
4386
+ return applyToolCallUpdate(ensureAgentMessage(conversation), update) ? conversation.messages.length - 1 : void 0;
4387
+ }
4097
4388
  function appendUserMessageChunk(conversation, content, messageId) {
4098
4389
  const userContent = contentToUserContent(content);
4099
4390
  if (!userContent) return;
4100
4391
  conversation.messages.push({ User: {
4101
- id: messageId ?? nextUserMessageId(),
4392
+ id: resolveUserMessageId(messageId),
4102
4393
  content: [userContent]
4103
4394
  } });
4395
+ return conversation.messages.length - 1;
4104
4396
  }
4105
4397
  function appendAgentMessageChunk(conversation, content, append) {
4106
4398
  const text = extractText(content);
4107
- if (text) append(ensureAgentMessage(conversation), text);
4399
+ if (!text) return;
4400
+ append(ensureAgentMessage(conversation), text);
4401
+ return conversation.messages.length - 1;
4108
4402
  }
4109
4403
  function applyUsageUpdate(conversation, update) {
4110
4404
  const usage = usageToTokenUsage(update);
@@ -4116,17 +4410,30 @@ function applyUsageUpdate(conversation, update) {
4116
4410
  function applyTokenUsage(conversation, usage, promptMessageId) {
4117
4411
  conversation.cumulative_token_usage = usage;
4118
4412
  const userId = promptMessageId ?? lastUserMessageId(conversation);
4119
- if (userId) conversation.request_token_usage[userId] = usage;
4413
+ if (userId) Object.defineProperty(conversation.request_token_usage, userId, {
4414
+ value: usage,
4415
+ writable: true,
4416
+ enumerable: true,
4417
+ configurable: true
4418
+ });
4120
4419
  }
4121
4420
  function applySessionInfoUpdate(conversation, update) {
4122
4421
  if (hasOwn(update, "title")) conversation.title = update.title ?? null;
4123
4422
  if (hasOwn(update, "updatedAt")) conversation.updated_at = update.updatedAt ?? conversation.updated_at;
4124
4423
  }
4125
- function recordClientOperation(conversation, state, _operation, timestamp = isoNow()) {
4126
- const acpx = ensureAcpxState$1(state);
4127
- updateConversationTimestamp(conversation, timestamp);
4128
- trimConversationForRuntime(conversation);
4129
- return acpx;
4424
+ //#endregion
4425
+ //#region src/session/conversation-retention.ts
4426
+ const MAX_RUNTIME_MESSAGES = 200;
4427
+ const MAX_RUNTIME_AGENT_TEXT_CHARS = 8e3;
4428
+ const MAX_RUNTIME_THINKING_CHARS = 4e3;
4429
+ const MAX_RUNTIME_TOOL_IO_CHARS = 4e3;
4430
+ const MAX_RUNTIME_REQUEST_TOKEN_USAGE = 100;
4431
+ function trimRuntimeText(value, maxChars) {
4432
+ if (value.length <= maxChars) return value;
4433
+ let end = Math.max(0, maxChars - 3);
4434
+ const last = value.charCodeAt(end - 1);
4435
+ if (last >= 55296 && last <= 56319) end -= 1;
4436
+ return `${value.slice(0, end)}...`;
4130
4437
  }
4131
4438
  function trimConversationForRuntime(conversation) {
4132
4439
  if (conversation.messages.length > MAX_RUNTIME_MESSAGES) conversation.messages = conversation.messages.slice(-200);
@@ -4161,6 +4468,39 @@ function trimRuntimeToolResult(result) {
4161
4468
  if (typeof result.output === "string") result.output = trimRuntimeText(result.output, MAX_RUNTIME_TOOL_IO_CHARS);
4162
4469
  }
4163
4470
  //#endregion
4471
+ //#region src/session/conversation-model.ts
4472
+ function isoNow() {
4473
+ return (/* @__PURE__ */ new Date()).toISOString();
4474
+ }
4475
+ function createSessionConversation(timestamp = isoNow()) {
4476
+ return createConversation(timestamp);
4477
+ }
4478
+ function cloneSessionConversation(conversation) {
4479
+ return conversation ? cloneConversation(conversation) : createSessionConversation();
4480
+ }
4481
+ function recordPromptSubmission(conversation, prompt, timestamp = isoNow(), messageId) {
4482
+ let promptMessageId = messageId;
4483
+ if (reducePromptSubmission(conversation, prompt, timestamp, () => promptMessageId ??= randomUUID()).messageIndex === void 0) return;
4484
+ trimConversationForRuntime(conversation);
4485
+ return promptMessageId;
4486
+ }
4487
+ function recordSessionUpdate(conversation, state, notification, timestamp = isoNow(), userMessageId) {
4488
+ const { acpx } = reduceSessionUpdate(conversation, state, notification, timestamp, userMessageId ?? randomUUID);
4489
+ trimConversationForRuntime(conversation);
4490
+ return acpx;
4491
+ }
4492
+ function recordPromptResponseUsage(conversation, usage, promptMessageId, timestamp = isoNow()) {
4493
+ if (!reducePromptResponseUsage(conversation, usage, promptMessageId, timestamp)) return false;
4494
+ trimConversationForRuntime(conversation);
4495
+ return true;
4496
+ }
4497
+ function recordClientOperation(conversation, state, _operation, timestamp = isoNow()) {
4498
+ const acpx = state ?? {};
4499
+ conversation.updated_at = timestamp;
4500
+ trimConversationForRuntime(conversation);
4501
+ return acpx;
4502
+ }
4503
+ //#endregion
4164
4504
  //#region src/runtime/engine/lifecycle.ts
4165
4505
  function createInitialSessionRecord(params) {
4166
4506
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -4283,19 +4623,27 @@ function storedEnvRecord(value) {
4283
4623
  const result = {};
4284
4624
  for (const [key, raw] of entries) {
4285
4625
  if (typeof raw !== "string") continue;
4286
- result[key] = raw;
4626
+ Object.defineProperty(result, key, {
4627
+ value: raw,
4628
+ writable: true,
4629
+ enumerable: true,
4630
+ configurable: true
4631
+ });
4287
4632
  }
4288
4633
  return Object.keys(result).length > 0 ? result : void 0;
4289
4634
  }
4290
4635
  function normalizeSystemPromptOption(value) {
4291
- const prompt = nonEmptyString(value);
4636
+ const prompt = nonEmptyPromptText(value);
4292
4637
  if (prompt !== void 0) return prompt;
4293
4638
  const append = appendedSystemPrompt(value);
4294
4639
  return append === void 0 ? void 0 : { append };
4295
4640
  }
4296
4641
  function appendedSystemPrompt(value) {
4297
4642
  if (typeof value !== "object" || value === null || Array.isArray(value)) return;
4298
- return nonEmptyString(value.append);
4643
+ return nonEmptyPromptText(value.append);
4644
+ }
4645
+ function nonEmptyPromptText(value) {
4646
+ return typeof value === "string" && value.length > 0 ? value : void 0;
4299
4647
  }
4300
4648
  function storedAllowedTools(value) {
4301
4649
  return Array.isArray(value) && value.every((item) => typeof item === "string") ? [...value] : void 0;
@@ -4346,316 +4694,108 @@ function clearDesiredConfigOption(state, configId) {
4346
4694
  function getDesiredModelId(state) {
4347
4695
  return normalizeModelId(state?.session_options?.model);
4348
4696
  }
4349
- function setCurrentModelId(record, modelId) {
4350
- const acpx = ensureAcpxState(record.acpx);
4351
- const normalized = normalizeModelId(modelId);
4352
- if (normalized) acpx.current_model_id = normalized;
4353
- else delete acpx.current_model_id;
4354
- record.acpx = acpx;
4355
- }
4356
4697
  function syncAdvertisedModelState(record, models) {
4357
4698
  if (!models) return;
4358
- const acpx = ensureAcpxState(record.acpx);
4359
- applyAdvertisedModelState(acpx, models);
4360
- record.acpx = acpx;
4361
- }
4362
- //#endregion
4363
- //#region src/session/model-application.ts
4364
- function currentModelIdFromSetModelResponse(response, fallbackModelId) {
4365
- return modelStateFromConfigOptions(response?.configOptions)?.currentModelId ?? fallbackModelId;
4366
- }
4367
- async function applyRequestedModelIfAdvertised(params) {
4368
- const requestedModel = typeof params.requestedModel === "string" ? params.requestedModel.trim() : "";
4369
- if (!requestedModel) return { applied: false };
4370
- const warning = assertRequestedModelSupported({
4371
- requestedModel,
4372
- models: params.models,
4373
- agentCommand: params.agentCommand,
4374
- context: "apply"
4375
- });
4376
- if (warning) params.onWarning?.(warning);
4377
- if (!params.models) return { applied: false };
4378
- if (params.models.currentModelId === requestedModel) return { applied: true };
4379
- return {
4380
- applied: true,
4381
- response: await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel, params.models, params.authority), params.timeoutMs)
4382
- };
4383
- }
4384
- //#endregion
4385
- //#region src/session/config-options.ts
4386
- function applyConfigOptionsToState(state, configOptions) {
4387
- const acpxState = cloneSessionAcpxState(state) ?? {};
4388
- applyConfigOptionsModelState(acpxState, configOptions);
4389
- return acpxState;
4390
- }
4391
- function applyConfigOptionsToRecord(record, result) {
4392
- const configOptions = result?.configOptions;
4393
- if (!configOptions) return;
4394
- record.acpx = applyConfigOptionsToState(record.acpx, configOptions);
4395
- }
4396
- function applyInitialModelSelection(record, originalModels, requestedModel, modelApplication) {
4397
- applyConfigOptionsToRecord(record, modelApplication.response);
4398
- syncAdvertisedModelState(record, modelApplication.response ? modelStateFromConfigOptions(modelApplication.response.configOptions) : originalModels);
4399
- if (modelApplication.applied) setCurrentModelId(record, currentModelIdFromSetModelResponse(modelApplication.response, requestedModel));
4400
- }
4401
- function applyAcceptedConfigOptions(state, response) {
4402
- const next = cloneSessionAcpxState(state) ?? {};
4403
- if (!response) return next;
4404
- applyConfigOptionsModelState(next, response.configOptions);
4405
- if (!next.desired_config_options) return next;
4406
- const desiredEntries = [];
4407
- 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]);
4408
- if (desiredEntries.length > 0) next.desired_config_options = Object.fromEntries(desiredEntries);
4409
- else delete next.desired_config_options;
4410
- return next;
4411
- }
4412
- function applyModelSelection(state, modelId, response) {
4413
- const modelConfigId = advertisedModelState(state)?.configId;
4414
- const next = applyAcceptedConfigOptions(state, response);
4415
- next.session_options = {
4416
- ...next.session_options,
4417
- model: modelId
4418
- };
4419
- next.current_model_id = currentModelIdFromSetModelResponse(response, modelId);
4420
- clearDesiredConfigOption(next, modelConfigId ?? advertisedModelState(next)?.configId);
4421
- return next;
4422
- }
4423
- function applyConfigOptionSelection(state, configId, value, response, modelConfigId = advertisedModelState(state)?.configId) {
4424
- if (configId === modelConfigId || configId === modelStateFromConfigOptions(response.configOptions)?.configId) return applyModelSelection(state, value, response);
4425
- const next = cloneSessionAcpxState(state) ?? {};
4426
- next.desired_config_options = {
4427
- ...next.desired_config_options,
4428
- [configId]: value
4429
- };
4430
- return applyAcceptedConfigOptions(next, response);
4431
- }
4432
- //#endregion
4433
- //#region src/session/turn-ownership.ts
4434
- const LOCK_RETRY_MS = 15;
4435
- const INCOMPLETE_RESERVATION_GRACE_MS = 15e3;
4436
- let lastCreatedAt = 0;
4437
- const localTurns = /* @__PURE__ */ new Map();
4438
- async function readLock(filePath) {
4439
- try {
4440
- const stat = await fs$1.lstat(filePath, { bigint: true });
4441
- if (!stat.isFile()) throw new Error(`Session turn lock is not a regular file: ${filePath}`);
4442
- return {
4443
- stat,
4444
- payload: await fs$1.readFile(filePath, "utf8")
4445
- };
4446
- } catch (error) {
4447
- if (error.code === "ENOENT") return;
4448
- throw error;
4449
- }
4450
- }
4451
- function parseLock(payload) {
4452
- try {
4453
- return JSON.parse(payload);
4454
- } catch {
4455
- return;
4456
- }
4457
- }
4458
- async function tryAcquireGuard(filePath, owner, signal) {
4459
- try {
4460
- return await acquireFileLock(filePath, {
4461
- managerKey: "acpx.session-turn",
4462
- lockPath: `${filePath}.guard`,
4463
- staleMs: Infinity,
4464
- timeoutMs: LOCK_RETRY_MS,
4465
- retry: {
4466
- retries: 8,
4467
- minTimeout: 1,
4468
- maxTimeout: 2,
4469
- factor: 1,
4470
- randomize: false
4471
- },
4472
- staleRecovery: "remove-if-unchanged",
4473
- payload: () => owner.payload,
4474
- shouldReclaim: ({ payload }) => owner.hasExited(payload, false, signal),
4475
- shouldRemoveStaleLock: ({ payload }) => owner.hasExited(payload, true, signal)
4476
- });
4477
- } catch (error) {
4478
- if (error.code === "file_lock_timeout") return;
4479
- throw error;
4480
- }
4481
- }
4482
- async function removeObservedLock(filePath, observed) {
4483
- const current = await readLock(filePath);
4484
- if (!current) return true;
4485
- if (current.payload !== observed.payload || current.stat.mtimeNs !== observed.stat.mtimeNs || current.stat.size !== observed.stat.size) return false;
4486
- await fs$1.unlink(filePath).catch((error) => {
4487
- if (error.code !== "ENOENT") throw error;
4488
- });
4489
- return true;
4490
- }
4491
- async function recoverAbandonedLock(filePath, owner, signal) {
4492
- const observed = await readLock(filePath);
4493
- if (!observed) return true;
4494
- const payload = parseLock(observed.payload);
4495
- const pid = lockOwnerPid(payload);
4496
- if (pid && (!await owner.hasExited(payload, false, signal) || !await owner.hasExited(payload, true, signal))) return false;
4497
- if (!pid && Date.now() - Number(observed.stat.mtimeMs) <= INCOMPLETE_RESERVATION_GRACE_MS) return false;
4498
- if (await removeObservedLock(filePath, observed)) {
4499
- incrementPerfCounter("session.events.stale_lock_recovered");
4500
- return true;
4501
- }
4502
- return false;
4503
- }
4504
- async function publishLock(filePath, payload, signal) {
4505
- await withTempFile({
4506
- rootDir: path.dirname(filePath),
4507
- prefix: "session-turn",
4508
- fileName: "lock"
4509
- }, async (temporaryPath) => {
4510
- await fs$1.writeFile(temporaryPath, payload, {
4511
- encoding: "utf8",
4512
- flag: "wx",
4513
- mode: 384
4514
- });
4515
- signal?.throwIfAborted();
4516
- try {
4517
- await fs$1.link(temporaryPath, filePath);
4518
- } catch (error) {
4519
- if (!isHardlinkFallbackError(error)) throw error;
4520
- await fs$1.writeFile(filePath, payload, {
4521
- encoding: "utf8",
4522
- flag: "wx",
4523
- mode: 384
4524
- });
4525
- }
4526
- });
4699
+ const acpx = ensureAcpxState(record.acpx);
4700
+ applyAdvertisedModelState(acpx, models);
4701
+ record.acpx = acpx;
4527
4702
  }
4528
- async function removePublishedLock(filePath, payload) {
4529
- const current = await readLock(filePath);
4530
- if (current?.payload === payload) await removeObservedLock(filePath, current);
4703
+ //#endregion
4704
+ //#region src/session/model-application.ts
4705
+ function currentModelIdFromSetModelResponse(response, fallbackModelId) {
4706
+ return modelStateFromConfigOptions(response?.configOptions)?.currentModelId ?? fallbackModelId;
4531
4707
  }
4532
- async function rejectAcquisition(filePath, receipt, error) {
4533
- const failures = [error];
4534
- await disposeTurn(filePath, receipt).catch((cleanupError) => {
4535
- failures.push(...cleanupError instanceof AggregateError ? cleanupError.errors : [cleanupError]);
4708
+ async function applyRequestedModelIfAdvertised(params) {
4709
+ const requestedModel = typeof params.requestedModel === "string" ? params.requestedModel.trim() : "";
4710
+ if (!requestedModel) return { applied: false };
4711
+ const warning = assertRequestedModelSupported({
4712
+ requestedModel,
4713
+ models: params.models,
4714
+ agentCommand: params.agentCommand,
4715
+ context: "apply"
4536
4716
  });
4537
- if (failures.length > 1) throw new AggregateError(failures, "Session turn acquisition and cleanup failed");
4538
- throw error;
4539
- }
4540
- async function releaseTurnGuard(receipt) {
4541
- await receipt.guard?.release();
4542
- receipt.guard = void 0;
4717
+ if (warning) params.onWarning?.(warning);
4718
+ if (!params.models) return { applied: false };
4719
+ const resolvedModelId = resolveRequestedModelId({
4720
+ ...params,
4721
+ requestedModel
4722
+ });
4723
+ if (params.models.currentModelId === resolvedModelId) return {
4724
+ applied: true,
4725
+ modelId: requestedModel,
4726
+ resolvedModelId
4727
+ };
4728
+ return {
4729
+ applied: true,
4730
+ modelId: requestedModel,
4731
+ resolvedModelId,
4732
+ response: await withTimeout(params.client.setSessionModel(params.sessionId, requestedModel, params.models, params.authority), params.timeoutMs)
4733
+ };
4543
4734
  }
4544
- async function removeTurnMarker(filePath, receipt) {
4545
- if (!receipt.removeMarker) return true;
4546
- if (receipt.guard && !await receipt.guard.verifyStillHeld()) await releaseTurnGuard(receipt);
4547
- receipt.guard ??= await tryAcquireGuard(filePath, receipt.owner);
4548
- if (!receipt.guard) return false;
4549
- await receipt.removeMarker();
4550
- receipt.removeMarker = void 0;
4551
- return true;
4735
+ //#endregion
4736
+ //#region src/session/config-options.ts
4737
+ function applyConfigOptionsToState(state, configOptions) {
4738
+ const acpxState = cloneSessionAcpxState(state) ?? {};
4739
+ applyConfigOptionsModelState(acpxState, configOptions);
4740
+ return acpxState;
4552
4741
  }
4553
- async function settleTurn(filePath, receipt) {
4554
- if (receipt.settled) return true;
4555
- const pending = receipt.pending ??= (async () => {
4556
- receipt.failed = false;
4557
- const failures = [];
4558
- try {
4559
- if (!await removeTurnMarker(filePath, receipt)) return false;
4560
- } catch (error) {
4561
- failures.push(error);
4562
- }
4563
- if (failures.length === 0 || !receipt.admitted) try {
4564
- await releaseTurnGuard(receipt);
4565
- } catch (error) {
4566
- failures.push(error);
4567
- }
4568
- if (failures.length > 1) throw new AggregateError(failures, "Session turn cleanup failed");
4569
- if (failures.length === 1) throw failures[0];
4570
- receipt.settled = true;
4571
- localTurns.delete(filePath);
4572
- return true;
4573
- })();
4574
- try {
4575
- return await pending;
4576
- } finally {
4577
- if (receipt.pending === pending) {
4578
- receipt.pending = void 0;
4579
- receipt.failed = !receipt.settled;
4580
- }
4581
- }
4742
+ function applyConfigOptionsToRecord(record, result) {
4743
+ const configOptions = result?.configOptions;
4744
+ if (!Array.isArray(configOptions)) return;
4745
+ record.acpx = applyConfigOptionsToState(record.acpx, configOptions);
4582
4746
  }
4583
- async function disposeTurn(filePath, receipt) {
4584
- if (!await settleTurn(filePath, receipt)) throw new Error(`Session turn cleanup is waiting for its guard: ${filePath}`);
4747
+ function applyInitialModelSelection(record, originalModels, modelApplication) {
4748
+ applyConfigOptionsToRecord(record, modelApplication.response);
4749
+ const configOptions = modelApplication.response?.configOptions;
4750
+ syncAdvertisedModelState(record, Array.isArray(configOptions) ? modelStateFromConfigOptions(configOptions) : originalModels);
4751
+ if (modelApplication.applied) record.acpx = applyModelSelection(record.acpx, modelApplication.modelId, modelApplication.response, modelApplication.resolvedModelId);
4585
4752
  }
4586
- async function tryPublishLock(filePath, payload, owner, signal) {
4587
- for (;;) try {
4588
- await publishLock(filePath, payload, signal);
4589
- return true;
4590
- } catch (error) {
4591
- if (error.code !== "EEXIST") throw error;
4592
- if (!await recoverAbandonedLock(filePath, owner, signal)) return false;
4753
+ function applyAcceptedConfigOptions(state, response, selection) {
4754
+ const next = cloneSessionAcpxState(state) ?? {};
4755
+ if (!Array.isArray(response?.configOptions)) {
4756
+ const option = next.config_options?.find((entry) => entry.id === selection.configId);
4757
+ if (option) option.currentValue = selection.value;
4758
+ return next;
4593
4759
  }
4760
+ applyConfigOptionsModelState(next, response.configOptions);
4761
+ reconcileDesiredConfigOptions(next, response.configOptions);
4762
+ return next;
4594
4763
  }
4595
- async function tryAcquireTurnCleanup(filePath, payload, owner, signal) {
4596
- const previous = localTurns.get(filePath);
4597
- if (previous) {
4598
- if (previous.failed) await settleTurn(filePath, previous);
4599
- return;
4600
- }
4601
- const receipt = {
4602
- owner,
4603
- removeMarker: async () => await removePublishedLock(filePath, payload),
4604
- admitted: false,
4605
- failed: false,
4606
- settled: false
4764
+ function reconcileDesiredConfigOptions(state, configOptions) {
4765
+ if (!state.desired_config_options) return;
4766
+ const desiredEntries = [];
4767
+ for (const option of configOptions) if (typeof option.currentValue === "string" && Object.hasOwn(state.desired_config_options, option.id)) desiredEntries.push([option.id, option.currentValue]);
4768
+ if (desiredEntries.length > 0) state.desired_config_options = Object.fromEntries(desiredEntries);
4769
+ else delete state.desired_config_options;
4770
+ }
4771
+ function applyModelSelection(state, modelId, response, resolvedModelId = modelId) {
4772
+ const modelConfigId = advertisedModelState(state)?.configId;
4773
+ const next = applyAcceptedConfigOptions(state, response, {
4774
+ configId: modelConfigId,
4775
+ value: resolvedModelId
4776
+ });
4777
+ next.session_options = {
4778
+ ...next.session_options,
4779
+ model: modelId
4607
4780
  };
4608
- localTurns.set(filePath, receipt);
4609
- try {
4610
- receipt.guard = await tryAcquireGuard(filePath, owner, signal);
4611
- } catch (error) {
4612
- localTurns.delete(filePath);
4613
- throw error;
4614
- }
4615
- if (!receipt.guard) {
4616
- localTurns.delete(filePath);
4617
- return;
4618
- }
4619
- return receipt;
4781
+ next.current_model_id = currentModelIdFromSetModelResponse(response, resolvedModelId);
4782
+ clearDesiredConfigOption(next, modelConfigId ?? advertisedModelState(next)?.configId);
4783
+ return next;
4620
4784
  }
4621
- async function tryAcquireTurn(filePath, payload, owner, signal) {
4622
- const receipt = await tryAcquireTurnCleanup(filePath, payload, owner, signal);
4623
- if (!receipt) return;
4624
- try {
4625
- signal?.throwIfAborted();
4626
- if (!await tryPublishLock(filePath, payload, owner, signal)) {
4627
- await disposeTurn(filePath, receipt);
4628
- return;
4629
- }
4630
- const observed = await readLock(filePath);
4631
- if (observed?.payload !== payload) throw new Error(`Session turn ownership changed before admission: ${filePath}`);
4632
- signal?.throwIfAborted();
4633
- receipt.admitted = true;
4634
- receipt.removeMarker = async () => await removeObservedLock(filePath, observed);
4635
- return { [Symbol.asyncDispose]: async () => await disposeTurn(filePath, receipt) };
4636
- } catch (error) {
4637
- return await rejectAcquisition(filePath, receipt, error);
4638
- }
4785
+ function isModelConfigSelection(configId, response, modelConfigId) {
4786
+ return configId === modelConfigId || configId === modelStateFromConfigOptions(response?.configOptions)?.configId;
4639
4787
  }
4640
- async function acquireSessionTurn(recordId, signal) {
4641
- const requestedPath = sessionEventLockPath(recordId);
4642
- await fs$1.mkdir(path.dirname(requestedPath), {
4643
- recursive: true,
4644
- mode: 448
4788
+ function applyConfigOptionSelection(state, configId, value, response, modelConfigId = advertisedModelState(state)?.configId, resolvedValue = value) {
4789
+ if (isModelConfigSelection(configId, response, modelConfigId)) return applyModelSelection(state, value, response, resolvedValue);
4790
+ const next = cloneSessionAcpxState(state) ?? {};
4791
+ next.desired_config_options = {
4792
+ ...next.desired_config_options,
4793
+ [configId]: value
4794
+ };
4795
+ return applyAcceptedConfigOptions(next, response, {
4796
+ configId,
4797
+ value
4645
4798
  });
4646
- const filePath = path.join(await fs$1.realpath(path.dirname(requestedPath)), path.basename(requestedPath));
4647
- const owner = await createLockOwner({ signal });
4648
- lastCreatedAt = Math.max(Date.now(), lastCreatedAt + 1);
4649
- const payload = `${JSON.stringify({
4650
- ...owner.payload,
4651
- created_at: new Date(lastCreatedAt).toISOString()
4652
- }, null, 2)}\n`;
4653
- for (;;) {
4654
- signal?.throwIfAborted();
4655
- const turn = await tryAcquireTurn(filePath, payload, owner, signal);
4656
- if (turn) return turn;
4657
- await setTimeout$1(LOCK_RETRY_MS, void 0, { signal });
4658
- }
4659
4799
  }
4660
4800
  //#endregion
4661
4801
  //#region src/session/execution/direct-lifetime.ts
@@ -4754,13 +4894,15 @@ async function replayDesiredModel(params) {
4754
4894
  context: "replay"
4755
4895
  }), params.suppressWarnings);
4756
4896
  if (!params.models) return { replayed: false };
4897
+ const resolvedModelId = resolveRequestedModelId({
4898
+ requestedModel: params.desiredModelId,
4899
+ models: params.models,
4900
+ agentCommand: params.record.agentCommand
4901
+ });
4757
4902
  const response = await withTimeout(params.client.setSessionModel(params.sessionId, params.desiredModelId, params.models, params.replay.authority), params.timeoutMs);
4758
4903
  params.replay.acknowledged = true;
4759
- params.record.acpx = applyModelSelection(params.record.acpx, params.desiredModelId, response);
4760
- const models = response ? modelStateFromConfigOptions(response.configOptions) : {
4761
- ...params.models,
4762
- currentModelId: params.desiredModelId
4763
- };
4904
+ params.record.acpx = applyModelSelection(params.record.acpx, params.desiredModelId, response, resolvedModelId);
4905
+ const models = advertisedModelState(params.record.acpx);
4764
4906
  if (params.verbose) process.stderr.write(`[acpx] replayed desired model ${params.desiredModelId} on ACP session ${params.sessionId} (previous ${params.previousSessionId})\n`);
4765
4907
  return {
4766
4908
  replayed: true,
@@ -4785,13 +4927,20 @@ async function replayDesiredConfigOptions(params) {
4785
4927
  for (const [configId, value] of Object.entries(params.desiredConfigOptions)) {
4786
4928
  if (configId === replacingKey || acceptedConfigOptions && !acceptsSavedConfigValue(acceptedConfigOptions.find((option) => option.id === configId), value)) continue;
4787
4929
  try {
4788
- const response = await withTimeout(params.client.setSessionConfigOption(params.sessionId, configId, value, advertisedModelState(params.record.acpx), params.replay.authority), params.timeoutMs);
4930
+ const models = advertisedModelState(params.record.acpx);
4931
+ const { modelConfigId, resolvedValue } = resolveRequestedConfigOption({
4932
+ configId,
4933
+ value,
4934
+ models,
4935
+ agentCommand: params.record.agentCommand
4936
+ });
4937
+ const response = await withTimeout(params.client.setSessionConfigOption(params.sessionId, configId, value, models, params.replay.authority), params.timeoutMs);
4789
4938
  params.replay.acknowledged = true;
4790
- params.record.acpx = applyConfigOptionSelection(params.record.acpx, configId, value, response);
4791
- acceptedConfigOptions = response.configOptions;
4939
+ params.record.acpx = applyConfigOptionSelection(params.record.acpx, configId, value, response, modelConfigId, resolvedValue);
4940
+ acceptedConfigOptions = params.record.acpx.config_options;
4792
4941
  result = {
4793
4942
  replayed: true,
4794
- models: modelStateFromConfigOptions(response.configOptions)
4943
+ models: advertisedModelState(params.record.acpx)
4795
4944
  };
4796
4945
  if (params.verbose) process.stderr.write(`[acpx] replayed desired config option ${configId} on ACP session ${params.sessionId} (previous ${params.previousSessionId})\n`);
4797
4946
  } catch (error) {
@@ -4823,7 +4972,7 @@ async function connectAndLoadSession(options) {
4823
4972
  logReconnectAttempt(record, storedProcessAlive, Boolean(record.pid) && !storedProcessAlive, options.verbose);
4824
4973
  const reusingLoadedSession = client.hasReusableSession(record.acpSessionId);
4825
4974
  if (reusingLoadedSession) incrementPerfCounter("runtime.connect_and_load.reused_session");
4826
- else await withTimeout(client.start(options.authority), options.timeoutMs);
4975
+ else await withTimeout(client.start(options.authority, { sessionOptions: mergeSessionOptions(options.sessionOptions, sessionOptionsFromRecord(record)) }), options.timeoutMs);
4827
4976
  assertControlAuthority(options.authority);
4828
4977
  options.onClientAvailable?.(options.activeController);
4829
4978
  applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
@@ -5240,8 +5389,13 @@ async function runSessionSetModelDirect(options) {
5240
5389
  replacingConfigOption: { key: "model" }
5241
5390
  }, async ({ client, sessionId, record }) => {
5242
5391
  const models = advertisedModelState(record.acpx);
5392
+ const resolvedModelId = resolveRequestedModelId({
5393
+ requestedModel: options.modelId,
5394
+ models,
5395
+ agentCommand: record.agentCommand
5396
+ });
5243
5397
  const response = await withTimeout(client.setSessionModel(sessionId, options.modelId, models), options.timeoutMs);
5244
- record.acpx = applyModelSelection(record.acpx, options.modelId, response);
5398
+ record.acpx = applyModelSelection(record.acpx, options.modelId, response, resolvedModelId);
5245
5399
  return response;
5246
5400
  }));
5247
5401
  return {
@@ -5255,8 +5409,14 @@ async function runSessionSetConfigOptionDirect(options) {
5255
5409
  replacingConfigOption: { key: options.configId }
5256
5410
  }, async ({ client, sessionId, record }) => {
5257
5411
  const models = advertisedModelState(record.acpx);
5412
+ const { modelConfigId, resolvedValue } = resolveRequestedConfigOption({
5413
+ configId: options.configId,
5414
+ value: options.value,
5415
+ models,
5416
+ agentCommand: record.agentCommand
5417
+ });
5258
5418
  const response = await withTimeout(client.setSessionConfigOption(sessionId, options.configId, options.value, models), options.timeoutMs);
5259
- record.acpx = applyConfigOptionSelection(record.acpx, options.configId, options.value, response, models?.configId);
5419
+ record.acpx = applyConfigOptionSelection(record.acpx, options.configId, options.value, response, modelConfigId, resolvedValue);
5260
5420
  return response;
5261
5421
  }));
5262
5422
  return {
@@ -5402,7 +5562,11 @@ async function setSessionModelOnOwner(options) {
5402
5562
  if (!submittedToOwner) return;
5403
5563
  const record = await resolveSessionRecord(options.sessionId);
5404
5564
  if (!submittedToOwner.persistsControlState) {
5405
- record.acpx = applyModelSelection(record.acpx, options.modelId, submittedToOwner.value.response);
5565
+ record.acpx = applyModelSelection(record.acpx, options.modelId, submittedToOwner.value.response, resolveRequestedModelId({
5566
+ requestedModel: options.modelId,
5567
+ models: advertisedModelState(record.acpx),
5568
+ agentCommand: record.agentCommand
5569
+ }));
5406
5570
  await writeSessionRecord(record);
5407
5571
  }
5408
5572
  return {
@@ -5433,7 +5597,14 @@ async function setSessionConfigOptionOnOwner(options) {
5433
5597
  if (!ownerResponse) return;
5434
5598
  const record = await resolveSessionRecord(options.sessionId);
5435
5599
  if (!ownerResponse.persistsControlState) {
5436
- record.acpx = applyConfigOptionSelection(record.acpx, options.configId, options.value, ownerResponse.value);
5600
+ const models = advertisedModelState(record.acpx);
5601
+ const { modelConfigId, resolvedValue } = resolveRequestedConfigOption({
5602
+ configId: options.configId,
5603
+ value: options.value,
5604
+ models,
5605
+ agentCommand: record.agentCommand
5606
+ });
5607
+ record.acpx = applyConfigOptionSelection(record.acpx, options.configId, options.value, ownerResponse.value, modelConfigId, resolvedValue);
5437
5608
  await writeSessionRecord(record);
5438
5609
  }
5439
5610
  return {
@@ -5551,7 +5722,7 @@ async function closeSession(sessionId) {
5551
5722
  }
5552
5723
  //#endregion
5553
5724
  //#region src/session/execution/session-management.ts
5554
- async function createSessionRecordWithClient(client, options) {
5725
+ async function createSessionRecordWithClient(client, options, resumedRecordId) {
5555
5726
  const cwd = absolutePath(options.cwd);
5556
5727
  await withTimeout(client.start({ signal: options.signal }), options.timeoutMs);
5557
5728
  assertControlAuthority({ signal: options.signal });
@@ -5561,7 +5732,7 @@ async function createSessionRecordWithClient(client, options) {
5561
5732
  const lifecycle = client.getAgentLifecycleSnapshot();
5562
5733
  const record = {
5563
5734
  ...createInitialSessionRecord({
5564
- recordId: sessionId,
5735
+ recordId: resumedRecordId ?? randomUUID(),
5565
5736
  sessionId,
5566
5737
  agentSessionId,
5567
5738
  agentCommand: options.agentCommand,
@@ -5577,7 +5748,7 @@ async function createSessionRecordWithClient(client, options) {
5577
5748
  };
5578
5749
  persistSessionOptions(record, options.sessionOptions);
5579
5750
  applyConfigOptionsToRecord(record, createdState.sessionResult);
5580
- applyInitialModelSelection(record, createdState.sessionResult.models, options.sessionOptions?.model, createdState.modelApplication);
5751
+ applyInitialModelSelection(record, createdState.sessionResult.models, createdState.modelApplication);
5581
5752
  await writeSessionRecord(record);
5582
5753
  assertControlAuthority({ signal: options.signal });
5583
5754
  return record;
@@ -5632,6 +5803,20 @@ async function resumeSessionRecordWithClient(client, options, cwd) {
5632
5803
  }
5633
5804
  async function createSessionWithClient(options) {
5634
5805
  assertControlAuthority({ signal: options.signal });
5806
+ let resumedRecordId;
5807
+ if (options.resumeSessionId) {
5808
+ const resumedRecord = await readSessionRecord(options.resumeSessionId);
5809
+ assertControlAuthority({ signal: options.signal });
5810
+ if (resumedRecord && resumedRecord.agentCommand === options.agentCommand) {
5811
+ resumedRecordId = resumedRecord.acpxRecordId;
5812
+ if (!resumedRecord.closed) await closeSession(resumedRecord.acpxRecordId);
5813
+ options = {
5814
+ ...options,
5815
+ resumeSessionId: resumedRecord.acpSessionId
5816
+ };
5817
+ assertControlAuthority({ signal: options.signal });
5818
+ }
5819
+ }
5635
5820
  const client = new AcpClient({
5636
5821
  agentCommand: options.agentCommand,
5637
5822
  agentArgv: options.agentArgv,
@@ -5651,7 +5836,7 @@ async function createSessionWithClient(options) {
5651
5836
  try {
5652
5837
  const create = async () => {
5653
5838
  assertControlAuthority({ signal: options.signal });
5654
- const record = await createSessionRecordWithClient(client, options);
5839
+ const record = await createSessionRecordWithClient(client, options, resumedRecordId);
5655
5840
  assertControlAuthority({ signal: options.signal });
5656
5841
  return record;
5657
5842
  };
@@ -5719,11 +5904,11 @@ async function listAgentSessions(options) {
5719
5904
  }
5720
5905
  async function ensureSession(options) {
5721
5906
  const cwd = absolutePath(options.cwd);
5722
- const ownership = await acquireSessionTurn(`ensure:${createHash("sha256").update(JSON.stringify([
5723
- options.agentCommand,
5907
+ const ownership = await acquireSessionScope({
5908
+ agentCommand: options.agentCommand,
5724
5909
  cwd,
5725
- normalizeName(options.name)
5726
- ])).digest("hex")}`, options.signal);
5910
+ name: normalizeName(options.name)
5911
+ }, options.signal);
5727
5912
  try {
5728
5913
  return await ensureSessionWithOwnership(options, cwd);
5729
5914
  } finally {
@@ -6133,14 +6318,25 @@ function createOwnedSessionControls(options) {
6133
6318
  },
6134
6319
  setSessionModel: async (modelId, deadline) => {
6135
6320
  const models = advertisedModelState(options.record.acpx);
6321
+ const resolvedModelId = resolveRequestedModelId({
6322
+ requestedModel: modelId,
6323
+ models,
6324
+ agentCommand: options.record.agentCommand
6325
+ });
6136
6326
  return await acceptControl(options, (authority) => options.client.setSessionModel(options.sessionId(), modelId, models, authority), (response) => {
6137
- options.record.acpx = applyModelSelection(options.record.acpx, modelId, response);
6327
+ options.record.acpx = applyModelSelection(options.record.acpx, modelId, response, resolvedModelId);
6138
6328
  }, deadline);
6139
6329
  },
6140
6330
  setSessionConfigOption: async (configId, value, deadline) => {
6141
6331
  const models = advertisedModelState(options.record.acpx);
6332
+ const { modelConfigId, resolvedValue } = resolveRequestedConfigOption({
6333
+ configId,
6334
+ value,
6335
+ models,
6336
+ agentCommand: options.record.agentCommand
6337
+ });
6142
6338
  return await acceptControl(options, (authority) => options.client.setSessionConfigOption(options.sessionId(), configId, value, models, authority), (response) => {
6143
- options.record.acpx = applyConfigOptionSelection(options.record.acpx, configId, value, response, models?.configId);
6339
+ options.record.acpx = applyConfigOptionSelection(options.record.acpx, configId, value, response, modelConfigId, resolvedValue);
6144
6340
  }, deadline);
6145
6341
  }
6146
6342
  };
@@ -6503,7 +6699,12 @@ var LiveSessionCheckpoint = class {
6503
6699
  async flushDirty() {
6504
6700
  while (this.dirty) {
6505
6701
  this.dirty = false;
6506
- await this.save();
6702
+ try {
6703
+ await this.save();
6704
+ } catch (error) {
6705
+ this.dirty = true;
6706
+ throw error;
6707
+ }
6507
6708
  }
6508
6709
  }
6509
6710
  };
@@ -6557,8 +6758,8 @@ var QueueTaskOutputFormatter = class {
6557
6758
  flush() {}
6558
6759
  };
6559
6760
  function markOutputAlreadyEmitted(error, outputAlreadyEmitted) {
6560
- if (!outputAlreadyEmitted || !error || typeof error !== "object") return;
6561
- error.outputAlreadyEmitted = true;
6761
+ if (!error || typeof error !== "object") return;
6762
+ error.outputAlreadyEmitted = outputAlreadyEmitted;
6562
6763
  }
6563
6764
  async function runWithOptionalInterrupt(options) {
6564
6765
  if (options.handleProcessInterrupts === false) return await options.run();
@@ -6601,11 +6802,17 @@ var AcpErrorTracker = class {
6601
6802
  return this.latestInbound ?? this.outbound.findLast((acp) => outboundAcpErrorMatches(error, acp));
6602
6803
  }
6603
6804
  };
6604
- function toPromptResult(stopReason, sessionId, client, meta) {
6805
+ function toPromptResult(stopReason, sessionId, client, before, meta) {
6806
+ const after = client.getPermissionStats();
6605
6807
  return {
6606
6808
  stopReason,
6607
6809
  sessionId,
6608
- permissionStats: client.getPermissionStats(),
6810
+ permissionStats: {
6811
+ requested: after.requested - before.requested,
6812
+ approved: after.approved - before.approved,
6813
+ denied: after.denied - before.denied,
6814
+ cancelled: after.cancelled - before.cancelled
6815
+ },
6609
6816
  ...meta === void 0 ? {} : { _meta: meta }
6610
6817
  };
6611
6818
  }
@@ -6625,62 +6832,39 @@ async function applyPromptModelIfAdvertised(params) {
6625
6832
  authority: { signal: params.signal },
6626
6833
  onWarning: params.suppressWarnings ? void 0 : (message) => process.stderr.write(`[acpx] warning: ${message}\n`)
6627
6834
  });
6628
- if (result.applied) params.record.acpx = applyModelSelection(params.record.acpx, requestedModel, result.response);
6835
+ if (result.applied) params.record.acpx = applyModelSelection(params.record.acpx, requestedModel, result.response, result.resolvedModelId);
6629
6836
  }
6630
- function jsonRpcIdKey(value) {
6837
+ function jsonRpcIdKey(message) {
6838
+ if (!("id" in message)) return;
6839
+ const value = message.id;
6631
6840
  if (typeof value === "string") return `s:${value}`;
6632
6841
  if (typeof value === "number" && Number.isFinite(value)) return `n:${value}`;
6633
6842
  }
6634
- function extractJsonRpcRequestInfo(message) {
6635
- const candidate = message;
6636
- if (typeof candidate.method !== "string") return;
6637
- const idKey = jsonRpcIdKey(candidate.id);
6638
- if (!idKey) return;
6639
- return {
6640
- idKey,
6641
- method: candidate.method
6642
- };
6643
- }
6644
- function extractJsonRpcResponseInfo(message) {
6645
- const candidate = message;
6646
- const idKey = jsonRpcIdKey(candidate.id);
6647
- if (!idKey) return;
6648
- const hasError = Object.hasOwn(candidate, "error");
6649
- const hasResult = Object.hasOwn(candidate, "result");
6650
- if (!hasError && !hasResult) return;
6651
- return {
6652
- idKey,
6653
- hasError
6654
- };
6655
- }
6656
6843
  const SESSION_RECONNECT_METHODS = /* @__PURE__ */ new Set(["session/load", "session/resume"]);
6657
- function filterRecoverableLoadFallbackOutput(messages) {
6658
- const requestMethodById = /* @__PURE__ */ new Map();
6659
- const failedLoadRequestIds = /* @__PURE__ */ new Set();
6660
- for (const message of messages) {
6661
- const request = extractJsonRpcRequestInfo(message);
6662
- if (request) {
6663
- requestMethodById.set(request.idKey, request.method);
6664
- continue;
6665
- }
6666
- const response = extractJsonRpcResponseInfo(message);
6667
- if (!response || !response.hasError) continue;
6668
- const requestMethod = requestMethodById.get(response.idKey);
6669
- if (requestMethod && SESSION_RECONNECT_METHODS.has(requestMethod)) failedLoadRequestIds.add(response.idKey);
6670
- }
6671
- if (failedLoadRequestIds.size === 0) return messages;
6672
- return messages.filter((message) => {
6673
- const request = extractJsonRpcRequestInfo(message);
6674
- if (request && SESSION_RECONNECT_METHODS.has(request.method) && failedLoadRequestIds.has(request.idKey)) return false;
6675
- const response = extractJsonRpcResponseInfo(message);
6676
- if (response && failedLoadRequestIds.has(response.idKey)) return false;
6677
- return true;
6678
- });
6844
+ function isReconnectRequest(direction, message) {
6845
+ return direction === "outbound" && "method" in message && typeof message.method === "string" && SESSION_RECONNECT_METHODS.has(message.method);
6846
+ }
6847
+ function isInboundResponse(direction, message) {
6848
+ return direction === "inbound" && ("result" in message || "error" in message);
6679
6849
  }
6680
6850
  function filterBufferedConnectOutput(messages, loadError) {
6681
6851
  if (loadError == null) return messages;
6682
- const filteredMessages = new Set(filterRecoverableLoadFallbackOutput(messages.map(({ message }) => message)));
6683
- return messages.filter(({ message }) => filteredMessages.has(message));
6852
+ const pending = /* @__PURE__ */ new Map();
6853
+ const suppressed = /* @__PURE__ */ new Set();
6854
+ for (const [index, { direction, message }] of messages.entries()) {
6855
+ const id = jsonRpcIdKey(message);
6856
+ if (!id) continue;
6857
+ if (isReconnectRequest(direction, message)) pending.set(id, index);
6858
+ else if (isInboundResponse(direction, message)) {
6859
+ const requestIndex = pending.get(id);
6860
+ if (requestIndex !== void 0 && "error" in message) {
6861
+ suppressed.add(requestIndex);
6862
+ suppressed.add(index);
6863
+ }
6864
+ pending.delete(id);
6865
+ }
6866
+ }
6867
+ return messages.filter((_message, index) => !suppressed.has(index));
6684
6868
  }
6685
6869
  function emitPromptRetryNotice(params) {
6686
6870
  if (params.suppressSdkConsoleErrors) return;
@@ -6744,7 +6928,7 @@ function buildQueuedTaskRunOptions(sessionRecordId, task, options, outputFormatt
6744
6928
  suppressSdkConsoleErrors: task.suppressSdkConsoleErrors ?? options.suppressSdkConsoleErrors,
6745
6929
  verbose: options.verbose,
6746
6930
  promptRetries: task.promptRetries ?? options.promptRetries ?? 0,
6747
- sessionOptions: mergeSessionOptions(task.sessionOptions, options.sessionOptions),
6931
+ sessionOptions: task.sessionOptions,
6748
6932
  onClientAvailable: options.onClientAvailable,
6749
6933
  onClientClosed: options.onClientClosed,
6750
6934
  onClientCloseFailure: options.onClientCloseFailure,
@@ -7016,6 +7200,7 @@ async function runOwnedSessionPrompt(options) {
7016
7200
  verbose: options.verbose,
7017
7201
  sessionOptions
7018
7202
  });
7203
+ const permissionStatsBefore = client.getPermissionStats();
7019
7204
  client.updateRuntimeOptions({
7020
7205
  permissionMode: options.permissionMode,
7021
7206
  nonInteractivePermissions: options.nonInteractivePermissions,
@@ -7094,6 +7279,7 @@ async function runOwnedSessionPrompt(options) {
7094
7279
  client,
7095
7280
  record,
7096
7281
  resumePolicy: options.resumePolicy,
7282
+ sessionOptions: options.sessionOptions,
7097
7283
  replacingConfigOption: requestedModelId(options.sessionOptions?.model) ? { key: "model" } : void 0,
7098
7284
  timeoutMs: options.timeoutMs,
7099
7285
  verbose: options.verbose,
@@ -7171,7 +7357,7 @@ async function runOwnedSessionPrompt(options) {
7171
7357
  applyConversation(record, conversation);
7172
7358
  const propagated = error instanceof Error ? error : new Error(formatErrorMessage(error));
7173
7359
  attachAcpErrorPayload(propagated, normalizedError.acp);
7174
- propagated.outputAlreadyEmitted = matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted;
7360
+ markOutputAlreadyEmitted(propagated, matchedAcpError !== void 0 && shouldMarkAcpErrorsEmitted);
7175
7361
  propagated.normalizedOutputError = normalizedError;
7176
7362
  throw propagated;
7177
7363
  };
@@ -7198,8 +7384,10 @@ async function runOwnedSessionPrompt(options) {
7198
7384
  record.lastUsedAt = now;
7199
7385
  record.closed = false;
7200
7386
  record.closedAt = void 0;
7201
- record.protocolVersion = client.initializeResult?.protocolVersion;
7202
- record.agentCapabilities = client.initializeResult?.agentCapabilities;
7387
+ if (client.initializeResult) {
7388
+ record.protocolVersion = client.initializeResult.protocolVersion;
7389
+ record.agentCapabilities = client.initializeResult.agentCapabilities;
7390
+ }
7203
7391
  applyConversation(record, conversation);
7204
7392
  applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
7205
7393
  stopTotalTimer();
@@ -7209,22 +7397,27 @@ async function runOwnedSessionPrompt(options) {
7209
7397
  options.ownedSignal?.throwIfAborted();
7210
7398
  const { sessionId: activeSessionId, resumed, loadError } = await connectForPrompt();
7211
7399
  options.ownedSignal?.throwIfAborted();
7212
- await applyPromptModelIfAdvertised({
7213
- client,
7214
- sessionId: activeSessionId,
7215
- requestedModel: options.sessionOptions?.model,
7216
- record,
7217
- timeoutMs: options.timeoutMs,
7218
- suppressWarnings: options.suppressSdkConsoleErrors,
7219
- signal: options.ownedSignal
7220
- });
7400
+ try {
7401
+ await applyPromptModelIfAdvertised({
7402
+ client,
7403
+ sessionId: activeSessionId,
7404
+ requestedModel: options.sessionOptions?.model,
7405
+ record,
7406
+ timeoutMs: options.timeoutMs,
7407
+ suppressWarnings: options.suppressSdkConsoleErrors,
7408
+ signal: options.ownedSignal
7409
+ });
7410
+ } catch (error) {
7411
+ if (error instanceof TimeoutError) closeClientOnExit = true;
7412
+ throw error;
7413
+ }
7221
7414
  options.ownedSignal?.throwIfAborted();
7222
7415
  output.setContext({ sessionId: record.acpxRecordId });
7223
7416
  await liveCheckpoint.checkpoint();
7224
7417
  const response = await savePromptSuccess(await runPromptWithRetries(activeSessionId));
7225
7418
  promptTurnActive = false;
7226
7419
  return {
7227
- ...toPromptResult(response.stopReason, record.acpxRecordId, client, response._meta),
7420
+ ...toPromptResult(response.stopReason, record.acpxRecordId, client, permissionStatsBefore, response._meta),
7228
7421
  record,
7229
7422
  resumed,
7230
7423
  loadError
@@ -7310,6 +7503,7 @@ async function runOnce(options, control) {
7310
7503
  let promptTurnActive = false;
7311
7504
  let promptTurnHadSideEffects = false;
7312
7505
  let controlState = {};
7506
+ let sawOwnedModelConfig = false;
7313
7507
  const acpErrors = new AcpErrorTracker();
7314
7508
  const client = new AcpClient({
7315
7509
  agentCommand: options.agentCommand,
@@ -7330,7 +7524,10 @@ async function runOnce(options, control) {
7330
7524
  acpErrors.observe(output, direction, message);
7331
7525
  },
7332
7526
  onSessionUpdate: (notification) => {
7333
- if (notification.update.sessionUpdate === "config_option_update") controlState = applyConfigOptionsToState(controlState, notification.update.configOptions);
7527
+ if (notification.update.sessionUpdate === "config_option_update" && client.hasReusableSession(notification.sessionId)) {
7528
+ controlState = applyConfigOptionsToState(controlState, notification.update.configOptions);
7529
+ sawOwnedModelConfig ||= controlState.model_control === "config_option";
7530
+ }
7334
7531
  if (promptTurnActive) promptTurnHadSideEffects = true;
7335
7532
  options.onSessionUpdate?.(notification);
7336
7533
  },
@@ -7344,7 +7541,13 @@ async function runOnce(options, control) {
7344
7541
  },
7345
7542
  sessionOptions: options.sessionOptions
7346
7543
  });
7544
+ const permissionStatsBefore = client.getPermissionStats();
7347
7545
  const closeOwnedClient = ownDirectClient(client, control?.signal);
7546
+ const initializeControlState = (createdSession) => {
7547
+ const configOptions = controlState.config_options ?? createdSession.configOptions ?? [];
7548
+ if (createdSession.models && !sawOwnedModelConfig) applyAdvertisedModelState(controlState, createdSession.models);
7549
+ controlState = applyConfigOptionsToState(controlState, configOptions);
7550
+ };
7348
7551
  const runExecPromptAttempt = async (sessionId) => {
7349
7552
  assertControlAuthority(authority);
7350
7553
  acpErrors.reset();
@@ -7377,29 +7580,34 @@ async function runOnce(options, control) {
7377
7580
  });
7378
7581
  assertControlAuthority(authority);
7379
7582
  const sessionId = createdSession.sessionId;
7380
- if (createdSession.models) applyAdvertisedModelState(controlState, createdSession.models);
7381
- controlState = applyConfigOptionsToState(controlState, createdSession.configOptions ?? []);
7583
+ initializeControlState(createdSession);
7382
7584
  const modelApplication = await applyRequestedModelIfAdvertised({
7383
7585
  client,
7384
7586
  sessionId,
7385
7587
  requestedModel: options.sessionOptions?.model,
7386
- models: createdSession.models,
7588
+ models: advertisedModelState(controlState),
7387
7589
  agentCommand: options.agentCommand,
7388
7590
  timeoutMs: options.timeoutMs,
7389
7591
  authority,
7390
7592
  onWarning: options.suppressSdkConsoleErrors ? void 0 : (message) => process.stderr.write(`[acpx] warning: ${message}\n`)
7391
7593
  });
7392
- if (modelApplication.response) controlState = applyConfigOptionsToState(controlState, modelApplication.response.configOptions);
7594
+ if (modelApplication.applied) controlState = applyModelSelection(controlState, modelApplication.modelId, modelApplication.response, modelApplication.resolvedModelId);
7393
7595
  for (const configOption of options.configOptions ?? []) {
7394
7596
  assertControlAuthority(authority);
7395
- const response = await withTimeout(client.setSessionConfigOption(sessionId, configOption.configId, configOption.value, advertisedModelState(controlState), authority), options.timeoutMs);
7396
- controlState = applyConfigOptionsToState(controlState, response.configOptions);
7597
+ const models = advertisedModelState(controlState);
7598
+ const { modelConfigId, resolvedValue } = resolveRequestedConfigOption({
7599
+ ...configOption,
7600
+ models,
7601
+ agentCommand: options.agentCommand
7602
+ });
7603
+ const response = await withTimeout(client.setSessionConfigOption(sessionId, configOption.configId, configOption.value, models, authority), options.timeoutMs);
7604
+ controlState = applyConfigOptionSelection(controlState, configOption.configId, configOption.value, response, modelConfigId, resolvedValue);
7397
7605
  }
7398
7606
  assertControlAuthority(authority);
7399
7607
  output.setContext({ sessionId });
7400
7608
  const response = await runExecPromptWithRetries(sessionId);
7401
7609
  promptTurnActive = false;
7402
- return toPromptResult(response.stopReason, sessionId, client, response._meta);
7610
+ return toPromptResult(response.stopReason, sessionId, client, permissionStatsBefore, response._meta);
7403
7611
  },
7404
7612
  handleInterrupt: async () => {
7405
7613
  await client.cancelActivePrompt(INTERRUPT_CANCEL_WAIT_MS);
@@ -7626,31 +7834,50 @@ function createLeaseHeartbeat(lease, shutdown) {
7626
7834
  };
7627
7835
  }
7628
7836
  async function runSessionQueueOwner(options) {
7629
- const lease = await tryAcquireQueueOwnerLease(options.sessionId, {
7630
- path: options.mcpConfigPath,
7631
- fingerprint: options.mcpConfigFingerprint
7632
- });
7633
- if (!lease) return;
7634
- let runtimeFailure;
7635
- try {
7636
- await runQueueOwnerRuntime(options, lease);
7637
- } catch (error) {
7638
- runtimeFailure = { error };
7639
- }
7837
+ const signals = [
7838
+ "SIGINT",
7839
+ "SIGTERM",
7840
+ "SIGHUP"
7841
+ ];
7842
+ let stopRequested = false;
7843
+ let requestRuntimeStop;
7844
+ const onSignal = () => {
7845
+ stopRequested = true;
7846
+ requestRuntimeStop?.();
7847
+ };
7640
7848
  try {
7641
- await releaseQueueOwnerLease(lease);
7642
- } catch (error) {
7643
- if (runtimeFailure) throw new AggregateError([runtimeFailure.error, error], "Queue owner shutdown failed", { cause: error });
7644
- throw error;
7849
+ for (const signal of signals) process.on(signal, onSignal);
7850
+ const lease = await tryAcquireQueueOwnerLease(options.sessionId, {
7851
+ path: options.mcpConfigPath,
7852
+ fingerprint: options.mcpConfigFingerprint
7853
+ });
7854
+ if (!lease) return;
7855
+ let runtimeFailure;
7856
+ try {
7857
+ await runQueueOwnerRuntime(options, lease, (requestStop) => {
7858
+ requestRuntimeStop = requestStop;
7859
+ if (stopRequested) requestStop();
7860
+ });
7861
+ } catch (error) {
7862
+ runtimeFailure = { error };
7863
+ }
7864
+ try {
7865
+ await releaseQueueOwnerLease(lease);
7866
+ } catch (error) {
7867
+ if (runtimeFailure) throw new AggregateError([runtimeFailure.error, error], "Queue owner shutdown failed", { cause: error });
7868
+ throw error;
7869
+ }
7870
+ if (runtimeFailure) throw runtimeFailure.error;
7871
+ } finally {
7872
+ for (const signal of signals) process.off(signal, onSignal);
7645
7873
  }
7646
- if (runtimeFailure) throw runtimeFailure.error;
7647
7874
  }
7648
7875
  async function keepOwnerForPendingControls(controls, shutdown) {
7649
7876
  if (shutdown.requested || !controls.hasPending) return false;
7650
7877
  await controls.drainAdmitted();
7651
7878
  return !shutdown.requested;
7652
7879
  }
7653
- async function runQueueOwnerRuntime(options, lease) {
7880
+ async function runQueueOwnerRuntime(options, lease, bindStopRequest) {
7654
7881
  const sessionRecord = await resolveSessionRecord(options.sessionId);
7655
7882
  let owner;
7656
7883
  let heartbeatTimer;
@@ -7724,13 +7951,8 @@ async function runQueueOwnerRuntime(options, lease) {
7724
7951
  sessionId: options.sessionId,
7725
7952
  verbose: options.verbose
7726
7953
  });
7727
- const onSignal = () => {
7728
- shutdown.request();
7729
- };
7954
+ bindStopRequest(shutdown.request);
7730
7955
  const heartbeat = createLeaseHeartbeat(lease, shutdown);
7731
- process.once("SIGINT", onSignal);
7732
- process.once("SIGTERM", onSignal);
7733
- process.once("SIGHUP", onSignal);
7734
7956
  try {
7735
7957
  owner = await SessionQueueOwner.start(lease, {
7736
7958
  cancelPrompt: async () => {
@@ -7812,9 +8034,6 @@ async function runQueueOwnerRuntime(options, lease) {
7812
8034
  }
7813
8035
  }
7814
8036
  } finally {
7815
- process.off("SIGINT", onSignal);
7816
- process.off("SIGTERM", onSignal);
7817
- process.off("SIGHUP", onSignal);
7818
8037
  await shutdown.shutdown();
7819
8038
  await lease.updates;
7820
8039
  }
@@ -7858,6 +8077,6 @@ async function sendSession(options) {
7858
8077
  }
7859
8078
  const queueOwnerRuntimeTestInternals = { queueOwnerExitIsFatal };
7860
8079
  //#endregion
7861
- 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, getAcpxVersion as et, installPerfMetricsCapture as f, 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 };
8080
+ export { isRequestedModelUnsupportedError as $, applyInitialModelSelection as A, reconcileAgentSessionId as B, setSessionModeOnOwner as C, connectAndLoadSession as D, withConnectedSession as E, persistSessionOptions as F, recordSessionUpdate as G, createSessionConversation as H, sessionOptionsFromRecord as I, advertisedModelState as J, trimConversationForRuntime 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, recordClientOperation as U, cloneSessionConversation as V, recordPromptSubmission as W, REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE as X, AcpClient as Y, REQUESTED_MODEL_UNSUPPORTED_REASONS as Z, cancelSessionPrompt as _, runQueuedTask as a, normalizeQueueOwnerTtlMs as at, setSessionConfigOption as b, LiveSessionCheckpoint as c, listSessionEvents as ct, flushPerfMetricsCapture as d, TimeoutError as dt, resolveRequestedConfigOption as et, installPerfMetricsCapture as f, assertControlAuthority as ft, listAgentSessions as g, ensureSession as h, runOnce as i, DEFAULT_QUEUE_OWNER_TTL_MS as it, applyModelSelection as j, applyConfigOptionsToRecord as k, runPromptTurn as l, isLegacyZedCodexAcpInvocation as lt, createSessionWithClient as m, withTimeout as mt, runSessionQueueOwner as n, getAcpxVersion as nt, sendSessionDirect as o, acquireSessionImport as ot, createSession as p, withInterrupt as pt, cloneSessionAcpxState as q, sendSession as r, permissionModeSatisfies as rt, DISCARD_OUTPUT_FORMATTER as s, acquireSessionScope as st, queueOwnerRuntimeTestInternals as t, resolveRequestedModelId as tt, buildQueueOwnerArgOverride as u, InterruptedError as ut, closeSession as v, setSessionModel as w, setSessionConfigOptionOnOwner as x, sessionControlTestInternals as y, createInitialSessionRecord as z };
7862
8081
 
7863
- //# sourceMappingURL=queue-owner-runtime-W0lly6ap.js.map
8082
+ //# sourceMappingURL=queue-owner-runtime-C0wIYowh.js.map