@falai/agent 2.2.4 → 2.4.2

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 (397) hide show
  1. package/README.md +2 -2
  2. package/dist/adapters/MemoryAdapter.d.ts.map +1 -1
  3. package/dist/adapters/MemoryAdapter.js +10 -1
  4. package/dist/adapters/MemoryAdapter.js.map +1 -1
  5. package/dist/adapters/MongoAdapter.d.ts.map +1 -1
  6. package/dist/adapters/MongoAdapter.js +33 -2
  7. package/dist/adapters/MongoAdapter.js.map +1 -1
  8. package/dist/adapters/OpenSearchAdapter.d.ts.map +1 -1
  9. package/dist/adapters/OpenSearchAdapter.js +15 -1
  10. package/dist/adapters/OpenSearchAdapter.js.map +1 -1
  11. package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  12. package/dist/adapters/PostgreSQLAdapter.js +35 -5
  13. package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
  14. package/dist/adapters/PrismaAdapter.d.ts.map +1 -1
  15. package/dist/adapters/PrismaAdapter.js +58 -15
  16. package/dist/adapters/PrismaAdapter.js.map +1 -1
  17. package/dist/adapters/RedisAdapter.d.ts.map +1 -1
  18. package/dist/adapters/RedisAdapter.js +10 -1
  19. package/dist/adapters/RedisAdapter.js.map +1 -1
  20. package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
  21. package/dist/adapters/SQLiteAdapter.js +38 -6
  22. package/dist/adapters/SQLiteAdapter.js.map +1 -1
  23. package/dist/cjs/adapters/MemoryAdapter.d.ts.map +1 -1
  24. package/dist/cjs/adapters/MemoryAdapter.js +10 -1
  25. package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
  26. package/dist/cjs/adapters/MongoAdapter.d.ts.map +1 -1
  27. package/dist/cjs/adapters/MongoAdapter.js +33 -2
  28. package/dist/cjs/adapters/MongoAdapter.js.map +1 -1
  29. package/dist/cjs/adapters/OpenSearchAdapter.d.ts.map +1 -1
  30. package/dist/cjs/adapters/OpenSearchAdapter.js +15 -1
  31. package/dist/cjs/adapters/OpenSearchAdapter.js.map +1 -1
  32. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  33. package/dist/cjs/adapters/PostgreSQLAdapter.js +35 -5
  34. package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
  35. package/dist/cjs/adapters/PrismaAdapter.d.ts.map +1 -1
  36. package/dist/cjs/adapters/PrismaAdapter.js +58 -15
  37. package/dist/cjs/adapters/PrismaAdapter.js.map +1 -1
  38. package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -1
  39. package/dist/cjs/adapters/RedisAdapter.js +10 -1
  40. package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
  41. package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
  42. package/dist/cjs/adapters/SQLiteAdapter.js +38 -6
  43. package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
  44. package/dist/cjs/core/Agent.d.ts +28 -18
  45. package/dist/cjs/core/Agent.d.ts.map +1 -1
  46. package/dist/cjs/core/Agent.js +58 -68
  47. package/dist/cjs/core/Agent.js.map +1 -1
  48. package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -1
  49. package/dist/cjs/core/AutoChainExecutor.js +14 -20
  50. package/dist/cjs/core/AutoChainExecutor.js.map +1 -1
  51. package/dist/cjs/core/BranchEvaluator.d.ts +4 -3
  52. package/dist/cjs/core/BranchEvaluator.d.ts.map +1 -1
  53. package/dist/cjs/core/BranchEvaluator.js +18 -23
  54. package/dist/cjs/core/BranchEvaluator.js.map +1 -1
  55. package/dist/cjs/core/Flow.d.ts +2 -1
  56. package/dist/cjs/core/Flow.d.ts.map +1 -1
  57. package/dist/cjs/core/Flow.js +10 -3
  58. package/dist/cjs/core/Flow.js.map +1 -1
  59. package/dist/cjs/core/FlowRouter.d.ts +1 -0
  60. package/dist/cjs/core/FlowRouter.d.ts.map +1 -1
  61. package/dist/cjs/core/FlowRouter.js +28 -5
  62. package/dist/cjs/core/FlowRouter.js.map +1 -1
  63. package/dist/cjs/core/PersistenceManager.d.ts +3 -0
  64. package/dist/cjs/core/PersistenceManager.d.ts.map +1 -1
  65. package/dist/cjs/core/PersistenceManager.js +57 -5
  66. package/dist/cjs/core/PersistenceManager.js.map +1 -1
  67. package/dist/cjs/core/PromptComposer.d.ts.map +1 -1
  68. package/dist/cjs/core/PromptComposer.js +24 -10
  69. package/dist/cjs/core/PromptComposer.js.map +1 -1
  70. package/dist/cjs/core/ResponseGenerationError.d.ts +30 -0
  71. package/dist/cjs/core/ResponseGenerationError.d.ts.map +1 -0
  72. package/dist/cjs/core/ResponseGenerationError.js +37 -0
  73. package/dist/cjs/core/ResponseGenerationError.js.map +1 -0
  74. package/dist/cjs/core/ResponseModal.d.ts +43 -102
  75. package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
  76. package/dist/cjs/core/ResponseModal.js +178 -1194
  77. package/dist/cjs/core/ResponseModal.js.map +1 -1
  78. package/dist/cjs/core/ResponsePipeline.d.ts +58 -152
  79. package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
  80. package/dist/cjs/core/ResponsePipeline.js +408 -457
  81. package/dist/cjs/core/ResponsePipeline.js.map +1 -1
  82. package/dist/cjs/core/SessionFinalizer.d.ts +34 -0
  83. package/dist/cjs/core/SessionFinalizer.d.ts.map +1 -0
  84. package/dist/cjs/core/SessionFinalizer.js +61 -0
  85. package/dist/cjs/core/SessionFinalizer.js.map +1 -0
  86. package/dist/cjs/core/SessionManager.d.ts +1 -1
  87. package/dist/cjs/core/SessionManager.d.ts.map +1 -1
  88. package/dist/cjs/core/SessionManager.js +18 -7
  89. package/dist/cjs/core/SessionManager.js.map +1 -1
  90. package/dist/cjs/core/SignalCoordinator.d.ts +103 -0
  91. package/dist/cjs/core/SignalCoordinator.d.ts.map +1 -0
  92. package/dist/cjs/core/SignalCoordinator.js +207 -0
  93. package/dist/cjs/core/SignalCoordinator.js.map +1 -0
  94. package/dist/cjs/core/SignalEvaluator.d.ts +2 -2
  95. package/dist/cjs/core/SignalEvaluator.d.ts.map +1 -1
  96. package/dist/cjs/core/SignalEvaluator.js +11 -26
  97. package/dist/cjs/core/SignalEvaluator.js.map +1 -1
  98. package/dist/cjs/core/SignalProcessor.d.ts.map +1 -1
  99. package/dist/cjs/core/SignalProcessor.js +28 -11
  100. package/dist/cjs/core/SignalProcessor.js.map +1 -1
  101. package/dist/cjs/core/Step.d.ts +3 -1
  102. package/dist/cjs/core/Step.d.ts.map +1 -1
  103. package/dist/cjs/core/Step.js +10 -3
  104. package/dist/cjs/core/Step.js.map +1 -1
  105. package/dist/cjs/core/StepLifecycle.d.ts +33 -0
  106. package/dist/cjs/core/StepLifecycle.d.ts.map +1 -0
  107. package/dist/cjs/core/StepLifecycle.js +97 -0
  108. package/dist/cjs/core/StepLifecycle.js.map +1 -0
  109. package/dist/cjs/core/ToolLoopExecutor.d.ts +104 -0
  110. package/dist/cjs/core/ToolLoopExecutor.d.ts.map +1 -0
  111. package/dist/cjs/core/ToolLoopExecutor.js +391 -0
  112. package/dist/cjs/core/ToolLoopExecutor.js.map +1 -0
  113. package/dist/cjs/core/ToolManager.d.ts +1 -1
  114. package/dist/cjs/core/ToolManager.d.ts.map +1 -1
  115. package/dist/cjs/core/ToolManager.js.map +1 -1
  116. package/dist/cjs/index.d.ts +4 -5
  117. package/dist/cjs/index.d.ts.map +1 -1
  118. package/dist/cjs/index.js +7 -6
  119. package/dist/cjs/index.js.map +1 -1
  120. package/dist/cjs/providers/AnthropicProvider.d.ts +2 -0
  121. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
  122. package/dist/cjs/providers/AnthropicProvider.js +28 -56
  123. package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
  124. package/dist/cjs/providers/DeepSeekProvider.d.ts +25 -21
  125. package/dist/cjs/providers/DeepSeekProvider.d.ts.map +1 -1
  126. package/dist/cjs/providers/DeepSeekProvider.js +48 -407
  127. package/dist/cjs/providers/DeepSeekProvider.js.map +1 -1
  128. package/dist/cjs/providers/GeminiProvider.d.ts +2 -0
  129. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  130. package/dist/cjs/providers/GeminiProvider.js +27 -56
  131. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  132. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts +129 -0
  133. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  134. package/dist/cjs/providers/OpenAICompatibleProvider.js +485 -0
  135. package/dist/cjs/providers/OpenAICompatibleProvider.js.map +1 -0
  136. package/dist/cjs/providers/OpenAIProvider.d.ts +9 -28
  137. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  138. package/dist/cjs/providers/OpenAIProvider.js +23 -417
  139. package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
  140. package/dist/cjs/providers/OpenRouterProvider.d.ts +10 -27
  141. package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
  142. package/dist/cjs/providers/OpenRouterProvider.js +28 -417
  143. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
  144. package/dist/cjs/providers/errorClassification.d.ts +61 -0
  145. package/dist/cjs/providers/errorClassification.d.ts.map +1 -0
  146. package/dist/cjs/providers/errorClassification.js +123 -0
  147. package/dist/cjs/providers/errorClassification.js.map +1 -0
  148. package/dist/cjs/providers/index.d.ts +4 -0
  149. package/dist/cjs/providers/index.d.ts.map +1 -1
  150. package/dist/cjs/providers/index.js +7 -1
  151. package/dist/cjs/providers/index.js.map +1 -1
  152. package/dist/cjs/types/agent.d.ts +2 -1
  153. package/dist/cjs/types/agent.d.ts.map +1 -1
  154. package/dist/cjs/types/ai.d.ts +20 -0
  155. package/dist/cjs/types/ai.d.ts.map +1 -1
  156. package/dist/cjs/types/errors.d.ts +33 -0
  157. package/dist/cjs/types/errors.d.ts.map +1 -1
  158. package/dist/cjs/types/errors.js +37 -1
  159. package/dist/cjs/types/errors.js.map +1 -1
  160. package/dist/cjs/types/flow.d.ts +10 -5
  161. package/dist/cjs/types/flow.d.ts.map +1 -1
  162. package/dist/cjs/types/history.d.ts +1 -1
  163. package/dist/cjs/types/history.d.ts.map +1 -1
  164. package/dist/cjs/types/index.d.ts +5 -4
  165. package/dist/cjs/types/index.d.ts.map +1 -1
  166. package/dist/cjs/types/index.js +3 -1
  167. package/dist/cjs/types/index.js.map +1 -1
  168. package/dist/cjs/types/persistence.d.ts +43 -2
  169. package/dist/cjs/types/persistence.d.ts.map +1 -1
  170. package/dist/cjs/types/session.d.ts +8 -0
  171. package/dist/cjs/types/session.d.ts.map +1 -1
  172. package/dist/cjs/types/signals.d.ts +18 -2
  173. package/dist/cjs/types/signals.d.ts.map +1 -1
  174. package/dist/cjs/types/template.d.ts +3 -1
  175. package/dist/cjs/types/template.d.ts.map +1 -1
  176. package/dist/cjs/types/tool.d.ts +4 -4
  177. package/dist/cjs/types/tool.d.ts.map +1 -1
  178. package/dist/cjs/utils/condition.d.ts +19 -0
  179. package/dist/cjs/utils/condition.d.ts.map +1 -1
  180. package/dist/cjs/utils/condition.js +86 -15
  181. package/dist/cjs/utils/condition.js.map +1 -1
  182. package/dist/cjs/utils/session.d.ts +1 -0
  183. package/dist/cjs/utils/session.d.ts.map +1 -1
  184. package/dist/cjs/utils/session.js +1 -0
  185. package/dist/cjs/utils/session.js.map +1 -1
  186. package/dist/core/Agent.d.ts +28 -18
  187. package/dist/core/Agent.d.ts.map +1 -1
  188. package/dist/core/Agent.js +58 -68
  189. package/dist/core/Agent.js.map +1 -1
  190. package/dist/core/AutoChainExecutor.d.ts.map +1 -1
  191. package/dist/core/AutoChainExecutor.js +14 -20
  192. package/dist/core/AutoChainExecutor.js.map +1 -1
  193. package/dist/core/BranchEvaluator.d.ts +4 -3
  194. package/dist/core/BranchEvaluator.d.ts.map +1 -1
  195. package/dist/core/BranchEvaluator.js +18 -23
  196. package/dist/core/BranchEvaluator.js.map +1 -1
  197. package/dist/core/Flow.d.ts +2 -1
  198. package/dist/core/Flow.d.ts.map +1 -1
  199. package/dist/core/Flow.js +10 -3
  200. package/dist/core/Flow.js.map +1 -1
  201. package/dist/core/FlowRouter.d.ts +1 -0
  202. package/dist/core/FlowRouter.d.ts.map +1 -1
  203. package/dist/core/FlowRouter.js +28 -5
  204. package/dist/core/FlowRouter.js.map +1 -1
  205. package/dist/core/PersistenceManager.d.ts +3 -0
  206. package/dist/core/PersistenceManager.d.ts.map +1 -1
  207. package/dist/core/PersistenceManager.js +58 -6
  208. package/dist/core/PersistenceManager.js.map +1 -1
  209. package/dist/core/PromptComposer.d.ts.map +1 -1
  210. package/dist/core/PromptComposer.js +24 -10
  211. package/dist/core/PromptComposer.js.map +1 -1
  212. package/dist/core/ResponseGenerationError.d.ts +30 -0
  213. package/dist/core/ResponseGenerationError.d.ts.map +1 -0
  214. package/dist/core/ResponseGenerationError.js +33 -0
  215. package/dist/core/ResponseGenerationError.js.map +1 -0
  216. package/dist/core/ResponseModal.d.ts +43 -102
  217. package/dist/core/ResponseModal.d.ts.map +1 -1
  218. package/dist/core/ResponseModal.js +171 -1186
  219. package/dist/core/ResponseModal.js.map +1 -1
  220. package/dist/core/ResponsePipeline.d.ts +58 -152
  221. package/dist/core/ResponsePipeline.d.ts.map +1 -1
  222. package/dist/core/ResponsePipeline.js +409 -458
  223. package/dist/core/ResponsePipeline.js.map +1 -1
  224. package/dist/core/SessionFinalizer.d.ts +34 -0
  225. package/dist/core/SessionFinalizer.d.ts.map +1 -0
  226. package/dist/core/SessionFinalizer.js +57 -0
  227. package/dist/core/SessionFinalizer.js.map +1 -0
  228. package/dist/core/SessionManager.d.ts +1 -1
  229. package/dist/core/SessionManager.d.ts.map +1 -1
  230. package/dist/core/SessionManager.js +18 -7
  231. package/dist/core/SessionManager.js.map +1 -1
  232. package/dist/core/SignalCoordinator.d.ts +103 -0
  233. package/dist/core/SignalCoordinator.d.ts.map +1 -0
  234. package/dist/core/SignalCoordinator.js +203 -0
  235. package/dist/core/SignalCoordinator.js.map +1 -0
  236. package/dist/core/SignalEvaluator.d.ts +2 -2
  237. package/dist/core/SignalEvaluator.d.ts.map +1 -1
  238. package/dist/core/SignalEvaluator.js +11 -26
  239. package/dist/core/SignalEvaluator.js.map +1 -1
  240. package/dist/core/SignalProcessor.d.ts.map +1 -1
  241. package/dist/core/SignalProcessor.js +28 -11
  242. package/dist/core/SignalProcessor.js.map +1 -1
  243. package/dist/core/Step.d.ts +3 -1
  244. package/dist/core/Step.d.ts.map +1 -1
  245. package/dist/core/Step.js +10 -3
  246. package/dist/core/Step.js.map +1 -1
  247. package/dist/core/StepLifecycle.d.ts +33 -0
  248. package/dist/core/StepLifecycle.d.ts.map +1 -0
  249. package/dist/core/StepLifecycle.js +93 -0
  250. package/dist/core/StepLifecycle.js.map +1 -0
  251. package/dist/core/ToolLoopExecutor.d.ts +104 -0
  252. package/dist/core/ToolLoopExecutor.d.ts.map +1 -0
  253. package/dist/core/ToolLoopExecutor.js +387 -0
  254. package/dist/core/ToolLoopExecutor.js.map +1 -0
  255. package/dist/core/ToolManager.d.ts +1 -1
  256. package/dist/core/ToolManager.d.ts.map +1 -1
  257. package/dist/core/ToolManager.js.map +1 -1
  258. package/dist/index.d.ts +4 -5
  259. package/dist/index.d.ts.map +1 -1
  260. package/dist/index.js +3 -3
  261. package/dist/index.js.map +1 -1
  262. package/dist/providers/AnthropicProvider.d.ts +2 -0
  263. package/dist/providers/AnthropicProvider.d.ts.map +1 -1
  264. package/dist/providers/AnthropicProvider.js +22 -50
  265. package/dist/providers/AnthropicProvider.js.map +1 -1
  266. package/dist/providers/DeepSeekProvider.d.ts +25 -21
  267. package/dist/providers/DeepSeekProvider.d.ts.map +1 -1
  268. package/dist/providers/DeepSeekProvider.js +49 -408
  269. package/dist/providers/DeepSeekProvider.js.map +1 -1
  270. package/dist/providers/GeminiProvider.d.ts +2 -0
  271. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  272. package/dist/providers/GeminiProvider.js +21 -50
  273. package/dist/providers/GeminiProvider.js.map +1 -1
  274. package/dist/providers/OpenAICompatibleProvider.d.ts +129 -0
  275. package/dist/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  276. package/dist/providers/OpenAICompatibleProvider.js +481 -0
  277. package/dist/providers/OpenAICompatibleProvider.js.map +1 -0
  278. package/dist/providers/OpenAIProvider.d.ts +9 -28
  279. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  280. package/dist/providers/OpenAIProvider.js +23 -417
  281. package/dist/providers/OpenAIProvider.js.map +1 -1
  282. package/dist/providers/OpenRouterProvider.d.ts +10 -27
  283. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  284. package/dist/providers/OpenRouterProvider.js +28 -417
  285. package/dist/providers/OpenRouterProvider.js.map +1 -1
  286. package/dist/providers/errorClassification.d.ts +61 -0
  287. package/dist/providers/errorClassification.d.ts.map +1 -0
  288. package/dist/providers/errorClassification.js +116 -0
  289. package/dist/providers/errorClassification.js.map +1 -0
  290. package/dist/providers/index.d.ts +4 -0
  291. package/dist/providers/index.d.ts.map +1 -1
  292. package/dist/providers/index.js +2 -0
  293. package/dist/providers/index.js.map +1 -1
  294. package/dist/types/agent.d.ts +2 -1
  295. package/dist/types/agent.d.ts.map +1 -1
  296. package/dist/types/ai.d.ts +20 -0
  297. package/dist/types/ai.d.ts.map +1 -1
  298. package/dist/types/errors.d.ts +33 -0
  299. package/dist/types/errors.d.ts.map +1 -1
  300. package/dist/types/errors.js +34 -0
  301. package/dist/types/errors.js.map +1 -1
  302. package/dist/types/flow.d.ts +10 -5
  303. package/dist/types/flow.d.ts.map +1 -1
  304. package/dist/types/history.d.ts +1 -1
  305. package/dist/types/history.d.ts.map +1 -1
  306. package/dist/types/index.d.ts +5 -4
  307. package/dist/types/index.d.ts.map +1 -1
  308. package/dist/types/index.js +1 -1
  309. package/dist/types/index.js.map +1 -1
  310. package/dist/types/persistence.d.ts +43 -2
  311. package/dist/types/persistence.d.ts.map +1 -1
  312. package/dist/types/session.d.ts +8 -0
  313. package/dist/types/session.d.ts.map +1 -1
  314. package/dist/types/signals.d.ts +18 -2
  315. package/dist/types/signals.d.ts.map +1 -1
  316. package/dist/types/template.d.ts +3 -1
  317. package/dist/types/template.d.ts.map +1 -1
  318. package/dist/types/tool.d.ts +4 -4
  319. package/dist/types/tool.d.ts.map +1 -1
  320. package/dist/utils/condition.d.ts +19 -0
  321. package/dist/utils/condition.d.ts.map +1 -1
  322. package/dist/utils/condition.js +84 -15
  323. package/dist/utils/condition.js.map +1 -1
  324. package/dist/utils/session.d.ts +1 -0
  325. package/dist/utils/session.d.ts.map +1 -1
  326. package/dist/utils/session.js +1 -0
  327. package/dist/utils/session.js.map +1 -1
  328. package/docs/README.md +1 -1
  329. package/docs/guides/branching.md +1 -1
  330. package/docs/guides/compaction.md +12 -5
  331. package/docs/guides/conditions.md +15 -2
  332. package/docs/guides/error-handling.md +59 -9
  333. package/docs/guides/instructions.md +2 -2
  334. package/docs/guides/persistence.md +69 -2
  335. package/docs/guides/streaming.md +2 -0
  336. package/docs/migration/README.md +5 -1
  337. package/docs/migration/v2-3-to-v2-4.md +316 -0
  338. package/docs/reference/adapters.md +55 -7
  339. package/docs/reference/branches.md +2 -2
  340. package/docs/reference/create-agent.md +3 -3
  341. package/docs/reference/errors.md +66 -1
  342. package/docs/reference/flow.md +1 -1
  343. package/docs/reference/instruction.md +4 -4
  344. package/docs/reference/providers.md +100 -3
  345. package/docs/reference/signals.md +14 -3
  346. package/docs/reference/step.md +2 -2
  347. package/docs/reference/tool.md +3 -1
  348. package/docs/start/05-go-to-production.md +3 -0
  349. package/package.json +1 -1
  350. package/src/adapters/MemoryAdapter.ts +15 -1
  351. package/src/adapters/MongoAdapter.ts +41 -2
  352. package/src/adapters/OpenSearchAdapter.ts +24 -1
  353. package/src/adapters/PostgreSQLAdapter.ts +45 -5
  354. package/src/adapters/PrismaAdapter.ts +82 -16
  355. package/src/adapters/RedisAdapter.ts +19 -1
  356. package/src/adapters/SQLiteAdapter.ts +47 -5
  357. package/src/core/Agent.ts +70 -85
  358. package/src/core/AutoChainExecutor.ts +27 -57
  359. package/src/core/BranchEvaluator.ts +24 -30
  360. package/src/core/Flow.ts +10 -3
  361. package/src/core/FlowRouter.ts +36 -4
  362. package/src/core/PersistenceManager.ts +79 -10
  363. package/src/core/PromptComposer.ts +25 -12
  364. package/src/core/ResponseGenerationError.ts +56 -0
  365. package/src/core/ResponseModal.ts +233 -1482
  366. package/src/core/ResponsePipeline.ts +492 -662
  367. package/src/core/SessionFinalizer.ts +78 -0
  368. package/src/core/SessionManager.ts +21 -9
  369. package/src/core/SignalCoordinator.ts +263 -0
  370. package/src/core/SignalEvaluator.ts +12 -29
  371. package/src/core/SignalProcessor.ts +34 -11
  372. package/src/core/Step.ts +11 -3
  373. package/src/core/StepLifecycle.ts +139 -0
  374. package/src/core/ToolLoopExecutor.ts +492 -0
  375. package/src/core/ToolManager.ts +2 -1
  376. package/src/index.ts +7 -5
  377. package/src/providers/AnthropicProvider.ts +30 -72
  378. package/src/providers/DeepSeekProvider.ts +74 -586
  379. package/src/providers/GeminiProvider.ts +29 -70
  380. package/src/providers/OpenAICompatibleProvider.ts +738 -0
  381. package/src/providers/OpenAIProvider.ts +29 -602
  382. package/src/providers/OpenRouterProvider.ts +38 -596
  383. package/src/providers/errorClassification.ts +172 -0
  384. package/src/providers/index.ts +13 -0
  385. package/src/types/agent.ts +2 -1
  386. package/src/types/ai.ts +22 -0
  387. package/src/types/errors.ts +57 -0
  388. package/src/types/flow.ts +10 -5
  389. package/src/types/history.ts +1 -2
  390. package/src/types/index.ts +5 -1
  391. package/src/types/persistence.ts +50 -2
  392. package/src/types/session.ts +9 -0
  393. package/src/types/signals.ts +20 -2
  394. package/src/types/template.ts +3 -1
  395. package/src/types/tool.ts +10 -10
  396. package/src/utils/condition.ts +115 -18
  397. package/src/utils/session.ts +2 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * SessionFinalizer owns end-of-turn session finalization: deterministic
