@johpaz/hive-core 1.0.8 → 1.0.10
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/package.json +10 -9
- package/src/agent/ethics.ts +70 -68
- package/src/agent/index.ts +48 -17
- package/src/agent/providers/index.ts +11 -5
- package/src/agent/soul.ts +19 -15
- package/src/agent/user.ts +19 -15
- package/src/agent/workspace.ts +6 -6
- package/src/agents/index.ts +4 -0
- package/src/agents/inter-agent-bus.test.ts +264 -0
- package/src/agents/inter-agent-bus.ts +279 -0
- package/src/agents/registry.test.ts +275 -0
- package/src/agents/registry.ts +273 -0
- package/src/agents/router.test.ts +229 -0
- package/src/agents/router.ts +251 -0
- package/src/agents/team-coordinator.test.ts +401 -0
- package/src/agents/team-coordinator.ts +480 -0
- package/src/canvas/canvas-manager.test.ts +159 -0
- package/src/canvas/canvas-manager.ts +219 -0
- package/src/canvas/canvas-tools.ts +189 -0
- package/src/canvas/index.ts +2 -0
- package/src/channels/whatsapp.ts +12 -12
- package/src/config/loader.ts +12 -9
- package/src/events/event-bus.test.ts +98 -0
- package/src/events/event-bus.ts +171 -0
- package/src/gateway/server.ts +131 -35
- package/src/index.ts +9 -1
- package/src/multi-agent/manager.ts +12 -12
- package/src/plugins/api.ts +129 -0
- package/src/plugins/index.ts +2 -0
- package/src/plugins/loader.test.ts +285 -0
- package/src/plugins/loader.ts +363 -0
- package/src/resilience/circuit-breaker.test.ts +129 -0
- package/src/resilience/circuit-breaker.ts +223 -0
- package/src/security/google-chat.test.ts +219 -0
- package/src/security/google-chat.ts +269 -0
- package/src/security/index.ts +5 -0
- package/src/security/pairing.test.ts +302 -0
- package/src/security/pairing.ts +250 -0
- package/src/security/rate-limit.test.ts +239 -0
- package/src/security/rate-limit.ts +270 -0
- package/src/security/signal.test.ts +92 -0
- package/src/security/signal.ts +321 -0
- package/src/state/store.test.ts +190 -0
- package/src/state/store.ts +310 -0
- package/src/storage/sqlite.ts +3 -3
- package/src/tools/cron.ts +42 -2
- package/src/tools/dynamic-registry.test.ts +226 -0
- package/src/tools/dynamic-registry.ts +258 -0
- package/src/tools/fs.test.ts +127 -0
- package/src/tools/fs.ts +364 -0
- package/src/tools/index.ts +1 -0
- package/src/tools/read.ts +23 -19
- package/src/utils/logger.ts +112 -33
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "bun:test";
|
|
2
|
+
import { TaskRouter, DefaultTaskExecutor, type TaskExecutor } from "./router";
|
|
3
|
+
import { AgentRegistry, type AgentDefinition, type Task } from "./registry";
|
|
4
|
+
import { InterAgentBus } from "./inter-agent-bus";
|
|
5
|
+
import { eventBus } from "../events/event-bus";
|
|
6
|
+
|
|
7
|
+
describe("TaskRouter", () => {
|
|
8
|
+
let registry: AgentRegistry;
|
|
9
|
+
let bus: InterAgentBus;
|
|
10
|
+
let router: TaskRouter;
|
|
11
|
+
let executor: TaskExecutor;
|
|
12
|
+
|
|
13
|
+
const createAgent = (id: string, capabilities: string[], maxTasks = 3): AgentDefinition => ({
|
|
14
|
+
id,
|
|
15
|
+
name: `Agent ${id}`,
|
|
16
|
+
description: `Test agent ${id}`,
|
|
17
|
+
capabilities,
|
|
18
|
+
modelConfig: { provider: "openai", model: "gpt-4" },
|
|
19
|
+
systemPrompt: "You are helpful",
|
|
20
|
+
maxConcurrentTasks: maxTasks,
|
|
21
|
+
currentLoad: 0,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const createTask = (id: string, capabilities: string[], requiresTeam = false): Task => ({
|
|
25
|
+
id,
|
|
26
|
+
description: `Task ${id}`,
|
|
27
|
+
requiredCapabilities: capabilities,
|
|
28
|
+
priority: "normal",
|
|
29
|
+
requiresTeam,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
registry = new AgentRegistry();
|
|
34
|
+
bus = new InterAgentBus();
|
|
35
|
+
router = new TaskRouter(registry, bus);
|
|
36
|
+
executor = new DefaultTaskExecutor();
|
|
37
|
+
eventBus.removeAllListeners();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("route", () => {
|
|
41
|
+
it("should route task to best agent", async () => {
|
|
42
|
+
registry.register(createAgent("agent-1", ["code"]));
|
|
43
|
+
registry.register(createAgent("agent-2", ["test"]));
|
|
44
|
+
|
|
45
|
+
const task = createTask("task-1", ["test"]);
|
|
46
|
+
const result = await router.route(task, executor);
|
|
47
|
+
|
|
48
|
+
expect(result.agentId).toBe("agent-2");
|
|
49
|
+
expect(result.status).toBe("success");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should return failed result when no agent available", async () => {
|
|
53
|
+
const task = createTask("task-1", ["nonexistent"]);
|
|
54
|
+
const result = await router.route(task, executor);
|
|
55
|
+
|
|
56
|
+
expect(result.status).toBe("failed");
|
|
57
|
+
expect(result.output).toEqual({ error: expect.stringContaining("No agent") });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should track statistics", async () => {
|
|
61
|
+
registry.register(createAgent("agent-1", ["code"]));
|
|
62
|
+
registry.register(createAgent("agent-2", ["test"]));
|
|
63
|
+
|
|
64
|
+
await router.route(createTask("task-1", ["code"]), executor);
|
|
65
|
+
await router.route(createTask("task-2", ["test"]), executor);
|
|
66
|
+
await router.route(createTask("task-3", ["code"]), executor);
|
|
67
|
+
|
|
68
|
+
const stats = router.getStats();
|
|
69
|
+
expect(stats.tasksRouted).toBe(3);
|
|
70
|
+
expect(stats.byAgent["agent-1"]).toBe(2);
|
|
71
|
+
expect(stats.byAgent["agent-2"]).toBe(1);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should track failures", async () => {
|
|
75
|
+
await router.route(createTask("task-1", ["nonexistent"]), executor);
|
|
76
|
+
|
|
77
|
+
const stats = router.getStats();
|
|
78
|
+
expect(stats.failures).toBe(1);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("team routing", () => {
|
|
83
|
+
it("should route to team for multiple capabilities", async () => {
|
|
84
|
+
registry.register(createAgent("agent-1", ["code"]));
|
|
85
|
+
registry.register(createAgent("agent-2", ["test"]));
|
|
86
|
+
registry.register(createAgent("agent-3", ["deploy"]));
|
|
87
|
+
|
|
88
|
+
const task = createTask("task-1", ["code", "test", "deploy"]);
|
|
89
|
+
const result = await router.route(task, executor);
|
|
90
|
+
|
|
91
|
+
expect(result.status).toBe("success");
|
|
92
|
+
expect(result.subtasks).toBeDefined();
|
|
93
|
+
expect(result.subtasks?.length).toBe(3);
|
|
94
|
+
|
|
95
|
+
const stats = router.getStats();
|
|
96
|
+
expect(stats.byTeam).toBe(1);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should route to single agent if team size is 1", async () => {
|
|
100
|
+
registry.register(createAgent("agent-1", ["code", "test", "deploy"]));
|
|
101
|
+
|
|
102
|
+
const task = createTask("task-1", ["code", "test"]);
|
|
103
|
+
const result = await router.route(task, executor);
|
|
104
|
+
|
|
105
|
+
expect(result.agentId).toBe("agent-1");
|
|
106
|
+
expect(result.subtasks).toBeUndefined();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should route to team when requiresTeam is true", async () => {
|
|
110
|
+
registry.register(createAgent("agent-1", ["code"]));
|
|
111
|
+
registry.register(createAgent("agent-2", ["code"]));
|
|
112
|
+
|
|
113
|
+
const task = createTask("task-1", ["code"], true);
|
|
114
|
+
const result = await router.route(task, executor);
|
|
115
|
+
|
|
116
|
+
expect(result.status).toBe("success");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("load management", () => {
|
|
121
|
+
it("should increment load during execution", async () => {
|
|
122
|
+
registry.register(createAgent("agent-1", ["code"]));
|
|
123
|
+
|
|
124
|
+
let loadDuringExecution = -1;
|
|
125
|
+
const trackingExecutor: TaskExecutor = {
|
|
126
|
+
execute: async (agent, task) => {
|
|
127
|
+
loadDuringExecution = registry.get("agent-1")?.currentLoad ?? -1;
|
|
128
|
+
return { done: true };
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
await router.route(createTask("task-1", ["code"]), trackingExecutor);
|
|
133
|
+
|
|
134
|
+
expect(loadDuringExecution).toBe(1);
|
|
135
|
+
expect(registry.get("agent-1")?.currentLoad).toBe(0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should decrement load after error", async () => {
|
|
139
|
+
registry.register(createAgent("agent-1", ["code"]));
|
|
140
|
+
|
|
141
|
+
const errorExecutor: TaskExecutor = {
|
|
142
|
+
execute: async () => {
|
|
143
|
+
throw new Error("Execution failed");
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const result = await router.route(createTask("task-1", ["code"]), errorExecutor);
|
|
148
|
+
|
|
149
|
+
expect(result.status).toBe("failed");
|
|
150
|
+
expect(registry.get("agent-1")?.currentLoad).toBe(0);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("decomposition", () => {
|
|
155
|
+
it("should decompose task by capabilities", async () => {
|
|
156
|
+
registry.register(createAgent("agent-1", ["code"]));
|
|
157
|
+
registry.register(createAgent("agent-2", ["test"]));
|
|
158
|
+
registry.register(createAgent("agent-3", ["deploy"]));
|
|
159
|
+
|
|
160
|
+
const task = createTask("task-1", ["code", "test", "deploy"]);
|
|
161
|
+
const result = await router.route(task, executor);
|
|
162
|
+
|
|
163
|
+
expect(result.subtasks).toBeDefined();
|
|
164
|
+
expect(result.subtasks?.length).toBe(3);
|
|
165
|
+
expect(result.subtasks?.map(s => s.agentId)).toContain("agent-1");
|
|
166
|
+
expect(result.subtasks?.map(s => s.agentId)).toContain("agent-2");
|
|
167
|
+
expect(result.subtasks?.map(s => s.agentId)).toContain("agent-3");
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe("synthesis", () => {
|
|
172
|
+
it("should synthesize results from team", async () => {
|
|
173
|
+
registry.register(createAgent("agent-1", ["code"]));
|
|
174
|
+
registry.register(createAgent("agent-2", ["test"]));
|
|
175
|
+
|
|
176
|
+
const task = createTask("task-1", ["code", "test"]);
|
|
177
|
+
const result = await router.route(task, executor);
|
|
178
|
+
|
|
179
|
+
expect(result.output).toHaveProperty("synthesized");
|
|
180
|
+
expect(result.output).toHaveProperty("results");
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe("DefaultTaskExecutor", () => {
|
|
185
|
+
it("should execute and return result", async () => {
|
|
186
|
+
const exec = new DefaultTaskExecutor();
|
|
187
|
+
const agent = createAgent("agent-1", ["code"]);
|
|
188
|
+
const task = createTask("task-1", ["code"]);
|
|
189
|
+
|
|
190
|
+
const result = await exec.execute(agent, task);
|
|
191
|
+
|
|
192
|
+
expect(result).toHaveProperty("agentId", "agent-1");
|
|
193
|
+
expect(result).toHaveProperty("taskId", "task-1");
|
|
194
|
+
expect(result).toHaveProperty("result", "executed");
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe("reset", () => {
|
|
199
|
+
it("should reset statistics", async () => {
|
|
200
|
+
registry.register(createAgent("agent-1", ["code"]));
|
|
201
|
+
|
|
202
|
+
await router.route(createTask("task-1", ["code"]), executor);
|
|
203
|
+
router.reset();
|
|
204
|
+
|
|
205
|
+
const stats = router.getStats();
|
|
206
|
+
expect(stats.tasksRouted).toBe(0);
|
|
207
|
+
expect(stats.byAgent).toEqual({});
|
|
208
|
+
expect(stats.byTeam).toBe(0);
|
|
209
|
+
expect(stats.failures).toBe(0);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe("average duration", () => {
|
|
214
|
+
it("should calculate average duration", async () => {
|
|
215
|
+
registry.register(createAgent("agent-1", ["code"]));
|
|
216
|
+
|
|
217
|
+
await router.route(createTask("task-1", ["code"]), executor);
|
|
218
|
+
await router.route(createTask("task-2", ["code"]), executor);
|
|
219
|
+
|
|
220
|
+
const stats = router.getStats();
|
|
221
|
+
expect(stats.averageDuration).toBeGreaterThanOrEqual(0);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should return 0 when no tasks routed", () => {
|
|
225
|
+
const stats = router.getStats();
|
|
226
|
+
expect(stats.averageDuration).toBe(0);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
});
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { logger } from "../utils/logger.ts";
|
|
2
|
+
import { eventBus } from "../events/event-bus.ts";
|
|
3
|
+
import type { AgentRegistry, AgentDefinition, Task, TaskResult } from "./registry.ts";
|
|
4
|
+
import type { InterAgentBus } from "./inter-agent-bus.ts";
|
|
5
|
+
|
|
6
|
+
export interface DecompositionResult {
|
|
7
|
+
subtasks: Task[];
|
|
8
|
+
strategy: "parallel" | "sequential" | "mixed";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SynthesisConfig {
|
|
12
|
+
method: "concat" | "summarize" | "hierarchical";
|
|
13
|
+
leadAgentId: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RouterStats {
|
|
17
|
+
tasksRouted: number;
|
|
18
|
+
byAgent: Record<string, number>;
|
|
19
|
+
byTeam: number;
|
|
20
|
+
failures: number;
|
|
21
|
+
averageDuration: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class TaskRouter {
|
|
25
|
+
private tasksRouted = 0;
|
|
26
|
+
private byAgent: Record<string, number> = {};
|
|
27
|
+
private byTeam = 0;
|
|
28
|
+
private failures = 0;
|
|
29
|
+
private totalDuration = 0;
|
|
30
|
+
private log = logger.child("task-router");
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
private registry: AgentRegistry,
|
|
34
|
+
private bus: InterAgentBus
|
|
35
|
+
) {}
|
|
36
|
+
|
|
37
|
+
async route(task: Task, executor: TaskExecutor): Promise<TaskResult> {
|
|
38
|
+
const startTime = Date.now();
|
|
39
|
+
this.tasksRouted++;
|
|
40
|
+
|
|
41
|
+
this.log.info(`Routing task: ${task.id}`, {
|
|
42
|
+
capabilities: task.requiredCapabilities,
|
|
43
|
+
requiresTeam: task.requiresTeam,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
let result: TaskResult;
|
|
48
|
+
|
|
49
|
+
if (task.requiresTeam || task.requiredCapabilities.length > 1) {
|
|
50
|
+
const team = this.registry.findTeamForTask(task);
|
|
51
|
+
|
|
52
|
+
if (team.length > 1) {
|
|
53
|
+
result = await this.executeWithTeam(team, task, executor);
|
|
54
|
+
this.byTeam++;
|
|
55
|
+
} else if (team.length === 1) {
|
|
56
|
+
result = await this.executeWithAgent(team[0]!, task, executor);
|
|
57
|
+
this.byAgent[team[0]!.id] = (this.byAgent[team[0]!.id] ?? 0) + 1;
|
|
58
|
+
} else {
|
|
59
|
+
throw new Error(`No agents available for task ${task.id}`);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
const agent = this.registry.findBestAgentForTask(task);
|
|
63
|
+
|
|
64
|
+
if (!agent) {
|
|
65
|
+
throw new Error(`No agent available for capabilities: ${task.requiredCapabilities.join(", ")}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
result = await this.executeWithAgent(agent, task, executor);
|
|
69
|
+
this.byAgent[agent.id] = (this.byAgent[agent.id] ?? 0) + 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const duration = Date.now() - startTime;
|
|
73
|
+
this.totalDuration += duration;
|
|
74
|
+
|
|
75
|
+
eventBus.emit("tool:completed", {
|
|
76
|
+
toolName: `task:${task.id}`,
|
|
77
|
+
result: result.status,
|
|
78
|
+
duration,
|
|
79
|
+
success: result.status === "success",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return result;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
this.failures++;
|
|
85
|
+
this.log.error(`Task routing failed: ${task.id}`, {
|
|
86
|
+
error: (error as Error).message,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
taskId: task.id,
|
|
91
|
+
agentId: "router",
|
|
92
|
+
status: "failed",
|
|
93
|
+
output: { error: (error as Error).message },
|
|
94
|
+
duration: Date.now() - startTime,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private async executeWithAgent(
|
|
100
|
+
agent: AgentDefinition,
|
|
101
|
+
task: Task,
|
|
102
|
+
executor: TaskExecutor
|
|
103
|
+
): Promise<TaskResult> {
|
|
104
|
+
this.registry.incrementLoad(agent.id);
|
|
105
|
+
|
|
106
|
+
const startTime = Date.now();
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const output = await executor.execute(agent, task);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
taskId: task.id,
|
|
113
|
+
agentId: agent.id,
|
|
114
|
+
status: "success",
|
|
115
|
+
output,
|
|
116
|
+
duration: Date.now() - startTime,
|
|
117
|
+
};
|
|
118
|
+
} finally {
|
|
119
|
+
this.registry.decrementLoad(agent.id);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private async executeWithTeam(
|
|
124
|
+
team: AgentDefinition[],
|
|
125
|
+
task: Task,
|
|
126
|
+
executor: TaskExecutor
|
|
127
|
+
): Promise<TaskResult> {
|
|
128
|
+
const lead = team[0]!;
|
|
129
|
+
const startTime = Date.now();
|
|
130
|
+
|
|
131
|
+
this.log.info(`Executing task ${task.id} with team of ${team.length} agents`, {
|
|
132
|
+
lead: lead.id,
|
|
133
|
+
members: team.map((a) => a.id),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
for (const agent of team) {
|
|
137
|
+
this.registry.incrementLoad(agent.id);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const decomposition = await this.decomposeTask(lead, task, executor);
|
|
142
|
+
|
|
143
|
+
const results: TaskResult[] = [];
|
|
144
|
+
|
|
145
|
+
for (const subtask of decomposition.subtasks) {
|
|
146
|
+
const agentIndex = results.length % team.length;
|
|
147
|
+
const agent = team[agentIndex]!;
|
|
148
|
+
|
|
149
|
+
const result = await this.executeWithAgent(agent, subtask, executor);
|
|
150
|
+
results.push(result);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const synthesized = await this.synthesizeResults(lead, results, executor);
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
taskId: task.id,
|
|
157
|
+
agentId: lead.id,
|
|
158
|
+
status: results.every((r) => r.status === "success") ? "success" : "partial",
|
|
159
|
+
output: synthesized,
|
|
160
|
+
duration: Date.now() - startTime,
|
|
161
|
+
subtasks: results,
|
|
162
|
+
};
|
|
163
|
+
} finally {
|
|
164
|
+
for (const agent of team) {
|
|
165
|
+
this.registry.decrementLoad(agent.id);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private async decomposeTask(
|
|
171
|
+
lead: AgentDefinition,
|
|
172
|
+
task: Task,
|
|
173
|
+
executor: TaskExecutor
|
|
174
|
+
): Promise<DecompositionResult> {
|
|
175
|
+
const subtasks: Task[] = [];
|
|
176
|
+
|
|
177
|
+
for (let i = 0; i < task.requiredCapabilities.length; i++) {
|
|
178
|
+
const capability = task.requiredCapabilities[i]!;
|
|
179
|
+
subtasks.push({
|
|
180
|
+
id: `${task.id}-${i}`,
|
|
181
|
+
description: `Subtask for ${capability}`,
|
|
182
|
+
requiredCapabilities: [capability],
|
|
183
|
+
priority: task.priority,
|
|
184
|
+
parentTaskId: task.id,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
subtasks,
|
|
190
|
+
strategy: "parallel",
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private async synthesizeResults(
|
|
195
|
+
lead: AgentDefinition,
|
|
196
|
+
results: TaskResult[],
|
|
197
|
+
executor: TaskExecutor
|
|
198
|
+
): Promise<unknown> {
|
|
199
|
+
if (results.length === 1) {
|
|
200
|
+
return results[0]!.output;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
synthesized: true,
|
|
205
|
+
results: results.map((r) => ({
|
|
206
|
+
agentId: r.agentId,
|
|
207
|
+
status: r.status,
|
|
208
|
+
output: r.output,
|
|
209
|
+
})),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
getStats(): RouterStats {
|
|
214
|
+
return {
|
|
215
|
+
tasksRouted: this.tasksRouted,
|
|
216
|
+
byAgent: { ...this.byAgent },
|
|
217
|
+
byTeam: this.byTeam,
|
|
218
|
+
failures: this.failures,
|
|
219
|
+
averageDuration: this.tasksRouted > 0
|
|
220
|
+
? this.totalDuration / this.tasksRouted
|
|
221
|
+
: 0,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
reset(): void {
|
|
226
|
+
this.tasksRouted = 0;
|
|
227
|
+
this.byAgent = {};
|
|
228
|
+
this.byTeam = 0;
|
|
229
|
+
this.failures = 0;
|
|
230
|
+
this.totalDuration = 0;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface TaskExecutor {
|
|
235
|
+
execute(agent: AgentDefinition, task: Task): Promise<unknown>;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export class DefaultTaskExecutor implements TaskExecutor {
|
|
239
|
+
private log = logger.child("task-executor");
|
|
240
|
+
|
|
241
|
+
async execute(agent: AgentDefinition, task: Task): Promise<unknown> {
|
|
242
|
+
this.log.info(`Executing task ${task.id} with agent ${agent.id}`);
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
agentId: agent.id,
|
|
246
|
+
taskId: task.id,
|
|
247
|
+
result: "executed",
|
|
248
|
+
timestamp: Date.now(),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|