trantor 0.18.76 → 0.18.77

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.76",
3
+ "version": "0.18.77",
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/agents.mjs CHANGED
@@ -1,16 +1,8 @@
1
1
  #!/usr/bin/env node
2
- // trantor agents [sessionId] [--json] — the LIVE sub-agent manifest for a session.
3
- //
4
- // What were this session's sub-agents (Agent/Task tool, Workflow swarms, agent-teams) doing —
5
- // what was each tasked with, did it return, what did it write, and do those files still survive
6
- // on disk? Derived fresh from the on-disk transcripts every run (so it reflects CURRENT disk,
7
- // catching files an agent finished that were later clobbered — the 2026-06-21 kill corrupted a
8
- // completed 30KB lib down to a 17-byte stub).
9
- //
10
- // trantor agents → the session of the newest handoff for THIS project (the
11
- // predecessor a fresh session is taking over from)
12
- // trantor agents <sessionId> → that specific session
13
- // trantor agents --json → structured manifest (for tools)
2
+ // trantor agents [sessionId] [--json] [--since <epochMs>] — the LIVE sub-agent manifest for a
3
+ // session: what was each sub-agent tasked with, did it return, what did it write, did it survive
4
+ // on disk — re-derived fresh from the transcripts every run. `--since <epochMs>` (#10007) reads
5
+ // agents launched before that instant "stale", never "in-flight"; the app polls this for liveness.
14
6
  import { readFileSync, readdirSync, existsSync } from "node:fs";
15
7
  import { join, basename } from "node:path";
16
8
  import { homedir } from "node:os";
