@memberjunction/server 2.50.0 → 2.51.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 (59) hide show
  1. package/README.md +133 -0
  2. package/dist/config.d.ts +264 -1
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +24 -1
  5. package/dist/config.js.map +1 -1
  6. package/dist/generated/generated.d.ts +3 -0
  7. package/dist/generated/generated.d.ts.map +1 -1
  8. package/dist/generated/generated.js +532 -517
  9. package/dist/generated/generated.js.map +1 -1
  10. package/dist/generic/ResolverBase.d.ts +1 -1
  11. package/dist/generic/ResolverBase.d.ts.map +1 -1
  12. package/dist/generic/ResolverBase.js +13 -11
  13. package/dist/generic/ResolverBase.js.map +1 -1
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/orm.d.ts.map +1 -1
  19. package/dist/orm.js +6 -0
  20. package/dist/orm.js.map +1 -1
  21. package/dist/resolvers/ActionResolver.d.ts +3 -3
  22. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  23. package/dist/resolvers/ActionResolver.js +13 -10
  24. package/dist/resolvers/ActionResolver.js.map +1 -1
  25. package/dist/resolvers/FileResolver.js +1 -1
  26. package/dist/resolvers/FileResolver.js.map +1 -1
  27. package/dist/resolvers/RunAIAgentResolver.d.ts +49 -8
  28. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  29. package/dist/resolvers/RunAIAgentResolver.js +389 -106
  30. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  31. package/dist/resolvers/SqlLoggingConfigResolver.d.ts +61 -0
  32. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -0
  33. package/dist/resolvers/SqlLoggingConfigResolver.js +477 -0
  34. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -0
  35. package/dist/resolvers/UserFavoriteResolver.d.ts +3 -3
  36. package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
  37. package/dist/resolvers/UserFavoriteResolver.js +6 -6
  38. package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
  39. package/dist/resolvers/UserResolver.d.ts +3 -3
  40. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  41. package/dist/resolvers/UserResolver.js +6 -6
  42. package/dist/resolvers/UserResolver.js.map +1 -1
  43. package/dist/resolvers/UserViewResolver.d.ts +4 -4
  44. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  45. package/dist/resolvers/UserViewResolver.js +6 -6
  46. package/dist/resolvers/UserViewResolver.js.map +1 -1
  47. package/package.json +25 -25
  48. package/src/config.ts +28 -0
  49. package/src/generated/generated.ts +527 -518
  50. package/src/generic/ResolverBase.ts +17 -10
  51. package/src/index.ts +2 -1
  52. package/src/orm.ts +6 -0
  53. package/src/resolvers/ActionResolver.ts +21 -26
  54. package/src/resolvers/FileResolver.ts +1 -1
  55. package/src/resolvers/RunAIAgentResolver.ts +398 -100
  56. package/src/resolvers/SqlLoggingConfigResolver.ts +691 -0
  57. package/src/resolvers/UserFavoriteResolver.ts +6 -6
  58. package/src/resolvers/UserResolver.ts +6 -6
  59. package/src/resolvers/UserViewResolver.ts +6 -6
@@ -1,9 +1,12 @@
1
- import { Resolver, Mutation, Arg, Ctx, ObjectType, Field } from 'type-graphql';
1
+ import { Resolver, Mutation, Arg, Ctx, ObjectType, Field, PubSub, PubSubEngine, Subscription, Root, ResolverFilterData, ID } from 'type-graphql';
2
2
  import { UserPayload } from '../types.js';
3
- import { LogError, LogStatus, Metadata } from '@memberjunction/core';
3
+ import { LogError, LogStatus } from '@memberjunction/core';
4
4
  import { AIAgentEntity } from '@memberjunction/core-entities';
5
5
  import { AgentRunner } from '@memberjunction/ai-agents';
6
+ import { ExecuteAgentResult } from '@memberjunction/aiengine';
7
+ import { AIEngine } from '@memberjunction/aiengine';
6
8
  import { ResolverBase } from '../generic/ResolverBase.js';
9
+ import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
7
10
 
8
11
  @ObjectType()
