@ricsam/r5d-worker 0.0.108 → 0.0.109
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 +336 -125
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/recovery-output-window.cjs +77 -0
- package/dist/cjs/recovery-protocol.cjs +31 -0
- package/dist/cjs/recovery-store.cjs +229 -0
- package/dist/cjs/supervisor.cjs +11 -4
- package/dist/mjs/main.mjs +339 -125
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/recovery-output-window.mjs +53 -0
- package/dist/mjs/recovery-protocol.mjs +6 -0
- package/dist/mjs/recovery-store.mjs +194 -0
- package/dist/mjs/supervisor.mjs +10 -4
- package/dist/types/main.d.ts +9 -2
- package/dist/types/recovery-output-window.d.ts +19 -0
- package/dist/types/recovery-protocol.d.ts +59 -0
- package/dist/types/recovery-store.d.ts +38 -0
- package/dist/types/supervisor.d.ts +2 -1
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -60,6 +60,9 @@ var import_plan_paths = require("./plan-paths.cjs");
|
|
|
60
60
|
var import_plan_parser = require("./plan-parser.cjs");
|
|
61
61
|
var import_plan_lint = require("./plan-lint.cjs");
|
|
62
62
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
63
|
+
var import_recovery_store = require("./recovery-store.cjs");
|
|
64
|
+
var import_recovery_output_window = require("./recovery-output-window.cjs");
|
|
65
|
+
var import_recovery_protocol = require("./recovery-protocol.cjs");
|
|
63
66
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
64
67
|
var import_node_crypto = require("node:crypto");
|
|
65
68
|
var import_node_os = __toESM(require("node:os"), 1);
|
|
@@ -161,6 +164,63 @@ const cancelledProcessRuns = /* @__PURE__ */ new Set();
|
|
|
161
164
|
const activePtys = /* @__PURE__ */ new Map();
|
|
162
165
|
const workspaceSyncPriorityPtyTargets = /* @__PURE__ */ new Map();
|
|
163
166
|
let currentWorkerSocket = null;
|
|
167
|
+
const workerRuntimeId = crypto.randomUUID();
|
|
168
|
+
const workerOutputWindow = new import_recovery_output_window.WorkerOutputWindow();
|
|
169
|
+
let recoveryStore;
|
|
170
|
+
let workerReconnectAttempt = 0;
|
|
171
|
+
let workerRecoveryId;
|
|
172
|
+
let workerExecutionLeaseDeadline = 0;
|
|
173
|
+
let workerExecutionLeaseTimer;
|
|
174
|
+
let workerLeaseExpired = false;
|
|
175
|
+
const infrastructureStoppedRunIds = /* @__PURE__ */ new Set();
|
|
176
|
+
function isWorkerCommunicationFailure(message) {
|
|
177
|
+
return /Could not resolve host|Couldn't connect|Failed to connect|Connection reset|Connection timed out|remote end hung up|Empty reply|requested URL returned error: (?:408|429|5\d\d)|fetch failed|socket connection|ECONNRESET|ECONNREFUSED|ETIMEDOUT|Unable to connect|network connection was lost|Worker communication recovery expired|operation was aborted due to timeout|operation timed out|Failed to (?:fetch|download).*\((?:408|429|5\d\d)\)/i.test(
|
|
178
|
+
message
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
async function retryWorkerServerRead(read) {
|
|
182
|
+
let deadline;
|
|
183
|
+
let retry = 0;
|
|
184
|
+
while (true) {
|
|
185
|
+
try {
|
|
186
|
+
return await read();
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (!recoveryStore || !isWorkerCommunicationFailure(error instanceof Error ? error.message : String(error))) throw error;
|
|
189
|
+
deadline ??= Date.now() + import_recovery_protocol.WORKER_RECOVERY_TIMEOUT_MS;
|
|
190
|
+
if (workerLeaseExpired || Date.now() >= deadline) throw new WorkerServerUnavailableError("Worker communication recovery expired");
|
|
191
|
+
currentWorkerSocket?.close(1012, "Communication recovery");
|
|
192
|
+
await Bun.sleep((0, import_supervisor.workerReconnectDelayMs)(Math.random, retry++));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function renewWorkerExecutionLease(leaseMs) {
|
|
197
|
+
if (!Number.isFinite(leaseMs) || leaseMs <= 0) return;
|
|
198
|
+
workerLeaseExpired = false;
|
|
199
|
+
workerExecutionLeaseDeadline = Date.now() + Math.min(leaseMs, import_recovery_protocol.WORKER_RECOVERY_TIMEOUT_MS);
|
|
200
|
+
if (workerExecutionLeaseTimer) clearTimeout(workerExecutionLeaseTimer);
|
|
201
|
+
workerExecutionLeaseTimer = setTimeout(
|
|
202
|
+
() => {
|
|
203
|
+
if (Date.now() < workerExecutionLeaseDeadline) return;
|
|
204
|
+
workerLeaseExpired = true;
|
|
205
|
+
for (const operation of recoveryStore?.operations() ?? []) {
|
|
206
|
+
if (operation.sessionId && operation.state !== "completed") recoveryStore?.cancelSession(operation.sessionId);
|
|
207
|
+
}
|
|
208
|
+
for (const [runId, active] of activeProcesses) {
|
|
209
|
+
if (!active.sessionId) continue;
|
|
210
|
+
infrastructureStoppedRunIds.add(runId);
|
|
211
|
+
recoveryStore?.unknownRun(runId);
|
|
212
|
+
cancelledProcessRuns.add(runId);
|
|
213
|
+
closeProcessStdin(active);
|
|
214
|
+
void (0, import_process_tree.terminateProcessTree)(active.process).catch(
|
|
215
|
+
(error) => process.stderr.write(`[r5d-worker] execution lease termination failed: ${error}
|
|
216
|
+
`)
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
Math.min(leaseMs, import_recovery_protocol.WORKER_RECOVERY_TIMEOUT_MS)
|
|
221
|
+
);
|
|
222
|
+
workerExecutionLeaseTimer.unref();
|
|
223
|
+
}
|
|
164
224
|
let workerAdmissionGeneration = 0;
|
|
165
225
|
const workspaceMutationGate = new import_workspace_mutation_gate.WorkspaceMutationGate();
|
|
166
226
|
let workspaceSyncQueue = Promise.resolve();
|
|
@@ -619,40 +679,46 @@ function parseRemoteFileManifest(value, collectionKey, label) {
|
|
|
619
679
|
});
|
|
620
680
|
}
|
|
621
681
|
async function fetchRemoteFileManifest(input) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
682
|
+
return retryWorkerServerRead(async () => {
|
|
683
|
+
const response = await fetch(input.url, {
|
|
684
|
+
signal: AbortSignal.timeout(3e4),
|
|
685
|
+
headers: {
|
|
686
|
+
Authorization: `Bearer ${input.token}`
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
if (!response.ok) {
|
|
690
|
+
const detail = (await readResponseText(response)).trim();
|
|
691
|
+
throw new Error(`Failed to fetch ${input.label} manifest (${response.status})${detail ? `: ${detail}` : ""}`);
|
|
625
692
|
}
|
|
693
|
+
return parseRemoteFileManifest(await response.json(), input.collectionKey, input.label);
|
|
626
694
|
});
|
|
627
|
-
if (!response.ok) {
|
|
628
|
-
const detail = (await readResponseText(response)).trim();
|
|
629
|
-
throw new Error(`Failed to fetch ${input.label} manifest (${response.status})${detail ? `: ${detail}` : ""}`);
|
|
630
|
-
}
|
|
631
|
-
return parseRemoteFileManifest(await response.json(), input.collectionKey, input.label);
|
|
632
695
|
}
|
|
633
696
|
async function downloadRemoteSyncedFile(input) {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
697
|
+
return retryWorkerServerRead(async () => {
|
|
698
|
+
const targetPath = import_node_path.default.join(input.targetDir, input.file.filename);
|
|
699
|
+
assertInsideRoot(input.targetDir, targetPath, input.label);
|
|
700
|
+
const response = await fetch(input.url, {
|
|
701
|
+
signal: AbortSignal.timeout(3e4),
|
|
702
|
+
headers: {
|
|
703
|
+
Authorization: `Bearer ${input.token}`
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
if (!response.ok) {
|
|
707
|
+
const detail = (await readResponseText(response)).trim();
|
|
708
|
+
throw new Error(`Failed to download ${input.label} ${input.file.filename} (${response.status})${detail ? `: ${detail}` : ""}`);
|
|
639
709
|
}
|
|
710
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
711
|
+
const receivedHash = (0, import_node_crypto.createHash)("sha256").update(buffer).digest("hex");
|
|
712
|
+
if (receivedHash !== input.file.sha256) {
|
|
713
|
+
throw new Error(`Downloaded ${input.label} ${input.file.filename} failed sha256 verification`);
|
|
714
|
+
}
|
|
715
|
+
if (buffer.length !== input.file.size) {
|
|
716
|
+
throw new Error(`Downloaded ${input.label} ${input.file.filename} has unexpected size`);
|
|
717
|
+
}
|
|
718
|
+
const tempPath = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
|
|
719
|
+
import_node_fs.default.writeFileSync(tempPath, buffer);
|
|
720
|
+
import_node_fs.default.renameSync(tempPath, targetPath);
|
|
640
721
|
});
|
|
641
|
-
if (!response.ok) {
|
|
642
|
-
const detail = (await readResponseText(response)).trim();
|
|
643
|
-
throw new Error(`Failed to download ${input.label} ${input.file.filename} (${response.status})${detail ? `: ${detail}` : ""}`);
|
|
644
|
-
}
|
|
645
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
646
|
-
const receivedHash = (0, import_node_crypto.createHash)("sha256").update(buffer).digest("hex");
|
|
647
|
-
if (receivedHash !== input.file.sha256) {
|
|
648
|
-
throw new Error(`Downloaded ${input.label} ${input.file.filename} failed sha256 verification`);
|
|
649
|
-
}
|
|
650
|
-
if (buffer.length !== input.file.size) {
|
|
651
|
-
throw new Error(`Downloaded ${input.label} ${input.file.filename} has unexpected size`);
|
|
652
|
-
}
|
|
653
|
-
const tempPath = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
|
|
654
|
-
import_node_fs.default.writeFileSync(tempPath, buffer);
|
|
655
|
-
import_node_fs.default.renameSync(tempPath, targetPath);
|
|
656
722
|
}
|
|
657
723
|
async function syncRemoteFileSet(input) {
|
|
658
724
|
import_node_fs.default.mkdirSync(input.targetDir, { recursive: true });
|
|
@@ -779,6 +845,7 @@ async function verifyR5dctlAuth(baseUrl, token, workerLabel) {
|
|
|
779
845
|
let response;
|
|
780
846
|
try {
|
|
781
847
|
response = await fetch(statusUrl, {
|
|
848
|
+
signal: AbortSignal.timeout(3e4),
|
|
782
849
|
headers: {
|
|
783
850
|
Authorization: `Bearer ${token}`
|
|
784
851
|
}
|
|
@@ -2942,23 +3009,46 @@ function closeProcessStdin(active) {
|
|
|
2942
3009
|
}
|
|
2943
3010
|
active.stdin = void 0;
|
|
2944
3011
|
}
|
|
2945
|
-
function
|
|
3012
|
+
function sendReplayWorkerMessage(ws, message) {
|
|
2946
3013
|
const target = currentWorkerSocket ?? ws;
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
3014
|
+
if (target.readyState !== WebSocket.OPEN) return false;
|
|
3015
|
+
const send = (payload) => {
|
|
3016
|
+
try {
|
|
3017
|
+
target.send(JSON.stringify(payload));
|
|
3018
|
+
return true;
|
|
3019
|
+
} catch {
|
|
3020
|
+
return false;
|
|
3021
|
+
}
|
|
3022
|
+
};
|
|
3023
|
+
return message.type === "exec_output" ? workerOutputWindow.send(message, send, target.bufferedAmount) : send(message);
|
|
3024
|
+
}
|
|
3025
|
+
function sendWorkerMessage(ws, message) {
|
|
3026
|
+
if (recoveryStore && "requestId" in message && typeof message.requestId === "string" && recoveryStore.isUnknown(message.requestId))
|
|
3027
|
+
return;
|
|
3028
|
+
const transportError = "error" in message && typeof message.error === "string" ? message.error : message.type === "workspace_sync_result" && message.result.outcome === "failed" ? message.result.error : void 0;
|
|
3029
|
+
if (recoveryStore && transportError && isWorkerCommunicationFailure(transportError) && "requestId" in message && typeof message.requestId === "string") {
|
|
3030
|
+
recoveryStore.unknown(message.requestId);
|
|
3031
|
+
currentWorkerSocket?.close(1012, "Communication recovery");
|
|
3032
|
+
return;
|
|
3033
|
+
}
|
|
3034
|
+
const recorded = recoveryStore?.record(message) ?? message;
|
|
3035
|
+
if ((message.type === "exec_exit" || message.type === "exec_error") && infrastructureStoppedRunIds.has(message.runId)) {
|
|
3036
|
+
recoveryStore?.unknownRun(message.runId);
|
|
3037
|
+
return;
|
|
2952
3038
|
}
|
|
3039
|
+
if (message.type === "exec_output" && workerRecoveryId !== void 0) return;
|
|
3040
|
+
sendReplayWorkerMessage(ws, recorded);
|
|
3041
|
+
}
|
|
3042
|
+
function sendSerializedWorkerMessage(ws, serialized) {
|
|
3043
|
+
sendWorkerMessage(ws, JSON.parse(serialized));
|
|
2953
3044
|
}
|
|
2954
3045
|
function sendWorkerMessageFromCurrentSource(ws, message) {
|
|
2955
|
-
if (
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
} catch (error) {
|
|
2959
|
-
process.stderr.write(`[r5d-worker] failed to send ${message.type}: ${error instanceof Error ? error.message : String(error)}
|
|
2960
|
-
`);
|
|
3046
|
+
if ("requestId" in message && typeof message.requestId === "string" && message.type !== "workspace_configured") {
|
|
3047
|
+
sendWorkerMessage(ws, message);
|
|
3048
|
+
return;
|
|
2961
3049
|
}
|
|
3050
|
+
if (currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) return;
|
|
3051
|
+
sendWorkerMessage(ws, message);
|
|
2962
3052
|
}
|
|
2963
3053
|
function buildActiveProcessReports() {
|
|
2964
3054
|
return Array.from(activeProcesses.entries()).filter(([, active]) => active.sessionId.length > 0).map(([runId, active]) => ({
|
|
@@ -3478,13 +3568,15 @@ async function startWorker(options, projectRuntime = {
|
|
|
3478
3568
|
reconciledProjectConfigFingerprints: /* @__PURE__ */ new Map(),
|
|
3479
3569
|
gitIdentity: null
|
|
3480
3570
|
}) {
|
|
3481
|
-
advanceWorkerAdmissionGeneration();
|
|
3482
3571
|
const config = readConfig(options.configPath);
|
|
3483
3572
|
const { baseUrl, token } = resolveCredentials(options, config);
|
|
3484
3573
|
initializeCredentialStoreDirectoryOnce();
|
|
3485
3574
|
const label = requireValue(options.label, "Missing required --label <label>");
|
|
3486
3575
|
validateLabel(label);
|
|
3487
3576
|
const rootDir = import_node_path.default.resolve(options.rootDir ?? process.env.R5D_ROOT ?? defaultRootDir());
|
|
3577
|
+
recoveryStore ??= new import_recovery_store.WorkerRecoveryStore(
|
|
3578
|
+
import_node_path.default.join(rootDir, ".worker-recovery", (0, import_node_crypto.createHash)("sha256").update(`${baseUrl}\0${label}`).digest("hex"), "protocol.sqlite")
|
|
3579
|
+
);
|
|
3488
3580
|
const projectsRoot = import_node_path.default.join(rootDir, "projects");
|
|
3489
3581
|
const syncRoot = import_node_path.default.join(rootDir, "sync");
|
|
3490
3582
|
const workspaceShadowRoot = import_node_path.default.join(syncRoot, "workspace");
|
|
@@ -3505,7 +3597,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
3505
3597
|
`);
|
|
3506
3598
|
runGit(["--version"]);
|
|
3507
3599
|
await verifyR5dctlAuth(baseUrl, token, label);
|
|
3508
|
-
const initializedWorkspaceState = await workspaceSyncSingleFlight.runExclusive(() => {
|
|
3600
|
+
const initializedWorkspaceState = projectRuntime.initializedWorkspaceState ?? await workspaceSyncSingleFlight.runExclusive(() => {
|
|
3509
3601
|
import_node_fs.default.mkdirSync(projectsRoot, { recursive: true });
|
|
3510
3602
|
import_node_fs.default.mkdirSync(syncRoot, { recursive: true });
|
|
3511
3603
|
import_node_fs.default.mkdirSync(artifactRoot, { recursive: true });
|
|
@@ -3516,8 +3608,9 @@ async function startWorker(options, projectRuntime = {
|
|
|
3516
3608
|
});
|
|
3517
3609
|
return { store, state: store.read() };
|
|
3518
3610
|
});
|
|
3611
|
+
projectRuntime.initializedWorkspaceState = initializedWorkspaceState;
|
|
3519
3612
|
const projectWorkspaceStateStore = initializedWorkspaceState.store;
|
|
3520
|
-
let projectWorkspaceState = initializedWorkspaceState.
|
|
3613
|
+
let projectWorkspaceState = initializedWorkspaceState.store.read();
|
|
3521
3614
|
const { projectConfigById, readyProjectIds, reconciledProjectConfigFingerprints } = projectRuntime;
|
|
3522
3615
|
const pendingCheckouts = /* @__PURE__ */ new Map();
|
|
3523
3616
|
const lastObservedProjectHeads = /* @__PURE__ */ new Map();
|
|
@@ -3557,11 +3650,12 @@ async function startWorker(options, projectRuntime = {
|
|
|
3557
3650
|
let workspaceConfigurationReceiptGeneration = 0;
|
|
3558
3651
|
let workspaceAutomaticTimer;
|
|
3559
3652
|
let workspacePeriodicTimer;
|
|
3560
|
-
let pendingAutomaticTrigger;
|
|
3653
|
+
let pendingAutomaticTrigger = projectRuntime.pendingAutomaticTrigger;
|
|
3561
3654
|
const pendingCreatedBranchPublicationNotBefore = /* @__PURE__ */ new Map();
|
|
3562
3655
|
const workspaceProjectionLedger = new import_workspace_projection_ledger.WorkspaceProjectionLedger();
|
|
3563
3656
|
let automaticSyncInFlight = false;
|
|
3564
3657
|
let terminalReplayTimer;
|
|
3658
|
+
let outputReplayTimer;
|
|
3565
3659
|
let workspaceSyncRequestsInFlight = 0;
|
|
3566
3660
|
let lastWorkspaceSyncMirrorObservationMs = 0;
|
|
3567
3661
|
let lastInventoriedMissingCheckouts;
|
|
@@ -4653,7 +4747,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
4653
4747
|
});
|
|
4654
4748
|
const workspaceSyncAdmissionSnapshotsMatch = (left, right) => left.generation === right.generation && left.activeIncidentId === right.activeIncidentId && left.deferredIncidentId === right.deferredIncidentId;
|
|
4655
4749
|
const assertWorkspaceSyncStillAdmitted = (admitted, trigger) => {
|
|
4656
|
-
if (!workspaceSyncAdmissionSnapshotsMatch(admitted, captureWorkspaceSyncAdmission()) ||
|
|
4750
|
+
if (!workspaceSyncAdmissionSnapshotsMatch(admitted, captureWorkspaceSyncAdmission()) || !deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, trigger)) {
|
|
4657
4751
|
throw new WorkspaceSyncAdmissionChangedError("Workspace synchronization admission changed while asynchronous work was in flight");
|
|
4658
4752
|
}
|
|
4659
4753
|
};
|
|
@@ -4707,6 +4801,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
4707
4801
|
bouncePending: unsafeWorkspaceSyncBouncePending,
|
|
4708
4802
|
invalidateExecution: () => {
|
|
4709
4803
|
workspaceConfigured = false;
|
|
4804
|
+
projectRuntime.configuration = void 0;
|
|
4710
4805
|
advanceWorkerAdmissionGeneration();
|
|
4711
4806
|
},
|
|
4712
4807
|
scheduleRecoveryBounce: () => {
|
|
@@ -4817,7 +4912,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
4817
4912
|
if (currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
4818
4913
|
throw new Error("Cannot start autonomous workspace synchronization without an open worker control socket");
|
|
4819
4914
|
}
|
|
4820
|
-
ws
|
|
4915
|
+
sendSerializedWorkerMessage(ws, JSON.stringify({ type: "workspace_sync_started", attemptId }));
|
|
4821
4916
|
}
|
|
4822
4917
|
let queueMs = 0;
|
|
4823
4918
|
let prepareMs = 0;
|
|
@@ -4869,6 +4964,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
4869
4964
|
};
|
|
4870
4965
|
const scheduleAutomaticWorkspaceSync = (trigger, delayMs = WORKSPACE_GIT_QUIET_MS) => {
|
|
4871
4966
|
pendingAutomaticTrigger = trigger;
|
|
4967
|
+
projectRuntime.pendingAutomaticTrigger = trigger;
|
|
4872
4968
|
const pendingBranchDeferral = () => (0, import_workspace_automatic_sync_policy.pendingCreatedBranchAutomaticSyncDeferral)(
|
|
4873
4969
|
projectWorkspaceState.locallyPendingCreatedBranches,
|
|
4874
4970
|
activeWorkspaceMutationTargets(),
|
|
@@ -4891,6 +4987,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
4891
4987
|
return;
|
|
4892
4988
|
}
|
|
4893
4989
|
pendingAutomaticTrigger = void 0;
|
|
4990
|
+
projectRuntime.pendingAutomaticTrigger = void 0;
|
|
4894
4991
|
automaticSyncInFlight = true;
|
|
4895
4992
|
void runWorkspaceSync({ trigger: scheduledTrigger }).then((result) => {
|
|
4896
4993
|
if (result.outcome === "failed") {
|
|
@@ -4914,9 +5011,14 @@ async function startWorker(options, projectRuntime = {
|
|
|
4914
5011
|
workspaceAutomaticTimer.unref();
|
|
4915
5012
|
};
|
|
4916
5013
|
const markWorkspaceDirty = (trigger, force = false, target) => {
|
|
5014
|
+
if (projectRuntime.markWorkspaceDirty && projectRuntime.markWorkspaceDirty !== markWorkspaceDirty) {
|
|
5015
|
+
projectRuntime.markWorkspaceDirty(trigger, force, target);
|
|
5016
|
+
return;
|
|
5017
|
+
}
|
|
4917
5018
|
if (target) recordVisibleWorkspaceMutation(target);
|
|
4918
5019
|
scheduleAutomaticWorkspaceSync(trigger, force ? 0 : WORKSPACE_GIT_QUIET_MS);
|
|
4919
5020
|
};
|
|
5021
|
+
projectRuntime.markWorkspaceDirty = markWorkspaceDirty;
|
|
4920
5022
|
const targetMayMutateVisibleWorkspace = (target) => target.type === "project" || target.rootProfile === "visible_projects";
|
|
4921
5023
|
const resolveMessageTarget = (target) => {
|
|
4922
5024
|
if (!workspaceConfigured) throw new Error("Worker workspace configuration has not completed successfully");
|
|
@@ -5434,6 +5536,10 @@ async function startWorker(options, projectRuntime = {
|
|
|
5434
5536
|
Authorization: `Bearer ${token}`
|
|
5435
5537
|
}
|
|
5436
5538
|
});
|
|
5539
|
+
const connectionAttemptTimer = setTimeout(() => {
|
|
5540
|
+
if (ws.readyState === WebSocket.CONNECTING) ws.terminate();
|
|
5541
|
+
}, 3e4);
|
|
5542
|
+
connectionAttemptTimer.unref();
|
|
5437
5543
|
let lastServerControlMessageAtMs = performance.now();
|
|
5438
5544
|
let serverHeartbeatProbeStartedAtMs;
|
|
5439
5545
|
let lastServerHeartbeatSweepAtMs = performance.now();
|
|
@@ -5529,7 +5635,9 @@ async function startWorker(options, projectRuntime = {
|
|
|
5529
5635
|
process.on("SIGTERM", handleGracefulShutdown);
|
|
5530
5636
|
process.on("SIGINT", handleGracefulShutdown);
|
|
5531
5637
|
ws.addEventListener("open", () => {
|
|
5638
|
+
clearTimeout(connectionAttemptTimer);
|
|
5532
5639
|
currentWorkerSocket = ws;
|
|
5640
|
+
workerRecoveryId = "connecting";
|
|
5533
5641
|
noteServerControlActivity();
|
|
5534
5642
|
lastServerHeartbeatSweepAtMs = performance.now();
|
|
5535
5643
|
serverHeartbeatWatchdog = setInterval(() => {
|
|
@@ -5573,6 +5681,9 @@ async function startWorker(options, projectRuntime = {
|
|
|
5573
5681
|
});
|
|
5574
5682
|
const hello = {
|
|
5575
5683
|
type: "hello",
|
|
5684
|
+
resumableProtocol: import_recovery_protocol.WORKER_RESUMABLE_PROTOCOL,
|
|
5685
|
+
runtimeId: workerRuntimeId,
|
|
5686
|
+
ledgerId: recoveryStore.ledgerId,
|
|
5576
5687
|
startupReady: startupRecoveryComplete,
|
|
5577
5688
|
hostInfo: {
|
|
5578
5689
|
hostname: (0, import_node_os.hostname)(),
|
|
@@ -5597,15 +5708,17 @@ async function startWorker(options, projectRuntime = {
|
|
|
5597
5708
|
root: rootDir
|
|
5598
5709
|
}
|
|
5599
5710
|
};
|
|
5600
|
-
ws
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
terminalReplayTimer = setInterval(() => {
|
|
5606
|
-
for (const terminal of pendingProcessTerminals.values()) {
|
|
5607
|
-
sendWorkerMessage(ws, terminal);
|
|
5711
|
+
sendSerializedWorkerMessage(ws, JSON.stringify(hello));
|
|
5712
|
+
outputReplayTimer = setInterval(() => {
|
|
5713
|
+
if (workerRecoveryId !== void 0 || currentWorkerSocket !== ws) return;
|
|
5714
|
+
for (const output of recoveryStore.output()) {
|
|
5715
|
+
if (!sendReplayWorkerMessage(ws, output)) break;
|
|
5608
5716
|
}
|
|
5717
|
+
}, 250);
|
|
5718
|
+
outputReplayTimer.unref();
|
|
5719
|
+
terminalReplayTimer = setInterval(() => {
|
|
5720
|
+
if (workerRecoveryId !== void 0) return;
|
|
5721
|
+
for (const terminal of recoveryStore?.terminals() ?? []) sendReplayWorkerMessage(ws, terminal);
|
|
5609
5722
|
}, 5e3);
|
|
5610
5723
|
terminalReplayTimer.unref();
|
|
5611
5724
|
process.stdout.write("[r5d-worker] connected\n");
|
|
@@ -5613,18 +5726,103 @@ async function startWorker(options, projectRuntime = {
|
|
|
5613
5726
|
ws.addEventListener("message", (event) => {
|
|
5614
5727
|
noteServerControlActivity();
|
|
5615
5728
|
void (async () => {
|
|
5729
|
+
if (currentWorkerSocket !== ws) return;
|
|
5616
5730
|
const message = JSON.parse(String(event.data));
|
|
5731
|
+
if (message.type === "recovery_request") {
|
|
5732
|
+
workerRecoveryId = message.recoveryId;
|
|
5733
|
+
sendActiveProcessReport(ws);
|
|
5734
|
+
sendWorkerMessage(ws, {
|
|
5735
|
+
type: "recovery_state",
|
|
5736
|
+
recoveryId: message.recoveryId,
|
|
5737
|
+
runtimeId: workerRuntimeId,
|
|
5738
|
+
ledgerId: recoveryStore.ledgerId,
|
|
5739
|
+
operations: recoveryStore.operations()
|
|
5740
|
+
});
|
|
5741
|
+
const cursors = new Map(recoveryStore.outputCursors().map((cursor) => [cursor.runId, cursor]));
|
|
5742
|
+
for (const cursor of message.outputOffsets) cursors.set(cursor.runId, cursor);
|
|
5743
|
+
workerOutputWindow.reset([...cursors.values()]);
|
|
5744
|
+
for (const response of recoveryStore.responses(message.requests)) sendReplayWorkerMessage(ws, response);
|
|
5745
|
+
for (const output of recoveryStore.output(message.outputOffsets)) {
|
|
5746
|
+
while (true) {
|
|
5747
|
+
if (currentWorkerSocket !== ws || workerRecoveryId !== message.recoveryId || ws.readyState !== WebSocket.OPEN || workerLeaseExpired)
|
|
5748
|
+
return;
|
|
5749
|
+
if (sendReplayWorkerMessage(ws, output)) break;
|
|
5750
|
+
await Bun.sleep(10);
|
|
5751
|
+
}
|
|
5752
|
+
}
|
|
5753
|
+
if (currentWorkerSocket !== ws || workerRecoveryId !== message.recoveryId) return;
|
|
5754
|
+
for (const terminal of recoveryStore.terminals()) sendReplayWorkerMessage(ws, terminal);
|
|
5755
|
+
sendWorkerMessage(ws, { type: "recovery_complete", recoveryId: message.recoveryId });
|
|
5756
|
+
return;
|
|
5757
|
+
}
|
|
5758
|
+
if (message.type === "recovery_ready") {
|
|
5759
|
+
if (workerRecoveryId !== message.recoveryId) return;
|
|
5760
|
+
workerRecoveryId = void 0;
|
|
5761
|
+
workerReconnectAttempt = 0;
|
|
5762
|
+
renewWorkerExecutionLease(message.leaseMs);
|
|
5763
|
+
return;
|
|
5764
|
+
}
|
|
5765
|
+
if (message.type === "worker_execution_lease") {
|
|
5766
|
+
renewWorkerExecutionLease(message.leaseMs);
|
|
5767
|
+
return;
|
|
5768
|
+
}
|
|
5769
|
+
if (message.type === "operation_result_ack") {
|
|
5770
|
+
recoveryStore.acknowledgeResult(message.requestId);
|
|
5771
|
+
return;
|
|
5772
|
+
}
|
|
5773
|
+
if (message.type === "exec_output_ack") {
|
|
5774
|
+
recoveryStore.acknowledgeOutput(message.runId, message.stream, message.nextOffset);
|
|
5775
|
+
workerOutputWindow.acknowledge(message.runId, message.stream, message.nextOffset);
|
|
5776
|
+
return;
|
|
5777
|
+
}
|
|
5778
|
+
if (message.type === "cancel_session") {
|
|
5779
|
+
recoveryStore.cancelSession(message.sessionId);
|
|
5780
|
+
await Promise.all(
|
|
5781
|
+
[...activeProcesses].filter(([, active]) => active.sessionId === message.sessionId).map(async ([runId, active]) => {
|
|
5782
|
+
cancelledProcessRuns.add(runId);
|
|
5783
|
+
closeProcessStdin(active);
|
|
5784
|
+
await (0, import_process_tree.terminateProcessTree)(active.process);
|
|
5785
|
+
})
|
|
5786
|
+
);
|
|
5787
|
+
sendWorkerMessage(ws, { type: "cancel_session_ack", cancellationId: message.cancellationId });
|
|
5788
|
+
return;
|
|
5789
|
+
}
|
|
5617
5790
|
if (!startupRecoveryComplete && message.type !== "ping") await startupRecoveryPromise;
|
|
5618
5791
|
if (!startupRecoveryComplete && message.type !== "ping") return;
|
|
5792
|
+
if (currentWorkerSocket !== ws) return;
|
|
5793
|
+
const operationRequestId = "requestId" in message && typeof message.requestId === "string" && message.type !== "workspace_config" ? message.requestId : void 0;
|
|
5794
|
+
if (operationRequestId) {
|
|
5795
|
+
const admission = recoveryStore.admit({ ...message, requestId: operationRequestId });
|
|
5796
|
+
if (admission === "conflict") {
|
|
5797
|
+
ws.close(1008, "Operation identity conflict");
|
|
5798
|
+
return;
|
|
5799
|
+
}
|
|
5800
|
+
sendWorkerMessage(ws, { type: "operation_received", requestId: operationRequestId });
|
|
5801
|
+
if (admission !== "new") {
|
|
5802
|
+
const response = recoveryStore.response(operationRequestId);
|
|
5803
|
+
if (response && admission !== "unknown") sendReplayWorkerMessage(ws, response);
|
|
5804
|
+
return;
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
5619
5807
|
const messageAdmissionGeneration = workerAdmissionGeneration;
|
|
5620
|
-
const
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5808
|
+
const admissionCredentials = configuredCredentialGenerationFingerprint;
|
|
5809
|
+
const assertMessageAdmission = () => {
|
|
5810
|
+
if (operationRequestId) {
|
|
5811
|
+
if (workerLeaseExpired || recoveryStore.isRequestCancelled(operationRequestId) || admissionCredentials !== configuredCredentialGenerationFingerprint || shutdownAfterClose || !workspaceConfigured) {
|
|
5812
|
+
recoveryStore.unknown(operationRequestId);
|
|
5813
|
+
throw new StaleWorkerAdmissionError();
|
|
5814
|
+
}
|
|
5815
|
+
return;
|
|
5816
|
+
}
|
|
5817
|
+
assertWorkerChildAdmission({
|
|
5818
|
+
capturedGeneration: messageAdmissionGeneration,
|
|
5819
|
+
currentGeneration: workerAdmissionGeneration,
|
|
5820
|
+
sourceSocketIsCurrent: currentWorkerSocket === ws,
|
|
5821
|
+
sourceSocketOpen: ws.readyState === WebSocket.OPEN,
|
|
5822
|
+
workspaceConfigured,
|
|
5823
|
+
runtimeHealthy: !shutdownAfterClose
|
|
5824
|
+
});
|
|
5825
|
+
};
|
|
5628
5826
|
if (message.type === "connected") {
|
|
5629
5827
|
return;
|
|
5630
5828
|
}
|
|
@@ -5640,8 +5838,17 @@ async function startWorker(options, projectRuntime = {
|
|
|
5640
5838
|
if (message.type === "workspace_config") {
|
|
5641
5839
|
const receiptGeneration = ++workspaceConfigurationReceiptGeneration;
|
|
5642
5840
|
const refreshesDeferredConfiguration = deferredWorkspaceConfiguration !== null && activeWorkspaceIncidentId === null && message.deferWorkspaceSyncForIncidentId === void 0;
|
|
5643
|
-
|
|
5644
|
-
const
|
|
5841
|
+
const digest = (0, import_recovery_store.workerOperationDigest)(message);
|
|
5842
|
+
const cached = projectRuntime.configuration;
|
|
5843
|
+
if (cached?.digest !== digest) advanceWorkerAdmissionGeneration();
|
|
5844
|
+
const canReuseConfiguration = cached?.digest === digest && !unsafeWorkspaceSyncBouncePending && credentialGenerationFingerprint({ ...message, workerBaseUrl: baseUrl, workerAuthHeader: bearerAuthHeader }) === configuredCredentialGenerationFingerprint;
|
|
5845
|
+
const configured = canReuseConfiguration ? cached.configured : await configureWorkerWorkspace(message, receiptGeneration);
|
|
5846
|
+
if (canReuseConfiguration && configured.result.outcome !== "failed") {
|
|
5847
|
+
workspaceConfigured = true;
|
|
5848
|
+
activeWorkspaceIncidentId = cached.incidentId;
|
|
5849
|
+
deferredWorkspaceConfiguration = cached.deferred;
|
|
5850
|
+
}
|
|
5851
|
+
projectRuntime.configuration = configured.result.outcome !== "failed" ? { digest, configured, incidentId: activeWorkspaceIncidentId, deferred: deferredWorkspaceConfiguration } : void 0;
|
|
5645
5852
|
unsafeWorkspaceSyncBouncePending = false;
|
|
5646
5853
|
if (configured.result.outcome !== "failed") unsafeWorkspaceSyncRecoveryAttempts = 0;
|
|
5647
5854
|
sendWorkerMessageFromCurrentSource(ws, {
|
|
@@ -5687,16 +5894,31 @@ async function startWorker(options, projectRuntime = {
|
|
|
5687
5894
|
return;
|
|
5688
5895
|
}
|
|
5689
5896
|
if (message.type === "sync_workspace") {
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5897
|
+
let retry = 0;
|
|
5898
|
+
let recoveryDeadline;
|
|
5899
|
+
while (true) {
|
|
5900
|
+
const result = await runWorkspaceSync({
|
|
5901
|
+
requestId: message.requestId,
|
|
5902
|
+
attemptId: message.attemptId,
|
|
5903
|
+
trigger: message.trigger,
|
|
5904
|
+
confirmedLargeDiff: message.confirmedLargeDiff,
|
|
5905
|
+
confirmationReason: message.confirmationReason,
|
|
5906
|
+
resetToCanonical: message.resetToCanonical,
|
|
5907
|
+
requiredAncestorHeads: message.requiredAncestorHeads,
|
|
5908
|
+
sendResult: false
|
|
5909
|
+
});
|
|
5910
|
+
if (result.outcome !== "failed" || !isWorkerCommunicationFailure(result.error ?? "")) {
|
|
5911
|
+
sendWorkspaceSyncResult(message.requestId, result);
|
|
5912
|
+
return;
|
|
5913
|
+
}
|
|
5914
|
+
recoveryDeadline ??= Date.now() + import_recovery_protocol.WORKER_RECOVERY_TIMEOUT_MS;
|
|
5915
|
+
if (workerLeaseExpired || Date.now() >= recoveryDeadline) {
|
|
5916
|
+
recoveryStore.unknown(message.requestId);
|
|
5917
|
+
return;
|
|
5918
|
+
}
|
|
5919
|
+
currentWorkerSocket?.close(1012, "Communication recovery");
|
|
5920
|
+
await Bun.sleep((0, import_supervisor.workerReconnectDelayMs)(Math.random, retry++));
|
|
5921
|
+
}
|
|
5700
5922
|
}
|
|
5701
5923
|
if (message.type === "create_project_branch") {
|
|
5702
5924
|
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
@@ -5962,6 +6184,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
5962
6184
|
}
|
|
5963
6185
|
if (message.type === "exec_terminal_ack") {
|
|
5964
6186
|
pendingProcessTerminals.delete(message.runId);
|
|
6187
|
+
recoveryStore.acknowledgeTerminal(message.runId);
|
|
5965
6188
|
return;
|
|
5966
6189
|
}
|
|
5967
6190
|
if (message.type === "port_forward_connect") {
|
|
@@ -6013,7 +6236,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6013
6236
|
return;
|
|
6014
6237
|
}
|
|
6015
6238
|
if (message.type === "ping") {
|
|
6016
|
-
ws
|
|
6239
|
+
sendSerializedWorkerMessage(ws, JSON.stringify({ type: "pong" }));
|
|
6017
6240
|
return;
|
|
6018
6241
|
}
|
|
6019
6242
|
if (message.type === "cancel") {
|
|
@@ -6033,7 +6256,8 @@ async function startWorker(options, projectRuntime = {
|
|
|
6033
6256
|
cancelledProcessRuns.add(message.runId);
|
|
6034
6257
|
cancelMessage = "Cancellation queued before command start";
|
|
6035
6258
|
}
|
|
6036
|
-
|
|
6259
|
+
sendSerializedWorkerMessage(
|
|
6260
|
+
ws,
|
|
6037
6261
|
JSON.stringify({
|
|
6038
6262
|
type: "cancel_result",
|
|
6039
6263
|
requestId: message.requestId,
|
|
@@ -6045,7 +6269,8 @@ async function startWorker(options, projectRuntime = {
|
|
|
6045
6269
|
return;
|
|
6046
6270
|
}
|
|
6047
6271
|
if (message.type === "exec_stdin") {
|
|
6048
|
-
const sendAck = (payload) =>
|
|
6272
|
+
const sendAck = (payload) => sendSerializedWorkerMessage(
|
|
6273
|
+
ws,
|
|
6049
6274
|
JSON.stringify({
|
|
6050
6275
|
type: "operation_result",
|
|
6051
6276
|
requestId: message.requestId,
|
|
@@ -6168,16 +6393,19 @@ async function startWorker(options, projectRuntime = {
|
|
|
6168
6393
|
ptyId: message.ptyId,
|
|
6169
6394
|
error: `Shell input is deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
|
|
6170
6395
|
});
|
|
6396
|
+
sendWorkerMessage(ws, { type: "operation_completed", requestId: message.requestId });
|
|
6171
6397
|
return;
|
|
6172
6398
|
}
|
|
6173
6399
|
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
6174
6400
|
markWorkspaceDirty({ type: "shell_inline", detail: `pty ${message.ptyId}` }, false, activePty.target);
|
|
6175
6401
|
}
|
|
6176
6402
|
writePty(ws, message);
|
|
6403
|
+
sendWorkerMessage(ws, { type: "operation_completed", requestId: message.requestId });
|
|
6177
6404
|
return;
|
|
6178
6405
|
}
|
|
6179
6406
|
if (message.type === "pty_resize") {
|
|
6180
6407
|
resizePty(message);
|
|
6408
|
+
sendWorkerMessage(ws, { type: "operation_completed", requestId: message.requestId });
|
|
6181
6409
|
return;
|
|
6182
6410
|
}
|
|
6183
6411
|
if (message.type === "pty_close") {
|
|
@@ -6186,6 +6414,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6186
6414
|
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
6187
6415
|
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} closed` }, true, activePty.target);
|
|
6188
6416
|
}
|
|
6417
|
+
sendWorkerMessage(ws, { type: "operation_completed", requestId: message.requestId });
|
|
6189
6418
|
return;
|
|
6190
6419
|
}
|
|
6191
6420
|
if (message.type === "exec") {
|
|
@@ -6267,7 +6496,11 @@ async function startWorker(options, projectRuntime = {
|
|
|
6267
6496
|
} finally {
|
|
6268
6497
|
if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
|
|
6269
6498
|
}
|
|
6270
|
-
|
|
6499
|
+
if (infrastructureStoppedRunIds.has(message.runId)) {
|
|
6500
|
+
recoveryStore.unknown(message.requestId);
|
|
6501
|
+
return;
|
|
6502
|
+
}
|
|
6503
|
+
sendSerializedWorkerMessage(ws, JSON.stringify(result));
|
|
6271
6504
|
if (hasWorkspaceEffect && targetMayMutateVisibleWorkspace(message.target)) {
|
|
6272
6505
|
markWorkspaceDirty(
|
|
6273
6506
|
{ type: "shell_inline", sessionId: message.sessionId, processRunId: message.runId, detail: "foreground command completed" },
|
|
@@ -6390,7 +6623,8 @@ async function startWorker(options, projectRuntime = {
|
|
|
6390
6623
|
assertAdmission: assertMessageAdmission
|
|
6391
6624
|
});
|
|
6392
6625
|
});
|
|
6393
|
-
|
|
6626
|
+
sendSerializedWorkerMessage(
|
|
6627
|
+
ws,
|
|
6394
6628
|
JSON.stringify({
|
|
6395
6629
|
type: "operation_result",
|
|
6396
6630
|
requestId: message.requestId,
|
|
@@ -6407,7 +6641,8 @@ async function startWorker(options, projectRuntime = {
|
|
|
6407
6641
|
markWorkspaceDirty(dirtyTrigger, false, message.target);
|
|
6408
6642
|
}
|
|
6409
6643
|
} catch (error) {
|
|
6410
|
-
|
|
6644
|
+
sendSerializedWorkerMessage(
|
|
6645
|
+
ws,
|
|
6411
6646
|
JSON.stringify({
|
|
6412
6647
|
type: "operation_result",
|
|
6413
6648
|
requestId: message.requestId,
|
|
@@ -6426,7 +6661,8 @@ async function startWorker(options, projectRuntime = {
|
|
|
6426
6661
|
}
|
|
6427
6662
|
const unhandledMessage = message;
|
|
6428
6663
|
if (typeof unhandledMessage.requestId === "string") {
|
|
6429
|
-
|
|
6664
|
+
sendSerializedWorkerMessage(
|
|
6665
|
+
ws,
|
|
6430
6666
|
JSON.stringify({
|
|
6431
6667
|
type: "operation_result",
|
|
6432
6668
|
requestId: unhandledMessage.requestId,
|
|
@@ -6440,6 +6676,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6440
6676
|
});
|
|
6441
6677
|
});
|
|
6442
6678
|
ws.addEventListener("close", (event) => {
|
|
6679
|
+
clearTimeout(connectionAttemptTimer);
|
|
6443
6680
|
const processEvents = process;
|
|
6444
6681
|
processEvents.off("SIGTERM", handleGracefulShutdown);
|
|
6445
6682
|
processEvents.off("SIGINT", handleGracefulShutdown);
|
|
@@ -6456,15 +6693,20 @@ async function startWorker(options, projectRuntime = {
|
|
|
6456
6693
|
clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
|
|
6457
6694
|
deferredWorkspaceConfigurationRefreshTimer = void 0;
|
|
6458
6695
|
}
|
|
6696
|
+
if (outputReplayTimer) {
|
|
6697
|
+
clearInterval(outputReplayTimer);
|
|
6698
|
+
outputReplayTimer = void 0;
|
|
6699
|
+
}
|
|
6459
6700
|
if (terminalReplayTimer) {
|
|
6460
6701
|
clearInterval(terminalReplayTimer);
|
|
6461
6702
|
terminalReplayTimer = void 0;
|
|
6462
6703
|
}
|
|
6463
|
-
if (
|
|
6464
|
-
|
|
6465
|
-
|
|
6704
|
+
if (projectRuntime.configuration) {
|
|
6705
|
+
projectRuntime.configuration.incidentId = activeWorkspaceIncidentId;
|
|
6706
|
+
projectRuntime.configuration.deferred = deferredWorkspaceConfiguration;
|
|
6466
6707
|
}
|
|
6467
|
-
|
|
6708
|
+
if (currentWorkerSocket === ws) currentWorkerSocket = null;
|
|
6709
|
+
workerRecoveryId = "reconnecting";
|
|
6468
6710
|
const reason = event.reason ? `: ${event.reason}` : "";
|
|
6469
6711
|
process.stderr.write(`[r5d-worker] disconnected (${event.code}${reason})
|
|
6470
6712
|
`);
|
|
@@ -6474,50 +6716,19 @@ async function startWorker(options, projectRuntime = {
|
|
|
6474
6716
|
if (shutdownAfterClose) {
|
|
6475
6717
|
process.exit(0);
|
|
6476
6718
|
}
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
if (!startupRecoveryComplete) {
|
|
6481
|
-
process.exit(1);
|
|
6482
|
-
return;
|
|
6483
|
-
}
|
|
6484
|
-
const reconnectDelayMs = (0, import_supervisor.workerReconnectDelayMs)();
|
|
6485
|
-
process.stderr.write(`[r5d-worker] workspace recovery complete; reconnecting in ${reconnectDelayMs}ms
|
|
6486
|
-
`);
|
|
6487
|
-
setTimeout(() => {
|
|
6488
|
-
void startWorker(options, projectRuntime).catch((error) => {
|
|
6489
|
-
process.stderr.write(`[r5d-worker] reconnect failed: ${error instanceof Error ? error.message : String(error)}
|
|
6719
|
+
const reconnect = () => {
|
|
6720
|
+
const delay = (0, import_supervisor.workerReconnectDelayMs)(Math.random, workerReconnectAttempt++);
|
|
6721
|
+
process.stderr.write(`[r5d-worker] reconnecting in ${delay}ms
|
|
6490
6722
|
`);
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
}, reconnectDelayMs);
|
|
6494
|
-
});
|
|
6495
|
-
return;
|
|
6496
|
-
}
|
|
6497
|
-
if (activeProcesses.size > 0 || credentialBearingProcessGroups.size > 0 || activePtys.size > 0 || pendingProcessTerminals.size > 0) {
|
|
6498
|
-
const scheduleReconnect = (reconnect2) => {
|
|
6499
|
-
const reconnectDelayMs = (0, import_supervisor.workerReconnectDelayMs)();
|
|
6500
|
-
process.stderr.write(
|
|
6501
|
-
`[r5d-worker] ${activeProcesses.size} process(es), ${credentialBearingProcessGroups.size} owned process group(s), and ${activePtys.size} PTY(s) active with ${pendingProcessTerminals.size} terminal report(s) pending; reconnecting in ${reconnectDelayMs}ms
|
|
6502
|
-
`
|
|
6503
|
-
);
|
|
6504
|
-
setTimeout(reconnect2, reconnectDelayMs);
|
|
6505
|
-
};
|
|
6506
|
-
const reconnect = () => {
|
|
6507
|
-
void startWorker(options, projectRuntime).catch((error) => {
|
|
6723
|
+
setTimeout(() => {
|
|
6724
|
+
void startupRecoveryPromise.then(() => startWorker(options, projectRuntime)).catch((error) => {
|
|
6508
6725
|
process.stderr.write(`[r5d-worker] reconnect failed: ${error instanceof Error ? error.message : String(error)}
|
|
6509
6726
|
`);
|
|
6510
|
-
|
|
6511
|
-
scheduleReconnect(reconnect);
|
|
6512
|
-
return;
|
|
6513
|
-
}
|
|
6514
|
-
process.exit(error instanceof WorkerServerUnavailableError ? import_supervisor.WORKER_RECONNECT_EXIT_CODE : 1);
|
|
6727
|
+
reconnect();
|
|
6515
6728
|
});
|
|
6516
|
-
};
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
}
|
|
6520
|
-
process.exit(import_supervisor.WORKER_RECONNECT_EXIT_CODE);
|
|
6729
|
+
}, delay);
|
|
6730
|
+
};
|
|
6731
|
+
reconnect();
|
|
6521
6732
|
});
|
|
6522
6733
|
ws.addEventListener("error", (event) => {
|
|
6523
6734
|
const message = "message" in event && typeof event.message === "string" && event.message.trim() ? `: ${event.message.trim()}` : "";
|