@@ -18,7 +10,10 @@ import { deriveSubagentManifest, formatSubagentManifest, resolveTranscriptForSid
18
10
 
19
11
  const args = process.argv.slice(2);
20
12
  const json = args.includes("--json");
21
- const sid = args.find((a) => !a.startsWith("--"));
13
+ const sinceIdx = args.indexOf("--since");
14
+ const sinceVal = sinceIdx !== -1 ? args[sinceIdx + 1] : undefined;
15
+ const sinceMs = sinceVal != null ? Number(sinceVal) : undefined;
16
+ const sid = args.find((a) => !a.startsWith("--") && a !== sinceVal);
22
17
 
23
18
  const HANDOFF_DIR = join(process.env.RELAY_DATA_DIR || join(homedir(), ".agent-bus"), "handoffs");
24
19
 
@@ -55,7 +50,7 @@ if (sid) {
55
50
  projectRoot = h.project || projectRoot;
56
51
  }
57
52
 
58
- const manifest = deriveSubagentManifest(transcript, { projectRoot });
53
+ const manifest = deriveSubagentManifest(transcript, { projectRoot, sinceMs: Number.isFinite(sinceMs) ? sinceMs : undefined });
59
54
  if (json) {
60
55
  process.stdout.write(JSON.stringify(manifest, null, 2) + "\n");
61
56
  } else {
@@ -68,11 +68,12 @@ function reconcileFile(absPath, agentBytes, projectRoot) {
68
68
  return { path: shortPath(absPath, projectRoot), agentBytes, onDiskNow, suspect };
69
69
  }
70
70
 
71
- function analyzeAgent(meta, completedIds, projectRoot) {
71
+ function analyzeAgent(meta, completedIds, projectRoot, sinceMs) {
72
72
  const wrote = new Map(); // absPath → last-write byte length
73
- let lastMs = 0, result = "";
73
+ let lastMs = 0, launchMs = 0, result = "";
74
74
  for (const r of parseLines(meta.jsonlPath)) {
75
75
  const ts = r.timestamp ? Date.parse(r.timestamp) || 0 : 0;
76
+ if (!launchMs && ts) launchMs = ts; // jsonl is append-ordered: first timestamped line = launch
76
77
  if (ts > lastMs) lastMs = ts;
77
78
  const c = r?.message?.content;
78
79
  if (!Array.isArray(c)) continue;
@@ -84,13 +85,22 @@ function analyzeAgent(meta, completedIds, projectRoot) {
84
85
  if (b?.type === "text") { const text = asString(b.text)?.trim(); if (text) result = text; }
85
86
  }
86
87
  }
87
- const status = meta.toolUseId && completedIds.has(meta.toolUseId) ? "completed" : "in-flight";
88
+ // #10007 liveness: "in-flight" means launched and never returned a result. But an agent whose
89
+ // launch predates the session's CURRENT process (sinceMs = process start) can never return —
90
+ // its turn died with the old process — so it reads "stale" and never counts as live.
91
+ const returned = meta.toolUseId && completedIds.has(meta.toolUseId);
92
+ let status = returned ? "completed" : "in-flight";
93
+ if (!returned) {
94
+ if (!launchMs) { try { launchMs = statSync(meta.jsonlPath).birthtimeMs || 0; } catch {} }
95
+ if (sinceMs != null && launchMs < sinceMs) status = "stale";
96
+ }
88
97
  return {
89
98
  name: meta.name || basename(meta.jsonlPath).replace(/\.jsonl$/, ""),
90
99
  agentType: meta.agentType || "",
91
100
  task: meta.description || "",
92
101
  workflow: meta.workflow || null,
93
102
  status,
103
+ launchMs: launchMs || null,
94
104
  wrote: [...wrote.entries()].map(([p, n]) => reconcileFile(p, n, projectRoot)),
95
105
  lastActivity: lastMs ? new Date(lastMs).toISOString() : null,
96
106
  lastActivityMs: lastMs,
@@ -99,10 +109,12 @@ function analyzeAgent(meta, completedIds, projectRoot) {
99
109
  };
100
110
  }
101
111
 
102
- // Derive the full manifest for a session from its parent transcript path. projectRoot (the repo
103
- // dir) is used only to shorten displayed file paths. Returns { sessionId, subagents[], counts }.
104
- export function deriveSubagentManifest(parentTranscript, { projectRoot } = {}) {
105
- const out = { sessionId: "", subagents: [], counts: { total: 0, completed: 0, inFlight: 0, suspectFiles: 0 } };
112
+ // Derive the full manifest for a session from its parent transcript path. projectRoot shortens
113
+ // displayed file paths. sinceMs (epoch ms, #10007) marks agents launched before it "stale" instead
114
+ // of "in-flight" — pass the session process's start when the answer feeds a LIVE claim (ACTIVE
115
+ // NOW), never for a handoff record. Returns { sessionId, subagents[], counts }.
116
+ export function deriveSubagentManifest(parentTranscript, { projectRoot, sinceMs } = {}) {
117
+ const out = { sessionId: "", subagents: [], counts: { total: 0, completed: 0, inFlight: 0, stale: 0, suspectFiles: 0 } };
106
118
  try {
107
119
  if (!parentTranscript || !existsSync(parentTranscript)) return out;
108
120
  const sid = basename(parentTranscript).replace(/\.jsonl$/i, "");
@@ -118,12 +130,13 @@ export function deriveSubagentManifest(parentTranscript, { projectRoot } = {}) {
118
130
  }
119
131
 
120
132
  const metas = collectMetas(subdir);
121
- out.subagents = metas.map((m) => analyzeAgent(m, completedIds, projectRoot))
133
+ out.subagents = metas.map((m) => analyzeAgent(m, completedIds, projectRoot, sinceMs))
122
134
  .sort((a, b) => (a.lastActivityMs || 0) - (b.lastActivityMs || 0));
123
135
 
124
136
  out.counts.total = out.subagents.length;
125
137
  out.counts.completed = out.subagents.filter((s) => s.status === "completed").length;
126
138
  out.counts.inFlight = out.subagents.filter((s) => s.status === "in-flight").length;
139
+ out.counts.stale = out.subagents.filter((s) => s.status === "stale").length;
127
140
  out.counts.suspectFiles = out.subagents.reduce((n, s) => n + s.wrote.filter((w) => w.suspect).length, 0);
128
141
  } catch {}
129
142
  return out;
@@ -140,7 +153,9 @@ export function formatSubagentManifest(m, { heading = true } = {}) {
140
153
  }
141
154
  }
142
155
  for (const s of m.subagents) {
143
- const badge = s.status === "completed" ? "✅ completed" : "🛑 IN-FLIGHT at handoff";
156
+ const badge = s.status === "completed" ? "✅ completed"
157
+ : s.status === "stale" ? "⏸ never returned, and launched before the current session process — stale"
158
+ : "🛑 IN-FLIGHT at handoff";
144
159
  L.push("");
145
160
  L.push(`• ${s.name} [${s.agentType}]${s.workflow ? ` · wf:${s.workflow}` : ""} ${badge}`);
146
161
  if (s.task) L.push(` task: ${s.task}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.18.76",
3
+ "version": "0.18.77",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "trantor": "bin/cli.mjs"