trantor 0.18.21 → 0.18.23
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 +2 -2
- package/README.md +5 -0
- package/bin/bridge.mjs +4 -1
- package/bin/crew.sh +21 -0
- package/hooks/sessionstart.mjs +17 -0
- package/hub.mjs +29 -2
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.18.
|
|
4
|
-
"description": "Trantor
|
|
3
|
+
"version": "0.18.23",
|
|
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": {
|
|
7
7
|
"command": "node",
|
package/README.md
CHANGED
|
@@ -364,6 +364,11 @@ model-authored handoff instead of superseding it; the successor is injected a ca
|
|
|
364
364
|
being spoken to; and a session hosted in a Workspace pane is replaced in place by a detached
|
|
365
365
|
driver — the same chain the app's [Hand off now] button runs.
|
|
366
366
|
|
|
367
|
+
Waking works the same way from the other end: hover a sleeping project in the app's sidebar and
|
|
368
|
+
**Wake** hosts its session as an in-app pane — `trantor open` under the hood, opened in the
|
|
369
|
+
project's own checkout wherever it is called from, handoff-beats-resume, and the same kickoff
|
|
370
|
+
prompt so the woken session catches up (handoff, board, memory) and recaps on its own.
|
|
371
|
+
|
|
367
372
|
Why crews never exhaust the orchestrator: bus messages are **by reference** (~70 tokens),
|
|
368
373
|
work products stay in each agent's own context — the orchestrator burns at coordination
|
|
369
374
|
rate, not work rate.
|
package/bin/bridge.mjs
CHANGED
|
@@ -110,7 +110,10 @@ async function tick() {
|
|
|
110
110
|
if (differs && (aMoved || bMoved)) {
|
|
111
111
|
const aWins = aMoved && bMoved ? p.origin === "A" : aMoved;
|
|
112
112
|
const [src, dstHub, dstId] = aWins ? [ta, TO, p.bId] : [tb, FROM, p.aId];
|
|
113
|
-
|
|
113
|
+
// reassign:true — the bridge REPLICATES a hand-change that already happened on the source
|
|
114
|
+
// hub; without the explicit marker the #5406 assignee-immutability guard would 409 every
|
|
115
|
+
// cross-hub sync whose assignee moved (deepseek flagged this at review, 2026-08-31).
|
|
116
|
+
const r = await call(dstHub, "POST", "/task/update", { id: dstId, status: src.status, assignee: src.assignee || "", reassign: true, by: src.by || "bridge" });
|
|
114
117
|
if (aWins) { p.lastA = ta.updated || 0; p.lastB = r.task.updated || 0; }
|
|
115
118
|
else { p.lastB = tb.updated || 0; p.lastA = r.task.updated || 0; }
|
|
116
119
|
synced++;
|
package/bin/crew.sh
CHANGED
|
@@ -194,6 +194,24 @@ _herdr_close_pane() { [ "$DRY" = "1" ] && { echo "[dry] herdr pane close $1";
|
|
|
194
194
|
# So the project gets ONE claude session id, chosen by us and remembered. First open starts claude
|
|
195
195
|
# under it; every later open resumes it. Discovering the id afterwards would be guesswork, and
|
|
196
196
|
# `--continue` would grab whatever ran last in this directory, which may be a different window.
|
|
197
|
+
# A NAMED project must open in ITS checkout, wherever the caller stands (2026-08-31: the app ran
|
|
198
|
+
# `trantor open crebral-health` from the Tauri process cwd and claude booted THERE — a trust
|
|
199
|
+
# prompt for a folder the operator never chose, transcripts under the wrong slug, no project
|
|
200
|
+
# memory, ACTIVE NOW blind). Resolution mirrors the app's project_dir: $TRANTOR_DEV_ROOT
|
|
201
|
+
# (default ~/development)/<name>. Unknown name from an unrelated cwd → refuse loudly rather
|
|
202
|
+
# than open somewhere silly.
|
|
203
|
+
_orch_resolve_dir() { # $1=cwd $2=project-arg → dir to open in (stdout); fails when unresolvable
|
|
204
|
+
local herebase; herebase="$(basename "$(git -C "$1" rev-parse --show-toplevel 2>/dev/null || echo "$1")")"
|
|
205
|
+
if [ -z "$2" ] || [ "$2" = "$herebase" ]; then printf '%s' "$1"; return 0; fi
|
|
206
|
+
local devroot="${TRANTOR_DEV_ROOT:-$HOME/development}"
|
|
207
|
+
if [ -d "$devroot/$2" ]; then
|
|
208
|
+
echo "— opening $2 in its checkout: $devroot/$2 —" >&2
|
|
209
|
+
printf '%s' "$devroot/$2"; return 0
|
|
210
|
+
fi
|
|
211
|
+
echo "trantor open: '$2' has no checkout at $devroot/$2 and this is '$herebase' — cd into the project first" >&2
|
|
212
|
+
return 1
|
|
213
|
+
}
|
|
214
|
+
|
|
197
215
|
_orch_sid() { # $1=project → the project's session uuid, minting one on first use
|
|
198
216
|
local f="${STATE%/*}/orch-sessions.txt" p sid
|
|
199
217
|
if [ -f "$f" ]; then
|
|
@@ -601,6 +619,7 @@ open_orchestrator() {
|
|
|
601
619
|
--*) echo "trantor open: unknown flag '$a'"; usage_open; return 1 ;;
|
|
602
620
|
*) PROJ="$a" ;;
|
|
603
621
|
esac; done
|
|
622
|
+
DIR="$(_orch_resolve_dir "$DIR" "$PROJ")" || exit 1
|
|
604
623
|
command -v herdr >/dev/null 2>&1 || { echo "trantor open needs herdr (the pane host) — install: curl -fsSL https://herdr.dev/install.sh | sh"; exit 1; }
|
|
605
624
|
local wsid="" orch="" live_ids="" live_names="" pair="" line fresh=0
|
|
606
625
|
local sid; sid="$(_orch_sid "$PROJ")" || { echo "trantor open: could not mint a session id (uuidgen missing?)" >&2; exit 1; }
|
|
@@ -635,6 +654,8 @@ open_orchestrator() {
|
|
|
635
654
|
if _herdr pane rename "$orch" "orchestrator · $PROJ" >/dev/null 2>&1; then
|
|
636
655
|
if _herdr_pane_has_agent "$orch"; then
|
|
637
656
|
echo "herdr:${wsid:-?}/$orch"
|
|
657
|
+
# CONTRACT: the app's orchestrator_open (desktop terminal.rs) matches this exact phrase
|
|
658
|
+
# to SKIP its kickoff prompt — a reattach is a live conversation, never to be typed into.
|
|
638
659
|
echo "— orchestrator already hosted: reattached to herdr:${wsid:-?}/$orch —" >&2
|
|
639
660
|
return 0
|
|
640
661
|
fi
|
package/hooks/sessionstart.mjs
CHANGED
|
@@ -369,6 +369,23 @@ try {
|
|
|
369
369
|
}
|
|
370
370
|
} catch {}
|
|
371
371
|
|
|
372
|
+
// THE ORCHESTRATOR ROLE (2026-08-31 — the operator had to say it by hand, twice in one day:
|
|
373
|
+
// "your job is to oversee and be a project manager, not a coder"). A session opened by
|
|
374
|
+
// `trantor open` carries TRANTOR_ORCH=<project>; every such session gets the doctrine at
|
|
375
|
+
// boot, so the role survives wakes, handoffs and restarts without anyone restating it.
|
|
376
|
+
try {
|
|
377
|
+
if (project && (process.env.TRANTOR_ORCH || "") === project) {
|
|
378
|
+
additionalContext += `<trantor-orchestrator-role project="${sanitize(project)}">\n` +
|
|
379
|
+
`🎛️ **You are this project's ORCHESTRATOR — a project manager, not a coder** (operator ruling, 2026-08-31).\n` +
|
|
380
|
+
`- Building goes to the CREW: invoke the trantor:crew skill (Advisor → \`trantor up\` seats → contracts over the bus), or relay_scrooge for grunt one-shots.\n` +
|
|
381
|
+
`- Spend your own tokens on design, contracts, supervision, integration and VERIFICATION — run the seats' tests yourself; bounce hollow dones.\n` +
|
|
382
|
+
`- Write code yourself ONLY for small one-head seam work that needs this session's full context — and say so when you do.\n` +
|
|
383
|
+
`- Check relay_inbox and the board before asking the operator anything a peer may already have answered.\n` +
|
|
384
|
+
`</trantor-orchestrator-role>\n`;
|
|
385
|
+
process.stderr.write(`[trantor] injected orchestrator-role doctrine for ${project}\n`);
|
|
386
|
+
}
|
|
387
|
+
} catch {}
|
|
388
|
+
|
|
372
389
|
// Update available? Surface it the way a terminal tool should — an in-terminal `systemMessage`
|
|
373
390
|
// line the USER sees at session start (NOT a macOS desktop popup, which macOS misattributes to
|
|
374
391
|
// Script Editor and which fires off-screen). It shows every session while an update is pending and
|
package/hub.mjs
CHANGED
|
@@ -435,7 +435,12 @@ setTimeout(overseerTick, 2000).unref?.();
|
|
|
435
435
|
// Env still wins at boot (an operator's declared config beats a seat's claim); otherwise the last
|
|
436
436
|
// registered seat is restored from state, so a hub restart doesn't silently end the duty feed.
|
|
437
437
|
let DUTY_SESSION = String(process.env.RELAY_DUTY_SESSION || state.dutySession || "");
|
|
438
|
-
|
|
438
|
+
// 2 MINUTES, not 10 (2026-08-31): scribe DMed the woken crebral-health session at 16:11 and the
|
|
439
|
+
// operator hand-relayed at 16:21:58 — beating the old 10m escalation by seconds. Two agents
|
|
440
|
+
// actively collaborating cannot wait ten minutes; with duty's direct-wake the full chain
|
|
441
|
+
// (escalate → duty nudge → target's hooks poll) now lands in ~3m. Duty's own batch rules
|
|
442
|
+
// (one nudge per recipient per batch, consumed on activity) keep the shorter window from nagging.
|
|
443
|
+
const DUTY_UNDELIVERED_MS = Number(process.env.RELAY_DUTY_UNDELIVERED_MS || 2 * 60 * 1000);
|
|
439
444
|
const dutyEscalated = new Set();
|
|
440
445
|
// #5686: the janitor died 08-27 and NOTHING noticed for 4 days — the hub kept escalating to a
|
|
441
446
|
// corpse. Duty liveness is now a first-class state: dark = configured but no heartbeat inside
|
|
@@ -1908,6 +1913,22 @@ const server = http.createServer(async (req, res) => {
|
|
|
1908
1913
|
if (req.method === "POST" && P === "/task/update") { // move/edit a card
|
|
1909
1914
|
const b = await body(req); const t = state.tasks.find(x => x.id === Number(b.id));
|
|
1910
1915
|
if (!t) return json(res, 404, { error: "no such task" });
|
|
1916
|
+
// Board integrity (#5406): a card can never change hands silently. The assignee is frozen once
|
|
1917
|
+
// set; a mutation is legitimate only as a HANDOFF (the current assignee reassigning to someone
|
|
1918
|
+
// else) or an EXPLICIT reassign (reassign:true — e.g. the orchestrator re-routing work after a
|
|
1919
|
+
// seat dies). A silent third-party overwrite 409s so the caller knows the board refused to move.
|
|
1920
|
+
// Runs BEFORE any other field mutation so a refused steal cannot half-apply a status move.
|
|
1921
|
+
if (b.assignee !== undefined) {
|
|
1922
|
+
const want = String(b.assignee).slice(0, 60);
|
|
1923
|
+
if (want !== t.assignee) {
|
|
1924
|
+
const mover = String(auth?.identity?.name || b.by || "").slice(0, 120);
|
|
1925
|
+
const isOwner = !!t.assignee && mover === t.assignee;
|
|
1926
|
+
const explicit = b.reassign === true;
|
|
1927
|
+
if (!isOwner && !explicit) {
|
|
1928
|
+
return json(res, 409, { error: "assignee is immutable", id: t.id, assignee: t.assignee });
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1911
1932
|
let eventType = "updated", eventFrom = null, eventTo = null;
|
|
1912
1933
|
if (b.status && ["todo","doing","testing","failed","done","blocked","stale"].includes(b.status) && b.status !== t.status) {
|
|
1913
1934
|
eventType = "moved"; eventFrom = t.status; eventTo = b.status;
|
|
@@ -1925,7 +1946,13 @@ const server = http.createServer(async (req, res) => {
|
|
|
1925
1946
|
if (b.difficulty && ["easy","medium","hard"].includes(b.difficulty)) t.difficulty = b.difficulty;
|
|
1926
1947
|
if (b.model !== undefined) t.model = String(b.model).slice(0, 60);
|
|
1927
1948
|
if (Array.isArray(b.deps)) t.deps = [...new Set(b.deps.map(Number).filter(n => Number.isInteger(n) && n > 0 && n !== t.id))].slice(0, 20);
|
|
1928
|
-
if (b.assignee !== undefined
|
|
1949
|
+
if (b.assignee !== undefined && String(b.assignee).slice(0, 60) !== t.assignee) {
|
|
1950
|
+
const prev = t.assignee || "(none)";
|
|
1951
|
+
const mover = String(auth?.identity?.name || b.by || "").slice(0, 120);
|
|
1952
|
+
t.assignee = String(b.assignee).slice(0, 60);
|
|
1953
|
+
// the handover is part of the card's story, not a silent overwrite
|
|
1954
|
+
appendTaskLog(t, mover, `reassigned ${prev} → ${t.assignee}${b.reassign === true ? " (explicit)" : " (handoff)"}`, now());
|
|
1955
|
+
}
|
|
1929
1956
|
if (b.title !== undefined) t.title = String(b.title).slice(0,200);
|
|
1930
1957
|
// the narrative line a human reads on the board ("assigned — did"), written by the cheap
|
|
1931
1958
|
// summarizer; rides the tasks.extra column, so it survives restarts everywhere
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.23",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"trantor": "bin/cli.mjs"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"scripts": {
|
|
14
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-usage-live.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-dark.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
|
-
"description": "The hub-world for AI agent crews
|
|
16
|
+
"description": "The hub-world for AI agent crews — 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": [
|
|
18
18
|
"hub.mjs",
|
|
19
19
|
"mcp.mjs",
|