acpx 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +7 -4
  2. package/dist/{cli-BGYGVo3b.js → cli-Bf3yjqzE.js} +4 -4
  3. package/dist/{cli-BGYGVo3b.js.map → cli-Bf3yjqzE.js.map} +1 -1
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +545 -247
  7. package/dist/cli.js.map +1 -1
  8. package/dist/{client-FzXPdgP7.d.ts → client-BssohYqM.d.ts} +30 -3
  9. package/dist/client-BssohYqM.d.ts.map +1 -0
  10. package/dist/{flags-D706STfk.js → flags-C-rwARqg.js} +96 -39
  11. package/dist/flags-C-rwARqg.js.map +1 -0
  12. package/dist/{flows-hcjHmU7P.js → flows-WLs26_5Y.js} +400 -335
  13. package/dist/flows-WLs26_5Y.js.map +1 -0
  14. package/dist/flows.d.ts +21 -1
  15. package/dist/flows.d.ts.map +1 -1
  16. package/dist/flows.js +1 -1
  17. package/dist/{live-checkpoint-B9ctAuqV.js → live-checkpoint-D5d-K9s1.js} +1355 -700
  18. package/dist/live-checkpoint-D5d-K9s1.js.map +1 -0
  19. package/dist/{output-BL9XRWzS.js → output-DPg20dvn.js} +1151 -717
  20. package/dist/output-DPg20dvn.js.map +1 -0
  21. package/dist/runtime.d.ts +30 -2
  22. package/dist/runtime.d.ts.map +1 -1
  23. package/dist/runtime.js +579 -425
  24. package/dist/runtime.js.map +1 -1
  25. package/dist/{session-options-BJyG6zEH.d.ts → session-options-CFudjdkU.d.ts} +7 -1
  26. package/dist/session-options-CFudjdkU.d.ts.map +1 -0
  27. package/package.json +15 -12
  28. package/skills/acpx/SKILL.md +11 -3
  29. package/dist/client-FzXPdgP7.d.ts.map +0 -1
  30. package/dist/flags-D706STfk.js.map +0 -1
  31. package/dist/flows-hcjHmU7P.js.map +0 -1
  32. package/dist/live-checkpoint-B9ctAuqV.js.map +0 -1
  33. package/dist/output-BL9XRWzS.js.map +0 -1
  34. package/dist/session-options-BJyG6zEH.d.ts.map +0 -1
@@ -79,6 +79,15 @@ var AgentDisconnectedError = class extends AcpxOperationalError {
79
79
  this.signal = signal;
80
80
  }
81
81
  };
