@ricsam/r5d-worker 0.0.82 → 0.0.83
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/main.cjs +422 -30
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-git-sync.cjs +69 -1
- package/dist/mjs/main.mjs +425 -31
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-git-sync.mjs +67 -1
- package/dist/types/main.d.ts +332 -0
- package/dist/types/workspace-git-sync.d.ts +28 -0
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -97,6 +97,7 @@ const DEFAULT_READ_MAX_BYTES = 5e4;
|
|
|
97
97
|
const MAX_LINE_LENGTH = 2e3;
|
|
98
98
|
const WORKSPACE_GIT_QUIET_MS = 5e3;
|
|
99
99
|
const WORKSPACE_GIT_PERIODIC_MS = 6e4;
|
|
100
|
+
const WORKSPACE_INCIDENT_CONFIG_REFRESH_TIMEOUT_MS = 6e4;
|
|
100
101
|
const PTY_INPUT_BUSY_GRACE_MS = 3e3;
|
|
101
102
|
const PTY_FOREGROUND_POLL_MS = 1e3;
|
|
102
103
|
const PTY_FOREGROUND_IDLE_ENABLED = process.env.R5D_PTY_FOREGROUND_IDLE !== "0";
|
|
@@ -255,6 +256,7 @@ const workerPtyTestHarness = {
|
|
|
255
256
|
removeEnvFiles: removePtyEnvFiles,
|
|
256
257
|
resolveEnvFileReferences: resolvePtyEnvFileReferences,
|
|
257
258
|
commandHasWorkspaceEffect: workerCommandHasWorkspaceEffect,
|
|
259
|
+
canonicalSyncTerminalHead,
|
|
258
260
|
ptyIsWorkspaceBusy: workerPtyIsWorkspaceBusy,
|
|
259
261
|
parseLinuxForegroundBusy: parseLinuxPtyForegroundBusy
|
|
260
262
|
};
|
|
@@ -1516,6 +1518,86 @@ function credentialReapContractStatus(probe) {
|
|
|
1516
1518
|
function verifiedCredentialReapContract(probe) {
|
|
1517
1519
|
return credentialReapContractStatus(probe) === "verified_systemd";
|
|
1518
1520
|
}
|
|
1521
|
+
function workspaceConfigurationIncidentDeferral(input) {
|
|
1522
|
+
const incidentId = input.requestedIncidentId ?? input.activeIncidentId;
|
|
1523
|
+
if (incidentId === null) return { incidentId: null };
|
|
1524
|
+
if (!/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/.test(incidentId)) {
|
|
1525
|
+
return { incidentId: null, error: new Error("Workspace configuration incident deferral requires a canonical incident UUID") };
|
|
1526
|
+
}
|
|
1527
|
+
if (input.requestedIncidentId !== void 0 && input.requestedIncidentId !== input.activeIncidentId) {
|
|
1528
|
+
return {
|
|
1529
|
+
incidentId: null,
|
|
1530
|
+
error: new Error(
|
|
1531
|
+
`Workspace configuration incident deferral ${input.requestedIncidentId} does not match active incident ${input.activeIncidentId ?? "none"}`
|
|
1532
|
+
)
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
return { incidentId };
|
|
1536
|
+
}
|
|
1537
|
+
function workspaceIncidentTerminalClearDisposition(input) {
|
|
1538
|
+
if (!input.deferredConfiguration) return "ordinary_resume";
|
|
1539
|
+
void input.clearedIncidentId;
|
|
1540
|
+
return input.deferredConfiguration.serverRefreshExpected ? "await_server_refresh" : "reconnect_for_refresh";
|
|
1541
|
+
}
|
|
1542
|
+
function deferredWorkspaceTargetIsAllowed(deferredConfiguration, activeIncidentId, target) {
|
|
1543
|
+
const canonicalTarget = target.type === "workspace" && target.rootProfile === "canonical_sync";
|
|
1544
|
+
if (deferredConfiguration) return activeIncidentId === deferredConfiguration.incidentId && canonicalTarget;
|
|
1545
|
+
return activeIncidentId === null || canonicalTarget;
|
|
1546
|
+
}
|
|
1547
|
+
function deferredWorkspaceSyncTriggerIsAllowed(deferredConfiguration, activeIncidentId, trigger) {
|
|
1548
|
+
const incidentId = deferredConfiguration?.incidentId ?? activeIncidentId;
|
|
1549
|
+
return incidentId === null || (deferredConfiguration === null || activeIncidentId === deferredConfiguration.incidentId) && (trigger.type === "remediation" || trigger.type === "remediation_confirm" || trigger.type === "remediation_reset");
|
|
1550
|
+
}
|
|
1551
|
+
function workspaceOperationsAreFenced(deferredConfiguration, activeIncidentId) {
|
|
1552
|
+
return deferredConfiguration !== null || activeIncidentId !== null;
|
|
1553
|
+
}
|
|
1554
|
+
function workspaceCommandTransportIsAllowed(deferredConfiguration, activeIncidentId, transport) {
|
|
1555
|
+
return !workspaceOperationsAreFenced(deferredConfiguration, activeIncidentId) || transport === "exec_start";
|
|
1556
|
+
}
|
|
1557
|
+
function deferredCredentialTransitionMustWait(input) {
|
|
1558
|
+
return input.incidentId !== null && input.transitionPhase !== "current" && input.canonicalRemediationActive;
|
|
1559
|
+
}
|
|
1560
|
+
function workspaceConfigurationIncidentSnapshotIsCurrent(capturedIncidentId, activeIncidentId) {
|
|
1561
|
+
return capturedIncidentId === null ? activeIncidentId === null : activeIncidentId === null || activeIncidentId === capturedIncidentId;
|
|
1562
|
+
}
|
|
1563
|
+
function deferredWorkspaceRefreshWatchdogIsCurrent(input) {
|
|
1564
|
+
return input.deferredConfiguration?.incidentId === input.capturedIncidentId && input.activeIncidentId === null;
|
|
1565
|
+
}
|
|
1566
|
+
function incidentDeferredProjectCheckouts(projects) {
|
|
1567
|
+
return projects.flatMap(
|
|
1568
|
+
(project) => project.executionDisabled ? [] : project.branches.map(({ branchName }) => ({ projectId: project.projectId, branchName }))
|
|
1569
|
+
).sort((left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName));
|
|
1570
|
+
}
|
|
1571
|
+
function deferredIncidentWorkspaceConfigurationResult(input) {
|
|
1572
|
+
return {
|
|
1573
|
+
type: "workspace_sync",
|
|
1574
|
+
attemptId: input.attemptId,
|
|
1575
|
+
workerLabel: input.workerLabel,
|
|
1576
|
+
trigger: { type: "connect", detail: "workspace synchronization deferred for active remediation" },
|
|
1577
|
+
outcome: "no_change",
|
|
1578
|
+
startingHead: input.head,
|
|
1579
|
+
...input.head ? { localHead: input.head } : {},
|
|
1580
|
+
rebaseCount: 0,
|
|
1581
|
+
diffSizeBytes: 0,
|
|
1582
|
+
gitStatus: "",
|
|
1583
|
+
affectedProjects: [],
|
|
1584
|
+
affectedPaths: [],
|
|
1585
|
+
activeMountIds: [],
|
|
1586
|
+
skippedMountIds: [...input.skippedMountIds].sort(),
|
|
1587
|
+
activeProjectBranchPublications: [],
|
|
1588
|
+
discardedPaths: [],
|
|
1589
|
+
localChangesDiscarded: false
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
function workspaceSyncFailureHydrationIsSafe(input) {
|
|
1593
|
+
if (input.error instanceof import_workspace_git_sync.WorkspaceRemediationAncestryError) return true;
|
|
1594
|
+
if (input.resetToCanonical) return false;
|
|
1595
|
+
try {
|
|
1596
|
+
return input.inspectCurrentHydration();
|
|
1597
|
+
} catch {
|
|
1598
|
+
return false;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1519
1601
|
function fenceUnsafeWorkspaceSyncFailure(input) {
|
|
1520
1602
|
if (input.hydrationCurrent) return false;
|
|
1521
1603
|
input.invalidateExecution();
|
|
@@ -1548,7 +1630,21 @@ const workerGitSecurityTestHarness = {
|
|
|
1548
1630
|
credentialReapContractStatus,
|
|
1549
1631
|
verifiedCredentialReapContract,
|
|
1550
1632
|
fenceUnsafeWorkspaceSyncFailure,
|
|
1633
|
+
workspaceConfigurationIncidentDeferral,
|
|
1634
|
+
workspaceIncidentTerminalClearDisposition,
|
|
1635
|
+
deferredWorkspaceTargetIsAllowed,
|
|
1636
|
+
deferredWorkspaceSyncTriggerIsAllowed,
|
|
1637
|
+
workspaceOperationsAreFenced,
|
|
1638
|
+
workspaceCommandTransportIsAllowed,
|
|
1639
|
+
deferredCredentialTransitionMustWait,
|
|
1640
|
+
workspaceConfigurationIncidentSnapshotIsCurrent,
|
|
1641
|
+
deferredWorkspaceRefreshWatchdogIsCurrent,
|
|
1642
|
+
incidentDeferredProjectCheckouts,
|
|
1643
|
+
deferredIncidentWorkspaceConfigurationResult,
|
|
1644
|
+
workspaceSyncFailureHydrationIsSafe,
|
|
1551
1645
|
assertWorkerChildAdmission,
|
|
1646
|
+
executeWriteFileOperation,
|
|
1647
|
+
executeEditFileOperation,
|
|
1552
1648
|
terminateCredentialBearingChildren,
|
|
1553
1649
|
terminateCredentialBearingChildrenWithRetention,
|
|
1554
1650
|
async commitCredentialGeneration(prepared, credential, children, beforeMutation, previousGenerationFenced = false) {
|
|
@@ -1699,6 +1795,15 @@ function hasProjectWorktree(checkoutPath) {
|
|
|
1699
1795
|
return false;
|
|
1700
1796
|
}
|
|
1701
1797
|
}
|
|
1798
|
+
function canonicalSyncTerminalHead(resolvedTarget, readHead = (rootPath) => runGit(["rev-parse", "HEAD"], { cwd: rootPath })) {
|
|
1799
|
+
if (resolvedTarget.target.type !== "workspace" || resolvedTarget.target.rootProfile !== "canonical_sync") return void 0;
|
|
1800
|
+
try {
|
|
1801
|
+
const head = readHead(resolvedTarget.rootPath);
|
|
1802
|
+
return /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(head) ? head : void 0;
|
|
1803
|
+
} catch {
|
|
1804
|
+
return void 0;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1702
1807
|
function describeWorkerSessionTarget(target) {
|
|
1703
1808
|
return target.type === "project" ? `${target.projectId}/${target.branchName}` : `${target.ownerUserId}/${target.rootProfile}`;
|
|
1704
1809
|
}
|
|
@@ -2003,8 +2108,10 @@ async function executeWriteFileOperation(input) {
|
|
|
2003
2108
|
planRoot: input.planRoot,
|
|
2004
2109
|
access: "write"
|
|
2005
2110
|
});
|
|
2111
|
+
input.assertAdmission();
|
|
2006
2112
|
const resolved = resolveWorkerFilePath(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
|
|
2007
2113
|
return withFileMutationQueue(mutationQueueKey(input.resolvedTarget.target, resolved), async () => {
|
|
2114
|
+
input.assertAdmission();
|
|
2008
2115
|
return writeWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.content, builtInPaths);
|
|
2009
2116
|
});
|
|
2010
2117
|
}
|
|
@@ -2020,8 +2127,10 @@ async function executeEditFileOperation(input) {
|
|
|
2020
2127
|
planRoot: input.planRoot,
|
|
2021
2128
|
access: "write"
|
|
2022
2129
|
});
|
|
2130
|
+
input.assertAdmission();
|
|
2023
2131
|
const resolved = resolveWorkerFilePath(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
|
|
2024
2132
|
return withFileMutationQueue(mutationQueueKey(input.resolvedTarget.target, resolved), async () => {
|
|
2133
|
+
input.assertAdmission();
|
|
2025
2134
|
return editWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.edits, builtInPaths);
|
|
2026
2135
|
});
|
|
2027
2136
|
}
|
|
@@ -2597,13 +2706,15 @@ async function executeStreamingCommand(input) {
|
|
|
2597
2706
|
});
|
|
2598
2707
|
})
|
|
2599
2708
|
]);
|
|
2709
|
+
const canonicalWorkspaceHead = canonicalSyncTerminalHead(input.resolvedTarget);
|
|
2600
2710
|
const terminal = {
|
|
2601
2711
|
type: "exec_exit",
|
|
2602
2712
|
runId: input.message.runId,
|
|
2603
2713
|
exitCode,
|
|
2604
2714
|
durationMs: Date.now() - startedAt,
|
|
2605
2715
|
...timedOut ? { timedOut: true } : {},
|
|
2606
|
-
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {}
|
|
2716
|
+
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {},
|
|
2717
|
+
...canonicalWorkspaceHead ? { canonicalWorkspaceHead } : {}
|
|
2607
2718
|
};
|
|
2608
2719
|
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
2609
2720
|
sendWorkerMessage(input.ws, terminal);
|
|
@@ -2611,12 +2722,14 @@ async function executeStreamingCommand(input) {
|
|
|
2611
2722
|
if (!started && error instanceof StaleWorkerAdmissionError) throw error;
|
|
2612
2723
|
const message = error instanceof Error ? error.message : String(error);
|
|
2613
2724
|
if (started) {
|
|
2725
|
+
const canonicalWorkspaceHead = canonicalSyncTerminalHead(input.resolvedTarget);
|
|
2614
2726
|
const terminal = {
|
|
2615
2727
|
type: "exec_error",
|
|
2616
2728
|
runId: input.message.runId,
|
|
2617
2729
|
error: message,
|
|
2618
2730
|
durationMs: Date.now() - startedAt,
|
|
2619
|
-
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {}
|
|
2731
|
+
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {},
|
|
2732
|
+
...canonicalWorkspaceHead ? { canonicalWorkspaceHead } : {}
|
|
2620
2733
|
};
|
|
2621
2734
|
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
2622
2735
|
sendWorkerMessage(input.ws, terminal);
|
|
@@ -3271,6 +3384,9 @@ async function startWorker(options) {
|
|
|
3271
3384
|
let workspaceGitIdentity = null;
|
|
3272
3385
|
let workspaceConfigured = false;
|
|
3273
3386
|
let activeWorkspaceIncidentId = null;
|
|
3387
|
+
let deferredWorkspaceConfiguration = null;
|
|
3388
|
+
let deferredWorkspaceConfigurationRefreshTimer;
|
|
3389
|
+
let workspaceConfigurationReceiptGeneration = 0;
|
|
3274
3390
|
let workspaceAutomaticTimer;
|
|
3275
3391
|
let workspacePeriodicTimer;
|
|
3276
3392
|
let pendingAutomaticTrigger;
|
|
@@ -3988,6 +4104,7 @@ async function startWorker(options) {
|
|
|
3988
4104
|
...result.conflictPaths ? { conflictPaths: result.conflictPaths } : {},
|
|
3989
4105
|
...result.conflictSnapshotRefs ? { conflictSnapshotRefs: result.conflictSnapshotRefs } : {},
|
|
3990
4106
|
...result.conflictKind ? { conflictKind: result.conflictKind } : {},
|
|
4107
|
+
...result.verifiedAncestorHeads ? { verifiedAncestorHeads: result.verifiedAncestorHeads } : {},
|
|
3991
4108
|
...result.error ? { error: result.error } : {}
|
|
3992
4109
|
};
|
|
3993
4110
|
};
|
|
@@ -4073,10 +4190,13 @@ async function startWorker(options) {
|
|
|
4073
4190
|
commitDetail: input.confirmationReason ?? input.trigger.detail,
|
|
4074
4191
|
allowLargeDiff: input.confirmedLargeDiff,
|
|
4075
4192
|
skipMountMirror: outerRemediation,
|
|
4193
|
+
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4194
|
+
assertStillAdmitted: input.assertStillAdmitted,
|
|
4076
4195
|
afterWorkspacePublished: outerRemediation ? void 0 : ({ activeMountIds, publishedHead }) => {
|
|
4077
4196
|
pushChangedProjectHeads(activeMountIds, publishedHead, publicationHeads, inboundMoveMountIds);
|
|
4078
4197
|
}
|
|
4079
4198
|
});
|
|
4199
|
+
input.assertStillAdmitted?.();
|
|
4080
4200
|
if (["no_change", "updated", "pushed"].includes(result.outcome) && !outerRemediation) {
|
|
4081
4201
|
applyObservedProjectHeadsAfterInboundWorkspace(observedProjectHeads, result.activeMountIds, false);
|
|
4082
4202
|
}
|
|
@@ -4099,6 +4219,28 @@ async function startWorker(options) {
|
|
|
4099
4219
|
const runWorkspaceSync = async (input) => {
|
|
4100
4220
|
const attemptId = input.attemptId ?? crypto.randomUUID();
|
|
4101
4221
|
const requestedAt = Date.now();
|
|
4222
|
+
const admittedGeneration = workerAdmissionGeneration;
|
|
4223
|
+
const admittedActiveIncidentId = activeWorkspaceIncidentId;
|
|
4224
|
+
const admittedDeferredIncidentId = deferredWorkspaceConfiguration?.incidentId ?? null;
|
|
4225
|
+
const assertStillAdmitted = () => {
|
|
4226
|
+
if (admittedGeneration !== workerAdmissionGeneration || admittedActiveIncidentId !== activeWorkspaceIncidentId || admittedDeferredIncidentId !== (deferredWorkspaceConfiguration?.incidentId ?? null) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || !deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4227
|
+
throw new Error("Workspace synchronization admission changed while asynchronous work was in flight");
|
|
4228
|
+
}
|
|
4229
|
+
};
|
|
4230
|
+
if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4231
|
+
const result2 = {
|
|
4232
|
+
...failedWorkspaceSyncResult(
|
|
4233
|
+
attemptId,
|
|
4234
|
+
input.trigger,
|
|
4235
|
+
new Error(
|
|
4236
|
+
`Workspace synchronization is deferred for incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}; only remediation synchronization is allowed`
|
|
4237
|
+
)
|
|
4238
|
+
),
|
|
4239
|
+
telemetry: { totalMs: 0, queueMs: 0, prepareMs: 0, synchronizeMs: 0 }
|
|
4240
|
+
};
|
|
4241
|
+
if (input.sendResult !== false) sendWorkspaceSyncResult(input.requestId, result2);
|
|
4242
|
+
return result2;
|
|
4243
|
+
}
|
|
4102
4244
|
if (!input.requestId && input.sendResult !== false) {
|
|
4103
4245
|
if (currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
4104
4246
|
throw new Error("Cannot start autonomous workspace synchronization without an open worker control socket");
|
|
@@ -4113,23 +4255,32 @@ async function startWorker(options) {
|
|
|
4113
4255
|
result = await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
4114
4256
|
queueEnteredAt = Date.now();
|
|
4115
4257
|
syncStartedAt = Date.now();
|
|
4258
|
+
if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4259
|
+
return failedWorkspaceSyncResult(
|
|
4260
|
+
attemptId,
|
|
4261
|
+
input.trigger,
|
|
4262
|
+
new Error(
|
|
4263
|
+
`Workspace synchronization was fenced while queued by incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
|
|
4264
|
+
)
|
|
4265
|
+
);
|
|
4266
|
+
}
|
|
4116
4267
|
try {
|
|
4268
|
+
assertStillAdmitted();
|
|
4117
4269
|
return await performWorkspaceSync({
|
|
4118
4270
|
attemptId,
|
|
4119
4271
|
trigger: input.trigger,
|
|
4120
4272
|
confirmedLargeDiff: input.confirmedLargeDiff,
|
|
4121
4273
|
confirmationReason: input.confirmationReason,
|
|
4122
|
-
resetToCanonical: input.resetToCanonical
|
|
4274
|
+
resetToCanonical: input.resetToCanonical,
|
|
4275
|
+
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4276
|
+
assertStillAdmitted
|
|
4123
4277
|
});
|
|
4124
4278
|
} catch (error) {
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
hydrationCurrent = false;
|
|
4131
|
-
}
|
|
4132
|
-
}
|
|
4279
|
+
const hydrationCurrent = workspaceSyncFailureHydrationIsSafe({
|
|
4280
|
+
error,
|
|
4281
|
+
resetToCanonical: input.resetToCanonical === true,
|
|
4282
|
+
inspectCurrentHydration: () => (0, import_workspace_git_sync.workspaceGitHydrationIsCurrent)(workspaceShadowRoot, buildWorkspaceMounts())
|
|
4283
|
+
});
|
|
4133
4284
|
if (!hydrationCurrent) {
|
|
4134
4285
|
process.stderr.write(
|
|
4135
4286
|
`[r5d-worker] workspace synchronization failed with an incompletely hydrated visible tree; exiting for exact recovery: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -4171,7 +4322,7 @@ async function startWorker(options) {
|
|
|
4171
4322
|
pendingCreatedBranchPublicationNotBefore
|
|
4172
4323
|
);
|
|
4173
4324
|
const initialDeferral = pendingBranchDeferral();
|
|
4174
|
-
if (!workspaceConfigured || activeWorkspaceIncidentId || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || automaticSyncInFlight || initialDeferral?.kind === "active_target") {
|
|
4325
|
+
if (!workspaceConfigured || activeWorkspaceIncidentId || deferredWorkspaceConfiguration || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || automaticSyncInFlight || initialDeferral?.kind === "active_target") {
|
|
4175
4326
|
return;
|
|
4176
4327
|
}
|
|
4177
4328
|
if (workspaceAutomaticTimer) clearTimeout(workspaceAutomaticTimer);
|
|
@@ -4179,7 +4330,8 @@ async function startWorker(options) {
|
|
|
4179
4330
|
() => {
|
|
4180
4331
|
workspaceAutomaticTimer = void 0;
|
|
4181
4332
|
const scheduledTrigger = pendingAutomaticTrigger;
|
|
4182
|
-
if (!scheduledTrigger || !workspaceConfigured || activeWorkspaceIncidentId || currentWorkerSocket !== ws)
|
|
4333
|
+
if (!scheduledTrigger || !workspaceConfigured || activeWorkspaceIncidentId || deferredWorkspaceConfiguration || currentWorkerSocket !== ws)
|
|
4334
|
+
return;
|
|
4183
4335
|
const currentDeferral = pendingBranchDeferral();
|
|
4184
4336
|
if (currentDeferral?.kind === "active_target") return;
|
|
4185
4337
|
if (currentDeferral?.kind === "creation_grace") {
|
|
@@ -4201,7 +4353,7 @@ async function startWorker(options) {
|
|
|
4201
4353
|
}).finally(() => {
|
|
4202
4354
|
automaticSyncInFlight = false;
|
|
4203
4355
|
heartbeatBusyGrace = (0, import_heartbeat.grantWorkerHeartbeatBusyGrace)(lastServerHeartbeatAt, heartbeatBusyGrace);
|
|
4204
|
-
if (pendingAutomaticTrigger && !activeWorkspaceIncidentId) {
|
|
4356
|
+
if (pendingAutomaticTrigger && !activeWorkspaceIncidentId && !deferredWorkspaceConfiguration) {
|
|
4205
4357
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, WORKSPACE_GIT_QUIET_MS);
|
|
4206
4358
|
}
|
|
4207
4359
|
});
|
|
@@ -4217,6 +4369,11 @@ async function startWorker(options) {
|
|
|
4217
4369
|
const targetMayMutateVisibleWorkspace = (target) => target.type === "project" || target.rootProfile === "visible_projects";
|
|
4218
4370
|
const resolveMessageTarget = (target) => {
|
|
4219
4371
|
if (!workspaceConfigured) throw new Error("Worker workspace configuration has not completed successfully");
|
|
4372
|
+
if (!deferredWorkspaceTargetIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, target)) {
|
|
4373
|
+
throw new Error(
|
|
4374
|
+
`Worker workspace configuration is fenced for incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}; only canonical remediation commands are allowed`
|
|
4375
|
+
);
|
|
4376
|
+
}
|
|
4220
4377
|
if (target.type === "project" && !readyProjectIds.has(target.projectId)) {
|
|
4221
4378
|
throw new Error(`Project ${target.projectId} is not ready on this worker`);
|
|
4222
4379
|
}
|
|
@@ -4230,7 +4387,26 @@ async function startWorker(options) {
|
|
|
4230
4387
|
projectConfigById
|
|
4231
4388
|
});
|
|
4232
4389
|
};
|
|
4233
|
-
const configureWorkerWorkspace = async (message) => {
|
|
4390
|
+
const configureWorkerWorkspace = async (message, receiptGeneration) => {
|
|
4391
|
+
const incidentDeferral = workspaceConfigurationIncidentDeferral({
|
|
4392
|
+
requestedIncidentId: message.deferWorkspaceSyncForIncidentId,
|
|
4393
|
+
activeIncidentId: activeWorkspaceIncidentId
|
|
4394
|
+
});
|
|
4395
|
+
if (incidentDeferral.error) {
|
|
4396
|
+
workspaceConfigured = false;
|
|
4397
|
+
return {
|
|
4398
|
+
result: failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, incidentDeferral.error),
|
|
4399
|
+
pending: incidentDeferredProjectCheckouts(message.projects),
|
|
4400
|
+
aheadOfOriginBranches: []
|
|
4401
|
+
};
|
|
4402
|
+
}
|
|
4403
|
+
if (incidentDeferral.incidentId) {
|
|
4404
|
+
workspaceConfigured = false;
|
|
4405
|
+
deferredWorkspaceConfiguration = {
|
|
4406
|
+
incidentId: incidentDeferral.incidentId,
|
|
4407
|
+
serverRefreshExpected: message.deferWorkspaceSyncForIncidentId === incidentDeferral.incidentId
|
|
4408
|
+
};
|
|
4409
|
+
}
|
|
4234
4410
|
const incomingCredentialGenerationFingerprint = credentialGenerationFingerprint({
|
|
4235
4411
|
...message,
|
|
4236
4412
|
workerBaseUrl: baseUrl,
|
|
@@ -4241,6 +4417,22 @@ async function startWorker(options) {
|
|
|
4241
4417
|
pendingFingerprintAtProcessStart: pendingCredentialGenerationIntentAtProcessStart?.fingerprint ?? null,
|
|
4242
4418
|
incomingFingerprint: incomingCredentialGenerationFingerprint
|
|
4243
4419
|
});
|
|
4420
|
+
if (deferredCredentialTransitionMustWait({
|
|
4421
|
+
incidentId: incidentDeferral.incidentId,
|
|
4422
|
+
transitionPhase: credentialTransitionPhase,
|
|
4423
|
+
canonicalRemediationActive: canonicalWorkspaceMutationIsActive(activeWorkspaceMutationTargets())
|
|
4424
|
+
})) {
|
|
4425
|
+
return {
|
|
4426
|
+
result: failedWorkspaceSyncResult(
|
|
4427
|
+
crypto.randomUUID(),
|
|
4428
|
+
{ type: "connect" },
|
|
4429
|
+
new Error("Workspace credential rotation is deferred until the active canonical remediation command finishes")
|
|
4430
|
+
),
|
|
4431
|
+
pending: incidentDeferredProjectCheckouts(message.projects),
|
|
4432
|
+
aheadOfOriginBranches: [],
|
|
4433
|
+
...incidentDeferral.incidentId ? { deferredWorkspaceSyncForIncidentId: incidentDeferral.incidentId } : {}
|
|
4434
|
+
};
|
|
4435
|
+
}
|
|
4244
4436
|
const credentialReapStatus = credentialReapContractStatus();
|
|
4245
4437
|
const verifiedCredentialReapContractNow = credentialReapStatus === "verified_systemd";
|
|
4246
4438
|
const credentialRestartIsContained = credentialGenerationRestartIsContained({
|
|
@@ -4354,6 +4546,12 @@ async function startWorker(options) {
|
|
|
4354
4546
|
workspaceSyncRequestsInFlight += 1;
|
|
4355
4547
|
try {
|
|
4356
4548
|
return await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
4549
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
4550
|
+
throw new Error("Workspace configuration was superseded by a newer server generation");
|
|
4551
|
+
}
|
|
4552
|
+
if (!workspaceConfigurationIncidentSnapshotIsCurrent(incidentDeferral.incidentId, activeWorkspaceIncidentId)) {
|
|
4553
|
+
throw new Error("Workspace configuration was superseded by a different active workspace incident");
|
|
4554
|
+
}
|
|
4357
4555
|
for (const project of message.projects) (0, import_repository_transition_policy.assertRepositoryTransitionState)(project);
|
|
4358
4556
|
const busyConfigurationChanges = (0, import_workspace_project_config_policy.busyProjectConfigurationChangeIds)({
|
|
4359
4557
|
currentProjects: [...projectConfigById.values()],
|
|
@@ -4375,10 +4573,12 @@ async function startWorker(options) {
|
|
|
4375
4573
|
const preserveOnlyBranches = message.projects.flatMap(
|
|
4376
4574
|
(project) => project.preserveOnlyBranches.map(({ branchId, branchName }) => ({ branchId, projectId: project.projectId, branchName }))
|
|
4377
4575
|
);
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4576
|
+
if (!incidentDeferral.incidentId) {
|
|
4577
|
+
projectWorkspaceState = projectWorkspaceStateStore.reconcile({
|
|
4578
|
+
desiredProjects: message.projects,
|
|
4579
|
+
preserveOnlyBranches
|
|
4580
|
+
});
|
|
4581
|
+
}
|
|
4382
4582
|
const stillPendingCreatedBranches = new Map(
|
|
4383
4583
|
projectWorkspaceState.locallyPendingCreatedBranches.map((branch) => [
|
|
4384
4584
|
(0, import_workspace_automatic_sync_policy.pendingCreatedBranchKey)(branch.projectId, branch.branchName),
|
|
@@ -4438,6 +4638,12 @@ async function startWorker(options) {
|
|
|
4438
4638
|
void 0,
|
|
4439
4639
|
credentialPublicationPreauthorized
|
|
4440
4640
|
);
|
|
4641
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
4642
|
+
throw new Error("Workspace configuration was superseded by a newer server generation");
|
|
4643
|
+
}
|
|
4644
|
+
if (!workspaceConfigurationIncidentSnapshotIsCurrent(incidentDeferral.incidentId, activeWorkspaceIncidentId)) {
|
|
4645
|
+
throw new Error("Workspace configuration was superseded by a different active workspace incident");
|
|
4646
|
+
}
|
|
4441
4647
|
configuredCredentialGenerationFingerprint = incomingCredentialGenerationFingerprint;
|
|
4442
4648
|
pendingCredentialGenerationIntentAtProcessStart = null;
|
|
4443
4649
|
bootstrapCredentialGenerationFingerprintAtProcessStart = null;
|
|
@@ -4452,6 +4658,34 @@ async function startWorker(options) {
|
|
|
4452
4658
|
branches: project.branches.filter(({ branchName }) => !pendingMirrorDeletes.has(projectBranchKey(project.projectId, branchName)))
|
|
4453
4659
|
}));
|
|
4454
4660
|
const nextProjectConfigById = new Map(effectiveProjects.map((project) => [project.projectId, project]));
|
|
4661
|
+
if (incidentDeferral.incidentId) {
|
|
4662
|
+
(0, import_workspace_git_sync.configureExistingWorkspaceGitForRemediation)({
|
|
4663
|
+
workspacePath: workspaceShadowRoot,
|
|
4664
|
+
remoteUrl: message.workspaceRemoteUrl,
|
|
4665
|
+
credentialHelper: nextWorkspaceCredentialHelper,
|
|
4666
|
+
credentialUsername: workerCredentialUsername,
|
|
4667
|
+
gitIdentity: message.gitIdentity
|
|
4668
|
+
});
|
|
4669
|
+
projectConfigById.clear();
|
|
4670
|
+
for (const project of effectiveProjects) projectConfigById.set(project.projectId, project);
|
|
4671
|
+
readyProjectIds.clear();
|
|
4672
|
+
reconciledProjectConfigFingerprints.clear();
|
|
4673
|
+
pendingCheckouts.clear();
|
|
4674
|
+
const pending2 = incidentDeferredProjectCheckouts(message.projects);
|
|
4675
|
+
for (const checkout of pending2) pendingCheckouts.set(projectBranchKey(checkout.projectId, checkout.branchName), checkout);
|
|
4676
|
+
workspaceConfigured = activeWorkspaceIncidentId === incidentDeferral.incidentId;
|
|
4677
|
+
return {
|
|
4678
|
+
result: deferredIncidentWorkspaceConfigurationResult({
|
|
4679
|
+
attemptId: crypto.randomUUID(),
|
|
4680
|
+
workerLabel: label,
|
|
4681
|
+
head: workspaceLocalHead(),
|
|
4682
|
+
skippedMountIds: buildWorkspaceMounts().map(({ id }) => id)
|
|
4683
|
+
}),
|
|
4684
|
+
pending: pending2,
|
|
4685
|
+
aheadOfOriginBranches: [],
|
|
4686
|
+
deferredWorkspaceSyncForIncidentId: incidentDeferral.incidentId
|
|
4687
|
+
};
|
|
4688
|
+
}
|
|
4455
4689
|
(0, import_workspace_git_sync.ensureWorkspaceGitClone)({
|
|
4456
4690
|
workspacePath: workspaceShadowRoot,
|
|
4457
4691
|
remoteUrl: message.workspaceRemoteUrl,
|
|
@@ -4519,10 +4753,24 @@ async function startWorker(options) {
|
|
|
4519
4753
|
{ ignoreBusy: true }
|
|
4520
4754
|
);
|
|
4521
4755
|
ensureConfiguredProjects();
|
|
4756
|
+
const configurationSyncAdmissionGeneration = workerAdmissionGeneration;
|
|
4757
|
+
const assertConfigurationSyncStillAdmitted = () => {
|
|
4758
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration || configurationSyncAdmissionGeneration !== workerAdmissionGeneration || !workspaceConfigurationIncidentSnapshotIsCurrent(incidentDeferral.incidentId, activeWorkspaceIncidentId) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
4759
|
+
throw new Error("Workspace configuration synchronization admission changed while asynchronous work was in flight");
|
|
4760
|
+
}
|
|
4761
|
+
};
|
|
4522
4762
|
const result = await performWorkspaceSync({
|
|
4523
4763
|
attemptId: crypto.randomUUID(),
|
|
4524
|
-
trigger: { type: "connect" }
|
|
4764
|
+
trigger: { type: "connect" },
|
|
4765
|
+
assertStillAdmitted: assertConfigurationSyncStillAdmitted
|
|
4525
4766
|
});
|
|
4767
|
+
assertConfigurationSyncStillAdmitted();
|
|
4768
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
4769
|
+
throw new Error("Workspace configuration was superseded by a newer server generation");
|
|
4770
|
+
}
|
|
4771
|
+
if (!workspaceConfigurationIncidentSnapshotIsCurrent(incidentDeferral.incidentId, activeWorkspaceIncidentId)) {
|
|
4772
|
+
throw new Error("Workspace configuration was superseded by a different active workspace incident");
|
|
4773
|
+
}
|
|
4526
4774
|
const publishedHead = result.publishedHead ?? result.localHead ?? result.startingHead;
|
|
4527
4775
|
if (publishedHead && ["no_change", "published", "updated", "conflict_reset", "reset"].includes(result.outcome)) {
|
|
4528
4776
|
const activeMountIds = new Set(result.activeMountIds ?? []);
|
|
@@ -4539,6 +4787,11 @@ async function startWorker(options) {
|
|
|
4539
4787
|
});
|
|
4540
4788
|
}
|
|
4541
4789
|
}
|
|
4790
|
+
if (deferredWorkspaceConfigurationRefreshTimer) {
|
|
4791
|
+
clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
|
|
4792
|
+
deferredWorkspaceConfigurationRefreshTimer = void 0;
|
|
4793
|
+
}
|
|
4794
|
+
deferredWorkspaceConfiguration = null;
|
|
4542
4795
|
workspaceConfigured = true;
|
|
4543
4796
|
const pending = [...pendingCheckouts.values()].sort(
|
|
4544
4797
|
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
@@ -4546,6 +4799,13 @@ async function startWorker(options) {
|
|
|
4546
4799
|
return { result, pending, aheadOfOriginBranches: collectAheadOfOriginBranches() };
|
|
4547
4800
|
});
|
|
4548
4801
|
} catch (error) {
|
|
4802
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
4803
|
+
return {
|
|
4804
|
+
result: failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, error),
|
|
4805
|
+
pending: incidentDeferredProjectCheckouts(message.projects),
|
|
4806
|
+
aheadOfOriginBranches: []
|
|
4807
|
+
};
|
|
4808
|
+
}
|
|
4549
4809
|
if (error instanceof import_registry_auth.RegistryAuthConfigurationError) {
|
|
4550
4810
|
workspaceConfigured = false;
|
|
4551
4811
|
githubCredential = null;
|
|
@@ -4600,7 +4860,7 @@ async function startWorker(options) {
|
|
|
4600
4860
|
workspaceAutomaticTimer = void 0;
|
|
4601
4861
|
}
|
|
4602
4862
|
void (async () => {
|
|
4603
|
-
if (workspaceConfigured && !activeWorkspaceIncidentId) {
|
|
4863
|
+
if (workspaceConfigured && !activeWorkspaceIncidentId && !deferredWorkspaceConfiguration) {
|
|
4604
4864
|
await runWorkspaceSync({
|
|
4605
4865
|
trigger: { type: "manual", detail: "graceful worker shutdown" }
|
|
4606
4866
|
});
|
|
@@ -4649,7 +4909,9 @@ async function startWorker(options) {
|
|
|
4649
4909
|
updateClis: true,
|
|
4650
4910
|
browserPortForwarding: true,
|
|
4651
4911
|
execStdinV1: true,
|
|
4652
|
-
ptyEnvFilesV1: true
|
|
4912
|
+
ptyEnvFilesV1: true,
|
|
4913
|
+
workspaceRemediationAncestorGuardV1: true,
|
|
4914
|
+
workspaceIncidentConfigDeferralV1: true
|
|
4653
4915
|
},
|
|
4654
4916
|
projectRoot: projectsRoot,
|
|
4655
4917
|
artifactRoot,
|
|
@@ -4685,26 +4947,35 @@ async function startWorker(options) {
|
|
|
4685
4947
|
return;
|
|
4686
4948
|
}
|
|
4687
4949
|
if (message.type === "workspace_config") {
|
|
4950
|
+
const receiptGeneration = ++workspaceConfigurationReceiptGeneration;
|
|
4951
|
+
const refreshesDeferredConfiguration = deferredWorkspaceConfiguration !== null && activeWorkspaceIncidentId === null && message.deferWorkspaceSyncForIncidentId === void 0;
|
|
4688
4952
|
advanceWorkerAdmissionGeneration();
|
|
4689
|
-
const configured = await configureWorkerWorkspace(message);
|
|
4953
|
+
const configured = await configureWorkerWorkspace(message, receiptGeneration);
|
|
4690
4954
|
sendWorkerMessageFromCurrentSource(ws, {
|
|
4691
4955
|
type: "workspace_configured",
|
|
4692
4956
|
requestId: message.requestId,
|
|
4693
4957
|
result: configured.result,
|
|
4694
4958
|
pendingCheckouts: configured.pending,
|
|
4695
|
-
aheadOfOriginBranches: configured.aheadOfOriginBranches
|
|
4959
|
+
aheadOfOriginBranches: configured.aheadOfOriginBranches,
|
|
4960
|
+
...configured.deferredWorkspaceSyncForIncidentId ? { deferredWorkspaceSyncForIncidentId: configured.deferredWorkspaceSyncForIncidentId } : {}
|
|
4696
4961
|
});
|
|
4697
4962
|
process.stdout.write(
|
|
4698
4963
|
`[r5d-worker] workspace configured: ${message.projects.length} project(s), ${configured.pending.length} pending checkout(s)
|
|
4699
4964
|
`
|
|
4700
4965
|
);
|
|
4966
|
+
if (refreshesDeferredConfiguration && configured.result.outcome === "failed" && deferredWorkspaceConfiguration !== null && currentWorkerSocket === ws) {
|
|
4967
|
+
workspaceConfigured = false;
|
|
4968
|
+
advanceWorkerAdmissionGeneration();
|
|
4969
|
+
ws.close(1012, "Deferred workspace configuration refresh failed");
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4701
4972
|
if (!workspacePeriodicTimer) {
|
|
4702
4973
|
workspacePeriodicTimer = setInterval(() => {
|
|
4703
4974
|
scheduleAutomaticWorkspaceSync({ type: "periodic", detail: "periodic workspace reconciliation" }, 0);
|
|
4704
4975
|
}, WORKSPACE_GIT_PERIODIC_MS);
|
|
4705
4976
|
workspacePeriodicTimer.unref();
|
|
4706
4977
|
}
|
|
4707
|
-
if (pendingAutomaticTrigger && !activeWorkspaceIncidentId) {
|
|
4978
|
+
if (pendingAutomaticTrigger && !activeWorkspaceIncidentId && !deferredWorkspaceConfiguration) {
|
|
4708
4979
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, 0);
|
|
4709
4980
|
}
|
|
4710
4981
|
return;
|
|
@@ -4716,11 +4987,20 @@ async function startWorker(options) {
|
|
|
4716
4987
|
trigger: message.trigger,
|
|
4717
4988
|
confirmedLargeDiff: message.confirmedLargeDiff,
|
|
4718
4989
|
confirmationReason: message.confirmationReason,
|
|
4719
|
-
resetToCanonical: message.resetToCanonical
|
|
4990
|
+
resetToCanonical: message.resetToCanonical,
|
|
4991
|
+
requiredAncestorHeads: message.requiredAncestorHeads
|
|
4720
4992
|
});
|
|
4721
4993
|
return;
|
|
4722
4994
|
}
|
|
4723
4995
|
if (message.type === "create_project_branch") {
|
|
4996
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
4997
|
+
sendWorkerMessage(ws, {
|
|
4998
|
+
type: "operation_result",
|
|
4999
|
+
requestId: message.requestId,
|
|
5000
|
+
error: `Project operations are deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5001
|
+
});
|
|
5002
|
+
return;
|
|
5003
|
+
}
|
|
4724
5004
|
try {
|
|
4725
5005
|
const pendingBranch = {
|
|
4726
5006
|
branchId: message.branchId,
|
|
@@ -4728,6 +5008,11 @@ async function startWorker(options) {
|
|
|
4728
5008
|
branchName: message.targetBranch
|
|
4729
5009
|
};
|
|
4730
5010
|
const created = await workspaceSyncSingleFlight.runMutation(() => {
|
|
5011
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5012
|
+
throw new Error(
|
|
5013
|
+
`Project branch creation was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5014
|
+
);
|
|
5015
|
+
}
|
|
4731
5016
|
const project = projectConfigById.get(message.projectId);
|
|
4732
5017
|
if (!project) throw new Error(`Project ${message.projectId} is missing from the worker workspace configuration`);
|
|
4733
5018
|
(0, import_repository_transition_policy.assertRepositoryExecutionEnabled)(project);
|
|
@@ -4811,9 +5096,22 @@ async function startWorker(options) {
|
|
|
4811
5096
|
return;
|
|
4812
5097
|
}
|
|
4813
5098
|
if (message.type === "delete_project_branch") {
|
|
5099
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5100
|
+
sendWorkerMessage(ws, {
|
|
5101
|
+
type: "operation_result",
|
|
5102
|
+
requestId: message.requestId,
|
|
5103
|
+
error: `Project operations are deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5104
|
+
});
|
|
5105
|
+
return;
|
|
5106
|
+
}
|
|
4814
5107
|
try {
|
|
4815
5108
|
const deletionKey = projectBranchKey(message.projectId, message.branchName);
|
|
4816
5109
|
const deletionNeedsSync = await workspaceSyncSingleFlight.runMutation(() => {
|
|
5110
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5111
|
+
throw new Error(
|
|
5112
|
+
`Project branch deletion was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5113
|
+
);
|
|
5114
|
+
}
|
|
4817
5115
|
const project = projectConfigById.get(message.projectId);
|
|
4818
5116
|
if (!project) throw new Error(`Project ${message.projectId} is missing from the worker workspace configuration`);
|
|
4819
5117
|
(0, import_repository_transition_policy.assertRepositoryExecutionEnabled)(project);
|
|
@@ -4913,8 +5211,38 @@ async function startWorker(options) {
|
|
|
4913
5211
|
}
|
|
4914
5212
|
if (message.type === "workspace_incident_updated") {
|
|
4915
5213
|
const previousIncidentId = activeWorkspaceIncidentId;
|
|
4916
|
-
|
|
5214
|
+
const nextIncidentId = (0, import_workspace_incident_state.applyWorkspaceIncidentUpdate)(activeWorkspaceIncidentId, message);
|
|
5215
|
+
if (nextIncidentId !== previousIncidentId) advanceWorkerAdmissionGeneration();
|
|
5216
|
+
activeWorkspaceIncidentId = nextIncidentId;
|
|
4917
5217
|
if (previousIncidentId && !activeWorkspaceIncidentId && (message.status === "resolved" || message.status === "confirmed" || message.status === "reset")) {
|
|
5218
|
+
const disposition = workspaceIncidentTerminalClearDisposition({
|
|
5219
|
+
deferredConfiguration: deferredWorkspaceConfiguration,
|
|
5220
|
+
clearedIncidentId: previousIncidentId
|
|
5221
|
+
});
|
|
5222
|
+
if (disposition !== "ordinary_resume") {
|
|
5223
|
+
pendingAutomaticTrigger ??= { type: "periodic", detail: "resume after workspace incident" };
|
|
5224
|
+
workspaceConfigured = false;
|
|
5225
|
+
advanceWorkerAdmissionGeneration();
|
|
5226
|
+
if (disposition === "reconnect_for_refresh") {
|
|
5227
|
+
ws.close(1012, "Refreshing deferred workspace configuration");
|
|
5228
|
+
} else {
|
|
5229
|
+
if (deferredWorkspaceConfigurationRefreshTimer) clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
|
|
5230
|
+
const deferredIncidentId = deferredWorkspaceConfiguration?.incidentId;
|
|
5231
|
+
deferredWorkspaceConfigurationRefreshTimer = setTimeout(() => {
|
|
5232
|
+
deferredWorkspaceConfigurationRefreshTimer = void 0;
|
|
5233
|
+
if (!deferredIncidentId || !deferredWorkspaceRefreshWatchdogIsCurrent({
|
|
5234
|
+
capturedIncidentId: deferredIncidentId,
|
|
5235
|
+
deferredConfiguration: deferredWorkspaceConfiguration,
|
|
5236
|
+
activeIncidentId: activeWorkspaceIncidentId
|
|
5237
|
+
}) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
5238
|
+
return;
|
|
5239
|
+
}
|
|
5240
|
+
ws.close(1012, "Timed out waiting for deferred workspace configuration refresh");
|
|
5241
|
+
}, WORKSPACE_INCIDENT_CONFIG_REFRESH_TIMEOUT_MS);
|
|
5242
|
+
deferredWorkspaceConfigurationRefreshTimer.unref();
|
|
5243
|
+
}
|
|
5244
|
+
return;
|
|
5245
|
+
}
|
|
4918
5246
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger ?? { type: "periodic", detail: "resume after workspace incident" }, 0);
|
|
4919
5247
|
}
|
|
4920
5248
|
return;
|
|
@@ -5018,6 +5346,12 @@ async function startWorker(options) {
|
|
|
5018
5346
|
sendAck({ error: `Process run ${message.runId} is not active on this worker` });
|
|
5019
5347
|
return;
|
|
5020
5348
|
}
|
|
5349
|
+
if (!deferredWorkspaceTargetIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, active.target)) {
|
|
5350
|
+
sendAck({
|
|
5351
|
+
error: `Process input is deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
|
|
5352
|
+
});
|
|
5353
|
+
return;
|
|
5354
|
+
}
|
|
5021
5355
|
if (!active.interactive || !active.stdin) {
|
|
5022
5356
|
sendAck({
|
|
5023
5357
|
error: `Process run ${message.runId} has no open stdin. Start a new shell command with "interactive": true to write to its stdin.`
|
|
@@ -5063,6 +5397,15 @@ async function startWorker(options) {
|
|
|
5063
5397
|
});
|
|
5064
5398
|
return;
|
|
5065
5399
|
}
|
|
5400
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5401
|
+
sendWorkerMessage(ws, {
|
|
5402
|
+
type: "pty_error",
|
|
5403
|
+
requestId: message.requestId,
|
|
5404
|
+
ptyId: message.ptyId,
|
|
5405
|
+
error: `Shells are deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5406
|
+
});
|
|
5407
|
+
return;
|
|
5408
|
+
}
|
|
5066
5409
|
await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(message.target, workspaceSyncSingleFlight, () => {
|
|
5067
5410
|
workspaceSyncPriorityPtyTargets.set(message.ptyId, message.target);
|
|
5068
5411
|
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
@@ -5071,6 +5414,11 @@ async function startWorker(options) {
|
|
|
5071
5414
|
let mutationLeaseTransferred = false;
|
|
5072
5415
|
try {
|
|
5073
5416
|
releaseWorkspaceMutation = await (0, import_workspace_command_sync_policy.acquireWorkspaceCommandMutation)(message.target, workspaceSyncSingleFlight);
|
|
5417
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5418
|
+
throw new Error(
|
|
5419
|
+
`Shell opening was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5420
|
+
);
|
|
5421
|
+
}
|
|
5074
5422
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
5075
5423
|
process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${describeWorkerSessionTarget(message.target)}
|
|
5076
5424
|
`);
|
|
@@ -5103,6 +5451,14 @@ async function startWorker(options) {
|
|
|
5103
5451
|
}
|
|
5104
5452
|
if (message.type === "pty_input") {
|
|
5105
5453
|
const activePty = activePtys.get(message.ptyId);
|
|
5454
|
+
if (activePty && workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5455
|
+
sendWorkerMessage(ws, {
|
|
5456
|
+
type: "pty_error",
|
|
5457
|
+
ptyId: message.ptyId,
|
|
5458
|
+
error: `Shell input is deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
|
|
5459
|
+
});
|
|
5460
|
+
return;
|
|
5461
|
+
}
|
|
5106
5462
|
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
5107
5463
|
markWorkspaceDirty({ type: "shell_inline", detail: `pty ${message.ptyId}` }, false, activePty.target);
|
|
5108
5464
|
}
|
|
@@ -5132,6 +5488,17 @@ async function startWorker(options) {
|
|
|
5132
5488
|
});
|
|
5133
5489
|
return;
|
|
5134
5490
|
}
|
|
5491
|
+
if (!workspaceCommandTransportIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, "exec")) {
|
|
5492
|
+
sendWorkerMessage(ws, {
|
|
5493
|
+
type: "exec_result",
|
|
5494
|
+
requestId: message.requestId,
|
|
5495
|
+
stdout: "",
|
|
5496
|
+
stderr: "",
|
|
5497
|
+
exitCode: 1,
|
|
5498
|
+
error: `One-shot commands are deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5499
|
+
});
|
|
5500
|
+
return;
|
|
5501
|
+
}
|
|
5135
5502
|
let result;
|
|
5136
5503
|
let targetReserved = false;
|
|
5137
5504
|
const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
|
|
@@ -5144,6 +5511,11 @@ async function startWorker(options) {
|
|
|
5144
5511
|
});
|
|
5145
5512
|
}
|
|
5146
5513
|
const runCommand = async () => {
|
|
5514
|
+
if (!workspaceCommandTransportIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, "exec")) {
|
|
5515
|
+
throw new Error(
|
|
5516
|
+
`One-shot command was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5517
|
+
);
|
|
5518
|
+
}
|
|
5147
5519
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
5148
5520
|
process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
|
|
5149
5521
|
`);
|
|
@@ -5154,7 +5526,14 @@ async function startWorker(options) {
|
|
|
5154
5526
|
token,
|
|
5155
5527
|
artifactRoot,
|
|
5156
5528
|
planRoot,
|
|
5157
|
-
assertAdmission:
|
|
5529
|
+
assertAdmission: () => {
|
|
5530
|
+
if (!workspaceCommandTransportIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, "exec")) {
|
|
5531
|
+
throw new Error(
|
|
5532
|
+
`One-shot command was fenced before spawn by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5533
|
+
);
|
|
5534
|
+
}
|
|
5535
|
+
assertMessageAdmission();
|
|
5536
|
+
}
|
|
5158
5537
|
});
|
|
5159
5538
|
};
|
|
5160
5539
|
result = await (0, import_workspace_command_sync_policy.runWorkspaceCommand)(message.target, workspaceSyncSingleFlight, runCommand);
|
|
@@ -5254,6 +5633,14 @@ async function startWorker(options) {
|
|
|
5254
5633
|
return;
|
|
5255
5634
|
}
|
|
5256
5635
|
if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes" || message.type === "code_list" || message.type === "code_read") {
|
|
5636
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5637
|
+
sendWorkerMessage(ws, {
|
|
5638
|
+
type: "operation_result",
|
|
5639
|
+
requestId: message.requestId,
|
|
5640
|
+
error: `Workspace operations are deferred for incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}; use a canonical remediation shell`
|
|
5641
|
+
});
|
|
5642
|
+
return;
|
|
5643
|
+
}
|
|
5257
5644
|
const reservesVisibleWorkspace = targetMayMutateVisibleWorkspace(message.target);
|
|
5258
5645
|
const mutatesVisibleWorkspace = (message.type === "write" || message.type === "edit") && targetMayMutateVisibleWorkspace(message.target);
|
|
5259
5646
|
if (reservesVisibleWorkspace) {
|
|
@@ -5272,7 +5659,8 @@ async function startWorker(options) {
|
|
|
5272
5659
|
baseUrl,
|
|
5273
5660
|
token,
|
|
5274
5661
|
artifactRoot,
|
|
5275
|
-
planRoot
|
|
5662
|
+
planRoot,
|
|
5663
|
+
assertAdmission: assertMessageAdmission
|
|
5276
5664
|
});
|
|
5277
5665
|
});
|
|
5278
5666
|
ws.send(
|
|
@@ -5302,7 +5690,7 @@ async function startWorker(options) {
|
|
|
5302
5690
|
} finally {
|
|
5303
5691
|
if (reservesVisibleWorkspace) {
|
|
5304
5692
|
workspaceSyncPriorityOperationTargets.delete(message.requestId);
|
|
5305
|
-
if (pendingAutomaticTrigger && !activeWorkspaceIncidentId) {
|
|
5693
|
+
if (pendingAutomaticTrigger && !activeWorkspaceIncidentId && !deferredWorkspaceConfiguration) {
|
|
5306
5694
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, dirtyTrigger ? WORKSPACE_GIT_QUIET_MS : 0);
|
|
5307
5695
|
}
|
|
5308
5696
|
}
|
|
@@ -5338,6 +5726,10 @@ async function startWorker(options) {
|
|
|
5338
5726
|
clearInterval(workspacePeriodicTimer);
|
|
5339
5727
|
workspacePeriodicTimer = void 0;
|
|
5340
5728
|
}
|
|
5729
|
+
if (deferredWorkspaceConfigurationRefreshTimer) {
|
|
5730
|
+
clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
|
|
5731
|
+
deferredWorkspaceConfigurationRefreshTimer = void 0;
|
|
5732
|
+
}
|
|
5341
5733
|
if (terminalReplayTimer) {
|
|
5342
5734
|
clearInterval(terminalReplayTimer);
|
|
5343
5735
|
terminalReplayTimer = void 0;
|