@ricsam/r5d-worker 0.0.124 → 0.0.125
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cjs/command-launcher.cjs +6 -2
- package/dist/cjs/main.cjs +22 -14
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/command-launcher.mjs +6 -2
- package/dist/mjs/main.mjs +22 -14
- package/dist/mjs/package.json +1 -1
- package/dist/types/command-launcher.d.ts +2 -1
- package/dist/types/main.d.ts +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ The launcher owns admission, OS isolation, budgets, and diagnostic evidence. Its
|
|
|
89
89
|
|
|
90
90
|
Requests must be processed concurrently so cancellation and cleanup cannot wait behind queued admission. Controller RPCs are bounded (10 seconds, or 90 seconds for acquisition); external policy must reject excessive queue waits within that bound. Controller loss or protocol failure stops command admission, preserves uncertain outcomes for recovery, reaps ordinary children, and restarts the runtime through its supervisor. It never falls back to direct spawning. Shutdown closes controller stdin; the controller must reap its jobs before exiting.
|
|
91
91
|
|
|
92
|
-
Cancellation is checked again immediately before spawn. Transient disconnects preserve commands and terminals; expiry of the five-minute execution lease cancels agent work and closes terminals. File operations, communication recovery, and cancellation do not enter launcher admission. Internal worker maintenance subprocesses are also outside this hook. The worker reports its own view of every lane (holders and queue, by run id) to the server with `capacity_report` whenever an admission changes; the platform exposes it through `r5dctl ps list` and `r5dctl workspace status`. Deployment-specific limits and service protection are documented in [Worker deployment resources](../../docs/worker-deployment-resources.md).
|
|
92
|
+
Cancellation is checked again immediately before spawn. A `cancel` carrying `scope: "unstarted"` is the server giving up on a start whose budget expired: it revokes only a launch that has not spawned (capacity queue, mount hold, preparation) and leaves a running process alone; `cancel_result.outcome` reports `unstarted`, `started`, `stopped`, or `unknown` (no record of the run; the cancellation is remembered so a later arrival is refused). Transient disconnects preserve commands and terminals; expiry of the five-minute execution lease cancels agent work and closes terminals. File operations, communication recovery, and cancellation do not enter launcher admission. Internal worker maintenance subprocesses are also outside this hook. The worker reports its own view of every lane (holders and queue, by run id) to the server with `capacity_report` whenever an admission changes; the platform exposes it through `r5dctl ps list` and `r5dctl workspace status`. Deployment-specific limits and service protection are documented in [Worker deployment resources](../../docs/worker-deployment-resources.md).
|
|
93
93
|
|
|
94
94
|
When the connected `r5d-browser` requests a port forward, the worker opens each relayed connection only to `127.0.0.1` on the requested worker port. Browser-side and worker-side ports may differ. The worker never opens a public listener, and a disconnected worker leaves the browser's long-lived mapping unavailable until the same worker label reconnects.
|
|
95
95
|
|
|
@@ -245,7 +245,9 @@ class WorkerCommandLauncher {
|
|
|
245
245
|
if (!argv.length) throw new Error("Command argv must not be empty");
|
|
246
246
|
return [...prefix, ...argv];
|
|
247
247
|
},
|
|
248
|
-
cancel: () =>
|
|
248
|
+
cancel: async () => {
|
|
249
|
+
await this.cancel(request.id);
|
|
250
|
+
},
|
|
249
251
|
diagnose: async () => {
|
|
250
252
|
this.assertHealthy();
|
|
251
253
|
if (!this.child) return void 0;
|
|
@@ -261,9 +263,10 @@ class WorkerCommandLauncher {
|
|
|
261
263
|
release: () => this.releaseRecord(record)
|
|
262
264
|
};
|
|
263
265
|
}
|
|
266
|
+
/** Revoke a launch. Returns whether this launcher still held it (queued or acquired, not yet released). */
|
|
264
267
|
async cancel(id) {
|
|
265
268
|
const record = this.launches.get(id);
|
|
266
|
-
if (!record || record.released) return;
|
|
269
|
+
if (!record || record.released) return false;
|
|
267
270
|
record.revoked = true;
|
|
268
271
|
record.rejectAdmission(new CommandLaunchCancelledError());
|
|
269
272
|
if (this.child) {
|
|
@@ -273,6 +276,7 @@ class WorkerCommandLauncher {
|
|
|
273
276
|
throw this.fail(error instanceof Error ? error : new Error(String(error)));
|
|
274
277
|
}
|
|
275
278
|
}
|
|
279
|
+
return true;
|
|
276
280
|
}
|
|
277
281
|
async cancelSession(sessionId) {
|
|
278
282
|
await this.cancelMatching((_id, ownerSessionId) => ownerSessionId === sessionId);
|
package/dist/cjs/main.cjs
CHANGED
|
@@ -325,9 +325,10 @@ function unregisterPendingReservation(key, controller) {
|
|
|
325
325
|
}
|
|
326
326
|
function abortPendingReservation(key, reason) {
|
|
327
327
|
const pending = pendingReservationAborts.get(key);
|
|
328
|
-
if (!pending) return;
|
|
328
|
+
if (!pending) return false;
|
|
329
329
|
pendingReservationAborts.delete(key);
|
|
330
330
|
pending.controller.abort(new import_workspace_mount_hold_fence.WorkspaceMountHoldAbortedError(reason));
|
|
331
|
+
return true;
|
|
331
332
|
}
|
|
332
333
|
function abortPendingReservations(reason, sessionId) {
|
|
333
334
|
for (const [key, pending] of [...pendingReservationAborts]) {
|
|
@@ -3191,32 +3192,38 @@ async function reapCompletedCredentialBearingProcessGroup(runId, subprocess) {
|
|
|
3191
3192
|
);
|
|
3192
3193
|
}
|
|
3193
3194
|
}
|
|
3194
|
-
async function cancelProcessRun(runId) {
|
|
3195
|
+
async function cancelProcessRun(runId, scope) {
|
|
3196
|
+
const spawned = activeProcesses.get(runId);
|
|
3197
|
+
if (scope === "unstarted" && spawned) return { cancelled: false, message: `${spawned.command} already started`, outcome: "started" };
|
|
3198
|
+
if (!spawned) cancelledProcessRuns.add(runId);
|
|
3199
|
+
const revocation = workerCommandLauncher?.cancel(runId);
|
|
3195
3200
|
let resourceCancellationError;
|
|
3201
|
+
let launchRevoked = false;
|
|
3196
3202
|
try {
|
|
3197
|
-
await
|
|
3203
|
+
launchRevoked = await revocation ?? false;
|
|
3198
3204
|
} catch (error) {
|
|
3199
3205
|
resourceCancellationError = error instanceof Error ? error.message : String(error);
|
|
3200
3206
|
}
|
|
3201
|
-
const active = activeProcesses.get(runId);
|
|
3202
3207
|
let cancelled = resourceCancellationError === void 0;
|
|
3203
3208
|
let cancelMessage;
|
|
3204
|
-
|
|
3209
|
+
let outcome;
|
|
3210
|
+
if (spawned) {
|
|
3211
|
+
outcome = "stopped";
|
|
3205
3212
|
try {
|
|
3206
|
-
closeProcessStdin(
|
|
3207
|
-
await (0, import_process_tree.terminateProcessTree)(
|
|
3208
|
-
cancelMessage = `Stopped ${
|
|
3213
|
+
closeProcessStdin(spawned);
|
|
3214
|
+
await (0, import_process_tree.terminateProcessTree)(spawned.process);
|
|
3215
|
+
cancelMessage = `Stopped ${spawned.command}`;
|
|
3209
3216
|
} catch (error) {
|
|
3210
3217
|
cancelled = false;
|
|
3211
|
-
cancelMessage = `Failed to stop ${
|
|
3218
|
+
cancelMessage = `Failed to stop ${spawned.command}: ${error instanceof Error ? error.message : String(error)}`;
|
|
3212
3219
|
}
|
|
3213
3220
|
} else {
|
|
3214
|
-
|
|
3215
|
-
|
|
3221
|
+
const reservationAborted = abortPendingReservation(`run:${runId}`, "Cancellation queued before command start");
|
|
3222
|
+
outcome = launchRevoked || reservationAborted ? "unstarted" : "unknown";
|
|
3216
3223
|
cancelMessage = "Cancellation queued before command start";
|
|
3217
3224
|
}
|
|
3218
3225
|
if (resourceCancellationError) cancelMessage += `; job resource termination failed: ${resourceCancellationError}`;
|
|
3219
|
-
return { cancelled, message: cancelMessage };
|
|
3226
|
+
return { cancelled, message: cancelMessage, outcome };
|
|
3220
3227
|
}
|
|
3221
3228
|
function closeProcessStdin(active) {
|
|
3222
3229
|
if (!active?.stdin) {
|
|
@@ -6168,7 +6175,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6168
6175
|
const operationRequestId = "requestId" in message && typeof message.requestId === "string" && message.type !== "workspace_config" ? message.requestId : void 0;
|
|
6169
6176
|
if (message.type === "cancel") {
|
|
6170
6177
|
const admission = recoveryJournal.admit({ ...message, requestId: message.requestId });
|
|
6171
|
-
const cancelled = await cancelProcessRun(message.runId);
|
|
6178
|
+
const cancelled = await cancelProcessRun(message.runId, message.scope);
|
|
6172
6179
|
let admitted;
|
|
6173
6180
|
try {
|
|
6174
6181
|
admitted = await admission;
|
|
@@ -6194,7 +6201,8 @@ async function startWorker(options, projectRuntime = {
|
|
|
6194
6201
|
requestId: message.requestId,
|
|
6195
6202
|
runId: message.runId,
|
|
6196
6203
|
cancelled: cancelled.cancelled,
|
|
6197
|
-
message: cancelled.message
|
|
6204
|
+
message: cancelled.message,
|
|
6205
|
+
outcome: cancelled.outcome
|
|
6198
6206
|
})
|
|
6199
6207
|
);
|
|
6200
6208
|
return;
|
package/dist/cjs/package.json
CHANGED
|
@@ -216,7 +216,9 @@ class WorkerCommandLauncher {
|
|
|
216
216
|
if (!argv.length) throw new Error("Command argv must not be empty");
|
|
217
217
|
return [...prefix, ...argv];
|
|
218
218
|
},
|
|
219
|
-
cancel: () =>
|
|
219
|
+
cancel: async () => {
|
|
220
|
+
await this.cancel(request.id);
|
|
221
|
+
},
|
|
220
222
|
diagnose: async () => {
|
|
221
223
|
this.assertHealthy();
|
|
222
224
|
if (!this.child) return void 0;
|
|
@@ -232,9 +234,10 @@ class WorkerCommandLauncher {
|
|
|
232
234
|
release: () => this.releaseRecord(record)
|
|
233
235
|
};
|
|
234
236
|
}
|
|
237
|
+
/** Revoke a launch. Returns whether this launcher still held it (queued or acquired, not yet released). */
|
|
235
238
|
async cancel(id) {
|
|
236
239
|
const record = this.launches.get(id);
|
|
237
|
-
if (!record || record.released) return;
|
|
240
|
+
if (!record || record.released) return false;
|
|
238
241
|
record.revoked = true;
|
|
239
242
|
record.rejectAdmission(new CommandLaunchCancelledError());
|
|
240
243
|
if (this.child) {
|
|
@@ -244,6 +247,7 @@ class WorkerCommandLauncher {
|
|
|
244
247
|
throw this.fail(error instanceof Error ? error : new Error(String(error)));
|
|
245
248
|
}
|
|
246
249
|
}
|
|
250
|
+
return true;
|
|
247
251
|
}
|
|
248
252
|
async cancelSession(sessionId) {
|
|
249
253
|
await this.cancelMatching((_id, ownerSessionId) => ownerSessionId === sessionId);
|
package/dist/mjs/main.mjs
CHANGED
|
@@ -346,9 +346,10 @@ function unregisterPendingReservation(key, controller) {
|
|
|
346
346
|
}
|
|
347
347
|
function abortPendingReservation(key, reason) {
|
|
348
348
|
const pending = pendingReservationAborts.get(key);
|
|
349
|
-
if (!pending) return;
|
|
349
|
+
if (!pending) return false;
|
|
350
350
|
pendingReservationAborts.delete(key);
|
|
351
351
|
pending.controller.abort(new WorkspaceMountHoldAbortedError(reason));
|
|
352
|
+
return true;
|
|
352
353
|
}
|
|
353
354
|
function abortPendingReservations(reason, sessionId) {
|
|
354
355
|
for (const [key, pending] of [...pendingReservationAborts]) {
|
|
@@ -3212,32 +3213,38 @@ async function reapCompletedCredentialBearingProcessGroup(runId, subprocess) {
|
|
|
3212
3213
|
);
|
|
3213
3214
|
}
|
|
3214
3215
|
}
|
|
3215
|
-
async function cancelProcessRun(runId) {
|
|
3216
|
+
async function cancelProcessRun(runId, scope) {
|
|
3217
|
+
const spawned = activeProcesses.get(runId);
|
|
3218
|
+
if (scope === "unstarted" && spawned) return { cancelled: false, message: `${spawned.command} already started`, outcome: "started" };
|
|
3219
|
+
if (!spawned) cancelledProcessRuns.add(runId);
|
|
3220
|
+
const revocation = workerCommandLauncher?.cancel(runId);
|
|
3216
3221
|
let resourceCancellationError;
|
|
3222
|
+
let launchRevoked = false;
|
|
3217
3223
|
try {
|
|
3218
|
-
await
|
|
3224
|
+
launchRevoked = await revocation ?? false;
|
|
3219
3225
|
} catch (error) {
|
|
3220
3226
|
resourceCancellationError = error instanceof Error ? error.message : String(error);
|
|
3221
3227
|
}
|
|
3222
|
-
const active = activeProcesses.get(runId);
|
|
3223
3228
|
let cancelled = resourceCancellationError === void 0;
|
|
3224
3229
|
let cancelMessage;
|
|
3225
|
-
|
|
3230
|
+
let outcome;
|
|
3231
|
+
if (spawned) {
|
|
3232
|
+
outcome = "stopped";
|
|
3226
3233
|
try {
|
|
3227
|
-
closeProcessStdin(
|
|
3228
|
-
await terminateProcessTree(
|
|
3229
|
-
cancelMessage = `Stopped ${
|
|
3234
|
+
closeProcessStdin(spawned);
|
|
3235
|
+
await terminateProcessTree(spawned.process);
|
|
3236
|
+
cancelMessage = `Stopped ${spawned.command}`;
|
|
3230
3237
|
} catch (error) {
|
|
3231
3238
|
cancelled = false;
|
|
3232
|
-
cancelMessage = `Failed to stop ${
|
|
3239
|
+
cancelMessage = `Failed to stop ${spawned.command}: ${error instanceof Error ? error.message : String(error)}`;
|
|
3233
3240
|
}
|
|
3234
3241
|
} else {
|
|
3235
|
-
|
|
3236
|
-
|
|
3242
|
+
const reservationAborted = abortPendingReservation(`run:${runId}`, "Cancellation queued before command start");
|
|
3243
|
+
outcome = launchRevoked || reservationAborted ? "unstarted" : "unknown";
|
|
3237
3244
|
cancelMessage = "Cancellation queued before command start";
|
|
3238
3245
|
}
|
|
3239
3246
|
if (resourceCancellationError) cancelMessage += `; job resource termination failed: ${resourceCancellationError}`;
|
|
3240
|
-
return { cancelled, message: cancelMessage };
|
|
3247
|
+
return { cancelled, message: cancelMessage, outcome };
|
|
3241
3248
|
}
|
|
3242
3249
|
function closeProcessStdin(active) {
|
|
3243
3250
|
if (!active?.stdin) {
|
|
@@ -6189,7 +6196,7 @@ async function startWorker(options, projectRuntime = {
|
|
|
6189
6196
|
const operationRequestId = "requestId" in message && typeof message.requestId === "string" && message.type !== "workspace_config" ? message.requestId : void 0;
|
|
6190
6197
|
if (message.type === "cancel") {
|
|
6191
6198
|
const admission = recoveryJournal.admit({ ...message, requestId: message.requestId });
|
|
6192
|
-
const cancelled = await cancelProcessRun(message.runId);
|
|
6199
|
+
const cancelled = await cancelProcessRun(message.runId, message.scope);
|
|
6193
6200
|
let admitted;
|
|
6194
6201
|
try {
|
|
6195
6202
|
admitted = await admission;
|
|
@@ -6215,7 +6222,8 @@ async function startWorker(options, projectRuntime = {
|
|
|
6215
6222
|
requestId: message.requestId,
|
|
6216
6223
|
runId: message.runId,
|
|
6217
6224
|
cancelled: cancelled.cancelled,
|
|
6218
|
-
message: cancelled.message
|
|
6225
|
+
message: cancelled.message,
|
|
6226
|
+
outcome: cancelled.outcome
|
|
6219
6227
|
})
|
|
6220
6228
|
);
|
|
6221
6229
|
return;
|
package/dist/mjs/package.json
CHANGED
|
@@ -86,7 +86,8 @@ export declare class WorkerCommandLauncher {
|
|
|
86
86
|
capacityReport(): CommandLaunchCapacityReport;
|
|
87
87
|
private notifyCapacityChange;
|
|
88
88
|
acquire(request: CommandLaunchRequest): Promise<CommandLaunchLease>;
|
|
89
|
-
|
|
89
|
+
/** Revoke a launch. Returns whether this launcher still held it (queued or acquired, not yet released). */
|
|
90
|
+
cancel(id: string): Promise<boolean>;
|
|
90
91
|
cancelSession(sessionId: string): Promise<void>;
|
|
91
92
|
cancelMatching(matches: (id: string, sessionId?: string) => boolean): Promise<void>;
|
|
92
93
|
close(): Promise<void>;
|
package/dist/types/main.d.ts
CHANGED
|
@@ -141,6 +141,8 @@ export type WorkerSessionTarget = {
|
|
|
141
141
|
ownerUserId: string;
|
|
142
142
|
rootProfile: "visible_projects" | "canonical_sync";
|
|
143
143
|
};
|
|
144
|
+
type WorkerCancelScope = "unstarted";
|
|
145
|
+
type WorkerCancelOutcome = "unstarted" | "started" | "stopped" | "unknown";
|
|
144
146
|
type WorkerClientMessage = WorkerRecoveryClientMessage | {
|
|
145
147
|
type: "capacity_report";
|
|
146
148
|
capacity: import("./command-launcher").CommandLaunchCapacityReport & {
|
|
@@ -262,6 +264,8 @@ type WorkerClientMessage = WorkerRecoveryClientMessage | {
|
|
|
262
264
|
runId: string;
|
|
263
265
|
cancelled: boolean;
|
|
264
266
|
message?: string;
|
|
267
|
+
/** `unstarted`: a launch that had not spawned was revoked; `started`: a spawned process was left alone; `stopped`; `unknown`: no record, cancellation recorded. */
|
|
268
|
+
outcome?: WorkerCancelOutcome;
|
|
265
269
|
} | {
|
|
266
270
|
type: "pong";
|
|
267
271
|
} | {
|
|
@@ -533,6 +537,8 @@ type WorkerServerMessage = WorkerRecoveryServerMessage | {
|
|
|
533
537
|
type: "cancel";
|
|
534
538
|
requestId: string;
|
|
535
539
|
runId: string;
|
|
540
|
+
/** `unstarted`: the server's start-deadline abandonment; never touch a spawned process. */
|
|
541
|
+
scope?: WorkerCancelScope;
|
|
536
542
|
} | {
|
|
537
543
|
type: "ping";
|
|
538
544
|
};
|