mcp-event-intelligence 0.1.1 → 0.2.1

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 (31) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +79 -4
  3. package/bin/mcp-event-intelligence.mjs +1 -1
  4. package/dist/src/composite/derivedGraph.d.ts +4 -0
  5. package/dist/src/composite/derivedGraph.js +46 -0
  6. package/dist/src/composite/engine.d.ts +3 -64
  7. package/dist/src/composite/engine.js +26 -343
  8. package/dist/src/composite/matching.d.ts +8 -0
  9. package/dist/src/composite/matching.js +109 -0
  10. package/dist/src/composite/runtimeSupport.d.ts +24 -0
  11. package/dist/src/composite/runtimeSupport.js +176 -0
  12. package/dist/src/composite/store.d.ts +59 -0
  13. package/dist/src/composite/store.js +1 -0
  14. package/dist/src/intelligenceProtocol/schemas.d.ts +9 -7
  15. package/dist/src/intelligenceProtocol/schemas.js +1 -0
  16. package/dist/src/intelligenceProtocol/triggerSchemas.d.ts +6 -6
  17. package/dist/src/mcpEvents/erpnextProvider.d.ts +20 -0
  18. package/dist/src/mcpEvents/erpnextProvider.js +80 -0
  19. package/package.json +5 -1
  20. package/scripts/host-integration.d.mts +34 -4
  21. package/scripts/host-integration.mjs +44 -1
  22. package/scripts/lib/composite-wake-coordinator.mjs +233 -9
  23. package/scripts/lib/event-processor.mjs +235 -20
  24. package/scripts/lib/local-event-intelligence-runtime.mjs +238 -83
  25. package/scripts/lib/mcp-events-client.mjs +184 -66
  26. package/scripts/lib/persistent-event-store.mjs +320 -1
  27. package/scripts/lib/temporal-deadline-scheduler.mjs +12 -2
  28. package/scripts/lib/wake-retry-scheduler.mjs +104 -0
  29. package/scripts/mcp-stdio-server.mjs +1 -1
  30. package/scripts/service.mjs +20 -0
  31. package/server.json +2 -2
