@substrate-ai/factory 0.20.1 → 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 (486) hide show
  1. package/README.md +39 -0
  2. package/package.json +6 -3
  3. package/dist/__tests__/config.test.d.ts +0 -11
  4. package/dist/__tests__/config.test.d.ts.map +0 -1
  5. package/dist/__tests__/config.test.js +0 -215
  6. package/dist/__tests__/config.test.js.map +0 -1
  7. package/dist/__tests__/factory-run-command.test.d.ts +0 -12
  8. package/dist/__tests__/factory-run-command.test.d.ts.map +0 -1
  9. package/dist/__tests__/factory-run-command.test.js +0 -454
  10. package/dist/__tests__/factory-run-command.test.js.map +0 -1
  11. package/dist/__tests__/factory-validate-command.test.d.ts +0 -15
  12. package/dist/__tests__/factory-validate-command.test.d.ts.map +0 -1
  13. package/dist/__tests__/factory-validate-command.test.js +0 -339
  14. package/dist/__tests__/factory-validate-command.test.js.map +0 -1
  15. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.d.ts +0 -72
  16. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.d.ts.map +0 -1
  17. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.js +0 -121
  18. package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.js.map +0 -1
  19. package/dist/__tests__/fixtures/llm-edge-routing.dot.d.ts +0 -28
  20. package/dist/__tests__/fixtures/llm-edge-routing.dot.d.ts.map +0 -1
  21. package/dist/__tests__/fixtures/llm-edge-routing.dot.js +0 -55
  22. package/dist/__tests__/fixtures/llm-edge-routing.dot.js.map +0 -1
  23. package/dist/__tests__/fixtures/manager-loop.dot.d.ts +0 -34
  24. package/dist/__tests__/fixtures/manager-loop.dot.d.ts.map +0 -1
  25. package/dist/__tests__/fixtures/manager-loop.dot.js +0 -61
  26. package/dist/__tests__/fixtures/manager-loop.dot.js.map +0 -1
  27. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.d.ts +0 -42
  28. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.d.ts.map +0 -1
  29. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.js +0 -118
  30. package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.js.map +0 -1
  31. package/dist/__tests__/fixtures/subgraph-parent.dot.d.ts +0 -35
  32. package/dist/__tests__/fixtures/subgraph-parent.dot.d.ts.map +0 -1
  33. package/dist/__tests__/fixtures/subgraph-parent.dot.js +0 -69
  34. package/dist/__tests__/fixtures/subgraph-parent.dot.js.map +0 -1
  35. package/dist/__tests__/integration/advanced-graph-events.test.d.ts +0 -19
  36. package/dist/__tests__/integration/advanced-graph-events.test.d.ts.map +0 -1
  37. package/dist/__tests__/integration/advanced-graph-events.test.js +0 -288
  38. package/dist/__tests__/integration/advanced-graph-events.test.js.map +0 -1
  39. package/dist/__tests__/integration/checkpoint-resume.test.d.ts +0 -10
  40. package/dist/__tests__/integration/checkpoint-resume.test.d.ts.map +0 -1
  41. package/dist/__tests__/integration/checkpoint-resume.test.js +0 -125
  42. package/dist/__tests__/integration/checkpoint-resume.test.js.map +0 -1
  43. package/dist/__tests__/integration/conditional-pipeline.test.d.ts +0 -10
  44. package/dist/__tests__/integration/conditional-pipeline.test.d.ts.map +0 -1
  45. package/dist/__tests__/integration/conditional-pipeline.test.js +0 -106
  46. package/dist/__tests__/integration/conditional-pipeline.test.js.map +0 -1
  47. package/dist/__tests__/integration/convergence-validation.test.d.ts +0 -14
  48. package/dist/__tests__/integration/convergence-validation.test.d.ts.map +0 -1
  49. package/dist/__tests__/integration/convergence-validation.test.js +0 -449
  50. package/dist/__tests__/integration/convergence-validation.test.js.map +0 -1
  51. package/dist/__tests__/integration/epic44-coverage-gate.test.d.ts +0 -12
  52. package/dist/__tests__/integration/epic44-coverage-gate.test.d.ts.map +0 -1
  53. package/dist/__tests__/integration/epic44-coverage-gate.test.js +0 -58
  54. package/dist/__tests__/integration/epic44-coverage-gate.test.js.map +0 -1
  55. package/dist/__tests__/integration/epic45-coverage-gate.test.d.ts +0 -11
  56. package/dist/__tests__/integration/epic45-coverage-gate.test.d.ts.map +0 -1
  57. package/dist/__tests__/integration/epic45-coverage-gate.test.js +0 -64
  58. package/dist/__tests__/integration/epic45-coverage-gate.test.js.map +0 -1
  59. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.d.ts +0 -2
  60. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.d.ts.map +0 -1
  61. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.js +0 -285
  62. package/dist/__tests__/integration/epic46-scenario-primary-executor.test.js.map +0 -1
  63. package/dist/__tests__/integration/events.test.d.ts +0 -8
  64. package/dist/__tests__/integration/events.test.d.ts.map +0 -1
  65. package/dist/__tests__/integration/events.test.js +0 -194
  66. package/dist/__tests__/integration/events.test.js.map +0 -1
  67. package/dist/__tests__/integration/graphs.d.ts +0 -59
  68. package/dist/__tests__/integration/graphs.d.ts.map +0 -1
  69. package/dist/__tests__/integration/graphs.js +0 -164
  70. package/dist/__tests__/integration/graphs.js.map +0 -1
  71. package/dist/__tests__/integration/helpers.d.ts +0 -127
  72. package/dist/__tests__/integration/helpers.d.ts.map +0 -1
  73. package/dist/__tests__/integration/helpers.js +0 -167
  74. package/dist/__tests__/integration/helpers.js.map +0 -1
  75. package/dist/__tests__/integration/integrity.test.d.ts +0 -8
  76. package/dist/__tests__/integration/integrity.test.d.ts.map +0 -1
  77. package/dist/__tests__/integration/integrity.test.js +0 -198
  78. package/dist/__tests__/integration/integrity.test.js.map +0 -1
  79. package/dist/__tests__/integration/llm-edge-routing.test.d.ts +0 -21
  80. package/dist/__tests__/integration/llm-edge-routing.test.d.ts.map +0 -1
  81. package/dist/__tests__/integration/llm-edge-routing.test.js +0 -341
  82. package/dist/__tests__/integration/llm-edge-routing.test.js.map +0 -1
  83. package/dist/__tests__/integration/manager-loop.test.d.ts +0 -24
  84. package/dist/__tests__/integration/manager-loop.test.d.ts.map +0 -1
  85. package/dist/__tests__/integration/manager-loop.test.js +0 -276
  86. package/dist/__tests__/integration/manager-loop.test.js.map +0 -1
  87. package/dist/__tests__/integration/multi-type-graph.test.d.ts +0 -10
  88. package/dist/__tests__/integration/multi-type-graph.test.d.ts.map +0 -1
  89. package/dist/__tests__/integration/multi-type-graph.test.js +0 -100
  90. package/dist/__tests__/integration/multi-type-graph.test.js.map +0 -1
  91. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.d.ts +0 -22
  92. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.d.ts.map +0 -1
  93. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.js +0 -515
  94. package/dist/__tests__/integration/parallel-fan-out-fan-in.test.js.map +0 -1
  95. package/dist/__tests__/integration/persistence.test.d.ts +0 -8
  96. package/dist/__tests__/integration/persistence.test.d.ts.map +0 -1
  97. package/dist/__tests__/integration/persistence.test.js +0 -129
  98. package/dist/__tests__/integration/persistence.test.js.map +0 -1
  99. package/dist/__tests__/integration/pipeline-templates-integration.test.d.ts +0 -16
  100. package/dist/__tests__/integration/pipeline-templates-integration.test.d.ts.map +0 -1
  101. package/dist/__tests__/integration/pipeline-templates-integration.test.js +0 -171
  102. package/dist/__tests__/integration/pipeline-templates-integration.test.js.map +0 -1
  103. package/dist/__tests__/integration/scenario-pipeline.test.d.ts +0 -11
  104. package/dist/__tests__/integration/scenario-pipeline.test.d.ts.map +0 -1
  105. package/dist/__tests__/integration/scenario-pipeline.test.js +0 -243
  106. package/dist/__tests__/integration/scenario-pipeline.test.js.map +0 -1
  107. package/dist/__tests__/integration/stylesheet-application.test.d.ts +0 -12
  108. package/dist/__tests__/integration/stylesheet-application.test.d.ts.map +0 -1
  109. package/dist/__tests__/integration/stylesheet-application.test.js +0 -119
  110. package/dist/__tests__/integration/stylesheet-application.test.js.map +0 -1
  111. package/dist/__tests__/integration/subgraph-execution.test.d.ts +0 -24
  112. package/dist/__tests__/integration/subgraph-execution.test.d.ts.map +0 -1
  113. package/dist/__tests__/integration/subgraph-execution.test.js +0 -291
  114. package/dist/__tests__/integration/subgraph-execution.test.js.map +0 -1
  115. package/dist/__tests__/integration/validation-errors.test.d.ts +0 -8
  116. package/dist/__tests__/integration/validation-errors.test.d.ts.map +0 -1
  117. package/dist/__tests__/integration/validation-errors.test.js +0 -150
  118. package/dist/__tests__/integration/validation-errors.test.js.map +0 -1
  119. package/dist/agent/__tests__/loop-detection.test.d.ts +0 -2
  120. package/dist/agent/__tests__/loop-detection.test.d.ts.map +0 -1
  121. package/dist/agent/__tests__/loop-detection.test.js +0 -236
  122. package/dist/agent/__tests__/loop-detection.test.js.map +0 -1
  123. package/dist/agent/__tests__/loop.test.d.ts +0 -2
  124. package/dist/agent/__tests__/loop.test.d.ts.map +0 -1
  125. package/dist/agent/__tests__/loop.test.js +0 -868
  126. package/dist/agent/__tests__/loop.test.js.map +0 -1
  127. package/dist/agent/__tests__/truncation.test.d.ts +0 -2
  128. package/dist/agent/__tests__/truncation.test.d.ts.map +0 -1
  129. package/dist/agent/__tests__/truncation.test.js +0 -276
  130. package/dist/agent/__tests__/truncation.test.js.map +0 -1
  131. package/dist/agent/tools/__tests__/anthropic-tools.test.d.ts +0 -6
  132. package/dist/agent/tools/__tests__/anthropic-tools.test.d.ts.map +0 -1
  133. package/dist/agent/tools/__tests__/anthropic-tools.test.js +0 -49
  134. package/dist/agent/tools/__tests__/anthropic-tools.test.js.map +0 -1
  135. package/dist/agent/tools/__tests__/environment.test.d.ts +0 -6
  136. package/dist/agent/tools/__tests__/environment.test.d.ts.map +0 -1
  137. package/dist/agent/tools/__tests__/environment.test.js +0 -33
  138. package/dist/agent/tools/__tests__/environment.test.js.map +0 -1
  139. package/dist/agent/tools/__tests__/gemini-tools.test.d.ts +0 -6
  140. package/dist/agent/tools/__tests__/gemini-tools.test.d.ts.map +0 -1
  141. package/dist/agent/tools/__tests__/gemini-tools.test.js +0 -98
  142. package/dist/agent/tools/__tests__/gemini-tools.test.js.map +0 -1
  143. package/dist/agent/tools/__tests__/openai-tools.test.d.ts +0 -6
  144. package/dist/agent/tools/__tests__/openai-tools.test.d.ts.map +0 -1
  145. package/dist/agent/tools/__tests__/openai-tools.test.js +0 -53
  146. package/dist/agent/tools/__tests__/openai-tools.test.js.map +0 -1
  147. package/dist/agent/tools/__tests__/patch.test.d.ts +0 -6
  148. package/dist/agent/tools/__tests__/patch.test.d.ts.map +0 -1
  149. package/dist/agent/tools/__tests__/patch.test.js +0 -116
  150. package/dist/agent/tools/__tests__/patch.test.js.map +0 -1
  151. package/dist/agent/tools/__tests__/profiles.test.d.ts +0 -6
  152. package/dist/agent/tools/__tests__/profiles.test.d.ts.map +0 -1
  153. package/dist/agent/tools/__tests__/profiles.test.js +0 -125
  154. package/dist/agent/tools/__tests__/profiles.test.js.map +0 -1
  155. package/dist/agent/tools/__tests__/registry.test.d.ts +0 -6
  156. package/dist/agent/tools/__tests__/registry.test.d.ts.map +0 -1
  157. package/dist/agent/tools/__tests__/registry.test.js +0 -94
  158. package/dist/agent/tools/__tests__/registry.test.js.map +0 -1
  159. package/dist/agent/tools/__tests__/shared.test.d.ts +0 -6
  160. package/dist/agent/tools/__tests__/shared.test.d.ts.map +0 -1
  161. package/dist/agent/tools/__tests__/shared.test.js +0 -131
  162. package/dist/agent/tools/__tests__/shared.test.js.map +0 -1
  163. package/dist/backend/__tests__/direct-backend.test.d.ts +0 -14
  164. package/dist/backend/__tests__/direct-backend.test.d.ts.map +0 -1
  165. package/dist/backend/__tests__/direct-backend.test.js +0 -393
  166. package/dist/backend/__tests__/direct-backend.test.js.map +0 -1
  167. package/dist/backend/__tests__/direct-bootstrap.test.d.ts +0 -7
  168. package/dist/backend/__tests__/direct-bootstrap.test.d.ts.map +0 -1
  169. package/dist/backend/__tests__/direct-bootstrap.test.js +0 -177
  170. package/dist/backend/__tests__/direct-bootstrap.test.js.map +0 -1
  171. package/dist/backend/__tests__/mock-backend.test.d.ts +0 -7
  172. package/dist/backend/__tests__/mock-backend.test.d.ts.map +0 -1
  173. package/dist/backend/__tests__/mock-backend.test.js +0 -273
  174. package/dist/backend/__tests__/mock-backend.test.js.map +0 -1
  175. package/dist/backend/__tests__/parity.test.d.ts +0 -17
  176. package/dist/backend/__tests__/parity.test.d.ts.map +0 -1
  177. package/dist/backend/__tests__/parity.test.js +0 -411
  178. package/dist/backend/__tests__/parity.test.js.map +0 -1
  179. package/dist/context/__tests__/auto-summarizer.test.d.ts +0 -14
  180. package/dist/context/__tests__/auto-summarizer.test.d.ts.map +0 -1
  181. package/dist/context/__tests__/auto-summarizer.test.js +0 -189
  182. package/dist/context/__tests__/auto-summarizer.test.js.map +0 -1
  183. package/dist/context/__tests__/context-cli-command.test.d.ts +0 -7
  184. package/dist/context/__tests__/context-cli-command.test.d.ts.map +0 -1
  185. package/dist/context/__tests__/context-cli-command.test.js +0 -331
  186. package/dist/context/__tests__/context-cli-command.test.js.map +0 -1
  187. package/dist/context/__tests__/pyramid-summary-integration.test.d.ts +0 -2
  188. package/dist/context/__tests__/pyramid-summary-integration.test.d.ts.map +0 -1
  189. package/dist/context/__tests__/pyramid-summary-integration.test.js +0 -533
  190. package/dist/context/__tests__/pyramid-summary-integration.test.js.map +0 -1
  191. package/dist/context/__tests__/summarizer.test.d.ts +0 -2
  192. package/dist/context/__tests__/summarizer.test.d.ts.map +0 -1
  193. package/dist/context/__tests__/summarizer.test.js +0 -189
  194. package/dist/context/__tests__/summarizer.test.js.map +0 -1
  195. package/dist/context/__tests__/summary-cache.test.d.ts +0 -2
  196. package/dist/context/__tests__/summary-cache.test.d.ts.map +0 -1
  197. package/dist/context/__tests__/summary-cache.test.js +0 -214
  198. package/dist/context/__tests__/summary-cache.test.js.map +0 -1
  199. package/dist/context/__tests__/summary-metrics.test.d.ts +0 -2
  200. package/dist/context/__tests__/summary-metrics.test.d.ts.map +0 -1
  201. package/dist/context/__tests__/summary-metrics.test.js +0 -172
  202. package/dist/context/__tests__/summary-metrics.test.js.map +0 -1
  203. package/dist/context/__tests__/summary-types.test.d.ts +0 -2
  204. package/dist/context/__tests__/summary-types.test.d.ts.map +0 -1
  205. package/dist/context/__tests__/summary-types.test.js +0 -130
  206. package/dist/context/__tests__/summary-types.test.js.map +0 -1
  207. package/dist/convergence/__tests__/budget.test.d.ts +0 -6
  208. package/dist/convergence/__tests__/budget.test.d.ts.map +0 -1
  209. package/dist/convergence/__tests__/budget.test.js +0 -187
  210. package/dist/convergence/__tests__/budget.test.js.map +0 -1
  211. package/dist/convergence/__tests__/controller.test.d.ts +0 -9
  212. package/dist/convergence/__tests__/controller.test.d.ts.map +0 -1
  213. package/dist/convergence/__tests__/controller.test.js +0 -585
  214. package/dist/convergence/__tests__/controller.test.js.map +0 -1
  215. package/dist/convergence/__tests__/dual-signal.test.d.ts +0 -14
  216. package/dist/convergence/__tests__/dual-signal.test.d.ts.map +0 -1
  217. package/dist/convergence/__tests__/dual-signal.test.js +0 -123
  218. package/dist/convergence/__tests__/dual-signal.test.js.map +0 -1
  219. package/dist/convergence/__tests__/epic46-integration.test.d.ts +0 -15
  220. package/dist/convergence/__tests__/epic46-integration.test.d.ts.map +0 -1
  221. package/dist/convergence/__tests__/epic46-integration.test.js +0 -522
  222. package/dist/convergence/__tests__/epic46-integration.test.js.map +0 -1
  223. package/dist/convergence/__tests__/plateau.test.d.ts +0 -6
  224. package/dist/convergence/__tests__/plateau.test.d.ts.map +0 -1
  225. package/dist/convergence/__tests__/plateau.test.js +0 -163
  226. package/dist/convergence/__tests__/plateau.test.js.map +0 -1
  227. package/dist/convergence/__tests__/remediation.test.d.ts +0 -11
  228. package/dist/convergence/__tests__/remediation.test.d.ts.map +0 -1
  229. package/dist/convergence/__tests__/remediation.test.js +0 -209
  230. package/dist/convergence/__tests__/remediation.test.js.map +0 -1
  231. package/dist/convergence/__tests__/scenario-primary.test.d.ts +0 -13
  232. package/dist/convergence/__tests__/scenario-primary.test.d.ts.map +0 -1
  233. package/dist/convergence/__tests__/scenario-primary.test.js +0 -183
  234. package/dist/convergence/__tests__/scenario-primary.test.js.map +0 -1
  235. package/dist/factory-command.test.d.ts +0 -8
  236. package/dist/factory-command.test.d.ts.map +0 -1
  237. package/dist/factory-command.test.js +0 -304
  238. package/dist/factory-command.test.js.map +0 -1
  239. package/dist/graph/__tests__/attractor-compliance.test.d.ts +0 -10
  240. package/dist/graph/__tests__/attractor-compliance.test.d.ts.map +0 -1
  241. package/dist/graph/__tests__/attractor-compliance.test.js +0 -766
  242. package/dist/graph/__tests__/attractor-compliance.test.js.map +0 -1
  243. package/dist/graph/__tests__/checkpoint.test.d.ts +0 -8
  244. package/dist/graph/__tests__/checkpoint.test.d.ts.map +0 -1
  245. package/dist/graph/__tests__/checkpoint.test.js +0 -329
  246. package/dist/graph/__tests__/checkpoint.test.js.map +0 -1
  247. package/dist/graph/__tests__/condition-parser.test.d.ts +0 -14
  248. package/dist/graph/__tests__/condition-parser.test.d.ts.map +0 -1
  249. package/dist/graph/__tests__/condition-parser.test.js +0 -406
  250. package/dist/graph/__tests__/condition-parser.test.js.map +0 -1
  251. package/dist/graph/__tests__/context.test.d.ts +0 -14
  252. package/dist/graph/__tests__/context.test.d.ts.map +0 -1
  253. package/dist/graph/__tests__/context.test.js +0 -276
  254. package/dist/graph/__tests__/context.test.js.map +0 -1
  255. package/dist/graph/__tests__/edge-selector-events.test.d.ts +0 -11
  256. package/dist/graph/__tests__/edge-selector-events.test.d.ts.map +0 -1
  257. package/dist/graph/__tests__/edge-selector-events.test.js +0 -184
  258. package/dist/graph/__tests__/edge-selector-events.test.js.map +0 -1
  259. package/dist/graph/__tests__/edge-selector.test.d.ts +0 -6
  260. package/dist/graph/__tests__/edge-selector.test.d.ts.map +0 -1
  261. package/dist/graph/__tests__/edge-selector.test.js +0 -452
  262. package/dist/graph/__tests__/edge-selector.test.js.map +0 -1
  263. package/dist/graph/__tests__/executor-convergence.test.d.ts +0 -12
  264. package/dist/graph/__tests__/executor-convergence.test.d.ts.map +0 -1
  265. package/dist/graph/__tests__/executor-convergence.test.js +0 -432
  266. package/dist/graph/__tests__/executor-convergence.test.js.map +0 -1
  267. package/dist/graph/__tests__/executor-fidelity.test.d.ts +0 -13
  268. package/dist/graph/__tests__/executor-fidelity.test.d.ts.map +0 -1
  269. package/dist/graph/__tests__/executor-fidelity.test.js +0 -335
  270. package/dist/graph/__tests__/executor-fidelity.test.js.map +0 -1
  271. package/dist/graph/__tests__/executor.test.d.ts +0 -14
  272. package/dist/graph/__tests__/executor.test.d.ts.map +0 -1
  273. package/dist/graph/__tests__/executor.test.js +0 -901
  274. package/dist/graph/__tests__/executor.test.js.map +0 -1
  275. package/dist/graph/__tests__/fidelity.test.d.ts +0 -8
  276. package/dist/graph/__tests__/fidelity.test.d.ts.map +0 -1
  277. package/dist/graph/__tests__/fidelity.test.js +0 -135
  278. package/dist/graph/__tests__/fidelity.test.js.map +0 -1
  279. package/dist/graph/__tests__/llm-evaluator.test.d.ts +0 -7
  280. package/dist/graph/__tests__/llm-evaluator.test.d.ts.map +0 -1
  281. package/dist/graph/__tests__/llm-evaluator.test.js +0 -106
  282. package/dist/graph/__tests__/llm-evaluator.test.js.map +0 -1
  283. package/dist/graph/__tests__/parser-chaining.test.d.ts +0 -13
  284. package/dist/graph/__tests__/parser-chaining.test.d.ts.map +0 -1
  285. package/dist/graph/__tests__/parser-chaining.test.js +0 -215
  286. package/dist/graph/__tests__/parser-chaining.test.js.map +0 -1
  287. package/dist/graph/__tests__/parser.test.d.ts +0 -22
  288. package/dist/graph/__tests__/parser.test.d.ts.map +0 -1
  289. package/dist/graph/__tests__/parser.test.js +0 -452
  290. package/dist/graph/__tests__/parser.test.js.map +0 -1
  291. package/dist/graph/__tests__/run-state.test.d.ts +0 -13
  292. package/dist/graph/__tests__/run-state.test.d.ts.map +0 -1
  293. package/dist/graph/__tests__/run-state.test.js +0 -189
  294. package/dist/graph/__tests__/run-state.test.js.map +0 -1
  295. package/dist/graph/__tests__/transformer.test.d.ts +0 -16
  296. package/dist/graph/__tests__/transformer.test.d.ts.map +0 -1
  297. package/dist/graph/__tests__/transformer.test.js +0 -350
  298. package/dist/graph/__tests__/transformer.test.js.map +0 -1
  299. package/dist/graph/__tests__/validator-errors.test.d.ts +0 -15
  300. package/dist/graph/__tests__/validator-errors.test.d.ts.map +0 -1
  301. package/dist/graph/__tests__/validator-errors.test.js +0 -572
  302. package/dist/graph/__tests__/validator-errors.test.js.map +0 -1
  303. package/dist/graph/__tests__/validator-warnings.test.d.ts +0 -15
  304. package/dist/graph/__tests__/validator-warnings.test.d.ts.map +0 -1
  305. package/dist/graph/__tests__/validator-warnings.test.js +0 -363
  306. package/dist/graph/__tests__/validator-warnings.test.js.map +0 -1
  307. package/dist/handlers/__tests__/codergen-handler.test.d.ts +0 -14
  308. package/dist/handlers/__tests__/codergen-handler.test.d.ts.map +0 -1
  309. package/dist/handlers/__tests__/codergen-handler.test.js +0 -442
  310. package/dist/handlers/__tests__/codergen-handler.test.js.map +0 -1
  311. package/dist/handlers/__tests__/fan-in.test.d.ts +0 -14
  312. package/dist/handlers/__tests__/fan-in.test.d.ts.map +0 -1
  313. package/dist/handlers/__tests__/fan-in.test.js +0 -399
  314. package/dist/handlers/__tests__/fan-in.test.js.map +0 -1
  315. package/dist/handlers/__tests__/join-policy.test.d.ts +0 -9
  316. package/dist/handlers/__tests__/join-policy.test.d.ts.map +0 -1
  317. package/dist/handlers/__tests__/join-policy.test.js +0 -201
  318. package/dist/handlers/__tests__/join-policy.test.js.map +0 -1
  319. package/dist/handlers/__tests__/manager-loop.test.d.ts +0 -14
  320. package/dist/handlers/__tests__/manager-loop.test.d.ts.map +0 -1
  321. package/dist/handlers/__tests__/manager-loop.test.js +0 -322
  322. package/dist/handlers/__tests__/manager-loop.test.js.map +0 -1
  323. package/dist/handlers/__tests__/parallel-events.test.d.ts +0 -12
  324. package/dist/handlers/__tests__/parallel-events.test.d.ts.map +0 -1
  325. package/dist/handlers/__tests__/parallel-events.test.js +0 -252
  326. package/dist/handlers/__tests__/parallel-events.test.js.map +0 -1
  327. package/dist/handlers/__tests__/parallel-handler.test.d.ts +0 -14
  328. package/dist/handlers/__tests__/parallel-handler.test.d.ts.map +0 -1
  329. package/dist/handlers/__tests__/parallel-handler.test.js +0 -337
  330. package/dist/handlers/__tests__/parallel-handler.test.js.map +0 -1
  331. package/dist/handlers/__tests__/parallel-join.test.d.ts +0 -9
  332. package/dist/handlers/__tests__/parallel-join.test.d.ts.map +0 -1
  333. package/dist/handlers/__tests__/parallel-join.test.js +0 -267
  334. package/dist/handlers/__tests__/parallel-join.test.js.map +0 -1
  335. package/dist/handlers/__tests__/registry.test.d.ts +0 -14
  336. package/dist/handlers/__tests__/registry.test.d.ts.map +0 -1
  337. package/dist/handlers/__tests__/registry.test.js +0 -315
  338. package/dist/handlers/__tests__/registry.test.js.map +0 -1
  339. package/dist/handlers/__tests__/subgraph-events.test.d.ts +0 -10
  340. package/dist/handlers/__tests__/subgraph-events.test.d.ts.map +0 -1
  341. package/dist/handlers/__tests__/subgraph-events.test.js +0 -189
  342. package/dist/handlers/__tests__/subgraph-events.test.js.map +0 -1
  343. package/dist/handlers/__tests__/subgraph-inheritance.test.d.ts +0 -14
  344. package/dist/handlers/__tests__/subgraph-inheritance.test.d.ts.map +0 -1
  345. package/dist/handlers/__tests__/subgraph-inheritance.test.js +0 -267
  346. package/dist/handlers/__tests__/subgraph-inheritance.test.js.map +0 -1
  347. package/dist/handlers/__tests__/subgraph.test.d.ts +0 -14
  348. package/dist/handlers/__tests__/subgraph.test.d.ts.map +0 -1
  349. package/dist/handlers/__tests__/subgraph.test.js +0 -369
  350. package/dist/handlers/__tests__/subgraph.test.js.map +0 -1
  351. package/dist/handlers/__tests__/tool-handler.test.d.ts +0 -11
  352. package/dist/handlers/__tests__/tool-handler.test.d.ts.map +0 -1
  353. package/dist/handlers/__tests__/tool-handler.test.js +0 -184
  354. package/dist/handlers/__tests__/tool-handler.test.js.map +0 -1
  355. package/dist/handlers/__tests__/tool-scenario.test.d.ts +0 -12
  356. package/dist/handlers/__tests__/tool-scenario.test.d.ts.map +0 -1
  357. package/dist/handlers/__tests__/tool-scenario.test.js +0 -222
  358. package/dist/handlers/__tests__/tool-scenario.test.js.map +0 -1
  359. package/dist/handlers/__tests__/wait-human-handler.test.d.ts +0 -11
  360. package/dist/handlers/__tests__/wait-human-handler.test.d.ts.map +0 -1
  361. package/dist/handlers/__tests__/wait-human-handler.test.js +0 -251
  362. package/dist/handlers/__tests__/wait-human-handler.test.js.map +0 -1
  363. package/dist/llm/__tests__/client.test.d.ts +0 -2
  364. package/dist/llm/__tests__/client.test.d.ts.map +0 -1
  365. package/dist/llm/__tests__/client.test.js +0 -198
  366. package/dist/llm/__tests__/client.test.js.map +0 -1
  367. package/dist/llm/__tests__/types.test.d.ts +0 -2
  368. package/dist/llm/__tests__/types.test.d.ts.map +0 -1
  369. package/dist/llm/__tests__/types.test.js +0 -289
  370. package/dist/llm/__tests__/types.test.js.map +0 -1
  371. package/dist/llm/middleware/__tests__/cost-tracking.test.d.ts +0 -2
  372. package/dist/llm/middleware/__tests__/cost-tracking.test.d.ts.map +0 -1
  373. package/dist/llm/middleware/__tests__/cost-tracking.test.js +0 -73
  374. package/dist/llm/middleware/__tests__/cost-tracking.test.js.map +0 -1
  375. package/dist/llm/middleware/__tests__/logging.test.d.ts +0 -2
  376. package/dist/llm/middleware/__tests__/logging.test.d.ts.map +0 -1
  377. package/dist/llm/middleware/__tests__/logging.test.js +0 -127
  378. package/dist/llm/middleware/__tests__/logging.test.js.map +0 -1
  379. package/dist/llm/middleware/__tests__/retry.test.d.ts +0 -2
  380. package/dist/llm/middleware/__tests__/retry.test.d.ts.map +0 -1
  381. package/dist/llm/middleware/__tests__/retry.test.js +0 -126
  382. package/dist/llm/middleware/__tests__/retry.test.js.map +0 -1
  383. package/dist/llm/providers/__tests__/anthropic.test.d.ts +0 -2
  384. package/dist/llm/providers/__tests__/anthropic.test.d.ts.map +0 -1
  385. package/dist/llm/providers/__tests__/anthropic.test.js +0 -412
  386. package/dist/llm/providers/__tests__/anthropic.test.js.map +0 -1
  387. package/dist/llm/providers/__tests__/gemini.test.d.ts +0 -2
  388. package/dist/llm/providers/__tests__/gemini.test.d.ts.map +0 -1
  389. package/dist/llm/providers/__tests__/gemini.test.js +0 -591
  390. package/dist/llm/providers/__tests__/gemini.test.js.map +0 -1
  391. package/dist/llm/providers/__tests__/openai.test.d.ts +0 -2
  392. package/dist/llm/providers/__tests__/openai.test.d.ts.map +0 -1
  393. package/dist/llm/providers/__tests__/openai.test.js +0 -546
  394. package/dist/llm/providers/__tests__/openai.test.js.map +0 -1
  395. package/dist/persistence/__tests__/factory-queries.test.d.ts +0 -9
  396. package/dist/persistence/__tests__/factory-queries.test.d.ts.map +0 -1
  397. package/dist/persistence/__tests__/factory-queries.test.js +0 -372
  398. package/dist/persistence/__tests__/factory-queries.test.js.map +0 -1
  399. package/dist/persistence/__tests__/factory-schema.test.d.ts +0 -6
  400. package/dist/persistence/__tests__/factory-schema.test.d.ts.map +0 -1
  401. package/dist/persistence/__tests__/factory-schema.test.js +0 -105
  402. package/dist/persistence/__tests__/factory-schema.test.js.map +0 -1
  403. package/dist/scenarios/__tests__/cli-command-list.test.d.ts +0 -7
  404. package/dist/scenarios/__tests__/cli-command-list.test.d.ts.map +0 -1
  405. package/dist/scenarios/__tests__/cli-command-list.test.js +0 -237
  406. package/dist/scenarios/__tests__/cli-command-list.test.js.map +0 -1
  407. package/dist/scenarios/__tests__/cli-command.test.d.ts +0 -11
  408. package/dist/scenarios/__tests__/cli-command.test.d.ts.map +0 -1
  409. package/dist/scenarios/__tests__/cli-command.test.js +0 -275
  410. package/dist/scenarios/__tests__/cli-command.test.js.map +0 -1
  411. package/dist/scenarios/__tests__/integrity-pipeline.test.d.ts +0 -15
  412. package/dist/scenarios/__tests__/integrity-pipeline.test.d.ts.map +0 -1
  413. package/dist/scenarios/__tests__/integrity-pipeline.test.js +0 -318
  414. package/dist/scenarios/__tests__/integrity-pipeline.test.js.map +0 -1
  415. package/dist/scenarios/__tests__/runner-twins.test.d.ts +0 -13
  416. package/dist/scenarios/__tests__/runner-twins.test.d.ts.map +0 -1
  417. package/dist/scenarios/__tests__/runner-twins.test.js +0 -205
  418. package/dist/scenarios/__tests__/runner-twins.test.js.map +0 -1
  419. package/dist/scenarios/__tests__/scorer.test.d.ts +0 -11
  420. package/dist/scenarios/__tests__/scorer.test.d.ts.map +0 -1
  421. package/dist/scenarios/__tests__/scorer.test.js +0 -225
  422. package/dist/scenarios/__tests__/scorer.test.js.map +0 -1
  423. package/dist/scenarios/__tests__/scoring-integration.test.d.ts +0 -8
  424. package/dist/scenarios/__tests__/scoring-integration.test.d.ts.map +0 -1
  425. package/dist/scenarios/__tests__/scoring-integration.test.js +0 -178
  426. package/dist/scenarios/__tests__/scoring-integration.test.js.map +0 -1
  427. package/dist/scenarios/__tests__/store.test.d.ts +0 -5
  428. package/dist/scenarios/__tests__/store.test.d.ts.map +0 -1
  429. package/dist/scenarios/__tests__/store.test.js +0 -169
  430. package/dist/scenarios/__tests__/store.test.js.map +0 -1
  431. package/dist/stylesheet/__tests__/stylesheet.test.d.ts +0 -17
  432. package/dist/stylesheet/__tests__/stylesheet.test.d.ts.map +0 -1
  433. package/dist/stylesheet/__tests__/stylesheet.test.js +0 -368
  434. package/dist/stylesheet/__tests__/stylesheet.test.js.map +0 -1
  435. package/dist/templates/__tests__/templates.test.d.ts +0 -7
  436. package/dist/templates/__tests__/templates.test.d.ts.map +0 -1
  437. package/dist/templates/__tests__/templates.test.js +0 -92
  438. package/dist/templates/__tests__/templates.test.js.map +0 -1
  439. package/dist/twins/__tests__/docker-compose.test.d.ts +0 -13
  440. package/dist/twins/__tests__/docker-compose.test.d.ts.map +0 -1
  441. package/dist/twins/__tests__/docker-compose.test.js +0 -247
  442. package/dist/twins/__tests__/docker-compose.test.js.map +0 -1
  443. package/dist/twins/__tests__/health-monitor.test.d.ts +0 -19
  444. package/dist/twins/__tests__/health-monitor.test.d.ts.map +0 -1
  445. package/dist/twins/__tests__/health-monitor.test.js +0 -301
  446. package/dist/twins/__tests__/health-monitor.test.js.map +0 -1
  447. package/dist/twins/__tests__/integration/e2e.test.d.ts +0 -18
  448. package/dist/twins/__tests__/integration/e2e.test.d.ts.map +0 -1
  449. package/dist/twins/__tests__/integration/e2e.test.js +0 -146
  450. package/dist/twins/__tests__/integration/e2e.test.js.map +0 -1
  451. package/dist/twins/__tests__/integration/helpers.d.ts +0 -32
  452. package/dist/twins/__tests__/integration/helpers.d.ts.map +0 -1
  453. package/dist/twins/__tests__/integration/helpers.js +0 -67
  454. package/dist/twins/__tests__/integration/helpers.js.map +0 -1
  455. package/dist/twins/__tests__/integration/lifecycle.test.d.ts +0 -14
  456. package/dist/twins/__tests__/integration/lifecycle.test.d.ts.map +0 -1
  457. package/dist/twins/__tests__/integration/lifecycle.test.js +0 -127
  458. package/dist/twins/__tests__/integration/lifecycle.test.js.map +0 -1
  459. package/dist/twins/__tests__/integration/persistence-integration.test.d.ts +0 -14
  460. package/dist/twins/__tests__/integration/persistence-integration.test.d.ts.map +0 -1
  461. package/dist/twins/__tests__/integration/persistence-integration.test.js +0 -132
  462. package/dist/twins/__tests__/integration/persistence-integration.test.js.map +0 -1
  463. package/dist/twins/__tests__/persistence.test.d.ts +0 -10
  464. package/dist/twins/__tests__/persistence.test.d.ts.map +0 -1
  465. package/dist/twins/__tests__/persistence.test.js +0 -300
  466. package/dist/twins/__tests__/persistence.test.js.map +0 -1
  467. package/dist/twins/__tests__/registry.test.d.ts +0 -7
  468. package/dist/twins/__tests__/registry.test.d.ts.map +0 -1
  469. package/dist/twins/__tests__/registry.test.js +0 -282
  470. package/dist/twins/__tests__/registry.test.js.map +0 -1
  471. package/dist/twins/__tests__/run-state.test.d.ts +0 -9
  472. package/dist/twins/__tests__/run-state.test.d.ts.map +0 -1
  473. package/dist/twins/__tests__/run-state.test.js +0 -112
  474. package/dist/twins/__tests__/run-state.test.js.map +0 -1
  475. package/dist/twins/__tests__/templates-cli.test.d.ts +0 -10
  476. package/dist/twins/__tests__/templates-cli.test.d.ts.map +0 -1
  477. package/dist/twins/__tests__/templates-cli.test.js +0 -187
  478. package/dist/twins/__tests__/templates-cli.test.js.map +0 -1
  479. package/dist/twins/__tests__/templates.test.d.ts +0 -7
  480. package/dist/twins/__tests__/templates.test.d.ts.map +0 -1
  481. package/dist/twins/__tests__/templates.test.js +0 -87
  482. package/dist/twins/__tests__/templates.test.js.map +0 -1
  483. package/dist/twins/__tests__/twins-cli.test.d.ts +0 -11
  484. package/dist/twins/__tests__/twins-cli.test.d.ts.map +0 -1
  485. package/dist/twins/__tests__/twins-cli.test.js +0 -365
  486. 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