trantor 0.17.39 → 0.17.40
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/bin/baton.mjs +1 -1
- package/hooks/handoff-now.mjs +3 -1
- package/hooks/heartbeat.mjs +2 -1
- package/hooks/lib/handoff.mjs +14 -1
- package/hooks/precompact.mjs +1 -1
- package/hub.mjs +0 -0
- package/package.json +2 -2
- package/ui.html +2 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.40",
|
|
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/baton.mjs
CHANGED
|
@@ -30,7 +30,7 @@ function findTranscript() {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const transcript = findTranscript();
|
|
33
|
-
const { file } = writeHandoff({ projectDir: cwd, sessionId: "", transcript, trigger: "manual-cli" });
|
|
33
|
+
const { file } = writeHandoff({ projectDir: cwd, sessionId: "", transcript, trigger: "manual-cli", force: true }); // manual = intentional, bypass the storm guard
|
|
34
34
|
console.log(`📋 handoff saved for ${project}: ${file}`);
|
|
35
35
|
const { spawned, armed, windowId } = spawnBaton({ projectDir: cwd, handoffFile: file });
|
|
36
36
|
console.log(spawned
|
package/hooks/handoff-now.mjs
CHANGED
|
@@ -12,7 +12,9 @@ import { basename } from "node:path";
|
|
|
12
12
|
const [, , projectDir = process.cwd(), sessionId = "", transcript = "", trigger = "context-warn", windowId = "", tty = ""] = process.argv;
|
|
13
13
|
try {
|
|
14
14
|
const conf = readConfig();
|
|
15
|
-
const
|
|
15
|
+
const result = writeHandoff({ projectDir, sessionId, transcript, trigger }); // auto path — honors the hub storm guard
|
|
16
|
+
if (result.skipped) { process.stderr.write(`[trantor] handoff SKIPPED by storm-guard (${result.reason}; ${result.sinceSec ?? "?"}s since last) — no fresh window spawned\n`); process.exit(0); }
|
|
17
|
+
const { file, record } = result;
|
|
16
18
|
process.stderr.write(`[trantor] baton handoff written: ${file}\n`);
|
|
17
19
|
await pingBus(basename(projectDir), record.id, conf);
|
|
18
20
|
if (maybeSpawn(projectDir, conf)) { // open the fresh session that takes over
|
package/hooks/heartbeat.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import { spawn } from "node:child_process";
|
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
21
|
import { readConfig, contextUsage, warnFrac, alreadyHandedOff, markHandedOff, controllingTty, terminalWindowForTty, subagentsActive } from "./lib/handoff.mjs";
|
|
22
22
|
import { resolveProject, hostId } from "../lib/project.mjs";
|
|
23
|
+
import { installedVersion } from "./lib/update-check.mjs"; // report our hook version so the hub can flag stale sessions
|
|
23
24
|
|
|
24
25
|
const HEARTBEAT_MS = Number(process.env.RELAY_HEARTBEAT_MS || 60 * 1000);
|
|
25
26
|
const FETCH_TIMEOUT_MS = Number(process.env.RELAY_HEARTBEAT_TIMEOUT_MS || 1500);
|
|
@@ -121,7 +122,7 @@ async function main(stdinRaw) {
|
|
|
121
122
|
await fetch(`${relayUrl()}/register`, {
|
|
122
123
|
method: "POST",
|
|
123
124
|
headers: { "content-type": "application/json" },
|
|
124
|
-
body: JSON.stringify({ session, project }),
|
|
125
|
+
body: JSON.stringify({ session, project, hookVersion: (() => { try { return installedVersion(); } catch { return ""; } })() }),
|
|
125
126
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
126
127
|
});
|
|
127
128
|
} catch {}
|
package/hooks/lib/handoff.mjs
CHANGED
|
@@ -213,8 +213,21 @@ export function verbatimRecentTail(transcript, chars = 7000) {
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
// ---- write + announce + spawn ----------------------------------------------
|
|
216
|
-
export function writeHandoff({ projectDir, sessionId, transcript, trigger, summary }) {
|
|
216
|
+
export function writeHandoff({ projectDir, sessionId, transcript, trigger, summary, force = false }) {
|
|
217
217
|
const projectName = basename(projectDir);
|
|
218
|
+
// Server-side storm guard: a session running OLD hooks (before the local markHandedOff guard) re-fires
|
|
219
|
+
// context-warn handoffs every few minutes — the crebral-cortex storm (9 in 49 min, each spawning a
|
|
220
|
+
// window). Ask the hub for clearance (rate-limit per project+session); a non-forced handoff inside the
|
|
221
|
+
// cooldown is SKIPPED — no file, no spawn. Manual (/trantor:handoff) + at-wall (precompact) handoffs
|
|
222
|
+
// force through. Fail-OPEN if the hub is unreachable, so a legit handoff is never blocked.
|
|
223
|
+
if (!force) {
|
|
224
|
+
try {
|
|
225
|
+
const body = JSON.stringify({ project: projectName, session: sessionId || "", trigger: trigger || "auto" });
|
|
226
|
+
const out = execSync(`curl -s --max-time 2 -X POST -H 'content-type: application/json' -d ${JSON.stringify(body)} ${JSON.stringify(relayUrl() + "/handoff")}`, { encoding: "utf8", timeout: 2500 });
|
|
227
|
+
const r = JSON.parse(out);
|
|
228
|
+
if (r && r.allow === false) return { skipped: true, reason: r.reason || "storm-guard", sinceSec: r.sinceSec };
|
|
229
|
+
} catch {}
|
|
230
|
+
}
|
|
218
231
|
if (!existsSync(HANDOFF_DIR)) mkdirSync(HANDOFF_DIR, { recursive: true });
|
|
219
232
|
const stamp = nowSec() || Date.now();
|
|
220
233
|
let gitStatus = "";
|
package/hooks/precompact.mjs
CHANGED
|
@@ -25,7 +25,7 @@ try {
|
|
|
25
25
|
const sessionId = input.session_id || "";
|
|
26
26
|
const conf = readConfig();
|
|
27
27
|
|
|
28
|
-
const { file, record } = writeHandoff({ projectDir, sessionId, transcript, trigger });
|
|
28
|
+
const { file, record } = writeHandoff({ projectDir, sessionId, transcript, trigger, force: true }); // at-wall backstop — must never be storm-guard-suppressed
|
|
29
29
|
process.stderr.write(`[trantor] handoff written: ${file} (trigger=${trigger})\n`);
|
|
30
30
|
|
|
31
31
|
await pingBus(projectName, record.id, conf);
|
package/hub.mjs
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.40",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"trantor": "bin/cli.mjs"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"zod": "^4.4.3"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"test": "node test.mjs && node test-scenarios.mjs && node test-failure.mjs && node test-handoff.mjs && node test-agents.mjs && node test-update.mjs && node test-balances.mjs && node test-subagent-cost.mjs"
|
|
13
|
+
"test": "node test.mjs && node test-scenarios.mjs && node test-failure.mjs && node test-handoff.mjs && node test-agents.mjs && node test-update.mjs && node test-handoff-guard.mjs && node test-balances.mjs && node test-subagent-cost.mjs"
|
|
14
14
|
},
|
|
15
15
|
"description": "The hub-world for AI agent crews — orchestrate Claude Code, Codex, Gemini, Kimi & DeepSeek as live crews with a plan-aware Advisor, a Kanban/flow command center, a testing gate, and an economics brain (Scrooge).",
|
|
16
16
|
"files": [
|
package/ui.html
CHANGED
|
@@ -53,6 +53,7 @@ main:not(.learn-open) .learn-body{display:none}
|
|
|
53
53
|
.agents{display:flex;gap:6px;flex-wrap:wrap}
|
|
54
54
|
.agent{display:flex;align-items:center;gap:5px;background:var(--card);border:1px solid var(--line);border-radius:16px;padding:2px 9px 2px 6px;font-size:11.5px;color:var(--mut)}
|
|
55
55
|
.agent .nm{color:var(--tx)}
|
|
56
|
+
.agent .stale{font-size:9.5px;font-weight:700;color:#ffb454;background:#2a1f10;border:1px solid #5a3c1a;border-radius:7px;padding:1px 5px;cursor:help}
|
|
56
57
|
.agent svg{flex:none}
|
|
57
58
|
.agent.offl{opacity:.42}
|
|
58
59
|
.agent.err{border-color:#ef4444;color:#ef4444}
|
|
@@ -768,7 +769,7 @@ async function render(){
|
|
|
768
769
|
const pmsgs = msgs.filter(m=>projOf(m)===p.project);
|
|
769
770
|
const done=pt.filter(t=>t.status==='done').length;
|
|
770
771
|
const pct=pt.length?Math.round(done/pt.length*100):0;
|
|
771
|
-
const agents=p.agents.sort((a,b)=>b.online-a.online).map(a=>`<span class="agent ${a.online?'':'offl'}${a.health==='down'?' down':a.health==='errored'?' err':''}" title="${esc(a.session)}${a.online?' · online':' · offline'}${a.health&&a.health!=='ok'?' · '+a.health:''}">${iconFor(a.session,15)}<span class="nm">${esc(a.session)}</span>${a.status?` <span class="ast">· ${esc(a.status)}</span>`:''}${poolOf(a.session)?` <span class="ast" style="opacity:.7">[${esc(poolOf(a.session))}]</span>`:''}</span>`).join('');
|
|
772
|
+
const agents=p.agents.sort((a,b)=>b.online-a.online).map(a=>`<span class="agent ${a.online?'':'offl'}${a.health==='down'?' down':a.health==='errored'?' err':''}" title="${esc(a.session)}${a.online?' · online':' · offline'}${a.health&&a.health!=='ok'?' · '+a.health:''}${a.staleHooks?' · OLD Trantor hooks ('+esc(a.hookVersion)+') — restart this session to get the baton safety fixes':''}">${iconFor(a.session,15)}<span class="nm">${esc(a.session)}</span>${a.staleHooks?` <span class="stale" title="running old Trantor hooks (${esc(a.hookVersion)}) — restart to load the baton storm/kill safety fixes">⚠ old hooks</span>`:''}${a.status?` <span class="ast">· ${esc(a.status)}</span>`:''}${poolOf(a.session)?` <span class="ast" style="opacity:.7">[${esc(poolOf(a.session))}]</span>`:''}</span>`).join('');
|
|
772
773
|
const cols=COLS.map(([k,label])=>{
|
|
773
774
|
let cards=pt.filter(t=>k==='testing'?(t.status==='testing'||t.status==='failed'):t.status===k);
|
|
774
775
|
if(k==='done')cards=[...cards].sort((a,b)=>(b.updated||0)-(a.updated||0)); // newest finished on top
|