acpx 0.8.0 → 0.10.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 (33) hide show
  1. package/README.md +8 -4
  2. package/dist/{cli-BGYGVo3b.js → cli-8dP_TqBp.js} +4 -4
  3. package/dist/{cli-BGYGVo3b.js.map → cli-8dP_TqBp.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 +930 -252
  7. package/dist/cli.js.map +1 -1
  8. package/dist/{client-FzXPdgP7.d.ts → client-C4iJBO0j.d.ts} +30 -3
  9. package/dist/client-C4iJBO0j.d.ts.map +1 -0
  10. package/dist/{flags-D706STfk.js → flags--2oX_ubW.js} +96 -39
  11. package/dist/flags--2oX_ubW.js.map +1 -0
  12. package/dist/{flows-hcjHmU7P.js → flows-e4umXVbY.js} +401 -336
  13. package/dist/flows-e4umXVbY.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-CuFft_Nd.js} +1614 -931
  18. package/dist/live-checkpoint-CuFft_Nd.js.map +1 -0
  19. package/dist/{output-BL9XRWzS.js → output-Di77Yugq.js} +1153 -719
  20. package/dist/output-Di77Yugq.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-Bh1bIqQ2.d.ts} +14 -1
  26. package/dist/{session-options-BJyG6zEH.d.ts.map → session-options-Bh1bIqQ2.d.ts.map} +1 -1
  27. package/package.json +21 -12
  28. package/skills/acpx/SKILL.md +22 -5
  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
@@ -3,12 +3,12 @@ import { fileURLToPath } from "node:url";
3
3
  import path from "node:path";
4
4
  import fs$1 from "node:fs/promises";
5
5
  import os from "node:os";
6
+ import { randomUUID } from "node:crypto";
6
7
  import { execFile, spawn } from "node:child_process";
7
8
  import { Readable, Writable } from "node:stream";
8
9
  import { ClientSideConnection, PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
9
10
  import readline from "node:readline/promises";
10
11
  import { promisify } from "node:util";
11
- import { randomUUID } from "node:crypto";
12
12
  //#region src/errors.ts
13
13
  var AcpxOperationalError = class extends Error {
14
14
  outputCode;
@@ -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,118 @@ 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;
828
+ }
829
+ //#endregion
830
+ //#region src/acp/jsonrpc.ts
831
+ function asRecord$4(value) {
832
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
833
+ return value;
834
+ }
835
+ function hasValidId(value) {
836
+ return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
837
+ }
838
+ function isErrorObject(value) {
839
+ const record = asRecord$4(value);
840
+ return !!record && typeof record.code === "number" && Number.isFinite(record.code) && typeof record.message === "string";
841
+ }
842
+ function hasResultOrError(value) {
843
+ const hasResult = Object.hasOwn(value, "result");
844
+ const hasError = Object.hasOwn(value, "error");
845
+ if (hasResult && hasError) return false;
846
+ if (!hasResult && !hasError) return false;
847
+ if (hasError && !isErrorObject(value.error)) return false;
848
+ return true;
849
+ }
850
+ function hasMethod(value) {
851
+ return typeof value.method === "string" && value.method.length > 0;
852
+ }
853
+ function isJsonRpcRequest(value) {
854
+ return hasMethod(value) && Object.hasOwn(value, "id") && hasValidId(value.id);
855
+ }
856
+ function isJsonRpcNotificationRecord(value) {
857
+ return hasMethod(value) && !Object.hasOwn(value, "id");
858
+ }
859
+ function isJsonRpcResponse(value) {
860
+ if (hasMethod(value) || !Object.hasOwn(value, "id") || !hasValidId(value.id)) return false;
861
+ return hasResultOrError(value);
862
+ }
863
+ function isAcpJsonRpcMessage(value) {
864
+ const record = asRecord$4(value);
865
+ if (!record || record.jsonrpc !== "2.0") return false;
866
+ return isJsonRpcNotificationRecord(record) || isJsonRpcRequest(record) || isJsonRpcResponse(record);
867
+ }
868
+ function isJsonRpcNotification(message) {
869
+ return Object.hasOwn(message, "method") && typeof message.method === "string" && !Object.hasOwn(message, "id");
870
+ }
871
+ function isSessionUpdateNotification(message) {
872
+ return isJsonRpcNotification(message) && message.method === "session/update";
873
+ }
874
+ function extractSessionUpdateNotification(message) {
875
+ if (!isSessionUpdateNotification(message)) return;
876
+ const params = asRecord$4(message.params);
877
+ if (!params) return;
878
+ const sessionId = typeof params.sessionId === "string" ? params.sessionId : null;
879
+ if (!sessionId) return;
880
+ const update = asRecord$4(params.update);
881
+ if (!update || typeof update.sessionUpdate !== "string") return;
882
+ return {
883
+ sessionId,
884
+ update
885
+ };
886
+ }
887
+ function parsePromptStopReason(message) {
888
+ if (!Object.hasOwn(message, "id") || !Object.hasOwn(message, "result")) return;
889
+ const record = asRecord$4(message.result);
890
+ if (!record) return;
891
+ return typeof record.stopReason === "string" ? record.stopReason : void 0;
892
+ }
893
+ function parseJsonRpcErrorMessage(message) {
894
+ if (!Object.hasOwn(message, "error")) return;
895
+ const errorRecord = asRecord$4(message.error);
896
+ if (!errorRecord || typeof errorRecord.message !== "string") return;
897
+ return errorRecord.message;
898
+ }
899
+ //#endregion
900
+ //#region src/session/event-log.ts
901
+ const DEFAULT_EVENT_SEGMENT_MAX_BYTES = 64 * 1024 * 1024;
902
+ function sessionBaseDir$1() {
903
+ return path.join(os.homedir(), ".acpx", "sessions");
904
+ }
905
+ function safeSessionId(sessionId) {
906
+ return encodeURIComponent(sessionId);
907
+ }
908
+ function sessionEventActivePath(sessionId) {
909
+ return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.ndjson`);
910
+ }
911
+ function sessionEventSegmentPath(sessionId, segment) {
912
+ return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.${segment}.ndjson`);
913
+ }
914
+ function sessionEventLockPath(sessionId) {
915
+ return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.lock`);
916
+ }
917
+ function defaultSessionEventLog(sessionId) {
918
+ return {
919
+ active_path: sessionEventActivePath(sessionId),
920
+ segment_count: 5,
921
+ max_segment_bytes: DEFAULT_EVENT_SEGMENT_MAX_BYTES,
922
+ max_segments: 5,
923
+ last_write_at: void 0,
924
+ last_write_error: null
925
+ };
728
926
  }
729
927
  //#endregion
730
928
  //#region src/acp/agent-session-id.ts
@@ -790,173 +988,18 @@ function serializeSessionRecordForDisk(record) {
790
988
  updated_at: canonical.updated_at,
791
989
  cumulative_token_usage: canonical.cumulative_token_usage,
792
990
  request_token_usage: canonical.request_token_usage,
793
- acpx: canonical.acpx
794
- };
795
- }
796
- //#endregion
797
- //#region src/perf-metrics.ts
798
- const counters = /* @__PURE__ */ new Map();
799
- const gauges = /* @__PURE__ */ new Map();
800
- const timings = /* @__PURE__ */ new Map();
801
- function hrNow() {
802
- return process.hrtime.bigint();
803
- }
804
- function durationMs(start) {
805
- return Number(process.hrtime.bigint() - start) / 1e6;
806
- }
807
- function roundMetric(value) {
808
- return Number(value.toFixed(3));
809
- }
810
- function incrementPerfCounter(name, delta = 1) {
811
- counters.set(name, (counters.get(name) ?? 0) + delta);
812
- }
813
- function setPerfGauge(name, value) {
814
- gauges.set(name, value);
815
- }
816
- function recordPerfDuration(name, durationMsValue) {
817
- const next = timings.get(name) ?? {
818
- count: 0,
819
- totalMs: 0,
820
- maxMs: 0
821
- };
822
- next.count += 1;
823
- next.totalMs += durationMsValue;
824
- next.maxMs = Math.max(next.maxMs, durationMsValue);
825
- timings.set(name, next);
826
- }
827
- async function measurePerf(name, run) {
828
- const startedAt = hrNow();
829
- try {
830
- return await run();
831
- } finally {
832
- recordPerfDuration(name, durationMs(startedAt));
833
- }
834
- }
835
- function startPerfTimer(name) {
836
- const startedAt = hrNow();
837
- return () => {
838
- const elapsedMs = durationMs(startedAt);
839
- recordPerfDuration(name, elapsedMs);
840
- return elapsedMs;
841
- };
842
- }
843
- function getPerfMetricsSnapshot() {
844
- return {
845
- counters: Object.fromEntries(counters.entries()),
846
- gauges: Object.fromEntries(gauges.entries()),
847
- timings: Object.fromEntries([...timings.entries()].map(([name, bucket]) => [name, {
848
- count: bucket.count,
849
- totalMs: roundMetric(bucket.totalMs),
850
- maxMs: roundMetric(bucket.maxMs)
851
- }]))
852
- };
853
- }
854
- function resetPerfMetrics() {
855
- counters.clear();
856
- gauges.clear();
857
- timings.clear();
858
- }
859
- function formatPerfMetric(name, durationMsValue) {
860
- return `${name}=${roundMetric(durationMsValue)}ms`;
861
- }
862
- //#endregion
863
- //#region src/persisted-key-policy.ts
864
- const SNAKE_CASE_KEY = /^[a-z][a-z0-9_]*$/;
865
- const ZED_TAG_KEYS = new Set([
866
- "User",
867
- "Agent",
868
- "Resume",
869
- "Text",
870
- "Mention",
871
- "Image",
872
- "Thinking",
873
- "RedactedThinking",
874
- "ToolUse"
875
- ]);
876
- const MAP_OBJECT_PATHS = new Set(["request_token_usage", "messages.Agent.tool_results"]);
877
- const OPAQUE_VALUE_PATHS = new Set([
878
- "agent_capabilities",
879
- "messages.Agent.content.ToolUse.input",
880
- "acpx.desired_config_options",
881
- "acpx.config_options"
882
- ]);
883
- function isRecord(value) {
884
- return !!value && typeof value === "object" && !Array.isArray(value);
885
- }
886
- function joinPath(path) {
887
- return path.join(".");
888
- }
889
- function isAllowedKey(path, key) {
890
- if (ZED_TAG_KEYS.has(key)) return true;
891
- return false;
892
- }
893
- function shouldSkipKeyRule(path) {
894
- return MAP_OBJECT_PATHS.has(joinPath(path));
895
- }
896
- function shouldSkipDescend(path) {
897
- return OPAQUE_VALUE_PATHS.has(joinPath(path)) || isToolResultOutputPath(path);
898
- }
899
- function isToolResultOutputPath(path) {
900
- if (path.length < 5 || path[path.length - 1] !== "output") return false;
901
- const toolResultsIndex = path.lastIndexOf("tool_results");
902
- if (toolResultsIndex === -1 || toolResultsIndex + 2 !== path.length - 1) return false;
903
- return path.slice(0, toolResultsIndex + 1).join(".") === "messages.Agent.tool_results";
904
- }
905
- function collectViolations(value, path, violations) {
906
- if (Array.isArray(value)) {
907
- for (const entry of value) collectViolations(entry, path, violations);
908
- return;
909
- }
910
- if (!isRecord(value)) return;
911
- 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
- }
918
- }
919
- function findPersistedKeyPolicyViolations(value) {
920
- const violations = [];
921
- collectViolations(value, [], violations);
922
- return violations;
923
- }
924
- function assertPersistedKeyPolicy(value) {
925
- const violations = findPersistedKeyPolicyViolations(value);
926
- if (violations.length === 0) return;
927
- throw new Error(`Persisted key policy violation (expected snake_case keys): ${violations.join(", ")}`);
928
- }
929
- //#endregion
930
- //#region src/session/event-log.ts
931
- const DEFAULT_EVENT_SEGMENT_MAX_BYTES = 64 * 1024 * 1024;
932
- function sessionBaseDir$1() {
933
- return path.join(os.homedir(), ".acpx", "sessions");
934
- }
935
- function safeSessionId(sessionId) {
936
- return encodeURIComponent(sessionId);
937
- }
938
- function sessionEventActivePath(sessionId) {
939
- return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.ndjson`);
940
- }
941
- function sessionEventSegmentPath(sessionId, segment) {
942
- return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.${segment}.ndjson`);
943
- }
944
- function sessionEventLockPath(sessionId) {
945
- return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.lock`);
946
- }
947
- function defaultSessionEventLog(sessionId) {
948
- return {
949
- active_path: sessionEventActivePath(sessionId),
950
- segment_count: 5,
951
- max_segment_bytes: DEFAULT_EVENT_SEGMENT_MAX_BYTES,
952
- max_segments: 5,
953
- last_write_at: void 0,
954
- last_write_error: null
991
+ acpx: canonical.acpx,
992
+ imported_from: canonical.importedFrom ? {
993
+ record_id: canonical.importedFrom.recordId,
994
+ cwd_original: canonical.importedFrom.cwdOriginal,
995
+ exported_by: canonical.importedFrom.exportedBy,
996
+ exported_at: canonical.importedFrom.exportedAt
997
+ } : void 0
955
998
  };
956
999
  }
957
1000
  //#endregion
958
1001
  //#region src/session/persistence/parse.ts
