@mcpjam/inspector 3.3.4 → 3.3.6

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 (30) hide show
  1. package/bin/start.js +96 -2
  2. package/dist/client/amazon_logo.svg +1 -0
  3. package/dist/client/arcee_logo.svg +1 -0
  4. package/dist/client/assets/{dead-clicks-autocapture-BN_CNzfJ.js → dead-clicks-autocapture-BMgZJ0Wv.js} +2 -2
  5. package/dist/client/assets/{exception-autocapture-UNbdAMIN.js → exception-autocapture-HqVjr75I.js} +2 -2
  6. package/dist/client/assets/{highlighted-body-OFNGDK62-BeJGRkh1.js → highlighted-body-OFNGDK62-CZgdeyBU.js} +2 -2
  7. package/dist/client/assets/index-BArhVpAv.css +1 -0
  8. package/dist/client/assets/{index-BCYq7eSL.js → index-CPz3J6s3.js} +453 -442
  9. package/dist/client/assets/{jszip.min-Dby9iLAe.js → jszip.min-DzIFtwMm.js} +2 -2
  10. package/dist/client/assets/{posthog-recorder-Dxir8QCJ.js → posthog-recorder-BeJOU0JC.js} +2 -2
  11. package/dist/client/assets/running-swarm-Bu13T5aG.png +0 -0
  12. package/dist/client/assets/{surveys-C-oT4BI3.js → surveys-BZ6tGdti.js} +2 -2
  13. package/dist/client/assets/{web-vitals-CJvzm1wu.js → web-vitals--9YBaAs8.js} +2 -2
  14. package/dist/client/bytedance_logo.svg +1 -0
  15. package/dist/client/cohere_logo.svg +1 -0
  16. package/dist/client/guest-credit-wall.png +0 -0
  17. package/dist/client/hunyuan_logo.svg +1 -0
  18. package/dist/client/inception_dark.svg +1 -0
  19. package/dist/client/inception_light.svg +1 -0
  20. package/dist/client/index.html +2 -2
  21. package/dist/client/kwaipilot_logo.svg +1 -0
  22. package/dist/client/morph_logo.svg +1 -0
  23. package/dist/client/nvidia_logo.svg +1 -0
  24. package/dist/client/poolside_logo.svg +1 -0
  25. package/dist/client/stepfun_logo.svg +1 -0
  26. package/dist/server/chunk-7TOT3ZWN.js +1902 -0
  27. package/dist/server/harness-install-cli.js +20 -0
  28. package/dist/server/index.js +24058 -11854
  29. package/package.json +8 -6
  30. package/dist/client/assets/index-BQF5QMTk.css +0 -1
