@substrate-ai/factory 0.19.54 → 0.20.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 (491) hide show
  1. package/README.md +39 -0
  2. package/dist/config.d.ts +4 -4
  3. package/package.json +12 -3
  4. package/dist/__tests__/config.test.d.ts +0 -11
  5. package/dist/__tests__/config.test.d.ts.map +0 -1
  6. package/dist/__tests__/config.test.js +0 -215
  7. package/dist/__tests__/config.test.js.map +0 -1
  8. package/dist/__tests__/factory-run-command.test.d.ts +0 -12
  9. package/dist/__tests__/factory-run-command.test.d.ts.map +0 -1
  10. package/dist/__tests__/factory-run-command.test.js +0 -454
  11. package/dist/__tests__/factory-run-command.test.js.map +0 -1
  12. package/dist/__tests__/factory-validate-command.test.d.ts +0 -15
  13. package/dist/__tests__/factory-validate-command.test.d.ts.map +0 -1
  14. package/dist/__tests__/factory-validate-command.test.js +0 -339
  15. package/dist/__tests__/factory-validate-command.test.js.map +0 -1
  16. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.d.ts +0 -72
  17. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.d.ts.map +0 -1
  18. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.js +0 -121
  19. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.js.map +0 -1
  20. package/dist/__tests__/fixtures/llm-edge-routing.dot.d.ts +0 -28
  21. package/dist/__tests__/fixtures/llm-edge-routing.dot.d.ts.map +0 -1
  22. package/dist/__tests__/fixtures/llm-edge-routing.dot.js +0 -55
  23. package/dist/__tests__/fixtures/llm-edge-routing.dot.js.map +0 -1
  24. package/dist/__tests__/fixtures/manager-loop.dot.d.ts +0 -34
  25. package/dist/__tests__/fixtures/manager-loop.dot.d.ts.map +0 -1
  26. package/dist/__tests__/fixtures/manager-loop.dot.js +0 -61
  27. package/dist/__tests__/fixtures/manager-loop.dot.js.map +0 -1
  28. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.d.ts +0 -42
  29. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.d.ts.map +0 -1
  30. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.js +0 -118
  31. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.js.map +0 -1
  32. package/dist/__tests__/fixtures/subgraph-parent.dot.d.ts +0 -35
  33. package/dist/__tests__/fixtures/subgraph-parent.dot.d.ts.map +0 -1
  34. package/dist/__tests__/fixtures/subgraph-parent.dot.js +0 -69
  35. package/dist/__tests__/fixtures/subgraph-parent.dot.js.map +0 -1
  36. package/dist/__tests__/integration/advanced-graph-events.test.d.ts +0 -19
  37. package/dist/__tests__/integration/advanced-graph-events.test.d.ts.map +0 -1
  38. package/dist/__tests__/integration/advanced-graph-events.test.js +0 -288
  39. package/dist/__tests__/integration/advanced-graph-events.test.js.map +0 -1
  40. package/dist/__tests__/integration/checkpoint-resume.test.d.ts +0 -10
  41. package/dist/__tests__/integration/checkpoint-resume.test.d.ts.map +0 -1
  42. package/dist/__tests__/integration/checkpoint-resume.test.js +0 -125
  43. package/dist/__tests__/integration/checkpoint-resume.test.js.map +0 -1
  44. package/dist/__tests__/integration/conditional-pipeline.test.d.ts +0 -10
  45. package/dist/__tests__/integration/conditional-pipeline.test.d.ts.map +0 -1
  46. package/dist/__tests__/integration/conditional-pipeline.test.js +0 -106
  47. package/dist/__tests__/integration/conditional-pipeline.test.js.map +0 -1
  48. package/dist/__tests__/integration/convergence-validation.test.d.ts +0 -14
  49. package/dist/__tests__/integration/convergence-validation.test.d.ts.map +0 -1
  50. package/dist/__tests__/integration/convergence-validation.test.js +0 -449
  51. package/dist/__tests__/integration/convergence-validation.test.js.map +0 -1
  52. package/dist/__tests__/integration/epic44-coverage-gate.test.d.ts +0 -12
  53. package/dist/__tests__/integration/epic44-coverage-gate.test.d.ts.map +0 -1
  54. package/dist/__tests__/integration/epic44-coverage-gate.test.js +0 -58
  55. package/dist/__tests__/integration/epic44-coverage-gate.test.js.map +0 -1
  56. package/dist/__tests__/integration/epic45-coverage-gate.test.d.ts +0 -11
  57. package/dist/__tests__/integration/epic45-coverage-gate.test.d.ts.map +0 -1
  58. package/dist/__tests__/integration/epic45-coverage-gate.test.js +0 -64
  59. package/dist/__tests__/integration/epic45-coverage-gate.test.js.map +0 -1
  60. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.d.ts +0 -2
  61. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.d.ts.map +0 -1
  62. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.js +0 -285
  63. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.js.map +0 -1
  64. package/dist/__tests__/integration/events.test.d.ts +0 -8
  65. package/dist/__tests__/integration/events.test.d.ts.map +0 -1
  66. package/dist/__tests__/integration/events.test.js +0 -194
  67. package/dist/__tests__/integration/events.test.js.map +0 -1
  68. package/dist/__tests__/integration/graphs.d.ts +0 -59
  69. package/dist/__tests__/integration/graphs.d.ts.map +0 -1
  70. package/dist/__tests__/integration/graphs.js +0 -164
  71. package/dist/__tests__/integration/graphs.js.map +0 -1
  72. package/dist/__tests__/integration/helpers.d.ts +0 -127
  73. package/dist/__tests__/integration/helpers.d.ts.map +0 -1
  74. package/dist/__tests__/integration/helpers.js +0 -167
  75. package/dist/__tests__/integration/helpers.js.map +0 -1
  76. package/dist/__tests__/integration/integrity.test.d.ts +0 -8
  77. package/dist/__tests__/integration/integrity.test.d.ts.map +0 -1
  78. package/dist/__tests__/integration/integrity.test.js +0 -198
  79. package/dist/__tests__/integration/integrity.test.js.map +0 -1
  80. package/dist/__tests__/integration/llm-edge-routing.test.d.ts +0 -21
  81. package/dist/__tests__/integration/llm-edge-routing.test.d.ts.map +0 -1
  82. package/dist/__tests__/integration/llm-edge-routing.test.js +0 -341
  83. package/dist/__tests__/integration/llm-edge-routing.test.js.map +0 -1
  84. package/dist/__tests__/integration/manager-loop.test.d.ts +0 -24
  85. package/dist/__tests__/integration/manager-loop.test.d.ts.map +0 -1
  86. package/dist/__tests__/integration/manager-loop.test.js +0 -276
  87. package/dist/__tests__/integration/manager-loop.test.js.map +0 -1
  88. package/dist/__tests__/integration/multi-type-graph.test.d.ts +0 -10
  89. package/dist/__tests__/integration/multi-type-graph.test.d.ts.map +0 -1
  90. package/dist/__tests__/integration/multi-type-graph.test.js +0 -100
  91. package/dist/__tests__/integration/multi-type-graph.test.js.map +0 -1
  92. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.d.ts +0 -22
  93. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.d.ts.map +0 -1
  94. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.js +0 -515
  95. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.js.map +0 -1
  96. package/dist/__tests__/integration/persistence.test.d.ts +0 -8
  97. package/dist/__tests__/integration/persistence.test.d.ts.map +0 -1
  98. package/dist/__tests__/integration/persistence.test.js +0 -129
  99. package/dist/__tests__/integration/persistence.test.js.map +0 -1
  100. package/dist/__tests__/integration/pipeline-templates-integration.test.d.ts +0 -16
  101. package/dist/__tests__/integration/pipeline-templates-integration.test.d.ts.map +0 -1
  102. package/dist/__tests__/integration/pipeline-templates-integration.test.js +0 -171
  103. package/dist/__tests__/integration/pipeline-templates-integration.test.js.map +0 -1
  104. package/dist/__tests__/integration/scenario-pipeline.test.d.ts +0 -11
  105. package/dist/__tests__/integration/scenario-pipeline.test.d.ts.map +0 -1
  106. package/dist/__tests__/integration/scenario-pipeline.test.js +0 -243
  107. package/dist/__tests__/integration/scenario-pipeline.test.js.map +0 -1
  108. package/dist/__tests__/integration/stylesheet-application.test.d.ts +0 -12
  109. package/dist/__tests__/integration/stylesheet-application.test.d.ts.map +0 -1
  110. package/dist/__tests__/integration/stylesheet-application.test.js +0 -119
  111. package/dist/__tests__/integration/stylesheet-application.test.js.map +0 -1
  112. package/dist/__tests__/integration/subgraph-execution.test.d.ts +0 -24
  113. package/dist/__tests__/integration/subgraph-execution.test.d.ts.map +0 -1
  114. package/dist/__tests__/integration/subgraph-execution.test.js +0 -291
  115. package/dist/__tests__/integration/subgraph-execution.test.js.map +0 -1
  116. package/dist/__tests__/integration/validation-errors.test.d.ts +0 -8
  117. package/dist/__tests__/integration/validation-errors.test.d.ts.map +0 -1
  118. package/dist/__tests__/integration/validation-errors.test.js +0 -150
  119. package/dist/__tests__/integration/validation-errors.test.js.map +0 -1
  120. package/dist/agent/__tests__/loop-detection.test.d.ts +0 -2
  121. package/dist/agent/__tests__/loop-detection.test.d.ts.map +0 -1
  122. package/dist/agent/__tests__/loop-detection.test.js +0 -236
  123. package/dist/agent/__tests__/loop-detection.test.js.map +0 -1
  124. package/dist/agent/__tests__/loop.test.d.ts +0 -2
  125. package/dist/agent/__tests__/loop.test.d.ts.map +0 -1
  126. package/dist/agent/__tests__/loop.test.js +0 -868
  127. package/dist/agent/__tests__/loop.test.js.map +0 -1
  128. package/dist/agent/__tests__/truncation.test.d.ts +0 -2
  129. package/dist/agent/__tests__/truncation.test.d.ts.map +0 -1
  130. package/dist/agent/__tests__/truncation.test.js +0 -276
  131. package/dist/agent/__tests__/truncation.test.js.map +0 -1
  132. package/dist/agent/tools/__tests__/anthropic-tools.test.d.ts +0 -6
  133. package/dist/agent/tools/__tests__/anthropic-tools.test.d.ts.map +0 -1
  134. package/dist/agent/tools/__tests__/anthropic-tools.test.js +0 -49
  135. package/dist/agent/tools/__tests__/anthropic-tools.test.js.map +0 -1
  136. package/dist/agent/tools/__tests__/environment.test.d.ts +0 -6
  137. package/dist/agent/tools/__tests__/environment.test.d.ts.map +0 -1
  138. package/dist/agent/tools/__tests__/environment.test.js +0 -33
  139. package/dist/agent/tools/__tests__/environment.test.js.map +0 -1
  140. package/dist/agent/tools/__tests__/gemini-tools.test.d.ts +0 -6
  141. package/dist/agent/tools/__tests__/gemini-tools.test.d.ts.map +0 -1
  142. package/dist/agent/tools/__tests__/gemini-tools.test.js +0 -98
  143. package/dist/agent/tools/__tests__/gemini-tools.test.js.map +0 -1
  144. package/dist/agent/tools/__tests__/openai-tools.test.d.ts +0 -6
  145. package/dist/agent/tools/__tests__/openai-tools.test.d.ts.map +0 -1
  146. package/dist/agent/tools/__tests__/openai-tools.test.js +0 -53
  147. package/dist/agent/tools/__tests__/openai-tools.test.js.map +0 -1
  148. package/dist/agent/tools/__tests__/patch.test.d.ts +0 -6
  149. package/dist/agent/tools/__tests__/patch.test.d.ts.map +0 -1
  150. package/dist/agent/tools/__tests__/patch.test.js +0 -116
  151. package/dist/agent/tools/__tests__/patch.test.js.map +0 -1
  152. package/dist/agent/tools/__tests__/profiles.test.d.ts +0 -6
  153. package/dist/agent/tools/__tests__/profiles.test.d.ts.map +0 -1
  154. package/dist/agent/tools/__tests__/profiles.test.js +0 -125
  155. package/dist/agent/tools/__tests__/profiles.test.js.map +0 -1
  156. package/dist/agent/tools/__tests__/registry.test.d.ts +0 -6
  157. package/dist/agent/tools/__tests__/registry.test.d.ts.map +0 -1
  158. package/dist/agent/tools/__tests__/registry.test.js +0 -94
  159. package/dist/agent/tools/__tests__/registry.test.js.map +0 -1
  160. package/dist/agent/tools/__tests__/shared.test.d.ts +0 -6
  161. package/dist/agent/tools/__tests__/shared.test.d.ts.map +0 -1
  162. package/dist/agent/tools/__tests__/shared.test.js +0 -131
  163. package/dist/agent/tools/__tests__/shared.test.js.map +0 -1
  164. package/dist/backend/__tests__/direct-backend.test.d.ts +0 -14
  165. package/dist/backend/__tests__/direct-backend.test.d.ts.map +0 -1
  166. package/dist/backend/__tests__/direct-backend.test.js +0 -393
  167. package/dist/backend/__tests__/direct-backend.test.js.map +0 -1
  168. package/dist/backend/__tests__/direct-bootstrap.test.d.ts +0 -7
  169. package/dist/backend/__tests__/direct-bootstrap.test.d.ts.map +0 -1
  170. package/dist/backend/__tests__/direct-bootstrap.test.js +0 -177
  171. package/dist/backend/__tests__/direct-bootstrap.test.js.map +0 -1
  172. package/dist/backend/__tests__/mock-backend.test.d.ts +0 -7
  173. package/dist/backend/__tests__/mock-backend.test.d.ts.map +0 -1
  174. package/dist/backend/__tests__/mock-backend.test.js +0 -273
  175. package/dist/backend/__tests__/mock-backend.test.js.map +0 -1
  176. package/dist/backend/__tests__/parity.test.d.ts +0 -17
  177. package/dist/backend/__tests__/parity.test.d.ts.map +0 -1
  178. package/dist/backend/__tests__/parity.test.js +0 -411
  179. package/dist/backend/__tests__/parity.test.js.map +0 -1
  180. package/dist/context/__tests__/auto-summarizer.test.d.ts +0 -14
  181. package/dist/context/__tests__/auto-summarizer.test.d.ts.map +0 -1
  182. package/dist/context/__tests__/auto-summarizer.test.js +0 -189
  183. package/dist/context/__tests__/auto-summarizer.test.js.map +0 -1
  184. package/dist/context/__tests__/context-cli-command.test.d.ts +0 -7
  185. package/dist/context/__tests__/context-cli-command.test.d.ts.map +0 -1
  186. package/dist/context/__tests__/context-cli-command.test.js +0 -331
  187. package/dist/context/__tests__/context-cli-command.test.js.map +0 -1
  188. package/dist/context/__tests__/pyramid-summary-integration.test.d.ts +0 -2
  189. package/dist/context/__tests__/pyramid-summary-integration.test.d.ts.map +0 -1
  190. package/dist/context/__tests__/pyramid-summary-integration.test.js +0 -533
  191. package/dist/context/__tests__/pyramid-summary-integration.test.js.map +0 -1
  192. package/dist/context/__tests__/summarizer.test.d.ts +0 -2
  193. package/dist/context/__tests__/summarizer.test.d.ts.map +0 -1
  194. package/dist/context/__tests__/summarizer.test.js +0 -189
  195. package/dist/context/__tests__/summarizer.test.js.map +0 -1
  196. package/dist/context/__tests__/summary-cache.test.d.ts +0 -2
  197. package/dist/context/__tests__/summary-cache.test.d.ts.map +0 -1
  198. package/dist/context/__tests__/summary-cache.test.js +0 -214
  199. package/dist/context/__tests__/summary-cache.test.js.map +0 -1
  200. package/dist/context/__tests__/summary-metrics.test.d.ts +0 -2
  201. package/dist/context/__tests__/summary-metrics.test.d.ts.map +0 -1
  202. package/dist/context/__tests__/summary-metrics.test.js +0 -172
  203. package/dist/context/__tests__/summary-metrics.test.js.map +0 -1
  204. package/dist/context/__tests__/summary-types.test.d.ts +0 -2
  205. package/dist/context/__tests__/summary-types.test.d.ts.map +0 -1
  206. package/dist/context/__tests__/summary-types.test.js +0 -130
  207. package/dist/context/__tests__/summary-types.test.js.map +0 -1
  208. package/dist/convergence/__tests__/budget.test.d.ts +0 -6
  209. package/dist/convergence/__tests__/budget.test.d.ts.map +0 -1
  210. package/dist/convergence/__tests__/budget.test.js +0 -187
  211. package/dist/convergence/__tests__/budget.test.js.map +0 -1
  212. package/dist/convergence/__tests__/controller.test.d.ts +0 -9
  213. package/dist/convergence/__tests__/controller.test.d.ts.map +0 -1
  214. package/dist/convergence/__tests__/controller.test.js +0 -585
  215. package/dist/convergence/__tests__/controller.test.js.map +0 -1
  216. package/dist/convergence/__tests__/dual-signal.test.d.ts +0 -14
  217. package/dist/convergence/__tests__/dual-signal.test.d.ts.map +0 -1
  218. package/dist/convergence/__tests__/dual-signal.test.js +0 -123
  219. package/dist/convergence/__tests__/dual-signal.test.js.map +0 -1
  220. package/dist/convergence/__tests__/epic46-integration.test.d.ts +0 -15
  221. package/dist/convergence/__tests__/epic46-integration.test.d.ts.map +0 -1
  222. package/dist/convergence/__tests__/epic46-integration.test.js +0 -522
  223. package/dist/convergence/__tests__/epic46-integration.test.js.map +0 -1
  224. package/dist/convergence/__tests__/plateau.test.d.ts +0 -6
  225. package/dist/convergence/__tests__/plateau.test.d.ts.map +0 -1
  226. package/dist/convergence/__tests__/plateau.test.js +0 -163
  227. package/dist/convergence/__tests__/plateau.test.js.map +0 -1
  228. package/dist/convergence/__tests__/remediation.test.d.ts +0 -11
  229. package/dist/convergence/__tests__/remediation.test.d.ts.map +0 -1
  230. package/dist/convergence/__tests__/remediation.test.js +0 -209
  231. package/dist/convergence/__tests__/remediation.test.js.map +0 -1
  232. package/dist/convergence/__tests__/scenario-primary.test.d.ts +0 -13
  233. package/dist/convergence/__tests__/scenario-primary.test.d.ts.map +0 -1
  234. package/dist/convergence/__tests__/scenario-primary.test.js +0 -183
  235. package/dist/convergence/__tests__/scenario-primary.test.js.map +0 -1
  236. package/dist/factory-command.test.d.ts +0 -8
  237. package/dist/factory-command.test.d.ts.map +0 -1
  238. package/dist/factory-command.test.js +0 -304
  239. package/dist/factory-command.test.js.map +0 -1
  240. package/dist/graph/__tests__/attractor-compliance.test.d.ts +0 -10
  241. package/dist/graph/__tests__/attractor-compliance.test.d.ts.map +0 -1
  242. package/dist/graph/__tests__/attractor-compliance.test.js +0 -766
  243. package/dist/graph/__tests__/attractor-compliance.test.js.map +0 -1
  244. package/dist/graph/__tests__/checkpoint.test.d.ts +0 -8
  245. package/dist/graph/__tests__/checkpoint.test.d.ts.map +0 -1
  246. package/dist/graph/__tests__/checkpoint.test.js +0 -329
  247. package/dist/graph/__tests__/checkpoint.test.js.map +0 -1
  248. package/dist/graph/__tests__/condition-parser.test.d.ts +0 -14
  249. package/dist/graph/__tests__/condition-parser.test.d.ts.map +0 -1
  250. package/dist/graph/__tests__/condition-parser.test.js +0 -406
  251. package/dist/graph/__tests__/condition-parser.test.js.map +0 -1
  252. package/dist/graph/__tests__/context.test.d.ts +0 -14
  253. package/dist/graph/__tests__/context.test.d.ts.map +0 -1
  254. package/dist/graph/__tests__/context.test.js +0 -276
  255. package/dist/graph/__tests__/context.test.js.map +0 -1
  256. package/dist/graph/__tests__/edge-selector-events.test.d.ts +0 -11
  257. package/dist/graph/__tests__/edge-selector-events.test.d.ts.map +0 -1
  258. package/dist/graph/__tests__/edge-selector-events.test.js +0 -184
  259. package/dist/graph/__tests__/edge-selector-events.test.js.map +0 -1
  260. package/dist/graph/__tests__/edge-selector.test.d.ts +0 -6
  261. package/dist/graph/__tests__/edge-selector.test.d.ts.map +0 -1
  262. package/dist/graph/__tests__/edge-selector.test.js +0 -452
  263. package/dist/graph/__tests__/edge-selector.test.js.map +0 -1
  264. package/dist/graph/__tests__/executor-convergence.test.d.ts +0 -12
  265. package/dist/graph/__tests__/executor-convergence.test.d.ts.map +0 -1
  266. package/dist/graph/__tests__/executor-convergence.test.js +0 -432
  267. package/dist/graph/__tests__/executor-convergence.test.js.map +0 -1
  268. package/dist/graph/__tests__/executor-fidelity.test.d.ts +0 -13
  269. package/dist/graph/__tests__/executor-fidelity.test.d.ts.map +0 -1
  270. package/dist/graph/__tests__/executor-fidelity.test.js +0 -335
  271. package/dist/graph/__tests__/executor-fidelity.test.js.map +0 -1
  272. package/dist/graph/__tests__/executor.test.d.ts +0 -14
  273. package/dist/graph/__tests__/executor.test.d.ts.map +0 -1
  274. package/dist/graph/__tests__/executor.test.js +0 -901
  275. package/dist/graph/__tests__/executor.test.js.map +0 -1
  276. package/dist/graph/__tests__/fidelity.test.d.ts +0 -8
  277. package/dist/graph/__tests__/fidelity.test.d.ts.map +0 -1
  278. package/dist/graph/__tests__/fidelity.test.js +0 -135
  279. package/dist/graph/__tests__/fidelity.test.js.map +0 -1
  280. package/dist/graph/__tests__/llm-evaluator.test.d.ts +0 -7
  281. package/dist/graph/__tests__/llm-evaluator.test.d.ts.map +0 -1
  282. package/dist/graph/__tests__/llm-evaluator.test.js +0 -106
  283. package/dist/graph/__tests__/llm-evaluator.test.js.map +0 -1
  284. package/dist/graph/__tests__/parser-chaining.test.d.ts +0 -13
  285. package/dist/graph/__tests__/parser-chaining.test.d.ts.map +0 -1
  286. package/dist/graph/__tests__/parser-chaining.test.js +0 -215
  287. package/dist/graph/__tests__/parser-chaining.test.js.map +0 -1
  288. package/dist/graph/__tests__/parser.test.d.ts +0 -22
  289. package/dist/graph/__tests__/parser.test.d.ts.map +0 -1
  290. package/dist/graph/__tests__/parser.test.js +0 -452
  291. package/dist/graph/__tests__/parser.test.js.map +0 -1
  292. package/dist/graph/__tests__/run-state.test.d.ts +0 -13
  293. package/dist/graph/__tests__/run-state.test.d.ts.map +0 -1
  294. package/dist/graph/__tests__/run-state.test.js +0 -189
  295. package/dist/graph/__tests__/run-state.test.js.map +0 -1
  296. package/dist/graph/__tests__/transformer.test.d.ts +0 -16
  297. package/dist/graph/__tests__/transformer.test.d.ts.map +0 -1
  298. package/dist/graph/__tests__/transformer.test.js +0 -350
  299. package/dist/graph/__tests__/transformer.test.js.map +0 -1
  300. package/dist/graph/__tests__/validator-errors.test.d.ts +0 -15
  301. package/dist/graph/__tests__/validator-errors.test.d.ts.map +0 -1
  302. package/dist/graph/__tests__/validator-errors.test.js +0 -572
  303. package/dist/graph/__tests__/validator-errors.test.js.map +0 -1
  304. package/dist/graph/__tests__/validator-warnings.test.d.ts +0 -15
  305. package/dist/graph/__tests__/validator-warnings.test.d.ts.map +0 -1
  306. package/dist/graph/__tests__/validator-warnings.test.js +0 -363
  307. package/dist/graph/__tests__/validator-warnings.test.js.map +0 -1
  308. package/dist/handlers/__tests__/codergen-handler.test.d.ts +0 -14
  309. package/dist/handlers/__tests__/codergen-handler.test.d.ts.map +0 -1
  310. package/dist/handlers/__tests__/codergen-handler.test.js +0 -442
  311. package/dist/handlers/__tests__/codergen-handler.test.js.map +0 -1
  312. package/dist/handlers/__tests__/fan-in.test.d.ts +0 -14
  313. package/dist/handlers/__tests__/fan-in.test.d.ts.map +0 -1
  314. package/dist/handlers/__tests__/fan-in.test.js +0 -399
  315. package/dist/handlers/__tests__/fan-in.test.js.map +0 -1
  316. package/dist/handlers/__tests__/join-policy.test.d.ts +0 -9
  317. package/dist/handlers/__tests__/join-policy.test.d.ts.map +0 -1
  318. package/dist/handlers/__tests__/join-policy.test.js +0 -201
  319. package/dist/handlers/__tests__/join-policy.test.js.map +0 -1
  320. package/dist/handlers/__tests__/manager-loop.test.d.ts +0 -14
  321. package/dist/handlers/__tests__/manager-loop.test.d.ts.map +0 -1
  322. package/dist/handlers/__tests__/manager-loop.test.js +0 -322
  323. package/dist/handlers/__tests__/manager-loop.test.js.map +0 -1
  324. package/dist/handlers/__tests__/parallel-events.test.d.ts +0 -12
  325. package/dist/handlers/__tests__/parallel-events.test.d.ts.map +0 -1
  326. package/dist/handlers/__tests__/parallel-events.test.js +0 -252
  327. package/dist/handlers/__tests__/parallel-events.test.js.map +0 -1
  328. package/dist/handlers/__tests__/parallel-handler.test.d.ts +0 -14
  329. package/dist/handlers/__tests__/parallel-handler.test.d.ts.map +0 -1
  330. package/dist/handlers/__tests__/parallel-handler.test.js +0 -337
  331. package/dist/handlers/__tests__/parallel-handler.test.js.map +0 -1
  332. package/dist/handlers/__tests__/parallel-join.test.d.ts +0 -9
  333. package/dist/handlers/__tests__/parallel-join.test.d.ts.map +0 -1
  334. package/dist/handlers/__tests__/parallel-join.test.js +0 -267
  335. package/dist/handlers/__tests__/parallel-join.test.js.map +0 -1
  336. package/dist/handlers/__tests__/registry.test.d.ts +0 -14
  337. package/dist/handlers/__tests__/registry.test.d.ts.map +0 -1
  338. package/dist/handlers/__tests__/registry.test.js +0 -315
  339. package/dist/handlers/__tests__/registry.test.js.map +0 -1
  340. package/dist/handlers/__tests__/subgraph-events.test.d.ts +0 -10
  341. package/dist/handlers/__tests__/subgraph-events.test.d.ts.map +0 -1
  342. package/dist/handlers/__tests__/subgraph-events.test.js +0 -189
  343. package/dist/handlers/__tests__/subgraph-events.test.js.map +0 -1
  344. package/dist/handlers/__tests__/subgraph-inheritance.test.d.ts +0 -14
  345. package/dist/handlers/__tests__/subgraph-inheritance.test.d.ts.map +0 -1
  346. package/dist/handlers/__tests__/subgraph-inheritance.test.js +0 -267
  347. package/dist/handlers/__tests__/subgraph-inheritance.test.js.map +0 -1
  348. package/dist/handlers/__tests__/subgraph.test.d.ts +0 -14
  349. package/dist/handlers/__tests__/subgraph.test.d.ts.map +0 -1
  350. package/dist/handlers/__tests__/subgraph.test.js +0 -369
  351. package/dist/handlers/__tests__/subgraph.test.js.map +0 -1
  352. package/dist/handlers/__tests__/tool-handler.test.d.ts +0 -11
  353. package/dist/handlers/__tests__/tool-handler.test.d.ts.map +0 -1
  354. package/dist/handlers/__tests__/tool-handler.test.js +0 -184
  355. package/dist/handlers/__tests__/tool-handler.test.js.map +0 -1
  356. package/dist/handlers/__tests__/tool-scenario.test.d.ts +0 -12
  357. package/dist/handlers/__tests__/tool-scenario.test.d.ts.map +0 -1
  358. package/dist/handlers/__tests__/tool-scenario.test.js +0 -222
  359. package/dist/handlers/__tests__/tool-scenario.test.js.map +0 -1
  360. package/dist/handlers/__tests__/wait-human-handler.test.d.ts +0 -11
  361. package/dist/handlers/__tests__/wait-human-handler.test.d.ts.map +0 -1
  362. package/dist/handlers/__tests__/wait-human-handler.test.js +0 -251
  363. package/dist/handlers/__tests__/wait-human-handler.test.js.map +0 -1
  364. package/dist/llm/__tests__/client.test.d.ts +0 -2
  365. package/dist/llm/__tests__/client.test.d.ts.map +0 -1
  366. package/dist/llm/__tests__/client.test.js +0 -198
  367. package/dist/llm/__tests__/client.test.js.map +0 -1
  368. package/dist/llm/__tests__/types.test.d.ts +0 -2
  369. package/dist/llm/__tests__/types.test.d.ts.map +0 -1
  370. package/dist/llm/__tests__/types.test.js +0 -289
  371. package/dist/llm/__tests__/types.test.js.map +0 -1
  372. package/dist/llm/middleware/__tests__/cost-tracking.test.d.ts +0 -2
  373. package/dist/llm/middleware/__tests__/cost-tracking.test.d.ts.map +0 -1
  374. package/dist/llm/middleware/__tests__/cost-tracking.test.js +0 -73
  375. package/dist/llm/middleware/__tests__/cost-tracking.test.js.map +0 -1
  376. package/dist/llm/middleware/__tests__/logging.test.d.ts +0 -2
  377. package/dist/llm/middleware/__tests__/logging.test.d.ts.map +0 -1
  378. package/dist/llm/middleware/__tests__/logging.test.js +0 -127
  379. package/dist/llm/middleware/__tests__/logging.test.js.map +0 -1
  380. package/dist/llm/middleware/__tests__/retry.test.d.ts +0 -2
  381. package/dist/llm/middleware/__tests__/retry.test.d.ts.map +0 -1
  382. package/dist/llm/middleware/__tests__/retry.test.js +0 -126
  383. package/dist/llm/middleware/__tests__/retry.test.js.map +0 -1
  384. package/dist/llm/providers/__tests__/anthropic.test.d.ts +0 -2
  385. package/dist/llm/providers/__tests__/anthropic.test.d.ts.map +0 -1
  386. package/dist/llm/providers/__tests__/anthropic.test.js +0 -412
  387. package/dist/llm/providers/__tests__/anthropic.test.js.map +0 -1
  388. package/dist/llm/providers/__tests__/gemini.test.d.ts +0 -2
  389. package/dist/llm/providers/__tests__/gemini.test.d.ts.map +0 -1
  390. package/dist/llm/providers/__tests__/gemini.test.js +0 -591
  391. package/dist/llm/providers/__tests__/gemini.test.js.map +0 -1
  392. package/dist/llm/providers/__tests__/openai.test.d.ts +0 -2
  393. package/dist/llm/providers/__tests__/openai.test.d.ts.map +0 -1
  394. package/dist/llm/providers/__tests__/openai.test.js +0 -546
  395. package/dist/llm/providers/__tests__/openai.test.js.map +0 -1
  396. package/dist/persistence/__tests__/factory-queries.test.d.ts +0 -9
  397. package/dist/persistence/__tests__/factory-queries.test.d.ts.map +0 -1
  398. package/dist/persistence/__tests__/factory-queries.test.js +0 -372
  399. package/dist/persistence/__tests__/factory-queries.test.js.map +0 -1
  400. package/dist/persistence/__tests__/factory-schema.test.d.ts +0 -6
  401. package/dist/persistence/__tests__/factory-schema.test.d.ts.map +0 -1
  402. package/dist/persistence/__tests__/factory-schema.test.js +0 -105
  403. package/dist/persistence/__tests__/factory-schema.test.js.map +0 -1
  404. package/dist/scenarios/__tests__/cli-command-list.test.d.ts +0 -7
  405. package/dist/scenarios/__tests__/cli-command-list.test.d.ts.map +0 -1
  406. package/dist/scenarios/__tests__/cli-command-list.test.js +0 -237
  407. package/dist/scenarios/__tests__/cli-command-list.test.js.map +0 -1
  408. package/dist/scenarios/__tests__/cli-command.test.d.ts +0 -11
  409. package/dist/scenarios/__tests__/cli-command.test.d.ts.map +0 -1
  410. package/dist/scenarios/__tests__/cli-command.test.js +0 -275
  411. package/dist/scenarios/__tests__/cli-command.test.js.map +0 -1
  412. package/dist/scenarios/__tests__/integrity-pipeline.test.d.ts +0 -15
  413. package/dist/scenarios/__tests__/integrity-pipeline.test.d.ts.map +0 -1
  414. package/dist/scenarios/__tests__/integrity-pipeline.test.js +0 -318
  415. package/dist/scenarios/__tests__/integrity-pipeline.test.js.map +0 -1
  416. package/dist/scenarios/__tests__/runner-twins.test.d.ts +0 -13
  417. package/dist/scenarios/__tests__/runner-twins.test.d.ts.map +0 -1
  418. package/dist/scenarios/__tests__/runner-twins.test.js +0 -205
  419. package/dist/scenarios/__tests__/runner-twins.test.js.map +0 -1
  420. package/dist/scenarios/__tests__/scorer.test.d.ts +0 -11
  421. package/dist/scenarios/__tests__/scorer.test.d.ts.map +0 -1
  422. package/dist/scenarios/__tests__/scorer.test.js +0 -225
  423. package/dist/scenarios/__tests__/scorer.test.js.map +0 -1
  424. package/dist/scenarios/__tests__/scoring-integration.test.d.ts +0 -8
  425. package/dist/scenarios/__tests__/scoring-integration.test.d.ts.map +0 -1
  426. package/dist/scenarios/__tests__/scoring-integration.test.js +0 -178
  427. package/dist/scenarios/__tests__/scoring-integration.test.js.map +0 -1
  428. package/dist/scenarios/__tests__/store.test.d.ts +0 -5
  429. package/dist/scenarios/__tests__/store.test.d.ts.map +0 -1
  430. package/dist/scenarios/__tests__/store.test.js +0 -169
  431. package/dist/scenarios/__tests__/store.test.js.map +0 -1
  432. package/dist/stylesheet/__tests__/stylesheet.test.d.ts +0 -17
  433. package/dist/stylesheet/__tests__/stylesheet.test.d.ts.map +0 -1
  434. package/dist/stylesheet/__tests__/stylesheet.test.js +0 -368
  435. package/dist/stylesheet/__tests__/stylesheet.test.js.map +0 -1
  436. package/dist/templates/__tests__/templates.test.d.ts +0 -7
  437. package/dist/templates/__tests__/templates.test.d.ts.map +0 -1
  438. package/dist/templates/__tests__/templates.test.js +0 -92
  439. package/dist/templates/__tests__/templates.test.js.map +0 -1
  440. package/dist/twins/__tests__/docker-compose.test.d.ts +0 -13
  441. package/dist/twins/__tests__/docker-compose.test.d.ts.map +0 -1
  442. package/dist/twins/__tests__/docker-compose.test.js +0 -247
  443. package/dist/twins/__tests__/docker-compose.test.js.map +0 -1
  444. package/dist/twins/__tests__/health-monitor.test.d.ts +0 -19
  445. package/dist/twins/__tests__/health-monitor.test.d.ts.map +0 -1
  446. package/dist/twins/__tests__/health-monitor.test.js +0 -301
  447. package/dist/twins/__tests__/health-monitor.test.js.map +0 -1
  448. package/dist/twins/__tests__/integration/e2e.test.d.ts +0 -18
  449. package/dist/twins/__tests__/integration/e2e.test.d.ts.map +0 -1
  450. package/dist/twins/__tests__/integration/e2e.test.js +0 -146
  451. package/dist/twins/__tests__/integration/e2e.test.js.map +0 -1
  452. package/dist/twins/__tests__/integration/health-monitor-integration.test.d.ts +0 -16
  453. package/dist/twins/__tests__/integration/health-monitor-integration.test.d.ts.map +0 -1
  454. package/dist/twins/__tests__/integration/health-monitor-integration.test.js +0 -183
  455. package/dist/twins/__tests__/integration/health-monitor-integration.test.js.map +0 -1
  456. package/dist/twins/__tests__/integration/helpers.d.ts +0 -32
  457. package/dist/twins/__tests__/integration/helpers.d.ts.map +0 -1
  458. package/dist/twins/__tests__/integration/helpers.js +0 -67
  459. package/dist/twins/__tests__/integration/helpers.js.map +0 -1
  460. package/dist/twins/__tests__/integration/lifecycle.test.d.ts +0 -14
  461. package/dist/twins/__tests__/integration/lifecycle.test.d.ts.map +0 -1
  462. package/dist/twins/__tests__/integration/lifecycle.test.js +0 -127
  463. package/dist/twins/__tests__/integration/lifecycle.test.js.map +0 -1
  464. package/dist/twins/__tests__/integration/persistence-integration.test.d.ts +0 -14
  465. package/dist/twins/__tests__/integration/persistence-integration.test.d.ts.map +0 -1
  466. package/dist/twins/__tests__/integration/persistence-integration.test.js +0 -132
  467. package/dist/twins/__tests__/integration/persistence-integration.test.js.map +0 -1
  468. package/dist/twins/__tests__/persistence.test.d.ts +0 -10
  469. package/dist/twins/__tests__/persistence.test.d.ts.map +0 -1
  470. package/dist/twins/__tests__/persistence.test.js +0 -300
  471. package/dist/twins/__tests__/persistence.test.js.map +0 -1
  472. package/dist/twins/__tests__/registry.test.d.ts +0 -7
  473. package/dist/twins/__tests__/registry.test.d.ts.map +0 -1
  474. package/dist/twins/__tests__/registry.test.js +0 -282
  475. package/dist/twins/__tests__/registry.test.js.map +0 -1
  476. package/dist/twins/__tests__/run-state.test.d.ts +0 -9
  477. package/dist/twins/__tests__/run-state.test.d.ts.map +0 -1
  478. package/dist/twins/__tests__/run-state.test.js +0 -112
  479. package/dist/twins/__tests__/run-state.test.js.map +0 -1
  480. package/dist/twins/__tests__/templates-cli.test.d.ts +0 -10
  481. package/dist/twins/__tests__/templates-cli.test.d.ts.map +0 -1
  482. package/dist/twins/__tests__/templates-cli.test.js +0 -187
  483. package/dist/twins/__tests__/templates-cli.test.js.map +0 -1
  484. package/dist/twins/__tests__/templates.test.d.ts +0 -7
  485. package/dist/twins/__tests__/templates.test.d.ts.map +0 -1
  486. package/dist/twins/__tests__/templates.test.js +0 -87
  487. package/dist/twins/__tests__/templates.test.js.map +0 -1
  488. package/dist/twins/__tests__/twins-cli.test.d.ts +0 -11
  489. package/dist/twins/__tests__/twins-cli.test.d.ts.map +0 -1
  490. package/dist/twins/__tests__/twins-cli.test.js +0 -365
  491. package/dist/twins/__tests__/twins-cli.test.js.map +0 -1