959
- function asRecord$4(value) {
1002
+ function asRecord$3(value) {
960
1003
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
961
1004
  return value;
962
1005
  }
@@ -968,7 +1011,7 @@ function isStringArray(value) {
968
1011
  }
969
1012
  function parseTokenUsage(raw) {
970
1013
  if (raw === void 0 || raw === null) return;
971
- const record = asRecord$4(raw);
1014
+ const record = asRecord$3(raw);
972
1015
  if (!record) return null;
973
1016
  const usage = {};
974
1017
  for (const field of [
@@ -979,14 +1022,17 @@ function parseTokenUsage(raw) {
979
1022
  ]) {
980
1023
  const value = record[field];
981
1024
  if (value === void 0) continue;
982
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return null;
1025
+ if (!isNonNegativeFiniteNumber(value)) return null;
983
1026
  usage[field] = value;
984
1027
  }
985
1028
  return usage;
986
1029
  }
1030
+ function isNonNegativeFiniteNumber(value) {
1031
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
1032
+ }
987
1033
  function parseRequestTokenUsage(raw) {
988
1034
  if (raw === void 0 || raw === null) return;
989
- const record = asRecord$4(raw);
1035
+ const record = asRecord$3(raw);
990
1036
  if (!record) return null;
991
1037
  const usage = {};
992
1038
  for (const [key, value] of Object.entries(record)) {
@@ -997,62 +1043,81 @@ function parseRequestTokenUsage(raw) {
997
1043
  return usage;
998
1044
  }
999
1045
  function isSessionMessageImage(raw) {
1000
- const record = asRecord$4(raw);
1046
+ const record = asRecord$3(raw);
1001
1047
  if (!record || typeof record.source !== "string") return false;
1002
1048
  if (record.size === void 0 || record.size === null) return true;
1003
- 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);
1049
+ const size = asRecord$3(record.size);
1050
+ return !!size && isFiniteNumber(size.width) && isFiniteNumber(size.height);
1051
+ }
1052
+ function isSessionMessageAudio(raw) {
1053
+ const record = asRecord$3(raw);
1054
+ return !!record && typeof record.source === "string" && typeof record.mime_type === "string";
1055
+ }
1056
+ function isFiniteNumber(value) {
1057
+ return typeof value === "number" && Number.isFinite(value);
1005
1058
  }
1006
1059
  function isUserContent(raw) {
1007
- const record = asRecord$4(raw);
1060
+ const record = asRecord$3(raw);
1008
1061
  if (!record) return false;
1009
1062
  if (typeof record.Text === "string") return true;
1010
1063
  if (record.Mention !== void 0) {
1011
- const mention = asRecord$4(record.Mention);
1064
+ const mention = asRecord$3(record.Mention);
1012
1065
  return !!mention && typeof mention.uri === "string" && typeof mention.content === "string";
1013
1066
  }
1014
1067
  if (record.Image !== void 0) return isSessionMessageImage(record.Image);
1068
+ if (record.Audio !== void 0) return isSessionMessageAudio(record.Audio);
1015
1069
  return false;
1016
1070
  }
1017
1071
  function isToolUse(raw) {
1018
- 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");
1072
+ const record = asRecord$3(raw);
1073
+ return !!record && hasStringFields(record, [
1074
+ "id",
1075
+ "name",
1076
+ "raw_input"
1077
+ ]) && hasOwn$1(record, "input") && typeof record.is_input_complete === "boolean" && isOptionalString(record.thought_signature);
1078
+ }
1079
+ function hasStringFields(record, keys) {
1080
+ return keys.every((key) => typeof record[key] === "string");
1081
+ }
1082
+ function isOptionalString(value) {
1083
+ return value === void 0 || value === null || typeof value === "string";
1020
1084
  }
1021
1085
  function isToolResultContent(raw) {
1022
- const record = asRecord$4(raw);
1086
+ const record = asRecord$3(raw);
1023
1087
  if (!record) return false;
1024
1088
  if (typeof record.Text === "string") return true;
1025
1089
  if (record.Image !== void 0) return isSessionMessageImage(record.Image);
1026
1090
  return false;
1027
1091
  }
1028
1092
  function isToolResult(raw) {
1029
- const record = asRecord$4(raw);
1093
+ const record = asRecord$3(raw);
1030
1094
  return !!record && typeof record.tool_use_id === "string" && typeof record.tool_name === "string" && typeof record.is_error === "boolean" && isToolResultContent(record.content);
1031
1095
  }
1032
1096
  function isAgentContent(raw) {
1033
- const record = asRecord$4(raw);
1097
+ const record = asRecord$3(raw);
1034
1098
  if (!record) return false;
1035
1099
  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
- }
1100
+ if (record.Thinking !== void 0) return isThinkingContent(record.Thinking);
1040
1101
  if (typeof record.RedactedThinking === "string") return true;
1041
1102
  if (record.ToolUse !== void 0) return isToolUse(record.ToolUse);
1042
1103
  return false;
1043
1104
  }
1105
+ function isThinkingContent(raw) {
1106
+ const thinking = asRecord$3(raw);
1107
+ return !!thinking && typeof thinking.text === "string" && isOptionalString(thinking.signature);
1108
+ }
1044
1109
  function isUserMessage$1(raw) {
1045
- const record = asRecord$4(raw);
1110
+ const record = asRecord$3(raw);
1046
1111
  if (!record || record.User === void 0) return false;
1047
- const user = asRecord$4(record.User);
1112
+ const user = asRecord$3(record.User);
1048
1113
  return !!user && typeof user.id === "string" && Array.isArray(user.content) && user.content.every((entry) => isUserContent(entry));
1049
1114
  }
1050
1115
  function isAgentMessage$1(raw) {
1051
- const record = asRecord$4(raw);
1116
+ const record = asRecord$3(raw);
1052
1117
  if (!record || record.Agent === void 0) return false;
1053
- const agent = asRecord$4(record.Agent);
1118
+ const agent = asRecord$3(record.Agent);
1054
1119
  if (!agent || !Array.isArray(agent.content) || !agent.content.every(isAgentContent)) return false;
1055
- const toolResults = asRecord$4(agent.tool_results);
1120
+ const toolResults = asRecord$3(agent.tool_results);
1056
1121
  if (!toolResults) return false;
1057
1122
  return Object.values(toolResults).every(isToolResult);
1058
1123
  }
@@ -1060,56 +1125,88 @@ function isConversationMessage(raw) {
1060
1125
  return raw === "Resume" || isUserMessage$1(raw) || isAgentMessage$1(raw);
1061
1126
  }
1062
1127
  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;
1128
+ if (!hasValidConversationCore(record)) return;
1129
+ const title = parseConversationTitle(record.title);
1130
+ if (title === INVALID_VALUE) return;
1065
1131
  const cumulativeTokenUsage = parseTokenUsage(record.cumulative_token_usage);
1066
1132
  const requestTokenUsage = parseRequestTokenUsage(record.request_token_usage);
1067
1133
  if (cumulativeTokenUsage === null || requestTokenUsage === null) return;
1068
1134
  return {
1069
- title: record.title === void 0 || record.title === null || typeof record.title === "string" ? record.title : null,
1135
+ title,
1070
1136
  messages: record.messages,
1071
1137
  updated_at: record.updated_at,
1072
1138
  cumulative_token_usage: cumulativeTokenUsage ?? {},
1073
1139
  request_token_usage: requestTokenUsage ?? {}
1074
1140
  };
1075
1141
  }
1142
+ const INVALID_VALUE = Symbol("invalid");
1143
+ function parseConversationTitle(value) {
1144
+ if (value === void 0 || value === null || typeof value === "string") return value;
1145
+ return INVALID_VALUE;
1146
+ }
1147
+ function hasValidConversationCore(record) {
1148
+ return Array.isArray(record.messages) && record.messages.every(isConversationMessage) && typeof record.updated_at === "string";
1149
+ }
1076
1150
  function parseAcpxState(raw) {
1077
- const record = asRecord$4(raw);
1151
+ const record = asRecord$3(raw);
1078
1152
  if (!record) return;
1079
1153
  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;
1154
+ assignBooleanTrue(state, "reset_on_next_ensure", record.reset_on_next_ensure);
1155
+ assignStringState(state, "current_mode_id", record.current_mode_id);
1156
+ assignStringState(state, "desired_mode_id", record.desired_mode_id);
1157
+ assignDesiredConfigOptions(state, record.desired_config_options);
1158
+ assignStringState(state, "current_model_id", record.current_model_id);
1090
1159
  if (isStringArray(record.available_models)) state.available_models = [...record.available_models];
1091
1160
  if (isStringArray(record.available_commands)) state.available_commands = [...record.available_commands];
1092
1161
  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
- }
1162
+ assignParsedSessionOptions(state, record.session_options);
1107
1163
  return state;
1108
1164
  }
1165
+ function assignBooleanTrue(state, key, value) {
1166
+ if (value === true) state[key] = true;
1167
+ }
1168
+ function assignStringState(state, key, value) {
1169
+ if (typeof value === "string") state[key] = value;
1170
+ }
1171
+ function assignDesiredConfigOptions(state, raw) {
1172
+ const desiredConfigOptions = asRecord$3(raw);
1173
+ if (!desiredConfigOptions) return;
1174
+ const parsed = Object.fromEntries(Object.entries(desiredConfigOptions).filter((entry) => {
1175
+ const [, value] = entry;
1176
+ return typeof value === "string";
1177
+ }));
1178
+ if (Object.keys(parsed).length > 0) state.desired_config_options = parsed;
1179
+ }
1180
+ function assignParsedSessionOptions(state, raw) {
1181
+ const sessionOptions = asRecord$3(raw);
1182
+ if (!sessionOptions) return;
1183
+ const parsedSessionOptions = {};
1184
+ assignSessionOptionModel(parsedSessionOptions, sessionOptions.model);
1185
+ assignSessionOptionAllowedTools(parsedSessionOptions, sessionOptions.allowed_tools);
1186
+ assignSessionOptionMaxTurns(parsedSessionOptions, sessionOptions.max_turns);
1187
+ assignSessionOptionSystemPrompt(parsedSessionOptions, sessionOptions.system_prompt);
1188
+ if (Object.keys(parsedSessionOptions).length > 0) state.session_options = parsedSessionOptions;
1189
+ }
1190
+ function assignSessionOptionModel(options, value) {
1191
+ if (typeof value === "string") options.model = value;
1192
+ }
1193
+ function assignSessionOptionAllowedTools(options, value) {
1194
+ if (isStringArray(value)) options.allowed_tools = [...value];
1195
+ }
1196
+ function assignSessionOptionMaxTurns(options, value) {
1197
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) options.max_turns = value;
1198
+ }
1199
+ function assignSessionOptionSystemPrompt(options, value) {
1200
+ if (typeof value === "string" && value.length > 0) {
1201
+ options.system_prompt = value;
1202
+ return;
1203
+ }
1204
+ const appendRecord = asRecord$3(value);
1205
+ if (appendRecord && typeof appendRecord.append === "string" && appendRecord.append.length > 0) options.system_prompt = { append: appendRecord.append };
1206
+ }
1109
1207
  function parseEventLog(raw, sessionId) {
1110
- 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);
1208
+ const record = asRecord$3(raw);
1209
+ if (!record || !hasValidEventLogCore(record)) return defaultSessionEventLog(sessionId);
1113
1210
  return {
1114
1211
  active_path: record.active_path,
1115
1212
  segment_count: record.segment_count,
@@ -1119,6 +1216,33 @@ function parseEventLog(raw, sessionId) {
1119
1216
  last_write_error: record.last_write_error == null || typeof record.last_write_error === "string" ? record.last_write_error : null
1120
1217
  };
1121
1218
  }
