@ricsam/r5d-worker 0.0.178 → 0.0.180
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/internal-r5dctl.cjs +1 -1
- package/dist/mjs/main.mjs +2 -2
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/personal/client.mjs +50 -14
- package/dist/mjs/personal/runtime.mjs +12 -4
- package/dist/mjs/runtime/workspace/authority.mjs +27 -0
- package/dist/types/personal/client.d.ts +1 -0
- package/dist/types/runtime/workspace/authority.d.ts +7 -0
- package/package.json +2 -2
package/dist/cjs/package.json
CHANGED
|
@@ -20605,7 +20605,7 @@ function resolveEntrypointPath(entrypoint) {
|
|
|
20605
20605
|
}
|
|
20606
20606
|
}
|
|
20607
20607
|
function getR5dctlVersion() {
|
|
20608
|
-
if (true) return "0.0.
|
|
20608
|
+
if (true) return "0.0.180";
|
|
20609
20609
|
const entrypoint = process.argv[1] ? resolveEntrypointPath(process.argv[1]) : null;
|
|
20610
20610
|
let current = entrypoint ? import_node_path2.default.dirname(entrypoint) : process.cwd();
|
|
20611
20611
|
for (let index = 0; index < 12; index += 1) {
|
package/dist/mjs/main.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { startManagerRpc } from "./runtime/releases/rpc-main.mjs";
|
|
|
7
7
|
import { ManagerRpcConfig } from "./runtime/releases/rpc-protocol.mjs";
|
|
8
8
|
const args = process.argv.slice(2);
|
|
9
9
|
if (args.includes("--version")) {
|
|
10
|
-
console.log(`r5d-worker ${true ? "0.0.
|
|
10
|
+
console.log(`r5d-worker ${true ? "0.0.180" : "development"}`);
|
|
11
11
|
} else if (!args.length || args.includes("--help")) {
|
|
12
12
|
console.log(
|
|
13
13
|
"Usage: r5d-worker start --label <label> [--root <dir>] [--base-url <url>] [--token <worker-token>] [--initial-sync merge|reset]\n r5d-worker executor /absolute/private/config.json\n r5d-worker manager /absolute/private/config.json\n\nRun independently supervised durable runtime services. Provision configuration with r5dinfra."
|
|
@@ -15,7 +15,7 @@ if (args.includes("--version")) {
|
|
|
15
15
|
} else if (args[0] === "start") {
|
|
16
16
|
const runtime = await startPersonalWorker(
|
|
17
17
|
parsePersonalWorkerOptions(args.slice(1)),
|
|
18
|
-
true ? "0.0.
|
|
18
|
+
true ? "0.0.180" : "development"
|
|
19
19
|
);
|
|
20
20
|
console.log(`Worker connected: ${runtime.resourceId}`);
|
|
21
21
|
let closing = false;
|
package/dist/mjs/package.json
CHANGED
|
@@ -25,6 +25,8 @@ class PersonalResponseError extends Error {
|
|
|
25
25
|
rejectedBeforeAdmission;
|
|
26
26
|
}
|
|
27
27
|
const storageErrorCodes = /* @__PURE__ */ new Set(["invalid_input", "conflict", "not_found", "busy", "stale_fence", "unknown", "unsafe_path", "git_failed"]);
|
|
28
|
+
const POLL_TIMEOUT_MS = 1e4;
|
|
29
|
+
const isStaleFence = (error) => error?.code === "stale_fence";
|
|
28
30
|
function parsePersonalWorkerOptions(args, environment = process.env) {
|
|
29
31
|
const values = {};
|
|
30
32
|
const allowed = /* @__PURE__ */ new Set(["label", "base-url", "token", "api-key", "config", "root", "initial-sync"]);
|
|
@@ -90,13 +92,13 @@ async function startPersonalWorker(options, version) {
|
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
const endpoint = `/api/personal/resources/worker/${encodeURIComponent(identity.resourceId)}`;
|
|
93
|
-
async function request(pathname, body) {
|
|
95
|
+
async function request(pathname, body, timeoutMs = 3e4) {
|
|
94
96
|
const response = await fetch(options.baseUrl + pathname, {
|
|
95
97
|
method: "POST",
|
|
96
98
|
headers: { authorization: `Bearer ${options.credential}`, "content-type": "application/json" },
|
|
97
99
|
redirect: "error",
|
|
98
100
|
body: JSON.stringify(body),
|
|
99
|
-
signal: AbortSignal.timeout(
|
|
101
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
100
102
|
});
|
|
101
103
|
const data = await response.json();
|
|
102
104
|
if (!response.ok) {
|
|
@@ -105,7 +107,7 @@ async function startPersonalWorker(options, version) {
|
|
|
105
107
|
return data;
|
|
106
108
|
}
|
|
107
109
|
const cli = await resolvePersonalCliEntrypoint(options.cliEntrypoint, version);
|
|
108
|
-
|
|
110
|
+
let bootId = randomUUID();
|
|
109
111
|
const metadata = () => ({
|
|
110
112
|
version,
|
|
111
113
|
bootId,
|
|
@@ -203,19 +205,47 @@ async function startPersonalWorker(options, version) {
|
|
|
203
205
|
const terminalStream = startTerminalStream({ url: streamUrl.toString(), credential: options.credential, version, runtime });
|
|
204
206
|
const loop = (async () => {
|
|
205
207
|
while (!stopped) {
|
|
208
|
+
let response;
|
|
206
209
|
try {
|
|
207
210
|
const sendMetadata = Date.now() - metadataAt > 6e4;
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
...sendMetadata ? { metadata: metadata() } : {}
|
|
211
|
-
|
|
211
|
+
response = await request(
|
|
212
|
+
`${endpoint}/poll`,
|
|
213
|
+
{ instanceId: identity.instanceId, ...sendMetadata ? { metadata: metadata() } : {} },
|
|
214
|
+
options.pollTimeoutMs ?? POLL_TIMEOUT_MS
|
|
215
|
+
);
|
|
212
216
|
if (sendMetadata) metadataAt = Date.now();
|
|
213
|
-
await runtime.renew({ ...grant, ...response });
|
|
214
|
-
scheduler.accept(response.actions ?? []);
|
|
215
217
|
} catch (error) {
|
|
216
218
|
process.stderr.write(`[r5d-worker] ${safeError(error)}; reconnecting without replacing the local keeper
|
|
217
219
|
`);
|
|
220
|
+
await Bun.sleep(250);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
await runtime.renew({ ...grant, ...response });
|
|
225
|
+
} catch (error) {
|
|
226
|
+
if (!isStaleFence(error)) {
|
|
227
|
+
process.stderr.write(`[r5d-worker] ${safeError(error)}; reconnecting without replacing the local keeper
|
|
228
|
+
`);
|
|
229
|
+
await Bun.sleep(250);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
bootId = randomUUID();
|
|
234
|
+
const rotated = PersonalWorkerGrant.parse(
|
|
235
|
+
await request("/api/personal/resources/register", { kind: "worker", ...identity, label: options.label, metadata: metadata() })
|
|
236
|
+
);
|
|
237
|
+
metadataAt = Date.now();
|
|
238
|
+
await runtime.renew(rotated);
|
|
239
|
+
process.stderr.write(`[r5d-worker] executor authority lapsed locally; re-registered with a new boot id (epoch ${rotated.workerFence.epoch})
|
|
240
|
+
`);
|
|
241
|
+
} catch (rotation) {
|
|
242
|
+
process.stderr.write(`[r5d-worker] ${safeError(rotation)}; authority rotation pending
|
|
243
|
+
`);
|
|
244
|
+
await Bun.sleep(250);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
218
247
|
}
|
|
248
|
+
scheduler.accept(response.actions ?? []);
|
|
219
249
|
await Bun.sleep(250);
|
|
220
250
|
}
|
|
221
251
|
})();
|
|
@@ -224,11 +254,17 @@ async function startPersonalWorker(options, version) {
|
|
|
224
254
|
root,
|
|
225
255
|
async close() {
|
|
226
256
|
stopped = true;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
257
|
+
try {
|
|
258
|
+
await terminalStream.close();
|
|
259
|
+
await loop;
|
|
260
|
+
await scheduler.drain();
|
|
261
|
+
} finally {
|
|
262
|
+
try {
|
|
263
|
+
await runtime.close();
|
|
264
|
+
} finally {
|
|
265
|
+
ledger.close();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
232
268
|
}
|
|
233
269
|
};
|
|
234
270
|
}
|
|
@@ -509,10 +509,18 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
509
509
|
async close() {
|
|
510
510
|
closing = true;
|
|
511
511
|
clearInterval(publicationTimer);
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
512
|
+
try {
|
|
513
|
+
await publication;
|
|
514
|
+
} catch (error) {
|
|
515
|
+
process.stderr.write(`[r5d-worker] final publication cycle failed: ${error instanceof WorkspaceError ? error.code : "publication_failed"}
|
|
516
|
+
`);
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
await tcp.close();
|
|
520
|
+
await authority.close();
|
|
521
|
+
} finally {
|
|
522
|
+
await daemon.close();
|
|
523
|
+
}
|
|
516
524
|
}
|
|
517
525
|
};
|
|
518
526
|
} catch (error) {
|
|
@@ -2031,6 +2031,7 @@ class WorkspaceAuthority {
|
|
|
2031
2031
|
await Promise.allSettled([...this.actions]);
|
|
2032
2032
|
await Promise.allSettled([...this.queues.values()]);
|
|
2033
2033
|
for (const b of this.benches.values()) await this.refresh(b);
|
|
2034
|
+
await this.drainLiveRuns();
|
|
2034
2035
|
if ([...this.benches.values()].some(
|
|
2035
2036
|
(b) => b.state.blocked || Object.values(b.state.runs).some((r) => !["completed", "cancelled", "rejected_capacity"].includes(r.state))
|
|
2036
2037
|
))
|
|
@@ -2043,6 +2044,32 @@ class WorkspaceAuthority {
|
|
|
2043
2044
|
await fs.rm(path.join(this.config.root, ".sync-lock"), { recursive: true });
|
|
2044
2045
|
this.closed = true;
|
|
2045
2046
|
}
|
|
2047
|
+
/** A stop ends what the keeper tracks. Live runs (an open shell, an agent
|
|
2048
|
+
* command) are cancelled through the executor and waited for, the way a
|
|
2049
|
+
* supervisor drain would, so that an orderly stop leaves cancelled receipts
|
|
2050
|
+
* behind rather than a lock nobody can release: the process is going away
|
|
2051
|
+
* either way. Outcomes that stay unknown after the drain still retain the
|
|
2052
|
+
* lock for review. */
|
|
2053
|
+
async drainLiveRuns() {
|
|
2054
|
+
const live = (b) => Object.entries(b.state.runs).filter(([, run]) => !["completed", "cancelled", "rejected_capacity", "unknown"].includes(run.state));
|
|
2055
|
+
if (![...this.benches.values()].some((b) => live(b).length)) return;
|
|
2056
|
+
for (const b of this.benches.values()) {
|
|
2057
|
+
for (const [operationId, run] of live(b)) {
|
|
2058
|
+
try {
|
|
2059
|
+
const identity = { userId: b.config.userId, sessionId: run.sessionId ?? b.config.sessionId };
|
|
2060
|
+
const route = await this.route(b, identity);
|
|
2061
|
+
await route.client.cancel({ ...identity, operationId, fence: route.workerFence }, `drain-${this.lockId.slice(0, 8)}-${sha256(operationId).slice(0, 16)}`);
|
|
2062
|
+
} catch {
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
const deadline = Date.now() + 15e3;
|
|
2067
|
+
for (; ; ) {
|
|
2068
|
+
for (const b of this.benches.values()) await this.refresh(b);
|
|
2069
|
+
if (![...this.benches.values()].some((b) => live(b).length) || Date.now() >= deadline) return;
|
|
2070
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2046
2073
|
}
|
|
2047
2074
|
export {
|
|
2048
2075
|
WorkspaceAuthority
|
|
@@ -11,6 +11,7 @@ export type PersonalWorkerOptions = {
|
|
|
11
11
|
root: string;
|
|
12
12
|
cliEntrypoint?: string;
|
|
13
13
|
initialSync?: PersonalWorkerInitialSync;
|
|
14
|
+
pollTimeoutMs?: number;
|
|
14
15
|
};
|
|
15
16
|
export declare function parsePersonalWorkerOptions(args: string[], environment?: Record<string, string | undefined>): PersonalWorkerOptions;
|
|
16
17
|
/** HTTP reconnects never replace the local source/PTY keeper. */
|
|
@@ -500,4 +500,11 @@ export declare class WorkspaceAuthority {
|
|
|
500
500
|
cancel(identity: WorkspaceIdentity, run: RunIdentity, actionId: string): Promise<import("..").ActionReceipt>;
|
|
501
501
|
close(): Promise<void>;
|
|
502
502
|
private finishClose;
|
|
503
|
+
/** A stop ends what the keeper tracks. Live runs (an open shell, an agent
|
|
504
|
+
* command) are cancelled through the executor and waited for, the way a
|
|
505
|
+
* supervisor drain would, so that an orderly stop leaves cancelled receipts
|
|
506
|
+
* behind rather than a lock nobody can release: the process is going away
|
|
507
|
+
* either way. Outcomes that stay unknown after the drain still retain the
|
|
508
|
+
* lock for review. */
|
|
509
|
+
private drainLiveRuns;
|
|
503
510
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/r5d-worker",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.180",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/mjs/main.mjs",
|
|
6
6
|
"module": "./dist/mjs/main.mjs",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"r5d-worker": "dist/mjs/main.mjs"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@ricsam/r5d-api": "^0.0.
|
|
24
|
+
"@ricsam/r5d-api": "^0.0.180",
|
|
25
25
|
"node-pty": "1.1.0",
|
|
26
26
|
"zod": "^4.1.13",
|
|
27
27
|
"picomatch": "^4.0.3"
|