@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,868 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { mkdir, rm, realpath, writeFile, readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import {
6
+ readFleetState,
7
+ writeFleetState,
8
+ updateAgentState,
9
+ initializeFleetState,
10
+ removeAgentState,
11
+ type StateLogger,
12
+ } from "../fleet-state.js";
13
+ import {
14
+ createInitialFleetState,
15
+ type FleetState,
16
+ type AgentState,
17
+ } from "../schemas/fleet-state.js";
18
+ import { StateFileError } from "../errors.js";
19
+
20
+ // Helper to create a temp directory
21
+ async function createTempDir(): Promise<string> {
22
+ const baseDir = join(
23
+ tmpdir(),
24
+ `herdctl-fleet-state-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
25
+ );
26
+ await mkdir(baseDir, { recursive: true });
27
+ // Resolve to real path to handle macOS /var -> /private/var symlink
28
+ return await realpath(baseDir);
29
+ }
30
+
31
+ // Helper to create a mock logger
32
+ function createMockLogger(): StateLogger & { warnings: string[] } {
33
+ const warnings: string[] = [];
34
+ return {
35
+ warnings,
36
+ warn: (message: string) => warnings.push(message),
37
+ };
38
+ }
39
+
40
+ describe("readFleetState", () => {
41
+ let tempDir: string;
42
+
43
+ beforeEach(async () => {
44
+ tempDir = await createTempDir();
45
+ });
46
+
47
+ afterEach(async () => {
48
+ await rm(tempDir, { recursive: true, force: true });
49
+ });
50
+
51
+ describe("valid state files", () => {
52
+ it("reads and validates state.yaml with full fleet state", async () => {
53
+ const stateFile = join(tempDir, "state.yaml");
54
+ const stateContent = `
55
+ fleet:
56
+ started_at: "2024-01-15T10:30:00Z"
57
+ agents:
58
+ my-agent:
59
+ status: running
60
+ current_job: job-123
61
+ last_job: job-122
62
+ next_schedule: hourly
63
+ next_trigger_at: "2024-01-15T11:00:00Z"
64
+ container_id: abc123
65
+ other-agent:
66
+ status: idle
67
+ `;
68
+ await writeFile(stateFile, stateContent, "utf-8");
69
+
70
+ const state = await readFleetState(stateFile);
71
+
72
+ expect(state.fleet.started_at).toBe("2024-01-15T10:30:00Z");
73
+ expect(state.agents["my-agent"]).toEqual({
74
+ status: "running",
75
+ current_job: "job-123",
76
+ last_job: "job-122",
77
+ next_schedule: "hourly",
78
+ next_trigger_at: "2024-01-15T11:00:00Z",
79
+ container_id: "abc123",
80
+ });
81
+ expect(state.agents["other-agent"]).toEqual({
82
+ status: "idle",
83
+ });
84
+ });
85
+
86
+ it("reads state with agent error status", async () => {
87
+ const stateFile = join(tempDir, "state.yaml");
88
+ const stateContent = `
89
+ fleet:
90
+ started_at: "2024-01-15T10:30:00Z"
91
+ agents:
92
+ failed-agent:
93
+ status: error
94
+ last_job: job-100
95
+ error_message: "Container exited with code 1"
96
+ `;
97
+ await writeFile(stateFile, stateContent, "utf-8");
98
+
99
+ const state = await readFleetState(stateFile);
100
+
101
+ expect(state.agents["failed-agent"]).toEqual({
102
+ status: "error",
103
+ last_job: "job-100",
104
+ error_message: "Container exited with code 1",
105
+ });
106
+ });
107
+
108
+ it("applies default values for missing optional fields", async () => {
109
+ const stateFile = join(tempDir, "state.yaml");
110
+ const stateContent = `
111
+ agents:
112
+ minimal-agent:
113
+ status: idle
114
+ `;
115
+ await writeFile(stateFile, stateContent, "utf-8");
116
+
117
+ const state = await readFleetState(stateFile);
118
+
119
+ expect(state.fleet).toEqual({});
120
+ expect(state.agents["minimal-agent"]).toEqual({
121
+ status: "idle",
122
+ });
123
+ });
124
+
125
+ it("handles empty agents map", async () => {
126
+ const stateFile = join(tempDir, "state.yaml");
127
+ const stateContent = `
128
+ fleet:
129
+ started_at: "2024-01-15T10:30:00Z"
130
+ agents: {}
131
+ `;
132
+ await writeFile(stateFile, stateContent, "utf-8");
133
+
134
+ const state = await readFleetState(stateFile);
135
+
136
+ expect(state.fleet.started_at).toBe("2024-01-15T10:30:00Z");
137
+ expect(state.agents).toEqual({});
138
+ });
139
+ });
140
+
141
+ describe("missing file handling", () => {
142
+ it("returns default empty state when file does not exist", async () => {
143
+ const stateFile = join(tempDir, "nonexistent.yaml");
144
+
145
+ const state = await readFleetState(stateFile);
146
+
147
+ expect(state).toEqual(createInitialFleetState());
148
+ expect(state.fleet).toEqual({});
149
+ expect(state.agents).toEqual({});
150
+ });
151
+
152
+ it("does not log warning for missing file", async () => {
153
+ const stateFile = join(tempDir, "nonexistent.yaml");
154
+ const logger = createMockLogger();
155
+
156
+ await readFleetState(stateFile, { logger });
157
+
158
+ expect(logger.warnings).toHaveLength(0);
159
+ });
160
+ });
161
+
162
+ describe("empty file handling", () => {
163
+ it("returns default state for empty file", async () => {
164
+ const stateFile = join(tempDir, "empty.yaml");
165
+ await writeFile(stateFile, "", "utf-8");
166
+
167
+ const state = await readFleetState(stateFile);
168
+
169
+ expect(state).toEqual(createInitialFleetState());
170
+ });
171
+
172
+ it("returns default state for file with only whitespace", async () => {
173
+ const stateFile = join(tempDir, "whitespace.yaml");
174
+ await writeFile(stateFile, " \n \n ", "utf-8");
175
+
176
+ const state = await readFleetState(stateFile);
177
+
178
+ expect(state).toEqual(createInitialFleetState());
179
+ });
180
+
181
+ it("returns default state for file with only comments", async () => {
182
+ const stateFile = join(tempDir, "comments.yaml");
183
+ await writeFile(stateFile, "# This is a comment\n", "utf-8");
184
+
185
+ const state = await readFleetState(stateFile);
186
+
187
+ expect(state).toEqual(createInitialFleetState());
188
+ });
189
+ });
190
+
191
+ describe("corrupted file handling", () => {
192
+ it("returns default state and logs warning for invalid YAML syntax", async () => {
193
+ const stateFile = join(tempDir, "invalid-syntax.yaml");
194
+ await writeFile(stateFile, "fleet: [unclosed", "utf-8");
195
+ const logger = createMockLogger();
196
+
197
+ const state = await readFleetState(stateFile, { logger });
198
+
199
+ expect(state).toEqual(createInitialFleetState());
200
+ expect(logger.warnings).toHaveLength(1);
201
+ // YAML parse errors come through as read errors
202
+ expect(logger.warnings[0]).toContain("Using default state");
203
+ });
204
+
205
+ it("returns default state and logs warning for invalid status enum", async () => {
206
+ const stateFile = join(tempDir, "invalid-status.yaml");
207
+ const stateContent = `
208
+ agents:
209
+ bad-agent:
210
+ status: invalid_status
211
+ `;
212
+ await writeFile(stateFile, stateContent, "utf-8");
213
+ const logger = createMockLogger();
214
+
215
+ const state = await readFleetState(stateFile, { logger });
216
+
217
+ expect(state).toEqual(createInitialFleetState());
218
+ expect(logger.warnings).toHaveLength(1);
219
+ expect(logger.warnings[0]).toContain("Corrupted state file");
220
+ });
221
+
222
+ it("returns default state and logs warning for wrong type structure", async () => {
223
+ const stateFile = join(tempDir, "wrong-type.yaml");
224
+ const stateContent = `
225
+ agents: "not an object"
226
+ `;
227
+ await writeFile(stateFile, stateContent, "utf-8");
228
+ const logger = createMockLogger();
229
+
230
+ const state = await readFleetState(stateFile, { logger });
231
+
232
+ expect(state).toEqual(createInitialFleetState());
233
+ expect(logger.warnings).toHaveLength(1);
234
+ });
235
+
236
+ it("uses default console.warn when no logger provided", async () => {
237
+ const stateFile = join(tempDir, "invalid.yaml");
238
+ await writeFile(stateFile, "fleet: [unclosed", "utf-8");
239
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
240
+
241
+ const state = await readFleetState(stateFile);
242
+
243
+ expect(state).toEqual(createInitialFleetState());
244
+ expect(consoleSpy).toHaveBeenCalled();
245
+ consoleSpy.mockRestore();
246
+ });
247
+ });
248
+
249
+ describe("FleetState type validation", () => {
250
+ it("validates all AgentStatus enum values", async () => {
251
+ const stateFile = join(tempDir, "all-statuses.yaml");
252
+ const stateContent = `
253
+ agents:
254
+ idle-agent:
255
+ status: idle
256
+ running-agent:
257
+ status: running
258
+ error-agent:
259
+ status: error
260
+ `;
261
+ await writeFile(stateFile, stateContent, "utf-8");
262
+
263
+ const state = await readFleetState(stateFile);
264
+
265
+ expect(state.agents["idle-agent"].status).toBe("idle");
266
+ expect(state.agents["running-agent"].status).toBe("running");
267
+ expect(state.agents["error-agent"].status).toBe("error");
268
+ });
269
+
270
+ it("allows nullable fields to be null", async () => {
271
+ const stateFile = join(tempDir, "nullable.yaml");
272
+ const stateContent = `
273
+ agents:
274
+ agent-with-nulls:
275
+ status: idle
276
+ current_job: null
277
+ last_job: null
278
+ next_schedule: null
279
+ next_trigger_at: null
280
+ container_id: null
281
+ error_message: null
282
+ `;
283
+ await writeFile(stateFile, stateContent, "utf-8");
284
+
285
+ const state = await readFleetState(stateFile);
286
+
287
+ expect(state.agents["agent-with-nulls"].current_job).toBeNull();
288
+ expect(state.agents["agent-with-nulls"].last_job).toBeNull();
289
+ expect(state.agents["agent-with-nulls"].error_message).toBeNull();
290
+ });
291
+ });
292
+ });
293
+
294
+ describe("writeFleetState", () => {
295
+ let tempDir: string;
296
+
297
+ beforeEach(async () => {
298
+ tempDir = await createTempDir();
299
+ });
300
+
301
+ afterEach(async () => {
302
+ await rm(tempDir, { recursive: true, force: true });
303
+ });
304
+
305
+ describe("successful writes", () => {
306
+ it("writes valid fleet state to file", async () => {
307
+ const stateFile = join(tempDir, "state.yaml");
308
+ const state: FleetState = {
309
+ fleet: {
310
+ started_at: "2024-01-15T10:30:00Z",
311
+ },
312
+ agents: {
313
+ "my-agent": {
314
+ status: "running",
315
+ current_job: "job-123",
316
+ },
317
+ },
318
+ };
319
+
320
+ await writeFleetState(stateFile, state);
321
+
322
+ const content = await readFile(stateFile, "utf-8");
323
+ expect(content).toContain("started_at:");
324
+ expect(content).toContain("my-agent:");
325
+ expect(content).toContain("status: running");
326
+ expect(content).toContain("current_job: job-123");
327
+ });
328
+
329
+ it("writes empty state correctly", async () => {
330
+ const stateFile = join(tempDir, "empty-state.yaml");
331
+ const state = createInitialFleetState();
332
+
333
+ await writeFleetState(stateFile, state);
334
+
335
+ const readState = await readFleetState(stateFile);
336
+ expect(readState).toEqual(state);
337
+ });
338
+
339
+ it("overwrites existing file", async () => {
340
+ const stateFile = join(tempDir, "overwrite.yaml");
341
+ await writeFile(stateFile, "old: content\n", "utf-8");
342
+ const state: FleetState = {
343
+ fleet: { started_at: "2024-01-15T10:30:00Z" },
344
+ agents: {},
345
+ };
346
+
347
+ await writeFleetState(stateFile, state);
348
+
349
+ const content = await readFile(stateFile, "utf-8");
350
+ expect(content).not.toContain("old:");
351
+ expect(content).toContain("started_at:");
352
+ });
353
+
354
+ it("preserves custom indent option", async () => {
355
+ const stateFile = join(tempDir, "custom-indent.yaml");
356
+ const state: FleetState = {
357
+ fleet: {},
358
+ agents: {
359
+ agent: { status: "idle" },
360
+ },
361
+ };
362
+
363
+ await writeFleetState(stateFile, state, { indent: 4 });
364
+
365
+ const content = await readFile(stateFile, "utf-8");
366
+ // Check for 4-space indentation
367
+ expect(content).toMatch(/^\s{4}agent:/m);
368
+ });
369
+
370
+ it("round-trips complex state correctly", async () => {
371
+ const stateFile = join(tempDir, "roundtrip.yaml");
372
+ const originalState: FleetState = {
373
+ fleet: {
374
+ started_at: "2024-01-15T10:30:00Z",
375
+ },
376
+ agents: {
377
+ "agent-1": {
378
+ status: "running",
379
+ current_job: "job-123",
380
+ last_job: "job-122",
381
+ next_schedule: "hourly",
382
+ next_trigger_at: "2024-01-15T11:00:00Z",
383
+ container_id: "container-abc",
384
+ },
385
+ "agent-2": {
386
+ status: "error",
387
+ last_job: "job-456",
388
+ error_message: "Out of memory",
389
+ },
390
+ "agent-3": {
391
+ status: "idle",
392
+ },
393
+ },
394
+ };
395
+
396
+ await writeFleetState(stateFile, originalState);
397
+ const readState = await readFleetState(stateFile);
398
+
399
+ expect(readState.fleet.started_at).toBe(originalState.fleet.started_at);
400
+ expect(readState.agents["agent-1"]).toEqual(originalState.agents["agent-1"]);
401
+ expect(readState.agents["agent-2"]).toEqual(originalState.agents["agent-2"]);
402
+ expect(readState.agents["agent-3"]).toEqual(originalState.agents["agent-3"]);
403
+ });
404
+ });
405
+
406
+ describe("atomic write behavior", () => {
407
+ it("writes atomically (no partial writes)", async () => {
408
+ const stateFile = join(tempDir, "atomic.yaml");
409
+ const state: FleetState = {
410
+ fleet: { started_at: "2024-01-15T10:30:00Z" },
411
+ agents: {
412
+ agent: {
413
+ status: "running",
414
+ current_job: "job-1",
415
+ },
416
+ },
417
+ };
418
+
419
+ await writeFleetState(stateFile, state);
420
+
421
+ // File should be complete and valid
422
+ const readState = await readFleetState(stateFile);
423
+ expect(readState.fleet.started_at).toBe("2024-01-15T10:30:00Z");
424
+ });
425
+
426
+ it("does not leave temp files on success", async () => {
427
+ const stateFile = join(tempDir, "no-temp.yaml");
428
+ const state = createInitialFleetState();
429
+
430
+ await writeFleetState(stateFile, state);
431
+
432
+ // Check no temp files exist
433
+ const { readdir } = await import("node:fs/promises");
434
+ const files = await readdir(tempDir);
435
+ const tempFiles = files.filter((f) => f.includes(".tmp."));
436
+ expect(tempFiles).toHaveLength(0);
437
+ });
438
+ });
439
+
440
+ describe("error handling", () => {
441
+ it("throws StateFileError when write fails", async () => {
442
+ // Try to write to a non-existent directory
443
+ const stateFile = join(tempDir, "nonexistent-dir", "state.yaml");
444
+ const state = createInitialFleetState();
445
+
446
+ await expect(writeFleetState(stateFile, state)).rejects.toThrow(StateFileError);
447
+ });
448
+
449
+ it("validates state before writing", async () => {
450
+ const stateFile = join(tempDir, "validate.yaml");
451
+ const invalidState = {
452
+ fleet: {},
453
+ agents: {
454
+ agent: {
455
+ status: "invalid_status", // Invalid status
456
+ },
457
+ },
458
+ } as unknown as FleetState;
459
+
460
+ await expect(writeFleetState(stateFile, invalidState)).rejects.toThrow();
461
+ });
462
+ });
463
+ });
464
+
465
+ describe("updateAgentState", () => {
466
+ let tempDir: string;
467
+
468
+ beforeEach(async () => {
469
+ tempDir = await createTempDir();
470
+ });
471
+
472
+ afterEach(async () => {
473
+ await rm(tempDir, { recursive: true, force: true });
474
+ });
475
+
476
+ describe("updating existing agents", () => {
477
+ it("updates single field of existing agent", async () => {
478
+ const stateFile = join(tempDir, "state.yaml");
479
+ const initialState: FleetState = {
480
+ fleet: { started_at: "2024-01-15T10:30:00Z" },
481
+ agents: {
482
+ "my-agent": {
483
+ status: "idle",
484
+ last_job: "job-100",
485
+ },
486
+ },
487
+ };
488
+ await writeFleetState(stateFile, initialState);
489
+
490
+ const updatedState = await updateAgentState(stateFile, "my-agent", {
491
+ status: "running",
492
+ });
493
+
494
+ expect(updatedState.agents["my-agent"].status).toBe("running");
495
+ expect(updatedState.agents["my-agent"].last_job).toBe("job-100");
496
+ });
497
+
498
+ it("updates multiple fields of existing agent", async () => {
499
+ const stateFile = join(tempDir, "state.yaml");
500
+ const initialState: FleetState = {
501
+ fleet: {},
502
+ agents: {
503
+ "my-agent": {
504
+ status: "idle",
505
+ },
506
+ },
507
+ };
508
+ await writeFleetState(stateFile, initialState);
509
+
510
+ const updatedState = await updateAgentState(stateFile, "my-agent", {
511
+ status: "running",
512
+ current_job: "job-200",
513
+ container_id: "container-xyz",
514
+ });
515
+
516
+ expect(updatedState.agents["my-agent"]).toEqual({
517
+ status: "running",
518
+ current_job: "job-200",
519
+ container_id: "container-xyz",
520
+ });
521
+ });
522
+
523
+ it("can set fields to null", async () => {
524
+ const stateFile = join(tempDir, "state.yaml");
525
+ const initialState: FleetState = {
526
+ fleet: {},
527
+ agents: {
528
+ "my-agent": {
529
+ status: "error",
530
+ error_message: "Some error",
531
+ current_job: "job-100",
532
+ },
533
+ },
534
+ };
535
+ await writeFleetState(stateFile, initialState);
536
+
537
+ const updatedState = await updateAgentState(stateFile, "my-agent", {
538
+ status: "idle",
539
+ error_message: null,
540
+ current_job: null,
541
+ });
542
+
543
+ expect(updatedState.agents["my-agent"].status).toBe("idle");
544
+ expect(updatedState.agents["my-agent"].error_message).toBeNull();
545
+ expect(updatedState.agents["my-agent"].current_job).toBeNull();
546
+ });
547
+
548
+ it("preserves other agents when updating one", async () => {
549
+ const stateFile = join(tempDir, "state.yaml");
550
+ const initialState: FleetState = {
551
+ fleet: {},
552
+ agents: {
553
+ "agent-1": { status: "idle" },
554
+ "agent-2": { status: "running", current_job: "job-100" },
555
+ },
556
+ };
557
+ await writeFleetState(stateFile, initialState);
558
+
559
+ const updatedState = await updateAgentState(stateFile, "agent-1", {
560
+ status: "running",
561
+ });
562
+
563
+ expect(updatedState.agents["agent-1"].status).toBe("running");
564
+ expect(updatedState.agents["agent-2"]).toEqual({
565
+ status: "running",
566
+ current_job: "job-100",
567
+ });
568
+ });
569
+
570
+ it("preserves fleet metadata when updating agent", async () => {
571
+ const stateFile = join(tempDir, "state.yaml");
572
+ const initialState: FleetState = {
573
+ fleet: { started_at: "2024-01-15T10:30:00Z" },
574
+ agents: {
575
+ "my-agent": { status: "idle" },
576
+ },
577
+ };
578
+ await writeFleetState(stateFile, initialState);
579
+
580
+ const updatedState = await updateAgentState(stateFile, "my-agent", {
581
+ status: "running",
582
+ });
583
+
584
+ expect(updatedState.fleet.started_at).toBe("2024-01-15T10:30:00Z");
585
+ });
586
+ });
587
+
588
+ describe("creating new agents", () => {
589
+ it("creates new agent if it does not exist", async () => {
590
+ const stateFile = join(tempDir, "state.yaml");
591
+ const initialState: FleetState = {
592
+ fleet: {},
593
+ agents: {},
594
+ };
595
+ await writeFleetState(stateFile, initialState);
596
+
597
+ const updatedState = await updateAgentState(stateFile, "new-agent", {
598
+ status: "running",
599
+ current_job: "job-1",
600
+ });
601
+
602
+ expect(updatedState.agents["new-agent"]).toEqual({
603
+ status: "running",
604
+ current_job: "job-1",
605
+ });
606
+ });
607
+
608
+ it("creates new agent with default status if not provided", async () => {
609
+ const stateFile = join(tempDir, "state.yaml");
610
+ const initialState: FleetState = {
611
+ fleet: {},
612
+ agents: {},
613
+ };
614
+ await writeFleetState(stateFile, initialState);
615
+
616
+ const updatedState = await updateAgentState(stateFile, "new-agent", {
617
+ last_job: "job-1",
618
+ });
619
+
620
+ expect(updatedState.agents["new-agent"].status).toBe("idle");
621
+ expect(updatedState.agents["new-agent"].last_job).toBe("job-1");
622
+ });
623
+
624
+ it("creates agent in file that does not exist", async () => {
625
+ const stateFile = join(tempDir, "new-state.yaml");
626
+
627
+ const updatedState = await updateAgentState(stateFile, "new-agent", {
628
+ status: "running",
629
+ });
630
+
631
+ expect(updatedState.agents["new-agent"].status).toBe("running");
632
+
633
+ // Verify it was persisted
634
+ const readState = await readFleetState(stateFile);
635
+ expect(readState.agents["new-agent"].status).toBe("running");
636
+ });
637
+ });
638
+
639
+ describe("file operations", () => {
640
+ it("writes changes back to file", async () => {
641
+ const stateFile = join(tempDir, "state.yaml");
642
+ const initialState: FleetState = {
643
+ fleet: {},
644
+ agents: {
645
+ "my-agent": { status: "idle" },
646
+ },
647
+ };
648
+ await writeFleetState(stateFile, initialState);
649
+
650
+ await updateAgentState(stateFile, "my-agent", { status: "running" });
651
+
652
+ // Read from file directly to verify persistence
653
+ const persistedState = await readFleetState(stateFile);
654
+ expect(persistedState.agents["my-agent"].status).toBe("running");
655
+ });
656
+
657
+ it("handles corrupted file by starting fresh", async () => {
658
+ const stateFile = join(tempDir, "corrupted.yaml");
659
+ await writeFile(stateFile, "invalid: [yaml", "utf-8");
660
+ const logger = createMockLogger();
661
+
662
+ const updatedState = await updateAgentState(
663
+ stateFile,
664
+ "new-agent",
665
+ { status: "running" },
666
+ { logger }
667
+ );
668
+
669
+ expect(updatedState.agents["new-agent"].status).toBe("running");
670
+ expect(logger.warnings).toHaveLength(1);
671
+ });
672
+ });
673
+ });
674
+
675
+ describe("initializeFleetState", () => {
676
+ let tempDir: string;
677
+
678
+ beforeEach(async () => {
679
+ tempDir = await createTempDir();
680
+ });
681
+
682
+ afterEach(async () => {
683
+ await rm(tempDir, { recursive: true, force: true });
684
+ });
685
+
686
+ it("sets started_at if not already set", async () => {
687
+ const stateFile = join(tempDir, "state.yaml");
688
+
689
+ const state = await initializeFleetState(stateFile);
690
+
691
+ expect(state.fleet.started_at).toBeDefined();
692
+ expect(new Date(state.fleet.started_at!).getTime()).toBeGreaterThan(0);
693
+ });
694
+
695
+ it("does not overwrite existing started_at", async () => {
696
+ const stateFile = join(tempDir, "state.yaml");
697
+ const originalTimestamp = "2024-01-01T00:00:00Z";
698
+ const initialState: FleetState = {
699
+ fleet: { started_at: originalTimestamp },
700
+ agents: {},
701
+ };
702
+ await writeFleetState(stateFile, initialState);
703
+
704
+ const state = await initializeFleetState(stateFile);
705
+
706
+ expect(state.fleet.started_at).toBe(originalTimestamp);
707
+ });
708
+
709
+ it("creates file if it does not exist", async () => {
710
+ const stateFile = join(tempDir, "new-state.yaml");
711
+
712
+ const state = await initializeFleetState(stateFile);
713
+
714
+ expect(state.fleet.started_at).toBeDefined();
715
+
716
+ // Verify file was created
717
+ const persistedState = await readFleetState(stateFile);
718
+ expect(persistedState.fleet.started_at).toBe(state.fleet.started_at);
719
+ });
720
+
721
+ it("preserves existing agents", async () => {
722
+ const stateFile = join(tempDir, "state.yaml");
723
+ const initialState: FleetState = {
724
+ fleet: {},
725
+ agents: {
726
+ "existing-agent": { status: "idle" },
727
+ },
728
+ };
729
+ await writeFleetState(stateFile, initialState);
730
+
731
+ const state = await initializeFleetState(stateFile);
732
+
733
+ expect(state.agents["existing-agent"]).toEqual({ status: "idle" });
734
+ expect(state.fleet.started_at).toBeDefined();
735
+ });
736
+ });
737
+
738
+ describe("removeAgentState", () => {
739
+ let tempDir: string;
740
+
741
+ beforeEach(async () => {
742
+ tempDir = await createTempDir();
743
+ });
744
+
745
+ afterEach(async () => {
746
+ await rm(tempDir, { recursive: true, force: true });
747
+ });
748
+
749
+ it("removes specified agent from state", async () => {
750
+ const stateFile = join(tempDir, "state.yaml");
751
+ const initialState: FleetState = {
752
+ fleet: {},
753
+ agents: {
754
+ "agent-1": { status: "idle" },
755
+ "agent-2": { status: "running" },
756
+ },
757
+ };
758
+ await writeFleetState(stateFile, initialState);
759
+
760
+ const updatedState = await removeAgentState(stateFile, "agent-1");
761
+
762
+ expect(updatedState.agents["agent-1"]).toBeUndefined();
763
+ expect(updatedState.agents["agent-2"]).toEqual({ status: "running" });
764
+ });
765
+
766
+ it("persists removal to file", async () => {
767
+ const stateFile = join(tempDir, "state.yaml");
768
+ const initialState: FleetState = {
769
+ fleet: {},
770
+ agents: {
771
+ "agent-to-remove": { status: "idle" },
772
+ },
773
+ };
774
+ await writeFleetState(stateFile, initialState);
775
+
776
+ await removeAgentState(stateFile, "agent-to-remove");
777
+
778
+ const persistedState = await readFleetState(stateFile);
779
+ expect(persistedState.agents["agent-to-remove"]).toBeUndefined();
780
+ });
781
+
782
+ it("handles removal of non-existent agent gracefully", async () => {
783
+ const stateFile = join(tempDir, "state.yaml");
784
+ const initialState: FleetState = {
785
+ fleet: {},
786
+ agents: {
787
+ "existing-agent": { status: "idle" },
788
+ },
789
+ };
790
+ await writeFleetState(stateFile, initialState);
791
+
792
+ const updatedState = await removeAgentState(stateFile, "non-existent");
793
+
794
+ expect(updatedState.agents["existing-agent"]).toEqual({ status: "idle" });
795
+ expect(Object.keys(updatedState.agents)).toHaveLength(1);
796
+ });
797
+
798
+ it("preserves fleet metadata when removing agent", async () => {
799
+ const stateFile = join(tempDir, "state.yaml");
800
+ const initialState: FleetState = {
801
+ fleet: { started_at: "2024-01-15T10:30:00Z" },
802
+ agents: {
803
+ "agent-to-remove": { status: "idle" },
804
+ },
805
+ };
806
+ await writeFleetState(stateFile, initialState);
807
+
808
+ const updatedState = await removeAgentState(stateFile, "agent-to-remove");
809
+
810
+ expect(updatedState.fleet.started_at).toBe("2024-01-15T10:30:00Z");
811
+ });
812
+ });
813
+
814
+ describe("concurrent operations", () => {
815
+ let tempDir: string;
816
+
817
+ beforeEach(async () => {
818
+ tempDir = await createTempDir();
819
+ });
820
+
821
+ afterEach(async () => {
822
+ await rm(tempDir, { recursive: true, force: true });
823
+ });
824
+
825
+ it("handles multiple concurrent reads", async () => {
826
+ const stateFile = join(tempDir, "state.yaml");
827
+ const state: FleetState = {
828
+ fleet: { started_at: "2024-01-15T10:30:00Z" },
829
+ agents: {
830
+ agent: { status: "running" },
831
+ },
832
+ };
833
+ await writeFleetState(stateFile, state);
834
+
835
+ const reads = [];
836
+ for (let i = 0; i < 50; i++) {
837
+ reads.push(readFleetState(stateFile));
838
+ }
839
+
840
+ const results = await Promise.all(reads);
841
+
842
+ for (const result of results) {
843
+ expect(result.fleet.started_at).toBe("2024-01-15T10:30:00Z");
844
+ expect(result.agents.agent.status).toBe("running");
845
+ }
846
+ });
847
+
848
+ it("handles sequential updates correctly", async () => {
849
+ const stateFile = join(tempDir, "state.yaml");
850
+ await writeFleetState(stateFile, createInitialFleetState());
851
+
852
+ // Sequential updates (not concurrent to avoid race conditions)
853
+ for (let i = 0; i < 10; i++) {
854
+ await updateAgentState(stateFile, `agent-${i}`, {
855
+ status: "running",
856
+ current_job: `job-${i}`,
857
+ });
858
+ }
859
+
860
+ const finalState = await readFleetState(stateFile);
861
+
862
+ expect(Object.keys(finalState.agents)).toHaveLength(10);
863
+ for (let i = 0; i < 10; i++) {
864
+ expect(finalState.agents[`agent-${i}`].status).toBe("running");
865
+ expect(finalState.agents[`agent-${i}`].current_job).toBe(`job-${i}`);
866
+ }
867
+ });
868
+ });