@ricsam/r5d-worker 0.0.111 → 0.0.112
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 +11 -6
- package/dist/cjs/command-launcher.cjs +345 -0
- package/dist/cjs/inline-output.cjs +62 -0
- package/dist/cjs/main.cjs +149 -102
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/supervisor.cjs +1 -1
- package/dist/mjs/command-launcher.mjs +316 -0
- package/dist/mjs/inline-output.mjs +37 -0
- package/dist/mjs/main.mjs +151 -103
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/supervisor.mjs +1 -1
- package/dist/types/command-launcher.d.ts +61 -0
- package/dist/types/inline-output.d.ts +3 -0
- package/dist/types/supervisor-launcher-cleanup-fixture.d.ts +1 -0
- package/package.json +1 -1
- package/dist/cjs/job-resources.cjs +0 -335
- package/dist/mjs/job-resources.mjs +0 -293
- package/dist/types/job-resources.d.ts +0 -61
package/dist/cjs/main.cjs
CHANGED
|
@@ -62,7 +62,8 @@ var import_plan_lint = require("./plan-lint.cjs");
|
|
|
62
62
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
63
63
|
var import_recovery_store = require("./recovery-store.cjs");
|
|
64
64
|
var import_recovery_output_window = require("./recovery-output-window.cjs");
|
|
65
|
-
var
|
|
65
|
+
var import_command_launcher = require("./command-launcher.cjs");
|
|
66
|
+
var import_inline_output = require("./inline-output.cjs");
|
|
66
67
|
var import_recovery_protocol = require("./recovery-protocol.cjs");
|
|
67
68
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
68
69
|
var import_node_crypto = require("node:crypto");
|
|
@@ -155,7 +156,7 @@ const PTY_INPUT_BUSY_GRACE_MS = 3e3;
|
|
|
155
156
|
const PTY_FOREGROUND_POLL_MS = 1e3;
|
|
156
157
|
const PTY_FOREGROUND_IDLE_ENABLED = process.env.R5D_PTY_FOREGROUND_IDLE !== "0";
|
|
157
158
|
const PTY_TMP_PATH_PREFIX = "r5d-worker-tmp://";
|
|
158
|
-
let
|
|
159
|
+
let workerCommandLauncher;
|
|
159
160
|
const activeProcesses = /* @__PURE__ */ new Map();
|
|
160
161
|
const credentialBearingProcessGroups = /* @__PURE__ */ new Map();
|
|
161
162
|
const credentialBearingProcessGroupTargets = /* @__PURE__ */ new Map();
|
|
@@ -175,6 +176,41 @@ let workerExecutionLeaseDeadline = 0;
|
|
|
175
176
|
let workerExecutionLeaseTimer;
|
|
176
177
|
let workerLeaseExpired = false;
|
|
177
178
|
const infrastructureStoppedRunIds = /* @__PURE__ */ new Set();
|
|
179
|
+
let launcherFailureInProgress = false;
|
|
180
|
+
let runtimeExitPromise;
|
|
181
|
+
function fenceWorkerRuntimeOperations() {
|
|
182
|
+
launcherFailureInProgress = true;
|
|
183
|
+
workerAdmissionGeneration += 1;
|
|
184
|
+
for (const operation of recoveryStore?.operations() ?? []) {
|
|
185
|
+
if (operation.state !== "completed") recoveryStore?.unknown(operation.requestId);
|
|
186
|
+
}
|
|
187
|
+
for (const runId of workerCommandLauncher?.activeIds ?? []) {
|
|
188
|
+
infrastructureStoppedRunIds.add(runId);
|
|
189
|
+
cancelledProcessRuns.add(runId);
|
|
190
|
+
recoveryStore?.unknownRun(runId);
|
|
191
|
+
}
|
|
192
|
+
for (const runId of activeProcesses.keys()) infrastructureStoppedRunIds.add(runId);
|
|
193
|
+
}
|
|
194
|
+
function exitWorkerRuntime(code) {
|
|
195
|
+
if (runtimeExitPromise) return runtimeExitPromise;
|
|
196
|
+
fenceWorkerRuntimeOperations();
|
|
197
|
+
runtimeExitPromise = (async () => {
|
|
198
|
+
await Promise.race([
|
|
199
|
+
Promise.allSettled([terminateActiveCredentialBearingChildren(), workerCommandLauncher?.close()]),
|
|
200
|
+
Bun.sleep(4e3)
|
|
201
|
+
]);
|
|
202
|
+
process.exit(code);
|
|
203
|
+
})();
|
|
204
|
+
return runtimeExitPromise;
|
|
205
|
+
}
|
|
206
|
+
function handleWorkerCommandLauncherFailure(error) {
|
|
207
|
+
if (!workerCommandLauncher || launcherFailureInProgress) return;
|
|
208
|
+
fenceWorkerRuntimeOperations();
|
|
209
|
+
process.stderr.write(`[r5d-worker] ${error.message}; stopping runtime for communication recovery
|
|
210
|
+
`);
|
|
211
|
+
currentWorkerSocket?.close(1012, "Command launcher recovery");
|
|
212
|
+
void exitWorkerRuntime(import_supervisor.WORKER_RECONNECT_EXIT_CODE);
|
|
213
|
+
}
|
|
178
214
|
function isWorkerCommunicationFailure(message) {
|
|
179
215
|
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(
|
|
180
216
|
message
|
|
@@ -204,22 +240,18 @@ function renewWorkerExecutionLease(leaseMs) {
|
|
|
204
240
|
() => {
|
|
205
241
|
if (Date.now() < workerExecutionLeaseDeadline) return;
|
|
206
242
|
workerLeaseExpired = true;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
} catch (error) {
|
|
210
|
-
process.stderr.write(`[r5d-worker] expired PTY resource termination failed: ${error}
|
|
243
|
+
void workerCommandLauncher?.cancelMatching((id) => id.startsWith("pty:")).catch((error) => {
|
|
244
|
+
process.stderr.write(`[r5d-worker] expired PTY launcher cancellation failed: ${error}
|
|
211
245
|
`);
|
|
212
|
-
}
|
|
246
|
+
});
|
|
213
247
|
closeAllPtys();
|
|
214
248
|
for (const operation of recoveryStore?.operations() ?? []) {
|
|
215
249
|
if (operation.sessionId && operation.state !== "completed") {
|
|
216
250
|
recoveryStore?.cancelSession(operation.sessionId);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
} catch (error) {
|
|
220
|
-
process.stderr.write(`[r5d-worker] expired command resource termination failed: ${error}
|
|
251
|
+
void workerCommandLauncher?.cancelSession(operation.sessionId).catch((error) => {
|
|
252
|
+
process.stderr.write(`[r5d-worker] expired command launcher cancellation failed: ${error}
|
|
221
253
|
`);
|
|
222
|
-
}
|
|
254
|
+
});
|
|
223
255
|
}
|
|
224
256
|
}
|
|
225
257
|
for (const [runId, active] of activeProcesses) {
|
|
@@ -1584,7 +1616,9 @@ async function fenceCredentialGenerationAtConfigureEntry(input) {
|
|
|
1584
1616
|
await input.terminatePreviousGeneration();
|
|
1585
1617
|
return true;
|
|
1586
1618
|
}
|
|
1587
|
-
function requireCredentialGenerationRestart(required, fatalExit = (exitCode2) =>
|
|
1619
|
+
function requireCredentialGenerationRestart(required, fatalExit = (exitCode2) => {
|
|
1620
|
+
void exitWorkerRuntime(exitCode2);
|
|
1621
|
+
}, exitCode = 1) {
|
|
1588
1622
|
if (!required) return;
|
|
1589
1623
|
fatalExit(exitCode);
|
|
1590
1624
|
throw new Error("Credential generation restart callback returned unexpectedly");
|
|
@@ -2750,27 +2784,28 @@ async function prepareShellEnvForTarget(input) {
|
|
|
2750
2784
|
...targetProcessEnv(input.rootDir, input.target)
|
|
2751
2785
|
};
|
|
2752
2786
|
}
|
|
2753
|
-
async function
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
process.stderr.write(`[r5d-worker]
|
|
2787
|
+
async function releaseWorkerCommandLaunch(resources) {
|
|
2788
|
+
await resources?.release();
|
|
2789
|
+
}
|
|
2790
|
+
function cancelWorkerCommandLaunch(resources) {
|
|
2791
|
+
void resources.cancel().catch((error) => {
|
|
2792
|
+
process.stderr.write(`[r5d-worker] command launcher cancellation failed: ${error instanceof Error ? error.message : String(error)}
|
|
2759
2793
|
`);
|
|
2760
|
-
}
|
|
2794
|
+
});
|
|
2761
2795
|
}
|
|
2762
|
-
async function
|
|
2796
|
+
async function acquireWorkerCommandLaunch(ws, message, assertAdmission) {
|
|
2763
2797
|
const id = message.type === "pty_open" ? `pty:${message.ptyId}` : message.runId;
|
|
2764
2798
|
const operationId = message.type === "pty_open" ? id : `exec:${id}`;
|
|
2765
2799
|
sendWorkerMessage(ws, { type: "heartbeat_lease", operationId, active: true });
|
|
2766
2800
|
try {
|
|
2767
|
-
return await
|
|
2801
|
+
return await workerCommandLauncher.acquire({
|
|
2768
2802
|
id,
|
|
2769
|
-
|
|
2803
|
+
kind: message.type,
|
|
2804
|
+
..."workspaceEffect" in message && message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {},
|
|
2770
2805
|
..."sessionId" in message && message.sessionId ? { sessionId: message.sessionId } : {},
|
|
2771
2806
|
assertAdmission: () => {
|
|
2772
2807
|
assertAdmission();
|
|
2773
|
-
if (cancelledProcessRuns.has(id)) throw new
|
|
2808
|
+
if (cancelledProcessRuns.has(id)) throw new import_command_launcher.CommandLaunchCancelledError();
|
|
2774
2809
|
}
|
|
2775
2810
|
});
|
|
2776
2811
|
} finally {
|
|
@@ -2829,7 +2864,7 @@ async function executeCommand(input) {
|
|
|
2829
2864
|
settlePreparation();
|
|
2830
2865
|
if (input.message.timeoutMs) {
|
|
2831
2866
|
timeout = setTimeout(() => {
|
|
2832
|
-
input.resources
|
|
2867
|
+
cancelWorkerCommandLaunch(input.resources);
|
|
2833
2868
|
void (0, import_process_tree.terminateProcessTree)(subprocess).catch((error) => {
|
|
2834
2869
|
process.stderr.write(
|
|
2835
2870
|
`[r5d-worker] failed to terminate timed-out command ${input.message.runId}: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -2839,10 +2874,11 @@ async function executeCommand(input) {
|
|
|
2839
2874
|
}, input.message.timeoutMs);
|
|
2840
2875
|
}
|
|
2841
2876
|
outputReaders = [
|
|
2842
|
-
(0,
|
|
2843
|
-
(0,
|
|
2877
|
+
(0, import_inline_output.collectWorkerInlineOutput)(subprocess.stdout, void 0, outputAbort.signal),
|
|
2878
|
+
(0, import_inline_output.collectWorkerInlineOutput)(subprocess.stderr, void 0, outputAbort.signal)
|
|
2844
2879
|
];
|
|
2845
2880
|
const [stdout, stderr, exitCode] = await Promise.all([outputReaders[0], outputReaders[1], subprocess.exited]);
|
|
2881
|
+
const diagnostic = await input.resources.diagnose();
|
|
2846
2882
|
return {
|
|
2847
2883
|
type: "exec_result",
|
|
2848
2884
|
requestId: input.message.requestId,
|
|
@@ -2850,14 +2886,12 @@ async function executeCommand(input) {
|
|
|
2850
2886
|
stderr,
|
|
2851
2887
|
exitCode,
|
|
2852
2888
|
durationMs: Date.now() - startedAt,
|
|
2853
|
-
...
|
|
2854
|
-
error: `Command exceeded its worker memory limit (${input.resources.memoryBytes} bytes). Reduce parallelism or memory use and retry.`
|
|
2855
|
-
} : {}
|
|
2889
|
+
...diagnostic ? { error: diagnostic } : {}
|
|
2856
2890
|
};
|
|
2857
2891
|
} catch (error) {
|
|
2858
2892
|
outputAbort.abort(error);
|
|
2859
|
-
if (error instanceof StaleWorkerAdmissionError) throw error;
|
|
2860
|
-
input.resources
|
|
2893
|
+
if (error instanceof StaleWorkerAdmissionError || error instanceof import_command_launcher.CommandLauncherUnavailableError) throw error;
|
|
2894
|
+
cancelWorkerCommandLaunch(input.resources);
|
|
2861
2895
|
return {
|
|
2862
2896
|
type: "exec_result",
|
|
2863
2897
|
requestId: input.message.requestId,
|
|
@@ -2877,12 +2911,14 @@ async function executeCommand(input) {
|
|
|
2877
2911
|
}
|
|
2878
2912
|
outputAbort.abort();
|
|
2879
2913
|
await Promise.allSettled(outputReaders);
|
|
2914
|
+
await releaseWorkerCommandLaunch(input.resources);
|
|
2880
2915
|
activeProcesses.delete(input.message.runId);
|
|
2881
2916
|
}
|
|
2882
2917
|
}
|
|
2883
2918
|
async function executeStreamingCommand(input) {
|
|
2884
2919
|
const startedAt = Date.now();
|
|
2885
2920
|
let started = false;
|
|
2921
|
+
let terminal;
|
|
2886
2922
|
let timedOut = false;
|
|
2887
2923
|
let timeout;
|
|
2888
2924
|
let spawnedProcess;
|
|
@@ -2894,7 +2930,7 @@ async function executeStreamingCommand(input) {
|
|
|
2894
2930
|
};
|
|
2895
2931
|
input.setPreparationHeartbeatLease(true);
|
|
2896
2932
|
try {
|
|
2897
|
-
if (cancelledProcessRuns.
|
|
2933
|
+
if (cancelledProcessRuns.has(input.message.runId)) {
|
|
2898
2934
|
sendWorkerMessage(input.ws, {
|
|
2899
2935
|
type: "exec_start_error",
|
|
2900
2936
|
requestId: input.message.requestId,
|
|
@@ -2957,7 +2993,7 @@ async function executeStreamingCommand(input) {
|
|
|
2957
2993
|
if (input.message.timeoutMs) {
|
|
2958
2994
|
timeout = setTimeout(() => {
|
|
2959
2995
|
timedOut = true;
|
|
2960
|
-
input.resources
|
|
2996
|
+
cancelWorkerCommandLaunch(input.resources);
|
|
2961
2997
|
closeProcessStdin(activeProcesses.get(input.message.runId));
|
|
2962
2998
|
void (0, import_process_tree.terminateProcessTree)(subprocess).catch((error) => {
|
|
2963
2999
|
process.stderr.write(
|
|
@@ -2986,18 +3022,19 @@ async function executeStreamingCommand(input) {
|
|
|
2986
3022
|
});
|
|
2987
3023
|
})
|
|
2988
3024
|
]);
|
|
2989
|
-
|
|
3025
|
+
const diagnostic = await input.resources.diagnose();
|
|
3026
|
+
if (diagnostic) {
|
|
2990
3027
|
sendWorkerMessage(input.ws, {
|
|
2991
3028
|
type: "exec_output",
|
|
2992
3029
|
runId: input.message.runId,
|
|
2993
3030
|
stream: "stderr",
|
|
2994
3031
|
data: `
|
|
2995
|
-
[r5d-worker]
|
|
3032
|
+
[r5d-worker] ${diagnostic}
|
|
2996
3033
|
`
|
|
2997
3034
|
});
|
|
2998
3035
|
}
|
|
2999
3036
|
const canonicalWorkspaceHead = canonicalSyncTerminalHead(input.resolvedTarget);
|
|
3000
|
-
|
|
3037
|
+
terminal = {
|
|
3001
3038
|
type: "exec_exit",
|
|
3002
3039
|
runId: input.message.runId,
|
|
3003
3040
|
exitCode,
|
|
@@ -3006,14 +3043,12 @@ async function executeStreamingCommand(input) {
|
|
|
3006
3043
|
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {},
|
|
3007
3044
|
...canonicalWorkspaceHead ? { canonicalWorkspaceHead } : {}
|
|
3008
3045
|
};
|
|
3009
|
-
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
3010
|
-
sendWorkerMessage(input.ws, terminal);
|
|
3011
3046
|
} catch (error) {
|
|
3012
|
-
if (!started && error instanceof StaleWorkerAdmissionError) throw error;
|
|
3047
|
+
if (error instanceof import_command_launcher.CommandLauncherUnavailableError || !started && error instanceof StaleWorkerAdmissionError) throw error;
|
|
3013
3048
|
const message = error instanceof Error ? error.message : String(error);
|
|
3014
3049
|
if (started) {
|
|
3015
3050
|
const canonicalWorkspaceHead = canonicalSyncTerminalHead(input.resolvedTarget);
|
|
3016
|
-
|
|
3051
|
+
terminal = {
|
|
3017
3052
|
type: "exec_error",
|
|
3018
3053
|
runId: input.message.runId,
|
|
3019
3054
|
error: message,
|
|
@@ -3021,8 +3056,6 @@ async function executeStreamingCommand(input) {
|
|
|
3021
3056
|
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {},
|
|
3022
3057
|
...canonicalWorkspaceHead ? { canonicalWorkspaceHead } : {}
|
|
3023
3058
|
};
|
|
3024
|
-
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
3025
|
-
sendWorkerMessage(input.ws, terminal);
|
|
3026
3059
|
} else {
|
|
3027
3060
|
sendWorkerMessage(input.ws, {
|
|
3028
3061
|
type: "exec_start_error",
|
|
@@ -3040,9 +3073,14 @@ async function executeStreamingCommand(input) {
|
|
|
3040
3073
|
if (spawnedProcess) {
|
|
3041
3074
|
await reapCompletedCredentialBearingProcessGroup(input.message.runId, spawnedProcess);
|
|
3042
3075
|
}
|
|
3076
|
+
await releaseWorkerCommandLaunch(input.resources);
|
|
3043
3077
|
activeProcesses.delete(input.message.runId);
|
|
3044
3078
|
cancelledProcessRuns.delete(input.message.runId);
|
|
3045
3079
|
}
|
|
3080
|
+
if (terminal) {
|
|
3081
|
+
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
3082
|
+
sendWorkerMessage(input.ws, terminal);
|
|
3083
|
+
}
|
|
3046
3084
|
}
|
|
3047
3085
|
async function reapCompletedCredentialBearingProcessGroup(runId, subprocess) {
|
|
3048
3086
|
try {
|
|
@@ -3082,6 +3120,7 @@ function sendReplayWorkerMessage(ws, message) {
|
|
|
3082
3120
|
return message.type === "exec_output" ? workerOutputWindow.send(message, send, target.bufferedAmount) : send(message);
|
|
3083
3121
|
}
|
|
3084
3122
|
function sendWorkerMessage(ws, message) {
|
|
3123
|
+
if (launcherFailureInProgress) return;
|
|
3085
3124
|
if (recoveryStore && "requestId" in message && typeof message.requestId === "string" && recoveryStore.isUnknown(message.requestId))
|
|
3086
3125
|
return;
|
|
3087
3126
|
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;
|
|
@@ -3443,6 +3482,29 @@ async function openPty(input) {
|
|
|
3443
3482
|
envFilesRemoved = true;
|
|
3444
3483
|
removePtyEnvFiles(stagedEnvFiles.paths);
|
|
3445
3484
|
};
|
|
3485
|
+
let terminalHandled = false;
|
|
3486
|
+
const finishPty = async (terminal) => {
|
|
3487
|
+
if (terminalHandled) return;
|
|
3488
|
+
terminalHandled = true;
|
|
3489
|
+
try {
|
|
3490
|
+
const diagnostic = await input.resources.diagnose();
|
|
3491
|
+
if (diagnostic) outputCoalescer.push(`\r
|
|
3492
|
+
[r5d-worker] ${diagnostic}\r
|
|
3493
|
+
`);
|
|
3494
|
+
} finally {
|
|
3495
|
+
cleanupEnvFiles();
|
|
3496
|
+
await releaseWorkerCommandLaunch(input.resources);
|
|
3497
|
+
outputCoalescer.flush();
|
|
3498
|
+
activePtys.delete(input.message.ptyId);
|
|
3499
|
+
input.releaseWorkspaceMutation?.();
|
|
3500
|
+
input.onTerminal?.();
|
|
3501
|
+
}
|
|
3502
|
+
sendWorkerMessage(input.ws, terminal);
|
|
3503
|
+
};
|
|
3504
|
+
const reportPtyCleanupFailure = (error) => {
|
|
3505
|
+
process.stderr.write(`[r5d-worker] PTY launcher cleanup failed: ${error instanceof Error ? error.message : String(error)}
|
|
3506
|
+
`);
|
|
3507
|
+
};
|
|
3446
3508
|
try {
|
|
3447
3509
|
const ptyProcess = createNodePtyBridge({
|
|
3448
3510
|
file: shellArgv[0],
|
|
@@ -3475,38 +3537,14 @@ async function openPty(input) {
|
|
|
3475
3537
|
outputCoalescer.push(data);
|
|
3476
3538
|
},
|
|
3477
3539
|
onExit: (event) => {
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
[r5d-worker] Terminal exceeded its memory limit (${input.resources.memoryBytes} bytes). Reduce parallelism or memory use and retry.\r
|
|
3482
|
-
`
|
|
3483
|
-
);
|
|
3484
|
-
outputCoalescer.flush();
|
|
3485
|
-
input.releaseWorkspaceMutation?.();
|
|
3486
|
-
activePtys.delete(input.message.ptyId);
|
|
3487
|
-
cleanupEnvFiles();
|
|
3488
|
-
input.onTerminal?.();
|
|
3489
|
-
void releaseWorkerJobResources(input.resources);
|
|
3490
|
-
sendWorkerMessage(input.ws, {
|
|
3491
|
-
type: "pty_exit",
|
|
3492
|
-
ptyId: input.message.ptyId,
|
|
3493
|
-
exitCode: event.exitCode,
|
|
3494
|
-
signal: event.signal
|
|
3495
|
-
});
|
|
3540
|
+
void finishPty({ type: "pty_exit", ptyId: input.message.ptyId, exitCode: event.exitCode, signal: event.signal }).catch(
|
|
3541
|
+
reportPtyCleanupFailure
|
|
3542
|
+
);
|
|
3496
3543
|
},
|
|
3497
3544
|
onError: (error) => {
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
cleanupEnvFiles();
|
|
3502
|
-
input.onTerminal?.();
|
|
3503
|
-
void releaseWorkerJobResources(input.resources);
|
|
3504
|
-
sendWorkerMessage(input.ws, {
|
|
3505
|
-
type: "pty_error",
|
|
3506
|
-
requestId: input.message.requestId,
|
|
3507
|
-
ptyId: input.message.ptyId,
|
|
3508
|
-
error: error.message
|
|
3509
|
-
});
|
|
3545
|
+
void finishPty({ type: "pty_error", requestId: input.message.requestId, ptyId: input.message.ptyId, error: error.message }).catch(
|
|
3546
|
+
reportPtyCleanupFailure
|
|
3547
|
+
);
|
|
3510
3548
|
},
|
|
3511
3549
|
onTerminationError: (error) => {
|
|
3512
3550
|
sendWorkerMessage(input.ws, {
|
|
@@ -3549,8 +3587,12 @@ function resizePty(message) {
|
|
|
3549
3587
|
}
|
|
3550
3588
|
ptyProcess.resize(Math.max(1, Math.min(Math.floor(message.cols || 80), 500)), Math.max(1, Math.min(Math.floor(message.rows || 24), 500)));
|
|
3551
3589
|
}
|
|
3552
|
-
function closePty(message) {
|
|
3553
|
-
|
|
3590
|
+
async function closePty(message) {
|
|
3591
|
+
try {
|
|
3592
|
+
await workerCommandLauncher?.cancel(`pty:${message.ptyId}`);
|
|
3593
|
+
} finally {
|
|
3594
|
+
activePtys.get(message.ptyId)?.kill();
|
|
3595
|
+
}
|
|
3554
3596
|
const ptyProcess = activePtys.get(message.ptyId);
|
|
3555
3597
|
if (!ptyProcess) {
|
|
3556
3598
|
return;
|
|
@@ -3642,9 +3684,15 @@ async function startWorker(options, projectRuntime = {
|
|
|
3642
3684
|
initializeCredentialStoreDirectoryOnce();
|
|
3643
3685
|
const label = requireValue(options.label, "Missing required --label <label>");
|
|
3644
3686
|
validateLabel(label);
|
|
3645
|
-
if (!
|
|
3646
|
-
|
|
3647
|
-
|
|
3687
|
+
if (!workerCommandLauncher) {
|
|
3688
|
+
const launcher = await (0, import_command_launcher.createWorkerCommandLauncher)({
|
|
3689
|
+
configuration: process.env.R5D_WORKER_COMMAND_LAUNCHER,
|
|
3690
|
+
env: (0, import_command_launcher.commandLauncherEnvironment)(),
|
|
3691
|
+
onFailure: handleWorkerCommandLauncherFailure
|
|
3692
|
+
});
|
|
3693
|
+
workerCommandLauncher = launcher;
|
|
3694
|
+
await launcher.initialize();
|
|
3695
|
+
process.stdout.write(`[r5d-worker] command launcher: ${launcher.mode}
|
|
3648
3696
|
`);
|
|
3649
3697
|
}
|
|
3650
3698
|
const rootDir = import_node_path.default.resolve(options.rootDir ?? process.env.R5D_ROOT ?? defaultRootDir());
|
|
@@ -4887,7 +4935,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
4887
4935
|
`[r5d-worker] workspace synchronization failed without a recoverable hydration basis; exiting for exact recovery after ${unsafeWorkspaceSyncRecoveryAttempts} in-process recovery attempt(s): ${errorMessage}
|
|
4888
4936
|
`
|
|
4889
4937
|
);
|
|
4890
|
-
|
|
4938
|
+
void exitWorkerRuntime(exitCode);
|
|
4891
4939
|
},
|
|
4892
4940
|
exitCode: 1
|
|
4893
4941
|
});
|
|
@@ -5698,7 +5746,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
5698
5746
|
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
5699
5747
|
ws.close(1e3, "Worker shutting down");
|
|
5700
5748
|
} else {
|
|
5701
|
-
|
|
5749
|
+
await exitWorkerRuntime(0);
|
|
5702
5750
|
}
|
|
5703
5751
|
})().catch((error) => {
|
|
5704
5752
|
process.stderr.write(`[r5d-worker] graceful workspace sync failed: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -5851,14 +5899,14 @@ async function startWorker(options, projectRuntime = {
|
|
|
5851
5899
|
}
|
|
5852
5900
|
if (message.type === "cancel_session") {
|
|
5853
5901
|
recoveryStore.cancelSession(message.sessionId);
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
[...activeProcesses].filter(([, active]) => active.sessionId === message.sessionId).map(async ([runId, active]) => {
|
|
5902
|
+
await Promise.all([
|
|
5903
|
+
workerCommandLauncher?.cancelSession(message.sessionId),
|
|
5904
|
+
...[...activeProcesses].filter(([, active]) => active.sessionId === message.sessionId).map(async ([runId, active]) => {
|
|
5857
5905
|
cancelledProcessRuns.add(runId);
|
|
5858
5906
|
closeProcessStdin(active);
|
|
5859
5907
|
await (0, import_process_tree.terminateProcessTree)(active.process);
|
|
5860
5908
|
})
|
|
5861
|
-
);
|
|
5909
|
+
]);
|
|
5862
5910
|
sendWorkerMessage(ws, { type: "cancel_session_ack", cancellationId: message.cancellationId });
|
|
5863
5911
|
return;
|
|
5864
5912
|
}
|
|
@@ -6305,7 +6353,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6305
6353
|
});
|
|
6306
6354
|
return;
|
|
6307
6355
|
}
|
|
6308
|
-
if (activeProcesses.size > 0 || activePtys.size > 0 || workspaceSyncRequestsInFlight > 0 || sessionArtifactSyncRequestsInFlight > 0) {
|
|
6356
|
+
if (activeProcesses.size > 0 || (workerCommandLauncher?.activeIds.length ?? 0) > 0 || activePtys.size > 0 || workspaceSyncRequestsInFlight > 0 || sessionArtifactSyncRequestsInFlight > 0) {
|
|
6309
6357
|
sendWorkerMessage(ws, {
|
|
6310
6358
|
type: "update_clis_result",
|
|
6311
6359
|
requestId: message.requestId,
|
|
@@ -6340,7 +6388,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6340
6388
|
if (message.type === "cancel") {
|
|
6341
6389
|
let resourceCancellationError;
|
|
6342
6390
|
try {
|
|
6343
|
-
|
|
6391
|
+
await workerCommandLauncher?.cancel(message.runId);
|
|
6344
6392
|
} catch (error) {
|
|
6345
6393
|
resourceCancellationError = error instanceof Error ? error.message : String(error);
|
|
6346
6394
|
}
|
|
@@ -6452,7 +6500,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6452
6500
|
let releaseWorkspaceMutation;
|
|
6453
6501
|
let mutationLeaseTransferred = false;
|
|
6454
6502
|
try {
|
|
6455
|
-
resources = await
|
|
6503
|
+
resources = await acquireWorkerCommandLaunch(ws, message, assertMessageAdmission);
|
|
6456
6504
|
await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(message.target, workspaceSyncSingleFlight, () => {
|
|
6457
6505
|
workspaceSyncPriorityPtyTargets.set(message.ptyId, message.target);
|
|
6458
6506
|
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
@@ -6491,8 +6539,8 @@ async function startWorker(options, projectRuntime = {
|
|
|
6491
6539
|
});
|
|
6492
6540
|
} finally {
|
|
6493
6541
|
workspaceSyncPriorityPtyTargets.delete(message.ptyId);
|
|
6542
|
+
if (!resourcesTransferred) await releaseWorkerCommandLaunch(resources);
|
|
6494
6543
|
if (!mutationLeaseTransferred) releaseWorkspaceMutation?.();
|
|
6495
|
-
if (!resourcesTransferred) await releaseWorkerJobResources(resources);
|
|
6496
6544
|
}
|
|
6497
6545
|
return;
|
|
6498
6546
|
}
|
|
@@ -6521,7 +6569,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6521
6569
|
}
|
|
6522
6570
|
if (message.type === "pty_close") {
|
|
6523
6571
|
const activePty = activePtys.get(message.ptyId);
|
|
6524
|
-
closePty(message);
|
|
6572
|
+
await closePty(message);
|
|
6525
6573
|
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
6526
6574
|
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} closed` }, true, activePty.target);
|
|
6527
6575
|
}
|
|
@@ -6555,7 +6603,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6555
6603
|
let targetReserved = false;
|
|
6556
6604
|
const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
|
|
6557
6605
|
try {
|
|
6558
|
-
resources = await
|
|
6606
|
+
resources = await acquireWorkerCommandLaunch(ws, message, assertMessageAdmission);
|
|
6559
6607
|
if (hasWorkspaceEffect) {
|
|
6560
6608
|
await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(message.target, workspaceSyncSingleFlight, () => {
|
|
6561
6609
|
workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
|
|
@@ -6609,7 +6657,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6609
6657
|
};
|
|
6610
6658
|
} finally {
|
|
6611
6659
|
if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
|
|
6612
|
-
await
|
|
6660
|
+
await releaseWorkerCommandLaunch(resources);
|
|
6613
6661
|
cancelledProcessRuns.delete(message.runId);
|
|
6614
6662
|
}
|
|
6615
6663
|
if (infrastructureStoppedRunIds.has(message.runId)) {
|
|
@@ -6683,7 +6731,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6683
6731
|
const execution = (async () => {
|
|
6684
6732
|
let targetReserved = false;
|
|
6685
6733
|
try {
|
|
6686
|
-
resources = await
|
|
6734
|
+
resources = await acquireWorkerCommandLaunch(ws, message, assertMessageAdmission);
|
|
6687
6735
|
if (hasWorkspaceEffect) {
|
|
6688
6736
|
await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(message.target, workspaceSyncSingleFlight, () => {
|
|
6689
6737
|
workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
|
|
@@ -6699,7 +6747,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6699
6747
|
});
|
|
6700
6748
|
} finally {
|
|
6701
6749
|
if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
|
|
6702
|
-
await
|
|
6750
|
+
await releaseWorkerCommandLaunch(resources);
|
|
6703
6751
|
cancelledProcessRuns.delete(message.runId);
|
|
6704
6752
|
}
|
|
6705
6753
|
})();
|
|
@@ -6831,11 +6879,10 @@ async function startWorker(options, projectRuntime = {
|
|
|
6831
6879
|
const reason = event.reason ? `: ${event.reason}` : "";
|
|
6832
6880
|
process.stderr.write(`[r5d-worker] disconnected (${event.code}${reason})
|
|
6833
6881
|
`);
|
|
6834
|
-
if (
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
process.exit(0);
|
|
6882
|
+
if (launcherFailureInProgress) return;
|
|
6883
|
+
if (reloadAfterClose || shutdownAfterClose) {
|
|
6884
|
+
void exitWorkerRuntime(reloadAfterClose ? import_supervisor.WORKER_RELOAD_EXIT_CODE : 0);
|
|
6885
|
+
return;
|
|
6839
6886
|
}
|
|
6840
6887
|
const reconnect = () => {
|
|
6841
6888
|
const delay = (0, import_supervisor.workerReconnectDelayMs)(Math.random, workerReconnectAttempt++);
|
|
@@ -6893,10 +6940,10 @@ async function main() {
|
|
|
6893
6940
|
try {
|
|
6894
6941
|
await startWorker(parsed.options);
|
|
6895
6942
|
} catch (error) {
|
|
6896
|
-
if (error instanceof WorkerServerUnavailableError) {
|
|
6943
|
+
if (error instanceof WorkerServerUnavailableError || error instanceof import_command_launcher.CommandLauncherUnavailableError) {
|
|
6897
6944
|
process.stderr.write(`[r5d-worker] ${error.message}
|
|
6898
6945
|
`);
|
|
6899
|
-
|
|
6946
|
+
await exitWorkerRuntime(import_supervisor.WORKER_RECONNECT_EXIT_CODE);
|
|
6900
6947
|
}
|
|
6901
6948
|
throw error;
|
|
6902
6949
|
}
|
|
@@ -6914,10 +6961,10 @@ function isCliEntrypoint() {
|
|
|
6914
6961
|
return typeof require === "function" && typeof module !== "undefined" && require.main === module;
|
|
6915
6962
|
}
|
|
6916
6963
|
if (isCliEntrypoint()) {
|
|
6917
|
-
main().catch((error) => {
|
|
6964
|
+
main().catch(async (error) => {
|
|
6918
6965
|
process.stderr.write(`[r5d-worker] ${error instanceof Error ? error.message : String(error)}
|
|
6919
6966
|
`);
|
|
6920
|
-
|
|
6967
|
+
await exitWorkerRuntime(1);
|
|
6921
6968
|
});
|
|
6922
6969
|
}
|
|
6923
6970
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/cjs/package.json
CHANGED
package/dist/cjs/supervisor.cjs
CHANGED
|
@@ -54,7 +54,7 @@ function workerReconnectDelayMs(random = Math.random, attempt = 0) {
|
|
|
54
54
|
return floor + Math.floor(sample * (ceiling - floor));
|
|
55
55
|
}
|
|
56
56
|
async function defaultRuntimeReaper(runtime) {
|
|
57
|
-
await (0, import_process_tree.terminateProcessTree)(runtime);
|
|
57
|
+
await (0, import_process_tree.terminateProcessTree)(runtime, { graceMs: 6e3 });
|
|
58
58
|
}
|
|
59
59
|
function isRetryableWorkerServerStatus(status) {
|
|
60
60
|
return status === 408 || status === 429 || status >= 500;
|