@herdctl/core 0.0.1

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 (520) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test.log +219 -0
  3. package/.turbo/turbo-typecheck.log +4 -0
  4. package/coverage/base.css +224 -0
  5. package/coverage/block-navigation.js +87 -0
  6. package/coverage/coverage-final.json +51 -0
  7. package/coverage/favicon.png +0 -0
  8. package/coverage/index.html +251 -0
  9. package/coverage/prettify.css +1 -0
  10. package/coverage/prettify.js +2 -0
  11. package/coverage/sort-arrow-sprite.png +0 -0
  12. package/coverage/sorter.js +210 -0
  13. package/coverage/src/config/index.html +191 -0
  14. package/coverage/src/config/index.ts.html +442 -0
  15. package/coverage/src/config/interpolate.ts.html +652 -0
  16. package/coverage/src/config/loader.ts.html +1501 -0
  17. package/coverage/src/config/merge.ts.html +823 -0
  18. package/coverage/src/config/parser.ts.html +1213 -0
  19. package/coverage/src/config/schema.ts.html +1123 -0
  20. package/coverage/src/fleet-manager/errors.ts.html +2326 -0
  21. package/coverage/src/fleet-manager/event-types.ts.html +1219 -0
  22. package/coverage/src/fleet-manager/fleet-manager.ts.html +7030 -0
  23. package/coverage/src/fleet-manager/index.html +206 -0
  24. package/coverage/src/fleet-manager/index.ts.html +469 -0
  25. package/coverage/src/fleet-manager/job-manager.ts.html +2074 -0
  26. package/coverage/src/fleet-manager/job-queue.ts.html +2479 -0
  27. package/coverage/src/fleet-manager/types.ts.html +2602 -0
  28. package/coverage/src/index.html +116 -0
  29. package/coverage/src/index.ts.html +181 -0
  30. package/coverage/src/runner/errors.ts.html +1006 -0
  31. package/coverage/src/runner/index.html +191 -0
  32. package/coverage/src/runner/index.ts.html +256 -0
  33. package/coverage/src/runner/job-executor.ts.html +1429 -0
  34. package/coverage/src/runner/message-processor.ts.html +1150 -0
  35. package/coverage/src/runner/sdk-adapter.ts.html +658 -0
  36. package/coverage/src/runner/types.ts.html +559 -0
  37. package/coverage/src/scheduler/errors.ts.html +388 -0
  38. package/coverage/src/scheduler/index.html +206 -0
  39. package/coverage/src/scheduler/index.ts.html +244 -0
  40. package/coverage/src/scheduler/interval.ts.html +652 -0
  41. package/coverage/src/scheduler/schedule-runner.ts.html +1411 -0
  42. package/coverage/src/scheduler/schedule-state.ts.html +718 -0
  43. package/coverage/src/scheduler/scheduler.ts.html +1795 -0
  44. package/coverage/src/scheduler/types.ts.html +733 -0
  45. package/coverage/src/state/directory.ts.html +736 -0
  46. package/coverage/src/state/errors.ts.html +376 -0
  47. package/coverage/src/state/fleet-state.ts.html +937 -0
  48. package/coverage/src/state/index.html +221 -0
  49. package/coverage/src/state/index.ts.html +322 -0
  50. package/coverage/src/state/job-metadata.ts.html +1420 -0
  51. package/coverage/src/state/job-output.ts.html +1033 -0
  52. package/coverage/src/state/schemas/fleet-state.ts.html +445 -0
  53. package/coverage/src/state/schemas/index.html +176 -0
  54. package/coverage/src/state/schemas/index.ts.html +286 -0
  55. package/coverage/src/state/schemas/job-metadata.ts.html +628 -0
  56. package/coverage/src/state/schemas/job-output.ts.html +616 -0
  57. package/coverage/src/state/schemas/session-info.ts.html +361 -0
  58. package/coverage/src/state/session.ts.html +844 -0
  59. package/coverage/src/state/types.ts.html +262 -0
  60. package/coverage/src/state/utils/atomic.ts.html +748 -0
  61. package/coverage/src/state/utils/index.html +146 -0
  62. package/coverage/src/state/utils/index.ts.html +103 -0
  63. package/coverage/src/state/utils/reads.ts.html +1621 -0
  64. package/coverage/src/work-sources/adapters/github.ts.html +3583 -0
  65. package/coverage/src/work-sources/adapters/index.html +131 -0
  66. package/coverage/src/work-sources/adapters/index.ts.html +277 -0
  67. package/coverage/src/work-sources/errors.ts.html +298 -0
  68. package/coverage/src/work-sources/index.html +176 -0
  69. package/coverage/src/work-sources/index.ts.html +529 -0
  70. package/coverage/src/work-sources/manager.ts.html +1324 -0
  71. package/coverage/src/work-sources/registry.ts.html +619 -0
  72. package/coverage/src/work-sources/types.ts.html +568 -0
  73. package/dist/config/__tests__/agent.test.d.ts +2 -0
  74. package/dist/config/__tests__/agent.test.d.ts.map +1 -0
  75. package/dist/config/__tests__/agent.test.js +752 -0
  76. package/dist/config/__tests__/agent.test.js.map +1 -0
  77. package/dist/config/__tests__/interpolate.test.d.ts +2 -0
  78. package/dist/config/__tests__/interpolate.test.d.ts.map +1 -0
  79. package/dist/config/__tests__/interpolate.test.js +509 -0
  80. package/dist/config/__tests__/interpolate.test.js.map +1 -0
  81. package/dist/config/__tests__/loader.test.d.ts +2 -0
  82. package/dist/config/__tests__/loader.test.d.ts.map +1 -0
  83. package/dist/config/__tests__/loader.test.js +631 -0
  84. package/dist/config/__tests__/loader.test.js.map +1 -0
  85. package/dist/config/__tests__/merge.test.d.ts +2 -0
  86. package/dist/config/__tests__/merge.test.d.ts.map +1 -0
  87. package/dist/config/__tests__/merge.test.js +672 -0
  88. package/dist/config/__tests__/merge.test.js.map +1 -0
  89. package/dist/config/__tests__/parser.test.d.ts +2 -0
  90. package/dist/config/__tests__/parser.test.d.ts.map +1 -0
  91. package/dist/config/__tests__/parser.test.js +476 -0
  92. package/dist/config/__tests__/parser.test.js.map +1 -0
  93. package/dist/config/__tests__/schema.test.d.ts +2 -0
  94. package/dist/config/__tests__/schema.test.d.ts.map +1 -0
  95. package/dist/config/__tests__/schema.test.js +776 -0
  96. package/dist/config/__tests__/schema.test.js.map +1 -0
  97. package/dist/config/index.d.ts +11 -0
  98. package/dist/config/index.d.ts.map +1 -0
  99. package/dist/config/index.js +26 -0
  100. package/dist/config/index.js.map +1 -0
  101. package/dist/config/interpolate.d.ts +76 -0
  102. package/dist/config/interpolate.d.ts.map +1 -0
  103. package/dist/config/interpolate.js +143 -0
  104. package/dist/config/interpolate.js.map +1 -0
  105. package/dist/config/loader.d.ts +147 -0
  106. package/dist/config/loader.d.ts.map +1 -0
  107. package/dist/config/loader.js +336 -0
  108. package/dist/config/loader.js.map +1 -0
  109. package/dist/config/merge.d.ts +84 -0
  110. package/dist/config/merge.d.ts.map +1 -0
  111. package/dist/config/merge.js +138 -0
  112. package/dist/config/merge.js.map +1 -0
  113. package/dist/config/parser.d.ts +143 -0
  114. package/dist/config/parser.d.ts.map +1 -0
  115. package/dist/config/parser.js +316 -0
  116. package/dist/config/parser.js.map +1 -0
  117. package/dist/config/schema.d.ts +1906 -0
  118. package/dist/config/schema.d.ts.map +1 -0
  119. package/dist/config/schema.js +268 -0
  120. package/dist/config/schema.js.map +1 -0
  121. package/dist/fleet-manager/__tests__/coverage.test.d.ts +13 -0
  122. package/dist/fleet-manager/__tests__/coverage.test.d.ts.map +1 -0
  123. package/dist/fleet-manager/__tests__/coverage.test.js +2282 -0
  124. package/dist/fleet-manager/__tests__/coverage.test.js.map +1 -0
  125. package/dist/fleet-manager/__tests__/errors.test.d.ts +7 -0
  126. package/dist/fleet-manager/__tests__/errors.test.d.ts.map +1 -0
  127. package/dist/fleet-manager/__tests__/errors.test.js +557 -0
  128. package/dist/fleet-manager/__tests__/errors.test.js.map +1 -0
  129. package/dist/fleet-manager/__tests__/event-helpers.test.d.ts +7 -0
  130. package/dist/fleet-manager/__tests__/event-helpers.test.d.ts.map +1 -0
  131. package/dist/fleet-manager/__tests__/event-helpers.test.js +368 -0
  132. package/dist/fleet-manager/__tests__/event-helpers.test.js.map +1 -0
  133. package/dist/fleet-manager/__tests__/integration.test.d.ts +11 -0
  134. package/dist/fleet-manager/__tests__/integration.test.d.ts.map +1 -0
  135. package/dist/fleet-manager/__tests__/integration.test.js +949 -0
  136. package/dist/fleet-manager/__tests__/integration.test.js.map +1 -0
  137. package/dist/fleet-manager/__tests__/job-control.test.d.ts +7 -0
  138. package/dist/fleet-manager/__tests__/job-control.test.d.ts.map +1 -0
  139. package/dist/fleet-manager/__tests__/job-control.test.js +215 -0
  140. package/dist/fleet-manager/__tests__/job-control.test.js.map +1 -0
  141. package/dist/fleet-manager/__tests__/job-manager.test.d.ts +7 -0
  142. package/dist/fleet-manager/__tests__/job-manager.test.d.ts.map +1 -0
  143. package/dist/fleet-manager/__tests__/job-manager.test.js +659 -0
  144. package/dist/fleet-manager/__tests__/job-manager.test.js.map +1 -0
  145. package/dist/fleet-manager/__tests__/job-queue.test.d.ts +5 -0
  146. package/dist/fleet-manager/__tests__/job-queue.test.d.ts.map +1 -0
  147. package/dist/fleet-manager/__tests__/job-queue.test.js +315 -0
  148. package/dist/fleet-manager/__tests__/job-queue.test.js.map +1 -0
  149. package/dist/fleet-manager/__tests__/reload.test.d.ts +7 -0
  150. package/dist/fleet-manager/__tests__/reload.test.d.ts.map +1 -0
  151. package/dist/fleet-manager/__tests__/reload.test.js +609 -0
  152. package/dist/fleet-manager/__tests__/reload.test.js.map +1 -0
  153. package/dist/fleet-manager/__tests__/status-queries.test.d.ts +7 -0
  154. package/dist/fleet-manager/__tests__/status-queries.test.d.ts.map +1 -0
  155. package/dist/fleet-manager/__tests__/status-queries.test.js +488 -0
  156. package/dist/fleet-manager/__tests__/status-queries.test.js.map +1 -0
  157. package/dist/fleet-manager/__tests__/trigger.test.d.ts +7 -0
  158. package/dist/fleet-manager/__tests__/trigger.test.d.ts.map +1 -0
  159. package/dist/fleet-manager/__tests__/trigger.test.js +471 -0
  160. package/dist/fleet-manager/__tests__/trigger.test.js.map +1 -0
  161. package/dist/fleet-manager/errors.d.ts +407 -0
  162. package/dist/fleet-manager/errors.d.ts.map +1 -0
  163. package/dist/fleet-manager/errors.js +569 -0
  164. package/dist/fleet-manager/errors.js.map +1 -0
  165. package/dist/fleet-manager/event-types.d.ts +302 -0
  166. package/dist/fleet-manager/event-types.d.ts.map +1 -0
  167. package/dist/fleet-manager/event-types.js +9 -0
  168. package/dist/fleet-manager/event-types.js.map +1 -0
  169. package/dist/fleet-manager/fleet-manager.d.ts +699 -0
  170. package/dist/fleet-manager/fleet-manager.d.ts.map +1 -0
  171. package/dist/fleet-manager/fleet-manager.js +1906 -0
  172. package/dist/fleet-manager/fleet-manager.js.map +1 -0
  173. package/dist/fleet-manager/index.d.ts +17 -0
  174. package/dist/fleet-manager/index.d.ts.map +1 -0
  175. package/dist/fleet-manager/index.js +29 -0
  176. package/dist/fleet-manager/index.js.map +1 -0
  177. package/dist/fleet-manager/job-manager.d.ts +271 -0
  178. package/dist/fleet-manager/job-manager.d.ts.map +1 -0
  179. package/dist/fleet-manager/job-manager.js +443 -0
  180. package/dist/fleet-manager/job-manager.js.map +1 -0
  181. package/dist/fleet-manager/job-queue.d.ts +422 -0
  182. package/dist/fleet-manager/job-queue.d.ts.map +1 -0
  183. package/dist/fleet-manager/job-queue.js +448 -0
  184. package/dist/fleet-manager/job-queue.js.map +1 -0
  185. package/dist/fleet-manager/types.d.ts +680 -0
  186. package/dist/fleet-manager/types.d.ts.map +1 -0
  187. package/dist/fleet-manager/types.js +8 -0
  188. package/dist/fleet-manager/types.js.map +1 -0
  189. package/dist/index.d.ts +20 -0
  190. package/dist/index.d.ts.map +1 -0
  191. package/dist/index.js +26 -0
  192. package/dist/index.js.map +1 -0
  193. package/dist/runner/__tests__/errors.test.d.ts +2 -0
  194. package/dist/runner/__tests__/errors.test.d.ts.map +1 -0
  195. package/dist/runner/__tests__/errors.test.js +264 -0
  196. package/dist/runner/__tests__/errors.test.js.map +1 -0
  197. package/dist/runner/__tests__/job-executor.test.d.ts +2 -0
  198. package/dist/runner/__tests__/job-executor.test.d.ts.map +1 -0
  199. package/dist/runner/__tests__/job-executor.test.js +1345 -0
  200. package/dist/runner/__tests__/job-executor.test.js.map +1 -0
  201. package/dist/runner/__tests__/message-processor.test.d.ts +2 -0
  202. package/dist/runner/__tests__/message-processor.test.d.ts.map +1 -0
  203. package/dist/runner/__tests__/message-processor.test.js +768 -0
  204. package/dist/runner/__tests__/message-processor.test.js.map +1 -0
  205. package/dist/runner/__tests__/sdk-adapter.test.d.ts +2 -0
  206. package/dist/runner/__tests__/sdk-adapter.test.d.ts.map +1 -0
  207. package/dist/runner/__tests__/sdk-adapter.test.js +554 -0
  208. package/dist/runner/__tests__/sdk-adapter.test.js.map +1 -0
  209. package/dist/runner/errors.d.ts +121 -0
  210. package/dist/runner/errors.d.ts.map +1 -0
  211. package/dist/runner/errors.js +212 -0
  212. package/dist/runner/errors.js.map +1 -0
  213. package/dist/runner/index.d.ts +12 -0
  214. package/dist/runner/index.d.ts.map +1 -0
  215. package/dist/runner/index.js +15 -0
  216. package/dist/runner/index.js.map +1 -0
  217. package/dist/runner/job-executor.d.ts +98 -0
  218. package/dist/runner/job-executor.d.ts.map +1 -0
  219. package/dist/runner/job-executor.js +333 -0
  220. package/dist/runner/job-executor.js.map +1 -0
  221. package/dist/runner/message-processor.d.ts +45 -0
  222. package/dist/runner/message-processor.d.ts.map +1 -0
  223. package/dist/runner/message-processor.js +294 -0
  224. package/dist/runner/message-processor.js.map +1 -0
  225. package/dist/runner/sdk-adapter.d.ts +60 -0
  226. package/dist/runner/sdk-adapter.d.ts.map +1 -0
  227. package/dist/runner/sdk-adapter.js +138 -0
  228. package/dist/runner/sdk-adapter.js.map +1 -0
  229. package/dist/runner/types.d.ts +135 -0
  230. package/dist/runner/types.d.ts.map +1 -0
  231. package/dist/runner/types.js +7 -0
  232. package/dist/runner/types.js.map +1 -0
  233. package/dist/scheduler/__tests__/errors.test.d.ts +2 -0
  234. package/dist/scheduler/__tests__/errors.test.d.ts.map +1 -0
  235. package/dist/scheduler/__tests__/errors.test.js +101 -0
  236. package/dist/scheduler/__tests__/errors.test.js.map +1 -0
  237. package/dist/scheduler/__tests__/interval.test.d.ts +2 -0
  238. package/dist/scheduler/__tests__/interval.test.d.ts.map +1 -0
  239. package/dist/scheduler/__tests__/interval.test.js +419 -0
  240. package/dist/scheduler/__tests__/interval.test.js.map +1 -0
  241. package/dist/scheduler/__tests__/schedule-runner.test.d.ts +2 -0
  242. package/dist/scheduler/__tests__/schedule-runner.test.d.ts.map +1 -0
  243. package/dist/scheduler/__tests__/schedule-runner.test.js +634 -0
  244. package/dist/scheduler/__tests__/schedule-runner.test.js.map +1 -0
  245. package/dist/scheduler/__tests__/schedule-state.test.d.ts +2 -0
  246. package/dist/scheduler/__tests__/schedule-state.test.d.ts.map +1 -0
  247. package/dist/scheduler/__tests__/schedule-state.test.js +572 -0
  248. package/dist/scheduler/__tests__/schedule-state.test.js.map +1 -0
  249. package/dist/scheduler/__tests__/scheduler.test.d.ts +2 -0
  250. package/dist/scheduler/__tests__/scheduler.test.d.ts.map +1 -0
  251. package/dist/scheduler/__tests__/scheduler.test.js +987 -0
  252. package/dist/scheduler/__tests__/scheduler.test.js.map +1 -0
  253. package/dist/scheduler/errors.d.ts +61 -0
  254. package/dist/scheduler/errors.d.ts.map +1 -0
  255. package/dist/scheduler/errors.js +81 -0
  256. package/dist/scheduler/errors.js.map +1 -0
  257. package/dist/scheduler/index.d.ts +13 -0
  258. package/dist/scheduler/index.d.ts.map +1 -0
  259. package/dist/scheduler/index.js +17 -0
  260. package/dist/scheduler/index.js.map +1 -0
  261. package/dist/scheduler/interval.d.ts +64 -0
  262. package/dist/scheduler/interval.d.ts.map +1 -0
  263. package/dist/scheduler/interval.js +139 -0
  264. package/dist/scheduler/interval.js.map +1 -0
  265. package/dist/scheduler/schedule-runner.d.ts +149 -0
  266. package/dist/scheduler/schedule-runner.d.ts.map +1 -0
  267. package/dist/scheduler/schedule-runner.js +277 -0
  268. package/dist/scheduler/schedule-runner.js.map +1 -0
  269. package/dist/scheduler/schedule-state.d.ts +105 -0
  270. package/dist/scheduler/schedule-state.d.ts.map +1 -0
  271. package/dist/scheduler/schedule-state.js +151 -0
  272. package/dist/scheduler/schedule-state.js.map +1 -0
  273. package/dist/scheduler/scheduler.d.ts +138 -0
  274. package/dist/scheduler/scheduler.d.ts.map +1 -0
  275. package/dist/scheduler/scheduler.js +423 -0
  276. package/dist/scheduler/scheduler.js.map +1 -0
  277. package/dist/scheduler/types.d.ts +160 -0
  278. package/dist/scheduler/types.d.ts.map +1 -0
  279. package/dist/scheduler/types.js +8 -0
  280. package/dist/scheduler/types.js.map +1 -0
  281. package/dist/state/__tests__/directory.test.d.ts +2 -0
  282. package/dist/state/__tests__/directory.test.d.ts.map +1 -0
  283. package/dist/state/__tests__/directory.test.js +414 -0
  284. package/dist/state/__tests__/directory.test.js.map +1 -0
  285. package/dist/state/__tests__/fleet-state.test.d.ts +2 -0
  286. package/dist/state/__tests__/fleet-state.test.d.ts.map +1 -0
  287. package/dist/state/__tests__/fleet-state.test.js +696 -0
  288. package/dist/state/__tests__/fleet-state.test.js.map +1 -0
  289. package/dist/state/__tests__/job-metadata-schema.test.d.ts +2 -0
  290. package/dist/state/__tests__/job-metadata-schema.test.d.ts.map +1 -0
  291. package/dist/state/__tests__/job-metadata-schema.test.js +329 -0
  292. package/dist/state/__tests__/job-metadata-schema.test.js.map +1 -0
  293. package/dist/state/__tests__/job-metadata.test.d.ts +2 -0
  294. package/dist/state/__tests__/job-metadata.test.d.ts.map +1 -0
  295. package/dist/state/__tests__/job-metadata.test.js +667 -0
  296. package/dist/state/__tests__/job-metadata.test.js.map +1 -0
  297. package/dist/state/__tests__/job-output.test.d.ts +2 -0
  298. package/dist/state/__tests__/job-output.test.d.ts.map +1 -0
  299. package/dist/state/__tests__/job-output.test.js +672 -0
  300. package/dist/state/__tests__/job-output.test.js.map +1 -0
  301. package/dist/state/__tests__/session-schema.test.d.ts +2 -0
  302. package/dist/state/__tests__/session-schema.test.d.ts.map +1 -0
  303. package/dist/state/__tests__/session-schema.test.js +323 -0
  304. package/dist/state/__tests__/session-schema.test.js.map +1 -0
  305. package/dist/state/__tests__/session.test.d.ts +2 -0
  306. package/dist/state/__tests__/session.test.d.ts.map +1 -0
  307. package/dist/state/__tests__/session.test.js +468 -0
  308. package/dist/state/__tests__/session.test.js.map +1 -0
  309. package/dist/state/directory.d.ts +42 -0
  310. package/dist/state/directory.d.ts.map +1 -0
  311. package/dist/state/directory.js +170 -0
  312. package/dist/state/directory.js.map +1 -0
  313. package/dist/state/errors.d.ts +44 -0
  314. package/dist/state/errors.d.ts.map +1 -0
  315. package/dist/state/errors.js +82 -0
  316. package/dist/state/errors.js.map +1 -0
  317. package/dist/state/fleet-state.d.ts +126 -0
  318. package/dist/state/fleet-state.d.ts.map +1 -0
  319. package/dist/state/fleet-state.js +196 -0
  320. package/dist/state/fleet-state.js.map +1 -0
  321. package/dist/state/index.d.ts +21 -0
  322. package/dist/state/index.d.ts.map +1 -0
  323. package/dist/state/index.js +30 -0
  324. package/dist/state/index.js.map +1 -0
  325. package/dist/state/job-metadata.d.ts +151 -0
  326. package/dist/state/job-metadata.d.ts.map +1 -0
  327. package/dist/state/job-metadata.js +287 -0
  328. package/dist/state/job-metadata.js.map +1 -0
  329. package/dist/state/job-output.d.ts +116 -0
  330. package/dist/state/job-output.d.ts.map +1 -0
  331. package/dist/state/job-output.js +218 -0
  332. package/dist/state/job-output.js.map +1 -0
  333. package/dist/state/schemas/__tests__/job-output.test.d.ts +2 -0
  334. package/dist/state/schemas/__tests__/job-output.test.d.ts.map +1 -0
  335. package/dist/state/schemas/__tests__/job-output.test.js +279 -0
  336. package/dist/state/schemas/__tests__/job-output.test.js.map +1 -0
  337. package/dist/state/schemas/fleet-state.d.ts +249 -0
  338. package/dist/state/schemas/fleet-state.d.ts.map +1 -0
  339. package/dist/state/schemas/fleet-state.js +97 -0
  340. package/dist/state/schemas/fleet-state.js.map +1 -0
  341. package/dist/state/schemas/index.d.ts +10 -0
  342. package/dist/state/schemas/index.d.ts.map +1 -0
  343. package/dist/state/schemas/index.js +10 -0
  344. package/dist/state/schemas/index.js.map +1 -0
  345. package/dist/state/schemas/job-metadata.d.ts +118 -0
  346. package/dist/state/schemas/job-metadata.d.ts.map +1 -0
  347. package/dist/state/schemas/job-metadata.js +123 -0
  348. package/dist/state/schemas/job-metadata.js.map +1 -0
  349. package/dist/state/schemas/job-output.d.ts +291 -0
  350. package/dist/state/schemas/job-output.d.ts.map +1 -0
  351. package/dist/state/schemas/job-output.js +132 -0
  352. package/dist/state/schemas/job-output.js.map +1 -0
  353. package/dist/state/schemas/session-info.d.ts +65 -0
  354. package/dist/state/schemas/session-info.d.ts.map +1 -0
  355. package/dist/state/schemas/session-info.js +58 -0
  356. package/dist/state/schemas/session-info.js.map +1 -0
  357. package/dist/state/session.d.ts +92 -0
  358. package/dist/state/session.d.ts.map +1 -0
  359. package/dist/state/session.js +173 -0
  360. package/dist/state/session.js.map +1 -0
  361. package/dist/state/types.d.ts +54 -0
  362. package/dist/state/types.d.ts.map +1 -0
  363. package/dist/state/types.js +18 -0
  364. package/dist/state/types.js.map +1 -0
  365. package/dist/state/utils/__tests__/atomic.test.d.ts +2 -0
  366. package/dist/state/utils/__tests__/atomic.test.d.ts.map +1 -0
  367. package/dist/state/utils/__tests__/atomic.test.js +537 -0
  368. package/dist/state/utils/__tests__/atomic.test.js.map +1 -0
  369. package/dist/state/utils/__tests__/reads.test.d.ts +2 -0
  370. package/dist/state/utils/__tests__/reads.test.d.ts.map +1 -0
  371. package/dist/state/utils/__tests__/reads.test.js +792 -0
  372. package/dist/state/utils/__tests__/reads.test.js.map +1 -0
  373. package/dist/state/utils/atomic.d.ts +89 -0
  374. package/dist/state/utils/atomic.d.ts.map +1 -0
  375. package/dist/state/utils/atomic.js +157 -0
  376. package/dist/state/utils/atomic.js.map +1 -0
  377. package/dist/state/utils/index.d.ts +6 -0
  378. package/dist/state/utils/index.d.ts.map +1 -0
  379. package/dist/state/utils/index.js +6 -0
  380. package/dist/state/utils/index.js.map +1 -0
  381. package/dist/state/utils/reads.d.ts +196 -0
  382. package/dist/state/utils/reads.d.ts.map +1 -0
  383. package/dist/state/utils/reads.js +346 -0
  384. package/dist/state/utils/reads.js.map +1 -0
  385. package/dist/work-sources/__tests__/github.test.d.ts +2 -0
  386. package/dist/work-sources/__tests__/github.test.d.ts.map +1 -0
  387. package/dist/work-sources/__tests__/github.test.js +1334 -0
  388. package/dist/work-sources/__tests__/github.test.js.map +1 -0
  389. package/dist/work-sources/__tests__/manager.test.d.ts +2 -0
  390. package/dist/work-sources/__tests__/manager.test.d.ts.map +1 -0
  391. package/dist/work-sources/__tests__/manager.test.js +424 -0
  392. package/dist/work-sources/__tests__/manager.test.js.map +1 -0
  393. package/dist/work-sources/__tests__/registry.test.d.ts +2 -0
  394. package/dist/work-sources/__tests__/registry.test.d.ts.map +1 -0
  395. package/dist/work-sources/__tests__/registry.test.js +381 -0
  396. package/dist/work-sources/__tests__/registry.test.js.map +1 -0
  397. package/dist/work-sources/__tests__/types.test.d.ts +2 -0
  398. package/dist/work-sources/__tests__/types.test.d.ts.map +1 -0
  399. package/dist/work-sources/__tests__/types.test.js +406 -0
  400. package/dist/work-sources/__tests__/types.test.js.map +1 -0
  401. package/dist/work-sources/adapters/github.d.ts +290 -0
  402. package/dist/work-sources/adapters/github.d.ts.map +1 -0
  403. package/dist/work-sources/adapters/github.js +803 -0
  404. package/dist/work-sources/adapters/github.js.map +1 -0
  405. package/dist/work-sources/adapters/index.d.ts +10 -0
  406. package/dist/work-sources/adapters/index.d.ts.map +1 -0
  407. package/dist/work-sources/adapters/index.js +31 -0
  408. package/dist/work-sources/adapters/index.js.map +1 -0
  409. package/dist/work-sources/errors.d.ts +40 -0
  410. package/dist/work-sources/errors.d.ts.map +1 -0
  411. package/dist/work-sources/errors.js +54 -0
  412. package/dist/work-sources/errors.js.map +1 -0
  413. package/dist/work-sources/index.d.ts +105 -0
  414. package/dist/work-sources/index.d.ts.map +1 -0
  415. package/dist/work-sources/index.js +24 -0
  416. package/dist/work-sources/index.js.map +1 -0
  417. package/dist/work-sources/manager.d.ts +370 -0
  418. package/dist/work-sources/manager.d.ts.map +1 -0
  419. package/dist/work-sources/manager.js +61 -0
  420. package/dist/work-sources/manager.js.map +1 -0
  421. package/dist/work-sources/registry.d.ts +128 -0
  422. package/dist/work-sources/registry.d.ts.map +1 -0
  423. package/dist/work-sources/registry.js +132 -0
  424. package/dist/work-sources/registry.js.map +1 -0
  425. package/dist/work-sources/types.d.ts +127 -0
  426. package/dist/work-sources/types.d.ts.map +1 -0
  427. package/dist/work-sources/types.js +8 -0
  428. package/dist/work-sources/types.js.map +1 -0
  429. package/package.json +23 -0
  430. package/src/config/__tests__/agent.test.ts +864 -0
  431. package/src/config/__tests__/interpolate.test.ts +644 -0
  432. package/src/config/__tests__/loader.test.ts +784 -0
  433. package/src/config/__tests__/merge.test.ts +751 -0
  434. package/src/config/__tests__/parser.test.ts +533 -0
  435. package/src/config/__tests__/schema.test.ts +873 -0
  436. package/src/config/index.ts +119 -0
  437. package/src/config/interpolate.ts +189 -0
  438. package/src/config/loader.ts +472 -0
  439. package/src/config/merge.ts +246 -0
  440. package/src/config/parser.ts +376 -0
  441. package/src/config/schema.ts +346 -0
  442. package/src/fleet-manager/__tests__/coverage.test.ts +2869 -0
  443. package/src/fleet-manager/__tests__/errors.test.ts +660 -0
  444. package/src/fleet-manager/__tests__/event-helpers.test.ts +448 -0
  445. package/src/fleet-manager/__tests__/integration.test.ts +1209 -0
  446. package/src/fleet-manager/__tests__/job-control.test.ts +283 -0
  447. package/src/fleet-manager/__tests__/job-manager.test.ts +869 -0
  448. package/src/fleet-manager/__tests__/job-queue.test.ts +401 -0
  449. package/src/fleet-manager/__tests__/reload.test.ts +751 -0
  450. package/src/fleet-manager/__tests__/status-queries.test.ts +595 -0
  451. package/src/fleet-manager/__tests__/trigger.test.ts +601 -0
  452. package/src/fleet-manager/errors.ts +747 -0
  453. package/src/fleet-manager/event-types.ts +378 -0
  454. package/src/fleet-manager/fleet-manager.ts +2315 -0
  455. package/src/fleet-manager/index.ts +128 -0
  456. package/src/fleet-manager/job-manager.ts +663 -0
  457. package/src/fleet-manager/job-queue.ts +798 -0
  458. package/src/fleet-manager/types.ts +839 -0
  459. package/src/index.ts +32 -0
  460. package/src/runner/__tests__/errors.test.ts +382 -0
  461. package/src/runner/__tests__/job-executor.test.ts +1708 -0
  462. package/src/runner/__tests__/message-processor.test.ts +960 -0
  463. package/src/runner/__tests__/sdk-adapter.test.ts +626 -0
  464. package/src/runner/errors.ts +307 -0
  465. package/src/runner/index.ts +57 -0
  466. package/src/runner/job-executor.ts +448 -0
  467. package/src/runner/message-processor.ts +355 -0
  468. package/src/runner/sdk-adapter.ts +191 -0
  469. package/src/runner/types.ts +158 -0
  470. package/src/scheduler/__tests__/errors.test.ts +159 -0
  471. package/src/scheduler/__tests__/interval.test.ts +515 -0
  472. package/src/scheduler/__tests__/schedule-runner.test.ts +798 -0
  473. package/src/scheduler/__tests__/schedule-state.test.ts +671 -0
  474. package/src/scheduler/__tests__/scheduler.test.ts +1280 -0
  475. package/src/scheduler/errors.ts +101 -0
  476. package/src/scheduler/index.ts +53 -0
  477. package/src/scheduler/interval.ts +189 -0
  478. package/src/scheduler/schedule-runner.ts +442 -0
  479. package/src/scheduler/schedule-state.ts +211 -0
  480. package/src/scheduler/scheduler.ts +570 -0
  481. package/src/scheduler/types.ts +216 -0
  482. package/src/state/__tests__/directory.test.ts +595 -0
  483. package/src/state/__tests__/fleet-state.test.ts +868 -0
  484. package/src/state/__tests__/job-metadata-schema.test.ts +414 -0
  485. package/src/state/__tests__/job-metadata.test.ts +831 -0
  486. package/src/state/__tests__/job-output.test.ts +856 -0
  487. package/src/state/__tests__/session-schema.test.ts +378 -0
  488. package/src/state/__tests__/session.test.ts +604 -0
  489. package/src/state/directory.ts +217 -0
  490. package/src/state/errors.ts +97 -0
  491. package/src/state/fleet-state.ts +284 -0
  492. package/src/state/index.ts +79 -0
  493. package/src/state/job-metadata.ts +445 -0
  494. package/src/state/job-output.ts +316 -0
  495. package/src/state/schemas/__tests__/job-output.test.ts +338 -0
  496. package/src/state/schemas/fleet-state.ts +120 -0
  497. package/src/state/schemas/index.ts +67 -0
  498. package/src/state/schemas/job-metadata.ts +181 -0
  499. package/src/state/schemas/job-output.ts +177 -0
  500. package/src/state/schemas/session-info.ts +92 -0
  501. package/src/state/session.ts +253 -0
  502. package/src/state/types.ts +59 -0
  503. package/src/state/utils/__tests__/atomic.test.ts +723 -0
  504. package/src/state/utils/__tests__/reads.test.ts +1071 -0
  505. package/src/state/utils/atomic.ts +221 -0
  506. package/src/state/utils/index.ts +6 -0
  507. package/src/state/utils/reads.ts +512 -0
  508. package/src/work-sources/__tests__/github.test.ts +1800 -0
  509. package/src/work-sources/__tests__/manager.test.ts +529 -0
  510. package/src/work-sources/__tests__/registry.test.ts +477 -0
  511. package/src/work-sources/__tests__/types.test.ts +479 -0
  512. package/src/work-sources/adapters/github.ts +1166 -0
  513. package/src/work-sources/adapters/index.ts +64 -0
  514. package/src/work-sources/errors.ts +71 -0
  515. package/src/work-sources/index.ts +148 -0
  516. package/src/work-sources/manager.ts +413 -0
  517. package/src/work-sources/registry.ts +178 -0
  518. package/src/work-sources/types.ts +161 -0
  519. package/tsconfig.json +9 -0
  520. package/vitest.config.ts +19 -0
