astrabot 0.1.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 +411 -0
- package/ai/ai.config.ts +27 -0
- package/ai/auto-retry.ts +117 -0
- package/ai/config-loader.ts +132 -0
- package/ai/index.ts +4 -0
- package/ai/retry-prompt.ts +30 -0
- package/bin/astra +2 -0
- package/core/retry/error-classifier.ts +208 -0
- package/core/retry/index.ts +29 -0
- package/core/retry/retry-config.ts +142 -0
- package/core/retry/retry-engine.ts +215 -0
- package/game/index.html +573 -0
- package/game/neon-breaker.html +1037 -0
- package/index.ts +140 -0
- package/modes/agent/action-tracker.ts +47 -0
- package/modes/agent/agent-tools.ts +338 -0
- package/modes/agent/approval.ts +184 -0
- package/modes/agent/diff-view.ts +34 -0
- package/modes/agent/orchestrator.ts +234 -0
- package/modes/agent/tool-executor.ts +993 -0
- package/modes/agent/types.ts +68 -0
- package/modes/ask/orchestrator.ts +230 -0
- package/modes/auto.ts +88 -0
- package/modes/cli.ts +43 -0
- package/modes/multi/agent-pool-manager.ts +337 -0
- package/modes/multi/examples.ts +441 -0
- package/modes/multi/message-broker.ts +179 -0
- package/modes/multi/multi-agent-orchestrator.ts +891 -0
- package/modes/multi/orchestrator.ts +414 -0
- package/modes/multi/types.ts +245 -0
- package/modes/multi/workflow-builder.ts +569 -0
- package/modes/plan/orchestrator.ts +198 -0
- package/modes/plan/planner.ts +121 -0
- package/modes/plan/selection.ts +43 -0
- package/modes/plan/types.ts +13 -0
- package/modes/plan/web-tools.ts +132 -0
- package/modes/setup.ts +210 -0
- package/package.json +62 -0
- package/session/index.ts +45 -0
- package/session/session-context.ts +188 -0
- package/session/session-manager.ts +374 -0
- package/session/session-tools.ts +109 -0
- package/session/store.ts +278 -0
- package/tsconfig.json +30 -0
- package/tui/spinner.ts +182 -0
- package/tui/terminal-md.ts +17 -0
- package/tui/wakeup.ts +231 -0
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentConfig,
|
|
3
|
+
AgentContext,
|
|
4
|
+
AgentExecutionResult,
|
|
5
|
+
AgentMessage,
|
|
6
|
+
MultiAgentWorkflow,
|
|
7
|
+
OrchestratorState,
|
|
8
|
+
OrchestratorEvent,
|
|
9
|
+
OrchestratorEventListener,
|
|
10
|
+
} from "./types";
|
|
11
|
+
import { AgentPoolManager } from "./agent-pool-manager";
|
|
12
|
+
import { MessageBroker } from "./message-broker";
|
|
13
|
+
import { ActionTracker } from "../agent/action-tracker";
|
|
14
|
+
import { ToolExecutor } from "../agent/tool-executor";
|
|
15
|
+
import { defaultAgentConfig, type AgentConfig as SingleAgentConfig } from "../agent/types";
|
|
16
|
+
import { createAgentTools } from "../agent/agent-tools";
|
|
17
|
+
import { ToolLoopAgent, stepCountIs } from "ai";
|
|
18
|
+
import { getAgentModel } from "../../ai";
|
|
19
|
+
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
20
|
+
import { getEnv, getMultiRetryConfig } from "../../ai/config-loader";
|
|
21
|
+
import { withRetry } from "../../core/retry";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Logger utility with consistent formatting
|
|
25
|
+
*/
|
|
26
|
+
const orchestrationLogger = {
|
|
27
|
+
info: (agentId: string, msg: string, details?: Record<string, unknown>) => {
|
|
28
|
+
const agentLabel = agentId.padEnd(20);
|
|
29
|
+
const detailsStr = details ? ` | ${JSON.stringify(details)}` : "";
|
|
30
|
+
console.log(`[${agentLabel}] ${msg}${detailsStr}`);
|
|
31
|
+
},
|
|
32
|
+
strategy: (strategy: string, msg: string) => {
|
|
33
|
+
console.log(`[STRATEGY:${strategy}] ${msg}`);
|
|
34
|
+
},
|
|
35
|
+
error: (agentId: string, msg: string, error?: Error) => {
|
|
36
|
+
const errorMsg = error ? `: ${error.message}` : "";
|
|
37
|
+
console.error(`[${agentId}] ✗ ${msg}${errorMsg}`);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Main orchestrator for coordinating multiple agents.
|
|
43
|
+
*
|
|
44
|
+
* Supports strategies:
|
|
45
|
+
* - Sequential: agents work one after another
|
|
46
|
+
* - Parallel: agents work simultaneously with concurrency limits
|
|
47
|
+
* - Hierarchical: coordinator delegates to specialist agents
|
|
48
|
+
* - Collaborative: agents communicate and negotiate
|
|
49
|
+
* - DAG: agents run when dependencies are satisfied
|
|
50
|
+
*/
|
|
51
|
+
export class MultiAgentOrchestrator {
|
|
52
|
+
private workflow: MultiAgentWorkflow;
|
|
53
|
+
private poolManager: AgentPoolManager;
|
|
54
|
+
private messageBroker: MessageBroker;
|
|
55
|
+
private state: OrchestratorState;
|
|
56
|
+
private trackers: Map<string, ActionTracker> = new Map();
|
|
57
|
+
private executors: Map<string, ToolExecutor> = new Map();
|
|
58
|
+
private agents: Map<string, ToolLoopAgent> = new Map();
|
|
59
|
+
private sharedTracker: ActionTracker;
|
|
60
|
+
private eventListeners: OrchestratorEventListener[] = [];
|
|
61
|
+
|
|
62
|
+
constructor(workflow: MultiAgentWorkflow) {
|
|
63
|
+
this.workflow = workflow;
|
|
64
|
+
this.poolManager = new AgentPoolManager();
|
|
65
|
+
this.messageBroker = new MessageBroker();
|
|
66
|
+
this.sharedTracker = new ActionTracker();
|
|
67
|
+
|
|
68
|
+
orchestrationLogger.info(
|
|
69
|
+
"ORCHESTRATOR",
|
|
70
|
+
"Initializing",
|
|
71
|
+
{ workflowId: workflow.id, agents: workflow.agents.length, strategy: workflow.strategy.type },
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Register all agents
|
|
75
|
+
for (const agentConfig of workflow.agents) {
|
|
76
|
+
this.poolManager.registerAgent(agentConfig);
|
|
77
|
+
orchestrationLogger.info(agentConfig.id, `Registered (${agentConfig.role})`, {
|
|
78
|
+
tools: agentConfig.tools.length,
|
|
79
|
+
maxSteps: agentConfig.maxSteps,
|
|
80
|
+
dependsOn: agentConfig.dependsOn?.join(","),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Initialize state
|
|
85
|
+
this.state = {
|
|
86
|
+
workflowId: workflow.id,
|
|
87
|
+
status: "pending",
|
|
88
|
+
pool: this.poolManager.getPool(),
|
|
89
|
+
sharedContext: {
|
|
90
|
+
goal: workflow.goal,
|
|
91
|
+
conversationHistory: [],
|
|
92
|
+
sharedState: new Map(),
|
|
93
|
+
metadata: {
|
|
94
|
+
startTime: new Date(),
|
|
95
|
+
currentStep: 0,
|
|
96
|
+
completedTasks: [],
|
|
97
|
+
failedTasks: [],
|
|
98
|
+
retryCount: 0,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
timeline: [],
|
|
102
|
+
startTime: new Date(),
|
|
103
|
+
currentCoordinator: this._findCoordinator(),
|
|
104
|
+
events: [],
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
orchestrationLogger.info("ORCHESTRATOR", "Initialization complete", {
|
|
108
|
+
coordinator: this.state.currentCoordinator,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ─── Event System ─────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Register a listener for orchestrator events
|
|
116
|
+
*/
|
|
117
|
+
onEvent(listener: OrchestratorEventListener): () => void {
|
|
118
|
+
this.eventListeners.push(listener);
|
|
119
|
+
return () => {
|
|
120
|
+
const idx = this.eventListeners.indexOf(listener);
|
|
121
|
+
if (idx >= 0) this.eventListeners.splice(idx, 1);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private _emitEvent(event: OrchestratorEvent): void {
|
|
126
|
+
this.state.events.push(event);
|
|
127
|
+
for (const listener of this.eventListeners) {
|
|
128
|
+
try {
|
|
129
|
+
listener(event);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error("Error in event listener:", err);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── Private Helpers ──────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
private _findCoordinator(): string {
|
|
139
|
+
const coordinators = this.workflow.agents.filter((a) => a.role === "coordinator");
|
|
140
|
+
if (coordinators.length > 0) return coordinators[0]!.id;
|
|
141
|
+
return this.workflow.agents[0]!.id;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private _getModelForAgent(agentConfig: AgentConfig) {
|
|
145
|
+
if (agentConfig.model) {
|
|
146
|
+
orchestrationLogger.info(agentConfig.id, `Custom model: ${agentConfig.model}`);
|
|
147
|
+
const apiKey = getEnv("OPENROUTER_API_KEY");
|
|
148
|
+
if (!apiKey) {
|
|
149
|
+
throw new Error(`OPENROUTER_API_KEY not set for agent ${agentConfig.id}`);
|
|
150
|
+
}
|
|
151
|
+
const provider = createOpenRouter({ apiKey });
|
|
152
|
+
return provider(agentConfig.model);
|
|
153
|
+
}
|
|
154
|
+
return getAgentModel();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private _buildSystemPrompt(agentConfig: AgentConfig): string {
|
|
158
|
+
const parts: string[] = [];
|
|
159
|
+
|
|
160
|
+
if (agentConfig.systemPrompt) {
|
|
161
|
+
parts.push(agentConfig.systemPrompt);
|
|
162
|
+
} else {
|
|
163
|
+
const rolePrompts: Record<AgentConfig["role"], string> = {
|
|
164
|
+
researcher:
|
|
165
|
+
"You are Astra, an AI-native development CLI companion tool built to help the user navigate, analyze, and build within their workspace codebase. If the user asks who you are, what your name is, or what model you are running on, you must always identify yourself exclusively as Astra. Do not mention your underlying model architecture or provider. You are a Research Agent. Gather information, analyze codebases, and provide detailed findings. You have read-only file access.",
|
|
166
|
+
implementer:
|
|
167
|
+
"You are Astra, an AI-native development CLI companion tool built to help the user navigate, analyze, and build within their workspace codebase. If the user asks who you are, what your name is, or what model you are running on, you must always identify yourself exclusively as Astra. Do not mention your underlying model architecture or provider. You are an Implementation Agent. Write code, modify files, and implement features. File changes are staged until approval.",
|
|
168
|
+
reviewer:
|
|
169
|
+
"You are Astra, an AI-native development CLI companion tool built to help the user navigate, analyze, and build within their workspace codebase. If the user asks who you are, what your name is, or what model you are running on, you must always identify yourself exclusively as Astra. Do not mention your underlying model architecture or provider. You are a Review Agent. Review code for quality, correctness, style, and potential issues. You can run tests and linting.",
|
|
170
|
+
coordinator:
|
|
171
|
+
"You are Astra, an AI-native development CLI companion tool built to help the user navigate, analyze, and build within their workspace codebase. If the user asks who you are, what your name is, or what model you are running on, you must always identify yourself exclusively as Astra. Do not mention your underlying model architecture or provider. You are a Coordinator Agent. Plan tasks, delegate work to other agents, and synthesize results. You manage the workflow.",
|
|
172
|
+
custom: `You are Astra, an AI-native development CLI companion tool built to help the user navigate, analyze, and build within their workspace codebase. If the user asks who you are, what your name is, or what model you are running on, you must always identify yourself exclusively as Astra. Do not mention your underlying model architecture or provider. You are a Custom Agent: ${agentConfig.name}. ${agentConfig.description}`,
|
|
173
|
+
};
|
|
174
|
+
parts.push(rolePrompts[agentConfig.role]);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (agentConfig.specializations?.length) {
|
|
178
|
+
parts.push(`\nSpecializations: ${agentConfig.specializations.join(", ")}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
parts.push("\nAll file mutations are staged until approval. Describe what you're doing clearly.");
|
|
182
|
+
return parts.join("\n");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private _filterToolsForAgent(
|
|
186
|
+
allTools: ReturnType<typeof createAgentTools>,
|
|
187
|
+
agentConfig: AgentConfig,
|
|
188
|
+
) {
|
|
189
|
+
const filtered: Record<string, any> = {};
|
|
190
|
+
for (const toolName of agentConfig.tools) {
|
|
191
|
+
if (toolName in allTools) {
|
|
192
|
+
filtered[toolName] = (allTools as Record<string, any>)[toolName];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
orchestrationLogger.info(agentConfig.id, `Filtered tools: ${Object.keys(filtered).length} available`);
|
|
196
|
+
return filtered;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private _createExecutorForAgent(agentConfig: AgentConfig) {
|
|
200
|
+
const config: SingleAgentConfig = defaultAgentConfig();
|
|
201
|
+
const tracker = new ActionTracker();
|
|
202
|
+
|
|
203
|
+
// Configure permissions by role
|
|
204
|
+
switch (agentConfig.role) {
|
|
205
|
+
case "researcher":
|
|
206
|
+
config.tools.allowShellExecution = false;
|
|
207
|
+
config.tools.allowFileModification = false;
|
|
208
|
+
config.tools.allowFileCreation = false;
|
|
209
|
+
config.tools.allowFolderCreation = false;
|
|
210
|
+
break;
|
|
211
|
+
case "implementer":
|
|
212
|
+
config.tools.allowShellExecution = true;
|
|
213
|
+
config.tools.allowFileModification = true;
|
|
214
|
+
config.tools.allowFileCreation = true;
|
|
215
|
+
config.tools.allowFolderCreation = true;
|
|
216
|
+
break;
|
|
217
|
+
case "reviewer":
|
|
218
|
+
config.tools.allowShellExecution = true;
|
|
219
|
+
config.tools.allowFileModification = false;
|
|
220
|
+
config.tools.allowFileCreation = false;
|
|
221
|
+
config.tools.allowFolderCreation = false;
|
|
222
|
+
break;
|
|
223
|
+
case "coordinator":
|
|
224
|
+
config.tools.allowShellExecution = false;
|
|
225
|
+
config.tools.allowFileModification = false;
|
|
226
|
+
config.tools.allowFileCreation = false;
|
|
227
|
+
config.tools.allowFolderCreation = false;
|
|
228
|
+
break;
|
|
229
|
+
case "custom":
|
|
230
|
+
const toolSet = new Set(agentConfig.tools);
|
|
231
|
+
config.tools.allowFileCreation = toolSet.has("create_file") || toolSet.has("create_folder");
|
|
232
|
+
config.tools.allowFileModification = toolSet.has("modify_file") || toolSet.has("replace_in_file");
|
|
233
|
+
config.tools.allowFolderCreation = toolSet.has("create_folder");
|
|
234
|
+
config.tools.allowShellExecution =
|
|
235
|
+
toolSet.has("run_command") || toolSet.has("run_tests") || toolSet.has("run_test_file");
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const executor = new ToolExecutor(tracker, config);
|
|
240
|
+
return { tracker, executor };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private _initializeAgent(agentConfig: AgentConfig) {
|
|
244
|
+
orchestrationLogger.info(agentConfig.id, "Initializing");
|
|
245
|
+
|
|
246
|
+
const { tracker, executor } = this._createExecutorForAgent(agentConfig);
|
|
247
|
+
this.trackers.set(agentConfig.id, tracker);
|
|
248
|
+
this.executors.set(agentConfig.id, executor);
|
|
249
|
+
|
|
250
|
+
const allTools = createAgentTools(executor);
|
|
251
|
+
const tools = this._filterToolsForAgent(allTools, agentConfig);
|
|
252
|
+
const model = this._getModelForAgent(agentConfig);
|
|
253
|
+
|
|
254
|
+
const agent = new ToolLoopAgent({
|
|
255
|
+
model,
|
|
256
|
+
stopWhen: stepCountIs(agentConfig.maxSteps),
|
|
257
|
+
tools,
|
|
258
|
+
instructions: [
|
|
259
|
+
this._buildSystemPrompt(agentConfig),
|
|
260
|
+
`Workspace root: ${defaultAgentConfig().codebasePath}`,
|
|
261
|
+
].join("\n"),
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
this.agents.set(agentConfig.id, agent);
|
|
265
|
+
orchestrationLogger.info(agentConfig.id, "Initialized");
|
|
266
|
+
return executor;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private _buildAgentPrompt(agentConfig: AgentConfig): string {
|
|
270
|
+
const parts: string[] = [];
|
|
271
|
+
parts.push(`Goal: ${this.workflow.goal}`);
|
|
272
|
+
parts.push(`\nYour role: ${agentConfig.description}`);
|
|
273
|
+
parts.push(`Your role type: ${agentConfig.role}`);
|
|
274
|
+
|
|
275
|
+
const recentMessages = this.state.sharedContext.conversationHistory.slice(-20);
|
|
276
|
+
if (recentMessages.length > 0) {
|
|
277
|
+
parts.push("\n--- Previous Agent Outputs ---");
|
|
278
|
+
for (const msg of recentMessages) {
|
|
279
|
+
parts.push(`[${msg.fromAgentId}]: ${msg.content.slice(0, 500)}`);
|
|
280
|
+
}
|
|
281
|
+
parts.push("--- End Previous Outputs ---\n");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const queuedMessages = this.poolManager.flushMessageQueue(agentConfig.id);
|
|
285
|
+
if (queuedMessages.length > 0) {
|
|
286
|
+
parts.push("\n--- Messages for you ---");
|
|
287
|
+
for (const msg of queuedMessages) {
|
|
288
|
+
const direction = msg.toAgentId ? `(from ${msg.fromAgentId})` : `(broadcast from ${msg.fromAgentId})`;
|
|
289
|
+
parts.push(`${direction}: ${msg.content.slice(0, 500)}`);
|
|
290
|
+
}
|
|
291
|
+
parts.push("--- End Messages ---\n");
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return parts.join("\n");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ─── Execution ────────────────────────────────────────────────────────────
|
|
298
|
+
|
|
299
|
+
async execute(): Promise<OrchestratorState> {
|
|
300
|
+
this.state.status = "running";
|
|
301
|
+
this._emitEvent({
|
|
302
|
+
type: "workflow:start",
|
|
303
|
+
timestamp: new Date(),
|
|
304
|
+
payload: { strategy: this.workflow.strategy.type },
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
orchestrationLogger.strategy(this.workflow.strategy.type, "Starting execution");
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
switch (this.workflow.strategy.type) {
|
|
311
|
+
case "sequential":
|
|
312
|
+
await this._executeSequential();
|
|
313
|
+
break;
|
|
314
|
+
case "parallel":
|
|
315
|
+
await this._executeParallel();
|
|
316
|
+
break;
|
|
317
|
+
case "hierarchical":
|
|
318
|
+
await this._executeHierarchical();
|
|
319
|
+
break;
|
|
320
|
+
case "collaborative":
|
|
321
|
+
await this._executeCollaborative();
|
|
322
|
+
break;
|
|
323
|
+
case "dag":
|
|
324
|
+
await this._executeDAG();
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
this.state.status = "completed";
|
|
329
|
+
this._emitEvent({
|
|
330
|
+
type: "workflow:complete",
|
|
331
|
+
timestamp: new Date(),
|
|
332
|
+
payload: { duration: this.state.endTime?.getTime()! - this.state.startTime.getTime() },
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
orchestrationLogger.strategy(this.workflow.strategy.type, "Completed successfully");
|
|
336
|
+
} catch (error) {
|
|
337
|
+
this.state.status = "failed";
|
|
338
|
+
this._emitEvent({
|
|
339
|
+
type: "workflow:failed",
|
|
340
|
+
timestamp: new Date(),
|
|
341
|
+
payload: { error: error instanceof Error ? error.message : String(error) },
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
orchestrationLogger.error("ORCHESTRATOR", "Execution failed", error instanceof Error ? error : undefined);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
this.state.endTime = new Date();
|
|
348
|
+
return this.state;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private async _executeAgent(agentConfig: AgentConfig): Promise<AgentExecutionResult> {
|
|
352
|
+
const startMs = Date.now();
|
|
353
|
+
orchestrationLogger.info(agentConfig.id, "Starting execution");
|
|
354
|
+
this.poolManager.activateAgent(agentConfig.id);
|
|
355
|
+
|
|
356
|
+
this._emitEvent({
|
|
357
|
+
type: "agent:start",
|
|
358
|
+
timestamp: new Date(),
|
|
359
|
+
agentId: agentConfig.id,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const executor = this._initializeAgent(agentConfig);
|
|
363
|
+
const agent = this.agents.get(agentConfig.id)!;
|
|
364
|
+
const agentInstance = this.poolManager.getAgent(agentConfig.id)!;
|
|
365
|
+
const tracker = this.trackers.get(agentConfig.id)!;
|
|
366
|
+
const executedTools: string[] = [];
|
|
367
|
+
const messagesSent: AgentMessage[] = [];
|
|
368
|
+
|
|
369
|
+
// Determine retry configuration from workflow strategy and environment
|
|
370
|
+
const multiRetryConfig = getMultiRetryConfig();
|
|
371
|
+
const strategyMaxRetries = this.workflow.strategy.config.retryOnFailure
|
|
372
|
+
? (this.workflow.strategy.config.maxRetries ?? 1)
|
|
373
|
+
: 0;
|
|
374
|
+
const effectiveMaxRetries = multiRetryConfig.enabled
|
|
375
|
+
? Math.max(strategyMaxRetries, multiRetryConfig.maxRetries)
|
|
376
|
+
: strategyMaxRetries;
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
const prompt = this._buildAgentPrompt(agentConfig);
|
|
380
|
+
orchestrationLogger.info(agentConfig.id, "Generating model response");
|
|
381
|
+
|
|
382
|
+
// Wrap the agent.generate call with automatic retry logic
|
|
383
|
+
const { result, stats } = await withRetry(
|
|
384
|
+
() => agent.generate({
|
|
385
|
+
prompt,
|
|
386
|
+
onStepFinish: ({ toolCalls }) => {
|
|
387
|
+
for (const tc of toolCalls) {
|
|
388
|
+
const toolName = String(tc.toolName);
|
|
389
|
+
if (!executedTools.includes(toolName)) {
|
|
390
|
+
executedTools.push(toolName);
|
|
391
|
+
orchestrationLogger.info(agentConfig.id, `Executed tool: ${toolName}`);
|
|
392
|
+
}
|
|
393
|
+
agentInstance.context.metadata.currentStep++;
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
}),
|
|
397
|
+
{
|
|
398
|
+
maxRetries: effectiveMaxRetries,
|
|
399
|
+
baseDelayMs: 1000,
|
|
400
|
+
maxDelayMs: 30000,
|
|
401
|
+
backoffMultiplier: multiRetryConfig.backoffMultiplier,
|
|
402
|
+
jitter: true,
|
|
403
|
+
maxJitterMs: 1000,
|
|
404
|
+
onRetry: (attempt, error, delayMs) => {
|
|
405
|
+
orchestrationLogger.info(
|
|
406
|
+
agentConfig.id,
|
|
407
|
+
`Retry ${attempt}/${effectiveMaxRetries} after ${error.category} error, waiting ${Math.round(delayMs / 1000)}s`,
|
|
408
|
+
);
|
|
409
|
+
this._emitEvent({
|
|
410
|
+
type: "agent:retry",
|
|
411
|
+
timestamp: new Date(),
|
|
412
|
+
agentId: agentConfig.id,
|
|
413
|
+
payload: { attempt, error: error.category, delayMs },
|
|
414
|
+
});
|
|
415
|
+
},
|
|
416
|
+
onExhausted: (error, totalAttempts) => {
|
|
417
|
+
orchestrationLogger.error(
|
|
418
|
+
agentConfig.id,
|
|
419
|
+
`All ${totalAttempts} attempts failed (${error.category})`,
|
|
420
|
+
);
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
const durationMs = Date.now() - startMs;
|
|
426
|
+
const executionResult: AgentExecutionResult = {
|
|
427
|
+
agentId: agentConfig.id,
|
|
428
|
+
success: true,
|
|
429
|
+
output: result.text,
|
|
430
|
+
executedTools,
|
|
431
|
+
messagesReceived: [...agentInstance.context.conversationHistory],
|
|
432
|
+
messagesSent,
|
|
433
|
+
context: agentInstance.context,
|
|
434
|
+
durationMs,
|
|
435
|
+
attemptNumber: stats.totalAttempts,
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
this.poolManager.updateCompletion(agentConfig.id, 100);
|
|
439
|
+
this.poolManager.deactivateAgent(agentConfig.id);
|
|
440
|
+
|
|
441
|
+
this._emitEvent({
|
|
442
|
+
type: "agent:complete",
|
|
443
|
+
timestamp: new Date(),
|
|
444
|
+
agentId: agentConfig.id,
|
|
445
|
+
payload: { duration: durationMs, steps: agentInstance.context.metadata.currentStep },
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
orchestrationLogger.info(agentConfig.id, "Execution completed", {
|
|
449
|
+
tools: executedTools.length,
|
|
450
|
+
durationMs,
|
|
451
|
+
attempts: stats.totalAttempts,
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
return executionResult;
|
|
455
|
+
} catch (error) {
|
|
456
|
+
this.poolManager.markAgentFailed(agentConfig.id);
|
|
457
|
+
|
|
458
|
+
this._emitEvent({
|
|
459
|
+
type: "agent:failed",
|
|
460
|
+
timestamp: new Date(),
|
|
461
|
+
agentId: agentConfig.id,
|
|
462
|
+
payload: { error: error instanceof Error ? error.message : String(error) },
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
orchestrationLogger.error(
|
|
466
|
+
agentConfig.id,
|
|
467
|
+
"Execution failed (all retries exhausted)",
|
|
468
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
agentId: agentConfig.id,
|
|
473
|
+
success: false,
|
|
474
|
+
output: "",
|
|
475
|
+
executedTools,
|
|
476
|
+
messagesReceived: [],
|
|
477
|
+
messagesSent,
|
|
478
|
+
context: agentInstance.context,
|
|
479
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
480
|
+
durationMs: Date.now() - startMs,
|
|
481
|
+
attemptNumber: 1 + (agentInstance.context.metadata.retryCount ?? 0),
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private async _executeAgentWithMessaging(agentConfig: AgentConfig): Promise<AgentExecutionResult> {
|
|
487
|
+
const result = await this._executeAgent(agentConfig);
|
|
488
|
+
|
|
489
|
+
if (result.success) {
|
|
490
|
+
const msg = this.messageBroker.broadcast({
|
|
491
|
+
fromAgentId: agentConfig.id,
|
|
492
|
+
type: "result",
|
|
493
|
+
content: result.output,
|
|
494
|
+
requiresResponse: false,
|
|
495
|
+
});
|
|
496
|
+
this.state.sharedContext.conversationHistory.push(msg);
|
|
497
|
+
orchestrationLogger.info(agentConfig.id, `Broadcast to ${this.workflow.agents.length - 1} agents`);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return result;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
private async _executeSequential(): Promise<void> {
|
|
504
|
+
orchestrationLogger.strategy("sequential", `Executing ${this.workflow.agents.length} agents`);
|
|
505
|
+
|
|
506
|
+
for (let i = 0; i < this.workflow.agents.length; i++) {
|
|
507
|
+
const agentConfig = this.workflow.agents[i]!;
|
|
508
|
+
orchestrationLogger.strategy("sequential", `[${i + 1}/${this.workflow.agents.length}] ${agentConfig.id}`);
|
|
509
|
+
|
|
510
|
+
const result = await this._executeAgent(agentConfig);
|
|
511
|
+
this.state.timeline.push(result);
|
|
512
|
+
|
|
513
|
+
if (result.success) {
|
|
514
|
+
this._updateSharedContext(agentConfig, result);
|
|
515
|
+
} else {
|
|
516
|
+
this._handleAgentFailure(agentConfig, result);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
private async _executeParallel(): Promise<void> {
|
|
522
|
+
const maxConcurrent = this.workflow.strategy.config.maxConcurrentAgents || this.workflow.agents.length;
|
|
523
|
+
const timeout = this.workflow.strategy.config.timeout || 30_000;
|
|
524
|
+
|
|
525
|
+
orchestrationLogger.strategy("parallel", `maxConcurrent=${maxConcurrent}, timeout=${timeout}ms`);
|
|
526
|
+
|
|
527
|
+
const executeWithTimeout = async (agentConfig: AgentConfig) => {
|
|
528
|
+
return Promise.race([
|
|
529
|
+
this._executeAgent(agentConfig),
|
|
530
|
+
new Promise<AgentExecutionResult>((_, reject) =>
|
|
531
|
+
setTimeout(() => {
|
|
532
|
+
orchestrationLogger.error(agentConfig.id, `Timeout after ${timeout}ms`);
|
|
533
|
+
reject(new Error(`Timeout after ${timeout}ms`));
|
|
534
|
+
}, timeout),
|
|
535
|
+
),
|
|
536
|
+
]);
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
for (let i = 0; i < this.workflow.agents.length; i += maxConcurrent) {
|
|
540
|
+
const batch = this.workflow.agents.slice(i, i + maxConcurrent);
|
|
541
|
+
const batchNum = Math.floor(i / maxConcurrent) + 1;
|
|
542
|
+
const totalBatches = Math.ceil(this.workflow.agents.length / maxConcurrent);
|
|
543
|
+
|
|
544
|
+
orchestrationLogger.strategy("parallel", `Batch ${batchNum}/${totalBatches} (${batch.length} agents)`);
|
|
545
|
+
|
|
546
|
+
try {
|
|
547
|
+
const results = await Promise.all(batch.map(executeWithTimeout));
|
|
548
|
+
for (const result of results) {
|
|
549
|
+
this.state.timeline.push(result);
|
|
550
|
+
const agent = this.workflow.agents.find((a) => a.id === result.agentId);
|
|
551
|
+
if (result.success && agent) {
|
|
552
|
+
this._updateSharedContext(agent, result);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
} catch (error) {
|
|
556
|
+
orchestrationLogger.error("ORCHESTRATOR", "Batch execution error", error instanceof Error ? error : undefined);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
private async _executeHierarchical(): Promise<void> {
|
|
562
|
+
const coordinatorId = this.state.currentCoordinator;
|
|
563
|
+
const coordinator = this.workflow.agents.find((a) => a.id === coordinatorId);
|
|
564
|
+
|
|
565
|
+
if (!coordinator) {
|
|
566
|
+
orchestrationLogger.error("ORCHESTRATOR", "Coordinator not found");
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
orchestrationLogger.strategy("hierarchical", `Coordinator: ${coordinatorId}`);
|
|
571
|
+
|
|
572
|
+
const coordinationResult = await this._executeAgent(coordinator);
|
|
573
|
+
this.state.timeline.push(coordinationResult);
|
|
574
|
+
if (coordinationResult.success) {
|
|
575
|
+
this._updateSharedContext(coordinator, coordinationResult);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const specialists = this.workflow.agents.filter((a) => a.id !== coordinatorId);
|
|
579
|
+
orchestrationLogger.strategy("hierarchical", `Executing ${specialists.length} specialists`);
|
|
580
|
+
|
|
581
|
+
for (const specialist of specialists) {
|
|
582
|
+
const result = await this._executeAgent(specialist);
|
|
583
|
+
this.state.timeline.push(result);
|
|
584
|
+
if (result.success) {
|
|
585
|
+
this._updateSharedContext(specialist, result);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
private async _executeCollaborative(): Promise<void> {
|
|
591
|
+
orchestrationLogger.strategy("collaborative", `Setting up ${this.workflow.agents.length} agents`);
|
|
592
|
+
|
|
593
|
+
for (const agentConfig of this.workflow.agents) {
|
|
594
|
+
this.messageBroker.subscribe(agentConfig.id, (message) => {
|
|
595
|
+
this.poolManager.queueMessageFor(agentConfig.id, message);
|
|
596
|
+
orchestrationLogger.info(agentConfig.id, `Message queued from ${message.fromAgentId}`);
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
for (let turn = 0; turn < this.workflow.agents.length; turn++) {
|
|
601
|
+
const agentConfig = this.workflow.agents[turn]!;
|
|
602
|
+
orchestrationLogger.strategy("collaborative", `Turn ${turn + 1}: ${agentConfig.id}`);
|
|
603
|
+
|
|
604
|
+
const result = await this._executeAgentWithMessaging(agentConfig);
|
|
605
|
+
this.state.timeline.push(result);
|
|
606
|
+
if (result.success) {
|
|
607
|
+
this._updateSharedContext(agentConfig, result);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* DAG (Directed Acyclic Graph) execution: agents run when all dependencies are satisfied.
|
|
614
|
+
*/
|
|
615
|
+
private async _executeDAG(): Promise<void> {
|
|
616
|
+
orchestrationLogger.strategy("dag", "Executing with dependency-aware scheduling");
|
|
617
|
+
const maxConcurrent = this.workflow.strategy.config.maxConcurrentAgents || 4;
|
|
618
|
+
|
|
619
|
+
const pending = new Set(this.workflow.agents.map((a) => a.id));
|
|
620
|
+
const running = new Set<string>();
|
|
621
|
+
let batch: string[] = [];
|
|
622
|
+
|
|
623
|
+
while (pending.size > 0 || running.size > 0) {
|
|
624
|
+
// Find ready agents
|
|
625
|
+
const ready = this.poolManager.getReadyAgents();
|
|
626
|
+
for (const agent of ready) {
|
|
627
|
+
if (!pending.has(agent.config.id)) continue;
|
|
628
|
+
if (batch.length < maxConcurrent) {
|
|
629
|
+
batch.push(agent.config.id);
|
|
630
|
+
pending.delete(agent.config.id);
|
|
631
|
+
running.add(agent.config.id);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (batch.length === 0) {
|
|
636
|
+
// Nothing ready and nothing running
|
|
637
|
+
if (pending.size > 0) {
|
|
638
|
+
// ✅ FIXED: Handle deadlock by skipping blocked agents
|
|
639
|
+
const blocked = Array.from(pending);
|
|
640
|
+
orchestrationLogger.error(
|
|
641
|
+
"ORCHESTRATOR",
|
|
642
|
+
`Deadlock: marking ${blocked.length} blocked agents as skipped`,
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
for (const blockedId of blocked) {
|
|
646
|
+
const blockedAgent = this.workflow.agents.find((a) => a.id === blockedId);
|
|
647
|
+
if (!blockedAgent) continue;
|
|
648
|
+
|
|
649
|
+
this.poolManager.markAgentSkipped(blockedId);
|
|
650
|
+
this.state.sharedContext.metadata.failedTasks.push(
|
|
651
|
+
`${blockedId}: skipped (dependencies failed)`,
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
const emptyResult: AgentExecutionResult = {
|
|
655
|
+
agentId: blockedId,
|
|
656
|
+
success: false,
|
|
657
|
+
output: "Agent skipped due to failed dependencies",
|
|
658
|
+
executedTools: [],
|
|
659
|
+
messagesReceived: [],
|
|
660
|
+
messagesSent: [],
|
|
661
|
+
context: this.poolManager.getAgent(blockedId)!.context,
|
|
662
|
+
error: new Error("Dependencies failed"),
|
|
663
|
+
durationMs: 0,
|
|
664
|
+
attemptNumber: 1,
|
|
665
|
+
};
|
|
666
|
+
this.state.timeline.push(emptyResult);
|
|
667
|
+
|
|
668
|
+
this._emitEvent({
|
|
669
|
+
type: "agent:failed",
|
|
670
|
+
timestamp: new Date(),
|
|
671
|
+
agentId: blockedId,
|
|
672
|
+
payload: { reason: "dependencies_failed" },
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
pending.clear();
|
|
676
|
+
}
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Execute batch in parallel
|
|
681
|
+
orchestrationLogger.strategy("dag", `Executing batch: ${batch.join(", ")}`);
|
|
682
|
+
const promises = batch.map((id) => {
|
|
683
|
+
const cfg = this.workflow.agents.find((a) => a.id === id)!;
|
|
684
|
+
return this._executeAgent(cfg);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
const results = await Promise.allSettled(promises);
|
|
688
|
+
batch = [];
|
|
689
|
+
|
|
690
|
+
for (const settled of results) {
|
|
691
|
+
const result = settled.status === "fulfilled" ? settled.value : null;
|
|
692
|
+
if (!result) continue;
|
|
693
|
+
|
|
694
|
+
running.delete(result.agentId);
|
|
695
|
+
this.state.timeline.push(result);
|
|
696
|
+
|
|
697
|
+
const agent = this.workflow.agents.find((a) => a.id === result.agentId);
|
|
698
|
+
if (result.success && agent) {
|
|
699
|
+
this._updateSharedContext(agent, result);
|
|
700
|
+
} else if (agent) {
|
|
701
|
+
// ✅ FIXED: Pass result to failure handler
|
|
702
|
+
await this._handleAgentFailure(agent, result);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
private _updateSharedContext(agentConfig: AgentConfig, result: AgentExecutionResult): void {
|
|
709
|
+
const msg: AgentMessage = {
|
|
710
|
+
id: `ctx_${Date.now()}_${agentConfig.id}`,
|
|
711
|
+
fromAgentId: agentConfig.id,
|
|
712
|
+
type: "result",
|
|
713
|
+
content: result.output,
|
|
714
|
+
timestamp: new Date(),
|
|
715
|
+
requiresResponse: false,
|
|
716
|
+
context: { role: agentConfig.role, steps: result.context.metadata.currentStep },
|
|
717
|
+
};
|
|
718
|
+
this.state.sharedContext.conversationHistory.push(msg);
|
|
719
|
+
this.state.sharedContext.metadata.completedTasks.push(`${agentConfig.id}: ${agentConfig.description}`);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
private async _handleAgentFailure(
|
|
723
|
+
agentConfig: AgentConfig,
|
|
724
|
+
result: AgentExecutionResult,
|
|
725
|
+
): Promise<void> {
|
|
726
|
+
const failureMode = this.workflow.strategy.config.failureMode ?? "fail-fast";
|
|
727
|
+
const maxRetries = this.workflow.strategy.config.maxRetries ?? 1;
|
|
728
|
+
const shouldRetry =
|
|
729
|
+
this.workflow.strategy.config.retryOnFailure &&
|
|
730
|
+
result.context.metadata.retryCount < maxRetries;
|
|
731
|
+
|
|
732
|
+
// ✅ FIXED: Proper retry logic
|
|
733
|
+
if (shouldRetry) {
|
|
734
|
+
this.poolManager.markAgentRetrying(agentConfig.id);
|
|
735
|
+
this._emitEvent({
|
|
736
|
+
type: "agent:retry",
|
|
737
|
+
timestamp: new Date(),
|
|
738
|
+
agentId: agentConfig.id,
|
|
739
|
+
payload: { attempt: 1 + result.context.metadata.retryCount },
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
orchestrationLogger.info(
|
|
743
|
+
agentConfig.id,
|
|
744
|
+
`Retry ${1 + result.context.metadata.retryCount}/${maxRetries}`,
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
// Retry will happen in the main execution loop
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Agent has failed permanently
|
|
752
|
+
this.poolManager.markAgentFailed(agentConfig.id);
|
|
753
|
+
this.state.sharedContext.metadata.failedTasks.push(
|
|
754
|
+
`${agentConfig.id}: ${agentConfig.description}`,
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
// ✅ FIXED: Cascade skip to dependent agents
|
|
758
|
+
const dependentIds = this.workflow.agents
|
|
759
|
+
.filter((a) => a.dependsOn?.includes(agentConfig.id))
|
|
760
|
+
.map((a) => a.id);
|
|
761
|
+
|
|
762
|
+
for (const depId of dependentIds) {
|
|
763
|
+
const depAgent = this.poolManager.getAgent(depId);
|
|
764
|
+
if (depAgent && (depAgent.status === "pending")) {
|
|
765
|
+
this.poolManager.markAgentSkipped(depId);
|
|
766
|
+
this.state.sharedContext.metadata.failedTasks.push(
|
|
767
|
+
`${depId}: skipped (dependency ${agentConfig.id} failed)`,
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
const skipResult: AgentExecutionResult = {
|
|
771
|
+
agentId: depId,
|
|
772
|
+
success: false,
|
|
773
|
+
output: `Skipped because dependency ${agentConfig.id} failed`,
|
|
774
|
+
executedTools: [],
|
|
775
|
+
messagesReceived: [],
|
|
776
|
+
messagesSent: [],
|
|
777
|
+
context: depAgent.context,
|
|
778
|
+
error: new Error(`Dependency ${agentConfig.id} failed`),
|
|
779
|
+
durationMs: 0,
|
|
780
|
+
attemptNumber: 1,
|
|
781
|
+
};
|
|
782
|
+
this.state.timeline.push(skipResult);
|
|
783
|
+
|
|
784
|
+
this._emitEvent({
|
|
785
|
+
type: "agent:failed",
|
|
786
|
+
timestamp: new Date(),
|
|
787
|
+
agentId: depId,
|
|
788
|
+
payload: { reason: `dependency_${agentConfig.id}_failed` },
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
// Recursively skip agents depending on this one
|
|
792
|
+
const depAgentConfig = this.workflow.agents.find((a) => a.id === depId);
|
|
793
|
+
if (depAgentConfig) {
|
|
794
|
+
await this._handleAgentFailure(depAgentConfig, skipResult);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Handle based on failure mode
|
|
800
|
+
if (failureMode === "fail-fast") {
|
|
801
|
+
throw new Error(
|
|
802
|
+
`Agent ${agentConfig.id} failed: ${result.error?.message || "unknown error"}`,
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
// "continue" and "fail-at-end" just mark as failed and continue
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// ─── Public API ────────────────────────────────────────────────────────────
|
|
809
|
+
|
|
810
|
+
getAllTrackers(): Map<string, ActionTracker> {
|
|
811
|
+
return this.trackers;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
getSharedTracker(): ActionTracker {
|
|
815
|
+
return this.sharedTracker;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
getAllExecutors(): Map<string, ToolExecutor> {
|
|
819
|
+
return this.executors;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
getState(): OrchestratorState {
|
|
823
|
+
return this.state;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
getTimeline(): AgentExecutionResult[] {
|
|
827
|
+
return this.state.timeline;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
getMessageHistory(): AgentMessage[] {
|
|
831
|
+
return this.messageBroker.messageBuffer;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
getEvents(): OrchestratorEvent[] {
|
|
835
|
+
return this.state.events;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
pause(): void {
|
|
839
|
+
this.state.status = "paused";
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
resume(): void {
|
|
843
|
+
if (this.state.status === "paused") {
|
|
844
|
+
this.state.status = "running";
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
getSummary() {
|
|
849
|
+
const poolStats = this.poolManager.getStats();
|
|
850
|
+
return {
|
|
851
|
+
workflowId: this.state.workflowId,
|
|
852
|
+
status: this.state.status,
|
|
853
|
+
goal: this.workflow.goal,
|
|
854
|
+
strategy: this.workflow.strategy.type,
|
|
855
|
+
startTime: this.state.startTime,
|
|
856
|
+
endTime: this.state.endTime,
|
|
857
|
+
duration: this.state.endTime
|
|
858
|
+
? this.state.endTime.getTime() - this.state.startTime.getTime()
|
|
859
|
+
: null,
|
|
860
|
+
totalAgents: this.workflow.agents.length,
|
|
861
|
+
completedTasks: this.state.sharedContext.metadata.completedTasks.length,
|
|
862
|
+
failedTasks: this.state.sharedContext.metadata.failedTasks.length,
|
|
863
|
+
poolStats,
|
|
864
|
+
executionResults: this.state.timeline.map((r) => ({
|
|
865
|
+
agentId: r.agentId,
|
|
866
|
+
success: r.success,
|
|
867
|
+
role: this.workflow.agents.find((a) => a.id === r.agentId)?.role,
|
|
868
|
+
steps: r.context.metadata.currentStep,
|
|
869
|
+
durationMs: r.durationMs,
|
|
870
|
+
toolsUsed: r.executedTools,
|
|
871
|
+
attemptNumber: r.attemptNumber,
|
|
872
|
+
})),
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Human-readable debug info
|
|
878
|
+
*/
|
|
879
|
+
debugInfo(): string {
|
|
880
|
+
const lines: string[] = [
|
|
881
|
+
`=== Orchestrator Debug Info ===`,
|
|
882
|
+
`Workflow: ${this.workflow.id}`,
|
|
883
|
+
`Status: ${this.state.status}`,
|
|
884
|
+
`Strategy: ${this.workflow.strategy.type}`,
|
|
885
|
+
`\n${this.poolManager.debugSnapshot()}`,
|
|
886
|
+
`\nMessage Broker: ${JSON.stringify(this.messageBroker.stats())}`,
|
|
887
|
+
`Events: ${this.state.events.length}`,
|
|
888
|
+
];
|
|
889
|
+
return lines.join("\n");
|
|
890
|
+
}
|
|
891
|
+
}
|