@os-eco/overstory-cli 0.6.1 → 0.6.5
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 +8 -7
- package/package.json +12 -4
- package/src/agents/checkpoint.test.ts +2 -2
- package/src/agents/hooks-deployer.test.ts +131 -16
- package/src/agents/hooks-deployer.ts +33 -1
- package/src/agents/identity.test.ts +27 -27
- package/src/agents/identity.ts +10 -10
- package/src/agents/lifecycle.test.ts +6 -6
- package/src/agents/lifecycle.ts +2 -2
- package/src/agents/manifest.test.ts +86 -0
- package/src/agents/overlay.test.ts +9 -9
- package/src/agents/overlay.ts +4 -4
- package/src/commands/agents.test.ts +8 -8
- package/src/commands/agents.ts +62 -91
- package/src/commands/clean.test.ts +36 -51
- package/src/commands/clean.ts +28 -49
- package/src/commands/completions.ts +14 -0
- package/src/commands/coordinator.test.ts +133 -26
- package/src/commands/coordinator.ts +101 -64
- package/src/commands/costs.test.ts +47 -47
- package/src/commands/costs.ts +96 -75
- package/src/commands/dashboard.test.ts +2 -2
- package/src/commands/dashboard.ts +75 -95
- package/src/commands/doctor.test.ts +2 -2
- package/src/commands/doctor.ts +92 -79
- package/src/commands/errors.test.ts +2 -2
- package/src/commands/errors.ts +56 -50
- package/src/commands/feed.test.ts +2 -2
- package/src/commands/feed.ts +86 -83
- package/src/commands/group.ts +167 -177
- package/src/commands/hooks.test.ts +2 -2
- package/src/commands/hooks.ts +52 -42
- package/src/commands/init.test.ts +19 -19
- package/src/commands/init.ts +7 -16
- package/src/commands/inspect.test.ts +18 -18
- package/src/commands/inspect.ts +55 -58
- package/src/commands/log.test.ts +26 -31
- package/src/commands/log.ts +97 -91
- package/src/commands/logs.test.ts +1 -1
- package/src/commands/logs.ts +101 -104
- package/src/commands/mail.test.ts +5 -5
- package/src/commands/mail.ts +157 -169
- package/src/commands/merge.test.ts +28 -66
- package/src/commands/merge.ts +21 -51
- package/src/commands/metrics.test.ts +8 -8
- package/src/commands/metrics.ts +34 -35
- package/src/commands/monitor.test.ts +3 -3
- package/src/commands/monitor.ts +57 -62
- package/src/commands/nudge.test.ts +1 -1
- package/src/commands/nudge.ts +41 -89
- package/src/commands/prime.test.ts +19 -51
- package/src/commands/prime.ts +13 -50
- package/src/commands/replay.test.ts +2 -2
- package/src/commands/replay.ts +79 -86
- package/src/commands/run.test.ts +1 -1
- package/src/commands/run.ts +97 -77
- package/src/commands/sling.test.ts +201 -5
- package/src/commands/sling.ts +37 -64
- package/src/commands/spec.test.ts +14 -40
- package/src/commands/spec.ts +32 -101
- package/src/commands/status.test.ts +97 -1
- package/src/commands/status.ts +63 -58
- package/src/commands/stop.test.ts +22 -40
- package/src/commands/stop.ts +18 -33
- package/src/commands/supervisor.test.ts +12 -14
- package/src/commands/supervisor.ts +144 -165
- package/src/commands/trace.test.ts +15 -15
- package/src/commands/trace.ts +59 -82
- package/src/commands/watch.test.ts +2 -2
- package/src/commands/watch.ts +38 -45
- package/src/commands/worktree.test.ts +213 -37
- package/src/commands/worktree.ts +110 -55
- package/src/config.test.ts +96 -0
- package/src/doctor/consistency.test.ts +14 -14
- package/src/doctor/databases.test.ts +22 -2
- package/src/doctor/databases.ts +16 -0
- package/src/doctor/dependencies.test.ts +55 -1
- package/src/doctor/dependencies.ts +113 -18
- package/src/doctor/merge-queue.test.ts +4 -4
- package/src/e2e/init-sling-lifecycle.test.ts +8 -8
- package/src/errors.ts +1 -1
- package/src/index.ts +223 -213
- package/src/logging/color.test.ts +74 -91
- package/src/logging/color.ts +52 -46
- package/src/logging/reporter.test.ts +10 -10
- package/src/logging/reporter.ts +6 -5
- package/src/mail/broadcast.test.ts +1 -1
- package/src/mail/client.test.ts +6 -6
- package/src/mail/store.test.ts +3 -3
- package/src/merge/queue.test.ts +73 -7
- package/src/merge/queue.ts +17 -2
- package/src/merge/resolver.test.ts +159 -7
- package/src/merge/resolver.ts +46 -2
- package/src/metrics/store.test.ts +44 -44
- package/src/metrics/store.ts +2 -2
- package/src/metrics/summary.test.ts +35 -35
- package/src/mulch/client.test.ts +1 -1
- package/src/schema-consistency.test.ts +239 -0
- package/src/sessions/compat.test.ts +3 -3
- package/src/sessions/compat.ts +2 -2
- package/src/sessions/store.test.ts +41 -4
- package/src/sessions/store.ts +13 -2
- package/src/types.ts +14 -14
- package/src/watchdog/daemon.test.ts +10 -10
- package/src/watchdog/daemon.ts +1 -1
- package/src/watchdog/health.test.ts +1 -1
- package/src/worktree/manager.test.ts +20 -20
- package/src/worktree/manager.ts +120 -4
- package/src/worktree/tmux.test.ts +98 -9
- package/src/worktree/tmux.ts +18 -0
|
@@ -23,7 +23,7 @@ describe("lifecycle", () => {
|
|
|
23
23
|
agentsDir,
|
|
24
24
|
agentName: "builder-1",
|
|
25
25
|
sessionId: "session-100",
|
|
26
|
-
|
|
26
|
+
taskId: "overstory-xyz1",
|
|
27
27
|
reason: "compaction",
|
|
28
28
|
progressSummary: "Built the widget",
|
|
29
29
|
pendingWork: "Tests remain",
|
|
@@ -56,7 +56,7 @@ describe("lifecycle", () => {
|
|
|
56
56
|
agentsDir,
|
|
57
57
|
agentName: "builder-2",
|
|
58
58
|
sessionId: "session-200",
|
|
59
|
-
|
|
59
|
+
taskId: "overstory-abc2",
|
|
60
60
|
reason: "crash",
|
|
61
61
|
progressSummary: "Halfway done",
|
|
62
62
|
pendingWork: "Finish implementation",
|
|
@@ -82,7 +82,7 @@ describe("lifecycle", () => {
|
|
|
82
82
|
agentsDir,
|
|
83
83
|
agentName: "builder-3",
|
|
84
84
|
sessionId: "session-300",
|
|
85
|
-
|
|
85
|
+
taskId: "overstory-def3",
|
|
86
86
|
reason: "manual",
|
|
87
87
|
progressSummary: "Done with phase 1",
|
|
88
88
|
pendingWork: "Phase 2",
|
|
@@ -116,7 +116,7 @@ describe("lifecycle", () => {
|
|
|
116
116
|
agentsDir,
|
|
117
117
|
agentName: "builder-4",
|
|
118
118
|
sessionId: "session-400",
|
|
119
|
-
|
|
119
|
+
taskId: "overstory-ghi4",
|
|
120
120
|
reason: "compaction",
|
|
121
121
|
progressSummary: "First session work",
|
|
122
122
|
pendingWork: "Continue",
|
|
@@ -137,7 +137,7 @@ describe("lifecycle", () => {
|
|
|
137
137
|
agentsDir,
|
|
138
138
|
agentName: "builder-4",
|
|
139
139
|
sessionId: "session-401",
|
|
140
|
-
|
|
140
|
+
taskId: "overstory-ghi4",
|
|
141
141
|
reason: "timeout",
|
|
142
142
|
progressSummary: "Second session work",
|
|
143
143
|
pendingWork: "Finish up",
|
|
@@ -172,7 +172,7 @@ describe("lifecycle", () => {
|
|
|
172
172
|
agentsDir,
|
|
173
173
|
agentName: "builder-5",
|
|
174
174
|
sessionId: "session-500",
|
|
175
|
-
|
|
175
|
+
taskId: "overstory-jkl5",
|
|
176
176
|
reason: "compaction",
|
|
177
177
|
progressSummary: "Done",
|
|
178
178
|
pendingWork: "Nothing",
|
package/src/agents/lifecycle.ts
CHANGED
|
@@ -73,7 +73,7 @@ export async function initiateHandoff(options: {
|
|
|
73
73
|
agentsDir: string;
|
|
74
74
|
agentName: string;
|
|
75
75
|
sessionId: string;
|
|
76
|
-
|
|
76
|
+
taskId: string;
|
|
77
77
|
reason: SessionHandoff["reason"];
|
|
78
78
|
progressSummary: string;
|
|
79
79
|
pendingWork: string;
|
|
@@ -83,7 +83,7 @@ export async function initiateHandoff(options: {
|
|
|
83
83
|
}): Promise<SessionHandoff> {
|
|
84
84
|
const checkpoint: SessionCheckpoint = {
|
|
85
85
|
agentName: options.agentName,
|
|
86
|
-
|
|
86
|
+
taskId: options.taskId,
|
|
87
87
|
sessionId: options.sessionId,
|
|
88
88
|
timestamp: new Date().toISOString(),
|
|
89
89
|
progressSummary: options.progressSummary,
|
|
@@ -635,6 +635,40 @@ describe("resolveModel", () => {
|
|
|
635
635
|
const result = resolveModel(config, baseManifest, "coordinator", "opus");
|
|
636
636
|
expect(result).toEqual({ model: "native-gw/claude-3-5-sonnet" });
|
|
637
637
|
});
|
|
638
|
+
|
|
639
|
+
test("handles deeply nested model ID (slashes in model name)", () => {
|
|
640
|
+
const config = makeConfig(
|
|
641
|
+
{ coordinator: "openrouter/openai/gpt-5.3" },
|
|
642
|
+
{
|
|
643
|
+
openrouter: {
|
|
644
|
+
type: "gateway",
|
|
645
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
646
|
+
authTokenEnv: "OPENROUTER_API_KEY",
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
);
|
|
650
|
+
const result = resolveModel(config, baseManifest, "coordinator", "opus");
|
|
651
|
+
// First "/" splits provider "openrouter" from model ID "openai/gpt-5.3"
|
|
652
|
+
expect(result.model).toBe("sonnet");
|
|
653
|
+
expect(result.env?.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe("openai/gpt-5.3");
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
test("handles model ID with multiple slashes after provider", () => {
|
|
657
|
+
const config = makeConfig(
|
|
658
|
+
{ coordinator: "mygateway/org/model/version" },
|
|
659
|
+
{
|
|
660
|
+
mygateway: {
|
|
661
|
+
type: "gateway",
|
|
662
|
+
baseUrl: "https://mygateway.example.com",
|
|
663
|
+
authTokenEnv: "MYGATEWAY_KEY",
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
);
|
|
667
|
+
const result = resolveModel(config, baseManifest, "coordinator", "opus");
|
|
668
|
+
// Provider is "mygateway", model ID is everything after the first "/"
|
|
669
|
+
expect(result.model).toBe("sonnet");
|
|
670
|
+
expect(result.env?.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe("org/model/version");
|
|
671
|
+
});
|
|
638
672
|
});
|
|
639
673
|
|
|
640
674
|
describe("resolveProviderEnv", () => {
|
|
@@ -704,6 +738,58 @@ describe("resolveProviderEnv", () => {
|
|
|
704
738
|
);
|
|
705
739
|
expect(result).not.toHaveProperty("ANTHROPIC_AUTH_TOKEN");
|
|
706
740
|
});
|
|
741
|
+
|
|
742
|
+
test("env always sets ANTHROPIC_API_KEY to empty string", () => {
|
|
743
|
+
const result = resolveProviderEnv(
|
|
744
|
+
"openrouter",
|
|
745
|
+
"openai/gpt-5.3",
|
|
746
|
+
{
|
|
747
|
+
openrouter: {
|
|
748
|
+
type: "gateway",
|
|
749
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
750
|
+
authTokenEnv: "OPENROUTER_API_KEY",
|
|
751
|
+
},
|
|
752
|
+
},
|
|
753
|
+
{ OPENROUTER_API_KEY: "my-token" },
|
|
754
|
+
);
|
|
755
|
+
expect(result).not.toBeNull();
|
|
756
|
+
expect(result?.ANTHROPIC_API_KEY).toBe("");
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
test("handles authTokenEnv pointing to undefined env var", () => {
|
|
760
|
+
const result = resolveProviderEnv(
|
|
761
|
+
"openrouter",
|
|
762
|
+
"openai/gpt-5.3",
|
|
763
|
+
{
|
|
764
|
+
openrouter: {
|
|
765
|
+
type: "gateway",
|
|
766
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
767
|
+
authTokenEnv: "MISSING_VAR",
|
|
768
|
+
},
|
|
769
|
+
},
|
|
770
|
+
{},
|
|
771
|
+
);
|
|
772
|
+
expect(result).not.toBeNull();
|
|
773
|
+
expect(result).not.toHaveProperty("ANTHROPIC_AUTH_TOKEN");
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
test("handles authTokenEnv field being undefined", () => {
|
|
777
|
+
const result = resolveProviderEnv(
|
|
778
|
+
"mygw",
|
|
779
|
+
"some-model",
|
|
780
|
+
{
|
|
781
|
+
mygw: {
|
|
782
|
+
type: "gateway",
|
|
783
|
+
baseUrl: "https://mygw.example.com",
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
{},
|
|
787
|
+
);
|
|
788
|
+
expect(result).not.toBeNull();
|
|
789
|
+
expect(result?.ANTHROPIC_BASE_URL).toBe("https://mygw.example.com");
|
|
790
|
+
expect(result?.ANTHROPIC_API_KEY).toBe("");
|
|
791
|
+
expect(result).not.toHaveProperty("ANTHROPIC_AUTH_TOKEN");
|
|
792
|
+
});
|
|
707
793
|
});
|
|
708
794
|
|
|
709
795
|
describe("manifest validation accepts arbitrary model strings", () => {
|
|
@@ -25,7 +25,7 @@ Read your assignment. Execute immediately.
|
|
|
25
25
|
function makeConfig(overrides?: Partial<OverlayConfig>): OverlayConfig {
|
|
26
26
|
return {
|
|
27
27
|
agentName: "test-builder",
|
|
28
|
-
|
|
28
|
+
taskId: "overstory-abc",
|
|
29
29
|
specPath: ".overstory/specs/overstory-abc.md",
|
|
30
30
|
branchName: "agent/test-builder/overstory-abc",
|
|
31
31
|
worktreePath: "/tmp/test-project/.overstory/worktrees/test-builder",
|
|
@@ -48,8 +48,8 @@ describe("generateOverlay", () => {
|
|
|
48
48
|
expect(output).toContain("my-scout");
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
test("output contains
|
|
52
|
-
const config = makeConfig({
|
|
51
|
+
test("output contains task ID", async () => {
|
|
52
|
+
const config = makeConfig({ taskId: "overstory-xyz" });
|
|
53
53
|
const output = await generateOverlay(config);
|
|
54
54
|
|
|
55
55
|
expect(output).toContain("overstory-xyz");
|
|
@@ -261,7 +261,7 @@ describe("generateOverlay", () => {
|
|
|
261
261
|
const config = makeConfig({
|
|
262
262
|
capability: "scout",
|
|
263
263
|
agentName: "recon-1",
|
|
264
|
-
|
|
264
|
+
taskId: "overstory-task1",
|
|
265
265
|
parentAgent: "lead-alpha",
|
|
266
266
|
});
|
|
267
267
|
const output = await generateOverlay(config);
|
|
@@ -419,7 +419,7 @@ describe("generateOverlay", () => {
|
|
|
419
419
|
});
|
|
420
420
|
|
|
421
421
|
test("default trackerCli renders as bd in quality gates", async () => {
|
|
422
|
-
const config = makeConfig({ capability: "builder",
|
|
422
|
+
const config = makeConfig({ capability: "builder", taskId: "overstory-task1" });
|
|
423
423
|
const output = await generateOverlay(config);
|
|
424
424
|
|
|
425
425
|
expect(output).toContain("bd close overstory-task1");
|
|
@@ -429,7 +429,7 @@ describe("generateOverlay", () => {
|
|
|
429
429
|
const config = makeConfig({
|
|
430
430
|
capability: "builder",
|
|
431
431
|
trackerCli: "sd",
|
|
432
|
-
|
|
432
|
+
taskId: "overstory-test1",
|
|
433
433
|
});
|
|
434
434
|
const output = await generateOverlay(config);
|
|
435
435
|
|
|
@@ -451,7 +451,7 @@ describe("generateOverlay", () => {
|
|
|
451
451
|
const config = makeConfig({
|
|
452
452
|
capability: "scout",
|
|
453
453
|
trackerCli: "sd",
|
|
454
|
-
|
|
454
|
+
taskId: "overstory-test2",
|
|
455
455
|
});
|
|
456
456
|
const output = await generateOverlay(config);
|
|
457
457
|
|
|
@@ -482,7 +482,7 @@ describe("generateOverlay", () => {
|
|
|
482
482
|
});
|
|
483
483
|
|
|
484
484
|
test("defaults backward-compatible: no trackerCli/trackerName produces bd/beads", async () => {
|
|
485
|
-
const config = makeConfig({ capability: "builder",
|
|
485
|
+
const config = makeConfig({ capability: "builder", taskId: "overstory-back" });
|
|
486
486
|
const output = await generateOverlay(config);
|
|
487
487
|
|
|
488
488
|
expect(output).toContain("bd close overstory-back");
|
|
@@ -521,7 +521,7 @@ describe("writeOverlay", () => {
|
|
|
521
521
|
const outputPath = join(worktreePath, ".claude", "CLAUDE.md");
|
|
522
522
|
const content = await Bun.file(outputPath).text();
|
|
523
523
|
expect(content).toContain("file-writer-test");
|
|
524
|
-
expect(content).toContain(config.
|
|
524
|
+
expect(content).toContain(config.taskId);
|
|
525
525
|
expect(content).toContain(config.branchName);
|
|
526
526
|
});
|
|
527
527
|
|
package/src/agents/overlay.ts
CHANGED
|
@@ -91,7 +91,7 @@ function formatQualityGates(config: OverlayConfig): string {
|
|
|
91
91
|
"Before reporting completion:",
|
|
92
92
|
"",
|
|
93
93
|
`1. **Record mulch learnings:** \`mulch record <domain> --type <convention|pattern|reference> --description "..."\` — capture reusable knowledge from your work`,
|
|
94
|
-
`2. **Close issue:** \`${config.trackerCli ?? "bd"} close ${config.
|
|
94
|
+
`2. **Close issue:** \`${config.trackerCli ?? "bd"} close ${config.taskId} --reason "summary of findings"\``,
|
|
95
95
|
`3. **Send results:** \`overstory mail send --to ${config.parentAgent ?? "coordinator"} --subject "done" --body "Summary" --type result --agent ${config.agentName}\``,
|
|
96
96
|
"",
|
|
97
97
|
"You are a read-only agent. Do NOT commit, modify files, or run quality gates.",
|
|
@@ -113,8 +113,8 @@ function formatQualityGates(config: OverlayConfig): string {
|
|
|
113
113
|
...gateLines,
|
|
114
114
|
`${gateLines.length + 1}. **Commit:** all changes committed to your branch (${config.branchName})`,
|
|
115
115
|
`${gateLines.length + 2}. **Record mulch learnings:** \`mulch record <domain> --type <convention|pattern|failure|decision> --description "..." --outcome-status success --outcome-agent ${config.agentName}\` — capture insights from your work`,
|
|
116
|
-
`${gateLines.length + 3}. **Signal completion:** send \`worker_done\` mail to ${config.parentAgent ?? "coordinator"}: \`overstory mail send --to ${config.parentAgent ?? "coordinator"} --subject "Worker done: ${config.
|
|
117
|
-
`${gateLines.length + 4}. **Close issue:** \`${config.trackerCli ?? "bd"} close ${config.
|
|
116
|
+
`${gateLines.length + 3}. **Signal completion:** send \`worker_done\` mail to ${config.parentAgent ?? "coordinator"}: \`overstory mail send --to ${config.parentAgent ?? "coordinator"} --subject "Worker done: ${config.taskId}" --body "Quality gates passed." --type worker_done --agent ${config.agentName}\``,
|
|
117
|
+
`${gateLines.length + 4}. **Close issue:** \`${config.trackerCli ?? "bd"} close ${config.taskId} --reason "summary of changes"\``,
|
|
118
118
|
"",
|
|
119
119
|
"Do NOT push to the canonical branch. Your work will be merged by the",
|
|
120
120
|
"coordinator via `overstory merge`.",
|
|
@@ -205,7 +205,7 @@ export async function generateOverlay(config: OverlayConfig): Promise<string> {
|
|
|
205
205
|
|
|
206
206
|
const replacements: Record<string, string> = {
|
|
207
207
|
"{{AGENT_NAME}}": config.agentName,
|
|
208
|
-
"{{BEAD_ID}}": config.
|
|
208
|
+
"{{BEAD_ID}}": config.taskId,
|
|
209
209
|
"{{SPEC_PATH}}": config.specPath ?? "No spec file provided",
|
|
210
210
|
"{{BRANCH_NAME}}": config.branchName,
|
|
211
211
|
"{{WORKTREE_PATH}}": config.worktreePath,
|
|
@@ -97,7 +97,7 @@ describe("discoverAgents", () => {
|
|
|
97
97
|
capability: "builder",
|
|
98
98
|
worktreePath: join(tempDir, ".overstory", "worktrees", "builder-test"),
|
|
99
99
|
branchName: "overstory/builder-test/task-123",
|
|
100
|
-
|
|
100
|
+
taskId: "task-123",
|
|
101
101
|
tmuxSession: "overstory-test-builder",
|
|
102
102
|
state: "working",
|
|
103
103
|
pid: 12345,
|
|
@@ -129,7 +129,7 @@ describe("discoverAgents", () => {
|
|
|
129
129
|
capability: "builder",
|
|
130
130
|
worktreePath: join(tempDir, ".overstory", "worktrees", "builder-test"),
|
|
131
131
|
branchName: "overstory/builder-test/task-123",
|
|
132
|
-
|
|
132
|
+
taskId: "task-123",
|
|
133
133
|
tmuxSession: "overstory-test-builder",
|
|
134
134
|
state: "working",
|
|
135
135
|
pid: 12345,
|
|
@@ -148,7 +148,7 @@ describe("discoverAgents", () => {
|
|
|
148
148
|
capability: "scout",
|
|
149
149
|
worktreePath: join(tempDir, ".overstory", "worktrees", "scout-test"),
|
|
150
150
|
branchName: "overstory/scout-test/task-456",
|
|
151
|
-
|
|
151
|
+
taskId: "task-456",
|
|
152
152
|
tmuxSession: "overstory-test-scout",
|
|
153
153
|
state: "working",
|
|
154
154
|
pid: 12346,
|
|
@@ -180,7 +180,7 @@ describe("discoverAgents", () => {
|
|
|
180
180
|
capability: "builder",
|
|
181
181
|
worktreePath: join(tempDir, ".overstory", "worktrees", "builder-working"),
|
|
182
182
|
branchName: "overstory/builder-working/task-123",
|
|
183
|
-
|
|
183
|
+
taskId: "task-123",
|
|
184
184
|
tmuxSession: "overstory-test-working",
|
|
185
185
|
state: "working",
|
|
186
186
|
pid: 12345,
|
|
@@ -199,7 +199,7 @@ describe("discoverAgents", () => {
|
|
|
199
199
|
capability: "builder",
|
|
200
200
|
worktreePath: join(tempDir, ".overstory", "worktrees", "builder-completed"),
|
|
201
201
|
branchName: "overstory/builder-completed/task-456",
|
|
202
|
-
|
|
202
|
+
taskId: "task-456",
|
|
203
203
|
tmuxSession: "overstory-test-completed",
|
|
204
204
|
state: "completed",
|
|
205
205
|
pid: null,
|
|
@@ -300,18 +300,18 @@ logging:
|
|
|
300
300
|
|
|
301
301
|
it("should show help with --help flag", async () => {
|
|
302
302
|
await agentsCommand(["--help"]);
|
|
303
|
-
expect(stdoutBuffer).toContain("
|
|
303
|
+
expect(stdoutBuffer).toContain("agents");
|
|
304
304
|
expect(stdoutBuffer).toContain("discover");
|
|
305
305
|
});
|
|
306
306
|
|
|
307
307
|
it("should show help with no subcommand", async () => {
|
|
308
308
|
await agentsCommand([]);
|
|
309
|
-
expect(stdoutBuffer).toContain("
|
|
309
|
+
expect(stdoutBuffer).toContain("agents");
|
|
310
310
|
expect(stdoutBuffer).toContain("discover");
|
|
311
311
|
});
|
|
312
312
|
|
|
313
313
|
it("should error on unknown subcommand", async () => {
|
|
314
|
-
await expect(agentsCommand(["unknown"])).rejects.toThrow("
|
|
314
|
+
await expect(agentsCommand(["unknown"])).rejects.toThrow("unknown command");
|
|
315
315
|
});
|
|
316
316
|
|
|
317
317
|
afterEach(async () => {
|
package/src/commands/agents.ts
CHANGED
|
@@ -5,26 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
|
+
import { Command } from "commander";
|
|
8
9
|
import { loadConfig } from "../config.ts";
|
|
9
10
|
import { ValidationError } from "../errors.ts";
|
|
10
11
|
import { openSessionStore } from "../sessions/compat.ts";
|
|
11
12
|
import { type AgentSession, SUPPORTED_CAPABILITIES } from "../types.ts";
|
|
12
13
|
|
|
13
|
-
/**
|
|
14
|
-
* Parse a named flag value from args.
|
|
15
|
-
*/
|
|
16
|
-
function getFlag(args: string[], flag: string): string | undefined {
|
|
17
|
-
const idx = args.indexOf(flag);
|
|
18
|
-
if (idx === -1 || idx + 1 >= args.length) {
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
21
|
-
return args[idx + 1];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function hasFlag(args: string[], flag: string): boolean {
|
|
25
|
-
return args.includes(flag);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
14
|
/**
|
|
29
15
|
* Discovered agent information including file scope.
|
|
30
16
|
*/
|
|
@@ -32,7 +18,7 @@ export interface DiscoveredAgent {
|
|
|
32
18
|
agentName: string;
|
|
33
19
|
capability: string;
|
|
34
20
|
state: string;
|
|
35
|
-
|
|
21
|
+
taskId: string;
|
|
36
22
|
branchName: string;
|
|
37
23
|
parentAgent: string | null;
|
|
38
24
|
depth: number;
|
|
@@ -131,7 +117,7 @@ export async function discoverAgents(
|
|
|
131
117
|
agentName: session.agentName,
|
|
132
118
|
capability: session.capability,
|
|
133
119
|
state: session.state,
|
|
134
|
-
|
|
120
|
+
taskId: session.taskId,
|
|
135
121
|
branchName: session.branchName,
|
|
136
122
|
parentAgent: session.parentAgent,
|
|
137
123
|
depth: session.depth,
|
|
@@ -180,7 +166,7 @@ function printAgents(agents: DiscoveredAgent[]): void {
|
|
|
180
166
|
for (const agent of agents) {
|
|
181
167
|
const icon = getStateIcon(agent.state);
|
|
182
168
|
w(` ${icon} ${agent.agentName} [${agent.capability}]\n`);
|
|
183
|
-
w(` State: ${agent.state} | Task: ${agent.
|
|
169
|
+
w(` State: ${agent.state} | Task: ${agent.taskId}\n`);
|
|
184
170
|
w(` Branch: ${agent.branchName}\n`);
|
|
185
171
|
w(` Parent: ${agent.parentAgent ?? "none"} | Depth: ${agent.depth}\n`);
|
|
186
172
|
|
|
@@ -194,94 +180,79 @@ function printAgents(agents: DiscoveredAgent[]): void {
|
|
|
194
180
|
}
|
|
195
181
|
}
|
|
196
182
|
|
|
197
|
-
const DISCOVER_HELP = `overstory agents discover — Find active agents by capability
|
|
198
|
-
|
|
199
|
-
Usage: overstory agents discover [--capability <type>] [--all] [--json]
|
|
200
|
-
|
|
201
|
-
Options:
|
|
202
|
-
--capability <type> Filter by capability (builder, scout, reviewer, lead, merger, coordinator, supervisor)
|
|
203
|
-
--all Include completed and zombie agents (default: active only)
|
|
204
|
-
--json Output as JSON
|
|
205
|
-
--help, -h Show this help`;
|
|
206
|
-
|
|
207
|
-
const AGENTS_HELP = `overstory agents — Discover and query agents
|
|
208
|
-
|
|
209
|
-
Usage: overstory agents <subcommand> [options]
|
|
210
|
-
|
|
211
|
-
Subcommands:
|
|
212
|
-
discover Find active agents by capability
|
|
213
|
-
|
|
214
|
-
Options:
|
|
215
|
-
--json Output as JSON
|
|
216
|
-
--help, -h Show this help
|
|
217
|
-
|
|
218
|
-
Run 'overstory agents <subcommand> --help' for subcommand-specific help.`;
|
|
219
|
-
|
|
220
183
|
/**
|
|
221
|
-
*
|
|
184
|
+
* Create the Commander command for `overstory agents`.
|
|
222
185
|
*/
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
186
|
+
export function createAgentsCommand(): Command {
|
|
187
|
+
const cmd = new Command("agents").description("Discover and query agents");
|
|
188
|
+
|
|
189
|
+
cmd
|
|
190
|
+
.command("discover")
|
|
191
|
+
.description("Find active agents by capability")
|
|
192
|
+
.option(
|
|
193
|
+
"--capability <type>",
|
|
194
|
+
"Filter by capability (builder, scout, reviewer, lead, merger, coordinator, supervisor)",
|
|
195
|
+
)
|
|
196
|
+
.option("--all", "Include completed and zombie agents (default: active only)")
|
|
197
|
+
.option("--json", "Output as JSON")
|
|
198
|
+
.action(async (opts: { capability?: string; all?: boolean; json?: boolean }) => {
|
|
199
|
+
const capability = opts.capability;
|
|
200
|
+
|
|
201
|
+
// Validate capability if provided
|
|
202
|
+
if (capability && !SUPPORTED_CAPABILITIES.includes(capability as never)) {
|
|
203
|
+
throw new ValidationError(
|
|
204
|
+
`Invalid capability: ${capability}. Must be one of: ${SUPPORTED_CAPABILITIES.join(", ")}`,
|
|
205
|
+
{
|
|
206
|
+
field: "capability",
|
|
207
|
+
value: capability,
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
}
|
|
228
211
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
// Validate capability if provided
|
|
234
|
-
if (capability && !SUPPORTED_CAPABILITIES.includes(capability as never)) {
|
|
235
|
-
throw new ValidationError(
|
|
236
|
-
`Invalid capability: ${capability}. Must be one of: ${SUPPORTED_CAPABILITIES.join(", ")}`,
|
|
237
|
-
{
|
|
238
|
-
field: "capability",
|
|
239
|
-
value: capability,
|
|
240
|
-
},
|
|
241
|
-
);
|
|
242
|
-
}
|
|
212
|
+
const cwd = process.cwd();
|
|
213
|
+
const config = await loadConfig(cwd);
|
|
214
|
+
const root = config.project.root;
|
|
243
215
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
216
|
+
const agents = await discoverAgents(root, {
|
|
217
|
+
capability,
|
|
218
|
+
includeAll: opts.all ?? false,
|
|
219
|
+
});
|
|
247
220
|
|
|
248
|
-
|
|
221
|
+
if (opts.json) {
|
|
222
|
+
process.stdout.write(`${JSON.stringify(agents, null, "\t")}\n`);
|
|
223
|
+
} else {
|
|
224
|
+
printAgents(agents);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
249
227
|
|
|
250
|
-
|
|
251
|
-
process.stdout.write(`${JSON.stringify(agents, null, "\t")}\n`);
|
|
252
|
-
} else {
|
|
253
|
-
printAgents(agents);
|
|
254
|
-
}
|
|
228
|
+
return cmd;
|
|
255
229
|
}
|
|
256
230
|
|
|
257
231
|
/**
|
|
258
232
|
* Entry point for `overstory agents <subcommand>`.
|
|
259
233
|
*/
|
|
260
234
|
export async function agentsCommand(args: string[]): Promise<void> {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
235
|
+
const cmd = createAgentsCommand();
|
|
236
|
+
cmd.exitOverride();
|
|
265
237
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (!subcommand) {
|
|
270
|
-
process.stdout.write(`${AGENTS_HELP}\n`);
|
|
238
|
+
if (args.length === 0) {
|
|
239
|
+
process.stdout.write(cmd.helpInformation());
|
|
271
240
|
return;
|
|
272
241
|
}
|
|
273
242
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
243
|
+
try {
|
|
244
|
+
await cmd.parseAsync(args, { from: "user" });
|
|
245
|
+
} catch (err: unknown) {
|
|
246
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
247
|
+
const code = (err as { code: string }).code;
|
|
248
|
+
if (code === "commander.helpDisplayed" || code === "commander.version") {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (code === "commander.unknownCommand") {
|
|
252
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
253
|
+
throw new ValidationError(message, { field: "subcommand" });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
throw err;
|
|
286
257
|
}
|
|
287
258
|
}
|