@moxt-ai/mobius 0.0.369 → 0.0.371

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 (45) hide show
  1. package/dist/{chunk-3GFBCVGZ.js → chunk-23EBYYOU.js} +68 -9
  2. package/dist/{chunk-3GFBCVGZ.js.map → chunk-23EBYYOU.js.map} +3 -3
  3. package/dist/{chunk-2RHEJMMU.js → chunk-MS57BIBB.js} +2 -2
  4. package/dist/{chunk-2RHEJMMU.js.map → chunk-MS57BIBB.js.map} +1 -1
  5. package/dist/{chunk-PG7W3R2P.js → chunk-XQEFITZA.js} +2 -2
  6. package/dist/cli/handler/diagnose/config.d.ts +23 -0
  7. package/dist/cli/handler/diagnose/config.d.ts.map +1 -0
  8. package/dist/cli/handler/diagnose/config.js +55 -0
  9. package/dist/cli/handler/diagnose/config.js.map +1 -0
  10. package/dist/cli/handler/diagnose/index.d.ts +4 -0
  11. package/dist/cli/handler/diagnose/index.d.ts.map +1 -0
  12. package/dist/cli/handler/diagnose/index.js +314 -0
  13. package/dist/cli/handler/diagnose/index.js.map +1 -0
  14. package/dist/cli/handler/diagnose/routes.d.ts +16 -0
  15. package/dist/cli/handler/diagnose/routes.d.ts.map +1 -0
  16. package/dist/cli/handler/diagnose/routes.js +89 -0
  17. package/dist/cli/handler/diagnose/routes.js.map +1 -0
  18. package/dist/cli/handler/diagnose/worker.d.ts +3 -0
  19. package/dist/cli/handler/diagnose/worker.d.ts.map +1 -0
  20. package/dist/cli/handler/diagnose/worker.js +95 -0
  21. package/dist/cli/handler/diagnose/worker.js.map +1 -0
  22. package/dist/cli/index.d.ts.map +1 -1
  23. package/dist/cli/index.js +2 -0
  24. package/dist/cli/index.js.map +1 -1
  25. package/dist/{cli-5UCO7ZEY.js → cli-FDG4YLBN.js} +626 -39
  26. package/dist/{cli-5UCO7ZEY.js.map → cli-FDG4YLBN.js.map} +4 -4
  27. package/dist/cli.js +3 -3
  28. package/dist/engine/runtime/relay/connect.d.ts +1 -1
  29. package/dist/engine/runtime/relay/connect.d.ts.map +1 -1
  30. package/dist/engine/runtime/relay/connect.js +35 -2
  31. package/dist/engine/runtime/relay/connect.js.map +1 -1
  32. package/dist/engine/runtime/relay/diagnostics.d.ts +2 -0
  33. package/dist/engine/runtime/relay/diagnostics.d.ts.map +1 -1
  34. package/dist/engine/runtime/relay/diagnostics.js +26 -3
  35. package/dist/engine/runtime/relay/diagnostics.js.map +1 -1
  36. package/dist/engine/runtime/relay/run-relay-connection.d.ts.map +1 -1
  37. package/dist/engine/runtime/relay/run-relay-connection.js +3 -1
  38. package/dist/engine/runtime/relay/run-relay-connection.js.map +1 -1
  39. package/dist/engine.js +2 -2
  40. package/dist/storage/diagnostics/relay-probe-files.d.ts +7 -0
  41. package/dist/storage/diagnostics/relay-probe-files.d.ts.map +1 -0
  42. package/dist/storage/diagnostics/relay-probe-files.js +40 -0
  43. package/dist/storage/diagnostics/relay-probe-files.js.map +1 -0
  44. package/package.json +1 -1
  45. /package/dist/{chunk-PG7W3R2P.js.map → chunk-XQEFITZA.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  require_semver
3
- } from "./chunk-PG7W3R2P.js";
3
+ } from "./chunk-XQEFITZA.js";
4
4
  import {
5
5
  BoundedLineLog,
6
6
  MissingAdapter,
@@ -17,6 +17,7 @@ import {
17
17
  canonicalPath,
18
18
  decodeIdentifier,
19
19
  decodeRuntimeUpgradeVersion,
20
+ describeRelayFailure,
20
21
  discoverLocalAgentInventory,
21
22
  ensurePrivateDirectory,
22
23
  external_exports,
@@ -35,13 +36,13 @@ import {
35
36
  unhandledRejectionLogFilename,
36
37
  validateLocalPath,
37
38
  validatePairingId
38
- } from "./chunk-3GFBCVGZ.js";
39
+ } from "./chunk-23EBYYOU.js";
39
40
  import {
40
41
  __callDispose,
41
42
  __toESM,
42
43
  __using,
43
44
  package_default
44
- } from "./chunk-2RHEJMMU.js";
45
+ } from "./chunk-MS57BIBB.js";
45
46
 
46
47
  // ../../node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/error.js
