@ricsam/r5d-worker 0.0.131 → 0.0.133

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 (47) hide show
  1. package/dist/cjs/atomic-rename.cjs +303 -0
  2. package/dist/cjs/git-blob-hash.cjs +41 -0
  3. package/dist/cjs/main.cjs +187 -38
  4. package/dist/cjs/package.json +1 -1
  5. package/dist/cjs/three-way-merge.cjs +346 -0
  6. package/dist/cjs/working-tree-mirror.cjs +1049 -64
  7. package/dist/cjs/workspace-command-sync-policy.cjs +8 -4
  8. package/dist/cjs/workspace-command-targets.cjs +63 -0
  9. package/dist/cjs/workspace-filesystem-job-types.cjs +11 -1
  10. package/dist/cjs/workspace-filesystem-jobs.cjs +2 -0
  11. package/dist/cjs/workspace-git-sync.cjs +846 -61
  12. package/dist/cjs/workspace-hydration-ledger.cjs +66 -0
  13. package/dist/cjs/workspace-hydration-merge.cjs +433 -0
  14. package/dist/cjs/workspace-hydration-recovery-state.cjs +53 -0
  15. package/dist/cjs/workspace-merge-projection.cjs +81 -10
  16. package/dist/cjs/workspace-project-config-policy.cjs +19 -12
  17. package/dist/mjs/atomic-rename.mjs +261 -0
  18. package/dist/mjs/git-blob-hash.mjs +16 -0
  19. package/dist/mjs/main.mjs +196 -39
  20. package/dist/mjs/package.json +1 -1
  21. package/dist/mjs/three-way-merge.mjs +318 -0
  22. package/dist/mjs/working-tree-mirror.mjs +1035 -64
  23. package/dist/mjs/workspace-command-sync-policy.mjs +8 -4
  24. package/dist/mjs/workspace-command-targets.mjs +37 -0
  25. package/dist/mjs/workspace-filesystem-job-types.mjs +11 -1
  26. package/dist/mjs/workspace-filesystem-jobs.mjs +4 -0
  27. package/dist/mjs/workspace-git-sync.mjs +854 -62
  28. package/dist/mjs/workspace-hydration-ledger.mjs +42 -0
  29. package/dist/mjs/workspace-hydration-merge.mjs +399 -0
  30. package/dist/mjs/workspace-hydration-recovery-state.mjs +29 -0
  31. package/dist/mjs/workspace-merge-projection.mjs +85 -11
  32. package/dist/mjs/workspace-project-config-policy.mjs +16 -10
  33. package/dist/types/atomic-rename.d.ts +78 -0
  34. package/dist/types/git-blob-hash.d.ts +10 -0
  35. package/dist/types/main.d.ts +21 -2
  36. package/dist/types/three-way-merge.d.ts +77 -0
  37. package/dist/types/working-tree-mirror.d.ts +270 -7
  38. package/dist/types/workspace-command-sync-policy.d.ts +12 -6
  39. package/dist/types/workspace-command-targets.d.ts +37 -0
  40. package/dist/types/workspace-filesystem-job-types.d.ts +46 -4
  41. package/dist/types/workspace-git-sync.d.ts +125 -3
  42. package/dist/types/workspace-hydration-ledger.d.ts +43 -0
  43. package/dist/types/workspace-hydration-merge.d.ts +95 -0
  44. package/dist/types/workspace-hydration-recovery-state.d.ts +10 -0
  45. package/dist/types/workspace-merge-projection.d.ts +19 -1
  46. package/dist/types/workspace-project-config-policy.d.ts +17 -3
  47. package/package.json +2 -2
