@ricsam/r5d-worker 0.0.132 → 0.0.134
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.
- package/dist/cjs/atomic-rename.cjs +303 -0
- package/dist/cjs/git-blob-hash.cjs +41 -0
- package/dist/cjs/main.cjs +187 -38
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/three-way-merge.cjs +346 -0
- package/dist/cjs/working-tree-mirror.cjs +1049 -64
- package/dist/cjs/workspace-command-sync-policy.cjs +8 -4
- package/dist/cjs/workspace-command-targets.cjs +63 -0
- package/dist/cjs/workspace-filesystem-job-types.cjs +11 -1
- package/dist/cjs/workspace-filesystem-jobs.cjs +2 -0
- package/dist/cjs/workspace-git-sync.cjs +846 -61
- package/dist/cjs/workspace-hydration-ledger.cjs +66 -0
- package/dist/cjs/workspace-hydration-merge.cjs +433 -0
- package/dist/cjs/workspace-hydration-recovery-state.cjs +53 -0
- package/dist/cjs/workspace-merge-projection.cjs +81 -10
- package/dist/cjs/workspace-project-config-policy.cjs +19 -12
- package/dist/mjs/atomic-rename.mjs +261 -0
- package/dist/mjs/git-blob-hash.mjs +16 -0
- package/dist/mjs/main.mjs +196 -39
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/three-way-merge.mjs +318 -0
- package/dist/mjs/working-tree-mirror.mjs +1035 -64
- package/dist/mjs/workspace-command-sync-policy.mjs +8 -4
- package/dist/mjs/workspace-command-targets.mjs +37 -0
- package/dist/mjs/workspace-filesystem-job-types.mjs +11 -1
- package/dist/mjs/workspace-filesystem-jobs.mjs +4 -0
- package/dist/mjs/workspace-git-sync.mjs +854 -62
- package/dist/mjs/workspace-hydration-ledger.mjs +42 -0
- package/dist/mjs/workspace-hydration-merge.mjs +399 -0
- package/dist/mjs/workspace-hydration-recovery-state.mjs +29 -0
- package/dist/mjs/workspace-merge-projection.mjs +85 -11
- package/dist/mjs/workspace-project-config-policy.mjs +16 -10
- package/dist/types/atomic-rename.d.ts +78 -0
- package/dist/types/git-blob-hash.d.ts +10 -0
- package/dist/types/main.d.ts +21 -2
- package/dist/types/three-way-merge.d.ts +77 -0
- package/dist/types/working-tree-mirror.d.ts +270 -7
- package/dist/types/workspace-command-sync-policy.d.ts +12 -6
- package/dist/types/workspace-command-targets.d.ts +37 -0
- package/dist/types/workspace-filesystem-job-types.d.ts +46 -4
- package/dist/types/workspace-git-sync.d.ts +125 -3
- package/dist/types/workspace-hydration-ledger.d.ts +43 -0
- package/dist/types/workspace-hydration-merge.d.ts +95 -0
- package/dist/types/workspace-hydration-recovery-state.d.ts +10 -0
- package/dist/types/workspace-merge-projection.d.ts +19 -1
- package/dist/types/workspace-project-config-policy.d.ts +17 -3
- package/package.json +2 -2
package/dist/mjs/main.mjs
CHANGED
|
@@ -62,11 +62,21 @@ import {
|
|
|
62
62
|
} from "./workspace-command-sync-policy.mjs";
|
|
63
63
|
import { WorkspaceMutationGate } from "./workspace-mutation-gate.mjs";
|
|
64
64
|
import { WorkspaceMountHoldAbortedError, WorkspaceMountHoldFence } from "./workspace-mount-hold-fence.mjs";
|
|
65
|
+
import {
|
|
66
|
+
commandTargets,
|
|
67
|
+
describeCommandAdditionalTargets,
|
|
68
|
+
normalizeCommandAdditionalTargets
|
|
69
|
+
} from "./workspace-command-targets.mjs";
|
|
65
70
|
import { installWorkspaceFilesystemExecutorFailureHandler, workspaceFilesystemExecutor } from "./workspace-filesystem-executor.mjs";
|
|
71
|
+
import { findHydrationRecovery } from "./workspace-hydration-recovery-state.mjs";
|
|
66
72
|
import { WorkspaceProjectionLedger } from "./workspace-projection-ledger.mjs";
|
|
67
73
|
import { WorkspaceSyncCoalescer } from "./workspace-sync-coalescer.mjs";
|
|
68
74
|
import { checkoutPathMovePublicationComplete } from "./workspace-path-move.mjs";
|
|
69
|
-
import {
|
|
75
|
+
import {
|
|
76
|
+
busyProjectConfigurationChanges,
|
|
77
|
+
deferredProjectConfigurationPendingBranches,
|
|
78
|
+
describeBusyProjectConfigurationChanges
|
|
79
|
+
} from "./workspace-project-config-policy.mjs";
|
|
70
80
|
import { creatorLocalProjectBranchIsAuthorized, workerProjectBranchDisposition } from "./workspace-preserve-only-policy.mjs";
|
|
71
81
|
import { assertProjectBranchDeletionIncarnation } from "./workspace-branch-incarnation-policy.mjs";
|
|
72
82
|
import { activeProjectBranchPublicationEvidence } from "./workspace-publication-evidence.mjs";
|
|
@@ -170,6 +180,16 @@ const workerLifecycleTestHarness = {
|
|
|
170
180
|
workerCommandHasWorkspaceEffect
|
|
171
181
|
};
|
|
172
182
|
class ProjectWorkspaceConfigurationDeferredError extends Error {
|
|
183
|
+
constructor(changes) {
|
|
184
|
+
super(
|
|
185
|
+
`Project workspace configuration deferred while visible checkout activity is active: ${describeBusyProjectConfigurationChanges(changes)}`
|
|
186
|
+
);
|
|
187
|
+
this.changes = changes;
|
|
188
|
+
this.name = "ProjectWorkspaceConfigurationDeferredError";
|
|
189
|
+
this.projectIds = changes.map(({ projectId }) => projectId);
|
|
190
|
+
}
|
|
191
|
+
changes;
|
|
192
|
+
projectIds;
|
|
173
193
|
}
|
|
174
194
|
class WorkerServerUnavailableError extends Error {
|
|
175
195
|
name = "WorkerServerUnavailableError";
|
|
@@ -393,13 +413,12 @@ const workspaceSyncSingleFlight = {
|
|
|
393
413
|
afterCurrent() {
|
|
394
414
|
return workspaceSyncQueue;
|
|
395
415
|
},
|
|
396
|
-
mountHold(
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (!workspaceMountHoldFence.isHeld(mountIds)) return null;
|
|
416
|
+
mountHold(targets, signal) {
|
|
417
|
+
const mountIds = targets.flatMap((target) => target.type === "project" ? [projectMountId(target.projectId, target.branchName)] : []);
|
|
418
|
+
if (mountIds.length === 0 || !workspaceMountHoldFence.isHeld(mountIds)) return null;
|
|
400
419
|
const holder = workspaceMountHoldFence.describeHold(mountIds);
|
|
401
420
|
process.stdout.write(
|
|
402
|
-
`[r5d-worker] command for ${
|
|
421
|
+
`[r5d-worker] command for ${targets.map(describeWorkerSessionTarget).join(", ")} waits for ${holder ?? "a workspace filesystem job"}
|
|
403
422
|
`
|
|
404
423
|
);
|
|
405
424
|
return workspaceMountHoldFence.waitForRelease(mountIds, { signal });
|
|
@@ -1012,6 +1031,7 @@ function validateProjectId(projectId) {
|
|
|
1012
1031
|
function validateBranchName(branchName) {
|
|
1013
1032
|
validateManagedBranchName(branchName);
|
|
1014
1033
|
}
|
|
1034
|
+
const commandTargetValidators = { validateProjectId, validateBranchName };
|
|
1015
1035
|
const NON_RECURSIVE_GIT_CONFIG_ARGS = [
|
|
1016
1036
|
"-c",
|
|
1017
1037
|
"submodule.recurse=false",
|
|
@@ -2963,6 +2983,7 @@ async function executeCommand(input) {
|
|
|
2963
2983
|
activeProcesses.set(input.message.runId, {
|
|
2964
2984
|
process: subprocess,
|
|
2965
2985
|
target: input.resolvedTarget.target,
|
|
2986
|
+
additionalTargets: [...input.additionalTargets],
|
|
2966
2987
|
sessionId: input.message.sessionId ?? "",
|
|
2967
2988
|
mode: "foreground",
|
|
2968
2989
|
pid: subprocess.pid,
|
|
@@ -3096,6 +3117,7 @@ async function executeStreamingCommand(input) {
|
|
|
3096
3117
|
activeProcesses.set(input.message.runId, {
|
|
3097
3118
|
process: subprocess,
|
|
3098
3119
|
target: input.resolvedTarget.target,
|
|
3120
|
+
additionalTargets: [...input.additionalTargets],
|
|
3099
3121
|
sessionId: input.message.sessionId,
|
|
3100
3122
|
mode: input.message.mode,
|
|
3101
3123
|
pid: subprocess.pid,
|
|
@@ -3336,6 +3358,7 @@ function buildActiveProcessReports() {
|
|
|
3336
3358
|
return Array.from(activeProcesses.entries()).filter(([, active]) => active.sessionId.length > 0).map(([runId, active]) => ({
|
|
3337
3359
|
runId,
|
|
3338
3360
|
target: active.target,
|
|
3361
|
+
...active.additionalTargets.length > 0 ? { additionalTargets: active.additionalTargets.flatMap((target) => target.type === "project" ? [target] : []) } : {},
|
|
3339
3362
|
sessionId: active.sessionId,
|
|
3340
3363
|
platform: process.platform,
|
|
3341
3364
|
arch: process.arch,
|
|
@@ -3743,6 +3766,7 @@ async function openPty(input) {
|
|
|
3743
3766
|
activePtys.set(input.message.ptyId, {
|
|
3744
3767
|
...ptyProcess,
|
|
3745
3768
|
target: input.resolvedTarget.target,
|
|
3769
|
+
additionalTargets: [...input.additionalTargets],
|
|
3746
3770
|
foregroundBusy: true,
|
|
3747
3771
|
lastInputAt: 0,
|
|
3748
3772
|
...input.releaseWorkspaceMutation ? { releaseWorkspaceMutation: input.releaseWorkspaceMutation } : {}
|
|
@@ -3968,6 +3992,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
3968
3992
|
let workspaceCredentialUsername = null;
|
|
3969
3993
|
let workspaceGitIdentity = projectRuntime.gitIdentity;
|
|
3970
3994
|
let workspaceConfigured = false;
|
|
3995
|
+
let hydrationRecoveryRequired;
|
|
3971
3996
|
let activeWorkspaceIncidentId = null;
|
|
3972
3997
|
let deferredWorkspaceConfiguration = null;
|
|
3973
3998
|
let deferredWorkspaceConfigurationRefreshTimer;
|
|
@@ -4267,12 +4292,12 @@ async function startWorker(options, projectRuntime = {
|
|
|
4267
4292
|
});
|
|
4268
4293
|
};
|
|
4269
4294
|
const activeWorkspaceMutationTargets = () => [
|
|
4270
|
-
...Array.from(activeProcesses.values()).filter(workerCommandHasWorkspaceEffect).
|
|
4295
|
+
...Array.from(activeProcesses.values()).filter(workerCommandHasWorkspaceEffect).flatMap(({ target, additionalTargets }) => [target, ...additionalTargets]),
|
|
4271
4296
|
...credentialBearingProcessGroupTargets.values(),
|
|
4272
|
-
...workspaceSyncPriorityProcessTargets.values(),
|
|
4297
|
+
...Array.from(workspaceSyncPriorityProcessTargets.values()).flat(),
|
|
4273
4298
|
...workspaceSyncPriorityOperationTargets.values(),
|
|
4274
|
-
...Array.from(activePtys.values()).filter((pty) => workerPtyIsWorkspaceBusy(pty)).
|
|
4275
|
-
...workspaceSyncPriorityPtyTargets.values()
|
|
4299
|
+
...Array.from(activePtys.values()).filter((pty) => workerPtyIsWorkspaceBusy(pty)).flatMap(({ target, additionalTargets }) => [target, ...additionalTargets]),
|
|
4300
|
+
...Array.from(workspaceSyncPriorityPtyTargets.values()).flat()
|
|
4276
4301
|
];
|
|
4277
4302
|
let visibleWorkspaceMutationEpoch = 0;
|
|
4278
4303
|
const projectWorkspaceMutationEpochs = /* @__PURE__ */ new Map();
|
|
@@ -4284,6 +4309,9 @@ async function startWorker(options, projectRuntime = {
|
|
|
4284
4309
|
const key = projectBranchKey(target.projectId, target.branchName);
|
|
4285
4310
|
projectWorkspaceMutationEpochs.set(key, (projectWorkspaceMutationEpochs.get(key) ?? 0) + 1);
|
|
4286
4311
|
};
|
|
4312
|
+
const recordAdditionalTargetMutations = (additional) => {
|
|
4313
|
+
for (const target of additional) recordVisibleWorkspaceMutation(target);
|
|
4314
|
+
};
|
|
4287
4315
|
const projectWorkspaceMutationToken = (projectId, branchName) => `${visibleWorkspaceMutationEpoch}:${projectWorkspaceMutationEpochs.get(projectBranchKey(projectId, branchName)) ?? 0}`;
|
|
4288
4316
|
const canonicalWorkspaceMutationIsActive = (targets) => targets.some((target) => target.type === "workspace" && target.rootProfile === "canonical_sync");
|
|
4289
4317
|
const projectBranchMountActivityBusy = (projectId, branchName, branchPath) => {
|
|
@@ -4369,6 +4397,24 @@ async function startWorker(options, projectRuntime = {
|
|
|
4369
4397
|
preserveLocalOnInitialOuterAbsence: creatorLocalIncarnation,
|
|
4370
4398
|
...initialPublicationSourceBranch ? { initialPublicationSourceRelativePath: workspaceProjectRelativePath(project.projectId, initialPublicationSourceBranch) } : {},
|
|
4371
4399
|
preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
|
|
4400
|
+
describeHolders: () => [
|
|
4401
|
+
...new Set(
|
|
4402
|
+
[
|
|
4403
|
+
...[...activeProcesses.values()].filter(
|
|
4404
|
+
(entry) => projectBranchHasActiveWorkspaceTarget(project.projectId, branch.branchName, [
|
|
4405
|
+
entry.target,
|
|
4406
|
+
...entry.additionalTargets
|
|
4407
|
+
])
|
|
4408
|
+
).map((entry) => entry.sessionId),
|
|
4409
|
+
...[...activePtys.entries()].filter(
|
|
4410
|
+
([, entry]) => projectBranchHasActiveWorkspaceTarget(project.projectId, branch.branchName, [
|
|
4411
|
+
entry.target,
|
|
4412
|
+
...entry.additionalTargets
|
|
4413
|
+
])
|
|
4414
|
+
).map(([ptyId]) => `pty:${ptyId}`)
|
|
4415
|
+
].filter((id) => typeof id === "string")
|
|
4416
|
+
)
|
|
4417
|
+
].join(", ") || "no live declared holder",
|
|
4372
4418
|
busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
|
|
4373
4419
|
mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
|
|
4374
4420
|
busyForRecovery: () => projectBranchMountExclusiveSyncBusy(project.projectId, branch.branchName, branchPath)
|
|
@@ -4894,6 +4940,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
4894
4940
|
affectedPaths: result.affectedPaths,
|
|
4895
4941
|
activeMountIds: result.activeMountIds,
|
|
4896
4942
|
skippedMountIds: result.skippedMountIds,
|
|
4943
|
+
...result.mountSkipReasons ? { mountSkipReasons: result.mountSkipReasons } : {},
|
|
4897
4944
|
...missingCheckouts ? { missingCheckouts } : {},
|
|
4898
4945
|
activeProjectBranchPublications,
|
|
4899
4946
|
discardedPaths: [],
|
|
@@ -4906,8 +4953,11 @@ async function startWorker(options, projectRuntime = {
|
|
|
4906
4953
|
};
|
|
4907
4954
|
};
|
|
4908
4955
|
const failedWorkspaceSyncResult = async (attemptId, trigger, error) => {
|
|
4956
|
+
const recovery = findHydrationRecovery(error);
|
|
4957
|
+
if (recovery) hydrationRecoveryRequired = recovery;
|
|
4909
4958
|
const localHead = await workspaceLocalHead();
|
|
4910
4959
|
return {
|
|
4960
|
+
...recovery ? { failureReason: "hydration_recovery_required", hydrationRecovery: recovery } : {},
|
|
4911
4961
|
type: "workspace_sync",
|
|
4912
4962
|
attemptId,
|
|
4913
4963
|
workerLabel: label,
|
|
@@ -5130,6 +5180,12 @@ async function startWorker(options, projectRuntime = {
|
|
|
5130
5180
|
} catch (error) {
|
|
5131
5181
|
workspaceProjectionLedger.reset();
|
|
5132
5182
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5183
|
+
if (findHydrationRecovery(error)) {
|
|
5184
|
+
workspaceConfigured = false;
|
|
5185
|
+
projectRuntime.configuration = void 0;
|
|
5186
|
+
advanceWorkerAdmissionGeneration();
|
|
5187
|
+
return await failedWorkspaceSyncResult(attemptId, input.trigger, error);
|
|
5188
|
+
}
|
|
5133
5189
|
const hydrationRecoverable = workspaceSyncFailureHydrationIsSafe({
|
|
5134
5190
|
error,
|
|
5135
5191
|
resetToCanonical: input.resetToCanonical === true,
|
|
@@ -5373,6 +5429,10 @@ async function startWorker(options, projectRuntime = {
|
|
|
5373
5429
|
projectRuntime.markWorkspaceDirty = markWorkspaceDirty;
|
|
5374
5430
|
const targetMayMutateVisibleWorkspace = (target) => target.type === "project" || target.rootProfile === "visible_projects";
|
|
5375
5431
|
const resolveMessageTarget = (target) => {
|
|
5432
|
+
if (hydrationRecoveryRequired)
|
|
5433
|
+
throw new Error(
|
|
5434
|
+
`Workspace hydration recovery required for transaction ${hydrationRecoveryRequired.transactionId}; inspect r5dctl workspace status`
|
|
5435
|
+
);
|
|
5376
5436
|
if (!workspaceConfigured) throw new Error("Worker workspace configuration has not completed successfully");
|
|
5377
5437
|
if (!deferredWorkspaceTargetIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, target)) {
|
|
5378
5438
|
throw new Error(
|
|
@@ -5392,6 +5452,11 @@ async function startWorker(options, projectRuntime = {
|
|
|
5392
5452
|
projectConfigById
|
|
5393
5453
|
});
|
|
5394
5454
|
};
|
|
5455
|
+
const resolveCommandMessageTarget = (target, additionalTargets) => {
|
|
5456
|
+
const primary = resolveMessageTarget(target);
|
|
5457
|
+
for (const additional of additionalTargets) resolveMessageTarget(additional);
|
|
5458
|
+
return primary;
|
|
5459
|
+
};
|
|
5395
5460
|
const configureWorkerWorkspace = async (message, receiptGeneration) => {
|
|
5396
5461
|
const configurationOperationId = crypto.randomUUID();
|
|
5397
5462
|
sendLifecycleProgress({
|
|
@@ -5566,7 +5631,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
5566
5631
|
throw new Error("Workspace configuration was superseded by a different active workspace incident");
|
|
5567
5632
|
}
|
|
5568
5633
|
for (const project of message.projects) assertRepositoryTransitionState(project);
|
|
5569
|
-
const busyConfigurationChanges =
|
|
5634
|
+
const busyConfigurationChanges = busyProjectConfigurationChanges({
|
|
5570
5635
|
currentProjects: [...projectConfigById.values()],
|
|
5571
5636
|
incomingProjects: message.projects,
|
|
5572
5637
|
activeTargets: activeWorkspaceMutationTargets(),
|
|
@@ -5577,12 +5642,44 @@ async function startWorker(options, projectRuntime = {
|
|
|
5577
5642
|
return readyProjectIds.has(project.projectId) && reconciledProjectConfigFingerprints.get(project.projectId) === fingerprint && project.branches.every(({ branchName }) => hasProjectWorktree(configuredProjectBranchPath(projectsRoot, project, branchName)));
|
|
5578
5643
|
}
|
|
5579
5644
|
});
|
|
5580
|
-
if (busyConfigurationChanges.length > 0)
|
|
5581
|
-
|
|
5582
|
-
|
|
5645
|
+
if (busyConfigurationChanges.length > 0) throw new ProjectWorkspaceConfigurationDeferredError(busyConfigurationChanges);
|
|
5646
|
+
workspaceConfigured = false;
|
|
5647
|
+
if (message.recoverHydrationTransactionId) {
|
|
5648
|
+
if (incidentDeferral.incidentId || message.resetToCanonical)
|
|
5649
|
+
throw new Error("Hydration preservation cannot run during a canonical reset or another workspace incident");
|
|
5650
|
+
if (activeProcesses.size || activePtys.size || credentialBearingProcessGroups.size || pendingProcessTerminals.size || activeWorkspaceMutationTargets().length) {
|
|
5651
|
+
throw new Error("Stop active worker processes and terminals before preserving interrupted hydration");
|
|
5652
|
+
}
|
|
5653
|
+
const preservedHydration = await workspaceFilesystemExecutor().run("hydration_preserve_interrupted", {
|
|
5654
|
+
workspacePath: workspaceShadowRoot,
|
|
5655
|
+
expectedTransactionId: message.recoverHydrationTransactionId
|
|
5656
|
+
});
|
|
5657
|
+
process.stdout.write(
|
|
5658
|
+
`[r5d-worker] Preserved hydration ${preservedHydration.transactionId}; archived evidence: ${preservedHydration.archivePath}
|
|
5659
|
+
`
|
|
5660
|
+
);
|
|
5661
|
+
for (const entry of preservedHydration.retainedEntries ?? []) {
|
|
5662
|
+
process.stdout.write(
|
|
5663
|
+
`[r5d-worker] Retained writer evidence for ${entry.mountId}: ${entry.sourcePath}; archived copy: ${entry.archivePath}
|
|
5664
|
+
`
|
|
5665
|
+
);
|
|
5666
|
+
}
|
|
5667
|
+
hydrationRecoveryRequired = void 0;
|
|
5668
|
+
}
|
|
5669
|
+
const interruptedHydration = await workspaceFilesystemExecutor().run("hydration_recovery_inspect", {
|
|
5670
|
+
workspacePath: workspaceShadowRoot
|
|
5671
|
+
});
|
|
5672
|
+
if (interruptedHydration) {
|
|
5673
|
+
throw Object.assign(
|
|
5674
|
+
new Error(
|
|
5675
|
+
`Interrupted hydration ${interruptedHydration.transactionId} requires explicit recovery; checkout files and snapshots are preserved`
|
|
5676
|
+
),
|
|
5677
|
+
{
|
|
5678
|
+
code: "hydration_recovery_required",
|
|
5679
|
+
hydrationRecovery: interruptedHydration
|
|
5680
|
+
}
|
|
5583
5681
|
);
|
|
5584
5682
|
}
|
|
5585
|
-
workspaceConfigured = false;
|
|
5586
5683
|
workspaceProjectionLedger.reset();
|
|
5587
5684
|
const preserveOnlyBranches = message.projects.flatMap(
|
|
5588
5685
|
(project) => project.preserveOnlyBranches.map(({ branchId, branchName }) => ({ branchId, projectId: project.projectId, branchName }))
|
|
@@ -5848,6 +5945,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
5848
5945
|
deferredWorkspaceConfigurationRefreshTimer = void 0;
|
|
5849
5946
|
}
|
|
5850
5947
|
deferredWorkspaceConfiguration = null;
|
|
5948
|
+
hydrationRecoveryRequired = void 0;
|
|
5851
5949
|
workspaceConfigured = true;
|
|
5852
5950
|
const pending = [...pendingCheckouts.values()].sort(
|
|
5853
5951
|
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
@@ -5890,7 +5988,8 @@ async function startWorker(options, projectRuntime = {
|
|
|
5890
5988
|
requireCredentialGenerationRestart(true, void 0, 1);
|
|
5891
5989
|
}
|
|
5892
5990
|
workspaceConfigured = false;
|
|
5893
|
-
const
|
|
5991
|
+
const busyDeferral = error instanceof ProjectWorkspaceConfigurationDeferredError ? error : void 0;
|
|
5992
|
+
const deferred = busyDeferral !== void 0 || error instanceof ProjectWorkspacePendingBranchPathChangeError;
|
|
5894
5993
|
if (deferred) pendingCheckouts.clear();
|
|
5895
5994
|
for (const pending of deferredProjectConfigurationPendingBranches(message.projects)) {
|
|
5896
5995
|
pendingCheckouts.set(projectBranchKey(pending.projectId, pending.branchName), pending);
|
|
@@ -5900,7 +5999,11 @@ async function startWorker(options, projectRuntime = {
|
|
|
5900
5999
|
pending: [...pendingCheckouts.values()].sort(
|
|
5901
6000
|
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
5902
6001
|
),
|
|
5903
|
-
aheadOfOriginBranches: []
|
|
6002
|
+
aheadOfOriginBranches: [],
|
|
6003
|
+
// A busy deferral is typed so the server can keep this socket, finish
|
|
6004
|
+
// receipt recovery and renew the execution lease instead of closing
|
|
6005
|
+
// the connection and retrying registration until the lease expires.
|
|
6006
|
+
...busyDeferral ? { deferredForBusyProjectIds: busyDeferral.projectIds } : {}
|
|
5904
6007
|
};
|
|
5905
6008
|
} finally {
|
|
5906
6009
|
workspaceSyncRequestsInFlight -= 1;
|
|
@@ -6089,7 +6192,9 @@ async function startWorker(options, projectRuntime = {
|
|
|
6089
6192
|
commandControlLaneV1: workerCommandLauncher?.supportsControlLane === true,
|
|
6090
6193
|
projectMirrorLeaseV1: true,
|
|
6091
6194
|
projectMirrorRefsTokensV1: true,
|
|
6092
|
-
projectBranchWorkingTreeModeV1: true
|
|
6195
|
+
projectBranchWorkingTreeModeV1: true,
|
|
6196
|
+
commandAdditionalTargetsV1: true,
|
|
6197
|
+
workspaceHydrationRecoveryV1: true
|
|
6093
6198
|
},
|
|
6094
6199
|
root: rootDir
|
|
6095
6200
|
}
|
|
@@ -6325,7 +6430,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6325
6430
|
}
|
|
6326
6431
|
const digest = workerOperationDigest(message);
|
|
6327
6432
|
const cached = projectRuntime.configuration;
|
|
6328
|
-
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;
|
|
6433
|
+
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;
|
|
6329
6434
|
if (!canReuseConfiguration) {
|
|
6330
6435
|
advanceWorkerAdmissionGeneration();
|
|
6331
6436
|
projectRuntime.configuration = void 0;
|
|
@@ -6337,7 +6442,8 @@ async function startWorker(options, projectRuntime = {
|
|
|
6337
6442
|
result: configured.result,
|
|
6338
6443
|
pendingCheckouts: configured.pending,
|
|
6339
6444
|
aheadOfOriginBranches: configured.aheadOfOriginBranches,
|
|
6340
|
-
...configured.deferredWorkspaceSyncForIncidentId ? { deferredWorkspaceSyncForIncidentId: configured.deferredWorkspaceSyncForIncidentId } : {}
|
|
6445
|
+
...configured.deferredWorkspaceSyncForIncidentId ? { deferredWorkspaceSyncForIncidentId: configured.deferredWorkspaceSyncForIncidentId } : {},
|
|
6446
|
+
...configured.deferredForBusyProjectIds ? { deferredForBusyProjectIds: configured.deferredForBusyProjectIds } : {}
|
|
6341
6447
|
});
|
|
6342
6448
|
if (receiptGeneration !== workspaceConfigurationReceiptGeneration || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
6343
6449
|
sendConfigured();
|
|
@@ -6368,7 +6474,17 @@ async function startWorker(options, projectRuntime = {
|
|
|
6368
6474
|
if (configured.result.outcome !== "failed") unsafeWorkspaceSyncRecoveryAttempts = 0;
|
|
6369
6475
|
sendConfigured();
|
|
6370
6476
|
sendLifecycleProgress(
|
|
6371
|
-
configured.result.
|
|
6477
|
+
configured.result.hydrationRecovery ? {
|
|
6478
|
+
phase: "failed",
|
|
6479
|
+
operationId: configured.result.attemptId,
|
|
6480
|
+
detail: `Hydration recovery required: ${configured.result.hydrationRecovery.transactionId}; files and snapshots preserved`
|
|
6481
|
+
} : configured.deferredForBusyProjectIds ? {
|
|
6482
|
+
// Not a failure: the previous generation stays on disk and the
|
|
6483
|
+
// server retries once the busy checkouts are idle.
|
|
6484
|
+
phase: "configuring",
|
|
6485
|
+
operationId: configured.result.attemptId,
|
|
6486
|
+
detail: `Workspace configuration deferred while checkout activity is active on ${configured.deferredForBusyProjectIds.length} project(s)`
|
|
6487
|
+
} : configured.result.outcome === "failed" ? {
|
|
6372
6488
|
phase: "failed",
|
|
6373
6489
|
operationId: configured.result.attemptId,
|
|
6374
6490
|
detail: configured.result.error ?? "Workspace configuration failed"
|
|
@@ -6381,10 +6497,12 @@ async function startWorker(options, projectRuntime = {
|
|
|
6381
6497
|
}
|
|
6382
6498
|
);
|
|
6383
6499
|
process.stdout.write(
|
|
6384
|
-
`[r5d-worker] workspace
|
|
6500
|
+
configured.result.hydrationRecovery ? `[r5d-worker] workspace hydration recovery required for ${configured.result.hydrationRecovery.transactionId}; execution remains fenced, files and snapshots preserved
|
|
6501
|
+
` : 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
|
|
6502
|
+
` : `[r5d-worker] workspace configured: ${message.projects.length} project(s), ${configured.pending.length} pending checkout(s)
|
|
6385
6503
|
`
|
|
6386
6504
|
);
|
|
6387
|
-
if (refreshesDeferredConfiguration && configured.result.outcome === "failed" && deferredWorkspaceConfiguration !== null && currentWorkerSocket === ws) {
|
|
6505
|
+
if (refreshesDeferredConfiguration && configured.result.outcome === "failed" && configured.result.failureReason !== "hydration_recovery_required" && !configured.deferredForBusyProjectIds && deferredWorkspaceConfiguration !== null && currentWorkerSocket === ws) {
|
|
6388
6506
|
workspaceConfigured = false;
|
|
6389
6507
|
advanceWorkerAdmissionGeneration();
|
|
6390
6508
|
ws.close(1012, "Deferred workspace configuration refresh failed");
|
|
@@ -6776,6 +6894,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6776
6894
|
if (active.workspaceEffect !== "none" && targetMayMutateVisibleWorkspace(active.target)) {
|
|
6777
6895
|
recordVisibleWorkspaceMutation(active.target);
|
|
6778
6896
|
}
|
|
6897
|
+
if (active.workspaceEffect !== "none") recordAdditionalTargetMutations(active.additionalTargets);
|
|
6779
6898
|
try {
|
|
6780
6899
|
let bytesWritten = 0;
|
|
6781
6900
|
if (message.data !== void 0 && message.data.length > 0) {
|
|
@@ -6825,16 +6944,19 @@ async function startWorker(options, projectRuntime = {
|
|
|
6825
6944
|
let resourcesTransferred = false;
|
|
6826
6945
|
let releaseWorkspaceMutation;
|
|
6827
6946
|
let mutationLeaseTransferred = false;
|
|
6947
|
+
let additionalTargets = [];
|
|
6828
6948
|
try {
|
|
6949
|
+
additionalTargets = normalizeCommandAdditionalTargets(message.target, message.additionalTargets, commandTargetValidators);
|
|
6829
6950
|
resources = await acquireWorkerCommandLaunch(ws, message, assertMessageAdmission);
|
|
6830
6951
|
const reservationAbort = registerPendingReservation(`pty:${message.ptyId}`, void 0);
|
|
6831
6952
|
try {
|
|
6832
6953
|
await reserveWorkspaceCommandAfterCurrentSync(
|
|
6833
|
-
message.target,
|
|
6954
|
+
commandTargets(message.target, additionalTargets),
|
|
6834
6955
|
workspaceSyncSingleFlight,
|
|
6835
6956
|
() => {
|
|
6836
|
-
workspaceSyncPriorityPtyTargets.set(message.ptyId, message.target);
|
|
6957
|
+
workspaceSyncPriorityPtyTargets.set(message.ptyId, commandTargets(message.target, additionalTargets));
|
|
6837
6958
|
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
6959
|
+
recordAdditionalTargetMutations(additionalTargets);
|
|
6838
6960
|
},
|
|
6839
6961
|
{ signal: reservationAbort.signal }
|
|
6840
6962
|
);
|
|
@@ -6847,19 +6969,26 @@ async function startWorker(options, projectRuntime = {
|
|
|
6847
6969
|
`Shell opening was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
6848
6970
|
);
|
|
6849
6971
|
}
|
|
6850
|
-
const resolvedTarget =
|
|
6851
|
-
process.stdout.write(
|
|
6852
|
-
`)
|
|
6972
|
+
const resolvedTarget = resolveCommandMessageTarget(message.target, additionalTargets);
|
|
6973
|
+
process.stdout.write(
|
|
6974
|
+
`[r5d-worker] pty ${message.ptyId}: ${describeWorkerSessionTarget(message.target)}${describeCommandAdditionalTargets(additionalTargets)}
|
|
6975
|
+
`
|
|
6976
|
+
);
|
|
6853
6977
|
await openPty({
|
|
6854
6978
|
resources,
|
|
6855
6979
|
ws,
|
|
6856
6980
|
message,
|
|
6857
6981
|
resolvedTarget,
|
|
6982
|
+
additionalTargets,
|
|
6858
6983
|
rootDir,
|
|
6859
|
-
assertAdmission:
|
|
6984
|
+
assertAdmission: () => {
|
|
6985
|
+
assertMessageAdmission();
|
|
6986
|
+
resolveCommandMessageTarget(message.target, additionalTargets);
|
|
6987
|
+
},
|
|
6860
6988
|
...releaseWorkspaceMutation ? { releaseWorkspaceMutation } : {},
|
|
6861
6989
|
...targetMayMutateVisibleWorkspace(message.target) ? {
|
|
6862
6990
|
onTerminal: () => {
|
|
6991
|
+
recordAdditionalTargetMutations(additionalTargets);
|
|
6863
6992
|
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} completed` }, true, message.target);
|
|
6864
6993
|
}
|
|
6865
6994
|
} : {}
|
|
@@ -6892,6 +7021,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6892
7021
|
return;
|
|
6893
7022
|
}
|
|
6894
7023
|
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
7024
|
+
recordAdditionalTargetMutations(activePty.additionalTargets);
|
|
6895
7025
|
markWorkspaceDirty({ type: "shell_inline", detail: `pty ${message.ptyId}` }, false, activePty.target);
|
|
6896
7026
|
}
|
|
6897
7027
|
writePty(ws, message);
|
|
@@ -6908,6 +7038,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6908
7038
|
abortPendingReservation(`pty:${message.ptyId}`, "Shell closed before it started");
|
|
6909
7039
|
await closePty(message);
|
|
6910
7040
|
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
7041
|
+
recordAdditionalTargetMutations(activePty.additionalTargets);
|
|
6911
7042
|
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} closed` }, true, activePty.target);
|
|
6912
7043
|
}
|
|
6913
7044
|
sendWorkerMessage(ws, { type: "operation_completed", requestId: message.requestId });
|
|
@@ -6939,18 +7070,21 @@ async function startWorker(options, projectRuntime = {
|
|
|
6939
7070
|
let resources;
|
|
6940
7071
|
let targetReserved = false;
|
|
6941
7072
|
const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
|
|
7073
|
+
let additionalTargets = [];
|
|
6942
7074
|
try {
|
|
7075
|
+
additionalTargets = normalizeCommandAdditionalTargets(message.target, message.additionalTargets, commandTargetValidators);
|
|
6943
7076
|
resources = await acquireWorkerCommandLaunch(ws, message, assertMessageAdmission);
|
|
6944
7077
|
if (hasWorkspaceEffect) {
|
|
6945
7078
|
const reservationAbort = registerPendingReservation(`run:${message.runId}`, message.sessionId);
|
|
6946
7079
|
try {
|
|
6947
7080
|
await reserveWorkspaceCommandAfterCurrentSync(
|
|
6948
|
-
message.target,
|
|
7081
|
+
commandTargets(message.target, additionalTargets),
|
|
6949
7082
|
workspaceSyncSingleFlight,
|
|
6950
7083
|
() => {
|
|
6951
|
-
workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
|
|
7084
|
+
workspaceSyncPriorityProcessTargets.set(message.runId, commandTargets(message.target, additionalTargets));
|
|
6952
7085
|
targetReserved = true;
|
|
6953
7086
|
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
7087
|
+
recordAdditionalTargetMutations(additionalTargets);
|
|
6954
7088
|
},
|
|
6955
7089
|
{ signal: reservationAbort.signal }
|
|
6956
7090
|
);
|
|
@@ -6964,13 +7098,16 @@ async function startWorker(options, projectRuntime = {
|
|
|
6964
7098
|
`One-shot command was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
6965
7099
|
);
|
|
6966
7100
|
}
|
|
6967
|
-
const resolvedTarget =
|
|
6968
|
-
process.stdout.write(
|
|
6969
|
-
`)
|
|
7101
|
+
const resolvedTarget = resolveCommandMessageTarget(message.target, additionalTargets);
|
|
7102
|
+
process.stdout.write(
|
|
7103
|
+
`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}${describeCommandAdditionalTargets(additionalTargets)}
|
|
7104
|
+
`
|
|
7105
|
+
);
|
|
6970
7106
|
return await executeCommand({
|
|
6971
7107
|
resources,
|
|
6972
7108
|
message,
|
|
6973
7109
|
resolvedTarget,
|
|
7110
|
+
additionalTargets,
|
|
6974
7111
|
baseUrl,
|
|
6975
7112
|
token,
|
|
6976
7113
|
rootDir,
|
|
@@ -6988,6 +7125,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6988
7125
|
);
|
|
6989
7126
|
}
|
|
6990
7127
|
assertMessageAdmission();
|
|
7128
|
+
resolveCommandMessageTarget(message.target, additionalTargets);
|
|
6991
7129
|
}
|
|
6992
7130
|
});
|
|
6993
7131
|
};
|
|
@@ -7013,6 +7151,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
7013
7151
|
}
|
|
7014
7152
|
sendSerializedWorkerMessage(ws, JSON.stringify(result));
|
|
7015
7153
|
if (hasWorkspaceEffect && targetMayMutateVisibleWorkspace(message.target)) {
|
|
7154
|
+
recordAdditionalTargetMutations(additionalTargets);
|
|
7016
7155
|
markWorkspaceDirty(
|
|
7017
7156
|
{ type: "shell_inline", sessionId: message.sessionId, processRunId: message.runId, detail: "foreground command completed" },
|
|
7018
7157
|
true,
|
|
@@ -7038,11 +7177,23 @@ async function startWorker(options, projectRuntime = {
|
|
|
7038
7177
|
});
|
|
7039
7178
|
const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
|
|
7040
7179
|
let resources;
|
|
7180
|
+
let additionalTargets;
|
|
7181
|
+
try {
|
|
7182
|
+
additionalTargets = normalizeCommandAdditionalTargets(message.target, message.additionalTargets, commandTargetValidators);
|
|
7183
|
+
} catch (error) {
|
|
7184
|
+
sendWorkerMessageFromCurrentSource(ws, {
|
|
7185
|
+
type: "exec_start_error",
|
|
7186
|
+
requestId: message.requestId,
|
|
7187
|
+
runId: message.runId,
|
|
7188
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7189
|
+
});
|
|
7190
|
+
return;
|
|
7191
|
+
}
|
|
7041
7192
|
const runCommand = async () => {
|
|
7042
7193
|
try {
|
|
7043
|
-
const resolvedTarget =
|
|
7194
|
+
const resolvedTarget = resolveCommandMessageTarget(message.target, additionalTargets);
|
|
7044
7195
|
process.stdout.write(
|
|
7045
|
-
`[r5d-worker] exec_start ${message.runId}${message.commandClass === "control" ? " (control)" : ""}: ${message.argv.join(" ")}
|
|
7196
|
+
`[r5d-worker] exec_start ${message.runId}${message.commandClass === "control" ? " (control)" : ""}: ${message.argv.join(" ")}${describeCommandAdditionalTargets(additionalTargets)}
|
|
7046
7197
|
`
|
|
7047
7198
|
);
|
|
7048
7199
|
await executeStreamingCommand({
|
|
@@ -7050,6 +7201,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
7050
7201
|
ws,
|
|
7051
7202
|
message,
|
|
7052
7203
|
resolvedTarget,
|
|
7204
|
+
additionalTargets,
|
|
7053
7205
|
baseUrl,
|
|
7054
7206
|
token,
|
|
7055
7207
|
rootDir,
|
|
@@ -7060,10 +7212,14 @@ async function startWorker(options, projectRuntime = {
|
|
|
7060
7212
|
active
|
|
7061
7213
|
});
|
|
7062
7214
|
},
|
|
7063
|
-
assertAdmission:
|
|
7215
|
+
assertAdmission: () => {
|
|
7216
|
+
assertMessageAdmission();
|
|
7217
|
+
resolveCommandMessageTarget(message.target, additionalTargets);
|
|
7218
|
+
}
|
|
7064
7219
|
});
|
|
7065
7220
|
} finally {
|
|
7066
7221
|
if (hasWorkspaceEffect && targetMayMutateVisibleWorkspace(message.target)) {
|
|
7222
|
+
recordAdditionalTargetMutations(additionalTargets);
|
|
7067
7223
|
markWorkspaceDirty(
|
|
7068
7224
|
{
|
|
7069
7225
|
type: "process_terminal",
|
|
@@ -7085,12 +7241,13 @@ async function startWorker(options, projectRuntime = {
|
|
|
7085
7241
|
const reservationAbort = registerPendingReservation(`run:${message.runId}`, message.sessionId);
|
|
7086
7242
|
try {
|
|
7087
7243
|
await reserveWorkspaceCommandAfterCurrentSync(
|
|
7088
|
-
message.target,
|
|
7244
|
+
commandTargets(message.target, additionalTargets),
|
|
7089
7245
|
workspaceSyncSingleFlight,
|
|
7090
7246
|
() => {
|
|
7091
|
-
workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
|
|
7247
|
+
workspaceSyncPriorityProcessTargets.set(message.runId, commandTargets(message.target, additionalTargets));
|
|
7092
7248
|
targetReserved = true;
|
|
7093
7249
|
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
7250
|
+
recordAdditionalTargetMutations(additionalTargets);
|
|
7094
7251
|
},
|
|
7095
7252
|
{ signal: reservationAbort.signal }
|
|
7096
7253
|
);
|
package/dist/mjs/package.json
CHANGED