trantor 0.17.47 → 0.17.49
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/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +80 -31
- package/bin/advise.mjs +4 -4
- package/bin/cli.mjs +1 -1
- package/bin/crew.sh +3 -2
- package/bin/doctor.mjs +1 -1
- package/bin/provider.mjs +38 -6
- package/hooks/agent-notify.mjs +64 -0
- package/hooks/hooks.json +7 -1
- package/hooks/subagent-cost.mjs +5 -1
- package/hooks/subagent-start.mjs +50 -18
- package/hub.mjs +0 -0
- package/mcp.mjs +5 -3
- package/package.json +2 -2
- package/skills/crew/SKILL.md +3 -3
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Trantor — the hub-world for AI agent crews: live message bus, presence, project Kanban/flow board + context-handoff for independent AI coding agents (Claude, Codex, Gemini, …)",
|
|
9
|
-
"version": "0.17.
|
|
9
|
+
"version": "0.17.49"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "trantor",
|
|
14
14
|
"source": "./",
|
|
15
15
|
"description": "The hub-world for AI agent crews. Say \"fire up the crew\" and Claude becomes the architect: a plan-aware Advisor routes the work (solo / cheap inline calls / live crew of Codex, GLM, Kimi & DeepSeek in their own terminal windows), a Kanban/flow command center with a testing gate tracks it, and an economics brain (Scrooge) keeps the receipts. Includes the relay MCP, a SessionStart auto-discovery hook, and a PreCompact context-handoff so a fresh session can take over a full window instead of compacting.",
|
|
16
|
-
"version": "0.17.
|
|
16
|
+
"version": "0.17.49",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Sasha Bogojevic"
|
|
19
19
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.49",
|
|
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
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
### The hub-world for AI agent crews.
|
|
13
13
|
|
|
14
14
|
**One Advisor decides how your work runs — solo, cheap inline calls, or a live crew of
|
|
15
|
-
Claude Code, Codex, GLM, Kimi
|
|
16
|
-
actual plans, supervised on a live + historical board you can scroll
|
|
17
|
-
from every failure.**
|
|
15
|
+
Claude Code, Codex, GLM, Kimi, DeepSeek — or *any model you bring* — in their own terminal
|
|
16
|
+
windows, routed by your actual plans, supervised on a live + historical board you can scroll
|
|
17
|
+
back through, learning from every failure.**
|
|
18
18
|
|
|
19
19
|
[](./LICENSE)
|
|
20
20
|

|
|
21
|
-

|
|
22
|
+

