@os-eco/overstory-cli 0.8.0 → 0.8.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 +3 -1
- package/package.json +1 -1
- package/src/commands/dashboard.test.ts +86 -0
- package/src/commands/dashboard.ts +8 -4
- package/src/commands/feed.test.ts +8 -0
- package/src/commands/inspect.test.ts +156 -1
- package/src/commands/inspect.ts +19 -4
- package/src/commands/replay.test.ts +8 -0
- package/src/commands/sling.ts +218 -121
- package/src/commands/status.test.ts +77 -0
- package/src/commands/status.ts +6 -3
- package/src/commands/stop.test.ts +134 -0
- package/src/commands/stop.ts +41 -11
- package/src/commands/trace.test.ts +8 -0
- package/src/index.ts +1 -1
- package/src/logging/theme.ts +4 -0
- package/src/runtimes/connections.test.ts +74 -0
- package/src/runtimes/connections.ts +34 -0
- package/src/runtimes/registry.test.ts +1 -1
- package/src/runtimes/registry.ts +2 -0
- package/src/runtimes/sapling.test.ts +1237 -0
- package/src/runtimes/sapling.ts +698 -0
- package/src/runtimes/types.ts +45 -0
- package/src/types.ts +5 -1
- package/src/watchdog/daemon.ts +34 -0
- package/src/watchdog/health.test.ts +102 -0
- package/src/watchdog/health.ts +140 -69
- package/src/worktree/process.test.ts +101 -0
- package/src/worktree/process.ts +111 -0
- package/src/worktree/tmux.ts +5 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless subprocess management for non-tmux agent runtimes.
|
|
3
|
+
*
|
|
4
|
+
* Used by `ov sling` when runtime.headless === true to bypass tmux entirely.
|
|
5
|
+
* Provides spawnHeadlessAgent() for direct Bun.spawn() invocation of
|
|
6
|
+
* headless agent processes (e.g., Sapling running with --json).
|
|
7
|
+
*
|
|
8
|
+
* Note: isProcessAlive() and killProcessTree() for headless process lifecycle
|
|
9
|
+
* management already exist in src/worktree/tmux.ts — not duplicated here.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { AgentError } from "../errors.ts";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Handle to a spawned headless agent subprocess.
|
|
16
|
+
*
|
|
17
|
+
* Provides the PID for session tracking, stdin for sending input to the
|
|
18
|
+
* agent process, and stdout for consuming NDJSON event output.
|
|
19
|
+
*
|
|
20
|
+
* stdout is null when the process was spawned with a stdoutFile redirect
|
|
21
|
+
* (file-redirect mode). In that case, stdout is written directly to the
|
|
22
|
+
* log file and no pipe backpressure can occur.
|
|
23
|
+
*/
|
|
24
|
+
export interface HeadlessProcess {
|
|
25
|
+
/** OS-level process ID. Stored in AgentSession.pid for watchdog monitoring. */
|
|
26
|
+
pid: number;
|
|
27
|
+
/** Writable sink for sending input to the process (e.g., RPC messages). */
|
|
28
|
+
stdin: { write(data: string | Uint8Array): number | Promise<number> };
|
|
29
|
+
/**
|
|
30
|
+
* Readable stream of the process stdout, or null when stdout was redirected
|
|
31
|
+
* to a file via stdoutFile. Consumed via runtime.parseEvents() when piped.
|
|
32
|
+
*/
|
|
33
|
+
stdout: ReadableStream<Uint8Array> | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Options for spawning a headless agent subprocess.
|
|
38
|
+
*
|
|
39
|
+
* When stdoutFile or stderrFile are provided, the corresponding stream is
|
|
40
|
+
* redirected to the given file path instead of a pipe. This eliminates
|
|
41
|
+
* backpressure: the child process can write unlimited output without blocking.
|
|
42
|
+
*
|
|
43
|
+
* Log files are useful for post-mortem inspection and do not need to be
|
|
44
|
+
* consumed by the caller.
|
|
45
|
+
*/
|
|
46
|
+
export interface SpawnHeadlessOptions {
|
|
47
|
+
/** Working directory for the subprocess. */
|
|
48
|
+
cwd: string;
|
|
49
|
+
/** Full environment for the subprocess (no implicit merging with process.env). */
|
|
50
|
+
env: Record<string, string>;
|
|
51
|
+
/**
|
|
52
|
+
* When set, redirect subprocess stdout to this file path instead of a pipe.
|
|
53
|
+
* HeadlessProcess.stdout will be null in this case.
|
|
54
|
+
*/
|
|
55
|
+
stdoutFile?: string;
|
|
56
|
+
/**
|
|
57
|
+
* When set, redirect subprocess stderr to this file path instead of a pipe.
|
|
58
|
+
*/
|
|
59
|
+
stderrFile?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Spawn a headless agent subprocess directly via Bun.spawn().
|
|
64
|
+
*
|
|
65
|
+
* Used by `ov sling` when runtime.headless === true to bypass all tmux
|
|
66
|
+
* session management.
|
|
67
|
+
*
|
|
68
|
+
* **Backpressure prevention:** Pass stdoutFile (and stderrFile) to redirect
|
|
69
|
+
* output to log files instead of pipes. This is the recommended mode for
|
|
70
|
+
* `ov sling` — it prevents the OS pipe buffer (~64 KB) from filling up and
|
|
71
|
+
* blocking the child process when the caller does not actively consume stdout.
|
|
72
|
+
*
|
|
73
|
+
* When no file paths are provided (default/legacy mode), stdout is a pipe and
|
|
74
|
+
* the caller is responsible for consuming it to prevent backpressure.
|
|
75
|
+
*
|
|
76
|
+
* The provided env is used as the full subprocess environment (no implicit
|
|
77
|
+
* merging with process.env — callers should merge explicitly if needed).
|
|
78
|
+
*
|
|
79
|
+
* @param argv - Full argv array from runtime.buildDirectSpawn(); first element is the executable
|
|
80
|
+
* @param opts - Working directory, environment, and optional log file paths
|
|
81
|
+
* @returns HeadlessProcess with pid, stdin, and stdout (null if file-redirected)
|
|
82
|
+
* @throws AgentError if argv is empty
|
|
83
|
+
*/
|
|
84
|
+
export async function spawnHeadlessAgent(
|
|
85
|
+
argv: string[],
|
|
86
|
+
opts: SpawnHeadlessOptions,
|
|
87
|
+
): Promise<HeadlessProcess> {
|
|
88
|
+
const [cmd, ...args] = argv;
|
|
89
|
+
if (!cmd) {
|
|
90
|
+
throw new AgentError("buildDirectSpawn returned empty argv array", {
|
|
91
|
+
agentName: "headless",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const stdoutTarget = opts.stdoutFile ? Bun.file(opts.stdoutFile) : "pipe";
|
|
96
|
+
const stderrTarget = opts.stderrFile ? Bun.file(opts.stderrFile) : "pipe";
|
|
97
|
+
|
|
98
|
+
const proc = Bun.spawn([cmd, ...args], {
|
|
99
|
+
cwd: opts.cwd,
|
|
100
|
+
env: opts.env,
|
|
101
|
+
stdout: stdoutTarget,
|
|
102
|
+
stderr: stderrTarget,
|
|
103
|
+
stdin: "pipe",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
pid: proc.pid,
|
|
108
|
+
stdin: proc.stdin,
|
|
109
|
+
stdout: opts.stdoutFile ? null : (proc.stdout as ReadableStream<Uint8Array>),
|
|
110
|
+
};
|
|
111
|
+
}
|
package/src/worktree/tmux.ts
CHANGED
|
@@ -98,6 +98,11 @@ export async function createSession(
|
|
|
98
98
|
exports.push(`export PATH="${overstoryBinDir}:$PATH"`);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// Clear Claude Code nesting guard so child agents can start.
|
|
102
|
+
// Claude Code >=2.1.66 sets CLAUDECODE=1 and refuses to launch when it's present.
|
|
103
|
+
// Overstory's agent spawning is intentional, not accidental nesting.
|
|
104
|
+
exports.push("unset CLAUDECODE CLAUDE_CODE_SSE_PORT CLAUDE_CODE_ENTRYPOINT");
|
|
105
|
+
|
|
101
106
|
// Add any additional environment variables
|
|
102
107
|
if (env) {
|
|
103
108
|
for (const [key, value] of Object.entries(env)) {
|