trantor 0.17.77 → 0.17.79

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.77",
3
+ "version": "0.17.79",
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
@@ -51,7 +51,7 @@ heartbeats, inbox delivery, handoff/baton pass, sub-agent cards):
51
51
  "relay": {
52
52
  "command": "node",
53
53
  "args": ["<absolute-path-to-trantor>/mcp.mjs"],
54
- "env": { "RELAY_URL": "http://127.0.0.1:4477", "RELAY_AGENT": "kimi-orch" },
54
+ "env": { "RELAY_AGENT": "kimi-orch" },
55
55
  "startupTimeoutMs": 15000,
56
56
  "toolTimeoutMs": 150000
57
57
  }
@@ -62,6 +62,13 @@ heartbeats, inbox delivery, handoff/baton pass, sub-agent cards):
62
62
  2. In the Kimi TUI: `/plugins install <absolute-path-to-trantor>` (or the GitHub URL), then
63
63
  `/reload`, then start a new session.
64
64
 
65
+ Since 0.17.76 the Kimi hooks are a **dialect bridge**, not a fork: `kimi/bridge.mjs` runs the
66
+ canonical hooks as children and translates Kimi's input/output conventions at the edges
67
+ (SessionStart context is stashed and delivered on the first prompt). Kimi hooks therefore sign
68
+ their hub calls, follow per-project hub pins, and inherit every future hook fix automatically.
69
+ Do **not** set `RELAY_URL` in MCP configs — a hardcoded URL overrides the per-project pin and
70
+ splits the project across hubs; the pin decides the hub.
71
+
65
72
  Notes: Kimi plugin installs are **snapshots** (`~/.kimi-code/plugins/managed/trantor/`) — after
66
73
  updating trantor, re-run `/plugins install` to refresh the skills/hooks. The MCP entry above always
67
74
  runs your live checkout, so the relay server itself never goes stale. Invoke the skills with
@@ -260,6 +267,25 @@ Every session registers automatically — **crew or not** — and a solo session
260
267
  shows up on the board as cards, so the dashboard reflects *all* the work on a project, not just
261
268
  crew runs.
262
269
 
