trantor 0.18.68 → 0.18.70
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/baton.mjs +11 -20
- package/bin/write-handoff.mjs +20 -26
- package/hooks/lib/handoff.mjs +13 -4
- package/hooks/pricing.mjs +14 -6
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.70",
|
|
4
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": {
|
package/bin/baton.mjs
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
// a whole-session handoff (auto-summary + verbatim in-flight tail), opens a fresh self-announcing
|
|
4
4
|
// session, and closes THIS window once it takes over. Run from inside the session you want to hand
|
|
5
5
|
// off. (The richer MODEL-authored handoff is the /trantor:handoff skill.)
|
|
6
|
-
import { readdirSync, statSync
|
|
6
|
+
import { readdirSync, statSync } from "node:fs";
|
|
7
7
|
import { join, basename, dirname } from "node:path";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
-
import { writeHandoff, spawnBaton, resolveHandoffSurface, armBaton, contextUsage, controllingTty, turnInFlight, armMaxMs, sessionProcessState } from "../hooks/lib/handoff.mjs";
|
|
11
|
+
import { writeHandoff, spawnBaton, resolveHandoffSurface, armBaton, contextUsage, controllingTty, turnInFlight, armMaxMs, sessionProcessState, stdinCarriesMarkdown } from "../hooks/lib/handoff.mjs";
|
|
12
12
|
|
|
13
13
|
// #6074: the skill path (write-handoff.mjs) and this CLI path must share ONE resolution of which
|
|
14
14
|
// project this is and where the session lives. Both call resolveHandoffSurface; the name comes
|
|
@@ -22,18 +22,11 @@ const project = resolved.project;
|
|
|
22
22
|
// cwd that cd'd somewhere else: a handoff record must carry the transcript it was written from.
|
|
23
23
|
const sessionDir = resolved.projectDir;
|
|
24
24
|
|
|
25
|
-
// Model-authored handoffs ride THIS binary too
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
// character devices, so a plain `trantor handoff` typed at a prompt (or run by a hook with stdin
|
|
31
|
-
// at /dev/null) keeps the auto-summary behavior. Detection must NOT be `!process.stdin.isTTY` —
|
|
32
|
-
// that misreads /dev/null as a handoff and errors where auto-summary used to work.
|
|
33
|
-
function stdinIsPipe() {
|
|
34
|
-
try { return fstatSync(0).isFIFO(); } catch { return false; }
|
|
35
|
-
}
|
|
36
|
-
if (stdinIsPipe() || process.argv.includes("--latest")) {
|
|
25
|
+
// Model-authored handoffs ride THIS binary too — always the global install's CURRENT code, never
|
|
26
|
+
// the plugin-cache copy a session booted with (the stale-0.18.20 bug). Markdown on stdin means
|
|
27
|
+
// "here is the handoff", so forward to write-handoff.mjs in the SAME package; --latest forwards too.
|
|
28
|
+
// What counts as markdown, and why /dev/null must not: stdinCarriesMarkdown in hooks/lib (#8459).
|
|
29
|
+
if (stdinCarriesMarkdown() || process.argv.includes("--latest")) {
|
|
37
30
|
const helper = join(dirname(fileURLToPath(import.meta.url)), "write-handoff.mjs");
|
|
38
31
|
const child = spawn(process.execPath, [helper, ...process.argv.slice(2)], { stdio: ["inherit", "inherit", "inherit"] });
|
|
39
32
|
child.on("exit", (c) => process.exit(c ?? 1));
|
|
@@ -64,12 +57,10 @@ function autoBaton() {
|
|
|
64
57
|
// The transcript's filename IS the writing session's id — record it, or an orchestrator-thread
|
|
65
58
|
// handoff carries no writer and the baton-hold + map-follow logic in sessionstart.mjs can't fire.
|
|
66
59
|
const sessionId = transcript ? basename(transcript, ".jsonl") : "";
|
|
67
|
-
// #6528: WHO pulled the trigger. A TTY stdin
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
// --reason rides through from the app (`--reason clicked|countdown|unattended`) so the RECORD
|
|
72
|
-
// finally names the real trigger instead of laundering every banner fire into "manual-cli".
|
|
60
|
+
// #6528: WHO pulled the trigger. A TTY stdin is a human typing it; the app chain (handoff_now)
|
|
61
|
+
// and hooks spawn this with piped stdio. Only the typed command keeps the storm-guard bypass
|
|
62
|
+
// (manual = intentional); every invoked path goes through the boundary gate like an auto handoff.
|
|
63
|
+
// --reason rides through from the app so the record names the real trigger, not "manual-cli".
|
|
73
64
|
const reasonArg = (() => {
|
|
74
65
|
const i = process.argv.indexOf("--reason");
|
|
75
66
|
const v = i >= 0 ? String(process.argv[i + 1] || "").trim() : "";
|
package/bin/write-handoff.mjs
CHANGED
|
@@ -2,23 +2,18 @@
|
|
|
2
2
|
// Save a model-authored handoff (piped on stdin) for this project; the next session auto-loads it.
|
|
3
3
|
// With --baton: ALSO open a fresh self-announcing session and close THIS window once it takes over
|
|
4
4
|
// (the one-command manual baton behind /trantor:handoff). Without it: just write the file (legacy).
|
|
5
|
-
import {
|
|
6
|
-
import { join
|
|
7
|
-
import {
|
|
8
|
-
import { execSync } from "node:child_process";
|
|
9
|
-
import { spawnBaton, handoffMode, resolveHandoffSurface, attachState, resolveSeat, resolveHandoffCard } from "../hooks/lib/handoff.mjs";
|
|
5
|
+
import { readFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { writeHandoff, spawnBaton, resolveHandoffSurface } from "../hooks/lib/handoff.mjs";
|
|
10
8
|
import { handoffDir } from "../lib/project.mjs";
|
|
11
9
|
|
|
12
10
|
const baton = process.argv.includes("--baton");
|
|
13
|
-
// --latest: pass the baton on a handoff ALREADY on disk
|
|
14
|
-
//
|
|
15
|
-
// hand it over — 2m28s of regenerated prose on a live scribe session, 2026-08-24.
|
|
11
|
+
// --latest: pass the baton on a handoff ALREADY on disk — composing it again cost 2m28s of
|
|
12
|
+
// regenerated prose on a live scribe session (drill: test/handoff/test-baton-latest.mjs).
|
|
16
13
|
const latest = process.argv.includes("--latest");
|
|
17
14
|
// #6074: ONE resolver for which project this is and where the session lives — shared with
|
|
18
|
-
// bin/baton.mjs
|
|
19
|
-
//
|
|
20
|
-
// the witnessed crebral-scribe/ios handoff recorded project "ios" from a subfolder cwd and never
|
|
21
|
-
// found its pane.
|
|
15
|
+
// bin/baton.mjs so the two cannot diverge. The name comes from the registration
|
|
16
|
+
// (TRANTOR_ORCH / RELAY_PROJECT / orch-sessions.txt) before the cwd; a subfolder cwd never renames the project.
|
|
22
17
|
const resolved = resolveHandoffSurface({ sessionId: process.env.CLAUDE_SESSION_ID || "" });
|
|
23
18
|
const project = resolved.projectDir;
|
|
24
19
|
const name = resolved.project;
|
|
@@ -37,8 +32,6 @@ if (!latest) {
|
|
|
37
32
|
// directly, so an AGENT_BUS_DIR install wrote where nothing would look.
|
|
38
33
|
const dir = handoffDir();
|
|
39
34
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
40
|
-
const stamp = (() => { try { return execSync("date +%s", { encoding: "utf8" }).trim(); } catch { return String(process.pid); } })();
|
|
41
|
-
let git = ""; try { git = execSync("git -C " + JSON.stringify(project) + " status --short 2>/dev/null | head -30", { encoding: "utf8" }).trim(); } catch {}
|
|
42
35
|
// --latest short-circuits everything below: find this project's newest UNCONSUMED handoff and hand
|
|
43
36
|
// that over, untouched.
|
|
44
37
|
if (latest) {
|
|
@@ -59,18 +52,19 @@ if (latest) {
|
|
|
59
52
|
process.exit(0);
|
|
60
53
|
}
|
|
61
54
|
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
// the
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
55
|
+
// ONE WRITER (#8263): every guarantee CONTRACT-hooks states for "a handoff record" — the read-first
|
|
56
|
+
// floor, verifyGates, the sub-agent manifest, supersedeOlderHandoffs, uncapped persistence — lives
|
|
57
|
+
// in writeHandoff; the literal record assembled here reached none of them. Only trigger,
|
|
58
|
+
// transcript_path "" and force (the command IS the intent) differ.
|
|
59
|
+
const { file } = writeHandoff({
|
|
60
|
+
projectDir: project,
|
|
61
|
+
projectName: name,
|
|
62
|
+
sessionId: "",
|
|
63
|
+
transcript: "",
|
|
64
|
+
trigger: baton ? "manual-baton" : "manual-skill",
|
|
65
|
+
summary: summary.trim(),
|
|
66
|
+
force: true,
|
|
67
|
+
});
|
|
74
68
|
console.log(`handoff saved: ${file}`);
|
|
75
69
|
|
|
76
70
|
if (baton) {
|
package/hooks/lib/handoff.mjs
CHANGED
|
@@ -649,11 +649,12 @@ export function writeHandoff({ projectDir, sessionId, transcript, trigger, summa
|
|
|
649
649
|
else if (r.json && r.json.allow === false) return { skipped: true, reason: r.json.reason || "storm-guard", sinceSec: r.json.sinceSec };
|
|
650
650
|
}
|
|
651
651
|
if (!existsSync(HANDOFF_DIR)) mkdirSync(HANDOFF_DIR, { recursive: true });
|
|
652
|
-
// #5648: an automatic digest must never recompose+supersede a FRESH model-authored handoff
|
|
653
|
-
//
|
|
654
|
-
// spawn path proceeds on the authored handoff exactly as if it had just written it.
|
|
652
|
+
// #5648: an automatic digest must never recompose+supersede a FRESH model-authored handoff —
|
|
653
|
+
// if one exists (<15min, unconsumed), point the baton at THAT and write nothing, so the caller's
|
|
654
|
+
// spawn path proceeds on the authored handoff exactly as if it had just written it. #8263: the
|
|
655
|
+
// defer keys on the TRIGGER — manual-skill/manual-baton IS the model's newer words and must land.
|
|
655
656
|
const fresh = freshAuthoredHandoff(projectName);
|
|
656
|
-
if (fresh) return { deferred: true, file: join(HANDOFF_DIR, `${fresh.id}.json`), record: fresh };
|
|
657
|
+
if (fresh && !MANUAL_TRIGGERS.includes(trigger)) return { deferred: true, file: join(HANDOFF_DIR, `${fresh.id}.json`), record: fresh };
|
|
657
658
|
const stamp = nowSec() || Date.now();
|
|
658
659
|
let gitStatus = "";
|
|
659
660
|
try { gitStatus = execSync("git -C " + JSON.stringify(projectDir) + " status --short 2>/dev/null | head -30", { encoding: "utf8" }).trim(); } catch {}
|
|
@@ -887,6 +888,14 @@ export function pathsReadIn(transcriptPath, paths) {
|
|
|
887
888
|
return { read, missed: want.filter(p => !read.includes(p)) };
|
|
888
889
|
}
|
|
889
890
|
|
|
891
|
+
/** Markdown on stdin, or a bare `trantor handoff`? A heredoc is a TEMP FILE, not a FIFO, and an
|
|
892
|
+
* isFIFO()-only check discarded the model's handoff while printing "handoff armed" (#8459).
|
|
893
|
+
* Character devices stay out: that is the TTY and the /dev/null a hook runs with.
|
|
894
|
+
*/
|
|
895
|
+
export function stdinCarriesMarkdown(fd = 0) {
|
|
896
|
+
try { const st = fstatSync(fd); return st.isFIFO() || st.isFile(); } catch { return false; }
|
|
897
|
+
}
|
|
898
|
+
|
|
890
899
|
// The self-announcing fresh session command (single-quoted so it survives osascript→shell un-escaped).
|
|
891
900
|
// Brevity is the point, but #8162 found it had become permission not to READ: the summary is injected
|
|
892
901
|
// at SessionStart, so a 3-sentence recap is producible without opening a file. The order is now
|
package/hooks/pricing.mjs
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
// trantor — Anthropic API pricing, the single source of truth for the NOTIONAL dollar cost of a
|
|
2
2
|
// Claude Code sub-agent's token usage (it's "notional" because on a Pro/Max plan those tokens are
|
|
3
3
|
// plan-covered, not billed — we still want the number). $/MTok. Re-confirm before trusting old reports.
|
|
4
|
-
export const PRICING_AS_OF = "2026-
|
|
4
|
+
export const PRICING_AS_OF = "2026-09";
|
|
5
5
|
|
|
6
6
|
// in/out = input/output; cw5m/cw1h = cache-WRITE at the 5-minute / 1-hour TTL; cr = cache-READ.
|
|
7
7
|
// (cache write 5m = 1.25× input, 1h = 2× input, read = 0.1× input — encoded directly here.)
|
|
8
8
|
export const ANTHROPIC_PRICING = {
|
|
9
|
-
opus: { in: 5, out: 25, cw5m: 6.25, cw1h: 10, cr: 0.5 },
|
|
10
|
-
|
|
9
|
+
opus: { in: 5, out: 25, cw5m: 6.25, cw1h: 10, cr: 0.5 }, // Opus 5 / 4.8 / 4.7 / 4.6
|
|
10
|
+
"opus-5-5": { in: 4, out: 20, cw5m: 5, cw1h: 8, cr: 0.2 }, // cheaper than Opus 5, and its cache read is 0.05× input, not 0.1×
|
|
11
|
+
sonnet: { in: 2, out: 10, cw5m: 2.5, cw1h: 4, cr: 0.2 }, // Sonnet 5
|
|
12
|
+
"sonnet-4": { in: 3, out: 15, cw5m: 3.75, cw1h: 6, cr: 0.3 }, // Sonnet 4 / 4.5 / 4.6
|
|
11
13
|
haiku: { in: 1, out: 5, cw5m: 1.25, cw1h: 2, cr: 0.1 },
|
|
12
|
-
|
|
14
|
+
fable: { in: 10, out: 50, cw5m: 12.5, cw1h: 20, cr: 0.25 }, // Fable 5 / 5.1, cache read 0.025× input
|
|
15
|
+
"opus-4-1": { in: 15, out: 75, cw5m: 18.75, cw1h: 30, cr: 1.5 }, // legacy numbering — far pricier
|
|
13
16
|
};
|
|
14
17
|
|
|
15
18
|
// Map a transcript model id ("claude-opus-4-8", "claude-sonnet-4-6", …) to a price tier, or null.
|
|
19
|
+
// MOST SPECIFIC FIRST: a generic includes("opus") priced the default Opus 5.5 off the Opus 5 tier (#8443).
|
|
20
|
+
// An unrecognised version stays null — costUsd:null has a reporting path, a wrong dollar figure does not.
|
|
16
21
|
export function tierFor(model) {
|
|
17
22
|
const m = String(model || "").toLowerCase();
|
|
18
23
|
if (/opus-4-1\b|opus-4\.1/.test(m)) return "opus-4-1";
|
|
19
|
-
if (m
|
|
20
|
-
if (
|
|
24
|
+
if (/opus-5-5\b|opus-5\.5/.test(m)) return "opus-5-5";
|
|
25
|
+
if (/opus-(5|4-8|4-7|4-6|4-5)\b/.test(m)) return "opus";
|
|
26
|
+
if (/sonnet-4(-[0-9]+)?\b/.test(m)) return "sonnet-4";
|
|
27
|
+
if (/sonnet-5\b/.test(m)) return "sonnet";
|
|
28
|
+
if (m.includes("fable") || m.includes("mythos")) return "fable";
|
|
21
29
|
if (m.includes("haiku")) return "haiku";
|
|
22
30
|
return null; // unknown → caller emits costUsd:null + a costNote rather than guessing
|
|
23
31
|
}
|