trantor 0.18.50 → 0.18.52
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/.claude-plugin/plugin.json +1 -1
- package/bin/cli.mjs +2 -0
- package/bin/crew-runner.mjs +285 -9
- package/bin/doctor.mjs +61 -1
- package/bin/drill-surface.mjs +47 -0
- package/bin/duty.mjs +68 -1
- package/bin/state-bench.mjs +880 -0
- package/bin/state.mjs +295 -0
- package/bin/write-handoff.mjs +7 -1
- package/hooks/lib/api.mjs +23 -3
- package/hooks/lib/handoff.mjs +115 -1
- package/hooks/sessionstart.mjs +10 -0
- package/hub/duty.mjs +43 -2
- package/hub/routes/admin.mjs +5 -0
- package/lib/duty-nudges.mjs +36 -2
- package/lib/state/assemble.mjs +96 -0
- package/lib/state/cost.mjs +88 -0
- package/lib/state/derive.mjs +229 -0
- package/lib/state/driver.mjs +535 -0
- package/lib/state/gate.mjs +355 -0
- package/lib/state/promote.mjs +122 -0
- package/lib/state/store.mjs +442 -0
- package/lib/state/validate.mjs +19 -4
- package/mcp.mjs +5 -1
- package/package.json +1 -1
package/bin/duty.mjs
CHANGED
|
@@ -245,6 +245,14 @@ if (cmd === "up") {
|
|
|
245
245
|
const env = (() => {
|
|
246
246
|
const e = { RELAY_URL: hub, RUNNER_RULES: RULES, CREW_KICKOFF: KICKOFF,
|
|
247
247
|
RUNNER_DUTY_NUDGES: "1",
|
|
248
|
+
// A parked seat waits for `trantor up`. That is right for a CODE seat — burning a
|
|
249
|
+
// plan re-sending the same contract helps nobody (#6270). It is wrong for DUTY,
|
|
250
|
+
// which is the seat every other seat's liveness runs through: on 2026-09-09 it
|
|
251
|
+
// parked on a quota read and sat holding 48 messages for 21.9 hours while the
|
|
252
|
+
// crew finished a night's work nobody gated. Duty is the one seat under a launchd
|
|
253
|
+
// keepalive, so it does not need to sit there — it can exit and let the supervisor
|
|
254
|
+
// bring it back clean, which also re-reads auth. 15 minutes, then hand over.
|
|
255
|
+
RUNNER_PARK_MAX_MS: "900000",
|
|
248
256
|
RUNNER_TITLE: "Trantor Duty Agent", RUNNER_ABOUT: ABOUT,
|
|
249
257
|
// launchd starts jobs with a MINIMAL Path — the resurrected seat could not find
|
|
250
258
|
// `claude` and every turn died exit 127 "missing-cli" (found live 2026-08-31,
|
|
@@ -360,6 +368,31 @@ if (cmd === "down") {
|
|
|
360
368
|
process.exit(0);
|
|
361
369
|
}
|
|
362
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Is duty actually moving mail? Read from the runner's own on-disk queue rather than trusting the
|
|
373
|
+
* process table. `pending-<agent>-<project>.json` is written on every failed delivery and deleted
|
|
374
|
+
* when the queue drains, so its existence means messages are held and its oldest entry says for how
|
|
375
|
+
* long. STALL_HOURS is deliberately well past the runner's own 15-minute retry ceiling: anything
|
|
376
|
+
* older than that is not a retry in progress, it is a parked seat nobody restarted.
|
|
377
|
+
*/
|
|
378
|
+
const STALL_HOURS = 1;
|
|
379
|
+
function dutyHealth() {
|
|
380
|
+
const f = join(BUS, `pending-${AGENT}-trantor-duty.json`);
|
|
381
|
+
try {
|
|
382
|
+
const j = JSON.parse(readFileSync(f, "utf8"));
|
|
383
|
+
const wake = Array.isArray(j.wake) ? j.wake : [];
|
|
384
|
+
const bcast = Array.isArray(j.bcast) ? j.bcast : [];
|
|
385
|
+
const held = wake.length + bcast.length;
|
|
386
|
+
if (!held) return { held: 0, stalled: false, oldestHours: "0.0" };
|
|
387
|
+
const stamps = [...wake, ...bcast].map(m => m?.ts).filter(Number.isFinite);
|
|
388
|
+
const oldest = stamps.length ? Math.min(...stamps) : j.ts;
|
|
389
|
+
const hours = (Date.now() - oldest) / 3.6e6;
|
|
390
|
+
return { held, stalled: hours >= STALL_HOURS, oldestHours: hours.toFixed(1) };
|
|
391
|
+
} catch {
|
|
392
|
+
return { held: 0, stalled: false, oldestHours: "0.0" }; // no queue file = nothing held
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
363
396
|
// status
|
|
364
397
|
{
|
|
365
398
|
const pid = alivePid();
|
|
@@ -370,7 +403,41 @@ if (cmd === "down") {
|
|
|
370
403
|
console.log(" whether a seat is still alive, and clears away dead runners. It never writes code");
|
|
371
404
|
console.log(" and never edits your project files. Stop it with: trantor duty down");
|
|
372
405
|
console.log("");
|
|
373
|
-
|
|
406
|
+
// A pid is not health. This line used to read `pid ? "RUNNING" : "NOT running"`, and on 2026-09-09
|
|
407
|
+
// it said RUNNING for a seat whose last successful turn was 21.8 hours earlier and which was
|
|
408
|
+
// holding 48 undelivered messages, the oldest 21.9 hours old — the runner had parked itself
|
|
409
|
+
// waiting on a quota reset and only `trantor up` un-parks it. Nothing noticed, because the one
|
|
410
|
+
// command whose job is to answer "is duty working" was answering "does a process exist".
|
|
411
|
+
//
|
|
412
|
+
// The irony worth keeping: ten lines below, this same function already refuses to assume the hub
|
|
413
|
+
// feed is wired, on the grounds that "a running seat the hub isn't feeding looks identical to a
|
|
414
|
+
// working one from the outside". That rigour was applied to the hub and not to the seat itself.
|
|
415
|
+
//
|
|
416
|
+
// The queue is the honest signal because it survives the process: the runner persists it to
|
|
417
|
+
// pending-<agent>-<project>.json on every failed delivery, so a backlog with an old head means
|
|
418
|
+
// messages are not moving no matter what the process table says.
|
|
419
|
+
const health = dutyHealth();
|
|
420
|
+
if (!pid) {
|
|
421
|
+
console.log("NOT running");
|
|
422
|
+
// A backlog matters MORE when the seat is down, not less: those messages have nobody to deliver
|
|
423
|
+
// them and no retry ladder running. Reporting it only in the pid branch was the first version
|
|
424
|
+
// of this fix, and it hid exactly the case that needs saying out loud.
|
|
425
|
+
if (health.held) {
|
|
426
|
+
console.log(` \x1b[31mand ${health.held} undelivered message(s) are held on disk, oldest ${health.oldestHours}h old\x1b[0m`);
|
|
427
|
+
console.log(` → nothing will deliver them until the seat is back: trantor duty up`);
|
|
428
|
+
} else {
|
|
429
|
+
console.log(" queue empty — nothing is waiting");
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
else if (health.stalled) {
|
|
433
|
+
console.log(`\x1b[31mSTALLED\x1b[0m (pid ${pid}) as ${SESSION} — the process is up but mail is not moving`);
|
|
434
|
+
console.log(` holding ${health.held} undelivered message(s), oldest ${health.oldestHours}h old`);
|
|
435
|
+
console.log(` → the runner parks on quota/api failure and only a restart un-parks it: trantor duty up`);
|
|
436
|
+
} else if (health.held) {
|
|
437
|
+
console.log(`RUNNING (pid ${pid}) as ${SESSION} — draining ${health.held} queued message(s), oldest ${health.oldestHours}h old`);
|
|
438
|
+
} else {
|
|
439
|
+
console.log(`RUNNING (pid ${pid}) as ${SESSION} — queue empty`);
|
|
440
|
+
}
|
|
374
441
|
console.log(existsSync(DUTY_PLIST)
|
|
375
442
|
? `keepalive: installed (${DUTY_LABEL}${process.platform === "darwin" ? (dutyLoaded() ? ", loaded" : ", not loaded in this session") : ""}) — launchd relaunches the seat after a crash or reboot`
|
|
376
443
|
: "keepalive: NOT installed — a crash or reboot leaves the seat down (trantor duty up installs it)");
|