@ariaflowagents/core 0.7.1 → 0.8.1

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 (224) hide show
  1. package/README.md +90 -1
  2. package/dist/agents/Agent.d.ts +188 -9
  3. package/dist/agents/Agent.d.ts.map +1 -1
  4. package/dist/agents/Agent.js +246 -24
  5. package/dist/agents/Agent.js.map +1 -1
  6. package/dist/agents/CompositeAgent.d.ts +4 -3
  7. package/dist/agents/CompositeAgent.d.ts.map +1 -1
  8. package/dist/agents/CompositeAgent.js +19 -9
  9. package/dist/agents/CompositeAgent.js.map +1 -1
  10. package/dist/agents/FlowAgent.d.ts +3 -2
  11. package/dist/agents/FlowAgent.d.ts.map +1 -1
  12. package/dist/agents/FlowAgent.js +16 -6
  13. package/dist/agents/FlowAgent.js.map +1 -1
  14. package/dist/agents/TriageAgent.d.ts +8 -2
  15. package/dist/agents/TriageAgent.d.ts.map +1 -1
  16. package/dist/agents/TriageAgent.js +39 -6
  17. package/dist/agents/TriageAgent.js.map +1 -1
  18. package/dist/agents/index.d.ts +1 -1
  19. package/dist/agents/index.d.ts.map +1 -1
  20. package/dist/agents/index.js +0 -1
  21. package/dist/agents/index.js.map +1 -1
  22. package/dist/flows/FlowManager.d.ts +8 -0
  23. package/dist/flows/FlowManager.d.ts.map +1 -1
  24. package/dist/flows/FlowManager.js +32 -2
  25. package/dist/flows/FlowManager.js.map +1 -1
  26. package/dist/foundation/AgentDefinition.d.ts +18 -0
  27. package/dist/foundation/AgentDefinition.d.ts.map +1 -0
  28. package/dist/foundation/AgentDefinition.js +2 -0
  29. package/dist/foundation/AgentDefinition.js.map +1 -0
  30. package/dist/foundation/AgentStateController.d.ts +26 -0
  31. package/dist/foundation/AgentStateController.d.ts.map +1 -0
  32. package/dist/foundation/AgentStateController.js +2 -0
  33. package/dist/foundation/AgentStateController.js.map +1 -0
  34. package/dist/foundation/ConversationEventLog.d.ts +72 -0
  35. package/dist/foundation/ConversationEventLog.d.ts.map +1 -0
  36. package/dist/foundation/ConversationEventLog.js +2 -0
  37. package/dist/foundation/ConversationEventLog.js.map +1 -0
  38. package/dist/foundation/ConversationState.d.ts +31 -0
  39. package/dist/foundation/ConversationState.d.ts.map +1 -0
  40. package/dist/foundation/ConversationState.js +2 -0
  41. package/dist/foundation/ConversationState.js.map +1 -0
  42. package/dist/foundation/DefaultAgentStateController.d.ts +24 -0
  43. package/dist/foundation/DefaultAgentStateController.d.ts.map +1 -0
  44. package/dist/foundation/DefaultAgentStateController.js +49 -0
  45. package/dist/foundation/DefaultAgentStateController.js.map +1 -0
  46. package/dist/foundation/DefaultConversationEventLog.d.ts +28 -0
  47. package/dist/foundation/DefaultConversationEventLog.d.ts.map +1 -0
  48. package/dist/foundation/DefaultConversationEventLog.js +195 -0
  49. package/dist/foundation/DefaultConversationEventLog.js.map +1 -0
  50. package/dist/foundation/DefaultConversationState.d.ts +34 -0
  51. package/dist/foundation/DefaultConversationState.d.ts.map +1 -0
  52. package/dist/foundation/DefaultConversationState.js +100 -0
  53. package/dist/foundation/DefaultConversationState.js.map +1 -0
  54. package/dist/foundation/DefaultToolExecutor.d.ts +58 -0
  55. package/dist/foundation/DefaultToolExecutor.d.ts.map +1 -0
  56. package/dist/foundation/DefaultToolExecutor.js +128 -0
  57. package/dist/foundation/DefaultToolExecutor.js.map +1 -0
  58. package/dist/foundation/ToolExecutor.d.ts +44 -0
  59. package/dist/foundation/ToolExecutor.d.ts.map +1 -0
  60. package/dist/foundation/ToolExecutor.js +2 -0
  61. package/dist/foundation/ToolExecutor.js.map +1 -0
  62. package/dist/foundation/createFoundation.d.ts +33 -0
  63. package/dist/foundation/createFoundation.d.ts.map +1 -0
  64. package/dist/foundation/createFoundation.js +34 -0
  65. package/dist/foundation/createFoundation.js.map +1 -0
  66. package/dist/foundation/index.d.ts +15 -0
  67. package/dist/foundation/index.d.ts.map +1 -0
  68. package/dist/foundation/index.js +8 -0
  69. package/dist/foundation/index.js.map +1 -0
  70. package/dist/hooks/HookRunner.d.ts +2 -0
  71. package/dist/hooks/HookRunner.d.ts.map +1 -1
  72. package/dist/hooks/HookRunner.js +4 -0
  73. package/dist/hooks/HookRunner.js.map +1 -1
  74. package/dist/index.d.ts +13 -2
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +9 -1
  77. package/dist/index.js.map +1 -1
  78. package/dist/memory/MemoryService.d.ts +40 -0
  79. package/dist/memory/MemoryService.d.ts.map +1 -0
  80. package/dist/memory/MemoryService.js +2 -0
  81. package/dist/memory/MemoryService.js.map +1 -0
  82. package/dist/memory/index.d.ts +5 -0
  83. package/dist/memory/index.d.ts.map +1 -0
  84. package/dist/memory/index.js +3 -0
  85. package/dist/memory/index.js.map +1 -0
  86. package/dist/memory/preloadMemory.d.ts +17 -0
  87. package/dist/memory/preloadMemory.d.ts.map +1 -0
  88. package/dist/memory/preloadMemory.js +62 -0
  89. package/dist/memory/preloadMemory.js.map +1 -0
  90. package/dist/memory/stores/InMemoryMemoryService.d.ts +20 -0
  91. package/dist/memory/stores/InMemoryMemoryService.d.ts.map +1 -0
  92. package/dist/memory/stores/InMemoryMemoryService.js +92 -0
  93. package/dist/memory/stores/InMemoryMemoryService.js.map +1 -0
  94. package/dist/memory/types.d.ts +49 -0
  95. package/dist/memory/types.d.ts.map +1 -0
  96. package/dist/memory/types.js +8 -0
  97. package/dist/memory/types.js.map +1 -0
  98. package/dist/prompts/AgentPrompt.d.ts +110 -0
  99. package/dist/prompts/AgentPrompt.d.ts.map +1 -0
  100. package/dist/prompts/AgentPrompt.js +373 -0
  101. package/dist/prompts/AgentPrompt.js.map +1 -0
  102. package/dist/prompts/PromptAssembly.d.ts +119 -0
  103. package/dist/prompts/PromptAssembly.d.ts.map +1 -0
  104. package/dist/prompts/PromptAssembly.js +150 -0
  105. package/dist/prompts/PromptAssembly.js.map +1 -0
  106. package/dist/prompts/PromptBuilder.d.ts +22 -3
  107. package/dist/prompts/PromptBuilder.d.ts.map +1 -1
  108. package/dist/prompts/PromptBuilder.js +242 -13
  109. package/dist/prompts/PromptBuilder.js.map +1 -1
  110. package/dist/prompts/PromptRenderer.d.ts +43 -0
  111. package/dist/prompts/PromptRenderer.d.ts.map +1 -0
  112. package/dist/prompts/PromptRenderer.js +114 -0
  113. package/dist/prompts/PromptRenderer.js.map +1 -0
  114. package/dist/prompts/brandVoice.d.ts +10 -0
  115. package/dist/prompts/brandVoice.d.ts.map +1 -0
  116. package/dist/prompts/brandVoice.js +87 -0
  117. package/dist/prompts/brandVoice.js.map +1 -0
  118. package/dist/prompts/index.d.ts +11 -4
  119. package/dist/prompts/index.d.ts.map +1 -1
  120. package/dist/prompts/index.js +7 -2
  121. package/dist/prompts/index.js.map +1 -1
  122. package/dist/prompts/security.d.ts +5 -0
  123. package/dist/prompts/security.d.ts.map +1 -0
  124. package/dist/prompts/security.js +52 -0
  125. package/dist/prompts/security.js.map +1 -0
  126. package/dist/prompts/types.d.ts +65 -1
  127. package/dist/prompts/types.d.ts.map +1 -1
  128. package/dist/prompts/types.js +26 -0
  129. package/dist/prompts/types.js.map +1 -1
  130. package/dist/runtime/ContextBudget.d.ts +57 -0
  131. package/dist/runtime/ContextBudget.d.ts.map +1 -0
  132. package/dist/runtime/ContextBudget.js +103 -0
  133. package/dist/runtime/ContextBudget.js.map +1 -0
  134. package/dist/runtime/ContextManager.d.ts +8 -5
  135. package/dist/runtime/ContextManager.d.ts.map +1 -1
  136. package/dist/runtime/ContextManager.js +47 -14
  137. package/dist/runtime/ContextManager.js.map +1 -1
  138. package/dist/runtime/FlowExecutor.d.ts +16 -11
  139. package/dist/runtime/FlowExecutor.d.ts.map +1 -1
  140. package/dist/runtime/FlowExecutor.js +32 -138
  141. package/dist/runtime/FlowExecutor.js.map +1 -1
  142. package/dist/runtime/Runtime.d.ts +31 -78
  143. package/dist/runtime/Runtime.d.ts.map +1 -1
  144. package/dist/runtime/Runtime.js +225 -1406
  145. package/dist/runtime/Runtime.js.map +1 -1
  146. package/dist/runtime/SessionCache.d.ts +16 -0
  147. package/dist/runtime/SessionCache.d.ts.map +1 -0
  148. package/dist/runtime/SessionCache.js +49 -0
  149. package/dist/runtime/SessionCache.js.map +1 -0
  150. package/dist/runtime/SessionMutex.d.ts +37 -0
  151. package/dist/runtime/SessionMutex.d.ts.map +1 -0
  152. package/dist/runtime/SessionMutex.js +59 -0
  153. package/dist/runtime/SessionMutex.js.map +1 -0
  154. package/dist/runtime/StreamEmitter.d.ts +34 -0
  155. package/dist/runtime/StreamEmitter.d.ts.map +1 -0
  156. package/dist/runtime/StreamEmitter.js +91 -0
  157. package/dist/runtime/StreamEmitter.js.map +1 -0
  158. package/dist/runtime/handoffFilters.d.ts +60 -0
  159. package/dist/runtime/handoffFilters.d.ts.map +1 -0
  160. package/dist/runtime/handoffFilters.js +95 -0
  161. package/dist/runtime/handoffFilters.js.map +1 -0
  162. package/dist/runtime/pipeline/AgentExecuteStage.d.ts +22 -0
  163. package/dist/runtime/pipeline/AgentExecuteStage.d.ts.map +1 -0
  164. package/dist/runtime/pipeline/AgentExecuteStage.js +889 -0
  165. package/dist/runtime/pipeline/AgentExecuteStage.js.map +1 -0
  166. package/dist/runtime/pipeline/ContextAssembleStage.d.ts +26 -0
  167. package/dist/runtime/pipeline/ContextAssembleStage.d.ts.map +1 -0
  168. package/dist/runtime/pipeline/ContextAssembleStage.js +253 -0
  169. package/dist/runtime/pipeline/ContextAssembleStage.js.map +1 -0
  170. package/dist/runtime/pipeline/ContextGatherStage.d.ts +21 -0
  171. package/dist/runtime/pipeline/ContextGatherStage.d.ts.map +1 -0
  172. package/dist/runtime/pipeline/ContextGatherStage.js +161 -0
  173. package/dist/runtime/pipeline/ContextGatherStage.js.map +1 -0
  174. package/dist/runtime/pipeline/IntakeStage.d.ts +25 -0
  175. package/dist/runtime/pipeline/IntakeStage.d.ts.map +1 -0
  176. package/dist/runtime/pipeline/IntakeStage.js +126 -0
  177. package/dist/runtime/pipeline/IntakeStage.js.map +1 -0
  178. package/dist/runtime/pipeline/PostStreamStage.d.ts +26 -0
  179. package/dist/runtime/pipeline/PostStreamStage.d.ts.map +1 -0
  180. package/dist/runtime/pipeline/PostStreamStage.js +129 -0
  181. package/dist/runtime/pipeline/PostStreamStage.js.map +1 -0
  182. package/dist/runtime/pipeline/TurnPipeline.d.ts +54 -0
  183. package/dist/runtime/pipeline/TurnPipeline.d.ts.map +1 -0
  184. package/dist/runtime/pipeline/TurnPipeline.js +15 -0
  185. package/dist/runtime/pipeline/TurnPipeline.js.map +1 -0
  186. package/dist/runtime/pipeline/TurnServices.d.ts +48 -0
  187. package/dist/runtime/pipeline/TurnServices.d.ts.map +1 -0
  188. package/dist/runtime/pipeline/TurnServices.js +2 -0
  189. package/dist/runtime/pipeline/TurnServices.js.map +1 -0
  190. package/dist/runtime/pipeline/agentTypeGuards.d.ts +4 -0
  191. package/dist/runtime/pipeline/agentTypeGuards.d.ts.map +1 -0
  192. package/dist/runtime/pipeline/agentTypeGuards.js +7 -0
  193. package/dist/runtime/pipeline/agentTypeGuards.js.map +1 -0
  194. package/dist/runtime/pipeline/index.d.ts +11 -0
  195. package/dist/runtime/pipeline/index.d.ts.map +1 -0
  196. package/dist/runtime/pipeline/index.js +13 -0
  197. package/dist/runtime/pipeline/index.js.map +1 -0
  198. package/dist/runtime/pipeline/outputProcessing.d.ts +23 -0
  199. package/dist/runtime/pipeline/outputProcessing.d.ts.map +1 -0
  200. package/dist/runtime/pipeline/outputProcessing.js +63 -0
  201. package/dist/runtime/pipeline/outputProcessing.js.map +1 -0
  202. package/dist/runtime/pipeline/sessionUtils.d.ts +12 -0
  203. package/dist/runtime/pipeline/sessionUtils.d.ts.map +1 -0
  204. package/dist/runtime/pipeline/sessionUtils.js +73 -0
  205. package/dist/runtime/pipeline/sessionUtils.js.map +1 -0
  206. package/dist/tools/Tool.d.ts +7 -0
  207. package/dist/tools/Tool.d.ts.map +1 -1
  208. package/dist/tools/Tool.js +12 -3
  209. package/dist/tools/Tool.js.map +1 -1
  210. package/dist/tools/memory.d.ts +26 -0
  211. package/dist/tools/memory.d.ts.map +1 -0
  212. package/dist/tools/memory.js +51 -0
  213. package/dist/tools/memory.js.map +1 -0
  214. package/dist/types/index.d.ts +177 -6
  215. package/dist/types/index.d.ts.map +1 -1
  216. package/dist/types/index.js.map +1 -1
  217. package/guides/AGENTS.md +173 -0
  218. package/guides/README.md +12 -0
  219. package/guides/TOOLS.md +93 -27
  220. package/package.json +12 -4
  221. package/dist/agents/LLMAgent.d.ts +0 -11
  222. package/dist/agents/LLMAgent.d.ts.map +0 -1
  223. package/dist/agents/LLMAgent.js +0 -31
  224. package/dist/agents/LLMAgent.js.map +0 -1
