mcp-event-intelligence 0.1.1 → 0.2.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.
@@ -22,7 +22,9 @@ import {
22
22
  TriggerControlPlane,
23
23
  } from './trigger-control-plane.mjs';
24
24
  import {
25
+ DEFAULT_EVENT_SCOPE_ID,
25
26
  PersistentEventStore,
27
+ normalizeEventScopeId,
26
28
  } from './persistent-event-store.mjs';
27
29
  import {
28
30
  TriggerInspector,
@@ -33,6 +35,66 @@ import {
33
35
  import {
34
36
  McpEventsClientManager,
35
37
  } from './mcp-events-client.mjs';
38
+ import {
39
+ WakeRetryScheduler,
40
+ } from './wake-retry-scheduler.mjs';
41
+
42
+ const REQUIRED_STORE_METHODS = [
43
+ 'putTrigger',
44
+ 'listTriggers',
45
+ 'getTriggerState',
46
+ 'setTriggerState',
47
+ 'appendTriggerMatch',
48
+ 'listTriggerMatches',
49
+ 'putEventSource',
50
+ 'listEventSources',
51
+ 'appendWake',
52
+ 'latestWake',
53
+ 'ensureWakeDelivery',
54
+ 'getWakeDelivery',
55
+ 'claimWakeDelivery',
56
+ 'completeWakeDelivery',
57
+ 'failWakeDelivery',
58
+ 'listDueWakeDeliveries',
59
+ 'appendAudit',
60
+ 'auditLength',
61
+ 'getMcpClientState',
62
+ 'putMcpClientState',
63
+ 'appendMcpOccurrence',
64
+ ];
65
+
66
+ function assertStoreContract(store) {
67
+ if (!store || typeof store !== 'object') {
68
+ throw new Error('Event Intelligence store must be an object');
69
+ }
70
+ const missing = REQUIRED_STORE_METHODS.filter(
71
+ (method) => typeof store[method] !== 'function',
72
+ );
73
+ if (missing.length) {
74
+ const error = new Error(
75
+ `Event Intelligence store is missing required methods: ${missing.join(', ')}`,
76
+ );
77
+ error.code = 'EVENT_INTELLIGENCE_STORE_INVALID';
78
+ throw error;
79
+ }
80
+ return store;
81
+ }
82
+
83
+ async function initializeStore(store) {
84
+ const restored =
85
+ typeof store.init === 'function'
86
+ ? await store.init()
87
+ : {};
88
+ assertStoreContract(store);
89
+ return restored;
90
+ }
91
+
92
+ function scopedPacketBuilder(scopeId) {
93
+ return (input) => ({
94
+ ...buildGenericRuntimeWakePacket(input),
95
+ scope_id: scopeId,
96
+ });
97
+ }
36
98
 
37
99
  export async function createLocalEventIntelligenceRuntime({
38
100
  env = process.env,
@@ -40,10 +102,11 @@ export async function createLocalEventIntelligenceRuntime({
40
102
  wakeHandlers = {},
41
103
  wake,
42
104
  semanticEvaluator,
105
+ store: providedStore,
43
106
  } = {}) {
44
107
  const dataDir = env.DATA_DIR ?? './data';
45
- const store = new PersistentEventStore(dataDir);
46
- const restored = await store.init();
108
+ const rootStore = providedStore ?? new PersistentEventStore(dataDir);
109
+ const restored = await initializeStore(rootStore);
47
110
 
48
111
  const evaluator =
49
112
  semanticEvaluator !== undefined
@@ -52,42 +115,106 @@ export async function createLocalEventIntelligenceRuntime({
52
115
  ? TypeSafeJevEvaluator.fromEnvironment(env)
53
116
  : null;
54
117
 
55
- const triggerEngine = new CompositeTriggerEngine(store, evaluator);
56
-
57
- const wakeCoordinators = new Map();
58
-
59
118
  const runtimeWakeTargets = readRuntimeWakeTargets(
60
119
  env.RUNTIME_WAKE_TARGETS_JSON,
61
120
  );
62
- for (const [runtime, target] of runtimeWakeTargets) {
63
- wakeCoordinators.set(
64
- runtime,
65
- new CompositeWakeCoordinator({
66
- store,
67
- triggerEngine,
68
- deliverer: createSignedRuntimeWakeDeliverer(target),
69
- packetBuilder: buildGenericRuntimeWakePacket,
70
- }),
71
- );
72
- }
73
-
74
121
  const hostWakeEntries =
75
122
  wakeHandlers instanceof Map
76
123
  ? [...wakeHandlers.entries()]
77
124
  : Object.entries(wakeHandlers || {});
78
125
 
126
+ if (wake !== undefined && typeof wake !== 'function') {
127
+ throw new Error('Host wake dispatcher must be a function');
128
+ }
79
129
  for (const [runtime, handler] of hostWakeEntries) {
80
130
  if (typeof handler !== 'function') {
81
131
  throw new Error(`Host wake handler for ${runtime} must be a function`);
82
132
  }
83
- wakeCoordinators.set(
84
- runtime,
85
- new CompositeWakeCoordinator({
133
+ }
134
+
135
+ const scopeContexts = new Map();
136
+
137
+ const createScopeContext = async (scopeIdInput = DEFAULT_EVENT_SCOPE_ID) => {
138
+ const scopeId = normalizeEventScopeId(scopeIdInput);
139
+ const cached = scopeContexts.get(scopeId);
140
+ if (cached) return cached;
141
+
142
+ let store;
143
+ if (scopeId === DEFAULT_EVENT_SCOPE_ID) {
144
+ store = rootStore;
145
+ } else {
146
+ if (typeof rootStore.forScope !== 'function') {
147
+ const error = new Error(
148
+ `Configured Event Intelligence store does not support scope ${scopeId}`,
149
+ );
150
+ error.code = 'EVENT_INTELLIGENCE_STORE_SCOPE_UNSUPPORTED';
151
+ throw error;
152
+ }
153
+ store = await rootStore.forScope(scopeId);
154
+ assertStoreContract(store);
155
+ }
156
+
157
+ const triggerEngine = new CompositeTriggerEngine(store, evaluator);
158
+ const wakeCoordinators = new Map();
159
+ const packetBuilder = scopedPacketBuilder(scopeId);
160
+ const deliveryOptions = {
161
+ leaseMs: Number(env.WAKE_DELIVERY_LEASE_MS ?? 30000),
162
+ maxAttempts: Number(env.WAKE_DELIVERY_MAX_ATTEMPTS ?? 5),
163
+ retryBaseDelayMs: Number(env.WAKE_RETRY_BASE_DELAY_MS ?? 1000),
164
+ retryMaxDelayMs: Number(env.WAKE_RETRY_MAX_DELAY_MS ?? 60000),
165
+ };
166
+
167
+ for (const [runtime, target] of runtimeWakeTargets) {
168
+ wakeCoordinators.set(
169
+ runtime,
170
+ new CompositeWakeCoordinator({
171
+ store,
172
+ triggerEngine,
173
+ deliverer: createSignedRuntimeWakeDeliverer(target),
174
+ packetBuilder,
175
+ ...deliveryOptions,
176
+ }),
177
+ );
178
+ }
179
+
180
+ for (const [runtime, handler] of hostWakeEntries) {
181
+ wakeCoordinators.set(
182
+ runtime,
183
+ new CompositeWakeCoordinator({
184
+ store,
185
+ triggerEngine,
186
+ packetBuilder,
187
+ ...deliveryOptions,
188
+ deliverer: async (packet) => {
189
+ const result = await handler(packet);
190
+ if (typeof result === 'string' && result) {
191
+ return { runtimeReceiptId: result };
192
+ }
193
+ if (
194
+ result &&
195
+ typeof result === 'object' &&
196
+ typeof result.runtimeReceiptId === 'string' &&
197
+ result.runtimeReceiptId
198
+ ) {
199
+ return result;
200
+ }
201
+ throw new Error(
202
+ `Host wake handler for ${runtime} must return runtimeReceiptId`,
203
+ );
204
+ },
205
+ }),
206
+ );
207
+ }
208
+
209
+ let wakeCoordinator = null;
210
+ if (wake) {
211
+ wakeCoordinator = new CompositeWakeCoordinator({
86
212
  store,
87
213
  triggerEngine,
88
- packetBuilder: buildGenericRuntimeWakePacket,
214
+ packetBuilder,
215
+ ...deliveryOptions,
89
216
  deliverer: async (packet) => {
90
- const result = await handler(packet);
217
+ const result = await wake(packet);
91
218
  if (typeof result === 'string' && result) {
92
219
  return { runtimeReceiptId: result };
93
220
  }
@@ -99,70 +226,82 @@ export async function createLocalEventIntelligenceRuntime({
99
226
  ) {
100
227
  return result;
101
228
  }
102
- throw new Error(
103
- `Host wake handler for ${runtime} must return runtimeReceiptId`,
104
- );
229
+ throw new Error('Host wake dispatcher must return runtimeReceiptId');
105
230
  },
106
- }),
107
- );
108
- }
109
-
110
- let wakeCoordinator = null;
111
- if (wake !== undefined) {
112
- if (typeof wake !== 'function') {
113
- throw new Error('Host wake dispatcher must be a function');
231
+ });
114
232
  }
115
- wakeCoordinator = new CompositeWakeCoordinator({
233
+
234
+ const derivedEventCoordinator = new DerivedEventCoordinator({ store });
235
+ const compositeEventConsumer = new CompositeEventConsumer({
116
236
  store,
117
237
  triggerEngine,
118
- packetBuilder: buildGenericRuntimeWakePacket,
119
- deliverer: async (packet) => {
120
- const result = await wake(packet);
121
- if (typeof result === 'string' && result) {
122
- return { runtimeReceiptId: result };
123
- }
124
- if (
125
- result &&
126
- typeof result === 'object' &&
127
- typeof result.runtimeReceiptId === 'string' &&
128
- result.runtimeReceiptId
129
- ) {
130
- return result;
131
- }
132
- throw new Error('Host wake dispatcher must return runtimeReceiptId');
133
- },
238
+ wakeCoordinator,
239
+ wakeCoordinators,
240
+ derivedEventCoordinator,
241
+ maxDerivedDepth: Number(env.MAX_DERIVED_EVENT_DEPTH ?? 16),
134
242
  });
135
- }
136
243
 
137
- const derivedEventCoordinator = new DerivedEventCoordinator({ store });
138
- const compositeEventConsumer = new CompositeEventConsumer({
139
- store,
140
- triggerEngine,
141
- wakeCoordinator,
142
- wakeCoordinators,
143
- derivedEventCoordinator,
144
- maxDerivedDepth: Number(env.MAX_DERIVED_EVENT_DEPTH ?? 16),
145
- });
244
+ const temporalScheduler = new TemporalDeadlineScheduler({
245
+ store,
246
+ compositeEventConsumer,
247
+ intervalMs: Number(env.TEMPORAL_TICK_MS ?? 1000),
248
+ });
249
+ temporalScheduler.start();
146
250
 
147
- const temporalScheduler = new TemporalDeadlineScheduler({
148
- store,
149
- compositeEventConsumer,
150
- intervalMs: Number(env.TEMPORAL_TICK_MS ?? 1000),
151
- });
152
- temporalScheduler.start();
251
+ const triggerControl = new TriggerControlPlane({
252
+ store,
253
+ triggerEngine,
254
+ });
255
+ const triggerInspector = new TriggerInspector({ store });
153
256
 
154
- const triggerControl = new TriggerControlPlane({
155
- store,
156
- triggerEngine,
157
- });
158
- const triggerInspector = new TriggerInspector({ store });
257
+ const coordinatorFor = (runtime) =>
258
+ wakeCoordinators.get(runtime) ?? wakeCoordinator;
259
+ const wakeRetryScheduler = new WakeRetryScheduler({
260
+ store,
261
+ resolveCoordinator: coordinatorFor,
262
+ intervalMs: Number(env.WAKE_RETRY_TICK_MS ?? 1000),
263
+ });
264
+ wakeRetryScheduler.start();
265
+
266
+ const context = {
267
+ scopeId,
268
+ store,
269
+ evaluator,
270
+ triggerEngine,
271
+ triggerControl,
272
+ triggerInspector,
273
+ compositeEventConsumer,
274
+ temporalScheduler,
275
+ wakeRetryScheduler,
276
+ wakeCoordinator,
277
+ wakeCoordinators,
278
+ close() {
279
+ temporalScheduler.stop();
280
+ wakeRetryScheduler.stop();
281
+ },
282
+ };
283
+ scopeContexts.set(scopeId, context);
284
+ return context;
285
+ };
286
+
287
+ const defaultContext = await createScopeContext(DEFAULT_EVENT_SCOPE_ID);
288
+
289
+ if (typeof rootStore.listScopeIds === 'function') {
290
+ const restoredScopes = await rootStore.listScopeIds();
291
+ for (const scopeId of restoredScopes) {
292
+ if (scopeId !== DEFAULT_EVENT_SCOPE_ID) {
293
+ await createScopeContext(scopeId);
294
+ }
295
+ }
296
+ }
159
297
 
160
298
  const mcpEventsClient = new McpEventsClientManager({
161
- store,
162
- compositeEventConsumer,
299
+ store: defaultContext.store,
300
+ compositeEventConsumer: defaultContext.compositeEventConsumer,
301
+ resolveScope: createScopeContext,
163
302
  connections: mcpEventConnections,
164
303
  registerEventSource: (source) =>
165
- triggerControl.registerEventSource(
304
+ defaultContext.triggerControl.registerEventSource(
166
305
  source,
167
306
  {
168
307
  type: 'system',
@@ -176,18 +315,24 @@ export async function createLocalEventIntelligenceRuntime({
176
315
 
177
316
  return {
178
317
  restored,
179
- store,
318
+ store: defaultContext.store,
180
319
  evaluator,
181
- triggerEngine,
182
- triggerControl,
183
- triggerInspector,
184
- compositeEventConsumer,
185
- temporalScheduler,
320
+ triggerEngine: defaultContext.triggerEngine,
321
+ triggerControl: defaultContext.triggerControl,
322
+ triggerInspector: defaultContext.triggerInspector,
323
+ compositeEventConsumer: defaultContext.compositeEventConsumer,
324
+ temporalScheduler: defaultContext.temporalScheduler,
186
325
  mcpEventsClient,
187
326
  discovery,
327
+ scope: createScopeContext,
328
+ loadedScopes() {
329
+ return [...scopeContexts.keys()].sort();
330
+ },
188
331
  async close() {
189
- temporalScheduler.stop();
190
332
  mcpEventsClient.stop();
333
+ for (const context of scopeContexts.values()) {
334
+ context.close();
335
+ }
191
336
  },
192
337
  };
193
338
  }