@@ -0,0 +1,1902 @@
1
+ // server/utils/logger.ts
2
+ import * as Sentry from "@sentry/node";
3
+ import { Axiom } from "@axiomhq/js";
4
+
5
+ // server/utils/log-events.ts
6
+ var warnedMissingEnv = false;
7
+ var ALLOWED_ENVIRONMENTS = [
8
+ "prod",
9
+ "staging",
10
+ "preview",
11
+ "dev",
12
+ "local",
13
+ "test"
14
+ ];
15
+ function resolveEnvironment() {
16
+ const fromEnv = process.env.ENVIRONMENT;
17
+ if (fromEnv && ALLOWED_ENVIRONMENTS.includes(fromEnv)) {
18
+ return fromEnv;
19
+ }
20
+ if (process.env.NODE_ENV === "test") return "test";
21
+ if (process.env.NODE_ENV === "production") {
22
+ if (!warnedMissingEnv) {
23
+ warnedMissingEnv = true;
24
+ process.stderr.write(
25
+ "[logging] ENVIRONMENT not set in production; defaulting to 'prod'\n"
26
+ );
27
+ }
28
+ return "prod";
29
+ }
30
+ return "dev";
31
+ }
32
+ var BAKED_VERSION = "3.3.6";
33
+ function blankToNull(value) {
34
+ return value === void 0 || value.trim() === "" ? null : value;
35
+ }
36
+ function resolveAppVersion() {
37
+ return blankToNull(BAKED_VERSION) ?? blankToNull(process.env.npm_package_version);
38
+ }
39
+ function resolveRelease() {
40
+ return blankToNull(process.env.RAILWAY_GIT_COMMIT_SHA) ?? blankToNull(process.env.GIT_SHA) ?? resolveAppVersion();
41
+ }
42
+
43
+ // server/utils/log-scrubber.ts
44
+ var FORBIDDEN_KEY_SUBSTRINGS = [
45
+ "authorization",
46
+ "cookie",
47
+ "password",
48
+ "token",
49
+ "secret",
50
+ "apikey",
51
+ "pkceverifier",
52
+ "pkcechallenge",
53
+ "stripecustomer",
54
+ "stripesubscription",
55
+ "stripeprice",
56
+ "x-mcp-session-auth",
57
+ "x-api-key",
58
+ // Header-broker model lease (E2B network-transform delivery). A signed model
59
+ // lease — never log it, even though the inspector never handles it directly
60
+ // (defense in depth).
61
+ "x-mcpjam-harness-lease"
62
+ ];
63
+ var ALLOWLISTED_KEYS = /* @__PURE__ */ new Set(["emaildomain"]);
64
+ var AUTH_HEADER_LIKE = /\b(authorization["']?\s*:\s*)["']?[^\n\r"'`]+/gi;
65
+ var TOKEN_LIKE = /\bBearer\s+[A-Za-z0-9._\-+/=]+\b/gi;
66
+ var EMAIL_LIKE = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
67
+ var SK_KEY_LIKE = /\bsk-[A-Za-z0-9]{16,}\b/g;
68
+ var JWT_LIKE = /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g;
69
+ var SECRET_PARAM_LIKE = /\b((?:api[_-]?key|apikey|access[_-]?token|refresh[_-]?token|id[_-]?token|client[_-]?secret|authorization|secret|password|passwd|pwd|token|auth|key|sig|signature)\s*[=:]\s*["']?)[^&\s"'`]+/gi;
70
+ var URL_BASIC_AUTH_LIKE = /(\/\/[^\s/:@]+:)[^\s@/]+@/g;
71
+ function isForbiddenKey(key) {
72
+ const lower = key.toLowerCase();
73
+ if (ALLOWLISTED_KEYS.has(lower)) return false;
74
+ if (lower === "email" || lower.endsWith("email")) return true;
75
+ return FORBIDDEN_KEY_SUBSTRINGS.some((s) => lower.includes(s));
76
+ }
77
+ function scrubString(s) {
78
+ return s.replace(AUTH_HEADER_LIKE, "$1[redacted]").replace(TOKEN_LIKE, "Bearer [redacted-token]").replace(JWT_LIKE, "[redacted-jwt]").replace(URL_BASIC_AUTH_LIKE, "$1[redacted]@").replace(EMAIL_LIKE, "[redacted-email]").replace(SK_KEY_LIKE, "[redacted-secret]").replace(SECRET_PARAM_LIKE, "$1[redacted]");
79
+ }
80
+ function scrubLogPayload(value) {
81
+ return scrubValue(value, /* @__PURE__ */ new WeakSet());
82
+ }
83
+ function scrubValue(value, seen) {
84
+ if (value === null || value === void 0) return value;
85
+ if (typeof value === "string") return scrubString(value);
86
+ if (typeof value !== "object") return value;
87
+ if (value instanceof Date) return value.toISOString();
88
+ if (value instanceof Error) {
89
+ return {
90
+ name: value.name,
91
+ message: scrubString(value.message),
92
+ stack: value.stack ? scrubString(value.stack) : void 0
93
+ };
94
+ }
95
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
96
+ return "[buffer]";
97
+ }
98
+ if (seen.has(value)) return "[circular]";
99
+ seen.add(value);
100
+ if (Array.isArray(value)) return value.map((v) => scrubValue(v, seen));
101
+ const out = {};
102
+ for (const [k, v] of Object.entries(value)) {
103
+ if (isForbiddenKey(k)) {
104
+ out[k] = "[redacted]";
105
+ continue;
106
+ }
107
+ out[k] = scrubValue(v, seen);
108
+ }
109
+ return out;
110
+ }
111
+
112
+ // server/utils/error-capture-stamp.ts
113
+ var CAPTURE_STAMP = Symbol.for("mcpjam.errorOriginCaptureHandled");
114
+ function isStampable(value) {
115
+ return typeof value === "object" && value !== null;
116
+ }
117
+ function safeErrorMessage(value) {
118
+ try {
119
+ return value instanceof Error ? value.message : String(value);
120
+ } catch {
121
+ return "[unreadable error value]";
122
+ }
123
+ }
124
+ function markOriginCaptureHandled(value) {
125
+ if (!isStampable(value)) return;
126
+ try {
127
+ Object.defineProperty(value, CAPTURE_STAMP, {
128
+ value: true,
129
+ enumerable: false,
130
+ configurable: true,
131
+ writable: true
132
+ });
133
+ } catch {
134
+ }
135
+ }
136
+ function ensureStampable(value) {
137
+ if (!isStampable(value)) return new Error(String(value));
138
+ let extensible;
139
+ try {
140
+ extensible = Object.isExtensible(value);
141
+ } catch {
142
+ extensible = false;
143
+ }
144
+ if (!extensible) {
145
+ const wrapped = new Error(
146
+ safeErrorMessage(value)
147
+ );
148
+ Object.defineProperty(wrapped, "cause", {
149
+ value,
150
+ enumerable: false,
151
+ configurable: true,
152
+ writable: true
153
+ });
154
+ return wrapped;
155
+ }
156
+ return value;
157
+ }
158
+ var MAX_CAUSE_DEPTH = 8;
159
+ function isOriginCaptureHandled(error) {
160
+ const seen = /* @__PURE__ */ new Set();
161
+ let current = error;
162
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH; depth += 1) {
163
+ if (!isStampable(current) || seen.has(current)) return false;
164
+ seen.add(current);
165
+ try {
166
+ if (current[CAPTURE_STAMP] === true) return true;
167
+ const normalized = current.normalized;
168
+ if (isStampable(normalized) && normalized[CAPTURE_STAMP] === true) {
169
+ return true;
170
+ }
171
+ current = current.cause;
172
+ } catch {
173
+ return false;
174
+ }
175
+ }
176
+ return false;
177
+ }
178
+
179
+ // server/utils/logger.ts
180
+ var isVerbose = () => process.env.VERBOSE_LOGS === "true";
181
+ var isDev = () => process.env.NODE_ENV !== "production";
182
+ var shouldLog = () => isVerbose() || isDev();
183
+ function isTrackingDisabled() {
184
+ const dnt = process.env.DO_NOT_TRACK;
185
+ return dnt === "1" || dnt === "true";
186
+ }
187
+ var axiomClient;
188
+ function getAxiom() {
189
+ if (isTrackingDisabled()) return null;
190
+ if (axiomClient === void 0) {
191
+ axiomClient = process.env.AXIOM_TOKEN && process.env.AXIOM_DATASET ? new Axiom({ token: process.env.AXIOM_TOKEN }) : null;
192
+ }
193
+ return axiomClient;
194
+ }
195
+ var dataset = () => process.env.AXIOM_DATASET ?? "";
196
+ var environment = () => resolveEnvironment();
197
+ function ingestToAxiom(level, message, context) {
198
+ const axiom = getAxiom();
199
+ if (!axiom) return;
200
+ axiom.ingest(dataset(), [
201
+ { ...context, level, message, environment: environment() }
202
+ ]);
203
+ }
204
+ function safeErrorText(error) {
205
+ try {
206
+ return error instanceof Error ? error.message : String(error);
207
+ } catch {
208
+ return "[unreadable error value]";
209
+ }
210
+ }
211
+ function captureOriginErrorToSentry(error, options) {
212
+ Sentry.captureException(error, options);
213
+ }
214
+ var logger = {
215
+ /**
216
+ * Log an error. Always sends to Sentry and Axiom, only prints to console in dev/verbose mode.
217
+ *
218
+ * This is the server's single Sentry capture path for free-form errors —
219
+ * `Hono.onError` routes through here, so route handlers never call
220
+ * `captureException` themselves and nothing double-counts.
221
+ *
222
+ * NOTE: For new production diagnostics in `server/routes/web/`, prefer `logger.event()` —
223
+ * free-form messages are not queryable in Axiom. Legacy callers (CLI, system code,
224
+ * non-route utilities) remain on this API. See `server/utils/LOGGING.md`.
225
+ */
226
+ error(message, error, context) {
227
+ if (!isOriginCaptureHandled(error)) {
228
+ Sentry.captureException(error ?? new Error(message), {
229
+ extra: { message, ...context }
230
+ });
231
+ }
232
+ ingestToAxiom("error", message, {
233
+ ...context,
234
+ error: safeErrorText(error)
235
+ });
236
+ if (shouldLog()) {
237
+ console.error(message, error);
238
+ }
239
+ },
240
+ /**
241
+ * Log a warning. Sends to Axiom, only prints to console in dev/verbose mode.
242
+ *
243
+ * Deliberately does NOT capture to Sentry. Every `logger.warn` in the tree
244
+ * used to become a Sentry event, which across thousands of self-hosted
245
+ * installs is the single largest quota-spike vector we have — and a warning
246
+ * is by definition something we chose not to treat as a failure. Axiom is
247
+ * the right home for it: queryable, cheap, no issue-tracker noise. A warning
248
+ * that genuinely needs a Sentry issue should be an explicit
249
+ * `logger.event(..., { error, sentry: true })` at the callsite that owns it.
250
+ *
251
+ * NOTE: For new production diagnostics in `server/routes/web/`, prefer `logger.event()` —
252
+ * free-form messages are not queryable in Axiom. Legacy callers (CLI, system code,
253
+ * non-route utilities) remain on this API. See `server/utils/LOGGING.md`.
254
+ */
255
+ warn(message, context) {
256
+ ingestToAxiom("warn", message, context);
257
+ if (shouldLog()) {
258
+ console.warn(message);
259
+ }
260
+ },
261
+ /**
262
+ * Log info. Always sends to Axiom. Only prints to console in dev/verbose mode.
263
+ *
264
+ * NOTE: For new production diagnostics in `server/routes/web/`, prefer `logger.event()` —
265
+ * free-form messages are not queryable in Axiom. Legacy callers (CLI, system code,
266
+ * non-route utilities) remain on this API. See `server/utils/LOGGING.md`.
267
+ */
268
+ info(message, context) {
269
+ ingestToAxiom("info", message, context);
270
+ if (shouldLog()) {
271
+ console.log(message);
272
+ }
273
+ },
274
+ /**
275
+ * Log debug info. Always sends to Axiom. Only prints to console in dev/verbose mode. Does not send to Sentry.
276
+ */
277
+ debug(message, ...args) {
278
+ ingestToAxiom("debug", message, args.length ? { args } : void 0);
279
+ if (shouldLog()) {
280
+ console.log(message, ...args);
281
+ }
282
+ },
283
+ /**
284
+ * Flush pending Axiom events. Call before process exit.
285
+ */
286
+ async flush() {
287
+ await getAxiom()?.flush();
288
+ },
289
+ event(eventName, base, payload, options) {
290
+ emit(eventName, base, payload, options);
291
+ },
292
+ systemEvent(eventName, base, payload, options) {
293
+ emit(eventName, base, payload, options);
294
+ }
295
+ };
296
+ var MAX_ERROR_MESSAGE_CHARS = 2e3;
297
+ var MAX_ERROR_STACK_CHARS = 4e3;
298
+ function serializeEmitError(error) {
299
+ try {
300
+ if (error instanceof Error) {
301
+ return {
302
+ name: error.name,
303
+ message: error.message.slice(0, MAX_ERROR_MESSAGE_CHARS),
304
+ ...error.stack ? { stack: error.stack.slice(0, MAX_ERROR_STACK_CHARS) } : {}
305
+ };
306
+ }
307
+ return String(error).slice(0, MAX_ERROR_MESSAGE_CHARS);
308
+ } catch {
309
+ return "[unserializable error]";
310
+ }
311
+ }
312
+ function emit(eventName, base, payload, options) {
313
+ const fullPayload = scrubLogPayload({
314
+ ...base,
315
+ ...payload,
316
+ ...options?.error !== void 0 ? { error: serializeEmitError(options.error) } : {},
317
+ event: eventName,
318
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
319
+ });
320
+ const axiom = getAxiom();
321
+ if (axiom) {
322
+ axiom.ingest(dataset(), [fullPayload]);
323
+ }
324
+ if (options?.sentry === true) {
325
+ if (options.error instanceof Error) {
326
+ Sentry.captureException(options.error, { extra: fullPayload });
327
+ } else {
328
+ Sentry.captureMessage(eventName, {
329
+ level: "error",
330
+ extra: {
331
+ ...fullPayload,
332
+ ...options.error !== void 0 ? { rawError: String(options.error) } : {}
333
+ }
334
+ });
335
+ }
336
+ }
337
+ if (shouldLog()) {
338
+ const consoleFn = eventName.endsWith(".failed") ? console.error : console.log;
339
+ consoleFn(`[event] ${eventName}`, fullPayload);
340
+ }
341
+ }
342
+ process.on("beforeExit", () => logger.flush());
343
+
344
+ // server/utils/harness/local/runtime-install.ts
345
+ import { createHash as createHash2, randomUUID } from "crypto";
346
+ import { createReadStream, createWriteStream } from "fs";
347
+ import {
348
+ mkdir,
349
+ readdir as readdir3,
350
+ readFile as readFile3,
351
+ rename,
352
+ rm,
353
+ stat as stat2,
354
+ writeFile
355
+ } from "fs/promises";
356
+ import { homedir } from "os";
357
+ import { basename, join as join2 } from "path";
358
+ import { pipeline } from "stream/promises";
359
+ import { Readable } from "stream";
360
+ import { x as extractTar } from "tar";
361
+
362
+ // server/utils/harness/local/pack-digests.generated.ts
363
+ var PACK_TREE_DIGESTS = {
364
+ "claude-code": {},
365
+ codex: {}
366
+ };
367
+ var PACK_RECORDS = {
368
+ "claude-code": {},
369
+ codex: {}
370
+ };
371
+ var EXPECTED_PACK_VERSION = "";
372
+
373
+ // server/utils/harness/local/pack-signing-key.ts
374
+ import { createPublicKey, verify as edVerify } from "crypto";
375
+ var PACK_SIGNING_KEYS = [
376
+ {
377
+ // Generated 2026-09-02. The private half went straight into the CI secret
378
+ // `LOCAL_HARNESS_PACK_SIGNING_KEY` and exists nowhere else — there is no
379
+ // copy to lose, and recovery is rotation, not restore.
380
+ keyId: "pack-2026-09",
381
+ publicKeyPem: `-----BEGIN PUBLIC KEY-----
382
+ MCowBQYDK2VwAyEAjaZXzr+4k5pJEuzIy5JUU858ksLJgjZv9b9jxmNhV6A=
383
+ -----END PUBLIC KEY-----
384
+ `
385
+ }
386
+ ];
387
+ function verifyPackManifestSignature(manifestBytes, signatureBase64, keys = PACK_SIGNING_KEYS) {
388
+ if (keys.length === 0) {
389
+ return {
390
+ ok: false,
391
+ reason: "no-keys",
392
+ message: "this Inspector build carries no runtime pack signing key, so it cannot verify that a downloaded pack came from MCPJam. Packs are installable from a local path for development only."
393
+ };
394
+ }
395
+ let signature;
396
+ try {
397
+ signature = Buffer.from(signatureBase64.trim(), "base64");
398
+ if (signature.length === 0) throw new Error("empty");
399
+ } catch {
400
+ return {
401
+ ok: false,
402
+ reason: "malformed",
403
+ message: "the pack manifest signature is not valid base64"
404
+ };
405
+ }
406
+ for (const key of keys) {
407
+ try {
408
+ const publicKey = createPublicKey(key.publicKeyPem);
409
+ if (edVerify(null, manifestBytes, publicKey, signature)) {
410
+ return { ok: true, keyId: key.keyId };
411
+ }
412
+ } catch {
413
+ continue;
414
+ }
415
+ }
416
+ return {
417
+ ok: false,
418
+ reason: "bad-signature",
419
+ message: "the runtime pack manifest is not signed by any key this Inspector trusts, so the pack is refused"
420
+ };
421
+ }
422
+
423
+ // server/utils/harness/local/runtime-identity.ts
424
+ import { createHash } from "crypto";
425
+ import { execFile as execFile2 } from "child_process";
426
+ import { readdir as readdir2, readFile as readFile2, realpath, stat } from "fs/promises";
427
+ import { dirname, isAbsolute, join, normalize, relative, sep } from "path";
428
+
429
+ // server/utils/harness/local/targets.ts
430
+ var LOCAL_PACK_TARGETS = /* @__PURE__ */ new Set([
431
+ "darwin-arm64",
432
+ "darwin-x64",
433
+ "linux-x64",
434
+ "linux-arm64",
435
+ "win32-x64"
436
+ ]);
437
+ function localPackTarget(platform = process.platform, arch = process.arch) {
438
+ const key = `${platform}-${arch}`;
439
+ return LOCAL_PACK_TARGETS.has(key) ? key : null;
440
+ }
441
+ var LOCAL_HARNESS_POLICY_VERSION = "local-harness-policy-2026-09-01";
442
+ var LOCAL_ISOLATION_POLICY_VERSION = "local-isolation-policy-2026-09-01";
443
+ function currentLocalPlatform(platform = process.platform) {
444
+ if (platform === "darwin" || platform === "linux" || platform === "win32") {
445
+ return platform;
446
+ }
447
+ return null;
448
+ }
449
+
450
+ // server/utils/harness/local/process-identity.ts
451
+ import { execFile } from "child_process";
452
+ import { readdir, readFile } from "fs/promises";
453
+ var PS_TIMEOUT_MS = 3e3;
454
+ function parseLinuxProcStat(raw) {
455
+ const close = raw.lastIndexOf(")");
456
+ if (close === -1) return null;
457
+ const fields = raw.slice(close + 2).split(" ");
458
+ const state = fields[0];
459
+ const starttime = fields[19];
460
+ if (!state || !/^[A-Za-z]$/.test(state)) return null;
461
+ if (!starttime || !/^\d+$/.test(starttime)) return null;
462
+ return { state, starttime };
463
+ }
464
+ function isDeadState(state) {
465
+ return state === "Z" || state === "X" || state === "x";
466
+ }
467
+ async function probeLinux(pid) {
468
+ let raw;
469
+ try {
470
+ raw = await readFile(`/proc/${pid}/stat`, "utf8");
471
+ } catch (error) {
472
+ const code = error.code;
473
+ if (code === "ENOENT" || code === "ESRCH") return { state: "gone" };
474
+ return {
475
+ state: "unknown",
476
+ reason: `/proc read failed (${code ?? "unknown"})`
477
+ };
478
+ }
479
+ const parsed = parseLinuxProcStat(raw);
480
+ if (parsed === null) {
481
+ return { state: "unknown", reason: "unparseable /proc stat line" };
482
+ }
483
+ if (isDeadState(parsed.state)) return { state: "gone" };
484
+ return { state: "alive", identity: `linux:${parsed.starttime}` };
485
+ }
486
+ function parseDarwinPsLine(raw) {
487
+ const line = raw.trim();
488
+ if (line.length === 0) return null;
489
+ const match = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+([\s\S]+)$/.exec(line);
490
+ if (match === null) return null;
491
+ const state = match[1];
492
+ const lstart = [match[2], match[3], match[4], match[5], match[6]].join(" ");
493
+ const command = match[7];
494
+ return { state, lstart, command };
495
+ }
496
+ async function probeDarwin(pid) {
497
+ const result = await new Promise((resolve) => {
498
+ execFile(
499
+ "/bin/ps",
500
+ ["-o", "state=,lstart=,command=", "-p", String(pid)],
501
+ {
502
+ timeout: PS_TIMEOUT_MS,
503
+ maxBuffer: 16 * 1024,
504
+ encoding: "utf8",
505
+ env: {}
506
+ },
507
+ (error, out) => {
508
+ if (!error) {
509
+ resolve({ ok: true, stdout: typeof out === "string" ? out : "" });
510
+ return;
511
+ }
512
+ const err = error;
513
+ resolve({
514
+ ok: false,
515
+ // `ps` exits 1 when no process matches — that IS an answer. A kill
516
+ // (timeout) or a spawn failure is not.
517
+ exitCode: typeof err.code === "number" ? err.code : null,
518
+ reason: err.killed ? "ps timed out" : `ps failed (${String(err.code ?? "unknown")})`
519
+ });
520
+ }
521
+ );
522
+ });
523
+ if (!result.ok) {
524
+ if (result.exitCode === 1) return { state: "gone" };
525
+ return { state: "unknown", reason: result.reason };
526
+ }
527
+ const parsed = parseDarwinPsLine(result.stdout);
528
+ if (parsed === null) {
529
+ return result.stdout.trim().length === 0 ? { state: "gone" } : { state: "unknown", reason: "unparseable ps output" };
530
+ }
531
+ if (isDeadState(parsed.state.charAt(0))) return { state: "gone" };
532
+ return {
533
+ state: "alive",
534
+ identity: `darwin:${parsed.lstart}|${parsed.command}`
535
+ };
536
+ }
537
+ async function probeProcess(pid, platform = process.platform) {
538
+ if (!Number.isInteger(pid) || pid <= 0) {
539
+ return { state: "unknown", reason: "implausible pid" };
540
+ }
541
+ if (platform === "linux") return probeLinux(pid);
542
+ if (platform === "darwin") return probeDarwin(pid);
543
+ return { state: "unknown", reason: `no liveness primitive on ${platform}` };
544
+ }
545
+ async function readProcessBirthIdentity(pid, platform = process.platform) {
546
+ const probe = await probeProcess(pid, platform);
547
+ return probe.state === "alive" ? probe.identity : null;
548
+ }
549
+ function supportsOwnershipProof(platform = process.platform) {
550
+ if (platform === "linux" || platform === "darwin") return true;
551
+ if (platform === "win32") return windowsJobLauncherVerified;
552
+ return false;
553
+ }
554
+ var windowsJobLauncherVerified = false;
555
+ function setWindowsJobLauncherVerified(verified) {
556
+ windowsJobLauncherVerified = verified;
557
+ }
558
+ function sameBirthIdentity(recorded, current) {
559
+ if (recorded === current) return true;
560
+ const r = /^darwin:([^|]+)\|([\s\S]*)$/.exec(recorded);
561
+ const c = /^darwin:([^|]+)\|([\s\S]*)$/.exec(current);
562
+ if (r === null || c === null) return false;
563
+ return r[1] === c[1] && /^\([^()]+\)$/.test(c[2]);
564
+ }
565
+ async function listGroupMembers(pgid, platform = process.platform) {
566
+ let pids = [];
567
+ if (platform === "darwin") {
568
+ const out = await new Promise((resolve) => {
569
+ execFile(
570
+ "/bin/ps",
571
+ ["-o", "pid=,state=", "-g", String(pgid)],
572
+ { timeout: PS_TIMEOUT_MS, maxBuffer: 64 * 1024, encoding: "utf8", env: {} },
573
+ (error, stdout) => resolve(error ? error.code === 1 ? "" : null : String(stdout))
574
+ );
575
+ });
576
+ if (out === null) return null;
577
+ pids = out.split("\n").map((l) => l.trim().split(/\s+/)).filter((f) => f.length >= 2 && /^\d+$/.test(f[0]) && !isDeadState(f[1].charAt(0))).map((f) => Number(f[0]));
578
+ } else if (platform === "linux") {
579
+ let entries;
580
+ try {
581
+ entries = await readdir("/proc");
582
+ } catch {
583
+ return null;
584
+ }
585
+ for (const entry of entries) {
586
+ if (!/^\d+$/.test(entry)) continue;
587
+ let raw;
588
+ try {
589
+ raw = await readFile(`/proc/${entry}/stat`, "utf8");
590
+ } catch {
591
+ continue;
592
+ }
593
+ const parsed = parseProcStatGroup(raw);
594
+ if (parsed !== null && parsed.pgrp === pgid && !isDeadState(parsed.state.charAt(0))) pids.push(Number(entry));
595
+ }
596
+ } else {
597
+ return null;
598
+ }
599
+ const members = [];
600
+ for (const pid of pids) {
601
+ if (pid === pgid) continue;
602
+ const identity = await readProcessBirthIdentity(pid, platform);
603
+ if (identity !== null) members.push({ pid, identity });
604
+ }
605
+ return members;
606
+ }
607
+ async function terminateOwnedProcess(args) {
608
+ const platform = args.platform ?? process.platform;
609
+ const initial = await probeProcess(args.pid, platform);
610
+ if (initial.state === "gone") return "already-gone";
611
+ if (initial.state === "unknown") return "unknown";
612
+ if (!sameBirthIdentity(args.identity, initial.identity)) return "not-owned";
613
+ const waitGone = async (ms) => {
614
+ const until = Date.now() + ms;
615
+ while (Date.now() < until) {
616
+ const p = await probeProcess(args.pid, platform);
617
+ if (p.state === "gone") return true;
618
+ await new Promise((r) => setTimeout(r, 50));
619
+ }
620
+ return false;
621
+ };
622
+ try {
623
+ process.kill(args.pid, "SIGTERM");
624
+ } catch {
625
+ return "already-gone";
626
+ }
627
+ if (await waitGone(args.graceMs)) return "graceful";
628
+ const again = await probeProcess(args.pid, platform);
629
+ if (again.state === "gone") return "graceful";
630
+ if (again.state === "unknown") return "unknown";
631
+ if (!sameBirthIdentity(args.identity, again.identity)) return "not-owned";
632
+ try {
633
+ process.kill(args.pid, "SIGKILL");
634
+ } catch {
635
+ return "graceful";
636
+ }
637
+ return await waitGone(500) ? "forced" : "escaped";
638
+ }
639
+ async function probeProcessGroup(pid, platform = process.platform) {
640
+ if (!Number.isInteger(pid) || pid <= 0) return "unknown";
641
+ if (platform === "linux") return probeLinuxGroup(pid);
642
+ if (platform === "darwin") return probeDarwinGroup(pid);
643
+ return "unknown";
644
+ }
645
+ async function readProcessGroupId(pid, platform = process.platform) {
646
+ if (!Number.isInteger(pid) || pid <= 0) return null;
647
+ if (platform === "linux") {
648
+ try {
649
+ const raw = await readFile(`/proc/${pid}/stat`, "utf8");
650
+ return parseProcStatGroup(raw)?.pgrp ?? null;
651
+ } catch {
652
+ return null;
653
+ }
654
+ }
655
+ if (platform === "darwin") {
656
+ return new Promise((resolvePromise) => {
657
+ execFile(
658
+ "/bin/ps",
659
+ ["-o", "pgid=", "-p", String(pid)],
660
+ {
661
+ timeout: PS_TIMEOUT_MS,
662
+ maxBuffer: 4 * 1024,
663
+ encoding: "utf8",
664
+ env: {}
665
+ },
666
+ (error, stdout) => {
667
+ if (error) {
668
+ resolvePromise(null);
669
+ return;
670
+ }
671
+ const parsed = Number(String(stdout).trim());
672
+ resolvePromise(Number.isInteger(parsed) && parsed > 0 ? parsed : null);
673
+ }
674
+ );
675
+ });
676
+ }
677
+ return null;
678
+ }
679
+ function parseProcStatGroup(raw) {
680
+ const close = raw.lastIndexOf(")");
681
+ if (close === -1) return null;
682
+ const fields = raw.slice(close + 1).trim().split(/\s+/);
683
+ const state = fields[0];
684
+ const pgrp = Number(fields[2]);
685
+ if (state === void 0 || !Number.isInteger(pgrp)) return null;
686
+ return { state, pgrp };
687
+ }
688
+ async function probeLinuxGroup(pgid) {
689
+ let entries;
690
+ try {
691
+ entries = await readdir("/proc");
692
+ } catch {
693
+ return "unknown";
694
+ }
695
+ let blind = false;
696
+ for (const entry of entries) {
697
+ if (!/^\d+$/.test(entry)) continue;
698
+ let raw;
699
+ try {
700
+ raw = await readFile(`/proc/${entry}/stat`, "utf8");
701
+ } catch (error) {
702
+ const code = error.code;
703
+ if (code !== "ENOENT" && code !== "ESRCH") blind = true;
704
+ continue;
705
+ }
706
+ const parsed = parseProcStatGroup(raw);
707
+ if (parsed === null) {
708
+ blind = true;
709
+ continue;
710
+ }
711
+ if (parsed.pgrp !== pgid) continue;
712
+ if (!isDeadState(parsed.state.charAt(0))) return "live";
713
+ }
714
+ return blind ? "unknown" : "empty";
715
+ }
716
+ async function probeDarwinGroup(pgid) {
717
+ const result = await new Promise((resolve) => {
718
+ execFile(
719
+ "/bin/ps",
720
+ ["-o", "state=", "-g", String(pgid)],
721
+ {
722
+ timeout: PS_TIMEOUT_MS,
723
+ maxBuffer: 64 * 1024,
724
+ encoding: "utf8",
725
+ env: {}
726
+ },
727
+ (error, out) => {
728
+ if (!error) {
729
+ resolve({ ok: true, stdout: typeof out === "string" ? out : "" });
730
+ return;
731
+ }
732
+ const err = error;
733
+ resolve({ ok: false, empty: err.killed !== true && err.code === 1 });
734
+ }
735
+ );
736
+ });
737
+ if (!result.ok) return result.empty ? "empty" : "unknown";
738
+ const states = result.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
739
+ if (states.some((state) => !isDeadState(state.charAt(0)))) return "live";
740
+ return "empty";
741
+ }
742
+ function signalProcessGroup(pid, signal, platform = process.platform) {
743
+ if (!Number.isInteger(pid) || pid <= 0) return false;
744
+ try {
745
+ if (platform === "win32") {
746
+ return false;
747
+ }
748
+ process.kill(-pid, signal);
749
+ return true;
750
+ } catch (error) {
751
+ if (error.code === "ESRCH") return true;
752
+ return false;
753
+ }
754
+ }
755
+ async function terminateOwnedProcessGroup(args) {
756
+ const platform = args.platform ?? process.platform;
757
+ const pollMs = args.pollMs ?? 50;
758
+ const settleGroup = async (goneOutcome, anchored) => {
759
+ const probeGroup = args.probeGroup ?? probeProcessGroup;
760
+ const before = await probeGroup(args.pid, platform);
761
+ if (before === "empty") return { outcome: goneOutcome };
762
+ if (before === "unknown") {
763
+ return {
764
+ outcome: "unknown",
765
+ reason: "the process group could not be enumerated, so a survivor could be neither ruled out nor signalled without risking a reused group id"
766
+ };
767
+ }
768
+ if (!anchored) {
769
+ return {
770
+ outcome: "unknown",
771
+ reason: "a live process group carries this id, but the root was already gone when the stop began, so the group cannot be tied to this tree"
772
+ };
773
+ }
774
+ signalProcessGroup(args.pid, "SIGKILL", platform);
775
+ await new Promise((r) => setTimeout(r, Math.min(args.graceMs, 500)));
776
+ const after = await probeGroup(args.pid, platform);
777
+ if (after === "empty") return { outcome: "forced" };
778
+ if (after === "unknown") {
779
+ return {
780
+ outcome: "unknown",
781
+ reason: "the process group could not be enumerated after SIGKILL"
782
+ };
783
+ }
784
+ return { outcome: "escaped" };
785
+ };
786
+ const initial = await probeProcess(args.pid, platform);
787
+ if (initial.state === "gone") return settleGroup("already-gone", false);
788
+ if (initial.state === "unknown") {
789
+ return { outcome: "unknown", reason: initial.reason };
790
+ }
791
+ if (!sameBirthIdentity(args.birthIdentity, initial.identity)) {
792
+ return { outcome: "not-owned" };
793
+ }
794
+ signalProcessGroup(args.pid, "SIGTERM", platform);
795
+ const deadline = Date.now() + args.graceMs;
796
+ while (Date.now() < deadline) {
797
+ await new Promise((r) => setTimeout(r, pollMs));
798
+ if ((await probeProcess(args.pid, platform)).state === "gone") {
799
+ return settleGroup("graceful", true);
800
+ }
801
+ }
802
+ const afterGrace = await probeProcess(args.pid, platform);
803
+ if (afterGrace.state === "gone") return settleGroup("graceful", true);
804
+ if (afterGrace.state === "unknown") {
805
+ return { outcome: "unknown", reason: afterGrace.reason };
806
+ }
807
+ if (!sameBirthIdentity(args.birthIdentity, afterGrace.identity)) {
808
+ return settleGroup("graceful", true);
809
+ }
810
+ signalProcessGroup(args.pid, "SIGKILL", platform);
811
+ const killDeadline = Date.now() + Math.max(args.graceMs, 1e3);
812
+ while (Date.now() < killDeadline) {
813
+ await new Promise((r) => setTimeout(r, pollMs));
814
+ if ((await probeProcess(args.pid, platform)).state === "gone") {
815
+ return settleGroup("forced", true);
816
+ }
817
+ }
818
+ const settled = await probeProcess(args.pid, platform);
819
+ if (settled.state === "gone") return settleGroup("forced", true);
820
+ if (settled.state === "unknown") {
821
+ return { outcome: "unknown", reason: settled.reason };
822
+ }
823
+ return { outcome: "escaped" };
824
+ }
825
+
826
+ // server/utils/harness/local/runtime-identity.ts
827
+ var MAX_BUNDLE_FILES = 5e4;
828
+ var MAX_BUNDLE_BYTES = 2 * 1024 * 1024 * 1024;
829
+ var PROBE_TIMEOUT_MS = 5e3;
830
+ var PROBE_MAX_BUFFER = 64 * 1024;
831
+ async function computeTreeDigest(root) {
832
+ return (await digestTreeWithSnapshot(root)).digest;
833
+ }
834
+ async function digestTreeWithSnapshot(root) {
835
+ const hash = createHash("sha256");
836
+ const entriesSnapshot = [];
837
+ const executableDigests = {};
838
+ let files = 0;
839
+ let bytes = 0;
840
+ const walk = async (dir) => {
841
+ const entries = await readdir2(dir, { withFileTypes: true });
842
+ entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
843
+ for (const entry of entries) {
844
+ const full = join(dir, entry.name);
845
+ const rel = relative(root, full).split(sep).join("/");
846
+ if (entry.isSymbolicLink()) {
847
+ throw new Error(
848
+ `managed runtime bundle contains a symlink at ${rel}; bundles are built as plain files so their digest describes exactly what runs`
849
+ );
850
+ }
851
+ if (entry.isDirectory()) {
852
+ hash.update(`d\0${rel}\0`);
853
+ await walk(full);
854
+ continue;
855
+ }
856
+ if (!entry.isFile()) {
857
+ throw new Error(
858
+ `managed runtime bundle contains a non-regular file at ${rel}`
859
+ );
860
+ }
861
+ if (++files > MAX_BUNDLE_FILES) {
862
+ throw new Error(
863
+ "managed runtime bundle exceeds the file-count ceiling"
864
+ );
865
+ }
866
+ const info = await stat(full);
867
+ bytes += info.size;
868
+ if (bytes > MAX_BUNDLE_BYTES) {
869
+ throw new Error("managed runtime bundle exceeds the size ceiling");
870
+ }
871
+ const content = await readFile2(full);
872
+ const executable = (info.mode & 73) !== 0 ? "1" : "0";
873
+ const contentDigest = createHash("sha256").update(content).digest();
874
+ hash.update(`f\0${rel}\0${executable}\0${info.size}\0`);
875
+ hash.update(contentDigest);
876
+ entriesSnapshot.push({
877
+ path: rel,
878
+ size: info.size,
879
+ mtimeMs: info.mtimeMs,
880
+ ctimeMs: info.ctimeMs,
881
+ ino: Number(info.ino),
882
+ mode: info.mode
883
+ });
884
+ if (isBaselineHashed(rel)) {
885
+ executableDigests[rel] = contentDigest.toString("hex");
886
+ }
887
+ }
888
+ };
889
+ await walk(root);
890
+ return {
891
+ root,
892
+ digest: `sha256:${hash.digest("hex")}`,
893
+ entries: entriesSnapshot,
894
+ executableDigests
895
+ };
896
+ }
897
+ var ALWAYS_REHASHED_RELATIVE_PATHS = [
898
+ "launcher.mjs",
899
+ "bridge.mjs"
900
+ ];
901
+ var STRICT_REHASHED_PATH_PATTERNS = [
902
+ /^bin\/node(\.exe)?$/,
903
+ /(^|\/)claude-agent-sdk-[a-z0-9-]+\/(claude|claude\.exe)$/,
904
+ /(^|\/)claude-code-[a-z0-9-]+\/(claude|claude\.exe)$/
905
+ ];
906
+ function strictReverifyEnabled() {
907
+ return process.env.MCPJAM_LOCAL_HARNESS_STRICT_REVERIFY === "true";
908
+ }
909
+ function isBaselineHashed(relativePath) {
910
+ return ALWAYS_REHASHED_RELATIVE_PATHS.includes(relativePath) || STRICT_REHASHED_PATH_PATTERNS.some((pattern) => pattern.test(relativePath));
911
+ }
912
+ function isRehashedOnRevalidate(relativePath) {
913
+ if (ALWAYS_REHASHED_RELATIVE_PATHS.includes(relativePath)) return true;
914
+ return strictReverifyEnabled() && STRICT_REHASHED_PATH_PATTERNS.some((pattern) => pattern.test(relativePath));
915
+ }
916
+ var verifiedRuntimeCache = /* @__PURE__ */ new Map();
917
+ var inFlightVerifications = /* @__PURE__ */ new Map();
918
+ function verificationCacheKey(root, expectedDigest) {
919
+ return `${root}\0${expectedDigest}`;
920
+ }
921
+ var cacheGeneration = 0;
922
+ function clearRuntimeVerificationCache() {
923
+ cacheGeneration += 1;
924
+ verifiedRuntimeCache.clear();
925
+ inFlightVerifications.clear();
926
+ }
927
+ async function verifyRuntime(root, expectedDigest) {
928
+ const key = verificationCacheKey(root, expectedDigest);
929
+ const cached = verifiedRuntimeCache.get(key);
930
+ if (cached !== void 0) {
931
+ return { ok: true, digest: cached.digest, snapshot: cached, cached: true };
932
+ }
933
+ const running = inFlightVerifications.get(key);
934
+ if (running !== void 0) return running;
935
+ const started = digestOnce(root, expectedDigest, key, cacheGeneration);
936
+ inFlightVerifications.set(key, started);
937
+ try {
938
+ return await started;
939
+ } finally {
940
+ if (inFlightVerifications.get(key) === started) {
941
+ inFlightVerifications.delete(key);
942
+ }
943
+ }
944
+ }
945
+ async function digestOnce(root, expectedDigest, key, generation) {
946
+ let snapshot;
947
+ try {
948
+ snapshot = await digestTreeWithSnapshot(root);
949
+ } catch (error) {
950
+ return {
951
+ ok: false,
952
+ reason: "unreadable",
953
+ message: error instanceof Error ? error.message : String(error)
954
+ };
955
+ }
956
+ if (snapshot.digest !== expectedDigest) {
957
+ return { ok: false, reason: "digest-mismatch", digest: snapshot.digest };
958
+ }
959
+ if (generation !== cacheGeneration) {
960
+ return {
961
+ ok: false,
962
+ reason: "unreadable",
963
+ message: "the runtime tree was replaced while it was being verified; verify again"
964
+ };
965
+ }
966
+ verifiedRuntimeCache.set(key, snapshot);
967
+ return { ok: true, digest: snapshot.digest, snapshot, cached: false };
968
+ }
969
+ function runtimeIdOf(parts) {
970
+ return `rt_${createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 32)}`;
971
+ }
972
+ async function resolveManagedBundle(args) {
973
+ const { manifest, runtimeRoot, platform } = args;
974
+ if (manifest.runtime.source !== "managed-bundle") {
975
+ return {
976
+ ok: false,
977
+ status: "bundle-absent",
978
+ message: `${manifest.harnessId} is not configured for a managed bundle`
979
+ };
980
+ }
981
+ const policy = manifest.runtime;
982
+ const root = join(runtimeRoot, policy.bundleName);
983
+ let canonicalRoot;
984
+ try {
985
+ canonicalRoot = await realpath(root);
986
+ const info = await stat(canonicalRoot);
987
+ if (!info.isDirectory()) throw new Error("not a directory");
988
+ } catch {
989
+ return {
990
+ ok: false,
991
+ status: "bundle-absent",
992
+ message: `the ${manifest.harnessId} runtime bundle is not present at ${root}. It ships with the Inspector install; reinstall or run the setup step that downloads it. Nothing is installed while a session starts.`
993
+ };
994
+ }
995
+ const arch = args.arch ?? process.arch;
996
+ const target = localPackTarget(platform, arch);
997
+ const expectedDigest = target === null ? void 0 : policy.bundleDigest[target];
998
+ if (expectedDigest === void 0) {
999
+ return {
1000
+ ok: false,
1001
+ status: "bundle-absent",
1002
+ // The TARGET, not just the OS: on a machine whose architecture nobody
1003
+ // builds for, "no pack for darwin" reads as a platform outage when the
1004
+ // real answer is "no pack for darwin-x64". A user comparing that against
1005
+ // the release assets needs the string that appears there.
1006
+ message: `no ${manifest.harnessId} runtime pack has been built for ${platform}-${arch}, so there is no digest to verify one against`
1007
+ };
1008
+ }
1009
+ const verification = await verifyRuntime(canonicalRoot, expectedDigest);
1010
+ if (!verification.ok && verification.reason === "unreadable") {
1011
+ return {
1012
+ ok: false,
1013
+ status: "bundle-corrupt",
1014
+ message: `the ${manifest.harnessId} runtime bundle could not be digested: ${verification.message}`
1015
+ };
1016
+ }
1017
+ if (!verification.ok) {
1018
+ return {
1019
+ ok: false,
1020
+ status: "bundle-digest-mismatch",
1021
+ message: `the ${manifest.harnessId} runtime bundle does not match the digest this Inspector was built with. Expected ${expectedDigest}, found ${verification.digest}. The bundle is not user- or session-writable by design, so a mismatch means it was replaced \u2014 local execution stays disabled until it is reinstalled.`
1022
+ };
1023
+ }
1024
+ const digest = verification.digest;
1025
+ const insideBundle = async (relativePath, label, requireExecutable) => {
1026
+ const full = join(canonicalRoot, relativePath);
1027
+ if (!normalize(full).startsWith(canonicalRoot + sep)) {
1028
+ return {
1029
+ ok: false,
1030
+ message: `the ${manifest.harnessId} manifest points its ${label} outside the bundle whose digest was verified`
1031
+ };
1032
+ }
1033
+ try {
1034
+ const info = await stat(full);
1035
+ if (!info.isFile()) throw new Error("not a file");
1036
+ if (requireExecutable && platform !== "win32" && (info.mode & 73) === 0) {
1037
+ throw new Error("not executable");
1038
+ }
1039
+ } catch {
1040
+ return {
1041
+ ok: false,
1042
+ message: `the ${manifest.harnessId} runtime bundle has no usable ${label} at ${relativePath}`
1043
+ };
1044
+ }
1045
+ return { ok: true, path: full };
1046
+ };
1047
+ const launcher = await insideBundle(
1048
+ policy.launcherRelativePath,
1049
+ "launcher",
1050
+ false
1051
+ );
1052
+ if (!launcher.ok) {
1053
+ return { ok: false, status: "bundle-corrupt", message: launcher.message };
1054
+ }
1055
+ const launcherPath = launcher.path;
1056
+ const bundledNode = await insideBundle(
1057
+ platform === "win32" ? `${policy.nodeLauncherRelativePath}.exe` : policy.nodeLauncherRelativePath,
1058
+ "Node binary",
1059
+ true
1060
+ );
1061
+ if (!bundledNode.ok) {
1062
+ return { ok: false, status: "bundle-corrupt", message: bundledNode.message };
1063
+ }
1064
+ let jobLauncherPath;
1065
+ if (platform === "win32" && policy.jobLauncherRelativePath) {
1066
+ const helper = await insideBundle(
1067
+ policy.jobLauncherRelativePath,
1068
+ "job launcher",
1069
+ true
1070
+ );
1071
+ if (helper.ok) jobLauncherPath = helper.path;
1072
+ }
1073
+ setWindowsJobLauncherVerified(jobLauncherPath !== void 0);
1074
+ return {
1075
+ ok: true,
1076
+ runtime: {
1077
+ runtimeId: runtimeIdOf([
1078
+ "managed-bundle",
1079
+ manifest.harnessId,
1080
+ manifest.adapterVersion,
1081
+ platform,
1082
+ digest
1083
+ ]),
1084
+ source: "managed-bundle",
1085
+ harnessId: manifest.harnessId,
1086
+ platform,
1087
+ adapterVersion: manifest.adapterVersion,
1088
+ rootPath: canonicalRoot,
1089
+ launcherPath,
1090
+ nodePath: bundledNode.path,
1091
+ ...jobLauncherPath !== void 0 ? { jobLauncherPath } : {},
1092
+ digest,
1093
+ vendorPackages: policy.vendorPackages
1094
+ }
1095
+ };
1096
+ }
1097
+ function systemInstallSearchPaths(platform) {
1098
+ switch (platform) {
1099
+ case "darwin":
1100
+ return ["/usr/local/bin", "/opt/homebrew/bin", "/usr/bin"];
1101
+ case "linux":
1102
+ return ["/usr/local/bin", "/usr/bin", "/bin"];
1103
+ case "win32":
1104
+ return ["C:\\Program Files", "C:\\Program Files (x86)"];
1105
+ }
1106
+ }
1107
+ function isUntrustedInstallPath(canonical, forbiddenRoots) {
1108
+ for (const root of forbiddenRoots) {
1109
+ if (canonical === root || canonical.startsWith(root + sep)) {
1110
+ return root;
1111
+ }
1112
+ }
1113
+ if (/(^|[\\/])node_modules([\\/]|$)/.test(canonical)) {
1114
+ return "a node_modules directory";
1115
+ }
1116
+ return null;
1117
+ }
1118
+ function extractVersion(line) {
1119
+ return /(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/.exec(line)?.[1] ?? null;
1120
+ }
1121
+ function isSupportedVersionRange(range) {
1122
+ return /^>=\s*\d+\.\d+\.\d+$/.test(range.trim());
1123
+ }
1124
+ function satisfiesMinimumRange(version, range) {
1125
+ const match = /^>=\s*(\d+)\.(\d+)\.(\d+)$/.exec(range.trim());
1126
+ if (!match) return false;
1127
+ if (version.includes("-")) return false;
1128
+ const parts = version.split(".").map((n) => Number.parseInt(n, 10));
1129
+ if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n)))
1130
+ return false;
1131
+ const min = [Number(match[1]), Number(match[2]), Number(match[3])];
1132
+ for (let i = 0; i < 3; i += 1) {
1133
+ if (parts[i] > min[i]) return true;
1134
+ if (parts[i] < min[i]) return false;
1135
+ }
1136
+ return true;
1137
+ }
1138
+ async function defaultProbe(executable, args) {
1139
+ return await new Promise((resolvePromise) => {
1140
+ execFile2(
1141
+ executable,
1142
+ [...args],
1143
+ {
1144
+ timeout: PROBE_TIMEOUT_MS,
1145
+ maxBuffer: PROBE_MAX_BUFFER,
1146
+ encoding: "utf8",
1147
+ windowsHide: true,
1148
+ env: { PATH: "", LANG: "C" }
1149
+ },
1150
+ (error, stdout) => {
1151
+ resolvePromise({
1152
+ stdout: typeof stdout === "string" ? stdout : "",
1153
+ exitCode: error ? 1 : 0
1154
+ });
1155
+ }
1156
+ );
1157
+ });
1158
+ }
1159
+ async function firstUntrustedAncestor(path, platform) {
1160
+ if (platform === "win32") return null;
1161
+ let dir = dirname(path);
1162
+ for (; ; ) {
1163
+ let info;
1164
+ try {
1165
+ info = await stat(dir);
1166
+ } catch {
1167
+ return dir;
1168
+ }
1169
+ const looselyWritable = (info.mode & 18) !== 0 && (info.mode & 512) === 0;
1170
+ if (info.uid !== 0 || looselyWritable) return dir;
1171
+ const parent = dirname(dir);
1172
+ if (parent === dir) return null;
1173
+ dir = parent;
1174
+ }
1175
+ }
1176
+ async function resolveSystemInstall(opts) {
1177
+ const { manifest, platform } = opts;
1178
+ if (manifest.runtime.source !== "system-install") {
1179
+ return {
1180
+ ok: false,
1181
+ status: "system-runtime-not-installed",
1182
+ message: `${manifest.harnessId} is not configured for a system install`
1183
+ };
1184
+ }
1185
+ if (platform === "darwin") {
1186
+ return {
1187
+ ok: false,
1188
+ status: "system-runtime-untrusted-path",
1189
+ message: `a system installation cannot be trusted on macOS by this Inspector: directory ACLs there can grant replacement rights without appearing in the file mode, and nothing here can read them. Use a managed bundle, whose digest is verified in full.`
1190
+ };
1191
+ }
1192
+ const policy = manifest.runtime;
1193
+ const searchPaths = opts.searchPaths ?? systemInstallSearchPaths(platform);
1194
+ const probe = opts.probe ?? defaultProbe;
1195
+ const accepted = [];
1196
+ const rejections = [];
1197
+ for (const dir of searchPaths) {
1198
+ for (const name of policy.executableNames) {
1199
+ const candidate = join(dir, name);
1200
+ let canonical;
1201
+ try {
1202
+ canonical = await realpath(candidate);
1203
+ } catch {
1204
+ continue;
1205
+ }
1206
+ if (!isAbsolute(canonical)) continue;
1207
+ const untrusted = isUntrustedInstallPath(canonical, opts.forbiddenRoots);
1208
+ if (untrusted) {
1209
+ rejections.push({
1210
+ status: "system-runtime-untrusted-path",
1211
+ message: `${candidate} resolves into ${untrusted}, which the session or the workspace can write`
1212
+ });
1213
+ continue;
1214
+ }
1215
+ let info;
1216
+ try {
1217
+ info = await stat(canonical);
1218
+ } catch {
1219
+ continue;
1220
+ }
1221
+ if (!info.isFile() || (info.mode & 73) === 0) {
1222
+ rejections.push({
1223
+ status: "system-runtime-not-installed",
1224
+ message: `${candidate} is not an executable file`
1225
+ });
1226
+ continue;
1227
+ }
1228
+ if ((info.mode & 18) !== 0) {
1229
+ rejections.push({
1230
+ status: "system-runtime-untrusted-path",
1231
+ message: `${candidate} is group- or world-writable, so its identity cannot be held between verification and launch`
1232
+ });
1233
+ continue;
1234
+ }
1235
+ if (platform !== "win32" && info.uid !== 0) {
1236
+ rejections.push({
1237
+ status: "system-runtime-untrusted-path",
1238
+ message: `${candidate} is owned by uid ${info.uid} rather than a system owner, so the supervised agent \u2014 which runs as that user \u2014 could replace it between verification and launch`
1239
+ });
1240
+ continue;
1241
+ }
1242
+ const untrustedAncestor = await firstUntrustedAncestor(
1243
+ canonical,
1244
+ platform
1245
+ );
1246
+ if (untrustedAncestor !== null) {
1247
+ rejections.push({
1248
+ status: "system-runtime-untrusted-path",
1249
+ message: `${candidate} lives under ${untrustedAncestor}, which is writable by a non-system owner. Replacing the executable there needs only directory permissions, so its verified identity cannot be held between verification and launch`
1250
+ });
1251
+ continue;
1252
+ }
1253
+ if (policy.vendorIdentityPolicy.requirePlatformProvenance) {
1254
+ rejections.push({
1255
+ status: "system-runtime-identity-mismatch",
1256
+ message: `${manifest.harnessId} requires platform code-signing/package provenance, and this Inspector has no verifier for ${platform} yet, so the candidate at ${candidate} cannot be accepted`
1257
+ });
1258
+ continue;
1259
+ }
1260
+ const result = await probe(
1261
+ canonical,
1262
+ policy.vendorIdentityPolicy.probeArgs
1263
+ );
1264
+ const line = result.stdout.trim().split("\n")[0] ?? "";
1265
+ if (result.exitCode !== 0 || !new RegExp(policy.vendorIdentityPolicy.stdoutPattern).test(line)) {
1266
+ rejections.push({
1267
+ status: "system-runtime-identity-mismatch",
1268
+ message: `${candidate} did not identify itself as ${manifest.harnessId} (probe output ${JSON.stringify(line.slice(0, 120))})`
1269
+ });
1270
+ continue;
1271
+ }
1272
+ if (!isSupportedVersionRange(policy.executableVersionRange)) {
1273
+ rejections.push({
1274
+ status: "system-runtime-identity-mismatch",
1275
+ message: `the ${manifest.harnessId} manifest declares version range ${JSON.stringify(policy.executableVersionRange)}, which is not one of the shapes this Inspector evaluates (">=x.y.z")`
1276
+ });
1277
+ continue;
1278
+ }
1279
+ const version = extractVersion(line);
1280
+ if (version === null || !satisfiesMinimumRange(version, policy.executableVersionRange)) {
1281
+ rejections.push({
1282
+ status: "system-runtime-identity-mismatch",
1283
+ message: `${candidate} reports version ${version ?? "(unparseable)"}, which is outside the manifest range ${policy.executableVersionRange}`
1284
+ });
1285
+ continue;
1286
+ }
1287
+ const content = await readFile2(canonical);
1288
+ const digest = `sha256:${createHash("sha256").update(content).digest("hex")}`;
1289
+ accepted.push({
1290
+ runtimeId: runtimeIdOf([
1291
+ "system-install",
1292
+ manifest.harnessId,
1293
+ manifest.adapterVersion,
1294
+ platform,
1295
+ canonical,
1296
+ digest,
1297
+ String(info.ino)
1298
+ ]),
1299
+ source: "system-install",
1300
+ harnessId: manifest.harnessId,
1301
+ platform,
1302
+ adapterVersion: manifest.adapterVersion,
1303
+ rootPath: canonical,
1304
+ launcherPath: canonical,
1305
+ digest,
1306
+ vendorPackages: {},
1307
+ vendorVersionLine: line
1308
+ });
1309
+ }
1310
+ }
1311
+ if (accepted.length === 0) {
1312
+ return {
1313
+ ok: false,
1314
+ // Report the FIRST real rejection's own status, not a blanket one.
1315
+ status: rejections[0]?.status ?? "system-runtime-not-installed",
1316
+ message: `no acceptable ${manifest.harnessId} installation was found in ${searchPaths.join(", ")}.` + (rejections.length ? ` Rejected: ${rejections.map((r) => r.message).join("; ")}.` : "") + ` Inspector never installs or upgrades a harness during a session.`
1317
+ };
1318
+ }
1319
+ if (accepted.length > 1) {
1320
+ const distinct = new Set(accepted.map((r) => r.rootPath));
1321
+ if (distinct.size > 1) {
1322
+ return {
1323
+ ok: false,
1324
+ status: "system-runtime-ambiguous",
1325
+ message: `more than one ${manifest.harnessId} installation is acceptable (${[...distinct].join(
1326
+ ", "
1327
+ )}). Remove or disambiguate them: consent must name exactly one runtime.`
1328
+ };
1329
+ }
1330
+ }
1331
+ return { ok: true, runtime: accepted[0] };
1332
+ }
1333
+ async function detectSnapshotDrift(snapshot) {
1334
+ const recorded = new Map(snapshot.entries.map((e) => [e.path, e]));
1335
+ let seen = 0;
1336
+ const walk = async (dir) => {
1337
+ const entries = await readdir2(dir, { withFileTypes: true });
1338
+ for (const entry of entries) {
1339
+ const full = join(dir, entry.name);
1340
+ const rel = relative(snapshot.root, full).split(sep).join("/");
1341
+ if (entry.isSymbolicLink()) return `a symlink appeared at ${rel}`;
1342
+ if (entry.isDirectory()) {
1343
+ const nested = await walk(full);
1344
+ if (nested !== null) return nested;
1345
+ continue;
1346
+ }
1347
+ if (!entry.isFile()) return `a non-regular file appeared at ${rel}`;
1348
+ const expected = recorded.get(rel);
1349
+ if (expected === void 0) return `an unexpected file appeared at ${rel}`;
1350
+ seen += 1;
1351
+ const info = await stat(full);
1352
+ if (info.size !== expected.size || info.mtimeMs !== expected.mtimeMs || info.ctimeMs !== expected.ctimeMs || Number(info.ino) !== expected.ino || info.mode !== expected.mode) {
1353
+ return `${rel} was modified`;
1354
+ }
1355
+ if (isRehashedOnRevalidate(rel)) {
1356
+ const content = await readFile2(full);
1357
+ const fileDigest = createHash("sha256").update(content).digest("hex");
1358
+ if (fileDigest !== snapshot.executableDigests[rel]) {
1359
+ return `${rel} was rewritten in place`;
1360
+ }
1361
+ }
1362
+ }
1363
+ return null;
1364
+ };
1365
+ const reason = await walk(snapshot.root);
1366
+ if (reason !== null) return reason;
1367
+ if (seen !== snapshot.entries.length) {
1368
+ return `${snapshot.entries.length - seen} file(s) went missing`;
1369
+ }
1370
+ return null;
1371
+ }
1372
+ async function revalidateRuntime(runtime) {
1373
+ try {
1374
+ if (runtime.source === "managed-bundle") {
1375
+ const snapshot = verifiedRuntimeCache.get(
1376
+ verificationCacheKey(runtime.rootPath, runtime.digest)
1377
+ );
1378
+ if (snapshot === void 0) {
1379
+ const verification = await verifyRuntime(
1380
+ runtime.rootPath,
1381
+ runtime.digest
1382
+ );
1383
+ if (!verification.ok) {
1384
+ return {
1385
+ ok: false,
1386
+ message: verification.reason === "digest-mismatch" ? `the ${runtime.harnessId} runtime bundle changed after consent was granted (expected ${runtime.digest}, found ${verification.digest})` : `the ${runtime.harnessId} runtime could not be re-verified: ${verification.message}`
1387
+ };
1388
+ }
1389
+ return { ok: true };
1390
+ }
1391
+ const drift = await detectSnapshotDrift(snapshot);
1392
+ if (drift === null) return { ok: true };
1393
+ verifiedRuntimeCache.delete(
1394
+ verificationCacheKey(runtime.rootPath, runtime.digest)
1395
+ );
1396
+ const reverified = await verifyRuntime(runtime.rootPath, runtime.digest);
1397
+ if (reverified.ok) return { ok: true };
1398
+ return {
1399
+ ok: false,
1400
+ message: reverified.reason === "digest-mismatch" ? `the ${runtime.harnessId} runtime bundle changed after consent was granted (${drift}; expected ${runtime.digest}, found ${reverified.digest})` : `the ${runtime.harnessId} runtime could not be re-verified: ${reverified.message}`
1401
+ };
1402
+ }
1403
+ const content = await readFile2(runtime.launcherPath);
1404
+ const digest = `sha256:${createHash("sha256").update(content).digest("hex")}`;
1405
+ if (digest !== runtime.digest) {
1406
+ return {
1407
+ ok: false,
1408
+ message: `the ${runtime.harnessId} executable at ${runtime.launcherPath} was replaced after consent was granted`
1409
+ };
1410
+ }
1411
+ return { ok: true };
1412
+ } catch (error) {
1413
+ return {
1414
+ ok: false,
1415
+ message: `the ${runtime.harnessId} runtime could not be re-verified: ${error instanceof Error ? error.message : String(error)}`
1416
+ };
1417
+ }
1418
+ }
1419
+
1420
+ // server/utils/harness/local/runtime-install.ts
1421
+ var MAX_ARCHIVE_BYTES = 1500 * 1024 * 1024;
1422
+ var MAX_EXTRACTED_BYTES = 2 * 1024 * 1024 * 1024;
1423
+ var MAX_MANIFEST_BYTES = 1024 * 1024;
1424
+ function runtimeInstallRoot() {
1425
+ const override = process.env.MCPJAM_RUNTIME_ROOT;
1426
+ if (override && override.trim().length > 0) return override.trim();
1427
+ return join2(homedir(), ".mcpjam", "harness-local", "runtime");
1428
+ }
1429
+ function packVersionRoot(packVersion, target = localPackTarget()) {
1430
+ return join2(targetInstallRoot(target), packVersion);
1431
+ }
1432
+ function targetInstallRoot(target) {
1433
+ return join2(runtimeInstallRoot(), target ?? packPlatformKey());
1434
+ }
1435
+ function expectedPackFor(harnessId, target) {
1436
+ const override = developmentPackExpectation();
1437
+ if (override !== null) return override;
1438
+ const record = PACK_RECORDS[harnessId]?.[target];
1439
+ if (record === void 0) return null;
1440
+ return { packVersion: record.packVersion, treeDigest: record.treeDigest };
1441
+ }
1442
+ function developmentPackExpectation() {
1443
+ const source = process.env.MCPJAM_LOCAL_HARNESS_PACK_SOURCE;
1444
+ const expected = process.env.MCPJAM_LOCAL_HARNESS_EXPECTED_PACK;
1445
+ if (!source || !expected) return null;
1446
+ const separator = expected.indexOf(":");
1447
+ if (separator <= 0) return null;
1448
+ const packVersion = expected.slice(0, separator).trim();
1449
+ const treeDigest = expected.slice(separator + 1).trim();
1450
+ if (packVersion.length === 0) return null;
1451
+ if (!/^sha256:[0-9a-f]{64}$/.test(treeDigest)) return null;
1452
+ return { packVersion, treeDigest };
1453
+ }
1454
+ function packSourceFor(packVersion, platformKey) {
1455
+ const override = process.env.MCPJAM_LOCAL_HARNESS_PACK_SOURCE;
1456
+ if (override && override.trim().length > 0) {
1457
+ const value = override.trim();
1458
+ return /^https?:\/\//.test(value) ? { kind: "url", location: value } : { kind: "file", location: value.replace(/^file:\/\//, "") };
1459
+ }
1460
+ return {
1461
+ kind: "url",
1462
+ location: `https://github.com/MCPJam/inspector/releases/download/v${packVersion}/local-harness-pack-${platformKey}-${packVersion}.tar.gz`
1463
+ };
1464
+ }
1465
+ function packPlatformKey(platform = process.platform, arch = process.arch) {
1466
+ return `${platform}-${arch}`;
1467
+ }
1468
+ async function readRuntimeInstallStatus(args) {
1469
+ const platform = currentLocalPlatform(args.platform ?? process.platform);
1470
+ if (platform === null) {
1471
+ return {
1472
+ state: "unsupported-platform",
1473
+ message: `${args.platform ?? process.platform} has no local harness runtime`
1474
+ };
1475
+ }
1476
+ const target = localPackTarget(args.platform, args.arch);
1477
+ const expected = target === null ? null : expectedPackFor(args.harnessId, target);
1478
+ if (expected === null) {
1479
+ return {
1480
+ state: "unsupported-platform",
1481
+ message: `no ${args.harnessId} runtime pack has been built for ${packPlatformKey(args.platform, args.arch)}`
1482
+ };
1483
+ }
1484
+ const inFlight = active.get(expected.packVersion);
1485
+ if (inFlight !== void 0) return inFlight.status;
1486
+ const root = packVersionRoot(expected.packVersion, target);
1487
+ try {
1488
+ const marker = JSON.parse(
1489
+ await readFile3(join2(root, INSTALL_MARKER), "utf8")
1490
+ );
1491
+ if (marker.treeDigest !== expected.treeDigest) {
1492
+ return {
1493
+ state: "corrupt",
1494
+ packVersion: expected.packVersion,
1495
+ message: "the installed runtime pack does not match the digest this Inspector expects; reinstall it"
1496
+ };
1497
+ }
1498
+ await stat2(join2(root, args.harnessId));
1499
+ return {
1500
+ state: "ready",
1501
+ packVersion: expected.packVersion,
1502
+ runtimeRoot: root,
1503
+ digest: expected.treeDigest
1504
+ };
1505
+ } catch {
1506
+ return { state: "absent", packVersion: expected.packVersion };
1507
+ }
1508
+ }
1509
+ var INSTALL_MARKER = ".mcpjam-pack-installed.json";
1510
+ var active = /* @__PURE__ */ new Map();
1511
+ async function installRuntimePack(options) {
1512
+ const platform = currentLocalPlatform(options.platform ?? process.platform);
1513
+ if (platform === null) {
1514
+ return {
1515
+ state: "unsupported-platform",
1516
+ message: `${options.platform ?? process.platform} has no local harness runtime`
1517
+ };
1518
+ }
1519
+ const target = localPackTarget(options.platform, options.arch);
1520
+ const expected = target === null ? null : expectedPackFor(options.harnessId, target);
1521
+ if (expected === null) {
1522
+ return {
1523
+ state: "unsupported-platform",
1524
+ message: `no ${options.harnessId} runtime pack has been built for ${packPlatformKey(options.platform, options.arch)}`
1525
+ };
1526
+ }
1527
+ const existing = active.get(expected.packVersion);
1528
+ if (existing !== void 0) return existing.promise;
1529
+ const record = {
1530
+ status: { state: "downloading", packVersion: expected.packVersion, percent: 0 },
1531
+ promise: Promise.resolve({
1532
+ state: "absent",
1533
+ packVersion: expected.packVersion
1534
+ })
1535
+ };
1536
+ active.set(expected.packVersion, record);
1537
+ const setStatus = (status) => {
1538
+ record.status = status;
1539
+ options.onProgress?.(status);
1540
+ };
1541
+ record.promise = (async () => {
1542
+ try {
1543
+ return await performInstall({
1544
+ ...options,
1545
+ platform,
1546
+ expected,
1547
+ setStatus
1548
+ });
1549
+ } catch (error) {
1550
+ const message = error instanceof Error ? error.message : String(error);
1551
+ logger.warn("[local-harness] runtime pack install failed", { message });
1552
+ return {
1553
+ state: "corrupt",
1554
+ packVersion: expected.packVersion,
1555
+ message
1556
+ };
1557
+ } finally {
1558
+ active.delete(expected.packVersion);
1559
+ }
1560
+ })();
1561
+ return record.promise;
1562
+ }
1563
+ async function performInstall(args) {
1564
+ const { expected, setStatus } = args;
1565
+ const platformKey = packPlatformKey(args.platform, args.arch);
1566
+ const target = localPackTarget(args.platform, args.arch);
1567
+ const versionRoot = packVersionRoot(expected.packVersion, target);
1568
+ const installRoot = targetInstallRoot(target);
1569
+ await mkdir(installRoot, { recursive: true, mode: 448 });
1570
+ const staging = join2(installRoot, `.mcpjam-tmp-${randomUUID()}`);
1571
+ await mkdir(staging, { recursive: true, mode: 448 });
1572
+ try {
1573
+ const source = packSourceFor(expected.packVersion, platformKey);
1574
+ const archivePath = join2(staging, "pack.tar.gz");
1575
+ setStatus({ state: "downloading", packVersion: expected.packVersion, percent: 0 });
1576
+ const archiveSha = await fetchArchive({
1577
+ source,
1578
+ destination: archivePath,
1579
+ onPercent: (percent) => setStatus({
1580
+ state: "downloading",
1581
+ packVersion: expected.packVersion,
1582
+ percent
1583
+ }),
1584
+ ...args.signal ? { signal: args.signal } : {}
1585
+ });
1586
+ setStatus({ state: "verifying", packVersion: expected.packVersion });
1587
+ const manifest = await loadAndVerifyManifest({
1588
+ source,
1589
+ packVersion: expected.packVersion,
1590
+ platformKey,
1591
+ staging
1592
+ });
1593
+ if (manifest !== null && manifest.archive?.sha256 !== void 0) {
1594
+ if (manifest.archive.sha256 !== archiveSha) {
1595
+ throw new Error(
1596
+ `the downloaded runtime pack archive does not match its signed manifest (expected ${manifest.archive.sha256}, got ${archiveSha})`
1597
+ );
1598
+ }
1599
+ }
1600
+ const extractRoot = join2(staging, "extracted");
1601
+ await mkdir(extractRoot, { recursive: true, mode: 448 });
1602
+ await extractTar({
1603
+ file: archivePath,
1604
+ cwd: extractRoot,
1605
+ // No links of any kind survive extraction, and nothing may be written
1606
+ // outside `cwd`. The digest would refuse a symlink later; refusing it
1607
+ // here means it never touches the disk.
1608
+ filter: (path, entry) => {
1609
+ const type = entry.type;
1610
+ if (type !== "File" && type !== "Directory") return false;
1611
+ return !path.split("/").includes("..");
1612
+ },
1613
+ preservePaths: false,
1614
+ strict: true
1615
+ });
1616
+ const packRoot = join2(extractRoot, args.harnessId);
1617
+ const info = await stat2(packRoot).catch(() => null);
1618
+ if (info === null || !info.isDirectory()) {
1619
+ throw new Error(
1620
+ `the runtime pack archive has no ${args.harnessId} directory at its root`
1621
+ );
1622
+ }
1623
+ await assertExtractedSize(packRoot);
1624
+ const digest = await computeTreeDigest(packRoot);
1625
+ if (manifest !== null && digest !== manifest.treeDigest) {
1626
+ throw new Error(
1627
+ `the extracted runtime pack does not match its signed manifest (expected ${manifest.treeDigest}, got ${digest})`
1628
+ );
1629
+ }
1630
+ if (digest !== expected.treeDigest) {
1631
+ throw new Error(
1632
+ `the runtime pack does not match the digest this Inspector was built with (expected ${expected.treeDigest}, got ${digest})`
1633
+ );
1634
+ }
1635
+ if (args.platform === "darwin") await clearQuarantine(extractRoot);
1636
+ await writeFile(
1637
+ join2(extractRoot, INSTALL_MARKER),
1638
+ `${JSON.stringify(
1639
+ {
1640
+ packVersion: expected.packVersion,
1641
+ harnessId: args.harnessId,
1642
+ platform: platformKey,
1643
+ treeDigest: digest,
1644
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
1645
+ },
1646
+ null,
1647
+ 2
1648
+ )}
1649
+ `,
1650
+ { mode: 384 }
1651
+ );
1652
+ await rm(versionRoot, { recursive: true, force: true });
1653
+ await rename(extractRoot, versionRoot);
1654
+ clearRuntimeVerificationCache();
1655
+ await sweepOtherVersions(installRoot, expected.packVersion);
1656
+ logger.info("[local-harness] runtime pack installed", {
1657
+ packVersion: expected.packVersion,
1658
+ platform: platformKey
1659
+ });
1660
+ return {
1661
+ state: "ready",
1662
+ packVersion: expected.packVersion,
1663
+ runtimeRoot: versionRoot,
1664
+ digest
1665
+ };
1666
+ } finally {
1667
+ await rm(staging, { recursive: true, force: true }).catch(() => {
1668
+ });
1669
+ }
1670
+ }
1671
+ async function assertExtractedSize(root) {
1672
+ let bytes = 0;
1673
+ const walk = async (dir) => {
1674
+ for (const entry of await readdir3(dir, { withFileTypes: true })) {
1675
+ const full = join2(dir, entry.name);
1676
+ if (entry.isDirectory()) {
1677
+ await walk(full);
1678
+ continue;
1679
+ }
1680
+ bytes += (await stat2(full)).size;
1681
+ if (bytes > MAX_EXTRACTED_BYTES) {
1682
+ throw new Error("the extracted runtime pack exceeds its size ceiling");
1683
+ }
1684
+ }
1685
+ };
1686
+ await walk(root);
1687
+ }
1688
+ async function sweepOtherVersions(installRoot, keepVersion) {
1689
+ let entries;
1690
+ try {
1691
+ entries = await readdir3(installRoot);
1692
+ } catch {
1693
+ return;
1694
+ }
1695
+ for (const entry of entries) {
1696
+ if (entry === keepVersion) continue;
1697
+ const path = join2(installRoot, entry);
1698
+ if (entry.startsWith(".mcpjam-tmp-")) {
1699
+ await rm(path, { recursive: true, force: true }).catch(() => {
1700
+ });
1701
+ continue;
1702
+ }
1703
+ if (!/^[\w.-]+$/.test(entry)) continue;
1704
+ const owned = await readFile3(join2(path, INSTALL_MARKER), "utf8").then(
1705
+ () => true,
1706
+ () => false
1707
+ );
1708
+ if (!owned) continue;
1709
+ await rm(path, { recursive: true, force: true }).catch(() => {
1710
+ });
1711
+ }
1712
+ }
1713
+ async function clearQuarantine(root) {
1714
+ const { execFile: execFile3 } = await import("child_process");
1715
+ await new Promise((resolvePromise) => {
1716
+ execFile3(
1717
+ "/usr/bin/xattr",
1718
+ ["-r", "-d", "com.apple.quarantine", root],
1719
+ { timeout: 6e4 },
1720
+ () => resolvePromise()
1721
+ );
1722
+ });
1723
+ }
1724
+ async function loadAndVerifyManifest(args) {
1725
+ const stem = `local-harness-pack-${args.platformKey}-${args.packVersion}`;
1726
+ const manifestName = `${stem}.manifest.json`;
1727
+ const signatureName = `${manifestName}.sig`;
1728
+ let manifestBytes;
1729
+ let signature;
1730
+ try {
1731
+ if (args.source.kind === "file") {
1732
+ const dir = args.source.location.endsWith(".tar.gz") ? args.source.location.slice(0, -basename(args.source.location).length) : args.source.location;
1733
+ manifestBytes = await readFile3(join2(dir, manifestName));
1734
+ signature = await readFile3(join2(dir, signatureName), "utf8");
1735
+ } else {
1736
+ const base = args.source.location.slice(
1737
+ 0,
1738
+ args.source.location.lastIndexOf("/") + 1
1739
+ );
1740
+ manifestBytes = Buffer.from(
1741
+ await (await fetchOrThrow(base + manifestName)).arrayBuffer()
1742
+ );
1743
+ signature = await (await fetchOrThrow(base + signatureName)).text();
1744
+ }
1745
+ } catch (error) {
1746
+ if (args.source.kind === "file") {
1747
+ logger.warn(
1748
+ "[local-harness] local pack source has no signed manifest; installing on the digest alone",
1749
+ { message: error instanceof Error ? error.message : String(error) }
1750
+ );
1751
+ return null;
1752
+ }
1753
+ throw new Error(
1754
+ `the runtime pack has no signed manifest alongside it, so it cannot be shown to have come from MCPJam`
1755
+ );
1756
+ }
1757
+ if (manifestBytes.length > MAX_MANIFEST_BYTES) {
1758
+ throw new Error("the runtime pack manifest is implausibly large");
1759
+ }
1760
+ const verified = verifyPackManifestSignature(manifestBytes, signature);
1761
+ if (!verified.ok) {
1762
+ if (args.source.kind === "file") {
1763
+ logger.warn(
1764
+ "[local-harness] local pack manifest signature not verified; installing on the digest alone",
1765
+ { reason: verified.reason }
1766
+ );
1767
+ return null;
1768
+ }
1769
+ throw new Error(verified.message);
1770
+ }
1771
+ const manifest = JSON.parse(manifestBytes.toString("utf8"));
1772
+ if (manifest.packVersion !== args.packVersion) {
1773
+ throw new Error(
1774
+ `the signed manifest is for pack version ${manifest.packVersion}, not ${args.packVersion}`
1775
+ );
1776
+ }
1777
+ if (manifest.platform !== args.platformKey) {
1778
+ throw new Error(
1779
+ `the signed manifest is for ${manifest.platform}, not ${args.platformKey}`
1780
+ );
1781
+ }
1782
+ return manifest;
1783
+ }
1784
+ async function fetchOrThrow(url) {
1785
+ const response = await fetch(url);
1786
+ if (!response.ok) {
1787
+ throw new Error(`${url} responded ${response.status}`);
1788
+ }
1789
+ return response;
1790
+ }
1791
+ async function fetchArchive(args) {
1792
+ const hash = createHash2("sha256");
1793
+ let received = 0;
1794
+ let total = 0;
1795
+ const track = new TransformStream({
1796
+ transform(chunk, controller) {
1797
+ hash.update(chunk);
1798
+ received += chunk.byteLength;
1799
+ if (received > MAX_ARCHIVE_BYTES) {
1800
+ throw new Error("the runtime pack archive exceeds its size ceiling");
1801
+ }
1802
+ if (total > 0) {
1803
+ args.onPercent(Math.min(99, Math.floor(received / total * 100)));
1804
+ }
1805
+ controller.enqueue(chunk);
1806
+ }
1807
+ });
1808
+ if (args.source.kind === "file") {
1809
+ const path = args.source.location;
1810
+ const info = await stat2(path);
1811
+ total = info.size;
1812
+ if (total > MAX_ARCHIVE_BYTES) {
1813
+ throw new Error("the runtime pack archive exceeds its size ceiling");
1814
+ }
1815
+ await pipeline(
1816
+ createReadStream(path),
1817
+ async function* (chunks) {
1818
+ for await (const chunk of chunks) {
1819
+ const buffer = chunk;
1820
+ hash.update(buffer);
1821
+ received += buffer.byteLength;
1822
+ args.onPercent(Math.min(99, Math.floor(received / total * 100)));
1823
+ yield buffer;
1824
+ }
1825
+ },
1826
+ createWriteStream(args.destination, { mode: 384 })
1827
+ );
1828
+ return hash.digest("hex");
1829
+ }
1830
+ const response = await fetch(args.source.location, {
1831
+ ...args.signal ? { signal: args.signal } : {}
1832
+ });
1833
+ if (!response.ok || response.body === null) {
1834
+ throw new Error(
1835
+ `the runtime pack could not be downloaded: ${args.source.location} responded ${response.status}`
1836
+ );
1837
+ }
1838
+ const declared = Number(response.headers.get("content-length") ?? "0");
1839
+ if (declared > MAX_ARCHIVE_BYTES) {
1840
+ throw new Error("the runtime pack archive exceeds its size ceiling");
1841
+ }
1842
+ total = declared;
1843
+ await pipeline(
1844
+ Readable.fromWeb(
1845
+ response.body.pipeThrough(track)
1846
+ ),
1847
+ createWriteStream(args.destination, { mode: 384 })
1848
+ );
1849
+ args.onPercent(100);
1850
+ return hash.digest("hex");
1851
+ }
1852
+ function reportLocalHarnessRuntimeStatusInBackground() {
1853
+ void (async () => {
1854
+ try {
1855
+ const status = await readRuntimeInstallStatus({
1856
+ harnessId: "claude-code"
1857
+ });
1858
+ if (status.state === "ready") {
1859
+ logger.info("[local-harness] runtime pack present", {
1860
+ packVersion: status.packVersion
1861
+ });
1862
+ return;
1863
+ }
1864
+ logger.debug("[local-harness] no runtime pack installed", {
1865
+ state: status.state,
1866
+ expected: EXPECTED_PACK_VERSION || "(none built)"
1867
+ });
1868
+ } catch {
1869
+ }
1870
+ })();
1871
+ }
1872
+
1873
+ export {
1874
+ resolveEnvironment,
1875
+ resolveAppVersion,
1876
+ resolveRelease,
1877
+ markOriginCaptureHandled,
1878
+ ensureStampable,
1879
+ isOriginCaptureHandled,
1880
+ captureOriginErrorToSentry,
1881
+ logger,
1882
+ PACK_TREE_DIGESTS,
1883
+ LOCAL_HARNESS_POLICY_VERSION,
1884
+ LOCAL_ISOLATION_POLICY_VERSION,
1885
+ currentLocalPlatform,
1886
+ probeProcess,
1887
+ readProcessBirthIdentity,
1888
+ supportsOwnershipProof,
1889
+ listGroupMembers,
1890
+ terminateOwnedProcess,
1891
+ probeProcessGroup,
1892
+ readProcessGroupId,
1893
+ terminateOwnedProcessGroup,
1894
+ resolveManagedBundle,
1895
+ resolveSystemInstall,
1896
+ revalidateRuntime,
1897
+ runtimeInstallRoot,
1898
+ readRuntimeInstallStatus,
1899
+ installRuntimePack,
1900
+ reportLocalHarnessRuntimeStatusInBackground
1901
+ };
1902
+ //# sourceMappingURL=chunk-7TOT3ZWN.js.map