47
48
  var CommanderError = class extends Error {
@@ -1343,9 +1344,9 @@ var Command = class _Command extends EventEmitter {
1343
1344
  * @param {object} [configuration] - configuration options
1344
1345
  * @return {(Command | object)} `this` command for chaining, or stored configuration
1345
1346
  */
1346
- configureHelp(configuration) {
1347
- if (configuration === void 0) return this._helpConfiguration;
1348
- this._helpConfiguration = configuration;
1347
+ configureHelp(configuration2) {
1348
+ if (configuration2 === void 0) return this._helpConfiguration;
1349
+ this._helpConfiguration = configuration2;
1349
1350
  return this;
1350
1351
  }
1351
1352
  /**
@@ -1370,11 +1371,11 @@ var Command = class _Command extends EventEmitter {
1370
1371
  * @param {object} [configuration] - configuration options
1371
1372
  * @return {(Command | object)} `this` command for chaining, or stored configuration
1372
1373
  */
1373
- configureOutput(configuration) {
1374
- if (configuration === void 0) return this._outputConfiguration;
1374
+ configureOutput(configuration2) {
1375
+ if (configuration2 === void 0) return this._outputConfiguration;
1375
1376
  this._outputConfiguration = {
1376
1377
  ...this._outputConfiguration,
1377
- ...configuration
1378
+ ...configuration2
1378
1379
  };
1379
1380
  return this;
1380
1381
  }
@@ -3984,7 +3985,7 @@ function controlSocketPath(stateDirectory, endpoint = "manager") {
3984
3985
  }
3985
3986
  async function readSocketMessage(socket, signal) {
3986
3987
  signal.throwIfAborted();
3987
- return await new Promise((resolve5, reject) => {
3988
+ return await new Promise((resolve6, reject) => {
3988
3989
  let encoded = "";
3989
3990
  const finish = (error, value) => {
3990
3991
  signal.removeEventListener("abort", abort);
@@ -3992,7 +3993,7 @@ async function readSocketMessage(socket, signal) {
3992
3993
  socket.removeAllListeners("error");
3993
3994
  socket.removeAllListeners("end");
3994
3995
  if (error === void 0) {
3995
- resolve5(value);
3996
+ resolve6(value);
3996
3997
  } else {
3997
3998
  reject(error);
3998
3999
  }
@@ -4028,14 +4029,14 @@ async function sendLocalRequest(stateDirectory, endpoint, command, signal) {
4028
4029
  deadline.throwIfAborted();
4029
4030
  const socket = createConnection(controlSocketPath(stateDirectory, endpoint));
4030
4031
  try {
4031
- await new Promise((resolve5, reject) => {
4032
+ await new Promise((resolve6, reject) => {
4032
4033
  const abort = () => {
4033
4034
  socket.destroy();
4034
4035
  reject(deadline.reason);
4035
4036
  };
4036
4037
  socket.once("connect", () => {
4037
4038
  deadline.removeEventListener("abort", abort);
4038
- resolve5();
4039
+ resolve6();
4039
4040
  });
4040
4041
  socket.once("error", reject);
4041
4042
  deadline.addEventListener("abort", abort, { once: true });
@@ -4089,21 +4090,21 @@ async function serveLocalControl(stateDirectory, endpoint, signal, handle, onLis
4089
4090
  });
4090
4091
  try {
4091
4092
  await platform.prepareControlSocket(path2);
4092
- await new Promise((resolve5, reject) => {
4093
- server.once("listening", resolve5);
4093
+ await new Promise((resolve6, reject) => {
4094
+ server.once("listening", resolve6);
4094
4095
  server.once("error", reject);
4095
4096
  server.listen(path2);
4096
4097
  });
4097
4098
  await platform.secureControlSocket(path2);
4098
4099
  await onListening();
4099
4100
  if (!signal.aborted) {
4100
- await new Promise((resolve5) => signal.addEventListener("abort", () => resolve5(), { once: true }));
4101
+ await new Promise((resolve6) => signal.addEventListener("abort", () => resolve6(), { once: true }));
4101
4102
  }
4102
4103
  } finally {
4103
4104
  for (const socket of sockets) {
4104
4105
  socket.destroy();
4105
4106
  }
4106
- await new Promise((resolve5) => server.close(() => resolve5()));
4107
+ await new Promise((resolve6) => server.close(() => resolve6()));
4107
4108
  await Promise.allSettled(requests);
4108
4109
  await platform.removeControlSocket(path2).catch(() => void 0);
4109
4110
  }
@@ -4893,13 +4894,13 @@ async function waitForManagerExit(stateDirectory, signal) {
4893
4894
  if (!await managerLockExists(stateDirectory)) {
4894
4895
  return;
4895
4896
  }
4896
- await new Promise((resolve5, reject) => {
4897
+ await new Promise((resolve6, reject) => {
4897
4898
  const timeout = setTimeout(finish, 20);
4898
4899
  const abort = () => finish(deadline.reason);
4899
4900
  function finish(cause) {
4900
4901
  clearTimeout(timeout);
4901
4902
  deadline.removeEventListener("abort", abort);
4902
- cause === void 0 ? resolve5() : reject(cause);
4903
+ cause === void 0 ? resolve6() : reject(cause);
4903
4904
  }
4904
4905
  deadline.addEventListener("abort", abort, { once: true });
4905
4906
  });
@@ -4963,13 +4964,13 @@ async function ensureManager(stateDirectory, launch, signal) {
4963
4964
  await launchManager(launch);
4964
4965
  managerStarted = true;
4965
4966
  }
4966
- await new Promise((resolve5, reject) => {
4967
+ await new Promise((resolve6, reject) => {
4967
4968
  const timeout = setTimeout(finish, 50);
4968
4969
  const abort = () => finish(signal.reason);
4969
4970
  function finish(cause2) {
4970
4971
  clearTimeout(timeout);
4971
4972
  signal.removeEventListener("abort", abort);
4972
- cause2 === void 0 ? resolve5() : reject(cause2);
4973
+ cause2 === void 0 ? resolve6() : reject(cause2);
4973
4974
  }
4974
4975
  signal.addEventListener("abort", abort, { once: true });
4975
4976
  });
@@ -5171,11 +5172,11 @@ async function checkDaemonReleaseUpgrade(signal, activity) {
5171
5172
  const _reservation = __using(_stack, reservation);
5172
5173
  signal.throwIfAborted();
5173
5174
  await requestInstalledDaemonReleaseSwitch(control.stateDirectory, launch, signal);
5174
- await new Promise((resolve5) => {
5175
+ await new Promise((resolve6) => {
5175
5176
  if (signal.aborted) {
5176
- resolve5();
5177
+ resolve6();
5177
5178
  } else {
5178
- signal.addEventListener("abort", () => resolve5(), { once: true });
5179
+ signal.addEventListener("abort", () => resolve6(), { once: true });
5179
5180
  }
5180
5181
  });
5181
5182
  } catch (_) {
@@ -5444,12 +5445,597 @@ function registerDaemonProcessLifecycleHandlers(program2, context) {
5444
5445
  registerUpgradeCommand(program2, context);
5445
5446
  }
5446
5447
 
5448
+ // src/cli/handler/diagnose/index.ts
5449
+ import { fork } from "node:child_process";
5450
+ import { join as join13, resolve as resolve3 } from "node:path";
5451
+
5452
+ // src/storage/diagnostics/relay-probe-files.ts
5453
+ import { mkdir, readFile as readFile6, rm as rm2, stat as stat2, writeFile as writeFile4 } from "node:fs/promises";
5454
+ import { join as join11 } from "node:path";
5455
+ var ProbeConfigurationFileError = class extends Error {
5456
+ code = "probe_config_invalid";
5457
+ };
5458
+ async function readPrivateProbeConfiguration(path2, signal) {
5459
+ signal.throwIfAborted();
5460
+ const metadata = await stat2(path2);
5461
+ if (!metadata.isFile() || metadata.size > 16384 || process.platform !== "win32" && (metadata.mode & 63) !== 0) {
5462
+ throw new ProbeConfigurationFileError(
5463
+ "The pairing configuration must be a private, small regular file (mode 0600)"
5464
+ );
5465
+ }
5466
+ try {
5467
+ return JSON.parse(await readFile6(path2, { encoding: "utf8", signal }));
5468
+ } catch {
5469
+ signal.throwIfAborted();
5470
+ throw new ProbeConfigurationFileError("The pairing configuration is not valid JSON");
5471
+ }
5472
+ }
5473
+ async function createProbeOutput(directory) {
5474
+ await mkdir(directory, { mode: 448 });
5475
+ }
5476
+ async function ensureProbeDirectory(directory) {
5477
+ await mkdir(directory, { recursive: true, mode: 448 });
5478
+ }
5479
+ async function writeProbeReport(directory, report) {
5480
+ await writeFile4(join11(directory, "report.json"), `${JSON.stringify(report, null, 2)}
5481
+ `, { mode: 384 });
5482
+ }
5483
+ async function writeProbeEvents(directory, events) {
5484
+ await writeFile4(join11(directory, "transport.jsonl"), events.map((event) => JSON.stringify(event)).join("\n"), {
5485
+ mode: 384
5486
+ });
5487
+ }
5488
+ async function removeProbeState(directory) {
5489
+ await rm2(join11(directory, "state"), { recursive: true, force: true });
5490
+ }
5491
+
5492
+ // src/cli/handler/diagnose/config.ts
5493
+ var pairing = external_exports.object({ pairingId: external_exports.string().min(1).max(128), pairingCode: external_exports.string().min(1).max(128) }).readonly();
5494
+ var configuration = external_exports.object({ relayOrigin: external_exports.url(), direct: pairing, proxy: pairing }).readonly();
5495
+ var RelayProbeError = class extends Error {
5496
+ code;
5497
+ constructor(code, message) {
5498
+ super(message);
5499
+ this.code = code;
5500
+ }
5501
+ };
5502
+ async function readProbeConfiguration(path2, signal) {
5503
+ const value = await readPrivateProbeConfiguration(path2, signal);
5504
+ const parsed = configuration.safeParse(value);
5505
+ if (!parsed.success) {
5506
+ throw new RelayProbeError(
5507
+ "probe_config_invalid",
5508
+ "Expected relayOrigin and direct/proxy pairingId and pairingCode"
5509
+ );
5510
+ }
5511
+ const config = parsed.data;
5512
+ const origin = new URL(config.relayOrigin);
5513
+ if (!["https:", "http:"].includes(origin.protocol) || origin.protocol === "http:" && !["127.0.0.1", "localhost"].includes(origin.hostname) || origin.username || origin.password || origin.search || origin.hash || origin.pathname !== "/") {
5514
+ throw new RelayProbeError(
5515
+ "probe_origin_invalid",
5516
+ "relayOrigin must use HTTPS (or loopback HTTP), without credentials or query parameters"
5517
+ );
5518
+ }
5519
+ if (config.direct.pairingId === config.proxy.pairingId || config.direct.pairingCode === config.proxy.pairingCode) {
5520
+ throw new RelayProbeError("probe_pairings_reused", "Direct and proxy probes require distinct, unused pairings");
5521
+ }
5522
+ return config;
5523
+ }
5524
+ function configuredProbeProxy() {
5525
+ const value = process.env["https_proxy"] || process.env["HTTPS_PROXY"] || process.env["http_proxy"] || process.env["HTTP_PROXY"];
5526
+ let proxy;
5527
+ try {
5528
+ proxy = new URL(value ?? "");
5529
+ } catch {
5530
+ throw new RelayProbeError("probe_proxy_missing", "Set HTTPS_PROXY or HTTP_PROXY to an HTTP or HTTPS proxy");
5531
+ }
5532
+ if (!["http:", "https:"].includes(proxy.protocol)) {
5533
+ throw new RelayProbeError("probe_proxy_invalid", "The comparison requires an HTTP or HTTPS proxy");
5534
+ }
5535
+ return proxy;
5536
+ }
5537
+ function failureCode(error) {
5538
+ const code = error instanceof Error ? Reflect.get(error, "code") : void 0;
5539
+ return typeof code === "string" && /^[A-Za-z0-9_-]{1,128}$/u.test(code) ? code : "probe_failed";
5540
+ }
5541
+
5542
+ // src/cli/handler/diagnose/routes.ts
5543
+ import { execFile } from "node:child_process";
5544
+ import { lookup } from "node:dns/promises";
5545
+ import { isIP } from "node:net";
5546
+ import { promisify } from "node:util";
5547
+ var execute = promisify(execFile);
5548
+ var routeEvidenceSchema = external_exports.object({
5549
+ hostname: external_exports.string(),
5550
+ addresses: external_exports.array(external_exports.string()),
5551
+ routes: external_exports.array(
5552
+ external_exports.object({ address: external_exports.string(), interface: external_exports.string(), gateway: external_exports.string(), tunnelInterface: external_exports.boolean() })
5553
+ ),
5554
+ failures: external_exports.array(external_exports.string()),
5555
+ omittedAddresses: external_exports.number().int().nonnegative()
5556
+ });
5557
+ async function addresses(hostname, signal) {
5558
+ if (isIP(hostname) !== 0) {
5559
+ return [hostname];
5560
+ }
5561
+ const aborted = Promise.withResolvers();
5562
+ const abort = () => aborted.reject(signal.reason);
5563
+ signal.addEventListener("abort", abort, { once: true });
5564
+ try {
5565
+ signal.throwIfAborted();
5566
+ const found = await Promise.race([lookup(hostname, { all: true }), aborted.promise]);
5567
+ return [...new Set(found.map((item) => item.address))];
5568
+ } finally {
5569
+ signal.removeEventListener("abort", abort);
5570
+ }
5571
+ }
5572
+ async function collectRouteEvidence(hostname, cancellation) {
5573
+ const signal = AbortSignal.any([cancellation, AbortSignal.timeout(5e3)]);
5574
+ const evidence = { hostname, addresses: [], routes: [], failures: [], omittedAddresses: 0 };
5575
+ try {
5576
+ const resolved = await addresses(hostname.replace(/^\[|\]$/gu, ""), signal);
5577
+ evidence.addresses.push(...resolved.slice(0, 8));
5578
+ evidence.omittedAddresses = Math.max(0, resolved.length - evidence.addresses.length);
5579
+ await Promise.all(
5580
+ evidence.addresses.map(async (address) => {
5581
+ try {
5582
+ let networkInterface = "";
5583
+ let gateway = "";
5584
+ if (process.platform === "darwin") {
5585
+ const { stdout } = await execute(
5586
+ "/sbin/route",
5587
+ ["-n", "get", ...isIP(address) === 6 ? ["-inet6"] : [], address],
5588
+ {
5589
+ signal,
5590
+ timeout: 2e3,
5591
+ maxBuffer: 32768
5592
+ }
5593
+ );
5594
+ networkInterface = stdout.match(/^\s*interface:\s*(\S+)/mu)?.[1] ?? "";
5595
+ gateway = stdout.match(/^\s*gateway:\s*(\S+)/mu)?.[1] ?? "";
5596
+ } else if (process.platform === "linux") {
5597
+ const { stdout } = await execute("ip", ["-json", "route", "get", address], {
5598
+ signal,
5599
+ timeout: 2e3,
5600
+ maxBuffer: 32768
5601
+ });
5602
+ const result = external_exports.array(external_exports.object({ dev: external_exports.string(), gateway: external_exports.string().optional() })).parse(JSON.parse(stdout));
5603
+ networkInterface = result[0]?.dev ?? "";
5604
+ gateway = result[0]?.gateway ?? "";
5605
+ } else {
5606
+ evidence.failures.push("route_platform_unsupported");
5607
+ return;
5608
+ }
5609
+ if (networkInterface.length === 0) {
5610
+ throw new RelayProbeError(
5611
+ "route_interface_unavailable",
5612
+ "The route did not identify an interface"
5613
+ );
5614
+ }
5615
+ evidence.routes.push({
5616
+ address,
5617
+ interface: networkInterface,
5618
+ gateway,
5619
+ tunnelInterface: /^(?:utun|tun|tap|wg|tailscale)/u.test(networkInterface)
5620
+ });
5621
+ } catch (error) {
5622
+ cancellation.throwIfAborted();
5623
+ evidence.failures.push(failureCode(error));
5624
+ }
5625
+ })
5626
+ );
5627
+ } catch (error) {
5628
+ cancellation.throwIfAborted();
5629
+ evidence.failures.push(failureCode(error));
5630
+ }
5631
+ return evidence;
5632
+ }
5633
+
5634
+ // src/cli/handler/diagnose/worker.ts
5635
+ import { join as join12 } from "node:path";
5636
+ async function runRelayProbeWorker(configPath, role, directory, signal) {
5637
+ if (!process.send) {
5638
+ throw new Error("The relay probe worker requires its coordinator");
5639
+ }
5640
+ const notify = (message) => {
5641
+ if (process.connected) {
5642
+ process.send?.({ ...message, role }, (error) => {
5643
+ if (error !== null) {
5644
+ controller.abort();
5645
+ }
5646
+ });
5647
+ }
5648
+ };
5649
+ const controller = new AbortController();
5650
+ const stopped = AbortSignal.any([signal, controller.signal]);
5651
+ const start = Promise.withResolvers();
5652
+ const onMessage = (value) => {
5653
+ const message = external_exports.object({ action: external_exports.enum(["start", "stop"]) }).safeParse(value);
5654
+ if (!message.success) {
5655
+ return;
5656
+ }
5657
+ if (message.data.action === "start") {
5658
+ start.resolve();
5659
+ } else {
5660
+ controller.abort();
5661
+ }
5662
+ };
5663
+ const abort = () => {
5664
+ controller.abort();
5665
+ start.resolve();
5666
+ };
5667
+ process.on("message", onMessage);
5668
+ process.once("disconnect", abort);
5669
+ stopped.addEventListener("abort", abort, { once: true });
5670
+ let routesBefore = [];
5671
+ try {
5672
+ const config = await readProbeConfiguration(configPath, stopped);
5673
+ const origin = new URL(config.relayOrigin);
5674
+ const proxy = role === "proxy" ? configuredProbeProxy() : null;
5675
+ routesBefore = await Promise.all([
5676
+ collectRouteEvidence(origin.hostname, stopped),
5677
+ ...proxy === null ? [] : [collectRouteEvidence(proxy.hostname, stopped)]
5678
+ ]);
5679
+ const workspace = join12(directory, "workspace");
5680
+ await ensureProbeDirectory(workspace);
5681
+ const engine = new MobiusRuntimeEngine({
5682
+ engineDataDir: join12(directory, "state"),
5683
+ agentLaunchEnvironment: { HOME: join12(directory, "home"), PATH: "" }
5684
+ });
5685
+ const pairing2 = config[role];
5686
+ const connection = await engine.pair(
5687
+ {
5688
+ relayOrigin: origin.href,
5689
+ pairingId: pairing2.pairingId,
5690
+ pairingCode: pairing2.pairingCode,
5691
+ defaultWorkspace: workspace
5692
+ },
5693
+ stopped
5694
+ );
5695
+ notify({ kind: "prepared", channelId: connection.channelId, runtimeId: connection.runtimeId, routesBefore });
5696
+ await start.promise;
5697
+ if (!stopped.aborted) {
5698
+ await engine.runLoop({
5699
+ connection: { kind: "saved", connectionId: connection.connectionId },
5700
+ signal: stopped,
5701
+ onConnectionStatus: (status) => notify({ kind: "status", status, timestamp: (/* @__PURE__ */ new Date()).toISOString() })
5702
+ });
5703
+ }
5704
+ const routesAfter = await Promise.all([
5705
+ collectRouteEvidence(origin.hostname, AbortSignal.timeout(6e3)),
5706
+ ...proxy === null ? [] : [collectRouteEvidence(proxy.hostname, AbortSignal.timeout(6e3))]
5707
+ ]);
5708
+ notify({ kind: "finished", routesAfter });
5709
+ } catch (error) {
5710
+ if (!stopped.aborted) {
5711
+ notify({ kind: "failed", code: failureCode(error), details: describeRelayFailure(error), routesBefore });
5712
+ process.exitCode = 1;
5713
+ }
5714
+ } finally {
5715
+ stopped.removeEventListener("abort", abort);
5716
+ process.off("message", onMessage);
5717
+ process.off("disconnect", abort);
5718
+ if (process.connected) {
5719
+ process.disconnect();
5720
+ }
5721
+ }
5722
+ }
5723
+
5724
+ // src/cli/handler/diagnose/index.ts
5725
+ var workerMessage = external_exports.discriminatedUnion("kind", [
5726
+ external_exports.object({
5727
+ kind: external_exports.literal("prepared"),
5728
+ role: external_exports.enum(["direct", "proxy"]),
5729
+ channelId: external_exports.string(),
5730
+ runtimeId: external_exports.string(),
5731
+ routesBefore: external_exports.array(routeEvidenceSchema)
5732
+ }),
5733
+ external_exports.object({
5734
+ kind: external_exports.literal("status"),
5735
+ role: external_exports.enum(["direct", "proxy"]),
5736
+ status: external_exports.string(),
5737
+ timestamp: external_exports.string()
5738
+ }),
5739
+ external_exports.object({
5740
+ kind: external_exports.literal("finished"),
5741
+ role: external_exports.enum(["direct", "proxy"]),
5742
+ routesAfter: external_exports.array(routeEvidenceSchema)
5743
+ }),
5744
+ external_exports.object({
5745
+ kind: external_exports.literal("failed"),
5746
+ role: external_exports.enum(["direct", "proxy"]),
5747
+ code: external_exports.string(),
5748
+ details: external_exports.record(external_exports.string(), external_exports.string()),
5749
+ routesBefore: external_exports.array(routeEvidenceSchema)
5750
+ })
5751
+ ]);
5752
+ var ProbeLeg = class {
5753
+ role;
5754
+ #child;
5755
+ #directory;
5756
+ #prepared = Promise.withResolvers();
5757
+ closed;
5758
+ #channelId = "";
5759
+ #runtimeId = "";
5760
+ #routesBefore = [];
5761
+ #routesAfter = [];
5762
+ #onlineCount = 0;
5763
+ #lastStatus = "starting";
5764
+ #events = [];
5765
+ #preparationFailures = [];
5766
+ #omittedEvents = 0;
5767
+ constructor(role, config, directory, proxy) {
5768
+ this.role = role;
5769
+ this.#directory = directory;
5770
+ const entrypoint = process.argv[1];
5771
+ if (entrypoint === void 0) {
5772
+ throw new RelayProbeError("probe_entrypoint_missing", "Cannot locate the Mobius CLI entrypoint");
5773
+ }
5774
+ const environment = {
5775
+ HOME: join13(directory, "home"),
5776
+ PATH: process.env["PATH"] ?? "",
5777
+ MOBIUS_STATE_DIRECTORY: join13(directory, "state")
5778
+ };
5779
+ for (const name of [
5780
+ "SystemRoot",
5781
+ "WINDIR",
5782
+ "TMPDIR",
5783
+ "TMP",
5784
+ "TEMP",
5785
+ "NODE_EXTRA_CA_CERTS",
5786
+ "SSL_CERT_FILE",
5787
+ "SSL_CERT_DIR"
5788
+ ]) {
5789
+ if (process.env[name] !== void 0) {
5790
+ environment[name] = process.env[name];
5791
+ }
5792
+ }
5793
+ environment["NO_PROXY"] = environment["no_proxy"] = role === "direct" ? "*" : "";
5794
+ if (role === "proxy") {
5795
+ environment["HTTPS_PROXY"] = environment["HTTP_PROXY"] = proxy.href;
5796
+ }
5797
+ this.#child = fork(
5798
+ resolve3(entrypoint),
5799
+ ["_relay-probe-worker", "--config", config, "--role", role, "--directory", directory],
5800
+ { env: environment, silent: true, windowsHide: true }
5801
+ );
5802
+ this.#child.stdout?.resume();
5803
+ let pending = "";
5804
+ this.#child.stderr?.on("data", (data) => {
5805
+ pending += data.toString();
5806
+ const lines = pending.split("\n");
5807
+ pending = lines.pop() ?? "";
5808
+ for (const line of lines) {
5809
+ if (line.startsWith("Mobius relay diagnostic ")) {
5810
+ process.stderr.write(`${role}: ${line}
5811
+ `);
5812
+ try {
5813
+ const event = external_exports.record(external_exports.string(), external_exports.string()).safeParse(JSON.parse(line.slice("Mobius relay diagnostic ".length)));
5814
+ if (event.success && event.data["channelId"] === this.#channelId) {
5815
+ this.#events.push(event.data);
5816
+ if (this.#events.length > 2e3) {
5817
+ this.#events.shift();
5818
+ this.#omittedEvents += 1;
5819
+ }
5820
+ }
5821
+ } catch {
5822
+ }
5823
+ }
5824
+ }
5825
+ if (pending.length > 32768) {
5826
+ pending = "";
5827
+ }
5828
+ });
5829
+ this.#child.on("message", (value) => {
5830
+ const message = workerMessage.safeParse(value);
5831
+ if (!message.success || message.data.role !== role) {
5832
+ return;
5833
+ }
5834
+ const event = message.data;
5835
+ switch (event.kind) {
5836
+ case "prepared":
5837
+ this.#channelId = event.channelId;
5838
+ this.#runtimeId = event.runtimeId;
5839
+ this.#routesBefore = event.routesBefore;
5840
+ this.#prepared.resolve(event);
5841
+ break;
5842
+ case "status":
5843
+ if (event.status === "online" && this.#lastStatus !== "online") {
5844
+ this.#onlineCount += 1;
5845
+ }
5846
+ this.#lastStatus = event.status;
5847
+ break;
5848
+ case "finished":
5849
+ this.#routesAfter = event.routesAfter;
5850
+ break;
5851
+ case "failed":
5852
+ this.#routesBefore = event.routesBefore;
5853
+ this.#preparationFailures.push(event.details);
5854
+ this.#prepared.reject(new RelayProbeError(event.code, `The ${role} probe failed`));
5855
+ break;
5856
+ }
5857
+ });
5858
+ this.closed = new Promise((done) => {
5859
+ this.#child.once("error", (error) => {
5860
+ this.#prepared.reject(error);
5861
+ done(1);
5862
+ });
5863
+ this.#child.once("close", (code) => {
5864
+ this.#prepared.reject(
5865
+ new RelayProbeError("probe_worker_exited", `The ${role} probe exited before preparation`)
5866
+ );
5867
+ done(code);
5868
+ });
5869
+ });
5870
+ void this.#prepared.promise.catch(() => void 0);
5871
+ }
5872
+ prepare = async () => await this.#prepared.promise;
5873
+ start = () => {
5874
+ if (this.#child.connected) {
5875
+ this.#child.send({ action: "start" }, () => void 0);
5876
+ }
5877
+ };
5878
+ stop = () => {
5879
+ if (this.#child.connected) {
5880
+ this.#child.send({ action: "stop" }, () => void 0);
5881
+ }
5882
+ };
5883
+ kill = () => {
5884
+ this.#child.kill("SIGKILL");
5885
+ };
5886
+ report = async () => {
5887
+ const events = this.#events;
5888
+ await writeProbeEvents(this.#directory, events);
5889
+ return {
5890
+ role: this.role,
5891
+ channelId: this.#channelId,
5892
+ runtimeId: this.#runtimeId,
5893
+ onlineCount: this.#onlineCount,
5894
+ routesBefore: this.#routesBefore,
5895
+ routesAfter: this.#routesAfter,
5896
+ transportFailures: events.filter((event) => event["event"] === "relay_transport_failed"),
5897
+ preparationFailures: this.#preparationFailures,
5898
+ disconnects: events.filter((event) => event["event"] === "relay_connection_closed"),
5899
+ exitCode: await this.closed,
5900
+ omittedEvents: this.#omittedEvents
5901
+ };
5902
+ };
5903
+ };
5904
+ async function compare(configPath, output, duration, context) {
5905
+ const config = await readProbeConfiguration(configPath, context.shutdownSignal);
5906
+ const proxy = configuredProbeProxy();
5907
+ const directory = resolve3(output);
5908
+ await createProbeOutput(directory);
5909
+ const legs = [];
5910
+ let startedAt = null;
5911
+ let endedAt = null;
5912
+ let completed = false;
5913
+ let failure = null;
5914
+ const deadline = AbortSignal.timeout(6e4);
5915
+ let durationTimer;
5916
+ let abortPreparation = () => void 0;
5917
+ try {
5918
+ for (const role of ["direct", "proxy"]) {
5919
+ context.shutdownSignal.throwIfAborted();
5920
+ const legDirectory = join13(directory, role);
5921
+ await ensureProbeDirectory(join13(legDirectory, "home"));
5922
+ legs.push(new ProbeLeg(role, resolve3(configPath), legDirectory, proxy));
5923
+ }
5924
+ const cancelled = Promise.withResolvers();
5925
+ context.shutdownSignal.throwIfAborted();
5926
+ abortPreparation = () => cancelled.reject(new RelayProbeError("probe_cancelled", "Relay comparison preparation was cancelled"));
5927
+ context.shutdownSignal.addEventListener("abort", abortPreparation, { once: true });
5928
+ deadline.addEventListener("abort", abortPreparation, { once: true });
5929
+ const prepared = await Promise.race([Promise.all(legs.map((leg) => leg.prepare())), cancelled.promise]);
5930
+ context.shutdownSignal.throwIfAborted();
5931
+ if (prepared[0]?.channelId === prepared[1]?.channelId || prepared[0]?.runtimeId === prepared[1]?.runtimeId) {
5932
+ throw new RelayProbeError(
5933
+ "probe_runtime_reused",
5934
+ "The Relay returned the same Runtime for distinct pairings"
5935
+ );
5936
+ }
5937
+ context.shutdownSignal.removeEventListener("abort", abortPreparation);
5938
+ deadline.removeEventListener("abort", abortPreparation);
5939
+ startedAt = (/* @__PURE__ */ new Date()).toISOString();
5940
+ const finish = Promise.withResolvers();
5941
+ const stop = () => finish.resolve();
5942
+ context.shutdownSignal.addEventListener("abort", stop, { once: true });
5943
+ durationTimer = setTimeout(() => {
5944
+ completed = true;
5945
+ finish.resolve();
5946
+ }, duration * 1e3);
5947
+ for (const leg of legs) {
5948
+ leg.start();
5949
+ }
5950
+ try {
5951
+ await Promise.race([
5952
+ finish.promise,
5953
+ ...legs.map(
5954
+ (leg) => leg.closed.then(() => {
5955
+ throw new RelayProbeError(
5956
+ "probe_worker_exited",
5957
+ `The ${leg.role} probe exited during comparison`
5958
+ );
5959
+ })
5960
+ )
5961
+ ]);
5962
+ } finally {
5963
+ context.shutdownSignal.removeEventListener("abort", stop);
5964
+ }
5965
+ } catch (error) {
5966
+ if (!context.shutdownSignal.aborted) {
5967
+ failure = failureCode(error);
5968
+ process.exitCode = 1;
5969
+ }
5970
+ } finally {
5971
+ endedAt = (/* @__PURE__ */ new Date()).toISOString();
5972
+ if (durationTimer !== void 0) {
5973
+ clearTimeout(durationTimer);
5974
+ }
5975
+ context.shutdownSignal.removeEventListener("abort", abortPreparation);
5976
+ deadline.removeEventListener("abort", abortPreparation);
5977
+ for (const leg of legs) {
5978
+ leg.stop();
5979
+ }
5980
+ const force = setTimeout(() => {
5981
+ for (const leg of legs) {
5982
+ leg.kill();
5983
+ }
5984
+ }, 1e4);
5985
+ try {
5986
+ await Promise.all(legs.map((leg) => leg.closed));
5987
+ const reports = await Promise.all(legs.map((leg) => leg.report()));
5988
+ if (reports.some((leg) => leg.exitCode !== 0 || leg.onlineCount === 0)) {
5989
+ process.exitCode = 1;
5990
+ failure ??= "probe_not_ready";
5991
+ }
5992
+ const report = {
5993
+ version: context.version,
5994
+ targetOrigin: new URL(config.relayOrigin).origin,
5995
+ proxyOrigin: proxy.origin,
5996
+ requestedDurationSeconds: duration,
5997
+ startedAt,
5998
+ endedAt,
5999
+ completed,
6000
+ failure,
6001
+ directMeans: "No explicit HTTP proxy; operating-system TUN/VPN routing still applies",
6002
+ legs: reports
6003
+ };
6004
+ await writeProbeReport(directory, report);
6005
+ process.stdout.write(`${join13(directory, "report.json")}
6006
+ `);
6007
+ } finally {
6008
+ clearTimeout(force);
6009
+ await Promise.all(legs.map((leg) => removeProbeState(join13(directory, leg.role))));
6010
+ }
6011
+ }
6012
+ }
6013
+ function registerDiagnoseCommand(program2, context) {
6014
+ program2.command("diagnose").description("collect isolated network diagnostics").command("relay").description("compare fresh direct and proxied Runtime channels over the same observation window").requiredOption(
6015
+ "--config <path>",
6016
+ "private JSON with relayOrigin and distinct direct/proxy pairingId and pairingCode"
6017
+ ).requiredOption("--output <directory>", "new directory for sanitized report.json and transport logs").option("--duration <seconds>", "observation duration after both pairings are prepared", "600").action(async (options) => {
6018
+ const duration = Number(options.duration);
6019
+ if (!Number.isSafeInteger(duration) || duration < 1 || duration > 86400) {
6020
+ throw new RelayProbeError(
6021
+ "probe_duration_invalid",
6022
+ "--duration must be a whole number from 1 to 86400 seconds"
6023
+ );
6024
+ }
6025
+ await compare(options.config, options.output, duration, context);
6026
+ });
6027
+ program2.command("_relay-probe-worker", { hidden: true }).requiredOption("--config <path>").requiredOption("--role <role>").requiredOption("--directory <path>").action(async (options) => {
6028
+ const role = external_exports.enum(["direct", "proxy"]).parse(options.role);
6029
+ await runRelayProbeWorker(options.config, role, options.directory, context.shutdownSignal);
6030
+ });
6031
+ }
6032
+
5447
6033
  // src/cli/handler/logs/logic/collect-local-logs.ts
5448
- import { join as join12, relative as relative4, resolve as resolve3 } from "node:path";
6034
+ import { join as join15, relative as relative4, resolve as resolve4 } from "node:path";
5449
6035
 
5450
6036
  // src/storage/logs/log-files.ts
5451
- import { lstat as lstat2, readdir, readFile as readFile6 } from "node:fs/promises";
5452
- import { join as join11, relative as relative3 } from "node:path";
6037
+ import { lstat as lstat2, readdir, readFile as readFile7 } from "node:fs/promises";
6038
+ import { join as join14, relative as relative3 } from "node:path";
5453
6039
  var LOG_FILE_NAME_PATTERN = /^[\w.-]{1,128}\.log$/u;
5454
6040
  var INSTALLATIONS_DIRECTORY = "installations";
5455
6041
  var VERSIONS_DIRECTORY = "versions";
@@ -5457,17 +6043,17 @@ function isAbsent(cause) {
5457
6043
  return typeof cause === "object" && cause !== null && Reflect.get(cause, "code") === "ENOENT";
5458
6044
  }
5459
6045
  async function findLogDirectories(root, unreadable, signal) {
5460
- const versions = join11(root, VERSIONS_DIRECTORY);
5461
- const installationsRoots = [join11(root, INSTALLATIONS_DIRECTORY)];
6046
+ const versions = join14(root, VERSIONS_DIRECTORY);
6047
+ const installationsRoots = [join14(root, INSTALLATIONS_DIRECTORY)];
5462
6048
  for (const version of await listDirectories(versions, root, unreadable, signal)) {
5463
- installationsRoots.push(join11(versions, version, INSTALLATIONS_DIRECTORY));
6049
+ installationsRoots.push(join14(versions, version, INSTALLATIONS_DIRECTORY));
5464
6050
  }
5465
6051
  const found = [];
5466
6052
  for (const installations of installationsRoots) {
5467
6053
  for (const dataDirectory of await listDirectories(installations, root, unreadable, signal)) {
5468
- const application = join11(installations, dataDirectory);
6054
+ const application = join14(installations, dataDirectory);
5469
6055
  for (const installationId of await listDirectories(application, root, unreadable, signal)) {
5470
- const logs = join11(application, installationId, "logs");
6056
+ const logs = join14(application, installationId, "logs");
5471
6057
  const status = await directoryStatus(logs);
5472
6058
  if (status === "listed") {
5473
6059
  found.push(logs);
@@ -5525,7 +6111,7 @@ async function readOwnedLogFile(path2, signal) {
5525
6111
  return { kind: "skipped" };
5526
6112
  }
5527
6113
  try {
5528
- return { kind: "read", content: await readFile6(path2, { encoding: "utf8", signal }) };
6114
+ return { kind: "read", content: await readFile7(path2, { encoding: "utf8", signal }) };
5529
6115
  } catch (cause) {
5530
6116
  if (signal.aborted) {
5531
6117
  throw cause;
@@ -5540,14 +6126,14 @@ function ownedByCurrentUser(metadata) {
5540
6126
  // src/cli/handler/logs/logic/collect-local-logs.ts
5541
6127
  async function collectLocalDaemonLogs(stateDirectory, tailLines, signal) {
5542
6128
  signal.throwIfAborted();
5543
- const root = resolve3(stateDirectory);
6129
+ const root = resolve4(stateDirectory);
5544
6130
  const entries = [];
5545
6131
  const unreadable = [];
5546
6132
  const directories = await findLogDirectories(root, unreadable, signal);
5547
6133
  for (const directory of directories) {
5548
6134
  const names = await listLogFiles(directory, root, unreadable);
5549
6135
  for (const name of names) {
5550
- const path2 = join12(directory, name);
6136
+ const path2 = join15(directory, name);
5551
6137
  const reported = relative4(root, path2);
5552
6138
  const read = await readOwnedLogFile(path2, signal);
5553
6139
  if (read.kind === "unreadable") {
@@ -5569,7 +6155,7 @@ async function collectLocalDaemonLogs(stateDirectory, tailLines, signal) {
5569
6155
  return { entries, unreadable: [...unreadable].sort() };
5570
6156
  }
5571
6157
  function defaultStateDirectory(environment = process.env) {
5572
- return resolve3(readStateDirectory(environment));
6158
+ return resolve4(readStateDirectory(environment));
5573
6159
  }
5574
6160
 
5575
6161
  // src/cli/handler/logs/index.ts
@@ -5634,7 +6220,7 @@ ${sections.join("\n")}`;
5634
6220
  }
5635
6221
 
5636
6222
  // src/cli/handler/pair/list.ts
5637
- import { resolve as resolve4 } from "node:path";
6223
+ import { resolve as resolve5 } from "node:path";
5638
6224
 
5639
6225
  // src/cli/handler/pair/logic/pair-list-output.ts
5640
6226
  function savedPairSummary(connection) {
@@ -5685,7 +6271,7 @@ async function writePairList(path2, pairs, signal) {
5685
6271
  function registerPairListCommand(command, context) {
5686
6272
  command.command("list").description("list saved pairings").addOption(valueOption("--output-json <existing-file.json>", "write JSON to an existing file")).action(async (options) => {
5687
6273
  const installation = await openMobiusCliInstallation(context.shutdownSignal, process.env);
5688
- const outputPath = options.outputJson === void 0 ? null : resolve4(options.outputJson);
6274
+ const outputPath = options.outputJson === void 0 ? null : resolve5(options.outputJson);
5689
6275
  const pairs = (await installation.listConnections(context.shutdownSignal)).map(savedPairSummary).sort(
5690
6276
  (left, right) => left.appId.localeCompare(right.appId) || left.connectionId.localeCompare(right.connectionId)
5691
6277
  );
@@ -5818,6 +6404,7 @@ function createProgram(cliContext) {
5818
6404
  registerVersionHandler(program2, cliContext);
5819
6405
  registerAgentsHandler(program2, cliContext);
5820
6406
  registerLogsCommand(program2, cliContext);
6407
+ registerDiagnoseCommand(program2, cliContext);
5821
6408
  registerInternalRuntimeHandlers(program2, cliContext);
5822
6409
  registerPairHandlers(program2, cliContext);
5823
6410
  registerDaemonProcessLifecycleHandlers(program2, cliContext);
@@ -5856,4 +6443,4 @@ async function runCLI() {
5856
6443
  export {
5857
6444
  runCLI
5858
6445
  };
5859
- //# sourceMappingURL=cli-5UCO7ZEY.js.map
6446
+ //# sourceMappingURL=cli-FDG4YLBN.js.map