1219
+ function hasValidEventLogCore(record) {
1220
+ return typeof record.active_path === "string" && isPositiveInteger(record.segment_count) && isPositiveInteger(record.max_segment_bytes) && isPositiveInteger(record.max_segments);
1221
+ }
1222
+ function isPositiveInteger(value) {
1223
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
1224
+ }
1225
+ function parseImportedFrom(raw) {
1226
+ if (raw == null) return;
1227
+ const record = asRecord$3(raw);
1228
+ if (!record || typeof record.record_id !== "string" || typeof record.cwd_original !== "string" || typeof record.exported_by !== "string" || typeof record.exported_at !== "string") return null;
1229
+ return {
1230
+ recordId: record.record_id,
1231
+ cwdOriginal: record.cwd_original,
1232
+ exportedBy: record.exported_by,
1233
+ exportedAt: record.exported_at
1234
+ };
1235
+ }
1236
+ function parseSessionRecordMetadata(record) {
1237
+ const lastRequestId = normalizeOptionalString(record.last_request_id);
1238
+ if (lastRequestId === null) return null;
1239
+ const importedFrom = parseImportedFrom(record.imported_from);
1240
+ if (importedFrom === null) return null;
1241
+ return {
1242
+ lastRequestId,
1243
+ importedFrom
1244
+ };
1245
+ }
1122
1246
  function normalizeOptionalName(value) {
1123
1247
  if (value == null) return;
1124
1248
  if (typeof value !== "string") return null;
@@ -1151,25 +1275,27 @@ function normalizeOptionalSignal(value) {
1151
1275
  return Symbol("invalid");
1152
1276
  }
1153
1277
  function parseSessionRecord(raw) {
1154
- const record = asRecord$4(raw);
1278
+ const record = asRecord$3(raw);
1155
1279
  if (!record) return null;
1156
1280
  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;
1281
+ const optionals = validSessionOptionals({
1282
+ name: normalizeOptionalName(record.name),
1283
+ pid: normalizeOptionalPid(record.pid),
1284
+ closed: normalizeOptionalBoolean(record.closed, false),
1285
+ closedAt: normalizeOptionalString(record.closed_at),
1286
+ agentStartedAt: normalizeOptionalString(record.agent_started_at),
1287
+ lastPromptAt: normalizeOptionalString(record.last_prompt_at),
1288
+ lastAgentExitCode: normalizeOptionalExitCode(record.last_agent_exit_code),
1289
+ lastAgentExitSignal: normalizeOptionalSignal(record.last_agent_exit_signal),
1290
+ lastAgentExitAt: normalizeOptionalString(record.last_agent_exit_at),
1291
+ lastAgentDisconnectReason: normalizeOptionalString(record.last_agent_disconnect_reason)
1292
+ });
1293
+ if (!hasValidSessionRecordCore(record) || !optionals) return null;
1168
1294
  const conversation = parseConversationRecord(record);
1169
1295
  if (!conversation) return null;
1170
1296
  const eventLog = parseEventLog(record.event_log, record.acpx_record_id);
1171
- const lastRequestId = normalizeOptionalString(record.last_request_id);
1172
- if (lastRequestId === null) return null;
1297
+ const metadata = parseSessionRecordMetadata(record);
1298
+ if (!metadata) return null;
1173
1299
  return {
1174
1300
  schema: SESSION_RECORD_SCHEMA,
1175
1301
  acpxRecordId: record.acpx_record_id,
@@ -1177,42 +1303,209 @@ function parseSessionRecord(raw) {
1177
1303
  agentSessionId: normalizeRuntimeSessionId(record.agent_session_id),
1178
1304
  agentCommand: record.agent_command,
1179
1305
  cwd: record.cwd,
1180
- name,
1306
+ name: optionals.name,
1181
1307
  createdAt: record.created_at,
1182
1308
  lastUsedAt: record.last_used_at,
1183
1309
  lastSeq: record.last_seq,
1184
- lastRequestId,
1310
+ lastRequestId: metadata.lastRequestId,
1185
1311
  eventLog,
1186
- closed,
1187
- closedAt,
1188
- pid,
1189
- agentStartedAt,
1190
- lastPromptAt,
1191
- lastAgentExitCode,
1192
- lastAgentExitSignal,
1193
- lastAgentExitAt,
1194
- lastAgentDisconnectReason,
1312
+ closed: optionals.closed,
1313
+ closedAt: optionals.closedAt,
1314
+ pid: optionals.pid,
1315
+ agentStartedAt: optionals.agentStartedAt,
1316
+ lastPromptAt: optionals.lastPromptAt,
1317
+ lastAgentExitCode: optionals.lastAgentExitCode,
1318
+ lastAgentExitSignal: optionals.lastAgentExitSignal,
1319
+ lastAgentExitAt: optionals.lastAgentExitAt,
1320
+ lastAgentDisconnectReason: optionals.lastAgentDisconnectReason,
1195
1321
  protocolVersion: typeof record.protocol_version === "number" ? record.protocol_version : void 0,
1196
- agentCapabilities: asRecord$4(record.agent_capabilities),
1322
+ agentCapabilities: asRecord$3(record.agent_capabilities),
1197
1323
  title: conversation.title,
1198
1324
  messages: conversation.messages,
1199
1325
  updated_at: conversation.updated_at,
1200
1326
  cumulative_token_usage: conversation.cumulative_token_usage,
1201
1327
  request_token_usage: conversation.request_token_usage,
1202
- acpx: parseAcpxState(record.acpx)
1328
+ acpx: parseAcpxState(record.acpx),
1329
+ importedFrom: metadata.importedFrom
1203
1330
  };
1204
1331
  }
1332
+ function hasValidSessionRecordCore(record) {
1333
+ return hasStringFields(record, [
1334
+ "acpx_record_id",
1335
+ "acp_session_id",
1336
+ "agent_command",
1337
+ "cwd",
1338
+ "created_at",
1339
+ "last_used_at"
1340
+ ]) && typeof record.last_seq === "number" && Number.isInteger(record.last_seq) && record.last_seq >= 0;
1341
+ }
1342
+ function validSessionOptionals(options) {
1343
+ if (hasNullOptionalSessionFields(options) || hasInvalidExitStatus(options)) return null;
1344
+ return options;
1345
+ }
1346
+ function hasNullOptionalSessionFields(options) {
1347
+ return [
1348
+ options.name,
1349
+ options.pid,
1350
+ options.closed,
1351
+ options.closedAt,
1352
+ options.agentStartedAt,
1353
+ options.lastPromptAt,
1354
+ options.lastAgentExitAt,
1355
+ options.lastAgentDisconnectReason
1356
+ ].some((value) => value === null);
1357
+ }
1358
+ function hasInvalidExitStatus(options) {
1359
+ return typeof options.lastAgentExitCode === "symbol" || typeof options.lastAgentExitSignal === "symbol";
1360
+ }
1361
+ //#endregion
1362
+ //#region src/perf-metrics.ts
1363
+ const counters = /* @__PURE__ */ new Map();
1364
+ const gauges = /* @__PURE__ */ new Map();
1365
+ const timings = /* @__PURE__ */ new Map();
1366
+ function hrNow() {
1367
+ return process.hrtime.bigint();
1368
+ }
1369
+ function durationMs(start) {
1370
+ return Number(process.hrtime.bigint() - start) / 1e6;
1371
+ }
1372
+ function roundMetric(value) {
1373
+ return Number(value.toFixed(3));
1374
+ }
1375
+ function incrementPerfCounter(name, delta = 1) {
1376
+ counters.set(name, (counters.get(name) ?? 0) + delta);
1377
+ }
1378
+ function setPerfGauge(name, value) {
1379
+ gauges.set(name, value);
1380
+ }
1381
+ function recordPerfDuration(name, durationMsValue) {
1382
+ const next = timings.get(name) ?? {
1383
+ count: 0,
1384
+ totalMs: 0,
1385
+ maxMs: 0
1386
+ };
1387
+ next.count += 1;
1388
+ next.totalMs += durationMsValue;
1389
+ next.maxMs = Math.max(next.maxMs, durationMsValue);
1390
+ timings.set(name, next);
1391
+ }
1392
+ async function measurePerf(name, run) {
1393
+ const startedAt = hrNow();
1394
+ try {
1395
+ return await run();
1396
+ } finally {
1397
+ recordPerfDuration(name, durationMs(startedAt));
1398
+ }
1399
+ }
1400
+ function startPerfTimer(name) {
1401
+ const startedAt = hrNow();
1402
+ return () => {
1403
+ const elapsedMs = durationMs(startedAt);
1404
+ recordPerfDuration(name, elapsedMs);
1405
+ return elapsedMs;
1406
+ };
1407
+ }
1408
+ function getPerfMetricsSnapshot() {
1409
+ return {
1410
+ counters: Object.fromEntries(counters.entries()),
1411
+ gauges: Object.fromEntries(gauges.entries()),
1412
+ timings: Object.fromEntries([...timings.entries()].map(([name, bucket]) => [name, {
1413
+ count: bucket.count,
1414
+ totalMs: roundMetric(bucket.totalMs),
1415
+ maxMs: roundMetric(bucket.maxMs)
1416
+ }]))
1417
+ };
1418
+ }
1419
+ function resetPerfMetrics() {
1420
+ counters.clear();
1421
+ gauges.clear();
1422
+ timings.clear();
1423
+ }
1424
+ function formatPerfMetric(name, durationMsValue) {
1425
+ return `${name}=${roundMetric(durationMsValue)}ms`;
1426
+ }
1427
+ //#endregion
1428
+ //#region src/persisted-key-policy.ts
1429
+ const SNAKE_CASE_KEY = /^[a-z][a-z0-9_]*$/;
1430
+ const ZED_TAG_KEYS = new Set([
1431
+ "User",
1432
+ "Agent",
1433
+ "Resume",
1434
+ "Text",
1435
+ "Mention",
1436
+ "Image",
1437
+ "Audio",
1438
+ "Thinking",
1439
+ "RedactedThinking",
1440
+ "ToolUse"
1441
+ ]);
1442
+ const MAP_OBJECT_PATHS = new Set(["request_token_usage", "messages.Agent.tool_results"]);
1443
+ const OPAQUE_VALUE_PATHS = new Set([
1444
+ "agent_capabilities",
1445
+ "messages.Agent.content.ToolUse.input",
1446
+ "acpx.desired_config_options",
1447
+ "acpx.config_options"
1448
+ ]);
1449
+ function isRecord(value) {
1450
+ return !!value && typeof value === "object" && !Array.isArray(value);
1451
+ }
1452
+ function joinPath(path) {
1453
+ return path.join(".");
1454
+ }
1455
+ function isAllowedKey(path, key) {
1456
+ if (ZED_TAG_KEYS.has(key)) return true;
1457
+ return false;
1458
+ }
1459
+ function shouldSkipKeyRule(path) {
1460
+ return MAP_OBJECT_PATHS.has(joinPath(path));
1461
+ }
1462
+ function shouldSkipDescend(path) {
1463
+ return OPAQUE_VALUE_PATHS.has(joinPath(path)) || isToolResultOutputPath(path);
1464
+ }
1465
+ function isToolResultOutputTail(path, toolResultsIndex) {
1466
+ return toolResultsIndex !== -1 && toolResultsIndex + 2 === path.length - 1;
1467
+ }
1468
+ function isToolResultOutputPath(path) {
1469
+ if (path.length < 5 || path[path.length - 1] !== "output") return false;
1470
+ const toolResultsIndex = path.lastIndexOf("tool_results");
1471
+ if (!isToolResultOutputTail(path, toolResultsIndex)) return false;
1472
+ return path.slice(0, toolResultsIndex + 1).join(".") === "messages.Agent.tool_results";
1473
+ }
1474
+ function collectViolations(value, path, violations) {
1475
+ if (Array.isArray(value)) {
1476
+ for (const entry of value) collectViolations(entry, path, violations);
1477
+ return;
1478
+ }
1479
+ if (!isRecord(value)) return;
1480
+ const skipKeyRule = shouldSkipKeyRule(path);
1481
+ for (const [key, child] of Object.entries(value)) collectKeyViolation(child, key, path, skipKeyRule, violations);
1482
+ }
1483
+ function collectKeyViolation(child, key, path, skipKeyRule, violations) {
1484
+ if (!skipKeyRule && !SNAKE_CASE_KEY.test(key) && !isAllowedKey(path, key)) violations.push(`${joinPath(path)}.${key}`.replace(/^\./, ""));
1485
+ const childPath = [...path, key];
1486
+ if (!shouldSkipDescend(childPath)) collectViolations(child, childPath, violations);
1487
+ }
1488
+ function findPersistedKeyPolicyViolations(value) {
1489
+ const violations = [];
1490
+ collectViolations(value, [], violations);
1491
+ return violations;
1492
+ }
1493
+ function assertPersistedKeyPolicy(value) {
1494
+ const violations = findPersistedKeyPolicyViolations(value);
1495
+ if (violations.length === 0) return;
1496
+ throw new Error(`Persisted key policy violation (expected snake_case keys): ${violations.join(", ")}`);
1497
+ }
1205
1498
  //#endregion
1206
1499
  //#region src/session/persistence/index.ts
1207
1500
  const SESSION_INDEX_SCHEMA = "acpx.session-index.v1";
1208
- function asRecord$3(value) {
1501
+ function asRecord$2(value) {
1209
1502
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
1210
1503
  return value;
1211
1504
  }
1212
1505
  function parseIndexEntry(raw) {
1213
- const record = asRecord$3(raw);
1506
+ const record = asRecord$2(raw);
1214
1507
  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;
1508
+ if (!hasRequiredIndexEntryFields(record)) return;
1216
1509
  if (record.name !== void 0 && typeof record.name !== "string") return;
1217
1510
  return {
1218
1511
  file: record.file,
@@ -1225,6 +1518,16 @@ function parseIndexEntry(raw) {
1225
1518
  lastUsedAt: record.lastUsedAt
1226
1519
  };
1227
1520
  }
1521
+ function hasRequiredIndexEntryFields(record) {
1522
+ return [
1523
+ "file",
1524
+ "acpxRecordId",
1525
+ "acpSessionId",
1526
+ "agentCommand",
1527
+ "cwd",
1528
+ "lastUsedAt"
1529
+ ].every((key) => typeof record[key] === "string") && typeof record.closed === "boolean";
1530
+ }
1228
1531
  function sessionIndexPath(sessionDir) {
1229
1532
  return path.join(sessionDir, "index.json");
1230
1533
  }
@@ -1244,7 +1547,7 @@ async function readSessionIndex(sessionDir) {
1244
1547
  const filePath = sessionIndexPath(sessionDir);
1245
1548
  try {
1246
1549
  const payload = await fs$1.readFile(filePath, "utf8");
1247
- const record = asRecord$3(JSON.parse(payload));
1550
+ const record = asRecord$2(JSON.parse(payload));
1248
1551
  if (!record || record.schema !== SESSION_INDEX_SCHEMA || !Array.isArray(record.files)) return;
1249
1552
  const files = record.files.filter((entry) => typeof entry === "string");
1250
1553
  if (files.length !== record.files.length || !Array.isArray(record.entries)) return;
@@ -1436,13 +1739,17 @@ async function findSessionByDirectoryWalk(options) {
1436
1739
  for (;;) {
1437
1740
  const match = sessions.find((session) => matchesSessionEntry(session, current, normalizedName));
1438
1741
  if (match) return await loadRecordFromIndexEntry(match);
1439
- if (current === walkBoundary || current === walkRoot) return;
1440
- const parent = path.dirname(current);
1441
- if (parent === current) return;
1742
+ const parent = nextWalkParent(current, walkBoundary, walkRoot);
1743
+ if (!parent) return;
1442
1744
  current = parent;
1443
- if (!isWithinBoundary(walkBoundary, current)) return;
1444
1745
  }
1445
1746
  }
1747
+ function nextWalkParent(current, walkBoundary, walkRoot) {
1748
+ if (current === walkBoundary || current === walkRoot) return;
1749
+ const parent = path.dirname(current);
1750
+ if (parent === current || !isWithinBoundary(walkBoundary, parent)) return;
1751
+ return parent;
1752
+ }
1446
1753
  function closedAtOrLastUsedAt(record) {
1447
1754
  return record.closedAt ?? record.lastUsedAt;
1448
1755
  }
@@ -1451,14 +1758,7 @@ function isSessionStreamFile(fileName, safeId) {
1451
1758
  }
1452
1759
  async function pruneSessions(options = {}) {
1453
1760
  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
- }
1761
+ const records = await loadPrunableRecords(filterPruneCandidates(await loadSessionIndexEntries(), options.agentCommand), options.before ?? (options.olderThanMs != null ? new Date(Date.now() - options.olderThanMs) : void 0));
1462
1762
  if (options.dryRun) return {
1463
1763
  pruned: records,
1464
1764
  bytesFreed: 0,
@@ -1470,24 +1770,7 @@ async function pruneSessions(options = {}) {
1470
1770
  if (options.includeHistory) try {
1471
1771
  dirEntries = await fs$1.readdir(sessionDir);
1472
1772
  } 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
- }
1773
+ for (const record of records) bytesFreed += await pruneSessionFiles(record, sessionDir, dirEntries, options.includeHistory === true);
1491
1774
  await rebuildSessionIndex(sessionDir).catch(() => {});
1492
1775
  return {
1493
1776
  pruned: records,
@@ -1495,6 +1778,35 @@ async function pruneSessions(options = {}) {
1495
1778
  dryRun: false
1496
1779
  };
1497
1780
  }
1781
+ function filterPruneCandidates(entries, agentCommand) {
1782
+ return entries.filter((entry) => entry.closed && (!agentCommand || entry.agentCommand === agentCommand));
1783
+ }
1784
+ async function loadPrunableRecords(entries, cutoff) {
1785
+ const records = [];
1786
+ const cutoffIso = cutoff?.toISOString();
1787
+ for (const entry of entries) {
1788
+ const record = await loadRecordFromIndexEntry(entry);
1789
+ if (record && isBeforeCutoff(record, cutoffIso)) records.push(record);
1790
+ }
1791
+ return records;
1792
+ }
1793
+ function isBeforeCutoff(record, cutoffIso) {
1794
+ return !cutoffIso || closedAtOrLastUsedAt(record) < cutoffIso;
1795
+ }
1796
+ async function pruneSessionFiles(record, sessionDir, dirEntries, includeHistory) {
1797
+ const safeId = encodeURIComponent(record.acpxRecordId);
1798
+ let bytesFreed = await unlinkCountingBytes(path.join(sessionDir, `${safeId}.json`));
1799
+ if (includeHistory) for (const name of dirEntries.filter((entry) => isSessionStreamFile(entry, safeId))) bytesFreed += await unlinkCountingBytes(path.join(sessionDir, name));
1800
+ return bytesFreed;
1801
+ }
1802
+ async function unlinkCountingBytes(filePath) {
1803
+ let bytes = 0;
1804
+ try {
1805
+ bytes = (await fs$1.stat(filePath)).size;
1806
+ } catch {}
1807
+ await fs$1.unlink(filePath).catch(() => void 0);
1808
+ return bytes;
1809
+ }
1498
1810
  //#endregion
1499
1811
  //#region src/permission-prompt.ts
1500
1812
  async function promptForPermission(options) {
@@ -1691,22 +2003,67 @@ function pickOption(options, kinds) {
1691
2003
  const match = options.find((option) => option.kind === kind);
1692
2004
  if (match) return match;
1693
2005
  }
1694
- }
2006
+ }
2007
+ const TOOL_KIND_TITLE_MATCHERS = [
2008
+ {
2009
+ kind: "read",
2010
+ needles: ["read", "cat"]
2011
+ },
2012
+ {
2013
+ kind: "search",
2014
+ needles: [
2015
+ "search",
2016
+ "find",
2017
+ "grep"
2018
+ ]
2019
+ },
2020
+ {
2021
+ kind: "edit",
2022
+ needles: [
2023
+ "write",
2024
+ "edit",
2025
+ "patch"
2026
+ ]
2027
+ },
2028
+ {
2029
+ kind: "delete",
2030
+ needles: ["delete", "remove"]
2031
+ },
2032
+ {
2033
+ kind: "move",
2034
+ needles: ["move", "rename"]
2035
+ },
2036
+ {
2037
+ kind: "execute",
2038
+ needles: [
2039
+ "run",
2040
+ "execute",
2041
+ "bash"
2042
+ ]
2043
+ },
2044
+ {
2045
+ kind: "fetch",
2046
+ needles: [
2047
+ "fetch",
2048
+ "http",
2049
+ "url"
2050
+ ]
2051
+ },
2052
+ {
2053
+ kind: "think",
2054
+ needles: ["think"]
2055
+ }
2056
+ ];
1695
2057
  function inferToolKind(params) {
1696
2058
  if (params.toolCall.kind) return params.toolCall.kind;
1697
2059
  const title = params.toolCall.title?.trim().toLowerCase();
1698
2060
  if (!title) return;
1699
2061
  const head = title.split(":", 1)[0]?.trim();
1700
2062
  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";
2063
+ return titleHeadToolKind(head) ?? "other";
2064
+ }
2065
+ function titleHeadToolKind(head) {
2066
+ return TOOL_KIND_TITLE_MATCHERS.find(({ needles }) => needles.some((needle) => head.includes(needle)))?.kind;
1710
2067
  }
1711
2068
  function isAutoApprovedReadKind(kind) {
1712
2069
  return kind === "read" || kind === "search";
@@ -1801,6 +2158,44 @@ function buildEscalationEvent(params, matchedRule) {
1801
2158
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1802
2159
  };
1803
2160
  }
2161
+ function selectedOrFirst(options, allowOption) {
2162
+ return { response: selected((allowOption ?? options[0]).optionId) };
2163
+ }
2164
+ function selectedOrCancelled(option) {
2165
+ return { response: option ? selected(option.optionId) : cancelled() };
2166
+ }
2167
+ async function resolveEscalatingPermissionRequest(params, policyMatch, allowOption, rejectOption) {
2168
+ if (canPromptForPermission$1()) return resolveInteractivePromptResult(params, allowOption, rejectOption);
2169
+ const escalation = buildEscalationEvent(params, policyMatch.matchedRule);
2170
+ return {
2171
+ response: withEscalationMetadata(rejectOption ? selected(rejectOption.optionId) : cancelled(), escalation),
2172
+ escalation
2173
+ };
2174
+ }
2175
+ async function resolveInteractivePromptResult(params, allowOption, rejectOption) {
2176
+ const approved = await promptForToolPermission(params);
2177
+ if (approved && allowOption) return { response: selected(allowOption.optionId) };
2178
+ if (!approved && rejectOption) return { response: selected(rejectOption.optionId) };
2179
+ return { response: cancelled() };
2180
+ }
2181
+ function resolvePolicyMatch(params, policyMatch, options, allowOption, rejectOption) {
2182
+ if (policyMatch?.action === "approve") return selectedOrFirst(options, allowOption);
2183
+ if (policyMatch?.action === "deny") return selectedOrCancelled(rejectOption);
2184
+ if (policyMatch?.action === "escalate") return resolveEscalatingPermissionRequest(params, policyMatch, allowOption, rejectOption);
2185
+ }
2186
+ function resolveModeMatch(options, mode, allowOption, rejectOption) {
2187
+ if (mode === "approve-all") return selectedOrFirst(options, allowOption);
2188
+ if (mode === "deny-all") return selectedOrCancelled(rejectOption);
2189
+ }
2190
+ function resolveNonInteractivePermission(nonInteractivePolicy, rejectOption) {
2191
+ if (nonInteractivePolicy === "fail") throw new PermissionPromptUnavailableError();
2192
+ return selectedOrCancelled(rejectOption);
2193
+ }
2194
+ async function resolveReadOrPromptPermission(params, nonInteractivePolicy, allowOption, rejectOption) {
2195
+ if (isAutoApprovedReadKind(inferToolKind(params)) && allowOption) return { response: selected(allowOption.optionId) };
2196
+ if (!canPromptForPermission$1()) return resolveNonInteractivePermission(nonInteractivePolicy, rejectOption);
2197
+ return resolveInteractivePromptResult(params, allowOption, rejectOption);
2198
+ }
1804
2199
  function permissionModeSatisfies(actual, required) {
1805
2200
  return PERMISSION_MODE_RANK[actual] >= PERMISSION_MODE_RANK[required];
1806
2201
  }
@@ -1809,46 +2204,11 @@ async function resolvePermissionRequestWithDetails(params, mode, nonInteractiveP
1809
2204
  if (options.length === 0) return { response: cancelled() };
1810
2205
  const allowOption = pickOption(options, ["allow_once", "allow_always"]);
1811
2206
  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() };
2207
+ const resolvedByPolicy = await resolvePolicyMatch(params, matchPermissionPolicy(params, policy), options, allowOption, rejectOption);
2208
+ if (resolvedByPolicy) return resolvedByPolicy;
2209
+ const resolvedByMode = resolveModeMatch(options, mode, allowOption, rejectOption);
2210
+ if (resolvedByMode) return resolvedByMode;
2211
+ return resolveReadOrPromptPermission(params, nonInteractivePolicy, allowOption, rejectOption);
1852
2212
  }
1853
2213
  const DECISION_FALLBACK_ORDER = {
1854
2214
  allow_once: ["allow_once", "allow_always"],
@@ -1875,21 +2235,35 @@ function readWindowsEnvValue(env, key) {
1875
2235
  const matchedKey = Object.keys(env).find((entry) => entry.toUpperCase() === key);
1876
2236
  return matchedKey ? env[matchedKey] : void 0;
1877
2237
  }
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));
2238
+ function windowsExecutableExtensions(env) {
2239
+ return (readWindowsEnvValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
2240
+ }
2241
+ function commandCandidates(command, env) {
2242
+ if (path.extname(command).length > 0) return [command];
2243
+ return windowsExecutableExtensions(env).map((extension) => `${command}${extension}`);
2244
+ }
2245
+ function commandHasPath(command) {
2246
+ return command.includes("/") || command.includes("\\") || path.isAbsolute(command);
2247
+ }
2248
+ function resolveWindowsPathCommand(command, env) {
2249
+ const candidates = commandCandidates(command, env);
1882
2250
  const pathValue = readWindowsEnvValue(env, "PATH");
1883
2251
  if (!pathValue) return;
1884
2252
  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
- }
2253
+ const resolved = findExistingCommandInDirectory(directory, candidates);
2254
+ if (resolved) return resolved;
1891
2255
  }
1892
2256
  }
2257
+ function findExistingCommandInDirectory(directory, candidates) {
2258
+ const trimmedDirectory = directory.trim();
2259
+ if (trimmedDirectory.length === 0) return;
2260
+ return candidates.map((candidate) => path.join(trimmedDirectory, candidate)).find((resolved) => fs.existsSync(resolved));
2261
+ }
2262
+ function resolveWindowsCommand(command, env = process.env) {
2263
+ const candidates = commandCandidates(command, env);
2264
+ if (commandHasPath(command)) return candidates.find((candidate) => fs.existsSync(candidate));
2265
+ return resolveWindowsPathCommand(command, env);
2266
+ }
1893
2267
  function shouldUseWindowsBatchShell(command, platform = process.platform, env = process.env) {
1894
2268
  if (platform !== "win32") return false;
1895
2269
  const resolvedCommand = resolveWindowsCommand(command, env) ?? command;
@@ -1982,32 +2356,16 @@ function splitCommandLine(value) {
1982
2356
  let quote = null;
1983
2357
  let escaping = false;
1984
2358
  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;
2359
+ const next = readCommandLineChar({
2360
+ ch,
2361
+ current,
2362
+ quote,
2363
+ escaping,
2364
+ parts
2365
+ });
2366
+ current = next.current;
2367
+ quote = next.quote;
2368
+ escaping = next.escaping;
2011
2369
  }
2012
2370
  if (escaping) current += "\\";
2013
2371
  if (quote) throw new Error("Invalid --agent command: unterminated quote");
@@ -2018,6 +2376,59 @@ function splitCommandLine(value) {
2018
2376
  args: parts.slice(1)
2019
2377
  };
2020
2378
  }
2379
+ function readCommandLineChar(state) {
2380
+ if (state.escaping) return {
2381
+ current: state.current + state.ch,
2382
+ quote: state.quote,
2383
+ escaping: false
2384
+ };
2385
+ if (state.ch === "\\" && state.quote !== "'") return {
2386
+ current: state.current,
2387
+ quote: state.quote,
2388
+ escaping: true
2389
+ };
2390
+ if (state.quote) return readQuotedCommandLineChar({
2391
+ ch: state.ch,
2392
+ current: state.current,
2393
+ quote: state.quote
2394
+ });
2395
+ return readUnquotedCommandLineChar(state);
2396
+ }
2397
+ function readQuotedCommandLineChar(state) {
2398
+ if (state.ch === state.quote) return {
2399
+ current: state.current,
2400
+ quote: null,
2401
+ escaping: false
2402
+ };
2403
+ return {
2404
+ current: state.current + state.ch,
2405
+ quote: state.quote,
2406
+ escaping: false
2407
+ };
2408
+ }
2409
+ function readUnquotedCommandLineChar(state) {
2410
+ if (state.ch === "'" || state.ch === "\"") return {
2411
+ current: state.current,
2412
+ quote: state.ch,
2413
+ escaping: false
2414
+ };
2415
+ if (/\s/.test(state.ch)) {
2416
+ flushCommandLinePart(state.parts, state.current);
2417
+ return {
2418
+ current: "",
2419
+ quote: null,
2420
+ escaping: false
2421
+ };
2422
+ }
2423
+ return {
2424
+ current: state.current + state.ch,
2425
+ quote: null,
2426
+ escaping: false
2427
+ };
2428
+ }
2429
+ function flushCommandLinePart(parts, current) {
2430
+ if (current.length > 0) parts.push(current);
2431
+ }
2021
2432
  function asAbsoluteCwd(cwd) {
2022
2433
  return path.resolve(cwd);
2023
2434
  }
@@ -2231,17 +2642,28 @@ async function ensureCopilotAcpSupport(command) {
2231
2642
  function buildClaudeCodeOptionsMeta(options) {
2232
2643
  if (!options) return;
2233
2644
  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;
2645
+ assignClaudeCodeOptions(claudeCodeOptions, options);
2237
2646
  const meta = {};
2238
2647
  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 };
2648
+ assignClaudeCodeSystemPrompt(meta, options.systemPrompt);
2242
2649
  if (Object.keys(meta).length === 0) return;
2243
2650
  return meta;
2244
2651
  }
2652
+ function assignClaudeCodeOptions(target, options) {
2653
+ if (typeof options.model === "string" && options.model.trim().length > 0) target.model = options.model;
2654
+ if (Array.isArray(options.allowedTools)) target.allowedTools = [...options.allowedTools];
2655
+ if (typeof options.maxTurns === "number") target.maxTurns = options.maxTurns;
2656
+ }
2657
+ function assignClaudeCodeSystemPrompt(target, systemPrompt) {
2658
+ if (typeof systemPrompt === "string" && systemPrompt.length > 0) {
2659
+ target.systemPrompt = systemPrompt;
2660
+ return;
2661
+ }
2662
+ if (isAppendSystemPrompt(systemPrompt)) target.systemPrompt = { append: systemPrompt.append };
2663
+ }
2664
+ function isAppendSystemPrompt(value) {
2665
+ return !!value && typeof value === "object" && typeof value.append === "string" && value.append.length > 0;
2666
+ }
2245
2667
  function resolveClaudeCodeExecutable(platform = process.platform, env = process.env) {
2246
2668
  if (platform !== "win32") return;
2247
2669
  if (readWindowsEnvValue(env, "CLAUDE_CODE_EXECUTABLE")) return;
@@ -2286,18 +2708,21 @@ function buildAgentEnvironment(authCredentials) {
2286
2708
  const env = { ...process.env };
2287
2709
  promotePrefixedAuthEnvironment(env);
2288
2710
  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
- }
2711
+ for (const [methodId, credential] of Object.entries(authCredentials)) assignAuthCredentialEnv(env, methodId, credential);
2299
2712
  return env;
2300
2713
  }
2714
+ function assignAuthCredentialEnv(env, methodId, credential) {
2715
+ if (typeof credential !== "string" || credential.trim().length === 0) return;
2716
+ if (!methodId.includes("=") && !methodId.includes("\0") && env[methodId] == null) env[methodId] = credential;
2717
+ const normalized = toEnvToken(methodId);
2718
+ if (normalized) {
2719
+ assignIfMissing(env, `${AUTH_ENV_PREFIX}${normalized}`, credential);
2720
+ assignIfMissing(env, normalized, credential);
2721
+ }
2722
+ }
2723
+ function assignIfMissing(env, key, value) {
2724
+ if (env[key] == null) env[key] = value;
2725
+ }
2301
2726
  function resolveConfiguredAuthCredential(methodId, authCredentials) {
2302
2727
  const configCredentials = authCredentials ?? {};
2303
2728
  return configCredentials[methodId] ?? configCredentials[toEnvToken(methodId)];
@@ -2315,71 +2740,6 @@ function buildAgentSpawnOptions(cwd, authCredentials) {
2315
2740
  };
2316
2741
  }
2317
2742
  //#endregion
2318
- //#region src/acp/jsonrpc.ts
2319
- function asRecord$2(value) {
2320
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
2321
- return value;
2322
- }
2323
- function hasValidId(value) {
2324
- return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
2325
- }
2326
- function isErrorObject(value) {
2327
- const record = asRecord$2(value);
2328
- return !!record && typeof record.code === "number" && Number.isFinite(record.code) && typeof record.message === "string";
2329
- }
2330
- function hasResultOrError(value) {
2331
- const hasResult = Object.hasOwn(value, "result");
2332
- const hasError = Object.hasOwn(value, "error");
2333
- if (hasResult && hasError) return false;
2334
- if (!hasResult && !hasError) return false;
2335
- if (hasError && !isErrorObject(value.error)) return false;
2336
- return true;
2337
- }
2338
- function isAcpJsonRpcMessage(value) {
2339
- const record = asRecord$2(value);
2340
- 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;
2350
- }
2351
- function isJsonRpcNotification(message) {
2352
- return Object.hasOwn(message, "method") && typeof message.method === "string" && !Object.hasOwn(message, "id");
2353
- }
2354
- function isSessionUpdateNotification(message) {
2355
- return isJsonRpcNotification(message) && message.method === "session/update";
2356
- }
2357
- function extractSessionUpdateNotification(message) {
2358
- if (!isSessionUpdateNotification(message)) return;
2359
- const params = asRecord$2(message.params);
2360
- if (!params) return;
2361
- const sessionId = typeof params.sessionId === "string" ? params.sessionId : null;
2362
- if (!sessionId) return;
2363
- const update = asRecord$2(params.update);
2364
- if (!update || typeof update.sessionUpdate !== "string") return;
2365
- return {
2366
- sessionId,
2367
- update
2368
- };
2369
- }
2370
- function parsePromptStopReason(message) {
2371
- if (!Object.hasOwn(message, "id") || !Object.hasOwn(message, "result")) return;
2372
- const record = asRecord$2(message.result);
2373
- if (!record) return;
2374
- return typeof record.stopReason === "string" ? record.stopReason : void 0;
2375
- }
2376
- function parseJsonRpcErrorMessage(message) {
2377
- if (!Object.hasOwn(message, "error")) return;
2378
- const errorRecord = asRecord$2(message.error);
2379
- if (!errorRecord || typeof errorRecord.message !== "string") return;
2380
- return errorRecord.message;
2381
- }
2382
- //#endregion
2383
2743
  //#region src/acp/session-control-errors.ts
2384
2744
  const SESSION_CONTROL_UNSUPPORTED_ACP_CODES = new Set([-32601, -32602]);
2385
2745
  function asRecord$1(value) {
@@ -2710,27 +3070,35 @@ var TerminalManager = class {
2710
3070
  async signalProcess(terminal, signal) {
2711
3071
  const pid = terminal.process.pid;
2712
3072
  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);
3073
+ await this.signalWindowsProcessGroup(terminal, pid, signal);
2720
3074
  return;
2721
3075
  }
2722
3076
  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);
3077
+ await this.signalPosixProcessGroup(terminal, pid, signal);
2730
3078
  return;
2731
3079
  }
2732
3080
  terminal.process.kill(signal);
2733
3081
  }
3082
+ async signalWindowsProcessGroup(terminal, pid, signal) {
3083
+ await this.captureDescendantPids(terminal, pid);
3084
+ if (this.isRunning(terminal)) {
3085
+ await killWindowsProcessTree(pid, signal);
3086
+ return;
3087
+ }
3088
+ for (const descendantPid of terminal.descendantPids) await killWindowsProcessTree(descendantPid, signal);
3089
+ }
3090
+ async signalPosixProcessGroup(terminal, pid, signal) {
3091
+ await this.captureDescendantPids(terminal, pid);
3092
+ if (hasLiveProcessGroup(pid)) {
3093
+ sendSignal(-pid, signal);
3094
+ return;
3095
+ }
3096
+ for (const descendantPid of terminal.descendantPids) sendSignal(descendantPid, signal);
3097
+ }
3098
+ async captureDescendantPids(terminal, pid) {
3099
+ if (!this.isRunning(terminal)) await terminal.processGroupSnapshotPromise?.catch(() => {});
3100
+ for (const descendantPid of await listDescendantPids(pid)) terminal.descendantPids.add(descendantPid);
3101
+ }
2734
3102
  async waitForCleanupAfterSignal(terminal) {
2735
3103
  return await Promise.race([this.waitForTerminalAndTrackedDescendants(terminal).then(() => true), waitMs(this.killGraceMs).then(() => false)]);
2736
3104
  }
@@ -2790,16 +3158,7 @@ async function listDescendantPids(rootPid) {
2790
3158
  return [];
2791
3159
  }
2792
3160
  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
- }
3161
+ for (const line of output.split("\n")) addProcessListLine(childrenByParent, line);
2803
3162
  const descendants = [];
