trantor 0.17.10 → 0.17.11
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 +14 -0
- package/package.json +1 -1
- package/ui.html +54 -0
|
@@ -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.11"
|
|
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.11",
|
|
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.11",
|
|
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
|
@@ -237,6 +237,20 @@ const server = http.createServer(async (req, res) => {
|
|
|
237
237
|
const events = (q.project ? state.cardEvents.filter(e => e.project === q.project) : state.cardEvents).slice(-limit);
|
|
238
238
|
return json(res, 200, { events });
|
|
239
239
|
}
|
|
240
|
+
// A single card's FULL story for the detail panel: the card itself, its status events, and the
|
|
241
|
+
// bus messages that reference it (#<id>) — i.e. the agent's own reports of what it did, why, how.
|
|
242
|
+
if (req.method === "GET" && P === "/card") {
|
|
243
|
+
const id = Number(q.id);
|
|
244
|
+
if (!Number.isInteger(id)) return json(res, 400, { error: "numeric id required" });
|
|
245
|
+
const task = state.tasks.find(t => t.id === id) || null;
|
|
246
|
+
const events = state.cardEvents.filter(e => e.taskId === id);
|
|
247
|
+
const re = new RegExp("#" + id + "(?![0-9])"); // #5 but not #50
|
|
248
|
+
const messages = state.messages.filter(m => re.test(String(m.text || ""))).slice(-200);
|
|
249
|
+
// fall back to the last event for title/project/assignee when the card was deleted
|
|
250
|
+
const last = events[events.length - 1];
|
|
251
|
+
const meta = task || (last ? { id, title: last.title, project: last.project, status: "deleted", assignee: last.assignee, difficulty: last.difficulty } : null);
|
|
252
|
+
return json(res, 200, { task: meta, events, messages });
|
|
253
|
+
}
|
|
240
254
|
if (req.method === "POST" && P === "/project") { // set a project's brief (what & why)
|
|
241
255
|
const b = await body(req); const k = String(b.project || "").slice(0, 80);
|
|
242
256
|
if (!k) return json(res, 400, { error: "project required" });
|
package/package.json
CHANGED
package/ui.html
CHANGED
|
@@ -162,6 +162,26 @@ main:not(.learn-open) .learn-body{display:none}
|
|
|
162
162
|
.tlgl{display:flex;align-items:center;gap:6px;padding-left:8px;font-size:11px;font-weight:600;color:var(--mut)}
|
|
163
163
|
.tlgl svg{flex:none}
|
|
164
164
|
.tlwrap .fhint{position:absolute;right:10px;bottom:6px;z-index:3}
|
|
165
|
+
.tlnode{cursor:pointer}
|
|
166
|
+
/* card detail modal — the full story of one card: status journey + the agent's own bus reports */
|
|
167
|
+
.cmodal{position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;background:rgba(4,7,12,.62)}
|
|
168
|
+
.cmpanel{background:var(--panel);border:1px solid var(--line);border-radius:14px;width:min(760px,93vw);max-height:84vh;display:flex;flex-direction:column;box-shadow:0 20px 64px rgba(0,0,0,.55)}
|
|
169
|
+
.cmhead{display:flex;align-items:flex-start;gap:10px;padding:15px 18px;border-bottom:1px solid var(--line)}
|
|
170
|
+
.cmtitle{font-size:15px;font-weight:700;color:var(--tx);line-height:1.35}
|
|
171
|
+
.cmmeta{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:6px;font-size:11.5px;color:var(--mut)}
|
|
172
|
+
.cmmeta svg{flex:none;vertical-align:middle}
|
|
173
|
+
.cmstat{font-size:10px;padding:1px 8px;border-radius:9px;border:1px solid var(--line)}
|
|
174
|
+
.cmclose{margin-left:auto;cursor:pointer;color:var(--mut);font-size:17px;line-height:1;padding:3px 7px;border-radius:6px;flex:none}
|
|
175
|
+
.cmclose:hover{background:var(--card);color:var(--tx)}
|
|
176
|
+
.cmbody{overflow-y:auto;padding:10px 18px 18px}
|
|
177
|
+
.cmrow{display:flex;gap:10px;padding:8px 0;border-top:1px solid #161e2c;font-size:12.5px;line-height:1.5}
|
|
178
|
+
.cmrow:first-child{border-top:none}
|
|
179
|
+
.cmrow .cmt{flex:none;color:var(--dim);font-size:10px;width:66px;padding-top:2px}
|
|
180
|
+
.cmrow.ev .cmtx{color:var(--mut)}
|
|
181
|
+
.cmrow .cmtx b{color:var(--tx);font-weight:600}
|
|
182
|
+
.cmrow.msg .cmfrom{color:var(--tx);font-weight:600}
|
|
183
|
+
.cmrow.msg svg{vertical-align:-2px}
|
|
184
|
+
.cmempty{color:var(--dim);font-style:italic;font-size:12px;padding:8px 0}
|
|
165
185
|
/* timeline view */
|
|
166
186
|
.timeline{padding:14px 18px;overflow-y:auto;max-height:66vh;display:flex;flex-direction:column;gap:5px;background:#0c111c;border-top:1px solid var(--line)}
|
|
167
187
|
.tevent{font-size:12.5px;line-height:1.4;color:var(--mut);display:flex;align-items:center;gap:7px;padding:4px 0}
|
|
@@ -227,6 +247,7 @@ aside h2{font-size:10.5px;text-transform:uppercase;letter-spacing:.09em;color:va
|
|
|
227
247
|
</div>
|
|
228
248
|
</aside>
|
|
229
249
|
</main>
|
|
250
|
+
<div id="cardmodal" class="cmodal" style="display:none"></div>
|
|
230
251
|
<script>
|
|
231
252
|
const $=s=>document.querySelector(s);
|
|
232
253
|
const esc=s=>String(s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
|
|
@@ -361,7 +382,40 @@ function wireTimeline(el){
|
|
|
361
382
|
if (TLSCROLL[proj] != null) s.scrollLeft = TLSCROLL[proj];
|
|
362
383
|
s.onscroll = () => { TLSCROLL[proj] = s.scrollLeft; };
|
|
363
384
|
});
|
|
385
|
+
el.querySelectorAll('.tlnode').forEach(n => n.onclick = () => openCard(+n.dataset.id));
|
|
364
386
|
}
|
|
387
|
+
// Card detail modal: one card's FULL story — its status journey interleaved with the agent's own bus
|
|
388
|
+
// reports (what it did, why, how). Click any card in the FLOW timeline to open it.
|
|
389
|
+
let cardEsc = null;
|
|
390
|
+
async function openCard(id){
|
|
391
|
+
let d; try { d = await (await fetch('/card?id=' + id)).json(); } catch { return; }
|
|
392
|
+
const t = d.task; if (!t) return;
|
|
393
|
+
const SC = { todo:'var(--mut)', doing:'var(--blu)', testing:'var(--amb)', failed:'var(--red)', blocked:'var(--red)', done:'var(--grn)', deleted:'var(--dim)' };
|
|
394
|
+
const items = [];
|
|
395
|
+
for (const e of (d.events||[])) items.push({ ts:e.ts, ev:e });
|
|
396
|
+
for (const m of (d.messages||[])) items.push({ ts:m.ts, m });
|
|
397
|
+
items.sort((a,b)=> (a.ts||0)-(b.ts||0));
|
|
398
|
+
const tm = ts => new Date(ts).toLocaleString([], {month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'});
|
|
399
|
+
const rows = items.map(it => {
|
|
400
|
+
if (it.ev){ const e=it.ev; const tx = e.type==='created'?`created as <b>${esc(e.to||'todo')}</b>` : e.type==='moved'?`moved <b>${esc(e.from||'')}</b> → <b>${esc(e.to||'')}</b>` : e.type==='deleted'?`deleted` : `updated`;
|
|
401
|
+
return `<div class="cmrow ev"><span class="cmt">${esc(tm(e.ts))}</span><span class="cmtx">${tx}${e.by?` · ${esc(String(e.by).split(':')[0])}`:''}</span></div>`; }
|
|
402
|
+
const m=it.m; return `<div class="cmrow msg"><span class="cmt">${esc(tm(m.ts))}</span><span class="cmtx">${iconFor(m.from,13)} <span class="cmfrom">${esc(String(m.from).split(':')[0])}</span>${m.to&&m.to!=='all'?` → ${esc(String(m.to).split(':')[0])}`:''}: ${esc(m.text)}</span></div>`;
|
|
403
|
+
}).join('');
|
|
404
|
+
const el = $('#cardmodal');
|
|
405
|
+
el.innerHTML = `<div class="cmpanel">`
|
|
406
|
+
+ `<div class="cmhead"><div><div class="cmtitle">${esc(t.title)}</div>`
|
|
407
|
+
+ `<div class="cmmeta">${iconFor(t.assignee||'',14)} ${esc(String(t.assignee||'unassigned').split(':')[0])}`
|
|
408
|
+
+ ` <span class="cmstat" style="color:${SC[t.status]||'var(--mut)'};border-color:${SC[t.status]||'var(--line)'}">${esc(t.status)}</span>`
|
|
409
|
+
+ `${t.difficulty?` · ${esc(t.difficulty)}`:''}${t.model?` · ${esc(String(t.model).slice(0,26))}`:''} · #${id} · ${esc(t.project||'')}</div></div>`
|
|
410
|
+
+ `<span class="cmclose" id="cmclose">✕</span></div>`
|
|
411
|
+
+ `<div class="cmbody">${rows || '<div class="cmempty">No recorded activity for this card yet — no bus reports reference #'+id+'.</div>'}</div></div>`;
|
|
412
|
+
el.style.display = 'flex';
|
|
413
|
+
el.querySelector('.cmpanel').onclick = e => e.stopPropagation();
|
|
414
|
+
el.onclick = closeCard; $('#cmclose').onclick = closeCard;
|
|
415
|
+
cardEsc = e => { if (e.key === 'Escape') closeCard(); };
|
|
416
|
+
document.addEventListener('keydown', cardEsc);
|
|
417
|
+
}
|
|
418
|
+
function closeCard(){ const el=$('#cardmodal'); el.style.display='none'; el.innerHTML=''; if(cardEsc){ document.removeEventListener('keydown', cardEsc); cardEsc=null; } }
|
|
365
419
|
let dragging = null; // suppresses re-render mid-gesture
|
|
366
420
|
function saveCam(proj, cam){ const c = JSON.parse(localStorage.getItem("abFlowCam") || "{}"); c[proj] = cam; localStorage.setItem("abFlowCam", JSON.stringify(c)); }
|
|
367
421
|
function wireFlow(el){
|