@ariaflowagents/core 0.4.6 → 0.4.7

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.
@@ -9,6 +9,7 @@ import { HookRunner } from '../hooks/HookRunner.js';
9
9
  import { ToolEnforcer } from '../guards/ToolEnforcer.js';
10
10
  import { defaultEnforcementRules } from '../guards/rules.js';
11
11
  import { MemoryStore } from '../session/stores/MemoryStore.js';
12
+ import { createHttpCallback } from '../callbacks/httpCallback.js';
12
13
  export class Runtime {
13
14
  config;
14
15
  agents = new Map();
@@ -27,7 +28,15 @@ export class Runtime {
27
28
  triageAgentId;
28
29
  constructor(config) {
29
30
  this.config = config;
30
- for (const agent of config.agents) {
31
+ // Robustly handle both array and object formats for agents
32
+ const rawAgents = config.agents;
33
+ const agentList = Array.isArray(rawAgents)
34
+ ? rawAgents
35
+ : Object.entries(rawAgents).map(([id, agent]) => ({
36
+ ...agent,
37
+ id: agent.id ?? id,
38
+ }));
39
+ for (const agent of agentList) {
31
40
  this.agents.set(agent.id, agent);
32
41
  }
33
42
  this.defaultAgentId = config.defaultAgentId;
@@ -36,7 +45,23 @@ export class Runtime {
36
45
  this.maxHandoffs = config.maxHandoffs ?? 10;
37
46
  this.stopConditions = config.stopConditions ?? defaultStopConditions;
38
47
  this.sessionStore = config.sessionStore ?? new MemoryStore();
39
- this.hookRunner = new HookRunner(config.hooks ?? {});
48
+ const hooks = { ...config.hooks };
49
+ if (config.callback) {
50
+ console.log('[Runtime] HTTP callback configured for:', config.callback.url);
51
+ console.log('[Runtime] Callback config:', JSON.stringify(config.callback, null, 2));
52
+ console.log('[Runtime] Hooks before wiring:', Object.keys(hooks));
53
+ const httpCallback = createHttpCallback(config.callback);
54
+ const originalHook = hooks.onStreamPart;
55
+ hooks.onStreamPart = async (context, part) => {
56
+ console.log('[Runtime] onStreamPart triggered for:', part.type);
57
+ if (originalHook) {
58
+ await originalHook(context, part);
59
+ }
60
+ await httpCallback(context, part);
61
+ };
62
+ console.log('[Runtime] Hooks after wiring:', Object.keys(hooks));
63
+ }
64
+ this.hookRunner = new HookRunner(hooks);
40
65
  this.enforcer = new ToolEnforcer(config.enforcementRules ?? defaultEnforcementRules);
41
66
  this.contextManager = config.contextManager;
42
67
  this.alwaysRouteThroughTriage = config.alwaysRouteThroughTriage ?? false;
@@ -77,6 +102,10 @@ export class Runtime {
77
102
  });
78
103
  const messagesAfter = session.messages.length;
79
104
  if (messagesBefore !== messagesAfter) {
105
+ // NOTE: This is the only yield that bypasses emit() because RunContext
106
+ // doesn't exist yet at this point. The onStreamPart hook will NOT fire
107
+ // for context-compacted events. This is intentional - context compaction
108
+ // occurs before the agent loop begins.
80
109
  yield {
81
110
  type: 'context-compacted',
82
111
  messagesBefore,
@@ -130,13 +159,13 @@ export class Runtime {
130
159
  catch (error) {
131
160
  await this.hookRunner.onError(context, error);
132
161
  await this.hookRunner.onEnd(context, { success: false, error: error });
133
- yield { type: 'error', error: error.message };
162
+ yield* this.emit(context, { type: 'error', error: error.message });
134
163
  }
135
164
  finally {
136
165
  controller.signal.removeEventListener('abort', abortHandler);
137
166
  this.abortControllers.delete(session.id);
138
167
  await this.sessionStore.save(session);
139
- yield { type: 'done', sessionId: session.id };
168
+ yield* this.emit(context, { type: 'done', sessionId: session.id });
140
169
  }
141
170
  }
142
171
  async *chat(sessionId, input, userId) {
@@ -145,30 +174,30 @@ export class Runtime {
145
174
  async *runLoop(context, injectionQueue, input, abortController) {
146
175
  while (context.handoffStack.length < this.maxHandoffs) {
147
176
  if (abortController?.signal.aborted) {
148
- yield {
177
+ yield* this.emit(context, {
149
178
  type: 'interrupted',
150
179
  sessionId: context.session.id,
151
180
  reason: abortController.signal.reason ?? 'Operation cancelled',
152
181
  timestamp: new Date(),
153
182
  lastAgentId: context.agentId,
154
183
  lastStep: context.stepCount,
155
- };
184
+ });
156
185
  return;
157
186
  }
158
187
  if (context.handoffStack.includes(context.agentId)) {
159
- yield {
188
+ yield* this.emit(context, {
160
189
  type: 'error',
161
190
  error: `Circular handoff detected: ${context.handoffStack.join(' -> ')} -> ${context.agentId}`,
162
- };
191
+ });
163
192
  return;
164
193
  }
165
194
  context.handoffStack.push(context.agentId);
166
195
  const agent = this.agents.get(context.agentId);
167
196
  if (!agent) {
168
- yield { type: 'error', error: `Agent "${context.agentId}" not found` };
197
+ yield* this.emit(context, { type: 'error', error: `Agent "${context.agentId}" not found` });
169
198
  return;
170
199
  }
171
- yield { type: 'agent-start', agentId: agent.id };
200
+ yield* this.emit(context, { type: 'agent-start', agentId: agent.id });
172
201
  await this.hookRunner.onAgentStart(context, agent.id);
173
202
  let autoContext;
174
203
  if (agent.autoRetrieve) {
@@ -182,18 +211,18 @@ export class Runtime {
182
211
  timestamp: Date.now(),
183
212
  };
184
213
  await this.hookRunner.onToolCall(context, callRecord);
185
- yield {
214
+ yield* this.emit(context, {
186
215
  type: 'tool-start',
187
216
  toolCallId,
188
217
  toolName,
189
218
  message: agent.autoRetrieve.message ?? 'Retrieving context...',
190
- };
191
- yield {
219
+ });
220
+ yield* this.emit(context, {
192
221
  type: 'tool-call',
193
222
  toolCallId,
194
223
  toolName,
195
224
  args: callRecord.args,
196
- };
225
+ });
197
226
  let result = null;
198
227
  try {
199
228
  result = await agent.autoRetrieve.run({ input, context });
@@ -203,31 +232,31 @@ export class Runtime {
203
232
  callRecord.success = false;
204
233
  callRecord.error = error;
205
234
  await this.hookRunner.onToolError(context, callRecord, callRecord.error);
206
- yield {
235
+ yield* this.emit(context, {
207
236
  type: 'tool-error',
208
237
  toolCallId,
209
238
  toolName,
210
239
  error: callRecord.error.message,
211
- };
240
+ });
212
241
  }
213
242
  finally {
214
243
  callRecord.durationMs = Date.now() - callRecord.timestamp;
215
244
  }
216
245
  if (callRecord.result) {
217
246
  await this.hookRunner.onToolResult(context, callRecord);
218
- yield {
247
+ yield* this.emit(context, {
219
248
  type: 'tool-result',
220
249
  toolCallId,
221
250
  toolName,
222
251
  result: callRecord.result,
223
- };
252
+ });
224
253
  }
225
- yield {
254
+ yield* this.emit(context, {
226
255
  type: 'tool-done',
227
256
  toolCallId,
228
257
  toolName,
229
258
  durationMs: callRecord.durationMs ?? 0,
230
- };
259
+ });
231
260
  if (result?.text?.trim()) {
232
261
  autoContext = {
233
262
  label: agent.autoRetrieve.label ?? 'Relevant Context',
@@ -243,16 +272,16 @@ export class Runtime {
243
272
  if (this.isFlowAgent(agent)) {
244
273
  const stopResult = checkStopConditions(context, this.stopConditions);
245
274
  if (stopResult.shouldStop) {
246
- yield { type: 'error', error: `Stopped: ${stopResult.reason}` };
275
+ yield* this.emit(context, { type: 'error', error: `Stopped: ${stopResult.reason}` });
247
276
  return;
248
277
  }
249
278
  const agentMaxSteps = agent.maxSteps ?? agent.maxTurns ?? this.maxSteps;
250
279
  if (agentMaxSteps <= 0) {
251
- yield { type: 'error', error: `Max steps exceeded for agent "${agent.id}"` };
280
+ yield* this.emit(context, { type: 'error', error: `Max steps exceeded for agent "${agent.id}"` });
252
281
  return;
253
282
  }
254
283
  context.stepCount += 1;
255
- yield { type: 'step-start', step: context.stepCount, agentId: agent.id };
284
+ yield* this.emit(context, { type: 'step-start', step: context.stepCount, agentId: agent.id });
256
285
  await this.hookRunner.onStepStart(context, context.stepCount);
257
286
  const toolCalls = [];
258
287
  try {
@@ -266,7 +295,7 @@ export class Runtime {
266
295
  if (context.session.metadata) {
267
296
  context.session.metadata.totalSteps += 1;
268
297
  }
269
- yield { type: 'step-end', step: context.stepCount, agentId: agent.id };
298
+ yield* this.emit(context, { type: 'step-end', step: context.stepCount, agentId: agent.id });
270
299
  await this.hookRunner.onStepEnd(context, context.stepCount, {
271
300
  toolCalls,
272
301
  finishReason: 'flow',
@@ -277,7 +306,7 @@ export class Runtime {
277
306
  catch (error) {
278
307
  context.consecutiveErrors += 1;
279
308
  await this.hookRunner.onError(context, error);
280
- yield { type: 'error', error: error.message };
309
+ yield* this.emit(context, { type: 'error', error: error.message });
281
310
  if (context.consecutiveErrors >= 3) {
282
311
  return;
283
312
  }
@@ -290,12 +319,12 @@ export class Runtime {
290
319
  while (agentSteps < agentMaxSteps) {
291
320
  const stopResult = checkStopConditions(context, this.stopConditions);
292
321
  if (stopResult.shouldStop) {
293
- yield { type: 'error', error: `Stopped: ${stopResult.reason}` };
322
+ yield* this.emit(context, { type: 'error', error: `Stopped: ${stopResult.reason}` });
294
323
  return;
295
324
  }
296
325
  agentSteps += 1;
297
326
  context.stepCount += 1;
298
- yield { type: 'step-start', step: context.stepCount, agentId: agent.id };
327
+ yield* this.emit(context, { type: 'step-start', step: context.stepCount, agentId: agent.id });
299
328
  await this.hookRunner.onStepStart(context, context.stepCount);
300
329
  try {
301
330
  const model = agent.model ?? this.defaultModel;
@@ -323,7 +352,7 @@ export class Runtime {
323
352
  }
324
353
  handoffTo = target;
325
354
  handoffReason = decision.reason ?? 'Routed by triage';
326
- yield { type: 'step-end', step: context.stepCount, agentId: agent.id };
355
+ yield* this.emit(context, { type: 'step-end', step: context.stepCount, agentId: agent.id });
327
356
  await this.hookRunner.onStepEnd(context, context.stepCount, {
328
357
  toolCalls: [],
329
358
  finishReason: 'handoff',
@@ -346,7 +375,7 @@ export class Runtime {
346
375
  if (finalResult) {
347
376
  continue;
348
377
  }
349
- yield { type: 'text-delta', text: chunk.text };
378
+ yield* this.emit(context, { type: 'text-delta', text: chunk.text });
350
379
  }
351
380
  if (chunk.type === 'tool-call') {
352
381
  const args = 'args' in chunk ? chunk.args : chunk.input;
@@ -363,18 +392,18 @@ export class Runtime {
363
392
  const toolConfig = this.agents.get(context.agentId)?.tools;
364
393
  const toolDef = toolConfig?.[chunk.toolName];
365
394
  const filler = toolDef?.filler ?? `Let me check ${chunk.toolName}...`;
366
- yield {
395
+ yield* this.emit(context, {
367
396
  type: 'tool-start',
368
397
  toolCallId: chunk.toolCallId,
369
398
  toolName: chunk.toolName,
370
399
  message: filler,
371
- };
372
- yield {
400
+ });
401
+ yield* this.emit(context, {
373
402
  type: 'tool-call',
374
403
  toolCallId: chunk.toolCallId,
375
404
  toolName: chunk.toolName,
376
405
  args,
377
- };
406
+ });
378
407
  }
379
408
  if (chunk.type === 'tool-result') {
380
409
  const startTime = toolCalls.find(call => call.toolCallId === chunk.toolCallId)?.timestamp ?? Date.now();
@@ -388,12 +417,12 @@ export class Runtime {
388
417
  }
389
418
  // Emit tool-done with duration
390
419
  const durationMs = Date.now() - startTime;
391
- yield {
420
+ yield* this.emit(context, {
392
421
  type: 'tool-done',
393
422
  toolCallId: chunk.toolCallId,
394
423
  toolName: chunk.toolName,
395
424
  durationMs,
396
- };
425
+ });
397
426
  if (callRecord) {
398
427
  const enforcement = await this.enforcer.checkResult(callRecord, {
399
428
  previousCalls: context.toolCallHistory,
@@ -405,31 +434,31 @@ export class Runtime {
405
434
  callRecord.success = false;
406
435
  callRecord.error = new Error(reason);
407
436
  await this.hookRunner.onToolError(context, callRecord, callRecord.error);
408
- yield {
437
+ yield* this.emit(context, {
409
438
  type: 'tool-error',
410
439
  toolCallId: chunk.toolCallId,
411
440
  toolName: chunk.toolName,
412
441
  error: reason,
413
- };
442
+ });
414
443
  finalResult = { type: 'final', text: reason };
415
444
  if (!finalEmitted) {
416
445
  finalEmitted = true;
417
- yield { type: 'text-delta', text: reason };
446
+ yield* this.emit(context, { type: 'text-delta', text: reason });
418
447
  }
419
448
  continue;
420
449
  }
421
450
  }
422
- yield {
451
+ yield* this.emit(context, {
423
452
  type: 'tool-result',
424
453
  toolCallId: chunk.toolCallId,
425
454
  toolName: chunk.toolName,
426
455
  result: toolResult,
427
- };
456
+ });
428
457
  if (isFinalResult(toolResult)) {
429
458
  finalResult = toolResult;
430
459
  if (!finalEmitted) {
431
460
  finalEmitted = true;
432
- yield { type: 'text-delta', text: toolResult.text };
461
+ yield* this.emit(context, { type: 'text-delta', text: toolResult.text });
433
462
  }
434
463
  continue;
435
464
  }
@@ -456,7 +485,7 @@ export class Runtime {
456
485
  }
457
486
  context.consecutiveErrors = 0;
458
487
  const finishReason = finalResult ? 'final' : await result.finishReason;
459
- yield { type: 'step-end', step: context.stepCount, agentId: agent.id };
488
+ yield* this.emit(context, { type: 'step-end', step: context.stepCount, agentId: agent.id });
460
489
  await this.hookRunner.onStepEnd(context, context.stepCount, {
461
490
  toolCalls,
462
491
  finishReason,
@@ -470,25 +499,25 @@ export class Runtime {
470
499
  catch (error) {
471
500
  context.consecutiveErrors += 1;
472
501
  await this.hookRunner.onError(context, error);
473
- yield { type: 'error', error: error.message };
502
+ yield* this.emit(context, { type: 'error', error: error.message });
474
503
  if (context.consecutiveErrors >= 3) {
475
504
  return;
476
505
  }
477
506
  }
478
507
  }
479
508
  }
480
- yield { type: 'agent-end', agentId: agent.id };
509
+ yield* this.emit(context, { type: 'agent-end', agentId: agent.id });
481
510
  await this.hookRunner.onAgentEnd(context, agent.id);
482
511
  if (!handoffTo) {
483
512
  break;
484
513
  }
485
514
  await this.hookRunner.onHandoff(context, context.agentId, handoffTo, handoffReason);
486
- yield {
515
+ yield* this.emit(context, {
487
516
  type: 'handoff',
488
517
  from: context.agentId,
489
518
  to: handoffTo,
490
519
  reason: handoffReason,
491
- };
520
+ });
492
521
  context.session.handoffHistory.push({
493
522
  from: context.agentId,
494
523
  to: handoffTo,
@@ -508,7 +537,7 @@ export class Runtime {
508
537
  context.agentId = handoffTo;
509
538
  }
510
539
  if (context.handoffStack.length >= this.maxHandoffs) {
511
- yield { type: 'error', error: `Maximum handoffs (${this.maxHandoffs}) exceeded` };
540
+ yield* this.emit(context, { type: 'error', error: `Maximum handoffs (${this.maxHandoffs}) exceeded` });
512
541
  }
513
542
  }
514
543
  createSession(id, userId) {
@@ -697,7 +726,7 @@ Do not change the flow requirements. Keep the reply concise.`;
697
726
  });
698
727
  for await (const chunk of result.fullStream) {
699
728
  if (chunk.type === 'text-delta') {
700
- yield { type: 'text-delta', text: chunk.text };
729
+ yield* this.emit(context, { type: 'text-delta', text: chunk.text });
701
730
  }
702
731
  }
703
732
  const response = await result.response;
@@ -709,7 +738,7 @@ Do not change the flow requirements. Keep the reply concise.`;
709
738
  context.session.metadata.totalTokens += totalTokens;
710
739
  context.session.metadata.totalSteps += 1;
711
740
  }
712
- yield { type: 'turn-end' };
741
+ yield* this.emit(context, { type: 'turn-end' });
713
742
  }
714
743
  async *runFlowAgent(agent, context, input, systemPrompt, handoffTool, onHandoff, toolCalls) {
715
744
  const model = agent.model ?? this.defaultModel;
@@ -739,7 +768,7 @@ Do not change the flow requirements. Keep the reply concise.`;
739
768
  for await (const part of flowManager.initialize()) {
740
769
  switch (part.type) {
741
770
  case 'text-delta':
742
- yield { type: 'text-delta', text: part.text };
771
+ yield* this.emit(context, { type: 'text-delta', text: part.text });
743
772
  break;
744
773
  case 'tool-call': {
745
774
  const toolCallId = part.toolCallId ?? crypto.randomUUID();
@@ -752,12 +781,12 @@ Do not change the flow requirements. Keep the reply concise.`;
752
781
  };
753
782
  toolCalls.push(callRecord);
754
783
  await this.hookRunner.onToolCall(context, callRecord);
755
- yield {
784
+ yield* this.emit(context, {
756
785
  type: 'tool-call',
757
786
  toolCallId,
758
787
  toolName: part.toolName,
759
788
  args: part.args,
760
- };
789
+ });
761
790
  break;
762
791
  }
763
792
  case 'tool-result': {
@@ -780,44 +809,44 @@ Do not change the flow requirements. Keep the reply concise.`;
780
809
  callRecord.success = false;
781
810
  callRecord.error = new Error(reason);
782
811
  await this.hookRunner.onToolError(context, callRecord, callRecord.error);
783
- yield {
812
+ yield* this.emit(context, {
784
813
  type: 'tool-error',
785
814
  toolCallId,
786
815
  toolName: part.toolName,
787
816
  error: reason,
788
- };
789
- yield { type: 'error', error: reason };
817
+ });
818
+ yield* this.emit(context, { type: 'error', error: reason });
790
819
  return;
791
820
  }
792
821
  }
793
- yield {
822
+ yield* this.emit(context, {
794
823
  type: 'tool-result',
795
824
  toolCallId,
796
825
  toolName: part.toolName,
797
826
  result: part.result,
798
- };
827
+ });
799
828
  break;
800
829
  }
801
830
  case 'handoff':
802
831
  onHandoff(part.targetAgent, part.reason);
803
832
  break;
804
833
  case 'node-enter':
805
- yield { type: 'node-enter', nodeName: part.nodeName };
834
+ yield* this.emit(context, { type: 'node-enter', nodeName: part.nodeName });
806
835
  break;
807
836
  case 'node-exit':
808
- yield { type: 'node-exit', nodeName: part.nodeName };
837
+ yield* this.emit(context, { type: 'node-exit', nodeName: part.nodeName });
809
838
  break;
810
839
  case 'flow-transition':
811
- yield { type: 'flow-transition', from: part.from, to: part.to };
840
+ yield* this.emit(context, { type: 'flow-transition', from: part.from, to: part.to });
812
841
  break;
813
842
  case 'flow-end':
814
- yield { type: 'flow-end', reason: part.reason };
843
+ yield* this.emit(context, { type: 'flow-end', reason: part.reason });
815
844
  break;
816
845
  case 'turn-end':
817
- yield { type: 'turn-end' };
846
+ yield* this.emit(context, { type: 'turn-end' });
818
847
  break;
819
848
  case 'error':
820
- yield { type: 'error', error: part.error };
849
+ yield* this.emit(context, { type: 'error', error: part.error });
821
850
  break;
822
851
  default:
823
852
  break;
@@ -827,7 +856,7 @@ Do not change the flow requirements. Keep the reply concise.`;
827
856
  for await (const part of flowManager.process(input, { appendUserToSession: false })) {
828
857
  switch (part.type) {
829
858
  case 'text-delta':
830
- yield { type: 'text-delta', text: part.text };
859
+ yield* this.emit(context, { type: 'text-delta', text: part.text });
831
860
  break;
832
861
  case 'tool-call': {
833
862
  const toolCallId = part.toolCallId ?? crypto.randomUUID();
@@ -840,12 +869,12 @@ Do not change the flow requirements. Keep the reply concise.`;
840
869
  };
841
870
  toolCalls.push(callRecord);
842
871
  await this.hookRunner.onToolCall(context, callRecord);
843
- yield {
872
+ yield* this.emit(context, {
844
873
  type: 'tool-call',
845
874
  toolCallId,
846
875
  toolName: part.toolName,
847
876
  args: part.args,
848
- };
877
+ });
849
878
  break;
850
879
  }
851
880
  case 'tool-result': {
@@ -868,44 +897,44 @@ Do not change the flow requirements. Keep the reply concise.`;
868
897
  callRecord.success = false;
869
898
  callRecord.error = new Error(reason);
870
899
  await this.hookRunner.onToolError(context, callRecord, callRecord.error);
871
- yield {
900
+ yield* this.emit(context, {
872
901
  type: 'tool-error',
873
902
  toolCallId,
874
903
  toolName: part.toolName,
875
904
  error: reason,
876
- };
877
- yield { type: 'error', error: reason };
905
+ });
906
+ yield* this.emit(context, { type: 'error', error: reason });
878
907
  return;
879
908
  }
880
909
  }
881
- yield {
910
+ yield* this.emit(context, {
882
911
  type: 'tool-result',
883
912
  toolCallId,
884
913
  toolName: part.toolName,
885
914
  result: part.result,
886
- };
915
+ });
887
916
  break;
888
917
  }
889
918
  case 'handoff':
890
919
  onHandoff(part.targetAgent, part.reason);
891
920
  break;
892
921
  case 'node-enter':
893
- yield { type: 'node-enter', nodeName: part.nodeName };
922
+ yield* this.emit(context, { type: 'node-enter', nodeName: part.nodeName });
894
923
  break;
895
924
  case 'node-exit':
896
- yield { type: 'node-exit', nodeName: part.nodeName };
925
+ yield* this.emit(context, { type: 'node-exit', nodeName: part.nodeName });
897
926
  break;
898
927
  case 'flow-transition':
899
- yield { type: 'flow-transition', from: part.from, to: part.to };
928
+ yield* this.emit(context, { type: 'flow-transition', from: part.from, to: part.to });
900
929
  break;
901
930
  case 'flow-end':
902
- yield { type: 'flow-end', reason: part.reason };
931
+ yield* this.emit(context, { type: 'flow-end', reason: part.reason });
903
932
  break;
904
933
  case 'turn-end':
905
- yield { type: 'turn-end' };
934
+ yield* this.emit(context, { type: 'turn-end' });
906
935
  break;
907
936
  case 'error':
908
- yield { type: 'error', error: part.error };
937
+ yield* this.emit(context, { type: 'error', error: part.error });
909
938
  break;
910
939
  default:
911
940
  break;
@@ -930,6 +959,10 @@ Do not change the flow requirements. Keep the reply concise.`;
930
959
  getAllAgents() {
931
960
  return Array.from(this.agents.values());
932
961
  }
962
+ async *emit(context, part) {
963
+ await this.hookRunner.onStreamPart(context, part);
964
+ yield part;
965
+ }
933
966
  }
934
967
  export function createRuntime(config) {
935
968
  return new Runtime(config);