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.
Files changed (71) hide show
  1. package/README.md +698 -20
  2. package/bin/install.js +0 -0
  3. package/flows/development-flow.json +452 -0
  4. package/flows/quick-flow.json +118 -0
  5. package/hooks/hooks-config-updated.json +285 -0
  6. package/hooks/skill-discovery.sh +539 -0
  7. package/package.json +3 -2
  8. package/references/SYSTEM_INDEX.md +400 -5
  9. package/references/agent-marketplace.md +2274 -0
  10. package/references/agent-protocol.md +1126 -0
  11. package/references/ai-code-suggestions.md +2413 -0
  12. package/references/checkpointing.md +595 -0
  13. package/references/collaboration-patterns.md +851 -0
  14. package/references/collaborative-sessions.md +1081 -0
  15. package/references/configuration-management.md +1810 -0
  16. package/references/cost-tracking.md +1095 -0
  17. package/references/enterprise-sso.md +2001 -0
  18. package/references/error-contracts-v2.md +968 -0
  19. package/references/event-driven.md +1031 -0
  20. package/references/flow-orchestration.md +940 -0
  21. package/references/flow-visualization.md +1557 -0
  22. package/references/ide-integrations.md +3513 -0
  23. package/references/interrupt-system.md +681 -0
  24. package/references/kubernetes-deployment.md +3099 -0
  25. package/references/memory-system.md +683 -0
  26. package/references/mobile-companion.md +3236 -0
  27. package/references/multi-llm-providers.md +2494 -0
  28. package/references/multi-project-memory.md +1182 -0
  29. package/references/observability.md +793 -0
  30. package/references/output-schemas.md +858 -0
  31. package/references/performance-profiler.md +955 -0
  32. package/references/plugin-system.md +1526 -0
  33. package/references/prompt-management.md +292 -0
  34. package/references/sandbox-execution.md +303 -0
  35. package/references/security-system.md +1253 -0
  36. package/references/skill-marketplace-integration.md +3901 -0
  37. package/references/streaming.md +696 -0
  38. package/references/testing-framework.md +1151 -0
  39. package/references/time-travel.md +802 -0
  40. package/references/tool-registry.md +886 -0
  41. package/references/voice-commands.md +3296 -0
  42. package/templates/agent-marketplace-config.json +220 -0
  43. package/templates/agent-protocol-config.json +136 -0
  44. package/templates/ai-suggestions-config.json +100 -0
  45. package/templates/checkpoint-state.json +61 -0
  46. package/templates/collaboration-config.json +157 -0
  47. package/templates/collaborative-sessions-config.json +153 -0
  48. package/templates/configuration-config.json +245 -0
  49. package/templates/cost-tracking-config.json +148 -0
  50. package/templates/enterprise-sso-config.json +438 -0
  51. package/templates/events-config.json +148 -0
  52. package/templates/flow-visualization-config.json +196 -0
  53. package/templates/ide-integrations-config.json +442 -0
  54. package/templates/kubernetes-config.json +764 -0
  55. package/templates/memory-state.json +84 -0
  56. package/templates/mobile-companion-config.json +600 -0
  57. package/templates/multi-llm-config.json +544 -0
  58. package/templates/multi-project-memory-config.json +145 -0
  59. package/templates/observability-config.json +109 -0
  60. package/templates/performance-profiler-config.json +125 -0
  61. package/templates/plugin-config.json +170 -0
  62. package/templates/prompt-management-config.json +86 -0
  63. package/templates/sandbox-config.json +185 -0
  64. package/templates/schemas-config.json +65 -0
  65. package/templates/security-config.json +120 -0
  66. package/templates/skill-marketplace-config.json +441 -0
  67. package/templates/streaming-config.json +72 -0
  68. package/templates/testing-config.json +81 -0
  69. package/templates/timetravel-config.json +62 -0
  70. package/templates/tool-registry-config.json +109 -0
  71. 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