trantor 0.17.48 → 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 +79 -30
- 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
|
@@ -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
|
|
@@ -135,11 +136,13 @@ project takes over with a full window (and a PreCompact hook does this automatic
|
|
|
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
138
|
2. **Windows open.** `trantor up codex kimi deepseek:deepseek glm:zai-coding-plan` spawns one titled
|
|
138
|
-
terminal window per agent.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
@@ -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": [
|