@devrouter/cli 0.6.0 → 0.6.1

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.
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
 
25
25
  // src/core/devpod-exec.ts
26
26
  var import_node_child_process33 = require("child_process");
27
- var import_node_crypto24 = require("crypto");
27
+ var import_node_crypto25 = require("crypto");
28
28
 
29
29
  // src/core/devpod-environment.ts
30
30
  var import_node_child_process3 = require("child_process");
@@ -2695,6 +2695,72 @@ function assertReadableComposeFile(file) {
2695
2695
  throw new Error("Managed container Compose configuration contains an unreadable file.");
2696
2696
  }
2697
2697
  }
2698
+ function resolveManagedServiceNetworkNamespace(options) {
2699
+ const { plan, containers, container, serviceConfig, env } = options;
2700
+ const service = container.labels["com.docker.compose.service"];
2701
+ const targetService = String(serviceConfig.network_mode).slice("service:".length);
2702
+ const target = containers.filter(
2703
+ (item) => item.labels["com.docker.compose.service"] === targetService
2704
+ );
2705
+ const primary = containers.filter(
2706
+ (item) => item.labels["com.docker.compose.service"] === plan.primaryService
2707
+ );
2708
+ const repoPath = import_node_path12.default.dirname(plan.composeDirectory);
2709
+ const owned = (item) => /^[a-f0-9]{64}$/.test(item.id) && containers.filter((candidate) => candidate.id === item.id).length === 1 && containers.filter(
2710
+ (candidate) => candidate.labels["com.docker.compose.service"] === item.labels["com.docker.compose.service"]
2711
+ ).length === 1 && plan.desiredServices.includes(item.labels["com.docker.compose.service"] ?? "") && hasExactComposeIdentity(item, {
2712
+ repoPath,
2713
+ service: item.labels["com.docker.compose.service"] ?? "",
2714
+ composeProject: container.labels["com.docker.compose.project"],
2715
+ composeFiles: plan.composeFiles
2716
+ });
2717
+ if (targetService === service || target.length !== 1 || primary.length !== 1 || !owned(container) || !owned(target[0]) || !owned(primary[0]) || primary[0].mounts.filter(
2718
+ (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
2719
+ ).length !== 1) {
2720
+ throw new Error("Managed service network namespace ownership is unavailable.");
2721
+ }
2722
+ const namespace = `container:${target[0].id}`;
2723
+ const ids2 = [container.id, target[0].id];
2724
+ const inspect = () => {
2725
+ const result = (0, import_node_child_process10.spawnSync)(
2726
+ "docker",
2727
+ [
2728
+ "inspect",
2729
+ "--type",
2730
+ "container",
2731
+ "--format",
2732
+ '{"id":{{json .Id}},"networkMode":{{json .HostConfig.NetworkMode}}}',
2733
+ ...ids2
2734
+ ],
2735
+ {
2736
+ env,
2737
+ encoding: "utf-8",
2738
+ stdio: ["ignore", "pipe", "ignore"],
2739
+ timeout: COMPOSE_HASH_TIMEOUT_MS,
2740
+ maxBuffer: 1024 * 1024
2741
+ }
2742
+ );
2743
+ if (result.status !== 0 || result.error || !result.stdout?.trim()) {
2744
+ throw new Error("Managed service network namespace inspection failed.");
2745
+ }
2746
+ const rows2 = result.stdout.trim().split(/\r?\n/).map((line) => JSON.parse(line));
2747
+ if (rows2.length !== ids2.length || ids2.some(
2748
+ (id5) => rows2.filter(
2749
+ (row) => isObject2(row) && row.id === id5 && typeof row.networkMode === "string"
2750
+ ).length !== 1
2751
+ ) || !rows2.some((row) => isObject2(row) && row.id === container.id && row.networkMode === namespace)) {
2752
+ throw new Error("Managed service network namespace identity changed.");
2753
+ }
2754
+ return ids2.map((id5) => rows2.find((row) => isObject2(row) && row.id === id5));
2755
+ };
2756
+ const before = JSON.stringify(inspect());
2757
+ serviceConfig.network_mode = namespace;
2758
+ return () => {
2759
+ if (JSON.stringify(inspect()) !== before) {
2760
+ throw new Error("Managed service network namespace changed during configuration inspection.");
2761
+ }
2762
+ };
2763
+ }
2698
2764
  function assertManagedContainerConfigUnchanged(options) {
2699
2765
  if (options.containers.length === 0) {
2700
2766
  throw new Error("Managed container configuration cannot be verified without containers.");
@@ -2730,6 +2796,10 @@ function assertManagedContainerConfigUnchanged(options) {
2730
2796
  workingDirectory
2731
2797
  ];
2732
2798
  const env = managedComposeEnvironment(options.workspace);
2799
+ if (options.endpoint) {
2800
+ env.DOCKER_HOST = options.endpoint;
2801
+ delete env.DOCKER_CONTEXT;
2802
+ }
2733
2803
  const rendered = (0, import_node_child_process10.spawnSync)(
2734
2804
  "docker",
2735
2805
  [...composeArgs, ...fileArgs, "config", "--format", "json"],
@@ -2745,19 +2815,38 @@ function assertManagedContainerConfigUnchanged(options) {
2745
2815
  if (rendered.status !== 0 || rendered.error || !rendered.stdout?.trim()) {
2746
2816
  throw new Error("Compose configuration rendering failed.");
2747
2817
  }
2748
- return (0, import_node_child_process10.spawnSync)(
2818
+ const hash = (input2) => (0, import_node_child_process10.spawnSync)(
2749
2819
  "docker",
2750
2820
  [...composeArgs, "-f", "-", "config", "--no-interpolate", "--hash", service],
2751
2821
  {
2752
2822
  cwd: workingDirectory,
2753
2823
  encoding: "utf-8",
2754
2824
  env,
2755
- input: rendered.stdout.replaceAll("$$", "$"),
2825
+ input: input2,
2756
2826
  stdio: ["pipe", "pipe", "ignore"],
2757
2827
  timeout: COMPOSE_HASH_TIMEOUT_MS,
2758
2828
  maxBuffer: 1024 * 1024
2759
2829
  }
2760
2830
  );
2831
+ const input = rendered.stdout.replaceAll("$$", "$");
2832
+ const result2 = hash(input);
2833
+ const match2 = typeof result2.stdout === "string" ? /^(\S+)\s+([a-f0-9]{64})$/.exec(result2.stdout.trim()) : null;
2834
+ if (result2.status !== 0 || result2.error || !match2 || match2[1] !== service || match2[2] === expectedHash)
2835
+ return result2;
2836
+ const model = JSON.parse(input);
2837
+ const serviceConfig = isObject2(model) && isObject2(model.services) ? model.services[service] : void 0;
2838
+ if (!isObject2(serviceConfig) || typeof serviceConfig.network_mode !== "string" || !serviceConfig.network_mode.startsWith("service:"))
2839
+ return result2;
2840
+ const revalidate = resolveManagedServiceNetworkNamespace({
2841
+ plan: options.plan,
2842
+ containers: options.containers,
2843
+ container,
2844
+ serviceConfig,
2845
+ env
2846
+ });
2847
+ const resolved = hash(JSON.stringify(model));
2848
+ revalidate();
2849
+ return resolved;
2761
2850
  } catch {
2762
2851
  throw new Error(`Could not verify managed Compose configuration for service '${service}'.`);
2763
2852
  }
@@ -4083,11 +4172,20 @@ function recoverManagedStopBaseline(state, readPlan, beforePersist) {
4083
4172
  );
4084
4173
  if (currentOwner.status !== "owned") throw new Error("Stop recovery owner is unavailable.");
4085
4174
  proveLocalDockerSelection(currentOwner.workspace);
4086
- assertManagedStopContainersAbsent(
4087
- baseline.endpoint,
4088
- baseline.containers.map((container) => container.id)
4089
- );
4090
4175
  const containers = inspectManagedStopContainers(baseline.project, baseline.endpoint);
4176
+ const missingIds = [];
4177
+ for (const recorded of baseline.containers) {
4178
+ const retained = containers.find((container) => container.id === recorded.id);
4179
+ if (!retained) {
4180
+ missingIds.push(recorded.id);
4181
+ } else if (!(0, import_node_util3.isDeepStrictEqual)(
4182
+ { ...recorded, mounts: orderedMounts(recorded.mounts) },
4183
+ identity(retained)
4184
+ )) {
4185
+ throw new Error("Stop recovery retained container identity changed.");
4186
+ }
4187
+ }
4188
+ if (missingIds.length > 0) assertManagedStopContainersAbsent(baseline.endpoint, missingIds);
4091
4189
  if (!(0, import_node_util3.isDeepStrictEqual)(
4092
4190
  containers.map((container) => container.labels["com.docker.compose.service"]).sort(),
4093
4191
  [...plan.desiredServices].sort()
@@ -4107,6 +4205,7 @@ function recoverManagedStopBaseline(state, readPlan, beforePersist) {
4107
4205
  assertManagedContainerConfigUnchanged({
4108
4206
  plan,
4109
4207
  containers,
4208
+ endpoint: baseline.endpoint,
4110
4209
  workspace: state.workspace ? {
4111
4210
  token: state.workspace,
4112
4211
  gitCommonDir: resolveGitCommonDir(state.repoPath)
@@ -5490,7 +5589,7 @@ function retain(session, previous) {
5490
5589
 
5491
5590
  // src/core/reliability-operation-store.ts
5492
5591
  function installedCliVersion() {
5493
- return true ? "0.6.0" : "0.0.0-dev";
5592
+ return true ? "0.6.1" : "0.0.0-dev";
5494
5593
  }
5495
5594
  function compareCliVersions(left, right) {
5496
5595
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -7593,7 +7692,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs19.default.rea
7593
7692
  const config = parseRepoConfigSource(raw, configPath);
7594
7693
  const requiredVersion = config.devrouter?.version;
7595
7694
  if (requiredVersion && !hasWarnedVersionMismatch) {
7596
- const cliVersion = true ? "0.6.0" : "0.0.0-dev";
7695
+ const cliVersion = true ? "0.6.1" : "0.0.0-dev";
7597
7696
  if (cliVersion !== "0.0.0-dev" && compareSemver2(requiredVersion, cliVersion) > 0) {
7598
7697
  hasWarnedVersionMismatch = true;
7599
7698
  process.stderr.write(
@@ -8636,6 +8735,7 @@ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
8636
8735
  assertManagedContainerConfigUnchanged({
8637
8736
  plan,
8638
8737
  containers: population,
8738
+ endpoint,
8639
8739
  workspace: workspaceEnv
8640
8740
  });
8641
8741
  if (!(0, import_node_util5.isDeepStrictEqual)(readAuthority(), authority) || !(0, import_node_util5.isDeepStrictEqual)(registration2(), owner) || resolveManagedStopEndpoint() !== endpoint || inspectManagedStopDaemon(endpoint) !== daemon)
@@ -10650,9 +10750,9 @@ var ExecutionOutcomeError = class extends Error {
10650
10750
  };
10651
10751
 
10652
10752
  // src/core/reliability-lifecycle.ts
10653
- var import_node_crypto23 = require("crypto");
10654
- var import_node_path41 = __toESM(require("path"));
10655
- var import_node_util7 = require("util");
10753
+ var import_node_crypto24 = require("crypto");
10754
+ var import_node_path42 = __toESM(require("path"));
10755
+ var import_node_util8 = require("util");
10656
10756
 
10657
10757
  // src/core/capacity-policy.ts
10658
10758
  var import_node_fs27 = __toESM(require("fs"));
@@ -12483,9 +12583,15 @@ function stepReliability(state, event, nowMs) {
12483
12583
 
12484
12584
  // src/core/reliability-worker.ts
12485
12585
  var import_node_child_process21 = require("child_process");
12586
+ var import_node_crypto20 = require("crypto");
12587
+ var import_node_fs31 = __toESM(require("fs"));
12588
+ var import_node_path36 = __toESM(require("path"));
12589
+
12590
+ // src/core/lifecycle-diagnostics.ts
12486
12591
  var import_node_crypto19 = require("crypto");
12487
12592
  var import_node_fs30 = __toESM(require("fs"));
12488
12593
  var import_node_path35 = __toESM(require("path"));
12594
+ var import_node_util7 = require("util");
12489
12595
 
12490
12596
  // src/core/lifecycle-progress.ts
12491
12597
  var ROLES = {
@@ -12540,6 +12646,89 @@ function reportLifecycleProgress(phase) {
12540
12646
  sender?.(phase);
12541
12647
  }
12542
12648
 
12649
+ // src/core/lifecycle-diagnostics.ts
12650
+ var MAX_TAIL_BYTES = 32768;
12651
+ var MAX_RECEIPT_BYTES = 65536;
12652
+ function privateDirectory(directory) {
12653
+ const stat = import_node_fs30.default.lstatSync(directory);
12654
+ return stat.isDirectory() && stat.uid === process.getuid?.() && (stat.mode & 63) === 0;
12655
+ }
12656
+ function lifecycleDiagnosticsPath(identity2) {
12657
+ return import_node_path35.default.join(
12658
+ DEVROUTER_HOME,
12659
+ "lifecycle-diagnostics",
12660
+ `${(0, import_node_crypto19.createHash)("sha256").update(identity2.repoPath).digest("hex")}.json`
12661
+ );
12662
+ }
12663
+ function readReceipt(identity2) {
12664
+ let descriptor;
12665
+ try {
12666
+ const file = lifecycleDiagnosticsPath(identity2);
12667
+ if (!privateDirectory(import_node_path35.default.dirname(file))) return void 0;
12668
+ descriptor = import_node_fs30.default.openSync(
12669
+ file,
12670
+ import_node_fs30.default.constants.O_RDONLY | import_node_fs30.default.constants.O_NOFOLLOW | import_node_fs30.default.constants.O_NONBLOCK
12671
+ );
12672
+ const stat = import_node_fs30.default.fstatSync(descriptor);
12673
+ if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0 || stat.size > MAX_RECEIPT_BYTES)
12674
+ return void 0;
12675
+ const buffer = Buffer.alloc(MAX_RECEIPT_BYTES + 1);
12676
+ const size = import_node_fs30.default.readSync(descriptor, buffer, 0, buffer.length, 0);
12677
+ if (size > MAX_RECEIPT_BYTES) return void 0;
12678
+ const receipt = JSON.parse(buffer.subarray(0, size).toString("utf8"));
12679
+ const timestamp = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
12680
+ const optionalTime = (value) => value === null || timestamp(value);
12681
+ if (receipt.version !== 1 || !(0, import_node_util7.isDeepStrictEqual)(receipt.identity, identity2) || typeof receipt.operationId !== "string" || typeof receipt.workerId !== "string" || !timestamp(receipt.startedAtMs) || !timestamp(receipt.updatedAtMs) || receipt.updatedAtMs < receipt.startedAtMs || !(receipt.phase === null || isLifecycleProgressPhase(receipt.phase)) || !optionalTime(receipt.phaseAtMs) || !optionalTime(receipt.lastOutputAtMs) || !optionalTime(receipt.lastProgressAtMs) || !(receipt.buildPackage === null || typeof receipt.buildPackage === "string" && receipt.buildPackage.length <= 128 && /^(?:@[a-zA-Z0-9_.-]+\/)?[a-zA-Z0-9_.-]+$/.test(receipt.buildPackage)) || typeof receipt.cancellationRequested !== "boolean" || typeof receipt.closed !== "boolean" || !Array.isArray(receipt.output) || receipt.output.length > 64)
12682
+ return void 0;
12683
+ let bytes2 = 0;
12684
+ for (const chunk of receipt.output) {
12685
+ if (!chunk || !["stdout", "stderr"].includes(chunk.stream) || typeof chunk.data !== "string" || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(chunk.data))
12686
+ return void 0;
12687
+ bytes2 += Buffer.byteLength(chunk.data, "base64");
12688
+ }
12689
+ return bytes2 <= MAX_TAIL_BYTES ? receipt : void 0;
12690
+ } catch {
12691
+ return void 0;
12692
+ } finally {
12693
+ if (descriptor !== void 0) import_node_fs30.default.closeSync(descriptor);
12694
+ }
12695
+ }
12696
+ function readLifecycleDiagnostics(identity2, now = Date.now()) {
12697
+ try {
12698
+ const before = readReliabilityOperation(identity2);
12699
+ const receipt = readReceipt(identity2);
12700
+ const after = readReliabilityOperation(identity2);
12701
+ if (!before || !after || !receipt || before.revision !== after.revision || !(0, import_node_util7.isDeepStrictEqual)(before, after) || !(0, import_node_util7.isDeepStrictEqual)(receipt.fence, reliabilityFence(after.state)) || after.state.operation?.id !== receipt.operationId || after.state.operation.kind !== "ensure" || after.worker !== null && after.worker.id !== receipt.workerId || !receipt.closed && !after.worker || now < receipt.updatedAtMs || [receipt.phaseAtMs, receipt.lastOutputAtMs, receipt.lastProgressAtMs].some(
12702
+ (time) => time !== null && (time < receipt.startedAtMs || time > receipt.updatedAtMs)
12703
+ ))
12704
+ return void 0;
12705
+ const age = (time) => time === null ? null : now - time;
12706
+ const metadata = {
12707
+ stage: receipt.phase ?? "unknown",
12708
+ elapsedMs: Math.max(0, (receipt.closed ? receipt.updatedAtMs : now) - receipt.startedAtMs),
12709
+ stageAgeMs: age(receipt.phaseAtMs),
12710
+ outputAgeMs: age(receipt.lastOutputAtMs),
12711
+ progressAgeMs: age(receipt.lastProgressAtMs),
12712
+ evidence: now - receipt.updatedAtMs < 3e4 ? "recent" : "stale",
12713
+ buildPackage: receipt.buildPackage,
12714
+ liveness: "unknown",
12715
+ executionDeadlineAtMs: null,
12716
+ handshakeTimeoutMs: 3e4,
12717
+ cancellationGraceMs: 2e3,
12718
+ cancellationRequested: receipt.cancellationRequested,
12719
+ workerDrained: after.state.operation.drained
12720
+ };
12721
+ return {
12722
+ operationId: receipt.operationId,
12723
+ revision: after.revision,
12724
+ metadata,
12725
+ output: receipt.output
12726
+ };
12727
+ } catch {
12728
+ return void 0;
12729
+ }
12730
+ }
12731
+
12543
12732
  // src/core/reliability-worker.ts
12544
12733
  var DEFAULT_OUTPUT_PAGE_BYTES = 48 * 1024;
12545
12734
  function workerGroupAbsent(pid) {
@@ -12551,11 +12740,11 @@ function workerGroupAbsent(pid) {
12551
12740
  if (process.platform !== "linux") return false;
12552
12741
  let observedZombie = false;
12553
12742
  try {
12554
- for (const entry of import_node_fs30.default.readdirSync("/proc")) {
12743
+ for (const entry of import_node_fs31.default.readdirSync("/proc")) {
12555
12744
  if (!/^\d+$/.test(entry)) continue;
12556
12745
  let stat;
12557
12746
  try {
12558
- stat = import_node_fs30.default.readFileSync(`/proc/${entry}/stat`, "utf8");
12747
+ stat = import_node_fs31.default.readFileSync(`/proc/${entry}/stat`, "utf8");
12559
12748
  } catch (error) {
12560
12749
  if (error.code === "ENOENT") continue;
12561
12750
  return false;
@@ -12576,19 +12765,19 @@ function workerGroupAbsent(pid) {
12576
12765
 
12577
12766
  // src/core/workspace-ensure.ts
12578
12767
  var import_node_child_process31 = require("child_process");
12579
- var import_node_fs35 = __toESM(require("fs"));
12580
- var import_node_path40 = __toESM(require("path"));
12768
+ var import_node_fs36 = __toESM(require("fs"));
12769
+ var import_node_path41 = __toESM(require("path"));
12581
12770
 
12582
12771
  // src/core/devpod-mutation.ts
12583
12772
  var import_node_child_process22 = require("child_process");
12584
- var import_node_fs31 = __toESM(require("fs"));
12585
- var import_node_path36 = __toESM(require("path"));
12586
- var DEVPOD_MUTATION_LOCK_FILE = import_node_path36.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
12773
+ var import_node_fs32 = __toESM(require("fs"));
12774
+ var import_node_path37 = __toESM(require("path"));
12775
+ var DEVPOD_MUTATION_LOCK_FILE = import_node_path37.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
12587
12776
  var DEVPOD_MUTATION_WAIT_MS = 18e5;
12588
12777
  var DevpodStartPostconditionError = class extends Error {
12589
12778
  };
12590
12779
  function withMutationLock2(activity, target, operation) {
12591
- import_node_fs31.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12780
+ import_node_fs32.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12592
12781
  return withFileLockSync(
12593
12782
  DEVPOD_MUTATION_LOCK_FILE,
12594
12783
  {
@@ -12790,7 +12979,7 @@ async function startDevpodWorkspace(options) {
12790
12979
 
12791
12980
  // src/core/host-port-claims.ts
12792
12981
  var import_node_child_process23 = require("child_process");
12793
- var import_node_path37 = __toESM(require("path"));
12982
+ var import_node_path38 = __toESM(require("path"));
12794
12983
  var COMPOSE_RENDER_TIMEOUT_MS = 3e4;
12795
12984
  var COMPOSE_RENDER_MAX_BUFFER = 8 * 1024 * 1024;
12796
12985
  var DOCKER_TIMEOUT_MS = 1e4;
@@ -12998,8 +13187,8 @@ function renderManagedComposeModel(plan, workspace) {
12998
13187
  }
12999
13188
  }
13000
13189
  function attributeHolderWorktree(workingDir) {
13001
- if (import_node_path37.default.basename(workingDir) !== ".devcontainer") return void 0;
13002
- return comparableWorkspacePath(import_node_path37.default.dirname(workingDir));
13190
+ if (import_node_path38.default.basename(workingDir) !== ".devcontainer") return void 0;
13191
+ return comparableWorkspacePath(import_node_path38.default.dirname(workingDir));
13003
13192
  }
13004
13193
  function holderAttribution(holder, repoPath) {
13005
13194
  const attribution = {};
@@ -13034,7 +13223,7 @@ function detectHostPortClaimConflicts(options) {
13034
13223
  );
13035
13224
  if (desired.length === 0) return [];
13036
13225
  const holders = listHolderSnapshots();
13037
- const devcontainerDir = import_node_path37.default.join(options.repoPath, ".devcontainer");
13226
+ const devcontainerDir = import_node_path38.default.join(options.repoPath, ".devcontainer");
13038
13227
  const conflicts = [];
13039
13228
  for (const binding of desired) {
13040
13229
  for (const holder of holders) {
@@ -13195,7 +13384,7 @@ function transportFailure() {
13195
13384
 
13196
13385
  // src/core/managed-host-preparation.ts
13197
13386
  var import_node_child_process25 = require("child_process");
13198
- var import_node_fs32 = __toESM(require("fs"));
13387
+ var import_node_fs33 = __toESM(require("fs"));
13199
13388
  var PREPARATION_TIMEOUT_MS = 6e4;
13200
13389
  var CONFIG_READ_ERROR = "Managed host preparation could not read .devrouter.yml.";
13201
13390
  var CONFIG_CHANGED_ERROR = "Managed host preparation changed .devrouter.yml; refusing to continue.";
@@ -13238,7 +13427,7 @@ function runPreparationCommand(argv, cwd) {
13238
13427
  }
13239
13428
  function readConfigBytes(configPath) {
13240
13429
  try {
13241
- return import_node_fs32.default.readFileSync(configPath);
13430
+ return import_node_fs33.default.readFileSync(configPath);
13242
13431
  } catch {
13243
13432
  return void 0;
13244
13433
  }
@@ -13271,6 +13460,43 @@ async function runManagedHostPreparation(repoPath, config) {
13271
13460
  }
13272
13461
  }
13273
13462
 
13463
+ // src/core/lifecycle-operation-status.ts
13464
+ function projectLifecycleOperation(operation) {
13465
+ const result = {
13466
+ operationId: operation.id,
13467
+ phase: "terminal",
13468
+ outcome: null,
13469
+ reason: null,
13470
+ exitCode: null
13471
+ };
13472
+ switch (operation.status) {
13473
+ case "NOT_STARTED":
13474
+ if (operation.drained) result.outcome = "NOT_STARTED";
13475
+ else result.phase = "queued";
13476
+ break;
13477
+ case "DISPATCH_PENDING":
13478
+ case "DISPATCH_RECORDED":
13479
+ case "RUNNING":
13480
+ if (operation.drained) {
13481
+ result.outcome = "COMPLETION_UNKNOWN";
13482
+ result.reason = "worker-drained-without-result";
13483
+ } else result.phase = operation.status === "RUNNING" ? "running" : "dispatching";
13484
+ break;
13485
+ case "COMPLETED":
13486
+ result.outcome = "COMPLETED";
13487
+ result.exitCode = operation.exitCode;
13488
+ break;
13489
+ case "NOT_LAUNCHED":
13490
+ result.outcome = "NOT_STARTED";
13491
+ break;
13492
+ case "INTERRUPTED":
13493
+ case "COMPLETION_UNKNOWN":
13494
+ result.outcome = operation.status;
13495
+ break;
13496
+ }
13497
+ return result;
13498
+ }
13499
+
13274
13500
  // src/core/managed-mount-nesting.ts
13275
13501
  var import_node_child_process26 = require("child_process");
13276
13502
  var NESTED_MOUNT_REASON = "no configured mount is nested inside another configured mount";
@@ -13423,6 +13649,8 @@ function readReliabilityStatus(options) {
13423
13649
  return void 0;
13424
13650
  }
13425
13651
  if (!record5) return void 0;
13652
+ const diagnosticReceipt = record5.state.operation ? readLifecycleDiagnostics(record5.identity) : void 0;
13653
+ const diagnostics = diagnosticReceipt?.operationId === record5.state.operation?.id && diagnosticReceipt?.revision === record5.revision ? diagnosticReceipt?.metadata : void 0;
13426
13654
  const reason2 = reliabilityAttention(record5.state) ?? (record5.state.desired === "running" && refusedManagedStart(record5.result) ? "start-refused" : void 0);
13427
13655
  return {
13428
13656
  desired: record5.state.desired,
@@ -13433,6 +13661,14 @@ function readReliabilityStatus(options) {
13433
13661
  correctiveActionsTaken: record5.state.incident.correctiveActionsTaken,
13434
13662
  actionLimit: record5.state.incident.actionLimit
13435
13663
  } : null,
13664
+ ...record5.state.operation ? {
13665
+ operation: {
13666
+ ...projectLifecycleOperation(record5.state.operation),
13667
+ kind: record5.state.operation.kind,
13668
+ drained: record5.state.operation.drained,
13669
+ ...diagnostics ? { diagnostics } : {}
13670
+ }
13671
+ } : {},
13436
13672
  ...reason2 ? {
13437
13673
  attention: {
13438
13674
  reason: reason2,
@@ -14060,33 +14296,33 @@ var import_node_child_process28 = require("child_process");
14060
14296
  var import_node_net3 = require("net");
14061
14297
 
14062
14298
  // src/core/network-local-daemon.ts
14063
- var import_node_fs33 = __toESM(require("fs"));
14064
- var import_node_path38 = __toESM(require("path"));
14299
+ var import_node_fs34 = __toESM(require("fs"));
14300
+ var import_node_path39 = __toESM(require("path"));
14065
14301
  function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
14066
14302
  const deadline = Date.now() + 2e3;
14067
14303
  try {
14068
14304
  if (!endpoint.startsWith("unix:///")) return false;
14069
14305
  const socket = endpoint.slice("unix://".length);
14070
- const table = import_node_fs33.default.readFileSync(import_node_path38.default.join(procRoot, "net/unix"), "utf8");
14306
+ const table = import_node_fs34.default.readFileSync(import_node_path39.default.join(procRoot, "net/unix"), "utf8");
14071
14307
  if (Buffer.byteLength(table) > 1024 * 1024) return false;
14072
14308
  const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
14073
14309
  if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
14074
14310
  const inode = `socket:[${rows2[0][6]}]`;
14075
- const ownNamespace = import_node_fs33.default.readlinkSync(import_node_path38.default.join(procRoot, "self/ns/net"));
14076
- const pids = import_node_fs33.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
14311
+ const ownNamespace = import_node_fs34.default.readlinkSync(import_node_path39.default.join(procRoot, "self/ns/net"));
14312
+ const pids = import_node_fs34.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
14077
14313
  if (pids.length > 4096) return false;
14078
14314
  for (const pid of pids) {
14079
14315
  if (Date.now() > deadline) return false;
14080
14316
  try {
14081
- const root = import_node_path38.default.join(procRoot, pid);
14082
- if (import_node_path38.default.basename(import_node_fs33.default.readlinkSync(import_node_path38.default.join(root, "exe"))) !== "dockerd") continue;
14083
- if (import_node_fs33.default.readlinkSync(import_node_path38.default.join(root, "ns/net")) !== ownNamespace) continue;
14084
- const descriptors = import_node_fs33.default.readdirSync(import_node_path38.default.join(root, "fd"));
14317
+ const root = import_node_path39.default.join(procRoot, pid);
14318
+ if (import_node_path39.default.basename(import_node_fs34.default.readlinkSync(import_node_path39.default.join(root, "exe"))) !== "dockerd") continue;
14319
+ if (import_node_fs34.default.readlinkSync(import_node_path39.default.join(root, "ns/net")) !== ownNamespace) continue;
14320
+ const descriptors = import_node_fs34.default.readdirSync(import_node_path39.default.join(root, "fd"));
14085
14321
  if (descriptors.length > 4096) continue;
14086
14322
  if (descriptors.some((fd) => {
14087
14323
  if (Date.now() > deadline) return false;
14088
14324
  try {
14089
- return import_node_fs33.default.readlinkSync(import_node_path38.default.join(root, "fd", fd)) === inode;
14325
+ return import_node_fs34.default.readlinkSync(import_node_path39.default.join(root, "fd", fd)) === inode;
14090
14326
  } catch {
14091
14327
  return false;
14092
14328
  }
@@ -14161,10 +14397,10 @@ function inspectLegacyNetworkCapacity(input) {
14161
14397
  }
14162
14398
 
14163
14399
  // src/core/network-managed.ts
14164
- var import_node_crypto22 = require("crypto");
14400
+ var import_node_crypto23 = require("crypto");
14165
14401
 
14166
14402
  // src/core/network-compose.ts
14167
- var import_node_crypto20 = require("crypto");
14403
+ var import_node_crypto21 = require("crypto");
14168
14404
  var SAFE_ENDPOINT_UPPER_BOUND_SEMANTICS = "full-lifecycle-demand";
14169
14405
  function isRecord5(value) {
14170
14406
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -14970,14 +15206,14 @@ function stableValue(value) {
14970
15206
  }
14971
15207
  function fingerprintComposeNetworkModel(input) {
14972
15208
  const projection = normalizedFingerprintProjection(input);
14973
- return (0, import_node_crypto20.createHash)("sha256").update(stableValue(projection), "utf8").digest("hex");
15209
+ return (0, import_node_crypto21.createHash)("sha256").update(stableValue(projection), "utf8").digest("hex");
14974
15210
  }
14975
15211
 
14976
15212
  // src/core/network-compose-files.ts
14977
15213
  var import_node_child_process29 = require("child_process");
14978
- var import_node_crypto21 = require("crypto");
14979
- var import_node_fs34 = __toESM(require("fs"));
14980
- var import_node_path39 = __toESM(require("path"));
15214
+ var import_node_crypto22 = require("crypto");
15215
+ var import_node_fs35 = __toESM(require("fs"));
15216
+ var import_node_path40 = __toESM(require("path"));
14981
15217
  var import_yaml4 = __toESM(require("yaml"));
14982
15218
  var OVERLAY = "docker-compose.devrouter-network.yml";
14983
15219
  var MARKER = "# devrouter:managed workspace network";
@@ -14990,12 +15226,12 @@ function object3(value) {
14990
15226
  function inspectNetworkComposeFiles(input) {
14991
15227
  const authoredNetworks = {};
14992
15228
  for (const file of input.plan.composeFiles) {
14993
- const stat = import_node_fs34.default.lstatSync(file);
15229
+ const stat = import_node_fs35.default.lstatSync(file);
14994
15230
  if (!stat.isFile() || stat.size > MAX_BYTES2)
14995
15231
  throw new Error("Network Compose source is unavailable or too large.");
14996
15232
  let source;
14997
15233
  try {
14998
- source = object3(import_yaml4.default.parse(import_node_fs34.default.readFileSync(file, "utf8")));
15234
+ source = object3(import_yaml4.default.parse(import_node_fs35.default.readFileSync(file, "utf8")));
14999
15235
  } catch {
15000
15236
  throw new Error("Network Compose source could not be parsed safely.");
15001
15237
  }
@@ -15045,7 +15281,7 @@ function inspectNetworkComposeFiles(input) {
15045
15281
  function prepareNetworkComposeFiles(plan, subnet) {
15046
15282
  const overlay = deriveComposeNetworkOverlay(subnet);
15047
15283
  if (overlay.status !== "ready" || !overlay.overlay) throw new Error("Network subnet is invalid.");
15048
- const overlayPath = import_node_path39.default.join(plan.composeDirectory, OVERLAY);
15284
+ const overlayPath = import_node_path40.default.join(plan.composeDirectory, OVERLAY);
15049
15285
  const ignored = (0, import_node_child_process29.spawnSync)("git", [
15050
15286
  "-C",
15051
15287
  plan.composeDirectory,
@@ -15060,9 +15296,9 @@ function prepareNetworkComposeFiles(plan, subnet) {
15060
15296
  const bytes2 = `${MARKER}
15061
15297
  ${import_yaml4.default.stringify(overlay.overlay)}`;
15062
15298
  const assertOwnership = () => {
15063
- if (!import_node_fs34.default.existsSync(overlayPath)) return;
15064
- const stat = import_node_fs34.default.lstatSync(overlayPath);
15065
- if (!stat.isFile() || stat.size > MAX_BYTES2 || import_node_fs34.default.readFileSync(overlayPath, "utf8") !== bytes2)
15299
+ if (!import_node_fs35.default.existsSync(overlayPath)) return;
15300
+ const stat = import_node_fs35.default.lstatSync(overlayPath);
15301
+ if (!stat.isFile() || stat.size > MAX_BYTES2 || import_node_fs35.default.readFileSync(overlayPath, "utf8") !== bytes2)
15066
15302
  throw new Error("Retained network overlay changed; repair is required.");
15067
15303
  };
15068
15304
  assertOwnership();
@@ -15076,7 +15312,7 @@ ${JSON.stringify(extended.config, null, 2)}
15076
15312
  ...plan,
15077
15313
  contents,
15078
15314
  composeFiles: [...plan.composeFiles, overlayPath],
15079
- effectiveConfigSha256: (0, import_node_crypto21.createHash)("sha256").update(contents).digest("hex")
15315
+ effectiveConfigSha256: (0, import_node_crypto22.createHash)("sha256").update(contents).digest("hex")
15080
15316
  };
15081
15317
  return {
15082
15318
  plan: result,
@@ -15394,7 +15630,7 @@ function createManagedNetworkSession(input) {
15394
15630
  "Insufficient endpoint headroom; request an allowed larger network before first allocation."
15395
15631
  );
15396
15632
  const authority = readNetworkOperationAuthority(identity2);
15397
- const fingerprint = (0, import_node_crypto22.createHash)("sha256").update(input.plan().sourceConfigSha256).update(fingerprintComposeNetworkModel(model)).digest("hex");
15633
+ const fingerprint = (0, import_node_crypto23.createHash)("sha256").update(input.plan().sourceConfigSha256).update(fingerprintComposeNetworkModel(model)).digest("hex");
15398
15634
  if (!saved) {
15399
15635
  if (policy.status !== "valid")
15400
15636
  throw new Error("New network allocation requires valid machine policy.");
@@ -15578,11 +15814,11 @@ var DEFAULT_READINESS_TIMEOUT_MS = 12e4;
15578
15814
  var POLL_INTERVAL_MS = 1e3;
15579
15815
  function assertOverlay(container, repoPath) {
15580
15816
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
15581
- if (!workingDir || !sameWorkspacePath(workingDir, import_node_path40.default.join(repoPath, ".devcontainer"))) {
15817
+ if (!workingDir || !sameWorkspacePath(workingDir, import_node_path41.default.join(repoPath, ".devcontainer"))) {
15582
15818
  throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
15583
15819
  }
15584
15820
  const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
15585
- const expectedOverlay = import_node_path40.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
15821
+ const expectedOverlay = import_node_path41.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
15586
15822
  if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
15587
15823
  throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
15588
15824
  }
@@ -15596,7 +15832,7 @@ function assertWorktreeOwnership(container, repoPath) {
15596
15832
  function isInsideWorktree(candidate, repoPath) {
15597
15833
  const normalizedCandidate = comparableWorkspacePath(candidate);
15598
15834
  const normalizedRoot = comparableWorkspacePath(repoPath);
15599
- return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${import_node_path40.default.sep}`);
15835
+ return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${import_node_path41.default.sep}`);
15600
15836
  }
15601
15837
  function assertReady(container, label) {
15602
15838
  if (!container.state.Running) {
@@ -15788,7 +16024,7 @@ function hasExactManagedComposeProject(repoPath, state) {
15788
16024
  }).some((container) => {
15789
16025
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
15790
16026
  return Boolean(
15791
- workingDir && sameWorkspacePath(workingDir, import_node_path40.default.join(repoPath, ".devcontainer"))
16027
+ workingDir && sameWorkspacePath(workingDir, import_node_path41.default.join(repoPath, ".devcontainer"))
15792
16028
  );
15793
16029
  });
15794
16030
  } catch {
@@ -16193,7 +16429,7 @@ function resolvePrimaryTarget(repoPath) {
16193
16429
  }
16194
16430
  function isPrimaryCheckout(repoPath) {
16195
16431
  try {
16196
- return import_node_fs35.default.statSync(import_node_path40.default.join(repoPath, ".git")).isDirectory();
16432
+ return import_node_fs36.default.statSync(import_node_path41.default.join(repoPath, ".git")).isDirectory();
16197
16433
  } catch {
16198
16434
  return false;
16199
16435
  }
@@ -16257,8 +16493,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16257
16493
  const target = options.repair ? resolveRepairTarget(repoPath, linked) : linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
16258
16494
  let devpodId = target.devpodId;
16259
16495
  if (target.kind === "linked") {
16260
- const overlayPath = import_node_path40.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
16261
- if (!import_node_fs35.default.existsSync(overlayPath)) {
16496
+ const overlayPath = import_node_path41.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
16497
+ if (!import_node_fs36.default.existsSync(overlayPath)) {
16262
16498
  throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
16263
16499
  }
16264
16500
  }
@@ -16278,6 +16514,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16278
16514
  if (variant !== "default" && managedPostStart.kind !== "runtime")
16279
16515
  throw new Error("Runtime variants require a managed runtime post-start adapter.");
16280
16516
  const managedRuntime = runtime.config.managedRuntime;
16517
+ const hostPortDeclarationsSha256 = managedHostPortDeclarationsIdentity(
16518
+ managedRuntime?.devcontainer.hostPorts ?? {}
16519
+ );
16281
16520
  if (options.repair && !managedRuntime)
16282
16521
  throw new Error("Repair requires managedRuntime configuration.");
16283
16522
  previousRoutes = listHostRouteState().filter(
@@ -16365,7 +16604,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16365
16604
  }
16366
16605
  const apps = proxyAppsFromConfig(runtime.config);
16367
16606
  const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
16368
- const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path40.default.basename(repoPath)) ?? "app";
16607
+ const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path41.default.basename(repoPath)) ?? "app";
16369
16608
  for (const [index, app] of apps.entries()) {
16370
16609
  if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
16371
16610
  const owner = target.kind === "linked" ? "workspace" : "checkout";
@@ -16602,9 +16841,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16602
16841
  composeProject: captureProject,
16603
16842
  profile: runtime.profile,
16604
16843
  variant,
16605
- hostPortDeclarationsSha256: managedHostPortDeclarationsIdentity(
16606
- managedRuntime?.devcontainer.hostPorts ?? {}
16607
- ),
16844
+ ...hostPortDeclarationsSha256 !== void 0 ? { hostPortDeclarationsSha256 } : {},
16608
16845
  desired: {
16609
16846
  apps: apps.map((app) => app.name).sort(),
16610
16847
  services: [...capturePlan.desiredProfileServices].sort(),
@@ -16815,9 +17052,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16815
17052
  composeProject: managedComposeProject,
16816
17053
  profile: runtime.profile,
16817
17054
  variant,
16818
- hostPortDeclarationsSha256: managedHostPortDeclarationsIdentity(
16819
- managedRuntime?.devcontainer.hostPorts ?? {}
16820
- ),
17055
+ ...hostPortDeclarationsSha256 !== void 0 ? { hostPortDeclarationsSha256 } : {},
16821
17056
  desired: {
16822
17057
  apps: apps.map((app) => app.name).sort(),
16823
17058
  services: [...managedPlan.desiredProfileServices].sort(),
@@ -17254,9 +17489,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
17254
17489
  }
17255
17490
  const repairLocked = () => {
17256
17491
  const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
17257
- import_node_fs35.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
17492
+ import_node_fs36.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
17258
17493
  return withFileLock(
17259
- import_node_path40.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
17494
+ import_node_path41.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
17260
17495
  {
17261
17496
  activity: "Managed runtime repair",
17262
17497
  target: repoPath,
@@ -17434,7 +17669,7 @@ function preflightRuntimeVariant(repoPath, options) {
17434
17669
  return variant;
17435
17670
  }
17436
17671
  function capacityPolicyTargets(repoPath) {
17437
- const policy = readCapacityPolicy(import_node_path41.default.join(DEVROUTER_HOME, "controller"));
17672
+ const policy = readCapacityPolicy(import_node_path42.default.join(DEVROUTER_HOME, "controller"));
17438
17673
  return policy?.admissions === "enabled" && policy.enrollments.some((entry) => entry.repoPath === repoPath);
17439
17674
  }
17440
17675
  function clearActiveLifecycleStartupWitness() {
@@ -17540,7 +17775,7 @@ async function executeLifecycleWorker(request, operation) {
17540
17775
  const owned = containers.filter(
17541
17776
  (container) => sameWorkspacePath(
17542
17777
  container.labels["com.docker.compose.project.working_dir"] ?? "",
17543
- import_node_path41.default.join(request.repoPath, ".devcontainer")
17778
+ import_node_path42.default.join(request.repoPath, ".devcontainer")
17544
17779
  )
17545
17780
  );
17546
17781
  const projects = owned.map((container) => container.labels["com.docker.compose.project"]);
@@ -17627,7 +17862,7 @@ function proveLifecycleStopped() {
17627
17862
  throw new Error("Full stop proof requires an explicit stop worker.");
17628
17863
  const settle = () => {
17629
17864
  claimLifecycleEffect();
17630
- if (initialStopAbsence && !(0, import_node_util7.isDeepStrictEqual)(
17865
+ if (initialStopAbsence && !(0, import_node_util8.isDeepStrictEqual)(
17631
17866
  proveInitialManagedDevsyAbsence(request.repoPath, initialStopAbsence.record.devpodId),
17632
17867
  initialStopAbsence
17633
17868
  ))
@@ -17647,7 +17882,7 @@ function proveLifecycleStopped() {
17647
17882
  const remaining = inspectWorkspaceContainers().filter(
17648
17883
  (container) => sameWorkspacePath(
17649
17884
  container.labels["com.docker.compose.project.working_dir"] ?? "",
17650
- import_node_path41.default.join(request.repoPath, ".devcontainer")
17885
+ import_node_path42.default.join(request.repoPath, ".devcontainer")
17651
17886
  )
17652
17887
  );
17653
17888
  if (remaining.some(
@@ -17677,7 +17912,7 @@ function proveLifecycleStopped() {
17677
17912
  return void 0;
17678
17913
  });
17679
17914
  if (settlement) {
17680
- const capacity = createLifecycleCapacityStore(import_node_path41.default.join(DEVROUTER_HOME, "controller"));
17915
+ const capacity = createLifecycleCapacityStore(import_node_path42.default.join(DEVROUTER_HOME, "controller"));
17681
17916
  for (let attempt = 0; ; attempt++) {
17682
17917
  try {
17683
17918
  const state = capacity.inspect();
@@ -17851,7 +18086,7 @@ async function devpodExecOutcome(repoPath, command) {
17851
18086
  }
17852
18087
  assertRunningDevpod(devpod.id, repoPath);
17853
18088
  const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
17854
- const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto24.randomUUID)()}__:`;
18089
+ const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto25.randomUUID)()}__:`;
17855
18090
  const statusMarkerBytes = Buffer.from(statusMarker, "ascii");
17856
18091
  const literalCommand = command.map(quotePosixArg).join(" ");
17857
18092
  const wrappedCommand = `${literalCommand}; __devrouter_status=$?; printf '${statusMarker}%s\\n' "$__devrouter_status" >&2; exit 0`;
@@ -18041,11 +18276,11 @@ async function devpodExecOutcome(repoPath, command) {
18041
18276
 
18042
18277
  // src/core/workspace-lifecycle.ts
18043
18278
  var import_node_child_process34 = require("child_process");
18044
- var import_node_fs37 = __toESM(require("fs"));
18045
- var import_node_path42 = __toESM(require("path"));
18279
+ var import_node_fs38 = __toESM(require("fs"));
18280
+ var import_node_path43 = __toESM(require("path"));
18046
18281
 
18047
18282
  // src/core/route-state.ts
18048
- var import_node_fs36 = __toESM(require("fs"));
18283
+ var import_node_fs37 = __toESM(require("fs"));
18049
18284
  function listRoutesForWorktreePath(worktreePath, routes = listHostRouteState()) {
18050
18285
  return routes.filter((route) => sameWorkspacePath(route.repoPath, worktreePath));
18051
18286
  }