@johpaz/hive-core 1.0.7 → 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.
Files changed (53) hide show
  1. package/package.json +10 -9
  2. package/src/agent/ethics.ts +70 -68
  3. package/src/agent/index.ts +48 -17
  4. package/src/agent/providers/index.ts +11 -5
  5. package/src/agent/soul.ts +19 -15
  6. package/src/agent/user.ts +19 -15
  7. package/src/agent/workspace.ts +6 -6
  8. package/src/agents/index.ts +4 -0
  9. package/src/agents/inter-agent-bus.test.ts +264 -0
  10. package/src/agents/inter-agent-bus.ts +279 -0
  11. package/src/agents/registry.test.ts +275 -0
  12. package/src/agents/registry.ts +273 -0
  13. package/src/agents/router.test.ts +229 -0
  14. package/src/agents/router.ts +251 -0
  15. package/src/agents/team-coordinator.test.ts +401 -0
  16. package/src/agents/team-coordinator.ts +480 -0
  17. package/src/canvas/canvas-manager.test.ts +159 -0
  18. package/src/canvas/canvas-manager.ts +219 -0
  19. package/src/canvas/canvas-tools.ts +189 -0
  20. package/src/canvas/index.ts +2 -0
  21. package/src/channels/whatsapp.ts +12 -12
  22. package/src/config/loader.ts +12 -9
  23. package/src/events/event-bus.test.ts +98 -0
  24. package/src/events/event-bus.ts +171 -0
  25. package/src/gateway/server.ts +131 -35
  26. package/src/index.ts +9 -1
  27. package/src/multi-agent/manager.ts +12 -12
  28. package/src/plugins/api.ts +129 -0
  29. package/src/plugins/index.ts +2 -0
  30. package/src/plugins/loader.test.ts +285 -0
  31. package/src/plugins/loader.ts +363 -0
  32. package/src/resilience/circuit-breaker.test.ts +129 -0
  33. package/src/resilience/circuit-breaker.ts +223 -0
  34. package/src/security/google-chat.test.ts +219 -0
  35. package/src/security/google-chat.ts +269 -0
  36. package/src/security/index.ts +5 -0
  37. package/src/security/pairing.test.ts +302 -0
  38. package/src/security/pairing.ts +250 -0
  39. package/src/security/rate-limit.test.ts +239 -0
  40. package/src/security/rate-limit.ts +270 -0
  41. package/src/security/signal.test.ts +92 -0
  42. package/src/security/signal.ts +321 -0
  43. package/src/state/store.test.ts +190 -0
  44. package/src/state/store.ts +310 -0
  45. package/src/storage/sqlite.ts +3 -3
  46. package/src/tools/cron.ts +42 -2
  47. package/src/tools/dynamic-registry.test.ts +226 -0
  48. package/src/tools/dynamic-registry.ts +258 -0
  49. package/src/tools/fs.test.ts +127 -0
  50. package/src/tools/fs.ts +364 -0
  51. package/src/tools/index.ts +1 -0
  52. package/src/tools/read.ts +23 -19
  53. package/src/utils/logger.ts +112 -33
