@mcpjam/inspector 3.3.5 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.md +33 -0
  2. package/bin/launch-workspace.d.mts +17 -0
  3. package/bin/launch-workspace.mjs +74 -0
  4. package/bin/start.js +31 -0
  5. package/dist/client/assets/dead-clicks-autocapture-BOZKS-GG.js +2 -0
  6. package/dist/client/assets/exception-autocapture-BbnuQ6YK.js +2 -0
  7. package/dist/client/assets/highlighted-body-OFNGDK62-BJ-PSGAP.js +2 -0
  8. package/dist/client/assets/{index-C-H-Rua1.js → index-CbyipXXu.js} +8 -2488
  9. package/dist/client/assets/index-Cd1YrfPX.css +1 -0
  10. package/dist/client/assets/jszip.min-Bh_hqLQz.js +2 -0
  11. package/dist/client/assets/posthog-recorder-CestZegQ.js +2 -0
  12. package/dist/client/assets/surveys-BjqljrVF.js +2 -0
  13. package/dist/client/assets/web-vitals-Bqx_fCFg.js +2 -0
  14. package/dist/client/claude-desktop-logo.png +0 -0
  15. package/dist/client/guest-credit-wall.png +0 -0
  16. package/dist/client/index.html +2 -2
  17. package/dist/client/user-testing-empty.png +0 -0
  18. package/dist/server/{chunk-GHVMCYUP.js → chunk-BLGJ4PIK.js} +1101 -134
  19. package/dist/server/harness-install-cli.js +1 -1
  20. package/dist/server/index.js +96027 -60973
  21. package/package.json +10 -4
  22. package/dist/client/assets/dead-clicks-autocapture-Df9Whene.js +0 -2
  23. package/dist/client/assets/exception-autocapture-J7Yit1Qe.js +0 -3
  24. package/dist/client/assets/highlighted-body-OFNGDK62-F3kgBmdV.js +0 -2
  25. package/dist/client/assets/index-BG0jrXch.css +0 -1
  26. package/dist/client/assets/jszip.min-6UaK1SU2.js +0 -3
  27. package/dist/client/assets/posthog-recorder-B9ffo2hn.js +0 -109
  28. package/dist/client/assets/surveys-O8sqQqoR.js +0 -2
  29. package/dist/client/assets/web-vitals-CKCOd0b9.js +0 -2
@@ -12,11 +12,19 @@ var ALLOWED_ENVIRONMENTS = [
12
12
  "local",
13
13
  "test"
14
14
  ];