2804
3163
  const queue = [...childrenByParent.get(rootPid) ?? []];
2805
3164
  for (let index = 0; index < queue.length; index += 1) {
@@ -2809,6 +3168,24 @@ async function listDescendantPids(rootPid) {
2809
3168
  }
2810
3169
  return descendants;
2811
3170
  }
3171
+ function addProcessListLine(childrenByParent, line) {
3172
+ const parsed = parseProcessListLine(line);
3173
+ if (!parsed) return;
3174
+ const children = childrenByParent.get(parsed.parentPid);
3175
+ if (children) children.push(parsed.pid);
3176
+ else childrenByParent.set(parsed.parentPid, [parsed.pid]);
3177
+ }
3178
+ function parseProcessListLine(line) {
3179
+ const match = line.trim().match(/^(\d+)\s+(\d+)$/);
3180
+ if (!match) return;
3181
+ const pid = Number(match[1]);
3182
+ const parentPid = Number(match[2]);
3183
+ if (!Number.isInteger(pid) || !Number.isInteger(parentPid) || pid <= 0 || parentPid <= 0) return;
3184
+ return {
3185
+ pid,
3186
+ parentPid
3187
+ };
3188
+ }
2812
3189
  async function runProcessListCommand() {
2813
3190
  if (process.platform === "win32") return await runWindowsProcessListCommand();
2814
3191
  return await new Promise((resolve, reject) => {
@@ -2979,6 +3356,20 @@ const DRAIN_POLL_INTERVAL_MS = 20;
2979
3356
  const AGENT_CLOSE_TERM_GRACE_MS = 1500;
2980
3357
  const AGENT_CLOSE_KILL_GRACE_MS = 1e3;
2981
3358
  const STARTUP_STDERR_MAX_CHARS = 8192;
3359
+ function toReconnectedSessionResult(response) {
3360
+ return {
3361
+ agentSessionId: extractRuntimeSessionId(response?._meta),
3362
+ configOptions: response?.configOptions ?? void 0,
3363
+ models: response?.models ?? void 0
3364
+ };
3365
+ }
3366
+ function childProcessIsRunning(agent) {
3367
+ if (!agent) return false;
3368
+ return agent.exitCode == null && agent.signalCode == null && !agent.killed;
3369
+ }
3370
+ function cancelledPermissionResponse() {
3371
+ return { outcome: { outcome: "cancelled" } };
3372
+ }
2982
3373
  function shouldSuppressSdkConsoleError(args) {
2983
3374
  if (args.length === 0) return false;
2984
3375
  return typeof args[0] === "string" && args[0] === "Error handling request";
@@ -2993,6 +3384,19 @@ function installSdkConsoleErrorSuppression() {
2993
3384
  console.error = originalConsoleError;
2994
3385
  };
2995
3386
  }
3387
+ function enqueueNdJsonLine(agentCommand, line, controller) {
3388
+ const trimmedLine = line.trim();
3389
+ if (!trimmedLine || shouldIgnoreNonJsonAgentOutputLine(agentCommand, trimmedLine)) return;
3390
+ try {
3391
+ const message = JSON.parse(trimmedLine);
3392
+ controller.enqueue(message);
3393
+ } catch (err) {
3394
+ console.error("Failed to parse JSON message:", trimmedLine, err);
3395
+ }
3396
+ }
3397
+ function enqueueNdJsonLines(agentCommand, lines, controller) {
3398
+ for (const line of lines) enqueueNdJsonLine(agentCommand, line, controller);
3399
+ }
2996
3400
  function createNdJsonMessageStream(agentCommand, output, input) {
2997
3401
  const textEncoder = new TextEncoder();
2998
3402
  const textDecoder = new TextDecoder();
@@ -3008,16 +3412,7 @@ function createNdJsonMessageStream(agentCommand, output, input) {
3008
3412
  content += textDecoder.decode(value, { stream: true });
3009
3413
  const lines = content.split("\n");
3010
3414
  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
- }
3415
+ enqueueNdJsonLines(agentCommand, lines, controller);
3021
3416
  }
3022
3417
  } finally {
3023
3418
  reader.releaseLock();
@@ -3105,7 +3500,7 @@ var AcpClient = class {
3105
3500
  }
3106
3501
  getAgentLifecycleSnapshot() {
3107
3502
  const pid = this.agent?.pid ?? this.lastKnownPid;
3108
- const running = Boolean(this.agent) && this.agent?.exitCode == null && this.agent?.signalCode == null && !this.agent?.killed;
3503
+ const running = childProcessIsRunning(this.agent);
3109
3504
  return {
3110
3505
  pid,
3111
3506
  startedAt: this.agentStartedAt,
@@ -3116,9 +3511,15 @@ var AcpClient = class {
3116
3511
  supportsLoadSession() {
3117
3512
  return Boolean(this.initResult?.agentCapabilities?.loadSession);
3118
3513
  }
3514
+ supportsResumeSession() {
3515
+ return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.resume);
3516
+ }
3119
3517
  supportsCloseSession() {
3120
3518
  return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.close);
3121
3519
  }
3520
+ supportsListSessions() {
3521
+ return Boolean(this.initResult?.agentCapabilities?.sessionCapabilities?.list);
3522
+ }
3122
3523
  setEventHandlers(handlers) {
3123
3524
  this.eventHandlers = { ...handlers };
3124
3525
  }
@@ -3126,17 +3527,20 @@ var AcpClient = class {
3126
3527
  this.eventHandlers = {};
3127
3528
  }
3128
3529
  updateRuntimeOptions(options) {
3530
+ const shouldRefreshPermissionPolicy = options.permissionMode !== void 0 || options.nonInteractivePermissions !== void 0;
3129
3531
  if (options.permissionMode) this.options.permissionMode = options.permissionMode;
3130
3532
  if (options.nonInteractivePermissions !== void 0) this.options.nonInteractivePermissions = options.nonInteractivePermissions;
3131
3533
  if (Object.prototype.hasOwnProperty.call(options, "permissionPolicy")) this.options.permissionPolicy = options.permissionPolicy;
3132
3534
  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
- }
3535
+ this.refreshRuntimePermissionPolicy(shouldRefreshPermissionPolicy);
3137
3536
  if (options.suppressSdkConsoleErrors !== void 0) this.options.suppressSdkConsoleErrors = options.suppressSdkConsoleErrors;
3138
3537
  if (options.verbose !== void 0) this.options.verbose = options.verbose;
3139
3538
  }
3539
+ refreshRuntimePermissionPolicy(enabled) {
3540
+ if (!enabled) return;
3541
+ this.filesystem.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
3542
+ this.terminalManager.updatePermissionPolicy(this.options.permissionMode, this.options.nonInteractivePermissions);
3543
+ }
3140
3544
  hasReusableSession(sessionId) {
3141
3545
  return this.connection != null && this.agent != null && isChildProcessRunning(this.agent) && this.loadedSessionId === sessionId;
3142
3546
  }
@@ -3148,32 +3552,10 @@ var AcpClient = class {
3148
3552
  async start() {
3149
3553
  if (this.connection && this.agent && isChildProcessRunning(this.agent)) return;
3150
3554
  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);
3555
+ const launch = await this.resolveAgentLaunchPlan();
3556
+ this.logAgentLaunch(launch);
3557
+ await this.ensureLaunchSupport(launch);
3558
+ const child = await this.spawnAgentProcess(launch);
3177
3559
  this.closing = false;
3178
3560
  this.agentStartedAt = isoNow$1();
3179
3561
  this.lastAgentExit = void 0;
@@ -3187,7 +3569,69 @@ var AcpClient = class {
3187
3569
  });
3188
3570
  const input = Writable.toWeb(child.stdin);
3189
3571
  const output = Readable.toWeb(child.stdout);
3190
- const connection = new ClientSideConnection(() => ({
3572
+ const stream = this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output));
3573
+ const connection = this.createConnection(stream);
3574
+ connection.signal.addEventListener("abort", () => {
3575
+ this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null);
3576
+ }, { once: true });
3577
+ const startupFailure = this.createStartupFailureWatcher(child, startupStderr);
3578
+ await this.initializeAgentConnection({
3579
+ child,
3580
+ connection,
3581
+ startupFailure,
3582
+ startupStderr,
3583
+ launch
3584
+ });
3585
+ }
3586
+ async resolveAgentLaunchPlan() {
3587
+ const configuredCommand = splitCommandLine(this.options.agentCommand);
3588
+ const resolvedBuiltInLaunch = resolveBuiltInAgentLaunch(this.options.agentCommand);
3589
+ const spawnCommand = resolvedBuiltInLaunch?.command ?? configuredCommand.command;
3590
+ let args = resolvedBuiltInLaunch?.args ?? configuredCommand.args;
3591
+ args = await resolveGeminiCommandArgs(spawnCommand, args);
3592
+ if (isQoderAcpCommand(spawnCommand, args)) args = buildQoderAcpCommandArgs(args, this.options);
3593
+ return {
3594
+ spawnCommand,
3595
+ args,
3596
+ resolvedBuiltInLaunch,
3597
+ geminiAcp: isGeminiAcpCommand(spawnCommand, args),
3598
+ copilotAcp: isCopilotAcpCommand(spawnCommand, args),
3599
+ claudeAcp: isClaudeAcpCommand(spawnCommand, args),
3600
+ spawnOptions: buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials)
3601
+ };
3602
+ }
3603
+ logAgentLaunch(plan) {
3604
+ const launch = plan.resolvedBuiltInLaunch;
3605
+ if (launch?.source === "installed") {
3606
+ this.log(`spawning installed built-in agent ${launch.packageName}${launch.packageVersion ? `@${launch.packageVersion}` : ""} via ${plan.spawnCommand} ${plan.args.join(" ")}`);
3607
+ return;
3608
+ }
3609
+ if (launch?.source === "package-exec") {
3610
+ this.log(`spawning built-in agent ${launch.packageName}@${launch.packageRange} via current Node package exec bridge ${plan.spawnCommand} ${plan.args.join(" ")}`);
3611
+ return;
3612
+ }
3613
+ this.log(`spawning agent: ${plan.spawnCommand} ${plan.args.join(" ")}`);
3614
+ }
3615
+ async ensureLaunchSupport(plan) {
3616
+ if (plan.copilotAcp) await ensureCopilotAcpSupport(plan.spawnCommand);
3617
+ if (!plan.claudeAcp) return;
3618
+ const claudeExe = resolveClaudeCodeExecutable(process.platform, plan.spawnOptions.env);
3619
+ if (claudeExe) {
3620
+ plan.spawnOptions.env.CLAUDE_CODE_EXECUTABLE = claudeExe;
3621
+ this.log(`resolved system Claude Code executable: ${claudeExe}`);
3622
+ }
3623
+ }
3624
+ async spawnAgentProcess(plan) {
3625
+ const spawnedChild = spawn(plan.spawnCommand, plan.args, buildSpawnCommandOptions(plan.spawnCommand, plan.spawnOptions));
3626
+ try {
3627
+ await waitForSpawn$1(spawnedChild);
3628
+ } catch (error) {
3629
+ throw new AgentSpawnError(this.options.agentCommand, error);
3630
+ }
3631
+ return requireAgentStdio(spawnedChild);
3632
+ }
3633
+ createConnection(stream) {
3634
+ return new ClientSideConnection(() => ({
3191
3635
  sessionUpdate: async (params) => {
3192
3636
  await this.handleSessionUpdate(params);
3193
3637
  },
@@ -3215,49 +3659,51 @@ var AcpClient = class {
3215
3659
  releaseTerminal: async (params) => {
3216
3660
  return this.handleReleaseTerminal(params);
3217
3661
  }
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);
3662
+ }), stream);
3663
+ }
3664
+ async initializeAgentConnection(params) {
3223
3665
  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;
3666
+ const initResult = await Promise.race([this.initializeProtocolConnection(params.connection, params.launch.geminiAcp), params.startupFailure.promise]);
3667
+ params.startupFailure.dispose();
3668
+ this.connection = params.connection;
3669
+ this.agent = params.child;
3246
3670
  this.initResult = initResult;
3247
3671
  this.log(`initialized protocol version ${initResult.protocolVersion}`);
3248
3672
  } 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;
3673
+ await this.handleInitializeFailure(params, error);
3259
3674
  }
