@os-eco/overstory-cli 0.9.4 → 0.11.0
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 +50 -19
- package/agents/builder.md +19 -9
- package/agents/coordinator.md +6 -6
- package/agents/lead.md +204 -87
- package/agents/merger.md +25 -14
- package/agents/reviewer.md +22 -16
- package/agents/scout.md +17 -12
- package/package.json +6 -3
- package/src/agents/capabilities.test.ts +85 -0
- package/src/agents/capabilities.ts +125 -0
- package/src/agents/headless-mail-injector.test.ts +448 -0
- package/src/agents/headless-mail-injector.ts +219 -0
- package/src/agents/headless-prompt.test.ts +102 -0
- package/src/agents/headless-prompt.ts +68 -0
- package/src/agents/hooks-deployer.test.ts +514 -14
- package/src/agents/hooks-deployer.ts +141 -0
- package/src/agents/mail-poll-detect.test.ts +153 -0
- package/src/agents/mail-poll-detect.ts +73 -0
- package/src/agents/overlay.test.ts +60 -4
- package/src/agents/overlay.ts +63 -8
- package/src/agents/scope-detect.test.ts +190 -0
- package/src/agents/scope-detect.ts +146 -0
- package/src/agents/turn-lock.test.ts +181 -0
- package/src/agents/turn-lock.ts +235 -0
- package/src/agents/turn-runner-dispatch.test.ts +182 -0
- package/src/agents/turn-runner-dispatch.ts +105 -0
- package/src/agents/turn-runner.test.ts +2312 -0
- package/src/agents/turn-runner.ts +1383 -0
- package/src/commands/agents.ts +9 -0
- package/src/commands/clean.ts +54 -0
- package/src/commands/coordinator.test.ts +254 -0
- package/src/commands/coordinator.ts +273 -8
- package/src/commands/dashboard.test.ts +188 -0
- package/src/commands/dashboard.ts +14 -4
- package/src/commands/doctor.ts +3 -1
- package/src/commands/group.test.ts +94 -0
- package/src/commands/group.ts +49 -20
- package/src/commands/init.test.ts +8 -0
- package/src/commands/init.ts +8 -1
- package/src/commands/log.test.ts +187 -11
- package/src/commands/log.ts +171 -71
- package/src/commands/mail.test.ts +162 -0
- package/src/commands/mail.ts +64 -9
- package/src/commands/merge.test.ts +230 -1
- package/src/commands/merge.ts +68 -12
- package/src/commands/nudge.test.ts +351 -4
- package/src/commands/nudge.ts +356 -34
- package/src/commands/run.test.ts +43 -7
- package/src/commands/serve/build.test.ts +202 -0
- package/src/commands/serve/build.ts +206 -0
- package/src/commands/serve/coordinator-actions.test.ts +339 -0
- package/src/commands/serve/coordinator-actions.ts +408 -0
- package/src/commands/serve/dev.test.ts +168 -0
- package/src/commands/serve/dev.ts +117 -0
- package/src/commands/serve/mail-actions.test.ts +312 -0
- package/src/commands/serve/mail-actions.ts +167 -0
- package/src/commands/serve/rest.test.ts +1323 -0
- package/src/commands/serve/rest.ts +708 -0
- package/src/commands/serve/static.ts +51 -0
- package/src/commands/serve/ws.test.ts +361 -0
- package/src/commands/serve/ws.ts +332 -0
- package/src/commands/serve.test.ts +459 -0
- package/src/commands/serve.ts +565 -0
- package/src/commands/sling.test.ts +177 -1
- package/src/commands/sling.ts +243 -71
- package/src/commands/status.test.ts +9 -0
- package/src/commands/status.ts +12 -4
- package/src/commands/stop.test.ts +255 -1
- package/src/commands/stop.ts +107 -8
- package/src/commands/watch.test.ts +43 -0
- package/src/commands/watch.ts +153 -28
- package/src/config.ts +23 -0
- package/src/doctor/consistency.test.ts +106 -0
- package/src/doctor/consistency.ts +48 -1
- package/src/doctor/serve.test.ts +95 -0
- package/src/doctor/serve.ts +86 -0
- package/src/doctor/types.ts +2 -1
- package/src/doctor/watchdog.ts +57 -1
- package/src/events/tailer.test.ts +234 -1
- package/src/events/tailer.ts +90 -0
- package/src/index.ts +57 -6
- package/src/insights/quality-gates.test.ts +141 -0
- package/src/insights/quality-gates.ts +156 -0
- package/src/json.ts +29 -0
- package/src/logging/theme.ts +4 -0
- package/src/mail/client.ts +15 -2
- package/src/mail/store.test.ts +82 -0
- package/src/mail/store.ts +41 -4
- package/src/merge/lock.test.ts +149 -0
- package/src/merge/lock.ts +140 -0
- package/src/merge/predict.test.ts +387 -0
- package/src/merge/predict.ts +249 -0
- package/src/merge/resolver.ts +1 -1
- package/src/mulch/client.ts +3 -3
- package/src/runtimes/__fixtures__/claude-stream-fixture.ts +22 -0
- package/src/runtimes/claude.test.ts +791 -1
- package/src/runtimes/claude.ts +323 -1
- package/src/runtimes/connections.test.ts +141 -1
- package/src/runtimes/connections.ts +73 -4
- package/src/runtimes/headless-connection.test.ts +264 -0
- package/src/runtimes/headless-connection.ts +158 -0
- package/src/runtimes/types.ts +10 -0
- package/src/schema-consistency.test.ts +1 -0
- package/src/sessions/store.test.ts +657 -29
- package/src/sessions/store.ts +286 -23
- package/src/test-setup.test.ts +31 -0
- package/src/test-setup.ts +28 -0
- package/src/types.ts +107 -2
- package/src/utils/pid.test.ts +85 -1
- package/src/utils/pid.ts +86 -1
- package/src/utils/process-scan.test.ts +53 -0
- package/src/utils/process-scan.ts +76 -0
- package/src/watchdog/daemon.test.ts +1607 -376
- package/src/watchdog/daemon.ts +462 -88
- package/src/watchdog/health.test.ts +282 -0
- package/src/watchdog/health.ts +126 -27
- package/src/worktree/manager.test.ts +218 -1
- package/src/worktree/manager.ts +55 -0
- package/src/worktree/process.test.ts +71 -0
- package/src/worktree/process.ts +25 -5
- package/src/worktree/tmux.test.ts +28 -0
- package/src/worktree/tmux.ts +27 -3
- package/templates/CLAUDE.md.tmpl +19 -8
- package/templates/overlay.md.tmpl +5 -2
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
buildInitialHeadlessPrompt,
|
|
4
|
+
encodeUserTurn,
|
|
5
|
+
formatMailSection,
|
|
6
|
+
} from "./headless-prompt.ts";
|
|
7
|
+
|
|
8
|
+
describe("encodeUserTurn", () => {
|
|
9
|
+
test("produces a valid NDJSON line", () => {
|
|
10
|
+
const line = encodeUserTurn("hello world");
|
|
11
|
+
expect(line).toEndWith("\n");
|
|
12
|
+
const parsed = JSON.parse(line.trim());
|
|
13
|
+
expect(parsed.type).toBe("user");
|
|
14
|
+
expect(parsed.message.role).toBe("user");
|
|
15
|
+
expect(parsed.message.content).toHaveLength(1);
|
|
16
|
+
expect(parsed.message.content[0].type).toBe("text");
|
|
17
|
+
expect(parsed.message.content[0].text).toBe("hello world");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("handles multi-line text", () => {
|
|
21
|
+
const text = "line one\nline two\nline three";
|
|
22
|
+
const line = encodeUserTurn(text);
|
|
23
|
+
const parsed = JSON.parse(line.trim());
|
|
24
|
+
expect(parsed.message.content[0].text).toBe(text);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("formatMailSection", () => {
|
|
29
|
+
test("returns empty string for no messages", () => {
|
|
30
|
+
expect(formatMailSection([])).toBe("");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("formats a single message", () => {
|
|
34
|
+
const result = formatMailSection([
|
|
35
|
+
{ from: "coordinator", subject: "dispatch", priority: "normal", body: "Start working." },
|
|
36
|
+
]);
|
|
37
|
+
expect(result).toContain("[MAIL] From: coordinator");
|
|
38
|
+
expect(result).toContain("Subject: dispatch");
|
|
39
|
+
expect(result).toContain("Start working.");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("separates multiple messages with dividers", () => {
|
|
43
|
+
const result = formatMailSection([
|
|
44
|
+
{ from: "lead", subject: "task-1", priority: "high", body: "First task." },
|
|
45
|
+
{ from: "orchestrator", subject: "context", priority: "low", body: "Extra context." },
|
|
46
|
+
]);
|
|
47
|
+
expect(result).toContain("---");
|
|
48
|
+
expect(result).toContain("First task.");
|
|
49
|
+
expect(result).toContain("Extra context.");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("buildInitialHeadlessPrompt", () => {
|
|
54
|
+
test("combines all three sections", () => {
|
|
55
|
+
const result = buildInitialHeadlessPrompt(
|
|
56
|
+
"## Prime Context\nExpertise here.",
|
|
57
|
+
"[MAIL] From: orchestrator | Subject: dispatch\n\nDo the thing.",
|
|
58
|
+
"Read your overlay and begin immediately.",
|
|
59
|
+
);
|
|
60
|
+
const parsed = JSON.parse(result.trim());
|
|
61
|
+
const text: string = parsed.message.content[0].text;
|
|
62
|
+
expect(text).toContain("Prime Context");
|
|
63
|
+
expect(text).toContain("[MAIL]");
|
|
64
|
+
expect(text).toContain("Read your overlay and begin immediately.");
|
|
65
|
+
expect(text).toContain("---");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("omits primeContext when undefined", () => {
|
|
69
|
+
const result = buildInitialHeadlessPrompt(
|
|
70
|
+
undefined,
|
|
71
|
+
"[MAIL] From: lead | Subject: dispatch\n\nTask body.",
|
|
72
|
+
"Begin.",
|
|
73
|
+
);
|
|
74
|
+
const parsed = JSON.parse(result.trim());
|
|
75
|
+
const text: string = parsed.message.content[0].text;
|
|
76
|
+
expect(text).not.toContain("Prime Context");
|
|
77
|
+
expect(text).toContain("[MAIL]");
|
|
78
|
+
expect(text).toContain("Begin.");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("omits dispatchMail when undefined", () => {
|
|
82
|
+
const result = buildInitialHeadlessPrompt("## Prime Context", undefined, "Begin.");
|
|
83
|
+
const parsed = JSON.parse(result.trim());
|
|
84
|
+
const text: string = parsed.message.content[0].text;
|
|
85
|
+
expect(text).toContain("Prime Context");
|
|
86
|
+
expect(text).not.toContain("[MAIL]");
|
|
87
|
+
expect(text).toContain("Begin.");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("always includes beacon even when other sections are empty", () => {
|
|
91
|
+
const result = buildInitialHeadlessPrompt(undefined, undefined, "Start now.");
|
|
92
|
+
const parsed = JSON.parse(result.trim());
|
|
93
|
+
const text: string = parsed.message.content[0].text;
|
|
94
|
+
expect(text).toBe("Start now.");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("output is valid NDJSON ending with newline", () => {
|
|
98
|
+
const result = buildInitialHeadlessPrompt("ctx", "mail", "go");
|
|
99
|
+
expect(result).toEndWith("\n");
|
|
100
|
+
expect(() => JSON.parse(result.trim())).not.toThrow();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the initial stdin prompt for a headless Claude Code agent.
|
|
3
|
+
*
|
|
4
|
+
* In headless mode (--input-format stream-json), the orchestrator owns stdin.
|
|
5
|
+
* Rather than relying on SessionStart hooks (which don't fire in headless mode),
|
|
6
|
+
* the orchestrator writes the prime context, pending dispatch mail, and activation
|
|
7
|
+
* beacon as the agent's first user turn immediately after spawn.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Encode text as a stream-json user turn for Claude Code's --input-format stream-json.
|
|
12
|
+
*
|
|
13
|
+
* Format matches the Claude Code headless stdin protocol:
|
|
14
|
+
* {"type":"user","message":{"role":"user","content":[{"type":"text","text":"..."}]}}
|
|
15
|
+
*/
|
|
16
|
+
export function encodeUserTurn(text: string): string {
|
|
17
|
+
const message = {
|
|
18
|
+
type: "user",
|
|
19
|
+
message: { role: "user", content: [{ type: "text", text }] },
|
|
20
|
+
};
|
|
21
|
+
return `${JSON.stringify(message)}\n`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Build the initial stdin prompt for a headless Claude agent.
|
|
26
|
+
*
|
|
27
|
+
* Combines prime context (mulch expertise, session state), pending dispatch mail,
|
|
28
|
+
* and the activation beacon into a single user turn. Replaces the SessionStart
|
|
29
|
+
* hook equivalents (ov prime + ov mail check --inject) for headless agents.
|
|
30
|
+
*
|
|
31
|
+
* Sections are separated by "---" dividers. Empty sections are omitted.
|
|
32
|
+
*
|
|
33
|
+
* @param primeContext - Output of `ov prime --agent <name>` (may be empty/undefined)
|
|
34
|
+
* @param dispatchMail - Pre-formatted dispatch mail body (may be empty/undefined)
|
|
35
|
+
* @param beacon - Activation phrase sent via tmux send-keys in interactive mode
|
|
36
|
+
* @returns NDJSON line ready to write to the agent's stdin
|
|
37
|
+
*/
|
|
38
|
+
export function buildInitialHeadlessPrompt(
|
|
39
|
+
primeContext: string | undefined,
|
|
40
|
+
dispatchMail: string | undefined,
|
|
41
|
+
beacon: string,
|
|
42
|
+
): string {
|
|
43
|
+
const parts: string[] = [];
|
|
44
|
+
if (primeContext) parts.push(primeContext);
|
|
45
|
+
if (dispatchMail) parts.push(dispatchMail);
|
|
46
|
+
parts.push(beacon);
|
|
47
|
+
|
|
48
|
+
const text = parts.join("\n\n---\n\n");
|
|
49
|
+
return encodeUserTurn(text);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Format a list of pending mail messages as a dispatch mail section.
|
|
54
|
+
*
|
|
55
|
+
* Used to inline pending inbox messages into the initial stdin prompt so
|
|
56
|
+
* the agent starts with all pre-dispatch mail already in context.
|
|
57
|
+
*/
|
|
58
|
+
export function formatMailSection(
|
|
59
|
+
messages: ReadonlyArray<{ from: string; subject: string; priority: string; body: string }>,
|
|
60
|
+
): string {
|
|
61
|
+
if (messages.length === 0) return "";
|
|
62
|
+
return messages
|
|
63
|
+
.map(
|
|
64
|
+
(m) =>
|
|
65
|
+
`[MAIL] From: ${m.from} | Subject: ${m.subject} | Priority: ${m.priority}\n\n${m.body}`,
|
|
66
|
+
)
|
|
67
|
+
.join("\n\n---\n\n");
|
|
68
|
+
}
|