@@ -0,0 +1,401 @@
1
+ import { describe, it, expect, beforeEach, vi } from "bun:test";
2
+ import { TeamCoordinator, type TeamConfig, type CoordinationMode } from "./team-coordinator";
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("TeamCoordinator", () => {
8
+ let registry: AgentRegistry;
9
+ let bus: InterAgentBus;
10
+ let coordinator: TeamCoordinator;
11
+
12
+ const createAgent = (id: string, capabilities: string[]): AgentDefinition => ({
13
+ id,
14
+ name: `Agent ${id}`,
15
+ description: `Test agent ${id}`,
16
+ capabilities,
17
+ modelConfig: { provider: "openai", model: "gpt-4" },
18
+ systemPrompt: "You are helpful",
19
+ maxConcurrentTasks: 3,
20
+ currentLoad: 0,
21
+ });
22
+
23
+ const createTask = (id: string, capabilities: string[]): Task => ({
24
+ id,
25
+ description: `Task ${id}`,
26
+ requiredCapabilities: capabilities,
27
+ priority: "normal",
28
+ });
29
+
30
+ const createTeamConfig = (
31
+ name: string,
32
+ leadId: string,
33
+ memberIds: string[],
34
+ mode: CoordinationMode = "normal"
35
+ ): TeamConfig => ({
36
+ name,
37
+ leadId,
38
+ memberIds,
39
+ coordinationMode: mode,
40
+ sharedContext: false,
41
+ });
42
+
43
+ beforeEach(() => {
44
+ registry = new AgentRegistry();
45
+ bus = new InterAgentBus();
46
+ coordinator = new TeamCoordinator(registry, bus);
47
+ eventBus.removeAllListeners();
48
+ });
49
+
50
+ describe("createTeam", () => {
51
+ it("should create a team", () => {
52
+ registry.register(createAgent("lead-1", ["code"]));
53
+
54
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", ["agent-2", "agent-3"]));
55
+
56
+ const team = coordinator.getTeam("team-1");
57
+ expect(team).toBeDefined();
58
+ expect(team?.leadId).toBe("lead-1");
59
+ expect(team?.memberIds).toEqual(["agent-2", "agent-3"]);
60
+ });
61
+
62
+ it("should update existing team", () => {
63
+ registry.register(createAgent("lead-1", ["code"]));
64
+
65
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", ["agent-2"]));
66
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", ["agent-3", "agent-4"]));
67
+
68
+ const team = coordinator.getTeam("team-1");
69
+ expect(team?.memberIds).toEqual(["agent-3", "agent-4"]);
70
+ });
71
+
72
+ it("should initialize team state", () => {
73
+ registry.register(createAgent("lead-1", ["code"]));
74
+
75
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", []));
76
+
77
+ const state = coordinator.getTeamState("team-1");
78
+ expect(state?.status).toBe("idle");
79
+ expect(state?.activeMembers).toEqual([]);
80
+ });
81
+ });
82
+
83
+ describe("removeTeam", () => {
84
+ it("should remove idle team", () => {
85
+ registry.register(createAgent("lead-1", ["code"]));
86
+
87
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", []));
88
+ coordinator.removeTeam("team-1");
89
+
90
+ expect(coordinator.getTeam("team-1")).toBeUndefined();
91
+ });
92
+
93
+ it("should not remove working team", async () => {
94
+ registry.register(createAgent("lead-1", ["code"]));
95
+
96
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", []));
97
+
98
+ const executor = vi.fn().mockImplementation(async () => {
99
+ coordinator.removeTeam("team-1");
100
+ return {};
101
+ });
102
+
103
+ const task = createTask("task-1", ["code"]);
104
+
105
+ const removePromise = new Promise<void>((resolve) => {
106
+ setTimeout(() => {
107
+ coordinator.removeTeam("team-1");
108
+ resolve();
109
+ }, 10);
110
+ });
111
+
112
+ await coordinator.coordinateTask("team-1", task, async () => {
113
+ await removePromise;
114
+ return {};
115
+ });
116
+
117
+ expect(coordinator.getTeam("team-1")).toBeDefined();
118
+ });
119
+ });
120
+
121
+ describe("coordinateTask", () => {
122
+ it("should throw for non-existent team", async () => {
123
+ const task = createTask("task-1", ["code"]);
124
+
125
+ await expect(coordinator.coordinateTask("unknown", task, vi.fn()))
126
+ .rejects.toThrow("Team not found");
127
+ });
128
+
129
+ it("should update team state during execution", async () => {
130
+ registry.register(createAgent("lead-1", ["code"]));
131
+
132
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", []));
133
+
134
+ const task = createTask("task-1", ["code"]);
135
+
136
+ await coordinator.coordinateTask("team-1", task, async () => ({}));
137
+
138
+ const state = coordinator.getTeamState("team-1");
139
+ expect(state?.status).toBe("idle");
140
+ expect(state?.currentTaskId).toBeUndefined();
141
+ });
142
+ });
143
+
144
+ describe("coordination modes", () => {
145
+ describe("normal mode", () => {
146
+ it("should execute task with lead agent", async () => {
147
+ registry.register(createAgent("lead-1", ["code"]));
148
+
149
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", [], "normal"));
150
+
151
+ const executor = vi.fn().mockResolvedValue({ result: "done" });
152
+ const task = createTask("task-1", ["code"]);
153
+
154
+ const result = await coordinator.coordinateTask("team-1", task, executor);
155
+
156
+ expect(result.status).toBe("success");
157
+ expect(result.agentId).toBe("lead-1");
158
+ expect(executor).toHaveBeenCalled();
159
+ });
160
+
161
+ it("should throw if lead agent not found", async () => {
162
+ coordinator.createTeam(createTeamConfig("team-1", "missing-lead", [], "normal"));
163
+
164
+ const task = createTask("task-1", ["code"]);
165
+
166
+ const result = await coordinator.coordinateTask("team-1", task, vi.fn());
167
+ expect(result.status).toBe("failed");
168
+ });
169
+ });
170
+
171
+ describe("delegate mode", () => {
172
+ it("should create and execute plan", async () => {
173
+ registry.register(createAgent("lead-1", ["code", "test"]));
174
+
175
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", [], "delegate"));
176
+
177
+ const executor = vi.fn().mockResolvedValue({ done: true });
178
+ const task = createTask("task-1", ["code", "test"]);
179
+
180
+ const result = await coordinator.coordinateTask("team-1", task, executor);
181
+
182
+ expect(result.status).toBe("success");
183
+ expect(executor).toHaveBeenCalledTimes(2);
184
+ });
185
+
186
+ it("should track step progress", async () => {
187
+ registry.register(createAgent("lead-1", ["code", "test"]));
188
+
189
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", [], "delegate"));
190
+
191
+ const executor = vi.fn().mockResolvedValue({});
192
+ const task = createTask("task-1", ["code", "test"]);
193
+
194
+ await coordinator.coordinateTask("team-1", task, executor);
195
+
196
+ const state = coordinator.getTeamState("team-1");
197
+ expect(state?.completedSteps).toBe(2);
198
+ expect(state?.totalSteps).toBe(2);
199
+ });
200
+ });
201
+
202
+ describe("collaborative mode", () => {
203
+ it("should execute with all members", async () => {
204
+ registry.register(createAgent("lead-1", ["code"]));
205
+ registry.register(createAgent("member-1", ["code"]));
206
+ registry.register(createAgent("member-2", ["code"]));
207
+
208
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", ["member-1", "member-2"], "collaborative"));
209
+
210
+ const executor = vi.fn().mockResolvedValue({ contribution: "done" });
211
+ const task = createTask("task-1", ["code"]);
212
+
213
+ const result = await coordinator.coordinateTask("team-1", task, executor);
214
+
215
+ expect(result.status).toBe("success");
216
+ expect(executor).toHaveBeenCalledTimes(3);
217
+ expect(result.output).toHaveProperty("collaborative", true);
218
+ expect(result.output).toHaveProperty("contributions");
219
+ });
220
+
221
+ it("should support shared context", async () => {
222
+ registry.register(createAgent("lead-1", ["code"]));
223
+ registry.register(createAgent("member-1", ["code"]));
224
+
225
+ coordinator.createTeam({
226
+ name: "team-1",
227
+ leadId: "lead-1",
228
+ memberIds: ["member-1"],
229
+ coordinationMode: "collaborative",
230
+ sharedContext: true,
231
+ });
232
+
233
+ const task = createTask("task-1", ["code"]);
234
+
235
+ await coordinator.coordinateTask("team-1", task, async () => ({ value: 42 }));
236
+
237
+ const state = coordinator.getTeamState("team-1");
238
+ expect(state?.sharedState).toHaveProperty("lead-1");
239
+ expect(state?.sharedState).toHaveProperty("member-1");
240
+ });
241
+
242
+ it("should respect max parallel tasks", async () => {
243
+ registry.register(createAgent("lead-1", ["code"]));
244
+ registry.register(createAgent("member-1", ["code"]));
245
+ registry.register(createAgent("member-2", ["code"]));
246
+
247
+ coordinator.createTeam({
248
+ name: "team-1",
249
+ leadId: "lead-1",
250
+ memberIds: ["member-1", "member-2"],
251
+ coordinationMode: "collaborative",
252
+ sharedContext: false,
253
+ maxParallelTasks: 1,
254
+ });
255
+
256
+ const executionOrder: string[] = [];
257
+ const executor = vi.fn().mockImplementation(async (agent) => {
258
+ executionOrder.push(agent.id);
259
+ return {};
260
+ });
261
+
262
+ const task = createTask("task-1", ["code"]);
263
+ await coordinator.coordinateTask("team-1", task, executor);
264
+
265
+ expect(executionOrder.length).toBe(3);
266
+ });
267
+ });
268
+
269
+ describe("hierarchical mode", () => {
270
+ it("should decompose and synthesize", async () => {
271
+ registry.register(createAgent("lead-1", ["code"]));
272
+ registry.register(createAgent("worker-1", ["test"]));
273
+ registry.register(createAgent("worker-2", ["deploy"]));
274
+
275
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", ["worker-1", "worker-2"], "hierarchical"));
276
+
277
+ const executor = vi.fn().mockResolvedValue({ done: true });
278
+ const task = createTask("task-1", ["code", "test", "deploy"]);
279
+
280
+ const result = await coordinator.coordinateTask("team-1", task, executor);
281
+
282
+ expect(result.status).toBe("success");
283
+ expect(result.output).toHaveProperty("synthesized");
284
+ expect(result.subtasks).toBeDefined();
285
+ });
286
+
287
+ it("should route subtasks to best agents", async () => {
288
+ registry.register(createAgent("lead-1", ["code"]));
289
+ registry.register(createAgent("worker-1", ["test"]));
290
+
291
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", ["worker-1"], "hierarchical"));
292
+
293
+ const task = createTask("task-1", ["test"]);
294
+
295
+ await coordinator.coordinateTask("team-1", task, async (agent) => ({ agentId: agent.id }));
296
+
297
+ expect(registry.get("worker-1")?.currentLoad).toBe(0);
298
+ });
299
+ });
300
+ });
301
+
302
+ describe("load management", () => {
303
+ it("should manage agent load during execution", async () => {
304
+ registry.register(createAgent("lead-1", ["code"]));
305
+
306
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", []));
307
+
308
+ let loadDuringExecution = -1;
309
+
310
+ await coordinator.coordinateTask("team-1", createTask("task-1", ["code"]), async (agent) => {
311
+ loadDuringExecution = registry.get(agent.id)?.currentLoad ?? -1;
312
+ return {};
313
+ });
314
+
315
+ expect(loadDuringExecution).toBe(1);
316
+ expect(registry.get("lead-1")?.currentLoad).toBe(0);
317
+ });
318
+
319
+ it("should decrement load after error", async () => {
320
+ registry.register(createAgent("lead-1", ["code"]));
321
+
322
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", []));
323
+
324
+ await coordinator.coordinateTask("team-1", createTask("task-1", ["code"]), async () => {
325
+ throw new Error("Failed");
326
+ });
327
+
328
+ expect(registry.get("lead-1")?.currentLoad).toBe(0);
329
+ });
330
+ });
331
+
332
+ describe("getStats", () => {
333
+ it("should return correct stats", async () => {
334
+ registry.register(createAgent("lead-1", ["code"]));
335
+ registry.register(createAgent("lead-2", ["test"]));
336
+
337
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", [], "normal"));
338
+ coordinator.createTeam(createTeamConfig("team-2", "lead-2", [], "collaborative"));
339
+
340
+ await coordinator.coordinateTask("team-1", createTask("task-1", ["code"]), vi.fn());
341
+
342
+ const stats = coordinator.getStats();
343
+
344
+ expect(stats.totalTeams).toBe(2);
345
+ expect(stats.byMode.normal).toBe(1);
346
+ expect(stats.byMode.collaborative).toBe(1);
347
+ expect(stats.tasksCompleted).toBe(1);
348
+ });
349
+
350
+ it("should track active teams", async () => {
351
+ registry.register(createAgent("lead-1", ["code"]));
352
+
353
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", []));
354
+
355
+ let statsPromise: Promise<void>;
356
+
357
+ const coordPromise = coordinator.coordinateTask("team-1", createTask("task-1", ["code"]), async () => {
358
+ await new Promise(r => setTimeout(r, 50));
359
+ return {};
360
+ });
361
+
362
+ await new Promise(r => setTimeout(r, 10));
363
+ const stats = coordinator.getStats();
364
+
365
+ await coordPromise;
366
+
367
+ expect(stats.activeTeams).toBeGreaterThanOrEqual(0);
368
+ });
369
+ });
370
+
371
+ describe("listTeams", () => {
372
+ it("should list all teams", () => {
373
+ registry.register(createAgent("lead-1", ["code"]));
374
+ registry.register(createAgent("lead-2", ["test"]));
375
+
376
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", []));
377
+ coordinator.createTeam(createTeamConfig("team-2", "lead-2", []));
378
+
379
+ const teams = coordinator.listTeams();
380
+
381
+ expect(teams.length).toBe(2);
382
+ expect(teams.map(t => t.name)).toContain("team-1");
383
+ expect(teams.map(t => t.name)).toContain("team-2");
384
+ });
385
+ });
386
+
387
+ describe("clear", () => {
388
+ it("should clear all teams", async () => {
389
+ registry.register(createAgent("lead-1", ["code"]));
390
+
391
+ coordinator.createTeam(createTeamConfig("team-1", "lead-1", []));
392
+
393
+ await coordinator.coordinateTask("team-1", createTask("task-1", ["code"]), vi.fn());
394
+
395
+ coordinator.clear();
396
+
397
+ expect(coordinator.listTeams().length).toBe(0);
398
+ expect(coordinator.getStats().tasksCompleted).toBe(0);
399
+ });
400
+ });
401
+ });