@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
package/src/index.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Kaseki Agent TypeScript Entry Point
|
|
2
|
+
// Public API exports for library users and service consumers
|
|
3
|
+
|
|
4
|
+
// Services
|
|
5
|
+
export { JobScheduler } from './job-scheduler';
|
|
6
|
+
export { ResultCache } from './result-cache';
|
|
7
|
+
export { WebhookManager } from './webhook-manager';
|
|
8
|
+
|
|
9
|
+
// Configuration & Utilities
|
|
10
|
+
export { KasekiApiConfig, validateApiKey, loadConfig } from './kaseki-api-config';
|
|
11
|
+
export { createGracefulShutdown, assertSupportedNodeVersion } from './kaseki-api-service';
|
|
12
|
+
|
|
13
|
+
// Types
|
|
14
|
+
export type {
|
|
15
|
+
Job,
|
|
16
|
+
RunRequest,
|
|
17
|
+
RunResponse,
|
|
18
|
+
StatusResponse,
|
|
19
|
+
StructuredProgress,
|
|
20
|
+
LogResponse,
|
|
21
|
+
ArtifactResponse,
|
|
22
|
+
RunsListResponse,
|
|
23
|
+
ErrorResponse,
|
|
24
|
+
ValidationResponse,
|
|
25
|
+
PreflightCheck,
|
|
26
|
+
PreflightResponse,
|
|
27
|
+
AnalysisResponse,
|
|
28
|
+
WebhookPayload,
|
|
29
|
+
WebhookEventType,
|
|
30
|
+
} from './kaseki-api-types';
|
|
31
|
+
|
|
32
|
+
// Client
|
|
33
|
+
export { KasekiApiClient } from './kaseki-api-client';
|
|
34
|
+
|
|
35
|
+
// Utilities (Phase 1 extractions)
|
|
36
|
+
export { sendErrorResponse, buildStatusResponse, detectContentType } from './utils/response-helpers';
|
|
37
|
+
export {
|
|
38
|
+
isNonEmptyFile,
|
|
39
|
+
isNonEmptyFile as fileIsNonEmpty,
|
|
40
|
+
readFirstLine,
|
|
41
|
+
readTailLines,
|
|
42
|
+
commandOutput,
|
|
43
|
+
fileExists,
|
|
44
|
+
readFileContent,
|
|
45
|
+
getFileStats,
|
|
46
|
+
} from './utils/file-helpers';
|
|
47
|
+
export {
|
|
48
|
+
isString,
|
|
49
|
+
isNumber,
|
|
50
|
+
isBoolean,
|
|
51
|
+
isRecord,
|
|
52
|
+
isArray,
|
|
53
|
+
isStringArray,
|
|
54
|
+
isRecordArray,
|
|
55
|
+
hasKeys,
|
|
56
|
+
} from './utils/type-guards';
|
|
57
|
+
export {
|
|
58
|
+
decodeUtf8TailSafely,
|
|
59
|
+
tailLogByLines,
|
|
60
|
+
readTailBytes,
|
|
61
|
+
} from './utils/utf8-helpers';
|
|
62
|
+
export { getJobOrRespond } from './utils/route-helpers';
|
|
63
|
+
export { jobLookupMiddleware } from './middleware/job-lookup';
|
|
64
|
+
|
|
65
|
+
// Utilities (Phase 2 consolidations)
|
|
66
|
+
export {
|
|
67
|
+
createJobSubmittedEvent,
|
|
68
|
+
createJobStartedEvent,
|
|
69
|
+
createJobCompletedEvent,
|
|
70
|
+
createJobCancelledEvent,
|
|
71
|
+
createJobFailedEvent,
|
|
72
|
+
} from './utils/webhook-event-builder';
|
|
73
|
+
export { HttpClientFactory } from './utils/http-client-factory';
|
|
74
|
+
export type { HttpRequestOptions, RetryConfig } from './utils/http-client-factory';
|
|
75
|
+
|
|
76
|
+
// Utilities (Phase 3 refactoring)
|
|
77
|
+
export { FailureArtifactWriter } from './utils/failure-artifact-writer';
|
|
78
|
+
export type { CleanupResult } from './utils/failure-artifact-writer';
|
|
79
|
+
export { StatusResponseBuilder } from './utils/status-response-builder';
|
|
80
|
+
export {
|
|
81
|
+
normalizeProgressEvent,
|
|
82
|
+
toStructuredProgress,
|
|
83
|
+
} from './utils/progress-normalizer';
|
|
84
|
+
|
|
85
|
+
// Route modules (Phase 3 refactoring)
|
|
86
|
+
export { createStatusRoutes } from './routes/status-routes';
|
|
87
|
+
export { createLogRoutes } from './routes/log-routes';
|
|
88
|
+
export { createArtifactRoutes, readArtifactContent } from './routes/artifact-routes';
|
|
89
|
+
export { createWebhookRoutes } from './routes/webhook-routes';
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instance Manager
|
|
3
|
+
*
|
|
4
|
+
* Handles kaseki instance lifecycle: naming, directories, metadata
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { createLogger } from '../logger';
|
|
10
|
+
|
|
11
|
+
const logger = createLogger('instance');
|
|
12
|
+
|
|
13
|
+
export interface InstanceMetadata {
|
|
14
|
+
id: string;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
completedAt?: string;
|
|
17
|
+
status: 'running' | 'completed' | 'failed';
|
|
18
|
+
exitCode?: number;
|
|
19
|
+
model?: string;
|
|
20
|
+
provider?: string;
|
|
21
|
+
repoUrl?: string;
|
|
22
|
+
gitRef?: string;
|
|
23
|
+
stages?: {
|
|
24
|
+
[stageName: string]: {
|
|
25
|
+
startTime?: string;
|
|
26
|
+
endTime?: string;
|
|
27
|
+
exitCode?: number;
|
|
28
|
+
duration?: number;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class InstanceManager {
|
|
34
|
+
private rootDir: string;
|
|
35
|
+
private instanceId: string | null = null;
|
|
36
|
+
|
|
37
|
+
constructor(rootDir: string = '/agents') {
|
|
38
|
+
this.rootDir = rootDir;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generate or get next instance ID
|
|
43
|
+
*/
|
|
44
|
+
async getOrCreateInstanceId(): Promise<string> {
|
|
45
|
+
if (this.instanceId) {
|
|
46
|
+
return this.instanceId;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Find next available instance number
|
|
50
|
+
const runsDir = path.join(this.rootDir, 'kaseki-runs');
|
|
51
|
+
let instanceNum = 1;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const entries = await fs.readdir(runsDir, { withFileTypes: true });
|
|
55
|
+
const existingNums = entries
|
|
56
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith('kaseki-'))
|
|
57
|
+
.map((entry) => {
|
|
58
|
+
const match = entry.name.match(/kaseki-(\d+)/);
|
|
59
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
60
|
+
})
|
|
61
|
+
.filter((num) => num > 0);
|
|
62
|
+
|
|
63
|
+
if (existingNums.length > 0) {
|
|
64
|
+
instanceNum = Math.max(...existingNums) + 1;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Directory doesn't exist yet, that's fine
|
|
68
|
+
instanceNum = 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.instanceId = `kaseki-${instanceNum}`;
|
|
72
|
+
logger.debug(`Generated instance ID: ${this.instanceId}`);
|
|
73
|
+
|
|
74
|
+
return this.instanceId;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get workspace directory path
|
|
79
|
+
*/
|
|
80
|
+
async getWorkspaceDir(): Promise<string> {
|
|
81
|
+
const id = await this.getOrCreateInstanceId();
|
|
82
|
+
return path.join(this.rootDir, 'kaseki-runs', id);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get results directory path
|
|
87
|
+
*/
|
|
88
|
+
async getResultsDir(): Promise<string> {
|
|
89
|
+
const id = await this.getOrCreateInstanceId();
|
|
90
|
+
return path.join(this.rootDir, 'kaseki-results', id);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get cache directory path
|
|
95
|
+
*/
|
|
96
|
+
getCacheDir(): string {
|
|
97
|
+
return path.join(this.rootDir, 'kaseki-cache');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create instance directories
|
|
102
|
+
*/
|
|
103
|
+
async createDirectories(): Promise<{ workspace: string; results: string }> {
|
|
104
|
+
const workspace = await this.getWorkspaceDir();
|
|
105
|
+
const results = await this.getResultsDir();
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await fs.mkdir(workspace, { recursive: true });
|
|
109
|
+
await fs.mkdir(results, { recursive: true });
|
|
110
|
+
logger.debug(`Created instance directories for ${this.instanceId}`);
|
|
111
|
+
|
|
112
|
+
return { workspace, results };
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw new Error(`Failed to create instance directories: ${error}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Initialize metadata file
|
|
120
|
+
*/
|
|
121
|
+
async initializeMetadata(metadata: Partial<InstanceMetadata>): Promise<void> {
|
|
122
|
+
const id = await this.getOrCreateInstanceId();
|
|
123
|
+
const resultsDir = await this.getResultsDir();
|
|
124
|
+
const metadataPath = path.join(resultsDir, 'metadata.json');
|
|
125
|
+
|
|
126
|
+
const fullMetadata: InstanceMetadata = {
|
|
127
|
+
id,
|
|
128
|
+
createdAt: new Date().toISOString(),
|
|
129
|
+
status: 'running',
|
|
130
|
+
stages: {},
|
|
131
|
+
...metadata,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
await fs.writeFile(
|
|
136
|
+
metadataPath,
|
|
137
|
+
JSON.stringify(fullMetadata, null, 2),
|
|
138
|
+
'utf-8'
|
|
139
|
+
);
|
|
140
|
+
logger.debug(`Initialized metadata for ${id}`);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
throw new Error(`Failed to initialize metadata: ${error}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Update metadata
|
|
148
|
+
*/
|
|
149
|
+
async updateMetadata(updates: Partial<InstanceMetadata>): Promise<void> {
|
|
150
|
+
const resultsDir = await this.getResultsDir();
|
|
151
|
+
const metadataPath = path.join(resultsDir, 'metadata.json');
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
let metadata: InstanceMetadata = {
|
|
155
|
+
id: this.instanceId || '',
|
|
156
|
+
createdAt: new Date().toISOString(),
|
|
157
|
+
status: 'running',
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Read existing metadata
|
|
161
|
+
try {
|
|
162
|
+
const content = await fs.readFile(metadataPath, 'utf-8');
|
|
163
|
+
metadata = JSON.parse(content);
|
|
164
|
+
} catch {
|
|
165
|
+
// File doesn't exist yet, that's fine
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Merge updates
|
|
169
|
+
const updated = { ...metadata, ...updates };
|
|
170
|
+
|
|
171
|
+
// Write back
|
|
172
|
+
await fs.writeFile(
|
|
173
|
+
metadataPath,
|
|
174
|
+
JSON.stringify(updated, null, 2),
|
|
175
|
+
'utf-8'
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
logger.debug(`Updated metadata for ${this.instanceId}`);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
logger.warn(`Failed to update metadata: ${error}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Record stage timing
|
|
186
|
+
*/
|
|
187
|
+
async recordStage(
|
|
188
|
+
stageName: string,
|
|
189
|
+
exitCode?: number,
|
|
190
|
+
startTime?: Date,
|
|
191
|
+
endTime?: Date
|
|
192
|
+
): Promise<void> {
|
|
193
|
+
const resultsDir = await this.getResultsDir();
|
|
194
|
+
const metadataPath = path.join(resultsDir, 'metadata.json');
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
let metadata: InstanceMetadata = {
|
|
198
|
+
id: this.instanceId || '',
|
|
199
|
+
createdAt: new Date().toISOString(),
|
|
200
|
+
status: 'running',
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const content = await fs.readFile(metadataPath, 'utf-8');
|
|
205
|
+
metadata = JSON.parse(content);
|
|
206
|
+
} catch {
|
|
207
|
+
// File doesn't exist
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!metadata.stages) {
|
|
211
|
+
metadata.stages = {};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
metadata.stages[stageName] = {
|
|
215
|
+
startTime: startTime?.toISOString(),
|
|
216
|
+
endTime: endTime?.toISOString(),
|
|
217
|
+
exitCode,
|
|
218
|
+
duration: startTime && endTime ?
|
|
219
|
+
(endTime.getTime() - startTime.getTime()) / 1000 : undefined,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
await fs.writeFile(
|
|
223
|
+
metadataPath,
|
|
224
|
+
JSON.stringify(metadata, null, 2),
|
|
225
|
+
'utf-8'
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
logger.debug(`Recorded stage: ${stageName} (exit code: ${exitCode})`);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
logger.warn(`Failed to record stage: ${error}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Finalize instance metadata
|
|
236
|
+
*/
|
|
237
|
+
async finalize(exitCode: number): Promise<void> {
|
|
238
|
+
await this.updateMetadata({
|
|
239
|
+
completedAt: new Date().toISOString(),
|
|
240
|
+
status: exitCode === 0 ? 'completed' : 'failed',
|
|
241
|
+
exitCode,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
logger.info(`Finalized instance ${this.instanceId}: exit code ${exitCode}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get instance metadata
|
|
249
|
+
*/
|
|
250
|
+
async getMetadata(): Promise<InstanceMetadata | null> {
|
|
251
|
+
try {
|
|
252
|
+
const resultsDir = await this.getResultsDir();
|
|
253
|
+
const metadataPath = path.join(resultsDir, 'metadata.json');
|
|
254
|
+
const content = await fs.readFile(metadataPath, 'utf-8');
|
|
255
|
+
return JSON.parse(content);
|
|
256
|
+
} catch {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Clean up instance workspace (optional)
|
|
263
|
+
*/
|
|
264
|
+
async cleanup(keepWorkspace: boolean = false): Promise<void> {
|
|
265
|
+
if (keepWorkspace) {
|
|
266
|
+
logger.debug(`Keeping workspace for ${this.instanceId}`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const workspace = await this.getWorkspaceDir();
|
|
272
|
+
await fs.rm(workspace, { recursive: true, force: true });
|
|
273
|
+
logger.debug(`Cleaned up workspace for ${this.instanceId}`);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
logger.warn(`Failed to cleanup workspace: ${error}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Get instance ID
|
|
281
|
+
*/
|
|
282
|
+
getInstanceId(): string | null {
|
|
283
|
+
return this.instanceId;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { readInstanceMetadata } from './instance-metadata-reader';
|
|
5
|
+
|
|
6
|
+
describe('instance-metadata-reader', () => {
|
|
7
|
+
let tempDir: string;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kaseki-test-'));
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should read metadata.json and host-start.json', () => {
|
|
18
|
+
const metadata = {
|
|
19
|
+
current_stage: 'validation',
|
|
20
|
+
exit_code: 0,
|
|
21
|
+
duration_seconds: 120,
|
|
22
|
+
model: 'claude-3-opus',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const hostStart = {
|
|
26
|
+
model: 'claude-3-opus',
|
|
27
|
+
repo_url: 'https://github.com/example/repo',
|
|
28
|
+
git_ref: 'main',
|
|
29
|
+
agentTimeoutSeconds: 1200,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
fs.writeFileSync(path.join(tempDir, 'metadata.json'), JSON.stringify(metadata));
|
|
33
|
+
fs.writeFileSync(path.join(tempDir, 'host-start.json'), JSON.stringify(hostStart));
|
|
34
|
+
|
|
35
|
+
const result = readInstanceMetadata(tempDir);
|
|
36
|
+
|
|
37
|
+
expect(result.metadata).toEqual(metadata);
|
|
38
|
+
expect(result.hostStart).toEqual(hostStart);
|
|
39
|
+
expect(result.elapsedSeconds).toBe(120);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should return empty objects when files do not exist', () => {
|
|
43
|
+
const result = readInstanceMetadata(tempDir);
|
|
44
|
+
|
|
45
|
+
expect(result.metadata).toEqual({});
|
|
46
|
+
expect(result.hostStart).toEqual({});
|
|
47
|
+
expect(result.elapsedSeconds).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should handle corrupt JSON in metadata.json gracefully', () => {
|
|
51
|
+
fs.writeFileSync(path.join(tempDir, 'metadata.json'), '{invalid json');
|
|
52
|
+
|
|
53
|
+
const result = readInstanceMetadata(tempDir);
|
|
54
|
+
|
|
55
|
+
expect(result.metadata).toEqual({});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should handle corrupt JSON in host-start.json gracefully', () => {
|
|
59
|
+
fs.writeFileSync(path.join(tempDir, 'host-start.json'), '{invalid json');
|
|
60
|
+
|
|
61
|
+
const result = readInstanceMetadata(tempDir);
|
|
62
|
+
|
|
63
|
+
expect(result.hostStart).toEqual({});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should read elapsed_seconds from resource.time when metadata.duration_seconds is missing', () => {
|
|
67
|
+
const metadata = { current_stage: 'completed' };
|
|
68
|
+
fs.writeFileSync(
|
|
69
|
+
path.join(tempDir, 'metadata.json'),
|
|
70
|
+
JSON.stringify(metadata)
|
|
71
|
+
);
|
|
72
|
+
fs.writeFileSync(
|
|
73
|
+
path.join(tempDir, 'resource.time'),
|
|
74
|
+
'user_cpu=1.234\nelapsed_seconds=90\nsystem_cpu=0.456'
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const result = readInstanceMetadata(tempDir);
|
|
78
|
+
|
|
79
|
+
expect(result.elapsedSeconds).toBe(90);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should prefer metadata.duration_seconds over resource.time', () => {
|
|
83
|
+
const metadata = { current_stage: 'completed', duration_seconds: 120 };
|
|
84
|
+
fs.writeFileSync(
|
|
85
|
+
path.join(tempDir, 'metadata.json'),
|
|
86
|
+
JSON.stringify(metadata)
|
|
87
|
+
);
|
|
88
|
+
fs.writeFileSync(
|
|
89
|
+
path.join(tempDir, 'resource.time'),
|
|
90
|
+
'elapsed_seconds=90'
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const result = readInstanceMetadata(tempDir);
|
|
94
|
+
|
|
95
|
+
expect(result.elapsedSeconds).toBe(120);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should handle malformed resource.time gracefully', () => {
|
|
99
|
+
const metadata = { current_stage: 'completed' };
|
|
100
|
+
fs.writeFileSync(
|
|
101
|
+
path.join(tempDir, 'metadata.json'),
|
|
102
|
+
JSON.stringify(metadata)
|
|
103
|
+
);
|
|
104
|
+
fs.writeFileSync(path.join(tempDir, 'resource.time'), 'malformed_content');
|
|
105
|
+
|
|
106
|
+
const result = readInstanceMetadata(tempDir);
|
|
107
|
+
|
|
108
|
+
expect(result.elapsedSeconds).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should return null for elapsedSeconds when neither source has valid data', () => {
|
|
112
|
+
const metadata = { current_stage: 'pending' };
|
|
113
|
+
fs.writeFileSync(
|
|
114
|
+
path.join(tempDir, 'metadata.json'),
|
|
115
|
+
JSON.stringify(metadata)
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const result = readInstanceMetadata(tempDir);
|
|
119
|
+
|
|
120
|
+
expect(result.elapsedSeconds).toBeNull();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should preserve all metadata fields', () => {
|
|
124
|
+
const metadata = {
|
|
125
|
+
current_stage: 'validation',
|
|
126
|
+
exit_code: 1,
|
|
127
|
+
duration_seconds: 120,
|
|
128
|
+
started_at: '2024-01-01T10:00:00Z',
|
|
129
|
+
model: 'gpt-4',
|
|
130
|
+
pi_duration_seconds: 60,
|
|
131
|
+
custom_field: 'custom_value',
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
fs.writeFileSync(path.join(tempDir, 'metadata.json'), JSON.stringify(metadata));
|
|
135
|
+
|
|
136
|
+
const result = readInstanceMetadata(tempDir);
|
|
137
|
+
|
|
138
|
+
expect(result.metadata).toEqual(metadata);
|
|
139
|
+
expect(result.metadata.custom_field).toBe('custom_value');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should propagate ENOENT errors when reading metadata.json', () => {
|
|
143
|
+
const metadataPath = path.join(tempDir, 'metadata.json');
|
|
144
|
+
|
|
145
|
+
// Create file, then delete it during read simulation
|
|
146
|
+
fs.writeFileSync(metadataPath, '{}');
|
|
147
|
+
|
|
148
|
+
// Override readFileSync to throw ENOENT
|
|
149
|
+
const originalReadFileSync = fs.readFileSync as any;
|
|
150
|
+
let readCallCount = 0;
|
|
151
|
+
(fs.readFileSync as any) = jest.fn((filePath: any, ...args: any[]) => {
|
|
152
|
+
readCallCount++;
|
|
153
|
+
if (readCallCount === 1 && filePath === metadataPath) {
|
|
154
|
+
const error = new Error('File not found') as any;
|
|
155
|
+
error.code = 'ENOENT';
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
return originalReadFileSync(filePath, ...args);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
expect(() => readInstanceMetadata(tempDir)).toThrow();
|
|
163
|
+
} finally {
|
|
164
|
+
(fs.readFileSync as any) = originalReadFileSync;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should propagate ESTALE errors when reading host-start.json', () => {
|
|
169
|
+
const hostStartPath = path.join(tempDir, 'host-start.json');
|
|
170
|
+
fs.writeFileSync(hostStartPath, '{}');
|
|
171
|
+
|
|
172
|
+
const originalReadFileSync = fs.readFileSync as any;
|
|
173
|
+
let readCallCount = 0;
|
|
174
|
+
(fs.readFileSync as any) = jest.fn((filePath: any, ...args: any[]) => {
|
|
175
|
+
readCallCount++;
|
|
176
|
+
if (readCallCount === 1 && filePath === hostStartPath) {
|
|
177
|
+
const error = new Error('Stale NFS file handle') as any;
|
|
178
|
+
error.code = 'ESTALE';
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
return originalReadFileSync(filePath, ...args);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
expect(() => readInstanceMetadata(tempDir)).toThrow();
|
|
186
|
+
} finally {
|
|
187
|
+
(fs.readFileSync as any) = originalReadFileSync;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* instance-metadata-reader.ts
|
|
3
|
+
*
|
|
4
|
+
* Encapsulates metadata reading logic for kaseki instances.
|
|
5
|
+
* Safely reads and parses metadata.json, host-start.json, and resource.time files
|
|
6
|
+
* with graceful error handling for transient I/O errors.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
export interface Metadata {
|
|
13
|
+
current_stage?: string;
|
|
14
|
+
exit_code?: number | string;
|
|
15
|
+
duration_seconds?: number;
|
|
16
|
+
started_at?: string;
|
|
17
|
+
start_time?: string;
|
|
18
|
+
model?: string;
|
|
19
|
+
pi_duration_seconds?: number;
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface HostStart {
|
|
24
|
+
model?: string;
|
|
25
|
+
repo_url?: string;
|
|
26
|
+
repo?: string;
|
|
27
|
+
git_ref?: string;
|
|
28
|
+
ref?: string;
|
|
29
|
+
agentTimeoutSeconds?: number;
|
|
30
|
+
[key: string]: any;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface InstanceMetadataInfo {
|
|
34
|
+
metadata: Metadata;
|
|
35
|
+
hostStart: HostStart;
|
|
36
|
+
elapsedSeconds: number | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if an error is a transient I/O error that should be retried or skipped.
|
|
41
|
+
*/
|
|
42
|
+
function isSkippableInstanceIoError(error: any): boolean {
|
|
43
|
+
return error && (error.code === 'ENOENT' || error.code === 'ESTALE');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Read elapsed seconds from metadata or resource.time file.
|
|
48
|
+
* Tries metadata.duration_seconds first, then falls back to resource.time.
|
|
49
|
+
*/
|
|
50
|
+
function readElapsedSeconds(resultDir: string, metadata: Metadata): number | null {
|
|
51
|
+
if (metadata.duration_seconds !== undefined) {
|
|
52
|
+
return metadata.duration_seconds;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const resourceTimePath = path.join(resultDir, 'resource.time');
|
|
56
|
+
if (!fs.existsSync(resourceTimePath)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const content = fs.readFileSync(resourceTimePath, 'utf8');
|
|
62
|
+
const match = content.match(/elapsed_seconds=([0-9]+(?:\.[0-9]+)?)/);
|
|
63
|
+
if (match) {
|
|
64
|
+
return parseFloat(match[1]);
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// File may be unreadable; return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Read metadata for a kaseki instance from its results directory.
|
|
75
|
+
*
|
|
76
|
+
* Reads three files:
|
|
77
|
+
* - metadata.json (primary source of truth)
|
|
78
|
+
* - host-start.json (configuration from run initiation)
|
|
79
|
+
* - resource.time (timing information if metadata incomplete)
|
|
80
|
+
*
|
|
81
|
+
* Handles transient I/O errors (ENOENT, ESTALE) by propagating them
|
|
82
|
+
* so callers can skip the instance and continue scanning.
|
|
83
|
+
*
|
|
84
|
+
* @param resultDir - Path to the kaseki results directory (e.g., /agents/kaseki-results/kaseki-1)
|
|
85
|
+
* @returns InstanceMetadataInfo with parsed metadata, host config, and elapsed time
|
|
86
|
+
* @throws Error if I/O error is transient (ENOENT, ESTALE); caller should skip this instance
|
|
87
|
+
*/
|
|
88
|
+
export function readInstanceMetadata(resultDir: string): InstanceMetadataInfo {
|
|
89
|
+
const metadataPath = path.join(resultDir, 'metadata.json');
|
|
90
|
+
const hostStartPath = path.join(resultDir, 'host-start.json');
|
|
91
|
+
|
|
92
|
+
let metadata: Metadata = {};
|
|
93
|
+
let hostStart: HostStart = {};
|
|
94
|
+
|
|
95
|
+
// Read metadata
|
|
96
|
+
if (fs.existsSync(metadataPath)) {
|
|
97
|
+
try {
|
|
98
|
+
const content = fs.readFileSync(metadataPath, 'utf8');
|
|
99
|
+
metadata = JSON.parse(content);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
if (isSkippableInstanceIoError(e)) {
|
|
102
|
+
throw e; // Propagate transient errors to caller
|
|
103
|
+
}
|
|
104
|
+
// Metadata may still be incomplete if run is in progress; use empty object
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Read host start config
|
|
109
|
+
if (fs.existsSync(hostStartPath)) {
|
|
110
|
+
try {
|
|
111
|
+
const content = fs.readFileSync(hostStartPath, 'utf8');
|
|
112
|
+
hostStart = JSON.parse(content);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
if (isSkippableInstanceIoError(e)) {
|
|
115
|
+
throw e; // Propagate transient errors to caller
|
|
116
|
+
}
|
|
117
|
+
// File may be unreadable; use empty object
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Read elapsed seconds
|
|
122
|
+
const elapsedSeconds = readElapsedSeconds(resultDir, metadata);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
metadata,
|
|
126
|
+
hostStart,
|
|
127
|
+
elapsedSeconds,
|
|
128
|
+
};
|
|
129
|
+
}
|