@ricsam/r5d-worker 0.0.58 → 0.0.60
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/README.md +1 -1
- package/dist/cjs/main.cjs +569 -191
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-convergence.cjs +256 -0
- package/dist/cjs/workspace-incident-state.cjs +32 -3
- package/dist/cjs/workspace-manifest-admission.cjs +64 -0
- package/dist/cjs/workspace-sync.cjs +129 -16
- package/dist/mjs/main.mjs +579 -197
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-convergence.mjs +213 -0
- package/dist/mjs/workspace-incident-state.mjs +31 -3
- package/dist/mjs/workspace-manifest-admission.mjs +30 -0
- package/dist/mjs/workspace-sync.mjs +126 -15
- package/dist/types/workspace-convergence.d.ts +82 -0
- package/dist/types/workspace-incident-state.d.ts +15 -1
- package/dist/types/workspace-manifest-admission.d.ts +24 -0
- package/dist/types/workspace-sync.d.ts +23 -6
- package/package.json +1 -1
- package/dist/cjs/workspace-sync-fast-path.cjs +0 -108
- package/dist/mjs/workspace-sync-fast-path.mjs +0 -81
- package/dist/types/workspace-sync-fast-path.d.ts +0 -69
package/dist/cjs/main.cjs
CHANGED
|
@@ -65,14 +65,12 @@ var import_process_tree = require("./process-tree.cjs");
|
|
|
65
65
|
var import_port_forward_client = require("./port-forward-client.cjs");
|
|
66
66
|
var import_workspace_incident_state = require("./workspace-incident-state.cjs");
|
|
67
67
|
var import_workspace_command_sync_policy = require("./workspace-command-sync-policy.cjs");
|
|
68
|
-
var
|
|
68
|
+
var import_workspace_convergence = require("./workspace-convergence.cjs");
|
|
69
|
+
var import_workspace_manifest_admission = require("./workspace-manifest-admission.cjs");
|
|
69
70
|
var import_supervisor = require("./supervisor.cjs");
|
|
70
71
|
var import_managed_paths = require("./managed-paths.cjs");
|
|
71
72
|
var import_workspace_sync = require("./workspace-sync.cjs");
|
|
72
73
|
const import_meta = {};
|
|
73
|
-
function workspaceSyncFastPathTelemetry(decision) {
|
|
74
|
-
return decision.hit ? { hit: true, reason: decision.reason, proofAgeMs: decision.proofAgeMs } : { hit: false, reason: decision.reason };
|
|
75
|
-
}
|
|
76
74
|
class WorkerServerUnavailableError extends Error {
|
|
77
75
|
name = "WorkerServerUnavailableError";
|
|
78
76
|
}
|
|
@@ -809,25 +807,13 @@ function fallbackInternalRemoteUrl(baseUrl, projectId) {
|
|
|
809
807
|
}
|
|
810
808
|
function visibleCheckoutRemoteFor(input) {
|
|
811
809
|
const canonicalCheckout = input.manifest?.canonicalCheckouts?.find((checkout) => checkout.branchName === input.branchName);
|
|
812
|
-
const isCanonicalManifestBranch = Boolean(canonicalCheckout);
|
|
813
|
-
const usesCanonicalRemote = isCanonicalManifestBranch || !input.manifest?.repoHttpUrl;
|
|
814
|
-
if (usesCanonicalRemote) {
|
|
815
|
-
return {
|
|
816
|
-
remoteUrl: fallbackInternalRemoteUrl(input.baseUrl, input.projectId),
|
|
817
|
-
authHeader: `Authorization: Bearer ${input.token}`,
|
|
818
|
-
persistAuth: false,
|
|
819
|
-
reconcileExistingOrigin: isCanonicalManifestBranch,
|
|
820
|
-
requireRemoteBranch: isCanonicalManifestBranch,
|
|
821
|
-
requiredBaseCommit: canonicalCheckout?.scaffoldCommitHash ?? null
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
810
|
return {
|
|
825
|
-
remoteUrl:
|
|
826
|
-
authHeader: input.
|
|
827
|
-
persistAuth:
|
|
828
|
-
reconcileExistingOrigin:
|
|
829
|
-
requireRemoteBranch:
|
|
830
|
-
requiredBaseCommit: null
|
|
811
|
+
remoteUrl: fallbackInternalRemoteUrl(input.baseUrl, input.projectId),
|
|
812
|
+
authHeader: `Authorization: Bearer ${input.token}`,
|
|
813
|
+
persistAuth: false,
|
|
814
|
+
reconcileExistingOrigin: true,
|
|
815
|
+
requireRemoteBranch: true,
|
|
816
|
+
requiredBaseCommit: canonicalCheckout?.scaffoldCommitHash ?? null
|
|
831
817
|
};
|
|
832
818
|
}
|
|
833
819
|
function gitExtraHeaderUrlForRemote(remoteUrl) {
|
|
@@ -1048,9 +1034,11 @@ function checkoutVisibleBranch(input) {
|
|
|
1048
1034
|
}
|
|
1049
1035
|
function migrateExistingCheckoutToRequiredRemote(input) {
|
|
1050
1036
|
if (originRemoteUrl(input.branchPath) === input.remoteUrl) return;
|
|
1051
|
-
|
|
1037
|
+
const dirtyWorkingTree = hasWorktreeChanges(input.branchPath);
|
|
1038
|
+
if (dirtyWorkingTree && input.requiredBaseCommit) {
|
|
1052
1039
|
throw new Error(`Cannot migrate dirty checkout for ${input.branchName} to its canonical remote`);
|
|
1053
1040
|
}
|
|
1041
|
+
const preserveWorkingTree = dirtyWorkingTree;
|
|
1054
1042
|
runGit(
|
|
1055
1043
|
visibleCheckoutFetchArgs("--no-tags", input.remoteUrl, `+refs/heads/${input.branchName}:refs/remotes/origin/${input.branchName}`),
|
|
1056
1044
|
{
|
|
@@ -1065,11 +1053,12 @@ function migrateExistingCheckoutToRequiredRemote(input) {
|
|
|
1065
1053
|
if (input.requiredBaseCommit && (!tryGit(["cat-file", "-e", `${input.requiredBaseCommit}^{commit}`], { cwd: input.branchPath }) || !tryGit(["merge-base", "--is-ancestor", input.requiredBaseCommit, canonicalRemoteRef], { cwd: input.branchPath }))) {
|
|
1066
1054
|
throw new Error(`Canonical branch ${input.branchName} is not derived from required scaffold ${input.requiredBaseCommit}`);
|
|
1067
1055
|
}
|
|
1056
|
+
ensureOriginRemote(input.branchPath, input.remoteUrl);
|
|
1057
|
+
if (preserveWorkingTree) return;
|
|
1068
1058
|
if (tryGit(["rev-parse", "--verify", "HEAD"], { cwd: input.branchPath })) {
|
|
1069
1059
|
const previousHead = runGit(["rev-parse", "HEAD"], { cwd: input.branchPath });
|
|
1070
1060
|
runGit(["update-ref", `refs/r5d/pre-canonical-checkout/${previousHead}`, previousHead], { cwd: input.branchPath });
|
|
1071
1061
|
}
|
|
1072
|
-
ensureOriginRemote(input.branchPath, input.remoteUrl);
|
|
1073
1062
|
runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.branchName}`], {
|
|
1074
1063
|
cwd: input.branchPath
|
|
1075
1064
|
});
|
|
@@ -1162,7 +1151,7 @@ function ensureVisibleGitCheckout(input) {
|
|
|
1162
1151
|
(0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
|
|
1163
1152
|
return branchPath;
|
|
1164
1153
|
}
|
|
1165
|
-
function
|
|
1154
|
+
function ensureBranchWorktree(input) {
|
|
1166
1155
|
const defaultBranch = input.manifest?.defaultBranch || "main";
|
|
1167
1156
|
const remote = visibleCheckoutRemoteFor(input);
|
|
1168
1157
|
return {
|
|
@@ -1195,7 +1184,7 @@ function resolveWorkerSessionTarget(input) {
|
|
|
1195
1184
|
}
|
|
1196
1185
|
const manifest = input.manifestByProjectId.get(input.target.projectId);
|
|
1197
1186
|
const projectRoot = projectRootFor(input.projectsRoot, input.target.projectId, input.manifestByProjectId);
|
|
1198
|
-
const
|
|
1187
|
+
const worktree = ensureBranchWorktree({
|
|
1199
1188
|
projectId: input.target.projectId,
|
|
1200
1189
|
baseUrl: input.baseUrl,
|
|
1201
1190
|
token: input.token,
|
|
@@ -1204,7 +1193,7 @@ function resolveWorkerSessionTarget(input) {
|
|
|
1204
1193
|
branchName: input.target.branchName,
|
|
1205
1194
|
manifest
|
|
1206
1195
|
});
|
|
1207
|
-
return { target: input.target, rootPath:
|
|
1196
|
+
return { target: input.target, rootPath: worktree.branchPath, manifest };
|
|
1208
1197
|
}
|
|
1209
1198
|
function workspaceSyncCheckoutTargets(input) {
|
|
1210
1199
|
const selected = /* @__PURE__ */ new Map();
|
|
@@ -2302,6 +2291,7 @@ async function openPty(input) {
|
|
|
2302
2291
|
onExit: (event) => {
|
|
2303
2292
|
input.releaseWorkspaceMutation?.();
|
|
2304
2293
|
activePtys.delete(input.message.ptyId);
|
|
2294
|
+
input.onTerminal?.();
|
|
2305
2295
|
sendWorkerMessage(input.ws, {
|
|
2306
2296
|
type: "pty_exit",
|
|
2307
2297
|
ptyId: input.message.ptyId,
|
|
@@ -2312,6 +2302,7 @@ async function openPty(input) {
|
|
|
2312
2302
|
onError: (error) => {
|
|
2313
2303
|
input.releaseWorkspaceMutation?.();
|
|
2314
2304
|
activePtys.delete(input.message.ptyId);
|
|
2305
|
+
input.onTerminal?.();
|
|
2315
2306
|
sendWorkerMessage(input.ws, {
|
|
2316
2307
|
type: "pty_error",
|
|
2317
2308
|
requestId: input.message.requestId,
|
|
@@ -2403,15 +2394,25 @@ async function startWorker(options) {
|
|
|
2403
2394
|
const pendingManifestCheckouts = /* @__PURE__ */ new Map();
|
|
2404
2395
|
let workspaceRemoteUrl = null;
|
|
2405
2396
|
let activeWorkspaceIncidentId = null;
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2397
|
+
const workspaceIncidentOrdering = new import_workspace_incident_state.WorkspaceIncidentOrderingFence();
|
|
2398
|
+
const workspaceConvergence = (0, import_workspace_convergence.processWorkspaceConvergenceState)(import_node_path.default.join(syncRoot, "workspace-convergence.json"));
|
|
2399
|
+
const workspaceManifestRevision = new import_workspace_convergence.WorkspaceManifestRevisionFence();
|
|
2400
|
+
let workspaceReady = false;
|
|
2401
|
+
let workspacePublicationTimer;
|
|
2402
|
+
let workspaceSafetyScan;
|
|
2403
|
+
let workspaceManifestHydrationTimer;
|
|
2404
|
+
let workspaceManifestHydrationInFlight = false;
|
|
2405
|
+
let workspaceSafetyScanInFlight = false;
|
|
2406
|
+
let workspaceHeadConvergenceInFlight = false;
|
|
2407
|
+
let pendingWorkspaceHead;
|
|
2408
|
+
let pendingPublication;
|
|
2409
|
+
const explicitWorkspacePublications = new import_workspace_convergence.ExplicitWorkspacePublicationQueue();
|
|
2409
2410
|
let terminalReplayTimer;
|
|
2410
2411
|
let workspaceSyncRequestsInFlight = 0;
|
|
2411
2412
|
let sessionArtifactSyncRequestsInFlight = 0;
|
|
2412
2413
|
let cliUpdateInProgress = false;
|
|
2413
2414
|
let reloadAfterClose = false;
|
|
2414
|
-
|
|
2415
|
+
let shutdownAfterClose = false;
|
|
2415
2416
|
const workspaceSyncInput = (trigger, overrides = {}) => {
|
|
2416
2417
|
if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
|
|
2417
2418
|
const projects = (0, import_workspace_sync.workspaceProjectsForSync)([...manifestByProjectId.values()], trigger);
|
|
@@ -2437,21 +2438,160 @@ async function startWorker(options) {
|
|
|
2437
2438
|
);
|
|
2438
2439
|
}
|
|
2439
2440
|
};
|
|
2440
|
-
const
|
|
2441
|
+
const sendWorkspaceReady = () => {
|
|
2442
|
+
const manifestRevision = workspaceManifestRevision.readyRevision();
|
|
2443
|
+
if (!workspaceReady || !manifestRevision) return;
|
|
2441
2444
|
try {
|
|
2442
2445
|
ws.send(
|
|
2443
2446
|
JSON.stringify({
|
|
2444
|
-
type: "
|
|
2445
|
-
|
|
2447
|
+
type: "workspace_ready",
|
|
2448
|
+
manifestRevision,
|
|
2449
|
+
pendingCheckouts: [...pendingManifestCheckouts.values()].sort(
|
|
2450
|
+
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
2451
|
+
),
|
|
2452
|
+
...workspaceConvergence.snapshot()
|
|
2453
|
+
})
|
|
2454
|
+
);
|
|
2455
|
+
} catch (error) {
|
|
2456
|
+
process.stderr.write(`[r5d-worker] failed to send workspace readiness: ${error instanceof Error ? error.message : String(error)}
|
|
2457
|
+
`);
|
|
2458
|
+
}
|
|
2459
|
+
};
|
|
2460
|
+
const targetMayMutateVisibleWorkspace = (target) => target.type === "project" || target.rootProfile === "visible_projects";
|
|
2461
|
+
const armWorkspacePublication = (trigger, options2 = { delayMs: import_workspace_convergence.WORKSPACE_PUBLICATION_QUIET_MS }) => {
|
|
2462
|
+
workspaceConvergence.schedule();
|
|
2463
|
+
if (currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) return;
|
|
2464
|
+
sendWorkspaceReady();
|
|
2465
|
+
if (options2.requestId && !explicitWorkspacePublications.schedule({ requestId: options2.requestId, trigger }, pendingPublication?.requestId)) {
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
if (!options2.requestId && explicitWorkspacePublications.hasScheduled()) return;
|
|
2469
|
+
if (workspacePublicationTimer) clearTimeout(workspacePublicationTimer);
|
|
2470
|
+
workspacePublicationTimer = setTimeout(
|
|
2471
|
+
() => {
|
|
2472
|
+
workspacePublicationTimer = void 0;
|
|
2473
|
+
if (options2.requestId) explicitWorkspacePublications.consumeScheduled(options2.requestId);
|
|
2474
|
+
if (currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) return;
|
|
2475
|
+
if (!workspaceReady || !workspaceRemoteUrl) {
|
|
2476
|
+
armWorkspacePublication(trigger, { delayMs: 1e3, ...options2.requestId ? { requestId: options2.requestId } : {} });
|
|
2477
|
+
return;
|
|
2478
|
+
}
|
|
2479
|
+
if (activeWorkspaceIncidentId) {
|
|
2480
|
+
workspaceConvergence.block();
|
|
2481
|
+
sendWorkspaceReady();
|
|
2482
|
+
if ((0, import_workspace_convergence.workspacePublicationIncidentAction)(options2.requestId) === "defer_automatic") return;
|
|
2483
|
+
if (pendingPublication) {
|
|
2484
|
+
explicitWorkspacePublications.enqueue({ requestId: options2.requestId, trigger }, pendingPublication.requestId);
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
const snapshot2 = workspaceConvergence.snapshot();
|
|
2488
|
+
pendingPublication = {
|
|
2489
|
+
requestId: options2.requestId,
|
|
2490
|
+
trigger,
|
|
2491
|
+
dirtyGeneration: snapshot2.dirtyGeneration,
|
|
2492
|
+
explicit: true,
|
|
2493
|
+
workInFlight: false,
|
|
2494
|
+
requestedAt: Date.now()
|
|
2495
|
+
};
|
|
2496
|
+
try {
|
|
2497
|
+
ws.send(
|
|
2498
|
+
JSON.stringify({
|
|
2499
|
+
type: "workspace_publication_request",
|
|
2500
|
+
requestId: options2.requestId,
|
|
2501
|
+
trigger,
|
|
2502
|
+
dirtyGeneration: snapshot2.dirtyGeneration,
|
|
2503
|
+
localHead: snapshot2.localHead,
|
|
2504
|
+
desiredCanonicalHead: snapshot2.desiredCanonicalHead
|
|
2505
|
+
})
|
|
2506
|
+
);
|
|
2507
|
+
} catch (error) {
|
|
2508
|
+
pendingPublication = void 0;
|
|
2509
|
+
workspaceConvergence.defer();
|
|
2510
|
+
process.stderr.write(
|
|
2511
|
+
`[r5d-worker] failed to request blocked workspace publication authority: ${error instanceof Error ? error.message : String(error)}
|
|
2512
|
+
`
|
|
2513
|
+
);
|
|
2514
|
+
armWorkspacePublication(trigger, { delayMs: 1e3, requestId: options2.requestId });
|
|
2515
|
+
}
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
if (pendingPublication) {
|
|
2519
|
+
armWorkspacePublication(trigger, { delayMs: 1e3, ...options2.requestId ? { requestId: options2.requestId } : {} });
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
const snapshot = workspaceConvergence.snapshot();
|
|
2523
|
+
const requestId = options2.requestId ?? crypto.randomUUID();
|
|
2524
|
+
pendingPublication = {
|
|
2525
|
+
requestId,
|
|
2446
2526
|
trigger,
|
|
2447
|
-
|
|
2527
|
+
dirtyGeneration: snapshot.dirtyGeneration,
|
|
2528
|
+
explicit: options2.requestId !== void 0,
|
|
2529
|
+
workInFlight: false,
|
|
2530
|
+
requestedAt: Date.now()
|
|
2531
|
+
};
|
|
2532
|
+
try {
|
|
2533
|
+
ws.send(
|
|
2534
|
+
JSON.stringify({
|
|
2535
|
+
type: "workspace_publication_request",
|
|
2536
|
+
requestId,
|
|
2537
|
+
trigger,
|
|
2538
|
+
dirtyGeneration: snapshot.dirtyGeneration,
|
|
2539
|
+
localHead: snapshot.localHead,
|
|
2540
|
+
desiredCanonicalHead: snapshot.desiredCanonicalHead
|
|
2541
|
+
})
|
|
2542
|
+
);
|
|
2543
|
+
} catch (error) {
|
|
2544
|
+
pendingPublication = void 0;
|
|
2545
|
+
workspaceConvergence.defer();
|
|
2546
|
+
process.stderr.write(
|
|
2547
|
+
`[r5d-worker] failed to request workspace publication: ${error instanceof Error ? error.message : String(error)}
|
|
2548
|
+
`
|
|
2549
|
+
);
|
|
2550
|
+
armWorkspacePublication(trigger, { delayMs: 1e3, ...options2.requestId ? { requestId: options2.requestId } : {} });
|
|
2551
|
+
}
|
|
2552
|
+
},
|
|
2553
|
+
Math.max(0, options2.delayMs)
|
|
2554
|
+
);
|
|
2555
|
+
workspacePublicationTimer.unref();
|
|
2556
|
+
};
|
|
2557
|
+
const markWorkspaceDirty = (trigger, force = false) => {
|
|
2558
|
+
workspaceConvergence.markDirty();
|
|
2559
|
+
armWorkspacePublication(trigger, { delayMs: force ? 0 : import_workspace_convergence.WORKSPACE_PUBLICATION_QUIET_MS });
|
|
2560
|
+
};
|
|
2561
|
+
const convergeAvailableWorkspaceHead = async () => {
|
|
2562
|
+
if (pendingWorkspaceHead === void 0 || !workspaceReady || !workspaceRemoteUrl || activeWorkspaceIncidentId || workspaceHeadConvergenceInFlight) {
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
const advertisedHead = pendingWorkspaceHead;
|
|
2566
|
+
pendingWorkspaceHead = void 0;
|
|
2567
|
+
workspaceConvergence.observeCanonicalHead(advertisedHead);
|
|
2568
|
+
workspaceHeadConvergenceInFlight = true;
|
|
2569
|
+
try {
|
|
2570
|
+
const snapshot = workspaceConvergence.snapshot();
|
|
2571
|
+
const hydrateVisible = !pendingPublication && snapshot.dirtyGeneration === snapshot.publishedGeneration;
|
|
2572
|
+
const converged = await workspaceSyncSingleFlight.runExclusive(
|
|
2573
|
+
() => (0, import_workspace_sync.convergeWorkspaceHead)(workspaceSyncInput({ type: "inbound_head", detail: advertisedHead ?? "unborn" }), {
|
|
2574
|
+
hydrateVisible
|
|
2448
2575
|
})
|
|
2449
2576
|
);
|
|
2577
|
+
workspaceConvergence.observeCanonicalHead(converged.canonicalHead);
|
|
2578
|
+
if (converged.hydrated) workspaceConvergence.setLocalHead(converged.localHead);
|
|
2579
|
+
if (hydrateVisible && converged.localChanges) {
|
|
2580
|
+
markWorkspaceDirty({ type: "periodic", detail: "inbound overlap retained for next publication" });
|
|
2581
|
+
}
|
|
2582
|
+
sendWorkspaceReady();
|
|
2450
2583
|
} catch (error) {
|
|
2584
|
+
pendingWorkspaceHead = advertisedHead;
|
|
2451
2585
|
process.stderr.write(
|
|
2452
|
-
`[r5d-worker] failed to
|
|
2586
|
+
`[r5d-worker] failed to fetch available workspace head: ${error instanceof Error ? error.message : String(error)}
|
|
2453
2587
|
`
|
|
2454
2588
|
);
|
|
2589
|
+
} finally {
|
|
2590
|
+
workspaceHeadConvergenceInFlight = false;
|
|
2591
|
+
heartbeatBusyGrace = (0, import_heartbeat.grantWorkerHeartbeatBusyGrace)(lastServerHeartbeatAt, heartbeatBusyGrace);
|
|
2592
|
+
if (pendingWorkspaceHead !== void 0) {
|
|
2593
|
+
setTimeout(() => void convergeAvailableWorkspaceHead(), 1e3).unref();
|
|
2594
|
+
}
|
|
2455
2595
|
}
|
|
2456
2596
|
};
|
|
2457
2597
|
const manifestCheckoutKey = (projectId, branchName) => `${projectId}\0${branchName}`;
|
|
@@ -2467,7 +2607,7 @@ async function startWorker(options) {
|
|
|
2467
2607
|
workspaceShadowRoot,
|
|
2468
2608
|
manifestByProjectId
|
|
2469
2609
|
});
|
|
2470
|
-
const
|
|
2610
|
+
const ensureVisibleWorktrees = (targets, options2 = {}) => {
|
|
2471
2611
|
const created = [];
|
|
2472
2612
|
for (const target of targets) {
|
|
2473
2613
|
const manifest = manifestByProjectId.get(target.projectId);
|
|
@@ -2496,20 +2636,62 @@ async function startWorker(options) {
|
|
|
2496
2636
|
reconcileExistingOrigin: remote.reconcileExistingOrigin,
|
|
2497
2637
|
requireRemoteBranch: remote.requireRemoteBranch,
|
|
2498
2638
|
requiredBaseCommit: remote.requiredBaseCommit,
|
|
2499
|
-
allowRequiredRemoteMigration: !(options2.strictCanonicalRevalidation && remote.
|
|
2639
|
+
allowRequiredRemoteMigration: !(options2.strictCanonicalRevalidation && remote.requiredBaseCommit),
|
|
2500
2640
|
clearPersistentAuth: !remote.persistAuth
|
|
2501
2641
|
});
|
|
2502
2642
|
if (!existed) created.push(target);
|
|
2503
2643
|
}
|
|
2504
2644
|
return created;
|
|
2505
2645
|
};
|
|
2506
|
-
const
|
|
2646
|
+
const schedulePendingManifestCheckoutHydration = (delayMs = 0) => {
|
|
2647
|
+
if (workspaceManifestHydrationTimer || workspaceManifestHydrationInFlight || pendingManifestCheckouts.size === 0 || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2650
|
+
workspaceManifestHydrationTimer = setTimeout(
|
|
2651
|
+
() => {
|
|
2652
|
+
workspaceManifestHydrationTimer = void 0;
|
|
2653
|
+
if (!workspaceReady || !workspaceRemoteUrl || activeWorkspaceIncidentId || pendingPublication || pendingManifestCheckouts.size === 0 || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
2654
|
+
if (pendingManifestCheckouts.size > 0 && currentWorkerSocket === ws && ws.readyState === WebSocket.OPEN) {
|
|
2655
|
+
schedulePendingManifestCheckoutHydration(1e3);
|
|
2656
|
+
}
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
workspaceManifestHydrationInFlight = true;
|
|
2660
|
+
void workspaceSyncSingleFlight.runExclusive(async () => {
|
|
2661
|
+
if (!workspaceReady || activeWorkspaceIncidentId || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2664
|
+
const targets = [...pendingManifestCheckouts.values()];
|
|
2665
|
+
ensureVisibleWorktrees(targets, { strictCanonicalRevalidation: true });
|
|
2666
|
+
for (const target of targets) {
|
|
2667
|
+
const manifest = manifestByProjectId.get(target.projectId);
|
|
2668
|
+
if (!manifest || !manifest.branches.includes(target.branchName)) continue;
|
|
2669
|
+
const branchPath = import_node_path.default.join(projectRootFor(projectsRoot, target.projectId, manifestByProjectId), target.branchName);
|
|
2670
|
+
if (hasNormalVisibleGitDir(branchPath)) {
|
|
2671
|
+
pendingManifestCheckouts.delete(manifestCheckoutKey(target.projectId, target.branchName));
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
}).then(() => sendWorkspaceReady()).catch((error) => {
|
|
2675
|
+
process.stderr.write(
|
|
2676
|
+
`[r5d-worker] pending manifest checkout hydration failed: ${error instanceof Error ? error.message : String(error)}
|
|
2677
|
+
`
|
|
2678
|
+
);
|
|
2679
|
+
}).finally(() => {
|
|
2680
|
+
workspaceManifestHydrationInFlight = false;
|
|
2681
|
+
heartbeatBusyGrace = (0, import_heartbeat.grantWorkerHeartbeatBusyGrace)(lastServerHeartbeatAt, heartbeatBusyGrace);
|
|
2682
|
+
if (pendingManifestCheckouts.size > 0) schedulePendingManifestCheckoutHydration(1e3);
|
|
2683
|
+
});
|
|
2684
|
+
},
|
|
2685
|
+
Math.max(0, delayMs)
|
|
2686
|
+
);
|
|
2687
|
+
workspaceManifestHydrationTimer.unref();
|
|
2688
|
+
};
|
|
2689
|
+
const runRequestedWorkspaceSync = async (authority, trigger, overrides = {}) => {
|
|
2507
2690
|
const requestedAt = Date.now();
|
|
2508
2691
|
let queueEnteredAt = requestedAt;
|
|
2509
2692
|
let prepareStartedAt = requestedAt;
|
|
2510
2693
|
let synchronizeStartedAt = requestedAt;
|
|
2511
2694
|
let synchronizeFinishedAt = requestedAt;
|
|
2512
|
-
let fastPathDecision = { hit: false, reason: "no_recent_proof" };
|
|
2513
2695
|
workspaceSyncRequestsInFlight += 1;
|
|
2514
2696
|
try {
|
|
2515
2697
|
let pendingTargets = [];
|
|
@@ -2518,48 +2700,12 @@ async function startWorker(options) {
|
|
|
2518
2700
|
const shouldEnsureCheckouts = !overrides.skipVisibleMirror && !overrides.resetToCanonical;
|
|
2519
2701
|
const syncProjects = (0, import_workspace_sync.workspaceProjectsForSync)([...manifestByProjectId.values()], trigger);
|
|
2520
2702
|
if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
|
|
2521
|
-
const scopeKey = (0, import_workspace_sync_fast_path.workspaceSyncScopeKey)(workspaceRemoteUrl, syncProjects);
|
|
2522
2703
|
const allowedCheckoutKeys = new Set(
|
|
2523
2704
|
syncProjects.flatMap((project) => project.branches.map((branchName) => manifestCheckoutKey(project.projectId, branchName)))
|
|
2524
2705
|
);
|
|
2525
2706
|
const scopedPendingTargets = [...pendingManifestCheckouts.values()].filter(
|
|
2526
2707
|
(target) => allowedCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
|
|
2527
2708
|
);
|
|
2528
|
-
const workerBusy = activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress;
|
|
2529
|
-
fastPathDecision = recentWorkspaceSyncProof.evaluate({
|
|
2530
|
-
trigger,
|
|
2531
|
-
allowRecentNoChangeFastPath,
|
|
2532
|
-
canonicalHead: authoritativeCanonicalHead,
|
|
2533
|
-
scopeKey,
|
|
2534
|
-
nowMs: Date.now(),
|
|
2535
|
-
skipVisibleMirror: overrides.skipVisibleMirror,
|
|
2536
|
-
resetToCanonical: overrides.resetToCanonical,
|
|
2537
|
-
incidentActive: activeWorkspaceIncidentId !== null,
|
|
2538
|
-
workerBusy,
|
|
2539
|
-
pendingCheckouts: scopedPendingTargets.length > 0 || Boolean(overrides.newVisibleCheckouts?.length)
|
|
2540
|
-
});
|
|
2541
|
-
if (fastPathDecision.hit) {
|
|
2542
|
-
prepareStartedAt = queueEnteredAt;
|
|
2543
|
-
synchronizeStartedAt = queueEnteredAt;
|
|
2544
|
-
synchronizeFinishedAt = queueEnteredAt;
|
|
2545
|
-
return {
|
|
2546
|
-
type: "workspace_sync",
|
|
2547
|
-
attemptId: overrides.attemptId ?? crypto.randomUUID(),
|
|
2548
|
-
workerLabel: label,
|
|
2549
|
-
trigger,
|
|
2550
|
-
outcome: "no_change",
|
|
2551
|
-
startingHead: fastPathDecision.canonicalHead,
|
|
2552
|
-
expectedHead: fastPathDecision.canonicalHead,
|
|
2553
|
-
publishedHead: fastPathDecision.canonicalHead,
|
|
2554
|
-
rebaseCount: 0,
|
|
2555
|
-
diffSizeBytes: 0,
|
|
2556
|
-
gitStatus: "",
|
|
2557
|
-
affectedProjects: [],
|
|
2558
|
-
affectedPaths: [],
|
|
2559
|
-
discardedPaths: [],
|
|
2560
|
-
localChangesDiscarded: false
|
|
2561
|
-
};
|
|
2562
|
-
}
|
|
2563
2709
|
prepareStartedAt = Date.now();
|
|
2564
2710
|
let checkoutTargets = [];
|
|
2565
2711
|
if (shouldEnsureCheckouts) {
|
|
@@ -2571,7 +2717,7 @@ async function startWorker(options) {
|
|
|
2571
2717
|
});
|
|
2572
2718
|
}
|
|
2573
2719
|
pendingTargets = shouldEnsureCheckouts ? scopedPendingTargets : [];
|
|
2574
|
-
const createdCheckouts =
|
|
2720
|
+
const createdCheckouts = ensureVisibleWorktrees(checkoutTargets, {
|
|
2575
2721
|
strictCanonicalRevalidation: true
|
|
2576
2722
|
});
|
|
2577
2723
|
const newVisibleCheckoutByKey = /* @__PURE__ */ new Map();
|
|
@@ -2585,24 +2731,12 @@ async function startWorker(options) {
|
|
|
2585
2731
|
...newVisibleCheckouts.length > 0 ? { newVisibleCheckouts } : {}
|
|
2586
2732
|
});
|
|
2587
2733
|
synchronizeStartedAt = Date.now();
|
|
2588
|
-
recentWorkspaceSyncProof.invalidate();
|
|
2589
|
-
const observationGeneration = recentWorkspaceSyncProof.beginObservation();
|
|
2590
|
-
const workerWasBusyDuringObservation = workerBusy;
|
|
2591
2734
|
let synchronized;
|
|
2592
2735
|
try {
|
|
2593
2736
|
synchronized = await (0, import_workspace_sync.synchronizeWorkspace)(input);
|
|
2594
2737
|
} finally {
|
|
2595
2738
|
synchronizeFinishedAt = Date.now();
|
|
2596
2739
|
}
|
|
2597
|
-
if ((synchronized.outcome === "no_change" || synchronized.outcome === "updated") && pendingTargets.length === 0) {
|
|
2598
|
-
recentWorkspaceSyncProof.recordStable({
|
|
2599
|
-
observationGeneration,
|
|
2600
|
-
canonicalHead: synchronized.publishedHead ?? null,
|
|
2601
|
-
scopeKey,
|
|
2602
|
-
observedAtMs: synchronizeFinishedAt,
|
|
2603
|
-
workerBusy: workerWasBusyDuringObservation || activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress || activeWorkspaceIncidentId !== null
|
|
2604
|
-
});
|
|
2605
|
-
}
|
|
2606
2740
|
return synchronized;
|
|
2607
2741
|
});
|
|
2608
2742
|
const completedAt = Date.now();
|
|
@@ -2612,8 +2746,7 @@ async function startWorker(options) {
|
|
|
2612
2746
|
totalMs: completedAt - requestedAt,
|
|
2613
2747
|
queueMs: queueEnteredAt - requestedAt,
|
|
2614
2748
|
prepareMs: synchronizeStartedAt - prepareStartedAt,
|
|
2615
|
-
synchronizeMs: synchronizeFinishedAt - synchronizeStartedAt
|
|
2616
|
-
fastPath: workspaceSyncFastPathTelemetry(fastPathDecision)
|
|
2749
|
+
synchronizeMs: synchronizeFinishedAt - synchronizeStartedAt
|
|
2617
2750
|
}
|
|
2618
2751
|
};
|
|
2619
2752
|
if (resultWithTelemetry.outcome === "no_change" || resultWithTelemetry.outcome === "published" || resultWithTelemetry.outcome === "updated" || resultWithTelemetry.outcome === "conflict_reset") {
|
|
@@ -2622,7 +2755,6 @@ async function startWorker(options) {
|
|
|
2622
2755
|
if (pendingManifestCheckouts.get(key) === target) pendingManifestCheckouts.delete(key);
|
|
2623
2756
|
}
|
|
2624
2757
|
}
|
|
2625
|
-
previousPeriodicFingerprint = null;
|
|
2626
2758
|
sendWorkspaceSyncResult(authority, resultWithTelemetry);
|
|
2627
2759
|
return resultWithTelemetry;
|
|
2628
2760
|
} catch (error) {
|
|
@@ -2645,12 +2777,10 @@ async function startWorker(options) {
|
|
|
2645
2777
|
totalMs: completedAt - requestedAt,
|
|
2646
2778
|
queueMs: queueEnteredAt - requestedAt,
|
|
2647
2779
|
prepareMs: Math.max(0, synchronizeStartedAt - prepareStartedAt),
|
|
2648
|
-
synchronizeMs: Math.max(0, synchronizeFinishedAt - synchronizeStartedAt)
|
|
2649
|
-
fastPath: workspaceSyncFastPathTelemetry(fastPathDecision)
|
|
2780
|
+
synchronizeMs: Math.max(0, synchronizeFinishedAt - synchronizeStartedAt)
|
|
2650
2781
|
},
|
|
2651
2782
|
error: error instanceof Error ? error.message : String(error)
|
|
2652
2783
|
};
|
|
2653
|
-
previousPeriodicFingerprint = null;
|
|
2654
2784
|
sendWorkspaceSyncResult(authority, result);
|
|
2655
2785
|
return result;
|
|
2656
2786
|
} finally {
|
|
@@ -2666,6 +2796,24 @@ async function startWorker(options) {
|
|
|
2666
2796
|
let heartbeatBusyGrace = null;
|
|
2667
2797
|
let heartbeatTimedOut = false;
|
|
2668
2798
|
let heartbeatWatchdog;
|
|
2799
|
+
const handleGracefulShutdown = () => {
|
|
2800
|
+
if (shutdownAfterClose) return;
|
|
2801
|
+
shutdownAfterClose = true;
|
|
2802
|
+
void (0, import_workspace_convergence.waitForWorkspacePublicationOnShutdown)({
|
|
2803
|
+
snapshot: () => workspaceConvergence.snapshot(),
|
|
2804
|
+
forcePublication: () => armWorkspacePublication({ type: "manual", detail: "graceful worker shutdown" }, { delayMs: 0 })
|
|
2805
|
+
}).then((outcome) => {
|
|
2806
|
+
process.stdout.write(`[r5d-worker] graceful workspace publication ${outcome}
|
|
2807
|
+
`);
|
|
2808
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
2809
|
+
ws.close(1e3, "Worker shutting down");
|
|
2810
|
+
} else {
|
|
2811
|
+
process.exit(0);
|
|
2812
|
+
}
|
|
2813
|
+
});
|
|
2814
|
+
};
|
|
2815
|
+
process.on("SIGTERM", handleGracefulShutdown);
|
|
2816
|
+
process.on("SIGINT", handleGracefulShutdown);
|
|
2669
2817
|
const stopHeartbeatWatchdog = () => {
|
|
2670
2818
|
if (heartbeatWatchdog) {
|
|
2671
2819
|
clearInterval(heartbeatWatchdog);
|
|
@@ -2696,7 +2844,8 @@ async function startWorker(options) {
|
|
|
2696
2844
|
updateClis: true,
|
|
2697
2845
|
canonicalResolverCheckout: true,
|
|
2698
2846
|
browserPortForwarding: true,
|
|
2699
|
-
globalWorkspaceSyncLeaseV1: true
|
|
2847
|
+
globalWorkspaceSyncLeaseV1: true,
|
|
2848
|
+
eventualWorkspaceSyncV1: true
|
|
2700
2849
|
},
|
|
2701
2850
|
projectRoot: projectsRoot,
|
|
2702
2851
|
artifactRoot,
|
|
@@ -2723,14 +2872,19 @@ async function startWorker(options) {
|
|
|
2723
2872
|
return;
|
|
2724
2873
|
}
|
|
2725
2874
|
if (message.type === "workspace_manifest") {
|
|
2726
|
-
|
|
2727
|
-
|
|
2875
|
+
const revisionToken = workspaceManifestRevision.begin(message.revision);
|
|
2876
|
+
workspaceReady = false;
|
|
2877
|
+
const manifestFilesystem = (0, import_workspace_manifest_admission.inspectWorkspaceManifestFilesystem)({
|
|
2878
|
+
projectsRoot,
|
|
2879
|
+
workspaceShadowRoot,
|
|
2880
|
+
projects: message.projects
|
|
2881
|
+
});
|
|
2882
|
+
const completed = await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
2883
|
+
if (!workspaceManifestRevision.isCurrent(revisionToken)) return false;
|
|
2884
|
+
const firstBootstrap = manifestFilesystem.firstBootstrap;
|
|
2728
2885
|
configureGitHubAuth(message.githubCredential);
|
|
2729
2886
|
visibleGitIdentity = message.gitIdentity;
|
|
2730
2887
|
workspaceRemoteUrl = message.workspaceRemoteUrl;
|
|
2731
|
-
const previousCheckoutKeys = new Set(
|
|
2732
|
-
allManifestCheckouts().map(({ projectId, branchName }) => manifestCheckoutKey(projectId, branchName))
|
|
2733
|
-
);
|
|
2734
2888
|
const migratedProjectIds = (0, import_managed_paths.migrateLegacyProjectRoots)(projectsRoot, message.projects);
|
|
2735
2889
|
manifestByProjectId.clear();
|
|
2736
2890
|
for (const project of message.projects) manifestByProjectId.set(project.projectId, project);
|
|
@@ -2743,80 +2897,77 @@ async function startWorker(options) {
|
|
|
2743
2897
|
}
|
|
2744
2898
|
for (const checkout of currentCheckouts) {
|
|
2745
2899
|
const key = manifestCheckoutKey(checkout.projectId, checkout.branchName);
|
|
2746
|
-
if (previousCheckoutKeys.has(key)) continue;
|
|
2747
2900
|
const branchPath = import_node_path.default.join(projectRootFor(projectsRoot, checkout.projectId, manifestByProjectId), checkout.branchName);
|
|
2748
2901
|
if (!hasNormalVisibleGitDir(branchPath)) pendingManifestCheckouts.set(key, checkout);
|
|
2749
2902
|
}
|
|
2750
|
-
|
|
2903
|
+
if (!workspaceManifestRevision.isCurrent(revisionToken)) return false;
|
|
2904
|
+
const created = firstBootstrap || migratedProjectIds.length > 0 ? ensureVisibleWorktrees(currentCheckouts, { strictCanonicalRevalidation: true }) : [];
|
|
2905
|
+
if (!workspaceManifestRevision.isCurrent(revisionToken)) return false;
|
|
2906
|
+
const bootstrapped = await (0, import_workspace_sync.convergeWorkspaceHead)(workspaceSyncInput({ type: "connect" }, { newVisibleCheckouts: created }), {
|
|
2907
|
+
// An existing checkout becomes routable after a fetch. Its
|
|
2908
|
+
// visible tree converges asynchronously; first bootstrap alone
|
|
2909
|
+
// must materialize the canonical snapshot before readiness.
|
|
2910
|
+
// Re-check the incident immediately before convergence so a
|
|
2911
|
+
// concurrently delivered fence forces fetch-only behavior.
|
|
2912
|
+
hydrateVisible: firstBootstrap && !activeWorkspaceIncidentId
|
|
2913
|
+
});
|
|
2914
|
+
if (!workspaceManifestRevision.isCurrent(revisionToken)) return false;
|
|
2915
|
+
workspaceConvergence.setLocalHead(bootstrapped.localHead);
|
|
2916
|
+
workspaceConvergence.observeCanonicalHead(bootstrapped.canonicalHead);
|
|
2917
|
+
for (const checkout of currentCheckouts) {
|
|
2918
|
+
const manifest = manifestByProjectId.get(checkout.projectId);
|
|
2919
|
+
if (!manifest) continue;
|
|
2920
|
+
const branchPath = import_node_path.default.join(projectRootFor(projectsRoot, checkout.projectId, manifestByProjectId), checkout.branchName);
|
|
2921
|
+
if (hasNormalVisibleGitDir(branchPath)) {
|
|
2922
|
+
pendingManifestCheckouts.delete(manifestCheckoutKey(checkout.projectId, checkout.branchName));
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2751
2925
|
process.stdout.write(
|
|
2752
2926
|
`[r5d-worker] workspace manifest: ${message.projects.length} projects${migratedProjectIds.length > 0 ? `; migrated ${migratedProjectIds.length} project checkout root(s)` : ""}
|
|
2753
2927
|
`
|
|
2754
2928
|
);
|
|
2929
|
+
return true;
|
|
2755
2930
|
});
|
|
2756
|
-
if (!
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2931
|
+
if (!completed || !workspaceManifestRevision.complete(revisionToken)) return;
|
|
2932
|
+
workspaceReady = true;
|
|
2933
|
+
sendWorkspaceReady();
|
|
2934
|
+
schedulePendingManifestCheckoutHydration();
|
|
2935
|
+
if (!workspaceSafetyScan) {
|
|
2936
|
+
workspaceSafetyScan = setInterval(() => {
|
|
2937
|
+
if (!workspaceReady || !workspaceRemoteUrl || activeWorkspaceIncidentId || pendingPublication || workspaceSafetyScanInFlight) {
|
|
2938
|
+
return;
|
|
2939
|
+
}
|
|
2940
|
+
workspaceSafetyScanInFlight = true;
|
|
2761
2941
|
void (async () => {
|
|
2762
|
-
const
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
)
|
|
2942
|
+
const before = workspaceConvergence.snapshot();
|
|
2943
|
+
const hydrateVisible = before.dirtyGeneration === before.publishedGeneration;
|
|
2944
|
+
const converged = await workspaceSyncSingleFlight.runExclusive(
|
|
2945
|
+
() => (0, import_workspace_sync.convergeWorkspaceHead)(workspaceSyncInput({ type: "periodic", detail: "idle convergence scan" }), {
|
|
2946
|
+
hydrateVisible
|
|
2947
|
+
})
|
|
2766
2948
|
);
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
)
|
|
2770
|
-
|
|
2771
|
-
recentWorkspaceSyncProof.invalidate();
|
|
2772
|
-
previousPeriodicFingerprint = null;
|
|
2773
|
-
requestWorkspaceSync({
|
|
2774
|
-
type: "periodic",
|
|
2775
|
-
detail: "workspace manifest additions"
|
|
2776
|
-
});
|
|
2777
|
-
return;
|
|
2949
|
+
workspaceConvergence.observeCanonicalHead(converged.canonicalHead);
|
|
2950
|
+
if (converged.hydrated) workspaceConvergence.setLocalHead(converged.localHead);
|
|
2951
|
+
if (converged.localChanges && hydrateVisible) {
|
|
2952
|
+
markWorkspaceDirty({ type: "periodic", detail: "idle safety scan" });
|
|
2778
2953
|
}
|
|
2779
|
-
|
|
2780
|
-
const observationGeneration = recentWorkspaceSyncProof.beginObservation();
|
|
2781
|
-
const input = workspaceSyncInput({ type: "periodic" });
|
|
2782
|
-
const workerBusy = activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress;
|
|
2783
|
-
return {
|
|
2784
|
-
observationGeneration,
|
|
2785
|
-
scopeKey: (0, import_workspace_sync_fast_path.workspaceSyncScopeKey)(input.remoteUrl, input.projects),
|
|
2786
|
-
fingerprint: (0, import_workspace_sync.calculateWorkspaceDiffFingerprint)(input),
|
|
2787
|
-
canonicalHead: (0, import_workspace_sync_fast_path.readShadowWorkspaceCanonicalHead)(workspaceShadowRoot),
|
|
2788
|
-
workerBusy
|
|
2789
|
-
};
|
|
2790
|
-
});
|
|
2791
|
-
if (observation.fingerprint === emptyFingerprint) {
|
|
2792
|
-
recentWorkspaceSyncProof.recordStable({
|
|
2793
|
-
observationGeneration: observation.observationGeneration,
|
|
2794
|
-
canonicalHead: observation.canonicalHead,
|
|
2795
|
-
scopeKey: observation.scopeKey,
|
|
2796
|
-
observedAtMs: Date.now(),
|
|
2797
|
-
workerBusy: observation.workerBusy || activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress
|
|
2798
|
-
});
|
|
2799
|
-
previousPeriodicFingerprint = null;
|
|
2800
|
-
return;
|
|
2801
|
-
}
|
|
2802
|
-
recentWorkspaceSyncProof.invalidate();
|
|
2803
|
-
if (previousPeriodicFingerprint !== observation.fingerprint) {
|
|
2804
|
-
previousPeriodicFingerprint = observation.fingerprint;
|
|
2805
|
-
return;
|
|
2806
|
-
}
|
|
2807
|
-
previousPeriodicFingerprint = null;
|
|
2808
|
-
requestWorkspaceSync({ type: "periodic" }, observation.fingerprint);
|
|
2954
|
+
sendWorkspaceReady();
|
|
2809
2955
|
})().catch((error) => {
|
|
2810
2956
|
process.stderr.write(
|
|
2811
|
-
`[r5d-worker]
|
|
2957
|
+
`[r5d-worker] idle workspace safety scan failed: ${error instanceof Error ? error.message : String(error)}
|
|
2812
2958
|
`
|
|
2813
2959
|
);
|
|
2814
2960
|
}).finally(() => {
|
|
2815
|
-
|
|
2961
|
+
workspaceSafetyScanInFlight = false;
|
|
2816
2962
|
heartbeatBusyGrace = (0, import_heartbeat.grantWorkerHeartbeatBusyGrace)(lastServerHeartbeatAt, heartbeatBusyGrace);
|
|
2817
2963
|
});
|
|
2818
|
-
},
|
|
2819
|
-
|
|
2964
|
+
}, import_workspace_convergence.WORKSPACE_IDLE_SAFETY_SCAN_MS);
|
|
2965
|
+
workspaceSafetyScan.unref();
|
|
2966
|
+
}
|
|
2967
|
+
pendingWorkspaceHead = workspaceConvergence.snapshot().desiredCanonicalHead;
|
|
2968
|
+
void convergeAvailableWorkspaceHead();
|
|
2969
|
+
if (workspaceConvergence.snapshot().dirtyGeneration > workspaceConvergence.snapshot().publishedGeneration) {
|
|
2970
|
+
armWorkspacePublication({ type: "periodic", detail: "resume dirty generation after reconnect" }, { delayMs: 0 });
|
|
2820
2971
|
}
|
|
2821
2972
|
return;
|
|
2822
2973
|
}
|
|
@@ -2836,12 +2987,181 @@ async function startWorker(options) {
|
|
|
2836
2987
|
confirmationReason: message.confirmationReason,
|
|
2837
2988
|
resetToCanonical: message.resetToCanonical,
|
|
2838
2989
|
skipVisibleMirror: message.skipVisibleMirror
|
|
2839
|
-
}
|
|
2840
|
-
message.canonicalHead,
|
|
2841
|
-
message.allowRecentNoChangeFastPath
|
|
2990
|
+
}
|
|
2842
2991
|
);
|
|
2843
2992
|
return;
|
|
2844
2993
|
}
|
|
2994
|
+
if (message.type === "workspace_head_available") {
|
|
2995
|
+
workspaceConvergence.observeCanonicalHead(message.canonicalHead);
|
|
2996
|
+
pendingWorkspaceHead = message.canonicalHead;
|
|
2997
|
+
sendWorkspaceReady();
|
|
2998
|
+
void convergeAvailableWorkspaceHead();
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
if (message.type === "workspace_publication_now") {
|
|
3002
|
+
armWorkspacePublication(message.trigger, { delayMs: 0, requestId: message.requestId });
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
3005
|
+
if (message.type === "workspace_publication_authority") {
|
|
3006
|
+
const publication = pendingPublication;
|
|
3007
|
+
if (!publication || publication.requestId !== message.requestId) {
|
|
3008
|
+
process.stderr.write(`[r5d-worker] ignoring publication authority for unknown request ${message.requestId}
|
|
3009
|
+
`);
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
if (message.status !== "granted") {
|
|
3013
|
+
pendingPublication = void 0;
|
|
3014
|
+
workspaceConvergence.observeCanonicalHead(message.canonicalHead);
|
|
3015
|
+
const applyBlockedResponse = message.status === "blocked" && workspaceIncidentOrdering.permitsBlockedResponse(message.incidentId);
|
|
3016
|
+
if (applyBlockedResponse) {
|
|
3017
|
+
workspaceConvergence.block();
|
|
3018
|
+
if (message.incidentId) activeWorkspaceIncidentId = message.incidentId;
|
|
3019
|
+
} else {
|
|
3020
|
+
workspaceConvergence.resetTransientPublicationState();
|
|
3021
|
+
workspaceConvergence.observeIncidentState(Boolean(activeWorkspaceIncidentId));
|
|
3022
|
+
if (message.status === "busy") {
|
|
3023
|
+
armWorkspacePublication(publication.trigger, {
|
|
3024
|
+
delayMs: message.retryAfterMs ?? 1e3,
|
|
3025
|
+
requestId: publication.requestId
|
|
3026
|
+
});
|
|
3027
|
+
} else if (!activeWorkspaceIncidentId) {
|
|
3028
|
+
const explicit = explicitWorkspacePublications.next();
|
|
3029
|
+
if (explicit) {
|
|
3030
|
+
armWorkspacePublication(explicit.trigger, { delayMs: 0, requestId: explicit.requestId });
|
|
3031
|
+
} else if (workspaceConvergence.snapshot().dirtyGeneration > workspaceConvergence.snapshot().publishedGeneration) {
|
|
3032
|
+
armWorkspacePublication(publication.trigger, { delayMs: 0 });
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
sendWorkspaceReady();
|
|
3037
|
+
return;
|
|
3038
|
+
}
|
|
3039
|
+
publication.attemptId = message.attemptId;
|
|
3040
|
+
publication.authorityToken = message.authorityToken;
|
|
3041
|
+
publication.workInFlight = true;
|
|
3042
|
+
pendingPublication = publication;
|
|
3043
|
+
if (currentWorkerSocket !== ws) {
|
|
3044
|
+
publication.workInFlight = false;
|
|
3045
|
+
workspaceConvergence.resetTransientPublicationState();
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
3048
|
+
workspaceConvergence.beginPreparing();
|
|
3049
|
+
sendWorkspaceReady();
|
|
3050
|
+
let prepareStartedAt = Date.now();
|
|
3051
|
+
let synchronizeStartedAt = prepareStartedAt;
|
|
3052
|
+
let synchronizeStarted = false;
|
|
3053
|
+
let result;
|
|
3054
|
+
try {
|
|
3055
|
+
result = await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
3056
|
+
prepareStartedAt = Date.now();
|
|
3057
|
+
const pendingTargets = [...pendingManifestCheckouts.values()];
|
|
3058
|
+
const createdCheckouts = ensureVisibleWorktrees(pendingTargets, { strictCanonicalRevalidation: true });
|
|
3059
|
+
synchronizeStartedAt = Date.now();
|
|
3060
|
+
synchronizeStarted = true;
|
|
3061
|
+
const prepared = await (0, import_workspace_sync.synchronizeWorkspace)(
|
|
3062
|
+
workspaceSyncInput(publication.trigger, {
|
|
3063
|
+
attemptId: message.attemptId,
|
|
3064
|
+
quarantineRef: message.quarantineRef,
|
|
3065
|
+
expectedCanonicalHead: message.expectedHead,
|
|
3066
|
+
newVisibleCheckouts: [...createdCheckouts, ...pendingTargets]
|
|
3067
|
+
})
|
|
3068
|
+
);
|
|
3069
|
+
for (const target of pendingTargets) {
|
|
3070
|
+
pendingManifestCheckouts.delete(manifestCheckoutKey(target.projectId, target.branchName));
|
|
3071
|
+
}
|
|
3072
|
+
return prepared.outcome === "updated" ? { ...prepared, outcome: "no_change" } : prepared;
|
|
3073
|
+
});
|
|
3074
|
+
} catch (error) {
|
|
3075
|
+
if (!synchronizeStarted) synchronizeStartedAt = Date.now();
|
|
3076
|
+
result = {
|
|
3077
|
+
type: "workspace_sync",
|
|
3078
|
+
attemptId: message.attemptId,
|
|
3079
|
+
workerLabel: label,
|
|
3080
|
+
trigger: publication.trigger,
|
|
3081
|
+
outcome: "failed",
|
|
3082
|
+
startingHead: message.expectedHead,
|
|
3083
|
+
expectedHead: message.expectedHead,
|
|
3084
|
+
rebaseCount: 0,
|
|
3085
|
+
diffSizeBytes: 0,
|
|
3086
|
+
gitStatus: "",
|
|
3087
|
+
affectedProjects: [],
|
|
3088
|
+
affectedPaths: [],
|
|
3089
|
+
discardedPaths: [],
|
|
3090
|
+
localChangesDiscarded: false,
|
|
3091
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3092
|
+
};
|
|
3093
|
+
}
|
|
3094
|
+
const finishedAt = Date.now();
|
|
3095
|
+
result = {
|
|
3096
|
+
...result,
|
|
3097
|
+
dirtyGeneration: publication.dirtyGeneration,
|
|
3098
|
+
telemetry: (0, import_workspace_convergence.workspacePublicationTelemetry)({
|
|
3099
|
+
requestedAt: publication.requestedAt,
|
|
3100
|
+
prepareStartedAt,
|
|
3101
|
+
synchronizeStartedAt,
|
|
3102
|
+
finishedAt
|
|
3103
|
+
})
|
|
3104
|
+
};
|
|
3105
|
+
workspaceConvergence.beginSubmitting();
|
|
3106
|
+
sendWorkspaceReady();
|
|
3107
|
+
try {
|
|
3108
|
+
ws.send(
|
|
3109
|
+
JSON.stringify({
|
|
3110
|
+
type: "workspace_publication_result",
|
|
3111
|
+
requestId: publication.requestId,
|
|
3112
|
+
attemptId: message.attemptId,
|
|
3113
|
+
authorityToken: message.authorityToken,
|
|
3114
|
+
dirtyGeneration: publication.dirtyGeneration,
|
|
3115
|
+
result
|
|
3116
|
+
})
|
|
3117
|
+
);
|
|
3118
|
+
} catch (error) {
|
|
3119
|
+
workspaceConvergence.resetTransientPublicationState();
|
|
3120
|
+
throw error;
|
|
3121
|
+
} finally {
|
|
3122
|
+
publication.workInFlight = false;
|
|
3123
|
+
if (currentWorkerSocket !== ws) workspaceConvergence.resetTransientPublicationState();
|
|
3124
|
+
}
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
if (message.type === "workspace_publication_completed") {
|
|
3128
|
+
const publication = pendingPublication;
|
|
3129
|
+
if (!publication || publication.requestId !== message.requestId || publication.attemptId !== message.attemptId) {
|
|
3130
|
+
process.stderr.write(`[r5d-worker] ignoring completion for unknown publication ${message.requestId}
|
|
3131
|
+
`);
|
|
3132
|
+
return;
|
|
3133
|
+
}
|
|
3134
|
+
pendingPublication = void 0;
|
|
3135
|
+
workspaceConvergence.observeCanonicalHead(message.canonicalHead);
|
|
3136
|
+
if (message.status === "published" || message.status === "no_change") {
|
|
3137
|
+
workspaceConvergence.complete({
|
|
3138
|
+
generation: Math.min(message.publishedGeneration ?? publication.dirtyGeneration, publication.dirtyGeneration),
|
|
3139
|
+
canonicalHead: message.canonicalHead,
|
|
3140
|
+
published: true
|
|
3141
|
+
});
|
|
3142
|
+
} else if (message.status === "blocked" && workspaceIncidentOrdering.permitsBlockedResponse(message.incidentId)) {
|
|
3143
|
+
workspaceConvergence.block();
|
|
3144
|
+
if (message.incidentId) activeWorkspaceIncidentId = message.incidentId;
|
|
3145
|
+
} else {
|
|
3146
|
+
workspaceConvergence.resetTransientPublicationState();
|
|
3147
|
+
workspaceConvergence.observeIncidentState(Boolean(activeWorkspaceIncidentId));
|
|
3148
|
+
}
|
|
3149
|
+
sendWorkspaceReady();
|
|
3150
|
+
const explicit = message.status === "retry" && publication.explicit ? { requestId: publication.requestId, trigger: publication.trigger } : explicitWorkspacePublications.next();
|
|
3151
|
+
if (explicit) {
|
|
3152
|
+
armWorkspacePublication(explicit.trigger, {
|
|
3153
|
+
delayMs: message.retryAfterMs ?? 0,
|
|
3154
|
+
requestId: explicit.requestId
|
|
3155
|
+
});
|
|
3156
|
+
} else if (message.status === "retry" || message.status === "failed" || workspaceConvergence.snapshot().dirtyGeneration > workspaceConvergence.snapshot().publishedGeneration) {
|
|
3157
|
+
armWorkspacePublication(publication.trigger, { delayMs: message.retryAfterMs ?? 1e3 });
|
|
3158
|
+
}
|
|
3159
|
+
if (message.canonicalHead !== workspaceConvergence.snapshot().localHead) {
|
|
3160
|
+
pendingWorkspaceHead = message.canonicalHead;
|
|
3161
|
+
void convergeAvailableWorkspaceHead();
|
|
3162
|
+
}
|
|
3163
|
+
return;
|
|
3164
|
+
}
|
|
2845
3165
|
if (message.type === "sync_session_artifacts") {
|
|
2846
3166
|
sessionArtifactSyncRequestsInFlight += 1;
|
|
2847
3167
|
try {
|
|
@@ -2869,18 +3189,27 @@ async function startWorker(options) {
|
|
|
2869
3189
|
return;
|
|
2870
3190
|
}
|
|
2871
3191
|
if (message.type === "workspace_incident_updated") {
|
|
2872
|
-
|
|
3192
|
+
workspaceIncidentOrdering.observe(message);
|
|
2873
3193
|
const previousIncidentId = activeWorkspaceIncidentId;
|
|
2874
3194
|
activeWorkspaceIncidentId = (0, import_workspace_incident_state.applyWorkspaceIncidentUpdate)(activeWorkspaceIncidentId, message);
|
|
2875
|
-
previousPeriodicFingerprint = null;
|
|
2876
3195
|
const releasedHead = (0, import_workspace_incident_state.releasePendingWorkspaceHead)(
|
|
2877
3196
|
previousIncidentId,
|
|
2878
3197
|
activeWorkspaceIncidentId,
|
|
2879
3198
|
message.status === "resolved" || message.status === "confirmed" || message.status === "reset" ? message.incidentId : null
|
|
2880
3199
|
);
|
|
2881
|
-
|
|
2882
|
-
|
|
3200
|
+
workspaceConvergence.observeIncidentState(Boolean(activeWorkspaceIncidentId));
|
|
3201
|
+
if (releasedHead.fetchAuthoritativeHead) {
|
|
3202
|
+
pendingWorkspaceHead = workspaceConvergence.snapshot().desiredCanonicalHead;
|
|
3203
|
+
void convergeAvailableWorkspaceHead();
|
|
2883
3204
|
}
|
|
3205
|
+
const explicit = !activeWorkspaceIncidentId ? explicitWorkspacePublications.next() : void 0;
|
|
3206
|
+
if (explicit) {
|
|
3207
|
+
armWorkspacePublication(explicit.trigger, { delayMs: 0, requestId: explicit.requestId });
|
|
3208
|
+
} else if (!activeWorkspaceIncidentId && workspaceConvergence.snapshot().dirtyGeneration > workspaceConvergence.snapshot().publishedGeneration) {
|
|
3209
|
+
armWorkspacePublication({ type: "periodic", detail: "resume after workspace incident" }, { delayMs: 0 });
|
|
3210
|
+
}
|
|
3211
|
+
if (!activeWorkspaceIncidentId) schedulePendingManifestCheckoutHydration();
|
|
3212
|
+
sendWorkspaceReady();
|
|
2884
3213
|
return;
|
|
2885
3214
|
}
|
|
2886
3215
|
if (message.type === "exec_terminal_ack") {
|
|
@@ -2916,7 +3245,6 @@ async function startWorker(options) {
|
|
|
2916
3245
|
return;
|
|
2917
3246
|
}
|
|
2918
3247
|
cliUpdateInProgress = true;
|
|
2919
|
-
recentWorkspaceSyncProof.invalidate();
|
|
2920
3248
|
try {
|
|
2921
3249
|
const result = await (0, import_cli_update.installCliUpdate)(message);
|
|
2922
3250
|
sendWorkerMessage(ws, {
|
|
@@ -2979,9 +3307,8 @@ async function startWorker(options) {
|
|
|
2979
3307
|
});
|
|
2980
3308
|
return;
|
|
2981
3309
|
}
|
|
2982
|
-
recentWorkspaceSyncProof.invalidate();
|
|
2983
3310
|
const releaseWorkspaceMutation = await (0, import_workspace_command_sync_policy.acquireWorkspaceCommandMutation)(message.target, workspaceSyncSingleFlight);
|
|
2984
|
-
let
|
|
3311
|
+
let mutationLeaseTransferred = false;
|
|
2985
3312
|
try {
|
|
2986
3313
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
2987
3314
|
process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${describeWorkerSessionTarget(message.target)}
|
|
@@ -2991,9 +3318,14 @@ async function startWorker(options) {
|
|
|
2991
3318
|
message,
|
|
2992
3319
|
resolvedTarget,
|
|
2993
3320
|
planRoot,
|
|
2994
|
-
...releaseWorkspaceMutation ? { releaseWorkspaceMutation } : {}
|
|
3321
|
+
...releaseWorkspaceMutation ? { releaseWorkspaceMutation } : {},
|
|
3322
|
+
...targetMayMutateVisibleWorkspace(message.target) ? {
|
|
3323
|
+
onTerminal: () => {
|
|
3324
|
+
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} completed` }, true);
|
|
3325
|
+
}
|
|
3326
|
+
} : {}
|
|
2995
3327
|
});
|
|
2996
|
-
|
|
3328
|
+
mutationLeaseTransferred = releaseWorkspaceMutation !== void 0;
|
|
2997
3329
|
} catch (error) {
|
|
2998
3330
|
sendWorkerMessage(ws, {
|
|
2999
3331
|
type: "pty_error",
|
|
@@ -3002,12 +3334,15 @@ async function startWorker(options) {
|
|
|
3002
3334
|
error: error instanceof Error ? error.message : String(error)
|
|
3003
3335
|
});
|
|
3004
3336
|
} finally {
|
|
3005
|
-
if (!
|
|
3337
|
+
if (!mutationLeaseTransferred) releaseWorkspaceMutation?.();
|
|
3006
3338
|
}
|
|
3007
3339
|
return;
|
|
3008
3340
|
}
|
|
3009
3341
|
if (message.type === "pty_input") {
|
|
3010
|
-
|
|
3342
|
+
const activePty = activePtys.get(message.ptyId);
|
|
3343
|
+
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
3344
|
+
markWorkspaceDirty({ type: "shell_inline", detail: `pty ${message.ptyId}` });
|
|
3345
|
+
}
|
|
3011
3346
|
writePty(ws, message);
|
|
3012
3347
|
return;
|
|
3013
3348
|
}
|
|
@@ -3016,8 +3351,11 @@ async function startWorker(options) {
|
|
|
3016
3351
|
return;
|
|
3017
3352
|
}
|
|
3018
3353
|
if (message.type === "pty_close") {
|
|
3019
|
-
|
|
3354
|
+
const activePty = activePtys.get(message.ptyId);
|
|
3020
3355
|
closePty(message);
|
|
3356
|
+
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
3357
|
+
armWorkspacePublication({ type: "process_terminal", detail: `pty ${message.ptyId} closed` }, { delayMs: 0 });
|
|
3358
|
+
}
|
|
3021
3359
|
return;
|
|
3022
3360
|
}
|
|
3023
3361
|
if (message.type === "exec") {
|
|
@@ -3031,7 +3369,6 @@ async function startWorker(options) {
|
|
|
3031
3369
|
});
|
|
3032
3370
|
return;
|
|
3033
3371
|
}
|
|
3034
|
-
recentWorkspaceSyncProof.invalidate();
|
|
3035
3372
|
let result;
|
|
3036
3373
|
try {
|
|
3037
3374
|
const runCommand = async () => {
|
|
@@ -3052,6 +3389,12 @@ async function startWorker(options) {
|
|
|
3052
3389
|
};
|
|
3053
3390
|
}
|
|
3054
3391
|
ws.send(JSON.stringify(result));
|
|
3392
|
+
if (targetMayMutateVisibleWorkspace(message.target)) {
|
|
3393
|
+
markWorkspaceDirty(
|
|
3394
|
+
{ type: "shell_inline", sessionId: message.sessionId, processRunId: message.runId, detail: "foreground command completed" },
|
|
3395
|
+
true
|
|
3396
|
+
);
|
|
3397
|
+
}
|
|
3055
3398
|
return;
|
|
3056
3399
|
}
|
|
3057
3400
|
if (message.type === "exec_start") {
|
|
@@ -3064,25 +3407,38 @@ async function startWorker(options) {
|
|
|
3064
3407
|
});
|
|
3065
3408
|
return;
|
|
3066
3409
|
}
|
|
3067
|
-
recentWorkspaceSyncProof.invalidate();
|
|
3068
3410
|
sendWorkerMessage(ws, {
|
|
3069
3411
|
type: "exec_accepted",
|
|
3070
3412
|
requestId: message.requestId,
|
|
3071
3413
|
runId: message.runId
|
|
3072
3414
|
});
|
|
3073
3415
|
const runCommand = async () => {
|
|
3074
|
-
|
|
3075
|
-
|
|
3416
|
+
try {
|
|
3417
|
+
const resolvedTarget = resolveMessageTarget(message.target);
|
|
3418
|
+
process.stdout.write(`[r5d-worker] exec_start ${message.runId}: ${message.argv.join(" ")}
|
|
3076
3419
|
`);
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3420
|
+
await executeStreamingCommand({
|
|
3421
|
+
ws,
|
|
3422
|
+
message,
|
|
3423
|
+
resolvedTarget,
|
|
3424
|
+
baseUrl,
|
|
3425
|
+
token,
|
|
3426
|
+
artifactRoot,
|
|
3427
|
+
planRoot
|
|
3428
|
+
});
|
|
3429
|
+
} finally {
|
|
3430
|
+
if (targetMayMutateVisibleWorkspace(message.target)) {
|
|
3431
|
+
markWorkspaceDirty(
|
|
3432
|
+
{
|
|
3433
|
+
type: "process_terminal",
|
|
3434
|
+
sessionId: message.sessionId,
|
|
3435
|
+
processRunId: message.runId,
|
|
3436
|
+
detail: "process completed"
|
|
3437
|
+
},
|
|
3438
|
+
true
|
|
3439
|
+
);
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3086
3442
|
};
|
|
3087
3443
|
const execution = (0, import_workspace_command_sync_policy.runWorkspaceCommand)(message.target, workspaceSyncSingleFlight, runCommand);
|
|
3088
3444
|
void execution.catch((error) => {
|
|
@@ -3096,9 +3452,8 @@ async function startWorker(options) {
|
|
|
3096
3452
|
return;
|
|
3097
3453
|
}
|
|
3098
3454
|
if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes") {
|
|
3099
|
-
if (message.type === "write" || message.type === "edit") recentWorkspaceSyncProof.invalidate();
|
|
3100
3455
|
try {
|
|
3101
|
-
const result = await
|
|
3456
|
+
const result = await (0, import_workspace_command_sync_policy.runWorkspaceCommand)(message.target, workspaceSyncSingleFlight, async () => {
|
|
3102
3457
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
3103
3458
|
return await executeOperation({
|
|
3104
3459
|
message,
|
|
@@ -3116,6 +3471,14 @@ async function startWorker(options) {
|
|
|
3116
3471
|
result
|
|
3117
3472
|
})
|
|
3118
3473
|
);
|
|
3474
|
+
if ((message.type === "write" || message.type === "edit") && targetMayMutateVisibleWorkspace(message.target)) {
|
|
3475
|
+
markWorkspaceDirty({
|
|
3476
|
+
type: message.type,
|
|
3477
|
+
sessionId: message.sessionId,
|
|
3478
|
+
toolCallId: message.requestId,
|
|
3479
|
+
...message.target.type === "project" ? { projectId: message.target.projectId, branchName: message.target.branchName } : {}
|
|
3480
|
+
});
|
|
3481
|
+
}
|
|
3119
3482
|
} catch (error) {
|
|
3120
3483
|
ws.send(
|
|
3121
3484
|
JSON.stringify({
|
|
@@ -3145,11 +3508,23 @@ async function startWorker(options) {
|
|
|
3145
3508
|
});
|
|
3146
3509
|
});
|
|
3147
3510
|
ws.addEventListener("close", (event) => {
|
|
3148
|
-
|
|
3511
|
+
process.off("SIGTERM", handleGracefulShutdown);
|
|
3512
|
+
process.off("SIGINT", handleGracefulShutdown);
|
|
3149
3513
|
stopHeartbeatWatchdog();
|
|
3150
|
-
if (
|
|
3151
|
-
|
|
3152
|
-
|
|
3514
|
+
if (workspacePublicationTimer) {
|
|
3515
|
+
clearTimeout(workspacePublicationTimer);
|
|
3516
|
+
workspacePublicationTimer = void 0;
|
|
3517
|
+
}
|
|
3518
|
+
if (!pendingPublication?.workInFlight) {
|
|
3519
|
+
workspaceConvergence.resetTransientPublicationState();
|
|
3520
|
+
}
|
|
3521
|
+
if (workspaceSafetyScan) {
|
|
3522
|
+
clearInterval(workspaceSafetyScan);
|
|
3523
|
+
workspaceSafetyScan = void 0;
|
|
3524
|
+
}
|
|
3525
|
+
if (workspaceManifestHydrationTimer) {
|
|
3526
|
+
clearTimeout(workspaceManifestHydrationTimer);
|
|
3527
|
+
workspaceManifestHydrationTimer = void 0;
|
|
3153
3528
|
}
|
|
3154
3529
|
if (terminalReplayTimer) {
|
|
3155
3530
|
clearInterval(terminalReplayTimer);
|
|
@@ -3165,6 +3540,9 @@ async function startWorker(options) {
|
|
|
3165
3540
|
if (reloadAfterClose) {
|
|
3166
3541
|
process.exit(import_supervisor.WORKER_RELOAD_EXIT_CODE);
|
|
3167
3542
|
}
|
|
3543
|
+
if (shutdownAfterClose) {
|
|
3544
|
+
process.exit(0);
|
|
3545
|
+
}
|
|
3168
3546
|
if (activeProcesses.size > 0 || pendingProcessTerminals.size > 0) {
|
|
3169
3547
|
process.stderr.write(
|
|
3170
3548
|
`[r5d-worker] ${activeProcesses.size} process(es) active and ${pendingProcessTerminals.size} terminal report(s) pending; reconnecting in ${import_supervisor.WORKER_RECONNECT_DELAY_MS}ms
|