@cyanautomation/kaseki-agent 1.4.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 (459) hide show
  1. package/.dockerignore +54 -0
  2. package/.eslintignore +11 -0
  3. package/.eslintrc.json +95 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +53 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +53 -0
  6. package/.github/ISSUE_TEMPLATE/security.md +51 -0
  7. package/.github/PULL_REQUEST_TEMPLATE/default.md +71 -0
  8. package/.github/dependabot.yml +38 -0
  9. package/.github/skills/dependency-cache-optimization/SKILL.md +526 -0
  10. package/.github/skills/docker-image-management/SKILL.md +532 -0
  11. package/.github/skills/frontend-design/SKILL.md +782 -0
  12. package/.github/skills/prompt-engineering/SKILL.md +360 -0
  13. package/.github/skills/quality-gate-config/SKILL.md +591 -0
  14. package/.github/skills/result-report-analysis/SKILL.md +576 -0
  15. package/.github/skills/test-automation/SKILL.md +593 -0
  16. package/.github/skills/workflow-diagnosis/SKILL.md +468 -0
  17. package/.github/workflows/build-docker-image.yml +453 -0
  18. package/.github/workflows/release.yml +68 -0
  19. package/.releaserc.json +135 -0
  20. package/CHANGELOG.md +117 -0
  21. package/CLAUDE.md +336 -0
  22. package/CONTRIBUTING.md +339 -0
  23. package/Dockerfile +217 -0
  24. package/README.md +1527 -0
  25. package/STYLE.md +521 -0
  26. package/add-js-extensions.d.ts +9 -0
  27. package/add-js-extensions.d.ts.map +1 -0
  28. package/add-js-extensions.js.map +1 -0
  29. package/dist/add-js-extensions.d.ts +9 -0
  30. package/dist/add-js-extensions.d.ts.map +1 -0
  31. package/dist/add-js-extensions.js +52 -0
  32. package/dist/add-js-extensions.js.map +1 -0
  33. package/dist/ansi-colors.d.ts +26 -0
  34. package/dist/ansi-colors.d.ts.map +1 -0
  35. package/dist/ansi-colors.js +51 -0
  36. package/dist/ansi-colors.js.map +1 -0
  37. package/dist/cli/BaseCommand.d.ts +18 -0
  38. package/dist/cli/BaseCommand.d.ts.map +1 -0
  39. package/dist/cli/BaseCommand.js +31 -0
  40. package/dist/cli/BaseCommand.js.map +1 -0
  41. package/dist/cli/KasekiCLI.d.ts +30 -0
  42. package/dist/cli/KasekiCLI.d.ts.map +1 -0
  43. package/dist/cli/KasekiCLI.js +134 -0
  44. package/dist/cli/KasekiCLI.js.map +1 -0
  45. package/dist/cli/commands/ConfigCommand.d.ts +13 -0
  46. package/dist/cli/commands/ConfigCommand.d.ts.map +1 -0
  47. package/dist/cli/commands/ConfigCommand.js +131 -0
  48. package/dist/cli/commands/ConfigCommand.js.map +1 -0
  49. package/dist/cli/commands/DoctorCommand.d.ts +45 -0
  50. package/dist/cli/commands/DoctorCommand.d.ts.map +1 -0
  51. package/dist/cli/commands/DoctorCommand.js +309 -0
  52. package/dist/cli/commands/DoctorCommand.js.map +1 -0
  53. package/dist/cli/commands/ListCommand.d.ts +9 -0
  54. package/dist/cli/commands/ListCommand.d.ts.map +1 -0
  55. package/dist/cli/commands/ListCommand.js +81 -0
  56. package/dist/cli/commands/ListCommand.js.map +1 -0
  57. package/dist/cli/commands/ReportCommand.d.ts +9 -0
  58. package/dist/cli/commands/ReportCommand.d.ts.map +1 -0
  59. package/dist/cli/commands/ReportCommand.js +98 -0
  60. package/dist/cli/commands/ReportCommand.js.map +1 -0
  61. package/dist/cli/commands/RunCommand.d.ts +13 -0
  62. package/dist/cli/commands/RunCommand.d.ts.map +1 -0
  63. package/dist/cli/commands/RunCommand.js +191 -0
  64. package/dist/cli/commands/RunCommand.js.map +1 -0
  65. package/dist/cli/commands/SecretsCommand.d.ts +9 -0
  66. package/dist/cli/commands/SecretsCommand.d.ts.map +1 -0
  67. package/dist/cli/commands/SecretsCommand.js +109 -0
  68. package/dist/cli/commands/SecretsCommand.js.map +1 -0
  69. package/dist/cli/commands/ServeCommand.d.ts +9 -0
  70. package/dist/cli/commands/ServeCommand.d.ts.map +1 -0
  71. package/dist/cli/commands/ServeCommand.js +50 -0
  72. package/dist/cli/commands/ServeCommand.js.map +1 -0
  73. package/dist/cli/commands/SetupCommand.d.ts +42 -0
  74. package/dist/cli/commands/SetupCommand.d.ts.map +1 -0
  75. package/dist/cli/commands/SetupCommand.js +249 -0
  76. package/dist/cli/commands/SetupCommand.js.map +1 -0
  77. package/dist/cli.d.ts +9 -0
  78. package/dist/cli.d.ts.map +1 -0
  79. package/dist/cli.js +130 -0
  80. package/dist/cli.js.map +1 -0
  81. package/dist/config/ConfigManager.d.ts +395 -0
  82. package/dist/config/ConfigManager.d.ts.map +1 -0
  83. package/dist/config/ConfigManager.js +446 -0
  84. package/dist/config/ConfigManager.js.map +1 -0
  85. package/dist/docker/DockerManager.d.ts +69 -0
  86. package/dist/docker/DockerManager.d.ts.map +1 -0
  87. package/dist/docker/DockerManager.js +266 -0
  88. package/dist/docker/DockerManager.js.map +1 -0
  89. package/dist/event-aggregator.d.ts +71 -0
  90. package/dist/event-aggregator.d.ts.map +1 -0
  91. package/dist/event-aggregator.js +95 -0
  92. package/dist/event-aggregator.js.map +1 -0
  93. package/dist/github-app-token.d.ts +16 -0
  94. package/dist/github-app-token.d.ts.map +1 -0
  95. package/dist/github-app-token.js +148 -0
  96. package/dist/github-app-token.js.map +1 -0
  97. package/dist/idempotency-store.d.ts +61 -0
  98. package/dist/idempotency-store.d.ts.map +1 -0
  99. package/dist/idempotency-store.js +321 -0
  100. package/dist/idempotency-store.js.map +1 -0
  101. package/dist/index.d.ts +25 -0
  102. package/dist/index.d.ts.map +1 -0
  103. package/dist/index.js +31 -0
  104. package/dist/index.js.map +1 -0
  105. package/dist/instance/InstanceManager.d.ts +81 -0
  106. package/dist/instance/InstanceManager.d.ts.map +1 -0
  107. package/dist/instance/InstanceManager.js +220 -0
  108. package/dist/instance/InstanceManager.js.map +1 -0
  109. package/dist/instance-metadata-reader.d.ts +48 -0
  110. package/dist/instance-metadata-reader.d.ts.map +1 -0
  111. package/dist/instance-metadata-reader.js +94 -0
  112. package/dist/instance-metadata-reader.js.map +1 -0
  113. package/dist/instance-state-derivation.d.ts +42 -0
  114. package/dist/instance-state-derivation.d.ts.map +1 -0
  115. package/dist/instance-state-derivation.js +133 -0
  116. package/dist/instance-state-derivation.js.map +1 -0
  117. package/dist/job-scheduler.d.ts +124 -0
  118. package/dist/job-scheduler.d.ts.map +1 -0
  119. package/dist/job-scheduler.js +992 -0
  120. package/dist/job-scheduler.js.map +1 -0
  121. package/dist/kaseki-api-client.d.ts +89 -0
  122. package/dist/kaseki-api-client.d.ts.map +1 -0
  123. package/dist/kaseki-api-client.js +405 -0
  124. package/dist/kaseki-api-client.js.map +1 -0
  125. package/dist/kaseki-api-config.d.ts +34 -0
  126. package/dist/kaseki-api-config.d.ts.map +1 -0
  127. package/dist/kaseki-api-config.js +113 -0
  128. package/dist/kaseki-api-config.js.map +1 -0
  129. package/dist/kaseki-api-routes.d.ts +13 -0
  130. package/dist/kaseki-api-routes.d.ts.map +1 -0
  131. package/dist/kaseki-api-routes.js +559 -0
  132. package/dist/kaseki-api-routes.js.map +1 -0
  133. package/dist/kaseki-api-service-wrapper.d.ts +43 -0
  134. package/dist/kaseki-api-service-wrapper.d.ts.map +1 -0
  135. package/dist/kaseki-api-service-wrapper.js +150 -0
  136. package/dist/kaseki-api-service-wrapper.js.map +1 -0
  137. package/dist/kaseki-api-service.d.ts +16 -0
  138. package/dist/kaseki-api-service.d.ts.map +1 -0
  139. package/dist/kaseki-api-service.js +143 -0
  140. package/dist/kaseki-api-service.js.map +1 -0
  141. package/dist/kaseki-api-types.d.ts +440 -0
  142. package/dist/kaseki-api-types.d.ts.map +1 -0
  143. package/dist/kaseki-api-types.js +64 -0
  144. package/dist/kaseki-api-types.js.map +1 -0
  145. package/dist/kaseki-cli-lib.d.ts +219 -0
  146. package/dist/kaseki-cli-lib.d.ts.map +1 -0
  147. package/dist/kaseki-cli-lib.js +523 -0
  148. package/dist/kaseki-cli-lib.js.map +1 -0
  149. package/dist/kaseki-cli.d.ts +38 -0
  150. package/dist/kaseki-cli.d.ts.map +1 -0
  151. package/dist/kaseki-cli.js +559 -0
  152. package/dist/kaseki-cli.js.map +1 -0
  153. package/dist/kaseki-report.d.ts +3 -0
  154. package/dist/kaseki-report.d.ts.map +1 -0
  155. package/dist/kaseki-report.js +140 -0
  156. package/dist/kaseki-report.js.map +1 -0
  157. package/dist/lib/subprocess-helpers.d.ts +98 -0
  158. package/dist/lib/subprocess-helpers.d.ts.map +1 -0
  159. package/dist/lib/subprocess-helpers.js +136 -0
  160. package/dist/lib/subprocess-helpers.js.map +1 -0
  161. package/dist/logger.d.ts +39 -0
  162. package/dist/logger.d.ts.map +1 -0
  163. package/dist/logger.js +79 -0
  164. package/dist/logger.js.map +1 -0
  165. package/dist/metrics.d.ts +19 -0
  166. package/dist/metrics.d.ts.map +1 -0
  167. package/dist/metrics.js +59 -0
  168. package/dist/metrics.js.map +1 -0
  169. package/dist/middleware/job-lookup.d.ts +27 -0
  170. package/dist/middleware/job-lookup.d.ts.map +1 -0
  171. package/dist/middleware/job-lookup.js +28 -0
  172. package/dist/middleware/job-lookup.js.map +1 -0
  173. package/dist/pi-event-filter.d.ts +3 -0
  174. package/dist/pi-event-filter.d.ts.map +1 -0
  175. package/dist/pi-event-filter.js +126 -0
  176. package/dist/pi-event-filter.js.map +1 -0
  177. package/dist/pi-progress-stream.d.ts +3 -0
  178. package/dist/pi-progress-stream.d.ts.map +1 -0
  179. package/dist/pi-progress-stream.js +205 -0
  180. package/dist/pi-progress-stream.js.map +1 -0
  181. package/dist/pi-progress-summarizer.d.ts +61 -0
  182. package/dist/pi-progress-summarizer.d.ts.map +1 -0
  183. package/dist/pi-progress-summarizer.js +246 -0
  184. package/dist/pi-progress-summarizer.js.map +1 -0
  185. package/dist/pre-flight-validator.d.ts +72 -0
  186. package/dist/pre-flight-validator.d.ts.map +1 -0
  187. package/dist/pre-flight-validator.js +513 -0
  188. package/dist/pre-flight-validator.js.map +1 -0
  189. package/dist/progress-stream-utils.d.ts +3 -0
  190. package/dist/progress-stream-utils.d.ts.map +1 -0
  191. package/dist/progress-stream-utils.js +15 -0
  192. package/dist/progress-stream-utils.js.map +1 -0
  193. package/dist/result-cache.d.ts +52 -0
  194. package/dist/result-cache.d.ts.map +1 -0
  195. package/dist/result-cache.js +134 -0
  196. package/dist/result-cache.js.map +1 -0
  197. package/dist/routes/artifact-routes.d.ts +10 -0
  198. package/dist/routes/artifact-routes.d.ts.map +1 -0
  199. package/dist/routes/artifact-routes.js +126 -0
  200. package/dist/routes/artifact-routes.js.map +1 -0
  201. package/dist/routes/log-routes.d.ts +8 -0
  202. package/dist/routes/log-routes.d.ts.map +1 -0
  203. package/dist/routes/log-routes.js +345 -0
  204. package/dist/routes/log-routes.js.map +1 -0
  205. package/dist/routes/status-routes.d.ts +8 -0
  206. package/dist/routes/status-routes.d.ts.map +1 -0
  207. package/dist/routes/status-routes.js +82 -0
  208. package/dist/routes/status-routes.js.map +1 -0
  209. package/dist/routes/webhook-routes.d.ts +6 -0
  210. package/dist/routes/webhook-routes.d.ts.map +1 -0
  211. package/dist/routes/webhook-routes.js +86 -0
  212. package/dist/routes/webhook-routes.js.map +1 -0
  213. package/dist/run-artifact-metadata-cache.d.ts +42 -0
  214. package/dist/run-artifact-metadata-cache.d.ts.map +1 -0
  215. package/dist/run-artifact-metadata-cache.js +139 -0
  216. package/dist/run-artifact-metadata-cache.js.map +1 -0
  217. package/dist/secret-value-cache.d.ts +13 -0
  218. package/dist/secret-value-cache.d.ts.map +1 -0
  219. package/dist/secret-value-cache.js +44 -0
  220. package/dist/secret-value-cache.js.map +1 -0
  221. package/dist/secrets/SecretsManager.d.ts +80 -0
  222. package/dist/secrets/SecretsManager.d.ts.map +1 -0
  223. package/dist/secrets/SecretsManager.js +306 -0
  224. package/dist/secrets/SecretsManager.js.map +1 -0
  225. package/dist/test-utils.d.ts +55 -0
  226. package/dist/test-utils.d.ts.map +1 -0
  227. package/dist/test-utils.js +48 -0
  228. package/dist/test-utils.js.map +1 -0
  229. package/dist/timestamp-tracker.d.ts +75 -0
  230. package/dist/timestamp-tracker.d.ts.map +1 -0
  231. package/dist/timestamp-tracker.js +121 -0
  232. package/dist/timestamp-tracker.js.map +1 -0
  233. package/dist/utils/failure-artifact-writer.d.ts +29 -0
  234. package/dist/utils/failure-artifact-writer.d.ts.map +1 -0
  235. package/dist/utils/failure-artifact-writer.js +157 -0
  236. package/dist/utils/failure-artifact-writer.js.map +1 -0
  237. package/dist/utils/file-helpers.d.ts +41 -0
  238. package/dist/utils/file-helpers.d.ts.map +1 -0
  239. package/dist/utils/file-helpers.js +143 -0
  240. package/dist/utils/file-helpers.js.map +1 -0
  241. package/dist/utils/http-client-factory.d.ts +46 -0
  242. package/dist/utils/http-client-factory.d.ts.map +1 -0
  243. package/dist/utils/http-client-factory.js +114 -0
  244. package/dist/utils/http-client-factory.js.map +1 -0
  245. package/dist/utils/progress-normalizer.d.ts +13 -0
  246. package/dist/utils/progress-normalizer.d.ts.map +1 -0
  247. package/dist/utils/progress-normalizer.js +57 -0
  248. package/dist/utils/progress-normalizer.js.map +1 -0
  249. package/dist/utils/response-helpers.d.ts +34 -0
  250. package/dist/utils/response-helpers.d.ts.map +1 -0
  251. package/dist/utils/response-helpers.js +78 -0
  252. package/dist/utils/response-helpers.js.map +1 -0
  253. package/dist/utils/route-helpers.d.ts +17 -0
  254. package/dist/utils/route-helpers.d.ts.map +1 -0
  255. package/dist/utils/route-helpers.js +22 -0
  256. package/dist/utils/route-helpers.js.map +1 -0
  257. package/dist/utils/status-response-builder.d.ts +23 -0
  258. package/dist/utils/status-response-builder.d.ts.map +1 -0
  259. package/dist/utils/status-response-builder.js +144 -0
  260. package/dist/utils/status-response-builder.js.map +1 -0
  261. package/dist/utils/type-guards.d.ts +37 -0
  262. package/dist/utils/type-guards.d.ts.map +1 -0
  263. package/dist/utils/type-guards.js +45 -0
  264. package/dist/utils/type-guards.js.map +1 -0
  265. package/dist/utils/utf8-helpers.d.ts +32 -0
  266. package/dist/utils/utf8-helpers.d.ts.map +1 -0
  267. package/dist/utils/utf8-helpers.js +97 -0
  268. package/dist/utils/utf8-helpers.js.map +1 -0
  269. package/dist/utils/webhook-event-builder.d.ts +26 -0
  270. package/dist/utils/webhook-event-builder.d.ts.map +1 -0
  271. package/dist/utils/webhook-event-builder.js +77 -0
  272. package/dist/utils/webhook-event-builder.js.map +1 -0
  273. package/dist/webhook-manager.d.ts +56 -0
  274. package/dist/webhook-manager.d.ts.map +1 -0
  275. package/dist/webhook-manager.js +359 -0
  276. package/dist/webhook-manager.js.map +1 -0
  277. package/docker/workspace-cache/package-lock.json +13 -0
  278. package/docker/workspace-cache/package.json +7 -0
  279. package/docker-compose.yml +53 -0
  280. package/docs/API.md +708 -0
  281. package/docs/BACKLOG.md +19 -0
  282. package/docs/BUILD_STRATEGY.md +404 -0
  283. package/docs/CLI.md +569 -0
  284. package/docs/DEPLOYMENT.md +521 -0
  285. package/docs/DEVELOPMENT.md +459 -0
  286. package/docs/DOCKER_SETUP.md +522 -0
  287. package/docs/ENHANCED_PROGRESS_LOGS.md +264 -0
  288. package/docs/IMPLEMENTATION_SUMMARY.md +549 -0
  289. package/docs/INTEGRATION_EXAMPLE.md +217 -0
  290. package/docs/NPM_SETUP.md +468 -0
  291. package/docs/PHASE1-4_IMPLEMENTATION.md +302 -0
  292. package/docs/PHASE1_COMPLETION.md +192 -0
  293. package/docs/PHASE2_COMPLETION.md +134 -0
  294. package/docs/PHASE6_MIGRATION.md +392 -0
  295. package/docs/PRINTF_SAFETY_FIX.md +282 -0
  296. package/docs/QUALITY_GATES.md +369 -0
  297. package/docs/SETUP_GUIDE.md +482 -0
  298. package/docs/TASK_PROMPT_TEMPLATES.md +533 -0
  299. package/docs/VALIDATION_FIX.md +139 -0
  300. package/docs/VERIFICATION_CHECKLIST.md +335 -0
  301. package/docs/repo-maturity.md +760 -0
  302. package/fix-tests.d.ts +9 -0
  303. package/fix-tests.d.ts.map +1 -0
  304. package/fix-tests.js.map +1 -0
  305. package/fix-tests.ts +53 -0
  306. package/jest.config.ts +31 -0
  307. package/kaseki +183 -0
  308. package/kaseki-agent.sh +1961 -0
  309. package/ops/logrotate/kaseki +10 -0
  310. package/package.json +83 -0
  311. package/perf/README.md +54 -0
  312. package/perf/pi-event-filter.benchmark.test.ts +98 -0
  313. package/run-kaseki-json.test.sh +106 -0
  314. package/run-kaseki.sh +990 -0
  315. package/scripts/allowlist-helper.sh +56 -0
  316. package/scripts/cleanup-kaseki.sh +168 -0
  317. package/scripts/deploy-pi-template.sh +293 -0
  318. package/scripts/docker-entrypoint.sh +71 -0
  319. package/scripts/dry-run-allowlist.sh +161 -0
  320. package/scripts/kaseki-activate.sh +396 -0
  321. package/scripts/kaseki-api.service +62 -0
  322. package/scripts/kaseki-container-entrypoint-wrapper.sh +119 -0
  323. package/scripts/kaseki-container-setup-remote.sh +172 -0
  324. package/scripts/kaseki-container-setup.sh +193 -0
  325. package/scripts/kaseki-healthcheck.sh +95 -0
  326. package/scripts/kaseki-install.sh +50 -0
  327. package/scripts/kaseki-maturity-score.sh +291 -0
  328. package/scripts/kaseki-performance-metrics.sh +122 -0
  329. package/scripts/kaseki-preflight.sh +270 -0
  330. package/scripts/kaseki-setup.sh +265 -0
  331. package/scripts/pi-setup-remote.sh +213 -0
  332. package/scripts/setup-github-labels.sh +42 -0
  333. package/scripts/suggest-allowlist.sh +68 -0
  334. package/scripts/templates/MULTI_HOST_DISTRIBUTED.md +337 -0
  335. package/scripts/templates/REST_API_SERVICE.md +490 -0
  336. package/scripts/templates/SINGLE_HOST_CLI.md +194 -0
  337. package/scripts/test-github-app.sh +248 -0
  338. package/src/add-js-extensions.ts +61 -0
  339. package/src/ansi-colors.test.ts +62 -0
  340. package/src/ansi-colors.ts +67 -0
  341. package/src/cli/BaseCommand.ts +40 -0
  342. package/src/cli/KasekiCLI.ts +154 -0
  343. package/src/cli/commands/ConfigCommand.ts +145 -0
  344. package/src/cli/commands/DoctorCommand.ts +329 -0
  345. package/src/cli/commands/ListCommand.ts +105 -0
  346. package/src/cli/commands/ReportCommand.ts +110 -0
  347. package/src/cli/commands/RunCommand.ts +218 -0
  348. package/src/cli/commands/SecretsCommand.ts +120 -0
  349. package/src/cli/commands/ServeCommand.ts +62 -0
  350. package/src/cli/commands/SetupCommand.ts +301 -0
  351. package/src/cli.ts +138 -0
  352. package/src/config/ConfigManager.ts +476 -0
  353. package/src/docker/DockerManager.ts +319 -0
  354. package/src/docker-entrypoint-packaging.test.ts +33 -0
  355. package/src/event-aggregator.test.ts +117 -0
  356. package/src/event-aggregator.ts +126 -0
  357. package/src/github-app-token.ts +215 -0
  358. package/src/idempotency-store.test.ts +117 -0
  359. package/src/idempotency-store.ts +385 -0
  360. package/src/index.ts +89 -0
  361. package/src/instance/InstanceManager.ts +285 -0
  362. package/src/instance-metadata-reader.test.ts +190 -0
  363. package/src/instance-metadata-reader.ts +129 -0
  364. package/src/instance-state-derivation.test.ts +263 -0
  365. package/src/instance-state-derivation.ts +148 -0
  366. package/src/job-scheduler.test.ts +1236 -0
  367. package/src/job-scheduler.ts +1117 -0
  368. package/src/kaseki-api-client.ts +488 -0
  369. package/src/kaseki-api-config.test.ts +315 -0
  370. package/src/kaseki-api-config.ts +175 -0
  371. package/src/kaseki-api-routes.test.ts +1615 -0
  372. package/src/kaseki-api-routes.ts +643 -0
  373. package/src/kaseki-api-service-wrapper.ts +188 -0
  374. package/src/kaseki-api-service.test.ts +418 -0
  375. package/src/kaseki-api-service.ts +192 -0
  376. package/src/kaseki-api-types.ts +320 -0
  377. package/src/kaseki-cli-lib.test.ts +552 -0
  378. package/src/kaseki-cli-lib.ts +760 -0
  379. package/src/kaseki-cli.ts +682 -0
  380. package/src/kaseki-report.test.ts +118 -0
  381. package/src/kaseki-report.ts +192 -0
  382. package/src/lib/subprocess-helpers.ts +177 -0
  383. package/src/logger.ts +114 -0
  384. package/src/metrics.ts +66 -0
  385. package/src/middleware/job-lookup.test.ts +113 -0
  386. package/src/middleware/job-lookup.ts +45 -0
  387. package/src/pi-event-filter.test.ts +183 -0
  388. package/src/pi-event-filter.ts +183 -0
  389. package/src/pi-progress-stream.ts +287 -0
  390. package/src/pi-progress-summarizer.test.ts +302 -0
  391. package/src/pi-progress-summarizer.ts +287 -0
  392. package/src/pre-flight-validator.test.ts +512 -0
  393. package/src/pre-flight-validator.ts +618 -0
  394. package/src/progress-stream-utils.test.ts +35 -0
  395. package/src/progress-stream-utils.ts +14 -0
  396. package/src/result-cache.test.ts +195 -0
  397. package/src/result-cache.ts +181 -0
  398. package/src/routes/artifact-routes.ts +169 -0
  399. package/src/routes/log-routes.ts +391 -0
  400. package/src/routes/status-routes.ts +92 -0
  401. package/src/routes/webhook-routes.ts +97 -0
  402. package/src/run-artifact-metadata-cache.test.ts +80 -0
  403. package/src/run-artifact-metadata-cache.ts +184 -0
  404. package/src/secret-value-cache.test.ts +66 -0
  405. package/src/secret-value-cache.ts +55 -0
  406. package/src/secrets/SecretsManager.ts +343 -0
  407. package/src/test-utils.ts +81 -0
  408. package/src/timestamp-tracker.test.ts +134 -0
  409. package/src/timestamp-tracker.ts +132 -0
  410. package/src/utils/failure-artifact-writer.ts +187 -0
  411. package/src/utils/file-helpers.test.ts +235 -0
  412. package/src/utils/file-helpers.ts +150 -0
  413. package/src/utils/http-client-factory.test.ts +245 -0
  414. package/src/utils/http-client-factory.ts +157 -0
  415. package/src/utils/progress-normalizer.test.ts +442 -0
  416. package/src/utils/progress-normalizer.ts +68 -0
  417. package/src/utils/response-helpers.test.ts +122 -0
  418. package/src/utils/response-helpers.ts +101 -0
  419. package/src/utils/route-helpers.ts +30 -0
  420. package/src/utils/status-response-builder.ts +159 -0
  421. package/src/utils/type-guards.ts +52 -0
  422. package/src/utils/utf8-helpers.ts +102 -0
  423. package/src/utils/webhook-event-builder.test.ts +143 -0
  424. package/src/utils/webhook-event-builder.ts +87 -0
  425. package/src/webhook-manager.test.ts +152 -0
  426. package/src/webhook-manager.ts +445 -0
  427. package/templates/allowlist-api-route.txt +7 -0
  428. package/templates/allowlist-comprehensive.txt +8 -0
  429. package/templates/allowlist-parser-fix.txt +6 -0
  430. package/templates/allowlist-ui-component.txt +9 -0
  431. package/templates/allowlist-utility.txt +9 -0
  432. package/test/actual-model-metadata.test.sh +102 -0
  433. package/test/dry-run.test.sh +131 -0
  434. package/test/fixtures/kaseki-report-exit-codes/metadata-exit-0.json +1 -0
  435. package/test/fixtures/kaseki-report-exit-codes/metadata-exit-1.json +1 -0
  436. package/test/fixtures/kaseki-report-exit-codes/metadata-exit-invalid.json +1 -0
  437. package/test/fixtures/kaseki-report-exit-codes/metadata-exit-str-0.json +1 -0
  438. package/test/fixtures/kaseki-report-exit-codes/metadata-exit-str-1.json +1 -0
  439. package/test/kaseki-api.integration.test.sh +165 -0
  440. package/test/pi-event-filter-failure.test.sh +83 -0
  441. package/test/printf-safety-focused.test.sh +99 -0
  442. package/test/printf-safety-results/results/restoration.jsonl +10 -0
  443. package/test/printf-safety-results/results/test.jsonl +0 -0
  444. package/test/printf-safety.test.sh +297 -0
  445. package/test/validation-fix.test.sh +79 -0
  446. package/test/validation-integration.test.sh +109 -0
  447. package/tests/allowlist-glob.test.sh +61 -0
  448. package/tests/dependency-cache-key.test.sh +48 -0
  449. package/tests/dependency-restore-mode.test.sh +48 -0
  450. package/tests/doctor-template-parity.test.sh +95 -0
  451. package/tests/github-operations.test.sh +142 -0
  452. package/tests/npm-install-flags.test.sh +58 -0
  453. package/tests/quality-gates.test.sh +178 -0
  454. package/tests/repo-memory.test.sh +103 -0
  455. package/tests/restore-disallowed-changes.test.sh +80 -0
  456. package/tests/validation-missing-npm-scripts.test.sh +93 -0
  457. package/tests/validation-strict-mode.test.sh +118 -0
  458. package/tsconfig.changed.json +7 -0
  459. package/tsconfig.json +39 -0
