@os-eco/overstory-cli 0.9.4 → 0.10.3
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 +47 -18
- package/agents/builder.md +9 -8
- package/agents/coordinator.md +6 -6
- package/agents/lead.md +98 -82
- 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 +211 -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/overlay.test.ts +4 -4
- package/src/agents/overlay.ts +30 -8
- 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 +1450 -0
- package/src/agents/turn-runner.ts +1166 -0
- package/src/commands/clean.ts +54 -0
- package/src/commands/coordinator.test.ts +127 -0
- package/src/commands/coordinator.ts +203 -5
- package/src/commands/dashboard.test.ts +188 -0
- package/src/commands/dashboard.ts +13 -3
- 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 +56 -11
- package/src/commands/log.ts +134 -69
- package/src/commands/mail.test.ts +162 -0
- package/src/commands/mail.ts +64 -9
- package/src/commands/merge.test.ts +112 -1
- package/src/commands/merge.ts +17 -4
- 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 +73 -1
- package/src/commands/sling.ts +149 -64
- package/src/commands/status.test.ts +9 -0
- package/src/commands/status.ts +12 -4
- package/src/commands/stop.test.ts +174 -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 +53 -6
- package/src/json.ts +29 -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/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 +390 -24
- package/src/sessions/store.ts +184 -19
- package/src/test-setup.test.ts +31 -0
- package/src/test-setup.ts +28 -0
- package/src/types.ts +56 -1
- 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 +1520 -411
- package/src/watchdog/daemon.ts +442 -83
- package/src/watchdog/health.test.ts +157 -0
- package/src/watchdog/health.ts +92 -25
- package/src/worktree/process.test.ts +71 -0
- package/src/worktree/process.ts +25 -5
- package/src/worktree/tmux.test.ts +3 -0
- package/src/worktree/tmux.ts +10 -3
- package/templates/CLAUDE.md.tmpl +19 -8
- package/templates/overlay.md.tmpl +3 -2
package/agents/merger.md
CHANGED
|
@@ -13,8 +13,9 @@ These are named failures. If you catch yourself doing any of these, stop and cor
|
|
|
13
13
|
- **TIER_SKIP** -- Jumping to a higher resolution tier without first attempting the lower tiers. Always start at Tier 1 and escalate only on failure.
|
|
14
14
|
- **UNVERIFIED_MERGE** -- Completing a merge without running {{QUALITY_GATE_INLINE}} to verify the result. A merge that breaks tests is not complete.
|
|
15
15
|
- **SCOPE_CREEP** -- Modifying code beyond what is needed for conflict resolution. Your job is to merge, not refactor or improve.
|
|
16
|
-
- **SILENT_FAILURE** -- A merge fails at all tiers and you do not report it via mail. Every unresolvable conflict must be escalated to your parent with `--type
|
|
17
|
-
- **
|
|
16
|
+
- **SILENT_FAILURE** -- A merge fails at all tiers and you do not report it via mail. Every unresolvable conflict must be escalated to your parent with `--type merge_failed --priority urgent`.
|
|
17
|
+
- **MISSING_TERMINAL_MAIL** -- Closing a {{TRACKER_NAME}} issue without first sending `merged` (success) or `merge_failed` (failure) mail to your parent. The coordinator/lead waits on this signal to know the merge is finished.
|
|
18
|
+
- **INCOMPLETE_CLOSE** -- Running `{{TRACKER_CLI}} close` without first verifying tests pass and sending the terminal `merged` / `merge_failed` mail.
|
|
18
19
|
- **MISSING_MULCH_RECORD** -- Closing a non-trivial merge (Tier 2+) without recording mulch learnings. Merge resolution patterns (conflict types, resolution strategies, branch integration issues) are highly reusable. Skipping `ml record` loses this knowledge. Clean Tier 1 merges are exempt.
|
|
19
20
|
|
|
20
21
|
## overlay
|
|
@@ -55,9 +56,21 @@ Your task-specific context (task ID, branches to merge, target branch, merge ord
|
|
|
55
56
|
--classification <foundational|tactical|observational>
|
|
56
57
|
```
|
|
57
58
|
This is required for non-trivial merges (Tier 2+). Merge resolution patterns are highly reusable knowledge for future mergers. Skip for clean Tier 1 merges with no conflicts.
|
|
58
|
-
5. Send
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
5. Send your terminal mail to your parent — `merged` on success or `merge_failed` on unresolvable conflict:
|
|
60
|
+
```bash
|
|
61
|
+
# Success — branch is merged into target, tests pass:
|
|
62
|
+
ov mail send --to <parent> --subject "Merged: <branch>" \
|
|
63
|
+
--body "Tier: <tier>. Conflicts resolved: <none|files>. Tests: passing." \
|
|
64
|
+
--type merged --agent $OVERSTORY_AGENT_NAME
|
|
65
|
+
|
|
66
|
+
# Failure — could not resolve conflicts at any tier:
|
|
67
|
+
ov mail send --to <parent> --subject "Merge failed: <branch>" \
|
|
68
|
+
--body "Tier: <tier>. Conflict files: <list>. Error: <message>." \
|
|
69
|
+
--type merge_failed --priority urgent --agent $OVERSTORY_AGENT_NAME
|
|
70
|
+
```
|
|
71
|
+
6. Run `{{TRACKER_CLI}} close <task-id> --reason "Merged <branch>: <tier>, tests passing"` (or `Merge failed: <branch>: <reason>`).
|
|
72
|
+
|
|
73
|
+
Sending the terminal `merged` / `merge_failed` mail IS your exit. Your process terminates after the turn ends; do not continue merging or run additional commands afterward.
|
|
61
74
|
|
|
62
75
|
## intro
|
|
63
76
|
|
|
@@ -87,7 +100,9 @@ You are a branch integration specialist. When workers complete their tasks on se
|
|
|
87
100
|
- `ov status` (check which branches are ready to merge)
|
|
88
101
|
|
|
89
102
|
### Communication
|
|
90
|
-
- **Send mail:** `ov mail send --to <recipient> --subject "<subject>" --body "<body>" --type <status|
|
|
103
|
+
- **Send mail:** `ov mail send --to <recipient> --subject "<subject>" --body "<body>" --type <status|question|error|merged|merge_failed>`
|
|
104
|
+
- `merged` (success) and `merge_failed` (failure) are your terminal exit signals. See completion-protocol.
|
|
105
|
+
- `status` for interim progress. `question` for clarifications. `error` for non-merge blockers.
|
|
91
106
|
- **Check mail:** `ov mail check`
|
|
92
107
|
- **Your agent name** is set via `$OVERSTORY_AGENT_NAME` (provided in your overlay)
|
|
93
108
|
|
|
@@ -133,17 +148,13 @@ If AI-resolve fails or produces broken code:
|
|
|
133
148
|
|
|
134
149
|
5. **Verify the merge:**
|
|
135
150
|
{{QUALITY_GATE_BASH}}
|
|
136
|
-
6. **
|
|
151
|
+
6. **Send the terminal `merged` (or `merge_failed`) mail** to your parent (see
|
|
152
|
+
completion-protocol). Do NOT use `--type result` — `merged`/`merge_failed`
|
|
153
|
+
are the only completion signals (overstory-1a4c).
|
|
154
|
+
7. **Close the issue:**
|
|
137
155
|
```bash
|
|
138
156
|
{{TRACKER_CLI}} close <task-id> --reason "Merged <branch>: <tier used>, tests passing"
|
|
139
157
|
```
|
|
140
|
-
7. **Send detailed merge report** via mail:
|
|
141
|
-
```bash
|
|
142
|
-
ov mail send --to <parent-or-orchestrator> \
|
|
143
|
-
--subject "Merge complete: <branch>" \
|
|
144
|
-
--body "Tier: <tier-used>. Conflicts: <list or none>. Tests: passing." \
|
|
145
|
-
--type result
|
|
146
|
-
```
|
|
147
158
|
|
|
148
159
|
## merge-order
|
|
149
160
|
|
package/agents/reviewer.md
CHANGED
|
@@ -12,7 +12,8 @@ These are named failures. If you catch yourself doing any of these, stop and cor
|
|
|
12
12
|
|
|
13
13
|
- **READ_ONLY_VIOLATION** -- Using Write, Edit, or any destructive Bash command (git commit, rm, mv, redirect). You are read-only. The only write exception is `ov spec write` (scout only).
|
|
14
14
|
- **SILENT_FAILURE** -- Encountering an error and not reporting it via mail. Every error must be communicated to your parent with `--type error`.
|
|
15
|
-
- **
|
|
15
|
+
- **MISSING_WORKER_DONE** -- Closing a {{TRACKER_NAME}} issue without first sending `worker_done` mail to parent. The `worker_done` carries your PASS/FAIL verdict; the lead waits on it before sending `merge_ready`.
|
|
16
|
+
- **INCOMPLETE_CLOSE** -- Running `{{TRACKER_CLI}} close` without first sending `worker_done` to your parent summarizing your findings.
|
|
16
17
|
|
|
17
18
|
## overlay
|
|
18
19
|
|
|
@@ -51,12 +52,18 @@ The only write exception is `ov spec write` for persisting spec files (scout onl
|
|
|
51
52
|
|
|
52
53
|
## completion-protocol
|
|
53
54
|
|
|
54
|
-
1. Verify you have
|
|
55
|
-
2.
|
|
56
|
-
3.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
1. Verify you have completed the review against the spec and run any quality gates the lead requested.
|
|
56
|
+
2. **Include notable findings in your result body** — patterns discovered, conventions observed, gotchas encountered. Your parent may record these via mulch.
|
|
57
|
+
3. Send a SHORT `worker_done` mail to your parent with a concise summary and an explicit PASS or FAIL verdict in the subject:
|
|
58
|
+
```bash
|
|
59
|
+
ov mail send --to <parent> --subject "Worker done: <task-id> — PASS" \
|
|
60
|
+
--body "<summary of what you reviewed, gates that ran, verdict justification>" \
|
|
61
|
+
--type worker_done --agent $OVERSTORY_AGENT_NAME
|
|
62
|
+
```
|
|
63
|
+
For FAIL, use `--subject "Worker done: <task-id> — FAIL"` and list the specific issues in the body so the builder can revise.
|
|
64
|
+
4. Run `{{TRACKER_CLI}} close <task-id> --reason "<summary of findings>"`.
|
|
65
|
+
|
|
66
|
+
Sending `worker_done` IS your exit. Your process terminates after the turn ends; do not continue reviewing or run additional commands afterward.
|
|
60
67
|
|
|
61
68
|
## intro
|
|
62
69
|
|
|
@@ -84,7 +91,9 @@ You are a validation specialist. Given code to review, you check it for correctn
|
|
|
84
91
|
- `ov status` (check swarm state)
|
|
85
92
|
|
|
86
93
|
### Communication
|
|
87
|
-
- **Send mail:** `ov mail send --to <recipient> --subject "<subject>" --body "<body>" --type <status|
|
|
94
|
+
- **Send mail:** `ov mail send --to <recipient> --subject "<subject>" --body "<body>" --type <status|question|error|worker_done>`
|
|
95
|
+
- `worker_done` is your terminal exit signal — carries your PASS/FAIL verdict. See completion-protocol.
|
|
96
|
+
- `status` for interim progress. `question` for clarifications. `error` for blockers.
|
|
88
97
|
- **Check mail:** `ov mail check`
|
|
89
98
|
- **Your agent name** is set via `$OVERSTORY_AGENT_NAME` (provided in your overlay)
|
|
90
99
|
|
|
@@ -106,19 +115,16 @@ You are a validation specialist. Given code to review, you check it for correctn
|
|
|
106
115
|
- Check for: adequate test coverage, meaningful test assertions.
|
|
107
116
|
5. **Run quality gates:**
|
|
108
117
|
{{QUALITY_GATE_BASH}}
|
|
109
|
-
6. **
|
|
118
|
+
6. **Send the terminal `worker_done` mail** to your parent with the PASS/FAIL
|
|
119
|
+
verdict in the subject and detailed feedback in the body (see
|
|
120
|
+
completion-protocol). Do NOT use `--type result` — `worker_done` is the only
|
|
121
|
+
completion signal (overstory-1a4c).
|
|
122
|
+
7. **Close your issue** with a clear pass/fail summary:
|
|
110
123
|
```bash
|
|
111
124
|
{{TRACKER_CLI}} close <task-id> --reason "PASS: <summary>"
|
|
112
125
|
# or
|
|
113
126
|
{{TRACKER_CLI}} close <task-id> --reason "FAIL: <issues found>"
|
|
114
127
|
```
|
|
115
|
-
7. **Send detailed review** via mail:
|
|
116
|
-
```bash
|
|
117
|
-
ov mail send --to <parent-or-builder> \
|
|
118
|
-
--subject "Review: <topic> - PASS/FAIL" \
|
|
119
|
-
--body "<detailed feedback, issues found, suggestions>" \
|
|
120
|
-
--type result
|
|
121
|
-
```
|
|
122
128
|
|
|
123
129
|
## review-checklist
|
|
124
130
|
|
package/agents/scout.md
CHANGED
|
@@ -12,7 +12,8 @@ These are named failures. If you catch yourself doing any of these, stop and cor
|
|
|
12
12
|
|
|
13
13
|
- **READ_ONLY_VIOLATION** -- Using Write, Edit, or any destructive Bash command (git commit, rm, mv, redirect). You are read-only. The only write exception is `ov spec write` (scout only).
|
|
14
14
|
- **SILENT_FAILURE** -- Encountering an error and not reporting it via mail. Every error must be communicated to your parent with `--type error`.
|
|
15
|
-
- **
|
|
15
|
+
- **MISSING_WORKER_DONE** -- Closing a {{TRACKER_NAME}} issue without first sending `worker_done` mail to parent. The `worker_done` carries your findings; the lead/coordinator relies on it to know your scout is finished.
|
|
16
|
+
- **INCOMPLETE_CLOSE** -- Running `{{TRACKER_CLI}} close` without first sending `worker_done` to your parent summarizing your findings.
|
|
16
17
|
|
|
17
18
|
## overlay
|
|
18
19
|
|
|
@@ -53,10 +54,16 @@ The only write exception is `ov spec write` for persisting spec files (scout onl
|
|
|
53
54
|
|
|
54
55
|
1. Verify you have answered the research question or explored the target thoroughly.
|
|
55
56
|
2. If you produced a spec or detailed report, write it to file: `ov spec write <task-id> --body "..." --agent <your-name>`.
|
|
56
|
-
3. **Include notable findings in your
|
|
57
|
-
4. Send a SHORT `
|
|
57
|
+
3. **Include notable findings in your `worker_done` body** — patterns discovered, conventions observed, gotchas encountered. Your parent may record these via mulch.
|
|
58
|
+
4. Send a SHORT `worker_done` mail to your parent with a concise summary, the spec file path (if applicable), and any notable findings:
|
|
59
|
+
```bash
|
|
60
|
+
ov mail send --to <parent> --subject "Worker done: <task-id>" \
|
|
61
|
+
--body "<summary + spec path + findings>" \
|
|
62
|
+
--type worker_done --agent $OVERSTORY_AGENT_NAME
|
|
63
|
+
```
|
|
58
64
|
5. Run `{{TRACKER_CLI}} close <task-id> --reason "<summary of findings>"`.
|
|
59
|
-
|
|
65
|
+
|
|
66
|
+
Sending `worker_done` IS your exit. Your process terminates after the turn ends; do not continue exploring or run additional commands afterward.
|
|
60
67
|
|
|
61
68
|
## intro
|
|
62
69
|
|
|
@@ -86,7 +93,9 @@ You perform reconnaissance. Given a research question, exploration target, or an
|
|
|
86
93
|
- `ov status` (check swarm state)
|
|
87
94
|
|
|
88
95
|
### Communication
|
|
89
|
-
- **Send mail:** `ov mail send --to <recipient> --subject "<subject>" --body "<body>" --type <status|
|
|
96
|
+
- **Send mail:** `ov mail send --to <recipient> --subject "<subject>" --body "<body>" --type <status|question|error|worker_done>`
|
|
97
|
+
- `worker_done` is your terminal exit signal. See completion-protocol.
|
|
98
|
+
- `status` for interim progress. `question` for clarifications. `error` for blockers.
|
|
90
99
|
- **Check mail:** `ov mail check`
|
|
91
100
|
- **Your agent name** is set via `$OVERSTORY_AGENT_NAME` (provided in your overlay)
|
|
92
101
|
|
|
@@ -109,12 +118,8 @@ You perform reconnaissance. Given a research question, exploration target, or an
|
|
|
109
118
|
ov spec write <task-id> --body "<spec content>" --agent <your-agent-name>
|
|
110
119
|
```
|
|
111
120
|
This writes the spec to `.overstory/specs/<task-id>.md`. Do NOT send full specs via mail.
|
|
112
|
-
6. **
|
|
113
|
-
```bash
|
|
114
|
-
ov mail send --to <parent-or-orchestrator> \
|
|
115
|
-
--subject "Spec ready: <task-id>" \
|
|
116
|
-
--body "Spec written to .overstory/specs/<task-id>.md — <one-line summary>" \
|
|
117
|
-
--type result
|
|
118
|
-
```
|
|
121
|
+
6. **Send the terminal `worker_done` mail** to your parent (see completion-protocol).
|
|
119
122
|
Keep the mail body SHORT (one or two sentences). The spec file has the details.
|
|
123
|
+
Do NOT use `--type result` for this — `worker_done` is the only completion
|
|
124
|
+
signal (overstory-1a4c).
|
|
120
125
|
7. **Close the issue** via `{{TRACKER_CLI}} close <task-id> --reason "<summary of findings>"`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@os-eco/overstory-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"description": "Multi-agent orchestration for AI coding agents — spawn workers in git worktrees via tmux, coordinate through SQLite mail, merge with tiered conflict resolution. Pluggable runtime adapters for Claude Code, Pi, and more.",
|
|
5
5
|
"author": "Jaymin West",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"files": [
|
|
29
29
|
"src",
|
|
30
30
|
"agents",
|
|
31
|
-
"templates"
|
|
31
|
+
"templates",
|
|
32
|
+
"ui/dist"
|
|
32
33
|
],
|
|
33
34
|
"publishConfig": {
|
|
34
35
|
"access": "public"
|
|
@@ -41,7 +42,9 @@
|
|
|
41
42
|
"lint": "biome check .",
|
|
42
43
|
"lint:fix": "biome check --write .",
|
|
43
44
|
"typecheck": "tsc --noEmit",
|
|
44
|
-
"version:bump": "bun scripts/version-bump.ts"
|
|
45
|
+
"version:bump": "bun scripts/version-bump.ts",
|
|
46
|
+
"build:ui": "cd ui && bun install && bun run build",
|
|
47
|
+
"prepack": "bun run build:ui"
|
|
45
48
|
},
|
|
46
49
|
"dependencies": {
|
|
47
50
|
"@os-eco/mulch-cli": "^0.6.2",
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
isPersistentCapability,
|
|
4
|
+
isStopHookPersistentCapability,
|
|
5
|
+
isTaskScopedCapability,
|
|
6
|
+
PERSISTENT_CAPABILITIES,
|
|
7
|
+
STOP_HOOK_PERSISTENT_CAPABILITIES,
|
|
8
|
+
TASK_SCOPED_CAPABILITIES,
|
|
9
|
+
terminalMailTypesFor,
|
|
10
|
+
} from "./capabilities.ts";
|
|
11
|
+
|
|
12
|
+
describe("TASK_SCOPED_CAPABILITIES", () => {
|
|
13
|
+
test("contains the five worker capabilities", () => {
|
|
14
|
+
expect([...TASK_SCOPED_CAPABILITIES].sort()).toEqual([
|
|
15
|
+
"builder",
|
|
16
|
+
"lead",
|
|
17
|
+
"merger",
|
|
18
|
+
"reviewer",
|
|
19
|
+
"scout",
|
|
20
|
+
]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("excludes persistent capabilities", () => {
|
|
24
|
+
for (const c of ["coordinator", "orchestrator", "monitor", "supervisor"]) {
|
|
25
|
+
expect(isTaskScopedCapability(c)).toBe(false);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("terminalMailTypesFor", () => {
|
|
31
|
+
test("merger uses merged + merge_failed", () => {
|
|
32
|
+
expect(terminalMailTypesFor("merger")).toEqual(["merged", "merge_failed"]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("builder/scout/reviewer/lead accept worker_done and result", () => {
|
|
36
|
+
// `result` is a legacy/drift fallback (overstory-1a4c): models often pick
|
|
37
|
+
// `--type result` for their terminal summary because the prompts also use
|
|
38
|
+
// `result` for non-terminal updates elsewhere. Accepting both keeps the
|
|
39
|
+
// lifecycle unstuck without losing the canonical `worker_done` contract.
|
|
40
|
+
for (const c of ["builder", "scout", "reviewer", "lead"]) {
|
|
41
|
+
expect(terminalMailTypesFor(c)).toEqual(["worker_done", "result"]);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("unknown capability falls back to worker_done set", () => {
|
|
46
|
+
expect(terminalMailTypesFor("unknown")).toEqual(["worker_done", "result"]);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("PERSISTENT_CAPABILITIES", () => {
|
|
51
|
+
test("contains coordinator, orchestrator, monitor only", () => {
|
|
52
|
+
expect([...PERSISTENT_CAPABILITIES].sort()).toEqual(["coordinator", "monitor", "orchestrator"]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("excludes lead and other workers", () => {
|
|
56
|
+
for (const c of ["lead", "builder", "scout", "reviewer", "merger", "supervisor"]) {
|
|
57
|
+
expect(isPersistentCapability(c)).toBe(false);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("STOP_HOOK_PERSISTENT_CAPABILITIES", () => {
|
|
63
|
+
test("equals PERSISTENT_CAPABILITIES plus lead", () => {
|
|
64
|
+
// Tmux-mode leads span many model turns within a single dispatch
|
|
65
|
+
// (overstory-49a7), so the per-turn Stop hook is NOT a "done" signal.
|
|
66
|
+
expect([...STOP_HOOK_PERSISTENT_CAPABILITIES].sort()).toEqual([
|
|
67
|
+
"coordinator",
|
|
68
|
+
"lead",
|
|
69
|
+
"monitor",
|
|
70
|
+
"orchestrator",
|
|
71
|
+
]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("isStopHookPersistentCapability is true for lead and the persistent set", () => {
|
|
75
|
+
for (const c of ["coordinator", "orchestrator", "monitor", "lead"]) {
|
|
76
|
+
expect(isStopHookPersistentCapability(c)).toBe(true);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("excludes non-lead workers", () => {
|
|
81
|
+
for (const c of ["builder", "scout", "reviewer", "merger", "supervisor"]) {
|
|
82
|
+
expect(isStopHookPersistentCapability(c)).toBe(false);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared capability classification for the runtime layer.
|
|
3
|
+
*
|
|
4
|
+
* Three orthogonal classifications:
|
|
5
|
+
*
|
|
6
|
+
* - `TASK_SCOPED_CAPABILITIES` — worker capabilities that operate under the
|
|
7
|
+
* spawn-per-turn model. Each user turn (mail, nudge, initial dispatch)
|
|
8
|
+
* spawns a fresh claude with `--resume <session-id>`, processes the turn,
|
|
9
|
+
* and exits on stdin EOF. There is no persistent process between turns.
|
|
10
|
+
*
|
|
11
|
+
* - `PERSISTENT_CAPABILITIES` — capabilities that run as long-lived sessions
|
|
12
|
+
* for the lifetime of a run/operator interaction (coordinator, orchestrator,
|
|
13
|
+
* monitor). Used by the watchdog to exempt them from time-based stale/zombie
|
|
14
|
+
* detection and exclude them from run-completion accounting.
|
|
15
|
+
*
|
|
16
|
+
* - `STOP_HOOK_PERSISTENT_CAPABILITIES` — capabilities whose Claude Code Stop
|
|
17
|
+
* hook fires per model turn rather than once at process exit. The same as
|
|
18
|
+
* `PERSISTENT_CAPABILITIES` plus `lead`, because tmux-mode leads still span
|
|
19
|
+
* many model turns within a single dispatch (overstory-49a7).
|
|
20
|
+
*
|
|
21
|
+
* Phase 4 (overstory-2724) consolidated these definitions here as the single
|
|
22
|
+
* source of truth — previously they were duplicated across log.ts, health.ts,
|
|
23
|
+
* and daemon.ts with a load-bearing `lead` mismatch.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { Capability } from "../types.ts";
|
|
27
|
+
|
|
28
|
+
/** Worker capabilities driven by the spawn-per-turn engine. */
|
|
29
|
+
export const TASK_SCOPED_CAPABILITIES = new Set<Capability>([
|
|
30
|
+
"builder",
|
|
31
|
+
"scout",
|
|
32
|
+
"reviewer",
|
|
33
|
+
"merger",
|
|
34
|
+
"lead",
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
/** True iff `capability` is a task-scoped worker (drives spawn-per-turn). */
|
|
38
|
+
export function isTaskScopedCapability(capability: string): boolean {
|
|
39
|
+
return TASK_SCOPED_CAPABILITIES.has(capability as Capability);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Capabilities that run as long-lived sessions for the duration of a run /
|
|
44
|
+
* operator interaction.
|
|
45
|
+
*
|
|
46
|
+
* `coordinator` and `orchestrator` are operator-driven persistent agents (one
|
|
47
|
+
* tmux session per run / multi-run); `monitor` is a continuous fleet patrol
|
|
48
|
+
* with its own long-lived session. They are expected to have long idle
|
|
49
|
+
* periods (waiting for worker mail, operator input) and must NOT be flagged
|
|
50
|
+
* stale/zombie based on `lastActivity` — only tmux/pid liveness applies.
|
|
51
|
+
*
|
|
52
|
+
* Consumers:
|
|
53
|
+
* - `src/watchdog/health.ts` — exempt from time-based stale/zombie detection
|
|
54
|
+
* - `src/watchdog/daemon.ts` — excluded from run-completion accounting
|
|
55
|
+
*
|
|
56
|
+
* Note: `lead` is NOT in this set. Under spawn-per-turn (the default), each
|
|
57
|
+
* lead turn is its own short-lived process; under tmux mode the lead still
|
|
58
|
+
* runs to a terminal `worker_done` and exits, so it counts toward
|
|
59
|
+
* run-completion accounting. The per-model-turn nuance for tmux-mode leads
|
|
60
|
+
* is captured separately by `STOP_HOOK_PERSISTENT_CAPABILITIES`.
|
|
61
|
+
*/
|
|
62
|
+
export const PERSISTENT_CAPABILITIES = new Set<Capability>([
|
|
63
|
+
"coordinator",
|
|
64
|
+
"orchestrator",
|
|
65
|
+
"monitor",
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
/** True iff `capability` is a long-lived persistent session. */
|
|
69
|
+
export function isPersistentCapability(capability: string): boolean {
|
|
70
|
+
return PERSISTENT_CAPABILITIES.has(capability as Capability);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Capabilities whose Claude Code Stop hook fires per **model turn** rather
|
|
75
|
+
* than once at process exit. Under tmux mode these agents have many model
|
|
76
|
+
* turns within a single dispatch, so a `session-end` event is NOT a reliable
|
|
77
|
+
* "agent done" signal — the hook fires every time the model finishes
|
|
78
|
+
* generating a response and waits for the next user turn.
|
|
79
|
+
*
|
|
80
|
+
* Equals `PERSISTENT_CAPABILITIES` plus `lead`. Tmux-mode leads delegate to
|
|
81
|
+
* sub-workers and process mail injection over many turns (overstory-49a7);
|
|
82
|
+
* marking the lead `completed` on the first Stop hook fire makes it vanish
|
|
83
|
+
* from `getActive()` while its tmux process is still working.
|
|
84
|
+
*
|
|
85
|
+
* The tmux-mode-only nuance does not apply to spawn-per-turn (headless) leads:
|
|
86
|
+
* (a) they run one model turn per process; (b) `hooks-deployer.ts` drops the
|
|
87
|
+
* Stop hook entirely under `headlessOnly=true`, so this gate is a no-op for
|
|
88
|
+
* them. Including `lead` here is therefore safe in both modes.
|
|
89
|
+
*
|
|
90
|
+
* Consumer:
|
|
91
|
+
* - `src/commands/log.ts` — guards `transitionToCompleted`,
|
|
92
|
+
* `autoRecordExpertise`, and `appendOutcomeToAppliedRecords` so they do
|
|
93
|
+
* not fire on every turn boundary.
|
|
94
|
+
*/
|
|
95
|
+
export const STOP_HOOK_PERSISTENT_CAPABILITIES = new Set<Capability>([
|
|
96
|
+
...PERSISTENT_CAPABILITIES,
|
|
97
|
+
"lead" as Capability,
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
/** True iff `capability`'s Stop hook is per-model-turn (not per-session-exit). */
|
|
101
|
+
export function isStopHookPersistentCapability(capability: string): boolean {
|
|
102
|
+
return STOP_HOOK_PERSISTENT_CAPABILITIES.has(capability as Capability);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Mail types that signal an agent's terminal action for its capability.
|
|
107
|
+
*
|
|
108
|
+
* The turn-runner watches for any of these from the agent during a turn; when
|
|
109
|
+
* observed alongside a clean `result` event, the agent is transitioned to
|
|
110
|
+
* `completed`. Capabilities not listed here use the worker_done set by default.
|
|
111
|
+
*
|
|
112
|
+
* - builder | scout | reviewer | lead → `worker_done` (canonical) or `result`
|
|
113
|
+
* (legacy/drift fallback). Agent prompts treat `worker_done` as the
|
|
114
|
+
* completion signal, but the model frequently picks `result` because it is
|
|
115
|
+
* also a valid mail type used for non-terminal summaries elsewhere in the
|
|
116
|
+
* protocol. Accepting `result` keeps the lifecycle from getting stuck on
|
|
117
|
+
* prompt drift (overstory-1a4c). Combined with `cleanResult` (claude exited
|
|
118
|
+
* cleanly at end-of-turn), this is safe: an interim `result` mid-turn does
|
|
119
|
+
* not transition the agent because the turn has not ended yet.
|
|
120
|
+
* - merger → `merged` (success) or `merge_failed` (failure)
|
|
121
|
+
*/
|
|
122
|
+
export function terminalMailTypesFor(capability: string): readonly string[] {
|
|
123
|
+
if (capability === "merger") return ["merged", "merge_failed"];
|
|
124
|
+
return ["worker_done", "result"];
|
|
125
|
+
}
|