@pythonluvr/openwar 0.4.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.
Files changed (298) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +297 -0
  3. package/bin/openwar +45 -0
  4. package/dist/adapters/anthropic.d.ts +15 -0
  5. package/dist/adapters/anthropic.d.ts.map +1 -0
  6. package/dist/adapters/anthropic.js +179 -0
  7. package/dist/adapters/anthropic.js.map +1 -0
  8. package/dist/adapters/gemini.d.ts +15 -0
  9. package/dist/adapters/gemini.d.ts.map +1 -0
  10. package/dist/adapters/gemini.js +141 -0
  11. package/dist/adapters/gemini.js.map +1 -0
  12. package/dist/adapters/grok.d.ts +6 -0
  13. package/dist/adapters/grok.d.ts.map +1 -0
  14. package/dist/adapters/grok.js +15 -0
  15. package/dist/adapters/grok.js.map +1 -0
  16. package/dist/adapters/index.d.ts +16 -0
  17. package/dist/adapters/index.d.ts.map +1 -0
  18. package/dist/adapters/index.js +35 -0
  19. package/dist/adapters/index.js.map +1 -0
  20. package/dist/adapters/mock.d.ts +17 -0
  21. package/dist/adapters/mock.d.ts.map +1 -0
  22. package/dist/adapters/mock.js +33 -0
  23. package/dist/adapters/mock.js.map +1 -0
  24. package/dist/adapters/openai-compat.d.ts +6 -0
  25. package/dist/adapters/openai-compat.d.ts.map +1 -0
  26. package/dist/adapters/openai-compat.js +19 -0
  27. package/dist/adapters/openai-compat.js.map +1 -0
  28. package/dist/adapters/openai.d.ts +23 -0
  29. package/dist/adapters/openai.d.ts.map +1 -0
  30. package/dist/adapters/openai.js +176 -0
  31. package/dist/adapters/openai.js.map +1 -0
  32. package/dist/adapters/sse.d.ts +6 -0
  33. package/dist/adapters/sse.d.ts.map +1 -0
  34. package/dist/adapters/sse.js +45 -0
  35. package/dist/adapters/sse.js.map +1 -0
  36. package/dist/adapters/types.d.ts +7 -0
  37. package/dist/adapters/types.d.ts.map +1 -0
  38. package/dist/adapters/types.js +2 -0
  39. package/dist/adapters/types.js.map +1 -0
  40. package/dist/auth/categories.d.ts +13 -0
  41. package/dist/auth/categories.d.ts.map +1 -0
  42. package/dist/auth/categories.js +47 -0
  43. package/dist/auth/categories.js.map +1 -0
  44. package/dist/auth/check.d.ts +31 -0
  45. package/dist/auth/check.d.ts.map +1 -0
  46. package/dist/auth/check.js +59 -0
  47. package/dist/auth/check.js.map +1 -0
  48. package/dist/auth/role-scope.d.ts +18 -0
  49. package/dist/auth/role-scope.d.ts.map +1 -0
  50. package/dist/auth/role-scope.js +62 -0
  51. package/dist/auth/role-scope.js.map +1 -0
  52. package/dist/auth/wildcards.d.ts +5 -0
  53. package/dist/auth/wildcards.d.ts.map +1 -0
  54. package/dist/auth/wildcards.js +36 -0
  55. package/dist/auth/wildcards.js.map +1 -0
  56. package/dist/brief.d.ts +9 -0
  57. package/dist/brief.d.ts.map +1 -0
  58. package/dist/brief.js +514 -0
  59. package/dist/brief.js.map +1 -0
  60. package/dist/cli.d.ts +2 -0
  61. package/dist/cli.d.ts.map +1 -0
  62. package/dist/cli.js +585 -0
  63. package/dist/cli.js.map +1 -0
  64. package/dist/coordinator/cost-tracker.d.ts +14 -0
  65. package/dist/coordinator/cost-tracker.d.ts.map +1 -0
  66. package/dist/coordinator/cost-tracker.js +63 -0
  67. package/dist/coordinator/cost-tracker.js.map +1 -0
  68. package/dist/coordinator/driver.d.ts +37 -0
  69. package/dist/coordinator/driver.d.ts.map +1 -0
  70. package/dist/coordinator/driver.js +644 -0
  71. package/dist/coordinator/driver.js.map +1 -0
  72. package/dist/coordinator/index.d.ts +14 -0
  73. package/dist/coordinator/index.d.ts.map +1 -0
  74. package/dist/coordinator/index.js +8 -0
  75. package/dist/coordinator/index.js.map +1 -0
  76. package/dist/coordinator/plan-parser.d.ts +17 -0
  77. package/dist/coordinator/plan-parser.d.ts.map +1 -0
  78. package/dist/coordinator/plan-parser.js +44 -0
  79. package/dist/coordinator/plan-parser.js.map +1 -0
  80. package/dist/coordinator/result-aggregator.d.ts +21 -0
  81. package/dist/coordinator/result-aggregator.d.ts.map +1 -0
  82. package/dist/coordinator/result-aggregator.js +58 -0
  83. package/dist/coordinator/result-aggregator.js.map +1 -0
  84. package/dist/coordinator/retry-policy.d.ts +7 -0
  85. package/dist/coordinator/retry-policy.d.ts.map +1 -0
  86. package/dist/coordinator/retry-policy.js +17 -0
  87. package/dist/coordinator/retry-policy.js.map +1 -0
  88. package/dist/coordinator/state-machine.d.ts +63 -0
  89. package/dist/coordinator/state-machine.d.ts.map +1 -0
  90. package/dist/coordinator/state-machine.js +242 -0
  91. package/dist/coordinator/state-machine.js.map +1 -0
  92. package/dist/coordinator/types.d.ts +3 -0
  93. package/dist/coordinator/types.d.ts.map +1 -0
  94. package/dist/coordinator/types.js +5 -0
  95. package/dist/coordinator/types.js.map +1 -0
  96. package/dist/detectors/banned-phrases.d.ts +3 -0
  97. package/dist/detectors/banned-phrases.d.ts.map +1 -0
  98. package/dist/detectors/banned-phrases.js +33 -0
  99. package/dist/detectors/banned-phrases.js.map +1 -0
  100. package/dist/detectors/blocker.d.ts +3 -0
  101. package/dist/detectors/blocker.d.ts.map +1 -0
  102. package/dist/detectors/blocker.js +62 -0
  103. package/dist/detectors/blocker.js.map +1 -0
  104. package/dist/detectors/completion.d.ts +3 -0
  105. package/dist/detectors/completion.d.ts.map +1 -0
  106. package/dist/detectors/completion.js +17 -0
  107. package/dist/detectors/completion.js.map +1 -0
  108. package/dist/detectors/confirmation-summary.d.ts +3 -0
  109. package/dist/detectors/confirmation-summary.d.ts.map +1 -0
  110. package/dist/detectors/confirmation-summary.js +76 -0
  111. package/dist/detectors/confirmation-summary.js.map +1 -0
  112. package/dist/detectors/destructive.d.ts +3 -0
  113. package/dist/detectors/destructive.d.ts.map +1 -0
  114. package/dist/detectors/destructive.js +96 -0
  115. package/dist/detectors/destructive.js.map +1 -0
  116. package/dist/detectors/index.d.ts +12 -0
  117. package/dist/detectors/index.d.ts.map +1 -0
  118. package/dist/detectors/index.js +19 -0
  119. package/dist/detectors/index.js.map +1 -0
  120. package/dist/detectors/phase-marker.d.ts +3 -0
  121. package/dist/detectors/phase-marker.d.ts.map +1 -0
  122. package/dist/detectors/phase-marker.js +25 -0
  123. package/dist/detectors/phase-marker.js.map +1 -0
  124. package/dist/framework.d.ts +2 -0
  125. package/dist/framework.d.ts.map +1 -0
  126. package/dist/framework.js +34 -0
  127. package/dist/framework.js.map +1 -0
  128. package/dist/index.d.ts +10 -0
  129. package/dist/index.d.ts.map +1 -0
  130. package/dist/index.js +10 -0
  131. package/dist/index.js.map +1 -0
  132. package/dist/io.d.ts +25 -0
  133. package/dist/io.d.ts.map +1 -0
  134. package/dist/io.js +83 -0
  135. package/dist/io.js.map +1 -0
  136. package/dist/mcp/client.d.ts +22 -0
  137. package/dist/mcp/client.d.ts.map +1 -0
  138. package/dist/mcp/client.js +44 -0
  139. package/dist/mcp/client.js.map +1 -0
  140. package/dist/mcp/index.d.ts +5 -0
  141. package/dist/mcp/index.d.ts.map +1 -0
  142. package/dist/mcp/index.js +6 -0
  143. package/dist/mcp/index.js.map +1 -0
  144. package/dist/mcp/registry.d.ts +16 -0
  145. package/dist/mcp/registry.d.ts.map +1 -0
  146. package/dist/mcp/registry.js +53 -0
  147. package/dist/mcp/registry.js.map +1 -0
  148. package/dist/mcp/transport-stdio.d.ts +26 -0
  149. package/dist/mcp/transport-stdio.d.ts.map +1 -0
  150. package/dist/mcp/transport-stdio.js +138 -0
  151. package/dist/mcp/transport-stdio.js.map +1 -0
  152. package/dist/mcp/types.d.ts +90 -0
  153. package/dist/mcp/types.d.ts.map +1 -0
  154. package/dist/mcp/types.js +23 -0
  155. package/dist/mcp/types.js.map +1 -0
  156. package/dist/orchestration/handoff.d.ts +27 -0
  157. package/dist/orchestration/handoff.d.ts.map +1 -0
  158. package/dist/orchestration/handoff.js +322 -0
  159. package/dist/orchestration/handoff.js.map +1 -0
  160. package/dist/phases/blocker.d.ts +8 -0
  161. package/dist/phases/blocker.d.ts.map +1 -0
  162. package/dist/phases/blocker.js +11 -0
  163. package/dist/phases/blocker.js.map +1 -0
  164. package/dist/phases/completion.d.ts +10 -0
  165. package/dist/phases/completion.d.ts.map +1 -0
  166. package/dist/phases/completion.js +47 -0
  167. package/dist/phases/completion.js.map +1 -0
  168. package/dist/phases/destructive.d.ts +10 -0
  169. package/dist/phases/destructive.d.ts.map +1 -0
  170. package/dist/phases/destructive.js +31 -0
  171. package/dist/phases/destructive.js.map +1 -0
  172. package/dist/phases/execute.d.ts +30 -0
  173. package/dist/phases/execute.d.ts.map +1 -0
  174. package/dist/phases/execute.js +222 -0
  175. package/dist/phases/execute.js.map +1 -0
  176. package/dist/phases/intake.d.ts +16 -0
  177. package/dist/phases/intake.d.ts.map +1 -0
  178. package/dist/phases/intake.js +105 -0
  179. package/dist/phases/intake.js.map +1 -0
  180. package/dist/roles/critic.d.ts +3 -0
  181. package/dist/roles/critic.d.ts.map +1 -0
  182. package/dist/roles/critic.js +35 -0
  183. package/dist/roles/critic.js.map +1 -0
  184. package/dist/roles/executor.d.ts +3 -0
  185. package/dist/roles/executor.d.ts.map +1 -0
  186. package/dist/roles/executor.js +46 -0
  187. package/dist/roles/executor.js.map +1 -0
  188. package/dist/roles/index.d.ts +8 -0
  189. package/dist/roles/index.d.ts.map +1 -0
  190. package/dist/roles/index.js +8 -0
  191. package/dist/roles/index.js.map +1 -0
  192. package/dist/roles/planner.d.ts +3 -0
  193. package/dist/roles/planner.d.ts.map +1 -0
  194. package/dist/roles/planner.js +50 -0
  195. package/dist/roles/planner.js.map +1 -0
  196. package/dist/roles/prompt-overlay.d.ts +9 -0
  197. package/dist/roles/prompt-overlay.d.ts.map +1 -0
  198. package/dist/roles/prompt-overlay.js +25 -0
  199. package/dist/roles/prompt-overlay.js.map +1 -0
  200. package/dist/roles/registry.d.ts +8 -0
  201. package/dist/roles/registry.d.ts.map +1 -0
  202. package/dist/roles/registry.js +45 -0
  203. package/dist/roles/registry.js.map +1 -0
  204. package/dist/roles/reviewer.d.ts +3 -0
  205. package/dist/roles/reviewer.d.ts.map +1 -0
  206. package/dist/roles/reviewer.js +46 -0
  207. package/dist/roles/reviewer.js.map +1 -0
  208. package/dist/roles/types.d.ts +2 -0
  209. package/dist/roles/types.d.ts.map +1 -0
  210. package/dist/roles/types.js +4 -0
  211. package/dist/roles/types.js.map +1 -0
  212. package/dist/runner.d.ts +3 -0
  213. package/dist/runner.d.ts.map +1 -0
  214. package/dist/runner.js +473 -0
  215. package/dist/runner.js.map +1 -0
  216. package/dist/sandbox/host-allowlist.d.ts +13 -0
  217. package/dist/sandbox/host-allowlist.d.ts.map +1 -0
  218. package/dist/sandbox/host-allowlist.js +85 -0
  219. package/dist/sandbox/host-allowlist.js.map +1 -0
  220. package/dist/sandbox/output-cap.d.ts +9 -0
  221. package/dist/sandbox/output-cap.d.ts.map +1 -0
  222. package/dist/sandbox/output-cap.js +66 -0
  223. package/dist/sandbox/output-cap.js.map +1 -0
  224. package/dist/sandbox/timeout.d.ts +7 -0
  225. package/dist/sandbox/timeout.d.ts.map +1 -0
  226. package/dist/sandbox/timeout.js +52 -0
  227. package/dist/sandbox/timeout.js.map +1 -0
  228. package/dist/sandbox/types.d.ts +18 -0
  229. package/dist/sandbox/types.d.ts.map +1 -0
  230. package/dist/sandbox/types.js +27 -0
  231. package/dist/sandbox/types.js.map +1 -0
  232. package/dist/sandbox/workdir.d.ts +9 -0
  233. package/dist/sandbox/workdir.d.ts.map +1 -0
  234. package/dist/sandbox/workdir.js +83 -0
  235. package/dist/sandbox/workdir.js.map +1 -0
  236. package/dist/state/index.d.ts +4 -0
  237. package/dist/state/index.d.ts.map +1 -0
  238. package/dist/state/index.js +4 -0
  239. package/dist/state/index.js.map +1 -0
  240. package/dist/state/paths.d.ts +5 -0
  241. package/dist/state/paths.d.ts.map +1 -0
  242. package/dist/state/paths.js +18 -0
  243. package/dist/state/paths.js.map +1 -0
  244. package/dist/state/persist.d.ts +14 -0
  245. package/dist/state/persist.d.ts.map +1 -0
  246. package/dist/state/persist.js +146 -0
  247. package/dist/state/persist.js.map +1 -0
  248. package/dist/state/transcript.d.ts +9 -0
  249. package/dist/state/transcript.d.ts.map +1 -0
  250. package/dist/state/transcript.js +34 -0
  251. package/dist/state/transcript.js.map +1 -0
  252. package/dist/tools/native/apply_patch.d.ts +18 -0
  253. package/dist/tools/native/apply_patch.d.ts.map +1 -0
  254. package/dist/tools/native/apply_patch.js +245 -0
  255. package/dist/tools/native/apply_patch.js.map +1 -0
  256. package/dist/tools/native/http_fetch.d.ts +6 -0
  257. package/dist/tools/native/http_fetch.d.ts.map +1 -0
  258. package/dist/tools/native/http_fetch.js +168 -0
  259. package/dist/tools/native/http_fetch.js.map +1 -0
  260. package/dist/tools/native/index.d.ts +9 -0
  261. package/dist/tools/native/index.d.ts.map +1 -0
  262. package/dist/tools/native/index.js +22 -0
  263. package/dist/tools/native/index.js.map +1 -0
  264. package/dist/tools/native/list_dir.d.ts +4 -0
  265. package/dist/tools/native/list_dir.d.ts.map +1 -0
  266. package/dist/tools/native/list_dir.js +175 -0
  267. package/dist/tools/native/list_dir.js.map +1 -0
  268. package/dist/tools/native/read_file.d.ts +4 -0
  269. package/dist/tools/native/read_file.d.ts.map +1 -0
  270. package/dist/tools/native/read_file.js +91 -0
  271. package/dist/tools/native/read_file.js.map +1 -0
  272. package/dist/tools/native/shell_exec.d.ts +4 -0
  273. package/dist/tools/native/shell_exec.d.ts.map +1 -0
  274. package/dist/tools/native/shell_exec.js +180 -0
  275. package/dist/tools/native/shell_exec.js.map +1 -0
  276. package/dist/tools/native/write_file.d.ts +4 -0
  277. package/dist/tools/native/write_file.d.ts.map +1 -0
  278. package/dist/tools/native/write_file.js +101 -0
  279. package/dist/tools/native/write_file.js.map +1 -0
  280. package/dist/tools/types.d.ts +48 -0
  281. package/dist/tools/types.d.ts.map +1 -0
  282. package/dist/tools/types.js +10 -0
  283. package/dist/tools/types.js.map +1 -0
  284. package/dist/types.d.ts +385 -0
  285. package/dist/types.d.ts.map +1 -0
  286. package/dist/types.js +9 -0
  287. package/dist/types.js.map +1 -0
  288. package/examples/README.md +73 -0
  289. package/examples/creative-brief.md +34 -0
  290. package/examples/critic-disagreement-brief.md +42 -0
  291. package/examples/engineering-brief.md +35 -0
  292. package/examples/file-editing-brief.md +33 -0
  293. package/examples/mcp-brief.md +34 -0
  294. package/examples/multi-agent-brief.md +43 -0
  295. package/examples/research-brief.md +35 -0
  296. package/openwar.md +248 -0
  297. package/package.json +76 -0
  298. package/templates/brief.md +62 -0
