acpx 0.15.1 → 0.16.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 (39) hide show
  1. package/dist/agent-registry-DU6mhZBL.js +785 -0
  2. package/dist/agent-registry-DU6mhZBL.js.map +1 -0
  3. package/dist/agent-registry.d.ts +70 -0
  4. package/dist/agent-registry.d.ts.map +1 -0
  5. package/dist/agent-registry.js +2 -0
  6. package/dist/{cli-BHAT_D3V.js → cli-BkYIxj_N.js} +6 -14
  7. package/dist/cli-BkYIxj_N.js.map +1 -0
  8. package/dist/cli.d.ts +1 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +137 -333
  11. package/dist/cli.js.map +1 -1
  12. package/dist/{client-Cvz6msGc.d.ts → client-5levcBIs.d.ts} +11 -9
  13. package/dist/client-5levcBIs.d.ts.map +1 -0
  14. package/dist/{flows-BOI1XkMp.js → flows-Bxs4udrs.js} +205 -277
  15. package/dist/flows-Bxs4udrs.js.map +1 -0
  16. package/dist/flows.d.ts +6 -17
  17. package/dist/flows.d.ts.map +1 -1
  18. package/dist/flows.js +1 -1
  19. package/dist/{flags-BHcZWTov.js → invocation-options-BY4lxkn3.js} +82 -58
  20. package/dist/invocation-options-BY4lxkn3.js.map +1 -0
  21. package/dist/{live-checkpoint-sB7dFOYR.js → live-checkpoint-BEfxBKCh.js} +997 -1412
  22. package/dist/live-checkpoint-BEfxBKCh.js.map +1 -0
  23. package/dist/{output-8-D6Gd4Z.js → output-uo5a27KB.js} +487 -490
  24. package/dist/output-uo5a27KB.js.map +1 -0
  25. package/dist/runtime.d.ts +105 -22
  26. package/dist/runtime.d.ts.map +1 -1
  27. package/dist/runtime.js +582 -366
  28. package/dist/runtime.js.map +1 -1
  29. package/dist/{session-options-DyIGRNfu.d.ts → session-options-t3d-F4dn.d.ts} +8 -6
  30. package/dist/session-options-t3d-F4dn.d.ts.map +1 -0
  31. package/package.json +27 -25
  32. package/skills/acpx/SKILL.md +19 -17
  33. package/dist/cli-BHAT_D3V.js.map +0 -1
  34. package/dist/client-Cvz6msGc.d.ts.map +0 -1
  35. package/dist/flags-BHcZWTov.js.map +0 -1
  36. package/dist/flows-BOI1XkMp.js.map +0 -1
  37. package/dist/live-checkpoint-sB7dFOYR.js.map +0 -1
  38. package/dist/output-8-D6Gd4Z.js.map +0 -1
  39. package/dist/session-options-DyIGRNfu.d.ts.map +0 -1
@@ -1,13 +1,19 @@
1
+ import { A as resolveAgentCommandParts, C as asAbsoluteCwd, E as isoNow$2, F as waitForSpawn, M as runTimedExecFile, N as splitCommandLine, P as waitForChildExit, S as PROCESS_HELPER_TIMEOUT_MS, T as isChildProcessRunning, _ as buildSpawnCommandOptions, b as readWindowsEnvValue, f as resolveBuiltInAgentLaunch, g as buildAgentSpawnCommand, j as resolveAgentSessionCwd, k as requireAgentStdio, n as AGENT_REGISTRY, r as BUILT_IN_AGENT_PACKAGES, t as AGENT_ARGV_REGISTRY, v as buildTerminalShellSpawnCommand, w as basenameToken, x as resolveWindowsExecutablePath, y as buildTerminalSpawnCommand } from "./agent-registry-DU6mhZBL.js";
1
2
  import fs, { readFileSync, statSync } from "node:fs";
2
- import { fileURLToPath } from "node:url";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
3
4
  import path from "node:path";
5
+ import { spawn } from "node:child_process";
4
6
  import fs$1 from "node:fs/promises";
5
7
  import os from "node:os";
8
+ import { tempFile } from "@openclaw/fs-safe/advanced";
6
9
  import { randomUUID } from "node:crypto";
7
- import { execFile, spawn } from "node:child_process";
8
10
  import { Readable, Writable } from "node:stream";
9
- import { PROTOCOL_VERSION, client, methods } from "@agentclientprotocol/sdk";
11
+ import { PROTOCOL_VERSION, RequestError, client, methods } from "@agentclientprotocol/sdk";
12
+ import { FsSafeError } from "@openclaw/fs-safe/errors";
13
+ import { isPathInside } from "@openclaw/fs-safe/path";
14
+ import { root } from "@openclaw/fs-safe/root";
10
15
  import readline from "node:readline/promises";
16
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
11
17
  //#region src/errors.ts
