trantor 0.18.20 → 0.18.22
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 +2 -2
- package/README.md +5 -0
- package/bin/crew.sh +33 -7
- package/hooks/sessionstart.mjs +14 -2
- package/hooks/statusline.mjs +47 -0
- package/hub.mjs +23 -0
- package/lib/balances.mjs +37 -6
- package/package.json +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.18.
|
|
4
|
-
"description": "Trantor
|
|
3
|
+
"version": "0.18.22",
|
|
4
|
+
"description": "Trantor — the hub-world for AI agent crews: live message bus, presence, project Kanban/flow board + crew orchestration for independent AI coding agents (Claude, Codex, Gemini, Kimi, DeepSeek)",
|
|
5
5
|
"mcpServers": {
|
|
6
6
|
"relay": {
|
|
7
7
|
"command": "node",
|
package/README.md
CHANGED
|
@@ -364,6 +364,11 @@ model-authored handoff instead of superseding it; the successor is injected a ca
|
|
|
364
364
|
being spoken to; and a session hosted in a Workspace pane is replaced in place by a detached
|
|
365
365
|
driver — the same chain the app's [Hand off now] button runs.
|
|
366
366
|
|
|
367
|
+
Waking works the same way from the other end: hover a sleeping project in the app's sidebar and
|
|
368
|
+
**Wake** hosts its session as an in-app pane — `trantor open` under the hood, opened in the
|
|
369
|
+
project's own checkout wherever it is called from, handoff-beats-resume, and the same kickoff
|
|
370
|
+
prompt so the woken session catches up (handoff, board, memory) and recaps on its own.
|
|
371
|
+
|
|
367
372
|
Why crews never exhaust the orchestrator: bus messages are **by reference** (~70 tokens),
|
|
368
373
|
work products stay in each agent's own context — the orchestrator burns at coordination
|
|
369
374
|
rate, not work rate.
|
package/bin/crew.sh
CHANGED
|
@@ -194,6 +194,24 @@ _herdr_close_pane() { [ "$DRY" = "1" ] && { echo "[dry] herdr pane close $1";
|
|
|
194
194
|
# So the project gets ONE claude session id, chosen by us and remembered. First open starts claude
|
|
195
195
|
# under it; every later open resumes it. Discovering the id afterwards would be guesswork, and
|
|
196
196
|
# `--continue` would grab whatever ran last in this directory, which may be a different window.
|
|
197
|
+
# A NAMED project must open in ITS checkout, wherever the caller stands (2026-08-31: the app ran
|
|
198
|
+
# `trantor open crebral-health` from the Tauri process cwd and claude booted THERE — a trust
|
|
199
|
+
# prompt for a folder the operator never chose, transcripts under the wrong slug, no project
|
|
200
|
+
# memory, ACTIVE NOW blind). Resolution mirrors the app's project_dir: $TRANTOR_DEV_ROOT
|
|
201
|
+
# (default ~/development)/<name>. Unknown name from an unrelated cwd → refuse loudly rather
|
|
202
|
+
# than open somewhere silly.
|
|
203
|
+
_orch_resolve_dir() { # $1=cwd $2=project-arg → dir to open in (stdout); fails when unresolvable
|
|
204
|
+
local herebase; herebase="$(basename "$(git -C "$1" rev-parse --show-toplevel 2>/dev/null || echo "$1")")"
|
|
205
|
+
if [ -z "$2" ] || [ "$2" = "$herebase" ]; then printf '%s' "$1"; return 0; fi
|
|
206
|
+
local devroot="${TRANTOR_DEV_ROOT:-$HOME/development}"
|
|
207
|
+
if [ -d "$devroot/$2" ]; then
|
|
208
|
+
echo "— opening $2 in its checkout: $devroot/$2 —" >&2
|
|
209
|
+
printf '%s' "$devroot/$2"; return 0
|
|
210
|
+
fi
|
|
211
|
+
echo "trantor open: '$2' has no checkout at $devroot/$2 and this is '$herebase' — cd into the project first" >&2
|
|
212
|
+
return 1
|
|
213
|
+
}
|
|
214
|
+
|
|
197
215
|
_orch_sid() { # $1=project → the project's session uuid, minting one on first use
|
|
198
216
|
local f="${STATE%/*}/orch-sessions.txt" p sid
|
|
199
217
|
if [ -f "$f" ]; then
|
|
@@ -244,26 +262,31 @@ _orch_cmd() { # $1=dir $2=sid
|
|
|
244
262
|
}
|
|
245
263
|
|
|
246
264
|
# A recorded thread that HANDED OFF has ended: resuming it replays a dead conversation while the
|
|
247
|
-
# handoff waits for a successor (the 2026-08-27 seam — "Trantor resumes the wrong thread").
|
|
248
|
-
#
|
|
249
|
-
# FRESH id
|
|
250
|
-
# orch-sessions.txt
|
|
265
|
+
# handoff waits for a successor (the 2026-08-27 seam — "Trantor resumes the wrong thread").
|
|
266
|
+
# ANY unconsumed handoff for the project means a successor is OWED a fresh window — open must
|
|
267
|
+
# start a FRESH id so the sessionstart hook claims the baton (it then records the fresh id in
|
|
268
|
+
# orch-sessions.txt; single writer: the hook + adopt — this function writes nothing).
|
|
269
|
+
# The old predicate required the handoff to be written BY the recorded session (session_id match)
|
|
270
|
+
# — but MANUAL handoffs carry no session_id at all, so on 2026-08-31 the reboot flow resumed the
|
|
271
|
+
# same maxed-out conversation TWICE while its handoff sat unclaimed, and the injected recap
|
|
272
|
+
# banner made each resume LOOK like a clean takeover. Who wrote it doesn't matter; that it is
|
|
273
|
+
# unclaimed does.
|
|
251
274
|
_orch_takeover_sid() { # $1=project $2=recorded-sid → prints the sid open should use
|
|
252
275
|
local fresh
|
|
253
276
|
if node -e '
|
|
254
277
|
const fs=require("fs"),path=require("path"),os=require("os");
|
|
255
278
|
const dir=path.join(process.env.AGENT_BUS_DIR||process.env.RELAY_DATA_DIR||path.join(os.homedir(),".agent-bus"),"handoffs");
|
|
256
|
-
const [proj
|
|
279
|
+
const [proj]=process.argv.slice(1);
|
|
257
280
|
try{
|
|
258
281
|
const re=new RegExp("^"+proj.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"-(\\d+)\\.json$");
|
|
259
282
|
const files=fs.readdirSync(dir).map(f=>{const m=re.exec(f);return m?{f,s:Number(m[1])}:null}).filter(Boolean).sort((a,b)=>b.s-a.s);
|
|
260
283
|
for(const {f} of files){
|
|
261
284
|
const r=JSON.parse(fs.readFileSync(path.join(dir,f),"utf8"));
|
|
262
285
|
if(r.consumed) continue;
|
|
263
|
-
process.exit(
|
|
286
|
+
process.exit(0); // newest UNCONSUMED exists → a successor is owed a fresh window
|
|
264
287
|
}
|
|
265
288
|
}catch(e){}
|
|
266
|
-
process.exit(1);' "$1"
|
|
289
|
+
process.exit(1);' "$1" 2>/dev/null; then
|
|
267
290
|
fresh="$(uuidgen 2>/dev/null | tr 'A-Z' 'a-z')"
|
|
268
291
|
[ -n "$fresh" ] && { echo "— recorded session $2 handed off: starting fresh as $fresh to claim it —" >&2; printf '%s' "$fresh"; return 0; }
|
|
269
292
|
fi
|
|
@@ -596,6 +619,7 @@ open_orchestrator() {
|
|
|
596
619
|
--*) echo "trantor open: unknown flag '$a'"; usage_open; return 1 ;;
|
|
597
620
|
*) PROJ="$a" ;;
|
|
598
621
|
esac; done
|
|
622
|
+
DIR="$(_orch_resolve_dir "$DIR" "$PROJ")" || exit 1
|
|
599
623
|
command -v herdr >/dev/null 2>&1 || { echo "trantor open needs herdr (the pane host) — install: curl -fsSL https://herdr.dev/install.sh | sh"; exit 1; }
|
|
600
624
|
local wsid="" orch="" live_ids="" live_names="" pair="" line fresh=0
|
|
601
625
|
local sid; sid="$(_orch_sid "$PROJ")" || { echo "trantor open: could not mint a session id (uuidgen missing?)" >&2; exit 1; }
|
|
@@ -630,6 +654,8 @@ open_orchestrator() {
|
|
|
630
654
|
if _herdr pane rename "$orch" "orchestrator · $PROJ" >/dev/null 2>&1; then
|
|
631
655
|
if _herdr_pane_has_agent "$orch"; then
|
|
632
656
|
echo "herdr:${wsid:-?}/$orch"
|
|
657
|
+
# CONTRACT: the app's orchestrator_open (desktop terminal.rs) matches this exact phrase
|
|
658
|
+
# to SKIP its kickoff prompt — a reattach is a live conversation, never to be typed into.
|
|
633
659
|
echo "— orchestrator already hosted: reattached to herdr:${wsid:-?}/$orch —" >&2
|
|
634
660
|
return 0
|
|
635
661
|
fi
|
package/hooks/sessionstart.mjs
CHANGED
|
@@ -441,6 +441,11 @@ try {
|
|
|
441
441
|
// The hold LAPSES (default 30m) so an unclaimed baton never strands every other session.
|
|
442
442
|
// Any other handoff keeps first-fresh-session-wins.
|
|
443
443
|
const isCompact = source === "compact";
|
|
444
|
+
// A RESUMED session must never claim a handoff (2026-08-31, twice in one afternoon): it
|
|
445
|
+
// already carries its whole history, so claiming injects the takeover banner into the same
|
|
446
|
+
// maxed-out context — the recap LOOKS right while the window never reset, which is worse
|
|
447
|
+
// than failing loudly. The successor is whatever fresh session `trantor open` starts.
|
|
448
|
+
const isResume = source === "resume";
|
|
444
449
|
const orchEnv = process.env.TRANTOR_ORCH || "";
|
|
445
450
|
const isOrchPane = !!orchEnv && (orchEnv === "1" || orchEnv === project); // project-matched: a child claude in another dir must not inherit the badge
|
|
446
451
|
const orchSid = readOrchSession(project);
|
|
@@ -450,12 +455,12 @@ try {
|
|
|
450
455
|
const ageMs = peek ? Date.now() - (Number(peek.stamp) || 0) * 1000 : 0;
|
|
451
456
|
const held = orchOrigin && !isOrchPane && !isCompact && ageMs < holdMs;
|
|
452
457
|
const handoff = !peek ? null
|
|
453
|
-
: (isCompact || held) ? peek
|
|
458
|
+
: (isCompact || held || isResume) ? peek
|
|
454
459
|
: loadPendingHandoff(basename(projectDir), {
|
|
455
460
|
claim: true,
|
|
456
461
|
freshSession: { session_id: stdinObj.session_id || "", transcript_path: stdinObj.transcript_path || "" },
|
|
457
462
|
});
|
|
458
|
-
const claimed = !!handoff && !isCompact && !held;
|
|
463
|
+
const claimed = !!handoff && !isCompact && !held && !isResume;
|
|
459
464
|
// Follow the thread: claiming the orchestrator's baton makes THIS session the orchestrator
|
|
460
465
|
// thread, so the map `trantor open` resumes and the app's chat reads moves with it. The pane
|
|
461
466
|
// also records itself on every fresh start — that keeps the map honest even if a future
|
|
@@ -468,6 +473,13 @@ try {
|
|
|
468
473
|
additionalContext += `<trantor-handoff-held id="${sanitize(handoff.id)}" from="${sanitize(handoff.machine)}">\n`;
|
|
469
474
|
additionalContext += `🔒 A handoff from this project's ORCHESTRATOR thread is pending, and it is being HELD for the Trantor orchestrator pane (\`trantor open\` claims it on start). This session did NOT claim it and does not have its content — do not act as the successor. If the user wants THIS session to take over instead: \`trantor adopt\`, then restart this session. Unclaimed, the hold lapses in ~${mins} minute(s) and the handoff becomes first-come.\n`;
|
|
470
475
|
additionalContext += `</trantor-handoff-held>\n`;
|
|
476
|
+
} else if (handoff && isResume) {
|
|
477
|
+
// No takeover banner here — injecting it into a resumed session is exactly the failure this
|
|
478
|
+
// guard exists for: the recap reads like a clean handoff while the context never reset.
|
|
479
|
+
process.stderr.write(`[trantor] pending handoff ${handoff.id} NOT claimed — this session was RESUMED, not started fresh\n`);
|
|
480
|
+
additionalContext += `<trantor-handoff-unclaimed id="${sanitize(handoff.id)}" reason="resumed-session">\n`;
|
|
481
|
+
additionalContext += `⚠️ An unclaimed handoff (${sanitize(handoff.id)}) is waiting for this project, but THIS session was RESUMED (\`--resume\`) — it already carries its own history and is NOT the successor. Do not act on the handoff. Tell the user plainly: a fresh session must claim it — \`trantor open\` starts one. If they want THIS resumed session to continue instead, they can ignore the handoff or clear it.\n`;
|
|
482
|
+
additionalContext += `</trantor-handoff-unclaimed>\n`;
|
|
471
483
|
} else if (handoff) {
|
|
472
484
|
process.stderr.write(`[trantor] ${isCompact ? "showing (not claiming, compact)" : "loaded"} pending handoff ${handoff.id}\n`);
|
|
473
485
|
// Baton claimed → supersede every OTHER instance of this durable identity (instance-keys
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// USAGE v2 — the Claude statusline sidechannel (docs/RESEARCH-orca-usage.md §1.1).
|
|
3
|
+
// Claude Code >=2.1.80 pipes a JSON blob (with `rate_limits`) into the statusLine command on
|
|
4
|
+
// every turn, piggybacked on the Messages API response — live usage that costs zero API budget.
|
|
5
|
+
// This forwarder reads that stdin, POSTs the windows to the hub's /usage/claude (signed), and
|
|
6
|
+
// prints NOTHING: it is designed to be tee'd ahead of the operator's real statusline command,
|
|
7
|
+
// never to be one. Every failure is swallowed — a usage forwarder must never break a statusline.
|
|
8
|
+
//
|
|
9
|
+
// Floor: one POST per session per 15s (stamp file) — the statusline ticks ~3x/sec while
|
|
10
|
+
// streaming, and the hub dedupes same-value posts inside 30s anyway.
|
|
11
|
+
import { readFileSync, writeFileSync, statSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
|
|
15
|
+
const FLOOR_MS = 15_000;
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
let raw = "";
|
|
19
|
+
for await (const c of process.stdin) raw += c;
|
|
20
|
+
const j = JSON.parse(raw);
|
|
21
|
+
const rl = j.rate_limits;
|
|
22
|
+
if (!rl || typeof rl !== "object") return; // most ticks carry none — free exit
|
|
23
|
+
const sid = String(j.session_id || j.sessionId || "nosession").replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 80);
|
|
24
|
+
const dir = join(homedir(), ".agent-bus");
|
|
25
|
+
const stamp = join(dir, `usage-claude-${sid}.stamp`);
|
|
26
|
+
try { if (Date.now() - statSync(stamp).mtimeMs < FLOOR_MS) return; } catch {}
|
|
27
|
+
try { mkdirSync(dir, { recursive: true }); writeFileSync(stamp, ""); } catch {}
|
|
28
|
+
|
|
29
|
+
const payload = {
|
|
30
|
+
configDir: process.env.CLAUDE_CONFIG_DIR || null,
|
|
31
|
+
fiveHour: rl.five_hour ?? rl.fiveHour ?? null,
|
|
32
|
+
sevenDay: rl.seven_day ?? rl.sevenDay ?? null,
|
|
33
|
+
// The fable/model-scoped window normally arrives via the OAuth poller, but accept the
|
|
34
|
+
// statusline-shaped variants too — schema drift degrades instead of going dark.
|
|
35
|
+
fable: rl.fable_weekly ?? rl.fable ?? null,
|
|
36
|
+
};
|
|
37
|
+
if (!payload.fiveHour && !payload.sevenDay && !payload.fable) return;
|
|
38
|
+
// PRIMARY: the local live cache — lib/balances.mjs merges it and skips the OAuth poll while
|
|
39
|
+
// it is fresh (Orca's isLiveClaudeUsageFresh, docs/RESEARCH-orca-usage.md §2). This is the
|
|
40
|
+
// path the app's footer actually reads (it shells the local CLI, not the hub).
|
|
41
|
+
try { writeFileSync(join(dir, "usage-claude-live.json"), JSON.stringify({ ts: Date.now(), ...payload })); } catch {}
|
|
42
|
+
// SECONDARY, best-effort: the hub copy, for dashboard surfaces that read hub state.
|
|
43
|
+
const { signedPost } = await import("./lib/api.mjs");
|
|
44
|
+
await signedPost("/usage/claude", payload, { timeoutMs: 2500 });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
main().catch(() => {}).finally(() => process.exit(0));
|
package/hub.mjs
CHANGED
|
@@ -1642,6 +1642,29 @@ const server = http.createServer(async (req, res) => {
|
|
|
1642
1642
|
if (ts >= (state.balances?.ts || 0)) { state.balances = { ts, by: String(b.by || "").slice(0, 120), entries }; dirty = true; }
|
|
1643
1643
|
return json(res, 200, { ok: true });
|
|
1644
1644
|
}
|
|
1645
|
+
// USAGE v2: the Claude statusline sidechannel. Claude Code >=2.1.80 pipes rate_limits into
|
|
1646
|
+
// the statusLine command on every turn; hooks/statusline.mjs forwards it here (floored 15s
|
|
1647
|
+
// client-side). The live windows PATCH the cached balances snapshot — free usage between
|
|
1648
|
+
// `trantor balances` runs, and the poller can skip Claude while liveTs is fresh (Orca's
|
|
1649
|
+
// lesson, docs/RESEARCH-orca-usage.md §1.1: the OAuth endpoint 429s under polling).
|
|
1650
|
+
if (req.method === "POST" && P === "/usage/claude") {
|
|
1651
|
+
const b = await body(req);
|
|
1652
|
+
const win = (w, name) => (w && (w.used_percentage ?? w.utilization) != null)
|
|
1653
|
+
? { name, usedPct: Math.round(Number(w.used_percentage ?? w.utilization)), resetsAt: w.resets_at ?? null } : null;
|
|
1654
|
+
const wins = [["fiveHour", "5h"], ["sevenDay", "7d"], ["fable", "Fable"]]
|
|
1655
|
+
.map(([k, n]) => win(b[k], n)).filter(Boolean);
|
|
1656
|
+
if (!wins.length) return json(res, 400, { error: "no usable windows" });
|
|
1657
|
+
state.balances ||= { ts: 0, by: "", entries: [] };
|
|
1658
|
+
let e = state.balances.entries.find(x => x.provider === "claude");
|
|
1659
|
+
if (!e) { e = { provider: "claude", label: "Claude", kind: "windows", ok: true, windows: [] }; state.balances.entries.push(e); }
|
|
1660
|
+
// Same-value posts inside 30s are dropped (the statusline ticks ~3x/sec while streaming).
|
|
1661
|
+
const sig = JSON.stringify(wins);
|
|
1662
|
+
if (e._liveSig === sig && now() - (e.liveTs || 0) < 30_000) return json(res, 200, { ok: true, deduped: true });
|
|
1663
|
+
for (const w of wins) { const cur = (e.windows ||= []).find(x => x.name === w.name); if (cur) Object.assign(cur, w); else e.windows.push(w); }
|
|
1664
|
+
e.ok = true; e.liveTs = now(); e._liveSig = sig; e.liveSource = "statusline";
|
|
1665
|
+
dirty = true;
|
|
1666
|
+
return json(res, 200, { ok: true, windows: wins.length });
|
|
1667
|
+
}
|
|
1645
1668
|
if (req.method === "GET" && P === "/balances") {
|
|
1646
1669
|
let cfg = {}; try { cfg = JSON.parse(readFileSync(join(homedir(), ".agent-bus", "config.json"), "utf8")); } catch {}
|
|
1647
1670
|
const low = { USD: 5, CNY: 35, EUR: 5, ...(cfg.lowBalance || {}) };
|
package/lib/balances.mjs
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
// ARCHITECTURE NOTE: the hub runs under launchd with a minimal env (no keys), so it can't fetch these
|
|
12
12
|
// itself. The env-having clients fetch + POST /balances; the hub caches + serves the dashboard.
|
|
13
13
|
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
|
|
14
18
|
const TIMEOUT = 8000;
|
|
15
19
|
|
|
16
20
|
async function getJSON(url, key, extraHeaders = {}) {
|
|
@@ -40,13 +44,40 @@ export const ADAPTERS = [
|
|
|
40
44
|
provider: "claude", label: "Claude", kind: "windows", match: ["claude", "anthropic"], envKeys: [],
|
|
41
45
|
keyless: true,
|
|
42
46
|
async fetch() {
|
|
47
|
+
// USAGE v2: the statusline sidechannel (hooks/statusline.mjs) keeps a live local cache —
|
|
48
|
+
// Claude Code pipes rate_limits to the statusLine every turn, free. When the OAuth
|
|
49
|
+
// endpoint fails (it 429s under polling — Orca's lesson, docs/RESEARCH-orca-usage.md
|
|
50
|
+
// §1.1), a fresh live capture answers instead of an error row. OAuth stays primary
|
|
51
|
+
// because only it carries the model-scoped (Fable) window.
|
|
52
|
+
const live = (() => {
|
|
53
|
+
try {
|
|
54
|
+
const l = JSON.parse(readFileSync(join(homedir(), ".agent-bus", "usage-claude-live.json"), "utf8"));
|
|
55
|
+
return l && Date.now() - (l.ts || 0) < 15 * 60 * 1000 ? l : null;
|
|
56
|
+
} catch { return null; }
|
|
57
|
+
})();
|
|
58
|
+
const liveWin = (w, name) => (w && (w.used_percentage ?? w.utilization) != null)
|
|
59
|
+
? { name, usedPct: Math.round(Number(w.used_percentage ?? w.utilization)), resetsAt: w.resets_at || null, locked: null }
|
|
60
|
+
: null;
|
|
61
|
+
const liveWindows = () => [liveWin(live?.fiveHour, "5h"), liveWin(live?.sevenDay, "7d")].filter(Boolean);
|
|
43
62
|
const tok = await claudeOAuthToken();
|
|
44
|
-
if (!tok)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
if (!tok) {
|
|
64
|
+
if (live && liveWindows().length) return { windows: liveWindows(), live: true };
|
|
65
|
+
throw new Error("no Claude Code OAuth token found");
|
|
66
|
+
}
|
|
67
|
+
let r;
|
|
68
|
+
try {
|
|
69
|
+
r = await fetch("https://api.anthropic.com/api/oauth/usage", {
|
|
70
|
+
headers: { authorization: `Bearer ${tok}`, "anthropic-beta": "oauth-2025-04-20" },
|
|
71
|
+
signal: AbortSignal.timeout(8000),
|
|
72
|
+
});
|
|
73
|
+
} catch (e) {
|
|
74
|
+
if (live && liveWindows().length) return { windows: liveWindows(), live: true };
|
|
75
|
+
throw e;
|
|
76
|
+
}
|
|
77
|
+
if (!r.ok) {
|
|
78
|
+
if (live && liveWindows().length) return { windows: liveWindows(), live: true };
|
|
79
|
+
throw new Error(`usage endpoint ${r.status}`);
|
|
80
|
+
}
|
|
50
81
|
const d = await r.json();
|
|
51
82
|
const win = (w, name) => (w && w.utilization != null)
|
|
52
83
|
? { name, usedPct: Math.round(w.utilization), resetsAt: w.resets_at || null, locked: w.locked_reason || null }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"trantor": "bin/cli.mjs"
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"zod": "^4.4.3"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
|
-
"test": "node bin/slop-gate.mjs --surface desktop/src && node test.mjs && node test-scenarios.mjs && node test-failure.mjs && node test-redelivery.mjs && node test-crew-completion.mjs && node test-contracts.mjs && node test-autonomy.mjs && node test-integrate.mjs && node test-handoff.mjs && node test-handoff-summarizer.mjs && node test-baton-turn-boundary.mjs && node test-agents.mjs && node test-update.mjs && node test-handoff-guard.mjs && node test-baton-latest.mjs && node test-balances.mjs && node test-subagent-cost.mjs && node test-inbox.mjs && node test-inflight.mjs && node test-notify.mjs && node test-focus.mjs && node test-cardlog.mjs && node test-checklist.mjs && node test-relay-note.mjs && node test-reaper.mjs && node test-events.mjs && node test-proposals.mjs && node test-scrub.mjs && node test-store-delta.mjs && node test-message-re.mjs && node test-claims.mjs && node test-adopt.mjs && node test-summarize.mjs && node test-identity-core.mjs && node test-identity.mjs && node test-identity-instances.mjs && node test-duty.mjs && node test-duty-dark.mjs && node test-duty-seat.mjs && node test-discovery.mjs && node test-doctor.mjs && node test-crew-env.mjs && node test-desktop-transport.mjs && node test-crew-worktree.mjs && bash test-crew-herdr.sh && npm --prefix desktop run test --silent && node test-splitbrain.mjs && node test-overseer.mjs && node test-overseer-lib.mjs && node test-overseer-warn.mjs && node test-provider-keys.mjs && node test-inbox-delivery.mjs && node test-identity-drift.mjs && node test-inbox-staleness.mjs && node test-seats.mjs && node test-seat-identity.mjs && node test-hub-routing.mjs && node test-hook-routing.mjs && node test-relay-wait.mjs && node test-dsh-seat.mjs && node test-kimi-bridge.mjs && node test-kimi-events.mjs && python3 engine/test-routing.py && node test-reconcile.mjs && node test-resources.mjs && node test-patrol.mjs && node test-bridge.mjs && bash test-crew.sh"
|
|
14
|
+
"test": "node bin/slop-gate.mjs --surface desktop/src && node test.mjs && node test-scenarios.mjs && node test-failure.mjs && node test-redelivery.mjs && node test-crew-completion.mjs && node test-contracts.mjs && node test-autonomy.mjs && node test-integrate.mjs && node test-handoff.mjs && node test-handoff-summarizer.mjs && node test-baton-turn-boundary.mjs && node test-agents.mjs && node test-update.mjs && node test-handoff-guard.mjs && node test-baton-latest.mjs && node test-balances.mjs && node test-usage-live.mjs && node test-subagent-cost.mjs && node test-inbox.mjs && node test-inflight.mjs && node test-notify.mjs && node test-focus.mjs && node test-cardlog.mjs && node test-checklist.mjs && node test-relay-note.mjs && node test-reaper.mjs && node test-events.mjs && node test-proposals.mjs && node test-scrub.mjs && node test-store-delta.mjs && node test-message-re.mjs && node test-claims.mjs && node test-adopt.mjs && node test-summarize.mjs && node test-identity-core.mjs && node test-identity.mjs && node test-identity-instances.mjs && node test-duty.mjs && node test-duty-dark.mjs && node test-duty-seat.mjs && node test-discovery.mjs && node test-doctor.mjs && node test-crew-env.mjs && node test-desktop-transport.mjs && node test-crew-worktree.mjs && bash test-crew-herdr.sh && npm --prefix desktop run test --silent && node test-splitbrain.mjs && node test-overseer.mjs && node test-overseer-lib.mjs && node test-overseer-warn.mjs && node test-provider-keys.mjs && node test-inbox-delivery.mjs && node test-identity-drift.mjs && node test-inbox-staleness.mjs && node test-seats.mjs && node test-seat-identity.mjs && node test-hub-routing.mjs && node test-hook-routing.mjs && node test-relay-wait.mjs && node test-dsh-seat.mjs && node test-kimi-bridge.mjs && node test-kimi-events.mjs && python3 engine/test-routing.py && node test-reconcile.mjs && node test-resources.mjs && node test-patrol.mjs && node test-bridge.mjs && bash test-crew.sh"
|
|
15
15
|
},
|
|
16
|
-
"description": "The hub-world for AI agent crews
|
|
16
|
+
"description": "The hub-world for AI agent crews — orchestrate Claude Code, Codex, GLM, Kimi, DeepSeek & any OpenRouter model as live crews with a plan-aware Advisor, a Kanban/flow command center, a testing gate, and an economics brain (Scrooge).",
|
|
17
17
|
"files": [
|
|
18
18
|
"hub.mjs",
|
|
19
19
|
"mcp.mjs",
|