@falai/agent 1.2.7 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (508) 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 +1196 -1015
  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 +524 -134
  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/GeminiProvider.d.ts +3 -3
  126. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  127. package/dist/cjs/providers/GeminiProvider.js +16 -14
  128. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  129. package/dist/cjs/types/agent.d.ts +183 -54
  130. package/dist/cjs/types/agent.d.ts.map +1 -1
  131. package/dist/cjs/types/agent.js +0 -6
  132. package/dist/cjs/types/agent.js.map +1 -1
  133. package/dist/cjs/types/ai.d.ts +3 -3
  134. package/dist/cjs/types/ai.d.ts.map +1 -1
  135. package/dist/cjs/types/errors.d.ts +15 -0
  136. package/dist/cjs/types/errors.d.ts.map +1 -0
  137. package/dist/cjs/types/errors.js +22 -0
  138. package/dist/cjs/types/errors.js.map +1 -0
  139. package/dist/cjs/types/flow.d.ts +513 -0
  140. package/dist/cjs/types/flow.d.ts.map +1 -0
  141. package/dist/cjs/types/{route.js → flow.js} +2 -2
  142. package/dist/cjs/types/flow.js.map +1 -0
  143. package/dist/cjs/types/index.d.ts +7 -6
  144. package/dist/cjs/types/index.d.ts.map +1 -1
  145. package/dist/cjs/types/index.js +6 -2
  146. package/dist/cjs/types/index.js.map +1 -1
  147. package/dist/cjs/types/persistence.d.ts +11 -7
  148. package/dist/cjs/types/persistence.d.ts.map +1 -1
  149. package/dist/cjs/types/routing.d.ts +1 -1
  150. package/dist/cjs/types/routing.d.ts.map +1 -1
  151. package/dist/cjs/types/session.d.ts +24 -23
  152. package/dist/cjs/types/session.d.ts.map +1 -1
  153. package/dist/cjs/types/signals.d.ts +248 -0
  154. package/dist/cjs/types/signals.d.ts.map +1 -0
  155. package/dist/cjs/types/signals.js +11 -0
  156. package/dist/cjs/types/signals.js.map +1 -0
  157. package/dist/cjs/types/template.d.ts +2 -8
  158. package/dist/cjs/types/template.d.ts.map +1 -1
  159. package/dist/cjs/types/tool.d.ts +36 -29
  160. package/dist/cjs/types/tool.d.ts.map +1 -1
  161. package/dist/cjs/types/tool.js +1 -1
  162. package/dist/cjs/types/tool.js.map +1 -1
  163. package/dist/cjs/utils/condition.d.ts +7 -1
  164. package/dist/cjs/utils/condition.d.ts.map +1 -1
  165. package/dist/cjs/utils/condition.js.map +1 -1
  166. package/dist/cjs/utils/id.d.ts +13 -5
  167. package/dist/cjs/utils/id.d.ts.map +1 -1
  168. package/dist/cjs/utils/id.js +24 -10
  169. package/dist/cjs/utils/id.js.map +1 -1
  170. package/dist/cjs/utils/index.d.ts +2 -2
  171. package/dist/cjs/utils/index.d.ts.map +1 -1
  172. package/dist/cjs/utils/index.js +7 -3
  173. package/dist/cjs/utils/index.js.map +1 -1
  174. package/dist/cjs/utils/session.d.ts +44 -5
  175. package/dist/cjs/utils/session.d.ts.map +1 -1
  176. package/dist/cjs/utils/session.js +197 -38
  177. package/dist/cjs/utils/session.js.map +1 -1
  178. package/dist/constants/index.d.ts +0 -9
  179. package/dist/constants/index.d.ts.map +1 -1
  180. package/dist/constants/index.js +3 -9
  181. package/dist/constants/index.js.map +1 -1
  182. package/dist/core/Agent.d.ts +119 -153
  183. package/dist/core/Agent.d.ts.map +1 -1
  184. package/dist/core/Agent.js +472 -325
  185. package/dist/core/Agent.js.map +1 -1
  186. package/dist/core/AutoChainExecutor.d.ts +107 -0
  187. package/dist/core/AutoChainExecutor.d.ts.map +1 -0
  188. package/dist/core/AutoChainExecutor.js +293 -0
  189. package/dist/core/AutoChainExecutor.js.map +1 -0
  190. package/dist/core/BranchEvaluator.d.ts +54 -0
  191. package/dist/core/BranchEvaluator.d.ts.map +1 -0
  192. package/dist/core/BranchEvaluator.js +126 -0
  193. package/dist/core/BranchEvaluator.js.map +1 -0
  194. package/dist/core/DirectiveBus.d.ts +88 -0
  195. package/dist/core/DirectiveBus.d.ts.map +1 -0
  196. package/dist/core/DirectiveBus.js +192 -0
  197. package/dist/core/DirectiveBus.js.map +1 -0
  198. package/dist/core/DirectiveChainTracker.d.ts +49 -0
  199. package/dist/core/DirectiveChainTracker.d.ts.map +1 -0
  200. package/dist/core/DirectiveChainTracker.js +117 -0
  201. package/dist/core/DirectiveChainTracker.js.map +1 -0
  202. package/dist/core/Flow.d.ts +186 -0
  203. package/dist/core/Flow.d.ts.map +1 -0
  204. package/dist/core/Flow.js +546 -0
  205. package/dist/core/Flow.js.map +1 -0
  206. package/dist/core/FlowRouter.d.ts +182 -0
  207. package/dist/core/FlowRouter.d.ts.map +1 -0
  208. package/dist/core/{RoutingEngine.js → FlowRouter.js} +322 -305
  209. package/dist/core/FlowRouter.js.map +1 -0
  210. package/dist/core/PersistenceManager.d.ts +2 -2
  211. package/dist/core/PersistenceManager.d.ts.map +1 -1
  212. package/dist/core/PersistenceManager.js +7 -7
  213. package/dist/core/PersistenceManager.js.map +1 -1
  214. package/dist/core/PromptComposer.d.ts +21 -8
  215. package/dist/core/PromptComposer.d.ts.map +1 -1
  216. package/dist/core/PromptComposer.js +183 -106
  217. package/dist/core/PromptComposer.js.map +1 -1
  218. package/dist/core/PromptSectionCache.d.ts +1 -1
  219. package/dist/core/PromptSectionCache.js +1 -1
  220. package/dist/core/ResponseEngine.d.ts +18 -8
  221. package/dist/core/ResponseEngine.d.ts.map +1 -1
  222. package/dist/core/ResponseEngine.js +38 -36
  223. package/dist/core/ResponseEngine.js.map +1 -1
  224. package/dist/core/ResponseModal.d.ts +73 -56
  225. package/dist/core/ResponseModal.d.ts.map +1 -1
  226. package/dist/core/ResponseModal.js +1198 -1017
  227. package/dist/core/ResponseModal.js.map +1 -1
  228. package/dist/core/ResponsePipeline.d.ts +124 -26
  229. package/dist/core/ResponsePipeline.d.ts.map +1 -1
  230. package/dist/core/ResponsePipeline.js +524 -135
  231. package/dist/core/ResponsePipeline.js.map +1 -1
  232. package/dist/core/SignalEvaluator.d.ts +86 -0
  233. package/dist/core/SignalEvaluator.d.ts.map +1 -0
  234. package/dist/core/SignalEvaluator.js +326 -0
  235. package/dist/core/SignalEvaluator.js.map +1 -0
  236. package/dist/core/SignalProcessor.d.ts +152 -0
  237. package/dist/core/SignalProcessor.d.ts.map +1 -0
  238. package/dist/core/SignalProcessor.js +555 -0
  239. package/dist/core/SignalProcessor.js.map +1 -0
  240. package/dist/core/Step.d.ts +43 -32
  241. package/dist/core/Step.d.ts.map +1 -1
  242. package/dist/core/Step.js +220 -126
  243. package/dist/core/Step.js.map +1 -1
  244. package/dist/core/StreamingToolExecutor.d.ts +2 -2
  245. package/dist/core/StreamingToolExecutor.d.ts.map +1 -1
  246. package/dist/core/StreamingToolExecutor.js.map +1 -1
  247. package/dist/core/ToolManager.d.ts +44 -13
  248. package/dist/core/ToolManager.d.ts.map +1 -1
  249. package/dist/core/ToolManager.js +174 -91
  250. package/dist/core/ToolManager.js.map +1 -1
  251. package/dist/core/createAgent.d.ts +35 -0
  252. package/dist/core/createAgent.d.ts.map +1 -0
  253. package/dist/core/createAgent.js +36 -0
  254. package/dist/core/createAgent.js.map +1 -0
  255. package/dist/core/flow-namespace.d.ts +49 -0
  256. package/dist/core/flow-namespace.d.ts.map +1 -0
  257. package/dist/core/flow-namespace.js +168 -0
  258. package/dist/core/flow-namespace.js.map +1 -0
  259. package/dist/index.d.ts +11 -14
  260. package/dist/index.d.ts.map +1 -1
  261. package/dist/index.js +9 -12
  262. package/dist/index.js.map +1 -1
  263. package/dist/providers/GeminiProvider.d.ts +3 -3
  264. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  265. package/dist/providers/GeminiProvider.js +16 -14
  266. package/dist/providers/GeminiProvider.js.map +1 -1
  267. package/dist/types/agent.d.ts +183 -54
  268. package/dist/types/agent.d.ts.map +1 -1
  269. package/dist/types/agent.js +0 -6
  270. package/dist/types/agent.js.map +1 -1
  271. package/dist/types/ai.d.ts +3 -3
  272. package/dist/types/ai.d.ts.map +1 -1
  273. package/dist/types/errors.d.ts +15 -0
  274. package/dist/types/errors.d.ts.map +1 -0
  275. package/dist/types/errors.js +18 -0
  276. package/dist/types/errors.js.map +1 -0
  277. package/dist/types/flow.d.ts +513 -0
  278. package/dist/types/flow.d.ts.map +1 -0
  279. package/dist/types/flow.js +5 -0
  280. package/dist/types/flow.js.map +1 -0
  281. package/dist/types/index.d.ts +7 -6
  282. package/dist/types/index.d.ts.map +1 -1
  283. package/dist/types/index.js +4 -1
  284. package/dist/types/index.js.map +1 -1
  285. package/dist/types/persistence.d.ts +11 -7
  286. package/dist/types/persistence.d.ts.map +1 -1
  287. package/dist/types/routing.d.ts +1 -1
  288. package/dist/types/routing.d.ts.map +1 -1
  289. package/dist/types/session.d.ts +24 -23
  290. package/dist/types/session.d.ts.map +1 -1
  291. package/dist/types/signals.d.ts +248 -0
  292. package/dist/types/signals.d.ts.map +1 -0
  293. package/dist/types/signals.js +10 -0
  294. package/dist/types/signals.js.map +1 -0
  295. package/dist/types/template.d.ts +2 -8
  296. package/dist/types/template.d.ts.map +1 -1
  297. package/dist/types/tool.d.ts +36 -29
  298. package/dist/types/tool.d.ts.map +1 -1
  299. package/dist/types/tool.js +1 -1
  300. package/dist/types/tool.js.map +1 -1
  301. package/dist/utils/condition.d.ts +7 -1
  302. package/dist/utils/condition.d.ts.map +1 -1
  303. package/dist/utils/condition.js.map +1 -1
  304. package/dist/utils/id.d.ts +13 -5
  305. package/dist/utils/id.d.ts.map +1 -1
  306. package/dist/utils/id.js +22 -9
  307. package/dist/utils/id.js.map +1 -1
  308. package/dist/utils/index.d.ts +2 -2
  309. package/dist/utils/index.d.ts.map +1 -1
  310. package/dist/utils/index.js +2 -2
  311. package/dist/utils/index.js.map +1 -1
  312. package/dist/utils/session.d.ts +44 -5
  313. package/dist/utils/session.d.ts.map +1 -1
  314. package/dist/utils/session.js +193 -37
  315. package/dist/utils/session.js.map +1 -1
  316. package/docs/README.md +15 -202
  317. package/docs/concepts/architecture.md +281 -0
  318. package/docs/concepts/directives.md +400 -0
  319. package/docs/concepts/pipeline.md +399 -0
  320. package/docs/guides/branching.md +263 -0
  321. package/docs/guides/compaction.md +163 -0
  322. package/docs/guides/conditions.md +167 -0
  323. package/docs/guides/error-handling.md +176 -0
  324. package/docs/guides/flow-control.md +409 -0
  325. package/docs/guides/instructions.md +210 -0
  326. package/docs/guides/persistence.md +182 -0
  327. package/docs/guides/streaming.md +137 -0
  328. package/docs/migration/README.md +15 -0
  329. package/docs/migration/route-to-flow.md +560 -0
  330. package/docs/migration/v1-to-v2.md +909 -0
  331. package/docs/reference/adapters.md +481 -0
  332. package/docs/reference/branches.md +241 -0
  333. package/docs/reference/create-agent.md +186 -0
  334. package/docs/reference/directive.md +243 -0
  335. package/docs/reference/errors.md +122 -0
  336. package/docs/reference/flow.md +238 -0
  337. package/docs/reference/instruction.md +177 -0
  338. package/docs/reference/pre-directive.md +131 -0
  339. package/docs/reference/providers.md +227 -0
  340. package/docs/reference/signals.md +356 -0
  341. package/docs/reference/step.md +339 -0
  342. package/docs/reference/tool.md +269 -0
  343. package/docs/start/01-install.md +81 -0
  344. package/docs/start/02-first-agent.md +196 -0
  345. package/docs/start/03-collect-data.md +222 -0
  346. package/docs/start/04-add-tools.md +276 -0
  347. package/docs/start/05-go-to-production.md +216 -0
  348. package/examples/01-quickstart.ts +20 -0
  349. package/examples/02-data-extraction.ts +90 -0
  350. package/examples/03-tools.ts +136 -0
  351. package/examples/04-instructions.ts +100 -0
  352. package/examples/05-branching.ts +140 -0
  353. package/examples/06-flow-control.ts +103 -0
  354. package/examples/07-streaming.ts +69 -0
  355. package/examples/08-persistence.ts +98 -0
  356. package/examples/09-signals.ts +144 -0
  357. package/examples/tsconfig.json +30 -0
  358. package/package.json +2 -1
  359. package/src/adapters/MemoryAdapter.ts +3 -3
  360. package/src/adapters/MongoAdapter.ts +3 -3
  361. package/src/adapters/OpenSearchAdapter.ts +10 -8
  362. package/src/adapters/PostgreSQLAdapter.ts +26 -10
  363. package/src/adapters/PrismaAdapter.ts +6 -6
  364. package/src/adapters/RedisAdapter.ts +3 -3
  365. package/src/adapters/SQLiteAdapter.ts +31 -12
  366. package/src/constants/index.ts +2 -10
  367. package/src/core/Agent.ts +585 -374
  368. package/src/core/AutoChainExecutor.ts +440 -0
  369. package/src/core/BranchEvaluator.ts +167 -0
  370. package/src/core/DirectiveBus.ts +248 -0
  371. package/src/core/DirectiveChainTracker.ts +144 -0
  372. package/src/core/Flow.ts +666 -0
  373. package/src/core/{RoutingEngine.ts → FlowRouter.ts} +385 -365
  374. package/src/core/PersistenceManager.ts +8 -8
  375. package/src/core/PromptComposer.ts +209 -140
  376. package/src/core/PromptSectionCache.ts +1 -1
  377. package/src/core/ResponseEngine.ts +61 -46
  378. package/src/core/ResponseModal.ts +1458 -1241
  379. package/src/core/ResponsePipeline.ts +675 -173
  380. package/src/core/SignalEvaluator.ts +420 -0
  381. package/src/core/SignalProcessor.ts +723 -0
  382. package/src/core/Step.ts +279 -176
  383. package/src/core/StreamingToolExecutor.ts +4 -4
  384. package/src/core/ToolManager.ts +200 -97
  385. package/src/core/createAgent.ts +40 -0
  386. package/src/core/flow-namespace.ts +219 -0
  387. package/src/index.ts +42 -36
  388. package/src/providers/GeminiProvider.ts +17 -15
  389. package/src/types/agent.ts +182 -53
  390. package/src/types/ai.ts +3 -3
  391. package/src/types/errors.ts +18 -0
  392. package/src/types/flow.ts +590 -0
  393. package/src/types/index.ts +43 -16
  394. package/src/types/persistence.ts +12 -8
  395. package/src/types/routing.ts +1 -1
  396. package/src/types/session.ts +26 -23
  397. package/src/types/signals.ts +321 -0
  398. package/src/types/template.ts +3 -11
  399. package/src/types/tool.ts +50 -42
  400. package/src/utils/condition.ts +13 -4
  401. package/src/utils/id.ts +27 -9
  402. package/src/utils/index.ts +6 -2
  403. package/src/utils/session.ts +238 -42
  404. package/dist/cjs/core/BatchExecutor.d.ts +0 -359
  405. package/dist/cjs/core/BatchExecutor.d.ts.map +0 -1
  406. package/dist/cjs/core/BatchExecutor.js +0 -861
  407. package/dist/cjs/core/BatchExecutor.js.map +0 -1
  408. package/dist/cjs/core/BatchPromptBuilder.d.ts +0 -89
  409. package/dist/cjs/core/BatchPromptBuilder.d.ts.map +0 -1
  410. package/dist/cjs/core/BatchPromptBuilder.js +0 -223
  411. package/dist/cjs/core/BatchPromptBuilder.js.map +0 -1
  412. package/dist/cjs/core/Route.d.ts +0 -180
  413. package/dist/cjs/core/Route.d.ts.map +0 -1
  414. package/dist/cjs/core/Route.js +0 -542
  415. package/dist/cjs/core/Route.js.map +0 -1
  416. package/dist/cjs/core/RoutingEngine.d.ts +0 -185
  417. package/dist/cjs/core/RoutingEngine.d.ts.map +0 -1
  418. package/dist/cjs/core/RoutingEngine.js.map +0 -1
  419. package/dist/cjs/types/route.d.ts +0 -336
  420. package/dist/cjs/types/route.d.ts.map +0 -1
  421. package/dist/cjs/types/route.js.map +0 -1
  422. package/dist/core/BatchExecutor.d.ts +0 -359
  423. package/dist/core/BatchExecutor.d.ts.map +0 -1
  424. package/dist/core/BatchExecutor.js +0 -856
  425. package/dist/core/BatchExecutor.js.map +0 -1
  426. package/dist/core/BatchPromptBuilder.d.ts +0 -89
  427. package/dist/core/BatchPromptBuilder.d.ts.map +0 -1
  428. package/dist/core/BatchPromptBuilder.js +0 -219
  429. package/dist/core/BatchPromptBuilder.js.map +0 -1
  430. package/dist/core/Route.d.ts +0 -180
  431. package/dist/core/Route.d.ts.map +0 -1
  432. package/dist/core/Route.js +0 -538
  433. package/dist/core/Route.js.map +0 -1
  434. package/dist/core/RoutingEngine.d.ts +0 -185
  435. package/dist/core/RoutingEngine.d.ts.map +0 -1
  436. package/dist/core/RoutingEngine.js.map +0 -1
  437. package/dist/types/route.d.ts +0 -336
  438. package/dist/types/route.d.ts.map +0 -1
  439. package/dist/types/route.js +0 -5
  440. package/dist/types/route.js.map +0 -1
  441. package/docs/CONTRIBUTING.md +0 -521
  442. package/docs/api/README.md +0 -3299
  443. package/docs/api/overview.md +0 -1410
  444. package/docs/architecture/data-extraction-flow.md +0 -360
  445. package/docs/architecture/multi-step-execution.md +0 -277
  446. package/docs/core/agent/README.md +0 -938
  447. package/docs/core/agent/context-management.md +0 -796
  448. package/docs/core/agent/rules-and-prohibitions.md +0 -113
  449. package/docs/core/agent/session-management.md +0 -693
  450. package/docs/core/ai-integration/prompt-composition.md +0 -355
  451. package/docs/core/ai-integration/providers.md +0 -515
  452. package/docs/core/ai-integration/response-processing.md +0 -433
  453. package/docs/core/conversation-flows/data-collection.md +0 -772
  454. package/docs/core/conversation-flows/route-dsl.md +0 -509
  455. package/docs/core/conversation-flows/routes.md +0 -249
  456. package/docs/core/conversation-flows/step-transitions.md +0 -731
  457. package/docs/core/conversation-flows/steps.md +0 -268
  458. package/docs/core/error-handling.md +0 -830
  459. package/docs/core/persistence/adapters.md +0 -255
  460. package/docs/core/persistence/session-storage.md +0 -656
  461. package/docs/core/routing/intelligent-routing.md +0 -470
  462. package/docs/core/tools/enhanced-tool.md +0 -186
  463. package/docs/core/tools/streaming-execution.md +0 -161
  464. package/docs/core/tools/tool-definition.md +0 -970
  465. package/docs/core/tools/tool-scoping.md +0 -819
  466. package/docs/guides/advanced-patterns/publishing.md +0 -186
  467. package/docs/guides/context-compaction.md +0 -96
  468. package/docs/guides/error-handling-patterns.md +0 -578
  469. package/docs/guides/getting-started/README.md +0 -795
  470. package/docs/guides/migration/README.md +0 -101
  471. package/docs/guides/migration/flexible-routing-conditions.md +0 -375
  472. package/docs/guides/migration/multi-step-execution.md +0 -393
  473. package/docs/guides/migration/response-modal-refactor.md +0 -518
  474. package/docs/guides/prompt-optimization.md +0 -164
  475. package/examples/advanced-patterns/context-compaction.ts +0 -223
  476. package/examples/advanced-patterns/knowledge-based-agent.ts +0 -735
  477. package/examples/advanced-patterns/persistent-onboarding.ts +0 -728
  478. package/examples/advanced-patterns/route-lifecycle-hooks.ts +0 -556
  479. package/examples/advanced-patterns/streaming-responses.ts +0 -656
  480. package/examples/ai-providers/anthropic-integration.ts +0 -388
  481. package/examples/ai-providers/openai-integration.ts +0 -228
  482. package/examples/condition-patterns/function-only-conditions.ts +0 -365
  483. package/examples/condition-patterns/mixed-array-conditions.ts +0 -477
  484. package/examples/condition-patterns/route-skipif-patterns.ts +0 -468
  485. package/examples/condition-patterns/step-skipif-patterns.ts +0 -0
  486. package/examples/condition-patterns/string-only-conditions.ts +0 -296
  487. package/examples/conversation-flows/completion-transitions.ts +0 -318
  488. package/examples/core-concepts/basic-agent.ts +0 -503
  489. package/examples/core-concepts/modern-streaming-api.ts +0 -309
  490. package/examples/core-concepts/schema-driven-extraction.ts +0 -332
  491. package/examples/core-concepts/session-management.ts +0 -494
  492. package/examples/integrations/database-integration.ts +0 -631
  493. package/examples/integrations/healthcare-integration.ts +0 -595
  494. package/examples/integrations/search-integration.ts +0 -530
  495. package/examples/integrations/server-session-management.ts +0 -307
  496. package/examples/persistence/custom-adapter.ts +0 -526
  497. package/examples/persistence/database-persistence.ts +0 -583
  498. package/examples/persistence/memory-sessions.ts +0 -495
  499. package/examples/persistence/prisma-schema.example.prisma +0 -74
  500. package/examples/persistence/redis-persistence.ts +0 -488
  501. package/examples/tools/basic-tools.ts +0 -765
  502. package/examples/tools/data-enrichment-tools.ts +0 -593
  503. package/examples/tools/enhanced-tool-metadata.ts +0 -268
  504. package/examples/tools/streaming-tool-execution.ts +0 -283
  505. package/src/core/BatchExecutor.ts +0 -1187
  506. package/src/core/BatchPromptBuilder.ts +0 -299
  507. package/src/core/Route.ts +0 -678
  508. 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,19 +1613,21 @@ 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);