12
18
  var AcpxOperationalError = class extends Error {
13
19
  outputCode;
@@ -412,15 +418,11 @@ function isRetryablePromptError(error) {
412
418
  if (isNonRetryablePromptError(error)) return false;
413
419
  const acp = extractAcpError(error);
414
420
  if (!acp) return false;
415
- if (isPermanentPromptAcpError(acp)) return false;
416
421
  return acp.code === -32603 || acp.code === -32700;
417
422
  }
418
423
  function isNonRetryablePromptError(error) {
419
424
  return error instanceof PermissionDeniedError || error instanceof PermissionPromptUnavailableError || isTimeoutLike(error) || isNoSessionLike(error) || isUsageLike(error);
420
425
  }
421
- function isPermanentPromptAcpError(acp) {
422
- return acp.code === -32001 || acp.code === -32002 || acp.code === -32601 || acp.code === -32602 || isAcpAuthRequiredPayload(acp);
423
- }
424
426
  function exitCodeForOutputErrorCode(code) {
425
427
  switch (code) {
426
428
  case "USAGE": return EXIT_CODES.USAGE;
@@ -432,254 +434,6 @@ function exitCodeForOutputErrorCode(code) {
432
434
  }
433
435
  }
434
436
  //#endregion
435
- //#region src/agent-registry.ts
436
- const ACP_ADAPTER_PACKAGE_RANGES = {
437
- pi: "^0.0.31",
438
- codex: "^1.1.5",
439
- claude: "^0.60.0",
440
- mux: "^0.28.0"
441
- };
442
- const AGENT_REGISTRY = {
443
- pi: `npx pi-acp@${ACP_ADAPTER_PACKAGE_RANGES.pi}`,
444
- openclaw: "openclaw acp",
445
- codex: `npx -y @agentclientprotocol/codex-acp@${ACP_ADAPTER_PACKAGE_RANGES.codex}`,
446
- claude: `npx -y @agentclientprotocol/claude-agent-acp@${ACP_ADAPTER_PACKAGE_RANGES.claude}`,
447
- gemini: "gemini --acp",
448
- cursor: "cursor-agent acp",
449
- copilot: "copilot --acp --stdio",
450
- droid: "droid exec --output-format acp",
451
- "fast-agent": "uvx fast-agent-mcp acp",
452
- "grok-build": "grok agent stdio",
453
- iflow: "iflow --experimental-acp",
454
- kilocode: "npx -y @kilocode/cli acp",
455
- kimi: "kimi acp",
456
- kiro: "kiro-cli-chat acp",
457
- mcode: "mcode acp",
458
- mux: `npx -y mux@${ACP_ADAPTER_PACKAGE_RANGES.mux} acp`,
459
- opencode: "npx -y opencode-ai acp",
460
- pool: "pool acp",
461
- qoder: "qodercli --acp",
462
- qwen: "qwen --acp",
463
- trae: "traecli acp serve",
464
- zeroclaw: "zeroclaw acp"
465
- };
466
- const AGENT_ARGV_REGISTRY = {
467
- pi: ["npx", `pi-acp@${ACP_ADAPTER_PACKAGE_RANGES.pi}`],
468
- openclaw: ["openclaw", "acp"],
469
- codex: [
470
- "npx",
471
- "-y",
472
- `@agentclientprotocol/codex-acp@${ACP_ADAPTER_PACKAGE_RANGES.codex}`
473
- ],
474
- claude: [
475
- "npx",
476
- "-y",
477
- `@agentclientprotocol/claude-agent-acp@${ACP_ADAPTER_PACKAGE_RANGES.claude}`
478
- ],
479
- gemini: ["gemini", "--acp"],
480
- cursor: ["cursor-agent", "acp"],
481
- copilot: [
482
- "copilot",
483
- "--acp",
484
- "--stdio"
485
- ],
486
- droid: [
487
- "droid",
488
- "exec",
489
- "--output-format",
490
- "acp"
491
- ],
492
- "fast-agent": [
493
- "uvx",
494
- "fast-agent-mcp",
495
- "acp"
496
- ],
497
- "grok-build": [
498
- "grok",
499
- "agent",
500
- "stdio"
501
- ],
502
- iflow: ["iflow", "--experimental-acp"],
503
- kilocode: [
504
- "npx",
505
- "-y",
506
- "@kilocode/cli",
507
- "acp"
508
- ],
509
- kimi: ["kimi", "acp"],
510
- kiro: ["kiro-cli-chat", "acp"],
511
- mcode: ["mcode", "acp"],
512
- mux: [
513
- "npx",
514
- "-y",
515
- `mux@${ACP_ADAPTER_PACKAGE_RANGES.mux}`,
516
- "acp"
517
- ],
518
- opencode: [
519
- "npx",
520
- "-y",
521
- "opencode-ai",
522
- "acp"
523
- ],
524
- pool: ["pool", "acp"],
525
- qoder: ["qodercli", "--acp"],
526
- qwen: ["qwen", "--acp"],
527
- trae: [
528
- "traecli",
529
- "acp",
530
- "serve"
531
- ],
532
- zeroclaw: ["zeroclaw", "acp"]
533
- };
534
- const BUILT_IN_AGENT_PACKAGES = {
535
- codex: {
536
- packageName: "@agentclientprotocol/codex-acp",
537
- packageRange: ACP_ADAPTER_PACKAGE_RANGES.codex,
538
- preferredBinName: "codex-acp",
539
- fallbackCommand: AGENT_REGISTRY.codex,
540
- legacyFallbackCommands: []
541
- },
542
- claude: {
543
- packageName: "@agentclientprotocol/claude-agent-acp",
544
- packageRange: ACP_ADAPTER_PACKAGE_RANGES.claude,
545
- preferredBinName: "claude-agent-acp",
546
- fallbackCommand: AGENT_REGISTRY.claude,
547
- legacyFallbackCommands: [`npm exec @agentclientprotocol/claude-agent-acp@${ACP_ADAPTER_PACKAGE_RANGES.claude}`]
548
- }
549
- };
550
- const AGENT_ALIASES = {
551
- "factory-droid": "droid",
552
- factorydroid: "droid"
553
- };
554
- const DEFAULT_AGENT_NAME = "codex";
555
- function normalizeAgentName$1(value) {
556
- return value.trim().toLowerCase();
557
- }
558
- function resolveCanonicalAgentName(value) {
559
- const normalized = normalizeAgentName$1(value);
560
- return AGENT_ALIASES[normalized] ?? normalized;
561
- }
562
- function mergeAgentRegistry(overrides) {
563
- if (!overrides) return { ...AGENT_REGISTRY };
564
- const merged = { ...AGENT_REGISTRY };
565
- for (const [name, command] of Object.entries(overrides)) {
566
- const normalized = normalizeAgentName$1(name);
567
- if (!normalized || !command.trim()) continue;
568
- merged[normalized] = command.trim();
569
- }
570
- return merged;
571
- }
572
- function resolveAgentCommand(agentName, overrides) {
573
- const normalized = normalizeAgentName$1(agentName);
574
- const registry = mergeAgentRegistry(overrides);
575
- return registry[normalized] ?? registry[AGENT_ALIASES[normalized] ?? normalized] ?? agentName;
576
- }
577
- function resolveAgentArgv(agentName) {
578
- const normalized = normalizeAgentName$1(agentName);
579
- const argv = AGENT_ARGV_REGISTRY[normalized] ?? AGENT_ARGV_REGISTRY[resolveCanonicalAgentName(agentName)];
580
- return argv ? [...argv] : void 0;
581
- }
582
- function findBuiltInAgentPackage(agentCommand) {
583
- const normalized = agentCommand.trim();
584
- return Object.values(BUILT_IN_AGENT_PACKAGES).find((spec) => spec.fallbackCommand === normalized || spec.legacyFallbackCommands?.includes(normalized));
585
- }
586
- function defaultResolvePackageRoot(packageName) {
587
- const segments = packageName.split("/");
588
- let cursor = path.dirname(fileURLToPath(import.meta.url));
589
- while (true) {
590
- const candidateRoot = path.join(cursor, "node_modules", ...segments);
591
- const manifestPath = path.join(candidateRoot, "package.json");
592
- if (fs.existsSync(manifestPath)) try {
593
- if (JSON.parse(fs.readFileSync(manifestPath, "utf8")).name === packageName) return candidateRoot;
594
- } catch {}
595
- const parent = path.dirname(cursor);
596
- if (parent === cursor) throw new Error(`Built-in agent package not found: ${packageName}`);
597
- cursor = parent;
598
- }
599
- }
600
- function resolvePackageBin(spec, manifest) {
601
- if (typeof manifest.bin === "string") return manifest.bin;
602
- if (!manifest.bin || typeof manifest.bin !== "object") return;
603
- return manifest.bin[spec.preferredBinName] ?? (Object.keys(manifest.bin).length === 1 ? Object.values(manifest.bin)[0] : void 0);
604
- }
605
- function defaultResolveNpmCliPath(execPath) {
606
- const candidate = path.resolve(path.dirname(execPath), "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
607
- if (!fs.existsSync(candidate)) throw new Error(`npm CLI not found for execPath: ${execPath}`);
608
- return candidate;
609
- }
610
- function resolveInstalledBuiltInAgentLaunch(agentCommand, options = {}) {
611
- const spec = findBuiltInAgentPackage(agentCommand);
612
- if (!spec) return;
613
- const readFileSync = options.readFileSync ?? fs.readFileSync;
614
- const existsSync = options.existsSync ?? fs.existsSync;
615
- const resolvePackageRoot = options.resolvePackageRoot ?? defaultResolvePackageRoot;
616
- try {
617
- const resolved = resolveInstalledBuiltInAgentPackage(spec, {
618
- readFileSync,
619
- existsSync,
620
- resolvePackageRoot
621
- });
622
- if (!resolved) return;
623
- return {
624
- source: "installed",
625
- command: process.execPath,
626
- args: [resolved.binPath],
627
- packageName: spec.packageName,
628
- packageRange: spec.packageRange,
629
- packageVersion: resolved.packageVersion,
630
- binPath: resolved.binPath
631
- };
632
- } catch {
633
- return;
634
- }
635
- }
636
- function resolveInstalledBuiltInAgentPackage(spec, options) {
637
- const packageRoot = options.resolvePackageRoot(spec.packageName);
638
- const manifest = JSON.parse(options.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
639
- if (manifest.name !== spec.packageName) return;
640
- const relativeBinPath = resolvePackageBin(spec, manifest);
641
- if (!relativeBinPath) return;
642
- const binPath = path.resolve(packageRoot, relativeBinPath);
643
- return options.existsSync(binPath) ? {
644
- packageVersion: manifest.version,
645
- binPath
646
- } : void 0;
647
- }
648
- function resolvePackageExecBuiltInAgentLaunch(agentCommand, options = {}) {
649
- const spec = findBuiltInAgentPackage(agentCommand);
650
- if (!spec) return;
651
- const existsSync = options.existsSync ?? fs.existsSync;
652
- const execPath = options.execPath ?? process.execPath;
653
- const resolveNpmCliPath = options.resolveNpmCliPath ?? defaultResolveNpmCliPath;
654
- try {
655
- const npmCliPath = resolveNpmCliPath(execPath);
656
- if (!existsSync(npmCliPath)) return;
657
- return {
658
- source: "package-exec",
659
- command: execPath,
660
- args: [
661
- npmCliPath,
662
- "exec",
663
- "--yes",
664
- `--package=${spec.packageName}@${spec.packageRange}`,
665
- "--",
666
- spec.preferredBinName
667
- ],
668
- packageName: spec.packageName,
669
- packageRange: spec.packageRange,
670
- npmCliPath
671
- };
672
- } catch {
673
- return;
674
- }
675
- }
676
- function resolveBuiltInAgentLaunch(agentCommand, options = {}) {
677
- return resolveInstalledBuiltInAgentLaunch(agentCommand, options) ?? resolvePackageExecBuiltInAgentLaunch(agentCommand, options);
678
- }
679
- function listBuiltInAgents(overrides) {
680
- return [.../* @__PURE__ */ new Set([...Object.keys(AGENT_REGISTRY), ...Object.keys(overrides ?? {})])];
681
- }
682
- //#endregion
683
437
  //#region src/async-control.ts
684
438
  var TimeoutError = class extends Error {
685
439
  constructor(timeoutMs) {
@@ -737,195 +491,38 @@ async function withInterrupt(run, onInterrupt) {
737
491
  });
738
492
  }
739
493
  //#endregion
740
- //#region src/prompt-content.ts
741
- var PromptInputValidationError = class extends Error {
742
- constructor(message) {
743
- super(message);
744
- this.name = "PromptInputValidationError";
745
- }
746
- };
747
- function asRecord$6(value) {
748
- if (!value || typeof value !== "object" || Array.isArray(value)) return;
749
- return value;
750
- }
751
- function isNonEmptyString(value) {
752
- return typeof value === "string" && value.trim().length > 0;
753
- }
754
- function isBase64Data(value) {
755
- if (value.length === 0 || value.length % 4 !== 0) return false;
756
- return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
757
- }
758
- function isImageMimeType(value) {
759
- return /^image\/[A-Za-z0-9.+-]+$/i.test(value);
760
- }
761
- function isAudioMimeType(value) {
762
- return /^audio\/[A-Za-z0-9.+-]+$/i.test(value);
763
- }
764
- function isTextBlock(value) {
765
- const record = asRecord$6(value);
766
- return record?.type === "text" && typeof record.text === "string";
767
- }
768
- function isImageBlock(value) {
769
- const record = asRecord$6(value);
770
- return record?.type === "image" && isNonEmptyString(record.mimeType) && isImageMimeType(record.mimeType) && typeof record.data === "string" && isBase64Data(record.data);
771
- }
772
- function isAudioBlock(value) {
773
- const record = asRecord$6(value);
774
- return record?.type === "audio" && isNonEmptyString(record.mimeType) && isAudioMimeType(record.mimeType) && typeof record.data === "string" && isBase64Data(record.data);
775
- }
776
- function isResourceLinkBlock(value) {
777
- const record = asRecord$6(value);
778
- return record?.type === "resource_link" && isNonEmptyString(record.uri) && (record.title === void 0 || typeof record.title === "string") && (record.name === void 0 || typeof record.name === "string");
779
- }
780
- function isResourcePayload(value) {
781
- const record = asRecord$6(value);
782
- if (!record || !isNonEmptyString(record.uri)) return false;
783
- return record.text === void 0 || typeof record.text === "string";
784
- }
785
- function isResourceBlock(value) {
786
- const record = asRecord$6(value);
787
- return record?.type === "resource" && isResourcePayload(record.resource);
788
- }
789
- const CONTENT_BLOCK_VALIDATORS = [
790
- isTextBlock,
791
- isImageBlock,
792
- isAudioBlock,
793
- isResourceLinkBlock,
794
- isResourceBlock
795
- ];
796
- function isContentBlock(value) {
797
- return CONTENT_BLOCK_VALIDATORS.some((validator) => validator(value));
798
- }
799
- const CONTENT_BLOCK_ERROR_VALIDATORS = {
800
- text: validateTextContentBlock,
801
- image: validateImageContentBlock,
802
- audio: validateAudioContentBlock,
803
- resource_link: validateResourceLinkContentBlock,
804
- resource: validateResourceContentBlock
805
- };
806
- function contentBlockErrorValidator(type) {
807
- return Object.hasOwn(CONTENT_BLOCK_ERROR_VALIDATORS, type) ? CONTENT_BLOCK_ERROR_VALIDATORS[type] : void 0;
808
- }
809
- function validateTextContentBlock(record, index) {
810
- return typeof record.text === "string" ? void 0 : `prompt[${index}] text block must include a string text field`;
811
- }
812
- function validateImageContentBlock(record, index) {
813
- if (!isNonEmptyString(record.mimeType)) return `prompt[${index}] image block must include a non-empty mimeType`;
814
- if (!isImageMimeType(record.mimeType)) return `prompt[${index}] image block mimeType must start with image/`;
815
- if (typeof record.data !== "string" || record.data.length === 0) return `prompt[${index}] image block must include non-empty base64 data`;
816
- return isBase64Data(record.data) ? void 0 : `prompt[${index}] image block data must be valid base64`;
817
- }
818
- function validateAudioContentBlock(record, index) {
819
- if (!isNonEmptyString(record.mimeType)) return `prompt[${index}] audio block must include a non-empty mimeType`;
820
- if (!isAudioMimeType(record.mimeType)) return `prompt[${index}] audio block mimeType must start with audio/`;
821
- if (typeof record.data !== "string" || record.data.length === 0) return `prompt[${index}] audio block must include non-empty base64 data`;
822
- return isBase64Data(record.data) ? void 0 : `prompt[${index}] audio block data must be valid base64`;
823
- }
824
- function validateResourceLinkContentBlock(record, index) {
825
- if (!isNonEmptyString(record.uri)) return `prompt[${index}] resource_link block must include a non-empty uri`;
826
- if (record.title !== void 0 && typeof record.title !== "string") return `prompt[${index}] resource_link block title must be a string when present`;
827
- if (record.name !== void 0 && typeof record.name !== "string") return `prompt[${index}] resource_link block name must be a string when present`;
828
- }
829
- function validateResourceContentBlock(record, index) {
830
- if (!asRecord$6(record.resource)) return `prompt[${index}] resource block must include a resource object`;
831
- return isResourcePayload(record.resource) ? void 0 : `prompt[${index}] resource block resource must include a non-empty uri and optional text`;
832
- }
833
- function getContentBlockValidationError(value, index) {
834
- const record = asRecord$6(value);
835
- if (!record || typeof record.type !== "string") return `prompt[${index}] must be an ACP content block object`;
836
- const validator = contentBlockErrorValidator(record.type);
837
- return validator ? validator(record, index) : `prompt[${index}] has unsupported content block type ${JSON.stringify(record.type)}`;
838
- }
839
- function isPromptInput(value) {
840
- return Array.isArray(value) && value.every((entry) => isContentBlock(entry));
841
- }
842
- function promptCapabilityRequirement(block) {
843
- switch (block.type) {
844
- case "image": return {
845
- blockType: "image",
846
- capability: "image"
847
- };
848
- case "audio": return {
849
- blockType: "audio",
850
- capability: "audio"
851
- };
852
- case "resource": return {
853
- blockType: "resource",
854
- capability: "embeddedContext"
855
- };
856
- default: return;
857
- }
858
- }
859
- function getUnsupportedPromptContentMessage(prompt, agentCapabilities) {
860
- for (const [index, block] of prompt.entries()) {
861
- const requirement = promptCapabilityRequirement(block);
862
- if (!requirement) continue;
863
- if (agentCapabilities?.promptCapabilities?.[requirement.capability] === true) continue;
864
- return `prompt[${index}] ${requirement.blockType} content requires agentCapabilities.promptCapabilities.${requirement.capability}`;
865
- }
866
- }
867
- function textPrompt(text) {
868
- return [{
869
- type: "text",
870
- text
871
- }];
872
- }
873
- function parseStructuredPrompt(source) {
874
- if (!source.startsWith("[")) return;
875
- try {
876
- const parsed = JSON.parse(source);
877
- if (isPromptInput(parsed)) return parsed;
878
- if (Array.isArray(parsed)) throw new PromptInputValidationError(parsed.map((entry, index) => getContentBlockValidationError(entry, index)).find((message) => message !== void 0) ?? "Structured prompt JSON must be an array of valid ACP content blocks");
879
- return;
880
- } catch (error) {
881
- if (error instanceof PromptInputValidationError) throw error;
882
- return;
883
- }
884
- }
885
- function parsePromptSource(source) {
886
- const trimmed = source.trim();
887
- const structured = parseStructuredPrompt(trimmed);
888
- if (structured) return structured;
889
- if (!trimmed) return [];
890
- return textPrompt(trimmed);
891
- }
892
- function mergePromptSourceWithText(source, suffixText) {
893
- const prompt = parsePromptSource(source);
894
- const appended = suffixText.trim();
895
- if (!appended) return prompt;
896
- if (prompt.length === 0) return textPrompt(appended);
897
- return [...prompt, ...textPrompt(appended)];
898
- }
899
- function promptToDisplayText(prompt) {
900
- return prompt.map((block) => contentBlockDisplayText(block)).filter((entry) => entry.trim().length > 0).join("\n\n").trim();
901
- }
902
- function contentBlockDisplayText(block) {
903
- switch (block.type) {
904
- case "text": return block.text;
905
- case "resource_link": return block.title ?? block.name ?? block.uri;
906
- case "resource": return resourceBlockDisplayText(block);
907
- case "image": return `[image] ${block.mimeType}`;
908
- case "audio": return `[audio] ${block.mimeType}`;
909
- default: return "";
910
- }
911
- }
912
- function resourceBlockDisplayText(block) {
913
- return "text" in block.resource && typeof block.resource.text === "string" ? block.resource.text : block.resource.uri;
494
+ //#region src/acp/codex-compat.ts
495
+ function isLegacyZedCodexAcpInvocation(agentCommand) {
496
+ return /@zed-industries\/codex-acp\b/u.test(agentCommand);
497
+ }
498
+ function preferCodexPermissionRefusal(request, agentName) {
499
+ if (agentName !== "@agentclientprotocol/codex-acp") return request;
500
+ const preferred = request.options.find((option) => option.kind === "reject_once" && ["decline", "reject_permissions"].includes(option.optionId));
501
+ return preferred ? {
502
+ ...request,
503
+ options: [preferred, ...request.options.filter((option) => option !== preferred)]
504
+ } : request;
505
+ }
506
+ function codexPermissionNotice(request, response, agentName) {
507
+ if (agentName !== "@agentclientprotocol/codex-acp") return;
508
+ if (response.outcome.outcome === "cancelled") return "No matching permission option was available. The request was safely cancelled; Codex may end the current turn.";
509
+ const selectedId = response.outcome.optionId;
510
+ return request.options.find((option) => option.optionId === selectedId)?.kind === "reject_once" && selectedId === "cancel" ? "Permission refused using Codex's cancellation option; this can end the current turn. The operation was not approved." : void 0;
914
511
  }
915
512
  //#endregion
916
513
  //#region src/acp/jsonrpc.ts
917
- function asRecord$5(value) {
514
+ function asRecord$6(value) {
918
515
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
919
516
  return value;
920
517
  }
921
518
  function isAcpMessageObject(value) {
922
- return asRecord$5(value) !== null;
519
+ return asRecord$6(value) !== null;
923
520
  }
924
521
  function hasValidId(value) {
925
522
  return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
926
523
  }
927
524
  function isErrorObject(value) {
928
- const record = asRecord$5(value);
525
+ const record = asRecord$6(value);
929
526
  return !!record && typeof record.code === "number" && Number.isFinite(record.code) && typeof record.message === "string";
930
527
  }
931
528
  function hasResultOrError(value) {
@@ -950,7 +547,7 @@ function isJsonRpcResponse(value) {
950
547
  return hasResultOrError(value);
951
548
  }
952
549
  function isAcpJsonRpcMessage(value) {
953
- const record = asRecord$5(value);
550
+ const record = asRecord$6(value);
954
551
  if (!record || record.jsonrpc !== "2.0") return false;
955
552
  return isJsonRpcNotificationRecord(record) || isJsonRpcRequest(record) || isJsonRpcResponse(record);
956
553
  }
@@ -962,11 +559,11 @@ function isSessionUpdateNotification(message) {
962
559
  }
963
560
  function extractSessionUpdateNotification(message) {
964
561
  if (!isSessionUpdateNotification(message)) return;
965
- const params = asRecord$5(message.params);
562
+ const params = asRecord$6(message.params);
966
563
  if (!params) return;
967
564
  const sessionId = typeof params.sessionId === "string" ? params.sessionId : null;
968
565
  if (!sessionId) return;
969
- const update = asRecord$5(params.update);
566
+ const update = asRecord$6(params.update);
970
567
  if (!update || typeof update.sessionUpdate !== "string") return;
971
568
  return {
972
569
  sessionId,
@@ -975,33 +572,139 @@ function extractSessionUpdateNotification(message) {
975
572
  }
976
573
  function parsePromptStopReason(message) {
977
574
  if (!Object.hasOwn(message, "id") || !Object.hasOwn(message, "result")) return;
978
- const record = asRecord$5(message.result);
575
+ const record = asRecord$6(message.result);
979
576
  if (!record) return;
980
577
  return typeof record.stopReason === "string" ? record.stopReason : void 0;
981
578
  }
579
+ function parsePermissionNotice(message) {
580
+ if (!Object.hasOwn(message, "id") || !Object.hasOwn(message, "result")) return;
581
+ const notice = asRecord$6(asRecord$6(asRecord$6(message.result)?._meta)?.acpx)?.permissionNotice;
582
+ return typeof notice === "string" ? notice : void 0;
583
+ }
982
584
  function parseJsonRpcErrorMessage(message) {
983
585
  if (!Object.hasOwn(message, "error")) return;
984
- const errorRecord = asRecord$5(message.error);
586
+ const errorRecord = asRecord$6(message.error);
985
587
  if (!errorRecord || typeof errorRecord.message !== "string") return;
986
588
  return errorRecord.message;
987
589
  }
988
590
  //#endregion
591
+ //#region src/process-liveness.ts
592
+ function isProcessAlive(pid) {
593
+ if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
594
+ try {
595
+ process.kill(pid, 0);
596
+ return true;
597
+ } catch {
598
+ return false;
599
+ }
600
+ }
601
+ //#endregion
602
+ //#region \0@oxc-project+runtime@0.149.0/helpers/esm/usingCtx.js
603
+ function _usingCtx() {
604
+ var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
605
+ var n = Error();
606
+ return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
607
+ }, e = {}, n = [];
608
+ function using(r, e) {
609
+ if (null != e) {
610
+ if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
611
+ if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
612
+ if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
613
+ if ("function" != typeof o) throw new TypeError("Object is not disposable.");
614
+ t && (o = function o() {
615
+ try {
616
+ t.call(e);
617
+ } catch (r) {
618
+ return Promise.reject(r);
619
+ }
620
+ }), n.push({
621
+ v: e,
622
+ d: o,
623
+ a: r
624
+ });
625
+ } else r && n.push({
626
+ d: e,
627
+ a: r
628
+ });
629
+ return e;
630
+ }
631
+ return {
632
+ e,
633
+ u: using.bind(null, !1),
634
+ a: using.bind(null, !0),
635
+ d: function d() {
636
+ var o, t = this.e, s = 0;
637
+ function next() {
638
+ for (; o = n.pop();) try {
639
+ if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
640
+ if (o.d) {
641
+ var r = o.d.call(o.v);
642
+ if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
643
+ } else s |= 1;
644
+ } catch (r) {
645
+ return err(r);
646
+ }
647
+ if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
648
+ if (t !== e) throw t;
649
+ }
650
+ function err(n) {
651
+ return t = t !== e ? new r(n, t) : n, next();
652
+ }
653
+ return next();
654
+ }
655
+ };
656
+ }
657
+ //#endregion
658
+ //#region src/state-files.ts
659
+ async function writePrivateFile(filePath, content, options = {}) {
660
+ try {
661
+ var _usingCtx$1 = _usingCtx();
662
+ const privateDirectory = options.privateDirectory !== false;
663
+ await fs$1.mkdir(path.dirname(filePath), {
664
+ recursive: true,
665
+ mode: privateDirectory ? 448 : void 0
666
+ });
667
+ const directory = await fs$1.realpath(path.dirname(filePath));
668
+ if (privateDirectory && process.platform !== "win32") await fs$1.chmod(directory, 448);
669
+ const temporary = _usingCtx$1.a(await tempFile({
670
+ rootDir: directory,
671
+ prefix: "acpx-write",
672
+ fileName: "record.json"
673
+ }));
674
+ const handle = await fs$1.open(temporary.path, "wx", 384);
675
+ try {
676
+ await handle.writeFile(content, "utf8");
677
+ await handle.chmod(384);
678
+ } finally {
679
+ await handle.close();
680
+ }
681
+ await fs$1.rename(temporary.path, path.join(directory, path.basename(filePath)));
682
+ } catch (_) {
683
+ _usingCtx$1.e = _;
684
+ } finally {
685
+ await _usingCtx$1.d();
686
+ }
687
+ }
688
+ async function writePrivateJsonFile(filePath, value) {
689
+ await writePrivateFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
690
+ }
691
+ //#endregion
989
692
  //#region src/session/event-log.ts
990
693
  const DEFAULT_EVENT_SEGMENT_MAX_BYTES = 67108864;
991
- function sessionBaseDir$1() {
694
+ function sessionBaseDir() {
992
695
  return path.join(os.homedir(), ".acpx", "sessions");
993
696
  }
994
697
  function safeSessionId(sessionId) {
995
698
  return encodeURIComponent(sessionId);
996
699
  }
997
700
  function sessionEventActivePath(sessionId) {
998
- return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.ndjson`);
701
+ return path.join(sessionBaseDir(), `${safeSessionId(sessionId)}.stream.ndjson`);
999
702
  }
1000
703
  function sessionEventSegmentPath(sessionId, segment) {
1001
- return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.${segment}.ndjson`);
704
+ return path.join(sessionBaseDir(), `${safeSessionId(sessionId)}.stream.${segment}.ndjson`);
1002
705
  }
1003
706
  function sessionEventLockPath(sessionId) {
1004
- return path.join(sessionBaseDir$1(), `${safeSessionId(sessionId)}.stream.lock`);
707
+ return path.join(sessionBaseDir(), `${safeSessionId(sessionId)}.stream.lock`);
1005
708
  }
1006
709
  function defaultSessionEventLog(sessionId) {
1007
710
  return {
@@ -1034,57 +737,45 @@ function extractAgentSessionId(meta) {
1034
737
  }
1035
738
  }
1036
739
  //#endregion
1037
- //#region src/session/runtime-session-id.ts
1038
- function normalizeRuntimeSessionId(value) {
1039
- return normalizeAgentSessionId(value);
1040
- }
1041
- function extractRuntimeSessionId(meta) {
1042
- return extractAgentSessionId(meta);
1043
- }
1044
- //#endregion
1045
740
  //#region src/session/persistence/serialize.ts
1046
741
  function serializeSessionRecordForDisk(record) {
1047
- const canonical = {
1048
- ...record,
1049
- schema: SESSION_RECORD_SCHEMA
1050
- };
1051
742
  return {
1052
- schema: canonical.schema,
1053
- acpx_record_id: canonical.acpxRecordId,
1054
- acp_session_id: canonical.acpSessionId,
1055
- agent_session_id: normalizeRuntimeSessionId(canonical.agentSessionId),
1056
- agent_command: canonical.agentCommand,
1057
- agent_argv: canonical.agentArgv,
1058
- cwd: canonical.cwd,
1059
- name: canonical.name,
1060
- created_at: canonical.createdAt,
1061
- last_used_at: canonical.lastUsedAt,
1062
- last_seq: canonical.lastSeq,
1063
- last_request_id: canonical.lastRequestId,
1064
- event_log: canonical.eventLog,
1065
- closed: canonical.closed,
1066
- closed_at: canonical.closedAt,
1067
- pid: canonical.pid,
1068
- agent_started_at: canonical.agentStartedAt,
1069
- last_prompt_at: canonical.lastPromptAt,
1070
- last_agent_exit_code: canonical.lastAgentExitCode,
1071
- last_agent_exit_signal: canonical.lastAgentExitSignal,
1072
- last_agent_exit_at: canonical.lastAgentExitAt,
1073
- last_agent_disconnect_reason: canonical.lastAgentDisconnectReason,
1074
- protocol_version: canonical.protocolVersion,
1075
- agent_capabilities: canonical.agentCapabilities,
1076
- title: canonical.title,
1077
- messages: canonical.messages,
1078
- updated_at: canonical.updated_at,
1079
- cumulative_token_usage: canonical.cumulative_token_usage,
1080
- cumulative_cost: canonical.cumulative_cost,
1081
- request_token_usage: canonical.request_token_usage,
1082
- acpx: canonical.acpx,
1083
- imported_from: canonical.importedFrom ? {
1084
- record_id: canonical.importedFrom.recordId,
1085
- cwd_original: canonical.importedFrom.cwdOriginal,
1086
- exported_by: canonical.importedFrom.exportedBy,
1087
- exported_at: canonical.importedFrom.exportedAt
743
+ schema: SESSION_RECORD_SCHEMA,
744
+ acpx_record_id: record.acpxRecordId,
745
+ acp_session_id: record.acpSessionId,
746
+ agent_session_id: normalizeAgentSessionId(record.agentSessionId),
747
+ agent_command: record.agentCommand,
748
+ agent_argv: record.agentArgv,
749
+ cwd: record.cwd,
750
+ name: record.name,
751
+ created_at: record.createdAt,
752
+ last_used_at: record.lastUsedAt,
753
+ last_seq: record.lastSeq,
754
+ last_request_id: record.lastRequestId,
755
+ event_log: record.eventLog,
756
+ closed: record.closed,
757
+ closed_at: record.closedAt,
758
+ pid: record.pid,
759
+ agent_started_at: record.agentStartedAt,
760
+ last_prompt_at: record.lastPromptAt,
761
+ last_agent_exit_code: record.lastAgentExitCode,
762
+ last_agent_exit_signal: record.lastAgentExitSignal,
763
+ last_agent_exit_at: record.lastAgentExitAt,
764
+ last_agent_disconnect_reason: record.lastAgentDisconnectReason,
765
+ protocol_version: record.protocolVersion,
766
+ agent_capabilities: record.agentCapabilities,
767
+ title: record.title,
768
+ messages: record.messages,
769
+ updated_at: record.updated_at,
770
+ cumulative_token_usage: record.cumulative_token_usage,
771
+ cumulative_cost: record.cumulative_cost,
772
+ request_token_usage: record.request_token_usage,
773
+ acpx: record.acpx,
774
+ imported_from: record.importedFrom ? {
775
+ record_id: record.importedFrom.recordId,
776
+ cwd_original: record.importedFrom.cwdOriginal,
777
+ exported_by: record.importedFrom.exportedBy,
778
+ exported_at: record.importedFrom.exportedAt
1088
779
  } : void 0
1089
780
  };
1090
781
  }
@@ -1134,7 +825,7 @@ function resolveAgentArgvForCommand(agentCommand) {
1134
825
  }
1135
826
  //#endregion
1136
827
  //#region src/session/persistence/parse.ts
1137
- function asRecord$4(value) {
828
+ function asRecord$5(value) {
1138
829
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
1139
830
  return value;
1140
831
  }
@@ -1153,12 +844,12 @@ function parsePersistedAgentArgv(record) {
1153
844
  function hasModelConfigOption(options) {
1154
845
  if (!Array.isArray(options)) return false;
1155
846
  return options.some((entry) => {
1156
- const option = asRecord$4(entry);
847
+ const option = asRecord$5(entry);
1157
848
  return option?.category === "model" || option?.id === "model";
1158
849
  });
1159
850
  }
1160
851
  function parseConfigOptions(raw) {
1161
- if (!Array.isArray(raw) || !raw.every((entry) => asRecord$4(entry) !== void 0)) return;
852
+ if (!Array.isArray(raw) || !raw.every((entry) => asRecord$5(entry) !== void 0)) return;
1162
853
  return raw;
1163
854
  }
1164
855
  function parseAvailableCommand(raw) {
@@ -1166,7 +857,7 @@ function parseAvailableCommand(raw) {
1166
857
  const name = raw.trim();
1167
858
  return name ? { name } : void 0;
1168
859
  }
1169
- const record = asRecord$4(raw);
860
+ const record = asRecord$5(raw);
1170
861
  if (!record) return;
1171
862
  const name = parseNonEmptyString(record.name);
1172
863
  if (!name) return;
@@ -1189,7 +880,7 @@ function parseNonEmptyString(value) {
1189
880
  }
1190
881
  function parseTokenUsage(raw) {
1191
882
  if (raw === void 0 || raw === null) return;
1192
- const record = asRecord$4(raw);
883
+ const record = asRecord$5(raw);
1193
884
  if (!record) return null;
1194
885
  const usage = {};
1195
886
  for (const field of [
@@ -1212,7 +903,7 @@ function isNonNegativeFiniteNumber(value) {
1212
903
  }
1213
904
  function parseUsageCost(raw) {
1214
905
  if (raw === void 0 || raw === null) return;
1215
- const record = asRecord$4(raw);
906
+ const record = asRecord$5(raw);
1216
907
  if (!record) return null;
1217
908
  return parseUsageCostRecord(record);
1218
909
  }
@@ -1238,7 +929,7 @@ function parseCostCurrency(value) {
1238
929
  }
1239
930
  function parseRequestTokenUsage(raw) {
1240
931
  if (raw === void 0 || raw === null) return;
1241
- const record = asRecord$4(raw);
932
+ const record = asRecord$5(raw);
1242
933
  if (!record) return null;
1243
934
  const usage = {};
1244
935
  for (const [key, value] of Object.entries(record)) {
@@ -1249,25 +940,25 @@ function parseRequestTokenUsage(raw) {
1249
940
  return usage;
1250
941
  }
1251
942
  function isSessionMessageImage(raw) {
1252
- const record = asRecord$4(raw);
943
+ const record = asRecord$5(raw);
1253
944
  if (!record || typeof record.source !== "string") return false;
1254
945
  if (record.size === void 0 || record.size === null) return true;
1255
- const size = asRecord$4(record.size);
946
+ const size = asRecord$5(record.size);
1256
947
  return !!size && isFiniteNumber(size.width) && isFiniteNumber(size.height);
1257
948
  }
1258
949
  function isSessionMessageAudio(raw) {
1259
- const record = asRecord$4(raw);
950
+ const record = asRecord$5(raw);
1260
951
  return !!record && typeof record.source === "string" && typeof record.mime_type === "string";
1261
952
  }
1262
953
  function isFiniteNumber(value) {
1263
954
  return typeof value === "number" && Number.isFinite(value);
1264
955
  }
1265
956
  function isUserContent(raw) {
1266
- const record = asRecord$4(raw);
957
+ const record = asRecord$5(raw);
1267
958
  if (!record) return false;
1268
959
  if (typeof record.Text === "string") return true;
1269
960
  if (record.Mention !== void 0) {
1270
- const mention = asRecord$4(record.Mention);
961
+ const mention = asRecord$5(record.Mention);
1271
962
  return !!mention && typeof mention.uri === "string" && typeof mention.content === "string";
1272
963
  }
1273
964
  if (record.Image !== void 0) return isSessionMessageImage(record.Image);
@@ -1275,7 +966,7 @@ function isUserContent(raw) {
1275
966
  return false;
1276
967
  }
1277
968
  function isToolUse(raw) {
1278
- const record = asRecord$4(raw);
969
+ const record = asRecord$5(raw);
1279
970
  return !!record && hasStringFields(record, [
1280
971
  "id",
1281
972
  "name",
@@ -1289,18 +980,18 @@ function isOptionalString(value) {
1289
980
  return value === void 0 || value === null || typeof value === "string";
1290
981
  }
1291
982
  function isToolResultContent(raw) {
1292
- const record = asRecord$4(raw);
983
+ const record = asRecord$5(raw);
1293
984
  if (!record) return false;
1294
985
  if (typeof record.Text === "string") return true;
1295
986
  if (record.Image !== void 0) return isSessionMessageImage(record.Image);
1296
987
  return false;
1297
988
  }
1298
989
  function isToolResult(raw) {
1299
- const record = asRecord$4(raw);
990
+ const record = asRecord$5(raw);
1300
991
  return !!record && typeof record.tool_use_id === "string" && typeof record.tool_name === "string" && typeof record.is_error === "boolean" && isToolResultContent(record.content);
1301
992
  }
1302
993
  function isAgentContent(raw) {
1303
- const record = asRecord$4(raw);
994
+ const record = asRecord$5(raw);
1304
995
  if (!record) return false;
1305
996
  if (typeof record.Text === "string") return true;
1306
997
  if (record.Thinking !== void 0) return isThinkingContent(record.Thinking);
@@ -1309,21 +1000,21 @@ function isAgentContent(raw) {
1309
1000
  return false;
1310
1001
  }
1311
1002
  function isThinkingContent(raw) {
1312
- const thinking = asRecord$4(raw);
1003
+ const thinking = asRecord$5(raw);
1313
1004
  return !!thinking && typeof thinking.text === "string" && isOptionalString(thinking.signature);
1314
1005
  }
1315
1006
  function isUserMessage$1(raw) {
1316
- const record = asRecord$4(raw);
1007
+ const record = asRecord$5(raw);
1317
1008
  if (!record || record.User === void 0) return false;
1318
- const user = asRecord$4(record.User);
1009
+ const user = asRecord$5(record.User);
1319
1010
  return !!user && typeof user.id === "string" && Array.isArray(user.content) && user.content.every((entry) => isUserContent(entry));
1320
1011
  }
1321
1012
  function isAgentMessage$1(raw) {
1322
- const record = asRecord$4(raw);
1013
+ const record = asRecord$5(raw);
1323
1014
  if (!record || record.Agent === void 0) return false;
1324
- const agent = asRecord$4(record.Agent);
1015
+ const agent = asRecord$5(record.Agent);
1325
1016
  if (!agent || !Array.isArray(agent.content) || !agent.content.every(isAgentContent)) return false;
1326
- const toolResults = asRecord$4(agent.tool_results);
1017
+ const toolResults = asRecord$5(agent.tool_results);
1327
1018
  if (!toolResults) return false;
1328
1019
  return Object.values(toolResults).every(isToolResult);
1329
1020
  }
@@ -1356,13 +1047,16 @@ function hasValidConversationCore(record) {
1356
1047
  return Array.isArray(record.messages) && record.messages.every(isConversationMessage) && typeof record.updated_at === "string";
1357
1048
  }
1358
1049
  function parseAcpxState(raw) {
1359
- const record = asRecord$4(raw);
1050
+ const record = asRecord$5(raw);
1360
1051
  if (!record) return;
1361
1052
  const state = {};
1362
- assignBooleanTrue(state, "reset_on_next_ensure", record.reset_on_next_ensure);
1053
+ if (record.reset_on_next_ensure === true) state.reset_on_next_ensure = true;
1363
1054
  assignStringState(state, "current_mode_id", record.current_mode_id);
1364
1055
  assignStringState(state, "desired_mode_id", record.desired_mode_id);
1365
- assignDesiredConfigOptions(state, record.desired_config_options);
1056
+ const desiredConfigOptions = parseStringMap(record.desired_config_options);
1057
+ if (desiredConfigOptions) state.desired_config_options = desiredConfigOptions;
1058
+ const modelNames = parseStringMap(record.available_model_names, true);
1059
+ if (modelNames) state.available_model_names = modelNames;
1366
1060
  assignParsedModelState(state, record);
1367
1061
  const availableCommands = parseAvailableCommands(record.available_commands);
1368
1062
  if (availableCommands) state.available_commands = availableCommands;
@@ -1377,60 +1071,37 @@ function assignParsedModelState(state, record) {
1377
1071
  if (configOptions) state.config_options = configOptions;
1378
1072
  if (state.model_control === void 0 && state.available_models !== void 0) state.model_control = hasModelConfigOption(state.config_options) ? "config_option" : "legacy_set_model";
1379
1073
  }
1380
- function assignBooleanTrue(state, key, value) {
1381
- if (value === true) state[key] = true;
1382
- }
1383
1074
  function assignStringState(state, key, value) {
1384
1075
  if (typeof value === "string") state[key] = value;
1385
1076
  }
1386
- function assignDesiredConfigOptions(state, raw) {
1387
- const desiredConfigOptions = asRecord$4(raw);
1388
- if (!desiredConfigOptions) return;
1389
- const parsed = Object.fromEntries(Object.entries(desiredConfigOptions).filter((entry) => {
1390
- const [, value] = entry;
1391
- return typeof value === "string";
1392
- }));
1393
- if (Object.keys(parsed).length > 0) state.desired_config_options = parsed;
1077
+ function parseStringMap(raw, preserveEmpty = false) {
1078
+ const record = asRecord$5(raw);
1079
+ if (!record) return;
1080
+ const parsed = Object.fromEntries(Object.entries(record).filter((entry) => typeof entry[1] === "string"));
1081
+ return Object.keys(parsed).length > 0 || preserveEmpty ? parsed : void 0;
1394
1082
  }
1395
1083
  function assignParsedSessionOptions(state, raw) {
1396
- const sessionOptions = asRecord$4(raw);
1084
+ const sessionOptions = asRecord$5(raw);
1397
1085
  if (!sessionOptions) return;
1398
1086
  const parsedSessionOptions = {};
1399
- assignSessionOptionModel(parsedSessionOptions, sessionOptions.model);
1400
- assignSessionOptionAllowedTools(parsedSessionOptions, sessionOptions.allowed_tools);
1401
- assignSessionOptionMaxTurns(parsedSessionOptions, sessionOptions.max_turns);
1087
+ if (typeof sessionOptions.model === "string") parsedSessionOptions.model = sessionOptions.model;
1088
+ if (isStringArray(sessionOptions.allowed_tools)) parsedSessionOptions.allowed_tools = [...sessionOptions.allowed_tools];
1089
+ if (isPositiveInteger(sessionOptions.max_turns)) parsedSessionOptions.max_turns = sessionOptions.max_turns;
1402
1090
  assignSessionOptionSystemPrompt(parsedSessionOptions, sessionOptions.system_prompt);
1403
- assignSessionOptionEnv(parsedSessionOptions, sessionOptions.env);
1091
+ const env = parseStringMap(sessionOptions.env);
1092
+ if (env) parsedSessionOptions.env = env;
1404
1093
  if (Object.keys(parsedSessionOptions).length > 0) state.session_options = parsedSessionOptions;
1405
1094
  }
1406
- function assignSessionOptionModel(options, value) {
1407
- if (typeof value === "string") options.model = value;
1408
- }
1409
- function assignSessionOptionAllowedTools(options, value) {
1410
- if (isStringArray(value)) options.allowed_tools = [...value];
1411
- }
1412
- function assignSessionOptionMaxTurns(options, value) {
1413
- if (typeof value === "number" && Number.isInteger(value) && value > 0) options.max_turns = value;
1414
- }
1415
1095
  function assignSessionOptionSystemPrompt(options, value) {
1416
1096
  if (typeof value === "string" && value.length > 0) {
1417
1097
  options.system_prompt = value;
1418
1098
  return;
1419
1099
  }
1420
- const appendRecord = asRecord$4(value);
1100
+ const appendRecord = asRecord$5(value);
1421
1101
  if (appendRecord && typeof appendRecord.append === "string" && appendRecord.append.length > 0) options.system_prompt = { append: appendRecord.append };
1422
1102
  }
1423
- function assignSessionOptionEnv(options, value) {
1424
- const env = asRecord$4(value);
1425
- if (!env) return;
1426
- const parsed = Object.fromEntries(Object.entries(env).filter((entry) => {
1427
- const [, raw] = entry;
1428
- return typeof raw === "string";
1429
- }));
1430
- if (Object.keys(parsed).length > 0) options.env = parsed;
1431
- }
1432
1103
  function parseEventLog(raw, sessionId) {
1433
- const record = asRecord$4(raw);
1104
+ const record = asRecord$5(raw);
1434
1105
  if (!record || !hasValidEventLogCore(record)) return defaultSessionEventLog(sessionId);
1435
1106
  return {
1436
1107
  active_path: record.active_path,
@@ -1449,7 +1120,7 @@ function isPositiveInteger(value) {
1449
1120
  }
1450
1121
  function parseImportedFrom(raw) {
1451
1122
  if (raw == null) return;
1452
- const record = asRecord$4(raw);
1123
+ const record = asRecord$5(raw);
1453
1124
  if (!record || typeof record.record_id !== "string" || typeof record.cwd_original !== "string" || typeof record.exported_by !== "string" || typeof record.exported_at !== "string") return null;
1454
1125
  return {
1455
1126
  recordId: record.record_id,
@@ -1476,8 +1147,7 @@ function normalizeOptionalName(value) {
1476
1147
  }
1477
1148
  function normalizeOptionalPid(value) {
1478
1149
  if (value == null) return;
1479
- if (!Number.isInteger(value) || value <= 0) return null;
1480
- return value;
1150
+ return isPositiveInteger(value) ? value : null;
1481
1151
  }
1482
1152
  function normalizeOptionalBoolean(value, fallback = false) {
1483
1153
  if (value == null) return fallback;
@@ -1500,7 +1170,7 @@ function normalizeOptionalSignal(value) {
1500
1170
  return Symbol("invalid");
1501
1171
  }
1502
1172
  function parseSessionRecord(raw) {
1503
- const record = asRecord$4(raw);
1173
+ const record = asRecord$5(raw);
1504
1174
  if (!record) return null;
1505
1175
  if (record.schema !== "acpx.session.v1") return null;
1506
1176
  const optionals = validSessionOptionals({
@@ -1525,7 +1195,7 @@ function parseSessionRecord(raw) {
1525
1195
  schema: SESSION_RECORD_SCHEMA,
1526
1196
  acpxRecordId: record.acpx_record_id,
1527
1197
  acpSessionId: record.acp_session_id,
1528
- agentSessionId: normalizeRuntimeSessionId(record.agent_session_id),
1198
+ agentSessionId: normalizeAgentSessionId(record.agent_session_id),
1529
1199
  agentCommand: record.agent_command,
1530
1200
  agentArgv: parsePersistedAgentArgv(record),
1531
1201
  cwd: record.cwd,
@@ -1545,7 +1215,7 @@ function parseSessionRecord(raw) {
1545
1215
  lastAgentExitAt: optionals.lastAgentExitAt,
1546
1216
  lastAgentDisconnectReason: optionals.lastAgentDisconnectReason,
1547
1217
  protocolVersion: typeof record.protocol_version === "number" ? record.protocol_version : void 0,
1548
- agentCapabilities: asRecord$4(record.agent_capabilities),
1218
+ agentCapabilities: asRecord$5(record.agent_capabilities),
1549
1219
  title: conversation.title,
1550
1220
  messages: conversation.messages,
1551
1221
  updated_at: conversation.updated_at,
@@ -1669,7 +1339,8 @@ const ZED_TAG_KEYS = /* @__PURE__ */ new Set([
1669
1339
  const MAP_OBJECT_PATHS = /* @__PURE__ */ new Set([
1670
1340
  "request_token_usage",
1671
1341
  "messages.Agent.tool_results",
1672
- "acpx.session_options.env"
1342
+ "acpx.session_options.env",
1343
+ "acpx.available_model_names"
1673
1344
  ]);
1674
1345
  const OPAQUE_VALUE_PATHS = /* @__PURE__ */ new Set([
1675
1346
  "agent_capabilities",
@@ -1683,10 +1354,6 @@ function isRecord(value) {
1683
1354
  function joinPath(path) {
1684
1355
  return path.join(".");
1685
1356
  }
1686
- function isAllowedKey(path, key) {
1687
- if (ZED_TAG_KEYS.has(key)) return true;
1688
- return false;
1689
- }
1690
1357
  function shouldSkipKeyRule(path) {
1691
1358
  return MAP_OBJECT_PATHS.has(joinPath(path));
1692
1359
  }
@@ -1712,7 +1379,7 @@ function collectViolations(value, path, violations) {
1712
1379
  for (const [key, child] of Object.entries(value)) collectKeyViolation(child, key, path, skipKeyRule, violations);
1713
1380
  }
1714
1381
  function collectKeyViolation(child, key, path, skipKeyRule, violations) {
1715
- if (!skipKeyRule && !SNAKE_CASE_KEY.test(key) && !isAllowedKey(path, key)) violations.push(`${joinPath(path)}.${key}`.replace(/^\./, ""));
1382
+ if (!skipKeyRule && !SNAKE_CASE_KEY.test(key) && !ZED_TAG_KEYS.has(key)) violations.push(`${joinPath(path)}.${key}`.replace(/^\./, ""));
1716
1383
  const childPath = [...path, key];
1717
1384
  if (!shouldSkipDescend(childPath)) collectViolations(child, childPath, violations);
1718
1385
  }
@@ -1727,20 +1394,14 @@ function assertPersistedKeyPolicy(value) {
1727
1394
  throw new Error(`Persisted key policy violation (expected snake_case keys): ${violations.join(", ")}`);
1728
1395
  }
1729
1396
  //#endregion
1730
- //#region src/session/persistence/atomic-write.ts
1731
- function createAtomicWriteTempPath(filePath, createUniqueId = randomUUID) {
1732
- const tempName = `.acpx-write.${process.pid}.${Date.now()}.${createUniqueId()}.tmp`;
1733
- return path.join(path.dirname(filePath), tempName);
1734
- }
1735
- //#endregion
1736
1397
  //#region src/session/persistence/index.ts
1737
1398
  const SESSION_INDEX_SCHEMA = "acpx.session-index.v1";
1738
- function asRecord$3(value) {
1399
+ function asRecord$4(value) {
1739
1400
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
1740
1401
  return value;
1741
1402
  }
1742
1403
  function parseIndexEntry(raw) {
1743
- const record = asRecord$3(raw);
1404
+ const record = asRecord$4(raw);
1744
1405
  if (!record) return;
1745
1406
  if (!hasRequiredIndexEntryFields(record)) return;
1746
1407
  if (record.name !== void 0 && typeof record.name !== "string") return;
@@ -1784,7 +1445,7 @@ async function readSessionIndex(sessionDir) {
1784
1445
  const filePath = sessionIndexPath(sessionDir);
1785
1446
  try {
1786
1447
  const payload = await fs$1.readFile(filePath, "utf8");
1787
- const record = asRecord$3(JSON.parse(payload));
1448
+ const record = asRecord$4(JSON.parse(payload));
1788
1449
  if (!record || record.schema !== SESSION_INDEX_SCHEMA || !Array.isArray(record.files)) return;
1789
1450
  const files = record.files.filter((entry) => typeof entry === "string");
1790
1451
  if (files.length !== record.files.length || !Array.isArray(record.entries)) return;
@@ -1800,18 +1461,14 @@ async function readSessionIndex(sessionDir) {
1800
1461
  }
1801
1462
  }
1802
1463
  async function writeSessionIndex(sessionDir, index) {
1803
- const filePath = sessionIndexPath(sessionDir);
1804
- const tempFile = createAtomicWriteTempPath(filePath);
1805
- const payload = JSON.stringify({
1464
+ await writePrivateJsonFile(sessionIndexPath(sessionDir), {
1806
1465
  schema: SESSION_INDEX_SCHEMA,
1807
- files: [...index.files].toSorted(),
1808
- entries: [...index.entries].toSorted((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt))
1809
- }, null, 2);
1810
- await fs$1.writeFile(tempFile, `${payload}\n`, "utf8");
1811
- await fs$1.rename(tempFile, filePath);
1466
+ files: index.files.toSorted(),
1467
+ entries: index.entries.toSorted((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt))
1468
+ });
1812
1469
  }
1813
1470
  async function rebuildSessionIndex(sessionDir) {
1814
- const files = (await fs$1.readdir(sessionDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".json") && entry.name !== "index.json").map((entry) => entry.name).toSorted();
1471
+ const files = await listSessionFiles(sessionDir);
1815
1472
  const indexEntries = [];
1816
1473
  for (const file of files) try {
1817
1474
  const payload = await fs$1.readFile(path.join(sessionDir, file), "utf8");
@@ -1828,23 +1485,26 @@ async function rebuildSessionIndex(sessionDir) {
1828
1485
  return index;
1829
1486
  }
1830
1487
  async function loadOrRebuildSessionIndex(sessionDir) {
1831
- const files = (await fs$1.readdir(sessionDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".json") && entry.name !== "index.json").map((entry) => entry.name).toSorted();
1488
+ const files = await listSessionFiles(sessionDir);
1832
1489
  const existing = await readSessionIndex(sessionDir);
1833
1490
  if (existing && existing.files.length === files.length && existing.files.every((file, index) => file === files[index])) return existing;
1834
1491
  return await rebuildSessionIndex(sessionDir);
1835
1492
  }
1493
+ async function listSessionFiles(sessionDir) {
1494
+ return (await fs$1.readdir(sessionDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".json") && entry.name !== "index.json").map((entry) => entry.name).toSorted();
1495
+ }
1836
1496
  //#endregion
1837
1497
  //#region src/session/persistence/repository.ts
1838
1498
  const DEFAULT_HISTORY_LIMIT = 20;
1839
1499
  function sessionFilePath(acpxRecordId) {
1840
- const safeId = encodeURIComponent(acpxRecordId);
1500
+ const safeId = safeSessionId(acpxRecordId);
1841
1501
  return path.join(sessionBaseDir(), `${safeId}.json`);
1842
1502
  }
1843
- function sessionBaseDir() {
1844
- return path.join(os.homedir(), ".acpx", "sessions");
1845
- }
1846
1503
  async function ensureSessionDir() {
1847
- await fs$1.mkdir(sessionBaseDir(), { recursive: true });
1504
+ await fs$1.mkdir(sessionBaseDir(), {
1505
+ recursive: true,
1506
+ mode: 448
1507
+ });
1848
1508
  }
1849
1509
  async function loadRecordFromIndexEntry(entry) {
1850
1510
  try {
@@ -1868,14 +1528,10 @@ function matchesSessionEntry(session, normalizedCwd, normalizedName, includeClos
1868
1528
  }
1869
1529
  async function writeSessionRecord(record) {
1870
1530
  await measurePerf("session.write_record", async () => {
1871
- await ensureSessionDir();
1872
1531
  const persisted = serializeSessionRecordForDisk(record);
1873
1532
  assertPersistedKeyPolicy(persisted);
1874
1533
  const file = sessionFilePath(record.acpxRecordId);
1875
- const tempFile = createAtomicWriteTempPath(file);
1876
- const payload = JSON.stringify(persisted, null, 2);
1877
- await fs$1.writeFile(tempFile, `${payload}\n`, "utf8");
1878
- await fs$1.rename(tempFile, file);
1534
+ await writePrivateJsonFile(file, persisted);
1879
1535
  const sessionDir = sessionBaseDir();
1880
1536
  const index = await loadOrRebuildSessionIndex(sessionDir);
1881
1537
  const fileName = path.basename(file);
@@ -1940,7 +1596,7 @@ function normalizeName(value) {
1940
1596
  const trimmed = value.trim();
1941
1597
  return trimmed.length > 0 ? trimmed : void 0;
1942
1598
  }
1943
- function isoNow$2() {
1599
+ function isoNow$1() {
1944
1600
  return (/* @__PURE__ */ new Date()).toISOString();
1945
1601
  }
1946
1602
  async function listSessions() {
@@ -2031,7 +1687,7 @@ function isBeforeCutoff(record, cutoffIso) {
2031
1687
  return !cutoffIso || closedAtOrLastUsedAt(record) < cutoffIso;
2032
1688
  }
2033
1689
  async function pruneSessionFiles(record, sessionDir, dirEntries, includeHistory) {
2034
- const safeId = encodeURIComponent(record.acpxRecordId);
1690
+ const safeId = safeSessionId(record.acpxRecordId);
2035
1691
  let bytesFreed = await unlinkCountingBytes(path.join(sessionDir, `${safeId}.json`));
2036
1692
  if (includeHistory) for (const name of dirEntries.filter((entry) => isSessionStreamFile(entry, safeId))) bytesFreed += await unlinkCountingBytes(path.join(sessionDir, name));
2037
1693
  return bytesFreed;
@@ -2068,10 +1724,6 @@ const WRITE_PREVIEW_MAX_CHARS = 1200;
2068
1724
  function nowIso$1() {
2069
1725
  return (/* @__PURE__ */ new Date()).toISOString();
2070
1726
  }
2071
- function isWithinRoot(rootDir, targetPath) {
2072
- const relative = path.relative(rootDir, targetPath);
2073
- return relative.length === 0 || !relative.startsWith("..") && !path.isAbsolute(relative);
2074
- }
2075
1727
  function toWritePreview(content) {
2076
1728
  const lines = content.replace(/\r\n/g, "\n").split("\n");
2077
1729
  const visibleLines = lines.slice(0, WRITE_PREVIEW_MAX_LINES);
@@ -2092,6 +1744,7 @@ function canPromptForPermission$2() {
2092
1744
  }
2093
1745
  var FileSystemHandlers = class {
2094
1746
  rootDir;
1747
+ workspace;
2095
1748
  permissionMode;
2096
1749
  nonInteractivePermissions;
2097
1750
  onOperation;
@@ -2121,7 +1774,7 @@ var FileSystemHandlers = class {
2121
1774
  });
2122
1775
  try {
2123
1776
  if (this.permissionMode === "deny-all") throw new PermissionDeniedError("Permission denied for fs/read_text_file (--deny-all)");
2124
- const content = await fs$1.readFile(filePath, "utf8");
1777
+ const content = await (await this.getWorkspace()).readText(`.${path.sep}${path.relative(this.rootDir, filePath)}`);
2125
1778
  const sliced = this.sliceContent(content, params.line, params.limit);
2126
1779
  this.emitOperation({
2127
1780
  method: "fs/read_text_file",
@@ -2156,8 +1809,12 @@ var FileSystemHandlers = class {
2156
1809
  });
2157
1810
  try {
2158
1811
  if (!await this.isWriteApproved(filePath, preview)) throw new PermissionDeniedError("Permission denied for fs/write_text_file");
2159
- await fs$1.mkdir(path.dirname(filePath), { recursive: true });
2160
- await fs$1.writeFile(filePath, params.content, "utf8");
1812
+ const file = await (await this.getWorkspace()).openWritable(`.${path.sep}${path.relative(this.rootDir, filePath)}`, { mode: 438 });
1813
+ try {
1814
+ await file.handle.writeFile(params.content, "utf8");
1815
+ } finally {
1816
+ await file.handle.close();
1817
+ }
2161
1818
  this.emitOperation({
2162
1819
  method: "fs/write_text_file",
2163
1820
  status: "completed",
@@ -2187,9 +1844,16 @@ var FileSystemHandlers = class {
2187
1844
  resolvePathWithinRoot(rawPath) {
2188
1845
  if (!path.isAbsolute(rawPath)) throw new Error(`Path must be absolute: ${rawPath}`);
2189
1846
  const resolved = path.resolve(rawPath);
2190
- if (!isWithinRoot(this.rootDir, resolved)) throw new Error(`Path is outside allowed cwd subtree: ${resolved}`);
1847
+ if (!isPathInside(this.rootDir, resolved)) throw new Error(`Path is outside allowed cwd subtree: ${resolved}`);
2191
1848
  return resolved;
2192
1849
  }
1850
+ getWorkspace() {
1851
+ return this.workspace ??= root(this.rootDir, {
1852
+ symlinks: "follow-within-root",
1853
+ hardlinks: "allow",
1854
+ maxBytes: Infinity
1855
+ });
1856
+ }
2193
1857
  sliceContent(content, line, limit) {
2194
1858
  if (line == null && limit == null) return content;
2195
1859
  const lines = content.split("\n");
@@ -2223,14 +1887,14 @@ function selected(optionId) {
2223
1887
  function cancelled() {
2224
1888
  return { outcome: { outcome: "cancelled" } };
2225
1889
  }
2226
- function withEscalationMetadata(response, event) {
1890
+ function withPermissionMetadata(response, metadata) {
2227
1891
  return {
2228
1892
  ...response,
2229
1893
  _meta: {
2230
1894
  ...response._meta,
2231
1895
  acpx: {
2232
1896
  ...response._meta?.acpx && typeof response._meta.acpx === "object" && !Array.isArray(response._meta.acpx) ? response._meta.acpx : {},
2233
- permissionEscalation: event
1897
+ ...metadata
2234
1898
  }
2235
1899
  }
2236
1900
  };
@@ -2405,7 +2069,7 @@ async function resolveEscalatingPermissionRequest(params, policyMatch, allowOpti
2405
2069
  if (canPromptForPermission$1()) return resolveInteractivePromptResult(params, allowOption, rejectOption);
2406
2070
  const escalation = buildEscalationEvent(params, policyMatch.matchedRule);
2407
2071
  return {
2408
- response: withEscalationMetadata(rejectOption ? selected(rejectOption.optionId) : cancelled(), escalation),
2072
+ response: withPermissionMetadata(rejectOption ? selected(rejectOption.optionId) : cancelled(), { permissionEscalation: escalation }),
2409
2073
  escalation
2410
2074
  };
2411
2075
  }
@@ -2467,419 +2131,180 @@ function classifyPermissionDecision(params, response) {
2467
2131
  return "denied";
2468
2132
  }
2469
2133
  //#endregion
2470
- //#region src/spawn-command-options.ts
2471
- function readWindowsEnvValue(env, key) {
2472
- const matchedKey = Object.keys(env).find((entry) => entry.toUpperCase() === key);
2473
- return matchedKey ? env[matchedKey] : void 0;
2474
- }
2475
- function windowsExecutableExtensions(env) {
2476
- return (readWindowsEnvValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
2477
- }
2478
- function commandCandidates(command, env) {
2479
- if (path.extname(command).length > 0) return [command];
2480
- return windowsExecutableExtensions(env).map((extension) => `${command}${extension}`);
2481
- }
2482
- function commandHasPath(command) {
2483
- return command.includes("/") || command.includes("\\") || path.isAbsolute(command);
2484
- }
2485
- function resolveWindowsPathCommand(command, env) {
2486
- const candidates = commandCandidates(command, env);
2487
- const pathValue = readWindowsEnvValue(env, "PATH");
2488
- if (!pathValue) return;
2489
- for (const directory of pathValue.split(";")) {
2490
- const resolved = findExistingCommandInDirectory(directory, candidates);
2491
- if (resolved) return resolved;
2492
- }
2493
- }
2494
- function findExistingCommandInDirectory(directory, candidates) {
2495
- const trimmedDirectory = directory.trim();
2496
- if (trimmedDirectory.length === 0) return;
2497
- return candidates.map((candidate) => path.join(trimmedDirectory, candidate)).find((resolved) => fs.existsSync(resolved));
2498
- }
2499
- function resolveWindowsWrapperToken(token, wrapperPath) {
2500
- const relative = token.match(/%~?dp0%?\s*[\\/]*(.*)$/i)?.[1]?.trim();
2501
- if (!relative) return;
2502
- const candidate = path.resolve(path.dirname(wrapperPath), relative.replace(/[\\/]+/g, path.sep).replace(/^[\\/]+/, ""));
2503
- return path.extname(candidate).toLowerCase() === ".exe" && fs.existsSync(candidate) ? candidate : void 0;
2504
- }
2505
- function resolveWindowsWrapperExecutable(wrapperPath) {
2506
- if (!fs.existsSync(wrapperPath)) return;
2507
- try {
2508
- return [...fs.readFileSync(wrapperPath, "utf8").matchAll(/"([^"\r\n]*)"/g)].map((match) => resolveWindowsWrapperToken(match[1] ?? "", wrapperPath)).find((candidate) => candidate !== void 0);
2509
- } catch {
2510
- return;
2134
+ //#region src/prompt-content.ts
2135
+ var PromptInputValidationError = class extends Error {
2136
+ constructor(message) {
2137
+ super(message);
2138
+ this.name = "PromptInputValidationError";
2511
2139
  }
2140
+ };
2141
+ function asRecord$3(value) {
2142
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
2143
+ return value;
2512
2144
  }
2513
- function resolveWindowsCommand(command, env = process.env) {
2514
- const candidates = commandCandidates(command, env);
2515
- if (commandHasPath(command)) return candidates.find((candidate) => fs.existsSync(candidate));
2516
- return resolveWindowsPathCommand(command, env);
2145
+ function isNonEmptyString(value) {
2146
+ return typeof value === "string" && value.trim().length > 0;
2517
2147
  }
2518
- /**
2519
- * Resolve a Windows command to a native executable suitable for direct spawn.
2520
- *
2521
- * Batch and PowerShell shims are intentionally rejected unless they point at a
2522
- * real `.exe` entrypoint. Callers that need shell execution should use the
2523
- * command-specific shell policy instead.
2524
- */
2525
- function resolveWindowsExecutablePath(command, env = process.env) {
2526
- const resolved = resolveWindowsCommand(command, env);
2527
- if (!resolved) return;
2528
- const absolute = path.resolve(resolved);
2529
- const extension = path.extname(absolute).toLowerCase();
2530
- if (extension === ".exe") return absolute;
2531
- if (extension !== ".cmd" && extension !== ".bat" && extension !== ".ps1") return;
2532
- const siblingExecutable = `${absolute.slice(0, -extension.length)}.exe`;
2533
- return fs.existsSync(siblingExecutable) ? siblingExecutable : resolveWindowsWrapperExecutable(absolute);
2534
- }
2535
- function shouldUseWindowsBatchShell(command, platform = process.platform, env = process.env) {
2536
- if (platform !== "win32") return false;
2537
- const resolvedCommand = resolveWindowsCommand(command, env) ?? command;
2538
- const ext = path.extname(resolvedCommand).toLowerCase();
2539
- return ext === ".cmd" || ext === ".bat";
2540
- }
2541
- const CMD_META_CHAR_RE = /([()\][%!^"`<>&|;, *?])/gu;
2542
- const CMD_BACKSLASH_QUOTE_RE = /(?=(\\+?)?)\1"/gu;
2543
- const CMD_TRAILING_BACKSLASH_RE = /(?=(\\+?)?)\1$/gu;
2544
- const CMD_SHIM_RE = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/iu;
2545
- function escapeCmdCommand(value) {
2546
- return value.replace(CMD_META_CHAR_RE, "^$1");
2547
- }
2548
- function escapeCmdArgument(value, doubleEscapeMeta) {
2549
- const escaped = `"${value.replace(CMD_BACKSLASH_QUOTE_RE, "$1$1\\\"").replace(CMD_TRAILING_BACKSLASH_RE, "$1$1")}"`.replace(CMD_META_CHAR_RE, "^$1");
2550
- return doubleEscapeMeta ? escaped.replace(CMD_META_CHAR_RE, "^$1") : escaped;
2551
- }
2552
- function buildAgentSpawnCommand(command, args, platform = process.platform, env = process.env) {
2553
- if (!shouldUseWindowsBatchShell(command, platform, env)) return {
2554
- command,
2555
- args: [...args]
2556
- };
2557
- const resolvedCommand = path.win32.normalize(resolveWindowsCommand(command, env) ?? command);
2558
- const doubleEscapeMeta = CMD_SHIM_RE.test(resolvedCommand);
2559
- const shellCommand = [escapeCmdCommand(resolvedCommand), ...args.map((arg) => escapeCmdArgument(arg, doubleEscapeMeta))].join(" ");
2560
- return {
2561
- command: readWindowsEnvValue(env, "COMSPEC") ?? "cmd.exe",
2562
- args: [
2563
- "/d",
2564
- "/s",
2565
- "/c",
2566
- `"${shellCommand}"`
2567
- ],
2568
- windowsVerbatimArguments: true
2569
- };
2148
+ function isBase64Data(value) {
2149
+ if (value.length === 0 || value.length % 4 !== 0) return false;
2150
+ return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
2570
2151
  }
2571
- function buildSpawnCommandOptions(command, options, platform = process.platform, env = process.env) {
2572
- if (!shouldUseWindowsBatchShell(command, platform, env)) return options;
2573
- return {
2574
- ...options,
2575
- shell: true
2576
- };
2152
+ function isImageMimeType(value) {
2153
+ return /^image\/[A-Za-z0-9.+-]+$/i.test(value);
2577
2154
  }
2578
- function buildTerminalSpawnCommand(command, args) {
2579
- return {
2580
- command,
2581
- args: args ?? [],
2582
- killProcessGroup: false
2583
- };
2155
+ function isAudioMimeType(value) {
2156
+ return /^audio\/[A-Za-z0-9.+-]+$/i.test(value);
2584
2157
  }
2585
- function buildTerminalShellSpawnCommand(command, platform = process.platform) {
2586
- if (platform === "win32") return {
2587
- command: "cmd.exe",
2588
- args: [
2589
- "/d",
2590
- "/s",
2591
- "/c",
2592
- command
2593
- ],
2594
- killProcessGroup: true
2595
- };
2596
- return {
2597
- command: "/bin/sh",
2598
- args: ["-c", command],
2599
- killProcessGroup: true
2600
- };
2158
+ function isTextBlock(value) {
2159
+ const record = asRecord$3(value);
2160
+ return record?.type === "text" && typeof record.text === "string";
2601
2161
  }
2602
- //#endregion
2603
- //#region src/version.ts
2604
- const UNKNOWN_VERSION = "0.0.0-unknown";
2605
- const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
2606
- let cachedVersion = null;
2607
- function parseVersion(value) {
2608
- if (typeof value !== "string") return null;
2609
- const trimmed = value.trim();
2610
- return trimmed.length > 0 ? trimmed : null;
2162
+ function isImageBlock(value) {
2163
+ const record = asRecord$3(value);
2164
+ return record?.type === "image" && isNonEmptyString(record.mimeType) && isImageMimeType(record.mimeType) && typeof record.data === "string" && isBase64Data(record.data);
2611
2165
  }
2612
- function readPackageVersion(packageJsonPath) {
2613
- try {
2614
- return parseVersion(JSON.parse(readFileSync(packageJsonPath, "utf8")).version);
2615
- } catch {
2616
- return null;
2617
- }
2166
+ function isAudioBlock(value) {
2167
+ const record = asRecord$3(value);
2168
+ return record?.type === "audio" && isNonEmptyString(record.mimeType) && isAudioMimeType(record.mimeType) && typeof record.data === "string" && isBase64Data(record.data);
2618
2169
  }
2619
- function resolveVersionFromAncestors(startDir) {
2620
- let current = startDir;
2621
- while (true) {
2622
- const packageVersion = readPackageVersion(path.join(current, "package.json"));
2623
- if (packageVersion) return packageVersion;
2624
- const parent = path.dirname(current);
2625
- if (parent === current) return null;
2626
- current = parent;
2627
- }
2170
+ function isResourceLinkBlock(value) {
2171
+ const record = asRecord$3(value);
2172
+ return record?.type === "resource_link" && isNonEmptyString(record.uri) && (record.title === void 0 || typeof record.title === "string") && (record.name === void 0 || typeof record.name === "string");
2628
2173
  }
2629
- function resolveAcpxVersion(params) {
2630
- const envVersion = resolvePackageEnvVersion(params?.env ?? process.env);
2631
- if (envVersion) return envVersion;
2632
- if (params?.packageJsonPath) return readPackageVersion(params.packageJsonPath) ?? UNKNOWN_VERSION;
2633
- return resolveVersionFromAncestors(MODULE_DIR) ?? UNKNOWN_VERSION;
2174
+ function isResourcePayload(value) {
2175
+ const record = asRecord$3(value);
2176
+ if (!record || !isNonEmptyString(record.uri)) return false;
2177
+ return record.text === void 0 || typeof record.text === "string";
2634
2178
  }
2635
- function resolvePackageEnvVersion(env) {
2636
- const envPackageName = parseVersion(env.npm_package_name);
2637
- const envVersion = parseVersion(env.npm_package_version);
2638
- return envPackageName === "acpx" ? envVersion : null;
2179
+ function isResourceBlock(value) {
2180
+ const record = asRecord$3(value);
2181
+ return record?.type === "resource" && isResourcePayload(record.resource);
2639
2182
  }
2640
- function getAcpxVersion() {
2641
- if (cachedVersion) return cachedVersion;
2642
- cachedVersion = resolveAcpxVersion();
2643
- return cachedVersion;
2183
+ const CONTENT_BLOCK_VALIDATORS = [
2184
+ isTextBlock,
2185
+ isImageBlock,
2186
+ isAudioBlock,
2187
+ isResourceLinkBlock,
2188
+ isResourceBlock
2189
+ ];
2190
+ function isContentBlock(value) {
2191
+ return CONTENT_BLOCK_VALIDATORS.some((validator) => validator(value));
2644
2192
  }
2645
- //#endregion
2646
- //#region src/acp/client-process.ts
2647
- const PROCESS_HELPER_TIMEOUT_MS = 8e3;
2648
- async function runTimedExecFile(command, args, options = {}) {
2649
- const timeoutMs = Math.max(1, Math.round(options.timeoutMs ?? 8e3));
2650
- return await new Promise((resolve, reject) => {
2651
- let settled = false;
2652
- let timer;
2653
- const child = execFile(command, [...args], {
2654
- encoding: "utf8",
2655
- maxBuffer: options.maxBufferBytes ?? 33554432,
2656
- killSignal: "SIGKILL",
2657
- windowsHide: options.windowsHide
2658
- }, (error, stdout) => {
2659
- if (settled) return;
2660
- settled = true;
2661
- if (timer) clearTimeout(timer);
2662
- if (error) {
2663
- reject(error);
2664
- return;
2665
- }
2666
- resolve(stdout);
2667
- });
2668
- timer = setTimeout(() => {
2669
- if (settled) return;
2670
- settled = true;
2671
- const killed = child.kill("SIGKILL");
2672
- child.stdout?.destroy();
2673
- child.stderr?.destroy();
2674
- child.unref();
2675
- reject(Object.assign(/* @__PURE__ */ new Error(`${command} timed out after ${timeoutMs}ms`), {
2676
- code: "ETIMEDOUT",
2677
- killed,
2678
- signal: "SIGKILL"
2679
- }));
2680
- }, timeoutMs);
2681
- });
2193
+ const CONTENT_BLOCK_ERROR_VALIDATORS = {
2194
+ text: validateTextContentBlock,
2195
+ image: validateImageContentBlock,
2196
+ audio: validateAudioContentBlock,
2197
+ resource_link: validateResourceLinkContentBlock,
2198
+ resource: validateResourceContentBlock
2199
+ };
2200
+ function contentBlockErrorValidator(type) {
2201
+ return Object.hasOwn(CONTENT_BLOCK_ERROR_VALIDATORS, type) ? CONTENT_BLOCK_ERROR_VALIDATORS[type] : void 0;
2682
2202
  }
2683
- function normalizeAgentCommandInput(value) {
2684
- if (typeof value === "string") return { agentCommand: value };
2685
- const parts = toCommandParts([...value]);
2686
- const argv = [parts.command, ...parts.args];
2687
- return {
2688
- agentCommand: renderArgvIdentity(argv),
2689
- agentArgv: argv
2690
- };
2203
+ function validateTextContentBlock(record, index) {
2204
+ return typeof record.text === "string" ? void 0 : `prompt[${index}] text block must include a string text field`;
2691
2205
  }
2692
- const IDENTITY_SAFE_ARG_RE = /^[A-Za-z0-9_@%+=:,./^~-]+$/u;
2693
- function renderArgvIdentity(argv) {
2694
- return argv.map((arg) => IDENTITY_SAFE_ARG_RE.test(arg) ? arg : JSON.stringify(arg)).join(" ");
2206
+ function validateImageContentBlock(record, index) {
2207
+ if (!isNonEmptyString(record.mimeType)) return `prompt[${index}] image block must include a non-empty mimeType`;
2208
+ if (!isImageMimeType(record.mimeType)) return `prompt[${index}] image block mimeType must start with image/`;
2209
+ if (typeof record.data !== "string" || record.data.length === 0) return `prompt[${index}] image block must include non-empty base64 data`;
2210
+ return isBase64Data(record.data) ? void 0 : `prompt[${index}] image block data must be valid base64`;
2695
2211
  }
2696
- function isoNow$1() {
2697
- return (/* @__PURE__ */ new Date()).toISOString();
2212
+ function validateAudioContentBlock(record, index) {
2213
+ if (!isNonEmptyString(record.mimeType)) return `prompt[${index}] audio block must include a non-empty mimeType`;
2214
+ if (!isAudioMimeType(record.mimeType)) return `prompt[${index}] audio block mimeType must start with audio/`;
2215
+ if (typeof record.data !== "string" || record.data.length === 0) return `prompt[${index}] audio block must include non-empty base64 data`;
2216
+ return isBase64Data(record.data) ? void 0 : `prompt[${index}] audio block data must be valid base64`;
2698
2217
  }
2699
- function waitForSpawn$1(child) {
2700
- return new Promise((resolve, reject) => {
2701
- const onSpawn = () => {
2702
- child.off("error", onError);
2703
- resolve();
2704
- };
2705
- const onError = (error) => {
2706
- child.off("spawn", onSpawn);
2707
- reject(error);
2708
- };
2709
- child.once("spawn", onSpawn);
2710
- child.once("error", onError);
2711
- });
2218
+ function validateResourceLinkContentBlock(record, index) {
2219
+ if (!isNonEmptyString(record.uri)) return `prompt[${index}] resource_link block must include a non-empty uri`;
2220
+ if (record.title !== void 0 && typeof record.title !== "string") return `prompt[${index}] resource_link block title must be a string when present`;
2221
+ if (record.name !== void 0 && typeof record.name !== "string") return `prompt[${index}] resource_link block name must be a string when present`;
2222
+ }
2223
+ function validateResourceContentBlock(record, index) {
2224
+ if (!asRecord$3(record.resource)) return `prompt[${index}] resource block must include a resource object`;
2225
+ return isResourcePayload(record.resource) ? void 0 : `prompt[${index}] resource block resource must include a non-empty uri and optional text`;
2712
2226
  }
2713
- function isChildProcessRunning(child) {
2714
- return child.exitCode == null && child.signalCode == null;
2227
+ function getContentBlockValidationError(value, index) {
2228
+ const record = asRecord$3(value);
2229
+ if (!record || typeof record.type !== "string") return `prompt[${index}] must be an ACP content block object`;
2230
+ const validator = contentBlockErrorValidator(record.type);
2231
+ return validator ? validator(record, index) : `prompt[${index}] has unsupported content block type ${JSON.stringify(record.type)}`;
2715
2232
  }
2716
- function requireAgentStdio(child) {
2717
- if (!child.stdin || !child.stdout || !child.stderr) throw new Error("ACP agent must be spawned with piped stdin/stdout/stderr");
2718
- return child;
2233
+ function isPromptInput(value) {
2234
+ return Array.isArray(value) && value.every((entry) => isContentBlock(entry));
2719
2235
  }
2720
- function waitForChildExit(child, timeoutMs) {
2721
- if (!isChildProcessRunning(child)) return Promise.resolve(true);
2722
- return new Promise((resolve) => {
2723
- let settled = false;
2724
- const timer = setTimeout(() => {
2725
- finish(false);
2726
- }, Math.max(0, timeoutMs));
2727
- const finish = (value) => {
2728
- if (settled) return;
2729
- settled = true;
2730
- child.off("close", onExitLike);
2731
- child.off("exit", onExitLike);
2732
- clearTimeout(timer);
2733
- resolve(value);
2236
+ function promptCapabilityRequirement(block) {
2237
+ switch (block.type) {
2238
+ case "image": return {
2239
+ blockType: "image",
2240
+ capability: "image"
2734
2241
  };
2735
- const onExitLike = () => {
2736
- finish(true);
2242
+ case "audio": return {
2243
+ blockType: "audio",
2244
+ capability: "audio"
2737
2245
  };
2738
- child.once("close", onExitLike);
2739
- child.once("exit", onExitLike);
2740
- });
2741
- }
2742
- function resolveAgentCommandParts(value, argv, platform = process.platform) {
2743
- if (argv) {
2744
- const parts = toCommandParts([...argv]);
2745
- assertWindowsLaunchableCommand(parts.command, platform);
2746
- return parts;
2747
- }
2748
- if (platform === "win32") throw new Error("Raw agent command strings are not supported on Windows. Configure the agent with an argv array, for example: \"argv\": [\"agent.exe\", \"--acp\"]. Legacy agents.<name>.args arrays are migrated automatically. Existing sessions without saved argv must be closed and recreated.");
2749
- return splitCommandLine(value);
2750
- }
2751
- function assertWindowsLaunchableCommand(command, platform) {
2752
- if (platform === "win32" && path.extname(command).toLowerCase() === ".sh") throw new Error(`Windows cannot launch shell script executable "${command}" directly. Configure an explicit interpreter argv, for example: "argv": ["bash", "${command}"]. acpx does not infer interpreters.`);
2753
- }
2754
- function splitCommandLine(value) {
2755
- const parts = [];
2756
- let current = "";
2757
- let quote = null;
2758
- let escaping = false;
2759
- let hasPart = false;
2760
- for (const ch of value) {
2761
- const next = readCommandLineChar({
2762
- ch,
2763
- current,
2764
- quote,
2765
- escaping,
2766
- parts,
2767
- hasPart
2768
- });
2769
- current = next.current;
2770
- quote = next.quote;
2771
- escaping = next.escaping;
2772
- hasPart = next.hasPart;
2773
- }
2774
- if (escaping) {
2775
- current += "\\";
2776
- hasPart = true;
2777
- }
2778
- if (quote) throw new Error("Invalid --agent command: unterminated quote");
2779
- if (hasPart) parts.push(current);
2780
- return toCommandParts(parts);
2781
- }
2782
- function toCommandParts(parts) {
2783
- if (parts.length === 0 || parts[0] === "") throw new Error("Invalid --agent command: empty command");
2784
- return {
2785
- command: parts[0],
2786
- args: parts.slice(1)
2787
- };
2788
- }
2789
- function readCommandLineChar(state) {
2790
- if (state.escaping) return {
2791
- current: state.current + state.ch,
2792
- quote: state.quote,
2793
- escaping: false,
2794
- hasPart: true
2795
- };
2796
- if (state.ch === "\\" && state.quote !== "'") return {
2797
- current: state.current,
2798
- quote: state.quote,
2799
- escaping: true,
2800
- hasPart: state.hasPart
2801
- };
2802
- if (state.quote) return readQuotedCommandLineChar({
2803
- ch: state.ch,
2804
- current: state.current,
2805
- quote: state.quote,
2806
- hasPart: state.hasPart
2807
- });
2808
- return readUnquotedCommandLineChar(state);
2809
- }
2810
- function readQuotedCommandLineChar(state) {
2811
- if (state.ch === state.quote) return {
2812
- current: state.current,
2813
- quote: null,
2814
- escaping: false,
2815
- hasPart: true
2816
- };
2817
- return {
2818
- current: state.current + state.ch,
2819
- quote: state.quote,
2820
- escaping: false,
2821
- hasPart: true
2822
- };
2823
- }
2824
- function readUnquotedCommandLineChar(state) {
2825
- if (state.ch === "'" || state.ch === "\"") return {
2826
- current: state.current,
2827
- quote: state.ch,
2828
- escaping: false,
2829
- hasPart: true
2830
- };
2831
- if (/\s/.test(state.ch)) {
2832
- flushCommandLinePart(state.parts, state.current, state.hasPart);
2833
- return {
2834
- current: "",
2835
- quote: null,
2836
- escaping: false,
2837
- hasPart: false
2246
+ case "resource": return {
2247
+ blockType: "resource",
2248
+ capability: "embeddedContext"
2838
2249
  };
2250
+ default: return;
2839
2251
  }
2840
- return {
2841
- current: state.current + state.ch,
2842
- quote: null,
2843
- escaping: false,
2844
- hasPart: true
2845
- };
2846
- }
2847
- function flushCommandLinePart(parts, current, hasPart) {
2848
- if (hasPart) parts.push(current);
2849
2252
  }
2850
- function asAbsoluteCwd(cwd) {
2851
- return path.resolve(cwd);
2253
+ function getUnsupportedPromptContentMessage(prompt, agentCapabilities) {
2254
+ for (const [index, block] of prompt.entries()) {
2255
+ const requirement = promptCapabilityRequirement(block);
2256
+ if (!requirement) continue;
2257
+ if (agentCapabilities?.promptCapabilities?.[requirement.capability] === true) continue;
2258
+ return `prompt[${index}] ${requirement.blockType} content requires agentCapabilities.promptCapabilities.${requirement.capability}`;
2259
+ }
2852
2260
  }
2853
- async function resolveAgentSessionCwd(cwd, agentCommand, options = {}) {
2854
- const resolved = asAbsoluteCwd(cwd);
2855
- if (!shouldTranslateWslWindowsCwd(agentCommand, options)) return resolved;
2856
- const translated = (await (options.runWslpath ?? runWslpath)(resolved)).trim();
2857
- if (!translated) throw new Error(`wslpath returned an empty Windows path for cwd: ${resolved}`);
2858
- return translated;
2261
+ function textPrompt(text) {
2262
+ return [{
2263
+ type: "text",
2264
+ text
2265
+ }];
2859
2266
  }
2860
- function shouldTranslateWslWindowsCwd(agentCommand, options) {
2861
- if (!isWsl(options)) return false;
2267
+ function parseStructuredPrompt(source) {
2268
+ if (!source.startsWith("[")) return;
2862
2269
  try {
2863
- const { command } = splitCommandLine(agentCommand);
2864
- return isWindowsExecutableCommand(command);
2865
- } catch {
2866
- return false;
2270
+ const parsed = JSON.parse(source);
2271
+ if (isPromptInput(parsed)) return parsed;
2272
+ if (Array.isArray(parsed)) throw new PromptInputValidationError(parsed.map((entry, index) => getContentBlockValidationError(entry, index)).find((message) => message !== void 0) ?? "Structured prompt JSON must be an array of valid ACP content blocks");
2273
+ return;
2274
+ } catch (error) {
2275
+ if (error instanceof PromptInputValidationError) throw error;
2276
+ return;
2867
2277
  }
2868
2278
  }
2869
- function isWsl(options) {
2870
- if ((options.platform ?? process.platform) !== "linux") return false;
2871
- return (options.existsSync ?? fs.existsSync)("/proc/sys/fs/binfmt_misc/WSLInterop");
2279
+ function parsePromptSource(source) {
2280
+ const trimmed = source.trim();
2281
+ const structured = parseStructuredPrompt(trimmed);
2282
+ if (structured) return structured;
2283
+ if (!trimmed) return [];
2284
+ return textPrompt(trimmed);
2285
+ }
2286
+ function mergePromptSourceWithText(source, suffixText) {
2287
+ const prompt = parsePromptSource(source);
2288
+ const appended = suffixText.trim();
2289
+ if (!appended) return prompt;
2290
+ if (prompt.length === 0) return textPrompt(appended);
2291
+ return [...prompt, ...textPrompt(appended)];
2872
2292
  }
2873
- const WINDOWS_EXECUTABLE_EXTENSION_RE = /\.(?:exe|cmd|bat)$/u;
2874
- function isWindowsExecutableCommand(command) {
2875
- const normalized = command.toLowerCase();
2876
- return WINDOWS_EXECUTABLE_EXTENSION_RE.test(normalized);
2293
+ function promptToDisplayText(prompt) {
2294
+ return prompt.map((block) => contentBlockDisplayText(block)).filter((entry) => entry.trim().length > 0).join("\n\n").trim();
2877
2295
  }
2878
- async function runWslpath(cwd) {
2879
- return await runTimedExecFile("wslpath", ["-w", cwd]);
2296
+ function contentBlockDisplayText(block) {
2297
+ switch (block.type) {
2298
+ case "text": return block.text;
2299
+ case "resource_link": return block.title ?? block.name ?? block.uri;
2300
+ case "resource": return resourceBlockDisplayText(block);
2301
+ case "image": return `[image] ${block.mimeType}`;
2302
+ case "audio": return `[audio] ${block.mimeType}`;
2303
+ default: return "";
2304
+ }
2880
2305
  }
2881
- function basenameToken(value) {
2882
- return path.basename(value).toLowerCase().replace(/\.(cmd|exe|bat)$/u, "");
2306
+ function resourceBlockDisplayText(block) {
2307
+ return "text" in block.resource && typeof block.resource.text === "string" ? block.resource.text : block.resource.uri;
2883
2308
  }
2884
2309
  //#endregion
2885
2310
  //#region src/acp/agent-command.ts
@@ -3207,6 +2632,82 @@ function buildAgentSpawnOptions(cwd, authCredentials, sessionEnv, agentProcessEn
3207
2632
  };
3208
2633
  }
3209
2634
  //#endregion
2635
+ //#region src/version.ts
2636
+ const UNKNOWN_VERSION = "0.0.0-unknown";
2637
+ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
2638
+ let cachedVersion = null;
2639
+ function parseVersion(value) {
2640
+ if (typeof value !== "string") return null;
2641
+ const trimmed = value.trim();
2642
+ return trimmed.length > 0 ? trimmed : null;
2643
+ }
2644
+ function readPackageVersion(packageJsonPath) {
2645
+ try {
2646
+ return parseVersion(JSON.parse(readFileSync(packageJsonPath, "utf8")).version);
2647
+ } catch {
2648
+ return null;
2649
+ }
2650
+ }
2651
+ function resolveVersionFromAncestors(startDir) {
2652
+ let current = startDir;
2653
+ while (true) {
2654
+ const packageVersion = readPackageVersion(path.join(current, "package.json"));
2655
+ if (packageVersion) return packageVersion;
2656
+ const parent = path.dirname(current);
2657
+ if (parent === current) return null;
2658
+ current = parent;
2659
+ }
2660
+ }
2661
+ function resolveAcpxVersion(params) {
2662
+ const envVersion = resolvePackageEnvVersion(params?.env ?? process.env);
2663
+ if (envVersion) return envVersion;
2664
+ if (params?.packageJsonPath) return readPackageVersion(params.packageJsonPath) ?? UNKNOWN_VERSION;
2665
+ return resolveVersionFromAncestors(MODULE_DIR) ?? UNKNOWN_VERSION;
2666
+ }
2667
+ function resolvePackageEnvVersion(env) {
2668
+ const envPackageName = parseVersion(env.npm_package_name);
2669
+ const envVersion = parseVersion(env.npm_package_version);
2670
+ return envPackageName === "acpx" ? envVersion : null;
2671
+ }
2672
+ function getAcpxVersion() {
2673
+ if (cachedVersion) return cachedVersion;
2674
+ cachedVersion = resolveAcpxVersion();
2675
+ return cachedVersion;
2676
+ }
2677
+ //#endregion
2678
+ //#region src/acp/client-protocol.ts
2679
+ const DEVIN_COMPATIBILITY_CLIENT_CAPABILITIES_META = Object.freeze({ "cognition.ai/requestDiagnostics": true });
2680
+ const DEVIN_COMPATIBILITY_CLIENT_NAME = "windsurf";
2681
+ const DEFAULT_DEVIN_COMPATIBILITY_CLIENT_VERSION = "1.110.1";
2682
+ function resolveClientInfo(devinAcp) {
2683
+ if (!devinAcp) return {
2684
+ name: "acpx",
2685
+ version: getAcpxVersion()
2686
+ };
2687
+ return {
2688
+ name: DEVIN_COMPATIBILITY_CLIENT_NAME,
2689
+ version: process.env.ACPX_DEVIN_WINDSURF_VERSION ?? DEFAULT_DEVIN_COMPATIBILITY_CLIENT_VERSION
2690
+ };
2691
+ }
2692
+ function resolveClientCapabilities(params) {
2693
+ const baseCapabilities = {
2694
+ fs: {
2695
+ readTextFile: params.fs,
2696
+ writeTextFile: params.fs
2697
+ },
2698
+ terminal: params.terminal,
2699
+ ...params.elicitationModes.length > 0 ? { elicitation: {
2700
+ ...params.elicitationModes.includes("form") ? { form: {} } : {},
2701
+ ...params.elicitationModes.includes("url") ? { url: {} } : {}
2702
+ } } : {}
2703
+ };
2704
+ if (!params.devinAcp) return baseCapabilities;
2705
+ return {
2706
+ ...baseCapabilities,
2707
+ _meta: DEVIN_COMPATIBILITY_CLIENT_CAPABILITIES_META
2708
+ };
2709
+ }
2710
+ //#endregion
3210
2711
  //#region src/acp/model-support.ts
3211
2712
  const REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE = "ACP_MODEL_UNSUPPORTED";
3212
2713
  const REQUESTED_MODEL_UNSUPPORTED_REASONS = ["missing-capability", "unadvertised-model"];
@@ -3261,9 +2762,8 @@ function parseAvailableModels(value) {
3261
2762
  function isModelSelectOption(option) {
3262
2763
  return option.type === "select" && (option.category === "model" || option.id === "model");
3263
2764
  }
3264
- function parseModelConfigOption(value) {
3265
- const option = asRecord$2(value);
3266
- if (!option || !isModelSelectOption(option) || typeof option.id !== "string" || typeof option.currentValue !== "string") return;
2765
+ function parseModelConfigOption(option) {
2766
+ if (!isModelSelectOption(option) || typeof option.id !== "string" || typeof option.currentValue !== "string") return;
3267
2767
  const availableModels = parseAvailableModels(option.options);
3268
2768
  return availableModels ? {
3269
2769
  configId: option.id,
@@ -3271,12 +2771,26 @@ function parseModelConfigOption(value) {
3271
2771
  availableModels
3272
2772
  } : void 0;
3273
2773
  }
2774
+ function modelConfigPriority(option) {
2775
+ if (option.category !== "model") return 0;
2776
+ return option.id === "model" ? 2 : 1;
2777
+ }
3274
2778
  function modelStateFromConfigOptions(configOptions) {
3275
2779
  if (!Array.isArray(configOptions)) return;
2780
+ let selected;
2781
+ let selectedPriority = -1;
3276
2782
  for (const value of configOptions) {
3277
- const models = parseModelConfigOption(value);
3278
- if (models) return models;
2783
+ const option = asRecord$2(value);
2784
+ if (!option) continue;
2785
+ const models = parseModelConfigOption(option);
2786
+ if (!models) continue;
2787
+ const priority = modelConfigPriority(option);
2788
+ if (priority > selectedPriority) {
2789
+ selected = models;
2790
+ selectedPriority = priority;
2791
+ }
3279
2792
  }
2793
+ return selected;
3280
2794
  }
3281
2795
  function modelStateFromLegacyResponse(response) {
3282
2796
  if (!response || typeof response !== "object") return;
@@ -3414,6 +2928,156 @@ function createNdJsonMessageStream(agentCommand, output, input, maxMessageBytes,
3414
2928
  };
3415
2929
  }
3416
2930
  //#endregion
2931
+ //#region src/acp/observed-stream.ts
2932
+ function observeAcpStream(base, options) {
2933
+ const elicitationRequestIds = /* @__PURE__ */ new Set();
2934
+ const shouldSuppressInboundReplaySessionUpdate = (message) => {
2935
+ return options.suppressReplaySessionUpdates() && isSessionUpdateNotification(message);
2936
+ };
2937
+ const observeInbound = (message) => {
2938
+ const requestId = elicitationRequestId(message);
2939
+ if (requestId !== void 0) {
2940
+ elicitationRequestIds.add(requestId);
2941
+ return;
2942
+ }
2943
+ if (!shouldSuppressInboundReplaySessionUpdate(message)) options.onMessage("inbound", message);
2944
+ };
2945
+ return {
2946
+ readable: new ReadableStream({ async start(controller) {
2947
+ const reader = base.readable.getReader();
2948
+ try {
2949
+ while (true) {
2950
+ const { value, done } = await reader.read();
2951
+ if (done) break;
2952
+ if (!value) continue;
2953
+ observeInbound(value);
2954
+ if (isExtensionNotification(value)) continue;
2955
+ controller.enqueue(value);
2956
+ }
2957
+ } finally {
2958
+ reader.releaseLock();
2959
+ controller.close();
2960
+ }
2961
+ } }),
2962
+ writable: new WritableStream({ async write(message) {
2963
+ const promptOwner = promptRequestOwner(message);
2964
+ const activePrompt = promptOwner ? options.bindPromptOwner(promptOwner) : void 0;
2965
+ const id = responseId(message);
2966
+ if (!(id !== void 0 && elicitationRequestIds.delete(id))) options.onMessage("outbound", message);
2967
+ const writer = base.writable.getWriter();
2968
+ try {
2969
+ await writer.write(message);
2970
+ } finally {
2971
+ writer.releaseLock();
2972
+ }
2973
+ if (activePrompt && promptOwner) options.onPromptRequestWritten(activePrompt, promptOwner);
2974
+ } })
2975
+ };
2976
+ }
2977
+ function isExtensionNotification(message) {
2978
+ if (!("method" in message) || "id" in message || typeof message.method !== "string") return false;
2979
+ return message.method !== methods.client.session.update && message.method !== methods.client.elicitation.complete && message.method !== methods.protocol.cancelRequest;
2980
+ }
2981
+ function elicitationRequestId(message) {
2982
+ if (!("method" in message) || message.method !== methods.client.elicitation.create || !("id" in message)) return;
2983
+ return message.id;
2984
+ }
2985
+ function responseId(message) {
2986
+ if (!("id" in message) || "method" in message) return;
2987
+ return message.id;
2988
+ }
2989
+ function promptRequestOwner(message) {
2990
+ if (!("method" in message) || message.method !== methods.agent.session.prompt || !("id" in message)) return;
2991
+ const sessionId = message.params?.sessionId;
2992
+ return typeof sessionId === "string" ? {
2993
+ requestId: message.id,
2994
+ sessionId
2995
+ } : void 0;
2996
+ }
2997
+ //#endregion
2998
+ //#region src/acp/process-descendants.ts
2999
+ function parseProcessTable(output) {
3000
+ const table = /* @__PURE__ */ new Map();
3001
+ for (const line of output.split("\n")) {
3002
+ const match = /^\s*(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(line);
3003
+ if (match && !match[3].startsWith("Z")) {
3004
+ const pid = Number(match[1]);
3005
+ if (Number.isSafeInteger(pid) && pid > 1 && pid !== process.pid) table.set(pid, {
3006
+ pid,
3007
+ parentPid: Number(match[2]),
3008
+ birth: match[4]
3009
+ });
3010
+ }
3011
+ }
3012
+ return table;
3013
+ }
3014
+ function includeDescendants(table, owned) {
3015
+ let expanded;
3016
+ do {
3017
+ expanded = false;
3018
+ for (const identity of table.values()) if (owned.has(identity.parentPid) && !owned.has(identity.pid)) {
3019
+ owned.add(identity.pid);
3020
+ expanded = true;
3021
+ }
3022
+ } while (expanded);
3023
+ }
3024
+ /** Best-effort POSIX cleanup for descendants witnessed during this child launch. */
3025
+ var ProcessDescendants = class {
3026
+ child;
3027
+ identities = /* @__PURE__ */ new Map();
3028
+ pending;
3029
+ retired = false;
3030
+ constructor(child) {
3031
+ this.child = child;
3032
+ }
3033
+ capture(timeoutMs = PROCESS_HELPER_TIMEOUT_MS) {
3034
+ if (this.retired || process.platform === "win32") return Promise.resolve(true);
3035
+ this.pending ??= this.readSnapshot(timeoutMs).finally(() => {
3036
+ this.pending = void 0;
3037
+ });
3038
+ return this.pending;
3039
+ }
3040
+ async readSnapshot(timeoutMs) {
3041
+ try {
3042
+ const output = await runTimedExecFile("ps", ["-eo", "pid=,ppid=,stat=,lstart="], { timeoutMs });
3043
+ if (!this.retired) this.refresh(parseProcessTable(output));
3044
+ return true;
3045
+ } catch {
3046
+ return false;
3047
+ }
3048
+ }
3049
+ refresh(table) {
3050
+ const owned = /* @__PURE__ */ new Set();
3051
+ for (const [pid, identity] of this.identities) if (table.get(pid)?.birth === identity.birth) owned.add(pid);
3052
+ const root = this.child.pid;
3053
+ if (root && isChildProcessRunning(this.child)) owned.add(root);
3054
+ includeDescendants(table, owned);
3055
+ owned.delete(root ?? 0);
3056
+ this.identities = new Map([...table].filter(([pid]) => owned.has(pid)));
3057
+ }
3058
+ async signal(signal, timeoutMs) {
3059
+ if (!await this.capture(timeoutMs) || this.retired) return;
3060
+ for (const pid of this.identities.keys()) try {
3061
+ process.kill(pid, signal);
3062
+ } catch {
3063
+ this.identities.delete(pid);
3064
+ }
3065
+ }
3066
+ async waitForExit(timeoutMs) {
3067
+ const deadline = Date.now() + timeoutMs;
3068
+ do {
3069
+ if (!await this.capture(Math.max(1, deadline - Date.now()))) return false;
3070
+ if (this.identities.size === 0) return true;
3071
+ await setTimeout$1(Math.min(100, Math.max(0, deadline - Date.now())));
3072
+ } while (Date.now() < deadline);
3073
+ return false;
3074
+ }
3075
+ retire() {
3076
+ this.retired = true;
3077
+ this.identities.clear();
3078
+ }
3079
+ };
3080
+ //#endregion
3417
3081
  //#region src/acp/session-control-errors.ts
3418
3082
  const SESSION_CONTROL_UNSUPPORTED_ACP_CODES = /* @__PURE__ */ new Set([-32601, -32602]);
3419
3083
  function asRecord$1(value) {
@@ -3485,20 +3149,6 @@ function trimToUtf8Boundary(buffer, limit) {
3485
3149
  if (start >= buffer.length) start = buffer.length - limit;
3486
3150
  return buffer.subarray(start);
3487
3151
  }
3488
- function waitForSpawn(process) {
3489
- return new Promise((resolve, reject) => {
3490
- const onSpawn = () => {
3491
- process.off("error", onError);
3492
- resolve();
3493
- };
3494
- const onError = (error) => {
3495
- process.off("spawn", onSpawn);
3496
- reject(error);
3497
- };
3498
- process.once("spawn", onSpawn);
3499
- process.once("error", onError);
3500
- });
3501
- }
3502
3152
  async function defaultConfirmExecute(commandLine) {
3503
3153
  return await promptForPermission({ prompt: `\n[permission] Allow terminal command "${commandLine}"? (y/N) ` });
3504
3154
  }
@@ -3913,11 +3563,12 @@ async function runProcessGroupListCommand(timeoutMs) {
3913
3563
  return await runTimedExecFile("ps", ["-eo", "pid=,pgid="], { timeoutMs });
3914
3564
  }
3915
3565
  async function runWindowsProcessListCommand(timeoutMs) {
3566
+ const command = ["Get-CimInstance Win32_Process |", "ForEach-Object { \"$($_.ProcessId) $($_.ParentProcessId)\" }"].join(" ");
3916
3567
  return await runTimedExecFile("powershell.exe", [
3917
3568
  "-NoProfile",
3918
3569
  "-NonInteractive",
3919
3570
  "-Command",
3920
- ["Get-CimInstance Win32_Process |", "ForEach-Object { \"$($_.ProcessId) $($_.ParentProcessId)\" }"].join(" ")
3571
+ command
3921
3572
  ], {
3922
3573
  timeoutMs,
3923
3574
  windowsHide: true
@@ -3970,10 +3621,8 @@ const REPLAY_DRAIN_TIMEOUT_MS = 5e3;
3970
3621
  const DRAIN_POLL_INTERVAL_MS = 20;
3971
3622
  const AGENT_CLOSE_TERM_GRACE_MS = 1500;
3972
3623
  const AGENT_CLOSE_KILL_GRACE_MS = 1e3;
3624
+ const AGENT_CLEANUP_BUDGET_MS = 8e3;
3973
3625
  const STARTUP_STDERR_MAX_CHARS = 8192;
3974
- const DEVIN_COMPATIBILITY_CLIENT_CAPABILITIES_META = Object.freeze({ "cognition.ai/requestDiagnostics": true });
3975
- const DEVIN_COMPATIBILITY_CLIENT_NAME = "windsurf";
3976
- const DEFAULT_DEVIN_COMPATIBILITY_CLIENT_VERSION = "1.110.1";
3977
3626
  const ELICITATION_CANCEL_MESSAGES = {
3978
3627
  inactive: "elicitation owner is no longer active",
3979
3628
  unavailable: "elicitation handler is unavailable",
@@ -3982,16 +3631,6 @@ const ELICITATION_CANCEL_MESSAGES = {
3982
3631
  cancelled: "elicitation request was cancelled",
3983
3632
  requestScoped: "request-scoped elicitation has no owner"
3984
3633
  };
3985
- function resolveClientInfo(devinAcp) {
3986
- if (!devinAcp) return {
3987
- name: "acpx",
3988
- version: getAcpxVersion()
3989
- };
3990
- return {
3991
- name: DEVIN_COMPATIBILITY_CLIENT_NAME,
3992
- version: process.env.ACPX_DEVIN_WINDSURF_VERSION ?? DEFAULT_DEVIN_COMPATIBILITY_CLIENT_VERSION
3993
- };
3994
- }
3995
3634
  function normalizeElicitationModes(modes) {
3996
3635
  return [...new Set((modes ?? []).filter((mode) => mode === "form" || mode === "url"))];
3997
3636
  }
@@ -4014,72 +3653,18 @@ function elicitationRequestScopeId(request) {
4014
3653
  const value = request.requestId;
4015
3654
  return value === null || typeof value === "string" || typeof value === "number" ? value : void 0;
4016
3655
  }
4017
- function waitForAbort(signal) {
4018
- if (signal.aborted) return Promise.resolve({ kind: "aborted" });
4019
- return new Promise((resolve) => {
4020
- signal.addEventListener("abort", () => resolve({ kind: "aborted" }), { once: true });
3656
+ async function raceWithAbort(signal, pending) {
3657
+ let onAbort;
3658
+ const aborted = new Promise((resolve) => {
3659
+ onAbort = () => resolve(void 0);
3660
+ if (signal.aborted) onAbort();
3661
+ else signal.addEventListener("abort", onAbort, { once: true });
4021
3662
  });
4022
- }
4023
- function isExtensionNotification(message) {
4024
- if (!("method" in message) || "id" in message || typeof message.method !== "string") return false;
4025
- return message.method !== methods.client.session.update && message.method !== methods.client.elicitation.complete && message.method !== methods.protocol.cancelRequest;
4026
- }
4027
- function elicitationRequestId(message) {
4028
- if (!("method" in message) || message.method !== methods.client.elicitation.create || !("id" in message)) return;
4029
- return message.id;
4030
- }
4031
- function responseId(message) {
4032
- if (!("id" in message) || "method" in message) return;
4033
- return message.id;
4034
- }
4035
- function promptRequestOwner(message) {
4036
- if (!("method" in message) || message.method !== methods.agent.session.prompt || !("id" in message)) return;
4037
- const sessionId = message.params?.sessionId;
4038
- return typeof sessionId === "string" ? {
4039
- requestId: message.id,
4040
- sessionId
4041
- } : void 0;
4042
- }
4043
- function createAgentConnectionFacade(connection) {
4044
- const agent = connection.agent;
4045
- return {
4046
- signal: connection.signal,
4047
- close: (error) => connection.close(error),
4048
- initialize: async (params) => await agent.request(methods.agent.initialize, params),
4049
- authenticate: async (params) => {
4050
- await agent.request(methods.agent.authenticate, params);
4051
- },
4052
- newSession: async (params) => await agent.request(methods.agent.session.new, params),
4053
- loadSession: async (params) => await agent.request(methods.agent.session.load, params),
4054
- resumeSession: async (params) => await agent.request(methods.agent.session.resume, params),
4055
- prompt: async (params) => await agent.request(methods.agent.session.prompt, params),
4056
- setSessionMode: async (params) => await agent.request(methods.agent.session.setMode, params),
4057
- setSessionConfigOption: async (params) => await agent.request(methods.agent.session.setConfigOption, params),
4058
- extMethod: async (method, params) => await agent.request(method, params),
4059
- cancel: async (params) => await agent.notify(methods.agent.session.cancel, params),
4060
- closeSession: async (params) => {
4061
- await agent.request(methods.agent.session.close, params);
4062
- },
4063
- listSessions: async (params) => await agent.request(methods.agent.session.list, params)
4064
- };
4065
- }
4066
- function resolveClientCapabilities(params) {
4067
- const baseCapabilities = {
4068
- fs: {
4069
- readTextFile: params.fs,
4070
- writeTextFile: params.fs
4071
- },
4072
- terminal: params.terminal,
4073
- ...params.elicitationModes.length > 0 ? { elicitation: {
4074
- ...params.elicitationModes.includes("form") ? { form: {} } : {},
4075
- ...params.elicitationModes.includes("url") ? { url: {} } : {}
4076
- } } : {}
4077
- };
4078
- if (!params.devinAcp) return baseCapabilities;
4079
- return {
4080
- ...baseCapabilities,
4081
- _meta: DEVIN_COMPATIBILITY_CLIENT_CAPABILITIES_META
4082
- };
3663
+ try {
3664
+ return await Promise.race([pending, aborted]);
3665
+ } finally {
3666
+ signal.removeEventListener("abort", onAbort);
3667
+ }
4083
3668
  }
4084
3669
  function hasResponseField(response, field) {
4085
3670
  return !!response && typeof response === "object" && field in response;
@@ -4091,7 +3676,7 @@ function normalizeResponseConfigOptions(response) {
4091
3676
  function toReconnectedSessionResult(response) {
4092
3677
  const configOptions = normalizeResponseConfigOptions(response);
4093
3678
  return {
4094
- agentSessionId: extractRuntimeSessionId(response?._meta),
3679
+ agentSessionId: extractAgentSessionId(response?._meta),
4095
3680
  configOptions,
4096
3681
  models: modelStateFromSessionResponse({
4097
3682
  configOptions,
@@ -4146,6 +3731,8 @@ var AcpClient = class {
4146
3731
  options;
4147
3732
  connection;
4148
3733
  agent;
3734
+ agentDescendants = /* @__PURE__ */ new WeakMap();
3735
+ agentCleanups = /* @__PURE__ */ new WeakMap();
4149
3736
  initResult;
4150
3737
  loadedSessionId;
4151
3738
  eventHandlers;
@@ -4288,6 +3875,7 @@ var AcpClient = class {
4288
3875
  this.logAgentLaunch(launch);
4289
3876
  await this.ensureLaunchSupport(launch);
4290
3877
  const { child, process: startedProcess } = await this.spawnAgentProcess(launch);
3878
+ this.agent = child;
4291
3879
  this.closing = false;
4292
3880
  this.agentStartedAt = startedProcess.startedAt;
4293
3881
  this.lastAgentExit = void 0;
@@ -4312,11 +3900,11 @@ var AcpClient = class {
4312
3900
  let connection;
4313
3901
  const stream = this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output, maxMessageBytes, (error) => {
4314
3902
  this.rejectPendingConnectionRequests(error);
4315
- connection?.close?.(error);
3903
+ connection?.close(error);
4316
3904
  }));
4317
3905
  connection = this.createConnection(stream, launch);
4318
3906
  connection.signal.addEventListener("abort", () => {
4319
- this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null);
3907
+ this.recordAgentExit(child, "connection_close", child.exitCode ?? null, child.signalCode ?? null);
4320
3908
  }, { once: true });
4321
3909
  await this.initializeAgentConnection({
4322
3910
  child,
@@ -4381,13 +3969,14 @@ var AcpClient = class {
4381
3969
  ...plan.spawnOptions,
4382
3970
  windowsVerbatimArguments: spawnCommand.windowsVerbatimArguments
4383
3971
  });
4384
- await waitForSpawn$1(spawnedChild);
3972
+ await waitForSpawn(spawnedChild);
4385
3973
  } catch (error) {
4386
3974
  const spawnError = new AgentSpawnError(this.options.agentCommand, error);
4387
3975
  this.notifyProcessSpawnFailure(launch, spawnError);
4388
3976
  throw spawnError;
4389
3977
  }
4390
3978
  const child = requireAgentStdio(spawnedChild);
3979
+ this.agentDescendants.set(child, new ProcessDescendants(child));
4391
3980
  const pid = child.pid;
4392
3981
  if (pid === void 0) {
4393
3982
  const spawnError = new AgentSpawnError(this.options.agentCommand, /* @__PURE__ */ new Error("spawned agent process did not expose a PID"));
@@ -4400,7 +3989,7 @@ var AcpClient = class {
4400
3989
  process: Object.freeze({
4401
3990
  ...launch,
4402
3991
  pid,
4403
- startedAt: isoNow$1()
3992
+ startedAt: isoNow$2()
4404
3993
  })
4405
3994
  };
4406
3995
  }
@@ -4433,34 +4022,34 @@ var AcpClient = class {
4433
4022
  }).onRequest(methods.client.terminal.create, async ({ params }) => {
4434
4023
  return await this.handleCreateTerminal(params);
4435
4024
  }).onRequest(methods.client.terminal.output, async ({ params }) => {
4436
- return await this.handleTerminalOutput(params);
4025
+ return await this.terminalManager.terminalOutput(params);
4437
4026
  }).onRequest(methods.client.terminal.waitForExit, async ({ params }) => {
4438
- return await this.handleWaitForTerminalExit(params);
4027
+ return await this.terminalManager.waitForTerminalExit(params);
4439
4028
  }).onRequest(methods.client.terminal.kill, async ({ params }) => {
4440
- return await this.handleKillTerminal(params);
4029
+ return await this.terminalManager.killTerminal(params);
4441
4030
  }).onRequest(methods.client.terminal.release, async ({ params }) => {
4442
- return await this.handleReleaseTerminal(params);
4031
+ return await this.terminalManager.releaseTerminal(params);
4443
4032
  });
4444
4033
  if (launch.devinAcp) app.onRequest("_cognition.ai/request_diagnostics", (params) => {
4445
4034
  return params && typeof params === "object" && !Array.isArray(params) ? params : {};
4446
4035
  }, async () => ({}));
4447
- return createAgentConnectionFacade(app.connect(stream));
4036
+ return app.connect(stream);
4448
4037
  }
4449
4038
  async initializeAgentConnection(params) {
4450
4039
  try {
4451
4040
  const initResult = await Promise.race([this.initializeProtocolConnection(params.connection, params.launch), params.startupFailure.promise]);
4452
4041
  params.startupFailure.dispose();
4453
4042
  this.connection = params.connection;
4454
- this.agent = params.child;
4455
4043
  this.initResult = initResult;
4044
+ await this.captureAgentDescendants(params.child);
4456
4045
  this.log(`initialized protocol version ${initResult.protocolVersion}`);
4457
4046
  } catch (error) {
4458
- params.connection.close?.(error);
4047
+ params.connection.close(error);
4459
4048
  await this.handleInitializeFailure(params, error);
4460
4049
  }
4461
4050
  }
4462
4051
  async initializeProtocolConnection(connection, launch) {
4463
- const initializePromise = connection.initialize({
4052
+ const initializePromise = connection.agent.request(methods.agent.initialize, {
4464
4053
  protocolVersion: PROTOCOL_VERSION,
4465
4054
  clientCapabilities: resolveClientCapabilities({
4466
4055
  devinAcp: launch.devinAcp,
@@ -4476,10 +4065,9 @@ var AcpClient = class {
4476
4065
  }
4477
4066
  async handleInitializeFailure(params, error) {
4478
4067
  params.startupFailure.dispose();
4068
+ await this.captureAgentDescendants(params.child);
4479
4069
  const normalizedError = error instanceof AcpMessageLimitError ? error : await this.normalizeInitializeError(error, params.child, params.startupStderr);
4480
- try {
4481
- params.child.kill();
4482
- } catch {}
4070
+ await this.terminateAgentProcess(params.child);
4483
4071
  if (params.launch.geminiAcp && error instanceof TimeoutError) throw new GeminiAcpStartupTimeoutError(await buildGeminiAcpStartupTimeoutMessage(params.launch.spawnCommand), {
4484
4072
  cause: error,
4485
4073
  retryable: true
@@ -4487,59 +4075,15 @@ var AcpClient = class {
4487
4075
  throw normalizedError;
4488
4076
  }
4489
4077
  createTappedStream(base) {
4490
- const onAcpMessage = () => this.eventHandlers.onAcpMessage;
4491
- const onAcpOutputMessage = () => this.eventHandlers.onAcpOutputMessage;
4492
- const elicitationRequestIds = /* @__PURE__ */ new Set();
4493
- const bindPromptOwner = (owner) => this.bindPromptOwner(owner);
4494
- const onPromptRequestWritten = (active, owner) => this.onPromptRequestWritten(active, owner);
4495
- const shouldSuppressInboundReplaySessionUpdate = (message) => {
4496
- return this.suppressReplaySessionUpdateMessages && isSessionUpdateNotification(message);
4497
- };
4498
- const observeInbound = (message) => {
4499
- const requestId = elicitationRequestId(message);
4500
- if (requestId !== void 0) {
4501
- elicitationRequestIds.add(requestId);
4502
- return;
4503
- }
4504
- if (!shouldSuppressInboundReplaySessionUpdate(message)) {
4505
- onAcpOutputMessage()?.("inbound", message);
4506
- onAcpMessage()?.("inbound", message);
4507
- }
4508
- };
4509
- return {
4510
- readable: new ReadableStream({ async start(controller) {
4511
- const reader = base.readable.getReader();
4512
- try {
4513
- while (true) {
4514
- const { value, done } = await reader.read();
4515
- if (done) break;
4516
- if (!value) continue;
4517
- observeInbound(value);
4518
- if (isExtensionNotification(value)) continue;
4519
- controller.enqueue(value);
4520
- }
4521
- } finally {
4522
- reader.releaseLock();
4523
- controller.close();
4524
- }
4525
- } }),
4526
- writable: new WritableStream({ async write(message) {
4527
- const promptOwner = promptRequestOwner(message);
4528
- const activePrompt = promptOwner ? bindPromptOwner(promptOwner) : void 0;
4529
- const id = responseId(message);
4530
- if (!(id !== void 0 && elicitationRequestIds.delete(id))) {
4531
- onAcpOutputMessage()?.("outbound", message);
4532
- onAcpMessage()?.("outbound", message);
4533
- }
4534
- const writer = base.writable.getWriter();
4535
- try {
4536
- await writer.write(message);
4537
- } finally {
4538
- writer.releaseLock();
4539
- }
4540
- if (activePrompt && promptOwner) onPromptRequestWritten(activePrompt, promptOwner);
4541
- } })
4542
- };
4078
+ return observeAcpStream(base, {
4079
+ onMessage: (direction, message) => {
4080
+ this.eventHandlers.onAcpOutputMessage?.(direction, message);
4081
+ this.eventHandlers.onAcpMessage?.(direction, message);
4082
+ },
4083
+ suppressReplaySessionUpdates: () => this.suppressReplaySessionUpdateMessages,
4084
+ bindPromptOwner: (owner) => this.bindPromptOwner(owner),
4085
+ onPromptRequestWritten: (active, owner) => this.onPromptRequestWritten(active, owner)
4086
+ });
4543
4087
  }
4544
4088
  async createSession(cwd = this.options.cwd) {
4545
4089
  const connection = this.getConnection();
@@ -4548,7 +4092,7 @@ var AcpClient = class {
4548
4092
  const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
4549
4093
  let result;
4550
4094
  try {
4551
- const createPromise = this.runConnectionRequest(() => connection.newSession({
4095
+ const createPromise = this.runConnectionRequest(() => connection.agent.request(methods.agent.session.new, {
4552
4096
  cwd: sessionCwd,
4553
4097
  mcpServers: this.options.mcpServers ?? [],
4554
4098
  _meta: buildClaudeCodeOptionsMeta(this.options.sessionOptions, claudeAcp)
@@ -4568,9 +4112,10 @@ var AcpClient = class {
4568
4112
  response: result
4569
4113
  });
4570
4114
  this.rememberSessionModels(result.sessionId, models);
4115
+ await this.captureAgentDescendants(this.agent);
4571
4116
  return {
4572
4117
  sessionId: result.sessionId,
4573
- agentSessionId: extractRuntimeSessionId(result._meta),
4118
+ agentSessionId: extractAgentSessionId(result._meta),
4574
4119
  configOptions,
4575
4120
  models,
4576
4121
  configOptionsPresent: hasResponseField(result, "configOptions"),
@@ -4587,7 +4132,7 @@ var AcpClient = class {
4587
4132
  const previousSuppression = this.applySessionUpdateSuppression(Boolean(options.suppressReplayUpdates));
4588
4133
  let response;
4589
4134
  try {
4590
- response = await this.runConnectionRequest(() => connection.loadSession({
4135
+ response = await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.load, {
4591
4136
  sessionId,
4592
4137
  cwd: sessionCwd,
4593
4138
  mcpServers: this.options.mcpServers ?? []
@@ -4599,12 +4144,13 @@ var AcpClient = class {
4599
4144
  this.loadedSessionId = sessionId;
4600
4145
  const result = toReconnectedSessionResult(response);
4601
4146
  this.updateRememberedSessionModels(sessionId, result);
4147
+ await this.captureAgentDescendants(this.agent);
4602
4148
  return result;
4603
4149
  }
4604
4150
  async resumeSession(sessionId, cwd = this.options.cwd) {
4605
4151
  const connection = this.getConnection();
4606
4152
  const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
4607
- const response = await this.runConnectionRequest(() => connection.resumeSession({
4153
+ const response = await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.resume, {
4608
4154
  sessionId,
4609
4155
  cwd: sessionCwd,
4610
4156
  mcpServers: this.options.mcpServers ?? []
@@ -4612,6 +4158,7 @@ var AcpClient = class {
4612
4158
  this.loadedSessionId = sessionId;
4613
4159
  const result = toReconnectedSessionResult(response);
4614
4160
  this.updateRememberedSessionModels(sessionId, result);
4161
+ await this.captureAgentDescendants(this.agent);
4615
4162
  return result;
4616
4163
  }
4617
4164
  applySessionUpdateSuppression(enabled) {
@@ -4627,14 +4174,15 @@ var AcpClient = class {
4627
4174
  this.suppressSessionUpdates = previous.suppressSessionUpdates;
4628
4175
  this.suppressReplaySessionUpdateMessages = previous.suppressReplaySessionUpdateMessages;
4629
4176
  }
4630
- async prompt(sessionId, prompt, onRequestWritten, onElicitation) {
4177
+ async prompt(sessionId, prompt, onRequestWritten, onElicitation, onPermissionRequest) {
4631
4178
  const connection = this.getConnection();
4632
4179
  const normalizedPrompt = this.normalizePromptForAgent(prompt);
4633
4180
  const restoreConsoleError = this.options.suppressSdkConsoleErrors ? installSdkConsoleErrorSuppression() : void 0;
4634
- const activePrompt = this.beginActivePrompt(sessionId, onRequestWritten, onElicitation);
4181
+ const previousActivePrompt = this.activePrompt;
4182
+ const activePrompt = this.beginActivePrompt(sessionId, onRequestWritten, onElicitation, onPermissionRequest);
4635
4183
  let promptPromise;
4636
4184
  try {
4637
- promptPromise = this.runConnectionRequest(() => connection.prompt({
4185
+ promptPromise = this.runConnectionRequest(() => connection.agent.request(methods.agent.session.prompt, {
4638
4186
  sessionId,
4639
4187
  prompt: normalizedPrompt
4640
4188
  }));
@@ -4645,6 +4193,7 @@ var AcpClient = class {
4645
4193
  }
4646
4194
  activePrompt.promise = promptPromise;
4647
4195
  try {
4196
+ previousActivePrompt?.elicitationController?.abort();
4648
4197
  return this.returnPromptResponseOrPermissionFailure(sessionId, await promptPromise);
4649
4198
  } catch (error) {
4650
4199
  this.throwPromptPermissionFailureIfPresent(sessionId);
@@ -4652,23 +4201,19 @@ var AcpClient = class {
4652
4201
  } finally {
4653
4202
  restoreConsoleError?.();
4654
4203
  this.clearActivePrompt(activePrompt);
4655
- this.cancellingSessionIds.delete(sessionId);
4656
- this.abortAndDropPermissionSignal(sessionId);
4657
- this.promptPermissionFailures.delete(sessionId);
4658
4204
  }
4659
4205
  }
4660
- beginActivePrompt(sessionId, onRequestWritten, elicitationHandler) {
4661
- const previous = this.activePrompt;
4206
+ beginActivePrompt(sessionId, onRequestWritten, elicitationHandler, permissionHandler) {
4662
4207
  this.cancellingSessionIds.delete(sessionId);
4663
4208
  const active = {
4664
4209
  sessionId,
4665
4210
  onRequestWritten,
4666
4211
  elicitationHandler,
4212
+ permissionHandler,
4667
4213
  elicitationController: new AbortController()
4668
4214
  };
4669
4215
  this.activePrompt = active;
4670
4216
  this.pendingPromptOwners.push(active);
4671
- previous?.elicitationController?.abort();
4672
4217
  return active;
4673
4218
  }
4674
4219
  bindPromptOwner(owner) {
@@ -4685,10 +4230,18 @@ var AcpClient = class {
4685
4230
  } catch {}
4686
4231
  }
4687
4232
  clearActivePrompt(active) {
4688
- if (this.activePrompt === active) this.activePrompt = void 0;
4689
4233
  const pendingIndex = this.pendingPromptOwners.indexOf(active);
4234
+ const clearSession = !this.pendingPromptOwners.some((candidate, index) => index > pendingIndex && candidate.sessionId === active.sessionId);
4235
+ if (this.activePrompt === active) this.activePrompt = void 0;
4690
4236
  if (pendingIndex >= 0) this.pendingPromptOwners.splice(pendingIndex, 1);
4237
+ let permissionController;
4238
+ if (clearSession) {
4239
+ this.cancellingSessionIds.delete(active.sessionId);
4240
+ permissionController = this.takePermissionAbortController(active.sessionId);
4241
+ this.promptPermissionFailures.delete(active.sessionId);
4242
+ }
4691
4243
  active.elicitationController.abort();
4244
+ permissionController?.abort();
4692
4245
  }
4693
4246
  normalizePromptForAgent(prompt) {
4694
4247
  const normalizedPrompt = typeof prompt === "string" ? textPrompt(prompt) : prompt;
@@ -4707,7 +4260,7 @@ var AcpClient = class {
4707
4260
  async setSessionMode(sessionId, modeId) {
4708
4261
  const connection = this.getConnection();
4709
4262
  try {
4710
- await this.runConnectionRequest(() => connection.setSessionMode({
4263
+ await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.setMode, {
4711
4264
  sessionId,
4712
4265
  modeId
4713
4266
  }));
@@ -4718,7 +4271,7 @@ var AcpClient = class {
4718
4271
  async setSessionConfigOption(sessionId, configId, value) {
4719
4272
  const connection = this.getConnection();
4720
4273
  try {
4721
- return await this.runConnectionRequest(() => connection.setSessionConfigOption({
4274
+ return await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.setConfigOption, {
4722
4275
  sessionId,
4723
4276
  configId,
4724
4277
  value
@@ -4740,7 +4293,7 @@ var AcpClient = class {
4740
4293
  async setSessionModelThroughConfig(sessionId, modelId, configId) {
4741
4294
  const connection = this.getConnection();
4742
4295
  try {
4743
- const response = await this.runConnectionRequest(() => connection.setSessionConfigOption({
4296
+ const response = await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.setConfigOption, {
4744
4297
  sessionId,
4745
4298
  configId,
4746
4299
  value: modelId
@@ -4754,7 +4307,7 @@ var AcpClient = class {
4754
4307
  async setSessionModelThroughLegacyMethod(sessionId, modelId) {
4755
4308
  const connection = this.getConnection();
4756
4309
  try {
4757
- await this.runConnectionRequest(() => connection.extMethod("session/set_model", {
4310
+ await this.runConnectionRequest(() => connection.agent.request("session/set_model", {
4758
4311
  sessionId,
4759
4312
  modelId
4760
4313
  }));
@@ -4802,26 +4355,32 @@ var AcpClient = class {
4802
4355
  }
4803
4356
  async cancel(sessionId) {
4804
4357
  const connection = this.getConnection();
4805
- const active = this.activePrompt;
4806
- if (active?.sessionId === sessionId) {
4358
+ const active = this.activePrompt?.sessionId === sessionId ? this.activePrompt : void 0;
4359
+ const cancellation = active?.cancelPromise ?? this.runConnectionRequest(() => connection.agent.notify(methods.agent.session.cancel, { sessionId })).catch((error) => {
4360
+ if (active?.cancelPromise === cancellation) active.cancelPromise = void 0;
4361
+ throw error;
4362
+ });
4363
+ const permissionController = this.takePermissionAbortController(sessionId);
4364
+ if (active) {
4365
+ active.cancelPromise = cancellation;
4807
4366
  this.cancellingSessionIds.add(sessionId);
4808
4367
  active.elicitationController?.abort();
4809
4368
  } else this.cancellingSessionIds.delete(sessionId);
4810
- this.abortAndDropPermissionSignal(sessionId);
4811
- await this.runConnectionRequest(() => connection.cancel({ sessionId }));
4369
+ permissionController?.abort();
4370
+ await cancellation;
4812
4371
  }
4813
4372
  async closeSession(sessionId) {
4814
4373
  const connection = this.getConnection();
4815
4374
  this.cancellingSessionIds.add(sessionId);
4816
4375
  if (this.activePrompt?.sessionId === sessionId) this.activePrompt.elicitationController?.abort();
4817
- await this.runConnectionRequest(() => connection.closeSession({ sessionId }));
4376
+ await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.close, { sessionId }));
4818
4377
  if (this.loadedSessionId === sessionId) this.loadedSessionId = void 0;
4819
4378
  this.modelConfigIds.delete(sessionId);
4820
4379
  this.legacyModelSessionIds.delete(sessionId);
4821
4380
  }
4822
4381
  async listSessions(params = {}) {
4823
4382
  const connection = this.getConnection();
4824
- return await this.runConnectionRequest(() => connection.listSessions(params));
4383
+ return await this.runConnectionRequest(() => connection.agent.request(methods.agent.session.list, params));
4825
4384
  }
4826
4385
  async requestCancelActivePrompt() {
4827
4386
  const active = this.activePrompt;
@@ -4881,18 +4440,36 @@ var AcpClient = class {
4881
4440
  this.activePrompt?.elicitationController?.abort();
4882
4441
  }
4883
4442
  closeConnection() {
4884
- this.connection?.close?.();
4443
+ this.connection?.close();
4444
+ }
4445
+ async captureAgentDescendants(child) {
4446
+ const descendants = child && this.agentDescendants.get(child);
4447
+ if (descendants && !await descendants.capture()) this.log("could not verify agent descendants; skipping unverified process cleanup");
4448
+ }
4449
+ terminateAgentProcess(child) {
4450
+ let cleanup = this.agentCleanups.get(child);
4451
+ if (!cleanup) {
4452
+ cleanup = Promise.resolve().then(() => this.cleanupAgentProcess(child));
4453
+ this.agentCleanups.set(child, cleanup);
4454
+ }
4455
+ return cleanup;
4885
4456
  }
4886
- async terminateAgentProcess(child) {
4457
+ async cleanupAgentProcess(child) {
4458
+ const descendants = this.agentDescendants.get(child);
4459
+ const deadline = Date.now() + AGENT_CLEANUP_BUDGET_MS;
4887
4460
  const stdinCloseGraceMs = resolveAgentCloseAfterStdinEndMs(this.options.agentCommand);
4888
- this.endAgentStdin(child);
4889
- let exited = await waitForChildExit(child, stdinCloseGraceMs);
4890
- exited = await this.killAgentIfRunning(child, exited, "SIGTERM", AGENT_CLOSE_TERM_GRACE_MS);
4891
- if (!exited) {
4892
- this.log(`agent did not exit after ${AGENT_CLOSE_TERM_GRACE_MS}ms; forcing SIGKILL`);
4893
- exited = await this.killAgentIfRunning(child, exited, "SIGKILL", AGENT_CLOSE_KILL_GRACE_MS);
4461
+ try {
4462
+ if (descendants) await descendants.capture(Math.max(1, deadline - Date.now()));
4463
+ this.endAgentStdin(child);
4464
+ await waitForChildExit(child, Math.min(stdinCloseGraceMs, Math.max(0, deadline - Date.now())));
4465
+ if (!await this.signalAgentAndDescendants(child, "SIGTERM", AGENT_CLOSE_TERM_GRACE_MS, deadline)) {
4466
+ this.log("agent processes did not exit after SIGTERM; forcing SIGKILL");
4467
+ await this.signalAgentAndDescendants(child, "SIGKILL", AGENT_CLOSE_KILL_GRACE_MS, deadline);
4468
+ }
4469
+ } finally {
4470
+ descendants?.retire();
4471
+ this.detachAgentHandles(child, isChildProcessRunning(child));
4894
4472
  }
4895
- this.detachAgentHandles(child, !exited);
4896
4473
  }
4897
4474
  endAgentStdin(child) {
4898
4475
  if (child.stdin.destroyed) return;
@@ -4900,12 +4477,15 @@ var AcpClient = class {
4900
4477
  child.stdin.end();
4901
4478
  } catch {}
4902
4479
  }
4903
- async killAgentIfRunning(child, alreadyExited, signal, waitMs) {
4904
- if (alreadyExited || !isChildProcessRunning(child)) return alreadyExited;
4905
- try {
4480
+ async signalAgentAndDescendants(child, signal, waitMs, deadline) {
4481
+ const descendants = this.agentDescendants.get(child);
4482
+ if (descendants && Date.now() < deadline) await descendants.signal(signal, deadline - Date.now());
4483
+ if (isChildProcessRunning(child)) try {
4906
4484
  child.kill(signal);
4907
4485
  } catch {}
4908
- return await waitForChildExit(child, waitMs);
4486
+ const remaining = Math.min(waitMs, Math.max(0, deadline - Date.now()));
4487
+ const [exited, descendantsExited] = await Promise.all([waitForChildExit(child, remaining), descendants ? descendants.waitForExit(remaining) : Promise.resolve(true)]);
4488
+ return exited && descendantsExited;
4909
4489
  }
4910
4490
  detachAgentHandles(agent, unref) {
4911
4491
  const stdin = agent.stdin;
@@ -5057,7 +4637,7 @@ var AcpClient = class {
5057
4637
  this.log(`agent advertised auth methods [${authMethods.map((m) => m.id).join(", ")}] but no matching credentials found — skipping (agent may handle auth internally)`);
5058
4638
  return;
5059
4639
  }
5060
- await connection.authenticate({ methodId: selected.methodId });
4640
+ await connection.agent.request(methods.agent.authenticate, { methodId: selected.methodId });
5061
4641
  this.log(`authenticated with method ${selected.methodId} (${selected.source})`);
5062
4642
  }
5063
4643
  async handlePermissionRequest(params) {
@@ -5076,7 +4656,7 @@ var AcpClient = class {
5076
4656
  if ("response" in resolved) return resolved.response;
5077
4657
  const { active, handler } = resolved.owner;
5078
4658
  const signal = AbortSignal.any([requestSignal, active.elicitationController.signal]);
5079
- const handlerAttempt = Promise.resolve().then(async () => await handler(request, {
4659
+ const outcome = await raceWithAbort(signal, Promise.resolve().then(async () => await handler(request, {
5080
4660
  requestId,
5081
4661
  signal
5082
4662
  })).then((response) => ({
@@ -5085,10 +4665,9 @@ var AcpClient = class {
5085
4665
  }), (error) => ({
5086
4666
  kind: "error",
5087
4667
  error
5088
- }));
5089
- const outcome = await Promise.race([handlerAttempt, waitForAbort(signal)]);
4668
+ })));
5090
4669
  if (this.activePrompt !== active) return cancelledElicitationResponse(ELICITATION_CANCEL_MESSAGES.inactive);
5091
- if (outcome.kind === "aborted" || signal.aborted) return cancelledElicitationResponse(ELICITATION_CANCEL_MESSAGES.cancelled);
4670
+ if (!outcome || signal.aborted) return cancelledElicitationResponse(ELICITATION_CANCEL_MESSAGES.cancelled);
5092
4671
  if (outcome.kind === "error" || !isKnownElicitationResponse(outcome.response)) return cancelledElicitationResponse(ELICITATION_CANCEL_MESSAGES.unavailable);
5093
4672
  return outcome.response;
5094
4673
  }
@@ -5115,15 +4694,31 @@ var AcpClient = class {
5115
4694
  isElicitationSessionCancelling(sessionId) {
5116
4695
  return this.closing || this.cancellingSessionIds.has(sessionId);
5117
4696
  }
4697
+ resolvePermissionOwner(sessionId) {
4698
+ const active = this.activePrompt;
4699
+ const handler = active?.permissionHandler ?? this.options.onPermissionRequest;
4700
+ if (!handler) return;
4701
+ const signal = this.cancellationSignalForSession(sessionId);
4702
+ if (!active?.permissionHandler) return {
4703
+ handler,
4704
+ signal
4705
+ };
4706
+ return {
4707
+ handler,
4708
+ signal: active.sessionId === sessionId ? AbortSignal.any([signal, active.elicitationController.signal]) : AbortSignal.abort()
4709
+ };
4710
+ }
5118
4711
  async tryHandlePermissionRequestWithHost(params) {
5119
- if (!this.options.onPermissionRequest) return;
5120
- const signal = this.cancellationSignalForSession(params.sessionId);
4712
+ const owner = this.resolvePermissionOwner(params.sessionId);
4713
+ if (!owner) return;
4714
+ const { handler, signal } = owner;
4715
+ if (signal.aborted) return this.hostPermissionDecisionResponse(params, signal, void 0);
5121
4716
  try {
5122
- const decision = await this.options.onPermissionRequest({
4717
+ const decision = await raceWithAbort(signal, handler({
5123
4718
  sessionId: params.sessionId,
5124
4719
  raw: params,
5125
4720
  inferredKind: inferToolKind(params)
5126
- }, { signal });
4721
+ }, { signal }));
5127
4722
  return this.hostPermissionDecisionResponse(params, signal, decision);
5128
4723
  } catch (error) {
5129
4724
  return this.hostPermissionErrorResponse(params, signal, error);
@@ -5135,9 +4730,9 @@ var AcpClient = class {
5135
4730
  return cancelledPermissionResponse();
5136
4731
  }
5137
4732
  if (!decision) return;
5138
- const response = decisionToResponse(params, decision);
4733
+ const response = decisionToResponse(preferCodexPermissionRefusal(params, this.initResult?.agentInfo?.name), decision);
5139
4734
  this.recordPermissionDecision(classifyPermissionDecision(params, response));
5140
- return response;
4735
+ return decision.outcome === "cancel" ? response : this.explainPermissionRefusal(params, response);
5141
4736
  }
5142
4737
  hostPermissionErrorResponse(params, signal, error) {
5143
4738
  if (signal.aborted || this.cancellingSessionIds.has(params.sessionId)) {
@@ -5148,16 +4743,29 @@ var AcpClient = class {
5148
4743
  }
5149
4744
  async resolvePermissionRequestFromMode(params) {
5150
4745
  try {
5151
- const result = await resolvePermissionRequestWithDetails(params, this.options.permissionMode, this.options.nonInteractivePermissions ?? "deny", this.options.permissionPolicy);
4746
+ const result = await resolvePermissionRequestWithDetails(preferCodexPermissionRefusal(params, this.initResult?.agentInfo?.name), this.options.permissionMode, this.options.nonInteractivePermissions ?? "deny", this.options.permissionPolicy);
5152
4747
  this.emitPermissionEscalation(result.escalation);
5153
4748
  return {
5154
- response: result.response,
4749
+ response: this.explainPermissionRefusal(params, result.response),
5155
4750
  recorded: false
5156
4751
  };
5157
4752
  } catch (error) {
5158
4753
  return this.handleModePermissionError(params.sessionId, error);
5159
4754
  }
5160
4755
  }
4756
+ explainPermissionRefusal(params, response) {
4757
+ const notice = codexPermissionNotice(params, response, this.initResult?.agentInfo?.name);
4758
+ if (!notice || this.cancellingSessionIds.has(params.sessionId)) return response;
4759
+ try {
4760
+ this.eventHandlers.onClientOperation?.({
4761
+ method: "session/request_permission",
4762
+ status: "completed",
4763
+ summary: notice,
4764
+ timestamp: isoNow$2()
4765
+ });
4766
+ } catch {}
4767
+ return withPermissionMetadata(response, { permissionNotice: notice });
4768
+ }
5161
4769
  emitPermissionEscalation(escalation) {
5162
4770
  if (escalation) this.eventHandlers.onPermissionEscalation?.(escalation);
5163
4771
  }
@@ -5172,8 +4780,9 @@ var AcpClient = class {
5172
4780
  }
5173
4781
  attachAgentLifecycleObservers(child, startedProcess, exitNotificationBarrier) {
5174
4782
  const onExit = (exitCode, signal) => {
5175
- const exitedAt = isoNow$1();
5176
- this.recordAgentExit("process_exit", exitCode, signal);
4783
+ const exitedAt = isoNow$2();
4784
+ this.recordAgentExit(child, "process_exit", exitCode, signal);
4785
+ this.terminateAgentProcess(child);
5177
4786
  exitNotificationBarrier.then(() => {
5178
4787
  this.notifyProcessExit(startedProcess, exitCode, signal, exitedAt);
5179
4788
  });
@@ -5184,10 +4793,10 @@ var AcpClient = class {
5184
4793
  onExit(child.exitCode, child.signalCode);
5185
4794
  }
5186
4795
  child.once("close", (exitCode, signal) => {
5187
- this.recordAgentExit("process_close", exitCode, signal);
4796
+ this.recordAgentExit(child, "process_close", exitCode, signal);
5188
4797
  });
5189
4798
  child.stdout.once("close", () => {
5190
- this.recordAgentExit("pipe_close", child.exitCode ?? null, child.signalCode ?? null);
4799
+ this.recordAgentExit(child, "pipe_close", child.exitCode ?? null, child.signalCode ?? null);
5191
4800
  });
5192
4801
  }
5193
4802
  notifyProcessSpawnFailure(launch, error) {
@@ -5196,7 +4805,7 @@ var AcpClient = class {
5196
4805
  const event = Object.freeze({
5197
4806
  ...launch,
5198
4807
  error,
5199
- failedAt: isoNow$1()
4808
+ failedAt: isoNow$2()
5200
4809
  });
5201
4810
  try {
5202
4811
  Promise.resolve(handler(event)).catch((observerError) => {
@@ -5227,12 +4836,12 @@ var AcpClient = class {
5227
4836
  const message = error instanceof Error ? error.message : String(error);
5228
4837
  this.log(`process lifecycle ${hook} hook failed: ${message}`);
5229
4838
  }
5230
- recordAgentExit(reason, exitCode, signal) {
5231
- if (this.lastAgentExit) return;
4839
+ recordAgentExit(child, reason, exitCode, signal) {
4840
+ if (this.agent !== child || this.lastAgentExit) return;
5232
4841
  this.lastAgentExit = {
5233
4842
  exitCode,
5234
4843
  signal,
5235
- exitedAt: isoNow$1(),
4844
+ exitedAt: isoNow$2(),
5236
4845
  reason,
5237
4846
  unexpectedDuringPrompt: !this.closing && Boolean(this.activePrompt)
5238
4847
  };
@@ -5286,6 +4895,7 @@ var AcpClient = class {
5286
4895
  return await this.filesystem.readTextFile(params);
5287
4896
  } catch (error) {
5288
4897
  this.recordPermissionError(params.sessionId, error);
4898
+ if (error instanceof FsSafeError && error.code === "not-found" || error instanceof Error && "code" in error && error.code === "ENOENT") throw RequestError.resourceNotFound(pathToFileURL(params.path).href);
5289
4899
  throw error;
5290
4900
  }
5291
4901
  }
@@ -5305,18 +4915,6 @@ var AcpClient = class {
5305
4915
  throw error;
5306
4916
  }
5307
4917
  }
5308
- async handleTerminalOutput(params) {
5309
- return await this.terminalManager.terminalOutput(params);
5310
- }
5311
- async handleWaitForTerminalExit(params) {
5312
- return await this.terminalManager.waitForTerminalExit(params);
5313
- }
5314
- async handleKillTerminal(params) {
5315
- return await this.terminalManager.killTerminal(params);
5316
- }
5317
- async handleReleaseTerminal(params) {
5318
- return await this.terminalManager.releaseTerminal(params);
5319
- }
5320
4918
  cancellationSignalForSession(sessionId) {
5321
4919
  let controller = this.permissionAbortControllers.get(sessionId);
5322
4920
  if (!controller) {
@@ -5325,12 +4923,10 @@ var AcpClient = class {
5325
4923
  }
5326
4924
  return controller.signal;
5327
4925
  }
5328
- abortAndDropPermissionSignal(sessionId) {
4926
+ takePermissionAbortController(sessionId) {
5329
4927
  const controller = this.permissionAbortControllers.get(sessionId);
5330
- if (controller) {
5331
- controller.abort();
5332
- this.permissionAbortControllers.delete(sessionId);
5333
- }
4928
+ this.permissionAbortControllers.delete(sessionId);
4929
+ return controller;
5334
4930
  }
5335
4931
  recordPermissionDecision(decision) {
5336
4932
  this.permissionStats.requested += 1;
@@ -5393,132 +4989,6 @@ var AcpClient = class {
5393
4989
  }
5394
4990
  };
5395
4991
  //#endregion
5396
- //#region src/runtime/engine/lifecycle.ts
5397
- function applyLifecycleSnapshotToRecord(record, snapshot) {
5398
- if (!snapshot) return;
5399
- record.pid = snapshot.running ? snapshot.pid : void 0;
5400
- record.agentStartedAt = snapshot.startedAt;
5401
- if (snapshot.lastExit) {
5402
- record.lastAgentExitCode = snapshot.lastExit.exitCode;
5403
- record.lastAgentExitSignal = snapshot.lastExit.signal;
5404
- record.lastAgentExitAt = snapshot.lastExit.exitedAt;
5405
- record.lastAgentDisconnectReason = snapshot.lastExit.reason;
5406
- return;
5407
- }
5408
- record.lastAgentExitCode = void 0;
5409
- record.lastAgentExitSignal = void 0;
5410
- record.lastAgentExitAt = void 0;
5411
- record.lastAgentDisconnectReason = void 0;
5412
- }
5413
- function reconcileAgentSessionId(record, agentSessionId) {
5414
- const normalized = normalizeRuntimeSessionId(agentSessionId);
5415
- if (!normalized) return;
5416
- record.agentSessionId = normalized;
5417
- }
5418
- function sessionHasAgentMessages(recordOrConversation) {
5419
- return recordOrConversation.messages.some((message) => typeof message === "object" && message !== null && "Agent" in message);
5420
- }
5421
- function applyConversation(record, conversation) {
5422
- record.title = conversation.title;
5423
- record.updated_at = conversation.updated_at;
5424
- record.messages = conversation.messages;
5425
- record.cumulative_token_usage = conversation.cumulative_token_usage;
5426
- record.cumulative_cost = conversation.cumulative_cost;
5427
- record.request_token_usage = conversation.request_token_usage;
5428
- }
5429
- //#endregion
5430
- //#region src/runtime/engine/session-options.ts
5431
- function mergeSessionOptions(preferred, fallback) {
5432
- const merged = { ...fallback };
5433
- assignDefinedOption(merged, "model", preferred?.model);
5434
- assignDefinedOption(merged, "allowedTools", preferred?.allowedTools);
5435
- assignDefinedOption(merged, "maxTurns", preferred?.maxTurns);
5436
- assignDefinedOption(merged, "systemPrompt", preferred?.systemPrompt);
5437
- assignDefinedOption(merged, "env", mergeEnvRecords(fallback?.env, preferred?.env));
5438
- return Object.keys(merged).length > 0 ? merged : void 0;
5439
- }
5440
- function mergeEnvRecords(fallback, preferred) {
5441
- if (!fallback && !preferred) return;
5442
- return {
5443
- ...fallback,
5444
- ...preferred
5445
- };
5446
- }
5447
- function assignDefinedOption(target, key, value) {
5448
- if (value !== void 0) target[key] = value;
5449
- }
5450
- function persistSessionOptions(record, options) {
5451
- const next = options === void 0 ? void 0 : persistedSessionOptions(options);
5452
- if (next !== void 0) {
5453
- record.acpx = {
5454
- ...record.acpx,
5455
- session_options: next
5456
- };
5457
- return;
5458
- }
5459
- if (!record.acpx) return;
5460
- delete record.acpx.session_options;
5461
- }
5462
- function sessionOptionsFromRecord(record) {
5463
- const stored = record.acpx?.session_options;
5464
- if (!stored) return;
5465
- const sessionOptions = {};
5466
- assignStoredOption(sessionOptions, "model", nonEmptyString(stored.model));
5467
- assignStoredOption(sessionOptions, "allowedTools", storedAllowedTools(stored.allowed_tools));
5468
- assignStoredOption(sessionOptions, "maxTurns", storedMaxTurns(stored.max_turns));
5469
- assignStoredOption(sessionOptions, "systemPrompt", storedSystemPromptOption(stored.system_prompt));
5470
- assignStoredOption(sessionOptions, "env", storedEnvRecord(stored.env));
5471
- return Object.keys(sessionOptions).length > 0 ? sessionOptions : void 0;
5472
- }
5473
- function persistedSessionOptions(options) {
5474
- const next = {
5475
- model: nonEmptyString(options.model),
5476
- allowed_tools: Array.isArray(options.allowedTools) ? [...options.allowedTools] : void 0,
5477
- max_turns: typeof options.maxTurns === "number" ? options.maxTurns : void 0,
5478
- system_prompt: normalizeSystemPromptOption(options.systemPrompt),
5479
- env: storedEnvRecord(options.env)
5480
- };
5481
- return hasPersistedSessionOptions(next) ? next : void 0;
5482
- }
5483
- function hasPersistedSessionOptions(options) {
5484
- return options.model !== void 0 || options.allowed_tools !== void 0 || options.max_turns !== void 0 || options.system_prompt !== void 0 || options.env !== void 0;
5485
- }
5486
- function storedEnvRecord(value) {
5487
- if (value === null || typeof value !== "object" || Array.isArray(value)) return;
5488
- const entries = Object.entries(value);
5489
- const result = {};
5490
- for (const [key, raw] of entries) {
5491
- if (typeof raw !== "string") continue;
5492
- result[key] = raw;
5493
- }
5494
- return Object.keys(result).length > 0 ? result : void 0;
5495
- }
5496
- function normalizeSystemPromptOption(value) {
5497
- const prompt = nonEmptyString(value);
5498
- if (prompt !== void 0) return prompt;
5499
- const append = appendedSystemPrompt(value);
5500
- return append === void 0 ? void 0 : { append };
5501
- }
5502
- function appendedSystemPrompt(value) {
5503
- if (typeof value !== "object" || value === null || Array.isArray(value)) return;
5504
- return nonEmptyString(value.append);
5505
- }
5506
- function assignStoredOption(target, key, value) {
5507
- assignDefinedOption(target, key, value);
5508
- }
5509
- function storedAllowedTools(value) {
5510
- return Array.isArray(value) && value.every((item) => typeof item === "string") ? [...value] : void 0;
5511
- }
5512
- function storedMaxTurns(value) {
5513
- return typeof value === "number" ? value : void 0;
5514
- }
5515
- function storedSystemPromptOption(value) {
5516
- return normalizeSystemPromptOption(value);
5517
- }
5518
- function nonEmptyString(value) {
5519
- return typeof value === "string" && value.trim().length > 0 ? value : void 0;
5520
- }
5521
- //#endregion
5522
4992
  //#region src/session/model-state.ts
5523
4993
  function configOptionsAreAuthoritative(state) {
5524
4994
  return state.model_control === "config_option";
@@ -5529,7 +4999,7 @@ function legacyModelState(state) {
5529
4999
  currentModelId: state.current_model_id ?? "",
5530
5000
  availableModels: state.available_models.map((modelId) => ({
5531
5001
  modelId,
5532
- name: modelId
5002
+ name: state.available_model_names && Object.hasOwn(state.available_model_names, modelId) ? state.available_model_names[modelId] : modelId
5533
5003
  }))
5534
5004
  };
5535
5005
  }
@@ -5543,11 +5013,13 @@ function advertisedModelState(state) {
5543
5013
  function applyAdvertisedModelState(state, models) {
5544
5014
  state.current_model_id = models.currentModelId;
5545
5015
  state.available_models = models.availableModels.map((model) => model.modelId);
5016
+ state.available_model_names = Object.fromEntries(models.availableModels.map((model) => [model.modelId, model.name]));
5546
5017
  state.model_control = models.configId ? "config_option" : "legacy_set_model";
5547
5018
  }
5548
5019
  function clearAdvertisedModelState(state) {
5549
5020
  delete state.current_model_id;
5550
5021
  delete state.available_models;
5022
+ delete state.available_model_names;
5551
5023
  delete state.model_control;
5552
5024
  }
5553
5025
  function removeModelConfigOptions(state) {
@@ -5901,6 +5373,7 @@ function cloneSessionAcpxState(state) {
5901
5373
  desired_config_options: state.desired_config_options ? { ...state.desired_config_options } : void 0,
5902
5374
  current_model_id: state.current_model_id,
5903
5375
  available_models: state.available_models ? [...state.available_models] : void 0,
5376
+ ...state.available_model_names ? { available_model_names: { ...state.available_model_names } } : {},
5904
5377
  model_control: state.model_control,
5905
5378
  available_commands: state.available_commands ? state.available_commands.map((command) => ({ ...command })) : void 0,
5906
5379
  config_options: state.config_options ? deepClone(state.config_options) : void 0,
@@ -5920,10 +5393,10 @@ function cloneSessionOptions(options) {
5920
5393
  function cloneSystemPromptOption(option) {
5921
5394
  return typeof option === "string" ? option : { append: option.append };
5922
5395
  }
5923
- function recordPromptSubmission(conversation, prompt, timestamp = isoNow()) {
5396
+ function recordPromptSubmission(conversation, prompt, timestamp = isoNow(), messageId) {
5924
5397
  const userContent = (typeof prompt === "string" ? textPrompt(prompt) : prompt).map((content) => contentToUserContent(content)).filter((content) => content !== void 0);
5925
5398
  if (userContent.length === 0) return;
5926
- const promptMessageId = nextUserMessageId();
5399
+ const promptMessageId = messageId ?? nextUserMessageId();
5927
5400
  conversation.messages.push({ User: {
5928
5401
  id: promptMessageId,
5929
5402
  content: userContent.map((content) => {
@@ -5949,10 +5422,10 @@ function hasAgentReplyAfterPrompt(conversation, promptMessageId) {
5949
5422
  }
5950
5423
  return false;
5951
5424
  }
5952
- function recordSessionUpdate(conversation, state, notification, timestamp = isoNow()) {
5425
+ function recordSessionUpdate(conversation, state, notification, timestamp = isoNow(), userMessageId) {
5953
5426
  const acpx = ensureAcpxState$1(state);
5954
5427
  const update = notification.update;
5955
- applySessionUpdate(conversation, acpx, update);
5428
+ applySessionUpdate(conversation, acpx, update, userMessageId);
5956
5429
  updateConversationTimestamp(conversation, timestamp);
5957
5430
  trimConversationForRuntime(conversation);
5958
5431
  return acpx;
@@ -5965,13 +5438,13 @@ function recordPromptResponseUsage(conversation, usage, promptMessageId, timesta
5965
5438
  trimConversationForRuntime(conversation);
5966
5439
  return true;
5967
5440
  }
5968
- function applySessionUpdate(conversation, acpx, update) {
5441
+ function applySessionUpdate(conversation, acpx, update, userMessageId) {
5969
5442
  const handler = SESSION_UPDATE_HANDLERS[update.sessionUpdate];
5970
- handler?.(conversation, acpx, update);
5443
+ handler?.(conversation, acpx, update, userMessageId);
5971
5444
  }
5972
5445
  const SESSION_UPDATE_HANDLERS = {
5973
- user_message_chunk: (conversation, _acpx, update) => {
5974
- if (update.sessionUpdate === "user_message_chunk") appendUserMessageChunk(conversation, update.content);
5446
+ user_message_chunk: (conversation, _acpx, update, userMessageId) => {
5447
+ if (update.sessionUpdate === "user_message_chunk") appendUserMessageChunk(conversation, update.content, userMessageId);
5975
5448
  },
5976
5449
  agent_message_chunk: (conversation, _acpx, update) => {
5977
5450
  if (update.sessionUpdate === "agent_message_chunk") appendAgentMessageChunk(conversation, update.content, appendAgentText);
@@ -6001,11 +5474,11 @@ const SESSION_UPDATE_HANDLERS = {
6001
5474
  if (update.sessionUpdate === "config_option_update") applyConfigOptionsModelState(acpx, deepClone(update.configOptions));
6002
5475
  }
6003
5476
  };
6004
- function appendUserMessageChunk(conversation, content) {
5477
+ function appendUserMessageChunk(conversation, content, messageId) {
6005
5478
  const userContent = contentToUserContent(content);
6006
5479
  if (!userContent) return;
6007
5480
  conversation.messages.push({ User: {
6008
- id: nextUserMessageId(),
5481
+ id: messageId ?? nextUserMessageId(),
6009
5482
  content: [userContent]
6010
5483
  } });
6011
5484
  }
@@ -6029,7 +5502,7 @@ function applySessionInfoUpdate(conversation, update) {
6029
5502
  if (hasOwn(update, "title")) conversation.title = update.title ?? null;
6030
5503
  if (hasOwn(update, "updatedAt")) conversation.updated_at = update.updatedAt ?? conversation.updated_at;
6031
5504
  }
6032
- function recordClientOperation(conversation, state, operation, timestamp = isoNow()) {
5505
+ function recordClientOperation(conversation, state, _operation, timestamp = isoNow()) {
6033
5506
  const acpx = ensureAcpxState$1(state);
6034
5507
  updateConversationTimestamp(conversation, timestamp);
6035
5508
  trimConversationForRuntime(conversation);
@@ -6068,6 +5541,147 @@ function trimRuntimeToolResult(result) {
6068
5541
  if (typeof result.output === "string") result.output = trimRuntimeText(result.output, MAX_RUNTIME_TOOL_IO_CHARS);
6069
5542
  }
6070
5543
  //#endregion
5544
+ //#region src/runtime/engine/lifecycle.ts
5545
+ function createInitialSessionRecord(params) {
5546
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5547
+ return {
5548
+ schema: "acpx.session.v1",
5549
+ acpxRecordId: params.recordId,
5550
+ acpSessionId: params.sessionId,
5551
+ agentSessionId: params.agentSessionId,
5552
+ agentCommand: params.agentCommand,
5553
+ agentArgv: params.agentArgv,
5554
+ cwd: params.cwd,
5555
+ name: params.name,
5556
+ createdAt: now,
5557
+ lastUsedAt: now,
5558
+ lastSeq: 0,
5559
+ eventLog: defaultSessionEventLog(params.recordId),
5560
+ closed: false,
5561
+ closedAt: void 0,
5562
+ ...createSessionConversation(now),
5563
+ acpx: {}
5564
+ };
5565
+ }
5566
+ function applyLifecycleSnapshotToRecord(record, snapshot) {
5567
+ if (!snapshot) return;
5568
+ record.pid = snapshot.running ? snapshot.pid : void 0;
5569
+ record.agentStartedAt = snapshot.startedAt;
5570
+ if (snapshot.lastExit) {
5571
+ record.lastAgentExitCode = snapshot.lastExit.exitCode;
5572
+ record.lastAgentExitSignal = snapshot.lastExit.signal;
5573
+ record.lastAgentExitAt = snapshot.lastExit.exitedAt;
5574
+ record.lastAgentDisconnectReason = snapshot.lastExit.reason;
5575
+ return;
5576
+ }
5577
+ record.lastAgentExitCode = void 0;
5578
+ record.lastAgentExitSignal = void 0;
5579
+ record.lastAgentExitAt = void 0;
5580
+ record.lastAgentDisconnectReason = void 0;
5581
+ }
5582
+ function reconcileAgentSessionId(record, agentSessionId) {
5583
+ const normalized = normalizeAgentSessionId(agentSessionId);
5584
+ if (!normalized) return;
5585
+ record.agentSessionId = normalized;
5586
+ }
5587
+ function sessionHasAgentMessages(recordOrConversation) {
5588
+ return recordOrConversation.messages.some((message) => typeof message === "object" && message !== null && "Agent" in message);
5589
+ }
5590
+ function applyConversation(record, conversation) {
5591
+ record.title = conversation.title;
5592
+ record.updated_at = conversation.updated_at;
5593
+ record.messages = conversation.messages;
5594
+ record.cumulative_token_usage = conversation.cumulative_token_usage;
5595
+ record.cumulative_cost = conversation.cumulative_cost;
5596
+ record.request_token_usage = conversation.request_token_usage;
5597
+ }
5598
+ //#endregion
5599
+ //#region src/runtime/engine/session-options.ts
5600
+ function mergeSessionOptions(preferred, fallback) {
5601
+ const merged = { ...fallback };
5602
+ assignDefinedOption(merged, "model", preferred?.model);
5603
+ assignDefinedOption(merged, "allowedTools", preferred?.allowedTools);
5604
+ assignDefinedOption(merged, "maxTurns", preferred?.maxTurns);
5605
+ assignDefinedOption(merged, "systemPrompt", preferred?.systemPrompt);
5606
+ assignDefinedOption(merged, "env", mergeEnvRecords(fallback?.env, preferred?.env));
5607
+ return Object.keys(merged).length > 0 ? merged : void 0;
5608
+ }
5609
+ function mergeEnvRecords(fallback, preferred) {
5610
+ if (!fallback && !preferred) return;
5611
+ return {
5612
+ ...fallback,
5613
+ ...preferred
5614
+ };
5615
+ }
5616
+ function assignDefinedOption(target, key, value) {
5617
+ if (value !== void 0) target[key] = value;
5618
+ }
5619
+ function persistSessionOptions(record, options) {
5620
+ const next = options === void 0 ? void 0 : persistedSessionOptions(options);
5621
+ if (next !== void 0) {
5622
+ record.acpx = {
5623
+ ...record.acpx,
5624
+ session_options: next
5625
+ };
5626
+ return;
5627
+ }
5628
+ if (!record.acpx) return;
5629
+ delete record.acpx.session_options;
5630
+ }
5631
+ function sessionOptionsFromRecord(record) {
5632
+ const stored = record.acpx?.session_options;
5633
+ if (!stored) return;
5634
+ const sessionOptions = {};
5635
+ assignDefinedOption(sessionOptions, "model", nonEmptyString(stored.model));
5636
+ assignDefinedOption(sessionOptions, "allowedTools", storedAllowedTools(stored.allowed_tools));
5637
+ assignDefinedOption(sessionOptions, "maxTurns", storedMaxTurns(stored.max_turns));
5638
+ assignDefinedOption(sessionOptions, "systemPrompt", normalizeSystemPromptOption(stored.system_prompt));
5639
+ assignDefinedOption(sessionOptions, "env", storedEnvRecord(stored.env));
5640
+ return Object.keys(sessionOptions).length > 0 ? sessionOptions : void 0;
5641
+ }
5642
+ function persistedSessionOptions(options) {
5643
+ const next = {
5644
+ model: nonEmptyString(options.model),
5645
+ allowed_tools: Array.isArray(options.allowedTools) ? [...options.allowedTools] : void 0,
5646
+ max_turns: typeof options.maxTurns === "number" ? options.maxTurns : void 0,
5647
+ system_prompt: normalizeSystemPromptOption(options.systemPrompt),
5648
+ env: storedEnvRecord(options.env)
5649
+ };
5650
+ return hasPersistedSessionOptions(next) ? next : void 0;
5651
+ }
5652
+ function hasPersistedSessionOptions(options) {
5653
+ return options.model !== void 0 || options.allowed_tools !== void 0 || options.max_turns !== void 0 || options.system_prompt !== void 0 || options.env !== void 0;
5654
+ }
5655
+ function storedEnvRecord(value) {
5656
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return;
5657
+ const entries = Object.entries(value);
5658
+ const result = {};
5659
+ for (const [key, raw] of entries) {
5660
+ if (typeof raw !== "string") continue;
5661
+ result[key] = raw;
5662
+ }
5663
+ return Object.keys(result).length > 0 ? result : void 0;
5664
+ }
5665
+ function normalizeSystemPromptOption(value) {
5666
+ const prompt = nonEmptyString(value);
5667
+ if (prompt !== void 0) return prompt;
5668
+ const append = appendedSystemPrompt(value);
5669
+ return append === void 0 ? void 0 : { append };
5670
+ }
5671
+ function appendedSystemPrompt(value) {
5672
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return;
5673
+ return nonEmptyString(value.append);
5674
+ }
5675
+ function storedAllowedTools(value) {
5676
+ return Array.isArray(value) && value.every((item) => typeof item === "string") ? [...value] : void 0;
5677
+ }
5678
+ function storedMaxTurns(value) {
5679
+ return typeof value === "number" ? value : void 0;
5680
+ }
5681
+ function nonEmptyString(value) {
5682
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
5683
+ }
5684
+ //#endregion
6071
5685
  //#region src/session/mode-preference.ts
6072
5686
  function ensureAcpxState(state) {
6073
5687
  return state ?? {};
@@ -6158,6 +5772,11 @@ function applyConfigOptionsToRecord(record, result) {
6158
5772
  if (!configOptions) return;
6159
5773
  record.acpx = applyConfigOptionsToState(record.acpx, configOptions);
6160
5774
  }
5775
+ function applyInitialModelSelection(record, originalModels, requestedModel, modelApplication) {
5776
+ applyConfigOptionsToRecord(record, modelApplication.response);
5777
+ syncAdvertisedModelState(record, modelApplication.response ? modelStateFromConfigOptions(modelApplication.response.configOptions) : originalModels);
5778
+ if (modelApplication.applied) setCurrentModelId(record, currentModelIdFromSetModelResponse(modelApplication.response, requestedModel));
5779
+ }
6161
5780
  function applyAcceptedConfigOptions(state, response) {
6162
5781
  const next = cloneSessionAcpxState(state) ?? {};
6163
5782
  if (!response) return next;
@@ -6192,15 +5811,6 @@ function applyConfigOptionSelection(state, configId, value, response, modelConfi
6192
5811
  }
6193
5812
  //#endregion
6194
5813
  //#region src/runtime/engine/reconnect.ts
6195
- function isProcessAlive(pid) {
6196
- if (!pid || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
6197
- try {
6198
- process.kill(pid, 0);
6199
- return true;
6200
- } catch {
6201
- return false;
6202
- }
6203
- }
6204
5814
  const SESSION_LOAD_UNSUPPORTED_CODES = /* @__PURE__ */ new Set([-32601, -32602]);
6205
5815
  function shouldFallbackToNewSession(error, record) {
6206
5816
  if (isHardReconnectFailure(error)) return false;
@@ -6318,12 +5928,6 @@ async function connectAndLoadSession(options) {
6318
5928
  record.closed = false;
6319
5929
  record.closedAt = void 0;
6320
5930
  options.onConnectedRecord?.(record);
6321
- let resumed = false;
6322
- let loadError;
6323
- let sessionId = record.acpSessionId;
6324
- let createdFreshSession = false;
6325
- let pendingAgentSessionId = record.agentSessionId;
6326
- let sessionModels;
6327
5931
  const loadState = await loadOrCreateRuntimeSession({
6328
5932
  client,
6329
5933
  record,
@@ -6331,12 +5935,7 @@ async function connectAndLoadSession(options) {
6331
5935
  sameSessionOnly,
6332
5936
  timeoutMs: options.timeoutMs
6333
5937
  });
6334
- resumed = loadState.resumed;
6335
- loadError = loadState.loadError;
6336
- sessionId = loadState.sessionId;
6337
- createdFreshSession = loadState.createdFreshSession;
6338
- pendingAgentSessionId = loadState.pendingAgentSessionId;
6339
- sessionModels = loadState.sessionModels;
5938
+ const { resumed, loadError, sessionId, createdFreshSession, pendingAgentSessionId, sessionModels } = loadState;
6340
5939
  const preferenceReplay = await replaySessionPreferences({
6341
5940
  client,
6342
5941
  record,
@@ -6474,35 +6073,17 @@ async function loadOrCreateRuntimeSession(params) {
6474
6073
  resumed: true,
6475
6074
  createdFreshSession: false
6476
6075
  };
6477
- if (params.client.supportsResumeSession()) return await resumeRuntimeSession(params);
6478
- if (params.client.supportsLoadSession()) return await loadRuntimeSession(params);
6076
+ if (params.client.supportsResumeSession()) return await loadRuntimeSession(params, true);
6077
+ if (params.client.supportsLoadSession()) return await loadRuntimeSession(params, false);
6479
6078
  if (params.sameSessionOnly) throw makeSessionResumeRequiredError({
6480
6079
  record: params.record,
6481
6080
  reason: "agent does not support session/resume or session/load"
6482
6081
  });
6483
6082
  return await createFreshRuntimeSession(params.client, params.record, params.timeoutMs);
6484
6083
  }
6485
- async function resumeRuntimeSession(params) {
6486
- try {
6487
- const resumeResult = await withTimeout(params.client.resumeSession(params.record.acpSessionId, params.record.cwd), params.timeoutMs);
6488
- reconcileAgentSessionId(params.record, resumeResult.agentSessionId);
6489
- applyConfigOptionsToRecord(params.record, resumeResult);
6490
- return {
6491
- sessionId: params.record.acpSessionId,
6492
- pendingAgentSessionId: params.record.agentSessionId,
6493
- sessionModels: resumeResult.models,
6494
- configOptionsPresent: resumeResult.configOptionsPresent,
6495
- legacyModelMetadataPresent: resumeResult.legacyModelMetadataPresent,
6496
- resumed: true,
6497
- createdFreshSession: false
6498
- };
6499
- } catch (error) {
6500
- return await recoverRuntimeSessionLoadFailure(params, error);
6501
- }
6502
- }
6503
- async function loadRuntimeSession(params) {
6084
+ async function loadRuntimeSession(params, resume) {
6504
6085
  try {
6505
- const loadResult = await withTimeout(params.client.loadSessionWithOptions(params.record.acpSessionId, params.record.cwd, { suppressReplayUpdates: true }), params.timeoutMs);
6086
+ const loadResult = await withTimeout(resume ? params.client.resumeSession(params.record.acpSessionId, params.record.cwd) : params.client.loadSessionWithOptions(params.record.acpSessionId, params.record.cwd, { suppressReplayUpdates: true }), params.timeoutMs);
6506
6087
  reconcileAgentSessionId(params.record, loadResult.agentSessionId);
6507
6088
  applyConfigOptionsToRecord(params.record, loadResult);
6508
6089
  return {
@@ -6619,7 +6200,7 @@ async function withConnectedSession(options) {
6619
6200
  resumed,
6620
6201
  loadError
6621
6202
  });
6622
- const now = isoNow$2();
6203
+ const now = isoNow$1();
6623
6204
  record.lastUsedAt = now;
6624
6205
  record.closed = false;
6625
6206
  record.closedAt = void 0;
@@ -6640,7 +6221,7 @@ async function withConnectedSession(options) {
6640
6221
  });
6641
6222
  else await client.cancelActivePrompt(2500);
6642
6223
  applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
6643
- record.lastUsedAt = isoNow$2();
6224
+ record.lastUsedAt = isoNow$1();
6644
6225
  await options.saveRecord(record).catch(() => {});
6645
6226
  await client.close();
6646
6227
  });
@@ -6666,10 +6247,13 @@ function recoveredSessionResult(response, conversation, promptMessageId) {
6666
6247
  ...responseMetaField(response?._meta)
6667
6248
  };
6668
6249
  }
6250
+ function abortTimedOutRequests(error, client, sessionId) {
6251
+ if (error instanceof TimeoutError) client.endPromptElicitation?.(sessionId);
6252
+ }
6669
6253
  async function runPromptTurn(params) {
6670
6254
  let settledResponse;
6671
6255
  try {
6672
- const promptPromise = params.client.prompt(params.sessionId, params.prompt, params.onPromptRequestWritten, params.onElicitation);
6256
+ const promptPromise = params.client.prompt(params.sessionId, params.prompt, params.onPromptRequestWritten, params.onElicitation, params.onPermissionRequest);
6673
6257
  promptPromise.then((response) => {
6674
6258
  settledResponse = response;
6675
6259
  }, () => {});
@@ -6686,6 +6270,7 @@ async function runPromptTurn(params) {
6686
6270
  ...responseMetaField(response._meta)
6687
6271
  };
6688
6272
  } catch (error) {
6273
+ abortTimedOutRequests(error, params.client, params.sessionId);
6689
6274
  if (!(error instanceof TimeoutError) || !params.promptMessageId) throw error;
6690
6275
  await params.client.waitForSessionUpdatesIdle?.({
6691
6276
  idleMs: SESSION_REPLY_IDLE_MS,
@@ -6749,6 +6334,6 @@ var LiveSessionCheckpoint = class {
6749
6334
  }
6750
6335
  };
6751
6336
  //#endregion
6752
- export { formatPerfMetric as $, AgentSpawnError as $t, RequestedModelUnsupportedError as A, DEFAULT_AGENT_NAME as At, absolutePath as B, extractAcpError as Bt, sessionOptionsFromRecord as C, parsePromptSource as Ct, AcpClient as D, TimeoutError as Dt, reconcileAgentSessionId as E, InterruptedError as Et, runTimedExecFile as F, resolveCanonicalAgentName as Ft, listSessions as G, NON_INTERACTIVE_PERMISSION_POLICIES as Gt, findSession as H, toAcpErrorPayload as Ht, splitCommandLine as I, exitCodeForOutputErrorCode as It, pruneSessions as J, OUTPUT_FORMATS as Jt, listSessionsForAgent as K, OUTPUT_ERROR_CODES as Kt, getAcpxVersion as L, formatErrorMessage as Lt, modelStateFromConfigOptions as M, normalizeAgentName$1 as Mt, normalizeAgentCommandInput as N, resolveAgentArgv as Nt, REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE as O, withInterrupt as Ot, renderArgvIdentity as P, resolveAgentCommand as Pt, assertPersistedKeyPolicy as Q, AcpxOperationalError as Qt, permissionModeSatisfies as R, isRetryablePromptError as Rt, persistSessionOptions as S, mergePromptSourceWithText as St, applyLifecycleSnapshotToRecord as T, textPrompt as Tt, findSessionByDirectoryWalk as U, AUTH_POLICIES as Ut, findGitRepositoryRoot as V, isAcpResourceNotFoundError as Vt, isoNow$2 as W, EXIT_CODES as Wt, writeSessionRecord as X, PERMISSION_POLICY_ACTIONS as Xt, resolveSessionRecord as Y, PERMISSION_MODES as Yt, createAtomicWriteTempPath as Z, SESSION_RECORD_SCHEMA as Zt, recordPromptSubmission as _, isAcpJsonRpcMessage as _t, applyConfigOptionSelection as a, setPerfGauge as at, advertisedModelState as b, PromptInputValidationError as bt, applyRequestedModelIfAdvertised as c, serializeSessionRecordForDisk as ct, setDesiredModeId as d, defaultSessionEventLog as dt, QueueConnectionError as en, getPerfMetricsSnapshot as et, syncAdvertisedModelState as f, sessionBaseDir$1 as ft, recordClientOperation as g, extractSessionUpdateNotification as gt, createSessionConversation as h, sessionEventSegmentPath as ht, connectAndLoadSession as i, resetPerfMetrics as it, isRequestedModelUnsupportedError as j, listBuiltInAgents as jt, REQUESTED_MODEL_UNSUPPORTED_REASONS as k, withTimeout as kt, currentModelIdFromSetModelResponse as l, normalizeRuntimeSessionId as lt, cloneSessionConversation as m, sessionEventLockPath as mt, runPromptTurn as n, measurePerf as nt, applyConfigOptionsToRecord as o, startPerfTimer as ot, cloneSessionAcpxState as p, sessionEventActivePath as pt, normalizeName as q, OUTPUT_ERROR_ORIGINS as qt, withConnectedSession as r, recordPerfDuration as rt, applyModelSelection as s, parseSessionRecord as st, LiveSessionCheckpoint as t, QueueProtocolError as tn, incrementPerfCounter as tt, setCurrentModelId as u, DEFAULT_EVENT_SEGMENT_MAX_BYTES as ut, recordSessionUpdate as v, parseJsonRpcErrorMessage as vt, applyConversation as w, promptToDisplayText as wt, mergeSessionOptions as x, isPromptInput as xt, trimConversationForRuntime as y, parsePromptStopReason as yt, DEFAULT_HISTORY_LIMIT as z, normalizeOutputError as zt };
6337
+ export { getPerfMetricsSnapshot as $, isRequestedModelUnsupportedError as A, isRetryablePromptError as At, absolutePath as B, OUTPUT_FORMATS as Bt, recordSessionUpdate as C, isLegacyZedCodexAcpInvocation as Ct, REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE as D, withTimeout as Dt, AcpClient as E, withInterrupt as Et, parsePromptSource as F, AUTH_POLICIES as Ft, listSessions as G, AgentSpawnError as Gt, findSession as H, PERMISSION_POLICY_ACTIONS as Ht, promptToDisplayText as I, EXIT_CODES as It, pruneSessions as J, listSessionsForAgent as K, QueueConnectionError as Kt, textPrompt as L, NON_INTERACTIVE_PERMISSION_POLICIES as Lt, PromptInputValidationError as M, extractAcpError as Mt, isPromptInput as N, isAcpResourceNotFoundError as Nt, REQUESTED_MODEL_UNSUPPORTED_REASONS as O, exitCodeForOutputErrorCode as Ot, mergePromptSourceWithText as P, toAcpErrorPayload as Pt, formatPerfMetric as Q, permissionModeSatisfies as R, OUTPUT_ERROR_CODES as Rt, recordPromptSubmission as S, parsePromptStopReason as St, advertisedModelState as T, TimeoutError as Tt, findSessionByDirectoryWalk as U, SESSION_RECORD_SCHEMA as Ut, findGitRepositoryRoot as V, PERMISSION_MODES as Vt, isoNow$1 as W, AcpxOperationalError as Wt, writeSessionRecord as X, resolveSessionRecord as Y, assertPersistedKeyPolicy as Z, reconcileAgentSessionId as _, isProcessAlive as _t, applyConfigOptionSelection as a, startPerfTimer as at, createSessionConversation as b, parseJsonRpcErrorMessage as bt, applyModelSelection as c, normalizeAgentSessionId as ct, mergeSessionOptions as d, sessionBaseDir as dt, incrementPerfCounter as et, persistSessionOptions as f, sessionEventActivePath as ft, createInitialSessionRecord as g, writePrivateJsonFile as gt, applyLifecycleSnapshotToRecord as h, writePrivateFile as ht, connectAndLoadSession as i, setPerfGauge as it, getAcpxVersion as j, normalizeOutputError as jt, RequestedModelUnsupportedError as k, formatErrorMessage as kt, applyRequestedModelIfAdvertised as l, DEFAULT_EVENT_SEGMENT_MAX_BYTES as lt, applyConversation as m, sessionEventSegmentPath as mt, runPromptTurn as n, recordPerfDuration as nt, applyConfigOptionsToRecord as o, parseSessionRecord as ot, sessionOptionsFromRecord as p, sessionEventLockPath as pt, normalizeName as q, QueueProtocolError as qt, withConnectedSession as r, resetPerfMetrics as rt, applyInitialModelSelection as s, serializeSessionRecordForDisk as st, LiveSessionCheckpoint as t, measurePerf as tt, setDesiredModeId as u, defaultSessionEventLog as ut, cloneSessionAcpxState as v, extractSessionUpdateNotification as vt, trimConversationForRuntime as w, InterruptedError as wt, recordClientOperation as x, parsePermissionNotice as xt, cloneSessionConversation as y, isAcpJsonRpcMessage as yt, DEFAULT_HISTORY_LIMIT as z, OUTPUT_ERROR_ORIGINS as zt };
6753
6338
 
6754
- //# sourceMappingURL=live-checkpoint-sB7dFOYR.js.map
6339
+ //# sourceMappingURL=live-checkpoint-BEfxBKCh.js.map