trantor 0.17.71 → 0.17.73
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/connect.mjs +88 -1
- package/bin/crew-runner.mjs +15 -2
- package/bin/doctor.mjs +7 -0
- package/mcp.mjs +8 -4
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.73",
|
|
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/connect.mjs
CHANGED
|
@@ -87,8 +87,95 @@ if (has("opencode")) {
|
|
|
87
87
|
}), p);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// ---- DeepSeek Harness (dsh) ----
|
|
91
|
+
// dsh has no single MCP config file — composition is a PROFILE (~/.dsh/profiles/<name>): a package.json
|
|
92
|
+
// naming the bundles it stacks and a cordis.patch.yml inserting plugin rows. We build a "trantor"
|
|
93
|
+
// profile on the stock headless bundle and mount two rows:
|
|
94
|
+
// 1. their Claude Code hooks bridge pointed at OUR hooks.json — presence, focus cards, heartbeats,
|
|
95
|
+
// file claims run inside dsh exactly as they do inside CC (verified live 2026-08-19);
|
|
96
|
+
// 2. their MCP client spawning our relay server — relay_* tools with the seat identity forwarded
|
|
97
|
+
// from the ambient RELAY_* env (crew-runner sets those per seat).
|
|
98
|
+
// The bridge's own protocol lib is declared as a dependency explicitly: the rc package forgets it
|
|
99
|
+
// (ERR_MODULE_NOT_FOUND at boot without it — reported upstream).
|
|
100
|
+
if (has("dsh")) {
|
|
101
|
+
const ROOT = dirname(MCP);
|
|
102
|
+
const prof = join(homedir(), ".dsh", "profiles", "trantor");
|
|
103
|
+
const pkgPath = join(prof, "package.json");
|
|
104
|
+
const patchPath = join(prof, "cordis.patch.yml");
|
|
105
|
+
const seatHooksPath = join(prof, "hooks.seat.json");
|
|
106
|
+
const pkg = {
|
|
107
|
+
name: "dsh-profile-trantor", private: true,
|
|
108
|
+
dependencies: {
|
|
109
|
+
"@deepseek-ai/dsh-hooks-claude-code": "*",
|
|
110
|
+
"@deepseek-ai/dsh-hook-protocol": "*",
|
|
111
|
+
},
|
|
112
|
+
dsh: { profile: { bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-headless"] } },
|
|
113
|
+
};
|
|
114
|
+
const patch = `# trantor — generated by \`trantor connect\` (edits survive: regenerate by deleting this file)
|
|
115
|
+
- insert:
|
|
116
|
+
- id: trantor-cc-hooks
|
|
117
|
+
name: '@deepseek-ai/dsh-hooks-claude-code'
|
|
118
|
+
config:
|
|
119
|
+
configPath: ${seatHooksPath}
|
|
120
|
+
pluginRoot: ${ROOT}
|
|
121
|
+
- id: trantor-relay
|
|
122
|
+
name: '@deepseek-ai/dsh-mcp-client'
|
|
123
|
+
config:
|
|
124
|
+
serverName: relay
|
|
125
|
+
transport: stdio
|
|
126
|
+
command: node
|
|
127
|
+
args: ['${MCP}']
|
|
128
|
+
env:
|
|
129
|
+
RELAY_URL: !!js process.env.RELAY_URL ?? '${URL_}'
|
|
130
|
+
RELAY_AGENT: !!js process.env.RELAY_AGENT ?? 'dsh'
|
|
131
|
+
RELAY_PROJECT: !!js process.env.RELAY_PROJECT ?? ''
|
|
132
|
+
RELAY_SESSION: !!js process.env.RELAY_SESSION ?? ''
|
|
133
|
+
`;
|
|
134
|
+
const fresh = !existsSync(patchPath);
|
|
135
|
+
if (!fresh) report("dsh", "already wired", prof);
|
|
136
|
+
else {
|
|
137
|
+
if (!DRY) {
|
|
138
|
+
mkdirSync(prof, { recursive: true });
|
|
139
|
+
// The seat runs the plugin's hooks MINUS SessionStart. Two reasons: the crew runner already
|
|
140
|
+
// owns registration/announcement, and dsh (rc.7/8) crashes at headless teardown when a slow
|
|
141
|
+
// detached SessionStart hook resolves late — its agent.inject() races disposal ("Cannot read
|
|
142
|
+
// properties of undefined (reading 'prepare')", exit code flips). Verified: with SessionStart
|
|
143
|
+
// present the crash reproduces; without it, 0 failures across repeated runs.
|
|
144
|
+
try {
|
|
145
|
+
const full = JSON.parse(readFileSync(join(ROOT, "hooks", "hooks.json"), "utf8"));
|
|
146
|
+
const subset = Object.fromEntries(Object.entries(full.hooks || {}).filter(([k]) => k !== "SessionStart"));
|
|
147
|
+
writeFileSync(seatHooksPath, JSON.stringify({
|
|
148
|
+
description: "trantor dsh SEAT hooks — the plugin hooks.json minus SessionStart (regenerated by trantor connect; see bin/connect.mjs for why)",
|
|
149
|
+
hooks: subset,
|
|
150
|
+
}, null, 2) + "\n");
|
|
151
|
+
} catch {}
|
|
152
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
153
|
+
writeFileSync(patchPath, patch);
|
|
154
|
+
// the profile ROOT config. dsh self-heals a missing one, but the heal races the first boot
|
|
155
|
+
// (observed: "Cannot read properties of undefined (reading 'prepare')" on the very first
|
|
156
|
+
// seat turn, clean on every run after) — so write the complete profile up front.
|
|
157
|
+
const rootPath = join(prof, "cordis.yml");
|
|
158
|
+
if (!existsSync(rootPath)) writeFileSync(rootPath, "# dsh profile root — an empty entry list; the tree is composed from bundles + cordis.patch.yml.\n[]\n");
|
|
159
|
+
// pnpm settings mirroring dsh's own profile template. autoInstallPeers:false is LOAD-BEARING:
|
|
160
|
+
// an installer that pulls the bridge's peers drops a SECOND copy of dsh's core packages into
|
|
161
|
+
// the profile, the loader mounts services from both module instances, and the first tool call
|
|
162
|
+
// dies on ctx.tools[TOOL_RUNTIME_SCHEDULER] being undefined (observed: every turn that used
|
|
163
|
+
// any tool crashed "reading 'prepare'"; tool-free turns worked).
|
|
164
|
+
writeFileSync(join(prof, "pnpm-workspace.yaml"), "packages:\n - .\n\nnodeLinker: hoisted\nautoInstallPeers: false\n");
|
|
165
|
+
// the two bridge packages must be importable from the profile's node_modules — via pnpm
|
|
166
|
+
// (peers OFF, hoisted) like dsh's own template; npm needs --legacy-peer-deps for the same
|
|
167
|
+
// no-duplicate-core guarantee.
|
|
168
|
+
try {
|
|
169
|
+
execSync(has("pnpm") ? "pnpm install --silent" : "npm install --legacy-peer-deps --no-fund --no-audit --loglevel=error",
|
|
170
|
+
{ cwd: prof, stdio: "ignore", timeout: 180000 });
|
|
171
|
+
} catch { report("dsh", "profile written, but the install FAILED — run inside it: pnpm install (or npm install --legacy-peer-deps)"); }
|
|
172
|
+
}
|
|
173
|
+
if (!out.some(r => r.cli === "dsh")) report("dsh", "wired (profile created)", prof);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
90
177
|
const found = out.length;
|
|
91
178
|
console.log(`trantor connect${DRY ? " (dry run)" : ""} — hub: ${URL_}`);
|
|
92
179
|
for (const r of out) console.log(` ${r.cli.padEnd(9)} ${r.status}${r.detail ? ` (${r.detail})` : ""}`);
|
|
93
|
-
if (!found) console.log(" no supported CLIs found on PATH (claude, codex, gemini, kimi, opencode)");
|
|
180
|
+
if (!found) console.log(" no supported CLIs found on PATH (claude, codex, gemini, kimi, opencode, dsh)");
|
|
94
181
|
console.log(DRY ? "\nRun without --dry-run to apply." : "\nDone. New sessions of each CLI auto-join the bus.");
|
package/bin/crew-runner.mjs
CHANGED
|
@@ -94,6 +94,7 @@ const inCmux = () => !!process.env.CMUX_SURFACE_ID;
|
|
|
94
94
|
// actual LLM logo in the pill is not possible — brand COLOR + the agent's name in the label is the
|
|
95
95
|
// closest cmux allows.
|
|
96
96
|
const BRAND_HEX = { claude: "#D97757", codex: "#e8e8ee", openai: "#e8e8ee", deepseek: "#5786FE",
|
|
97
|
+
dsh: "#4D6BFE",
|
|
97
98
|
kimi: "#8b8bf5", moonshot: "#8b8bf5", glm: "#5ea0f5", zai: "#5ea0f5", gemini: "#8E75B2", openrouter: "#94A3B8" };
|
|
98
99
|
function cmuxStatus(value, color, icon = "robot", opts = {}) {
|
|
99
100
|
if (!inCmux()) return;
|
|
@@ -141,12 +142,19 @@ const CLI = {
|
|
|
141
142
|
next: `opencode run -c{M} "$(cat {P})"`, mflag: " -m ", env: join(homedir(), ".token-scrooge", ".env") },
|
|
142
143
|
claude: { first: `claude{M} -p "$(cat {P})" --dangerously-skip-permissions`,
|
|
143
144
|
next: `claude -c{M} -p "$(cat {P})" --dangerously-skip-permissions`, mflag: " --model " },
|
|
145
|
+
// DeepSeek Harness. Every turn is a FRESH session — headless has no resume yet — so the seat
|
|
146
|
+
// relies on the wake prompt + the board (via the relay tools its profile mounts) rather than
|
|
147
|
+
// conversation memory. `trantor connect` builds the ~/.dsh/profiles/trantor composition: their
|
|
148
|
+
// CC-hooks bridge running OUR hooks + their MCP client running our relay server. No model flag:
|
|
149
|
+
// headless takes only the task; the model is profile config.
|
|
150
|
+
dsh: { first: `dsh --profile trantor "$(cat {P})" < /dev/null`,
|
|
151
|
+
next: `dsh --profile trantor "$(cat {P})" < /dev/null`, mflag: "", env: join(homedir(), ".token-scrooge", ".env") },
|
|
144
152
|
};
|
|
145
153
|
// BYOM: any agent label that isn't a known native CLI is treated as an opencode-driven provider
|
|
146
154
|
// seat (opencode is the universal adapter). This is what lets a BROUGHT provider — `trantor up
|
|
147
155
|
// <label>:<provider>` for any opencode vendor the user configured — run with no per-provider code
|
|
148
156
|
// here; its model id arrives pre-qualified (`<provider>/<model>`) as CREW_MODEL.
|
|
149
|
-
const NATIVE = new Set(["codex", "gemini", "kimi", "claude"]);
|
|
157
|
+
const NATIVE = new Set(["codex", "gemini", "kimi", "claude", "dsh"]);
|
|
150
158
|
const cli = CLI[AGENT] || (NATIVE.has(AGENT) ? null : CLI.opencode);
|
|
151
159
|
if (!cli) { console.error(`unknown agent '${AGENT}' (native: ${[...NATIVE].join(", ")}; any other name = an opencode provider seat)`); process.exit(1); }
|
|
152
160
|
if (!CLI[AGENT]) log(`'${AGENT}' is not a built-in seat — running it as an opencode provider (BYOM)`);
|
|
@@ -191,7 +199,12 @@ const PENDING_MAX = 50;
|
|
|
191
199
|
// TRANTOR_RETRY_MS (comma-separated ms) shortens the ladder so the redelivery drill can exercise
|
|
192
200
|
// a real backoff in seconds instead of waiting out the production one.
|
|
193
201
|
const RETRY_MS = (() => {
|
|
194
|
-
|
|
202
|
+
// Guard the UNSET case explicitly: "".split(",") is [""], Number("") is 0, and a >=0 filter
|
|
203
|
+
// accepted it — so every production runner got a ZERO backoff and a failing seat became a
|
|
204
|
+
// retry storm (observed live: 43 crashed turns in ~3 minutes on the first dsh seat). The
|
|
205
|
+
// hermetic drill never caught it because it always SET the override.
|
|
206
|
+
const raw = process.env.TRANTOR_RETRY_MS;
|
|
207
|
+
const custom = raw ? raw.split(",").map(Number).filter(n => Number.isFinite(n) && n > 0) : [];
|
|
195
208
|
return custom.length ? custom : [30e3, 60e3, 120e3, 300e3, 900e3];
|
|
196
209
|
})();
|
|
197
210
|
function savePending(wake, bcast) {
|
package/bin/doctor.mjs
CHANGED
|
@@ -116,6 +116,13 @@ const CLIS = [
|
|
|
116
116
|
{ name: "kimi", bin: "kimi", wired: () => !!read(join(H, ".kimi", "mcp.json"))?.mcpServers?.relay, auth: () => existsSync(join(H, ".kimi", "credentials")), login: "kimi → /login (Kimi account or Moonshot API key)" },
|
|
117
117
|
{ name: "deepseek (via opencode)", bin: "opencode", wired: () => !!read(join(H, ".config", "opencode", "opencode.json"))?.mcp?.relay, auth: () => !!process.env.DEEPSEEK_API_KEY || (existsSync(join(H, ".agent-bus", ".env")) && readFileSync(join(H, ".agent-bus", ".env"), "utf8").includes("DEEPSEEK_API_KEY")) || !!read(join(H, ".local", "share", "opencode", "auth.json")), login: `get a key at platform.deepseek.com, then: echo 'DEEPSEEK_API_KEY=sk-…' >> ~/.agent-bus/.env` },
|
|
118
118
|
{ name: "glm (via opencode · coding plan)", bin: "opencode", wired: () => !!read(join(H, ".config", "opencode", "opencode.json"))?.mcp?.relay, auth: () => !!read(join(H, ".config", "opencode", "opencode.json"))?.provider?.["zai-coding-plan"]?.options?.apiKey, login: `put your Z.ai coding-plan key at ~/.config/opencode/opencode.json → provider["zai-coding-plan"].options.apiKey, then seat: trantor up glm:zai-coding-plan/glm-5.1` },
|
|
119
|
+
// DeepSeek Harness — the open-source harness (everything-is-a-plugin). Wired = the trantor
|
|
120
|
+
// profile exists (built by `trantor connect`: their CC-hooks bridge running OUR hooks + their MCP
|
|
121
|
+
// client running our relay). API-billed via DEEPSEEK_API_KEY, same key the opencode deepseek seat uses.
|
|
122
|
+
{ name: "dsh (DeepSeek Harness)", bin: "dsh",
|
|
123
|
+
wired: () => existsSync(join(H, ".dsh", "profiles", "trantor", "cordis.patch.yml")),
|
|
124
|
+
auth: () => !!process.env.DEEPSEEK_API_KEY || [join(H, ".token-scrooge", ".env"), join(H, ".agent-bus", ".env")].some(f => { try { return readFileSync(f, "utf8").includes("DEEPSEEK_API_KEY"); } catch { return false; } }),
|
|
125
|
+
login: "npm i -g @deepseek-ai/dsh && trantor connect (uses DEEPSEEK_API_KEY). Seat: trantor up dsh" },
|
|
119
126
|
// OpenRouter — the BYOM on-ramp: ONE key fronts hundreds of models. Rides opencode; the same
|
|
120
127
|
// OPENROUTER_API_KEY Scrooge already uses authenticates the crew seat (the runner sources the
|
|
121
128
|
// .env files). Available the moment the key exists in env/opencode + declared `openrouter=api`.
|
package/mcp.mjs
CHANGED
|
@@ -99,12 +99,12 @@ async function seedCursor() {
|
|
|
99
99
|
// mints a random id at boot — its lifetime ≈ the session's. The endorsed subkey it keys signs all
|
|
100
100
|
// traffic; the durable identity keeps enrollment and attribution.
|
|
101
101
|
const INSTANCE_ID = `mcp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
102
|
-
async function api(method, path, payload) {
|
|
102
|
+
async function api(method, path, payload, { timeoutMs } = {}) {
|
|
103
103
|
// PROJECT explicitly, never the client's cwd fallback: this server's project is fixed at boot,
|
|
104
104
|
// and letting the hub be re-derived per call is how a session ends up writing to two hubs.
|
|
105
105
|
const r = method.toUpperCase() === "GET"
|
|
106
|
-
? await signedGet(path, { session: SESSION, instance: INSTANCE_ID, project: PROJECT })
|
|
107
|
-
: await signedPost(path, payload, { session: SESSION, instance: INSTANCE_ID, project: PROJECT });
|
|
106
|
+
? await signedGet(path, { session: SESSION, instance: INSTANCE_ID, project: PROJECT, timeoutMs })
|
|
107
|
+
: await signedPost(path, payload, { session: SESSION, instance: INSTANCE_ID, project: PROJECT, timeoutMs });
|
|
108
108
|
if (!r.ok) throw new Error(`hub ${r.status} on ${path}`);
|
|
109
109
|
return r.json;
|
|
110
110
|
}
|
|
@@ -312,7 +312,11 @@ server.tool("relay_wait", "Block up to `timeout` seconds waiting for the next me
|
|
|
312
312
|
// resolves inline on every current client instead of being shipped to the background.
|
|
313
313
|
const w = Math.min(timeout ?? 25, 110);
|
|
314
314
|
await seedCursor();
|
|
315
|
-
|
|
315
|
+
// The client-side deadline must OUTLIVE the hub's hold. Since reads moved onto the shared
|
|
316
|
+
// signed client, this call inherited its 1.5s default — so every long-poll that had no message
|
|
317
|
+
// already waiting was aborted at 1.5s and surfaced as "hub 0 on /poll". Parking was dead: the
|
|
318
|
+
// tool erred on every quiet wait and only ever "worked" when a message beat the abort.
|
|
319
|
+
const { messages, cursor: c } = await api("GET", `/poll?session=${encodeURIComponent(SESSION)}&since=${cursor}&wait=${w}`, undefined, { timeoutMs: (w + 15) * 1000 });
|
|
316
320
|
cursor = c;
|
|
317
321
|
return { content: [{ type: "text", text: messages.length ? messages.map(fmt).join("\n") : "(timed out, no message)" }] };
|
|
318
322
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.73",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"trantor": "bin/cli.mjs"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"pg": "^8.22.0"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
|
-
"test": "node test.mjs && node test-scenarios.mjs && node test-failure.mjs && node test-redelivery.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 && node test-inbox.mjs && node test-inflight.mjs && node test-notify.mjs && node test-focus.mjs && node test-reaper.mjs && node test-events.mjs && node test-proposals.mjs && node test-scrub.mjs && node test-store-delta.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-discovery.mjs && node test-doctor.mjs && 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-hub-routing.mjs && node test-hook-routing.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"
|
|
14
|
+
"test": "node test.mjs && node test-scenarios.mjs && node test-failure.mjs && node test-redelivery.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 && node test-inbox.mjs && node test-inflight.mjs && node test-notify.mjs && node test-focus.mjs && node test-reaper.mjs && node test-events.mjs && node test-proposals.mjs && node test-scrub.mjs && node test-store-delta.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-discovery.mjs && node test-doctor.mjs && 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-hub-routing.mjs && node test-hook-routing.mjs && node test-relay-wait.mjs && node test-dsh-seat.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
16
|
"description": "The hub-world for AI agent crews \u2014 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": [
|