elsabro 2.3.0 → 3.8.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 +698 -20
- package/bin/install.js +0 -0
- package/flows/development-flow.json +452 -0
- package/flows/quick-flow.json +118 -0
- package/hooks/hooks-config-updated.json +285 -0
- package/hooks/skill-discovery.sh +539 -0
- package/package.json +3 -2
- package/references/SYSTEM_INDEX.md +400 -5
- package/references/agent-marketplace.md +2274 -0
- package/references/agent-protocol.md +1126 -0
- package/references/ai-code-suggestions.md +2413 -0
- package/references/checkpointing.md +595 -0
- package/references/collaboration-patterns.md +851 -0
- package/references/collaborative-sessions.md +1081 -0
- package/references/configuration-management.md +1810 -0
- package/references/cost-tracking.md +1095 -0
- package/references/enterprise-sso.md +2001 -0
- package/references/error-contracts-v2.md +968 -0
- package/references/event-driven.md +1031 -0
- package/references/flow-orchestration.md +940 -0
- package/references/flow-visualization.md +1557 -0
- package/references/ide-integrations.md +3513 -0
- package/references/interrupt-system.md +681 -0
- package/references/kubernetes-deployment.md +3099 -0
- package/references/memory-system.md +683 -0
- package/references/mobile-companion.md +3236 -0
- package/references/multi-llm-providers.md +2494 -0
- package/references/multi-project-memory.md +1182 -0
- package/references/observability.md +793 -0
- package/references/output-schemas.md +858 -0
- package/references/performance-profiler.md +955 -0
- package/references/plugin-system.md +1526 -0
- package/references/prompt-management.md +292 -0
- package/references/sandbox-execution.md +303 -0
- package/references/security-system.md +1253 -0
- package/references/skill-marketplace-integration.md +3901 -0
- package/references/streaming.md +696 -0
- package/references/testing-framework.md +1151 -0
- package/references/time-travel.md +802 -0
- package/references/tool-registry.md +886 -0
- package/references/voice-commands.md +3296 -0
- package/templates/agent-marketplace-config.json +220 -0
- package/templates/agent-protocol-config.json +136 -0
- package/templates/ai-suggestions-config.json +100 -0
- package/templates/checkpoint-state.json +61 -0
- package/templates/collaboration-config.json +157 -0
- package/templates/collaborative-sessions-config.json +153 -0
- package/templates/configuration-config.json +245 -0
- package/templates/cost-tracking-config.json +148 -0
- package/templates/enterprise-sso-config.json +438 -0
- package/templates/events-config.json +148 -0
- package/templates/flow-visualization-config.json +196 -0
- package/templates/ide-integrations-config.json +442 -0
- package/templates/kubernetes-config.json +764 -0
- package/templates/memory-state.json +84 -0
- package/templates/mobile-companion-config.json +600 -0
- package/templates/multi-llm-config.json +544 -0
- package/templates/multi-project-memory-config.json +145 -0
- package/templates/observability-config.json +109 -0
- package/templates/performance-profiler-config.json +125 -0
- package/templates/plugin-config.json +170 -0
- package/templates/prompt-management-config.json +86 -0
- package/templates/sandbox-config.json +185 -0
- package/templates/schemas-config.json +65 -0
- package/templates/security-config.json +120 -0
- package/templates/skill-marketplace-config.json +441 -0
- package/templates/streaming-config.json +72 -0
- package/templates/testing-config.json +81 -0
- package/templates/timetravel-config.json +62 -0
- package/templates/tool-registry-config.json +109 -0
- package/templates/voice-commands-config.json +658 -0
|
@@ -0,0 +1,1031 @@
|
|
|
1
|
+
# Event-Driven Architecture (v3.2)
|
|
2
|
+
|
|
3
|
+
Sistema de eventos para comunicación desacoplada entre componentes, event sourcing y webhooks externos.
|
|
4
|
+
|
|
5
|
+
## Arquitectura
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ EVENT-DRIVEN ARCHITECTURE │
|
|
10
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
11
|
+
│ │
|
|
12
|
+
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
13
|
+
│ │ EVENT BUS │ │
|
|
14
|
+
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
|
|
15
|
+
│ │ │ Publishers │ │ │
|
|
16
|
+
│ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │
|
|
17
|
+
│ │ │ │ Agents │ │ Flows │ │ External │ │ │ │
|
|
18
|
+
│ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │
|
|
19
|
+
│ │ │ │ │ │ │ │ │
|
|
20
|
+
│ │ │ └─────────────┼─────────────┘ │ │ │
|
|
21
|
+
│ │ │ ▼ │ │ │
|
|
22
|
+
│ │ │ ┌──────────────────────────────────────────────────┐ │ │ │
|
|
23
|
+
│ │ │ │ Event Router │ │ │ │
|
|
24
|
+
│ │ │ │ topic → [subscriber1, subscriber2, ...] │ │ │ │
|
|
25
|
+
│ │ │ └──────────────────────────────────────────────────┘ │ │ │
|
|
26
|
+
│ │ │ │ │ │ │
|
|
27
|
+
│ │ │ ┌─────────────┼─────────────┐ │ │ │
|
|
28
|
+
│ │ │ ▼ ▼ ▼ │ │ │
|
|
29
|
+
│ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │
|
|
30
|
+
│ │ │ │ Handler1 │ │ Handler2 │ │ Webhook │ │ │ │
|
|
31
|
+
│ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │
|
|
32
|
+
│ │ └─────────────────────────────────────────────────────────┘ │ │
|
|
33
|
+
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
34
|
+
│ │ │
|
|
35
|
+
│ ▼ │
|
|
36
|
+
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
37
|
+
│ │ EVENT STORE │ │
|
|
38
|
+
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
|
|
39
|
+
│ │ │ Append-Only Log │ │ │
|
|
40
|
+
│ │ │ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │ │ │
|
|
41
|
+
│ │ │ │ E001 │→│ E002 │→│ E003 │→│ E004 │→│ E005 │→ ... │ │ │
|
|
42
|
+
│ │ │ └───────┘ └───────┘ └───────┘ └───────┘ └───────┘ │ │ │
|
|
43
|
+
│ │ └─────────────────────────────────────────────────────────┘ │ │
|
|
44
|
+
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
|
|
45
|
+
│ │ │ Projections (Read Models) │ │ │
|
|
46
|
+
│ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │
|
|
47
|
+
│ │ │ │ TaskView │ │ FlowView │ │ AgentView│ │ │ │
|
|
48
|
+
│ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │
|
|
49
|
+
│ │ └─────────────────────────────────────────────────────────┘ │ │
|
|
50
|
+
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
51
|
+
│ │
|
|
52
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## EventBus
|
|
58
|
+
|
|
59
|
+
### API Principal
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
interface Event<T = unknown> {
|
|
63
|
+
id: string;
|
|
64
|
+
type: string;
|
|
65
|
+
topic: string;
|
|
66
|
+
timestamp: string;
|
|
67
|
+
source: string;
|
|
68
|
+
correlationId?: string;
|
|
69
|
+
causationId?: string;
|
|
70
|
+
metadata?: Record<string, unknown>;
|
|
71
|
+
payload: T;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface Subscription {
|
|
75
|
+
id: string;
|
|
76
|
+
topic: string;
|
|
77
|
+
pattern?: string;
|
|
78
|
+
handler: EventHandler;
|
|
79
|
+
filter?: EventFilter;
|
|
80
|
+
options?: SubscriptionOptions;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface SubscriptionOptions {
|
|
84
|
+
queue?: string; // For competing consumers
|
|
85
|
+
durable?: boolean; // Survive restarts
|
|
86
|
+
ack?: 'auto' | 'manual'; // Acknowledgment mode
|
|
87
|
+
maxRetries?: number;
|
|
88
|
+
retryDelay?: number;
|
|
89
|
+
timeout?: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
type EventHandler<T = unknown> = (event: Event<T>) => Promise<void>;
|
|
93
|
+
type EventFilter = (event: Event) => boolean;
|
|
94
|
+
|
|
95
|
+
class EventBus {
|
|
96
|
+
private subscriptions: Map<string, Set<Subscription>>;
|
|
97
|
+
private eventStore: EventStore;
|
|
98
|
+
private webhookManager: WebhookManager;
|
|
99
|
+
private config: EventBusConfig;
|
|
100
|
+
|
|
101
|
+
constructor(config: EventBusConfig) {
|
|
102
|
+
this.config = config;
|
|
103
|
+
this.subscriptions = new Map();
|
|
104
|
+
this.eventStore = new EventStore(config.store);
|
|
105
|
+
this.webhookManager = new WebhookManager(config.webhooks);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Publish event to topic
|
|
109
|
+
async publish<T>(topic: string, payload: T, options?: PublishOptions): Promise<Event<T>> {
|
|
110
|
+
const event: Event<T> = {
|
|
111
|
+
id: this.generateId(),
|
|
112
|
+
type: options?.type || this.inferType(topic),
|
|
113
|
+
topic,
|
|
114
|
+
timestamp: new Date().toISOString(),
|
|
115
|
+
source: options?.source || 'elsabro',
|
|
116
|
+
correlationId: options?.correlationId,
|
|
117
|
+
causationId: options?.causationId,
|
|
118
|
+
metadata: options?.metadata,
|
|
119
|
+
payload
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Store event (event sourcing)
|
|
123
|
+
if (this.config.store.enabled) {
|
|
124
|
+
await this.eventStore.append(event);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Notify local subscribers
|
|
128
|
+
await this.notifySubscribers(event);
|
|
129
|
+
|
|
130
|
+
// Trigger webhooks
|
|
131
|
+
if (this.config.webhooks.enabled) {
|
|
132
|
+
await this.webhookManager.trigger(event);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Emit telemetry
|
|
136
|
+
TelemetryManager.emit({
|
|
137
|
+
name: 'event.published',
|
|
138
|
+
attributes: {
|
|
139
|
+
event_id: event.id,
|
|
140
|
+
topic: event.topic,
|
|
141
|
+
type: event.type
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return event;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Publish multiple events atomically
|
|
149
|
+
async publishBatch<T>(events: Array<{ topic: string; payload: T; options?: PublishOptions }>): Promise<Event<T>[]> {
|
|
150
|
+
const published: Event<T>[] = [];
|
|
151
|
+
|
|
152
|
+
// Store all events atomically
|
|
153
|
+
const eventRecords = events.map(e => ({
|
|
154
|
+
id: this.generateId(),
|
|
155
|
+
type: e.options?.type || this.inferType(e.topic),
|
|
156
|
+
topic: e.topic,
|
|
157
|
+
timestamp: new Date().toISOString(),
|
|
158
|
+
source: e.options?.source || 'elsabro',
|
|
159
|
+
correlationId: e.options?.correlationId,
|
|
160
|
+
payload: e.payload
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
if (this.config.store.enabled) {
|
|
164
|
+
await this.eventStore.appendBatch(eventRecords);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Notify subscribers for each event
|
|
168
|
+
for (const event of eventRecords) {
|
|
169
|
+
await this.notifySubscribers(event);
|
|
170
|
+
published.push(event);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return published;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Subscribe to topic
|
|
177
|
+
subscribe<T>(
|
|
178
|
+
topic: string,
|
|
179
|
+
handler: EventHandler<T>,
|
|
180
|
+
options?: SubscriptionOptions
|
|
181
|
+
): Subscription {
|
|
182
|
+
const subscription: Subscription = {
|
|
183
|
+
id: this.generateId(),
|
|
184
|
+
topic,
|
|
185
|
+
handler: handler as EventHandler,
|
|
186
|
+
options
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
if (!this.subscriptions.has(topic)) {
|
|
190
|
+
this.subscriptions.set(topic, new Set());
|
|
191
|
+
}
|
|
192
|
+
this.subscriptions.get(topic)!.add(subscription);
|
|
193
|
+
|
|
194
|
+
return subscription;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Subscribe with pattern matching
|
|
198
|
+
subscribePattern<T>(
|
|
199
|
+
pattern: string,
|
|
200
|
+
handler: EventHandler<T>,
|
|
201
|
+
options?: SubscriptionOptions
|
|
202
|
+
): Subscription {
|
|
203
|
+
const subscription: Subscription = {
|
|
204
|
+
id: this.generateId(),
|
|
205
|
+
topic: '*',
|
|
206
|
+
pattern,
|
|
207
|
+
handler: handler as EventHandler,
|
|
208
|
+
options
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
if (!this.subscriptions.has('*')) {
|
|
212
|
+
this.subscriptions.set('*', new Set());
|
|
213
|
+
}
|
|
214
|
+
this.subscriptions.get('*')!.add(subscription);
|
|
215
|
+
|
|
216
|
+
return subscription;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Subscribe with filter
|
|
220
|
+
subscribeFiltered<T>(
|
|
221
|
+
topic: string,
|
|
222
|
+
handler: EventHandler<T>,
|
|
223
|
+
filter: EventFilter,
|
|
224
|
+
options?: SubscriptionOptions
|
|
225
|
+
): Subscription {
|
|
226
|
+
const subscription: Subscription = {
|
|
227
|
+
id: this.generateId(),
|
|
228
|
+
topic,
|
|
229
|
+
handler: handler as EventHandler,
|
|
230
|
+
filter,
|
|
231
|
+
options
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
if (!this.subscriptions.has(topic)) {
|
|
235
|
+
this.subscriptions.set(topic, new Set());
|
|
236
|
+
}
|
|
237
|
+
this.subscriptions.get(topic)!.add(subscription);
|
|
238
|
+
|
|
239
|
+
return subscription;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Unsubscribe
|
|
243
|
+
unsubscribe(subscriptionId: string): boolean {
|
|
244
|
+
for (const [_, subs] of this.subscriptions) {
|
|
245
|
+
for (const sub of subs) {
|
|
246
|
+
if (sub.id === subscriptionId) {
|
|
247
|
+
subs.delete(sub);
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Wait for event (one-time)
|
|
256
|
+
async once<T>(topic: string, timeout?: number): Promise<Event<T>> {
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
const timeoutId = timeout
|
|
259
|
+
? setTimeout(() => {
|
|
260
|
+
this.unsubscribe(sub.id);
|
|
261
|
+
reject(new Error('Event timeout'));
|
|
262
|
+
}, timeout)
|
|
263
|
+
: null;
|
|
264
|
+
|
|
265
|
+
const sub = this.subscribe<T>(topic, async (event) => {
|
|
266
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
267
|
+
this.unsubscribe(sub.id);
|
|
268
|
+
resolve(event);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Replay events from store
|
|
274
|
+
async replay(
|
|
275
|
+
topic: string,
|
|
276
|
+
from: string | Date,
|
|
277
|
+
to?: string | Date,
|
|
278
|
+
handler?: EventHandler
|
|
279
|
+
): Promise<Event[]> {
|
|
280
|
+
const events = await this.eventStore.query({
|
|
281
|
+
topic,
|
|
282
|
+
from: typeof from === 'string' ? from : from.toISOString(),
|
|
283
|
+
to: to ? (typeof to === 'string' ? to : to.toISOString()) : undefined
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (handler) {
|
|
287
|
+
for (const event of events) {
|
|
288
|
+
await handler(event);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return events;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Get event history
|
|
296
|
+
async history(options: HistoryOptions): Promise<Event[]> {
|
|
297
|
+
return this.eventStore.query(options);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private async notifySubscribers(event: Event): Promise<void> {
|
|
301
|
+
const directSubs = this.subscriptions.get(event.topic) || new Set();
|
|
302
|
+
const patternSubs = this.subscriptions.get('*') || new Set();
|
|
303
|
+
|
|
304
|
+
const allSubs = [...directSubs, ...patternSubs];
|
|
305
|
+
|
|
306
|
+
for (const sub of allSubs) {
|
|
307
|
+
// Check pattern match
|
|
308
|
+
if (sub.pattern && !this.matchPattern(event.topic, sub.pattern)) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Check filter
|
|
313
|
+
if (sub.filter && !sub.filter(event)) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Handle with retry logic
|
|
318
|
+
await this.handleWithRetry(sub, event);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private async handleWithRetry(sub: Subscription, event: Event): Promise<void> {
|
|
323
|
+
const maxRetries = sub.options?.maxRetries || 3;
|
|
324
|
+
const retryDelay = sub.options?.retryDelay || 1000;
|
|
325
|
+
|
|
326
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
327
|
+
try {
|
|
328
|
+
const timeout = sub.options?.timeout || 30000;
|
|
329
|
+
await Promise.race([
|
|
330
|
+
sub.handler(event),
|
|
331
|
+
new Promise((_, reject) =>
|
|
332
|
+
setTimeout(() => reject(new Error('Handler timeout')), timeout)
|
|
333
|
+
)
|
|
334
|
+
]);
|
|
335
|
+
return;
|
|
336
|
+
} catch (error) {
|
|
337
|
+
if (attempt === maxRetries) {
|
|
338
|
+
// Move to dead letter queue
|
|
339
|
+
await this.eventStore.appendDeadLetter(event, error as Error, sub.id);
|
|
340
|
+
TelemetryManager.emit({
|
|
341
|
+
name: 'event.handler.failed',
|
|
342
|
+
attributes: {
|
|
343
|
+
event_id: event.id,
|
|
344
|
+
subscription_id: sub.id,
|
|
345
|
+
error: (error as Error).message
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
} else {
|
|
349
|
+
await this.delay(retryDelay * Math.pow(2, attempt));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private matchPattern(topic: string, pattern: string): boolean {
|
|
356
|
+
const regex = new RegExp(
|
|
357
|
+
'^' + pattern.replace(/\*/g, '[^.]+').replace(/#/g, '.*') + '$'
|
|
358
|
+
);
|
|
359
|
+
return regex.test(topic);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private inferType(topic: string): string {
|
|
363
|
+
// agent.started → AgentStarted
|
|
364
|
+
return topic
|
|
365
|
+
.split('.')
|
|
366
|
+
.map(p => p.charAt(0).toUpperCase() + p.slice(1))
|
|
367
|
+
.join('');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private generateId(): string {
|
|
371
|
+
return `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private delay(ms: number): Promise<void> {
|
|
375
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## EventStore
|
|
383
|
+
|
|
384
|
+
### Event Sourcing
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
interface EventStoreConfig {
|
|
388
|
+
enabled: boolean;
|
|
389
|
+
backend: 'file' | 'sqlite' | 'postgres' | 'memory';
|
|
390
|
+
path?: string;
|
|
391
|
+
connectionString?: string;
|
|
392
|
+
retention?: {
|
|
393
|
+
maxEvents?: number;
|
|
394
|
+
maxAge?: string; // "30d", "1y"
|
|
395
|
+
};
|
|
396
|
+
snapshots?: {
|
|
397
|
+
enabled: boolean;
|
|
398
|
+
frequency: number; // Every N events
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
class EventStore {
|
|
403
|
+
private backend: EventStoreBackend;
|
|
404
|
+
private config: EventStoreConfig;
|
|
405
|
+
private sequence: number = 0;
|
|
406
|
+
|
|
407
|
+
constructor(config: EventStoreConfig) {
|
|
408
|
+
this.config = config;
|
|
409
|
+
this.backend = this.createBackend(config);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Append single event
|
|
413
|
+
async append(event: Event): Promise<void> {
|
|
414
|
+
const record: StoredEvent = {
|
|
415
|
+
...event,
|
|
416
|
+
sequence: ++this.sequence,
|
|
417
|
+
storedAt: new Date().toISOString()
|
|
418
|
+
};
|
|
419
|
+
await this.backend.append(record);
|
|
420
|
+
|
|
421
|
+
// Check for snapshot
|
|
422
|
+
if (this.config.snapshots?.enabled) {
|
|
423
|
+
if (this.sequence % this.config.snapshots.frequency === 0) {
|
|
424
|
+
await this.createSnapshot();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Append batch atomically
|
|
430
|
+
async appendBatch(events: Event[]): Promise<void> {
|
|
431
|
+
const records: StoredEvent[] = events.map(event => ({
|
|
432
|
+
...event,
|
|
433
|
+
sequence: ++this.sequence,
|
|
434
|
+
storedAt: new Date().toISOString()
|
|
435
|
+
}));
|
|
436
|
+
await this.backend.appendBatch(records);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Append to dead letter queue
|
|
440
|
+
async appendDeadLetter(event: Event, error: Error, subscriberId: string): Promise<void> {
|
|
441
|
+
const deadLetter: DeadLetterEvent = {
|
|
442
|
+
originalEvent: event,
|
|
443
|
+
error: {
|
|
444
|
+
message: error.message,
|
|
445
|
+
stack: error.stack
|
|
446
|
+
},
|
|
447
|
+
subscriberId,
|
|
448
|
+
failedAt: new Date().toISOString(),
|
|
449
|
+
retryCount: 0
|
|
450
|
+
};
|
|
451
|
+
await this.backend.appendDeadLetter(deadLetter);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Query events
|
|
455
|
+
async query(options: QueryOptions): Promise<Event[]> {
|
|
456
|
+
return this.backend.query(options);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Get events by correlation ID
|
|
460
|
+
async getByCorrelation(correlationId: string): Promise<Event[]> {
|
|
461
|
+
return this.backend.query({ correlationId });
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Get stream of events for aggregate
|
|
465
|
+
async getStream(aggregateType: string, aggregateId: string): Promise<Event[]> {
|
|
466
|
+
return this.backend.query({
|
|
467
|
+
topic: `${aggregateType}.${aggregateId}.*`
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Create snapshot
|
|
472
|
+
async createSnapshot(): Promise<void> {
|
|
473
|
+
const state = await this.computeState();
|
|
474
|
+
await this.backend.saveSnapshot({
|
|
475
|
+
sequence: this.sequence,
|
|
476
|
+
state,
|
|
477
|
+
createdAt: new Date().toISOString()
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Restore from snapshot
|
|
482
|
+
async restoreFromSnapshot(): Promise<void> {
|
|
483
|
+
const snapshot = await this.backend.getLatestSnapshot();
|
|
484
|
+
if (snapshot) {
|
|
485
|
+
this.sequence = snapshot.sequence;
|
|
486
|
+
// Restore state...
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Rebuild projections
|
|
491
|
+
async rebuildProjection(
|
|
492
|
+
projectionName: string,
|
|
493
|
+
handler: (event: Event) => Promise<void>
|
|
494
|
+
): Promise<void> {
|
|
495
|
+
const events = await this.backend.query({ from: '0' });
|
|
496
|
+
for (const event of events) {
|
|
497
|
+
await handler(event);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Cleanup old events
|
|
502
|
+
async cleanup(): Promise<number> {
|
|
503
|
+
if (!this.config.retention) return 0;
|
|
504
|
+
|
|
505
|
+
const cutoff = this.config.retention.maxAge
|
|
506
|
+
? this.calculateCutoff(this.config.retention.maxAge)
|
|
507
|
+
: undefined;
|
|
508
|
+
|
|
509
|
+
return this.backend.deleteOlderThan(cutoff);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
private createBackend(config: EventStoreConfig): EventStoreBackend {
|
|
513
|
+
switch (config.backend) {
|
|
514
|
+
case 'file':
|
|
515
|
+
return new FileEventStoreBackend(config.path!);
|
|
516
|
+
case 'sqlite':
|
|
517
|
+
return new SQLiteEventStoreBackend(config.path!);
|
|
518
|
+
case 'postgres':
|
|
519
|
+
return new PostgresEventStoreBackend(config.connectionString!);
|
|
520
|
+
default:
|
|
521
|
+
return new MemoryEventStoreBackend();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private calculateCutoff(maxAge: string): Date {
|
|
526
|
+
const match = maxAge.match(/^(\d+)([dmy])$/);
|
|
527
|
+
if (!match) throw new Error('Invalid maxAge format');
|
|
528
|
+
|
|
529
|
+
const value = parseInt(match[1]);
|
|
530
|
+
const unit = match[2];
|
|
531
|
+
const now = new Date();
|
|
532
|
+
|
|
533
|
+
switch (unit) {
|
|
534
|
+
case 'd': return new Date(now.setDate(now.getDate() - value));
|
|
535
|
+
case 'm': return new Date(now.setMonth(now.getMonth() - value));
|
|
536
|
+
case 'y': return new Date(now.setFullYear(now.getFullYear() - value));
|
|
537
|
+
default: return now;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
private async computeState(): Promise<unknown> {
|
|
542
|
+
// Compute current state from events
|
|
543
|
+
return {};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## WebhookManager
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
interface WebhookConfig {
|
|
554
|
+
enabled: boolean;
|
|
555
|
+
endpoints: WebhookEndpoint[];
|
|
556
|
+
signing: {
|
|
557
|
+
enabled: boolean;
|
|
558
|
+
algorithm: 'sha256' | 'sha512';
|
|
559
|
+
header: string;
|
|
560
|
+
};
|
|
561
|
+
retry: {
|
|
562
|
+
maxAttempts: number;
|
|
563
|
+
backoff: 'linear' | 'exponential';
|
|
564
|
+
initialDelay: number;
|
|
565
|
+
maxDelay: number;
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
interface WebhookEndpoint {
|
|
570
|
+
id: string;
|
|
571
|
+
url: string;
|
|
572
|
+
events: string[]; // Topics to subscribe to
|
|
573
|
+
secret?: string;
|
|
574
|
+
headers?: Record<string, string>;
|
|
575
|
+
enabled: boolean;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
interface WebhookDelivery {
|
|
579
|
+
id: string;
|
|
580
|
+
endpointId: string;
|
|
581
|
+
eventId: string;
|
|
582
|
+
status: 'pending' | 'success' | 'failed';
|
|
583
|
+
attempts: number;
|
|
584
|
+
response?: {
|
|
585
|
+
status: number;
|
|
586
|
+
body: string;
|
|
587
|
+
};
|
|
588
|
+
error?: string;
|
|
589
|
+
deliveredAt?: string;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
class WebhookManager {
|
|
593
|
+
private config: WebhookConfig;
|
|
594
|
+
private deliveryLog: Map<string, WebhookDelivery>;
|
|
595
|
+
|
|
596
|
+
constructor(config: WebhookConfig) {
|
|
597
|
+
this.config = config;
|
|
598
|
+
this.deliveryLog = new Map();
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Trigger webhooks for event
|
|
602
|
+
async trigger(event: Event): Promise<WebhookDelivery[]> {
|
|
603
|
+
const matchingEndpoints = this.config.endpoints.filter(ep =>
|
|
604
|
+
ep.enabled && this.matchesTopic(event.topic, ep.events)
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
const deliveries: WebhookDelivery[] = [];
|
|
608
|
+
|
|
609
|
+
for (const endpoint of matchingEndpoints) {
|
|
610
|
+
const delivery = await this.deliver(endpoint, event);
|
|
611
|
+
deliveries.push(delivery);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return deliveries;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Deliver to single endpoint
|
|
618
|
+
private async deliver(endpoint: WebhookEndpoint, event: Event): Promise<WebhookDelivery> {
|
|
619
|
+
const deliveryId = this.generateId();
|
|
620
|
+
const delivery: WebhookDelivery = {
|
|
621
|
+
id: deliveryId,
|
|
622
|
+
endpointId: endpoint.id,
|
|
623
|
+
eventId: event.id,
|
|
624
|
+
status: 'pending',
|
|
625
|
+
attempts: 0
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
this.deliveryLog.set(deliveryId, delivery);
|
|
629
|
+
|
|
630
|
+
// Prepare payload
|
|
631
|
+
const payload = JSON.stringify({
|
|
632
|
+
event: event.type,
|
|
633
|
+
timestamp: event.timestamp,
|
|
634
|
+
data: event.payload
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// Sign payload
|
|
638
|
+
const signature = this.config.signing.enabled && endpoint.secret
|
|
639
|
+
? this.sign(payload, endpoint.secret)
|
|
640
|
+
: undefined;
|
|
641
|
+
|
|
642
|
+
// Deliver with retry
|
|
643
|
+
for (let attempt = 1; attempt <= this.config.retry.maxAttempts; attempt++) {
|
|
644
|
+
delivery.attempts = attempt;
|
|
645
|
+
|
|
646
|
+
try {
|
|
647
|
+
const response = await fetch(endpoint.url, {
|
|
648
|
+
method: 'POST',
|
|
649
|
+
headers: {
|
|
650
|
+
'Content-Type': 'application/json',
|
|
651
|
+
...endpoint.headers,
|
|
652
|
+
...(signature ? { [this.config.signing.header]: signature } : {})
|
|
653
|
+
},
|
|
654
|
+
body: payload,
|
|
655
|
+
signal: AbortSignal.timeout(30000)
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
delivery.response = {
|
|
659
|
+
status: response.status,
|
|
660
|
+
body: await response.text()
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
if (response.ok) {
|
|
664
|
+
delivery.status = 'success';
|
|
665
|
+
delivery.deliveredAt = new Date().toISOString();
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
} catch (error) {
|
|
669
|
+
delivery.error = (error as Error).message;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Retry delay
|
|
673
|
+
if (attempt < this.config.retry.maxAttempts) {
|
|
674
|
+
const delay = this.calculateDelay(attempt);
|
|
675
|
+
await this.delay(delay);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (delivery.status !== 'success') {
|
|
680
|
+
delivery.status = 'failed';
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return delivery;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Register new endpoint
|
|
687
|
+
async registerEndpoint(endpoint: Omit<WebhookEndpoint, 'id'>): Promise<WebhookEndpoint> {
|
|
688
|
+
const newEndpoint: WebhookEndpoint = {
|
|
689
|
+
id: this.generateId(),
|
|
690
|
+
...endpoint
|
|
691
|
+
};
|
|
692
|
+
this.config.endpoints.push(newEndpoint);
|
|
693
|
+
return newEndpoint;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Update endpoint
|
|
697
|
+
async updateEndpoint(id: string, updates: Partial<WebhookEndpoint>): Promise<WebhookEndpoint | null> {
|
|
698
|
+
const index = this.config.endpoints.findIndex(ep => ep.id === id);
|
|
699
|
+
if (index === -1) return null;
|
|
700
|
+
|
|
701
|
+
this.config.endpoints[index] = {
|
|
702
|
+
...this.config.endpoints[index],
|
|
703
|
+
...updates
|
|
704
|
+
};
|
|
705
|
+
return this.config.endpoints[index];
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Delete endpoint
|
|
709
|
+
async deleteEndpoint(id: string): Promise<boolean> {
|
|
710
|
+
const index = this.config.endpoints.findIndex(ep => ep.id === id);
|
|
711
|
+
if (index === -1) return false;
|
|
712
|
+
this.config.endpoints.splice(index, 1);
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Get delivery history
|
|
717
|
+
getDeliveries(endpointId?: string): WebhookDelivery[] {
|
|
718
|
+
const deliveries = Array.from(this.deliveryLog.values());
|
|
719
|
+
return endpointId
|
|
720
|
+
? deliveries.filter(d => d.endpointId === endpointId)
|
|
721
|
+
: deliveries;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Retry failed delivery
|
|
725
|
+
async retryDelivery(deliveryId: string): Promise<WebhookDelivery | null> {
|
|
726
|
+
const delivery = this.deliveryLog.get(deliveryId);
|
|
727
|
+
if (!delivery || delivery.status !== 'failed') return null;
|
|
728
|
+
|
|
729
|
+
const endpoint = this.config.endpoints.find(ep => ep.id === delivery.endpointId);
|
|
730
|
+
if (!endpoint) return null;
|
|
731
|
+
|
|
732
|
+
// Get original event from store
|
|
733
|
+
const eventStore = new EventStore(this.config as any);
|
|
734
|
+
const events = await eventStore.query({ id: delivery.eventId });
|
|
735
|
+
if (events.length === 0) return null;
|
|
736
|
+
|
|
737
|
+
return this.deliver(endpoint, events[0]);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
private matchesTopic(topic: string, patterns: string[]): boolean {
|
|
741
|
+
return patterns.some(pattern => {
|
|
742
|
+
if (pattern === '*') return true;
|
|
743
|
+
if (pattern.endsWith('.*')) {
|
|
744
|
+
return topic.startsWith(pattern.slice(0, -2));
|
|
745
|
+
}
|
|
746
|
+
return topic === pattern;
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
private sign(payload: string, secret: string): string {
|
|
751
|
+
const crypto = require('crypto');
|
|
752
|
+
const algorithm = this.config.signing.algorithm === 'sha512' ? 'sha512' : 'sha256';
|
|
753
|
+
return crypto.createHmac(algorithm, secret).update(payload).digest('hex');
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
private calculateDelay(attempt: number): number {
|
|
757
|
+
const { backoff, initialDelay, maxDelay } = this.config.retry;
|
|
758
|
+
|
|
759
|
+
let delay = initialDelay;
|
|
760
|
+
if (backoff === 'exponential') {
|
|
761
|
+
delay = initialDelay * Math.pow(2, attempt - 1);
|
|
762
|
+
} else {
|
|
763
|
+
delay = initialDelay * attempt;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return Math.min(delay, maxDelay);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
private generateId(): string {
|
|
770
|
+
return `wh_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
private delay(ms: number): Promise<void> {
|
|
774
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
---
|
|
780
|
+
|
|
781
|
+
## Event Types Predefinidos
|
|
782
|
+
|
|
783
|
+
```typescript
|
|
784
|
+
// Agent Events
|
|
785
|
+
interface AgentStartedEvent {
|
|
786
|
+
agentId: string;
|
|
787
|
+
agentType: string;
|
|
788
|
+
taskId: string;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
interface AgentCompletedEvent {
|
|
792
|
+
agentId: string;
|
|
793
|
+
result: unknown;
|
|
794
|
+
duration_ms: number;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
interface AgentFailedEvent {
|
|
798
|
+
agentId: string;
|
|
799
|
+
error: string;
|
|
800
|
+
recoverable: boolean;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Flow Events
|
|
804
|
+
interface FlowStartedEvent {
|
|
805
|
+
flowId: string;
|
|
806
|
+
flowName: string;
|
|
807
|
+
input: unknown;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
interface FlowNodeEnteredEvent {
|
|
811
|
+
flowId: string;
|
|
812
|
+
nodeId: string;
|
|
813
|
+
nodeType: string;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
interface FlowCompletedEvent {
|
|
817
|
+
flowId: string;
|
|
818
|
+
result: unknown;
|
|
819
|
+
duration_ms: number;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Task Events
|
|
823
|
+
interface TaskCreatedEvent {
|
|
824
|
+
taskId: string;
|
|
825
|
+
subject: string;
|
|
826
|
+
description: string;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
interface TaskStatusChangedEvent {
|
|
830
|
+
taskId: string;
|
|
831
|
+
oldStatus: string;
|
|
832
|
+
newStatus: string;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Checkpoint Events
|
|
836
|
+
interface CheckpointCreatedEvent {
|
|
837
|
+
checkpointId: string;
|
|
838
|
+
type: 'auto' | 'manual' | 'interrupt';
|
|
839
|
+
reason: string;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
interface CheckpointRestoredEvent {
|
|
843
|
+
checkpointId: string;
|
|
844
|
+
previousCheckpointId?: string;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Memory Events
|
|
848
|
+
interface MemoryStoredEvent {
|
|
849
|
+
level: 'short-term' | 'long-term' | 'entity' | 'contextual';
|
|
850
|
+
key: string;
|
|
851
|
+
size: number;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Sandbox Events
|
|
855
|
+
interface SandboxExecutionStartedEvent {
|
|
856
|
+
executionId: string;
|
|
857
|
+
language: string;
|
|
858
|
+
provider: string;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
interface SandboxExecutionCompletedEvent {
|
|
862
|
+
executionId: string;
|
|
863
|
+
exitCode: number;
|
|
864
|
+
duration_ms: number;
|
|
865
|
+
}
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
---
|
|
869
|
+
|
|
870
|
+
## Integración con ELSABRO
|
|
871
|
+
|
|
872
|
+
### Uso en Flows
|
|
873
|
+
|
|
874
|
+
```typescript
|
|
875
|
+
// Emit events during flow execution
|
|
876
|
+
class FlowEngine {
|
|
877
|
+
private eventBus: EventBus;
|
|
878
|
+
|
|
879
|
+
async executeFlow(flowId: string, input: unknown): Promise<unknown> {
|
|
880
|
+
// Emit flow started
|
|
881
|
+
await this.eventBus.publish('flow.started', {
|
|
882
|
+
flowId,
|
|
883
|
+
flowName: this.getFlowName(flowId),
|
|
884
|
+
input
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
try {
|
|
888
|
+
const result = await this.runNodes(flowId, input);
|
|
889
|
+
|
|
890
|
+
// Emit flow completed
|
|
891
|
+
await this.eventBus.publish('flow.completed', {
|
|
892
|
+
flowId,
|
|
893
|
+
result,
|
|
894
|
+
duration_ms: this.getDuration()
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
return result;
|
|
898
|
+
} catch (error) {
|
|
899
|
+
// Emit flow failed
|
|
900
|
+
await this.eventBus.publish('flow.failed', {
|
|
901
|
+
flowId,
|
|
902
|
+
error: (error as Error).message
|
|
903
|
+
});
|
|
904
|
+
throw error;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
### Uso en Agentes
|
|
911
|
+
|
|
912
|
+
```typescript
|
|
913
|
+
// Emit events during agent execution
|
|
914
|
+
class AgentRunner {
|
|
915
|
+
private eventBus: EventBus;
|
|
916
|
+
|
|
917
|
+
async runAgent(agent: Agent, task: Task): Promise<AgentResult> {
|
|
918
|
+
const agentId = this.generateId();
|
|
919
|
+
|
|
920
|
+
// Emit agent started
|
|
921
|
+
await this.eventBus.publish('agent.started', {
|
|
922
|
+
agentId,
|
|
923
|
+
agentType: agent.type,
|
|
924
|
+
taskId: task.id
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
try {
|
|
928
|
+
const result = await agent.run(task);
|
|
929
|
+
|
|
930
|
+
// Emit agent completed
|
|
931
|
+
await this.eventBus.publish('agent.completed', {
|
|
932
|
+
agentId,
|
|
933
|
+
result,
|
|
934
|
+
duration_ms: this.getDuration()
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
return result;
|
|
938
|
+
} catch (error) {
|
|
939
|
+
// Emit agent failed
|
|
940
|
+
await this.eventBus.publish('agent.failed', {
|
|
941
|
+
agentId,
|
|
942
|
+
error: (error as Error).message,
|
|
943
|
+
recoverable: this.isRecoverable(error)
|
|
944
|
+
});
|
|
945
|
+
throw error;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
### Suscripción a Eventos
|
|
952
|
+
|
|
953
|
+
```typescript
|
|
954
|
+
// En commands o skills
|
|
955
|
+
const eventBus = new EventBus(config);
|
|
956
|
+
|
|
957
|
+
// Subscribe to all agent events
|
|
958
|
+
eventBus.subscribePattern('agent.*', async (event) => {
|
|
959
|
+
console.log(`Agent event: ${event.type}`, event.payload);
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
// Subscribe to specific event with filter
|
|
963
|
+
eventBus.subscribeFiltered(
|
|
964
|
+
'task.status.changed',
|
|
965
|
+
async (event: Event<TaskStatusChangedEvent>) => {
|
|
966
|
+
// Only handle completed tasks
|
|
967
|
+
if (event.payload.newStatus === 'completed') {
|
|
968
|
+
await notifyUser(event);
|
|
969
|
+
}
|
|
970
|
+
},
|
|
971
|
+
(event) => event.payload.newStatus === 'completed'
|
|
972
|
+
);
|
|
973
|
+
|
|
974
|
+
// Wait for specific event
|
|
975
|
+
const result = await eventBus.once<FlowCompletedEvent>('flow.completed', 60000);
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
---
|
|
979
|
+
|
|
980
|
+
## Configuración
|
|
981
|
+
|
|
982
|
+
```json
|
|
983
|
+
{
|
|
984
|
+
"events": {
|
|
985
|
+
"enabled": true,
|
|
986
|
+
"bus": {
|
|
987
|
+
"maxListeners": 100,
|
|
988
|
+
"defaultTimeout": 30000
|
|
989
|
+
},
|
|
990
|
+
"store": {
|
|
991
|
+
"enabled": true,
|
|
992
|
+
"backend": "file",
|
|
993
|
+
"path": ".elsabro/events",
|
|
994
|
+
"retention": {
|
|
995
|
+
"maxEvents": 10000,
|
|
996
|
+
"maxAge": "30d"
|
|
997
|
+
},
|
|
998
|
+
"snapshots": {
|
|
999
|
+
"enabled": true,
|
|
1000
|
+
"frequency": 100
|
|
1001
|
+
}
|
|
1002
|
+
},
|
|
1003
|
+
"webhooks": {
|
|
1004
|
+
"enabled": true,
|
|
1005
|
+
"endpoints": [],
|
|
1006
|
+
"signing": {
|
|
1007
|
+
"enabled": true,
|
|
1008
|
+
"algorithm": "sha256",
|
|
1009
|
+
"header": "X-Webhook-Signature"
|
|
1010
|
+
},
|
|
1011
|
+
"retry": {
|
|
1012
|
+
"maxAttempts": 3,
|
|
1013
|
+
"backoff": "exponential",
|
|
1014
|
+
"initialDelay": 1000,
|
|
1015
|
+
"maxDelay": 30000
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
|
|
1024
|
+
## Changelog
|
|
1025
|
+
|
|
1026
|
+
- **v3.2.0**: Initial Event-Driven Architecture
|
|
1027
|
+
- EventBus with pub/sub
|
|
1028
|
+
- EventStore for event sourcing
|
|
1029
|
+
- WebhookManager for external integrations
|
|
1030
|
+
- Predefined event types
|
|
1031
|
+
- Integration with flows and agents
|