role-os 2.0.0 → 2.2.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/CHANGELOG.md +61 -0
- package/README.es.md +123 -54
- package/README.fr.md +90 -21
- package/README.hi.md +90 -21
- package/README.it.md +130 -61
- package/README.ja.md +91 -22
- package/README.md +72 -16
- package/README.pt-BR.md +90 -21
- package/README.zh.md +160 -88
- package/package.json +2 -2
- package/src/artifacts.mjs +569 -437
- package/src/brainstorm-render.mjs +462 -0
- package/src/brainstorm-roles.mjs +774 -0
- package/src/brainstorm.mjs +778 -0
- package/src/dispatch.mjs +339 -310
- package/src/evidence.mjs +9 -9
- package/src/mission-run.mjs +111 -13
- package/src/mission.mjs +508 -388
- package/src/packs.mjs +430 -359
- package/src/route.mjs +715 -564
- package/src/run.mjs +5 -2
- package/starter-pack/agents/engineering/audit-synthesizer.md +56 -0
- package/starter-pack/agents/engineering/component-auditor.md +46 -0
- package/starter-pack/agents/engineering/seam-auditor.md +46 -0
- package/starter-pack/agents/engineering/test-truth-auditor.md +48 -0
package/src/dispatch.mjs
CHANGED
|
@@ -1,310 +1,339 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime dispatch engine.
|
|
3
|
-
*
|
|
4
|
-
* Turns a staffed, validated chain into an executable dispatch manifest
|
|
5
|
-
* that multi-claude can consume. Role-OS owns staffing/routing/evidence;
|
|
6
|
-
* multi-claude owns execution.
|
|
7
|
-
*
|
|
8
|
-
* This module:
|
|
9
|
-
* 1. Maps roles → role configs (tool profiles, system prompts, budgets)
|
|
10
|
-
* 2. Tracks execution state (queued → running → completed/failed/blocked)
|
|
11
|
-
* 3. Collects outputs back into the packet system
|
|
12
|
-
* 4. Generates escalation packets for blocked/rejected work
|
|
13
|
-
* 5. Closes chains to final completion
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
17
|
-
import { join, resolve } from "node:path";
|
|
18
|
-
import { resolveBlocked, resolveRejected } from "./escalation.mjs";
|
|
19
|
-
|
|
20
|
-
// ── Tool profiles per role ────────────────────────────────────────────────────
|
|
21
|
-
// What tools each role is allowed to use. Sandboxing by contract.
|
|
22
|
-
|
|
23
|
-
const TOOL_PROFILES = {
|
|
24
|
-
// Core
|
|
25
|
-
"Orchestrator": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
26
|
-
"Product Strategist": ["Read", "Glob", "Grep", "Write"],
|
|
27
|
-
"Critic Reviewer": ["Read", "Glob", "Grep", "Bash"],
|
|
28
|
-
|
|
29
|
-
// Design
|
|
30
|
-
"UI Designer": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
31
|
-
"Brand Guardian": ["Read", "Glob", "Grep"],
|
|
32
|
-
|
|
33
|
-
// Engineering
|
|
34
|
-
"Backend Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
35
|
-
"Frontend Developer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
36
|
-
"Test Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
37
|
-
"Performance Engineer": ["Read", "Glob", "Grep", "Bash"],
|
|
38
|
-
"Refactor Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
39
|
-
"Security Reviewer": ["Read", "Glob", "Grep", "Bash"],
|
|
40
|
-
"Dependency Auditor": ["Read", "Glob", "Grep", "Bash"],
|
|
41
|
-
|
|
42
|
-
// Treatment
|
|
43
|
-
"Repo Researcher": ["Read", "Glob", "Grep", "Bash"],
|
|
44
|
-
"Repo Translator": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
45
|
-
"Docs Architect": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
46
|
-
"Metadata Curator": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
47
|
-
"Coverage Auditor": ["Read", "Glob", "Grep", "Bash"],
|
|
48
|
-
"Deployment Verifier": ["Read", "Glob", "Grep", "Bash"],
|
|
49
|
-
"Release Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
50
|
-
|
|
51
|
-
// Growth / Marketing
|
|
52
|
-
"Launch Strategist": ["Read", "Glob", "Grep", "Write"],
|
|
53
|
-
"Content Strategist": ["Read", "Glob", "Grep", "Write"],
|
|
54
|
-
"Community Manager": ["Read", "Glob", "Grep", "Write"],
|
|
55
|
-
"Support Triage Lead": ["Read", "Glob", "Grep", "Write"],
|
|
56
|
-
"Launch Copywriter": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
57
|
-
|
|
58
|
-
// Product
|
|
59
|
-
"Feedback Synthesizer": ["Read", "Glob", "Grep"],
|
|
60
|
-
"Roadmap Prioritizer": ["Read", "Glob", "Grep", "Write"],
|
|
61
|
-
"Spec Writer": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
62
|
-
|
|
63
|
-
// Research
|
|
64
|
-
"UX Researcher": ["Read", "Glob", "Grep"],
|
|
65
|
-
"Competitive Analyst": ["Read", "Glob", "Grep"],
|
|
66
|
-
"Trend Researcher": ["Read", "Glob", "Grep"],
|
|
67
|
-
"User Interview Synthesizer": ["Read", "Glob", "Grep"],
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
]
|
|
91
|
-
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* @param {
|
|
221
|
-
* @returns {
|
|
222
|
-
*/
|
|
223
|
-
export function
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
*
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Runtime dispatch engine.
|
|
3
|
+
*
|
|
4
|
+
* Turns a staffed, validated chain into an executable dispatch manifest
|
|
5
|
+
* that multi-claude can consume. Role-OS owns staffing/routing/evidence;
|
|
6
|
+
* multi-claude owns execution.
|
|
7
|
+
*
|
|
8
|
+
* This module:
|
|
9
|
+
* 1. Maps roles → role configs (tool profiles, system prompts, budgets)
|
|
10
|
+
* 2. Tracks execution state (queued → running → completed/failed/blocked)
|
|
11
|
+
* 3. Collects outputs back into the packet system
|
|
12
|
+
* 4. Generates escalation packets for blocked/rejected work
|
|
13
|
+
* 5. Closes chains to final completion
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
17
|
+
import { join, resolve } from "node:path";
|
|
18
|
+
import { resolveBlocked, resolveRejected } from "./escalation.mjs";
|
|
19
|
+
|
|
20
|
+
// ── Tool profiles per role ────────────────────────────────────────────────────
|
|
21
|
+
// What tools each role is allowed to use. Sandboxing by contract.
|
|
22
|
+
|
|
23
|
+
const TOOL_PROFILES = {
|
|
24
|
+
// Core
|
|
25
|
+
"Orchestrator": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
26
|
+
"Product Strategist": ["Read", "Glob", "Grep", "Write"],
|
|
27
|
+
"Critic Reviewer": ["Read", "Glob", "Grep", "Bash"],
|
|
28
|
+
|
|
29
|
+
// Design
|
|
30
|
+
"UI Designer": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
31
|
+
"Brand Guardian": ["Read", "Glob", "Grep"],
|
|
32
|
+
|
|
33
|
+
// Engineering
|
|
34
|
+
"Backend Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
35
|
+
"Frontend Developer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
36
|
+
"Test Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
37
|
+
"Performance Engineer": ["Read", "Glob", "Grep", "Bash"],
|
|
38
|
+
"Refactor Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
39
|
+
"Security Reviewer": ["Read", "Glob", "Grep", "Bash"],
|
|
40
|
+
"Dependency Auditor": ["Read", "Glob", "Grep", "Bash"],
|
|
41
|
+
|
|
42
|
+
// Treatment
|
|
43
|
+
"Repo Researcher": ["Read", "Glob", "Grep", "Bash"],
|
|
44
|
+
"Repo Translator": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
45
|
+
"Docs Architect": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
46
|
+
"Metadata Curator": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
47
|
+
"Coverage Auditor": ["Read", "Glob", "Grep", "Bash"],
|
|
48
|
+
"Deployment Verifier": ["Read", "Glob", "Grep", "Bash"],
|
|
49
|
+
"Release Engineer": ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
|
|
50
|
+
|
|
51
|
+
// Growth / Marketing
|
|
52
|
+
"Launch Strategist": ["Read", "Glob", "Grep", "Write"],
|
|
53
|
+
"Content Strategist": ["Read", "Glob", "Grep", "Write"],
|
|
54
|
+
"Community Manager": ["Read", "Glob", "Grep", "Write"],
|
|
55
|
+
"Support Triage Lead": ["Read", "Glob", "Grep", "Write"],
|
|
56
|
+
"Launch Copywriter": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
57
|
+
|
|
58
|
+
// Product
|
|
59
|
+
"Feedback Synthesizer": ["Read", "Glob", "Grep"],
|
|
60
|
+
"Roadmap Prioritizer": ["Read", "Glob", "Grep", "Write"],
|
|
61
|
+
"Spec Writer": ["Read", "Glob", "Grep", "Write", "Edit"],
|
|
62
|
+
|
|
63
|
+
// Research
|
|
64
|
+
"UX Researcher": ["Read", "Glob", "Grep"],
|
|
65
|
+
"Competitive Analyst": ["Read", "Glob", "Grep"],
|
|
66
|
+
"Trend Researcher": ["Read", "Glob", "Grep"],
|
|
67
|
+
"User Interview Synthesizer": ["Read", "Glob", "Grep"],
|
|
68
|
+
|
|
69
|
+
// Brainstorm
|
|
70
|
+
"Context Scout": ["Read", "Glob", "Grep"],
|
|
71
|
+
"User Value Scout": ["Read", "Glob", "Grep"],
|
|
72
|
+
"Creative Leap Scout": ["Read", "Glob", "Grep"],
|
|
73
|
+
"Normalizer": ["Read", "Glob", "Grep"],
|
|
74
|
+
"Synthesizer": ["Read", "Glob", "Grep", "Write"],
|
|
75
|
+
"Product Expander": ["Read", "Glob", "Grep", "Write"],
|
|
76
|
+
"Judge": ["Read", "Glob", "Grep"],
|
|
77
|
+
"Mechanics Scout": ["Read", "Glob", "Grep"],
|
|
78
|
+
"Market Scout": ["Read", "Glob", "Grep"],
|
|
79
|
+
"Contrarian Scout": ["Read", "Glob", "Grep"],
|
|
80
|
+
"Feasibility Scout": ["Read", "Glob", "Grep"],
|
|
81
|
+
"Quality Bar Scout": ["Read", "Glob", "Grep"],
|
|
82
|
+
"Scenario Expander": ["Read", "Glob", "Grep", "Write"],
|
|
83
|
+
"Moat Expander": ["Read", "Glob", "Grep", "Write"],
|
|
84
|
+
|
|
85
|
+
// Brainstorm v0.3 analysts
|
|
86
|
+
"Context Analyst": ["Read", "Glob", "Grep"],
|
|
87
|
+
"User Value Analyst": ["Read", "Glob", "Grep"],
|
|
88
|
+
"Mechanics Analyst": ["Read", "Glob", "Grep"],
|
|
89
|
+
"Positioning Analyst": ["Read", "Glob", "Grep"],
|
|
90
|
+
"Contrarian Analyst": ["Read", "Glob", "Grep"],
|
|
91
|
+
|
|
92
|
+
// Deep Audit
|
|
93
|
+
"Component Auditor": ["Read", "Glob", "Grep"],
|
|
94
|
+
"Seam Auditor": ["Read", "Glob", "Grep"],
|
|
95
|
+
"Test Truth Auditor": ["Read", "Glob", "Grep"],
|
|
96
|
+
"Audit Synthesizer": ["Read", "Glob", "Grep", "Write"],
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// ── Default role config ─────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
const DEFAULTS = {
|
|
102
|
+
model: "sonnet",
|
|
103
|
+
maxTurns: 30,
|
|
104
|
+
maxBudgetUsd: 5.0,
|
|
105
|
+
timeoutMs: 10 * 60 * 1000, // 10 minutes
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// ── Execution states ──────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
export const EXEC_STATES = [
|
|
111
|
+
"queued", // in chain, not yet launched
|
|
112
|
+
"running", // role session active
|
|
113
|
+
"waiting", // waiting for upstream handoff
|
|
114
|
+
"blocked", // hit a block condition
|
|
115
|
+
"needs-review", // completed work, awaiting verdict
|
|
116
|
+
"completed", // approved and done
|
|
117
|
+
"failed", // session error or timeout
|
|
118
|
+
"interrupted", // manually stopped
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
// ── System prompt builder ─────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
function buildRolePrompt(roleName, packetContent, chainContext) {
|
|
124
|
+
return `You are operating as ${roleName} in a Role-OS managed chain.
|
|
125
|
+
|
|
126
|
+
## Your Role Contract
|
|
127
|
+
Follow the ${roleName} contract exactly. Your quality bar, inputs, outputs, and escalation rules are defined in .claude/agents/.
|
|
128
|
+
|
|
129
|
+
## Current Packet
|
|
130
|
+
${packetContent}
|
|
131
|
+
|
|
132
|
+
## Chain Context
|
|
133
|
+
You are step ${chainContext.stepNumber} of ${chainContext.totalSteps} in this chain.
|
|
134
|
+
${chainContext.previousRole ? `Previous role: ${chainContext.previousRole} (${chainContext.previousStatus})` : "You are the first role in this chain."}
|
|
135
|
+
${chainContext.nextRole ? `Next role: ${chainContext.nextRole}` : "You are the last role before Critic review."}
|
|
136
|
+
|
|
137
|
+
## Handoff Requirements
|
|
138
|
+
When you finish, produce a structured handoff:
|
|
139
|
+
1. Summary of what you did
|
|
140
|
+
2. Artifacts produced (files, changes, docs)
|
|
141
|
+
3. Open questions or risks for the next role
|
|
142
|
+
4. Evidence items for your verdict (kind, reference, claim, status)
|
|
143
|
+
|
|
144
|
+
## Stop Conditions
|
|
145
|
+
- If you cannot proceed: output a BLOCKED status with a clear reason
|
|
146
|
+
- If the upstream handoff is insufficient: output a BLOCKED status explaining what's missing
|
|
147
|
+
- Do not silently skip work — surface every gap explicitly
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Dispatch manifest builder ─────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Build a dispatch manifest from a routed chain.
|
|
155
|
+
* This is the contract between Role-OS (planning) and multi-claude (execution).
|
|
156
|
+
*
|
|
157
|
+
* @param {Object} options
|
|
158
|
+
* @param {string} options.packetFile - Path to the packet markdown
|
|
159
|
+
* @param {string} options.packetContent - Packet markdown content
|
|
160
|
+
* @param {Array<{role: {name: string, pack: string}}>} options.chainRoles - Assembled chain
|
|
161
|
+
* @param {string} options.cwd - Working directory for execution
|
|
162
|
+
* @param {Object} [options.overrides] - Per-role config overrides
|
|
163
|
+
* @returns {DispatchManifest}
|
|
164
|
+
*/
|
|
165
|
+
export function buildDispatchManifest({ packetFile, packetContent, chainRoles, cwd, overrides = {} }) {
|
|
166
|
+
const runId = `run-${Date.now()}`;
|
|
167
|
+
const steps = [];
|
|
168
|
+
|
|
169
|
+
for (let i = 0; i < chainRoles.length; i++) {
|
|
170
|
+
const { role } = chainRoles[i];
|
|
171
|
+
const roleOverrides = overrides[role.name] || {};
|
|
172
|
+
|
|
173
|
+
const chainContext = {
|
|
174
|
+
stepNumber: i + 1,
|
|
175
|
+
totalSteps: chainRoles.length,
|
|
176
|
+
previousRole: i > 0 ? chainRoles[i - 1].role.name : null,
|
|
177
|
+
previousStatus: i > 0 ? "pending" : null,
|
|
178
|
+
nextRole: i < chainRoles.length - 1 ? chainRoles[i + 1].role.name : null,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
steps.push({
|
|
182
|
+
stepIndex: i,
|
|
183
|
+
packetId: `${runId}-step-${i}`,
|
|
184
|
+
role: role.name,
|
|
185
|
+
pack: role.pack,
|
|
186
|
+
tools: TOOL_PROFILES[role.name] || ["Read", "Glob", "Grep"],
|
|
187
|
+
systemPrompt: buildRolePrompt(role.name, packetContent, chainContext),
|
|
188
|
+
model: roleOverrides.model || DEFAULTS.model,
|
|
189
|
+
maxTurns: roleOverrides.maxTurns || DEFAULTS.maxTurns,
|
|
190
|
+
maxBudgetUsd: roleOverrides.maxBudgetUsd || DEFAULTS.maxBudgetUsd,
|
|
191
|
+
timeoutMs: roleOverrides.timeoutMs || DEFAULTS.timeoutMs,
|
|
192
|
+
state: "queued",
|
|
193
|
+
dependsOn: i > 0 ? [`${runId}-step-${i - 1}`] : [],
|
|
194
|
+
handoff: {
|
|
195
|
+
from: i > 0 ? chainRoles[i - 1].role.name : null,
|
|
196
|
+
to: i < chainRoles.length - 1 ? chainRoles[i + 1].role.name : null,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
version: 1,
|
|
203
|
+
runId,
|
|
204
|
+
createdAt: new Date().toISOString(),
|
|
205
|
+
packetFile: resolve(packetFile),
|
|
206
|
+
cwd: resolve(cwd),
|
|
207
|
+
totalSteps: steps.length,
|
|
208
|
+
steps,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── Execution state tracker ───────────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Update a step's execution state in the manifest.
|
|
216
|
+
*
|
|
217
|
+
* @param {DispatchManifest} manifest
|
|
218
|
+
* @param {number} stepIndex
|
|
219
|
+
* @param {string} newState - One of EXEC_STATES
|
|
220
|
+
* @param {Object} [result] - Execution result data
|
|
221
|
+
* @returns {DispatchManifest} Updated manifest (mutated in place)
|
|
222
|
+
*/
|
|
223
|
+
export function updateStepState(manifest, stepIndex, newState, result = {}) {
|
|
224
|
+
const step = manifest.steps[stepIndex];
|
|
225
|
+
if (!step) throw new Error(`Invalid step index: ${stepIndex}`);
|
|
226
|
+
if (!EXEC_STATES.includes(newState)) throw new Error(`Invalid state: ${newState}`);
|
|
227
|
+
|
|
228
|
+
step.state = newState;
|
|
229
|
+
step.result = {
|
|
230
|
+
...step.result,
|
|
231
|
+
...result,
|
|
232
|
+
updatedAt: new Date().toISOString(),
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Auto-advance: if this step completed, mark next step as ready
|
|
236
|
+
if (newState === "completed" && stepIndex < manifest.steps.length - 1) {
|
|
237
|
+
const nextStep = manifest.steps[stepIndex + 1];
|
|
238
|
+
if (nextStep.state === "queued") {
|
|
239
|
+
nextStep.state = "waiting"; // ready to launch
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return manifest;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Get the chain's overall status from individual step states.
|
|
248
|
+
*
|
|
249
|
+
* @param {DispatchManifest} manifest
|
|
250
|
+
* @returns {{status: string, completedSteps: number, totalSteps: number, currentStep: Object|null, blockedSteps: Object[]}}
|
|
251
|
+
*/
|
|
252
|
+
export function getChainStatus(manifest) {
|
|
253
|
+
const completed = manifest.steps.filter(s => s.state === "completed").length;
|
|
254
|
+
const blocked = manifest.steps.filter(s => s.state === "blocked");
|
|
255
|
+
const failed = manifest.steps.filter(s => s.state === "failed");
|
|
256
|
+
const running = manifest.steps.find(s => s.state === "running");
|
|
257
|
+
const waiting = manifest.steps.find(s => s.state === "waiting");
|
|
258
|
+
|
|
259
|
+
let status;
|
|
260
|
+
if (failed.length > 0) status = "failed";
|
|
261
|
+
else if (blocked.length > 0) status = "blocked";
|
|
262
|
+
else if (completed === manifest.steps.length) status = "completed";
|
|
263
|
+
else if (running) status = "running";
|
|
264
|
+
else if (waiting) status = "ready";
|
|
265
|
+
else status = "queued";
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
status,
|
|
269
|
+
completedSteps: completed,
|
|
270
|
+
totalSteps: manifest.steps.length,
|
|
271
|
+
currentStep: running || waiting || null,
|
|
272
|
+
blockedSteps: blocked,
|
|
273
|
+
failedSteps: failed,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ── Escalation packet generator ───────────────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Generate an escalation packet when a step is blocked or rejected.
|
|
281
|
+
*
|
|
282
|
+
* @param {Object} step - The blocked/rejected step
|
|
283
|
+
* @param {string} reason - Block/rejection reason
|
|
284
|
+
* @param {string} verdict - "blocked" or "reject"
|
|
285
|
+
* @returns {Object} Escalation packet ready for routing
|
|
286
|
+
*/
|
|
287
|
+
export function generateEscalationPacket(step, reason, verdict) {
|
|
288
|
+
const escalation = verdict === "blocked"
|
|
289
|
+
? resolveBlocked(reason)
|
|
290
|
+
: resolveRejected(reason, step.role);
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
type: "escalation",
|
|
294
|
+
sourceStep: step.packetId,
|
|
295
|
+
sourceRole: step.role,
|
|
296
|
+
verdict,
|
|
297
|
+
reason,
|
|
298
|
+
escalation,
|
|
299
|
+
packet: {
|
|
300
|
+
title: `Escalation: ${step.role} ${verdict} — ${reason.slice(0, 80)}`,
|
|
301
|
+
assignedRole: escalation.targetRole,
|
|
302
|
+
recovery: escalation.recovery,
|
|
303
|
+
requiredArtifact: escalation.requiredArtifact,
|
|
304
|
+
context: escalation.handoffContext || escalation.reason,
|
|
305
|
+
sourcePacketFile: step.packetId,
|
|
306
|
+
},
|
|
307
|
+
generatedAt: new Date().toISOString(),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ── Manifest persistence ──────────────────────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Save a dispatch manifest to disk.
|
|
315
|
+
*
|
|
316
|
+
* @param {DispatchManifest} manifest
|
|
317
|
+
* @param {string} outputDir - Directory to write to
|
|
318
|
+
* @returns {string} Path to the saved manifest
|
|
319
|
+
*/
|
|
320
|
+
export function saveManifest(manifest, outputDir) {
|
|
321
|
+
mkdirSync(outputDir, { recursive: true });
|
|
322
|
+
const path = join(outputDir, `${manifest.runId}.dispatch.json`);
|
|
323
|
+
writeFileSync(path, JSON.stringify(manifest, null, 2));
|
|
324
|
+
return path;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Load a dispatch manifest from disk.
|
|
329
|
+
*
|
|
330
|
+
* @param {string} path
|
|
331
|
+
* @returns {DispatchManifest}
|
|
332
|
+
*/
|
|
333
|
+
export function loadManifest(path) {
|
|
334
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ── Exports for integration ───────────────────────────────────────────────────
|
|
338
|
+
|
|
339
|
+
export { TOOL_PROFILES, DEFAULTS };
|