|
|
23
23
|
|
|
24
24
|
</div>
|
|
25
25
|
|
|
@@ -65,9 +65,10 @@ on your machine, so you (or an agent installing it for you) can see the whole fo
|
|
|
65
65
|
**SubagentStop** (record each sub-agent's notional cost on the board).
|
|
66
66
|
|
|
67
67
|
**What it does *not* do:** no cloud, no accounts, no telemetry, nothing phones home; it never uploads
|
|
68
|
-
your code or keys; it doesn't touch other CLIs' credentials — Codex,
|
|
69
|
-
ones *you* already installed and signed into, and Trantor just coordinates
|
|
70
|
-
API keys in `~/.agent-bus/.env` are used only to call the
|
|
68
|
+
your code or keys; it doesn't touch other CLIs' credentials — Codex, Kimi, DeepSeek, GLM (and any
|
|
69
|
+
provider you bring) are ones *you* already installed and signed into, and Trantor just coordinates
|
|
70
|
+
them locally. The optional API keys in `~/.agent-bus/.env` are used only to call the models *you*
|
|
71
|
+
opted into for routing.
|
|
71
72
|
|
|
72
73
|
**Remove everything, anytime:**
|
|
73
74
|
```bash
|
|
@@ -134,12 +135,14 @@ project takes over with a full window (and a PreCompact hook does this automatic
|
|
|
134
135
|
(`solo | scrooge | crew | hybrid`), a routing table with a **reason per package**, why
|
|
135
136
|
that many seats ("seats follow the work, not the install list"), and a real-money estimate
|
|
136
137
|
with quota-pool accounting. You say go.
|
|
137
|
-
2. **Windows open.** `trantor up codex kimi deepseek:deepseek
|
|
138
|
-
terminal window per agent.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
2. **Windows open.** `trantor up codex kimi deepseek:deepseek glm:zai-coding-plan` spawns one titled
|
|
139
|
+
terminal window per agent. The seats aren't a fixed list — they're **whatever you've got**:
|
|
140
|
+
the native CLIs (Codex, Kimi) plus *any* provider wired through OpenCode (DeepSeek, GLM, and
|
|
141
|
+
**OpenRouter's hundreds of models, or a custom endpoint you bring** — see *Bring your own
|
|
142
|
+
model* below). `agent:model` pins a model; `agent:provider --difficulty hard` picks the
|
|
143
|
+
**best live model** for the work at spawn (capability × cost), enumerated from the provider
|
|
144
|
+
itself — never a guessed endpoint. **Serialized and then verified on the bus** — the launcher
|
|
145
|
+
ends with "crew verified" or names the no-shows loudly. The orchestrator never gets a green lie.
|
|
143
146
|
3. **Work flows over the bus.** Contracts arrive as messages; each agent owns its own files;
|
|
144
147
|
coordination happens in <280-char messages you can read on the dashboard. Crew members
|
|
145
148
|
live under a **runner**: the CLI works one turn and exits, the runner long-polls the bus
|
|
@@ -202,6 +205,39 @@ ledger; Trantor turns that into decisions:
|
|
|
202
205
|
- **Fractal delegation** (`relay_scrooge`): the architect *and* crew members push stateless
|
|
203
206
|
grunt work to cheap models, with ledger receipts.
|
|
204
207
|
|
|
208
|
+
## Bring your own model (BYOM)
|
|
209
|
+
|
|
210
|
+
The crew roster isn't a hardcoded list — it's **derived from what you've configured**. Trantor
|
|
211
|
+
ships built-in seats for Codex and Kimi (native CLIs) and DeepSeek + GLM (via OpenCode), but
|
|
212
|
+
**any** provider OpenCode can reach becomes a first-class seat with its own identity on the
|
|
213
|
+
board — no code change, no waiting for us to add it.
|
|
214
|
+
|
|
215
|
+
- **OpenRouter is the on-ramp.** One key fronts **hundreds of models** (incl. vendors that have
|
|
216
|
+
no CLI of their own). Drop `OPENROUTER_API_KEY` in `~/.agent-bus/.env` and `openrouter` is a
|
|
217
|
+
seat: `trantor up openrouter` live-selects the best model for the work, or
|
|
218
|
+
`trantor up openrouter:openrouter/<vendor>/<model>` pins one.
|
|
219
|
+
- **Add any provider in one command:**
|
|
220
|
+
```bash
|
|
221
|
+
trantor provider add <name> --key sk-… --plan api # a provider OpenCode already knows
|
|
222
|
+
trantor provider add acme --key sk-… --base-url https://api.acme.ai/v1 --models acme-lg,acme-sm
|
|
223
|
+
```
|
|
224
|
+
The second form wires a **custom OpenAI-compatible endpoint** into OpenCode for you (no
|
|
225
|
+
hand-editing config). Then `trantor provider` lists every seat with its availability + tier, and
|
|
226
|
+
`trantor models [<provider>]` browses the live models behind a seat — including the router's pick
|
|
227
|
+
per difficulty so you can see *hard → strong, easy → cheap* at a glance.
|
|
228
|
+
- **It routes by difficulty, not just price.** `scrooge-capabilities` scores a provider's whole
|
|
229
|
+
catalog (real Artificial-Analysis benchmarks where a model matches, a price-tier proxy for the
|
|
230
|
+
long tail) and the router weighs **capability × cost, gated by the work's difficulty** — so hard
|
|
231
|
+
work escalates to a genuinely strong model while easy work stays cheap. Run it once (a weekly
|
|
232
|
+
cron keeps it fresh): `scrooge-capabilities`.
|
|
233
|
+
- **Every seat is its own colleague.** Each opencode-driven provider gets a distinct bus label
|
|
234
|
+
(`glm:<project>`, `openrouter:<project>`, `acme:<project>`), so they never collide and each shows
|
|
235
|
+
up as its own agent on the dashboard.
|
|
236
|
+
|
|
237
|
+
> Brought a key for Inception, a new Japanese model, a niche fine-tune? `trantor provider add` it,
|
|
238
|
+
> `scrooge-capabilities` scores it, and the Advisor routes to it by difficulty — same as the
|
|
239
|
+
> built-ins.
|
|
240
|
+
|
|
205
241
|
## Context handoff — sessions that never hit the wall
|
|
206
242
|
|
|
207
243
|
A PreCompact hook writes a rich handoff before Claude Code compacts; a fresh session in the
|
|
@@ -231,29 +267,41 @@ rate, not work rate.
|
|
|
231
267
|
## The CLI
|
|
232
268
|
|
|
233
269
|
```
|
|
234
|
-
trantor setup | doctor | connect | profile |
|
|
270
|
+
trantor setup | doctor | connect | profile | provider | models
|
|
271
|
+
| up <agents…> | swap <old> <new> | down | ui | advise | hub | watch
|
|
235
272
|
```
|
|
236
273
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
274
|
+
- **`trantor provider`** — `list` every crew seat (built-in + brought) with availability + tier ·
|
|
275
|
+
`add <name> --key … [--plan api] [--base-url <url> --models a,b]` to bring any provider (custom
|
|
276
|
+
endpoints are wired into OpenCode for you) · `remove <name>`.
|
|
277
|
+
- **`trantor models [<provider>]`** — browse the live models behind each seat + the router's pick
|
|
278
|
+
per difficulty.
|
|
279
|
+
- **`trantor up`** — `agent:model` pins a model (`deepseek:deepseek-v4-pro`); `agent:provider
|
|
280
|
+
--task <k> --difficulty <d>` picks the **best live model** for the work at spawn
|
|
281
|
+
(`glm:zai-coding-plan --difficulty hard`); spawns are verified on the bus with one retry;
|
|
282
|
+
geometry auto-detects the screen you're working on (`CREW_RECT="X,Y,W,H"` to override).
|
|
283
|
+
- **`trantor swap <oldAgent> <newSpec>`** replaces an exhausted agent with a live-selected one.
|
|
284
|
+
- **`trantor down`** kills crew processes via their ttys and closes windows without macOS
|
|
285
|
+
"Terminate?" dialogs.
|
|
243
286
|
|
|
244
287
|
## Works with any MCP agent
|
|
245
288
|
|
|
246
|
-
Claude Code, Codex CLI,
|
|
247
|
-
`trantor connect` automatically (idempotent,
|
|
248
|
-
Anything else that speaks MCP: point it at
|
|
249
|
-
server auto-registers the session, so presence
|
|
289
|
+
Claude Code, Codex CLI, Kimi Code CLI, and **OpenCode** (the universal adapter — DeepSeek, GLM,
|
|
290
|
+
OpenRouter, and any provider you bring) are wired by `trantor connect` automatically (idempotent,
|
|
291
|
+
backed-up, never overwrites your customizations). Anything else that speaks MCP: point it at
|
|
292
|
+
`mcp.mjs` with `RELAY_AGENT=<brand>` — loading the server auto-registers the session, so presence
|
|
293
|
+
works before the model says a word.
|
|
294
|
+
|
|
295
|
+
*(Gemini CLI: Google retired the free seat in 2026, so it's no longer a default crew seat —
|
|
296
|
+
GLM via OpenCode is its replacement. Gemini still works as a Scrooge cheap-model via
|
|
297
|
+
`GEMINI_API_KEY`, and a paid-key holder can still run `trantor up gemini`.)*
|
|
250
298
|
|
|
251
299
|
## How it works
|
|
252
300
|
|
|
253
301
|
```
|
|
254
|
-
Claude (architect/plugin) codex ─ runner
|
|
302
|
+
Claude (architect/plugin) codex ─ runner kimi ─ runner opencode ─ runner (DeepSeek·GLM·OpenRouter·BYOM)
|
|
255
303
|
│ advise/contracts │ one turn, exit; runner long-polls (free) + resumes with context
|
|
256
|
-
|
|
304
|
+
└───────────┬─────────────┴──────────────┴───────────────────────────────────┘
|
|
257
305
|
▼
|
|
258
306
|
hub.mjs ←— plain HTTP + SSE · presence/messages/board/history/lessons/learning/economics
|
|
259
307
|
(Node built-ins only · state in ~/.agent-bus/bus.json · loopback by default)
|
|
@@ -286,10 +334,11 @@ The `relay_*` tool names and the `~/.agent-bus` state dir remain until a later r
|
|
|
286
334
|
## Tests
|
|
287
335
|
|
|
288
336
|
```bash
|
|
289
|
-
npm test #
|
|
290
|
-
# honest presence + TTL prune, spawn no-shows, the testing gate,
|
|
291
|
-
# /history + backfill, /learning shape, /todos sync, /card detail,
|
|
292
|
-
# plan tiers,
|
|
337
|
+
npm test # unit + 120+ protocol-level scenario drills + capability-routing, all with mock agents
|
|
338
|
+
# (no LLMs, seconds, $0): honest presence + TTL prune, spawn no-shows, the testing gate,
|
|
339
|
+
# bounce trails, lessons, /history + backfill, /learning shape, /todos sync, /card detail,
|
|
340
|
+
# advisor decisions across plan tiers, BYOM roster derivation + brought-provider routing,
|
|
341
|
+
# difficulty-aware model selection, deps validation, virgin-machine doctor, failure drills
|
|
293
342
|
```
|
|
294
343
|
|
|
295
344
|
## License
|
package/bin/advise.mjs
CHANGED
|
@@ -36,7 +36,7 @@ export const BUILTIN_ROSTER = {
|
|
|
36
36
|
codex: { cli: "codex", launch: "codex", session: "codex", provider: "codex" },
|
|
37
37
|
kimi: { cli: "kimi", launch: "kimi", session: "kimi", provider: "kimi" },
|
|
38
38
|
deepseek: { cli: "opencode", launch: "deepseek:deepseek", session: "deepseek", provider: "deepseek", providerOc: "deepseek" },
|
|
39
|
-
glm: { cli: "opencode", launch: "
|
|
39
|
+
glm: { cli: "opencode", launch: "glm:zai-coding-plan", session: "glm", provider: "zai", providerOc: "zai-coding-plan" },
|
|
40
40
|
openrouter: { cli: "opencode", launch: "openrouter:openrouter", session: "openrouter", provider: "openrouter", providerOc: "openrouter" },
|
|
41
41
|
};
|
|
42
42
|
// opencode provider ids already claimed by a built-in (so discovery never duplicates them) + the
|
|
@@ -193,12 +193,12 @@ export function advise(input, world = loadWorld()) {
|
|
|
193
193
|
const seat = roster[r.executor];
|
|
194
194
|
return {
|
|
195
195
|
order: i + 1, title: r.title, difficulty: r.difficulty,
|
|
196
|
-
// bus identity = the
|
|
197
|
-
//
|
|
196
|
+
// bus identity = the seat's session label (every opencode-driven seat has its OWN label, so
|
|
197
|
+
// glm is `glm:<project>`, openrouter `openrouter:<project>`, a brought provider `<name>:<project>`).
|
|
198
198
|
assignee: r.executor === "scrooge" || r.executor === "orchestrator" ? undefined : `${seat?.session || r.executor}:<project>`,
|
|
199
199
|
// launch = the EXACT `trantor up` spec to spawn this seat; the orchestrator runs
|
|
200
200
|
// `trantor up <launch> --task <task> --difficulty <difficulty>`. Carrying it explicitly is
|
|
201
|
-
// what teaches the orchestrator the GLM path (`
|
|
201
|
+
// what teaches the orchestrator the GLM path (`glm:zai-coding-plan`) instead of guessing.
|
|
202
202
|
launch: ["scrooge", "orchestrator"].includes(r.executor) ? undefined : (seat?.launch || r.executor),
|
|
203
203
|
// "auto" = resolve a LIVE model at spawn (the launch spec already pins the provider; the
|
|
204
204
|
// runner picks the best live model for it). Was `<cli>-default` — a stale default.
|
package/bin/cli.mjs
CHANGED
|
@@ -53,7 +53,7 @@ switch (cmd) {
|
|
|
53
53
|
trantor profile declare your plans: trantor profile set claude=max codex=plus deepseek=api
|
|
54
54
|
trantor provider bring ANY model (BYOM): list seats · add <name> --key … · remove <name>
|
|
55
55
|
trantor models browse live models behind each seat + the router's pick per difficulty
|
|
56
|
-
trantor up … spawn a crew here: trantor up codex kimi deepseek:deepseek
|
|
56
|
+
trantor up … spawn a crew here: trantor up codex kimi deepseek:deepseek glm:zai-coding-plan
|
|
57
57
|
trantor down tear the crew down (kills processes, closes windows, no dialogs)
|
|
58
58
|
trantor ui open the live dashboard (board + flow views)
|
|
59
59
|
trantor catchup "where are we?" — the continuous board + git, with a synthesized brief
|
package/bin/crew.sh
CHANGED
|
@@ -52,8 +52,9 @@ down() {
|
|
|
52
52
|
case "$CMD" in up|swap) ;; *) echo "usage: crew.sh up <agent...> | crew.sh swap <oldAgent> <newAgent[:provider[/model]]> | crew.sh down"; exit 1 ;; esac
|
|
53
53
|
|
|
54
54
|
# --task/--difficulty drive LAZY live-model selection for provider-only specs (agent:provider).
|
|
55
|
-
# An agent spec is one of: `codex` (CLI default) · `
|
|
56
|
-
#
|
|
55
|
+
# An agent spec is one of: `codex` (CLI default) · `glm:zai-coding-plan` (provider only → pick the
|
|
56
|
+
# best live model now) · `glm:zai-coding-plan/glm-5.2` (full pin). The agent label is the bus
|
|
57
|
+
# identity; any non-native label runs via opencode (legacy `opencode:zai-coding-plan` still works).
|
|
57
58
|
TASK="code"; DIFF="medium"; _ARGS=()
|
|
58
59
|
while [ $# -gt 0 ]; do
|
|
59
60
|
case "$1" in
|
package/bin/doctor.mjs
CHANGED
|
@@ -60,7 +60,7 @@ const CLIS = [
|
|
|
60
60
|
{ name: "gemini (CLI retired 2026-06-18)", bin: "gemini", wired: () => !!read(join(H, ".gemini", "settings.json"))?.mcpServers?.relay, auth: () => existsSync(join(H, ".gemini", "oauth_creds.json")) || !!process.env.GEMINI_API_KEY || !!process.env.GOOGLE_API_KEY, login: "Gemini CLI retired 2026-06-18 (free/Pro/Ultra). Crew seat → GLM (opencode) or Antigravity `agy`. Gemini still serves as a Scrooge cheap-model via GEMINI_API_KEY." },
|
|
61
61
|
{ 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)" },
|
|
62
62
|
{ 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` },
|
|
63
|
-
{ 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
|
|
63
|
+
{ 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` },
|
|
64
64
|
// OpenRouter — the BYOM on-ramp: ONE key fronts hundreds of models. Rides opencode; the same
|
|
65
65
|
// OPENROUTER_API_KEY Scrooge already uses authenticates the crew seat (the runner sources the
|
|
66
66
|
// .env files). Available the moment the key exists in env/opencode + declared `openrouter=api`.
|
package/bin/provider.mjs
CHANGED
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
//
|
|
5
5
|
// trantor provider # list seats: built-in + discovered, with availability + tier
|
|
6
6
|
// trantor provider add <name> [--key sk-…] [--plan api|coding-plan|max] [--label <bus-name>]
|
|
7
|
+
// [--base-url <url> [--models m1,m2]] # wire a CUSTOM OpenAI-compatible endpoint
|
|
7
8
|
// trantor provider remove <name> # drop it from your profile (leaves the key in place)
|
|
8
9
|
//
|
|
9
10
|
// `add` writes <NAME>_API_KEY to ~/.agent-bus/.env (if --key given), declares the plan in your
|
|
10
|
-
// quota profile, verifies opencode can see the provider's models, and prints the seat spec.
|
|
11
|
-
//
|
|
11
|
+
// quota profile, verifies opencode can see the provider's models, and prints the seat spec. For a
|
|
12
|
+
// provider opencode already knows (openrouter, groq, …) the key is enough; for a CUSTOM endpoint,
|
|
13
|
+
// pass --base-url and it writes the opencode.json provider block for you. The Advisor then routes
|
|
14
|
+
// to it automatically; run `scrooge-capabilities` so it routes well by difficulty.
|
|
12
15
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync, appendFileSync } from "node:fs";
|
|
13
16
|
import { join, dirname } from "node:path";
|
|
14
17
|
import { homedir } from "node:os";
|
|
@@ -23,6 +26,26 @@ const has = (c) => { try { execSync(`command -v ${c}`, { stdio: "ignore", shell:
|
|
|
23
26
|
const C = { dim: "\x1b[2m", grn: "\x1b[32m", red: "\x1b[31m", yel: "\x1b[33m", gold: "\x1b[38;5;208m", off: "\x1b[0m" };
|
|
24
27
|
const envKeyName = (p) => `${String(p).toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`;
|
|
25
28
|
|
|
29
|
+
const OC_CONFIG = join(H, ".config", "opencode", "opencode.json");
|
|
30
|
+
// Wire a CUSTOM OpenAI-compatible provider into opencode.json (matching opencode's schema +
|
|
31
|
+
// the existing providers' `options.apiKey` style). Merges, never clobbers other providers.
|
|
32
|
+
// `configPath` is injectable so it can be unit-tested against a temp file.
|
|
33
|
+
export function wireOpencodeProvider(name, baseUrl, models, configPath = OC_CONFIG) {
|
|
34
|
+
const cfg = existsSync(configPath) ? read(configPath, {}) : {};
|
|
35
|
+
cfg.$schema ||= "https://opencode.ai/config.json";
|
|
36
|
+
cfg.provider ||= {};
|
|
37
|
+
const block = {
|
|
38
|
+
npm: "@ai-sdk/openai-compatible",
|
|
39
|
+
name: name.charAt(0).toUpperCase() + name.slice(1),
|
|
40
|
+
options: { baseURL: baseUrl, apiKey: `{env:${envKeyName(name)}}` },
|
|
41
|
+
};
|
|
42
|
+
if (models && models.length) block.models = Object.fromEntries(models.map(m => [m, {}]));
|
|
43
|
+
cfg.provider[name] = { ...cfg.provider[name], ...block };
|
|
44
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
45
|
+
writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
46
|
+
return configPath;
|
|
47
|
+
}
|
|
48
|
+
|
|
26
49
|
function opencodeModelCount(providerOc) {
|
|
27
50
|
if (!has("opencode")) return null;
|
|
28
51
|
try { return execSync(`opencode models ${providerOc} 2>/dev/null`, { encoding: "utf8" }).split("\n").filter(Boolean).length; }
|
|
@@ -47,7 +70,7 @@ function listSeats() {
|
|
|
47
70
|
}
|
|
48
71
|
|
|
49
72
|
function addProvider(name, opts) {
|
|
50
|
-
if (!name) { console.error("usage: trantor provider add <name> [--key sk-…] [--plan api] [--label <bus-name>]"); process.exit(1); }
|
|
73
|
+
if (!name) { console.error("usage: trantor provider add <name> [--key sk-…] [--plan api] [--label <bus-name>] [--base-url <url> [--models m1,m2]]"); process.exit(1); }
|
|
51
74
|
const provider = name.toLowerCase();
|
|
52
75
|
const label = (opts.label || provider).toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
53
76
|
const plan = (opts.plan || "api").toLowerCase();
|
|
@@ -73,13 +96,20 @@ function addProvider(name, opts) {
|
|
|
73
96
|
console.log(`${C.grn}✓${C.off} profile: ${provider}=${plan}`);
|
|
74
97
|
} catch (e) { console.log(`${C.yel}⚠${C.off} could not set profile (run: trantor profile set ${provider}=${plan})`); }
|
|
75
98
|
|
|
76
|
-
// 3)
|
|
99
|
+
// 3) custom endpoint → write the opencode.json provider block (known providers skip this)
|
|
100
|
+
if (opts.baseUrl) {
|
|
101
|
+
const models = (opts.models || "").split(",").map(s => s.trim()).filter(Boolean);
|
|
102
|
+
const where = wireOpencodeProvider(provider, opts.baseUrl, models);
|
|
103
|
+
console.log(`${C.grn}✓${C.off} wired custom provider '${provider}' → ${where.replace(H, "~")} (baseURL ${opts.baseUrl}${models.length ? `, ${models.length} models` : ""})`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 4) verify opencode can see the provider's models
|
|
77
107
|
const n = opencodeModelCount(provider);
|
|
78
108
|
if (n == null) console.log(`${C.yel}⚠${C.off} opencode not on PATH — install it to run this seat (it's the universal adapter)`);
|
|
79
|
-
else if (n === 0) console.log(`${C.yel}⚠${C.off} opencode lists 0 models for '${provider}'. If it's a known provider, the key above is enough; for a
|
|
109
|
+
else if (n === 0) console.log(`${C.yel}⚠${C.off} opencode lists 0 models for '${provider}'.${opts.baseUrl ? " Check the baseURL/key, or pass --models m1,m2 to declare them." : " If it's a known provider, the key above is enough; for a CUSTOM endpoint re-run with --base-url <url> [--models m1,m2]."} Re-check: trantor models ${provider}`);
|
|
80
110
|
else console.log(`${C.grn}✓${C.off} opencode sees ${n} models for '${provider}'`);
|
|
81
111
|
|
|
82
|
-
//
|
|
112
|
+
// 5) score it for difficulty-aware routing + show the seat
|
|
83
113
|
console.log(`\n${C.gold}Seat ready.${C.off} Launch it:`);
|
|
84
114
|
console.log(` trantor up ${label === provider ? label : `${label}:${provider}`} ${C.dim}# live-selects the best model for the work${C.off}`);
|
|
85
115
|
console.log(` trantor up ${label}:${provider}/<model> ${C.dim}# pin a specific model${C.off}`);
|
|
@@ -106,6 +136,8 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
|
106
136
|
if (rest[i] === "--key") opts.key = rest[++i];
|
|
107
137
|
else if (rest[i] === "--plan") opts.plan = rest[++i];
|
|
108
138
|
else if (rest[i] === "--label") opts.label = rest[++i];
|
|
139
|
+
else if (rest[i] === "--base-url" || rest[i] === "--baseurl") opts.baseUrl = rest[++i];
|
|
140
|
+
else if (rest[i] === "--models") opts.models = rest[++i];
|
|
109
141
|
else pos.push(rest[i]);
|
|
110
142
|
}
|
|
111
143
|
if (!sub || sub === "list") listSeats();
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// trantor Notification hook — surface CC's OWN background/child agents (the `claude agents` / fork / subtask
|
|
3
|
+
// sessions that DON'T go through the Task-tool sub-agent path, so SubagentStart/Stop never see them) on the
|
|
4
|
+
// board. CC fires a Notification with notification_type "agent_needs_input" (the agent paused, waiting on the
|
|
5
|
+
// user) or "agent_completed" (it finished). We card these as source:"cc-bg-agent" — a DISTINCT population
|
|
6
|
+
// from cc-subagent, so they never double-count against sub-agent notional cost.
|
|
7
|
+
//
|
|
8
|
+
// • agent_needs_input → post/flip a card to "blocked" (needs input) so the board shows it's waiting.
|
|
9
|
+
// • agent_completed → flip/close the card to "done".
|
|
10
|
+
//
|
|
11
|
+
// UNVERIFIED-LIVE: the Notification stdin payload's exact field names for the agent id are not documented
|
|
12
|
+
// (session_id + notification_type + message are the confirmed fields). We read several candidates and let the
|
|
13
|
+
// hub key/guard on whatever we send; TRANTOR_DEBUG_NOTIFY=1 dumps the raw payload to stderr so the first real
|
|
14
|
+
// firing reveals the true shape. Fail-silent: a notification must never block the session.
|
|
15
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { resolveProject, hostId } from "../lib/project.mjs";
|
|
19
|
+
|
|
20
|
+
function readStdin() {
|
|
21
|
+
return new Promise(res => { let d = ""; process.stdin.setEncoding("utf8");
|
|
22
|
+
process.stdin.on("data", c => (d += c)); process.stdin.on("end", () => res(d));
|
|
23
|
+
setTimeout(() => res(d), 100); });
|
|
24
|
+
}
|
|
25
|
+
function relayUrl() {
|
|
26
|
+
if (process.env.RELAY_URL) return process.env.RELAY_URL;
|
|
27
|
+
try { const c = join(homedir(), ".agent-bus", "config.json"); if (existsSync(c)) { const u = JSON.parse(readFileSync(c, "utf8")).url; if (u) return u; } } catch {}
|
|
28
|
+
return "http://127.0.0.1:4477";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const input = JSON.parse((await readStdin()) || "{}");
|
|
33
|
+
if (process.env.TRANTOR_DEBUG_NOTIFY === "1") process.stderr.write(`[trantor] notify payload: ${JSON.stringify(input)}\n`);
|
|
34
|
+
const ntype = String(input.notification_type || input.notificationType || input.type || "");
|
|
35
|
+
// only the two agent-lifecycle notification types are board-relevant; ignore permission/idle/auth/elicitation
|
|
36
|
+
if (ntype !== "agent_needs_input" && ntype !== "agent_completed") { process.stdout.write("{}"); process.exit(0); }
|
|
37
|
+
|
|
38
|
+
const cwd = input.cwd || process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
39
|
+
const project = resolveProject(cwd);
|
|
40
|
+
// candidate id for pairing needs_input→completed and for guarding vs an already-carded cc-subagent. The
|
|
41
|
+
// agent's OWN session id is the most likely stable key for a background/child agent across both events.
|
|
42
|
+
const agentId = String(input.agent_id || input.agentId || input.session_id || input.sessionId || "").slice(0, 120);
|
|
43
|
+
const agentType = String(input.agent_type || input.agentType || "agent").slice(0, 40);
|
|
44
|
+
// parent = the session that spawned it → nest under that session's focus card (same rule as cc-subagent)
|
|
45
|
+
const parent = String(input.parent_session_id || input.parentSessionId || "").slice(0, 120);
|
|
46
|
+
// a lightly-cleaned human label; Notification carries a `message`. NOTE: Teams privacy gate applies before
|
|
47
|
+
// this reaches a shared board (it can carry prompt text) — same rule as focus-card titles.
|
|
48
|
+
const msg = String(input.message || "").replace(/\s+/g, " ").trim().slice(0, 90);
|
|
49
|
+
const title = `${agentType}${msg ? `: ${msg}` : ""}`.slice(0, 180);
|
|
50
|
+
|
|
51
|
+
await fetch(`${relayUrl()}/task`, {
|
|
52
|
+
method: "POST", headers: { "content-type": "application/json" },
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
project, source: "cc-bg-agent", notificationType: ntype,
|
|
55
|
+
title, agentId, agentType, parent,
|
|
56
|
+
assignee: `${agentType}:${project}`, by: `${hostId()}:${project}`, phase: "sub-agents",
|
|
57
|
+
}),
|
|
58
|
+
signal: AbortSignal.timeout(1500),
|
|
59
|
+
}).catch(() => {});
|
|
60
|
+
} catch (e) {
|
|
61
|
+
process.stderr.write(`[trantor] agent-notify error: ${e?.message || e}\n`);
|
|
62
|
+
}
|
|
63
|
+
process.stdout.write("{}");
|
|
64
|
+
process.exit(0);
|
package/hooks/hooks.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "trantor — auto-register each session + inject live roster (SessionStart); turn each substantive user prompt into the session's live 'focus' card so a regular session's own work shows IN PROGRESS (UserPromptSubmit); post an in-flight 'doing' card when a sub-agent is dispatched (PreToolUse); heartbeat presence on every tool call + deliver unread bus messages to a busy session mid-turn + mirror the session's TodoWrite list onto the board as cards (PostToolUse); write a handoff before compaction (PreCompact); card each sub-agent's notional API cost when it finishes (SubagentStop)",
|
|
2
|
+
"description": "trantor — auto-register each session + inject live roster (SessionStart); turn each substantive user prompt into the session's live 'focus' card so a regular session's own work shows IN PROGRESS (UserPromptSubmit); post an in-flight 'doing' card when a sub-agent is dispatched (PreToolUse) and enrich it with the native agent_id + parent session when the sub-agent spawns (SubagentStart); heartbeat presence on every tool call + deliver unread bus messages to a busy session mid-turn + mirror the session's TodoWrite list onto the board as cards (PostToolUse); write a handoff before compaction (PreCompact); surface CC's own background/child agents (fork/--agent/subtask) on the board when they need input or complete (Notification); card each sub-agent's notional API cost when it finishes (SubagentStop)",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{ "matcher": "", "hooks": [ { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart.mjs" } ] }
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
"PreToolUse": [
|
|
11
11
|
{ "matcher": "Task|Agent", "hooks": [ { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/subagent-start.mjs" } ] }
|
|
12
12
|
],
|
|
13
|
+
"SubagentStart": [
|
|
14
|
+
{ "matcher": "", "hooks": [ { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/subagent-start.mjs" } ] }
|
|
15
|
+
],
|
|
13
16
|
"PostToolUse": [
|
|
14
17
|
{ "matcher": "", "hooks": [ { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/heartbeat.mjs" } ] },
|
|
15
18
|
{ "matcher": "", "hooks": [ { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/inbox-deliver.mjs" } ] },
|
|
@@ -18,6 +21,9 @@
|
|
|
18
21
|
"PreCompact": [
|
|
19
22
|
{ "matcher": "", "hooks": [ { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/precompact.mjs" } ] }
|
|
20
23
|
],
|
|
24
|
+
"Notification": [
|
|
25
|
+
{ "matcher": "", "hooks": [ { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/agent-notify.mjs" } ] }
|
|
26
|
+
],
|
|
21
27
|
"SubagentStop": [
|
|
22
28
|
{ "matcher": "", "hooks": [ { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/subagent-cost.mjs" } ] }
|
|
23
29
|
]
|
package/hooks/subagent-cost.mjs
CHANGED
|
@@ -81,6 +81,10 @@ try {
|
|
|
81
81
|
const project = resolveProject(cwd);
|
|
82
82
|
const agentType = String(input.agent_type || "subagent").slice(0, 40);
|
|
83
83
|
const effort = input.effort?.level || "";
|
|
84
|
+
// agent_id pairs this completion with its SubagentStart "doing" card (robust key, replaces the fragile
|
|
85
|
+
// title match); parent nests it under the spawning session's focus card. Both native SubagentStop fields.
|
|
86
|
+
const agentId = String(input.agent_id || "").slice(0, 80);
|
|
87
|
+
const parent = String(input.parent_session_id || input.session_id || "").slice(0, 120);
|
|
84
88
|
|
|
85
89
|
const file = findTranscript(input);
|
|
86
90
|
if (!file) { process.stderr.write("[trantor] subagent-cost: no transcript found\n"); process.stdout.write("{}"); process.exit(0); }
|
|
@@ -104,7 +108,7 @@ try {
|
|
|
104
108
|
await fetch(`${relayUrl()}/task`, {
|
|
105
109
|
method: "POST", headers: { "content-type": "application/json" },
|
|
106
110
|
body: JSON.stringify({
|
|
107
|
-
project, title, status: "done",
|
|
111
|
+
project, title, status: "done", agentType, agentId, parent,
|
|
108
112
|
assignee: `${agentType}:${project}`, by: `${hostId()}:${project}`,
|
|
109
113
|
source: "cc-subagent", costKind: "subagent-notional",
|
|
110
114
|
costUsd: safeUsd, costNote: safeNote, model, effort, tokens: suspect ? null : tokens,
|
package/hooks/subagent-start.mjs
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// trantor
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
2
|
+
// trantor sub-agent in-flight hook — posts/enriches a "doing" card so the board shows sub-agent work IN
|
|
3
|
+
// PROGRESS while it runs. The SubagentStop hook (subagent-cost.mjs) flips it to "done" (with notional cost).
|
|
4
|
+
//
|
|
5
|
+
// This ONE script serves TWO hook events (dispatch on input.hook_event_name):
|
|
6
|
+
//
|
|
7
|
+
// • PreToolUse (matcher Task|Agent) — fires at DISPATCH TIME and carries tool_input.prompt, so it has a
|
|
8
|
+
// good human title. It CREATES the in-flight card (source:"cc-subagent", title-fingerprinted). Works on
|
|
9
|
+
// every CC version → the universal in-flight source, no regression on older CC.
|
|
10
|
+
//
|
|
11
|
+
// • SubagentStart (native, CC 2.1.x+) — fires when the sub-agent actually SPAWNS and carries the real
|
|
12
|
+
// agent_id + agent_type (+ parent session), but NO prompt. It ENRICHES the card the PreToolUse create
|
|
13
|
+
// already made: stamps agent_id (robust start↔stop pairing key, replacing the fragile title match) and
|
|
14
|
+
// parent (for nesting sub-agents under the session focus card). If no create card exists (a spawn with
|
|
15
|
+
// no matching PreToolUse — rare), it CREATES one keyed by agent_id so nothing orphans.
|
|
16
|
+
//
|
|
17
|
+
// Registering BOTH never double-posts: on modern CC the PreToolUse create runs first, then SubagentStart
|
|
18
|
+
// finds that card by (project, agentType) and enriches in place (creates nothing). Fail-silent throughout —
|
|
19
|
+
// never block or delay a dispatch.
|
|
7
20
|
import { readFileSync, existsSync } from "node:fs";
|
|
8
21
|
import { join } from "node:path";
|
|
9
22
|
import { homedir } from "node:os";
|
|
@@ -19,26 +32,45 @@ function relayUrl() {
|
|
|
19
32
|
try { const c = join(homedir(), ".agent-bus", "config.json"); if (existsSync(c)) { const u = JSON.parse(readFileSync(c, "utf8")).url; if (u) return u; } } catch {}
|
|
20
33
|
return "http://127.0.0.1:4477";
|
|
21
34
|
}
|
|
35
|
+
const post = (url, body) => fetch(url, {
|
|
36
|
+
method: "POST", headers: { "content-type": "application/json" },
|
|
37
|
+
body: JSON.stringify(body), signal: AbortSignal.timeout(1500),
|
|
38
|
+
}).catch(() => {});
|
|
22
39
|
|
|
23
40
|
try {
|
|
24
41
|
const input = JSON.parse((await readStdin()) || "{}");
|
|
25
|
-
const ti = input.tool_input || {};
|
|
26
42
|
const cwd = input.cwd || process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
27
43
|
const project = resolveProject(cwd);
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
const url = `${relayUrl()}/task`;
|
|
45
|
+
|
|
46
|
+
if (input.hook_event_name === "SubagentStart") {
|
|
47
|
+
// ENRICH path — the sub-agent has spawned; attach its real id + parent to the in-flight card.
|
|
48
|
+
// agent_type is the native SubagentStart field (PreToolUse used tool_input.subagent_type).
|
|
49
|
+
const agentType = String(input.agent_type || input.subagent_type || "subagent").slice(0, 40);
|
|
50
|
+
const agentId = String(input.agent_id || "").slice(0, 80);
|
|
51
|
+
// parent = the session that spawned this sub-agent → nest it under that session's focus card.
|
|
52
|
+
const parent = String(input.parent_session_id || input.session_id || "").slice(0, 120);
|
|
53
|
+
if (agentId) {
|
|
54
|
+
await post(url, {
|
|
55
|
+
project, enrich: true, agentType, agentId, parent,
|
|
56
|
+
by: `${hostId()}:${project}`,
|
|
57
|
+
source: "cc-subagent", costKind: "subagent-notional", phase: "sub-agents",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// CREATE path (PreToolUse Task|Agent) — dispatch time, has the prompt → good title.
|
|
62
|
+
const ti = input.tool_input || {};
|
|
63
|
+
const agentType = String(ti.subagent_type || "subagent").slice(0, 40);
|
|
64
|
+
// MUST mirror subagent-cost.mjs's title derivation so the hub's title fingerprint pairs this start card
|
|
65
|
+
// with the SubagentStop "done" card on any client that predates agent_id pairing (legacy fallback).
|
|
66
|
+
const task = String(ti.prompt || ti.description || agentType).replace(/\s+/g, " ").trim().slice(0, 90);
|
|
67
|
+
const title = `${agentType}: ${task}`.slice(0, 180);
|
|
68
|
+
await post(url, {
|
|
69
|
+
project, title, status: "doing", agentType,
|
|
37
70
|
assignee: `${agentType}:${project}`, by: `${hostId()}:${project}`,
|
|
38
71
|
source: "cc-subagent", costKind: "subagent-notional", phase: "sub-agents",
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
}).catch(() => {});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
42
74
|
} catch (e) {
|
|
43
75
|
process.stderr.write(`[trantor] subagent-start error: ${e?.message || e}\n`);
|
|
44
76
|
}
|
package/hub.mjs
CHANGED
|
Binary file
|
package/mcp.mjs
CHANGED
|
@@ -189,10 +189,12 @@ server.tool("relay_inbox", "Read NEW messages addressed to this session since th
|
|
|
189
189
|
return { content: [{ type: "text", text: messages.length ? messages.map(fmt).join("\n") : "(no new messages)" }] };
|
|
190
190
|
});
|
|
191
191
|
|
|
192
|
-
server.tool("relay_wait", "Block up to `timeout` seconds waiting for the next message to this session (long-poll). Returns the instant a message arrives. When idle, park by calling this repeatedly. IMPORTANT:
|
|
193
|
-
{ timeout: z.number().optional().describe("seconds to wait, default 25
|
|
192
|
+
server.tool("relay_wait", "Block up to `timeout` seconds waiting for the next message to this session (long-poll). Returns the instant a message arrives. When idle, park by calling this repeatedly. IMPORTANT: MCP clients cap or background long tool calls — OpenCode ~60s; Codex ~120s; Claude Code auto-backgrounds ANY MCP call at 120s (CC 2.1.212+, tunable via CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS), which breaks inline parking. Use timeout 50 and loop for cross-client safety.",
|
|
193
|
+
{ timeout: z.number().optional().describe("seconds to wait, default 25. Effective wait is capped at 110s so the call always returns inline (a longer wait would be auto-backgrounded by Claude Code's 120s MCP floor or dropped by other clients). Use 50 and call repeatedly to park while idle.") },
|
|
194
194
|
async ({ timeout }) => {
|
|
195
|
-
|
|
195
|
+
// Cap below the 120s MCP auto-background floor (CC 2.1.212+) so relay_wait always
|
|
196
|
+
// resolves inline on every current client instead of being shipped to the background.
|
|
197
|
+
const w = Math.min(timeout ?? 25, 110);
|
|
196
198
|
const { messages, cursor: c } = await api("GET", `/poll?session=${encodeURIComponent(SESSION)}&since=${cursor}&wait=${w}`);
|
|
197
199
|
cursor = c;
|
|
198
200
|
return { content: [{ type: "text", text: messages.length ? messages.map(fmt).join("\n") : "(timed out, no message)" }] };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trantor",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.49",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"trantor": "bin/cli.mjs"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"zod": "^4.4.3"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"test": "node test.mjs && node test-scenarios.mjs && node test-failure.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-focus.mjs && python3 engine/test-routing.py"
|
|
13
|
+
"test": "node test.mjs && node test-scenarios.mjs && node test-failure.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 && python3 engine/test-routing.py"
|
|
14
14
|
},
|
|
15
15
|
"description": "The hub-world for AI agent crews — 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).",
|
|
16
16
|
"files": [
|
package/skills/crew/SKILL.md
CHANGED
|
@@ -49,11 +49,11 @@ EXACT launch spec per provider (do not improvise these):
|
|
|
49
49
|
| Codex | `codex` (or `codex:gpt-5.5` to pin) | OpenAI CLI |
|
|
50
50
|
| Kimi | `kimi` | Moonshot coding-plan |
|
|
51
51
|
| DeepSeek | `deepseek:deepseek` | runs via opencode; `deepseek` alone = CLI default |
|
|
52
|
-
| **GLM (Z.ai)** | **`
|
|
52
|
+
| **GLM (Z.ai)** | **`glm:zai-coding-plan`** | **runs via opencode, NOT a bare `glm`/`zai` terminal command.** `glm:zai-coding-plan/glm-5.2` pins a model; `glm:zai-coding-plan` live-selects. (Legacy `opencode:zai-coding-plan` still works.) |
|
|
53
53
|
| **OpenRouter (BYOM)** | **`openrouter`** | **the bring-your-own-model on-ramp — one key fronts hundreds of vendors (incl. ones with no CLI).** Bare `openrouter` live-selects the best OpenRouter model for the difficulty; pin one with `openrouter:openrouter/<vendor>/<model>`. Its own bus identity `openrouter:<project>` (never collides with the GLM `opencode` seat). |
|
|
54
54
|
|
|
55
55
|
`agent:provider` live-selects the best model now; `agent:provider/model` pins one. Example:
|
|
56
|
-
`trantor up codex kimi deepseek:deepseek
|
|
56
|
+
`trantor up codex kimi deepseek:deepseek glm:zai-coding-plan --task code --difficulty hard`.
|
|
57
57
|
**Whatever the advisor's `launch` field says, run that verbatim** — the roster above is just the
|
|
58
58
|
built-in menu, but the advisor picks the right seats/specs for THIS work (a user who's only brought
|
|
59
59
|
an OpenRouter key gets `openrouter` for everything; one who brought five providers gets a
|
|
@@ -66,7 +66,7 @@ advisor routes to a brought provider you don't recognize, that's expected — ru
|
|
|
66
66
|
|
|
67
67
|
⚠️ **Gemini CLI is RETIRED (Google killed the free seat 2026-06-18).** The advisor no longer
|
|
68
68
|
offers it and you must NOT fire up `gemini` — `gemini --yolo` exits 1 and crash-loops on the
|
|
69
|
-
bus. Its replacement seat is **GLM via `
|
|
69
|
+
bus. Its replacement seat is **GLM via `glm:zai-coding-plan`**. (Gemini still serves as a
|
|
70
70
|
Scrooge cheap-model via `GEMINI_API_KEY` — that's a separate, working path, not a crew seat.)
|
|
71
71
|
Only a holder of a paid Gemini enterprise key should ever `trantor up gemini`.
|
|
72
72
|
|