@@ -0,0 +1,472 @@
1
+ /**
2
+ * Configuration loader for herdctl
3
+ *
4
+ * Provides a single entry point to load and resolve all configuration:
5
+ * - Auto-discovers herdctl.yaml by walking up the directory tree
6
+ * - Loads fleet config and all referenced agent configs
7
+ * - Merges fleet defaults into agent configs
8
+ * - Interpolates environment variables
9
+ * - Validates the entire configuration tree
10
+ */
11
+
12
+ import { readFile, access } from "node:fs/promises";
13
+ import { dirname, join, resolve } from "node:path";
14
+ import { parse as parseYaml, YAMLParseError } from "yaml";
15
+ import { ZodError } from "zod";
16
+ import {
17
+ FleetConfigSchema,
18
+ AgentConfigSchema,
19
+ type FleetConfig,
20
+ type AgentConfig,
21
+ } from "./schema.js";
22
+ import { ConfigError, FileReadError, SchemaValidationError } from "./parser.js";
23
+ import { mergeAgentConfig, type ExtendedDefaults } from "./merge.js";
24
+ import { interpolateConfig, type InterpolateOptions } from "./interpolate.js";
25
+
26
+ // =============================================================================
27
+ // Constants
28
+ // =============================================================================
29
+
30
+ /**
31
+ * Default config file names to search for
32
+ */
33
+ export const CONFIG_FILE_NAMES = ["herdctl.yaml", "herdctl.yml"] as const;
34
+
35
+ // =============================================================================
36
+ // Error Classes
37
+ // =============================================================================
38
+
39
+ /**
40
+ * Error thrown when no configuration file is found
41
+ */
42
+ export class ConfigNotFoundError extends ConfigError {
43
+ public readonly searchedPaths: string[];
44
+ public readonly startDirectory: string;
45
+
46
+ constructor(startDirectory: string, searchedPaths: string[]) {
47
+ super(
48
+ `No herdctl configuration file found. ` +
49
+ `Searched from '${startDirectory}' up to filesystem root. ` +
50
+ `Create a herdctl.yaml file to get started.`
51
+ );
52
+ this.name = "ConfigNotFoundError";
53
+ this.searchedPaths = searchedPaths;
54
+ this.startDirectory = startDirectory;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Error thrown when agent loading fails
60
+ */
61
+ export class AgentLoadError extends ConfigError {
62
+ public readonly agentPath: string;
63
+ public readonly agentName?: string;
64
+
65
+ constructor(agentPath: string, cause: Error, agentName?: string) {
66
+ const nameInfo = agentName ? ` (${agentName})` : "";
67
+ super(`Failed to load agent '${agentPath}'${nameInfo}: ${cause.message}`);
68
+ this.name = "AgentLoadError";
69
+ this.agentPath = agentPath;
70
+ this.agentName = agentName;
71
+ this.cause = cause;
72
+ }
73
+ }
74
+
75
+ // =============================================================================
76
+ // Types
77
+ // =============================================================================
78
+
79
+ /**
80
+ * A fully resolved agent configuration with computed properties
81
+ */
82
+ export interface ResolvedAgent extends AgentConfig {
83
+ /**
84
+ * The absolute path to the agent configuration file
85
+ */
86
+ configPath: string;
87
+ }
88
+
89
+ /**
90
+ * A fully resolved configuration with all agents loaded and merged
91
+ */
92
+ export interface ResolvedConfig {
93
+ /**
94
+ * The parsed and validated fleet configuration
95
+ */
96
+ fleet: FleetConfig;
97
+
98
+ /**
99
+ * All agent configurations, fully resolved with defaults merged
100
+ */
101
+ agents: ResolvedAgent[];
102
+
103
+ /**
104
+ * The absolute path to the fleet configuration file
105
+ */
106
+ configPath: string;
107
+
108
+ /**
109
+ * The directory containing the fleet configuration
110
+ */
111
+ configDir: string;
112
+ }
113
+
114
+ /**
115
+ * Options for the loadConfig function
116
+ */
117
+ export interface LoadConfigOptions {
118
+ /**
119
+ * Custom environment variables for interpolation
120
+ * Defaults to process.env
121
+ */
122
+ env?: Record<string, string | undefined>;
123
+
124
+ /**
125
+ * Whether to interpolate environment variables
126
+ * Defaults to true
127
+ */
128
+ interpolate?: boolean;
129
+
130
+ /**
131
+ * Whether to merge fleet defaults into agent configs
132
+ * Defaults to true
133
+ */
134
+ mergeDefaults?: boolean;
135
+ }
136
+
137
+ // =============================================================================
138
+ // File Discovery
139
+ // =============================================================================
140
+
141
+ /**
142
+ * Check if a file exists and is accessible
143
+ */
144
+ async function fileExists(filePath: string): Promise<boolean> {
145
+ try {
146
+ await access(filePath);
147
+ return true;
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Find a configuration file by walking up the directory tree
155
+ *
156
+ * Searches for herdctl.yaml or herdctl.yml starting from the given directory
157
+ * and walking up to the filesystem root (similar to how git finds .git).
158
+ *
159
+ * @param startDir - The directory to start searching from
160
+ * @returns The absolute path to the config file, or null if not found
161
+ */
162
+ export async function findConfigFile(
163
+ startDir: string
164
+ ): Promise<{ path: string; searchedPaths: string[] } | null> {
165
+ const searchedPaths: string[] = [];
166
+ let currentDir = resolve(startDir);
167
+
168
+ while (true) {
169
+ // Check for each possible config file name
170
+ for (const fileName of CONFIG_FILE_NAMES) {
171
+ const configPath = join(currentDir, fileName);
172
+ searchedPaths.push(configPath);
173
+
174
+ if (await fileExists(configPath)) {
175
+ return { path: configPath, searchedPaths };
176
+ }
177
+ }
178
+
179
+ // Move up to parent directory
180
+ const parentDir = dirname(currentDir);
181
+
182
+ // Stop if we've reached the root
183
+ if (parentDir === currentDir) {
184
+ return null;
185
+ }
186
+
187
+ currentDir = parentDir;
188
+ }
189
+ }
190
+
191
+ // =============================================================================
192
+ // Internal Parsing Functions
193
+ // =============================================================================
194
+
195
+ /**
196
+ * Parse and validate fleet config from YAML content
197
+ */
198
+ function parseFleetYaml(content: string, filePath: string): FleetConfig {
199
+ let rawConfig: unknown;
200
+ try {
201
+ rawConfig = parseYaml(content);
202
+ } catch (error) {
203
+ if (error instanceof YAMLParseError) {
204
+ const position = error.linePos?.[0];
205
+ const locationInfo = position
206
+ ? ` at line ${position.line}, column ${position.col}`
207
+ : "";
208
+ throw new ConfigError(
209
+ `Invalid YAML syntax in '${filePath}'${locationInfo}: ${error.message}`
210
+ );
211
+ }
212
+ throw error;
213
+ }
214
+
215
+ // Handle empty files
216
+ if (rawConfig === null || rawConfig === undefined) {
217
+ rawConfig = {};
218
+ }
219
+
220
+ try {
221
+ return FleetConfigSchema.parse(rawConfig);
222
+ } catch (error) {
223
+ if (error instanceof ZodError) {
224
+ throw new SchemaValidationError(error);
225
+ }
226
+ throw error;
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Parse and validate agent config from YAML content
232
+ */
233
+ function parseAgentYaml(content: string, filePath: string): AgentConfig {
234
+ let rawConfig: unknown;
235
+ try {
236
+ rawConfig = parseYaml(content);
237
+ } catch (error) {
238
+ if (error instanceof YAMLParseError) {
239
+ const position = error.linePos?.[0];
240
+ const locationInfo = position
241
+ ? ` at line ${position.line}, column ${position.col}`
242
+ : "";
243
+ throw new ConfigError(
244
+ `Invalid YAML syntax in '${filePath}'${locationInfo}: ${error.message}`
245
+ );
246
+ }
247
+ throw error;
248
+ }
249
+
250
+ // Handle empty files
251
+ if (rawConfig === null || rawConfig === undefined) {
252
+ rawConfig = {};
253
+ }
254
+
255
+ try {
256
+ return AgentConfigSchema.parse(rawConfig);
257
+ } catch (error) {
258
+ if (error instanceof ZodError) {
259
+ const issues = error.issues.map((issue) => ({
260
+ path: issue.path.join(".") || "(root)",
261
+ message: issue.message,
262
+ }));
263
+ const issueMessages = issues
264
+ .map((i) => ` - ${i.path}: ${i.message}`)
265
+ .join("\n");
266
+ throw new ConfigError(
267
+ `Agent configuration validation failed in '${filePath}':\n${issueMessages}`
268
+ );
269
+ }
270
+ throw error;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Resolve an agent path relative to the fleet config directory
276
+ */
277
+ function resolveAgentPath(agentPath: string, fleetConfigDir: string): string {
278
+ if (agentPath.startsWith("/")) {
279
+ return agentPath;
280
+ }
281
+ return resolve(fleetConfigDir, agentPath);
282
+ }
283
+
284
+ // =============================================================================
285
+ // Main Loading Function
286
+ // =============================================================================
287
+
288
+ /**
289
+ * Load complete configuration from a file path or by auto-discovery
290
+ *
291
+ * This function:
292
+ * 1. Finds the config file (if not provided, searches up directory tree)
293
+ * 2. Parses and validates the fleet configuration
294
+ * 3. Loads and validates all referenced agent configurations
295
+ * 4. Interpolates environment variables (optional)
296
+ * 5. Merges fleet defaults into agent configs (optional)
297
+ * 6. Returns a fully resolved configuration object
298
+ *
299
+ * @param configPath - Path to herdctl.yaml, or directory to search from.
300
+ * If not provided, searches from current working directory.
301
+ * @param options - Loading options
302
+ * @returns A fully resolved configuration
303
+ * @throws {ConfigNotFoundError} If no config file is found
304
+ * @throws {FileReadError} If a config file cannot be read
305
+ * @throws {ConfigError} If YAML syntax is invalid
306
+ * @throws {SchemaValidationError} If configuration fails validation
307
+ * @throws {AgentLoadError} If an agent configuration fails to load
308
+ *
309
+ * @example
310
+ * ```typescript
311
+ * // Auto-discover config file
312
+ * const config = await loadConfig();
313
+ *
314
+ * // Load from specific path
315
+ * const config = await loadConfig("./my-project/herdctl.yaml");
316
+ *
317
+ * // Load from specific directory
318
+ * const config = await loadConfig("./my-project");
319
+ *
320
+ * // Load without environment interpolation
321
+ * const config = await loadConfig(undefined, { interpolate: false });
322
+ * ```
323
+ */
324
+ export async function loadConfig(
325
+ configPath?: string,
326
+ options: LoadConfigOptions = {}
327
+ ): Promise<ResolvedConfig> {
328
+ const {
329
+ env = process.env,
330
+ interpolate = true,
331
+ mergeDefaults = true,
332
+ } = options;
333
+
334
+ // Determine the config file path
335
+ let resolvedConfigPath: string;
336
+ let searchedPaths: string[] = [];
337
+
338
+ if (configPath) {
339
+ // Check if it's a file or directory
340
+ const isYamlFile =
341
+ configPath.endsWith(".yaml") || configPath.endsWith(".yml");
342
+
343
+ if (isYamlFile) {
344
+ // Treat as direct file path
345
+ resolvedConfigPath = resolve(configPath);
346
+ } else {
347
+ // Treat as directory - search from there
348
+ const found = await findConfigFile(configPath);
349
+ if (!found) {
350
+ throw new ConfigNotFoundError(configPath, searchedPaths);
351
+ }
352
+ resolvedConfigPath = found.path;
353
+ searchedPaths = found.searchedPaths;
354
+ }
355
+ } else {
356
+ // Auto-discover from current working directory
357
+ const found = await findConfigFile(process.cwd());
358
+ if (!found) {
359
+ throw new ConfigNotFoundError(process.cwd(), searchedPaths);
360
+ }
361
+ resolvedConfigPath = found.path;
362
+ searchedPaths = found.searchedPaths;
363
+ }
364
+
365
+ // Read the fleet config file
366
+ let fleetContent: string;
367
+ try {
368
+ fleetContent = await readFile(resolvedConfigPath, "utf-8");
369
+ } catch (error) {
370
+ throw new FileReadError(
371
+ resolvedConfigPath,
372
+ error instanceof Error ? error : undefined
373
+ );
374
+ }
375
+
376
+ // Parse the fleet config
377
+ let fleetConfig = parseFleetYaml(fleetContent, resolvedConfigPath);
378
+
379
+ // Interpolate environment variables in fleet config
380
+ if (interpolate) {
381
+ fleetConfig = interpolateConfig(fleetConfig, { env });
382
+ }
383
+
384
+ const configDir = dirname(resolvedConfigPath);
385
+
386
+ // Load all agent configs
387
+ const agents: ResolvedAgent[] = [];
388
+
389
+ // FleetConfigSchema has default of [], so agents is always defined
390
+ const agentRefs = fleetConfig.agents;
391
+
392
+ for (const agentRef of agentRefs) {
393
+ const agentPath = resolveAgentPath(agentRef.path, configDir);
394
+
395
+ // Read agent config file
396
+ let agentContent: string;
397
+ try {
398
+ agentContent = await readFile(agentPath, "utf-8");
399
+ } catch (error) {
400
+ throw new AgentLoadError(
401
+ agentRef.path,
402
+ new FileReadError(agentPath, error instanceof Error ? error : undefined)
403
+ );
404
+ }
405
+
406
+ // Parse and validate agent config
407
+ let agentConfig: AgentConfig;
408
+ try {
409
+ agentConfig = parseAgentYaml(agentContent, agentPath);
410
+ } catch (error) {
411
+ throw new AgentLoadError(
412
+ agentRef.path,
413
+ error instanceof Error ? error : new Error(String(error))
414
+ );
415
+ }
416
+
417
+ // Interpolate environment variables in agent config
418
+ if (interpolate) {
419
+ agentConfig = interpolateConfig(agentConfig, { env });
420
+ }
421
+
422
+ // Merge fleet defaults into agent config
423
+ if (mergeDefaults && fleetConfig.defaults) {
424
+ agentConfig = mergeAgentConfig(
425
+ fleetConfig.defaults as ExtendedDefaults,
426
+ agentConfig
427
+ );
428
+ }
429
+
430
+ agents.push({
431
+ ...agentConfig,
432
+ configPath: agentPath,
433
+ });
434
+ }
435
+
436
+ return {
437
+ fleet: fleetConfig,
438
+ agents,
439
+ configPath: resolvedConfigPath,
440
+ configDir,
441
+ };
442
+ }
443
+
444
+ /**
445
+ * Load configuration without throwing on errors
446
+ *
447
+ * @param configPath - Path to herdctl.yaml or directory to search from
448
+ * @param options - Loading options
449
+ * @returns Success result with config, or failure result with error
450
+ */
451
+ export async function safeLoadConfig(
452
+ configPath?: string,
453
+ options: LoadConfigOptions = {}
454
+ ): Promise<
455
+ | { success: true; data: ResolvedConfig }
456
+ | { success: false; error: ConfigError }
457
+ > {
458
+ try {
459
+ const config = await loadConfig(configPath, options);
460
+ return { success: true, data: config };
461
+ } catch (error) {
462
+ if (error instanceof ConfigError) {
463
+ return { success: false, error };
464
+ }
465
+ return {
466
+ success: false,
467
+ error: new ConfigError(
468
+ error instanceof Error ? error.message : String(error)
469
+ ),
470
+ };
471
+ }
472
+ }
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Deep merge utilities for herdctl configuration
3
+ *
4
+ * Provides functions to merge fleet-level defaults with agent-specific overrides.
5
+ * - Nested objects merge recursively
6
+ * - Arrays are replaced, not merged (agent's array replaces defaults)
7
+ * - Agent-specific values override fleet defaults
8
+ */
9
+
10
+ import type {
11
+ AgentConfig,
12
+ Defaults,
13
+ WorkSource,
14
+ Session,
15
+ Docker,
16
+ PermissionMode,
17
+ BashPermissions,
18
+ } from "./schema.js";
19
+
20
+ // =============================================================================
21
+ // Input Types (for merging - fields are optional before Zod applies defaults)
22
+ // =============================================================================
23
+
24
+ /**
25
+ * Permissions input type - all fields optional before Zod applies defaults
26
+ */
27
+ export interface PermissionsInput {
28
+ mode?: PermissionMode;
29
+ allowed_tools?: string[];
30
+ denied_tools?: string[];
31
+ bash?: BashPermissions;
32
+ }
33
+
34
+ // =============================================================================
35
+ // Type Guards
36
+ // =============================================================================
37
+
38
+ /**
39
+ * Check if a value is a plain object (not an array, null, or other type)
40
+ */
41
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
42
+ return (
43
+ typeof value === "object" &&
44
+ value !== null &&
45
+ !Array.isArray(value) &&
46
+ Object.prototype.toString.call(value) === "[object Object]"
47
+ );
48
+ }
49
+
50
+ // =============================================================================
51
+ // Deep Merge
52
+ // =============================================================================
53
+
54
+ /**
55
+ * Deep merge two objects. The override object's values take precedence.
56
+ * Arrays are replaced entirely (not merged).
57
+ * Objects are merged recursively.
58
+ *
59
+ * @param base - The base object (defaults)
60
+ * @param override - The override object (agent-specific)
61
+ * @returns A new merged object
62
+ */
63
+ export function deepMerge<T extends Record<string, unknown>>(
64
+ base: T | undefined,
65
+ override: T | undefined
66
+ ): T | undefined {
67
+ // If base is undefined, return override
68
+ if (base === undefined) {
69
+ return override;
70
+ }
71
+
72
+ // If override is undefined, return base
73
+ if (override === undefined) {
74
+ return base;
75
+ }
76
+
77
+ // Create a new object to hold the result
78
+ const result = { ...base } as Record<string, unknown>;
79
+
80
+ // Iterate over the override object
81
+ for (const key of Object.keys(override)) {
82
+ const baseValue = base[key];
83
+ const overrideValue = override[key];
84
+
85
+ // If the override value is undefined, skip it (keep base value)
86
+ if (overrideValue === undefined) {
87
+ continue;
88
+ }
89
+
90
+ // If the override value is an array, replace entirely
91
+ if (Array.isArray(overrideValue)) {
92
+ result[key] = overrideValue;
93
+ continue;
94
+ }
95
+
96
+ // If both values are plain objects, merge recursively
97
+ if (isPlainObject(baseValue) && isPlainObject(overrideValue)) {
98
+ result[key] = deepMerge(baseValue, overrideValue);
99
+ continue;
100
+ }
101
+
102
+ // Otherwise, override value takes precedence
103
+ result[key] = overrideValue;
104
+ }
105
+
106
+ return result as T;
107
+ }
108
+
109
+ // =============================================================================
110
+ // Agent Config Merge Types
111
+ // =============================================================================
112
+
113
+ /**
114
+ * The fields from fleet defaults that can be merged into agent config
115
+ */
116
+ export interface MergeableDefaults {
117
+ permissions?: PermissionsInput;
118
+ work_source?: WorkSource;
119
+ session?: Session;
120
+ docker?: Docker;
121
+ model?: string;
122
+ max_turns?: number;
123
+ permission_mode?: PermissionMode;
124
+ }
125
+
126
+ /**
127
+ * Extended defaults schema that includes all mergeable fields.
128
+ * Uses input types (with optional fields) since these are pre-validation values.
129
+ */
130
+ export interface ExtendedDefaults {
131
+ docker?: Docker;
132
+ permissions?: PermissionsInput;
133
+ work_source?: WorkSource;
134
+ instances?: { max_concurrent?: number };
135
+ session?: Session;
136
+ model?: string;
137
+ max_turns?: number;
138
+ permission_mode?: PermissionMode;
139
+ }
140
+
141
+ // =============================================================================
142
+ // Agent Config Merge
143
+ // =============================================================================
144
+
145
+ /**
146
+ * Merge fleet defaults into an agent configuration.
147
+ *
148
+ * The merge applies to the following fields:
149
+ * - permissions: Deep merged (agent overrides fleet defaults)
150
+ * - work_source: Deep merged
151
+ * - session: Deep merged
152
+ * - docker: Deep merged
153
+ * - model: Agent value overrides default
154
+ * - max_turns: Agent value overrides default
155
+ * - permission_mode: Agent value overrides default
156
+ *
157
+ * Arrays within these objects (e.g., allowed_tools) are replaced, not merged.
158
+ *
159
+ * @param defaults - The fleet-level defaults
160
+ * @param agent - The agent-specific configuration
161
+ * @returns A new agent configuration with defaults merged in
162
+ */
163
+ export function mergeAgentConfig(
164
+ defaults: ExtendedDefaults | undefined,
165
+ agent: AgentConfig
166
+ ): AgentConfig {
167
+ // If no defaults, return agent as-is
168
+ if (!defaults) {
169
+ return agent;
170
+ }
171
+
172
+ // Start with the agent config
173
+ const result: AgentConfig = { ...agent };
174
+
175
+ // Merge permissions (deep merge)
176
+ if (defaults.permissions || agent.permissions) {
177
+ result.permissions = deepMerge(
178
+ defaults.permissions as Record<string, unknown> | undefined,
179
+ agent.permissions as Record<string, unknown> | undefined
180
+ ) as AgentConfig["permissions"];
181
+ }
182
+
183
+ // Merge work_source (deep merge)
184
+ if (defaults.work_source || agent.work_source) {
185
+ result.work_source = deepMerge(
186
+ defaults.work_source as Record<string, unknown> | undefined,
187
+ agent.work_source as Record<string, unknown> | undefined
188
+ ) as AgentConfig["work_source"];
189
+ }
190
+
191
+ // Merge session (deep merge)
192
+ if (defaults.session || agent.session) {
193
+ result.session = deepMerge(
194
+ defaults.session as Record<string, unknown> | undefined,
195
+ agent.session as Record<string, unknown> | undefined
196
+ ) as AgentConfig["session"];
197
+ }
198
+
199
+ // Merge docker (deep merge)
200
+ if (defaults.docker || agent.docker) {
201
+ result.docker = deepMerge(
202
+ defaults.docker as Record<string, unknown> | undefined,
203
+ agent.docker as Record<string, unknown> | undefined
204
+ ) as AgentConfig["docker"];
205
+ }
206
+
207
+ // Merge instances (deep merge)
208
+ if (defaults.instances || agent.instances) {
209
+ result.instances = deepMerge(
210
+ defaults.instances as Record<string, unknown> | undefined,
211
+ agent.instances as Record<string, unknown> | undefined
212
+ ) as AgentConfig["instances"];
213
+ }
214
+
215
+ // Merge scalar values (agent takes precedence if defined)
216
+ if (defaults.model !== undefined && result.model === undefined) {
217
+ result.model = defaults.model;
218
+ }
219
+
220
+ if (defaults.max_turns !== undefined && result.max_turns === undefined) {
221
+ result.max_turns = defaults.max_turns;
222
+ }
223
+
224
+ if (
225
+ defaults.permission_mode !== undefined &&
226
+ result.permission_mode === undefined
227
+ ) {
228
+ result.permission_mode = defaults.permission_mode;
229
+ }
230
+
231
+ return result;
232
+ }
233
+
234
+ /**
235
+ * Merge fleet defaults into multiple agent configurations.
236
+ *
237
+ * @param defaults - The fleet-level defaults
238
+ * @param agents - Array of agent configurations
239
+ * @returns Array of merged agent configurations
240
+ */
241
+ export function mergeAllAgentConfigs(
242
+ defaults: ExtendedDefaults | undefined,
243
+ agents: AgentConfig[]
244
+ ): AgentConfig[] {
245
+ return agents.map((agent) => mergeAgentConfig(defaults, agent));
246
+ }