@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.
- package/.dockerignore +54 -0
- package/.eslintignore +11 -0
- package/.eslintrc.json +95 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +53 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +53 -0
- package/.github/ISSUE_TEMPLATE/security.md +51 -0
- package/.github/PULL_REQUEST_TEMPLATE/default.md +71 -0
- package/.github/dependabot.yml +38 -0
- package/.github/skills/dependency-cache-optimization/SKILL.md +526 -0
- package/.github/skills/docker-image-management/SKILL.md +532 -0
- package/.github/skills/frontend-design/SKILL.md +782 -0
- package/.github/skills/prompt-engineering/SKILL.md +360 -0
- package/.github/skills/quality-gate-config/SKILL.md +591 -0
- package/.github/skills/result-report-analysis/SKILL.md +576 -0
- package/.github/skills/test-automation/SKILL.md +593 -0
- package/.github/skills/workflow-diagnosis/SKILL.md +468 -0
- package/.github/workflows/build-docker-image.yml +453 -0
- package/.github/workflows/release.yml +68 -0
- package/.releaserc.json +135 -0
- package/CHANGELOG.md +117 -0
- package/CLAUDE.md +336 -0
- package/CONTRIBUTING.md +339 -0
- package/Dockerfile +217 -0
- package/README.md +1527 -0
- package/STYLE.md +521 -0
- package/add-js-extensions.d.ts +9 -0
- package/add-js-extensions.d.ts.map +1 -0
- package/add-js-extensions.js.map +1 -0
- package/dist/add-js-extensions.d.ts +9 -0
- package/dist/add-js-extensions.d.ts.map +1 -0
- package/dist/add-js-extensions.js +52 -0
- package/dist/add-js-extensions.js.map +1 -0
- package/dist/ansi-colors.d.ts +26 -0
- package/dist/ansi-colors.d.ts.map +1 -0
- package/dist/ansi-colors.js +51 -0
- package/dist/ansi-colors.js.map +1 -0
- package/dist/cli/BaseCommand.d.ts +18 -0
- package/dist/cli/BaseCommand.d.ts.map +1 -0
- package/dist/cli/BaseCommand.js +31 -0
- package/dist/cli/BaseCommand.js.map +1 -0
- package/dist/cli/KasekiCLI.d.ts +30 -0
- package/dist/cli/KasekiCLI.d.ts.map +1 -0
- package/dist/cli/KasekiCLI.js +134 -0
- package/dist/cli/KasekiCLI.js.map +1 -0
- package/dist/cli/commands/ConfigCommand.d.ts +13 -0
- package/dist/cli/commands/ConfigCommand.d.ts.map +1 -0
- package/dist/cli/commands/ConfigCommand.js +131 -0
- package/dist/cli/commands/ConfigCommand.js.map +1 -0
- package/dist/cli/commands/DoctorCommand.d.ts +45 -0
- package/dist/cli/commands/DoctorCommand.d.ts.map +1 -0
- package/dist/cli/commands/DoctorCommand.js +309 -0
- package/dist/cli/commands/DoctorCommand.js.map +1 -0
- package/dist/cli/commands/ListCommand.d.ts +9 -0
- package/dist/cli/commands/ListCommand.d.ts.map +1 -0
- package/dist/cli/commands/ListCommand.js +81 -0
- package/dist/cli/commands/ListCommand.js.map +1 -0
- package/dist/cli/commands/ReportCommand.d.ts +9 -0
- package/dist/cli/commands/ReportCommand.d.ts.map +1 -0
- package/dist/cli/commands/ReportCommand.js +98 -0
- package/dist/cli/commands/ReportCommand.js.map +1 -0
- package/dist/cli/commands/RunCommand.d.ts +13 -0
- package/dist/cli/commands/RunCommand.d.ts.map +1 -0
- package/dist/cli/commands/RunCommand.js +191 -0
- package/dist/cli/commands/RunCommand.js.map +1 -0
- package/dist/cli/commands/SecretsCommand.d.ts +9 -0
- package/dist/cli/commands/SecretsCommand.d.ts.map +1 -0
- package/dist/cli/commands/SecretsCommand.js +109 -0
- package/dist/cli/commands/SecretsCommand.js.map +1 -0
- package/dist/cli/commands/ServeCommand.d.ts +9 -0
- package/dist/cli/commands/ServeCommand.d.ts.map +1 -0
- package/dist/cli/commands/ServeCommand.js +50 -0
- package/dist/cli/commands/ServeCommand.js.map +1 -0
- package/dist/cli/commands/SetupCommand.d.ts +42 -0
- package/dist/cli/commands/SetupCommand.d.ts.map +1 -0
- package/dist/cli/commands/SetupCommand.js +249 -0
- package/dist/cli/commands/SetupCommand.js.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +130 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/ConfigManager.d.ts +395 -0
- package/dist/config/ConfigManager.d.ts.map +1 -0
- package/dist/config/ConfigManager.js +446 -0
- package/dist/config/ConfigManager.js.map +1 -0
- package/dist/docker/DockerManager.d.ts +69 -0
- package/dist/docker/DockerManager.d.ts.map +1 -0
- package/dist/docker/DockerManager.js +266 -0
- package/dist/docker/DockerManager.js.map +1 -0
- package/dist/event-aggregator.d.ts +71 -0
- package/dist/event-aggregator.d.ts.map +1 -0
- package/dist/event-aggregator.js +95 -0
- package/dist/event-aggregator.js.map +1 -0
- package/dist/github-app-token.d.ts +16 -0
- package/dist/github-app-token.d.ts.map +1 -0
- package/dist/github-app-token.js +148 -0
- package/dist/github-app-token.js.map +1 -0
- package/dist/idempotency-store.d.ts +61 -0
- package/dist/idempotency-store.d.ts.map +1 -0
- package/dist/idempotency-store.js +321 -0
- package/dist/idempotency-store.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/instance/InstanceManager.d.ts +81 -0
- package/dist/instance/InstanceManager.d.ts.map +1 -0
- package/dist/instance/InstanceManager.js +220 -0
- package/dist/instance/InstanceManager.js.map +1 -0
- package/dist/instance-metadata-reader.d.ts +48 -0
- package/dist/instance-metadata-reader.d.ts.map +1 -0
- package/dist/instance-metadata-reader.js +94 -0
- package/dist/instance-metadata-reader.js.map +1 -0
- package/dist/instance-state-derivation.d.ts +42 -0
- package/dist/instance-state-derivation.d.ts.map +1 -0
- package/dist/instance-state-derivation.js +133 -0
- package/dist/instance-state-derivation.js.map +1 -0
- package/dist/job-scheduler.d.ts +124 -0
- package/dist/job-scheduler.d.ts.map +1 -0
- package/dist/job-scheduler.js +992 -0
- package/dist/job-scheduler.js.map +1 -0
- package/dist/kaseki-api-client.d.ts +89 -0
- package/dist/kaseki-api-client.d.ts.map +1 -0
- package/dist/kaseki-api-client.js +405 -0
- package/dist/kaseki-api-client.js.map +1 -0
- package/dist/kaseki-api-config.d.ts +34 -0
- package/dist/kaseki-api-config.d.ts.map +1 -0
- package/dist/kaseki-api-config.js +113 -0
- package/dist/kaseki-api-config.js.map +1 -0
- package/dist/kaseki-api-routes.d.ts +13 -0
- package/dist/kaseki-api-routes.d.ts.map +1 -0
- package/dist/kaseki-api-routes.js +559 -0
- package/dist/kaseki-api-routes.js.map +1 -0
- package/dist/kaseki-api-service-wrapper.d.ts +43 -0
- package/dist/kaseki-api-service-wrapper.d.ts.map +1 -0
- package/dist/kaseki-api-service-wrapper.js +150 -0
- package/dist/kaseki-api-service-wrapper.js.map +1 -0
- package/dist/kaseki-api-service.d.ts +16 -0
- package/dist/kaseki-api-service.d.ts.map +1 -0
- package/dist/kaseki-api-service.js +143 -0
- package/dist/kaseki-api-service.js.map +1 -0
- package/dist/kaseki-api-types.d.ts +440 -0
- package/dist/kaseki-api-types.d.ts.map +1 -0
- package/dist/kaseki-api-types.js +64 -0
- package/dist/kaseki-api-types.js.map +1 -0
- package/dist/kaseki-cli-lib.d.ts +219 -0
- package/dist/kaseki-cli-lib.d.ts.map +1 -0
- package/dist/kaseki-cli-lib.js +523 -0
- package/dist/kaseki-cli-lib.js.map +1 -0
- package/dist/kaseki-cli.d.ts +38 -0
- package/dist/kaseki-cli.d.ts.map +1 -0
- package/dist/kaseki-cli.js +559 -0
- package/dist/kaseki-cli.js.map +1 -0
- package/dist/kaseki-report.d.ts +3 -0
- package/dist/kaseki-report.d.ts.map +1 -0
- package/dist/kaseki-report.js +140 -0
- package/dist/kaseki-report.js.map +1 -0
- package/dist/lib/subprocess-helpers.d.ts +98 -0
- package/dist/lib/subprocess-helpers.d.ts.map +1 -0
- package/dist/lib/subprocess-helpers.js +136 -0
- package/dist/lib/subprocess-helpers.js.map +1 -0
- package/dist/logger.d.ts +39 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +79 -0
- package/dist/logger.js.map +1 -0
- package/dist/metrics.d.ts +19 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +59 -0
- package/dist/metrics.js.map +1 -0
- package/dist/middleware/job-lookup.d.ts +27 -0
- package/dist/middleware/job-lookup.d.ts.map +1 -0
- package/dist/middleware/job-lookup.js +28 -0
- package/dist/middleware/job-lookup.js.map +1 -0
- package/dist/pi-event-filter.d.ts +3 -0
- package/dist/pi-event-filter.d.ts.map +1 -0
- package/dist/pi-event-filter.js +126 -0
- package/dist/pi-event-filter.js.map +1 -0
- package/dist/pi-progress-stream.d.ts +3 -0
- package/dist/pi-progress-stream.d.ts.map +1 -0
- package/dist/pi-progress-stream.js +205 -0
- package/dist/pi-progress-stream.js.map +1 -0
- package/dist/pi-progress-summarizer.d.ts +61 -0
- package/dist/pi-progress-summarizer.d.ts.map +1 -0
- package/dist/pi-progress-summarizer.js +246 -0
- package/dist/pi-progress-summarizer.js.map +1 -0
- package/dist/pre-flight-validator.d.ts +72 -0
- package/dist/pre-flight-validator.d.ts.map +1 -0
- package/dist/pre-flight-validator.js +513 -0
- package/dist/pre-flight-validator.js.map +1 -0
- package/dist/progress-stream-utils.d.ts +3 -0
- package/dist/progress-stream-utils.d.ts.map +1 -0
- package/dist/progress-stream-utils.js +15 -0
- package/dist/progress-stream-utils.js.map +1 -0
- package/dist/result-cache.d.ts +52 -0
- package/dist/result-cache.d.ts.map +1 -0
- package/dist/result-cache.js +134 -0
- package/dist/result-cache.js.map +1 -0
- package/dist/routes/artifact-routes.d.ts +10 -0
- package/dist/routes/artifact-routes.d.ts.map +1 -0
- package/dist/routes/artifact-routes.js +126 -0
- package/dist/routes/artifact-routes.js.map +1 -0
- package/dist/routes/log-routes.d.ts +8 -0
- package/dist/routes/log-routes.d.ts.map +1 -0
- package/dist/routes/log-routes.js +345 -0
- package/dist/routes/log-routes.js.map +1 -0
- package/dist/routes/status-routes.d.ts +8 -0
- package/dist/routes/status-routes.d.ts.map +1 -0
- package/dist/routes/status-routes.js +82 -0
- package/dist/routes/status-routes.js.map +1 -0
- package/dist/routes/webhook-routes.d.ts +6 -0
- package/dist/routes/webhook-routes.d.ts.map +1 -0
- package/dist/routes/webhook-routes.js +86 -0
- package/dist/routes/webhook-routes.js.map +1 -0
- package/dist/run-artifact-metadata-cache.d.ts +42 -0
- package/dist/run-artifact-metadata-cache.d.ts.map +1 -0
- package/dist/run-artifact-metadata-cache.js +139 -0
- package/dist/run-artifact-metadata-cache.js.map +1 -0
- package/dist/secret-value-cache.d.ts +13 -0
- package/dist/secret-value-cache.d.ts.map +1 -0
- package/dist/secret-value-cache.js +44 -0
- package/dist/secret-value-cache.js.map +1 -0
- package/dist/secrets/SecretsManager.d.ts +80 -0
- package/dist/secrets/SecretsManager.d.ts.map +1 -0
- package/dist/secrets/SecretsManager.js +306 -0
- package/dist/secrets/SecretsManager.js.map +1 -0
- package/dist/test-utils.d.ts +55 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +48 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/timestamp-tracker.d.ts +75 -0
- package/dist/timestamp-tracker.d.ts.map +1 -0
- package/dist/timestamp-tracker.js +121 -0
- package/dist/timestamp-tracker.js.map +1 -0
- package/dist/utils/failure-artifact-writer.d.ts +29 -0
- package/dist/utils/failure-artifact-writer.d.ts.map +1 -0
- package/dist/utils/failure-artifact-writer.js +157 -0
- package/dist/utils/failure-artifact-writer.js.map +1 -0
- package/dist/utils/file-helpers.d.ts +41 -0
- package/dist/utils/file-helpers.d.ts.map +1 -0
- package/dist/utils/file-helpers.js +143 -0
- package/dist/utils/file-helpers.js.map +1 -0
- package/dist/utils/http-client-factory.d.ts +46 -0
- package/dist/utils/http-client-factory.d.ts.map +1 -0
- package/dist/utils/http-client-factory.js +114 -0
- package/dist/utils/http-client-factory.js.map +1 -0
- package/dist/utils/progress-normalizer.d.ts +13 -0
- package/dist/utils/progress-normalizer.d.ts.map +1 -0
- package/dist/utils/progress-normalizer.js +57 -0
- package/dist/utils/progress-normalizer.js.map +1 -0
- package/dist/utils/response-helpers.d.ts +34 -0
- package/dist/utils/response-helpers.d.ts.map +1 -0
- package/dist/utils/response-helpers.js +78 -0
- package/dist/utils/response-helpers.js.map +1 -0
- package/dist/utils/route-helpers.d.ts +17 -0
- package/dist/utils/route-helpers.d.ts.map +1 -0
- package/dist/utils/route-helpers.js +22 -0
- package/dist/utils/route-helpers.js.map +1 -0
- package/dist/utils/status-response-builder.d.ts +23 -0
- package/dist/utils/status-response-builder.d.ts.map +1 -0
- package/dist/utils/status-response-builder.js +144 -0
- package/dist/utils/status-response-builder.js.map +1 -0
- package/dist/utils/type-guards.d.ts +37 -0
- package/dist/utils/type-guards.d.ts.map +1 -0
- package/dist/utils/type-guards.js +45 -0
- package/dist/utils/type-guards.js.map +1 -0
- package/dist/utils/utf8-helpers.d.ts +32 -0
- package/dist/utils/utf8-helpers.d.ts.map +1 -0
- package/dist/utils/utf8-helpers.js +97 -0
- package/dist/utils/utf8-helpers.js.map +1 -0
- package/dist/utils/webhook-event-builder.d.ts +26 -0
- package/dist/utils/webhook-event-builder.d.ts.map +1 -0
- package/dist/utils/webhook-event-builder.js +77 -0
- package/dist/utils/webhook-event-builder.js.map +1 -0
- package/dist/webhook-manager.d.ts +56 -0
- package/dist/webhook-manager.d.ts.map +1 -0
- package/dist/webhook-manager.js +359 -0
- package/dist/webhook-manager.js.map +1 -0
- package/docker/workspace-cache/package-lock.json +13 -0
- package/docker/workspace-cache/package.json +7 -0
- package/docker-compose.yml +53 -0
- package/docs/API.md +708 -0
- package/docs/BACKLOG.md +19 -0
- package/docs/BUILD_STRATEGY.md +404 -0
- package/docs/CLI.md +569 -0
- package/docs/DEPLOYMENT.md +521 -0
- package/docs/DEVELOPMENT.md +459 -0
- package/docs/DOCKER_SETUP.md +522 -0
- package/docs/ENHANCED_PROGRESS_LOGS.md +264 -0
- package/docs/IMPLEMENTATION_SUMMARY.md +549 -0
- package/docs/INTEGRATION_EXAMPLE.md +217 -0
- package/docs/NPM_SETUP.md +468 -0
- package/docs/PHASE1-4_IMPLEMENTATION.md +302 -0
- package/docs/PHASE1_COMPLETION.md +192 -0
- package/docs/PHASE2_COMPLETION.md +134 -0
- package/docs/PHASE6_MIGRATION.md +392 -0
- package/docs/PRINTF_SAFETY_FIX.md +282 -0
- package/docs/QUALITY_GATES.md +369 -0
- package/docs/SETUP_GUIDE.md +482 -0
- package/docs/TASK_PROMPT_TEMPLATES.md +533 -0
- package/docs/VALIDATION_FIX.md +139 -0
- package/docs/VERIFICATION_CHECKLIST.md +335 -0
- package/docs/repo-maturity.md +760 -0
- package/fix-tests.d.ts +9 -0
- package/fix-tests.d.ts.map +1 -0
- package/fix-tests.js.map +1 -0
- package/fix-tests.ts +53 -0
- package/jest.config.ts +31 -0
- package/kaseki +183 -0
- package/kaseki-agent.sh +1961 -0
- package/ops/logrotate/kaseki +10 -0
- package/package.json +83 -0
- package/perf/README.md +54 -0
- package/perf/pi-event-filter.benchmark.test.ts +98 -0
- package/run-kaseki-json.test.sh +106 -0
- package/run-kaseki.sh +990 -0
- package/scripts/allowlist-helper.sh +56 -0
- package/scripts/cleanup-kaseki.sh +168 -0
- package/scripts/deploy-pi-template.sh +293 -0
- package/scripts/docker-entrypoint.sh +71 -0
- package/scripts/dry-run-allowlist.sh +161 -0
- package/scripts/kaseki-activate.sh +396 -0
- package/scripts/kaseki-api.service +62 -0
- package/scripts/kaseki-container-entrypoint-wrapper.sh +119 -0
- package/scripts/kaseki-container-setup-remote.sh +172 -0
- package/scripts/kaseki-container-setup.sh +193 -0
- package/scripts/kaseki-healthcheck.sh +95 -0
- package/scripts/kaseki-install.sh +50 -0
- package/scripts/kaseki-maturity-score.sh +291 -0
- package/scripts/kaseki-performance-metrics.sh +122 -0
- package/scripts/kaseki-preflight.sh +270 -0
- package/scripts/kaseki-setup.sh +265 -0
- package/scripts/pi-setup-remote.sh +213 -0
- package/scripts/setup-github-labels.sh +42 -0
- package/scripts/suggest-allowlist.sh +68 -0
- package/scripts/templates/MULTI_HOST_DISTRIBUTED.md +337 -0
- package/scripts/templates/REST_API_SERVICE.md +490 -0
- package/scripts/templates/SINGLE_HOST_CLI.md +194 -0
- package/scripts/test-github-app.sh +248 -0
- package/src/add-js-extensions.ts +61 -0
- package/src/ansi-colors.test.ts +62 -0
- package/src/ansi-colors.ts +67 -0
- package/src/cli/BaseCommand.ts +40 -0
- package/src/cli/KasekiCLI.ts +154 -0
- package/src/cli/commands/ConfigCommand.ts +145 -0
- package/src/cli/commands/DoctorCommand.ts +329 -0
- package/src/cli/commands/ListCommand.ts +105 -0
- package/src/cli/commands/ReportCommand.ts +110 -0
- package/src/cli/commands/RunCommand.ts +218 -0
- package/src/cli/commands/SecretsCommand.ts +120 -0
- package/src/cli/commands/ServeCommand.ts +62 -0
- package/src/cli/commands/SetupCommand.ts +301 -0
- package/src/cli.ts +138 -0
- package/src/config/ConfigManager.ts +476 -0
- package/src/docker/DockerManager.ts +319 -0
- package/src/docker-entrypoint-packaging.test.ts +33 -0
- package/src/event-aggregator.test.ts +117 -0
- package/src/event-aggregator.ts +126 -0
- package/src/github-app-token.ts +215 -0
- package/src/idempotency-store.test.ts +117 -0
- package/src/idempotency-store.ts +385 -0
- package/src/index.ts +89 -0
- package/src/instance/InstanceManager.ts +285 -0
- package/src/instance-metadata-reader.test.ts +190 -0
- package/src/instance-metadata-reader.ts +129 -0
- package/src/instance-state-derivation.test.ts +263 -0
- package/src/instance-state-derivation.ts +148 -0
- package/src/job-scheduler.test.ts +1236 -0
- package/src/job-scheduler.ts +1117 -0
- package/src/kaseki-api-client.ts +488 -0
- package/src/kaseki-api-config.test.ts +315 -0
- package/src/kaseki-api-config.ts +175 -0
- package/src/kaseki-api-routes.test.ts +1615 -0
- package/src/kaseki-api-routes.ts +643 -0
- package/src/kaseki-api-service-wrapper.ts +188 -0
- package/src/kaseki-api-service.test.ts +418 -0
- package/src/kaseki-api-service.ts +192 -0
- package/src/kaseki-api-types.ts +320 -0
- package/src/kaseki-cli-lib.test.ts +552 -0
- package/src/kaseki-cli-lib.ts +760 -0
- package/src/kaseki-cli.ts +682 -0
- package/src/kaseki-report.test.ts +118 -0
- package/src/kaseki-report.ts +192 -0
- package/src/lib/subprocess-helpers.ts +177 -0
- package/src/logger.ts +114 -0
- package/src/metrics.ts +66 -0
- package/src/middleware/job-lookup.test.ts +113 -0
- package/src/middleware/job-lookup.ts +45 -0
- package/src/pi-event-filter.test.ts +183 -0
- package/src/pi-event-filter.ts +183 -0
- package/src/pi-progress-stream.ts +287 -0
- package/src/pi-progress-summarizer.test.ts +302 -0
- package/src/pi-progress-summarizer.ts +287 -0
- package/src/pre-flight-validator.test.ts +512 -0
- package/src/pre-flight-validator.ts +618 -0
- package/src/progress-stream-utils.test.ts +35 -0
- package/src/progress-stream-utils.ts +14 -0
- package/src/result-cache.test.ts +195 -0
- package/src/result-cache.ts +181 -0
- package/src/routes/artifact-routes.ts +169 -0
- package/src/routes/log-routes.ts +391 -0
- package/src/routes/status-routes.ts +92 -0
- package/src/routes/webhook-routes.ts +97 -0
- package/src/run-artifact-metadata-cache.test.ts +80 -0
- package/src/run-artifact-metadata-cache.ts +184 -0
- package/src/secret-value-cache.test.ts +66 -0
- package/src/secret-value-cache.ts +55 -0
- package/src/secrets/SecretsManager.ts +343 -0
- package/src/test-utils.ts +81 -0
- package/src/timestamp-tracker.test.ts +134 -0
- package/src/timestamp-tracker.ts +132 -0
- package/src/utils/failure-artifact-writer.ts +187 -0
- package/src/utils/file-helpers.test.ts +235 -0
- package/src/utils/file-helpers.ts +150 -0
- package/src/utils/http-client-factory.test.ts +245 -0
- package/src/utils/http-client-factory.ts +157 -0
- package/src/utils/progress-normalizer.test.ts +442 -0
- package/src/utils/progress-normalizer.ts +68 -0
- package/src/utils/response-helpers.test.ts +122 -0
- package/src/utils/response-helpers.ts +101 -0
- package/src/utils/route-helpers.ts +30 -0
- package/src/utils/status-response-builder.ts +159 -0
- package/src/utils/type-guards.ts +52 -0
- package/src/utils/utf8-helpers.ts +102 -0
- package/src/utils/webhook-event-builder.test.ts +143 -0
- package/src/utils/webhook-event-builder.ts +87 -0
- package/src/webhook-manager.test.ts +152 -0
- package/src/webhook-manager.ts +445 -0
- package/templates/allowlist-api-route.txt +7 -0
- package/templates/allowlist-comprehensive.txt +8 -0
- package/templates/allowlist-parser-fix.txt +6 -0
- package/templates/allowlist-ui-component.txt +9 -0
- package/templates/allowlist-utility.txt +9 -0
- package/test/actual-model-metadata.test.sh +102 -0
- package/test/dry-run.test.sh +131 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-0.json +1 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-1.json +1 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-invalid.json +1 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-str-0.json +1 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-str-1.json +1 -0
- package/test/kaseki-api.integration.test.sh +165 -0
- package/test/pi-event-filter-failure.test.sh +83 -0
- package/test/printf-safety-focused.test.sh +99 -0
- package/test/printf-safety-results/results/restoration.jsonl +10 -0
- package/test/printf-safety-results/results/test.jsonl +0 -0
- package/test/printf-safety.test.sh +297 -0
- package/test/validation-fix.test.sh +79 -0
- package/test/validation-integration.test.sh +109 -0
- package/tests/allowlist-glob.test.sh +61 -0
- package/tests/dependency-cache-key.test.sh +48 -0
- package/tests/dependency-restore-mode.test.sh +48 -0
- package/tests/doctor-template-parity.test.sh +95 -0
- package/tests/github-operations.test.sh +142 -0
- package/tests/npm-install-flags.test.sh +58 -0
- package/tests/quality-gates.test.sh +178 -0
- package/tests/repo-memory.test.sh +103 -0
- package/tests/restore-disallowed-changes.test.sh +80 -0
- package/tests/validation-missing-npm-scripts.test.sh +93 -0
- package/tests/validation-strict-mode.test.sh +118 -0
- package/tsconfig.changed.json +7 -0
- 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
|
+
}
|