@@ -0,0 +1,889 @@
1
+ /**
2
+ * AgentExecuteStage — Stage 4 of the turn pipeline.
3
+ *
4
+ * Absorbs the full runLoop from Runtime: handoff loop, flow agent execution,
5
+ * LLM agent execution (structured triage, streamText, tool loop),
6
+ * tool enforcement, output processing, result handling, and handoff delegation.
7
+ */
8
+ import crypto from 'node:crypto';
9
+ import { streamText, streamObject, stepCountIs } from 'ai';
10
+ import { z } from 'zod';
11
+ import { runContextGatherStage, emitGatherEvents } from './ContextGatherStage.js';
12
+ import { runContextAssembleStage, buildStructuredTriagePrompt } from './ContextAssembleStage.js';
13
+ import { isFlowAgent, isTriageAgent } from './agentTypeGuards.js';
14
+ import { getFlowState, setFlowState, clearFlowState, updateFlowStateSnapshot, appendSessionMessage, appendSessionMessages, } from './sessionUtils.js';
15
+ import { runOutputProcessing, postProcessPersistedAssistantMessages, getAgentOutputProcessors, } from './outputProcessing.js';
16
+ import { checkStopConditions } from '../../guards/StopConditions.js';
17
+ import { createHandoffTool, isHandoffResult } from '../../tools/handoff.js';
18
+ import { isFinalResult } from '../../tools/final.js';
19
+ import { runInputProcessors } from '../../processors/ProcessorRunner.js';
20
+ import { getChunkArgs, getChunkErrorMessage, getChunkResult, getChunkToolCallId } from '../../utils/streamChunk.js';
21
+ import { isRecord } from '../../utils/isRecord.js';
22
+ import { estimateTokenCount } from '../ContextBudget.js';
23
+ /**
24
+ * Runs the full agent execution loop (Stage 4).
25
+ * Handles handoffs, flow agents, LLM agents, structured triage, tool loops.
26
+ */
27
+ export async function* runAgentExecuteStage(execInput, services) {
28
+ const { context, injectionQueue, abortController } = execInput;
29
+ let currentInput = execInput.input;
30
+ const abortSignal = abortController?.signal;
31
+ let interruptionEmitted = false;
32
+ let circularRecoveryActive = false;
33
+ let circularRecoveryAttempts = 0;
34
+ while (context.handoffStack.length < services.maxHandoffs) {
35
+ if (abortSignal?.aborted) {
36
+ if (!interruptionEmitted) {
37
+ interruptionEmitted = true;
38
+ yield* services.streamEmitter.emit(context, {
39
+ type: 'interrupted',
40
+ sessionId: context.session.id,
41
+ reason: abortSignal.reason ?? 'Operation cancelled',
42
+ timestamp: new Date(),
43
+ lastAgentId: context.agentId,
44
+ lastStep: context.stepCount,
45
+ });
46
+ }
47
+ return;
48
+ }
49
+ // Circular handoff detection
50
+ if (services.agentStateController.isCircularHandoff(context.handoffStack, context.agentId) && !circularRecoveryActive) {
51
+ const err = `Circular handoff detected: ${context.handoffStack.join(' -> ')} -> ${context.agentId}`;
52
+ yield* services.streamEmitter.emit(context, { type: 'error', error: err });
53
+ circularRecoveryAttempts += 1;
54
+ if (circularRecoveryAttempts > 1) {
55
+ yield* emitCircularHandoffFallback(context, services);
56
+ return;
57
+ }
58
+ circularRecoveryActive = true;
59
+ context.handoffStack = [];
60
+ }
61
+ context.handoffStack.push(context.agentId);
62
+ const agent = services.agents.get(context.agentId);
63
+ if (!agent) {
64
+ yield* services.streamEmitter.emit(context, { type: 'error', error: `Agent "${context.agentId}" not found` });
65
+ return;
66
+ }
67
+ // Agent-specific input processors
68
+ if (agent.inputProcessors && agent.inputProcessors.length > 0) {
69
+ const procCtx = {
70
+ session: context.session,
71
+ agentId: agent.id,
72
+ toolCallHistory: context.toolCallHistory,
73
+ abortSignal,
74
+ };
75
+ const outcome = await runInputProcessors({
76
+ processors: agent.inputProcessors,
77
+ input: currentInput,
78
+ messages: context.session.messages,
79
+ context: procCtx,
80
+ });
81
+ if (outcome.blocked) {
82
+ yield* services.streamEmitter.emit(context, {
83
+ type: 'tripwire',
84
+ phase: 'input',
85
+ processorId: outcome.processorId,
86
+ reason: outcome.reason,
87
+ message: outcome.message,
88
+ });
89
+ yield* services.streamEmitter.emit(context, { type: 'text-delta', text: outcome.message });
90
+ yield* services.streamEmitter.emit(context, { type: 'turn-end' });
91
+ return;
92
+ }
93
+ if (outcome.input !== currentInput) {
94
+ currentInput = outcome.input;
95
+ const last = context.session.messages[context.session.messages.length - 1];
96
+ if (last && last.role === 'user' && typeof last.content === 'string') {
97
+ last.content = currentInput;
98
+ services.conversationState.touchSession(context.session);
99
+ }
100
+ }
101
+ }
102
+ yield* services.streamEmitter.emit(context, { type: 'agent-start', agentId: agent.id });
103
+ await services.hookRunner.onAgentStart(context, agent.id);
104
+ // Stage 2: Context Gather
105
+ const gatherResult = await runContextGatherStage(agent, context, currentInput, services, abortSignal);
106
+ yield* emitGatherEvents(gatherResult.autoRetrieveEvents, context, services);
107
+ const { extractionSnapshot, activeRoutes } = gatherResult;
108
+ // Stage 3: Context Assemble
109
+ const turnToolErrors = [];
110
+ const assembleGen = runContextAssembleStage({ agent, context, injectionQueue, gatherResult, currentInput, circularRecoveryActive }, services);
111
+ let assembleNext = await assembleGen.next();
112
+ while (!assembleNext.done) {
113
+ yield assembleNext.value;
114
+ assembleNext = await assembleGen.next();
115
+ }
116
+ let system = assembleNext.value.systemPrompt;
117
+ const handoffCandidates = circularRecoveryActive ? [] : getHandoffCandidates(agent, services, activeRoutes);
118
+ const handoffTool = handoffCandidates.length > 0
119
+ ? createHandoffTool(handoffCandidates, context.agentId)
120
+ : null;
121
+ let handoffTo = null;
122
+ let handoffReason = 'No reason provided';
123
+ if (isFlowAgent(agent)) {
124
+ yield* runFlowAgentStep(agent, context, currentInput, system, handoffTool, abortSignal, services, turnToolErrors, interruptionEmitted, (to, reason) => { handoffTo = to; handoffReason = reason ?? 'No reason provided'; }, (v) => { interruptionEmitted = v; });
125
+ }
126
+ else {
127
+ const result = yield* runLLMAgentSteps(agent, context, currentInput, system, handoffTool, abortSignal, services, turnToolErrors, interruptionEmitted, activeRoutes, (to, reason) => { handoffTo = to; handoffReason = reason ?? 'No reason provided'; }, (v) => { interruptionEmitted = v; });
128
+ if (result === 'return')
129
+ return;
130
+ }
131
+ yield* services.streamEmitter.emit(context, { type: 'agent-end', agentId: agent.id });
132
+ await services.hookRunner.onAgentEnd(context, agent.id);
133
+ if (circularRecoveryActive && handoffTo) {
134
+ yield* services.streamEmitter.emit(context, {
135
+ type: 'error',
136
+ error: `Circular handoff recovery failed: attempted handoff ${context.agentId} -> ${handoffTo}`,
137
+ });
138
+ yield* emitCircularHandoffFallback(context, services);
139
+ return;
140
+ }
141
+ if (circularRecoveryActive) {
142
+ circularRecoveryActive = false;
143
+ }
144
+ if (!handoffTo)
145
+ break;
146
+ await services.hookRunner.onHandoff(context, context.agentId, handoffTo, handoffReason);
147
+ yield* services.streamEmitter.emit(context, {
148
+ type: 'handoff',
149
+ from: context.agentId,
150
+ to: handoffTo,
151
+ reason: handoffReason,
152
+ });
153
+ services.agentStateController.recordHandoff({
154
+ session: context.session,
155
+ fromAgentId: context.agentId,
156
+ toAgentId: handoffTo,
157
+ reason: handoffReason,
158
+ });
159
+ // Apply handoff input filter if configured
160
+ const handoffAgent = services.agents.get(context.agentId);
161
+ const agentRoutes = handoffAgent?.routes;
162
+ const matchedRoute = agentRoutes?.find((r) => r.agentId === handoffTo);
163
+ if (matchedRoute?.inputFilter) {
164
+ try {
165
+ const filterInput = {
166
+ messages: [...context.session.messages],
167
+ workingMemory: { ...context.session.workingMemory },
168
+ sourceAgentId: context.agentId,
169
+ targetAgentId: handoffTo,
170
+ reason: handoffReason,
171
+ };
172
+ const filterResult = await matchedRoute.inputFilter(filterInput);
173
+ context.session.messages = filterResult.messages;
174
+ const internalState = {};
175
+ const internalKeys = [
176
+ 'runtimeEventLog',
177
+ '__ariaSessionTurn',
178
+ '__ariaRedactCarry',
179
+ 'flowStateByAgent',
180
+ '__ariaAssistantText',
181
+ '__ariaContextBudget',
182
+ ];
183
+ for (const key of internalKeys) {
184
+ if (key in context.session.workingMemory) {
185
+ internalState[key] = context.session.workingMemory[key];
186
+ }
187
+ }
188
+ context.session.workingMemory = { ...filterResult.workingMemory, ...internalState };
189
+ }
190
+ catch (err) {
191
+ console.warn('[AriaFlow] Handoff input filter failed, using full context:', err);
192
+ }
193
+ }
194
+ services.agentStateController.setActiveAgent(context.session, handoffTo);
195
+ await services.eventLog.checkpoint(context.session);
196
+ if (services.sessionCache) {
197
+ services.sessionCache.put(context.session);
198
+ }
199
+ context.agentId = handoffTo;
200
+ }
201
+ if (context.handoffStack.length >= services.maxHandoffs) {
202
+ yield* services.streamEmitter.emit(context, { type: 'error', error: `Maximum handoffs (${services.maxHandoffs}) exceeded` });
203
+ }
204
+ }
205
+ // --- Flow agent step ---
206
+ async function* runFlowAgentStep(agent, context, currentInput, system, handoffTool, abortSignal, services, turnToolErrors, interruptionEmitted, onHandoff, setInterruptionEmitted) {
207
+ const stopResult = checkStopConditions(context, services.stopConditions);
208
+ if (stopResult.shouldStop) {
209
+ yield* services.streamEmitter.emit(context, { type: 'error', error: `Stopped: ${stopResult.reason}` });
210
+ return;
211
+ }
212
+ const agentMaxSteps = agent.maxSteps ?? agent.maxTurns ?? services.maxSteps;
213
+ if (agentMaxSteps <= 0) {
214
+ yield* services.streamEmitter.emit(context, { type: 'error', error: `Max steps exceeded for agent "${agent.id}"` });
215
+ return;
216
+ }
217
+ context.stepCount += 1;
218
+ yield* services.streamEmitter.emit(context, { type: 'step-start', step: context.stepCount, agentId: agent.id });
219
+ await services.hookRunner.onStepStart(context, context.stepCount);
220
+ const toolCalls = [];
221
+ let handoffTo = null;
222
+ let handoffReason = 'No reason provided';
223
+ try {
224
+ const helpers = buildFlowExecutorHelpers(services);
225
+ for await (const part of services.flowExecutor.runFlowAgent(agent, context, currentInput, system, handoffTool ?? undefined, (target, reason) => {
226
+ handoffTo = target;
227
+ handoffReason = reason ?? 'No reason provided';
228
+ onHandoff(target, reason);
229
+ }, toolCalls, helpers, abortSignal)) {
230
+ yield part;
231
+ }
232
+ context.consecutiveErrors = 0;
233
+ if (context.session.metadata) {
234
+ context.session.metadata.totalSteps += 1;
235
+ }
236
+ yield* services.streamEmitter.emit(context, { type: 'step-end', step: context.stepCount, agentId: agent.id });
237
+ await services.hookRunner.onStepEnd(context, context.stepCount, {
238
+ toolCalls,
239
+ finishReason: 'flow',
240
+ tokensUsed: 0,
241
+ handoffTo: handoffTo ?? undefined,
242
+ });
243
+ }
244
+ catch (error) {
245
+ if (abortSignal?.aborted) {
246
+ if (!interruptionEmitted) {
247
+ setInterruptionEmitted(true);
248
+ yield* services.streamEmitter.emit(context, {
249
+ type: 'interrupted',
250
+ sessionId: context.session.id,
251
+ reason: abortSignal.reason ?? 'Operation cancelled',
252
+ timestamp: new Date(),
253
+ lastAgentId: context.agentId,
254
+ lastStep: context.stepCount,
255
+ });
256
+ }
257
+ return;
258
+ }
259
+ context.consecutiveErrors += 1;
260
+ await services.hookRunner.onError(context, error);
261
+ yield* services.streamEmitter.emit(context, { type: 'error', error: error.message });
262
+ if (context.consecutiveErrors >= 3) {
263
+ return;
264
+ }
265
+ }
266
+ }
267
+ // --- LLM agent step loop ---
268
+ async function* runLLMAgentSteps(agent, context, currentInput, system, handoffTool, abortSignal, services, turnToolErrors, interruptionEmitted, activeRoutes, onHandoff, setInterruptionEmitted) {
269
+ const tools = wrapToolsWithEnforcement(context, handoffTool ? { ...agent.tools, handoff: handoffTool } : { ...agent.tools }, services);
270
+ let agentSteps = 0;
271
+ const agentMaxSteps = agent.maxSteps ?? agent.maxTurns ?? services.maxSteps;
272
+ let handoffTo = null;
273
+ let handoffReason = 'No reason provided';
274
+ while (agentSteps < agentMaxSteps) {
275
+ const stopResult = checkStopConditions(context, services.stopConditions);
276
+ if (stopResult.shouldStop) {
277
+ yield* services.streamEmitter.emit(context, { type: 'error', error: `Stopped: ${stopResult.reason}` });
278
+ return 'return';
279
+ }
280
+ agentSteps += 1;
281
+ context.stepCount += 1;
282
+ yield* services.streamEmitter.emit(context, { type: 'step-start', step: context.stepCount, agentId: agent.id });
283
+ await services.hookRunner.onStepStart(context, context.stepCount);
284
+ try {
285
+ const model = agent.model ?? services.defaultModel;
286
+ if (!model) {
287
+ throw new Error(`Agent "${agent.id}" is missing a model`);
288
+ }
289
+ // Structured triage
290
+ if (isTriageAgent(agent) && agent.triageMode === 'structured') {
291
+ const triageResult = yield* runStructuredTriage(agent, context, system, abortSignal, services, activeRoutes);
292
+ handoffTo = triageResult.handoffTo;
293
+ handoffReason = triageResult.handoffReason;
294
+ onHandoff(handoffTo, handoffReason);
295
+ yield* services.streamEmitter.emit(context, { type: 'step-end', step: context.stepCount, agentId: agent.id });
296
+ await services.hookRunner.onStepEnd(context, context.stepCount, {
297
+ toolCalls: [],
298
+ finishReason: 'handoff',
299
+ tokensUsed: 0,
300
+ handoffTo: handoffTo ?? undefined,
301
+ });
302
+ break;
303
+ }
304
+ // onBeforeModelCall hook
305
+ if (services.config.hooks?.onBeforeModelCall) {
306
+ system = await runBeforeModelCallHook(agent, context, system, services);
307
+ }
308
+ const result = streamText({
309
+ model: model,
310
+ system,
311
+ messages: context.session.messages,
312
+ tools,
313
+ abortSignal,
314
+ stopWhen: stepCountIs(agent.toolMaxSteps ?? 5),
315
+ experimental_context: {
316
+ session: context.session,
317
+ agentId: agent.id,
318
+ },
319
+ experimental_telemetry: agent.telemetry ?? services.config.telemetry,
320
+ });
321
+ const toolCalls = [];
322
+ let finalResult = null;
323
+ let finalEmitted = false;
324
+ const outputProcessors = getAgentOutputProcessors(agent, services.outputProcessors);
325
+ const bufferOutput = services.outputProcessorMode === 'buffer' && outputProcessors.length > 0;
326
+ const bufferedParts = [];
327
+ let stoppedByGuard = false;
328
+ // Process stream chunks
329
+ for await (const chunk of result.fullStream) {
330
+ const chunkResult = yield* processStreamChunk(chunk, context, agent, services, toolCalls, turnToolErrors, finalResult, finalEmitted, bufferOutput, bufferedParts);
331
+ if (chunkResult.finalResult)
332
+ finalResult = chunkResult.finalResult;
333
+ if (chunkResult.finalEmitted)
334
+ finalEmitted = chunkResult.finalEmitted;
335
+ if (chunkResult.handoffTo) {
336
+ handoffTo = chunkResult.handoffTo;
337
+ handoffReason = chunkResult.handoffReason ?? 'No reason provided';
338
+ onHandoff(handoffTo, handoffReason);
339
+ }
340
+ if (chunkResult.stoppedByGuard) {
341
+ stoppedByGuard = true;
342
+ break;
343
+ }
344
+ }
345
+ if (stoppedByGuard)
346
+ return 'return';
347
+ const response = await result.response;
348
+ const finishReason = finalResult ? 'final' : await result.finishReason;
349
+ // Finalize buffered output
350
+ if (finalResult || (bufferOutput && finishReason !== 'tool-calls') || turnToolErrors.length > 0) {
351
+ let rawText = finalResult ? finalResult.text : bufferedParts.join('');
352
+ if (turnToolErrors.length > 0) {
353
+ const failedTools = turnToolErrors.map(e => `\`${e.toolName}\``).join(', ');
354
+ rawText = `I encountered an error while using the following tools: ${failedTools}. I cannot proceed with the requested action at this time.`;
355
+ yield* services.streamEmitter.emit(context, {
356
+ type: 'error',
357
+ error: `Turn blocked by critical tool failures: ${failedTools}`,
358
+ });
359
+ }
360
+ const getAbortSignal = () => abortSignal;
361
+ const processed = await runOutputProcessing(agent, context, rawText, services.outputProcessors, services.outputRedactions, getAbortSignal);
362
+ if (processed.tripwire) {
363
+ yield* services.streamEmitter.emit(context, {
364
+ type: 'tripwire',
365
+ phase: 'output',
366
+ processorId: processed.tripwire.processorId,
367
+ reason: processed.tripwire.reason,
368
+ message: processed.tripwire.message,
369
+ });
370
+ }
371
+ if (bufferOutput || turnToolErrors.length > 0) {
372
+ yield* services.streamEmitter.emit(context, { type: 'text-delta', text: processed.text });
373
+ }
374
+ appendSessionMessage(context.session, { role: 'assistant', content: processed.text }, services.conversationState);
375
+ }
376
+ else {
377
+ const beforeLen = context.session.messages.length;
378
+ appendSessionMessages(context.session, response.messages, services.conversationState);
379
+ const getAbortSignal = () => abortSignal;
380
+ const tripwires = await postProcessPersistedAssistantMessages(agent, context, beforeLen, services.outputProcessors, services.outputRedactions, getAbortSignal);
381
+ for (const t of tripwires) {
382
+ yield* services.streamEmitter.emit(context, {
383
+ type: 'tripwire',
384
+ phase: 'output',
385
+ processorId: t.processorId,
386
+ reason: t.reason,
387
+ message: t.message,
388
+ });
389
+ }
390
+ }
391
+ const usage = await result.usage;
392
+ const totalTokens = usage.totalTokens ?? 0;
393
+ context.totalTokens += totalTokens;
394
+ if (context.session.metadata) {
395
+ context.session.metadata.totalTokens += totalTokens;
396
+ context.session.metadata.totalSteps += 1;
397
+ }
398
+ context.consecutiveErrors = 0;
399
+ yield* services.streamEmitter.emit(context, { type: 'step-end', step: context.stepCount, agentId: agent.id });
400
+ await services.hookRunner.onStepEnd(context, context.stepCount, {
401
+ toolCalls,
402
+ finishReason,
403
+ tokensUsed: totalTokens,
404
+ handoffTo: handoffTo ?? undefined,
405
+ });
406
+ if (finalResult || finishReason !== 'tool-calls' || handoffTo) {
407
+ break;
408
+ }
409
+ }
410
+ catch (error) {
411
+ console.error('RUNTIME_LOOP_ERROR:', error);
412
+ if (abortSignal?.aborted) {
413
+ if (!interruptionEmitted) {
414
+ setInterruptionEmitted(true);
415
+ yield* services.streamEmitter.emit(context, {
416
+ type: 'interrupted',
417
+ sessionId: context.session.id,
418
+ reason: abortSignal.reason ?? 'Operation cancelled',
419
+ timestamp: new Date(),
420
+ lastAgentId: context.agentId,
421
+ lastStep: context.stepCount,
422
+ });
423
+ }
424
+ return 'return';
425
+ }
426
+ context.consecutiveErrors += 1;
427
+ await services.hookRunner.onError(context, error);
428
+ yield* services.streamEmitter.emit(context, { type: 'error', error: error.message });
429
+ if (context.consecutiveErrors >= 3) {
430
+ return 'return';
431
+ }
432
+ }
433
+ }
434
+ return 'break';
435
+ }
436
+ async function* processStreamChunk(chunk, context, agent, services, toolCalls, turnToolErrors, finalResult, finalEmitted, bufferOutput, bufferedParts) {
437
+ const result = {};
438
+ if (chunk.type === 'text-delta') {
439
+ if (finalResult)
440
+ return result;
441
+ if (turnToolErrors.length > 0)
442
+ return result;
443
+ if (bufferOutput) {
444
+ bufferedParts.push(chunk.text);
445
+ }
446
+ else {
447
+ yield* services.streamEmitter.emit(context, { type: 'text-delta', text: chunk.text });
448
+ }
449
+ }
450
+ if (chunk.type === 'tool-call') {
451
+ const args = getChunkArgs(chunk);
452
+ const idempotencyKey = services.toolExecutor.buildIdempotencyKey({
453
+ sessionId: context.session.id,
454
+ agentId: context.agentId,
455
+ step: context.stepCount,
456
+ toolName: chunk.toolName,
457
+ toolCallId: chunk.toolCallId,
458
+ });
459
+ const callRecord = {
460
+ toolCallId: chunk.toolCallId,
461
+ toolName: chunk.toolName,
462
+ args,
463
+ idempotencyKey,
464
+ success: true,
465
+ timestamp: Date.now(),
466
+ };
467
+ toolCalls.push(callRecord);
468
+ await services.hookRunner.onToolCall(context, callRecord);
469
+ const toolConfig = services.agents.get(context.agentId)?.tools;
470
+ const toolDef = toolConfig?.[chunk.toolName];
471
+ const filler = toolDef?.filler ?? `Let me check ${chunk.toolName}...`;
472
+ yield* services.streamEmitter.emit(context, {
473
+ type: 'tool-start',
474
+ toolCallId: chunk.toolCallId,
475
+ toolName: chunk.toolName,
476
+ message: filler,
477
+ });
478
+ yield* services.streamEmitter.emit(context, {
479
+ type: 'tool-call',
480
+ toolCallId: chunk.toolCallId,
481
+ toolName: chunk.toolName,
482
+ args,
483
+ });
484
+ }
485
+ if (chunk.type === 'tool-error') {
486
+ const errText = getChunkErrorMessage(chunk);
487
+ const args = getChunkArgs(chunk);
488
+ const toolCallId = getChunkToolCallId(chunk);
489
+ const callRecord = toolCalls.find(call => call.toolCallId === toolCallId);
490
+ if (callRecord) {
491
+ callRecord.success = false;
492
+ callRecord.error = new Error(errText);
493
+ if (args !== undefined)
494
+ callRecord.args = args;
495
+ context.toolCallHistory.push(callRecord);
496
+ await services.hookRunner.onToolError(context, callRecord, callRecord.error);
497
+ const toolPolicy = agent.toolPolicies?.[callRecord.toolName];
498
+ const toolDef = agent.tools?.[callRecord.toolName];
499
+ const isCritical = toolPolicy?.critical ?? toolDef?.critical ?? true;
500
+ if (isCritical) {
501
+ turnToolErrors.push(callRecord);
502
+ }
503
+ }
504
+ yield* services.streamEmitter.emit(context, {
505
+ type: 'tool-error',
506
+ toolCallId: toolCallId ?? chunk.toolCallId,
507
+ toolName: chunk.toolName,
508
+ error: errText,
509
+ });
510
+ }
511
+ if (chunk.type === 'tool-result') {
512
+ const startTime = toolCalls.find(call => call.toolCallId === chunk.toolCallId)?.timestamp ?? Date.now();
513
+ const toolResult = getChunkResult(chunk);
514
+ const callRecord = toolCalls.find(call => call.toolCallId === chunk.toolCallId);
515
+ if (callRecord) {
516
+ callRecord.result = toolResult;
517
+ callRecord.durationMs = Date.now() - callRecord.timestamp;
518
+ context.toolCallHistory.push(callRecord);
519
+ await services.hookRunner.onToolResult(context, callRecord);
520
+ }
521
+ const durationMs = Date.now() - startTime;
522
+ yield* services.streamEmitter.emit(context, {
523
+ type: 'tool-done',
524
+ toolCallId: chunk.toolCallId,
525
+ toolName: chunk.toolName,
526
+ durationMs,
527
+ });
528
+ if (callRecord) {
529
+ const enforcement = await services.enforcer.checkResult(callRecord, {
530
+ previousCalls: context.toolCallHistory,
531
+ currentStep: context.stepCount,
532
+ sessionState: context.session.state ?? {},
533
+ });
534
+ if (!enforcement.allowed) {
535
+ const reason = enforcement.reason ?? 'Tool result blocked by enforcement';
536
+ callRecord.success = false;
537
+ callRecord.error = new Error(reason);
538
+ await services.hookRunner.onToolError(context, callRecord, callRecord.error);
539
+ turnToolErrors.push(callRecord);
540
+ yield* services.streamEmitter.emit(context, {
541
+ type: 'tool-error',
542
+ toolCallId: chunk.toolCallId,
543
+ toolName: chunk.toolName,
544
+ error: reason,
545
+ });
546
+ result.finalResult = { type: 'final', text: reason };
547
+ if (!finalEmitted) {
548
+ result.finalEmitted = true;
549
+ if (bufferOutput) {
550
+ bufferedParts.push(reason);
551
+ }
552
+ else {
553
+ yield* services.streamEmitter.emit(context, { type: 'text-delta', text: reason });
554
+ }
555
+ }
556
+ return result;
557
+ }
558
+ }
559
+ yield* services.streamEmitter.emit(context, {
560
+ type: 'tool-result',
561
+ toolCallId: chunk.toolCallId,
562
+ toolName: chunk.toolName,
563
+ result: toolResult,
564
+ });
565
+ const stopCheck = checkStopConditions(context, services.stopConditions);
566
+ if (stopCheck.shouldStop) {
567
+ yield* services.streamEmitter.emit(context, { type: 'error', error: `Stopped: ${stopCheck.reason}` });
568
+ result.stoppedByGuard = true;
569
+ return result;
570
+ }
571
+ if (isFinalResult(toolResult)) {
572
+ result.finalResult = toolResult;
573
+ if (!finalEmitted) {
574
+ result.finalEmitted = true;
575
+ if (bufferOutput) {
576
+ bufferedParts.push(toolResult.text);
577
+ }
578
+ else {
579
+ yield* services.streamEmitter.emit(context, { type: 'text-delta', text: toolResult.text });
580
+ }
581
+ }
582
+ return result;
583
+ }
584
+ if (isHandoffResult(toolResult)) {
585
+ const targetAgent = toolResult.targetAgent ?? toolResult.targetAgentId;
586
+ result.handoffTo = targetAgent;
587
+ result.handoffReason = toolResult.reason ?? 'No reason provided';
588
+ }
589
+ }
590
+ return result;
591
+ }
592
+ // --- Structured triage ---
593
+ async function* runStructuredTriage(agent, context, system, abortSignal, services, activeRoutes) {
594
+ const allowed = new Set((activeRoutes ?? agent.routes).map(route => route.agentId));
595
+ // Single-candidate short-circuit
596
+ if (allowed.size === 1) {
597
+ const [onlyAgent] = allowed;
598
+ return { handoffTo: onlyAgent, handoffReason: 'Single active route — direct handoff' };
599
+ }
600
+ const model = agent.model ?? services.defaultModel;
601
+ const schema = z.object({
602
+ agentId: z.string().describe('Target agent ID for this request.'),
603
+ reason: z.string().describe('Short, concrete reason grounded in the user request.'),
604
+ confidence: z.number().min(0).max(1).describe('Routing confidence from 0 to 1.'),
605
+ stayWithCurrent: z.boolean().describe('True only if the current agent is best fit.'),
606
+ });
607
+ const result = streamObject({
608
+ model: model,
609
+ schema,
610
+ system: buildStructuredTriagePrompt(agent, activeRoutes),
611
+ messages: context.session.messages,
612
+ abortSignal,
613
+ experimental_telemetry: agent.telemetry ?? services.config.telemetry,
614
+ });
615
+ let handoffTo;
616
+ let handoffReason = 'Routed by triage';
617
+ let resolved = false;
618
+ for await (const partial of result.partialObjectStream) {
619
+ if (partial.agentId && allowed.has(partial.agentId)) {
620
+ handoffTo = partial.agentId;
621
+ handoffReason = partial.reason ?? 'Routed by triage';
622
+ resolved = true;
623
+ break;
624
+ }
625
+ }
626
+ if (!resolved) {
627
+ const fullObject = await result.object;
628
+ let target = fullObject.agentId;
629
+ if (!allowed.has(target)) {
630
+ const fallback = agent.defaultAgent ?? agent.routes[0]?.agentId;
631
+ if (fallback && fallback !== agent.id) {
632
+ target = fallback;
633
+ }
634
+ else {
635
+ // No valid target -- respond directly from triage
636
+ handoffTo = agent.id;
637
+ handoffReason = 'No valid routing target. Responding directly.';
638
+ return { handoffTo: handoffTo, handoffReason };
639
+ }
640
+ }
641
+ handoffTo = target;
642
+ handoffReason = fullObject.reason ?? 'Routed by triage';
643
+ }
644
+ return { handoffTo: handoffTo, handoffReason };
645
+ }
646
+ // --- Tool enforcement wrapper ---
647
+ function wrapToolsWithEnforcement(context, tools, services) {
648
+ const wrapped = {};
649
+ for (const [toolName, toolDef] of Object.entries(tools ?? {})) {
650
+ if (!('execute' in toolDef) || typeof toolDef.execute !== 'function') {
651
+ wrapped[toolName] = toolDef;
652
+ continue;
653
+ }
654
+ const exec = toolDef.execute;
655
+ wrapped[toolName] = {
656
+ ...toolDef,
657
+ execute: async (args, options) => {
658
+ const toolCallId = typeof options?.toolCallId === 'string'
659
+ ? options.toolCallId
660
+ : crypto.randomUUID();
661
+ const idempotencyKey = services.toolExecutor.buildIdempotencyKey({
662
+ sessionId: context.session.id,
663
+ agentId: context.agentId,
664
+ step: context.stepCount,
665
+ toolName,
666
+ toolCallId,
667
+ });
668
+ const callRecord = {
669
+ toolCallId,
670
+ toolName,
671
+ args,
672
+ idempotencyKey,
673
+ success: true,
674
+ timestamp: Date.now(),
675
+ };
676
+ const enforcement = await services.enforcer.check(callRecord, {
677
+ previousCalls: context.toolCallHistory,
678
+ currentStep: context.stepCount,
679
+ sessionState: context.session.state ?? {},
680
+ });
681
+ if (!enforcement.allowed) {
682
+ const reason = enforcement.reason ?? 'Tool call blocked by enforcement';
683
+ callRecord.success = false;
684
+ callRecord.error = new Error(reason);
685
+ context.toolCallHistory.push(callRecord);
686
+ await services.hookRunner.onToolError(context, callRecord, callRecord.error);
687
+ throw callRecord.error;
688
+ }
689
+ const enrichedOptions = withToolExecutionMetadata(options, context, toolName, toolCallId, idempotencyKey, services);
690
+ try {
691
+ return await exec(args, enrichedOptions);
692
+ }
693
+ catch (error) {
694
+ console.error(`[Runtime] Tool execution failed for ${toolName}:`, error);
695
+ throw error;
696
+ }
697
+ },
698
+ };
699
+ }
700
+ return wrapped;
701
+ }
702
+ function withToolExecutionMetadata(options, context, toolName, toolCallId, idempotencyKey, services) {
703
+ const baseOptions = options ?? {};
704
+ const existingContext = isRecord(baseOptions.experimental_context)
705
+ ? baseOptions.experimental_context
706
+ : {};
707
+ return {
708
+ ...baseOptions,
709
+ toolCallId,
710
+ experimental_context: {
711
+ ...existingContext,
712
+ session: context.session,
713
+ sessionId: context.session.id,
714
+ agentId: context.agentId,
715
+ step: context.stepCount,
716
+ turn: services.conversationState.getSessionTurn(context.session),
717
+ toolName,
718
+ toolCallId,
719
+ idempotencyKey,
720
+ memoryService: services.memoryService,
721
+ },
722
+ };
723
+ }
724
+ // --- Handoff candidates ---
725
+ function getHandoffCandidates(agent, services, activeRoutes) {
726
+ if (isTriageAgent(agent)) {
727
+ const routes = activeRoutes ?? agent.routes;
728
+ return routes
729
+ .map(route => services.agents.get(route.agentId))
730
+ .filter((candidate) => Boolean(candidate));
731
+ }
732
+ const targets = agent.canHandoffTo;
733
+ if (!targets || targets.length === 0)
734
+ return [];
735
+ return targets
736
+ .map(id => services.agents.get(id))
737
+ .filter((candidate) => Boolean(candidate));
738
+ }
739
+ // --- Circular handoff fallback ---
740
+ function getCircularHandoffFallbackMessage(services) {
741
+ const configured = services.config.circularHandoffFallbackMessage?.trim();
742
+ if (configured)
743
+ return configured;
744
+ return 'I hit a routing issue between agents. I will continue here. Tell me the exact outcome you need in one sentence.';
745
+ }
746
+ async function* emitCircularHandoffFallback(context, services) {
747
+ const fallback = getCircularHandoffFallbackMessage(services);
748
+ appendSessionMessage(context.session, { role: 'assistant', content: fallback }, services.conversationState);
749
+ yield* services.streamEmitter.emit(context, { type: 'text-delta', text: fallback });
750
+ }
751
+ // --- onBeforeModelCall hook ---
752
+ async function runBeforeModelCallHook(agent, context, system, services) {
753
+ try {
754
+ const messageTokens = context.session.messages.reduce((sum, m) => {
755
+ const txt = typeof m.content === 'string'
756
+ ? m.content
757
+ : Array.isArray(m.content)
758
+ ? m.content
759
+ .filter((p) => p.type === 'text')
760
+ .map((p) => p.text)
761
+ .join('')
762
+ : '';
763
+ return sum + estimateTokenCount(txt);
764
+ }, 0);
765
+ const systemTokens = estimateTokenCount(system);
766
+ const budgetData = (context.session.workingMemory['__ariaContextBudget'] ?? {});
767
+ const allocations = (budgetData.allocations ?? {});
768
+ const hookData = {
769
+ systemPrompt: system,
770
+ messages: context.session.messages,
771
+ estimatedTokens: systemTokens + messageTokens,
772
+ agentId: agent.id,
773
+ tokenBreakdown: {
774
+ basePrompt: allocations.basePrompt ?? 0,
775
+ autoRetrieve: allocations.autoRetrieve ?? 0,
776
+ workingMemory: allocations.workingMemory ?? 0,
777
+ extraction: 0,
778
+ longTermMemory: allocations.longTermMemory ?? 0,
779
+ policyInjections: allocations.policyInjections ?? 0,
780
+ messageHistory: messageTokens,
781
+ },
782
+ };
783
+ const hookResult = await services.config.hooks.onBeforeModelCall(context, hookData);
784
+ if (hookResult?.systemPrompt !== undefined) {
785
+ system = hookResult.systemPrompt;
786
+ }
787
+ if (hookResult?.messages !== undefined) {
788
+ context.session.messages = hookResult.messages;
789
+ }
790
+ }
791
+ catch (err) {
792
+ console.warn('[AriaFlow] onBeforeModelCall hook failed, using originals:', err);
793
+ }
794
+ return system;
795
+ }
796
+ // --- FlowExecutorHelpers builder ---
797
+ function matchesDetourRule(input, patterns) {
798
+ if (!patterns || patterns.length === 0)
799
+ return false;
800
+ for (const pattern of patterns) {
801
+ try {
802
+ const regex = new RegExp(pattern, 'i');
803
+ if (regex.test(input))
804
+ return true;
805
+ }
806
+ catch {
807
+ if (input.includes(pattern.toLowerCase()))
808
+ return true;
809
+ }
810
+ }
811
+ return false;
812
+ }
813
+ function buildFlowExecutorHelpers(services) {
814
+ return {
815
+ emit: (ctx, part) => services.streamEmitter.emit(ctx, part),
816
+ runOutputProcessing: (agent, ctx, text) => runOutputProcessing(agent, ctx, text, services.outputProcessors, services.outputRedactions),
817
+ appendSessionMessage: (s, m) => appendSessionMessage(s, m, services.conversationState),
818
+ appendSessionMessages: (s, ms) => appendSessionMessages(s, ms, services.conversationState),
819
+ postProcessPersistedAssistantMessages: (agent, ctx, start) => postProcessPersistedAssistantMessages(agent, ctx, start, services.outputProcessors, services.outputRedactions),
820
+ getAgentOutputProcessors: (agent) => getAgentOutputProcessors(agent, services.outputProcessors),
821
+ getSessionTurn: (s) => services.conversationState.getSessionTurn(s),
822
+ buildToolIdempotencyKey: (ctx, tn, tcid) => services.toolExecutor.buildIdempotencyKey({
823
+ sessionId: ctx.session.id,
824
+ agentId: ctx.agentId,
825
+ step: ctx.stepCount,
826
+ toolName: tn,
827
+ toolCallId: tcid,
828
+ }),
829
+ setFlowState: (s, id, st) => setFlowState(s, id, st, services.conversationState),
830
+ getFlowState: (s, id) => getFlowState(s, id),
831
+ updateFlowStateSnapshot: (s, id, st) => updateFlowStateSnapshot(s, id, st),
832
+ clearFlowState: (s, id) => clearFlowState(s, id, services.conversationState),
833
+ buildFlowWithHandoff: (a, ht, suppress) => buildFlowWithHandoff(a, ht, suppress),
834
+ getFlowNode: (a, nid) => a.flow.nodes.find(node => node.id === nid),
835
+ touchSession: (s) => services.conversationState.touchSession(s),
836
+ matchesDetourRule: (i, p) => matchesDetourRule(i, p),
837
+ enforcecheck: (call, ctx) => services.enforcer.check(call, {
838
+ previousCalls: ctx.toolCallHistory,
839
+ currentStep: ctx.stepCount,
840
+ sessionState: ctx.session.state ?? {},
841
+ }),
842
+ enforcecheckResult: (call, ctx) => services.enforcer.checkResult(call, {
843
+ previousCalls: ctx.toolCallHistory,
844
+ currentStep: ctx.stepCount,
845
+ sessionState: ctx.session.state ?? {},
846
+ }),
847
+ onToolCallHook: (ctx, call) => services.hookRunner.onToolCall(ctx, call),
848
+ onToolResultHook: (ctx, call) => services.hookRunner.onToolResult(ctx, call),
849
+ onToolErrorHook: (ctx, call, error) => services.hookRunner.onToolError(ctx, call, error),
850
+ };
851
+ }
852
+ // --- buildFlowWithHandoff (moved from Runtime) ---
853
+ function buildFlowWithHandoff(agent, handoffTool, suppressAutoRespond) {
854
+ const needsInitialSuppression = suppressAutoRespond &&
855
+ agent.flow.nodes.some(n => n.id === agent.initialNode && n.autoRespond === undefined);
856
+ if (!handoffTool && !needsInitialSuppression)
857
+ return agent.flow;
858
+ return {
859
+ ...agent.flow,
860
+ nodes: agent.flow.nodes.map(node => {
861
+ const shouldSuppress = suppressAutoRespond
862
+ && node.id === agent.initialNode
863
+ && node.autoRespond === undefined;
864
+ const existingTools = node.tools;
865
+ if (typeof existingTools === 'function') {
866
+ return {
867
+ ...node,
868
+ tools: (ctx) => {
869
+ const resolved = existingTools(ctx) ?? {};
870
+ if (!handoffTool)
871
+ return resolved;
872
+ return resolved.handoff ? resolved : { ...resolved, handoff: handoffTool };
873
+ },
874
+ ...(shouldSuppress ? { autoRespond: false } : {}),
875
+ };
876
+ }
877
+ const toolSet = existingTools ?? {};
878
+ if (!handoffTool || toolSet.handoff) {
879
+ return shouldSuppress ? { ...node, autoRespond: false } : node;
880
+ }
881
+ return {
882
+ ...node,
883
+ tools: { ...toolSet, handoff: handoffTool },
884
+ ...(shouldSuppress ? { autoRespond: false } : {}),
885
+ };
886
+ }),
887
+ };
888
+ }
889
+ //# sourceMappingURL=AgentExecuteStage.js.map