1341
1620
  // Map to store tool execution results for history
1342
1621
  const toolResultsMap = new Map();
1622
+ // Map to store tool call arguments for history reconstruction
1623
+ const toolArgsMap = new Map();
1343
1624
  // Execute initial dynamic tool calls
1344
1625
  if (toolCalls && toolCalls.length > 0) {
1345
1626
  utils_1.logger.debug(`[ResponseModal] Executing ${toolCalls.length} dynamic tool calls:`, toolCalls.map(tc => tc.toolName));
1346
1627
  for (const toolCall of toolCalls) {
1347
- const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1628
+ const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
1348
1629
  if (!tool) {
1349
- 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.`);
1350
1631
  continue;
1351
1632
  }
1352
1633
  try {
@@ -1370,6 +1651,7 @@ class ResponseModal {
1370
1651
  }
1371
1652
  // Store the actual tool result data for history
1372
1653
  toolResultsMap.set(toolCall.toolName, (0, utils_1.serializeToolResult)(toolResult));
1654
+ toolArgsMap.set(toolCall.toolName, toolCall.arguments);
1373
1655
  // Check if tool execution was successful
1374
1656
  if (!toolResult.success) {
1375
1657
  utils_1.logger.error(`[ResponseModal] Tool execution failed: ${toolCall.toolName} - ${toolResult.error}`);
@@ -1419,7 +1701,7 @@ class ResponseModal {
1419
1701
  // Create tool result history items
1420
1702
  const toolResultHistoryItems = [];
1421
1703
  for (const toolCall of toolCalls || []) {
1422
- const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1704
+ const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
1423
1705
  if (tool) {
1424
1706
  // Create HistoryItem format for tool results
1425
1707
  // Add assistant message with tool_calls
@@ -1477,9 +1759,9 @@ class ResponseModal {
1477
1759
  utils_1.logger.debug(`[ResponseModal] Follow-up call produced ${followUpToolCalls.length} additional tool calls`);
1478
1760
  // Execute the follow-up tool calls
1479
1761
  for (const toolCall of followUpToolCalls) {
1480
- const tool = this.findAvailableTool(toolCall.toolName, selectedRoute);
1762
+ const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
1481
1763
  if (!tool) {
1482
- 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.`);
1483
1765
  continue;
1484
1766
  }
1485
1767
  try {
@@ -1527,6 +1809,7 @@ class ResponseModal {
1527
1809
  }
1528
1810
  // Store the follow-up tool result for potential next loop iteration
1529
1811
  toolResultsMap.set(toolCall.toolName, (0, utils_1.serializeToolResult)(toolResult));
1812
+ toolArgsMap.set(toolCall.toolName, toolCall.arguments);
1530
1813
  utils_1.logger.debug(`[ResponseModal] Executed follow-up tool: ${toolCall.toolName} (success: ${toolResult.success})`);
1531
1814
  }
1532
1815
  catch (error) {
@@ -1547,7 +1830,7 @@ class ResponseModal {
1547
1830
  }
1548
1831
  }
1549
1832
  if (toolLoopCount >= MAX_TOOL_LOOPS) {
1550
- 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.`);
1551
1834
  }
1552
1835
  // If tools were executed but no final text message was produced,
1553
1836
  // make one more LLM call to generate a proper text response from tool results.
@@ -1567,7 +1850,7 @@ class ResponseModal {
1567
1850
  tool_calls: [{
1568
1851
  id: toolName,
1569
1852
  name: toolName,
1570
- arguments: {},
1853
+ arguments: toolArgsMap.get(toolName) || {},
1571
1854
  }],
1572
1855
  });
1573
1856
  finalToolResultHistoryItems.push({
@@ -1630,30 +1913,30 @@ class ResponseModal {
1630
1913
  */
1631
1914
  async collectDataFromResponse(params) {
1632
1915
  try {
1633
- const { result, selectedRoute, nextStep, session } = params;
1916
+ const { result, selectedFlow, nextStep, session } = params;
1634
1917
  let updatedSession = session;
1635
- // Extract collected data from final response (only for route-based interactions)
1636
- if (selectedRoute && result.structured) {
1918
+ // Extract collected data from final response (only for flow-based interactions)
1919
+ if (selectedFlow && result.structured) {
1637
1920
  try {
1638
1921
  const collectedData = {};
1639
1922
  // AgentStructuredResponse extends Record<string, unknown>, so we can safely access properties
1640
1923
  const structuredData = result.structured;
1641
- // Collect ALL route fields (required + optional) from structured response
1642
- const allRouteFields = new Set();
1643
- // Add route required fields
1644
- if (selectedRoute.requiredFields) {
1645
- 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)));
1646
1929
  }
1647
- // Add route optional fields
1648
- if (selectedRoute.optionalFields) {
1649
- 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)));
1650
1933
  }
1651
- // 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)
1652
1935
  if (nextStep?.collect) {
1653
- nextStep.collect.forEach(field => allRouteFields.add(String(field)));
1936
+ nextStep.collect.forEach(field => allFlowFields.add(String(field)));
1654
1937
  }
1655
1938
  // Extract all available fields from structured response
1656
- for (const field of allRouteFields) {
1939
+ for (const field of allFlowFields) {
1657
1940
  const fieldKey = String(field);
1658
1941
  if (fieldKey in structuredData && structuredData[fieldKey] !== undefined && structuredData[fieldKey] !== null) {
1659
1942
  collectedData[fieldKey] = structuredData[fieldKey];
@@ -1703,207 +1986,122 @@ class ResponseModal {
1703
1986
  }
1704
1987
  }
1705
1988
  /**
1706
- * 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
+ *
1707
2011
  * @private
1708
2012
  */
1709
- async handleRouteCompletion(params) {
1710
- const { selectedRoute, session, context, history, historyEvents, signal } = params;
1711
- // Get endStep spec from route
1712
- const endStepSpec = selectedRoute.endStepSpec;
1713
- // Create a temporary step for completion message generation using endStep configuration
1714
- const completionStep = new Step_1.Step(selectedRoute.id, {
1715
- description: endStepSpec.description,
1716
- id: endStepSpec.id || constants_1.END_ROUTE_ID,
1717
- collect: endStepSpec.collect,
1718
- requires: endStepSpec.requires,
1719
- 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,
1720
2029
  });
1721
- // Build response schema for completion (message only, no data collection)
1722
- const completionSchema = {
1723
- type: "object",
1724
- properties: {
1725
- message: {
1726
- type: "string",
1727
- description: "A natural, warm farewell message for the user. Must NOT contain task names, field names, collected data, or any internal/technical information.",
1728
- },
1729
- },
1730
- required: ["message"],
1731
- additionalProperties: false,
1732
- };
1733
- const templateContext = (0, template_1.createTemplateContext)({ context, session, history: historyEvents });
1734
- // Build completion response prompt using ResponseEngine
1735
- // Filter out conditional guidelines - only include always-active ones
1736
- const alwaysActiveGuidelines = [
1737
- ...this.agent.getGuidelines().filter(g => !g.condition),
1738
- ...selectedRoute.getGuidelines().filter(g => !g.condition),
1739
- ];
1740
- let completitionPrompt = "Send a brief, natural farewell message. Do NOT mention internal data or task details.";
1741
- if (endStepSpec.prompt) {
1742
- completitionPrompt = await (0, utils_1.render)(endStepSpec.prompt, templateContext);
1743
- }
1744
- const completionPrompt = await this.responseEngine.buildResponsePrompt({
1745
- route: selectedRoute,
1746
- currentStep: completionStep,
1747
- rules: selectedRoute.getRules(),
1748
- prohibitions: selectedRoute.getProhibitions(),
1749
- directives: [
1750
- "The conversation task has been completed successfully",
1751
- "Generate a natural, friendly farewell message for the user",
1752
- "Do NOT mention task names, route names, collected data, field names, or any internal/technical information",
1753
- "Do NOT list or summarize the data you collected - the user already knows what they told you",
1754
- "Do NOT use words like 'tarefa', 'dados coletados', 'prospecção', 'concluída' or similar internal terms",
1755
- "Keep it brief, warm, and conversational - as if ending a natural conversation",
1756
- "Do NOT ask for more information - the conversation is ending",
1757
- completitionPrompt,
1758
- ],
1759
- history: historyEvents,
1760
- agentOptions: this.agent.getAgentOptions(),
1761
- combinedGuidelines: alwaysActiveGuidelines, // Only non-conditional guidelines
1762
- combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
1763
- context,
1764
- session,
1765
- agentSchema: undefined, // No data collection schema for completion
1766
- });
1767
- // Generate completion message using AI provider
1768
- const agentOptions = this.agent.getAgentOptions();
1769
- utils_1.logger.debug(`[ResponseModal] Calling AI provider for completion message...`);
1770
- const completionResult = await agentOptions.provider.generateMessage({
1771
- prompt: completionPrompt,
1772
- history, // Use HistoryItem[] for AI provider
1773
- context,
1774
- signal,
1775
- parameters: { jsonSchema: completionSchema, schemaName: "completion_message" },
1776
- });
1777
- utils_1.logger.debug(`[ResponseModal] AI provider returned completion result`);
1778
- const message = completionResult.structured?.message || completionResult.message;
1779
- utils_1.logger.debug(`[ResponseModal] Generated completion message for route: ${selectedRoute.title}`);
1780
- // Check for onComplete transition
1781
- const transitionConfig = await selectedRoute.evaluateOnComplete({ data: session.data }, context);
2030
+ // 3) Wire pendingDirective when onComplete returned a target.
1782
2031
  if (transitionConfig) {
1783
- // Find target route by ID or title
1784
- const targetRoute = this.agent.getRoutes().find((r) => r.id === transitionConfig.nextStep || r.title === transitionConfig.nextStep);
1785
- if (targetRoute) {
1786
- const renderedCondition = await (0, utils_1.render)(transitionConfig.condition, templateContext);
1787
- // Set pending transition in session
1788
- session.pendingTransition = {
1789
- targetRouteId: targetRoute.id,
1790
- condition: renderedCondition,
1791
- 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
+ },
1792
2043
  };
1793
- 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}`);
1794
2045
  }