package/src/index.ts ADDED
@@ -0,0 +1,89 @@
1
+ // Kaseki Agent TypeScript Entry Point
2
+ // Public API exports for library users and service consumers
3
+
4
+ // Services
5
+ export { JobScheduler } from './job-scheduler';
6
+ export { ResultCache } from './result-cache';
7
+ export { WebhookManager } from './webhook-manager';
8
+
9
+ // Configuration & Utilities
10
+ export { KasekiApiConfig, validateApiKey, loadConfig } from './kaseki-api-config';
11
+ export { createGracefulShutdown, assertSupportedNodeVersion } from './kaseki-api-service';
12
+
13
+ // Types
14
+ export type {
15
+ Job,
16
+ RunRequest,
17
+ RunResponse,
18
+ StatusResponse,
19
+ StructuredProgress,
20
+ LogResponse,
21
+ ArtifactResponse,
22
+ RunsListResponse,
23
+ ErrorResponse,
24
+ ValidationResponse,
25
+ PreflightCheck,
26
+ PreflightResponse,
27
+ AnalysisResponse,
28
+ WebhookPayload,
29
+ WebhookEventType,
30
+ } from './kaseki-api-types';
31
+
32
+ // Client
33
+ export { KasekiApiClient } from './kaseki-api-client';
34
+
35
+ // Utilities (Phase 1 extractions)
36
+ export { sendErrorResponse, buildStatusResponse, detectContentType } from './utils/response-helpers';
37
+ export {
38
+ isNonEmptyFile,
39
+ isNonEmptyFile as fileIsNonEmpty,
40
+ readFirstLine,
41
+ readTailLines,
42
+ commandOutput,
43
+ fileExists,
44
+ readFileContent,
45
+ getFileStats,
46
+ } from './utils/file-helpers';
47
+ export {
48
+ isString,
49
+ isNumber,
50
+ isBoolean,
51
+ isRecord,
52
+ isArray,
53
+ isStringArray,
54
+ isRecordArray,
55
+ hasKeys,
56
+ } from './utils/type-guards';
57
+ export {
58
+ decodeUtf8TailSafely,
59
+ tailLogByLines,
60
+ readTailBytes,
61
+ } from './utils/utf8-helpers';
62
+ export { getJobOrRespond } from './utils/route-helpers';
63
+ export { jobLookupMiddleware } from './middleware/job-lookup';
64
+
65
+ // Utilities (Phase 2 consolidations)
66
+ export {
67
+ createJobSubmittedEvent,
68
+ createJobStartedEvent,
69
+ createJobCompletedEvent,
70
+ createJobCancelledEvent,
71
+ createJobFailedEvent,
72
+ } from './utils/webhook-event-builder';
73
+ export { HttpClientFactory } from './utils/http-client-factory';
74
+ export type { HttpRequestOptions, RetryConfig } from './utils/http-client-factory';
75
+
76
+ // Utilities (Phase 3 refactoring)
77
+ export { FailureArtifactWriter } from './utils/failure-artifact-writer';
78
+ export type { CleanupResult } from './utils/failure-artifact-writer';
79
+ export { StatusResponseBuilder } from './utils/status-response-builder';
80
+ export {
81
+ normalizeProgressEvent,
82
+ toStructuredProgress,
83
+ } from './utils/progress-normalizer';
84
+
85
+ // Route modules (Phase 3 refactoring)
86
+ export { createStatusRoutes } from './routes/status-routes';
87
+ export { createLogRoutes } from './routes/log-routes';
88
+ export { createArtifactRoutes, readArtifactContent } from './routes/artifact-routes';
89
+ export { createWebhookRoutes } from './routes/webhook-routes';
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Instance Manager
3
+ *
4
+ * Handles kaseki instance lifecycle: naming, directories, metadata
5
+ */
6
+
7
+ import fs from 'fs/promises';
8
+ import path from 'path';
9
+ import { createLogger } from '../logger';
10
+
11
+ const logger = createLogger('instance');
12
+
13
+ export interface InstanceMetadata {
14
+ id: string;
15
+ createdAt: string;
16
+ completedAt?: string;
17
+ status: 'running' | 'completed' | 'failed';
18
+ exitCode?: number;
19
+ model?: string;
20
+ provider?: string;
21
+ repoUrl?: string;
22
+ gitRef?: string;
23
+ stages?: {
24
+ [stageName: string]: {
25
+ startTime?: string;
26
+ endTime?: string;
27
+ exitCode?: number;
28
+ duration?: number;
29
+ };
30
+ };
31
+ }
32
+
33
+ export class InstanceManager {
34
+ private rootDir: string;
35
+ private instanceId: string | null = null;
36
+
37
+ constructor(rootDir: string = '/agents') {
38
+ this.rootDir = rootDir;
39
+ }
40
+
41
+ /**
42
+ * Generate or get next instance ID
43
+ */
44
+ async getOrCreateInstanceId(): Promise<string> {
45
+ if (this.instanceId) {
46
+ return this.instanceId;
47
+ }
48
+
49
+ // Find next available instance number
50
+ const runsDir = path.join(this.rootDir, 'kaseki-runs');
51
+ let instanceNum = 1;
52
+
53
+ try {
54
+ const entries = await fs.readdir(runsDir, { withFileTypes: true });
55
+ const existingNums = entries
56
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith('kaseki-'))
57
+ .map((entry) => {
58
+ const match = entry.name.match(/kaseki-(\d+)/);
59
+ return match ? parseInt(match[1], 10) : 0;
60
+ })
61
+ .filter((num) => num > 0);
62
+
63
+ if (existingNums.length > 0) {
64
+ instanceNum = Math.max(...existingNums) + 1;
65
+ }
66
+ } catch {
67
+ // Directory doesn't exist yet, that's fine
68
+ instanceNum = 1;
69
+ }
70
+
71
+ this.instanceId = `kaseki-${instanceNum}`;
72
+ logger.debug(`Generated instance ID: ${this.instanceId}`);
73
+
74
+ return this.instanceId;
75
+ }
76
+
77
+ /**
78
+ * Get workspace directory path
79
+ */
80
+ async getWorkspaceDir(): Promise<string> {
81
+ const id = await this.getOrCreateInstanceId();
82
+ return path.join(this.rootDir, 'kaseki-runs', id);
83
+ }
84
+
85
+ /**
86
+ * Get results directory path
87
+ */
88
+ async getResultsDir(): Promise<string> {
89
+ const id = await this.getOrCreateInstanceId();
90
+ return path.join(this.rootDir, 'kaseki-results', id);
91
+ }
92
+
93
+ /**
94
+ * Get cache directory path
95
+ */
96
+ getCacheDir(): string {
97
+ return path.join(this.rootDir, 'kaseki-cache');
98
+ }
99
+
100
+ /**
101
+ * Create instance directories
102
+ */
103
+ async createDirectories(): Promise<{ workspace: string; results: string }> {
104
+ const workspace = await this.getWorkspaceDir();
105
+ const results = await this.getResultsDir();
106
+
107
+ try {
108
+ await fs.mkdir(workspace, { recursive: true });
109
+ await fs.mkdir(results, { recursive: true });
110
+ logger.debug(`Created instance directories for ${this.instanceId}`);
111
+
112
+ return { workspace, results };
113
+ } catch (error) {
114
+ throw new Error(`Failed to create instance directories: ${error}`);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Initialize metadata file
120
+ */
121
+ async initializeMetadata(metadata: Partial<InstanceMetadata>): Promise<void> {
122
+ const id = await this.getOrCreateInstanceId();
123
+ const resultsDir = await this.getResultsDir();
124
+ const metadataPath = path.join(resultsDir, 'metadata.json');
125
+
126
+ const fullMetadata: InstanceMetadata = {
127
+ id,
128
+ createdAt: new Date().toISOString(),
129
+ status: 'running',
130
+ stages: {},
131
+ ...metadata,
132
+ };
133
+
134
+ try {
135
+ await fs.writeFile(
136
+ metadataPath,
137
+ JSON.stringify(fullMetadata, null, 2),
138
+ 'utf-8'
139
+ );
140
+ logger.debug(`Initialized metadata for ${id}`);
141
+ } catch (error) {
142
+ throw new Error(`Failed to initialize metadata: ${error}`);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Update metadata
148
+ */
149
+ async updateMetadata(updates: Partial<InstanceMetadata>): Promise<void> {
150
+ const resultsDir = await this.getResultsDir();
151
+ const metadataPath = path.join(resultsDir, 'metadata.json');
152
+
153
+ try {
154
+ let metadata: InstanceMetadata = {
155
+ id: this.instanceId || '',
156
+ createdAt: new Date().toISOString(),
157
+ status: 'running',
158
+ };
159
+
160
+ // Read existing metadata
161
+ try {
162
+ const content = await fs.readFile(metadataPath, 'utf-8');
163
+ metadata = JSON.parse(content);
164
+ } catch {
165
+ // File doesn't exist yet, that's fine
166
+ }
167
+
168
+ // Merge updates
169
+ const updated = { ...metadata, ...updates };
170
+
171
+ // Write back
172
+ await fs.writeFile(
173
+ metadataPath,
174
+ JSON.stringify(updated, null, 2),
175
+ 'utf-8'
176
+ );
177
+
178
+ logger.debug(`Updated metadata for ${this.instanceId}`);
179
+ } catch (error) {
180
+ logger.warn(`Failed to update metadata: ${error}`);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Record stage timing
186
+ */
187
+ async recordStage(
188
+ stageName: string,
189
+ exitCode?: number,
190
+ startTime?: Date,
191
+ endTime?: Date
192
+ ): Promise<void> {
193
+ const resultsDir = await this.getResultsDir();
194
+ const metadataPath = path.join(resultsDir, 'metadata.json');
195
+
196
+ try {
197
+ let metadata: InstanceMetadata = {
198
+ id: this.instanceId || '',
199
+ createdAt: new Date().toISOString(),
200
+ status: 'running',
201
+ };
202
+
203
+ try {
204
+ const content = await fs.readFile(metadataPath, 'utf-8');
205
+ metadata = JSON.parse(content);
206
+ } catch {
207
+ // File doesn't exist
208
+ }
209
+
210
+ if (!metadata.stages) {
211
+ metadata.stages = {};
212
+ }
213
+
214
+ metadata.stages[stageName] = {
215
+ startTime: startTime?.toISOString(),
216
+ endTime: endTime?.toISOString(),
217
+ exitCode,
218
+ duration: startTime && endTime ?
219
+ (endTime.getTime() - startTime.getTime()) / 1000 : undefined,
220
+ };
221
+
222
+ await fs.writeFile(
223
+ metadataPath,
224
+ JSON.stringify(metadata, null, 2),
225
+ 'utf-8'
226
+ );
227
+
228
+ logger.debug(`Recorded stage: ${stageName} (exit code: ${exitCode})`);
229
+ } catch (error) {
230
+ logger.warn(`Failed to record stage: ${error}`);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Finalize instance metadata
236
+ */
237
+ async finalize(exitCode: number): Promise<void> {
238
+ await this.updateMetadata({
239
+ completedAt: new Date().toISOString(),
240
+ status: exitCode === 0 ? 'completed' : 'failed',
241
+ exitCode,
242
+ });
243
+
244
+ logger.info(`Finalized instance ${this.instanceId}: exit code ${exitCode}`);
245
+ }
246
+
247
+ /**
248
+ * Get instance metadata
249
+ */
250
+ async getMetadata(): Promise<InstanceMetadata | null> {
251
+ try {
252
+ const resultsDir = await this.getResultsDir();
253
+ const metadataPath = path.join(resultsDir, 'metadata.json');
254
+ const content = await fs.readFile(metadataPath, 'utf-8');
255
+ return JSON.parse(content);
256
+ } catch {
257
+ return null;
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Clean up instance workspace (optional)
263
+ */
264
+ async cleanup(keepWorkspace: boolean = false): Promise<void> {
265
+ if (keepWorkspace) {
266
+ logger.debug(`Keeping workspace for ${this.instanceId}`);
267
+ return;
268
+ }
269
+
270
+ try {
271
+ const workspace = await this.getWorkspaceDir();
272
+ await fs.rm(workspace, { recursive: true, force: true });
273
+ logger.debug(`Cleaned up workspace for ${this.instanceId}`);
274
+ } catch (error) {
275
+ logger.warn(`Failed to cleanup workspace: ${error}`);
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Get instance ID
281
+ */
282
+ getInstanceId(): string | null {
283
+ return this.instanceId;
284
+ }
285
+ }
@@ -0,0 +1,190 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { readInstanceMetadata } from './instance-metadata-reader';
5
+
6
+ describe('instance-metadata-reader', () => {
7
+ let tempDir: string;
8
+
9
+ beforeEach(() => {
10
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kaseki-test-'));
11
+ });
12
+
13
+ afterEach(() => {
14
+ fs.rmSync(tempDir, { recursive: true, force: true });
15
+ });
16
+
17
+ it('should read metadata.json and host-start.json', () => {
18
+ const metadata = {
19
+ current_stage: 'validation',
20
+ exit_code: 0,
21
+ duration_seconds: 120,
22
+ model: 'claude-3-opus',
23
+ };
24
+
25
+ const hostStart = {
26
+ model: 'claude-3-opus',
27
+ repo_url: 'https://github.com/example/repo',
28
+ git_ref: 'main',
29
+ agentTimeoutSeconds: 1200,
30
+ };
31
+
32
+ fs.writeFileSync(path.join(tempDir, 'metadata.json'), JSON.stringify(metadata));
33
+ fs.writeFileSync(path.join(tempDir, 'host-start.json'), JSON.stringify(hostStart));
34
+
35
+ const result = readInstanceMetadata(tempDir);
36
+
37
+ expect(result.metadata).toEqual(metadata);
38
+ expect(result.hostStart).toEqual(hostStart);
39
+ expect(result.elapsedSeconds).toBe(120);
40
+ });
41
+
42
+ it('should return empty objects when files do not exist', () => {
43
+ const result = readInstanceMetadata(tempDir);
44
+
45
+ expect(result.metadata).toEqual({});
46
+ expect(result.hostStart).toEqual({});
47
+ expect(result.elapsedSeconds).toBeNull();
48
+ });
49
+
50
+ it('should handle corrupt JSON in metadata.json gracefully', () => {
51
+ fs.writeFileSync(path.join(tempDir, 'metadata.json'), '{invalid json');
52
+
53
+ const result = readInstanceMetadata(tempDir);
54
+
55
+ expect(result.metadata).toEqual({});
56
+ });
57
+
58
+ it('should handle corrupt JSON in host-start.json gracefully', () => {
59
+ fs.writeFileSync(path.join(tempDir, 'host-start.json'), '{invalid json');
60
+
61
+ const result = readInstanceMetadata(tempDir);
62
+
63
+ expect(result.hostStart).toEqual({});
64
+ });
65
+
66
+ it('should read elapsed_seconds from resource.time when metadata.duration_seconds is missing', () => {
67
+ const metadata = { current_stage: 'completed' };
68
+ fs.writeFileSync(
69
+ path.join(tempDir, 'metadata.json'),
70
+ JSON.stringify(metadata)
71
+ );
72
+ fs.writeFileSync(
73
+ path.join(tempDir, 'resource.time'),
74
+ 'user_cpu=1.234\nelapsed_seconds=90\nsystem_cpu=0.456'
75
+ );
76
+
77
+ const result = readInstanceMetadata(tempDir);
78
+
79
+ expect(result.elapsedSeconds).toBe(90);
80
+ });
81
+
82
+ it('should prefer metadata.duration_seconds over resource.time', () => {
83
+ const metadata = { current_stage: 'completed', duration_seconds: 120 };
84
+ fs.writeFileSync(
85
+ path.join(tempDir, 'metadata.json'),
86
+ JSON.stringify(metadata)
87
+ );
88
+ fs.writeFileSync(
89
+ path.join(tempDir, 'resource.time'),
90
+ 'elapsed_seconds=90'
91
+ );
92
+
93
+ const result = readInstanceMetadata(tempDir);
94
+
95
+ expect(result.elapsedSeconds).toBe(120);
96
+ });
97
+
98
+ it('should handle malformed resource.time gracefully', () => {
99
+ const metadata = { current_stage: 'completed' };
100
+ fs.writeFileSync(
101
+ path.join(tempDir, 'metadata.json'),
102
+ JSON.stringify(metadata)
103
+ );
104
+ fs.writeFileSync(path.join(tempDir, 'resource.time'), 'malformed_content');
105
+
106
+ const result = readInstanceMetadata(tempDir);
107
+
108
+ expect(result.elapsedSeconds).toBeNull();
109
+ });
110
+
111
+ it('should return null for elapsedSeconds when neither source has valid data', () => {
112
+ const metadata = { current_stage: 'pending' };
113
+ fs.writeFileSync(
114
+ path.join(tempDir, 'metadata.json'),
115
+ JSON.stringify(metadata)
116
+ );
117
+
118
+ const result = readInstanceMetadata(tempDir);
119
+
120
+ expect(result.elapsedSeconds).toBeNull();
121
+ });
122
+
123
+ it('should preserve all metadata fields', () => {
124
+ const metadata = {
125
+ current_stage: 'validation',
126
+ exit_code: 1,
127
+ duration_seconds: 120,
128
+ started_at: '2024-01-01T10:00:00Z',
129
+ model: 'gpt-4',
130
+ pi_duration_seconds: 60,
131
+ custom_field: 'custom_value',
132
+ };
133
+
134
+ fs.writeFileSync(path.join(tempDir, 'metadata.json'), JSON.stringify(metadata));
135
+
136
+ const result = readInstanceMetadata(tempDir);
137
+
138
+ expect(result.metadata).toEqual(metadata);
139
+ expect(result.metadata.custom_field).toBe('custom_value');
140
+ });
141
+
142
+ it('should propagate ENOENT errors when reading metadata.json', () => {
143
+ const metadataPath = path.join(tempDir, 'metadata.json');
144
+
145
+ // Create file, then delete it during read simulation
146
+ fs.writeFileSync(metadataPath, '{}');
147
+
148
+ // Override readFileSync to throw ENOENT
149
+ const originalReadFileSync = fs.readFileSync as any;
150
+ let readCallCount = 0;
151
+ (fs.readFileSync as any) = jest.fn((filePath: any, ...args: any[]) => {
152
+ readCallCount++;
153
+ if (readCallCount === 1 && filePath === metadataPath) {
154
+ const error = new Error('File not found') as any;
155
+ error.code = 'ENOENT';
156
+ throw error;
157
+ }
158
+ return originalReadFileSync(filePath, ...args);
159
+ });
160
+
161
+ try {
162
+ expect(() => readInstanceMetadata(tempDir)).toThrow();
163
+ } finally {
164
+ (fs.readFileSync as any) = originalReadFileSync;
165
+ }
166
+ });
167
+
168
+ it('should propagate ESTALE errors when reading host-start.json', () => {
169
+ const hostStartPath = path.join(tempDir, 'host-start.json');
170
+ fs.writeFileSync(hostStartPath, '{}');
171
+
172
+ const originalReadFileSync = fs.readFileSync as any;
173
+ let readCallCount = 0;
174
+ (fs.readFileSync as any) = jest.fn((filePath: any, ...args: any[]) => {
175
+ readCallCount++;
176
+ if (readCallCount === 1 && filePath === hostStartPath) {
177
+ const error = new Error('Stale NFS file handle') as any;
178
+ error.code = 'ESTALE';
179
+ throw error;
180
+ }
181
+ return originalReadFileSync(filePath, ...args);
182
+ });
183
+
184
+ try {
185
+ expect(() => readInstanceMetadata(tempDir)).toThrow();
186
+ } finally {
187
+ (fs.readFileSync as any) = originalReadFileSync;
188
+ }
189
+ });
190
+ });
@@ -0,0 +1,129 @@
1
+ /**
2
+ * instance-metadata-reader.ts
3
+ *
4
+ * Encapsulates metadata reading logic for kaseki instances.
5
+ * Safely reads and parses metadata.json, host-start.json, and resource.time files
6
+ * with graceful error handling for transient I/O errors.
7
+ */
8
+
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+
12
+ export interface Metadata {
13
+ current_stage?: string;
14
+ exit_code?: number | string;
15
+ duration_seconds?: number;
16
+ started_at?: string;
17
+ start_time?: string;
18
+ model?: string;
19
+ pi_duration_seconds?: number;
20
+ [key: string]: any;
21
+ }
22
+
23
+ export interface HostStart {
24
+ model?: string;
25
+ repo_url?: string;
26
+ repo?: string;
27
+ git_ref?: string;
28
+ ref?: string;
29
+ agentTimeoutSeconds?: number;
30
+ [key: string]: any;
31
+ }
32
+
33
+ export interface InstanceMetadataInfo {
34
+ metadata: Metadata;
35
+ hostStart: HostStart;
36
+ elapsedSeconds: number | null;
37
+ }
38
+
39
+ /**
40
+ * Check if an error is a transient I/O error that should be retried or skipped.
41
+ */
42
+ function isSkippableInstanceIoError(error: any): boolean {
43
+ return error && (error.code === 'ENOENT' || error.code === 'ESTALE');
44
+ }
45
+
46
+ /**
47
+ * Read elapsed seconds from metadata or resource.time file.
48
+ * Tries metadata.duration_seconds first, then falls back to resource.time.
49
+ */
50
+ function readElapsedSeconds(resultDir: string, metadata: Metadata): number | null {
51
+ if (metadata.duration_seconds !== undefined) {
52
+ return metadata.duration_seconds;
53
+ }
54
+
55
+ const resourceTimePath = path.join(resultDir, 'resource.time');
56
+ if (!fs.existsSync(resourceTimePath)) {
57
+ return null;
58
+ }
59
+
60
+ try {
61
+ const content = fs.readFileSync(resourceTimePath, 'utf8');
62
+ const match = content.match(/elapsed_seconds=([0-9]+(?:\.[0-9]+)?)/);
63
+ if (match) {
64
+ return parseFloat(match[1]);
65
+ }
66
+ } catch {
67
+ // File may be unreadable; return null
68
+ }
69
+
70
+ return null;
71
+ }
72
+
73
+ /**
74
+ * Read metadata for a kaseki instance from its results directory.
75
+ *
76
+ * Reads three files:
77
+ * - metadata.json (primary source of truth)
78
+ * - host-start.json (configuration from run initiation)
79
+ * - resource.time (timing information if metadata incomplete)
80
+ *
81
+ * Handles transient I/O errors (ENOENT, ESTALE) by propagating them
82
+ * so callers can skip the instance and continue scanning.
83
+ *
84
+ * @param resultDir - Path to the kaseki results directory (e.g., /agents/kaseki-results/kaseki-1)
85
+ * @returns InstanceMetadataInfo with parsed metadata, host config, and elapsed time
86
+ * @throws Error if I/O error is transient (ENOENT, ESTALE); caller should skip this instance
87
+ */
88
+ export function readInstanceMetadata(resultDir: string): InstanceMetadataInfo {
89
+ const metadataPath = path.join(resultDir, 'metadata.json');
90
+ const hostStartPath = path.join(resultDir, 'host-start.json');
91
+
92
+ let metadata: Metadata = {};
93
+ let hostStart: HostStart = {};
94
+
95
+ // Read metadata
96
+ if (fs.existsSync(metadataPath)) {
97
+ try {
98
+ const content = fs.readFileSync(metadataPath, 'utf8');
99
+ metadata = JSON.parse(content);
100
+ } catch (e) {
101
+ if (isSkippableInstanceIoError(e)) {
102
+ throw e; // Propagate transient errors to caller
103
+ }
104
+ // Metadata may still be incomplete if run is in progress; use empty object
105
+ }
106
+ }
107
+
108
+ // Read host start config
109
+ if (fs.existsSync(hostStartPath)) {
110
+ try {
111
+ const content = fs.readFileSync(hostStartPath, 'utf8');
112
+ hostStart = JSON.parse(content);
113
+ } catch (e) {
114
+ if (isSkippableInstanceIoError(e)) {
115
+ throw e; // Propagate transient errors to caller
116
+ }
117
+ // File may be unreadable; use empty object
118
+ }
119
+ }
120
+
121
+ // Read elapsed seconds
122
+ const elapsedSeconds = readElapsedSeconds(resultDir, metadata);
123
+
124
+ return {
125
+ metadata,
126
+ hostStart,
127
+ elapsedSeconds,
128
+ };
129
+ }