package/dist/cjs/main.cjs CHANGED
@@ -87,7 +87,9 @@ var import_workspace_automatic_sync_policy = require("./workspace-automatic-sync
87
87
  var import_workspace_command_sync_policy = require("./workspace-command-sync-policy.cjs");
88
88
  var import_workspace_mutation_gate = require("./workspace-mutation-gate.cjs");
89
89
  var import_workspace_mount_hold_fence = require("./workspace-mount-hold-fence.cjs");
90
+ var import_workspace_command_targets = require("./workspace-command-targets.cjs");
90
91
  var import_workspace_filesystem_executor = require("./workspace-filesystem-executor.cjs");
92
+ var import_workspace_hydration_recovery_state = require("./workspace-hydration-recovery-state.cjs");
91
93
  var import_workspace_projection_ledger = require("./workspace-projection-ledger.cjs");
92
94
  var import_workspace_sync_coalescer = require("./workspace-sync-coalescer.cjs");
93
95
  var import_workspace_path_move = require("./workspace-path-move.cjs");
@@ -149,6 +151,16 @@ const workerLifecycleTestHarness = {
149
151
  workerCommandHasWorkspaceEffect
150
152
  };
151
153
  class ProjectWorkspaceConfigurationDeferredError extends Error {
154
+ constructor(changes) {
155
+ super(
156
+ `Project workspace configuration deferred while visible checkout activity is active: ${(0, import_workspace_project_config_policy.describeBusyProjectConfigurationChanges)(changes)}`
157
+ );
158
+ this.changes = changes;
159
+ this.name = "ProjectWorkspaceConfigurationDeferredError";
160
+ this.projectIds = changes.map(({ projectId }) => projectId);
161
+ }
162
+ changes;
163
+ projectIds;
152
164
  }
153
165
  class WorkerServerUnavailableError extends Error {
154
166
  name = "WorkerServerUnavailableError";
@@ -372,13 +384,12 @@ const workspaceSyncSingleFlight = {
372
384
  afterCurrent() {
373
385
  return workspaceSyncQueue;
374
386
  },
375
- mountHold(target, signal) {
376
- if (target.type !== "project") return null;
377
- const mountIds = [projectMountId(target.projectId, target.branchName)];
378
- if (!workspaceMountHoldFence.isHeld(mountIds)) return null;
387
+ mountHold(targets, signal) {
388
+ const mountIds = targets.flatMap((target) => target.type === "project" ? [projectMountId(target.projectId, target.branchName)] : []);
389
+ if (mountIds.length === 0 || !workspaceMountHoldFence.isHeld(mountIds)) return null;
379
390
  const holder = workspaceMountHoldFence.describeHold(mountIds);
380
391
  process.stdout.write(
381
- `[r5d-worker] command for ${target.projectId}/${target.branchName} waits for ${holder ?? "a workspace filesystem job"}
392
+ `[r5d-worker] command for ${targets.map(describeWorkerSessionTarget).join(", ")} waits for ${holder ?? "a workspace filesystem job"}
382
393
  `
383
394
  );
384
395
  return workspaceMountHoldFence.waitForRelease(mountIds, { signal });
@@ -991,6 +1002,7 @@ function validateProjectId(projectId) {
991
1002
  function validateBranchName(branchName) {
992
1003
  (0, import_managed_paths.validateManagedBranchName)(branchName);
993
1004
  }
1005
+ const commandTargetValidators = { validateProjectId, validateBranchName };
994
1006
  const NON_RECURSIVE_GIT_CONFIG_ARGS = [
995
1007
  "-c",
996
1008
  "submodule.recurse=false",
@@ -2942,6 +2954,7 @@ async function executeCommand(input) {
2942
2954
  activeProcesses.set(input.message.runId, {
2943
2955
  process: subprocess,
2944
2956
  target: input.resolvedTarget.target,
2957
+ additionalTargets: [...input.additionalTargets],
2945
2958
  sessionId: input.message.sessionId ?? "",
2946
2959
  mode: "foreground",
2947
2960
  pid: subprocess.pid,
@@ -3075,6 +3088,7 @@ async function executeStreamingCommand(input) {
3075
3088
  activeProcesses.set(input.message.runId, {
3076
3089
  process: subprocess,
3077
3090
  target: input.resolvedTarget.target,
3091
+ additionalTargets: [...input.additionalTargets],
3078
3092
  sessionId: input.message.sessionId,
3079
3093
  mode: input.message.mode,
3080
3094
  pid: subprocess.pid,
@@ -3315,6 +3329,7 @@ function buildActiveProcessReports() {
3315
3329
  return Array.from(activeProcesses.entries()).filter(([, active]) => active.sessionId.length > 0).map(([runId, active]) => ({
3316
3330
  runId,
3317
3331
  target: active.target,
3332
+ ...active.additionalTargets.length > 0 ? { additionalTargets: active.additionalTargets.flatMap((target) => target.type === "project" ? [target] : []) } : {},
3318
3333
  sessionId: active.sessionId,
3319
3334
  platform: process.platform,
3320
3335
  arch: process.arch,
@@ -3722,6 +3737,7 @@ async function openPty(input) {
3722
3737
  activePtys.set(input.message.ptyId, {
3723
3738
  ...ptyProcess,
3724
3739
  target: input.resolvedTarget.target,
3740
+ additionalTargets: [...input.additionalTargets],
3725
3741
  foregroundBusy: true,
3726
3742
  lastInputAt: 0,
3727
3743
  ...input.releaseWorkspaceMutation ? { releaseWorkspaceMutation: input.releaseWorkspaceMutation } : {}
@@ -3947,6 +3963,7 @@ async function startWorker(options, projectRuntime = {
3947
3963
  let workspaceCredentialUsername = null;
3948
3964
  let workspaceGitIdentity = projectRuntime.gitIdentity;
3949
3965
  let workspaceConfigured = false;
3966
+ let hydrationRecoveryRequired;
3950
3967
  let activeWorkspaceIncidentId = null;
3951
3968
  let deferredWorkspaceConfiguration = null;
3952
3969
  let deferredWorkspaceConfigurationRefreshTimer;
@@ -4246,12 +4263,12 @@ async function startWorker(options, projectRuntime = {
4246
4263
  });
4247
4264
  };
4248
4265
  const activeWorkspaceMutationTargets = () => [
4249
- ...Array.from(activeProcesses.values()).filter(workerCommandHasWorkspaceEffect).map(({ target }) => target),
4266
+ ...Array.from(activeProcesses.values()).filter(workerCommandHasWorkspaceEffect).flatMap(({ target, additionalTargets }) => [target, ...additionalTargets]),
4250
4267
  ...credentialBearingProcessGroupTargets.values(),
4251
- ...workspaceSyncPriorityProcessTargets.values(),
4268
+ ...Array.from(workspaceSyncPriorityProcessTargets.values()).flat(),
4252
4269
  ...workspaceSyncPriorityOperationTargets.values(),
4253
- ...Array.from(activePtys.values()).filter((pty) => workerPtyIsWorkspaceBusy(pty)).map(({ target }) => target),
4254
- ...workspaceSyncPriorityPtyTargets.values()
4270
+ ...Array.from(activePtys.values()).filter((pty) => workerPtyIsWorkspaceBusy(pty)).flatMap(({ target, additionalTargets }) => [target, ...additionalTargets]),
4271
+ ...Array.from(workspaceSyncPriorityPtyTargets.values()).flat()
4255
4272
  ];
4256
4273
  let visibleWorkspaceMutationEpoch = 0;
4257
4274
  const projectWorkspaceMutationEpochs = /* @__PURE__ */ new Map();
@@ -4263,6 +4280,9 @@ async function startWorker(options, projectRuntime = {
4263
4280
  const key = projectBranchKey(target.projectId, target.branchName);
4264
4281
  projectWorkspaceMutationEpochs.set(key, (projectWorkspaceMutationEpochs.get(key) ?? 0) + 1);
4265
4282
  };
4283
+ const recordAdditionalTargetMutations = (additional) => {
4284
+ for (const target of additional) recordVisibleWorkspaceMutation(target);
4285
+ };
4266
4286
  const projectWorkspaceMutationToken = (projectId, branchName) => `${visibleWorkspaceMutationEpoch}:${projectWorkspaceMutationEpochs.get(projectBranchKey(projectId, branchName)) ?? 0}`;
4267
4287
  const canonicalWorkspaceMutationIsActive = (targets) => targets.some((target) => target.type === "workspace" && target.rootProfile === "canonical_sync");
4268
4288
  const projectBranchMountActivityBusy = (projectId, branchName, branchPath) => {
@@ -4348,6 +4368,24 @@ async function startWorker(options, projectRuntime = {
4348
4368
  preserveLocalOnInitialOuterAbsence: creatorLocalIncarnation,
4349
4369
  ...initialPublicationSourceBranch ? { initialPublicationSourceRelativePath: workspaceProjectRelativePath(project.projectId, initialPublicationSourceBranch) } : {},
4350
4370
  preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
4371
+ describeHolders: () => [
4372
+ ...new Set(
4373
+ [
4374
+ ...[...activeProcesses.values()].filter(
4375
+ (entry) => (0, import_workspace_automatic_sync_policy.projectBranchHasActiveWorkspaceTarget)(project.projectId, branch.branchName, [
4376
+ entry.target,
4377
+ ...entry.additionalTargets
4378
+ ])
4379
+ ).map((entry) => entry.sessionId),
4380
+ ...[...activePtys.entries()].filter(
4381
+ ([, entry]) => (0, import_workspace_automatic_sync_policy.projectBranchHasActiveWorkspaceTarget)(project.projectId, branch.branchName, [
4382
+ entry.target,
4383
+ ...entry.additionalTargets
4384
+ ])
4385
+ ).map(([ptyId]) => `pty:${ptyId}`)
4386
+ ].filter((id) => typeof id === "string")
4387
+ )
4388
+ ].join(", ") || "no live declared holder",
4351
4389
  busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
4352
4390
  mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
4353
4391
  busyForRecovery: () => projectBranchMountExclusiveSyncBusy(project.projectId, branch.branchName, branchPath)
@@ -4873,6 +4911,7 @@ async function startWorker(options, projectRuntime = {
4873
4911
  affectedPaths: result.affectedPaths,
4874
4912
  activeMountIds: result.activeMountIds,
4875
4913
  skippedMountIds: result.skippedMountIds,
4914
+ ...result.mountSkipReasons ? { mountSkipReasons: result.mountSkipReasons } : {},
4876
4915
  ...missingCheckouts ? { missingCheckouts } : {},
4877
4916
  activeProjectBranchPublications,
4878
4917
  discardedPaths: [],
@@ -4885,8 +4924,11 @@ async function startWorker(options, projectRuntime = {
4885
4924
  };
4886
4925
  };
4887
4926
  const failedWorkspaceSyncResult = async (attemptId, trigger, error) => {
4927
+ const recovery = (0, import_workspace_hydration_recovery_state.findHydrationRecovery)(error);
4928
+ if (recovery) hydrationRecoveryRequired = recovery;
4888
4929
  const localHead = await workspaceLocalHead();
4889
4930
  return {
4931
+ ...recovery ? { failureReason: "hydration_recovery_required", hydrationRecovery: recovery } : {},
4890
4932
  type: "workspace_sync",
4891
4933
  attemptId,
4892
4934
  workerLabel: label,
@@ -5109,6 +5151,12 @@ async function startWorker(options, projectRuntime = {
5109
5151
  } catch (error) {
5110
5152
  workspaceProjectionLedger.reset();
5111
5153
  const errorMessage = error instanceof Error ? error.message : String(error);
5154
+ if ((0, import_workspace_hydration_recovery_state.findHydrationRecovery)(error)) {
5155
+ workspaceConfigured = false;
5156
+ projectRuntime.configuration = void 0;
5157
+ advanceWorkerAdmissionGeneration();
5158
+ return await failedWorkspaceSyncResult(attemptId, input.trigger, error);
5159
+ }
5112
5160
  const hydrationRecoverable = workspaceSyncFailureHydrationIsSafe({
5113
5161
  error,
5114
5162
  resetToCanonical: input.resetToCanonical === true,
@@ -5352,6 +5400,10 @@ async function startWorker(options, projectRuntime = {
5352
5400
  projectRuntime.markWorkspaceDirty = markWorkspaceDirty;
5353
5401
  const targetMayMutateVisibleWorkspace = (target) => target.type === "project" || target.rootProfile === "visible_projects";
5354
5402
  const resolveMessageTarget = (target) => {
5403
+ if (hydrationRecoveryRequired)
5404
+ throw new Error(
5405
+ `Workspace hydration recovery required for transaction ${hydrationRecoveryRequired.transactionId}; inspect r5dctl workspace status`
5406
+ );
5355
5407
  if (!workspaceConfigured) throw new Error("Worker workspace configuration has not completed successfully");
5356
5408
  if (!deferredWorkspaceTargetIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, target)) {
5357
5409
  throw new Error(
@@ -5371,6 +5423,11 @@ async function startWorker(options, projectRuntime = {
5371
5423
  projectConfigById
5372
5424
  });
5373
5425
  };
5426
+ const resolveCommandMessageTarget = (target, additionalTargets) => {
5427
+ const primary = resolveMessageTarget(target);
5428
+ for (const additional of additionalTargets) resolveMessageTarget(additional);
5429
+ return primary;
5430
+ };
5374
5431
  const configureWorkerWorkspace = async (message, receiptGeneration) => {
5375
5432
  const configurationOperationId = crypto.randomUUID();
5376
5433
  sendLifecycleProgress({
@@ -5545,7 +5602,7 @@ async function startWorker(options, projectRuntime = {
5545
5602
  throw new Error("Workspace configuration was superseded by a different active workspace incident");
5546
5603
  }
5547
5604
  for (const project of message.projects) (0, import_repository_transition_policy.assertRepositoryTransitionState)(project);
5548
- const busyConfigurationChanges = (0, import_workspace_project_config_policy.busyProjectConfigurationChangeIds)({
5605
+ const busyConfigurationChanges = (0, import_workspace_project_config_policy.busyProjectConfigurationChanges)({
5549
5606
  currentProjects: [...projectConfigById.values()],
5550
5607
  incomingProjects: message.projects,
5551
5608
  activeTargets: activeWorkspaceMutationTargets(),
@@ -5556,12 +5613,44 @@ async function startWorker(options, projectRuntime = {
5556
5613
  return readyProjectIds.has(project.projectId) && reconciledProjectConfigFingerprints.get(project.projectId) === fingerprint && project.branches.every(({ branchName }) => hasProjectWorktree(configuredProjectBranchPath(projectsRoot, project, branchName)));
5557
5614
  }
5558
5615
  });
5559
- if (busyConfigurationChanges.length > 0) {
5560
- throw new ProjectWorkspaceConfigurationDeferredError(
5561
- `Project workspace configuration deferred while visible checkout activity is active: ${busyConfigurationChanges.join(", ")}`
5616
+ if (busyConfigurationChanges.length > 0) throw new ProjectWorkspaceConfigurationDeferredError(busyConfigurationChanges);
5617
+ workspaceConfigured = false;
5618
+ if (message.recoverHydrationTransactionId) {
5619
+ if (incidentDeferral.incidentId || message.resetToCanonical)
5620
+ throw new Error("Hydration preservation cannot run during a canonical reset or another workspace incident");
5621
+ if (activeProcesses.size || activePtys.size || credentialBearingProcessGroups.size || pendingProcessTerminals.size || activeWorkspaceMutationTargets().length) {
5622
+ throw new Error("Stop active worker processes and terminals before preserving interrupted hydration");
5623
+ }
5624
+ const preservedHydration = await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("hydration_preserve_interrupted", {
5625
+ workspacePath: workspaceShadowRoot,
5626
+ expectedTransactionId: message.recoverHydrationTransactionId
5627
+ });
5628
+ process.stdout.write(
5629
+ `[r5d-worker] Preserved hydration ${preservedHydration.transactionId}; archived evidence: ${preservedHydration.archivePath}
5630
+ `
5631
+ );
5632
+ for (const entry of preservedHydration.retainedEntries ?? []) {
5633
+ process.stdout.write(
5634
+ `[r5d-worker] Retained writer evidence for ${entry.mountId}: ${entry.sourcePath}; archived copy: ${entry.archivePath}
5635
+ `
5636
+ );
5637
+ }
5638
+ hydrationRecoveryRequired = void 0;
5639
+ }
5640
+ const interruptedHydration = await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("hydration_recovery_inspect", {
5641
+ workspacePath: workspaceShadowRoot
5642
+ });
5643
+ if (interruptedHydration) {
5644
+ throw Object.assign(
5645
+ new Error(
5646
+ `Interrupted hydration ${interruptedHydration.transactionId} requires explicit recovery; checkout files and snapshots are preserved`
5647
+ ),
5648
+ {
5649
+ code: "hydration_recovery_required",
5650
+ hydrationRecovery: interruptedHydration
5651
+ }
5562
5652
  );
5563
5653
  }
5564
- workspaceConfigured = false;
5565
5654
  workspaceProjectionLedger.reset();
5566
5655
  const preserveOnlyBranches = message.projects.flatMap(
5567
5656
  (project) => project.preserveOnlyBranches.map(({ branchId, branchName }) => ({ branchId, projectId: project.projectId, branchName }))
@@ -5827,6 +5916,7 @@ async function startWorker(options, projectRuntime = {
5827
5916
  deferredWorkspaceConfigurationRefreshTimer = void 0;
5828
5917
  }
5829
5918
  deferredWorkspaceConfiguration = null;
5919
+ hydrationRecoveryRequired = void 0;
5830
5920
  workspaceConfigured = true;
5831
5921
  const pending = [...pendingCheckouts.values()].sort(
5832
5922
  (left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
@@ -5869,7 +5959,8 @@ async function startWorker(options, projectRuntime = {
5869
5959
  requireCredentialGenerationRestart(true, void 0, 1);
5870
5960
  }
5871
5961
  workspaceConfigured = false;
5872
- const deferred = error instanceof ProjectWorkspaceConfigurationDeferredError || error instanceof import_project_workspace_state.ProjectWorkspacePendingBranchPathChangeError;
5962
+ const busyDeferral = error instanceof ProjectWorkspaceConfigurationDeferredError ? error : void 0;
5963
+ const deferred = busyDeferral !== void 0 || error instanceof import_project_workspace_state.ProjectWorkspacePendingBranchPathChangeError;
5873
5964
  if (deferred) pendingCheckouts.clear();
5874
5965
  for (const pending of (0, import_workspace_project_config_policy.deferredProjectConfigurationPendingBranches)(message.projects)) {
5875
5966
  pendingCheckouts.set(projectBranchKey(pending.projectId, pending.branchName), pending);
@@ -5879,7 +5970,11 @@ async function startWorker(options, projectRuntime = {
5879
5970
  pending: [...pendingCheckouts.values()].sort(
5880
5971
  (left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
5881
5972
  ),
5882
- aheadOfOriginBranches: []
5973
+ aheadOfOriginBranches: [],
5974
+ // A busy deferral is typed so the server can keep this socket, finish
5975
+ // receipt recovery and renew the execution lease instead of closing
5976
+ // the connection and retrying registration until the lease expires.
5977
+ ...busyDeferral ? { deferredForBusyProjectIds: busyDeferral.projectIds } : {}
5883
5978
  };
5884
5979
  } finally {
5885
5980
  workspaceSyncRequestsInFlight -= 1;
@@ -6068,7 +6163,9 @@ async function startWorker(options, projectRuntime = {
6068
6163
  commandControlLaneV1: workerCommandLauncher?.supportsControlLane === true,
6069
6164
  projectMirrorLeaseV1: true,
6070
6165
  projectMirrorRefsTokensV1: true,
6071
- projectBranchWorkingTreeModeV1: true
6166
+ projectBranchWorkingTreeModeV1: true,
6167
+ commandAdditionalTargetsV1: true,
6168
+ workspaceHydrationRecoveryV1: true
6072
6169
  },
6073
6170
  root: rootDir
6074
6171
  }
@@ -6304,7 +6401,7 @@ async function startWorker(options, projectRuntime = {
6304
6401
  }
6305
6402
  const digest = (0, import_recovery_store.workerOperationDigest)(message);
6306
6403
  const cached = projectRuntime.configuration;
6307
- const canReuseConfiguration = cached?.digest === digest && cached.workspaceSync?.remoteUrl === message.workspaceRemoteUrl && Boolean(cached.workspaceSync.credentialHelper) && cached.workspaceSync.credentialUsername === workerCredentialUsername && cached.workspaceSync.gitIdentity?.name === message.gitIdentity.name && cached.workspaceSync.gitIdentity?.email === message.gitIdentity.email && !unsafeWorkspaceSyncBouncePending && credentialGenerationFingerprint({ ...message, workerBaseUrl: baseUrl, workerAuthHeader: bearerAuthHeader }) === configuredCredentialGenerationFingerprint;
6404
+ const canReuseConfiguration = !message.recoverHydrationTransactionId && !hydrationRecoveryRequired && cached?.digest === digest && cached.workspaceSync?.remoteUrl === message.workspaceRemoteUrl && Boolean(cached.workspaceSync.credentialHelper) && cached.workspaceSync.credentialUsername === workerCredentialUsername && cached.workspaceSync.gitIdentity?.name === message.gitIdentity.name && cached.workspaceSync.gitIdentity?.email === message.gitIdentity.email && !unsafeWorkspaceSyncBouncePending && credentialGenerationFingerprint({ ...message, workerBaseUrl: baseUrl, workerAuthHeader: bearerAuthHeader }) === configuredCredentialGenerationFingerprint;
6308
6405
  if (!canReuseConfiguration) {
6309
6406
  advanceWorkerAdmissionGeneration();
6310
6407
  projectRuntime.configuration = void 0;
@@ -6316,7 +6413,8 @@ async function startWorker(options, projectRuntime = {
6316
6413
  result: configured.result,
6317
6414
  pendingCheckouts: configured.pending,
6318
6415
  aheadOfOriginBranches: configured.aheadOfOriginBranches,
6319
- ...configured.deferredWorkspaceSyncForIncidentId ? { deferredWorkspaceSyncForIncidentId: configured.deferredWorkspaceSyncForIncidentId } : {}
6416
+ ...configured.deferredWorkspaceSyncForIncidentId ? { deferredWorkspaceSyncForIncidentId: configured.deferredWorkspaceSyncForIncidentId } : {},
6417
+ ...configured.deferredForBusyProjectIds ? { deferredForBusyProjectIds: configured.deferredForBusyProjectIds } : {}
6320
6418
  });
6321
6419
  if (receiptGeneration !== workspaceConfigurationReceiptGeneration || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
6322
6420
  sendConfigured();
@@ -6347,7 +6445,17 @@ async function startWorker(options, projectRuntime = {
6347
6445
  if (configured.result.outcome !== "failed") unsafeWorkspaceSyncRecoveryAttempts = 0;
6348
6446
  sendConfigured();
6349
6447
  sendLifecycleProgress(
6350
- configured.result.outcome === "failed" ? {
6448
+ configured.result.hydrationRecovery ? {
6449
+ phase: "failed",
6450
+ operationId: configured.result.attemptId,
6451
+ detail: `Hydration recovery required: ${configured.result.hydrationRecovery.transactionId}; files and snapshots preserved`
6452
+ } : configured.deferredForBusyProjectIds ? {
6453
+ // Not a failure: the previous generation stays on disk and the
6454
+ // server retries once the busy checkouts are idle.
6455
+ phase: "configuring",
6456
+ operationId: configured.result.attemptId,
6457
+ detail: `Workspace configuration deferred while checkout activity is active on ${configured.deferredForBusyProjectIds.length} project(s)`
6458
+ } : configured.result.outcome === "failed" ? {
6351
6459
  phase: "failed",
6352
6460
  operationId: configured.result.attemptId,
6353
6461
  detail: configured.result.error ?? "Workspace configuration failed"
@@ -6360,10 +6468,12 @@ async function startWorker(options, projectRuntime = {
6360
6468
  }
6361
6469
  );
6362
6470
  process.stdout.write(
6363
- `[r5d-worker] workspace configured: ${message.projects.length} project(s), ${configured.pending.length} pending checkout(s)
6471
+ configured.result.hydrationRecovery ? `[r5d-worker] workspace hydration recovery required for ${configured.result.hydrationRecovery.transactionId}; execution remains fenced, files and snapshots preserved
6472
+ ` : configured.deferredForBusyProjectIds ? `[r5d-worker] workspace configuration deferred while visible checkout activity is active on ${configured.deferredForBusyProjectIds.join(", ")}: ${configured.pending.length} checkout(s) pending until the server retries
6473
+ ` : `[r5d-worker] workspace configured: ${message.projects.length} project(s), ${configured.pending.length} pending checkout(s)
6364
6474
  `
6365
6475
  );
6366
- if (refreshesDeferredConfiguration && configured.result.outcome === "failed" && deferredWorkspaceConfiguration !== null && currentWorkerSocket === ws) {
6476
+ if (refreshesDeferredConfiguration && configured.result.outcome === "failed" && configured.result.failureReason !== "hydration_recovery_required" && !configured.deferredForBusyProjectIds && deferredWorkspaceConfiguration !== null && currentWorkerSocket === ws) {
6367
6477
  workspaceConfigured = false;
6368
6478
  advanceWorkerAdmissionGeneration();
6369
6479
  ws.close(1012, "Deferred workspace configuration refresh failed");
@@ -6755,6 +6865,7 @@ async function startWorker(options, projectRuntime = {
6755
6865
  if (active.workspaceEffect !== "none" && targetMayMutateVisibleWorkspace(active.target)) {
6756
6866
  recordVisibleWorkspaceMutation(active.target);
6757
6867
  }
6868
+ if (active.workspaceEffect !== "none") recordAdditionalTargetMutations(active.additionalTargets);
6758
6869
  try {
6759
6870
  let bytesWritten = 0;
6760
6871
  if (message.data !== void 0 && message.data.length > 0) {
@@ -6804,16 +6915,19 @@ async function startWorker(options, projectRuntime = {
6804
6915
  let resourcesTransferred = false;
6805
6916
  let releaseWorkspaceMutation;
6806
6917
  let mutationLeaseTransferred = false;
6918
+ let additionalTargets = [];
6807
6919
  try {
6920
+ additionalTargets = (0, import_workspace_command_targets.normalizeCommandAdditionalTargets)(message.target, message.additionalTargets, commandTargetValidators);
6808
6921
  resources = await acquireWorkerCommandLaunch(ws, message, assertMessageAdmission);
6809
6922
  const reservationAbort = registerPendingReservation(`pty:${message.ptyId}`, void 0);
6810
6923
  try {
6811
6924
  await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(
6812
- message.target,
6925
+ (0, import_workspace_command_targets.commandTargets)(message.target, additionalTargets),
6813
6926
  workspaceSyncSingleFlight,
6814
6927
  () => {
6815
- workspaceSyncPriorityPtyTargets.set(message.ptyId, message.target);
6928
+ workspaceSyncPriorityPtyTargets.set(message.ptyId, (0, import_workspace_command_targets.commandTargets)(message.target, additionalTargets));
6816
6929
  if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
6930
+ recordAdditionalTargetMutations(additionalTargets);
6817
6931
  },
6818
6932
  { signal: reservationAbort.signal }
6819
6933
  );
@@ -6826,19 +6940,26 @@ async function startWorker(options, projectRuntime = {
6826
6940
  `Shell opening was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
6827
6941
  );
6828
6942
  }
6829
- const resolvedTarget = resolveMessageTarget(message.target);
6830
- process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${describeWorkerSessionTarget(message.target)}
6831
- `);
6943
+ const resolvedTarget = resolveCommandMessageTarget(message.target, additionalTargets);
6944
+ process.stdout.write(
6945
+ `[r5d-worker] pty ${message.ptyId}: ${describeWorkerSessionTarget(message.target)}${(0, import_workspace_command_targets.describeCommandAdditionalTargets)(additionalTargets)}
6946
+ `
6947
+ );
6832
6948
  await openPty({
6833
6949
  resources,
6834
6950
  ws,
6835
6951
  message,
6836
6952
  resolvedTarget,
6953
+ additionalTargets,
6837
6954
  rootDir,
6838
- assertAdmission: assertMessageAdmission,
6955
+ assertAdmission: () => {
6956
+ assertMessageAdmission();
6957
+ resolveCommandMessageTarget(message.target, additionalTargets);
6958
+ },
6839
6959
  ...releaseWorkspaceMutation ? { releaseWorkspaceMutation } : {},
6840
6960
  ...targetMayMutateVisibleWorkspace(message.target) ? {
6841
6961
  onTerminal: () => {
6962
+ recordAdditionalTargetMutations(additionalTargets);
6842
6963
  markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} completed` }, true, message.target);
6843
6964
  }
6844
6965
  } : {}
@@ -6871,6 +6992,7 @@ async function startWorker(options, projectRuntime = {
6871
6992
  return;
6872
6993
  }
6873
6994
  if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
6995
+ recordAdditionalTargetMutations(activePty.additionalTargets);
6874
6996
  markWorkspaceDirty({ type: "shell_inline", detail: `pty ${message.ptyId}` }, false, activePty.target);
6875
6997
  }
6876
6998
  writePty(ws, message);
@@ -6887,6 +7009,7 @@ async function startWorker(options, projectRuntime = {
6887
7009
  abortPendingReservation(`pty:${message.ptyId}`, "Shell closed before it started");
6888
7010
  await closePty(message);
6889
7011
  if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
7012
+ recordAdditionalTargetMutations(activePty.additionalTargets);
6890
7013
  markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} closed` }, true, activePty.target);
6891
7014
  }
6892
7015
  sendWorkerMessage(ws, { type: "operation_completed", requestId: message.requestId });
@@ -6918,18 +7041,21 @@ async function startWorker(options, projectRuntime = {
6918
7041
  let resources;
6919
7042
  let targetReserved = false;
6920
7043
  const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
7044
+ let additionalTargets = [];
6921
7045
  try {
7046
+ additionalTargets = (0, import_workspace_command_targets.normalizeCommandAdditionalTargets)(message.target, message.additionalTargets, commandTargetValidators);
6922
7047
  resources = await acquireWorkerCommandLaunch(ws, message, assertMessageAdmission);
6923
7048
  if (hasWorkspaceEffect) {
6924
7049
  const reservationAbort = registerPendingReservation(`run:${message.runId}`, message.sessionId);
6925
7050
  try {
6926
7051
  await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(
6927
- message.target,
7052
+ (0, import_workspace_command_targets.commandTargets)(message.target, additionalTargets),
6928
7053
  workspaceSyncSingleFlight,
6929
7054
  () => {
6930
- workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
7055
+ workspaceSyncPriorityProcessTargets.set(message.runId, (0, import_workspace_command_targets.commandTargets)(message.target, additionalTargets));
6931
7056
  targetReserved = true;
6932
7057
  if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
7058
+ recordAdditionalTargetMutations(additionalTargets);
6933
7059
  },
6934
7060
  { signal: reservationAbort.signal }
6935
7061
  );
@@ -6943,13 +7069,16 @@ async function startWorker(options, projectRuntime = {
6943
7069
  `One-shot command was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
6944
7070
  );
6945
7071
  }
6946
- const resolvedTarget = resolveMessageTarget(message.target);
6947
- process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
6948
- `);
7072
+ const resolvedTarget = resolveCommandMessageTarget(message.target, additionalTargets);
7073
+ process.stdout.write(
7074
+ `[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}${(0, import_workspace_command_targets.describeCommandAdditionalTargets)(additionalTargets)}
7075
+ `
7076
+ );
6949
7077
  return await executeCommand({
6950
7078
  resources,
6951
7079
  message,
6952
7080
  resolvedTarget,
7081
+ additionalTargets,
6953
7082
  baseUrl,
6954
7083
  token,
6955
7084
  rootDir,
@@ -6967,6 +7096,7 @@ async function startWorker(options, projectRuntime = {
6967
7096
  );
6968
7097
  }
6969
7098
  assertMessageAdmission();
7099
+ resolveCommandMessageTarget(message.target, additionalTargets);
6970
7100
  }
6971
7101
  });
6972
7102
  };
@@ -6992,6 +7122,7 @@ async function startWorker(options, projectRuntime = {
6992
7122
  }
6993
7123
  sendSerializedWorkerMessage(ws, JSON.stringify(result));
6994
7124
  if (hasWorkspaceEffect && targetMayMutateVisibleWorkspace(message.target)) {
7125
+ recordAdditionalTargetMutations(additionalTargets);
6995
7126
  markWorkspaceDirty(
6996
7127
  { type: "shell_inline", sessionId: message.sessionId, processRunId: message.runId, detail: "foreground command completed" },
6997
7128
  true,
@@ -7017,11 +7148,23 @@ async function startWorker(options, projectRuntime = {
7017
7148
  });
7018
7149
  const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
7019
7150
  let resources;
7151
+ let additionalTargets;
7152
+ try {
7153
+ additionalTargets = (0, import_workspace_command_targets.normalizeCommandAdditionalTargets)(message.target, message.additionalTargets, commandTargetValidators);
7154
+ } catch (error) {
7155
+ sendWorkerMessageFromCurrentSource(ws, {
7156
+ type: "exec_start_error",
7157
+ requestId: message.requestId,
7158
+ runId: message.runId,
7159
+ error: error instanceof Error ? error.message : String(error)
7160
+ });
7161
+ return;
7162
+ }
7020
7163
  const runCommand = async () => {
7021
7164
  try {
7022
- const resolvedTarget = resolveMessageTarget(message.target);
7165
+ const resolvedTarget = resolveCommandMessageTarget(message.target, additionalTargets);
7023
7166
  process.stdout.write(
7024
- `[r5d-worker] exec_start ${message.runId}${message.commandClass === "control" ? " (control)" : ""}: ${message.argv.join(" ")}
7167
+ `[r5d-worker] exec_start ${message.runId}${message.commandClass === "control" ? " (control)" : ""}: ${message.argv.join(" ")}${(0, import_workspace_command_targets.describeCommandAdditionalTargets)(additionalTargets)}
7025
7168
  `
7026
7169
  );
7027
7170
  await executeStreamingCommand({
@@ -7029,6 +7172,7 @@ async function startWorker(options, projectRuntime = {
7029
7172
  ws,
7030
7173
  message,
7031
7174
  resolvedTarget,
7175
+ additionalTargets,
7032
7176
  baseUrl,
7033
7177
  token,
7034
7178
  rootDir,
@@ -7039,10 +7183,14 @@ async function startWorker(options, projectRuntime = {
7039
7183
  active
7040
7184
  });
7041
7185
  },
7042
- assertAdmission: assertMessageAdmission
7186
+ assertAdmission: () => {
7187
+ assertMessageAdmission();
7188
+ resolveCommandMessageTarget(message.target, additionalTargets);
7189
+ }
7043
7190
  });
7044
7191
  } finally {
7045
7192
  if (hasWorkspaceEffect && targetMayMutateVisibleWorkspace(message.target)) {
7193
+ recordAdditionalTargetMutations(additionalTargets);
7046
7194
  markWorkspaceDirty(
7047
7195
  {
7048
7196
  type: "process_terminal",
@@ -7064,12 +7212,13 @@ async function startWorker(options, projectRuntime = {
7064
7212
  const reservationAbort = registerPendingReservation(`run:${message.runId}`, message.sessionId);
7065
7213
  try {
7066
7214
  await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(
7067
- message.target,
7215
+ (0, import_workspace_command_targets.commandTargets)(message.target, additionalTargets),
7068
7216
  workspaceSyncSingleFlight,
7069
7217
  () => {
7070
- workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
7218
+ workspaceSyncPriorityProcessTargets.set(message.runId, (0, import_workspace_command_targets.commandTargets)(message.target, additionalTargets));
7071
7219
  targetReserved = true;
7072
7220
  if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
7221
+ recordAdditionalTargetMutations(additionalTargets);
7073
7222
  },
7074
7223
  { signal: reservationAbort.signal }
7075
7224
  );
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.131",
3
+ "version": "0.0.133",
4
4
  "type": "commonjs"
5
5
  }