15
+ var ENVIRONMENT_ALIASES = {
16
+ production: "prod",
17
+ development: "dev"
18
+ };
15
19
  function resolveEnvironment() {
16
- const fromEnv = process.env.ENVIRONMENT;
20
+ const fromEnv = process.env.ENVIRONMENT?.trim();
17
21
  if (fromEnv && ALLOWED_ENVIRONMENTS.includes(fromEnv)) {
18
22
  return fromEnv;
19
23
  }
24
+ const aliased = fromEnv ? ENVIRONMENT_ALIASES[fromEnv] : void 0;
25
+ if (aliased) {
26
+ return aliased;
27
+ }
20
28
  if (process.env.NODE_ENV === "test") return "test";
21
29
  if (process.env.NODE_ENV === "production") {
22
30
  if (!warnedMissingEnv) {
@@ -29,7 +37,7 @@ function resolveEnvironment() {
29
37
  }
30
38
  return "dev";
31
39
  }
32
- var BAKED_VERSION = "3.3.5";
40
+ var BAKED_VERSION = "3.4.0";
33
41
  function blankToNull(value) {
34
42
  return value === void 0 || value.trim() === "" ? null : value;
35
43
  }
@@ -342,19 +350,19 @@ function emit(eventName, base, payload, options) {
342
350
  process.on("beforeExit", () => logger.flush());
343
351
 
344
352
  // server/utils/harness/local/runtime-install.ts
345
- import { createHash as createHash2, randomUUID } from "crypto";
353
+ import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
346
354
  import { createReadStream, createWriteStream } from "fs";
347
355
  import {
348
- mkdir,
349
- readdir as readdir3,
350
- readFile as readFile3,
351
- rename,
352
- rm,
353
- stat as stat2,
354
- writeFile
356
+ mkdir as mkdir3,
357
+ readdir as readdir4,
358
+ readFile as readFile5,
359
+ rename as rename2,
360
+ rm as rm3,
361
+ stat as stat4,
362
+ writeFile as writeFile3
355
363
  } from "fs/promises";
356
364
  import { homedir } from "os";
357
- import { basename, join as join2 } from "path";
365
+ import { basename, join as join5 } from "path";
358
366
  import { pipeline } from "stream/promises";
359
367
  import { Readable } from "stream";
360
368
  import { x as extractTar } from "tar";
@@ -372,7 +380,18 @@ var EXPECTED_PACK_VERSION = "";
372
380
 
373
381
  // server/utils/harness/local/pack-signing-key.ts
374
382
  import { createPublicKey, verify as edVerify } from "crypto";
375
- var PACK_SIGNING_KEYS = [];
383
+ var PACK_SIGNING_KEYS = [
384
+ {
385
+ // Generated 2026-09-02. The private half went straight into the CI secret
386
+ // `LOCAL_HARNESS_PACK_SIGNING_KEY` and exists nowhere else — there is no
387
+ // copy to lose, and recovery is rotation, not restore.
388
+ keyId: "pack-2026-09",
389
+ publicKeyPem: `-----BEGIN PUBLIC KEY-----
390
+ MCowBQYDK2VwAyEAjaZXzr+4k5pJEuzIy5JUU858ksLJgjZv9b9jxmNhV6A=
391
+ -----END PUBLIC KEY-----
392
+ `
393
+ }
394
+ ];
376
395
  function verifyPackManifestSignature(manifestBytes, signatureBase64, keys = PACK_SIGNING_KEYS) {
377
396
  if (keys.length === 0) {
378
397
  return {
@@ -413,7 +432,7 @@ function verifyPackManifestSignature(manifestBytes, signatureBase64, keys = PACK
413
432
  import { createHash } from "crypto";
414
433
  import { execFile as execFile2 } from "child_process";
415
434
  import { readdir as readdir2, readFile as readFile2, realpath, stat } from "fs/promises";
416
- import { dirname, isAbsolute, join, normalize, relative, sep } from "path";
435
+ import { dirname, isAbsolute, join as join2, normalize, relative, sep } from "path";
417
436
 
418
437
  // server/utils/harness/local/targets.ts
419
438
  var LOCAL_PACK_TARGETS = /* @__PURE__ */ new Set([
@@ -437,8 +456,9 @@ function currentLocalPlatform(platform = process.platform) {
437
456
  }
438
457
 
439
458
  // server/utils/harness/local/process-identity.ts
440
- import { execFile } from "child_process";
459
+ import { execFile, spawn } from "child_process";
441
460
  import { readdir, readFile } from "fs/promises";
461
+ import { join } from "path";
442
462
  var PS_TIMEOUT_MS = 3e3;
443
463
  function parseLinuxProcStat(raw) {
444
464
  const close = raw.lastIndexOf(")");
@@ -523,12 +543,125 @@ async function probeDarwin(pid) {
523
543
  identity: `darwin:${parsed.lstart}|${parsed.command}`
524
544
  };
525
545
  }
546
+ var WIN32_PROBE_TIMEOUT_MS = 15e3;
547
+ function win32SystemRoot() {
548
+ return process.env.SYSTEMROOT ?? process.env.WINDIR ?? "C:\\Windows";
549
+ }
550
+ function win32PowerShellPath() {
551
+ return join(
552
+ win32SystemRoot(),
553
+ "System32",
554
+ "WindowsPowerShell",
555
+ "v1.0",
556
+ "powershell.exe"
557
+ );
558
+ }
559
+ function parseWin32ProbeLine(raw) {
560
+ const line = raw.trim();
561
+ const match = /^(\d+)\|(.+)$/.exec(line);
562
+ if (match === null) return null;
563
+ return { fileTime: match[1], name: match[2] };
564
+ }
565
+ async function probeWin32(pid) {
566
+ try {
567
+ process.kill(pid, 0);
568
+ } catch (error) {
569
+ const code = error.code;
570
+ if (code === "ESRCH") return { state: "gone" };
571
+ if (code !== "EPERM") {
572
+ return {
573
+ state: "unknown",
574
+ reason: `kill(0) failed (${code ?? "unknown"})`
575
+ };
576
+ }
577
+ }
578
+ const script = [
579
+ `$p = Get-Process -Id ${pid} -ErrorAction SilentlyContinue`,
580
+ "if ($null -eq $p) { exit 1 }",
581
+ "try { $t = $p.StartTime.ToFileTimeUtc() } catch { exit 3 }",
582
+ "Write-Output ('{0}|{1}' -f $t, $p.ProcessName)"
583
+ ].join("; ");
584
+ const result = await new Promise((resolve) => {
585
+ let settled = false;
586
+ let timer;
587
+ const done = (value) => {
588
+ if (settled) return;
589
+ settled = true;
590
+ if (timer !== void 0) clearTimeout(timer);
591
+ resolve(value);
592
+ };
593
+ let child;
594
+ try {
595
+ child = spawn(
596
+ win32PowerShellPath(),
597
+ [
598
+ "-NoProfile",
599
+ "-NonInteractive",
600
+ "-InputFormat",
601
+ "None",
602
+ "-ExecutionPolicy",
603
+ "Bypass",
604
+ "-Command",
605
+ script
606
+ ],
607
+ { stdio: ["ignore", "pipe", "pipe"], env: process.env, windowsHide: true }
608
+ );
609
+ } catch (error) {
610
+ done({
611
+ ok: false,
612
+ exitCode: null,
613
+ reason: `PowerShell failed to start (${String(
614
+ error.code ?? "unknown"
615
+ )})`
616
+ });
617
+ return;
618
+ }
619
+ let stdout = "";
620
+ child.stdout?.setEncoding("utf8");
621
+ child.stdout?.on("data", (chunk) => {
622
+ if (stdout.length < 16 * 1024) stdout += chunk;
623
+ });
624
+ child.stderr?.resume();
625
+ timer = setTimeout(() => {
626
+ try {
627
+ child.kill();
628
+ } catch {
629
+ }
630
+ done({ ok: false, exitCode: null, reason: "Get-Process timed out" });
631
+ }, WIN32_PROBE_TIMEOUT_MS);
632
+ child.on(
633
+ "error",
634
+ (error) => done({
635
+ ok: false,
636
+ exitCode: null,
637
+ reason: `PowerShell failed (${String(error.code ?? "unknown")})`
638
+ })
639
+ );
640
+ child.on("close", (code) => {
641
+ if (code === 0) done({ ok: true, stdout });
642
+ else done({ ok: false, exitCode: code, reason: `Get-Process exited ${code}` });
643
+ });
644
+ });
645
+ if (!result.ok) {
646
+ if (result.exitCode === 1) return { state: "gone" };
647
+ return { state: "unknown", reason: result.reason };
648
+ }
649
+ const parsed = parseWin32ProbeLine(result.stdout);
650
+ if (parsed === null) {
651
+ return { state: "unknown", reason: "unparseable Get-Process output" };
652
+ }
653
+ return {
654
+ state: "alive",
655
+ identity: `win32:${parsed.fileTime}|${parsed.name}`
656
+ };
657
+ }
526
658
  async function probeProcess(pid, platform = process.platform) {
527
659
  if (!Number.isInteger(pid) || pid <= 0) {
528
660
  return { state: "unknown", reason: "implausible pid" };
529
661
  }
530
662
  if (platform === "linux") return probeLinux(pid);
531
663
  if (platform === "darwin") return probeDarwin(pid);
664
+ if (platform === "win32") return probeWin32(pid);
532
665
  return { state: "unknown", reason: `no liveness primitive on ${platform}` };
533
666
  }
534
667
  async function readProcessBirthIdentity(pid, platform = process.platform) {
@@ -629,6 +762,12 @@ async function probeProcessGroup(pid, platform = process.platform) {
629
762
  if (!Number.isInteger(pid) || pid <= 0) return "unknown";
630
763
  if (platform === "linux") return probeLinuxGroup(pid);
631
764
  if (platform === "darwin") return probeDarwinGroup(pid);
765
+ if (platform === "win32") {
766
+ const root = await probeProcess(pid, platform);
767
+ if (root.state === "gone") return "empty";
768
+ if (root.state === "alive") return "live";
769
+ return "unknown";
770
+ }
632
771
  return "unknown";
633
772
  }
634
773
  async function readProcessGroupId(pid, platform = process.platform) {
@@ -732,7 +871,8 @@ function signalProcessGroup(pid, signal, platform = process.platform) {
732
871
  if (!Number.isInteger(pid) || pid <= 0) return false;
733
872
  try {
734
873
  if (platform === "win32") {
735
- return false;
874
+ process.kill(pid, signal);
875
+ return true;
736
876
  }
737
877
  process.kill(-pid, signal);
738
878
  return true;
@@ -830,7 +970,7 @@ async function digestTreeWithSnapshot(root) {
830
970
  const entries = await readdir2(dir, { withFileTypes: true });
831
971
  entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
832
972
  for (const entry of entries) {
833
- const full = join(dir, entry.name);
973
+ const full = join2(dir, entry.name);
834
974
  const rel = relative(root, full).split(sep).join("/");
835
975
  if (entry.isSymbolicLink()) {
836
976
  throw new Error(
@@ -968,7 +1108,7 @@ async function resolveManagedBundle(args) {
968
1108
  };
969
1109
  }
970
1110
  const policy = manifest.runtime;
971
- const root = join(runtimeRoot, policy.bundleName);
1111
+ const root = join2(runtimeRoot, policy.bundleName);
972
1112
  let canonicalRoot;
973
1113
  try {
974
1114
  canonicalRoot = await realpath(root);
@@ -1012,7 +1152,7 @@ async function resolveManagedBundle(args) {
1012
1152
  }
1013
1153
  const digest = verification.digest;
1014
1154
  const insideBundle = async (relativePath, label, requireExecutable) => {
1015
- const full = join(canonicalRoot, relativePath);
1155
+ const full = join2(canonicalRoot, relativePath);
1016
1156
  if (!normalize(full).startsWith(canonicalRoot + sep)) {
1017
1157
  return {
1018
1158
  ok: false,
@@ -1185,7 +1325,7 @@ async function resolveSystemInstall(opts) {
1185
1325
  const rejections = [];
1186
1326
  for (const dir of searchPaths) {
1187
1327
  for (const name of policy.executableNames) {
1188
- const candidate = join(dir, name);
1328
+ const candidate = join2(dir, name);
1189
1329
  let canonical;
1190
1330
  try {
1191
1331
  canonical = await realpath(candidate);
@@ -1325,7 +1465,7 @@ async function detectSnapshotDrift(snapshot) {
1325
1465
  const walk = async (dir) => {
1326
1466
  const entries = await readdir2(dir, { withFileTypes: true });
1327
1467
  for (const entry of entries) {
1328
- const full = join(dir, entry.name);
1468
+ const full = join2(dir, entry.name);
1329
1469
  const rel = relative(snapshot.root, full).split(sep).join("/");
1330
1470
  if (entry.isSymbolicLink()) return `a symlink appeared at ${rel}`;
1331
1471
  if (entry.isDirectory()) {
@@ -1406,6 +1546,568 @@ async function revalidateRuntime(runtime) {
1406
1546
  }
1407
1547
  }
1408
1548
 
1549
+ // server/utils/harness/local/compatibility.ts
1550
+ var LOCAL_HARNESS_MANIFEST = {
1551
+ "claude-code": {
1552
+ harnessId: "claude-code",
1553
+ adapterVersion: "1.0.100",
1554
+ runtime: {
1555
+ source: "managed-bundle",
1556
+ bundleName: "claude-code",
1557
+ // Generated by the release pack build, and EMPTY until one has run.
1558
+ // Either way the result is `bundle-absent` before any tree is read, but
1559
+ // WHICH message depends on what is on disk: with no bundle directory —
1560
+ // today's state — `resolveManagedBundle` reports the missing bundle
1561
+ // first; with a directory present, the empty map has no entry for this
1562
+ // machine's `<os>-<arch>` and it names the target instead ("no pack has
1563
+ // been built for linux-x64"). Both fail closed; only the second names a
1564
+ // pack a user could go looking for.
1565
+ bundleDigest: PACK_TREE_DIGESTS["claude-code"],
1566
+ launcherRelativePath: "launcher.mjs",
1567
+ nodeLauncherRelativePath: "bin/node",
1568
+ jobLauncherRelativePath: "bin/mcpjam-job-launcher.exe",
1569
+ vendorPackages: {
1570
+ "@anthropic-ai/claude-code": "pinned-by-adapter-bridge-lockfile"
1571
+ }
1572
+ },
1573
+ argvPolicy: { requiredFlags: [], deniedFlags: [] },
1574
+ configStrategy: "synthetic-home",
1575
+ supportsBuiltinToolApprovals: true,
1576
+ permissionProfileMapping: {
1577
+ "read-only": "allow-reads",
1578
+ "workspace-edits": "allow-edits"
1579
+ // `unrestricted` is deliberately absent: Claude Code CAN surface
1580
+ // approvals, so there is no reason to offer a profile that switches them
1581
+ // off on a host.
1582
+ },
1583
+ // Native eligibility is per platform. macOS and Linux have POSIX process
1584
+ // groups, which is what whole-tree cleanup is built on there.
1585
+ //
1586
+ // Windows has no process group; its whole-tree guarantee is a Job Object
1587
+ // with KILL_ON_JOB_CLOSE, created by `tools/mcpjam-job-launcher`, which
1588
+ // ships INSIDE the Windows pack so its bytes are covered by the tree
1589
+ // digest. Listing `win32` here is necessary but not sufficient:
1590
+ // `supportsOwnershipProof('win32')` still answers false until runtime
1591
+ // resolution has verified that launcher, so a pack without one keeps the
1592
+ // platform refused exactly as before.
1593
+ //
1594
+ // Earned, not asserted: the conformance suite's `windows-latest` leg runs
1595
+ // the full scenario and the abort scenario against the real pack and the
1596
+ // real vendor CLI, and its survivor scan is what turns "we wrote a Job
1597
+ // Object" into evidence that stopping a session stops everything it
1598
+ // started. That leg is gating; the day it goes red, this entry is wrong.
1599
+ nativePlatforms: ["darwin", "linux", "win32"],
1600
+ // Empty until a backend's escape probes actually pass (I6).
1601
+ isolatedBackends: {},
1602
+ lifecycleConformanceVersion: "",
1603
+ adapterBootstrapDir: ".harness-bootstrap/claude-code",
1604
+ adapterBootstrapFiles: [
1605
+ "package.json",
1606
+ "pnpm-lock.yaml",
1607
+ "pnpm-workspace.yaml",
1608
+ "bridge.mjs"
1609
+ ],
1610
+ bridgeBundleDigest: `sha256:${"0".repeat(64)}`
1611
+ },
1612
+ codex: {
1613
+ harnessId: "codex",
1614
+ adapterVersion: "1.0.98",
1615
+ runtime: {
1616
+ source: "managed-bundle",
1617
+ bundleName: "codex",
1618
+ bundleDigest: PACK_TREE_DIGESTS.codex,
1619
+ launcherRelativePath: "launcher.mjs",
1620
+ nodeLauncherRelativePath: "bin/node",
1621
+ vendorPackages: {
1622
+ "@openai/codex-sdk": "pinned-by-adapter-bridge-lockfile"
1623
+ }
1624
+ },
1625
+ argvPolicy: { requiredFlags: [], deniedFlags: [] },
1626
+ configStrategy: "explicit-config-root",
1627
+ supportsBuiltinToolApprovals: false,
1628
+ // No mapping at all: the adapter rejects every mode but `allow-all`, and
1629
+ // `allow-all` on a host with no outer boundary is the thing this plan
1630
+ // exists to prevent. Codex earns a mapping when its adapter gains a
1631
+ // reviewed restricted mode, or inside a verified isolation backend.
1632
+ permissionProfileMapping: {},
1633
+ nativePlatforms: [],
1634
+ isolatedBackends: {},
1635
+ lifecycleConformanceVersion: "",
1636
+ adapterBootstrapDir: ".harness-bootstrap/codex",
1637
+ adapterBootstrapFiles: ["package.json", "pnpm-lock.yaml", "bridge.mjs"],
1638
+ bridgeBundleDigest: `sha256:${"0".repeat(64)}`
1639
+ }
1640
+ };
1641
+ function localPermissionModeFor(harnessId, permissionProfile, targetKind) {
1642
+ const manifests = LOCAL_HARNESS_MANIFEST;
1643
+ const manifest = Object.prototype.hasOwnProperty.call(manifests, harnessId) ? manifests[harnessId] : void 0;
1644
+ if (manifest === void 0) return null;
1645
+ if (permissionProfile === "unrestricted" && targetKind === "local-native") {
1646
+ return null;
1647
+ }
1648
+ return manifest.permissionProfileMapping[permissionProfile] ?? null;
1649
+ }
1650
+ function resolveLocalCompatibility(query, manifests = LOCAL_HARNESS_MANIFEST) {
1651
+ const manifest = Object.prototype.hasOwnProperty.call(
1652
+ manifests,
1653
+ query.harnessId
1654
+ ) ? manifests[query.harnessId] : void 0;
1655
+ if (!manifest) {
1656
+ return {
1657
+ ok: false,
1658
+ status: "harness-not-supported",
1659
+ message: `${query.harnessId} has no reviewed local compatibility manifest. Run it hosted, or add a manifest entry with conformance evidence.`
1660
+ };
1661
+ }
1662
+ if (query.installedAdapterVersion !== manifest.adapterVersion) {
1663
+ return {
1664
+ ok: false,
1665
+ status: "adapter-version-mismatch",
1666
+ message: `${query.harnessId} adapter ${query.installedAdapterVersion ?? "(version not supplied)"} is installed but the local manifest was reviewed against ${manifest.adapterVersion}. An adapter upgrade can change the command shapes the local provider translates, so it must be re-reviewed before local execution is enabled.`
1667
+ };
1668
+ }
1669
+ if (manifest.lifecycleConformanceVersion === "") {
1670
+ return {
1671
+ ok: false,
1672
+ status: "conformance-missing",
1673
+ message: `${query.harnessId} has no recorded lifecycle conformance evidence. Local execution stays disabled until the conformance suite has been run for this harness/runtime/platform/mode tuple and its version is recorded in the manifest.`
1674
+ };
1675
+ }
1676
+ if (query.platform === null) {
1677
+ return {
1678
+ ok: false,
1679
+ status: "platform-not-supported",
1680
+ // NOT "run the harness hosted instead". A requested-local turn has Send
1681
+ // disabled in this state (`localHarnessBlocksSend`), so nothing falls
1682
+ // back — and on a local Inspector with no cloud data plane there is no
1683
+ // hosted target to switch to either, which makes the instruction
1684
+ // unactionable exactly where it is most likely to be read.
1685
+ message: `local execution is not supported on this platform.`
1686
+ };
1687
+ }
1688
+ if (query.targetKind === "local-native") {
1689
+ if (!manifest.nativePlatforms.includes(query.platform)) {
1690
+ const reason = manifest.nativePlatforms.length === 0 ? `${query.harnessId} is not eligible for native mode on any platform: its adapter cannot surface tool approvals, so native execution would give it the OS user's full authority with no approval gate. Use hosted, or a verified isolation backend.` : `${query.harnessId} native mode is not supported on ${query.platform} (supported: ${manifest.nativePlatforms.join(", ")}).`;
1691
+ return {
1692
+ ok: false,
1693
+ status: "native-not-eligible",
1694
+ message: reason
1695
+ };
1696
+ }
1697
+ } else {
1698
+ if (query.backend === void 0) {
1699
+ return {
1700
+ ok: false,
1701
+ status: "backend-not-verified",
1702
+ message: "an isolated target must name its isolation backend."
1703
+ };
1704
+ }
1705
+ const verifiedHere = manifest.isolatedBackends[query.platform] ?? [];
1706
+ if (!verifiedHere.includes(query.backend)) {
1707
+ return {
1708
+ ok: false,
1709
+ status: "backend-not-verified",
1710
+ message: `isolation backend ${query.backend} has not passed conformance for ${query.harnessId} on ${query.platform}. Isolated mode never falls back to native \u2014 run hosted until the backend is verified for this platform.`
1711
+ };
1712
+ }
1713
+ }
1714
+ const permissionMode = manifest.permissionProfileMapping[query.permissionProfile];
1715
+ if (permissionMode === void 0) {
1716
+ const offered = Object.keys(manifest.permissionProfileMapping);
1717
+ return {
1718
+ ok: false,
1719
+ status: "permission-profile-not-supported",
1720
+ message: `${query.harnessId} does not offer the ${query.permissionProfile} permission profile locally` + (offered.length ? ` (offered: ${offered.join(", ")}).` : `; it has no locally offered profile at all.`)
1721
+ };
1722
+ }
1723
+ if (query.permissionProfile === "unrestricted" && query.targetKind === "local-native") {
1724
+ return {
1725
+ ok: false,
1726
+ status: "permission-profile-not-supported",
1727
+ message: `the unrestricted profile requires a verified isolation backend. Native mode has no host containment, so an unrestricted turn there would run with the OS user's full authority.`
1728
+ };
1729
+ }
1730
+ return { ok: true, manifest, permissionMode };
1731
+ }
1732
+
1733
+ // server/utils/harness/local/runtime-lifecycle.ts
1734
+ import { randomUUID as randomUUID2 } from "crypto";
1735
+ import {
1736
+ mkdir as mkdir2,
1737
+ readdir as readdir3,
1738
+ readFile as readFile4,
1739
+ rename,
1740
+ rm as rm2,
1741
+ stat as stat3,
1742
+ writeFile as writeFile2
1743
+ } from "fs/promises";
1744
+ import { join as join4 } from "path";
1745
+
1746
+ // server/utils/harness/local/local-state-lock.ts
1747
+ import { randomUUID } from "crypto";
1748
+ import { mkdir, readFile as readFile3, rm, stat as stat2, writeFile } from "fs/promises";
1749
+ import { join as join3 } from "path";
1750
+ var DEFAULT_STALE_MS = 3e4;
1751
+ var DEFAULT_POLL_MS = 25;
1752
+ var DEFAULT_TIMEOUT_MS = 45e3;
1753
+ async function readLockBody(file) {
1754
+ try {
1755
+ const parsed = JSON.parse(await readFile3(file, "utf8"));
1756
+ if (!parsed || typeof parsed !== "object") return null;
1757
+ const body = parsed;
1758
+ if (typeof body.pid !== "number" || !Number.isInteger(body.pid) || body.pid <= 0 || typeof body.nonce !== "string" || body.nonce.length === 0) {
1759
+ return null;
1760
+ }
1761
+ return { pid: body.pid, nonce: body.nonce, at: Number(body.at) || 0 };
1762
+ } catch {
1763
+ return null;
1764
+ }
1765
+ }
1766
+ function lockHolderProvablyGone(body) {
1767
+ if (body === null || body.pid === process.pid) return false;
1768
+ try {
1769
+ process.kill(body.pid, 0);
1770
+ return false;
1771
+ } catch (error) {
1772
+ return error.code === "ESRCH";
1773
+ }
1774
+ }
1775
+ function createLocalStateMutationLock(options) {
1776
+ let mutationChain = Promise.resolve();
1777
+ const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
1778
+ const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
1779
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1780
+ const acquire = async () => {
1781
+ const root = options.rootDir();
1782
+ const file = join3(root, options.lockFileName);
1783
+ await mkdir(root, { recursive: true, mode: 448 });
1784
+ const nonce = randomUUID();
1785
+ const deadline = Date.now() + timeoutMs;
1786
+ for (; ; ) {
1787
+ try {
1788
+ await writeFile(
1789
+ file,
1790
+ JSON.stringify({ pid: process.pid, nonce, at: Date.now() }),
1791
+ { flag: "wx", mode: 384 }
1792
+ );
1793
+ return async () => {
1794
+ const held = await readLockBody(file);
1795
+ if (held?.nonce !== nonce) {
1796
+ logger.warn(
1797
+ `[local-harness] ${options.resourceLabel} lock was taken over; not releasing`
1798
+ );
1799
+ return;
1800
+ }
1801
+ await rm(file, { force: true }).catch(() => {
1802
+ });
1803
+ };
1804
+ } catch (error) {
1805
+ if (error.code !== "EEXIST") throw error;
1806
+ const body = await readLockBody(file);
1807
+ const age = await stat2(file).then(
1808
+ (info) => Date.now() - info.mtimeMs,
1809
+ () => 0
1810
+ );
1811
+ const holderGone = lockHolderProvablyGone(body);
1812
+ const unattributable = body === null && age > staleMs;
1813
+ if (holderGone || unattributable) {
1814
+ logger.warn(
1815
+ `[local-harness] breaking an abandoned ${options.resourceLabel} lock`,
1816
+ { age, holderGone, unattributable }
1817
+ );
1818
+ await rm(file, { force: true }).catch(() => {
1819
+ });
1820
+ continue;
1821
+ }
1822
+ if (Date.now() > deadline) {
1823
+ throw new Error(
1824
+ `timed out waiting for the local harness ${options.resourceLabel} lock` + (body === null ? "" : ` (held by pid ${body.pid})`) + ". Its holder could not be proven gone, and taking it over would admit concurrent state writers."
1825
+ );
1826
+ }
1827
+ await new Promise(
1828
+ (resolvePromise) => setTimeout(resolvePromise, pollMs)
1829
+ );
1830
+ }
1831
+ }
1832
+ };
1833
+ return (operation) => {
1834
+ const guarded = async () => {
1835
+ const release = await acquire();
1836
+ try {
1837
+ return await operation();
1838
+ } finally {
1839
+ await release();
1840
+ }
1841
+ };
1842
+ const run = mutationChain.then(guarded, guarded);
1843
+ mutationChain = run.then(
1844
+ () => void 0,
1845
+ () => void 0
1846
+ );
1847
+ return run;
1848
+ };
1849
+ }
1850
+
1851
+ // server/utils/harness/local/runtime-lifecycle.ts
1852
+ var OPERATION_FILE = ".mcpjam-operation.json";
1853
+ var USE_DIR = ".mcpjam-inuse";
1854
+ var STAGING_PREFIX = ".mcpjam-tmp-";
1855
+ var STAGING_OWNER_FILE = ".mcpjam-staging-owner.json";
1856
+ function operationKeyString(key) {
1857
+ return [
1858
+ key.runtimeRoot,
1859
+ key.harnessId,
1860
+ key.target,
1861
+ key.packVersion,
1862
+ key.treeDigest
1863
+ ].join("\0");
1864
+ }
1865
+ function installRootOf(key) {
1866
+ return join4(key.runtimeRoot, key.target);
1867
+ }
1868
+ var locksByRoot = /* @__PURE__ */ new Map();
1869
+ function lockFor(key) {
1870
+ const root = installRootOf(key);
1871
+ let lock = locksByRoot.get(root);
1872
+ if (lock === void 0) {
1873
+ lock = createLocalStateMutationLock({
1874
+ rootDir: () => root,
1875
+ lockFileName: ".mcpjam-runtime-lifecycle.lock",
1876
+ resourceLabel: "runtime lifecycle",
1877
+ // Short by construction: every critical section here is a read, a
1878
+ // compare and an atomic write. A caller waiting longer than this is
1879
+ // waiting on something that is not doing that.
1880
+ timeoutMs: 15e3
1881
+ });
1882
+ locksByRoot.set(root, lock);
1883
+ }
1884
+ return lock;
1885
+ }
1886
+ function ownerProvablyGone(record) {
1887
+ if (record.ownerPid === process.pid) {
1888
+ return record.ownerStartedAt !== processStartedAt();
1889
+ }
1890
+ try {
1891
+ process.kill(record.ownerPid, 0);
1892
+ return false;
1893
+ } catch (error) {
1894
+ return error.code === "ESRCH";
1895
+ }
1896
+ }
1897
+ var cachedStartedAt = null;
1898
+ function processStartedAt() {
1899
+ if (cachedStartedAt === null) {
1900
+ cachedStartedAt = Math.floor((Date.now() - process.uptime() * 1e3) / 1e3);
1901
+ }
1902
+ return cachedStartedAt;
1903
+ }
1904
+ async function writeJsonAtomic(file, value) {
1905
+ const tmp = `${file}.${process.pid}.${randomUUID2()}.tmp`;
1906
+ await writeFile2(tmp, `${JSON.stringify(value, null, 2)}
1907
+ `, { mode: 384 });
1908
+ await rename(tmp, file);
1909
+ }
1910
+ async function readJson(file) {
1911
+ try {
1912
+ return JSON.parse(await readFile4(file, "utf8"));
1913
+ } catch {
1914
+ return null;
1915
+ }
1916
+ }
1917
+ async function readRuntimeOperation(key) {
1918
+ const file = join4(installRootOf(key), OPERATION_FILE);
1919
+ const record = await readJson(file);
1920
+ if (record === null) return null;
1921
+ if (record.packVersion !== key.packVersion || record.treeDigest !== key.treeDigest || record.harnessId !== key.harnessId) {
1922
+ return null;
1923
+ }
1924
+ const terminal = record.state === "ready" || record.state === "failed" || record.state === "interrupted";
1925
+ if (terminal || !ownerProvablyGone(record)) return record;
1926
+ return lockFor(key)(async () => {
1927
+ const fresh = await readJson(file);
1928
+ if (fresh === null) return null;
1929
+ if (fresh.attemptId !== record.attemptId) return fresh;
1930
+ if (fresh.state === "ready" || fresh.state === "failed" || fresh.state === "interrupted") {
1931
+ return fresh;
1932
+ }
1933
+ if (!ownerProvablyGone(fresh)) return fresh;
1934
+ const reconciled = {
1935
+ ...fresh,
1936
+ state: "interrupted",
1937
+ message: "the Inspector that started this install is no longer running, so the attempt was abandoned partway through",
1938
+ updatedAt: Date.now()
1939
+ };
1940
+ await writeJsonAtomic(file, reconciled);
1941
+ logger.info("[local-harness] recovered an interrupted install attempt", {
1942
+ attemptId: reconciled.attemptId,
1943
+ packVersion: reconciled.packVersion
1944
+ });
1945
+ return reconciled;
1946
+ });
1947
+ }
1948
+ async function beginInstallAttempt(key) {
1949
+ const root = installRootOf(key);
1950
+ await mkdir2(root, { recursive: true, mode: 448 });
1951
+ const file = join4(root, OPERATION_FILE);
1952
+ return lockFor(key)(async () => {
1953
+ const existing = await readJson(file);
1954
+ if (existing !== null && existing.packVersion === key.packVersion && existing.treeDigest === key.treeDigest && existing.harnessId === key.harnessId) {
1955
+ const live = existing.state !== "ready" && existing.state !== "failed" && existing.state !== "interrupted";
1956
+ if (live && !ownerProvablyGone(existing)) {
1957
+ return { kind: "joined", record: existing };
1958
+ }
1959
+ }
1960
+ const record = {
1961
+ attemptId: `att_${randomUUID2()}`,
1962
+ ownerPid: process.pid,
1963
+ ownerStartedAt: processStartedAt(),
1964
+ harnessId: key.harnessId,
1965
+ target: key.target,
1966
+ packVersion: key.packVersion,
1967
+ treeDigest: key.treeDigest,
1968
+ state: "reserved",
1969
+ startedAt: Date.now(),
1970
+ updatedAt: Date.now()
1971
+ };
1972
+ await writeJsonAtomic(file, record);
1973
+ return { kind: "started", record };
1974
+ });
1975
+ }
1976
+ async function updateInstallAttempt(key, attemptId, patch) {
1977
+ const file = join4(installRootOf(key), OPERATION_FILE);
1978
+ return lockFor(key)(async () => {
1979
+ const existing = await readJson(file);
1980
+ if (existing === null || existing.attemptId !== attemptId) return false;
1981
+ await writeJsonAtomic(file, {
1982
+ ...existing,
1983
+ ...patch,
1984
+ updatedAt: Date.now()
1985
+ });
1986
+ return true;
1987
+ });
1988
+ }
1989
+ async function attemptStillOwns(key, attemptId) {
1990
+ const record = await readJson(
1991
+ join4(installRootOf(key), OPERATION_FILE)
1992
+ );
1993
+ return record?.attemptId === attemptId;
1994
+ }
1995
+ async function reserveRuntimeUse(args) {
1996
+ const dir = join4(installRootOf(args.key), USE_DIR);
1997
+ await mkdir2(dir, { recursive: true, mode: 448 });
1998
+ const file = join4(dir, `${process.pid}-${randomUUID2()}.json`);
1999
+ const record = {
2000
+ pid: process.pid,
2001
+ startedAt: processStartedAt(),
2002
+ runtimeRoot: args.runtimeRoot,
2003
+ at: Date.now(),
2004
+ ...args.label ? { label: args.label } : {}
2005
+ };
2006
+ await writeJsonAtomic(file, record);
2007
+ let released = false;
2008
+ return {
2009
+ release: async () => {
2010
+ if (released) return;
2011
+ released = true;
2012
+ await rm2(file, { force: true }).catch(() => {
2013
+ });
2014
+ }
2015
+ };
2016
+ }
2017
+ async function runtimeUseState(args) {
2018
+ const dir = join4(installRootOf(args.key), USE_DIR);
2019
+ let entries;
2020
+ try {
2021
+ entries = await readdir3(dir);
2022
+ } catch {
2023
+ return { busy: false, holders: [] };
2024
+ }
2025
+ const holders = [];
2026
+ for (const entry of entries) {
2027
+ if (!entry.endsWith(".json")) continue;
2028
+ const file = join4(dir, entry);
2029
+ const record = await readJson(file);
2030
+ if (record === null) {
2031
+ continue;
2032
+ }
2033
+ if (record.runtimeRoot !== args.runtimeRoot) continue;
2034
+ if (ownerProvablyGone({ ownerPid: record.pid, ownerStartedAt: record.startedAt })) {
2035
+ await rm2(file, { force: true }).catch(() => {
2036
+ });
2037
+ continue;
2038
+ }
2039
+ holders.push(record.pid);
2040
+ }
2041
+ return { busy: holders.length > 0, holders };
2042
+ }
2043
+ async function claimStagingDirectory(args) {
2044
+ await writeJsonAtomic(join4(args.staging, STAGING_OWNER_FILE), {
2045
+ pid: process.pid,
2046
+ startedAt: processStartedAt(),
2047
+ attemptId: args.attemptId,
2048
+ at: Date.now()
2049
+ });
2050
+ }
2051
+ async function sweepAbandonedStaging(key) {
2052
+ const root = installRootOf(key);
2053
+ let entries;
2054
+ try {
2055
+ entries = await readdir3(root);
2056
+ } catch {
2057
+ return { removed: 0, left: 0 };
2058
+ }
2059
+ let removed = 0;
2060
+ let left = 0;
2061
+ for (const entry of entries) {
2062
+ if (!entry.startsWith(STAGING_PREFIX)) continue;
2063
+ const path = join4(root, entry);
2064
+ const owner = await readJson(join4(path, STAGING_OWNER_FILE));
2065
+ if (owner === null) {
2066
+ left += 1;
2067
+ continue;
2068
+ }
2069
+ if (!ownerProvablyGone({ ownerPid: owner.pid, ownerStartedAt: owner.startedAt })) {
2070
+ left += 1;
2071
+ continue;
2072
+ }
2073
+ await rm2(path, { recursive: true, force: true }).catch(() => {
2074
+ });
2075
+ removed += 1;
2076
+ }
2077
+ if (removed > 0 || left > 0) {
2078
+ logger.debug("[local-harness] staging sweep", { removed, left });
2079
+ }
2080
+ return { removed, left };
2081
+ }
2082
+ async function recoverInterruptedActivation(args) {
2083
+ const previous = `${args.versionRoot}${PREVIOUS_SUFFIX}`;
2084
+ const hasPrevious = await stat3(previous).then(
2085
+ (info) => info.isDirectory(),
2086
+ () => false
2087
+ );
2088
+ if (!hasPrevious) return false;
2089
+ return lockFor(args.key)(async () => {
2090
+ const current = await stat3(args.versionRoot).then(
2091
+ (info) => info.isDirectory(),
2092
+ () => false
2093
+ );
2094
+ if (current) {
2095
+ await rm2(previous, { recursive: true, force: true }).catch(() => {
2096
+ });
2097
+ return false;
2098
+ }
2099
+ await rename(previous, args.versionRoot);
2100
+ logger.warn("[local-harness] restored a runtime after an interrupted activation", {
2101
+ packVersion: args.key.packVersion
2102
+ });
2103
+ return true;
2104
+ });
2105
+ }
2106
+ var PREVIOUS_SUFFIX = ".mcpjam-previous";
2107
+ function withRuntimeLifecycleLock(key, operation) {
2108
+ return lockFor(key)(operation);
2109
+ }
2110
+
1409
2111
  // server/utils/harness/local/runtime-install.ts
1410
2112
  var MAX_ARCHIVE_BYTES = 1500 * 1024 * 1024;
1411
2113
  var MAX_EXTRACTED_BYTES = 2 * 1024 * 1024 * 1024;
@@ -1413,13 +2115,13 @@ var MAX_MANIFEST_BYTES = 1024 * 1024;
1413
2115
  function runtimeInstallRoot() {
1414
2116
  const override = process.env.MCPJAM_RUNTIME_ROOT;
1415
2117
  if (override && override.trim().length > 0) return override.trim();
1416
- return join2(homedir(), ".mcpjam", "harness-local", "runtime");
2118
+ return join5(homedir(), ".mcpjam", "harness-local", "runtime");
1417
2119
  }
1418
2120
  function packVersionRoot(packVersion, target = localPackTarget()) {
1419
- return join2(targetInstallRoot(target), packVersion);
2121
+ return join5(targetInstallRoot(target), packVersion);
1420
2122
  }
1421
2123
  function targetInstallRoot(target) {
1422
- return join2(runtimeInstallRoot(), target ?? packPlatformKey());
2124
+ return join5(runtimeInstallRoot(), target ?? packPlatformKey());
1423
2125
  }
1424
2126
  function expectedPackFor(harnessId, target) {
1425
2127
  const override = developmentPackExpectation();
@@ -1455,124 +2157,357 @@ function packPlatformKey(platform = process.platform, arch = process.arch) {
1455
2157
  return `${platform}-${arch}`;
1456
2158
  }
1457
2159
  async function readRuntimeInstallStatus(args) {
1458
- const platform = currentLocalPlatform(args.platform ?? process.platform);
1459
- if (platform === null) {
1460
- return {
1461
- state: "unsupported-platform",
1462
- message: `${args.platform ?? process.platform} has no local harness runtime`
1463
- };
2160
+ const resolved = resolveInstallTarget(args);
2161
+ if (!resolved.ok) return resolved.status;
2162
+ const { key, expected, versionRoot } = resolved;
2163
+ await recoverInterruptedActivation({ key, versionRoot });
2164
+ const inProcess = active.get(operationKeyString(key));
2165
+ if (inProcess !== void 0) return inProcess.status;
2166
+ const record = await readRuntimeOperation(key);
2167
+ if (record !== null && !isRecordTerminal(record)) {
2168
+ return operationRecordStatus(record);
2169
+ }
2170
+ const onDisk = await readMarkerStatus({
2171
+ harnessId: args.harnessId,
2172
+ versionRoot,
2173
+ expected
2174
+ });
2175
+ if (onDisk.state === "ready") return onDisk;
2176
+ if (record !== null && isRecordTerminal(record) && record.state !== "ready") {
2177
+ return operationRecordStatus(record);
1464
2178
  }
1465
- const target = localPackTarget(args.platform, args.arch);
1466
- const expected = target === null ? null : expectedPackFor(args.harnessId, target);
1467
- if (expected === null) {
1468
- return {
1469
- state: "unsupported-platform",
1470
- message: `no ${args.harnessId} runtime pack has been built for ${packPlatformKey(args.platform, args.arch)}`
1471
- };
2179
+ return onDisk;
2180
+ }
2181
+ function manifestWithExpectedBundleDigest(manifest, harnessId, target) {
2182
+ if (target === null) return manifest;
2183
+ if (manifest.runtime.source !== "managed-bundle") return manifest;
2184
+ const expected = expectedPackFor(harnessId, target);
2185
+ if (expected === null) return manifest;
2186
+ if (manifest.runtime.bundleDigest[target] === expected.treeDigest) {
2187
+ return manifest;
1472
2188
  }
1473
- const inFlight = active.get(expected.packVersion);
1474
- if (inFlight !== void 0) return inFlight.status;
1475
- const root = packVersionRoot(expected.packVersion, target);
2189
+ return {
2190
+ ...manifest,
2191
+ runtime: {
2192
+ ...manifest.runtime,
2193
+ bundleDigest: {
2194
+ ...manifest.runtime.bundleDigest,
2195
+ [target]: expected.treeDigest
2196
+ }
2197
+ }
2198
+ };
2199
+ }
2200
+ async function readVerifiedRuntimeStatus(args) {
2201
+ const status = await readRuntimeInstallStatus(args);
2202
+ if (status.state !== "ready") return status;
2203
+ const platform = currentLocalPlatform(args.platform ?? process.platform);
2204
+ if (platform === null) return status;
2205
+ const resolvedBundle = await resolveManagedBundle({
2206
+ manifest: manifestWithExpectedBundleDigest(
2207
+ LOCAL_HARNESS_MANIFEST[args.harnessId],
2208
+ args.harnessId,
2209
+ localPackTarget(args.platform, args.arch)
2210
+ ),
2211
+ runtimeRoot: status.runtimeRoot,
2212
+ platform,
2213
+ ...args.arch ? { arch: args.arch } : {}
2214
+ });
2215
+ if (resolvedBundle.ok) return status;
2216
+ return {
2217
+ state: "corrupt",
2218
+ packVersion: status.packVersion,
2219
+ message: resolvedBundle.message
2220
+ };
2221
+ }
2222
+ async function readMarkerStatus(args) {
1476
2223
  try {
1477
2224
  const marker = JSON.parse(
1478
- await readFile3(join2(root, INSTALL_MARKER), "utf8")
2225
+ await readFile5(join5(args.versionRoot, INSTALL_MARKER), "utf8")
1479
2226
  );
1480
- if (marker.treeDigest !== expected.treeDigest) {
2227
+ if (marker.treeDigest !== args.expected.treeDigest) {
1481
2228
  return {
1482
2229
  state: "corrupt",
1483
- packVersion: expected.packVersion,
2230
+ packVersion: args.expected.packVersion,
1484
2231
  message: "the installed runtime pack does not match the digest this Inspector expects; reinstall it"
1485
2232
  };
1486
2233
  }
1487
- await stat2(join2(root, args.harnessId));
2234
+ await stat4(join5(args.versionRoot, args.harnessId));
1488
2235
  return {
1489
2236
  state: "ready",
1490
- packVersion: expected.packVersion,
1491
- runtimeRoot: root,
1492
- digest: expected.treeDigest
2237
+ packVersion: args.expected.packVersion,
2238
+ runtimeRoot: args.versionRoot,
2239
+ digest: args.expected.treeDigest
1493
2240
  };
1494
2241
  } catch {
1495
- return { state: "absent", packVersion: expected.packVersion };
2242
+ return { state: "absent", packVersion: args.expected.packVersion };
2243
+ }
2244
+ }
2245
+ function isRecordTerminal(record) {
2246
+ return record.state === "ready" || record.state === "failed" || record.state === "interrupted";
2247
+ }
2248
+ function operationRecordStatus(record) {
2249
+ switch (record.state) {
2250
+ case "reserved":
2251
+ case "downloading":
2252
+ return {
2253
+ state: "downloading",
2254
+ packVersion: record.packVersion,
2255
+ percent: record.percent ?? 0,
2256
+ attemptId: record.attemptId
2257
+ };
2258
+ case "verifying":
2259
+ case "activating":
2260
+ return {
2261
+ state: "verifying",
2262
+ packVersion: record.packVersion,
2263
+ attemptId: record.attemptId
2264
+ };
2265
+ case "failed":
2266
+ return {
2267
+ state: "failed",
2268
+ packVersion: record.packVersion,
2269
+ reason: record.reason ?? "unknown",
2270
+ message: record.message ?? "the install did not complete",
2271
+ attemptId: record.attemptId
2272
+ };
2273
+ case "interrupted":
2274
+ return {
2275
+ state: "interrupted",
2276
+ packVersion: record.packVersion,
2277
+ message: record.message ?? "setup was interrupted before it finished",
2278
+ attemptId: record.attemptId
2279
+ };
2280
+ case "ready":
2281
+ return {
2282
+ state: "ready",
2283
+ packVersion: record.packVersion,
2284
+ runtimeRoot: packVersionRoot(record.packVersion, record.target),
2285
+ digest: record.treeDigest
2286
+ };
2287
+ }
2288
+ }
2289
+ function resolveInstallTarget(args) {
2290
+ const platform = currentLocalPlatform(args.platform ?? process.platform);
2291
+ if (platform === null) {
2292
+ return {
2293
+ ok: false,
2294
+ status: {
2295
+ state: "unsupported-platform",
2296
+ message: `${args.platform ?? process.platform} has no local harness runtime`
2297
+ }
2298
+ };
2299
+ }
2300
+ const target = localPackTarget(args.platform, args.arch);
2301
+ const expected = target === null ? null : expectedPackFor(args.harnessId, target);
2302
+ if (expected === null || target === null) {
2303
+ return {
2304
+ ok: false,
2305
+ status: {
2306
+ state: "unsupported-platform",
2307
+ message: `no ${args.harnessId} runtime pack has been built for ${packPlatformKey(args.platform, args.arch)}`
2308
+ }
2309
+ };
1496
2310
  }
2311
+ return {
2312
+ ok: true,
2313
+ platform,
2314
+ target,
2315
+ expected,
2316
+ versionRoot: packVersionRoot(expected.packVersion, target),
2317
+ key: {
2318
+ runtimeRoot: runtimeInstallRoot(),
2319
+ harnessId: args.harnessId,
2320
+ target,
2321
+ packVersion: expected.packVersion,
2322
+ treeDigest: expected.treeDigest
2323
+ }
2324
+ };
1497
2325
  }
1498
2326
  var INSTALL_MARKER = ".mcpjam-pack-installed.json";
1499
2327
  var active = /* @__PURE__ */ new Map();
1500
- async function installRuntimePack(options) {
1501
- const platform = currentLocalPlatform(options.platform ?? process.platform);
1502
- if (platform === null) {
2328
+ async function startRuntimeInstall(options) {
2329
+ const resolved = resolveInstallTarget(options);
2330
+ if (!resolved.ok) {
1503
2331
  return {
1504
- state: "unsupported-platform",
1505
- message: `${options.platform ?? process.platform} has no local harness runtime`
2332
+ kind: "refused",
2333
+ status: resolved.status,
2334
+ reason: resolved.status.state === "unsupported-platform" ? resolved.status.message : "this machine has no runtime pack to install"
1506
2335
  };
1507
2336
  }
1508
- const target = localPackTarget(options.platform, options.arch);
1509
- const expected = target === null ? null : expectedPackFor(options.harnessId, target);
1510
- if (expected === null) {
2337
+ const { key, expected, versionRoot } = resolved;
2338
+ if (options.expectedPack !== void 0 && (options.expectedPack.packVersion !== expected.packVersion || options.expectedPack.treeDigest !== expected.treeDigest)) {
1511
2339
  return {
1512
- state: "unsupported-platform",
1513
- message: `no ${options.harnessId} runtime pack has been built for ${packPlatformKey(options.platform, options.arch)}`
2340
+ kind: "refused",
2341
+ reason: "this Inspector now expects a different runtime than the one that was approved, so it will not download it under that approval",
2342
+ status: {
2343
+ state: "failed",
2344
+ packVersion: expected.packVersion,
2345
+ reason: "verification",
2346
+ message: `the approved runtime was ${options.expectedPack.packVersion} (${options.expectedPack.treeDigest.slice(0, 19)}\u2026) but this Inspector now expects ${expected.packVersion} (${expected.treeDigest.slice(0, 19)}\u2026). Authorize the new one.`
2347
+ }
2348
+ };
2349
+ }
2350
+ const keyString = operationKeyString(key);
2351
+ const running = active.get(keyString);
2352
+ if (running !== void 0) {
2353
+ return { kind: "joined", status: running.status };
2354
+ }
2355
+ await recoverInterruptedActivation({ key, versionRoot });
2356
+ const verified = await readVerifiedRuntimeStatus(options);
2357
+ if (verified.state === "ready") return { kind: "ready", status: verified };
2358
+ const attempt = await beginInstallAttempt(key);
2359
+ if (attempt.kind === "joined") {
2360
+ return {
2361
+ kind: "joined",
2362
+ status: operationRecordStatus(attempt.record),
2363
+ attemptId: attempt.record.attemptId
1514
2364
  };
1515
2365
  }
1516
- const existing = active.get(expected.packVersion);
1517
- if (existing !== void 0) return existing.promise;
1518
2366
  const record = {
1519
- status: { state: "downloading", packVersion: expected.packVersion, percent: 0 },
2367
+ status: {
2368
+ state: "downloading",
2369
+ packVersion: expected.packVersion,
2370
+ percent: 0,
2371
+ attemptId: attempt.record.attemptId
2372
+ },
1520
2373
  promise: Promise.resolve({
1521
2374
  state: "absent",
1522
2375
  packVersion: expected.packVersion
1523
2376
  })
1524
2377
  };
1525
- active.set(expected.packVersion, record);
2378
+ active.set(keyString, record);
2379
+ record.promise = runInstallAttempt({
2380
+ options,
2381
+ resolved,
2382
+ attemptId: attempt.record.attemptId,
2383
+ record,
2384
+ keyString
2385
+ });
2386
+ void record.promise;
2387
+ return {
2388
+ kind: "started",
2389
+ status: record.status,
2390
+ attemptId: attempt.record.attemptId
2391
+ };
2392
+ }
2393
+ async function installRuntimePack(options) {
2394
+ const started = await startRuntimeInstall(options);
2395
+ if (started.kind === "ready" || started.kind === "refused") {
2396
+ return started.status;
2397
+ }
2398
+ const resolved = resolveInstallTarget(options);
2399
+ if (!resolved.ok) return resolved.status;
2400
+ const running = active.get(operationKeyString(resolved.key));
2401
+ if (running !== void 0) return running.promise;
2402
+ return awaitForeignAttempt(resolved.key, options);
2403
+ }
2404
+ async function awaitForeignAttempt(key, options) {
2405
+ for (; ; ) {
2406
+ if (options.signal?.aborted === true) {
2407
+ return readRuntimeInstallStatus(options);
2408
+ }
2409
+ const record = await readRuntimeOperation(key);
2410
+ if (record === null) return readRuntimeInstallStatus(options);
2411
+ if (isRecordTerminal(record)) {
2412
+ return record.state === "ready" ? readRuntimeInstallStatus(options) : operationRecordStatus(record);
2413
+ }
2414
+ options.onProgress?.(operationRecordStatus(record));
2415
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 500));
2416
+ }
2417
+ }
2418
+ async function runInstallAttempt(args) {
2419
+ const { options, resolved, attemptId, record, keyString } = args;
2420
+ const { key, expected } = resolved;
1526
2421
  const setStatus = (status) => {
1527
2422
  record.status = status;
1528
2423
  options.onProgress?.(status);
2424
+ void updateInstallAttempt(key, attemptId, {
2425
+ state: status.state === "downloading" ? "downloading" : status.state === "verifying" ? "verifying" : "reserved",
2426
+ ...status.state === "downloading" ? { percent: status.percent } : {}
2427
+ }).catch(() => {
2428
+ });
1529
2429
  };
1530
- record.promise = (async () => {
1531
- try {
1532
- return await performInstall({
1533
- ...options,
1534
- platform,
1535
- expected,
1536
- setStatus
1537
- });
1538
- } catch (error) {
1539
- const message = error instanceof Error ? error.message : String(error);
1540
- logger.warn("[local-harness] runtime pack install failed", { message });
1541
- return {
1542
- state: "corrupt",
1543
- packVersion: expected.packVersion,
1544
- message
1545
- };
1546
- } finally {
1547
- active.delete(expected.packVersion);
1548
- }
1549
- })();
1550
- return record.promise;
2430
+ try {
2431
+ const result = await performInstall({
2432
+ ...options,
2433
+ platform: resolved.platform,
2434
+ expected,
2435
+ key,
2436
+ attemptId,
2437
+ setStatus
2438
+ });
2439
+ record.status = result;
2440
+ await updateInstallAttempt(key, attemptId, {
2441
+ state: result.state === "ready" ? "ready" : "failed",
2442
+ ...result.state === "failed" ? { reason: result.reason, message: result.message } : {}
2443
+ }).catch(() => {
2444
+ });
2445
+ return result;
2446
+ } catch (error) {
2447
+ const failure = classifyInstallFailure(error, expected.packVersion);
2448
+ logger.warn("[local-harness] runtime pack install failed", {
2449
+ reason: failure.reason,
2450
+ message: failure.message
2451
+ });
2452
+ record.status = failure;
2453
+ await updateInstallAttempt(key, attemptId, {
2454
+ state: "failed",
2455
+ reason: failure.reason,
2456
+ message: failure.message
2457
+ }).catch(() => {
2458
+ });
2459
+ return failure;
2460
+ } finally {
2461
+ active.delete(keyString);
2462
+ }
2463
+ }
2464
+ function classifyInstallFailure(error, packVersion) {
2465
+ const message = error instanceof Error ? error.message : String(error);
2466
+ const code = error?.code;
2467
+ const causeCode = error?.cause?.code;
2468
+ const reason = code === "ENOSPC" || code === "EACCES" || code === "EPERM" || code === "EROFS" || code === "EDQUOT" ? "disk" : code === "ENOTFOUND" || code === "ECONNRESET" || code === "ETIMEDOUT" || code === "ECONNREFUSED" || causeCode === "ENOTFOUND" || causeCode === "ECONNRESET" || causeCode === "ETIMEDOUT" || causeCode === "ECONNREFUSED" || /could not be downloaded|responded \d{3}|fetch failed|network/i.test(
2469
+ message
2470
+ ) ? "network" : /does not match|signature|signed manifest|no signed manifest|digest|exceeds its size ceiling|has no .* directory at its root/i.test(
2471
+ message
2472
+ ) ? "verification" : "unknown";
2473
+ return { state: "failed", packVersion, reason, message };
1551
2474
  }
1552
2475
  async function performInstall(args) {
1553
- const { expected, setStatus } = args;
2476
+ const { expected, setStatus, key, attemptId } = args;
1554
2477
  const platformKey = packPlatformKey(args.platform, args.arch);
1555
2478
  const target = localPackTarget(args.platform, args.arch);
1556
2479
  const versionRoot = packVersionRoot(expected.packVersion, target);
1557
2480
  const installRoot = targetInstallRoot(target);
1558
- await mkdir(installRoot, { recursive: true, mode: 448 });
1559
- const staging = join2(installRoot, `.mcpjam-tmp-${randomUUID()}`);
1560
- await mkdir(staging, { recursive: true, mode: 448 });
2481
+ await mkdir3(installRoot, { recursive: true, mode: 448 });
2482
+ await sweepAbandonedStaging(key);
2483
+ const staging = join5(installRoot, `${STAGING_PREFIX}${randomUUID3()}`);
2484
+ await mkdir3(staging, { recursive: true, mode: 448 });
2485
+ await claimStagingDirectory({ staging, attemptId });
1561
2486
  try {
1562
2487
  const source = packSourceFor(expected.packVersion, platformKey);
1563
- const archivePath = join2(staging, "pack.tar.gz");
1564
- setStatus({ state: "downloading", packVersion: expected.packVersion, percent: 0 });
2488
+ const archivePath = join5(staging, "pack.tar.gz");
2489
+ setStatus({
2490
+ state: "downloading",
2491
+ packVersion: expected.packVersion,
2492
+ percent: 0,
2493
+ attemptId
2494
+ });
1565
2495
  const archiveSha = await fetchArchive({
1566
2496
  source,
1567
2497
  destination: archivePath,
1568
2498
  onPercent: (percent) => setStatus({
1569
2499
  state: "downloading",
1570
2500
  packVersion: expected.packVersion,
1571
- percent
2501
+ percent,
2502
+ attemptId
1572
2503
  }),
1573
2504
  ...args.signal ? { signal: args.signal } : {}
1574
2505
  });
1575
- setStatus({ state: "verifying", packVersion: expected.packVersion });
2506
+ setStatus({
2507
+ state: "verifying",
2508
+ packVersion: expected.packVersion,
2509
+ attemptId
2510
+ });
1576
2511
  const manifest = await loadAndVerifyManifest({
1577
2512
  source,
1578
2513
  packVersion: expected.packVersion,
@@ -1586,8 +2521,8 @@ async function performInstall(args) {
1586
2521
  );
1587
2522
  }
1588
2523
  }
1589
- const extractRoot = join2(staging, "extracted");
1590
- await mkdir(extractRoot, { recursive: true, mode: 448 });
2524
+ const extractRoot = join5(staging, "extracted");
2525
+ await mkdir3(extractRoot, { recursive: true, mode: 448 });
1591
2526
  await extractTar({
1592
2527
  file: archivePath,
1593
2528
  cwd: extractRoot,
@@ -1602,8 +2537,8 @@ async function performInstall(args) {
1602
2537
  preservePaths: false,
1603
2538
  strict: true
1604
2539
  });
1605
- const packRoot = join2(extractRoot, args.harnessId);
1606
- const info = await stat2(packRoot).catch(() => null);
2540
+ const packRoot = join5(extractRoot, args.harnessId);
2541
+ const info = await stat4(packRoot).catch(() => null);
1607
2542
  if (info === null || !info.isDirectory()) {
1608
2543
  throw new Error(
1609
2544
  `the runtime pack archive has no ${args.harnessId} directory at its root`
@@ -1622,8 +2557,8 @@ async function performInstall(args) {
1622
2557
  );
1623
2558
  }
1624
2559
  if (args.platform === "darwin") await clearQuarantine(extractRoot);
1625
- await writeFile(
1626
- join2(extractRoot, INSTALL_MARKER),
2560
+ await writeFile3(
2561
+ join5(extractRoot, INSTALL_MARKER),
1627
2562
  `${JSON.stringify(
1628
2563
  {
1629
2564
  packVersion: expected.packVersion,
@@ -1638,10 +2573,18 @@ async function performInstall(args) {
1638
2573
  `,
1639
2574
  { mode: 384 }
1640
2575
  );
1641
- await rm(versionRoot, { recursive: true, force: true });
1642
- await rename(extractRoot, versionRoot);
1643
- clearRuntimeVerificationCache();
1644
- await sweepOtherVersions(installRoot, expected.packVersion);
2576
+ setStatus({
2577
+ state: "verifying",
2578
+ packVersion: expected.packVersion,
2579
+ attemptId
2580
+ });
2581
+ await updateInstallAttempt(key, attemptId, { state: "activating" });
2582
+ await activateVerifiedPack({
2583
+ key,
2584
+ attemptId,
2585
+ versionRoot,
2586
+ extractRoot
2587
+ });
1645
2588
  logger.info("[local-harness] runtime pack installed", {
1646
2589
  packVersion: expected.packVersion,
1647
2590
  platform: platformKey
@@ -1653,20 +2596,60 @@ async function performInstall(args) {
1653
2596
  digest
1654
2597
  };
1655
2598
  } finally {
1656
- await rm(staging, { recursive: true, force: true }).catch(() => {
2599
+ await rm3(staging, { recursive: true, force: true }).catch(() => {
1657
2600
  });
1658
2601
  }
1659
2602
  }
2603
+ async function activateVerifiedPack(args) {
2604
+ const { key, versionRoot, extractRoot } = args;
2605
+ if (!await attemptStillOwns(key, args.attemptId)) {
2606
+ throw new Error(
2607
+ "another install took over this runtime while this one was downloading, so it was not activated"
2608
+ );
2609
+ }
2610
+ const inUse = await runtimeUseState({ key, runtimeRoot: versionRoot });
2611
+ if (inUse.busy) {
2612
+ throw new Error(
2613
+ `this runtime is in use by ${inUse.holders.length} running session(s) on this machine, so it was not replaced. Stop them and retry.`
2614
+ );
2615
+ }
2616
+ await withRuntimeLifecycleLock(key, async () => {
2617
+ const stillFree = await runtimeUseState({ key, runtimeRoot: versionRoot });
2618
+ if (stillFree.busy) {
2619
+ throw new Error(
2620
+ "a session started using this runtime while it was being replaced, so it was left alone. Stop it and retry."
2621
+ );
2622
+ }
2623
+ const previous = `${versionRoot}${PREVIOUS_SUFFIX}`;
2624
+ await rm3(previous, { recursive: true, force: true }).catch(() => {
2625
+ });
2626
+ const hasCurrent = await stat4(versionRoot).then(
2627
+ () => true,
2628
+ () => false
2629
+ );
2630
+ if (hasCurrent) await rename2(versionRoot, previous);
2631
+ try {
2632
+ await rename2(extractRoot, versionRoot);
2633
+ } catch (error) {
2634
+ if (hasCurrent) await rename2(previous, versionRoot).catch(() => {
2635
+ });
2636
+ throw error;
2637
+ }
2638
+ await rm3(previous, { recursive: true, force: true }).catch(() => {
2639
+ });
2640
+ });
2641
+ clearRuntimeVerificationCache();
2642
+ }
1660
2643
  async function assertExtractedSize(root) {
1661
2644
  let bytes = 0;
1662
2645
  const walk = async (dir) => {
1663
- for (const entry of await readdir3(dir, { withFileTypes: true })) {
1664
- const full = join2(dir, entry.name);
2646
+ for (const entry of await readdir4(dir, { withFileTypes: true })) {
2647
+ const full = join5(dir, entry.name);
1665
2648
  if (entry.isDirectory()) {
1666
2649
  await walk(full);
1667
2650
  continue;
1668
2651
  }
1669
- bytes += (await stat2(full)).size;
2652
+ bytes += (await stat4(full)).size;
1670
2653
  if (bytes > MAX_EXTRACTED_BYTES) {
1671
2654
  throw new Error("the extracted runtime pack exceeds its size ceiling");
1672
2655
  }
@@ -1674,31 +2657,6 @@ async function assertExtractedSize(root) {
1674
2657
  };
1675
2658
  await walk(root);
1676
2659
  }
1677
- async function sweepOtherVersions(installRoot, keepVersion) {
1678
- let entries;
1679
- try {
1680
- entries = await readdir3(installRoot);
1681
- } catch {
1682
- return;
1683
- }
1684
- for (const entry of entries) {
1685
- if (entry === keepVersion) continue;
1686
- const path = join2(installRoot, entry);
1687
- if (entry.startsWith(".mcpjam-tmp-")) {
1688
- await rm(path, { recursive: true, force: true }).catch(() => {
1689
- });
1690
- continue;
1691
- }
1692
- if (!/^[\w.-]+$/.test(entry)) continue;
1693
- const owned = await readFile3(join2(path, INSTALL_MARKER), "utf8").then(
1694
- () => true,
1695
- () => false
1696
- );
1697
- if (!owned) continue;
1698
- await rm(path, { recursive: true, force: true }).catch(() => {
1699
- });
1700
- }
1701
- }
1702
2660
  async function clearQuarantine(root) {
1703
2661
  const { execFile: execFile3 } = await import("child_process");
1704
2662
  await new Promise((resolvePromise) => {
@@ -1719,8 +2677,8 @@ async function loadAndVerifyManifest(args) {
1719
2677
  try {
1720
2678
  if (args.source.kind === "file") {
1721
2679
  const dir = args.source.location.endsWith(".tar.gz") ? args.source.location.slice(0, -basename(args.source.location).length) : args.source.location;
1722
- manifestBytes = await readFile3(join2(dir, manifestName));
1723
- signature = await readFile3(join2(dir, signatureName), "utf8");
2680
+ manifestBytes = await readFile5(join5(dir, manifestName));
2681
+ signature = await readFile5(join5(dir, signatureName), "utf8");
1724
2682
  } else {
1725
2683
  const base = args.source.location.slice(
1726
2684
  0,
@@ -1796,7 +2754,7 @@ async function fetchArchive(args) {
1796
2754
  });
1797
2755
  if (args.source.kind === "file") {
1798
2756
  const path = args.source.location;
1799
- const info = await stat2(path);
2757
+ const info = await stat4(path);
1800
2758
  total = info.size;
1801
2759
  if (total > MAX_ARCHIVE_BYTES) {
1802
2760
  throw new Error("the runtime pack archive exceeds its size ceiling");
@@ -1868,7 +2826,7 @@ export {
1868
2826
  isOriginCaptureHandled,
1869
2827
  captureOriginErrorToSentry,
1870
2828
  logger,
1871
- PACK_TREE_DIGESTS,
2829
+ localPackTarget,
1872
2830
  LOCAL_HARNESS_POLICY_VERSION,
1873
2831
  LOCAL_ISOLATION_POLICY_VERSION,
1874
2832
  currentLocalPlatform,
@@ -1883,9 +2841,18 @@ export {
1883
2841
  resolveManagedBundle,
1884
2842
  resolveSystemInstall,
1885
2843
  revalidateRuntime,
2844
+ LOCAL_HARNESS_MANIFEST,
2845
+ localPermissionModeFor,
2846
+ resolveLocalCompatibility,
2847
+ createLocalStateMutationLock,
2848
+ reserveRuntimeUse,
1886
2849
  runtimeInstallRoot,
2850
+ expectedPackFor,
1887
2851
  readRuntimeInstallStatus,
2852
+ manifestWithExpectedBundleDigest,
2853
+ readVerifiedRuntimeStatus,
2854
+ startRuntimeInstall,
1888
2855
  installRuntimePack,
1889
2856
  reportLocalHarnessRuntimeStatusInBackground
1890
2857
  };
1891
- //# sourceMappingURL=chunk-GHVMCYUP.js.map
2858
+ //# sourceMappingURL=chunk-BLGJ4PIK.js.map