@os-eco/overstory-cli 0.6.11 → 0.7.2

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.
Files changed (87) hide show
  1. package/README.md +12 -13
  2. package/agents/builder.md +1 -1
  3. package/agents/coordinator.md +12 -11
  4. package/agents/lead.md +25 -24
  5. package/agents/monitor.md +4 -4
  6. package/agents/reviewer.md +1 -1
  7. package/agents/scout.md +5 -5
  8. package/agents/supervisor.md +36 -32
  9. package/package.json +5 -3
  10. package/src/agents/guard-rules.ts +97 -0
  11. package/src/agents/hooks-deployer.ts +7 -90
  12. package/src/agents/overlay.test.ts +30 -7
  13. package/src/agents/overlay.ts +10 -9
  14. package/src/commands/agents.test.ts +5 -0
  15. package/src/commands/clean.test.ts +3 -0
  16. package/src/commands/completions.ts +1 -1
  17. package/src/commands/coordinator.test.ts +1 -0
  18. package/src/commands/coordinator.ts +34 -18
  19. package/src/commands/costs.test.ts +6 -1
  20. package/src/commands/costs.ts +13 -20
  21. package/src/commands/dashboard.ts +38 -138
  22. package/src/commands/doctor.test.ts +1 -1
  23. package/src/commands/doctor.ts +2 -2
  24. package/src/commands/ecosystem.ts +2 -1
  25. package/src/commands/errors.test.ts +4 -5
  26. package/src/commands/errors.ts +4 -62
  27. package/src/commands/feed.test.ts +2 -2
  28. package/src/commands/feed.ts +12 -106
  29. package/src/commands/init.test.ts +1 -2
  30. package/src/commands/init.ts +1 -8
  31. package/src/commands/inspect.test.ts +14 -0
  32. package/src/commands/inspect.ts +10 -44
  33. package/src/commands/log.test.ts +14 -0
  34. package/src/commands/log.ts +39 -0
  35. package/src/commands/logs.ts +7 -63
  36. package/src/commands/mail.test.ts +5 -0
  37. package/src/commands/metrics.test.ts +2 -2
  38. package/src/commands/metrics.ts +3 -17
  39. package/src/commands/monitor.ts +30 -16
  40. package/src/commands/nudge.test.ts +1 -0
  41. package/src/commands/prime.test.ts +2 -0
  42. package/src/commands/prime.ts +6 -2
  43. package/src/commands/replay.test.ts +2 -2
  44. package/src/commands/replay.ts +12 -135
  45. package/src/commands/run.test.ts +1 -0
  46. package/src/commands/run.ts +7 -23
  47. package/src/commands/sling.test.ts +68 -1
  48. package/src/commands/sling.ts +62 -24
  49. package/src/commands/status.test.ts +1 -0
  50. package/src/commands/status.ts +4 -17
  51. package/src/commands/stop.test.ts +1 -0
  52. package/src/commands/supervisor.ts +35 -18
  53. package/src/commands/trace.test.ts +6 -6
  54. package/src/commands/trace.ts +11 -109
  55. package/src/commands/worktree.test.ts +9 -0
  56. package/src/config.ts +39 -0
  57. package/src/doctor/consistency.test.ts +14 -0
  58. package/src/e2e/init-sling-lifecycle.test.ts +3 -5
  59. package/src/index.ts +2 -1
  60. package/src/logging/format.ts +214 -0
  61. package/src/logging/theme.ts +132 -0
  62. package/src/mail/broadcast.test.ts +1 -0
  63. package/src/merge/resolver.ts +23 -4
  64. package/src/metrics/store.test.ts +46 -0
  65. package/src/metrics/store.ts +11 -0
  66. package/src/mulch/client.test.ts +20 -0
  67. package/src/mulch/client.ts +312 -45
  68. package/src/runtimes/claude.test.ts +616 -0
  69. package/src/runtimes/claude.ts +218 -0
  70. package/src/runtimes/pi-guards.test.ts +433 -0
  71. package/src/runtimes/pi-guards.ts +349 -0
  72. package/src/runtimes/pi.test.ts +620 -0
  73. package/src/runtimes/pi.ts +244 -0
  74. package/src/runtimes/registry.test.ts +86 -0
  75. package/src/runtimes/registry.ts +46 -0
  76. package/src/runtimes/types.ts +188 -0
  77. package/src/schema-consistency.test.ts +1 -0
  78. package/src/sessions/compat.ts +1 -0
  79. package/src/sessions/store.test.ts +31 -0
  80. package/src/sessions/store.ts +37 -4
  81. package/src/types.ts +21 -0
  82. package/src/watchdog/daemon.test.ts +7 -4
  83. package/src/watchdog/daemon.ts +1 -1
  84. package/src/watchdog/health.test.ts +1 -0
  85. package/src/watchdog/triage.ts +14 -4
  86. package/src/worktree/tmux.test.ts +28 -13
  87. package/src/worktree/tmux.ts +14 -28
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Shared guard rule constants for overstory agent hook generation.
3
+ *
4
+ * Pure data module — named exports only, no logic. These constants are the
5
+ * single source of truth for tool names, bash patterns, and safe prefixes
6
+ * used when generating PreToolUse guards in hooks-deployer.ts.
7
+ */
8
+
9
+ /**
10
+ * Claude Code native team/task tools that bypass overstory orchestration.
11
+ * All overstory agents must use `overstory sling` for delegation, not these.
12
+ */
13
+ export const NATIVE_TEAM_TOOLS = [
14
+ "Task",
15
+ "TeamCreate",
16
+ "TeamDelete",
17
+ "SendMessage",
18
+ "TaskCreate",
19
+ "TaskUpdate",
20
+ "TaskList",
21
+ "TaskGet",
22
+ "TaskOutput",
23
+ "TaskStop",
24
+ ];
25
+
26
+ /**
27
+ * Tools that require human interaction and block indefinitely in non-interactive
28
+ * tmux sessions. Agents run non-interactively and must never call these tools.
29
+ * Use overstory mail (--type question) to escalate to the orchestrator instead.
30
+ */
31
+ export const INTERACTIVE_TOOLS = ["AskUserQuestion", "EnterPlanMode", "EnterWorktree"];
32
+
33
+ /** Tools that non-implementation agents must not use. */
34
+ export const WRITE_TOOLS = ["Write", "Edit", "NotebookEdit"];
35
+
36
+ /**
37
+ * Bash commands that modify files and must be blocked for non-implementation agents.
38
+ * Each pattern is a regex fragment used inside a grep -qE check.
39
+ */
40
+ export const DANGEROUS_BASH_PATTERNS = [
41
+ "sed\\s+-i",
42
+ "sed\\s+--in-place",
43
+ "echo\\s+.*>",
44
+ "printf\\s+.*>",
45
+ "cat\\s+.*>",
46
+ "tee\\s",
47
+ "\\bvim\\b",
48
+ "\\bnano\\b",
49
+ "\\bvi\\b",
50
+ "\\bmv\\s",
51
+ "\\bcp\\s",
52
+ "\\brm\\s",
53
+ "\\bmkdir\\s",
54
+ "\\btouch\\s",
55
+ "\\bchmod\\s",
56
+ "\\bchown\\s",
57
+ ">>",
58
+ "\\bgit\\s+add\\b",
59
+ "\\bgit\\s+commit\\b",
60
+ "\\bgit\\s+merge\\b",
61
+ "\\bgit\\s+push\\b",
62
+ "\\bgit\\s+reset\\b",
63
+ "\\bgit\\s+checkout\\b",
64
+ "\\bgit\\s+rebase\\b",
65
+ "\\bgit\\s+stash\\b",
66
+ "\\bnpm\\s+install\\b",
67
+ "\\bbun\\s+install\\b",
68
+ "\\bbun\\s+add\\b",
69
+ // Runtime eval flags — bypass shell pattern guards by executing JS/Python directly
70
+ "\\bbun\\s+-e\\b",
71
+ "\\bbun\\s+--eval\\b",
72
+ "\\bnode\\s+-e\\b",
73
+ "\\bnode\\s+--eval\\b",
74
+ "\\bdeno\\s+eval\\b",
75
+ "\\bpython3?\\s+-c\\b",
76
+ "\\bperl\\s+-e\\b",
77
+ "\\bruby\\s+-e\\b",
78
+ ];
79
+
80
+ /**
81
+ * Bash commands that are always safe for non-implementation agents.
82
+ * If a command starts with any of these prefixes, it bypasses the dangerous command check.
83
+ * This whitelist is checked BEFORE the blocklist.
84
+ */
85
+ export const SAFE_BASH_PREFIXES = [
86
+ "ov ",
87
+ "overstory ",
88
+ "bd ",
89
+ "sd ",
90
+ "git status",
91
+ "git log",
92
+ "git diff",
93
+ "git show",
94
+ "git blame",
95
+ "git branch",
96
+ "mulch ",
97
+ ];
@@ -3,6 +3,13 @@ import { dirname, join } from "node:path";
3
3
  import { DEFAULT_QUALITY_GATES } from "../config.ts";
