@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/run-kaseki.sh ADDED
@@ -0,0 +1,990 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ ROOT="${KASEKI_ROOT:-/agents}"
6
+ RUNS="$ROOT/kaseki-runs"
7
+ RESULTS="$ROOT/kaseki-results"
8
+ CACHE="${KASEKI_CACHE_DIR:-$ROOT/kaseki-cache}"
9
+ if [ -n "${KASEKI_IMAGE:-}" ]; then
10
+ IMAGE="$KASEKI_IMAGE"
11
+ elif [ -r "$SCRIPT_DIR/.kaseki-image" ]; then
12
+ IMAGE="$(cat "$SCRIPT_DIR/.kaseki-image")"
13
+ else
14
+ IMAGE="docker.io/cyanautomation/kaseki-agent:latest"
15
+ fi
16
+ KASEKI_CONTAINER_USER="${KASEKI_CONTAINER_USER:-$(id -u):$(id -g)}"
17
+ REPO_URL="${REPO_URL:-https://github.com/CyanAutomation/crudmapper}"
18
+ GIT_REF="${GIT_REF:-main}"
19
+ KASEKI_PROVIDER="${KASEKI_PROVIDER:-openrouter}"
20
+ KASEKI_MODEL="${KASEKI_MODEL:-openrouter/free}"
21
+ KASEKI_AGENT_TIMEOUT_SECONDS="${KASEKI_AGENT_TIMEOUT_SECONDS:-1200}"
22
+ KASEKI_VALIDATION_COMMANDS="${KASEKI_VALIDATION_COMMANDS-npm run check;npm run test;npm run build}"
23
+ KASEKI_DEBUG_RAW_EVENTS="${KASEKI_DEBUG_RAW_EVENTS:-0}"
24
+ KASEKI_KEEP_WORKSPACE="${KASEKI_KEEP_WORKSPACE:-0}"
25
+ KASEKI_STREAM_PROGRESS="${KASEKI_STREAM_PROGRESS:-1}"
26
+ KASEKI_VALIDATE_AFTER_AGENT_FAILURE="${KASEKI_VALIDATE_AFTER_AGENT_FAILURE:-0}"
27
+ KASEKI_VALIDATION_FAIL_FAST="${KASEKI_VALIDATION_FAIL_FAST:-1}"
28
+ KASEKI_STRICT_SCRIPT_CHECK="${KASEKI_STRICT_SCRIPT_CHECK:-0}"
29
+ KASEKI_PUBLISH_MODE="${KASEKI_PUBLISH_MODE:-auto}"
30
+ KASEKI_AGENT_GUARDRAILS="${KASEKI_AGENT_GUARDRAILS:-1}"
31
+ KASEKI_RESTORE_DISALLOWED_CHANGES="${KASEKI_RESTORE_DISALLOWED_CHANGES:-1}"
32
+ KASEKI_TASK_MODE="${KASEKI_TASK_MODE:-patch}"
33
+ KASEKI_ALLOW_EMPTY_DIFF="${KASEKI_ALLOW_EMPTY_DIFF:-0}"
34
+ KASEKI_VERIFY_OPENROUTER_AUTH="${KASEKI_VERIFY_OPENROUTER_AUTH:-0}"
35
+ KASEKI_DOCTOR_REQUIRE_OPENROUTER_KEY="${KASEKI_DOCTOR_REQUIRE_OPENROUTER_KEY:-1}"
36
+ KASEKI_DRY_RUN="${KASEKI_DRY_RUN:-0}"
37
+ KASEKI_CHANGED_FILES_ALLOWLIST="${KASEKI_CHANGED_FILES_ALLOWLIST:-src/lib/parser.ts tests/parser.validation.ts}"
38
+ KASEKI_VALIDATION_ALLOWLIST="${KASEKI_VALIDATION_ALLOWLIST:-}"
39
+ KASEKI_MAX_DIFF_BYTES="${KASEKI_MAX_DIFF_BYTES:-200000}"
40
+ KASEKI_NPM_OMIT_DEV="${KASEKI_NPM_OMIT_DEV:-0}"
41
+ TASK_PROMPT="${TASK_PROMPT:-Make normalizeRole treat a non-string Name fallback safely when FriendlyName is empty or missing. It should fall back to \"Unnamed Role\" instead of preserving arbitrary truthy non-string values. Add or update exactly one compact table-driven Vitest case in tests/parser.validation.ts, with a neutral static test title and no per-case assertion messages or explanatory comments. Do not add broad repeated test blocks. Do not print, inspect, or expose environment variables, secrets, credentials, or API keys. Keep changes limited to the source and test files needed for this fix.}"
42
+ HOST_SECRET_FILE="${OPENROUTER_API_KEY_FILE:-/run/secrets/openrouter_api_key}"
43
+ KASEKI_LOG_DIR="${KASEKI_LOG_DIR:-/var/log/kaseki}"
44
+ KASEKI_STRICT_HOST_LOGGING="${KASEKI_STRICT_HOST_LOGGING:-0}"
45
+ KASEKI_APPEND_METRICS_JSONL="${KASEKI_APPEND_METRICS_JSONL:-1}"
46
+ KASEKI_METRICS_JSONL_PATH="${KASEKI_METRICS_JSONL_PATH:-/var/log/kaseki/metrics.jsonl}"
47
+ INSTANCE="${INSTANCE:-}"
48
+ KASEKI_JSON_LOG_COMPONENT="run-kaseki"
49
+
50
+ json_escape() {
51
+ local value="${1-}"
52
+ value="${value//\\/\\\\}"
53
+ value="${value//\"/\\\"}"
54
+ value="${value//$'\n'/\\n}"
55
+ value="${value//$'\r'/\\r}"
56
+ value="${value//$'\t'/\\t}"
57
+ printf '%s' "$value"
58
+ }
59
+
60
+ emit_json_log() {
61
+ local stage="$1"
62
+ local status="$2"
63
+ local detail="${3-}"
64
+ local now instance_value
65
+ now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
66
+ instance_value="${INSTANCE:-pending}"
67
+ printf '{"timestamp":"%s","component":"%s","stage":"%s","status":"%s","instance":"%s","detail":"%s"}\n' \
68
+ "$now" \
69
+ "$KASEKI_JSON_LOG_COMPONENT" \
70
+ "$(json_escape "$stage")" \
71
+ "$(json_escape "$status")" \
72
+ "$(json_escape "$instance_value")" \
73
+ "$(json_escape "$detail")"
74
+ }
75
+
76
+ emit_json_log "run" "started" "run-kaseki.sh starting"
77
+
78
+
79
+ run_preflight() {
80
+ local mode="$1"
81
+ local preflight_script
82
+ preflight_script="$SCRIPT_DIR/scripts/kaseki-preflight.sh"
83
+ if [ ! -x "$preflight_script" ]; then
84
+ printf 'Error: preflight script not found or not executable: %s\n' "$preflight_script" >&2
85
+ exit 1
86
+ fi
87
+ "$preflight_script" "$mode"
88
+ }
89
+
90
+ setup_host_logging() {
91
+ local instance_for_log="$1"
92
+ local stamp host_log_file
93
+ if mkdir -p "$KASEKI_LOG_DIR" 2>/dev/null && [ -w "$KASEKI_LOG_DIR" ]; then
94
+ stamp="$(date -u +%Y%m%dT%H%M%SZ)"
95
+ host_log_file="$KASEKI_LOG_DIR/run-kaseki-${instance_for_log:-session}-${stamp}.log"
96
+ exec > >(tee -a "$host_log_file") 2> >(tee -a "$host_log_file" >&2)
97
+ printf 'Host log mirror: %s\n' "$host_log_file"
98
+ return 0
99
+ fi
100
+ if [ "$KASEKI_STRICT_HOST_LOGGING" = "1" ]; then
101
+ printf 'Error: strict host logging enabled, but KASEKI_LOG_DIR is not writable: %s\n' "$KASEKI_LOG_DIR" >&2
102
+ exit 1
103
+ fi
104
+ printf 'Warning: host logging disabled; KASEKI_LOG_DIR is unavailable: %s\n' "$KASEKI_LOG_DIR" >&2
105
+ }
106
+
107
+ read_secret_value() {
108
+ local inline_value="$1"
109
+ local file_path="$2"
110
+ if [ -n "$inline_value" ]; then
111
+ printf '%s' "$inline_value"
112
+ return 0
113
+ fi
114
+ if [ -n "$file_path" ] && [ -r "$file_path" ]; then
115
+ sed -e '1{s/^\xef\xbb\xbf//}' "$file_path" | sed -e "\${s/[[:space:]]*$//;}"
116
+ return 0
117
+ fi
118
+ return 1
119
+ }
120
+
121
+ normalize_private_key_pem() {
122
+ if ! command -v node >/dev/null 2>&1; then
123
+ cat
124
+ return 0
125
+ fi
126
+ # shellcheck disable=SC2016
127
+ node -e 'const fs = require('"'"'node:fs'"'"');
128
+ let value = fs.readFileSync(0, '"'"'utf8'"'"').trim();
129
+ if (!value) process.exit(0);
130
+ value = value.replace(/\\n/g, '"'"'\n'"'"');
131
+ const match = value.match(/-----BEGIN ([A-Z ]*PRIVATE KEY)-----([\\s\\S]*?)-----END \\1-----/);
132
+ if (!match) {
133
+ process.stdout.write(value);
134
+ if (!value.endsWith('"'"'\n'"'"')) process.stdout.write('"'"'\n'"'"');
135
+ process.exit(0);
136
+ }
137
+ const body = match[2].replace(/\s+/g, '"'"''"'"');
138
+ const lines = body.match(/.{1,64}/g) || [];
139
+ process.stdout.write(`-----BEGIN ${match[1]}-----\n${lines.join('"'"'\n'"'"')}\n-----END ${match[1]}-----\n`);'
140
+ }
141
+
142
+ # GitHub App credentials (optional, for auto PR creation)
143
+ GITHUB_APP_ID="${GITHUB_APP_ID:-}"
144
+ GITHUB_APP_ID_FILE="${GITHUB_APP_ID_FILE:-}"
145
+ GITHUB_APP_ID_INPUT_FILE="$GITHUB_APP_ID_FILE"
146
+ GITHUB_APP_CLIENT_ID="${GITHUB_APP_CLIENT_ID:-}"
147
+ GITHUB_APP_CLIENT_ID_FILE="${GITHUB_APP_CLIENT_ID_FILE:-}"
148
+ GITHUB_APP_CLIENT_ID_INPUT_FILE="$GITHUB_APP_CLIENT_ID_FILE"
149
+ GITHUB_APP_PRIVATE_KEY_FILE="${GITHUB_APP_PRIVATE_KEY_FILE:-}"
150
+ GITHUB_APP_PRIVATE_KEY="${GITHUB_APP_PRIVATE_KEY:-}"
151
+
152
+ # ============================================================================
153
+ # Argument Parsing
154
+ # ============================================================================
155
+
156
+ show_help() {
157
+ cat << 'HELP'
158
+ kaseki-agent - Ephemeral coding-agent runner with Docker isolation
159
+
160
+ USAGE:
161
+ ./run-kaseki.sh [<repo-url> [<git-ref> [<instance-name>]]]
162
+ ./run-kaseki.sh --doctor
163
+ ./run-kaseki.sh --help
164
+
165
+ ARGUMENTS:
166
+ <repo-url> Git repository URL (e.g., https://github.com/org/repo)
167
+ Supports GitHub, GitLab, Bitbucket, and self-hosted servers
168
+ Default: https://github.com/CyanAutomation/crudmapper
169
+ <git-ref> Git reference: branch, tag, or commit (default: main)
170
+ <instance-name> Kaseki instance name (must match kaseki-N pattern)
171
+ Auto-generated if not provided
172
+
173
+ OPTIONS:
174
+ --doctor Run health check and exit
175
+ --help, -h Show this help message
176
+
177
+ ENVIRONMENT VARIABLES (override defaults, CLI args take precedence):
178
+ REPO_URL Repository URL
179
+ GIT_REF Git reference
180
+ OPENROUTER_API_KEY OpenRouter API key (or use OPENROUTER_API_KEY_FILE)
181
+ OPENROUTER_API_KEY_FILE Path to file containing API key
182
+ KASEKI_MODEL AI model (default: openrouter/free)
183
+ KASEKI_AGENT_TIMEOUT_SECONDS Timeout in seconds (default: 1200)
184
+ KASEKI_VALIDATION_COMMANDS Semicolon-separated validation cmds
185
+ KASEKI_STREAM_PROGRESS Stream sanitized progress lines (default: 1)
186
+ KASEKI_KEEP_WORKSPACE Keep per-run workspace after exit (default: 0)
187
+ KASEKI_VALIDATE_AFTER_AGENT_FAILURE
188
+ Run validation even when the agent fails (default: 0)
189
+ KASEKI_AGENT_GUARDRAILS Prepend safety instructions to the agent prompt (default: 1)
190
+ KASEKI_RESTORE_DISALLOWED_CHANGES
191
+ Restore changes outside the allowlist before validation (default: 1)
192
+ KASEKI_TASK_MODE patch or inspect (inspect allows empty diffs)
193
+ KASEKI_ALLOW_EMPTY_DIFF Treat no-change runs as success when 1 (default: 0)
194
+ KASEKI_VERIFY_OPENROUTER_AUTH In --doctor, verify key with OpenRouter when 1
195
+ KASEKI_CACHE_DIR Persistent host cache directory (default: /agents/kaseki-cache)
196
+ KASEKI_CHANGED_FILES_ALLOWLIST Space-separated file patterns (agent phase)
197
+ KASEKI_VALIDATION_ALLOWLIST Space-separated file patterns (validation phase; optional)
198
+ KASEKI_MAX_DIFF_BYTES Max diff size in bytes (default: 200000)
199
+ KASEKI_NPM_OMIT_DEV Set to 1 to omit dev dependencies during npm ci (default: 0)
200
+ GITHUB_APP_ID GitHub App ID (optional, for PR creation)
201
+ GITHUB_APP_ID_FILE Path to file containing GitHub App ID
202
+ GITHUB_APP_CLIENT_ID GitHub App Client ID (optional)
203
+ GITHUB_APP_CLIENT_ID_FILE Path to file containing GitHub App Client ID
204
+ GITHUB_APP_PRIVATE_KEY_FILE Path to GitHub App private key PEM file (preferred)
205
+ GITHUB_APP_PRIVATE_KEY GitHub App private key inline (fallback; avoid for production)
206
+
207
+ EXAMPLES:
208
+ # All defaults
209
+ ./run-kaseki.sh
210
+
211
+ # Custom repo, auto instance
212
+ ./run-kaseki.sh https://github.com/org/myrepo
213
+
214
+ # Custom repo and ref
215
+ ./run-kaseki.sh https://github.com/org/myrepo feature/branch
216
+
217
+ # Explicit instance name
218
+ ./run-kaseki.sh https://github.com/org/myrepo main kaseki-42
219
+
220
+ # Via environment variables (legacy)
221
+ REPO_URL=https://... GIT_REF=main ./run-kaseki.sh
222
+
223
+ # Dry-run mode (no agent execution, tests setup and validation)
224
+ ./run-kaseki.sh --dry-run
225
+ KASEKI_DRY_RUN=1 ./run-kaseki.sh
226
+
227
+ # Health check
228
+ ./run-kaseki.sh --doctor
229
+ HELP
230
+ }
231
+
232
+ usage_error() {
233
+ printf 'Error: %s\n\n' "$1" >&2
234
+ show_help >&2
235
+ exit 2
236
+ }
237
+
238
+ json_encode() {
239
+ local value
240
+ value="$(cat)"
241
+ value="${value//\\/\\\\}"
242
+ value="${value//\"/\\\"}"
243
+ value="${value//$'\b'/\\b}"
244
+ value="${value//$'\f'/\\f}"
245
+ value="${value//$'\n'/\\n}"
246
+ value="${value//$'\r'/\\r}"
247
+ value="${value//$'\t'/\\t}"
248
+ printf '"%s"' "$value"
249
+ }
250
+
251
+ json_string() {
252
+ printf '%s' "$1" | json_encode
253
+ }
254
+
255
+ file_sha256() {
256
+ if command -v sha256sum >/dev/null 2>&1; then
257
+ sha256sum "$1" | awk '{print $1}'
258
+ elif command -v shasum >/dev/null 2>&1; then
259
+ shasum -a 256 "$1" | awk '{print $1}'
260
+ else
261
+ return 1
262
+ fi
263
+ }
264
+
265
+ require_non_negative_int() {
266
+ local name="$1"
267
+ local value="$2"
268
+ if [[ ! "$value" =~ ^[0-9]+$ ]]; then
269
+ printf 'Error: %s must be a non-negative integer, got: %s\n' "$name" "$value" >&2
270
+ exit 2
271
+ fi
272
+ printf '%s' "$value"
273
+ }
274
+
275
+ is_git_url() {
276
+ local str="$1"
277
+ # URLs must start with http(s):// and contain at least one /
278
+ # Supports GitHub, GitLab, Bitbucket, and self-hosted Git servers
279
+ [[ "$str" =~ ^https?:// ]] && [[ "$str" == */* ]]
280
+ }
281
+
282
+ is_instance_name() {
283
+ local str="$1"
284
+ [[ "$str" =~ ^kaseki-[0-9]+$ ]]
285
+ }
286
+
287
+ # Parse command-line arguments
288
+ PARSED_REPO_URL="${REPO_URL:-https://github.com/CyanAutomation/crudmapper}"
289
+ PARSED_GIT_REF="${GIT_REF:-main}"
290
+ INSTANCE=""
291
+
292
+ # Argument index tracker
293
+ arg_idx=0
294
+
295
+ for arg in "$@"; do
296
+ if [ $arg_idx -eq 0 ]; then
297
+ # First argument could be: --doctor, --help, --dry-run, repo-url, or help
298
+ if [ "$arg" = "--doctor" ]; then
299
+ if [ "$#" -gt 1 ]; then
300
+ usage_error "--doctor does not accept positional arguments"
301
+ fi
302
+ SHOW_DOCTOR="1"
303
+ arg_idx=$((arg_idx + 1))
304
+ continue
305
+ elif [ "$arg" = "--dry-run" ]; then
306
+ if [ "$#" -gt 1 ]; then
307
+ usage_error "--dry-run does not accept positional arguments"
308
+ fi
309
+ KASEKI_DRY_RUN="1"
310
+ arg_idx=$((arg_idx + 1))
311
+ continue
312
+ elif [ "$arg" = "--help" ] || [ "$arg" = "-h" ]; then
313
+ show_help
314
+ exit 0
315
+ elif is_git_url "$arg"; then
316
+ PARSED_REPO_URL="$arg"
317
+ arg_idx=$((arg_idx + 1))
318
+ elif is_instance_name "$arg"; then
319
+ # Edge case: passed instance name as first arg without repo
320
+ INSTANCE="$arg"
321
+ arg_idx=$((arg_idx + 1))
322
+ elif [[ "$arg" == -* ]]; then
323
+ usage_error "unknown option: $arg"
324
+ else
325
+ # Could be short ref like "main" without repo URL
326
+ PARSED_GIT_REF="$arg"
327
+ arg_idx=$((arg_idx + 1))
328
+ fi
329
+ elif [ $arg_idx -eq 1 ]; then
330
+ # Second argument: git-ref or instance-name
331
+ if is_instance_name "$arg"; then
332
+ INSTANCE="$arg"
333
+ else
334
+ PARSED_GIT_REF="$arg"
335
+ fi
336
+ arg_idx=$((arg_idx + 1))
337
+ elif [ $arg_idx -eq 2 ]; then
338
+ # Third argument: instance-name
339
+ if is_instance_name "$arg"; then
340
+ INSTANCE="$arg"
341
+ else
342
+ usage_error "third argument must be an instance name matching kaseki-N, got: $arg"
343
+ fi
344
+ arg_idx=$((arg_idx + 1))
345
+ else
346
+ usage_error "too many positional arguments"
347
+ fi
348
+ done
349
+
350
+ if [ "${SHOW_DOCTOR:-0}" = "1" ]; then
351
+ INSTANCE=""
352
+ fi
353
+
354
+ if [ "${SHOW_DOCTOR:-0}" != "1" ]; then
355
+ run_preflight run
356
+ fi
357
+
358
+ if [ "${SHOW_DOCTOR:-0}" = "1" ]; then
359
+ INSTANCE=""
360
+ fi
361
+
362
+ setup_host_logging "${INSTANCE:-session}"
363
+
364
+ doctor() {
365
+ local status=0
366
+ local image_present=0
367
+ local openrouter_key_source=""
368
+ local openrouter_key_value=""
369
+ printf 'Kaseki doctor\n'
370
+ printf 'Root: %s\n' "$ROOT"
371
+ printf 'Image: %s\n' "$IMAGE"
372
+ printf 'Cache: %s\n' "$CACHE"
373
+ printf 'Container user: %s\n' "$KASEKI_CONTAINER_USER"
374
+
375
+ if run_preflight doctor; then
376
+ :
377
+ else
378
+ status=1
379
+ fi
380
+
381
+ if command -v docker >/dev/null 2>&1; then
382
+ printf 'Docker: %s\n' "$(docker --version)"
383
+ else
384
+ printf 'Docker: missing\n' >&2
385
+ status=1
386
+ fi
387
+
388
+ mkdir -p "$RUNS" "$RESULTS" "$CACHE" 2>/dev/null || {
389
+ printf 'Writable Kaseki directories: failed to create %s, %s, and %s\n' "$RUNS" "$RESULTS" "$CACHE" >&2
390
+ status=1
391
+ }
392
+ [ -w "$RUNS" ] && [ -w "$RESULTS" ] && [ -w "$CACHE" ] && printf 'Writable Kaseki directories: ok\n'
393
+
394
+ if [ -n "${OPENROUTER_API_KEY:-}" ]; then
395
+ printf 'OpenRouter API key: env\n'
396
+ openrouter_key_source="env"
397
+ openrouter_key_value="$OPENROUTER_API_KEY"
398
+ elif [ -r "$HOST_SECRET_FILE" ] && [ -s "$HOST_SECRET_FILE" ]; then
399
+ printf 'OpenRouter API key: secret file (%s)\n' "$HOST_SECRET_FILE"
400
+ openrouter_key_source="secret file"
401
+ openrouter_key_value="$(cat "$HOST_SECRET_FILE")"
402
+ else
403
+ printf 'OpenRouter API key: missing\n' >&2
404
+ if [ "$KASEKI_DOCTOR_REQUIRE_OPENROUTER_KEY" = "1" ]; then
405
+ status=1
406
+ else
407
+ printf 'OpenRouter API key: warning only for doctor (KASEKI_DOCTOR_REQUIRE_OPENROUTER_KEY=0)\n' >&2
408
+ fi
409
+ fi
410
+
411
+ if [ "$KASEKI_VERIFY_OPENROUTER_AUTH" = "1" ]; then
412
+ if [ -z "$openrouter_key_value" ]; then
413
+ printf 'OpenRouter API key auth: skipped (missing key)\n' >&2
414
+ elif command -v curl >/dev/null 2>&1; then
415
+ if curl -fsS -H "Authorization: Bearer $openrouter_key_value" https://openrouter.ai/api/v1/auth/key >/dev/null 2>&1; then
416
+ printf 'OpenRouter API key auth: ok (%s)\n' "$openrouter_key_source"
417
+ else
418
+ printf 'OpenRouter API key auth: failed (%s)\n' "$openrouter_key_source" >&2
419
+ status=1
420
+ fi
421
+ elif command -v wget >/dev/null 2>&1; then
422
+ if wget -qO- --header="Authorization: Bearer $openrouter_key_value" https://openrouter.ai/api/v1/auth/key >/dev/null 2>&1; then
423
+ printf 'OpenRouter API key auth: ok (%s)\n' "$openrouter_key_source"
424
+ else
425
+ printf 'OpenRouter API key auth: failed (%s)\n' "$openrouter_key_source" >&2
426
+ status=1
427
+ fi
428
+ else
429
+ printf 'OpenRouter API key auth: skipped (curl or wget required)\n' >&2
430
+ status=1
431
+ fi
432
+ fi
433
+ unset openrouter_key_value
434
+
435
+ local docker_image_error=""
436
+ if docker image inspect "$IMAGE" >/dev/null 2>&1; then
437
+ printf 'Docker image: present\n'
438
+ image_present=1
439
+ else
440
+ docker_image_error="$(docker image inspect "$IMAGE" 2>&1 >/dev/null || true)"
441
+ if printf '%s' "$docker_image_error" | grep -qi 'permission denied'; then
442
+ printf 'Docker image: unavailable because Docker socket permission was denied (%s)\n' "$IMAGE" >&2
443
+ elif printf '%s' "$docker_image_error" | grep -Eqi 'cannot connect|is the docker daemon running'; then
444
+ printf 'Docker image: unavailable because Docker daemon is unreachable (%s)\n' "$IMAGE" >&2
445
+ else
446
+ printf 'Docker image: missing locally (%s)\n' "$IMAGE" >&2
447
+ fi
448
+ status=1
449
+ fi
450
+
451
+ if [ "$image_present" -eq 1 ]; then
452
+ local mismatch=0
453
+ local missing_host_template=0
454
+ local pairs
455
+ if docker run --rm --entrypoint test "$IMAGE" -f /app/run-kaseki.sh >/dev/null 2>&1; then
456
+ printf 'Docker image template payload: ok\n'
457
+ else
458
+ printf 'Docker image template payload: missing /app/run-kaseki.sh; deploy will need a local rebuild or a newer image.\n' >&2
459
+ status=1
460
+ fi
461
+ pairs='kaseki-agent.sh:/usr/local/bin/kaseki-agent lib/pi-event-filter.js:/usr/local/bin/kaseki-pi-event-filter lib/pi-progress-stream.js:/usr/local/bin/kaseki-pi-progress-stream lib/kaseki-report.js:/usr/local/bin/kaseki-report lib/github-app-token.js:/usr/local/bin/github-app-token'
462
+ for pair in $pairs; do
463
+ local host_file="${pair%%:*}"
464
+ local image_file="${pair#*:}"
465
+ local host_path="$SCRIPT_DIR/$host_file"
466
+ local host_sum image_sum
467
+ if [ ! -f "$host_path" ]; then
468
+ printf 'Image/template parity: missing host file %s\n' "$host_file" >&2
469
+ mismatch=1
470
+ missing_host_template=1
471
+ continue
472
+ fi
473
+ host_sum="$(file_sha256 "$host_path" || true)"
474
+ image_sum="$(docker run --rm --entrypoint sha256sum "$IMAGE" "$image_file" 2>/dev/null | awk '{print $1}' || true)"
475
+ if [ -z "$host_sum" ] || [ -z "$image_sum" ] || [ "$host_sum" != "$image_sum" ]; then
476
+ printf 'Image/template parity: mismatch for %s vs %s\n' "$host_file" "$image_file" >&2
477
+ mismatch=1
478
+ fi
479
+ done
480
+ if [ "$mismatch" -eq 0 ]; then
481
+ printf 'Image/template parity: ok\n'
482
+ elif [ "$missing_host_template" -eq 1 ]; then
483
+ status=1
484
+ printf 'Image/template parity: missing deployed template files; this looks like a source checkout or incomplete template.\n' >&2
485
+ printf 'Image/template parity: deploy from the source checkout with: sudo KASEKI_IMAGE_PULL_POLICY=missing ./scripts/deploy-pi-template.sh\n' >&2
486
+ printf 'Image/template parity: then run: /agents/kaseki-template/run-kaseki.sh --doctor\n' >&2
487
+ else
488
+ status=1
489
+ printf 'Image/template parity: mismatch; rebuild/pull the image or set KASEKI_IMAGE to the matching local image.\n' >&2
490
+ fi
491
+ fi
492
+
493
+ # Check GitHub App credentials (optional)
494
+ github_app_ready=0
495
+ github_app_id_value="$(read_secret_value "$GITHUB_APP_ID" "$GITHUB_APP_ID_INPUT_FILE" 2>/dev/null || true)"
496
+ github_app_client_id_value="$(read_secret_value "$GITHUB_APP_CLIENT_ID" "$GITHUB_APP_CLIENT_ID_INPUT_FILE" 2>/dev/null || true)"
497
+ if [ -n "$github_app_id_value" ] && [ -n "$github_app_client_id_value" ]; then
498
+ if [ -r "$GITHUB_APP_PRIVATE_KEY_FILE" ] || [ -n "$GITHUB_APP_PRIVATE_KEY" ]; then
499
+ printf 'GitHub App credentials: configured\n'
500
+ github_app_ready=1
501
+ fi
502
+ fi
503
+ if [ "$github_app_ready" -eq 0 ] && { [ -n "$GITHUB_APP_ID" ] || [ -n "$GITHUB_APP_ID_FILE" ] || [ -n "$GITHUB_APP_CLIENT_ID" ] || [ -n "$GITHUB_APP_CLIENT_ID_FILE" ] || [ -n "$GITHUB_APP_PRIVATE_KEY_FILE" ] || [ -n "$GITHUB_APP_PRIVATE_KEY" ]; }; then
504
+ printf 'GitHub App credentials: incomplete (need APP_ID or APP_ID_FILE, CLIENT_ID or CLIENT_ID_FILE, and PRIVATE_KEY_FILE or PRIVATE_KEY)\n' >&2
505
+ fi
506
+ unset github_app_id_value github_app_client_id_value
507
+
508
+ return "$status"
509
+ }
510
+
511
+ if [ "${SHOW_DOCTOR:-0}" = "1" ]; then
512
+ doctor
513
+ exit "$?"
514
+ fi
515
+
516
+ # Use parsed CLI arguments, falling back to env vars if not provided via CLI
517
+ REPO_URL="$PARSED_REPO_URL"
518
+ GIT_REF="$PARSED_GIT_REF"
519
+
520
+ mkdir -p "$RUNS" "$RESULTS" "$CACHE"
521
+
522
+ INSTANCE_AUTO_RESERVED=0
523
+ if [ -z "$INSTANCE" ]; then
524
+ next=1
525
+ while true; do
526
+ candidate="kaseki-$next"
527
+ if [ -d "$RESULTS/$candidate" ]; then
528
+ next=$((next + 1))
529
+ continue
530
+ fi
531
+ if mkdir "$RUNS/$candidate" 2>/dev/null; then
532
+ INSTANCE="$candidate"
533
+ INSTANCE_AUTO_RESERVED=1
534
+ break
535
+ fi
536
+ if [ -d "$RUNS/$candidate" ]; then
537
+ next=$((next + 1))
538
+ continue
539
+ fi
540
+ echo "Failed to reserve instance directory: $RUNS/$candidate" >&2
541
+ exit 1
542
+ done
543
+ fi
544
+
545
+ case "$INSTANCE" in
546
+ kaseki-[0-9]*) ;;
547
+ *) echo "Instance must look like kaseki-N, got: $INSTANCE" >&2; exit 2 ;;
548
+ esac
549
+
550
+ FINAL_RUN_DIR="$RUNS/$INSTANCE"
551
+ FINAL_RESULT_DIR="$RESULTS/$INSTANCE"
552
+ RUN_STAGE_DIR="$(mktemp -d "$RUNS/.staging-run-${INSTANCE}-XXXXXX")"
553
+ RESULT_STAGE_DIR="$(mktemp -d "$RESULTS/.staging-result-${INSTANCE}-XXXXXX")"
554
+ RUN_DIR="$RUN_STAGE_DIR"
555
+ RESULT_DIR="$RESULT_STAGE_DIR"
556
+ WORKSPACE="$RUN_DIR/workspace"
557
+ SECRET_FILE="$RUN_DIR/openrouter_api_key"
558
+ GITHUB_APP_ID_FILE="$RUN_DIR/github_app_id"
559
+ GITHUB_APP_CLIENT_ID_FILE="$RUN_DIR/github_app_client_id"
560
+ GITHUB_APP_PRIVATE_KEY_MOUNTED_FILE="$RUN_DIR/github_app_private_key"
561
+ PROMOTED_RESULT_DIR=0
562
+ PROMOTED_RUN_DIR=0
563
+
564
+ if [ -d "$FINAL_RESULT_DIR" ]; then
565
+ echo "Result directory already exists for $INSTANCE: $FINAL_RESULT_DIR" >&2
566
+ echo "Choose a new instance name; Kaseki does not overwrite prior results." >&2
567
+ exit 2
568
+ fi
569
+
570
+ if [ "$INSTANCE_AUTO_RESERVED" -eq 0 ] && [ -n "${INSTANCE:-}" ] && [ -d "$FINAL_RUN_DIR" ]; then
571
+ echo "Instance already reserved: $INSTANCE" >&2
572
+ exit 2
573
+ fi
574
+
575
+ # shellcheck disable=SC2317,SC2329
576
+ # Invoked via trap in the unified exit handler.
577
+ cleanup_secret() {
578
+ rm -f "$SECRET_FILE" "$GITHUB_APP_ID_FILE" "$GITHUB_APP_CLIENT_ID_FILE" "$GITHUB_APP_PRIVATE_KEY_MOUNTED_FILE"
579
+ }
580
+ # shellcheck disable=SC2317,SC2329
581
+ # Invoked via trap in the unified exit handler.
582
+ cleanup_staging_dirs() {
583
+ [ "$PROMOTED_RESULT_DIR" -eq 1 ] || rm -rf "$RESULT_STAGE_DIR"
584
+ [ "$PROMOTED_RUN_DIR" -eq 1 ] || rm -rf "$RUN_STAGE_DIR"
585
+ }
586
+ # shellcheck disable=SC2317,SC2329
587
+ # Invoked via trap on process exit/signals.
588
+ unified_exit_handler() {
589
+ local code=$?
590
+ if [ "$code" -eq 0 ]; then
591
+ emit_json_log "run" "finished" "run-kaseki.sh completed successfully"
592
+ else
593
+ emit_json_log "run" "error" "run-kaseki.sh exited with code $code"
594
+ fi
595
+ cleanup_secret
596
+ cleanup_staging_dirs
597
+ }
598
+ trap unified_exit_handler EXIT INT TERM HUP
599
+
600
+ mkdir -p "$WORKSPACE" "$RESULT_DIR" "$CACHE"
601
+ chmod 0755 "$RUN_DIR" "$WORKSPACE" "$RESULT_DIR" "$CACHE"
602
+
603
+ START_EPOCH="$(date +%s)"
604
+ MAX_DIFF_BYTES_VALUE="$(require_non_negative_int "KASEKI_MAX_DIFF_BYTES" "$KASEKI_MAX_DIFF_BYTES")"
605
+ AGENT_TIMEOUT_SECONDS_VALUE="$(require_non_negative_int "KASEKI_AGENT_TIMEOUT_SECONDS" "$KASEKI_AGENT_TIMEOUT_SECONDS")"
606
+ FAILURE_EXIT_CODE_VALUE="$(require_non_negative_int "exit_code" "2")"
607
+ HOST_EXIT_CODE_FILE="$RESULT_DIR/host_exit_code"
608
+
609
+ initialize_result_artifacts() {
610
+ : > "$RESULT_DIR/stdout.log"
611
+ : > "$RESULT_DIR/stderr.log"
612
+ : > "$RESULT_DIR/pi-events.jsonl"
613
+ : > "$RESULT_DIR/pi-summary.json"
614
+ : > "$RESULT_DIR/git.status"
615
+ : > "$RESULT_DIR/git.diff"
616
+ : > "$RESULT_DIR/changed-files.txt"
617
+ : > "$RESULT_DIR/validation.log"
618
+ : > "$RESULT_DIR/validation-timings.tsv"
619
+ : > "$RESULT_DIR/stage-timings.tsv"
620
+ : > "$RESULT_DIR/dependency-cache.log"
621
+ : > "$RESULT_DIR/quality.log"
622
+ : > "$RESULT_DIR/secret-scan.log"
623
+ : > "$RESULT_DIR/git-push.log"
624
+ : > "$RESULT_DIR/progress.log"
625
+ : > "$RESULT_DIR/progress.jsonl"
626
+ : > "$RESULT_DIR/format-check-command.txt"
627
+ : > "$RESULT_DIR/failure.json"
628
+ : > "$RESULT_DIR/result-summary.md"
629
+ : > "$HOST_EXIT_CODE_FILE"
630
+ }
631
+
632
+ persist_host_status() {
633
+ local exit_code="$1"
634
+ # Keep host-side status deterministic even when container startup fails.
635
+ printf '%s\n' "$exit_code" > "$HOST_EXIT_CODE_FILE"
636
+ }
637
+
638
+ write_failure_json() {
639
+ local exit_code="$1"
640
+ local failed_command="$2"
641
+ local message="$3"
642
+ local stderr_tail
643
+ stderr_tail="$(tail -20 "$RESULT_DIR/stderr.log" 2>/dev/null || true)"
644
+ cat > "$RESULT_DIR/failure.json" <<META
645
+ {
646
+ "instance": $(json_string "$INSTANCE"),
647
+ "exit_code": $exit_code,
648
+ "failed_command": $(json_string "$failed_command"),
649
+ "message": $(json_string "$message"),
650
+ "stderr_tail": $(json_string "$stderr_tail"),
651
+ "artifacts_dir": $(json_string "$RESULT_DIR"),
652
+ "metadata": "metadata.json",
653
+ "stderr": "stderr.log",
654
+ "stdout": "stdout.log",
655
+ "progress": "progress.jsonl",
656
+ "summary": "result-summary.md"
657
+ }
658
+ META
659
+ }
660
+
661
+ write_host_metadata_failure() {
662
+ local exit_code="$1"
663
+ local failed_command="$2"
664
+ local message="$3"
665
+ printf '%s\n' "$exit_code" > "$RESULT_DIR/exit_code"
666
+ printf '%s\n' "$exit_code" > "$RESULT_DIR/host_docker_exit_code"
667
+ persist_host_status "$exit_code"
668
+ printf 'elapsed_seconds=0\n' > "$RESULT_DIR/resource.time"
669
+ cat > "$RESULT_DIR/metadata.json" <<META
670
+ {
671
+ "instance": $(json_string "$INSTANCE"),
672
+ "repo_url": $(json_string "$REPO_URL"),
673
+ "git_ref": $(json_string "$GIT_REF"),
674
+ "provider": $(json_string "$KASEKI_PROVIDER"),
675
+ "model": $(json_string "$KASEKI_MODEL"),
676
+ "started_at": $(json_string "$(date -u +%Y-%m-%dT%H:%M:%SZ)"),
677
+ "current_stage": $(json_string "$failed_command"),
678
+ "duration_seconds": 0,
679
+ "total_duration_seconds": 0,
680
+ "pi_duration_seconds": 0,
681
+ "exit_code": $exit_code,
682
+ "failed_command": $(json_string "$failed_command")
683
+ }
684
+ META
685
+ cat > "$RESULT_DIR/result-summary.md" <<SUMMARY
686
+ # Kaseki Result: $INSTANCE
687
+
688
+ - Status: failed
689
+ - Failed command: $failed_command
690
+ - Message: $message
691
+ - Artifacts: $RESULT_DIR
692
+ SUMMARY
693
+ write_failure_json "$exit_code" "$failed_command" "$message"
694
+ }
695
+
696
+ record_host_stage_timing() {
697
+ local stage="$1"
698
+ local exit_code="$2"
699
+ local duration_seconds="${3:-0}"
700
+ local detail="${4:-}"
701
+ printf '%s\t%s\t%s\t%s\n' "$stage" "$exit_code" "$duration_seconds" "$detail" >> "$RESULT_DIR/stage-timings.tsv"
702
+ }
703
+
704
+ fail_before_container() {
705
+ local exit_code="$1"
706
+ local failed_command="$2"
707
+ local message="$3"
708
+ printf '%s\n' "$message" > "$RESULT_DIR/stderr.log"
709
+ write_host_metadata_failure "$exit_code" "$failed_command" "$message"
710
+ record_host_stage_timing "$failed_command" "$exit_code" 0 "$message"
711
+ write_cleanup_log
712
+ promote_staging_dirs
713
+ cat "$RESULT_DIR/stderr.log" >&2
714
+ exit "$exit_code"
715
+ }
716
+
717
+ write_cleanup_log() {
718
+ {
719
+ printf 'cleanup_started_at=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
720
+ printf 'keep_workspace=%s\n' "$KASEKI_KEEP_WORKSPACE"
721
+ if [ "$KASEKI_KEEP_WORKSPACE" != "1" ]; then
722
+ rm -rf "$WORKSPACE"
723
+ printf 'workspace_removed=true\n'
724
+ else
725
+ printf 'workspace_removed=false\n'
726
+ fi
727
+ if command -v docker >/dev/null 2>&1; then
728
+ printf '%s\n' 'docker_system_df_after_run:'
729
+ docker system df 2>&1 || true
730
+ fi
731
+ printf 'cleanup_finished_at=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
732
+ } > "$RESULT_DIR/cleanup.log"
733
+
734
+ if [ "$KASEKI_KEEP_WORKSPACE" != "1" ]; then
735
+ rmdir "$RUN_DIR" 2>/dev/null || true
736
+ fi
737
+ }
738
+
739
+ promote_staging_dirs() {
740
+ mkdir -p "$RUNS" "$RESULTS"
741
+ if [ "$PROMOTED_RESULT_DIR" -eq 0 ]; then
742
+ mv "$RESULT_STAGE_DIR" "$FINAL_RESULT_DIR"
743
+ RESULT_DIR="$FINAL_RESULT_DIR"
744
+ HOST_EXIT_CODE_FILE="$RESULT_DIR/host_exit_code"
745
+ PROMOTED_RESULT_DIR=1
746
+ fi
747
+ if [ "$KASEKI_KEEP_WORKSPACE" = "1" ] && [ "$PROMOTED_RUN_DIR" -eq 0 ]; then
748
+ mv "$RUN_STAGE_DIR" "$FINAL_RUN_DIR"
749
+ RUN_DIR="$FINAL_RUN_DIR"
750
+ WORKSPACE="$RUN_DIR/workspace"
751
+ PROMOTED_RUN_DIR=1
752
+ fi
753
+ }
754
+
755
+ initialize_result_artifacts
756
+
757
+ cat > "$RESULT_DIR/host-start.json" <<META
758
+ {
759
+ "instance": $(json_string "$INSTANCE"),
760
+ "repo_url": $(json_string "$REPO_URL"),
761
+ "git_ref": $(json_string "$GIT_REF"),
762
+ "provider": $(json_string "$KASEKI_PROVIDER"),
763
+ "model": $(json_string "$KASEKI_MODEL"),
764
+ "task_mode": $(json_string "$KASEKI_TASK_MODE"),
765
+ "allow_empty_diff": $(json_string "$KASEKI_ALLOW_EMPTY_DIFF"),
766
+ "dry_run": $(json_string "$KASEKI_DRY_RUN"),
767
+ "container_user": $(json_string "$KASEKI_CONTAINER_USER"),
768
+ "changed_files_allowlist": $(json_string "$KASEKI_CHANGED_FILES_ALLOWLIST"),
769
+ "max_diff_bytes": $MAX_DIFF_BYTES_VALUE,
770
+ "agentTimeoutSeconds": $AGENT_TIMEOUT_SECONDS_VALUE,
771
+ "started_at": $(json_string "$(date -u +%Y-%m-%dT%H:%M:%SZ)"),
772
+ "host": $(json_string "$(hostname)"),
773
+ "image": $(json_string "$IMAGE"),
774
+ "cache_dir": $(json_string "$CACHE")
775
+ }
776
+ META
777
+
778
+ if [ -n "${OPENROUTER_API_KEY:-}" ]; then
779
+ key_source="env"
780
+ key_value="$OPENROUTER_API_KEY"
781
+ elif [ -r "$HOST_SECRET_FILE" ]; then
782
+ key_source="secret file"
783
+ key_value="$(cat "$HOST_SECRET_FILE")"
784
+ else
785
+ fail_before_container "$FAILURE_EXIT_CODE_VALUE" "missing OPENROUTER_API_KEY" "OpenRouter API key is required. Set OPENROUTER_API_KEY or provide a readable secret file at $HOST_SECRET_FILE (override with OPENROUTER_API_KEY_FILE)."
786
+ fi
787
+
788
+ if [ -z "$key_value" ]; then
789
+ fail_before_container "$FAILURE_EXIT_CODE_VALUE" "empty OpenRouter API key from ${key_source}" "OpenRouter API key source \"$key_source\" resolved to an empty value."
790
+ fi
791
+
792
+ printf 'OpenRouter API key source: %s\n' "$key_source"
793
+ printf '%s' "$key_value" > "$SECRET_FILE"
794
+ chmod 0600 "$SECRET_FILE"
795
+ unset key_value key_source
796
+
797
+ if ! command -v docker >/dev/null 2>&1; then
798
+ fail_before_container "$FAILURE_EXIT_CODE_VALUE" "preflight docker" "Docker is required but was not found on the host."
799
+ fi
800
+
801
+ docker_image_error="$(docker image inspect "$IMAGE" 2>&1 >/dev/null || true)"
802
+ if [ -n "$docker_image_error" ]; then
803
+ if printf '%s' "$docker_image_error" | grep -qi 'permission denied'; then
804
+ fail_before_container "$FAILURE_EXIT_CODE_VALUE" "preflight docker socket" "Docker socket permission denied while inspecting $IMAGE. Add the API container user to the host Docker socket group, for example group_add with DOCKER_GID."
805
+ elif printf '%s' "$docker_image_error" | grep -Eqi 'cannot connect|is the docker daemon running'; then
806
+ fail_before_container "$FAILURE_EXIT_CODE_VALUE" "preflight docker daemon" "Docker daemon is unreachable while inspecting $IMAGE. Verify /var/run/docker.sock is mounted and the host daemon is running."
807
+ else
808
+ fail_before_container "$FAILURE_EXIT_CODE_VALUE" "preflight docker image" "Docker image is missing locally: $IMAGE. Pull it or set KASEKI_IMAGE to an available image."
809
+ fi
810
+ fi
811
+
812
+ if command -v git >/dev/null 2>&1; then
813
+ preflight_start="$(date +%s)"
814
+ if ! git ls-remote --exit-code "$REPO_URL" "$GIT_REF" >"$RESULT_DIR/preflight-git.log" 2>&1; then
815
+ message="Git ref preflight failed for $REPO_URL at $GIT_REF. The repository or ref may not exist, may be private, or may be unreachable. See preflight-git.log."
816
+ {
817
+ printf '%s\n' "$message"
818
+ cat "$RESULT_DIR/preflight-git.log"
819
+ } > "$RESULT_DIR/stderr.log"
820
+ write_host_metadata_failure 128 "preflight git ref" "$message"
821
+ record_host_stage_timing "preflight git ref" 128 "$(($(date +%s) - preflight_start))" "$message"
822
+ write_cleanup_log
823
+ promote_staging_dirs
824
+ cat "$RESULT_DIR/stderr.log" >&2
825
+ exit 128
826
+ fi
827
+ record_host_stage_timing "preflight git ref" 0 "$(($(date +%s) - preflight_start))" "ok"
828
+ else
829
+ printf 'Git: missing on host; skipping git ref preflight.\n' >> "$RESULT_DIR/progress.log"
830
+ fi
831
+
832
+ # Handle GitHub App credentials (optional)
833
+ GITHUB_APP_ENABLED="0"
834
+ case "$KASEKI_PUBLISH_MODE" in
835
+ auto|none|branch|draft_pr) ;;
836
+ *)
837
+ fail_host 2 "invalid publish mode" "Invalid KASEKI_PUBLISH_MODE: $KASEKI_PUBLISH_MODE (expected auto, none, branch, or draft_pr)"
838
+ ;;
839
+ esac
840
+ github_app_id_value="$(read_secret_value "$GITHUB_APP_ID" "$GITHUB_APP_ID_INPUT_FILE" 2>/dev/null || true)"
841
+ github_app_client_id_value="$(read_secret_value "$GITHUB_APP_CLIENT_ID" "$GITHUB_APP_CLIENT_ID_INPUT_FILE" 2>/dev/null || true)"
842
+ if [ "$KASEKI_PUBLISH_MODE" != "none" ] && [ -n "$github_app_id_value" ] && [ -n "$github_app_client_id_value" ]; then
843
+ github_private_key_value=""
844
+ if [ -n "$GITHUB_APP_PRIVATE_KEY_FILE" ] && [ -r "$GITHUB_APP_PRIVATE_KEY_FILE" ]; then
845
+ github_private_key_value="$(cat "$GITHUB_APP_PRIVATE_KEY_FILE")"
846
+ elif [ -n "$GITHUB_APP_PRIVATE_KEY" ]; then
847
+ github_private_key_value="$GITHUB_APP_PRIVATE_KEY"
848
+ fi
849
+
850
+ if [ -n "$github_private_key_value" ]; then
851
+ printf 'GitHub App credentials: configured\n'
852
+ GITHUB_APP_ENABLED="1"
853
+ printf '%s\n' "$github_app_id_value" > "$GITHUB_APP_ID_FILE"
854
+ chmod 0600 "$GITHUB_APP_ID_FILE"
855
+ printf '%s\n' "$github_app_client_id_value" > "$GITHUB_APP_CLIENT_ID_FILE"
856
+ chmod 0600 "$GITHUB_APP_CLIENT_ID_FILE"
857
+ printf '%s' "$github_private_key_value" | normalize_private_key_pem > "$GITHUB_APP_PRIVATE_KEY_MOUNTED_FILE"
858
+ chmod 0600 "$GITHUB_APP_PRIVATE_KEY_MOUNTED_FILE"
859
+ unset github_private_key_value
860
+ fi
861
+ fi
862
+ unset GITHUB_APP_PRIVATE_KEY github_app_id_value github_app_client_id_value
863
+
864
+ if { [ "$KASEKI_PUBLISH_MODE" = "branch" ] || [ "$KASEKI_PUBLISH_MODE" = "draft_pr" ]; } && [ "$GITHUB_APP_ENABLED" != "1" ]; then
865
+ fail_host 7 "github app credentials" "KASEKI_PUBLISH_MODE=$KASEKI_PUBLISH_MODE requires readable GitHub App credentials."
866
+ fi
867
+
868
+ prepare_worker_paths() {
869
+ if [ "$(id -u)" -ne 0 ]; then
870
+ return 0
871
+ fi
872
+
873
+ chown -R "$KASEKI_CONTAINER_USER" "$RUN_DIR" "$RESULT_DIR"
874
+ for secret_path in "$SECRET_FILE" "$GITHUB_APP_ID_FILE" "$GITHUB_APP_CLIENT_ID_FILE" "$GITHUB_APP_PRIVATE_KEY_MOUNTED_FILE"; do
875
+ if [ -f "$secret_path" ]; then
876
+ chown "$KASEKI_CONTAINER_USER" "$secret_path"
877
+ fi
878
+ done
879
+ }
880
+
881
+ prepare_worker_paths
882
+
883
+ docker_args=(
884
+ run --rm
885
+ --name "$INSTANCE"
886
+ --read-only
887
+ --tmpfs "/tmp:rw,nosuid,nodev,size=256m"
888
+ --security-opt no-new-privileges:true
889
+ --cap-drop ALL
890
+ -u "$KASEKI_CONTAINER_USER"
891
+ -e KASEKI_INSTANCE="$INSTANCE"
892
+ -e REPO_URL="$REPO_URL"
893
+ -e GIT_REF="$GIT_REF"
894
+ -e KASEKI_PROVIDER="$KASEKI_PROVIDER"
895
+ -e KASEKI_MODEL="$KASEKI_MODEL"
896
+ -e KASEKI_AGENT_TIMEOUT_SECONDS="$KASEKI_AGENT_TIMEOUT_SECONDS"
897
+ -e KASEKI_VALIDATION_COMMANDS="$KASEKI_VALIDATION_COMMANDS"
898
+ -e KASEKI_DEBUG_RAW_EVENTS="$KASEKI_DEBUG_RAW_EVENTS"
899
+ -e KASEKI_TASK_MODE="$KASEKI_TASK_MODE"
900
+ -e KASEKI_ALLOW_EMPTY_DIFF="$KASEKI_ALLOW_EMPTY_DIFF"
901
+ -e KASEKI_CHANGED_FILES_ALLOWLIST="$KASEKI_CHANGED_FILES_ALLOWLIST"
902
+ -e KASEKI_VALIDATION_ALLOWLIST="$KASEKI_VALIDATION_ALLOWLIST"
903
+ -e KASEKI_MAX_DIFF_BYTES="$KASEKI_MAX_DIFF_BYTES"
904
+ -e KASEKI_AGENT_GUARDRAILS="$KASEKI_AGENT_GUARDRAILS"
905
+ -e KASEKI_RESTORE_DISALLOWED_CHANGES="$KASEKI_RESTORE_DISALLOWED_CHANGES"
906
+ -e KASEKI_NPM_OMIT_DEV="$KASEKI_NPM_OMIT_DEV"
907
+ -e KASEKI_DRY_RUN="$KASEKI_DRY_RUN"
908
+ -e KASEKI_LOG_DIR="/results"
909
+ -e TASK_PROMPT="$TASK_PROMPT"
910
+ -e GITHUB_APP_ENABLED="$GITHUB_APP_ENABLED"
911
+ -e KASEKI_PUBLISH_MODE="$KASEKI_PUBLISH_MODE"
912
+ -e KASEKI_STREAM_PROGRESS="$KASEKI_STREAM_PROGRESS"
913
+ -e KASEKI_VALIDATE_AFTER_AGENT_FAILURE="$KASEKI_VALIDATE_AFTER_AGENT_FAILURE"
914
+ -e KASEKI_VALIDATION_FAIL_FAST="$KASEKI_VALIDATION_FAIL_FAST"
915
+ -e KASEKI_STRICT_SCRIPT_CHECK="$KASEKI_STRICT_SCRIPT_CHECK"
916
+ -e KASEKI_DEPENDENCY_CACHE_DIR="/cache/dependencies"
917
+ -e TMPDIR="/workspace/tmp"
918
+ -e NPM_CONFIG_CACHE="/cache/npm-cache"
919
+ -e npm_config_cache="/cache/npm-cache"
920
+ -v "$WORKSPACE:/workspace:rw"
921
+ -v "$CACHE:/cache:rw"
922
+ -v "$RESULT_DIR:/results:rw"
923
+ -v "$SECRET_FILE:/run/secrets/openrouter_api_key:ro"
924
+ )
925
+ if [ "$GITHUB_APP_ENABLED" = "1" ]; then
926
+ docker_args+=(
927
+ -v "$GITHUB_APP_ID_FILE:/run/secrets/github_app_id:ro"
928
+ -v "$GITHUB_APP_CLIENT_ID_FILE:/run/secrets/github_app_client_id:ro"
929
+ -v "$GITHUB_APP_PRIVATE_KEY_MOUNTED_FILE:/run/secrets/github_app_private_key:ro"
930
+ )
931
+ fi
932
+ if [ "$KASEKI_DRY_RUN" = "1" ]; then
933
+ docker_args+=(--entrypoint /bin/bash)
934
+ fi
935
+ docker_args+=(
936
+ -w /workspace
937
+ "$IMAGE"
938
+ )
939
+ if [ "$KASEKI_DRY_RUN" = "1" ]; then
940
+ docker_args+=(
941
+ -lc
942
+ 'set -euo pipefail
943
+ printf "[progress] startup check: container booted\n"
944
+ node --version
945
+ git --version
946
+ pi --version >/dev/null
947
+ test -r /run/secrets/openrouter_api_key
948
+ test -w /workspace
949
+ test -w /results
950
+ test -w /cache
951
+ printf "startup_check=ok\n" > /results/startup-check.txt
952
+ printf "[progress] startup check: completed\n"'
953
+ )
954
+ fi
955
+
956
+ set +e
957
+ docker "${docker_args[@]}"
958
+ DOCKER_EXIT="$?"
959
+ set -e
960
+
961
+ END_EPOCH="$(date +%s)"
962
+ printf 'elapsed_seconds=%s\n' "$((END_EPOCH - START_EPOCH))" > "$RESULT_DIR/resource.time"
963
+ printf '%s\n' "$DOCKER_EXIT" > "$RESULT_DIR/host_docker_exit_code"
964
+ persist_host_status "$DOCKER_EXIT"
965
+
966
+ write_cleanup_log
967
+ promote_staging_dirs
968
+
969
+ METRICS_SCRIPT="$SCRIPT_DIR/scripts/kaseki-metrics.sh"
970
+ if [ -x "$METRICS_SCRIPT" ] && [ -f "$RESULT_DIR/stage-timings.tsv" ] && [ -f "$RESULT_DIR/metadata.json" ]; then
971
+ if "$METRICS_SCRIPT" "$RESULT_DIR/stage-timings.tsv" "$RESULT_DIR/metadata.json" "$RESULT_DIR/metrics.json" >/dev/null 2>&1; then
972
+ if [ "$KASEKI_APPEND_METRICS_JSONL" = "1" ]; then
973
+ mkdir -p "$(dirname "$KASEKI_METRICS_JSONL_PATH")" 2>/dev/null || true
974
+ if [ -w "$(dirname "$KASEKI_METRICS_JSONL_PATH")" ] || [ -w "$KASEKI_METRICS_JSONL_PATH" ]; then
975
+ "$METRICS_SCRIPT" "$RESULT_DIR/stage-timings.tsv" "$RESULT_DIR/metadata.json" >> "$KASEKI_METRICS_JSONL_PATH" 2>/dev/null || true
976
+ fi
977
+ fi
978
+ else
979
+ printf 'Warning: metrics generation failed for %s\n' "$RESULT_DIR" >&2
980
+ fi
981
+ fi
982
+
983
+ printf '%s\n' "$INSTANCE"
984
+ if [ "$KASEKI_KEEP_WORKSPACE" = "1" ]; then
985
+ printf 'run_dir=%s\n' "$FINAL_RUN_DIR"
986
+ else
987
+ printf 'run_dir=%s\n' "$FINAL_RUN_DIR (removed)"
988
+ fi
989
+ printf 'result_dir=%s\n' "$FINAL_RESULT_DIR"
990
+ exit "$DOCKER_EXIT"