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