trantor 0.18.65 → 0.18.67

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.65",
3
+ "version": "0.18.67",
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": {
@@ -1014,14 +1014,32 @@ async function loadLessons() {
1014
1014
  // #5683: every prompt section is capped (bin/crew-payload.mjs) and the payload has ONE hard total
1015
1015
  // cap; below the caps the composition is byte-identical to the old concatenation.
1016
1016
  function composedTurn({ base = "", wakeText = "", ctxText = "", againText = "", tailText = "", rulesText = "", lessons = null }) {
1017
+ // STABLE FIRST, VOLATILE LAST — this order is a COST decision, not a stylistic one (#8199).
1018
+ // Every provider we send this to caches on an exact prefix match from token 0: DeepSeek's docs say
1019
+ // "only requests with identical prefixes (starting from the 0th token) will be considered
1020
+ // duplicates, and partial matches in the middle of the input will not trigger a cache hit", and a
1021
+ // hit bills at roughly a tenth of a miss. This array used to open with `base` and then the
1022
+ // per-turn WAKE, with the big unchanging blocks (RULES, lessons) buried at the end. On an ordinary
1023
+ // turn `base` is empty — the sha rides inside `again` — so token 0 was the wake text itself, which
1024
+ // is different every turn by definition. No cache could ever hit, and ~4,800 tokens of identical preamble were re-bought at full rate per turn,
1025
+ // on every foreign-CLI seat: dsh, codex, kimi and every opencode/BYOM seat. The state path had
1026
+ // known this for weeks — lib/state/assemble.mjs: "the bytes before STATE_DELIM are the caller's
1027
+ // preamble and NOTHING else, or prefix caching never engages" — and it was never carried across.
1028
+ //
1029
+ // Reading order improves with it rather than suffering: rules and lessons are the standing brief,
1030
+ // then context and history, and the ASK lands last, which is what #7063 found a work order wants.
1031
+ //
1032
+ // The `order` field is UNRELATED to this and deliberately unchanged: it ranks what gets trimmed
1033
+ // when the payload busts its total cap, and wake is trimmed last there because losing the
1034
+ // instruction is worse than losing the cache on that one turn.
1017
1035
  const built = composePrompt([
1018
- { name: "base", text: base },
1019
- { name: "wake", text: wakeText, trim: "truncate", order: 4 },
1020
- { name: "ctx", text: ctxText, trim: "drop", order: 1 },
1021
- { name: "again", text: againText },
1022
- { name: "tail", text: tailText },
1023
1036
  { name: "rules", text: rulesText, trim: "drop", order: 3 },
1024
1037
  { name: "lessons", text: lessons?.text || "", trim: "drop", order: 2 },
1038
+ { name: "ctx", text: ctxText, trim: "drop", order: 1 },
1039
+ { name: "tail", text: tailText },
1040
+ { name: "again", text: againText },
1041
+ { name: "base", text: base },
1042
+ { name: "wake", text: wakeText, trim: "truncate", order: 4 },
1025
1043
  ]);
1026
1044
  const parts = built.sections.filter(s => s.chars).map(s => `${s.name} ${s.chars.toLocaleString("en-US")}c`).join(" · ");
1027
1045
  const lessonsNote = lessons && lessons.total ? ` (lessons ${lessons.kept}/${lessons.total})` : "";
@@ -751,33 +751,23 @@ export function maybeSpawn(projectDir, conf = readConfig(), handoffFile = "", de
751
751
  try {
752
752
  if (_platform !== "darwin") return false;
753
753
  if (_env.TRANTOR_NO_HANDOFF_SPAWN === "1") return false;
754
- // #8089: a pane session gets NO Terminal window but it does get a successor. This used to
755
- // return false on the theory that "the pane claims the handoff (trantor open) on its own",
756
- // and for an ARMED baton nothing was driving that: /trantor:handoff always runs inside a turn,
757
- // so it always arms, so this is always the path taken and spawnPaneBaton, which the direct
758
- // path (spawnBaton) calls right here, was never reached. The record was written and the session
759
- // sat there. Witnessed on crebral-health 2026-09-19: written 00:18:58, unclaimed, original alive.
760
- const paneId = _pane(_env);
761
- if (paneId) {
762
- if (!handoffFile) {
763
- _log(`[trantor] pane ${paneId} needs the handoff file to pass the baton and none was given no successor opened\n`);
764
- return false;
765
- }
766
- const ok = _spawnPane(projectDir, handoffFile, paneId);
767
- _log(`[trantor] herdr pane ${paneId}: ${ok ? "baton driver spawned — it replaces this pane in place" : "baton driver FAILED to spawn — no successor"}\n`);
768
- return ok;
754
+ // #8089 REVERTED 2026-09-19, and the revert is the point. A pane session gets NO Terminal
755
+ // window AND no baton driver from here: the APP owns the replacement. app handoff_now runs
756
+ // `trantor handoff --write-only`, waits for the record (including the armed-mid-turn case it
757
+ // explicitly handles), then does its OWN idle gate, kill and reopen (#6081, #5509). Making this
758
+ // spawn a pane baton put a SECOND driver on the same pane, racing the app's — which broke the
759
+ // chat handoff button and the skill, both of which had been working. The stranding this was
760
+ // meant to fix is real but lives in the SKILL path, where no app driver exists; fixing it here
761
+ // cannot tell the two flows apart, so it must be fixed where the flows are distinguishable.
762
+ if (_pane(_env)) {
763
+ _log(`[trantor] session lives in herdr pane ${_pane(_env)} no Terminal window and no baton driver from here; the app (or the skill path) owns the replacement\n`);
764
+ return false;
769
765
  }
770
766
  if (conf.autoHandoffPrompt === false) return false;
771
767
  if (_hasPane(basename(projectDir))) {
772
- // Same correction as above for the cwd-keyed pane: drive the replacement, do not assume
773
- // something else will. Without HERDR_PANE_ID the driver resolves the pane from crew-windows.
774
- if (!handoffFile) {
775
- _log(`[trantor] orch pane hosts ${basename(projectDir)} but no handoff file was given — no successor opened\n`);
776
- return false;
777
- }
778
- const ok = _spawnPane(projectDir, handoffFile);
779
- _log(`[trantor] orch pane hosts ${basename(projectDir)}: ${ok ? "baton driver spawned — it replaces the pane in place" : "baton driver FAILED to spawn — no successor"}\n`);
780
- return ok;
768
+ _log(`[trantor] orch pane hosts ${basename(projectDir)} no Terminal window; the app or the skill path claims the handoff
769
+ `);
770
+ return false;
781
771
  }
782
772
  const script = join(HERE, "..", "..", "bin", "handoff-prompt.sh");
783
773
  if (!existsSync(script)) { _log(`[trantor] handoff-prompt.sh missing\n`); return false; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.18.65",
3
+ "version": "0.18.67",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "trantor": "bin/cli.mjs"