trantor 0.18.18 → 0.18.19
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/plugin.json +1 -1
- package/README.md +1 -0
- package/hooks/lib/handoff.mjs +7 -3
- package/hub.mjs +29 -0
- package/mcp.mjs +12 -4
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.19",
|
|
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": {
|
package/README.md
CHANGED
|
@@ -370,6 +370,7 @@ rate, not work rate.
|
|
|
370
370
|
| `relay_project_brief(text)` | The project's what/why on the dashboard |
|
|
371
371
|
| `relay_task_add(title, …, difficulty, model, deps, note?, project?)` | Cards with difficulty/model badges + DAG edges; `note` seeds the card's **permanent log**; `project` targets another board when you orchestrate from elsewhere |
|
|
372
372
|
| `relay_task_move(id, status, note?)` | `todo → doing → testing → done` (the gate), `failed`, `blocked` — moves to testing/done should carry a `note`: what you did + the evidence, stored on the card forever |
|
|
373
|
+
| `relay_task_check(id, index, done?)` | Tick one acceptance item on a card's checklist (seeded via `relay_task_add`'s `checklist`) — checked/total is the card's one honest progress denominator |
|
|
373
374
|
| `relay_board` | The project's full board, as text |
|
|
374
375
|
| `relay_scrooge(prompt, task?, difficulty?)` | Fractal cheap-model delegation, with the ledger receipt |
|
|
375
376
|
| `relay_lesson(text, scope?)` | Record a failure lesson — auto-injected into all future crews |
|
package/hooks/lib/handoff.mjs
CHANGED
|
@@ -96,13 +96,17 @@ export function guardContextTokens(rows) {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
// The transcript logs the model WITHOUT the [1m] marker, so we cannot tell a
|
|
99
|
-
// 200k window from a 1M one.
|
|
100
|
-
// window
|
|
101
|
-
// the
|
|
99
|
+
// 200k window from a 1M one in general. Fable is the known exception (#5503):
|
|
100
|
+
// its window is 1M and the name is all the transcript ever gives us — the
|
|
101
|
+
// undeclared window kept the early-warning off and the session hit the wall
|
|
102
|
+
// silently. An explicit declaration (env RELAY_CONTEXT_WINDOW or
|
|
103
|
+
// config.contextWindow) always wins over any name-based inference. Returns 0
|
|
104
|
+
// when unknown (→ no warning).
|
|
102
105
|
export function resolveWindow(model = "", conf = readConfig()) {
|
|
103
106
|
const explicit = Number(process.env.RELAY_CONTEXT_WINDOW || conf.contextWindow || 0);
|
|
104
107
|
if (explicit > 0) return explicit;
|
|
105
108
|
if (/\[1m\]|-1m\b|:1m\b/i.test(model)) return 1_000_000; // honored if ever present
|
|
109
|
+
if (/fable/i.test(model)) return 1_000_000; // #5503: fable is 1M by name
|
|
106
110
|
return 0;
|
|
107
111
|
}
|
|
108
112
|
|
package/hub.mjs
CHANGED
|
@@ -156,6 +156,16 @@ function appendTaskNote(t, b, ts = Date.now()) {
|
|
|
156
156
|
if (!b || typeof b.note !== "string") return false;
|
|
157
157
|
return appendTaskLog(t, b.by || "", b.note, ts);
|
|
158
158
|
}
|
|
159
|
+
// Card checklists (#5624): acceptance items are the one honest denominator for a progress bar.
|
|
160
|
+
// Accepts plain strings (fresh items) or {text,done} (round-trips); caps 20 items x 200 chars.
|
|
161
|
+
// Returns null for a non-array so callers can distinguish "not sent" from "sent empty".
|
|
162
|
+
function cleanChecklist(v) {
|
|
163
|
+
if (!Array.isArray(v)) return null;
|
|
164
|
+
return v.slice(0, 20)
|
|
165
|
+
.map(it => typeof it === "string" ? { text: it.slice(0, 200), done: false }
|
|
166
|
+
: { text: String(it?.text ?? "").slice(0, 200), done: !!it?.done })
|
|
167
|
+
.filter(it => it.text);
|
|
168
|
+
}
|
|
159
169
|
function runTaskBootMigrations() {
|
|
160
170
|
let changed = false;
|
|
161
171
|
const bootNow = Date.now();
|
|
@@ -1826,6 +1836,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1826
1836
|
deps: Array.isArray(b.deps) ? [...new Set(b.deps.map(Number).filter(n => Number.isInteger(n) && n > 0))].slice(0, 20) : [],
|
|
1827
1837
|
by: b.by || "", ts: ts0, updated: ts0,
|
|
1828
1838
|
history: [{ to: st0, by: b.by || "", ts: ts0 }] };
|
|
1839
|
+
{ const cl = cleanChecklist(b.checklist); if (cl?.length) t.checklist = cl; } // #5624 — rides `extra`, survives restarts
|
|
1829
1840
|
if (b.source === "cc-subagent") { t._fp = subFp(b.title); if (b.agentType) t._atype = String(b.agentType).slice(0, 40); if (b.agentId) t._aid = String(b.agentId).slice(0, 80); if (b.parent) t.parent = String(b.parent).slice(0, 120); t.count = 1; if (t.status === "doing") { t._everStarted = true; t._inflight = 1; } }
|
|
1830
1841
|
appendTaskNote(t, b, ts0);
|
|
1831
1842
|
state.tasks.push(t); if (state.tasks.length > 2000) state.tasks.splice(0, 500);
|
|
@@ -1862,11 +1873,29 @@ const server = http.createServer(async (req, res) => {
|
|
|
1862
1873
|
// the narrative line a human reads on the board ("assigned — did"), written by the cheap
|
|
1863
1874
|
// summarizer; rides the tasks.extra column, so it survives restarts everywhere
|
|
1864
1875
|
if (b.summary !== undefined) t.summary = String(b.summary).slice(0, 220);
|
|
1876
|
+
// #5624: full checklist replace (null clears). Item-level toggles ride /task/checklist-toggle.
|
|
1877
|
+
if (b.checklist !== undefined) {
|
|
1878
|
+
const cl = cleanChecklist(b.checklist);
|
|
1879
|
+
if (cl) { if (cl.length) t.checklist = cl; else delete t.checklist; }
|
|
1880
|
+
else if (b.checklist === null) delete t.checklist;
|
|
1881
|
+
}
|
|
1865
1882
|
appendTaskNote(t, b);
|
|
1866
1883
|
if (b.delete) { eventType = "deleted"; eventFrom = null; eventTo = null; state.tasks = state.tasks.filter(x => x.id !== t.id); }
|
|
1867
1884
|
appendCardEvent(eventType, t, b.by, eventFrom, eventTo);
|
|
1868
1885
|
t.updated = now(); dirty = true; return json(res, 200, { ok: true, task: t });
|
|
1869
1886
|
}
|
|
1887
|
+
// #5624: toggle ONE acceptance item. Index-addressed against the card's current checklist —
|
|
1888
|
+
// a stale index 400s instead of silently toggling the wrong item.
|
|
1889
|
+
if (req.method === "POST" && P === "/task/checklist-toggle") {
|
|
1890
|
+
const b = await body(req); const t = state.tasks.find(x => x.id === Number(b.id));
|
|
1891
|
+
if (!t) return json(res, 404, { error: "no such task" });
|
|
1892
|
+
const i = Number(b.index);
|
|
1893
|
+
if (!Array.isArray(t.checklist) || !Number.isInteger(i) || i < 0 || i >= t.checklist.length) {
|
|
1894
|
+
return json(res, 400, { error: "no such checklist item" });
|
|
1895
|
+
}
|
|
1896
|
+
t.checklist[i].done = !!b.done;
|
|
1897
|
+
t.updated = now(); dirty = true; return json(res, 200, { ok: true, task: t });
|
|
1898
|
+
}
|
|
1870
1899
|
// Manual board sweep — the aggressive companion to the automatic reaper. The reaper only touches
|
|
1871
1900
|
// OFFLINE-owner cards (no false positives on live work); /sweep is the explicit "this live seat forgot
|
|
1872
1901
|
// its card" path: it stales EVERY doing/testing card untouched past `olderMs`, regardless of owner
|
package/mcp.mjs
CHANGED
|
@@ -182,11 +182,19 @@ server.tool("relay_contracts", "What you dispatched and are still owed. Lists ev
|
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
server.tool("relay_task_add", "Add a Kanban card to a project's board on the dashboard (what you're about to work on). Defaults: THIS project, assigned to you, status 'todo'. Pass `project` to target another board — e.g. when you orchestrate a crew that runs in a different directory than the one you launched Claude from. Keep the team's progress visible. Attach a `note` whenever context isn't obvious from the title — it lands on the card's permanent log ({ts,by,text}, kept: last 40).",
|
|
185
|
-
{ title: z.string().describe("short task title"), status: z.enum(["todo","doing","testing","failed","done","blocked"]).optional(), assignee: z.string().optional().describe("session id to assign (default: you)"), difficulty: z.enum(["easy","medium","hard"]).optional().describe("difficulty tag — drives model/agent routing (relay_advise) and shows on the board"), model: z.string().optional().describe("the model this card is routed to (from relay_advise routing, or the CLI default) — shown on the card"), deps: z.array(z.number()).optional().describe("card ids this card depends on — drawn as branch edges in the Flow view (e.g. integration depends on every crew card)"), phase: z.string().optional().describe("phase/milestone this card belongs to (e.g. 'P5', 'Auth', 'Launch') — groups it in the Flow view's phase flowchart. Optional; otherwise inferred from the title prefix + time."), note: z.string().max(2000).optional().describe("optional card-log entry (<=2000 chars): context, the plan, or a link — stored on the card as {ts,by,text}"), project: z.string().optional().describe("board to add to (default: this session's project). Set to the crew's project when you orchestrate from a different directory") },
|
|
186
|
-
async ({ title, status, assignee, difficulty, model, deps, phase, note, project }) => {
|
|
185
|
+
{ title: z.string().describe("short task title"), status: z.enum(["todo","doing","testing","failed","done","blocked"]).optional(), assignee: z.string().optional().describe("session id to assign (default: you)"), difficulty: z.enum(["easy","medium","hard"]).optional().describe("difficulty tag — drives model/agent routing (relay_advise) and shows on the board"), model: z.string().optional().describe("the model this card is routed to (from relay_advise routing, or the CLI default) — shown on the card"), deps: z.array(z.number()).optional().describe("card ids this card depends on — drawn as branch edges in the Flow view (e.g. integration depends on every crew card)"), phase: z.string().optional().describe("phase/milestone this card belongs to (e.g. 'P5', 'Auth', 'Launch') — groups it in the Flow view's phase flowchart. Optional; otherwise inferred from the title prefix + time."), note: z.string().max(2000).optional().describe("optional card-log entry (<=2000 chars): context, the plan, or a link — stored on the card as {ts,by,text}"), project: z.string().optional().describe("board to add to (default: this session's project). Set to the crew's project when you orchestrate from a different directory"), checklist: z.array(z.string().max(200)).max(20).optional().describe("acceptance items for the card — the honest denominator for its progress bar. Tick them off with relay_task_check as each is truly met") },
|
|
186
|
+
async ({ title, status, assignee, difficulty, model, deps, phase, note, project, checklist }) => {
|
|
187
187
|
const proj = project || PROJECT;
|
|
188
|
-
const { task } = await api("POST", "/task", { project: proj, title, status: status || "todo", assignee: assignee || SESSION, difficulty, model, deps, phase, note, by: SESSION });
|
|
189
|
-
return { content: [{ type: "text", text: `card #${task.id} added to ${proj}: "${title}" [${task.status}]${phase?` · phase ${phase}`:""}` }] };
|
|
188
|
+
const { task } = await api("POST", "/task", { project: proj, title, status: status || "todo", assignee: assignee || SESSION, difficulty, model, deps, phase, note, checklist, by: SESSION });
|
|
189
|
+
return { content: [{ type: "text", text: `card #${task.id} added to ${proj}: "${title}" [${task.status}]${phase?` · phase ${phase}`:""}${task.checklist?.length?` · ${task.checklist.length} acceptance item(s)`:""}` }] };
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
server.tool("relay_task_check", "Tick (or untick) ONE acceptance item on a card's checklist — the card's progress bar reads checked/total, so tick an item only when it is genuinely met (tests run, behavior observed), never to make the bar move. Items are 0-indexed in the order relay_task_add listed them.",
|
|
193
|
+
{ id: z.number().describe("card id"), index: z.number().int().min(0).describe("0-based checklist item index"), done: z.boolean().optional().describe("default true; pass false to untick") },
|
|
194
|
+
async ({ id, index, done }) => {
|
|
195
|
+
const { task } = await api("POST", "/task/checklist-toggle", { id, index, done: done !== false, by: SESSION });
|
|
196
|
+
const n = task.checklist.filter(c => c.done).length;
|
|
197
|
+
return { content: [{ type: "text", text: `card #${id} checklist: [${done !== false ? "x" : " "}] "${task.checklist[index].text}" — ${n}/${task.checklist.length} done` }] };
|
|
190
198
|
});
|
|
191
199
|
|
|
192
200
|
server.tool("relay_phase_goal", "Set what a PHASE is for — its goal — shown as the phase header in the Flow view (overrides the theme auto-derived from card titles). Capture this when you plan a phase so the board says what each milestone needs to do, not just 'P5'. Phase keys match relay_task_add's `phase` (or the inferred title-prefix family like 'P5').",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.19",
|
|
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-autonomy.mjs && node test-integrate.mjs && node test-handoff.mjs && node test-handoff-summarizer.mjs && node test-baton-turn-boundary.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-crew-env.mjs && node test-desktop-transport.mjs && node test-crew-worktree.mjs && bash test-crew-herdr.sh && npm --prefix desktop run test --silent && 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"
|
|
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-autonomy.mjs && node test-integrate.mjs && node test-handoff.mjs && node test-handoff-summarizer.mjs && node test-baton-turn-boundary.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-checklist.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-crew-env.mjs && node test-desktop-transport.mjs && node test-crew-worktree.mjs && bash test-crew-herdr.sh && npm --prefix desktop run test --silent && 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": [
|