@ricsam/r5d-worker 0.0.81 → 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 +743 -121
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-command-sync-policy.cjs +3 -3
- package/dist/cjs/workspace-git-sync.cjs +446 -123
- package/dist/cjs/workspace-merge-projection.cjs +392 -0
- package/dist/mjs/main.mjs +744 -122
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-command-sync-policy.mjs +3 -3
- package/dist/mjs/workspace-git-sync.mjs +448 -123
- package/dist/mjs/workspace-merge-projection.mjs +355 -0
- package/dist/types/main.d.ts +395 -0
- package/dist/types/working-tree-mirror.d.ts +1 -2
- package/dist/types/workspace-command-sync-policy.d.ts +4 -3
- package/dist/types/workspace-git-sync.d.ts +34 -0
- package/dist/types/workspace-merge-projection.d.ts +42 -0
- package/package.json +1 -1
package/dist/mjs/main.mjs
CHANGED
|
@@ -79,12 +79,14 @@ import {
|
|
|
79
79
|
pruneAuthoritativelyDesiredBranchDeletions
|
|
80
80
|
} from "./project-workspace-state.mjs";
|
|
81
81
|
import {
|
|
82
|
+
configureExistingWorkspaceGitForRemediation,
|
|
82
83
|
ensureWorkspaceGitClone,
|
|
83
84
|
hydrateWorkspaceGitMounts,
|
|
84
85
|
recoverWorkspaceGitHydration,
|
|
85
86
|
resetWorkspaceGit,
|
|
86
87
|
synchronizeWorkspaceGit,
|
|
87
|
-
workspaceGitHydrationIsCurrent
|
|
88
|
+
workspaceGitHydrationIsCurrent,
|
|
89
|
+
WorkspaceRemediationAncestryError
|
|
88
90
|
} from "./workspace-git-sync.mjs";
|
|
89
91
|
class ProjectWorkspaceConfigurationDeferredError extends Error {
|
|
90
92
|
}
|
|
@@ -99,6 +101,11 @@ const DEFAULT_READ_MAX_BYTES = 5e4;
|
|
|
99
101
|
const MAX_LINE_LENGTH = 2e3;
|
|
100
102
|
const WORKSPACE_GIT_QUIET_MS = 5e3;
|
|
101
103
|
const WORKSPACE_GIT_PERIODIC_MS = 6e4;
|
|
104
|
+
const WORKSPACE_INCIDENT_CONFIG_REFRESH_TIMEOUT_MS = 6e4;
|
|
105
|
+
const PTY_INPUT_BUSY_GRACE_MS = 3e3;
|
|
106
|
+
const PTY_FOREGROUND_POLL_MS = 1e3;
|
|
107
|
+
const PTY_FOREGROUND_IDLE_ENABLED = process.env.R5D_PTY_FOREGROUND_IDLE !== "0";
|
|
108
|
+
const PTY_TMP_PATH_PREFIX = "r5d-worker-tmp://";
|
|
102
109
|
const activeProcesses = /* @__PURE__ */ new Map();
|
|
103
110
|
const credentialBearingProcessGroups = /* @__PURE__ */ new Map();
|
|
104
111
|
const credentialBearingProcessGroupTargets = /* @__PURE__ */ new Map();
|
|
@@ -156,6 +163,107 @@ function assertWorkerChildAdmission(input) {
|
|
|
156
163
|
throw new StaleWorkerAdmissionError();
|
|
157
164
|
}
|
|
158
165
|
}
|
|
166
|
+
function pathIsInsideRoot(rootPath, candidatePath) {
|
|
167
|
+
const relative = path.relative(rootPath, candidatePath);
|
|
168
|
+
return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
|
|
169
|
+
}
|
|
170
|
+
function resolvePtyEnvFilePath(requestedPath, temporaryRoot = os.tmpdir()) {
|
|
171
|
+
const canonicalTemporaryRoot = fs.realpathSync.native(temporaryRoot);
|
|
172
|
+
if (requestedPath.startsWith(PTY_TMP_PATH_PREFIX)) {
|
|
173
|
+
const filename = requestedPath.slice(PTY_TMP_PATH_PREFIX.length);
|
|
174
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/.test(filename)) {
|
|
175
|
+
throw new Error("PTY environment file temporary token must contain one safe filename");
|
|
176
|
+
}
|
|
177
|
+
return path.join(canonicalTemporaryRoot, filename);
|
|
178
|
+
}
|
|
179
|
+
if (!path.isAbsolute(requestedPath)) {
|
|
180
|
+
throw new Error(`PTY environment file path must be absolute or use ${PTY_TMP_PATH_PREFIX}`);
|
|
181
|
+
}
|
|
182
|
+
const resolvedPath = path.resolve(requestedPath);
|
|
183
|
+
const canonicalParent = fs.realpathSync.native(path.dirname(resolvedPath));
|
|
184
|
+
const canonicalPath = path.join(canonicalParent, path.basename(resolvedPath));
|
|
185
|
+
if (!pathIsInsideRoot(canonicalTemporaryRoot, canonicalPath) || canonicalPath === canonicalTemporaryRoot) {
|
|
186
|
+
throw new Error(`PTY environment file path must be inside ${canonicalTemporaryRoot}`);
|
|
187
|
+
}
|
|
188
|
+
return canonicalPath;
|
|
189
|
+
}
|
|
190
|
+
function removePtyEnvFiles(paths) {
|
|
191
|
+
for (const filePath of paths) {
|
|
192
|
+
try {
|
|
193
|
+
fs.rmSync(filePath, { force: true });
|
|
194
|
+
} catch (error) {
|
|
195
|
+
process.stderr.write(
|
|
196
|
+
`[r5d-worker] failed to remove PTY environment file ${filePath}: ${error instanceof Error ? error.message : String(error)}
|
|
197
|
+
`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function stagePtyEnvFiles(envFiles, temporaryRoot = os.tmpdir()) {
|
|
203
|
+
const staged = { paths: [], resolvedByRequestedPath: /* @__PURE__ */ new Map() };
|
|
204
|
+
try {
|
|
205
|
+
for (const envFile of envFiles ?? []) {
|
|
206
|
+
if (!envFile || typeof envFile.path !== "string" || typeof envFile.content !== "string") {
|
|
207
|
+
throw new Error("PTY environment files require string path and content values");
|
|
208
|
+
}
|
|
209
|
+
if (envFile.mode !== void 0 && envFile.mode !== 384) {
|
|
210
|
+
throw new Error("PTY environment files must use mode 0600");
|
|
211
|
+
}
|
|
212
|
+
const resolvedPath = resolvePtyEnvFilePath(envFile.path, temporaryRoot);
|
|
213
|
+
if (staged.resolvedByRequestedPath.has(envFile.path) || staged.paths.includes(resolvedPath)) {
|
|
214
|
+
throw new Error(`Duplicate PTY environment file path: ${envFile.path}`);
|
|
215
|
+
}
|
|
216
|
+
const descriptor = fs.openSync(
|
|
217
|
+
resolvedPath,
|
|
218
|
+
fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | (typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0),
|
|
219
|
+
384
|
|
220
|
+
);
|
|
221
|
+
staged.paths.push(resolvedPath);
|
|
222
|
+
staged.resolvedByRequestedPath.set(envFile.path, resolvedPath);
|
|
223
|
+
try {
|
|
224
|
+
fs.writeFileSync(descriptor, envFile.content, "utf8");
|
|
225
|
+
fs.fchmodSync(descriptor, 384);
|
|
226
|
+
fs.fsyncSync(descriptor);
|
|
227
|
+
} finally {
|
|
228
|
+
fs.closeSync(descriptor);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return staged;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
removePtyEnvFiles(staged.paths);
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function resolvePtyEnvFileReferences(env, staged) {
|
|
238
|
+
return Object.fromEntries(Object.entries(env).map(([name, value]) => [name, staged.resolvedByRequestedPath.get(value) ?? value]));
|
|
239
|
+
}
|
|
240
|
+
function workerCommandHasWorkspaceEffect(message) {
|
|
241
|
+
return message.workspaceEffect !== "none";
|
|
242
|
+
}
|
|
243
|
+
function workerPtyIsWorkspaceBusy(pty, now = Date.now(), foregroundIdleEnabled = PTY_FOREGROUND_IDLE_ENABLED) {
|
|
244
|
+
return !foregroundIdleEnabled || pty.foregroundBusy || now - pty.lastInputAt < PTY_INPUT_BUSY_GRACE_MS;
|
|
245
|
+
}
|
|
246
|
+
function parseLinuxPtyForegroundBusy(stat) {
|
|
247
|
+
const commandEnd = stat.lastIndexOf(")");
|
|
248
|
+
if (commandEnd < 0) return null;
|
|
249
|
+
const fields = stat.slice(commandEnd + 1).trim().split(/\s+/);
|
|
250
|
+
const processGroup = Number(fields[2]);
|
|
251
|
+
const foregroundProcessGroup = Number(fields[5]);
|
|
252
|
+
if (!Number.isSafeInteger(processGroup) || processGroup <= 0 || !Number.isSafeInteger(foregroundProcessGroup)) return null;
|
|
253
|
+
return foregroundProcessGroup !== processGroup;
|
|
254
|
+
}
|
|
255
|
+
const workerPtyTestHarness = {
|
|
256
|
+
temporaryPathPrefix: PTY_TMP_PATH_PREFIX,
|
|
257
|
+
inputBusyGraceMs: PTY_INPUT_BUSY_GRACE_MS,
|
|
258
|
+
resolveEnvFilePath: resolvePtyEnvFilePath,
|
|
259
|
+
stageEnvFiles: stagePtyEnvFiles,
|
|
260
|
+
removeEnvFiles: removePtyEnvFiles,
|
|
261
|
+
resolveEnvFileReferences: resolvePtyEnvFileReferences,
|
|
262
|
+
commandHasWorkspaceEffect: workerCommandHasWorkspaceEffect,
|
|
263
|
+
canonicalSyncTerminalHead,
|
|
264
|
+
ptyIsWorkspaceBusy: workerPtyIsWorkspaceBusy,
|
|
265
|
+
parseLinuxForegroundBusy: parseLinuxPtyForegroundBusy
|
|
266
|
+
};
|
|
159
267
|
function defaultConfigPath() {
|
|
160
268
|
return path.join(os.homedir(), ".config", "r5d", "r5dctl", "config.json");
|
|
161
269
|
}
|
|
@@ -1414,6 +1522,86 @@ function credentialReapContractStatus(probe) {
|
|
|
1414
1522
|
function verifiedCredentialReapContract(probe) {
|
|
1415
1523
|
return credentialReapContractStatus(probe) === "verified_systemd";
|
|
1416
1524
|
}
|
|
1525
|
+
function workspaceConfigurationIncidentDeferral(input) {
|
|
1526
|
+
const incidentId = input.requestedIncidentId ?? input.activeIncidentId;
|
|
1527
|
+
if (incidentId === null) return { incidentId: null };
|
|
1528
|
+
if (!/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/.test(incidentId)) {
|
|
1529
|
+
return { incidentId: null, error: new Error("Workspace configuration incident deferral requires a canonical incident UUID") };
|
|
1530
|
+
}
|
|
1531
|
+
if (input.requestedIncidentId !== void 0 && input.requestedIncidentId !== input.activeIncidentId) {
|
|
1532
|
+
return {
|
|
1533
|
+
incidentId: null,
|
|
1534
|
+
error: new Error(
|
|
1535
|
+
`Workspace configuration incident deferral ${input.requestedIncidentId} does not match active incident ${input.activeIncidentId ?? "none"}`
|
|
1536
|
+
)
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
return { incidentId };
|
|
1540
|
+
}
|
|
1541
|
+
function workspaceIncidentTerminalClearDisposition(input) {
|
|
1542
|
+
if (!input.deferredConfiguration) return "ordinary_resume";
|
|
1543
|
+
void input.clearedIncidentId;
|
|
1544
|
+
return input.deferredConfiguration.serverRefreshExpected ? "await_server_refresh" : "reconnect_for_refresh";
|
|
1545
|
+
}
|
|
1546
|
+
function deferredWorkspaceTargetIsAllowed(deferredConfiguration, activeIncidentId, target) {
|
|
1547
|
+
const canonicalTarget = target.type === "workspace" && target.rootProfile === "canonical_sync";
|
|
1548
|
+
if (deferredConfiguration) return activeIncidentId === deferredConfiguration.incidentId && canonicalTarget;
|
|
1549
|
+
return activeIncidentId === null || canonicalTarget;
|
|
1550
|
+
}
|
|
1551
|
+
function deferredWorkspaceSyncTriggerIsAllowed(deferredConfiguration, activeIncidentId, trigger) {
|
|
1552
|
+
const incidentId = deferredConfiguration?.incidentId ?? activeIncidentId;
|
|
1553
|
+
return incidentId === null || (deferredConfiguration === null || activeIncidentId === deferredConfiguration.incidentId) && (trigger.type === "remediation" || trigger.type === "remediation_confirm" || trigger.type === "remediation_reset");
|
|
1554
|
+
}
|
|
1555
|
+
function workspaceOperationsAreFenced(deferredConfiguration, activeIncidentId) {
|
|
1556
|
+
return deferredConfiguration !== null || activeIncidentId !== null;
|
|
1557
|
+
}
|
|
1558
|
+
function workspaceCommandTransportIsAllowed(deferredConfiguration, activeIncidentId, transport) {
|
|
1559
|
+
return !workspaceOperationsAreFenced(deferredConfiguration, activeIncidentId) || transport === "exec_start";
|
|
1560
|
+
}
|
|
1561
|
+
function deferredCredentialTransitionMustWait(input) {
|
|
1562
|
+
return input.incidentId !== null && input.transitionPhase !== "current" && input.canonicalRemediationActive;
|
|
1563
|
+
}
|
|
1564
|
+
function workspaceConfigurationIncidentSnapshotIsCurrent(capturedIncidentId, activeIncidentId) {
|
|
1565
|
+
return capturedIncidentId === null ? activeIncidentId === null : activeIncidentId === null || activeIncidentId === capturedIncidentId;
|
|
1566
|
+
}
|
|
1567
|
+
function deferredWorkspaceRefreshWatchdogIsCurrent(input) {
|
|
1568
|
+
return input.deferredConfiguration?.incidentId === input.capturedIncidentId && input.activeIncidentId === null;
|
|
1569
|
+
}
|
|
1570
|
+
function incidentDeferredProjectCheckouts(projects) {
|
|
1571
|
+
return projects.flatMap(
|
|
1572
|
+
(project) => project.executionDisabled ? [] : project.branches.map(({ branchName }) => ({ projectId: project.projectId, branchName }))
|
|
1573
|
+
).sort((left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName));
|
|
1574
|
+
}
|
|
1575
|
+
function deferredIncidentWorkspaceConfigurationResult(input) {
|
|
1576
|
+
return {
|
|
1577
|
+
type: "workspace_sync",
|
|
1578
|
+
attemptId: input.attemptId,
|
|
1579
|
+
workerLabel: input.workerLabel,
|
|
1580
|
+
trigger: { type: "connect", detail: "workspace synchronization deferred for active remediation" },
|
|
1581
|
+
outcome: "no_change",
|
|
1582
|
+
startingHead: input.head,
|
|
1583
|
+
...input.head ? { localHead: input.head } : {},
|
|
1584
|
+
rebaseCount: 0,
|
|
1585
|
+
diffSizeBytes: 0,
|
|
1586
|
+
gitStatus: "",
|
|
1587
|
+
affectedProjects: [],
|
|
1588
|
+
affectedPaths: [],
|
|
1589
|
+
activeMountIds: [],
|
|
1590
|
+
skippedMountIds: [...input.skippedMountIds].sort(),
|
|
1591
|
+
activeProjectBranchPublications: [],
|
|
1592
|
+
discardedPaths: [],
|
|
1593
|
+
localChangesDiscarded: false
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
function workspaceSyncFailureHydrationIsSafe(input) {
|
|
1597
|
+
if (input.error instanceof WorkspaceRemediationAncestryError) return true;
|
|
1598
|
+
if (input.resetToCanonical) return false;
|
|
1599
|
+
try {
|
|
1600
|
+
return input.inspectCurrentHydration();
|
|
1601
|
+
} catch {
|
|
1602
|
+
return false;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1417
1605
|
function fenceUnsafeWorkspaceSyncFailure(input) {
|
|
1418
1606
|
if (input.hydrationCurrent) return false;
|
|
1419
1607
|
input.invalidateExecution();
|
|
@@ -1446,7 +1634,21 @@ const workerGitSecurityTestHarness = {
|
|
|
1446
1634
|
credentialReapContractStatus,
|
|
1447
1635
|
verifiedCredentialReapContract,
|
|
1448
1636
|
fenceUnsafeWorkspaceSyncFailure,
|
|
1637
|
+
workspaceConfigurationIncidentDeferral,
|
|
1638
|
+
workspaceIncidentTerminalClearDisposition,
|
|
1639
|
+
deferredWorkspaceTargetIsAllowed,
|
|
1640
|
+
deferredWorkspaceSyncTriggerIsAllowed,
|
|
1641
|
+
workspaceOperationsAreFenced,
|
|
1642
|
+
workspaceCommandTransportIsAllowed,
|
|
1643
|
+
deferredCredentialTransitionMustWait,
|
|
1644
|
+
workspaceConfigurationIncidentSnapshotIsCurrent,
|
|
1645
|
+
deferredWorkspaceRefreshWatchdogIsCurrent,
|
|
1646
|
+
incidentDeferredProjectCheckouts,
|
|
1647
|
+
deferredIncidentWorkspaceConfigurationResult,
|
|
1648
|
+
workspaceSyncFailureHydrationIsSafe,
|
|
1449
1649
|
assertWorkerChildAdmission,
|
|
1650
|
+
executeWriteFileOperation,
|
|
1651
|
+
executeEditFileOperation,
|
|
1450
1652
|
terminateCredentialBearingChildren,
|
|
1451
1653
|
terminateCredentialBearingChildrenWithRetention,
|
|
1452
1654
|
async commitCredentialGeneration(prepared, credential, children, beforeMutation, previousGenerationFenced = false) {
|
|
@@ -1597,6 +1799,15 @@ function hasProjectWorktree(checkoutPath) {
|
|
|
1597
1799
|
return false;
|
|
1598
1800
|
}
|
|
1599
1801
|
}
|
|
1802
|
+
function canonicalSyncTerminalHead(resolvedTarget, readHead = (rootPath) => runGit(["rev-parse", "HEAD"], { cwd: rootPath })) {
|
|
1803
|
+
if (resolvedTarget.target.type !== "workspace" || resolvedTarget.target.rootProfile !== "canonical_sync") return void 0;
|
|
1804
|
+
try {
|
|
1805
|
+
const head = readHead(resolvedTarget.rootPath);
|
|
1806
|
+
return /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(head) ? head : void 0;
|
|
1807
|
+
} catch {
|
|
1808
|
+
return void 0;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1600
1811
|
function describeWorkerSessionTarget(target) {
|
|
1601
1812
|
return target.type === "project" ? `${target.projectId}/${target.branchName}` : `${target.ownerUserId}/${target.rootProfile}`;
|
|
1602
1813
|
}
|
|
@@ -1901,8 +2112,10 @@ async function executeWriteFileOperation(input) {
|
|
|
1901
2112
|
planRoot: input.planRoot,
|
|
1902
2113
|
access: "write"
|
|
1903
2114
|
});
|
|
2115
|
+
input.assertAdmission();
|
|
1904
2116
|
const resolved = resolveWorkerFilePath(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
|
|
1905
2117
|
return withFileMutationQueue(mutationQueueKey(input.resolvedTarget.target, resolved), async () => {
|
|
2118
|
+
input.assertAdmission();
|
|
1906
2119
|
return writeWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.content, builtInPaths);
|
|
1907
2120
|
});
|
|
1908
2121
|
}
|
|
@@ -1918,8 +2131,10 @@ async function executeEditFileOperation(input) {
|
|
|
1918
2131
|
planRoot: input.planRoot,
|
|
1919
2132
|
access: "write"
|
|
1920
2133
|
});
|
|
2134
|
+
input.assertAdmission();
|
|
1921
2135
|
const resolved = resolveWorkerFilePath(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
|
|
1922
2136
|
return withFileMutationQueue(mutationQueueKey(input.resolvedTarget.target, resolved), async () => {
|
|
2137
|
+
input.assertAdmission();
|
|
1923
2138
|
return editWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.edits, builtInPaths);
|
|
1924
2139
|
});
|
|
1925
2140
|
}
|
|
@@ -2337,7 +2552,9 @@ async function executeCommand(input) {
|
|
|
2337
2552
|
});
|
|
2338
2553
|
spawnedProcess = subprocess;
|
|
2339
2554
|
credentialBearingProcessGroups.set(subprocess.pid, subprocess);
|
|
2340
|
-
|
|
2555
|
+
if (workerCommandHasWorkspaceEffect(input.message)) {
|
|
2556
|
+
credentialBearingProcessGroupTargets.set(subprocess.pid, input.resolvedTarget.target);
|
|
2557
|
+
}
|
|
2341
2558
|
activeProcesses.set(input.message.runId, {
|
|
2342
2559
|
process: subprocess,
|
|
2343
2560
|
target: input.resolvedTarget.target,
|
|
@@ -2348,7 +2565,8 @@ async function executeCommand(input) {
|
|
|
2348
2565
|
argv: input.message.argv,
|
|
2349
2566
|
command: input.message.argv.join(" "),
|
|
2350
2567
|
cwd,
|
|
2351
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2568
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2569
|
+
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {}
|
|
2352
2570
|
});
|
|
2353
2571
|
if (input.message.timeoutMs) {
|
|
2354
2572
|
timeout = setTimeout(() => {
|
|
@@ -2434,7 +2652,9 @@ async function executeStreamingCommand(input) {
|
|
|
2434
2652
|
});
|
|
2435
2653
|
spawnedProcess = subprocess;
|
|
2436
2654
|
credentialBearingProcessGroups.set(subprocess.pid, subprocess);
|
|
2437
|
-
|
|
2655
|
+
if (workerCommandHasWorkspaceEffect(input.message)) {
|
|
2656
|
+
credentialBearingProcessGroupTargets.set(subprocess.pid, input.resolvedTarget.target);
|
|
2657
|
+
}
|
|
2438
2658
|
activeProcesses.set(input.message.runId, {
|
|
2439
2659
|
process: subprocess,
|
|
2440
2660
|
target: input.resolvedTarget.target,
|
|
@@ -2447,7 +2667,8 @@ async function executeStreamingCommand(input) {
|
|
|
2447
2667
|
command: input.message.command,
|
|
2448
2668
|
cwd,
|
|
2449
2669
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2450
|
-
...interactive ? { interactive: true, stdin: subprocess.stdin } : {}
|
|
2670
|
+
...interactive ? { interactive: true, stdin: subprocess.stdin } : {},
|
|
2671
|
+
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {}
|
|
2451
2672
|
});
|
|
2452
2673
|
started = true;
|
|
2453
2674
|
sendWorkerMessage(input.ws, {
|
|
@@ -2489,12 +2710,15 @@ async function executeStreamingCommand(input) {
|
|
|
2489
2710
|
});
|
|
2490
2711
|
})
|
|
2491
2712
|
]);
|
|
2713
|
+
const canonicalWorkspaceHead = canonicalSyncTerminalHead(input.resolvedTarget);
|
|
2492
2714
|
const terminal = {
|
|
2493
2715
|
type: "exec_exit",
|
|
2494
2716
|
runId: input.message.runId,
|
|
2495
2717
|
exitCode,
|
|
2496
2718
|
durationMs: Date.now() - startedAt,
|
|
2497
|
-
...timedOut ? { timedOut: true } : {}
|
|
2719
|
+
...timedOut ? { timedOut: true } : {},
|
|
2720
|
+
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {},
|
|
2721
|
+
...canonicalWorkspaceHead ? { canonicalWorkspaceHead } : {}
|
|
2498
2722
|
};
|
|
2499
2723
|
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
2500
2724
|
sendWorkerMessage(input.ws, terminal);
|
|
@@ -2502,11 +2726,14 @@ async function executeStreamingCommand(input) {
|
|
|
2502
2726
|
if (!started && error instanceof StaleWorkerAdmissionError) throw error;
|
|
2503
2727
|
const message = error instanceof Error ? error.message : String(error);
|
|
2504
2728
|
if (started) {
|
|
2729
|
+
const canonicalWorkspaceHead = canonicalSyncTerminalHead(input.resolvedTarget);
|
|
2505
2730
|
const terminal = {
|
|
2506
2731
|
type: "exec_error",
|
|
2507
2732
|
runId: input.message.runId,
|
|
2508
2733
|
error: message,
|
|
2509
|
-
durationMs: Date.now() - startedAt
|
|
2734
|
+
durationMs: Date.now() - startedAt,
|
|
2735
|
+
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {},
|
|
2736
|
+
...canonicalWorkspaceHead ? { canonicalWorkspaceHead } : {}
|
|
2510
2737
|
};
|
|
2511
2738
|
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
2512
2739
|
sendWorkerMessage(input.ws, terminal);
|
|
@@ -2587,7 +2814,8 @@ function buildActiveProcessReports() {
|
|
|
2587
2814
|
command: active.command,
|
|
2588
2815
|
...active.cwd ? { cwd: active.cwd } : {},
|
|
2589
2816
|
startedAt: active.startedAt,
|
|
2590
|
-
...active.interactive ? { interactive: true } : {}
|
|
2817
|
+
...active.interactive ? { interactive: true } : {},
|
|
2818
|
+
...active.workspaceEffect === "none" ? { workspaceEffect: "none" } : {}
|
|
2591
2819
|
}));
|
|
2592
2820
|
}
|
|
2593
2821
|
function sendActiveProcessReport(ws) {
|
|
@@ -2599,8 +2827,13 @@ function sendActiveProcessReport(ws) {
|
|
|
2599
2827
|
const PTY_BRIDGE_SCRIPT = String.raw`
|
|
2600
2828
|
const readline = require("node:readline");
|
|
2601
2829
|
const nodePty = require("node-pty");
|
|
2830
|
+
const fs = require("node:fs");
|
|
2831
|
+
const { execFile } = require("node:child_process");
|
|
2602
2832
|
|
|
2603
2833
|
let ptyProcess = null;
|
|
2834
|
+
let foregroundTimer = null;
|
|
2835
|
+
let foregroundPollInFlight = false;
|
|
2836
|
+
let lastForegroundBusy = null;
|
|
2604
2837
|
|
|
2605
2838
|
function send(message, callback) {
|
|
2606
2839
|
process.stdout.write(JSON.stringify(message) + "\n", callback);
|
|
@@ -2610,6 +2843,46 @@ function decode(data) {
|
|
|
2610
2843
|
return Buffer.from(data, "base64").toString("utf8");
|
|
2611
2844
|
}
|
|
2612
2845
|
|
|
2846
|
+
function emitForeground(busy) {
|
|
2847
|
+
if (busy === lastForegroundBusy) return;
|
|
2848
|
+
lastForegroundBusy = busy;
|
|
2849
|
+
send({ type: "foreground", busy });
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
function linuxForegroundBusy(pid) {
|
|
2853
|
+
const stat = fs.readFileSync("/proc/" + pid + "/stat", "utf8");
|
|
2854
|
+
const commandEnd = stat.lastIndexOf(")");
|
|
2855
|
+
if (commandEnd < 0) throw new Error("invalid /proc stat");
|
|
2856
|
+
const fields = stat.slice(commandEnd + 1).trim().split(/\s+/);
|
|
2857
|
+
const processGroup = Number(fields[2]);
|
|
2858
|
+
const foregroundProcessGroup = Number(fields[5]);
|
|
2859
|
+
if (!Number.isSafeInteger(processGroup) || processGroup <= 0 || !Number.isSafeInteger(foregroundProcessGroup)) {
|
|
2860
|
+
throw new Error("invalid process group fields");
|
|
2861
|
+
}
|
|
2862
|
+
return foregroundProcessGroup !== processGroup;
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
function pollForeground() {
|
|
2866
|
+
if (!ptyProcess || !Number.isSafeInteger(ptyProcess.pid) || ptyProcess.pid <= 0) return;
|
|
2867
|
+
if (process.platform === "linux") {
|
|
2868
|
+
try {
|
|
2869
|
+
emitForeground(linuxForegroundBusy(ptyProcess.pid));
|
|
2870
|
+
} catch {
|
|
2871
|
+
emitForeground(true);
|
|
2872
|
+
}
|
|
2873
|
+
return;
|
|
2874
|
+
}
|
|
2875
|
+
if (process.platform !== "darwin" || foregroundPollInFlight) return;
|
|
2876
|
+
foregroundPollInFlight = true;
|
|
2877
|
+
const pid = ptyProcess.pid;
|
|
2878
|
+
execFile("/bin/ps", ["-o", "tpgid=", "-p", String(pid)], (error, stdout) => {
|
|
2879
|
+
foregroundPollInFlight = false;
|
|
2880
|
+
if (!ptyProcess || ptyProcess.pid !== pid) return;
|
|
2881
|
+
const foregroundProcessGroup = Number(String(stdout).trim());
|
|
2882
|
+
emitForeground(Boolean(error) || !Number.isSafeInteger(foregroundProcessGroup) || foregroundProcessGroup !== pid);
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2613
2886
|
const rl = readline.createInterface({ input: process.stdin });
|
|
2614
2887
|
|
|
2615
2888
|
rl.on("line", (line) => {
|
|
@@ -2628,9 +2901,14 @@ rl.on("line", (line) => {
|
|
|
2628
2901
|
send({ type: "output", data: Buffer.from(data, "utf8").toString("base64") });
|
|
2629
2902
|
});
|
|
2630
2903
|
ptyProcess.onExit((event) => {
|
|
2904
|
+
if (foregroundTimer) clearInterval(foregroundTimer);
|
|
2905
|
+
foregroundTimer = null;
|
|
2631
2906
|
send({ type: "exit", exitCode: event.exitCode, signal: event.signal }, () => process.exit(0));
|
|
2632
2907
|
});
|
|
2633
|
-
send({ type: "opened" });
|
|
2908
|
+
send({ type: "opened", pid: ptyProcess.pid });
|
|
2909
|
+
pollForeground();
|
|
2910
|
+
foregroundTimer = setInterval(pollForeground, ${PTY_FOREGROUND_POLL_MS});
|
|
2911
|
+
foregroundTimer.unref();
|
|
2634
2912
|
return;
|
|
2635
2913
|
}
|
|
2636
2914
|
|
|
@@ -2723,7 +3001,11 @@ function createNodePtyBridge(options) {
|
|
|
2723
3001
|
};
|
|
2724
3002
|
const handleEvent = (event) => {
|
|
2725
3003
|
if (event.type === "opened") {
|
|
2726
|
-
options.onOpened();
|
|
3004
|
+
options.onOpened(event.pid);
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
if (event.type === "foreground") {
|
|
3008
|
+
options.onForeground(event.busy);
|
|
2727
3009
|
return;
|
|
2728
3010
|
}
|
|
2729
3011
|
if (event.type === "output") {
|
|
@@ -2804,6 +3086,9 @@ function createNodePtyBridge(options) {
|
|
|
2804
3086
|
}
|
|
2805
3087
|
};
|
|
2806
3088
|
}
|
|
3089
|
+
const workerPtyBridgeTestHarness = {
|
|
3090
|
+
create: createNodePtyBridge
|
|
3091
|
+
};
|
|
2807
3092
|
function resolveHostShell(command, platform = process.platform) {
|
|
2808
3093
|
if (platform === "win32") {
|
|
2809
3094
|
const file2 = process.env.COMSPEC || "powershell.exe";
|
|
@@ -2839,64 +3124,90 @@ async function openPty(input) {
|
|
|
2839
3124
|
}
|
|
2840
3125
|
});
|
|
2841
3126
|
input.assertAdmission();
|
|
2842
|
-
const
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
}
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
}
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
3127
|
+
const stagedEnvFiles = stagePtyEnvFiles(input.message.envFiles);
|
|
3128
|
+
let envFilesRemoved = false;
|
|
3129
|
+
const cleanupEnvFiles = () => {
|
|
3130
|
+
if (envFilesRemoved) return;
|
|
3131
|
+
envFilesRemoved = true;
|
|
3132
|
+
removePtyEnvFiles(stagedEnvFiles.paths);
|
|
3133
|
+
};
|
|
3134
|
+
try {
|
|
3135
|
+
const ptyProcess = createNodePtyBridge({
|
|
3136
|
+
file: shell.file,
|
|
3137
|
+
args: shell.args,
|
|
3138
|
+
ptyOptions: {
|
|
3139
|
+
name: "xterm-256color",
|
|
3140
|
+
cols: Math.max(1, Math.min(Math.floor(input.message.cols || 80), 500)),
|
|
3141
|
+
rows: Math.max(1, Math.min(Math.floor(input.message.rows || 24), 500)),
|
|
3142
|
+
cwd: input.resolvedTarget.rootPath,
|
|
3143
|
+
env: workerChildProcessEnvironment([
|
|
3144
|
+
githubProcessEnv(),
|
|
3145
|
+
resolvePtyEnvFileReferences(input.message.env ?? {}, stagedEnvFiles),
|
|
3146
|
+
planProcessEnv,
|
|
3147
|
+
targetProcessEnv,
|
|
3148
|
+
shell.env ?? {}
|
|
3149
|
+
])
|
|
3150
|
+
},
|
|
3151
|
+
onOpened: () => {
|
|
3152
|
+
sendWorkerMessage(input.ws, {
|
|
3153
|
+
type: "pty_opened",
|
|
3154
|
+
requestId: input.message.requestId,
|
|
3155
|
+
ptyId: input.message.ptyId
|
|
3156
|
+
});
|
|
3157
|
+
},
|
|
3158
|
+
onForeground: (busy) => {
|
|
3159
|
+
const activePty = activePtys.get(input.message.ptyId);
|
|
3160
|
+
if (activePty && (busy || input.message.command === void 0)) activePty.foregroundBusy = busy;
|
|
3161
|
+
},
|
|
3162
|
+
onOutput: (data) => {
|
|
3163
|
+
outputCoalescer.push(data);
|
|
3164
|
+
},
|
|
3165
|
+
onExit: (event) => {
|
|
3166
|
+
outputCoalescer.flush();
|
|
3167
|
+
input.releaseWorkspaceMutation?.();
|
|
3168
|
+
activePtys.delete(input.message.ptyId);
|
|
3169
|
+
cleanupEnvFiles();
|
|
3170
|
+
input.onTerminal?.();
|
|
3171
|
+
sendWorkerMessage(input.ws, {
|
|
3172
|
+
type: "pty_exit",
|
|
3173
|
+
ptyId: input.message.ptyId,
|
|
3174
|
+
exitCode: event.exitCode,
|
|
3175
|
+
signal: event.signal
|
|
3176
|
+
});
|
|
3177
|
+
},
|
|
3178
|
+
onError: (error) => {
|
|
3179
|
+
outputCoalescer.flush();
|
|
3180
|
+
input.releaseWorkspaceMutation?.();
|
|
3181
|
+
activePtys.delete(input.message.ptyId);
|
|
3182
|
+
cleanupEnvFiles();
|
|
3183
|
+
input.onTerminal?.();
|
|
3184
|
+
sendWorkerMessage(input.ws, {
|
|
3185
|
+
type: "pty_error",
|
|
3186
|
+
requestId: input.message.requestId,
|
|
3187
|
+
ptyId: input.message.ptyId,
|
|
3188
|
+
error: error.message
|
|
3189
|
+
});
|
|
3190
|
+
},
|
|
3191
|
+
onTerminationError: (error) => {
|
|
3192
|
+
sendWorkerMessage(input.ws, {
|
|
3193
|
+
type: "pty_error",
|
|
3194
|
+
requestId: input.message.requestId,
|
|
3195
|
+
ptyId: input.message.ptyId,
|
|
3196
|
+
error: error.message
|
|
3197
|
+
});
|
|
3198
|
+
}
|
|
3199
|
+
});
|
|
3200
|
+
activePtys.set(input.message.ptyId, {
|
|
3201
|
+
...ptyProcess,
|
|
3202
|
+
target: input.resolvedTarget.target,
|
|
3203
|
+
foregroundBusy: true,
|
|
3204
|
+
lastInputAt: 0,
|
|
3205
|
+
...input.releaseWorkspaceMutation ? { releaseWorkspaceMutation: input.releaseWorkspaceMutation } : {}
|
|
3206
|
+
});
|
|
3207
|
+
} catch (error) {
|
|
3208
|
+
cleanupEnvFiles();
|
|
3209
|
+
throw error;
|
|
3210
|
+
}
|
|
2900
3211
|
}
|
|
2901
3212
|
function writePty(ws, message) {
|
|
2902
3213
|
const ptyProcess = activePtys.get(message.ptyId);
|
|
@@ -2908,6 +3219,7 @@ function writePty(ws, message) {
|
|
|
2908
3219
|
});
|
|
2909
3220
|
return;
|
|
2910
3221
|
}
|
|
3222
|
+
ptyProcess.lastInputAt = Date.now();
|
|
2911
3223
|
ptyProcess.write(message.data);
|
|
2912
3224
|
}
|
|
2913
3225
|
function resizePty(message) {
|
|
@@ -3076,6 +3388,9 @@ async function startWorker(options) {
|
|
|
3076
3388
|
let workspaceGitIdentity = null;
|
|
3077
3389
|
let workspaceConfigured = false;
|
|
3078
3390
|
let activeWorkspaceIncidentId = null;
|
|
3391
|
+
let deferredWorkspaceConfiguration = null;
|
|
3392
|
+
let deferredWorkspaceConfigurationRefreshTimer;
|
|
3393
|
+
let workspaceConfigurationReceiptGeneration = 0;
|
|
3079
3394
|
let workspaceAutomaticTimer;
|
|
3080
3395
|
let workspacePeriodicTimer;
|
|
3081
3396
|
let pendingAutomaticTrigger;
|
|
@@ -3345,13 +3660,24 @@ async function startWorker(options) {
|
|
|
3345
3660
|
});
|
|
3346
3661
|
};
|
|
3347
3662
|
const activeWorkspaceMutationTargets = () => [
|
|
3348
|
-
...Array.from(activeProcesses.values()
|
|
3663
|
+
...Array.from(activeProcesses.values()).filter(workerCommandHasWorkspaceEffect).map(({ target }) => target),
|
|
3349
3664
|
...credentialBearingProcessGroupTargets.values(),
|
|
3350
3665
|
...workspaceSyncPriorityProcessTargets.values(),
|
|
3351
3666
|
...workspaceSyncPriorityOperationTargets.values(),
|
|
3352
|
-
...Array.from(activePtys.values()
|
|
3667
|
+
...Array.from(activePtys.values()).filter((pty) => workerPtyIsWorkspaceBusy(pty)).map(({ target }) => target),
|
|
3353
3668
|
...workspaceSyncPriorityPtyTargets.values()
|
|
3354
3669
|
];
|
|
3670
|
+
let visibleWorkspaceMutationEpoch = 0;
|
|
3671
|
+
const projectWorkspaceMutationEpochs = /* @__PURE__ */ new Map();
|
|
3672
|
+
const recordVisibleWorkspaceMutation = (target) => {
|
|
3673
|
+
if (target.type === "workspace") {
|
|
3674
|
+
if (target.rootProfile === "visible_projects") visibleWorkspaceMutationEpoch += 1;
|
|
3675
|
+
return;
|
|
3676
|
+
}
|
|
3677
|
+
const key = projectBranchKey(target.projectId, target.branchName);
|
|
3678
|
+
projectWorkspaceMutationEpochs.set(key, (projectWorkspaceMutationEpochs.get(key) ?? 0) + 1);
|
|
3679
|
+
};
|
|
3680
|
+
const projectWorkspaceMutationToken = (projectId, branchName) => `${visibleWorkspaceMutationEpoch}:${projectWorkspaceMutationEpochs.get(projectBranchKey(projectId, branchName)) ?? 0}`;
|
|
3355
3681
|
const canonicalWorkspaceMutationIsActive = (targets) => targets.some((target) => target.type === "workspace" && target.rootProfile === "canonical_sync");
|
|
3356
3682
|
const projectBranchMountActivityBusy = (projectId, branchName, branchPath) => {
|
|
3357
3683
|
const activeTargets = activeWorkspaceMutationTargets();
|
|
@@ -3388,6 +3714,7 @@ async function startWorker(options) {
|
|
|
3388
3714
|
preserveLocalOnInitialOuterAbsence: creatorLocalIncarnation,
|
|
3389
3715
|
preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
|
|
3390
3716
|
busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
|
|
3717
|
+
mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
|
|
3391
3718
|
busyForRecovery: () => projectBranchMountActivityBusy(project.projectId, branch.branchName, branchPath)
|
|
3392
3719
|
});
|
|
3393
3720
|
mounts.push({
|
|
@@ -3401,6 +3728,7 @@ async function startWorker(options) {
|
|
|
3401
3728
|
preserveLocalOnInitialOuterAbsence: creatorLocalIncarnation,
|
|
3402
3729
|
preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
|
|
3403
3730
|
busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
|
|
3731
|
+
mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
|
|
3404
3732
|
busyForRecovery: () => projectBranchMountActivityBusy(project.projectId, branch.branchName, branchPath)
|
|
3405
3733
|
});
|
|
3406
3734
|
}
|
|
@@ -3421,6 +3749,7 @@ async function startWorker(options) {
|
|
|
3421
3749
|
const activeTargets = activeWorkspaceMutationTargets();
|
|
3422
3750
|
return canonicalWorkspaceMutationIsActive(activeTargets) || hasActiveVisibleProjectsWorkspaceTarget(activeTargets);
|
|
3423
3751
|
},
|
|
3752
|
+
mutationToken: () => String(visibleWorkspaceMutationEpoch),
|
|
3424
3753
|
busyForRecovery: () => {
|
|
3425
3754
|
const activeTargets = activeWorkspaceMutationTargets();
|
|
3426
3755
|
return canonicalWorkspaceMutationIsActive(activeTargets) || hasActiveVisibleProjectsWorkspaceTarget(activeTargets);
|
|
@@ -3778,6 +4107,8 @@ async function startWorker(options) {
|
|
|
3778
4107
|
localChangesDiscarded: false,
|
|
3779
4108
|
...result.conflictPaths ? { conflictPaths: result.conflictPaths } : {},
|
|
3780
4109
|
...result.conflictSnapshotRefs ? { conflictSnapshotRefs: result.conflictSnapshotRefs } : {},
|
|
4110
|
+
...result.conflictKind ? { conflictKind: result.conflictKind } : {},
|
|
4111
|
+
...result.verifiedAncestorHeads ? { verifiedAncestorHeads: result.verifiedAncestorHeads } : {},
|
|
3781
4112
|
...result.error ? { error: result.error } : {}
|
|
3782
4113
|
};
|
|
3783
4114
|
};
|
|
@@ -3844,10 +4175,7 @@ async function startWorker(options) {
|
|
|
3844
4175
|
};
|
|
3845
4176
|
}
|
|
3846
4177
|
const outerRemediation = input.trigger.type === "remediation" || input.trigger.type === "remediation_confirm";
|
|
3847
|
-
const
|
|
3848
|
-
(mount) => !(mount.deleteWhenSourceMissing && !fs.existsSync(mount.sourcePath)) && mount.busy?.()
|
|
3849
|
-
);
|
|
3850
|
-
const observedProjectHeads = outerRemediation || ordinaryCycleHasBusyLiveMount ? [] : observeProjectHeadsBeforeOuterWorkspace({ allowNonFastForward: false, failClosed: false });
|
|
4178
|
+
const observedProjectHeads = outerRemediation ? [] : observeProjectHeadsBeforeOuterWorkspace({ allowNonFastForward: false, failClosed: false });
|
|
3851
4179
|
const inboundMoveMountIds = new Set(
|
|
3852
4180
|
observedProjectHeads.flatMap(
|
|
3853
4181
|
({ projectId, observations }) => observations.filter(({ shouldMove }) => shouldMove).map(({ branchName }) => projectMountId(projectId, branchName))
|
|
@@ -3866,10 +4194,13 @@ async function startWorker(options) {
|
|
|
3866
4194
|
commitDetail: input.confirmationReason ?? input.trigger.detail,
|
|
3867
4195
|
allowLargeDiff: input.confirmedLargeDiff,
|
|
3868
4196
|
skipMountMirror: outerRemediation,
|
|
4197
|
+
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4198
|
+
assertStillAdmitted: input.assertStillAdmitted,
|
|
3869
4199
|
afterWorkspacePublished: outerRemediation ? void 0 : ({ activeMountIds, publishedHead }) => {
|
|
3870
4200
|
pushChangedProjectHeads(activeMountIds, publishedHead, publicationHeads, inboundMoveMountIds);
|
|
3871
4201
|
}
|
|
3872
4202
|
});
|
|
4203
|
+
input.assertStillAdmitted?.();
|
|
3873
4204
|
if (["no_change", "updated", "pushed"].includes(result.outcome) && !outerRemediation) {
|
|
3874
4205
|
applyObservedProjectHeadsAfterInboundWorkspace(observedProjectHeads, result.activeMountIds, false);
|
|
3875
4206
|
}
|
|
@@ -3892,6 +4223,28 @@ async function startWorker(options) {
|
|
|
3892
4223
|
const runWorkspaceSync = async (input) => {
|
|
3893
4224
|
const attemptId = input.attemptId ?? crypto.randomUUID();
|
|
3894
4225
|
const requestedAt = Date.now();
|
|
4226
|
+
const admittedGeneration = workerAdmissionGeneration;
|
|
4227
|
+
const admittedActiveIncidentId = activeWorkspaceIncidentId;
|
|
4228
|
+
const admittedDeferredIncidentId = deferredWorkspaceConfiguration?.incidentId ?? null;
|
|
4229
|
+
const assertStillAdmitted = () => {
|
|
4230
|
+
if (admittedGeneration !== workerAdmissionGeneration || admittedActiveIncidentId !== activeWorkspaceIncidentId || admittedDeferredIncidentId !== (deferredWorkspaceConfiguration?.incidentId ?? null) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || !deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4231
|
+
throw new Error("Workspace synchronization admission changed while asynchronous work was in flight");
|
|
4232
|
+
}
|
|
4233
|
+
};
|
|
4234
|
+
if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4235
|
+
const result2 = {
|
|
4236
|
+
...failedWorkspaceSyncResult(
|
|
4237
|
+
attemptId,
|
|
4238
|
+
input.trigger,
|
|
4239
|
+
new Error(
|
|
4240
|
+
`Workspace synchronization is deferred for incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}; only remediation synchronization is allowed`
|
|
4241
|
+
)
|
|
4242
|
+
),
|
|
4243
|
+
telemetry: { totalMs: 0, queueMs: 0, prepareMs: 0, synchronizeMs: 0 }
|
|
4244
|
+
};
|
|
4245
|
+
if (input.sendResult !== false) sendWorkspaceSyncResult(input.requestId, result2);
|
|
4246
|
+
return result2;
|
|
4247
|
+
}
|
|
3895
4248
|
if (!input.requestId && input.sendResult !== false) {
|
|
3896
4249
|
if (currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
3897
4250
|
throw new Error("Cannot start autonomous workspace synchronization without an open worker control socket");
|
|
@@ -3906,23 +4259,32 @@ async function startWorker(options) {
|
|
|
3906
4259
|
result = await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
3907
4260
|
queueEnteredAt = Date.now();
|
|
3908
4261
|
syncStartedAt = Date.now();
|
|
4262
|
+
if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4263
|
+
return failedWorkspaceSyncResult(
|
|
4264
|
+
attemptId,
|
|
4265
|
+
input.trigger,
|
|
4266
|
+
new Error(
|
|
4267
|
+
`Workspace synchronization was fenced while queued by incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
|
|
4268
|
+
)
|
|
4269
|
+
);
|
|
4270
|
+
}
|
|
3909
4271
|
try {
|
|
4272
|
+
assertStillAdmitted();
|
|
3910
4273
|
return await performWorkspaceSync({
|
|
3911
4274
|
attemptId,
|
|
3912
4275
|
trigger: input.trigger,
|
|
3913
4276
|
confirmedLargeDiff: input.confirmedLargeDiff,
|
|
3914
4277
|
confirmationReason: input.confirmationReason,
|
|
3915
|
-
resetToCanonical: input.resetToCanonical
|
|
4278
|
+
resetToCanonical: input.resetToCanonical,
|
|
4279
|
+
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4280
|
+
assertStillAdmitted
|
|
3916
4281
|
});
|
|
3917
4282
|
} catch (error) {
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
hydrationCurrent = false;
|
|
3924
|
-
}
|
|
3925
|
-
}
|
|
4283
|
+
const hydrationCurrent = workspaceSyncFailureHydrationIsSafe({
|
|
4284
|
+
error,
|
|
4285
|
+
resetToCanonical: input.resetToCanonical === true,
|
|
4286
|
+
inspectCurrentHydration: () => workspaceGitHydrationIsCurrent(workspaceShadowRoot, buildWorkspaceMounts())
|
|
4287
|
+
});
|
|
3926
4288
|
if (!hydrationCurrent) {
|
|
3927
4289
|
process.stderr.write(
|
|
3928
4290
|
`[r5d-worker] workspace synchronization failed with an incompletely hydrated visible tree; exiting for exact recovery: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -3964,7 +4326,7 @@ async function startWorker(options) {
|
|
|
3964
4326
|
pendingCreatedBranchPublicationNotBefore
|
|
3965
4327
|
);
|
|
3966
4328
|
const initialDeferral = pendingBranchDeferral();
|
|
3967
|
-
if (!workspaceConfigured || activeWorkspaceIncidentId || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || automaticSyncInFlight || initialDeferral?.kind === "active_target") {
|
|
4329
|
+
if (!workspaceConfigured || activeWorkspaceIncidentId || deferredWorkspaceConfiguration || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || automaticSyncInFlight || initialDeferral?.kind === "active_target") {
|
|
3968
4330
|
return;
|
|
3969
4331
|
}
|
|
3970
4332
|
if (workspaceAutomaticTimer) clearTimeout(workspaceAutomaticTimer);
|
|
@@ -3972,7 +4334,8 @@ async function startWorker(options) {
|
|
|
3972
4334
|
() => {
|
|
3973
4335
|
workspaceAutomaticTimer = void 0;
|
|
3974
4336
|
const scheduledTrigger = pendingAutomaticTrigger;
|
|
3975
|
-
if (!scheduledTrigger || !workspaceConfigured || activeWorkspaceIncidentId || currentWorkerSocket !== ws)
|
|
4337
|
+
if (!scheduledTrigger || !workspaceConfigured || activeWorkspaceIncidentId || deferredWorkspaceConfiguration || currentWorkerSocket !== ws)
|
|
4338
|
+
return;
|
|
3976
4339
|
const currentDeferral = pendingBranchDeferral();
|
|
3977
4340
|
if (currentDeferral?.kind === "active_target") return;
|
|
3978
4341
|
if (currentDeferral?.kind === "creation_grace") {
|
|
@@ -3994,7 +4357,7 @@ async function startWorker(options) {
|
|
|
3994
4357
|
}).finally(() => {
|
|
3995
4358
|
automaticSyncInFlight = false;
|
|
3996
4359
|
heartbeatBusyGrace = grantWorkerHeartbeatBusyGrace(lastServerHeartbeatAt, heartbeatBusyGrace);
|
|
3997
|
-
if (pendingAutomaticTrigger && !activeWorkspaceIncidentId) {
|
|
4360
|
+
if (pendingAutomaticTrigger && !activeWorkspaceIncidentId && !deferredWorkspaceConfiguration) {
|
|
3998
4361
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, WORKSPACE_GIT_QUIET_MS);
|
|
3999
4362
|
}
|
|
4000
4363
|
});
|
|
@@ -4003,12 +4366,18 @@ async function startWorker(options) {
|
|
|
4003
4366
|
);
|
|
4004
4367
|
workspaceAutomaticTimer.unref();
|
|
4005
4368
|
};
|
|
4006
|
-
const markWorkspaceDirty = (trigger, force = false) => {
|
|
4369
|
+
const markWorkspaceDirty = (trigger, force = false, target) => {
|
|
4370
|
+
if (target) recordVisibleWorkspaceMutation(target);
|
|
4007
4371
|
scheduleAutomaticWorkspaceSync(trigger, force ? 0 : WORKSPACE_GIT_QUIET_MS);
|
|
4008
4372
|
};
|
|
4009
4373
|
const targetMayMutateVisibleWorkspace = (target) => target.type === "project" || target.rootProfile === "visible_projects";
|
|
4010
4374
|
const resolveMessageTarget = (target) => {
|
|
4011
4375
|
if (!workspaceConfigured) throw new Error("Worker workspace configuration has not completed successfully");
|
|
4376
|
+
if (!deferredWorkspaceTargetIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, target)) {
|
|
4377
|
+
throw new Error(
|
|
4378
|
+
`Worker workspace configuration is fenced for incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}; only canonical remediation commands are allowed`
|
|
4379
|
+
);
|
|
4380
|
+
}
|
|
4012
4381
|
if (target.type === "project" && !readyProjectIds.has(target.projectId)) {
|
|
4013
4382
|
throw new Error(`Project ${target.projectId} is not ready on this worker`);
|
|
4014
4383
|
}
|
|
@@ -4022,7 +4391,26 @@ async function startWorker(options) {
|
|
|
4022
4391
|
projectConfigById
|
|
4023
4392
|
});
|
|
4024
4393
|
};
|
|
4025
|
-
const configureWorkerWorkspace = async (message) => {
|
|
4394
|
+
const configureWorkerWorkspace = async (message, receiptGeneration) => {
|
|
4395
|
+
const incidentDeferral = workspaceConfigurationIncidentDeferral({
|
|
4396
|
+
requestedIncidentId: message.deferWorkspaceSyncForIncidentId,
|
|
4397
|
+
activeIncidentId: activeWorkspaceIncidentId
|
|
4398
|
+
});
|
|
4399
|
+
if (incidentDeferral.error) {
|
|
4400
|
+
workspaceConfigured = false;
|
|
4401
|
+
return {
|
|
4402
|
+
result: failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, incidentDeferral.error),
|
|
4403
|
+
pending: incidentDeferredProjectCheckouts(message.projects),
|
|
4404
|
+
aheadOfOriginBranches: []
|
|
4405
|
+
};
|
|
4406
|
+
}
|
|
4407
|
+
if (incidentDeferral.incidentId) {
|
|
4408
|
+
workspaceConfigured = false;
|
|
4409
|
+
deferredWorkspaceConfiguration = {
|
|
4410
|
+
incidentId: incidentDeferral.incidentId,
|
|
4411
|
+
serverRefreshExpected: message.deferWorkspaceSyncForIncidentId === incidentDeferral.incidentId
|
|
4412
|
+
};
|
|
4413
|
+
}
|
|
4026
4414
|
const incomingCredentialGenerationFingerprint = credentialGenerationFingerprint({
|
|
4027
4415
|
...message,
|
|
4028
4416
|
workerBaseUrl: baseUrl,
|
|
@@ -4033,6 +4421,22 @@ async function startWorker(options) {
|
|
|
4033
4421
|
pendingFingerprintAtProcessStart: pendingCredentialGenerationIntentAtProcessStart?.fingerprint ?? null,
|
|
4034
4422
|
incomingFingerprint: incomingCredentialGenerationFingerprint
|
|
4035
4423
|
});
|
|
4424
|
+
if (deferredCredentialTransitionMustWait({
|
|
4425
|
+
incidentId: incidentDeferral.incidentId,
|
|
4426
|
+
transitionPhase: credentialTransitionPhase,
|
|
4427
|
+
canonicalRemediationActive: canonicalWorkspaceMutationIsActive(activeWorkspaceMutationTargets())
|
|
4428
|
+
})) {
|
|
4429
|
+
return {
|
|
4430
|
+
result: failedWorkspaceSyncResult(
|
|
4431
|
+
crypto.randomUUID(),
|
|
4432
|
+
{ type: "connect" },
|
|
4433
|
+
new Error("Workspace credential rotation is deferred until the active canonical remediation command finishes")
|
|
4434
|
+
),
|
|
4435
|
+
pending: incidentDeferredProjectCheckouts(message.projects),
|
|
4436
|
+
aheadOfOriginBranches: [],
|
|
4437
|
+
...incidentDeferral.incidentId ? { deferredWorkspaceSyncForIncidentId: incidentDeferral.incidentId } : {}
|
|
4438
|
+
};
|
|
4439
|
+
}
|
|
4036
4440
|
const credentialReapStatus = credentialReapContractStatus();
|
|
4037
4441
|
const verifiedCredentialReapContractNow = credentialReapStatus === "verified_systemd";
|
|
4038
4442
|
const credentialRestartIsContained = credentialGenerationRestartIsContained({
|
|
@@ -4146,6 +4550,12 @@ async function startWorker(options) {
|
|
|
4146
4550
|
workspaceSyncRequestsInFlight += 1;
|
|
4147
4551
|
try {
|
|
4148
4552
|
return await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
4553
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
4554
|
+
throw new Error("Workspace configuration was superseded by a newer server generation");
|
|
4555
|
+
}
|
|
4556
|
+
if (!workspaceConfigurationIncidentSnapshotIsCurrent(incidentDeferral.incidentId, activeWorkspaceIncidentId)) {
|
|
4557
|
+
throw new Error("Workspace configuration was superseded by a different active workspace incident");
|
|
4558
|
+
}
|
|
4149
4559
|
for (const project of message.projects) assertRepositoryTransitionState(project);
|
|
4150
4560
|
const busyConfigurationChanges = busyProjectConfigurationChangeIds({
|
|
4151
4561
|
currentProjects: [...projectConfigById.values()],
|
|
@@ -4167,10 +4577,12 @@ async function startWorker(options) {
|
|
|
4167
4577
|
const preserveOnlyBranches = message.projects.flatMap(
|
|
4168
4578
|
(project) => project.preserveOnlyBranches.map(({ branchId, branchName }) => ({ branchId, projectId: project.projectId, branchName }))
|
|
4169
4579
|
);
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4580
|
+
if (!incidentDeferral.incidentId) {
|
|
4581
|
+
projectWorkspaceState = projectWorkspaceStateStore.reconcile({
|
|
4582
|
+
desiredProjects: message.projects,
|
|
4583
|
+
preserveOnlyBranches
|
|
4584
|
+
});
|
|
4585
|
+
}
|
|
4174
4586
|
const stillPendingCreatedBranches = new Map(
|
|
4175
4587
|
projectWorkspaceState.locallyPendingCreatedBranches.map((branch) => [
|
|
4176
4588
|
pendingCreatedBranchKey(branch.projectId, branch.branchName),
|
|
@@ -4230,6 +4642,12 @@ async function startWorker(options) {
|
|
|
4230
4642
|
void 0,
|
|
4231
4643
|
credentialPublicationPreauthorized
|
|
4232
4644
|
);
|
|
4645
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
4646
|
+
throw new Error("Workspace configuration was superseded by a newer server generation");
|
|
4647
|
+
}
|
|
4648
|
+
if (!workspaceConfigurationIncidentSnapshotIsCurrent(incidentDeferral.incidentId, activeWorkspaceIncidentId)) {
|
|
4649
|
+
throw new Error("Workspace configuration was superseded by a different active workspace incident");
|
|
4650
|
+
}
|
|
4233
4651
|
configuredCredentialGenerationFingerprint = incomingCredentialGenerationFingerprint;
|
|
4234
4652
|
pendingCredentialGenerationIntentAtProcessStart = null;
|
|
4235
4653
|
bootstrapCredentialGenerationFingerprintAtProcessStart = null;
|
|
@@ -4244,6 +4662,34 @@ async function startWorker(options) {
|
|
|
4244
4662
|
branches: project.branches.filter(({ branchName }) => !pendingMirrorDeletes.has(projectBranchKey(project.projectId, branchName)))
|
|
4245
4663
|
}));
|
|
4246
4664
|
const nextProjectConfigById = new Map(effectiveProjects.map((project) => [project.projectId, project]));
|
|
4665
|
+
if (incidentDeferral.incidentId) {
|
|
4666
|
+
configureExistingWorkspaceGitForRemediation({
|
|
4667
|
+
workspacePath: workspaceShadowRoot,
|
|
4668
|
+
remoteUrl: message.workspaceRemoteUrl,
|
|
4669
|
+
credentialHelper: nextWorkspaceCredentialHelper,
|
|
4670
|
+
credentialUsername: workerCredentialUsername,
|
|
4671
|
+
gitIdentity: message.gitIdentity
|
|
4672
|
+
});
|
|
4673
|
+
projectConfigById.clear();
|
|
4674
|
+
for (const project of effectiveProjects) projectConfigById.set(project.projectId, project);
|
|
4675
|
+
readyProjectIds.clear();
|
|
4676
|
+
reconciledProjectConfigFingerprints.clear();
|
|
4677
|
+
pendingCheckouts.clear();
|
|
4678
|
+
const pending2 = incidentDeferredProjectCheckouts(message.projects);
|
|
4679
|
+
for (const checkout of pending2) pendingCheckouts.set(projectBranchKey(checkout.projectId, checkout.branchName), checkout);
|
|
4680
|
+
workspaceConfigured = activeWorkspaceIncidentId === incidentDeferral.incidentId;
|
|
4681
|
+
return {
|
|
4682
|
+
result: deferredIncidentWorkspaceConfigurationResult({
|
|
4683
|
+
attemptId: crypto.randomUUID(),
|
|
4684
|
+
workerLabel: label,
|
|
4685
|
+
head: workspaceLocalHead(),
|
|
4686
|
+
skippedMountIds: buildWorkspaceMounts().map(({ id }) => id)
|
|
4687
|
+
}),
|
|
4688
|
+
pending: pending2,
|
|
4689
|
+
aheadOfOriginBranches: [],
|
|
4690
|
+
deferredWorkspaceSyncForIncidentId: incidentDeferral.incidentId
|
|
4691
|
+
};
|
|
4692
|
+
}
|
|
4247
4693
|
ensureWorkspaceGitClone({
|
|
4248
4694
|
workspacePath: workspaceShadowRoot,
|
|
4249
4695
|
remoteUrl: message.workspaceRemoteUrl,
|
|
@@ -4264,7 +4710,7 @@ async function startWorker(options) {
|
|
|
4264
4710
|
sourcePath: recoverySourceOverrides.get(mount.id) ?? mount.sourcePath,
|
|
4265
4711
|
busy: mount.busyForRecovery ?? mount.busy
|
|
4266
4712
|
}));
|
|
4267
|
-
recoverWorkspaceGitHydration(workspaceShadowRoot, recoveryMounts);
|
|
4713
|
+
recoverWorkspaceGitHydration(workspaceShadowRoot, recoveryMounts, { preserveStaleBases: true });
|
|
4268
4714
|
for (const project of message.projects) {
|
|
4269
4715
|
for (const { branchName } of project.preserveOnlyBranches) {
|
|
4270
4716
|
if (stillPendingCreatedBranches.has(pendingCreatedBranchKey(project.projectId, branchName))) {
|
|
@@ -4311,10 +4757,24 @@ async function startWorker(options) {
|
|
|
4311
4757
|
{ ignoreBusy: true }
|
|
4312
4758
|
);
|
|
4313
4759
|
ensureConfiguredProjects();
|
|
4760
|
+
const configurationSyncAdmissionGeneration = workerAdmissionGeneration;
|
|
4761
|
+
const assertConfigurationSyncStillAdmitted = () => {
|
|
4762
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration || configurationSyncAdmissionGeneration !== workerAdmissionGeneration || !workspaceConfigurationIncidentSnapshotIsCurrent(incidentDeferral.incidentId, activeWorkspaceIncidentId) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
4763
|
+
throw new Error("Workspace configuration synchronization admission changed while asynchronous work was in flight");
|
|
4764
|
+
}
|
|
4765
|
+
};
|
|
4314
4766
|
const result = await performWorkspaceSync({
|
|
4315
4767
|
attemptId: crypto.randomUUID(),
|
|
4316
|
-
trigger: { type: "connect" }
|
|
4768
|
+
trigger: { type: "connect" },
|
|
4769
|
+
assertStillAdmitted: assertConfigurationSyncStillAdmitted
|
|
4317
4770
|
});
|
|
4771
|
+
assertConfigurationSyncStillAdmitted();
|
|
4772
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
4773
|
+
throw new Error("Workspace configuration was superseded by a newer server generation");
|
|
4774
|
+
}
|
|
4775
|
+
if (!workspaceConfigurationIncidentSnapshotIsCurrent(incidentDeferral.incidentId, activeWorkspaceIncidentId)) {
|
|
4776
|
+
throw new Error("Workspace configuration was superseded by a different active workspace incident");
|
|
4777
|
+
}
|
|
4318
4778
|
const publishedHead = result.publishedHead ?? result.localHead ?? result.startingHead;
|
|
4319
4779
|
if (publishedHead && ["no_change", "published", "updated", "conflict_reset", "reset"].includes(result.outcome)) {
|
|
4320
4780
|
const activeMountIds = new Set(result.activeMountIds ?? []);
|
|
@@ -4331,6 +4791,11 @@ async function startWorker(options) {
|
|
|
4331
4791
|
});
|
|
4332
4792
|
}
|
|
4333
4793
|
}
|
|
4794
|
+
if (deferredWorkspaceConfigurationRefreshTimer) {
|
|
4795
|
+
clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
|
|
4796
|
+
deferredWorkspaceConfigurationRefreshTimer = void 0;
|
|
4797
|
+
}
|
|
4798
|
+
deferredWorkspaceConfiguration = null;
|
|
4334
4799
|
workspaceConfigured = true;
|
|
4335
4800
|
const pending = [...pendingCheckouts.values()].sort(
|
|
4336
4801
|
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
@@ -4338,6 +4803,13 @@ async function startWorker(options) {
|
|
|
4338
4803
|
return { result, pending, aheadOfOriginBranches: collectAheadOfOriginBranches() };
|
|
4339
4804
|
});
|
|
4340
4805
|
} catch (error) {
|
|
4806
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
4807
|
+
return {
|
|
4808
|
+
result: failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, error),
|
|
4809
|
+
pending: incidentDeferredProjectCheckouts(message.projects),
|
|
4810
|
+
aheadOfOriginBranches: []
|
|
4811
|
+
};
|
|
4812
|
+
}
|
|
4341
4813
|
if (error instanceof RegistryAuthConfigurationError) {
|
|
4342
4814
|
workspaceConfigured = false;
|
|
4343
4815
|
githubCredential = null;
|
|
@@ -4392,7 +4864,7 @@ async function startWorker(options) {
|
|
|
4392
4864
|
workspaceAutomaticTimer = void 0;
|
|
4393
4865
|
}
|
|
4394
4866
|
void (async () => {
|
|
4395
|
-
if (workspaceConfigured && !activeWorkspaceIncidentId) {
|
|
4867
|
+
if (workspaceConfigured && !activeWorkspaceIncidentId && !deferredWorkspaceConfiguration) {
|
|
4396
4868
|
await runWorkspaceSync({
|
|
4397
4869
|
trigger: { type: "manual", detail: "graceful worker shutdown" }
|
|
4398
4870
|
});
|
|
@@ -4440,7 +4912,10 @@ async function startWorker(options) {
|
|
|
4440
4912
|
capabilities: {
|
|
4441
4913
|
updateClis: true,
|
|
4442
4914
|
browserPortForwarding: true,
|
|
4443
|
-
execStdinV1: true
|
|
4915
|
+
execStdinV1: true,
|
|
4916
|
+
ptyEnvFilesV1: true,
|
|
4917
|
+
workspaceRemediationAncestorGuardV1: true,
|
|
4918
|
+
workspaceIncidentConfigDeferralV1: true
|
|
4444
4919
|
},
|
|
4445
4920
|
projectRoot: projectsRoot,
|
|
4446
4921
|
artifactRoot,
|
|
@@ -4476,26 +4951,35 @@ async function startWorker(options) {
|
|
|
4476
4951
|
return;
|
|
4477
4952
|
}
|
|
4478
4953
|
if (message.type === "workspace_config") {
|
|
4954
|
+
const receiptGeneration = ++workspaceConfigurationReceiptGeneration;
|
|
4955
|
+
const refreshesDeferredConfiguration = deferredWorkspaceConfiguration !== null && activeWorkspaceIncidentId === null && message.deferWorkspaceSyncForIncidentId === void 0;
|
|
4479
4956
|
advanceWorkerAdmissionGeneration();
|
|
4480
|
-
const configured = await configureWorkerWorkspace(message);
|
|
4957
|
+
const configured = await configureWorkerWorkspace(message, receiptGeneration);
|
|
4481
4958
|
sendWorkerMessageFromCurrentSource(ws, {
|
|
4482
4959
|
type: "workspace_configured",
|
|
4483
4960
|
requestId: message.requestId,
|
|
4484
4961
|
result: configured.result,
|
|
4485
4962
|
pendingCheckouts: configured.pending,
|
|
4486
|
-
aheadOfOriginBranches: configured.aheadOfOriginBranches
|
|
4963
|
+
aheadOfOriginBranches: configured.aheadOfOriginBranches,
|
|
4964
|
+
...configured.deferredWorkspaceSyncForIncidentId ? { deferredWorkspaceSyncForIncidentId: configured.deferredWorkspaceSyncForIncidentId } : {}
|
|
4487
4965
|
});
|
|
4488
4966
|
process.stdout.write(
|
|
4489
4967
|
`[r5d-worker] workspace configured: ${message.projects.length} project(s), ${configured.pending.length} pending checkout(s)
|
|
4490
4968
|
`
|
|
4491
4969
|
);
|
|
4970
|
+
if (refreshesDeferredConfiguration && configured.result.outcome === "failed" && deferredWorkspaceConfiguration !== null && currentWorkerSocket === ws) {
|
|
4971
|
+
workspaceConfigured = false;
|
|
4972
|
+
advanceWorkerAdmissionGeneration();
|
|
4973
|
+
ws.close(1012, "Deferred workspace configuration refresh failed");
|
|
4974
|
+
return;
|
|
4975
|
+
}
|
|
4492
4976
|
if (!workspacePeriodicTimer) {
|
|
4493
4977
|
workspacePeriodicTimer = setInterval(() => {
|
|
4494
4978
|
scheduleAutomaticWorkspaceSync({ type: "periodic", detail: "periodic workspace reconciliation" }, 0);
|
|
4495
4979
|
}, WORKSPACE_GIT_PERIODIC_MS);
|
|
4496
4980
|
workspacePeriodicTimer.unref();
|
|
4497
4981
|
}
|
|
4498
|
-
if (pendingAutomaticTrigger && !activeWorkspaceIncidentId) {
|
|
4982
|
+
if (pendingAutomaticTrigger && !activeWorkspaceIncidentId && !deferredWorkspaceConfiguration) {
|
|
4499
4983
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, 0);
|
|
4500
4984
|
}
|
|
4501
4985
|
return;
|
|
@@ -4507,11 +4991,20 @@ async function startWorker(options) {
|
|
|
4507
4991
|
trigger: message.trigger,
|
|
4508
4992
|
confirmedLargeDiff: message.confirmedLargeDiff,
|
|
4509
4993
|
confirmationReason: message.confirmationReason,
|
|
4510
|
-
resetToCanonical: message.resetToCanonical
|
|
4994
|
+
resetToCanonical: message.resetToCanonical,
|
|
4995
|
+
requiredAncestorHeads: message.requiredAncestorHeads
|
|
4511
4996
|
});
|
|
4512
4997
|
return;
|
|
4513
4998
|
}
|
|
4514
4999
|
if (message.type === "create_project_branch") {
|
|
5000
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5001
|
+
sendWorkerMessage(ws, {
|
|
5002
|
+
type: "operation_result",
|
|
5003
|
+
requestId: message.requestId,
|
|
5004
|
+
error: `Project operations are deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5005
|
+
});
|
|
5006
|
+
return;
|
|
5007
|
+
}
|
|
4515
5008
|
try {
|
|
4516
5009
|
const pendingBranch = {
|
|
4517
5010
|
branchId: message.branchId,
|
|
@@ -4519,6 +5012,11 @@ async function startWorker(options) {
|
|
|
4519
5012
|
branchName: message.targetBranch
|
|
4520
5013
|
};
|
|
4521
5014
|
const created = await workspaceSyncSingleFlight.runMutation(() => {
|
|
5015
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5016
|
+
throw new Error(
|
|
5017
|
+
`Project branch creation was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5018
|
+
);
|
|
5019
|
+
}
|
|
4522
5020
|
const project = projectConfigById.get(message.projectId);
|
|
4523
5021
|
if (!project) throw new Error(`Project ${message.projectId} is missing from the worker workspace configuration`);
|
|
4524
5022
|
assertRepositoryExecutionEnabled(project);
|
|
@@ -4602,9 +5100,22 @@ async function startWorker(options) {
|
|
|
4602
5100
|
return;
|
|
4603
5101
|
}
|
|
4604
5102
|
if (message.type === "delete_project_branch") {
|
|
5103
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5104
|
+
sendWorkerMessage(ws, {
|
|
5105
|
+
type: "operation_result",
|
|
5106
|
+
requestId: message.requestId,
|
|
5107
|
+
error: `Project operations are deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5108
|
+
});
|
|
5109
|
+
return;
|
|
5110
|
+
}
|
|
4605
5111
|
try {
|
|
4606
5112
|
const deletionKey = projectBranchKey(message.projectId, message.branchName);
|
|
4607
5113
|
const deletionNeedsSync = await workspaceSyncSingleFlight.runMutation(() => {
|
|
5114
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5115
|
+
throw new Error(
|
|
5116
|
+
`Project branch deletion was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5117
|
+
);
|
|
5118
|
+
}
|
|
4608
5119
|
const project = projectConfigById.get(message.projectId);
|
|
4609
5120
|
if (!project) throw new Error(`Project ${message.projectId} is missing from the worker workspace configuration`);
|
|
4610
5121
|
assertRepositoryExecutionEnabled(project);
|
|
@@ -4704,8 +5215,38 @@ async function startWorker(options) {
|
|
|
4704
5215
|
}
|
|
4705
5216
|
if (message.type === "workspace_incident_updated") {
|
|
4706
5217
|
const previousIncidentId = activeWorkspaceIncidentId;
|
|
4707
|
-
|
|
5218
|
+
const nextIncidentId = applyWorkspaceIncidentUpdate(activeWorkspaceIncidentId, message);
|
|
5219
|
+
if (nextIncidentId !== previousIncidentId) advanceWorkerAdmissionGeneration();
|
|
5220
|
+
activeWorkspaceIncidentId = nextIncidentId;
|
|
4708
5221
|
if (previousIncidentId && !activeWorkspaceIncidentId && (message.status === "resolved" || message.status === "confirmed" || message.status === "reset")) {
|
|
5222
|
+
const disposition = workspaceIncidentTerminalClearDisposition({
|
|
5223
|
+
deferredConfiguration: deferredWorkspaceConfiguration,
|
|
5224
|
+
clearedIncidentId: previousIncidentId
|
|
5225
|
+
});
|
|
5226
|
+
if (disposition !== "ordinary_resume") {
|
|
5227
|
+
pendingAutomaticTrigger ??= { type: "periodic", detail: "resume after workspace incident" };
|
|
5228
|
+
workspaceConfigured = false;
|
|
5229
|
+
advanceWorkerAdmissionGeneration();
|
|
5230
|
+
if (disposition === "reconnect_for_refresh") {
|
|
5231
|
+
ws.close(1012, "Refreshing deferred workspace configuration");
|
|
5232
|
+
} else {
|
|
5233
|
+
if (deferredWorkspaceConfigurationRefreshTimer) clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
|
|
5234
|
+
const deferredIncidentId = deferredWorkspaceConfiguration?.incidentId;
|
|
5235
|
+
deferredWorkspaceConfigurationRefreshTimer = setTimeout(() => {
|
|
5236
|
+
deferredWorkspaceConfigurationRefreshTimer = void 0;
|
|
5237
|
+
if (!deferredIncidentId || !deferredWorkspaceRefreshWatchdogIsCurrent({
|
|
5238
|
+
capturedIncidentId: deferredIncidentId,
|
|
5239
|
+
deferredConfiguration: deferredWorkspaceConfiguration,
|
|
5240
|
+
activeIncidentId: activeWorkspaceIncidentId
|
|
5241
|
+
}) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
5242
|
+
return;
|
|
5243
|
+
}
|
|
5244
|
+
ws.close(1012, "Timed out waiting for deferred workspace configuration refresh");
|
|
5245
|
+
}, WORKSPACE_INCIDENT_CONFIG_REFRESH_TIMEOUT_MS);
|
|
5246
|
+
deferredWorkspaceConfigurationRefreshTimer.unref();
|
|
5247
|
+
}
|
|
5248
|
+
return;
|
|
5249
|
+
}
|
|
4709
5250
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger ?? { type: "periodic", detail: "resume after workspace incident" }, 0);
|
|
4710
5251
|
}
|
|
4711
5252
|
return;
|
|
@@ -4809,12 +5350,21 @@ async function startWorker(options) {
|
|
|
4809
5350
|
sendAck({ error: `Process run ${message.runId} is not active on this worker` });
|
|
4810
5351
|
return;
|
|
4811
5352
|
}
|
|
5353
|
+
if (!deferredWorkspaceTargetIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, active.target)) {
|
|
5354
|
+
sendAck({
|
|
5355
|
+
error: `Process input is deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
|
|
5356
|
+
});
|
|
5357
|
+
return;
|
|
5358
|
+
}
|
|
4812
5359
|
if (!active.interactive || !active.stdin) {
|
|
4813
5360
|
sendAck({
|
|
4814
5361
|
error: `Process run ${message.runId} has no open stdin. Start a new shell command with "interactive": true to write to its stdin.`
|
|
4815
5362
|
});
|
|
4816
5363
|
return;
|
|
4817
5364
|
}
|
|
5365
|
+
if (active.workspaceEffect !== "none" && targetMayMutateVisibleWorkspace(active.target)) {
|
|
5366
|
+
recordVisibleWorkspaceMutation(active.target);
|
|
5367
|
+
}
|
|
4818
5368
|
try {
|
|
4819
5369
|
let bytesWritten = 0;
|
|
4820
5370
|
if (message.data !== void 0 && message.data.length > 0) {
|
|
@@ -4825,8 +5375,8 @@ async function startWorker(options) {
|
|
|
4825
5375
|
await active.stdin.end();
|
|
4826
5376
|
active.stdin = void 0;
|
|
4827
5377
|
}
|
|
4828
|
-
if (targetMayMutateVisibleWorkspace(active.target)) {
|
|
4829
|
-
markWorkspaceDirty({ type: "shell_inline", detail: `exec stdin ${message.runId}` });
|
|
5378
|
+
if (active.workspaceEffect !== "none" && targetMayMutateVisibleWorkspace(active.target)) {
|
|
5379
|
+
markWorkspaceDirty({ type: "shell_inline", detail: `exec stdin ${message.runId}` }, false, active.target);
|
|
4830
5380
|
}
|
|
4831
5381
|
sendAck({
|
|
4832
5382
|
result: {
|
|
@@ -4851,13 +5401,28 @@ async function startWorker(options) {
|
|
|
4851
5401
|
});
|
|
4852
5402
|
return;
|
|
4853
5403
|
}
|
|
5404
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5405
|
+
sendWorkerMessage(ws, {
|
|
5406
|
+
type: "pty_error",
|
|
5407
|
+
requestId: message.requestId,
|
|
5408
|
+
ptyId: message.ptyId,
|
|
5409
|
+
error: `Shells are deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5410
|
+
});
|
|
5411
|
+
return;
|
|
5412
|
+
}
|
|
4854
5413
|
await reserveWorkspaceCommandAfterCurrentSync(message.target, workspaceSyncSingleFlight, () => {
|
|
4855
5414
|
workspaceSyncPriorityPtyTargets.set(message.ptyId, message.target);
|
|
5415
|
+
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
4856
5416
|
});
|
|
4857
5417
|
let releaseWorkspaceMutation;
|
|
4858
5418
|
let mutationLeaseTransferred = false;
|
|
4859
5419
|
try {
|
|
4860
5420
|
releaseWorkspaceMutation = await acquireWorkspaceCommandMutation(message.target, workspaceSyncSingleFlight);
|
|
5421
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5422
|
+
throw new Error(
|
|
5423
|
+
`Shell opening was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5424
|
+
);
|
|
5425
|
+
}
|
|
4861
5426
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
4862
5427
|
process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${describeWorkerSessionTarget(message.target)}
|
|
4863
5428
|
`);
|
|
@@ -4870,7 +5435,7 @@ async function startWorker(options) {
|
|
|
4870
5435
|
...releaseWorkspaceMutation ? { releaseWorkspaceMutation } : {},
|
|
4871
5436
|
...targetMayMutateVisibleWorkspace(message.target) ? {
|
|
4872
5437
|
onTerminal: () => {
|
|
4873
|
-
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} completed` }, true);
|
|
5438
|
+
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} completed` }, true, message.target);
|
|
4874
5439
|
}
|
|
4875
5440
|
} : {}
|
|
4876
5441
|
});
|
|
@@ -4890,8 +5455,16 @@ async function startWorker(options) {
|
|
|
4890
5455
|
}
|
|
4891
5456
|
if (message.type === "pty_input") {
|
|
4892
5457
|
const activePty = activePtys.get(message.ptyId);
|
|
5458
|
+
if (activePty && workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5459
|
+
sendWorkerMessage(ws, {
|
|
5460
|
+
type: "pty_error",
|
|
5461
|
+
ptyId: message.ptyId,
|
|
5462
|
+
error: `Shell input is deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
|
|
5463
|
+
});
|
|
5464
|
+
return;
|
|
5465
|
+
}
|
|
4893
5466
|
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
4894
|
-
markWorkspaceDirty({ type: "shell_inline", detail: `pty ${message.ptyId}` });
|
|
5467
|
+
markWorkspaceDirty({ type: "shell_inline", detail: `pty ${message.ptyId}` }, false, activePty.target);
|
|
4895
5468
|
}
|
|
4896
5469
|
writePty(ws, message);
|
|
4897
5470
|
return;
|
|
@@ -4904,7 +5477,7 @@ async function startWorker(options) {
|
|
|
4904
5477
|
const activePty = activePtys.get(message.ptyId);
|
|
4905
5478
|
closePty(message);
|
|
4906
5479
|
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
4907
|
-
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} closed` }, true);
|
|
5480
|
+
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} closed` }, true, activePty.target);
|
|
4908
5481
|
}
|
|
4909
5482
|
return;
|
|
4910
5483
|
}
|
|
@@ -4919,14 +5492,34 @@ async function startWorker(options) {
|
|
|
4919
5492
|
});
|
|
4920
5493
|
return;
|
|
4921
5494
|
}
|
|
5495
|
+
if (!workspaceCommandTransportIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, "exec")) {
|
|
5496
|
+
sendWorkerMessage(ws, {
|
|
5497
|
+
type: "exec_result",
|
|
5498
|
+
requestId: message.requestId,
|
|
5499
|
+
stdout: "",
|
|
5500
|
+
stderr: "",
|
|
5501
|
+
exitCode: 1,
|
|
5502
|
+
error: `One-shot commands are deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5503
|
+
});
|
|
5504
|
+
return;
|
|
5505
|
+
}
|
|
4922
5506
|
let result;
|
|
4923
5507
|
let targetReserved = false;
|
|
5508
|
+
const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
|
|
4924
5509
|
try {
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
5510
|
+
if (hasWorkspaceEffect) {
|
|
5511
|
+
await reserveWorkspaceCommandAfterCurrentSync(message.target, workspaceSyncSingleFlight, () => {
|
|
5512
|
+
workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
|
|
5513
|
+
targetReserved = true;
|
|
5514
|
+
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
5515
|
+
});
|
|
5516
|
+
}
|
|
4929
5517
|
const runCommand = async () => {
|
|
5518
|
+
if (!workspaceCommandTransportIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, "exec")) {
|
|
5519
|
+
throw new Error(
|
|
5520
|
+
`One-shot command was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5521
|
+
);
|
|
5522
|
+
}
|
|
4930
5523
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
4931
5524
|
process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
|
|
4932
5525
|
`);
|
|
@@ -4937,7 +5530,14 @@ async function startWorker(options) {
|
|
|
4937
5530
|
token,
|
|
4938
5531
|
artifactRoot,
|
|
4939
5532
|
planRoot,
|
|
4940
|
-
assertAdmission:
|
|
5533
|
+
assertAdmission: () => {
|
|
5534
|
+
if (!workspaceCommandTransportIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, "exec")) {
|
|
5535
|
+
throw new Error(
|
|
5536
|
+
`One-shot command was fenced before spawn by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5537
|
+
);
|
|
5538
|
+
}
|
|
5539
|
+
assertMessageAdmission();
|
|
5540
|
+
}
|
|
4941
5541
|
});
|
|
4942
5542
|
};
|
|
4943
5543
|
result = await runWorkspaceCommand(message.target, workspaceSyncSingleFlight, runCommand);
|
|
@@ -4955,10 +5555,11 @@ async function startWorker(options) {
|
|
|
4955
5555
|
if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
|
|
4956
5556
|
}
|
|
4957
5557
|
ws.send(JSON.stringify(result));
|
|
4958
|
-
if (targetMayMutateVisibleWorkspace(message.target)) {
|
|
5558
|
+
if (hasWorkspaceEffect && targetMayMutateVisibleWorkspace(message.target)) {
|
|
4959
5559
|
markWorkspaceDirty(
|
|
4960
5560
|
{ type: "shell_inline", sessionId: message.sessionId, processRunId: message.runId, detail: "foreground command completed" },
|
|
4961
|
-
true
|
|
5561
|
+
true,
|
|
5562
|
+
message.target
|
|
4962
5563
|
);
|
|
4963
5564
|
}
|
|
4964
5565
|
return;
|
|
@@ -4978,6 +5579,7 @@ async function startWorker(options) {
|
|
|
4978
5579
|
requestId: message.requestId,
|
|
4979
5580
|
runId: message.runId
|
|
4980
5581
|
});
|
|
5582
|
+
const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
|
|
4981
5583
|
const runCommand = async () => {
|
|
4982
5584
|
try {
|
|
4983
5585
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
@@ -4994,7 +5596,7 @@ async function startWorker(options) {
|
|
|
4994
5596
|
assertAdmission: assertMessageAdmission
|
|
4995
5597
|
});
|
|
4996
5598
|
} finally {
|
|
4997
|
-
if (targetMayMutateVisibleWorkspace(message.target)) {
|
|
5599
|
+
if (hasWorkspaceEffect && targetMayMutateVisibleWorkspace(message.target)) {
|
|
4998
5600
|
markWorkspaceDirty(
|
|
4999
5601
|
{
|
|
5000
5602
|
type: "process_terminal",
|
|
@@ -5002,7 +5604,8 @@ async function startWorker(options) {
|
|
|
5002
5604
|
processRunId: message.runId,
|
|
5003
5605
|
detail: "process completed"
|
|
5004
5606
|
},
|
|
5005
|
-
true
|
|
5607
|
+
true,
|
|
5608
|
+
message.target
|
|
5006
5609
|
);
|
|
5007
5610
|
}
|
|
5008
5611
|
}
|
|
@@ -5010,10 +5613,13 @@ async function startWorker(options) {
|
|
|
5010
5613
|
const execution = (async () => {
|
|
5011
5614
|
let targetReserved = false;
|
|
5012
5615
|
try {
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5616
|
+
if (hasWorkspaceEffect) {
|
|
5617
|
+
await reserveWorkspaceCommandAfterCurrentSync(message.target, workspaceSyncSingleFlight, () => {
|
|
5618
|
+
workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
|
|
5619
|
+
targetReserved = true;
|
|
5620
|
+
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
5621
|
+
});
|
|
5622
|
+
}
|
|
5017
5623
|
await runWorkspaceCommand(message.target, workspaceSyncSingleFlight, runCommand);
|
|
5018
5624
|
} finally {
|
|
5019
5625
|
if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
|
|
@@ -5031,11 +5637,20 @@ async function startWorker(options) {
|
|
|
5031
5637
|
return;
|
|
5032
5638
|
}
|
|
5033
5639
|
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") {
|
|
5640
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5641
|
+
sendWorkerMessage(ws, {
|
|
5642
|
+
type: "operation_result",
|
|
5643
|
+
requestId: message.requestId,
|
|
5644
|
+
error: `Workspace operations are deferred for incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}; use a canonical remediation shell`
|
|
5645
|
+
});
|
|
5646
|
+
return;
|
|
5647
|
+
}
|
|
5034
5648
|
const reservesVisibleWorkspace = targetMayMutateVisibleWorkspace(message.target);
|
|
5035
5649
|
const mutatesVisibleWorkspace = (message.type === "write" || message.type === "edit") && targetMayMutateVisibleWorkspace(message.target);
|
|
5036
5650
|
if (reservesVisibleWorkspace) {
|
|
5037
5651
|
await reserveWorkspaceCommandAfterCurrentSync(message.target, workspaceSyncSingleFlight, () => {
|
|
5038
5652
|
workspaceSyncPriorityOperationTargets.set(message.requestId, message.target);
|
|
5653
|
+
if (mutatesVisibleWorkspace) recordVisibleWorkspaceMutation(message.target);
|
|
5039
5654
|
});
|
|
5040
5655
|
}
|
|
5041
5656
|
let dirtyTrigger;
|
|
@@ -5048,7 +5663,8 @@ async function startWorker(options) {
|
|
|
5048
5663
|
baseUrl,
|
|
5049
5664
|
token,
|
|
5050
5665
|
artifactRoot,
|
|
5051
|
-
planRoot
|
|
5666
|
+
planRoot,
|
|
5667
|
+
assertAdmission: assertMessageAdmission
|
|
5052
5668
|
});
|
|
5053
5669
|
});
|
|
5054
5670
|
ws.send(
|
|
@@ -5065,7 +5681,7 @@ async function startWorker(options) {
|
|
|
5065
5681
|
toolCallId: message.requestId,
|
|
5066
5682
|
...message.target.type === "project" ? { projectId: message.target.projectId, branchName: message.target.branchName } : {}
|
|
5067
5683
|
};
|
|
5068
|
-
markWorkspaceDirty(dirtyTrigger);
|
|
5684
|
+
markWorkspaceDirty(dirtyTrigger, false, message.target);
|
|
5069
5685
|
}
|
|
5070
5686
|
} catch (error) {
|
|
5071
5687
|
ws.send(
|
|
@@ -5078,7 +5694,7 @@ async function startWorker(options) {
|
|
|
5078
5694
|
} finally {
|
|
5079
5695
|
if (reservesVisibleWorkspace) {
|
|
5080
5696
|
workspaceSyncPriorityOperationTargets.delete(message.requestId);
|
|
5081
|
-
if (pendingAutomaticTrigger && !activeWorkspaceIncidentId) {
|
|
5697
|
+
if (pendingAutomaticTrigger && !activeWorkspaceIncidentId && !deferredWorkspaceConfiguration) {
|
|
5082
5698
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, dirtyTrigger ? WORKSPACE_GIT_QUIET_MS : 0);
|
|
5083
5699
|
}
|
|
5084
5700
|
}
|
|
@@ -5114,6 +5730,10 @@ async function startWorker(options) {
|
|
|
5114
5730
|
clearInterval(workspacePeriodicTimer);
|
|
5115
5731
|
workspacePeriodicTimer = void 0;
|
|
5116
5732
|
}
|
|
5733
|
+
if (deferredWorkspaceConfigurationRefreshTimer) {
|
|
5734
|
+
clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
|
|
5735
|
+
deferredWorkspaceConfigurationRefreshTimer = void 0;
|
|
5736
|
+
}
|
|
5117
5737
|
if (terminalReplayTimer) {
|
|
5118
5738
|
clearInterval(terminalReplayTimer);
|
|
5119
5739
|
terminalReplayTimer = void 0;
|
|
@@ -5225,5 +5845,7 @@ export {
|
|
|
5225
5845
|
syncSessionArtifacts,
|
|
5226
5846
|
workerChildProcessEnvironment,
|
|
5227
5847
|
workerGitSecurityTestHarness,
|
|
5848
|
+
workerPtyBridgeTestHarness,
|
|
5849
|
+
workerPtyTestHarness,
|
|
5228
5850
|
writeWorkerTextFile
|
|
5229
5851
|
};
|