3
+ * history compaction, persistence auto-save, the step `finalize` hook,
4
+ * and syncing the finalized session back to the agent's live session.
5
+ *
6
+ * Every turn finalizes exactly once: the non-streaming path in
7
+ * `respond()` after generation completes; the streaming path in the
8
+ * final-chunk interception loop after the post-signal phase (so
9
+ * post-phase session mutations are persisted), with only the
10
+ * pre-routing signal/auto-chain halt chunks finalizing at their own
11
+ * yield sites.
12
+ */
13
+
14
+ import type { AgentOptions, SessionState } from "../types";
15
+ import type { CompactionOptions } from "../types/compaction";
16
+ import type { PersistenceManager } from "./PersistenceManager";
17
+ import type { StepLifecycle } from "./StepLifecycle";
18
+ import { CompactionEngine } from "./CompactionEngine";
19
+ import { logger } from "../utils";
20
+
21
+ export class SessionFinalizer<TContext = unknown, TData = unknown> {
22
+ constructor(
23
+ private readonly deps: {
24
+ getCompactionOptions: () => CompactionOptions | undefined;
25
+ getPersistenceManager: () => PersistenceManager<TData> | undefined;
26
+ getAgentOptions: () => AgentOptions<TContext, TData>;
27
+ getCurrentSession: () => SessionState<TData> | undefined;
28
+ setCurrentSession: (session: SessionState<TData>) => void;
29
+ stepLifecycle: StepLifecycle<TContext, TData>;
30
+ /** ResponseModalOptions.enableAutoSave (undefined means enabled). */
31
+ enableAutoSave?: boolean;
32
+ }
33
+ ) { }
34
+
35
+ /**
36
+ * Handle session persistence and finalization.
37
+ */
38
+ async finalize(session: SessionState<TData>, context: TContext): Promise<void> {
39
+ // Deterministic compaction: runs on every finalize (not just addMessage)
40
+ // so respond()-only callers get bounded history too
41
+ const compactionOptions = this.deps.getCompactionOptions();
42
+ if (compactionOptions && session.history && session.history.length > 0) {
43
+ try {
44
+ const result = await CompactionEngine.checkAndCompact(session.history, compactionOptions);
45
+ if (result.strategy !== 'none') {
46
+ session.history = result.history;
47
+ logger.info(
48
+ `[SessionFinalizer] Compaction applied: strategy='${result.strategy}', ` +
49
+ `estimatedTokens=${result.estimatedTokens}, messagesCompacted=${result.messagesCompacted}`
50
+ );
51
+ }
52
+ } catch (error) {
53
+ logger.warn("[SessionFinalizer] Compaction failed at finalize, continuing without compaction", error);
54
+ }
55
+ }
56
+
57
+ // Auto-save session step to persistence if configured
58
+ const persistenceManager = this.deps.getPersistenceManager();
59
+ const agentOptions = this.deps.getAgentOptions();
60
+ if (
61
+ persistenceManager &&
62
+ session.id &&
63
+ (this.deps.enableAutoSave !== false && agentOptions.persistence?.autoSave !== false)
64
+ ) {
65
+ await persistenceManager.saveSessionState(session.id, session);
66
+ logger.debug(`[SessionFinalizer] Auto-saved session step to persistence: ${session.id}`);
67
+ }
68
+
69
+ // Execute finalize function
70
+ await this.deps.stepLifecycle.runFinalize(session, context);
71
+
72
+ // Update current session if we have one
73
+ const currentSession = this.deps.getCurrentSession();
74
+ if (currentSession) {
75
+ this.deps.setCurrentSession(session);
76
+ }
77
+ }
78
+ }
@@ -79,6 +79,14 @@ export class SessionManager<TData = unknown> {
79
79
  effectiveSessionId
80
80
  );
