@substrate-ai/core 0.19.54

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 +55 -0
  2. package/dist/__tests__/adapter.test.d.ts +12 -0
  3. package/dist/__tests__/adapter.test.d.ts.map +1 -0
  4. package/dist/__tests__/adapter.test.js +259 -0
  5. package/dist/__tests__/adapter.test.js.map +1 -0
  6. package/dist/__tests__/event-bus.test.d.ts +14 -0
  7. package/dist/__tests__/event-bus.test.d.ts.map +1 -0
  8. package/dist/__tests__/event-bus.test.js +199 -0
  9. package/dist/__tests__/event-bus.test.js.map +1 -0
  10. package/dist/__tests__/output-quality.test.d.ts +8 -0
  11. package/dist/__tests__/output-quality.test.d.ts.map +1 -0
  12. package/dist/__tests__/output-quality.test.js +166 -0
  13. package/dist/__tests__/output-quality.test.js.map +1 -0
  14. package/dist/__tests__/schema-suffix.test.d.ts +9 -0
  15. package/dist/__tests__/schema-suffix.test.d.ts.map +1 -0
  16. package/dist/__tests__/schema-suffix.test.js +126 -0
  17. package/dist/__tests__/schema-suffix.test.js.map +1 -0
  18. package/dist/__tests__/yaml-parser.test.d.ts +18 -0
  19. package/dist/__tests__/yaml-parser.test.d.ts.map +1 -0
  20. package/dist/__tests__/yaml-parser.test.js +475 -0
  21. package/dist/__tests__/yaml-parser.test.js.map +1 -0
  22. package/dist/__type-checks__.d.ts +11 -0
  23. package/dist/__type-checks__.d.ts.map +1 -0
  24. package/dist/__type-checks__.js +19 -0
  25. package/dist/__type-checks__.js.map +1 -0
  26. package/dist/adapters/__tests__/adapter-output-normalizer.test.d.ts +12 -0
  27. package/dist/adapters/__tests__/adapter-output-normalizer.test.d.ts.map +1 -0
  28. package/dist/adapters/__tests__/adapter-output-normalizer.test.js +193 -0
  29. package/dist/adapters/__tests__/adapter-output-normalizer.test.js.map +1 -0
  30. package/dist/adapters/adapter-format-error.d.ts +35 -0
  31. package/dist/adapters/adapter-format-error.d.ts.map +1 -0
  32. package/dist/adapters/adapter-format-error.js +38 -0
  33. package/dist/adapters/adapter-format-error.js.map +1 -0
  34. package/dist/adapters/adapter-output-normalizer.d.ts +50 -0
  35. package/dist/adapters/adapter-output-normalizer.d.ts.map +1 -0
  36. package/dist/adapters/adapter-output-normalizer.js +233 -0
  37. package/dist/adapters/adapter-output-normalizer.js.map +1 -0
  38. package/dist/adapters/adapter-registry.d.ts +50 -0
  39. package/dist/adapters/adapter-registry.d.ts.map +1 -0
  40. package/dist/adapters/adapter-registry.js +101 -0
  41. package/dist/adapters/adapter-registry.js.map +1 -0
  42. package/dist/adapters/claude-adapter.d.ts +59 -0
  43. package/dist/adapters/claude-adapter.d.ts.map +1 -0
  44. package/dist/adapters/claude-adapter.js +367 -0
  45. package/dist/adapters/claude-adapter.js.map +1 -0
  46. package/dist/adapters/codex-adapter.d.ts +64 -0
  47. package/dist/adapters/codex-adapter.d.ts.map +1 -0
  48. package/dist/adapters/codex-adapter.js +263 -0
  49. package/dist/adapters/codex-adapter.js.map +1 -0
  50. package/dist/adapters/gemini-adapter.d.ts +57 -0
  51. package/dist/adapters/gemini-adapter.d.ts.map +1 -0
  52. package/dist/adapters/gemini-adapter.js +311 -0
  53. package/dist/adapters/gemini-adapter.js.map +1 -0
  54. package/dist/adapters/index.d.ts +10 -0
  55. package/dist/adapters/index.d.ts.map +1 -0
  56. package/dist/adapters/index.js +14 -0
  57. package/dist/adapters/index.js.map +1 -0
  58. package/dist/adapters/schemas.d.ts +137 -0
  59. package/dist/adapters/schemas.d.ts.map +1 -0
  60. package/dist/adapters/schemas.js +140 -0
  61. package/dist/adapters/schemas.js.map +1 -0
  62. package/dist/adapters/types.d.ts +245 -0
  63. package/dist/adapters/types.d.ts.map +1 -0
  64. package/dist/adapters/types.js +6 -0
  65. package/dist/adapters/types.js.map +1 -0
  66. package/dist/adapters/worker-adapter.d.ts +188 -0
  67. package/dist/adapters/worker-adapter.d.ts.map +1 -0
  68. package/dist/adapters/worker-adapter.js +19 -0
  69. package/dist/adapters/worker-adapter.js.map +1 -0
  70. package/dist/budget/budget-tracker.d.ts +22 -0
  71. package/dist/budget/budget-tracker.d.ts.map +1 -0
  72. package/dist/budget/budget-tracker.js +39 -0
  73. package/dist/budget/budget-tracker.js.map +1 -0
  74. package/dist/budget/index.d.ts +6 -0
  75. package/dist/budget/index.d.ts.map +1 -0
  76. package/dist/budget/index.js +5 -0
  77. package/dist/budget/index.js.map +1 -0
  78. package/dist/config/config-migrator.d.ts +58 -0
  79. package/dist/config/config-migrator.d.ts.map +1 -0
  80. package/dist/config/config-migrator.js +158 -0
  81. package/dist/config/config-migrator.js.map +1 -0
  82. package/dist/config/config-system-impl.d.ts +63 -0
  83. package/dist/config/config-system-impl.d.ts.map +1 -0
  84. package/dist/config/config-system-impl.js +364 -0
  85. package/dist/config/config-system-impl.js.map +1 -0
  86. package/dist/config/config-watcher.d.ts +59 -0
  87. package/dist/config/config-watcher.d.ts.map +1 -0
  88. package/dist/config/config-watcher.js +137 -0
  89. package/dist/config/config-watcher.js.map +1 -0
  90. package/dist/config/defaults.d.ts +13 -0
  91. package/dist/config/defaults.d.ts.map +1 -0
  92. package/dist/config/defaults.js +62 -0
  93. package/dist/config/defaults.js.map +1 -0
  94. package/dist/config/errors.d.ts +25 -0
  95. package/dist/config/errors.d.ts.map +1 -0
  96. package/dist/config/errors.js +49 -0
  97. package/dist/config/errors.js.map +1 -0
  98. package/dist/config/index.d.ts +19 -0
  99. package/dist/config/index.d.ts.map +1 -0
  100. package/dist/config/index.js +39 -0
  101. package/dist/config/index.js.map +1 -0
  102. package/dist/config/types.d.ts +456 -0
  103. package/dist/config/types.d.ts.map +1 -0
  104. package/dist/config/types.js +174 -0
  105. package/dist/config/types.js.map +1 -0
  106. package/dist/config/version-utils.d.ts +39 -0
  107. package/dist/config/version-utils.d.ts.map +1 -0
  108. package/dist/config/version-utils.js +66 -0
  109. package/dist/config/version-utils.js.map +1 -0
  110. package/dist/context/index.d.ts +2 -0
  111. package/dist/context/index.d.ts.map +1 -0
  112. package/dist/context/index.js +2 -0
  113. package/dist/context/index.js.map +1 -0
  114. package/dist/context/types.d.ts +113 -0
  115. package/dist/context/types.d.ts.map +1 -0
  116. package/dist/context/types.js +59 -0
  117. package/dist/context/types.js.map +1 -0
  118. package/dist/cost-tracker/cost-tracker-impl.d.ts +51 -0
  119. package/dist/cost-tracker/cost-tracker-impl.d.ts.map +1 -0
  120. package/dist/cost-tracker/cost-tracker-impl.js +85 -0
  121. package/dist/cost-tracker/cost-tracker-impl.js.map +1 -0
  122. package/dist/cost-tracker/cost-tracker-subscriber.d.ts +31 -0
  123. package/dist/cost-tracker/cost-tracker-subscriber.d.ts.map +1 -0
  124. package/dist/cost-tracker/cost-tracker-subscriber.js +116 -0
  125. package/dist/cost-tracker/cost-tracker-subscriber.js.map +1 -0
  126. package/dist/cost-tracker/index.d.ts +11 -0
  127. package/dist/cost-tracker/index.d.ts.map +1 -0
  128. package/dist/cost-tracker/index.js +7 -0
  129. package/dist/cost-tracker/index.js.map +1 -0
  130. package/dist/cost-tracker/token-rates.d.ts +25 -0
  131. package/dist/cost-tracker/token-rates.d.ts.map +1 -0
  132. package/dist/cost-tracker/token-rates.js +99 -0
  133. package/dist/cost-tracker/token-rates.js.map +1 -0
  134. package/dist/cost-tracker/types.d.ts +6 -0
  135. package/dist/cost-tracker/types.d.ts.map +1 -0
  136. package/dist/cost-tracker/types.js +2 -0
  137. package/dist/cost-tracker/types.js.map +1 -0
  138. package/dist/dispatch/dispatcher-impl.d.ts +92 -0
  139. package/dist/dispatch/dispatcher-impl.d.ts.map +1 -0
  140. package/dist/dispatch/dispatcher-impl.js +847 -0
  141. package/dist/dispatch/dispatcher-impl.js.map +1 -0
  142. package/dist/dispatch/index.d.ts +15 -0
  143. package/dist/dispatch/index.d.ts.map +1 -0
  144. package/dist/dispatch/index.js +14 -0
  145. package/dist/dispatch/index.js.map +1 -0
  146. package/dist/dispatch/interface-change-detector.d.ts +46 -0
  147. package/dist/dispatch/interface-change-detector.d.ts.map +1 -0
  148. package/dist/dispatch/interface-change-detector.js +135 -0
  149. package/dist/dispatch/interface-change-detector.js.map +1 -0
  150. package/dist/dispatch/output-quality.d.ts +43 -0
  151. package/dist/dispatch/output-quality.d.ts.map +1 -0
  152. package/dist/dispatch/output-quality.js +148 -0
  153. package/dist/dispatch/output-quality.js.map +1 -0
  154. package/dist/dispatch/types.d.ts +271 -0
  155. package/dist/dispatch/types.d.ts.map +1 -0
  156. package/dist/dispatch/types.js +76 -0
  157. package/dist/dispatch/types.js.map +1 -0
  158. package/dist/dispatch/yaml-parser.d.ts +40 -0
  159. package/dist/dispatch/yaml-parser.d.ts.map +1 -0
  160. package/dist/dispatch/yaml-parser.js +323 -0
  161. package/dist/dispatch/yaml-parser.js.map +1 -0
  162. package/dist/events/core-events.d.ts +288 -0
  163. package/dist/events/core-events.d.ts.map +1 -0
  164. package/dist/events/core-events.js +10 -0
  165. package/dist/events/core-events.js.map +1 -0
  166. package/dist/events/event-bus.d.ts +55 -0
  167. package/dist/events/event-bus.d.ts.map +1 -0
  168. package/dist/events/event-bus.js +52 -0
  169. package/dist/events/event-bus.js.map +1 -0
  170. package/dist/events/index.d.ts +9 -0
  171. package/dist/events/index.d.ts.map +1 -0
  172. package/dist/events/index.js +6 -0
  173. package/dist/events/index.js.map +1 -0
  174. package/dist/events/types.d.ts +21 -0
  175. package/dist/events/types.d.ts.map +1 -0
  176. package/dist/events/types.js +8 -0
  177. package/dist/events/types.js.map +1 -0
  178. package/dist/git/git-manager.d.ts +31 -0
  179. package/dist/git/git-manager.d.ts.map +1 -0
  180. package/dist/git/git-manager.js +46 -0
  181. package/dist/git/git-manager.js.map +1 -0
  182. package/dist/git/git-utils.d.ts +166 -0
  183. package/dist/git/git-utils.d.ts.map +1 -0
  184. package/dist/git/git-utils.js +347 -0
  185. package/dist/git/git-utils.js.map +1 -0
  186. package/dist/git/git-worktree-manager-impl.d.ts +58 -0
  187. package/dist/git/git-worktree-manager-impl.d.ts.map +1 -0
  188. package/dist/git/git-worktree-manager-impl.js +336 -0
  189. package/dist/git/git-worktree-manager-impl.js.map +1 -0
  190. package/dist/git/git-worktree-manager.d.ts +122 -0
  191. package/dist/git/git-worktree-manager.d.ts.map +1 -0
  192. package/dist/git/git-worktree-manager.js +14 -0
  193. package/dist/git/git-worktree-manager.js.map +1 -0
  194. package/dist/git/index.d.ts +11 -0
  195. package/dist/git/index.d.ts.map +1 -0
  196. package/dist/git/index.js +8 -0
  197. package/dist/git/index.js.map +1 -0
  198. package/dist/index.d.ts +33 -0
  199. package/dist/index.d.ts.map +1 -0
  200. package/dist/index.js +47 -0
  201. package/dist/index.js.map +1 -0
  202. package/dist/llm/client.d.ts +42 -0
  203. package/dist/llm/client.d.ts.map +1 -0
  204. package/dist/llm/client.js +27 -0
  205. package/dist/llm/client.js.map +1 -0
  206. package/dist/monitor/index.d.ts +15 -0
  207. package/dist/monitor/index.d.ts.map +1 -0
  208. package/dist/monitor/index.js +9 -0
  209. package/dist/monitor/index.js.map +1 -0
  210. package/dist/monitor/monitor-agent-impl.d.ts +56 -0
  211. package/dist/monitor/monitor-agent-impl.d.ts.map +1 -0
  212. package/dist/monitor/monitor-agent-impl.js +178 -0
  213. package/dist/monitor/monitor-agent-impl.js.map +1 -0
  214. package/dist/monitor/monitor-agent.d.ts +36 -0
  215. package/dist/monitor/monitor-agent.d.ts.map +1 -0
  216. package/dist/monitor/monitor-agent.js +6 -0
  217. package/dist/monitor/monitor-agent.js.map +1 -0
  218. package/dist/monitor/performance-aggregates.d.ts +41 -0
  219. package/dist/monitor/performance-aggregates.d.ts.map +1 -0
  220. package/dist/monitor/performance-aggregates.js +6 -0
  221. package/dist/monitor/performance-aggregates.js.map +1 -0
  222. package/dist/monitor/recommendation-engine.d.ts +27 -0
  223. package/dist/monitor/recommendation-engine.d.ts.map +1 -0
  224. package/dist/monitor/recommendation-engine.js +140 -0
  225. package/dist/monitor/recommendation-engine.js.map +1 -0
  226. package/dist/monitor/recommendation-types.d.ts +30 -0
  227. package/dist/monitor/recommendation-types.d.ts.map +1 -0
  228. package/dist/monitor/recommendation-types.js +43 -0
  229. package/dist/monitor/recommendation-types.js.map +1 -0
  230. package/dist/monitor/report-generator.d.ts +48 -0
  231. package/dist/monitor/report-generator.d.ts.map +1 -0
  232. package/dist/monitor/report-generator.js +123 -0
  233. package/dist/monitor/report-generator.js.map +1 -0
  234. package/dist/monitor/task-type-classifier.d.ts +19 -0
  235. package/dist/monitor/task-type-classifier.d.ts.map +1 -0
  236. package/dist/monitor/task-type-classifier.js +68 -0
  237. package/dist/monitor/task-type-classifier.js.map +1 -0
  238. package/dist/persistence/adapter.d.ts +4 -0
  239. package/dist/persistence/adapter.d.ts.map +1 -0
  240. package/dist/persistence/adapter.js +56 -0
  241. package/dist/persistence/adapter.js.map +1 -0
  242. package/dist/persistence/cost-types.d.ts +87 -0
  243. package/dist/persistence/cost-types.d.ts.map +1 -0
  244. package/dist/persistence/cost-types.js +14 -0
  245. package/dist/persistence/cost-types.js.map +1 -0
  246. package/dist/persistence/dolt-adapter-transaction.test.d.ts +10 -0
  247. package/dist/persistence/dolt-adapter-transaction.test.d.ts.map +1 -0
  248. package/dist/persistence/dolt-adapter-transaction.test.js +359 -0
  249. package/dist/persistence/dolt-adapter-transaction.test.js.map +1 -0
  250. package/dist/persistence/dolt-adapter.d.ts +77 -0
  251. package/dist/persistence/dolt-adapter.d.ts.map +1 -0
  252. package/dist/persistence/dolt-adapter.js +98 -0
  253. package/dist/persistence/dolt-adapter.js.map +1 -0
  254. package/dist/persistence/dolt-client.d.ts +69 -0
  255. package/dist/persistence/dolt-client.d.ts.map +1 -0
  256. package/dist/persistence/dolt-client.js +278 -0
  257. package/dist/persistence/dolt-client.js.map +1 -0
  258. package/dist/persistence/dolt-errors.d.ts +10 -0
  259. package/dist/persistence/dolt-errors.d.ts.map +1 -0
  260. package/dist/persistence/dolt-errors.js +15 -0
  261. package/dist/persistence/dolt-errors.js.map +1 -0
  262. package/dist/persistence/dolt-init.d.ts +64 -0
  263. package/dist/persistence/dolt-init.d.ts.map +1 -0
  264. package/dist/persistence/dolt-init.js +233 -0
  265. package/dist/persistence/dolt-init.js.map +1 -0
  266. package/dist/persistence/index.d.ts +23 -0
  267. package/dist/persistence/index.d.ts.map +1 -0
  268. package/dist/persistence/index.js +23 -0
  269. package/dist/persistence/index.js.map +1 -0
  270. package/dist/persistence/memory-adapter.d.ts +156 -0
  271. package/dist/persistence/memory-adapter.d.ts.map +1 -0
  272. package/dist/persistence/memory-adapter.js +1167 -0
  273. package/dist/persistence/memory-adapter.js.map +1 -0
  274. package/dist/persistence/monitor-database.d.ts +113 -0
  275. package/dist/persistence/monitor-database.d.ts.map +1 -0
  276. package/dist/persistence/monitor-database.js +345 -0
  277. package/dist/persistence/monitor-database.js.map +1 -0
  278. package/dist/persistence/queries/amendments.d.ts +91 -0
  279. package/dist/persistence/queries/amendments.d.ts.map +1 -0
  280. package/dist/persistence/queries/amendments.js +185 -0
  281. package/dist/persistence/queries/amendments.js.map +1 -0
  282. package/dist/persistence/queries/cost.d.ts +130 -0
  283. package/dist/persistence/queries/cost.d.ts.map +1 -0
  284. package/dist/persistence/queries/cost.js +384 -0
  285. package/dist/persistence/queries/cost.js.map +1 -0
  286. package/dist/persistence/queries/decisions.d.ts +131 -0
  287. package/dist/persistence/queries/decisions.d.ts.map +1 -0
  288. package/dist/persistence/queries/decisions.js +339 -0
  289. package/dist/persistence/queries/decisions.js.map +1 -0
  290. package/dist/persistence/queries/metrics.d.ts +155 -0
  291. package/dist/persistence/queries/metrics.d.ts.map +1 -0
  292. package/dist/persistence/queries/metrics.js +237 -0
  293. package/dist/persistence/queries/metrics.js.map +1 -0
  294. package/dist/persistence/queries/retry-escalated.d.ts +35 -0
  295. package/dist/persistence/queries/retry-escalated.d.ts.map +1 -0
  296. package/dist/persistence/queries/retry-escalated.js +83 -0
  297. package/dist/persistence/queries/retry-escalated.js.map +1 -0
  298. package/dist/persistence/schema-version.d.ts +89 -0
  299. package/dist/persistence/schema-version.d.ts.map +1 -0
  300. package/dist/persistence/schema-version.js +67 -0
  301. package/dist/persistence/schema-version.js.map +1 -0
  302. package/dist/persistence/schema.d.ts +26 -0
  303. package/dist/persistence/schema.d.ts.map +1 -0
  304. package/dist/persistence/schema.js +509 -0
  305. package/dist/persistence/schema.js.map +1 -0
  306. package/dist/persistence/schemas/decisions.d.ts +176 -0
  307. package/dist/persistence/schemas/decisions.d.ts.map +1 -0
  308. package/dist/persistence/schemas/decisions.js +139 -0
  309. package/dist/persistence/schemas/decisions.js.map +1 -0
  310. package/dist/persistence/schemas/operational.d.ts +194 -0
  311. package/dist/persistence/schemas/operational.d.ts.map +1 -0
  312. package/dist/persistence/schemas/operational.js +197 -0
  313. package/dist/persistence/schemas/operational.js.map +1 -0
  314. package/dist/persistence/types.d.ts +98 -0
  315. package/dist/persistence/types.d.ts.map +1 -0
  316. package/dist/persistence/types.js +22 -0
  317. package/dist/persistence/types.js.map +1 -0
  318. package/dist/quality-gates/index.d.ts +2 -0
  319. package/dist/quality-gates/index.d.ts.map +1 -0
  320. package/dist/quality-gates/index.js +2 -0
  321. package/dist/quality-gates/index.js.map +1 -0
  322. package/dist/quality-gates/types.d.ts +106 -0
  323. package/dist/quality-gates/types.d.ts.map +1 -0
  324. package/dist/quality-gates/types.js +5 -0
  325. package/dist/quality-gates/types.js.map +1 -0
  326. package/dist/routing/index.d.ts +19 -0
  327. package/dist/routing/index.d.ts.map +1 -0
  328. package/dist/routing/index.js +32 -0
  329. package/dist/routing/index.js.map +1 -0
  330. package/dist/routing/model-routing-config.d.ts +75 -0
  331. package/dist/routing/model-routing-config.d.ts.map +1 -0
  332. package/dist/routing/model-routing-config.js +110 -0
  333. package/dist/routing/model-routing-config.js.map +1 -0
  334. package/dist/routing/model-routing-resolver.d.ts +48 -0
  335. package/dist/routing/model-routing-resolver.d.ts.map +1 -0
  336. package/dist/routing/model-routing-resolver.js +105 -0
  337. package/dist/routing/model-routing-resolver.js.map +1 -0
  338. package/dist/routing/model-tier.d.ts +21 -0
  339. package/dist/routing/model-tier.d.ts.map +1 -0
  340. package/dist/routing/model-tier.js +34 -0
  341. package/dist/routing/model-tier.js.map +1 -0
  342. package/dist/routing/provider-status.d.ts +99 -0
  343. package/dist/routing/provider-status.d.ts.map +1 -0
  344. package/dist/routing/provider-status.js +163 -0
  345. package/dist/routing/provider-status.js.map +1 -0
  346. package/dist/routing/routing-decision.d.ts +127 -0
  347. package/dist/routing/routing-decision.d.ts.map +1 -0
  348. package/dist/routing/routing-decision.js +111 -0
  349. package/dist/routing/routing-decision.js.map +1 -0
  350. package/dist/routing/routing-engine-impl.d.ts +132 -0
  351. package/dist/routing/routing-engine-impl.d.ts.map +1 -0
  352. package/dist/routing/routing-engine-impl.js +450 -0
  353. package/dist/routing/routing-engine-impl.js.map +1 -0
  354. package/dist/routing/routing-engine.d.ts +83 -0
  355. package/dist/routing/routing-engine.d.ts.map +1 -0
  356. package/dist/routing/routing-engine.js +24 -0
  357. package/dist/routing/routing-engine.js.map +1 -0
  358. package/dist/routing/routing-policy.d.ts +138 -0
  359. package/dist/routing/routing-policy.d.ts.map +1 -0
  360. package/dist/routing/routing-policy.js +159 -0
  361. package/dist/routing/routing-policy.js.map +1 -0
  362. package/dist/routing/routing-recommender.d.ts +60 -0
  363. package/dist/routing/routing-recommender.d.ts.map +1 -0
  364. package/dist/routing/routing-recommender.js +209 -0
  365. package/dist/routing/routing-recommender.js.map +1 -0
  366. package/dist/routing/routing-telemetry.d.ts +36 -0
  367. package/dist/routing/routing-telemetry.d.ts.map +1 -0
  368. package/dist/routing/routing-telemetry.js +39 -0
  369. package/dist/routing/routing-telemetry.js.map +1 -0
  370. package/dist/routing/routing-token-accumulator.d.ts +68 -0
  371. package/dist/routing/routing-token-accumulator.d.ts.map +1 -0
  372. package/dist/routing/routing-token-accumulator.js +111 -0
  373. package/dist/routing/routing-token-accumulator.js.map +1 -0
  374. package/dist/routing/routing-tuner.d.ts +69 -0
  375. package/dist/routing/routing-tuner.d.ts.map +1 -0
  376. package/dist/routing/routing-tuner.js +217 -0
  377. package/dist/routing/routing-tuner.js.map +1 -0
  378. package/dist/routing/types.d.ts +152 -0
  379. package/dist/routing/types.d.ts.map +1 -0
  380. package/dist/routing/types.js +13 -0
  381. package/dist/routing/types.js.map +1 -0
  382. package/dist/supervisor/analysis.d.ts +178 -0
  383. package/dist/supervisor/analysis.d.ts.map +1 -0
  384. package/dist/supervisor/analysis.js +420 -0
  385. package/dist/supervisor/analysis.js.map +1 -0
  386. package/dist/supervisor/experimenter.d.ts +118 -0
  387. package/dist/supervisor/experimenter.d.ts.map +1 -0
  388. package/dist/supervisor/experimenter.js +493 -0
  389. package/dist/supervisor/experimenter.js.map +1 -0
  390. package/dist/supervisor/index.d.ts +13 -0
  391. package/dist/supervisor/index.d.ts.map +1 -0
  392. package/dist/supervisor/index.js +11 -0
  393. package/dist/supervisor/index.js.map +1 -0
  394. package/dist/telemetry/batch-buffer.d.ts +53 -0
  395. package/dist/telemetry/batch-buffer.d.ts.map +1 -0
  396. package/dist/telemetry/batch-buffer.js +83 -0
  397. package/dist/telemetry/batch-buffer.js.map +1 -0
  398. package/dist/telemetry/categorizer.d.ts +65 -0
  399. package/dist/telemetry/categorizer.d.ts.map +1 -0
  400. package/dist/telemetry/categorizer.js +338 -0
  401. package/dist/telemetry/categorizer.js.map +1 -0
  402. package/dist/telemetry/consumer-analyzer.d.ts +53 -0
  403. package/dist/telemetry/consumer-analyzer.d.ts.map +1 -0
  404. package/dist/telemetry/consumer-analyzer.js +182 -0
  405. package/dist/telemetry/consumer-analyzer.js.map +1 -0
  406. package/dist/telemetry/cost-table.d.ts +36 -0
  407. package/dist/telemetry/cost-table.d.ts.map +1 -0
  408. package/dist/telemetry/cost-table.js +127 -0
  409. package/dist/telemetry/cost-table.js.map +1 -0
  410. package/dist/telemetry/efficiency-scorer.d.ts +103 -0
  411. package/dist/telemetry/efficiency-scorer.d.ts.map +1 -0
  412. package/dist/telemetry/efficiency-scorer.js +311 -0
  413. package/dist/telemetry/efficiency-scorer.js.map +1 -0
  414. package/dist/telemetry/index.d.ts +28 -0
  415. package/dist/telemetry/index.d.ts.map +1 -0
  416. package/dist/telemetry/index.js +37 -0
  417. package/dist/telemetry/index.js.map +1 -0
  418. package/dist/telemetry/ingestion-server.d.ts +99 -0
  419. package/dist/telemetry/ingestion-server.d.ts.map +1 -0
  420. package/dist/telemetry/ingestion-server.js +315 -0
  421. package/dist/telemetry/ingestion-server.js.map +1 -0
  422. package/dist/telemetry/log-turn-analyzer.d.ts +35 -0
  423. package/dist/telemetry/log-turn-analyzer.d.ts.map +1 -0
  424. package/dist/telemetry/log-turn-analyzer.js +132 -0
  425. package/dist/telemetry/log-turn-analyzer.js.map +1 -0
  426. package/dist/telemetry/normalizer.d.ts +43 -0
  427. package/dist/telemetry/normalizer.d.ts.map +1 -0
  428. package/dist/telemetry/normalizer.js +320 -0
  429. package/dist/telemetry/normalizer.js.map +1 -0
  430. package/dist/telemetry/recommender.d.ts +116 -0
  431. package/dist/telemetry/recommender.d.ts.map +1 -0
  432. package/dist/telemetry/recommender.js +532 -0
  433. package/dist/telemetry/recommender.js.map +1 -0
  434. package/dist/telemetry/source-detector.d.ts +19 -0
  435. package/dist/telemetry/source-detector.d.ts.map +1 -0
  436. package/dist/telemetry/source-detector.js +73 -0
  437. package/dist/telemetry/source-detector.js.map +1 -0
  438. package/dist/telemetry/task-baselines.d.ts +30 -0
  439. package/dist/telemetry/task-baselines.d.ts.map +1 -0
  440. package/dist/telemetry/task-baselines.js +44 -0
  441. package/dist/telemetry/task-baselines.js.map +1 -0
  442. package/dist/telemetry/telemetry-pipeline.d.ts +122 -0
  443. package/dist/telemetry/telemetry-pipeline.d.ts.map +1 -0
  444. package/dist/telemetry/telemetry-pipeline.js +349 -0
  445. package/dist/telemetry/telemetry-pipeline.js.map +1 -0
  446. package/dist/telemetry/timestamp-normalizer.d.ts +32 -0
  447. package/dist/telemetry/timestamp-normalizer.d.ts.map +1 -0
  448. package/dist/telemetry/timestamp-normalizer.js +133 -0
  449. package/dist/telemetry/timestamp-normalizer.js.map +1 -0
  450. package/dist/telemetry/token-extractor.d.ts +57 -0
  451. package/dist/telemetry/token-extractor.d.ts.map +1 -0
  452. package/dist/telemetry/token-extractor.js +200 -0
  453. package/dist/telemetry/token-extractor.js.map +1 -0
  454. package/dist/telemetry/turn-analyzer.d.ts +34 -0
  455. package/dist/telemetry/turn-analyzer.d.ts.map +1 -0
  456. package/dist/telemetry/turn-analyzer.js +101 -0
  457. package/dist/telemetry/turn-analyzer.js.map +1 -0
  458. package/dist/telemetry/types.d.ts +456 -0
  459. package/dist/telemetry/types.d.ts.map +1 -0
  460. package/dist/telemetry/types.js +186 -0
  461. package/dist/telemetry/types.js.map +1 -0
  462. package/dist/types.d.ts +80 -0
  463. package/dist/types.d.ts.map +1 -0
  464. package/dist/types.js +6 -0
  465. package/dist/types.js.map +1 -0
  466. package/dist/version-manager/index.d.ts +11 -0
  467. package/dist/version-manager/index.d.ts.map +1 -0
  468. package/dist/version-manager/index.js +8 -0
  469. package/dist/version-manager/index.js.map +1 -0
  470. package/dist/version-manager/update-checker.d.ts +44 -0
  471. package/dist/version-manager/update-checker.d.ts.map +1 -0
  472. package/dist/version-manager/update-checker.js +171 -0
  473. package/dist/version-manager/update-checker.js.map +1 -0
  474. package/dist/version-manager/version-cache.d.ts +42 -0
  475. package/dist/version-manager/version-cache.d.ts.map +1 -0
  476. package/dist/version-manager/version-cache.js +69 -0
  477. package/dist/version-manager/version-cache.js.map +1 -0
  478. package/dist/version-manager/version-manager-impl.d.ts +81 -0
  479. package/dist/version-manager/version-manager-impl.d.ts.map +1 -0
  480. package/dist/version-manager/version-manager-impl.js +223 -0
  481. package/dist/version-manager/version-manager-impl.js.map +1 -0
  482. package/dist/version-manager/version-manager.d.ts +70 -0
  483. package/dist/version-manager/version-manager.d.ts.map +1 -0
  484. package/dist/version-manager/version-manager.js +8 -0
  485. package/dist/version-manager/version-manager.js.map +1 -0
  486. package/package.json +27 -0
