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