trantor 0.18.106 → 0.18.108
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/crew-runner.mjs +11 -6
- package/bin/doctor.mjs +12 -0
- package/hooks/lib/handoff.mjs +9 -3
- package/lib/herdr-integration.mjs +28 -0
- package/lib/model-catalog.mjs +14 -0
- package/lib/state/flags.mjs +22 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.108",
|
|
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/crew-runner.mjs
CHANGED
|
@@ -39,13 +39,13 @@ import {
|
|
|
39
39
|
} from "../lib/duty-nudges.mjs";
|
|
40
40
|
import { reserveNudgeIds, releaseNudgeIds } from "../lib/nudge-ledger.mjs";
|
|
41
41
|
import { dutyRecipientResolver } from "../lib/duty-recipient.mjs";
|
|
42
|
-
import { cliEffortFlag, contextWindow } from "../lib/model-catalog.mjs";
|
|
42
|
+
import { cliEffortFlag, contextWindow, effortForTurn } from "../lib/model-catalog.mjs";
|
|
43
43
|
import {
|
|
44
44
|
BREAKER_WINDOW, STATE_ENV, TURN_RESULT_SCHEMA,
|
|
45
45
|
breakerVerdict, describeTurn, hasJsonSchemaFlag, parseEnvelope, renderCardTail, runStep,
|
|
46
46
|
} from "../lib/state/driver.mjs";
|
|
47
47
|
import { costLine } from "../lib/state/cost.mjs";
|
|
48
|
-
import { resolveStateFlags } from "../lib/state/flags.mjs";
|
|
48
|
+
import { resolveStateFlags, stateFeatureOn } from "../lib/state/flags.mjs";
|
|
49
49
|
|
|
50
50
|
import { awaitSwapRelease } from "./crew/stage.mjs";
|
|
51
51
|
|
|
@@ -660,7 +660,8 @@ const stateFlags = resolveStateFlags();
|
|
|
660
660
|
for (const [name, flag] of Object.entries(stateFlags)) {
|
|
661
661
|
if (flag.value && process.env[name] === undefined) process.env[name] = flag.value;
|
|
662
662
|
}
|
|
663
|
-
|
|
663
|
+
// TRANTOR_STATE=0 is the master kill switch (#12070): it beats an ASSEMBLE=1 from any layer.
|
|
664
|
+
const STATE_FLAG_ON = stateFeatureOn(STATE_ENV, stateFlags, ["1"]);
|
|
664
665
|
const STATE_SCHEMA_FILE = join(homedir(), ".agent-bus", `state-schema-${PERSONA}-${PROJ}.json`);
|
|
665
666
|
const STATE_MODE = (() => {
|
|
666
667
|
if (!STATE_FLAG_ON) return false;
|
|
@@ -837,12 +838,13 @@ async function runTurn(prompt, isFirst, trigger = "kickoff", opts = {}) {
|
|
|
837
838
|
// #10197: the card status snapshot the turn is judged against at its end — a move to
|
|
838
839
|
// testing/done under the turn is delivery even without a commit. null = the hub read failed:
|
|
839
840
|
// the delivery check stands down rather than claim no-delivery on a transport error.
|
|
840
|
-
let cardStatusBefore = null, cardLogBefore = [];
|
|
841
|
+
let cardStatusBefore = null, cardLogBefore = [], cardDifficulty = "";
|
|
841
842
|
if (!opts.state && sessionCard > 0) {
|
|
842
843
|
try {
|
|
843
844
|
const { task } = await api(`/card?project=${encodeURIComponent(PROJ)}&id=${sessionCard}`);
|
|
844
845
|
cardStatusBefore = task ? String(task.status || "") : null;
|
|
845
846
|
cardLogBefore = task?.log || [];
|
|
847
|
+
cardDifficulty = task?.difficulty || "";
|
|
846
848
|
} catch { cardStatusBefore = null; }
|
|
847
849
|
}
|
|
848
850
|
// #9778: the docker snapshot the turn is judged against — containers the CLI spawns are recorded
|
|
@@ -857,7 +859,9 @@ async function runTurn(prompt, isFirst, trigger = "kickoff", opts = {}) {
|
|
|
857
859
|
// The flagged row (TDD §4.6). `{S}` exists only in `stateNext`, so the replaceAll below is a
|
|
858
860
|
// no-op on every other path — which is what "flag off = byte-identical" has to mean.
|
|
859
861
|
if (opts.state && cli.stateNext) cmd = cli.stateNext;
|
|
860
|
-
|
|
862
|
+
// Effort follows the bound card's difficulty each turn; kickoff, pulse and unreadable cards keep the launch effort.
|
|
863
|
+
const turnEffort = effortForTurn({ agent: DRIVER, model: MODEL, launchEffort: EFFORT, cardDifficulty });
|
|
864
|
+
const mfrag = (MODEL && cli.mflag ? `${cli.mflag}${MODEL}` : "") + turnEffort.flag;
|
|
861
865
|
cmd = cmd.replaceAll("{M}", mfrag).replaceAll("{P}", pf).replaceAll("{SID}", sid).replaceAll("{DIR}", TURN_DIR)
|
|
862
866
|
.replaceAll("{S}", STATE_SCHEMA_FILE);
|
|
863
867
|
// PRECEDENCE: each file is PREPENDED, so the list is iterated in written order, highest priority
|
|
@@ -869,7 +873,8 @@ async function runTurn(prompt, isFirst, trigger = "kickoff", opts = {}) {
|
|
|
869
873
|
cmd = withEnvFiles(withSecretExports(seatSignalPrefix(TURN_DIR) + cmd, Object.keys(secrets)), envs);
|
|
870
874
|
log(`turn starting (${isFirst ? "fresh session" : "resume"})${MODEL ? ` · model=${MODEL}` : ""}`);
|
|
871
875
|
// #7777: the one effort line — which effort parameters were set, or that the model is uncatalogued.
|
|
872
|
-
if (
|
|
876
|
+
if (turnEffort.changed) log(`effort: ${turnEffort.effort.difficulty} for #${sessionCard} (launch ${EFFORT?.difficulty || "none"}) — ${turnEffort.text}`);
|
|
877
|
+
else if (EFFORT && EFFORT_FLAG.text) log(EFFORT_FLAG.text);
|
|
873
878
|
// #11559: the one compaction line — only where the block actually rides the spawn.
|
|
874
879
|
if (cli.pinned && OC_COMPACTION) log(ocCompactionLogLine(OC_COMPACTION_WINDOW));
|
|
875
880
|
cmuxStatus("building", "#4a90d9", "hammer", { priority: 50 }); herdrAgent("working");
|
package/bin/doctor.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import { sfetchJson } from "../lib/signed-fetch.mjs";
|
|
|
14
14
|
import { scan } from "../lib/splitbrain.mjs";
|
|
15
15
|
import { resolveSecrets, envFileSecrets, backendFor } from "../lib/secrets.mjs";
|
|
16
16
|
import { resolveStateFlags } from "../lib/state/flags.mjs";
|
|
17
|
+
import { probeClaudeIntegration, HERDR_CLAUDE_INSTALL } from "../lib/herdr-integration.mjs";
|
|
17
18
|
|
|
18
19
|
const H = homedir();
|
|
19
20
|
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
@@ -182,6 +183,17 @@ else {
|
|
|
182
183
|
: warn("plugin not installed", "claude plugin marketplace add sashabogi/trantor && claude plugin install trantor");
|
|
183
184
|
}
|
|
184
185
|
|
|
186
|
+
// herdr's own claude integration reports the session id at SessionStart; without it the desktop app
|
|
187
|
+
// cannot tell which session a pane is running. Read-only probe, and an odd answer is "unknown".
|
|
188
|
+
section("herdr (session identity)");
|
|
189
|
+
{
|
|
190
|
+
const h = probeClaudeIntegration();
|
|
191
|
+
if (h.state === "absent") note("herdr: skipped (not installed)");
|
|
192
|
+
else if (h.state === "installed") ok(`herdr claude integration installed (${h.detail})`);
|
|
193
|
+
else if (h.state === "unknown") note(`herdr claude integration: unknown (${h.detail})`);
|
|
194
|
+
else warn(h.state === "outdated" ? `herdr claude integration is outdated (${h.detail})` : "herdr claude integration is not installed", HERDR_CLAUDE_INSTALL);
|
|
195
|
+
}
|
|
196
|
+
|
|
185
197
|
// crew CLIs: installed / wired / authenticated
|
|
186
198
|
section("crew CLIs (install any subset — seats follow the work)");
|
|
187
199
|
const CLIS = [
|
package/hooks/lib/handoff.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import { resolveProject, orchSessionsPath, hostId } from "../../lib/project.mjs"
|
|
|
18
18
|
// attachState async on a path that is deliberately synchronous.
|
|
19
19
|
import { statePath, readState } from "../../lib/state/store.mjs";
|
|
20
20
|
import { stateError } from "../../lib/state/schema.mjs";
|
|
21
|
+
import { resolveStateFlags, stateFeatureOn } from "../../lib/state/flags.mjs";
|
|
21
22
|
import { deriveState } from "../../lib/state/derive.mjs";
|
|
22
23
|
|
|
23
24
|
// Writer and reader MUST resolve the same directory — see lib/project.mjs busDir(). This used to
|
|
@@ -59,6 +60,7 @@ export function contextUsage(transcriptPath, conf = readConfig()) {
|
|
|
59
60
|
let model = "";
|
|
60
61
|
for (const line of lines) {
|
|
61
62
|
let r; try { r = JSON.parse(line); } catch { continue; }
|
|
63
|
+
if (r?.type === "system" && r?.subtype === "compact_boundary") { rows.push("compact"); continue; }
|
|
62
64
|
const u = r?.message?.usage;
|
|
63
65
|
if (r?.type !== "assistant" || !u) continue;
|
|
64
66
|
const tokens = (u.input_tokens || 0) + (u.cache_read_input_tokens || 0) + (u.cache_creation_input_tokens || 0);
|
|
@@ -78,6 +80,8 @@ export function guardContextTokens(rows) {
|
|
|
78
80
|
let max = 0;
|
|
79
81
|
const recent = [];
|
|
80
82
|
for (const t of rows) {
|
|
83
|
+
// a compaction starts the session over: readings before it say nothing about the window now
|
|
84
|
+
if (t === "compact") { max = 0; recent.length = 0; continue; }
|
|
81
85
|
if (!t || t <= 0) continue;
|
|
82
86
|
recent.push(t);
|
|
83
87
|
if (recent.length > 5) recent.shift();
|
|
@@ -441,9 +445,11 @@ export function capSummary(text, cap = 4096) {
|
|
|
441
445
|
// `state` rides beside it, validated on write and NEVER capped, so capSummary cannot eat a member (#6528).
|
|
442
446
|
|
|
443
447
|
/** Dark by default. The prose path is untouched either way; this flag only decides whether the
|
|
444
|
-
* structured field is built and rendered (TDD §4.5, "Fallback").
|
|
445
|
-
|
|
446
|
-
|
|
448
|
+
* structured field is built and rendered (TDD §4.5, "Fallback"). The hooks never source
|
|
449
|
+
* ~/.agent-bus/.env, so the flag resolves like the runner's: process env first, that file second
|
|
450
|
+
* (#12069), and a TRANTOR_STATE=0 master switch turns it off (#12070). */
|
|
451
|
+
export function stateHandoffEnabled(env = process.env, file) {
|
|
452
|
+
return stateFeatureOn("TRANTOR_STATE_HANDOFF", resolveStateFlags(env, file));
|
|
447
453
|
}
|
|
448
454
|
|
|
449
455
|
/** The bus id of the seat writing this handoff — the same resolution sessionstart.mjs uses, so a
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Is herdr's own claude integration installed? Session identity in the desktop app relies on it
|
|
2
|
+
// reporting the session at SessionStart (pane.report_agent_session), and nothing else checks it.
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
export const HERDR_CLAUDE_INSTALL = "herdr integration install claude";
|
|
6
|
+
|
|
7
|
+
/** Decide from `herdr integration status` output (pure; `ran` false = herdr would not start).
|
|
8
|
+
* Returns { state: absent | installed | outdated | missing | unknown, detail }; an unrecognised
|
|
9
|
+
* shape is "unknown", never an error. */
|
|
10
|
+
export function claudeIntegrationState({ ran, status = 0, stdout = "" } = {}) {
|
|
11
|
+
if (!ran) return { state: "absent", detail: "herdr is not installed" };
|
|
12
|
+
if (status !== 0) return { state: "unknown", detail: `herdr integration status exited ${status}` };
|
|
13
|
+
const line = String(stdout).split("\n").map((l) => l.trim()).find((l) => l.startsWith("claude:"));
|
|
14
|
+
if (!line) return { state: "unknown", detail: "no claude line in the herdr integration status output" };
|
|
15
|
+
const rest = line.slice("claude:".length).trim();
|
|
16
|
+
if (/^not installed\b/i.test(rest)) return { state: "missing", detail: rest };
|
|
17
|
+
if (/^outdated\b/i.test(rest)) return { state: "outdated", detail: rest };
|
|
18
|
+
if (/^current\b/i.test(rest)) return { state: "installed", detail: rest };
|
|
19
|
+
return { state: "unknown", detail: rest };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Run the read-only probe. A missing binary, a timeout or a crash all fold into the decision. */
|
|
23
|
+
export function probeClaudeIntegration(run = spawnSync) {
|
|
24
|
+
const r = run("herdr", ["integration", "status"], { encoding: "utf8", timeout: 5000 });
|
|
25
|
+
if (r.error?.code === "ENOENT") return claudeIntegrationState({ ran: false });
|
|
26
|
+
if (r.error) return { state: "unknown", detail: String(r.error.message || r.error) };
|
|
27
|
+
return claudeIntegrationState({ ran: true, status: r.status ?? 1, stdout: r.stdout });
|
|
28
|
+
}
|
package/lib/model-catalog.mjs
CHANGED
|
@@ -119,3 +119,17 @@ export function cliEffortFlag(agent, effort) {
|
|
|
119
119
|
}
|
|
120
120
|
return { flag: "", text: `effort(${difficulty}): ${model} in catalog — no per-request effort parameters (provider default)` };
|
|
121
121
|
}
|
|
122
|
+
|
|
123
|
+
// effortForTurn: the effort a single turn runs at. A bound card's easy/medium/hard difficulty wins
|
|
124
|
+
// over the launch effort; anything else (no card, unknown value, CLI with no flag) keeps launch.
|
|
125
|
+
export function effortForTurn({ agent, model, launchEffort, cardDifficulty }, cat = loadCatalog()) {
|
|
126
|
+
const launch = { effort: launchEffort || null, ...(launchEffort ? cliEffortFlag(agent, launchEffort) : { flag: "", text: "" }) };
|
|
127
|
+
const d = String(cardDifficulty || "").toLowerCase();
|
|
128
|
+
if (!["easy", "medium", "hard"].includes(d) || d === launchEffort?.difficulty) return { ...launch, changed: false };
|
|
129
|
+
try {
|
|
130
|
+
const effort = resolveEffort(agent, model || launchEffort?.model || "", d, cat);
|
|
131
|
+
const cli = cliEffortFlag(agent, effort);
|
|
132
|
+
if (!cli.flag) return { ...launch, changed: false };
|
|
133
|
+
return { effort, ...cli, changed: true };
|
|
134
|
+
} catch { return { ...launch, changed: false }; }
|
|
135
|
+
}
|
package/lib/state/flags.mjs
CHANGED
|
@@ -14,9 +14,15 @@ export const STATE_ENV = "TRANTOR_STATE_ASSEMBLE";
|
|
|
14
14
|
// resolution cannot drift apart.
|
|
15
15
|
export const STATE_FLAGS = ["TRANTOR_STATE", "TRANTOR_STATE_ASSEMBLE", "TRANTOR_STATE_HANDOFF", "TRANTOR_STATE_GATE"];
|
|
16
16
|
|
|
17
|
+
/** The crew .env. AGENT_BUS_DIR (already how tests and the handoff dir are redirected) moves it too,
|
|
18
|
+
* so a run pointed at a scratch bus never reads the operator's real file. */
|
|
19
|
+
export function defaultFlagsFile(env = process.env) {
|
|
20
|
+
return join(env.AGENT_BUS_DIR || join(homedir(), ".agent-bus"), ".env");
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
/** `{ [name]: { value, layer } }` per flag; layer is "process env", "~/.agent-bus/.env (crew)" or
|
|
18
24
|
* "unset". Pure — env in, no writes — so the doctor reports the same answer the runner got. */
|
|
19
|
-
export function resolveStateFlags(env = process.env, file =
|
|
25
|
+
export function resolveStateFlags(env = process.env, file = defaultFlagsFile(env)) {
|
|
20
26
|
const fromFile = parseEnvFile(file); // a missing file parses to {}
|
|
21
27
|
return Object.fromEntries(STATE_FLAGS.map((name) => {
|
|
22
28
|
if (env[name] !== undefined) return [name, { value: env[name], layer: "process env" }];
|
|
@@ -24,3 +30,18 @@ export function resolveStateFlags(env = process.env, file = join(homedir(), ".ag
|
|
|
24
30
|
return [name, { value: "", layer: "unset" }];
|
|
25
31
|
}));
|
|
26
32
|
}
|
|
33
|
+
|
|
34
|
+
const OFF_VALUES = ["0", "false", "off", "no"];
|
|
35
|
+
|
|
36
|
+
/** True when TRANTOR_STATE resolved to an explicit off value: the master kill switch. Unset or any
|
|
37
|
+
* other value leaves the sub-flags in charge. */
|
|
38
|
+
export function stateMasterOff(resolved) {
|
|
39
|
+
return OFF_VALUES.includes(String(resolved?.TRANTOR_STATE?.value ?? "").trim().toLowerCase());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Whether one state sub-feature (e.g. TRANTOR_STATE_HANDOFF) is on: the master switch wins, then
|
|
43
|
+
* the sub-flag's own value. `on` lists the values that count as on (ASSEMBLE accepts only "1"). */
|
|
44
|
+
export function stateFeatureOn(name, resolved, on = ["1", "true", "on", "yes"]) {
|
|
45
|
+
if (stateMasterOff(resolved)) return false;
|
|
46
|
+
return on.includes(String(resolved?.[name]?.value ?? "").trim().toLowerCase());
|
|
47
|
+
}
|