agent-shim 8.9.0 → 8.11.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.
package/dist/index.mjs CHANGED
@@ -54,8 +54,113 @@ var categories_default_default = {
54
54
  };
55
55
 
56
56
  // src/config/schema.ts
57
- import path from "node:path";
57
+ import path2 from "node:path";
58
58
  import { z } from "zod";
59
+
60
+ // src/versionDiscovery.ts
61
+ import os from "node:os";
62
+ import path from "node:path";
63
+
64
+ // src/cliError.ts
65
+ var EXIT_FAILURE = 1;
66
+ var EXIT_USAGE = 2;
67
+ var CliError = class extends Error {
68
+ /** The process exit status this failure maps to. `EXIT_FAILURE` unless a subclass says otherwise. */
69
+ exitCode = EXIT_FAILURE;
70
+ };
71
+ var UsageError = class extends CliError {
72
+ exitCode = EXIT_USAGE;
73
+ constructor(message) {
74
+ super(message);
75
+ this.name = "UsageError";
76
+ }
77
+ };
78
+
79
+ // src/versionDiscovery.ts
80
+ function defaultVersionsDir() {
81
+ return path.join(os.homedir(), ".local", "share", "claude", "versions");
82
+ }
83
+ var NUMERIC_DOTTED_VERSION_RE = /^\d+(?:\.\d+)*$/;
84
+ function isNumericDottedVersion(name) {
85
+ return NUMERIC_DOTTED_VERSION_RE.test(name);
86
+ }
87
+ function compareVersions(a, b) {
88
+ if (!isNumericDottedVersion(a)) {
89
+ throw new Error(`compareVersions: "${a}" is not a valid plain dotted-numeric version`);
90
+ }
91
+ if (!isNumericDottedVersion(b)) {
92
+ throw new Error(`compareVersions: "${b}" is not a valid plain dotted-numeric version`);
93
+ }
94
+ const segmentsA = a.split(".").map(Number);
95
+ const segmentsB = b.split(".").map(Number);
96
+ const length = Math.max(segmentsA.length, segmentsB.length);
97
+ for (let i = 0; i < length; i += 1) {
98
+ const segmentA = segmentsA[i] ?? 0;
99
+ const segmentB = segmentsB[i] ?? 0;
100
+ if (segmentA !== segmentB) {
101
+ return segmentA - segmentB;
102
+ }
103
+ }
104
+ return 0;
105
+ }
106
+ var ClaudeVersionNotInstalledError = class extends CliError {
107
+ constructor(version, versionsDir, installed) {
108
+ super(
109
+ `Claude Code ${version} is pinned for this launch but is not installed under "${versionsDir}"` + (installed.length === 0 ? " (no versions are installed there)." : `. Installed: ${installed.join(", ")}.`) + " Install it, or change or remove the pin (--claude-version, AGENT_SHIM_CLAUDE_VERSION, or launch.claudeVersion)."
110
+ );
111
+ this.version = version;
112
+ this.versionsDir = versionsDir;
113
+ this.installed = installed;
114
+ this.name = "ClaudeVersionNotInstalledError";
115
+ }
116
+ version;
117
+ versionsDir;
118
+ installed;
119
+ };
120
+ function installedVersions(entries) {
121
+ return entries.filter((entry) => entry.isFile && entry.isExecutable && entry.sizeBytes > 0 && isNumericDottedVersion(entry.name)).map((entry) => entry.name).sort(compareVersions);
122
+ }
123
+ function pickHighestVersion(entries) {
124
+ const candidates = entries.filter(
125
+ (entry) => entry.isFile && entry.isExecutable && entry.sizeBytes > 0 && isNumericDottedVersion(entry.name)
126
+ );
127
+ if (candidates.length === 0) {
128
+ return void 0;
129
+ }
130
+ return candidates.reduce(
131
+ (highest, candidate) => compareVersions(candidate.name, highest.name) > 0 ? candidate : highest
132
+ ).name;
133
+ }
134
+ function discoverClaudeBinary(options) {
135
+ const versionsDir = options.versionsDir ?? defaultVersionsDir();
136
+ const entries = options.listVersionsDir(versionsDir);
137
+ const highestVersion = pickHighestVersion(entries);
138
+ if (options.version !== void 0) {
139
+ const installed = installedVersions(entries);
140
+ if (!installed.includes(options.version)) {
141
+ throw new ClaudeVersionNotInstalledError(options.version, versionsDir, installed);
142
+ }
143
+ return { path: path.join(versionsDir, options.version), source: "versions-dir", version: options.version };
144
+ }
145
+ if (highestVersion !== void 0) {
146
+ return {
147
+ path: path.join(versionsDir, highestVersion),
148
+ source: "versions-dir",
149
+ version: highestVersion
150
+ };
151
+ }
152
+ for (const dir of options.pathDirs) {
153
+ const found = options.findExecutableInDir(dir, "claude");
154
+ if (found !== void 0 && !options.isOwnBinary(found)) {
155
+ return { path: found, source: "path-fallback" };
156
+ }
157
+ }
158
+ throw new Error(
159
+ `Could not find a claude binary. Looked for genuinely-executable versioned binaries under "${versionsDir}" (missing, empty, or containing nothing executable) and for a "claude" executable on PATH (never this tool's own binary). Install Claude Code, or set the versions directory / PATH correctly, then try again.`
160
+ );
161
+ }
162
+
163
+ // src/config/schema.ts
59
164
  var CATEGORY_NAMES = ["secret", "runtime", "history", "knowledge", "settings"];
60
165
  var OVERRIDABLE_CATEGORIES = ["runtime", "history", "knowledge", "settings"];
