oxe-cc 1.4.1 → 1.5.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/.cursor/commands/oxe-verify-audit.md +46 -0
- package/.cursor/commands/oxe-workflow-authoring.md +47 -0
- package/.github/prompts/oxe-compact.prompt.md +1 -1
- package/.github/prompts/oxe-plan-agent.prompt.md +1 -0
- package/.github/prompts/oxe-verify-audit.prompt.md +46 -0
- package/.github/prompts/oxe-workflow-authoring.prompt.md +47 -0
- package/.github/workflows/ci.yml +1 -0
- package/.github/workflows/release.yml +1 -0
- package/AGENTS.md +3 -1
- package/CHANGELOG.md +25 -0
- package/QUICKSTART.md +99 -0
- package/README.md +19 -10
- package/bin/lib/oxe-install-resolve.cjs +10 -0
- package/bin/lib/oxe-operational.cjs +34 -28
- package/bin/lib/oxe-project-health.cjs +38 -6
- package/bin/lib/oxe-release.cjs +423 -0
- package/bin/oxe-cc.js +389 -294
- package/commands/oxe/verify-audit.md +50 -0
- package/commands/oxe/workflow-authoring.md +50 -0
- package/docs/INCIDENT-PLAYBOOK.md +181 -0
- package/docs/RELEASE-READINESS.md +46 -0
- package/docs/ROLES.md +129 -0
- package/docs/RUNTIME-SMOKE-MATRIX.md +128 -0
- package/docs/TEAM-ADOPTION.md +153 -0
- package/docs/WALKTHROUGH.md +241 -0
- package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +28 -0
- package/lib/runtime/scheduler/multi-agent-coordinator.js +152 -26
- package/lib/sdk/README.md +2 -0
- package/lib/sdk/index.cjs +22 -8
- package/lib/sdk/index.d.ts +60 -16
- package/oxe/templates/config.template.json +1 -0
- package/package.json +28 -20
- package/packages/runtime/package.json +1 -1
- package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +357 -193
- package/vscode-extension/oxe-agents-1.5.0.vsix +0 -0
- package/vscode-extension/package.json +1 -1
|
@@ -3,10 +3,11 @@ import path from 'path';
|
|
|
3
3
|
import { appendEvent } from '../events/bus';
|
|
4
4
|
import type { ExecutionGraph, GraphNode } from '../compiler/graph-compiler';
|
|
5
5
|
import type { WorkspaceManager } from '../workspace/workspace-manager';
|
|
6
|
-
import type { TaskExecutor, TaskResult, SchedulerContext } from './scheduler';
|
|
7
|
-
import { Scheduler } from './scheduler';
|
|
8
|
-
import { buildHandoff } from './agent-roles';
|
|
9
|
-
import type { CooperativeHandoff } from './agent-roles';
|
|
6
|
+
import type { TaskExecutor, TaskResult, SchedulerContext, RunResult } from './scheduler';
|
|
7
|
+
import { Scheduler } from './scheduler';
|
|
8
|
+
import { buildHandoff } from './agent-roles';
|
|
9
|
+
import type { CooperativeHandoff } from './agent-roles';
|
|
10
|
+
import { AgentRegistry } from './agent-registry';
|
|
10
11
|
|
|
11
12
|
export type CoordinationMode = 'parallel' | 'competitive' | 'cooperative';
|
|
12
13
|
|
|
@@ -18,14 +19,15 @@ export interface AgentSpec {
|
|
|
18
19
|
assignedTaskIds?: string[];
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export interface CoordinationOptions {
|
|
22
|
-
mode: CoordinationMode;
|
|
23
|
-
agents: AgentSpec[];
|
|
24
|
-
projectRoot: string;
|
|
25
|
-
sessionId: string | null;
|
|
26
|
-
runId: string;
|
|
27
|
-
onEvent?: SchedulerContext['onEvent'];
|
|
28
|
-
|
|
22
|
+
export interface CoordinationOptions {
|
|
23
|
+
mode: CoordinationMode;
|
|
24
|
+
agents: AgentSpec[];
|
|
25
|
+
projectRoot: string;
|
|
26
|
+
sessionId: string | null;
|
|
27
|
+
runId: string;
|
|
28
|
+
onEvent?: SchedulerContext['onEvent'];
|
|
29
|
+
heartbeatTimeoutMs?: number;
|
|
30
|
+
}
|
|
29
31
|
|
|
30
32
|
export interface ArbitrationRecord {
|
|
31
33
|
work_item_id: string;
|
|
@@ -43,38 +45,60 @@ export interface MultiAgentOwnership {
|
|
|
43
45
|
owner_agent_id: string;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
export interface MultiAgentStatusSnapshot {
|
|
47
|
-
run_id: string;
|
|
48
|
-
mode: CoordinationMode;
|
|
49
|
-
workspace_isolation_required: 'isolated';
|
|
50
|
-
workspace_isolation_enforced: boolean;
|
|
48
|
+
export interface MultiAgentStatusSnapshot {
|
|
49
|
+
run_id: string;
|
|
50
|
+
mode: CoordinationMode;
|
|
51
|
+
workspace_isolation_required: 'isolated';
|
|
52
|
+
workspace_isolation_enforced: boolean;
|
|
51
53
|
agent_count: number;
|
|
52
54
|
ownership: MultiAgentOwnership[];
|
|
53
55
|
completed: string[];
|
|
54
56
|
failed: string[];
|
|
55
57
|
blocked: string[];
|
|
56
|
-
agent_results: Array<{
|
|
57
|
-
agent_id: string;
|
|
58
|
-
isolation_level: 'shared' | 'isolated';
|
|
59
|
-
assigned_task_ids: string[];
|
|
60
|
-
completed: string[];
|
|
61
|
-
failed: string[];
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
58
|
+
agent_results: Array<{
|
|
59
|
+
agent_id: string;
|
|
60
|
+
isolation_level: 'shared' | 'isolated';
|
|
61
|
+
assigned_task_ids: string[];
|
|
62
|
+
completed: string[];
|
|
63
|
+
failed: string[];
|
|
64
|
+
timed_out: boolean;
|
|
65
|
+
reassigned_task_ids: string[];
|
|
66
|
+
}>;
|
|
67
|
+
orphan_reassignments: Array<{ from_agent_id: string; to_agent_id: string; work_item_ids: string[] }>;
|
|
68
|
+
timed_out_agents: Array<{ agent_id: string; work_item_ids: string[]; detected_at: string }>;
|
|
69
|
+
updated_at: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface MultiAgentOperationalSummary {
|
|
73
|
+
run_id: string;
|
|
74
|
+
mode: CoordinationMode;
|
|
75
|
+
workspace_isolation_enforced: boolean;
|
|
76
|
+
agent_count: number;
|
|
77
|
+
completed_count: number;
|
|
78
|
+
failed_count: number;
|
|
79
|
+
blocked_count: number;
|
|
80
|
+
ownership_count: number;
|
|
81
|
+
handoff_count: number;
|
|
82
|
+
arbitration_count: number;
|
|
83
|
+
orphan_reassignment_count: number;
|
|
84
|
+
timeout_count: number;
|
|
85
|
+
participating_agents: string[];
|
|
86
|
+
health: 'healthy' | 'degraded';
|
|
87
|
+
updated_at: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface CoordinationResult {
|
|
91
|
+
mode: CoordinationMode;
|
|
92
|
+
run_id: string;
|
|
93
|
+
completed: string[];
|
|
94
|
+
failed: string[];
|
|
95
|
+
blocked: string[];
|
|
96
|
+
agent_results: Array<{ agent_id: string; completed: string[]; failed: string[] }>;
|
|
97
|
+
handoffs?: CooperativeHandoff[];
|
|
98
|
+
arbitration_results?: ArbitrationRecord[];
|
|
99
|
+
state?: MultiAgentStatusSnapshot;
|
|
100
|
+
summary?: MultiAgentOperationalSummary;
|
|
101
|
+
}
|
|
78
102
|
|
|
79
103
|
function ensureRunDir(projectRoot: string, runId: string): string {
|
|
80
104
|
const dir = path.join(projectRoot, '.oxe', 'runs', runId);
|
|
@@ -82,18 +106,36 @@ function ensureRunDir(projectRoot: string, runId: string): string {
|
|
|
82
106
|
return dir;
|
|
83
107
|
}
|
|
84
108
|
|
|
85
|
-
function persistMultiAgentArtifacts(
|
|
86
|
-
projectRoot: string,
|
|
87
|
-
runId: string,
|
|
88
|
-
state: MultiAgentStatusSnapshot,
|
|
89
|
-
handoffs: CooperativeHandoff[] = [],
|
|
90
|
-
arbitrationResults: ArbitrationRecord[] = []
|
|
91
|
-
): void {
|
|
92
|
-
const runDir = ensureRunDir(projectRoot, runId);
|
|
93
|
-
fs.writeFileSync(path.join(runDir, 'multi-agent-state.json'), JSON.stringify(state, null, 2), 'utf8');
|
|
94
|
-
fs.writeFileSync(path.join(runDir, 'handoffs.json'), JSON.stringify(handoffs, null, 2), 'utf8');
|
|
95
|
-
fs.writeFileSync(path.join(runDir, 'arbitration-results.json'), JSON.stringify(arbitrationResults, null, 2), 'utf8');
|
|
96
|
-
|
|
109
|
+
function persistMultiAgentArtifacts(
|
|
110
|
+
projectRoot: string,
|
|
111
|
+
runId: string,
|
|
112
|
+
state: MultiAgentStatusSnapshot,
|
|
113
|
+
handoffs: CooperativeHandoff[] = [],
|
|
114
|
+
arbitrationResults: ArbitrationRecord[] = []
|
|
115
|
+
): void {
|
|
116
|
+
const runDir = ensureRunDir(projectRoot, runId);
|
|
117
|
+
fs.writeFileSync(path.join(runDir, 'multi-agent-state.json'), JSON.stringify(state, null, 2), 'utf8');
|
|
118
|
+
fs.writeFileSync(path.join(runDir, 'handoffs.json'), JSON.stringify(handoffs, null, 2), 'utf8');
|
|
119
|
+
fs.writeFileSync(path.join(runDir, 'arbitration-results.json'), JSON.stringify(arbitrationResults, null, 2), 'utf8');
|
|
120
|
+
const summary: MultiAgentOperationalSummary = {
|
|
121
|
+
run_id: state.run_id,
|
|
122
|
+
mode: state.mode,
|
|
123
|
+
workspace_isolation_enforced: state.workspace_isolation_enforced,
|
|
124
|
+
agent_count: state.agent_count,
|
|
125
|
+
completed_count: state.completed.length,
|
|
126
|
+
failed_count: state.failed.length,
|
|
127
|
+
blocked_count: state.blocked.length,
|
|
128
|
+
ownership_count: state.ownership.length,
|
|
129
|
+
handoff_count: handoffs.length,
|
|
130
|
+
arbitration_count: arbitrationResults.length,
|
|
131
|
+
orphan_reassignment_count: state.orphan_reassignments.length,
|
|
132
|
+
timeout_count: state.timed_out_agents.length,
|
|
133
|
+
participating_agents: state.agent_results.map((entry) => entry.agent_id),
|
|
134
|
+
health: state.timed_out_agents.length > 0 || state.failed.length > 0 ? 'degraded' : 'healthy',
|
|
135
|
+
updated_at: state.updated_at,
|
|
136
|
+
};
|
|
137
|
+
fs.writeFileSync(path.join(runDir, 'multi-agent-summary.json'), JSON.stringify(summary, null, 2), 'utf8');
|
|
138
|
+
}
|
|
97
139
|
|
|
98
140
|
function ensureIsolatedAgents(agents: AgentSpec[]): void {
|
|
99
141
|
const shared = agents.filter((agent) => agent.workspaceManager.isolation_level !== 'isolated');
|
|
@@ -115,20 +157,21 @@ function buildOwnership(agents: AgentSpec[], partitions: string[][]): MultiAgent
|
|
|
115
157
|
return ownership;
|
|
116
158
|
}
|
|
117
159
|
|
|
118
|
-
function makeState(
|
|
119
|
-
mode: CoordinationMode,
|
|
120
|
-
runId: string,
|
|
121
|
-
agents: AgentSpec[],
|
|
122
|
-
partitions: string[][],
|
|
123
|
-
agentResults: Array<{ agent_id: string; completed: string[]; failed: string[] }>,
|
|
124
|
-
completed: string[],
|
|
125
|
-
failed: string[],
|
|
126
|
-
blocked: string[],
|
|
127
|
-
orphanReassignments: Array<{ from_agent_id: string; to_agent_id: string; work_item_ids: string[] }
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
160
|
+
function makeState(
|
|
161
|
+
mode: CoordinationMode,
|
|
162
|
+
runId: string,
|
|
163
|
+
agents: AgentSpec[],
|
|
164
|
+
partitions: string[][],
|
|
165
|
+
agentResults: Array<{ agent_id: string; completed: string[]; failed: string[]; timed_out: boolean; reassigned_task_ids: string[] }>,
|
|
166
|
+
completed: string[],
|
|
167
|
+
failed: string[],
|
|
168
|
+
blocked: string[],
|
|
169
|
+
orphanReassignments: Array<{ from_agent_id: string; to_agent_id: string; work_item_ids: string[] }>,
|
|
170
|
+
timedOutAgents: Array<{ agent_id: string; work_item_ids: string[]; detected_at: string }>
|
|
171
|
+
): MultiAgentStatusSnapshot {
|
|
172
|
+
return {
|
|
173
|
+
run_id: runId,
|
|
174
|
+
mode,
|
|
132
175
|
workspace_isolation_required: 'isolated',
|
|
133
176
|
workspace_isolation_enforced: true,
|
|
134
177
|
agent_count: agents.length,
|
|
@@ -140,84 +183,187 @@ function makeState(
|
|
|
140
183
|
const result = agentResults.find((entry) => entry.agent_id === agent.id);
|
|
141
184
|
return {
|
|
142
185
|
agent_id: agent.id,
|
|
143
|
-
isolation_level: agent.workspaceManager.isolation_level,
|
|
144
|
-
assigned_task_ids: partitions[idx] ?? agent.assignedTaskIds ?? [],
|
|
145
|
-
completed: result?.completed ?? [],
|
|
146
|
-
failed: result?.failed ?? [],
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
186
|
+
isolation_level: agent.workspaceManager.isolation_level,
|
|
187
|
+
assigned_task_ids: partitions[idx] ?? agent.assignedTaskIds ?? [],
|
|
188
|
+
completed: result?.completed ?? [],
|
|
189
|
+
failed: result?.failed ?? [],
|
|
190
|
+
timed_out: Boolean(result?.timed_out),
|
|
191
|
+
reassigned_task_ids: result?.reassigned_task_ids ?? [],
|
|
192
|
+
};
|
|
193
|
+
}),
|
|
194
|
+
orphan_reassignments: orphanReassignments,
|
|
195
|
+
timed_out_agents: timedOutAgents,
|
|
196
|
+
updated_at: new Date().toISOString(),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function runGraphForAgent(
|
|
201
|
+
graph: ExecutionGraph,
|
|
202
|
+
nodeIds: string[],
|
|
203
|
+
agent: AgentSpec,
|
|
204
|
+
idx: number,
|
|
205
|
+
opts: CoordinationOptions,
|
|
206
|
+
heartbeatTimeoutMs: number | null
|
|
207
|
+
): Promise<{
|
|
208
|
+
agent_id: string;
|
|
209
|
+
completed: string[];
|
|
210
|
+
failed: string[];
|
|
211
|
+
timed_out: boolean;
|
|
212
|
+
assigned_task_ids: string[];
|
|
213
|
+
reassigned_task_ids: string[];
|
|
214
|
+
}> {
|
|
215
|
+
const subGraph = subGraphFor(graph, nodeIds);
|
|
216
|
+
if (subGraph.nodes.size === 0) {
|
|
217
|
+
return {
|
|
218
|
+
agent_id: agent.id,
|
|
219
|
+
completed: [],
|
|
220
|
+
failed: [],
|
|
221
|
+
timed_out: false,
|
|
222
|
+
assigned_task_ids: nodeIds,
|
|
223
|
+
reassigned_task_ids: [],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const ctx: SchedulerContext = {
|
|
227
|
+
projectRoot: opts.projectRoot,
|
|
228
|
+
sessionId: opts.sessionId,
|
|
229
|
+
runId: `${opts.runId}-agent${idx}`,
|
|
230
|
+
executor: agent.executor,
|
|
231
|
+
workspaceManager: agent.workspaceManager,
|
|
232
|
+
onEvent: opts.onEvent,
|
|
233
|
+
};
|
|
234
|
+
const scheduler = new Scheduler();
|
|
235
|
+
const work = scheduler.run(subGraph, ctx);
|
|
236
|
+
if (!heartbeatTimeoutMs || heartbeatTimeoutMs <= 0) {
|
|
237
|
+
const result = await work;
|
|
238
|
+
return {
|
|
239
|
+
agent_id: agent.id,
|
|
240
|
+
completed: result.completed,
|
|
241
|
+
failed: result.failed,
|
|
242
|
+
timed_out: false,
|
|
243
|
+
assigned_task_ids: nodeIds,
|
|
244
|
+
reassigned_task_ids: [],
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
let timer: NodeJS.Timeout | null = null;
|
|
248
|
+
const raced: { type: 'result'; result: RunResult } | { type: 'timeout' } = await Promise.race([
|
|
249
|
+
work.then((result) => ({ type: 'result' as const, result })),
|
|
250
|
+
new Promise<{ type: 'timeout' }>((resolve) => {
|
|
251
|
+
timer = setTimeout(() => resolve({ type: 'timeout' }), heartbeatTimeoutMs);
|
|
252
|
+
}),
|
|
253
|
+
]);
|
|
254
|
+
if (timer) clearTimeout(timer);
|
|
255
|
+
if (raced && raced.type === 'timeout') {
|
|
256
|
+
return {
|
|
257
|
+
agent_id: agent.id,
|
|
258
|
+
completed: [],
|
|
259
|
+
failed: [],
|
|
260
|
+
timed_out: true,
|
|
261
|
+
assigned_task_ids: nodeIds,
|
|
262
|
+
reassigned_task_ids: [],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const result = raced.result;
|
|
266
|
+
return {
|
|
267
|
+
agent_id: agent.id,
|
|
268
|
+
completed: result.completed,
|
|
269
|
+
failed: result.failed,
|
|
270
|
+
timed_out: false,
|
|
271
|
+
assigned_task_ids: nodeIds,
|
|
272
|
+
reassigned_task_ids: [],
|
|
273
|
+
};
|
|
274
|
+
}
|
|
153
275
|
|
|
154
276
|
// ─── Parallel mode ───────────────────────────────────────────────────────────
|
|
155
277
|
|
|
156
|
-
async function runParallel(
|
|
157
|
-
graph: ExecutionGraph,
|
|
158
|
-
opts: CoordinationOptions
|
|
159
|
-
): Promise<CoordinationResult> {
|
|
160
|
-
const { agents, projectRoot, sessionId, runId } = opts;
|
|
161
|
-
ensureIsolatedAgents(agents);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
allIds.
|
|
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
|
-
const
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
278
|
+
async function runParallel(
|
|
279
|
+
graph: ExecutionGraph,
|
|
280
|
+
opts: CoordinationOptions
|
|
281
|
+
): Promise<CoordinationResult> {
|
|
282
|
+
const { agents, projectRoot, sessionId, runId } = opts;
|
|
283
|
+
ensureIsolatedAgents(agents);
|
|
284
|
+
const heartbeatTimeoutMs = opts.heartbeatTimeoutMs ?? null;
|
|
285
|
+
|
|
286
|
+
const partitions = agents.map((agent) => [...(agent.assignedTaskIds ?? [])]);
|
|
287
|
+
if (partitions.every((partition) => partition.length === 0)) {
|
|
288
|
+
const allIds = [...graph.nodes.keys()];
|
|
289
|
+
allIds.forEach((id, index) => {
|
|
290
|
+
partitions[index % agents.length].push(id);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
const registry = new AgentRegistry(heartbeatTimeoutMs == null ? 30_000 : heartbeatTimeoutMs);
|
|
294
|
+
agents.forEach((agent, idx) => {
|
|
295
|
+
registry.register(agent.id, agent.executor, agent.workspaceManager, partitions[idx] ?? []);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
appendEvent(projectRoot, sessionId, {
|
|
299
|
+
type: 'RunStarted',
|
|
300
|
+
run_id: runId,
|
|
301
|
+
payload: { mode: 'parallel', agent_count: agents.length, isolation_level: 'isolated' },
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const initialResults = await Promise.all(
|
|
305
|
+
agents.map(async (agent, idx) => {
|
|
306
|
+
registry.beat(agent.id, partitions[idx][0] || null);
|
|
307
|
+
const result = await runGraphForAgent(graph, partitions[idx], agent, idx, opts, heartbeatTimeoutMs);
|
|
308
|
+
registry.setStatus(agent.id, result.timed_out ? 'timeout' : 'idle');
|
|
309
|
+
return result;
|
|
310
|
+
})
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
const timedOutAgents = [];
|
|
314
|
+
const blocked: string[] = [];
|
|
315
|
+
const orphanReassignments: Array<{ from_agent_id: string; to_agent_id: string; work_item_ids: string[] }> = [];
|
|
316
|
+
const agentResults = initialResults.map((entry) => ({
|
|
317
|
+
...entry,
|
|
318
|
+
reassigned_task_ids: entry.reassigned_task_ids || [],
|
|
319
|
+
}));
|
|
320
|
+
const liveAgents = agentResults.filter((entry) => !entry.timed_out);
|
|
321
|
+
for (const timedOut of agentResults.filter((entry) => entry.timed_out)) {
|
|
322
|
+
timedOutAgents.push({
|
|
323
|
+
agent_id: timedOut.agent_id,
|
|
324
|
+
work_item_ids: timedOut.assigned_task_ids,
|
|
325
|
+
detected_at: new Date().toISOString(),
|
|
326
|
+
});
|
|
327
|
+
const fallback = liveAgents.find((entry) => entry.agent_id !== timedOut.agent_id);
|
|
328
|
+
if (!fallback || timedOut.assigned_task_ids.length === 0) continue;
|
|
329
|
+
const fallbackIdx = agents.findIndex((agent) => agent.id === fallback.agent_id);
|
|
330
|
+
const timeoutIdx = agents.findIndex((agent) => agent.id === timedOut.agent_id);
|
|
331
|
+
const rerun = await runGraphForAgent(graph, timedOut.assigned_task_ids, agents[fallbackIdx], fallbackIdx, opts, null);
|
|
332
|
+
fallback.completed.push(...rerun.completed);
|
|
333
|
+
fallback.failed.push(...rerun.failed);
|
|
334
|
+
fallback.reassigned_task_ids.push(...timedOut.assigned_task_ids);
|
|
335
|
+
partitions[fallbackIdx] = [...partitions[fallbackIdx], ...timedOut.assigned_task_ids];
|
|
336
|
+
partitions[timeoutIdx] = [];
|
|
337
|
+
orphanReassignments.push({
|
|
338
|
+
from_agent_id: timedOut.agent_id,
|
|
339
|
+
to_agent_id: fallback.agent_id,
|
|
340
|
+
work_item_ids: timedOut.assigned_task_ids,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const completed = Array.from(new Set(agentResults.flatMap((result) => result.completed)));
|
|
345
|
+
const failed = Array.from(new Set(agentResults.flatMap((result) => result.failed)));
|
|
346
|
+
const state = makeState('parallel', runId, agents, partitions, agentResults, completed, failed, blocked, orphanReassignments, timedOutAgents);
|
|
347
|
+
persistMultiAgentArtifacts(projectRoot, runId, state);
|
|
348
|
+
|
|
349
|
+
appendEvent(projectRoot, sessionId, {
|
|
350
|
+
type: 'RunCompleted',
|
|
206
351
|
run_id: runId,
|
|
207
352
|
payload: { mode: 'parallel', completed: completed.length, failed: failed.length },
|
|
208
353
|
});
|
|
209
354
|
|
|
210
|
-
return {
|
|
211
|
-
mode: 'parallel',
|
|
212
|
-
run_id: runId,
|
|
213
|
-
completed,
|
|
214
|
-
failed,
|
|
355
|
+
return {
|
|
356
|
+
mode: 'parallel',
|
|
357
|
+
run_id: runId,
|
|
358
|
+
completed,
|
|
359
|
+
failed,
|
|
215
360
|
blocked,
|
|
216
|
-
agent_results: agentResults,
|
|
217
|
-
arbitration_results: [],
|
|
218
|
-
state,
|
|
219
|
-
|
|
220
|
-
}
|
|
361
|
+
agent_results: agentResults,
|
|
362
|
+
arbitration_results: [],
|
|
363
|
+
state,
|
|
364
|
+
summary: loadMultiAgentSummary(projectRoot, runId) || undefined,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
221
367
|
|
|
222
368
|
// ─── Competitive mode ────────────────────────────────────────────────────────
|
|
223
369
|
|
|
@@ -255,21 +401,22 @@ async function runCompetitive(
|
|
|
255
401
|
}
|
|
256
402
|
|
|
257
403
|
const partitions = [Array.from(graph.nodes.keys()), Array.from(graph.nodes.keys())];
|
|
258
|
-
const state = makeState(
|
|
259
|
-
'competitive',
|
|
260
|
-
runId,
|
|
261
|
-
opts.agents,
|
|
262
|
-
partitions,
|
|
263
|
-
[
|
|
264
|
-
{ agent_id: agentA.id, completed, failed },
|
|
265
|
-
{ agent_id: agentB.id, completed: [], failed: [] },
|
|
266
|
-
],
|
|
267
|
-
completed,
|
|
268
|
-
failed,
|
|
269
|
-
blocked,
|
|
270
|
-
[]
|
|
271
|
-
|
|
272
|
-
|
|
404
|
+
const state = makeState(
|
|
405
|
+
'competitive',
|
|
406
|
+
runId,
|
|
407
|
+
opts.agents,
|
|
408
|
+
partitions,
|
|
409
|
+
[
|
|
410
|
+
{ agent_id: agentA.id, completed, failed, timed_out: false, reassigned_task_ids: [] },
|
|
411
|
+
{ agent_id: agentB.id, completed: [], failed: [], timed_out: false, reassigned_task_ids: [] },
|
|
412
|
+
],
|
|
413
|
+
completed,
|
|
414
|
+
failed,
|
|
415
|
+
blocked,
|
|
416
|
+
[],
|
|
417
|
+
[]
|
|
418
|
+
);
|
|
419
|
+
persistMultiAgentArtifacts(projectRoot, runId, state, [], arbitrationResults);
|
|
273
420
|
|
|
274
421
|
appendEvent(projectRoot, sessionId, {
|
|
275
422
|
type: 'RunCompleted',
|
|
@@ -283,14 +430,15 @@ async function runCompetitive(
|
|
|
283
430
|
completed,
|
|
284
431
|
failed,
|
|
285
432
|
blocked,
|
|
286
|
-
agent_results: [
|
|
287
|
-
{ agent_id: agentA.id, completed, failed },
|
|
288
|
-
{ agent_id: agentB.id, completed: [], failed: [] },
|
|
289
|
-
],
|
|
290
|
-
arbitration_results: arbitrationResults,
|
|
291
|
-
state,
|
|
292
|
-
|
|
293
|
-
}
|
|
433
|
+
agent_results: [
|
|
434
|
+
{ agent_id: agentA.id, completed, failed },
|
|
435
|
+
{ agent_id: agentB.id, completed: [], failed: [] },
|
|
436
|
+
],
|
|
437
|
+
arbitration_results: arbitrationResults,
|
|
438
|
+
state,
|
|
439
|
+
summary: loadMultiAgentSummary(projectRoot, runId) || undefined,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
294
442
|
|
|
295
443
|
async function competeTwoAgents(
|
|
296
444
|
nodeId: string,
|
|
@@ -433,21 +581,22 @@ async function runCooperative(
|
|
|
433
581
|
}
|
|
434
582
|
|
|
435
583
|
const partitions = [Array.from(graph.nodes.keys()), Array.from(graph.nodes.keys())];
|
|
436
|
-
const state = makeState(
|
|
437
|
-
'cooperative',
|
|
438
|
-
runId,
|
|
439
|
-
opts.agents,
|
|
440
|
-
partitions,
|
|
441
|
-
[
|
|
442
|
-
{ agent_id: planner.id, completed: [], failed: [] },
|
|
443
|
-
{ agent_id: executor.id, completed, failed },
|
|
444
|
-
],
|
|
445
|
-
completed,
|
|
446
|
-
failed,
|
|
447
|
-
blocked,
|
|
448
|
-
[]
|
|
449
|
-
|
|
450
|
-
|
|
584
|
+
const state = makeState(
|
|
585
|
+
'cooperative',
|
|
586
|
+
runId,
|
|
587
|
+
opts.agents,
|
|
588
|
+
partitions,
|
|
589
|
+
[
|
|
590
|
+
{ agent_id: planner.id, completed: [], failed: [], timed_out: false, reassigned_task_ids: [] },
|
|
591
|
+
{ agent_id: executor.id, completed, failed, timed_out: false, reassigned_task_ids: [] },
|
|
592
|
+
],
|
|
593
|
+
completed,
|
|
594
|
+
failed,
|
|
595
|
+
blocked,
|
|
596
|
+
[],
|
|
597
|
+
[]
|
|
598
|
+
);
|
|
599
|
+
persistMultiAgentArtifacts(projectRoot, runId, state, handoffs, []);
|
|
451
600
|
|
|
452
601
|
appendEvent(projectRoot, sessionId, {
|
|
453
602
|
type: 'RunCompleted',
|
|
@@ -461,15 +610,16 @@ async function runCooperative(
|
|
|
461
610
|
completed,
|
|
462
611
|
failed,
|
|
463
612
|
blocked,
|
|
464
|
-
agent_results: [
|
|
465
|
-
{ agent_id: planner.id, completed: [], failed: [] },
|
|
466
|
-
{ agent_id: executor.id, completed, failed },
|
|
467
|
-
],
|
|
468
|
-
handoffs,
|
|
469
|
-
arbitration_results: [],
|
|
470
|
-
state,
|
|
471
|
-
|
|
472
|
-
}
|
|
613
|
+
agent_results: [
|
|
614
|
+
{ agent_id: planner.id, completed: [], failed: [] },
|
|
615
|
+
{ agent_id: executor.id, completed, failed },
|
|
616
|
+
],
|
|
617
|
+
handoffs,
|
|
618
|
+
arbitration_results: [],
|
|
619
|
+
state,
|
|
620
|
+
summary: loadMultiAgentSummary(projectRoot, runId) || undefined,
|
|
621
|
+
};
|
|
622
|
+
}
|
|
473
623
|
|
|
474
624
|
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
475
625
|
|
|
@@ -485,19 +635,33 @@ export class MultiAgentCoordinator {
|
|
|
485
635
|
}
|
|
486
636
|
}
|
|
487
637
|
|
|
488
|
-
export function multiAgentStatePath(projectRoot: string, runId: string): string {
|
|
489
|
-
return path.join(projectRoot, '.oxe', 'runs', runId, 'multi-agent-state.json');
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
export function
|
|
493
|
-
|
|
494
|
-
|
|
638
|
+
export function multiAgentStatePath(projectRoot: string, runId: string): string {
|
|
639
|
+
return path.join(projectRoot, '.oxe', 'runs', runId, 'multi-agent-state.json');
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
export function multiAgentSummaryPath(projectRoot: string, runId: string): string {
|
|
643
|
+
return path.join(projectRoot, '.oxe', 'runs', runId, 'multi-agent-summary.json');
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export function loadMultiAgentState(projectRoot: string, runId: string): MultiAgentStatusSnapshot | null {
|
|
647
|
+
const statePath = multiAgentStatePath(projectRoot, runId);
|
|
648
|
+
if (!fs.existsSync(statePath)) return null;
|
|
495
649
|
try {
|
|
496
650
|
return JSON.parse(fs.readFileSync(statePath, 'utf8')) as MultiAgentStatusSnapshot;
|
|
497
|
-
} catch {
|
|
498
|
-
return null;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
651
|
+
} catch {
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
export function loadMultiAgentSummary(projectRoot: string, runId: string): MultiAgentOperationalSummary | null {
|
|
657
|
+
const summaryPath = multiAgentSummaryPath(projectRoot, runId);
|
|
658
|
+
if (!fs.existsSync(summaryPath)) return null;
|
|
659
|
+
try {
|
|
660
|
+
return JSON.parse(fs.readFileSync(summaryPath, 'utf8')) as MultiAgentOperationalSummary;
|
|
661
|
+
} catch {
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
501
665
|
|
|
502
666
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
503
667
|
|
|
Binary file
|