@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
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Docker Manager
3
+ *
4
+ * Handles Docker operations: image pulling, container spawning, log streaming
5
+ */
6
+
7
+ import { execSync, spawn } from 'child_process';
8
+ import { createLogger } from '../logger';
9
+
10
+ const logger = createLogger('docker');
11
+
12
+ export interface ContainerConfig {
13
+ image: string;
14
+ name: string;
15
+ workspaceDir: string;
16
+ resultsDir: string;
17
+ cacheDir?: string;
18
+ apiKeyFile?: string;
19
+ environment: Record<string, string>;
20
+ timeout?: number; // in seconds
21
+ entrypoint?: string;
22
+ command?: string[];
23
+ }
24
+
25
+ export interface ContainerResult {
26
+ exitCode: number;
27
+ stdout: string;
28
+ stderr: string;
29
+ }
30
+
31
+ export class DockerManager {
32
+ /**
33
+ * Check if Docker is available and daemon is running
34
+ */
35
+ static isDockerAvailable(): boolean {
36
+ try {
37
+ execSync('docker ps > /dev/null 2>&1', { shell: '/bin/bash' });
38
+ return true;
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Pull Docker image (with retry logic)
46
+ */
47
+ static pullImage(image: string, maxRetries: number = 3): boolean {
48
+ logger.info(`Pulling Docker image: ${image}`);
49
+
50
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
51
+ try {
52
+ execSync(`docker pull ${image}`, {
53
+ stdio: 'inherit',
54
+ timeout: 5 * 60 * 1000, // 5 minute timeout
55
+ });
56
+ logger.info(`Successfully pulled image: ${image}`);
57
+ return true;
58
+ } catch (error) {
59
+ if (attempt < maxRetries) {
60
+ logger.warn(`Pull attempt ${attempt} failed. Retrying...`);
61
+ } else {
62
+ logger.error(`Failed to pull image after ${maxRetries} attempts: ${error}`);
63
+ return false;
64
+ }
65
+ }
66
+ }
67
+
68
+ return false;
69
+ }
70
+
71
+ /**
72
+ * Check if Docker image exists locally
73
+ */
74
+ static imageExists(image: string): boolean {
75
+ try {
76
+ execSync(`docker inspect ${image} > /dev/null 2>&1`, { shell: '/bin/bash' });
77
+ return true;
78
+ } catch {
79
+ return false;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Get Docker image ID
85
+ */
86
+ static getImageId(image: string): string | null {
87
+ try {
88
+ const id = execSync(`docker inspect -f '{{.ID}}' ${image}`, {
89
+ encoding: 'utf-8',
90
+ }).trim();
91
+ return id || null;
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Run Docker container and stream output
99
+ */
100
+ static async runContainer(config: ContainerConfig): Promise<ContainerResult> {
101
+ logger.debug(`Running container: ${config.name}`);
102
+
103
+ // Ensure directories exist
104
+ this.ensureDirectories(config.workspaceDir, config.resultsDir, config.cacheDir);
105
+
106
+ // Build Docker run command
107
+ const dockerArgs = this.buildDockerArgs(config);
108
+
109
+ logger.debug(`Docker command: docker run ${dockerArgs.join(' ')}`);
110
+
111
+ return new Promise((resolve) => {
112
+ let stdout = '';
113
+ let stderr = '';
114
+
115
+ const child = spawn('docker', ['run', ...dockerArgs], {
116
+ stdio: ['ignore', 'pipe', 'pipe'],
117
+ timeout: (config.timeout || 1200) * 1000,
118
+ });
119
+
120
+ // Collect stdout
121
+ if (child.stdout) {
122
+ child.stdout.on('data', (data: Buffer) => {
123
+ const text = data.toString();
124
+ stdout += text;
125
+ // Also stream to console for real-time feedback
126
+ process.stdout.write(text);
127
+ });
128
+ }
129
+
130
+ // Collect stderr
131
+ if (child.stderr) {
132
+ child.stderr.on('data', (data: Buffer) => {
133
+ const text = data.toString();
134
+ stderr += text;
135
+ process.stderr.write(text);
136
+ });
137
+ }
138
+
139
+ // Handle completion
140
+ child.on('exit', (code: number | null) => {
141
+ resolve({
142
+ exitCode: code ?? 1,
143
+ stdout,
144
+ stderr,
145
+ });
146
+ });
147
+
148
+ // Handle errors
149
+ child.on('error', (error: Error) => {
150
+ logger.error(`Failed to spawn docker: ${error.message}`);
151
+ resolve({
152
+ exitCode: 1,
153
+ stdout,
154
+ stderr: error.message,
155
+ });
156
+ });
157
+
158
+ // Handle timeout
159
+ child.on('timeout', () => {
160
+ logger.warn(`Container timeout after ${config.timeout} seconds`);
161
+ child.kill('SIGTERM');
162
+ resolve({
163
+ exitCode: 124,
164
+ stdout,
165
+ stderr: `Container timeout after ${config.timeout} seconds`,
166
+ });
167
+ });
168
+ });
169
+ }
170
+
171
+ /**
172
+ * Build Docker run arguments
173
+ */
174
+ private static buildDockerArgs(config: ContainerConfig): string[] {
175
+ const args: string[] = [];
176
+
177
+ // Container name
178
+ args.push('--name', config.name);
179
+
180
+ // Security flags (from run-kaseki.sh)
181
+ args.push('--read-only');
182
+ args.push('--cap-drop=ALL');
183
+ args.push('--security-opt', 'no-new-privileges:true');
184
+ args.push('-u', '10001:10001');
185
+
186
+ // Temporary filesystems for writable areas
187
+ args.push('--tmpfs', '/tmp:rw,nosuid,nodev,noexec');
188
+ args.push('--tmpfs', '/var/tmp:rw,nosuid,nodev,noexec');
189
+ args.push('--tmpfs', '/run:rw,nosuid,nodev,noexec');
190
+
191
+ // Mount workspace (rw for agent to clone repo)
192
+ args.push('-v', `${config.workspaceDir}:/workspace:rw`);
193
+
194
+ // Mount results (rw for writing outputs)
195
+ args.push('-v', `${config.resultsDir}:/results:rw`);
196
+
197
+ // Mount cache if provided
198
+ if (config.cacheDir) {
199
+ args.push('-v', `${config.cacheDir}:/cache:rw`);
200
+ }
201
+
202
+ // Mount API key file (ro for security)
203
+ if (config.apiKeyFile) {
204
+ args.push('-v', `${config.apiKeyFile}:/run/secrets/openrouter_api_key:ro`);
205
+ }
206
+
207
+ // Set environment variables (never pass API key via env)
208
+ for (const [key, value] of Object.entries(config.environment)) {
209
+ if (key !== 'OPENROUTER_API_KEY') {
210
+ // Skip inline API key
211
+ args.push('-e', `${key}=${value}`);
212
+ }
213
+ }
214
+
215
+ // Set entrypoint if provided
216
+ if (config.entrypoint) {
217
+ args.push('--entrypoint', config.entrypoint);
218
+ }
219
+
220
+ // Image
221
+ args.push(config.image);
222
+
223
+ // Command and arguments
224
+ if (config.command) {
225
+ args.push(...config.command);
226
+ }
227
+
228
+ return args;
229
+ }
230
+
231
+ /**
232
+ * Ensure required directories exist with correct permissions
233
+ */
234
+ private static ensureDirectories(
235
+ workspaceDir: string,
236
+ resultsDir: string,
237
+ cacheDir?: string
238
+ ): void {
239
+ const dirs = [workspaceDir, resultsDir];
240
+ if (cacheDir) {
241
+ dirs.push(cacheDir);
242
+ }
243
+
244
+ for (const dir of dirs) {
245
+ try {
246
+ execSync(`mkdir -p "${dir}"`, { shell: '/bin/bash' });
247
+ logger.debug(`Created/verified directory: ${dir}`);
248
+ } catch (error) {
249
+ throw new Error(`Failed to create directory ${dir}: ${error}`);
250
+ }
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Stop a running container
256
+ */
257
+ static stopContainer(containerId: string, timeout: number = 10): boolean {
258
+ try {
259
+ execSync(`docker stop -t ${timeout} ${containerId}`, { stdio: 'ignore' });
260
+ logger.debug(`Stopped container: ${containerId}`);
261
+ return true;
262
+ } catch {
263
+ logger.warn(`Failed to stop container: ${containerId}`);
264
+ return false;
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Remove a container
270
+ */
271
+ static removeContainer(containerId: string, force: boolean = true): boolean {
272
+ try {
273
+ const args = ['rm'];
274
+ if (force) args.push('-f');
275
+ args.push(containerId);
276
+ execSync(`docker ${args.join(' ')}`, { stdio: 'ignore' });
277
+ logger.debug(`Removed container: ${containerId}`);
278
+ return true;
279
+ } catch {
280
+ logger.warn(`Failed to remove container: ${containerId}`);
281
+ return false;
282
+ }
283
+ }
284
+
285
+ /**
286
+ * List running containers matching a pattern
287
+ */
288
+ static listContainers(pattern: string = 'kaseki'): string[] {
289
+ try {
290
+ const output = execSync(
291
+ `docker ps --filter "name=${pattern}" --format "{{.Names}}"`,
292
+ { encoding: 'utf-8' }
293
+ );
294
+ return output
295
+ .split('\n')
296
+ .filter((name) => name.trim())
297
+ .map((name) => name.trim());
298
+ } catch {
299
+ return [];
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Get container logs
305
+ */
306
+ static getContainerLogs(containerId: string, tail?: number): string {
307
+ try {
308
+ const args = ['logs'];
309
+ if (tail) {
310
+ args.push(`--tail=${tail}`);
311
+ }
312
+ args.push(containerId);
313
+
314
+ return execSync(`docker ${args.join(' ')}`, { encoding: 'utf-8' });
315
+ } catch {
316
+ return '';
317
+ }
318
+ }
319
+ }
@@ -0,0 +1,33 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ const repoRoot = path.resolve(__dirname, '..');
5
+
6
+ describe('Docker runtime packaging', () => {
7
+ test('worker can resolve allowlist helper from the /app fallback path', () => {
8
+ const script = fs.readFileSync(path.join(repoRoot, 'kaseki-agent.sh'), 'utf-8');
9
+ expect(script).toContain('ALLOWLIST_HELPER="$SCRIPT_DIR/scripts/allowlist-helper.sh"');
10
+ expect(script).toContain('[ -r /app/scripts/allowlist-helper.sh ]');
11
+ expect(script).toContain('ALLOWLIST_HELPER="/app/scripts/allowlist-helper.sh"');
12
+ });
13
+
14
+ test('image entrypoint dispatches api and explicit commands without replacing entrypoint', () => {
15
+ const dockerfile = fs.readFileSync(path.join(repoRoot, 'Dockerfile'), 'utf-8');
16
+ const entrypoint = fs.readFileSync(path.join(repoRoot, 'scripts/docker-entrypoint.sh'), 'utf-8');
17
+
18
+ expect(dockerfile).toContain('ENTRYPOINT ["/usr/local/bin/kaseki-entrypoint"]');
19
+ expect(dockerfile).toContain('CMD ["agent"]');
20
+ expect(entrypoint).toContain('api|kaseki-api)');
21
+ expect(entrypoint).toContain('exec node /app/dist/kaseki-api-service.js');
22
+ expect(entrypoint).toContain('exec "$@"');
23
+ });
24
+
25
+ test('template deployment preserves the configured image ref and records digest separately', () => {
26
+ const deployScript = fs.readFileSync(path.join(repoRoot, 'scripts/deploy-pi-template.sh'), 'utf-8');
27
+
28
+ expect(deployScript).toContain('REQUESTED_IMAGE="${KASEKI_IMAGE:-docker.io/cyanautomation/kaseki-agent:latest}"');
29
+ expect(deployScript).toContain('printf \'%s\\n\' "$configured_image" > "$target/.kaseki-image"');
30
+ expect(deployScript).toContain('docker image inspect "$deployed_image" --format');
31
+ expect(deployScript).not.toContain('IMAGE="$resolved_digest"');
32
+ });
33
+ });
@@ -0,0 +1,117 @@
1
+ import { EventCounterAggregator } from './event-aggregator';
2
+
3
+ describe('EventCounterAggregator', () => {
4
+ it('should record event types and increment counts', () => {
5
+ const aggregator = new EventCounterAggregator();
6
+ aggregator.recordEventType('assistant_message');
7
+ aggregator.recordEventType('assistant_message');
8
+ aggregator.recordEventType('thinking_start');
9
+
10
+ const summary = aggregator.summary();
11
+ expect(summary.event_counts['assistant_message']).toBe(2);
12
+ expect(summary.event_counts['thinking_start']).toBe(1);
13
+ });
14
+
15
+ it('should handle missing event types by using <missing> placeholder', () => {
16
+ const aggregator = new EventCounterAggregator();
17
+ aggregator.recordEventType(undefined);
18
+ aggregator.recordEventType(undefined);
19
+
20
+ const summary = aggregator.summary();
21
+ expect(summary.event_counts['<missing>']).toBe(2);
22
+ });
23
+
24
+ it('should cap cardinality at 1000 keys and fold into __other__', () => {
25
+ const aggregator = new EventCounterAggregator();
26
+
27
+ // Add 1001 unique event types
28
+ for (let i = 0; i < 1001; i++) {
29
+ aggregator.recordEventType(`event_${i}`);
30
+ }
31
+
32
+ const summary = aggregator.summary();
33
+ const uniqueKeys = Object.keys(summary.event_counts).filter((k) => k !== '__other__');
34
+
35
+ // Should have at most 1000 unique keys (plus __other__)
36
+ expect(uniqueKeys.length).toBe(1000);
37
+ expect(summary.event_counts['__other__']).toBe(1);
38
+ });
39
+
40
+ it('should record model and API observations', () => {
41
+ const aggregator = new EventCounterAggregator();
42
+ aggregator.recordModelAndApi({ model: 'claude-3-opus', api: 'openrouter' });
43
+ aggregator.recordModelAndApi({ model: 'claude-3-opus', api: 'openrouter' });
44
+ aggregator.recordModelAndApi({ model: 'gpt-4', api: 'openai' });
45
+
46
+ const summary = aggregator.summary();
47
+ expect(summary.selected_model).toBe('claude-3-opus');
48
+ expect(summary.selected_api).toBe('openrouter');
49
+ });
50
+
51
+ it.each([
52
+ ['empty object', {}],
53
+ ['null', null],
54
+ ['undefined', undefined],
55
+ ['empty string', ''],
56
+ ['non-object string', 'not an object'],
57
+ ['number', 42],
58
+ ])(
59
+ 'should ignore %s model/API observations',
60
+ (_description, invalidObservation) => {
61
+ const emptyAggregator = new EventCounterAggregator();
62
+ emptyAggregator.recordModelAndApi(invalidObservation);
63
+
64
+ const emptySummary = emptyAggregator.summary();
65
+ expect(emptySummary.selected_model).toBe('');
66
+ expect(emptySummary.selected_api).toBe('');
67
+
68
+ const aggregator = new EventCounterAggregator();
69
+ aggregator.recordModelAndApi({ model: 'claude-3-opus', api: 'openrouter' });
70
+ aggregator.recordModelAndApi(invalidObservation);
71
+
72
+ const summary = aggregator.summary();
73
+ expect(summary.selected_model).toBe('claude-3-opus');
74
+ expect(summary.selected_api).toBe('openrouter');
75
+ }
76
+ );
77
+
78
+ it('should track assistant event types separately', () => {
79
+ const aggregator = new EventCounterAggregator();
80
+ aggregator.recordAssistantEventType('assistant_message');
81
+ aggregator.recordAssistantEventType('thinking');
82
+ aggregator.recordAssistantEventType('thinking');
83
+
84
+ const summary = aggregator.summary();
85
+ expect(summary.assistant_event_counts['assistant_message']).toBe(1);
86
+ expect(summary.assistant_event_counts['thinking']).toBe(2);
87
+ });
88
+
89
+ it('should count tool start and end events', () => {
90
+ const aggregator = new EventCounterAggregator();
91
+ aggregator.recordToolStart();
92
+ aggregator.recordToolStart();
93
+ aggregator.recordToolEnd();
94
+ aggregator.recordToolEnd();
95
+ aggregator.recordToolEnd();
96
+
97
+ const summary = aggregator.summary();
98
+ expect(summary.tool_start_count).toBe(2);
99
+ expect(summary.tool_end_count).toBe(3);
100
+ });
101
+
102
+ it('should select top model/api by frequency', () => {
103
+ const aggregator = new EventCounterAggregator();
104
+
105
+ // Model A more frequent
106
+ aggregator.recordModelAndApi({ model: 'model-a', api: 'api-a' });
107
+ aggregator.recordModelAndApi({ model: 'model-a', api: 'api-a' });
108
+ aggregator.recordModelAndApi({ model: 'model-a', api: 'api-b' });
109
+ aggregator.recordModelAndApi({ model: 'model-b', api: 'api-a' });
110
+
111
+ const summary = aggregator.summary();
112
+ expect(summary.selected_model).toBe('model-a');
113
+ // api-a has count 3 (appears with model-a twice and model-b once)
114
+ expect(summary.selected_api).toBe('api-a');
115
+ });
116
+
117
+ });
@@ -0,0 +1,126 @@
1
+ /**
2
+ * event-aggregator.ts
3
+ *
4
+ * Encapsulates counter aggregation logic for Pi event stream processing.
5
+ * Tracks event types, models, APIs, tool executions, and assistant message types
6
+ * with bounded cardinality (1000 keys per counter).
7
+ */
8
+
9
+ const MAX_DISTINCT_SUMMARY_KEYS = 1000;
10
+ const OTHER_BUCKET_KEY = '__other__';
11
+
12
+ export interface EventCountMap {
13
+ [key: string]: number;
14
+ }
15
+
16
+ export interface AggregatorSummary {
17
+ selected_model: string;
18
+ selected_api: string;
19
+ event_counts: EventCountMap;
20
+ assistant_event_counts: EventCountMap;
21
+ tool_start_count: number;
22
+ tool_end_count: number;
23
+ }
24
+
25
+ /**
26
+ * EventCounterAggregator manages counter maps for event stream aggregation.
27
+ *
28
+ * Responsibilities:
29
+ * - Track event type counts with cardinality cap
30
+ * - Track assistant message type counts
31
+ * - Observe and count models and APIs
32
+ * - Track tool execution start/end events
33
+ * - Provide summarized output with top model/API selected
34
+ */
35
+ export class EventCounterAggregator {
36
+ private eventCounts: EventCountMap = {};
37
+ private assistantEventCounts: EventCountMap = {};
38
+ private models: EventCountMap = {};
39
+ private apis: EventCountMap = {};
40
+ private toolStartCount = 0;
41
+ private toolEndCount = 0;
42
+
43
+ /**
44
+ * Increment a counter in a map with cardinality cap.
45
+ * Once a map reaches MAX_DISTINCT_SUMMARY_KEYS entries, new unseen keys
46
+ * are folded into the "__other__" bucket.
47
+ */
48
+ private incrementMap(
49
+ map: EventCountMap,
50
+ key: string | undefined,
51
+ maxDistinctKeys: number = MAX_DISTINCT_SUMMARY_KEYS
52
+ ): void {
53
+ if (!key) return;
54
+
55
+ let targetKey = key;
56
+ if (
57
+ map[key] === undefined &&
58
+ Object.keys(map).filter((k) => k !== OTHER_BUCKET_KEY).length >= maxDistinctKeys
59
+ ) {
60
+ targetKey = OTHER_BUCKET_KEY;
61
+ }
62
+ map[targetKey] = (map[targetKey] ?? 0) + 1;
63
+ }
64
+
65
+ /**
66
+ * Record an event type observation.
67
+ */
68
+ recordEventType(eventType: string | undefined): void {
69
+ this.incrementMap(this.eventCounts, eventType ?? '<missing>', MAX_DISTINCT_SUMMARY_KEYS);
70
+ }
71
+
72
+ /**
73
+ * Record an assistant message type observation.
74
+ */
75
+ recordAssistantEventType(assistantType: string | undefined): void {
76
+ this.incrementMap(
77
+ this.assistantEventCounts,
78
+ assistantType,
79
+ MAX_DISTINCT_SUMMARY_KEYS
80
+ );
81
+ }
82
+
83
+ /**
84
+ * Record model and API observations from a message object.
85
+ */
86
+ recordModelAndApi(message: any): void {
87
+ if (!message || typeof message !== 'object') return;
88
+ this.incrementMap(this.models, message.model, MAX_DISTINCT_SUMMARY_KEYS);
89
+ this.incrementMap(this.apis, message.api, MAX_DISTINCT_SUMMARY_KEYS);
90
+ }
91
+
92
+ /**
93
+ * Record a tool execution start event.
94
+ */
95
+ recordToolStart(): void {
96
+ this.toolStartCount++;
97
+ }
98
+
99
+ /**
100
+ * Record a tool execution end event.
101
+ */
102
+ recordToolEnd(): void {
103
+ this.toolEndCount++;
104
+ }
105
+
106
+ /**
107
+ * Get the top model by frequency.
108
+ */
109
+ private topByFrequency(map: EventCountMap): string {
110
+ return Object.entries(map).sort((a, b) => b[1] - a[1])[0]?.[0] ?? '';
111
+ }
112
+
113
+ /**
114
+ * Generate summary with selected model/API and all counters.
115
+ */
116
+ summary(): AggregatorSummary {
117
+ return {
118
+ selected_model: this.topByFrequency(this.models),
119
+ selected_api: this.topByFrequency(this.apis),
120
+ event_counts: this.eventCounts,
121
+ assistant_event_counts: this.assistantEventCounts,
122
+ tool_start_count: this.toolStartCount,
123
+ tool_end_count: this.toolEndCount,
124
+ };
125
+ }
126
+ }