9
12
  export class AIAgentRunResult {
@@ -11,37 +14,323 @@ export class AIAgentRunResult {
11
14
  success: boolean;
12
15
 
13
16
  @Field({ nullable: true })
14
- output?: string;
17
+ errorMessage?: string;
15
18
 
16
19
  @Field({ nullable: true })
17
- parsedResult?: string;
20
+ executionTimeMs?: number;
21
+
22
+ @Field()
23
+ payload: string; // JSON serialized ExecuteAgentResult with scalars only
24
+ }
25
+
26
+ @ObjectType()
27
+ export class AgentExecutionProgress {
28
+ @Field()
29
+ currentStep: string;
30
+
31
+ @Field()
32
+ percentage: number;
33
+
34
+ @Field()
35
+ message: string;
18
36
 
19
37
  @Field({ nullable: true })
20
- error?: string;
38
+ agentName?: string;
21
39
 
22
40
  @Field({ nullable: true })
23
- executionTimeMs?: number;
41
+ agentType?: string;
42
+ }
43
+
44
+ @ObjectType()
45
+ export class AgentStreamingContent {
46
+ @Field()
47
+ content: string;
48
+
49
+ @Field()
50
+ isPartial: boolean;
51
+
52
+ @Field({ nullable: true })
53
+ stepName?: string;
54
+
55
+ @Field({ nullable: true })
56
+ agentName?: string;
57
+ }
58
+
59
+ @ObjectType()
60
+ export class AgentExecutionStepSummary {
61
+ @Field()
62
+ stepId: string;
63
+
64
+ @Field()
65
+ stepName: string;
66
+
67
+ @Field({ nullable: true })
68
+ agentName?: string;
69
+
70
+ @Field({ nullable: true })
71
+ agentType?: string;
72
+
73
+ @Field()
74
+ startTime: Date;
24
75
 
25
76
  @Field({ nullable: true })
26
- tokensUsed?: number;
77
+ endTime?: Date;
78
+
79
+ @Field()
80
+ status: string;
27
81
 
28
82
  @Field({ nullable: true })
29
- agentRunId?: string;
83
+ result?: string;
84
+ }
85
+
86
+ @ObjectType()
87
+ export class AgentPartialResult {
88
+ @Field()
89
+ currentStep: string;
30
90
 
31
91
  @Field({ nullable: true })
32
- rawResult?: string;
92
+ partialOutput?: string;
93
+ }
94
+
95
+ @ObjectType()
96
+ export class AgentExecutionStreamMessage {
97
+ @Field(() => ID)
98
+ sessionId: string;
99
+
100
+ @Field(() => ID)
101
+ agentRunId: string;
102
+
103
+ @Field()
104
+ type: 'progress' | 'streaming' | 'partial_result' | 'complete';
105
+
106
+ @Field({ nullable: true })
107
+ progress?: AgentExecutionProgress;
108
+
109
+ @Field({ nullable: true })
110
+ streaming?: AgentStreamingContent;
33
111
 
34
112
  @Field({ nullable: true })
35
- nextStep?: string;
113
+ partialResult?: AgentPartialResult;
114
+
115
+ @Field()
116
+ timestamp: Date;
36
117
  }
37
118
 
119
+
120
+
121
+
122
+
38
123
  @Resolver()
39
124
  export class RunAIAgentResolver extends ResolverBase {
125
+ /**
126
+ * Sanitize ExecuteAgentResult for JSON serialization
127
+ * Removes circular references and non-serializable objects
128
+ */
129
+ private sanitizeAgentResult(result: ExecuteAgentResult): any {
130
+ const sanitized: any = {
131
+ success: result.success,
132
+ returnValue: result.returnValue,
133
+ errorMessage: result.errorMessage,
134
+ finalStep: result.finalStep,
135
+ cancelled: result.cancelled,
136
+ cancellationReason: result.cancellationReason
137
+ };
138
+
139
+ // Safely extract agent run data
140
+ if (result.agentRun) {
141
+ sanitized.agentRun = {
142
+ ID: result.agentRun.ID,
143
+ Status: result.agentRun.Status,
144
+ StartedAt: result.agentRun.StartedAt,
145
+ CompletedAt: result.agentRun.CompletedAt,
146
+ Success: result.agentRun.Success,
147
+ ErrorMessage: result.agentRun.ErrorMessage,
148
+ AgentID: result.agentRun.AgentID,
149
+ Result: result.agentRun.Result,
150
+ TotalTokensUsed: result.agentRun.TotalTokensUsed,
151
+ TotalCost: result.agentRun.TotalCost
152
+ };
153
+ }
154
+
155
+ // Safely extract execution tree (hierarchical structure with all step data)
156
+ if (result.executionTree && Array.isArray(result.executionTree)) {
157
+ sanitized.executionTree = this.sanitizeExecutionTree(result.executionTree);
158
+ }
159
+
160
+ return sanitized;
161
+ }
162
+
163
+ /**
164
+ * Sanitize execution tree for JSON serialization
165
+ */
166
+ private sanitizeExecutionTree(nodes: any[]): any[] {
167
+ return nodes.map(node => ({
168
+ step: node.step ? {
169
+ ID: node.step.ID,
170
+ AgentRunID: node.step.AgentRunID,
171
+ StepNumber: node.step.StepNumber,
172
+ StepType: node.step.StepType,
173
+ StepName: node.step.StepName,
174
+ TargetID: node.step.TargetID,
175
+ Status: node.step.Status,
176
+ StartedAt: node.step.StartedAt,
177
+ CompletedAt: node.step.CompletedAt,
178
+ Success: node.step.Success,
179
+ ErrorMessage: node.step.ErrorMessage,
180
+ InputData: node.step.InputData,
181
+ OutputData: node.step.OutputData
182
+ } : null,
183
+ inputData: node.inputData,
184
+ outputData: node.outputData,
185
+ executionType: node.executionType,
186
+ startTime: node.startTime,
187
+ endTime: node.endTime,
188
+ durationMs: node.durationMs,
189
+ nextStepDecision: node.nextStepDecision,
190
+ children: node.children && node.children.length > 0
191
+ ? this.sanitizeExecutionTree(node.children)
192
+ : [],
193
+ depth: node.depth,
194
+ parentStepId: node.parentStepId,
195
+ agentHierarchy: node.agentHierarchy
196
+ }));
197
+ }
198
+
199
+ /**
200
+ * Parse and validate JSON input
201
+ */
202
+ private parseJsonInput(jsonString: string | undefined, fieldName: string): any {
203
+ if (!jsonString) return {};
204
+
205
+ try {
206
+ return JSON.parse(jsonString);
207
+ } catch (parseError) {
208
+ throw new Error(`Invalid JSON in ${fieldName}: ${(parseError as Error).message}`);
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Validate the agent entity
214
+ */
215
+ private async validateAgent(agentId: string, currentUser: any): Promise<AIAgentEntity> {
216
+ // Use AIEngine to get cached agent data
217
+ await AIEngine.Instance.Config(false, currentUser);
218
+
219
+ // Find agent in cached collection
220
+ const agentEntity = AIEngine.Instance.Agents.find((a: AIAgentEntity) => a.ID === agentId);
221
+
222
+ if (!agentEntity) {
223
+ throw new Error(`AI Agent with ID ${agentId} not found`);
224
+ }
225
+
226
+ // Check if agent is active
227
+ if (agentEntity.Status !== 'Active') {
228
+ throw new Error(`AI Agent "${agentEntity.Name}" is not active (Status: ${agentEntity.Status})`);
229
+ }
230
+
231
+ return agentEntity;
232
+ }
233
+
234
+ /**
235
+ * Create streaming progress callback
236
+ */
237
+ private createProgressCallback(pubSub: PubSubEngine, sessionId: string, userPayload: UserPayload, agentRunIdRef: { id: string }) {
238
+ return (progress: any) => {
239
+ // Only publish progress for significant steps (not initialization noise)
240
+ const significantSteps = ['prompt_execution', 'action_execution', 'subagent_execution', 'decision_processing'];
241
+ if (!significantSteps.includes(progress.step)) {
242
+ console.log(`🔇 Skipping noise progress: ${progress.step}`);
243
+ return;
244
+ }
245
+
246
+ console.log('📡 Publishing progress update:', {
247
+ step: progress.step,
248
+ percentage: progress.percentage,
249
+ message: progress.message,
250
+ sessionId,
251
+ agentRunId: agentRunIdRef.id
252
+ });
253
+
254
+ // Publish progress updates
255
+ const progressMsg: AgentExecutionStreamMessage = {
256
+ sessionId,
257
+ agentRunId: agentRunIdRef.id,
258
+ type: 'progress',
259
+ progress: {
260
+ currentStep: progress.step,
261
+ percentage: progress.percentage,
262
+ message: progress.message,
263
+ agentName: (progress.metadata as any)?.agentName || undefined,
264
+ agentType: (progress.metadata as any)?.agentType || undefined
265
+ },
266
+ timestamp: new Date()
267
+ };
268
+ this.PublishProgressUpdate(pubSub, progressMsg, userPayload);
269
+ };
270
+ }
271
+
272
+ private PublishProgressUpdate(pubSub: PubSubEngine, data: any, userPayload: UserPayload) {
273
+ pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
274
+ message: JSON.stringify({
275
+ resolver: 'RunAIAgentResolver',
276
+ type: 'ExecutionProgress',
277
+ status: 'ok',
278
+ data,
279
+ }),
280
+ sessionId: userPayload.sessionId,
281
+ });
282
+ }
283
+
284
+
285
+ private PublishStreamingUpdate(pubSub: PubSubEngine, data: any, userPayload: UserPayload) {
286
+ pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
287
+ message: JSON.stringify({
288
+ resolver: 'RunAIAgentResolver',
289
+ type: 'StreamingContent',
290
+ status: 'ok',
291
+ data,
292
+ }),
293
+ sessionId: userPayload.sessionId,
294
+ });
295
+ }
296
+
297
+ /**
298
+ * Create streaming content callback
299
+ */
300
+ private createStreamingCallback(pubSub: PubSubEngine, sessionId: string, userPayload: UserPayload, agentRunIdRef: { id: string }) {
301
+ return (chunk: any) => {
302
+ console.log('💬 Publishing streaming content:', {
303
+ content: chunk.content.substring(0, 50) + '...',
304
+ isComplete: chunk.isComplete,
305
+ stepType: chunk.stepType,
306
+ sessionId,
307
+ agentRunId: agentRunIdRef.id
308
+ });
309
+
310
+ // Publish streaming content
311
+ const streamMsg: AgentExecutionStreamMessage = {
312
+ sessionId,
313
+ agentRunId: agentRunIdRef.id,
314
+ type: 'streaming',
315
+ streaming: {
316
+ content: chunk.content,
317
+ isPartial: !chunk.isComplete,
318
+ stepName: chunk.stepType,
319
+ agentName: chunk.modelName
320
+ },
321
+ timestamp: new Date()
322
+ };
323
+ this.PublishStreamingUpdate(pubSub, streamMsg, userPayload);
324
+ };
325
+ }
326
+
40
327
  @Mutation(() => AIAgentRunResult)