3260
3675
  }
3676
+ async initializeProtocolConnection(connection, geminiAcp) {
3677
+ const initializePromise = connection.initialize({
3678
+ protocolVersion: PROTOCOL_VERSION,
3679
+ clientCapabilities: {
3680
+ fs: {
3681
+ readTextFile: true,
3682
+ writeTextFile: true
3683
+ },
3684
+ terminal: this.options.terminal !== false
3685
+ },
3686
+ clientInfo: {
3687
+ name: "acpx",
3688
+ version: "0.1.0"
3689
+ }
3690
+ });
3691
+ const initialized = geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise;
3692
+ await this.authenticateIfRequired(connection, initialized.authMethods ?? []);
3693
+ return initialized;
3694
+ }
3695
+ async handleInitializeFailure(params, error) {
3696
+ params.startupFailure.dispose();
3697
+ const normalizedError = await this.normalizeInitializeError(error, params.child, params.startupStderr);
3698
+ try {
3699
+ params.child.kill();
3700
+ } catch {}
3701
+ if (params.launch.geminiAcp && error instanceof TimeoutError) throw new GeminiAcpStartupTimeoutError(await buildGeminiAcpStartupTimeoutMessage(params.launch.spawnCommand), {
3702
+ cause: error,
3703
+ retryable: true
3704
+ });
3705
+ throw normalizedError;
3706
+ }
3261
3707
  createTappedStream(base) {
3262
3708
  const onAcpMessage = () => this.eventHandlers.onAcpMessage;
3263
3709
  const onAcpOutputMessage = () => this.eventHandlers.onAcpOutputMessage;
@@ -3330,10 +3776,7 @@ var AcpClient = class {
3330
3776
  async loadSessionWithOptions(sessionId, cwd = this.options.cwd, options = {}) {
3331
3777
  const connection = this.getConnection();
3332
3778
  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);
3779
+ const previousSuppression = this.applySessionUpdateSuppression(Boolean(options.suppressReplayUpdates));
3337
3780
  let response;
3338
3781
  try {
3339
3782
  response = await this.runConnectionRequest(() => connection.loadSession({
@@ -3343,24 +3786,44 @@ var AcpClient = class {
3343
3786
  }));
3344
3787
  await this.waitForSessionUpdateDrain(options.replayIdleMs ?? REPLAY_IDLE_MS, options.replayDrainTimeoutMs ?? REPLAY_DRAIN_TIMEOUT_MS);
3345
3788
  } finally {
3346
- this.suppressSessionUpdates = previousSuppression;
3347
- this.suppressReplaySessionUpdateMessages = previousReplaySuppression;
3789
+ this.restoreSessionUpdateSuppression(previousSuppression);
3348
3790
  }
3349
3791
  this.loadedSessionId = sessionId;
3350
- return {
3351
- agentSessionId: extractRuntimeSessionId(response?._meta),
3352
- configOptions: response?.configOptions ?? void 0,
3353
- models: response?.models ?? void 0
3792
+ return toReconnectedSessionResult(response);
3793
+ }
3794
+ async resumeSession(sessionId, cwd = this.options.cwd) {
3795
+ const connection = this.getConnection();
3796
+ const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
3797
+ const response = await this.runConnectionRequest(() => connection.resumeSession({
3798
+ sessionId,
3799
+ cwd: sessionCwd,
3800
+ mcpServers: this.options.mcpServers ?? []
3801
+ }));
3802
+ this.loadedSessionId = sessionId;
3803
+ return toReconnectedSessionResult(response);
3804
+ }
3805
+ applySessionUpdateSuppression(enabled) {
3806
+ const previous = {
3807
+ suppressSessionUpdates: this.suppressSessionUpdates,
3808
+ suppressReplaySessionUpdateMessages: this.suppressReplaySessionUpdateMessages
3354
3809
  };
3810
+ this.suppressSessionUpdates = previous.suppressSessionUpdates || enabled;
3811
+ this.suppressReplaySessionUpdateMessages = previous.suppressReplaySessionUpdateMessages || enabled;
3812
+ return previous;
3813
+ }
3814
+ restoreSessionUpdateSuppression(previous) {
3815
+ this.suppressSessionUpdates = previous.suppressSessionUpdates;
3816
+ this.suppressReplaySessionUpdateMessages = previous.suppressReplaySessionUpdateMessages;
3355
3817
  }
3356
3818
  async prompt(sessionId, prompt) {
3357
3819
  const connection = this.getConnection();
3820
+ const normalizedPrompt = this.normalizePromptForAgent(prompt);
3358
3821
  const restoreConsoleError = this.options.suppressSdkConsoleErrors ? installSdkConsoleErrorSuppression() : void 0;
3359
3822
  let promptPromise;
3360
3823
  try {
3361
3824
  promptPromise = this.runConnectionRequest(() => connection.prompt({
3362
3825
  sessionId,
3363
- prompt: typeof prompt === "string" ? textPrompt(prompt) : prompt
3826
+ prompt: normalizedPrompt
3364
3827
  }));
3365
3828
  } catch (error) {
3366
3829
  restoreConsoleError?.();
@@ -3371,13 +3834,9 @@ var AcpClient = class {
3371
3834
  promise: promptPromise
3372
3835
  };
3373
3836
  try {
3374
- const response = await promptPromise;
3375
- const permissionFailure = this.consumePromptPermissionFailure(sessionId);
3376
- if (permissionFailure) throw permissionFailure;
3377
- return response;
3837
+ return this.returnPromptResponseOrPermissionFailure(sessionId, await promptPromise);
3378
3838
  } catch (error) {
3379
- const permissionFailure = this.consumePromptPermissionFailure(sessionId);
3380
- if (permissionFailure) throw permissionFailure;
3839
+ this.throwPromptPermissionFailureIfPresent(sessionId);
3381
3840
  throw error;
3382
3841
  } finally {
3383
3842
  restoreConsoleError?.();
@@ -3387,6 +3846,20 @@ var AcpClient = class {
3387
3846
  this.promptPermissionFailures.delete(sessionId);
3388
3847
  }
3389
3848
  }
3849
+ normalizePromptForAgent(prompt) {
3850
+ const normalizedPrompt = typeof prompt === "string" ? textPrompt(prompt) : prompt;
3851
+ const unsupportedPromptContent = getUnsupportedPromptContentMessage(normalizedPrompt, this.initResult?.agentCapabilities);
3852
+ if (unsupportedPromptContent) throw new UnsupportedPromptContentError(unsupportedPromptContent);
3853
+ return normalizedPrompt;
3854
+ }
3855
+ returnPromptResponseOrPermissionFailure(sessionId, response) {
3856
+ this.throwPromptPermissionFailureIfPresent(sessionId);
3857
+ return response;
3858
+ }
3859
+ throwPromptPermissionFailureIfPresent(sessionId) {
3860
+ const permissionFailure = this.consumePromptPermissionFailure(sessionId);
3861
+ if (permissionFailure) throw permissionFailure;
3862
+ }
3390
3863
  async setSessionMode(sessionId, modeId) {
3391
3864
  const connection = this.getConnection();
3392
3865
  try {
@@ -3437,6 +3910,10 @@ var AcpClient = class {
3437
3910
  await this.runConnectionRequest(() => connection.closeSession({ sessionId }));
3438
3911
  if (this.loadedSessionId === sessionId) this.loadedSessionId = void 0;
3439
3912
  }
3913
+ async listSessions(params = {}) {
3914
+ const connection = this.getConnection();
3915
+ return await this.runConnectionRequest(() => connection.listSessions(params));
3916
+ }
3440
3917
  async requestCancelActivePrompt() {
3441
3918
  const active = this.activePrompt;
3442
3919
  if (!active) return false;
@@ -3486,25 +3963,28 @@ var AcpClient = class {
3486
3963
  }
3487
3964
  async terminateAgentProcess(child) {
3488
3965
  const stdinCloseGraceMs = resolveAgentCloseAfterStdinEndMs(this.options.agentCommand);
3489
- if (!child.stdin.destroyed) try {
3490
- child.stdin.end();
3491
- } catch {}
3966
+ this.endAgentStdin(child);
3492
3967
  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)) {
3968
+ exited = await this.killAgentIfRunning(child, exited, "SIGTERM", AGENT_CLOSE_TERM_GRACE_MS);
3969
+ if (!exited) {
3500
3970
  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);
3971
+ exited = await this.killAgentIfRunning(child, exited, "SIGKILL", AGENT_CLOSE_KILL_GRACE_MS);
3505
3972
  }
3506
3973
  this.detachAgentHandles(child, !exited);
3507
3974
  }
3975
+ endAgentStdin(child) {
3976
+ if (child.stdin.destroyed) return;
3977
+ try {
3978
+ child.stdin.end();
3979
+ } catch {}
3980
+ }
3981
+ async killAgentIfRunning(child, alreadyExited, signal, waitMs) {
3982
+ if (alreadyExited || !isChildProcessRunning(child)) return alreadyExited;
3983
+ try {
3984
+ child.kill(signal);
3985
+ } catch {}
3986
+ return await waitForChildExit(child, waitMs);
3987
+ }
3508
3988
  detachAgentHandles(agent, unref) {
3509
3989
  const stdin = agent.stdin;
3510
3990
  const stdout = agent.stdout;
@@ -3625,49 +4105,71 @@ var AcpClient = class {
3625
4105
  this.log(`authenticated with method ${selected.methodId} (${selected.source})`);
3626
4106
  }
3627
4107
  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
- }
4108
+ if (this.cancellingSessionIds.has(params.sessionId)) return cancelledPermissionResponse();
4109
+ const hostResponse = await this.tryHandlePermissionRequestWithHost(params);
4110
+ if (hostResponse) return hostResponse;
4111
+ const { response, recorded } = await this.resolvePermissionRequestFromMode(params);
4112
+ if (!recorded) {
4113
+ const decision = classifyPermissionDecision(params, response);
4114
+ this.recordPermissionDecision(decision);
3653
4115
  }
3654
- let response;
4116
+ return response;
4117
+ }
4118
+ async tryHandlePermissionRequestWithHost(params) {
4119
+ if (!this.options.onPermissionRequest) return;
4120
+ const signal = this.cancellationSignalForSession(params.sessionId);
3655
4121
  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);
4122
+ const decision = await this.options.onPermissionRequest({
4123
+ sessionId: params.sessionId,
4124
+ raw: params,
4125
+ inferredKind: inferToolKind(params)
4126
+ }, { signal });
4127
+ return this.hostPermissionDecisionResponse(params, signal, decision);
3659
4128
  } 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;
4129
+ return this.hostPermissionErrorResponse(params, signal, error);
4130
+ }
4131
+ }
4132
+ hostPermissionDecisionResponse(params, signal, decision) {
4133
+ if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
4134
+ this.recordPermissionDecision("cancelled");
4135
+ return cancelledPermissionResponse();
3666
4136
  }
3667
- const decision = classifyPermissionDecision(params, response);
3668
- this.recordPermissionDecision(decision);
4137
+ if (!decision) return;
4138
+ const response = decisionToResponse(params, decision);
4139
+ this.recordPermissionDecision(classifyPermissionDecision(params, response));
3669
4140
  return response;
3670
4141
  }
4142
+ hostPermissionErrorResponse(params, signal, error) {
4143
+ if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
4144
+ this.recordPermissionDecision("cancelled");
4145
+ return cancelledPermissionResponse();
4146
+ }
4147
+ this.log(`onPermissionRequest threw, falling through to mode-based resolver: ${error instanceof Error ? error.message : String(error)}`);
4148
+ }
4149
+ async resolvePermissionRequestFromMode(params) {
4150
+ try {
4151
+ const result = await resolvePermissionRequestWithDetails(params, this.options.permissionMode, this.options.nonInteractivePermissions ?? "deny", this.options.permissionPolicy);
4152
+ this.emitPermissionEscalation(result.escalation);
4153
+ return {
4154
+ response: result.response,
4155
+ recorded: false
4156
+ };
4157
+ } catch (error) {
4158
+ return this.handleModePermissionError(params.sessionId, error);
4159
+ }
4160
+ }
4161
+ emitPermissionEscalation(escalation) {
4162
+ if (escalation) this.eventHandlers.onPermissionEscalation?.(escalation);
4163
+ }
4164
+ handleModePermissionError(sessionId, error) {
4165
+ if (!(error instanceof PermissionPromptUnavailableError)) throw error;
4166
+ this.notePromptPermissionFailure(sessionId, error);
4167
+ this.recordPermissionDecision("cancelled");
4168
+ return {
4169
+ response: cancelledPermissionResponse(),
4170
+ recorded: true
4171
+ };
4172
+ }
3671
4173
  attachAgentLifecycleObservers(child) {
3672
4174
  child.once("exit", (exitCode, signal) => {
3673
4175
  this.recordAgentExit("process_exit", exitCode, signal);
@@ -3837,25 +4339,54 @@ var AcpClient = class {
3837
4339
  }
3838
4340
  };
3839
4341
  //#endregion
4342
+ //#region src/runtime/engine/lifecycle.ts
4343
+ function applyLifecycleSnapshotToRecord(record, snapshot) {
4344
+ if (!snapshot) return;
4345
+ record.pid = snapshot.running ? snapshot.pid : void 0;
4346
+ record.agentStartedAt = snapshot.startedAt;
4347
+ if (snapshot.lastExit) {
4348
+ record.lastAgentExitCode = snapshot.lastExit.exitCode;
4349
+ record.lastAgentExitSignal = snapshot.lastExit.signal;
4350
+ record.lastAgentExitAt = snapshot.lastExit.exitedAt;
4351
+ record.lastAgentDisconnectReason = snapshot.lastExit.reason;
4352
+ return;
4353
+ }
4354
+ record.lastAgentExitCode = void 0;
4355
+ record.lastAgentExitSignal = void 0;
4356
+ record.lastAgentExitAt = void 0;
4357
+ record.lastAgentDisconnectReason = void 0;
4358
+ }
4359
+ function reconcileAgentSessionId(record, agentSessionId) {
4360
+ const normalized = normalizeRuntimeSessionId(agentSessionId);
4361
+ if (!normalized) return;
4362
+ record.agentSessionId = normalized;
4363
+ }
4364
+ function sessionHasAgentMessages(recordOrConversation) {
4365
+ return recordOrConversation.messages.some((message) => typeof message === "object" && message !== null && "Agent" in message);
4366
+ }
4367
+ function applyConversation(record, conversation) {
4368
+ record.title = conversation.title;
4369
+ record.updated_at = conversation.updated_at;
4370
+ record.messages = conversation.messages;
4371
+ record.cumulative_token_usage = conversation.cumulative_token_usage;
4372
+ record.request_token_usage = conversation.request_token_usage;
4373
+ }
4374
+ //#endregion
3840
4375
  //#region src/runtime/engine/session-options.ts
3841
4376
  function mergeSessionOptions(preferred, fallback) {
3842
4377
  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;
4378
+ assignDefinedOption(merged, "model", preferred?.model);
4379
+ assignDefinedOption(merged, "allowedTools", preferred?.allowedTools);
4380
+ assignDefinedOption(merged, "maxTurns", preferred?.maxTurns);
4381
+ assignDefinedOption(merged, "systemPrompt", preferred?.systemPrompt);
3847
4382
  return Object.keys(merged).length > 0 ? merged : void 0;
3848
4383
  }
4384
+ function assignDefinedOption(target, key, value) {
4385
+ if (value !== void 0) target[key] = value;
4386
+ }
3849
4387
  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) {
4388
+ const next = options === void 0 ? void 0 : persistedSessionOptions(options);
4389
+ if (next !== void 0) {
3859
4390
  record.acpx = {
3860
4391
  ...record.acpx,
3861
4392
  session_options: next
@@ -3869,14 +4400,49 @@ function sessionOptionsFromRecord(record) {
3869
4400
  const stored = record.acpx?.session_options;
3870
4401
  if (!stored) return;
3871
4402
  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 };
4403
+ assignStoredOption(sessionOptions, "model", nonEmptyString(stored.model));
4404
+ assignStoredOption(sessionOptions, "allowedTools", storedAllowedTools(stored.allowed_tools));
4405
+ assignStoredOption(sessionOptions, "maxTurns", storedMaxTurns(stored.max_turns));
4406
+ assignStoredOption(sessionOptions, "systemPrompt", storedSystemPromptOption(stored.system_prompt));
3878
4407
  return Object.keys(sessionOptions).length > 0 ? sessionOptions : void 0;
3879
4408
  }
4409
+ function persistedSessionOptions(options) {
4410
+ const next = {
4411
+ model: nonEmptyString(options.model),
4412
+ allowed_tools: Array.isArray(options.allowedTools) ? [...options.allowedTools] : void 0,
4413
+ max_turns: typeof options.maxTurns === "number" ? options.maxTurns : void 0,
4414
+ system_prompt: normalizeSystemPromptOption(options.systemPrompt)
4415
+ };
4416
+ return hasPersistedSessionOptions(next) ? next : void 0;
4417
+ }
4418
+ function hasPersistedSessionOptions(options) {
4419
+ return options.model !== void 0 || options.allowed_tools !== void 0 || options.max_turns !== void 0 || options.system_prompt !== void 0;
4420
+ }
4421
+ function normalizeSystemPromptOption(value) {
4422
+ const prompt = nonEmptyString(value);
4423
+ if (prompt !== void 0) return prompt;
4424
+ const append = appendedSystemPrompt(value);
4425
+ return append === void 0 ? void 0 : { append };
4426
+ }
4427
+ function appendedSystemPrompt(value) {
4428
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return;
4429
+ return nonEmptyString(value.append);
4430
+ }
4431
+ function assignStoredOption(target, key, value) {
4432
+ assignDefinedOption(target, key, value);
4433
+ }
4434
+ function storedAllowedTools(value) {
4435
+ return Array.isArray(value) && value.every((item) => typeof item === "string") ? [...value] : void 0;
4436
+ }
4437
+ function storedMaxTurns(value) {
4438
+ return typeof value === "number" ? value : void 0;
4439
+ }
4440
+ function storedSystemPromptOption(value) {
4441
+ return normalizeSystemPromptOption(value);
4442
+ }
4443
+ function nonEmptyString(value) {
4444
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
4445
+ }
3880
4446
  //#endregion
3881
4447
  //#region src/session/conversation-model.ts
3882
4448
  const MAX_RUNTIME_MESSAGES = 200;
@@ -3903,13 +4469,17 @@ function normalizeAgentName(value) {
3903
4469
  return trimmed.length > 0 ? trimmed : void 0;
3904
4470
  }
3905
4471
  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;
4472
+ switch (content.type) {
4473
+ case "text": return content.text;
4474
+ case "resource_link": return content.title ?? content.name ?? content.uri;
4475
+ case "resource": return extractResourceText(content);
4476
+ case "audio": return `[audio] ${content.mimeType}`;
4477
+ default: return;
3911
4478
  }
3912
4479
  }
4480
+ function extractResourceText(content) {
4481
+ return "text" in content.resource && typeof content.resource.text === "string" ? content.resource.text : content.resource.uri;
4482
+ }
3913
4483
  function contentToUserContent(content) {
3914
4484
  if (content.type === "text") return { Text: content.text };
3915
4485
  if (content.type === "resource_link") {
@@ -3919,17 +4489,22 @@ function contentToUserContent(content) {
3919
4489
  content: value
3920
4490
  } };
3921
4491
  }
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
- }
4492
+ if (content.type === "resource") return resourceToUserContent(content);
3929
4493
  if (content.type === "image") return { Image: {
3930
4494
  source: content.data,
3931
4495
  size: null
3932
4496
  } };
4497
+ if (content.type === "audio") return { Audio: {
4498
+ source: content.data,
4499
+ mime_type: content.mimeType
4500
+ } };
4501
+ }
4502
+ function resourceToUserContent(content) {
4503
+ if ("text" in content.resource && typeof content.resource.text === "string") return { Text: content.resource.text };
4504
+ return { Mention: {
4505
+ uri: content.resource.uri,
4506
+ content: content.resource.uri
4507
+ } };
3933
4508
  }