@@ -0,0 +1,847 @@
1
+ /**
2
+ * DispatcherImpl — concrete implementation of the Dispatcher interface.
3
+ *
4
+ * Spawns autonomous coding agents (Claude, Codex, Gemini) as subprocesses,
5
+ * tracks their lifecycle, enforces concurrency limits, parses their YAML
6
+ * output, and emits events through the event bus.
7
+ *
8
+ * Architecture:
9
+ * - Uses child_process.spawn (ADR-005) — NOT exec
10
+ * - Communicates via event bus (ADR-001: modular monolith)
11
+ * - Prompt delivered via stdin
12
+ * - YAML output parsed from stdout
13
+ * - Concurrency limited with FIFO queue
14
+ */
15
+ import { spawn, execSync } from 'node:child_process';
16
+ import { freemem, platform } from 'node:os';
17
+ import { randomUUID } from 'node:crypto';
18
+ import { DispatcherShuttingDownError, DEFAULT_TIMEOUTS, DEFAULT_MAX_TURNS } from './types.js';
19
+ import { parseYamlResult } from './yaml-parser.js';
20
+ import { estimateOutputQuality } from './output-quality.js';
21
+ import { AdapterOutputNormalizer } from '../adapters/adapter-output-normalizer.js';
22
+ import { AdapterFormatError } from '../adapters/adapter-format-error.js';
23
+ // Grace period (ms) between SIGTERM and SIGKILL during shutdown()
24
+ const SHUTDOWN_GRACE_MS = 10_000;
25
+ // Maximum time (ms) to wait for processes to exit after SIGKILL during shutdown()
26
+ const SHUTDOWN_MAX_WAIT_MS = 30_000;
27
+ // Characters per token for estimation heuristic
28
+ const CHARS_PER_TOKEN = 4;
29
+ // YAML output format reminder appended to prompts for non-Claude agents.
30
+ // Claude Code follows the methodology pack's embedded YAML instructions reliably;
31
+ // other agents (Codex, Gemini) sometimes need an explicit final reminder.
32
+ // When an outputSchema is provided, the suffix includes actual field names
33
+ // so the agent knows exactly what to emit.
34
+ /**
35
+ * Extract top-level field names from a Zod schema for prompt injection.
36
+ * Returns field names with type hints (e.g., "result: <string>", "files_modified: <list>").
37
+ * Exported for testing.
38
+ */
39
+ export function extractSchemaFields(schema) {
40
+ // Zod object schemas have a .shape property with field definitions.
41
+ // Navigate through .innerType() for transformed/preprocessed schemas.
42
+ let current = schema;
43
+ // Unwrap ZodEffects / pipe (transform/preprocess/refine)
44
+ // Zod 3.x: typeName === 'ZodEffects', inner at _def.schema
45
+ // Zod 4.x: type === 'pipe', inner at _def.in
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ let def = current?._def;
48
+ while (true) {
49
+ if (def?.typeName === 'ZodEffects' && def?.schema != null) {
50
+ current = def.schema;
51
+ }
52
+ else if (def?.type === 'pipe' && def?.in != null) {
53
+ current = def.in;
54
+ }
55
+ else {
56
+ break;
57
+ }
58
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
+ def = current?._def;
60
+ }
61
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
+ const shape = current?.shape;
63
+ if (shape == null || typeof shape !== 'object')
64
+ return [];
65
+ const fields = [];
66
+ for (const [key, fieldDef] of Object.entries(shape)) {
67
+ const typeName = resolveZodTypeName(fieldDef);
68
+ fields.push(`${key}: ${typeName}`);
69
+ }
70
+ return fields;
71
+ }
72
+ /**
73
+ * Resolve a human-readable type hint from a Zod field definition.
74
+ */
75
+ function resolveZodTypeName(fieldDef) {
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ const d = fieldDef;
78
+ // Support both Zod 3.x (_def.typeName) and Zod 4.x (_def.type)
79
+ const typeName = (d?._def?.typeName ?? d?._def?.type);
80
+ // Unwrap wrappers: optional, default, preprocess
81
+ if (typeName === 'ZodOptional' || typeName === 'optional' || typeName === 'ZodDefault' || typeName === 'default') {
82
+ return d._def?.innerType != null ? resolveZodTypeName(d._def.innerType) : '<value>';
83
+ }
84
+ if (typeName === 'ZodEffects') {
85
+ return d._def?.schema != null ? resolveZodTypeName(d._def.schema) : '<value>';
86
+ }
87
+ if (typeName === 'transform')
88
+ return '<value>';
89
+ if (typeName === 'pipe') {
90
+ // Zod 4.x pipe: _def.out is the output schema (for preprocess/transform)
91
+ return d._def?.out != null ? resolveZodTypeName(d._def.out) : '<value>';
92
+ }
93
+ if (typeName === 'ZodString' || typeName === 'string')
94
+ return '<string>';
95
+ if (typeName === 'ZodNumber' || typeName === 'number')
96
+ return '<number>';
97
+ if (typeName === 'ZodBoolean' || typeName === 'boolean')
98
+ return '<boolean>';
99
+ if (typeName === 'ZodEnum' || typeName === 'enum') {
100
+ // Zod 3.x: _def.values is string[]; Zod 4.x: _def.entries is Record<string, string>
101
+ const values = d._def?.values;
102
+ const entries = d._def?.entries;
103
+ if (values != null)
104
+ return values.join(' | ');
105
+ if (entries != null)
106
+ return Object.keys(entries).join(' | ');
107
+ return '<enum>';
108
+ }
109
+ if (typeName === 'ZodArray' || typeName === 'array')
110
+ return '<list>';
111
+ if (typeName === 'ZodObject' || typeName === 'object')
112
+ return '<object>';
113
+ return '<value>';
114
+ }
115
+ /** Exported for testing. */
116
+ export function buildYamlOutputSuffix(outputSchema) {
117
+ const fields = outputSchema != null ? extractSchemaFields(outputSchema) : [];
118
+ const fieldLines = fields.length > 0
119
+ ? fields.map((f) => ` ${f}`).join('\n')
120
+ : ' result: success\n # ... additional fields as specified in the task above';
121
+ return `
122
+
123
+ ---
124
+ IMPORTANT: When you have completed the task, output your structured result as a fenced YAML block at the END of your response. Use this exact format:
125
+
126
+ \`\`\`yaml
127
+ ${fieldLines}
128
+ \`\`\`
129
+
130
+ The YAML block MUST be the last thing in your output. Do not add any text after the closing fence.`;
131
+ }
132
+ // Minimum free system memory (bytes) required before spawning a new agent.
133
+ // When free memory is below this threshold, dispatches are held in the queue
134
+ // and retried periodically until memory recovers.
135
+ // Override with SUBSTRATE_MEMORY_THRESHOLD_MB env var (e.g. "128" for 128 MB).
136
+ //
137
+ // History: originally 256MB when concurrent test suite runs caused OOM.
138
+ // Lowered to 128MB in v0.8.5 — concurrent dispatch is now controlled by
139
+ // concurrency slots, and the test suite has been optimized. Only level 4
140
+ // (critical) kernel pressure hard-gates to 0.
141
+ const MIN_FREE_MEMORY_BYTES = (() => {
142
+ const envMB = process.env['SUBSTRATE_MEMORY_THRESHOLD_MB'];
143
+ if (envMB) {
144
+ const parsed = parseInt(envMB, 10);
145
+ if (!isNaN(parsed) && parsed >= 0)
146
+ return parsed * 1024 * 1024;
147
+ }
148
+ return 128 * 1024 * 1024; // 128 MB default
149
+ })();
150
+ // How often (ms) to re-check memory when the queue is held due to pressure
151
+ const MEMORY_PRESSURE_POLL_MS = 10_000;
152
+ // Maximum time (ms) to hold the dispatch queue due to memory pressure before
153
+ // forcing dispatch with a warning. Prevents indefinite stalls when the system
154
+ // is persistently pressured (e.g., macOS with many concurrent processes).
155
+ const MEMORY_PRESSURE_MAX_HOLD_MS = 300_000; // 5 minutes
156
+ // Tracks the most recently observed macOS kernel pressure level (1=normal,
157
+ // 2=warn, 4=critical). Updated inside getAvailableMemory() on darwin.
158
+ // Read by _isMemoryPressured() to include in the warn log (Story 23-8, AC3).
159
+ let _lastKnownPressureLevel = 0;
160
+ /**
161
+ * Get available system memory in bytes, accounting for platform differences.
162
+ *
163
+ * On macOS, the previous approach (free + inactive pages) dramatically
164
+ * overestimates availability because inactive pages may be compressed,
165
+ * swapped, or require I/O to reclaim. With heavy agent concurrency, this
166
+ * leads to spawning too many processes and triggering real memory pressure.
167
+ *
168
+ * New approach:
169
+ * 1. Check kern.memorystatus_vm_pressure_level — the kernel's own assessment.
170
+ * Level 4 (critical) = hard gate, return 0.
171
+ * Level 2 (warn) = halve the vm_stat estimate as a conservative signal.
172
+ * Level 1 (normal) = trust vm_stat as-is.
173
+ * Note: level 2 fires frequently on macOS when the compressor is active,
174
+ * even with gigabytes of reclaimable memory. Hard-gating at 2 caused
175
+ * false stalls on 24GB+ machines with >50% free RAM.
176
+ * 2. Use page calculation: free + inactive + purgeable + speculative.
177
+ * These categories are reclaimable by the OS — matching macOS Activity
178
+ * Monitor's definition of available memory (Physical - Used).
179
+ * Inactive pages ("Cached Files") are included because macOS reclaims
180
+ * them transparently. Excluding them caused substrate to see ~3 GB
181
+ * available when Activity Monitor showed ~8 GB on a 24 GB machine.
182
+ */
183
+ function getAvailableMemory(logger) {
184
+ if (platform() === 'darwin') {
185
+ let pressureLevel = 0;
186
+ try {
187
+ // Kernel memory pressure level (1=normal, 2=warn, 4=critical)
188
+ pressureLevel = parseInt(execSync('sysctl -n kern.memorystatus_vm_pressure_level', {
189
+ timeout: 1000,
190
+ encoding: 'utf-8',
191
+ }).trim(), 10);
192
+ _lastKnownPressureLevel = pressureLevel;
193
+ if (pressureLevel >= 4) {
194
+ logger.warn({ pressureLevel }, 'macOS kernel reports critical memory pressure');
195
+ return 0;
196
+ }
197
+ }
198
+ catch {
199
+ // sysctl not available — fall through to vm_stat
200
+ }
201
+ try {
202
+ const vmstat = execSync('vm_stat', { timeout: 2000, encoding: 'utf-8' });
203
+ const pageSize = parseInt(vmstat.match(/page size of (\d+)/)?.[1] ?? '4096', 10);
204
+ const free = parseInt(vmstat.match(/Pages free:\s+(\d+)/)?.[1] ?? '0', 10);
205
+ const inactive = parseInt(vmstat.match(/Pages inactive:\s+(\d+)/)?.[1] ?? '0', 10);
206
+ const purgeable = parseInt(vmstat.match(/Pages purgeable:\s+(\d+)/)?.[1] ?? '0', 10);
207
+ const speculative = parseInt(vmstat.match(/Pages speculative:\s+(\d+)/)?.[1] ?? '0', 10);
208
+ const available = (free + inactive + purgeable + speculative) * pageSize;
209
+ // At warn level (2), log but trust the vm_stat estimate as-is.
210
+ // Level 2 fires frequently on macOS when the compressor is active,
211
+ // even with gigabytes of reclaimable memory. Halving caused false
212
+ // stalls on 24GB machines during single-story runs.
213
+ // Only level 4 (critical) hard-gates to 0 (handled above).
214
+ if (pressureLevel >= 2) {
215
+ logger.debug({ pressureLevel, available }, 'macOS kernel reports memory pressure level 2 (warn) — using raw estimate');
216
+ }
217
+ return available;
218
+ }
219
+ catch {
220
+ return freemem();
221
+ }
222
+ }
223
+ // Non-darwin: pressure level is always 0
224
+ _lastKnownPressureLevel = 0;
225
+ return freemem();
226
+ }
227
+ // ---------------------------------------------------------------------------
228
+ // Mutable handle implementation
229
+ // ---------------------------------------------------------------------------
230
+ class MutableDispatchHandle {
231
+ id;
232
+ status;
233
+ _cancelFn;
234
+ constructor(id, initialStatus, cancelFn) {
235
+ this.id = id;
236
+ this.status = initialStatus;
237
+ this._cancelFn = cancelFn;
238
+ }
239
+ cancel() {
240
+ return this._cancelFn();
241
+ }
242
+ }
243
+ // ---------------------------------------------------------------------------
244
+ // DispatcherImpl
245
+ // ---------------------------------------------------------------------------
246
+ export class DispatcherImpl {
247
+ _eventBus;
248
+ _adapterRegistry;
249
+ _config;
250
+ _logger;
251
+ _normalizer;
252
+ _running = new Map();
253
+ _queue = [];
254
+ _shuttingDown = false;
255
+ _memoryPressureTimer = null;
256
+ _memoryPressureHoldStart = null;
257
+ constructor(eventBus, adapterRegistry, config, logger = console, normalizer = new AdapterOutputNormalizer()) {
258
+ this._eventBus = eventBus;
259
+ this._adapterRegistry = adapterRegistry;
260
+ this._config = config;
261
+ this._logger = logger;
262
+ this._normalizer = normalizer;
263
+ }
264
+ // ---------------------------------------------------------------------------
265
+ // Dispatcher interface
266
+ // ---------------------------------------------------------------------------
267
+ dispatch(request) {
268
+ if (this._shuttingDown) {
269
+ const handle = new MutableDispatchHandle(randomUUID(), 'queued', async () => { });
270
+ handle.status = 'failed';
271
+ return Object.assign(handle, {
272
+ result: Promise.reject(new DispatcherShuttingDownError()),
273
+ });
274
+ }
275
+ const id = randomUUID();
276
+ const resultPromise = new Promise((resolve, reject) => {
277
+ const typedResolve = resolve;
278
+ if (this._running.size < this._config.maxConcurrency && !this._isMemoryPressured()) {
279
+ // Reserve the running slot synchronously before async work begins
280
+ // This ensures getRunning() returns the correct count immediately
281
+ this._reserveSlot(id);
282
+ // Start the actual dispatch asynchronously
283
+ this._startDispatch(id, request, typedResolve).catch((err) => {
284
+ // If _startDispatch throws unexpectedly, clean up the slot and drain the queue
285
+ this._running.delete(id);
286
+ this._drainQueue();
287
+ reject(err);
288
+ });
289
+ }
290
+ else {
291
+ // Queue it
292
+ const queueHandle = new MutableDispatchHandle(id, 'queued', async () => {
293
+ this._removeFromQueue(id);
294
+ resolve({
295
+ id,
296
+ status: 'failed',
297
+ exitCode: -1,
298
+ output: '',
299
+ parsed: null,
300
+ parseError: 'Cancelled while queued',
301
+ durationMs: 0,
302
+ tokenEstimate: { input: 0, output: 0 },
303
+ });
304
+ });
305
+ this._queue.push({
306
+ id,
307
+ request: request,
308
+ handle: queueHandle,
309
+ resolve: typedResolve,
310
+ reject,
311
+ });
312
+ this._logger.debug({ id, queueLength: this._queue.length }, 'Dispatch queued');
313
+ }
314
+ });
315
+ // Determine initial status by checking whether the id was placed in the
316
+ // running map (via _reserveSlot) or in the queue. Reading both maps
317
+ // directly avoids any reliance on side-effect ordering from the async
318
+ // _startDispatch call above.
319
+ const initialStatus = this._running.has(id) ? 'running' : 'queued';
320
+ const cancelFn = async () => {
321
+ // If queued, remove from queue and reject the pending promise so the caller is not left hanging
322
+ const queueIdx = this._queue.findIndex((q) => q.id === id);
323
+ if (queueIdx !== -1) {
324
+ const [queued] = this._queue.splice(queueIdx, 1);
325
+ queued?.reject(new Error(`Dispatch ${id} was cancelled while queued`));
326
+ return;
327
+ }
328
+ // If running, kill the process
329
+ const entry = this._running.get(id);
330
+ if (entry !== undefined && entry.proc != null) {
331
+ try {
332
+ entry.proc.kill('SIGTERM');
333
+ }
334
+ catch {
335
+ // Process may already be dead
336
+ }
337
+ }
338
+ };
339
+ const handle = new MutableDispatchHandle(id, initialStatus, cancelFn);
340
+ return Object.assign(handle, { result: resultPromise });
341
+ }
342
+ getPending() {
343
+ return this._queue.length;
344
+ }
345
+ getRunning() {
346
+ return this._running.size;
347
+ }
348
+ async shutdown() {
349
+ this._shuttingDown = true;
350
+ this._stopMemoryPressureTimer();
351
+ this._logger.info({ running: this._running.size, queued: this._queue.length }, 'Dispatcher shutting down');
352
+ // Reject all queued dispatches
353
+ const queued = this._queue.splice(0, this._queue.length);
354
+ for (const entry of queued) {
355
+ entry.reject(new DispatcherShuttingDownError());
356
+ }
357
+ if (this._running.size === 0) {
358
+ return;
359
+ }
360
+ // Snapshot running dispatches
361
+ const runningEntries = Array.from(this._running.values());
362
+ // Send SIGTERM to all running processes, mark as terminated
363
+ for (const entry of runningEntries) {
364
+ entry.terminated = true;
365
+ if (entry.timeoutHandle !== null) {
366
+ clearTimeout(entry.timeoutHandle);
367
+ entry.timeoutHandle = null;
368
+ }
369
+ // Guard: proc may be null for placeholder slots reserved before spawn completes
370
+ if (entry.proc !== null && entry.proc !== undefined) {
371
+ try {
372
+ entry.proc.kill('SIGTERM');
373
+ }
374
+ catch {
375
+ // Process may already be dead
376
+ }
377
+ }
378
+ }
379
+ // Wait grace period
380
+ await new Promise((resolve) => setTimeout(resolve, SHUTDOWN_GRACE_MS));
381
+ // SIGKILL any still remaining
382
+ for (const entry of runningEntries) {
383
+ if (this._running.has(entry.id)) {
384
+ // Guard: proc may be null for placeholder slots
385
+ if (entry.proc !== null && entry.proc !== undefined) {
386
+ try {
387
+ entry.proc.kill('SIGKILL');
388
+ }
389
+ catch {
390
+ // Process may already be dead
391
+ }
392
+ }
393
+ }
394
+ }
395
+ // Wait for all to exit (they should have after SIGKILL)
396
+ // A maximum wait of 30 seconds is enforced to avoid hanging forever if
397
+ // SIGKILL fails (e.g., on Windows or in edge cases).
398
+ if (this._running.size > 0) {
399
+ await new Promise((resolve) => {
400
+ const startWait = Date.now();
401
+ const checkInterval = setInterval(() => {
402
+ if (this._running.size === 0 || Date.now() - startWait >= SHUTDOWN_MAX_WAIT_MS) {
403
+ clearInterval(checkInterval);
404
+ resolve();
405
+ }
406
+ }, 50);
407
+ });
408
+ }
409
+ this._logger.info('Dispatcher shutdown complete');
410
+ }
411
+ // ---------------------------------------------------------------------------
412
+ // Internal dispatch lifecycle
413
+ // ---------------------------------------------------------------------------
414
+ async _startDispatch(id, request, resolve) {
415
+ const { prompt, agent, taskType, timeout, outputSchema, workingDirectory, model, maxTurns, maxContextTokens, otlpEndpoint, storyKey, optimizationDirectives } = request;
416
+ // Resolve effective model: explicit request.model wins; then routing resolver; then undefined (adapter default)
417
+ let effectiveModel = model;
418
+ if (effectiveModel === undefined && this._config.routingResolver !== undefined) {
419
+ const resolution = this._config.routingResolver.resolveModel(taskType);
420
+ if (resolution !== null) {
421
+ effectiveModel = resolution.model;
422
+ // Emit routing:model-selected before agent:spawned
423
+ this._eventBus.emit('routing:model-selected', {
424
+ dispatchId: id,
425
+ taskType,
426
+ model: resolution.model,
427
+ phase: resolution.phase,
428
+ source: resolution.source,
429
+ });
430
+ this._logger.debug({ id, taskType, model: resolution.model, routingSource: resolution.source }, 'Routing resolved model');
431
+ }
432
+ else {
433
+ this._logger.debug({ id, taskType, routingSource: 'fallback' }, 'Routing returned null — using adapter default');
434
+ }
435
+ }
436
+ // Look up adapter
437
+ const adapter = this._adapterRegistry.get(agent);
438
+ if (adapter === undefined) {
439
+ this._logger.warn({ id, agent }, 'No adapter found for agent');
440
+ this._running.delete(id);
441
+ this._drainQueue();
442
+ resolve({
443
+ id,
444
+ status: 'failed',
445
+ exitCode: -1,
446
+ output: '',
447
+ parsed: null,
448
+ parseError: `No adapter registered for agent "${agent}"`,
449
+ durationMs: 0,
450
+ tokenEstimate: {
451
+ input: Math.ceil(prompt.length / CHARS_PER_TOKEN),
452
+ output: 0,
453
+ },
454
+ });
455
+ return;
456
+ }
457
+ // Build spawn command from adapter, using workingDirectory from request or process.cwd() as fallback
458
+ const worktreePath = workingDirectory ?? process.cwd();
459
+ const resolvedMaxTurns = maxTurns ?? DEFAULT_MAX_TURNS[taskType];
460
+ // For agents that require it (declared via capabilities), append a YAML output
461
+ // format reminder to the prompt. Claude Code follows methodology pack format
462
+ // instructions reliably; other agents need an explicit final nudge.
463
+ const capabilities = adapter.getCapabilities();
464
+ const effectivePrompt = capabilities.requiresYamlSuffix === true
465
+ ? prompt + buildYamlOutputSuffix(outputSchema)
466
+ : prompt;
467
+ const cmd = adapter.buildCommand(effectivePrompt, {
468
+ worktreePath,
469
+ billingMode: 'subscription',
470
+ ...(effectiveModel !== undefined ? { model: effectiveModel } : {}),
471
+ ...(resolvedMaxTurns !== undefined ? { maxTurns: resolvedMaxTurns } : {}),
472
+ ...(maxContextTokens !== undefined ? { maxContextTokens } : {}),
473
+ ...(otlpEndpoint !== undefined ? { otlpEndpoint } : {}),
474
+ ...(storyKey !== undefined ? { storyKey } : {}),
475
+ ...(optimizationDirectives !== undefined ? { optimizationDirectives } : {}),
476
+ taskType,
477
+ dispatchId: id,
478
+ });
479
+ // Resolve timeout, applying per-agent multiplier from adapter capabilities.
480
+ // Agents that are slower (e.g., Codex) declare timeoutMultiplier > 1.0
481
+ // so all timeouts scale automatically without per-project config overrides.
482
+ const baseTimeoutMs = timeout ??
483
+ this._config.defaultTimeouts[taskType] ??
484
+ DEFAULT_TIMEOUTS[taskType] ??
485
+ 300_000;
486
+ const timeoutMultiplier = capabilities.timeoutMultiplier ?? 1.0;
487
+ const timeoutMs = Math.round(baseTimeoutMs * timeoutMultiplier);
488
+ // Spawn the process
489
+ const env = { ...process.env };
490
+ // Cap Node.js heap per spawned agent to prevent memory exhaustion when
491
+ // multiple agents run vitest concurrently (each vitest fork inherits this).
492
+ const parentNodeOpts = env['NODE_OPTIONS'] ?? '';
493
+ if (!parentNodeOpts.includes('--max-old-space-size')) {
494
+ env['NODE_OPTIONS'] = `${parentNodeOpts} --max-old-space-size=512`.trim();
495
+ }
496
+ if (cmd.env !== undefined) {
497
+ Object.assign(env, cmd.env);
498
+ }
499
+ if (cmd.unsetEnvKeys !== undefined) {
500
+ for (const key of cmd.unsetEnvKeys) {
501
+ delete env[key];
502
+ }
503
+ }
504
+ const proc = spawn(cmd.binary, cmd.args, {
505
+ cwd: cmd.cwd,
506
+ env,
507
+ stdio: ['pipe', 'pipe', 'pipe'],
508
+ });
509
+ const startedAt = Date.now();
510
+ // Handle spawn errors — if the binary doesn't exist or can't be executed,
511
+ // the 'error' event fires. Without this handler, spawn failures are silent.
512
+ proc.on('error', (err) => {
513
+ this._logger.error({ id, binary: cmd.binary, error: err.message }, 'Process spawn failed');
514
+ });
515
+ // Write prompt to stdin and close — guard against EPIPE if process exits early
516
+ if (proc.stdin !== null) {
517
+ proc.stdin.on('error', (err) => {
518
+ // Suppress EPIPE errors that occur when the process exits before stdin is consumed
519
+ if (err.code !== 'EPIPE') {
520
+ this._logger.warn({ id, error: err.message }, 'stdin write error');
521
+ }
522
+ });
523
+ try {
524
+ proc.stdin.write(prompt);
525
+ proc.stdin.end();
526
+ }
527
+ catch {
528
+ // Process may have already exited — stdin write failures are non-fatal
529
+ }
530
+ }
531
+ // Set up output collection
532
+ const stdoutChunks = [];
533
+ const stderrChunks = [];
534
+ if (proc.stdout !== null) {
535
+ proc.stdout.on('data', (chunk) => {
536
+ stdoutChunks.push(chunk);
537
+ const dataStr = chunk.toString('utf-8');
538
+ this._eventBus.emit('agent:output', {
539
+ dispatchId: id,
540
+ data: dataStr,
541
+ });
542
+ });
543
+ }
544
+ if (proc.stderr !== null) {
545
+ proc.stderr.on('data', (chunk) => {
546
+ stderrChunks.push(chunk);
547
+ });
548
+ }
549
+ // Create the active dispatch entry
550
+ const activeDispatch = {
551
+ id,
552
+ agent,
553
+ taskType,
554
+ proc,
555
+ startedAt,
556
+ timeoutHandle: null,
557
+ stdoutChunks,
558
+ stderrChunks,
559
+ resolve,
560
+ timedOut: false,
561
+ terminated: false,
562
+ };
563
+ this._running.set(id, activeDispatch);
564
+ // Emit spawned event
565
+ this._eventBus.emit('agent:spawned', {
566
+ dispatchId: id,
567
+ agent,
568
+ taskType,
569
+ });
570
+ this._logger.debug({ id, agent, taskType, timeoutMs }, 'Agent dispatched');
571
+ // Set up timeout
572
+ activeDispatch.timeoutHandle = setTimeout(() => {
573
+ activeDispatch.timedOut = true;
574
+ proc.kill('SIGTERM');
575
+ const durationMs = Date.now() - startedAt;
576
+ const output = Buffer.concat(stdoutChunks).toString('utf-8');
577
+ const inputTokens = Math.ceil(prompt.length / CHARS_PER_TOKEN);
578
+ const outputTokens = Math.ceil(output.length / CHARS_PER_TOKEN);
579
+ this._eventBus.emit('agent:timeout', {
580
+ dispatchId: id,
581
+ timeoutMs,
582
+ });
583
+ this._logger.warn({ id, agent, taskType, timeoutMs }, 'Agent timed out');
584
+ this._running.delete(id);
585
+ this._drainQueue();
586
+ resolve({
587
+ id,
588
+ status: 'timeout',
589
+ exitCode: -1,
590
+ output,
591
+ parsed: null,
592
+ parseError: `Agent timed out after ${String(timeoutMs)}ms`,
593
+ durationMs,
594
+ tokenEstimate: { input: inputTokens, output: outputTokens },
595
+ });
596
+ }, timeoutMs);
597
+ // Set up close handler
598
+ proc.on('close', (exitCode) => {
599
+ const entry = this._running.get(id);
600
+ if (entry === undefined || entry.timedOut) {
601
+ return;
602
+ }
603
+ // If terminated (shutdown in progress), clean up the running slot so
604
+ // shutdown() can detect that all processes have exited.
605
+ if (entry.terminated) {
606
+ this._running.delete(id);
607
+ this._drainQueue();
608
+ return;
609
+ }
610
+ if (entry.timeoutHandle !== null) {
611
+ clearTimeout(entry.timeoutHandle);
612
+ entry.timeoutHandle = null;
613
+ }
614
+ const durationMs = Date.now() - startedAt;
615
+ const stdout = Buffer.concat(stdoutChunks).toString('utf-8');
616
+ const code = exitCode ?? 1;
617
+ const inputTokens = Math.ceil(prompt.length / CHARS_PER_TOKEN);
618
+ this._running.delete(id);
619
+ this._drainQueue();
620
+ if (code === 0) {
621
+ // Parse YAML output via AdapterOutputNormalizer (multi-strategy extraction)
622
+ const normalizeResult = this._normalizer.normalize(stdout, agent);
623
+ let parsed = null;
624
+ let parseError = null;
625
+ if (normalizeResult instanceof AdapterFormatError) {
626
+ // All normalization strategies exhausted — return an adapter format error result
627
+ const errMsg = [
628
+ normalizeResult.adapter_id,
629
+ normalizeResult.tried_strategies.join(','),
630
+ normalizeResult.raw_output_snippet,
631
+ ].join(' | ');
632
+ // Estimate output quality for observability (especially non-Claude backends)
633
+ const quality = estimateOutputQuality(stdout);
634
+ this._eventBus.emit('agent:completed', {
635
+ dispatchId: id,
636
+ exitCode: code,
637
+ output: stdout,
638
+ inputTokens,
639
+ outputTokens: Math.ceil(stdout.length / CHARS_PER_TOKEN),
640
+ qualityScore: quality.qualityScore,
641
+ agent,
642
+ ...(effectiveModel !== undefined && { model: effectiveModel }),
643
+ });
644
+ this._logger.warn({ id, agent, taskType, adapterError: true, snippet: normalizeResult.raw_output_snippet }, 'Adapter format error — all normalization strategies exhausted');
645
+ resolve({
646
+ id,
647
+ status: 'completed',
648
+ exitCode: code,
649
+ output: stdout,
650
+ parsed: null,
651
+ parseError: errMsg,
652
+ adapterError: true,
653
+ verdict: 'error',
654
+ errorMessage: errMsg,
655
+ durationMs,
656
+ tokenEstimate: { input: inputTokens, output: Math.ceil(stdout.length / CHARS_PER_TOKEN) },
657
+ });
658
+ return;
659
+ }
660
+ // Successful normalization — parse the extracted YAML
661
+ const parseResult = parseYamlResult(normalizeResult.yaml, outputSchema);
662
+ parsed = parseResult.parsed;
663
+ parseError = parseResult.error;
664
+ // Estimate output quality for observability (especially non-Claude backends)
665
+ const quality = estimateOutputQuality(stdout);
666
+ if (quality.hedgingCount > 0 || quality.qualityScore < 40) {
667
+ this._logger.warn({ id, agent, taskType, qualityScore: quality.qualityScore, hedging: quality.hedgingPhrases }, 'Low output quality detected');
668
+ }
669
+ this._eventBus.emit('agent:completed', {
670
+ dispatchId: id,
671
+ exitCode: code,
672
+ output: stdout,
673
+ inputTokens,
674
+ outputTokens: Math.ceil(stdout.length / CHARS_PER_TOKEN),
675
+ qualityScore: quality.qualityScore,
676
+ agent,
677
+ ...(effectiveModel !== undefined && { model: effectiveModel }),
678
+ });
679
+ this._logger.debug({ id, agent, taskType, durationMs, qualityScore: quality.qualityScore }, 'Agent completed');
680
+ resolve({
681
+ id,
682
+ status: 'completed',
683
+ exitCode: code,
684
+ output: stdout,
685
+ parsed,
686
+ parseError,
687
+ durationMs,
688
+ tokenEstimate: { input: inputTokens, output: Math.ceil(stdout.length / CHARS_PER_TOKEN) },
689
+ });
690
+ }
691
+ else {
692
+ const stderr = Buffer.concat(stderrChunks).toString('utf-8');
693
+ this._eventBus.emit('agent:failed', {
694
+ dispatchId: id,
695
+ error: stderr || `Process exited with code ${String(code)}`,
696
+ exitCode: code,
697
+ });
698
+ this._logger.warn({ id, agent, taskType, exitCode: code, durationMs, stderr: stderr.slice(0, 500) }, 'Agent failed');
699
+ // Combine stdout and stderr so callers have full context for failures
700
+ const combinedOutput = stderr ? `${stdout}\n--- stderr ---\n${stderr}` : stdout;
701
+ resolve({
702
+ id,
703
+ status: 'failed',
704
+ exitCode: code,
705
+ output: combinedOutput,
706
+ parsed: null,
707
+ parseError: `Agent exited with code ${String(code)}`,
708
+ durationMs,
709
+ tokenEstimate: { input: inputTokens, output: Math.ceil(combinedOutput.length / CHARS_PER_TOKEN) },
710
+ });
711
+ }
712
+ });
713
+ }
714
+ // ---------------------------------------------------------------------------
715
+ // Slot reservation
716
+ // ---------------------------------------------------------------------------
717
+ /**
718
+ * Synchronously reserve a slot in the running map with a placeholder entry.
719
+ * This ensures getRunning() reflects the correct count immediately, before
720
+ * the async _startDispatch has a chance to set up the real ActiveDispatch entry.
721
+ */
722
+ _reserveSlot(id) {
723
+ const placeholder = {
724
+ id,
725
+ agent: '',
726
+ taskType: '',
727
+ proc: null,
728
+ startedAt: Date.now(),
729
+ timeoutHandle: null,
730
+ stdoutChunks: [],
731
+ stderrChunks: [],
732
+ resolve: () => undefined,
733
+ timedOut: false,
734
+ terminated: false,
735
+ };
736
+ this._running.set(id, placeholder);
737
+ }
738
+ // ---------------------------------------------------------------------------
739
+ // Queue management
740
+ // ---------------------------------------------------------------------------
741
+ _drainQueue() {
742
+ if (this._queue.length === 0) {
743
+ this._stopMemoryPressureTimer();
744
+ return;
745
+ }
746
+ if (this._running.size >= this._config.maxConcurrency) {
747
+ return;
748
+ }
749
+ if (this._shuttingDown) {
750
+ return;
751
+ }
752
+ if (this._isMemoryPressured()) {
753
+ this._startMemoryPressureTimer();
754
+ return;
755
+ }
756
+ const next = this._queue.shift();
757
+ if (next === undefined) {
758
+ return;
759
+ }
760
+ next.handle.status = 'running';
761
+ this._logger.debug({ id: next.id, queueLength: this._queue.length }, 'Dequeued dispatch');
762
+ this._startDispatch(next.id, next.request, next.resolve).catch(next.reject);
763
+ }
764
+ _removeFromQueue(id) {
765
+ const idx = this._queue.findIndex((q) => q.id === id);
766
+ if (idx !== -1) {
767
+ this._queue.splice(idx, 1);
768
+ }
769
+ }
770
+ // ---------------------------------------------------------------------------
771
+ // Memory pressure management
772
+ // ---------------------------------------------------------------------------
773
+ _isMemoryPressured() {
774
+ const free = getAvailableMemory(this._logger);
775
+ if (free < MIN_FREE_MEMORY_BYTES) {
776
+ const now = Date.now();
777
+ if (this._memoryPressureHoldStart === null) {
778
+ this._memoryPressureHoldStart = now;
779
+ }
780
+ const holdDurationMs = now - this._memoryPressureHoldStart;
781
+ // If we've been holding for too long, force dispatch with a warning
782
+ if (holdDurationMs >= MEMORY_PRESSURE_MAX_HOLD_MS) {
783
+ this._logger.warn({
784
+ freeMB: Math.round(free / 1024 / 1024),
785
+ thresholdMB: Math.round(MIN_FREE_MEMORY_BYTES / 1024 / 1024),
786
+ pressureLevel: _lastKnownPressureLevel,
787
+ holdDurationMs,
788
+ }, 'Memory pressure hold exceeded max duration — forcing dispatch');
789
+ this._memoryPressureHoldStart = null;
790
+ return false;
791
+ }
792
+ // AC3 (Story 23-8): log freeMB, thresholdMB, and pressureLevel at warn
793
+ this._logger.warn({
794
+ freeMB: Math.round(free / 1024 / 1024),
795
+ thresholdMB: Math.round(MIN_FREE_MEMORY_BYTES / 1024 / 1024),
796
+ pressureLevel: _lastKnownPressureLevel,
797
+ holdDurationMs,
798
+ }, 'Memory pressure detected — holding dispatch queue');
799
+ return true;
800
+ }
801
+ // Memory cleared — reset hold timer
802
+ this._memoryPressureHoldStart = null;
803
+ return false;
804
+ }
805
+ /**
806
+ * Return current memory pressure state (Story 23-8, AC1).
807
+ *
808
+ * Used by the orchestrator before dispatching a story phase so it can
809
+ * implement backoff-retry without waiting on the dispatcher's internal queue.
810
+ */
811
+ getMemoryState() {
812
+ const free = getAvailableMemory(this._logger);
813
+ return {
814
+ freeMB: Math.round(free / 1024 / 1024),
815
+ thresholdMB: Math.round(MIN_FREE_MEMORY_BYTES / 1024 / 1024),
816
+ pressureLevel: _lastKnownPressureLevel,
817
+ isPressured: free < MIN_FREE_MEMORY_BYTES,
818
+ };
819
+ }
820
+ _startMemoryPressureTimer() {
821
+ if (this._memoryPressureTimer !== null)
822
+ return;
823
+ this._memoryPressureTimer = setInterval(() => {
824
+ this._drainQueue();
825
+ }, MEMORY_PRESSURE_POLL_MS);
826
+ this._memoryPressureTimer.unref();
827
+ }
828
+ _stopMemoryPressureTimer() {
829
+ if (this._memoryPressureTimer !== null) {
830
+ clearInterval(this._memoryPressureTimer);
831
+ this._memoryPressureTimer = null;
832
+ }
833
+ }
834
+ }
835
+ // ---------------------------------------------------------------------------
836
+ // createDispatcher factory
837
+ // ---------------------------------------------------------------------------
838
+ /**
839
+ * Create a new DispatcherImpl instance.
840
+ *
841
+ * @param options - Configuration options for the dispatcher
842
+ * @returns A new Dispatcher instance
843
+ */
844
+ export function createDispatcher(options) {
845
+ return new DispatcherImpl(options.eventBus, options.adapterRegistry, options.config, options.logger ?? console, options.normalizer ?? new AdapterOutputNormalizer());
846
+ }
847
+ //# sourceMappingURL=dispatcher-impl.js.map