@ricsam/r5d-worker 0.0.122 → 0.0.124
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 +6 -3
- package/dist/cjs/command-launcher.cjs +76 -2
- package/dist/cjs/control-command-policy.cjs +177 -0
- package/dist/cjs/main.cjs +541 -247
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-checkout-garbage.cjs +32 -3
- package/dist/cjs/project-workspace-state.cjs +51 -35
- package/dist/cjs/project-worktrees.cjs +79 -34
- package/dist/cjs/recovery-journal-protocol.cjs +56 -0
- package/dist/cjs/recovery-journal-runtime.cjs +133 -0
- package/dist/cjs/recovery-journal-thread.cjs +9 -0
- package/dist/cjs/recovery-journal.cjs +735 -0
- package/dist/cjs/recovery-store.cjs +8 -0
- package/dist/cjs/session-file-mutations.cjs +61 -0
- package/dist/cjs/working-tree-mirror.cjs +1 -0
- package/dist/cjs/workspace-command-sync-policy.cjs +37 -8
- package/dist/cjs/workspace-filesystem-executor-thread.cjs +36 -0
- package/dist/cjs/workspace-filesystem-executor.cjs +327 -0
- package/dist/cjs/workspace-filesystem-job-types.cjs +134 -0
- package/dist/cjs/workspace-filesystem-jobs.cjs +57 -0
- package/dist/cjs/workspace-git-sync.cjs +275 -201
- package/dist/cjs/workspace-mount-hold-fence.cjs +120 -0
- package/dist/mjs/command-launcher.mjs +75 -2
- package/dist/mjs/control-command-policy.mjs +146 -0
- package/dist/mjs/main.mjs +549 -253
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-checkout-garbage.mjs +30 -2
- package/dist/mjs/project-workspace-state.mjs +51 -35
- package/dist/mjs/project-worktrees.mjs +74 -34
- package/dist/mjs/recovery-journal-protocol.mjs +30 -0
- package/dist/mjs/recovery-journal-runtime.mjs +112 -0
- package/dist/mjs/recovery-journal-thread.mjs +8 -0
- package/dist/mjs/recovery-journal.mjs +687 -0
- package/dist/mjs/recovery-store.mjs +8 -0
- package/dist/mjs/session-file-mutations.mjs +37 -0
- package/dist/mjs/working-tree-mirror.mjs +1 -0
- package/dist/mjs/workspace-command-sync-policy.mjs +37 -8
- package/dist/mjs/workspace-filesystem-executor-thread.mjs +38 -0
- package/dist/mjs/workspace-filesystem-executor.mjs +287 -0
- package/dist/mjs/workspace-filesystem-job-types.mjs +106 -0
- package/dist/mjs/workspace-filesystem-jobs.mjs +51 -0
- package/dist/mjs/workspace-git-sync.mjs +264 -202
- package/dist/mjs/workspace-mount-hold-fence.mjs +95 -0
- package/dist/types/command-launcher.d.ts +42 -0
- package/dist/types/control-command-policy.d.ts +53 -0
- package/dist/types/main.d.ts +31 -19
- package/dist/types/project-checkout-garbage.d.ts +19 -2
- package/dist/types/project-workspace-state.d.ts +9 -9
- package/dist/types/project-worktrees.d.ts +49 -5
- package/dist/types/recovery-journal-protocol.d.ts +35 -0
- package/dist/types/recovery-journal-runtime.d.ts +12 -0
- package/dist/types/recovery-journal-stall-fixture.d.ts +1 -0
- package/dist/types/recovery-journal-thread.d.ts +1 -0
- package/dist/types/recovery-journal.d.ts +246 -0
- package/dist/types/recovery-store.d.ts +6 -0
- package/dist/types/session-file-mutations.d.ts +22 -0
- package/dist/types/workspace-command-sync-policy.d.ts +22 -7
- package/dist/types/workspace-filesystem-executor-thread.d.ts +1 -0
- package/dist/types/workspace-filesystem-executor.d.ts +123 -0
- package/dist/types/workspace-filesystem-job-types.d.ts +246 -0
- package/dist/types/workspace-filesystem-jobs.d.ts +7 -0
- package/dist/types/workspace-git-sync.d.ts +113 -7
- package/dist/types/workspace-mount-hold-fence.d.ts +42 -0
- package/package.json +1 -1
- package/dist/cjs/project-snapshot-recovery-runner.cjs +0 -171
- package/dist/mjs/project-snapshot-recovery-runner.mjs +0 -135
- package/dist/types/project-snapshot-recovery-runner.d.ts +0 -10
package/dist/cjs/main.cjs
CHANGED
|
@@ -61,6 +61,8 @@ var import_plan_paths = require("./plan-paths.cjs");
|
|
|
61
61
|
var import_plan_parser = require("./plan-parser.cjs");
|
|
62
62
|
var import_plan_lint = require("./plan-lint.cjs");
|
|
63
63
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
64
|
+
var import_recovery_journal = require("./recovery-journal.cjs");
|
|
65
|
+
var import_session_file_mutations = require("./session-file-mutations.cjs");
|
|
64
66
|
var import_recovery_store = require("./recovery-store.cjs");
|
|
65
67
|
var import_recovery_output_window = require("./recovery-output-window.cjs");
|
|
66
68
|
var import_command_launcher = require("./command-launcher.cjs");
|
|
@@ -75,6 +77,7 @@ var import_cli_update = require("./cli-update.cjs");
|
|
|
75
77
|
var import_git_process_environment = require("./git-process-environment.cjs");
|
|
76
78
|
var import_process_tree = require("./process-tree.cjs");
|
|
77
79
|
var import_project_checkout_garbage = require("./project-checkout-garbage.cjs");
|
|
80
|
+
var import_control_command_policy = require("./control-command-policy.cjs");
|
|
78
81
|
var import_pty_output_coalescer = require("./pty-output-coalescer.cjs");
|
|
79
82
|
var import_port_forward_client = require("./port-forward-client.cjs");
|
|
80
83
|
var import_registry_auth = require("./registry-auth.cjs");
|
|
@@ -82,6 +85,8 @@ var import_workspace_incident_state = require("./workspace-incident-state.cjs");
|
|
|
82
85
|
var import_workspace_automatic_sync_policy = require("./workspace-automatic-sync-policy.cjs");
|
|
83
86
|
var import_workspace_command_sync_policy = require("./workspace-command-sync-policy.cjs");
|
|
84
87
|
var import_workspace_mutation_gate = require("./workspace-mutation-gate.cjs");
|
|
88
|
+
var import_workspace_mount_hold_fence = require("./workspace-mount-hold-fence.cjs");
|
|
89
|
+
var import_workspace_filesystem_executor = require("./workspace-filesystem-executor.cjs");
|
|
85
90
|
var import_workspace_projection_ledger = require("./workspace-projection-ledger.cjs");
|
|
86
91
|
var import_workspace_sync_coalescer = require("./workspace-sync-coalescer.cjs");
|
|
87
92
|
var import_workspace_path_move = require("./workspace-path-move.cjs");
|
|
@@ -94,7 +99,6 @@ var import_supervisor = require("./supervisor.cjs");
|
|
|
94
99
|
var import_managed_paths = require("./managed-paths.cjs");
|
|
95
100
|
var import_project_worktrees = require("./project-worktrees.cjs");
|
|
96
101
|
var import_project_mirror_fetch_policy = require("./project-mirror-fetch-policy.cjs");
|
|
97
|
-
var import_project_snapshot_recovery_runner = require("./project-snapshot-recovery-runner.cjs");
|
|
98
102
|
var import_project_workspace_state = require("./project-workspace-state.cjs");
|
|
99
103
|
var import_workspace_git_sync = require("./workspace-git-sync.cjs");
|
|
100
104
|
const import_meta = {};
|
|
@@ -110,7 +114,7 @@ function workerServerHeartbeatAction(input) {
|
|
|
110
114
|
return input.probeElapsedMs >= input.timeoutMs ? "terminate" : "wait";
|
|
111
115
|
}
|
|
112
116
|
const WORKSPACE_HYDRATION_LEASE_OPERATION_PREFIX = "hydration:";
|
|
113
|
-
function createWorkspaceHydrationLeaseHooks(send, options
|
|
117
|
+
function createWorkspaceHydrationLeaseHooks(send, options) {
|
|
114
118
|
const yieldToEventLoop = options.yieldToEventLoop ?? (() => new Promise((resolve) => setImmediate(resolve)));
|
|
115
119
|
const log = options.log ?? ((line) => process.stdout.write(`${line}
|
|
116
120
|
`));
|
|
@@ -131,13 +135,17 @@ function createWorkspaceHydrationLeaseHooks(send, options = {}) {
|
|
|
131
135
|
log(
|
|
132
136
|
`[r5d-worker] released hydration lease ${context.transactionId} after ${startedAt === void 0 ? "?" : Math.round(now() - startedAt)}ms`
|
|
133
137
|
);
|
|
138
|
+
},
|
|
139
|
+
holdMounts(mountIds, description) {
|
|
140
|
+
return options.fence.hold(mountIds, description);
|
|
134
141
|
}
|
|
135
142
|
};
|
|
136
143
|
}
|
|
137
144
|
const workerLifecycleTestHarness = {
|
|
138
145
|
boundedWorkerLifecycleProgress,
|
|
139
146
|
workerServerHeartbeatAction,
|
|
140
|
-
createWorkspaceHydrationLeaseHooks
|
|
147
|
+
createWorkspaceHydrationLeaseHooks,
|
|
148
|
+
workerCommandHasWorkspaceEffect
|
|
141
149
|
};
|
|
142
150
|
class ProjectWorkspaceConfigurationDeferredError extends Error {
|
|
143
151
|
}
|
|
@@ -158,6 +166,26 @@ const PTY_FOREGROUND_POLL_MS = 1e3;
|
|
|
158
166
|
const PTY_FOREGROUND_IDLE_ENABLED = process.env.R5D_PTY_FOREGROUND_IDLE !== "0";
|
|
159
167
|
const PTY_TMP_PATH_PREFIX = "r5d-worker-tmp://";
|
|
160
168
|
let workerCommandLauncher;
|
|
169
|
+
const trustedControlPath = process.env.PATH;
|
|
170
|
+
let capacityReportTimer;
|
|
171
|
+
let capacityReportSocket = null;
|
|
172
|
+
let lastCapacityReport;
|
|
173
|
+
function scheduleCapacityReport(force = false) {
|
|
174
|
+
if (force) lastCapacityReport = void 0;
|
|
175
|
+
if (capacityReportTimer) return;
|
|
176
|
+
capacityReportTimer = setTimeout(() => {
|
|
177
|
+
capacityReportTimer = void 0;
|
|
178
|
+
const ws = capacityReportSocket;
|
|
179
|
+
if (!ws || ws.readyState !== WebSocket.OPEN || !workerCommandLauncher) return;
|
|
180
|
+
const capacity = { ...workerCommandLauncher.capacityReport(), reportedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
181
|
+
const { reportedAt: _reportedAt, ...comparable } = capacity;
|
|
182
|
+
const serialized = JSON.stringify(comparable);
|
|
183
|
+
if (serialized === lastCapacityReport) return;
|
|
184
|
+
lastCapacityReport = serialized;
|
|
185
|
+
sendWorkerMessage(ws, { type: "capacity_report", capacity });
|
|
186
|
+
}, 100);
|
|
187
|
+
capacityReportTimer.unref?.();
|
|
188
|
+
}
|
|
161
189
|
const activeProcesses = /* @__PURE__ */ new Map();
|
|
162
190
|
const credentialBearingProcessGroups = /* @__PURE__ */ new Map();
|
|
163
191
|
const credentialBearingProcessGroupTargets = /* @__PURE__ */ new Map();
|
|
@@ -170,7 +198,7 @@ const workspaceSyncPriorityPtyTargets = /* @__PURE__ */ new Map();
|
|
|
170
198
|
let currentWorkerSocket = null;
|
|
171
199
|
const workerRuntimeId = crypto.randomUUID();
|
|
172
200
|
const workerOutputWindow = new import_recovery_output_window.WorkerOutputWindow();
|
|
173
|
-
let
|
|
201
|
+
let recoveryJournal;
|
|
174
202
|
let workerReconnectAttempt = 0;
|
|
175
203
|
let workerRecoveryId;
|
|
176
204
|
let workerExecutionLeaseDeadline = 0;
|
|
@@ -182,13 +210,13 @@ let runtimeExitPromise;
|
|
|
182
210
|
function fenceWorkerRuntimeOperations() {
|
|
183
211
|
launcherFailureInProgress = true;
|
|
184
212
|
workerAdmissionGeneration += 1;
|
|
185
|
-
for (const operation of
|
|
186
|
-
if (operation.state !== "completed")
|
|
213
|
+
for (const operation of recoveryJournal?.activeAndPendingOperations() ?? []) {
|
|
214
|
+
if (operation.state !== "completed") recoveryJournal?.unknown(operation.requestId);
|
|
187
215
|
}
|
|
188
216
|
for (const runId of workerCommandLauncher?.activeIds ?? []) {
|
|
189
217
|
infrastructureStoppedRunIds.add(runId);
|
|
190
218
|
cancelledProcessRuns.add(runId);
|
|
191
|
-
|
|
219
|
+
recoveryJournal?.unknownRun(runId);
|
|
192
220
|
}
|
|
193
221
|
for (const runId of activeProcesses.keys()) infrastructureStoppedRunIds.add(runId);
|
|
194
222
|
}
|
|
@@ -197,13 +225,21 @@ function exitWorkerRuntime(code) {
|
|
|
197
225
|
fenceWorkerRuntimeOperations();
|
|
198
226
|
runtimeExitPromise = (async () => {
|
|
199
227
|
await Promise.race([
|
|
200
|
-
Promise.allSettled([terminateActiveCredentialBearingChildren(), workerCommandLauncher?.close()]),
|
|
228
|
+
Promise.allSettled([terminateActiveCredentialBearingChildren(), workerCommandLauncher?.close(), recoveryJournal?.flush()]),
|
|
201
229
|
Bun.sleep(4e3)
|
|
202
230
|
]);
|
|
203
231
|
process.exit(code);
|
|
204
232
|
})();
|
|
205
233
|
return runtimeExitPromise;
|
|
206
234
|
}
|
|
235
|
+
function handleRecoveryJournalFailure(error) {
|
|
236
|
+
if (launcherFailureInProgress) return;
|
|
237
|
+
fenceWorkerRuntimeOperations();
|
|
238
|
+
process.stderr.write(`[r5d-worker] ${error.message}; stopping runtime for recovery journal restart
|
|
239
|
+
`);
|
|
240
|
+
currentWorkerSocket?.close(1012, "Recovery journal failure");
|
|
241
|
+
void exitWorkerRuntime(import_supervisor.WORKER_RECONNECT_EXIT_CODE);
|
|
242
|
+
}
|
|
207
243
|
function handleWorkerCommandLauncherFailure(error) {
|
|
208
244
|
if (!workerCommandLauncher || launcherFailureInProgress) return;
|
|
209
245
|
fenceWorkerRuntimeOperations();
|
|
@@ -224,7 +260,7 @@ async function retryWorkerServerRead(read) {
|
|
|
224
260
|
try {
|
|
225
261
|
return await read();
|
|
226
262
|
} catch (error) {
|
|
227
|
-
if (!
|
|
263
|
+
if (!recoveryJournal || !isWorkerCommunicationFailure(error instanceof Error ? error.message : String(error))) throw error;
|
|
228
264
|
deadline ??= Date.now() + import_recovery_protocol.WORKER_RECOVERY_TIMEOUT_MS;
|
|
229
265
|
if (workerLeaseExpired || Date.now() >= deadline) throw new WorkerServerUnavailableError("Worker communication recovery expired");
|
|
230
266
|
currentWorkerSocket?.close(1012, "Communication recovery");
|
|
@@ -246,9 +282,10 @@ function renewWorkerExecutionLease(leaseMs) {
|
|
|
246
282
|
`);
|
|
247
283
|
});
|
|
248
284
|
closeAllPtys();
|
|
249
|
-
for (const operation of
|
|
285
|
+
for (const operation of recoveryJournal?.activeAndPendingOperations() ?? []) {
|
|
250
286
|
if (operation.sessionId && operation.state !== "completed") {
|
|
251
|
-
|
|
287
|
+
void recoveryJournal?.cancelSession(operation.sessionId);
|
|
288
|
+
abortPendingReservations("Execution lease expired before command start", operation.sessionId);
|
|
252
289
|
void workerCommandLauncher?.cancelSession(operation.sessionId).catch((error) => {
|
|
253
290
|
process.stderr.write(`[r5d-worker] expired command launcher cancellation failed: ${error}
|
|
254
291
|
`);
|
|
@@ -258,7 +295,7 @@ function renewWorkerExecutionLease(leaseMs) {
|
|
|
258
295
|
for (const [runId, active] of activeProcesses) {
|
|
259
296
|
if (!active.sessionId) continue;
|
|
260
297
|
infrastructureStoppedRunIds.add(runId);
|
|
261
|
-
|
|
298
|
+
recoveryJournal?.unknownRun(runId);
|
|
262
299
|
cancelledProcessRuns.add(runId);
|
|
263
300
|
closeProcessStdin(active);
|
|
264
301
|
void (0, import_process_tree.terminateProcessTree)(active.process).catch(
|
|
@@ -273,6 +310,31 @@ function renewWorkerExecutionLease(leaseMs) {
|
|
|
273
310
|
}
|
|
274
311
|
let workerAdmissionGeneration = 0;
|
|
275
312
|
const workspaceMutationGate = new import_workspace_mutation_gate.WorkspaceMutationGate();
|
|
313
|
+
const workspaceMountHoldFence = new import_workspace_mount_hold_fence.WorkspaceMountHoldFence();
|
|
314
|
+
const projectMountId = (projectId, branchName) => `project:${projectId}:${encodeURIComponent(branchName)}`;
|
|
315
|
+
const pendingReservationAborts = /* @__PURE__ */ new Map();
|
|
316
|
+
const sessionFileMutations = new import_session_file_mutations.SessionFileMutationTracker();
|
|
317
|
+
function registerPendingReservation(key, sessionId) {
|
|
318
|
+
abortPendingReservation(key, "Reservation superseded by a newer request with the same id");
|
|
319
|
+
const controller = new AbortController();
|
|
320
|
+
pendingReservationAborts.set(key, { controller, sessionId });
|
|
321
|
+
return controller;
|
|
322
|
+
}
|
|
323
|
+
function unregisterPendingReservation(key, controller) {
|
|
324
|
+
if (pendingReservationAborts.get(key)?.controller === controller) pendingReservationAborts.delete(key);
|
|
325
|
+
}
|
|
326
|
+
function abortPendingReservation(key, reason) {
|
|
327
|
+
const pending = pendingReservationAborts.get(key);
|
|
328
|
+
if (!pending) return;
|
|
329
|
+
pendingReservationAborts.delete(key);
|
|
330
|
+
pending.controller.abort(new import_workspace_mount_hold_fence.WorkspaceMountHoldAbortedError(reason));
|
|
331
|
+
}
|
|
332
|
+
function abortPendingReservations(reason, sessionId) {
|
|
333
|
+
for (const [key, pending] of [...pendingReservationAborts]) {
|
|
334
|
+
if (sessionId !== void 0 && pending.sessionId !== sessionId) continue;
|
|
335
|
+
abortPendingReservation(key, reason);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
276
338
|
let workspaceSyncQueue = Promise.resolve();
|
|
277
339
|
let startupProjectSnapshotRecoveryCompleted = false;
|
|
278
340
|
const checkoutGarbageCollectors = /* @__PURE__ */ new Map();
|
|
@@ -307,6 +369,17 @@ const workspaceSyncSingleFlight = {
|
|
|
307
369
|
},
|
|
308
370
|
afterCurrent() {
|
|
309
371
|
return workspaceSyncQueue;
|
|
372
|
+
},
|
|
373
|
+
mountHold(target, signal) {
|
|
374
|
+
if (target.type !== "project") return null;
|
|
375
|
+
const mountIds = [projectMountId(target.projectId, target.branchName)];
|
|
376
|
+
if (!workspaceMountHoldFence.isHeld(mountIds)) return null;
|
|
377
|
+
const holder = workspaceMountHoldFence.describeHold(mountIds);
|
|
378
|
+
process.stdout.write(
|
|
379
|
+
`[r5d-worker] command for ${target.projectId}/${target.branchName} waits for ${holder ?? "a workspace filesystem job"}
|
|
380
|
+
`
|
|
381
|
+
);
|
|
382
|
+
return workspaceMountHoldFence.waitForRelease(mountIds, { signal });
|
|
310
383
|
}
|
|
311
384
|
};
|
|
312
385
|
let githubCredential = null;
|
|
@@ -963,14 +1036,15 @@ async function runGitAsync(args, options = {}) {
|
|
|
963
1036
|
env: (0, import_git_process_environment.workerGitProcessEnvironment)(),
|
|
964
1037
|
...bounded ? { detached: true } : {}
|
|
965
1038
|
});
|
|
966
|
-
const completion = Promise.all([
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
subprocess.exited
|
|
970
|
-
]).then(([stdout, stderr, exitCode]) => ({ stdout, stderr, exitCode }));
|
|
1039
|
+
const completion = Promise.all([new Response(subprocess.stdout).text(), new Response(subprocess.stderr).text(), subprocess.exited]).then(
|
|
1040
|
+
([stdout, stderr, exitCode]) => ({ stdout, stderr, exitCode })
|
|
1041
|
+
);
|
|
971
1042
|
let timer;
|
|
972
1043
|
const timedOut = bounded ? new Promise((_resolve, reject) => {
|
|
973
|
-
timer = setTimeout(
|
|
1044
|
+
timer = setTimeout(
|
|
1045
|
+
() => reject(new GitTransportTimeoutError(`git ${args.join(" ")} timed out after ${options.timeoutMs}ms`)),
|
|
1046
|
+
options.timeoutMs
|
|
1047
|
+
);
|
|
974
1048
|
}) : null;
|
|
975
1049
|
try {
|
|
976
1050
|
const { stdout, stderr, exitCode } = timedOut ? await Promise.race([completion, timedOut]) : await completion;
|
|
@@ -2059,12 +2133,12 @@ async function streamCommandOutput(stream, onData) {
|
|
|
2059
2133
|
}
|
|
2060
2134
|
const text = decoder.decode(chunk.value, { stream: true });
|
|
2061
2135
|
if (text.length > 0) {
|
|
2062
|
-
onData(text);
|
|
2136
|
+
await onData(text);
|
|
2063
2137
|
}
|
|
2064
2138
|
}
|
|
2065
2139
|
const trailing = decoder.decode();
|
|
2066
2140
|
if (trailing.length > 0) {
|
|
2067
|
-
onData(trailing);
|
|
2141
|
+
await onData(trailing);
|
|
2068
2142
|
}
|
|
2069
2143
|
} finally {
|
|
2070
2144
|
reader.releaseLock();
|
|
@@ -2203,12 +2277,21 @@ function mutationQueueKey(target, resolved) {
|
|
|
2203
2277
|
}
|
|
2204
2278
|
return `${describeWorkerSessionTarget(target)}:${import_node_path.default.posix.normalize(resolved.repoRelativePath)}`;
|
|
2205
2279
|
}
|
|
2206
|
-
function
|
|
2280
|
+
async function statIfExists(targetPath) {
|
|
2281
|
+
try {
|
|
2282
|
+
return await import_node_fs.default.promises.stat(targetPath);
|
|
2283
|
+
} catch (error) {
|
|
2284
|
+
const code = error.code;
|
|
2285
|
+
if (code === "ENOENT" || code === "ENOTDIR") return null;
|
|
2286
|
+
throw error;
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
async function readWorkerTextFile(branchPath, filePath, offset, limit, builtInPaths) {
|
|
2207
2290
|
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
2208
|
-
if (!
|
|
2291
|
+
if (!(await statIfExists(resolved.absolutePath))?.isFile()) {
|
|
2209
2292
|
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
2210
2293
|
}
|
|
2211
|
-
const buffer = import_node_fs.default.
|
|
2294
|
+
const buffer = await import_node_fs.default.promises.readFile(resolved.absolutePath);
|
|
2212
2295
|
const formatted = formatLineNumberedContent(buffer.toString("utf8"), offset, limit);
|
|
2213
2296
|
return {
|
|
2214
2297
|
type: "read",
|
|
@@ -2218,25 +2301,25 @@ function readWorkerTextFile(branchPath, filePath, offset, limit, builtInPaths) {
|
|
|
2218
2301
|
...formatted
|
|
2219
2302
|
};
|
|
2220
2303
|
}
|
|
2221
|
-
function writeWorkerTextFile(branchPath, filePath, content, builtInPaths) {
|
|
2304
|
+
async function writeWorkerTextFile(branchPath, filePath, content, builtInPaths) {
|
|
2222
2305
|
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
2223
|
-
import_node_fs.default.
|
|
2224
|
-
import_node_fs.default.
|
|
2306
|
+
await import_node_fs.default.promises.mkdir(import_node_path.default.dirname(resolved.absolutePath), { recursive: true });
|
|
2307
|
+
await import_node_fs.default.promises.writeFile(resolved.absolutePath, content, "utf8");
|
|
2225
2308
|
return {
|
|
2226
2309
|
type: "write",
|
|
2227
2310
|
file: resolved.displayPath,
|
|
2228
2311
|
gitBlobHash: getGitBlobHashForContent(content)
|
|
2229
2312
|
};
|
|
2230
2313
|
}
|
|
2231
|
-
function editWorkerTextFile(branchPath, filePath, edits, builtInPaths) {
|
|
2314
|
+
async function editWorkerTextFile(branchPath, filePath, edits, builtInPaths) {
|
|
2232
2315
|
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
2233
2316
|
if (!Array.isArray(edits) || edits.length === 0) {
|
|
2234
2317
|
throw new Error("edit requires at least one replacement");
|
|
2235
2318
|
}
|
|
2236
|
-
if (!
|
|
2319
|
+
if (!(await statIfExists(resolved.absolutePath))?.isFile()) {
|
|
2237
2320
|
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
2238
2321
|
}
|
|
2239
|
-
const originalContent = import_node_fs.default.
|
|
2322
|
+
const originalContent = await import_node_fs.default.promises.readFile(resolved.absolutePath, "utf8");
|
|
2240
2323
|
const ranges = edits.map((edit, index) => {
|
|
2241
2324
|
if (edit.oldText === edit.newText) {
|
|
2242
2325
|
throw new Error(`edits[${index}].oldText and edits[${index}].newText must be different`);
|
|
@@ -2268,7 +2351,7 @@ function editWorkerTextFile(branchPath, filePath, edits, builtInPaths) {
|
|
|
2268
2351
|
for (const range of [...sortedRanges].reverse()) {
|
|
2269
2352
|
nextContent = nextContent.slice(0, range.start) + range.edit.newText + nextContent.slice(range.end);
|
|
2270
2353
|
}
|
|
2271
|
-
import_node_fs.default.
|
|
2354
|
+
await import_node_fs.default.promises.writeFile(resolved.absolutePath, nextContent, "utf8");
|
|
2272
2355
|
return {
|
|
2273
2356
|
type: "edit",
|
|
2274
2357
|
file: resolved.displayPath,
|
|
@@ -2301,7 +2384,7 @@ async function executePlanOperation(input) {
|
|
|
2301
2384
|
let created = false;
|
|
2302
2385
|
if (message.createIfMissing) {
|
|
2303
2386
|
try {
|
|
2304
|
-
import_node_fs.default.
|
|
2387
|
+
await import_node_fs.default.promises.writeFile(resolved.absolutePath, (0, import_plan_parser.createPlanMarkdown)(message.planId), { encoding: "utf8", flag: "wx" });
|
|
2305
2388
|
created = true;
|
|
2306
2389
|
} catch (error) {
|
|
2307
2390
|
if (error.code !== "EEXIST") throw error;
|
|
@@ -2310,11 +2393,16 @@ async function executePlanOperation(input) {
|
|
|
2310
2393
|
return {
|
|
2311
2394
|
type: "read_plan",
|
|
2312
2395
|
created,
|
|
2313
|
-
read: readWorkerTextFile(resolvedTarget.rootPath, filePath, void 0, void 0, builtInPaths)
|
|
2396
|
+
read: await readWorkerTextFile(resolvedTarget.rootPath, filePath, void 0, void 0, builtInPaths)
|
|
2314
2397
|
};
|
|
2315
2398
|
}
|
|
2316
|
-
const content = import_node_fs.default.
|
|
2317
|
-
writeWorkerTextFile(
|
|
2399
|
+
const content = await import_node_fs.default.promises.readFile(resolved.absolutePath, "utf8");
|
|
2400
|
+
await writeWorkerTextFile(
|
|
2401
|
+
resolvedTarget.rootPath,
|
|
2402
|
+
filePath,
|
|
2403
|
+
(0, import_plan_parser.updateTaskCheckbox)(content, message.taskId, message.checked),
|
|
2404
|
+
builtInPaths
|
|
2405
|
+
);
|
|
2318
2406
|
return {
|
|
2319
2407
|
type: "update_task_status",
|
|
2320
2408
|
planId: message.planId,
|
|
@@ -2335,7 +2423,11 @@ async function executeReadFileOperation(input) {
|
|
|
2335
2423
|
rootDir: input.rootDir,
|
|
2336
2424
|
access: "read"
|
|
2337
2425
|
});
|
|
2338
|
-
|
|
2426
|
+
const resolved = resolveWorkerFilePath(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
|
|
2427
|
+
return withFileMutationQueue(
|
|
2428
|
+
mutationQueueKey(input.resolvedTarget.target, resolved),
|
|
2429
|
+
() => readWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.offset, input.message.limit, builtInPaths)
|
|
2430
|
+
);
|
|
2339
2431
|
}
|
|
2340
2432
|
async function executeWriteFileOperation(input) {
|
|
2341
2433
|
const builtInPaths = await prepareBuiltInToolPathsForTarget({
|
|
@@ -2388,16 +2480,16 @@ function wildcardToRegex(pattern) {
|
|
|
2388
2480
|
function normalizeFindPattern(pattern) {
|
|
2389
2481
|
return /[*?]/.test(pattern) ? pattern : `*${pattern}*`;
|
|
2390
2482
|
}
|
|
2391
|
-
function walkWorkerEntries(branchPath, start) {
|
|
2483
|
+
async function walkWorkerEntries(branchPath, start) {
|
|
2392
2484
|
const entries = [];
|
|
2393
2485
|
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
2394
|
-
const realVirtualRoot = start.scope === "virtual" ? import_node_fs.default.
|
|
2395
|
-
const visit = (absolutePath, isRoot) => {
|
|
2486
|
+
const realVirtualRoot = start.scope === "virtual" ? await import_node_fs.default.promises.realpath(start.virtualRootPath) : null;
|
|
2487
|
+
const visit = async (absolutePath, isRoot) => {
|
|
2396
2488
|
let stat;
|
|
2397
2489
|
try {
|
|
2398
|
-
stat = import_node_fs.default.
|
|
2490
|
+
stat = await import_node_fs.default.promises.stat(absolutePath);
|
|
2399
2491
|
if (realVirtualRoot) {
|
|
2400
|
-
assertInsideRoot(realVirtualRoot, import_node_fs.default.
|
|
2492
|
+
assertInsideRoot(realVirtualRoot, await import_node_fs.default.promises.realpath(absolutePath), `${start.displayPath} path`);
|
|
2401
2493
|
}
|
|
2402
2494
|
} catch (error) {
|
|
2403
2495
|
if (isRoot) throw error;
|
|
@@ -2410,7 +2502,7 @@ function walkWorkerEntries(branchPath, start) {
|
|
|
2410
2502
|
if (!stat.isDirectory()) return;
|
|
2411
2503
|
let realDirectoryPath;
|
|
2412
2504
|
try {
|
|
2413
|
-
realDirectoryPath = import_node_fs.default.
|
|
2505
|
+
realDirectoryPath = await import_node_fs.default.promises.realpath(absolutePath);
|
|
2414
2506
|
} catch (error) {
|
|
2415
2507
|
if (isRoot) throw error;
|
|
2416
2508
|
return;
|
|
@@ -2419,25 +2511,25 @@ function walkWorkerEntries(branchPath, start) {
|
|
|
2419
2511
|
visitedDirectories.add(realDirectoryPath);
|
|
2420
2512
|
let children;
|
|
2421
2513
|
try {
|
|
2422
|
-
children = import_node_fs.default.
|
|
2514
|
+
children = (await import_node_fs.default.promises.readdir(absolutePath, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
|
|
2423
2515
|
} catch (error) {
|
|
2424
2516
|
if (isRoot) throw error;
|
|
2425
2517
|
return;
|
|
2426
2518
|
}
|
|
2427
2519
|
for (const child of children) {
|
|
2428
2520
|
if (IGNORED_ENTRY_NAMES.has(child.name)) continue;
|
|
2429
|
-
visit(import_node_path.default.join(absolutePath, child.name), false);
|
|
2521
|
+
await visit(import_node_path.default.join(absolutePath, child.name), false);
|
|
2430
2522
|
}
|
|
2431
2523
|
};
|
|
2432
|
-
visit(start.absolutePath, true);
|
|
2524
|
+
await visit(start.absolutePath, true);
|
|
2433
2525
|
return entries;
|
|
2434
2526
|
}
|
|
2435
2527
|
function isProbablyText(bytes) {
|
|
2436
2528
|
return !bytes.subarray(0, Math.min(bytes.length, 4096)).includes(0);
|
|
2437
2529
|
}
|
|
2438
|
-
function grepWorkerFiles(branchPath, input, builtInPaths) {
|
|
2530
|
+
async function grepWorkerFiles(branchPath, input, builtInPaths) {
|
|
2439
2531
|
const start = resolveWorkerFilePath(branchPath, input.path ?? ".", builtInPaths);
|
|
2440
|
-
if (!
|
|
2532
|
+
if (!await statIfExists(start.absolutePath)) {
|
|
2441
2533
|
throw new Error(`Path not found: ${start.displayPath}`);
|
|
2442
2534
|
}
|
|
2443
2535
|
const limit = normalizeToolLimit(input.limit, 100, 1e3);
|
|
@@ -2447,12 +2539,12 @@ function grepWorkerFiles(branchPath, input, builtInPaths) {
|
|
|
2447
2539
|
const lines = [];
|
|
2448
2540
|
let matchCount = 0;
|
|
2449
2541
|
let truncated = false;
|
|
2450
|
-
for (const entry of walkWorkerEntries(branchPath, start)) {
|
|
2542
|
+
for (const entry of await walkWorkerEntries(branchPath, start)) {
|
|
2451
2543
|
if (!entry.stat.isFile()) continue;
|
|
2452
2544
|
if (globRegex && !globRegex.test(entry.matchPath)) continue;
|
|
2453
2545
|
let bytes;
|
|
2454
2546
|
try {
|
|
2455
|
-
bytes = import_node_fs.default.
|
|
2547
|
+
bytes = await import_node_fs.default.promises.readFile(entry.absolutePath);
|
|
2456
2548
|
} catch {
|
|
2457
2549
|
continue;
|
|
2458
2550
|
}
|
|
@@ -2505,9 +2597,9 @@ async function executeGrepOperation(input) {
|
|
|
2505
2597
|
builtInPaths
|
|
2506
2598
|
);
|
|
2507
2599
|
}
|
|
2508
|
-
function findWorkerFiles(branchPath, input, builtInPaths) {
|
|
2600
|
+
async function findWorkerFiles(branchPath, input, builtInPaths) {
|
|
2509
2601
|
const start = resolveWorkerFilePath(branchPath, input.path ?? ".", builtInPaths);
|
|
2510
|
-
if (!
|
|
2602
|
+
if (!(await statIfExists(start.absolutePath))?.isDirectory()) {
|
|
2511
2603
|
throw new Error(`Directory not found: ${start.displayPath}`);
|
|
2512
2604
|
}
|
|
2513
2605
|
const pattern = normalizeFindPattern(input.pattern ?? "*");
|
|
@@ -2517,7 +2609,7 @@ function findWorkerFiles(branchPath, input, builtInPaths) {
|
|
|
2517
2609
|
const matches = [];
|
|
2518
2610
|
let count = 0;
|
|
2519
2611
|
let truncated = false;
|
|
2520
|
-
for (const entry of walkWorkerEntries(branchPath, start)) {
|
|
2612
|
+
for (const entry of await walkWorkerEntries(branchPath, start)) {
|
|
2521
2613
|
if (entry.absolutePath === start.absolutePath) continue;
|
|
2522
2614
|
const isDirectory = entry.stat.isDirectory();
|
|
2523
2615
|
if (entryType === "file" && !entry.stat.isFile()) continue;
|
|
@@ -2562,13 +2654,13 @@ async function executeFindOperation(input) {
|
|
|
2562
2654
|
builtInPaths
|
|
2563
2655
|
);
|
|
2564
2656
|
}
|
|
2565
|
-
function listWorkerDirectory(branchPath, inputPath = ".", inputLimit, builtInPaths) {
|
|
2657
|
+
async function listWorkerDirectory(branchPath, inputPath = ".", inputLimit, builtInPaths) {
|
|
2566
2658
|
const resolved = resolveWorkerFilePath(branchPath, inputPath, builtInPaths);
|
|
2567
|
-
if (!
|
|
2659
|
+
if (!(await statIfExists(resolved.absolutePath))?.isDirectory()) {
|
|
2568
2660
|
throw new Error(`Directory not found: ${resolved.displayPath}`);
|
|
2569
2661
|
}
|
|
2570
2662
|
const limit = normalizeToolLimit(inputLimit, 200, 1e3);
|
|
2571
|
-
const entries = import_node_fs.default.
|
|
2663
|
+
const entries = (await import_node_fs.default.promises.readdir(resolved.absolutePath, { withFileTypes: true })).filter((entry) => !IGNORED_ENTRY_NAMES.has(entry.name)).sort((a, b) => Number(b.isDirectory()) - Number(a.isDirectory()) || a.name.localeCompare(b.name));
|
|
2572
2664
|
const displayed = entries.slice(0, limit).map((entry) => `${entry.name}${entry.isDirectory() ? "/" : ""}`);
|
|
2573
2665
|
const truncated = entries.length > displayed.length;
|
|
2574
2666
|
const hint = truncated ? "\n... truncated. Increase limit to continue." : "";
|
|
@@ -2619,36 +2711,40 @@ function resolveWorkerCodePath(branchPath, inputPath) {
|
|
|
2619
2711
|
}
|
|
2620
2712
|
return { absolutePath, displayPath };
|
|
2621
2713
|
}
|
|
2622
|
-
function listWorkerCodeDirectory(branchPath, inputPath) {
|
|
2714
|
+
async function listWorkerCodeDirectory(branchPath, inputPath) {
|
|
2623
2715
|
const resolved = resolveWorkerCodePath(branchPath, inputPath);
|
|
2624
|
-
if (!import_node_fs.default.
|
|
2716
|
+
if (!(await import_node_fs.default.promises.stat(resolved.absolutePath)).isDirectory()) {
|
|
2625
2717
|
throw new Error(`Code directory not found: ${resolved.displayPath}`);
|
|
2626
2718
|
}
|
|
2627
|
-
const
|
|
2719
|
+
const listed = (await import_node_fs.default.promises.readdir(resolved.absolutePath, { withFileTypes: true })).filter(
|
|
2720
|
+
(entry) => entry.name.toLowerCase() !== ".git"
|
|
2721
|
+
);
|
|
2722
|
+
const entries = [];
|
|
2723
|
+
for (const entry of listed) {
|
|
2628
2724
|
const entryPath = import_node_path.default.posix.join(resolved.displayPath, entry.name);
|
|
2629
2725
|
try {
|
|
2630
2726
|
const resolvedEntry = resolveWorkerCodePath(branchPath, entryPath);
|
|
2631
|
-
const stats = import_node_fs.default.
|
|
2632
|
-
if (!stats.isFile() && !stats.isDirectory())
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
}
|
|
2640
|
-
];
|
|
2727
|
+
const stats = await import_node_fs.default.promises.stat(resolvedEntry.absolutePath);
|
|
2728
|
+
if (!stats.isFile() && !stats.isDirectory()) continue;
|
|
2729
|
+
entries.push({
|
|
2730
|
+
name: entry.name,
|
|
2731
|
+
type: stats.isDirectory() ? "directory" : "file",
|
|
2732
|
+
size: stats.isDirectory() ? null : stats.size,
|
|
2733
|
+
mtime: stats.mtime.toISOString()
|
|
2734
|
+
});
|
|
2641
2735
|
} catch {
|
|
2642
|
-
return [];
|
|
2643
2736
|
}
|
|
2644
|
-
}
|
|
2737
|
+
}
|
|
2738
|
+
entries.sort(
|
|
2739
|
+
(left, right) => Number(right.type === "directory") - Number(left.type === "directory") || left.name.localeCompare(right.name)
|
|
2740
|
+
);
|
|
2645
2741
|
return { type: "code_list", path: resolved.displayPath, entries };
|
|
2646
2742
|
}
|
|
2647
|
-
function readWorkerCodeFile(branchPath, inputPath) {
|
|
2743
|
+
async function readWorkerCodeFile(branchPath, inputPath) {
|
|
2648
2744
|
const resolved = resolveWorkerCodePath(branchPath, inputPath);
|
|
2649
|
-
const stats = import_node_fs.default.
|
|
2745
|
+
const stats = await import_node_fs.default.promises.stat(resolved.absolutePath);
|
|
2650
2746
|
if (!stats.isFile()) throw new Error(`Code file not found: ${resolved.displayPath}`);
|
|
2651
|
-
const bytes = import_node_fs.default.
|
|
2747
|
+
const bytes = await import_node_fs.default.promises.readFile(resolved.absolutePath);
|
|
2652
2748
|
return {
|
|
2653
2749
|
type: "code_read",
|
|
2654
2750
|
path: resolved.displayPath,
|
|
@@ -2657,17 +2753,17 @@ function readWorkerCodeFile(branchPath, inputPath) {
|
|
|
2657
2753
|
base64: bytes.toString("base64")
|
|
2658
2754
|
};
|
|
2659
2755
|
}
|
|
2660
|
-
function executeCodeListOperation(input) {
|
|
2756
|
+
async function executeCodeListOperation(input) {
|
|
2661
2757
|
if (input.resolvedTarget.target.type !== "project") throw new Error("Code listing requires a project branch target");
|
|
2662
2758
|
return listWorkerCodeDirectory(input.resolvedTarget.rootPath, input.message.path);
|
|
2663
2759
|
}
|
|
2664
|
-
function executeCodeReadOperation(input) {
|
|
2760
|
+
async function executeCodeReadOperation(input) {
|
|
2665
2761
|
if (input.resolvedTarget.target.type !== "project") throw new Error("Code reading requires a project branch target");
|
|
2666
2762
|
return readWorkerCodeFile(input.resolvedTarget.rootPath, input.message.path);
|
|
2667
2763
|
}
|
|
2668
|
-
function readWorkerImageFile(branchPath, filePath, builtInPaths) {
|
|
2764
|
+
async function readWorkerImageFile(branchPath, filePath, builtInPaths) {
|
|
2669
2765
|
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
2670
|
-
if (!
|
|
2766
|
+
if (!(await statIfExists(resolved.absolutePath))?.isFile()) {
|
|
2671
2767
|
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
2672
2768
|
}
|
|
2673
2769
|
const extension = import_node_path.default.extname(resolved.absolutePath).toLowerCase();
|
|
@@ -2675,7 +2771,7 @@ function readWorkerImageFile(branchPath, filePath, builtInPaths) {
|
|
|
2675
2771
|
if (!mediaType) {
|
|
2676
2772
|
throw new Error("read supports PNG and JPEG image inputs only");
|
|
2677
2773
|
}
|
|
2678
|
-
const bytes = import_node_fs.default.
|
|
2774
|
+
const bytes = await import_node_fs.default.promises.readFile(resolved.absolutePath);
|
|
2679
2775
|
const dimensions = readImageDimensions(bytes, mediaType);
|
|
2680
2776
|
return {
|
|
2681
2777
|
type: "view_file_bytes",
|
|
@@ -2775,6 +2871,10 @@ function cancelWorkerCommandLaunch(resources) {
|
|
|
2775
2871
|
});
|
|
2776
2872
|
}
|
|
2777
2873
|
async function acquireWorkerCommandLaunch(ws, message, assertAdmission) {
|
|
2874
|
+
if (message.type === "exec_start" && message.commandClass === "control") {
|
|
2875
|
+
(0, import_control_command_policy.assertControlCommandArgv)(message.argv);
|
|
2876
|
+
if (message.interactive) throw new Error("Control commands cannot be interactive");
|
|
2877
|
+
}
|
|
2778
2878
|
const id = message.type === "pty_open" ? `pty:${message.ptyId}` : message.runId;
|
|
2779
2879
|
const operationId = message.type === "pty_open" ? id : `exec:${id}`;
|
|
2780
2880
|
sendWorkerMessage(ws, { type: "heartbeat_lease", operationId, active: true });
|
|
@@ -2783,6 +2883,7 @@ async function acquireWorkerCommandLaunch(ws, message, assertAdmission) {
|
|
|
2783
2883
|
id,
|
|
2784
2884
|
kind: message.type,
|
|
2785
2885
|
..."workspaceEffect" in message && message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {},
|
|
2886
|
+
...message.type === "exec_start" && message.commandClass === "control" ? { commandClass: "control" } : {},
|
|
2786
2887
|
..."sessionId" in message && message.sessionId ? { sessionId: message.sessionId } : {},
|
|
2787
2888
|
assertAdmission: () => {
|
|
2788
2889
|
assertAdmission();
|
|
@@ -2930,8 +3031,17 @@ async function executeStreamingCommand(input) {
|
|
|
2930
3031
|
});
|
|
2931
3032
|
const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
|
|
2932
3033
|
const interactive = input.message.interactive === true;
|
|
3034
|
+
const control = input.message.commandClass === "control";
|
|
3035
|
+
let argv = input.message.argv;
|
|
3036
|
+
let environment = workerChildProcessEnvironment([githubProcessEnv(), input.message.env ?? {}, targetProcessEnv2]);
|
|
3037
|
+
if (control) {
|
|
3038
|
+
(0, import_control_command_policy.assertControlCommandArgv)(argv);
|
|
3039
|
+
if (interactive) throw new Error("Control commands cannot be interactive");
|
|
3040
|
+
argv = [(0, import_control_command_policy.resolveControlCommandExecutable)(trustedControlPath, (program, options) => Bun.which(program, options)), ...argv.slice(1)];
|
|
3041
|
+
environment = (0, import_control_command_policy.controlCommandEnvironment)(environment, trustedControlPath);
|
|
3042
|
+
}
|
|
2933
3043
|
input.assertAdmission();
|
|
2934
|
-
const subprocess = Bun.spawn(input.resources.wrap(
|
|
3044
|
+
const subprocess = Bun.spawn(input.resources.wrap(argv), {
|
|
2935
3045
|
cwd,
|
|
2936
3046
|
// Without an explicit stdin the process reads /dev/null and interactive
|
|
2937
3047
|
// prompts see immediate EOF; "pipe" keeps stdin open for exec_stdin.
|
|
@@ -2939,7 +3049,7 @@ async function executeStreamingCommand(input) {
|
|
|
2939
3049
|
stdout: "pipe",
|
|
2940
3050
|
stderr: "pipe",
|
|
2941
3051
|
detached: true,
|
|
2942
|
-
env:
|
|
3052
|
+
env: environment
|
|
2943
3053
|
});
|
|
2944
3054
|
spawnedProcess = subprocess;
|
|
2945
3055
|
credentialBearingProcessGroups.set(subprocess.pid, subprocess);
|
|
@@ -2954,12 +3064,13 @@ async function executeStreamingCommand(input) {
|
|
|
2954
3064
|
pid: subprocess.pid,
|
|
2955
3065
|
processGroupId: process.platform === "win32" ? void 0 : subprocess.pid,
|
|
2956
3066
|
credentialId: input.message.credentialId,
|
|
2957
|
-
argv
|
|
3067
|
+
argv,
|
|
2958
3068
|
command: input.message.command,
|
|
2959
3069
|
cwd,
|
|
2960
3070
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2961
3071
|
...interactive ? { interactive: true, stdin: subprocess.stdin } : {},
|
|
2962
|
-
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {}
|
|
3072
|
+
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {},
|
|
3073
|
+
...control ? { commandClass: "control" } : {}
|
|
2963
3074
|
});
|
|
2964
3075
|
settlePreparation();
|
|
2965
3076
|
started = true;
|
|
@@ -2971,7 +3082,8 @@ async function executeStreamingCommand(input) {
|
|
|
2971
3082
|
pid: subprocess.pid,
|
|
2972
3083
|
...process.platform === "win32" ? {} : { processGroupId: subprocess.pid }
|
|
2973
3084
|
});
|
|
2974
|
-
|
|
3085
|
+
const timeoutMs = control ? Math.min(input.message.timeoutMs ?? import_control_command_policy.CONTROL_COMMAND_RUNTIME_MS, import_control_command_policy.CONTROL_COMMAND_RUNTIME_MS) : input.message.timeoutMs;
|
|
3086
|
+
if (timeoutMs) {
|
|
2975
3087
|
timeout = setTimeout(() => {
|
|
2976
3088
|
timedOut = true;
|
|
2977
3089
|
cancelWorkerCommandLaunch(input.resources);
|
|
@@ -2982,30 +3094,32 @@ async function executeStreamingCommand(input) {
|
|
|
2982
3094
|
`
|
|
2983
3095
|
);
|
|
2984
3096
|
});
|
|
2985
|
-
},
|
|
3097
|
+
}, timeoutMs);
|
|
2986
3098
|
}
|
|
2987
3099
|
const [exitCode] = await Promise.all([
|
|
2988
3100
|
subprocess.exited,
|
|
2989
|
-
streamCommandOutput(
|
|
2990
|
-
|
|
3101
|
+
streamCommandOutput(
|
|
3102
|
+
subprocess.stdout,
|
|
3103
|
+
(data) => sendWorkerOutput(input.ws, {
|
|
2991
3104
|
type: "exec_output",
|
|
2992
3105
|
runId: input.message.runId,
|
|
2993
3106
|
stream: "stdout",
|
|
2994
3107
|
data
|
|
2995
|
-
})
|
|
2996
|
-
|
|
2997
|
-
streamCommandOutput(
|
|
2998
|
-
|
|
3108
|
+
})
|
|
3109
|
+
),
|
|
3110
|
+
streamCommandOutput(
|
|
3111
|
+
subprocess.stderr,
|
|
3112
|
+
(data) => sendWorkerOutput(input.ws, {
|
|
2999
3113
|
type: "exec_output",
|
|
3000
3114
|
runId: input.message.runId,
|
|
3001
3115
|
stream: "stderr",
|
|
3002
3116
|
data
|
|
3003
|
-
})
|
|
3004
|
-
|
|
3117
|
+
})
|
|
3118
|
+
)
|
|
3005
3119
|
]);
|
|
3006
3120
|
const diagnostic = await input.resources.diagnose();
|
|
3007
3121
|
if (diagnostic) {
|
|
3008
|
-
|
|
3122
|
+
await sendWorkerOutput(input.ws, {
|
|
3009
3123
|
type: "exec_output",
|
|
3010
3124
|
runId: input.message.runId,
|
|
3011
3125
|
stream: "stderr",
|
|
@@ -3077,6 +3191,33 @@ async function reapCompletedCredentialBearingProcessGroup(runId, subprocess) {
|
|
|
3077
3191
|
);
|
|
3078
3192
|
}
|
|
3079
3193
|
}
|
|
3194
|
+
async function cancelProcessRun(runId) {
|
|
3195
|
+
let resourceCancellationError;
|
|
3196
|
+
try {
|
|
3197
|
+
await workerCommandLauncher?.cancel(runId);
|
|
3198
|
+
} catch (error) {
|
|
3199
|
+
resourceCancellationError = error instanceof Error ? error.message : String(error);
|
|
3200
|
+
}
|
|
3201
|
+
const active = activeProcesses.get(runId);
|
|
3202
|
+
let cancelled = resourceCancellationError === void 0;
|
|
3203
|
+
let cancelMessage;
|
|
3204
|
+
if (active) {
|
|
3205
|
+
try {
|
|
3206
|
+
closeProcessStdin(active);
|
|
3207
|
+
await (0, import_process_tree.terminateProcessTree)(active.process);
|
|
3208
|
+
cancelMessage = `Stopped ${active.command}`;
|
|
3209
|
+
} catch (error) {
|
|
3210
|
+
cancelled = false;
|
|
3211
|
+
cancelMessage = `Failed to stop ${active.command}: ${error instanceof Error ? error.message : String(error)}`;
|
|
3212
|
+
}
|
|
3213
|
+
} else {
|
|
3214
|
+
cancelledProcessRuns.add(runId);
|
|
3215
|
+
abortPendingReservation(`run:${runId}`, "Cancellation queued before command start");
|
|
3216
|
+
cancelMessage = "Cancellation queued before command start";
|
|
3217
|
+
}
|
|
3218
|
+
if (resourceCancellationError) cancelMessage += `; job resource termination failed: ${resourceCancellationError}`;
|
|
3219
|
+
return { cancelled, message: cancelMessage };
|
|
3220
|
+
}
|
|
3080
3221
|
function closeProcessStdin(active) {
|
|
3081
3222
|
if (!active?.stdin) {
|
|
3082
3223
|
return;
|
|
@@ -3102,21 +3243,35 @@ function sendReplayWorkerMessage(ws, message) {
|
|
|
3102
3243
|
}
|
|
3103
3244
|
function sendWorkerMessage(ws, message) {
|
|
3104
3245
|
if (launcherFailureInProgress) return;
|
|
3105
|
-
if (recoveryStore && "requestId" in message && typeof message.requestId === "string" && recoveryStore.isUnknown(message.requestId))
|
|
3106
|
-
return;
|
|
3107
3246
|
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;
|
|
3108
|
-
if (
|
|
3109
|
-
|
|
3247
|
+
if (recoveryJournal && transportError && isWorkerCommunicationFailure(transportError) && "requestId" in message && typeof message.requestId === "string") {
|
|
3248
|
+
recoveryJournal.unknown(message.requestId);
|
|
3110
3249
|
currentWorkerSocket?.close(1012, "Communication recovery");
|
|
3111
3250
|
return;
|
|
3112
3251
|
}
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
recoveryStore?.unknownRun(message.runId);
|
|
3252
|
+
if (!recoveryJournal) {
|
|
3253
|
+
sendReplayWorkerMessage(ws, message);
|
|
3116
3254
|
return;
|
|
3117
3255
|
}
|
|
3118
|
-
|
|
3119
|
-
|
|
3256
|
+
recoveryJournal.send(message);
|
|
3257
|
+
}
|
|
3258
|
+
function sendWorkerOutput(ws, frame) {
|
|
3259
|
+
if (launcherFailureInProgress) return Promise.resolve();
|
|
3260
|
+
if (!recoveryJournal) {
|
|
3261
|
+
sendReplayWorkerMessage(ws, frame);
|
|
3262
|
+
return Promise.resolve();
|
|
3263
|
+
}
|
|
3264
|
+
return recoveryJournal.sendOutput(frame);
|
|
3265
|
+
}
|
|
3266
|
+
function deliverJournaledWorkerMessage(message) {
|
|
3267
|
+
const target = currentWorkerSocket;
|
|
3268
|
+
if ((message.type === "exec_exit" || message.type === "exec_error") && typeof message.runId === "string" && infrastructureStoppedRunIds.has(message.runId)) {
|
|
3269
|
+
recoveryJournal?.unknownRun(message.runId);
|
|
3270
|
+
return true;
|
|
3271
|
+
}
|
|
3272
|
+
if (!target || target.readyState !== WebSocket.OPEN) return false;
|
|
3273
|
+
if (message.type === "exec_output" && workerRecoveryId !== void 0) return false;
|
|
3274
|
+
return sendReplayWorkerMessage(target, message);
|
|
3120
3275
|
}
|
|
3121
3276
|
function sendSerializedWorkerMessage(ws, serialized) {
|
|
3122
3277
|
sendWorkerMessage(ws, JSON.parse(serialized));
|
|
@@ -3145,7 +3300,8 @@ function buildActiveProcessReports() {
|
|
|
3145
3300
|
...active.cwd ? { cwd: active.cwd } : {},
|
|
3146
3301
|
startedAt: active.startedAt,
|
|
3147
3302
|
...active.interactive ? { interactive: true } : {},
|
|
3148
|
-
...active.workspaceEffect === "none" ? { workspaceEffect: "none" } : {}
|
|
3303
|
+
...active.workspaceEffect === "none" ? { workspaceEffect: "none" } : {},
|
|
3304
|
+
...active.commandClass ? { commandClass: active.commandClass } : {}
|
|
3149
3305
|
}));
|
|
3150
3306
|
}
|
|
3151
3307
|
function sendActiveProcessReport(ws) {
|
|
@@ -3667,6 +3823,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
3667
3823
|
validateLabel(label);
|
|
3668
3824
|
if (!workerCommandLauncher) {
|
|
3669
3825
|
const launcher = await (0, import_command_launcher.createWorkerCommandLauncher)({
|
|
3826
|
+
onCapacityChange: () => scheduleCapacityReport(),
|
|
3670
3827
|
configuration: process.env.R5D_WORKER_COMMAND_LAUNCHER,
|
|
3671
3828
|
env: (0, import_command_launcher.commandLauncherEnvironment)(),
|
|
3672
3829
|
onFailure: handleWorkerCommandLauncherFailure
|
|
@@ -3677,9 +3834,19 @@ async function startWorker(options, projectRuntime = {
|
|
|
3677
3834
|
`);
|
|
3678
3835
|
}
|
|
3679
3836
|
const rootDir = import_node_path.default.resolve(options.rootDir ?? process.env.R5D_ROOT ?? defaultRootDir());
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3837
|
+
if (!recoveryJournal) {
|
|
3838
|
+
const journal = await import_recovery_journal.WorkerRecoveryJournal.open({
|
|
3839
|
+
filename: import_node_path.default.join(
|
|
3840
|
+
rootDir,
|
|
3841
|
+
".worker-recovery",
|
|
3842
|
+
(0, import_node_crypto.createHash)("sha256").update(`${baseUrl}\0${label}`).digest("hex"),
|
|
3843
|
+
"protocol.sqlite"
|
|
3844
|
+
)
|
|
3845
|
+
});
|
|
3846
|
+
journal.deliver = deliverJournaledWorkerMessage;
|
|
3847
|
+
journal.onFailure = handleRecoveryJournalFailure;
|
|
3848
|
+
recoveryJournal = journal;
|
|
3849
|
+
}
|
|
3683
3850
|
const projectsRoot = import_node_path.default.join(rootDir, "projects");
|
|
3684
3851
|
const syncRoot = import_node_path.default.join(rootDir, "sync");
|
|
3685
3852
|
const workspaceShadowRoot = import_node_path.default.join(syncRoot, "workspace");
|
|
@@ -3782,7 +3949,6 @@ async function startWorker(options, projectRuntime = {
|
|
|
3782
3949
|
const bearerAuthHeader = `Authorization: Bearer ${token}`;
|
|
3783
3950
|
const workerCredentialUsername = credentialUsernameForAuthHeader(bearerAuthHeader);
|
|
3784
3951
|
const projectBranchKey = (projectId, branchName) => `${projectId}\0${branchName}`;
|
|
3785
|
-
const projectMountId = (projectId, branchName) => `project:${projectId}:${encodeURIComponent(branchName)}`;
|
|
3786
3952
|
const projectPlanMountId = (projectId, branchName) => `plan:${projectId}:${encodeURIComponent(branchName)}`;
|
|
3787
3953
|
const workspaceProjectRelativePath = (projectId, branchName) => import_node_path.default.posix.join("projects", projectId, "branches", encodeURIComponent(branchName));
|
|
3788
3954
|
const workspacePlanRelativePath = (projectId, branchName) => import_node_path.default.posix.join("plans", projectId, encodeURIComponent(branchName));
|
|
@@ -3865,7 +4031,8 @@ async function startWorker(options, projectRuntime = {
|
|
|
3865
4031
|
sourcePath: configuredProjectBranchPath(projectsRoot, project, branchName),
|
|
3866
4032
|
planSourcePath: import_node_path.default.join(planRoot, project.projectId, ...branchName.split("/"))
|
|
3867
4033
|
}));
|
|
3868
|
-
(0, import_project_worktrees.removeProjectWorktrees)({ projectRoot, branchNames: branches.map(({ branchName }) => branchName) });
|
|
4034
|
+
const removed = (0, import_project_worktrees.removeProjectWorktrees)({ projectsRoot, projectRoot, branchNames: branches.map(({ branchName }) => branchName) });
|
|
4035
|
+
if (removed.stagedForCollectionPath) checkoutGarbageCollector.enqueue(removed.stagedForCollectionPath);
|
|
3869
4036
|
for (const branch of branches) {
|
|
3870
4037
|
import_node_fs.default.rmSync(branch.planSourcePath, { recursive: true, force: true });
|
|
3871
4038
|
const key = projectBranchKey(project.projectId, branch.branchName);
|
|
@@ -3953,14 +4120,14 @@ async function startWorker(options, projectRuntime = {
|
|
|
3953
4120
|
)
|
|
3954
4121
|
};
|
|
3955
4122
|
};
|
|
3956
|
-
const stageDurableWorkspaceDeletions = () => {
|
|
4123
|
+
const stageDurableWorkspaceDeletions = async () => {
|
|
3957
4124
|
for (const deletion of projectWorkspaceState.pendingTreeDeletions) {
|
|
3958
4125
|
const cleanupConfig = cleanupConfigForDurableDeletion(deletion);
|
|
3959
4126
|
if (deletion.kind === "project") {
|
|
3960
4127
|
if (!deletion.projectDeleted) {
|
|
3961
4128
|
const nextProject = projectConfigById.get(deletion.projectId);
|
|
3962
4129
|
if (!nextProject) throw new Error(`Checkout-path move lost desired project ${deletion.projectId}`);
|
|
3963
|
-
(0,
|
|
4130
|
+
await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("project_checkout_path_move", {
|
|
3964
4131
|
oldProjectRoot: deletion.managedPath,
|
|
3965
4132
|
newProjectRoot: configuredProjectRoot(projectsRoot, nextProject),
|
|
3966
4133
|
projectsDurabilityRoot: projectsRoot,
|
|
@@ -4327,7 +4494,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
4327
4494
|
}
|
|
4328
4495
|
}
|
|
4329
4496
|
if (transitionReseedRequired) {
|
|
4330
|
-
projectWorkspaceState = projectWorkspaceStateStore.recordEnabledRepositoryTransition({
|
|
4497
|
+
projectWorkspaceState = await projectWorkspaceStateStore.recordEnabledRepositoryTransition({
|
|
4331
4498
|
projectId: project.projectId,
|
|
4332
4499
|
transitionId: project.repositoryTransitionId
|
|
4333
4500
|
});
|
|
@@ -4496,7 +4663,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
4496
4663
|
)
|
|
4497
4664
|
];
|
|
4498
4665
|
if (publishedTombstoneIds.length > 0) {
|
|
4499
|
-
projectWorkspaceState = projectWorkspaceStateStore.recordTreePublication({
|
|
4666
|
+
projectWorkspaceState = await projectWorkspaceStateStore.recordTreePublication({
|
|
4500
4667
|
tombstoneIds: publishedTombstoneIds,
|
|
4501
4668
|
publishedHead
|
|
4502
4669
|
});
|
|
@@ -4515,7 +4682,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
4515
4682
|
forgetLocalProjectMirrorRefsTokens(deletion.projectId, deletion.branchName);
|
|
4516
4683
|
}
|
|
4517
4684
|
if (deletion.tombstoneId && !deletion.projectDeleted) {
|
|
4518
|
-
projectWorkspaceState = projectWorkspaceStateStore.recordMirrorRefDeletion({
|
|
4685
|
+
projectWorkspaceState = await projectWorkspaceStateStore.recordMirrorRefDeletion({
|
|
4519
4686
|
tombstoneId: deletion.tombstoneId,
|
|
4520
4687
|
branchName: deletion.branchName
|
|
4521
4688
|
});
|
|
@@ -4712,7 +4879,10 @@ async function startWorker(options, projectRuntime = {
|
|
|
4712
4879
|
};
|
|
4713
4880
|
};
|
|
4714
4881
|
const workspaceHydrationTransactionHooks = createWorkspaceHydrationLeaseHooks(
|
|
4715
|
-
(message) => sendWorkerMessageFromCurrentSource(ws, message)
|
|
4882
|
+
(message) => sendWorkerMessageFromCurrentSource(ws, message),
|
|
4883
|
+
{
|
|
4884
|
+
fence: workspaceMountHoldFence
|
|
4885
|
+
}
|
|
4716
4886
|
);
|
|
4717
4887
|
const performWorkspaceSync = async (input) => {
|
|
4718
4888
|
if (!workspaceRemoteUrl || !workspaceCredentialHelper || !workspaceCredentialUsername || !workspaceGitIdentity) {
|
|
@@ -5370,7 +5540,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
5370
5540
|
(project) => project.preserveOnlyBranches.map(({ branchId, branchName }) => ({ branchId, projectId: project.projectId, branchName }))
|
|
5371
5541
|
);
|
|
5372
5542
|
if (!incidentDeferral.incidentId) {
|
|
5373
|
-
projectWorkspaceState = projectWorkspaceStateStore.reconcile({
|
|
5543
|
+
projectWorkspaceState = await projectWorkspaceStateStore.reconcile({
|
|
5374
5544
|
desiredProjects: message.projects,
|
|
5375
5545
|
preserveOnlyBranches
|
|
5376
5546
|
});
|
|
@@ -5381,7 +5551,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
5381
5551
|
(candidate) => candidate.projectId === project.projectId && candidate.branchName === branch.branchName
|
|
5382
5552
|
);
|
|
5383
5553
|
if (pending2 && pending2.branchId === branch.branchId) {
|
|
5384
|
-
projectWorkspaceState = projectWorkspaceStateStore.clearPendingCreatedBranch(pending2);
|
|
5554
|
+
projectWorkspaceState = await projectWorkspaceStateStore.clearPendingCreatedBranch(pending2);
|
|
5385
5555
|
}
|
|
5386
5556
|
}
|
|
5387
5557
|
}
|
|
@@ -5549,7 +5719,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
5549
5719
|
for (const projectId of [...readyProjectIds]) {
|
|
5550
5720
|
if (!projectConfigById.has(projectId)) readyProjectIds.delete(projectId);
|
|
5551
5721
|
}
|
|
5552
|
-
stageDurableWorkspaceDeletions();
|
|
5722
|
+
await stageDurableWorkspaceDeletions();
|
|
5553
5723
|
const mountsBeforeGit = buildWorkspaceMounts();
|
|
5554
5724
|
await (0, import_workspace_git_sync.hydrateWorkspaceGitMounts)(
|
|
5555
5725
|
workspaceShadowRoot,
|
|
@@ -5595,7 +5765,12 @@ async function startWorker(options, projectRuntime = {
|
|
|
5595
5765
|
const connectSyncMs = Date.now() - connectSyncStartedAtMs;
|
|
5596
5766
|
const result = syncResult.telemetry ? syncResult : {
|
|
5597
5767
|
...syncResult,
|
|
5598
|
-
telemetry: {
|
|
5768
|
+
telemetry: {
|
|
5769
|
+
totalMs: connectSyncMs,
|
|
5770
|
+
queueMs: 0,
|
|
5771
|
+
prepareMs: lastWorkspaceSyncMirrorObservationMs,
|
|
5772
|
+
synchronizeMs: connectSyncMs
|
|
5773
|
+
}
|
|
5599
5774
|
};
|
|
5600
5775
|
assertConfigurationSyncStillAdmitted();
|
|
5601
5776
|
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
@@ -5614,7 +5789,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
5614
5789
|
) ? [deletion.tombstoneId] : []
|
|
5615
5790
|
);
|
|
5616
5791
|
if (layoutCleanupIds.length > 0) {
|
|
5617
|
-
projectWorkspaceState = projectWorkspaceStateStore.recordTreePublication({
|
|
5792
|
+
projectWorkspaceState = await projectWorkspaceStateStore.recordTreePublication({
|
|
5618
5793
|
tombstoneIds: layoutCleanupIds,
|
|
5619
5794
|
publishedHead
|
|
5620
5795
|
});
|
|
@@ -5721,26 +5896,33 @@ async function startWorker(options, projectRuntime = {
|
|
|
5721
5896
|
if (startupProjectSnapshotRecoveryCompleted) return;
|
|
5722
5897
|
sendLifecycleProgress({ phase: "recovering", operationId, detail: "Checking interrupted workspace transactions" });
|
|
5723
5898
|
const snapshotRecovery = await workspaceSyncSingleFlight.runExclusive(
|
|
5724
|
-
async () => await (0,
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5899
|
+
async () => await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run(
|
|
5900
|
+
"project_snapshots_recover",
|
|
5901
|
+
{
|
|
5902
|
+
projectsRoot,
|
|
5903
|
+
temporaryRoot: projectWorktreeSnapshotsRoot,
|
|
5904
|
+
currentProcessId: process.pid,
|
|
5905
|
+
currentOwnerSessionId: import_project_worktrees.PROJECT_WORKTREE_SNAPSHOT_OWNER_SESSION_ID
|
|
5906
|
+
},
|
|
5907
|
+
{
|
|
5908
|
+
onProgress: (progress) => {
|
|
5909
|
+
const lifecycleProgress = {
|
|
5910
|
+
phase: "recovering",
|
|
5911
|
+
operationId,
|
|
5912
|
+
detail: describeSnapshotProgress(progress),
|
|
5913
|
+
completedBytes: progress.completedBytes,
|
|
5914
|
+
completedItems: progress.completedBranches,
|
|
5915
|
+
totalItems: progress.totalBranches,
|
|
5916
|
+
...progress.totalBytes === void 0 ? {} : { totalBytes: progress.totalBytes }
|
|
5917
|
+
};
|
|
5918
|
+
sendLifecycleProgress(lifecycleProgress);
|
|
5919
|
+
process.stdout.write(
|
|
5920
|
+
`[r5d-worker] ${lifecycleProgress.detail}: ${progress.completedBranches}/${progress.totalBranches} branch(es), ${progress.completedBytes}/${progress.totalBytes ?? "?"} bytes
|
|
5740
5921
|
`
|
|
5741
|
-
|
|
5922
|
+
);
|
|
5923
|
+
}
|
|
5742
5924
|
}
|
|
5743
|
-
|
|
5925
|
+
)
|
|
5744
5926
|
);
|
|
5745
5927
|
if (snapshotRecovery.restored.length > 0) {
|
|
5746
5928
|
process.stdout.write(`[r5d-worker] restored ${snapshotRecovery.restored.length} interrupted project snapshot(s)
|
|
@@ -5835,7 +6017,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
5835
6017
|
type: "hello",
|
|
5836
6018
|
resumableProtocol: import_recovery_protocol.WORKER_RESUMABLE_PROTOCOL,
|
|
5837
6019
|
runtimeId: workerRuntimeId,
|
|
5838
|
-
ledgerId:
|
|
6020
|
+
ledgerId: recoveryJournal.ledgerId,
|
|
5839
6021
|
startupReady: startupRecoveryComplete,
|
|
5840
6022
|
hostInfo: {
|
|
5841
6023
|
hostname: (0, import_node_os.hostname)(),
|
|
@@ -5854,6 +6036,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
5854
6036
|
workspaceConfigResetToCanonicalV1: true,
|
|
5855
6037
|
projectBranchDeletionFastAckV1: true,
|
|
5856
6038
|
projectBranchDeletionStagedRemovalV1: true,
|
|
6039
|
+
commandControlLaneV1: workerCommandLauncher?.supportsControlLane === true,
|
|
5857
6040
|
projectMirrorLeaseV1: true,
|
|
5858
6041
|
projectMirrorRefsTokensV1: true,
|
|
5859
6042
|
projectBranchWorkingTreeModeV1: true
|
|
@@ -5862,16 +6045,39 @@ async function startWorker(options, projectRuntime = {
|
|
|
5862
6045
|
}
|
|
5863
6046
|
};
|
|
5864
6047
|
sendSerializedWorkerMessage(ws, JSON.stringify(hello));
|
|
6048
|
+
let outputPumpInFlight = false;
|
|
5865
6049
|
outputReplayTimer = setInterval(() => {
|
|
5866
|
-
if (workerRecoveryId !== void 0 || currentWorkerSocket !== ws) return;
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
6050
|
+
if (workerRecoveryId !== void 0 || currentWorkerSocket !== ws || outputPumpInFlight || !recoveryJournal) return;
|
|
6051
|
+
outputPumpInFlight = true;
|
|
6052
|
+
void (async () => {
|
|
6053
|
+
try {
|
|
6054
|
+
for await (const page of recoveryJournal.outputReplay(void 0)) {
|
|
6055
|
+
for (const output of page) {
|
|
6056
|
+
if (workerRecoveryId !== void 0 || currentWorkerSocket !== ws || !sendReplayWorkerMessage(ws, output)) return;
|
|
6057
|
+
}
|
|
6058
|
+
}
|
|
6059
|
+
} catch (error) {
|
|
6060
|
+
process.stderr.write(`[r5d-worker] output replay pump failed: ${error instanceof Error ? error.message : String(error)}
|
|
6061
|
+
`);
|
|
6062
|
+
} finally {
|
|
6063
|
+
outputPumpInFlight = false;
|
|
6064
|
+
}
|
|
6065
|
+
})();
|
|
5870
6066
|
}, 250);
|
|
5871
6067
|
outputReplayTimer.unref();
|
|
6068
|
+
let terminalPumpInFlight = false;
|
|
5872
6069
|
terminalReplayTimer = setInterval(() => {
|
|
5873
|
-
if (workerRecoveryId !== void 0) return;
|
|
5874
|
-
|
|
6070
|
+
if (workerRecoveryId !== void 0 || terminalPumpInFlight || !recoveryJournal) return;
|
|
6071
|
+
terminalPumpInFlight = true;
|
|
6072
|
+
void recoveryJournal.terminals().then((terminals) => {
|
|
6073
|
+
if (workerRecoveryId !== void 0 || currentWorkerSocket !== ws) return;
|
|
6074
|
+
for (const terminal of terminals) sendReplayWorkerMessage(ws, terminal);
|
|
6075
|
+
}).catch((error) => {
|
|
6076
|
+
process.stderr.write(`[r5d-worker] terminal replay pump failed: ${error instanceof Error ? error.message : String(error)}
|
|
6077
|
+
`);
|
|
6078
|
+
}).finally(() => {
|
|
6079
|
+
terminalPumpInFlight = false;
|
|
6080
|
+
});
|
|
5875
6081
|
}, 5e3);
|
|
5876
6082
|
terminalReplayTimer.unref();
|
|
5877
6083
|
process.stdout.write("[r5d-worker] connected\n");
|
|
@@ -5884,28 +6090,37 @@ async function startWorker(options, projectRuntime = {
|
|
|
5884
6090
|
if (message.type === "recovery_request") {
|
|
5885
6091
|
workerRecoveryId = message.recoveryId;
|
|
5886
6092
|
sendActiveProcessReport(ws);
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
6093
|
+
recoveryJournal.pauseDelivery();
|
|
6094
|
+
try {
|
|
6095
|
+
const state = await recoveryJournal.recoveryState(message.requests);
|
|
6096
|
+
if (currentWorkerSocket !== ws || workerRecoveryId !== message.recoveryId) return;
|
|
6097
|
+
sendWorkerMessage(ws, {
|
|
6098
|
+
type: "recovery_state",
|
|
6099
|
+
recoveryId: message.recoveryId,
|
|
6100
|
+
runtimeId: workerRuntimeId,
|
|
6101
|
+
ledgerId: recoveryJournal.ledgerId,
|
|
6102
|
+
operations: state.operations
|
|
6103
|
+
});
|
|
6104
|
+
const cursors = new Map(state.cursors.map((cursor) => [cursor.runId, cursor]));
|
|
6105
|
+
for (const cursor of message.outputOffsets) cursors.set(cursor.runId, cursor);
|
|
6106
|
+
workerOutputWindow.reset([...cursors.values()]);
|
|
6107
|
+
for (const response of state.responses) sendReplayWorkerMessage(ws, response);
|
|
6108
|
+
for await (const page of recoveryJournal.outputReplay(message.outputOffsets)) {
|
|
6109
|
+
for (const output of page) {
|
|
6110
|
+
while (true) {
|
|
6111
|
+
if (currentWorkerSocket !== ws || workerRecoveryId !== message.recoveryId || ws.readyState !== WebSocket.OPEN || workerLeaseExpired)
|
|
6112
|
+
return;
|
|
6113
|
+
if (sendReplayWorkerMessage(ws, output)) break;
|
|
6114
|
+
await Bun.sleep(10);
|
|
6115
|
+
}
|
|
6116
|
+
}
|
|
5904
6117
|
}
|
|
6118
|
+
if (currentWorkerSocket !== ws || workerRecoveryId !== message.recoveryId) return;
|
|
6119
|
+
for (const terminal of state.terminals) sendReplayWorkerMessage(ws, terminal);
|
|
6120
|
+
sendWorkerMessage(ws, { type: "recovery_complete", recoveryId: message.recoveryId });
|
|
6121
|
+
} finally {
|
|
6122
|
+
recoveryJournal.resumeDelivery();
|
|
5905
6123
|
}
|
|
5906
|
-
if (currentWorkerSocket !== ws || workerRecoveryId !== message.recoveryId) return;
|
|
5907
|
-
for (const terminal of recoveryStore.terminals()) sendReplayWorkerMessage(ws, terminal);
|
|
5908
|
-
sendWorkerMessage(ws, { type: "recovery_complete", recoveryId: message.recoveryId });
|
|
5909
6124
|
return;
|
|
5910
6125
|
}
|
|
5911
6126
|
if (message.type === "recovery_ready") {
|
|
@@ -5920,17 +6135,23 @@ async function startWorker(options, projectRuntime = {
|
|
|
5920
6135
|
return;
|
|
5921
6136
|
}
|
|
5922
6137
|
if (message.type === "operation_result_ack") {
|
|
5923
|
-
|
|
6138
|
+
recoveryJournal.acknowledgeResult(message.requestId);
|
|
5924
6139
|
return;
|
|
5925
6140
|
}
|
|
5926
6141
|
if (message.type === "exec_output_ack") {
|
|
5927
|
-
|
|
6142
|
+
recoveryJournal.acknowledgeOutput(message.runId, message.stream, message.nextOffset);
|
|
5928
6143
|
workerOutputWindow.acknowledge(message.runId, message.stream, message.nextOffset);
|
|
5929
6144
|
return;
|
|
5930
6145
|
}
|
|
5931
6146
|
if (message.type === "cancel_session") {
|
|
5932
|
-
|
|
6147
|
+
const durableCancellation = recoveryJournal.cancelSession(message.sessionId);
|
|
6148
|
+
abortPendingReservations("Session cancelled before command start", message.sessionId);
|
|
5933
6149
|
await Promise.all([
|
|
6150
|
+
durableCancellation,
|
|
6151
|
+
// File writes already in flight for the session settle before the
|
|
6152
|
+
// acknowledgement; queued ones re-check the cancellation before
|
|
6153
|
+
// their first write and never start.
|
|
6154
|
+
sessionFileMutations.settled(message.sessionId),
|
|
5934
6155
|
workerCommandLauncher?.cancelSession(message.sessionId),
|
|
5935
6156
|
...[...activeProcesses].filter(([, active]) => active.sessionId === message.sessionId).map(async ([runId, active]) => {
|
|
5936
6157
|
cancelledProcessRuns.add(runId);
|
|
@@ -5945,27 +6166,74 @@ async function startWorker(options, projectRuntime = {
|
|
|
5945
6166
|
if (!startupRecoveryComplete && message.type !== "ping") return;
|
|
5946
6167
|
if (currentWorkerSocket !== ws) return;
|
|
5947
6168
|
const operationRequestId = "requestId" in message && typeof message.requestId === "string" && message.type !== "workspace_config" ? message.requestId : void 0;
|
|
6169
|
+
if (message.type === "cancel") {
|
|
6170
|
+
const admission = recoveryJournal.admit({ ...message, requestId: message.requestId });
|
|
6171
|
+
const cancelled = await cancelProcessRun(message.runId);
|
|
6172
|
+
let admitted;
|
|
6173
|
+
try {
|
|
6174
|
+
admitted = await admission;
|
|
6175
|
+
} catch (error) {
|
|
6176
|
+
if (!(error instanceof import_recovery_journal.WorkerRecoveryJournalBusyError)) throw error;
|
|
6177
|
+
process.stderr.write(`[r5d-worker] cancel ${message.runId} stopped the run but was not durably admitted: ${error.message}
|
|
6178
|
+
`);
|
|
6179
|
+
return;
|
|
6180
|
+
}
|
|
6181
|
+
if (admitted.admission === "conflict") {
|
|
6182
|
+
ws.close(1008, "Operation identity conflict");
|
|
6183
|
+
return;
|
|
6184
|
+
}
|
|
6185
|
+
sendWorkerMessage(ws, { type: "operation_received", requestId: message.requestId });
|
|
6186
|
+
if (admitted.admission !== "new") {
|
|
6187
|
+
if (admitted.response && admitted.admission !== "unknown") sendReplayWorkerMessage(ws, admitted.response);
|
|
6188
|
+
return;
|
|
6189
|
+
}
|
|
6190
|
+
sendSerializedWorkerMessage(
|
|
6191
|
+
ws,
|
|
6192
|
+
JSON.stringify({
|
|
6193
|
+
type: "cancel_result",
|
|
6194
|
+
requestId: message.requestId,
|
|
6195
|
+
runId: message.runId,
|
|
6196
|
+
cancelled: cancelled.cancelled,
|
|
6197
|
+
message: cancelled.message
|
|
6198
|
+
})
|
|
6199
|
+
);
|
|
6200
|
+
return;
|
|
6201
|
+
}
|
|
5948
6202
|
if (operationRequestId) {
|
|
5949
|
-
|
|
5950
|
-
|
|
6203
|
+
let admitted;
|
|
6204
|
+
try {
|
|
6205
|
+
admitted = await recoveryJournal.admit({ ...message, requestId: operationRequestId });
|
|
6206
|
+
} catch (error) {
|
|
6207
|
+
if (!(error instanceof import_recovery_journal.WorkerRecoveryJournalBusyError)) throw error;
|
|
6208
|
+
process.stderr.write(`[r5d-worker] ${message.type} ${operationRequestId} was not admitted: ${error.message}
|
|
6209
|
+
`);
|
|
6210
|
+
return;
|
|
6211
|
+
}
|
|
6212
|
+
if (admitted.admission === "conflict") {
|
|
5951
6213
|
ws.close(1008, "Operation identity conflict");
|
|
5952
6214
|
return;
|
|
5953
6215
|
}
|
|
5954
6216
|
sendWorkerMessage(ws, { type: "operation_received", requestId: operationRequestId });
|
|
5955
|
-
if (admission !== "new") {
|
|
5956
|
-
|
|
5957
|
-
if (
|
|
5958
|
-
|
|
6217
|
+
if (admitted.admission !== "new") {
|
|
6218
|
+
if (admitted.response && admitted.admission !== "unknown") sendReplayWorkerMessage(ws, admitted.response);
|
|
6219
|
+
if (!(admitted.admission === "unknown" && message.type === "delete_project_branch" && await recoveryJournal.readmitUnknownBranchDeletion(
|
|
6220
|
+
operationRequestId,
|
|
6221
|
+
"sessionId" in message && typeof message.sessionId === "string" ? message.sessionId : void 0
|
|
6222
|
+
))) {
|
|
5959
6223
|
return;
|
|
5960
6224
|
}
|
|
5961
6225
|
}
|
|
6226
|
+
if (recoveryJournal.isRequestCancelled(operationRequestId)) {
|
|
6227
|
+
recoveryJournal.unknown(operationRequestId);
|
|
6228
|
+
return;
|
|
6229
|
+
}
|
|
5962
6230
|
}
|
|
5963
6231
|
const messageAdmissionGeneration = workerAdmissionGeneration;
|
|
5964
6232
|
const admissionCredentials = configuredCredentialGenerationFingerprint;
|
|
5965
6233
|
const assertMessageAdmission = () => {
|
|
5966
6234
|
if (operationRequestId) {
|
|
5967
|
-
if (workerLeaseExpired ||
|
|
5968
|
-
|
|
6235
|
+
if (workerLeaseExpired || recoveryJournal.isRequestCancelled(operationRequestId) || admissionCredentials !== configuredCredentialGenerationFingerprint || shutdownAfterClose || !workspaceConfigured) {
|
|
6236
|
+
recoveryJournal.unknown(operationRequestId);
|
|
5969
6237
|
throw new StaleWorkerAdmissionError();
|
|
5970
6238
|
}
|
|
5971
6239
|
return;
|
|
@@ -5980,6 +6248,8 @@ async function startWorker(options, projectRuntime = {
|
|
|
5980
6248
|
});
|
|
5981
6249
|
};
|
|
5982
6250
|
if (message.type === "connected") {
|
|
6251
|
+
capacityReportSocket = ws;
|
|
6252
|
+
scheduleCapacityReport(true);
|
|
5983
6253
|
return;
|
|
5984
6254
|
}
|
|
5985
6255
|
if (message.type === "project_mirror_refs_tokens") {
|
|
@@ -6100,7 +6370,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6100
6370
|
}
|
|
6101
6371
|
recoveryDeadline ??= Date.now() + import_recovery_protocol.WORKER_RECOVERY_TIMEOUT_MS;
|
|
6102
6372
|
if (workerLeaseExpired || Date.now() >= recoveryDeadline) {
|
|
6103
|
-
|
|
6373
|
+
recoveryJournal.unknown(message.requestId);
|
|
6104
6374
|
return;
|
|
6105
6375
|
}
|
|
6106
6376
|
currentWorkerSocket?.close(1012, "Communication recovery");
|
|
@@ -6130,7 +6400,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6130
6400
|
projectId: message.projectId,
|
|
6131
6401
|
branchName: message.targetBranch
|
|
6132
6402
|
};
|
|
6133
|
-
const created = await workspaceSyncSingleFlight.runBranchMutation(() => {
|
|
6403
|
+
const created = await workspaceSyncSingleFlight.runBranchMutation(async () => {
|
|
6134
6404
|
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
6135
6405
|
throw new Error(
|
|
6136
6406
|
`Project branch creation was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
@@ -6145,7 +6415,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6145
6415
|
const pendingWasRecorded = projectWorkspaceState.locallyPendingCreatedBranches.some(
|
|
6146
6416
|
({ projectId, branchName }) => projectId === message.projectId && branchName === message.targetBranch
|
|
6147
6417
|
);
|
|
6148
|
-
projectWorkspaceState = projectWorkspaceStateStore.recordPendingCreatedBranch(pendingBranch);
|
|
6418
|
+
projectWorkspaceState = await projectWorkspaceStateStore.recordPendingCreatedBranch(pendingBranch);
|
|
6149
6419
|
let gitBranchCreated = false;
|
|
6150
6420
|
try {
|
|
6151
6421
|
if (!readyProjectIds.has(project.projectId)) {
|
|
@@ -6166,7 +6436,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6166
6436
|
}
|
|
6167
6437
|
return { branchPath: existingPath, baseCommitHash: existingBranch.baseCommitHash };
|
|
6168
6438
|
}
|
|
6169
|
-
const createdBranch = (0, import_project_worktrees.createOrRetryLinkedProjectBranch)({
|
|
6439
|
+
const createdBranch = await (0, import_project_worktrees.createOrRetryLinkedProjectBranch)({
|
|
6170
6440
|
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
6171
6441
|
primaryBranchName: primaryProjectBranch(project),
|
|
6172
6442
|
sourceBranchName: message.sourceBranch,
|
|
@@ -6191,7 +6461,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6191
6461
|
return createdBranch;
|
|
6192
6462
|
} catch (error) {
|
|
6193
6463
|
if (!pendingWasRecorded && !gitBranchCreated && !(0, import_project_worktrees.projectBranchMayExistAfterCreateFailure)(error)) {
|
|
6194
|
-
projectWorkspaceState = projectWorkspaceStateStore.rollbackPendingCreatedBranch(pendingBranch);
|
|
6464
|
+
projectWorkspaceState = await projectWorkspaceStateStore.rollbackPendingCreatedBranch(pendingBranch);
|
|
6195
6465
|
}
|
|
6196
6466
|
throw error;
|
|
6197
6467
|
}
|
|
@@ -6235,7 +6505,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6235
6505
|
});
|
|
6236
6506
|
try {
|
|
6237
6507
|
const deletionKey = projectBranchKey(message.projectId, message.branchName);
|
|
6238
|
-
const deletionNeedsSync = await workspaceSyncSingleFlight.runBranchMutation(() => {
|
|
6508
|
+
const deletionNeedsSync = await workspaceSyncSingleFlight.runBranchMutation(async () => {
|
|
6239
6509
|
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
6240
6510
|
throw new Error(
|
|
6241
6511
|
`Project branch deletion was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
@@ -6262,12 +6532,12 @@ async function startWorker(options, projectRuntime = {
|
|
|
6262
6532
|
if (!configuredBranch && !pendingLocalBranch && !hasDurableTombstone && !pendingMirrorDeletes.has(deletionKey)) {
|
|
6263
6533
|
return false;
|
|
6264
6534
|
}
|
|
6265
|
-
projectWorkspaceState = projectWorkspaceStateStore.beginBranchDeletion({
|
|
6535
|
+
projectWorkspaceState = await projectWorkspaceStateStore.beginBranchDeletion({
|
|
6266
6536
|
projectId: message.projectId,
|
|
6267
6537
|
branchName: message.branchName
|
|
6268
6538
|
});
|
|
6269
6539
|
if (pendingLocalBranch) {
|
|
6270
|
-
projectWorkspaceState = projectWorkspaceStateStore.clearPendingCreatedBranch(pendingLocalBranch);
|
|
6540
|
+
projectWorkspaceState = await projectWorkspaceStateStore.clearPendingCreatedBranch(pendingLocalBranch);
|
|
6271
6541
|
}
|
|
6272
6542
|
pendingCreatedBranchPublicationNotBefore.delete((0, import_workspace_automatic_sync_policy.pendingCreatedBranchKey)(message.projectId, message.branchName));
|
|
6273
6543
|
if (pendingMirrorDeletes.has(deletionKey)) return true;
|
|
@@ -6371,7 +6641,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6371
6641
|
}
|
|
6372
6642
|
if (message.type === "exec_terminal_ack") {
|
|
6373
6643
|
pendingProcessTerminals.delete(message.runId);
|
|
6374
|
-
|
|
6644
|
+
recoveryJournal.acknowledgeTerminal(message.runId);
|
|
6375
6645
|
return;
|
|
6376
6646
|
}
|
|
6377
6647
|
if (message.type === "port_forward_connect") {
|
|
@@ -6426,42 +6696,6 @@ async function startWorker(options, projectRuntime = {
|
|
|
6426
6696
|
sendSerializedWorkerMessage(ws, JSON.stringify({ type: "pong" }));
|
|
6427
6697
|
return;
|
|
6428
6698
|
}
|
|
6429
|
-
if (message.type === "cancel") {
|
|
6430
|
-
let resourceCancellationError;
|
|
6431
|
-
try {
|
|
6432
|
-
await workerCommandLauncher?.cancel(message.runId);
|
|
6433
|
-
} catch (error) {
|
|
6434
|
-
resourceCancellationError = error instanceof Error ? error.message : String(error);
|
|
6435
|
-
}
|
|
6436
|
-
const active = activeProcesses.get(message.runId);
|
|
6437
|
-
let cancelled = resourceCancellationError === void 0;
|
|
6438
|
-
let cancelMessage;
|
|
6439
|
-
if (active) {
|
|
6440
|
-
try {
|
|
6441
|
-
closeProcessStdin(active);
|
|
6442
|
-
await (0, import_process_tree.terminateProcessTree)(active.process);
|
|
6443
|
-
cancelMessage = `Stopped ${active.command}`;
|
|
6444
|
-
} catch (error) {
|
|
6445
|
-
cancelled = false;
|
|
6446
|
-
cancelMessage = `Failed to stop ${active.command}: ${error instanceof Error ? error.message : String(error)}`;
|
|
6447
|
-
}
|
|
6448
|
-
} else {
|
|
6449
|
-
cancelledProcessRuns.add(message.runId);
|
|
6450
|
-
cancelMessage = "Cancellation queued before command start";
|
|
6451
|
-
}
|
|
6452
|
-
if (resourceCancellationError) cancelMessage += `; job resource termination failed: ${resourceCancellationError}`;
|
|
6453
|
-
sendSerializedWorkerMessage(
|
|
6454
|
-
ws,
|
|
6455
|
-
JSON.stringify({
|
|
6456
|
-
type: "cancel_result",
|
|
6457
|
-
requestId: message.requestId,
|
|
6458
|
-
runId: message.runId,
|
|
6459
|
-
cancelled,
|
|
6460
|
-
message: cancelMessage
|
|
6461
|
-
})
|
|
6462
|
-
);
|
|
6463
|
-
return;
|
|
6464
|
-
}
|
|
6465
6699
|
if (message.type === "exec_stdin") {
|
|
6466
6700
|
const sendAck = (payload) => sendSerializedWorkerMessage(
|
|
6467
6701
|
ws,
|
|
@@ -6542,10 +6776,20 @@ async function startWorker(options, projectRuntime = {
|
|
|
6542
6776
|
let mutationLeaseTransferred = false;
|
|
6543
6777
|
try {
|
|
6544
6778
|
resources = await acquireWorkerCommandLaunch(ws, message, assertMessageAdmission);
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6779
|
+
const reservationAbort = registerPendingReservation(`pty:${message.ptyId}`, void 0);
|
|
6780
|
+
try {
|
|
6781
|
+
await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(
|
|
6782
|
+
message.target,
|
|
6783
|
+
workspaceSyncSingleFlight,
|
|
6784
|
+
() => {
|
|
6785
|
+
workspaceSyncPriorityPtyTargets.set(message.ptyId, message.target);
|
|
6786
|
+
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
6787
|
+
},
|
|
6788
|
+
{ signal: reservationAbort.signal }
|
|
6789
|
+
);
|
|
6790
|
+
} finally {
|
|
6791
|
+
unregisterPendingReservation(`pty:${message.ptyId}`, reservationAbort);
|
|
6792
|
+
}
|
|
6549
6793
|
releaseWorkspaceMutation = await (0, import_workspace_command_sync_policy.acquireWorkspaceCommandMutation)(message.target, workspaceSyncSingleFlight);
|
|
6550
6794
|
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
6551
6795
|
throw new Error(
|
|
@@ -6610,6 +6854,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6610
6854
|
}
|
|
6611
6855
|
if (message.type === "pty_close") {
|
|
6612
6856
|
const activePty = activePtys.get(message.ptyId);
|
|
6857
|
+
abortPendingReservation(`pty:${message.ptyId}`, "Shell closed before it started");
|
|
6613
6858
|
await closePty(message);
|
|
6614
6859
|
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
6615
6860
|
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} closed` }, true, activePty.target);
|
|
@@ -6646,11 +6891,21 @@ async function startWorker(options, projectRuntime = {
|
|
|
6646
6891
|
try {
|
|
6647
6892
|
resources = await acquireWorkerCommandLaunch(ws, message, assertMessageAdmission);
|
|
6648
6893
|
if (hasWorkspaceEffect) {
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6894
|
+
const reservationAbort = registerPendingReservation(`run:${message.runId}`, message.sessionId);
|
|
6895
|
+
try {
|
|
6896
|
+
await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(
|
|
6897
|
+
message.target,
|
|
6898
|
+
workspaceSyncSingleFlight,
|
|
6899
|
+
() => {
|
|
6900
|
+
workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
|
|
6901
|
+
targetReserved = true;
|
|
6902
|
+
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
6903
|
+
},
|
|
6904
|
+
{ signal: reservationAbort.signal }
|
|
6905
|
+
);
|
|
6906
|
+
} finally {
|
|
6907
|
+
unregisterPendingReservation(`run:${message.runId}`, reservationAbort);
|
|
6908
|
+
}
|
|
6654
6909
|
}
|
|
6655
6910
|
const runCommand = async () => {
|
|
6656
6911
|
if (!workspaceCommandTransportIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, "exec")) {
|
|
@@ -6702,7 +6957,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6702
6957
|
cancelledProcessRuns.delete(message.runId);
|
|
6703
6958
|
}
|
|
6704
6959
|
if (infrastructureStoppedRunIds.has(message.runId)) {
|
|
6705
|
-
|
|
6960
|
+
recoveryJournal.unknown(message.requestId);
|
|
6706
6961
|
return;
|
|
6707
6962
|
}
|
|
6708
6963
|
sendSerializedWorkerMessage(ws, JSON.stringify(result));
|
|
@@ -6735,8 +6990,10 @@ async function startWorker(options, projectRuntime = {
|
|
|
6735
6990
|
const runCommand = async () => {
|
|
6736
6991
|
try {
|
|
6737
6992
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
6738
|
-
process.stdout.write(
|
|
6739
|
-
`)
|
|
6993
|
+
process.stdout.write(
|
|
6994
|
+
`[r5d-worker] exec_start ${message.runId}${message.commandClass === "control" ? " (control)" : ""}: ${message.argv.join(" ")}
|
|
6995
|
+
`
|
|
6996
|
+
);
|
|
6740
6997
|
await executeStreamingCommand({
|
|
6741
6998
|
resources,
|
|
6742
6999
|
ws,
|
|
@@ -6774,11 +7031,21 @@ async function startWorker(options, projectRuntime = {
|
|
|
6774
7031
|
try {
|
|
6775
7032
|
resources = await acquireWorkerCommandLaunch(ws, message, assertMessageAdmission);
|
|
6776
7033
|
if (hasWorkspaceEffect) {
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
7034
|
+
const reservationAbort = registerPendingReservation(`run:${message.runId}`, message.sessionId);
|
|
7035
|
+
try {
|
|
7036
|
+
await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(
|
|
7037
|
+
message.target,
|
|
7038
|
+
workspaceSyncSingleFlight,
|
|
7039
|
+
() => {
|
|
7040
|
+
workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
|
|
7041
|
+
targetReserved = true;
|
|
7042
|
+
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
7043
|
+
},
|
|
7044
|
+
{ signal: reservationAbort.signal }
|
|
7045
|
+
);
|
|
7046
|
+
} finally {
|
|
7047
|
+
unregisterPendingReservation(`run:${message.runId}`, reservationAbort);
|
|
7048
|
+
}
|
|
6782
7049
|
}
|
|
6783
7050
|
await (0, import_workspace_command_sync_policy.runReservedWorkspaceCommand)(message.target, workspaceSyncSingleFlight, runCommand, () => {
|
|
6784
7051
|
if (targetReserved) {
|
|
@@ -6813,16 +7080,40 @@ async function startWorker(options, projectRuntime = {
|
|
|
6813
7080
|
return;
|
|
6814
7081
|
}
|
|
6815
7082
|
const reservesVisibleWorkspace = targetMayMutateVisibleWorkspace(message.target);
|
|
6816
|
-
const
|
|
7083
|
+
const mutatesFiles = message.type === "write" || message.type === "edit" || message.type === "update_task_status" || message.type === "read_plan" && message.createIfMissing;
|
|
7084
|
+
const mutatesVisibleWorkspace = mutatesFiles && targetMayMutateVisibleWorkspace(message.target);
|
|
6817
7085
|
if (reservesVisibleWorkspace) {
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
7086
|
+
const reservationAbort = registerPendingReservation(
|
|
7087
|
+
`op:${message.requestId}`,
|
|
7088
|
+
"sessionId" in message && typeof message.sessionId === "string" ? message.sessionId : void 0
|
|
7089
|
+
);
|
|
7090
|
+
try {
|
|
7091
|
+
await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(
|
|
7092
|
+
message.target,
|
|
7093
|
+
workspaceSyncSingleFlight,
|
|
7094
|
+
() => {
|
|
7095
|
+
workspaceSyncPriorityOperationTargets.set(message.requestId, message.target);
|
|
7096
|
+
if (mutatesVisibleWorkspace) recordVisibleWorkspaceMutation(message.target);
|
|
7097
|
+
},
|
|
7098
|
+
{ signal: reservationAbort.signal }
|
|
7099
|
+
);
|
|
7100
|
+
} catch (error) {
|
|
7101
|
+
sendSerializedWorkerMessage(
|
|
7102
|
+
ws,
|
|
7103
|
+
JSON.stringify({
|
|
7104
|
+
type: "operation_result",
|
|
7105
|
+
requestId: message.requestId,
|
|
7106
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7107
|
+
})
|
|
7108
|
+
);
|
|
7109
|
+
return;
|
|
7110
|
+
} finally {
|
|
7111
|
+
unregisterPendingReservation(`op:${message.requestId}`, reservationAbort);
|
|
7112
|
+
}
|
|
6822
7113
|
}
|
|
6823
7114
|
let dirtyTrigger;
|
|
6824
7115
|
try {
|
|
6825
|
-
const
|
|
7116
|
+
const operation = (0, import_workspace_command_sync_policy.runWorkspaceCommand)(message.target, workspaceSyncSingleFlight, async () => {
|
|
6826
7117
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
6827
7118
|
return await executeOperation({
|
|
6828
7119
|
message,
|
|
@@ -6833,6 +7124,8 @@ async function startWorker(options, projectRuntime = {
|
|
|
6833
7124
|
assertAdmission: assertMessageAdmission
|
|
6834
7125
|
});
|
|
6835
7126
|
});
|
|
7127
|
+
if (mutatesFiles && typeof message.sessionId === "string") sessionFileMutations.track(message.sessionId, operation);
|
|
7128
|
+
const result = await operation;
|
|
6836
7129
|
sendSerializedWorkerMessage(
|
|
6837
7130
|
ws,
|
|
6838
7131
|
JSON.stringify({
|
|
@@ -6916,6 +7209,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6916
7209
|
projectRuntime.configuration.deferred = deferredWorkspaceConfiguration;
|
|
6917
7210
|
}
|
|
6918
7211
|
if (currentWorkerSocket === ws) currentWorkerSocket = null;
|
|
7212
|
+
abortPendingReservations("Worker connection closed before command start");
|
|
6919
7213
|
workerRecoveryId = "reconnecting";
|
|
6920
7214
|
const reason = event.reason ? `: ${event.reason}` : "";
|
|
6921
7215
|
process.stderr.write(`[r5d-worker] disconnected (${event.code}${reason})
|
|
@@ -6949,6 +7243,11 @@ async function startWorker(options, projectRuntime = {
|
|
|
6949
7243
|
});
|
|
6950
7244
|
}
|
|
6951
7245
|
function installWorkerRuntimeFailureLogging() {
|
|
7246
|
+
(0, import_workspace_filesystem_executor.installWorkspaceFilesystemExecutorFailureHandler)((error) => {
|
|
7247
|
+
process.stderr.write(`[r5d-worker] ${error.message}; restarting the worker runtime
|
|
7248
|
+
`);
|
|
7249
|
+
void exitWorkerRuntime(import_supervisor.WORKER_RECONNECT_EXIT_CODE);
|
|
7250
|
+
});
|
|
6952
7251
|
process.on("uncaughtException", (error) => {
|
|
6953
7252
|
process.stderr.write(`[r5d-worker] uncaught exception: ${error instanceof Error ? error.stack ?? error.message : String(error)}
|
|
6954
7253
|
`);
|
|
@@ -6961,11 +7260,6 @@ function installWorkerRuntimeFailureLogging() {
|
|
|
6961
7260
|
});
|
|
6962
7261
|
}
|
|
6963
7262
|
async function main() {
|
|
6964
|
-
if (process.argv[2] === import_project_snapshot_recovery_runner.PROJECT_SNAPSHOT_RECOVERY_HELPER_COMMAND) {
|
|
6965
|
-
const exitCode2 = (0, import_project_snapshot_recovery_runner.runProjectSnapshotRecoveryHelper)(process.argv.slice(3));
|
|
6966
|
-
if (exitCode2 !== 0) process.exit(exitCode2);
|
|
6967
|
-
return;
|
|
6968
|
-
}
|
|
6969
7263
|
const parsed = parseArgs(process.argv.slice(2));
|
|
6970
7264
|
if (parsed.command === "help") {
|
|
6971
7265
|
printHelp();
|