270
+ ## The board keeps itself honest
271
+
272
+ Cards carry their **story on the card**: every `note` lands in a permanent per-card log (last 40
273
+ entries) that survives restarts, retention, and id remaps — the drawer shows it as the card's
274
+ narrative, so clicking an old card tells you what actually happened, not a one-line status trail.
275
+ The `todo` lane has a lifecycle: cards untouched for 14 days (`RELAY_TODO_STALE_MS`) move to
276
+ STALE with an "aged out" note instead of rotting silently, and todo tiles wear an age badge from
277
+ day 7. A commit closes the session's focus card and the two link both ways. `trantor doctor`
278
+ cross-checks every hub you know about against the per-project pins and reports any **split-brain**
279
+ (a project live on two hubs) with the exact fix — and `trantor adopt <project>` migrates a project
280
+ between hubs in one verified step, telling stale sessions to restart.
281
+
282
+ Crew output is gated mechanically, too: `bin/slop-gate.mjs` runs the vendored
283
+ [anti-slop](https://github.com/dmmulroy/anti-slop) Oxlint rules over an agent's **changed files
284
+ only** — unexplained type assertions, `unknown` laundering, runtime `typeof`, Reflect tricks and
285
+ friends fail the card before it can reach done, while legacy debt burns down on its own card
286
+ instead of blocking everyone. A surface that has paid its debt off stays paid: `desktop/src` is at
287
+ zero and `npm test` now lints it in full (`node bin/slop-gate.mjs --surface desktop/src`).
288
+
263
289
  ## The brain — plan-aware economics
264
290
 
265
291
  Trantor's economics engine ([Scrooge](https://github.com/sashabogi/token-scrooge) — installed
@@ -326,8 +352,8 @@ rate, not work rate.
326
352
  | `relay_send(to, text)` / `relay_inbox` / `relay_wait(t)` | Live messaging: direct, read-new, long-poll wake |
327
353
  | `relay_peers` / `relay_status(text)` / `relay_whoami` | Presence: who's alive (honest, heartbeat-backed), doing what |
328
354
  | `relay_project_brief(text)` | The project's what/why on the dashboard |
329
- | `relay_task_add(title, …, difficulty, model, deps, project?)` | Cards with difficulty/model badges + DAG edges; `project` targets another board when you orchestrate from elsewhere |
330
- | `relay_task_move(id, status)` | `todo → doing → testing → done` (the gate), `failed`, `blocked` |
355
+ | `relay_task_add(title, …, difficulty, model, deps, note?, project?)` | Cards with difficulty/model badges + DAG edges; `note` seeds the card's **permanent log**; `project` targets another board when you orchestrate from elsewhere |
356
+ | `relay_task_move(id, status, note?)` | `todo → doing → testing → done` (the gate), `failed`, `blocked` — moves to testing/done should carry a `note`: what you did + the evidence, stored on the card forever |
331
357
  | `relay_board` | The project's full board, as text |
332
358
  | `relay_scrooge(prompt, task?, difficulty?)` | Fractal cheap-model delegation, with the ledger receipt |
333
359
  | `relay_lesson(text, scope?)` | Record a failure lesson — auto-injected into all future crews |
@@ -338,6 +364,7 @@ rate, not work rate.
338
364
  ```
339
365
  trantor setup | doctor | connect | profile | provider | models
340
366
  | up <agents…> | swap <old> <new> | down | ui | advise | hub | watch
367
+ | adopt <project> | reconcile | duty | orchestrate | patrol | app | backfill | init-hooks
341
368
  ```
342
369
 
343
370
  - **`trantor provider`** — `list` every crew seat (built-in + brought) with availability + tier ·
@@ -355,9 +382,12 @@ trantor setup | doctor | connect | profile | provider | models
355
382
 
356
383
  ## Works with any MCP agent
357
384
 
358
- Claude Code, Codex CLI, Kimi Code CLI, and **OpenCode** (the universal adapter — DeepSeek, GLM,
359
- OpenRouter, and any provider you bring) are wired by `trantor connect` automatically (idempotent,
360
- backed-up, never overwrites your customizations). Anything else that speaks MCP: point it at
385
+ Claude Code, Codex CLI, Kimi Code CLI, **DeepSeek Harness (`dsh`)**, and **OpenCode** (the
386
+ universal adapter — DeepSeek, GLM, OpenRouter, and any provider you bring) are wired by
387
+ `trantor connect` automatically (idempotent, backed-up, never overwrites your customizations).
388
+ For `dsh`, connect builds a full profile (`~/.dsh/profiles/trantor`): DeepSeek's own Claude Code
389
+ hooks bridge running the trantor hooks, plus their MCP client running the relay server — then
390
+ `trantor up dsh` runs it as a crew seat (fresh session per turn; uses `DEEPSEEK_API_KEY`). Anything else that speaks MCP: point it at
361
391
  `mcp.mjs` with `RELAY_AGENT=<brand>` — loading the server auto-registers the session, so presence
362
392
  works before the model says a word.
363
393
 
package/bin/models.mjs CHANGED
@@ -7,7 +7,7 @@
7
7
  import { execSync } from "node:child_process";
8
8
  import { dirname, join } from "node:path";
9
9
  import { homedir } from "node:os";
10
- import { existsSync } from "node:fs";
10
+ import { existsSync, statSync } from "node:fs";
11
11
  import { pathToFileURL, fileURLToPath } from "node:url";
12
12
  import { buildRoster, loadWorld } from "./advise.mjs";
13
13
 
@@ -57,7 +57,10 @@ function detail(name) {
57
57
  if (existsSync(SCROOGE) || has("scrooge")) {
58
58
  console.log(`\n${C.gold}router picks (code task):${C.off}`);
59
59
  for (const d of ["easy", "medium", "hard"]) console.log(` ${d.padEnd(7)} → ${routePick(models, d)}`);
60
- console.log(`${C.dim}(run scrooge-capabilities to (re)score the catalog for accurate difficulty routing)${C.off}`);
60
+ const caps = join(H, ".token-scrooge", "capabilities.json");
61
+ // only nag when the scores are missing or older than the weekly refresh allows for
62
+ const ageDays = existsSync(caps) ? (Date.now() - statSync(caps).mtimeMs) / 86400e3 : Infinity;
63
+ if (ageDays > 9) console.log(`${C.dim}(scores ${ageDays === Infinity ? "missing" : `${Math.floor(ageDays)}d old`} — run scrooge-capabilities to (re)score the catalog for accurate difficulty routing)${C.off}`);
61
64
  }
62
65
  }
63
66
 
package/bin/slop-gate.mjs CHANGED
@@ -3,6 +3,7 @@
3
3
  //
4
4
  // node bin/slop-gate.mjs # lint changed + untracked lintable files (the crew gate)
5
5
  // node bin/slop-gate.mjs --all # lint the whole configured surface (advisory audit)
6
+ // node bin/slop-gate.mjs --surface desktop/src # lint ONE paid-off surface in full (hard gate in npm test)
6
7
  //
7
8
  // The rules are the vendored dmmulroy/anti-slop set (tools/oxlint/anti-slop — ours to tune):
8
9
  // they mechanically reject low-evidence AI patterns — unexplained type assertions, unknown
@@ -13,6 +14,10 @@
13
14
  import { execSync, spawnSync } from "node:child_process";
14
15
 
15
16
  const ALL = process.argv.includes("--all");
17
+ // A surface that has burned its debt down to zero (desktop/src, #4798) is gated in FULL, not by
18
+ // diff: the whole point of paying it off is that it stays paid. npm test runs this.
19
+ const SURFACE_AT = process.argv.indexOf("--surface");
20
+ const SURFACE = SURFACE_AT > -1 ? process.argv[SURFACE_AT + 1] : null;
16
21
  const LINTABLE = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
17
22
 
18
23
  function sh(cmd) {
@@ -21,7 +26,9 @@ function sh(cmd) {
21
26
  }
22
27
 
23
28
  let args;
24
- if (ALL) {
29
+ if (SURFACE) {
30
+ args = [SURFACE];
31
+ } else if (ALL) {
25
32
  args = ["."];
26
33
  } else {
27
34
  const changed = sh("git diff --name-only HEAD");
@@ -40,9 +47,10 @@ const hits = out.split("\n").filter(l => /error\s+anti-slop\(/.test(l));
40
47
 
41
48
  if (hits.length) {
42
49
  console.log(hits.join("\n"));
43
- console.log(`\nslop-gate: ${hits.length} anti-slop error(s) in your changes fix them (or state the SAFETY invariant) before moving the card to testing.`);
50
+ const where = SURFACE ? `in ${SURFACE} (a zero-debt surfacekeep it at zero)` : "in your changes";
51
+ console.log(`\nslop-gate: ${hits.length} anti-slop error(s) ${where} — fix them (or state the SAFETY invariant) before moving the card to testing.`);
44
52
  process.exit(1);
45
53
  }
46
54
  const warnings = out.split("\n").filter(l => /\bwarning\b/.test(l)).length;
47
- console.log(`slop-gate: clean${warnings ? ` (${warnings} advisory warning(s) — not gating)` : ""}.`);
55
+ console.log(`slop-gate${SURFACE ? ` (${SURFACE})` : ""}: clean${warnings ? ` (${warnings} advisory warning(s) — not gating)` : ""}.`);
48
56
  process.exit(0);
@@ -22,7 +22,7 @@ Usage: scrooge-capabilities # refresh from AA (+OpenRouter), show a
22
22
  scrooge-capabilities --dry-run # fetch + match, print, but don't write
23
23
  Exit: 0 updated · 1 nothing fetched (no key / network) · 2 error.
24
24
  """
25
- import sys, os, json, argparse, math, urllib.request, urllib.error
25
+ import sys, os, json, argparse, math, time, urllib.request, urllib.error
26
26
 
27
27
  HOME = os.path.expanduser("~")
28
28
  SCROOGE_DIR = os.environ.get("SCROOGE_HOME", os.path.join(HOME, ".token-scrooge"))
@@ -56,10 +56,21 @@ def load_env_file(path):
56
56
  except Exception:
57
57
  pass
58
58
 
59
- def http_get_json(url, headers, timeout=30):
59
+ def http_get_json(url, headers, timeout=30, retries=(5, 20, 60)):
60
+ # the weekly launchd run can land while the Mac has no network yet (2026-08-17: DNS error,
61
+ # scores went a week stale) -- back off and retry on connection failures, not on HTTP errors
60
62
  req = urllib.request.Request(url, headers=headers, method="GET")
61
- with urllib.request.urlopen(req, timeout=timeout) as r:
62
- return json.loads(r.read().decode())
63
+ for i, wait in enumerate(list(retries) + [None]):
64
+ try:
65
+ with urllib.request.urlopen(req, timeout=timeout) as r:
66
+ return json.loads(r.read().decode())
67
+ except urllib.error.HTTPError:
68
+ raise
69
+ except (urllib.error.URLError, OSError) as e:
70
+ if wait is None:
71
+ raise
72
+ sys.stderr.write(WARN(" network: %s -- retry %d in %ds\n" % (str(e)[:60], i + 1, wait)))
73
+ time.sleep(wait)
63
74
 
64
75
  def norm(s):
65
76
  return s.lower().replace(".", "-").replace("_", "-")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.17.77",
3
+ "version": "0.17.79",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "trantor": "bin/cli.mjs"
@@ -11,7 +11,7 @@
11
11
  "zod": "^4.4.3"
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-cardlog.mjs && node test-relay-note.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 && node test-kimi-bridge.mjs && node test-kimi-events.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 bin/slop-gate.mjs --surface desktop/src && 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-cardlog.mjs && node test-relay-note.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 && node test-kimi-bridge.mjs && node test-kimi-events.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": [
@@ -51,6 +51,7 @@ EXACT launch spec per provider (do not improvise these):
51
51
  | DeepSeek | `deepseek:deepseek` | runs via opencode; `deepseek` alone = CLI default |
52
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
+ | **DeepSeek Harness** | **`dsh`** | DeepSeek's own open-source harness as a seat (API-billed via `DEEPSEEK_API_KEY`). `trantor connect` builds its profile (their CC-hooks bridge running trantor's hooks + their MCP client running the relay). **Fresh session per turn — no resume** — so the seat relies on the wake prompt + the board, not conversation memory; give it self-contained contracts. |
54
55
 
55
56
  `agent:provider` live-selects the best model now; `agent:provider/model` pins one. Example:
56
57
  `trantor up codex kimi deepseek:deepseek glm:zai-coding-plan --task code --difficulty hard`.
@@ -97,8 +98,12 @@ Loop until the board is done — you are a foreman, not a mailbox:
97
98
  they auto-inject into every future crew's prompts.
98
99
 
99
100
  ## Phase 5 — verification gate + integration
100
- Card flow is `todo → doing → testing → done`; `testing` runs the project's tests/typecheck;
101
- `done` only green; `failed` (+ bus report) pulses red on the board until you bounce it.
101
+ Card flow is `todo → doing → testing → done`; `testing` runs the seat's OWN test file (never the
102
+ full suite from a seat suites spawn hubs on fixed ports and collide across seats; the
103
+ orchestrator runs the full suite at integration) plus `node bin/slop-gate.mjs` where the repo has
104
+ one (the anti-slop lint over the seat's changed files — a card must not reach done failing it);
105
+ `done` only green, and moves to testing/done carry a `note` with the evidence — the note is the
106
+ card's permanent story; `failed` (+ bus report) pulses red on the board until you bounce it.
102
107
  Enforce the gate — bounce anything that skipped it (bounces are visible: "↩ bounced" on the
103
108
  card, history in its tooltip). When all report done: integrate, fix contract mismatches
104
109
  YOURSELF, move your card through testing → done, broadcast "🚀 <thing> is live", and when the