@ai.ntellect/core 0.6.21 → 0.7.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 (37) hide show
  1. package/.mocharc.json +2 -1
  2. package/README.md +61 -85
  3. package/graph/controller.ts +1 -1
  4. package/graph/event-manager.ts +288 -0
  5. package/graph/index.ts +153 -367
  6. package/graph/logger.ts +70 -0
  7. package/graph/node.ts +398 -0
  8. package/graph/observer.ts +361 -0
  9. package/interfaces/index.ts +102 -1
  10. package/modules/agenda/index.ts +3 -16
  11. package/package.json +10 -5
  12. package/test/graph/index.test.ts +244 -113
  13. package/test/graph/observer.test.ts +398 -0
  14. package/test/modules/agenda/node-cron.test.ts +37 -16
  15. package/test/modules/memory/adapters/in-memory.test.ts +2 -2
  16. package/test/modules/memory/adapters/meilisearch.test.ts +28 -24
  17. package/test/modules/memory/base.test.ts +3 -3
  18. package/tsconfig.json +4 -2
  19. package/types/index.ts +23 -2
  20. package/dist/graph/controller.js +0 -72
  21. package/dist/graph/index.js +0 -501
  22. package/dist/index.js +0 -41
  23. package/dist/interfaces/index.js +0 -17
  24. package/dist/modules/agenda/adapters/node-cron/index.js +0 -29
  25. package/dist/modules/agenda/index.js +0 -140
  26. package/dist/modules/embedding/adapters/ai/index.js +0 -57
  27. package/dist/modules/embedding/index.js +0 -59
  28. package/dist/modules/memory/adapters/in-memory/index.js +0 -210
  29. package/dist/modules/memory/adapters/meilisearch/index.js +0 -320
  30. package/dist/modules/memory/adapters/redis/index.js +0 -158
  31. package/dist/modules/memory/index.js +0 -103
  32. package/dist/types/index.js +0 -2
  33. package/dist/utils/generate-action-schema.js +0 -43
  34. package/dist/utils/header-builder.js +0 -34
  35. package/test/modules/embedding/ai.test.ts +0 -78
  36. package/test/modules/memory/adapters/redis.test.ts +0 -169
  37. package/test/services/agenda.test.ts +0 -279
package/graph/index.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  import { EventEmitter } from "events";
2
+ import { BehaviorSubject, Subject } from "rxjs";
2
3
  import { ZodSchema } from "zod";
3
- import { GraphContext, GraphDefinition, Node } from "../types";
4
- import { IEventEmitter } from "../interfaces";
4
+ import { GraphObservable, IEventEmitter } from "../interfaces";
5
+ import { GraphContext, GraphDefinition, GraphEvent, Node } from "../types";
6
+ import { GraphEventManager } from "./event-manager";
7
+ import { GraphLogger } from "./logger";
8
+ import { GraphNode } from "./node";
9
+ import { GraphObserver } from "./observer";
5
10
 
6
11
  /**
7
12
  * @module GraphFlow
@@ -23,10 +28,18 @@ export class GraphFlow<T extends ZodSchema> {
23
28
  private globalErrorHandler?: (error: Error, context: GraphContext<T>) => void;
24
29
  private graphEvents?: string[];
25
30
  private entryNode?: string;
26
- private logs: string[] = [];
27
31
  private verbose: boolean = false;
28
32
  public nodes: Map<string, Node<T, any>>;
29
33
 
34
+ private eventSubject: Subject<GraphEvent<T>> = new Subject();
35
+ private stateSubject: BehaviorSubject<GraphContext<T>>;
36
+ private destroySubject: Subject<void> = new Subject();
37
+
38
+ public observer: GraphObserver<T>;
39
+ private logger: GraphLogger;
40
+ private eventManager: GraphEventManager<T>;
41
+ private nodeExecutor: GraphNode<T>;
42
+
30
43
  /**
31
44
  * Creates a new instance of GraphFlow
32
45
  * @param {string} name - The name of the graph flow
@@ -49,17 +62,36 @@ export class GraphFlow<T extends ZodSchema> {
49
62
  this.graphEvents = config.events;
50
63
  this.verbose = options.verbose ?? false;
51
64
 
65
+ this.stateSubject = new BehaviorSubject<GraphContext<T>>(this.context);
66
+
67
+ this.logger = new GraphLogger(name, options.verbose);
68
+ this.eventManager = new GraphEventManager(
69
+ this.eventEmitter,
70
+ this.nodes,
71
+ name,
72
+ this.context,
73
+ config.events,
74
+ config.entryNode,
75
+ config.onError
76
+ );
77
+ this.nodeExecutor = new GraphNode(
78
+ this.nodes,
79
+ this.logger,
80
+ this.eventManager,
81
+ this.eventSubject,
82
+ this.stateSubject
83
+ );
84
+
85
+ this.setupEventStreams();
52
86
  this.setupEventListeners();
53
87
  this.setupGraphEventListeners();
54
- }
55
88
 
56
- /**
57
- * Creates a new context for execution
58
- * @private
59
- * @returns {GraphContext<T>} A cloned context to prevent pollution during parallel execution
60
- */
61
- private createNewContext(): GraphContext<T> {
62
- return structuredClone(this.context);
89
+ this.observer = new GraphObserver(
90
+ this,
91
+ this.eventSubject,
92
+ this.stateSubject,
93
+ this.destroySubject
94
+ );
63
95
  }
