@ricsam/r5d-worker 0.0.100 → 0.0.103

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 (33) hide show
  1. package/README.md +16 -3
  2. package/dist/cjs/main.cjs +390 -322
  3. package/dist/cjs/package.json +1 -1
  4. package/dist/cjs/project-mirror-fetch-policy.cjs +48 -7
  5. package/dist/cjs/project-mirror-refs-token.cjs +36 -2
  6. package/dist/cjs/project-worktrees.cjs +49 -32
  7. package/dist/cjs/working-tree-mirror.cjs +347 -99
  8. package/dist/cjs/workspace-git-sync.cjs +189 -28
  9. package/dist/cjs/workspace-merge-projection.cjs +5 -19
  10. package/dist/cjs/workspace-mutation-gate.cjs +35 -0
  11. package/dist/cjs/workspace-projection-ledger.cjs +81 -0
  12. package/dist/cjs/workspace-sync-coalescer.cjs +58 -0
  13. package/dist/mjs/main.mjs +395 -324
  14. package/dist/mjs/package.json +1 -1
  15. package/dist/mjs/project-mirror-fetch-policy.mjs +46 -6
  16. package/dist/mjs/project-mirror-refs-token.mjs +30 -1
  17. package/dist/mjs/project-worktrees.mjs +52 -33
  18. package/dist/mjs/working-tree-mirror.mjs +347 -99
  19. package/dist/mjs/workspace-git-sync.mjs +189 -28
  20. package/dist/mjs/workspace-merge-projection.mjs +5 -19
  21. package/dist/mjs/workspace-mutation-gate.mjs +35 -0
  22. package/dist/mjs/workspace-projection-ledger.mjs +57 -0
  23. package/dist/mjs/workspace-sync-coalescer.mjs +34 -0
  24. package/dist/types/main.d.ts +29 -17
  25. package/dist/types/project-mirror-fetch-policy.d.ts +70 -15
  26. package/dist/types/project-mirror-refs-token.d.ts +34 -0
  27. package/dist/types/project-worktrees.d.ts +26 -8
  28. package/dist/types/working-tree-mirror.d.ts +56 -11
  29. package/dist/types/workspace-git-sync.d.ts +41 -4
  30. package/dist/types/workspace-mutation-gate.d.ts +24 -3
  31. package/dist/types/workspace-projection-ledger.d.ts +59 -0
  32. package/dist/types/workspace-sync-coalescer.d.ts +28 -0
  33. package/package.json +1 -1
package/dist/cjs/main.cjs CHANGED
@@ -72,6 +72,8 @@ var import_workspace_incident_state = require("./workspace-incident-state.cjs");
72
72
  var import_workspace_automatic_sync_policy = require("./workspace-automatic-sync-policy.cjs");
73
73
  var import_workspace_command_sync_policy = require("./workspace-command-sync-policy.cjs");
74
74
  var import_workspace_mutation_gate = require("./workspace-mutation-gate.cjs");
75
+ var import_workspace_projection_ledger = require("./workspace-projection-ledger.cjs");
76
+ var import_workspace_sync_coalescer = require("./workspace-sync-coalescer.cjs");
75
77
  var import_workspace_path_move = require("./workspace-path-move.cjs");
76
78
  var import_workspace_project_config_policy = require("./workspace-project-config-policy.cjs");
77
79
  var import_workspace_preserve_only_policy = require("./workspace-preserve-only-policy.cjs");