@@ -0,0 +1,644 @@
1
+ // Coordinator driver. The IO-bearing partner to state-machine.ts.
2
+ //
3
+ // Responsibilities:
4
+ // - Resolve role definitions + per-role tool subsets at start
5
+ // - Walk the FSM, invoking the right role per state
6
+ // - Stream adapter output through detectors (banned phrases, blocker)
7
+ // - Parse handoffs and convert outcomes into StepSignals for the FSM
8
+ // - Maintain CostUsage; emit budget_overrun signals when limits are hit
9
+ // - Handle Phase 3 prompts at the executor's tool-call gate
10
+ // - Persist snapshot after every state transition
11
+ // - Handle SIGINT/abort cleanly (kills in-flight tool processes via the
12
+ // existing sandbox timeout/kill paths)
13
+ //
14
+ // Public entrypoint: `runCoordinator(opts)`. Returns RunResult-compatible
15
+ // shape so the main runner can defer to it transparently.
16
+ import { DEFAULT_BUDGETS } from "../types.js";
17
+ import { snapshot as detectorSnapshot } from "../detectors/index.js";
18
+ import { buildSystemPrompt } from "../roles/prompt-overlay.js";
19
+ import { getRole } from "../roles/registry.js";
20
+ import { parseHandoffFromText } from "../orchestration/handoff.js";
21
+ import { parsePlanFromText, scopeWarningsForPlan } from "./plan-parser.js";
22
+ import { step, applyMutations, } from "./state-machine.js";
23
+ import { newCostUsage, estimateTokens, setWallClock, recordToolCall, checkBudgets, } from "./cost-tracker.js";
24
+ import { aggregateResults } from "./result-aggregator.js";
25
+ import { checkAuthorizationWithRole } from "../auth/check.js";
26
+ const MAX_PHASE_3_PROMPTS_PER_SUBTASK = 6;
27
+ export async function runCoordinator(opts) {
28
+ const { brief, framework, adapter, io, signal } = opts;
29
+ const events = [];
30
+ const sessionApproved = [...opts.sessionApproved];
31
+ const startTime = Date.now();
32
+ const cost = newCostUsage();
33
+ let snap = opts.initialSnapshot ?? {
34
+ state: "init",
35
+ plan: null,
36
+ current_subtask_index: -1,
37
+ subtask_states: {},
38
+ active_roles: opts.roleIds,
39
+ budgets: opts.budgets,
40
+ cost: { tokens_used: 0, wall_clock_ms: 0, tool_calls_by_subtask: {} },
41
+ };
42
+ // Per-role conversation history. Each role sees its own thread.
43
+ const roleHistories = {};
44
+ for (const id of opts.roleIds)
45
+ roleHistories[id] = [];
46
+ // Per-sub-task outcomes (for the aggregator).
47
+ const outcomes = new Map();
48
+ const recordEvent = (ev) => {
49
+ events.push(ev);
50
+ };
51
+ const transitionTo = (next, reason, subtaskId) => {
52
+ snap = { ...snap, state: next };
53
+ recordEvent({
54
+ type: "state_enter",
55
+ state: next,
56
+ at: new Date().toISOString(),
57
+ ...(subtaskId ? { subtask_id: subtaskId } : {}),
58
+ });
59
+ void reason;
60
+ persist();
61
+ };
62
+ const persist = () => {
63
+ setWallClock(cost, Date.now() - startTime);
64
+ snap = {
65
+ ...snap,
66
+ cost: {
67
+ tokens_used: cost.tokens_used,
68
+ wall_clock_ms: cost.wall_clock_ms,
69
+ tool_calls_by_subtask: { ...cost.tool_calls_by_subtask },
70
+ },
71
+ };
72
+ opts.onSnapshot(snap, events);
73
+ };
74
+ const advance = (sig) => {
75
+ const result = step(snap, sig);
76
+ snap = applyMutations(snap, result.mutations);
77
+ transitionTo(result.next_state, result.reason);
78
+ };
79
+ const budgetCheck = (subtaskId) => {
80
+ const check = checkBudgets(cost, opts.budgets, subtaskId);
81
+ if (check.exceeded) {
82
+ recordEvent({
83
+ type: "budget_halt",
84
+ metric: check.exceeded,
85
+ used: check.used,
86
+ limit: check.limit,
87
+ at: new Date().toISOString(),
88
+ });
89
+ return check.exceeded;
90
+ }
91
+ return null;
92
+ };
93
+ // Main loop. Bounded by an outer iteration cap so a misbehaving role can't
94
+ // loop forever even if its signals confuse the FSM.
95
+ const ITERATION_CAP = 200;
96
+ for (let iter = 0; iter < ITERATION_CAP; iter++) {
97
+ if (signal?.aborted) {
98
+ transitionTo("escalate", "operator aborted");
99
+ break;
100
+ }
101
+ if (snap.state === "complete" ||
102
+ snap.state === "block" ||
103
+ snap.state === "escalate") {
104
+ break;
105
+ }
106
+ // Budget check before every IO-bearing state.
107
+ if (snap.state === "plan" || snap.state === "execute" || snap.state === "review_step") {
108
+ const sub = currentSubtask(snap);
109
+ const over = budgetCheck(sub?.id ?? null);
110
+ if (over) {
111
+ advance({ kind: "budget_overrun", metric: over });
112
+ continue;
113
+ }
114
+ }
115
+ if (snap.state === "init") {
116
+ advance({ kind: "execute_ok" }); // init transitions unconditionally
117
+ continue;
118
+ }
119
+ if (snap.state === "plan") {
120
+ const sig = await runPlanState({
121
+ brief,
122
+ framework,
123
+ adapter,
124
+ io,
125
+ roleHistories,
126
+ cost,
127
+ events,
128
+ recordEvent,
129
+ signal,
130
+ });
131
+ if (sig.kind === "plan_ready" && sig.kind === "plan_ready") {
132
+ // Capture the plan into the snapshot.
133
+ snap = { ...snap, plan: { subtasks: sig._subtasks } };
134
+ }
135
+ advance({ kind: sig.kind, ...("reason" in sig ? { reason: sig.reason } : {}) });
136
+ continue;
137
+ }
138
+ if (snap.state === "dispatch") {
139
+ advance({ kind: "execute_ok" }); // dispatch transitions unconditionally to execute
140
+ continue;
141
+ }
142
+ if (snap.state === "execute") {
143
+ const sub = currentSubtask(snap);
144
+ if (!sub) {
145
+ advance({ kind: "fatal", reason: "no current sub-task at execute" });
146
+ continue;
147
+ }
148
+ io.banner(`executor [${snap.current_subtask_index + 1}/${snap.plan.subtasks.length}] ${sub.title}`);
149
+ const sig = await runExecuteState({
150
+ brief,
151
+ framework,
152
+ adapter,
153
+ io,
154
+ subtask: sub,
155
+ priorReview: outcomes.get(sub.id)?.review,
156
+ roleHistories,
157
+ toolDefinitions: opts.toolDefinitions,
158
+ toolExecutors: opts.toolExecutors,
159
+ sandbox: opts.sandbox,
160
+ sessionApproved,
161
+ cost,
162
+ events,
163
+ recordEvent,
164
+ onMessage: opts.onMessage,
165
+ onApproval: opts.onApproval,
166
+ budgets: opts.budgets,
167
+ signal,
168
+ });
169
+ if (sig.handoff) {
170
+ outcomes.set(sub.id, {
171
+ id: sub.id,
172
+ title: sub.title,
173
+ execution: sig.handoff,
174
+ ...(outcomes.get(sub.id) ?? {}),
175
+ });
176
+ // Re-assign because the spread above duplicated; force-update.
177
+ outcomes.set(sub.id, { id: sub.id, title: sub.title, execution: sig.handoff });
178
+ }
179
+ advance(sig.signal);
180
+ continue;
181
+ }
182
+ if (snap.state === "review_step") {
183
+ const sub = currentSubtask(snap);
184
+ if (!sub) {
185
+ advance({ kind: "fatal", reason: "no current sub-task at review" });
186
+ continue;
187
+ }
188
+ const exec = outcomes.get(sub.id)?.execution;
189
+ if (!exec) {
190
+ advance({ kind: "fatal", reason: "no execution handoff at review" });
191
+ continue;
192
+ }
193
+ io.banner(`reviewer [${snap.current_subtask_index + 1}/${snap.plan.subtasks.length}] ${sub.title}`);
194
+ const reviewSig = await runReviewState({
195
+ brief,
196
+ framework,
197
+ adapter,
198
+ io,
199
+ subtask: sub,
200
+ executionHandoff: exec,
201
+ roleHistories,
202
+ cost,
203
+ events,
204
+ recordEvent,
205
+ signal,
206
+ includeCritic: opts.roleIds.includes("critic"),
207
+ });
208
+ if (reviewSig.review) {
209
+ const prior = outcomes.get(sub.id);
210
+ outcomes.set(sub.id, {
211
+ id: sub.id,
212
+ title: sub.title,
213
+ ...(prior ?? {}),
214
+ review: reviewSig.review,
215
+ });
216
+ }
217
+ advance(reviewSig.signal);
218
+ continue;
219
+ }
220
+ if (snap.state === "retry") {
221
+ // FSM-internal transition; no IO. Just step.
222
+ advance({ kind: "execute_ok" });
223
+ continue;
224
+ }
225
+ if (snap.state === "next_subtask") {
226
+ advance({ kind: "execute_ok" });
227
+ continue;
228
+ }
229
+ advance({ kind: "fatal", reason: `unknown state ${snap.state}` });
230
+ }
231
+ // Phase 4 / final report.
232
+ if (snap.state === "complete" && snap.plan) {
233
+ const report = aggregateResults({
234
+ plan: { kind: "plan", subtasks: snap.plan.subtasks, rationale: "" },
235
+ outcomes: [...outcomes.values()],
236
+ });
237
+ io.banner("Phase 4: Completion");
238
+ io.write(report.text + "\n");
239
+ opts.onMessage({
240
+ role: "assistant",
241
+ content: report.text,
242
+ at: new Date().toISOString(),
243
+ meta: { phase: "completion", step_index: 0, orch_role: null },
244
+ });
245
+ }
246
+ return {
247
+ session_id: opts.sessionId,
248
+ final_phase: snap.state === "complete" ? "done" : "blocker",
249
+ completed: snap.state === "complete",
250
+ halted: snap.state !== "complete",
251
+ halt_reason: snap.state === "complete" ? undefined : snap.state,
252
+ final_state: snap.state,
253
+ snapshot: snap,
254
+ cost,
255
+ events,
256
+ messages: [], // The runner owns the full session message list.
257
+ };
258
+ }
259
+ // ---------- State helpers ----------
260
+ function currentSubtask(snap) {
261
+ if (!snap.plan)
262
+ return null;
263
+ return snap.plan.subtasks[snap.current_subtask_index] ?? null;
264
+ }
265
+ async function runPlanState(args) {
266
+ const role = getRole("planner");
267
+ if (!role) {
268
+ return { kind: "plan_invalid", _subtasks: [], reason: "planner role missing from registry" };
269
+ }
270
+ args.recordEvent({ type: "role_invoked", role: role.id, at: new Date().toISOString() });
271
+ args.io.banner("planner");
272
+ const system = buildSystemPrompt({
273
+ framework: args.framework,
274
+ brief: args.brief,
275
+ role,
276
+ extra: "You are now in Phase 1 of the orchestration. Produce the plan handoff.",
277
+ });
278
+ const userTurn = {
279
+ role: "user",
280
+ content: "Decompose this brief into linear sub-tasks. End with the fenced JSON plan handoff.",
281
+ at: new Date().toISOString(),
282
+ meta: { phase: "execute", orch_role: "planner", step_index: 0 },
283
+ };
284
+ const history = [...(args.roleHistories["planner"] ?? []), userTurn];
285
+ const { text } = await streamAndCollect(args.adapter, system, history, args.io, args.signal);
286
+ args.cost.tokens_used += estimateTokens(system) + estimateTokens(userTurn.content) + estimateTokens(text);
287
+ const assistant = {
288
+ role: "assistant",
289
+ content: text,
290
+ at: new Date().toISOString(),
291
+ meta: { phase: "execute", orch_role: "planner", step_index: 1 },
292
+ };
293
+ args.roleHistories["planner"] = [...history, assistant];
294
+ const parsed = parsePlanFromText(text);
295
+ if (!parsed.ok) {
296
+ args.io.warn(`planner output invalid (${parsed.reason}): ${parsed.message}`);
297
+ return { kind: "plan_invalid", _subtasks: [], reason: parsed.message };
298
+ }
299
+ // Scope warnings (non-blocking).
300
+ const warns = scopeWarningsForPlan(parsed.plan, args.brief);
301
+ for (const w of warns) {
302
+ args.io.warn(`plan scope warning: ${w.subtask_id} mentions ${w.category} which is not in authorized_costs`);
303
+ }
304
+ return { kind: "plan_ready", _subtasks: parsed.plan.subtasks };
305
+ }
306
+ async function runExecuteState(args) {
307
+ const role = getRole("executor");
308
+ if (!role) {
309
+ return { signal: { kind: "fatal", reason: "executor role missing" } };
310
+ }
311
+ args.recordEvent({
312
+ type: "role_invoked",
313
+ role: role.id,
314
+ subtask_id: args.subtask.id,
315
+ at: new Date().toISOString(),
316
+ });
317
+ const subBrief = subtaskAsSubBrief(args.subtask, args.priorReview);
318
+ const system = buildSystemPrompt({
319
+ framework: args.framework,
320
+ brief: args.brief,
321
+ role,
322
+ extra: subBrief,
323
+ });
324
+ const userTurn = {
325
+ role: "user",
326
+ content: subBrief,
327
+ at: new Date().toISOString(),
328
+ meta: { phase: "execute", orch_role: "executor", subtask_id: args.subtask.id, step_index: 0 },
329
+ };
330
+ args.onMessage(userTurn);
331
+ const history = [...(args.roleHistories["executor"] ?? []), userTurn];
332
+ // Inner tool-call loop, capped per the brief's budget.
333
+ let assembledText = "";
334
+ let toolCallsThisStep = 0;
335
+ const maxToolCalls = args.budgets.max_tool_calls_per_subtask;
336
+ const recordedCalls = [];
337
+ for (let round = 0; round < maxToolCalls + 1; round++) {
338
+ const collected = await streamAndCollectWithTools(args.adapter, system, history, args.io, args.toolDefinitions, args.signal);
339
+ assembledText = collected.text;
340
+ args.cost.tokens_used += estimateTokens(collected.text);
341
+ if (collected.toolCalls.length === 0)
342
+ break;
343
+ const assistant = {
344
+ role: "assistant",
345
+ content: collected.text +
346
+ (collected.text ? "\n" : "") +
347
+ collected.toolCalls
348
+ .map((c) => `[tool: ${c.name}(${JSON.stringify(c.arguments)})]`)
349
+ .join("\n"),
350
+ at: new Date().toISOString(),
351
+ meta: { phase: "execute", orch_role: "executor", subtask_id: args.subtask.id, step_index: round + 1 },
352
+ };
353
+ history.push(assistant);
354
+ args.onMessage(assistant);
355
+ for (const call of collected.toolCalls) {
356
+ toolCallsThisStep++;
357
+ if (toolCallsThisStep > maxToolCalls) {
358
+ return {
359
+ signal: { kind: "execute_blocked", reason: `tool-call budget exceeded for ${args.subtask.id}` },
360
+ };
361
+ }
362
+ const toolDef = args.toolDefinitions.find((t) => t.name === call.name);
363
+ if (!toolDef) {
364
+ history.push({
365
+ role: "user",
366
+ content: `[tool_result ${call.id} (error)]\nTool "${call.name}" is not registered.`,
367
+ at: new Date().toISOString(),
368
+ meta: { phase: "execute", orch_role: "executor", subtask_id: args.subtask.id },
369
+ });
370
+ continue;
371
+ }
372
+ // Two-tier auth: role scope first, then brief auth.
373
+ const decision = checkAuthorizationWithRole({
374
+ tool: toolDef,
375
+ role,
376
+ authorizedCosts: args.brief.frontmatter.authorized_costs,
377
+ sessionApproved: args.sessionApproved,
378
+ });
379
+ if (decision.kind === "role_scope_violation") {
380
+ return {
381
+ signal: {
382
+ kind: "fatal",
383
+ reason: `executor attempted ${call.name} outside its role scope (missing: ${decision.missing_categories.join(", ")})`,
384
+ },
385
+ };
386
+ }
387
+ if (decision.kind === "needs_operator") {
388
+ const approved = await args.io.confirm(`Phase 3: executor wants to call ${call.name}, requires categories: ${decision.decision.missing_categories.join(", ")}. Approve?`);
389
+ if (!approved) {
390
+ args.onApproval({ action: call.name, approved: false });
391
+ history.push({
392
+ role: "user",
393
+ content: `[tool_result ${call.id} (error)]\nOperator denied this tool call.`,
394
+ at: new Date().toISOString(),
395
+ meta: { phase: "execute", orch_role: "executor", subtask_id: args.subtask.id },
396
+ });
397
+ continue;
398
+ }
399
+ // Promote categories session-wide.
400
+ for (const c of decision.decision.missing_categories) {
401
+ if (!args.sessionApproved.includes(c))
402
+ args.sessionApproved.push(c);
403
+ }
404
+ args.onApproval({
405
+ action: call.name,
406
+ approved: true,
407
+ session_categories: [...decision.decision.missing_categories],
408
+ });
409
+ }
410
+ // Dispatch.
411
+ const executor = args.toolExecutors.get(call.name);
412
+ if (!executor) {
413
+ history.push({
414
+ role: "user",
415
+ content: `[tool_result ${call.id} (error)]\nTool "${call.name}" has no executor registered.`,
416
+ at: new Date().toISOString(),
417
+ meta: { phase: "execute", orch_role: "executor", subtask_id: args.subtask.id },
418
+ });
419
+ continue;
420
+ }
421
+ const result = await executor(call, args.sandbox);
422
+ recordToolCall(args.cost, args.subtask.id);
423
+ recordedCalls.push({
424
+ call_id: call.id,
425
+ name: call.name,
426
+ arguments: call.arguments,
427
+ at: new Date().toISOString(),
428
+ authorized: true,
429
+ result: { success: result.success, content: result.content },
430
+ });
431
+ history.push({
432
+ role: "user",
433
+ content: `[tool_result ${call.id}${result.success ? "" : " (error)"}]\n${result.content}`,
434
+ at: new Date().toISOString(),
435
+ meta: { phase: "execute", orch_role: "executor", subtask_id: args.subtask.id },
436
+ });
437
+ }
438
+ }
439
+ // Final assistant turn (text without trailing tool calls).
440
+ const finalAssistant = {
441
+ role: "assistant",
442
+ content: assembledText,
443
+ at: new Date().toISOString(),
444
+ meta: { phase: "execute", orch_role: "executor", subtask_id: args.subtask.id },
445
+ };
446
+ history.push(finalAssistant);
447
+ args.onMessage(finalAssistant);
448
+ args.roleHistories["executor"] = history;
449
+ // Parse handoff.
450
+ const parsed = parseHandoffFromText(assembledText);
451
+ if (parsed.ok && parsed.handoff.kind === "execution") {
452
+ // Merge in recorded tool calls (executor may have summarized; we trust the runtime record).
453
+ const handoff = {
454
+ ...parsed.handoff,
455
+ subtask_id: args.subtask.id,
456
+ tool_calls: parsed.handoff.tool_calls.length > 0 ? parsed.handoff.tool_calls : recordedCalls,
457
+ };
458
+ // Detector pass for blocker.
459
+ const det = detectorSnapshot(assembledText, {
460
+ authorized_costs: args.brief.frontmatter.authorized_costs,
461
+ });
462
+ if (det.blocker?.blocked) {
463
+ return {
464
+ signal: { kind: "execute_blocked", reason: det.blocker.reason ?? "blocker" },
465
+ handoff,
466
+ };
467
+ }
468
+ return { signal: { kind: "execute_ok" }, handoff };
469
+ }
470
+ if (parsed.ok && parsed.handoff.kind === "escalation") {
471
+ return {
472
+ signal: { kind: "execute_blocked", reason: parsed.handoff.reason },
473
+ };
474
+ }
475
+ return {
476
+ signal: { kind: "execute_blocked", reason: parsed.ok ? "wrong handoff kind" : parsed.message },
477
+ };
478
+ }
479
+ function subtaskAsSubBrief(st, priorReview) {
480
+ const lines = [];
481
+ lines.push(`# Sub-task ${st.id}: ${st.title}`);
482
+ lines.push("");
483
+ lines.push(`Instruction:\n${st.instruction}`);
484
+ lines.push("");
485
+ lines.push(`Acceptance criteria:`);
486
+ for (const c of st.acceptance_criteria)
487
+ lines.push(`- ${c}`);
488
+ if (priorReview && priorReview.verdict === "needs_retry") {
489
+ lines.push("");
490
+ lines.push(`---`);
491
+ lines.push(`The reviewer asked for a retry. Address this revision:`);
492
+ lines.push(priorReview.suggested_revision ?? priorReview.rationale);
493
+ }
494
+ lines.push("");
495
+ lines.push("Phase 0 of OpenWar applies recursively to this sub-task: confirm you have understood the instruction and acceptance criteria in your own words at the top of your reply, then execute. End with the fenced JSON ExecutionHandoff.");
496
+ return lines.join("\n");
497
+ }
498
+ async function runReviewState(args) {
499
+ const reviewer = getRole("reviewer");
500
+ if (!reviewer) {
501
+ return { signal: { kind: "fatal", reason: "reviewer role missing" } };
502
+ }
503
+ const reviewerResult = await invokeReviewer(reviewer, args);
504
+ if (!reviewerResult.review) {
505
+ return { signal: { kind: "fatal", reason: "reviewer produced no valid handoff" } };
506
+ }
507
+ if (!args.includeCritic) {
508
+ return { signal: verdictToSignal(reviewerResult.review.verdict), review: reviewerResult.review };
509
+ }
510
+ const critic = getRole("critic");
511
+ if (!critic) {
512
+ // No critic registered; fall back to reviewer alone.
513
+ return { signal: verdictToSignal(reviewerResult.review.verdict), review: reviewerResult.review };
514
+ }
515
+ const criticResult = await invokeReviewer(critic, args);
516
+ if (!criticResult.review) {
517
+ return { signal: verdictToSignal(reviewerResult.review.verdict), review: reviewerResult.review };
518
+ }
519
+ if (reviewerResult.review.verdict !== criticResult.review.verdict) {
520
+ args.io.warn(`reviewer/critic disagreement on ${args.subtask.id}: reviewer=${reviewerResult.review.verdict} critic=${criticResult.review.verdict}`);
521
+ return { signal: { kind: "review_disagreement" }, review: reviewerResult.review };
522
+ }
523
+ return { signal: verdictToSignal(reviewerResult.review.verdict), review: reviewerResult.review };
524
+ }
525
+ async function invokeReviewer(role, args) {
526
+ args.recordEvent({
527
+ type: "role_invoked",
528
+ role: role.id,
529
+ subtask_id: args.subtask.id,
530
+ at: new Date().toISOString(),
531
+ });
532
+ const system = buildSystemPrompt({
533
+ framework: args.framework,
534
+ brief: args.brief,
535
+ role,
536
+ extra: `# Sub-task being reviewed: ${args.subtask.id}\n\n` +
537
+ `Title: ${args.subtask.title}\n\n` +
538
+ `Acceptance criteria:\n${args.subtask.acceptance_criteria.map((c) => `- ${c}`).join("\n")}`,
539
+ });
540
+ const userTurn = {
541
+ role: "user",
542
+ content: "Evaluate the executor's ExecutionHandoff below against the acceptance criteria above. End your reply with the fenced JSON ReviewHandoff.\n\n```json\n" +
543
+ JSON.stringify(args.executionHandoff, null, 2) +
544
+ "\n```",
545
+ at: new Date().toISOString(),
546
+ meta: { phase: "execute", orch_role: role.id, subtask_id: args.subtask.id },
547
+ };
548
+ const history = [...(args.roleHistories[role.id] ?? []), userTurn];
549
+ const { text } = await streamAndCollect(args.adapter, system, history, args.io, args.signal);
550
+ args.cost.tokens_used += estimateTokens(text);
551
+ const assistant = {
552
+ role: "assistant",
553
+ content: text,
554
+ at: new Date().toISOString(),
555
+ meta: { phase: "execute", orch_role: role.id, subtask_id: args.subtask.id },
556
+ };
557
+ args.roleHistories[role.id] = [...history, assistant];
558
+ const parsed = parseHandoffFromText(text);
559
+ if (!parsed.ok)
560
+ return {};
561
+ if (parsed.handoff.kind !== "review")
562
+ return {};
563
+ return { review: parsed.handoff };
564
+ }
565
+ function verdictToSignal(verdict) {
566
+ if (verdict === "pass")
567
+ return { kind: "review_pass" };
568
+ if (verdict === "needs_retry")
569
+ return { kind: "review_needs_retry" };
570
+ return { kind: "review_fail" };
571
+ }
572
+ async function streamAndCollect(adapter, system, messages, io, signal) {
573
+ let assembled = "";
574
+ for await (const ev of adapter.sendMessage({
575
+ system,
576
+ messages,
577
+ ...(signal ? { signal } : {}),
578
+ })) {
579
+ if (ev.type === "text_delta") {
580
+ io.write(ev.delta);
581
+ assembled += ev.delta;
582
+ }
583
+ else if (ev.type === "done") {
584
+ io.write("\n");
585
+ if (ev.message && ev.message.length >= assembled.length)
586
+ assembled = ev.message;
587
+ return { text: assembled };
588
+ }
589
+ else if (ev.type === "error") {
590
+ throw ev.error;
591
+ }
592
+ }
593
+ return { text: assembled };
594
+ }
595
+ async function streamAndCollectWithTools(adapter, system, messages, io, tools, signal) {
596
+ let assembled = "";
597
+ const toolCalls = [];
598
+ const opts = { system, messages };
599
+ if (tools && tools.length > 0)
600
+ opts.tools = tools;
601
+ if (signal)
602
+ opts.signal = signal;
603
+ for await (const ev of adapter.sendMessage(opts)) {
604
+ if (ev.type === "text_delta") {
605
+ io.write(ev.delta);
606
+ assembled += ev.delta;
607
+ }
608
+ else if (ev.type === "tool_call_complete") {
609
+ toolCalls.push(ev.call);
610
+ }
611
+ else if (ev.type === "done") {
612
+ io.write("\n");
613
+ if (ev.message && ev.message.length >= assembled.length)
614
+ assembled = ev.message;
615
+ if (ev.tool_calls && ev.tool_calls.length > 0) {
616
+ for (const c of ev.tool_calls) {
617
+ if (!toolCalls.find((t) => t.id === c.id))
618
+ toolCalls.push(c);
619
+ }
620
+ }
621
+ return { text: assembled, toolCalls };
622
+ }
623
+ else if (ev.type === "error") {
624
+ throw ev.error;
625
+ }
626
+ }
627
+ return { text: assembled, toolCalls };
628
+ }
629
+ // Helper to extract SubTaskState defaults used elsewhere.
630
+ export function newSubtaskState(id) {
631
+ return { id, status: "pending", attempts: 0 };
632
+ }
633
+ // Compute initial Budgets from the brief.
634
+ export function resolveBudgets(brief) {
635
+ const fmBudgets = brief.frontmatter.budgets ?? {};
636
+ return {
637
+ max_tokens: fmBudgets.max_tokens ?? DEFAULT_BUDGETS.max_tokens,
638
+ max_wall_clock_minutes: fmBudgets.max_wall_clock_minutes ?? DEFAULT_BUDGETS.max_wall_clock_minutes,
639
+ max_tool_calls_per_subtask: fmBudgets.max_tool_calls_per_subtask ?? DEFAULT_BUDGETS.max_tool_calls_per_subtask,
640
+ max_retries_per_subtask: fmBudgets.max_retries_per_subtask ?? DEFAULT_BUDGETS.max_retries_per_subtask,
641
+ };
642
+ }
643
+ void MAX_PHASE_3_PROMPTS_PER_SUBTASK;
644
+ //# sourceMappingURL=driver.js.map