3934
4509
  function nextUserMessageId() {
3935
4510
  return randomUUID();
@@ -4031,38 +4606,67 @@ function ensureToolUseContent(agent, toolCallId) {
4031
4606
  }
4032
4607
  function upsertToolResult(agent, toolCallId, patch) {
4033
4608
  const existing = agent.tool_results[toolCallId];
4609
+ const fallback = existingToolResultValues(existing);
4034
4610
  const next = {
4035
4611
  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
4612
+ tool_name: patch.tool_name ?? fallback.tool_name,
4613
+ is_error: patch.is_error ?? fallback.is_error,
4614
+ content: patch.content ?? fallback.content,
4615
+ output: patch.output ?? fallback.output
4040
4616
  };
4041
4617
  agent.tool_results[toolCallId] = next;
4042
4618
  }
4619
+ function existingToolResultValues(existing) {
4620
+ if (existing) return existing;
4621
+ return {
4622
+ tool_use_id: "",
4623
+ tool_name: "tool_call",
4624
+ is_error: false,
4625
+ content: { Text: "" },
4626
+ output: void 0
4627
+ };
4628
+ }
4043
4629
  function applyToolCallUpdate(agent, update) {
4044
4630
  const tool = ensureToolUseContent(agent, update.toolCallId);
4631
+ applyToolIdentityUpdate(tool, update);
4632
+ applyToolInputUpdate(tool, update);
4633
+ applyToolStatusUpdate(tool, update);
4634
+ applyToolResultUpdate(agent, tool, update);
4635
+ }
4636
+ function applyToolIdentityUpdate(tool, update) {
4045
4637
  if (hasOwn(update, "title")) tool.name = normalizeAgentName(update.title) ?? tool.name ?? "tool_call";
4046
4638
  if (hasOwn(update, "kind")) {
4047
4639
  const kindName = normalizeAgentName(update.kind);
4048
4640
  if (!tool.name || tool.name === "tool_call") tool.name = kindName ?? tool.name;
4049
4641
  }
4050
- if (hasOwn(update, "rawInput")) {
4051
- const rawInput = deepClone(update.rawInput);
4052
- tool.input = rawInput ?? {};
4053
- tool.raw_input = toRawInput(rawInput);
4054
- }
4642
+ }
4643
+ function applyToolInputUpdate(tool, update) {
4644
+ if (!hasOwn(update, "rawInput")) return;
4645
+ const rawInput = deepClone(update.rawInput);
4646
+ tool.input = rawInput ?? {};
4647
+ tool.raw_input = toRawInput(rawInput);
4648
+ }
4649
+ function applyToolStatusUpdate(tool, update) {
4055
4650
  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
- }
4651
+ }
4652
+ function applyToolResultUpdate(agent, tool, update) {
4653
+ if (!hasToolResultPatch(update)) return;
4654
+ const status = update.status;
4655
+ const output = hasOwn(update, "rawOutput") ? deepClone(update.rawOutput) : void 0;
4656
+ upsertToolResult(agent, update.toolCallId, {
4657
+ tool_name: tool.name,
4658
+ is_error: statusIndicatesError(status),
4659
+ content: output === void 0 ? void 0 : toToolResultContent(output),
4660
+ output
4661
+ });
4662
+ }
4663
+ function hasToolResultPatch(update) {
4664
+ return [
4665
+ "rawOutput",
4666
+ "status",
4667
+ "title",
4668
+ "kind"
4669
+ ].some((key) => hasOwn(update, key));
4066
4670
  }
