@falai/agent 1.2.8 → 2.0.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 (522) hide show
  1. package/README.md +40 -886
  2. package/dist/adapters/MemoryAdapter.js +2 -2
  3. package/dist/adapters/MemoryAdapter.js.map +1 -1
  4. package/dist/adapters/MongoAdapter.js +2 -2
  5. package/dist/adapters/MongoAdapter.js.map +1 -1
  6. package/dist/adapters/OpenSearchAdapter.d.ts.map +1 -1
  7. package/dist/adapters/OpenSearchAdapter.js +9 -7
  8. package/dist/adapters/OpenSearchAdapter.js.map +1 -1
  9. package/dist/adapters/PostgreSQLAdapter.d.ts +14 -0
  10. package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  11. package/dist/adapters/PostgreSQLAdapter.js +25 -9
  12. package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
  13. package/dist/adapters/PrismaAdapter.js +5 -5
  14. package/dist/adapters/PrismaAdapter.js.map +1 -1
  15. package/dist/adapters/RedisAdapter.js +2 -2
  16. package/dist/adapters/RedisAdapter.js.map +1 -1
  17. package/dist/adapters/SQLiteAdapter.d.ts +17 -0
  18. package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
  19. package/dist/adapters/SQLiteAdapter.js +30 -11
  20. package/dist/adapters/SQLiteAdapter.js.map +1 -1
  21. package/dist/cjs/adapters/MemoryAdapter.js +2 -2
  22. package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
  23. package/dist/cjs/adapters/MongoAdapter.js +2 -2
  24. package/dist/cjs/adapters/MongoAdapter.js.map +1 -1
  25. package/dist/cjs/adapters/OpenSearchAdapter.d.ts.map +1 -1
  26. package/dist/cjs/adapters/OpenSearchAdapter.js +9 -7
  27. package/dist/cjs/adapters/OpenSearchAdapter.js.map +1 -1
  28. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts +14 -0
  29. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  30. package/dist/cjs/adapters/PostgreSQLAdapter.js +25 -9
  31. package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
  32. package/dist/cjs/adapters/PrismaAdapter.js +5 -5
  33. package/dist/cjs/adapters/PrismaAdapter.js.map +1 -1
  34. package/dist/cjs/adapters/RedisAdapter.js +2 -2
  35. package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
  36. package/dist/cjs/adapters/SQLiteAdapter.d.ts +17 -0
  37. package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
  38. package/dist/cjs/adapters/SQLiteAdapter.js +30 -11
  39. package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
  40. package/dist/cjs/constants/index.d.ts +0 -9
  41. package/dist/cjs/constants/index.d.ts.map +1 -1
  42. package/dist/cjs/constants/index.js +2 -11
  43. package/dist/cjs/constants/index.js.map +1 -1
  44. package/dist/cjs/core/Agent.d.ts +119 -153
  45. package/dist/cjs/core/Agent.d.ts.map +1 -1
  46. package/dist/cjs/core/Agent.js +471 -324
  47. package/dist/cjs/core/Agent.js.map +1 -1
  48. package/dist/cjs/core/AutoChainExecutor.d.ts +107 -0
  49. package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -0
  50. package/dist/cjs/core/AutoChainExecutor.js +297 -0
  51. package/dist/cjs/core/AutoChainExecutor.js.map +1 -0
  52. package/dist/cjs/core/BranchEvaluator.d.ts +54 -0
  53. package/dist/cjs/core/BranchEvaluator.d.ts.map +1 -0
  54. package/dist/cjs/core/BranchEvaluator.js +130 -0
  55. package/dist/cjs/core/BranchEvaluator.js.map +1 -0
  56. package/dist/cjs/core/DirectiveBus.d.ts +88 -0
  57. package/dist/cjs/core/DirectiveBus.d.ts.map +1 -0
  58. package/dist/cjs/core/DirectiveBus.js +196 -0
  59. package/dist/cjs/core/DirectiveBus.js.map +1 -0
  60. package/dist/cjs/core/DirectiveChainTracker.d.ts +49 -0
  61. package/dist/cjs/core/DirectiveChainTracker.d.ts.map +1 -0
  62. package/dist/cjs/core/DirectiveChainTracker.js +121 -0
  63. package/dist/cjs/core/DirectiveChainTracker.js.map +1 -0
  64. package/dist/cjs/core/Flow.d.ts +186 -0
  65. package/dist/cjs/core/Flow.d.ts.map +1 -0
  66. package/dist/cjs/core/Flow.js +550 -0
  67. package/dist/cjs/core/Flow.js.map +1 -0
  68. package/dist/cjs/core/FlowRouter.d.ts +182 -0
  69. package/dist/cjs/core/FlowRouter.d.ts.map +1 -0
  70. package/dist/cjs/core/{RoutingEngine.js → FlowRouter.js} +323 -306
  71. package/dist/cjs/core/FlowRouter.js.map +1 -0
  72. package/dist/cjs/core/PersistenceManager.d.ts +2 -2
  73. package/dist/cjs/core/PersistenceManager.d.ts.map +1 -1
  74. package/dist/cjs/core/PersistenceManager.js +7 -7
  75. package/dist/cjs/core/PersistenceManager.js.map +1 -1
  76. package/dist/cjs/core/PromptComposer.d.ts +21 -8
  77. package/dist/cjs/core/PromptComposer.d.ts.map +1 -1
  78. package/dist/cjs/core/PromptComposer.js +182 -105
  79. package/dist/cjs/core/PromptComposer.js.map +1 -1
  80. package/dist/cjs/core/PromptSectionCache.d.ts +1 -1
  81. package/dist/cjs/core/PromptSectionCache.js +1 -1
  82. package/dist/cjs/core/ResponseEngine.d.ts +18 -8
  83. package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
  84. package/dist/cjs/core/ResponseEngine.js +38 -36
  85. package/dist/cjs/core/ResponseEngine.js.map +1 -1
  86. package/dist/cjs/core/ResponseModal.d.ts +73 -56
  87. package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
  88. package/dist/cjs/core/ResponseModal.js +1191 -1014
  89. package/dist/cjs/core/ResponseModal.js.map +1 -1
  90. package/dist/cjs/core/ResponsePipeline.d.ts +124 -26
  91. package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
  92. package/dist/cjs/core/ResponsePipeline.js +509 -136
  93. package/dist/cjs/core/ResponsePipeline.js.map +1 -1
  94. package/dist/cjs/core/SignalEvaluator.d.ts +86 -0
  95. package/dist/cjs/core/SignalEvaluator.d.ts.map +1 -0
  96. package/dist/cjs/core/SignalEvaluator.js +333 -0
  97. package/dist/cjs/core/SignalEvaluator.js.map +1 -0
  98. package/dist/cjs/core/SignalProcessor.d.ts +152 -0
  99. package/dist/cjs/core/SignalProcessor.d.ts.map +1 -0
  100. package/dist/cjs/core/SignalProcessor.js +562 -0
  101. package/dist/cjs/core/SignalProcessor.js.map +1 -0
  102. package/dist/cjs/core/Step.d.ts +43 -32
  103. package/dist/cjs/core/Step.d.ts.map +1 -1
  104. package/dist/cjs/core/Step.js +221 -126
  105. package/dist/cjs/core/Step.js.map +1 -1
  106. package/dist/cjs/core/StreamingToolExecutor.d.ts +2 -2
  107. package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -1
  108. package/dist/cjs/core/StreamingToolExecutor.js.map +1 -1
  109. package/dist/cjs/core/ToolManager.d.ts +44 -13
  110. package/dist/cjs/core/ToolManager.d.ts.map +1 -1
  111. package/dist/cjs/core/ToolManager.js +174 -91
  112. package/dist/cjs/core/ToolManager.js.map +1 -1
  113. package/dist/cjs/core/createAgent.d.ts +35 -0
  114. package/dist/cjs/core/createAgent.d.ts.map +1 -0
  115. package/dist/cjs/core/createAgent.js +39 -0
  116. package/dist/cjs/core/createAgent.js.map +1 -0
  117. package/dist/cjs/core/flow-namespace.d.ts +49 -0
  118. package/dist/cjs/core/flow-namespace.d.ts.map +1 -0
  119. package/dist/cjs/core/flow-namespace.js +171 -0
  120. package/dist/cjs/core/flow-namespace.js.map +1 -0
  121. package/dist/cjs/index.d.ts +11 -14
  122. package/dist/cjs/index.d.ts.map +1 -1
  123. package/dist/cjs/index.js +18 -22
  124. package/dist/cjs/index.js.map +1 -1
  125. package/dist/cjs/providers/AnthropicProvider.d.ts +1 -1
  126. package/dist/cjs/providers/AnthropicProvider.js +1 -1
  127. package/dist/cjs/providers/GeminiProvider.d.ts +1 -1
  128. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  129. package/dist/cjs/providers/GeminiProvider.js +1 -1
  130. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  131. package/dist/cjs/providers/OpenAIProvider.d.ts +1 -1
  132. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  133. package/dist/cjs/providers/OpenAIProvider.js +1 -1
  134. package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
  135. package/dist/cjs/types/agent.d.ts +183 -54
  136. package/dist/cjs/types/agent.d.ts.map +1 -1
  137. package/dist/cjs/types/agent.js +0 -6
  138. package/dist/cjs/types/agent.js.map +1 -1
  139. package/dist/cjs/types/ai.d.ts +3 -3
  140. package/dist/cjs/types/ai.d.ts.map +1 -1
  141. package/dist/cjs/types/errors.d.ts +15 -0
  142. package/dist/cjs/types/errors.d.ts.map +1 -0
  143. package/dist/cjs/types/errors.js +22 -0
  144. package/dist/cjs/types/errors.js.map +1 -0
  145. package/dist/cjs/types/flow.d.ts +513 -0
  146. package/dist/cjs/types/flow.d.ts.map +1 -0
  147. package/dist/cjs/types/{route.js → flow.js} +2 -2
  148. package/dist/cjs/types/flow.js.map +1 -0
  149. package/dist/cjs/types/index.d.ts +7 -6
  150. package/dist/cjs/types/index.d.ts.map +1 -1
  151. package/dist/cjs/types/index.js +6 -2
  152. package/dist/cjs/types/index.js.map +1 -1
  153. package/dist/cjs/types/persistence.d.ts +11 -7
  154. package/dist/cjs/types/persistence.d.ts.map +1 -1
  155. package/dist/cjs/types/routing.d.ts +1 -1
  156. package/dist/cjs/types/routing.d.ts.map +1 -1
  157. package/dist/cjs/types/session.d.ts +24 -23
  158. package/dist/cjs/types/session.d.ts.map +1 -1
  159. package/dist/cjs/types/signals.d.ts +248 -0
  160. package/dist/cjs/types/signals.d.ts.map +1 -0
  161. package/dist/cjs/types/signals.js +11 -0
  162. package/dist/cjs/types/signals.js.map +1 -0
  163. package/dist/cjs/types/template.d.ts +2 -8
  164. package/dist/cjs/types/template.d.ts.map +1 -1
  165. package/dist/cjs/types/tool.d.ts +36 -29
  166. package/dist/cjs/types/tool.d.ts.map +1 -1
  167. package/dist/cjs/types/tool.js +1 -1
  168. package/dist/cjs/types/tool.js.map +1 -1
  169. package/dist/cjs/utils/condition.d.ts +7 -1
  170. package/dist/cjs/utils/condition.d.ts.map +1 -1
  171. package/dist/cjs/utils/condition.js.map +1 -1
  172. package/dist/cjs/utils/id.d.ts +13 -5
  173. package/dist/cjs/utils/id.d.ts.map +1 -1
  174. package/dist/cjs/utils/id.js +24 -10
  175. package/dist/cjs/utils/id.js.map +1 -1
  176. package/dist/cjs/utils/index.d.ts +2 -2
  177. package/dist/cjs/utils/index.d.ts.map +1 -1
  178. package/dist/cjs/utils/index.js +7 -3
  179. package/dist/cjs/utils/index.js.map +1 -1
  180. package/dist/cjs/utils/session.d.ts +44 -5
  181. package/dist/cjs/utils/session.d.ts.map +1 -1
  182. package/dist/cjs/utils/session.js +197 -38
  183. package/dist/cjs/utils/session.js.map +1 -1
  184. package/dist/constants/index.d.ts +0 -9
  185. package/dist/constants/index.d.ts.map +1 -1
  186. package/dist/constants/index.js +3 -9
  187. package/dist/constants/index.js.map +1 -1
  188. package/dist/core/Agent.d.ts +119 -153
  189. package/dist/core/Agent.d.ts.map +1 -1
  190. package/dist/core/Agent.js +472 -325
  191. package/dist/core/Agent.js.map +1 -1
  192. package/dist/core/AutoChainExecutor.d.ts +107 -0
  193. package/dist/core/AutoChainExecutor.d.ts.map +1 -0
  194. package/dist/core/AutoChainExecutor.js +293 -0
  195. package/dist/core/AutoChainExecutor.js.map +1 -0
  196. package/dist/core/BranchEvaluator.d.ts +54 -0
  197. package/dist/core/BranchEvaluator.d.ts.map +1 -0
  198. package/dist/core/BranchEvaluator.js +126 -0
  199. package/dist/core/BranchEvaluator.js.map +1 -0
  200. package/dist/core/DirectiveBus.d.ts +88 -0
  201. package/dist/core/DirectiveBus.d.ts.map +1 -0
  202. package/dist/core/DirectiveBus.js +192 -0
  203. package/dist/core/DirectiveBus.js.map +1 -0
  204. package/dist/core/DirectiveChainTracker.d.ts +49 -0
  205. package/dist/core/DirectiveChainTracker.d.ts.map +1 -0
  206. package/dist/core/DirectiveChainTracker.js +117 -0
  207. package/dist/core/DirectiveChainTracker.js.map +1 -0
  208. package/dist/core/Flow.d.ts +186 -0
  209. package/dist/core/Flow.d.ts.map +1 -0
  210. package/dist/core/Flow.js +546 -0
  211. package/dist/core/Flow.js.map +1 -0
  212. package/dist/core/FlowRouter.d.ts +182 -0
  213. package/dist/core/FlowRouter.d.ts.map +1 -0
  214. package/dist/core/{RoutingEngine.js → FlowRouter.js} +322 -305
  215. package/dist/core/FlowRouter.js.map +1 -0
  216. package/dist/core/PersistenceManager.d.ts +2 -2
  217. package/dist/core/PersistenceManager.d.ts.map +1 -1
  218. package/dist/core/PersistenceManager.js +7 -7
  219. package/dist/core/PersistenceManager.js.map +1 -1
  220. package/dist/core/PromptComposer.d.ts +21 -8
  221. package/dist/core/PromptComposer.d.ts.map +1 -1
  222. package/dist/core/PromptComposer.js +183 -106
  223. package/dist/core/PromptComposer.js.map +1 -1
  224. package/dist/core/PromptSectionCache.d.ts +1 -1
  225. package/dist/core/PromptSectionCache.js +1 -1
  226. package/dist/core/ResponseEngine.d.ts +18 -8
  227. package/dist/core/ResponseEngine.d.ts.map +1 -1
  228. package/dist/core/ResponseEngine.js +38 -36
  229. package/dist/core/ResponseEngine.js.map +1 -1
  230. package/dist/core/ResponseModal.d.ts +73 -56
  231. package/dist/core/ResponseModal.d.ts.map +1 -1
  232. package/dist/core/ResponseModal.js +1193 -1016
  233. package/dist/core/ResponseModal.js.map +1 -1
  234. package/dist/core/ResponsePipeline.d.ts +124 -26
  235. package/dist/core/ResponsePipeline.d.ts.map +1 -1
  236. package/dist/core/ResponsePipeline.js +509 -137
  237. package/dist/core/ResponsePipeline.js.map +1 -1
  238. package/dist/core/SignalEvaluator.d.ts +86 -0
  239. package/dist/core/SignalEvaluator.d.ts.map +1 -0
  240. package/dist/core/SignalEvaluator.js +326 -0
  241. package/dist/core/SignalEvaluator.js.map +1 -0
  242. package/dist/core/SignalProcessor.d.ts +152 -0
  243. package/dist/core/SignalProcessor.d.ts.map +1 -0
  244. package/dist/core/SignalProcessor.js +555 -0
  245. package/dist/core/SignalProcessor.js.map +1 -0
  246. package/dist/core/Step.d.ts +43 -32
  247. package/dist/core/Step.d.ts.map +1 -1
  248. package/dist/core/Step.js +220 -126
  249. package/dist/core/Step.js.map +1 -1
  250. package/dist/core/StreamingToolExecutor.d.ts +2 -2
  251. package/dist/core/StreamingToolExecutor.d.ts.map +1 -1
  252. package/dist/core/StreamingToolExecutor.js.map +1 -1
  253. package/dist/core/ToolManager.d.ts +44 -13
  254. package/dist/core/ToolManager.d.ts.map +1 -1
  255. package/dist/core/ToolManager.js +174 -91
  256. package/dist/core/ToolManager.js.map +1 -1
  257. package/dist/core/createAgent.d.ts +35 -0
  258. package/dist/core/createAgent.d.ts.map +1 -0
  259. package/dist/core/createAgent.js +36 -0
  260. package/dist/core/createAgent.js.map +1 -0
  261. package/dist/core/flow-namespace.d.ts +49 -0
  262. package/dist/core/flow-namespace.d.ts.map +1 -0
  263. package/dist/core/flow-namespace.js +168 -0
  264. package/dist/core/flow-namespace.js.map +1 -0
  265. package/dist/index.d.ts +11 -14
  266. package/dist/index.d.ts.map +1 -1
  267. package/dist/index.js +9 -12
  268. package/dist/index.js.map +1 -1
  269. package/dist/providers/AnthropicProvider.d.ts +1 -1
  270. package/dist/providers/AnthropicProvider.js +1 -1
  271. package/dist/providers/GeminiProvider.d.ts +1 -1
  272. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  273. package/dist/providers/GeminiProvider.js +1 -1
  274. package/dist/providers/GeminiProvider.js.map +1 -1
  275. package/dist/providers/OpenAIProvider.d.ts +1 -1
  276. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  277. package/dist/providers/OpenAIProvider.js +1 -1
  278. package/dist/providers/OpenAIProvider.js.map +1 -1
  279. package/dist/types/agent.d.ts +183 -54
  280. package/dist/types/agent.d.ts.map +1 -1
  281. package/dist/types/agent.js +0 -6
  282. package/dist/types/agent.js.map +1 -1
  283. package/dist/types/ai.d.ts +3 -3
  284. package/dist/types/ai.d.ts.map +1 -1
  285. package/dist/types/errors.d.ts +15 -0
  286. package/dist/types/errors.d.ts.map +1 -0
  287. package/dist/types/errors.js +18 -0
  288. package/dist/types/errors.js.map +1 -0
  289. package/dist/types/flow.d.ts +513 -0
  290. package/dist/types/flow.d.ts.map +1 -0
  291. package/dist/types/flow.js +5 -0
  292. package/dist/types/flow.js.map +1 -0
  293. package/dist/types/index.d.ts +7 -6
  294. package/dist/types/index.d.ts.map +1 -1
  295. package/dist/types/index.js +4 -1
  296. package/dist/types/index.js.map +1 -1
  297. package/dist/types/persistence.d.ts +11 -7
  298. package/dist/types/persistence.d.ts.map +1 -1
  299. package/dist/types/routing.d.ts +1 -1
  300. package/dist/types/routing.d.ts.map +1 -1
  301. package/dist/types/session.d.ts +24 -23
  302. package/dist/types/session.d.ts.map +1 -1
  303. package/dist/types/signals.d.ts +248 -0
  304. package/dist/types/signals.d.ts.map +1 -0
  305. package/dist/types/signals.js +10 -0
  306. package/dist/types/signals.js.map +1 -0
  307. package/dist/types/template.d.ts +2 -8
  308. package/dist/types/template.d.ts.map +1 -1
  309. package/dist/types/tool.d.ts +36 -29
  310. package/dist/types/tool.d.ts.map +1 -1
  311. package/dist/types/tool.js +1 -1
  312. package/dist/types/tool.js.map +1 -1
  313. package/dist/utils/condition.d.ts +7 -1
  314. package/dist/utils/condition.d.ts.map +1 -1
  315. package/dist/utils/condition.js.map +1 -1
  316. package/dist/utils/id.d.ts +13 -5
  317. package/dist/utils/id.d.ts.map +1 -1
  318. package/dist/utils/id.js +22 -9
  319. package/dist/utils/id.js.map +1 -1
  320. package/dist/utils/index.d.ts +2 -2
  321. package/dist/utils/index.d.ts.map +1 -1
  322. package/dist/utils/index.js +2 -2
  323. package/dist/utils/index.js.map +1 -1
  324. package/dist/utils/session.d.ts +44 -5
  325. package/dist/utils/session.d.ts.map +1 -1
  326. package/dist/utils/session.js +193 -37
  327. package/dist/utils/session.js.map +1 -1
  328. package/docs/README.md +22 -200
  329. package/docs/concepts/architecture.md +281 -0
  330. package/docs/concepts/directives.md +400 -0
  331. package/docs/concepts/pipeline.md +399 -0
  332. package/docs/guides/branching.md +263 -0
  333. package/docs/guides/compaction.md +163 -0
  334. package/docs/guides/conditions.md +167 -0
  335. package/docs/guides/error-handling.md +176 -0
  336. package/docs/guides/flow-control.md +409 -0
  337. package/docs/guides/instructions.md +210 -0
  338. package/docs/guides/persistence.md +182 -0
  339. package/docs/guides/streaming.md +137 -0
  340. package/docs/migration/README.md +14 -0
  341. package/docs/migration/route-to-flow.md +561 -0
  342. package/docs/migration/v1-to-v2.md +909 -0
  343. package/docs/reference/adapters.md +481 -0
  344. package/docs/reference/branches.md +241 -0
  345. package/docs/reference/create-agent.md +186 -0
  346. package/docs/reference/directive.md +243 -0
  347. package/docs/reference/errors.md +122 -0
  348. package/docs/reference/flow.md +238 -0
  349. package/docs/reference/instruction.md +177 -0
  350. package/docs/reference/pre-directive.md +131 -0
  351. package/docs/reference/providers.md +227 -0
  352. package/docs/reference/signals.md +356 -0
  353. package/docs/reference/step.md +339 -0
  354. package/docs/reference/tool.md +269 -0
  355. package/docs/start/01-install.md +81 -0
  356. package/docs/start/02-first-agent.md +196 -0
  357. package/docs/start/03-collect-data.md +222 -0
  358. package/docs/start/04-add-tools.md +276 -0
  359. package/docs/start/05-go-to-production.md +216 -0
  360. package/examples/01-quickstart.ts +20 -0
  361. package/examples/02-data-extraction.ts +90 -0
  362. package/examples/03-tools.ts +136 -0
  363. package/examples/04-instructions.ts +100 -0
  364. package/examples/05-branching.ts +140 -0
  365. package/examples/06-flow-control.ts +103 -0
  366. package/examples/07-streaming.ts +69 -0
  367. package/examples/08-persistence.ts +98 -0
  368. package/examples/09-signals.ts +144 -0
  369. package/examples/tsconfig.json +30 -0
  370. package/package.json +2 -1
  371. package/src/adapters/MemoryAdapter.ts +3 -3
  372. package/src/adapters/MongoAdapter.ts +3 -3
  373. package/src/adapters/OpenSearchAdapter.ts +10 -8
  374. package/src/adapters/PostgreSQLAdapter.ts +26 -10
  375. package/src/adapters/PrismaAdapter.ts +6 -6
  376. package/src/adapters/RedisAdapter.ts +3 -3
  377. package/src/adapters/SQLiteAdapter.ts +31 -12
  378. package/src/constants/index.ts +2 -10
  379. package/src/core/Agent.ts +585 -374
  380. package/src/core/AutoChainExecutor.ts +440 -0
  381. package/src/core/BranchEvaluator.ts +167 -0
  382. package/src/core/DirectiveBus.ts +248 -0
  383. package/src/core/DirectiveChainTracker.ts +144 -0
  384. package/src/core/Flow.ts +666 -0
  385. package/src/core/{RoutingEngine.ts → FlowRouter.ts} +385 -365
  386. package/src/core/PersistenceManager.ts +8 -8
  387. package/src/core/PromptComposer.ts +209 -140
  388. package/src/core/PromptSectionCache.ts +1 -1
  389. package/src/core/ResponseEngine.ts +61 -46
  390. package/src/core/ResponseModal.ts +1453 -1240
  391. package/src/core/ResponsePipeline.ts +655 -175
  392. package/src/core/SignalEvaluator.ts +420 -0
  393. package/src/core/SignalProcessor.ts +723 -0
  394. package/src/core/Step.ts +279 -176
  395. package/src/core/StreamingToolExecutor.ts +4 -4
  396. package/src/core/ToolManager.ts +200 -97
  397. package/src/core/createAgent.ts +40 -0
  398. package/src/core/flow-namespace.ts +219 -0
  399. package/src/index.ts +42 -36
  400. package/src/providers/AnthropicProvider.ts +2 -2
  401. package/src/providers/GeminiProvider.ts +2 -2
  402. package/src/providers/OpenAIProvider.ts +2 -2
  403. package/src/types/agent.ts +182 -53
  404. package/src/types/ai.ts +3 -3
  405. package/src/types/errors.ts +18 -0
  406. package/src/types/flow.ts +590 -0
  407. package/src/types/index.ts +43 -16
  408. package/src/types/persistence.ts +12 -8
  409. package/src/types/routing.ts +1 -1
  410. package/src/types/session.ts +26 -23
  411. package/src/types/signals.ts +321 -0
  412. package/src/types/template.ts +3 -11
  413. package/src/types/tool.ts +50 -42
  414. package/src/utils/condition.ts +13 -4
  415. package/src/utils/id.ts +27 -9
  416. package/src/utils/index.ts +6 -2
  417. package/src/utils/session.ts +238 -42
  418. package/dist/cjs/core/BatchExecutor.d.ts +0 -359
  419. package/dist/cjs/core/BatchExecutor.d.ts.map +0 -1
  420. package/dist/cjs/core/BatchExecutor.js +0 -861
  421. package/dist/cjs/core/BatchExecutor.js.map +0 -1
  422. package/dist/cjs/core/BatchPromptBuilder.d.ts +0 -89
  423. package/dist/cjs/core/BatchPromptBuilder.d.ts.map +0 -1
  424. package/dist/cjs/core/BatchPromptBuilder.js +0 -223
  425. package/dist/cjs/core/BatchPromptBuilder.js.map +0 -1
  426. package/dist/cjs/core/Route.d.ts +0 -180
  427. package/dist/cjs/core/Route.d.ts.map +0 -1
  428. package/dist/cjs/core/Route.js +0 -542
  429. package/dist/cjs/core/Route.js.map +0 -1
  430. package/dist/cjs/core/RoutingEngine.d.ts +0 -185
  431. package/dist/cjs/core/RoutingEngine.d.ts.map +0 -1
  432. package/dist/cjs/core/RoutingEngine.js.map +0 -1
  433. package/dist/cjs/types/route.d.ts +0 -336
  434. package/dist/cjs/types/route.d.ts.map +0 -1
  435. package/dist/cjs/types/route.js.map +0 -1
  436. package/dist/core/BatchExecutor.d.ts +0 -359
  437. package/dist/core/BatchExecutor.d.ts.map +0 -1
  438. package/dist/core/BatchExecutor.js +0 -856
  439. package/dist/core/BatchExecutor.js.map +0 -1
  440. package/dist/core/BatchPromptBuilder.d.ts +0 -89
  441. package/dist/core/BatchPromptBuilder.d.ts.map +0 -1
  442. package/dist/core/BatchPromptBuilder.js +0 -219
  443. package/dist/core/BatchPromptBuilder.js.map +0 -1
  444. package/dist/core/Route.d.ts +0 -180
  445. package/dist/core/Route.d.ts.map +0 -1
  446. package/dist/core/Route.js +0 -538
  447. package/dist/core/Route.js.map +0 -1
  448. package/dist/core/RoutingEngine.d.ts +0 -185
  449. package/dist/core/RoutingEngine.d.ts.map +0 -1
  450. package/dist/core/RoutingEngine.js.map +0 -1
  451. package/dist/types/route.d.ts +0 -336
  452. package/dist/types/route.d.ts.map +0 -1
  453. package/dist/types/route.js +0 -5
  454. package/dist/types/route.js.map +0 -1
  455. package/docs/CONTRIBUTING.md +0 -521
  456. package/docs/api/README.md +0 -3299
  457. package/docs/api/overview.md +0 -1410
  458. package/docs/architecture/data-extraction-flow.md +0 -360
  459. package/docs/architecture/multi-step-execution.md +0 -277
  460. package/docs/core/agent/README.md +0 -938
  461. package/docs/core/agent/context-management.md +0 -796
  462. package/docs/core/agent/rules-and-prohibitions.md +0 -113
  463. package/docs/core/agent/session-management.md +0 -693
  464. package/docs/core/ai-integration/prompt-composition.md +0 -355
  465. package/docs/core/ai-integration/providers.md +0 -515
  466. package/docs/core/ai-integration/response-processing.md +0 -433
  467. package/docs/core/conversation-flows/data-collection.md +0 -772
  468. package/docs/core/conversation-flows/route-dsl.md +0 -509
  469. package/docs/core/conversation-flows/routes.md +0 -249
  470. package/docs/core/conversation-flows/step-transitions.md +0 -731
  471. package/docs/core/conversation-flows/steps.md +0 -268
  472. package/docs/core/error-handling.md +0 -830
  473. package/docs/core/persistence/adapters.md +0 -255
  474. package/docs/core/persistence/session-storage.md +0 -656
  475. package/docs/core/routing/intelligent-routing.md +0 -470
  476. package/docs/core/tools/enhanced-tool.md +0 -186
  477. package/docs/core/tools/streaming-execution.md +0 -161
  478. package/docs/core/tools/tool-definition.md +0 -970
  479. package/docs/core/tools/tool-scoping.md +0 -819
  480. package/docs/guides/advanced-patterns/publishing.md +0 -186
  481. package/docs/guides/context-compaction.md +0 -96
  482. package/docs/guides/error-handling-patterns.md +0 -578
  483. package/docs/guides/getting-started/README.md +0 -795
  484. package/docs/guides/migration/README.md +0 -101
  485. package/docs/guides/migration/flexible-routing-conditions.md +0 -375
  486. package/docs/guides/migration/multi-step-execution.md +0 -393
  487. package/docs/guides/migration/response-modal-refactor.md +0 -518
  488. package/docs/guides/prompt-optimization.md +0 -164
  489. package/examples/advanced-patterns/context-compaction.ts +0 -223
  490. package/examples/advanced-patterns/knowledge-based-agent.ts +0 -735
  491. package/examples/advanced-patterns/persistent-onboarding.ts +0 -728
  492. package/examples/advanced-patterns/route-lifecycle-hooks.ts +0 -556
  493. package/examples/advanced-patterns/streaming-responses.ts +0 -656
  494. package/examples/ai-providers/anthropic-integration.ts +0 -388
  495. package/examples/ai-providers/openai-integration.ts +0 -228
  496. package/examples/condition-patterns/function-only-conditions.ts +0 -365
  497. package/examples/condition-patterns/mixed-array-conditions.ts +0 -477
  498. package/examples/condition-patterns/route-skipif-patterns.ts +0 -468
  499. package/examples/condition-patterns/step-skipif-patterns.ts +0 -0
  500. package/examples/condition-patterns/string-only-conditions.ts +0 -296
  501. package/examples/conversation-flows/completion-transitions.ts +0 -318
  502. package/examples/core-concepts/basic-agent.ts +0 -503
  503. package/examples/core-concepts/modern-streaming-api.ts +0 -309
  504. package/examples/core-concepts/schema-driven-extraction.ts +0 -332
  505. package/examples/core-concepts/session-management.ts +0 -494
  506. package/examples/integrations/database-integration.ts +0 -631
  507. package/examples/integrations/healthcare-integration.ts +0 -595
  508. package/examples/integrations/search-integration.ts +0 -530
  509. package/examples/integrations/server-session-management.ts +0 -307
  510. package/examples/persistence/custom-adapter.ts +0 -526
  511. package/examples/persistence/database-persistence.ts +0 -583
  512. package/examples/persistence/memory-sessions.ts +0 -495
  513. package/examples/persistence/prisma-schema.example.prisma +0 -74
  514. package/examples/persistence/redis-persistence.ts +0 -488
  515. package/examples/tools/basic-tools.ts +0 -765
  516. package/examples/tools/data-enrichment-tools.ts +0 -593
  517. package/examples/tools/enhanced-tool-metadata.ts +0 -268
  518. package/examples/tools/streaming-tool-execution.ts +0 -283
  519. package/src/core/BatchExecutor.ts +0 -1187
  520. package/src/core/BatchPromptBuilder.ts +0 -299
  521. package/src/core/Route.ts +0 -678
  522. package/src/types/route.ts +0 -392
