@ricsam/r5d-worker 0.0.90 → 0.0.92

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.
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var project_snapshot_recovery_runner_exports = {};
30
+ __export(project_snapshot_recovery_runner_exports, {
31
+ PROJECT_SNAPSHOT_RECOVERY_HELPER_COMMAND: () => PROJECT_SNAPSHOT_RECOVERY_HELPER_COMMAND,
32
+ recoverProjectSnapshotsInChild: () => recoverProjectSnapshotsInChild,
33
+ runProjectSnapshotRecoveryHelper: () => runProjectSnapshotRecoveryHelper
34
+ });
35
+ module.exports = __toCommonJS(project_snapshot_recovery_runner_exports);
36
+ var import_node_path = __toESM(require("node:path"), 1);
37
+ var import_node_child_process = require("node:child_process");
38
+ var import_project_worktrees = require("./project-worktrees.cjs");
39
+ const PROJECT_SNAPSHOT_RECOVERY_HELPER_COMMAND = "__recover-project-snapshots";
40
+ function requireAbsolutePath(value, label) {
41
+ if (!value) throw new Error(`Missing ${label}`);
42
+ const resolved = import_node_path.default.resolve(value);
43
+ if (resolved !== value) throw new Error(`${label} must be an absolute path`);
44
+ return resolved;
45
+ }
46
+ function parseHelperArguments(args) {
47
+ let projectsRoot;
48
+ let snapshotRoot;
49
+ for (let index = 0; index < args.length; index += 1) {
50
+ const argument = args[index];
51
+ if (argument === "--projects-root") {
52
+ projectsRoot = args[index + 1];
53
+ index += 1;
54
+ continue;
55
+ }
56
+ if (argument === "--snapshot-root") {
57
+ snapshotRoot = args[index + 1];
58
+ index += 1;
59
+ continue;
60
+ }
61
+ throw new Error(`Unknown project snapshot recovery helper argument: ${argument ?? "(missing)"}`);
62
+ }
63
+ return {
64
+ projectsRoot: requireAbsolutePath(projectsRoot, "projects root"),
65
+ snapshotRoot: requireAbsolutePath(snapshotRoot, "snapshot root")
66
+ };
67
+ }
68
+ function writeHelperMessage(message) {
69
+ process.stdout.write(`${JSON.stringify(message)}
70
+ `);
71
+ }
72
+ function runProjectSnapshotRecoveryHelper(args) {
73
+ const { projectsRoot, snapshotRoot } = parseHelperArguments(args);
74
+ const result = (0, import_project_worktrees.recoverStaleProjectWorktreeSnapshots)({
75
+ projectsRoot,
76
+ temporaryRoot: snapshotRoot,
77
+ onProgress: (progress) => writeHelperMessage({ type: "progress", progress })
78
+ });
79
+ writeHelperMessage({ type: "result", result });
80
+ return result.failed.length === 0 ? 0 : 1;
81
+ }
82
+ function isRecoveryProgress(value) {
83
+ if (!value || typeof value !== "object") return false;
84
+ const progress = value;
85
+ return progress.operation === "restore" && (progress.phase === "scanning" || progress.phase === "copying" || progress.phase === "moving" || progress.phase === "syncing" || progress.phase === "cleaning") && typeof progress.projectRoot === "string" && Number.isSafeInteger(progress.completedBranches) && Number.isSafeInteger(progress.totalBranches) && typeof progress.completedBytes === "number" && Number.isFinite(progress.completedBytes) && (progress.totalBytes === void 0 || typeof progress.totalBytes === "number" && Number.isFinite(progress.totalBytes));
86
+ }
87
+ function isRecoveryResult(value) {
88
+ if (!value || typeof value !== "object") return false;
89
+ const result = value;
90
+ return Array.isArray(result.restored) && Array.isArray(result.removed) && Array.isArray(result.failed);
91
+ }
92
+ function recoveryChildEnvironment() {
93
+ const environment = {};
94
+ for (const name of ["PATH", "TMPDIR", "TMP", "TEMP", "SystemRoot", "WINDIR"]) {
95
+ const value = process.env[name];
96
+ if (value !== void 0) environment[name] = value;
97
+ }
98
+ return environment;
99
+ }
100
+ async function recoverProjectSnapshotsInChild(input) {
101
+ const entrypoint = process.argv[1];
102
+ if (!entrypoint) throw new Error("Cannot locate the r5d-worker runtime entrypoint for snapshot recovery");
103
+ const child = (0, import_node_child_process.spawn)(
104
+ process.execPath,
105
+ [
106
+ entrypoint,
107
+ PROJECT_SNAPSHOT_RECOVERY_HELPER_COMMAND,
108
+ "--projects-root",
109
+ import_node_path.default.resolve(input.projectsRoot),
110
+ "--snapshot-root",
111
+ import_node_path.default.resolve(input.snapshotRoot)
112
+ ],
113
+ {
114
+ env: recoveryChildEnvironment(),
115
+ stdio: ["ignore", "pipe", "pipe"]
116
+ }
117
+ );
118
+ const terminateOnParentExit = () => {
119
+ if (child.exitCode === null && child.signalCode === null) child.kill("SIGTERM");
120
+ };
121
+ const parentProcessEvents = process;
122
+ parentProcessEvents.once("exit", terminateOnParentExit);
123
+ let stdout = "";
124
+ let stderr = "";
125
+ let result;
126
+ child.stdout.setEncoding("utf8");
127
+ child.stdout.on("data", (chunk) => {
128
+ stdout += chunk;
129
+ while (true) {
130
+ const newline = stdout.indexOf("\n");
131
+ if (newline < 0) break;
132
+ const line = stdout.slice(0, newline);
133
+ stdout = stdout.slice(newline + 1);
134
+ if (!line.trim()) continue;
135
+ let message;
136
+ try {
137
+ message = JSON.parse(line);
138
+ } catch {
139
+ stderr += `Invalid recovery helper output: ${line}
140
+ `;
141
+ continue;
142
+ }
143
+ if (!message || typeof message !== "object") continue;
144
+ const record = message;
145
+ if (record.type === "progress" && isRecoveryProgress(record.progress)) input.onProgress?.(record.progress);
146
+ if (record.type === "result" && isRecoveryResult(record.result)) result = record.result;
147
+ }
148
+ });
149
+ child.stderr.setEncoding("utf8");
150
+ child.stderr.on("data", (chunk) => {
151
+ stderr = `${stderr}${chunk}`.slice(-32768);
152
+ });
153
+ const exitCode = await new Promise((resolve, reject) => {
154
+ child.once("error", reject);
155
+ child.once("close", (code, signal) => {
156
+ if (signal) reject(new Error(`Project snapshot recovery helper exited on signal ${signal}`));
157
+ else resolve(code ?? 1);
158
+ });
159
+ }).finally(() => parentProcessEvents.off("exit", terminateOnParentExit));
160
+ if (exitCode !== 0 || !result) {
161
+ const detail = stderr.trim();
162
+ throw new Error(detail || `Project snapshot recovery helper exited with status ${exitCode}`);
163
+ }
164
+ return result;
165
+ }
166
+ // Annotate the CommonJS export names for ESM import in node:
167
+ 0 && (module.exports = {
168
+ PROJECT_SNAPSHOT_RECOVERY_HELPER_COMMAND,
169
+ recoverProjectSnapshotsInChild,
170
+ runProjectSnapshotRecoveryHelper
171
+ });
@@ -318,7 +318,7 @@ function normalizeSnapshotManifest(value) {
318
318
  if (record.version !== PROJECT_WORKTREE_SNAPSHOT_MANIFEST_VERSION) {
319
319
  throw new Error("Unsupported project worktree snapshot manifest version");
320
320
  }
321
- if (record.state !== "building" && record.state !== "prepared" && record.state !== "consumed") {
321
+ if (record.state !== "building" && record.state !== "prepared" && record.state !== "restoring" && record.state !== "consumed") {
322
322
  throw new Error("Project worktree snapshot manifest has an invalid state");
323
323
  }
324
324
  if (!Number.isSafeInteger(record.ownerProcessId) || record.ownerProcessId <= 0) {
@@ -399,7 +399,7 @@ function assertSameSnapshotIdentity(expected, actual) {
399
399
  function advanceSnapshotState(snapshotRoot, expected, state) {
400
400
  const current = readSnapshotManifest(snapshotRoot);
401
401
  assertSameSnapshotIdentity(expected, current);
402
- const allowed = current.state === "building" && state === "prepared" || current.state === "prepared" && state === "consumed" || current.state === state;
402
+ const allowed = current.state === "building" && state === "prepared" || current.state === "prepared" && state === "restoring" || current.state === "prepared" && state === "consumed" || current.state === "restoring" && state === "consumed" || current.state === state;
403
403
  if (!allowed) throw new Error(`Project worktree snapshot cannot advance from ${current.state} to ${state}`);
404
404
  return current.state === state ? current : writeSnapshotManifest(snapshotRoot, { ...current, state });
405
405
  }
@@ -423,6 +423,20 @@ function copyCheckoutTree(sourceRoot, targetRoot) {
423
423
  verbatimSymlinks: true
424
424
  });
425
425
  }
426
+ function measureCheckoutTree(root) {
427
+ const stat = import_node_fs.default.lstatSync(root);
428
+ if (stat.isSymbolicLink()) return 0;
429
+ if (stat.isFile()) return stat.size;
430
+ if (!stat.isDirectory()) return 0;
431
+ let bytes = 0;
432
+ for (const entry of import_node_fs.default.readdirSync(root).sort()) bytes += measureCheckoutTree(import_node_path.default.join(root, entry));
433
+ return bytes;
434
+ }
435
+ function snapshotBranchMeasurements(branches, sourcePath) {
436
+ return new Map(
437
+ branches.map(({ branchName, snapshotPresent }) => [branchName, snapshotPresent ? measureCheckoutTree(sourcePath(branchName)) : 0])
438
+ );
439
+ }
426
440
  function assertNoOutstandingSnapshotForProject(temporaryRoot, projectRoot) {
427
441
  let entries;
428
442
  try {
@@ -445,7 +459,7 @@ function assertNoOutstandingSnapshotForProject(temporaryRoot, projectRoot) {
445
459
  }
446
460
  }
447
461
  }
448
- function snapshotBranchTrees(projectRoot, branches, temporaryRoot) {
462
+ function snapshotBranchTrees(projectRoot, branches, temporaryRoot, onProgress) {
449
463
  const resolvedProjectRoot = import_node_path.default.resolve(projectRoot);
450
464
  const resolvedTemporaryRoot = import_node_path.default.resolve(temporaryRoot ?? import_node_os.default.tmpdir());
451
465
  assertNoOutstandingSnapshotForProject(resolvedTemporaryRoot, resolvedProjectRoot);
@@ -472,13 +486,57 @@ function snapshotBranchTrees(projectRoot, branches, temporaryRoot) {
472
486
  branches: branchSnapshots,
473
487
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
474
488
  });
489
+ onProgress?.({
490
+ operation: "snapshot",
491
+ phase: "scanning",
492
+ projectRoot: resolvedProjectRoot,
493
+ completedBranches: 0,
494
+ totalBranches: branchSnapshots.length,
495
+ completedBytes: 0
496
+ });
497
+ const branchBytes = snapshotBranchMeasurements(branchSnapshots, (branchName) => branchPath(resolvedProjectRoot, branchName));
498
+ const totalBytes = [...branchBytes.values()].reduce((total, bytes) => total + bytes, 0);
499
+ let completedBytes = 0;
500
+ let completedBranches = 0;
475
501
  for (const { branchName, snapshotPresent } of branchSnapshots) {
476
- if (!snapshotPresent) continue;
477
- const snapshotPath = fullSnapshotPath(root, branchName);
478
- copyCheckoutTree(branchPath(resolvedProjectRoot, branchName), snapshotPath);
479
- paths.set(branchName, snapshotPath);
502
+ onProgress?.({
503
+ operation: "snapshot",
504
+ phase: "copying",
505
+ projectRoot: resolvedProjectRoot,
506
+ branchName,
507
+ completedBranches,
508
+ totalBranches: branchSnapshots.length,
509
+ completedBytes,
510
+ totalBytes
511
+ });
512
+ if (snapshotPresent) {
513
+ const snapshotPath = fullSnapshotPath(root, branchName);
514
+ copyCheckoutTree(branchPath(resolvedProjectRoot, branchName), snapshotPath);
515
+ paths.set(branchName, snapshotPath);
516
+ }
517
+ completedBytes += branchBytes.get(branchName) ?? 0;
518
+ completedBranches += 1;
519
+ onProgress?.({
520
+ operation: "snapshot",
521
+ phase: "copying",
522
+ projectRoot: resolvedProjectRoot,
523
+ branchName,
524
+ completedBranches,
525
+ totalBranches: branchSnapshots.length,
526
+ completedBytes,
527
+ totalBytes
528
+ });
480
529
  }
481
530
  const treesRoot = import_node_path.default.join(root, "trees");
531
+ onProgress?.({
532
+ operation: "snapshot",
533
+ phase: "syncing",
534
+ projectRoot: resolvedProjectRoot,
535
+ completedBranches,
536
+ totalBranches: branchSnapshots.length,
537
+ completedBytes,
538
+ totalBytes
539
+ });
482
540
  if (import_node_fs.default.existsSync(treesRoot)) fsyncTree(treesRoot);
483
541
  manifest = advanceSnapshotState(root, manifest, "prepared");
484
542
  return { root, manifest, paths };
@@ -487,33 +545,127 @@ function snapshotBranchTrees(projectRoot, branches, temporaryRoot) {
487
545
  throw error;
488
546
  }
489
547
  }
490
- function restoreProjectWorktreeSnapshot(snapshotRoot, manifest) {
491
- const current = readSnapshotManifest(snapshotRoot);
548
+ function restoreProjectWorktreeSnapshot(snapshotRoot, manifest, onProgress) {
549
+ let current = readSnapshotManifest(snapshotRoot);
492
550
  assertSameSnapshotIdentity(manifest, current);
493
- if (current.state !== "prepared") throw new Error(`Project worktree snapshot is ${current.state}, not prepared`);
551
+ if (current.state !== "prepared" && current.state !== "restoring") {
552
+ throw new Error(`Project worktree snapshot is ${current.state}, not prepared or restoring`);
553
+ }
554
+ const treesRoot = import_node_path.default.join(snapshotRoot, "trees");
555
+ const treesRootPresent = import_node_fs.default.existsSync(treesRoot);
556
+ const treesRootStat = treesRootPresent ? import_node_fs.default.lstatSync(treesRoot) : null;
557
+ if (treesRootStat && (!treesRootStat.isDirectory() || treesRootStat.isSymbolicLink())) {
558
+ throw new Error("Project worktree snapshot trees root must be a real directory");
559
+ }
560
+ const branchSourceRoot = current.state === "restoring" && !treesRootPresent ? current.projectRoot : treesRoot;
494
561
  for (const { branchName, snapshotPresent } of current.branches) {
495
562
  const targetPath = branchPath(current.projectRoot, branchName);
496
563
  assertNoSymlinkBranchPathComponents(current.projectRoot, targetPath);
497
- if (!snapshotPresent) continue;
498
- const sourcePath = fullSnapshotPath(snapshotRoot, branchName);
564
+ const sourcePath = import_node_path.default.join(branchSourceRoot, ...branchName.split("/"));
565
+ if (!snapshotPresent) {
566
+ if (current.state === "restoring" && !treesRootPresent && import_node_fs.default.existsSync(sourcePath)) {
567
+ throw new Error(`Restored project branch unexpectedly exists: ${branchName}`);
568
+ }
569
+ continue;
570
+ }
499
571
  const stat = import_node_fs.default.lstatSync(sourcePath);
500
572
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
501
573
  throw new Error(`Project worktree snapshot branch must be a real directory: ${branchName}`);
502
574
  }
503
575
  }
576
+ onProgress?.({
577
+ operation: "restore",
578
+ phase: "scanning",
579
+ projectRoot: current.projectRoot,
580
+ completedBranches: 0,
581
+ totalBranches: current.branches.length,
582
+ completedBytes: 0
583
+ });
584
+ const branchBytes = snapshotBranchMeasurements(current.branches, (branchName) => import_node_path.default.join(branchSourceRoot, ...branchName.split("/")));
585
+ const totalBytes = [...branchBytes.values()].reduce((total, bytes) => total + bytes, 0);
586
+ const presentBranches = current.branches.filter(({ snapshotPresent }) => snapshotPresent);
587
+ const destinationParent = import_node_path.default.dirname(current.projectRoot);
588
+ import_node_fs.default.mkdirSync(destinationParent, { recursive: true });
589
+ const canMoveWholeSnapshot = presentBranches.length > 0 && (current.state === "restoring" || treesRootStat !== null && treesRootStat.dev === import_node_fs.default.lstatSync(destinationParent).dev);
590
+ if (canMoveWholeSnapshot) {
591
+ if (current.state === "prepared") current = advanceSnapshotState(snapshotRoot, current, "restoring");
592
+ onProgress?.({
593
+ operation: "restore",
594
+ phase: "moving",
595
+ projectRoot: current.projectRoot,
596
+ completedBranches: treesRootPresent ? 0 : current.branches.length,
597
+ totalBranches: current.branches.length,
598
+ completedBytes: treesRootPresent ? 0 : totalBytes,
599
+ totalBytes
600
+ });
601
+ if (treesRootPresent) {
602
+ import_node_fs.default.rmSync(current.projectRoot, { recursive: true, force: true });
603
+ fsyncDirectoryChain(destinationParent, import_node_path.default.dirname(destinationParent));
604
+ import_node_fs.default.renameSync(treesRoot, current.projectRoot);
605
+ fsyncDirectory(snapshotRoot);
606
+ }
607
+ fsyncManagedProjectRootEntry(current.projectRoot);
608
+ onProgress?.({
609
+ operation: "restore",
610
+ phase: "moving",
611
+ projectRoot: current.projectRoot,
612
+ completedBranches: current.branches.length,
613
+ totalBranches: current.branches.length,
614
+ completedBytes: totalBytes,
615
+ totalBytes
616
+ });
617
+ return;
618
+ }
619
+ if (current.state === "restoring") {
620
+ throw new Error("Project worktree snapshot entered atomic restore state without a movable snapshot tree");
621
+ }
504
622
  for (const { branchName } of [...current.branches].sort((left, right) => right.branchName.length - left.branchName.length)) {
505
623
  const targetPath = branchPath(current.projectRoot, branchName);
506
624
  const targetParent = import_node_path.default.dirname(targetPath);
507
625
  import_node_fs.default.rmSync(targetPath, { recursive: true, force: true });
508
626
  if (import_node_fs.default.existsSync(targetParent)) fsyncDirectoryChain(targetParent, current.projectRoot);
509
627
  }
628
+ let completedBytes = 0;
629
+ let completedBranches = 0;
510
630
  for (const { branchName, snapshotPresent } of current.branches) {
511
- if (!snapshotPresent) continue;
512
- const targetPath = branchPath(current.projectRoot, branchName);
513
- copyCheckoutTree(fullSnapshotPath(snapshotRoot, branchName), targetPath);
514
- fsyncTree(targetPath);
515
- fsyncDirectoryChain(import_node_path.default.dirname(targetPath), current.projectRoot);
631
+ onProgress?.({
632
+ operation: "restore",
633
+ phase: "copying",
634
+ projectRoot: current.projectRoot,
635
+ branchName,
636
+ completedBranches,
637
+ totalBranches: current.branches.length,
638
+ completedBytes,
639
+ totalBytes
640
+ });
641
+ if (snapshotPresent) {
642
+ const targetPath = branchPath(current.projectRoot, branchName);
643
+ copyCheckoutTree(fullSnapshotPath(snapshotRoot, branchName), targetPath);
644
+ fsyncTree(targetPath);
645
+ fsyncDirectoryChain(import_node_path.default.dirname(targetPath), current.projectRoot);
646
+ }
647
+ completedBytes += branchBytes.get(branchName) ?? 0;
648
+ completedBranches += 1;
649
+ onProgress?.({
650
+ operation: "restore",
651
+ phase: "copying",
652
+ projectRoot: current.projectRoot,
653
+ branchName,
654
+ completedBranches,
655
+ totalBranches: current.branches.length,
656
+ completedBytes,
657
+ totalBytes
658
+ });
516
659
  }
660
+ onProgress?.({
661
+ operation: "restore",
662
+ phase: "syncing",
663
+ projectRoot: current.projectRoot,
664
+ completedBranches,
665
+ totalBranches: current.branches.length,
666
+ completedBytes,
667
+ totalBytes
668
+ });
517
669
  import_node_fs.default.mkdirSync(current.projectRoot, { recursive: true });
518
670
  fsyncManagedProjectRootEntry(current.projectRoot);
519
671
  }
@@ -534,7 +686,7 @@ function persistReconciledProjectAndConsumeSnapshot(snapshot, afterProjectTreeFs
534
686
  function rollbackProjectWorktreeSnapshot(snapshot) {
535
687
  const current = readSnapshotManifest(snapshot.root);
536
688
  assertSameSnapshotIdentity(snapshot.manifest, current);
537
- if (current.state === "prepared") {
689
+ if (current.state === "prepared" || current.state === "restoring") {
538
690
  restoreProjectWorktreeSnapshot(snapshot.root, current);
539
691
  consumeAndRemoveProjectWorktreeSnapshot(snapshot.root, current);
540
692
  return;
@@ -616,8 +768,8 @@ function recoverStaleProjectWorktreeSnapshots(input) {
616
768
  assertNoSymlinkPathComponents(projectsRoot, manifest.projectRoot);
617
769
  const ownedByThisProcessSession = manifest.ownerProcessId === currentProcessId && manifest.ownerSessionId === currentOwnerSessionId;
618
770
  if (ownedByThisProcessSession || ownerProcessId !== currentProcessId && processAlive(ownerProcessId)) continue;
619
- if (manifest.state === "prepared") {
620
- restoreProjectWorktreeSnapshot(candidate, manifest);
771
+ if (manifest.state === "prepared" || manifest.state === "restoring") {
772
+ restoreProjectWorktreeSnapshot(candidate, manifest, input.onProgress);
621
773
  const consumed = advanceSnapshotState(candidate, manifest, "consumed");
622
774
  removeSnapshotStorage(candidate);
623
775
  assertSameSnapshotIdentity(manifest, consumed);
@@ -803,7 +955,11 @@ async function initializeLinkedLayout(input) {
803
955
  );
804
956
  const checkoutPath = branchPath(input.projectRoot, branch.branchName);
805
957
  import_node_fs.default.mkdirSync(import_node_path.default.dirname(checkoutPath), { recursive: true });
806
- await gitAsync(primaryPath, ["worktree", "add", "--force", checkoutPath, branch.branchName], `create linked worktree ${branch.branchName}`);
958
+ await gitAsync(
959
+ primaryPath,
960
+ ["worktree", "add", "--force", checkoutPath, branch.branchName],
961
+ `create linked worktree ${branch.branchName}`
962
+ );
807
963
  }
808
964
  return mirrorHeads;
809
965
  }
@@ -837,7 +993,11 @@ async function reconcileLinkedLayout(input) {
837
993
  import_node_fs.default.rmSync(checkoutPath, { recursive: true, force: true });
838
994
  await gitAsync(primaryPath, ["branch", "-f", branch.branchName, resolved.seed], `anchor project branch ${branch.branchName}`);
839
995
  import_node_fs.default.mkdirSync(import_node_path.default.dirname(checkoutPath), { recursive: true });
840
- await gitAsync(primaryPath, ["worktree", "add", "--force", checkoutPath, branch.branchName], `create linked worktree ${branch.branchName}`);
996
+ await gitAsync(
997
+ primaryPath,
998
+ ["worktree", "add", "--force", checkoutPath, branch.branchName],
999
+ `create linked worktree ${branch.branchName}`
1000
+ );
841
1001
  }
842
1002
  return { mirrorHeads, mutatedBranches };
843
1003
  }
@@ -864,7 +1024,7 @@ async function ensureProjectWorktrees(input) {
864
1024
  assertNoOutstandingSnapshotForProject(snapshotStorageRoot, projectRoot);
865
1025
  let snapshot;
866
1026
  const ensureSnapshot = () => {
867
- snapshot ??= snapshotBranchTrees(projectRoot, input.branches, snapshotStorageRoot);
1027
+ snapshot ??= snapshotBranchTrees(projectRoot, input.branches, snapshotStorageRoot, input.onSnapshotProgress);
868
1028
  return snapshot;
869
1029
  };
870
1030
  try {
@@ -21,10 +21,12 @@ __export(supervisor_exports, {
21
21
  WORKER_CREDENTIAL_RESTART_EXIT_CODE: () => WORKER_CREDENTIAL_RESTART_EXIT_CODE,
22
22
  WORKER_RECONNECT_DELAY_MS: () => WORKER_RECONNECT_DELAY_MS,
23
23
  WORKER_RECONNECT_EXIT_CODE: () => WORKER_RECONNECT_EXIT_CODE,
24
+ WORKER_RECONNECT_JITTER_MS: () => WORKER_RECONNECT_JITTER_MS,
24
25
  WORKER_RELOAD_EXIT_CODE: () => WORKER_RELOAD_EXIT_CODE,
25
26
  WORKER_RUNTIME_ENV: () => WORKER_RUNTIME_ENV,
26
27
  isRetryableWorkerServerStatus: () => isRetryableWorkerServerStatus,
27
- superviseWorkerRuntime: () => superviseWorkerRuntime
28
+ superviseWorkerRuntime: () => superviseWorkerRuntime,
29
+ workerReconnectDelayMs: () => workerReconnectDelayMs
28
30
  });
29
31
  module.exports = __toCommonJS(supervisor_exports);
30
32
  var import_node_os = require("node:os");
@@ -34,6 +36,7 @@ const WORKER_RELOAD_EXIT_CODE = 75;
34
36
  const WORKER_RECONNECT_EXIT_CODE = 76;
35
37
  const WORKER_CREDENTIAL_RESTART_EXIT_CODE = 77;
36
38
  const WORKER_RECONNECT_DELAY_MS = 2e3;
39
+ const WORKER_RECONNECT_JITTER_MS = 3e3;
37
40
  const SUPERVISOR_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
38
41
  function defaultRuntimeSpawner(argv, options) {
39
42
  return Bun.spawn(argv, options);
@@ -41,6 +44,10 @@ function defaultRuntimeSpawner(argv, options) {
41
44
  function defaultRuntimeDelay(delayMs) {
42
45
  return new Promise((resolve) => setTimeout(resolve, delayMs));
43
46
  }
47
+ function workerReconnectDelayMs(random = Math.random) {
48
+ const sample = Math.max(0, Math.min(1, random()));
49
+ return WORKER_RECONNECT_DELAY_MS + Math.floor(sample * WORKER_RECONNECT_JITTER_MS);
50
+ }
44
51
  async function defaultRuntimeReaper(runtime) {
45
52
  await (0, import_process_tree.terminateProcessTree)(runtime);
46
53
  }
@@ -53,7 +60,7 @@ function signalExitCode(signal) {
53
60
  function osSignalNumber(signal) {
54
61
  return import_node_os.constants.signals[signal];
55
62
  }
56
- async function superviseWorkerRuntime(argv, env = process.env, spawnRuntime = defaultRuntimeSpawner, waitBeforeReconnect = defaultRuntimeDelay, reapCredentialRuntime = defaultRuntimeReaper) {
63
+ async function superviseWorkerRuntime(argv, env = process.env, spawnRuntime = defaultRuntimeSpawner, waitBeforeReconnect = defaultRuntimeDelay, reapCredentialRuntime = defaultRuntimeReaper, reconnectDelay = workerReconnectDelayMs) {
57
64
  if (!process.argv[1]) {
58
65
  throw new Error("Cannot locate the r5d-worker entrypoint");
59
66
  }
@@ -105,9 +112,10 @@ async function superviseWorkerRuntime(argv, env = process.env, spawnRuntime = de
105
112
  continue;
106
113
  }
107
114
  if (exitCode === WORKER_RECONNECT_EXIT_CODE) {
108
- process.stderr.write(`[r5d-worker] reconnecting in ${WORKER_RECONNECT_DELAY_MS}ms
115
+ const reconnectDelayMs = reconnectDelay();
116
+ process.stderr.write(`[r5d-worker] reconnecting in ${reconnectDelayMs}ms
109
117
  `);
110
- await waitBeforeReconnect(WORKER_RECONNECT_DELAY_MS);
118
+ await waitBeforeReconnect(reconnectDelayMs);
111
119
  if (receivedSignal) return signalExitCode(receivedSignal);
112
120
  continue;
113
121
  }
@@ -123,8 +131,10 @@ async function superviseWorkerRuntime(argv, env = process.env, spawnRuntime = de
123
131
  WORKER_CREDENTIAL_RESTART_EXIT_CODE,
124
132
  WORKER_RECONNECT_DELAY_MS,
125
133
  WORKER_RECONNECT_EXIT_CODE,
134
+ WORKER_RECONNECT_JITTER_MS,
126
135
  WORKER_RELOAD_EXIT_CODE,
127
136
  WORKER_RUNTIME_ENV,
128
137
  isRetryableWorkerServerStatus,
129
- superviseWorkerRuntime
138
+ superviseWorkerRuntime,
139
+ workerReconnectDelayMs
130
140
  });