trantor 0.18.68 → 0.18.69

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.18.68",
3
+ "version": "0.18.69",
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": {
@@ -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 { writeFileSync, readFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
6
- import { join, basename } from "node:path";
7
- import { homedir, hostname } from "node:os";
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. Without it the only route was "compose a
14
- // fresh one on stdin", so a session that had just written a 5KB handoff had to write it again to
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 (the `trantor handoff` CLI path) so the two cannot diverge. The name comes from
19
- // the session's registration (TRANTOR_ORCH / RELAY_PROJECT / orch-sessions.txt) before the cwd;
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
- // The manual skill path opens the §5 ledger with WRITTEN like every other writer (#5642), and
63
- // carries the same interface the hooks-side records have: transcript_path ("" — the summary IS
64
- // the model's own words), mode (attended|unattended, #5648).
65
- const rec = { id: `${name}-${stamp}`, project, projectName: name, machine: hostname(), trigger: baton ? "manual-baton" : "manual-skill", stamp: Number(stamp) || 0, summary: summary.trim() || "(empty)", transcript_path: "", mode: handoffMode(name), gitStatus: git, consumed: false, states: [{ state: "written", ts: Number(stamp) || 0, by: baton ? "manual-baton" : "manual-skill" }] };
66
- // The structured working state rides beside the prose (TDD §4.5), dark behind
67
- // TRANTOR_STATE_HANDOFF. The manual path gets it for the same reason the hook path does: this
68
- // record is what the successor loads, and `summary` here IS the model's own handoff — the richest
69
- // STATE block there is.
70
- const seat = resolveSeat(name);
71
- attachState(rec, { project: name, seat, card: resolveHandoffCard({ projectName: name, seat }), worktree: project });
72
- const file = join(dir, `${rec.id}.json`);
73
- writeFileSync(file, JSON.stringify(rec, null, 2));
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) {
@@ -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
- // If one exists (<15min, unconsumed), point the baton at THAT and write nothing — the caller's
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 {}
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-06";
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
- sonnet: { in: 3, out: 15, cw5m: 3.75, cw1h: 6, cr: 0.3 },
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
- "opus-4-1": { in: 15, out: 75, cw5m: 18.75, cw1h: 30, cr: 1.5 }, // legacy numbering — far pricier
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.includes("opus")) return "opus";
20
- if (m.includes("sonnet")) return "sonnet";
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.18.68",
3
+ "version": "0.18.69",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "trantor": "bin/cli.mjs"