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.
Files changed (36) hide show
  1. package/.cursor/commands/oxe-verify-audit.md +46 -0
  2. package/.cursor/commands/oxe-workflow-authoring.md +47 -0
  3. package/.github/prompts/oxe-compact.prompt.md +1 -1
  4. package/.github/prompts/oxe-plan-agent.prompt.md +1 -0
  5. package/.github/prompts/oxe-verify-audit.prompt.md +46 -0
  6. package/.github/prompts/oxe-workflow-authoring.prompt.md +47 -0
  7. package/.github/workflows/ci.yml +1 -0
  8. package/.github/workflows/release.yml +1 -0
  9. package/AGENTS.md +3 -1
  10. package/CHANGELOG.md +25 -0
  11. package/QUICKSTART.md +99 -0
  12. package/README.md +19 -10
  13. package/bin/lib/oxe-install-resolve.cjs +10 -0
  14. package/bin/lib/oxe-operational.cjs +34 -28
  15. package/bin/lib/oxe-project-health.cjs +38 -6
  16. package/bin/lib/oxe-release.cjs +423 -0
  17. package/bin/oxe-cc.js +389 -294
  18. package/commands/oxe/verify-audit.md +50 -0
  19. package/commands/oxe/workflow-authoring.md +50 -0
  20. package/docs/INCIDENT-PLAYBOOK.md +181 -0
  21. package/docs/RELEASE-READINESS.md +46 -0
  22. package/docs/ROLES.md +129 -0
  23. package/docs/RUNTIME-SMOKE-MATRIX.md +128 -0
  24. package/docs/TEAM-ADOPTION.md +153 -0
  25. package/docs/WALKTHROUGH.md +241 -0
  26. package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +28 -0
  27. package/lib/runtime/scheduler/multi-agent-coordinator.js +152 -26
  28. package/lib/sdk/README.md +2 -0
  29. package/lib/sdk/index.cjs +22 -8
  30. package/lib/sdk/index.d.ts +60 -16
  31. package/oxe/templates/config.template.json +1 -0
  32. package/package.json +28 -20
  33. package/packages/runtime/package.json +1 -1
  34. package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +357 -193
  35. package/vscode-extension/oxe-agents-1.5.0.vsix +0 -0
  36. 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
- orphan_reassignments: Array<{ from_agent_id: string; to_agent_id: string; work_item_ids: string[] }>;
64
- updated_at: string;
65
- }
66
-
67
- export interface CoordinationResult {
68
- mode: CoordinationMode;
69
- run_id: string;
70
- completed: string[];
71
- failed: string[];
72
- blocked: string[];
73
- agent_results: Array<{ agent_id: string; completed: string[]; failed: string[] }>;
74
- handoffs?: CooperativeHandoff[];
75
- arbitration_results?: ArbitrationRecord[];
76
- state?: MultiAgentStatusSnapshot;
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
- ): MultiAgentStatusSnapshot {
129
- return {
130
- run_id: runId,
131
- mode,
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
- orphan_reassignments: orphanReassignments,
150
- updated_at: new Date().toISOString(),
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
- const partitions = agents.map((agent) => [...(agent.assignedTaskIds ?? [])]);
164
- if (partitions.every((partition) => partition.length === 0)) {
165
- const allIds = [...graph.nodes.keys()];
166
- allIds.forEach((id, index) => {
167
- partitions[index % agents.length].push(id);
168
- });
169
- }
170
-
171
- appendEvent(projectRoot, sessionId, {
172
- type: 'RunStarted',
173
- run_id: runId,
174
- payload: { mode: 'parallel', agent_count: agents.length, isolation_level: 'isolated' },
175
- });
176
-
177
- const agentResults = await Promise.all(
178
- agents.map(async (agent, idx) => {
179
- const subGraph = subGraphFor(graph, partitions[idx]);
180
- if (subGraph.nodes.size === 0) {
181
- return { agent_id: agent.id, completed: [], failed: [] };
182
- }
183
- const ctx: SchedulerContext = {
184
- projectRoot,
185
- sessionId,
186
- runId: `${runId}-agent${idx}`,
187
- executor: agent.executor,
188
- workspaceManager: agent.workspaceManager,
189
- onEvent: opts.onEvent,
190
- };
191
- const scheduler = new Scheduler();
192
- const result = await scheduler.run(subGraph, ctx);
193
- return { agent_id: agent.id, completed: result.completed, failed: result.failed };
194
- })
195
- );
196
-
197
- const completed = agentResults.flatMap((result) => result.completed);
198
- const failed = agentResults.flatMap((result) => result.failed);
199
- const blocked: string[] = [];
200
- const orphanReassignments: Array<{ from_agent_id: string; to_agent_id: string; work_item_ids: string[] }> = [];
201
- const state = makeState('parallel', runId, agents, partitions, agentResults, completed, failed, blocked, orphanReassignments);
202
- persistMultiAgentArtifacts(projectRoot, runId, state);
203
-
204
- appendEvent(projectRoot, sessionId, {
205
- type: 'RunCompleted',
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
- persistMultiAgentArtifacts(projectRoot, runId, state, [], arbitrationResults);
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
- persistMultiAgentArtifacts(projectRoot, runId, state, handoffs, []);
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 loadMultiAgentState(projectRoot: string, runId: string): MultiAgentStatusSnapshot | null {
493
- const statePath = multiAgentStatePath(projectRoot, runId);
494
- if (!fs.existsSync(statePath)) return null;
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
 
@@ -2,7 +2,7 @@
2
2
  "name": "oxe-agents",
3
3
  "displayName": "OXE Agents",
4
4
  "description": "Agentes OXE para GitHub Copilot Chat — cada fase do ciclo como um @agente no VS Code",
5
- "version": "1.4.0",
5
+ "version": "1.5.0",
6
6
  "publisher": "oxe-cc",
7
7
  "license": "MIT",
8
8
  "engines": {