4
4
  import { AgentError } from "../errors.ts";
5
5
  import type { QualityGate } from "../types.ts";
6
+ import {
7
+ DANGEROUS_BASH_PATTERNS,
8
+ INTERACTIVE_TOOLS,
9
+ NATIVE_TEAM_TOOLS,
10
+ SAFE_BASH_PREFIXES,
11
+ WRITE_TOOLS,
12
+ } from "./guard-rules.ts";
6
13
 
7
14
  /**
8
15
  * Capabilities that must never modify project files.
@@ -31,96 +38,6 @@ const COORDINATION_CAPABILITIES = new Set(["coordinator", "supervisor", "monitor
31
38
  */
32
39
  const COORDINATION_SAFE_PREFIXES = ["git add", "git commit"];
33
40
 
34
- /**
35
- * Claude Code native team/task tools that bypass overstory orchestration.
36
- * All overstory agents must use `overstory sling` for delegation, not these.
37
- */
38
- const NATIVE_TEAM_TOOLS = [
39
- "Task",
40
- "TeamCreate",
41
- "TeamDelete",
42
- "SendMessage",
43
- "TaskCreate",
44
- "TaskUpdate",
45
- "TaskList",
46
- "TaskGet",
47
- "TaskOutput",
48
- "TaskStop",
49
- ];
50
-
51
- /**
52
- * Tools that require human interaction and block indefinitely in non-interactive
53
- * tmux sessions. Agents run non-interactively and must never call these tools.
54
- * Use overstory mail (--type question) to escalate to the orchestrator instead.
55
- */
56
- const INTERACTIVE_TOOLS = ["AskUserQuestion", "EnterPlanMode", "EnterWorktree"];
57
-
58
- /** Tools that non-implementation agents must not use. */
59
- const WRITE_TOOLS = ["Write", "Edit", "NotebookEdit"];
60
-
61
- /**
62
- * Bash commands that modify files and must be blocked for non-implementation agents.
63
- * Each pattern is a regex fragment used inside a grep -qE check.
64
- */
65
- const DANGEROUS_BASH_PATTERNS = [
66
- "sed\\s+-i",
67
- "sed\\s+--in-place",
68
- "echo\\s+.*>",
69
- "printf\\s+.*>",
70
- "cat\\s+.*>",
71
- "tee\\s",
72
- "\\bvim\\b",
73
- "\\bnano\\b",
74
- "\\bvi\\b",
75
- "\\bmv\\s",
76
- "\\bcp\\s",
77
- "\\brm\\s",
78
- "\\bmkdir\\s",
79
- "\\btouch\\s",
80
- "\\bchmod\\s",
81
- "\\bchown\\s",
82
- ">>",
83
- "\\bgit\\s+add\\b",
84
- "\\bgit\\s+commit\\b",
85
- "\\bgit\\s+merge\\b",
86
- "\\bgit\\s+push\\b",
87
- "\\bgit\\s+reset\\b",
88
- "\\bgit\\s+checkout\\b",
89
- "\\bgit\\s+rebase\\b",
90
- "\\bgit\\s+stash\\b",
91
- "\\bnpm\\s+install\\b",
92
- "\\bbun\\s+install\\b",
93
- "\\bbun\\s+add\\b",
94
- // Runtime eval flags — bypass shell pattern guards by executing JS/Python directly
95
- "\\bbun\\s+-e\\b",
96
- "\\bbun\\s+--eval\\b",
97
- "\\bnode\\s+-e\\b",
98
- "\\bnode\\s+--eval\\b",
99
- "\\bdeno\\s+eval\\b",
100
- "\\bpython3?\\s+-c\\b",
101
- "\\bperl\\s+-e\\b",
102
- "\\bruby\\s+-e\\b",
103
- ];
104
-
105
- /**
106
- * Bash commands that are always safe for non-implementation agents.
107
- * If a command starts with any of these prefixes, it bypasses the dangerous command check.
108
- * This whitelist is checked BEFORE the blocklist.
109
- */
110
- const SAFE_BASH_PREFIXES = [
111
- "ov ",
112
- "overstory ",
113
- "bd ",
114
- "sd ",
115
- "git status",
116
- "git log",
117
- "git diff",
118
- "git show",
119
- "git blame",
120
- "git branch",
121
- "mulch ",
122
- ];
123
-
124
41
  /**
125
42
  * Extract command prefixes from quality gate configurations.
126
43
  *
@@ -265,7 +265,7 @@ describe("generateOverlay", () => {
265
265
  expect(output).not.toContain("bun run lint");
266
266
  });
267
267
 
268
- test("scout completion section includes bd close and mail send", async () => {
268
+ test("scout completion section includes sd close and mail send", async () => {
269
269
  const config = makeConfig({
270
270
  capability: "scout",
271
271
  agentName: "recon-1",
@@ -274,7 +274,7 @@ describe("generateOverlay", () => {
274
274
  });
275
275
  const output = await generateOverlay(config);
276
276
 
277
- expect(output).toContain("bd close overstory-task1");
277
+ expect(output).toContain("sd close overstory-task1");
278
278
  expect(output).toContain("ov mail send --to lead-alpha");
279
279
  });
280
280
 
@@ -426,14 +426,14 @@ describe("generateOverlay", () => {
426
426
  expect(output).not.toContain("Quality Gates");
427
427
  });
428
428
 
429
- test("default trackerCli renders as bd in quality gates", async () => {
429
+ test("default trackerCli renders as sd in quality gates", async () => {
430
430
  const config = makeConfig({ capability: "builder", taskId: "overstory-task1" });
431
431
  const output = await generateOverlay(config);
432
432
 
433
- expect(output).toContain("bd close overstory-task1");
433
+ expect(output).toContain("sd close overstory-task1");
434
434
  });
435
435
 
436
- test("custom trackerCli replaces bd in quality gates", async () => {
436
+ test("custom trackerCli replaces sd in quality gates", async () => {
437
437
  const config = makeConfig({
438
438
  capability: "builder",
439
439
  trackerCli: "sd",
@@ -489,11 +489,11 @@ describe("generateOverlay", () => {
489
489
  expect(output).not.toContain("{{TRACKER_NAME}}");
490
490
  });
491
491
 
492
- test("defaults backward-compatible: no trackerCli/trackerName produces bd/beads", async () => {
492
+ test("defaults: no trackerCli/trackerName produces sd/seeds", async () => {
493
493
  const config = makeConfig({ capability: "builder", taskId: "overstory-back" });
494
494
  const output = await generateOverlay(config);
495
495
 
496
- expect(output).toContain("bd close overstory-back");
496
+ expect(output).toContain("sd close overstory-back");
497
497
  });
498
498
 
499
499
  test("dispatch overrides: skipReview injects SKIP REVIEW directive for leads", async () => {
@@ -736,6 +736,29 @@ describe("writeOverlay", () => {
736
736
  const exists = await Bun.file(outputPath).exists();
737
737
  expect(exists).toBe(true);
738
738
  });
739
+
740
+ test("writes to custom instruction path when provided", async () => {
741
+ const worktreePath = join(tempDir, "worktree");
742
+ const config = makeConfig();
743
+ await writeOverlay(worktreePath, config, "/nonexistent-canonical-root", "AGENTS.md");
744
+ const outputPath = join(worktreePath, "AGENTS.md");
745
+ expect(await Bun.file(outputPath).exists()).toBe(true);
746
+ expect(await Bun.file(join(worktreePath, ".claude", "CLAUDE.md")).exists()).toBe(false);
747
+ });
748
+
749
+ test("custom instruction path creates necessary subdirectories", async () => {
750
+ const worktreePath = join(tempDir, "worktree");
751
+ const config = makeConfig();
752
+ await writeOverlay(
753
+ worktreePath,
754
+ config,
755
+ "/nonexistent-canonical-root",
756
+ ".pi/instructions/AGENT.md",
757
+ );
758
+ expect(await Bun.file(join(worktreePath, ".pi", "instructions", "AGENT.md")).exists()).toBe(
759
+ true,
760
+ );
761
+ });
739
762
  });
740
763
 
741
764
  describe("isCanonicalRoot", () => {
@@ -182,7 +182,7 @@ function formatQualityGates(config: OverlayConfig): string {
182
182
  "Before reporting completion:",
183
183
  "",
184
184
  `1. **Record mulch learnings:** \`ml record <domain> --type <convention|pattern|reference> --description "..."\` — capture reusable knowledge from your work`,
185
- `2. **Close issue:** \`${config.trackerCli ?? "bd"} close ${config.taskId} --reason "summary of findings"\``,
185
+ `2. **Close issue:** \`${config.trackerCli ?? "sd"} close ${config.taskId} --reason "summary of findings"\``,
186
186
  `3. **Send results:** \`ov mail send --to ${config.parentAgent ?? "coordinator"} --subject "done" --body "Summary" --type result --agent ${config.agentName}\``,
187
187
  "",
188
188
  "You are a read-only agent. Do NOT commit, modify files, or run quality gates.",
@@ -207,7 +207,7 @@ function formatQualityGates(config: OverlayConfig): string {
207
207
  `${gateLines.length + 1}. **Commit:** all changes committed to your branch (${config.branchName})`,
208
208
  `${gateLines.length + 2}. **Record mulch learnings:** \`ml record <domain> --type <convention|pattern|failure|decision> --description "..." --outcome-status success --outcome-agent ${config.agentName}\` — capture insights from your work`,
209
209
  `${gateLines.length + 3}. **Signal completion:** send \`worker_done\` mail to ${config.parentAgent ?? "coordinator"}: \`ov mail send --to ${config.parentAgent ?? "coordinator"} --subject "Worker done: ${config.taskId}" --body "Quality gates passed." --type worker_done --agent ${config.agentName}\``,
210
- `${gateLines.length + 4}. **Close issue:** \`${config.trackerCli ?? "bd"} close ${config.taskId} --reason "summary of changes"\``,
210
+ `${gateLines.length + 4}. **Close issue:** \`${config.trackerCli ?? "sd"} close ${config.taskId} --reason "summary of changes"\``,
211
211
  "",
212
212
  "Do NOT push to the canonical branch. Your work will be merged by the",
213
213
  "coordinator via `ov merge`.",
@@ -225,7 +225,7 @@ function formatConstraints(config: OverlayConfig): string {
225
225
  "",
226
226
  "- You are **read-only**: do NOT modify, create, or delete any files",
227
227
  "- Do NOT commit, push, or make any git state changes",
228
- `- Report completion via \`${config.trackerCli ?? "bd"} close\` AND \`ov mail send --type result\``,
228
+ `- Report completion via \`${config.trackerCli ?? "sd"} close\` AND \`ov mail send --type result\``,
229
229
  "- If you encounter a blocking issue, send mail with `--priority urgent --type error`",
230
230
  ].join("\n");
231
231
  }
@@ -238,7 +238,7 @@ function formatConstraints(config: OverlayConfig): string {
238
238
  "- Only modify files in your File Scope",
239
239
  `- Commit only to your branch: ${config.branchName}`,
240
240
  "- Never push to the canonical branch",
241
- `- Report completion via \`${config.trackerCli ?? "bd"} close\` AND \`ov mail send --type result\``,
241
+ `- Report completion via \`${config.trackerCli ?? "sd"} close\` AND \`ov mail send --type result\``,
242
242
  "- If you encounter a blocking issue, send mail with `--priority urgent --type error`",
243
243
  ].join("\n");
244
244
  }
@@ -318,7 +318,7 @@ export async function generateOverlay(config: OverlayConfig): Promise<string> {
318
318
  "{{QUALITY_GATE_STEPS}}": formatQualityGatesSteps(config.qualityGates),
319
319
  "{{QUALITY_GATE_BASH}}": formatQualityGatesBash(config.qualityGates),
320
320
  "{{QUALITY_GATE_CAPABILITIES}}": formatQualityGatesCapabilities(config.qualityGates),
321
- "{{TRACKER_CLI}}": config.trackerCli ?? "bd",
321
+ "{{TRACKER_CLI}}": config.trackerCli ?? "sd",
322
322
  "{{TRACKER_NAME}}": config.trackerName ?? "seeds",
323
323
  };
324
324
 
@@ -369,6 +369,7 @@ export async function writeOverlay(
369
369
  worktreePath: string,
370
370
  config: OverlayConfig,
371
371
  canonicalRoot: string,
372
+ instructionPath = ".claude/CLAUDE.md",
372
373
  ): Promise<void> {
373
374
  // Guard: never write agent overlays to the canonical project root.
374
375
  // The project root's .claude/CLAUDE.md belongs to the orchestrator/user.
@@ -383,13 +384,13 @@ export async function writeOverlay(
383
384
  }
384
385
 
385
386
  const content = await generateOverlay(config);
386
- const claudeDir = join(worktreePath, ".claude");
387
- const outputPath = join(claudeDir, "CLAUDE.md");
387
+ const outputPath = join(worktreePath, instructionPath);
388
+ const outputDir = dirname(outputPath);
388
389
 
389
390
  try {
390
- await mkdir(claudeDir, { recursive: true });
391
+ await mkdir(outputDir, { recursive: true });
391
392
  } catch (err) {
392
- throw new AgentError(`Failed to create .claude/ directory at: ${claudeDir}`, {
393
+ throw new AgentError(`Failed to create directory for instruction file at: ${outputDir}`, {
393
394
  agentName: config.agentName,
394
395
  cause: err instanceof Error ? err : undefined,
395
396
  });
@@ -108,6 +108,7 @@ describe("discoverAgents", () => {
108
108
  lastActivity: "2024-01-01T00:01:00Z",
109
109
  escalationLevel: 0,
110
110
  stalledSince: null,
111
+ transcriptPath: null,
111
112
  };
112
113
 
113
114
  store.upsert(session);
@@ -140,6 +141,7 @@ describe("discoverAgents", () => {
140
141
  lastActivity: "2024-01-01T00:01:00Z",
141
142
  escalationLevel: 0,
142
143
  stalledSince: null,
144
+ transcriptPath: null,
143
145
  };
144
146
 
145
147
  const scout: AgentSession = {
@@ -159,6 +161,7 @@ describe("discoverAgents", () => {
159
161
  lastActivity: "2024-01-01T00:01:00Z",
160
162
  escalationLevel: 0,
161
163
  stalledSince: null,
164
+ transcriptPath: null,
162
165
  };
163
166
 
164
167
  store.upsert(builder);
@@ -191,6 +194,7 @@ describe("discoverAgents", () => {
191
194
  lastActivity: "2024-01-01T00:01:00Z",
192
195
  escalationLevel: 0,
193
196
  stalledSince: null,
197
+ transcriptPath: null,
194
198
  };
195
199
 
196
200
  const completed: AgentSession = {
@@ -210,6 +214,7 @@ describe("discoverAgents", () => {
210
214
  lastActivity: "2024-01-01T00:02:00Z",
211
215
  escalationLevel: 0,
212
216
  stalledSince: null,
217
+ transcriptPath: null,
213
218
  };
214
219
 
215
220
  store.upsert(working);
@@ -159,6 +159,7 @@ describe("--all", () => {
159
159
  lastActivity: new Date().toISOString(),
160
160
  escalationLevel: 0,
161
161
  stalledSince: null,
162
+ transcriptPath: null,
162
163
  });
163
164
  store.close();
164
165
 
@@ -303,6 +304,7 @@ describe("individual flags", () => {
303
304
  lastActivity: new Date().toISOString(),
304
305
  escalationLevel: 0,
305
306
  stalledSince: null,
307
+ transcriptPath: null,
306
308
  });
307
309
  store.close();
308
310
 
@@ -424,6 +426,7 @@ describe("synthetic session-end events", () => {
424
426
  lastActivity: new Date().toISOString(),
425
427
  escalationLevel: 0,
426
428
  stalledSince: null,
429
+ transcriptPath: null,
427
430
  ...overrides,
428
431
  };
429
432
  }
@@ -356,7 +356,7 @@ export const COMMANDS: readonly CommandDef[] = [
356
356
  },
357
357
  {
358
358
  name: "supervisor",
359
- desc: "Per-project supervisor agent",
359
+ desc: "[DEPRECATED] Per-project supervisor agent",
360
360
  flags: [
361
361
  { name: "--json", desc: "JSON output" },
362
362
  { name: "--help", desc: "Show help" },
@@ -297,6 +297,7 @@ function makeCoordinatorSession(overrides: Partial<AgentSession> = {}): AgentSes
297
297
  lastActivity: new Date().toISOString(),
298
298
  escalationLevel: 0,
299
299
  stalledSince: null,
300
+ transcriptPath: null,
300
301
  ...overrides,
301
302
  };
302
303
  }
@@ -15,13 +15,13 @@
15
15
  import { mkdir, unlink } from "node:fs/promises";
16
16
  import { join } from "node:path";
17
17
  import { Command } from "commander";
18
- import { deployHooks } from "../agents/hooks-deployer.ts";
19
18
  import { createIdentity, loadIdentity } from "../agents/identity.ts";
20
19
  import { createManifestLoader, resolveModel } from "../agents/manifest.ts";
21
20
  import { loadConfig } from "../config.ts";
22
21
  import { AgentError, ValidationError } from "../errors.ts";
23
22
  import { jsonOutput } from "../json.ts";
24
23
  import { printHint, printSuccess, printWarning } from "../logging/color.ts";
24
+ import { getRuntime } from "../runtimes/registry.ts";
25
25
  import { openSessionStore } from "../sessions/compat.ts";
26
26
  import { createRunStore } from "../sessions/store.ts";
27
27
  import { resolveBackend, trackerCliName } from "../tracker/factory.ts";
@@ -62,6 +62,7 @@ export interface CoordinatorDeps {
62
62
  sendKeys: (name: string, keys: string) => Promise<void>;
63
63
  waitForTuiReady: (
64
64
  name: string,
65
+ detectReady: (paneContent: string) => import("../runtimes/types.ts").ReadyState,
65
66
  timeoutMs?: number,
66
67
  pollIntervalMs?: number,
67
68
  ) => Promise<boolean>;
@@ -318,13 +319,26 @@ async function startCoordinator(
318
319
  store.updateState(COORDINATOR_NAME, "completed");
319
320
  }
320
321
 
322
+ // Resolve model and runtime early (needed for deployConfig and spawn)
323
+ const manifestLoader = createManifestLoader(
324
+ join(projectRoot, config.agents.manifestPath),
325
+ join(projectRoot, config.agents.baseDir),
326
+ );
327
+ const manifest = await manifestLoader.load();
328
+ const resolvedModel = resolveModel(config, manifest, "coordinator", "opus");
329
+ const runtime = getRuntime(undefined, config);
330
+
321
331
  // Deploy hooks to the project root so the coordinator gets event logging,
322
332
  // mail check --inject, and activity tracking via the standard hook pipeline.
323
333
  // The ENV_GUARD prefix on all hooks (both template and generated guards)
324
334
  // ensures they only activate when OVERSTORY_AGENT_NAME is set (i.e. for
325
335
  // the coordinator's tmux session), so the user's own Claude Code session
326
336
  // at the project root is unaffected.
327
- await deployHooks(projectRoot, COORDINATOR_NAME, "coordinator");
337
+ await runtime.deployConfig(projectRoot, undefined, {
338
+ agentName: COORDINATOR_NAME,
339
+ capability: "coordinator",
340
+ worktreePath: projectRoot,
341
+ });
328
342
 
329
343
  // Create coordinator identity if first run
330
344
  const identityBaseDir = join(projectRoot, ".overstory", "agents");
@@ -341,14 +355,6 @@ async function startCoordinator(
341
355
  });
342
356
  }
343
357
 
344
- // Resolve model from config > manifest > fallback
345
- const manifestLoader = createManifestLoader(
346
- join(projectRoot, config.agents.manifestPath),
347
- join(projectRoot, config.agents.baseDir),
348
- );
349
- const manifest = await manifestLoader.load();
350
- const { model, env } = resolveModel(config, manifest, "coordinator", "opus");
351
-
352
358
  // Preflight: verify tmux is installed before attempting to spawn.
353
359
  // Without this check, a missing tmux leads to cryptic errors later.
354
360
  await tmux.ensureTmuxAvailable();
@@ -359,15 +365,22 @@ async function startCoordinator(
359
365
  // (overstory-gaio, overstory-0kwf).
360
366
  const agentDefPath = join(projectRoot, ".overstory", "agent-defs", "coordinator.md");
361
367
  const agentDefFile = Bun.file(agentDefPath);
362
- let claudeCmd = `claude --model ${model} --permission-mode bypassPermissions`;
368
+ let appendSystemPrompt: string | undefined;
363
369
  if (await agentDefFile.exists()) {
364
- const agentDef = await agentDefFile.text();
365
- // Single-quote the content for safe shell expansion (only escape single quotes)
366
- const escaped = agentDef.replace(/'/g, "'\\''");
367
- claudeCmd += ` --append-system-prompt '${escaped}'`;
370
+ appendSystemPrompt = await agentDefFile.text();
368
371
  }
369
- const pid = await tmux.createSession(tmuxSession, projectRoot, claudeCmd, {
370
- ...env,
372
+ const spawnCmd = runtime.buildSpawnCommand({
373
+ model: resolvedModel.model,
374
+ permissionMode: "bypass",
375
+ cwd: projectRoot,
376
+ appendSystemPrompt,
377
+ env: {
378
+ ...runtime.buildEnv(resolvedModel),
379
+ OVERSTORY_AGENT_NAME: COORDINATOR_NAME,
380
+ },
381
+ });
382
+ const pid = await tmux.createSession(tmuxSession, projectRoot, spawnCmd, {
383
+ ...runtime.buildEnv(resolvedModel),
371
384
  OVERSTORY_AGENT_NAME: COORDINATOR_NAME,
372
385
  });
373
386
 
@@ -392,12 +405,15 @@ async function startCoordinator(
392
405
  lastActivity: new Date().toISOString(),
393
406
  escalationLevel: 0,
394
407
  stalledSince: null,
408
+ transcriptPath: null,
395
409
  };
396
410
 
397
411
  store.upsert(session);
398
412
 
399
413
  // Wait for Claude Code TUI to render before sending input
400
- const tuiReady = await tmux.waitForTuiReady(tmuxSession);
414
+ const tuiReady = await tmux.waitForTuiReady(tmuxSession, (content) =>
415
+ runtime.detectReady(content),
416
+ );
401
417
  if (!tuiReady) {
402
418
  // Session may have died — check liveness before proceeding
403
419
  const alive = await tmux.isSessionAlive(tmuxSession);
@@ -303,7 +303,7 @@ describe("costsCommand", () => {
303
303
  await costsCommand([]);
304
304
  const out = output();
305
305
 
306
- expect(out).toContain("=".repeat(70));
306
+ expect(out).toContain("\u2500".repeat(70));
307
307
  });
308
308
 
309
309
  test("shows Total row", async () => {
@@ -780,6 +780,7 @@ describe("costsCommand", () => {
780
780
  lastActivity: new Date().toISOString(),
781
781
  escalationLevel: 0,
782
782
  stalledSince: null,
783
+ transcriptPath: null,
783
784
  });
784
785
  sessionStore.close();
785
786
 
@@ -836,6 +837,7 @@ describe("costsCommand", () => {
836
837
  lastActivity: new Date().toISOString(),
837
838
  escalationLevel: 0,
838
839
  stalledSince: null,
840
+ transcriptPath: null,
839
841
  });
840
842
  sessionStore.close();
841
843
 
@@ -900,6 +902,7 @@ describe("costsCommand", () => {
900
902
  lastActivity: new Date().toISOString(),
901
903
  escalationLevel: 0,
902
904
  stalledSince: null,
905
+ transcriptPath: null,
903
906
  });
904
907
  sessionStore.upsert({
905
908
  id: "sess-002",
@@ -918,6 +921,7 @@ describe("costsCommand", () => {
918
921
  lastActivity: new Date().toISOString(),
919
922
  escalationLevel: 0,
920
923
  stalledSince: null,
924
+ transcriptPath: null,
921
925
  });
922
926
  sessionStore.close();
923
927
 
@@ -977,6 +981,7 @@ describe("costsCommand", () => {
977
981
  lastActivity: new Date().toISOString(),
978
982
  escalationLevel: 0,
979
983
  stalledSince: null,
984
+ transcriptPath: null,
980
985
  });
981
986
  sessionStore.close();
982
987