trantor 0.17.64 → 0.17.65
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/hooks/heartbeat.mjs +16 -0
- package/hub.mjs +32 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.65",
|
|
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/hooks/heartbeat.mjs
CHANGED
|
@@ -147,6 +147,22 @@ async function main(stdinRaw) {
|
|
|
147
147
|
}
|
|
148
148
|
} catch {}
|
|
149
149
|
|
|
150
|
+
// Overseer narration, same ambient pattern (10-min machine-wide stamp): the narrate worker was
|
|
151
|
+
// built "to run ambiently from the heartbeat" but was never actually wired in — warns sat
|
|
152
|
+
// mechanical forever unless someone ran it by hand. The worker exits in one cheap signed GET per
|
|
153
|
+
// hub when nothing is unnarrated; Scrooge is only invoked when there IS a warn to explain.
|
|
154
|
+
try {
|
|
155
|
+
const narStamp = join(homedir(), ".agent-bus", "narrate.stamp");
|
|
156
|
+
const last = existsSync(narStamp) ? Number(readFileSync(narStamp, "utf8")) || 0 : 0;
|
|
157
|
+
if (Date.now() - last > 10 * 60 * 1000) {
|
|
158
|
+
writeFileSync(narStamp, String(Date.now()));
|
|
159
|
+
const worker = spawn(process.execPath, [join(HERE, "..", "bin", "overseer-narrate.mjs"), "--quiet"], {
|
|
160
|
+
detached: true, stdio: "ignore",
|
|
161
|
+
});
|
|
162
|
+
worker.unref();
|
|
163
|
+
}
|
|
164
|
+
} catch {}
|
|
165
|
+
|
|
150
166
|
// Same cadence as the presence ping: check context pressure and hand off early
|
|
151
167
|
// if we've crossed the warn threshold of a known window.
|
|
152
168
|
await maybeEarlyWarn(stdinRaw, session);
|
package/hub.mjs
CHANGED
|
@@ -214,6 +214,10 @@ import("./lib/overseer.mjs").then(m => { _overseer = m; }).catch(() => {});
|
|
|
214
214
|
const OVERSEER_TICK_MS = Number(process.env.RELAY_OVERSEER_TICK_MS || 30 * 1000);
|
|
215
215
|
const OVERSEER_DEDUP_MS = Number(process.env.RELAY_OVERSEER_DEDUP_MS || 10 * 60 * 1000);
|
|
216
216
|
const overseerWarned = new Map(); // dedup key -> ts
|
|
217
|
+
// Heartbeat for the WATCHER itself: /overseer/status must distinguish "fleet is clear" from "the
|
|
218
|
+
// overseer stopped ticking" — a monitor that cannot prove it is alive reads as clear when dead.
|
|
219
|
+
let overseerLastTick = 0;
|
|
220
|
+
let overseerLastCollisions = [];
|
|
217
221
|
function overseerPolicy() {
|
|
218
222
|
const p = state.orgPolicy && typeof state.orgPolicy === "object" ? state.orgPolicy : {};
|
|
219
223
|
return {
|
|
@@ -236,6 +240,7 @@ function overseerTick() {
|
|
|
236
240
|
if (!_overseer?.detectCollisions) return;
|
|
237
241
|
let collisions = [];
|
|
238
242
|
try { collisions = _overseer.detectCollisions(overseerInputs()) || []; } catch { return; }
|
|
243
|
+
overseerLastTick = now(); overseerLastCollisions = collisions;
|
|
239
244
|
const cut = now() - OVERSEER_DEDUP_MS;
|
|
240
245
|
for (const [k, ts] of overseerWarned) if (ts < cut) overseerWarned.delete(k);
|
|
241
246
|
const pol = overseerPolicy();
|
|
@@ -495,7 +500,7 @@ function cmpSemver(a, b) {
|
|
|
495
500
|
const AUTH_HEADERS = ["x-trantor-pubkey", "x-trantor-sig", "x-trantor-ts", "x-trantor-nonce"];
|
|
496
501
|
const PUBLIC_ENDPOINTS = new Set(["/", "/ui", "/health", "/enroll"]);
|
|
497
502
|
const OWNER_ENDPOINTS = new Set(["/project/delete", "/sweep", "/reconcile", "/invite", "/import", "/policy"]);
|
|
498
|
-
const READ_ENDPOINTS = new Set(["/peers", "/tasks", "/events", "/inbox", "/peer", "/card", "/stream", "/history", "/projects", "/catchup", "/phases", "/recent", "/handoffs", "/verify-gates", "/claims", "/overseer/context"]);
|
|
503
|
+
const READ_ENDPOINTS = new Set(["/peers", "/tasks", "/events", "/inbox", "/peer", "/card", "/stream", "/history", "/projects", "/catchup", "/phases", "/recent", "/handoffs", "/verify-gates", "/claims", "/overseer/context", "/overseer/status"]);
|
|
499
504
|
const roleRank = { read: 1, write: 2, owner: 3 };
|
|
500
505
|
const hasAuthHeaders = (req) => AUTH_HEADERS.some(h => !!req.headers[h]);
|
|
501
506
|
const authPath = (u) => `${u.pathname}${u.search || ""}`;
|
|
@@ -1013,6 +1018,32 @@ const server = http.createServer(async (req, res) => {
|
|
|
1013
1018
|
}
|
|
1014
1019
|
// What a session arriving on <project> needs to know: its autonomy level, who else is live,
|
|
1015
1020
|
// which files are in flight, which projects are declared codependent, current collisions.
|
|
1021
|
+
if (req.method === "GET" && P === "/overseer/status") {
|
|
1022
|
+
// The Overseer view's backbone: is the watcher ALIVE, and what is it watching right now.
|
|
1023
|
+
// `warnings` is the LIVE detection result from the last tick (pre-dedup), not the event log —
|
|
1024
|
+
// the log answers "what did it do", this answers "what does it see".
|
|
1025
|
+
const pol = overseerPolicy();
|
|
1026
|
+
const cutoff = now() - ONLINE_MS;
|
|
1027
|
+
const livePeers = Object.entries(state.peers).filter(([, v]) => v.lastSeen > cutoff);
|
|
1028
|
+
pruneClaims();
|
|
1029
|
+
return json(res, 200, {
|
|
1030
|
+
engine: !!_overseer?.detectCollisions,
|
|
1031
|
+
lastTickTs: overseerLastTick,
|
|
1032
|
+
tickMs: OVERSEER_TICK_MS,
|
|
1033
|
+
dedupMs: OVERSEER_DEDUP_MS,
|
|
1034
|
+
dutySession: DUTY_SESSION || "",
|
|
1035
|
+
watching: {
|
|
1036
|
+
sessions: livePeers.length,
|
|
1037
|
+
projects: new Set(livePeers.map(([, v]) => v.project).filter(Boolean)).size,
|
|
1038
|
+
claims: fileClaims.size,
|
|
1039
|
+
links: pol.links.length,
|
|
1040
|
+
},
|
|
1041
|
+
autonomy: pol.autonomy,
|
|
1042
|
+
links: pol.links,
|
|
1043
|
+
warnings: overseerLastCollisions,
|
|
1044
|
+
warnedRecent: overseerWarned.size,
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1016
1047
|
if (req.method === "GET" && P === "/overseer/context") {
|
|
1017
1048
|
const proj = canon(String(q.project || "").slice(0, 80));
|
|
1018
1049
|
if (!proj) return json(res, 400, { error: "project required" });
|