trantor 0.17.7 → 0.17.8
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/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/hub.mjs +24 -2
- package/package.json +1 -1
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Trantor — the hub-world for AI agent crews: live message bus, presence, project Kanban/flow board + context-handoff for independent AI coding agents (Claude, Codex, Gemini, …)",
|
|
9
|
-
"version": "0.17.
|
|
9
|
+
"version": "0.17.8"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "trantor",
|
|
14
14
|
"source": "./",
|
|
15
15
|
"description": "The hub-world for AI agent crews. Say \"fire up the crew\" and Claude becomes the architect: a plan-aware Advisor routes the work (solo / cheap inline calls / live crew of Codex, Gemini, Kimi & DeepSeek in their own terminal windows), a Kanban/flow command center with a testing gate tracks it, and an economics brain (Scrooge) keeps the receipts. Includes the relay MCP, a SessionStart auto-discovery hook, and a PreCompact context-handoff so a fresh session can take over a full window instead of compacting.",
|
|
16
|
-
"version": "0.17.
|
|
16
|
+
"version": "0.17.8",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Sasha Bogojevic"
|
|
19
19
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.8",
|
|
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/hub.mjs
CHANGED
|
@@ -50,11 +50,11 @@ function scanTelemetry() {
|
|
|
50
50
|
|
|
51
51
|
// peers: { session: { lastSeen, status, project } } ; tasks: kanban cards
|
|
52
52
|
// projectMeta: { project: { brief, by, updated } } — the "what & why" blurb per project
|
|
53
|
-
let state = { messages: [], peers: {}, seq: 0, tasks: [], taskSeq: 0, projectMeta: {}, lessons: [], cardEvents: [] };
|
|
53
|
+
let state = { messages: [], peers: {}, seq: 0, tasks: [], taskSeq: 0, projectMeta: {}, lessons: [], cardEvents: [], cardEventsBackfilled: false };
|
|
54
54
|
try {
|
|
55
55
|
if (existsSync(DATA)) {
|
|
56
56
|
const loaded = JSON.parse(readFileSync(DATA, "utf8"));
|
|
57
|
-
state = { messages: loaded.messages || [], peers: {}, seq: loaded.seq || 0, tasks: loaded.tasks || [], taskSeq: loaded.taskSeq || 0, projectMeta: loaded.projectMeta || {}, lessons: loaded.lessons || [], cardEvents: Array.isArray(loaded.cardEvents) ? loaded.cardEvents : [] };
|
|
57
|
+
state = { messages: loaded.messages || [], peers: {}, seq: loaded.seq || 0, tasks: loaded.tasks || [], taskSeq: loaded.taskSeq || 0, projectMeta: loaded.projectMeta || {}, lessons: loaded.lessons || [], cardEvents: Array.isArray(loaded.cardEvents) ? loaded.cardEvents : [], cardEventsBackfilled: !!loaded.cardEventsBackfilled };
|
|
58
58
|
for (const [s, v] of Object.entries(loaded.peers || {})) // migrate old numeric form
|
|
59
59
|
state.peers[s] = typeof v === "number" ? { lastSeen: v, status: "", project: "" } : { lastSeen: v.lastSeen || 0, status: v.status || "", project: v.project || "" };
|
|
60
60
|
}
|
|
@@ -62,6 +62,28 @@ try {
|
|
|
62
62
|
let dirty = false;
|
|
63
63
|
const persist = () => { if (dirty) { try { writeFileSync(DATA, JSON.stringify(state)); dirty = false; } catch {} } };
|
|
64
64
|
setInterval(persist, 1000).unref?.();
|
|
65
|
+
// One-time backfill: reconstruct the cardEvents history log from each card's authoritative per-card
|
|
66
|
+
// `history` trail, so projects that existed BEFORE the cardEvents log show their FULL past in the
|
|
67
|
+
// TIMELINE view (not just events from now on). Guarded by a flag so it runs once where cardEvents
|
|
68
|
+
// persists; in team mode cardEvents is in-memory, so this re-derives from the persisted task.history
|
|
69
|
+
// on every boot — which is exactly right.
|
|
70
|
+
function backfillCardEvents() {
|
|
71
|
+
if (state.cardEventsBackfilled && state.cardEvents.length) return;
|
|
72
|
+
const events = [];
|
|
73
|
+
for (const t of (state.tasks || [])) for (const h of (t.history || [])) {
|
|
74
|
+
events.push({ ts: h.ts || 0, type: h.from ? "moved" : "created", taskId: t.id, project: t.project,
|
|
75
|
+
title: t.title, from: h.from || null, to: h.to || null, by: h.by || "",
|
|
76
|
+
difficulty: t.difficulty || null, assignee: t.assignee || null });
|
|
77
|
+
}
|
|
78
|
+
if (events.length) {
|
|
79
|
+
events.sort((a, b) => (a.ts || 0) - (b.ts || 0));
|
|
80
|
+
if (events.length > 5000) events.splice(0, events.length - 5000);
|
|
81
|
+
events.forEach((e, i) => { e.id = i + 1; });
|
|
82
|
+
state.cardEvents = events; dirty = true;
|
|
83
|
+
}
|
|
84
|
+
state.cardEventsBackfilled = true; dirty = true;
|
|
85
|
+
}
|
|
86
|
+
backfillCardEvents();
|
|
65
87
|
function prunePeers() {
|
|
66
88
|
const cutoff = now() - PEER_TTL_MS;
|
|
67
89
|
let removed = false;
|