oxe-cc 0.9.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/banner.txt +1 -1
- package/bin/lib/oxe-dashboard.cjs +9 -7
- package/bin/lib/oxe-operational.cjs +569 -4
- package/bin/oxe-cc.js +141 -57
- package/lib/runtime/compiler/graph-compiler.d.ts +83 -0
- package/lib/runtime/compiler/graph-compiler.js +135 -0
- package/lib/runtime/compiler/index.d.ts +1 -0
- package/lib/runtime/compiler/index.js +17 -0
- package/lib/runtime/context/context-pack-builder.d.ts +36 -0
- package/lib/runtime/context/context-pack-builder.js +136 -0
- package/lib/runtime/context/index.d.ts +1 -0
- package/lib/runtime/context/index.js +17 -0
- package/lib/runtime/delivery/branch-manager.d.ts +19 -0
- package/lib/runtime/delivery/branch-manager.js +78 -0
- package/lib/runtime/delivery/ci-checks.d.ts +34 -0
- package/lib/runtime/delivery/ci-checks.js +209 -0
- package/lib/runtime/delivery/index.d.ts +3 -0
- package/lib/runtime/delivery/index.js +19 -0
- package/lib/runtime/delivery/pr-manager.d.ts +30 -0
- package/lib/runtime/delivery/pr-manager.js +82 -0
- package/lib/runtime/events/bus.d.ts +9 -0
- package/lib/runtime/events/bus.js +63 -0
- package/lib/runtime/events/catalog.d.ts +3 -0
- package/lib/runtime/events/catalog.js +30 -0
- package/lib/runtime/events/envelope.d.ts +13 -0
- package/lib/runtime/events/envelope.js +2 -0
- package/lib/runtime/events/index.d.ts +3 -0
- package/lib/runtime/events/index.js +19 -0
- package/lib/runtime/evidence/evidence-store.d.ts +22 -0
- package/lib/runtime/evidence/evidence-store.js +106 -0
- package/lib/runtime/evidence/index.d.ts +1 -0
- package/lib/runtime/evidence/index.js +17 -0
- package/lib/runtime/gate/gate-manager.d.ts +39 -0
- package/lib/runtime/gate/gate-manager.js +104 -0
- package/lib/runtime/gate/index.d.ts +1 -0
- package/lib/runtime/gate/index.js +17 -0
- package/lib/runtime/index.d.ts +16 -0
- package/lib/runtime/index.js +40 -0
- package/lib/runtime/models/attempt.d.ts +12 -0
- package/lib/runtime/models/attempt.js +2 -0
- package/lib/runtime/models/evidence.d.ts +9 -0
- package/lib/runtime/models/evidence.js +2 -0
- package/lib/runtime/models/gate-decision.d.ts +10 -0
- package/lib/runtime/models/gate-decision.js +2 -0
- package/lib/runtime/models/index.d.ts +8 -0
- package/lib/runtime/models/index.js +24 -0
- package/lib/runtime/models/run.d.ts +13 -0
- package/lib/runtime/models/run.js +2 -0
- package/lib/runtime/models/session.d.ts +10 -0
- package/lib/runtime/models/session.js +2 -0
- package/lib/runtime/models/verification-result.d.ts +9 -0
- package/lib/runtime/models/verification-result.js +2 -0
- package/lib/runtime/models/work-item.d.ts +15 -0
- package/lib/runtime/models/work-item.js +2 -0
- package/lib/runtime/models/workspace.d.ts +25 -0
- package/lib/runtime/models/workspace.js +2 -0
- package/lib/runtime/plugins/index.d.ts +2 -0
- package/lib/runtime/plugins/index.js +18 -0
- package/lib/runtime/plugins/plugin-abi.d.ts +76 -0
- package/lib/runtime/plugins/plugin-abi.js +2 -0
- package/lib/runtime/plugins/plugin-registry.d.ts +21 -0
- package/lib/runtime/plugins/plugin-registry.js +114 -0
- package/lib/runtime/policy/index.d.ts +1 -0
- package/lib/runtime/policy/index.js +17 -0
- package/lib/runtime/policy/policy-engine.d.ts +40 -0
- package/lib/runtime/policy/policy-engine.js +80 -0
- package/lib/runtime/projection/index.d.ts +1 -0
- package/lib/runtime/projection/index.js +17 -0
- package/lib/runtime/projection/projection-engine.d.ts +11 -0
- package/lib/runtime/projection/projection-engine.js +218 -0
- package/lib/runtime/reducers/debug-reducer.d.ts +10 -0
- package/lib/runtime/reducers/debug-reducer.js +30 -0
- package/lib/runtime/reducers/index.d.ts +2 -0
- package/lib/runtime/reducers/index.js +18 -0
- package/lib/runtime/reducers/run-state-reducer.d.ts +20 -0
- package/lib/runtime/reducers/run-state-reducer.js +110 -0
- package/lib/runtime/scheduler/index.d.ts +1 -0
- package/lib/runtime/scheduler/index.js +17 -0
- package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +34 -0
- package/lib/runtime/scheduler/multi-agent-coordinator.js +166 -0
- package/lib/runtime/scheduler/scheduler.d.ts +39 -0
- package/lib/runtime/scheduler/scheduler.js +196 -0
- package/lib/runtime/verification/index.d.ts +1 -0
- package/lib/runtime/verification/index.js +17 -0
- package/lib/runtime/verification/verification-compiler.d.ts +56 -0
- package/lib/runtime/verification/verification-compiler.js +147 -0
- package/lib/runtime/workspace/index.d.ts +5 -0
- package/lib/runtime/workspace/index.js +24 -0
- package/lib/runtime/workspace/strategies/ephemeral-container.d.ts +22 -0
- package/lib/runtime/workspace/strategies/ephemeral-container.js +109 -0
- package/lib/runtime/workspace/strategies/git-worktree.d.ts +12 -0
- package/lib/runtime/workspace/strategies/git-worktree.js +79 -0
- package/lib/runtime/workspace/strategies/inplace.d.ts +10 -0
- package/lib/runtime/workspace/strategies/inplace.js +37 -0
- package/lib/runtime/workspace/workspace-manager.d.ts +13 -0
- package/lib/runtime/workspace/workspace-manager.js +2 -0
- package/lib/sdk/index.cjs +24 -7
- package/lib/sdk/index.d.ts +17 -7
- package/package.json +9 -3
- package/packages/runtime/package.json +17 -0
- package/packages/runtime/src/compiler/graph-compiler.ts +245 -0
- package/packages/runtime/src/compiler/index.ts +1 -0
- package/packages/runtime/src/context/context-pack-builder.ts +193 -0
- package/packages/runtime/src/context/index.ts +1 -0
- package/packages/runtime/src/delivery/branch-manager.ts +84 -0
- package/packages/runtime/src/delivery/ci-checks.ts +252 -0
- package/packages/runtime/src/delivery/index.ts +3 -0
- package/packages/runtime/src/delivery/pr-manager.ts +112 -0
- package/packages/runtime/src/events/bus.ts +92 -0
- package/packages/runtime/src/events/catalog.ts +29 -0
- package/packages/runtime/src/events/envelope.ts +14 -0
- package/packages/runtime/src/events/index.ts +3 -0
- package/packages/runtime/src/evidence/evidence-store.ts +130 -0
- package/packages/runtime/src/evidence/index.ts +1 -0
- package/packages/runtime/src/gate/gate-manager.ts +137 -0
- package/packages/runtime/src/gate/index.ts +1 -0
- package/packages/runtime/src/index.ts +32 -0
- package/packages/runtime/src/models/attempt.ts +19 -0
- package/packages/runtime/src/models/evidence.ts +21 -0
- package/packages/runtime/src/models/gate-decision.ts +21 -0
- package/packages/runtime/src/models/index.ts +8 -0
- package/packages/runtime/src/models/run.ts +24 -0
- package/packages/runtime/src/models/session.ts +11 -0
- package/packages/runtime/src/models/verification-result.ts +10 -0
- package/packages/runtime/src/models/work-item.ts +25 -0
- package/packages/runtime/src/models/workspace.ts +28 -0
- package/packages/runtime/src/plugins/index.ts +2 -0
- package/packages/runtime/src/plugins/plugin-abi.ts +95 -0
- package/packages/runtime/src/plugins/plugin-registry.ts +119 -0
- package/packages/runtime/src/policy/index.ts +1 -0
- package/packages/runtime/src/policy/policy-engine.ts +113 -0
- package/packages/runtime/src/projection/index.ts +1 -0
- package/packages/runtime/src/projection/projection-engine.ts +249 -0
- package/packages/runtime/src/reducers/debug-reducer.ts +36 -0
- package/packages/runtime/src/reducers/index.ts +2 -0
- package/packages/runtime/src/reducers/run-state-reducer.ts +127 -0
- package/packages/runtime/src/scheduler/index.ts +1 -0
- package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +231 -0
- package/packages/runtime/src/scheduler/scheduler.ts +281 -0
- package/packages/runtime/src/verification/index.ts +1 -0
- package/packages/runtime/src/verification/verification-compiler.ts +225 -0
- package/packages/runtime/src/workspace/index.ts +5 -0
- package/packages/runtime/src/workspace/strategies/ephemeral-container.ts +121 -0
- package/packages/runtime/src/workspace/strategies/git-worktree.ts +77 -0
- package/packages/runtime/src/workspace/strategies/inplace.ts +35 -0
- package/packages/runtime/src/workspace/workspace-manager.ts +15 -0
- package/packages/runtime/tsconfig.json +17 -0
- package/vscode-extension/oxe-agents-0.9.2.vsix +0 -0
- package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
- package/vscode-extension/package.json +1 -1
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type { RunState } from '../reducers/run-state-reducer';
|
|
2
|
+
import type { ExecutionGraph } from '../compiler/graph-compiler';
|
|
3
|
+
import type { VerificationResult } from '../models/verification-result';
|
|
4
|
+
import type { CheckResult } from '../verification/verification-compiler';
|
|
5
|
+
|
|
6
|
+
function isoToDate(iso: string | null | undefined): string {
|
|
7
|
+
if (!iso) return '—';
|
|
8
|
+
return iso.slice(0, 10);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function statusIcon(status: string): string {
|
|
12
|
+
switch (status) {
|
|
13
|
+
case 'completed': return '✓';
|
|
14
|
+
case 'failed': return '✗';
|
|
15
|
+
case 'blocked': return '⊘';
|
|
16
|
+
case 'running': return '⟳';
|
|
17
|
+
default: return '○';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function verifyIcon(status: string): string {
|
|
22
|
+
switch (status) {
|
|
23
|
+
case 'pass': return '✓ PASS';
|
|
24
|
+
case 'fail': return '✗ FAIL';
|
|
25
|
+
case 'skip': return '— SKIP';
|
|
26
|
+
case 'error': return '! ERROR';
|
|
27
|
+
default: return status;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class ProjectionEngine {
|
|
32
|
+
projectPlan(state: RunState, graph: ExecutionGraph): string {
|
|
33
|
+
const run = state.run;
|
|
34
|
+
const lines: string[] = [
|
|
35
|
+
'# OXE — Plano de Execução',
|
|
36
|
+
'',
|
|
37
|
+
'<!-- Gerado automaticamente pelo Projection Engine — não editar diretamente -->',
|
|
38
|
+
'',
|
|
39
|
+
'## Resumo',
|
|
40
|
+
'',
|
|
41
|
+
`- **Run:** ${run?.run_id ?? '—'}`,
|
|
42
|
+
`- **Status:** ${run?.status ?? 'sem run'}`,
|
|
43
|
+
`- **Iniciado em:** ${isoToDate(run?.started_at)}`,
|
|
44
|
+
`- **Nós:** ${graph.metadata.node_count} tarefas em ${graph.metadata.wave_count} onda(s)`,
|
|
45
|
+
'',
|
|
46
|
+
'## Ondas e tarefas',
|
|
47
|
+
'',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
for (const wave of graph.waves) {
|
|
51
|
+
lines.push(`### Onda ${wave.wave_number}`);
|
|
52
|
+
lines.push('');
|
|
53
|
+
lines.push('| ID | Título | Status | Dependências |');
|
|
54
|
+
lines.push('|----|--------|--------|--------------|');
|
|
55
|
+
for (const nodeId of wave.node_ids) {
|
|
56
|
+
const node = graph.nodes.get(nodeId);
|
|
57
|
+
const wItem = state.workItems.get(nodeId);
|
|
58
|
+
const status = wItem?.status ?? 'pending';
|
|
59
|
+
const deps = node?.depends_on.join(', ') || '—';
|
|
60
|
+
lines.push(`| ${nodeId} | ${node?.title ?? nodeId} | ${statusIcon(status)} ${status} | ${deps} |`);
|
|
61
|
+
}
|
|
62
|
+
lines.push('');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (state.completedWorkItems.size > 0 || state.failedWorkItems.size > 0) {
|
|
66
|
+
lines.push('## Progresso');
|
|
67
|
+
lines.push('');
|
|
68
|
+
lines.push(`- **Concluídos:** ${[...state.completedWorkItems].join(', ') || '—'}`);
|
|
69
|
+
lines.push(`- **Falhos:** ${[...state.failedWorkItems].join(', ') || '—'}`);
|
|
70
|
+
lines.push(`- **Bloqueados:** ${[...state.blockedWorkItems].join(', ') || '—'}`);
|
|
71
|
+
lines.push('');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return lines.join('\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
projectVerify(
|
|
78
|
+
state: RunState,
|
|
79
|
+
results: VerificationResult[],
|
|
80
|
+
checkResults?: CheckResult[]
|
|
81
|
+
): string {
|
|
82
|
+
const run = state.run;
|
|
83
|
+
const lines: string[] = [
|
|
84
|
+
'# OXE — Verificação',
|
|
85
|
+
'',
|
|
86
|
+
'<!-- Gerado automaticamente pelo Projection Engine — não editar diretamente -->',
|
|
87
|
+
'',
|
|
88
|
+
'## Auditoria de pré-execução',
|
|
89
|
+
'',
|
|
90
|
+
`- **Run:** ${run?.run_id ?? '—'}`,
|
|
91
|
+
`- **Iniciado em:** ${isoToDate(run?.started_at)}`,
|
|
92
|
+
`- **Concluído em:** ${isoToDate(run?.ended_at)}`,
|
|
93
|
+
`- **Status:** ${run?.status ?? '—'}`,
|
|
94
|
+
'',
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
if (results.length > 0) {
|
|
98
|
+
lines.push('## Critérios de aceite');
|
|
99
|
+
lines.push('');
|
|
100
|
+
lines.push('| ID | Check | Status | Evidências |');
|
|
101
|
+
lines.push('|----|-------|--------|------------|');
|
|
102
|
+
for (const r of results) {
|
|
103
|
+
const evidenceList = r.evidence_refs.join(', ') || '—';
|
|
104
|
+
lines.push(`| ${r.check_id} | ${r.summary ?? r.check_id} | ${verifyIcon(r.status)} | ${evidenceList} |`);
|
|
105
|
+
}
|
|
106
|
+
lines.push('');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (checkResults && checkResults.length > 0) {
|
|
110
|
+
lines.push('## Resultados dos checks executados');
|
|
111
|
+
lines.push('');
|
|
112
|
+
lines.push('| Check | Aceite | Status | Duração |');
|
|
113
|
+
lines.push('|-------|--------|--------|---------|');
|
|
114
|
+
for (const cr of checkResults) {
|
|
115
|
+
lines.push(`| ${cr.check_id} | ${cr.acceptance_ref ?? '—'} | ${verifyIcon(cr.status)} | ${cr.duration_ms}ms |`);
|
|
116
|
+
}
|
|
117
|
+
lines.push('');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const passed = results.filter((r) => r.status === 'pass').length;
|
|
121
|
+
const failed = results.filter((r) => r.status === 'fail').length;
|
|
122
|
+
const allPass = failed === 0 && results.length > 0;
|
|
123
|
+
|
|
124
|
+
lines.push('## Conclusão');
|
|
125
|
+
lines.push('');
|
|
126
|
+
lines.push(`- **Total de critérios:** ${results.length}`);
|
|
127
|
+
lines.push(`- **Aprovados:** ${passed}`);
|
|
128
|
+
lines.push(`- **Reprovados:** ${failed}`);
|
|
129
|
+
lines.push('');
|
|
130
|
+
lines.push(allPass
|
|
131
|
+
? '> ✓ **Verificação concluída com sucesso.** Todos os critérios foram atendidos.'
|
|
132
|
+
: `> ✗ **Verificação com falhas.** ${failed} critério(s) não atendido(s).`);
|
|
133
|
+
lines.push('');
|
|
134
|
+
|
|
135
|
+
return lines.join('\n');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
projectState(state: RunState): string {
|
|
139
|
+
const run = state.run;
|
|
140
|
+
const completed = [...state.completedWorkItems];
|
|
141
|
+
const failed = [...state.failedWorkItems];
|
|
142
|
+
const blocked = [...state.blockedWorkItems];
|
|
143
|
+
|
|
144
|
+
const lines: string[] = [
|
|
145
|
+
'# OXE — Estado',
|
|
146
|
+
'',
|
|
147
|
+
'<!-- Gerado automaticamente pelo Projection Engine — não editar diretamente -->',
|
|
148
|
+
'',
|
|
149
|
+
'## Fase atual',
|
|
150
|
+
'',
|
|
151
|
+
`- **Status da run:** ${run?.status ?? 'sem run ativa'}`,
|
|
152
|
+
`- **Run ID:** ${run?.run_id ?? '—'}`,
|
|
153
|
+
`- **Modo:** ${run?.mode ?? '—'}`,
|
|
154
|
+
'',
|
|
155
|
+
'## Runtime operacional',
|
|
156
|
+
'',
|
|
157
|
+
`- **runtime_status:** ${run?.status ?? '—'}`,
|
|
158
|
+
`- **active_run_ref:** ${run?.run_id ?? '—'}`,
|
|
159
|
+
'',
|
|
160
|
+
'## Progresso',
|
|
161
|
+
'',
|
|
162
|
+
`- **Concluídos (${completed.length}):** ${completed.join(', ') || '—'}`,
|
|
163
|
+
`- **Falhos (${failed.length}):** ${failed.join(', ') || '—'}`,
|
|
164
|
+
`- **Bloqueados (${blocked.length}):** ${blocked.join(', ') || '—'}`,
|
|
165
|
+
'',
|
|
166
|
+
`- **lifecycleStatus:** ${run?.status === 'completed' ? 'closed' : run?.status === 'running' ? 'executing' : 'pending_execute'}`,
|
|
167
|
+
'',
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
return lines.join('\n');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
projectRunSummary(state: RunState): string {
|
|
174
|
+
const run = state.run;
|
|
175
|
+
const completed = [...state.completedWorkItems];
|
|
176
|
+
const failed = [...state.failedWorkItems];
|
|
177
|
+
const blocked = [...state.blockedWorkItems];
|
|
178
|
+
const totalAttempts = [...state.attempts.values()].reduce((s, a) => s + a.length, 0);
|
|
179
|
+
|
|
180
|
+
const lines: string[] = [
|
|
181
|
+
`## Run Summary — ${run?.run_id ?? 'unknown'}`,
|
|
182
|
+
'',
|
|
183
|
+
`**Status:** ${run?.status ?? '—'} `,
|
|
184
|
+
`**Mode:** ${run?.mode ?? '—'} `,
|
|
185
|
+
`**Started:** ${isoToDate(run?.started_at)} `,
|
|
186
|
+
`**Ended:** ${isoToDate(run?.ended_at)} `,
|
|
187
|
+
'',
|
|
188
|
+
`**Completed:** ${completed.length} tasks `,
|
|
189
|
+
`**Failed:** ${failed.length} tasks `,
|
|
190
|
+
`**Blocked:** ${blocked.length} tasks `,
|
|
191
|
+
`**Total attempts:** ${totalAttempts} `,
|
|
192
|
+
'',
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
if (completed.length > 0) {
|
|
196
|
+
lines.push(`**✓ Completed:** ${completed.join(', ')}`);
|
|
197
|
+
}
|
|
198
|
+
if (failed.length > 0) {
|
|
199
|
+
lines.push(`**✗ Failed:** ${failed.join(', ')}`);
|
|
200
|
+
}
|
|
201
|
+
if (blocked.length > 0) {
|
|
202
|
+
lines.push(`**⊘ Blocked:** ${blocked.join(', ')}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return lines.join('\n');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
projectPRSummary(state: RunState, graph: ExecutionGraph): string {
|
|
209
|
+
const run = state.run;
|
|
210
|
+
const completed = [...state.completedWorkItems];
|
|
211
|
+
const failed = [...state.failedWorkItems];
|
|
212
|
+
|
|
213
|
+
const lines: string[] = [
|
|
214
|
+
'## Summary',
|
|
215
|
+
'',
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
if (completed.length > 0) {
|
|
219
|
+
for (const id of completed) {
|
|
220
|
+
const node = graph.nodes.get(id);
|
|
221
|
+
lines.push(`- ✓ ${node?.title ?? id} (\`${id}\`)`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (failed.length > 0) {
|
|
226
|
+
lines.push('');
|
|
227
|
+
lines.push('**⚠️ Incomplete tasks:**');
|
|
228
|
+
for (const id of failed) {
|
|
229
|
+
const node = graph.nodes.get(id);
|
|
230
|
+
lines.push(`- ✗ ${node?.title ?? id} (\`${id}\`)`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
lines.push('');
|
|
235
|
+
lines.push('## Test plan');
|
|
236
|
+
lines.push('');
|
|
237
|
+
|
|
238
|
+
for (const wave of graph.waves) {
|
|
239
|
+
lines.push(`- [ ] Wave ${wave.wave_number}: ${wave.node_ids.join(', ')}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
lines.push('');
|
|
243
|
+
lines.push(`**Run:** \`${run?.run_id ?? '—'}\` | **Status:** ${run?.status ?? '—'}`);
|
|
244
|
+
lines.push('');
|
|
245
|
+
lines.push('🤖 Generated with [OXE Runtime](https://github.com/propagno/oxe-build)');
|
|
246
|
+
|
|
247
|
+
return lines.join('\n');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { OxeEvent } from '../events/envelope';
|
|
2
|
+
import { createEmptyRunState, applyEventExported as applyEvent } from './run-state-reducer';
|
|
3
|
+
import type { RunState } from './run-state-reducer';
|
|
4
|
+
|
|
5
|
+
export interface ReplayStep {
|
|
6
|
+
index: number;
|
|
7
|
+
event: OxeEvent;
|
|
8
|
+
state: RunState;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function* stepReplay(events: OxeEvent[]): Generator<ReplayStep> {
|
|
12
|
+
let state = createEmptyRunState();
|
|
13
|
+
for (let i = 0; i < events.length; i++) {
|
|
14
|
+
state = applyEvent(state, events[i]);
|
|
15
|
+
yield { index: i, event: events[i], state };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function replayUntil(
|
|
20
|
+
events: OxeEvent[],
|
|
21
|
+
predicate: (step: ReplayStep) => boolean
|
|
22
|
+
): ReplayStep | null {
|
|
23
|
+
for (const step of stepReplay(events)) {
|
|
24
|
+
if (predicate(step)) return step;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function replaySlice(events: OxeEvent[], from: number, to: number): ReplayStep[] {
|
|
30
|
+
const steps: ReplayStep[] = [];
|
|
31
|
+
for (const step of stepReplay(events)) {
|
|
32
|
+
if (step.index >= from && step.index <= to) steps.push(step);
|
|
33
|
+
if (step.index > to) break;
|
|
34
|
+
}
|
|
35
|
+
return steps;
|
|
36
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { OxeEvent } from '../events/envelope';
|
|
2
|
+
import type { Run } from '../models/run';
|
|
3
|
+
import type { WorkItem } from '../models/work-item';
|
|
4
|
+
import type { Attempt } from '../models/attempt';
|
|
5
|
+
import type { Workspace } from '../models/workspace';
|
|
6
|
+
|
|
7
|
+
export interface RunState {
|
|
8
|
+
run: Run | null;
|
|
9
|
+
workItems: Map<string, WorkItem>;
|
|
10
|
+
attempts: Map<string, Attempt[]>;
|
|
11
|
+
workspaces: Map<string, Workspace>;
|
|
12
|
+
completedWorkItems: Set<string>;
|
|
13
|
+
failedWorkItems: Set<string>;
|
|
14
|
+
blockedWorkItems: Set<string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createEmptyRunState(): RunState {
|
|
18
|
+
return {
|
|
19
|
+
run: null,
|
|
20
|
+
workItems: new Map(),
|
|
21
|
+
attempts: new Map(),
|
|
22
|
+
workspaces: new Map(),
|
|
23
|
+
completedWorkItems: new Set(),
|
|
24
|
+
failedWorkItems: new Set(),
|
|
25
|
+
blockedWorkItems: new Set(),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function reduce(events: OxeEvent[]): RunState {
|
|
30
|
+
return events.reduce(applyEvent, createEmptyRunState());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Exported alias so debug-reducer can import applyEvent without circular issues
|
|
34
|
+
export { applyEvent as applyEventExported };
|
|
35
|
+
|
|
36
|
+
function applyEvent(state: RunState, event: OxeEvent): RunState {
|
|
37
|
+
switch (event.type) {
|
|
38
|
+
case 'RunStarted': {
|
|
39
|
+
const run = event.payload as unknown as Run;
|
|
40
|
+
return { ...state, run };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
case 'RunCompleted': {
|
|
44
|
+
if (!state.run) return state;
|
|
45
|
+
const status = (event.payload as { status?: Run['status'] }).status ?? 'completed';
|
|
46
|
+
return {
|
|
47
|
+
...state,
|
|
48
|
+
run: { ...state.run, status, ended_at: event.timestamp },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
case 'WorkItemReady': {
|
|
53
|
+
if (!event.work_item_id) return state;
|
|
54
|
+
const workItems = new Map(state.workItems);
|
|
55
|
+
const existing = workItems.get(event.work_item_id);
|
|
56
|
+
if (existing) {
|
|
57
|
+
workItems.set(event.work_item_id, { ...existing, status: 'ready' });
|
|
58
|
+
} else {
|
|
59
|
+
// First time we see this work item — create from payload
|
|
60
|
+
const item = event.payload as unknown as WorkItem;
|
|
61
|
+
workItems.set(event.work_item_id, { ...item, work_item_id: event.work_item_id, status: 'ready' });
|
|
62
|
+
}
|
|
63
|
+
return { ...state, workItems };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
case 'AttemptStarted': {
|
|
67
|
+
if (!event.work_item_id || !event.attempt_id) return state;
|
|
68
|
+
const attempts = new Map(state.attempts);
|
|
69
|
+
const attempt: Attempt = {
|
|
70
|
+
attempt_id: event.attempt_id,
|
|
71
|
+
work_item_id: event.work_item_id,
|
|
72
|
+
attempt_number: (event.payload as { attempt_number?: number }).attempt_number ?? 1,
|
|
73
|
+
workspace_id: null,
|
|
74
|
+
agent_profile: null,
|
|
75
|
+
model: null,
|
|
76
|
+
started_at: event.timestamp,
|
|
77
|
+
ended_at: null,
|
|
78
|
+
outcome: null,
|
|
79
|
+
};
|
|
80
|
+
const existing = attempts.get(event.work_item_id) ?? [];
|
|
81
|
+
attempts.set(event.work_item_id, [...existing, attempt]);
|
|
82
|
+
return { ...state, attempts };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
case 'WorkspaceAllocated': {
|
|
86
|
+
const ws = event.payload as unknown as Workspace;
|
|
87
|
+
if (!ws.workspace_id) return state;
|
|
88
|
+
const workspaces = new Map(state.workspaces);
|
|
89
|
+
workspaces.set(ws.workspace_id, { ...ws, status: 'ready' });
|
|
90
|
+
return { ...state, workspaces };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
case 'WorkItemCompleted': {
|
|
94
|
+
if (!event.work_item_id) return state;
|
|
95
|
+
const workItems = new Map(state.workItems);
|
|
96
|
+
const item = workItems.get(event.work_item_id);
|
|
97
|
+
if (item) workItems.set(event.work_item_id, { ...item, status: 'completed' });
|
|
98
|
+
const completedWorkItems = new Set(state.completedWorkItems);
|
|
99
|
+
completedWorkItems.add(event.work_item_id);
|
|
100
|
+
return { ...state, workItems, completedWorkItems };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
case 'WorkItemBlocked': {
|
|
104
|
+
if (!event.work_item_id) return state;
|
|
105
|
+
const workItems = new Map(state.workItems);
|
|
106
|
+
const item = workItems.get(event.work_item_id);
|
|
107
|
+
if (item) workItems.set(event.work_item_id, { ...item, status: 'blocked' });
|
|
108
|
+
const blockedWorkItems = new Set(state.blockedWorkItems);
|
|
109
|
+
blockedWorkItems.add(event.work_item_id);
|
|
110
|
+
return { ...state, workItems, blockedWorkItems };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
default:
|
|
114
|
+
return state;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function getWorkItemStatus(
|
|
119
|
+
state: RunState,
|
|
120
|
+
workItemId: string
|
|
121
|
+
): WorkItem['status'] | null {
|
|
122
|
+
return state.workItems.get(workItemId)?.status ?? null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function getAttemptCount(state: RunState, workItemId: string): number {
|
|
126
|
+
return state.attempts.get(workItemId)?.length ?? 0;
|
|
127
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './scheduler';
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { appendEvent } from '../events/bus';
|
|
2
|
+
import type { ExecutionGraph, GraphNode } from '../compiler/graph-compiler';
|
|
3
|
+
import type { WorkspaceManager } from '../workspace/workspace-manager';
|
|
4
|
+
import type { TaskExecutor, TaskResult, SchedulerContext } from './scheduler';
|
|
5
|
+
import { Scheduler } from './scheduler';
|
|
6
|
+
import type { WorkspaceLease } from '../models/workspace';
|
|
7
|
+
|
|
8
|
+
export type CoordinationMode = 'parallel' | 'competitive' | 'cooperative';
|
|
9
|
+
|
|
10
|
+
export interface AgentSpec {
|
|
11
|
+
id: string;
|
|
12
|
+
executor: TaskExecutor;
|
|
13
|
+
workspaceManager: WorkspaceManager;
|
|
14
|
+
/** Task IDs this agent is responsible for (used in parallel mode) */
|
|
15
|
+
assignedTaskIds?: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CoordinationOptions {
|
|
19
|
+
mode: CoordinationMode;
|
|
20
|
+
agents: AgentSpec[];
|
|
21
|
+
projectRoot: string;
|
|
22
|
+
sessionId: string | null;
|
|
23
|
+
runId: string;
|
|
24
|
+
onEvent?: SchedulerContext['onEvent'];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CoordinationResult {
|
|
28
|
+
mode: CoordinationMode;
|
|
29
|
+
run_id: string;
|
|
30
|
+
completed: string[];
|
|
31
|
+
failed: string[];
|
|
32
|
+
blocked: string[];
|
|
33
|
+
agent_results: Array<{ agent_id: string; completed: string[]; failed: string[] }>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ─── Parallel mode ───────────────────────────────────────────────────────────
|
|
37
|
+
// Tasks are partitioned across agents. Each agent runs its own Scheduler
|
|
38
|
+
// on a sub-graph. Results are merged.
|
|
39
|
+
|
|
40
|
+
async function runParallel(
|
|
41
|
+
graph: ExecutionGraph,
|
|
42
|
+
opts: CoordinationOptions
|
|
43
|
+
): Promise<CoordinationResult> {
|
|
44
|
+
const { agents, projectRoot, sessionId, runId } = opts;
|
|
45
|
+
|
|
46
|
+
// Partition tasks across agents (round-robin if assignedTaskIds not set)
|
|
47
|
+
const partitions = agents.map((a) => a.assignedTaskIds ?? []);
|
|
48
|
+
if (partitions.every((p) => p.length === 0)) {
|
|
49
|
+
const allIds = [...graph.nodes.keys()];
|
|
50
|
+
allIds.forEach((id, i) => {
|
|
51
|
+
partitions[i % agents.length].push(id);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
appendEvent(projectRoot, sessionId, {
|
|
56
|
+
type: 'RunStarted',
|
|
57
|
+
run_id: runId,
|
|
58
|
+
payload: { mode: 'parallel', agent_count: agents.length },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const agentResults = await Promise.all(
|
|
62
|
+
agents.map(async (agent, idx) => {
|
|
63
|
+
const subGraph = subGraphFor(graph, partitions[idx]);
|
|
64
|
+
if (subGraph.nodes.size === 0) {
|
|
65
|
+
return { agent_id: agent.id, completed: [], failed: [] };
|
|
66
|
+
}
|
|
67
|
+
const ctx: SchedulerContext = {
|
|
68
|
+
projectRoot,
|
|
69
|
+
sessionId,
|
|
70
|
+
runId: `${runId}-agent${idx}`,
|
|
71
|
+
executor: agent.executor,
|
|
72
|
+
workspaceManager: agent.workspaceManager,
|
|
73
|
+
onEvent: opts.onEvent,
|
|
74
|
+
};
|
|
75
|
+
const scheduler = new Scheduler();
|
|
76
|
+
const result = await scheduler.run(subGraph, ctx);
|
|
77
|
+
return { agent_id: agent.id, completed: result.completed, failed: result.failed };
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const completed = agentResults.flatMap((r) => r.completed);
|
|
82
|
+
const failed = agentResults.flatMap((r) => r.failed);
|
|
83
|
+
|
|
84
|
+
appendEvent(projectRoot, sessionId, {
|
|
85
|
+
type: 'RunCompleted',
|
|
86
|
+
run_id: runId,
|
|
87
|
+
payload: { mode: 'parallel', completed: completed.length, failed: failed.length },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return { mode: 'parallel', run_id: runId, completed, failed, blocked: [], agent_results: agentResults };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── Competitive mode ────────────────────────────────────────────────────────
|
|
94
|
+
// Two agents attempt the same task. First success wins; the loser's workspace
|
|
95
|
+
// is disposed. Requires exactly 2 agents.
|
|
96
|
+
|
|
97
|
+
async function runCompetitive(
|
|
98
|
+
graph: ExecutionGraph,
|
|
99
|
+
opts: CoordinationOptions
|
|
100
|
+
): Promise<CoordinationResult> {
|
|
101
|
+
if (opts.agents.length < 2) {
|
|
102
|
+
throw new Error('Competitive mode requires at least 2 agents');
|
|
103
|
+
}
|
|
104
|
+
const [agentA, agentB] = opts.agents;
|
|
105
|
+
const { projectRoot, sessionId, runId } = opts;
|
|
106
|
+
|
|
107
|
+
appendEvent(projectRoot, sessionId, {
|
|
108
|
+
type: 'RunStarted',
|
|
109
|
+
run_id: runId,
|
|
110
|
+
payload: { mode: 'competitive' },
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const completed: string[] = [];
|
|
114
|
+
const failed: string[] = [];
|
|
115
|
+
|
|
116
|
+
for (const wave of graph.waves) {
|
|
117
|
+
for (const nodeId of wave.node_ids) {
|
|
118
|
+
const node = graph.nodes.get(nodeId)!;
|
|
119
|
+
const result = await competeTwoAgents(nodeId, node, agentA, agentB, opts);
|
|
120
|
+
if (result.success) completed.push(nodeId);
|
|
121
|
+
else failed.push(nodeId);
|
|
122
|
+
if (failed.length > 0) break;
|
|
123
|
+
}
|
|
124
|
+
if (failed.length > 0) break;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
appendEvent(projectRoot, sessionId, {
|
|
128
|
+
type: 'RunCompleted',
|
|
129
|
+
run_id: runId,
|
|
130
|
+
payload: { mode: 'competitive', completed: completed.length },
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
mode: 'competitive',
|
|
135
|
+
run_id: runId,
|
|
136
|
+
completed,
|
|
137
|
+
failed,
|
|
138
|
+
blocked: [],
|
|
139
|
+
agent_results: [
|
|
140
|
+
{ agent_id: agentA.id, completed, failed },
|
|
141
|
+
{ agent_id: agentB.id, completed: [], failed: [] },
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function competeTwoAgents(
|
|
147
|
+
nodeId: string,
|
|
148
|
+
node: GraphNode,
|
|
149
|
+
agentA: AgentSpec,
|
|
150
|
+
agentB: AgentSpec,
|
|
151
|
+
opts: CoordinationOptions
|
|
152
|
+
): Promise<TaskResult> {
|
|
153
|
+
const { projectRoot, sessionId, runId } = opts;
|
|
154
|
+
|
|
155
|
+
const allocA = await agentA.workspaceManager.allocate({
|
|
156
|
+
work_item_id: nodeId, attempt_number: 1, strategy: node.workspace_strategy, mutation_scope: node.mutation_scope,
|
|
157
|
+
});
|
|
158
|
+
const allocB = await agentB.workspaceManager.allocate({
|
|
159
|
+
work_item_id: nodeId, attempt_number: 1, strategy: node.workspace_strategy, mutation_scope: node.mutation_scope,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
appendEvent(projectRoot, sessionId, {
|
|
163
|
+
type: 'AttemptStarted',
|
|
164
|
+
run_id: runId,
|
|
165
|
+
work_item_id: nodeId,
|
|
166
|
+
payload: { mode: 'competitive', agents: [agentA.id, agentB.id] },
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Race both agents — first success wins
|
|
170
|
+
const [resultA, resultB] = await Promise.all([
|
|
171
|
+
agentA.executor.execute(node, allocA, runId, 1).catch((e) => ({
|
|
172
|
+
success: false, failure_class: 'env' as const, evidence: [], output: String(e),
|
|
173
|
+
})),
|
|
174
|
+
agentB.executor.execute(node, allocB, runId, 1).catch((e) => ({
|
|
175
|
+
success: false, failure_class: 'env' as const, evidence: [], output: String(e),
|
|
176
|
+
})),
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
// Clean up both workspaces
|
|
180
|
+
await Promise.all([
|
|
181
|
+
agentA.workspaceManager.dispose(allocA.workspace_id).catch(() => {}),
|
|
182
|
+
agentB.workspaceManager.dispose(allocB.workspace_id).catch(() => {}),
|
|
183
|
+
]);
|
|
184
|
+
|
|
185
|
+
// Pick winner: prefer success; if both succeed, prefer A (primary agent)
|
|
186
|
+
const winner = resultA.success ? resultA : resultB.success ? resultB : resultA;
|
|
187
|
+
|
|
188
|
+
if (winner.success) {
|
|
189
|
+
appendEvent(projectRoot, sessionId, { type: 'WorkItemCompleted', run_id: runId, work_item_id: nodeId, payload: { mode: 'competitive' } });
|
|
190
|
+
} else {
|
|
191
|
+
appendEvent(projectRoot, sessionId, { type: 'WorkItemBlocked', run_id: runId, work_item_id: nodeId, payload: { mode: 'competitive', failure_class: winner.failure_class } });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return winner;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
export class MultiAgentCoordinator {
|
|
200
|
+
async run(graph: ExecutionGraph, opts: CoordinationOptions): Promise<CoordinationResult> {
|
|
201
|
+
switch (opts.mode) {
|
|
202
|
+
case 'parallel': return runParallel(graph, opts);
|
|
203
|
+
case 'competitive': return runCompetitive(graph, opts);
|
|
204
|
+
case 'cooperative':
|
|
205
|
+
// Cooperative mode: planner (agent[0]) prepares context,
|
|
206
|
+
// executor (agent[1]) implements. For R3, delegate to sequential parallel.
|
|
207
|
+
return runParallel(graph, { ...opts, mode: 'parallel' });
|
|
208
|
+
default:
|
|
209
|
+
throw new Error(`Unknown coordination mode: ${opts.mode}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
function subGraphFor(graph: ExecutionGraph, nodeIds: string[]): ExecutionGraph {
|
|
217
|
+
const ids = new Set(nodeIds);
|
|
218
|
+
const nodes = new Map([...graph.nodes].filter(([id]) => ids.has(id)));
|
|
219
|
+
const edges = graph.edges.filter((e) => ids.has(e.from) && ids.has(e.to));
|
|
220
|
+
const waves = graph.waves.map((w) => ({
|
|
221
|
+
wave_number: w.wave_number,
|
|
222
|
+
node_ids: w.node_ids.filter((id) => ids.has(id)),
|
|
223
|
+
})).filter((w) => w.node_ids.length > 0);
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
nodes,
|
|
227
|
+
edges,
|
|
228
|
+
waves,
|
|
229
|
+
metadata: { ...graph.metadata, node_count: nodes.size, wave_count: waves.length },
|
|
230
|
+
};
|
|
231
|
+
}
|