llm-relay 0.85.0 → 0.86.0
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 +39 -62
- package/THIRD_PARTY_NOTICES.md +5 -4
- package/dist/accounting-state.js.map +1 -1
- package/dist/accounting-store-schema.js.map +1 -1
- package/dist/accounting-store.js.map +1 -1
- package/dist/accounting.js.map +1 -1
- package/dist/authEnv.js.map +1 -1
- package/dist/availability-snapshot.js.map +1 -1
- package/dist/availability.js.map +1 -1
- package/dist/backend.js.map +1 -1
- package/dist/benchmarks.js.map +1 -1
- package/dist/breaker-persistence.js.map +1 -1
- package/dist/candidate-runner.d.ts +6 -12
- package/dist/candidate-runner.js +16 -4
- package/dist/candidate-runner.js.map +1 -1
- package/dist/candidates.js.map +1 -1
- package/dist/catalog.js.map +1 -1
- package/dist/circuit-breaker.d.ts +14 -17
- package/dist/circuit-breaker.js +46 -6
- package/dist/circuit-breaker.js.map +1 -1
- package/dist/claude-hook.js.map +1 -1
- package/dist/cli.d.ts +4 -1
- package/dist/cli.js +127 -12
- package/dist/cli.js.map +1 -1
- package/dist/config/routing-parser.d.ts +3 -13
- package/dist/config/routing-parser.js +16 -10
- package/dist/config/routing-parser.js.map +1 -1
- package/dist/config-reload.d.ts +34 -0
- package/dist/config-reload.js +180 -0
- package/dist/config-reload.js.map +1 -0
- package/dist/config-types.d.ts +26 -65
- package/dist/config-types.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/configured-lane-execution-launcher.d.ts +36 -0
- package/dist/configured-lane-execution-launcher.js +133 -0
- package/dist/configured-lane-execution-launcher.js.map +1 -0
- package/dist/context-limits.js.map +1 -1
- package/dist/control-authorization.js.map +1 -1
- package/dist/cooldown-clear.js.map +1 -1
- package/dist/credential-fleet.js.map +1 -1
- package/dist/credential-select.js.map +1 -1
- package/dist/daemon-dispatch-view.d.ts +21 -0
- package/dist/daemon-dispatch-view.js +16 -0
- package/dist/daemon-dispatch-view.js.map +1 -0
- package/dist/dashboard/.vite/dashboard-bundle-graph.json +2 -1
- package/dist/dashboard/.vite/manifest.json +2 -2
- package/dist/dashboard/assets/index-BoSmrF3q.js +123 -0
- package/dist/dashboard/assets/index-DKPeE07T.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard-auth.js.map +1 -1
- package/dist/dashboard-contract.d.ts +2 -2
- package/dist/dashboard-routes.js.map +1 -1
- package/dist/dashboard-snapshot.js.map +1 -1
- package/dist/dashboard-static.js.map +1 -1
- package/dist/delegate-gate/cli.js.map +1 -1
- package/dist/dispatch-exhaustion-persistence.d.ts +1 -0
- package/dist/dispatch-exhaustion-persistence.js +7 -2
- package/dist/dispatch-exhaustion-persistence.js.map +1 -1
- package/dist/dispatch-lane-stats.d.ts +10 -17
- package/dist/dispatch-lane-stats.js +0 -5
- package/dist/dispatch-lane-stats.js.map +1 -1
- package/dist/dispatch.d.ts +32 -78
- package/dist/dispatch.js +51 -54
- package/dist/dispatch.js.map +1 -1
- package/dist/documents.js.map +1 -1
- package/dist/dotenv.js.map +1 -1
- package/dist/dynamic-pools.js.map +1 -1
- package/dist/executable-lookup.d.ts +2 -0
- package/dist/executable-lookup.js +7 -4
- package/dist/executable-lookup.js.map +1 -1
- package/dist/host-routing.js.map +1 -1
- package/dist/installed-hosts.js.map +1 -1
- package/dist/json-shape.js.map +1 -1
- package/dist/key-checker.js.map +1 -1
- package/dist/key-import.js.map +1 -1
- package/dist/keys-cli.js.map +1 -1
- package/dist/keystore.js.map +1 -1
- package/dist/lane-activity.d.ts +8 -0
- package/dist/lane-activity.js +27 -2
- package/dist/lane-activity.js.map +1 -1
- package/dist/lane-affinity.d.ts +4 -4
- package/dist/lane-affinity.js.map +1 -1
- package/dist/lane-cadence.js.map +1 -1
- package/dist/lane-execution-broker.d.ts +124 -0
- package/dist/lane-execution-broker.js +479 -0
- package/dist/lane-execution-broker.js.map +1 -0
- package/dist/lane-launch-env.d.ts +14 -0
- package/dist/lane-launch-env.js +52 -0
- package/dist/lane-launch-env.js.map +1 -0
- package/dist/lane-manifest.js.map +1 -1
- package/dist/lane-probe.js.map +1 -1
- package/dist/lane-quota-probe.js.map +1 -1
- package/dist/log.js.map +1 -1
- package/dist/mcp/job-archive.d.ts +12 -9
- package/dist/mcp/job-archive.js +44 -37
- package/dist/mcp/job-archive.js.map +1 -1
- package/dist/mcp/job-journal.d.ts +42 -2
- package/dist/mcp/job-journal.js +212 -32
- package/dist/mcp/job-journal.js.map +1 -1
- package/dist/mcp/lane-execution-client.d.ts +26 -0
- package/dist/mcp/lane-execution-client.js +117 -0
- package/dist/mcp/lane-execution-client.js.map +1 -0
- package/dist/mcp/lane-runner.d.ts +140 -25
- package/dist/mcp/lane-runner.js +361 -49
- package/dist/mcp/lane-runner.js.map +1 -1
- package/dist/mcp/persistence-lock.d.ts +13 -0
- package/dist/mcp/persistence-lock.js +4 -0
- package/dist/mcp/persistence-lock.js.map +1 -0
- package/dist/mcp/process-cpu.d.ts +18 -0
- package/dist/mcp/process-cpu.js +133 -0
- package/dist/mcp/process-cpu.js.map +1 -0
- package/dist/mcp/readonly-boundary.js.map +1 -1
- package/dist/mcp/server.d.ts +97 -3
- package/dist/mcp/server.js +547 -61
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tree-delta.js.map +1 -1
- package/dist/mcp/windows-npm-shim.d.ts +32 -0
- package/dist/mcp/windows-npm-shim.js +92 -0
- package/dist/mcp/windows-npm-shim.js.map +1 -0
- package/dist/metadata.js.map +1 -1
- package/dist/openai-dialect.js.map +1 -1
- package/dist/openai-request.js.map +1 -1
- package/dist/os-keyring.js.map +1 -1
- package/dist/ping/cadence.js.map +1 -1
- package/dist/ping/metrics.js.map +1 -1
- package/dist/ping/ping.js.map +1 -1
- package/dist/ping/probe-cache.js.map +1 -1
- package/dist/ping/quota.js.map +1 -1
- package/dist/ping/runtime-telemetry.js.map +1 -1
- package/dist/pool-health.js.map +1 -1
- package/dist/process-safety-net.js +18 -10
- package/dist/process-safety-net.js.map +1 -1
- package/dist/quota-observation.js.map +1 -1
- package/dist/rate-limits.js.map +1 -1
- package/dist/refusal-interpretation.js.map +1 -1
- package/dist/registry.js.map +1 -1
- package/dist/request-log.js.map +1 -1
- package/dist/reshaper.js.map +1 -1
- package/dist/resolved-attempt.js.map +1 -1
- package/dist/responses-request.js.map +1 -1
- package/dist/routes/admin.d.ts +8 -0
- package/dist/routes/admin.js +54 -8
- package/dist/routes/admin.js.map +1 -1
- package/dist/routes/messages.js +10 -10
- package/dist/routes/messages.js.map +1 -1
- package/dist/routes/openai-front.js +4 -4
- package/dist/routes/openai-front.js.map +1 -1
- package/dist/secret-file-acl.js.map +1 -1
- package/dist/self-update.js.map +1 -1
- package/dist/server.d.ts +19 -0
- package/dist/server.js +70 -4
- package/dist/server.js.map +1 -1
- package/dist/session-pin.js.map +1 -1
- package/dist/setup-claude.d.ts +6 -1
- package/dist/setup-claude.js +3 -3
- package/dist/setup-claude.js.map +1 -1
- package/dist/spend-headroom.js.map +1 -1
- package/dist/sse.js.map +1 -1
- package/dist/state-paths.js.map +1 -1
- package/dist/storage/file-lock.d.ts +26 -0
- package/dist/storage/file-lock.js +127 -0
- package/dist/storage/file-lock.js.map +1 -0
- package/dist/storage/json-store.d.ts +33 -0
- package/dist/storage/json-store.js +68 -1
- package/dist/storage/json-store.js.map +1 -1
- package/dist/stream-commit.js.map +1 -1
- package/dist/stream-pipeline.js.map +1 -1
- package/dist/target-facts.js.map +1 -1
- package/dist/telemetry.d.ts +3 -1
- package/dist/telemetry.js +2 -1
- package/dist/telemetry.js.map +1 -1
- package/dist/tier-data.js.map +1 -1
- package/dist/tool-dialects.js.map +1 -1
- package/dist/usage-observer.js.map +1 -1
- package/dist/winenv.js.map +1 -1
- package/docs/README.md +4 -2
- package/docs/dashboard-bundle-inventory.json +5 -4
- package/package.json +15 -14
- package/scripts/install-skill.mjs +4 -2
- package/skills/llm-relay/SKILL.md +10 -8
- package/skills/llm-relay/references/dispatch-lanes.md +18 -1
- package/dist/dashboard/assets/index-C-I6hPeY.js +0 -403
- package/dist/dashboard/assets/index-CrSNlLMD.css +0 -1
package/dist/mcp/lane-runner.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { exec, execFile, execSync } from "node:child_process";
|
|
1
|
+
import { exec, execFile, execSync, spawn } from "node:child_process";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
2
3
|
import { existsSync, statSync } from "node:fs";
|
|
3
4
|
import { resolve as resolvePath } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import { nullJobJournal } from "./job-journal.js";
|
|
6
|
-
import {
|
|
5
|
+
import { resolveWindowsNpmShim } from "./windows-npm-shim.js";
|
|
6
|
+
import { nullJobJournal, } from "./job-journal.js";
|
|
7
|
+
import { nullJobArchive } from "./job-archive.js";
|
|
7
8
|
import { classifyLaneProbeOutput } from "../lane-quota-probe.js";
|
|
8
9
|
import { laneOfRung } from "../lane-manifest.js";
|
|
9
10
|
export const DEPTH_ENV = "LLM_RELAY_DISPATCH_DEPTH";
|
|
@@ -227,12 +228,120 @@ function observeOutput(child, onOutput) {
|
|
|
227
228
|
watch("stdout");
|
|
228
229
|
watch("stderr");
|
|
229
230
|
}
|
|
231
|
+
function spawnPosixLane(processApi, command, args, opts) {
|
|
232
|
+
const spawnProcess = processApi.spawn;
|
|
233
|
+
if (!spawnProcess) {
|
|
234
|
+
return {
|
|
235
|
+
result: Promise.resolve({
|
|
236
|
+
code: null,
|
|
237
|
+
stdout: "",
|
|
238
|
+
stderr: "POSIX lane spawning requires the injected spawn boundary",
|
|
239
|
+
timedOut: false,
|
|
240
|
+
}),
|
|
241
|
+
kill: () => { },
|
|
242
|
+
pids: () => [],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
let child;
|
|
246
|
+
let timedOut = false;
|
|
247
|
+
let bufferExceeded = null;
|
|
248
|
+
let timeoutTimer;
|
|
249
|
+
const stdoutChunks = [];
|
|
250
|
+
const stderrChunks = [];
|
|
251
|
+
let stdoutBytes = 0;
|
|
252
|
+
let stderrBytes = 0;
|
|
253
|
+
const killOwned = () => {
|
|
254
|
+
if (child?.pid)
|
|
255
|
+
terminateProcessTree(child.pid, processApi.platform);
|
|
256
|
+
child?.kill();
|
|
257
|
+
};
|
|
258
|
+
const clearTimer = () => {
|
|
259
|
+
if (timeoutTimer !== undefined)
|
|
260
|
+
clearTimeout(timeoutTimer);
|
|
261
|
+
timeoutTimer = undefined;
|
|
262
|
+
};
|
|
263
|
+
const append = (stream, chunk) => {
|
|
264
|
+
const data = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
265
|
+
const isStdout = stream === "stdout";
|
|
266
|
+
const used = isStdout ? stdoutBytes : stderrBytes;
|
|
267
|
+
const remaining = Math.max(0, MAX_OUTPUT_BYTES - used);
|
|
268
|
+
if (remaining > 0) {
|
|
269
|
+
const kept = data.length <= remaining ? data : data.subarray(0, remaining);
|
|
270
|
+
(isStdout ? stdoutChunks : stderrChunks).push(kept);
|
|
271
|
+
if (isStdout)
|
|
272
|
+
stdoutBytes += kept.length;
|
|
273
|
+
else
|
|
274
|
+
stderrBytes += kept.length;
|
|
275
|
+
}
|
|
276
|
+
if (data.length > remaining && bufferExceeded === null) {
|
|
277
|
+
bufferExceeded = stream;
|
|
278
|
+
killOwned();
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const result = new Promise((resolve) => {
|
|
282
|
+
let settled = false;
|
|
283
|
+
const settle = (code, error) => {
|
|
284
|
+
if (settled)
|
|
285
|
+
return;
|
|
286
|
+
settled = true;
|
|
287
|
+
clearTimer();
|
|
288
|
+
const stdout = Buffer.concat(stdoutChunks).toString("utf8");
|
|
289
|
+
let stderr = Buffer.concat(stderrChunks).toString("utf8");
|
|
290
|
+
if (!stderr && error)
|
|
291
|
+
stderr = error.message;
|
|
292
|
+
if (bufferExceeded !== null) {
|
|
293
|
+
const msg = `${bufferExceeded} exceeded ${MAX_OUTPUT_BYTES} byte lane output limit`;
|
|
294
|
+
stderr = stderr ? `${stderr}\n${msg}` : msg;
|
|
295
|
+
}
|
|
296
|
+
resolve({
|
|
297
|
+
code: bufferExceeded === null ? code : null,
|
|
298
|
+
stdout,
|
|
299
|
+
stderr,
|
|
300
|
+
timedOut,
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
try {
|
|
304
|
+
child = spawnProcess(command, args, {
|
|
305
|
+
cwd: opts.cwd,
|
|
306
|
+
env: opts.env,
|
|
307
|
+
windowsHide: true,
|
|
308
|
+
detached: true,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
catch (e) {
|
|
312
|
+
settle(null, e);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
child.stdout?.on("data", (chunk) => append("stdout", chunk));
|
|
316
|
+
child.stderr?.on("data", (chunk) => append("stderr", chunk));
|
|
317
|
+
observeOutput(child, opts.onOutput);
|
|
318
|
+
child.on("error", (err) => settle(null, err));
|
|
319
|
+
child.on("close", (code) => settle(code));
|
|
320
|
+
child.stdin?.end();
|
|
321
|
+
if (opts.timeoutMs > 0) {
|
|
322
|
+
timeoutTimer = setTimeout(() => {
|
|
323
|
+
timedOut = true;
|
|
324
|
+
killOwned();
|
|
325
|
+
}, opts.timeoutMs);
|
|
326
|
+
timeoutTimer.unref?.();
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
return {
|
|
330
|
+
result,
|
|
331
|
+
kill: () => {
|
|
332
|
+
clearTimer();
|
|
333
|
+
killOwned();
|
|
334
|
+
},
|
|
335
|
+
pids: () => (child?.pid === undefined ? [] : [child.pid]),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
230
338
|
const nodeProcessApi = {
|
|
231
339
|
platform: process.platform,
|
|
232
340
|
execFile: (command, args, opts, callback) => execFile(command, args, opts, callback),
|
|
233
341
|
exec: (command, opts, callback) => exec(command, opts, callback),
|
|
342
|
+
spawn: (command, args, opts) => spawn(command, args, opts),
|
|
234
343
|
};
|
|
235
|
-
export function createLaneSpawner(processApi, hostEnv = process.env) {
|
|
344
|
+
export function createLaneSpawner(processApi, hostEnv = process.env, resolveNpmShim = resolveWindowsNpmShim) {
|
|
236
345
|
return (command, args, opts) => {
|
|
237
346
|
if (hostEnv["VITEST"]) {
|
|
238
347
|
return {
|
|
@@ -245,6 +354,8 @@ export function createLaneSpawner(processApi, hostEnv = process.env) {
|
|
|
245
354
|
kill: () => { },
|
|
246
355
|
};
|
|
247
356
|
}
|
|
357
|
+
if (processApi.platform !== "win32")
|
|
358
|
+
return spawnPosixLane(processApi, command, args, opts);
|
|
248
359
|
const execOpts = {
|
|
249
360
|
encoding: "utf8",
|
|
250
361
|
maxBuffer: MAX_OUTPUT_BYTES,
|
|
@@ -269,15 +380,17 @@ export function createLaneSpawner(processApi, hostEnv = process.env) {
|
|
|
269
380
|
});
|
|
270
381
|
};
|
|
271
382
|
child = processApi.execFile(command, args, execOpts, (err, stdout, stderr) => {
|
|
272
|
-
if (!
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const line = `${quoteCmdArg(command)} ${args.map(quoteCmdArg).join(" ")}`;
|
|
278
|
-
const fallback = processApi.exec(line, execOpts, (err2, stdout2, stderr2) => {
|
|
279
|
-
settle(err2, stdout2 ?? "", stderr2 ?? "");
|
|
383
|
+
if (err && processApi.platform === "win32" && err.code === "ENOENT" && !killed) {
|
|
384
|
+
const resolved = resolveNpmShim(command, args, {
|
|
385
|
+
env: opts.env,
|
|
386
|
+
cwd: opts.cwd,
|
|
387
|
+
nodeExecutable: process.execPath,
|
|
280
388
|
});
|
|
389
|
+
if (!resolved.ok) {
|
|
390
|
+
settle(Object.assign(new Error(resolved.error), { code: "ENOENT" }), stdout ?? "", stderr ?? "");
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const fallback = processApi.execFile(resolved.command, resolved.args, execOpts, (err2, stdout2, stderr2) => settle(err2, stdout2 ?? "", stderr2 ?? ""));
|
|
281
394
|
child = fallback;
|
|
282
395
|
fallback.stdin?.end();
|
|
283
396
|
observeOutput(fallback, opts.onOutput);
|
|
@@ -292,9 +405,8 @@ export function createLaneSpawner(processApi, hostEnv = process.env) {
|
|
|
292
405
|
result,
|
|
293
406
|
kill: () => {
|
|
294
407
|
killed = true;
|
|
295
|
-
if (child?.pid)
|
|
408
|
+
if (child?.pid)
|
|
296
409
|
terminateProcessTree(child.pid, processApi.platform);
|
|
297
|
-
}
|
|
298
410
|
child?.kill();
|
|
299
411
|
},
|
|
300
412
|
pids: () => (child?.pid === undefined ? [] : [child.pid]),
|
|
@@ -355,17 +467,20 @@ function recentOf(job, elsewhere) {
|
|
|
355
467
|
};
|
|
356
468
|
}
|
|
357
469
|
function newestFirst(a, b) {
|
|
358
|
-
return b.startedAt - a.startedAt ||
|
|
359
|
-
}
|
|
360
|
-
let jobCounter = 0;
|
|
361
|
-
function nextJobId() {
|
|
362
|
-
jobCounter += 1;
|
|
363
|
-
return `job-${String(jobCounter).padStart(4, "0")}`;
|
|
470
|
+
return b.startedAt - a.startedAt || b.id.localeCompare(a.id);
|
|
364
471
|
}
|
|
365
|
-
export function
|
|
366
|
-
|
|
367
|
-
|
|
472
|
+
export function createJobIdFactory(entropy = randomBytes(16)) {
|
|
473
|
+
const bytes = Buffer.from(entropy);
|
|
474
|
+
if (bytes.length !== 16)
|
|
475
|
+
throw new Error("job id entropy must be exactly 16 bytes");
|
|
476
|
+
const instance = BigInt(`0x${bytes.toString("hex")}`).toString(10).padStart(39, "0");
|
|
477
|
+
let local = 0n;
|
|
478
|
+
return () => {
|
|
479
|
+
local += 1n;
|
|
480
|
+
return `job-9${instance}${local.toString(10).padStart(8, "0")}`;
|
|
481
|
+
};
|
|
368
482
|
}
|
|
483
|
+
const defaultJobIdFactory = createJobIdFactory();
|
|
369
484
|
export class LaneJobStore {
|
|
370
485
|
jobs = new Map();
|
|
371
486
|
kills = new Map();
|
|
@@ -381,22 +496,23 @@ export class LaneJobStore {
|
|
|
381
496
|
};
|
|
382
497
|
journal;
|
|
383
498
|
archive;
|
|
384
|
-
|
|
499
|
+
adoptedStartingTrees = new Map();
|
|
500
|
+
brokerOrphans = new Map();
|
|
501
|
+
brokerExecutions = new Map();
|
|
502
|
+
nextJobId;
|
|
503
|
+
constructor(journal = nullJobJournal, archive = nullJobArchive, nextJobId = defaultJobIdFactory) {
|
|
385
504
|
this.journal = journal;
|
|
386
505
|
this.archive = archive;
|
|
506
|
+
this.nextJobId = nextJobId;
|
|
387
507
|
this.restoreArchive();
|
|
388
508
|
this.adoptOrphans();
|
|
389
509
|
}
|
|
390
510
|
restoreArchive() {
|
|
391
|
-
const { jobs
|
|
392
|
-
seedJobCounter(lastSeq);
|
|
511
|
+
const { jobs } = this.archive.restore();
|
|
393
512
|
for (const row of jobs) {
|
|
394
513
|
if (this.jobs.has(row.id))
|
|
395
514
|
continue;
|
|
396
515
|
this.jobs.set(row.id, { ...row, restored: true });
|
|
397
|
-
const seq = jobSeqOf(row.id);
|
|
398
|
-
if (seq !== null)
|
|
399
|
-
seedJobCounter(seq);
|
|
400
516
|
}
|
|
401
517
|
}
|
|
402
518
|
flush() {
|
|
@@ -406,9 +522,12 @@ export class LaneJobStore {
|
|
|
406
522
|
for (const row of this.journal.orphans()) {
|
|
407
523
|
if (this.jobs.has(row.jobId))
|
|
408
524
|
continue;
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
525
|
+
if (row.brokerExecution !== undefined) {
|
|
526
|
+
const claimed = this.journal.claimBrokerOrphan?.(row.jobId);
|
|
527
|
+
if (claimed !== undefined)
|
|
528
|
+
this.brokerOrphans.set(row.jobId, claimed);
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
412
531
|
const killed = {
|
|
413
532
|
id: row.jobId,
|
|
414
533
|
status: "killed",
|
|
@@ -428,14 +547,16 @@ export class LaneJobStore {
|
|
|
428
547
|
process: { pids: [], survivors: [], terminated: false },
|
|
429
548
|
};
|
|
430
549
|
this.jobs.set(row.jobId, killed);
|
|
431
|
-
|
|
550
|
+
if (row.startingTree !== undefined) {
|
|
551
|
+
this.adoptedStartingTrees.set(row.jobId, { cwd: row.cwd, startingTree: row.startingTree });
|
|
552
|
+
}
|
|
553
|
+
if (this.archive.record(killed))
|
|
554
|
+
this.journal.clearOrphan?.(row);
|
|
432
555
|
}
|
|
433
556
|
}
|
|
434
557
|
create(laneId, spec, cwd, dispatchSource, label) {
|
|
435
|
-
seedJobCounter(this.archive.lastSeqOnDisk());
|
|
436
|
-
seedJobCounter(this.journal.maxSeqOnDisk());
|
|
437
558
|
const job = {
|
|
438
|
-
id: nextJobId(),
|
|
559
|
+
id: this.nextJobId(),
|
|
439
560
|
status: "running",
|
|
440
561
|
laneId,
|
|
441
562
|
spec,
|
|
@@ -452,9 +573,6 @@ export class LaneJobStore {
|
|
|
452
573
|
...(label ? { label } : {}),
|
|
453
574
|
};
|
|
454
575
|
this.jobs.set(job.id, job);
|
|
455
|
-
const seq = jobSeqOf(job.id);
|
|
456
|
-
if (seq !== null)
|
|
457
|
-
this.archive.noteSeq(seq);
|
|
458
576
|
this.journal.note({
|
|
459
577
|
jobId: job.id,
|
|
460
578
|
laneId: job.laneId,
|
|
@@ -469,6 +587,12 @@ export class LaneJobStore {
|
|
|
469
587
|
this.kills.set(id, handle.kill);
|
|
470
588
|
this.owned.set(id, handle);
|
|
471
589
|
}
|
|
590
|
+
runningPids(id) {
|
|
591
|
+
const job = this.jobs.get(id);
|
|
592
|
+
if (!job || job.status !== "running")
|
|
593
|
+
return [];
|
|
594
|
+
return readOwnedPids(this.owned.get(id));
|
|
595
|
+
}
|
|
472
596
|
registerKill(id, kill) {
|
|
473
597
|
this.kills.set(id, kill);
|
|
474
598
|
this.owned.set(id, { kill });
|
|
@@ -479,14 +603,25 @@ export class LaneJobStore {
|
|
|
479
603
|
const kill = this.kills.get(id);
|
|
480
604
|
this.kills.delete(id);
|
|
481
605
|
this.owned.delete(id);
|
|
482
|
-
this.
|
|
483
|
-
|
|
606
|
+
const brokerOwned = this.brokerExecutions.has(id);
|
|
607
|
+
this.brokerExecutions.delete(id);
|
|
608
|
+
if (!job) {
|
|
609
|
+
this.journal.clear(id);
|
|
484
610
|
return;
|
|
611
|
+
}
|
|
485
612
|
delete job.activity;
|
|
613
|
+
delete job.liveness;
|
|
486
614
|
const pids = readOwnedPids(handle);
|
|
615
|
+
if (brokerOwned) {
|
|
616
|
+
job.process = { pids: [], survivors: [], terminated: false };
|
|
617
|
+
if (this.archive.record(job))
|
|
618
|
+
this.journal.clear(id);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
487
621
|
if (kill === undefined) {
|
|
488
622
|
job.process = { pids, survivors: [], terminated: false };
|
|
489
|
-
this.archive.record(job)
|
|
623
|
+
if (this.archive.record(job))
|
|
624
|
+
this.journal.clear(id);
|
|
490
625
|
return;
|
|
491
626
|
}
|
|
492
627
|
try {
|
|
@@ -495,7 +630,8 @@ export class LaneJobStore {
|
|
|
495
630
|
catch {
|
|
496
631
|
}
|
|
497
632
|
job.process = { pids, survivors: pids.filter((pid) => this.isAlive(pid)), terminated: true };
|
|
498
|
-
this.archive.record(job)
|
|
633
|
+
if (this.archive.record(job))
|
|
634
|
+
this.journal.clear(id);
|
|
499
635
|
}
|
|
500
636
|
ownedProcesses() {
|
|
501
637
|
const rows = [];
|
|
@@ -524,7 +660,17 @@ export class LaneJobStore {
|
|
|
524
660
|
delete job.activity;
|
|
525
661
|
delete job.readOnly;
|
|
526
662
|
delete job.launch;
|
|
527
|
-
delete job.
|
|
663
|
+
delete job.liveness;
|
|
664
|
+
this.brokerExecutions.delete(id);
|
|
665
|
+
delete job.executionOwner;
|
|
666
|
+
this.journal.note({
|
|
667
|
+
jobId: job.id,
|
|
668
|
+
laneId: job.laneId,
|
|
669
|
+
...(job.spec === undefined ? {} : { spec: job.spec }),
|
|
670
|
+
cwd: job.cwd,
|
|
671
|
+
startedAt: job.startedAt,
|
|
672
|
+
...(job.label === undefined ? {} : { label: job.label }),
|
|
673
|
+
});
|
|
528
674
|
}
|
|
529
675
|
beginAttemptActivity(id, now) {
|
|
530
676
|
const job = this.jobs.get(id);
|
|
@@ -542,6 +688,167 @@ export class LaneJobStore {
|
|
|
542
688
|
else
|
|
543
689
|
activity.stderrBytes += bytes;
|
|
544
690
|
}
|
|
691
|
+
noteStartingTree(id, tree, scope) {
|
|
692
|
+
const job = this.jobs.get(id);
|
|
693
|
+
if (!job || job.status !== "running")
|
|
694
|
+
return;
|
|
695
|
+
this.journal.noteStartingTree?.(id, tree, scope);
|
|
696
|
+
}
|
|
697
|
+
takeBrokerOrphans() {
|
|
698
|
+
const rows = [...this.brokerOrphans.values()].map((row) => ({ ...row }));
|
|
699
|
+
this.brokerOrphans.clear();
|
|
700
|
+
return rows;
|
|
701
|
+
}
|
|
702
|
+
brokerExecution(id) {
|
|
703
|
+
return this.brokerExecutions.get(id);
|
|
704
|
+
}
|
|
705
|
+
noteExecutionOwner(id, owner) {
|
|
706
|
+
const job = this.jobs.get(id);
|
|
707
|
+
if (job === undefined || job.status !== "running")
|
|
708
|
+
return;
|
|
709
|
+
if (owner === undefined)
|
|
710
|
+
delete job.executionOwner;
|
|
711
|
+
else
|
|
712
|
+
job.executionOwner = owner;
|
|
713
|
+
}
|
|
714
|
+
clearBrokerExecution(id) {
|
|
715
|
+
const job = this.jobs.get(id);
|
|
716
|
+
if (job === undefined || job.status !== "running")
|
|
717
|
+
return;
|
|
718
|
+
this.brokerExecutions.delete(id);
|
|
719
|
+
if (job.executionOwner === "relay-daemon")
|
|
720
|
+
delete job.executionOwner;
|
|
721
|
+
this.journal.note({
|
|
722
|
+
jobId: job.id,
|
|
723
|
+
laneId: job.laneId,
|
|
724
|
+
...(job.spec === undefined ? {} : { spec: job.spec }),
|
|
725
|
+
cwd: job.cwd,
|
|
726
|
+
startedAt: job.startedAt,
|
|
727
|
+
...(job.label === undefined ? {} : { label: job.label }),
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
noteBrokerExecution(id, executionId) {
|
|
731
|
+
const job = this.jobs.get(id);
|
|
732
|
+
if (job === undefined || job.status !== "running")
|
|
733
|
+
return;
|
|
734
|
+
const execution = { kind: "daemon-v1", executionId };
|
|
735
|
+
this.brokerExecutions.set(id, executionId);
|
|
736
|
+
this.journal.noteBrokerExecution?.(id, execution);
|
|
737
|
+
}
|
|
738
|
+
adoptBrokerRunning(row, snapshot) {
|
|
739
|
+
if (row.brokerExecution === undefined)
|
|
740
|
+
return undefined;
|
|
741
|
+
const existing = this.jobs.get(row.jobId);
|
|
742
|
+
if (existing !== undefined)
|
|
743
|
+
return existing;
|
|
744
|
+
const job = {
|
|
745
|
+
id: row.jobId,
|
|
746
|
+
status: "running",
|
|
747
|
+
laneId: row.laneId,
|
|
748
|
+
spec: row.spec,
|
|
749
|
+
startedAt: row.startedAt,
|
|
750
|
+
endedAt: undefined,
|
|
751
|
+
exitCode: null,
|
|
752
|
+
stdout: "",
|
|
753
|
+
stderr: "",
|
|
754
|
+
timedOut: false,
|
|
755
|
+
cwd: row.cwd,
|
|
756
|
+
error: undefined,
|
|
757
|
+
attempts: [],
|
|
758
|
+
restored: true,
|
|
759
|
+
executionOwner: "relay-daemon",
|
|
760
|
+
...(row.label === undefined ? {} : { label: row.label }),
|
|
761
|
+
};
|
|
762
|
+
this.jobs.set(job.id, job);
|
|
763
|
+
this.brokerExecutions.set(job.id, row.brokerExecution.executionId);
|
|
764
|
+
if (snapshot !== undefined)
|
|
765
|
+
this.applyBrokerSnapshot(job.id, snapshot);
|
|
766
|
+
return job;
|
|
767
|
+
}
|
|
768
|
+
applyBrokerSnapshot(id, snapshot) {
|
|
769
|
+
const job = this.jobs.get(id);
|
|
770
|
+
const executionId = this.brokerExecutions.get(id);
|
|
771
|
+
if (job === undefined ||
|
|
772
|
+
executionId === undefined ||
|
|
773
|
+
snapshot.executionId !== executionId ||
|
|
774
|
+
snapshot.jobId !== id ||
|
|
775
|
+
snapshot.laneId !== job.laneId) {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
if (snapshot.launchNotes !== undefined)
|
|
779
|
+
job.launch = [...snapshot.launchNotes];
|
|
780
|
+
job.activity = {
|
|
781
|
+
attemptStartedAt: snapshot.startedAt,
|
|
782
|
+
lastOutputAt: snapshot.lastOutputAt,
|
|
783
|
+
stdoutBytes: snapshot.stdoutBytes,
|
|
784
|
+
stderrBytes: snapshot.stderrBytes,
|
|
785
|
+
};
|
|
786
|
+
if (snapshot.status === "running")
|
|
787
|
+
return true;
|
|
788
|
+
if (snapshot.status === "completed") {
|
|
789
|
+
if (snapshot.code !== 0 || snapshot.timedOut !== false || snapshot.stdout === undefined)
|
|
790
|
+
return false;
|
|
791
|
+
if (isContentEmpty(snapshot.stdout)) {
|
|
792
|
+
job.status = "failed";
|
|
793
|
+
job.error = EMPTY_OUTPUT_REASON;
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
job.status = "completed";
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
else if (snapshot.status === "failed") {
|
|
800
|
+
if (snapshot.timedOut !== false || snapshot.code === 0)
|
|
801
|
+
return false;
|
|
802
|
+
job.status = "failed";
|
|
803
|
+
}
|
|
804
|
+
else if (snapshot.status === "timed_out") {
|
|
805
|
+
if (snapshot.timedOut !== true)
|
|
806
|
+
return false;
|
|
807
|
+
job.status = "timed_out";
|
|
808
|
+
job.error = "the daemon-owned lane exceeded its configured timeout and was stopped before it finished";
|
|
809
|
+
}
|
|
810
|
+
else if (snapshot.status === "cancelled") {
|
|
811
|
+
job.status = "cancelled";
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
const _never = snapshot.status;
|
|
815
|
+
return _never;
|
|
816
|
+
}
|
|
817
|
+
if (snapshot.code !== undefined)
|
|
818
|
+
job.exitCode = snapshot.code;
|
|
819
|
+
if (snapshot.stdout !== undefined)
|
|
820
|
+
job.stdout = snapshot.stdout;
|
|
821
|
+
if (snapshot.stderr !== undefined)
|
|
822
|
+
job.stderr = snapshot.stderr;
|
|
823
|
+
if (snapshot.timedOut !== undefined)
|
|
824
|
+
job.timedOut = snapshot.timedOut;
|
|
825
|
+
job.endedAt = snapshot.endedAt ?? Date.now();
|
|
826
|
+
delete job.activity;
|
|
827
|
+
delete job.liveness;
|
|
828
|
+
this.brokerExecutions.delete(id);
|
|
829
|
+
if (this.archive.record(job))
|
|
830
|
+
this.journal.clear(id);
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
833
|
+
markBrokerKilled(id, message) {
|
|
834
|
+
const job = this.jobs.get(id);
|
|
835
|
+
if (job === undefined || !this.brokerExecutions.has(id) || job.status !== "running")
|
|
836
|
+
return false;
|
|
837
|
+
job.status = "killed";
|
|
838
|
+
job.endedAt = Date.now();
|
|
839
|
+
job.error = message;
|
|
840
|
+
delete job.activity;
|
|
841
|
+
delete job.liveness;
|
|
842
|
+
this.brokerExecutions.delete(id);
|
|
843
|
+
if (this.archive.record(job))
|
|
844
|
+
this.journal.clear(id);
|
|
845
|
+
return true;
|
|
846
|
+
}
|
|
847
|
+
takeAdoptedStartingTrees() {
|
|
848
|
+
const adopted = [...this.adoptedStartingTrees].map(([jobId, value]) => ({ jobId, ...value }));
|
|
849
|
+
this.adoptedStartingTrees.clear();
|
|
850
|
+
return adopted;
|
|
851
|
+
}
|
|
545
852
|
noteReadOnly(id, laneId, binding) {
|
|
546
853
|
const job = this.jobs.get(id);
|
|
547
854
|
if (!job || job.status !== "running")
|
|
@@ -553,14 +860,15 @@ export class LaneJobStore {
|
|
|
553
860
|
if (!job)
|
|
554
861
|
return;
|
|
555
862
|
job.treeDelta = text;
|
|
556
|
-
if (job.status !== "running")
|
|
557
|
-
this.
|
|
863
|
+
if (job.status !== "running" && this.archive.record(job)) {
|
|
864
|
+
this.journal.clear(id);
|
|
865
|
+
}
|
|
558
866
|
}
|
|
559
|
-
|
|
867
|
+
noteLiveness(id, liveness) {
|
|
560
868
|
const job = this.jobs.get(id);
|
|
561
869
|
if (!job || job.status !== "running")
|
|
562
870
|
return;
|
|
563
|
-
job.
|
|
871
|
+
job.liveness = { ...liveness };
|
|
564
872
|
}
|
|
565
873
|
noteLaunch(id, notes) {
|
|
566
874
|
const job = this.jobs.get(id);
|
|
@@ -687,6 +995,8 @@ export class LaneJobStore {
|
|
|
687
995
|
return false;
|
|
688
996
|
if (job.status !== "running")
|
|
689
997
|
return false;
|
|
998
|
+
if (this.brokerExecutions.has(id))
|
|
999
|
+
return false;
|
|
690
1000
|
job.status = "cancelled";
|
|
691
1001
|
job.endedAt = Date.now();
|
|
692
1002
|
this.reap(id);
|
|
@@ -694,6 +1004,8 @@ export class LaneJobStore {
|
|
|
694
1004
|
}
|
|
695
1005
|
cancelAll() {
|
|
696
1006
|
for (const id of [...this.jobs.values()].filter((j) => j.status === "running").map((j) => j.id)) {
|
|
1007
|
+
if (this.brokerExecutions.has(id))
|
|
1008
|
+
continue;
|
|
697
1009
|
this.cancel(id);
|
|
698
1010
|
}
|
|
699
1011
|
}
|