@sparkleideas/shared 3.0.0-alpha.7
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 +323 -0
- package/__tests__/hooks/bash-safety.test.ts +289 -0
- package/__tests__/hooks/file-organization.test.ts +335 -0
- package/__tests__/hooks/git-commit.test.ts +336 -0
- package/__tests__/hooks/index.ts +23 -0
- package/__tests__/hooks/session-hooks.test.ts +357 -0
- package/__tests__/hooks/task-hooks.test.ts +193 -0
- package/docs/EVENTS_IMPLEMENTATION_SUMMARY.md +388 -0
- package/docs/EVENTS_QUICK_REFERENCE.md +470 -0
- package/docs/EVENTS_README.md +352 -0
- package/package.json +39 -0
- package/src/core/config/defaults.ts +207 -0
- package/src/core/config/index.ts +15 -0
- package/src/core/config/loader.ts +271 -0
- package/src/core/config/schema.ts +188 -0
- package/src/core/config/validator.ts +209 -0
- package/src/core/event-bus.ts +236 -0
- package/src/core/index.ts +22 -0
- package/src/core/interfaces/agent.interface.ts +251 -0
- package/src/core/interfaces/coordinator.interface.ts +363 -0
- package/src/core/interfaces/event.interface.ts +267 -0
- package/src/core/interfaces/index.ts +19 -0
- package/src/core/interfaces/memory.interface.ts +332 -0
- package/src/core/interfaces/task.interface.ts +223 -0
- package/src/core/orchestrator/event-coordinator.ts +122 -0
- package/src/core/orchestrator/health-monitor.ts +214 -0
- package/src/core/orchestrator/index.ts +89 -0
- package/src/core/orchestrator/lifecycle-manager.ts +263 -0
- package/src/core/orchestrator/session-manager.ts +279 -0
- package/src/core/orchestrator/task-manager.ts +317 -0
- package/src/events/domain-events.ts +584 -0
- package/src/events/event-store.test.ts +387 -0
- package/src/events/event-store.ts +588 -0
- package/src/events/example-usage.ts +293 -0
- package/src/events/index.ts +90 -0
- package/src/events/projections.ts +561 -0
- package/src/events/state-reconstructor.ts +349 -0
- package/src/events.ts +367 -0
- package/src/hooks/INTEGRATION.md +658 -0
- package/src/hooks/README.md +532 -0
- package/src/hooks/example-usage.ts +499 -0
- package/src/hooks/executor.ts +379 -0
- package/src/hooks/hooks.test.ts +421 -0
- package/src/hooks/index.ts +131 -0
- package/src/hooks/registry.ts +333 -0
- package/src/hooks/safety/bash-safety.ts +604 -0
- package/src/hooks/safety/file-organization.ts +473 -0
- package/src/hooks/safety/git-commit.ts +623 -0
- package/src/hooks/safety/index.ts +46 -0
- package/src/hooks/session-hooks.ts +559 -0
- package/src/hooks/task-hooks.ts +513 -0
- package/src/hooks/types.ts +357 -0
- package/src/hooks/verify-exports.test.ts +125 -0
- package/src/index.ts +195 -0
- package/src/mcp/connection-pool.ts +438 -0
- package/src/mcp/index.ts +183 -0
- package/src/mcp/server.ts +774 -0
- package/src/mcp/session-manager.ts +428 -0
- package/src/mcp/tool-registry.ts +566 -0
- package/src/mcp/transport/http.ts +557 -0
- package/src/mcp/transport/index.ts +294 -0
- package/src/mcp/transport/stdio.ts +324 -0
- package/src/mcp/transport/websocket.ts +484 -0
- package/src/mcp/types.ts +565 -0
- package/src/plugin-interface.ts +663 -0
- package/src/plugin-loader.ts +638 -0
- package/src/plugin-registry.ts +604 -0
- package/src/plugins/index.ts +34 -0
- package/src/plugins/official/hive-mind-plugin.ts +330 -0
- package/src/plugins/official/index.ts +24 -0
- package/src/plugins/official/maestro-plugin.ts +508 -0
- package/src/plugins/types.ts +108 -0
- package/src/resilience/bulkhead.ts +277 -0
- package/src/resilience/circuit-breaker.ts +326 -0
- package/src/resilience/index.ts +26 -0
- package/src/resilience/rate-limiter.ts +420 -0
- package/src/resilience/retry.ts +224 -0
- package/src/security/index.ts +39 -0
- package/src/security/input-validation.ts +265 -0
- package/src/security/secure-random.ts +159 -0
- package/src/services/index.ts +16 -0
- package/src/services/v3-progress.service.ts +505 -0
- package/src/types/agent.types.ts +144 -0
- package/src/types/index.ts +22 -0
- package/src/types/mcp.types.ts +300 -0
- package/src/types/memory.types.ts +263 -0
- package/src/types/swarm.types.ts +255 -0
- package/src/types/task.types.ts +205 -0
- package/src/types.ts +367 -0
- package/src/utils/secure-logger.d.ts +69 -0
- package/src/utils/secure-logger.d.ts.map +1 -0
- package/src/utils/secure-logger.js +208 -0
- package/src/utils/secure-logger.js.map +1 -0
- package/src/utils/secure-logger.ts +257 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Reconstructor - ADR-007 Implementation
|
|
3
|
+
*
|
|
4
|
+
* Reconstructs aggregate state from event streams.
|
|
5
|
+
* Implements event sourcing patterns for V3.
|
|
6
|
+
*
|
|
7
|
+
* @module v3/shared/events/state-reconstructor
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { EventStore, type EventSnapshot } from './event-store.js';
|
|
11
|
+
import type { DomainEvent } from './domain-events.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Aggregate root interface
|
|
15
|
+
*/
|
|
16
|
+
export interface AggregateRoot {
|
|
17
|
+
id: string;
|
|
18
|
+
version: number;
|
|
19
|
+
apply(event: DomainEvent): void;
|
|
20
|
+
getState(): Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Reconstructor options
|
|
25
|
+
*/
|
|
26
|
+
export interface ReconstructorOptions {
|
|
27
|
+
useSnapshots: boolean;
|
|
28
|
+
snapshotInterval: number; // Create snapshot every N events
|
|
29
|
+
maxEventsToReplay: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* State Reconstructor
|
|
34
|
+
*
|
|
35
|
+
* Reconstructs aggregate state from event history.
|
|
36
|
+
* Supports snapshots for performance optimization.
|
|
37
|
+
*/
|
|
38
|
+
export class StateReconstructor {
|
|
39
|
+
private readonly options: ReconstructorOptions;
|
|
40
|
+
|
|
41
|
+
constructor(
|
|
42
|
+
private readonly eventStore: EventStore,
|
|
43
|
+
options?: Partial<ReconstructorOptions>
|
|
44
|
+
) {
|
|
45
|
+
this.options = {
|
|
46
|
+
useSnapshots: true,
|
|
47
|
+
snapshotInterval: 100,
|
|
48
|
+
maxEventsToReplay: 10000,
|
|
49
|
+
...options,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Reconstruct aggregate state from events
|
|
55
|
+
*/
|
|
56
|
+
async reconstruct<T extends AggregateRoot>(
|
|
57
|
+
aggregateId: string,
|
|
58
|
+
factory: (id: string) => T
|
|
59
|
+
): Promise<T> {
|
|
60
|
+
const aggregate = factory(aggregateId);
|
|
61
|
+
|
|
62
|
+
// Try to load from snapshot first
|
|
63
|
+
if (this.options.useSnapshots) {
|
|
64
|
+
const snapshot = await this.eventStore.getSnapshot(aggregateId);
|
|
65
|
+
if (snapshot) {
|
|
66
|
+
this.applySnapshot(aggregate, snapshot);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Get events after snapshot version (or all if no snapshot)
|
|
71
|
+
const events = await this.eventStore.getEvents(aggregateId, aggregate.version + 1);
|
|
72
|
+
|
|
73
|
+
// Apply events
|
|
74
|
+
for (const event of events) {
|
|
75
|
+
if (events.length > this.options.maxEventsToReplay) {
|
|
76
|
+
throw new Error(`Too many events to replay (${events.length}). Consider creating a snapshot.`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
aggregate.apply(event);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Create snapshot if interval reached
|
|
83
|
+
if (this.options.useSnapshots && aggregate.version % this.options.snapshotInterval === 0) {
|
|
84
|
+
await this.createSnapshot(aggregate);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return aggregate;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Reconstruct state at a specific point in time
|
|
92
|
+
*/
|
|
93
|
+
async reconstructAtTime<T extends AggregateRoot>(
|
|
94
|
+
aggregateId: string,
|
|
95
|
+
factory: (id: string) => T,
|
|
96
|
+
timestamp: Date
|
|
97
|
+
): Promise<T> {
|
|
98
|
+
const aggregate = factory(aggregateId);
|
|
99
|
+
|
|
100
|
+
// Get all events up to timestamp
|
|
101
|
+
const allEvents = await this.eventStore.getEvents(aggregateId);
|
|
102
|
+
const events = allEvents.filter((e) => e.timestamp <= timestamp.getTime());
|
|
103
|
+
|
|
104
|
+
// Apply events
|
|
105
|
+
for (const event of events) {
|
|
106
|
+
aggregate.apply(event);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return aggregate;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Reconstruct state at a specific version
|
|
114
|
+
*/
|
|
115
|
+
async reconstructAtVersion<T extends AggregateRoot>(
|
|
116
|
+
aggregateId: string,
|
|
117
|
+
factory: (id: string) => T,
|
|
118
|
+
targetVersion: number
|
|
119
|
+
): Promise<T> {
|
|
120
|
+
const aggregate = factory(aggregateId);
|
|
121
|
+
|
|
122
|
+
// Get events up to target version
|
|
123
|
+
const events = await this.eventStore.getEvents(aggregateId);
|
|
124
|
+
const limitedEvents = events.filter((e) => e.version <= targetVersion);
|
|
125
|
+
|
|
126
|
+
// Apply events
|
|
127
|
+
for (const event of limitedEvents) {
|
|
128
|
+
aggregate.apply(event);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return aggregate;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Apply snapshot to aggregate
|
|
136
|
+
*/
|
|
137
|
+
private applySnapshot(aggregate: AggregateRoot, snapshot: EventSnapshot): void {
|
|
138
|
+
// Type assertion for aggregate that has restoreFromSnapshot
|
|
139
|
+
const restorable = aggregate as AggregateRoot & {
|
|
140
|
+
restoreFromSnapshot?(state: unknown): void;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
if (typeof restorable.restoreFromSnapshot === 'function') {
|
|
144
|
+
restorable.restoreFromSnapshot(snapshot.state);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Update version
|
|
148
|
+
(aggregate as any).version = snapshot.version;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Create snapshot for aggregate
|
|
153
|
+
*/
|
|
154
|
+
private async createSnapshot(aggregate: AggregateRoot): Promise<void> {
|
|
155
|
+
const snapshot: EventSnapshot = {
|
|
156
|
+
aggregateId: aggregate.id,
|
|
157
|
+
aggregateType: this.getAggregateType(aggregate),
|
|
158
|
+
version: aggregate.version,
|
|
159
|
+
state: aggregate.getState(),
|
|
160
|
+
timestamp: Date.now(),
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
await this.eventStore.saveSnapshot(snapshot);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get aggregate type from instance
|
|
168
|
+
*/
|
|
169
|
+
private getAggregateType(aggregate: AggregateRoot): 'agent' | 'task' | 'memory' | 'swarm' {
|
|
170
|
+
const typeName = aggregate.constructor.name.toLowerCase().replace('aggregate', '');
|
|
171
|
+
// Map to valid aggregate types
|
|
172
|
+
if (typeName === 'agent' || typeName === 'task' || typeName === 'memory' || typeName === 'swarm') {
|
|
173
|
+
return typeName;
|
|
174
|
+
}
|
|
175
|
+
return 'agent'; // Default fallback
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Agent Aggregate - Example implementation
|
|
181
|
+
*/
|
|
182
|
+
export class AgentAggregate implements AggregateRoot {
|
|
183
|
+
id: string;
|
|
184
|
+
version = 0;
|
|
185
|
+
|
|
186
|
+
private state = {
|
|
187
|
+
name: '',
|
|
188
|
+
role: '',
|
|
189
|
+
status: 'idle' as string,
|
|
190
|
+
currentTask: null as string | null,
|
|
191
|
+
completedTasks: [] as string[],
|
|
192
|
+
capabilities: [] as string[],
|
|
193
|
+
createdAt: null as Date | null,
|
|
194
|
+
lastActiveAt: null as Date | null,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
constructor(id: string) {
|
|
198
|
+
this.id = id;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
apply(event: DomainEvent): void {
|
|
202
|
+
this.version = event.version;
|
|
203
|
+
|
|
204
|
+
switch (event.type) {
|
|
205
|
+
case 'agent:spawned':
|
|
206
|
+
this.state.name = event.payload.name as string;
|
|
207
|
+
this.state.role = event.payload.role as string;
|
|
208
|
+
this.state.capabilities = (event.payload.capabilities as string[]) ?? [];
|
|
209
|
+
this.state.status = 'idle';
|
|
210
|
+
this.state.createdAt = new Date(event.timestamp);
|
|
211
|
+
break;
|
|
212
|
+
|
|
213
|
+
case 'agent:started':
|
|
214
|
+
this.state.status = 'active';
|
|
215
|
+
this.state.lastActiveAt = new Date(event.timestamp);
|
|
216
|
+
break;
|
|
217
|
+
|
|
218
|
+
case 'agent:task-assigned':
|
|
219
|
+
this.state.currentTask = event.payload.taskId as string;
|
|
220
|
+
this.state.status = 'busy';
|
|
221
|
+
this.state.lastActiveAt = new Date(event.timestamp);
|
|
222
|
+
break;
|
|
223
|
+
|
|
224
|
+
case 'agent:task-completed':
|
|
225
|
+
this.state.completedTasks.push(event.payload.taskId as string);
|
|
226
|
+
this.state.currentTask = null;
|
|
227
|
+
this.state.status = 'active';
|
|
228
|
+
this.state.lastActiveAt = new Date(event.timestamp);
|
|
229
|
+
break;
|
|
230
|
+
|
|
231
|
+
case 'agent:terminated':
|
|
232
|
+
this.state.status = 'terminated';
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
getState(): Record<string, unknown> {
|
|
238
|
+
return { ...this.state };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
restoreFromSnapshot(snapshotState: unknown): void {
|
|
242
|
+
const state = snapshotState as typeof this.state;
|
|
243
|
+
this.state = {
|
|
244
|
+
...state,
|
|
245
|
+
createdAt: state.createdAt ? new Date(state.createdAt) : null,
|
|
246
|
+
lastActiveAt: state.lastActiveAt ? new Date(state.lastActiveAt) : null,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Getters for type safety
|
|
251
|
+
get name(): string { return this.state.name; }
|
|
252
|
+
get role(): string { return this.state.role; }
|
|
253
|
+
get status(): string { return this.state.status; }
|
|
254
|
+
get currentTask(): string | null { return this.state.currentTask; }
|
|
255
|
+
get completedTasks(): string[] { return [...this.state.completedTasks]; }
|
|
256
|
+
get capabilities(): string[] { return [...this.state.capabilities]; }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Task Aggregate - Example implementation
|
|
261
|
+
*/
|
|
262
|
+
export class TaskAggregate implements AggregateRoot {
|
|
263
|
+
id: string;
|
|
264
|
+
version = 0;
|
|
265
|
+
|
|
266
|
+
private state = {
|
|
267
|
+
title: '',
|
|
268
|
+
description: '',
|
|
269
|
+
type: '',
|
|
270
|
+
priority: 'normal' as string,
|
|
271
|
+
status: 'pending' as string,
|
|
272
|
+
assignedAgent: null as string | null,
|
|
273
|
+
result: null as unknown,
|
|
274
|
+
createdAt: null as Date | null,
|
|
275
|
+
startedAt: null as Date | null,
|
|
276
|
+
completedAt: null as Date | null,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
constructor(id: string) {
|
|
280
|
+
this.id = id;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
apply(event: DomainEvent): void {
|
|
284
|
+
this.version = event.version;
|
|
285
|
+
|
|
286
|
+
switch (event.type) {
|
|
287
|
+
case 'task:created':
|
|
288
|
+
this.state.title = event.payload.title as string;
|
|
289
|
+
this.state.description = event.payload.description as string;
|
|
290
|
+
this.state.type = event.payload.taskType as string;
|
|
291
|
+
this.state.priority = (event.payload.priority as string) ?? 'normal';
|
|
292
|
+
this.state.status = 'pending';
|
|
293
|
+
this.state.createdAt = new Date(event.timestamp);
|
|
294
|
+
break;
|
|
295
|
+
|
|
296
|
+
case 'task:started':
|
|
297
|
+
this.state.assignedAgent = event.payload.agentId as string;
|
|
298
|
+
this.state.status = 'running';
|
|
299
|
+
this.state.startedAt = new Date(event.timestamp);
|
|
300
|
+
break;
|
|
301
|
+
|
|
302
|
+
case 'task:completed':
|
|
303
|
+
this.state.result = event.payload.result;
|
|
304
|
+
this.state.status = 'completed';
|
|
305
|
+
this.state.completedAt = new Date(event.timestamp);
|
|
306
|
+
break;
|
|
307
|
+
|
|
308
|
+
case 'task:failed':
|
|
309
|
+
this.state.status = 'failed';
|
|
310
|
+
this.state.completedAt = new Date(event.timestamp);
|
|
311
|
+
break;
|
|
312
|
+
|
|
313
|
+
case 'task:cancelled':
|
|
314
|
+
this.state.status = 'cancelled';
|
|
315
|
+
this.state.completedAt = new Date(event.timestamp);
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
getState(): Record<string, unknown> {
|
|
321
|
+
return { ...this.state };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
restoreFromSnapshot(snapshotState: unknown): void {
|
|
325
|
+
const state = snapshotState as typeof this.state;
|
|
326
|
+
this.state = {
|
|
327
|
+
...state,
|
|
328
|
+
createdAt: state.createdAt ? new Date(state.createdAt) : null,
|
|
329
|
+
startedAt: state.startedAt ? new Date(state.startedAt) : null,
|
|
330
|
+
completedAt: state.completedAt ? new Date(state.completedAt) : null,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Getters
|
|
335
|
+
get title(): string { return this.state.title; }
|
|
336
|
+
get status(): string { return this.state.status; }
|
|
337
|
+
get assignedAgent(): string | null { return this.state.assignedAgent; }
|
|
338
|
+
get result(): unknown { return this.state.result; }
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Factory function
|
|
343
|
+
*/
|
|
344
|
+
export function createStateReconstructor(
|
|
345
|
+
eventStore: EventStore,
|
|
346
|
+
options?: Partial<ReconstructorOptions>
|
|
347
|
+
): StateReconstructor {
|
|
348
|
+
return new StateReconstructor(eventStore, options);
|
|
349
|
+
}
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Event Bus System
|
|
3
|
+
* Event-driven communication for the 15-agent swarm
|
|
4
|
+
*
|
|
5
|
+
* Based on ADR-007 (Event Sourcing for State Changes)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
EventType,
|
|
10
|
+
EventHandler,
|
|
11
|
+
SwarmEvent,
|
|
12
|
+
AgentId
|
|
13
|
+
} from './types.js';
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Event Bus Interface
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
export interface IEventBus {
|
|
20
|
+
subscribe<T>(eventType: EventType, handler: EventHandler<T>): () => void;
|
|
21
|
+
subscribeAll(handler: EventHandler): () => void;
|
|
22
|
+
emit<T>(event: SwarmEvent<T>): Promise<void>;
|
|
23
|
+
emitSync<T>(event: SwarmEvent<T>): void;
|
|
24
|
+
getHistory(filter?: EventFilter): SwarmEvent[];
|
|
25
|
+
clear(): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface EventFilter {
|
|
29
|
+
types?: EventType[];
|
|
30
|
+
sources?: (AgentId | 'swarm')[];
|
|
31
|
+
since?: number;
|
|
32
|
+
until?: number;
|
|
33
|
+
limit?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Event Store Interface (Event Sourcing)
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
export interface IEventStore {
|
|
41
|
+
append(event: SwarmEvent): Promise<void>;
|
|
42
|
+
getEvents(aggregateId: string, fromVersion?: number): Promise<SwarmEvent[]>;
|
|
43
|
+
getAllEvents(filter?: EventFilter): Promise<SwarmEvent[]>;
|
|
44
|
+
getSnapshot(aggregateId: string): Promise<EventStoreSnapshot | null>;
|
|
45
|
+
saveSnapshot(snapshot: EventStoreSnapshot): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface EventStoreSnapshot {
|
|
49
|
+
aggregateId: string;
|
|
50
|
+
version: number;
|
|
51
|
+
state: unknown;
|
|
52
|
+
timestamp: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// Event Bus Implementation
|
|
57
|
+
// =============================================================================
|
|
58
|
+
|
|
59
|
+
export class EventBus implements IEventBus {
|
|
60
|
+
private handlers: Map<EventType | '*', Set<EventHandler>> = new Map();
|
|
61
|
+
private history: SwarmEvent[] = [];
|
|
62
|
+
private maxHistorySize: number;
|
|
63
|
+
|
|
64
|
+
constructor(options: { maxHistorySize?: number } = {}) {
|
|
65
|
+
this.maxHistorySize = options.maxHistorySize ?? 10000;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
subscribe<T>(eventType: EventType, handler: EventHandler<T>): () => void {
|
|
69
|
+
if (!this.handlers.has(eventType)) {
|
|
70
|
+
this.handlers.set(eventType, new Set());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const handlers = this.handlers.get(eventType)!;
|
|
74
|
+
handlers.add(handler as EventHandler);
|
|
75
|
+
|
|
76
|
+
return () => {
|
|
77
|
+
handlers.delete(handler as EventHandler);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
subscribeAll(handler: EventHandler): () => void {
|
|
82
|
+
if (!this.handlers.has('*')) {
|
|
83
|
+
this.handlers.set('*', new Set());
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const handlers = this.handlers.get('*')!;
|
|
87
|
+
handlers.add(handler);
|
|
88
|
+
|
|
89
|
+
return () => {
|
|
90
|
+
handlers.delete(handler);
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async emit<T>(event: SwarmEvent<T>): Promise<void> {
|
|
95
|
+
this.addToHistory(event);
|
|
96
|
+
|
|
97
|
+
const typeHandlers = this.handlers.get(event.type) ?? new Set();
|
|
98
|
+
const allHandlers = this.handlers.get('*') ?? new Set();
|
|
99
|
+
|
|
100
|
+
const allPromises: Promise<void>[] = [];
|
|
101
|
+
|
|
102
|
+
for (const handler of typeHandlers) {
|
|
103
|
+
allPromises.push(this.safeExecute(handler, event));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const handler of allHandlers) {
|
|
107
|
+
allPromises.push(this.safeExecute(handler, event));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await Promise.all(allPromises);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
emitSync<T>(event: SwarmEvent<T>): void {
|
|
114
|
+
this.addToHistory(event);
|
|
115
|
+
|
|
116
|
+
const typeHandlers = this.handlers.get(event.type) ?? new Set();
|
|
117
|
+
const allHandlers = this.handlers.get('*') ?? new Set();
|
|
118
|
+
|
|
119
|
+
for (const handler of typeHandlers) {
|
|
120
|
+
try {
|
|
121
|
+
const result = handler(event);
|
|
122
|
+
if (result instanceof Promise) {
|
|
123
|
+
result.catch(err => console.error(`Event handler error: ${err}`));
|
|
124
|
+
}
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.error(`Event handler error: ${err}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const handler of allHandlers) {
|
|
131
|
+
try {
|
|
132
|
+
const result = handler(event);
|
|
133
|
+
if (result instanceof Promise) {
|
|
134
|
+
result.catch(err => console.error(`Event handler error: ${err}`));
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error(`Event handler error: ${err}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getHistory(filter?: EventFilter): SwarmEvent[] {
|
|
143
|
+
let events = [...this.history];
|
|
144
|
+
|
|
145
|
+
if (filter?.types?.length) {
|
|
146
|
+
events = events.filter(e => filter.types!.includes(e.type));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (filter?.sources?.length) {
|
|
150
|
+
events = events.filter(e => filter.sources!.includes(e.source));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (filter?.since) {
|
|
154
|
+
events = events.filter(e => e.timestamp >= filter.since!);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (filter?.until) {
|
|
158
|
+
events = events.filter(e => e.timestamp <= filter.until!);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (filter?.limit) {
|
|
162
|
+
events = events.slice(-filter.limit);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return events;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
clear(): void {
|
|
169
|
+
this.history = [];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private addToHistory(event: SwarmEvent): void {
|
|
173
|
+
this.history.push(event);
|
|
174
|
+
|
|
175
|
+
if (this.history.length > this.maxHistorySize) {
|
|
176
|
+
this.history = this.history.slice(-Math.floor(this.maxHistorySize / 2));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private async safeExecute(handler: EventHandler, event: SwarmEvent): Promise<void> {
|
|
181
|
+
try {
|
|
182
|
+
await handler(event);
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.error(`Event handler error for ${event.type}: ${err}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// =============================================================================
|
|
190
|
+
// In-Memory Event Store
|
|
191
|
+
// =============================================================================
|
|
192
|
+
|
|
193
|
+
export class InMemoryEventStore implements IEventStore {
|
|
194
|
+
private events: Map<string, SwarmEvent[]> = new Map();
|
|
195
|
+
private allEvents: SwarmEvent[] = [];
|
|
196
|
+
private snapshots: Map<string, EventStoreSnapshot> = new Map();
|
|
197
|
+
|
|
198
|
+
async append(event: SwarmEvent): Promise<void> {
|
|
199
|
+
const aggregateId = this.extractAggregateId(event);
|
|
200
|
+
|
|
201
|
+
if (!this.events.has(aggregateId)) {
|
|
202
|
+
this.events.set(aggregateId, []);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.events.get(aggregateId)!.push(event);
|
|
206
|
+
this.allEvents.push(event);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async getEvents(aggregateId: string, fromVersion?: number): Promise<SwarmEvent[]> {
|
|
210
|
+
const events = this.events.get(aggregateId) ?? [];
|
|
211
|
+
|
|
212
|
+
if (fromVersion !== undefined) {
|
|
213
|
+
return events.slice(fromVersion);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return events;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async getAllEvents(filter?: EventFilter): Promise<SwarmEvent[]> {
|
|
220
|
+
let events = [...this.allEvents];
|
|
221
|
+
|
|
222
|
+
if (filter?.types?.length) {
|
|
223
|
+
events = events.filter(e => filter.types!.includes(e.type));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (filter?.sources?.length) {
|
|
227
|
+
events = events.filter(e => filter.sources!.includes(e.source));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (filter?.since) {
|
|
231
|
+
events = events.filter(e => e.timestamp >= filter.since!);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (filter?.until) {
|
|
235
|
+
events = events.filter(e => e.timestamp <= filter.until!);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (filter?.limit) {
|
|
239
|
+
events = events.slice(-filter.limit);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return events;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async getSnapshot(aggregateId: string): Promise<EventStoreSnapshot | null> {
|
|
246
|
+
return this.snapshots.get(aggregateId) ?? null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async saveSnapshot(snapshot: EventStoreSnapshot): Promise<void> {
|
|
250
|
+
this.snapshots.set(snapshot.aggregateId, snapshot);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private extractAggregateId(event: SwarmEvent): string {
|
|
254
|
+
if (event.source !== 'swarm') {
|
|
255
|
+
return event.source;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (typeof event.payload === 'object' && event.payload !== null) {
|
|
259
|
+
const payload = event.payload as Record<string, unknown>;
|
|
260
|
+
if ('agentId' in payload) return payload.agentId as string;
|
|
261
|
+
if ('taskId' in payload) return payload.taskId as string;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return 'swarm';
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// =============================================================================
|
|
269
|
+
// Event Factory Functions
|
|
270
|
+
// =============================================================================
|
|
271
|
+
|
|
272
|
+
let eventCounter = 0;
|
|
273
|
+
|
|
274
|
+
export function createEvent<T>(
|
|
275
|
+
type: EventType,
|
|
276
|
+
source: AgentId | 'swarm',
|
|
277
|
+
payload: T
|
|
278
|
+
): SwarmEvent<T> {
|
|
279
|
+
return {
|
|
280
|
+
id: `evt-${Date.now()}-${++eventCounter}`,
|
|
281
|
+
type,
|
|
282
|
+
timestamp: Date.now(),
|
|
283
|
+
source,
|
|
284
|
+
payload
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Agent Events
|
|
289
|
+
export function agentSpawnedEvent(agentId: AgentId, role: string): SwarmEvent {
|
|
290
|
+
return createEvent('agent:spawned', 'swarm', { agentId, role });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function agentStatusChangedEvent(
|
|
294
|
+
agentId: AgentId,
|
|
295
|
+
previousStatus: string,
|
|
296
|
+
newStatus: string
|
|
297
|
+
): SwarmEvent {
|
|
298
|
+
return createEvent('agent:status-changed', agentId, { previousStatus, newStatus });
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function agentTaskAssignedEvent(agentId: AgentId, taskId: string): SwarmEvent {
|
|
302
|
+
return createEvent('agent:task-assigned', 'swarm', { agentId, taskId });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function agentTaskCompletedEvent(agentId: AgentId, taskId: string, result: unknown): SwarmEvent {
|
|
306
|
+
return createEvent('agent:task-completed', agentId, { taskId, result });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function agentErrorEvent(agentId: AgentId, error: Error): SwarmEvent {
|
|
310
|
+
return createEvent('agent:error', agentId, {
|
|
311
|
+
message: error.message,
|
|
312
|
+
stack: error.stack
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Task Events
|
|
317
|
+
export function taskCreatedEvent(taskId: string, type: string, title: string): SwarmEvent {
|
|
318
|
+
return createEvent('task:created', 'swarm', { taskId, type, title });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function taskQueuedEvent(taskId: string, priority: string): SwarmEvent {
|
|
322
|
+
return createEvent('task:queued', 'swarm', { taskId, priority });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function taskAssignedEvent(taskId: string, agentId: AgentId): SwarmEvent {
|
|
326
|
+
return createEvent('task:assigned', 'swarm', { taskId, agentId });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function taskStartedEvent(taskId: string, agentId: AgentId): SwarmEvent {
|
|
330
|
+
return createEvent('task:started', agentId, { taskId });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function taskCompletedEvent(taskId: string, result: unknown): SwarmEvent {
|
|
334
|
+
return createEvent('task:completed', 'swarm', { taskId, result });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function taskFailedEvent(taskId: string, error: Error): SwarmEvent {
|
|
338
|
+
return createEvent('task:failed', 'swarm', {
|
|
339
|
+
taskId,
|
|
340
|
+
error: error.message,
|
|
341
|
+
stack: error.stack
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function taskBlockedEvent(taskId: string, blockedBy: string[]): SwarmEvent {
|
|
346
|
+
return createEvent('task:blocked', 'swarm', { taskId, blockedBy });
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Swarm Events
|
|
350
|
+
export function swarmInitializedEvent(config: unknown): SwarmEvent {
|
|
351
|
+
return createEvent('swarm:initialized', 'swarm', { config });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export function swarmPhaseChangedEvent(previousPhase: string, newPhase: string): SwarmEvent {
|
|
355
|
+
return createEvent('swarm:phase-changed', 'swarm', { previousPhase, newPhase });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function swarmMilestoneReachedEvent(milestoneId: string, name: string): SwarmEvent {
|
|
359
|
+
return createEvent('swarm:milestone-reached', 'swarm', { milestoneId, name });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function swarmErrorEvent(error: Error): SwarmEvent {
|
|
363
|
+
return createEvent('swarm:error', 'swarm', {
|
|
364
|
+
message: error.message,
|
|
365
|
+
stack: error.stack
|
|
366
|
+
});
|
|
367
|
+
}
|