trantor 0.17.93 → 0.17.95

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.17.93",
3
+ "version": "0.17.95",
4
4
  "description": "Trantor \u2014 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": {
@@ -187,9 +187,26 @@ function digest(turns, budget = 56_000) {
187
187
  return out;
188
188
  }
189
189
 
190
- function haveScrooge() {
191
- if (process.env.TRANTOR_NO_SCROOGE === "1") return false; // opt out (tests / no-LLM summary)
192
- try { execSync("command -v scrooge", { stdio: "ignore" }); return true; } catch { return false; }
190
+ // Where scrooge actually lives. `command -v` alone was the bug: it installs into ~/.local/bin,
191
+ // which is NOT on a default PATH, so every handoff written from a hook, launchd job or precompact
192
+ // silently failed the check and dumped raw transcript instead. Those are precisely the automatic
193
+ // paths, so the summarizer was missing exactly when nobody was watching. Resolve to an absolute
194
+ // path and exec THAT.
195
+ const SCROOGE_DIRS = [
196
+ join(homedir(), ".local", "bin"),
197
+ "/opt/homebrew/bin",
198
+ "/usr/local/bin",
199
+ "/usr/bin",
200
+ ];
201
+ function resolveScrooge() {
202
+ if (process.env.TRANTOR_NO_SCROOGE === "1") return ""; // opt out (tests / no-LLM summary)
203
+ if (process.env.TRANTOR_SCROOGE_BIN && existsSync(process.env.TRANTOR_SCROOGE_BIN)) return process.env.TRANTOR_SCROOGE_BIN;
204
+ try {
205
+ const p = execSync("command -v scrooge", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
206
+ if (p && existsSync(p)) return p;
207
+ } catch {}
208
+ for (const d of SCROOGE_DIRS) { const p = join(d, "scrooge"); if (existsSync(p)) return p; }
209
+ return "";
193
210
  }
194
211
 
195
212
  export function buildSummary(transcriptPath) {
@@ -198,14 +215,38 @@ export function buildSummary(transcriptPath) {
198
215
  try { convo = digest(collectTurns(transcriptPath)); } catch { convo = ""; }
199
216
  if (!convo) return "*(transcript unreadable)*";
200
217
  const sys = "You are writing a SESSION HANDOFF so a fresh Claude Code session can take over without losing context. The text spans an entire (possibly multi-hour) session: opening turns, an even sample of the middle, and the recent tail. Produce a concise but COMPLETE markdown handoff with these sections: TASK (what we're doing + the goal), STATE (done / in-progress), KEY DECISIONS, OPEN THREADS & NEXT STEPS (concrete actions), KEY FILES & locations (exact paths). Be specific. Cover the whole arc, not just the end. Do not pad.";
201
- if (haveScrooge()) {
218
+ // Cut the raw tail on a TURN boundary. A blind slice(-12000) opens mid-sentence, which is how the
219
+ // 2026-08-24 handoff began, and a successor cannot tell a truncated thought from a complete one.
220
+ const tail = (n) => {
221
+ // Only trim to a turn boundary when we ACTUALLY truncated. When the whole digest fits, trimming
222
+ // would throw away the session's opening — which is the part a successor needs most, and which
223
+ // test-handoff.mjs rightly insists on.
224
+ if (convo.length <= n) return convo;
225
+ const cut = convo.slice(-n);
226
+ const b = cut.indexOf("\n\n");
227
+ return b > 0 && b < 2000 ? cut.slice(b + 2) : cut;
228
+ };
229
+ // Say WHICH failure this was. One string for "not installed" and "the call died" meant nobody
230
+ // could tell a missing tool from a broken one, and the reason only ever reached stderr.
231
+ const degraded = (why) =>
232
+ `*(⚠️ DEGRADED HANDOFF — this is a raw transcript tail, not a written summary.*\n`
233
+ + `*Reason: ${why}. It may open mid-thought and it OMITS anything older than the tail;*\n`
234
+ + `*treat the project's memory files as the reliable record and re-read them before acting.)*\n\n${tail(12000)}`;
235
+
236
+ const bin = resolveScrooge();
237
+ if (bin) {
202
238
  try {
203
- return execSync(`scrooge -t summarize -d medium --system ${JSON.stringify(sys)}`, {
204
- input: convo, encoding: "utf8", timeout: 60_000, maxBuffer: 8 * 1024 * 1024,
205
- }).trim() || `*(empty summary raw recent tail)*\n\n${convo.slice(-8000)}`;
206
- } catch (e) { process.stderr.write(`[trantor] scrooge summarize failed: ${e?.message}\n`); }
239
+ // 29s observed summarizing a 56KB digest, so 60s left almost no headroom on a slow provider.
240
+ return execSync(`${JSON.stringify(bin)} -t summarize -d medium --system ${JSON.stringify(sys)}`, {
241
+ input: convo, encoding: "utf8", timeout: 180_000, maxBuffer: 8 * 1024 * 1024,
242
+ }).trim() || degraded("the summarizer returned nothing");
243
+ } catch (e) {
244
+ const why = `the summarizer failed: ${String(e?.message || e).slice(0, 200)}`;
245
+ process.stderr.write(`[trantor] scrooge summarize failed: ${e?.message}\n`);
246
+ return degraded(why);
247
+ }
207
248
  }
208
- return `*(no summarizer available representative transcript digest)*\n\n${convo.slice(-12000)}`;
249
+ return degraded("no summarizer is installed (scrooge was not found on PATH or in the usual locations)");
209
250
  }
210
251
 
211
252
  // The exact recent exchange, VERBATIM (not summarized/sampled) — so a baton-pass handoff carries the
package/hub.mjs CHANGED
@@ -388,7 +388,13 @@ function dutyTick() {
388
388
  const floor = now() - 24 * 3600 * 1000; // never escalate ancient history
389
389
  for (const m of state.messages) {
390
390
  if (m.ts > cutoff || m.ts < floor) continue;
391
- if (!m.to || m.to === "all" || m.to === DUTY_SESSION || m.from === "hub:duty") continue;
391
+ // `hub:*` is the hub's own pseudo-identity, not a session: nothing polls it and nothing ever
392
+ // will, so a message addressed there can never be "delivered". Escalating it is a category
393
+ // error that feeds itself — the duty seat acks the escalation to hub:duty, that ack is
394
+ // undelivered too, and since dutyEscalated prunes its oldest ids at 5,000 the same ones come
395
+ // back around. Reported from the seat as "a fresh identical echo every stop-hook cycle".
396
+ // Skipping the FROM side was already here; the TO side is the half that loops.
397
+ if (!m.to || m.to === "all" || m.to === DUTY_SESSION || m.from === "hub:duty" || m.to.startsWith("hub:")) continue;
392
398
  if (dutyEscalated.has(m.id)) continue;
393
399
  if ((state.peers[m.to]?.deliveredUpTo || 0) >= m.id) continue;
394
400
  dutyEscalated.add(m.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.17.93",
3
+ "version": "0.17.95",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "trantor": "bin/cli.mjs"
@@ -11,7 +11,7 @@
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-handoff.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-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-seat.mjs && node test-discovery.mjs && node test-doctor.mjs && 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-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-handoff.mjs && node test-handoff-summarizer.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-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-seat.mjs && node test-discovery.mjs && node test-doctor.mjs && 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
16
  "description": "The hub-world for AI agent crews \u2014 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": [