64
96
 
65
97
  /**
@@ -67,77 +99,49 @@ export class GraphFlow<T extends ZodSchema> {
67
99
  * @private
68
100
  * @description Attaches all node-based event triggers while preserving external listeners
69
101
  */
70
- private setupEventListeners(): void {
71
- // First remove only the existing node-based listeners that we might have created previously
72
- // We do NOT remove, for example, "nodeStarted" or "nodeCompleted" listeners that test code added.
73
- for (const [eventName, listener] of this.eventEmitter
74
- .rawListeners("*")
75
- .entries()) {
76
- // This can be tricky—EventEmitter doesn't directly let you remove by "type" of listener.
77
- // Alternatively, we can store references in a separate structure.
78
- // For simplicity, let's do a full removeAllListeners() on node-specified events (only),
79
- // then re-add them below, but keep the test-based events like "nodeStarted" or "nodeCompleted".
80
- }
81
-
82
- // The simplest approach: removeAllListeners for each event that is declared as a node event
83
- // so we don't stack up duplicates:
84
- const allEvents = new Set<string>();
85
- for (const node of this.nodes.values()) {
86
- if (node.events) {
87
- node.events.forEach((evt) => allEvents.add(evt));
102
+ private setupEventStreams(): void {
103
+ this.eventManager.on("nodeStarted", (data) => {
104
+ this.addLog(`Event: Node "${data.name}" started`);
105
+ });
106
+
107
+ this.eventManager.on("nodeCompleted", (data) => {
108
+ this.addLog(`Event: Node "${data.name}" completed`);
109
+ });
110
+
111
+ this.eventManager.on("nodeError", (data) => {
112
+ let errorMessage = "Unknown error";
113
+ if (data.error) {
114
+ errorMessage =
115
+ data.error instanceof Error
116
+ ? data.error.message
117
+ : data.error.errors?.[0]?.message ||
118
+ data.error.message ||
119
+ "Unknown error";
88
120
  }
89
- }
90
- for (const evt of allEvents) {
91
- // remove only those events that are used by nodes
92
- this.eventEmitter.removeAllListeners(evt);
93
- }
121
+ this.addLog(`Event: Node "${data.name}" error: ${errorMessage}`);
122
+ });
94
123
 
95
- // Now re-add the node-based event triggers
96
- for (const node of this.nodes.values()) {
97
- if (node.events && node.events.length > 0) {
98
- node.events.forEach((event) => {
99
- this.eventEmitter.on(
100
- event,
101
- async (data?: Partial<GraphContext<T>>) => {
102
- const freshContext = this.createNewContext();
103
- if (data) Object.assign(freshContext, data);
104
-
105
- // If triggered by an event, we pass "true" so event-driven node will skip `next`.
106
- await this.executeNode(
107
- node.name,
108
- freshContext,
109
- undefined,
110
- /* triggeredByEvent= */ true
111
- );
112
- }
113
- );
114
- });
115
- }
116
- }
117
- }
118
-
119
- private addLog(message: string): void {
120
- const logMessage = `[${new Date().toISOString()}] ${message}`;
121
- this.logs.push(logMessage);
122
- if (this.verbose) {
123
- console.log(`[${this.name}] ${message}`);
124
- }
124
+ this.eventManager.on("nodeStateChanged", (data) => {
125
+ this.addLog(`Event: Node "${data.name}" state changed`);
126
+ });
125
127
  }
126
128
 
127
129
  /**
128
- * Enable or disable verbose logging
129
- * @param {boolean} enabled - Whether to enable verbose logging
130
+ * Sets up event listeners for node-based events
131
+ * @private
132
+ * @description Attaches all node-based event triggers while preserving external listeners
130
133
  */
131
- public setVerbose(enabled: boolean): void {
132
- this.verbose = enabled;
134
+ private setupEventListeners(): void {
135
+ this.eventManager.setupEventListeners();
133
136
  }
134
137
 
135
138
  /**
136
- * Get current verbose setting
137
- * @returns {boolean} Current verbose setting
139
+ * Sets up event listeners for graph-based events
140
+ * @private
141
+ * @description Attaches all graph-based event triggers
138
142
  */
139
- public isVerbose(): boolean {
140
- return this.verbose;
143
+ private setupGraphEventListeners(): void {
144
+ this.eventManager.setupGraphEventListeners();
141
145
  }
142
146
 
143
147
  /**
@@ -155,198 +159,58 @@ export class GraphFlow<T extends ZodSchema> {
155
159
  inputs: any,
156
160
  triggeredByEvent: boolean = false
157
161
  ): Promise<void> {
158
- const node = this.nodes.get(nodeName);
159
- if (!node) throw new Error(`Node "${nodeName}" not found.`);
160
-
161
- this.addLog(`🚀 Starting node "${nodeName}"`);
162
- this.eventEmitter.emit("nodeStarted", { name: nodeName });
163
-
164
- try {
165
- const localContext = structuredClone(context);
166
-
167
- if (node.condition && !node.condition(localContext)) {
168
- this.addLog(`⏭️ Skipping node "${nodeName}" - condition not met`);
169
- return;
170
- }
171
-
172
- // Validate inputs
173
- if (node.inputs) {
174
- if (!inputs) {
175
- this.addLog(`❌ Missing required inputs for node "${nodeName}"`);
176
- throw new Error(`Inputs required for node "${nodeName}"`);
177
- }
178
- this.addLog(`📥 Validating inputs for node "${nodeName}"`);
179
- inputs = node.inputs.parse(inputs);
180
- }
181
-
182
- // Handle retry logic
183
- if (node.retry && node.retry.maxAttempts > 0) {
184
- let attempts = 0;
185
- let lastError: Error | null = null;
186
-
187
- while (attempts < node.retry.maxAttempts) {
188
- try {
189
- this.addLog(`🔄 Attempt ${attempts + 1}/${node.retry.maxAttempts}`);
190
- await node.execute(localContext, inputs);
191
- lastError = null;
192
- break;
193
- } catch (error: any) {
194
- lastError = error as Error;
195
- attempts++;
196
- this.addLog(`❌ Attempt ${attempts} failed: ${error.message}`);
197
-
198
- if (attempts === node.retry.maxAttempts) {
199
- // Si toutes les tentatives ont échoué et qu'il y a un gestionnaire d'échec
200
- if (node.retry.onRetryFailed) {
201
- this.addLog(
202
- `🔄 Executing retry failure handler for node "${nodeName}"`
203
- );
204
- try {
205
- await node.retry.onRetryFailed(lastError, localContext);
206
- // Si le gestionnaire d'échec réussit, on continue l'exécution
207
- // SEULEMENT si le gestionnaire a explicitement retourné true
208
- if (node.retry.continueOnFailed) {
209
- this.addLog(
210
- `✅ Retry failure handler succeeded for node "${nodeName}" - continuing execution`
211
- );
212
- break;
213
- } else {
214
- this.addLog(
215
- `⚠️ Retry failure handler executed but node "${nodeName}" will still fail`
216
- );
217
- throw lastError;
218
- }
219
- } catch (handlerError: any) {
220
- this.addLog(
221
- `❌ Retry failure handler failed for node "${nodeName}": ${handlerError.message}`
222
- );
223
- throw handlerError;
224
- }
225
- }
226
- // Si pas de gestionnaire d'échec ou si le gestionnaire a échoué
227
- throw lastError;
228
- }
229
-
230
- if (attempts < node.retry.maxAttempts) {
231
- this.addLog(
232
- `⏳ Waiting ${node.retry.delay}ms before next attempt`
233
- );
234
- await new Promise((resolve) =>
235
- setTimeout(resolve, node.retry?.delay || 0)
236
- );
237
- }
238
- }
239
- }
240
- } else {
241
- await node.execute(localContext, inputs);
242
- }
243
-
244
- // Validate outputs
245
- if (node.outputs) {
246
- this.addLog(`📤 Validating outputs for node "${nodeName}"`);
247
- node.outputs.parse(localContext);
248
- }
249
-
250
- Object.assign(context, localContext);
251
-
252
- this.addLog(
253
- `✅ Node "${nodeName}" executed successfully ${JSON.stringify(context)}`
254
- );
255
- this.eventEmitter.emit("nodeCompleted", { name: nodeName });
256
-
257
- // Handle waitForEvent
258
- if (node.waitForEvent && !triggeredByEvent) {
259
- this.addLog(
260
- `⏳ Node "${nodeName}" waiting for events: ${node.events?.join(", ")}`
261
- );
262
-
263
- await new Promise<void>((resolve) => {
264
- const eventHandler = () => {
265
- this.addLog(`🚀 Event received for node "${nodeName}"`);
266
- resolve();
267
- };
268
-
269
- node.events?.forEach((event) => {
270
- this.eventEmitter.once(event, eventHandler);
271
- });
272
- });
162
+ return this.nodeExecutor.executeNode(
163
+ nodeName,
164
+ context,
165
+ inputs,
166
+ triggeredByEvent
167
+ );
168
+ }
273
169
 
274
- const nextNodes =
275
- typeof node.next === "function"
276
- ? node.next(context)
277
- : node.next || [];
278
-
279
- if (nextNodes.length > 0) {
280
- this.addLog(`➡️ Executing next nodes: ${nextNodes.join(", ")}`);
281
-
282
- // Execute next nodes
283
- for (const nextNodeName of nextNodes) {
284
- this.addLog(`🔄 Starting branch for node "${nextNodeName}"`);
285
- const nextNode = this.nodes.get(nextNodeName);
286
- if (nextNode) {
287
- await this.executeNode(
288
- nextNodeName,
289
- context,
290
- undefined,
291
- nextNode.waitForEvent
292
- );
293
- }
294
- this.addLog(`✅ Branch "${nextNodeName}" completed`);
295
- }
170
+ private addLog(message: string): void {
171
+ this.logger.addLog(message);
172
+ }
296
173
 
297
- this.eventEmitter.emit("graphCompleted", {
298
- name: this.name,
299
- context: this.context,
300
- });
174
+ public getLogs(): string[] {
175
+ return this.logger.getLogs();
176
+ }
301
177
 
302
- return;
303
- }
304
- }
178
+ public clearLogs(): void {
179
+ this.logger.clearLogs();
180
+ }
305
181
 
306
- // Execute next nodes
307
- const nextNodes =
308
- typeof node.next === "function"
309
- ? node.next(localContext)
310
- : node.next || [];
311
-
312
- if (nextNodes.length > 0) {
313
- this.addLog(`➡️ Executing next nodes: ${nextNodes.join(", ")}`);
314
-
315
- // Execute next nodes
316
- for (const nextNodeName of nextNodes) {
317
- this.addLog(`🔄 Starting branch for node "${nextNodeName}"`);
318
- const nextNode = this.nodes.get(nextNodeName);
319
- if (nextNode) {
320
- await this.executeNode(
321
- nextNodeName,
322
- context,
323
- undefined,
324
- nextNode.waitForEvent
325
- );
326
- }
327
- this.addLog(`✅ Branch "${nextNodeName}" completed`);
328
- }
329
- }
182
+ /**
183
+ * Get the observer instance for monitoring graph state and events
184
+ */
185
+ public observe(
186
+ options: {
187
+ debounce?: number;
188
+ delay?: number;
189
+ stream?: boolean;
190
+ properties?: (keyof GraphContext<T> extends string
191
+ ? keyof GraphContext<T>
192
+ : never)[];
193
+ onStreamLetter?: (data: { letter: string; property: string }) => void;
194
+ onStreamComplete?: () => void;
195
+ } = {}
196
+ ): GraphObservable<T> {
197
+ return this.observer.state(options) as GraphObservable<T>;
198
+ }
330
199
 
331
- // Mettre à jour le contexte global
332
- Object.assign(this.context, context);
333
- } catch (error: any) {
334
- this.addLog(`❌ Error in node "${nodeName}": ${error.message}`);
335
- this.eventEmitter.emit("nodeError", { name: nodeName, error });
336
- throw error;
337
- }
200
+ /**
201
+ * Enable or disable verbose logging
202
+ * @param {boolean} enabled - Whether to enable verbose logging
203
+ */
204
+ public setVerbose(enabled: boolean): void {
205
+ this.logger.setVerbose(enabled);
338
206
  }
339
207
 
340
208
  /**
341
- * Validates the current context against the schema
342
- * @private
343
- * @param {GraphContext<T>} context - Context to validate
344
- * @throws {Error} If validation fails
209
+ * Get current verbose setting
210
+ * @returns {boolean} Current verbose setting
345
211
  */
346
- private validateContext(context: GraphContext<T>): void {
347
- if (this.validator) {
348
- this.validator.parse(context);
349
- }
212
+ public isVerbose(): boolean {
213
+ return this.logger.isVerbose();
350
214
  }
351
215
 
352
216
  /**
@@ -356,13 +220,12 @@ export class GraphFlow<T extends ZodSchema> {
356
220
  * @param {any} inputParams - Optional input parameters for the start node
357
221
  * @returns {Promise<GraphContext<T>>} Final context after execution
358
222
  */
359
- async execute(
223
+ public async execute(
360
224
  startNode: string,
361
225
  inputParams?: any,
362
226
  inputContext?: Partial<GraphContext<T>>
363
227
  ): Promise<GraphContext<T>> {
364
228
  if (inputParams) {
365
- // Merge inputParams into context
366
229
  Object.assign(this.context, inputParams);
367
230
  }
368
231
 
@@ -373,7 +236,12 @@ export class GraphFlow<T extends ZodSchema> {
373
236
  this.eventEmitter.emit("graphStarted", { name: this.name });
374
237
 
375
238
  try {
376
- await this.executeNode(startNode, this.context, inputParams, false);
239
+ await this.nodeExecutor.executeNode(
240
+ startNode,
241
+ this.context,
242
+ inputParams,
243
+ false
244
+ );
377
245
 
378
246
  this.eventEmitter.emit("graphCompleted", {
379
247
  name: this.name,
@@ -392,31 +260,19 @@ export class GraphFlow<T extends ZodSchema> {
392
260
  * Emits an event to trigger event-based nodes
393
261
  * @param {string} eventName - Name of the event to emit
394
262
  * @param {Partial<GraphContext<T>>} data - Optional data to merge with context
395
- * @returns {Promise<GraphContext<T>>} Updated context after event handling
263
+ * @returns {Promise<void>}
396
264
  */
397
265
  public async emit(
398
266
  eventName: string,
399
267
  data?: Partial<GraphContext<T>>
400
- ): Promise<GraphContext<T>> {
401
- const workingContext = this.createNewContext();
402
-
403
- if (data) {
404
- Object.assign(workingContext, data);
405
- }
406
-
407
- const eventNodes = Array.from(this.nodes.values()).filter((node) =>
408
- node.events?.includes(eventName)
409
- );
410
-
411
- // Exécuter les nœuds d'événements séquentiellement
412
- for (const node of eventNodes) {
413
- await this.executeNode(node.name, workingContext, undefined, true);
414
- }
415
-
416
- // Mettre à jour le contexte global
417
- this.context = workingContext;
418
-
419
- return this.getContext();
268
+ ): Promise<void> {
269
+ const event: GraphEvent<T> = {
270
+ type: eventName,
271
+ payload: data,
272
+ timestamp: Date.now(),
273
+ };
274
+ this.eventSubject.next(event);
275
+ await new Promise((resolve) => setTimeout(resolve, 0));
420
276
  }
421
277
 
422
278
  /**
@@ -424,15 +280,15 @@ export class GraphFlow<T extends ZodSchema> {
424
280
  * @param {string} eventName - Name of the event to listen for
425
281
  * @param {Function} handler - Handler function to execute when event is emitted
426
282
  */
427
- on(eventName: string, handler: (...args: any[]) => void): void {
428
- this.eventEmitter.on(eventName, handler);
283
+ public on(eventName: string, handler: (...args: any[]) => void): void {
284
+ this.eventManager.on(eventName, handler);
429
285
  }
430
286
 
431
287
  /**
432
288
  * Updates the graph definition with new configuration
433
289
  * @param {GraphDefinition<T>} definition - New graph definition
434
290
  */
435
- load(definition: GraphDefinition<T>): void {
291
+ public load(definition: GraphDefinition<T>): void {
436
292
  // Clear all existing nodes
437
293
  this.nodes.clear();
438
294
  // Wipe out old node-based event listeners
@@ -494,8 +350,8 @@ export class GraphFlow<T extends ZodSchema> {
494
350
  * @param {string} message - Message to log
495
351
  * @param {any} data - Optional data to log
496
352
  */
497
- log(message: string, data?: any): void {
498
- console.log(`[Graph ${this.name}] ${message}`, data);
353
+ public log(message: string, data?: any): void {
354
+ this.logger.log(message, data);
499
355
  }
500
356
 
501
357
  /**
@@ -503,105 +359,35 @@ export class GraphFlow<T extends ZodSchema> {
503
359
  * @param {Node<T>} node - Node to add
504
360
  * @throws {Error} If node with same name already exists
505
361
  */
506
- addNode(node: Node<T, any>): void {
362
+ public addNode(node: Node<T, any>): void {
507
363
  this.nodes.set(node.name, node);
508
- if (node.events && node.events.length > 0) {
509
- for (const evt of node.events) {
510
- this.eventEmitter.on(evt, async (data?: Partial<GraphContext<T>>) => {
511
- const freshContext = this.createNewContext();
512
- if (data) Object.assign(freshContext, data);
513
- await this.executeNode(node.name, freshContext, undefined, true);
514
- });
515
- }
516
- }
364
+ this.eventManager.setupEventListeners();
517
365
  }
518
366
 
519
367
  /**
520
368
  * Removes a node from the graph
521
369
  * @param {string} nodeName - Name of the node to remove
522
370
  */
523
- removeNode(nodeName: string): void {
524
- const node = this.nodes.get(nodeName);
525
- if (!node) return;
526
-
527
- // remove the node from the map
371
+ public removeNode(nodeName: string): void {
528
372
  this.nodes.delete(nodeName);
529
-
530
- // remove any of its event-based listeners
531
- if (node.events && node.events.length > 0) {
532
- for (const evt of node.events) {
533
- // removeAllListeners(evt) would also remove other node listeners,
534
- // so we need a more fine-grained approach. Ideally, we should keep a reference
535
- // to the exact listener function we attached. For brevity, let's remove all for that event:
536
- this.eventEmitter.removeAllListeners(evt);
537
- }
538
- // Then reattach the others that remain in the graph
539
- for (const n of this.nodes.values()) {
540
- if (n.events && n.events.length > 0) {
541
- n.events.forEach((e) => {
542
- this.eventEmitter.on(e, async (data?: Partial<GraphContext<T>>) => {
543
- const freshContext = this.createNewContext();
544
- if (data) Object.assign(freshContext, data);
545
- await this.executeNode(n.name, freshContext, undefined, true);
546
- });
547
- });
548
- }
549
- }
550
- }
373
+ this.eventManager.setupEventListeners();
551
374
  }
552
375
 
553
376
  /**
554
377
  * Returns all nodes in the graph
555
378
  * @returns {Node<T>[]} Array of all nodes
556
379
  */
557
- getNodes(): Node<T, any>[] {
380
+ public getNodes(): Node<T, any>[] {
558
381
  return Array.from(this.nodes.values());
559
382
  }
560
383
 
561
- private setupGraphEventListeners(): void {
562
- if (this.graphEvents && this.graphEvents.length > 0) {
563
- this.graphEvents.forEach((event) => {
564
- this.eventEmitter.on(event, async (data?: Partial<GraphContext<T>>) => {
565
- const freshContext = this.createNewContext();
566
- if (data) Object.assign(freshContext, data);
567
-
568
- // Emit "graphStarted"
569
- this.eventEmitter.emit("graphStarted", { name: this.name });
570
-
571
- try {
572
- // Execute the graph starting from the entry node
573
- if (!this.entryNode) {
574
- throw new Error("No entry node defined for graph event handling");
575
- }
576
-
577
- await this.executeNode(
578
- this.entryNode,
579
- freshContext,
580
- undefined,
581
- false
582
- );
583
-
584
- // Emit "graphCompleted"
585
- this.eventEmitter.emit("graphCompleted", {
586
- name: this.name,
587
- context: this.context,
588
- });
589
- } catch (error) {
590
- // Emit "graphError"
591
- this.eventEmitter.emit("graphError", { name: this.name, error });
592
- this.globalErrorHandler?.(error as Error, freshContext);
593
- throw error;
594
- }
595
- });
596
- });
597
- }
598
- }
599
-
600
- getLogs(): string[] {
601
- return [...this.logs];
602
- }
603
-
604
- clearLogs(): void {
605
- this.logs = [];
384
+ /**
385
+ * Cleanup resources
386
+ */
387
+ public destroy(): void {
388
+ this.destroySubject.next();
389
+ this.destroySubject.complete();
390
+ this.eventSubject.complete();
391
+ this.stateSubject.complete();
606
392
  }
607
393
  }