@os-eco/overstory-cli 0.8.2 → 0.8.4
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 +1 -1
- package/agents/builder.md +2 -2
- package/agents/lead.md +2 -2
- package/agents/merger.md +2 -2
- package/agents/orchestrator.md +1 -1
- package/agents/reviewer.md +2 -2
- package/agents/scout.md +2 -2
- package/agents/supervisor.md +3 -3
- package/package.json +1 -1
- package/src/agents/overlay.test.ts +42 -0
- package/src/agents/overlay.ts +1 -0
- package/src/commands/agents.ts +8 -9
- package/src/commands/coordinator.ts +1 -1
- package/src/commands/costs.ts +7 -25
- package/src/commands/log.ts +2 -1
- package/src/commands/monitor.ts +1 -1
- package/src/commands/sling.test.ts +34 -10
- package/src/commands/sling.ts +51 -35
- package/src/commands/stop.test.ts +52 -4
- package/src/commands/stop.ts +5 -3
- package/src/commands/supervisor.ts +1 -1
- package/src/config.test.ts +63 -0
- package/src/config.ts +40 -5
- package/src/index.ts +2 -2
- package/src/merge/resolver.test.ts +99 -0
- package/src/merge/resolver.ts +31 -0
- package/src/metrics/transcript.test.ts +5 -17
- package/src/metrics/transcript.ts +0 -2
- package/src/runtimes/claude.ts +18 -1
- package/src/runtimes/codex.test.ts +22 -8
- package/src/runtimes/codex.ts +26 -16
- package/src/runtimes/copilot.ts +5 -0
- package/src/runtimes/gemini.ts +5 -0
- package/src/runtimes/pi.ts +5 -0
- package/src/runtimes/registry.test.ts +36 -0
- package/src/runtimes/registry.ts +34 -4
- package/src/runtimes/sapling.ts +5 -0
- package/src/runtimes/types.ts +9 -0
- package/src/types.ts +7 -0
package/src/runtimes/codex.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Implements the AgentRuntime contract for the OpenAI `codex` CLI.
|
|
3
3
|
//
|
|
4
4
|
// Key differences from Claude/Pi adapters:
|
|
5
|
-
// -
|
|
5
|
+
// - Interactive: `codex` (without `exec`) stays alive in tmux for orchestration
|
|
6
6
|
// - Instruction file: AGENTS.md (not .claude/CLAUDE.md)
|
|
7
7
|
// - No hooks: Codex uses OS-level sandbox (Seatbelt/Landlock)
|
|
8
|
-
// -
|
|
8
|
+
// - One-shot calls still use `codex exec` (buildPrintCommand)
|
|
9
9
|
|
|
10
10
|
import { mkdir } from "node:fs/promises";
|
|
11
11
|
import { dirname, join } from "node:path";
|
|
@@ -22,9 +22,9 @@ import type {
|
|
|
22
22
|
/**
|
|
23
23
|
* Codex runtime adapter.
|
|
24
24
|
*
|
|
25
|
-
* Implements AgentRuntime for the OpenAI `codex` CLI. Codex
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* Implements AgentRuntime for the OpenAI `codex` CLI. Tmux-spawned Codex
|
|
26
|
+
* agents run in interactive mode (`codex`) so sessions stay alive and can be
|
|
27
|
+
* nudged via tmux.
|
|
28
28
|
*
|
|
29
29
|
* Security is enforced via Codex's OS-level sandbox (Seatbelt on macOS,
|
|
30
30
|
* Landlock on Linux) rather than hook-based guards. The `--full-auto` flag
|
|
@@ -40,11 +40,17 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
40
40
|
/** Relative path to the instruction file within a worktree. */
|
|
41
41
|
readonly instructionPath = "AGENTS.md";
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Anthropic aliases used by overstory manifests that Codex CLI does not
|
|
45
|
+
* accept as --model values.
|
|
46
|
+
*/
|
|
47
|
+
private static readonly MANIFEST_ALIASES = new Set(["sonnet", "opus", "haiku"]);
|
|
48
|
+
|
|
43
49
|
/**
|
|
44
50
|
* Build the shell command string to spawn a Codex agent in a tmux pane.
|
|
45
51
|
*
|
|
46
|
-
* Uses `codex
|
|
47
|
-
*
|
|
52
|
+
* Uses interactive `codex` with `--full-auto` for workspace-write sandbox +
|
|
53
|
+
* automatic approvals.
|
|
48
54
|
*
|
|
49
55
|
* The prompt directs the agent to read AGENTS.md for its full instructions.
|
|
50
56
|
* If `appendSystemPrompt` or `appendSystemPromptFile` is provided, the
|
|
@@ -56,7 +62,12 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
56
62
|
* @returns Shell command string suitable for tmux new-session -c
|
|
57
63
|
*/
|
|
58
64
|
buildSpawnCommand(opts: SpawnOpts): string {
|
|
59
|
-
|
|
65
|
+
// When model comes from default manifest aliases (sonnet/opus/haiku),
|
|
66
|
+
// omit --model so Codex uses the user's configured default model.
|
|
67
|
+
let cmd = "codex --full-auto";
|
|
68
|
+
if (!CodexRuntime.MANIFEST_ALIASES.has(opts.model)) {
|
|
69
|
+
cmd += ` --model ${opts.model}`;
|
|
70
|
+
}
|
|
60
71
|
|
|
61
72
|
if (opts.appendSystemPromptFile) {
|
|
62
73
|
// Read role definition from file at shell expansion time — avoids tmux
|
|
@@ -128,11 +139,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
128
139
|
}
|
|
129
140
|
|
|
130
141
|
/**
|
|
131
|
-
* Codex
|
|
132
|
-
*
|
|
133
|
-
* Unlike Claude Code and Pi which maintain persistent TUI sessions,
|
|
134
|
-
* `codex exec` starts processing immediately and exits on completion.
|
|
135
|
-
* No TUI readiness detection is needed.
|
|
142
|
+
* Codex interactive startup is treated as ready once a pane exists.
|
|
136
143
|
*
|
|
137
144
|
* @param _paneContent - Captured tmux pane content (unused)
|
|
138
145
|
* @returns Always `{ phase: "ready" }`
|
|
@@ -144,9 +151,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
144
151
|
/**
|
|
145
152
|
* Codex does not require beacon verification/resend.
|
|
146
153
|
*
|
|
147
|
-
*
|
|
148
|
-
* swallows the initial Enter during late initialization. Codex exec is
|
|
149
|
-
* headless — it processes the prompt immediately with no TUI startup delay.
|
|
154
|
+
* Codex accepts startup input reliably once spawned.
|
|
150
155
|
*/
|
|
151
156
|
requiresBeaconVerification(): boolean {
|
|
152
157
|
return false;
|
|
@@ -225,4 +230,9 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
225
230
|
buildEnv(model: ResolvedModel): Record<string, string> {
|
|
226
231
|
return model.env ?? {};
|
|
227
232
|
}
|
|
233
|
+
|
|
234
|
+
/** Codex does not produce transcript files. */
|
|
235
|
+
getTranscriptDir(_projectRoot: string): string | null {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
228
238
|
}
|
package/src/runtimes/copilot.ts
CHANGED
|
@@ -223,4 +223,9 @@ export class CopilotRuntime implements AgentRuntime {
|
|
|
223
223
|
buildEnv(model: ResolvedModel): Record<string, string> {
|
|
224
224
|
return model.env ?? {};
|
|
225
225
|
}
|
|
226
|
+
|
|
227
|
+
/** Copilot does not produce transcript files. */
|
|
228
|
+
getTranscriptDir(_projectRoot: string): string | null {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
226
231
|
}
|
package/src/runtimes/gemini.ts
CHANGED
|
@@ -232,4 +232,9 @@ export class GeminiRuntime implements AgentRuntime {
|
|
|
232
232
|
buildEnv(model: ResolvedModel): Record<string, string> {
|
|
233
233
|
return model.env ?? {};
|
|
234
234
|
}
|
|
235
|
+
|
|
236
|
+
/** Gemini does not produce transcript files. */
|
|
237
|
+
getTranscriptDir(_projectRoot: string): string | null {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
235
240
|
}
|
package/src/runtimes/pi.ts
CHANGED
|
@@ -245,4 +245,9 @@ export class PiRuntime implements AgentRuntime {
|
|
|
245
245
|
buildEnv(model: ResolvedModel): Record<string, string> {
|
|
246
246
|
return model.env ?? {};
|
|
247
247
|
}
|
|
248
|
+
|
|
249
|
+
/** Pi uses RPC — no transcript files. */
|
|
250
|
+
getTranscriptDir(_projectRoot: string): string | null {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
248
253
|
}
|
|
@@ -117,4 +117,40 @@ describe("getRuntime", () => {
|
|
|
117
117
|
expect(runtime).toBeInstanceOf(GeminiRuntime);
|
|
118
118
|
expect(runtime.id).toBe("gemini");
|
|
119
119
|
});
|
|
120
|
+
|
|
121
|
+
describe("capability routing", () => {
|
|
122
|
+
it("resolves capability-specific runtime from config", () => {
|
|
123
|
+
const config = {
|
|
124
|
+
runtime: { default: "claude", capabilities: { builder: "gemini" } },
|
|
125
|
+
} as unknown as OverstoryConfig;
|
|
126
|
+
const runtime = getRuntime(undefined, config, "builder");
|
|
127
|
+
expect(runtime).toBeInstanceOf(GeminiRuntime);
|
|
128
|
+
expect(runtime.id).toBe("gemini");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("falls back to default when capability has no override", () => {
|
|
132
|
+
const config = {
|
|
133
|
+
runtime: { default: "codex", capabilities: { builder: "gemini" } },
|
|
134
|
+
} as unknown as OverstoryConfig;
|
|
135
|
+
const runtime = getRuntime(undefined, config, "scout");
|
|
136
|
+
expect(runtime).toBeInstanceOf(CodexRuntime);
|
|
137
|
+
expect(runtime.id).toBe("codex");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("explicit name overrides capability routing", () => {
|
|
141
|
+
const config = {
|
|
142
|
+
runtime: { default: "claude", capabilities: { builder: "gemini" } },
|
|
143
|
+
} as unknown as OverstoryConfig;
|
|
144
|
+
const runtime = getRuntime("copilot", config, "builder");
|
|
145
|
+
expect(runtime).toBeInstanceOf(CopilotRuntime);
|
|
146
|
+
expect(runtime.id).toBe("copilot");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("works when capabilities is undefined", () => {
|
|
150
|
+
const config = { runtime: { default: "claude" } } as OverstoryConfig;
|
|
151
|
+
const runtime = getRuntime(undefined, config, "coordinator");
|
|
152
|
+
expect(runtime).toBeInstanceOf(ClaudeRuntime);
|
|
153
|
+
expect(runtime.id).toBe("claude");
|
|
154
|
+
});
|
|
155
|
+
});
|
|
120
156
|
});
|
package/src/runtimes/registry.ts
CHANGED
|
@@ -20,24 +20,54 @@ const runtimes = new Map<string, () => AgentRuntime>([
|
|
|
20
20
|
["sapling", () => new SaplingRuntime()],
|
|
21
21
|
]);
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Return all registered runtime adapter instances.
|
|
25
|
+
*
|
|
26
|
+
* Used by callers that need to enumerate all runtimes (e.g. to build a
|
|
27
|
+
* dynamic list of known instruction file paths from each runtime's
|
|
28
|
+
* `instructionPath` property).
|
|
29
|
+
*
|
|
30
|
+
* @returns Array of one fresh instance per registered runtime.
|
|
31
|
+
*/
|
|
32
|
+
export function getAllRuntimes(): AgentRuntime[] {
|
|
33
|
+
return [
|
|
34
|
+
new ClaudeRuntime(),
|
|
35
|
+
new CodexRuntime(),
|
|
36
|
+
new PiRuntime(),
|
|
37
|
+
new CopilotRuntime(),
|
|
38
|
+
new GeminiRuntime(),
|
|
39
|
+
new SaplingRuntime(),
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
23
43
|
/**
|
|
24
44
|
* Resolve a runtime adapter by name.
|
|
25
45
|
*
|
|
26
46
|
* Lookup order:
|
|
27
47
|
* 1. Explicit `name` argument (if provided)
|
|
28
|
-
* 2. `config.runtime.
|
|
29
|
-
* 3. `
|
|
48
|
+
* 2. `config.runtime.capabilities[capability]` (if capability provided)
|
|
49
|
+
* 3. `config.runtime.default` (if config is provided)
|
|
50
|
+
* 4. `"claude"` (hardcoded fallback)
|
|
30
51
|
*
|
|
31
52
|
* Special cases:
|
|
32
53
|
* - Pi runtime receives `config.runtime.pi` for model alias expansion.
|
|
33
54
|
*
|
|
34
55
|
* @param name - Runtime name to resolve (e.g. "claude"). Omit to use config default.
|
|
35
56
|
* @param config - Overstory config for reading the default runtime.
|
|
57
|
+
* @param capability - Agent capability (e.g. "coordinator", "builder") for per-capability routing.
|
|
36
58
|
* @throws {Error} If the resolved runtime name is not registered.
|
|
37
59
|
* @returns A fresh AgentRuntime instance.
|
|
38
60
|
*/
|
|
39
|
-
export function getRuntime(
|
|
40
|
-
|
|
61
|
+
export function getRuntime(
|
|
62
|
+
name?: string,
|
|
63
|
+
config?: OverstoryConfig,
|
|
64
|
+
capability?: string,
|
|
65
|
+
): AgentRuntime {
|
|
66
|
+
const capabilityRuntime =
|
|
67
|
+
capability && config?.runtime?.capabilities
|
|
68
|
+
? config.runtime.capabilities[capability]
|
|
69
|
+
: undefined;
|
|
70
|
+
const runtimeName = name ?? capabilityRuntime ?? config?.runtime?.default ?? "claude";
|
|
41
71
|
|
|
42
72
|
// Pi runtime needs config for model alias expansion.
|
|
43
73
|
if (runtimeName === "pi") {
|
package/src/runtimes/sapling.ts
CHANGED
package/src/runtimes/types.ts
CHANGED
|
@@ -184,6 +184,15 @@ export interface AgentRuntime {
|
|
|
184
184
|
*/
|
|
185
185
|
parseTranscript(path: string): Promise<TranscriptSummary | null>;
|
|
186
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Return the directory containing session transcript files for this runtime,
|
|
189
|
+
* or null if transcript discovery is not supported.
|
|
190
|
+
*
|
|
191
|
+
* @param projectRoot - Absolute path to the project root
|
|
192
|
+
* @returns Absolute path to the transcript directory, or null
|
|
193
|
+
*/
|
|
194
|
+
getTranscriptDir(projectRoot: string): string | null;
|
|
195
|
+
|
|
187
196
|
/**
|
|
188
197
|
* Build runtime-specific environment variables for model/provider routing.
|
|
189
198
|
* Claude Code uses ANTHROPIC_API_KEY; Codex uses OPENAI_API_KEY; Pi passes
|
package/src/types.ts
CHANGED
|
@@ -97,6 +97,11 @@ export interface OverstoryConfig {
|
|
|
97
97
|
runtime?: {
|
|
98
98
|
/** Default runtime adapter name (default: "claude"). */
|
|
99
99
|
default: string;
|
|
100
|
+
/**
|
|
101
|
+
* Per-capability runtime overrides. Maps capability names (e.g. "coordinator", "builder")
|
|
102
|
+
* to runtime adapter names. Lookup chain: explicit --runtime flag > capabilities[cap] > default > "claude".
|
|
103
|
+
*/
|
|
104
|
+
capabilities?: Partial<Record<string, string>>;
|
|
100
105
|
/**
|
|
101
106
|
* Runtime adapter for headless one-shot AI calls (--print mode).
|
|
102
107
|
* Used by merge/resolver.ts and watchdog/triage.ts.
|
|
@@ -343,6 +348,8 @@ export interface OverlayConfig {
|
|
|
343
348
|
trackerName?: string; // "seeds" or "beads"
|
|
344
349
|
/** Quality gate commands for the agent overlay. Falls back to defaults if undefined. */
|
|
345
350
|
qualityGates?: QualityGate[];
|
|
351
|
+
/** Relative path to the instruction file within the worktree (runtime-specific). Defaults to .claude/CLAUDE.md. */
|
|
352
|
+
instructionPath?: string;
|
|
346
353
|
}
|
|
347
354
|
|
|
348
355
|
// === Merge Queue ===
|