1795
- else {
1796
- 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.`);
1797
2049
  }
1798
2050
  }
1799
- return message;
2051
+ else {
2052
+ utils_1.logger.debug(`[ResponseModal] Flow ${selectedFlow.title} completed; session released to idle.`);
2053
+ }
2054
+ return nextSession;
1800
2055
  }
1801
2056
  /**
1802
- * Stream route completion response
2057
+ * Stream flow completion response
1803
2058
  * @private
1804
2059
  */
1805
- async *streamRouteCompletion(params) {
1806
- const { selectedRoute, context, history, historyEvents, signal } = params;
1807
- let session = params.session;
1808
- // Get endStep spec from route
1809
- const endStepSpec = selectedRoute.endStepSpec;
1810
- // Create a temporary step for completion message generation using endStep configuration
1811
- const completionStep = new Step_1.Step(selectedRoute.id, {
1812
- description: endStepSpec.description,
1813
- id: endStepSpec.id || constants_1.END_ROUTE_ID,
1814
- collect: endStepSpec.collect,
1815
- requires: endStepSpec.requires,
1816
- 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.",
1817
- });
1818
- // Build response schema for completion
1819
- const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, completionStep, this.agent.getSchema());
1820
- const templateContext = (0, template_1.createTemplateContext)({ context, session, history: historyEvents }); // Use Event[] for template context
1821
- // Build completion response prompt
1822
- const completionPrompt = await this.responseEngine.buildResponsePrompt({
1823
- route: selectedRoute,
1824
- currentStep: completionStep,
1825
- rules: selectedRoute.getRules(),
1826
- prohibitions: selectedRoute.getProhibitions(),
1827
- directives: undefined, // No directives for completion
1828
- history: historyEvents,
1829
- agentOptions: this.agent.getAgentOptions(),
1830
- combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
1831
- combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
1832
- context,
1833
- session,
1834
- agentSchema: this.agent.getSchema(),
1835
- });
1836
- // Stream completion message using AI provider
1837
- const agentOptions = this.agent.getAgentOptions();
1838
- const stream = agentOptions.provider.generateMessageStream({
1839
- prompt: completionPrompt,
1840
- 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,
1841
2078
  context,
1842
- signal,
1843
- parameters: { jsonSchema: responseSchema, schemaName: "completion_message_stream" },
2079
+ history,
1844
2080
  });
1845
- utils_1.logger.debug(`[ResponseModal] Streaming completion message for route: ${selectedRoute.title}`);
1846
- // Check for onComplete transition
1847
- const transitionConfig = await selectedRoute.evaluateOnComplete({ data: session.data }, context);
1848
- if (transitionConfig) {
1849
- // Find target route by ID or title
1850
- const targetRoute = this.agent.getRoutes().find((r) => r.id === transitionConfig.nextStep || r.title === transitionConfig.nextStep);
1851
- if (targetRoute) {
1852
- const renderedCondition = await (0, utils_1.render)(transitionConfig.condition, templateContext);
1853
- // Set pending transition in session
1854
- session = {
1855
- ...session,
1856
- pendingTransition: {
1857
- targetRouteId: targetRoute.id,
1858
- condition: renderedCondition,
1859
- reason: "route_complete",
1860
- },
1861
- };
1862
- utils_1.logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed with pending transition to: ${targetRoute.title}`);
1863
- }
1864
- else {
1865
- utils_1.logger.warn(`[ResponseModal] Route ${selectedRoute.title} completed but target route not found: ${transitionConfig.nextStep}`);
1866
- }
1867
- }
1868
- // Set step to END_ROUTE marker
1869
- session = (0, utils_1.enterStep)(session, constants_1.END_ROUTE_ID, "Route completed");
1870
- utils_1.logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`);
1871
- // Stream completion chunks
1872
- for await (const chunk of stream) {
1873
- // Update current session if we have one
1874
- if (chunk.done) {
1875
- await this.finalizeSession(session, context);
1876
- }
1877
- // Response structure completeness (Requirement 8.1, 8.2, 8.3)
1878
- // - executedSteps: empty for route completion (no new steps executed)
1879
- // - stoppedReason: 'route_complete' for completed routes
1880
- // - session.currentStep: set to END_ROUTE
1881
- yield {
1882
- delta: chunk.delta,
1883
- accumulated: chunk.accumulated,
1884
- done: chunk.done,
1885
- session,
1886
- toolCalls: undefined,
1887
- isRouteComplete: true,
1888
- executedSteps: chunk.done ? [] : undefined,
1889
- stoppedReason: chunk.done ? 'route_complete' : undefined,
1890
- metadata: chunk.metadata,
1891
- structured: chunk.structured,
1892
- };
1893
- }
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
+ };
1894
2092
  }
1895
2093
  /**
1896
- * Generate fallback response when no routes are available
2094
+ * Generate fallback response when no flows are available
1897
2095
  * @private
1898
2096
  */
1899
2097
  async generateFallbackResponse(params) {
1900
2098
  const { history, context, session, signal } = params;
1901
- utils_1.logger.debug(`[ResponseModal] No route selected, generating basic response`);
1902
- // Build basic response prompt without route context
1903
- 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({
1904
2102
  agentOptions: this.agent.getAgentOptions(),
1905
2103
  terms: this.agent.getTerms(),
1906
- guidelines: this.agent.getGuidelines(),
2104
+ instructions: this.collectScopedInstructions(),
1907
2105
  context,
1908
2106
  session,
1909
2107
  });
@@ -1923,18 +2121,18 @@ class ResponseModal {
1923
2121
  schemaName: "fallback_response",
1924
2122
  },
1925
2123
  });
1926
- return result.structured?.message || result.message;
2124
+ return { message: result.structured?.message || result.message, appliedInstructions };
1927
2125
  }
1928
2126
  /**
1929
- * Stream fallback response when no routes are available
2127
+ * Stream fallback response when no flows are available
1930
2128
  * @private
1931
2129
  */
1932
2130
  async *streamFallbackResponse(params) {
1933
2131
  const { history, context, session, signal } = params;
1934
- const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
2132
+ const { prompt: fallbackPrompt, appliedInstructions } = await this.responseEngine.buildFallbackPrompt({
1935
2133
  agentOptions: this.agent.getAgentOptions(),
1936
2134
  terms: this.agent.getTerms(),
1937
- guidelines: this.agent.getGuidelines(),
2135
+ instructions: this.collectScopedInstructions(),
1938
2136
  context,
1939
2137
  session,
1940
2138
  });
@@ -1960,8 +2158,8 @@ class ResponseModal {
1960
2158
  await this.finalizeSession(session, context);
1961
2159
  }
1962
2160
  // Response structure completeness (Requirement 8.1, 8.2, 8.3)
1963
- // - executedSteps: empty for fallback (no route/step execution)
1964
- // - stoppedReason: undefined for fallback (no route context)
2161
+ // - executedSteps: empty for fallback (no flow/step execution)
2162
+ // - stoppedReason: undefined for fallback (no flow context)
1965
2163
  // - session.currentStep: unchanged (no step progression)
1966
2164
  yield {
1967
2165
  delta: chunk.delta,
@@ -1969,11 +2167,12 @@ class ResponseModal {
1969
2167
  done: chunk.done,
1970
2168
  session,
1971
2169
  toolCalls: undefined,
1972
- isRouteComplete: false,
2170
+ isFlowComplete: false,
1973
2171
  executedSteps: chunk.done ? [] : undefined,
1974
2172
  stoppedReason: undefined,
1975
2173
  metadata: chunk.metadata,
1976
2174
  structured: chunk.structured,
2175
+ appliedInstructions: chunk.done ? appliedInstructions : undefined,
1977
2176
  };
1978
2177
  }
1979
2178
  }
@@ -1994,52 +2193,52 @@ class ResponseModal {
1994
2193
  // Execute finalize function
1995
2194
  await this.executeStepFinalize(session, context);
1996
2195
  // Update current session if we have one
1997
- const currentSession = this.agent.getCurrentSession();
2196
+ const currentSession = this.agent.currentSession;
1998
2197
  if (currentSession) {
1999
- this.agent.setCurrentSession(session);
2198
+ this.agent.currentSession = session;
2000
2199
  }
2001
2200
  }
2002
2201
  // ============================================================================
2003
2202
  // UTILITY METHODS - Helper methods for tool management and other utilities
2004
2203
  // ============================================================================
2005
2204
  /**
2006
- * 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
2007
2206
  * Delegates to ToolManager for unified tool resolution
2008
2207
  * @private
2009
2208
  */
2010
- findAvailableTool(toolName, route) {
2209
+ findAvailableTool(toolName, flow) {
2011
2210
  // Use ToolManager for unified tool resolution
2012
2211
  const toolManager = this.getToolManager();
2013
2212
  if (toolManager) {
2014
- return toolManager.find(toolName, undefined, undefined, route);
2213
+ return toolManager.find(toolName, undefined, undefined, flow);
2015
2214
  }
2016
2215
  // Fallback to legacy resolution if ToolManager not available
2017
2216
  utils_1.logger.warn(`[ResponseModal] ToolManager not available, using legacy tool resolution for: ${toolName}`);
2018
- // Check route-level tools first (if route provided)
2019
- if (route) {
2020
- const routeTool = route
2217
+ // Check flow-level tools first (if flow provided)
2218
+ if (flow) {
2219
+ const flowTool = flow
2021
2220
  .getTools()
2022
- .find((tool) => tool.id === toolName || tool.name === toolName);
2023
- if (routeTool)
2024
- return routeTool;
2221
+ .find((tool) => tool.id === toolName || tool.id === toolName);
2222
+ if (flowTool)
2223
+ return flowTool;
2025
2224
  }
2026
2225
  // Fall back to agent-level tools
2027
2226
  const agentTools = this.agent.getTools();
2028
- return agentTools.find((tool) => tool.id === toolName || tool.name === toolName);
2227
+ return agentTools.find((tool) => tool.id === toolName || tool.id === toolName);
2029
2228
  }
2030
2229
  /**
2031
- * 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
2032
2231
  * Delegates to ToolManager for unified tool resolution and deduplication
2033
2232
  * @private
2034
2233
  */
2035
- collectAvailableTools(route, step) {
2234
+ collectAvailableTools(flow, step) {
2036
2235
  // Use ToolManager for unified tool collection if available
2037
2236
  const toolManager = this.getToolManager();
2038
2237
  if (toolManager) {
2039
- const availableTools = toolManager.getAvailable(undefined, step, route);
2238
+ const availableTools = toolManager.getAvailable(undefined, step, flow);
2040
2239
  return availableTools.map((tool) => ({
2041
2240
  id: tool.id,
2042
- name: tool.name || tool.id,
2241
+ name: tool.id || tool.id,
2043
2242
  description: tool.description,
2044
2243
  parameters: tool.parameters,
2045
2244
  }));
@@ -2051,9 +2250,9 @@ class ResponseModal {
2051
2250
  this.agent.getTools().forEach((tool) => {
2052
2251
  availableTools.set(tool.id, tool);
2053
2252
  });
2054
- // Add route-level tools (these take precedence)
2055
- if (route) {
2056
- route.getTools().forEach((tool) => {
2253
+ // Add flow-level tools (these take precedence)
2254
+ if (flow) {
2255
+ flow.getTools().forEach((tool) => {
2057
2256
  availableTools.set(tool.id, tool);
2058
2257
  });
2059
2258
  }
@@ -2096,7 +2295,7 @@ class ResponseModal {
2096
2295
  // Convert to the format expected by AI providers
2097
2296
  return Array.from(availableTools.values()).map((tool) => ({
2098
2297
  id: tool.id,
2099
- name: tool.name || tool.id,
2298
+ name: tool.id || tool.id,
2100
2299
  description: tool.description,
2101
2300
  parameters: tool.parameters,
2102
2301
  }));
@@ -2105,7 +2304,7 @@ class ResponseModal {
2105
2304
  * Execute a prepare or finalize function/tool
2106
2305
  * @private
2107
2306
  */
2108
- async executePrepareFinalize(prepareOrFinalize, context, data, route, step) {
2307
+ async executePrepareFinalize(prepareOrFinalize, context, data, flow, step) {
2109
2308
  if (!prepareOrFinalize)
2110
2309
  return;
2111
2310
  if (typeof prepareOrFinalize === "function") {
@@ -2119,7 +2318,7 @@ class ResponseModal {
2119
2318
  // Tool ID - use ToolManager for unified resolution
2120
2319
  const toolManager = this.getToolManager();
2121
2320
  if (toolManager) {
2122
- tool = toolManager.find(prepareOrFinalize, undefined, step, route);
2321
+ tool = toolManager.find(prepareOrFinalize, undefined, step, flow);
2123
2322
  }
2124
2323
  else {
2125
2324
  // Fallback to legacy resolution if ToolManager not available
@@ -2129,9 +2328,9 @@ class ResponseModal {
2129
2328
  this.agent.getTools().forEach((t) => {
2130
2329
  availableTools.set(t.id, t);
2131
2330
  });
2132
- // Add route-level tools
2133
- if (route) {
2134
- route.getTools().forEach((t) => {
2331
+ // Add flow-level tools
2332
+ if (flow) {
2333
+ flow.getTools().forEach((t) => {
2135
2334
  availableTools.set(t.id, t);
2136
2335
  });
2137
2336
  }
@@ -2183,24 +2382,6 @@ class ResponseModal {
2183
2382
  }
2184
2383
  }
2185
2384
  }
2186
- /**
2187
- * Merge terms with route-specific taking precedence on conflicts
2188
- * @private
2189
- */
2190
- mergeTerms(agentTerms, routeTerms) {
2191
- const merged = new Map();
2192
- // Add agent terms first
2193
- agentTerms.forEach((term) => {
2194
- const name = typeof term.name === "string" ? term.name : term.name.toString();
2195
- merged.set(name, term);
2196
- });
2197
- // Add route terms (these take precedence)
2198
- routeTerms.forEach((term) => {
2199
- const name = typeof term.name === "string" ? term.name : term.name.toString();
2200
- merged.set(name, term);
2201
- });
2202
- return Array.from(merged.values());
2203
- }
2204
2385
  }
2205
2386
  exports.ResponseModal = ResponseModal;
2206
2387
  //# sourceMappingURL=ResponseModal.js.map