4067
4671
  function asRecord(value) {
4068
4672
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
@@ -4093,9 +4697,12 @@ function usageToTokenUsage(update) {
4093
4697
  "cachedReadTokens"
4094
4698
  ])
4095
4699
  };
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;
4700
+ if (!hasTokenUsageValue(normalized)) return;
4097
4701
  return normalized;
4098
4702
  }
4703
+ function hasTokenUsageValue(usage) {
4704
+ return Object.values(usage).some((value) => value !== void 0);
4705
+ }
4099
4706
  function ensureAcpxState$1(state) {
4100
4707
  return state ?? {};
4101
4708
  }
@@ -4134,14 +4741,21 @@ function cloneSessionAcpxState(state) {
4134
4741
  available_models: state.available_models ? [...state.available_models] : void 0,
4135
4742
  available_commands: state.available_commands ? [...state.available_commands] : void 0,
4136
4743
  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
4744
+ session_options: cloneSessionOptions(state.session_options)
4745
+ };
4746
+ }
4747
+ function cloneSessionOptions(options) {
4748
+ if (!options) return;
4749
+ return {
4750
+ model: options.model,
4751
+ allowed_tools: options.allowed_tools ? [...options.allowed_tools] : void 0,
4752
+ max_turns: options.max_turns,
4753
+ ...options.system_prompt !== void 0 ? { system_prompt: cloneSystemPromptOption(options.system_prompt) } : {}
4143
4754
  };
4144
4755
  }
