trantor 0.18.53 → 0.18.55
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 +2 -0
- package/bin/crew/herdr.mjs +60 -4
- package/bin/crew/open.mjs +16 -6
- package/bin/crew/state.mjs +10 -1
- package/bin/crew-runner.mjs +111 -320
- package/bin/slop-gate.mjs +69 -41
- package/hooks/agent-notify.mjs +4 -13
- package/hooks/file-claim.mjs +6 -22
- package/hooks/handoff-now.mjs +5 -9
- package/hooks/heartbeat.mjs +16 -51
- package/hooks/inbox-deliver.mjs +8 -36
- package/hooks/lib/api.mjs +17 -66
- package/hooks/lib/handoff.mjs +69 -217
- package/hooks/lib/inbox-ledger.mjs +3 -11
- package/hooks/lib/resources.mjs +6 -16
- package/hooks/lib/update-check.mjs +4 -12
- package/hooks/overseer-warn.mjs +4 -16
- package/hooks/precompact.mjs +5 -10
- package/hooks/prompt-focus.mjs +6 -12
- package/hooks/sessionstart.mjs +38 -125
- package/hooks/statusline.mjs +4 -9
- package/hooks/stop-inbox.mjs +9 -49
- package/hooks/subagent-cost.mjs +2 -6
- package/hooks/subagent-start.mjs +4 -18
- package/hooks/todo-sync.mjs +2 -5
- package/hub/reaper.mjs +7 -2
- package/lib/autonomy.mjs +6 -30
- package/lib/balances.mjs +12 -48
- package/lib/classify-failure.mjs +6 -36
- package/lib/duty-nudges.mjs +2 -13
- package/lib/enroll.mjs +2 -12
- package/lib/identity.mjs +4 -22
- package/lib/integrate.mjs +6 -20
- package/lib/overseer.mjs +2 -15
- package/lib/project.mjs +22 -72
- package/lib/provider-keys.mjs +2 -5
- package/lib/providers.mjs +6 -27
- package/lib/redact.mjs +4 -24
- package/lib/same-project.mjs +2 -15
- package/lib/scrub.mjs +4 -13
- package/lib/seat-why.mjs +2 -9
- package/lib/seats.mjs +3 -18
- package/lib/signed-fetch.mjs +2 -8
- package/lib/splitbrain.mjs +4 -24
- package/lib/state/apply.mjs +7 -26
- package/lib/state/assemble.mjs +10 -33
- package/lib/state/cost.mjs +10 -32
- package/lib/state/derive.mjs +14 -52
- package/lib/state/driver.mjs +26 -108
- package/lib/state/gate.mjs +13 -71
- package/lib/state/migrate.mjs +3 -6
- package/lib/state/promote.mjs +7 -27
- package/lib/state/schema.mjs +12 -33
- package/lib/state/store.mjs +35 -121
- package/lib/state/validate.mjs +7 -31
- package/lib/store-contract.mjs +17 -40
- package/lib/store-pg.mjs +27 -32
- package/lib/subagent-manifest.mjs +2 -17
- package/lib/subagent-scan.mjs +0 -0
- package/lib/turn-policy.mjs +10 -40
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.55",
|
|
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/README.md
CHANGED
|
@@ -368,6 +368,8 @@ Waking works the same way from the other end: hover a sleeping project in the ap
|
|
|
368
368
|
**Wake** hosts its session as an in-app pane — `trantor open` under the hood, opened in the
|
|
369
369
|
project's own checkout wherever it is called from, handoff-beats-resume, and the same kickoff
|
|
370
370
|
prompt so the woken session catches up (handoff, board, memory) and recaps on its own.
|
|
371
|
+
A reboot can leave herdr with a pane in its layout and no terminal behind it; `trantor open`,
|
|
372
|
+
the app's Resume and `trantor prune` all treat such a pane as dead and host a fresh one.
|
|
371
373
|
|
|
372
374
|
Why crews never exhaust the orchestrator: bus messages are **by reference** (~70 tokens),
|
|
373
375
|
work products stay in each agent's own context — the orchestrator burns at coordination
|
package/bin/crew/herdr.mjs
CHANGED
|
@@ -37,11 +37,26 @@ export function splitPane(ctx, pane, direction, cwd = "", execute = herdrCall) {
|
|
|
37
37
|
return parsed?.result?.pane?.pane_id || "";
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// A pane herdr LISTS is not always a pane herdr can RUN (#7247: a restart kept w2:p8 in the
|
|
41
|
+
// layout, so list/get/rename answered, while read/process-info/run said pane_not_found). Rename
|
|
42
|
+
// is a layout question; process-info is the terminal question, and only a terminal hosts a session.
|
|
43
|
+
export function paneAlive(ctx, pane) {
|
|
44
|
+
if (!pane) return false;
|
|
45
|
+
const result = herdrCall(ctx, ["pane", "process-info", "--pane", pane]);
|
|
46
|
+
const parsed = parseJsonOutput(result.stdout);
|
|
47
|
+
return Boolean(result.ok && parsed && !parsed.error && parsed.result?.process_info);
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
export function workspacePane(ctx, workspace, preferredCwd) {
|
|
41
|
-
const
|
|
42
|
-
const panes = Array.isArray(parsed) ? parsed : parsed?.panes || parsed?.result?.panes || [];
|
|
51
|
+
const panes = listPanes(ctx);
|
|
43
52
|
const matches = panes.filter(pane => (pane.workspace_id || pane.workspace || "") === workspace);
|
|
44
|
-
|
|
53
|
+
const preferred = matches.filter(pane => (pane.cwd || "") === preferredCwd);
|
|
54
|
+
const ordered = [...preferred, ...matches.filter(pane => !preferred.includes(pane))];
|
|
55
|
+
for (const pane of ordered) {
|
|
56
|
+
const id = pane.pane_id || pane.id || "";
|
|
57
|
+
if (id && paneAlive(ctx, id)) return id;
|
|
58
|
+
}
|
|
59
|
+
return "";
|
|
45
60
|
}
|
|
46
61
|
|
|
47
62
|
export function closeWorkspace(ctx, id) {
|
|
@@ -70,13 +85,47 @@ function runSeat(ctx, pane, agent, command) {
|
|
|
70
85
|
}
|
|
71
86
|
|
|
72
87
|
function trackedWorkspace(ctx) {
|
|
73
|
-
|
|
88
|
+
// #7285: duplicate rows of ONE workspace must dedupe — a takeover used to append a second row of
|
|
89
|
+
// the live workspace, and slice(0,-1) then handed that live workspace to the stale list.
|
|
90
|
+
const ids = [];
|
|
91
|
+
for (const id of readRows(ctx).filter(row => row.project === ctx.project && row.kind === "herdrws").map(row => row.handle)) {
|
|
92
|
+
const at = ids.indexOf(id);
|
|
93
|
+
if (at >= 0) ids.splice(at, 1);
|
|
94
|
+
ids.push(id);
|
|
95
|
+
}
|
|
74
96
|
return { reuse: ids.at(-1) || "", stale: ids.slice(0, -1) };
|
|
75
97
|
}
|
|
76
98
|
|
|
99
|
+
function listPanes(ctx) {
|
|
100
|
+
const parsed = parseJsonOutput(herdrCall(ctx, ["pane", "list"]).stdout);
|
|
101
|
+
return Array.isArray(parsed) ? parsed : parsed?.panes || parsed?.result?.panes || [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// #7285: the workspaces hosting THIS project's tracked orchestrator pane. Herdr pane ids embed
|
|
105
|
+
// their workspace (#7247 forensics: 'w2:p4Y' lived in w2), so the handle prefix names the home
|
|
106
|
+
// without a round trip; when herdr answers, the pane list is a second opinion (stub ids carry no
|
|
107
|
+
// prefix). Closing such a workspace kills the orchestrator's shell.
|
|
108
|
+
function orchPaneWorkspaces(ctx) {
|
|
109
|
+
const handles = readRows(ctx).filter(row => row.project === ctx.project && row.kind === "orch").map(row => row.handle);
|
|
110
|
+
if (!handles.length) return new Set();
|
|
111
|
+
const spaces = new Set(handles.map(handle => handle.split(":")[0]).filter(Boolean));
|
|
112
|
+
if (ctx.dry) return spaces;
|
|
113
|
+
const tracked = new Set(handles);
|
|
114
|
+
for (const pane of listPanes(ctx)) {
|
|
115
|
+
if (!tracked.has(pane.pane_id || pane.id || "")) continue;
|
|
116
|
+
const space = pane.workspace_id || pane.workspace || "";
|
|
117
|
+
if (space) spaces.add(space);
|
|
118
|
+
}
|
|
119
|
+
return spaces;
|
|
120
|
+
}
|
|
121
|
+
|
|
77
122
|
function prepareWorkspace(ctx, prune) {
|
|
78
123
|
const tracked = trackedWorkspace(ctx);
|
|
124
|
+
const orchSpaces = orchPaneWorkspaces(ctx);
|
|
79
125
|
for (const id of tracked.stale) {
|
|
126
|
+
// #7285: never close reuse itself, nor a workspace hosting the tracked orch pane — `up` once
|
|
127
|
+
// killed its own orchestrator that way. The row stays: the workspace is still live.
|
|
128
|
+
if (id === tracked.reuse || orchSpaces.has(id)) continue;
|
|
80
129
|
console.log(` → closing stale stacked crew workspace for ${ctx.project} (${id})`);
|
|
81
130
|
closeWorkspace(ctx, id);
|
|
82
131
|
dropState(ctx, ctx.project, "herdrws", "", id);
|
|
@@ -92,6 +141,12 @@ function prepareWorkspace(ctx, prune) {
|
|
|
92
141
|
console.log(` → adopting existing untracked crew workspace for ${ctx.project} (${id})`);
|
|
93
142
|
reuse = id;
|
|
94
143
|
recordState(ctx, ctx.project, "herdrws", "__ws__", id);
|
|
144
|
+
} else if (orchSpaces.has(id)) {
|
|
145
|
+
// #7285: a labeled workspace hosting the tracked orch pane is the orchestrator's home, not a
|
|
146
|
+
// stray — adopt it, never close it (the #7285 rule extends past the stale loop).
|
|
147
|
+
console.log(` → adopting the crew workspace hosting the tracked orchestrator for ${ctx.project} (${id})`);
|
|
148
|
+
reuse = id;
|
|
149
|
+
recordState(ctx, ctx.project, "herdrws", "__ws__", id);
|
|
95
150
|
} else {
|
|
96
151
|
console.log(` → closing stray crew workspace for ${ctx.project} (${id})`);
|
|
97
152
|
closeWorkspace(ctx, id);
|
|
@@ -179,5 +234,6 @@ export function createHerdrAdapter(ctx) {
|
|
|
179
234
|
closeWorkspace: id => closeWorkspace(ctx, id),
|
|
180
235
|
closePane: id => closePane(ctx, id),
|
|
181
236
|
liveWorkspaces: () => workspaceList(ctx),
|
|
237
|
+
paneAlive: pane => paneAlive(ctx, pane),
|
|
182
238
|
};
|
|
183
239
|
}
|
package/bin/crew/open.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { basename, join } from "node:path";
|
|
5
5
|
import { spawn, execSync } from "node:child_process";
|
|
6
6
|
import { call, parseJsonOutput } from "./core.mjs";
|
|
7
|
-
import { createWorkspace, herdrCall, reportAgent, splitPane, workspaceList, workspacePane } from "./herdr.mjs";
|
|
7
|
+
import { createWorkspace, herdrCall, paneAlive, reportAgent, splitPane, workspaceList, workspacePane } from "./herdr.mjs";
|
|
8
8
|
import { dropState, readRows, recordState } from "./state.mjs";
|
|
9
9
|
import { resolveOrchestratorDir } from "./worktrees.mjs";
|
|
10
10
|
|
|
@@ -103,6 +103,14 @@ function tracked(ctx, live) {
|
|
|
103
103
|
function reattach(ctx, workspace, pane, id) {
|
|
104
104
|
if (!pane) return false;
|
|
105
105
|
if (ctx.dry) { console.log(`herdr:${workspace || "%DRYWS"}/${pane}`); return true; }
|
|
106
|
+
// Two probes, because herdr answers them from different places: a pane a restart left in the
|
|
107
|
+
// layout without a terminal still answers rename, and only process-info says it cannot host
|
|
108
|
+
// anything. Either way the row is dead and the open path hosts a fresh pane.
|
|
109
|
+
if (!paneAlive(ctx, pane)) {
|
|
110
|
+
dropState(ctx, ctx.project, "orch");
|
|
111
|
+
console.error(`— tracked orchestrator pane ${pane} has no terminal behind it (herdr restarted without it): hosting a fresh pane —`);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
106
114
|
const renamed = herdrCall(ctx, ["pane", "rename", pane, `orchestrator · ${ctx.project}`]);
|
|
107
115
|
if (!renamed.ok) { dropState(ctx, ctx.project, "orch"); return false; }
|
|
108
116
|
if (!paneHasAgent(ctx, pane)) {
|
|
@@ -166,11 +174,10 @@ export function maybeBuildGraft(dir) {
|
|
|
166
174
|
const cur = existsSync(gi) ? readFileSync(gi, "utf8") : "";
|
|
167
175
|
if (!/^graft\/?$/m.test(cur)) writeFileSync(gi, cur + (cur && !cur.endsWith("\n") ? "\n" : "") + "graft/\n");
|
|
168
176
|
} catch {}
|
|
169
|
-
// The build is backgrounded, so
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
//
|
|
177
|
+
// The build is backgrounded, so its exit code cannot gate the pane, but a failure has to land
|
|
178
|
+
// SOMEWHERE: under stdio:"ignore" a graft without native parsers (see deploy/setup.sh) failed in
|
|
179
|
+
// silence while the line below still claimed an index was being built.
|
|
180
|
+
|
|
174
181
|
// The log goes to the OS temp dir rather than the project: nothing to gitignore, nothing to clean
|
|
175
182
|
// up, and no exit handler — after unref() the parent usually dies first, so an on-exit cleanup
|
|
176
183
|
// would be a promise we cannot keep. It is simply overwritten by the next open of this project.
|
|
@@ -203,6 +210,9 @@ export function openOrchestrator(ctx, args) {
|
|
|
203
210
|
const prior = tracked(ctx, live);
|
|
204
211
|
if (reattach(ctx, prior.workspace, prior.pane, id)) return 0;
|
|
205
212
|
const chosen = chooseWorkspace(ctx, live, prior.workspace);
|
|
213
|
+
// #7285: ONE herdrws row per project — re-record exactly like the orch row below, never append.
|
|
214
|
+
// The appended duplicate made the next `up` close the "stale" copy of the live workspace.
|
|
215
|
+
dropState(ctx, ctx.project, "herdrws");
|
|
206
216
|
recordState(ctx, ctx.project, "herdrws", "__ws__", chosen.workspace);
|
|
207
217
|
const pane = hostPane(ctx, chosen, id);
|
|
208
218
|
dropState(ctx, ctx.project, "orch");
|
package/bin/crew/state.mjs
CHANGED
|
@@ -27,6 +27,9 @@ export function writeRows(ctx, rows) {
|
|
|
27
27
|
export function recordState(ctx, project, kind, agent, handle) {
|
|
28
28
|
if (ctx.dry) return;
|
|
29
29
|
const rows = readRows(ctx);
|
|
30
|
+
// #7285: an exact duplicate carries no new fact, and a doubled herdrws row is how `up` came to
|
|
31
|
+
// see the live workspace as its own stale copy. One row per fact — refuse the push.
|
|
32
|
+
if (rows.some(row => row.project === project && row.kind === kind && row.agent === agent && row.handle === handle)) return;
|
|
30
33
|
rows.push({ project, kind, agent, handle });
|
|
31
34
|
writeRows(ctx, rows);
|
|
32
35
|
}
|
|
@@ -139,7 +142,13 @@ export function prune(ctx, adapters) {
|
|
|
139
142
|
if (row.kind === "cmuxws") return !cmuxLive.proven || cmuxLive.ids.has(row.handle);
|
|
140
143
|
if (row.kind === "cmux") return !cmuxLive.proven || cmuxLive.names.has(`trantor:${row.project}`);
|
|
141
144
|
if (row.kind === "herdrws") return !herdrLive.proven || herdrLive.ids.has(row.handle);
|
|
142
|
-
|
|
145
|
+
// Workspace first (the cmux 0.17.61 lesson: pane ids there were not a liveness fact), then
|
|
146
|
+
// the one per-pane fact herdr does state plainly: a pane whose terminal is gone answers
|
|
147
|
+
// process-info with pane_not_found. A pane that merely hosts a shell stays.
|
|
148
|
+
if (["herdr", "orch"].includes(row.kind)) {
|
|
149
|
+
if (!herdrLive.proven) return true;
|
|
150
|
+
return herdrLive.names.has(`trantor:${row.project}`) && (!adapters.herdr.paneAlive || adapters.herdr.paneAlive(row.handle));
|
|
151
|
+
}
|
|
143
152
|
return true;
|
|
144
153
|
});
|
|
145
154
|
writeRows(ctx, kept);
|