41
328
  async RunAIAgent(
42
329
  @Arg('agentId') agentId: string,
43
330
  @Ctx() { userPayload }: { userPayload: UserPayload },
44
331
  @Arg('messages') messagesJson: string,
332
+ @Arg('sessionId') sessionId: string,
333
+ @PubSub() pubSub: PubSubEngine,
45
334
  @Arg('data', { nullable: true }) data?: string,
46
335
  @Arg('templateData', { nullable: true }) templateData?: string
47
336
  ): Promise<AIAgentRunResult> {
@@ -50,125 +339,134 @@ export class RunAIAgentResolver extends ResolverBase {
50
339
  try {
51
340
  LogStatus(`=== RUNNING AI AGENT FOR ID: ${agentId} ===`);
52
341
 
53
- // Parse messages (required)
54
- let parsedMessages;
55
- try {
56
- parsedMessages = JSON.parse(messagesJson);
57
- if (!Array.isArray(parsedMessages)) {
58
- throw new Error('Messages must be an array');
59
- }
60
- } catch (parseError) {
61
- return {
62
- success: false,
63
- error: `Invalid JSON in messages: ${(parseError as Error).message}`,
64
- executionTimeMs: Date.now() - startTime
65
- };
66
- }
67
-
68
- // Parse data contexts (JSON strings)
69
- let parsedData = {};
70
- let parsedTemplateData = {};
71
-
72
- if (data) {
73
- try {
74
- parsedData = JSON.parse(data);
75
- } catch (parseError) {
76
- return {
77
- success: false,
78
- error: `Invalid JSON in data: ${(parseError as Error).message}`,
79
- executionTimeMs: Date.now() - startTime
80
- };
81
- }
342
+ // Parse and validate messages
343
+ const parsedMessages = this.parseJsonInput(messagesJson, 'messages');
344
+ if (!Array.isArray(parsedMessages)) {
345
+ throw new Error('Messages must be an array');
82
346
  }
83
347
 
84
- if (templateData) {
85
- try {
86
- parsedTemplateData = JSON.parse(templateData);
87
- } catch (parseError) {
88
- return {
89
- success: false,
90
- error: `Invalid JSON in template data: ${(parseError as Error).message}`,
91
- executionTimeMs: Date.now() - startTime
92
- };
93
- }
94
- }
348
+ // Parse data contexts
349
+ const parsedData = this.parseJsonInput(data, 'data');
350
+ const parsedTemplateData = this.parseJsonInput(templateData, 'templateData');
95
351
 
96
- // Get current user from payload
352
+ // Get and validate current user
97
353
  const currentUser = this.GetUserFromPayload(userPayload);
98
354
  if (!currentUser) {
99
- return {
100
- success: false,
101
- error: 'Unable to determine current user',
102
- executionTimeMs: Date.now() - startTime
103
- };
355
+ throw new Error('Unable to determine current user');
104
356
  }
105
357
 
106
- const md = new Metadata();
107
-
108
- // Load the AI agent entity
109
- const agentEntity = await md.GetEntityObject<AIAgentEntity>('AI Agents', currentUser);
110
- await agentEntity.Load(agentId);
111
-
112
- if (!agentEntity.IsSaved) {
113
- return {
114
- success: false,
115
- error: `AI Agent with ID ${agentId} not found`,
116
- executionTimeMs: Date.now() - startTime
117
- };
118
- }
358
+ // Validate agent
359
+ const agentEntity = await this.validateAgent(agentId, currentUser);
119
360
 
120
- // Check if agent is active
121
- if (agentEntity.Status !== 'Active') {
122
- return {
123
- success: false,
124
- error: `AI Agent "${agentEntity.Name}" is not active (Status: ${agentEntity.Status})`,
125
- executionTimeMs: Date.now() - startTime
126
- };
127
- }
128
-
129
- // Create AI agent runner and execute
361
+ // Create AI agent runner
130
362
  const agentRunner = new AgentRunner();
131
363
 
132
- // Execute the agent with the parsed messages and optional data
364
+ // Track agent run ID for streaming (use ref to update later)
365
+ const agentRunIdRef = { id: 'pending' };
366
+
367
+ console.log(`🚀 Starting agent execution with sessionId: ${sessionId}`);
368
+
369
+ // Execute the agent with streaming callbacks
133
370
  const result = await agentRunner.RunAgent({
134
371
  agent: agentEntity,
135
372
  conversationMessages: parsedMessages,
136
373
  contextUser: currentUser,
137
- ...(Object.keys(parsedData).length > 0 && { data: parsedData }),
138
- ...(Object.keys(parsedTemplateData).length > 0 && { templateData: parsedTemplateData })
374
+ onProgress: this.createProgressCallback(pubSub, sessionId, userPayload, agentRunIdRef),
375
+ onStreaming: this.createStreamingCallback(pubSub, sessionId, userPayload, agentRunIdRef)
139
376
  });
140
377
 
378
+ // Update agent run ID once available
379
+ if (result.agentRun) {
380
+ agentRunIdRef.id = result.agentRun.ID;
381
+ }
382
+
141
383
  const executionTime = Date.now() - startTime;
142
384
 
143
- if (result.nextStep !== 'failed') {
385
+ // Publish final events
386
+ this.publishFinalEvents(pubSub, sessionId, userPayload, result);
387
+
388
+ // Create sanitized payload for JSON serialization
389
+ const sanitizedResult = this.sanitizeAgentResult(result);
390
+ const payload = JSON.stringify(sanitizedResult);
391
+
392
+ // Log completion
393
+ if (result.success) {
144
394
  LogStatus(`=== AI AGENT RUN COMPLETED FOR: ${agentEntity.Name} (${executionTime}ms) ===`);
145
-
146
- return {
147
- success: true,
148
- output: result.rawResult,
149
- parsedResult: typeof result.returnValue === 'string' ? result.returnValue : JSON.stringify(result.returnValue),
150
- rawResult: result.rawResult,
151
- executionTimeMs: executionTime,
152
- nextStep: result.nextStep
153
- };
154
395
  } else {
155
396
  LogError(`AI Agent run failed for ${agentEntity.Name}: ${result.errorMessage}`);
156
- return {
157
- success: false,
158
- error: result.errorMessage,
159
- executionTimeMs: executionTime,
160
- nextStep: result.nextStep
161
- };
162
397
  }
163
398
 
399
+ return {
400
+ success: result.success,
401
+ errorMessage: result.errorMessage || undefined,
402
+ executionTimeMs: executionTime,
403
+ payload
404
+ };
405
+
164
406
  } catch (error) {
165
407
  const executionTime = Date.now() - startTime;
166
408
  LogError(`AI Agent run failed:`, undefined, error);
167
- return {
409
+
410
+ // Create error payload
411
+ const errorResult = {
168
412
  success: false,
169
- error: (error as Error).message || 'Unknown error occurred',
413
+ errorMessage: (error as Error).message || 'Unknown error occurred',
170
414
  executionTimeMs: executionTime
171
415
  };
416
+
417
+ return {
418
+ success: false,
419
+ errorMessage: errorResult.errorMessage,
420
+ executionTimeMs: executionTime,
421
+ payload: JSON.stringify(errorResult)
422
+ };
172
423
  }
173
424
  }
425
+
426
+ /**
427
+ * Publish final streaming events (partial result and completion)
428
+ */
429
+ private publishFinalEvents(pubSub: PubSubEngine, sessionId: string, userPayload: UserPayload, result: ExecuteAgentResult) {
430
+ if (result.agentRun) {
431
+ // Get the last step from execution tree
432
+ let lastStep = 'Completed';
433
+ if (result.executionTree && result.executionTree.length > 0) {
434
+ // Find the last step by traversing the tree
435
+ const findLastStep = (nodes: any[]): any => {
436
+ let last = nodes[nodes.length - 1];
437
+ if (last.children && last.children.length > 0) {
438
+ return findLastStep(last.children);
439
+ }
440
+ return last;
441
+ };
442
+ const lastNode = findLastStep(result.executionTree);
443
+ lastStep = lastNode.step?.StepName || 'Completed';
444
+ }
445
+
446
+ // Publish partial result
447
+ const partialResult: AgentPartialResult = {
448
+ currentStep: lastStep,
449
+ partialOutput: result.returnValue || undefined
450
+ };
451
+
452
+ const partialMsg: AgentExecutionStreamMessage = {
453
+ sessionId,
454
+ agentRunId: result.agentRun.ID,
455
+ type: 'partial_result',
456
+ partialResult,
457
+ timestamp: new Date()
458
+ };
459
+ this.PublishStreamingUpdate(pubSub, partialMsg, userPayload);
460
+ }
461
+
462
+ // Publish completion
463
+ const completeMsg: AgentExecutionStreamMessage = {
464
+ sessionId,
465
+ agentRunId: result.agentRun?.ID || 'unknown',
466
+ type: 'complete',
467
+ timestamp: new Date()
468
+ };
469
+ this.PublishStreamingUpdate(pubSub, completeMsg, userPayload);
470
+ }
471
+
174
472
  }