@haven_ai/connect 0.0.0-dev.202609031523.fd49e1a → 0.0.0-dev.202609031608.fceb089

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/README.md CHANGED
@@ -377,7 +377,9 @@ npx @haven_ai/connect@alpha --doctor --repair --runtime codex-desktop
377
377
  ```
378
378
 
379
379
  `--doctor` is read-only and needs NO setup token: it checks the runtime config,
380
- the agent credential files, the pinned signer runtime install, the hosted MCP
380
+ the agent credential files, the pinned signer runtime install (and, since
381
+ #2424, whether that install was made under a local runtime-spec override —
382
+ see the last section of this file), the hosted MCP
381
383
  (authorized `tools/list`), and starts the local signer for a real stdio
382
384
  handshake — reporting its advertised compat versions. Every failing check
383
385
  prints one concrete repair action; the exit code is non-zero on any failure.
@@ -404,3 +406,75 @@ which.
404
406
  `--repair` re-runs what setup already owns — reinstall the pinned signer
405
407
  runtime, rewrite the wrapper + sidecar, and re-write the runtime config from
406
408
  the STORED credentials. It never touches keys and never needs a new token.
409
+
410
+ ## Installing an unpublished signer / SDK / MCP build (`HAVEN_SIGNER_SPEC`, #2424)
411
+
412
+ Setup installs the connector's **pinned** siblings — `@haven_ai/signer@<pin>`
413
+ and `@haven_ai/sdk@<pin>` into `~/.haven/signer-runtime/<pin>`, and for
414
+ `--local` also `@haven_ai/mcp@<pin>` into `~/.haven/mcp-runtime/<pin>`. That
415
+ is right for every user and wrong for the one developer iterating on the
416
+ signer or SDK, who otherwise has to publish to find out whether a change works
417
+ end to end. Three environment variables name a different spec:
418
+
419
+ | Variable | Replaces | Read by |
420
+ |---|---|---|
421
+ | `HAVEN_SIGNER_SPEC` | `@haven_ai/signer@<pin>` | the signer runtime (default topology) |
422
+ | `HAVEN_SDK_SPEC` | `@haven_ai/sdk@<pin>` | both runtimes — each installs the SDK |
423
+ | `HAVEN_MCP_SPEC` | `@haven_ai/mcp@<pin>` | the `--local` MCP runtime |
424
+
425
+ A value is anything `npm install` accepts for that package: a checkout
426
+ (`file:/abs/path/to/packages/signer`), a tarball from `npm pack`
427
+ (`/abs/haven_ai-signer-0.0.0.tgz`), or an explicit version
428
+ (`@haven_ai/signer@<version>`). Set it in the shell that runs the setup
429
+ command — the command itself is unchanged:
430
+
431
+ ```bash
432
+ HAVEN_SIGNER_SPEC=file:$PWD/packages/signer npx @haven_ai/connect@alpha --setup <token> --runtime claude-code
433
+ ```
434
+
435
+ Environment variables rather than a flag, deliberately: the install runs from
436
+ three entry points (`--setup`, `--doctor --repair`, `--rekey-finish`) and all
437
+ three honour the same variables, so a re-key cannot silently reinstall the
438
+ registry build; and the setup command is minted by the dashboard and pasted
439
+ verbatim, often by an agent, so the override sits beside it instead of being
440
+ spliced into a line the developer did not write.
441
+
442
+ **What an active override changes:**
443
+
444
+ - The runtime directory is `~/.haven/signer-runtime/override-<hash>` (or
445
+ `mcp-runtime/override-<hash>`), keyed by a short hash of the **resolved**
446
+ specs — overridden and pinned alike — so it can never poison the
447
+ version-named directory the normal path reuses, and a pinned-sibling bump
448
+ gets a fresh one.
449
+ - The install is **never reused** from an earlier run: a rebuilt `file:`
450
+ package must not be shadowed by a cache hit.
451
+ - Setup prints `RUNTIME SPEC OVERRIDE ACTIVE …` first, naming each variable
452
+ and the pin it replaced; `--rekey-finish` prints the same line.
453
+ - `signer-runtime.json` / `mcp-runtime.json` record the override under
454
+ `runtime_spec_override`, and their `*_version` fields hold what npm actually
455
+ installed rather than the manifest pins.
456
+ - The wrapper the agent client launches carries a
457
+ `// HAVEN RUNTIME SPEC OVERRIDE (#2424): …` comment.
458
+ - `--doctor` reports a failing `runtime_spec_override` check — "runtime spec
459
+ overridden — not the pinned manifest" — whenever the sidecar says the
460
+ install ran under one **or** a `HAVEN_*_SPEC` variable is set in the shell
461
+ running the doctor (a `--repair` from that shell would install it). The
462
+ override is legitimate; the finding is its record. Under an override the
463
+ `signer_runtime` check compares the directory against the sidecar's own
464
+ record, not the manifest.
465
+
466
+ **What it never changes:** the post-setup handshake probe still requires
467
+ every tool in the manifest's `requiredTools` / `requiredSignerTools`, so a
468
+ local build that dropped a tool fails setup exactly like a bad registry
469
+ version would. A set-but-malformed value — empty, containing whitespace or a
470
+ shell metacharacter — is refused **before** npm runs and before anything is
471
+ written, with a message naming the variable. With no variable set, nothing
472
+ here runs: the install arguments, directory, sidecar and wrapper are
473
+ byte-for-byte what they were before the override existed (pinned by exact
474
+ characterization tests in `signer-runtime.test.ts` and
475
+ `local-mcp-runtime.test.ts`).
476
+
477
+ To return to the pinned manifest: unset the variables and run
478
+ `--doctor --repair --runtime <runtime>` (or re-run setup). The override
479
+ directories live under `~/.haven` like every other runtime directory, so the
480
+ same reset that removes `~/.haven` removes them.
package/dist/cli.cjs CHANGED
@@ -535,9 +535,9 @@ var init_runtime_manifest = __esm({
535
535
  mcpPackage: "@haven_ai/mcp",
536
536
  mcpVersion: mcp.MCP_VERSION,
537
537
  sdkPackage: "@haven_ai/sdk",
538
- sdkVersion: "0.0.0-dev.202609031523.fd49e1a",
538
+ sdkVersion: "0.0.0-dev.202609031608.fceb089",
539
539
  signerPackage: "@haven_ai/signer",
540
- signerVersion: "0.0.0-dev.202609031523.fd49e1a",
540
+ signerVersion: "0.0.0-dev.202609031608.fceb089",
541
541
  // Sourced from the SDK, never a literal (#1161). This field read '20.0.0'
542
542
  // while every package's `engines` said `>=24` and the docs said `>=24.0.0`,
543
543
  // so the guard that was supposed to enforce the floor waved Node v23 through
@@ -1608,32 +1608,129 @@ var init_probes = __esm({
1608
1608
  "src/probes.ts"() {
1609
1609
  }
1610
1610
  });
1611
+ function resolveRuntimeSpecOverride(env) {
1612
+ const override = {};
1613
+ for (const pkg of Object.keys(RUNTIME_SPEC_ENV)) {
1614
+ const variable = RUNTIME_SPEC_ENV[pkg];
1615
+ const raw = env[variable];
1616
+ if (raw === void 0) continue;
1617
+ override[pkg] = validateSpec(variable, raw);
1618
+ }
1619
+ return Object.keys(override).length > 0 ? override : void 0;
1620
+ }
1621
+ function validateSpec(variable, raw) {
1622
+ if (raw.length === 0 || raw.trim().length === 0) {
1623
+ throw new RuntimeSpecOverrideError(variable, "it is empty");
1624
+ }
1625
+ if (/\s/.test(raw)) {
1626
+ throw new RuntimeSpecOverrideError(variable, "it contains whitespace");
1627
+ }
1628
+ if (/[\u0000-\u001f\u007f]/.test(raw)) {
1629
+ throw new RuntimeSpecOverrideError(variable, "it contains a control character");
1630
+ }
1631
+ const meta = SHELL_METACHARACTERS.exec(raw);
1632
+ if (meta) {
1633
+ throw new RuntimeSpecOverrideError(variable, `it contains the shell metacharacter ${JSON.stringify(meta[0])}`);
1634
+ }
1635
+ return raw;
1636
+ }
1637
+ function runtimeSpecOverrideDirectoryKey(resolvedSpecs) {
1638
+ const digest = crypto.createHash("sha256").update(resolvedSpecs.join("\n")).digest("hex");
1639
+ return `override-${digest.slice(0, 12)}`;
1640
+ }
1641
+ function overrideApplies(override, packages) {
1642
+ return override !== void 0 && packages.some((pkg) => override[pkg] !== void 0);
1643
+ }
1644
+ function runtimeSpecOverrideNotice(runtimeLabel, override, pinned, runtimeDirectory) {
1645
+ const lines = [`RUNTIME SPEC OVERRIDE ACTIVE for the local Haven ${runtimeLabel} \u2014 this is NOT the pinned manifest.`];
1646
+ for (const pkg of Object.keys(RUNTIME_SPEC_ENV)) {
1647
+ const spec = override[pkg];
1648
+ if (spec === void 0) continue;
1649
+ const pin = pinned[pkg];
1650
+ lines.push(` ${RUNTIME_SPEC_ENV[pkg]}=${spec}${pin ? ` (instead of ${pin})` : ""}`);
1651
+ }
1652
+ lines.push(` Installing into ${runtimeDirectory} \u2014 the pinned runtime directory is untouched.`);
1653
+ lines.push(" Unset the variable(s) and re-run to return to the pinned manifest.");
1654
+ return lines;
1655
+ }
1656
+ function describeRuntimeSpecOverride(override) {
1657
+ return Object.keys(RUNTIME_SPEC_ENV).filter((pkg) => override[pkg] !== void 0).map((pkg) => `${RUNTIME_SPEC_ENV[pkg]}=${override[pkg]}`).join(" ");
1658
+ }
1659
+ var RUNTIME_SPEC_ENV, SHELL_METACHARACTERS, RuntimeSpecOverrideError;
1660
+ var init_runtime_spec_override = __esm({
1661
+ "src/runtime-spec-override.ts"() {
1662
+ RUNTIME_SPEC_ENV = {
1663
+ signer: "HAVEN_SIGNER_SPEC",
1664
+ sdk: "HAVEN_SDK_SPEC",
1665
+ mcp: "HAVEN_MCP_SPEC"
1666
+ };
1667
+ SHELL_METACHARACTERS = /[;&|<>$`'"()!#*?[\]{}\\]/;
1668
+ RuntimeSpecOverrideError = class extends Error {
1669
+ code = "runtime_spec_override_invalid";
1670
+ variable;
1671
+ constructor(variable, reason) {
1672
+ super(`${variable} is set but not usable as an npm package spec: ${reason}. Unset it, or set it to a spec npm install accepts (file:/abs/path, a .tgz, or @haven_ai/<pkg>@<version>).`);
1673
+ this.name = "RuntimeSpecOverrideError";
1674
+ this.variable = variable;
1675
+ }
1676
+ };
1677
+ }
1678
+ });
1611
1679
  async function prepareSignerRuntime(input, deps = {}) {
1612
1680
  const homeDir = input.homeDir ?? os.homedir();
1613
- const runtimeDirectory = path.resolve(homeDir, ".haven", "signer-runtime", MCP_RUNTIME_MANIFEST.signerVersion);
1681
+ const override = resolveSignerRuntimeOverride(deps.env ?? process.env);
1682
+ const signerSpec = override?.signer ?? signerPackageSpec();
1683
+ const sdkSpec = override?.sdk ?? sdkPackageSpec();
1684
+ const resolvedSpecs = [signerSpec, sdkSpec];
1685
+ const overrideRecord = override ? { specs: override, resolved_specs: resolvedSpecs, directory_key: runtimeSpecOverrideDirectoryKey(resolvedSpecs) } : void 0;
1686
+ const runtimeDirectory = path.resolve(
1687
+ homeDir,
1688
+ ".haven",
1689
+ "signer-runtime",
1690
+ overrideRecord ? overrideRecord.directory_key : MCP_RUNTIME_MANIFEST.signerVersion
1691
+ );
1614
1692
  const npmCacheDirectory = path.resolve(homeDir, ".haven", "npm-cache");
1615
1693
  const cliPath = path.join(runtimeDirectory, "node_modules", "@haven_ai", "signer", "dist", "cli.js");
1616
1694
  const messages = [];
1695
+ if (override) {
1696
+ messages.push(...runtimeSpecOverrideNotice(
1697
+ "signer runtime",
1698
+ override,
1699
+ { signer: signerPackageSpec(), sdk: sdkPackageSpec() },
1700
+ runtimeDirectory
1701
+ ));
1702
+ }
1617
1703
  await promises.mkdir(runtimeDirectory, { recursive: true, mode: 448 });
1618
1704
  await promises.chmod(runtimeDirectory, 448).catch(() => void 0);
1619
1705
  await promises.mkdir(npmCacheDirectory, { recursive: true, mode: 448 });
1620
1706
  await promises.chmod(npmCacheDirectory, 448).catch(() => void 0);
1621
- if (await installedRuntimeMatches(runtimeDirectory, cliPath)) {
1707
+ if (override) {
1708
+ await installRuntimePackages(runtimeDirectory, npmCacheDirectory, resolvedSpecs, deps);
1709
+ messages.push(`Installed local Haven signer runtime from override (${resolvedSpecs.join(" ")}).`);
1710
+ } else if (await installedRuntimeMatches(runtimeDirectory, cliPath)) {
1622
1711
  messages.push(`Using existing local Haven signer runtime ${signerPackageSpec()}.`);
1623
1712
  } else {
1624
- await installRuntimePackages(runtimeDirectory, npmCacheDirectory, deps);
1713
+ await installRuntimePackages(runtimeDirectory, npmCacheDirectory, resolvedSpecs, deps);
1625
1714
  messages.push(`Installed local Haven signer runtime ${signerPackageSpec()}.`);
1626
1715
  }
1627
1716
  await assertFileExists(cliPath, "local Haven signer CLI");
1717
+ const installedVersions = override ? await readInstalledVersions(runtimeDirectory) : void 0;
1628
1718
  const wrapperPath = path.join(input.credentialDirectory, "bin", "haven-signer.mjs");
1629
- await writeWrapper({ wrapperPath, cliPath, signerPath: input.signerPath });
1719
+ await writeWrapper({
1720
+ wrapperPath,
1721
+ cliPath,
1722
+ signerPath: input.signerPath,
1723
+ overrideComment: override ? describeRuntimeSpecOverride(override) : void 0
1724
+ });
1630
1725
  await writeRuntimeSidecar({
1631
1726
  path: path.join(input.credentialDirectory, "signer-runtime.json"),
1632
1727
  wrapperPath,
1633
1728
  runtimeDirectory,
1634
1729
  npmCacheDirectory,
1635
1730
  cliPath,
1636
- serverName: input.serverName
1731
+ serverName: input.serverName,
1732
+ override: overrideRecord,
1733
+ installedVersions
1637
1734
  });
1638
1735
  messages.push(`Prepared stable local Haven signer wrapper: ${wrapperPath}`);
1639
1736
  return {
@@ -1643,10 +1740,17 @@ async function prepareSignerRuntime(input, deps = {}) {
1643
1740
  runtimeDirectory,
1644
1741
  npmCacheDirectory,
1645
1742
  cliPath,
1646
- messages
1743
+ messages,
1744
+ ...overrideRecord ? { runtimeSpecOverride: overrideRecord } : {}
1647
1745
  };
1648
1746
  }
1649
- async function installRuntimePackages(runtimeDirectory, npmCacheDirectory, deps) {
1747
+ function resolveSignerRuntimeOverride(env) {
1748
+ const override = resolveRuntimeSpecOverride(env);
1749
+ if (!overrideApplies(override, ["signer", "sdk"])) return void 0;
1750
+ const { signer, sdk } = override;
1751
+ return { ...signer !== void 0 ? { signer } : {}, ...sdk !== void 0 ? { sdk } : {} };
1752
+ }
1753
+ async function installRuntimePackages(runtimeDirectory, npmCacheDirectory, packageSpecs, deps) {
1650
1754
  const { runCommand, onProgress } = deps;
1651
1755
  const baseArgs = [
1652
1756
  "install",
@@ -1656,8 +1760,7 @@ async function installRuntimePackages(runtimeDirectory, npmCacheDirectory, deps)
1656
1760
  "--no-fund",
1657
1761
  "--omit=dev",
1658
1762
  "--prefer-offline",
1659
- signerPackageSpec(),
1660
- sdkPackageSpec()
1763
+ ...packageSpecs
1661
1764
  ];
1662
1765
  const run = async (args) => {
1663
1766
  const startedAt = Date.now();
@@ -1680,23 +1783,33 @@ async function installRuntimePackages(runtimeDirectory, npmCacheDirectory, deps)
1680
1783
  await run([...baseArgs, "--cache", npmCacheDirectory]);
1681
1784
  } catch (err) {
1682
1785
  throw new Error(
1683
- `Could not install local Haven signer runtime ${signerPackageSpec()}: ${err instanceof Error ? err.message : String(err)}`
1786
+ `Could not install local Haven signer runtime ${packageSpecs.join(" ")}: ${err instanceof Error ? err.message : String(err)}`
1684
1787
  );
1685
1788
  }
1686
1789
  }
1687
1790
  }
1688
1791
  async function installedRuntimeMatches(runtimeDirectory, cliPath) {
1792
+ return installedRuntimeMatchesVersions(runtimeDirectory, cliPath, {
1793
+ signerVersion: MCP_RUNTIME_MANIFEST.signerVersion,
1794
+ sdkVersion: MCP_RUNTIME_MANIFEST.sdkVersion
1795
+ });
1796
+ }
1797
+ async function installedRuntimeMatchesVersions(runtimeDirectory, cliPath, expected) {
1689
1798
  try {
1690
1799
  await assertFileExists(cliPath, "local Haven signer CLI");
1691
- const [signerPackage, sdkPackage] = await Promise.all([
1692
- readPackageJson(path.join(runtimeDirectory, "node_modules", "@haven_ai", "signer", "package.json")),
1693
- readPackageJson(path.join(runtimeDirectory, "node_modules", "@haven_ai", "sdk", "package.json"))
1694
- ]);
1695
- return signerPackage.version === MCP_RUNTIME_MANIFEST.signerVersion && sdkPackage.version === MCP_RUNTIME_MANIFEST.sdkVersion;
1800
+ const installed = await readInstalledVersions(runtimeDirectory);
1801
+ return installed.signerVersion === expected.signerVersion && installed.sdkVersion === expected.sdkVersion;
1696
1802
  } catch {
1697
1803
  return false;
1698
1804
  }
1699
1805
  }
1806
+ async function readInstalledVersions(runtimeDirectory) {
1807
+ const [signerPackage, sdkPackage] = await Promise.all([
1808
+ readPackageJson(path.join(runtimeDirectory, "node_modules", "@haven_ai", "signer", "package.json")),
1809
+ readPackageJson(path.join(runtimeDirectory, "node_modules", "@haven_ai", "sdk", "package.json"))
1810
+ ]);
1811
+ return { signerVersion: signerPackage.version ?? "", sdkVersion: sdkPackage.version ?? "" };
1812
+ }
1700
1813
  async function readPackageJson(path) {
1701
1814
  return JSON.parse(await promises.readFile(path, "utf8"));
1702
1815
  }
@@ -1705,6 +1818,7 @@ async function writeWrapper(input) {
1705
1818
  await promises.chmod(path.dirname(input.wrapperPath), 448).catch(() => void 0);
1706
1819
  const source = [
1707
1820
  "#!/usr/bin/env node",
1821
+ ...input.overrideComment ? [`// HAVEN RUNTIME SPEC OVERRIDE (#2424): ${input.overrideComment} \u2014 this wrapper launches a NON-pinned signer build.`] : [],
1708
1822
  "import { spawn } from 'node:child_process'",
1709
1823
  "",
1710
1824
  `const cliPath = ${JSON.stringify(input.cliPath)}`,
@@ -1736,13 +1850,14 @@ async function writeRuntimeSidecar(input) {
1736
1850
  const value = {
1737
1851
  ...input.serverName ? { server_name: input.serverName } : {},
1738
1852
  signer_package: MCP_RUNTIME_MANIFEST.signerPackage,
1739
- signer_version: MCP_RUNTIME_MANIFEST.signerVersion,
1853
+ signer_version: input.installedVersions?.signerVersion ?? MCP_RUNTIME_MANIFEST.signerVersion,
1740
1854
  sdk_package: MCP_RUNTIME_MANIFEST.sdkPackage,
1741
- sdk_version: MCP_RUNTIME_MANIFEST.sdkVersion,
1855
+ sdk_version: input.installedVersions?.sdkVersion ?? MCP_RUNTIME_MANIFEST.sdkVersion,
1742
1856
  wrapper_path: input.wrapperPath,
1743
1857
  runtime_directory: input.runtimeDirectory,
1744
1858
  npm_cache_directory: input.npmCacheDirectory,
1745
- cli_path: input.cliPath
1859
+ cli_path: input.cliPath,
1860
+ ...input.override ? { runtime_spec_override: input.override } : {}
1746
1861
  };
1747
1862
  await promises.writeFile(input.path, `${JSON.stringify(value, null, 2)}
1748
1863
  `, { mode: 384 });
@@ -1759,6 +1874,7 @@ var execFileAsync, SIGNER_INSTALL_TIMEOUT_MS, SIGNER_INSTALL_HEARTBEAT_MS;
1759
1874
  var init_signer_runtime = __esm({
1760
1875
  "src/signer-runtime.ts"() {
1761
1876
  init_runtime_manifest();
1877
+ init_runtime_spec_override();
1762
1878
  execFileAsync = util.promisify(child_process.execFile);
1763
1879
  SIGNER_INSTALL_TIMEOUT_MS = 6e5;
1764
1880
  SIGNER_INSTALL_HEARTBEAT_MS = 15e3;
@@ -1767,27 +1883,50 @@ var init_signer_runtime = __esm({
1767
1883
  async function prepareLocalMcpRuntime(input, deps = {}) {
1768
1884
  assertSupportedNodeVersion(input.nodeVersion);
1769
1885
  const homeDir = input.homeDir ?? os.homedir();
1770
- const runtimeDirectory = path.resolve(homeDir, ".haven", "mcp-runtime", MCP_RUNTIME_MANIFEST.mcpVersion);
1886
+ const override = resolveLocalMcpRuntimeOverride(deps.env ?? process.env);
1887
+ const mcpSpec = override?.mcp ?? mcpPackageSpec();
1888
+ const sdkSpec = override?.sdk ?? sdkPackageSpec();
1889
+ const resolvedSpecs = [mcpSpec, sdkSpec];
1890
+ const overrideRecord = override ? { specs: override, resolved_specs: resolvedSpecs, directory_key: runtimeSpecOverrideDirectoryKey(resolvedSpecs) } : void 0;
1891
+ const runtimeDirectory = path.resolve(
1892
+ homeDir,
1893
+ ".haven",
1894
+ "mcp-runtime",
1895
+ overrideRecord ? overrideRecord.directory_key : MCP_RUNTIME_MANIFEST.mcpVersion
1896
+ );
1771
1897
  const npmCacheDirectory = path.resolve(homeDir, ".haven", "npm-cache");
1772
1898
  const cliPath = path.join(runtimeDirectory, "node_modules", "@haven_ai", "mcp", "dist", "cli.js");
1773
1899
  const messages = [];
1900
+ if (override) {
1901
+ messages.push(...runtimeSpecOverrideNotice(
1902
+ "MCP runtime",
1903
+ override,
1904
+ { mcp: mcpPackageSpec(), sdk: sdkPackageSpec() },
1905
+ runtimeDirectory
1906
+ ));
1907
+ }
1774
1908
  await promises.mkdir(runtimeDirectory, { recursive: true, mode: 448 });
1775
1909
  await promises.chmod(runtimeDirectory, 448).catch(() => void 0);
1776
1910
  await promises.mkdir(npmCacheDirectory, { recursive: true, mode: 448 });
1777
1911
  await promises.chmod(npmCacheDirectory, 448).catch(() => void 0);
1778
- if (await installedRuntimeMatches2(runtimeDirectory, cliPath)) {
1912
+ if (override) {
1913
+ await installRuntimePackages2(runtimeDirectory, npmCacheDirectory, resolvedSpecs, deps);
1914
+ messages.push(`Installed local Haven MCP runtime from override (${resolvedSpecs.join(" ")}).`);
1915
+ } else if (await installedRuntimeMatches2(runtimeDirectory, cliPath)) {
1779
1916
  messages.push(`Using existing local Haven MCP runtime ${mcpPackageSpec()}.`);
1780
1917
  } else {
1781
- await installRuntimePackages2(runtimeDirectory, npmCacheDirectory, deps);
1918
+ await installRuntimePackages2(runtimeDirectory, npmCacheDirectory, resolvedSpecs, deps);
1782
1919
  messages.push(`Installed local Haven MCP runtime ${mcpPackageSpec()}.`);
1783
1920
  }
1784
1921
  await assertFileExists2(cliPath, "local Haven MCP CLI");
1922
+ const installedVersions = override ? await readInstalledVersions2(runtimeDirectory) : void 0;
1785
1923
  const wrapperPath = path.join(input.credentialDirectory, "bin", "haven-mcp");
1786
1924
  await writeWrapper2({
1787
1925
  wrapperPath,
1788
1926
  cliPath,
1789
1927
  identityPath: input.identityPath,
1790
- signerPath: input.signerPath
1928
+ signerPath: input.signerPath,
1929
+ overrideComment: override ? describeRuntimeSpecOverride(override) : void 0
1791
1930
  });
1792
1931
  await writeRuntimeSidecar2({
1793
1932
  path: path.join(input.credentialDirectory, "mcp-runtime.json"),
@@ -1795,7 +1934,9 @@ async function prepareLocalMcpRuntime(input, deps = {}) {
1795
1934
  runtimeDirectory,
1796
1935
  npmCacheDirectory,
1797
1936
  cliPath,
1798
- serverName: input.serverName
1937
+ serverName: input.serverName,
1938
+ override: overrideRecord,
1939
+ installedVersions
1799
1940
  });
1800
1941
  messages.push(`Prepared stable local Haven MCP wrapper: ${wrapperPath}`);
1801
1942
  return {
@@ -1805,7 +1946,8 @@ async function prepareLocalMcpRuntime(input, deps = {}) {
1805
1946
  runtimeDirectory,
1806
1947
  npmCacheDirectory,
1807
1948
  cliPath,
1808
- messages
1949
+ messages,
1950
+ ...overrideRecord ? { runtimeSpecOverride: overrideRecord } : {}
1809
1951
  };
1810
1952
  }
1811
1953
  function assertSupportedNodeVersion(nodeVersion = process.versions.node, minimumNodeVersion = MCP_RUNTIME_MANIFEST.minimumNodeVersion, subject = "Haven setup") {
@@ -1813,7 +1955,13 @@ function assertSupportedNodeVersion(nodeVersion = process.versions.node, minimum
1813
1955
  throw new UnsupportedNodeVersionError(nodeVersion, minimumNodeVersion, subject);
1814
1956
  }
1815
1957
  }
1816
- async function installRuntimePackages2(runtimeDirectory, npmCacheDirectory, deps) {
1958
+ function resolveLocalMcpRuntimeOverride(env) {
1959
+ const override = resolveRuntimeSpecOverride(env);
1960
+ if (!overrideApplies(override, ["mcp", "sdk"])) return void 0;
1961
+ const { mcp, sdk } = override;
1962
+ return { ...mcp !== void 0 ? { mcp } : {}, ...sdk !== void 0 ? { sdk } : {} };
1963
+ }
1964
+ async function installRuntimePackages2(runtimeDirectory, npmCacheDirectory, packageSpecs, deps) {
1817
1965
  const { runCommand, onProgress } = deps;
1818
1966
  const baseArgs = [
1819
1967
  "install",
@@ -1823,8 +1971,7 @@ async function installRuntimePackages2(runtimeDirectory, npmCacheDirectory, deps
1823
1971
  "--no-fund",
1824
1972
  "--omit=dev",
1825
1973
  "--prefer-offline",
1826
- mcpPackageSpec(),
1827
- sdkPackageSpec()
1974
+ ...packageSpecs
1828
1975
  ];
1829
1976
  const run = async (args) => {
1830
1977
  const startedAt = Date.now();
@@ -1846,22 +1993,26 @@ async function installRuntimePackages2(runtimeDirectory, npmCacheDirectory, deps
1846
1993
  try {
1847
1994
  await run([...baseArgs, "--cache", npmCacheDirectory]);
1848
1995
  } catch (err) {
1849
- throw new Error(`Could not install local Haven MCP runtime ${mcpPackageSpec()}: ${err instanceof Error ? err.message : String(err)}`);
1996
+ throw new Error(`Could not install local Haven MCP runtime ${packageSpecs.join(" ")}: ${err instanceof Error ? err.message : String(err)}`);
1850
1997
  }
1851
1998
  }
1852
1999
  }
1853
2000
  async function installedRuntimeMatches2(runtimeDirectory, cliPath) {
1854
2001
  try {
1855
2002
  await assertFileExists2(cliPath, "local Haven MCP CLI");
1856
- const [mcpPackage, sdkPackage] = await Promise.all([
1857
- readPackageJson2(path.join(runtimeDirectory, "node_modules", "@haven_ai", "mcp", "package.json")),
1858
- readPackageJson2(path.join(runtimeDirectory, "node_modules", "@haven_ai", "sdk", "package.json"))
1859
- ]);
1860
- return mcpPackage.version === MCP_RUNTIME_MANIFEST.mcpVersion && sdkPackage.version === MCP_RUNTIME_MANIFEST.sdkVersion;
2003
+ const installed = await readInstalledVersions2(runtimeDirectory);
2004
+ return installed.mcpVersion === MCP_RUNTIME_MANIFEST.mcpVersion && installed.sdkVersion === MCP_RUNTIME_MANIFEST.sdkVersion;
1861
2005
  } catch {
1862
2006
  return false;
1863
2007
  }
1864
2008
  }
2009
+ async function readInstalledVersions2(runtimeDirectory) {
2010
+ const [mcpPackage, sdkPackage] = await Promise.all([
2011
+ readPackageJson2(path.join(runtimeDirectory, "node_modules", "@haven_ai", "mcp", "package.json")),
2012
+ readPackageJson2(path.join(runtimeDirectory, "node_modules", "@haven_ai", "sdk", "package.json"))
2013
+ ]);
2014
+ return { mcpVersion: mcpPackage.version ?? "", sdkVersion: sdkPackage.version ?? "" };
2015
+ }
1865
2016
  async function readPackageJson2(path) {
1866
2017
  return JSON.parse(await promises.readFile(path, "utf8"));
1867
2018
  }
@@ -1870,6 +2021,7 @@ async function writeWrapper2(input) {
1870
2021
  await promises.chmod(path.dirname(input.wrapperPath), 448).catch(() => void 0);
1871
2022
  const source = [
1872
2023
  "#!/usr/bin/env node",
2024
+ ...input.overrideComment ? [`// HAVEN RUNTIME SPEC OVERRIDE (#2424): ${input.overrideComment} \u2014 this wrapper launches a NON-pinned MCP build.`] : [],
1873
2025
  "import { spawn } from 'node:child_process'",
1874
2026
  "",
1875
2027
  `const cliPath = ${JSON.stringify(input.cliPath)}`,
@@ -1893,14 +2045,15 @@ async function writeRuntimeSidecar2(input) {
1893
2045
  const value = {
1894
2046
  ...input.serverName ? { server_name: input.serverName } : {},
1895
2047
  mcp_package: MCP_RUNTIME_MANIFEST.mcpPackage,
1896
- mcp_version: MCP_RUNTIME_MANIFEST.mcpVersion,
2048
+ mcp_version: input.installedVersions?.mcpVersion ?? MCP_RUNTIME_MANIFEST.mcpVersion,
1897
2049
  sdk_package: MCP_RUNTIME_MANIFEST.sdkPackage,
1898
- sdk_version: MCP_RUNTIME_MANIFEST.sdkVersion,
2050
+ sdk_version: input.installedVersions?.sdkVersion ?? MCP_RUNTIME_MANIFEST.sdkVersion,
1899
2051
  minimum_node_version: MCP_RUNTIME_MANIFEST.minimumNodeVersion,
1900
2052
  wrapper_path: input.wrapperPath,
1901
2053
  runtime_directory: input.runtimeDirectory,
1902
2054
  npm_cache_directory: input.npmCacheDirectory,
1903
- cli_path: input.cliPath
2055
+ cli_path: input.cliPath,
2056
+ ...input.override ? { runtime_spec_override: input.override } : {}
1904
2057
  };
1905
2058
  await promises.writeFile(input.path, `${JSON.stringify(value, null, 2)}
1906
2059
  `, { mode: 384 });
@@ -1918,6 +2071,7 @@ var init_local_mcp_runtime = __esm({
1918
2071
  "src/local-mcp-runtime.ts"() {
1919
2072
  init_signer_runtime();
1920
2073
  init_runtime_manifest();
2074
+ init_runtime_spec_override();
1921
2075
  execFileAsync2 = util.promisify(child_process.execFile);
1922
2076
  UnsupportedNodeVersionError = class extends Error {
1923
2077
  code = "local_mcp_unsupported_node_version";
@@ -2704,7 +2858,7 @@ function supportsLocalMcp(runtime) {
2704
2858
  return runtime === "codex-cli" || runtime === "codex-desktop" || runtime === "claude-code";
2705
2859
  }
2706
2860
  async function prepareRuntimeForLocalMcp(input, deps) {
2707
- const prepare = deps.prepareLocalMcpRuntime ?? ((runtimeInput) => prepareLocalMcpRuntime(runtimeInput, { runCommand: deps.runCommand, onProgress: deps.onProgress }));
2861
+ const prepare = deps.prepareLocalMcpRuntime ?? ((runtimeInput) => prepareLocalMcpRuntime(runtimeInput, { runCommand: deps.runCommand, onProgress: deps.onProgress, env: deps.env }));
2708
2862
  return prepare({
2709
2863
  credentialDirectory: input.credentialDirectory,
2710
2864
  identityPath: input.identityPath,
@@ -2719,7 +2873,7 @@ async function prepareSignerForRuntime(input, deps) {
2719
2873
  // install heartbeat was dead code in production and the console still
2720
2874
  // went silent for the whole cold install — the exact symptom the issue
2721
2875
  // set out to remove, at a longer timeout.
2722
- prepareSignerRuntime(runtimeInput, { runCommand: deps.runCommand, onProgress: deps.onProgress })
2876
+ prepareSignerRuntime(runtimeInput, { runCommand: deps.runCommand, onProgress: deps.onProgress, env: deps.env })
2723
2877
  ));
2724
2878
  return prepare({
2725
2879
  credentialDirectory: input.credentialDirectory,
@@ -3167,7 +3321,7 @@ async function finishRekey(options, deps = {}) {
3167
3321
  signerPath: `${stored.directory}/signer.json`,
3168
3322
  homeDir: options.homeDir
3169
3323
  },
3170
- { runCommand: deps.runCommand }
3324
+ { runCommand: deps.runCommand, env: deps.env }
3171
3325
  );
3172
3326
  const result = await (deps.writeConfig ?? writeHostedRuntimeConfig)(
3173
3327
  { runCommand: deps.runCommand, homeDir: options.homeDir },
@@ -3183,6 +3337,11 @@ async function finishRekey(options, deps = {}) {
3183
3337
  { command: prepared.command, args: prepared.args }
3184
3338
  );
3185
3339
  configRewritten = result.hostedConfigured;
3340
+ if (prepared.runtimeSpecOverride) {
3341
+ messages.push(
3342
+ ` Signer runtime: RUNTIME SPEC OVERRIDE ACTIVE \u2014 ${describeRuntimeSpecOverride(prepared.runtimeSpecOverride.specs)} (NOT the pinned manifest; installed into ${prepared.runtimeDirectory})`
3343
+ );
3344
+ }
3186
3345
  messages.push(` Config: ${result.target}`);
3187
3346
  messages.push(...result.messages.map((line) => ` ${line}`));
3188
3347
  if (!configRewritten) {
@@ -3249,6 +3408,7 @@ var init_rekey = __esm({
3249
3408
  init_api();
3250
3409
  init_runtime_install();
3251
3410
  init_signer_runtime();
3411
+ init_runtime_spec_override();
3252
3412
  init_key();
3253
3413
  init_redact();
3254
3414
  init_rekey_messages();
@@ -3425,6 +3585,46 @@ function rekeyPendingCheck(status, hostedDelegateAddress, runtime, slug) {
3425
3585
  detail: `A re-key started ${started} is still open (expires ${status.expiresAt ?? "unknown"}). Paste this address into "Replace signing key" on the Haven agent page: ${address}. Parked at ${status.path}. ` + wedgeNote
3426
3586
  };
3427
3587
  }
3588
+ async function runtimeSpecOverrideCheck(directory, sidecar, env) {
3589
+ const facts = [];
3590
+ const installed = sidecar?.runtime_spec_override;
3591
+ if (installed) {
3592
+ facts.push(
3593
+ `signer runtime installed under ${describeRuntimeSpecOverride(installed.specs)} (resolved: ${installed.resolved_specs.join(" ")}; directory key ${installed.directory_key})`
3594
+ );
3595
+ }
3596
+ const mcpInstalled = await readMcpSidecarOverride(directory);
3597
+ if (mcpInstalled) {
3598
+ facts.push(
3599
+ `local MCP runtime installed under ${describeRuntimeSpecOverride(mcpInstalled.specs)} (resolved: ${mcpInstalled.resolved_specs.join(" ")}; directory key ${mcpInstalled.directory_key})`
3600
+ );
3601
+ }
3602
+ let shell;
3603
+ try {
3604
+ const active = resolveRuntimeSpecOverride(env);
3605
+ if (active) shell = `set in this shell: ${describeRuntimeSpecOverride(active)} \u2014 a --repair from here installs these, not the pin`;
3606
+ } catch (err) {
3607
+ shell = err instanceof RuntimeSpecOverrideError ? `set in this shell but REFUSED: ${err.message}` : `set in this shell but unreadable: ${err instanceof Error ? err.message : String(err)}`;
3608
+ }
3609
+ if (shell) facts.push(shell);
3610
+ if (facts.length === 0) return void 0;
3611
+ const variables = Object.values(RUNTIME_SPEC_ENV).join(" / ");
3612
+ return {
3613
+ id: "runtime_spec_override",
3614
+ label: "Runtime spec override",
3615
+ ok: false,
3616
+ detail: `runtime spec overridden \u2014 not the pinned manifest (${MCP_RUNTIME_MANIFEST.signerPackage}@${MCP_RUNTIME_MANIFEST.signerVersion}, ${MCP_RUNTIME_MANIFEST.sdkPackage}@${MCP_RUNTIME_MANIFEST.sdkVersion}). ${facts.join(". ")}.`,
3617
+ repair: `Developer override (#2424). To return to the pinned manifest: unset ${variables}, then run ${RERUN} --doctor --repair --runtime <runtime>. If the override is intentional, this finding is the record of it.`
3618
+ };
3619
+ }
3620
+ async function readMcpSidecarOverride(directory) {
3621
+ try {
3622
+ const parsed = JSON.parse(await promises.readFile(path.join(directory, "mcp-runtime.json"), "utf8"));
3623
+ return parsed.runtime_spec_override;
3624
+ } catch {
3625
+ return void 0;
3626
+ }
3627
+ }
3428
3628
  async function readIdentity(directory) {
3429
3629
  try {
3430
3630
  return JSON.parse(await promises.readFile(path.join(directory, "identity.json"), "utf8"));
@@ -3459,6 +3659,18 @@ async function checksForAgent(entry, input, deps) {
3459
3659
  detail: "No signer-runtime.json sidecar \u2014 the pinned signer runtime was never prepared (or a pre-#1586 npx config).",
3460
3660
  repair: `Run: ${RERUN} --doctor --repair --runtime ${input.runtime}`
3461
3661
  });
3662
+ } else if (sidecar.runtime_spec_override) {
3663
+ const matches = await installedRuntimeMatchesVersions(sidecar.runtime_directory, sidecar.cli_path, {
3664
+ signerVersion: sidecar.signer_version,
3665
+ sdkVersion: sidecar.sdk_version
3666
+ });
3667
+ checks.push({
3668
+ id: "signer_runtime",
3669
+ label: "Signer runtime (preinstalled wrapper)",
3670
+ ok: matches,
3671
+ detail: matches ? `Installed ${sidecar.signer_package}@${sidecar.signer_version} at ${sidecar.runtime_directory} (override install \u2014 see runtime_spec_override)` : `Override runtime directory is stale or empty (${sidecar.runtime_directory}) \u2014 the CLI or package versions are missing.`,
3672
+ ...matches ? {} : { repair: `Run: ${RERUN} --doctor --repair --runtime ${input.runtime} with the same HAVEN_*_SPEC variables set.` }
3673
+ });
3462
3674
  } else {
3463
3675
  const matches = await installedRuntimeMatches(sidecar.runtime_directory, sidecar.cli_path);
3464
3676
  const versionOk = sidecar.signer_version === MCP_RUNTIME_MANIFEST.signerVersion;
@@ -3471,6 +3683,8 @@ async function checksForAgent(entry, input, deps) {
3471
3683
  ...ok ? {} : { repair: `Run: ${RERUN} --doctor --repair --runtime ${input.runtime}` }
3472
3684
  });
3473
3685
  }
3686
+ const overrideCheck = await runtimeSpecOverrideCheck(directory, sidecar, deps.env ?? process.env);
3687
+ if (overrideCheck) checks.push(overrideCheck);
3474
3688
  const hostedUrl = identity?.hosted_mcp_url ?? (identity?.api_url ? `${identity.api_url}/mcp` : void 0);
3475
3689
  if (identity?.api_key && hostedUrl) {
3476
3690
  const probe = await (deps.probeHosted ?? probeHostedMcpTools)(identity.api_key, hostedUrl, deps.fetch);
@@ -3665,7 +3879,7 @@ async function runDoctor(input, deps = {}) {
3665
3879
  signerCapabilities = result.signerCapabilities;
3666
3880
  for (const check of result.checks) primaryChecksById.set(check.id, check);
3667
3881
  }
3668
- for (const id of ["credentials", "signer_runtime"]) {
3882
+ for (const id of ["credentials", "signer_runtime", "runtime_spec_override"]) {
3669
3883
  const check = primaryChecksById.get(id);
3670
3884
  if (check) checks.push(check);
3671
3885
  }
@@ -3828,7 +4042,7 @@ async function runRepair(input, deps = {}) {
3828
4042
  const signerPath = path.join(directory, "signer.json");
3829
4043
  const prepared = await prepareSignerRuntime(
3830
4044
  { credentialDirectory: directory, signerPath, homeDir, serverName },
3831
- { runCommand: deps.runCommand }
4045
+ { runCommand: deps.runCommand, env: deps.env }
3832
4046
  );
3833
4047
  messages.push(...prepared.messages);
3834
4048
  const names = serverNamesFor(serverName);
@@ -3855,6 +4069,7 @@ var init_doctor = __esm({
3855
4069
  init_runtime_manifest();
3856
4070
  init_probes();
3857
4071
  init_signer_runtime();
4072
+ init_runtime_spec_override();
3858
4073
  init_config_writers();
3859
4074
  init_runtime_registry();
3860
4075
  init_signer_consent();
@@ -4055,7 +4270,7 @@ function defaultPromptIo() {
4055
4270
  // src/runtime.ts
4056
4271
  init_local_mcp_runtime();
4057
4272
  init_runtime_manifest();
4058
- var CONNECTOR_VERSION = "0.0.0-dev.202609031523.fd49e1a";
4273
+ var CONNECTOR_VERSION = "0.0.0-dev.202609031608.fceb089";
4059
4274
  var CONNECT_OUTCOME_SCHEMA_VERSION = 1;
4060
4275
  var failureOutcomesByError = /* @__PURE__ */ new WeakMap();
4061
4276
  function failureOutcomeFor(runtimeHint, error) {