@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.
- package/README.md +12 -13
- package/agents/builder.md +1 -1
- package/agents/coordinator.md +12 -11
- package/agents/lead.md +25 -24
- package/agents/monitor.md +4 -4
- package/agents/reviewer.md +1 -1
- package/agents/scout.md +5 -5
- package/agents/supervisor.md +36 -32
- package/package.json +5 -3
- package/src/agents/guard-rules.ts +97 -0
- package/src/agents/hooks-deployer.ts +7 -90
- package/src/agents/overlay.test.ts +30 -7
- package/src/agents/overlay.ts +10 -9
- package/src/commands/agents.test.ts +5 -0
- package/src/commands/clean.test.ts +3 -0
- package/src/commands/completions.ts +1 -1
- package/src/commands/coordinator.test.ts +1 -0
- package/src/commands/coordinator.ts +34 -18
- package/src/commands/costs.test.ts +6 -1
- package/src/commands/costs.ts +13 -20
- package/src/commands/dashboard.ts +38 -138
- package/src/commands/doctor.test.ts +1 -1
- package/src/commands/doctor.ts +2 -2
- package/src/commands/ecosystem.ts +2 -1
- package/src/commands/errors.test.ts +4 -5
- package/src/commands/errors.ts +4 -62
- package/src/commands/feed.test.ts +2 -2
- package/src/commands/feed.ts +12 -106
- package/src/commands/init.test.ts +1 -2
- package/src/commands/init.ts +1 -8
- package/src/commands/inspect.test.ts +14 -0
- package/src/commands/inspect.ts +10 -44
- package/src/commands/log.test.ts +14 -0
- package/src/commands/log.ts +39 -0
- package/src/commands/logs.ts +7 -63
- package/src/commands/mail.test.ts +5 -0
- package/src/commands/metrics.test.ts +2 -2
- package/src/commands/metrics.ts +3 -17
- package/src/commands/monitor.ts +30 -16
- package/src/commands/nudge.test.ts +1 -0
- package/src/commands/prime.test.ts +2 -0
- package/src/commands/prime.ts +6 -2
- package/src/commands/replay.test.ts +2 -2
- package/src/commands/replay.ts +12 -135
- package/src/commands/run.test.ts +1 -0
- package/src/commands/run.ts +7 -23
- package/src/commands/sling.test.ts +68 -1
- package/src/commands/sling.ts +62 -24
- package/src/commands/status.test.ts +1 -0
- package/src/commands/status.ts +4 -17
- package/src/commands/stop.test.ts +1 -0
- package/src/commands/supervisor.ts +35 -18
- package/src/commands/trace.test.ts +6 -6
- package/src/commands/trace.ts +11 -109
- package/src/commands/worktree.test.ts +9 -0
- package/src/config.ts +39 -0
- package/src/doctor/consistency.test.ts +14 -0
- package/src/e2e/init-sling-lifecycle.test.ts +3 -5
- package/src/index.ts +2 -1
- package/src/logging/format.ts +214 -0
- package/src/logging/theme.ts +132 -0
- package/src/mail/broadcast.test.ts +1 -0
- package/src/merge/resolver.ts +23 -4
- package/src/metrics/store.test.ts +46 -0
- package/src/metrics/store.ts +11 -0
- package/src/mulch/client.test.ts +20 -0
- package/src/mulch/client.ts +312 -45
- package/src/runtimes/claude.test.ts +616 -0
- package/src/runtimes/claude.ts +218 -0
- package/src/runtimes/pi-guards.test.ts +433 -0
- package/src/runtimes/pi-guards.ts +349 -0
- package/src/runtimes/pi.test.ts +620 -0
- package/src/runtimes/pi.ts +244 -0
- package/src/runtimes/registry.test.ts +86 -0
- package/src/runtimes/registry.ts +46 -0
- package/src/runtimes/types.ts +188 -0
- package/src/schema-consistency.test.ts +1 -0
- package/src/sessions/compat.ts +1 -0
- package/src/sessions/store.test.ts +31 -0
- package/src/sessions/store.ts +37 -4
- package/src/types.ts +21 -0
- package/src/watchdog/daemon.test.ts +7 -4
- package/src/watchdog/daemon.ts +1 -1
- package/src/watchdog/health.test.ts +1 -0
- package/src/watchdog/triage.ts +14 -4
- package/src/worktree/tmux.test.ts +28 -13
- 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
|
|
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("
|
|
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
|
|
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("
|
|
433
|
+
expect(output).toContain("sd close overstory-task1");
|
|
434
434
|
});
|
|
435
435
|
|
|
436
|
-
test("custom trackerCli replaces
|
|
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
|
|
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("
|
|
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", () => {
|
package/src/agents/overlay.ts
CHANGED
|
@@ -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 ?? "
|
|
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 ?? "
|
|
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 ?? "
|
|
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 ?? "
|
|
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 ?? "
|
|
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
|
|
387
|
-
const
|
|
387
|
+
const outputPath = join(worktreePath, instructionPath);
|
|
388
|
+
const outputDir = dirname(outputPath);
|
|
388
389
|
|
|
389
390
|
try {
|
|
390
|
-
await mkdir(
|
|
391
|
+
await mkdir(outputDir, { recursive: true });
|
|
391
392
|
} catch (err) {
|
|
392
|
-
throw new AgentError(`Failed to create
|
|
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" },
|
|
@@ -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
|
|
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
|
|
368
|
+
let appendSystemPrompt: string | undefined;
|
|
363
369
|
if (await agentDefFile.exists()) {
|
|
364
|
-
|
|
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
|
|
370
|
-
|
|
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("
|
|
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
|
|