@@ -1,868 +0,0 @@
1
- // packages/factory/src/agent/__tests__/loop.test.ts
2
- // Tests for CodingAgentSession and the core agentic loop.
3
- // Story 48-7: Coding Agent Loop — Core Agentic Loop
4
- import { describe, it, expect, vi } from 'vitest';
5
- import { EventEmitter } from 'node:events';
6
- import { createSession, CodingAgentSession, convertHistoryToMessages } from '../loop.js';
7
- import { EventKind, SessionState } from '../types.js';
8
- // ---------------------------------------------------------------------------
9
- // Test helpers / shared mocks
10
- // ---------------------------------------------------------------------------
11
- function makeUsage(override = {}) {
12
- return {
13
- inputTokens: 10,
14
- outputTokens: 5,
15
- totalTokens: 15,
16
- ...override,
17
- };
18
- }
19
- function makeTextResponse(content = 'Done!') {
20
- return {
21
- content,
22
- toolCalls: [],
23
- usage: makeUsage(),
24
- model: 'test-model',
25
- stopReason: 'stop',
26
- providerMetadata: {},
27
- };
28
- }
29
- function makeToolCallResponse(toolName, callId = 'call1', args = {}) {
30
- return {
31
- content: '',
32
- toolCalls: [{ id: callId, name: toolName, arguments: args }],
33
- usage: makeUsage(),
34
- model: 'test-model',
35
- stopReason: 'tool_calls',
36
- providerMetadata: {},
37
- };
38
- }
39
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
- function makeToolDefinition(name, executor) {
41
- return {
42
- name,
43
- description: `Mock ${name} tool`,
44
- inputSchema: { type: 'object', properties: {}, required: [] },
45
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
- executor: executor ?? vi.fn().mockResolvedValue(`${name} output`),
47
- };
48
- }
49
- function makeProfile(tools = [], overrides = {}) {
50
- return {
51
- id: 'test',
52
- model: 'test-model',
53
- supports_streaming: false,
54
- context_window_size: 100_000,
55
- supports_parallel_tool_calls: true,
56
- build_system_prompt: vi.fn().mockReturnValue('System prompt'),
57
- tools: vi.fn().mockReturnValue(tools),
58
- provider_options: vi.fn().mockReturnValue({}),
59
- ...overrides,
60
- };
61
- }
62
- function makeMockClient(completeFn) {
63
- return { complete: completeFn };
64
- }
65
- function makeEnv() {
66
- return {
67
- workdir: '/tmp',
68
- exec: vi.fn().mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }),
69
- };
70
- }
71
- function makeConfig(overrides = {}) {
72
- return {
73
- max_turns: 0,
74
- max_tool_rounds_per_input: 0,
75
- tool_output_limits: new Map(),
76
- ...overrides,
77
- };
78
- }
79
- // Collect all events for specific kinds
80
- function collectEvents(session, ...kinds) {
81
- const events = [];
82
- for (const kind of kinds) {
83
- session.on(kind, e => events.push(e));
84
- }
85
- return events;
86
- }
87
- // Collect all events from a session
88
- function collectAllEvents(session) {
89
- const events = [];
90
- for (const kind of Object.values(EventKind)) {
91
- session.on(kind, e => events.push(e));
92
- }
93
- return events;
94
- }
95
- // ---------------------------------------------------------------------------
96
- // Test Suite: createSession
97
- // ---------------------------------------------------------------------------
98
- describe('createSession', () => {
99
- it('returns a CodingAgentSession instance in IDLE state with a UUID id', () => {
100
- const session = createSession({
101
- llmClient: makeMockClient(vi.fn()),
102
- providerProfile: makeProfile(),
103
- executionEnv: makeEnv(),
104
- });
105
- expect(session).toBeInstanceOf(CodingAgentSession);
106
- expect(session.state).toBe(SessionState.IDLE);
107
- expect(session.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
108
- });
109
- it('emits SESSION_START event synchronously during creation', () => {
110
- // Spy on EventEmitter.prototype.emit before construction
111
- const emitSpy = vi.spyOn(EventEmitter.prototype, 'emit');
112
- const session = createSession({
113
- llmClient: makeMockClient(vi.fn()),
114
- providerProfile: makeProfile(),
115
- executionEnv: makeEnv(),
116
- });
117
- const startCall = emitSpy.mock.calls.find(args => args[0] === EventKind.SESSION_START);
118
- expect(startCall).toBeDefined();
119
- if (startCall) {
120
- const event = startCall[1];
121
- expect(event.kind).toBe(EventKind.SESSION_START);
122
- expect(event.session_id).toBe(session.id);
123
- }
124
- emitSpy.mockRestore();
125
- });
126
- it('returns session with empty history', () => {
127
- const session = createSession({
128
- llmClient: makeMockClient(vi.fn()),
129
- providerProfile: makeProfile(),
130
- executionEnv: makeEnv(),
131
- });
132
- expect(session.history).toHaveLength(0);
133
- });
134
- });
135
- // ---------------------------------------------------------------------------
136
- // Test Suite: close()
137
- // ---------------------------------------------------------------------------
138
- describe('session.close()', () => {
139
- it('transitions state to CLOSED and emits SESSION_END', () => {
140
- const session = createSession({
141
- llmClient: makeMockClient(vi.fn()),
142
- providerProfile: makeProfile(),
143
- executionEnv: makeEnv(),
144
- });
145
- const events = [];
146
- session.on(EventKind.SESSION_END, e => events.push(e));
147
- session.close();
148
- expect(session.state).toBe(SessionState.CLOSED);
149
- expect(events).toHaveLength(1);
150
- expect(events[0].kind).toBe(EventKind.SESSION_END);
151
- expect(events[0].session_id).toBe(session.id);
152
- });
153
- });
154
- // ---------------------------------------------------------------------------
155
- // Test Suite: processInput — natural completion
156
- // ---------------------------------------------------------------------------
157
- describe('processInput — natural completion (no tool calls)', () => {
158
- it('emits USER_INPUT then ASSISTANT_TEXT_END then PROCESSING_END for text-only response', async () => {
159
- const complete = vi.fn().mockResolvedValue(makeTextResponse('Hello, world!'));
160
- const session = createSession({
161
- llmClient: makeMockClient(complete),
162
- providerProfile: makeProfile(),
163
- executionEnv: makeEnv(),
164
- });
165
- const events = collectAllEvents(session);
166
- await session.processInput('hi');
167
- const kinds = events.map(e => e.kind);
168
- expect(kinds).toContain(EventKind.USER_INPUT);
169
- expect(kinds).toContain(EventKind.ASSISTANT_TEXT_END);
170
- expect(kinds).toContain(EventKind.PROCESSING_END);
171
- // Verify ordering
172
- const uiIdx = kinds.indexOf(EventKind.USER_INPUT);
173
- const ateIdx = kinds.indexOf(EventKind.ASSISTANT_TEXT_END);
174
- const peIdx = kinds.indexOf(EventKind.PROCESSING_END);
175
- expect(uiIdx).toBeLessThan(ateIdx);
176
- expect(ateIdx).toBeLessThan(peIdx);
177
- });
178
- it('appends UserTurn and AssistantTurn to history', async () => {
179
- const complete = vi.fn().mockResolvedValue(makeTextResponse('Response'));
180
- const session = createSession({
181
- llmClient: makeMockClient(complete),
182
- providerProfile: makeProfile(),
183
- executionEnv: makeEnv(),
184
- });
185
- await session.processInput('my question');
186
- expect(session.history).toHaveLength(2);
187
- expect(session.history[0].type).toBe('user');
188
- expect(session.history[1].type).toBe('assistant');
189
- });
190
- it('returns to IDLE state after processing', async () => {
191
- const complete = vi.fn().mockResolvedValue(makeTextResponse());
192
- const session = createSession({
193
- llmClient: makeMockClient(complete),
194
- providerProfile: makeProfile(),
195
- executionEnv: makeEnv(),
196
- });
197
- await session.processInput('test');
198
- expect(session.state).toBe(SessionState.IDLE);
199
- });
200
- it('calls LLM with a request built from the session (model, systemPrompt, toolChoice)', async () => {
201
- const complete = vi.fn().mockResolvedValue(makeTextResponse());
202
- const profile = makeProfile();
203
- const session = createSession({
204
- llmClient: makeMockClient(complete),
205
- providerProfile: profile,
206
- executionEnv: makeEnv(),
207
- });
208
- await session.processInput('hello');
209
- expect(complete).toHaveBeenCalledOnce();
210
- const req = complete.mock.calls[0][0];
211
- expect(req.model).toBe('test-model');
212
- expect(req.systemPrompt).toBe('System prompt');
213
- expect(req.toolChoice).toBe('auto');
214
- });
215
- });
216
- // ---------------------------------------------------------------------------
217
- // Test Suite: processInput — tool call loop
218
- // ---------------------------------------------------------------------------
219
- describe('processInput — one tool call then natural completion', () => {
220
- it('emits TOOL_CALL_START, TOOL_CALL_END, final ASSISTANT_TEXT_END, PROCESSING_END', async () => {
221
- const tool = makeToolDefinition('my_tool');
222
- const complete = vi
223
- .fn()
224
- .mockResolvedValueOnce(makeToolCallResponse('my_tool'))
225
- .mockResolvedValueOnce(makeTextResponse('All done'));
226
- const session = createSession({
227
- llmClient: makeMockClient(complete),
228
- providerProfile: makeProfile([tool]),
229
- executionEnv: makeEnv(),
230
- });
231
- const events = collectAllEvents(session);
232
- await session.processInput('run my tool');
233
- const kinds = events.map(e => e.kind);
234
- expect(kinds).toContain(EventKind.TOOL_CALL_START);
235
- expect(kinds).toContain(EventKind.TOOL_CALL_END);
236
- expect(kinds).toContain(EventKind.PROCESSING_END);
237
- // LLM was called twice (once returning tool call, once returning text)
238
- expect(complete).toHaveBeenCalledTimes(2);
239
- });
240
- it('appends UserTurn, AssistantTurn, ToolResultsTurn, and final AssistantTurn to history', async () => {
241
- const tool = makeToolDefinition('my_tool');
242
- const complete = vi
243
- .fn()
244
- .mockResolvedValueOnce(makeToolCallResponse('my_tool'))
245
- .mockResolvedValueOnce(makeTextResponse('All done'));
246
- const session = createSession({
247
- llmClient: makeMockClient(complete),
248
- providerProfile: makeProfile([tool]),
249
- executionEnv: makeEnv(),
250
- });
251
- await session.processInput('run my tool');
252
- expect(session.history).toHaveLength(4);
253
- expect(session.history[0].type).toBe('user');
254
- expect(session.history[1].type).toBe('assistant');
255
- expect(session.history[2].type).toBe('tool_results');
256
- expect(session.history[3].type).toBe('assistant');
257
- });
258
- });
259
- // ---------------------------------------------------------------------------
260
- // Test Suite: Limit enforcement
261
- // ---------------------------------------------------------------------------
262
- describe('max_tool_rounds_per_input enforcement', () => {
263
- it('stops after N rounds and emits TURN_LIMIT with correct data', async () => {
264
- const tool = makeToolDefinition('looping_tool');
265
- // LLM always returns a tool call (never natural completion)
266
- const complete = vi.fn().mockResolvedValue(makeToolCallResponse('looping_tool'));
267
- const session = createSession({
268
- llmClient: makeMockClient(complete),
269
- providerProfile: makeProfile([tool]),
270
- executionEnv: makeEnv(),
271
- config: makeConfig({ max_tool_rounds_per_input: 2 }),
272
- });
273
- const turnLimitEvents = [];
274
- session.on(EventKind.TURN_LIMIT, e => turnLimitEvents.push(e));
275
- const processingEndEvents = [];
276
- session.on(EventKind.PROCESSING_END, e => processingEndEvents.push(e));
277
- await session.processInput('loop me');
278
- expect(turnLimitEvents).toHaveLength(1);
279
- expect(turnLimitEvents[0].data.reason).toBe('max_tool_rounds_per_input');
280
- expect(turnLimitEvents[0].data.round).toBe(2);
281
- // PROCESSING_END emitted after TURN_LIMIT
282
- expect(processingEndEvents).toHaveLength(1);
283
- expect(session.state).toBe(SessionState.IDLE);
284
- });
285
- });
286
- describe('max_turns enforcement', () => {
287
- it('stops loop when total turn count reaches max_turns and emits TURN_LIMIT', async () => {
288
- const tool = makeToolDefinition('looping_tool');
289
- const complete = vi.fn().mockResolvedValue(makeToolCallResponse('looping_tool'));
290
- // max_turns: 3 — after UserTurn(1) + AssistantTurn(2) + ToolResultsTurn(3) = 3 turns
291
- // At next iteration start, history.length = 3 >= 3 → TURN_LIMIT
292
- const session = createSession({
293
- llmClient: makeMockClient(complete),
294
- providerProfile: makeProfile([tool]),
295
- executionEnv: makeEnv(),
296
- config: makeConfig({ max_turns: 3 }),
297
- });
298
- const turnLimitEvents = [];
299
- session.on(EventKind.TURN_LIMIT, e => turnLimitEvents.push(e));
300
- await session.processInput('test');
301
- expect(turnLimitEvents).toHaveLength(1);
302
- expect(turnLimitEvents[0].data.reason).toBe('max_turns');
303
- expect(session.state).toBe(SessionState.IDLE);
304
- });
305
- it('does not enforce limit when max_turns is 0 (unlimited)', async () => {
306
- const complete = vi.fn().mockResolvedValue(makeTextResponse());
307
- const session = createSession({
308
- llmClient: makeMockClient(complete),
309
- providerProfile: makeProfile(),
310
- executionEnv: makeEnv(),
311
- config: makeConfig({ max_turns: 0 }),
312
- });
313
- const turnLimitEvents = [];
314
- session.on(EventKind.TURN_LIMIT, e => turnLimitEvents.push(e));
315
- await session.processInput('test');
316
- expect(turnLimitEvents).toHaveLength(0);
317
- });
318
- });
319
- // ---------------------------------------------------------------------------
320
- // Test Suite: Unknown tool handling
321
- // ---------------------------------------------------------------------------
322
- describe('unknown tool handling', () => {
323
- it('returns is_error: true without throwing for unknown tool calls', async () => {
324
- // Profile has NO tools registered
325
- const complete = vi
326
- .fn()
327
- .mockResolvedValueOnce(makeToolCallResponse('nonexistent_tool', 'call99'))
328
- .mockResolvedValueOnce(makeTextResponse());
329
- const session = createSession({
330
- llmClient: makeMockClient(complete),
331
- providerProfile: makeProfile([]), // empty tools list
332
- executionEnv: makeEnv(),
333
- });
334
- const toolEndEvents = [];
335
- session.on(EventKind.TOOL_CALL_END, e => toolEndEvents.push(e));
336
- // Should NOT throw
337
- await expect(session.processInput('call unknown')).resolves.toBeUndefined();
338
- expect(toolEndEvents).toHaveLength(1);
339
- expect(toolEndEvents[0].data.is_error).toBe(true);
340
- expect(toolEndEvents[0].data.output).toContain('Unknown tool: nonexistent_tool');
341
- });
342
- it('records the error in ToolResultsTurn history', async () => {
343
- const complete = vi
344
- .fn()
345
- .mockResolvedValueOnce(makeToolCallResponse('ghost_tool', 'c1'))
346
- .mockResolvedValueOnce(makeTextResponse());
347
- const session = createSession({
348
- llmClient: makeMockClient(complete),
349
- providerProfile: makeProfile([]),
350
- executionEnv: makeEnv(),
351
- });
352
- await session.processInput('call ghost');
353
- const toolResultsTurn = session.history.find(t => t.type === 'tool_results');
354
- expect(toolResultsTurn).toBeDefined();
355
- if (toolResultsTurn?.type === 'tool_results') {
356
- expect(toolResultsTurn.results[0].is_error).toBe(true);
357
- expect(toolResultsTurn.results[0].content).toContain('Unknown tool: ghost_tool');
358
- }
359
- else {
360
- throw new Error('Expected tool_results turn');
361
- }
362
- });
363
- });
364
- // ---------------------------------------------------------------------------
365
- // Test Suite: TOOL_CALL_END carries full untruncated output
366
- // ---------------------------------------------------------------------------
367
- describe('TOOL_CALL_END full output in event', () => {
368
- it('event carries full untruncated output when truncation is applied to LLM content', async () => {
369
- // 'shell' has a 30,000 char default limit
370
- const largeOutput = 'X'.repeat(35_000);
371
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
372
- const shellTool = makeToolDefinition('shell', vi.fn().mockResolvedValue(largeOutput));
373
- const complete = vi
374
- .fn()
375
- .mockResolvedValueOnce(makeToolCallResponse('shell', 'c1'))
376
- .mockResolvedValueOnce(makeTextResponse());
377
- const session = createSession({
378
- llmClient: makeMockClient(complete),
379
- providerProfile: makeProfile([shellTool]),
380
- executionEnv: makeEnv(),
381
- });
382
- const toolEndEvents = [];
383
- session.on(EventKind.TOOL_CALL_END, e => toolEndEvents.push(e));
384
- await session.processInput('run shell');
385
- // Event output = full untruncated
386
- expect(toolEndEvents).toHaveLength(1);
387
- expect(toolEndEvents[0].data.output).toBe(largeOutput);
388
- expect(toolEndEvents[0].data.output.length).toBe(35_000);
389
- // History ToolResultsTurn content should be truncated
390
- const toolResultsTurn = session.history.find(t => t.type === 'tool_results');
391
- expect(toolResultsTurn).toBeDefined();
392
- if (toolResultsTurn?.type === 'tool_results') {
393
- expect(toolResultsTurn.results[0].content).toContain('characters truncated from middle.');
394
- expect(toolResultsTurn.results[0].content.length).toBeLessThan(35_000);
395
- }
396
- });
397
- });
398
- // ---------------------------------------------------------------------------
399
- // Test Suite: Parallel vs sequential tool dispatch
400
- // ---------------------------------------------------------------------------
401
- describe('parallel tool call dispatch', () => {
402
- it('dispatches multiple tool calls concurrently when supports_parallel_tool_calls=true', async () => {
403
- let activeCount = 0;
404
- let maxActiveCount = 0;
405
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
406
- const makeParallelExecutor = () => vi.fn().mockImplementation(async () => {
407
- activeCount++;
408
- maxActiveCount = Math.max(maxActiveCount, activeCount);
409
- await new Promise(r => setTimeout(r, 15));
410
- activeCount--;
411
- return 'output';
412
- });
413
- const tool1 = makeToolDefinition('tool1', makeParallelExecutor());
414
- const tool2 = makeToolDefinition('tool2', makeParallelExecutor());
415
- const complete = vi
416
- .fn()
417
- .mockResolvedValueOnce({
418
- content: '',
419
- toolCalls: [
420
- { id: 'c1', name: 'tool1', arguments: {} },
421
- { id: 'c2', name: 'tool2', arguments: {} },
422
- ],
423
- usage: makeUsage(),
424
- model: 'test-model',
425
- stopReason: 'tool_calls',
426
- providerMetadata: {},
427
- })
428
- .mockResolvedValueOnce(makeTextResponse());
429
- const profile = makeProfile([tool1, tool2], { supports_parallel_tool_calls: true });
430
- const session = createSession({
431
- llmClient: makeMockClient(complete),
432
- providerProfile: profile,
433
- executionEnv: makeEnv(),
434
- });
435
- await session.processInput('run tools in parallel');
436
- // Parallel: both tools run simultaneously → maxActiveCount = 2
437
- expect(maxActiveCount).toBe(2);
438
- });
439
- it('dispatches tool calls sequentially when supports_parallel_tool_calls=false', async () => {
440
- const callOrder = [];
441
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
442
- const makeSeqExecutor = (name) => vi.fn().mockImplementation(async () => {
443
- callOrder.push(`start_${name}`);
444
- await new Promise(r => setTimeout(r, 10));
445
- callOrder.push(`end_${name}`);
446
- return 'output';
447
- });
448
- const tool1 = makeToolDefinition('tool1', makeSeqExecutor('tool1'));
449
- const tool2 = makeToolDefinition('tool2', makeSeqExecutor('tool2'));
450
- const complete = vi
451
- .fn()
452
- .mockResolvedValueOnce({
453
- content: '',
454
- toolCalls: [
455
- { id: 'c1', name: 'tool1', arguments: {} },
456
- { id: 'c2', name: 'tool2', arguments: {} },
457
- ],
458
- usage: makeUsage(),
459
- model: 'test-model',
460
- stopReason: 'tool_calls',
461
- providerMetadata: {},
462
- })
463
- .mockResolvedValueOnce(makeTextResponse());
464
- const profile = makeProfile([tool1, tool2], { supports_parallel_tool_calls: false });
465
- const session = createSession({
466
- llmClient: makeMockClient(complete),
467
- providerProfile: profile,
468
- executionEnv: makeEnv(),
469
- });
470
- await session.processInput('run tools sequentially');
471
- // Sequential: tool1 must complete before tool2 starts
472
- expect(callOrder).toEqual(['start_tool1', 'end_tool1', 'start_tool2', 'end_tool2']);
473
- });
474
- });
475
- // ---------------------------------------------------------------------------
476
- // Test Suite: convertHistoryToMessages
477
- // ---------------------------------------------------------------------------
478
- describe('convertHistoryToMessages', () => {
479
- it('converts UserTurn to user role message with text content', () => {
480
- const history = [{ type: 'user', content: 'hello', timestamp: new Date() }];
481
- const messages = convertHistoryToMessages(history);
482
- expect(messages).toHaveLength(1);
483
- expect(messages[0].role).toBe('user');
484
- expect(messages[0].content[0]).toMatchObject({ kind: 'text', text: 'hello' });
485
- });
486
- it('converts SteeringTurn to user role message', () => {
487
- const history = [{ type: 'steering', content: 'steer this', timestamp: new Date() }];
488
- const messages = convertHistoryToMessages(history);
489
- expect(messages).toHaveLength(1);
490
- expect(messages[0].role).toBe('user');
491
- expect(messages[0].content[0]).toMatchObject({ kind: 'text', text: 'steer this' });
492
- });
493
- it('skips SystemTurn (system prompt passed separately)', () => {
494
- const history = [{ type: 'system', content: 'system msg', timestamp: new Date() }];
495
- const messages = convertHistoryToMessages(history);
496
- expect(messages).toHaveLength(0);
497
- });
498
- it('converts ToolResultsTurn to user role with tool_result content parts', () => {
499
- const history = [{
500
- type: 'tool_results',
501
- results: [{ tool_call_id: 'c1', content: 'result', is_error: false }],
502
- timestamp: new Date(),
503
- }];
504
- const messages = convertHistoryToMessages(history);
505
- expect(messages).toHaveLength(1);
506
- expect(messages[0].role).toBe('user');
507
- expect(messages[0].content[0]).toMatchObject({
508
- kind: 'tool_result',
509
- toolResult: { toolCallId: 'c1', content: 'result', isError: false },
510
- });
511
- });
512
- it('converts AssistantTurn with tool calls to assistant role with tool_call parts', () => {
513
- const history = [{
514
- type: 'assistant',
515
- content: 'Using tool',
516
- tool_calls: [{ id: 'tc1', name: 'my_tool', arguments: { x: 1 } }],
517
- reasoning: null,
518
- usage: makeUsage(),
519
- response_id: null,
520
- timestamp: new Date(),
521
- }];
522
- const messages = convertHistoryToMessages(history);
523
- expect(messages).toHaveLength(1);
524
- expect(messages[0].role).toBe('assistant');
525
- const parts = messages[0].content;
526
- expect(parts.some(p => p.kind === 'text')).toBe(true);
527
- expect(parts.some(p => p.kind === 'tool_call')).toBe(true);
528
- });
529
- });
530
- // ---------------------------------------------------------------------------
531
- // Test Suite: error handling
532
- // ---------------------------------------------------------------------------
533
- describe('error handling', () => {
534
- it('emits ERROR event and sets state to CLOSED when LLM throws', async () => {
535
- const complete = vi.fn().mockRejectedValue(new Error('LLM unavailable'));
536
- const session = createSession({
537
- llmClient: makeMockClient(complete),
538
- providerProfile: makeProfile(),
539
- executionEnv: makeEnv(),
540
- });
541
- const errorEvents = [];
542
- session.on(EventKind.ERROR, e => errorEvents.push(e));
543
- await expect(session.processInput('test')).rejects.toThrow('LLM unavailable');
544
- expect(errorEvents).toHaveLength(1);
545
- expect(errorEvents[0].data.message).toBe('LLM unavailable');
546
- expect(session.state).toBe(SessionState.CLOSED);
547
- });
548
- });
549
- // ---------------------------------------------------------------------------
550
- // Test Suite: steer() — steering injection (Story 48-8)
551
- // ---------------------------------------------------------------------------
552
- describe('steer() — steering injection', () => {
553
- it('message queued via steer() appears as SteeringTurn before next LLM call', async () => {
554
- const tool = makeToolDefinition('my_tool');
555
- const complete = vi
556
- .fn()
557
- .mockResolvedValueOnce(makeToolCallResponse('my_tool'))
558
- .mockResolvedValueOnce(makeTextResponse('Done'));
559
- const session = createSession({
560
- llmClient: makeMockClient(complete),
561
- providerProfile: makeProfile([tool]),
562
- executionEnv: makeEnv(),
563
- });
564
- // Queue a steering message before processing starts
565
- session.steer('please use a different approach');
566
- await session.processInput('do something');
567
- // SteeringTurn should appear in history
568
- const steeringTurns = session.history.filter(t => t.type === 'steering');
569
- expect(steeringTurns.length).toBeGreaterThanOrEqual(1);
570
- const turn = steeringTurns[0];
571
- expect(turn?.type).toBe('steering');
572
- if (turn?.type === 'steering') {
573
- expect(turn.content).toBe('please use a different approach');
574
- }
575
- });
576
- it('STEERING_INJECTED event is emitted with correct content', async () => {
577
- const complete = vi.fn().mockResolvedValue(makeTextResponse('Done'));
578
- const session = createSession({
579
- llmClient: makeMockClient(complete),
580
- providerProfile: makeProfile(),
581
- executionEnv: makeEnv(),
582
- });
583
- const steeringEvents = [];
584
- session.on(EventKind.STEERING_INJECTED, e => steeringEvents.push(e));
585
- session.steer('redirect now');
586
- await session.processInput('hello');
587
- expect(steeringEvents).toHaveLength(1);
588
- expect(steeringEvents[0].data.content).toBe('redirect now');
589
- });
590
- it('steering message is in history before the next LLM request (correct history position)', async () => {
591
- const tool = makeToolDefinition('my_tool');
592
- // Capture what history looks like at each LLM call
593
- const historySnapshotsAtLLMCall = [];
594
- const complete = vi.fn().mockImplementation(() => {
595
- // Capture the types of turns in history at call time
596
- historySnapshotsAtLLMCall.push(session.history.map(t => t.type));
597
- if (complete.mock.calls.length === 1) {
598
- return Promise.resolve(makeToolCallResponse('my_tool'));
599
- }
600
- return Promise.resolve(makeTextResponse('Done'));
601
- });
602
- const session = createSession({
603
- llmClient: makeMockClient(complete),
604
- providerProfile: makeProfile([tool]),
605
- executionEnv: makeEnv(),
606
- });
607
- // Steer before the second LLM call (while the tool is executing)
608
- // We'll queue it before processInput; it will be drained before the first LLM call
609
- session.steer('steered!');
610
- await session.processInput('use tool');
611
- // First LLM call should see the steering turn in history
612
- expect(historySnapshotsAtLLMCall[0]).toContain('steering');
613
- });
614
- it('steer() called while IDLE: message is buffered and injected on next processInput call', async () => {
615
- const complete = vi.fn().mockResolvedValue(makeTextResponse('OK'));
616
- const session = createSession({
617
- llmClient: makeMockClient(complete),
618
- providerProfile: makeProfile(),
619
- executionEnv: makeEnv(),
620
- });
621
- // Session is IDLE; queue steering
622
- session.steer('buffered-steer');
623
- expect(session._steeringQueue).toHaveLength(1);
624
- const steeringEvents = [];
625
- session.on(EventKind.STEERING_INJECTED, e => steeringEvents.push(e));
626
- await session.processInput('now process');
627
- // Steering should have been drained
628
- expect(steeringEvents).toHaveLength(1);
629
- expect(steeringEvents[0].data.content).toBe('buffered-steer');
630
- expect(session._steeringQueue).toHaveLength(0);
631
- });
632
- it('multiple steer() messages are drained FIFO', async () => {
633
- const complete = vi.fn().mockResolvedValue(makeTextResponse('OK'));
634
- const session = createSession({
635
- llmClient: makeMockClient(complete),
636
- providerProfile: makeProfile(),
637
- executionEnv: makeEnv(),
638
- });
639
- session.steer('first');
640
- session.steer('second');
641
- session.steer('third');
642
- const steeringEvents = [];
643
- session.on(EventKind.STEERING_INJECTED, e => steeringEvents.push(e));
644
- await session.processInput('go');
645
- expect(steeringEvents).toHaveLength(3);
646
- expect(steeringEvents[0].data.content).toBe('first');
647
- expect(steeringEvents[1].data.content).toBe('second');
648
- expect(steeringEvents[2].data.content).toBe('third');
649
- });
650
- });
651
- // ---------------------------------------------------------------------------
652
- // Test Suite: _drainSteering (Story 48-8)
653
- // ---------------------------------------------------------------------------
654
- describe('_drainSteering()', () => {
655
- it('dequeues all messages in FIFO order, appends SteeringTurns, emits STEERING_INJECTED', () => {
656
- const complete = vi.fn();
657
- const session = createSession({
658
- llmClient: makeMockClient(complete),
659
- providerProfile: makeProfile(),
660
- executionEnv: makeEnv(),
661
- });
662
- const steeringEvents = [];
663
- session.on(EventKind.STEERING_INJECTED, e => steeringEvents.push(e));
664
- session._steeringQueue.push('msg1', 'msg2', 'msg3');
665
- session._drainSteering();
666
- // All messages dequeued
667
- expect(session._steeringQueue).toHaveLength(0);
668
- // SteeringTurns appended to history
669
- expect(session.history).toHaveLength(3);
670
- expect(session.history.every(t => t.type === 'steering')).toBe(true);
671
- expect(session.history[0].content).toBe('msg1');
672
- expect(session.history[1].content).toBe('msg2');
673
- expect(session.history[2].content).toBe('msg3');
674
- // Events emitted per message
675
- expect(steeringEvents).toHaveLength(3);
676
- expect(steeringEvents[0].data.content).toBe('msg1');
677
- expect(steeringEvents[1].data.content).toBe('msg2');
678
- expect(steeringEvents[2].data.content).toBe('msg3');
679
- });
680
- it('does nothing when steering queue is empty', () => {
681
- const session = createSession({
682
- llmClient: makeMockClient(vi.fn()),
683
- providerProfile: makeProfile(),
684
- executionEnv: makeEnv(),
685
- });
686
- const steeringEvents = [];
687
- session.on(EventKind.STEERING_INJECTED, e => steeringEvents.push(e));
688
- session._drainSteering();
689
- expect(session.history).toHaveLength(0);
690
- expect(steeringEvents).toHaveLength(0);
691
- });
692
- it('SteeringTurn has a timestamp (Date instance)', () => {
693
- const session = createSession({
694
- llmClient: makeMockClient(vi.fn()),
695
- providerProfile: makeProfile(),
696
- executionEnv: makeEnv(),
697
- });
698
- session._steeringQueue.push('msg');
699
- session._drainSteering();
700
- const turn = session.history[0];
701
- expect(turn?.type).toBe('steering');
702
- if (turn?.type === 'steering') {
703
- expect(turn.timestamp).toBeInstanceOf(Date);
704
- }
705
- });
706
- });
707
- // ---------------------------------------------------------------------------
708
- // Test Suite: follow_up() — follow-up queue (Story 48-8)
709
- // ---------------------------------------------------------------------------
710
- describe('follow_up() — follow-up queue', () => {
711
- it('queued follow-up triggers a new processing cycle after natural completion', async () => {
712
- const complete = vi
713
- .fn()
714
- .mockResolvedValueOnce(makeTextResponse('First response'))
715
- .mockResolvedValueOnce(makeTextResponse('Second response'));
716
- const session = createSession({
717
- llmClient: makeMockClient(complete),
718
- providerProfile: makeProfile(),
719
- executionEnv: makeEnv(),
720
- });
721
- session.follow_up('follow-up message');
722
- const userInputEvents = [];
723
- session.on(EventKind.USER_INPUT, e => userInputEvents.push(e));
724
- const processingEndEvents = [];
725
- session.on(EventKind.PROCESSING_END, e => processingEndEvents.push(e));
726
- await session.processInput('initial message');
727
- // LLM should have been called twice
728
- expect(complete).toHaveBeenCalledTimes(2);
729
- // USER_INPUT emitted twice (once per processInput call)
730
- expect(userInputEvents).toHaveLength(2);
731
- expect(userInputEvents[0].data.content).toBe('initial message');
732
- expect(userInputEvents[1].data.content).toBe('follow-up message');
733
- // PROCESSING_END emitted exactly once (after follow-up is fully exhausted)
734
- expect(processingEndEvents).toHaveLength(1);
735
- });
736
- it('PROCESSING_END is NOT emitted until all follow-ups are exhausted', async () => {
737
- const complete = vi
738
- .fn()
739
- .mockResolvedValueOnce(makeTextResponse('R1'))
740
- .mockResolvedValueOnce(makeTextResponse('R2'))
741
- .mockResolvedValueOnce(makeTextResponse('R3'));
742
- const session = createSession({
743
- llmClient: makeMockClient(complete),
744
- providerProfile: makeProfile(),
745
- executionEnv: makeEnv(),
746
- });
747
- session.follow_up('follow-up-1');
748
- session.follow_up('follow-up-2');
749
- const processingEndEvents = [];
750
- session.on(EventKind.PROCESSING_END, e => processingEndEvents.push(e));
751
- await session.processInput('initial');
752
- expect(complete).toHaveBeenCalledTimes(3);
753
- expect(processingEndEvents).toHaveLength(1);
754
- });
755
- it('follow-up messages are processed FIFO', async () => {
756
- const complete = vi.fn()
757
- .mockResolvedValueOnce(makeTextResponse('R1'))
758
- .mockResolvedValueOnce(makeTextResponse('R2'))
759
- .mockResolvedValueOnce(makeTextResponse('R3'));
760
- const session = createSession({
761
- llmClient: makeMockClient(complete),
762
- providerProfile: makeProfile(),
763
- executionEnv: makeEnv(),
764
- });
765
- session.follow_up('fu-first');
766
- session.follow_up('fu-second');
767
- const inputEvents = [];
768
- session.on(EventKind.USER_INPUT, e => inputEvents.push(e));
769
- await session.processInput('initial');
770
- expect(inputEvents).toHaveLength(3);
771
- expect(inputEvents[0].data.content).toBe('initial');
772
- expect(inputEvents[1].data.content).toBe('fu-first');
773
- expect(inputEvents[2].data.content).toBe('fu-second');
774
- });
775
- });
776
- // ---------------------------------------------------------------------------
777
- // Test Suite: Loop detection integration (Story 48-8)
778
- // ---------------------------------------------------------------------------
779
- describe('loop detection integration', () => {
780
- it('LOOP_DETECTION event is emitted when the same tool is called 10+ times', async () => {
781
- const tool = makeToolDefinition('looping_tool');
782
- // LLM returns same tool call many times then completes
783
- const complete = vi.fn();
784
- // Return 'looping_tool' call 10 times, then text
785
- for (let i = 0; i < 10; i++) {
786
- complete.mockResolvedValueOnce(makeToolCallResponse('looping_tool', `call${i}`, {}));
787
- }
788
- complete.mockResolvedValueOnce(makeTextResponse('Done'));
789
- const session = createSession({
790
- llmClient: makeMockClient(complete),
791
- providerProfile: makeProfile([tool]),
792
- executionEnv: makeEnv(),
793
- config: makeConfig({ enable_loop_detection: true, loop_detection_window: 10 }),
794
- });
795
- const loopEvents = [];
796
- session.on(EventKind.LOOP_DETECTION, e => loopEvents.push(e));
797
- await session.processInput('run loop');
798
- expect(loopEvents).toHaveLength(1);
799
- expect(loopEvents[0].data.message).toContain('Loop detected');
800
- expect(loopEvents[0].data.message).toContain('10');
801
- expect(loopEvents[0].data.message).toContain('repeating pattern');
802
- });
803
- it('LOOP_DETECTION injects SteeringTurn directly into history', async () => {
804
- const tool = makeToolDefinition('looping_tool');
805
- const complete = vi.fn();
806
- for (let i = 0; i < 10; i++) {
807
- complete.mockResolvedValueOnce(makeToolCallResponse('looping_tool', `call${i}`, {}));
808
- }
809
- complete.mockResolvedValueOnce(makeTextResponse('Done'));
810
- const session = createSession({
811
- llmClient: makeMockClient(complete),
812
- providerProfile: makeProfile([tool]),
813
- executionEnv: makeEnv(),
814
- config: makeConfig({ enable_loop_detection: true, loop_detection_window: 10 }),
815
- });
816
- await session.processInput('run');
817
- const steeringTurns = session.history.filter(t => t.type === 'steering');
818
- const loopWarning = steeringTurns.find(t => t.type === 'steering' && t.content.includes('Loop detected'));
819
- expect(loopWarning).toBeDefined();
820
- if (loopWarning?.type === 'steering') {
821
- expect(loopWarning.content).toContain('repeating pattern');
822
- }
823
- });
824
- it('enable_loop_detection: false suppresses LOOP_DETECTION event even with repeating pattern', async () => {
825
- const tool = makeToolDefinition('looping_tool');
826
- const complete = vi.fn();
827
- for (let i = 0; i < 10; i++) {
828
- complete.mockResolvedValueOnce(makeToolCallResponse('looping_tool', `call${i}`, {}));
829
- }
830
- complete.mockResolvedValueOnce(makeTextResponse('Done'));
831
- const session = createSession({
832
- llmClient: makeMockClient(complete),
833
- providerProfile: makeProfile([tool]),
834
- executionEnv: makeEnv(),
835
- config: makeConfig({
836
- enable_loop_detection: false,
837
- loop_detection_window: 10,
838
- max_tool_rounds_per_input: 12, // allow enough rounds
839
- }),
840
- });
841
- const loopEvents = [];
842
- session.on(EventKind.LOOP_DETECTION, e => loopEvents.push(e));
843
- await session.processInput('run no detection');
844
- expect(loopEvents).toHaveLength(0);
845
- });
846
- it('loop detection warning message has correct format', async () => {
847
- const tool = makeToolDefinition('repeat_tool');
848
- const windowSize = 6;
849
- const complete = vi.fn();
850
- for (let i = 0; i < windowSize; i++) {
851
- complete.mockResolvedValueOnce(makeToolCallResponse('repeat_tool', `c${i}`, {}));
852
- }
853
- complete.mockResolvedValueOnce(makeTextResponse('Done'));
854
- const session = createSession({
855
- llmClient: makeMockClient(complete),
856
- providerProfile: makeProfile([tool]),
857
- executionEnv: makeEnv(),
858
- config: makeConfig({ enable_loop_detection: true, loop_detection_window: windowSize }),
859
- });
860
- const loopEvents = [];
861
- session.on(EventKind.LOOP_DETECTION, e => loopEvents.push(e));
862
- await session.processInput('run');
863
- expect(loopEvents).toHaveLength(1);
864
- const expectedMsg = `Loop detected: the last ${windowSize} tool calls follow a repeating pattern. Try a different approach.`;
865
- expect(loopEvents[0].data.message).toBe(expectedMsg);
866
- });
867
- });
868
- //# sourceMappingURL=loop.test.js.map