@@ -1,4 +1,4 @@
1
- import { appendFile, mkdir, readFile } from 'node:fs/promises';
1
+ import { appendFile, mkdir, readFile, readdir } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import {
4
4
  AuditChain,
@@ -9,6 +9,29 @@ import {
9
9
  McpEventOccurrenceSchema,
10
10
  } from '../../dist/src/intelligenceProtocol/index.js';
11
11
 
12
+ export const DEFAULT_EVENT_SCOPE_ID = 'default';
13
+
14
+ export function normalizeEventScopeId(input = DEFAULT_EVENT_SCOPE_ID) {
15
+ const scopeId = String(input ?? DEFAULT_EVENT_SCOPE_ID).trim();
16
+ if (!scopeId) throw new Error('Event Intelligence scopeId must not be empty');
17
+ if (scopeId.length > 240) {
18
+ throw new Error('Event Intelligence scopeId must be at most 240 characters');
19
+ }
20
+ return scopeId;
21
+ }
22
+
23
+ function scopeDirectoryName(scopeId) {
24
+ return Buffer.from(scopeId, 'utf8').toString('base64url');
25
+ }
26
+
27
+ function scopeIdFromDirectoryName(name) {
28
+ try {
29
+ return Buffer.from(name, 'base64url').toString('utf8');
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
12
35
  async function readJsonLines(filePath) {
13
36
  try {
14
37
  const raw = await readFile(filePath, 'utf8');
@@ -28,6 +51,7 @@ export class PersistentEventStore {
28
51
  #events = [];
29
52
  #decisions = [];
30
53
  #wakes = [];
54
+ #wakeDeliveries = new Map();
31
55
  #triggers = new Map();
32
56
  #triggerStates = new Map();
33
57
  #eventSources = new Map();
@@ -40,6 +64,7 @@ export class PersistentEventStore {
40
64
  #derivedEvents = new Map();
41
65
  #derivedContracts = new Map();
42
66
  #auditChain = new AuditChain();
67
+ #scopeStores = new Map();
43
68
 
44
69
  constructor(dataDir) {
45
70
  this.dataDir = dataDir;
@@ -47,6 +72,7 @@ export class PersistentEventStore {
47
72
  events: path.join(dataDir, 'events.jsonl'),
48
73
  decisions: path.join(dataDir, 'decisions.jsonl'),
49
74
  wakes: path.join(dataDir, 'wakes.jsonl'),
75
+ wakeDeliveries: path.join(dataDir, 'wake-deliveries.jsonl'),
50
76
  triggers: path.join(dataDir, 'triggers.jsonl'),
51
77
  triggerStates: path.join(dataDir, 'trigger-states.jsonl'),
52
78
  eventSources: path.join(dataDir, 'event-sources.jsonl'),
@@ -60,6 +86,41 @@ export class PersistentEventStore {
60
86
  };
61
87
  }
62
88
 
89
+ async forScope(scopeIdInput = DEFAULT_EVENT_SCOPE_ID) {
90
+ const scopeId = normalizeEventScopeId(scopeIdInput);
91
+ if (scopeId === DEFAULT_EVENT_SCOPE_ID) return this;
92
+
93
+ const existing = this.#scopeStores.get(scopeId);
94
+ if (existing) return existing;
95
+
96
+ const scoped = new PersistentEventStore(
97
+ path.join(this.dataDir, 'scopes', scopeDirectoryName(scopeId)),
98
+ );
99
+ await scoped.init();
100
+ this.#scopeStores.set(scopeId, scoped);
101
+ return scoped;
102
+ }
103
+
104
+ async listScopeIds() {
105
+ const scopes = new Set([
106
+ DEFAULT_EVENT_SCOPE_ID,
107
+ ...this.#scopeStores.keys(),
108
+ ]);
109
+ try {
110
+ const entries = await readdir(path.join(this.dataDir, 'scopes'), {
111
+ withFileTypes: true,
112
+ });
113
+ for (const entry of entries) {
114
+ if (!entry.isDirectory()) continue;
115
+ const scopeId = scopeIdFromDirectoryName(entry.name);
116
+ if (scopeId) scopes.add(scopeId);
117
+ }
118
+ } catch (error) {
119
+ if (error?.code !== 'ENOENT') throw error;
120
+ }
121
+ return [...scopes].sort();
122
+ }
123
+
63
124
  async init() {
64
125
  await mkdir(this.dataDir, { recursive: true });
65
126
 
@@ -67,6 +128,7 @@ export class PersistentEventStore {
67
128
  events,
68
129
  decisions,
69
130
  wakes,
131
+ wakeDeliveries,
70
132
  triggers,
71
133
  triggerStates,
72
134
  eventSources,
@@ -81,6 +143,7 @@ export class PersistentEventStore {
81
143
  readJsonLines(this.files.events),
82
144
  readJsonLines(this.files.decisions),
83
145
  readJsonLines(this.files.wakes),
146
+ readJsonLines(this.files.wakeDeliveries),
84
147
  readJsonLines(this.files.triggers),
85
148
  readJsonLines(this.files.triggerStates),
86
149
  readJsonLines(this.files.eventSources),
@@ -97,6 +160,42 @@ export class PersistentEventStore {
97
160
  this.#decisions = decisions;
98
161
  this.#wakes = wakes;
99
162
 
163
+ this.#wakeDeliveries = new Map();
164
+ for (const raw of wakeDeliveries) {
165
+ if (!raw?.wakeId || !raw?.runtime || (!raw?.sourceId && !raw?.matchId)) continue;
166
+ const status = [
167
+ 'pending',
168
+ 'claimed',
169
+ 'retry_pending',
170
+ 'delivered',
171
+ 'dead_letter',
172
+ ].includes(raw.status)
173
+ ? raw.status
174
+ : 'pending';
175
+ const record = {
176
+ wakeId: String(raw.wakeId),
177
+ sourceType: raw.sourceType === 'event' ? 'event' : 'composite',
178
+ sourceId: String(raw.sourceId || raw.matchId),
179
+ matchId: raw.matchId ? String(raw.matchId) : '',
180
+ triggerId: String(raw.triggerId || ''),
181
+ triggerVersion: String(raw.triggerVersion || '1'),
182
+ runtime: String(raw.runtime),
183
+ status,
184
+ attemptCount:
185
+ Number.isInteger(raw.attemptCount) && raw.attemptCount >= 0
186
+ ? raw.attemptCount
187
+ : 0,
188
+ nextAttemptAt: String(raw.nextAttemptAt || raw.updatedAt || new Date(0).toISOString()),
189
+ leaseOwner: raw.leaseOwner ? String(raw.leaseOwner) : null,
190
+ leaseUntil: raw.leaseUntil ? String(raw.leaseUntil) : null,
191
+ runtimeReceiptId: raw.runtimeReceiptId ? String(raw.runtimeReceiptId) : null,
192
+ lastError: raw.lastError ? String(raw.lastError) : null,
193
+ createdAt: String(raw.createdAt || new Date(0).toISOString()),
194
+ updatedAt: String(raw.updatedAt || new Date(0).toISOString()),
195
+ };
196
+ this.#wakeDeliveries.set(record.wakeId, record);
197
+ }
198
+
100
199
  this.#triggers = new Map();
101
200
  for (const raw of triggers) {
102
201
  const definition = parseCompositeTriggerDefinition(raw);
@@ -265,6 +364,7 @@ export class PersistentEventStore {
265
364
  events: events.length,
266
365
  decisions: decisions.length,
267
366
  wakes: wakes.length,
367
+ wakeDeliveries: this.#wakeDeliveries.size,
268
368
  triggers: this.#triggers.size,
269
369
  triggerStates: this.#triggerStates.size,
270
370
  eventSources: this.#eventSources.size,
@@ -307,6 +407,213 @@ export class PersistentEventStore {
307
407
  });
308
408
  }
309
409
 
410
+ getWakeDelivery(wakeId) {
411
+ return this.#wakeDeliveries.get(String(wakeId)) ?? null;
412
+ }
413
+
414
+ listDueWakeDeliveries(nowIso = new Date().toISOString()) {
415
+ const now = Date.parse(nowIso);
416
+ return [...this.#wakeDeliveries.values()]
417
+ .filter((record) => {
418
+ if (record.status === 'pending' || record.status === 'retry_pending') {
419
+ return Date.parse(record.nextAttemptAt) <= now;
420
+ }
421
+ if (record.status === 'claimed' && record.leaseUntil) {
422
+ return Date.parse(record.leaseUntil) <= now;
423
+ }
424
+ if (record.status === 'delivered') {
425
+ const latestWake = this.latestWake(record.wakeId);
426
+ const wakeFinalized =
427
+ latestWake?.status === 'delivered' ||
428
+ latestWake?.status === 'handled';
429
+ if (record.sourceType === 'event') {
430
+ return !wakeFinalized;
431
+ }
432
+ const match = this.#triggerMatches.get(record.matchId);
433
+ return !wakeFinalized || match?.status !== 'fired';
434
+ }
435
+ return false;
436
+ })
437
+ .sort((a, b) =>
438
+ Date.parse(a.nextAttemptAt) - Date.parse(b.nextAttemptAt) ||
439
+ a.wakeId.localeCompare(b.wakeId)
440
+ );
441
+ }
442
+
443
+ async ensureWakeDelivery(input) {
444
+ return this.#serialized(async () => {
445
+ const wakeId = String(input.wakeId || '');
446
+ const sourceType = input.sourceType === 'event' ? 'event' : 'composite';
447
+ const matchId = String(input.matchId || '');
448
+ const sourceId = String(input.sourceId || matchId || '');
449
+ const runtime = String(input.runtime || '');
450
+ if (!wakeId || !sourceId || !runtime) {
451
+ throw new Error('Wake delivery requires wakeId, sourceId and runtime');
452
+ }
453
+ if (sourceType === 'composite' && !matchId) {
454
+ throw new Error('Composite wake delivery requires matchId');
455
+ }
456
+ const existing = this.#wakeDeliveries.get(wakeId);
457
+ if (existing) return existing;
458
+
459
+ const now = String(input.now ?? new Date().toISOString());
460
+ const record = {
461
+ wakeId,
462
+ sourceType,
463
+ sourceId,
464
+ matchId,
465
+ triggerId: String(input.triggerId || ''),
466
+ triggerVersion: String(input.triggerVersion || '1'),
467
+ runtime,
468
+ status: 'pending',
469
+ attemptCount: 0,
470
+ nextAttemptAt: now,
471
+ leaseOwner: null,
472
+ leaseUntil: null,
473
+ runtimeReceiptId: null,
474
+ lastError: null,
475
+ createdAt: now,
476
+ updatedAt: now,
477
+ };
478
+ this.#wakeDeliveries.set(wakeId, record);
479
+ await appendFile(
480
+ this.files.wakeDeliveries,
481
+ `${JSON.stringify(record)}\n`,
482
+ 'utf8',
483
+ );
484
+ return record;
485
+ });
486
+ }
487
+
488
+ async claimWakeDelivery(wakeIdInput, {
489
+ workerId,
490
+ now = new Date().toISOString(),
491
+ leaseMs = 30000,
492
+ } = {}) {
493
+ return this.#serialized(async () => {
494
+ const wakeId = String(wakeIdInput || '');
495
+ const owner = String(workerId || '');
496
+ if (!wakeId || !owner) {
497
+ throw new Error('Wake delivery claim requires wakeId and workerId');
498
+ }
499
+ const current = this.#wakeDeliveries.get(wakeId);
500
+ if (!current) return null;
501
+ if (current.status === 'delivered' || current.status === 'dead_letter') {
502
+ return null;
503
+ }
504
+
505
+ const nowMs = Date.parse(now);
506
+ const ready =
507
+ (
508
+ (current.status === 'pending' || current.status === 'retry_pending') &&
509
+ Date.parse(current.nextAttemptAt) <= nowMs
510
+ ) ||
511
+ (
512
+ current.status === 'claimed' &&
513
+ current.leaseUntil &&
514
+ Date.parse(current.leaseUntil) <= nowMs
515
+ );
516
+ if (!ready) return null;
517
+
518
+ const record = {
519
+ ...current,
520
+ status: 'claimed',
521
+ attemptCount: current.attemptCount + 1,
522
+ leaseOwner: owner,
523
+ leaseUntil: new Date(nowMs + Math.max(1000, Number(leaseMs) || 30000)).toISOString(),
524
+ updatedAt: now,
525
+ };
526
+ this.#wakeDeliveries.set(wakeId, record);
527
+ await appendFile(
528
+ this.files.wakeDeliveries,
529
+ `${JSON.stringify(record)}\n`,
530
+ 'utf8',
531
+ );
532
+ return record;
533
+ });
534
+ }
535
+
536
+ async completeWakeDelivery(wakeIdInput, {
537
+ workerId,
538
+ runtimeReceiptId,
539
+ now = new Date().toISOString(),
540
+ } = {}) {
541
+ return this.#serialized(async () => {
542
+ const wakeId = String(wakeIdInput || '');
543
+ const current = this.#wakeDeliveries.get(wakeId);
544
+ if (!current) return null;
545
+ if (current.status === 'delivered') return current;
546
+ if (
547
+ current.status !== 'claimed' ||
548
+ current.leaseOwner !== String(workerId || '')
549
+ ) {
550
+ const error = new Error(`Wake delivery ${wakeId} is not owned by this worker`);
551
+ error.code = 'WAKE_DELIVERY_CLAIM_LOST';
552
+ throw error;
553
+ }
554
+
555
+ const record = {
556
+ ...current,
557
+ status: 'delivered',
558
+ runtimeReceiptId: String(runtimeReceiptId || ''),
559
+ leaseOwner: null,
560
+ leaseUntil: null,
561
+ nextAttemptAt: now,
562
+ lastError: null,
563
+ updatedAt: now,
564
+ };
565
+ this.#wakeDeliveries.set(wakeId, record);
566
+ await appendFile(
567
+ this.files.wakeDeliveries,
568
+ `${JSON.stringify(record)}\n`,
569
+ 'utf8',
570
+ );
571
+ return record;
572
+ });
573
+ }
574
+
575
+ async failWakeDelivery(wakeIdInput, {
576
+ workerId,
577
+ error,
578
+ now = new Date().toISOString(),
579
+ maxAttempts = 5,
580
+ nextAttemptAt = now,
581
+ } = {}) {
582
+ return this.#serialized(async () => {
583
+ const wakeId = String(wakeIdInput || '');
584
+ const current = this.#wakeDeliveries.get(wakeId);
585
+ if (!current) return null;
586
+ if (
587
+ current.status !== 'claimed' ||
588
+ current.leaseOwner !== String(workerId || '')
589
+ ) {
590
+ const claimError = new Error(
591
+ `Wake delivery ${wakeId} is not owned by this worker`,
592
+ );
593
+ claimError.code = 'WAKE_DELIVERY_CLAIM_LOST';
594
+ throw claimError;
595
+ }
596
+
597
+ const terminal = current.attemptCount >= Math.max(1, Number(maxAttempts) || 5);
598
+ const record = {
599
+ ...current,
600
+ status: terminal ? 'dead_letter' : 'retry_pending',
601
+ leaseOwner: null,
602
+ leaseUntil: null,
603
+ nextAttemptAt: terminal ? now : String(nextAttemptAt),
604
+ lastError: error instanceof Error ? error.message : String(error || 'wake delivery failed'),
605
+ updatedAt: now,
606
+ };
607
+ this.#wakeDeliveries.set(wakeId, record);
608
+ await appendFile(
609
+ this.files.wakeDeliveries,
610
+ `${JSON.stringify(record)}\n`,
611
+ 'utf8',
612
+ );
613
+ return record;
614
+ });
615
+ }
616
+
310
617
  async putTrigger(definitionInput) {
311
618
  return this.#serialized(async () => {
312
619
  const definition = parseCompositeTriggerDefinition(definitionInput);
@@ -750,6 +1057,18 @@ export class PersistentEventStore {
750
1057
  return this.#auditChain.verify();
751
1058
  }
752
1059
 
1060
+ async drain() {
1061
+ await this.#queue;
1062
+ const scoped = [...this.#scopeStores.values()];
1063
+ await Promise.all(scoped.map((store) =>
1064
+ typeof store.drain === 'function' ? store.drain() : undefined
1065
+ ));
1066
+ }
1067
+
1068
+ async close() {
1069
+ await this.drain();
1070
+ }
1071
+
753
1072
  #eventKey(lineage) {
754
1073
  return `${lineage.environmentId}:${lineage.subscriptionId}:${lineage.sourceEventId}`;
755
1074
  }
@@ -104,8 +104,18 @@ export class TemporalDeadlineScheduler {
104
104
  }
105
105
 
106
106
  stop() {
107
- if (!this.timer) return;
108
- clearInterval(this.timer);
107
+ if (this.timer) clearInterval(this.timer);
109
108
  this.timer = null;
110
109
  }
110
+
111
+ async drain() {
112
+ while (this.running) {
113
+ await new Promise((resolve) => setImmediate(resolve));
114
+ }
115
+ }
116
+
117
+ async close() {
118
+ this.stop();
119
+ await this.drain();
120
+ }
111
121
  }
@@ -0,0 +1,104 @@
1
+ export class WakeRetryScheduler {
2
+ constructor({
3
+ store,
4
+ resolveCoordinator,
5
+ eventProcessor = null,
6
+ intervalMs = 1000,
7
+ now = () => new Date(),
8
+ }) {
9
+ this.store = store;
10
+ this.resolveCoordinator = resolveCoordinator;
11
+ this.eventProcessor = eventProcessor;
12
+ this.intervalMs = Math.max(100, Number(intervalMs) || 1000);
13
+ this.now = now;
14
+ this.timer = null;
15
+ this.running = false;
16
+ }
17
+
18
+ async runDue() {
19
+ if (this.running) return [];
20
+ this.running = true;
21
+ try {
22
+ const due = this.store.listDueWakeDeliveries(this.now().toISOString());
23
+ const outcomes = [];
24
+
25
+ for (const delivery of due) {
26
+ if (delivery.sourceType === 'event') {
27
+ if (!this.eventProcessor || typeof this.eventProcessor.retryWake !== 'function') {
28
+ outcomes.push({
29
+ wakeId: delivery.wakeId,
30
+ status: 'event_processor_unconfigured',
31
+ });
32
+ continue;
33
+ }
34
+ const result = await this.eventProcessor.retryWake(delivery.wakeId);
35
+ outcomes.push({
36
+ wakeId: delivery.wakeId,
37
+ status: result.status,
38
+ });
39
+ continue;
40
+ }
41
+
42
+ const coordinator = this.resolveCoordinator(delivery.runtime);
43
+ if (!coordinator) {
44
+ outcomes.push({
45
+ wakeId: delivery.wakeId,
46
+ status: 'runtime_unconfigured',
47
+ });
48
+ continue;
49
+ }
50
+
51
+ const match = this.store.listTriggerMatches(delivery.triggerId)
52
+ .filter((candidate) => candidate.matchId === delivery.matchId)
53
+ .sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt))[0];
54
+
55
+ if (!match) {
56
+ outcomes.push({
57
+ wakeId: delivery.wakeId,
58
+ status: 'match_missing',
59
+ });
60
+ continue;
61
+ }
62
+
63
+ const result = await coordinator.deliverMatched(match);
64
+ outcomes.push({
65
+ wakeId: delivery.wakeId,
66
+ status: result.status,
67
+ });
68
+ }
69
+
70
+ return outcomes;
71
+ } finally {
72
+ this.running = false;
73
+ }
74
+ }
75
+
76
+ start() {
77
+ if (this.timer) return;
78
+ this.timer = setInterval(() => {
79
+ this.runDue().catch((error) => {
80
+ console.error(JSON.stringify({
81
+ message: 'wake_retry_scheduler_error',
82
+ error: error instanceof Error ? error.message : String(error),
83
+ }));
84
+ });
85
+ }, this.intervalMs);
86
+ this.timer.unref?.();
87
+ }
88
+
89
+ stop() {
90
+ if (this.timer) clearInterval(this.timer);
91
+ this.timer = null;
92
+ }
93
+
94
+ async drain() {
95
+ while (this.running) {
96
+ await new Promise((resolve) => setImmediate(resolve));
97
+ }
98
+ }
99
+
100
+ async close() {
101
+ this.stop();
102
+ await this.drain();
103
+ }
104
+ }
@@ -341,7 +341,7 @@ export async function buildEventIntelligenceMcpServer({
341
341
  const server = new McpServer(
342
342
  {
343
343
  name: 'mcp-event-intelligence',
344
- version: '0.1.1',
344
+ version: '0.2.1',
345
345
  },
346
346
  {
347
347
  instructions:
@@ -49,6 +49,9 @@ import {
49
49
  import {
50
50
  TemporalDeadlineScheduler,
51
51
  } from './lib/temporal-deadline-scheduler.mjs';
52
+ import {
53
+ WakeRetryScheduler,
54
+ } from './lib/wake-retry-scheduler.mjs';
52
55
 
53
56
  const port = Number(process.env.PORT ?? 3000);
54
57
  const dataDir = process.env.DATA_DIR ?? './data';
@@ -66,10 +69,18 @@ const wakeDeliverer = createHttpWakeDeliverer(
66
69
  process.env.RUNTIME_WAKE_URL ?? '',
67
70
  );
68
71
 
72
+ const deliveryOptions = {
73
+ leaseMs: Number(process.env.WAKE_DELIVERY_LEASE_MS ?? 30000),
74
+ maxAttempts: Number(process.env.WAKE_DELIVERY_MAX_ATTEMPTS ?? 5),
75
+ retryBaseDelayMs: Number(process.env.WAKE_RETRY_BASE_DELAY_MS ?? 1000),
76
+ retryMaxDelayMs: Number(process.env.WAKE_RETRY_MAX_DELAY_MS ?? 60000),
77
+ };
78
+
69
79
  const processor = new EventProcessor({
70
80
  store,
71
81
  evaluator,
72
82
  wakeDeliverer,
83
+ ...deliveryOptions,
73
84
  });
74
85
  const compositeTriggers = new CompositeTriggerEngine(store, evaluator);
75
86
  const runtimeWakeTargets = readRuntimeWakeTargets(
@@ -83,6 +94,7 @@ const wakeCoordinators = new Map(
83
94
  triggerEngine: compositeTriggers,
84
95
  deliverer: createSignedRuntimeWakeDeliverer(target),
85
96
  packetBuilder: buildGenericRuntimeWakePacket,
97
+ ...deliveryOptions,
86
98
  }),
87
99
  ]),
88
100
  );
@@ -102,6 +114,14 @@ const temporalScheduler = new TemporalDeadlineScheduler({
102
114
  });
103
115
  temporalScheduler.start();
104
116
 
117
+ const wakeRetryScheduler = new WakeRetryScheduler({
118
+ store,
119
+ eventProcessor: processor,
120
+ resolveCoordinator: (runtime) => wakeCoordinators.get(runtime) ?? null,
121
+ intervalMs: Number(process.env.WAKE_RETRY_TICK_MS ?? 1000),
122
+ });
123
+ wakeRetryScheduler.start();
124
+
105
125
  const triggerControl = new TriggerControlPlane({
106
126
  store,
107
127
  triggerEngine: compositeTriggers,
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.sarooo17/event-intelligence",
4
4
  "title": "MCP Event Intelligence",
5
5
  "description": "Durable temporal event intelligence: composite triggers, derived events, and runtime wakeups.",
6
- "version": "0.1.1",
6
+ "version": "0.2.1",
7
7
  "repository": {
8
8
  "url": "https://github.com/sarooo17/event-intelligence",
9
9
  "source": "github"
@@ -13,7 +13,7 @@
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "mcp-event-intelligence",
16
- "version": "0.1.1",
16
+ "version": "0.2.1",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  },