61
166
  function isOverridableCategory(name) {
@@ -99,7 +204,9 @@ var LaunchSchema = z.strictObject({
99
204
  remoteControl: z.boolean().optional(),
100
205
  provider: z.string().min(1).optional(),
101
206
  headroom: z.boolean().optional(),
102
- trackUsage: z.boolean().optional()
207
+ trackUsage: z.boolean().optional(),
208
+ /** The exact Claude Code version to run, as it is named in Claude Code's versions directory (`2.1.220`). A pin that is not installed fails the launch; it never falls back to the highest version. */
209
+ claudeVersion: z.string().regex(NUMERIC_DOTTED_VERSION_RE, "must be an exact dotted-numeric Claude Code version such as 2.1.220").optional()
103
210
  });
104
211
  var CREDENTIAL_TARGETS = ["bearer", "apiKey", "oauthToken"];
105
212
  var PROVIDER_CREDENTIAL_TARGETS = ["bearer", "apiKey"];
@@ -118,7 +225,7 @@ var CommandSourceOptions = {
118
225
  var CredentialSourceSchema = z.union([
119
226
  z.strictObject({ env: z.string().min(1) }),
120
227
  z.strictObject({
121
- file: z.string().min(1).refine((filePath) => filePath.startsWith("~/") || path.isAbsolute(filePath), {
228
+ file: z.string().min(1).refine((filePath) => filePath.startsWith("~/") || path2.isAbsolute(filePath), {
122
229
  message: "a credential file must be an absolute or ~-rooted path, since a launch can start in any directory"
123
230
  })
124
231
  }),
@@ -297,28 +404,11 @@ var SHIPPED_CATEGORY_DEFAULTS = Object.freeze({
297
404
 
298
405
  // src/config/store.ts
299
406
  import fs from "node:fs";
300
- import path2 from "node:path";
407
+ import path3 from "node:path";
301
408
  import { randomUUID } from "node:crypto";
302
409
 
303
410
  // src/config/load.ts
304
411
  import { cosmiconfigSync } from "cosmiconfig";
305
-
306
- // src/cliError.ts
307
- var EXIT_FAILURE = 1;
308
- var EXIT_USAGE = 2;
309
- var CliError = class extends Error {
310
- /** The process exit status this failure maps to. `EXIT_FAILURE` unless a subclass says otherwise. */
311
- exitCode = EXIT_FAILURE;
312
- };
313
- var UsageError = class extends CliError {
314
- exitCode = EXIT_USAGE;
315
- constructor(message) {
316
- super(message);
317
- this.name = "UsageError";
318
- }
319
- };
320
-
321
- // src/config/load.ts
322
412
  function createExplorer(moduleName = "agent-shim") {
323
413
  return cosmiconfigSync(moduleName, { searchPlaces: [] });
324
414
  }
@@ -439,9 +529,9 @@ function readJson(filePath, schema, storeFs = nodeStoreFs) {
439
529
  return result.data;
440
530
  }
441
531
  function writeTextAtomic(filePath, contents, storeFs = nodeStoreFs) {
442
- const dir = path2.dirname(filePath);
532
+ const dir = path3.dirname(filePath);
443
533
  storeFs.mkdirSync(dir);
444
- const tempPath = path2.join(dir, `.${path2.basename(filePath)}.${String(process.pid)}.${randomUUID()}.tmp`);
534
+ const tempPath = path3.join(dir, `.${path3.basename(filePath)}.${String(process.pid)}.${randomUUID()}.tmp`);
445
535
  storeFs.writeFileUtf8(tempPath, contents);
446
536
  try {
447
537
  storeFs.renameSync(tempPath, filePath);
@@ -465,9 +555,9 @@ function applyPatch(filePath, schema, patch, options = {}) {
465
555
  if (!result.success) {
466
556
  throw new ConfigValidationError(filePath, result.error.issues);
467
557
  }
468
- const validated = result.data;
469
- writeJsonAtomic(filePath, validated, storeFs);
470
- return validated;
558
+ const validated2 = result.data;
559
+ writeJsonAtomic(filePath, validated2, storeFs);
560
+ return validated2;
471
561
  }
472
562
 
473
563
  // src/config/classify.ts
@@ -890,6 +980,9 @@ function flattenLayers(layers, options) {
890
980
  if (layer.launch.trackUsage !== void 0) {
891
981
  launch.trackUsage = layer.launch.trackUsage;
892
982
  }
983
+ if (layer.launch.claudeVersion !== void 0) {
984
+ launch.claudeVersion = layer.launch.claudeVersion;
985
+ }
893
986
  }
894
987
  if (layer.entries === void 0) {
895
988
  continue;
@@ -1127,7 +1220,7 @@ function dedupeDiagnostics(diagnostics) {
1127
1220
  }
1128
1221
 
1129
1222
  // src/resolve/plan.ts
1130
- import path3 from "node:path";
1223
+ import path4 from "node:path";
1131
1224
  function parentOf(rel) {
1132
1225
  const index = rel.lastIndexOf("/");
1133
1226
  return index === -1 ? "" : rel.slice(0, index);
@@ -1177,7 +1270,7 @@ function planFarm(params) {
1177
1270
  }
1178
1271
  return value;
1179
1272
  };
1180
- const linkTarget = (rel) => path3.join(params.facts.claudeHome, rel);
1273
+ const linkTarget = (rel) => path4.join(params.facts.claudeHome, rel);
1181
1274
  const visit = (rel) => {
1182
1275
  const fact = params.facts.entries.get(rel);
1183
1276
  const decision = params.decisions.get(rel);
@@ -1255,7 +1348,7 @@ function descendPolicy(rules) {
1255
1348
  }
1256
1349
 
1257
1350
  // src/resolve/walk.ts
1258
- import path4 from "node:path";
1351
+ import path5 from "node:path";
1259
1352
 
1260
1353
  // src/resolve/extends.ts
1261
1354
  function lineariseProfile(rootName, load) {
@@ -1323,10 +1416,10 @@ function profileLayers(rootName, load, startId) {
1323
1416
 
1324
1417
  // src/resolve/walk.ts
1325
1418
  function walkDirectoryAncestors(cwd, options) {
1326
- const limit = path4.resolve(options.limit ?? options.home);
1419
+ const limit = path5.resolve(options.limit ?? options.home);
1327
1420
  const isReadable = options.isReadable ?? (() => true);
1328
1421
  const collected = [];
1329
- let current = path4.resolve(cwd);
1422
+ let current = path5.resolve(cwd);
1330
1423
  for (; ; ) {
1331
1424
  if (!isReadable(current)) {
1332
1425
  break;
@@ -1335,7 +1428,7 @@ function walkDirectoryAncestors(cwd, options) {
1335
1428
  if (current === limit) {
1336
1429
  break;
1337
1430
  }
1338
- const parent = path4.dirname(current);
1431
+ const parent = path5.dirname(current);
1339
1432
  if (parent === current) {
1340
1433
  break;
1341
1434
  }
@@ -1459,7 +1552,7 @@ function resolveDecisions(input) {
1459
1552
  }
1460
1553
 
1461
1554
  // src/launcher/farm.ts
1462
- import path6 from "node:path";
1555
+ import path7 from "node:path";
1463
1556
  import { z as z2 } from "zod";
1464
1557
 
1465
1558
  // src/resolve/reconcile.ts
@@ -1563,7 +1656,7 @@ var LEGACY_FARM_MANIFEST_FILENAME = ".claude-use-farm.json";
1563
1656
 
1564
1657
  // src/launcher/lock.ts
1565
1658
  import { randomUUID as randomUUID2 } from "node:crypto";
1566
- import path5 from "node:path";
1659
+ import path6 from "node:path";
1567
1660
  var DEFAULT_STALE_AFTER_MS = 12e4;
1568
1661
  var DEFAULT_RETRY_DELAY_MS = 50;
1569
1662
  var DEFAULT_MAX_ATTEMPTS = 200;
@@ -1582,7 +1675,7 @@ var IdentityLockBusyError = class extends CliError {
1582
1675
  holderPid;
1583
1676
  };
1584
1677
  function identityLockPath(dir, identity) {
1585
- return path5.join(dir, `.${identity}.lock`);
1678
+ return path6.join(dir, `.${identity}.lock`);
1586
1679
  }
1587
1680
  function parseLockRecord(raw) {
1588
1681
  if (raw === void 0) {
@@ -1661,7 +1754,7 @@ var FarmManifestSchema = z2.strictObject({
1661
1754
  links: z2.array(z2.strictObject({ rel: z2.string(), target: z2.string() }))
1662
1755
  });
1663
1756
  function readFarmManifest(fs12, farmRoot) {
1664
- const raw = fs12.readFileUtf8(path6.join(farmRoot, FARM_MANIFEST_FILENAME)) ?? fs12.readFileUtf8(path6.join(farmRoot, LEGACY_FARM_MANIFEST_FILENAME));
1757
+ const raw = fs12.readFileUtf8(path7.join(farmRoot, FARM_MANIFEST_FILENAME)) ?? fs12.readFileUtf8(path7.join(farmRoot, LEGACY_FARM_MANIFEST_FILENAME));
1665
1758
  if (raw === void 0) {
1666
1759
  return void 0;
1667
1760
  }
@@ -1677,7 +1770,7 @@ function readFarmManifest(fs12, farmRoot) {
1677
1770
  function buildEntryFacts(params) {
1678
1771
  const entries = /* @__PURE__ */ new Map();
1679
1772
  const visit = (rel) => {
1680
- const absolute = path6.join(params.claudeHome, rel);
1773
+ const absolute = path7.join(params.claudeHome, rel);
1681
1774
  const stat = params.fs.lstat(absolute);
1682
1775
  if (stat === void 0) {
1683
1776
  return void 0;
@@ -1720,7 +1813,7 @@ function buildEntryFacts(params) {
1720
1813
  };
1721
1814
  }
1722
1815
  function listSubtree(fs12, root, rel) {
1723
- const absolute = path6.join(root, rel);
1816
+ const absolute = path7.join(root, rel);
1724
1817
  const stat = fs12.lstat(absolute);
1725
1818
  if (stat === void 0) {
1726
1819
  return [];
@@ -1750,7 +1843,7 @@ function reconciliationScope(fs12, farmRoot, manifest) {
1750
1843
  if (manifest !== void 0) {
1751
1844
  return topLevelScopeRoots(manifest.materialised);
1752
1845
  }
1753
- return [...fs12.readdir(farmRoot)].filter((name) => name !== FARM_MANIFEST_FILENAME && name !== LEGACY_FARM_MANIFEST_FILENAME && fs12.lstat(path6.join(farmRoot, name))?.kind === "dir").sort();
1846
+ return [...fs12.readdir(farmRoot)].filter((name) => name !== FARM_MANIFEST_FILENAME && name !== LEGACY_FARM_MANIFEST_FILENAME && fs12.lstat(path7.join(farmRoot, name))?.kind === "dir").sort();
1754
1847
  }
1755
1848
  function collectFarmListing(fs12, farmRoot, scopeRoots) {
1756
1849
  return scopeRoots.flatMap((root) => listSubtree(fs12, farmRoot, root));
@@ -1758,7 +1851,7 @@ function collectFarmListing(fs12, farmRoot, scopeRoots) {
1758
1851
  function collectCanonicalCounterparts(fs12, claudeHome, farmListing) {
1759
1852
  const out = [];
1760
1853
  for (const entry of farmListing) {
1761
- const absolute = path6.join(claudeHome, entry.rel);
1854
+ const absolute = path7.join(claudeHome, entry.rel);
1762
1855
  const stat = fs12.lstat(absolute);
1763
1856
  if (stat === void 0) {
1764
1857
  continue;
@@ -1788,21 +1881,21 @@ function carryOver(params) {
1788
1881
  if (accounted.has(name)) {
1789
1882
  continue;
1790
1883
  }
1791
- const stat = params.fs.lstat(path6.join(params.previousRoot, name));
1884
+ const stat = params.fs.lstat(path7.join(params.previousRoot, name));
1792
1885
  if (stat === void 0 || stat.kind === "symlink") {
1793
1886
  continue;
1794
1887
  }
1795
- if (params.fs.lstat(path6.join(params.farmRoot, name)) !== void 0) {
1888
+ if (params.fs.lstat(path7.join(params.farmRoot, name)) !== void 0) {
1796
1889
  const category = params.classification === void 0 ? void 0 : classifyEntries([name], params.classification).classification.get(name);
1797
1890
  if (category === "runtime") {
1798
- params.fs.removeRecursive(path6.join(params.previousRoot, name));
1891
+ params.fs.removeRecursive(path7.join(params.previousRoot, name));
1799
1892
  autoResolved.push(name);
1800
1893
  continue;
1801
1894
  }
1802
1895
  collided.push(name);
1803
1896
  continue;
1804
1897
  }
1805
- params.fs.rename(path6.join(params.previousRoot, name), path6.join(params.farmRoot, name));
1898
+ params.fs.rename(path7.join(params.previousRoot, name), path7.join(params.farmRoot, name));
1806
1899
  carried.push(name);
1807
1900
  }
1808
1901
  return { carried, autoResolved, collided };
@@ -1810,9 +1903,9 @@ function carryOver(params) {
1810
1903
  function buildScratchTree(params) {
1811
1904
  params.fs.mkdirp(params.scratchRoot);
1812
1905
  for (const entry of params.plan.entries) {
1813
- const absolute = path6.join(params.scratchRoot, entry.rel);
1906
+ const absolute = path7.join(params.scratchRoot, entry.rel);
1814
1907
  if (entry.kind === "link") {
1815
- params.fs.mkdirp(path6.dirname(absolute));
1908
+ params.fs.mkdirp(path7.dirname(absolute));
1816
1909
  params.fs.symlink(entry.target, absolute);
1817
1910
  continue;
1818
1911
  }
@@ -1821,14 +1914,14 @@ function buildScratchTree(params) {
1821
1914
  }
1822
1915
  }
1823
1916
  params.fs.writeFileUtf8(
1824
- path6.join(params.scratchRoot, FARM_MANIFEST_FILENAME),
1917
+ path7.join(params.scratchRoot, FARM_MANIFEST_FILENAME),
1825
1918
  `${JSON.stringify(params.manifest, null, 2)}
1826
1919
  `
1827
1920
  );
1828
1921
  }
1829
1922
  function swapIn(params) {
1830
1923
  if (params.fs.lstat(params.farmRoot) === void 0) {
1831
- params.fs.mkdirp(path6.dirname(params.farmRoot));
1924
+ params.fs.mkdirp(path7.dirname(params.farmRoot));
1832
1925
  params.fs.rename(params.scratchRoot, params.farmRoot);
1833
1926
  return { carried: [], autoResolved: [], collided: [] };
1834
1927
  }
@@ -1852,14 +1945,14 @@ function recoverInterruptedSwap(params) {
1852
1945
  const names = [...params.fs.readdir(params.identitiesDir)].sort();
1853
1946
  const removedScratch = [];
1854
1947
  for (const name of names.filter((candidate) => candidate.startsWith(scratchPrefix))) {
1855
- params.fs.removeRecursive(path6.join(params.identitiesDir, name));
1948
+ params.fs.removeRecursive(path7.join(params.identitiesDir, name));
1856
1949
  removedScratch.push(name);
1857
1950
  }
1858
1951
  let previous = names.filter((candidate) => candidate.startsWith(previousPrefix));
1859
1952
  let restoredFrom;
1860
1953
  const first = previous[0];
1861
1954
  if (first !== void 0 && params.fs.lstat(params.farmRoot) === void 0) {
1862
- params.fs.rename(path6.join(params.identitiesDir, first), params.farmRoot);
1955
+ params.fs.rename(path7.join(params.identitiesDir, first), params.farmRoot);
1863
1956
  restoredFrom = first;
1864
1957
  previous = previous.slice(1);
1865
1958
  }
@@ -1867,7 +1960,7 @@ function recoverInterruptedSwap(params) {
1867
1960
  const autoResolved = [];
1868
1961
  const retained = [];
1869
1962
  for (const name of previous) {
1870
- const previousRoot = path6.join(params.identitiesDir, name);
1963
+ const previousRoot = path7.join(params.identitiesDir, name);
1871
1964
  const result = carryOver({
1872
1965
  fs: params.fs,
1873
1966
  previousRoot,
@@ -1892,7 +1985,7 @@ function recoverInterruptedSwap(params) {
1892
1985
  };
1893
1986
  }
1894
1987
  function recoverFarm(params) {
1895
- const farmRoot = path6.join(params.identitiesDir, params.identity);
1988
+ const farmRoot = path7.join(params.identitiesDir, params.identity);
1896
1989
  const lock = acquireIdentityLock({
1897
1990
  identity: params.identity,
1898
1991
  dir: params.identitiesDir,
@@ -1951,16 +2044,16 @@ function executeReconciliation(params) {
1951
2044
  });
1952
2045
  continue;
1953
2046
  }
1954
- const from = path6.join(params.farmRoot, action.rel);
1955
- const canonical = path6.join(params.claudeHome, action.rel);
2047
+ const from = path7.join(params.farmRoot, action.rel);
2048
+ const canonical = path7.join(params.claudeHome, action.rel);
1956
2049
  if (action.kind === "adopt") {
1957
- params.fs.mkdirp(path6.dirname(canonical));
2050
+ params.fs.mkdirp(path7.dirname(canonical));
1958
2051
  params.fs.copyRecursive(from, canonical);
1959
2052
  adopted.push(action.rel);
1960
2053
  continue;
1961
2054
  }
1962
2055
  const preserved = `${canonical}.farm-conflict-${String(params.nowMs)}`;
1963
- params.fs.mkdirp(path6.dirname(canonical));
2056
+ params.fs.mkdirp(path7.dirname(canonical));
1964
2057
  params.fs.copyRecursive(from, preserved);
1965
2058
  conflicts.push(action.rel);
1966
2059
  }
@@ -1985,7 +2078,7 @@ function farmAlreadyMatches(existing, next) {
1985
2078
  return existing.identity === next.identity && existing.claudeHome === next.claudeHome && sameStrings(existing.materialised, next.materialised) && sameLinks(existing.links, next.links);
1986
2079
  }
1987
2080
  function resyncFarm(params) {
1988
- const farmRoot = path6.join(params.identitiesDir, params.identity);
2081
+ const farmRoot = path7.join(params.identitiesDir, params.identity);
1989
2082
  const lock = acquireIdentityLock({
1990
2083
  identity: params.identity,
1991
2084
  dir: params.identitiesDir,
@@ -2065,8 +2158,8 @@ function resyncFarm(params) {
2065
2158
  diagnostics
2066
2159
  };
2067
2160
  }
2068
- const scratchRoot = path6.join(params.identitiesDir, `.${params.identity}.scratch.${params.uniqueSuffix}`);
2069
- const previousRoot = path6.join(params.identitiesDir, `.${params.identity}.previous.${params.uniqueSuffix}`);
2161
+ const scratchRoot = path7.join(params.identitiesDir, `.${params.identity}.scratch.${params.uniqueSuffix}`);
2162
+ const previousRoot = path7.join(params.identitiesDir, `.${params.identity}.previous.${params.uniqueSuffix}`);
2070
2163
  buildScratchTree({ fs: params.fs, scratchRoot, plan: resolved.farm, manifest });
2071
2164
  const swap = swapIn({ fs: params.fs, farmRoot, scratchRoot, previousRoot, classification: params.classification });
2072
2165
  if (swap.retainedPrevious !== void 0) {
@@ -2495,7 +2588,7 @@ function providerBaseUrl(port, provider) {
2495
2588
  }
2496
2589
 
2497
2590
  // src/frontdoor/state.ts
2498
- import path7 from "node:path";
2591
+ import path8 from "node:path";
2499
2592
  import { z as z3 } from "zod";
2500
2593
  var FRONT_DOOR_PROTOCOL = 2;
2501
2594
  var FrontDoorSessionSchema = z3.strictObject({ pid: z3.number().int().positive(), startedAt: z3.number(), token: z3.string().min(1) });
@@ -2547,7 +2640,7 @@ function readFrontDoorState(fs12, stateFile) {
2547
2640
  }
2548
2641
  }
2549
2642
  function writeFrontDoorState(fs12, stateFile, state) {
2550
- fs12.mkdirp(path7.dirname(stateFile));
2643
+ fs12.mkdirp(path8.dirname(stateFile));
2551
2644
  fs12.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
2552
2645
  `);
2553
2646
  }
@@ -2557,7 +2650,7 @@ function frontDoorOrigin(state) {
2557
2650
  }
2558
2651
  function writeFrontDoorSession(fs12, sessionsDir, session) {
2559
2652
  fs12.mkdirPrivate(sessionsDir);
2560
- fs12.writeFilePrivate(path7.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
2653
+ fs12.writeFilePrivate(path8.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
2561
2654
  `);
2562
2655
  }
2563
2656
  function listFrontDoorSessions(fs12, sessionsDir) {
@@ -2567,7 +2660,7 @@ function listFrontDoorSessions(fs12, sessionsDir) {
2567
2660
  continue;
2568
2661
  }
2569
2662
  try {
2570
- const parsed = FrontDoorSessionSchema.safeParse(JSON.parse(fs12.readFileUtf8(path7.join(sessionsDir, name)) ?? ""));
2663
+ const parsed = FrontDoorSessionSchema.safeParse(JSON.parse(fs12.readFileUtf8(path8.join(sessionsDir, name)) ?? ""));
2571
2664
  if (parsed.success) {
2572
2665
  sessions.push({ pid: parsed.data.pid, startedAt: parsed.data.startedAt });
2573
2666
  }
@@ -2578,7 +2671,7 @@ function listFrontDoorSessions(fs12, sessionsDir) {
2578
2671
  return sessions.sort((a, b) => a.pid - b.pid);
2579
2672
  }
2580
2673
  function removeFrontDoorSession(fs12, sessionsDir, pid) {
2581
- fs12.removeRecursive(path7.join(sessionsDir, `${String(pid)}.json`));
2674
+ fs12.removeRecursive(path8.join(sessionsDir, `${String(pid)}.json`));
2582
2675
  }
2583
2676
  function pruneDeadFrontDoorSessions(fs12, sessionsDir, isRunning) {
2584
2677
  const removed = [];
@@ -2722,8 +2815,8 @@ function parseConnectTarget(authority) {
2722
2815
  function isInterceptedHost(host, interceptHosts) {
2723
2816
  return interceptHosts.includes(host);
2724
2817
  }
2725
- function servedByPipeline(path29) {
2726
- return path29?.startsWith(ROUTED_PATH_PREFIX) === true;
2818
+ function servedByPipeline(path30) {
2819
+ return path30?.startsWith(ROUTED_PATH_PREFIX) === true;
2727
2820
  }
2728
2821
  var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
2729
2822
  "connection",
@@ -3247,13 +3340,18 @@ function createRcSessionTracker(deps) {
3247
3340
  entries.set(id, record);
3248
3341
  return record;
3249
3342
  };
3343
+ const persistCredential = (entry) => {
3344
+ if (entry.oauthAuthorization !== void 0) {
3345
+ deps.credentialStore?.write(entry.id, { authorization: entry.oauthAuthorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform });
3346
+ }
3347
+ };
3250
3348
  const observeExchange = (request4) => {
3251
3349
  const method = (request4.method ?? "").toUpperCase();
3252
- const path29 = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
3253
- if (!path29.startsWith(RC_SESSIONS_PATH_PREFIX)) {
3350
+ const path30 = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
3351
+ if (!path30.startsWith(RC_SESSIONS_PATH_PREFIX)) {
3254
3352
  return void 0;
3255
3353
  }
3256
- const tail = path29.slice(RC_SESSIONS_PATH_PREFIX.length);
3354
+ const tail = path30.slice(RC_SESSIONS_PATH_PREFIX.length);
3257
3355
  const facts = {
3258
3356
  oauthAuthorization: oauthAuthorizationOf(request4.headers),
3259
3357
  anthropicVersion: singleHeader(request4.headers, "anthropic-version"),
@@ -3286,7 +3384,7 @@ function createRcSessionTracker(deps) {
3286
3384
  if (id2 === void 0) {
3287
3385
  return;
3288
3386
  }
3289
- birth(id2, facts);
3387
+ persistCredential(birth(id2, facts));
3290
3388
  }
3291
3389
  };
3292
3390
  }
@@ -3300,6 +3398,7 @@ function createRcSessionTracker(deps) {
3300
3398
  entry.oauthAuthorization = facts.oauthAuthorization ?? entry.oauthAuthorization;
3301
3399
  entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
3302
3400
  entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
3401
+ persistCredential(entry);
3303
3402
  const bodyKind = method === "POST" && rest === `${id}/worker/events` ? "worker-events" : method === "POST" && rest === `${id}/events` ? "client-events" : method === "POST" && rest === `${id}/worker/heartbeat` ? "heartbeat" : method === "PUT" && rest === `${id}/worker` ? "worker-registration" : void 0;
3304
3403
  let chunks = [];
3305
3404
  let buffered = 0;
@@ -3353,6 +3452,7 @@ function createRcSessionTracker(deps) {
3353
3452
  },
3354
3453
  onResponse: (status, headers) => {
3355
3454
  if (singleHeader(headers, CONFLICT_REASON_HEADER) !== void 0 || archiving && isSuccessful(status)) {
3455
+ deps.credentialStore?.remove(id);
3356
3456
  expire(id);
3357
3457
  return;
3358
3458
  }
@@ -3385,7 +3485,18 @@ function createRcSessionTracker(deps) {
3385
3485
  },
3386
3486
  credentialOf: (sessionId) => {
3387
3487
  const entry = entries.get(sessionId);
3388
- return entry === void 0 ? void 0 : { authorization: entry.oauthAuthorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
3488
+ if (entry === void 0) {
3489
+ return void 0;
3490
+ }
3491
+ if (entry.oauthAuthorization === void 0 && deps.credentialStore !== void 0) {
3492
+ const persisted = deps.credentialStore.read(sessionId);
3493
+ if (persisted?.authorization !== void 0) {
3494
+ entry.oauthAuthorization = persisted.authorization;
3495
+ entry.anthropicVersion = entry.anthropicVersion ?? persisted.anthropicVersion;
3496
+ entry.anthropicClientPlatform = entry.anthropicClientPlatform ?? persisted.anthropicClientPlatform;
3497
+ }
3498
+ }
3499
+ return { authorization: entry.oauthAuthorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
3389
3500
  },
3390
3501
  noteSequenceNums: (sessionId, sequenceNums) => {
3391
3502
  const entry = entries.get(sessionId);
@@ -4001,9 +4112,9 @@ function sequenceNumsOfBody(body) {
4001
4112
  }
4002
4113
  function frontDoorRcControl(transport, token) {
4003
4114
  const headers = { authorization: `Bearer ${token}` };
4004
- const listAnswerOf = async (path29, what) => {
4115
+ const listAnswerOf = async (path30, what) => {
4005
4116
  try {
4006
- return await transport.request({ method: "GET", path: path29, headers });
4117
+ return await transport.request({ method: "GET", path: path30, headers });
4007
4118
  } catch (error) {
4008
4119
  throw new Error(`${what}: could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
4009
4120
  }
@@ -4075,10 +4186,10 @@ function frontDoorRcControl(transport, token) {
4075
4186
  }
4076
4187
  return pending;
4077
4188
  };
4078
- const writeOf = async (path29, body, describeFailure) => {
4189
+ const writeOf = async (path30, body, describeFailure) => {
4079
4190
  let answer;
4080
4191
  try {
4081
- answer = await transport.request({ method: "POST", path: path29, headers: { ...headers, "content-type": "application/json" }, body });
4192
+ answer = await transport.request({ method: "POST", path: path30, headers: { ...headers, "content-type": "application/json" }, body });
4082
4193
  } catch (error) {
4083
4194
  return { ok: false, message: `could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}` };
4084
4195
  }
@@ -4126,15 +4237,27 @@ var SUCCESS_STATUS_MAX_EXCLUSIVE2 = 300;
4126
4237
  function isSuccessful2(status) {
4127
4238
  return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE2;
4128
4239
  }
4129
- var UPSTREAM_LOCAL_PORT_START = 47900;
4130
- var UPSTREAM_LOCAL_PORT_END = 47919;
4131
- var UPSTREAM_LOCAL_PORT_COUNT = UPSTREAM_LOCAL_PORT_END - UPSTREAM_LOCAL_PORT_START + 1;
4132
- var nextUpstreamLocalPort = UPSTREAM_LOCAL_PORT_START;
4133
- function upstreamLocalPort() {
4134
- const port = nextUpstreamLocalPort;
4135
- nextUpstreamLocalPort = nextUpstreamLocalPort === UPSTREAM_LOCAL_PORT_END ? UPSTREAM_LOCAL_PORT_START : nextUpstreamLocalPort + 1;
4136
- return port;
4137
- }
4240
+ var SourcePortRange = class {
4241
+ constructor(start, end) {
4242
+ this.start = start;
4243
+ this.end = end;
4244
+ this.count = end - start + 1;
4245
+ this.next = start;
4246
+ }
4247
+ start;
4248
+ end;
4249
+ count;
4250
+ next;
4251
+ /** The next port in rotation, wrapping from the end back to the start. */
4252
+ take() {
4253
+ const port = this.next;
4254
+ this.next = this.next === this.end ? this.start : this.next + 1;
4255
+ return port;
4256
+ }
4257
+ };
4258
+ var DOOR_SOURCE_PORT_START = 47900;
4259
+ var DOOR_SOURCE_PORT_END = 47919;
4260
+ var DOOR_SOURCE_PORTS = new SourcePortRange(DOOR_SOURCE_PORT_START, DOOR_SOURCE_PORT_END);
4138
4261
  var realAddressLookup = (host, _options, callback) => {
4139
4262
  if (net2.isIP(host) !== 0) {
4140
4263
  callback(null, [{ address: host, family: net2.isIP(host) }]);
@@ -4172,14 +4295,14 @@ function dialTarget(options, defaultPort) {
4172
4295
  function literalDial(target) {
4173
4296
  return net2.connect({ host: target.host, port: target.port });
4174
4297
  }
4175
- async function reservedDial(target) {
4298
+ async function reservedDial(target, ports) {
4176
4299
  for (let attempt = 0; ; attempt += 1) {
4177
- const outcome = await dialOnce({ host: target.host, port: target.port, localPort: upstreamLocalPort(), lookup: realAddressLookup });
4300
+ const outcome = await dialOnce({ host: target.host, port: target.port, localPort: ports.take(), lookup: realAddressLookup });
4178
4301
  if (outcome.error === null) {
4179
4302
  return outcome;
4180
4303
  }
4181
4304
  const held = outcome.error.code !== void 0 && HELD_PORT_ERRNOS.has(outcome.error.code);
4182
- if (!held || attempt === UPSTREAM_LOCAL_PORT_COUNT - 1) {
4305
+ if (!held || attempt === ports.count - 1) {
4183
4306
  return outcome;
4184
4307
  }
4185
4308
  }
@@ -4188,26 +4311,36 @@ function requestServername(options, fallback) {
4188
4311
  return "servername" in options && typeof options.servername === "string" && options.servername !== "" ? options.servername : fallback;
4189
4312
  }
4190
4313
  var ExemptHttpAgent = class extends http.Agent {
4314
+ constructor(ports = DOOR_SOURCE_PORTS) {
4315
+ super();
4316
+ this.ports = ports;
4317
+ }
4318
+ ports;
4191
4319
  // A reserved-port dial that has to retry cannot produce its socket synchronously, so it settles first and arrives through the callback (node's documented asynchronous createConnection form); a literal dial has nothing to retry and keeps node's own synchronous shape.
4192
4320
  createConnection(options, oncreate) {
4193
4321
  const target = dialTarget(options, HTTP_PORT);
4194
4322
  if (net2.isIP(target.host) !== 0) {
4195
4323
  return literalDial(target);
4196
4324
  }
4197
- void reservedDial(target).then((outcome) => {
4325
+ void reservedDial(target, this.ports).then((outcome) => {
4198
4326
  oncreate?.(outcome.error, outcome.socket);
4199
4327
  });
4200
4328
  return void 0;
4201
4329
  }
4202
4330
  };
4203
4331
  var ExemptTlsAgent = class extends https2.Agent {
4332
+ constructor(ports = DOOR_SOURCE_PORTS) {
4333
+ super();
4334
+ this.ports = ports;
4335
+ }
4336
+ ports;
4204
4337
  // The same split as the plain sibling: a literal dial wraps TLS around the socket node's own agent would have returned, a reserved dial settles (retries included) first and arrives through the callback, which is exactly where node's own agent wraps its own dials.
4205
4338
  createConnection(options, oncreate) {
4206
4339
  const target = dialTarget(options, HTTPS_PORT);
4207
4340
  if (net2.isIP(target.host) !== 0) {
4208
4341
  return tls.connect({ socket: literalDial(target), servername: requestServername(options, target.host), ...tlsFacts(options) });
4209
4342
  }
4210
- void reservedDial(target).then((outcome) => {
4343
+ void reservedDial(target, this.ports).then((outcome) => {
4211
4344
  if (outcome.error !== null) {
4212
4345
  oncreate?.(outcome.error, outcome.socket);
4213
4346
  return;
@@ -4268,12 +4401,12 @@ var NO_CHUNKS = {
4268
4401
  };
4269
4402
  function realRcStreamDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_PORT }) {
4270
4403
  const agent = new ExemptTlsAgent();
4271
- const request4 = async (method, path29, headers, body, signal) => await new Promise((resolve, reject) => {
4404
+ const request4 = async (method, path30, headers, body, signal) => await new Promise((resolve, reject) => {
4272
4405
  const options = {
4273
4406
  host: target.host,
4274
4407
  port: target.port,
4275
4408
  method,
4276
- path: path29,
4409
+ path: path30,
4277
4410
  // The Host header names the host whose API this is, not the address dialled: a redirected test dials a loopback stand-in while still speaking to the API host by name, exactly as the forwarded paths present their SNI.
4278
4411
  headers: { ...headers, host: target.servername ?? target.host, ...body === void 0 ? {} : { "content-length": String(Buffer.byteLength(body, "utf8")) } },
4279
4412
  agent,
@@ -4310,9 +4443,9 @@ function realRcStreamDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_P
4310
4443
  return { status, body: await readAll(response) };
4311
4444
  },
4312
4445
  openStream: async (sessionId, headers, resume, signal) => {
4313
- const path29 = resume === void 0 ? `/v1/code/sessions/${encodeURIComponent(sessionId)}/events/stream` : `/v1/code/sessions/${encodeURIComponent(sessionId)}/events/stream?from_sequence_num=${String(resume.fromSequenceNum)}`;
4446
+ const path30 = resume === void 0 ? `/v1/code/sessions/${encodeURIComponent(sessionId)}/events/stream` : `/v1/code/sessions/${encodeURIComponent(sessionId)}/events/stream?from_sequence_num=${String(resume.fromSequenceNum)}`;
4314
4447
  const streamHeaders = resume === void 0 ? headers : { ...headers, "last-event-id": String(resume.fromSequenceNum) };
4315
- const { status, response } = await request4("GET", path29, streamHeaders, void 0, signal);
4448
+ const { status, response } = await request4("GET", path30, streamHeaders, void 0, signal);
4316
4449
  if (!isSuccessful2(status)) {
4317
4450
  await readAll(response);
4318
4451
  return { status, chunks: NO_CHUNKS };
@@ -4321,9 +4454,9 @@ function realRcStreamDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_P
4321
4454
  }
4322
4455
  };
4323
4456
  }
4324
- function realConnectEffects() {
4325
- const plainAgent = new ExemptHttpAgent();
4326
- const tlsAgent = new ExemptTlsAgent();
4457
+ function realConnectEffects(ports = DOOR_SOURCE_PORTS) {
4458
+ const plainAgent = new ExemptHttpAgent(ports);
4459
+ const tlsAgent = new ExemptTlsAgent(ports);
4327
4460
  const listenOnce = async (port, onSocket) => await new Promise((resolve, reject) => {
4328
4461
  const accepted = /* @__PURE__ */ new Set();
4329
4462
  const server = net2.createServer((socket) => {
@@ -4441,7 +4574,7 @@ function realConnectEffects() {
4441
4574
  };
4442
4575
  },
4443
4576
  connectTlsUpstream: async (host, port, clientAlpn) => {
4444
- const dialled = await reservedDial({ host, port });
4577
+ const dialled = await reservedDial({ host, port }, ports);
4445
4578
  if (dialled.error !== null) {
4446
4579
  throw dialled.error;
4447
4580
  }
@@ -4523,10 +4656,68 @@ function realConnectEffects() {
4523
4656
  };
4524
4657
  }
4525
4658
 
4659
+ // src/frontdoor/rcCredentialStore.ts
4660
+ import path9 from "node:path";
4661
+ function isSafeSessionId(sessionId) {
4662
+ return /^[A-Za-z0-9_-]+$/.test(sessionId);
4663
+ }
4664
+ function isRecord3(value) {
4665
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4666
+ }
4667
+ function credentialOfJson(parsed) {
4668
+ if (!isRecord3(parsed)) {
4669
+ return void 0;
4670
+ }
4671
+ const { authorization, anthropicVersion, anthropicClientPlatform } = parsed;
4672
+ if (typeof authorization !== "string" || authorization === "") {
4673
+ return void 0;
4674
+ }
4675
+ return {
4676
+ authorization,
4677
+ anthropicVersion: typeof anthropicVersion === "string" ? anthropicVersion : void 0,
4678
+ anthropicClientPlatform: typeof anthropicClientPlatform === "string" ? anthropicClientPlatform : void 0
4679
+ };
4680
+ }
4681
+ function createRcCredentialStore(fs12, dir) {
4682
+ fs12.mkdirPrivate(dir);
4683
+ const fileFor = (sessionId) => path9.join(dir, `${sessionId}.json`);
4684
+ return {
4685
+ read: (sessionId) => {
4686
+ if (!isSafeSessionId(sessionId)) {
4687
+ return void 0;
4688
+ }
4689
+ const raw = fs12.readFileUtf8(fileFor(sessionId));
4690
+ if (raw === void 0) {
4691
+ return void 0;
4692
+ }
4693
+ let parsed;
4694
+ try {
4695
+ parsed = JSON.parse(raw);
4696
+ } catch {
4697
+ return void 0;
4698
+ }
4699
+ return credentialOfJson(parsed);
4700
+ },
4701
+ write: (sessionId, credential) => {
4702
+ if (!isSafeSessionId(sessionId)) {
4703
+ return;
4704
+ }
4705
+ fs12.writeFilePrivate(fileFor(sessionId), `${JSON.stringify(credential, null, 2)}
4706
+ `);
4707
+ },
4708
+ remove: (sessionId) => {
4709
+ if (!isSafeSessionId(sessionId)) {
4710
+ return;
4711
+ }
4712
+ fs12.removeRecursive(fileFor(sessionId));
4713
+ }
4714
+ };
4715
+ }
4716
+
4526
4717
  // src/frontdoor/rcApi.ts
4527
4718
  import { createORPCClient, ORPCError } from "@orpc/client";
4528
4719
  import { RPCLink } from "@orpc/client/fetch";
4529
- import { eventIterator, os, withEventMeta } from "@orpc/server";
4720
+ import { eventIterator, os as os2, withEventMeta } from "@orpc/server";
4530
4721
  import { BodyLimitPlugin, RPCHandler } from "@orpc/server/node";
4531
4722
  import { Agent as Agent3, fetch as undiciFetch } from "undici";
4532
4723
 
@@ -4613,7 +4804,7 @@ var RcAnswerOutputSchema = z4.strictObject({
4613
4804
  var RC_ORPC_PATH_PREFIX = "/__agent-shim/orpc";
4614
4805
  var RC_SUBSCRIBER_BUFFER_EVENTS = 100;
4615
4806
  function createRcApiRouter(deps) {
4616
- const authed = os.$context().use(({ context, next }) => {
4807
+ const authed = os2.$context().use(({ context, next }) => {
4617
4808
  const presented = context.headers.authorization;
4618
4809
  const bearerPrefix = "bearer ".length;
4619
4810
  const token = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
@@ -4780,7 +4971,7 @@ function createSseParser() {
4780
4971
  }
4781
4972
  };
4782
4973
  }
4783
- function isRecord3(value) {
4974
+ function isRecord4(value) {
4784
4975
  return typeof value === "object" && value !== null && !Array.isArray(value);
4785
4976
  }
4786
4977
  function sequenceNumOf(value) {
@@ -4802,7 +4993,7 @@ function parseRcStreamEnvelope(parsed) {
4802
4993
  } catch {
4803
4994
  return void 0;
4804
4995
  }
4805
- if (!isRecord3(data)) {
4996
+ if (!isRecord4(data)) {
4806
4997
  return void 0;
4807
4998
  }
4808
4999
  const eventType = data.event_type;
@@ -4891,6 +5082,7 @@ function createRcStreamHub(deps) {
4891
5082
  if (!isSuccessful3(answer.status)) {
4892
5083
  throw new Error(`the client read stream answered HTTP ${String(answer.status)}`);
4893
5084
  }
5085
+ deps.log?.(`rc stream ${attachment.sessionId}: attached as client ${attachment.clientId}${resume === void 0 ? ", reading from the stream's own head" : `, resuming after sequence number ${String(resume.fromSequenceNum)}`}`);
4894
5086
  const parser = createSseParser();
4895
5087
  for await (const chunk of answer.chunks) {
4896
5088
  attachment.controller.signal.throwIfAborted();
@@ -4914,6 +5106,7 @@ function createRcStreamHub(deps) {
4914
5106
  const live = deps.trackedSessions().includes(attachment.sessionId);
4915
5107
  const credential = deps.credentialOf(attachment.sessionId);
4916
5108
  if (!live || credential?.authorization === void 0) {
5109
+ deps.log?.(`rc stream ${attachment.sessionId}: the attachment ended (${live ? "the session's client credential is no longer known" : "the tracker gave up on the session"})`);
4917
5110
  if (attachments.get(attachment.sessionId) === attachment) {
4918
5111
  attachments.delete(attachment.sessionId);
4919
5112
  }
@@ -4935,6 +5128,7 @@ function createRcStreamHub(deps) {
4935
5128
  return;
4936
5129
  }
4937
5130
  if (outcome === "dropped") {
5131
+ deps.log?.(`rc stream ${attachment.sessionId}: the stream ended of its own accord, reconnecting`);
4938
5132
  retriedUnauthorized = false;
4939
5133
  continue;
4940
5134
  }
@@ -4984,7 +5178,7 @@ import { randomUUID as randomUUID3 } from "node:crypto";
4984
5178
 
4985
5179
  // src/headroom/state.ts
4986
5180
  import { createHash as createHash3 } from "node:crypto";
4987
- import path8 from "node:path";
5181
+ import path10 from "node:path";
4988
5182
  import { z as z5 } from "zod";
4989
5183
  var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
4990
5184
  var HEADROOM_STATE_SCHEMA_VERSION = 2;
@@ -5025,7 +5219,7 @@ function readHeadroomState(fs12, stateFile) {
5025
5219
  }
5026
5220
  }
5027
5221
  function writeHeadroomState(fs12, stateFile, state) {
5028
- fs12.mkdirp(path8.dirname(stateFile));
5222
+ fs12.mkdirp(path10.dirname(stateFile));
5029
5223
  fs12.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
5030
5224
  `);
5031
5225
  }
@@ -5053,7 +5247,7 @@ function readAllProviders(fs12, providersDir) {
5053
5247
  continue;
5054
5248
  }
5055
5249
  try {
5056
- const parsed = parseJson(fs12.readFileUtf8(path8.join(providersDir, name)), ProviderSchema);
5250
+ const parsed = parseJson(fs12.readFileUtf8(path10.join(providersDir, name)), ProviderSchema);
5057
5251
  if (parsed !== void 0) {
5058
5252
  result.push({ name: name.slice(0, -".json".length), provider: parsed });
5059
5253
  }
@@ -5065,11 +5259,11 @@ function readAllProviders(fs12, providersDir) {
5065
5259
  }
5066
5260
  function writeSession(fs12, sessionsDir, session) {
5067
5261
  fs12.mkdirp(sessionsDir);
5068
- fs12.writeFileUtf8(path8.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
5262
+ fs12.writeFileUtf8(path10.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
5069
5263
  `);
5070
5264
  }
5071
5265
  function removeSession(fs12, sessionsDir, pid) {
5072
- fs12.removeRecursive(path8.join(sessionsDir, `${String(pid)}.json`));
5266
+ fs12.removeRecursive(path10.join(sessionsDir, `${String(pid)}.json`));
5073
5267
  }
5074
5268
  function listSessions(fs12, sessionsDir) {
5075
5269
  const sessions = [];
@@ -5078,7 +5272,7 @@ function listSessions(fs12, sessionsDir) {
5078
5272
  continue;
5079
5273
  }
5080
5274
  try {
5081
- const parsed = parseJson(fs12.readFileUtf8(path8.join(sessionsDir, name)), HeadroomSessionSchema);
5275
+ const parsed = parseJson(fs12.readFileUtf8(path10.join(sessionsDir, name)), HeadroomSessionSchema);
5082
5276
  if (parsed !== void 0) {
5083
5277
  sessions.push(parsed);
5084
5278
  }
@@ -5183,10 +5377,10 @@ function ensureFrontDoor(params) {
5183
5377
  }
5184
5378
 
5185
5379
  // src/usage/read.ts
5186
- import path10 from "node:path";
5380
+ import path12 from "node:path";
5187
5381
 
5188
5382
  // src/usage/account.ts
5189
- import path9 from "node:path";
5383
+ import path11 from "node:path";
5190
5384
 
5191
5385
  // src/usage/schema.ts
5192
5386
  import { z as z6 } from "zod";
@@ -5352,7 +5546,7 @@ function isIdentityName(name) {
5352
5546
  return IdentitySchema.shape.name.safeParse(name).success;
5353
5547
  }
5354
5548
  function claudeJsonPath(identitiesDir, identity) {
5355
- return path9.join(identitiesDir, identity, CLAUDE_JSON);
5549
+ return path11.join(identitiesDir, identity, CLAUDE_JSON);
5356
5550
  }
5357
5551
  function readAccountMetadata(fs12, identitiesDir, identity) {
5358
5552
  if (!isIdentityName(identity)) {
@@ -5411,7 +5605,7 @@ function readUsageLog(fs12, logDir, options = {}) {
5411
5605
  if (sinceMs !== void 0 && segment.endsAt <= sinceMs) {
5412
5606
  continue;
5413
5607
  }
5414
- const contents = fs12.readFileUtf8(path10.join(logDir, segment.name));
5608
+ const contents = fs12.readFileUtf8(path12.join(logDir, segment.name));
5415
5609
  if (contents === void 0) {
5416
5610
  continue;
5417
5611
  }
@@ -5453,7 +5647,7 @@ function snapshotPath(snapshotsDir, identity) {
5453
5647
  if (!isIdentityName(identity)) {
5454
5648
  throw new Error(`"${identity}" is not a valid identity name.`);
5455
5649
  }
5456
- return path10.join(snapshotsDir, `${identity}.json`);
5650
+ return path12.join(snapshotsDir, `${identity}.json`);
5457
5651
  }
5458
5652
  function readUsageSnapshot(fs12, snapshotsDir, identity) {
5459
5653
  const file = snapshotPath(snapshotsDir, identity);
@@ -5787,7 +5981,7 @@ function rankPool(input) {
5787
5981
  }
5788
5982
 
5789
5983
  // src/usage/poolPick.ts
5790
- import path11 from "node:path";
5984
+ import path13 from "node:path";
5791
5985
  import { z as z9 } from "zod";
5792
5986
 
5793
5987
  // src/usage/store.ts
@@ -5854,13 +6048,13 @@ function recordStickyPick(fs12, picksFile, directory, identity, nowMs) {
5854
6048
  const current = readPicksFile(fs12, picksFile).file.picks;
5855
6049
  const kept = Object.entries(current).filter(([, entry]) => nowMs - Date.parse(entry.at) <= USAGE_RETENTION_MS);
5856
6050
  const next = { schemaVersion: SCHEMA_VERSION, picks: { ...Object.fromEntries(kept), [directory]: { identity, at: new Date(nowMs).toISOString() } } };
5857
- fs12.mkdirPrivate(path11.dirname(picksFile));
6051
+ fs12.mkdirPrivate(path13.dirname(picksFile));
5858
6052
  fs12.writeFilePrivate(picksFile, `${JSON.stringify(next, null, 2)}
5859
6053
  `);
5860
6054
  }
5861
6055
 
5862
6056
  // src/launcher/identity.ts
5863
- import path12 from "node:path";
6057
+ import path14 from "node:path";
5864
6058
  function poolNameOf(selector) {
5865
6059
  return selector.startsWith(POOL_SELECTOR_PREFIX) ? selector.slice(POOL_SELECTOR_PREFIX.length) : void 0;
5866
6060
  }
@@ -5909,7 +6103,7 @@ function decideConfigProfile(params) {
5909
6103
  return { source: "none" };
5910
6104
  }
5911
6105
  function loadIdentity(identitiesDir, name, fs12) {
5912
- return loadConfigFile(path12.join(identitiesDir, name, "identity.json"), IdentitySchema, fs12.readConfigFile);
6106
+ return loadConfigFile(path14.join(identitiesDir, name, "identity.json"), IdentitySchema, fs12.readConfigFile);
5913
6107
  }
5914
6108
 
5915
6109
  // src/launcher/pool.ts
@@ -6124,7 +6318,7 @@ function hashSettings(settings) {
6124
6318
  }
6125
6319
 
6126
6320
  // src/headroom/socket.ts
6127
- import path13 from "node:path";
6321
+ import path15 from "node:path";
6128
6322
  var GROUP_OR_OTHER_BITS = 63;
6129
6323
  var OCTAL_RADIX = 8;
6130
6324
  var MODE_DIGITS = 4;
@@ -6133,7 +6327,7 @@ var HEADROOM_SOCKET_NAME = /^(\d+)\.sock$/;
6133
6327
  var UNIX_SOCKET_PATH_MAX_BYTES = 103;
6134
6328
  var PLATFORMS_WITHOUT_UNIX_SOCKETS = ["win32"];
6135
6329
  function headroomSocketPath(paths, supervisorPid) {
6136
- return path13.join(paths.headroomSocketDir, `${String(supervisorPid)}${HEADROOM_SOCKET_SUFFIX}`);
6330
+ return path15.join(paths.headroomSocketDir, `${String(supervisorPid)}${HEADROOM_SOCKET_SUFFIX}`);
6137
6331
  }
6138
6332
  function platformRefusal(platform) {
6139
6333
  return PLATFORMS_WITHOUT_UNIX_SOCKETS.includes(platform) ? `headroom routing needs a unix domain socket, which headroom cannot serve on ${platform}; agent-shim never falls back to a TCP port for this hop` : void 0;
@@ -6182,7 +6376,7 @@ function pruneStaleHeadroomSockets(fs12, paths, isRunning) {
6182
6376
  if (pid === void 0 || isRunning(Number.parseInt(pid, 10))) {
6183
6377
  continue;
6184
6378
  }
6185
- fs12.removeRecursive(path13.join(paths.headroomSocketDir, name));
6379
+ fs12.removeRecursive(path15.join(paths.headroomSocketDir, name));
6186
6380
  removed.push(name);
6187
6381
  }
6188
6382
  return removed;
@@ -6236,7 +6430,7 @@ function versionSatisfies(version, source) {
6236
6430
  if (installed === void 0 || wanted === void 0) {
6237
6431
  return true;
6238
6432
  }
6239
- const cmp = compareVersions(installed, wanted);
6433
+ const cmp = compareVersions2(installed, wanted);
6240
6434
  switch (specifier[0]) {
6241
6435
  case "==":
6242
6436
  return cmp === 0;
@@ -6251,7 +6445,7 @@ function versionSatisfies(version, source) {
6251
6445
  case "<":
6252
6446
  return cmp < 0;
6253
6447
  case "~=":
6254
- return compareVersions(installed.slice(0, Math.max(0, wanted.length - 1)), wanted.slice(0, Math.max(0, wanted.length - 1))) === 0 && cmp >= 0;
6448
+ return compareVersions2(installed.slice(0, Math.max(0, wanted.length - 1)), wanted.slice(0, Math.max(0, wanted.length - 1))) === 0 && cmp >= 0;
6255
6449
  default:
6256
6450
  return true;
6257
6451
  }
@@ -6263,7 +6457,7 @@ function parseVersion(input) {
6263
6457
  }
6264
6458
  return match[0].split(".").map((part) => Number.parseInt(part, 10));
6265
6459
  }
6266
- function compareVersions(a, b) {
6460
+ function compareVersions2(a, b) {
6267
6461
  for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
6268
6462
  const left = a[index] ?? 0;
6269
6463
  const right = b[index] ?? 0;
@@ -6505,15 +6699,15 @@ function evaluateAmbientCredentialGuard(params) {
6505
6699
 
6506
6700
  // src/paths.ts
6507
6701
  import fs3 from "node:fs";
6508
- import os2 from "node:os";
6509
- import path14 from "node:path";
6510
- function resolveAgentShimHome(env = process.env, home = os2.homedir(), isDirectory = isExistingDirectory) {
6702
+ import os3 from "node:os";
6703
+ import path16 from "node:path";
6704
+ function resolveAgentShimHome(env = process.env, home = os3.homedir(), isDirectory = isExistingDirectory) {
6511
6705
  const fromEnv = env.AGENT_SHIM_HOME;
6512
6706
  if (fromEnv !== void 0 && fromEnv !== "") {
6513
6707
  return fromEnv;
6514
6708
  }
6515
- const current = path14.join(home, HOME_DIRNAME);
6516
- const legacy = path14.join(home, LEGACY_HOME_DIRNAME);
6709
+ const current = path16.join(home, HOME_DIRNAME);
6710
+ const legacy = path16.join(home, LEGACY_HOME_DIRNAME);
6517
6711
  return !isDirectory(current) && isDirectory(legacy) ? legacy : current;
6518
6712
  }
6519
6713
  function isExistingDirectory(candidate) {
@@ -6524,40 +6718,41 @@ function resolveClaudeHome() {
6524
6718
  if (fromEnv !== void 0 && fromEnv !== "") {
6525
6719
  return fromEnv;
6526
6720
  }
6527
- return path14.join(os2.homedir(), ".claude");
6721
+ return path16.join(os3.homedir(), ".claude");
6528
6722
  }
6529
6723
  function buildLayoutPaths(root) {
6530
6724
  return {
6531
6725
  root,
6532
- identitiesDir: path14.join(root, "identities"),
6533
- configProfilesDir: path14.join(root, "config-profiles"),
6534
- providersDir: path14.join(root, "providers"),
6535
- directoryRulesFile: path14.join(root, "directory-rules.json"),
6536
- activeIdentityFile: path14.join(root, "active-identity"),
6537
- globalConfigFile: path14.join(root, "config.json"),
6538
- categoriesLocalFile: path14.join(root, "categories.local.json"),
6539
- claudeShimFile: path14.join(root, "claude-shim.json"),
6540
- headroomDir: path14.join(root, "headroom"),
6541
- headroomStateFile: path14.join(root, "headroom", `state.v${String(HEADROOM_STATE_SCHEMA_VERSION)}.json`),
6542
- headroomSocketDir: path14.join(root, "headroom", "run"),
6543
- headroomLockFile: path14.join(root, "headroom", "start.lock"),
6544
- headroomSessionsDir: path14.join(root, "headroom", "sessions"),
6545
- logsDir: path14.join(root, "logs"),
6546
- headroomLogPath: path14.join(root, "logs", "headroom.log"),
6547
- frontdoorDir: path14.join(root, "frontdoor"),
6548
- frontdoorCaDir: path14.join(root, "frontdoor", "ca"),
6549
- frontdoorCaCertFile: path14.join(root, "frontdoor", "ca", "ca.pem"),
6550
- frontdoorCaKeyFile: path14.join(root, "frontdoor", "ca", "ca.key"),
6551
- frontdoorCaBundlesDir: path14.join(root, "frontdoor", "ca", "bundles"),
6552
- frontdoorStateFile: path14.join(root, "frontdoor", "state.json"),
6553
- frontdoorLockFile: path14.join(root, "frontdoor", "start.lock"),
6554
- frontdoorControlTokenFile: path14.join(root, "frontdoor", "control-token"),
6555
- frontdoorSessionsDir: path14.join(root, "frontdoor", "sessions"),
6556
- frontdoorLogPath: path14.join(root, "logs", "frontdoor.log"),
6557
- usageDir: path14.join(root, "usage"),
6558
- usageLogDir: path14.join(root, "usage", "log"),
6559
- usageSnapshotsDir: path14.join(root, "usage", "snapshots"),
6560
- usagePicksFile: path14.join(root, "usage", "picks.json")
6726
+ identitiesDir: path16.join(root, "identities"),
6727
+ configProfilesDir: path16.join(root, "config-profiles"),
6728
+ providersDir: path16.join(root, "providers"),
6729
+ directoryRulesFile: path16.join(root, "directory-rules.json"),
6730
+ activeIdentityFile: path16.join(root, "active-identity"),
6731
+ globalConfigFile: path16.join(root, "config.json"),
6732
+ categoriesLocalFile: path16.join(root, "categories.local.json"),
6733
+ claudeShimFile: path16.join(root, "claude-shim.json"),
6734
+ headroomDir: path16.join(root, "headroom"),
6735
+ headroomStateFile: path16.join(root, "headroom", `state.v${String(HEADROOM_STATE_SCHEMA_VERSION)}.json`),
6736
+ headroomSocketDir: path16.join(root, "headroom", "run"),
6737
+ headroomLockFile: path16.join(root, "headroom", "start.lock"),
6738
+ headroomSessionsDir: path16.join(root, "headroom", "sessions"),
6739
+ logsDir: path16.join(root, "logs"),
6740
+ headroomLogPath: path16.join(root, "logs", "headroom.log"),
6741
+ frontdoorDir: path16.join(root, "frontdoor"),
6742
+ frontdoorCaDir: path16.join(root, "frontdoor", "ca"),
6743
+ frontdoorCaCertFile: path16.join(root, "frontdoor", "ca", "ca.pem"),
6744
+ frontdoorCaKeyFile: path16.join(root, "frontdoor", "ca", "ca.key"),
6745
+ frontdoorCaBundlesDir: path16.join(root, "frontdoor", "ca", "bundles"),
6746
+ frontdoorStateFile: path16.join(root, "frontdoor", "state.json"),
6747
+ frontdoorLockFile: path16.join(root, "frontdoor", "start.lock"),
6748
+ frontdoorControlTokenFile: path16.join(root, "frontdoor", "control-token"),
6749
+ frontdoorSessionsDir: path16.join(root, "frontdoor", "sessions"),
6750
+ frontdoorRcCredentialsDir: path16.join(root, "frontdoor", "rc-credentials"),
6751
+ frontdoorLogPath: path16.join(root, "logs", "frontdoor.log"),
6752
+ usageDir: path16.join(root, "usage"),
6753
+ usageLogDir: path16.join(root, "usage", "log"),
6754
+ usageSnapshotsDir: path16.join(root, "usage", "snapshots"),
6755
+ usagePicksFile: path16.join(root, "usage", "picks.json")
6561
6756
  };
6562
6757
  }
6563
6758
  function resolveLayoutPaths() {
@@ -6566,11 +6761,11 @@ function resolveLayoutPaths() {
6566
6761
 
6567
6762
  // src/identityStore.ts
6568
6763
  import fs5 from "node:fs";
6569
- import path16 from "node:path";
6764
+ import path18 from "node:path";
6570
6765
 
6571
6766
  // src/configProfilesStore.ts
6572
6767
  import fs4 from "node:fs";
6573
- import path15 from "node:path";
6768
+ import path17 from "node:path";
6574
6769
  var ProfileNotFoundError = class extends CliError {
6575
6770
  constructor(profileName) {
6576
6771
  super(`No configuration profile named "${profileName}". Run \`agent-shim profile add ${profileName}\` first.`);
@@ -6596,7 +6791,7 @@ var InvalidCategoryNameError = class extends CliError {
6596
6791
  categoryName;
6597
6792
  };
6598
6793
  function profileJsonPath(paths, name) {
6599
- return path15.join(paths.configProfilesDir, `${name}.json`);
6794
+ return path17.join(paths.configProfilesDir, `${name}.json`);
6600
6795
  }
6601
6796
  function profileExists(paths, name) {
6602
6797
  return fs4.existsSync(profileJsonPath(paths, name));
@@ -6784,7 +6979,7 @@ var InvalidIdentityNameError = class extends CliError {
6784
6979
  attemptedName;
6785
6980
  };
6786
6981
  function identityJsonPath(paths, name) {
6787
- return path16.join(paths.identitiesDir, name, "identity.json");
6982
+ return path18.join(paths.identitiesDir, name, "identity.json");
6788
6983
  }
6789
6984
  function identityExists(paths, name) {
6790
6985
  return fs5.existsSync(identityJsonPath(paths, name));
@@ -6885,7 +7080,7 @@ function setIdentityCredential(paths, identityName, change) {
6885
7080
  });
6886
7081
  }
6887
7082
  function belongsToIdentity(entry, name) {
6888
- return entry === name || entry === path16.basename(identityLockPath("", name)) || entry.startsWith(`.${name}.scratch.`) || entry.startsWith(`.${name}.previous.`);
7083
+ return entry === name || entry === path18.basename(identityLockPath("", name)) || entry.startsWith(`.${name}.scratch.`) || entry.startsWith(`.${name}.previous.`);
6889
7084
  }
6890
7085
  function removeIdentity(paths, name) {
6891
7086
  if (!identityExists(paths, name)) {
@@ -6893,7 +7088,7 @@ function removeIdentity(paths, name) {
6893
7088
  }
6894
7089
  for (const entry of fs5.readdirSync(paths.identitiesDir)) {
6895
7090
  if (belongsToIdentity(entry, name)) {
6896
- fs5.rmSync(path16.join(paths.identitiesDir, entry), { recursive: true, force: true });
7091
+ fs5.rmSync(path18.join(paths.identitiesDir, entry), { recursive: true, force: true });
6897
7092
  }
6898
7093
  }
6899
7094
  if (readActiveIdentity(paths) === name) {
@@ -6903,7 +7098,7 @@ function removeIdentity(paths, name) {
6903
7098
 
6904
7099
  // src/providersStore.ts
6905
7100
  import fs6 from "node:fs";
6906
- import path17 from "node:path";
7101
+ import path19 from "node:path";
6907
7102
 
6908
7103
  // src/credentialCache.ts
6909
7104
  import { z as z12 } from "zod";
@@ -7168,7 +7363,7 @@ var InvalidProviderNameError = class extends CliError {
7168
7363
  };
7169
7364
  var PROVIDER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]*$/;
7170
7365
  function providerJsonPath(paths, name) {
7171
- return path17.join(paths.providersDir, `${name}.json`);
7366
+ return path19.join(paths.providersDir, `${name}.json`);
7172
7367
  }
7173
7368
  function providerExists(paths, name) {
7174
7369
  return fs6.existsSync(providerJsonPath(paths, name));
@@ -7176,14 +7371,14 @@ function providerExists(paths, name) {
7176
7371
  var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
7177
7372
  var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
7178
7373
  var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
7179
- function isRecord4(value) {
7374
+ function isRecord5(value) {
7180
7375
  return typeof value === "object" && value !== null && !Array.isArray(value);
7181
7376
  }
7182
7377
  function legacyProviderConversion(raw) {
7183
- if (!isRecord4(raw) || "credential" in raw) {
7378
+ if (!isRecord5(raw) || "credential" in raw) {
7184
7379
  return void 0;
7185
7380
  }
7186
- const env = isRecord4(raw.env) ? raw.env : void 0;
7381
+ const env = isRecord5(raw.env) ? raw.env : void 0;
7187
7382
  const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
7188
7383
  const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
7189
7384
  if (fields.length === 0) {
@@ -7244,7 +7439,7 @@ function readProvider(paths, name) {
7244
7439
  return parseProviderFile(filePath, raw);
7245
7440
  }
7246
7441
  function loadProvider(providersDir, name, port) {
7247
- const filePath = path17.join(providersDir, `${name}.json`);
7442
+ const filePath = path19.join(providersDir, `${name}.json`);
7248
7443
  const raw = port.readConfigFile(filePath);
7249
7444
  return raw === void 0 ? void 0 : parseProviderFile(filePath, raw);
7250
7445
  }
@@ -7499,21 +7694,21 @@ function removeDirectoryRule(paths, rulePath) {
7499
7694
  import os5 from "node:os";
7500
7695
 
7501
7696
  // src/launcher/cascade.ts
7502
- import path19 from "node:path";
7697
+ import path21 from "node:path";
7503
7698
 
7504
7699
  // src/pathNorm.ts
7505
- import path18 from "node:path";
7700
+ import path20 from "node:path";
7506
7701
  function expandTilde(value, home) {
7507
7702
  if (value === "~") {
7508
7703
  return home;
7509
7704
  }
7510
7705
  if (value.startsWith("~/")) {
7511
- return path18.join(home, value.slice(2));
7706
+ return path20.join(home, value.slice(2));
7512
7707
  }
7513
7708
  return value;
7514
7709
  }
7515
7710
  function normaliseRulePath(rulePath, home) {
7516
- return path18.resolve(expandTilde(rulePath, home));
7711
+ return path20.resolve(expandTilde(rulePath, home));
7517
7712
  }
7518
7713
 
7519
7714
  // src/launcher/cascade.ts
@@ -7523,7 +7718,7 @@ function loadCascadeInput(params) {
7523
7718
  const globalConfig = loadConfigFile(params.paths.globalConfigFile, GlobalConfigSchema, params.read);
7524
7719
  const directoryRules = loadConfigFile(params.paths.directoryRulesFile, DirectoryRulesSchema, params.read);
7525
7720
  const loadProfile = (name) => {
7526
- const filepath = path19.join(params.paths.configProfilesDir, `${name}.json`);
7721
+ const filepath = path21.join(params.paths.configProfilesDir, `${name}.json`);
7527
7722
  const loaded = loadConfigFile(filepath, ConfigProfileSchema, params.read);
7528
7723
  if (loaded === void 0) {
7529
7724
  return void 0;
@@ -7538,8 +7733,8 @@ function loadCascadeInput(params) {
7538
7733
  });
7539
7734
  const levels = [];
7540
7735
  for (const dir of dirs) {
7541
- const portable = loadConfigFile(path19.join(dir, PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(path19.join(dir, LEGACY_PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read);
7542
- const portableLocal = loadConfigFile(path19.join(dir, PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(path19.join(dir, LEGACY_PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read);
7736
+ const portable = loadConfigFile(path21.join(dir, PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(path21.join(dir, LEGACY_PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read);
7737
+ const portableLocal = loadConfigFile(path21.join(dir, PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(path21.join(dir, LEGACY_PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read);
7543
7738
  const rules = (directoryRules?.config.rules ?? []).map((rule, index) => ({ rule, index })).filter(({ rule }) => normaliseRulePath(rule.path, params.home) === dir).map(({ rule, index }) => {
7544
7739
  const entryOrder = directoryRules?.ruleEntryOrders[index];
7545
7740
  return {
@@ -7590,7 +7785,7 @@ function readDirectorySelections(loaded) {
7590
7785
  // src/realCredentialCache.ts
7591
7786
  import { spawnSync } from "node:child_process";
7592
7787
  import fs7 from "node:fs";
7593
- import path20 from "node:path";
7788
+ import path22 from "node:path";
7594
7789
  var KEYCHAIN_CACHE_SERVICE = "agent-shim-credential-cache";
7595
7790
  var CACHE_DIR_MODE = 448;
7596
7791
  var CACHE_FILE_MODE = 384;
@@ -7617,8 +7812,8 @@ function security(args, stdin) {
7617
7812
  return { status: result.status, stdout: result.stdout };
7618
7813
  }
7619
7814
  function createRealCredentialCache(root) {
7620
- const dir = path20.join(root, "credential-cache");
7621
- const filePath = (owner) => path20.join(dir, cacheFileNameFor(owner));
7815
+ const dir = path22.join(root, "credential-cache");
7816
+ const filePath = (owner) => path22.join(dir, cacheFileNameFor(owner));
7622
7817
  return {
7623
7818
  read(store, owner) {
7624
7819
  if (store === "file") {
@@ -7670,72 +7865,8 @@ import { spawn, spawnSync as spawnSync2 } from "node:child_process";
7670
7865
  import { createHash as createHash5 } from "node:crypto";
7671
7866
  import fs8 from "node:fs";
7672
7867
  import os4 from "node:os";
7673
- import path22 from "node:path";
7868
+ import path23 from "node:path";
7674
7869
  import { isSea } from "node:sea";
7675
-
7676
- // src/versionDiscovery.ts
7677
- import os3 from "node:os";
7678
- import path21 from "node:path";
7679
- function defaultVersionsDir() {
7680
- return path21.join(os3.homedir(), ".local", "share", "claude", "versions");
7681
- }
7682
- var NUMERIC_DOTTED_VERSION_RE = /^\d+(?:\.\d+)*$/;
7683
- function isNumericDottedVersion(name) {
7684
- return NUMERIC_DOTTED_VERSION_RE.test(name);
7685
- }
7686
- function compareVersions2(a, b) {
7687
- if (!isNumericDottedVersion(a)) {
7688
- throw new Error(`compareVersions: "${a}" is not a valid plain dotted-numeric version`);
7689
- }
7690
- if (!isNumericDottedVersion(b)) {
7691
- throw new Error(`compareVersions: "${b}" is not a valid plain dotted-numeric version`);
7692
- }
7693
- const segmentsA = a.split(".").map(Number);
7694
- const segmentsB = b.split(".").map(Number);
7695
- const length = Math.max(segmentsA.length, segmentsB.length);
7696
- for (let i = 0; i < length; i += 1) {
7697
- const segmentA = segmentsA[i] ?? 0;
7698
- const segmentB = segmentsB[i] ?? 0;
7699
- if (segmentA !== segmentB) {
7700
- return segmentA - segmentB;
7701
- }
7702
- }
7703
- return 0;
7704
- }
7705
- function pickHighestVersion(entries) {
7706
- const candidates = entries.filter(
7707
- (entry) => entry.isFile && entry.isExecutable && entry.sizeBytes > 0 && isNumericDottedVersion(entry.name)
7708
- );
7709
- if (candidates.length === 0) {
7710
- return void 0;
7711
- }
7712
- return candidates.reduce(
7713
- (highest, candidate) => compareVersions2(candidate.name, highest.name) > 0 ? candidate : highest
7714
- ).name;
7715
- }
7716
- function discoverClaudeBinary(options) {
7717
- const versionsDir = options.versionsDir ?? defaultVersionsDir();
7718
- const entries = options.listVersionsDir(versionsDir);
7719
- const highestVersion = pickHighestVersion(entries);
7720
- if (highestVersion !== void 0) {
7721
- return {
7722
- path: path21.join(versionsDir, highestVersion),
7723
- source: "versions-dir",
7724
- version: highestVersion
7725
- };
7726
- }
7727
- for (const dir of options.pathDirs) {
7728
- const found = options.findExecutableInDir(dir, "claude");
7729
- if (found !== void 0 && !options.isOwnBinary(found)) {
7730
- return { path: found, source: "path-fallback" };
7731
- }
7732
- }
7733
- throw new Error(
7734
- `Could not find a claude binary. Looked for genuinely-executable versioned binaries under "${versionsDir}" (missing, empty, or containing nothing executable) and for a "claude" executable on PATH (never this tool's own binary). Install Claude Code, or set the versions directory / PATH correctly, then try again.`
7735
- );
7736
- }
7737
-
7738
- // src/realPorts.ts
7739
7870
  function isEnoent3(error) {
7740
7871
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
7741
7872
  }
@@ -7893,7 +8024,7 @@ var realRunPort = {
7893
8024
  }
7894
8025
  };
7895
8026
  function expandHome2(filePath) {
7896
- return filePath.startsWith("~/") ? path22.join(os4.homedir(), filePath.slice(1)) : filePath;
8027
+ return filePath.startsWith("~/") ? path23.join(os4.homedir(), filePath.slice(1)) : filePath;
7897
8028
  }
7898
8029
  var MACOS_GUI_SESSION_MANAGER = "Aqua";
7899
8030
  function hasDesktopSession() {
@@ -7966,7 +8097,7 @@ function spawnDaemonThrough(executable) {
7966
8097
  return (paths, subcommand, logPath) => spawnDetachedDaemon({ command: executable, args: [subcommand] }, paths, subcommand, logPath);
7967
8098
  }
7968
8099
  function spawnDetachedDaemon(invocation, paths, subcommand, logPath) {
7969
- fs8.mkdirSync(path22.dirname(logPath), { recursive: true });
8100
+ fs8.mkdirSync(path23.dirname(logPath), { recursive: true });
7970
8101
  const logFd = fs8.openSync(logPath, "a");
7971
8102
  try {
7972
8103
  const child = spawn(invocation.command, invocation.args, detachedDaemonSpawnOptions(logFd, { ...process.env, AGENT_SHIM_HOME: paths.root }));
@@ -8048,7 +8179,7 @@ function listVersionsDir(dir) {
8048
8179
  throw error;
8049
8180
  }
8050
8181
  return entries.filter((entry) => entry.isFile()).map((entry) => {
8051
- const fullPath = path22.join(dir, entry.name);
8182
+ const fullPath = path23.join(dir, entry.name);
8052
8183
  const stat = fs8.statSync(fullPath);
8053
8184
  return {
8054
8185
  name: entry.name,
@@ -8063,7 +8194,7 @@ function windowsPathExtensions(pathext) {
8063
8194
  return (pathext ?? WINDOWS_DEFAULT_PATHEXT).split(";").map((ext) => ext.trim()).filter((ext) => ext.length > 0);
8064
8195
  }
8065
8196
  function resolveExecutableCandidate(dir, name, env) {
8066
- const candidate = path22.join(dir, name);
8197
+ const candidate = path23.join(dir, name);
8067
8198
  if (env.platform === "win32") {
8068
8199
  const extensions = windowsPathExtensions(env.pathext);
8069
8200
  const alreadyHasRecognisedExtension = extensions.some((ext) => candidate.toLowerCase().endsWith(ext.toLowerCase()));
@@ -8112,13 +8243,13 @@ function resolveOwnExecutablePath(env) {
8112
8243
  if (env.argv1 !== void 0 && !env.argv1.includes("/") && !env.argv1.includes("\\")) {
8113
8244
  return searchPathForExecutable(env.pathDirs, env.argv1, env.findExecutableInDir) ?? env.execPath;
8114
8245
  }
8115
- const candidate = env.argv1 === void 0 ? env.execPath : path22.resolve(env.cwd, env.argv1);
8116
- const candidateDir = path22.dirname(candidate);
8117
- const candidateDirOnPath = env.pathDirs.some((dir) => path22.resolve(dir) === candidateDir);
8246
+ const candidate = env.argv1 === void 0 ? env.execPath : path23.resolve(env.cwd, env.argv1);
8247
+ const candidateDir = path23.dirname(candidate);
8248
+ const candidateDirOnPath = env.pathDirs.some((dir) => path23.resolve(dir) === candidateDir);
8118
8249
  if (candidateDirOnPath) {
8119
8250
  return candidate;
8120
8251
  }
8121
- const viaPath = searchPathForExecutable(env.pathDirs, path22.basename(candidate), env.findExecutableInDir);
8252
+ const viaPath = searchPathForExecutable(env.pathDirs, path23.basename(candidate), env.findExecutableInDir);
8122
8253
  return viaPath ?? candidate;
8123
8254
  }
8124
8255
  function realOwnExecutablePath() {
@@ -8126,7 +8257,7 @@ function realOwnExecutablePath() {
8126
8257
  argv1: process.argv[1],
8127
8258
  execPath: process.execPath,
8128
8259
  cwd: process.cwd(),
8129
- pathDirs: (process.env.PATH ?? "").split(path22.delimiter).filter((dir) => dir !== ""),
8260
+ pathDirs: (process.env.PATH ?? "").split(path23.delimiter).filter((dir) => dir !== ""),
8130
8261
  findExecutableInDir
8131
8262
  });
8132
8263
  }
@@ -8137,16 +8268,20 @@ function realContentSourcePath() {
8137
8268
  return resolveContentSourcePath({ isSea: isSea(), execPath: process.execPath, argv1: process.argv[1] });
8138
8269
  }
8139
8270
  function realResolveClaudeBinary(isOwnBinary) {
8140
- return () => discoverClaudeBinary({
8271
+ return (request4) => discoverClaudeBinary({
8141
8272
  listVersionsDir,
8142
- pathDirs: (process.env.PATH ?? "").split(path22.delimiter).filter((dir) => dir !== ""),
8273
+ pathDirs: (process.env.PATH ?? "").split(path23.delimiter).filter((dir) => dir !== ""),
8143
8274
  findExecutableInDir,
8144
- isOwnBinary
8275
+ isOwnBinary,
8276
+ ...request4?.version === void 0 ? {} : { version: request4.version }
8145
8277
  });
8146
8278
  }
8279
+ function realInstalledClaudeVersions() {
8280
+ return installedVersions(listVersionsDir(defaultVersionsDir()));
8281
+ }
8147
8282
 
8148
8283
  // src/checkReport.ts
8149
- import path23 from "node:path";
8284
+ import path24 from "node:path";
8150
8285
  import { z as z13 } from "zod";
8151
8286
 
8152
8287
  // src/cli/bool.ts
@@ -8185,6 +8320,34 @@ function parseEnvBool(variable, value) {
8185
8320
  return parsed;
8186
8321
  }
8187
8322
 
8323
+ // src/launcher/claudeVersion.ts
8324
+ var InvalidClaudeVersionError = class extends UsageError {
8325
+ constructor(value, origin) {
8326
+ super(`${origin} names Claude Code version "${value}", which is not an exact dotted-numeric version such as 2.1.220.`);
8327
+ this.value = value;
8328
+ this.origin = origin;
8329
+ this.name = "InvalidClaudeVersionError";
8330
+ }
8331
+ value;
8332
+ origin;
8333
+ };
8334
+ function validated(value, origin) {
8335
+ if (!NUMERIC_DOTTED_VERSION_RE.test(value)) {
8336
+ throw new InvalidClaudeVersionError(value, origin);
8337
+ }
8338
+ return value;
8339
+ }
8340
+ function resolveClaudeVersion(params) {
8341
+ if (params.flag !== void 0) {
8342
+ return { version: validated(params.flag, "--claude-version"), source: "flag" };
8343
+ }
8344
+ const fromEnv = params.env.AGENT_SHIM_CLAUDE_VERSION;
8345
+ if (fromEnv !== void 0 && fromEnv !== "") {
8346
+ return { version: validated(fromEnv, "AGENT_SHIM_CLAUDE_VERSION"), source: "environment" };
8347
+ }
8348
+ return params.cascade === void 0 ? void 0 : { version: params.cascade, source: "cascade" };
8349
+ }
8350
+
8188
8351
  // src/checkReport.ts
8189
8352
  var HISTORY_PROJECTS_KEY_PREFIX = "history/projects/";
8190
8353
  function projectFragmentOf(rawKey) {
@@ -8335,6 +8498,19 @@ function buildCredentialReport(provider, identity, identityName, cacheEnv) {
8335
8498
  ...identityCached === void 0 ? {} : { identityCached }
8336
8499
  };
8337
8500
  }
8501
+ function buildClaudeVersionReport(params, cascadePin) {
8502
+ const installed = params.installedClaudeVersions;
8503
+ if (installed === void 0) {
8504
+ return void 0;
8505
+ }
8506
+ const pin = resolveClaudeVersion({ env: params.env, ...cascadePin === void 0 ? {} : { cascade: cascadePin } });
8507
+ const highestInstalled = installed.at(-1);
8508
+ return {
8509
+ ...pin === void 0 ? {} : { pinned: { ...pin, installed: installed.includes(pin.version) } },
8510
+ ...highestInstalled === void 0 ? {} : { highestInstalled },
8511
+ installed
8512
+ };
8513
+ }
8338
8514
  function runCheck(params) {
8339
8515
  const facts = buildEntryFacts({
8340
8516
  fs: params.farmFs,
@@ -8359,6 +8535,7 @@ function runCheck(params) {
8359
8535
  const settingsShared = resolved.flattened.categories.get("settings") ?? SHIPPED_CATEGORY_DEFAULTS.settings;
8360
8536
  const settingsExposure = inspectSettingsExposure({ settingsShared, files: params.settingsFiles });
8361
8537
  const keychain = params.platform === "darwin" && params.run !== void 0 && params.farmRoot !== void 0 ? lookupKeychainService(params.run, params.farmRoot) : void 0;
8538
+ const claudeVersion = buildClaudeVersionReport(params, resolved.flattened.launch.claudeVersion);
8362
8539
  return {
8363
8540
  ...params.identityName === void 0 ? {} : { identityName: params.identityName },
8364
8541
  identitySource: params.identitySource,
@@ -8371,7 +8548,8 @@ function runCheck(params) {
8371
8548
  ambientCredential,
8372
8549
  ...keychain === void 0 ? {} : { keychain },
8373
8550
  settingsExposure,
8374
- credential: buildCredentialReport(params.provider, params.identity, params.identityName, params.credentialCache)
8551
+ credential: buildCredentialReport(params.provider, params.identity, params.identityName, params.credentialCache),
8552
+ ...claudeVersion === void 0 ? {} : { claudeVersion }
8375
8553
  };
8376
8554
  }
8377
8555
  function formatCheckReport(report) {
@@ -8420,6 +8598,17 @@ function formatCheckReport(report) {
8420
8598
  if (credential.applies === "stored-login") {
8421
8599
  lines.push(" The identity's stored login (no credential block applies).");
8422
8600
  }
8601
+ if (report.claudeVersion !== void 0) {
8602
+ lines.push("", "Claude Code version:");
8603
+ const { pinned, highestInstalled, installed } = report.claudeVersion;
8604
+ if (pinned !== void 0) {
8605
+ lines.push(
8606
+ pinned.installed ? ` ${pinned.version}, pinned by the ${pinned.source} (installed)` : ` ${pinned.version}, pinned by the ${pinned.source}, but NOT installed; a launch here fails. Installed: ${installed.length === 0 ? "none" : installed.join(", ")}`
8607
+ );
8608
+ } else {
8609
+ lines.push(highestInstalled === void 0 ? " none installed in the versions directory (a launch falls back to a claude on PATH)" : ` ${highestInstalled} (the highest installed; nothing pins a version)`);
8610
+ }
8611
+ }
8423
8612
  lines.push("", "Ambient-credential exposure:");
8424
8613
  lines.push(
8425
8614
  report.ambientCredential.ok ? ` OK \u2014 none of ${AMBIENT_CREDENTIAL_VARS.join(", ")} is set to a non-empty value.` : report.ambientCredential.message.split("\n").map((line) => ` ${line}`).join("\n")
@@ -8458,11 +8647,12 @@ function checkReportToJson(report) {
8458
8647
  ambientCredential: report.ambientCredential.ok ? { ok: true } : { ok: false, variable: report.ambientCredential.variable, message: report.ambientCredential.message },
8459
8648
  ...report.keychain === void 0 ? {} : { keychain: report.keychain },
8460
8649
  settingsExposure: report.settingsExposure,
8461
- credential: report.credential
8650
+ credential: report.credential,
8651
+ ...report.claudeVersion === void 0 ? {} : { claudeVersion: report.claudeVersion }
8462
8652
  };
8463
8653
  }
8464
8654
  function checkReportHasWarnings(report) {
8465
- return report.resolved.diagnostics.some((diagnostic) => diagnostic.severity !== "info") || report.projectEncodingAmbiguities.length > 0 || !report.ambientCredential.ok || report.credential.provider?.problem !== void 0;
8655
+ return report.resolved.diagnostics.some((diagnostic) => diagnostic.severity !== "info") || report.projectEncodingAmbiguities.length > 0 || !report.ambientCredential.ok || report.credential.provider?.problem !== void 0 || report.claudeVersion?.pinned?.installed === false;
8466
8656
  }
8467
8657
  function loadCheckProvider(paths, name) {
8468
8658
  let definition;
@@ -8523,10 +8713,10 @@ function collectCheckReport(params) {
8523
8713
  }).input;
8524
8714
  const providerName = cascadeProviderName(cascade);
8525
8715
  const provider = providerName === void 0 ? void 0 : loadCheckProvider(paths, providerName);
8526
- const farmRoot = identityDecision.name === void 0 ? void 0 : path23.join(paths.identitiesDir, identityDecision.name);
8716
+ const farmRoot = identityDecision.name === void 0 ? void 0 : path24.join(paths.identitiesDir, identityDecision.name);
8527
8717
  const settingsFiles = {
8528
- "settings.json": realFsPort.readFileUtf8(path23.join(claudeHome, "settings.json")),
8529
- "settings.local.json": realFsPort.readFileUtf8(path23.join(claudeHome, "settings.local.json"))
8718
+ "settings.json": realFsPort.readFileUtf8(path24.join(claudeHome, "settings.json")),
8719
+ "settings.local.json": realFsPort.readFileUtf8(path24.join(claudeHome, "settings.local.json"))
8530
8720
  };
8531
8721
  const report = runCheck({
8532
8722
  cwd,
@@ -8548,6 +8738,7 @@ function collectCheckReport(params) {
8548
8738
  settingsFiles,
8549
8739
  run: realRunPort,
8550
8740
  credentialCache: realCredentialCacheEnv(paths),
8741
+ installedClaudeVersions: realInstalledClaudeVersions(),
8551
8742
  ...farmRoot === void 0 ? {} : { farmRoot },
8552
8743
  platform: process.platform,
8553
8744
  ...provider === void 0 ? {} : { provider }
@@ -8557,7 +8748,7 @@ function collectCheckReport(params) {
8557
8748
 
8558
8749
  // src/claudeShim.ts
8559
8750
  import fs9 from "node:fs";
8560
- import path24 from "node:path";
8751
+ import path25 from "node:path";
8561
8752
  import { z as z14 } from "zod";
8562
8753
  var ClaudeShimStateSchema = z14.strictObject({
8563
8754
  targetPath: z14.string().min(1),
@@ -8569,10 +8760,10 @@ function commandFilename(ownExecutablePath, commandName) {
8569
8760
  }
8570
8761
  function resolveOwnBinaryCheck(paths, contentSourcePath) {
8571
8762
  const state = readJson(paths.claudeShimFile, ClaudeShimStateSchema);
8572
- const recordedTarget = state === void 0 ? void 0 : path24.resolve(state.targetPath);
8763
+ const recordedTarget = state === void 0 ? void 0 : path25.resolve(state.targetPath);
8573
8764
  let ownStat;
8574
8765
  return (candidate) => {
8575
- if (recordedTarget !== void 0 && path24.resolve(candidate) === recordedTarget) {
8766
+ if (recordedTarget !== void 0 && path25.resolve(candidate) === recordedTarget) {
8576
8767
  return true;
8577
8768
  }
8578
8769
  ownStat ??= statOrUndefined(contentSourcePath);
@@ -8596,20 +8787,20 @@ function statOrUndefined(target) {
8596
8787
  }
8597
8788
  var nodeLinkFs = { link: fs9.linkSync, copyFile: fs9.copyFileSync, chmod: fs9.chmodSync };
8598
8789
  function findPathShadow(params) {
8599
- const resolvedTargetDir = path24.resolve(params.targetDir);
8790
+ const resolvedTargetDir = path25.resolve(params.targetDir);
8600
8791
  for (const dir of params.pathDirs) {
8601
8792
  const found = params.findExecutableInDir(dir, params.targetFilename);
8602
8793
  if (found === void 0) {
8603
8794
  continue;
8604
8795
  }
8605
- return path24.resolve(dir) === resolvedTargetDir ? { status: "ok" } : { status: "shadowed", by: found };
8796
+ return path25.resolve(dir) === resolvedTargetDir ? { status: "ok" } : { status: "shadowed", by: found };
8606
8797
  }
8607
- return params.pathDirs.some((dir) => path24.resolve(dir) === resolvedTargetDir) ? { status: "ok" } : { status: "not-on-path" };
8798
+ return params.pathDirs.some((dir) => path25.resolve(dir) === resolvedTargetDir) ? { status: "ok" } : { status: "not-on-path" };
8608
8799
  }
8609
8800
 
8610
8801
  // src/doctorReport.ts
8611
8802
  import fs10 from "node:fs";
8612
- import path25 from "node:path";
8803
+ import path26 from "node:path";
8613
8804
  function refinePathShadow(status, ownExecutablePath, realpath) {
8614
8805
  if (status.status !== "shadowed") {
8615
8806
  return status;
@@ -8633,7 +8824,7 @@ function validateJson(schema, input) {
8633
8824
  return { ok: true, data: result.data };
8634
8825
  }
8635
8826
  function pushPathResolution(push, resolution) {
8636
- const ownDir = path25.dirname(resolution.ownExecutablePath);
8827
+ const ownDir = path26.dirname(resolution.ownExecutablePath);
8637
8828
  switch (resolution.agentShim.status) {
8638
8829
  case "ok":
8639
8830
  push("path-resolution", "pass", `\`agent-shim\` on PATH resolves to this running executable, ${resolution.ownExecutablePath}.`, "agent-shim");
@@ -8699,24 +8890,24 @@ function pushProvider(push, entry, cacheEnv) {
8699
8890
  push("provider", "fail", new LegacyProviderFileError(entry.path, conversion).message, entry.name);
8700
8891
  return;
8701
8892
  }
8702
- const validated = ProviderSchema.safeParse(parsed);
8703
- if (!validated.success) {
8704
- push("provider", "fail", new ConfigValidationError(entry.path, validated.error.issues).message, entry.name);
8893
+ const validated2 = ProviderSchema.safeParse(parsed);
8894
+ if (!validated2.success) {
8895
+ push("provider", "fail", new ConfigValidationError(entry.path, validated2.error.issues).message, entry.name);
8705
8896
  return;
8706
8897
  }
8707
- const cache = cacheClause(cacheEnv, `provider ${entry.name}`, validated.data.credential);
8708
- push("provider", cache.unreadable ? "fail" : "pass", `${entry.name} is valid (${describeProviderEndpoint(validated.data)}, credential ${describeCredential(validated.data.credential)})${cache.text}.`, entry.name);
8898
+ const cache = cacheClause(cacheEnv, `provider ${entry.name}`, validated2.data.credential);
8899
+ push("provider", cache.unreadable ? "fail" : "pass", `${entry.name} is valid (${describeProviderEndpoint(validated2.data)}, credential ${describeCredential(validated2.data.credential)})${cache.text}.`, entry.name);
8709
8900
  }
8710
8901
  function pushLegacyName(push, params) {
8711
8902
  const legacyVariables = Object.keys(params.env).filter((name) => name.startsWith(LEGACY_ENV_PREFIX) && params.env[name] !== void 0).sort();
8712
- if (legacyVariables.length === 0 && path25.basename(params.rootPath) !== LEGACY_HOME_DIRNAME) {
8903
+ if (legacyVariables.length === 0 && path26.basename(params.rootPath) !== LEGACY_HOME_DIRNAME) {
8713
8904
  push("legacy-name", "pass", "Nothing here still uses the former `claude-use` name.");
8714
8905
  return;
8715
8906
  }
8716
8907
  for (const name of legacyVariables) {
8717
8908
  push("legacy-name", "warn", `${name} is the former name of ${name.replace(LEGACY_ENV_PREFIX, ENV_PREFIX)}. Rename it; the old name still works but is only a fallback.`, name);
8718
8909
  }
8719
- if (path25.basename(params.rootPath) === LEGACY_HOME_DIRNAME) {
8910
+ if (path26.basename(params.rootPath) === LEGACY_HOME_DIRNAME) {
8720
8911
  push(
8721
8912
  "legacy-name",
8722
8913
  "warn",
@@ -8729,6 +8920,13 @@ function runDoctor(params) {
8729
8920
  const push = (section, severity, message, subject) => {
8730
8921
  findings.push({ section, severity, message, ...subject === void 0 ? {} : { subject } });
8731
8922
  };
8923
+ const checkPin = (section, subject, where, version) => {
8924
+ const installed = params.installedClaudeVersions;
8925
+ if (version === void 0 || installed === void 0 || installed.includes(version)) {
8926
+ return;
8927
+ }
8928
+ push(section, "warn", `${where} pins Claude Code ${version}, which is not installed (installed: ${installed.length === 0 ? "none" : installed.join(", ")}); a launch that resolves to it fails.`, subject);
8929
+ };
8732
8930
  const ambient = detectAmbientCredential(params.env);
8733
8931
  if (ambient === void 0) {
8734
8932
  push("ambient-credential", "pass", "No ambient-credential environment variable is set.");
@@ -8761,12 +8959,13 @@ function runDoctor(params) {
8761
8959
  pushLegacyName(push, params);
8762
8960
  const profileSources = /* @__PURE__ */ new Map();
8763
8961
  for (const entry of params.configProfiles) {
8764
- const validated = validateJson(ConfigProfileSchema, entry);
8765
- if (!validated.ok) {
8766
- push("config-profile", "fail", validated.message, entry.name);
8962
+ const validated2 = validateJson(ConfigProfileSchema, entry);
8963
+ if (!validated2.ok) {
8964
+ push("config-profile", "fail", validated2.message, entry.name);
8767
8965
  continue;
8768
8966
  }
8769
- profileSources.set(entry.name, { name: entry.name, profile: validated.data });
8967
+ profileSources.set(entry.name, { name: entry.name, profile: validated2.data });
8968
+ checkPin("config-profile", entry.name, `Configuration profile "${entry.name}"`, validated2.data.launch?.claudeVersion);
8770
8969
  }
8771
8970
  const loadProfile = (name) => profileSources.get(name);
8772
8971
  for (const name of profileSources.keys()) {
@@ -8782,13 +8981,13 @@ function runDoctor(params) {
8782
8981
  const validProfileNames = new Set(profileSources.keys());
8783
8982
  const validIdentityNames = /* @__PURE__ */ new Set();
8784
8983
  for (const entry of params.identities) {
8785
- const validated = validateJson(IdentitySchema, entry);
8786
- if (!validated.ok) {
8787
- push("identity", "fail", validated.message, entry.name);
8984
+ const validated2 = validateJson(IdentitySchema, entry);
8985
+ if (!validated2.ok) {
8986
+ push("identity", "fail", validated2.message, entry.name);
8788
8987
  continue;
8789
8988
  }
8790
8989
  validIdentityNames.add(entry.name);
8791
- const defaultProfile = validated.data.defaultConfigProfile;
8990
+ const defaultProfile = validated2.data.defaultConfigProfile;
8792
8991
  if (defaultProfile !== void 0 && !validProfileNames.has(defaultProfile)) {
8793
8992
  push(
8794
8993
  "identity",
@@ -8797,7 +8996,7 @@ function runDoctor(params) {
8797
8996
  entry.name
8798
8997
  );
8799
8998
  } else {
8800
- const credential = validated.data.credential;
8999
+ const credential = validated2.data.credential;
8801
9000
  const cache = credential === void 0 ? { text: "", unreadable: false } : cacheClause(params.credentialCache, `identity ${entry.name}`, credential);
8802
9001
  push(
8803
9002
  "identity",
@@ -8835,12 +9034,12 @@ function runDoctor(params) {
8835
9034
  if (params.directoryRules.raw === void 0) {
8836
9035
  push("directory-rules", "pass", "No directory-rules.json configured.");
8837
9036
  } else {
8838
- const validated = validateJson(DirectoryRulesSchema, params.directoryRules);
8839
- if (!validated.ok) {
8840
- push("directory-rules", "fail", validated.message);
9037
+ const validated2 = validateJson(DirectoryRulesSchema, params.directoryRules);
9038
+ if (!validated2.ok) {
9039
+ push("directory-rules", "fail", validated2.message);
8841
9040
  } else {
8842
9041
  push("directory-rules", "pass", `${params.directoryRules.path} is valid.`);
8843
- for (const rule of validated.data.rules) {
9042
+ for (const rule of validated2.data.rules) {
8844
9043
  const badRefs = [];
8845
9044
  if (rule.identity !== void 0 && !selectionExists(rule.identity)) {
8846
9045
  badRefs.push(poolNameOf(rule.identity) === void 0 ? `identity "${rule.identity}"` : `pool "${poolNameOf(rule.identity) ?? ""}"`);
@@ -8848,6 +9047,7 @@ function runDoctor(params) {
8848
9047
  if (rule.configProfile !== void 0 && !validProfileNames.has(rule.configProfile)) {
8849
9048
  badRefs.push(`configuration profile "${rule.configProfile}"`);
8850
9049
  }
9050
+ checkPin("directory-rules", rule.path, `Rule for "${rule.path}"`, rule.launch?.claudeVersion);
8851
9051
  if (badRefs.length > 0) {
8852
9052
  push("directory-rules", "fail", `Rule for "${rule.path}" names ${badRefs.join(" and ")}, which do not exist.`, rule.path);
8853
9053
  } else {
@@ -8859,11 +9059,11 @@ function runDoctor(params) {
8859
9059
  if (params.globalConfig.raw === void 0) {
8860
9060
  push("global-config", "pass", "No config.json configured.");
8861
9061
  } else {
8862
- const validated = validateJson(GlobalConfigSchema, params.globalConfig);
8863
- if (!validated.ok) {
8864
- push("global-config", "fail", validated.message);
9062
+ const validated2 = validateJson(GlobalConfigSchema, params.globalConfig);
9063
+ if (!validated2.ok) {
9064
+ push("global-config", "fail", validated2.message);
8865
9065
  } else {
8866
- const defaultProfile = validated.data.defaultConfigProfile;
9066
+ const defaultProfile = validated2.data.defaultConfigProfile;
8867
9067
  if (defaultProfile !== void 0 && !validProfileNames.has(defaultProfile)) {
8868
9068
  push(
8869
9069
  "global-config",
@@ -8872,27 +9072,28 @@ function runDoctor(params) {
8872
9072
  );
8873
9073
  } else {
8874
9074
  push("global-config", "pass", `${params.globalConfig.path} is valid.`);
9075
+ checkPin("global-config", void 0, "config.json", validated2.data.launch?.claudeVersion);
8875
9076
  }
8876
9077
  }
8877
9078
  }
8878
9079
  if (params.categoriesLocal.raw === void 0) {
8879
9080
  push("categories-local", "pass", "No categories.local.json configured.");
8880
9081
  } else {
8881
- const validated = validateJson(CategoryClassificationOverlaySchema, params.categoriesLocal);
9082
+ const validated2 = validateJson(CategoryClassificationOverlaySchema, params.categoriesLocal);
8882
9083
  push(
8883
9084
  "categories-local",
8884
- validated.ok ? "pass" : "fail",
8885
- validated.ok ? `${params.categoriesLocal.path} is valid.` : validated.message
9085
+ validated2.ok ? "pass" : "fail",
9086
+ validated2.ok ? `${params.categoriesLocal.path} is valid.` : validated2.message
8886
9087
  );
8887
9088
  }
8888
9089
  if (params.headroom.state.raw === void 0) {
8889
9090
  push("headroom", "pass", "No headroom daemon has ever run; nothing to check.");
8890
9091
  } else {
8891
- const validated = validateJson(HeadroomStateSchema, params.headroom.state);
8892
- if (!validated.ok) {
8893
- push("headroom", "fail", validated.message);
9092
+ const validated2 = validateJson(HeadroomStateSchema, params.headroom.state);
9093
+ if (!validated2.ok) {
9094
+ push("headroom", "fail", validated2.message);
8894
9095
  } else {
8895
- const state = validated.data;
9096
+ const state = validated2.data;
8896
9097
  const supervisorAlive = state.supervisorPid !== void 0 && params.headroom.isRunning(state.supervisorPid);
8897
9098
  const headroomAlive = state.headroomPid !== void 0 && params.headroom.isRunning(state.headroomPid);
8898
9099
  if (state.supervisorPid === void 0) {
@@ -9025,18 +9226,18 @@ function collectDoctorReport(params) {
9025
9226
  const { paths } = params;
9026
9227
  const identityNames = fs10.existsSync(paths.identitiesDir) ? fs10.readdirSync(paths.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && isIdentityDirectoryName(entry.name)).map((entry) => entry.name).sort() : [];
9027
9228
  const identities = identityNames.map((name) => {
9028
- const farmRoot = path25.join(paths.identitiesDir, name);
9029
- const identityPath = path25.join(farmRoot, "identity.json");
9229
+ const farmRoot = path26.join(paths.identitiesDir, name);
9230
+ const identityPath = path26.join(farmRoot, "identity.json");
9030
9231
  return { name, path: identityPath, raw: realFsPort.readFileUtf8(identityPath), farmRoot };
9031
9232
  });
9032
9233
  const profileNames = fs10.existsSync(paths.configProfilesDir) ? fs10.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort() : [];
9033
9234
  const configProfiles = profileNames.map((name) => {
9034
- const profilePath = path25.join(paths.configProfilesDir, `${name}.json`);
9235
+ const profilePath = path26.join(paths.configProfilesDir, `${name}.json`);
9035
9236
  return { name, path: profilePath, raw: realFsPort.readFileUtf8(profilePath) };
9036
9237
  });
9037
9238
  const providerNames = fs10.existsSync(paths.providersDir) ? fs10.readdirSync(paths.providersDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort() : [];
9038
9239
  const providers = providerNames.map((name) => {
9039
- const providerPath = path25.join(paths.providersDir, `${name}.json`);
9240
+ const providerPath = path26.join(paths.providersDir, `${name}.json`);
9040
9241
  return { name, path: providerPath, raw: realFsPort.readFileUtf8(providerPath) };
9041
9242
  });
9042
9243
  const ownExecutablePath = realOwnExecutablePath();
@@ -9048,12 +9249,12 @@ function collectDoctorReport(params) {
9048
9249
  binaryDiscovery = { ok: false, message: error instanceof Error ? error.message : String(error) };
9049
9250
  }
9050
9251
  const shimState = readJson(paths.claudeShimFile, ClaudeShimStateSchema);
9051
- const pathDirs = (params.env.PATH ?? "").split(path25.delimiter).filter((dir) => dir !== "");
9252
+ const pathDirs = (params.env.PATH ?? "").split(path26.delimiter).filter((dir) => dir !== "");
9052
9253
  const claudeShimShadow = shimState === void 0 ? void 0 : refinePathShadow(
9053
9254
  findPathShadow({
9054
9255
  pathDirs,
9055
- targetDir: path25.dirname(shimState.targetPath),
9056
- targetFilename: path25.basename(shimState.targetPath),
9256
+ targetDir: path26.dirname(shimState.targetPath),
9257
+ targetFilename: path26.basename(shimState.targetPath),
9057
9258
  findExecutableInDir
9058
9259
  }),
9059
9260
  shimState.targetPath,
@@ -9062,6 +9263,7 @@ function collectDoctorReport(params) {
9062
9263
  const report = runDoctor({
9063
9264
  env: params.env,
9064
9265
  credentialCache: realCredentialCacheEnv(paths),
9266
+ installedClaudeVersions: realInstalledClaudeVersions(),
9065
9267
  identities,
9066
9268
  configProfiles,
9067
9269
  providers,
@@ -9077,7 +9279,7 @@ function collectDoctorReport(params) {
9077
9279
  agentShim: refinePathShadow(
9078
9280
  findPathShadow({
9079
9281
  pathDirs,
9080
- targetDir: path25.dirname(ownExecutablePath),
9282
+ targetDir: path26.dirname(ownExecutablePath),
9081
9283
  targetFilename: commandFilename(ownExecutablePath, "agent-shim"),
9082
9284
  findExecutableInDir
9083
9285
  }),
@@ -9098,7 +9300,7 @@ function collectDoctorReport(params) {
9098
9300
 
9099
9301
  // src/launcher.ts
9100
9302
  import { randomUUID as randomUUID4 } from "node:crypto";
9101
- import path27 from "node:path";
9303
+ import path28 from "node:path";
9102
9304
 
9103
9305
  // src/launcher/argv.ts
9104
9306
  var ConflictingIdentityError = class extends UsageError {
@@ -9111,7 +9313,7 @@ var ConflictingIdentityError = class extends UsageError {
9111
9313
  positional;
9112
9314
  flag;
9113
9315
  };
9114
- var VALUED_FLAGS = ["--identity", "--config-profile", "--provider", "--category", "--share", "--hide"];
9316
+ var VALUED_FLAGS = ["--identity", "--config-profile", "--provider", "--claude-version", "--category", "--share", "--hide"];
9115
9317
  var BOOLEAN_FLAGS = [
9116
9318
  { flag: "--headroom", key: "headroom" },
9117
9319
  { flag: "--track-usage", key: "trackUsage" },
@@ -9157,6 +9359,7 @@ function parseLauncherArgv(argv) {
9157
9359
  let flagIdentity;
9158
9360
  let configProfile;
9159
9361
  let provider;
9362
+ let claudeVersion;
9160
9363
  const booleans = {};
9161
9364
  const categoryFlags = [];
9162
9365
  const shareFlags = [];
@@ -9211,6 +9414,9 @@ function parseLauncherArgv(argv) {
9211
9414
  case "--provider":
9212
9415
  provider = value;
9213
9416
  break;
9417
+ case "--claude-version":
9418
+ claudeVersion = value;
9419
+ break;
9214
9420
  case "--category":
9215
9421
  categoryFlags.push(value);
9216
9422
  break;
@@ -9232,6 +9438,7 @@ function parseLauncherArgv(argv) {
9232
9438
  ...identity === void 0 ? {} : { identity },
9233
9439
  ...configProfile === void 0 ? {} : { configProfile },
9234
9440
  ...provider === void 0 ? {} : { provider },
9441
+ ...claudeVersion === void 0 ? {} : { claudeVersion },
9235
9442
  ...native ? { native: true } : {},
9236
9443
  ...booleans,
9237
9444
  categoryFlags,
@@ -9358,7 +9565,7 @@ function buildCliOverride(params) {
9358
9565
  }
9359
9566
 
9360
9567
  // src/launcher/flags.ts
9361
- import path26 from "node:path";
9568
+ import path27 from "node:path";
9362
9569
 
9363
9570
  // src/headroom/headers.ts
9364
9571
  function mergeAnthropicCustomHeaders(existing, additions) {
@@ -9428,7 +9635,7 @@ function buildEnv(params) {
9428
9635
  const parentCustomHeaders = params.baseEnv.ANTHROPIC_CUSTOM_HEADERS;
9429
9636
  let providerCustomHeaders;
9430
9637
  if (!params.configDirEscapeHatch && params.resolvedIdentityName !== void 0) {
9431
- env.CLAUDE_CONFIG_DIR = path26.join(params.identitiesDir, params.resolvedIdentityName);
9638
+ env.CLAUDE_CONFIG_DIR = path27.join(params.identitiesDir, params.resolvedIdentityName);
9432
9639
  }
9433
9640
  if (params.provider !== void 0) {
9434
9641
  const providerEnv = params.provider.definition.env ?? {};
@@ -9589,7 +9796,7 @@ function prepareLaunch(params) {
9589
9796
  identityDefaultConfigProfile: loadedIdentity?.config.defaultConfigProfile,
9590
9797
  globalDefaultConfigProfile: params.globalDefaultConfigProfile
9591
9798
  });
9592
- if (configProfileDecision.name !== void 0 && params.allowMissingConfigProfile !== true && fs12.readConfigFile(path27.join(paths.configProfilesDir, `${configProfileDecision.name}.json`)) === void 0) {
9799
+ if (configProfileDecision.name !== void 0 && params.allowMissingConfigProfile !== true && fs12.readConfigFile(path28.join(paths.configProfilesDir, `${configProfileDecision.name}.json`)) === void 0) {
9593
9800
  log.error(
9594
9801
  `agent-shim: no configuration profile named "${configProfileDecision.name}" (selected via ${configProfileDecision.source}). Run \`agent-shim profile add ${configProfileDecision.name}\` first.`
9595
9802
  );
@@ -9693,7 +9900,11 @@ function prepareLaunch(params) {
9693
9900
  } else if (params.farm !== void 0) {
9694
9901
  cascadeLaunch = flattenLayers(assembleCascade(params.farm.loadCascade(configProfileDecision.name, cliOverride)).layers, { home: params.farm.home }).launch;
9695
9902
  }
9696
- const discovered = params.resolveClaudeBinary();
9903
+ const pinnedVersion = resolveClaudeVersion({ env, ...parsedArgv.claudeVersion === void 0 ? {} : { flag: parsedArgv.claudeVersion }, ...cascadeLaunch?.claudeVersion === void 0 ? {} : { cascade: cascadeLaunch.claudeVersion } });
9904
+ const discovered = params.resolveClaudeBinary(pinnedVersion === void 0 ? void 0 : { version: pinnedVersion.version });
9905
+ if (pinnedVersion !== void 0) {
9906
+ log.info(`agent-shim: Claude Code ${pinnedVersion.version} (pinned by ${pinnedVersion.source})`);
9907
+ }
9697
9908
  const resolvedFlags = resolveLaunchFlags({
9698
9909
  env,
9699
9910
  // A pool pick reads the usage the front door records, so a pool launch records its own unless something says otherwise.
@@ -9831,11 +10042,11 @@ function probeFrontDoorSync(port, caPem) {
9831
10042
 
9832
10043
  // src/frontdoor/trust.ts
9833
10044
  import { createHash as createHash6 } from "node:crypto";
9834
- import path28 from "node:path";
10045
+ import path29 from "node:path";
9835
10046
  var BUNDLE_NAME_HEX_DIGITS = 16;
9836
10047
  function resolveTrustBundle(params) {
9837
10048
  const { caCertFile, bundlesDir, variable, inherited, fs: fs12 } = params;
9838
- if (inherited === void 0 || inherited === "" || path28.resolve(inherited) === path28.resolve(caCertFile)) {
10049
+ if (inherited === void 0 || inherited === "" || path29.resolve(inherited) === path29.resolve(caCertFile)) {
9839
10050
  return { path: caCertFile };
9840
10051
  }
9841
10052
  const caPem = fs12.readFileUtf8(caCertFile);
@@ -9853,7 +10064,7 @@ function resolveTrustBundle(params) {
9853
10064
  }
9854
10065
  const combined = `${inheritedPem.trimEnd()}
9855
10066
  ${caPem}`;
9856
- const bundle = path28.join(bundlesDir, `${createHash6("sha256").update(combined).digest("hex").slice(0, BUNDLE_NAME_HEX_DIGITS)}.pem`);
10067
+ const bundle = path29.join(bundlesDir, `${createHash6("sha256").update(combined).digest("hex").slice(0, BUNDLE_NAME_HEX_DIGITS)}.pem`);
9857
10068
  if (!fs12.exists(bundle)) {
9858
10069
  fs12.mkdirp(bundlesDir);
9859
10070
  fs12.writeFileAtomic(bundle, combined);
@@ -10080,6 +10291,7 @@ export {
10080
10291
  createRcApiNodeHandler,
10081
10292
  createRcApiRouter,
10082
10293
  createRcControlHandler,
10294
+ createRcCredentialStore,
10083
10295
  createRcEventFanout,
10084
10296
  createRcSessionTracker,
10085
10297
  createRcStreamHub,