trantor 0.18.95 → 0.18.96

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.18.95",
3
+ "version": "0.18.96",
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": {
@@ -405,6 +405,11 @@ try {
405
405
  // A RESUMED session must never claim a handoff: it already carries its history, so the takeover banner
406
406
  // would land in the same maxed-out context and look right while the window never reset.
407
407
  const isResume = source === "resume";
408
+ // A print-mode one-shot (`claude -p`) is never the successor either (#11577): SessionStart fires
409
+ // for it with source "startup" — the hook INPUT is indistinguishable from a human start — but
410
+ // the CLI marks the hook ENV (CLAUDE_CODE_ENTRYPOINT=sdk-cli; interactive "cli", SDKs sdk-*).
411
+ // Unguarded, it claimed the waiting handoff and superseded the live orchestrator.
412
+ const isPrintRun = String(process.env.CLAUDE_CODE_ENTRYPOINT || "cli").startsWith("sdk-");
408
413
  const orchEnv = process.env.TRANTOR_ORCH || "";
409
414
  const isOrchPane = !!orchEnv && (orchEnv === "1" || orchEnv === project); // project-matched: a child claude in another dir must not inherit the badge
410
415
  const orchSid = readOrchSession(project);
@@ -413,19 +418,24 @@ try {
413
418
  const orchOrigin = !!(peek && orchSid && peek.session_id && peek.session_id === orchSid);
414
419
  const ageMs = peek ? Date.now() - (Number(peek.stamp) || 0) * 1000 : 0;
415
420
  const held = orchOrigin && !isOrchPane && !isCompact && ageMs < holdMs;
416
- const handoff = !peek ? null
421
+ const handoff = !peek || isPrintRun ? null
417
422
  : (isCompact || held || isResume) ? peek
418
423
  : loadPendingHandoff(basename(projectDir), {
419
424
  claim: true,
420
425
  freshSession: { session_id: stdinObj.session_id || "", transcript_path: stdinObj.transcript_path || "" },
421
426
  });
422
427
  const claimed = !!handoff && !isCompact && !held && !isResume;
428
+ // The handoff banner never rides a one-shot either: it is not the successor and nobody asked it
429
+ // to recap, so a print start with a pending handoff gets one quiet stderr line and nothing else.
430
+ if (isPrintRun && peek && !isCompact) {
431
+ process.stderr.write(`[trantor] pending handoff ${peek.id} NOT claimed — print-mode one-shot (claude -p) is never the successor\n`);
432
+ }
423
433
  // Follow the thread: claiming the orchestrator's baton makes THIS session the orchestrator
424
434
  // thread, so the map `trantor open` resumes and the app's chat reads moves with it. The pane
425
435
  // also records itself on every fresh start — that keeps the map honest even if a future
426
436
  // claude forks the session id on resume.
427
437
  if (claimed && orchOrigin && stdinObj.session_id) writeOrchSession(project, String(stdinObj.session_id), "sessionstart-claim");
428
- if (isOrchPane && !isCompact && stdinObj.session_id) writeOrchSession(project, String(stdinObj.session_id), "orch-pane-start");
438
+ if (isOrchPane && !isCompact && !isPrintRun && stdinObj.session_id) writeOrchSession(project, String(stdinObj.session_id), "orch-pane-start");
429
439
  if (handoff && held) {
430
440
  process.stderr.write(`[trantor] pending handoff ${handoff.id} HELD for the orch pane (${Math.round(ageMs / 60000)}m old)\n`);
431
441
  const mins = Math.max(1, Math.round((holdMs - ageMs) / 60000));
package/lib/project.mjs CHANGED
@@ -1,17 +1,23 @@
1
1
  // trantor — canonical project identity (client side): one repo = one lane, keyed by the id recorded
2
2
  // in the checkout (.trantor/project.json) or, unmarked, by the git repo root basename; an explicit
3
3
  // RELAY_PROJECT always wins and the hub folds aliases on top.
4
- import { execSync } from "node:child_process";
4
+ import { execSync, execFileSync } from "node:child_process";
5
5
  import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, readdirSync, statSync, realpathSync } from "node:fs";
6
6
  import { basename, join, dirname, resolve, sep } from "node:path";
7
7
  import { homedir, hostname } from "node:os";
8
8
  import { asRecord, asString } from "./decode.mjs";
9
9
 
10
10
  export function gitRoot(dir) {
11
- try {
12
- return execSync(`git -C ${JSON.stringify(dir)} rev-parse --show-toplevel`,
13
- { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 2000 }).trim();
14
- } catch { return ""; }
11
+ // A loaded runner can miss the short deadline; retry timeouts, not non-repository errors (#11580).
12
+ for (const timeout of [2000, 10000]) {
13
+ try {
14
+ return execFileSync("git", ["-C", dir, "rev-parse", "--show-toplevel"],
15
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
16
+ } catch (error) {
17
+ if (error.code !== "ETIMEDOUT") return "";
18
+ }
19
+ }
20
+ return "";
15
21
  }
16
22
 
17
23
  // A BUS SEAT WORKTREE is named for its project BY CONSTRUCTION — the bus creates
package/mcp.mjs CHANGED
@@ -4,12 +4,10 @@
4
4
  // via the agent's MCP config. Identity + hub URL come from env (RELAY_SESSION, RELAY_URL).
5
5
  // Loading this server AUTO-REGISTERS the session — so presence works on every agent.
6
6
  import { writeFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
7
- import { join, basename, dirname } from "node:path";
7
+ import { join, basename } from "node:path";
8
8
  import { homedir, hostname } from "node:os";
9
9
  import { execSync, spawnSync } from "node:child_process";
10
10
  import { randomUUID } from "node:crypto";
11
- import { createRequire } from "node:module";
12
- import { fileURLToPath, pathToFileURL } from "node:url";
13
11
  import { advise, loadWorld } from "./bin/advise.mjs";
14
12
  import { loadSeatRecord } from "./lib/seat-record.mjs";
15
13
  import { resolveProject, hostId, resolveHubInfo, nonSeatReason, handoffDir, orchWriterSid } from "./lib/project.mjs";
@@ -20,43 +18,7 @@ import { assertNoSecrets } from "./lib/scrub.mjs";
20
18
  import { isMessageCardTitle, OPEN_CARD_STATUSES } from "./lib/turn-policy.mjs";
21
19
  import { markDoing, hollowVerdict, blastRadius, blastLine } from "./hooks/lib/hollow-move.mjs";
22
20
 
23
- // Runtime dep resolution: `claude plugin install` snapshots the repo, not an npm tarball, so a
24
- // GitHub-sourced plugin ships no node_modules and a static SDK import dies with
25
- // ERR_MODULE_NOT_FOUND before any line runs — silently: every relay tool just disappears.
26
- // Resolve ourselves: plain import(spec) first; on NOT_FOUND borrow the global trantor's tree.
27
- const HERE = dirname(fileURLToPath(import.meta.url));
28
- const req = createRequire(import.meta.url);
29
-
30
- let fallbackRoots = null;
31
- function borrowRoots() {
32
- if (fallbackRoots) return fallbackRoots;
33
- const roots = [];
34
- const add = (p) => { if (p && !roots.includes(p)) roots.push(p); };
35
- // Cheap guesses first — every one of these is a string join, no process spawn.
36
- if (process.env.npm_config_prefix) add(join(process.env.npm_config_prefix, "lib", "node_modules"));
37
- add(join(dirname(process.execPath), "..", "lib", "node_modules")); // homebrew, nvm, volta, asdf
38
- // Only shell out if the guesses missed — `npm root -g` costs ~0.5s of MCP startup.
39
- if (!roots.some((r) => existsSync(join(r, "trantor")))) {
40
- try { add(execSync("npm root -g", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim()); } catch {}
41
- }
42
- fallbackRoots = roots.flatMap((r) => [join(r, "trantor"), r]);
43
- return fallbackRoots;
44
- }
45
-
46
- async function dep(spec) {
47
- try {
48
- return await import(spec);
49
- } catch (err) {
50
- if (err?.code !== "ERR_MODULE_NOT_FOUND" && err?.code !== "ERR_PACKAGE_PATH_NOT_EXPORTED") throw err;
51
- }
52
- for (const path of [HERE, ...borrowRoots()]) {
53
- try { return await import(pathToFileURL(req.resolve(spec, { paths: [path] })).href); } catch {}
54
- }
55
- throw new Error(
56
- `[trantor-mcp] cannot resolve '${spec}'. This plugin snapshot has no node_modules and no global ` +
57
- `trantor install was found to borrow from. Fix: npm i -g trantor (or: cd ${HERE} && npm install --omit=dev)`,
58
- );
59
- }
21
+ import { dep } from "./lib/dep.mjs";
60
22
 
61
23
  const { McpServer } = await dep("@modelcontextprotocol/sdk/server/mcp.js");
62
24
  const { StdioServerTransport } = await dep("@modelcontextprotocol/sdk/server/stdio.js");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.18.95",
3
+ "version": "0.18.96",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "trantor": "bin/cli.mjs"