81
81
  if (session) {
82
+ // Loaded session data wins over anything staged before it existed
83
+ const pending = this.agent?.consumePendingData();
84
+ if (pending && Object.keys(pending).length > 0) {
85
+ log.debug(
86
+ "SessionManager: discarding pre-session staged data in favor of loaded session state",
87
+ pending
88
+ );
89
+ }
82
90
  this.currentSession = session;
83
91
  return session;
84
92
  }
@@ -99,6 +107,9 @@ export class SessionManager<TData = unknown> {
99
107
  const session = createSession<TData>({
100
108
  id: sessionId,
101
109
  history: [], // Session manages its own history
110
+ // Seed with data staged before the session existed (initialData,
111
+ // pre-session updateCollectedData calls)
112
+ data: this.agent?.consumePendingData() ?? ({} as Partial<TData>),
102
113
  });
103
114
 
104
115
  this.currentSession = session;
@@ -237,15 +248,16 @@ export class SessionManager<TData = unknown> {
237
248
  await this.getOrCreate();
238
249
 
239
250
  if (this.currentSession && data) {
240
- this.currentSession.data = {
241
- ...this.currentSession.data,
242
- ...data,
243
- };
244
- this.currentSession.metadata!.lastUpdatedAt = new Date();
245
-
246
- // Synchronize with agent's collected data for bidirectional sync
247
251
  if (this.agent) {
248
- await this.agent.updateCollectedData(this.currentSession.data);
252
+ // Route through the agent so schema validation and onDataUpdate
253
+ // hooks run; it writes into this session's data directly
254
+ await this.agent.updateCollectedData(data);
255
+ } else {
256
+ this.currentSession.data = {
257
+ ...this.currentSession.data,
258
+ ...data,
259
+ };
260
+ this.currentSession.metadata!.lastUpdatedAt = new Date();
249
261
  }
250
262
 
251
263
  // Auto-save to persistence
@@ -306,7 +318,7 @@ export class SessionManager<TData = unknown> {
306
318
  *
307
319
  * @internal Called from ResponseModal after stream()/generate() completion
308
320
  */
309
- syncSession(session: SessionState<TData>): void {
321
+ syncSession(session: SessionState<TData> | undefined): void {
310
322
  this.currentSession = session;
311
323
  }
312
324
 
@@ -0,0 +1,263 @@
1
+ /**
2
+ * SignalCoordinator owns the signal pre/post phase orchestration for a turn:
3
+ *
4
+ * - `runPrePhase()` / `runPostPhase()` — delegation to the SignalProcessor,
5
+ * returning a stable empty shape when no processor is configured.
6
+ * - `applyPositionDirective()` — applies a pre-phase directive's position
7
+ * field (goTo, goToStep, complete, abort, reset) as the turn's routing
8
+ * decision, discarding the router's result.
9
+ * - `applyPostPhase()` — runs the post phase and applies its directive to
10
+ * the turn result in one step: session updates, pendingDirective for
11
+ * position fields (no mid-turn re-entry per D6), and the reply override
12
+ * of the user-visible message.
13
+ */
14
+
15
+ import type { Directive, Event, SessionState } from "../types";
16
+ import type { SignalFiring } from "../types/signals";
17
+ import type { SignalProcessor } from "./SignalProcessor";
18
+ import type { Flow } from "./Flow";
19
+ import type { Step } from "./Step";
20
+ import { hasDirectivePositionField } from "./ResponsePipeline";
21
+ import { enterFlow, enterStep, mergeCollected, logger } from "../utils";
22
+
23
+ /** Result of a signal phase run (pre or post). */
24
+ export interface SignalPhaseResult<TContext = unknown, TData = unknown> {
25
+ firings: SignalFiring<TContext, TData>[];
26
+ updatedSession: SessionState<TData>;
27
+ mergedDirective: Directive<TContext, TData> | undefined;
28
+ }
29
+
30
+ export class SignalCoordinator<TContext = unknown, TData = unknown> {
31
+ constructor(
32
+ private readonly deps: {
33
+ getFlows: () => Flow<TContext, TData>[];
34
+ signalProcessor?: SignalProcessor<TContext, TData>;
35
+ }
36
+ ) { }
37
+
38
+ /** Whether a signal processor is configured (zero-cost path when absent). */
39
+ get enabled(): boolean {
40
+ return this.deps.signalProcessor !== undefined;
41
+ }
42
+
43
+ /**
44
+ * PRE-SIGNAL PHASE — Evaluates pre/both signals in parallel with routing.
45
+ *
46
+ * Delegates to `signalProcessor.runPreSignalPhase(...)` when configured.
47
+ * When no signal processor is present (zero-cost path), returns a stable
48
+ * empty shape so callers don't need to branch.
49
+ *
50
+ * @requirements 2.1, 2.3, 8.6, 13.3
51
+ */
52
+ async runPrePhase(
53
+ session: SessionState<TData>,
54
+ context: TContext,
55
+ history: Event[],
56
+ ): Promise<SignalPhaseResult<TContext, TData>> {
57
+ if (!this.deps.signalProcessor) {
58
+ return { firings: [], updatedSession: session, mergedDirective: undefined };
59
+ }
60
+ const result = await this.deps.signalProcessor.runPreSignalPhase({ session, history, context });
61
+ return {
62
+ firings: result.firings,
63
+ updatedSession: result.updatedSession,
64
+ mergedDirective: result.mergedDirective,
65
+ };
66
+ }
67
+
68
+ /**
69
+ * POST-SIGNAL PHASE — Evaluates post/both signals after finalize/onComplete.
70
+ *
71
+ * Delegates to `signalProcessor.runPostSignalPhase(...)` when configured.
72
+ * When no signal processor is present, returns a stable empty shape.
73
+ *
74
+ * Post-phase signals see the complete turn result: assistant message in history,
75
+ * collected data, tool results. Position directives from this phase set
76
+ * `session.pendingDirective` (no mid-turn re-entry per D6 decision).
77
+ *
78
+ * Pre-LLM-only fields (`appendPrompt`, `injectTools`, `halt`) are already
79
+ * dropped inside `runPostSignalPhase` per Phase 4.5 — this seam does NOT
80
+ * re-introduce them.
81
+ *
82
+ * @requirements 9.1, 9.2, 9.3, 9.4
83
+ */
84
+ async runPostPhase(
85
+ session: SessionState<TData>,
86
+ context: TContext,
87
+ history: Event[],
88
+ ): Promise<SignalPhaseResult<TContext, TData>> {
89
+ if (!this.deps.signalProcessor) {
90
+ return { firings: [], updatedSession: session, mergedDirective: undefined };
91
+ }
92
+ const result = await this.deps.signalProcessor.runPostSignalPhase({ session, history, context });
93
+ return {
94
+ firings: result.firings,
95
+ updatedSession: result.updatedSession,
96
+ mergedDirective: result.mergedDirective,
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Run the post phase and apply its merged directive to the turn result:
102
+ * session updates from the phase, `pendingDirective` for position fields
103
+ * (Requirement 9.3, no mid-turn re-entry per D6), and the reply override
104
+ * of the user-visible message (undefined means "leave it unchanged"; an
105
+ * empty string is an explicit replacement).
106
+ *
107
+ * @requirements 9.1, 9.2, 9.3, 9.4
108
+ */
109
+ async applyPostPhase(params: {
110
+ session: SessionState<TData>;
111
+ context: TContext;
112
+ historyEvents: Event[];
113
+ message: string;
114
+ }): Promise<{
115
+ session: SessionState<TData>;
116
+ message: string;
117
+ firings: SignalFiring<TContext, TData>[];
118
+ /** True when the directive's `reply` replaced the message. */
119
+ replyOverridden: boolean;
120
+ }> {
121
+ const postResult = await this.runPostPhase(
122
+ params.session, params.context, params.historyEvents,
123
+ );
124
+ let session = postResult.updatedSession;
125
+
126
+ // Requirement 9.3: Post-phase position directive sets session.pendingDirective
127
+ if (postResult.mergedDirective && hasDirectivePositionField(postResult.mergedDirective)) {
128
+ session = { ...session, pendingDirective: postResult.mergedDirective };
129
+ }
130
+
131
+ const replyOverridden = postResult.mergedDirective?.reply !== undefined;
132
+ const message = replyOverridden
133
+ ? postResult.mergedDirective!.reply!
134
+ : params.message;
135
+
136
+ return { session, message, firings: postResult.firings, replyOverridden };
137
+ }
138
+
139
+ /**
140
+ * Apply a signal's position directive (goTo, goToStep, complete, abort, reset).
141
+ * Discards routing result and uses the signal's position decision.
142
+ *
143
+ * @requirements 8.3
144
+ */
145
+ applyPositionDirective(
146
+ signalResult: {
147
+ firings: SignalFiring<TContext, TData>[];
148
+ updatedSession: SessionState<TData>;
149
+ mergedDirective: Directive<TContext, TData> | undefined;
150
+ },
151
+ ): {
152
+ selectedFlow?: Flow<TContext, TData>;
153
+ selectedStep?: Step<TContext, TData>;
154
+ responseDirectives?: string[];
155
+ session: SessionState<TData>;
156
+ isFlowComplete: boolean;
157
+ signalFirings?: SignalFiring<TContext, TData>[];
158
+ signalPreDirective?: Directive<TContext, TData>;
159
+ signalHalted?: boolean;
160
+ signalHaltReply?: string;
161
+ } {
162
+ const directive = signalResult.mergedDirective!;
163
+ let session = signalResult.updatedSession;
164
+ const flows = this.deps.getFlows();
165
+ let selectedFlow: Flow<TContext, TData> | undefined;
166
+ let selectedStep: Step<TContext, TData> | undefined;
167
+ let isFlowComplete = false;
168
+
169
+ // Apply data updates if present alongside position
170
+ if (directive.dataUpdate) {
171
+ session = mergeCollected(session, directive.dataUpdate);
172
+ }
173
+
174
+ if (directive.goTo) {
175
+ const flowTarget = typeof directive.goTo === 'string'
176
+ ? directive.goTo
177
+ : directive.goTo.flow ?? directive.goTo.step;
178
+
179
+ if (flowTarget) {
180
+ const targetFlow = flows.find(f => f.id === flowTarget || f.title === flowTarget);
181
+ if (targetFlow) {
182
+ session = enterFlow(session, targetFlow.id, targetFlow.title);
183
+ selectedFlow = targetFlow;
184
+
185
+ if (typeof directive.goTo === 'object' && directive.goTo.step) {
186
+ const targetStep = targetFlow.getStep(directive.goTo.step);
187
+ if (targetStep) {
188
+ session = enterStep(session, targetStep.id, targetStep.description);
189
+ selectedStep = targetStep;
190
+ }
191
+ }
192
+ } else {
193
+ logger.warn(`[Signals] Pre-phase goTo target not found: "${flowTarget}". Falling back to no flow.`);
194
+ }
195
+ }
196
+ } else if (directive.goToStep) {
197
+ const stepTarget = typeof directive.goToStep === 'string'
198
+ ? directive.goToStep
199
+ : directive.goToStep.step;
200
+ const flowTarget = typeof directive.goToStep === 'object'
201
+ ? directive.goToStep.flow
202
+ : undefined;
203
+
204
+ if (flowTarget) {
205
+ const targetFlow = flows.find(f => f.id === flowTarget || f.title === flowTarget);
206
+ if (targetFlow) {
207
+ session = enterFlow(session, targetFlow.id, targetFlow.title);
208
+ selectedFlow = targetFlow;
209
+ const targetStep = targetFlow.getStep(stepTarget);
210
+ if (targetStep) {
211
+ session = enterStep(session, targetStep.id, targetStep.description);
212
+ selectedStep = targetStep;
213
+ }
214
+ }
215
+ } else if (session.currentFlow) {
216
+ const currentFlow = flows.find(f => f.id === session.currentFlow?.id);
217
+ if (currentFlow) {
218
+ selectedFlow = currentFlow;
219
+ const targetStep = currentFlow.getStep(stepTarget);
220
+ if (targetStep) {
221
+ session = enterStep(session, targetStep.id, targetStep.description);
222
+ selectedStep = targetStep;
223
+ }
224
+ }
225
+ }
226
+ } else if (directive.complete) {
227
+ isFlowComplete = true;
228
+ } else if (directive.abort) {
229
+ // Abort — no flow, session cleared or marked
230
+ isFlowComplete = true;
231
+ } else if (directive.reset) {
232
+ if (session.currentFlow) {
233
+ const currentFlow = flows.find(f => f.id === session.currentFlow?.id);
234
+ if (currentFlow) {
235
+ selectedFlow = currentFlow;
236
+ const resetStep = typeof directive.reset === 'object' && directive.reset.step
237
+ ? directive.reset.step
238
+ : undefined;
239
+ if (resetStep) {
240
+ const targetStep = currentFlow.getStep(resetStep);
241
+ if (targetStep) {
242
+ session = enterStep(session, targetStep.id, targetStep.description);
243
+ selectedStep = targetStep;
244
+ }
245
+ } else {
246
+ const initialStep = currentFlow.initialStep;
247
+ session = enterStep(session, initialStep.id, initialStep.description);
248
+ selectedStep = initialStep;
249
+ }
250
+ }
251
+ }
252
+ }
253
+
254
+ return {
255
+ selectedFlow,
256
+ selectedStep,
257
+ session,
258
+ isFlowComplete,
259
+ signalFirings: signalResult.firings,
260
+ signalPreDirective: signalResult.mergedDirective || undefined,
261
+ };
262
+ }
263
+ }
@@ -18,6 +18,7 @@ import type {
18
18
  SignalPredicateContext,
19
19
  } from "../types/signals";
20
20
  import { eventsToHistory, logger } from "../utils";
21
+ import { splitWhenConditions, evaluateIfPredicates } from "../utils/condition";
21
22
 
22
23
  // ──────────────────────────────────────────────────────────────────────────────
23
24
  // Types
@@ -44,8 +45,8 @@ export interface EvaluateSignalsParams<TContext = unknown, TData = unknown> {
44
45
  /**
45
46
  * Builds the classifier prompt for a batch of signals.
46
47
  *
47
- * Splits `when` entries by `!` prefix at render time:
48
- * - Non-`!` → "TRIGGER WHEN (ANY matches)"
48
+ * Splits `when` entries with the shared ConditionWhen parser:
49
+ * - Positive entries → "TRIGGER WHEN (ANY matches)"
49
50
  * - `!` entries → prefix stripped, "DO NOT TRIGGER WHEN (ANY inhibits)"
50
51
  *
51
52
  * Extraction schemas render under "WHEN MATCHED, EXTRACT".
@@ -80,11 +81,11 @@ export function buildSignalClassifierPrompt<TContext = unknown, TData = unknown>
80
81
  }
81
82
 
82
83
  // Split when entries
83
- const whenEntries = normalizeWhen(signal.when);
84
- const positiveEntries = whenEntries.filter(w => !w.startsWith("!"));
85
- const negativeEntries = whenEntries.filter(w => w.startsWith("!")).map(w => w.slice(1));
84
+ const whenConditions = splitWhenConditions(signal.when);
85
+ const positiveEntries = whenConditions.positive;
86
+ const negativeEntries = whenConditions.negative;
86
87
 
87
- const isUnconditional = whenEntries.length === 0;
88
+ const isUnconditional = positiveEntries.length === 0 && negativeEntries.length === 0;
88
89
 
89
90
  if (isUnconditional && signal.extract) {
90
91
  // Unconditional + extract → ALWAYS EXTRACT
@@ -94,6 +95,8 @@ export function buildSignalClassifierPrompt<TContext = unknown, TData = unknown>
94
95
  for (const entry of positiveEntries) {
95
96
  lines.push(` • ${entry}`);
96
97
  }
98
+ } else if (negativeEntries.length > 0) {
99
+ lines.push(` TRIGGER WHEN: no positive condition; trigger unless an exclusion matches.`);
97
100
  }
98
101
 
99
102
  if (negativeEntries.length > 0) {
@@ -207,23 +210,9 @@ export class SignalEvaluator<TContext = unknown, TData = unknown> {
207
210
  predicates: SignalPredicate<TContext, TData> | SignalPredicate<TContext, TData>[],
208
211
  ctx: SignalPredicateContext<TContext, TData>,
209
212
  ): Promise<boolean> {
210
- const predicateArray = Array.isArray(predicates) ? predicates : [predicates];
211
-
212
- for (let i = 0; i < predicateArray.length; i++) {
213
- try {
214
- const result = await predicateArray[i](ctx);
215
- if (!result) {
216
- return false;
217
- }
218
- } catch (error) {
219
- logger.error(
220
- `[Signals] Predicate at index ${i} threw: ${error instanceof Error ? error.message : String(error)}`,
221
- );
222
- return false;
223
- }
224
- }
225
-
226
- return true;
213
+ return evaluateIfPredicates<SignalPredicateContext<TContext, TData>>(
214
+ predicates, ctx, "Signals"
215
+ );
227
216
  }
228
217
 
229
218
  /**
@@ -350,12 +339,6 @@ export function splitIntoBatches<T>(items: T[], batchSize: number): T[][] {
350
339
  // Internal helpers
351
340
  // ──────────────────────────────────────────────────────────────────────────────
352
341
 
353
- /** Normalize `when` to a string array. */
354
- function normalizeWhen(when: string | string[] | undefined): string[] {
355
- if (!when) return [];
356
- return Array.isArray(when) ? when : [when];
357
- }
358
-
359
342
  /**
360
343
  * Render extraction schema properties as human-readable lines for the prompt.
361
344
  * Lists each property's name, type, and constraints (enum, min/max, format).
@@ -27,6 +27,11 @@ import type {
27
27
  import type { SignalEvaluator } from "./SignalEvaluator";
28
28
  import { logger } from "../utils";
29
29
 
30
+ /** Recorded on `SignalFiring.extractionError` when a matched extraction-mode
31
+ * signal yields no payload (model omitted the field or returned null). */
32
+ const EXTRACTION_MISSING_MESSAGE =
33
+ "Signal matched in extraction mode but the classifier returned no extracted payload.";
34
+
30
35
  // ──────────────────────────────────────────────────────────────────────────────
31
36
  // behaviorAllowsExecution — Algorithm 2
32
37
  // ──────────────────────────────────────────────────────────────────────────────
@@ -454,23 +459,29 @@ export class SignalProcessor<TContext = unknown, TData = unknown> {
454
459
  }
455
460
  }
456
461
 
457
- // ── STEP 6b: unconditional + extract signals ────────────────────────
458
- const unconditionalWithExtract = unconditional.filter((s) => s.extract != null);
459
- if (unconditionalWithExtract.length > 0) {
462
+ // ── STEP 6b: extraction for code-matched signals ─────────────────────
463
+ // Signals matched by code — unconditional (no `when`/`if`) or code-only
464
+ // (`if` without `when`) — that carry an `extract` schema still need a
465
+ // dedicated extraction call. LLM-conditioned signals already had their
466
+ // extraction merged into the STEP 6 classifier batch, so they are
467
+ // excluded here by identity. Without this, an `if` + `extract` signal
468
+ // (e.g. the documented `leadStage` example) would reach its handler with
469
+ // `ctx.extracted` permanently undefined.
470
+ const codeMatchedWithExtract = matched.filter(
471
+ (m) => m.signal.extract != null && !llmConditioned.includes(m.signal),
472
+ );
473
+ if (codeMatchedWithExtract.length > 0) {
460
474
  const extractResults = await this.evaluator.evaluateSignals({
461
- signals: unconditionalWithExtract,
475
+ signals: codeMatchedWithExtract.map((m) => m.signal),
462
476
  session,
463
477
  history,
464
478
  context,
465
479
  });
466
480
 
467
- // Attach extracted data to already-matched unconditional entries
468
- for (const s of unconditionalWithExtract) {
469
- const id = s.id ?? 'unknown';
470
- const entry = matched.find((m) => m.signal === s);
471
- if (entry) {
472
- entry.extracted = extractResults[id]?.extracted;
473
- }
481
+ // Attach extracted data back onto the already-matched entries.
482
+ for (const m of codeMatchedWithExtract) {
483
+ const id = m.signal.id ?? 'unknown';
484
+ m.extracted = extractResults[id]?.extracted;
474
485
  }
475
486
  }
476
487
 
@@ -490,6 +501,16 @@ export class SignalProcessor<TContext = unknown, TData = unknown> {
490
501
  // Turn-local directive bus for this handler (captures dispatch calls)
491
502
  const handlerBus: SignalDirective<TContext, TData>[] = [];
492
503
 
504
+ // A signal in extraction mode that matched but produced no payload
505
+ // would otherwise pass `extracted: undefined` to the handler with no
506
+ // trace. Surface the miss on the firing and in logs instead.
507
+ const extractionMissing = m.signal.extract != null && m.extracted == null;
508
+ if (extractionMissing) {
509
+ logger.warn(
510
+ `[Signals] Signal "${m.signal.id ?? 'unknown'}" matched in extraction mode ` +
511
+ `but the classifier returned no extracted payload; handler receives extracted: undefined.`,
512
+ );
513
+ }
493
514
 
494
515
  const sigCtx = buildSignalContext<TContext, TData, any>({
495
516
  signal: m.signal,
@@ -555,6 +576,7 @@ export class SignalProcessor<TContext = unknown, TData = unknown> {
555
576
  extracted: m.extracted,
556
577
  directive: resolvedDirective,
557
578
  durationMs: handlerDurationMs,
579
+ ...(extractionMissing ? { extractionError: EXTRACTION_MISSING_MESSAGE } : {}),
558
580
  });
559
581
 
560
582
  // stopOtherSignals → break iteration
@@ -574,6 +596,7 @@ export class SignalProcessor<TContext = unknown, TData = unknown> {
574
596
  reason: m.reason,
575
597
  handlerError: errorMessage,
576
598
  durationMs: handlerDurationMs,
599
+ ...(extractionMissing ? { extractionError: EXTRACTION_MISSING_MESSAGE } : {}),
577
600
  });
578
601
 
579
602
  // Continue iteration — handler errors never break the turn
package/src/core/Step.ts CHANGED
@@ -15,6 +15,7 @@ import type { StepResult, BranchSpec, BranchResult } from "../types/flow";
15
15
  import { ToolScope, Template, TemplateContext } from "../types";
16
16
  import type { ConditionWhen, ConditionIf } from "../types/flow";
17
17
  import { generateStepId, logger } from "../utils";
18
+ import { splitWhenConditions } from "../utils/condition";
18
19
  import { Agent } from './Agent'
19
20
 
20
21
  /**
@@ -448,7 +449,8 @@ export class Step<TContext = unknown, TData = unknown> {
448
449
  /**
449
450
  * Evaluate when/if conditions using the v2 split logic.
450
451
  * `if` (code predicate) evaluates first (free); `when` (AI) evaluates only when `if` passes.
451
- * `if` predicates use AND semantics. `when` strings use OR semantics.
452
+ * `if` predicates use AND semantics. Non-`!` `when` strings use OR semantics;
453
+ * `!` strings are exclusions where any match inhibits activation.
452
454
  * When both fields are set, the passing `if` gate and AI match are combined with AND.
453
455
  */
454
456
  async evaluateWhen(
@@ -456,6 +458,7 @@ export class Step<TContext = unknown, TData = unknown> {
456
458
  ): Promise<{
457
459
  shouldActivate: boolean;
458
460
  aiContextStrings: string[];
461
+ aiExclusionStrings: string[];
459
462
  hasProgrammaticConditions: boolean;
460
463
  }> {
461
464
  // If neither `when` nor `if` is set, step is always eligible
@@ -463,6 +466,7 @@ export class Step<TContext = unknown, TData = unknown> {
463
466
  return {
464
467
  shouldActivate: true,
465
468
  aiContextStrings: [],
469
+ aiExclusionStrings: [],
466
470
  hasProgrammaticConditions: false
467
471
  };
468
472
  }
@@ -483,6 +487,7 @@ export class Step<TContext = unknown, TData = unknown> {
483
487
  return {
484
488
  shouldActivate: false,
485
489
  aiContextStrings: [],
490
+ aiExclusionStrings: [],
486
491
  hasProgrammaticConditions: true
487
492
  };
488
493
  }
@@ -491,6 +496,7 @@ export class Step<TContext = unknown, TData = unknown> {
491
496
  return {
492
497
  shouldActivate: false,
493
498
  aiContextStrings: [],
499
+ aiExclusionStrings: [],
494
500
  hasProgrammaticConditions: true
495
501
  };
496
502
  }
@@ -499,13 +505,14 @@ export class Step<TContext = unknown, TData = unknown> {
499
505
 
500
506
  // `if` passed (or was absent) — now evaluate `when` (AI-evaluated strings)
501
507
  if (this.when) {
502
- const whenStrings = Array.isArray(this.when) ? this.when : [this.when];
508
+ const whenConditions = splitWhenConditions(this.when);
503
509
  // `when` strings are handed to the AI — return them as aiContextStrings
504
510
  // The programmatic result is true (strings don't fail programmatically;
505
511
  // they're scored by the AI at routing time)
506
512
  return {
507
513
  shouldActivate: true,
508
- aiContextStrings: whenStrings,
514
+ aiContextStrings: whenConditions.positive,
515
+ aiExclusionStrings: whenConditions.negative,
509
516
  hasProgrammaticConditions: !!this.if
510
517
  };
511
518
  }
@@ -514,6 +521,7 @@ export class Step<TContext = unknown, TData = unknown> {
514
521
  return {
515
522
  shouldActivate: true,
516
523
  aiContextStrings: [],
524
+ aiExclusionStrings: [],
517
525
  hasProgrammaticConditions: true
518
526
  };
519
527
  }