82
+ var UnsupportedPromptContentError = class extends AcpxOperationalError {
83
+ constructor(message) {
84
+ super(message, {
85
+ outputCode: "USAGE",
86
+ detailCode: "UNSUPPORTED_PROMPT_CONTENT",
87
+ origin: "acp"
88
+ });
89
+ }
90
+ };
82
91
  var SessionResumeRequiredError = class extends AcpxOperationalError {
83
92
  constructor(message, options) {
84
93
  super(message, {
@@ -235,16 +244,15 @@ function extractAcpErrorInternal(value, depth) {
235
244
  if (direct) return direct;
236
245
  const record = asRecord$7(value);
237
246
  if (!record) return;
238
- if ("error" in record) {
239
- const nested = extractAcpErrorInternal(record.error, depth + 1);
240
- if (nested) return nested;
241
- }
242
- if ("acp" in record) {
243
- const nested = extractAcpErrorInternal(record.acp, depth + 1);
244
- if (nested) return nested;
245
- }
246
- if ("cause" in record) {
247
- const nested = extractAcpErrorInternal(record.cause, depth + 1);
247
+ return extractNestedAcpError(record, depth);
248
+ }
249
+ function extractNestedAcpError(record, depth) {
250
+ for (const key of [
251
+ "error",
252
+ "acp",
253
+ "cause"
254
+ ]) if (key in record) {
255
+ const nested = extractAcpErrorInternal(record[key], depth + 1);
248
256
  if (nested) return nested;
249
257
  }
250
258
  }
@@ -296,7 +304,15 @@ function asRecord$6(value) {
296
304
  function isAuthRequiredMessage(value) {
297
305
  if (!value) return false;
298
306
  const normalized = value.toLowerCase();
299
- return normalized.includes("auth required") || normalized.includes("authentication required") || normalized.includes("authorization required") || normalized.includes("credential required") || normalized.includes("credentials required") || normalized.includes("token required") || normalized.includes("login required");
307
+ return [
308
+ "auth required",
309
+ "authentication required",
310
+ "authorization required",
311
+ "credential required",
312
+ "credentials required",
313
+ "token required",
314
+ "login required"
315
+ ].some((needle) => normalized.includes(needle));
300
316
  }
301
317
  function isAcpAuthRequiredPayload(acp) {
302
318
  if (!acp) return false;
@@ -304,12 +320,16 @@ function isAcpAuthRequiredPayload(acp) {
304
320
  if (isAuthRequiredMessage(acp.message)) return true;
305
321
  const data = asRecord$6(acp.data);
306
322
  if (!data) return false;
307
- if (data.authRequired === true) return true;
308
- const methodId = data.methodId;
309
- if (typeof methodId === "string" && methodId.trim().length > 0) return true;
310
- const methods = data.methods;
311
- if (Array.isArray(methods) && methods.length > 0) return true;
312
- return false;
323
+ return hasAuthRequiredData(data);
324
+ }
325
+ function hasAuthRequiredData(data) {
326
+ return data.authRequired === true || hasNonEmptyString(data.methodId) || hasNonEmptyArray(data.methods);
327
+ }
328
+ function hasNonEmptyString(value) {
329
+ return typeof value === "string" && value.trim().length > 0;
330
+ }
331
+ function hasNonEmptyArray(value) {
332
+ return Array.isArray(value) && value.length > 0;
313
333
  }
314
334
  function isOutputErrorCode(value) {
315
335
  return typeof value === "string" && OUTPUT_ERROR_CODES.includes(value);
@@ -357,20 +377,25 @@ function mapErrorCode(error) {
357
377
  }
358
378
  function normalizeOutputError(error, options = {}) {
359
379
  const meta = readOutputErrorMeta(error);
360
- let code = mapErrorCode(error) ?? options.defaultCode ?? "RUNTIME";
361
- if (meta.outputCode) code = meta.outputCode;
362
- if (code === "RUNTIME" && isAcpResourceNotFoundError(error)) code = "NO_SESSION";
380
+ const code = resolveOutputErrorCode(error, options, meta);
363
381
  const acp = options.acp ?? meta.acp ?? extractAcpError(error);
364
- const detailCode = meta.detailCode ?? options.detailCode ?? (error instanceof AuthPolicyError || isAcpAuthRequiredPayload(acp) ? "AUTH_REQUIRED" : void 0);
365
382
  return {
366
383
  code,
367
384
  message: formatErrorMessage(error),
368
- detailCode,
385
+ detailCode: resolveDetailCode(error, acp, options, meta),
369
386
  origin: meta.origin ?? options.origin,
370
387
  retryable: meta.retryable ?? options.retryable,
371
388
  acp
372
389
  };
373
390
  }
391
+ function resolveOutputErrorCode(error, options, meta) {
392
+ const code = meta.outputCode ?? mapErrorCode(error) ?? options.defaultCode ?? "RUNTIME";
393
+ if (code === "RUNTIME" && isAcpResourceNotFoundError(error)) return "NO_SESSION";
394
+ return code;
395
+ }
396
+ function resolveDetailCode(error, acp, options, meta) {
397
+ return meta.detailCode ?? options.detailCode ?? (error instanceof AuthPolicyError || isAcpAuthRequiredPayload(acp) ? "AUTH_REQUIRED" : void 0);
398
+ }
374
399
  /**
375
400
  * Returns true when an error from `client.prompt()` looks transient and
376
401
  * can reasonably be retried (e.g. model-API 400/500, network hiccups that
@@ -380,15 +405,18 @@ function normalizeOutputError(error, options = {}) {
380
405
  * invalid params, timeout, permission) return false.
381
406
  */
382
407
  function isRetryablePromptError(error) {
383
- if (error instanceof PermissionDeniedError || error instanceof PermissionPromptUnavailableError) return false;
384
- if (isTimeoutLike(error) || isNoSessionLike(error) || isUsageLike(error)) return false;
408
+ if (isNonRetryablePromptError(error)) return false;
385
409
  const acp = extractAcpError(error);
386
410
  if (!acp) return false;
387
- if (acp.code === -32001 || acp.code === -32002) return false;
388
- if (isAcpAuthRequiredPayload(acp)) return false;
389
- if (acp.code === -32601 || acp.code === -32602) return false;
411
+ if (isPermanentPromptAcpError(acp)) return false;
390
412
  return acp.code === -32603 || acp.code === -32700;
391
413
  }
414
+ function isNonRetryablePromptError(error) {
415
+ return error instanceof PermissionDeniedError || error instanceof PermissionPromptUnavailableError || isTimeoutLike(error) || isNoSessionLike(error) || isUsageLike(error);
416
+ }
417
+ function isPermanentPromptAcpError(acp) {
418
+ return acp.code === -32001 || acp.code === -32002 || acp.code === -32601 || acp.code === -32602 || isAcpAuthRequiredPayload(acp);
419
+ }
392
420
  function exitCodeForOutputErrorCode(code) {
393
421
  switch (code) {
394
422
  case "USAGE": return EXIT_CODES.USAGE;
@@ -403,13 +431,13 @@ function exitCodeForOutputErrorCode(code) {
403
431
  //#region src/agent-registry.ts
404
432
  const ACP_ADAPTER_PACKAGE_RANGES = {
405
433
  pi: "^0.0.26",
406
- codex: "^0.12.0",
407
- claude: "^0.31.0"
434
+ codex: "^0.0.44",
435
+ claude: "^0.36.1"
408
436
  };
409
437
  const AGENT_REGISTRY = {
410
438
  pi: `npx pi-acp@${ACP_ADAPTER_PACKAGE_RANGES.pi}`,
411
439
  openclaw: "openclaw acp",
412
- codex: `npx @zed-industries/codex-acp@${ACP_ADAPTER_PACKAGE_RANGES.codex}`,
440
+ codex: `npx -y @agentclientprotocol/codex-acp@${ACP_ADAPTER_PACKAGE_RANGES.codex}`,
413
441
  claude: `npx -y @agentclientprotocol/claude-agent-acp@${ACP_ADAPTER_PACKAGE_RANGES.claude}`,
414
442
  gemini: "gemini --acp",
415
443
  cursor: "cursor-agent acp",
@@ -426,7 +454,7 @@ const AGENT_REGISTRY = {
426
454
  };
427
455
  const BUILT_IN_AGENT_PACKAGES = {
428
456
  codex: {
429
- packageName: "@zed-industries/codex-acp",
457
+ packageName: "@agentclientprotocol/codex-acp",
430
458
  packageRange: ACP_ADAPTER_PACKAGE_RANGES.codex,
431
459
  preferredBinName: "codex-acp",
432
460
  fallbackCommand: AGENT_REGISTRY.codex,
@@ -498,26 +526,37 @@ function resolveInstalledBuiltInAgentLaunch(agentCommand, options = {}) {
498
526
  const existsSync = options.existsSync ?? fs.existsSync;
499
527
  const resolvePackageRoot = options.resolvePackageRoot ?? defaultResolvePackageRoot;
500
528
  try {
501
- const packageRoot = resolvePackageRoot(spec.packageName);
502
- const manifest = JSON.parse(readFileSync(path.join(packageRoot, "package.json"), "utf8"));
503
- if (manifest.name !== spec.packageName) return;
504
- const relativeBinPath = resolvePackageBin(spec, manifest);
505
- if (!relativeBinPath) return;
506
- const binPath = path.resolve(packageRoot, relativeBinPath);
507
- if (!existsSync(binPath)) return;
529
+ const resolved = resolveInstalledBuiltInAgentPackage(spec, {
530
+ readFileSync,
531
+ existsSync,
532
+ resolvePackageRoot
533
+ });
534
+ if (!resolved) return;
508
535
  return {
509
536
  source: "installed",
510
537
  command: process.execPath,
511
- args: [binPath],
538
+ args: [resolved.binPath],
512
539
  packageName: spec.packageName,
513
540
  packageRange: spec.packageRange,
514
- packageVersion: manifest.version,
515
- binPath
541
+ packageVersion: resolved.packageVersion,
542
+ binPath: resolved.binPath
516
543
  };
517
544
  } catch {
518
545
  return;
519
546
  }
520
547
  }
548
+ function resolveInstalledBuiltInAgentPackage(spec, options) {
549
+ const packageRoot = options.resolvePackageRoot(spec.packageName);
550
+ const manifest = JSON.parse(options.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
551
+ if (manifest.name !== spec.packageName) return;
552
+ const relativeBinPath = resolvePackageBin(spec, manifest);
553
+ if (!relativeBinPath) return;
554
+ const binPath = path.resolve(packageRoot, relativeBinPath);
555
+ return options.existsSync(binPath) ? {
556
+ packageVersion: manifest.version,
557
+ binPath
558
+ } : void 0;
559
+ }
521
560
  function resolvePackageExecBuiltInAgentLaunch(agentCommand, options = {}) {
522
561
  const spec = findBuiltInAgentPackage(agentCommand);
523
562
  if (!spec) return;
@@ -633,6 +672,9 @@ function isBase64Data(value) {
633
672
  function isImageMimeType(value) {
634
673
  return /^image\/[A-Za-z0-9.+-]+$/i.test(value);
635
674
  }
675
+ function isAudioMimeType(value) {
676
+ return /^audio\/[A-Za-z0-9.+-]+$/i.test(value);
677
+ }
636
678
  function isTextBlock(value) {
637
679
  const record = asRecord$5(value);
638
680
  return record?.type === "text" && typeof record.text === "string";
@@ -641,6 +683,10 @@ function isImageBlock(value) {
641
683
  const record = asRecord$5(value);
642
684
  return record?.type === "image" && isNonEmptyString(record.mimeType) && isImageMimeType(record.mimeType) && typeof record.data === "string" && isBase64Data(record.data);
643
685
  }
686
+ function isAudioBlock(value) {
687
+ const record = asRecord$5(value);
688
+ return record?.type === "audio" && isNonEmptyString(record.mimeType) && isAudioMimeType(record.mimeType) && typeof record.data === "string" && isBase64Data(record.data);
689
+ }
644
690
  function isResourceLinkBlock(value) {
645
691
  const record = asRecord$5(value);
646
692
  return record?.type === "resource_link" && isNonEmptyString(record.uri) && (record.title === void 0 || typeof record.title === "string") && (record.name === void 0 || typeof record.name === "string");
@@ -654,35 +700,84 @@ function isResourceBlock(value) {
654
700
  const record = asRecord$5(value);
655
701
  return record?.type === "resource" && isResourcePayload(record.resource);
656
702
  }
703
+ const CONTENT_BLOCK_VALIDATORS = [
704
+ isTextBlock,
705
+ isImageBlock,
706
+ isAudioBlock,
707
+ isResourceLinkBlock,
708
+ isResourceBlock
709
+ ];
657
710
  function isContentBlock(value) {
658
- return isTextBlock(value) || isImageBlock(value) || isResourceLinkBlock(value) || isResourceBlock(value);
711
+ return CONTENT_BLOCK_VALIDATORS.some((validator) => validator(value));
712
+ }
713
+ const CONTENT_BLOCK_ERROR_VALIDATORS = {
714
+ text: validateTextContentBlock,
715
+ image: validateImageContentBlock,
716
+ audio: validateAudioContentBlock,
717
+ resource_link: validateResourceLinkContentBlock,
718
+ resource: validateResourceContentBlock
719
+ };
720
+ function contentBlockErrorValidator(type) {
721
+ return Object.hasOwn(CONTENT_BLOCK_ERROR_VALIDATORS, type) ? CONTENT_BLOCK_ERROR_VALIDATORS[type] : void 0;
722
+ }
723
+ function validateTextContentBlock(record, index) {
724
+ return typeof record.text === "string" ? void 0 : `prompt[${index}] text block must include a string text field`;
725
+ }
726
+ function validateImageContentBlock(record, index) {
727
+ if (!isNonEmptyString(record.mimeType)) return `prompt[${index}] image block must include a non-empty mimeType`;
728
+ if (!isImageMimeType(record.mimeType)) return `prompt[${index}] image block mimeType must start with image/`;
729
+ if (typeof record.data !== "string" || record.data.length === 0) return `prompt[${index}] image block must include non-empty base64 data`;
730
+ return isBase64Data(record.data) ? void 0 : `prompt[${index}] image block data must be valid base64`;
731
+ }
732
+ function validateAudioContentBlock(record, index) {
733
+ if (!isNonEmptyString(record.mimeType)) return `prompt[${index}] audio block must include a non-empty mimeType`;
734
+ if (!isAudioMimeType(record.mimeType)) return `prompt[${index}] audio block mimeType must start with audio/`;
735
+ if (typeof record.data !== "string" || record.data.length === 0) return `prompt[${index}] audio block must include non-empty base64 data`;
736
+ return isBase64Data(record.data) ? void 0 : `prompt[${index}] audio block data must be valid base64`;
737
+ }
738
+ function validateResourceLinkContentBlock(record, index) {
739
+ if (!isNonEmptyString(record.uri)) return `prompt[${index}] resource_link block must include a non-empty uri`;
740
+ if (record.title !== void 0 && typeof record.title !== "string") return `prompt[${index}] resource_link block title must be a string when present`;
741
+ if (record.name !== void 0 && typeof record.name !== "string") return `prompt[${index}] resource_link block name must be a string when present`;
742
+ }
743
+ function validateResourceContentBlock(record, index) {
744
+ if (!asRecord$5(record.resource)) return `prompt[${index}] resource block must include a resource object`;
745
+ return isResourcePayload(record.resource) ? void 0 : `prompt[${index}] resource block resource must include a non-empty uri and optional text`;
659
746
  }
660
747
  function getContentBlockValidationError(value, index) {
661
748
  const record = asRecord$5(value);
662
749
  if (!record || typeof record.type !== "string") return `prompt[${index}] must be an ACP content block object`;
663
- switch (record.type) {
664
- case "text": return typeof record.text === "string" ? void 0 : `prompt[${index}] text block must include a string text field`;
665
- case "image":
666
- if (!isNonEmptyString(record.mimeType)) return `prompt[${index}] image block must include a non-empty mimeType`;
667
- if (!isImageMimeType(record.mimeType)) return `prompt[${index}] image block mimeType must start with image/`;
668
- if (typeof record.data !== "string" || record.data.length === 0) return `prompt[${index}] image block must include non-empty base64 data`;
669
- if (!isBase64Data(record.data)) return `prompt[${index}] image block data must be valid base64`;
670
- return;
671
- case "resource_link":
672
- if (!isNonEmptyString(record.uri)) return `prompt[${index}] resource_link block must include a non-empty uri`;
673
- if (record.title !== void 0 && typeof record.title !== "string") return `prompt[${index}] resource_link block title must be a string when present`;
674
- if (record.name !== void 0 && typeof record.name !== "string") return `prompt[${index}] resource_link block name must be a string when present`;
675
- return;
676
- case "resource":
677
- if (!asRecord$5(record.resource)) return `prompt[${index}] resource block must include a resource object`;
678
- if (!isResourcePayload(record.resource)) return `prompt[${index}] resource block resource must include a non-empty uri and optional text`;
679
- return;
680
- default: return `prompt[${index}] has unsupported content block type ${JSON.stringify(record.type)}`;
681
- }
750
+ const validator = contentBlockErrorValidator(record.type);
751
+ return validator ? validator(record, index) : `prompt[${index}] has unsupported content block type ${JSON.stringify(record.type)}`;
682
752
  }
683
753
  function isPromptInput(value) {
684
754
  return Array.isArray(value) && value.every((entry) => isContentBlock(entry));
685
755
  }
756
+ function promptCapabilityRequirement(block) {
757
+ switch (block.type) {
758
+ case "image": return {
759
+ blockType: "image",
760
+ capability: "image"
761
+ };
762
+ case "audio": return {
763
+ blockType: "audio",
764
+ capability: "audio"
765
+ };
766
+ case "resource": return {
767
+ blockType: "resource",
768
+ capability: "embeddedContext"
769
+ };
770
+ default: return;
771
+ }
772
+ }
773
+ function getUnsupportedPromptContentMessage(prompt, agentCapabilities) {
774
+ for (const [index, block] of prompt.entries()) {
775
+ const requirement = promptCapabilityRequirement(block);
776
+ if (!requirement) continue;
777
+ if (agentCapabilities?.promptCapabilities?.[requirement.capability] === true) continue;
778
+ return `prompt[${index}] ${requirement.blockType} content requires agentCapabilities.promptCapabilities.${requirement.capability}`;
779
+ }
780
+ }
686
781
  function textPrompt(text) {
687
782
  return [{
688
783
  type: "text",
@@ -716,15 +811,20 @@ function mergePromptSourceWithText(source, suffixText) {
716
811
  return [...prompt, ...textPrompt(appended)];
717
812
  }
718
813
  function promptToDisplayText(prompt) {
719
- return prompt.map((block) => {
720
- switch (block.type) {
721
- case "text": return block.text;
722
- case "resource_link": return block.title ?? block.name ?? block.uri;
723
- case "resource": return "text" in block.resource && typeof block.resource.text === "string" ? block.resource.text : block.resource.uri;
724
- case "image": return `[image] ${block.mimeType}`;
725
- default: return "";
726
- }
727
- }).filter((entry) => entry.trim().length > 0).join("\n\n").trim();
814
+ return prompt.map((block) => contentBlockDisplayText(block)).filter((entry) => entry.trim().length > 0).join("\n\n").trim();
815
+ }
816
+ function contentBlockDisplayText(block) {
817
+ switch (block.type) {
818
+ case "text": return block.text;
819
+ case "resource_link": return block.title ?? block.name ?? block.uri;
820
+ case "resource": return resourceBlockDisplayText(block);
821
+ case "image": return `[image] ${block.mimeType}`;
822
+ case "audio": return `[audio] ${block.mimeType}`;
823
+ default: return "";
824
+ }
825
+ }
826
+ function resourceBlockDisplayText(block) {
827
+ return "text" in block.resource && typeof block.resource.text === "string" ? block.resource.text : block.resource.uri;
728
828
  }
729
829
  //#endregion
730
830
  //#region src/acp/agent-session-id.ts
@@ -869,6 +969,7 @@ const ZED_TAG_KEYS = new Set([
869
969
  "Text",
870
970
  "Mention",
871
971
  "Image",
972
+ "Audio",
872
973
  "Thinking",
873
974
  "RedactedThinking",
874
975
  "ToolUse"
@@ -896,10 +997,13 @@ function shouldSkipKeyRule(path) {
896
997
  function shouldSkipDescend(path) {
897
998
  return OPAQUE_VALUE_PATHS.has(joinPath(path)) || isToolResultOutputPath(path);
898
999
  }
1000
+ function isToolResultOutputTail(path, toolResultsIndex) {
1001
+ return toolResultsIndex !== -1 && toolResultsIndex + 2 === path.length - 1;
1002
+ }
899
1003
  function isToolResultOutputPath(path) {
900
1004
  if (path.length < 5 || path[path.length - 1] !== "output") return false;
901
1005
  const toolResultsIndex = path.lastIndexOf("tool_results");
902
- if (toolResultsIndex === -1 || toolResultsIndex + 2 !== path.length - 1) return false;
1006
+ if (!isToolResultOutputTail(path, toolResultsIndex)) return false;
903
1007
  return path.slice(0, toolResultsIndex + 1).join(".") === "messages.Agent.tool_results";
904
1008
  }
905
1009
  function collectViolations(value, path, violations) {
@@ -909,12 +1013,12 @@ function collectViolations(value, path, violations) {
909
1013
  }
910
1014
  if (!isRecord(value)) return;
911
1015
  const skipKeyRule = shouldSkipKeyRule(path);
912
- for (const [key, child] of Object.entries(value)) {
913
- if (!skipKeyRule && !SNAKE_CASE_KEY.test(key) && !isAllowedKey(path, key)) violations.push(`${joinPath(path)}.${key}`.replace(/^\./, ""));
914
- const childPath = [...path, key];
915
- if (shouldSkipDescend(childPath)) continue;
916
- collectViolations(child, childPath, violations);
917
- }
1016
+ for (const [key, child] of Object.entries(value)) collectKeyViolation(child, key, path, skipKeyRule, violations);
1017
+ }
1018
+ function collectKeyViolation(child, key, path, skipKeyRule, violations) {
1019
+ if (!skipKeyRule && !SNAKE_CASE_KEY.test(key) && !isAllowedKey(path, key)) violations.push(`${joinPath(path)}.${key}`.replace(/^\./, ""));
1020
+ const childPath = [...path, key];
1021
+ if (!shouldSkipDescend(childPath)) collectViolations(child, childPath, violations);
918
1022
  }
919
1023
  function findPersistedKeyPolicyViolations(value) {
920
1024
  const violations = [];
@@ -979,11 +1083,14 @@ function parseTokenUsage(raw) {
979
1083
  ]) {
980
1084
  const value = record[field];
981
1085
  if (value === void 0) continue;
982
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return null;
1086
+ if (!isNonNegativeFiniteNumber(value)) return null;
983
1087
  usage[field] = value;
984
1088
  }
985
1089
  return usage;
986
1090
  }
1091
+ function isNonNegativeFiniteNumber(value) {
1092
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
1093
+ }
987
1094
  function parseRequestTokenUsage(raw) {
988
1095
  if (raw === void 0 || raw === null) return;
989
1096
  const record = asRecord$4(raw);
@@ -1001,7 +1108,14 @@ function isSessionMessageImage(raw) {
1001
1108
  if (!record || typeof record.source !== "string") return false;
1002
1109
  if (record.size === void 0 || record.size === null) return true;
1003
1110
  const size = asRecord$4(record.size);
1004
- return !!size && typeof size.width === "number" && Number.isFinite(size.width) && typeof size.height === "number" && Number.isFinite(size.height);
1111
+ return !!size && isFiniteNumber(size.width) && isFiniteNumber(size.height);
1112
+ }
1113
+ function isSessionMessageAudio(raw) {
1114
+ const record = asRecord$4(raw);
1115
+ return !!record && typeof record.source === "string" && typeof record.mime_type === "string";
1116
+ }
1117
+ function isFiniteNumber(value) {
1118
+ return typeof value === "number" && Number.isFinite(value);
1005
1119
  }
1006
1120
  function isUserContent(raw) {
1007
1121
  const record = asRecord$4(raw);
@@ -1012,11 +1126,22 @@ function isUserContent(raw) {
1012
1126
  return !!mention && typeof mention.uri === "string" && typeof mention.content === "string";
1013
1127
  }
1014
1128
  if (record.Image !== void 0) return isSessionMessageImage(record.Image);
1129
+ if (record.Audio !== void 0) return isSessionMessageAudio(record.Audio);
1015
1130
  return false;
1016
1131
  }
1017
1132
  function isToolUse(raw) {
1018
1133
  const record = asRecord$4(raw);
1019
- return !!record && typeof record.id === "string" && typeof record.name === "string" && typeof record.raw_input === "string" && hasOwn$1(record, "input") && typeof record.is_input_complete === "boolean" && (record.thought_signature === void 0 || record.thought_signature === null || typeof record.thought_signature === "string");
1134
+ return !!record && hasStringFields(record, [
1135
+ "id",
1136
+ "name",
1137
+ "raw_input"
1138
+ ]) && hasOwn$1(record, "input") && typeof record.is_input_complete === "boolean" && isOptionalString(record.thought_signature);
1139
+ }
1140
+ function hasStringFields(record, keys) {
1141
+ return keys.every((key) => typeof record[key] === "string");
1142
+ }
1143
+ function isOptionalString(value) {
1144
+ return value === void 0 || value === null || typeof value === "string";
1020
1145
  }
1021
1146
  function isToolResultContent(raw) {
1022
1147
  const record = asRecord$4(raw);
@@ -1033,14 +1158,15 @@ function isAgentContent(raw) {
1033
1158
  const record = asRecord$4(raw);
1034
1159
  if (!record) return false;
1035
1160
  if (typeof record.Text === "string") return true;
1036
- if (record.Thinking !== void 0) {
1037
- const thinking = asRecord$4(record.Thinking);
1038
- return !!thinking && typeof thinking.text === "string" && (thinking.signature === void 0 || thinking.signature === null || typeof thinking.signature === "string");
1039
- }
1161
+ if (record.Thinking !== void 0) return isThinkingContent(record.Thinking);
1040
1162
  if (typeof record.RedactedThinking === "string") return true;
1041
1163
  if (record.ToolUse !== void 0) return isToolUse(record.ToolUse);
1042
1164
  return false;
1043
1165
  }
1166
+ function isThinkingContent(raw) {
1167
+ const thinking = asRecord$4(raw);
1168
+ return !!thinking && typeof thinking.text === "string" && isOptionalString(thinking.signature);
1169
+ }
1044
1170
  function isUserMessage$1(raw) {
1045
1171
  const record = asRecord$4(raw);
1046
1172
  if (!record || record.User === void 0) return false;
@@ -1060,56 +1186,88 @@ function isConversationMessage(raw) {
1060
1186
  return raw === "Resume" || isUserMessage$1(raw) || isAgentMessage$1(raw);
1061
1187
  }
1062
1188
  function parseConversationRecord(record) {
1063
- if (!Array.isArray(record.messages) || !record.messages.every(isConversationMessage) || typeof record.updated_at !== "string") return;
1064
- if (record.title !== void 0 && record.title !== null && typeof record.title !== "string") return;
1189
+ if (!hasValidConversationCore(record)) return;
1190
+ const title = parseConversationTitle(record.title);
1191
+ if (title === INVALID_VALUE) return;
1065
1192
  const cumulativeTokenUsage = parseTokenUsage(record.cumulative_token_usage);
1066
1193
  const requestTokenUsage = parseRequestTokenUsage(record.request_token_usage);
1067
1194
  if (cumulativeTokenUsage === null || requestTokenUsage === null) return;
1068
1195
  return {
1069
- title: record.title === void 0 || record.title === null || typeof record.title === "string" ? record.title : null,
1196
+ title,
1070
1197
  messages: record.messages,
1071
1198
  updated_at: record.updated_at,
1072
1199
  cumulative_token_usage: cumulativeTokenUsage ?? {},
1073
1200
  request_token_usage: requestTokenUsage ?? {}
1074
1201
  };
1075
1202
  }
1203
+ const INVALID_VALUE = Symbol("invalid");
1204
+ function parseConversationTitle(value) {
1205
+ if (value === void 0 || value === null || typeof value === "string") return value;
1206
+ return INVALID_VALUE;
1207
+ }
1208
+ function hasValidConversationCore(record) {
1209
+ return Array.isArray(record.messages) && record.messages.every(isConversationMessage) && typeof record.updated_at === "string";
1210
+ }
1076
1211
  function parseAcpxState(raw) {
1077
1212
  const record = asRecord$4(raw);
1078
1213
  if (!record) return;
1079
1214
  const state = {};
1080
- if (record.reset_on_next_ensure === true) state.reset_on_next_ensure = true;
1081
- if (typeof record.current_mode_id === "string") state.current_mode_id = record.current_mode_id;
1082
- if (typeof record.desired_mode_id === "string") state.desired_mode_id = record.desired_mode_id;
1083
- const desiredConfigOptions = asRecord$4(record.desired_config_options);
1084
- if (desiredConfigOptions) {
1085
- const parsed = {};
1086
- for (const [key, value] of Object.entries(desiredConfigOptions)) if (typeof key === "string" && typeof value === "string") parsed[key] = value;
1087
- if (Object.keys(parsed).length > 0) state.desired_config_options = parsed;
1088
- }
1089
- if (typeof record.current_model_id === "string") state.current_model_id = record.current_model_id;
1215
+ assignBooleanTrue(state, "reset_on_next_ensure", record.reset_on_next_ensure);
1216
+ assignStringState(state, "current_mode_id", record.current_mode_id);
1217
+ assignStringState(state, "desired_mode_id", record.desired_mode_id);
1218
+ assignDesiredConfigOptions(state, record.desired_config_options);
1219
+ assignStringState(state, "current_model_id", record.current_model_id);
1090
1220
  if (isStringArray(record.available_models)) state.available_models = [...record.available_models];
1091
1221
  if (isStringArray(record.available_commands)) state.available_commands = [...record.available_commands];
1092
1222
  if (Array.isArray(record.config_options)) state.config_options = record.config_options;
1093
- const sessionOptions = asRecord$4(record.session_options);
1094
- if (sessionOptions) {
1095
- const parsedSessionOptions = {};
1096
- if (typeof sessionOptions.model === "string") parsedSessionOptions.model = sessionOptions.model;
1097
- if (isStringArray(sessionOptions.allowed_tools)) parsedSessionOptions.allowed_tools = [...sessionOptions.allowed_tools];
1098
- if (typeof sessionOptions.max_turns === "number" && Number.isInteger(sessionOptions.max_turns) && sessionOptions.max_turns > 0) parsedSessionOptions.max_turns = sessionOptions.max_turns;
1099
- const rawSystemPrompt = sessionOptions.system_prompt;
1100
- if (typeof rawSystemPrompt === "string" && rawSystemPrompt.length > 0) parsedSessionOptions.system_prompt = rawSystemPrompt;
1101
- else {
1102
- const appendRecord = asRecord$4(rawSystemPrompt);
1103
- if (appendRecord && typeof appendRecord.append === "string" && appendRecord.append.length > 0) parsedSessionOptions.system_prompt = { append: appendRecord.append };
1104
- }
1105
- if (Object.keys(parsedSessionOptions).length > 0) state.session_options = parsedSessionOptions;
1106
- }
1223
+ assignParsedSessionOptions(state, record.session_options);
1107
1224
  return state;
1108
1225
  }
1226
+ function assignBooleanTrue(state, key, value) {
1227
+ if (value === true) state[key] = true;
1228
+ }
1229
+ function assignStringState(state, key, value) {
1230
+ if (typeof value === "string") state[key] = value;
1231
+ }
1232
+ function assignDesiredConfigOptions(state, raw) {
1233
+ const desiredConfigOptions = asRecord$4(raw);
1234
+ if (!desiredConfigOptions) return;
1235
+ const parsed = Object.fromEntries(Object.entries(desiredConfigOptions).filter((entry) => {
1236
+ const [, value] = entry;
1237
+ return typeof value === "string";
1238
+ }));
1239
+ if (Object.keys(parsed).length > 0) state.desired_config_options = parsed;
1240
+ }
1241
+ function assignParsedSessionOptions(state, raw) {
1242
+ const sessionOptions = asRecord$4(raw);
1243
+ if (!sessionOptions) return;
1244
+ const parsedSessionOptions = {};
1245
+ assignSessionOptionModel(parsedSessionOptions, sessionOptions.model);
1246
+ assignSessionOptionAllowedTools(parsedSessionOptions, sessionOptions.allowed_tools);
1247
+ assignSessionOptionMaxTurns(parsedSessionOptions, sessionOptions.max_turns);
1248
+ assignSessionOptionSystemPrompt(parsedSessionOptions, sessionOptions.system_prompt);
1249
+ if (Object.keys(parsedSessionOptions).length > 0) state.session_options = parsedSessionOptions;
1250
+ }
1251
+ function assignSessionOptionModel(options, value) {
1252
+ if (typeof value === "string") options.model = value;
1253
+ }
1254
+ function assignSessionOptionAllowedTools(options, value) {
1255
+ if (isStringArray(value)) options.allowed_tools = [...value];
1256
+ }
1257
+ function assignSessionOptionMaxTurns(options, value) {
1258
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) options.max_turns = value;
1259
+ }
1260
+ function assignSessionOptionSystemPrompt(options, value) {
1261
+ if (typeof value === "string" && value.length > 0) {
1262
+ options.system_prompt = value;
1263
+ return;
1264
+ }
1265
+ const appendRecord = asRecord$4(value);
1266
+ if (appendRecord && typeof appendRecord.append === "string" && appendRecord.append.length > 0) options.system_prompt = { append: appendRecord.append };
1267
+ }
1109
1268
  function parseEventLog(raw, sessionId) {
1110
1269
  const record = asRecord$4(raw);
1111
- if (!record) return defaultSessionEventLog(sessionId);
1112
- if (typeof record.active_path !== "string" || typeof record.segment_count !== "number" || !Number.isInteger(record.segment_count) || record.segment_count < 1 || typeof record.max_segment_bytes !== "number" || !Number.isInteger(record.max_segment_bytes) || record.max_segment_bytes < 1 || typeof record.max_segments !== "number" || !Number.isInteger(record.max_segments) || record.max_segments < 1) return defaultSessionEventLog(sessionId);
1270
+ if (!record || !hasValidEventLogCore(record)) return defaultSessionEventLog(sessionId);
1113
1271
  return {
1114
1272
  active_path: record.active_path,
1115
1273
  segment_count: record.segment_count,
@@ -1119,6 +1277,12 @@ function parseEventLog(raw, sessionId) {
1119
1277
  last_write_error: record.last_write_error == null || typeof record.last_write_error === "string" ? record.last_write_error : null
1120
1278
  };
1121
1279
  }
1280
+ function hasValidEventLogCore(record) {
1281
+ return typeof record.active_path === "string" && isPositiveInteger(record.segment_count) && isPositiveInteger(record.max_segment_bytes) && isPositiveInteger(record.max_segments);
1282
+ }
1283
+ function isPositiveInteger(value) {
1284
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
1285
+ }
1122
1286
  function normalizeOptionalName(value) {
1123
1287
  if (value == null) return;
1124
1288
  if (typeof value !== "string") return null;
@@ -1154,17 +1318,19 @@ function parseSessionRecord(raw) {
1154
1318
  const record = asRecord$4(raw);
1155
1319
  if (!record) return null;
1156
1320
  if (record.schema !== "acpx.session.v1") return null;
1157
- const name = normalizeOptionalName(record.name);
1158
- const pid = normalizeOptionalPid(record.pid);
1159
- const closed = normalizeOptionalBoolean(record.closed, false);
1160
- const closedAt = normalizeOptionalString(record.closed_at);
1161
- const agentStartedAt = normalizeOptionalString(record.agent_started_at);
1162
- const lastPromptAt = normalizeOptionalString(record.last_prompt_at);
1163
- const lastAgentExitCode = normalizeOptionalExitCode(record.last_agent_exit_code);
1164
- const lastAgentExitSignal = normalizeOptionalSignal(record.last_agent_exit_signal);
1165
- const lastAgentExitAt = normalizeOptionalString(record.last_agent_exit_at);
1166
- const lastAgentDisconnectReason = normalizeOptionalString(record.last_agent_disconnect_reason);
1167
- if (typeof record.acpx_record_id !== "string" || typeof record.acp_session_id !== "string" || typeof record.agent_command !== "string" || typeof record.cwd !== "string" || typeof record.created_at !== "string" || typeof record.last_used_at !== "string" || typeof record.last_seq !== "number" || !Number.isInteger(record.last_seq) || record.last_seq < 0 || name === null || pid === null || closed === null || closedAt === null || agentStartedAt === null || lastPromptAt === null || typeof lastAgentExitCode === "symbol" || typeof lastAgentExitSignal === "symbol" || lastAgentExitAt === null || lastAgentDisconnectReason === null) return null;
1321
+ const optionals = validSessionOptionals({
1322
+ name: normalizeOptionalName(record.name),
1323
+ pid: normalizeOptionalPid(record.pid),
1324
+ closed: normalizeOptionalBoolean(record.closed, false),
1325
+ closedAt: normalizeOptionalString(record.closed_at),
1326
+ agentStartedAt: normalizeOptionalString(record.agent_started_at),
1327
+ lastPromptAt: normalizeOptionalString(record.last_prompt_at),
1328
+ lastAgentExitCode: normalizeOptionalExitCode(record.last_agent_exit_code),
1329
+ lastAgentExitSignal: normalizeOptionalSignal(record.last_agent_exit_signal),
1330
+ lastAgentExitAt: normalizeOptionalString(record.last_agent_exit_at),
1331
+ lastAgentDisconnectReason: normalizeOptionalString(record.last_agent_disconnect_reason)
1332
+ });
1333
+ if (!hasValidSessionRecordCore(record) || !optionals) return null;
1168
1334
  const conversation = parseConversationRecord(record);
1169
1335
  if (!conversation) return null;
1170
1336
  const eventLog = parseEventLog(record.event_log, record.acpx_record_id);
@@ -1177,21 +1343,21 @@ function parseSessionRecord(raw) {
1177
1343
  agentSessionId: normalizeRuntimeSessionId(record.agent_session_id),
1178
1344
  agentCommand: record.agent_command,
1179
1345
  cwd: record.cwd,
1180
- name,
1346
+ name: optionals.name,
1181
1347
  createdAt: record.created_at,
1182
1348
  lastUsedAt: record.last_used_at,
1183
1349
  lastSeq: record.last_seq,
1184
1350
  lastRequestId,
1185
1351
  eventLog,
1186
- closed,
1187
- closedAt,
1188
- pid,
1189
- agentStartedAt,
1190
- lastPromptAt,
1191
- lastAgentExitCode,
1192
- lastAgentExitSignal,
1193
- lastAgentExitAt,
1194
- lastAgentDisconnectReason,
1352
+ closed: optionals.closed,
1353
+ closedAt: optionals.closedAt,
1354
+ pid: optionals.pid,
1355
+ agentStartedAt: optionals.agentStartedAt,
1356
+ lastPromptAt: optionals.lastPromptAt,
1357
+ lastAgentExitCode: optionals.lastAgentExitCode,
1358
+ lastAgentExitSignal: optionals.lastAgentExitSignal,
1359
+ lastAgentExitAt: optionals.lastAgentExitAt,
1360
+ lastAgentDisconnectReason: optionals.lastAgentDisconnectReason,
1195
1361
  protocolVersion: typeof record.protocol_version === "number" ? record.protocol_version : void 0,
1196
1362
  agentCapabilities: asRecord$4(record.agent_capabilities),
1197
1363
  title: conversation.title,
@@ -1202,6 +1368,35 @@ function parseSessionRecord(raw) {
1202
1368
  acpx: parseAcpxState(record.acpx)
1203
1369
  };
1204
1370
  }
1371
+ function hasValidSessionRecordCore(record) {
1372
+ return hasStringFields(record, [
1373
+ "acpx_record_id",
1374
+ "acp_session_id",
1375
+ "agent_command",
1376
+ "cwd",
1377
+ "created_at",
1378
+ "last_used_at"
1379
+ ]) && typeof record.last_seq === "number" && Number.isInteger(record.last_seq) && record.last_seq >= 0;
1380
+ }
1381
+ function validSessionOptionals(options) {
1382
+ if (hasNullOptionalSessionFields(options) || hasInvalidExitStatus(options)) return null;
1383
+ return options;
1384
+ }
1385
+ function hasNullOptionalSessionFields(options) {
1386
+ return [
1387
+ options.name,
1388
+ options.pid,
1389
+ options.closed,
1390
+ options.closedAt,
1391
+ options.agentStartedAt,
1392
+ options.lastPromptAt,
1393
+ options.lastAgentExitAt,
1394
+ options.lastAgentDisconnectReason
1395
+ ].some((value) => value === null);
1396
+ }
1397
+ function hasInvalidExitStatus(options) {
1398
+ return typeof options.lastAgentExitCode === "symbol" || typeof options.lastAgentExitSignal === "symbol";
1399
+ }
1205
1400
  //#endregion
1206
1401
  //#region src/session/persistence/index.ts
1207
1402
  const SESSION_INDEX_SCHEMA = "acpx.session-index.v1";
@@ -1212,7 +1407,7 @@ function asRecord$3(value) {
1212
1407
  function parseIndexEntry(raw) {
1213
1408
  const record = asRecord$3(raw);
1214
1409
  if (!record) return;
1215
- if (typeof record.file !== "string" || typeof record.acpxRecordId !== "string" || typeof record.acpSessionId !== "string" || typeof record.agentCommand !== "string" || typeof record.cwd !== "string" || typeof record.lastUsedAt !== "string" || typeof record.closed !== "boolean") return;
1410
+ if (!hasRequiredIndexEntryFields(record)) return;
1216
1411
  if (record.name !== void 0 && typeof record.name !== "string") return;
1217
1412
  return {
1218
1413
  file: record.file,
@@ -1225,6 +1420,16 @@ function parseIndexEntry(raw) {
1225
1420
  lastUsedAt: record.lastUsedAt
1226
1421
  };
1227
1422
  }
1423
+ function hasRequiredIndexEntryFields(record) {
1424
+ return [
1425
+ "file",
1426
+ "acpxRecordId",
1427
+ "acpSessionId",
1428
+ "agentCommand",
1429
+ "cwd",
1430
+ "lastUsedAt"
1431
+ ].every((key) => typeof record[key] === "string") && typeof record.closed === "boolean";
1432
+ }
1228
1433
  function sessionIndexPath(sessionDir) {
1229
1434
  return path.join(sessionDir, "index.json");
1230
1435
  }
@@ -1436,13 +1641,17 @@ async function findSessionByDirectoryWalk(options) {
1436
1641
  for (;;) {
1437
1642
  const match = sessions.find((session) => matchesSessionEntry(session, current, normalizedName));
1438
1643
  if (match) return await loadRecordFromIndexEntry(match);
1439
- if (current === walkBoundary || current === walkRoot) return;
1440
- const parent = path.dirname(current);
1441
- if (parent === current) return;
1644
+ const parent = nextWalkParent(current, walkBoundary, walkRoot);
1645
+ if (!parent) return;
1442
1646
  current = parent;
1443
- if (!isWithinBoundary(walkBoundary, current)) return;
1444
1647
  }
1445
1648
  }
1649
+ function nextWalkParent(current, walkBoundary, walkRoot) {
1650
+ if (current === walkBoundary || current === walkRoot) return;
1651
+ const parent = path.dirname(current);
1652
+ if (parent === current || !isWithinBoundary(walkBoundary, parent)) return;
1653
+ return parent;
1654
+ }
1446
1655
  function closedAtOrLastUsedAt(record) {
1447
1656
  return record.closedAt ?? record.lastUsedAt;
1448
1657
  }
@@ -1451,14 +1660,7 @@ function isSessionStreamFile(fileName, safeId) {
1451
1660
  }
1452
1661
  async function pruneSessions(options = {}) {
1453
1662
  await ensureSessionDir();
1454
- let eligible = (await loadSessionIndexEntries()).filter((entry) => entry.closed);
1455
- if (options.agentCommand) eligible = eligible.filter((entry) => entry.agentCommand === options.agentCommand);
1456
- const cutoff = options.before ?? (options.olderThanMs != null ? new Date(Date.now() - options.olderThanMs) : void 0);
1457
- const records = [];
1458
- for (const entry of eligible) {
1459
- const record = await loadRecordFromIndexEntry(entry);
1460
- if (record && (!cutoff || closedAtOrLastUsedAt(record) < cutoff.toISOString())) records.push(record);
1461
- }
1663
+ const records = await loadPrunableRecords(filterPruneCandidates(await loadSessionIndexEntries(), options.agentCommand), options.before ?? (options.olderThanMs != null ? new Date(Date.now() - options.olderThanMs) : void 0));
1462
1664
  if (options.dryRun) return {
1463
1665
  pruned: records,
1464
1666
  bytesFreed: 0,
@@ -1470,24 +1672,7 @@ async function pruneSessions(options = {}) {
1470
1672
  if (options.includeHistory) try {
1471
1673
  dirEntries = await fs$1.readdir(sessionDir);
1472
1674
  } catch {}
1473
- for (const record of records) {
1474
- const safeId = encodeURIComponent(record.acpxRecordId);
1475
- const jsonFile = path.join(sessionDir, `${safeId}.json`);
1476
- try {
1477
- const stat = await fs$1.stat(jsonFile);
1478
- bytesFreed += stat.size;
1479
- } catch {}
1480
- await fs$1.unlink(jsonFile).catch(() => void 0);
1481
- if (options.includeHistory) for (const name of dirEntries) {
1482
- if (!isSessionStreamFile(name, safeId)) continue;
1483
- const filePath = path.join(sessionDir, name);
1484
- try {
1485
- const stat = await fs$1.stat(filePath);
1486
- bytesFreed += stat.size;
1487
- } catch {}
1488
- await fs$1.unlink(filePath).catch(() => void 0);
1489
- }
1490
- }
1675
+ for (const record of records) bytesFreed += await pruneSessionFiles(record, sessionDir, dirEntries, options.includeHistory === true);
1491
1676
  await rebuildSessionIndex(sessionDir).catch(() => {});
1492
1677
  return {
1493
1678
  pruned: records,
@@ -1495,6 +1680,35 @@ async function pruneSessions(options = {}) {
1495
1680
  dryRun: false
1496
1681
  };
1497
1682
  }
1683
+ function filterPruneCandidates(entries, agentCommand) {
1684
+ return entries.filter((entry) => entry.closed && (!agentCommand || entry.agentCommand === agentCommand));
1685
+ }
1686
+ async function loadPrunableRecords(entries, cutoff) {
1687
+ const records = [];
1688
+ const cutoffIso = cutoff?.toISOString();
1689
+ for (const entry of entries) {
1690
+ const record = await loadRecordFromIndexEntry(entry);
1691
+ if (record && isBeforeCutoff(record, cutoffIso)) records.push(record);
1692
+ }
1693
+ return records;
1694
+ }
1695
+ function isBeforeCutoff(record, cutoffIso) {
1696
+ return !cutoffIso || closedAtOrLastUsedAt(record) < cutoffIso;
1697
+ }
1698
+ async function pruneSessionFiles(record, sessionDir, dirEntries, includeHistory) {
1699
+ const safeId = encodeURIComponent(record.acpxRecordId);
1700
+ let bytesFreed = await unlinkCountingBytes(path.join(sessionDir, `${safeId}.json`));
1701
+ if (includeHistory) for (const name of dirEntries.filter((entry) => isSessionStreamFile(entry, safeId))) bytesFreed += await unlinkCountingBytes(path.join(sessionDir, name));
1702
+ return bytesFreed;
1703
+ }
1704
+ async function unlinkCountingBytes(filePath) {
1705
+ let bytes = 0;
1706
+ try {
1707
+ bytes = (await fs$1.stat(filePath)).size;
1708
+ } catch {}
1709
+ await fs$1.unlink(filePath).catch(() => void 0);
1710
+ return bytes;
1711
+ }
1498
1712
  //#endregion
1499
1713
  //#region src/permission-prompt.ts
1500
1714
  async function promptForPermission(options) {
@@ -1692,21 +1906,66 @@ function pickOption(options, kinds) {
1692
1906
  if (match) return match;
1693
1907
  }
1694
1908
  }
1909
+ const TOOL_KIND_TITLE_MATCHERS = [
1910
+ {
1911
+ kind: "read",
1912
+ needles: ["read", "cat"]
1913
+ },
1914
+ {
1915
+ kind: "search",
1916
+ needles: [
1917
+ "search",
1918
+ "find",
1919
+ "grep"
1920
+ ]
1921
+ },
1922
+ {
1923
+ kind: "edit",
1924
+ needles: [
1925
+ "write",
1926
+ "edit",
1927
+ "patch"
1928
+ ]
1929
+ },
1930
+ {
1931
+ kind: "delete",
1932
+ needles: ["delete", "remove"]
1933
+ },
1934
+ {
1935
+ kind: "move",
1936
+ needles: ["move", "rename"]
1937
+ },
1938
+ {
1939
+ kind: "execute",
1940
+ needles: [
1941
+ "run",
1942
+ "execute",
1943
+ "bash"
1944
+ ]
1945
+ },
1946
+ {
1947
+ kind: "fetch",
1948
+ needles: [
1949
+ "fetch",
1950
+ "http",
1951
+ "url"
1952
+ ]
1953
+ },
1954
+ {
1955
+ kind: "think",
1956
+ needles: ["think"]
1957
+ }
1958
+ ];
1695
1959
  function inferToolKind(params) {
1696
1960
  if (params.toolCall.kind) return params.toolCall.kind;
1697
1961
  const title = params.toolCall.title?.trim().toLowerCase();
1698
1962
  if (!title) return;
1699
1963
  const head = title.split(":", 1)[0]?.trim();
1700
1964
  if (!head) return;
1701
- if (head.includes("read") || head.includes("cat")) return "read";
1702
- if (head.includes("search") || head.includes("find") || head.includes("grep")) return "search";
1703
- if (head.includes("write") || head.includes("edit") || head.includes("patch")) return "edit";
1704
- if (head.includes("delete") || head.includes("remove")) return "delete";
1705
- if (head.includes("move") || head.includes("rename")) return "move";
1706
- if (head.includes("run") || head.includes("execute") || head.includes("bash")) return "execute";
1707
- if (head.includes("fetch") || head.includes("http") || head.includes("url")) return "fetch";
1708
- if (head.includes("think")) return "think";
1709
- return "other";
1965
+ return titleHeadToolKind(head) ?? "other";
1966
+ }
1967
+ function titleHeadToolKind(head) {
1968
+ return TOOL_KIND_TITLE_MATCHERS.find(({ needles }) => needles.some((needle) => head.includes(needle)))?.kind;
1710
1969
  }
1711
1970
  function isAutoApprovedReadKind(kind) {
1712
1971
  return kind === "read" || kind === "search";
@@ -1801,6 +2060,44 @@ function buildEscalationEvent(params, matchedRule) {
1801
2060
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1802
2061
  };
1803
2062
  }
2063
+ function selectedOrFirst(options, allowOption) {
2064
+ return { response: selected((allowOption ?? options[0]).optionId) };
2065
+ }
2066
+ function selectedOrCancelled(option) {
2067
+ return { response: option ? selected(option.optionId) : cancelled() };
2068
+ }
2069
+ async function resolveEscalatingPermissionRequest(params, policyMatch, allowOption, rejectOption) {
2070
+ if (canPromptForPermission$1()) return resolveInteractivePromptResult(params, allowOption, rejectOption);
2071
+ const escalation = buildEscalationEvent(params, policyMatch.matchedRule);
2072
+ return {
2073
+ response: withEscalationMetadata(rejectOption ? selected(rejectOption.optionId) : cancelled(), escalation),
2074
+ escalation
2075
+ };
2076
+ }
2077
+ async function resolveInteractivePromptResult(params, allowOption, rejectOption) {
2078
+ const approved = await promptForToolPermission(params);
2079
+ if (approved && allowOption) return { response: selected(allowOption.optionId) };
2080
+ if (!approved && rejectOption) return { response: selected(rejectOption.optionId) };
2081
+ return { response: cancelled() };
2082
+ }
2083
+ function resolvePolicyMatch(params, policyMatch, options, allowOption, rejectOption) {
2084
+ if (policyMatch?.action === "approve") return selectedOrFirst(options, allowOption);
2085
+ if (policyMatch?.action === "deny") return selectedOrCancelled(rejectOption);
2086
+ if (policyMatch?.action === "escalate") return resolveEscalatingPermissionRequest(params, policyMatch, allowOption, rejectOption);
2087
+ }
2088
+ function resolveModeMatch(options, mode, allowOption, rejectOption) {
2089
+ if (mode === "approve-all") return selectedOrFirst(options, allowOption);
2090
+ if (mode === "deny-all") return selectedOrCancelled(rejectOption);
2091
+ }
2092
+ function resolveNonInteractivePermission(nonInteractivePolicy, rejectOption) {
2093
+ if (nonInteractivePolicy === "fail") throw new PermissionPromptUnavailableError();
2094
+ return selectedOrCancelled(rejectOption);
2095
+ }
2096
+ async function resolveReadOrPromptPermission(params, nonInteractivePolicy, allowOption, rejectOption) {
2097
+ if (isAutoApprovedReadKind(inferToolKind(params)) && allowOption) return { response: selected(allowOption.optionId) };
2098
+ if (!canPromptForPermission$1()) return resolveNonInteractivePermission(nonInteractivePolicy, rejectOption);
2099
+ return resolveInteractivePromptResult(params, allowOption, rejectOption);
2100
+ }
1804
2101
  function permissionModeSatisfies(actual, required) {
1805
2102
  return PERMISSION_MODE_RANK[actual] >= PERMISSION_MODE_RANK[required];
1806
2103
  }
@@ -1809,46 +2106,11 @@ async function resolvePermissionRequestWithDetails(params, mode, nonInteractiveP
1809
2106
  if (options.length === 0) return { response: cancelled() };
1810
2107
  const allowOption = pickOption(options, ["allow_once", "allow_always"]);
1811
2108
  const rejectOption = pickOption(options, ["reject_once", "reject_always"]);
1812
- const policyMatch = matchPermissionPolicy(params, policy);
1813
- if (policyMatch?.action === "approve") {
1814
- if (allowOption) return { response: selected(allowOption.optionId) };
1815
- return { response: selected(options[0].optionId) };
1816
- }
1817
- if (policyMatch?.action === "deny") {
1818
- if (rejectOption) return { response: selected(rejectOption.optionId) };
1819
- return { response: cancelled() };
1820
- }
1821
- if (policyMatch?.action === "escalate") {
1822
- if (canPromptForPermission$1()) {
1823
- const approved = await promptForToolPermission(params);
1824
- if (approved && allowOption) return { response: selected(allowOption.optionId) };
1825
- if (!approved && rejectOption) return { response: selected(rejectOption.optionId) };
1826
- return { response: cancelled() };
1827
- }
1828
- const escalation = buildEscalationEvent(params, policyMatch.matchedRule);
1829
- return {
1830
- response: withEscalationMetadata(rejectOption ? selected(rejectOption.optionId) : cancelled(), escalation),
1831
- escalation
1832
- };
1833
- }
1834
- if (mode === "approve-all") {
1835
- if (allowOption) return { response: selected(allowOption.optionId) };
1836
- return { response: selected(options[0].optionId) };
1837
- }
1838
- if (mode === "deny-all") {
1839
- if (rejectOption) return { response: selected(rejectOption.optionId) };
1840
- return { response: cancelled() };
1841
- }
1842
- if (isAutoApprovedReadKind(inferToolKind(params)) && allowOption) return { response: selected(allowOption.optionId) };
1843
- if (!canPromptForPermission$1()) {
1844
- if (nonInteractivePolicy === "fail") throw new PermissionPromptUnavailableError();
1845
- if (rejectOption) return { response: selected(rejectOption.optionId) };
1846
- return { response: cancelled() };
1847
- }
1848
- const approved = await promptForToolPermission(params);
1849
- if (approved && allowOption) return { response: selected(allowOption.optionId) };
1850
- if (!approved && rejectOption) return { response: selected(rejectOption.optionId) };
1851
- return { response: cancelled() };
2109
+ const resolvedByPolicy = await resolvePolicyMatch(params, matchPermissionPolicy(params, policy), options, allowOption, rejectOption);
2110
+ if (resolvedByPolicy) return resolvedByPolicy;
2111
+ const resolvedByMode = resolveModeMatch(options, mode, allowOption, rejectOption);
2112
+ if (resolvedByMode) return resolvedByMode;
2113
+ return resolveReadOrPromptPermission(params, nonInteractivePolicy, allowOption, rejectOption);
1852
2114
  }
1853
2115
  const DECISION_FALLBACK_ORDER = {
1854
2116
  allow_once: ["allow_once", "allow_always"],
@@ -1875,21 +2137,35 @@ function readWindowsEnvValue(env, key) {
1875
2137
  const matchedKey = Object.keys(env).find((entry) => entry.toUpperCase() === key);
1876
2138
  return matchedKey ? env[matchedKey] : void 0;
1877
2139
  }
1878
- function resolveWindowsCommand(command, env = process.env) {
1879
- const extensions = (readWindowsEnvValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
1880
- const candidates = path.extname(command).length > 0 ? [command] : extensions.map((extension) => `${command}${extension}`);
1881
- if (command.includes("/") || command.includes("\\") || path.isAbsolute(command)) return candidates.find((candidate) => fs.existsSync(candidate));
2140
+ function windowsExecutableExtensions(env) {
2141
+ return (readWindowsEnvValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
2142
+ }
2143
+ function commandCandidates(command, env) {
2144
+ if (path.extname(command).length > 0) return [command];
2145
+ return windowsExecutableExtensions(env).map((extension) => `${command}${extension}`);
2146
+ }
2147
+ function commandHasPath(command) {
2148
+ return command.includes("/") || command.includes("\\") || path.isAbsolute(command);
2149
+ }
2150
+ function resolveWindowsPathCommand(command, env) {
2151
+ const candidates = commandCandidates(command, env);
1882
2152
  const pathValue = readWindowsEnvValue(env, "PATH");
1883
2153
  if (!pathValue) return;
1884
2154
  for (const directory of pathValue.split(";")) {
1885
- const trimmedDirectory = directory.trim();
1886
- if (trimmedDirectory.length === 0) continue;
1887
- for (const candidate of candidates) {
1888
- const resolved = path.join(trimmedDirectory, candidate);
1889
- if (fs.existsSync(resolved)) return resolved;
1890
- }
2155
+ const resolved = findExistingCommandInDirectory(directory, candidates);
2156
+ if (resolved) return resolved;
1891
2157
  }
1892
2158
  }
2159
+ function findExistingCommandInDirectory(directory, candidates) {
2160
+ const trimmedDirectory = directory.trim();
2161
+ if (trimmedDirectory.length === 0) return;
2162
+ return candidates.map((candidate) => path.join(trimmedDirectory, candidate)).find((resolved) => fs.existsSync(resolved));
2163
+ }
2164
+ function resolveWindowsCommand(command, env = process.env) {
2165
+ const candidates = commandCandidates(command, env);
2166
+ if (commandHasPath(command)) return candidates.find((candidate) => fs.existsSync(candidate));
2167
+ return resolveWindowsPathCommand(command, env);
2168
+ }
1893
2169
  function shouldUseWindowsBatchShell(command, platform = process.platform, env = process.env) {
1894
2170
  if (platform !== "win32") return false;
1895
2171
  const resolvedCommand = resolveWindowsCommand(command, env) ?? command;
@@ -1982,32 +2258,16 @@ function splitCommandLine(value) {
1982
2258
  let quote = null;
1983
2259
  let escaping = false;
1984
2260
  for (const ch of value) {
1985
- if (escaping) {
1986
- current += ch;
1987
- escaping = false;
1988
- continue;
1989
- }
1990
- if (ch === "\\" && quote !== "'") {
1991
- escaping = true;
1992
- continue;
1993
- }
1994
- if (quote) {
1995
- if (ch === quote) quote = null;
1996
- else current += ch;
1997
- continue;
1998
- }
1999
- if (ch === "'" || ch === "\"") {
2000
- quote = ch;
2001
- continue;
2002
- }
2003
- if (/\s/.test(ch)) {
2004
- if (current.length > 0) {
2005
- parts.push(current);
2006
- current = "";
2007
- }
2008
- continue;
2009
- }
2010
- current += ch;
2261
+ const next = readCommandLineChar({
2262
+ ch,
2263
+ current,
2264
+ quote,
2265
+ escaping,
2266
+ parts
2267
+ });
2268
+ current = next.current;
2269
+ quote = next.quote;
2270
+ escaping = next.escaping;
2011
2271
  }
2012
2272
  if (escaping) current += "\\";
2013
2273
  if (quote) throw new Error("Invalid --agent command: unterminated quote");
@@ -2018,6 +2278,59 @@ function splitCommandLine(value) {
2018
2278
  args: parts.slice(1)
2019
2279
  };
2020
2280
  }
2281
+ function readCommandLineChar(state) {
2282
+ if (state.escaping) return {
2283
+ current: state.current + state.ch,
2284
+ quote: state.quote,
2285
+ escaping: false
2286
+ };
2287
+ if (state.ch === "\\" && state.quote !== "'") return {
2288
+ current: state.current,
2289
+ quote: state.quote,
2290
+ escaping: true
2291
+ };
2292
+ if (state.quote) return readQuotedCommandLineChar({
2293
+ ch: state.ch,
2294
+ current: state.current,
2295
+ quote: state.quote
2296
+ });
2297
+ return readUnquotedCommandLineChar(state);
2298
+ }
2299
+ function readQuotedCommandLineChar(state) {
2300
+ if (state.ch === state.quote) return {
2301
+ current: state.current,
2302
+ quote: null,
2303
+ escaping: false
2304
+ };
2305
+ return {
2306
+ current: state.current + state.ch,
2307
+ quote: state.quote,
2308
+ escaping: false
2309
+ };
2310
+ }
2311
+ function readUnquotedCommandLineChar(state) {
2312
+ if (state.ch === "'" || state.ch === "\"") return {
2313
+ current: state.current,
2314
+ quote: state.ch,
2315
+ escaping: false
2316
+ };
2317
+ if (/\s/.test(state.ch)) {
2318
+ flushCommandLinePart(state.parts, state.current);
2319
+ return {
2320
+ current: "",
2321
+ quote: null,
2322
+ escaping: false
2323
+ };
2324
+ }
2325
+ return {
2326
+ current: state.current + state.ch,
2327
+ quote: null,
2328
+ escaping: false
2329
+ };
2330
+ }
2331
+ function flushCommandLinePart(parts, current) {
2332
+ if (current.length > 0) parts.push(current);
2333
+ }
2021
2334
  function asAbsoluteCwd(cwd) {
2022
2335
  return path.resolve(cwd);
2023
2336
  }
@@ -2231,17 +2544,28 @@ async function ensureCopilotAcpSupport(command) {
2231
2544
  function buildClaudeCodeOptionsMeta(options) {
2232
2545
  if (!options) return;
2233
2546
  const claudeCodeOptions = {};
2234
- if (typeof options.model === "string" && options.model.trim().length > 0) claudeCodeOptions.model = options.model;
2235
- if (Array.isArray(options.allowedTools)) claudeCodeOptions.allowedTools = [...options.allowedTools];
2236
- if (typeof options.maxTurns === "number") claudeCodeOptions.maxTurns = options.maxTurns;
2547
+ assignClaudeCodeOptions(claudeCodeOptions, options);
2237
2548
  const meta = {};
2238
2549
  if (Object.keys(claudeCodeOptions).length > 0) meta.claudeCode = { options: claudeCodeOptions };
2239
- const systemPrompt = options.systemPrompt;
2240
- if (typeof systemPrompt === "string" && systemPrompt.length > 0) meta.systemPrompt = systemPrompt;
2241
- else if (systemPrompt && typeof systemPrompt === "object" && typeof systemPrompt.append === "string" && systemPrompt.append.length > 0) meta.systemPrompt = { append: systemPrompt.append };
2550
+ assignClaudeCodeSystemPrompt(meta, options.systemPrompt);
2242
2551
  if (Object.keys(meta).length === 0) return;
2243
2552
  return meta;
2244
2553
  }
2554
+ function assignClaudeCodeOptions(target, options) {
2555
+ if (typeof options.model === "string" && options.model.trim().length > 0) target.model = options.model;
2556
+ if (Array.isArray(options.allowedTools)) target.allowedTools = [...options.allowedTools];
2557
+ if (typeof options.maxTurns === "number") target.maxTurns = options.maxTurns;
2558
+ }
2559
+ function assignClaudeCodeSystemPrompt(target, systemPrompt) {
2560
+ if (typeof systemPrompt === "string" && systemPrompt.length > 0) {
2561
+ target.systemPrompt = systemPrompt;
2562
+ return;
2563
+ }
2564
+ if (isAppendSystemPrompt(systemPrompt)) target.systemPrompt = { append: systemPrompt.append };
2565
+ }
2566
+ function isAppendSystemPrompt(value) {
2567
+ return !!value && typeof value === "object" && typeof value.append === "string" && value.append.length > 0;
2568
+ }
2245
2569
  function resolveClaudeCodeExecutable(platform = process.platform, env = process.env) {
2246
2570
  if (platform !== "win32") return;
2247
2571
  if (readWindowsEnvValue(env, "CLAUDE_CODE_EXECUTABLE")) return;
@@ -2286,18 +2610,21 @@ function buildAgentEnvironment(authCredentials) {
2286
2610
  const env = { ...process.env };
2287
2611
  promotePrefixedAuthEnvironment(env);
2288
2612
  if (!authCredentials) return env;
2289
- for (const [methodId, credential] of Object.entries(authCredentials)) {
2290
- if (typeof credential !== "string" || credential.trim().length === 0) continue;
2291
- if (!methodId.includes("=") && !methodId.includes("\0") && env[methodId] == null) env[methodId] = credential;
2292
- const normalized = toEnvToken(methodId);
2293
- if (normalized) {
2294
- const prefixed = `${AUTH_ENV_PREFIX}${normalized}`;
2295
- if (env[prefixed] == null) env[prefixed] = credential;
2296
- if (env[normalized] == null) env[normalized] = credential;
2297
- }
2298
- }
2613
+ for (const [methodId, credential] of Object.entries(authCredentials)) assignAuthCredentialEnv(env, methodId, credential);
2299
2614
  return env;
2300
2615
  }
2616
+ function assignAuthCredentialEnv(env, methodId, credential) {
2617
+ if (typeof credential !== "string" || credential.trim().length === 0) return;
2618
+ if (!methodId.includes("=") && !methodId.includes("\0") && env[methodId] == null) env[methodId] = credential;
2619
+ const normalized = toEnvToken(methodId);
2620
+ if (normalized) {
2621
+ assignIfMissing(env, `${AUTH_ENV_PREFIX}${normalized}`, credential);
2622
+ assignIfMissing(env, normalized, credential);
2623
+ }
2624
+ }
2625
+ function assignIfMissing(env, key, value) {
2626
+ if (env[key] == null) env[key] = value;
2627
+ }
2301
2628
  function resolveConfiguredAuthCredential(methodId, authCredentials) {
2302
2629
  const configCredentials = authCredentials ?? {};
2303
2630
  return configCredentials[methodId] ?? configCredentials[toEnvToken(methodId)];
@@ -2335,18 +2662,23 @@ function hasResultOrError(value) {
2335
2662
  if (hasError && !isErrorObject(value.error)) return false;
2336
2663
  return true;
2337
2664
  }
2665
+ function hasMethod(value) {
2666
+ return typeof value.method === "string" && value.method.length > 0;
2667
+ }
2668
+ function isJsonRpcRequest(value) {
2669
+ return hasMethod(value) && Object.hasOwn(value, "id") && hasValidId(value.id);
2670
+ }
2671
+ function isJsonRpcNotificationRecord(value) {
2672
+ return hasMethod(value) && !Object.hasOwn(value, "id");
2673
+ }
2674
+ function isJsonRpcResponse(value) {
2675
+ if (hasMethod(value) || !Object.hasOwn(value, "id") || !hasValidId(value.id)) return false;
2676
+ return hasResultOrError(value);
2677
+ }
2338
2678
  function isAcpJsonRpcMessage(value) {
2339
2679
  const record = asRecord$2(value);
2340
2680
  if (!record || record.jsonrpc !== "2.0") return false;
2341
- const hasMethod = typeof record.method === "string" && record.method.length > 0;
2342
- const hasId = Object.hasOwn(record, "id");
2343
- if (hasMethod && !hasId) return true;
2344
- if (hasMethod && hasId) return hasValidId(record.id);
2345
- if (!hasMethod && hasId) {
2346
- if (!hasValidId(record.id)) return false;
2347
- return hasResultOrError(record);
2348
- }
2349
- return false;
2681
+ return isJsonRpcNotificationRecord(record) || isJsonRpcRequest(record) || isJsonRpcResponse(record);
2350
2682
  }
2351
2683
  function isJsonRpcNotification(message) {
2352
2684
  return Object.hasOwn(message, "method") && typeof message.method === "string" && !Object.hasOwn(message, "id");
@@ -2710,27 +3042,35 @@ var TerminalManager = class {
2710
3042
  async signalProcess(terminal, signal) {
2711
3043
  const pid = terminal.process.pid;
2712
3044
  if (terminal.killProcessGroup && pid && process.platform === "win32") {
2713
- if (!this.isRunning(terminal)) await terminal.processGroupSnapshotPromise?.catch(() => {});
2714
- for (const descendantPid of await listDescendantPids(pid)) terminal.descendantPids.add(descendantPid);
2715
- if (this.isRunning(terminal)) {
2716
- await killWindowsProcessTree(pid, signal);
2717
- return;
2718
- }
2719
- for (const descendantPid of terminal.descendantPids) await killWindowsProcessTree(descendantPid, signal);
3045
+ await this.signalWindowsProcessGroup(terminal, pid, signal);
2720
3046
  return;
2721
3047
  }
2722
3048
  if (terminal.killProcessGroup && pid) {
2723
- if (!this.isRunning(terminal)) await terminal.processGroupSnapshotPromise?.catch(() => {});
2724
- for (const descendantPid of await listDescendantPids(pid)) terminal.descendantPids.add(descendantPid);
2725
- if (process.platform !== "win32" && hasLiveProcessGroup(pid)) {
2726
- sendSignal(-pid, signal);
2727
- return;
2728
- }
2729
- for (const descendantPid of terminal.descendantPids) sendSignal(descendantPid, signal);
3049
+ await this.signalPosixProcessGroup(terminal, pid, signal);
2730
3050
  return;
2731
3051
  }
2732
3052
  terminal.process.kill(signal);
2733
3053
  }
3054
+ async signalWindowsProcessGroup(terminal, pid, signal) {
3055
+ await this.captureDescendantPids(terminal, pid);
3056
+ if (this.isRunning(terminal)) {
3057
+ await killWindowsProcessTree(pid, signal);
3058
+ return;
3059
+ }
3060
+ for (const descendantPid of terminal.descendantPids) await killWindowsProcessTree(descendantPid, signal);
3061
+ }
3062
+ async signalPosixProcessGroup(terminal, pid, signal) {
3063
+ await this.captureDescendantPids(terminal, pid);
3064
+ if (hasLiveProcessGroup(pid)) {
3065
+ sendSignal(-pid, signal);
3066
+ return;
3067
+ }
3068
+ for (const descendantPid of terminal.descendantPids) sendSignal(descendantPid, signal);
3069
+ }
3070
+ async captureDescendantPids(terminal, pid) {
3071
+ if (!this.isRunning(terminal)) await terminal.processGroupSnapshotPromise?.catch(() => {});
3072
+ for (const descendantPid of await listDescendantPids(pid)) terminal.descendantPids.add(descendantPid);
3073
+ }
2734
3074
  async waitForCleanupAfterSignal(terminal) {
2735
3075
  return await Promise.race([this.waitForTerminalAndTrackedDescendants(terminal).then(() => true), waitMs(this.killGraceMs).then(() => false)]);
2736
3076
  }
@@ -2790,16 +3130,7 @@ async function listDescendantPids(rootPid) {
2790
3130
  return [];
2791
3131
  }
2792
3132
  const childrenByParent = /* @__PURE__ */ new Map();
2793
- for (const line of output.split("\n")) {
2794
- const match = line.trim().match(/^(\d+)\s+(\d+)$/);
2795
- if (!match) continue;
2796
- const pid = Number(match[1]);
2797
- const parentPid = Number(match[2]);
2798
- if (!Number.isInteger(pid) || !Number.isInteger(parentPid) || pid <= 0 || parentPid <= 0) continue;
2799
- const children = childrenByParent.get(parentPid);
2800
- if (children) children.push(pid);
2801
- else childrenByParent.set(parentPid, [pid]);
2802
- }
3133
+ for (const line of output.split("\n")) addProcessListLine(childrenByParent, line);
2803
3134
  const descendants = [];
2804
3135
  const queue = [...childrenByParent.get(rootPid) ?? []];
2805
3136
  for (let index = 0; index < queue.length; index += 1) {
@@ -2809,6 +3140,24 @@ async function listDescendantPids(rootPid) {
2809
3140
  }
2810
3141
  return descendants;
2811
3142
  }
3143
+ function addProcessListLine(childrenByParent, line) {
3144
+ const parsed = parseProcessListLine(line);
3145
+ if (!parsed) return;
3146
+ const children = childrenByParent.get(parsed.parentPid);
3147
+ if (children) children.push(parsed.pid);
3148
+ else childrenByParent.set(parsed.parentPid, [parsed.pid]);
3149
+ }
3150
+ function parseProcessListLine(line) {
3151
+ const match = line.trim().match(/^(\d+)\s+(\d+)$/);
3152
+ if (!match) return;
3153
+ const pid = Number(match[1]);
3154
+ const parentPid = Number(match[2]);
3155
+ if (!Number.isInteger(pid) || !Number.isInteger(parentPid) || pid <= 0 || parentPid <= 0) return;
3156
+ return {
3157
+ pid,
3158
+ parentPid
3159
+ };
3160
+ }
2812
3161
  async function runProcessListCommand() {
2813
3162
  if (process.platform === "win32") return await runWindowsProcessListCommand();
2814
3163
  return await new Promise((resolve, reject) => {
@@ -2979,6 +3328,20 @@ const DRAIN_POLL_INTERVAL_MS = 20;
2979
3328
  const AGENT_CLOSE_TERM_GRACE_MS = 1500;
2980
3329
  const AGENT_CLOSE_KILL_GRACE_MS = 1e3;
2981
3330
  const STARTUP_STDERR_MAX_CHARS = 8192;
3331
+ function toReconnectedSessionResult(response) {
3332
+ return {
3333
+ agentSessionId: extractRuntimeSessionId(response?._meta),
3334
+ configOptions: response?.configOptions ?? void 0,
3335
+ models: response?.models ?? void 0
3336
+ };
3337
+ }
3338
+ function childProcessIsRunning(agent) {
3339
+ if (!agent) return false;
3340
+ return agent.exitCode == null && agent.signalCode == null && !agent.killed;
3341
+ }
3342
+ function cancelledPermissionResponse() {
3343
+ return { outcome: { outcome: "cancelled" } };
3344
+ }
2982
3345
  function shouldSuppressSdkConsoleError(args) {
2983
3346
  if (args.length === 0) return false;
2984
3347
  return typeof args[0] === "string" && args[0] === "Error handling request";
@@ -2993,6 +3356,19 @@ function installSdkConsoleErrorSuppression() {
2993
3356
  console.error = originalConsoleError;
2994
3357
  };
2995
3358
  }
3359
+ function enqueueNdJsonLine(agentCommand, line, controller) {
3360
+ const trimmedLine = line.trim();
3361
+ if (!trimmedLine || shouldIgnoreNonJsonAgentOutputLine(agentCommand, trimmedLine)) return;
3362
+ try {
3363
+ const message = JSON.parse(trimmedLine);
3364
+ controller.enqueue(message);
3365
+ } catch (err) {
3366
+ console.error("Failed to parse JSON message:", trimmedLine, err);
3367
+ }
3368
+ }
3369
+ function enqueueNdJsonLines(agentCommand, lines, controller) {
3370
+ for (const line of lines) enqueueNdJsonLine(agentCommand, line, controller);
3371
+ }
2996
3372
  function createNdJsonMessageStream(agentCommand, output, input) {
2997
3373
  const textEncoder = new TextEncoder();
2998
3374
  const textDecoder = new TextDecoder();
@@ -3008,16 +3384,7 @@ function createNdJsonMessageStream(agentCommand, output, input) {
3008
3384
  content += textDecoder.decode(value, { stream: true });
3009
3385
  const lines = content.split("\n");
3010
3386
  content = lines.pop() || "";
3011
- for (const line of lines) {
3012
- const trimmedLine = line.trim();
3013
- if (!trimmedLine || shouldIgnoreNonJsonAgentOutputLine(agentCommand, trimmedLine)) continue;
3014
- try {
3015
- const message = JSON.parse(trimmedLine);
3016
- controller.enqueue(message);
3017
- } catch (err) {
3018
- console.error("Failed to parse JSON message:", trimmedLine, err);
3019
- }
3020
- }
3387
+ enqueueNdJsonLines(agentCommand, lines, controller);
3021
3388
  }
3022
3389
  } finally {
3023
3390
  reader.releaseLock();
@@ -3105,7 +3472,7 @@ var AcpClient = class {
3105
3472
  }
3106
3473
  getAgentLifecycleSnapshot() {
3107
3474
  const pid = this.agent?.pid ?? this.lastKnownPid;
3108
- const running = Boolean(this.agent) && this.agent?.exitCode == null && this.agent?.signalCode == null && !this.agent?.killed;
3475
+ const running = childProcessIsRunning(this.agent);
3109
3476
  return {
3110
3477
  pid,
3111
3478
  startedAt: this.agentStartedAt,
@@ -3116,9 +3483,15 @@ var AcpClient = class {
3116
3483
  supportsLoadSession() {
3117
3484
  return Boolean(this.initResult?.agentCapabilities?.loadSession);
3118
3485
  }
3486
+ supportsResumeSession() {
3487
+ return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.resume);
3488
+ }
3119
3489
  supportsCloseSession() {
3120
3490
  return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.close);
3121
3491
  }
3492
+ supportsListSessions() {
3493
+ return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.list);
3494
+ }
3122
3495
  setEventHandlers(handlers) {
3123
3496
  this.eventHandlers = { ...handlers };
3124
3497
  }
@@ -3126,17 +3499,20 @@ var AcpClient = class {
3126
3499
  this.eventHandlers = {};
3127
3500
  }
3128
3501
  updateRuntimeOptions(options) {
3502
+ const shouldRefreshPermissionPolicy = options.permissionMode !== void 0 || options.nonInteractivePermissions !== void 0;
3129
3503
  if (options.permissionMode) this.options.permissionMode = options.permissionMode;
3130
3504
  if (options.nonInteractivePermissions !== void 0) this.options.nonInteractivePermissions = options.nonInteractivePermissions;
3131
3505
  if (Object.prototype.hasOwnProperty.call(options, "permissionPolicy")) this.options.permissionPolicy = options.permissionPolicy;
3132
3506
  if (options.terminal !== void 0) this.options.terminal = options.terminal;
3133
- if (options.permissionMode || options.nonInteractivePermissions !== void 0) {
3134
- this.filesystem.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
3135
- this.terminalManager.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
3136
- }
3507
+ this.refreshRuntimePermissionPolicy(shouldRefreshPermissionPolicy);
3137
3508
  if (options.suppressSdkConsoleErrors !== void 0) this.options.suppressSdkConsoleErrors = options.suppressSdkConsoleErrors;
3138
3509
  if (options.verbose !== void 0) this.options.verbose = options.verbose;
3139
3510
  }
3511
+ refreshRuntimePermissionPolicy(enabled) {
3512
+ if (!enabled) return;
3513
+ this.filesystem.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
3514
+ this.terminalManager.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
3515
+ }
3140
3516
  hasReusableSession(sessionId) {
3141
3517
  return this.connection != null && this.agent != null && isChildProcessRunning(this.agent) && this.loadedSessionId === sessionId;
3142
3518
  }
@@ -3148,32 +3524,10 @@ var AcpClient = class {
3148
3524
  async start() {
3149
3525
  if (this.connection && this.agent && isChildProcessRunning(this.agent)) return;
3150
3526
  if (this.connection || this.agent) await this.close();
3151
- const configuredCommand = splitCommandLine(this.options.agentCommand);
3152
- const resolvedBuiltInLaunch = resolveBuiltInAgentLaunch(this.options.agentCommand);
3153
- const spawnCommand = resolvedBuiltInLaunch?.command ?? configuredCommand.command;
3154
- let args = resolvedBuiltInLaunch?.args ?? configuredCommand.args;
3155
- args = await resolveGeminiCommandArgs(spawnCommand, args);
3156
- if (isQoderAcpCommand(spawnCommand, args)) args = buildQoderAcpCommandArgs(args, this.options);
3157
- if (resolvedBuiltInLaunch?.source === "installed") this.log(`spawning installed built-in agent ${resolvedBuiltInLaunch.packageName}${resolvedBuiltInLaunch.packageVersion ? `@${resolvedBuiltInLaunch.packageVersion}` : ""} via ${spawnCommand} ${args.join(" ")}`);
3158
- else if (resolvedBuiltInLaunch?.source === "package-exec") this.log(`spawning built-in agent ${resolvedBuiltInLaunch.packageName}@${resolvedBuiltInLaunch.packageRange} via current Node package exec bridge ${spawnCommand} ${args.join(" ")}`);
3159
- else this.log(`spawning agent: ${spawnCommand} ${args.join(" ")}`);
3160
- const geminiAcp = isGeminiAcpCommand(spawnCommand, args);
3161
- if (isCopilotAcpCommand(spawnCommand, args)) await ensureCopilotAcpSupport(spawnCommand);
3162
- const agentSpawnOptions = buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials);
3163
- if (isClaudeAcpCommand(spawnCommand, args)) {
3164
- const claudeExe = resolveClaudeCodeExecutable(process.platform, agentSpawnOptions.env);
3165
- if (claudeExe) {
3166
- agentSpawnOptions.env.CLAUDE_CODE_EXECUTABLE = claudeExe;
3167
- this.log(`resolved system Claude Code executable: ${claudeExe}`);
3168
- }
3169
- }
3170
- const spawnedChild = spawn(spawnCommand, args, buildSpawnCommandOptions(spawnCommand, agentSpawnOptions));
3171
- try {
3172
- await waitForSpawn$1(spawnedChild);
3173
- } catch (error) {
3174
- throw new AgentSpawnError(this.options.agentCommand, error);
3175
- }
3176
- const child = requireAgentStdio(spawnedChild);
3527
+ const launch = await this.resolveAgentLaunchPlan();
3528
+ this.logAgentLaunch(launch);
3529
+ await this.ensureLaunchSupport(launch);
3530
+ const child = await this.spawnAgentProcess(launch);
3177
3531
  this.closing = false;
3178
3532
  this.agentStartedAt = isoNow$1();
3179
3533
  this.lastAgentExit = void 0;
@@ -3187,17 +3541,79 @@ var AcpClient = class {
3187
3541
  });
3188
3542
  const input = Writable.toWeb(child.stdin);
3189
3543
  const output = Readable.toWeb(child.stdout);
3190
- const connection = new ClientSideConnection(() => ({
3191
- sessionUpdate: async (params) => {
3192
- await this.handleSessionUpdate(params);
3193
- },
3194
- requestPermission: async (params) => {
3195
- return this.handlePermissionRequest(params);
3196
- },
3197
- readTextFile: async (params) => {
3198
- return this.handleReadTextFile(params);
3199
- },
3200
- writeTextFile: async (params) => {
3544
+ const stream = this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output));
3545
+ const connection = this.createConnection(stream);
3546
+ connection.signal.addEventListener("abort", () => {
3547
+ this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null);
3548
+ }, { once: true });
3549
+ const startupFailure = this.createStartupFailureWatcher(child, startupStderr);
3550
+ await this.initializeAgentConnection({
3551
+ child,
3552
+ connection,
3553
+ startupFailure,
3554
+ startupStderr,
3555
+ launch
3556
+ });
3557
+ }
3558
+ async resolveAgentLaunchPlan() {
3559
+ const configuredCommand = splitCommandLine(this.options.agentCommand);
3560
+ const resolvedBuiltInLaunch = resolveBuiltInAgentLaunch(this.options.agentCommand);
3561
+ const spawnCommand = resolvedBuiltInLaunch?.command ?? configuredCommand.command;
3562
+ let args = resolvedBuiltInLaunch?.args ?? configuredCommand.args;
3563
+ args = await resolveGeminiCommandArgs(spawnCommand, args);
3564
+ if (isQoderAcpCommand(spawnCommand, args)) args = buildQoderAcpCommandArgs(args, this.options);
3565
+ return {
3566
+ spawnCommand,
3567
+ args,
3568
+ resolvedBuiltInLaunch,
3569
+ geminiAcp: isGeminiAcpCommand(spawnCommand, args),
3570
+ copilotAcp: isCopilotAcpCommand(spawnCommand, args),
3571
+ claudeAcp: isClaudeAcpCommand(spawnCommand, args),
3572
+ spawnOptions: buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials)
3573
+ };
3574
+ }
3575
+ logAgentLaunch(plan) {
3576
+ const launch = plan.resolvedBuiltInLaunch;
3577
+ if (launch?.source === "installed") {
3578
+ this.log(`spawning installed built-in agent ${launch.packageName}${launch.packageVersion ? `@${launch.packageVersion}` : ""} via ${plan.spawnCommand} ${plan.args.join(" ")}`);
3579
+ return;
3580
+ }
3581
+ if (launch?.source === "package-exec") {
3582
+ this.log(`spawning built-in agent ${launch.packageName}@${launch.packageRange} via current Node package exec bridge ${plan.spawnCommand} ${plan.args.join(" ")}`);
3583
+ return;
3584
+ }
3585
+ this.log(`spawning agent: ${plan.spawnCommand} ${plan.args.join(" ")}`);
3586
+ }
3587
+ async ensureLaunchSupport(plan) {
3588
+ if (plan.copilotAcp) await ensureCopilotAcpSupport(plan.spawnCommand);
3589
+ if (!plan.claudeAcp) return;
3590
+ const claudeExe = resolveClaudeCodeExecutable(process.platform, plan.spawnOptions.env);
3591
+ if (claudeExe) {
3592
+ plan.spawnOptions.env.CLAUDE_CODE_EXECUTABLE = claudeExe;
3593
+ this.log(`resolved system Claude Code executable: ${claudeExe}`);
3594
+ }
3595
+ }
3596
+ async spawnAgentProcess(plan) {
3597
+ const spawnedChild = spawn(plan.spawnCommand, plan.args, buildSpawnCommandOptions(plan.spawnCommand, plan.spawnOptions));
3598
+ try {
3599
+ await waitForSpawn$1(spawnedChild);
3600
+ } catch (error) {
3601
+ throw new AgentSpawnError(this.options.agentCommand, error);
3602
+ }
3603
+ return requireAgentStdio(spawnedChild);
3604
+ }
3605
+ createConnection(stream) {
3606
+ return new ClientSideConnection(() => ({
3607
+ sessionUpdate: async (params) => {
3608
+ await this.handleSessionUpdate(params);
3609
+ },
3610
+ requestPermission: async (params) => {
3611
+ return this.handlePermissionRequest(params);
3612
+ },
3613
+ readTextFile: async (params) => {
3614
+ return this.handleReadTextFile(params);
3615
+ },
3616
+ writeTextFile: async (params) => {
3201
3617
  return this.handleWriteTextFile(params);
3202
3618
  },
3203
3619
  createTerminal: async (params) => {
@@ -3215,49 +3631,51 @@ var AcpClient = class {
3215
3631
  releaseTerminal: async (params) => {
3216
3632
  return this.handleReleaseTerminal(params);
3217
3633
  }
3218
- }), this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output)));
3219
- connection.signal.addEventListener("abort", () => {
3220
- this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null);
3221
- }, { once: true });
3222
- const startupFailure = this.createStartupFailureWatcher(child, startupStderr);
3634
+ }), stream);
3635
+ }
3636
+ async initializeAgentConnection(params) {
3223
3637
  try {
3224
- const initResult = await Promise.race([(async () => {
3225
- const initializePromise = connection.initialize({
3226
- protocolVersion: PROTOCOL_VERSION,
3227
- clientCapabilities: {
3228
- fs: {
3229
- readTextFile: true,
3230
- writeTextFile: true
3231
- },
3232
- terminal: this.options.terminal !== false
3233
- },
3234
- clientInfo: {
3235
- name: "acpx",
3236
- version: "0.1.0"
3237
- }
3238
- });
3239
- const initialized = geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise;
3240
- await this.authenticateIfRequired(connection, initialized.authMethods ?? []);
3241
- return initialized;
3242
- })(), startupFailure.promise]);
3243
- startupFailure.dispose();
3244
- this.connection = connection;
3245
- this.agent = child;
3638
+ const initResult = await Promise.race([this.initializeProtocolConnection(params.connection, params.launch.geminiAcp), params.startupFailure.promise]);
3639
+ params.startupFailure.dispose();
3640
+ this.connection = params.connection;
3641
+ this.agent = params.child;
3246
3642
  this.initResult = initResult;
3247
3643
  this.log(`initialized protocol version ${initResult.protocolVersion}`);
3248
3644
  } catch (error) {
3249
- startupFailure.dispose();
3250
- const normalizedError = await this.normalizeInitializeError(error, child, startupStderr);
3251
- try {
3252
- child.kill();
3253
- } catch {}
3254
- if (geminiAcp && error instanceof TimeoutError) throw new GeminiAcpStartupTimeoutError(await buildGeminiAcpStartupTimeoutMessage(spawnCommand), {
3255
- cause: error,
3256
- retryable: true
3257
- });
3258
- throw normalizedError;
3645
+ await this.handleInitializeFailure(params, error);
3259
3646
  }
3260
3647
  }
3648
+ async initializeProtocolConnection(connection, geminiAcp) {
3649
+ const initializePromise = connection.initialize({
3650
+ protocolVersion: PROTOCOL_VERSION,
3651
+ clientCapabilities: {
3652
+ fs: {
3653
+ readTextFile: true,
3654
+ writeTextFile: true
3655
+ },
3656
+ terminal: this.options.terminal !== false
3657
+ },
3658
+ clientInfo: {
3659
+ name: "acpx",
3660
+ version: "0.1.0"
3661
+ }
3662
+ });
3663
+ const initialized = geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise;
3664
+ await this.authenticateIfRequired(connection, initialized.authMethods ?? []);
3665
+ return initialized;
3666
+ }
3667
+ async handleInitializeFailure(params, error) {
3668
+ params.startupFailure.dispose();
3669
+ const normalizedError = await this.normalizeInitializeError(error, params.child, params.startupStderr);
3670
+ try {
3671
+ params.child.kill();
3672
+ } catch {}
3673
+ if (params.launch.geminiAcp && error instanceof TimeoutError) throw new GeminiAcpStartupTimeoutError(await buildGeminiAcpStartupTimeoutMessage(params.launch.spawnCommand), {
3674
+ cause: error,
3675
+ retryable: true
3676
+ });
3677
+ throw normalizedError;
3678
+ }
3261
3679
  createTappedStream(base) {
3262
3680
  const onAcpMessage = () => this.eventHandlers.onAcpMessage;
3263
3681
  const onAcpOutputMessage = () => this.eventHandlers.onAcpOutputMessage;
@@ -3330,10 +3748,7 @@ var AcpClient = class {
3330
3748
  async loadSessionWithOptions(sessionId, cwd = this.options.cwd, options = {}) {
3331
3749
  const connection = this.getConnection();
3332
3750
  const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
3333
- const previousSuppression = this.suppressSessionUpdates;
3334
- const previousReplaySuppression = this.suppressReplaySessionUpdateMessages;
3335
- this.suppressSessionUpdates = previousSuppression || Boolean(options.suppressReplayUpdates);
3336
- this.suppressReplaySessionUpdateMessages = previousReplaySuppression || Boolean(options.suppressReplayUpdates);
3751
+ const previousSuppression = this.applySessionUpdateSuppression(Boolean(options.suppressReplayUpdates));
3337
3752
  let response;
3338
3753
  try {
3339
3754
  response = await this.runConnectionRequest(() => connection.loadSession({
@@ -3343,24 +3758,44 @@ var AcpClient = class {
3343
3758
  }));
3344
3759
  await this.waitForSessionUpdateDrain(options.replayIdleMs ?? REPLAY_IDLE_MS, options.replayDrainTimeoutMs ?? REPLAY_DRAIN_TIMEOUT_MS);
3345
3760
  } finally {
3346
- this.suppressSessionUpdates = previousSuppression;
3347
- this.suppressReplaySessionUpdateMessages = previousReplaySuppression;
3761
+ this.restoreSessionUpdateSuppression(previousSuppression);
3348
3762
  }
3349
3763
  this.loadedSessionId = sessionId;
3350
- return {
3351
- agentSessionId: extractRuntimeSessionId(response?._meta),
3352
- configOptions: response?.configOptions ?? void 0,
3353
- models: response?.models ?? void 0
3764
+ return toReconnectedSessionResult(response);
3765
+ }
3766
+ async resumeSession(sessionId, cwd = this.options.cwd) {
3767
+ const connection = this.getConnection();
3768
+ const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
3769
+ const response = await this.runConnectionRequest(() => connection.resumeSession({
3770
+ sessionId,
3771
+ cwd: sessionCwd,
3772
+ mcpServers: this.options.mcpServers ?? []
3773
+ }));
3774
+ this.loadedSessionId = sessionId;
3775
+ return toReconnectedSessionResult(response);
3776
+ }
3777
+ applySessionUpdateSuppression(enabled) {
3778
+ const previous = {
3779
+ suppressSessionUpdates: this.suppressSessionUpdates,
3780
+ suppressReplaySessionUpdateMessages: this.suppressReplaySessionUpdateMessages
3354
3781
  };
3782
+ this.suppressSessionUpdates = previous.suppressSessionUpdates || enabled;
3783
+ this.suppressReplaySessionUpdateMessages = previous.suppressReplaySessionUpdateMessages || enabled;
3784
+ return previous;
3785
+ }
3786
+ restoreSessionUpdateSuppression(previous) {
3787
+ this.suppressSessionUpdates = previous.suppressSessionUpdates;
3788
+ this.suppressReplaySessionUpdateMessages = previous.suppressReplaySessionUpdateMessages;
3355
3789
  }
3356
3790
  async prompt(sessionId, prompt) {
3357
3791
  const connection = this.getConnection();
3792
+ const normalizedPrompt = this.normalizePromptForAgent(prompt);
3358
3793
  const restoreConsoleError = this.options.suppressSdkConsoleErrors ? installSdkConsoleErrorSuppression() : void 0;
3359
3794
  let promptPromise;
3360
3795
  try {
3361
3796
  promptPromise = this.runConnectionRequest(() => connection.prompt({
3362
3797
  sessionId,
3363
- prompt: typeof prompt === "string" ? textPrompt(prompt) : prompt
3798
+ prompt: normalizedPrompt
3364
3799
  }));
3365
3800
  } catch (error) {
3366
3801
  restoreConsoleError?.();
@@ -3371,13 +3806,9 @@ var AcpClient = class {
3371
3806
  promise: promptPromise
3372
3807
  };
3373
3808
  try {
3374
- const response = await promptPromise;
3375
- const permissionFailure = this.consumePromptPermissionFailure(sessionId);
3376
- if (permissionFailure) throw permissionFailure;
3377
- return response;
3809
+ return this.returnPromptResponseOrPermissionFailure(sessionId, await promptPromise);
3378
3810
  } catch (error) {
3379
- const permissionFailure = this.consumePromptPermissionFailure(sessionId);
3380
- if (permissionFailure) throw permissionFailure;
3811
+ this.throwPromptPermissionFailureIfPresent(sessionId);
3381
3812
  throw error;
3382
3813
  } finally {
3383
3814
  restoreConsoleError?.();
@@ -3387,6 +3818,20 @@ var AcpClient = class {
3387
3818
  this.promptPermissionFailures.delete(sessionId);
3388
3819
  }
3389
3820
  }
3821
+ normalizePromptForAgent(prompt) {
3822
+ const normalizedPrompt = typeof prompt === "string" ? textPrompt(prompt) : prompt;
3823
+ const unsupportedPromptContent = getUnsupportedPromptContentMessage(normalizedPrompt, this.initResult?.agentCapabilities);
3824
+ if (unsupportedPromptContent) throw new UnsupportedPromptContentError(unsupportedPromptContent);
3825
+ return normalizedPrompt;
3826
+ }
3827
+ returnPromptResponseOrPermissionFailure(sessionId, response) {
3828
+ this.throwPromptPermissionFailureIfPresent(sessionId);
3829
+ return response;
3830
+ }
3831
+ throwPromptPermissionFailureIfPresent(sessionId) {
3832
+ const permissionFailure = this.consumePromptPermissionFailure(sessionId);
3833
+ if (permissionFailure) throw permissionFailure;
3834
+ }
3390
3835
  async setSessionMode(sessionId, modeId) {
3391
3836
  const connection = this.getConnection();
3392
3837
  try {
@@ -3437,6 +3882,10 @@ var AcpClient = class {
3437
3882
  await this.runConnectionRequest(() => connection.closeSession({ sessionId }));
3438
3883
  if (this.loadedSessionId === sessionId) this.loadedSessionId = void 0;
3439
3884
  }
3885
+ async listSessions(params = {}) {
3886
+ const connection = this.getConnection();
3887
+ return await this.runConnectionRequest(() => connection.listSessions(params));
3888
+ }
3440
3889
  async requestCancelActivePrompt() {
3441
3890
  const active = this.activePrompt;
3442
3891
  if (!active) return false;
@@ -3486,25 +3935,28 @@ var AcpClient = class {
3486
3935
  }
3487
3936
  async terminateAgentProcess(child) {
3488
3937
  const stdinCloseGraceMs = resolveAgentCloseAfterStdinEndMs(this.options.agentCommand);
3489
- if (!child.stdin.destroyed) try {
3490
- child.stdin.end();
3491
- } catch {}
3938
+ this.endAgentStdin(child);
3492
3939
  let exited = await waitForChildExit(child, stdinCloseGraceMs);
3493
- if (!exited && isChildProcessRunning(child)) {
3494
- try {
3495
- child.kill("SIGTERM");
3496
- } catch {}
3497
- exited = await waitForChildExit(child, AGENT_CLOSE_TERM_GRACE_MS);
3498
- }
3499
- if (!exited && isChildProcessRunning(child)) {
3940
+ exited = await this.killAgentIfRunning(child, exited, "SIGTERM", AGENT_CLOSE_TERM_GRACE_MS);
3941
+ if (!exited) {
3500
3942
  this.log(`agent did not exit after ${AGENT_CLOSE_TERM_GRACE_MS}ms; forcing SIGKILL`);
3501
- try {
3502
- child.kill("SIGKILL");
3503
- } catch {}
3504
- exited = await waitForChildExit(child, AGENT_CLOSE_KILL_GRACE_MS);
3943
+ exited = await this.killAgentIfRunning(child, exited, "SIGKILL", AGENT_CLOSE_KILL_GRACE_MS);
3505
3944
  }
3506
3945
  this.detachAgentHandles(child, !exited);
3507
3946
  }
3947
+ endAgentStdin(child) {
3948
+ if (child.stdin.destroyed) return;
3949
+ try {
3950
+ child.stdin.end();
3951
+ } catch {}
3952
+ }
3953
+ async killAgentIfRunning(child, alreadyExited, signal, waitMs) {
3954
+ if (alreadyExited || !isChildProcessRunning(child)) return alreadyExited;
3955
+ try {
3956
+ child.kill(signal);
3957
+ } catch {}
3958
+ return await waitForChildExit(child, waitMs);
3959
+ }
3508
3960
  detachAgentHandles(agent, unref) {
3509
3961
  const stdin = agent.stdin;
3510
3962
  const stdout = agent.stdout;
@@ -3625,49 +4077,71 @@ var AcpClient = class {
3625
4077
  this.log(`authenticated with method ${selected.methodId} (${selected.source})`);
3626
4078
  }
3627
4079
  async handlePermissionRequest(params) {
3628
- if (this.cancellingSessionIds.has(params.sessionId)) return { outcome: { outcome: "cancelled" } };
3629
- if (this.options.onPermissionRequest) {
3630
- const signal = this.cancellationSignalForSession(params.sessionId);
3631
- try {
3632
- const decision = await this.options.onPermissionRequest({
3633
- sessionId: params.sessionId,
3634
- raw: params,
3635
- inferredKind: inferToolKind(params)
3636
- }, { signal });
3637
- if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
3638
- this.recordPermissionDecision("cancelled");
3639
- return { outcome: { outcome: "cancelled" } };
3640
- }
3641
- if (decision) {
3642
- const response = decisionToResponse(params, decision);
3643
- this.recordPermissionDecision(classifyPermissionDecision(params, response));
3644
- return response;
3645
- }
3646
- } catch (error) {
3647
- if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
3648
- this.recordPermissionDecision("cancelled");
3649
- return { outcome: { outcome: "cancelled" } };
3650
- }
3651
- this.log(`onPermissionRequest threw, falling through to mode-based resolver: ${error instanceof Error ? error.message : String(error)}`);
3652
- }
4080
+ if (this.cancellingSessionIds.has(params.sessionId)) return cancelledPermissionResponse();
4081
+ const hostResponse = await this.tryHandlePermissionRequestWithHost(params);
4082
+ if (hostResponse) return hostResponse;
4083
+ const { response, recorded } = await this.resolvePermissionRequestFromMode(params);
4084
+ if (!recorded) {
4085
+ const decision = classifyPermissionDecision(params, response);
4086
+ this.recordPermissionDecision(decision);
3653
4087
  }
3654
- let response;
4088
+ return response;
4089
+ }
4090
+ async tryHandlePermissionRequestWithHost(params) {
4091
+ if (!this.options.onPermissionRequest) return;
4092
+ const signal = this.cancellationSignalForSession(params.sessionId);
3655
4093
  try {
3656
- const result = await resolvePermissionRequestWithDetails(params, this.options.permissionMode, this.options.nonInteractivePermissions ?? "deny", this.options.permissionPolicy);
3657
- response = result.response;
3658
- if (result.escalation) this.eventHandlers.onPermissionEscalation?.(result.escalation);
4094
+ const decision = await this.options.onPermissionRequest({
4095
+ sessionId: params.sessionId,
4096
+ raw: params,
4097
+ inferredKind: inferToolKind(params)
4098
+ }, { signal });
4099
+ return this.hostPermissionDecisionResponse(params, signal, decision);
3659
4100
  } catch (error) {
3660
- if (error instanceof PermissionPromptUnavailableError) {
3661
- this.notePromptPermissionFailure(params.sessionId, error);
3662
- this.recordPermissionDecision("cancelled");
3663
- return { outcome: { outcome: "cancelled" } };
3664
- }
3665
- throw error;
4101
+ return this.hostPermissionErrorResponse(params, signal, error);
3666
4102
  }
3667
- const decision = classifyPermissionDecision(params, response);
3668
- this.recordPermissionDecision(decision);
4103
+ }
4104
+ hostPermissionDecisionResponse(params, signal, decision) {
4105
+ if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
4106
+ this.recordPermissionDecision("cancelled");
4107
+ return cancelledPermissionResponse();
4108
+ }
4109
+ if (!decision) return;
4110
+ const response = decisionToResponse(params, decision);
4111
+ this.recordPermissionDecision(classifyPermissionDecision(params, response));
3669
4112
  return response;
3670
4113
  }
4114
+ hostPermissionErrorResponse(params, signal, error) {
4115
+ if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
4116
+ this.recordPermissionDecision("cancelled");
4117
+ return cancelledPermissionResponse();
4118
+ }
4119
+ this.log(`onPermissionRequest threw, falling through to mode-based resolver: ${error instanceof Error ? error.message : String(error)}`);
4120
+ }
4121
+ async resolvePermissionRequestFromMode(params) {
4122
+ try {
4123
+ const result = await resolvePermissionRequestWithDetails(params, this.options.permissionMode, this.options.nonInteractivePermissions ?? "deny", this.options.permissionPolicy);
4124
+ this.emitPermissionEscalation(result.escalation);
4125
+ return {
4126
+ response: result.response,
4127
+ recorded: false
4128
+ };
4129
+ } catch (error) {
4130
+ return this.handleModePermissionError(params.sessionId, error);
4131
+ }
4132
+ }
4133
+ emitPermissionEscalation(escalation) {
4134
+ if (escalation) this.eventHandlers.onPermissionEscalation?.(escalation);
4135
+ }
4136
+ handleModePermissionError(sessionId, error) {
4137
+ if (!(error instanceof PermissionPromptUnavailableError)) throw error;
4138
+ this.notePromptPermissionFailure(sessionId, error);
4139
+ this.recordPermissionDecision("cancelled");
4140
+ return {
4141
+ response: cancelledPermissionResponse(),
4142
+ recorded: true
4143
+ };
4144
+ }
3671
4145
  attachAgentLifecycleObservers(child) {
3672
4146
  child.once("exit", (exitCode, signal) => {
3673
4147
  this.recordAgentExit("process_exit", exitCode, signal);
@@ -3837,25 +4311,54 @@ var AcpClient = class {
3837
4311
  }
3838
4312
  };
3839
4313
  //#endregion
4314
+ //#region src/runtime/engine/lifecycle.ts
4315
+ function applyLifecycleSnapshotToRecord(record, snapshot) {
4316
+ if (!snapshot) return;
4317
+ record.pid = snapshot.running ? snapshot.pid : void 0;
4318
+ record.agentStartedAt = snapshot.startedAt;
4319
+ if (snapshot.lastExit) {
4320
+ record.lastAgentExitCode = snapshot.lastExit.exitCode;
4321
+ record.lastAgentExitSignal = snapshot.lastExit.signal;
4322
+ record.lastAgentExitAt = snapshot.lastExit.exitedAt;
4323
+ record.lastAgentDisconnectReason = snapshot.lastExit.reason;
4324
+ return;
4325
+ }
4326
+ record.lastAgentExitCode = void 0;
4327
+ record.lastAgentExitSignal = void 0;
4328
+ record.lastAgentExitAt = void 0;
4329
+ record.lastAgentDisconnectReason = void 0;
4330
+ }
4331
+ function reconcileAgentSessionId(record, agentSessionId) {
4332
+ const normalized = normalizeRuntimeSessionId(agentSessionId);
4333
+ if (!normalized) return;
4334
+ record.agentSessionId = normalized;
4335
+ }
4336
+ function sessionHasAgentMessages(recordOrConversation) {
4337
+ return recordOrConversation.messages.some((message) => typeof message === "object" && message !== null && "Agent" in message);
4338
+ }
4339
+ function applyConversation(record, conversation) {
4340
+ record.title = conversation.title;
4341
+ record.updated_at = conversation.updated_at;
4342
+ record.messages = conversation.messages;
4343
+ record.cumulative_token_usage = conversation.cumulative_token_usage;
4344
+ record.request_token_usage = conversation.request_token_usage;
4345
+ }
4346
+ //#endregion
3840
4347
  //#region src/runtime/engine/session-options.ts
3841
4348
  function mergeSessionOptions(preferred, fallback) {
3842
4349
  const merged = { ...fallback };
3843
- if (preferred?.model !== void 0) merged.model = preferred.model;
3844
- if (preferred?.allowedTools !== void 0) merged.allowedTools = preferred.allowedTools;
3845
- if (preferred?.maxTurns !== void 0) merged.maxTurns = preferred.maxTurns;
3846
- if (preferred?.systemPrompt !== void 0) merged.systemPrompt = preferred.systemPrompt;
4350
+ assignDefinedOption(merged, "model", preferred?.model);
4351
+ assignDefinedOption(merged, "allowedTools", preferred?.allowedTools);
4352
+ assignDefinedOption(merged, "maxTurns", preferred?.maxTurns);
4353
+ assignDefinedOption(merged, "systemPrompt", preferred?.systemPrompt);
3847
4354
  return Object.keys(merged).length > 0 ? merged : void 0;
3848
4355
  }
4356
+ function assignDefinedOption(target, key, value) {
4357
+ if (value !== void 0) target[key] = value;
4358
+ }
3849
4359
  function persistSessionOptions(record, options) {
3850
- const systemPromptOption = options?.systemPrompt;
3851
- const normalizedSystemPrompt = typeof systemPromptOption === "string" && systemPromptOption.length > 0 ? systemPromptOption : systemPromptOption && typeof systemPromptOption === "object" && typeof systemPromptOption.append === "string" && systemPromptOption.append.length > 0 ? { append: systemPromptOption.append } : void 0;
3852
- const next = options && {
3853
- model: typeof options.model === "string" ? options.model : void 0,
3854
- allowed_tools: Array.isArray(options.allowedTools) ? [...options.allowedTools] : void 0,
3855
- max_turns: typeof options.maxTurns === "number" ? options.maxTurns : void 0,
3856
- system_prompt: normalizedSystemPrompt
3857
- };
3858
- if (Boolean(next && (typeof next.model === "string" && next.model.trim().length > 0 || Array.isArray(next.allowed_tools) || typeof next.max_turns === "number" || next.system_prompt !== void 0)) && next) {
4360
+ const next = options === void 0 ? void 0 : persistedSessionOptions(options);
4361
+ if (next !== void 0) {
3859
4362
  record.acpx = {
3860
4363
  ...record.acpx,
3861
4364
  session_options: next
@@ -3869,14 +4372,49 @@ function sessionOptionsFromRecord(record) {
3869
4372
  const stored = record.acpx?.session_options;
3870
4373
  if (!stored) return;
3871
4374
  const sessionOptions = {};
3872
- if (typeof stored.model === "string" && stored.model.trim().length > 0) sessionOptions.model = stored.model;
3873
- if (Array.isArray(stored.allowed_tools)) sessionOptions.allowedTools = [...stored.allowed_tools];
3874
- if (typeof stored.max_turns === "number") sessionOptions.maxTurns = stored.max_turns;
3875
- const storedSystemPrompt = stored.system_prompt;
3876
- if (typeof storedSystemPrompt === "string" && storedSystemPrompt.length > 0) sessionOptions.systemPrompt = storedSystemPrompt;
3877
- else if (storedSystemPrompt && typeof storedSystemPrompt === "object" && typeof storedSystemPrompt.append === "string" && storedSystemPrompt.append.length > 0) sessionOptions.systemPrompt = { append: storedSystemPrompt.append };
4375
+ assignStoredOption(sessionOptions, "model", nonEmptyString(stored.model));
4376
+ assignStoredOption(sessionOptions, "allowedTools", storedAllowedTools(stored.allowed_tools));
4377
+ assignStoredOption(sessionOptions, "maxTurns", storedMaxTurns(stored.max_turns));
4378
+ assignStoredOption(sessionOptions, "systemPrompt", storedSystemPromptOption(stored.system_prompt));
3878
4379
  return Object.keys(sessionOptions).length > 0 ? sessionOptions : void 0;
3879
4380
  }
4381
+ function persistedSessionOptions(options) {
4382
+ const next = {
4383
+ model: nonEmptyString(options.model),
4384
+ allowed_tools: Array.isArray(options.allowedTools) ? [...options.allowedTools] : void 0,
4385
+ max_turns: typeof options.maxTurns === "number" ? options.maxTurns : void 0,
4386
+ system_prompt: normalizeSystemPromptOption(options.systemPrompt)
4387
+ };
4388
+ return hasPersistedSessionOptions(next) ? next : void 0;
4389
+ }
4390
+ function hasPersistedSessionOptions(options) {
4391
+ return options.model !== void 0 || options.allowed_tools !== void 0 || options.max_turns !== void 0 || options.system_prompt !== void 0;
4392
+ }
4393
+ function normalizeSystemPromptOption(value) {
4394
+ const prompt = nonEmptyString(value);
4395
+ if (prompt !== void 0) return prompt;
4396
+ const append = appendedSystemPrompt(value);
4397
+ return append === void 0 ? void 0 : { append };
4398
+ }
4399
+ function appendedSystemPrompt(value) {
4400
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return;
4401
+ return nonEmptyString(value.append);
4402
+ }
4403
+ function assignStoredOption(target, key, value) {
4404
+ assignDefinedOption(target, key, value);
4405
+ }
4406
+ function storedAllowedTools(value) {
4407
+ return Array.isArray(value) && value.every((item) => typeof item === "string") ? [...value] : void 0;
4408
+ }
4409
+ function storedMaxTurns(value) {
4410
+ return typeof value === "number" ? value : void 0;
4411
+ }
4412
+ function storedSystemPromptOption(value) {
4413
+ return normalizeSystemPromptOption(value);
4414
+ }
4415
+ function nonEmptyString(value) {
4416
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
4417
+ }
3880
4418
  //#endregion
3881
4419
  //#region src/session/conversation-model.ts
3882
4420
  const MAX_RUNTIME_MESSAGES = 200;
@@ -3903,13 +4441,17 @@ function normalizeAgentName(value) {
3903
4441
  return trimmed.length > 0 ? trimmed : void 0;
3904
4442
  }
3905
4443
  function extractText(content) {
3906
- if (content.type === "text") return content.text;
3907
- if (content.type === "resource_link") return content.title ?? content.name ?? content.uri;
3908
- if (content.type === "resource") {
3909
- if ("text" in content.resource && typeof content.resource.text === "string") return content.resource.text;
3910
- return content.resource.uri;
4444
+ switch (content.type) {
4445
+ case "text": return content.text;
4446
+ case "resource_link": return content.title ?? content.name ?? content.uri;
4447
+ case "resource": return extractResourceText(content);
4448
+ case "audio": return `[audio] ${content.mimeType}`;
4449
+ default: return;
3911
4450
  }
3912
4451
  }
4452
+ function extractResourceText(content) {
4453
+ return "text" in content.resource && typeof content.resource.text === "string" ? content.resource.text : content.resource.uri;
4454
+ }
3913
4455
  function contentToUserContent(content) {
3914
4456
  if (content.type === "text") return { Text: content.text };
3915
4457
  if (content.type === "resource_link") {
@@ -3919,17 +4461,22 @@ function contentToUserContent(content) {
3919
4461
  content: value
3920
4462
  } };
3921
4463
  }
3922
- if (content.type === "resource") {
3923
- if ("text" in content.resource && typeof content.resource.text === "string") return { Text: content.resource.text };
3924
- return { Mention: {
3925
- uri: content.resource.uri,
3926
- content: content.resource.uri
3927
- } };
3928
- }
4464
+ if (content.type === "resource") return resourceToUserContent(content);
3929
4465
  if (content.type === "image") return { Image: {
3930
4466
  source: content.data,
3931
4467
  size: null
3932
4468
  } };
4469
+ if (content.type === "audio") return { Audio: {
4470
+ source: content.data,
4471
+ mime_type: content.mimeType
4472
+ } };
4473
+ }
4474
+ function resourceToUserContent(content) {
4475
+ if ("text" in content.resource && typeof content.resource.text === "string") return { Text: content.resource.text };
4476
+ return { Mention: {
4477
+ uri: content.resource.uri,
4478
+ content: content.resource.uri
4479
+ } };
3933
4480
  }
3934
4481
  function nextUserMessageId() {
3935
4482
  return randomUUID();
@@ -4031,38 +4578,67 @@ function ensureToolUseContent(agent, toolCallId) {
4031
4578
  }
4032
4579
  function upsertToolResult(agent, toolCallId, patch) {
4033
4580
  const existing = agent.tool_results[toolCallId];
4581
+ const fallback = existingToolResultValues(existing);
4034
4582
  const next = {
4035
4583
  tool_use_id: toolCallId,
4036
- tool_name: patch.tool_name ?? existing?.tool_name ?? "tool_call",
4037
- is_error: patch.is_error ?? existing?.is_error ?? false,
4038
- content: patch.content ?? existing?.content ?? { Text: "" },
4039
- output: patch.output ?? existing?.output
4584
+ tool_name: patch.tool_name ?? fallback.tool_name,
4585
+ is_error: patch.is_error ?? fallback.is_error,
4586
+ content: patch.content ?? fallback.content,
4587
+ output: patch.output ?? fallback.output
4040
4588
  };
4041
4589
  agent.tool_results[toolCallId] = next;
4042
4590
  }
4591
+ function existingToolResultValues(existing) {
4592
+ if (existing) return existing;
4593
+ return {
4594
+ tool_use_id: "",
4595
+ tool_name: "tool_call",
4596
+ is_error: false,
4597
+ content: { Text: "" },
4598
+ output: void 0
4599
+ };
4600
+ }
4043
4601
  function applyToolCallUpdate(agent, update) {
4044
4602
  const tool = ensureToolUseContent(agent, update.toolCallId);
4603
+ applyToolIdentityUpdate(tool, update);
4604
+ applyToolInputUpdate(tool, update);
4605
+ applyToolStatusUpdate(tool, update);
4606
+ applyToolResultUpdate(agent, tool, update);
4607
+ }
4608
+ function applyToolIdentityUpdate(tool, update) {
4045
4609
  if (hasOwn(update, "title")) tool.name = normalizeAgentName(update.title) ?? tool.name ?? "tool_call";
4046
4610
  if (hasOwn(update, "kind")) {
4047
4611
  const kindName = normalizeAgentName(update.kind);
4048
4612
  if (!tool.name || tool.name === "tool_call") tool.name = kindName ?? tool.name;
4049
4613
  }
4050
- if (hasOwn(update, "rawInput")) {
4051
- const rawInput = deepClone(update.rawInput);
4052
- tool.input = rawInput ?? {};
4053
- tool.raw_input = toRawInput(rawInput);
4054
- }
4614
+ }
4615
+ function applyToolInputUpdate(tool, update) {
4616
+ if (!hasOwn(update, "rawInput")) return;
4617
+ const rawInput = deepClone(update.rawInput);
4618
+ tool.input = rawInput ?? {};
4619
+ tool.raw_input = toRawInput(rawInput);
4620
+ }
4621
+ function applyToolStatusUpdate(tool, update) {
4055
4622
  if (hasOwn(update, "status")) tool.is_input_complete = statusIndicatesComplete(update.status);
4056
- if (hasOwn(update, "rawOutput") || hasOwn(update, "status") || hasOwn(update, "title") || hasOwn(update, "kind")) {
4057
- const status = update.status;
4058
- const output = hasOwn(update, "rawOutput") ? deepClone(update.rawOutput) : void 0;
4059
- upsertToolResult(agent, update.toolCallId, {
4060
- tool_name: tool.name,
4061
- is_error: statusIndicatesError(status),
4062
- content: output === void 0 ? void 0 : toToolResultContent(output),
4063
- output
4064
- });
4065
- }
4623
+ }
4624
+ function applyToolResultUpdate(agent, tool, update) {
4625
+ if (!hasToolResultPatch(update)) return;
4626
+ const status = update.status;
4627
+ const output = hasOwn(update, "rawOutput") ? deepClone(update.rawOutput) : void 0;
4628
+ upsertToolResult(agent, update.toolCallId, {
4629
+ tool_name: tool.name,
4630
+ is_error: statusIndicatesError(status),
4631
+ content: output === void 0 ? void 0 : toToolResultContent(output),
4632
+ output
4633
+ });
4634
+ }
4635
+ function hasToolResultPatch(update) {
4636
+ return [
4637
+ "rawOutput",
4638
+ "status",
4639
+ "title",
4640
+ "kind"
4641
+ ].some((key) => hasOwn(update, key));
4066
4642
  }
4067
4643
  function asRecord(value) {
4068
4644
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
@@ -4093,9 +4669,12 @@ function usageToTokenUsage(update) {
4093
4669
  "cachedReadTokens"
4094
4670
  ])
4095
4671
  };
4096
- if (normalized.input_tokens === void 0 && normalized.output_tokens === void 0 && normalized.cache_creation_input_tokens === void 0 && normalized.cache_read_input_tokens === void 0) return;
4672
+ if (!hasTokenUsageValue(normalized)) return;
4097
4673
  return normalized;
4098
4674
  }
4675
+ function hasTokenUsageValue(usage) {
4676
+ return Object.values(usage).some((value) => value !== void 0);
4677
+ }
4099
4678
  function ensureAcpxState$1(state) {
4100
4679
  return state ?? {};
4101
4680
  }
@@ -4134,14 +4713,21 @@ function cloneSessionAcpxState(state) {
4134
4713
  available_models: state.available_models ? [...state.available_models] : void 0,
4135
4714
  available_commands: state.available_commands ? [...state.available_commands] : void 0,
4136
4715
  config_options: state.config_options ? deepClone(state.config_options) : void 0,
4137
- session_options: state.session_options ? {
4138
- model: state.session_options.model,
4139
- allowed_tools: state.session_options.allowed_tools ? [...state.session_options.allowed_tools] : void 0,
4140
- max_turns: state.session_options.max_turns,
4141
- ...state.session_options.system_prompt !== void 0 ? { system_prompt: typeof state.session_options.system_prompt === "string" ? state.session_options.system_prompt : { append: state.session_options.system_prompt.append } } : {}
4142
- } : void 0
4716
+ session_options: cloneSessionOptions(state.session_options)
4717
+ };
4718
+ }
4719
+ function cloneSessionOptions(options) {
4720
+ if (!options) return;
4721
+ return {
4722
+ model: options.model,
4723
+ allowed_tools: options.allowed_tools ? [...options.allowed_tools] : void 0,
4724
+ max_turns: options.max_turns,
4725
+ ...options.system_prompt !== void 0 ? { system_prompt: cloneSystemPromptOption(options.system_prompt) } : {}
4143
4726
  };
4144
4727
  }
4728
+ function cloneSystemPromptOption(option) {
4729
+ return typeof option === "string" ? option : { append: option.append };
4730
+ }
4145
4731
  function recordPromptSubmission(conversation, prompt, timestamp = isoNow()) {
4146
4732
  const userContent = (typeof prompt === "string" ? textPrompt(prompt) : prompt).map((content) => contentToUserContent(content)).filter((content) => content !== void 0);
4147
4733
  if (userContent.length === 0) return;
@@ -4174,57 +4760,70 @@ function hasAgentReplyAfterPrompt(conversation, promptMessageId) {
4174
4760
  function recordSessionUpdate(conversation, state, notification, timestamp = isoNow()) {
4175
4761
  const acpx = ensureAcpxState$1(state);
4176
4762
  const update = notification.update;
4177
- switch (update.sessionUpdate) {
4178
- case "user_message_chunk": {
4179
- const userContent = contentToUserContent(update.content);
4180
- if (userContent) conversation.messages.push({ User: {
4181
- id: nextUserMessageId(),
4182
- content: [userContent]
4183
- } });
4184
- break;
4185
- }
4186
- case "agent_message_chunk": {
4187
- const text = extractText(update.content);
4188
- if (text) appendAgentText(ensureAgentMessage(conversation), text);
4189
- break;
4190
- }
4191
- case "agent_thought_chunk": {
4192
- const text = extractText(update.content);
4193
- if (text) appendAgentThinking(ensureAgentMessage(conversation), text);
4194
- break;
4195
- }
4196
- case "tool_call":
4197
- case "tool_call_update":
4198
- applyToolCallUpdate(ensureAgentMessage(conversation), update);
4199
- break;
4200
- case "usage_update": {
4201
- const usage = usageToTokenUsage(update);
4202
- if (usage) {
4203
- conversation.cumulative_token_usage = usage;
4204
- const userId = lastUserMessageId(conversation);
4205
- if (userId) conversation.request_token_usage[userId] = usage;
4206
- }
4207
- break;
4208
- }
4209
- case "session_info_update":
4210
- if (hasOwn(update, "title")) conversation.title = update.title ?? null;
4211
- if (hasOwn(update, "updatedAt")) conversation.updated_at = update.updatedAt ?? conversation.updated_at;
4212
- break;
4213
- case "available_commands_update":
4214
- acpx.available_commands = update.availableCommands.map((entry) => entry.name).filter((entry) => typeof entry === "string" && entry.trim().length > 0);
4215
- break;
4216
- case "current_mode_update":
4217
- acpx.current_mode_id = update.currentModeId;
4218
- break;
4219
- case "config_option_update":
4220
- acpx.config_options = deepClone(update.configOptions);
4221
- break;
4222
- default: break;
4223
- }
4763
+ applySessionUpdate(conversation, acpx, update);
4224
4764
  updateConversationTimestamp(conversation, timestamp);
4225
4765
  trimConversationForRuntime(conversation);
4226
4766
  return acpx;
4227
4767
  }
4768
+ function applySessionUpdate(conversation, acpx, update) {
4769
+ const handler = SESSION_UPDATE_HANDLERS[update.sessionUpdate];
4770
+ handler?.(conversation, acpx, update);
4771
+ }
4772
+ const SESSION_UPDATE_HANDLERS = {
4773
+ user_message_chunk: (conversation, _acpx, update) => {
4774
+ if (update.sessionUpdate === "user_message_chunk") appendUserMessageChunk(conversation, update.content);
4775
+ },
4776
+ agent_message_chunk: (conversation, _acpx, update) => {
4777
+ if (update.sessionUpdate === "agent_message_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentText);
4778
+ },
4779
+ agent_thought_chunk: (conversation, _acpx, update) => {
4780
+ if (update.sessionUpdate === "agent_thought_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentThinking);
4781
+ },
4782
+ tool_call: (conversation, _acpx, update) => {
4783
+ if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") applyToolCallUpdate(ensureAgentMessage(conversation), update);
4784
+ },
4785
+ tool_call_update: (conversation, _acpx, update) => {
4786
+ if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") applyToolCallUpdate(ensureAgentMessage(conversation), update);
4787
+ },
4788
+ usage_update: (conversation, _acpx, update) => {
4789
+ if (update.sessionUpdate === "usage_update") applyUsageUpdate(conversation, update);
4790
+ },
4791
+ session_info_update: (conversation, _acpx, update) => {
4792
+ if (update.sessionUpdate === "session_info_update") applySessionInfoUpdate(conversation, update);
4793
+ },
4794
+ available_commands_update: (_conversation, acpx, update) => {
4795
+ if (update.sessionUpdate === "available_commands_update") acpx.available_commands = update.availableCommands.map((entry) => entry.name).filter((entry) => typeof entry === "string" && entry.trim().length > 0);
4796
+ },
4797
+ current_mode_update: (_conversation, acpx, update) => {
4798
+ if (update.sessionUpdate === "current_mode_update") acpx.current_mode_id = update.currentModeId;
4799
+ },
4800
+ config_option_update: (_conversation, acpx, update) => {
4801
+ if (update.sessionUpdate === "config_option_update") acpx.config_options = deepClone(update.configOptions);
4802
+ }
4803
+ };
4804
+ function appendUserMessageChunk(conversation, content) {
4805
+ const userContent = contentToUserContent(content);
4806
+ if (!userContent) return;
4807
+ conversation.messages.push({ User: {
4808
+ id: nextUserMessageId(),
4809
+ content: [userContent]
4810
+ } });
4811
+ }
4812
+ function appendAgentMessageChunk(conversation, content, append) {
4813
+ const text = extractText(content);
4814
+ if (text) append(ensureAgentMessage(conversation), text);
4815
+ }
4816
+ function applyUsageUpdate(conversation, update) {
4817
+ const usage = usageToTokenUsage(update);
4818
+ if (!usage) return;
4819
+ conversation.cumulative_token_usage = usage;
4820
+ const userId = lastUserMessageId(conversation);
4821
+ if (userId) conversation.request_token_usage[userId] = usage;
4822
+ }
4823
+ function applySessionInfoUpdate(conversation, update) {
4824
+ if (hasOwn(update, "title")) conversation.title = update.title ?? null;
4825
+ if (hasOwn(update, "updatedAt")) conversation.updated_at = update.updatedAt ?? conversation.updated_at;
4826
+ }
4228
4827
  function recordClientOperation(conversation, state, operation, timestamp = isoNow()) {
4229
4828
  const acpx = ensureAcpxState$1(state);
4230
4829
  updateConversationTimestamp(conversation, timestamp);
@@ -4233,25 +4832,36 @@ function recordClientOperation(conversation, state, operation, timestamp = isoNo
4233
4832
  }
4234
4833
  function trimConversationForRuntime(conversation) {
4235
4834
  if (conversation.messages.length > MAX_RUNTIME_MESSAGES) conversation.messages = conversation.messages.slice(-MAX_RUNTIME_MESSAGES);
4236
- for (const message of conversation.messages) {
4237
- if (!isAgentMessage(message)) {
4238
- if (isUserMessage(message)) message.User.content = message.User.content.map((content) => {
4239
- if ("Text" in content) return { Text: trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS) };
4240
- return content;
4241
- });
4242
- continue;
4243
- }
4244
- for (const content of message.Agent.content) if ("Text" in content) content.Text = trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS);
4245
- else if ("Thinking" in content) content.Thinking.text = trimRuntimeText(content.Thinking.text, MAX_RUNTIME_THINKING_CHARS);
4246
- else if ("ToolUse" in content) content.ToolUse.raw_input = trimRuntimeText(content.ToolUse.raw_input, MAX_RUNTIME_TOOL_IO_CHARS);
4247
- for (const result of Object.values(message.Agent.tool_results)) {
4248
- if ("Text" in result.content) result.content.Text = trimRuntimeText(result.content.Text, MAX_RUNTIME_TOOL_IO_CHARS);
4249
- if (typeof result.output === "string") result.output = trimRuntimeText(result.output, MAX_RUNTIME_TOOL_IO_CHARS);
4250
- }
4251
- }
4835
+ for (const message of conversation.messages) trimRuntimeMessage(message);
4252
4836
  const requestUsageEntries = Object.entries(conversation.request_token_usage);
4253
4837
  if (requestUsageEntries.length > MAX_RUNTIME_REQUEST_TOKEN_USAGE) conversation.request_token_usage = Object.fromEntries(requestUsageEntries.slice(-MAX_RUNTIME_REQUEST_TOKEN_USAGE));
4254
4838
  }
4839
+ function trimRuntimeMessage(message) {
4840
+ if (isUserMessage(message)) {
4841
+ trimRuntimeUserMessage(message.User);
4842
+ return;
4843
+ }
4844
+ if (isAgentMessage(message)) trimRuntimeAgentMessage(message.Agent);
4845
+ }
4846
+ function trimRuntimeUserMessage(message) {
4847
+ message.content = message.content.map((content) => {
4848
+ if ("Text" in content) return { Text: trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS) };
4849
+ return content;
4850
+ });
4851
+ }
4852
+ function trimRuntimeAgentMessage(message) {
4853
+ for (const content of message.content) trimRuntimeAgentContent(content);
4854
+ for (const result of Object.values(message.tool_results)) trimRuntimeToolResult(result);
4855
+ }
4856
+ function trimRuntimeAgentContent(content) {
4857
+ if ("Text" in content) content.Text = trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS);
4858
+ else if ("Thinking" in content) content.Thinking.text = trimRuntimeText(content.Thinking.text, MAX_RUNTIME_THINKING_CHARS);
4859
+ else if ("ToolUse" in content) content.ToolUse.raw_input = trimRuntimeText(content.ToolUse.raw_input, MAX_RUNTIME_TOOL_IO_CHARS);
4860
+ }
4861
+ function trimRuntimeToolResult(result) {
4862
+ if ("Text" in result.content) result.content.Text = trimRuntimeText(result.content.Text, MAX_RUNTIME_TOOL_IO_CHARS);
4863
+ if (typeof result.output === "string") result.output = trimRuntimeText(result.output, MAX_RUNTIME_TOOL_IO_CHARS);
4864
+ }
4255
4865
  //#endregion
4256
4866
  //#region src/session/config-options.ts
4257
4867
  function applyConfigOptionsToRecord(record, result) {
@@ -4373,39 +4983,6 @@ async function applyRequestedModelIfAdvertised(params) {
4373
4983
  return true;
4374
4984
  }
4375
4985
  //#endregion
4376
- //#region src/runtime/engine/lifecycle.ts
4377
- function applyLifecycleSnapshotToRecord(record, snapshot) {
4378
- if (!snapshot) return;
4379
- record.pid = snapshot.pid;
4380
- record.agentStartedAt = snapshot.startedAt;
4381
- if (snapshot.lastExit) {
4382
- record.lastAgentExitCode = snapshot.lastExit.exitCode;
4383
- record.lastAgentExitSignal = snapshot.lastExit.signal;
4384
- record.lastAgentExitAt = snapshot.lastExit.exitedAt;
4385
- record.lastAgentDisconnectReason = snapshot.lastExit.reason;
4386
- return;
4387
- }
4388
- record.lastAgentExitCode = void 0;
4389
- record.lastAgentExitSignal = void 0;
4390
- record.lastAgentExitAt = void 0;
4391
- record.lastAgentDisconnectReason = void 0;
4392
- }
4393
- function reconcileAgentSessionId(record, agentSessionId) {
4394
- const normalized = normalizeRuntimeSessionId(agentSessionId);
4395
- if (!normalized) return;
4396
- record.agentSessionId = normalized;
4397
- }
4398
- function sessionHasAgentMessages(recordOrConversation) {
4399
- return recordOrConversation.messages.some((message) => typeof message === "object" && message !== null && "Agent" in message);
4400
- }
4401
- function applyConversation(record, conversation) {
4402
- record.title = conversation.title;
4403
- record.updated_at = conversation.updated_at;
4404
- record.messages = conversation.messages;
4405
- record.cumulative_token_usage = conversation.cumulative_token_usage;
4406
- record.request_token_usage = conversation.request_token_usage;
4407
- }
4408
- //#endregion
4409
4986
  //#region src/runtime/engine/reconnect.ts
4410
4987
  function isProcessAlive(pid) {
4411
4988
  if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
@@ -4418,15 +4995,19 @@ function isProcessAlive(pid) {
4418
4995
  }
4419
4996
  const SESSION_LOAD_UNSUPPORTED_CODES = new Set([-32601, -32602]);
4420
4997
  function shouldFallbackToNewSession(error, record) {
4421
- if (error instanceof TimeoutError || error instanceof InterruptedError) return false;
4422
- if (isAcpResourceNotFoundError(error)) return true;
4998
+ if (isHardReconnectFailure(error)) return false;
4423
4999
  const acp = extractAcpError(error);
4424
- if (acp && SESSION_LOAD_UNSUPPORTED_CODES.has(acp.code)) return true;
4425
- if (!sessionHasAgentMessages(record)) {
4426
- if (isAcpQueryClosedBeforeResponseError(error)) return true;
4427
- if (acp?.code === -32603) return true;
4428
- }
4429
- return false;
5000
+ if (isAcpResourceNotFoundError(error) || isUnsupportedSessionLoadAcpError(acp)) return true;
5001
+ return !sessionHasAgentMessages(record) && isFallbackSafeEmptySessionError(error, acp);
5002
+ }
5003
+ function isHardReconnectFailure(error) {
5004
+ return error instanceof TimeoutError || error instanceof InterruptedError;
5005
+ }
5006
+ function isUnsupportedSessionLoadAcpError(acp) {
5007
+ return !!acp && SESSION_LOAD_UNSUPPORTED_CODES.has(acp.code);
5008
+ }
5009
+ function isFallbackSafeEmptySessionError(error, acp) {
5010
+ return isAcpQueryClosedBeforeResponseError(error) || acp?.code === -32603;
4430
5011
  }
4431
5012
  function requiresSameSession(resumePolicy) {
4432
5013
  return resumePolicy === "same-session-only";
@@ -4490,11 +5071,7 @@ async function connectAndLoadSession(options) {
4490
5071
  const desiredModelId = getDesiredModelId(record.acpx);
4491
5072
  const desiredConfigOptions = getDesiredConfigOptions(record.acpx);
4492
5073
  const storedProcessAlive = isProcessAlive(record.pid);
4493
- const shouldReconnect = Boolean(record.pid) && !storedProcessAlive;
4494
- if (options.verbose) {
4495
- if (storedProcessAlive) process.stderr.write(`[acpx] saved session pid ${record.pid} is running; reconnecting with loadSession\n`);
4496
- else if (shouldReconnect) process.stderr.write(`[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session/load\n`);
4497
- }
5074
+ logReconnectAttempt(record, storedProcessAlive, Boolean(record.pid) && !storedProcessAlive, options.verbose);
4498
5075
  const reusingLoadedSession = client.hasReusableSession(record.acpSessionId);
4499
5076
  if (reusingLoadedSession) incrementPerfCounter("runtime.connect_and_load.reused_session");
4500
5077
  else await withTimeout(client.start(), options.timeoutMs);
@@ -4509,82 +5086,35 @@ async function connectAndLoadSession(options) {
4509
5086
  let createdFreshSession = false;
4510
5087
  let pendingAgentSessionId = record.agentSessionId;
4511
5088
  let sessionModels;
4512
- if (reusingLoadedSession) resumed = true;
4513
- else if (client.supportsLoadSession()) try {
4514
- const loadResult = await withTimeout(client.loadSessionWithOptions(record.acpSessionId, record.cwd, { suppressReplayUpdates: true }), options.timeoutMs);
4515
- reconcileAgentSessionId(record, loadResult.agentSessionId);
4516
- applyConfigOptionsToRecord(record, loadResult);
4517
- sessionModels = loadResult.models;
4518
- resumed = true;
4519
- } catch (error) {
4520
- loadError = formatErrorMessage(error);
4521
- if (sameSessionOnly) throw makeSessionResumeRequiredError({
4522
- record,
4523
- reason: loadError,
4524
- cause: error
4525
- });
4526
- if (!shouldFallbackToNewSession(error, record)) throw error;
4527
- const createdSession = await withTimeout(client.createSession(record.cwd), options.timeoutMs);
4528
- sessionId = createdSession.sessionId;
4529
- createdFreshSession = true;
4530
- pendingAgentSessionId = createdSession.agentSessionId;
4531
- applyConfigOptionsToRecord(record, createdSession);
4532
- sessionModels = createdSession.models;
4533
- }
4534
- else {
4535
- if (sameSessionOnly) throw makeSessionResumeRequiredError({
4536
- record,
4537
- reason: "agent does not support session/load"
4538
- });
4539
- const createdSession = await withTimeout(client.createSession(record.cwd), options.timeoutMs);
4540
- sessionId = createdSession.sessionId;
4541
- createdFreshSession = true;
4542
- pendingAgentSessionId = createdSession.agentSessionId;
4543
- applyConfigOptionsToRecord(record, createdSession);
4544
- sessionModels = createdSession.models;
4545
- }
4546
- if (createdFreshSession) {
4547
- try {
4548
- await replayDesiredMode({
4549
- client,
4550
- sessionId,
4551
- desiredModeId,
4552
- previousSessionId: originalSessionId,
4553
- timeoutMs: options.timeoutMs,
4554
- verbose: options.verbose
4555
- });
4556
- await replayDesiredModel({
4557
- client,
4558
- sessionId,
4559
- desiredModelId,
4560
- previousSessionId: originalSessionId,
4561
- record,
4562
- models: sessionModels,
4563
- timeoutMs: options.timeoutMs,
4564
- verbose: options.verbose
4565
- });
4566
- await replayDesiredConfigOptions({
4567
- client,
4568
- sessionId,
4569
- desiredConfigOptions,
4570
- previousSessionId: originalSessionId,
4571
- timeoutMs: options.timeoutMs,
4572
- verbose: options.verbose
4573
- });
4574
- } catch (error) {
4575
- restoreOriginalSessionState({
4576
- record,
4577
- sessionId: originalSessionId,
4578
- agentSessionId: originalAgentSessionId
4579
- });
4580
- if (options.verbose) process.stderr.write(`[acpx] ${formatErrorMessage(error)}\n`);
4581
- throw error;
4582
- }
4583
- record.acpSessionId = sessionId;
4584
- reconcileAgentSessionId(record, pendingAgentSessionId);
4585
- }
4586
- syncAdvertisedModelState(record, sessionModels);
4587
- if (createdFreshSession && desiredModelId && sessionModels) setCurrentModelId(record, desiredModelId);
5089
+ const loadState = await loadOrCreateRuntimeSession({
5090
+ client,
5091
+ record,
5092
+ reusingLoadedSession,
5093
+ sameSessionOnly,
5094
+ timeoutMs: options.timeoutMs
5095
+ });
5096
+ resumed = loadState.resumed;
5097
+ loadError = loadState.loadError;
5098
+ sessionId = loadState.sessionId;
5099
+ createdFreshSession = loadState.createdFreshSession;
5100
+ pendingAgentSessionId = loadState.pendingAgentSessionId;
5101
+ sessionModels = loadState.sessionModels;
5102
+ await replayFreshSessionPreferences({
5103
+ client,
5104
+ record,
5105
+ createdFreshSession,
5106
+ sessionId,
5107
+ pendingAgentSessionId,
5108
+ originalSessionId,
5109
+ originalAgentSessionId,
5110
+ desiredModeId,
5111
+ desiredModelId,
5112
+ desiredConfigOptions,
5113
+ sessionModels,
5114
+ timeoutMs: options.timeoutMs,
5115
+ verbose: options.verbose
5116
+ });
5117
+ applyReconnectedModelState(record, sessionModels, createdFreshSession, desiredModelId);
4588
5118
  options.onSessionIdResolved?.(sessionId);
4589
5119
  return {
4590
5120
  sessionId,
@@ -4593,6 +5123,131 @@ async function connectAndLoadSession(options) {
4593
5123
  loadError
4594
5124
  };
4595
5125
  }
5126
+ function applyReconnectedModelState(record, sessionModels, createdFreshSession, desiredModelId) {
5127
+ syncAdvertisedModelState(record, sessionModels);
5128
+ if (createdFreshSession && desiredModelId && sessionModels) setCurrentModelId(record, desiredModelId);
5129
+ }
5130
+ function logReconnectAttempt(record, storedProcessAlive, shouldReconnect, verbose) {
5131
+ if (!verbose) return;
5132
+ if (storedProcessAlive) {
5133
+ process.stderr.write(`[acpx] saved session pid ${record.pid} is running; reconnecting to saved ACP session\n`);
5134
+ return;
5135
+ }
5136
+ if (shouldReconnect) process.stderr.write(`[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session reconnect\n`);
5137
+ }
5138
+ async function replayFreshSessionPreferences(params) {
5139
+ if (!params.createdFreshSession) return;
5140
+ try {
5141
+ await replayDesiredMode({
5142
+ client: params.client,
5143
+ sessionId: params.sessionId,
5144
+ desiredModeId: params.desiredModeId,
5145
+ previousSessionId: params.originalSessionId,
5146
+ timeoutMs: params.timeoutMs,
5147
+ verbose: params.verbose
5148
+ });
5149
+ await replayDesiredModel({
5150
+ client: params.client,
5151
+ sessionId: params.sessionId,
5152
+ desiredModelId: params.desiredModelId,
5153
+ previousSessionId: params.originalSessionId,
5154
+ record: params.record,
5155
+ models: params.sessionModels,
5156
+ timeoutMs: params.timeoutMs,
5157
+ verbose: params.verbose
5158
+ });
5159
+ await replayDesiredConfigOptions({
5160
+ client: params.client,
5161
+ sessionId: params.sessionId,
5162
+ desiredConfigOptions: params.desiredConfigOptions,
5163
+ previousSessionId: params.originalSessionId,
5164
+ timeoutMs: params.timeoutMs,
5165
+ verbose: params.verbose
5166
+ });
5167
+ } catch (error) {
5168
+ restoreOriginalSessionState({
5169
+ record: params.record,
5170
+ sessionId: params.originalSessionId,
5171
+ agentSessionId: params.originalAgentSessionId
5172
+ });
5173
+ if (params.verbose) process.stderr.write(`[acpx] ${formatErrorMessage(error)}\n`);
5174
+ throw error;
5175
+ }
5176
+ params.record.acpSessionId = params.sessionId;
5177
+ reconcileAgentSessionId(params.record, params.pendingAgentSessionId);
5178
+ }
5179
+ async function loadOrCreateRuntimeSession(params) {
5180
+ if (params.reusingLoadedSession) return {
5181
+ sessionId: params.record.acpSessionId,
5182
+ pendingAgentSessionId: params.record.agentSessionId,
5183
+ sessionModels: void 0,
5184
+ resumed: true,
5185
+ createdFreshSession: false
5186
+ };
5187
+ if (params.client.supportsResumeSession()) return await resumeRuntimeSession(params);
5188
+ if (params.client.supportsLoadSession()) return await loadRuntimeSession(params);
5189
+ if (params.sameSessionOnly) throw makeSessionResumeRequiredError({
5190
+ record: params.record,
5191
+ reason: "agent does not support session/resume or session/load"
5192
+ });
5193
+ return await createFreshRuntimeSession(params.client, params.record, params.timeoutMs);
5194
+ }
5195
+ async function resumeRuntimeSession(params) {
5196
+ try {
5197
+ const resumeResult = await withTimeout(params.client.resumeSession(params.record.acpSessionId, params.record.cwd), params.timeoutMs);
5198
+ reconcileAgentSessionId(params.record, resumeResult.agentSessionId);
5199
+ applyConfigOptionsToRecord(params.record, resumeResult);
5200
+ return {
5201
+ sessionId: params.record.acpSessionId,
5202
+ pendingAgentSessionId: params.record.agentSessionId,
5203
+ sessionModels: resumeResult.models,
5204
+ resumed: true,
5205
+ createdFreshSession: false
5206
+ };
5207
+ } catch (error) {
5208
+ return await recoverRuntimeSessionLoadFailure(params, error);
5209
+ }
5210
+ }
5211
+ async function loadRuntimeSession(params) {
5212
+ try {
5213
+ const loadResult = await withTimeout(params.client.loadSessionWithOptions(params.record.acpSessionId, params.record.cwd, { suppressReplayUpdates: true }), params.timeoutMs);
5214
+ reconcileAgentSessionId(params.record, loadResult.agentSessionId);
5215
+ applyConfigOptionsToRecord(params.record, loadResult);
5216
+ return {
5217
+ sessionId: params.record.acpSessionId,
5218
+ pendingAgentSessionId: params.record.agentSessionId,
5219
+ sessionModels: loadResult.models,
5220
+ resumed: true,
5221
+ createdFreshSession: false
5222
+ };
5223
+ } catch (error) {
5224
+ return await recoverRuntimeSessionLoadFailure(params, error);
5225
+ }
5226
+ }
5227
+ async function recoverRuntimeSessionLoadFailure(params, error) {
5228
+ const loadError = formatErrorMessage(error);
5229
+ if (params.sameSessionOnly) throw makeSessionResumeRequiredError({
5230
+ record: params.record,
5231
+ reason: loadError,
5232
+ cause: error
5233
+ });
5234
+ if (!shouldFallbackToNewSession(error, params.record)) throw error;
5235
+ return {
5236
+ ...await createFreshRuntimeSession(params.client, params.record, params.timeoutMs),
5237
+ loadError
5238
+ };
5239
+ }
5240
+ async function createFreshRuntimeSession(client, record, timeoutMs) {
5241
+ const createdSession = await withTimeout(client.createSession(record.cwd), timeoutMs);
5242
+ applyConfigOptionsToRecord(record, createdSession);
5243
+ return {
5244
+ sessionId: createdSession.sessionId,
5245
+ pendingAgentSessionId: createdSession.agentSessionId,
5246
+ sessionModels: createdSession.models,
5247
+ resumed: false,
5248
+ createdFreshSession: true
5249
+ };
5250
+ }
4596
5251
  //#endregion
4597
5252
  //#region src/runtime/engine/connected-session.ts
4598
5253
  function createActiveSessionController(params) {
@@ -4785,6 +5440,6 @@ var LiveSessionCheckpoint = class {
4785
5440
  }
4786
5441
  };
4787
5442
  //#endregion
4788
- export { getPerfMetricsSnapshot as $, parsePromptStopReason as A, EXIT_CODES as At, normalizeName as B, mergeSessionOptions as C, formatErrorMessage as Ct, extractSessionUpdateNotification as D, isAcpResourceNotFoundError as Dt, AcpClient as E, extractAcpError as Et, findSession as F, PERMISSION_MODES as Ft, DEFAULT_EVENT_SEGMENT_MAX_BYTES as G, resolveSessionRecord as H, findSessionByDirectoryWalk as I, PERMISSION_POLICY_ACTIONS as It, sessionEventActivePath as J, defaultSessionEventLog as K, isoNow$2 as L, SESSION_RECORD_SCHEMA as Lt, DEFAULT_HISTORY_LIMIT as M, OUTPUT_ERROR_CODES as Mt, absolutePath as N, OUTPUT_ERROR_ORIGINS as Nt, isAcpJsonRpcMessage as O, toAcpErrorPayload as Ot, findGitRepositoryRoot as P, OUTPUT_FORMATS as Pt, formatPerfMetric as Q, listSessions as R, QueueConnectionError as Rt, trimConversationForRuntime as S, exitCodeForOutputErrorCode as St, sessionOptionsFromRecord as T, normalizeOutputError as Tt, writeSessionRecord as U, pruneSessions as V, parseSessionRecord as W, sessionEventSegmentPath as X, sessionEventLockPath as Y, assertPersistedKeyPolicy as Z, cloneSessionConversation as _, withTimeout as _t, applyConversation as a, startPerfTimer as at, recordPromptSubmission as b, normalizeAgentName$1 as bt, applyRequestedModelIfAdvertised as c, PromptInputValidationError as ct, setDesiredConfigOption as d, parsePromptSource as dt, incrementPerfCounter as et, setDesiredModeId as f, promptToDisplayText as ft, cloneSessionAcpxState as g, withInterrupt as gt, applyConfigOptionsToRecord as h, TimeoutError as ht, connectAndLoadSession as i, setPerfGauge as it, permissionModeSatisfies as j, NON_INTERACTIVE_PERMISSION_POLICIES as jt, parseJsonRpcErrorMessage as k, AUTH_POLICIES as kt, assertRequestedModelSupported as l, isPromptInput as lt, syncAdvertisedModelState as m, InterruptedError as mt, runPromptTurn as n, recordPerfDuration as nt, applyLifecycleSnapshotToRecord as o, serializeSessionRecordForDisk as ot, setDesiredModelId as p, textPrompt as pt, sessionBaseDir$1 as q, withConnectedSession as r, resetPerfMetrics as rt, reconcileAgentSessionId as s, normalizeRuntimeSessionId as st, LiveSessionCheckpoint as t, measurePerf as tt, setCurrentModelId as u, mergePromptSourceWithText as ut, createSessionConversation as v, DEFAULT_AGENT_NAME as vt, persistSessionOptions as w, isRetryablePromptError as wt, recordSessionUpdate as x, resolveAgentCommand as xt, recordClientOperation as y, listBuiltInAgents as yt, listSessionsForAgent as z, QueueProtocolError as zt };
5443
+ export { getPerfMetricsSnapshot as $, parsePromptStopReason as A, EXIT_CODES as At, normalizeName as B, QueueProtocolError as Bt, applyConversation as C, formatErrorMessage as Ct, extractSessionUpdateNotification as D, isAcpResourceNotFoundError as Dt, AcpClient as E, extractAcpError as Et, findSession as F, PERMISSION_MODES as Ft, DEFAULT_EVENT_SEGMENT_MAX_BYTES as G, resolveSessionRecord as H, findSessionByDirectoryWalk as I, PERMISSION_POLICY_ACTIONS as It, sessionEventActivePath as J, defaultSessionEventLog as K, isoNow$2 as L, SESSION_RECORD_SCHEMA as Lt, DEFAULT_HISTORY_LIMIT as M, OUTPUT_ERROR_CODES as Mt, absolutePath as N, OUTPUT_ERROR_ORIGINS as Nt, isAcpJsonRpcMessage as O, toAcpErrorPayload as Ot, findGitRepositoryRoot as P, OUTPUT_FORMATS as Pt, formatPerfMetric as Q, listSessions as R, AgentSpawnError as Rt, sessionOptionsFromRecord as S, exitCodeForOutputErrorCode as St, reconcileAgentSessionId as T, normalizeOutputError as Tt, writeSessionRecord as U, pruneSessions as V, parseSessionRecord as W, sessionEventSegmentPath as X, sessionEventLockPath as Y, assertPersistedKeyPolicy as Z, recordPromptSubmission as _, withTimeout as _t, applyRequestedModelIfAdvertised as a, startPerfTimer as at, mergeSessionOptions as b, normalizeAgentName$1 as bt, setDesiredConfigOption as c, PromptInputValidationError as ct, syncAdvertisedModelState as d, parsePromptSource as dt, incrementPerfCounter as et, applyConfigOptionsToRecord as f, promptToDisplayText as ft, recordClientOperation as g, withInterrupt as gt, createSessionConversation as h, TimeoutError as ht, connectAndLoadSession as i, setPerfGauge as it, permissionModeSatisfies as j, NON_INTERACTIVE_PERMISSION_POLICIES as jt, parseJsonRpcErrorMessage as k, AUTH_POLICIES as kt, setDesiredModeId as l, isPromptInput as lt, cloneSessionConversation as m, InterruptedError as mt, runPromptTurn as n, recordPerfDuration as nt, assertRequestedModelSupported as o, serializeSessionRecordForDisk as ot, cloneSessionAcpxState as p, textPrompt as pt, sessionBaseDir$1 as q, withConnectedSession as r, resetPerfMetrics as rt, setCurrentModelId as s, normalizeRuntimeSessionId as st, LiveSessionCheckpoint as t, measurePerf as tt, setDesiredModelId as u, mergePromptSourceWithText as ut, recordSessionUpdate as v, DEFAULT_AGENT_NAME as vt, applyLifecycleSnapshotToRecord as w, isRetryablePromptError as wt, persistSessionOptions as x, resolveAgentCommand as xt, trimConversationForRuntime as y, listBuiltInAgents as yt, listSessionsForAgent as z, QueueConnectionError as zt };
4789
5444
 
4790
- //# sourceMappingURL=live-checkpoint-B9ctAuqV.js.map
5445
+ //# sourceMappingURL=live-checkpoint-D5d-K9s1.js.map