@@ -175,6 +177,12 @@ const workspaceSyncSingleFlight = {
175
177
  acquireMutation() {
176
178
  return workspaceMutationGate.acquireMutation();
177
179
  },
180
+ runBranchMutation(operation) {
181
+ return workspaceMutationGate.runBranchMutation(operation);
182
+ },
183
+ branchMutationQueuePosition() {
184
+ return workspaceMutationGate.branchMutationQueuePosition();
185
+ },
178
186
  afterCurrent() {
179
187
  return workspaceSyncQueue;
180
188
  }
@@ -305,6 +313,7 @@ const workerPtyTestHarness = {
305
313
  removeEnvFiles: removePtyEnvFiles,
306
314
  resolveEnvFileReferences: resolvePtyEnvFileReferences,
307
315
  commandHasWorkspaceEffect: workerCommandHasWorkspaceEffect,
316
+ targetProcessEnv,
308
317
  canonicalSyncTerminalHead,
309
318
  ptyIsWorkspaceBusy: workerPtyIsWorkspaceBusy,
310
319
  parseLinuxForegroundBusy: parseLinuxPtyForegroundBusy
@@ -312,21 +321,12 @@ const workerPtyTestHarness = {
312
321
  function defaultConfigPath() {
313
322
  return import_node_path.default.join(import_node_os.default.homedir(), ".config", "r5d", "r5dctl", "config.json");
314
323
  }
315
- function defaultProjectsRoot() {
316
- return import_node_path.default.join(import_node_os.default.homedir(), ".r5d", "projects");
317
- }
318
- function defaultSyncRoot() {
319
- return import_node_path.default.join(import_node_os.default.homedir(), ".r5d", "sync");
320
- }
321
- function defaultArtifactRoot() {
322
- return import_node_path.default.join(import_node_os.default.homedir(), ".r5d", "artifacts");
323
- }
324
- function defaultPlanRoot() {
325
- return import_node_path.default.join(import_node_os.default.homedir(), ".r5d", "plans");
324
+ function defaultRootDir() {
325
+ return import_node_path.default.join(import_node_os.default.homedir(), ".r5d");
326
326
  }
327
327
  function printHelp() {
328
328
  process.stdout.write(`Usage:
329
- r5d-worker start --label <label> [--base-url <url>] [--token <token>] [--api-key <key>]
329
+ r5d-worker start --label <label> [--root <dir>] [--base-url <url>] [--token <token>] [--api-key <key>]
330
330
  r5d-worker --version
331
331
 
332
332
  Options:
@@ -335,10 +335,7 @@ Options:
335
335
  --token <token> r5d worker token
336
336
  --api-key <key> r5d API key
337
337
  --config <path> Config path (default ~/.config/r5d/r5dctl/config.json)
338
- --project-root <dir> Override projects checkout root (default ~/.r5d/projects)
339
- --sync-root <dir> Override r5d internal sync git root (default ~/.r5d/sync)
340
- --artifact-root <dir> Override session artifact root (default from R5D_ARTIFACTS_ROOT or ~/.r5d/artifacts)
341
- --plan-root <dir> Override synced plan root (default from R5D_PLANS_ROOT or ~/.r5d/plans)
338
+ --root <dir> Override worker data root (default from R5D_ROOT or ~/.r5d)
342
339
  -v, --version Show r5d-worker version
343
340
  -h, --help Show this help
344
341
  `);
@@ -453,24 +450,9 @@ function parseArgs(argv) {
453
450
  options.label = label;
454
451
  continue;
455
452
  }
456
- const projectRoot = readOption("--project-root");
457
- if (projectRoot !== void 0) {
458
- options.projectRoot = projectRoot;
459
- continue;
460
- }
461
- const syncRoot = readOption("--sync-root");
462
- if (syncRoot !== void 0) {
463
- options.syncRoot = syncRoot;
464
- continue;
465
- }
466
- const artifactRoot = readOption("--artifact-root");
467
- if (artifactRoot !== void 0) {
468
- options.artifactRoot = artifactRoot;
469
- continue;
470
- }
471
- const planRoot = readOption("--plan-root");
472
- if (planRoot !== void 0) {
473
- options.planRoot = planRoot;
453
+ const rootDir = readOption("--root");
454
+ if (rootDir !== void 0) {
455
+ options.rootDir = rootDir;
474
456
  continue;
475
457
  }
476
458
  rest.push(arg);
@@ -515,11 +497,9 @@ async function readResponseText(response) {
515
497
  return "";
516
498
  }
517
499
  }
518
- const R5D_ARTIFACTS_DIR_REF = "$R5D_ARTIFACTS_DIR";
519
- const R5D_PLANS_DIR_REF = "$R5D_PLANS_DIR";
520
- const R5D_ACTIVE_PLAN_FILE_REF = "$R5D_ACTIVE_PLAN_FILE";
521
- const R5D_PLANS_DIR_ENV = "R5D_PLANS_DIR";
522
- const BUILT_IN_TOOL_PATH_REFS = [R5D_ARTIFACTS_DIR_REF, R5D_PLANS_DIR_REF, R5D_ACTIVE_PLAN_FILE_REF];
500
+ const R5D_ROOT_REF = "$R5D_ROOT";
501
+ const R5D_ROOT_ENV = "R5D_ROOT";
502
+ const BUILT_IN_TOOL_PATH_REFS = [R5D_ROOT_REF];
523
503
  function parseBuiltInToolPath(inputPath) {
524
504
  const normalized = inputPath.replace(/\\/g, "/");
525
505
  const match = /^\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))(?:\/(.*))?$/.exec(normalized);
@@ -535,6 +515,26 @@ function parseBuiltInToolPath(inputPath) {
535
515
  relativePath: match[3] ?? ""
536
516
  };
537
517
  }
518
+ function expandRootPathSegments(relativePath, builtInPaths) {
519
+ const values = {
520
+ R5D_SESSION_ID: builtInPaths.sessionId,
521
+ R5D_PROJECT_ID: builtInPaths.projectId,
522
+ R5D_BRANCH_NAME: builtInPaths.branchName,
523
+ R5D_ACTIVE_PLAN_ID: builtInPaths.activePlanId
524
+ };
525
+ let expandedPath = relativePath;
526
+ for (const [variableName, value] of Object.entries(values)) {
527
+ const variablePattern = new RegExp(`\\$(?:\\{${variableName}\\}|${variableName})(?=\\/|\\.|$)`, "g");
528
+ if (!variablePattern.test(expandedPath)) continue;
529
+ if (!value) throw new Error(`$${variableName} is unavailable for this worker path`);
530
+ expandedPath = expandedPath.replace(variablePattern, value);
531
+ }
532
+ const unsupportedVariable = /\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/.exec(expandedPath);
533
+ if (unsupportedVariable) {
534
+ throw new Error(`Unsupported ${R5D_ROOT_REF} path variable "$${unsupportedVariable[1] ?? unsupportedVariable[2]}"`);
535
+ }
536
+ return expandedPath.split("/").filter(Boolean);
537
+ }
538
538
  function validateArtifactSessionId(sessionId) {
539
539
  if (!/^(migration-)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(sessionId)) {
540
540
  throw new Error(`Invalid artifact session id: ${sessionId}`);
@@ -550,14 +550,6 @@ function sessionArtifactDir(artifactRoot, sessionId) {
550
550
  validateArtifactSessionId(sessionId);
551
551
  return import_node_path.default.join(artifactRoot, sessionId);
552
552
  }
553
- function artifactEnv(artifactRoot, sessionId) {
554
- if (!sessionId) {
555
- return {};
556
- }
557
- return {
558
- R5D_ARTIFACTS_DIR: sessionArtifactDir(artifactRoot, sessionId)
559
- };
560
- }
561
553
  function projectPlanDir(planRoot, projectId, branchName) {
562
554
  validateProjectId(projectId);
563
555
  validateBranchName(branchName);
@@ -570,21 +562,18 @@ function targetPlanDir(planRoot, target) {
570
562
  }
571
563
  return import_node_path.default.join(planRoot, "workspace");
572
564
  }
573
- function planEnv(planRoot, target, activePlanId) {
574
- const plansDir = targetPlanDir(planRoot, target);
575
- const env = {
576
- [R5D_PLANS_DIR_ENV]: plansDir
577
- };
565
+ function planEnv(activePlanId) {
566
+ const env = {};
578
567
  if (activePlanId) {
579
568
  validatePlanId(activePlanId);
580
569
  env.R5D_ACTIVE_PLAN_ID = activePlanId;
581
- env.R5D_ACTIVE_PLAN_FILE = import_node_path.default.join(plansDir, `${activePlanId}.plan.md`);
582
570
  }
583
571
  return env;
584
572
  }
585
573
  function isArtifactEnvPath(filePath) {
586
574
  try {
587
- return parseBuiltInToolPath(filePath)?.ref === R5D_ARTIFACTS_DIR_REF;
575
+ const parsed = parseBuiltInToolPath(filePath);
576
+ return parsed?.ref === R5D_ROOT_REF && parsed.relativePath.split("/").filter(Boolean)[0] === "artifacts";
588
577
  } catch {
589
578
  return false;
590
579
  }
@@ -726,48 +715,46 @@ async function prepareBuiltInToolPaths(input) {
726
715
  if (!parsed) {
727
716
  return void 0;
728
717
  }
729
- if (parsed.ref === R5D_ARTIFACTS_DIR_REF) {
718
+ const builtInPaths = {
719
+ rootDir: input.rootDir,
720
+ sessionId: input.sessionId,
721
+ activePlanId: input.activePlanId,
722
+ ...input.target.type === "project" ? { projectId: input.target.projectId, branchName: input.target.branchName } : {}
723
+ };
724
+ const rootPathSegments = expandRootPathSegments(parsed.relativePath, builtInPaths);
725
+ if (rootPathSegments[0] === "artifacts") {
730
726
  if (input.access === "write") {
731
727
  throw new Error(
732
- `${R5D_ARTIFACTS_DIR_REF} attachments are read-only. Copy them into a project path before editing or creating files.`
728
+ `${R5D_ROOT_REF}/artifacts attachments are read-only. Copy them into a project path before editing or creating files.`
733
729
  );
734
730
  }
735
- if (!input.sessionId) {
736
- throw new Error(`${R5D_ARTIFACTS_DIR_REF} paths require an active chat session`);
737
- }
738
- return {
739
- artifactsDir: await syncSessionArtifacts({
740
- baseUrl: input.baseUrl,
741
- token: input.token,
742
- sessionId: input.sessionId,
743
- artifactRoot: input.artifactRoot
744
- })
745
- };
746
- }
747
- const plansDir = targetPlanDir(input.planRoot, input.target);
748
- import_node_fs.default.mkdirSync(plansDir, { recursive: true });
749
- if (parsed.ref === R5D_PLANS_DIR_REF) {
750
- return { plansDir };
731
+ const referencedSessionId = rootPathSegments[1];
732
+ if (!referencedSessionId) return builtInPaths;
733
+ validateArtifactSessionId(referencedSessionId);
734
+ await syncSessionArtifacts({
735
+ baseUrl: input.baseUrl,
736
+ token: input.token,
737
+ sessionId: referencedSessionId,
738
+ artifactRoot: import_node_path.default.join(input.rootDir, "artifacts")
739
+ });
751
740
  }
752
- if (!input.activePlanId) {
753
- throw new Error(`${R5D_ACTIVE_PLAN_FILE_REF} paths require an active plan`);
741
+ if (rootPathSegments[0] === "plans") {
742
+ import_node_fs.default.mkdirSync(targetPlanDir(import_node_path.default.join(input.rootDir, "plans"), input.target), { recursive: true });
754
743
  }
755
- validatePlanId(input.activePlanId);
756
- return {
757
- plansDir,
758
- activePlanFile: import_node_path.default.join(plansDir, `${input.activePlanId}.plan.md`)
759
- };
744
+ return builtInPaths;
760
745
  }
761
746
  async function prepareArtifactEnvForShell(input) {
747
+ const env = { [R5D_ROOT_ENV]: input.rootDir };
762
748
  if (!input.sessionId) {
763
- return {};
749
+ return env;
764
750
  }
751
+ const artifactRoot = import_node_path.default.join(input.rootDir, "artifacts");
765
752
  try {
766
753
  await syncSessionArtifacts({
767
754
  baseUrl: input.baseUrl,
768
755
  token: input.token,
769
756
  sessionId: input.sessionId,
770
- artifactRoot: input.artifactRoot
757
+ artifactRoot
771
758
  });
772
759
  } catch (error) {
773
760
  process.stderr.write(
@@ -775,16 +762,14 @@ async function prepareArtifactEnvForShell(input) {
775
762
  `
776
763
  );
777
764
  }
778
- const artifactsDir = sessionArtifactDir(input.artifactRoot, input.sessionId);
765
+ const artifactsDir = sessionArtifactDir(artifactRoot, input.sessionId);
779
766
  import_node_fs.default.mkdirSync(artifactsDir, { recursive: true });
780
- return {
781
- R5D_ARTIFACTS_DIR: artifactsDir
782
- };
767
+ return env;
783
768
  }
784
769
  function preparePlanEnvForShell(input) {
785
- const plansDir = targetPlanDir(input.planRoot, input.target);
770
+ const plansDir = targetPlanDir(import_node_path.default.join(input.rootDir, "plans"), input.target);
786
771
  import_node_fs.default.mkdirSync(plansDir, { recursive: true });
787
- return planEnv(input.planRoot, input.target, input.activePlanId);
772
+ return planEnv(input.activePlanId);
788
773
  }
789
774
  async function verifyR5dctlAuth(baseUrl, token, workerLabel) {
790
775
  const statusUrl = new URL("/api/r5dctl/auth/status", baseUrl);
@@ -952,30 +937,17 @@ function resolveVirtualWorkerFilePath(inputPath, parsed, builtInPaths) {
952
937
  if (parsed.relativePath.split("/").includes("..")) {
953
938
  throw new Error(`Invalid ${parsed.ref} path: ${inputPath}`);
954
939
  }
955
- let rootPath;
956
- let absolutePath;
957
- if (parsed.ref === R5D_ARTIFACTS_DIR_REF) {
958
- rootPath = builtInPaths?.artifactsDir;
959
- } else if (parsed.ref === R5D_PLANS_DIR_REF) {
960
- rootPath = builtInPaths?.plansDir;
961
- } else {
962
- if (parsed.relativePath) {
963
- throw new Error(`${R5D_ACTIVE_PLAN_FILE_REF} must reference the active plan file directly`);
964
- }
965
- rootPath = builtInPaths?.plansDir;
966
- absolutePath = builtInPaths?.activePlanFile;
967
- }
968
- if (!rootPath || parsed.ref === R5D_ACTIVE_PLAN_FILE_REF && !absolutePath) {
969
- const requirement = parsed.ref === R5D_ARTIFACTS_DIR_REF ? "an active chat session" : "an active plan";
970
- throw new Error(`${parsed.ref} paths require ${requirement}`);
971
- }
972
- const normalizedPath = parsed.relativePath ? import_node_path.default.posix.normalize(parsed.relativePath).replace(/^\/+|\/+$/g, "") : "";
940
+ const rootPath = builtInPaths?.rootDir;
941
+ if (!rootPath) throw new Error(`${R5D_ROOT_REF} paths require an available worker root`);
942
+ const expandedPath = expandRootPathSegments(parsed.relativePath, builtInPaths);
943
+ if (expandedPath.includes("..")) throw new Error(`Invalid ${parsed.ref} path: ${inputPath}`);
944
+ const normalizedPath = expandedPath.length > 0 ? import_node_path.default.posix.normalize(expandedPath.join("/")).replace(/^\/+|\/+$/g, "") : "";
973
945
  const normalizedRelativePath = normalizedPath === "." ? "" : normalizedPath;
974
- absolutePath ??= normalizedRelativePath ? import_node_path.default.resolve(rootPath, ...normalizedRelativePath.split("/")) : import_node_path.default.resolve(rootPath);
946
+ const absolutePath = normalizedRelativePath ? import_node_path.default.resolve(rootPath, ...normalizedRelativePath.split("/")) : import_node_path.default.resolve(rootPath);
975
947
  assertPathInsideRealRoot(rootPath, absolutePath, `${parsed.ref} path`);
976
948
  return {
977
949
  absolutePath,
978
- displayPath: normalizedRelativePath ? `${parsed.ref}/${normalizedRelativePath}` : parsed.ref,
950
+ displayPath: parsed.relativePath ? `${parsed.ref}/${parsed.relativePath}` : parsed.ref,
979
951
  repoRelativePath: null,
980
952
  scope: "virtual",
981
953
  virtualRootPath: import_node_path.default.resolve(rootPath)
@@ -2219,8 +2191,7 @@ async function executeReadFileOperation(input) {
2219
2191
  token: input.token,
2220
2192
  sessionId: input.message.sessionId,
2221
2193
  activePlanId: input.message.activePlanId,
2222
- artifactRoot: input.artifactRoot,
2223
- planRoot: input.planRoot,
2194
+ rootDir: input.rootDir,
2224
2195
  access: "read"
2225
2196
  });
2226
2197
  return readWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.offset, input.message.limit, builtInPaths);
@@ -2233,8 +2204,7 @@ async function executeWriteFileOperation(input) {
2233
2204
  token: input.token,
2234
2205
  sessionId: input.message.sessionId,
2235
2206
  activePlanId: input.message.activePlanId,
2236
- artifactRoot: input.artifactRoot,
2237
- planRoot: input.planRoot,
2207
+ rootDir: input.rootDir,
2238
2208
  access: "write"
2239
2209
  });
2240
2210
  input.assertAdmission();
@@ -2252,8 +2222,7 @@ async function executeEditFileOperation(input) {
2252
2222
  token: input.token,
2253
2223
  sessionId: input.message.sessionId,
2254
2224
  activePlanId: input.message.activePlanId,
2255
- artifactRoot: input.artifactRoot,
2256
- planRoot: input.planRoot,
2225
+ rootDir: input.rootDir,
2257
2226
  access: "write"
2258
2227
  });
2259
2228
  input.assertAdmission();
@@ -2380,8 +2349,7 @@ async function executeGrepOperation(input) {
2380
2349
  token: input.token,
2381
2350
  sessionId: input.message.sessionId,
2382
2351
  activePlanId: input.message.activePlanId,
2383
- artifactRoot: input.artifactRoot,
2384
- planRoot: input.planRoot,
2352
+ rootDir: input.rootDir,
2385
2353
  access: "read"
2386
2354
  });
2387
2355
  return grepWorkerFiles(
@@ -2439,8 +2407,7 @@ async function executeFindOperation(input) {
2439
2407
  token: input.token,
2440
2408
  sessionId: input.message.sessionId,
2441
2409
  activePlanId: input.message.activePlanId,
2442
- artifactRoot: input.artifactRoot,
2443
- planRoot: input.planRoot,
2410
+ rootDir: input.rootDir,
2444
2411
  access: "read"
2445
2412
  });
2446
2413
  return findWorkerFiles(
@@ -2481,8 +2448,7 @@ async function executeLsOperation(input) {
2481
2448
  token: input.token,
2482
2449
  sessionId: input.message.sessionId,
2483
2450
  activePlanId: input.message.activePlanId,
2484
- artifactRoot: input.artifactRoot,
2485
- planRoot: input.planRoot,
2451
+ rootDir: input.rootDir,
2486
2452
  access: "read"
2487
2453
  });
2488
2454
  return listWorkerDirectory(input.resolvedTarget.rootPath, input.message.path, input.message.limit, builtInPaths);
@@ -2588,8 +2554,7 @@ async function executeViewFileBytesOperation(input) {
2588
2554
  token: input.token,
2589
2555
  sessionId: input.message.sessionId,
2590
2556
  activePlanId: input.message.activePlanId,
2591
- artifactRoot: input.artifactRoot,
2592
- planRoot: input.planRoot,
2557
+ rootDir: input.rootDir,
2593
2558
  access: "read"
2594
2559
  });
2595
2560
  return readWorkerImageFile(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
@@ -2629,27 +2594,30 @@ function targetIdentityEnv(target) {
2629
2594
  R5D_BRANCH_NAME: ""
2630
2595
  };
2631
2596
  }
2597
+ function targetProcessEnv(rootDir, target) {
2598
+ return { [R5D_ROOT_ENV]: rootDir, ...targetIdentityEnv(target) };
2599
+ }
2632
2600
  async function prepareShellEnvForTarget(input) {
2633
2601
  const artifactProcessEnv = await prepareArtifactEnvForShell({
2634
2602
  baseUrl: input.baseUrl,
2635
2603
  token: input.token,
2636
2604
  sessionId: input.sessionId,
2637
- artifactRoot: input.artifactRoot
2605
+ rootDir: input.rootDir
2638
2606
  });
2639
2607
  if (input.target.type === "workspace" && input.target.rootProfile === "canonical_sync") {
2640
2608
  return {
2641
2609
  ...artifactProcessEnv,
2642
- ...targetIdentityEnv(input.target)
2610
+ ...targetProcessEnv(input.rootDir, input.target)
2643
2611
  };
2644
2612
  }
2645
2613
  return {
2646
2614
  ...artifactProcessEnv,
2647
2615
  ...preparePlanEnvForShell({
2648
2616
  target: input.target,
2649
- planRoot: input.planRoot,
2617
+ rootDir: input.rootDir,
2650
2618
  activePlanId: input.activePlanId
2651
2619
  }),
2652
- ...targetIdentityEnv(input.target)
2620
+ ...targetProcessEnv(input.rootDir, input.target)
2653
2621
  };
2654
2622
  }
2655
2623
  async function executeCommand(input) {
@@ -2664,13 +2632,12 @@ async function executeCommand(input) {
2664
2632
  };
2665
2633
  input.setPreparationHeartbeatLease(true);
2666
2634
  try {
2667
- const targetProcessEnv = await prepareShellEnvForTarget({
2635
+ const targetProcessEnv2 = await prepareShellEnvForTarget({
2668
2636
  target: input.resolvedTarget.target,
2669
2637
  baseUrl: input.baseUrl,
2670
2638
  token: input.token,
2671
2639
  sessionId: input.message.sessionId,
2672
- artifactRoot: input.artifactRoot,
2673
- planRoot: input.planRoot,
2640
+ rootDir: input.rootDir,
2674
2641
  activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
2675
2642
  });
2676
2643
  const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
@@ -2680,7 +2647,7 @@ async function executeCommand(input) {
2680
2647
  stdout: "pipe",
2681
2648
  stderr: "pipe",
2682
2649
  detached: true,
2683
- env: workerChildProcessEnvironment([githubProcessEnv(), input.message.env ?? {}, targetProcessEnv])
2650
+ env: workerChildProcessEnvironment([githubProcessEnv(), input.message.env ?? {}, targetProcessEnv2])
2684
2651
  });
2685
2652
  spawnedProcess = subprocess;
2686
2653
  credentialBearingProcessGroups.set(subprocess.pid, subprocess);
@@ -2769,13 +2736,12 @@ async function executeStreamingCommand(input) {
2769
2736
  });
2770
2737
  return;
2771
2738
  }
2772
- const targetProcessEnv = await prepareShellEnvForTarget({
2739
+ const targetProcessEnv2 = await prepareShellEnvForTarget({
2773
2740
  target: input.resolvedTarget.target,
2774
2741
  baseUrl: input.baseUrl,
2775
2742
  token: input.token,
2776
2743
  sessionId: input.message.sessionId,
2777
- artifactRoot: input.artifactRoot,
2778
- planRoot: input.planRoot,
2744
+ rootDir: input.rootDir,
2779
2745
  activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
2780
2746
  });
2781
2747
  const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
@@ -2789,7 +2755,7 @@ async function executeStreamingCommand(input) {
2789
2755
  stdout: "pipe",
2790
2756
  stderr: "pipe",
2791
2757
  detached: true,
2792
- env: workerChildProcessEnvironment([githubProcessEnv(), input.message.env ?? {}, targetProcessEnv])
2758
+ env: workerChildProcessEnvironment([githubProcessEnv(), input.message.env ?? {}, targetProcessEnv2])
2793
2759
  });
2794
2760
  spawnedProcess = subprocess;
2795
2761
  credentialBearingProcessGroups.set(subprocess.pid, subprocess);
@@ -3253,10 +3219,10 @@ async function openPty(input) {
3253
3219
  }
3254
3220
  const planProcessEnv = !(input.resolvedTarget.target.type === "workspace" && input.resolvedTarget.target.rootProfile === "canonical_sync") ? preparePlanEnvForShell({
3255
3221
  target: input.resolvedTarget.target,
3256
- planRoot: input.planRoot,
3222
+ rootDir: input.rootDir,
3257
3223
  activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
3258
3224
  }) : {};
3259
- const targetProcessEnv = targetIdentityEnv(input.resolvedTarget.target);
3225
+ const targetEnv = targetProcessEnv(input.rootDir, input.resolvedTarget.target);
3260
3226
  const shell = resolveHostShell(input.message.command);
3261
3227
  const outputCoalescer = (0, import_pty_output_coalescer.createPtyOutputCoalescer)({
3262
3228
  send: (data) => {
@@ -3288,7 +3254,7 @@ async function openPty(input) {
3288
3254
  githubProcessEnv(),
3289
3255
  resolvePtyEnvFileReferences(input.message.env ?? {}, stagedEnvFiles),
3290
3256
  planProcessEnv,
3291
- targetProcessEnv,
3257
+ targetEnv,
3292
3258
  shell.env ?? {}
3293
3259
  ])
3294
3260
  },
@@ -3461,12 +3427,13 @@ async function startWorker(options) {
3461
3427
  initializeCredentialStoreDirectoryOnce();
3462
3428
  const label = requireValue(options.label, "Missing required --label <label>");
3463
3429
  validateLabel(label);
3464
- const projectsRoot = import_node_path.default.resolve(options.projectRoot ?? defaultProjectsRoot());
3465
- const syncRoot = import_node_path.default.resolve(options.syncRoot ?? process.env.R5D_SYNC_ROOT ?? defaultSyncRoot());
3430
+ const rootDir = import_node_path.default.resolve(options.rootDir ?? process.env.R5D_ROOT ?? defaultRootDir());
3431
+ const projectsRoot = import_node_path.default.join(rootDir, "projects");
3432
+ const syncRoot = import_node_path.default.join(rootDir, "sync");
3466
3433
  const workspaceShadowRoot = import_node_path.default.join(syncRoot, "workspace");
3467
3434
  const projectWorktreeSnapshotsRoot = import_node_path.default.join(syncRoot, "project-worktree-snapshots");
3468
- const artifactRoot = import_node_path.default.resolve(options.artifactRoot ?? process.env.R5D_ARTIFACTS_ROOT ?? defaultArtifactRoot());
3469
- const planRoot = import_node_path.default.resolve(options.planRoot ?? process.env.R5D_PLANS_ROOT ?? defaultPlanRoot());
3435
+ const artifactRoot = import_node_path.default.join(rootDir, "artifacts");
3436
+ const planRoot = import_node_path.default.join(rootDir, "plans");
3470
3437
  (0, import_managed_paths.assertDisjointManagedRoots)([
3471
3438
  { label: "projects root", rootPath: projectsRoot },
3472
3439
  { label: "sync root", rootPath: syncRoot },
@@ -3475,13 +3442,7 @@ async function startWorker(options) {
3475
3442
  ]);
3476
3443
  process.stdout.write(`[r5d-worker] label: ${label}
3477
3444
  `);
3478
- process.stdout.write(`[r5d-worker] projects root: ${projectsRoot}
3479
- `);
3480
- process.stdout.write(`[r5d-worker] sync root: ${syncRoot}
3481
- `);
3482
- process.stdout.write(`[r5d-worker] artifact root: ${artifactRoot}
3483
- `);
3484
- process.stdout.write(`[r5d-worker] plan root: ${planRoot}
3445
+ process.stdout.write(`[r5d-worker] root: ${rootDir}
3485
3446
  `);
3486
3447
  process.stdout.write(`[r5d-worker] server: ${baseUrl}
3487
3448
  `);
@@ -3506,9 +3467,29 @@ async function startWorker(options) {
3506
3467
  const reconciledProjectConfigFingerprints = /* @__PURE__ */ new Map();
3507
3468
  const lastObservedProjectHeads = /* @__PURE__ */ new Map();
3508
3469
  const advertisedProjectMirrorRefsTokens = /* @__PURE__ */ new Map();
3470
+ const advertisedProjectMirrorMountTokens = /* @__PURE__ */ new Map();
3471
+ let advertisedWorkspaceHead;
3509
3472
  const fetchedProjectMirrorRefsTokens = /* @__PURE__ */ new Map();
3510
- const lastProjectMirrorFetchAtMs = /* @__PURE__ */ new Map();
3473
+ const fetchedProjectMirrorMountTokens = /* @__PURE__ */ new Map();
3511
3474
  const projectMirrorFetchSkippingEnabled = process.env.R5D_PROJECT_MIRROR_FETCH_SKIP !== "0";
3475
+ const forgetLocalProjectMirrorRefsTokens = (projectId, branchName) => {
3476
+ fetchedProjectMirrorRefsTokens.delete(projectId);
3477
+ if (branchName === void 0) fetchedProjectMirrorMountTokens.delete(projectId);
3478
+ else fetchedProjectMirrorMountTokens.get(projectId)?.delete(branchName);
3479
+ };
3480
+ const recordLocalProjectMirrorRefsTokens = async (project) => {
3481
+ try {
3482
+ const tokens = await (0, import_project_worktrees.computeLocalProjectMirrorRefsTokens)({
3483
+ projectRoot: configuredProjectRoot(projectsRoot, project),
3484
+ primaryBranchName: primaryProjectBranch(project),
3485
+ mounts: project.branches.map(({ branchId, branchName }) => ({ branchId, branchName }))
3486
+ });
3487
+ fetchedProjectMirrorRefsTokens.set(project.projectId, tokens.project);
3488
+ fetchedProjectMirrorMountTokens.set(project.projectId, tokens.mounts);
3489
+ } catch {
3490
+ forgetLocalProjectMirrorRefsTokens(project.projectId);
3491
+ }
3492
+ };
3512
3493
  const pendingMirrorDeletes = /* @__PURE__ */ new Map();
3513
3494
  let workspaceRemoteUrl = null;
3514
3495
  let workspaceCredentialHelper = null;
@@ -3523,6 +3504,7 @@ async function startWorker(options) {
3523
3504
  let workspacePeriodicTimer;
3524
3505
  let pendingAutomaticTrigger;
3525
3506
  const pendingCreatedBranchPublicationNotBefore = /* @__PURE__ */ new Map();
3507
+ const workspaceProjectionLedger = new import_workspace_projection_ledger.WorkspaceProjectionLedger();
3526
3508
  let automaticSyncInFlight = false;
3527
3509
  let terminalReplayTimer;
3528
3510
  let workspaceSyncRequestsInFlight = 0;
@@ -3892,7 +3874,7 @@ async function startWorker(options) {
3892
3874
  workspaceRelativePath: "workspace-plans",
3893
3875
  sourceMode: "all",
3894
3876
  hydrateDeletionMode: "all",
3895
- // Workspace-level agents receive this directory through R5D_PLANS_DIR.
3877
+ // Workspace-level agents reach this directory through $R5D_ROOT/plans.
3896
3878
  // Keep both projection and hydration away from it while any visible-
3897
3879
  // projects process, PTY, or built-in write/edit is active or reserved.
3898
3880
  busy: () => {
@@ -3930,7 +3912,10 @@ async function startWorker(options) {
3930
3912
  deleteWhenSourceMissing: true
3931
3913
  });
3932
3914
  }
3933
- return mounts;
3915
+ return mounts.map((mount) => {
3916
+ const lastProjectedMutationToken = workspaceProjectionLedger.lastProjectedToken(mount);
3917
+ return lastProjectedMutationToken === void 0 ? mount : { ...mount, lastProjectedMutationToken };
3918
+ });
3934
3919
  };
3935
3920
  const workspaceLocalHead = async () => {
3936
3921
  if (!import_node_fs.default.existsSync(import_node_path.default.join(workspaceShadowRoot, ".git"))) return null;
@@ -4155,17 +4140,7 @@ async function startWorker(options) {
4155
4140
  for (const result of results) {
4156
4141
  lastObservedProjectHeads.set(projectBranchKey(project.projectId, result.branchName), result.head);
4157
4142
  }
4158
- try {
4159
- fetchedProjectMirrorRefsTokens.set(
4160
- project.projectId,
4161
- await (0, import_project_worktrees.computeLocalProjectMirrorRefsToken)({
4162
- projectRoot: configuredProjectRoot(projectsRoot, project),
4163
- primaryBranchName: primaryProjectBranch(project)
4164
- })
4165
- );
4166
- } catch {
4167
- fetchedProjectMirrorRefsTokens.delete(project.projectId);
4168
- }
4143
+ await recordLocalProjectMirrorRefsTokens(project);
4169
4144
  }
4170
4145
  const publishedTombstoneIds = [
4171
4146
  ...new Set(
@@ -4191,7 +4166,7 @@ async function startWorker(options) {
4191
4166
  credentialHelper: deletion.credentialHelper,
4192
4167
  credentialUsername: deletion.credentialUsername
4193
4168
  });
4194
- fetchedProjectMirrorRefsTokens.delete(deletion.projectId);
4169
+ forgetLocalProjectMirrorRefsTokens(deletion.projectId, deletion.branchName);
4195
4170
  }
4196
4171
  if (deletion.tombstoneId && !deletion.projectDeleted) {
4197
4172
  projectWorkspaceState = projectWorkspaceStateStore.recordMirrorRefDeletion({
@@ -4203,8 +4178,8 @@ async function startWorker(options) {
4203
4178
  if (deletion.projectDeleted && !projectConfigById.has(deletion.projectId) && ![...pendingMirrorDeletes.values()].some((pending) => pending.projectId === deletion.projectId)) {
4204
4179
  removeProjectCredentialStore(deletion.projectId);
4205
4180
  advertisedProjectMirrorRefsTokens.delete(deletion.projectId);
4206
- fetchedProjectMirrorRefsTokens.delete(deletion.projectId);
4207
- lastProjectMirrorFetchAtMs.delete(deletion.projectId);
4181
+ advertisedProjectMirrorMountTokens.delete(deletion.projectId);
4182
+ forgetLocalProjectMirrorRefsTokens(deletion.projectId);
4208
4183
  }
4209
4184
  lastObservedProjectHeads.delete(key);
4210
4185
  }
@@ -4213,39 +4188,20 @@ async function startWorker(options) {
4213
4188
  const connection = projectConnection(project);
4214
4189
  const projectRoot = configuredProjectRoot(projectsRoot, project);
4215
4190
  const primaryBranchName = primaryProjectBranch(project);
4216
- if (options2.refreshRemote) {
4217
- const advertisedBeforeFetch = advertisedProjectMirrorRefsTokens.get(project.projectId);
4218
- const observations = await (0, import_project_worktrees.observeProjectMirrorHeads)({
4219
- projectRoot,
4220
- primaryBranchName,
4221
- branchNames: project.branches.map(({ branchName }) => branchName),
4222
- mirrorUrl: connection.mirrorUrl,
4223
- credentialHelper: connection.credentialHelper,
4224
- credentialUsername: connection.mirrorCredentialUsername,
4225
- allowNonFastForward: options2.allowNonFastForward,
4226
- branchIds: new Map(project.branches.map(({ branchId, branchName }) => [branchName, branchId]))
4227
- });
4228
- if (advertisedBeforeFetch === void 0) fetchedProjectMirrorRefsTokens.delete(project.projectId);
4229
- else fetchedProjectMirrorRefsTokens.set(project.projectId, advertisedBeforeFetch);
4230
- lastProjectMirrorFetchAtMs.set(project.projectId, Date.now());
4231
- return { projectId: project.projectId, projectRoot, primaryBranchName, observations };
4232
- }
4233
- return {
4234
- projectId: project.projectId,
4191
+ const observations = await (0, import_project_worktrees.observeProjectMirrorHeads)({
4235
4192
  projectRoot,
4236
4193
  primaryBranchName,
4237
- observations: await (0, import_project_worktrees.observeProjectMirrorHeads)({
4238
- projectRoot,
4239
- primaryBranchName,
4240
- branchNames: project.branches.map(({ branchName }) => branchName),
4241
- mirrorUrl: connection.mirrorUrl,
4242
- credentialHelper: connection.credentialHelper,
4243
- credentialUsername: connection.mirrorCredentialUsername,
4244
- allowNonFastForward: options2.allowNonFastForward,
4245
- branchIds: new Map(project.branches.map(({ branchId, branchName }) => [branchName, branchId])),
4246
- refreshRemote: false
4247
- })
4248
- };
4194
+ branchNames: project.branches.map(({ branchName }) => branchName),
4195
+ mirrorUrl: connection.mirrorUrl,
4196
+ credentialHelper: connection.credentialHelper,
4197
+ credentialUsername: connection.mirrorCredentialUsername,
4198
+ allowNonFastForward: options2.allowNonFastForward,
4199
+ branchIds: new Map(project.branches.map(({ branchId, branchName }) => [branchName, branchId])),
4200
+ refreshRemote: options2.fetch.scope !== "none",
4201
+ ...options2.fetch.scope === "mounts" ? { fetchOnlyBranches: new Set(options2.fetch.mounts.map(({ branchName }) => branchName)) } : {}
4202
+ });
4203
+ if (options2.fetch.scope !== "none") await recordLocalProjectMirrorRefsTokens(project);
4204
+ return { projectId: project.projectId, projectRoot, primaryBranchName, observations };
4249
4205
  };
4250
4206
  const observeProjectHeadsBeforeOuterWorkspace = async (input) => {
4251
4207
  const observationStartedAt = Date.now();
@@ -4256,28 +4212,39 @@ async function startWorker(options) {
4256
4212
  if (input.failClosed) {
4257
4213
  const bundles2 = [];
4258
4214
  for (const project of eligibleProjects) {
4259
- bundles2.push(await observeSingleProjectMirrorBundle(project, { allowNonFastForward: input.allowNonFastForward, refreshRemote: true }));
4215
+ bundles2.push(
4216
+ await observeSingleProjectMirrorBundle(project, {
4217
+ allowNonFastForward: input.allowNonFastForward,
4218
+ fetch: { scope: "project", reason: "fail_closed" }
4219
+ })
4220
+ );
4260
4221
  }
4261
4222
  return bundles2;
4262
4223
  }
4263
- let fetchedCount = 0;
4224
+ let wholeMirrorFetches = 0;
4225
+ let scopedMirrorFetches = 0;
4226
+ let fetchedMounts = 0;
4227
+ let eligibleMounts = 0;
4264
4228
  const bundles = await mapWithConcurrencyLimit(
4265
4229
  eligibleProjects,
4266
4230
  PROJECT_MIRROR_OBSERVE_CONCURRENCY,
4267
4231
  async (project) => {
4268
4232
  try {
4269
- const decision = (0, import_project_mirror_fetch_policy.projectMirrorFetchDecision)({
4270
- advertisedToken: advertisedProjectMirrorRefsTokens.get(project.projectId),
4271
- fetchedToken: fetchedProjectMirrorRefsTokens.get(project.projectId),
4272
- lastFetchAtMs: lastProjectMirrorFetchAtMs.get(project.projectId),
4273
- nowMs: Date.now(),
4233
+ const fetch2 = (0, import_project_mirror_fetch_policy.planProjectMirrorObservationFetch)({
4234
+ branchNames: project.branches.map(({ branchName }) => branchName),
4235
+ advertisedProjectToken: advertisedProjectMirrorRefsTokens.get(project.projectId),
4236
+ advertisedMountTokens: advertisedProjectMirrorMountTokens.get(project.projectId),
4237
+ fetchedProjectToken: fetchedProjectMirrorRefsTokens.get(project.projectId),
4238
+ fetchedMountTokens: fetchedProjectMirrorMountTokens.get(project.projectId) ?? /* @__PURE__ */ new Map(),
4274
4239
  skippingEnabled: projectMirrorFetchSkippingEnabled
4275
4240
  });
4276
- if (decision.fetch) fetchedCount += 1;
4277
- return await observeSingleProjectMirrorBundle(project, {
4278
- allowNonFastForward: input.allowNonFastForward,
4279
- refreshRemote: decision.fetch
4280
- });
4241
+ eligibleMounts += project.branches.length;
4242
+ if (fetch2.scope === "project") wholeMirrorFetches += 1;
4243
+ if (fetch2.scope === "mounts") {
4244
+ scopedMirrorFetches += 1;
4245
+ fetchedMounts += fetch2.mounts.length;
4246
+ }
4247
+ return await observeSingleProjectMirrorBundle(project, { allowNonFastForward: input.allowNonFastForward, fetch: fetch2 });
4281
4248
  } catch (error) {
4282
4249
  process.stderr.write(
4283
4250
  `[r5d-worker] failed to observe project mirror heads before workspace update: ${error instanceof Error ? error.message : String(error)}
@@ -4287,9 +4254,9 @@ async function startWorker(options) {
4287
4254
  }
4288
4255
  }
4289
4256
  );
4290
- if (fetchedCount > 0) {
4257
+ if (wholeMirrorFetches + scopedMirrorFetches > 0) {
4291
4258
  process.stderr.write(
4292
- `[r5d-worker] project mirror observation fetched ${fetchedCount} of ${eligibleProjects.length} mirror(s)
4259
+ `[r5d-worker] project mirror observation fetched ${wholeMirrorFetches + scopedMirrorFetches} of ${eligibleProjects.length} mirror(s) (${wholeMirrorFetches} whole, ${scopedMirrorFetches} scoped to ${fetchedMounts} of ${eligibleMounts} mount(s))
4293
4260
  `
4294
4261
  );
4295
4262
  }
@@ -4403,6 +4370,7 @@ async function startWorker(options) {
4403
4370
  }
4404
4371
  const configuredMounts = buildWorkspaceMounts();
4405
4372
  if (input.resetToCanonical) {
4373
+ workspaceProjectionLedger.reset();
4406
4374
  const resetHasBusyLiveMount = configuredMounts.some(
4407
4375
  (mount) => !(mount.deleteWhenSourceMissing && !import_node_fs.default.existsSync(mount.sourcePath)) && mount.busy?.()
4408
4376
  );
@@ -4445,6 +4413,7 @@ async function startWorker(options) {
4445
4413
  }
4446
4414
  const outerRemediation = input.trigger.type === "remediation" || input.trigger.type === "remediation_confirm";
4447
4415
  const mounts = outerRemediation ? (0, import_workspace_git_sync.workspaceGitMountsForRemediation)(configuredMounts) : configuredMounts;
4416
+ if (outerRemediation) workspaceProjectionLedger.reset();
4448
4417
  const observedProjectHeads = outerRemediation ? [] : await observeProjectHeadsBeforeOuterWorkspace({ allowNonFastForward: false, failClosed: false });
4449
4418
  const expectedMirrorHeads = /* @__PURE__ */ new Map();
4450
4419
  for (const bundle of observedProjectHeads) {
@@ -4471,6 +4440,10 @@ async function startWorker(options) {
4471
4440
  allowLargeDiff: input.confirmedLargeDiff,
4472
4441
  skipMountMirror: outerRemediation,
4473
4442
  requiredAncestorHeads: input.requiredAncestorHeads,
4443
+ // Same gate as the mirror fetches: while the server's read of the hidden
4444
+ // workspace head equals the head this clone last fetched or pushed, the
4445
+ // cycle's workspace head fetches would transfer nothing.
4446
+ advertisedRemoteHead: projectMirrorFetchSkippingEnabled && !outerRemediation ? advertisedWorkspaceHead : void 0,
4474
4447
  assertStillAdmitted: input.assertStillAdmitted,
4475
4448
  afterWorkspacePublished: outerRemediation ? void 0 : async ({ activeMountIds, publishedHead }) => {
4476
4449
  await pushChangedProjectHeads(activeMountIds, publishedHead, publicationHeads, expectedMirrorHeads, inboundMoveMountIds);
@@ -4492,7 +4465,11 @@ async function startWorker(options) {
4492
4465
  result.localHead ?? result.remoteHead
4493
4466
  );
4494
4467
  }
4495
- return mapWorkspaceGitResult(input.attemptId, input.trigger, result);
4468
+ const mapped = await mapWorkspaceGitResult(input.attemptId, input.trigger, result);
4469
+ if (!outerRemediation) {
4470
+ workspaceProjectionLedger.recordCycle({ result: mapped, mounts, projectedMutationTokens: result.projectedMutationTokens });
4471
+ }
4472
+ return mapped;
4496
4473
  };
4497
4474
  const sendWorkspaceSyncResult = (requestId, result) => {
4498
4475
  try {
@@ -4530,17 +4507,143 @@ async function startWorker(options) {
4530
4507
  }, unsafeWorkspaceSyncRecoveryBounceDelayMs(attempt));
4531
4508
  timer.unref();
4532
4509
  };
4510
+ const captureWorkspaceSyncAdmission = () => ({
4511
+ generation: workerAdmissionGeneration,
4512
+ activeIncidentId: activeWorkspaceIncidentId,
4513
+ deferredIncidentId: deferredWorkspaceConfiguration?.incidentId ?? null
4514
+ });
4515
+ const workspaceSyncAdmissionSnapshotsMatch = (left, right) => left.generation === right.generation && left.activeIncidentId === right.activeIncidentId && left.deferredIncidentId === right.deferredIncidentId;
4516
+ const assertWorkspaceSyncStillAdmitted = (admitted, trigger) => {
4517
+ if (!workspaceSyncAdmissionSnapshotsMatch(admitted, captureWorkspaceSyncAdmission()) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || !deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, trigger)) {
4518
+ throw new WorkspaceSyncAdmissionChangedError("Workspace synchronization admission changed while asynchronous work was in flight");
4519
+ }
4520
+ };
4521
+ const executeWorkspaceSyncCycle = async (input) => {
4522
+ const attemptId = input.attemptId;
4523
+ lastWorkspaceSyncMirrorObservationMs = 0;
4524
+ if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
4525
+ return await failedWorkspaceSyncResult(
4526
+ attemptId,
4527
+ input.trigger,
4528
+ new Error(
4529
+ `Workspace synchronization was fenced while queued by incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
4530
+ )
4531
+ );
4532
+ }
4533
+ try {
4534
+ input.assertStillAdmitted();
4535
+ const hasDurableBranchDeletion = projectWorkspaceState.tombstones.some((tombstone) => tombstone.kind === "branch");
4536
+ return await performWorkspaceSync({
4537
+ attemptId,
4538
+ trigger: input.trigger,
4539
+ confirmedLargeDiff: input.confirmedLargeDiff === true || hasDurableBranchDeletion,
4540
+ confirmationReason: input.confirmationReason ?? (hasDurableBranchDeletion ? "durable project branch deletion" : void 0),
4541
+ resetToCanonical: input.resetToCanonical,
4542
+ requiredAncestorHeads: input.requiredAncestorHeads,
4543
+ assertStillAdmitted: input.assertStillAdmitted
4544
+ });
4545
+ } catch (error) {
4546
+ workspaceProjectionLedger.reset();
4547
+ const errorMessage = error instanceof Error ? error.message : String(error);
4548
+ const hydrationRecoverable = workspaceSyncFailureHydrationIsSafe({
4549
+ error,
4550
+ resetToCanonical: input.resetToCanonical === true,
4551
+ inspectRecoverableHydration: () => (0, import_workspace_git_sync.workspaceGitHydrationIsRecoverable)(workspaceShadowRoot, buildWorkspaceMounts())
4552
+ });
4553
+ if (hydrationRecoverable) {
4554
+ if (error instanceof WorkspaceSyncAdmissionChangedError) {
4555
+ process.stderr.write(
4556
+ `[r5d-worker] workspace synchronization was cancelled with every mount at a recorded basis; the next cycle converges it: ${errorMessage}
4557
+ `
4558
+ );
4559
+ }
4560
+ return await failedWorkspaceSyncResult(attemptId, input.trigger, error);
4561
+ }
4562
+ const fenced = fenceUnsafeWorkspaceSyncFailure({
4563
+ hydrationRecoverable,
4564
+ resetToCanonical: input.resetToCanonical === true,
4565
+ recoveryAttempts: unsafeWorkspaceSyncRecoveryAttempts,
4566
+ maxRecoveryAttempts: WORKSPACE_UNSAFE_SYNC_MAX_RECOVERY_ATTEMPTS,
4567
+ hasRetainableWork: activeProcesses.size > 0 || credentialBearingProcessGroups.size > 0 || activePtys.size > 0 || pendingProcessTerminals.size > 0,
4568
+ bouncePending: unsafeWorkspaceSyncBouncePending,
4569
+ invalidateExecution: () => {
4570
+ workspaceConfigured = false;
4571
+ advanceWorkerAdmissionGeneration();
4572
+ },
4573
+ scheduleRecoveryBounce: () => {
4574
+ unsafeWorkspaceSyncRecoveryAttempts += 1;
4575
+ scheduleUnsafeWorkspaceSyncRecoveryBounce(errorMessage);
4576
+ },
4577
+ fatalExit: (exitCode) => {
4578
+ process.stderr.write(
4579
+ `[r5d-worker] workspace synchronization failed without a recoverable hydration basis; exiting for exact recovery after ${unsafeWorkspaceSyncRecoveryAttempts} in-process recovery attempt(s): ${errorMessage}
4580
+ `
4581
+ );
4582
+ process.exit(exitCode);
4583
+ },
4584
+ exitCode: 1
4585
+ });
4586
+ if (fenced === "recovery_pending") {
4587
+ process.stderr.write(
4588
+ `[r5d-worker] workspace synchronization failed without a recoverable hydration basis while in-process recovery is already pending: ${errorMessage}
4589
+ `
4590
+ );
4591
+ }
4592
+ return {
4593
+ ...await failedWorkspaceSyncResult(attemptId, input.trigger, error),
4594
+ failureReason: "unsafe_hydration",
4595
+ recoveryAttempt: unsafeWorkspaceSyncRecoveryAttempts
4596
+ };
4597
+ }
4598
+ };
4599
+ const REMEDIATION_TRIGGER_TYPES = /* @__PURE__ */ new Set(["remediation", "remediation_confirm", "remediation_reset"]);
4600
+ const isOrdinaryWorkspaceSyncRequest = (input) => input.confirmedLargeDiff !== true && input.resetToCanonical !== true && !(input.requiredAncestorHeads && input.requiredAncestorHeads.length > 0) && !REMEDIATION_TRIGGER_TYPES.has(input.trigger.type);
4601
+ const LOCAL_ACTIVITY_TRIGGER_TYPES = /* @__PURE__ */ new Set([
4602
+ "write",
4603
+ "edit",
4604
+ "shell_inline",
4605
+ "process_terminal",
4606
+ "process_cancel"
4607
+ ]);
4608
+ const answerOrdinaryWorkspaceSyncFromLastCycle = (requests) => {
4609
+ if (!workspaceConfigured) return null;
4610
+ return workspaceProjectionLedger.answerFromLastCycle({
4611
+ // A workspace-rooted command carries no project target because it may
4612
+ // have touched any checkout.
4613
+ targetMountIds: requests.map(
4614
+ ({ trigger }) => LOCAL_ACTIVITY_TRIGGER_TYPES.has(trigger.type) && trigger.projectId && trigger.branchName ? projectMountId(trigger.projectId, trigger.branchName) : null
4615
+ ),
4616
+ mounts: buildWorkspaceMounts(),
4617
+ pendingDeletions: pendingMirrorDeletes.size > 0 || projectWorkspaceState.pendingTreeDeletions.length > 0 || projectWorkspaceState.pendingMirrorRefDeletions.length > 0
4618
+ });
4619
+ };
4620
+ const ordinaryWorkspaceSyncCoalescer = new import_workspace_sync_coalescer.WorkspaceSyncCoalescer({
4621
+ runExclusive: (operation) => workspaceSyncSingleFlight.runExclusive(operation),
4622
+ runBatch: async (requests) => {
4623
+ const startedAtMs = Date.now();
4624
+ const admission = captureWorkspaceSyncAdmission();
4625
+ const staleAttemptIds = /* @__PURE__ */ new Set();
4626
+ const admitted = [];
4627
+ for (const request of requests) {
4628
+ if (workspaceSyncAdmissionSnapshotsMatch(request.admission, admission)) admitted.push(request);
4629
+ else staleAttemptIds.add(request.attemptId);
4630
+ }
4631
+ const leader = admitted[0];
4632
+ if (!leader) return { result: null, ranCycle: false, startedAtMs, finishedAtMs: Date.now(), prepareMs: 0, staleAttemptIds };
4633
+ const answered = answerOrdinaryWorkspaceSyncFromLastCycle(admitted);
4634
+ if (answered) return { result: answered, ranCycle: false, startedAtMs, finishedAtMs: Date.now(), prepareMs: 0, staleAttemptIds };
4635
+ const result = await executeWorkspaceSyncCycle({
4636
+ attemptId: leader.attemptId,
4637
+ trigger: leader.trigger,
4638
+ assertStillAdmitted: () => assertWorkspaceSyncStillAdmitted(admission, leader.trigger)
4639
+ });
4640
+ return { result, ranCycle: true, startedAtMs, finishedAtMs: Date.now(), prepareMs: lastWorkspaceSyncMirrorObservationMs, staleAttemptIds };
4641
+ }
4642
+ });
4533
4643
  const runWorkspaceSync = async (input) => {
4534
4644
  const attemptId = input.attemptId ?? crypto.randomUUID();
4535
4645
  const requestedAt = Date.now();
4536
- const admittedGeneration = workerAdmissionGeneration;
4537
- const admittedActiveIncidentId = activeWorkspaceIncidentId;
4538
- const admittedDeferredIncidentId = deferredWorkspaceConfiguration?.incidentId ?? null;
4539
- const assertStillAdmitted = () => {
4540
- if (admittedGeneration !== workerAdmissionGeneration || admittedActiveIncidentId !== activeWorkspaceIncidentId || admittedDeferredIncidentId !== (deferredWorkspaceConfiguration?.incidentId ?? null) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || !deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
4541
- throw new WorkspaceSyncAdmissionChangedError("Workspace synchronization admission changed while asynchronous work was in flight");
4542
- }
4543
- };
4646
+ const admission = captureWorkspaceSyncAdmission();
4544
4647
  if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
4545
4648
  const result2 = {
4546
4649
  ...await failedWorkspaceSyncResult(
@@ -4566,104 +4669,47 @@ async function startWorker(options) {
4566
4669
  }
4567
4670
  ws.send(JSON.stringify({ type: "workspace_sync_started", attemptId }));
4568
4671
  }
4569
- let queueEnteredAt = requestedAt;
4570
- let syncStartedAt = requestedAt;
4672
+ let queueMs = 0;
4673
+ let prepareMs = 0;
4674
+ let synchronizeMs = 0;
4571
4675
  workspaceSyncRequestsInFlight += 1;
4572
4676
  let result;
4573
4677
  try {
4574
- result = await workspaceSyncSingleFlight.runExclusive(async () => {
4575
- queueEnteredAt = Date.now();
4576
- syncStartedAt = Date.now();
4577
- lastWorkspaceSyncMirrorObservationMs = 0;
4578
- if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
4579
- return await failedWorkspaceSyncResult(
4580
- attemptId,
4581
- input.trigger,
4582
- new Error(
4583
- `Workspace synchronization was fenced while queued by incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
4584
- )
4585
- );
4586
- }
4587
- try {
4588
- assertStillAdmitted();
4589
- const hasDurableBranchDeletion = projectWorkspaceState.tombstones.some((tombstone) => tombstone.kind === "branch");
4590
- return await performWorkspaceSync({
4678
+ if (isOrdinaryWorkspaceSyncRequest(input)) {
4679
+ const settlement = await ordinaryWorkspaceSyncCoalescer.request({ attemptId, trigger: input.trigger, admission });
4680
+ queueMs = settlement.startedAtMs - requestedAt;
4681
+ prepareMs = settlement.prepareMs;
4682
+ synchronizeMs = settlement.finishedAtMs - settlement.startedAtMs;
4683
+ result = settlement.staleAttemptIds.has(attemptId) || settlement.result === null ? await failedWorkspaceSyncResult(
4684
+ attemptId,
4685
+ input.trigger,
4686
+ new Error("Workspace synchronization admission changed while the request was queued")
4687
+ ) : { ...settlement.result, attemptId, trigger: input.trigger };
4688
+ } else {
4689
+ result = await workspaceSyncSingleFlight.runExclusive(async () => {
4690
+ const startedAt = Date.now();
4691
+ queueMs = startedAt - requestedAt;
4692
+ const cycleResult = await executeWorkspaceSyncCycle({
4591
4693
  attemptId,
4592
4694
  trigger: input.trigger,
4593
- confirmedLargeDiff: input.confirmedLargeDiff === true || hasDurableBranchDeletion,
4594
- confirmationReason: input.confirmationReason ?? (hasDurableBranchDeletion ? "durable project branch deletion" : void 0),
4695
+ confirmedLargeDiff: input.confirmedLargeDiff,
4696
+ confirmationReason: input.confirmationReason,
4595
4697
  resetToCanonical: input.resetToCanonical,
4596
4698
  requiredAncestorHeads: input.requiredAncestorHeads,
4597
- assertStillAdmitted
4598
- });
4599
- } catch (error) {
4600
- const errorMessage = error instanceof Error ? error.message : String(error);
4601
- const hydrationRecoverable = workspaceSyncFailureHydrationIsSafe({
4602
- error,
4603
- resetToCanonical: input.resetToCanonical === true,
4604
- inspectRecoverableHydration: () => (0, import_workspace_git_sync.workspaceGitHydrationIsRecoverable)(workspaceShadowRoot, buildWorkspaceMounts())
4605
- });
4606
- if (hydrationRecoverable) {
4607
- if (error instanceof WorkspaceSyncAdmissionChangedError) {
4608
- process.stderr.write(
4609
- `[r5d-worker] workspace synchronization was cancelled with every mount at a recorded basis; the next cycle converges it: ${errorMessage}
4610
- `
4611
- );
4612
- }
4613
- return await failedWorkspaceSyncResult(attemptId, input.trigger, error);
4614
- }
4615
- const fenced = fenceUnsafeWorkspaceSyncFailure({
4616
- hydrationRecoverable,
4617
- resetToCanonical: input.resetToCanonical === true,
4618
- recoveryAttempts: unsafeWorkspaceSyncRecoveryAttempts,
4619
- maxRecoveryAttempts: WORKSPACE_UNSAFE_SYNC_MAX_RECOVERY_ATTEMPTS,
4620
- hasRetainableWork: activeProcesses.size > 0 || credentialBearingProcessGroups.size > 0 || activePtys.size > 0 || pendingProcessTerminals.size > 0,
4621
- bouncePending: unsafeWorkspaceSyncBouncePending,
4622
- invalidateExecution: () => {
4623
- workspaceConfigured = false;
4624
- advanceWorkerAdmissionGeneration();
4625
- },
4626
- scheduleRecoveryBounce: () => {
4627
- unsafeWorkspaceSyncRecoveryAttempts += 1;
4628
- scheduleUnsafeWorkspaceSyncRecoveryBounce(errorMessage);
4629
- },
4630
- fatalExit: (exitCode) => {
4631
- process.stderr.write(
4632
- `[r5d-worker] workspace synchronization failed without a recoverable hydration basis; exiting for exact recovery after ${unsafeWorkspaceSyncRecoveryAttempts} in-process recovery attempt(s): ${errorMessage}
4633
- `
4634
- );
4635
- process.exit(exitCode);
4636
- },
4637
- exitCode: 1
4699
+ assertStillAdmitted: () => assertWorkspaceSyncStillAdmitted(admission, input.trigger)
4638
4700
  });
4639
- if (fenced === "recovery_pending") {
4640
- process.stderr.write(
4641
- `[r5d-worker] workspace synchronization failed without a recoverable hydration basis while in-process recovery is already pending: ${errorMessage}
4642
- `
4643
- );
4644
- }
4645
- return {
4646
- ...await failedWorkspaceSyncResult(attemptId, input.trigger, error),
4647
- failureReason: "unsafe_hydration",
4648
- recoveryAttempt: unsafeWorkspaceSyncRecoveryAttempts
4649
- };
4650
- }
4651
- });
4701
+ prepareMs = lastWorkspaceSyncMirrorObservationMs;
4702
+ synchronizeMs = Date.now() - startedAt;
4703
+ return cycleResult;
4704
+ });
4705
+ }
4652
4706
  } finally {
4653
4707
  workspaceSyncRequestsInFlight -= 1;
4654
4708
  }
4655
4709
  if (result.outcome !== "failed") unsafeWorkspaceSyncRecoveryAttempts = 0;
4656
- const finishedAt = Date.now();
4657
4710
  result = {
4658
4711
  ...result,
4659
- telemetry: {
4660
- totalMs: finishedAt - requestedAt,
4661
- queueMs: queueEnteredAt - requestedAt,
4662
- // Duration of the pre-outer mirror-head observation phase; near zero
4663
- // on a cycle whose token-gated observation skipped every fetch.
4664
- prepareMs: lastWorkspaceSyncMirrorObservationMs,
4665
- synchronizeMs: finishedAt - syncStartedAt
4666
- }
4712
+ telemetry: { totalMs: Date.now() - requestedAt, queueMs, prepareMs, synchronizeMs }
4667
4713
  };
4668
4714
  if (input.sendResult !== false) sendWorkspaceSyncResult(input.requestId, result);
4669
4715
  sendLifecycleProgress(
@@ -4933,6 +4979,7 @@ async function startWorker(options) {
4933
4979
  );
4934
4980
  }
4935
4981
  workspaceConfigured = false;
4982
+ workspaceProjectionLedger.reset();
4936
4983
  const preserveOnlyBranches = message.projects.flatMap(
4937
4984
  (project) => project.preserveOnlyBranches.map(({ branchId, branchName }) => ({ branchId, projectId: project.projectId, branchName }))
4938
4985
  );
@@ -4942,6 +4989,16 @@ async function startWorker(options) {
4942
4989
  preserveOnlyBranches
4943
4990
  });
4944
4991
  }
4992
+ for (const project of message.projects) {
4993
+ for (const branch of project.branches) {
4994
+ const pending2 = projectWorkspaceState.locallyPendingCreatedBranches.find(
4995
+ (candidate) => candidate.projectId === project.projectId && candidate.branchName === branch.branchName
4996
+ );
4997
+ if (pending2 && pending2.branchId === branch.branchId) {
4998
+ projectWorkspaceState = projectWorkspaceStateStore.clearPendingCreatedBranch(pending2);
4999
+ }
5000
+ }
5001
+ }
4945
5002
  const stillPendingCreatedBranches = new Map(
4946
5003
  projectWorkspaceState.locallyPendingCreatedBranches.map((branch) => [
4947
5004
  (0, import_workspace_automatic_sync_policy.pendingCreatedBranchKey)(branch.projectId, branch.branchName),
@@ -5383,11 +5440,10 @@ async function startWorker(options) {
5383
5440
  workspaceConfigResetToCanonicalV1: true,
5384
5441
  projectBranchDeletionFastAckV1: true,
5385
5442
  projectMirrorLeaseV1: true,
5386
- projectMirrorRefsTokensV1: true
5443
+ projectMirrorRefsTokensV1: true,
5444
+ projectBranchWorkingTreeModeV1: true
5387
5445
  },
5388
- projectRoot: projectsRoot,
5389
- artifactRoot,
5390
- planRoot
5446
+ root: rootDir
5391
5447
  }
5392
5448
  };
5393
5449
  ws.send(JSON.stringify(hello));
@@ -5422,14 +5478,12 @@ async function startWorker(options) {
5422
5478
  return;
5423
5479
  }
5424
5480
  if (message.type === "project_mirror_refs_tokens") {
5481
+ const advertised = (0, import_project_mirror_fetch_policy.parseProjectMirrorRefsTokensMessage)(message);
5425
5482
  advertisedProjectMirrorRefsTokens.clear();
5426
- if (message.tokens && typeof message.tokens === "object") {
5427
- for (const [projectId, token2] of Object.entries(message.tokens)) {
5428
- if (typeof token2 === "string" && /^[0-9a-f]{64}$/.test(token2)) {
5429
- advertisedProjectMirrorRefsTokens.set(projectId, token2);
5430
- }
5431
- }
5432
- }
5483
+ for (const [projectId, token2] of advertised.projectTokens) advertisedProjectMirrorRefsTokens.set(projectId, token2);
5484
+ advertisedProjectMirrorMountTokens.clear();
5485
+ for (const [projectId, tokens] of advertised.mountTokens) advertisedProjectMirrorMountTokens.set(projectId, tokens);
5486
+ advertisedWorkspaceHead = advertised.workspaceHead;
5433
5487
  return;
5434
5488
  }
5435
5489
  if (message.type === "workspace_config") {
@@ -5502,13 +5556,21 @@ async function startWorker(options) {
5502
5556
  });
5503
5557
  return;
5504
5558
  }
5559
+ sendWorkerMessage(ws, {
5560
+ type: "operation_queued",
5561
+ requestId: message.requestId,
5562
+ queuePosition: workspaceSyncSingleFlight.branchMutationQueuePosition()
5563
+ });
5505
5564
  try {
5565
+ if (message.workingTree !== "carry" && message.workingTree !== "clean") {
5566
+ throw new Error('Project branch creation requires workingTree "carry" or "clean"');
5567
+ }
5506
5568
  const pendingBranch = {
5507
5569
  branchId: message.branchId,
5508
5570
  projectId: message.projectId,
5509
5571
  branchName: message.targetBranch
5510
5572
  };
5511
- const created = await workspaceSyncSingleFlight.runMutation(() => {
5573
+ const created = await workspaceSyncSingleFlight.runBranchMutation(() => {
5512
5574
  if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
5513
5575
  throw new Error(
5514
5576
  `Project branch creation was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
@@ -5549,6 +5611,7 @@ async function startWorker(options) {
5549
5611
  primaryBranchName: primaryProjectBranch(project),
5550
5612
  sourceBranchName: message.sourceBranch,
5551
5613
  branchName: message.targetBranch,
5614
+ workingTree: message.workingTree,
5552
5615
  pendingRetry: pendingWasRecorded
5553
5616
  });
5554
5617
  gitBranchCreated = true;
@@ -5605,9 +5668,14 @@ async function startWorker(options) {
5605
5668
  });
5606
5669
  return;
5607
5670
  }
5671
+ sendWorkerMessage(ws, {
5672
+ type: "operation_queued",
5673
+ requestId: message.requestId,
5674
+ queuePosition: workspaceSyncSingleFlight.branchMutationQueuePosition()
5675
+ });
5608
5676
  try {
5609
5677
  const deletionKey = projectBranchKey(message.projectId, message.branchName);
5610
- const deletionNeedsSync = await workspaceSyncSingleFlight.runMutation(() => {
5678
+ const deletionNeedsSync = await workspaceSyncSingleFlight.runBranchMutation(() => {
5611
5679
  if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
5612
5680
  throw new Error(
5613
5681
  `Project branch deletion was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
@@ -5638,6 +5706,9 @@ async function startWorker(options) {
5638
5706
  projectId: message.projectId,
5639
5707
  branchName: message.branchName
5640
5708
  });
5709
+ if (pendingLocalBranch) {
5710
+ projectWorkspaceState = projectWorkspaceStateStore.clearPendingCreatedBranch(pendingLocalBranch);
5711
+ }
5641
5712
  pendingCreatedBranchPublicationNotBefore.delete((0, import_workspace_automatic_sync_policy.pendingCreatedBranchKey)(message.projectId, message.branchName));
5642
5713
  if (pendingMirrorDeletes.has(deletionKey)) return true;
5643
5714
  stageProjectBranchDeletion(project, message.branchName, true);
@@ -5915,7 +5986,7 @@ async function startWorker(options) {
5915
5986
  ws,
5916
5987
  message,
5917
5988
  resolvedTarget,
5918
- planRoot,
5989
+ rootDir,
5919
5990
  assertAdmission: assertMessageAdmission,
5920
5991
  ...releaseWorkspaceMutation ? { releaseWorkspaceMutation } : {},
5921
5992
  ...targetMayMutateVisibleWorkspace(message.target) ? {
@@ -6013,8 +6084,7 @@ async function startWorker(options) {
6013
6084
  resolvedTarget,
6014
6085
  baseUrl,
6015
6086
  token,
6016
- artifactRoot,
6017
- planRoot,
6087
+ rootDir,
6018
6088
  setPreparationHeartbeatLease: (active) => {
6019
6089
  sendWorkerMessageFromCurrentSource(ws, {
6020
6090
  type: "heartbeat_lease",
@@ -6083,8 +6153,7 @@ async function startWorker(options) {
6083
6153
  resolvedTarget,
6084
6154
  baseUrl,
6085
6155
  token,
6086
- artifactRoot,
6087
- planRoot,
6156
+ rootDir,
6088
6157
  setPreparationHeartbeatLease: (active) => {
6089
6158
  sendWorkerMessageFromCurrentSource(ws, {
6090
6159
  type: "heartbeat_lease",
@@ -6166,8 +6235,7 @@ async function startWorker(options) {
6166
6235
  resolvedTarget,
6167
6236
  baseUrl,
6168
6237
  token,
6169
- artifactRoot,
6170
- planRoot,
6238
+ rootDir,
6171
6239
  assertAdmission: assertMessageAdmission
6172
6240
  });
6173
6241
  });