trantor 0.17.72 → 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.17.72",
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.");
@@ -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
- const custom = String(process.env.TRANTOR_RETRY_MS || "").split(",").map(Number).filter(n => Number.isFinite(n) && n >= 0);
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.17.72",
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 && node test-relay-wait.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": [