@@ -5,14 +5,11 @@
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.ResponseModal = exports.ResponseGenerationError = void 0;
8
- const Step_1 = require("./Step");
9
8
  const ResponseEngine_1 = require("./ResponseEngine");
10
9
  const ResponsePipeline_1 = require("./ResponsePipeline");
11
- const BatchExecutor_1 = require("./BatchExecutor");
12
- const BatchPromptBuilder_1 = require("./BatchPromptBuilder");
10
+ const AutoChainExecutor_1 = require("./AutoChainExecutor");
13
11
  const utils_1 = require("../utils");
14
12
  const template_1 = require("../utils/template");
15
- const constants_1 = require("../constants");
16
13
  /**
17
14
  * Error class for response generation failures
18
15
  */
@@ -31,7 +28,8 @@ class ResponseGenerationError extends Error {
31
28
  */
32
29
  static fromError(error, phase, params, context) {
33
30
  const message = error instanceof Error ? error.message : String(error);
34
- return new ResponseGenerationError(`Response generation failed in ${phase}: ${message}`, { originalError: error, params, phase, context });
31
+ return new ResponseGenerationError(`[ResponseGenerationError] Response generation failed in ${phase}: ${message}. ` +
32
+ `Check provider configuration and the ${phase} phase handler.`, { originalError: error, params, phase, context });
35
33
  }
36
34
  /**
37
35
  * Check if an error is a ResponseGenerationError
@@ -52,12 +50,8 @@ class ResponseModal {
52
50
  // Initialize response engine
53
51
  this.responseEngine = new ResponseEngine_1.ResponseEngine(this.agent.promptSectionCache);
54
52
  // Initialize response pipeline with agent dependencies
55
- this.responsePipeline = new ResponsePipeline_1.ResponsePipeline(this.agent.getAgentOptions(), () => this.agent.getRoutes(), // Pass a function to get routes dynamically
56
- this.agent.getTools(), this.agent.getRoutingEngine(), this.agent.updateContext.bind(this.agent), this.agent.getUpdateDataMethod(), this.agent.updateCollectedData.bind(this.agent), this.getToolManager());
57
- // Initialize batch executor for multi-step execution
58
- this.batchExecutor = new BatchExecutor_1.BatchExecutor();
59
- // Initialize batch prompt builder for combined prompts
60
- this.batchPromptBuilder = new BatchPromptBuilder_1.BatchPromptBuilder(this.agent.promptSectionCache);
53
+ this.responsePipeline = new ResponsePipeline_1.ResponsePipeline(this.agent.getAgentOptions(), () => this.agent.getFlows(), // Pass a function to get flows dynamically
54
+ this.agent.getTools(), this.agent.getFlowRouter(), this.agent.updateContext.bind(this.agent), this.agent.getUpdateDataMethod(), this.agent.updateCollectedData.bind(this.agent), this.getToolManager(), this.agent.signalProcessor);
61
55
  }
62
56
  /**
63
57
  * Generate a non-streaming response using unified logic
@@ -73,7 +67,8 @@ class ResponseModal {
73
67
  return result;
74
68
  }
75
69
  catch (error) {
76
- throw new ResponseGenerationError(`Failed to generate response: ${error instanceof Error ? error.message : String(error)}`, { originalError: error, params, phase: 'response_generation' });
70
+ throw new ResponseGenerationError(`[ResponseGenerationError] Response generation failed: ${error instanceof Error ? error.message : String(error)}. ` +
71
+ `Check provider configuration and network connectivity.`, { originalError: error, params, phase: 'response_generation' });
77
72
  }
78
73
  }
79
74
  /**
@@ -226,6 +221,17 @@ class ResponseModal {
226
221
  utils_1.logger.warn(`[ResponseModal] ToolManager not available on agent - tool execution will use fallback methods`);
227
222
  return undefined;
228
223
  }
224
+ /**
225
+ * Collect scoped instructions from agent, flow, and step into a ScopedInstructions value.
226
+ * @private
227
+ */
228
+ collectScopedInstructions(flow, step) {
229
+ return {
230
+ global: this.agent.instructions,
231
+ flow: flow ? { flowTitle: flow.title, items: flow.instructions } : undefined,
232
+ step: step ? { stepId: step.id, items: step.getInstructions() } : undefined,
233
+ };
234
+ }
229
235
  // UNIFIED RESPONSE LOGIC - Consolidates common logic between streaming and non-streaming
230
236
  // ============================================================================
231
237
  /**
@@ -238,7 +244,8 @@ class ResponseModal {
238
244
  const { history: simpleHistory, contextOverride, signal } = params;
239
245
  // Validate input parameters
240
246
  if (!simpleHistory) {
241
- throw new ResponseGenerationError('History is required for response generation', { params, phase: 'validation' });
247
+ throw new ResponseGenerationError('[ResponseGenerationError] Missing history: history is required for response generation. ' +
248
+ 'Pass a valid history array to the respond/stream method.', { params, phase: 'validation' });
242
249
  }
243
250
  // Convert HistoryItem[] to Event[] for internal processing
244
251
  const historyEvents = (0, utils_1.historyToEvents)(simpleHistory);
@@ -250,7 +257,7 @@ class ResponseModal {
250
257
  try {
251
258
  // Set current context and session in pipeline for consistency
252
259
  this.responsePipeline.setContext(await this.agent.getContext());
253
- this.responsePipeline.setCurrentSession(this.agent.getCurrentSession());
260
+ this.responsePipeline.setCurrentSession(this.agent.currentSession);
254
261
  responseContext = await this.responsePipeline.prepareResponseContext({
255
262
  contextOverride,
256
263
  session: params.session ? (0, utils_1.cloneDeep)(params.session) : undefined,
@@ -289,8 +296,8 @@ class ResponseModal {
289
296
  catch (error) {
290
297
  throw ResponseGenerationError.fromError(error, 'step_preparation', params, { session, effectiveContext });
291
298
  }
292
- // PHASE 2: ROUTING + STEP SELECTION - Determine which route and step to use
293
- // Also performs pre-extraction and batch determination
299
+ // PHASE 2: ROUTING + STEP SELECTION - Determine which flow and step to use
300
+ // Performs pre-extraction and step selection
294
301
  let routingResult;
295
302
  try {
296
303
  routingResult = await this.handleUnifiedRoutingAndStepSelection({
@@ -307,14 +314,15 @@ class ResponseModal {
307
314
  effectiveContext,
308
315
  session: routingResult.session,
309
316
  history,
310
- selectedRoute: routingResult.selectedRoute,
317
+ selectedFlow: routingResult.selectedFlow,
311
318
  selectedStep: routingResult.selectedStep,
312
319
  responseDirectives: routingResult.responseDirectives,
313
- isRouteComplete: routingResult.isRouteComplete,
314
- batchSteps: routingResult.batchSteps,
315
- batchStoppedReason: routingResult.batchStoppedReason,
316
- batchStoppedAtStep: routingResult.batchStoppedAtStep,
320
+ isFlowComplete: routingResult.isFlowComplete,
317
321
  signal,
322
+ signalFirings: routingResult.signalFirings,
323
+ signalPreDirective: routingResult.signalPreDirective,
324
+ signalHalted: routingResult.signalHalted,
325
+ signalHaltReply: routingResult.signalHaltReply,
318
326
  };
319
327
  }
320
328
  catch (error) {
@@ -331,27 +339,141 @@ class ResponseModal {
331
339
  */
332
340
  async handleUnifiedRoutingAndStepSelection(params) {
333
341
  try {
334
- // Use the ResponsePipeline for optimized routing and step selection
335
- // This avoids duplicate logic and leverages existing optimizations
336
- // ResponsePipeline expects Event[] for history
342
+ // Create a fresh chain tracker for this turn (Requirement 22.1)
343
+ this.responsePipeline.createChainTracker();
344
+ // ROUTING SKIP OPTIMIZATION (Requirements 20.1, 20.2, 20.3):
345
+ // When the current step has collect fields AND pre-extraction populates at least
346
+ // one of those fields, skip FlowRouter.decideFlowAndStep for this turn.
347
+ const routingSkipResult = await this.attemptRoutingSkipForCollect(params);
348
+ if (routingSkipResult) {
349
+ // Even when routing is skipped, run pre-signal phase if processor is present
350
+ if (this.agent.signalProcessor) {
351
+ const signalResult = await this.responsePipeline.runPreSignalPhase(params.session, params.context, params.history);
352
+ // If signal halts, override the routing skip result
353
+ if (signalResult.mergedDirective?.halt) {
354
+ return {
355
+ ...routingSkipResult,
356
+ session: signalResult.updatedSession,
357
+ signalFirings: signalResult.firings,
358
+ signalHalted: true,
359
+ signalHaltReply: signalResult.mergedDirective.reply,
360
+ };
361
+ }
362
+ // If signal has position fields, override routing skip result
363
+ if ((0, ResponsePipeline_1.hasDirectivePositionField)(signalResult.mergedDirective)) {
364
+ return this.applySignalPositionDirective(signalResult, params);
365
+ }
366
+ // Non-position directive: propagate for pre-LLM augmentation
367
+ return {
368
+ ...routingSkipResult,
369
+ session: signalResult.updatedSession,
370
+ signalFirings: signalResult.firings,
371
+ signalPreDirective: signalResult.mergedDirective || undefined,
372
+ };
373
+ }
374
+ return routingSkipResult;
375
+ }
376
+ // ── PARALLEL PRE-SIGNAL PHASE + ROUTING (Algorithm 5) ────────────────
377
+ // When signalProcessor is present, run pre-signals in parallel with routing.
378
+ // When absent, call the router directly (zero overhead, preserve current behavior).
379
+ if (this.agent.signalProcessor) {
380
+ // Run pre-signal phase in parallel with routing (Requirement 8.1)
381
+ const [signalResult, routingResult] = await Promise.all([
382
+ this.responsePipeline.runPreSignalPhase(params.session, params.context, params.history),
383
+ this.responsePipeline.handleRoutingAndStepSelection({
384
+ session: params.session,
385
+ history: params.history,
386
+ context: params.context,
387
+ signal: params.signal,
388
+ }),
389
+ ]);
390
+ // ── Requirement 8.2: halt → discard routing, skip LLM ────────────
391
+ if (signalResult.mergedDirective?.halt) {
392
+ return {
393
+ selectedFlow: undefined,
394
+ selectedStep: undefined,
395
+ session: signalResult.updatedSession,
396
+ isFlowComplete: false,
397
+ signalFirings: signalResult.firings,
398
+ signalHalted: true,
399
+ signalHaltReply: signalResult.mergedDirective.reply,
400
+ };
401
+ }
402
+ // ── Requirement 8.3: position directive → discard routing, apply signal position ──
403
+ if ((0, ResponsePipeline_1.hasDirectivePositionField)(signalResult.mergedDirective)) {
404
+ return this.applySignalPositionDirective(signalResult, params);
405
+ }
406
+ // ── Requirement 8.4: non-position directive → use routing, propagate augmentation ──
407
+ // ── Requirement 8.5: no directive → use routing as-is ─────────────
408
+ let updatedSession = signalResult.updatedSession;
409
+ // Apply data/context updates from signal to the routed session
410
+ if (signalResult.mergedDirective?.dataUpdate) {
411
+ updatedSession = (0, utils_1.mergeCollected)(updatedSession, signalResult.mergedDirective.dataUpdate);
412
+ }
413
+ // Use routing result for flow/step, but carry signal session state
414
+ // Merge routing session changes on top of signal session
415
+ const routingSession = routingResult.session;
416
+ updatedSession = {
417
+ ...updatedSession,
418
+ currentFlow: routingSession.currentFlow,
419
+ currentStep: routingSession.currentStep,
420
+ flowHistory: routingSession.flowHistory,
421
+ pendingDirective: routingSession.pendingDirective,
422
+ };
423
+ const isFlowComplete = routingResult.isFlowComplete;
424
+ // PRE-EXTRACTION: same logic as below — extract data from user message
425
+ if (routingResult.selectedFlow && !isFlowComplete) {
426
+ if (this.shouldPreExtractData(routingResult.selectedFlow)) {
427
+ utils_1.logger.debug(`[ResponseModal] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`);
428
+ const extractedData = await this.preExtractFlowData({
429
+ route: routingResult.selectedFlow,
430
+ history: params.history,
431
+ context: params.context,
432
+ session: updatedSession,
433
+ signal: params.signal,
434
+ });
435
+ if (extractedData && Object.keys(extractedData).length > 0) {
436
+ utils_1.logger.debug(`[ResponseModal] Pre-extracted data:`, extractedData);
437
+ updatedSession = (0, utils_1.mergeCollected)(updatedSession, extractedData);
438
+ await this.agent.updateCollectedData(extractedData);
439
+ }
440
+ }
441
+ }
442
+ // Determine next step
443
+ const stepResult = await this.responsePipeline.determineNextStep({
444
+ selectedFlow: routingResult.selectedFlow,
445
+ selectedStep: routingResult.selectedStep,
446
+ session: updatedSession,
447
+ isFlowComplete,
448
+ });
449
+ return {
450
+ selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
451
+ selectedStep: stepResult.nextStep,
452
+ responseDirectives: routingResult.responseDirectives,
453
+ session: stepResult.session,
454
+ isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
455
+ signalFirings: signalResult.firings,
456
+ signalPreDirective: signalResult.mergedDirective || undefined,
457
+ };
458
+ }
459
+ // ── No signal processor: existing behavior (zero overhead) ────────────
337
460
  const routingResult = await this.responsePipeline.handleRoutingAndStepSelection({
338
461
  session: params.session,
339
- history: params.history, // Already Event[]
462
+ history: params.history,
340
463
  context: params.context,
341
464
  signal: params.signal,
342
465
  });
343
466
  let updatedSession = routingResult.session;
344
- const isRouteComplete = routingResult.isRouteComplete;
345
- // PRE-EXTRACTION: If entering a route that collects data, extract data from user message first
467
+ const isFlowComplete = routingResult.isFlowComplete;
468
+ // PRE-EXTRACTION: If entering a flow that collects data, extract data from user message first
346
469
  // This allows us to skip steps whose data is already provided
347
- // Requirement 3.1: Perform Pre_Extraction before determining the Batch
348
- if (routingResult.selectedRoute && !isRouteComplete) {
349
- // Always pre-extract when route collects data (not just on new route entry)
350
- // This ensures batch determination has the most up-to-date data
351
- if (this.shouldPreExtractData(routingResult.selectedRoute)) {
352
- utils_1.logger.debug(`[ResponseModal] Pre-extracting data for route: ${routingResult.selectedRoute.title}`);
353
- const extractedData = await this.preExtractRouteData({
354
- route: routingResult.selectedRoute,
470
+ if (routingResult.selectedFlow && !isFlowComplete) {
471
+ // Always pre-extract when flow collects data (not just on new flow entry)
472
+ // This ensures step selection has the most up-to-date data
473
+ if (this.shouldPreExtractData(routingResult.selectedFlow)) {
474
+ utils_1.logger.debug(`[ResponseModal] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`);
475
+ const extractedData = await this.preExtractFlowData({
476
+ route: routingResult.selectedFlow,
355
477
  history: params.history,
356
478
  context: params.context,
357
479
  session: updatedSession,
@@ -359,51 +481,27 @@ class ResponseModal {
359
481
  });
360
482
  if (extractedData && Object.keys(extractedData).length > 0) {
361
483
  utils_1.logger.debug(`[ResponseModal] Pre-extracted data:`, extractedData);
362
- // Requirement 3.3: Merge pre-extracted data into session before batch determination
484
+ // Merge pre-extracted data into session before step selection
363
485
  updatedSession = (0, utils_1.mergeCollected)(updatedSession, extractedData);
364
486
  // Also update agent's collected data
365
487
  await this.agent.updateCollectedData(extractedData);
366
488
  }
367
489
  }
368
490
  }
369
- // BATCH DETERMINATION: Use BatchExecutor to determine which steps can execute together
370
- // Requirement 3.4: Pre-extraction results affect batch determination
371
- let batchSteps;
372
- let batchStoppedReason;
373
- let batchStoppedAtStep;
374
- if (routingResult.selectedRoute && !isRouteComplete) {
375
- // Determine current step position for batch determination
376
- const currentStep = routingResult.selectedStep ||
377
- (updatedSession.currentStep ? routingResult.selectedRoute.getStep(updatedSession.currentStep.id) : undefined);
378
- utils_1.logger.debug(`[ResponseModal] Determining batch starting from step: ${currentStep?.id || 'initial'}`);
379
- const batchResult = await this.batchExecutor.determineBatch({
380
- route: routingResult.selectedRoute,
381
- currentStep,
382
- sessionData: updatedSession.data || {},
383
- context: params.context,
384
- maxSteps: this.agent.getAgentOptions().maxStepsPerBatch,
385
- });
386
- batchSteps = batchResult.steps;
387
- batchStoppedReason = batchResult.stoppedReason;
388
- batchStoppedAtStep = batchResult.stoppedAtStep;
389
- utils_1.logger.debug(`[ResponseModal] Batch determined: ${batchSteps.length} steps, stopped reason: ${batchStoppedReason}`);
390
- }
391
491
  // Determine next step using pipeline method for consistency
392
492
  const stepResult = await this.responsePipeline.determineNextStep({
393
- selectedRoute: routingResult.selectedRoute,
493
+ selectedFlow: routingResult.selectedFlow,
394
494
  selectedStep: routingResult.selectedStep,
395
495
  session: updatedSession, // Use updated session with pre-extracted data
396
- isRouteComplete, // Use updated completion status
496
+ isFlowComplete, // Use updated completion status
397
497
  });
398
498
  return {
399
- selectedRoute: routingResult.selectedRoute,
499
+ selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
400
500
  selectedStep: stepResult.nextStep, // Use the determined next step
401
501
  responseDirectives: routingResult.responseDirectives,
402
502
  session: stepResult.session,
403
- isRouteComplete, // Use updated completion status
404
- batchSteps,
405
- batchStoppedReason,
406
- batchStoppedAtStep,
503
+ // If a branch changed the flow, the original isFlowComplete no longer applies
504
+ isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
407
505
  };
408
506
  }
409
507
  catch (error) {
@@ -411,31 +509,219 @@ class ResponseModal {
411
509
  }
412
510
  }
413
511
  /**
414
- * Check if a route should pre-extract data before determining the initial step
512
+ * Apply a signal's position directive (goTo, goToStep, complete, abort, reset).
513
+ * Discards routing result and uses the signal's position decision.
415
514
  * @private
515
+ * @requirements 8.3
416
516
  */
417
- shouldPreExtractData(route) {
418
- // Pre-extract if route has declared required or optional fields
419
- if (route.requiredFields && route.requiredFields.length > 0) {
517
+ applySignalPositionDirective(signalResult, _params) {
518
+ const directive = signalResult.mergedDirective;
519
+ let session = signalResult.updatedSession;
520
+ const flows = this.agent.getFlows();
521
+ let selectedFlow;
522
+ let selectedStep;
523
+ let isFlowComplete = false;
524
+ // Apply data updates if present alongside position
525
+ if (directive.dataUpdate) {
526
+ session = (0, utils_1.mergeCollected)(session, directive.dataUpdate);
527
+ }
528
+ if (directive.goTo) {
529
+ const flowTarget = typeof directive.goTo === 'string'
530
+ ? directive.goTo
531
+ : directive.goTo.flow ?? directive.goTo.step;
532
+ if (flowTarget) {
533
+ const targetFlow = flows.find(f => f.id === flowTarget || f.title === flowTarget);
534
+ if (targetFlow) {
535
+ session = (0, utils_1.enterFlow)(session, targetFlow.id, targetFlow.title);
536
+ selectedFlow = targetFlow;
537
+ if (typeof directive.goTo === 'object' && directive.goTo.step) {
538
+ const targetStep = targetFlow.getStep(directive.goTo.step);
539
+ if (targetStep) {
540
+ session = (0, utils_1.enterStep)(session, targetStep.id, targetStep.description);
541
+ selectedStep = targetStep;
542
+ }
543
+ }
544
+ }
545
+ else {
546
+ utils_1.logger.warn(`[Signals] Pre-phase goTo target not found: "${flowTarget}". Falling back to no flow.`);
547
+ }
548
+ }
549
+ }
550
+ else if (directive.goToStep) {
551
+ const stepTarget = typeof directive.goToStep === 'string'
552
+ ? directive.goToStep
553
+ : directive.goToStep.step;
554
+ const flowTarget = typeof directive.goToStep === 'object'
555
+ ? directive.goToStep.flow
556
+ : undefined;
557
+ if (flowTarget) {
558
+ const targetFlow = flows.find(f => f.id === flowTarget || f.title === flowTarget);
559
+ if (targetFlow) {
560
+ session = (0, utils_1.enterFlow)(session, targetFlow.id, targetFlow.title);
561
+ selectedFlow = targetFlow;
562
+ const targetStep = targetFlow.getStep(stepTarget);
563
+ if (targetStep) {
564
+ session = (0, utils_1.enterStep)(session, targetStep.id, targetStep.description);
565
+ selectedStep = targetStep;
566
+ }
567
+ }
568
+ }
569
+ else if (session.currentFlow) {
570
+ const currentFlow = flows.find(f => f.id === session.currentFlow?.id);
571
+ if (currentFlow) {
572
+ selectedFlow = currentFlow;
573
+ const targetStep = currentFlow.getStep(stepTarget);
574
+ if (targetStep) {
575
+ session = (0, utils_1.enterStep)(session, targetStep.id, targetStep.description);
576
+ selectedStep = targetStep;
577
+ }
578
+ }
579
+ }
580
+ }
581
+ else if (directive.complete) {
582
+ isFlowComplete = true;
583
+ }
584
+ else if (directive.abort) {
585
+ // Abort — no flow, session cleared or marked
586
+ isFlowComplete = true;
587
+ }
588
+ else if (directive.reset) {
589
+ if (session.currentFlow) {
590
+ const currentFlow = flows.find(f => f.id === session.currentFlow?.id);
591
+ if (currentFlow) {
592
+ selectedFlow = currentFlow;
593
+ const resetStep = typeof directive.reset === 'object' && directive.reset.step
594
+ ? directive.reset.step
595
+ : undefined;
596
+ if (resetStep) {
597
+ const targetStep = currentFlow.getStep(resetStep);
598
+ if (targetStep) {
599
+ session = (0, utils_1.enterStep)(session, targetStep.id, targetStep.description);
600
+ selectedStep = targetStep;
601
+ }
602
+ }
603
+ else {
604
+ const initialStep = currentFlow.initialStep;
605
+ session = (0, utils_1.enterStep)(session, initialStep.id, initialStep.description);
606
+ selectedStep = initialStep;
607
+ }
608
+ }
609
+ }
610
+ }
611
+ return {
612
+ selectedFlow,
613
+ selectedStep,
614
+ session,
615
+ isFlowComplete,
616
+ signalFirings: signalResult.firings,
617
+ signalPreDirective: signalResult.mergedDirective || undefined,
618
+ };
619
+ }
620
+ /**
621
+ * Routing skip optimization (Requirements 20.1, 20.2, 20.3):
622
+ * When the current step declares `collect` fields AND pre-extraction populates
623
+ * at least one of those fields from the user's message, skip routing for this turn.
624
+ *
625
+ * Returns the routing result if the skip applies, or undefined to fall through
626
+ * to normal routing.
627
+ * @private
628
+ */
629
+ async attemptRoutingSkipForCollect(params) {
630
+ const { session } = params;
631
+ // Only applies when we already have a current flow and step
632
+ if (!session.currentFlow || !session.currentStep) {
633
+ return undefined;
634
+ }
635
+ // Also skip this optimization if there's a pending directive (it takes priority)
636
+ if (session.pendingDirective) {
637
+ return undefined;
638
+ }
639
+ // Look up the actual Flow and Step objects to access `collect`
640
+ const currentFlow = this.agent.getFlows().find((f) => f.id === session.currentFlow?.id);
641
+ if (!currentFlow) {
642
+ return undefined;
643
+ }
644
+ const currentStep = currentFlow.getStep(session.currentStep.id);
645
+ if (!currentStep || !currentStep.collect || currentStep.collect.length === 0) {
646
+ return undefined;
647
+ }
648
+ // We have a step with collect fields. Run pre-extraction to see if the
649
+ // user's message populates any of them.
650
+ const collectFields = currentStep.collect;
651
+ // Snapshot current data for comparison
652
+ const dataBefore = { ...session.data };
653
+ // Run pre-extraction against the current flow
654
+ const extractedData = await this.preExtractFlowData({
655
+ route: currentFlow,
656
+ history: params.history,
657
+ context: params.context,
658
+ session,
659
+ signal: params.signal,
660
+ });
661
+ if (!extractedData || Object.keys(extractedData).length === 0) {
662
+ return undefined;
663
+ }
664
+ // Determine which collect fields were newly populated by pre-extraction
665
+ const populatedCollectFields = [];
666
+ for (const field of collectFields) {
667
+ const key = field;
668
+ const hadValue = dataBefore[field] !== undefined && dataBefore[field] !== null;
669
+ const hasNewValue = extractedData[field] !== undefined && extractedData[field] !== null;
670
+ if (hasNewValue && !hadValue) {
671
+ populatedCollectFields.push(key);
672
+ }
673
+ }
674
+ if (populatedCollectFields.length === 0) {
675
+ // Pre-extraction didn't populate any declared collect field — no skip
676
+ return undefined;
677
+ }
678
+ // ROUTING SKIP: pre-extraction populated collect fields → retain current flow/step
679
+ utils_1.logger.debug(`[ResponseModal] Routing skip: pre-extraction populated collect fields [${populatedCollectFields.join(', ')}] for step "${currentStep.id}" — skipping FlowRouter`);
680
+ // Merge extracted data into session
681
+ const updatedSession = (0, utils_1.mergeCollected)(session, extractedData);
682
+ await this.agent.updateCollectedData(extractedData);
683
+ // Determine next step using pipeline method for consistency
684
+ // Pass the current flow/step as the routing result (retained)
685
+ const stepResult = await this.responsePipeline.determineNextStep({
686
+ selectedFlow: currentFlow,
687
+ selectedStep: currentStep,
688
+ session: updatedSession,
689
+ isFlowComplete: false,
690
+ });
691
+ return {
692
+ selectedFlow: stepResult.flowChanged || currentFlow,
693
+ selectedStep: stepResult.nextStep,
694
+ responseDirectives: undefined,
695
+ session: stepResult.session,
696
+ isFlowComplete: stepResult.flowChanged ? false : false,
697
+ };
698
+ }
699
+ /**
700
+ * Check if a flow should pre-extract data before determining the initial step
701
+ * @private
702
+ */
703
+ shouldPreExtractData(flow) {
704
+ // Pre-extract if flow has declared required or optional fields
705
+ if (flow.requiredFields && flow.requiredFields.length > 0) {
420
706
  return true;
421
707
  }
422
- if (route.optionalFields && route.optionalFields.length > 0) {
708
+ if (flow.optionalFields && flow.optionalFields.length > 0) {
423
709
  return true;
424
710
  }
425
- // Pre-extract if any step in the route collects data
426
- const steps = route.getAllSteps();
711
+ // Pre-extract if any step in the flow collects data
712
+ const steps = flow.getAllSteps();
427
713
  const hasDataCollectionSteps = steps.some(step => step.collect && step.collect.length > 0);
428
714
  return hasDataCollectionSteps;
429
715
  }
430
716
  /**
431
- * Pre-extract data from user message when entering a route
717
+ * Pre-extract data from user message when entering a flow
432
718
  * This allows skipping steps whose data is already provided
433
719
  * @private
434
720
  */
435
- async preExtractRouteData(params) {
436
- const { route, history, signal } = params;
437
- // Build a schema for data extraction based on route's fields
438
- const extractionSchema = this.agent.getSchema();
721
+ async preExtractFlowData(params) {
722
+ const { route: flow, history, signal } = params;
723
+ // Build a schema for data extraction based on flow's fields
724
+ const extractionSchema = this.agent.schema;
439
725
  if (!extractionSchema) {
440
726
  utils_1.logger.warn(`[ResponseModal] No schema available for pre-extraction`);
441
727
  return {};
@@ -452,11 +738,11 @@ class ResponseModal {
452
738
  `Extract data for these fields if present:`,
453
739
  ];
454
740
  // Add field descriptions
455
- if (route.requiredFields) {
456
- extractionPrompt.push(`Required fields: ${route.requiredFields.join(', ')}`);
741
+ if (flow.requiredFields) {
742
+ extractionPrompt.push(`Required fields: ${flow.requiredFields.join(', ')}`);
457
743
  }
458
- if (route.optionalFields) {
459
- extractionPrompt.push(`Optional fields: ${route.optionalFields.join(', ')}`);
744
+ if (flow.optionalFields) {
745
+ extractionPrompt.push(`Optional fields: ${flow.optionalFields.join(', ')}`);
460
746
  }
461
747
  extractionPrompt.push(``, `Return ONLY the extracted data as JSON. If no data can be extracted, return an empty object {}.`);
462
748
  // Convert Event[] to HistoryItem[] for provider call
@@ -489,98 +775,176 @@ class ResponseModal {
489
775
  * @private
490
776
  */
491
777
  async generateUnifiedResponse(responseContext) {
492
- const { effectiveContext, session: initialSession, history, selectedRoute, selectedStep, responseDirectives, isRouteComplete, batchSteps, batchStoppedReason, signal, } = responseContext;
778
+ const { effectiveContext, session: initialSession, history, selectedFlow, selectedStep, responseDirectives, isFlowComplete, signal, signalFirings: preSignalFirings, signalPreDirective, signalHalted, signalHaltReply, } = responseContext;
493
779
  let session = initialSession;
494
- // Get last user message (needed for both route and completion handling)
780
+ // Accumulator for signal firings across both phases (fire order)
781
+ const signalFirings = [...(preSignalFirings || [])];
782
+ // Get last user message (needed for both flow and completion handling)
495
783
  // Convert HistoryItem[] to Event[] for internal processing
496
784
  const historyEvents = (0, utils_1.historyToEvents)(history);
785
+ // ── SIGNAL HALT (Requirement 8.2) ─────────────────────────────────────
786
+ // Pre-signal phase emitted halt → skip LLM call entirely.
787
+ if (signalHalted) {
788
+ const haltMessage = signalHaltReply || '';
789
+ // Run post-signal phase even on halt (post-phase sees complete turn context)
790
+ const postResult = await this.responsePipeline.runPostSignalPhase(session, effectiveContext, historyEvents);
791
+ session = postResult.updatedSession;
792
+ signalFirings.push(...postResult.firings);
793
+ // Apply post-phase position directive as pendingDirective (Requirement 9.3)
794
+ if (postResult.mergedDirective && (0, ResponsePipeline_1.hasDirectivePositionField)(postResult.mergedDirective)) {
795
+ session = { ...session, pendingDirective: postResult.mergedDirective };
796
+ }
797
+ await this.finalizeSession(session, effectiveContext);
798
+ return {
799
+ message: haltMessage,
800
+ session,
801
+ toolCalls: undefined,
802
+ isFlowComplete: false,
803
+ executedSteps: [],
804
+ stoppedReason: signalHaltReply ? 'reply' : 'halt',
805
+ triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
806
+ };
807
+ }
497
808
  let message;
498
809
  let toolCalls = undefined;
499
810
  let executedSteps;
500
811
  let stoppedReason;
501
- if (selectedRoute && !isRouteComplete) {
502
- // Check if we have batch steps to execute
503
- if (batchSteps && batchSteps.length > 0) {
504
- // BATCH EXECUTION: Execute multiple steps in a single LLM call
505
- utils_1.logger.debug(`[ResponseModal] Executing batch of ${batchSteps.length} steps`);
506
- const batchResult = await this.executeBatchResponse({
507
- selectedRoute,
508
- batchSteps,
509
- responseDirectives,
510
- session,
511
- history,
512
- context: effectiveContext,
513
- historyEvents,
812
+ let appliedInstructions;
813
+ if (selectedFlow && !isFlowComplete) {
814
+ // AUTO-CHAIN: Walk consecutive auto-steps before any LLM work.
815
+ // If the current step is auto, the executor advances through it (and any
816
+ // subsequent auto-steps) until an interactive step or terminal condition.
817
+ let resolvedStep = selectedStep;
818
+ const currentStepInstance = session.currentStep
819
+ ? selectedFlow.getStep(session.currentStep.id)
820
+ : selectedStep;
821
+ if (currentStepInstance?.auto) {
822
+ const autoChainExecutor = new AutoChainExecutor_1.AutoChainExecutor({
823
+ maxAutoStepsPerTurn: this.agent.maxAutoStepsPerTurn,
514
824
  });
515
- message = batchResult.message;
516
- toolCalls = batchResult.toolCalls;
517
- session = batchResult.session;
518
- executedSteps = batchResult.executedSteps;
519
- stoppedReason = batchStoppedReason;
520
- }
521
- else {
522
- // SINGLE STEP EXECUTION: Fall back to single-step processing
523
- // This happens when batch determination returns empty (first step needs input)
524
- const result = await this.processRouteResponse({
525
- selectedRoute,
526
- selectedStep,
527
- responseDirectives,
825
+ const autoResult = await autoChainExecutor.run({
528
826
  session,
529
- history,
530
827
  context: effectiveContext,
531
- historyEvents,
532
- signal,
828
+ flow: selectedFlow,
533
829
  });
534
- message = result.message;
535
- toolCalls = result.toolCalls;
536
- session = result.session;
537
- // Track executed step for single-step execution
538
- if (selectedStep) {
539
- executedSteps = [{
540
- id: selectedStep.id,
541
- routeId: selectedRoute.id,
542
- }];
830
+ session = autoResult.session;
831
+ // Handle halt: emit verbatim reply, persist, return — no LLM call.
832
+ if (autoResult.stoppedReason === 'halt') {
833
+ message = autoResult.mergedDirective?.reply || '';
834
+ stoppedReason = 'halt';
835
+ executedSteps = [];
836
+ await this.finalizeSession(session, effectiveContext);
837
+ return {
838
+ message,
839
+ session,
840
+ toolCalls: undefined,
841
+ isFlowComplete: false,
842
+ executedSteps,
843
+ stoppedReason,
844
+ };
543
845
  }
544
- stoppedReason = batchStoppedReason || 'needs_input';
545
- }
546
- }
547
- else if (isRouteComplete && selectedRoute) {
548
- // Handle route completion
549
- utils_1.logger.debug(`[ResponseModal] Generating completion message for route: ${selectedRoute.title}`);
550
- try {
551
- message = await this.handleRouteCompletion({
552
- selectedRoute,
553
- session,
554
- context: effectiveContext,
555
- history,
556
- historyEvents,
557
- signal,
558
- });
559
- // Set step to END_ROUTE marker
560
- session = (0, utils_1.enterStep)(session, constants_1.END_ROUTE_ID, "Route completed");
561
- stoppedReason = 'route_complete';
562
- utils_1.logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`);
846
+ // Handle flow completion or cross-flow redirect from auto-chain.
847
+ // The auto-chain ended without resolving to an interactive step.
848
+ // Possible reasons: last_step (no successor), completed (explicit
849
+ // complete directive), or goto (cross-flow redirect).
850
+ if (autoResult.stoppedReason === 'last_step' || autoResult.stoppedReason === 'completed' || autoResult.stoppedReason === 'goto') {
851
+ utils_1.logger.debug(`[ResponseModal] Auto-chain ended with ${autoResult.stoppedReason}`);
852
+ session = await this.applyFlowCompletion({
853
+ selectedFlow,
854
+ session,
855
+ context: effectiveContext,
856
+ history,
857
+ });
858
+ await this.finalizeSession(session, effectiveContext);
859
+ return {
860
+ message: '',
861
+ session,
862
+ toolCalls: undefined,
863
+ isFlowComplete: true,
864
+ executedSteps: [],
865
+ stoppedReason: autoResult.stoppedReason,
866
+ };
867
+ }
868
+ // Normal case: auto-chain resolved to an interactive step.
869
+ resolvedStep = autoResult.resolvedStep;
563
870
  }
564
- catch (error) {
565
- utils_1.logger.error(`[ResponseModal] Error generating completion message:`, error);
566
- // Fallback to simple completion message
567
- message = `Thank you! I've recorded all the information for your ${selectedRoute.title.toLowerCase()}.`;
568
- session = (0, utils_1.enterStep)(session, constants_1.END_ROUTE_ID, "Route completed");
569
- stoppedReason = 'route_complete';
871
+ // SINGLE STEP EXECUTION: Process the resolved interactive step.
872
+ // The auto-chain (if it ran) already walked auto-steps. Only the
873
+ // interactive step remains for the LLM call.
874
+ const result = await this.processFlowResponse({
875
+ selectedFlow,
876
+ selectedStep: resolvedStep,
877
+ responseDirectives,
878
+ session,
879
+ history,
880
+ context: effectiveContext,
881
+ historyEvents,
882
+ signal,
883
+ // Propagate signal pre-directive's appendPrompt for this turn's LLM call (Requirement 8.4)
884
+ transientAppendage: signalPreDirective?.appendPrompt,
885
+ // Merge signal pre-directive (halt/reply/injectTools) into the pre-LLM bus
886
+ mergedPreDirective: signalPreDirective,
887
+ });
888
+ message = result.message;
889
+ toolCalls = result.toolCalls;
890
+ session = result.session;
891
+ appliedInstructions = result.appliedInstructions;
892
+ // Track executed step for single-step execution
893
+ if (resolvedStep) {
894
+ executedSteps = [{
895
+ id: resolvedStep.id,
896
+ flowId: selectedFlow.id,
897
+ }];
570
898
  }
899
+ // Use stoppedReason from processFlowResponse if set (halt/reply),
900
+ // otherwise default to 'needs_input' for normal LLM responses.
901
+ stoppedReason = result.stoppedReason || 'needs_input';
902
+ }
903
+ else if (isFlowComplete && selectedFlow) {
904
+ // Flow completion path: pure state transition, no LLM call.
905
+ // The framework emits no message of its own.
906
+ // stoppedReason is 'last_step' because this completion was detected by
907
+ // implicit terminus (no successor or all successors skipped), not by an
908
+ // explicit `complete` directive.
909
+ utils_1.logger.debug(`[ResponseModal] Releasing session to idle for completed flow: ${selectedFlow.title}`);
910
+ session = await this.applyFlowCompletion({
911
+ selectedFlow,
912
+ session,
913
+ context: effectiveContext,
914
+ history,
915
+ });
916
+ message = '';
917
+ stoppedReason = 'last_step';
918
+ executedSteps = [];
571
919
  }
572
920
  else {
573
- // Fallback: No routes defined, generate a simple response
574
- message = await this.generateFallbackResponse({
921
+ // Fallback: No flows defined, generate a simple response
922
+ const fallbackResult = await this.generateFallbackResponse({
575
923
  history,
576
924
  context: effectiveContext,
577
925
  session,
578
926
  });
927
+ message = fallbackResult.message;
928
+ appliedInstructions = fallbackResult.appliedInstructions;
579
929
  // For fallback responses, set empty executedSteps and no stoppedReason
580
- // since there's no route/step execution happening
930
+ // since there's no flow/step execution happening
581
931
  executedSteps = [];
582
932
  stoppedReason = undefined;
583
933
  }
934
+ // POST-SIGNAL PHASE (Requirement 9.1, 9.2, 9.3, 9.4)
935
+ // Runs after finalize/onComplete and before session persistence.
936
+ // Post-phase signals see the complete turn result: assistant message in
937
+ // history, collected data, tool results.
938
+ const postResult = await this.responsePipeline.runPostSignalPhase(session, effectiveContext, historyEvents);
939
+ session = postResult.updatedSession;
940
+ // Append post-phase firings to the accumulator (preserves fire order)
941
+ signalFirings.push(...postResult.firings);
942
+ // Requirement 9.3: Post-phase position directive sets session.pendingDirective
943
+ // (no mid-turn re-entry per D6 decision). Pre-LLM-only fields are already
944
+ // dropped inside runPostSignalPhase per Phase 4.5.
945
+ if (postResult.mergedDirective && (0, ResponsePipeline_1.hasDirectivePositionField)(postResult.mergedDirective)) {
946
+ session = { ...session, pendingDirective: postResult.mergedDirective };
947
+ }
584
948
  // Ensure response structure completeness (Requirement 8.1, 8.2, 8.3)
585
949
  // - executedSteps: array of steps executed (empty array if none)
586
950
  // - stoppedReason: why execution stopped (undefined for fallback)
@@ -589,400 +953,30 @@ class ResponseModal {
589
953
  message,
590
954
  session,
591
955
  toolCalls,
592
- isRouteComplete,
593
- executedSteps: executedSteps || [],
594
- stoppedReason,
595
- };
596
- }
597
- /**
598
- * Execute a batch of steps with a single LLM call
599
- *
600
- * This method:
601
- * 1. Executes all prepare hooks for steps in the batch (in order)
602
- * 2. Builds a combined prompt using BatchPromptBuilder
603
- * 3. Makes a single LLM call
604
- * 4. Collects data from the response for all steps
605
- * 5. Executes all finalize hooks for steps in the batch (in order)
606
- *
607
- * @private
608
- * **Validates: Requirements 1.1, 4.4, 5.1, 5.2**
609
- */
610
- async executeBatchResponse(params) {
611
- const { selectedRoute, batchSteps, history, context, historyEvents, signal } = params;
612
- let session = params.session;
613
- utils_1.logger.debug(`[ResponseModal] Starting batch execution for ${batchSteps.length} steps`);
614
- // Create hook executor function
615
- const executeHook = async (hook, hookContext, data, step) => {
616
- // Find the route for this step
617
- const route = selectedRoute;
618
- // Convert StepOptions to Step if needed for executePrepareFinalize
619
- const stepInstance = step?.id ? route.getStep(step.id) : undefined;
620
- await this.executePrepareFinalize(hook, hookContext, data, route, stepInstance);
621
- };
622
- // PHASE 1: Execute all prepare hooks (Requirement 5.1)
623
- utils_1.logger.debug(`[ResponseModal] Executing prepare hooks for batch`);
624
- const prepareResult = await this.batchExecutor.executePrepareHooks({
625
- steps: batchSteps,
626
- context,
627
- data: session.data,
628
- executeHook,
629
- });
630
- if (!prepareResult.success) {
631
- // Prepare hook failed - return error response
632
- utils_1.logger.error(`[ResponseModal] Prepare hook failed:`, prepareResult.error);
633
- throw new ResponseGenerationError(`Prepare hook failed: ${prepareResult.error?.message}`, {
634
- phase: 'prepare_hooks',
635
- context: {
636
- stepId: prepareResult.error?.stepId,
637
- executedSteps: prepareResult.executedSteps,
638
- }
639
- });
640
- }
641
- // PHASE 2: Build combined prompt using BatchPromptBuilder (Requirement 4.4)
642
- utils_1.logger.debug(`[ResponseModal] Building batch prompt`);
643
- const batchPromptResult = await this.batchPromptBuilder.buildBatchPrompt({
644
- steps: batchSteps,
645
- route: selectedRoute,
646
- history: historyEvents,
647
- context,
648
- session,
649
- agentOptions: this.agent.getAgentOptions(),
650
- });
651
- utils_1.logger.debug(`[ResponseModal] Batch prompt built with ${batchPromptResult.stepCount} steps, collecting: ${batchPromptResult.collectFields.join(', ')}`);
652
- // Build response schema for batch (includes all collect fields)
653
- const responseSchema = this.buildBatchResponseSchema(batchPromptResult.collectFields);
654
- // Collect available tools for AI (from all steps in batch)
655
- const availableTools = this.collectBatchAvailableTools(selectedRoute, batchSteps);
656
- // PHASE 3: Make single LLM call (Requirement 4.4)
657
- utils_1.logger.debug(`[ResponseModal] Making LLM call for batch`);
658
- const agentOptions = this.agent.getAgentOptions();
659
- const result = await agentOptions.provider.generateMessage({
660
- prompt: batchPromptResult.prompt,
661
- history, // Use HistoryItem[] for AI provider
662
- context,
663
- tools: availableTools,
664
- signal,
665
- parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "batch_response" } : undefined,
666
- });
667
- let message = result.structured?.message || result.message;
668
- let toolCalls = result.structured?.toolCalls;
669
- utils_1.logger.debug(`[ResponseModal] LLM response received for batch`);
670
- // Execute tools if any
671
- if (toolCalls && toolCalls.length > 0) {
672
- const toolResult = await this.executeUnifiedToolLoop({
673
- toolCalls,
674
- context,
675
- session,
676
- history,
677
- selectedRoute,
678
- responsePrompt: batchPromptResult.prompt,
679
- availableTools,
680
- responseSchema,
681
- signal,
682
- });
683
- session = toolResult.session;
684
- toolCalls = toolResult.finalToolCalls;
685
- if (toolResult.finalMessage) {
686
- message = toolResult.finalMessage;
687
- }
688
- }
689
- // PHASE 4: Collect data from response for all steps (Requirement 6.1, 6.2, 6.3)
690
- utils_1.logger.debug(`[ResponseModal] Collecting batch data`);
691
- const collectResult = this.batchExecutor.collectBatchData({
692
- steps: batchSteps,
693
- llmResponse: result.structured || {},
694
- session,
695
- schema: this.agent.getSchema(),
696
- });
697
- session = collectResult.session;
698
- if (collectResult.collectedData && Object.keys(collectResult.collectedData).length > 0) {
699
- // Update agent's collected data
700
- await this.agent.updateCollectedData(collectResult.collectedData);
701
- utils_1.logger.debug(`[ResponseModal] Batch collected data:`, collectResult.collectedData);
702
- }
703
- if (collectResult.validationErrors && collectResult.validationErrors.length > 0) {
704
- utils_1.logger.warn(`[ResponseModal] Batch data validation errors:`, collectResult.validationErrors);
705
- }
706
- // Update session to final step position
707
- const lastStep = batchSteps[batchSteps.length - 1];
708
- if (lastStep?.id) {
709
- session = (0, utils_1.enterStep)(session, lastStep.id, lastStep.description);
710
- utils_1.logger.debug(`[ResponseModal] Updated session to final batch step: ${lastStep.id}`);
711
- }
712
- // PHASE 5: Execute all finalize hooks (Requirement 5.2)
713
- utils_1.logger.debug(`[ResponseModal] Executing finalize hooks for batch`);
714
- const finalizeResult = await this.batchExecutor.executeFinalizeHooks({
715
- steps: batchSteps,
716
- context,
717
- data: session.data,
718
- executeHook,
719
- });
720
- if (finalizeResult.errors && finalizeResult.errors.length > 0) {
721
- // Log finalize errors but don't fail (Requirement 5.5)
722
- utils_1.logger.warn(`[ResponseModal] Some finalize hooks failed:`, finalizeResult.errors);
723
- }
724
- // Build executed steps list
725
- const executedSteps = batchSteps
726
- .filter(step => step.id)
727
- .map(step => ({
728
- id: step.id,
729
- routeId: selectedRoute.id,
730
- }));
731
- utils_1.logger.debug(`[ResponseModal] Batch execution complete. Executed ${executedSteps.length} steps`);
732
- return {
733
- message,
734
- toolCalls,
735
- session,
736
- executedSteps,
737
- };
738
- }
739
- /**
740
- * Build response schema for batch execution
741
- * @private
742
- */
743
- buildBatchResponseSchema(collectFields) {
744
- const properties = {
745
- message: {
746
- type: "string",
747
- description: "Natural, conversational response directed at the user. Must NOT contain field names, raw data, or internal information.",
748
- },
749
- };
750
- const agentSchema = this.agent.getSchema();
751
- // Add collect fields to schema, using agent schema definitions when available
752
- for (const field of collectFields) {
753
- if (agentSchema?.properties && agentSchema.properties[field]) {
754
- properties[field] = agentSchema.properties[field];
755
- }
756
- else {
757
- // Dynamic fallback when no agent schema is defined
758
- properties[field] = {
759
- type: "string",
760
- description: `Collected value for ${field}`,
761
- };
762
- }
763
- }
764
- return {
765
- type: "object",
766
- properties,
767
- required: ["message"],
768
- additionalProperties: true,
769
- };
770
- }
771
- /**
772
- * Collect available tools from all steps in the batch
773
- * @private
774
- */
775
- collectBatchAvailableTools(route, batchSteps) {
776
- const availableTools = new Map();
777
- // Add agent-level tools
778
- this.agent.getTools().forEach((tool) => {
779
- availableTools.set(tool.id, tool);
780
- });
781
- // Add route-level tools
782
- route.getTools().forEach((tool) => {
783
- availableTools.set(tool.id, tool);
784
- });
785
- // Add step-level tools from all batch steps
786
- for (const step of batchSteps) {
787
- if (step.tools) {
788
- for (const toolRef of step.tools) {
789
- if (typeof toolRef === "string") {
790
- // Reference to registered tool - already in availableTools
791
- }
792
- else if (typeof toolRef === 'object' && 'id' in toolRef && toolRef.id) {
793
- // Inline tool definition
794
- availableTools.set(toolRef.id, toolRef);
795
- }
796
- }
797
- }
798
- }
799
- // Convert to the format expected by AI providers
800
- return Array.from(availableTools.values()).map((tool) => ({
801
- id: tool.id,
802
- name: tool.name || tool.id,
803
- description: tool.description,
804
- parameters: tool.parameters,
805
- }));
806
- }
807
- /**
808
- * Unified streaming response generation
809
- * @private
810
- */
811
- async *generateUnifiedStreamingResponse(responseContext) {
812
- const { effectiveContext, session: initialSession, history, selectedRoute, selectedStep, responseDirectives, isRouteComplete, batchSteps, batchStoppedReason, } = responseContext;
813
- const session = initialSession;
814
- // Get last user message (needed for both route and completion handling)
815
- // Convert HistoryItem[] to Event[] for internal processing
816
- const historyEvents = (0, utils_1.historyToEvents)(history);
817
- if (selectedRoute && !isRouteComplete) {
818
- // Check if we have batch steps to execute
819
- if (batchSteps && batchSteps.length > 0) {
820
- // BATCH EXECUTION: Execute multiple steps with streaming
821
- // Note: For streaming, we still use batch execution but stream the response
822
- utils_1.logger.debug(`[ResponseModal] Streaming batch execution for ${batchSteps.length} steps`);
823
- yield* this.streamBatchResponse({
824
- selectedRoute,
825
- batchSteps,
826
- responseDirectives,
827
- session,
828
- history,
829
- context: effectiveContext,
830
- historyEvents,
831
- batchStoppedReason,
832
- });
833
- }
834
- else {
835
- // SINGLE STEP EXECUTION: Fall back to single-step streaming
836
- yield* this.processRouteStreamingResponse({
837
- selectedRoute,
838
- selectedStep,
839
- responseDirectives,
840
- session,
841
- history,
842
- context: effectiveContext,
843
- historyEvents,
844
- });
845
- }
846
- }
847
- else if (isRouteComplete && selectedRoute) {
848
- // Handle route completion streaming
849
- yield* this.streamRouteCompletion({
850
- selectedRoute,
851
- session,
852
- context: effectiveContext,
853
- history,
854
- historyEvents,
855
- });
856
- }
857
- else {
858
- // Fallback: No routes defined, stream a simple response
859
- yield* this.streamFallbackResponse({
860
- history,
861
- context: effectiveContext,
862
- session,
863
- });
864
- }
865
- }
866
- /**
867
- * Stream a batch response with multiple steps
868
- *
869
- * Similar to executeBatchResponse but streams the LLM response.
870
- *
871
- * @private
872
- */
873
- async *streamBatchResponse(params) {
874
- const { selectedRoute, batchSteps, history, context, historyEvents, batchStoppedReason, signal } = params;
875
- let session = params.session;
876
- // Create hook executor function
877
- const executeHook = async (hook, hookContext, data, step) => {
878
- const route = selectedRoute;
879
- const stepInstance = step?.id ? route.getStep(step.id) : undefined;
880
- await this.executePrepareFinalize(hook, hookContext, data, route, stepInstance);
881
- };
882
- // PHASE 1: Execute all prepare hooks
883
- const prepareResult = await this.batchExecutor.executePrepareHooks({
884
- steps: batchSteps,
885
- context,
886
- data: session.data,
887
- executeHook,
888
- });
889
- if (!prepareResult.success) {
890
- // Yield error chunk
891
- yield {
892
- delta: "",
893
- accumulated: "",
894
- done: true,
895
- session,
896
- error: new ResponseGenerationError(`Prepare hook failed: ${prepareResult.error?.message}`, { phase: 'prepare_hooks' }),
897
- };
898
- return;
899
- }
900
- // PHASE 2: Build combined prompt
901
- const batchPromptResult = await this.batchPromptBuilder.buildBatchPrompt({
902
- steps: batchSteps,
903
- route: selectedRoute,
904
- history: historyEvents,
905
- context,
906
- session,
907
- agentOptions: this.agent.getAgentOptions(),
908
- });
909
- const responseSchema = this.buildBatchResponseSchema(batchPromptResult.collectFields);
910
- const availableTools = this.collectBatchAvailableTools(selectedRoute, batchSteps);
911
- // PHASE 3: Stream LLM response
912
- const agentOptions = this.agent.getAgentOptions();
913
- const stream = agentOptions.provider.generateMessageStream({
914
- prompt: batchPromptResult.prompt,
915
- history, // Use HistoryItem[] for AI provider
916
- context,
917
- tools: availableTools,
918
- signal,
919
- parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "batch_stream_response" } : undefined,
920
- });
921
- // Build executed steps list
922
- const executedSteps = batchSteps
923
- .filter(step => step.id)
924
- .map(step => ({
925
- id: step.id,
926
- routeId: selectedRoute.id,
927
- }));
928
- // Stream chunks
929
- for await (const chunk of stream) {
930
- // On final chunk, collect data and execute finalize hooks
931
- if (chunk.done) {
932
- // Collect data from response
933
- if (chunk.structured) {
934
- const collectResult = this.batchExecutor.collectBatchData({
935
- steps: batchSteps,
936
- llmResponse: chunk.structured,
937
- session,
938
- schema: this.agent.getSchema(),
939
- });
940
- session = collectResult.session;
941
- if (collectResult.collectedData && Object.keys(collectResult.collectedData).length > 0) {
942
- await this.agent.updateCollectedData(collectResult.collectedData);
943
- }
944
- }
945
- // Update session to final step position
946
- const lastStep = batchSteps[batchSteps.length - 1];
947
- if (lastStep?.id) {
948
- session = (0, utils_1.enterStep)(session, lastStep.id, lastStep.description);
949
- }
950
- // Execute finalize hooks
951
- await this.batchExecutor.executeFinalizeHooks({
952
- steps: batchSteps,
953
- context,
954
- data: session.data,
955
- executeHook,
956
- });
957
- // Finalize session
958
- await this.finalizeSession(session, context);
959
- }
960
- yield {
961
- delta: chunk.delta,
962
- accumulated: chunk.accumulated,
963
- done: chunk.done,
964
- session,
965
- toolCalls: chunk.structured?.toolCalls,
966
- isRouteComplete: false,
967
- executedSteps: chunk.done ? executedSteps : undefined,
968
- stoppedReason: chunk.done ? batchStoppedReason : undefined,
969
- metadata: chunk.metadata,
970
- structured: chunk.structured,
971
- };
972
- }
956
+ isFlowComplete: isFlowComplete,
957
+ executedSteps: executedSteps || [],
958
+ stoppedReason,
959
+ appliedInstructions,
960
+ triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
961
+ };
973
962
  }
974
963
  /**
975
964
  * Execute prepare function for current step if available
976
965
  * @private
977
966
  */
978
967
  async executeStepPrepare(session, context) {
979
- if (session.currentRoute && session.currentStep) {
980
- const currentRoute = this.agent.getRoutes().find((r) => r.id === session.currentRoute?.id);
981
- if (currentRoute) {
982
- const currentStep = currentRoute.getStep(session.currentStep.id);
968
+ if (session.currentFlow && session.currentStep) {
969
+ const currentFlow = this.agent.getFlows().find((r) => r.id === session.currentFlow?.id);
970
+ if (currentFlow) {
971
+ const currentStep = currentFlow.getStep(session.currentStep.id);
972
+ // Skip auto-steps — their prepare is handled by AutoChainExecutor
973
+ if (currentStep?.auto) {
974
+ utils_1.logger.debug(`[ResponseModal] Skipping pre-routing prepare for auto-step: ${currentStep.id}`);
975
+ return;
976
+ }
983
977
  if (currentStep?.prepare) {
984
978
  utils_1.logger.debug(`[ResponseModal] Executing prepare for step: ${currentStep.id}`);
985
- await this.executePrepareFinalize(currentStep.prepare, context, session.data, currentRoute, currentStep);
979
+ await this.executePrepareFinalize(currentStep.prepare, context, session.data, currentFlow, currentStep);
986
980
  }
987
981
  }
988
982
  }
@@ -992,23 +986,23 @@ class ResponseModal {
992
986
  * @private
993
987
  */
994
988
  async executeStepFinalize(session, context) {
995
- if (session.currentRoute && session.currentStep) {
996
- const currentRoute = this.agent.getRoutes().find((r) => r.id === session.currentRoute?.id);
997
- if (currentRoute) {
998
- const currentStep = currentRoute.getStep(session.currentStep.id);
989
+ if (session.currentFlow && session.currentStep) {
990
+ const currentFlow = this.agent.getFlows().find((r) => r.id === session.currentFlow?.id);
991
+ if (currentFlow) {
992
+ const currentStep = currentFlow.getStep(session.currentStep.id);
999
993
  if (currentStep?.finalize) {
1000
994
  utils_1.logger.debug(`[ResponseModal] Executing finalize for step: ${currentStep.id}`);
1001
- await this.executePrepareFinalize(currentStep.finalize, context, session.data, currentRoute, currentStep);
995
+ await this.executePrepareFinalize(currentStep.finalize, context, session.data, currentFlow, currentStep);
1002
996
  }
1003
997
  }
1004
998
  }
1005
999
  }
1006
1000
  /**
1007
- * Process route response with unified tool execution and data collection
1001
+ * Process flow response with unified tool execution and data collection
1008
1002
  * @private
1009
1003
  */
1010
- async processRouteResponse(params) {
1011
- const { selectedRoute, selectedStep, responseDirectives, history, context, historyEvents, signal } = params;
1004
+ async processFlowResponse(params) {
1005
+ const { selectedFlow, selectedStep, responseDirectives, history, context, historyEvents, signal, transientAppendage, mergedPreDirective } = params;
1012
1006
  let session = params.session;
1013
1007
  // Determine next step
1014
1008
  let nextStep;
@@ -1016,25 +1010,42 @@ class ResponseModal {
1016
1010
  nextStep = selectedStep;
1017
1011
  }
1018
1012
  else {
1019
- // Determine current step from session if we're already in this route
1020
- const isInSameRoute = session.currentRoute?.id === selectedRoute.id;
1021
- const currentStep = isInSameRoute && session.currentStep
1022
- ? selectedRoute.getStep(session.currentStep.id)
1013
+ // Determine current step from session if we're already in this flow
1014
+ const isInSameFlow = session.currentFlow?.id === selectedFlow.id;
1015
+ const currentStep = isInSameFlow && session.currentStep
1016
+ ? selectedFlow.getStep(session.currentStep.id)
1023
1017
  : undefined;
1024
- utils_1.logger.debug(`[ResponseModal] Step determination: route match=${isInSameRoute}, currentRoute=${session.currentRoute?.id}, selectedRoute=${selectedRoute.id}, currentStep=${currentStep?.id || 'none'}`);
1025
- // Get candidate steps based on current position in the route
1026
- const routingEngine = this.agent.getRoutingEngine();
1027
- const candidates = await routingEngine.getCandidateStepsWithConditions(selectedRoute, currentStep, // Pass current step instead of undefined to maintain progression
1028
- (0, template_1.createTemplateContext)({ data: session.data, session, context }));
1029
- utils_1.logger.debug(`[ResponseModal] Found ${candidates.length} candidate steps${currentStep ? ' from current step ' + currentStep.id : ' (new route entry)'}`);
1030
- if (candidates.length > 0) {
1031
- nextStep = candidates[0].step;
1032
- utils_1.logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new route'}`);
1018
+ utils_1.logger.debug(`[ResponseModal] Step determination: flow match=${isInSameFlow}, currentFlow=${session.currentFlow?.id}, selectedFlow=${selectedFlow.id}, currentStep=${currentStep?.id || 'none'}`);
1019
+ // STEP 1 (Algorithm 1): branches win over linear chain
1020
+ if (currentStep?.branches && currentStep.branches.length > 0) {
1021
+ const branchResult = await this.responsePipeline.evaluateStepBranches(currentStep, selectedFlow, session, context);
1022
+ if (branchResult) {
1023
+ if (branchResult.nextStep) {
1024
+ nextStep = branchResult.nextStep;
1025
+ session = branchResult.session;
1026
+ }
1027
+ else {
1028
+ // Flow transition or completion — no local step to render
1029
+ // Return empty message with updated session; caller handles flow transition
1030
+ return { message: '', session: branchResult.session };
1031
+ }
1032
+ }
1033
1033
  }
1034
- else {
1035
- // Fallback to initial step even if it should be skipped
1036
- nextStep = selectedRoute.initialStep;
1037
- utils_1.logger.warn(`[ResponseModal] No valid steps found, using initial step: ${nextStep.id}`);
1034
+ if (!nextStep) {
1035
+ // Get candidate steps based on current position in the flow
1036
+ const flowRouter = this.agent.getFlowRouter();
1037
+ const candidates = await flowRouter.getCandidateStepsWithConditions(selectedFlow, currentStep, // Pass current step instead of undefined to maintain progression
1038
+ (0, template_1.createTemplateContext)({ data: session.data, session, context }));
1039
+ utils_1.logger.debug(`[ResponseModal] Found ${candidates.length} candidate steps${currentStep ? ' from current step ' + currentStep.id : ' (new flow entry)'}`);
1040
+ if (candidates.length > 0) {
1041
+ nextStep = candidates[0].step;
1042
+ utils_1.logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
1043
+ }
1044
+ else {
1045
+ // Fallback to initial step even if it should be skipped
1046
+ nextStep = selectedFlow.initialStep;
1047
+ utils_1.logger.warn(`[FlowConfigurationError] No valid steps found: all candidates were skipped in flow. Falling back to initial step "${nextStep.id}". Review step skip conditions.`);
1048
+ }
1038
1049
  }
1039
1050
  }
1040
1051
  // Update session with next step
@@ -1043,14 +1054,14 @@ class ResponseModal {
1043
1054
  const sessionData = session.data || {};
1044
1055
  const missingRequires = nextStep.requires.filter(field => sessionData[String(field)] === undefined);
1045
1056
  if (missingRequires.length > 0) {
1046
- const warning = `[Agent] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
1047
- `missing required fields [${missingRequires.join(', ')}]. Staying at current step.`;
1057
+ const warning = `[FlowConfigurationError] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
1058
+ `missing required fields [${missingRequires.join(', ')}]. Staying at current step. Ensure preceding steps collect these fields.`;
1048
1059
  utils_1.logger.warn(warning);
1049
1060
  console.warn(warning);
1050
1061
  // Stay at the current step - don't enter the next one
1051
1062
  const currentStepId = session.currentStep?.id;
1052
1063
  if (currentStepId) {
1053
- const currentStepInstance = selectedRoute.getStep(currentStepId);
1064
+ const currentStepInstance = selectedFlow.getStep(currentStepId);
1054
1065
  if (currentStepInstance) {
1055
1066
  nextStep = currentStepInstance;
1056
1067
  utils_1.logger.debug(`[ResponseModal] Staying at current step: ${nextStep.id} due to missing requires`);
@@ -1066,76 +1077,271 @@ class ResponseModal {
1066
1077
  session = (0, utils_1.enterStep)(session, nextStep.id, nextStep.description);
1067
1078
  utils_1.logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
1068
1079
  }
1069
- // Build response schema for this route (with collect fields from step)
1070
- const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, nextStep, this.agent.getSchema());
1071
- // Build response prompt
1072
- const responsePrompt = await this.responseEngine.buildResponsePrompt({
1073
- route: selectedRoute,
1074
- currentStep: nextStep,
1075
- rules: selectedRoute.getRules(),
1076
- prohibitions: selectedRoute.getProhibitions(),
1077
- directives: responseDirectives,
1078
- history: historyEvents,
1079
- agentOptions: this.agent.getAgentOptions(),
1080
- combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
1081
- combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
1082
- context,
1083
- session,
1084
- agentSchema: this.agent.getSchema(),
1085
- });
1086
- // Collect available tools for AI
1087
- const availableTools = this.collectAvailableTools(selectedRoute, nextStep);
1088
- // Generate message using AI provider
1089
- const agentOptions = this.agent.getAgentOptions();
1090
- const result = await agentOptions.provider.generateMessage({
1091
- prompt: responsePrompt,
1092
- history, // Use HistoryItem[] for AI provider
1093
- context,
1094
- tools: availableTools,
1095
- signal,
1096
- parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "response_output" } : undefined,
1097
- });
1098
- let message = result.structured?.message || result.message;
1099
- let toolCalls = result.structured?.toolCalls;
1100
- // Debug: Log initial AI response
1101
- utils_1.logger.debug(`[ResponseModal] Initial AI response:`, {
1102
- hasMessage: !!message,
1103
- messageLength: message?.length || 0,
1104
- hasToolCalls: !!toolCalls,
1105
- toolCallsCount: toolCalls?.length || 0,
1106
- toolNames: toolCalls?.map(tc => tc.toolName) || [],
1107
- });
1108
- // Execute tools with unified loop handling
1109
- const toolResult = await this.executeUnifiedToolLoop({
1110
- toolCalls,
1111
- context,
1112
- session,
1113
- history,
1114
- selectedRoute,
1115
- responsePrompt,
1116
- availableTools,
1117
- responseSchema,
1118
- signal,
1119
- });
1120
- session = toolResult.session;
1121
- toolCalls = toolResult.finalToolCalls;
1122
- if (toolResult.finalMessage) {
1123
- message = toolResult.finalMessage;
1124
- }
1125
- // Collect data from response
1126
- // Use follow-up structured data from tool loop when available, fall back to original result
1127
- const dataSource = toolResult.structured
1128
- ? { structured: toolResult.structured }
1129
- : result;
1130
- session = await this.collectDataFromResponse({ result: dataSource, selectedRoute, nextStep, session });
1131
- return { message, toolCalls, session };
1080
+ // Build response schema for this flow (with collect fields from step)
1081
+ const responseSchema = this.responseEngine.responseSchemaForFlow(selectedFlow, nextStep, this.agent.schema);
1082
+ // ── HALT SHORT-CIRCUIT (Requirement 2.5, 2.6, 2.7) ──────────────────────
1083
+ // After pre-LLM emissions are merged, if `halt: true` then skip the LLM
1084
+ // call entirely. The behavior depends on whether `reply` is also set.
1085
+ if (mergedPreDirective?.halt) {
1086
+ if (mergedPreDirective.reply) {
1087
+ // halt + reply: emit the reply as the assistant message
1088
+ utils_1.logger.debug(`[ResponseModal] Halt with reply — skipping LLM call for step ${nextStep.id}`);
1089
+ return { message: mergedPreDirective.reply, session, stoppedReason: 'reply' };
1090
+ }
1091
+ else {
1092
+ // halt without reply: emit empty assistant content
1093
+ utils_1.logger.debug(`[ResponseModal] Halt without reply — skipping LLM call for step ${nextStep.id}`);
1094
+ return { message: '', session, stoppedReason: 'halt' };
1095
+ }
1096
+ }
1097
+ // ── STEP.REPLY SHORT-CIRCUIT (Requirement 25.1–25.7, 17.9) ──────────────
1098
+ // A step with `reply` set emits a verbatim template response without LLM.
1099
+ // onEnter and prepare have already fired normally at this point.
1100
+ // If prepare returned a PreDirective with `reply`, that overrides
1101
+ // the step-declared reply (last-emission-wins per Algorithm 4).
1102
+ if (nextStep.reply != null) {
1103
+ // Determine the effective reply: prepare-emitted reply wins over step-declared
1104
+ const effectiveReply = mergedPreDirective?.reply ?? await (0, utils_1.render)(nextStep.reply, (0, template_1.createTemplateContext)({ data: session.data || {}, context, session }));
1105
+ utils_1.logger.debug(`[ResponseModal] Step.reply — skipping LLM call for step ${nextStep.id}`);
1106
+ return { message: effectiveReply, session, stoppedReason: 'reply' };
1107
+ }
1108
+ // Transient appendage: per-turn slot from PreDirective.appendPrompt.
1109
+ // Fresh each turn, never cached, never persisted.
1110
+ // Wrapped in try/finally to ensure cleanup even on abnormal termination.
1111
+ let turnTransientAppendage = transientAppendage;
1112
+ try {
1113
+ // Build response prompt
1114
+ const { prompt: responsePrompt, appliedInstructions } = await this.responseEngine.buildResponsePrompt({
1115
+ flow: selectedFlow,
1116
+ currentStep: nextStep,
1117
+ rules: [],
1118
+ prohibitions: [],
1119
+ directives: responseDirectives,
1120
+ history: historyEvents,
1121
+ agentOptions: this.agent.getAgentOptions(),
1122
+ instructions: this.collectScopedInstructions(selectedFlow, nextStep),
1123
+ combinedTerms: this.agent.getTerms(),
1124
+ context,
1125
+ session,
1126
+ agentSchema: this.agent.schema,
1127
+ transientAppendage: turnTransientAppendage,
1128
+ });
1129
+ // Collect available tools for AI
1130
+ const availableTools = this.collectAvailableTools(selectedFlow, nextStep);
1131
+ // Generate message using AI provider
1132
+ const agentOptions = this.agent.getAgentOptions();
1133
+ const result = await agentOptions.provider.generateMessage({
1134
+ prompt: responsePrompt,
1135
+ history, // Use HistoryItem[] for AI provider
1136
+ context,
1137
+ tools: availableTools,
1138
+ signal,
1139
+ parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "response_output" } : undefined,
1140
+ });
1141
+ let message = result.structured?.message || result.message;
1142
+ let toolCalls = result.structured?.toolCalls;
1143
+ // Debug: Log initial AI response
1144
+ utils_1.logger.debug(`[ResponseModal] Initial AI response:`, {
1145
+ hasMessage: !!message,
1146
+ messageLength: message?.length || 0,
1147
+ hasToolCalls: !!toolCalls,
1148
+ toolCallsCount: toolCalls?.length || 0,
1149
+ toolNames: toolCalls?.map(tc => tc.toolName) || [],
1150
+ });
1151
+ // Execute tools with unified loop handling
1152
+ const toolResult = await this.executeUnifiedToolLoop({
1153
+ toolCalls,
1154
+ context,
1155
+ session,
1156
+ history,
1157
+ selectedFlow,
1158
+ responsePrompt,
1159
+ availableTools,
1160
+ responseSchema,
1161
+ signal,
1162
+ });
1163
+ session = toolResult.session;
1164
+ toolCalls = toolResult.finalToolCalls;
1165
+ if (toolResult.finalMessage) {
1166
+ message = toolResult.finalMessage;
1167
+ }
1168
+ // Collect data from response
1169
+ // Use follow-up structured data from tool loop when available, fall back to original result
1170
+ const dataSource = toolResult.structured
1171
+ ? { structured: toolResult.structured }
1172
+ : result;
1173
+ session = await this.collectDataFromResponse({ result: dataSource, selectedFlow, nextStep, session });
1174
+ return { message, toolCalls, session, appliedInstructions };
1175
+ }
1176
+ finally {
1177
+ // Drain the transient appendage at end of turn.
1178
+ // This ensures PreDirective.appendPrompt does not leak to subsequent
1179
+ // turns even when the turn terminates abnormally (error, abort, reject).
1180
+ turnTransientAppendage = undefined;
1181
+ }
1182
+ }
1183
+ /**
1184
+ * Unified streaming response generation
1185
+ * @private
1186
+ */
1187
+ async *generateUnifiedStreamingResponse(responseContext) {
1188
+ const { effectiveContext, session: initialSession, history, selectedFlow, selectedStep, responseDirectives, isFlowComplete, signal, signalFirings: preSignalFirings, signalPreDirective, signalHalted, signalHaltReply, } = responseContext;
1189
+ let session = initialSession;
1190
+ // Accumulator for signal firings across both phases (fire order)
1191
+ const signalFirings = [...(preSignalFirings || [])];
1192
+ // Convert HistoryItem[] to Event[] for internal processing
1193
+ const historyEvents = (0, utils_1.historyToEvents)(history);
1194
+ // ── SIGNAL HALT (Requirement 8.2) ─────────────────────────────────────
1195
+ if (signalHalted) {
1196
+ const haltMessage = signalHaltReply || '';
1197
+ // Run post-signal phase even on halt
1198
+ const postResult = await this.responsePipeline.runPostSignalPhase(session, effectiveContext, historyEvents);
1199
+ session = postResult.updatedSession;
1200
+ signalFirings.push(...postResult.firings);
1201
+ if (postResult.mergedDirective && (0, ResponsePipeline_1.hasDirectivePositionField)(postResult.mergedDirective)) {
1202
+ session = { ...session, pendingDirective: postResult.mergedDirective };
1203
+ }
1204
+ await this.finalizeSession(session, effectiveContext);
1205
+ yield {
1206
+ delta: haltMessage,
1207
+ accumulated: haltMessage,
1208
+ done: true,
1209
+ session,
1210
+ stoppedReason: haltMessage ? 'reply' : 'halt',
1211
+ executedSteps: [],
1212
+ triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
1213
+ };
1214
+ return;
1215
+ }
1216
+ // ── Determine the inner stream generator based on flow state ────────
1217
+ let innerStream;
1218
+ if (selectedFlow && !isFlowComplete) {
1219
+ // AUTO-CHAIN: Walk consecutive auto-steps before any LLM work (streaming path).
1220
+ let resolvedStep = selectedStep;
1221
+ const currentStepInstance = session.currentStep
1222
+ ? selectedFlow.getStep(session.currentStep.id)
1223
+ : selectedStep;
1224
+ if (currentStepInstance?.auto) {
1225
+ const autoChainExecutor = new AutoChainExecutor_1.AutoChainExecutor({
1226
+ maxAutoStepsPerTurn: this.agent.maxAutoStepsPerTurn,
1227
+ });
1228
+ const autoResult = await autoChainExecutor.run({
1229
+ session,
1230
+ context: effectiveContext,
1231
+ flow: selectedFlow,
1232
+ });
1233
+ session = autoResult.session;
1234
+ // Handle halt: emit verbatim reply as a single chunk, done.
1235
+ if (autoResult.stoppedReason === 'halt') {
1236
+ const reply = autoResult.mergedDirective?.reply || '';
1237
+ await this.finalizeSession(session, effectiveContext);
1238
+ yield {
1239
+ delta: reply,
1240
+ accumulated: reply,
1241
+ done: true,
1242
+ session,
1243
+ stoppedReason: 'halt',
1244
+ executedSteps: [],
1245
+ triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
1246
+ };
1247
+ return;
1248
+ }
1249
+ // Handle flow completion or cross-flow redirect from auto-chain.
1250
+ if (autoResult.stoppedReason === 'last_step' || autoResult.stoppedReason === 'completed' || autoResult.stoppedReason === 'goto') {
1251
+ innerStream = this.streamFlowCompletion({
1252
+ selectedFlow,
1253
+ session,
1254
+ context: effectiveContext,
1255
+ history,
1256
+ historyEvents,
1257
+ stoppedReason: autoResult.stoppedReason,
1258
+ });
1259
+ }
1260
+ else {
1261
+ // Normal case: resolved to an interactive step.
1262
+ resolvedStep = autoResult.resolvedStep;
1263
+ innerStream = this.processFlowStreamingResponse({
1264
+ selectedFlow,
1265
+ selectedStep: resolvedStep,
1266
+ responseDirectives,
1267
+ session,
1268
+ history,
1269
+ context: effectiveContext,
1270
+ historyEvents,
1271
+ signal,
1272
+ transientAppendage: signalPreDirective?.appendPrompt,
1273
+ mergedPreDirective: signalPreDirective,
1274
+ });
1275
+ }
1276
+ }
1277
+ else {
1278
+ // No auto-step: directly stream the interactive step.
1279
+ innerStream = this.processFlowStreamingResponse({
1280
+ selectedFlow,
1281
+ selectedStep: resolvedStep,
1282
+ responseDirectives,
1283
+ session,
1284
+ history,
1285
+ context: effectiveContext,
1286
+ historyEvents,
1287
+ signal,
1288
+ // Propagate signal pre-directive's appendPrompt for this turn's LLM call
1289
+ transientAppendage: signalPreDirective?.appendPrompt,
1290
+ mergedPreDirective: signalPreDirective,
1291
+ });
1292
+ }
1293
+ }
1294
+ else if (isFlowComplete && selectedFlow) {
1295
+ // Handle flow completion streaming — implicit terminus (no successor
1296
+ // or all successors skipped), so the reason is 'last_step'.
1297
+ innerStream = this.streamFlowCompletion({
1298
+ selectedFlow,
1299
+ session,
1300
+ context: effectiveContext,
1301
+ history,
1302
+ historyEvents,
1303
+ stoppedReason: 'last_step',
1304
+ });
1305
+ }
1306
+ else {
1307
+ // Fallback: No flows defined, stream a simple response
1308
+ innerStream = this.streamFallbackResponse({
1309
+ history,
1310
+ context: effectiveContext,
1311
+ session,
1312
+ });
1313
+ }
1314
+ // ── Intercept the inner stream to run post-signal phase on the final chunk ──
1315
+ // This mirrors the non-streaming path: post-phase runs after finalize/onComplete
1316
+ // and before session persistence, attaching triggeredSignals to the final chunk
1317
+ // (Requirement 11.2).
1318
+ for await (const chunk of innerStream) {
1319
+ if (chunk.done) {
1320
+ // Run post-signal phase on final chunk (Requirement 9.1, 9.2)
1321
+ const postResult = await this.responsePipeline.runPostSignalPhase(chunk.session || session, effectiveContext, historyEvents);
1322
+ let finalSession = postResult.updatedSession;
1323
+ signalFirings.push(...postResult.firings);
1324
+ // Requirement 9.3: Post-phase position directive sets session.pendingDirective
1325
+ if (postResult.mergedDirective && (0, ResponsePipeline_1.hasDirectivePositionField)(postResult.mergedDirective)) {
1326
+ finalSession = { ...finalSession, pendingDirective: postResult.mergedDirective };
1327
+ }
1328
+ yield {
1329
+ ...chunk,
1330
+ session: finalSession,
1331
+ triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
1332
+ };
1333
+ }
1334
+ else {
1335
+ yield chunk;
1336
+ }
1337
+ }
1132
1338
  }
1133
1339
  /**
1134
- * Process route streaming response with unified tool execution and data collection
1340
+ * Process flow streaming response with unified tool execution and data collection
1135
1341
  * @private
1136
1342
  */
1137
- async *processRouteStreamingResponse(params) {
1138
- const { selectedRoute, selectedStep, responseDirectives, history, context, historyEvents, signal } = params;
1343
+ async *processFlowStreamingResponse(params) {
1344
+ const { selectedFlow, selectedStep, responseDirectives, history, context, historyEvents, signal, transientAppendage, mergedPreDirective } = params;
1139
1345
  let session = params.session;
1140
1346
  // Determine next step (same logic as non-streaming)
1141
1347
  let nextStep;
@@ -1143,21 +1349,44 @@ class ResponseModal {
1143
1349
  nextStep = selectedStep;
1144
1350
  }
1145
1351
  else {
1146
- // Determine current step from session if we're already in this route
1147
- const currentStep = session.currentRoute?.id === selectedRoute.id && session.currentStep
1148
- ? selectedRoute.getStep(session.currentStep.id)
1352
+ // Determine current step from session if we're already in this flow
1353
+ const currentStep = session.currentFlow?.id === selectedFlow.id && session.currentStep
1354
+ ? selectedFlow.getStep(session.currentStep.id)
1149
1355
  : undefined;
1150
- // Get candidate steps based on current position in the route
1151
- const routingEngine = this.agent.getRoutingEngine();
1152
- const candidates = await routingEngine.getCandidateStepsWithConditions(selectedRoute, currentStep, // Pass current step instead of undefined to maintain progression
1153
- (0, template_1.createTemplateContext)({ data: session.data, session, context }));
1154
- if (candidates.length > 0) {
1155
- nextStep = candidates[0].step;
1156
- utils_1.logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new route'}`);
1356
+ // STEP 1 (Algorithm 1): branches win over linear chain
1357
+ if (currentStep?.branches && currentStep.branches.length > 0) {
1358
+ const branchResult = await this.responsePipeline.evaluateStepBranches(currentStep, selectedFlow, session, context);
1359
+ if (branchResult) {
1360
+ // Branch resolved yield a final chunk with the updated session and return
1361
+ if (branchResult.nextStep) {
1362
+ session = branchResult.session;
1363
+ nextStep = branchResult.nextStep;
1364
+ }
1365
+ else {
1366
+ // Flow transition or completion — no step to render
1367
+ yield {
1368
+ delta: '',
1369
+ accumulated: '',
1370
+ done: true,
1371
+ session: branchResult.session,
1372
+ };
1373
+ return;
1374
+ }
1375
+ }
1157
1376
  }
1158
- else {
1159
- nextStep = selectedRoute.initialStep;
1160
- utils_1.logger.warn(`[ResponseModal] No valid steps found, using initial step: ${nextStep.id}`);
1377
+ if (!nextStep) {
1378
+ // Get candidate steps based on current position in the flow
1379
+ const flowRouter = this.agent.getFlowRouter();
1380
+ const candidates = await flowRouter.getCandidateStepsWithConditions(selectedFlow, currentStep, // Pass current step instead of undefined to maintain progression
1381
+ (0, template_1.createTemplateContext)({ data: session.data, session, context }));
1382
+ if (candidates.length > 0) {
1383
+ nextStep = candidates[0].step;
1384
+ utils_1.logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
1385
+ }
1386
+ else {
1387
+ nextStep = selectedFlow.initialStep;
1388
+ utils_1.logger.warn(`[FlowConfigurationError] No valid steps found: all candidates were skipped in flow. Falling back to initial step "${nextStep.id}". Review step skip conditions.`);
1389
+ }
1161
1390
  }
1162
1391
  }
1163
1392
  // Update session with next step
@@ -1166,13 +1395,13 @@ class ResponseModal {
1166
1395
  const sessionData = session.data || {};
1167
1396
  const missingRequires = nextStep.requires.filter(field => sessionData[String(field)] === undefined);
1168
1397
  if (missingRequires.length > 0) {
1169
- const warning = `[Agent] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
1170
- `missing required fields [${missingRequires.join(', ')}]. Staying at current step.`;
1398
+ const warning = `[FlowConfigurationError] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
1399
+ `missing required fields [${missingRequires.join(', ')}]. Staying at current step. Ensure preceding steps collect these fields.`;
1171
1400
  utils_1.logger.warn(warning);
1172
1401
  console.warn(warning);
1173
1402
  const currentStepId = session.currentStep?.id;
1174
1403
  if (currentStepId) {
1175
- const currentStepInstance = selectedRoute.getStep(currentStepId);
1404
+ const currentStepInstance = selectedFlow.getStep(currentStepId);
1176
1405
  if (currentStepInstance) {
1177
1406
  nextStep = currentStepInstance;
1178
1407
  utils_1.logger.debug(`[ResponseModal] Staying at current step: ${nextStep.id} due to missing requires`);
@@ -1189,142 +1418,192 @@ class ResponseModal {
1189
1418
  utils_1.logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
1190
1419
  }
1191
1420
  // Build response schema and prompt (same as non-streaming)
1192
- const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, nextStep, this.agent.getSchema());
1193
- const responsePrompt = await this.responseEngine.buildResponsePrompt({
1194
- route: selectedRoute,
1195
- currentStep: nextStep,
1196
- rules: selectedRoute.getRules(),
1197
- prohibitions: selectedRoute.getProhibitions(),
1198
- directives: responseDirectives,
1199
- history: historyEvents,
1200
- agentOptions: this.agent.getAgentOptions(),
1201
- combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
1202
- combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
1203
- context,
1204
- session,
1205
- agentSchema: this.agent.getSchema(),
1206
- });
1207
- // Collect available tools for AI
1208
- const availableTools = this.collectAvailableTools(selectedRoute, nextStep);
1209
- // Generate message stream using AI provider
1210
- const agentOptions = this.agent.getAgentOptions();
1211
- const stream = agentOptions.provider.generateMessageStream({
1212
- prompt: responsePrompt,
1213
- history, // Use HistoryItem[] for AI provider
1214
- context,
1215
- tools: availableTools,
1216
- signal,
1217
- parameters: { jsonSchema: responseSchema, schemaName: "response_stream_output" },
1218
- });
1219
- // Stream chunks with unified tool handling
1220
- for await (const chunk of stream) {
1221
- let toolCalls = undefined;
1222
- // Extract tool calls from AI response on final chunk
1223
- if (chunk.done && chunk.structured?.toolCalls) {
1224
- toolCalls = chunk.structured.toolCalls;
1225
- const toolManager = this.getToolManager();
1226
- // Use concurrent execution for the initial batch of tool calls
1227
- if (toolManager && typeof toolManager.executeWithConcurrency === 'function') {
1228
- const toolCallRequests = toolCalls.map((tc, i) => ({
1229
- id: `${tc.toolName}-${i}-${Date.now()}`,
1230
- toolName: tc.toolName,
1231
- arguments: tc.arguments,
1232
- }));
1233
- const historyEvents = (0, utils_1.historyToEvents)(history);
1234
- try {
1235
- for await (const update of toolManager.executeWithConcurrency({
1236
- toolCalls: toolCallRequests,
1237
- context,
1238
- data: session.data,
1239
- history: historyEvents,
1240
- signal,
1241
- route: selectedRoute,
1242
- step: nextStep,
1243
- })) {
1244
- // Apply context updates
1245
- if (update.contextUpdate) {
1246
- try {
1247
- await this.agent.updateContext(update.contextUpdate);
1248
- }
1249
- catch (error) {
1250
- utils_1.logger.error(`[ResponseModal] Failed to update context from concurrent tool:`, error);
1421
+ const responseSchema = this.responseEngine.responseSchemaForFlow(selectedFlow, nextStep, this.agent.schema);
1422
+ // ── HALT SHORT-CIRCUIT (Requirement 2.5, 2.6, 2.7) ──────────────────────
1423
+ // After pre-LLM emissions are merged, if `halt: true` then skip the LLM
1424
+ // call entirely. Emit a single done chunk with the appropriate content.
1425
+ if (mergedPreDirective?.halt) {
1426
+ const reply = mergedPreDirective.reply || '';
1427
+ const reason = mergedPreDirective.reply ? 'reply' : 'halt';
1428
+ utils_1.logger.debug(`[ResponseModal] Halt (streaming) — skipping LLM call for step ${nextStep.id}, stoppedReason: ${reason}`);
1429
+ await this.finalizeSession(session, context);
1430
+ yield {
1431
+ delta: reply,
1432
+ accumulated: reply,
1433
+ done: true,
1434
+ session,
1435
+ stoppedReason: reason,
1436
+ executedSteps: [{ id: nextStep.id, flowId: selectedFlow.id }],
1437
+ };
1438
+ return;
1439
+ }
1440
+ // ── STEP.REPLY SHORT-CIRCUIT (Requirement 25.1–25.7, 17.9) ──────────────
1441
+ // A step with `reply` set emits a verbatim template response without LLM.
1442
+ // onEnter and prepare have already fired normally. If prepare returned
1443
+ // a PreDirective with `reply`, that overrides the step-declared reply.
1444
+ if (nextStep.reply != null) {
1445
+ const effectiveReply = mergedPreDirective?.reply ?? await (0, utils_1.render)(nextStep.reply, (0, template_1.createTemplateContext)({ data: session.data || {}, context, session }));
1446
+ utils_1.logger.debug(`[ResponseModal] Step.reply (streaming) skipping LLM call for step ${nextStep.id}`);
1447
+ await this.finalizeSession(session, context);
1448
+ yield {
1449
+ delta: effectiveReply,
1450
+ accumulated: effectiveReply,
1451
+ done: true,
1452
+ session,
1453
+ stoppedReason: 'reply',
1454
+ executedSteps: [{ id: nextStep.id, flowId: selectedFlow.id }],
1455
+ };
1456
+ return;
1457
+ }
1458
+ // Transient appendage: per-turn slot from PreDirective.appendPrompt.
1459
+ // Fresh each turn, never cached, never persisted.
1460
+ // Wrapped in try/finally to ensure cleanup even on abnormal termination.
1461
+ let turnTransientAppendage = transientAppendage;
1462
+ try {
1463
+ const { prompt: responsePrompt, appliedInstructions } = await this.responseEngine.buildResponsePrompt({
1464
+ flow: selectedFlow,
1465
+ currentStep: nextStep,
1466
+ rules: [],
1467
+ prohibitions: [],
1468
+ directives: responseDirectives,
1469
+ history: historyEvents,
1470
+ agentOptions: this.agent.getAgentOptions(),
1471
+ instructions: this.collectScopedInstructions(selectedFlow, nextStep),
1472
+ combinedTerms: this.agent.getTerms(),
1473
+ context,
1474
+ session,
1475
+ agentSchema: this.agent.schema,
1476
+ transientAppendage: turnTransientAppendage,
1477
+ });
1478
+ // Collect available tools for AI
1479
+ const availableTools = this.collectAvailableTools(selectedFlow, nextStep);
1480
+ // Generate message stream using AI provider
1481
+ const agentOptions = this.agent.getAgentOptions();
1482
+ const stream = agentOptions.provider.generateMessageStream({
1483
+ prompt: responsePrompt,
1484
+ history, // Use HistoryItem[] for AI provider
1485
+ context,
1486
+ tools: availableTools,
1487
+ signal,
1488
+ parameters: { jsonSchema: responseSchema, schemaName: "response_stream_output" },
1489
+ });
1490
+ // Stream chunks with unified tool handling
1491
+ for await (const chunk of stream) {
1492
+ let toolCalls = undefined;
1493
+ // Extract tool calls from AI response on final chunk
1494
+ if (chunk.done && chunk.structured?.toolCalls) {
1495
+ toolCalls = chunk.structured.toolCalls;
1496
+ const toolManager = this.getToolManager();
1497
+ // Use concurrent execution for the initial batch of tool calls
1498
+ if (toolManager && typeof toolManager.executeWithConcurrency === 'function') {
1499
+ const toolCallRequests = toolCalls.map((tc, i) => ({
1500
+ id: `${tc.toolName}-${i}-${Date.now()}`,
1501
+ toolName: tc.toolName,
1502
+ arguments: tc.arguments,
1503
+ }));
1504
+ const historyEvents = (0, utils_1.historyToEvents)(history);
1505
+ try {
1506
+ for await (const update of toolManager.executeWithConcurrency({
1507
+ toolCalls: toolCallRequests,
1508
+ context,
1509
+ data: session.data,
1510
+ history: historyEvents,
1511
+ signal,
1512
+ flow: selectedFlow,
1513
+ step: nextStep,
1514
+ })) {
1515
+ // Apply context updates
1516
+ if (update.contextUpdate) {
1517
+ try {
1518
+ await this.agent.updateContext(update.contextUpdate);
1519
+ }
1520
+ catch (error) {
1521
+ utils_1.logger.error(`[ResponseModal] Failed to update context from concurrent tool:`, error);
1522
+ }
1251
1523
  }
1252
- }
1253
- // Apply data updates
1254
- if (update.dataUpdate) {
1255
- try {
1256
- const updateDataMethod = this.agent.getUpdateDataMethod();
1257
- session = await updateDataMethod(session, update.dataUpdate);
1524
+ // Apply data updates
1525
+ if (update.dataUpdate) {
1526
+ try {
1527
+ const updateDataMethod = this.agent.getUpdateDataMethod();
1528
+ session = await updateDataMethod(session, update.dataUpdate);
1529
+ }
1530
+ catch (error) {
1531
+ utils_1.logger.error(`[ResponseModal] Failed to update data from concurrent tool:`, error);
1532
+ }
1258
1533
  }
1259
- catch (error) {
1260
- utils_1.logger.error(`[ResponseModal] Failed to update data from concurrent tool:`, error);
1534
+ // Yield progress updates immediately
1535
+ if (update.progress) {
1536
+ yield {
1537
+ delta: '',
1538
+ accumulated: chunk.accumulated,
1539
+ done: false,
1540
+ session,
1541
+ toolCalls: undefined,
1542
+ isFlowComplete: false,
1543
+ metadata: { toolProgress: update.progress, toolCallId: update.toolCallId },
1544
+ };
1261
1545
  }
1262
1546
  }
1263
- // Yield progress updates immediately
1264
- if (update.progress) {
1265
- yield {
1266
- delta: '',
1267
- accumulated: chunk.accumulated,
1268
- done: false,
1269
- session,
1270
- toolCalls: undefined,
1271
- isRouteComplete: false,
1272
- metadata: { toolProgress: update.progress, toolCallId: update.toolCallId },
1273
- };
1274
- }
1547
+ utils_1.logger.debug(`[ResponseModal] Concurrent tool execution completed for ${toolCallRequests.length} tools`);
1548
+ }
1549
+ catch (error) {
1550
+ utils_1.logger.error(`[ResponseModal] Concurrent tool execution failed, falling back to sequential:`, error);
1551
+ // Fall back to the unified tool loop on failure
1552
+ const toolResult = await this.executeUnifiedToolLoop({
1553
+ toolCalls, context, session, history, selectedFlow,
1554
+ responsePrompt, availableTools, responseSchema, signal,
1555
+ });
1556
+ session = toolResult.session;
1557
+ toolCalls = toolResult.finalToolCalls;
1275
1558
  }
1276
- utils_1.logger.debug(`[ResponseModal] Concurrent tool execution completed for ${toolCallRequests.length} tools`);
1277
1559
  }
1278
- catch (error) {
1279
- utils_1.logger.error(`[ResponseModal] Concurrent tool execution failed, falling back to sequential:`, error);
1280
- // Fall back to the unified tool loop on failure
1560
+ else {
1561
+ // Fallback: no ToolManager or no executeWithConcurrency, use unified tool loop
1281
1562
  const toolResult = await this.executeUnifiedToolLoop({
1282
- toolCalls, context, session, history, selectedRoute,
1563
+ toolCalls, context, session, history, selectedFlow,
1283
1564
  responsePrompt, availableTools, responseSchema, signal,
1284
1565
  });
1285
1566
  session = toolResult.session;
1286
1567
  toolCalls = toolResult.finalToolCalls;
1287
1568
  }
1288
1569
  }
1289
- else {
1290
- // Fallback: no ToolManager or no executeWithConcurrency, use unified tool loop
1291
- const toolResult = await this.executeUnifiedToolLoop({
1292
- toolCalls, context, session, history, selectedRoute,
1293
- responsePrompt, availableTools, responseSchema, signal,
1570
+ // Extract collected data on final chunk
1571
+ if (chunk.done && chunk.structured && nextStep.collect) {
1572
+ session = await this.collectDataFromResponse({
1573
+ result: { structured: chunk.structured },
1574
+ selectedFlow,
1575
+ nextStep,
1576
+ session,
1294
1577
  });
1295
- session = toolResult.session;
1296
- toolCalls = toolResult.finalToolCalls;
1297
1578
  }
1298
- }
1299
- // Extract collected data on final chunk
1300
- if (chunk.done && chunk.structured && nextStep.collect) {
1301
- session = await this.collectDataFromResponse({
1302
- result: { structured: chunk.structured },
1303
- selectedRoute,
1304
- nextStep,
1579
+ // Handle session finalization on final chunk
1580
+ if (chunk.done) {
1581
+ await this.finalizeSession(session, context);
1582
+ }
1583
+ // Response structure completeness (Requirement 8.1, 8.2, 8.3)
1584
+ // - executedSteps: single step executed in this response
1585
+ // - stoppedReason: 'needs_input' for single-step execution (waiting for user input)
1586
+ // - session.currentStep: reflects the executed step
1587
+ yield {
1588
+ delta: chunk.delta,
1589
+ accumulated: chunk.accumulated,
1590
+ done: chunk.done,
1305
1591
  session,
1306
- });
1307
- }
1308
- // Handle session finalization on final chunk
1309
- if (chunk.done) {
1310
- await this.finalizeSession(session, context);
1592
+ toolCalls,
1593
+ isFlowComplete: false,
1594
+ executedSteps: chunk.done ? [{ id: nextStep.id, flowId: selectedFlow.id }] : undefined,
1595
+ stoppedReason: chunk.done ? 'needs_input' : undefined,
1596
+ metadata: chunk.metadata,
1597
+ structured: chunk.structured,
1598
+ appliedInstructions: chunk.done ? appliedInstructions : undefined,
1599
+ };
1311
1600
  }
1312
- // Response structure completeness (Requirement 8.1, 8.2, 8.3)
1313
- // - executedSteps: single step executed in this response
1314
- // - stoppedReason: 'needs_input' for single-step execution (waiting for user input)
1315
- // - session.currentStep: reflects the executed step
1316
- yield {
1317
- delta: chunk.delta,
1318
- accumulated: chunk.accumulated,
1319
- done: chunk.done,
1320
- session,
1321
- toolCalls,
1322
- isRouteComplete: false,
1323
- executedSteps: chunk.done ? [{ id: nextStep.id, routeId: selectedRoute.id }] : undefined,
1324
- stoppedReason: chunk.done ? 'needs_input' : undefined,
1325
- metadata: chunk.metadata,
1326
- structured: chunk.structured,
1327
- };
1601
+ }
1602
+ finally {
1603
+ // Drain the transient appendage at end of turn.
1604
+ // This ensures PreDirective.appendPrompt does not leak to subsequent
1605
+ // turns even when the turn terminates abnormally (error, abort, reject).
1606
+ turnTransientAppendage = undefined;
1328
1607
  }
1329
1608
  }
1330
1609
  /**
@@ -1334,7 +1613,7 @@ class ResponseModal {
1334
1613
  */
1335
1614
  async executeUnifiedToolLoop(params) {
1336
1615
  try {
1337
- const { context, history, selectedRoute, responsePrompt, availableTools, responseSchema, signal } = params;
1616
+ const { context, history, selectedFlow, responsePrompt, availableTools, responseSchema, signal } = params;
1338
1617
  let { toolCalls, session } = params;
1339
1618
  // Convert HistoryItem[] to Event[] for internal processing
1340
1619
  const historyEvents = (0, utils_1.historyToEvents)(history);
@@ -1346,9 +1625,9 @@ class ResponseModal {
1346
1625
  if (toolCalls && toolCalls.length > 0) {
1347
1626
  utils_1.logger.debug(`[ResponseModal] Executing ${toolCalls.length} dynamic tool calls:`, toolCalls.map(tc => tc.toolName));
1348
1627
  for (const toolCall of toolCalls) {
1349
- const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1628
+ const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
1350
1629
  if (!tool) {
1351
- utils_1.logger.warn(`[ResponseModal] Tool not found: ${toolCall.toolName}`);
1630
+ utils_1.logger.warn(`[ToolExecutionError] Tool not found: "${toolCall.toolName}" is not registered in any scope. Skipping this tool call. Register the tool or check the tool name.`);
1352
1631
  continue;
1353
1632
  }
1354
1633
  try {
@@ -1422,7 +1701,7 @@ class ResponseModal {
1422
1701
  // Create tool result history items
1423
1702
  const toolResultHistoryItems = [];
1424
1703
  for (const toolCall of toolCalls || []) {
1425
- const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1704
+ const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
1426
1705
  if (tool) {
1427
1706
  // Create HistoryItem format for tool results
1428
1707
  // Add assistant message with tool_calls
@@ -1480,9 +1759,9 @@ class ResponseModal {
1480
1759
  utils_1.logger.debug(`[ResponseModal] Follow-up call produced ${followUpToolCalls.length} additional tool calls`);
1481
1760
  // Execute the follow-up tool calls
1482
1761
  for (const toolCall of followUpToolCalls) {
1483
- const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1762
+ const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
1484
1763
  if (!tool) {
1485
- utils_1.logger.warn(`[ResponseModal] Tool not found in follow-up: ${toolCall.toolName}`);
1764
+ utils_1.logger.warn(`[ToolExecutionError] Tool not found in follow-up: "${toolCall.toolName}" is not registered in any scope. Skipping this tool call. Register the tool or check the tool name.`);
1486
1765
  continue;
1487
1766
  }
1488
1767
  try {
@@ -1551,7 +1830,7 @@ class ResponseModal {
1551
1830
  }
1552
1831
  }
1553
1832
  if (toolLoopCount >= MAX_TOOL_LOOPS) {
1554
- utils_1.logger.warn(`[ResponseModal] Tool loop limit reached (${MAX_TOOL_LOOPS}), stopping`);
1833
+ utils_1.logger.warn(`[ResponseGenerationError] Tool loop limit reached: ${toolLoopCount} iterations hit the cap (${MAX_TOOL_LOOPS}). Stopping tool execution. Increase MAX_TOOL_LOOPS or reduce recursive tool calls.`);
1555
1834
  }
1556
1835
  // If tools were executed but no final text message was produced,
1557
1836
  // make one more LLM call to generate a proper text response from tool results.
@@ -1634,30 +1913,30 @@ class ResponseModal {
1634
1913
  */
1635
1914
  async collectDataFromResponse(params) {
1636
1915
  try {
1637
- const { result, selectedRoute, nextStep, session } = params;
1916
+ const { result, selectedFlow, nextStep, session } = params;
1638
1917
  let updatedSession = session;
1639
- // Extract collected data from final response (only for route-based interactions)
1640
- if (selectedRoute && result.structured) {
1918
+ // Extract collected data from final response (only for flow-based interactions)
1919
+ if (selectedFlow && result.structured) {
1641
1920
  try {
1642
1921
  const collectedData = {};
1643
1922
  // AgentStructuredResponse extends Record<string, unknown>, so we can safely access properties
1644
1923
  const structuredData = result.structured;
1645
- // Collect ALL route fields (required + optional) from structured response
1646
- const allRouteFields = new Set();
1647
- // Add route required fields
1648
- if (selectedRoute.requiredFields) {
1649
- selectedRoute.requiredFields.forEach(field => allRouteFields.add(String(field)));
1924
+ // Collect ALL flow fields (required + optional) from structured response
1925
+ const allFlowFields = new Set();
1926
+ // Add flow required fields
1927
+ if (selectedFlow.requiredFields) {
1928
+ selectedFlow.requiredFields.forEach(field => allFlowFields.add(String(field)));
1650
1929
  }
1651
- // Add route optional fields
1652
- if (selectedRoute.optionalFields) {
1653
- selectedRoute.optionalFields.forEach(field => allRouteFields.add(String(field)));
1930
+ // Add flow optional fields
1931
+ if (selectedFlow.optionalFields) {
1932
+ selectedFlow.optionalFields.forEach(field => allFlowFields.add(String(field)));
1654
1933
  }
1655
- // Also include current step's collect fields (in case they're not in route fields)
1934
+ // Also include current step's collect fields (in case they're not in flow fields)
1656
1935
  if (nextStep?.collect) {
1657
- nextStep.collect.forEach(field => allRouteFields.add(String(field)));
1936
+ nextStep.collect.forEach(field => allFlowFields.add(String(field)));
1658
1937
  }
1659
1938
  // Extract all available fields from structured response
1660
- for (const field of allRouteFields) {
1939
+ for (const field of allFlowFields) {
1661
1940
  const fieldKey = String(field);
1662
1941
  if (fieldKey in structuredData && structuredData[fieldKey] !== undefined && structuredData[fieldKey] !== null) {
1663
1942
  collectedData[fieldKey] = structuredData[fieldKey];
@@ -1707,207 +1986,122 @@ class ResponseModal {
1707
1986
  }
1708
1987
  }
1709
1988
  /**
1710
- * Handle route completion logic
1989
+ * Apply flow completion: release the session to idle state.
1990
+ *
1991
+ * This is a pure state transition. The framework emits **no message of
1992
+ * its own** at the completion boundary — every word delivered to the
1993
+ * user comes from a developer-defined step prompt. If the dev wants a
1994
+ * closing turn, they add a final interactive step with their own
1995
+ * `prompt`; the framework respects that step's natural LLM output.
1996
+ *
1997
+ * Behavior:
1998
+ * - Marks the active `flowHistory` entry as `completed: true` and
1999
+ * stamps `exitedAt`.
2000
+ * - Evaluates `flow.onComplete` for an explicit follow-up transition.
2001
+ * When set, populates `session.pendingDirective` (the next turn's
2002
+ * pipeline applies it). When absent, the session is fully idle.
2003
+ * - Clears `currentFlow` and `currentStep` to `undefined`.
2004
+ * - Clears owned fields when the flow is `reentrant` so subsequent
2005
+ * re-selections start from a clean state.
2006
+ *
2007
+ * Returns the updated session. Callers compose any reply text from
2008
+ * their own sources (an upstream LLM turn, a directive's `reply`, or
2009
+ * an empty string for silent completion).
2010
+ *
1711
2011
  * @private
1712
2012
  */
1713
- async handleRouteCompletion(params) {
1714
- const { selectedRoute, session, context, history, historyEvents, signal } = params;
1715
- // Get endStep spec from route
1716
- const endStepSpec = selectedRoute.endStepSpec;
1717
- // Create a temporary step for completion message generation using endStep configuration
1718
- const completionStep = new Step_1.Step(selectedRoute.id, {
1719
- description: endStepSpec.description,
1720
- id: endStepSpec.id || constants_1.END_ROUTE_ID,
1721
- collect: endStepSpec.collect,
1722
- requires: endStepSpec.requires,
1723
- prompt: endStepSpec.prompt || "Send a brief, natural farewell message thanking the user. Do NOT list or mention any collected data, field names, or internal information.",
2013
+ async applyFlowCompletion(params) {
2014
+ const { selectedFlow, session, context } = params;
2015
+ // 1) Evaluate onComplete first — needs the still-active session shape.
2016
+ const transitionConfig = await selectedFlow.evaluateOnComplete({ data: session.data }, context);
2017
+ // 2) Release to idle. If the flow is reentrant, scrub its owned
2018
+ // fields so re-selection on a future turn starts clean. When
2019
+ // onComplete fires we still go idle here — the next turn's
2020
+ // pipeline applies the pendingDirective before any routing.
2021
+ const ownedFields = selectedFlow.reentrant
2022
+ ? [
2023
+ ...(selectedFlow.requiredFields ?? []),
2024
+ ...(selectedFlow.optionalFields ?? []),
2025
+ ]
2026
+ : undefined;
2027
+ let nextSession = (0, utils_1.completeCurrentFlow)(session, {
2028
+ clearOwnedFields: ownedFields,
1724
2029
  });
1725
- // Build response schema for completion (message only, no data collection)
1726
- const completionSchema = {
1727
- type: "object",
1728
- properties: {
1729
- message: {
1730
- type: "string",
1731
- description: "A natural, warm farewell message for the user. Must NOT contain task names, field names, collected data, or any internal/technical information.",
1732
- },
1733
- },
1734
- required: ["message"],
1735
- additionalProperties: false,
1736
- };
1737
- const templateContext = (0, template_1.createTemplateContext)({ context, session, history: historyEvents });
1738
- // Build completion response prompt using ResponseEngine
1739
- // Filter out conditional guidelines - only include always-active ones
1740
- const alwaysActiveGuidelines = [
1741
- ...this.agent.getGuidelines().filter(g => !g.condition),
1742
- ...selectedRoute.getGuidelines().filter(g => !g.condition),
1743
- ];
1744
- let completitionPrompt = "Send a brief, natural farewell message. Do NOT mention internal data or task details.";
1745
- if (endStepSpec.prompt) {
1746
- completitionPrompt = await (0, utils_1.render)(endStepSpec.prompt, templateContext);
1747
- }
1748
- const completionPrompt = await this.responseEngine.buildResponsePrompt({
1749
- route: selectedRoute,
1750
- currentStep: completionStep,
1751
- rules: selectedRoute.getRules(),
1752
- prohibitions: selectedRoute.getProhibitions(),
1753
- directives: [
1754
- "The conversation task has been completed successfully",
1755
- "Generate a natural, friendly farewell message for the user",
1756
- "Do NOT mention task names, route names, collected data, field names, or any internal/technical information",
1757
- "Do NOT list or summarize the data you collected - the user already knows what they told you",
1758
- "Do NOT use words like 'tarefa', 'dados coletados', 'prospecção', 'concluída' or similar internal terms",
1759
- "Keep it brief, warm, and conversational - as if ending a natural conversation",
1760
- "Do NOT ask for more information - the conversation is ending",
1761
- completitionPrompt,
1762
- ],
1763
- history: historyEvents,
1764
- agentOptions: this.agent.getAgentOptions(),
1765
- combinedGuidelines: alwaysActiveGuidelines, // Only non-conditional guidelines
1766
- combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
1767
- context,
1768
- session,
1769
- agentSchema: undefined, // No data collection schema for completion
1770
- });
1771
- // Generate completion message using AI provider
1772
- const agentOptions = this.agent.getAgentOptions();
1773
- utils_1.logger.debug(`[ResponseModal] Calling AI provider for completion message...`);
1774
- const completionResult = await agentOptions.provider.generateMessage({
1775
- prompt: completionPrompt,
1776
- history, // Use HistoryItem[] for AI provider
1777
- context,
1778
- signal,
1779
- parameters: { jsonSchema: completionSchema, schemaName: "completion_message" },
1780
- });
1781
- utils_1.logger.debug(`[ResponseModal] AI provider returned completion result`);
1782
- const message = completionResult.structured?.message || completionResult.message;
1783
- utils_1.logger.debug(`[ResponseModal] Generated completion message for route: ${selectedRoute.title}`);
1784
- // Check for onComplete transition
1785
- const transitionConfig = await selectedRoute.evaluateOnComplete({ data: session.data }, context);
2030
+ // 3) Wire pendingDirective when onComplete returned a target.
1786
2031
  if (transitionConfig) {
1787
- // Find target route by ID or title
1788
- const targetRoute = this.agent.getRoutes().find((r) => r.id === transitionConfig.nextStep || r.title === transitionConfig.nextStep);
1789
- if (targetRoute) {
1790
- const renderedCondition = await (0, utils_1.render)(transitionConfig.condition, templateContext);
1791
- // Set pending transition in session
1792
- session.pendingTransition = {
1793
- targetRouteId: targetRoute.id,
1794
- condition: renderedCondition,
1795
- reason: "route_complete",
2032
+ const goToTarget = typeof transitionConfig.goTo === 'string'
2033
+ ? transitionConfig.goTo
2034
+ : transitionConfig.goTo?.flow;
2035
+ const targetFlow = goToTarget ? this.agent.getFlows().find((r) => r.id === goToTarget ||
2036
+ r.title === goToTarget) : undefined;
2037
+ if (targetFlow) {
2038
+ nextSession = {
2039
+ ...nextSession,
2040
+ pendingDirective: {
2041
+ goTo: targetFlow.id,
2042
+ },
1796
2043
  };
1797
- utils_1.logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed with pending transition to: ${targetRoute.title}`);
2044
+ utils_1.logger.debug(`[ResponseModal] Flow ${selectedFlow.title} completed with pending directive to: ${targetFlow.title}`);
1798
2045
  }
1799
- else {
1800
- utils_1.logger.warn(`[ResponseModal] Route ${selectedRoute.title} completed but target route not found: ${transitionConfig.nextStep}`);
2046
+ else if (goToTarget) {
2047
+ utils_1.logger.warn(`[FlowConfigurationError] onComplete target not found: flow "${selectedFlow.title}" completed but onComplete target "${goToTarget}" does not match any flow. ` +
2048
+ `Fix the onComplete value to reference an existing flow id/title, or remove onComplete to release the session to idle.`);
1801
2049
  }
1802
2050
  }
1803
- return message;
2051
+ else {
2052
+ utils_1.logger.debug(`[ResponseModal] Flow ${selectedFlow.title} completed; session released to idle.`);
2053
+ }
2054
+ return nextSession;
1804
2055
  }
1805
2056
  /**
1806
- * Stream route completion response
2057
+ * Stream flow completion response
1807
2058
  * @private
1808
2059
  */
1809
- async *streamRouteCompletion(params) {
1810
- const { selectedRoute, context, history, historyEvents, signal } = params;
1811
- let session = params.session;
1812
- // Get endStep spec from route
1813
- const endStepSpec = selectedRoute.endStepSpec;
1814
- // Create a temporary step for completion message generation using endStep configuration
1815
- const completionStep = new Step_1.Step(selectedRoute.id, {
1816
- description: endStepSpec.description,
1817
- id: endStepSpec.id || constants_1.END_ROUTE_ID,
1818
- collect: endStepSpec.collect,
1819
- requires: endStepSpec.requires,
1820
- prompt: endStepSpec.prompt || "Send a brief, natural farewell message thanking the user. Do NOT list or mention any collected data, field names, or internal information.",
1821
- });
1822
- // Build response schema for completion
1823
- const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, completionStep, this.agent.getSchema());
1824
- const templateContext = (0, template_1.createTemplateContext)({ context, session, history: historyEvents }); // Use Event[] for template context
1825
- // Build completion response prompt
1826
- const completionPrompt = await this.responseEngine.buildResponsePrompt({
1827
- route: selectedRoute,
1828
- currentStep: completionStep,
1829
- rules: selectedRoute.getRules(),
1830
- prohibitions: selectedRoute.getProhibitions(),
1831
- directives: undefined, // No directives for completion
1832
- history: historyEvents,
1833
- agentOptions: this.agent.getAgentOptions(),
1834
- combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
1835
- combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
1836
- context,
1837
- session,
1838
- agentSchema: this.agent.getSchema(),
1839
- });
1840
- // Stream completion message using AI provider
1841
- const agentOptions = this.agent.getAgentOptions();
1842
- const stream = agentOptions.provider.generateMessageStream({
1843
- prompt: completionPrompt,
1844
- history, // Use HistoryItem[] for AI provider
2060
+ /**
2061
+ * Stream a flow completion as a single terminal chunk.
2062
+ *
2063
+ * No LLM call is made. The framework no longer authors a farewell — the
2064
+ * completion path is a pure state transition. The chunk emits an empty
2065
+ * `delta` and a `done: true` flag with the idle session attached so
2066
+ * downstream consumers can finalize cleanly.
2067
+ *
2068
+ * If the developer wants closing copy in a streaming response, they
2069
+ * should add a final interactive step whose own LLM turn delivers it.
2070
+ *
2071
+ * @private
2072
+ */
2073
+ async *streamFlowCompletion(params) {
2074
+ const { selectedFlow, context, history } = params;
2075
+ const session = await this.applyFlowCompletion({
2076
+ selectedFlow,
2077
+ session: params.session,
1845
2078
  context,
1846
- signal,
1847
- parameters: { jsonSchema: responseSchema, schemaName: "completion_message_stream" },
2079
+ history,
1848
2080
  });
1849
- utils_1.logger.debug(`[ResponseModal] Streaming completion message for route: ${selectedRoute.title}`);
1850
- // Check for onComplete transition
1851
- const transitionConfig = await selectedRoute.evaluateOnComplete({ data: session.data }, context);
1852
- if (transitionConfig) {
1853
- // Find target route by ID or title
1854
- const targetRoute = this.agent.getRoutes().find((r) => r.id === transitionConfig.nextStep || r.title === transitionConfig.nextStep);
1855
- if (targetRoute) {
1856
- const renderedCondition = await (0, utils_1.render)(transitionConfig.condition, templateContext);
1857
- // Set pending transition in session
1858
- session = {
1859
- ...session,
1860
- pendingTransition: {
1861
- targetRouteId: targetRoute.id,
1862
- condition: renderedCondition,
1863
- reason: "route_complete",
1864
- },
1865
- };
1866
- utils_1.logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed with pending transition to: ${targetRoute.title}`);
1867
- }
1868
- else {
1869
- utils_1.logger.warn(`[ResponseModal] Route ${selectedRoute.title} completed but target route not found: ${transitionConfig.nextStep}`);
1870
- }
1871
- }
1872
- // Set step to END_ROUTE marker
1873
- session = (0, utils_1.enterStep)(session, constants_1.END_ROUTE_ID, "Route completed");
1874
- utils_1.logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`);
1875
- // Stream completion chunks
1876
- for await (const chunk of stream) {
1877
- // Update current session if we have one
1878
- if (chunk.done) {
1879
- await this.finalizeSession(session, context);
1880
- }
1881
- // Response structure completeness (Requirement 8.1, 8.2, 8.3)
1882
- // - executedSteps: empty for route completion (no new steps executed)
1883
- // - stoppedReason: 'route_complete' for completed routes
1884
- // - session.currentStep: set to END_ROUTE
1885
- yield {
1886
- delta: chunk.delta,
1887
- accumulated: chunk.accumulated,
1888
- done: chunk.done,
1889
- session,
1890
- toolCalls: undefined,
1891
- isRouteComplete: true,
1892
- executedSteps: chunk.done ? [] : undefined,
1893
- stoppedReason: chunk.done ? 'route_complete' : undefined,
1894
- metadata: chunk.metadata,
1895
- structured: chunk.structured,
1896
- };
1897
- }
2081
+ await this.finalizeSession(session, context);
2082
+ yield {
2083
+ delta: '',
2084
+ accumulated: '',
2085
+ done: true,
2086
+ session,
2087
+ toolCalls: undefined,
2088
+ isFlowComplete: true,
2089
+ executedSteps: [],
2090
+ stoppedReason: params.stoppedReason ?? 'completed',
2091
+ };
1898
2092
  }
1899
2093
  /**
1900
- * Generate fallback response when no routes are available
2094
+ * Generate fallback response when no flows are available
1901
2095
  * @private
1902
2096
  */
1903
2097
  async generateFallbackResponse(params) {
1904
2098
  const { history, context, session, signal } = params;
1905
- utils_1.logger.debug(`[ResponseModal] No route selected, generating basic response`);
1906
- // Build basic response prompt without route context
1907
- const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
2099
+ utils_1.logger.debug(`[ResponseModal] No flow selected, generating basic response`);
2100
+ // Build basic response prompt without flow context
2101
+ const { prompt: fallbackPrompt, appliedInstructions } = await this.responseEngine.buildFallbackPrompt({
1908
2102
  agentOptions: this.agent.getAgentOptions(),
1909
2103
  terms: this.agent.getTerms(),
1910
- guidelines: this.agent.getGuidelines(),
2104
+ instructions: this.collectScopedInstructions(),
1911
2105
  context,
1912
2106
  session,
1913
2107
  });
@@ -1927,18 +2121,18 @@ class ResponseModal {
1927
2121
  schemaName: "fallback_response",
1928
2122
  },
1929
2123
  });
1930
- return result.structured?.message || result.message;
2124
+ return { message: result.structured?.message || result.message, appliedInstructions };
1931
2125
  }
1932
2126
  /**
1933
- * Stream fallback response when no routes are available
2127
+ * Stream fallback response when no flows are available
1934
2128
  * @private
1935
2129
  */
1936
2130
  async *streamFallbackResponse(params) {
1937
2131
  const { history, context, session, signal } = params;
1938
- const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
2132
+ const { prompt: fallbackPrompt, appliedInstructions } = await this.responseEngine.buildFallbackPrompt({
1939
2133
  agentOptions: this.agent.getAgentOptions(),
1940
2134
  terms: this.agent.getTerms(),
1941
- guidelines: this.agent.getGuidelines(),
2135
+ instructions: this.collectScopedInstructions(),
1942
2136
  context,
1943
2137
  session,
1944
2138
  });
@@ -1964,8 +2158,8 @@ class ResponseModal {
1964
2158
  await this.finalizeSession(session, context);
1965
2159
  }
1966
2160
  // Response structure completeness (Requirement 8.1, 8.2, 8.3)
1967
- // - executedSteps: empty for fallback (no route/step execution)
1968
- // - stoppedReason: undefined for fallback (no route context)
2161
+ // - executedSteps: empty for fallback (no flow/step execution)
2162
+ // - stoppedReason: undefined for fallback (no flow context)
1969
2163
  // - session.currentStep: unchanged (no step progression)
1970
2164
  yield {
1971
2165
  delta: chunk.delta,
@@ -1973,11 +2167,12 @@ class ResponseModal {
1973
2167
  done: chunk.done,
1974
2168
  session,
1975
2169
  toolCalls: undefined,
1976
- isRouteComplete: false,
2170
+ isFlowComplete: false,
1977
2171
  executedSteps: chunk.done ? [] : undefined,
1978
2172
  stoppedReason: undefined,
1979
2173
  metadata: chunk.metadata,
1980
2174
  structured: chunk.structured,
2175
+ appliedInstructions: chunk.done ? appliedInstructions : undefined,
1981
2176
  };
1982
2177
  }
1983
2178
  }
@@ -1998,52 +2193,52 @@ class ResponseModal {
1998
2193
  // Execute finalize function
1999
2194
  await this.executeStepFinalize(session, context);
2000
2195
  // Update current session if we have one
2001
- const currentSession = this.agent.getCurrentSession();
2196
+ const currentSession = this.agent.currentSession;
2002
2197
  if (currentSession) {
2003
- this.agent.setCurrentSession(session);
2198
+ this.agent.currentSession = session;
2004
2199
  }
2005
2200
  }
2006
2201
  // ============================================================================
2007
2202
  // UTILITY METHODS - Helper methods for tool management and other utilities
2008
2203
  // ============================================================================
2009
2204
  /**
2010
- * Find an available tool by name for the given route using ToolManager
2205
+ * Find an available tool by name for the given flow using ToolManager
2011
2206
  * Delegates to ToolManager for unified tool resolution
2012
2207
  * @private
2013
2208
  */
2014
- findAvailableTool(toolName, route) {
2209
+ findAvailableTool(toolName, flow) {
2015
2210
  // Use ToolManager for unified tool resolution
2016
2211
  const toolManager = this.getToolManager();
2017
2212
  if (toolManager) {
2018
- return toolManager.find(toolName, undefined, undefined, route);
2213
+ return toolManager.find(toolName, undefined, undefined, flow);
2019
2214
  }
2020
2215
  // Fallback to legacy resolution if ToolManager not available
2021
2216
  utils_1.logger.warn(`[ResponseModal] ToolManager not available, using legacy tool resolution for: ${toolName}`);
2022
- // Check route-level tools first (if route provided)
2023
- if (route) {
2024
- const routeTool = route
2217
+ // Check flow-level tools first (if flow provided)
2218
+ if (flow) {
2219
+ const flowTool = flow
2025
2220
  .getTools()
2026
- .find((tool) => tool.id === toolName || tool.name === toolName);
2027
- if (routeTool)
2028
- return routeTool;
2221
+ .find((tool) => tool.id === toolName || tool.id === toolName);
2222
+ if (flowTool)
2223
+ return flowTool;
2029
2224
  }
2030
2225
  // Fall back to agent-level tools
2031
2226
  const agentTools = this.agent.getTools();
2032
- return agentTools.find((tool) => tool.id === toolName || tool.name === toolName);
2227
+ return agentTools.find((tool) => tool.id === toolName || tool.id === toolName);
2033
2228
  }
2034
2229
  /**
2035
- * Collect all available tools for the given route and step context using ToolManager
2230
+ * Collect all available tools for the given flow and step context using ToolManager
2036
2231
  * Delegates to ToolManager for unified tool resolution and deduplication
2037
2232
  * @private
2038
2233
  */
2039
- collectAvailableTools(route, step) {
2234
+ collectAvailableTools(flow, step) {
2040
2235
  // Use ToolManager for unified tool collection if available
2041
2236
  const toolManager = this.getToolManager();
2042
2237
  if (toolManager) {
2043
- const availableTools = toolManager.getAvailable(undefined, step, route);
2238
+ const availableTools = toolManager.getAvailable(undefined, step, flow);
2044
2239
  return availableTools.map((tool) => ({
2045
2240
  id: tool.id,
2046
- name: tool.name || tool.id,
2241
+ name: tool.id || tool.id,
2047
2242
  description: tool.description,
2048
2243
  parameters: tool.parameters,
2049
2244
  }));
@@ -2055,9 +2250,9 @@ class ResponseModal {
2055
2250
  this.agent.getTools().forEach((tool) => {
2056
2251
  availableTools.set(tool.id, tool);
2057
2252
  });
2058
- // Add route-level tools (these take precedence)
2059
- if (route) {
2060
- route.getTools().forEach((tool) => {
2253
+ // Add flow-level tools (these take precedence)
2254
+ if (flow) {
2255
+ flow.getTools().forEach((tool) => {
2061
2256
  availableTools.set(tool.id, tool);
2062
2257
  });
2063
2258
  }
@@ -2100,7 +2295,7 @@ class ResponseModal {
2100
2295
  // Convert to the format expected by AI providers
2101
2296
  return Array.from(availableTools.values()).map((tool) => ({
2102
2297
  id: tool.id,
2103
- name: tool.name || tool.id,
2298
+ name: tool.id || tool.id,
2104
2299
  description: tool.description,
2105
2300
  parameters: tool.parameters,
2106
2301
  }));
@@ -2109,7 +2304,7 @@ class ResponseModal {
2109
2304
  * Execute a prepare or finalize function/tool
2110
2305
  * @private
2111
2306
  */
2112
- async executePrepareFinalize(prepareOrFinalize, context, data, route, step) {
2307
+ async executePrepareFinalize(prepareOrFinalize, context, data, flow, step) {
2113
2308
  if (!prepareOrFinalize)
2114
2309
  return;
2115
2310
  if (typeof prepareOrFinalize === "function") {
@@ -2123,7 +2318,7 @@ class ResponseModal {
2123
2318
  // Tool ID - use ToolManager for unified resolution
2124
2319
  const toolManager = this.getToolManager();
2125
2320
  if (toolManager) {
2126
- tool = toolManager.find(prepareOrFinalize, undefined, step, route);
2321
+ tool = toolManager.find(prepareOrFinalize, undefined, step, flow);
2127
2322
  }
2128
2323
  else {
2129
2324
  // Fallback to legacy resolution if ToolManager not available
@@ -2133,9 +2328,9 @@ class ResponseModal {
2133
2328
  this.agent.getTools().forEach((t) => {
2134
2329
  availableTools.set(t.id, t);
2135
2330
  });
2136
- // Add route-level tools
2137
- if (route) {
2138
- route.getTools().forEach((t) => {
2331
+ // Add flow-level tools
2332
+ if (flow) {
2333
+ flow.getTools().forEach((t) => {
2139
2334
  availableTools.set(t.id, t);
2140
2335
  });
2141
2336
  }
@@ -2187,24 +2382,6 @@ class ResponseModal {
2187
2382
  }
2188
2383
  }
2189
2384
  }
2190
- /**
2191
- * Merge terms with route-specific taking precedence on conflicts
2192
- * @private
2193
- */
2194
- mergeTerms(agentTerms, routeTerms) {
2195
- const merged = new Map();
2196
- // Add agent terms first
2197
- agentTerms.forEach((term) => {
2198
- const name = typeof term.name === "string" ? term.name : term.name.toString();
2199
- merged.set(name, term);
2200
- });
2201
- // Add route terms (these take precedence)
2202
- routeTerms.forEach((term) => {
2203
- const name = typeof term.name === "string" ? term.name : term.name.toString();
2204
- merged.set(name, term);
2205
- });
2206
- return Array.from(merged.values());
2207
- }
2208
2385
  }
2209
2386
  exports.ResponseModal = ResponseModal;
2210
2387
  //# sourceMappingURL=ResponseModal.js.map