4756
+ function cloneSystemPromptOption(option) {
4757
+ return typeof option === "string" ? option : { append: option.append };
4758
+ }
4145
4759
  function recordPromptSubmission(conversation, prompt, timestamp = isoNow()) {
4146
4760
  const userContent = (typeof prompt === "string" ? textPrompt(prompt) : prompt).map((content) => contentToUserContent(content)).filter((content) => content !== void 0);
4147
4761
  if (userContent.length === 0) return;
@@ -4174,57 +4788,70 @@ function hasAgentReplyAfterPrompt(conversation, promptMessageId) {
4174
4788
  function recordSessionUpdate(conversation, state, notification, timestamp = isoNow()) {
4175
4789
  const acpx = ensureAcpxState$1(state);
4176
4790
  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
- }
4791
+ applySessionUpdate(conversation, acpx, update);
4224
4792
  updateConversationTimestamp(conversation, timestamp);
4225
4793
  trimConversationForRuntime(conversation);
4226
4794
  return acpx;
4227
4795
  }
4796
+ function applySessionUpdate(conversation, acpx, update) {
4797
+ const handler = SESSION_UPDATE_HANDLERS[update.sessionUpdate];
4798
+ handler?.(conversation, acpx, update);
4799
+ }
4800
+ const SESSION_UPDATE_HANDLERS = {
4801
+ user_message_chunk: (conversation, _acpx, update) => {
4802
+ if (update.sessionUpdate === "user_message_chunk") appendUserMessageChunk(conversation, update.content);
4803
+ },
4804
+ agent_message_chunk: (conversation, _acpx, update) => {
4805
+ if (update.sessionUpdate === "agent_message_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentText);
4806
+ },
4807
+ agent_thought_chunk: (conversation, _acpx, update) => {
4808
+ if (update.sessionUpdate === "agent_thought_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentThinking);
4809
+ },
4810
+ tool_call: (conversation, _acpx, update) => {
4811
+ if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") applyToolCallUpdate(ensureAgentMessage(conversation), update);
4812
+ },
4813
+ tool_call_update: (conversation, _acpx, update) => {
4814
+ if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") applyToolCallUpdate(ensureAgentMessage(conversation), update);
4815
+ },
4816
+ usage_update: (conversation, _acpx, update) => {
4817
+ if (update.sessionUpdate === "usage_update") applyUsageUpdate(conversation, update);
4818
+ },
4819
+ session_info_update: (conversation, _acpx, update) => {
4820
+ if (update.sessionUpdate === "session_info_update") applySessionInfoUpdate(conversation, update);
4821
+ },
4822
+ available_commands_update: (_conversation, acpx, update) => {
4823
+ if (update.sessionUpdate === "available_commands_update") acpx.available_commands = update.availableCommands.map((entry) => entry.name).filter((entry) => typeof entry === "string" && entry.trim().length > 0);
4824
+ },
4825
+ current_mode_update: (_conversation, acpx, update) => {
4826
+ if (update.sessionUpdate === "current_mode_update") acpx.current_mode_id = update.currentModeId;
4827
+ },
4828
+ config_option_update: (_conversation, acpx, update) => {
4829
+ if (update.sessionUpdate === "config_option_update") acpx.config_options = deepClone(update.configOptions);
4830
+ }
4831
+ };
4832
+ function appendUserMessageChunk(conversation, content) {
4833
+ const userContent = contentToUserContent(content);
4834
+ if (!userContent) return;
4835
+ conversation.messages.push({ User: {
4836
+ id: nextUserMessageId(),
4837
+ content: [userContent]
4838
+ } });
4839
+ }
4840
+ function appendAgentMessageChunk(conversation, content, append) {
4841
+ const text = extractText(content);
4842
+ if (text) append(ensureAgentMessage(conversation), text);
4843
+ }
4844
+ function applyUsageUpdate(conversation, update) {
4845
+ const usage = usageToTokenUsage(update);
4846
+ if (!usage) return;
4847
+ conversation.cumulative_token_usage = usage;
4848
+ const userId = lastUserMessageId(conversation);
4849
+ if (userId) conversation.request_token_usage[userId] = usage;
4850
+ }
4851
+ function applySessionInfoUpdate(conversation, update) {
4852
+ if (hasOwn(update, "title")) conversation.title = update.title ?? null;
4853
+ if (hasOwn(update, "updatedAt")) conversation.updated_at = update.updatedAt ?? conversation.updated_at;
4854
+ }
4228
4855
  function recordClientOperation(conversation, state, operation, timestamp = isoNow()) {
4229
4856
  const acpx = ensureAcpxState$1(state);
4230
4857
  updateConversationTimestamp(conversation, timestamp);
@@ -4233,25 +4860,36 @@ function recordClientOperation(conversation, state, operation, timestamp = isoNo
4233
4860
  }
4234
4861
  function trimConversationForRuntime(conversation) {
4235
4862
  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
- }
4863
+ for (const message of conversation.messages) trimRuntimeMessage(message);
4252
4864
  const requestUsageEntries = Object.entries(conversation.request_token_usage);
4253
4865
  if (requestUsageEntries.length > MAX_RUNTIME_REQUEST_TOKEN_USAGE) conversation.request_token_usage = Object.fromEntries(requestUsageEntries.slice(-MAX_RUNTIME_REQUEST_TOKEN_USAGE));
4254
4866
  }
4867
+ function trimRuntimeMessage(message) {
4868
+ if (isUserMessage(message)) {
4869
+ trimRuntimeUserMessage(message.User);
4870
+ return;
4871
+ }
4872
+ if (isAgentMessage(message)) trimRuntimeAgentMessage(message.Agent);
4873
+ }
4874
+ function trimRuntimeUserMessage(message) {
4875
+ message.content = message.content.map((content) => {
4876
+ if ("Text" in content) return { Text: trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS) };
4877
+ return content;
4878
+ });
4879
+ }
4880
+ function trimRuntimeAgentMessage(message) {
4881
+ for (const content of message.content) trimRuntimeAgentContent(content);
4882
+ for (const result of Object.values(message.tool_results)) trimRuntimeToolResult(result);
4883
+ }
4884
+ function trimRuntimeAgentContent(content) {
4885
+ if ("Text" in content) content.Text = trimRuntimeText(content.Text, MAX_RUNTIME_AGENT_TEXT_CHARS);
4886
+ else if ("Thinking" in content) content.Thinking.text = trimRuntimeText(content.Thinking.text, MAX_RUNTIME_THINKING_CHARS);
4887
+ else if ("ToolUse" in content) content.ToolUse.raw_input = trimRuntimeText(content.ToolUse.raw_input, MAX_RUNTIME_TOOL_IO_CHARS);
4888
+ }
4889
+ function trimRuntimeToolResult(result) {
4890
+ if ("Text" in result.content) result.content.Text = trimRuntimeText(result.content.Text, MAX_RUNTIME_TOOL_IO_CHARS);
4891
+ if (typeof result.output === "string") result.output = trimRuntimeText(result.output, MAX_RUNTIME_TOOL_IO_CHARS);
4892
+ }
4255
4893
  //#endregion
4256
4894
  //#region src/session/config-options.ts
4257
4895
  function applyConfigOptionsToRecord(record, result) {
@@ -4373,39 +5011,6 @@ async function applyRequestedModelIfAdvertised(params) {
4373
5011
  return true;
4374
5012
  }
4375
5013
  //#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
5014
  //#region src/runtime/engine/reconnect.ts
4410
5015
  function isProcessAlive(pid) {
4411
5016
  if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
@@ -4418,15 +5023,19 @@ function isProcessAlive(pid) {
4418
5023
  }
4419
5024
  const SESSION_LOAD_UNSUPPORTED_CODES = new Set([-32601, -32602]);
4420
5025
  function shouldFallbackToNewSession(error, record) {
4421
- if (error instanceof TimeoutError || error instanceof InterruptedError) return false;
4422
- if (isAcpResourceNotFoundError(error)) return true;
5026
+ if (isHardReconnectFailure(error)) return false;
4423
5027
  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;
5028
+ if (isAcpResourceNotFoundError(error) || isUnsupportedSessionLoadAcpError(acp)) return true;
5029
+ return !sessionHasAgentMessages(record) && isFallbackSafeEmptySessionError(error, acp);
5030
+ }
5031
+ function isHardReconnectFailure(error) {
5032
+ return error instanceof TimeoutError || error instanceof InterruptedError;
5033
+ }
5034
+ function isUnsupportedSessionLoadAcpError(acp) {
5035
+ return !!acp && SESSION_LOAD_UNSUPPORTED_CODES.has(acp.code);
5036
+ }
5037
+ function isFallbackSafeEmptySessionError(error, acp) {
5038
+ return isAcpQueryClosedBeforeResponseError(error) || acp?.code === -32603;
4430
5039
  }
4431
5040
  function requiresSameSession(resumePolicy) {
4432
5041
  return resumePolicy === "same-session-only";
@@ -4483,18 +5092,14 @@ function restoreOriginalSessionState(params) {
4483
5092
  async function connectAndLoadSession(options) {
4484
5093
  const record = options.record;
4485
5094
  const client = options.client;
4486
- const sameSessionOnly = requiresSameSession(options.resumePolicy);
5095
+ const sameSessionOnly = requiresSameSession(options.resumePolicy) || Boolean(record.importedFrom);
4487
5096
  const originalSessionId = record.acpSessionId;
4488
5097
  const originalAgentSessionId = record.agentSessionId;
4489
5098
  const desiredModeId = getDesiredModeId(record.acpx);
4490
5099
  const desiredModelId = getDesiredModelId(record.acpx);
4491
5100
  const desiredConfigOptions = getDesiredConfigOptions(record.acpx);
4492
5101
  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
- }
5102
+ logReconnectAttempt(record, storedProcessAlive, Boolean(record.pid) && !storedProcessAlive, options.verbose);
4498
5103
  const reusingLoadedSession = client.hasReusableSession(record.acpSessionId);
4499
5104
  if (reusingLoadedSession) incrementPerfCounter("runtime.connect_and_load.reused_session");
4500
5105
  else await withTimeout(client.start(), options.timeoutMs);
@@ -4509,82 +5114,35 @@ async function connectAndLoadSession(options) {
4509
5114
  let createdFreshSession = false;
4510
5115
  let pendingAgentSessionId = record.agentSessionId;
4511
5116
  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);
5117
+ const loadState = await loadOrCreateRuntimeSession({
5118
+ client,
5119
+ record,
5120
+ reusingLoadedSession,
5121
+ sameSessionOnly,
5122
+ timeoutMs: options.timeoutMs
5123
+ });
5124
+ resumed = loadState.resumed;
5125
+ loadError = loadState.loadError;
5126
+ sessionId = loadState.sessionId;
5127
+ createdFreshSession = loadState.createdFreshSession;
5128
+ pendingAgentSessionId = loadState.pendingAgentSessionId;
5129
+ sessionModels = loadState.sessionModels;
5130
+ await replayFreshSessionPreferences({
5131
+ client,
5132
+ record,
5133
+ createdFreshSession,
5134
+ sessionId,
5135
+ pendingAgentSessionId,
5136
+ originalSessionId,
5137
+ originalAgentSessionId,
5138
+ desiredModeId,
5139
+ desiredModelId,
5140
+ desiredConfigOptions,
5141
+ sessionModels,
5142
+ timeoutMs: options.timeoutMs,
5143
+ verbose: options.verbose
5144
+ });
5145
+ applyReconnectedModelState(record, sessionModels, createdFreshSession, desiredModelId);
4588
5146
  options.onSessionIdResolved?.(sessionId);
4589
5147
  return {
4590
5148
  sessionId,
@@ -4593,6 +5151,131 @@ async function connectAndLoadSession(options) {
4593
5151
  loadError
4594
5152
  };
4595
5153
  }
5154
+ function applyReconnectedModelState(record, sessionModels, createdFreshSession, desiredModelId) {
5155
+ syncAdvertisedModelState(record, sessionModels);
5156
+ if (createdFreshSession && desiredModelId && sessionModels) setCurrentModelId(record, desiredModelId);
5157
+ }
5158
+ function logReconnectAttempt(record, storedProcessAlive, shouldReconnect, verbose) {
5159
+ if (!verbose) return;
5160
+ if (storedProcessAlive) {
5161
+ process.stderr.write(`[acpx] saved session pid ${record.pid} is running; reconnecting to saved ACP session\n`);
5162
+ return;
5163
+ }
5164
+ if (shouldReconnect) process.stderr.write(`[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session reconnect\n`);
5165
+ }
5166
+ async function replayFreshSessionPreferences(params) {
5167
+ if (!params.createdFreshSession) return;
5168
+ try {
5169
+ await replayDesiredMode({
5170
+ client: params.client,
5171
+ sessionId: params.sessionId,
5172
+ desiredModeId: params.desiredModeId,
5173
+ previousSessionId: params.originalSessionId,
5174
+ timeoutMs: params.timeoutMs,
5175
+ verbose: params.verbose
5176
+ });
5177
+ await replayDesiredModel({
5178
+ client: params.client,
5179
+ sessionId: params.sessionId,
5180
+ desiredModelId: params.desiredModelId,
5181
+ previousSessionId: params.originalSessionId,
5182
+ record: params.record,
5183
+ models: params.sessionModels,
5184
+ timeoutMs: params.timeoutMs,
5185
+ verbose: params.verbose
5186
+ });
5187
+ await replayDesiredConfigOptions({
5188
+ client: params.client,
5189
+ sessionId: params.sessionId,
5190
+ desiredConfigOptions: params.desiredConfigOptions,
5191
+ previousSessionId: params.originalSessionId,
5192
+ timeoutMs: params.timeoutMs,
5193
+ verbose: params.verbose
5194
+ });
5195
+ } catch (error) {
5196
+ restoreOriginalSessionState({
5197
+ record: params.record,
5198
+ sessionId: params.originalSessionId,
5199
+ agentSessionId: params.originalAgentSessionId
5200
+ });
5201
+ if (params.verbose) process.stderr.write(`[acpx] ${formatErrorMessage(error)}\n`);
5202
+ throw error;
5203
+ }
5204
+ params.record.acpSessionId = params.sessionId;
5205
+ reconcileAgentSessionId(params.record, params.pendingAgentSessionId);
5206
+ }
5207
+ async function loadOrCreateRuntimeSession(params) {
5208
+ if (params.reusingLoadedSession) return {
5209
+ sessionId: params.record.acpSessionId,
5210
+ pendingAgentSessionId: params.record.agentSessionId,
5211
+ sessionModels: void 0,
5212
+ resumed: true,
5213
+ createdFreshSession: false
5214
+ };
5215
+ if (params.client.supportsResumeSession()) return await resumeRuntimeSession(params);
5216
+ if (params.client.supportsLoadSession()) return await loadRuntimeSession(params);
5217
+ if (params.sameSessionOnly) throw makeSessionResumeRequiredError({
5218
+ record: params.record,
5219
+ reason: "agent does not support session/resume or session/load"
5220
+ });
5221
+ return await createFreshRuntimeSession(params.client, params.record, params.timeoutMs);
5222
+ }
5223
+ async function resumeRuntimeSession(params) {
5224
+ try {
5225
+ const resumeResult = await withTimeout(params.client.resumeSession(params.record.acpSessionId, params.record.cwd), params.timeoutMs);
5226
+ reconcileAgentSessionId(params.record, resumeResult.agentSessionId);
5227
+ applyConfigOptionsToRecord(params.record, resumeResult);
5228
+ return {
5229
+ sessionId: params.record.acpSessionId,
5230
+ pendingAgentSessionId: params.record.agentSessionId,
5231
+ sessionModels: resumeResult.models,
5232
+ resumed: true,
5233
+ createdFreshSession: false
5234
+ };
5235
+ } catch (error) {
5236
+ return await recoverRuntimeSessionLoadFailure(params, error);
5237
+ }
5238
+ }
5239
+ async function loadRuntimeSession(params) {
5240
+ try {
5241
+ const loadResult = await withTimeout(params.client.loadSessionWithOptions(params.record.acpSessionId, params.record.cwd, { suppressReplayUpdates: true }), params.timeoutMs);
5242
+ reconcileAgentSessionId(params.record, loadResult.agentSessionId);
5243
+ applyConfigOptionsToRecord(params.record, loadResult);
5244
+ return {
5245
+ sessionId: params.record.acpSessionId,
5246
+ pendingAgentSessionId: params.record.agentSessionId,
5247
+ sessionModels: loadResult.models,
5248
+ resumed: true,
5249
+ createdFreshSession: false
5250
+ };
5251
+ } catch (error) {
5252
+ return await recoverRuntimeSessionLoadFailure(params, error);
5253
+ }
5254
+ }
5255
+ async function recoverRuntimeSessionLoadFailure(params, error) {
5256
+ const loadError = formatErrorMessage(error);
5257
+ if (params.sameSessionOnly) throw makeSessionResumeRequiredError({
5258
+ record: params.record,
5259
+ reason: loadError,
5260
+ cause: error
5261
+ });
5262
+ if (!shouldFallbackToNewSession(error, params.record)) throw error;
5263
+ return {
5264
+ ...await createFreshRuntimeSession(params.client, params.record, params.timeoutMs),
5265
+ loadError
5266
+ };
5267
+ }
5268
+ async function createFreshRuntimeSession(client, record, timeoutMs) {
5269
+ const createdSession = await withTimeout(client.createSession(record.cwd), timeoutMs);
5270
+ applyConfigOptionsToRecord(record, createdSession);
5271
+ return {
5272
+ sessionId: createdSession.sessionId,
5273
+ pendingAgentSessionId: createdSession.agentSessionId,
5274
+ sessionModels: createdSession.models,
5275
+ resumed: false,
5276
+ createdFreshSession: true
5277
+ };
5278
+ }
4596
5279
  //#endregion
4597
5280
  //#region src/runtime/engine/connected-session.ts
4598
5281
  function createActiveSessionController(params) {
@@ -4785,6 +5468,6 @@ var LiveSessionCheckpoint = class {
4785
5468
  }
4786
5469
  };
4787
5470
  //#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 };
5471
+ export { defaultSessionEventLog as $, findGitRepositoryRoot as A, EXIT_CODES as At, assertPersistedKeyPolicy as B, QueueConnectionError as Bt, applyConversation as C, formatErrorMessage as Ct, permissionModeSatisfies as D, isAcpResourceNotFoundError as Dt, AcpClient as E, extractAcpError as Et, listSessionsForAgent as F, PERMISSION_MODES as Ft, recordPerfDuration as G, getPerfMetricsSnapshot as H, normalizeName as I, PERMISSION_POLICY_ACTIONS as It, startPerfTimer as J, resetPerfMetrics as K, pruneSessions as L, SESSION_RECORD_SCHEMA as Lt, findSessionByDirectoryWalk as M, OUTPUT_ERROR_CODES as Mt, isoNow$2 as N, OUTPUT_ERROR_ORIGINS as Nt, DEFAULT_HISTORY_LIMIT as O, toAcpErrorPayload as Ot, listSessions as P, OUTPUT_FORMATS as Pt, DEFAULT_EVENT_SEGMENT_MAX_BYTES as Q, resolveSessionRecord as R, AcpxOperationalError as Rt, sessionOptionsFromRecord as S, exitCodeForOutputErrorCode as St, reconcileAgentSessionId as T, normalizeOutputError as Tt, incrementPerfCounter as U, formatPerfMetric as V, QueueProtocolError as Vt, measurePerf as W, serializeSessionRecordForDisk as X, parseSessionRecord as Y, normalizeRuntimeSessionId as Z, recordPromptSubmission as _, withTimeout as _t, applyRequestedModelIfAdvertised as a, isAcpJsonRpcMessage as at, mergeSessionOptions as b, normalizeAgentName$1 as bt, setDesiredConfigOption as c, PromptInputValidationError as ct, syncAdvertisedModelState as d, parsePromptSource as dt, sessionBaseDir$1 as et, applyConfigOptionsToRecord as f, promptToDisplayText as ft, recordClientOperation as g, withInterrupt as gt, createSessionConversation as h, TimeoutError as ht, connectAndLoadSession as i, extractSessionUpdateNotification as it, findSession as j, NON_INTERACTIVE_PERMISSION_POLICIES as jt, absolutePath as k, AUTH_POLICIES as kt, setDesiredModeId as l, isPromptInput as lt, cloneSessionConversation as m, InterruptedError as mt, runPromptTurn as n, sessionEventLockPath as nt, assertRequestedModelSupported as o, parseJsonRpcErrorMessage as ot, cloneSessionAcpxState as p, textPrompt as pt, setPerfGauge as q, withConnectedSession as r, sessionEventSegmentPath as rt, setCurrentModelId as s, parsePromptStopReason as st, LiveSessionCheckpoint as t, sessionEventActivePath 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, writeSessionRecord as z, AgentSpawnError as zt };
4789
5472
 
4790
- //# sourceMappingURL=live-checkpoint-B9ctAuqV.js.map
5473
+ //# sourceMappingURL=live-checkpoint-CuFft_Nd.js.map