@wardby/cli 0.2.1 → 0.3.0
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/.env.example +34 -4
- package/README.md +41 -4
- package/dist/claude-coding-worker/driver.d.ts +6 -1
- package/dist/claude-coding-worker/driver.js +27 -1
- package/dist/claude-coding-worker/main.js +2 -0
- package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
- package/dist/claude-coding-worker/tool-socket.js +26 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +13 -4
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +219 -29
- package/dist/coding/collect-exclude.d.ts +25 -0
- package/dist/coding/collect-exclude.js +76 -0
- package/dist/coding/profile.d.ts +67 -22
- package/dist/coding/profile.js +58 -25
- package/dist/coding/protected-path-wording.d.ts +24 -0
- package/dist/coding/protected-path-wording.js +55 -0
- package/dist/coding/protected-paths.d.ts +31 -0
- package/dist/coding/protected-paths.js +48 -0
- package/dist/coding/protocol.d.ts +75 -4
- package/dist/coding/protocol.js +107 -7
- package/dist/coding/registry/adapters.d.ts +2 -0
- package/dist/coding/registry/adapters.js +9 -0
- package/dist/coding/registry/allowlist.d.ts +9 -0
- package/dist/coding/registry/allowlist.js +28 -0
- package/dist/coding/registry/json-scan.d.ts +55 -0
- package/dist/coding/registry/json-scan.js +191 -0
- package/dist/coding/registry/lockfiles.d.ts +6 -0
- package/dist/coding/registry/lockfiles.js +89 -0
- package/dist/coding/registry/npm-lockfile.d.ts +9 -0
- package/dist/coding/registry/npm-lockfile.js +73 -0
- package/dist/coding/registry/npm-plan.d.ts +29 -0
- package/dist/coding/registry/npm-plan.js +289 -0
- package/dist/coding/registry/npm.d.ts +7 -0
- package/dist/coding/registry/npm.js +252 -0
- package/dist/coding/registry/pypi.d.ts +4 -0
- package/dist/coding/registry/pypi.js +210 -0
- package/dist/coding/registry/report.d.ts +37 -0
- package/dist/coding/registry/report.js +31 -0
- package/dist/coding/registry/token.d.ts +4 -0
- package/dist/coding/registry/token.js +7 -0
- package/dist/coding/registry/types.d.ts +286 -0
- package/dist/coding/registry/types.js +19 -0
- package/dist/coding/registry/worker-config.d.ts +14 -0
- package/dist/coding/registry/worker-config.js +31 -0
- package/dist/coding/services/builtins.d.ts +22 -0
- package/dist/coding/services/builtins.js +85 -0
- package/dist/coding/services/catalog.d.ts +443 -0
- package/dist/coding/services/catalog.js +159 -0
- package/dist/coding/services/declaration.d.ts +11 -0
- package/dist/coding/services/declaration.js +114 -0
- package/dist/coding/services/note.d.ts +8 -0
- package/dist/coding/services/note.js +15 -0
- package/dist/coding/services/resolve.d.ts +25 -0
- package/dist/coding/services/resolve.js +50 -0
- package/dist/coding/services/wording.d.ts +25 -0
- package/dist/coding/services/wording.js +70 -0
- package/dist/coding-proxy/main.js +24 -4
- package/dist/coding-worker/artifact.d.ts +6 -0
- package/dist/coding-worker/debug-trace.d.ts +37 -0
- package/dist/coding-worker/debug-trace.js +117 -0
- package/dist/coding-worker/driver.d.ts +13 -1
- package/dist/coding-worker/driver.js +102 -13
- package/dist/coding-worker/errors.js +8 -0
- package/dist/coding-worker/main.js +10 -2
- package/dist/coding-worker/sdk.d.ts +2 -2
- package/dist/coding-worker/sdk.js +7 -2
- package/dist/coding-worker/types.d.ts +3 -0
- package/dist/config/providers.d.ts +41 -0
- package/dist/config/providers.js +66 -0
- package/dist/core/budget-groups.d.ts +108 -5
- package/dist/core/budget-groups.js +125 -19
- package/dist/core/budget-wording.d.ts +22 -0
- package/dist/core/budget-wording.js +55 -0
- package/dist/core/datastores.js +18 -2
- package/dist/core/db.d.ts +5 -1
- package/dist/core/db.js +8 -2
- package/dist/core/dispatch.d.ts +33 -6
- package/dist/core/dispatch.js +260 -43
- package/dist/core/engine-native.js +27 -10
- package/dist/core/grants.d.ts +86 -0
- package/dist/core/grants.js +126 -0
- package/dist/core/host-events.d.ts +49 -0
- package/dist/core/host-events.js +257 -0
- package/dist/core/host-identity-links.d.ts +54 -0
- package/dist/core/host-identity-links.js +189 -0
- package/dist/core/host-status.d.ts +61 -0
- package/dist/core/host-status.js +211 -0
- package/dist/core/in-flight-runs.d.ts +8 -0
- package/dist/core/in-flight-runs.js +56 -0
- package/dist/core/provider-wording.d.ts +12 -0
- package/dist/core/provider-wording.js +44 -0
- package/dist/core/reconciler.d.ts +38 -2
- package/dist/core/reconciler.js +86 -2
- package/dist/core/repo-access.d.ts +99 -0
- package/dist/core/repo-access.js +136 -0
- package/dist/core/review-host-checks.d.ts +11 -0
- package/dist/core/review-host-checks.js +41 -0
- package/dist/core/review-host-tools.d.ts +47 -0
- package/dist/core/review-host-tools.js +347 -0
- package/dist/core/run-heartbeat.d.ts +27 -0
- package/dist/core/run-heartbeat.js +54 -0
- package/dist/core/runner.d.ts +30 -8
- package/dist/core/runner.js +237 -40
- package/dist/core/secrets.d.ts +11 -2
- package/dist/core/secrets.js +25 -6
- package/dist/core/subagent-memory-tools.d.ts +1 -1
- package/dist/core/subagent-memory-tools.js +16 -3
- package/dist/core/tool-admin.d.ts +81 -0
- package/dist/core/tool-admin.js +129 -0
- package/dist/core/tool-names.d.ts +42 -0
- package/dist/core/tool-names.js +64 -0
- package/dist/core/untrusted-content.d.ts +32 -0
- package/dist/core/untrusted-content.js +72 -0
- package/dist/core/webhooks.d.ts +8 -1
- package/dist/core/webhooks.js +23 -2
- package/dist/generated/prisma/browser.d.ts +100 -0
- package/dist/generated/prisma/client.d.ts +100 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +30 -0
- package/dist/generated/prisma/enums.d.ts +6 -0
- package/dist/generated/prisma/enums.js +6 -1
- package/dist/generated/prisma/internal/class.d.ts +143 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +1746 -577
- package/dist/generated/prisma/internal/prismaNamespace.js +179 -6
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +186 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +179 -6
- package/dist/generated/prisma/models/Agent.d.ts +294 -1
- package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
- package/dist/generated/prisma/models/AgentRepository.js +1 -0
- package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
- package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
- package/dist/generated/prisma/models/CodingProxySession.d.ts +123 -2
- package/dist/generated/prisma/models/CodingRun.d.ts +1276 -95
- package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
- package/dist/generated/prisma/models/CodingService.js +1 -0
- package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
- package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
- package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
- package/dist/generated/prisma/models/HostIdentity.js +1 -0
- package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
- package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
- package/dist/generated/prisma/models/Principal.d.ts +455 -0
- package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
- package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
- package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
- package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
- package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
- package/dist/generated/prisma/models/RegistryFetch.js +1 -0
- package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
- package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
- package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
- package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
- package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
- package/dist/generated/prisma/models/ResourceGrant.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +389 -1
- package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
- package/dist/generated/prisma/models/RunHostCheck.js +1 -0
- package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
- package/dist/generated/prisma/models/RunHostStatus.js +1 -0
- package/dist/generated/prisma/models/Tool.d.ts +15 -3
- package/dist/generated/prisma/models.d.ts +13 -0
- package/dist/help/build.d.ts +1 -0
- package/dist/help/build.js +9 -0
- package/dist/help/catalog.d.ts +24 -0
- package/dist/help/catalog.js +160 -0
- package/dist/help/cli.d.ts +2 -0
- package/dist/help/cli.js +65 -0
- package/dist/help/runtime.d.ts +3 -0
- package/dist/help/runtime.js +44 -0
- package/dist/help/search.d.ts +9 -0
- package/dist/help/search.js +104 -0
- package/dist/help-index.json +660 -0
- package/dist/import/cli-args.js +3 -2
- package/dist/import/create.d.ts +5 -0
- package/dist/import/create.js +67 -13
- package/dist/import/index.js +19 -9
- package/dist/import/neutral-schema.d.ts +11 -11
- package/dist/mcp/auth/access.d.ts +70 -0
- package/dist/mcp/auth/access.js +58 -0
- package/dist/mcp/auth/grants-cli.d.ts +149 -0
- package/dist/mcp/auth/grants-cli.js +518 -0
- package/dist/mcp/auth/host-account-cli.d.ts +2 -0
- package/dist/mcp/auth/host-account-cli.js +47 -0
- package/dist/mcp/auth/ownership.d.ts +26 -52
- package/dist/mcp/auth/ownership.js +19 -14
- package/dist/mcp/auth/repo-authorization.d.ts +22 -0
- package/dist/mcp/auth/repo-authorization.js +51 -0
- package/dist/mcp/auth/resource-server.d.ts +31 -2
- package/dist/mcp/auth/resource-server.js +84 -3
- package/dist/mcp/auth/self-hosted/browser.js +2 -2
- package/dist/mcp/auth/self-hosted/cli.js +47 -13
- package/dist/mcp/auth/self-hosted/credentials.d.ts +20 -2
- package/dist/mcp/auth/self-hosted/credentials.js +62 -4
- package/dist/mcp/auth/self-hosted/session.d.ts +2 -1
- package/dist/mcp/context.d.ts +27 -1
- package/dist/mcp/errors.d.ts +25 -6
- package/dist/mcp/errors.js +98 -0
- package/dist/mcp/host-events/github-ingress.d.ts +36 -0
- package/dist/mcp/host-events/github-ingress.js +92 -0
- package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
- package/dist/mcp/host-events/github-user-callback.js +50 -0
- package/dist/mcp/index.d.ts +8 -1
- package/dist/mcp/index.js +106 -16
- package/dist/mcp/server.js +27 -9
- package/dist/mcp/tools/agents.js +446 -48
- package/dist/mcp/tools/budget-groups.js +3 -3
- package/dist/mcp/tools/datastore.js +22 -15
- package/dist/mcp/tools/grants.d.ts +2 -0
- package/dist/mcp/tools/grants.js +239 -0
- package/dist/mcp/tools/help.d.ts +5 -0
- package/dist/mcp/tools/help.js +67 -0
- package/dist/mcp/tools/host-accounts.d.ts +2 -0
- package/dist/mcp/tools/host-accounts.js +114 -0
- package/dist/mcp/tools/memory.d.ts +7 -1
- package/dist/mcp/tools/memory.js +5 -5
- package/dist/mcp/tools/repositories.d.ts +2 -0
- package/dist/mcp/tools/repositories.js +181 -0
- package/dist/mcp/tools/runs.d.ts +6 -0
- package/dist/mcp/tools/runs.js +60 -6
- package/dist/mcp/tools/scheduling.js +3 -6
- package/dist/mcp/tools/secrets.js +18 -6
- package/dist/mcp/tools/services.d.ts +2 -0
- package/dist/mcp/tools/services.js +222 -0
- package/dist/mcp/tools/subagents.js +52 -16
- package/dist/mcp/tools/tools.d.ts +41 -0
- package/dist/mcp/tools/tools.js +201 -40
- package/dist/mcp/tools/trigger.js +36 -6
- package/dist/mcp/tools/webhooks.js +15 -4
- package/dist/mcp/transport/streamable-http.d.ts +10 -0
- package/dist/mcp/transport/streamable-http.js +32 -2
- package/dist/providers/auth/delegating.d.ts +19 -0
- package/dist/providers/auth/delegating.js +71 -0
- package/dist/providers/auth/index.d.ts +2 -0
- package/dist/providers/auth/index.js +11 -0
- package/dist/providers/auth/self-hosted.d.ts +2 -1
- package/dist/providers/auth/self-hosted.js +16 -4
- package/dist/providers/auth/types.d.ts +6 -0
- package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
- package/dist/providers/coding-proxy/memory-ledger.js +21 -2
- package/dist/providers/coding-proxy/metering.d.ts +4 -0
- package/dist/providers/coding-proxy/metering.js +28 -2
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
- package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
- package/dist/providers/coding-proxy/proxy.d.ts +13 -0
- package/dist/providers/coding-proxy/proxy.js +479 -18
- package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
- package/dist/providers/coding-proxy/registry/audit.js +380 -0
- package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
- package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
- package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
- package/dist/providers/coding-proxy/registry/plan.js +304 -0
- package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
- package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
- package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
- package/dist/providers/coding-proxy/registry/service.js +1137 -0
- package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
- package/dist/providers/coding-proxy/registry/store.js +70 -0
- package/dist/providers/coding-proxy/runtime.d.ts +17 -0
- package/dist/providers/coding-proxy/runtime.js +63 -0
- package/dist/providers/coding-proxy/secure-fetch.js +0 -1
- package/dist/providers/coding-proxy/server.d.ts +11 -0
- package/dist/providers/coding-proxy/server.js +163 -0
- package/dist/providers/coding-proxy/types.d.ts +15 -0
- package/dist/providers/engine/types.d.ts +10 -0
- package/dist/providers/executor/build.d.ts +2 -2
- package/dist/providers/executor/composition.d.ts +3 -0
- package/dist/providers/executor/composition.js +19 -0
- package/dist/providers/executor/container.d.ts +105 -3
- package/dist/providers/executor/container.js +296 -20
- package/dist/providers/executor/dbos.d.ts +2 -3
- package/dist/providers/executor/in-process.d.ts +2 -3
- package/dist/providers/executor/routing.d.ts +7 -0
- package/dist/providers/executor/routing.js +13 -0
- package/dist/providers/executor/types.d.ts +22 -0
- package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
- package/dist/providers/jobs/claude-tool-setup.js +50 -0
- package/dist/providers/jobs/collect-prune.d.ts +7 -0
- package/dist/providers/jobs/collect-prune.js +27 -0
- package/dist/providers/jobs/docker-isolation.d.ts +48 -3
- package/dist/providers/jobs/docker-isolation.js +213 -22
- package/dist/providers/jobs/docker-services.d.ts +35 -0
- package/dist/providers/jobs/docker-services.js +191 -0
- package/dist/providers/jobs/docker.d.ts +49 -2
- package/dist/providers/jobs/docker.js +279 -32
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
- package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
- package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
- package/dist/providers/jobs/kubernetes-isolation.js +238 -57
- package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
- package/dist/providers/jobs/kubernetes-platform.js +11 -5
- package/dist/providers/jobs/kubernetes-preflight.js +3 -0
- package/dist/providers/jobs/kubernetes.d.ts +21 -2
- package/dist/providers/jobs/kubernetes.js +112 -21
- package/dist/providers/jobs/types.d.ts +18 -0
- package/dist/providers/llm/anthropic.js +6 -2
- package/dist/providers/llm/bedrock.js +2 -1
- package/dist/providers/llm/claude-messages.d.ts +5 -1
- package/dist/providers/llm/claude-messages.js +1 -0
- package/dist/providers/llm/claude-provider.d.ts +3 -1
- package/dist/providers/llm/claude-provider.js +5 -1
- package/dist/providers/llm/index.d.ts +1 -1
- package/dist/providers/llm/index.js +1 -1
- package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
- package/dist/providers/llm/pricing-anthropic.js +32 -14
- package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
- package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
- package/dist/providers/llm/routing.d.ts +10 -1
- package/dist/providers/llm/routing.js +18 -0
- package/dist/providers/llm/types.d.ts +12 -0
- package/dist/providers/llm/types.js +8 -1
- package/dist/providers/review-host/diff-lines.d.ts +16 -0
- package/dist/providers/review-host/diff-lines.js +59 -0
- package/dist/providers/review-host/github-events.d.ts +6 -0
- package/dist/providers/review-host/github-events.js +180 -0
- package/dist/providers/review-host/github-user-auth.d.ts +38 -0
- package/dist/providers/review-host/github-user-auth.js +128 -0
- package/dist/providers/review-host/github.d.ts +57 -0
- package/dist/providers/review-host/github.js +567 -0
- package/dist/providers/review-host/index.d.ts +12 -0
- package/dist/providers/review-host/index.js +30 -0
- package/dist/providers/review-host/review-format.d.ts +19 -0
- package/dist/providers/review-host/review-format.js +59 -0
- package/dist/providers/review-host/types.d.ts +284 -0
- package/dist/providers/review-host/types.js +24 -0
- package/dist/providers/vcs/git.d.ts +16 -6
- package/dist/providers/vcs/git.js +77 -13
- package/dist/providers/vcs/github.d.ts +74 -3
- package/dist/providers/vcs/github.js +195 -15
- package/dist/providers/vcs/types.d.ts +42 -4
- package/dist/quickstart/index.js +13 -2
- package/dist/sandbox/fetch-policy.d.ts +20 -2
- package/dist/sandbox/fetch-policy.js +64 -3
- package/dist/sandbox/host-functions.d.ts +9 -1
- package/dist/sandbox/host-functions.js +12 -5
- package/dist/serve.js +1 -1
- package/dist/wardby-bin.js +6 -0
- package/docs/README.md +30 -0
- package/docs/architecture-runtime.md +90 -0
- package/docs/assets/brand/wardby-icon-512.png +0 -0
- package/docs/assets/brand/wardby-icon.svg +16 -0
- package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
- package/docs/assets/brand/wardby-mascot.png +0 -0
- package/docs/assets/brand/wardby-mascot.svg +5 -0
- package/docs/assets/wardby-workflow.svg +106 -0
- package/docs/code-review-agents.md +481 -0
- package/docs/coding-agent-setup.md +172 -0
- package/docs/coding-packages.md +455 -0
- package/docs/coding-services.md +300 -0
- package/docs/coding-worker-byo-images.md +98 -0
- package/docs/coding-worker-isolation.md +1061 -0
- package/docs/getting-started-gke.md +615 -0
- package/docs/getting-started-identity-provider.md +308 -0
- package/docs/getting-started.md +133 -0
- package/docs/observability.md +53 -0
- package/docs/release-verification.md +66 -0
- package/docs/security-deployment.md +657 -0
- package/help/code-review-agents.md +31 -0
- package/help/coding-packages.md +31 -0
- package/help/coding-services.md +71 -0
- package/help/creating-agents.md +68 -0
- package/help/deploy-gke.md +39 -0
- package/help/deployment-targets.md +39 -0
- package/help/errors/budget-group-exhausted.md +25 -0
- package/help/errors/docker-isolation-unsupported.md +26 -0
- package/help/errors/protected-path.md +45 -0
- package/help/errors/repo-access.md +27 -0
- package/help/errors/service-declaration-invalid.md +27 -0
- package/help/errors/service-declaration-unavailable.md +25 -0
- package/help/errors/service-launcher-unsupported.md +30 -0
- package/help/errors/service-not-allowed.md +27 -0
- package/help/errors/service-unknown.md +23 -0
- package/help/errors/service-unready.md +49 -0
- package/help/getting-started.md +31 -0
- package/help/github.md +30 -0
- package/help/identity-and-access.md +39 -0
- package/help/mcp.md +30 -0
- package/help/native-capabilities.md +30 -0
- package/help/observability.md +41 -0
- package/help/operating-agents.md +30 -0
- package/help/security.md +27 -0
- package/help/troubleshooting/budgets.md +26 -0
- package/help/troubleshooting/coding-workers.md +47 -0
- package/help/troubleshooting/repository-access.md +27 -0
- package/package.json +12 -3
- package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
- package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
- package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
- package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
- package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
- package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
- package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
- package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
- package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
- package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
- package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
- package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
- package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
- package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
- package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
- package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
- package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
- package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
- package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
- package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
- package/prisma/schema.prisma +407 -21
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { mkdir, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join, resolve, sep } from "node:path";
|
|
4
|
+
import { normalizeCollectExclusions } from "../../coding/collect-exclude.js";
|
|
4
5
|
import { CodingProfileSchema } from "../../coding/profile.js";
|
|
6
|
+
import { parseStoredServices, storedServiceNames, workerServices } from "../../coding/services/catalog.js";
|
|
7
|
+
import { MAX_SERVICE_DECLARATION_BYTES, SERVICE_DECLARATION_PATH } from "../../coding/services/declaration.js";
|
|
8
|
+
import { SERVICE_UNREADY_CATEGORY, SERVICE_UNREADY_ERROR, serviceUnreadyName, serviceUnreadySentence, } from "../../coding/services/wording.js";
|
|
9
|
+
import { PROTECTED_PATH_CATEGORY, protectedPathFromError, protectedPathSentence, } from "../../coding/protected-path-wording.js";
|
|
10
|
+
import { budgetSentence } from "../../core/budget-wording.js";
|
|
11
|
+
import { classifyProviderFailure, providerClassOfCategory, providerSentence, } from "../../core/provider-wording.js";
|
|
5
12
|
import { logger } from "../../core/logger.js";
|
|
13
|
+
import { requiredLevel } from "../../core/repo-access.js";
|
|
6
14
|
import { ensurePrivateDirectory } from "../../core/private-directory.js";
|
|
7
15
|
import { codingRunObserver } from "../../coding/observability.js";
|
|
8
16
|
import { CODING_PROTOCOL_VERSION, CodingRunResultSchema, CodingTaskInputSchema, parseCodingAgentOutputJson, redactAndTruncate, } from "../../coding/protocol.js";
|
|
9
17
|
import { getModelPricing } from "../llm/pricing.js";
|
|
10
18
|
import { getAnthropicPricing } from "../llm/pricing-anthropic.js";
|
|
11
|
-
import { isImmutableDockerImage } from "../jobs/docker-isolation.js";
|
|
19
|
+
import { CODING_PROXY_ALIAS, CODING_PROXY_PORT, isImmutableDockerImage } from "../jobs/docker-isolation.js";
|
|
20
|
+
import { normalizeRegistryLockfiles } from "../../coding/registry/lockfiles.js";
|
|
12
21
|
import { HEARTBEAT_TIMEOUT_MS } from "../../core/timing.js";
|
|
13
22
|
const containerLog = logger.child({ module: "container-executor" });
|
|
14
23
|
const TERMINAL_STATUSES = new Set(["succeeded", "failed", "refused", "lost", "budget_exhausted", "cancelled"]);
|
|
@@ -33,6 +42,19 @@ function proxyProtocol(provider) {
|
|
|
33
42
|
return "anthropic-messages";
|
|
34
43
|
throw new Error("coding_provider_unsupported");
|
|
35
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Claude Code's commands run in its tool runner, which never reports back through the agent, so the
|
|
47
|
+
* host rewrites registry-proxy download URLs in collected lockfiles itself (the Codex driver does it
|
|
48
|
+
* in the worker). Best effort and symlink-safe (lockfiles.ts); a Codex workspace is left alone.
|
|
49
|
+
*/
|
|
50
|
+
export async function normalizeCollectedLockfiles(provider, workspacePath) {
|
|
51
|
+
if (provider !== "claude-code")
|
|
52
|
+
return [];
|
|
53
|
+
return normalizeRegistryLockfiles({
|
|
54
|
+
workspace: workspacePath,
|
|
55
|
+
proxyBaseUrl: `http://${CODING_PROXY_ALIAS}:${CODING_PROXY_PORT}`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
36
58
|
export class PrismaContainerExecutionStore {
|
|
37
59
|
db;
|
|
38
60
|
options;
|
|
@@ -43,7 +65,10 @@ export class PrismaContainerExecutionStore {
|
|
|
43
65
|
async load(runId) {
|
|
44
66
|
const row = await this.db.run.findUnique({
|
|
45
67
|
where: { id: runId },
|
|
46
|
-
include: {
|
|
68
|
+
include: {
|
|
69
|
+
agent: { include: { codingProfile: true, budgetGroup: { select: { name: true } } } },
|
|
70
|
+
codingRun: { include: { proxySession: true } },
|
|
71
|
+
},
|
|
47
72
|
});
|
|
48
73
|
if (!row?.codingRun)
|
|
49
74
|
return null;
|
|
@@ -60,10 +85,12 @@ export class PrismaContainerExecutionStore {
|
|
|
60
85
|
provider: row.codingRun.provider,
|
|
61
86
|
model: row.codingRun.model,
|
|
62
87
|
timeoutSec: row.codingRun.timeoutSec,
|
|
63
|
-
allowedEgress: row.codingRun.allowedEgress,
|
|
64
88
|
protectedPaths: row.codingRun.protectedPaths,
|
|
89
|
+
collectExclude: row.codingRun.collectExclude,
|
|
65
90
|
rootCodingRunId: row.codingRun.rootCodingRunId,
|
|
66
91
|
budgetUsd: Number(row.codingRun.budgetReservedUsd),
|
|
92
|
+
agentBudgetUsd: Number(row.agent.budgetUsd),
|
|
93
|
+
budgetGroupName: row.agent.budgetGroup?.name ?? null,
|
|
67
94
|
tokensIn: row.tokensIn,
|
|
68
95
|
tokensOut: row.tokensOut,
|
|
69
96
|
costUsd: Number(row.costUsd),
|
|
@@ -74,7 +101,13 @@ export class PrismaContainerExecutionStore {
|
|
|
74
101
|
proxySessionId: row.codingRun.proxySession?.id ?? null,
|
|
75
102
|
result: row.codingRun.result,
|
|
76
103
|
workerImage: row.codingRun.workerImage,
|
|
104
|
+
toolImage: row.codingRun.toolImage,
|
|
77
105
|
workspaceDiskMb: row.codingRun.workspaceDiskMb,
|
|
106
|
+
debugTrace: row.codingRun.debugTrace,
|
|
107
|
+
services: row.codingRun.services,
|
|
108
|
+
failureCategory: row.codingRun.failureCategory,
|
|
109
|
+
profileRepository: row.agent.codingProfile?.repository ?? null,
|
|
110
|
+
repositoryAuthorizedVia: row.agent.codingProfile?.repositoryAuthorizedVia ?? null,
|
|
78
111
|
};
|
|
79
112
|
}
|
|
80
113
|
async claimProvisioning(runId, claimId) {
|
|
@@ -247,6 +280,9 @@ export class RunCapabilityVault {
|
|
|
247
280
|
}
|
|
248
281
|
class PreflightError extends Error {
|
|
249
282
|
}
|
|
283
|
+
/** A refusal by repository authorization; its message is the failure category's prefix (repo_access_*). */
|
|
284
|
+
class RepoAccessError extends PreflightError {
|
|
285
|
+
}
|
|
250
286
|
export class ContainerExecutor {
|
|
251
287
|
options;
|
|
252
288
|
artifactRoot;
|
|
@@ -273,6 +309,12 @@ export class ContainerExecutor {
|
|
|
273
309
|
if (image !== undefined && !isImmutableDockerImage(image))
|
|
274
310
|
throw new Error("coding_worker_image_invalid");
|
|
275
311
|
}
|
|
312
|
+
for (const versions of Object.values(options.claudeToolRunnerImages ?? {})) {
|
|
313
|
+
for (const image of Object.values(versions)) {
|
|
314
|
+
if (!isImmutableDockerImage(image))
|
|
315
|
+
throw new Error("coding_worker_image_invalid");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
276
318
|
if (!/^[A-Za-z][A-Za-z0-9_.:-]{0,199}$/.test(options.credentialRef)) {
|
|
277
319
|
throw new Error("coding_credential_ref_invalid");
|
|
278
320
|
}
|
|
@@ -397,6 +439,9 @@ export class ContainerExecutor {
|
|
|
397
439
|
if ((await this.options.store.claimProvisioning(runId, claimId)) !== "claimed")
|
|
398
440
|
return;
|
|
399
441
|
}
|
|
442
|
+
// A launched run (recovered after a restart) is in flight: a transient
|
|
443
|
+
// host error is retried once before it is stopped.
|
|
444
|
+
await this.authorizeRepository(run, { inFlight: Boolean(handle) });
|
|
400
445
|
try {
|
|
401
446
|
workspace = await this.options.vcs.recoverWorkspace(prepared);
|
|
402
447
|
if (!workspace && handle)
|
|
@@ -474,8 +519,13 @@ export class ContainerExecutor {
|
|
|
474
519
|
await this.options.sessions.cancelSession(sessionId).catch(() => undefined);
|
|
475
520
|
if (handle)
|
|
476
521
|
await this.options.jobs.stop(handle, "executor_failure").catch(() => undefined);
|
|
477
|
-
const
|
|
478
|
-
const
|
|
522
|
+
const outOfBudget = spendEnabled && (await this.outOfBudget(sessionId));
|
|
523
|
+
const status = outOfBudget
|
|
524
|
+
? "budget_exhausted"
|
|
525
|
+
: !spendEnabled && error instanceof PreflightError
|
|
526
|
+
? "refused"
|
|
527
|
+
: "failed";
|
|
528
|
+
const failure = outOfBudget ? this.budgetFailure(error) : this.failure(error);
|
|
479
529
|
this.emit({
|
|
480
530
|
stage: "stopping",
|
|
481
531
|
runId,
|
|
@@ -487,9 +537,17 @@ export class ContainerExecutor {
|
|
|
487
537
|
this.terminal(run, status, failure.audit);
|
|
488
538
|
if (handle)
|
|
489
539
|
await this.options.jobs.remove(handle).catch(() => undefined);
|
|
540
|
+
// A service sidecar that never became ready (kubernetes.ts waitForKeeper) is named on the host.
|
|
541
|
+
const unreadyService = outOfBudget ? null : serviceUnreadyName(error);
|
|
542
|
+
const protectedPath = outOfBudget ? null : protectedPathFromError(error);
|
|
490
543
|
if (workspace) {
|
|
491
544
|
await this.options.vcs.cleanup(workspace).catch(() => undefined);
|
|
492
|
-
await this.options.vcs.notifyContinuationFinished?.(workspace, "failed", {
|
|
545
|
+
await this.options.vcs.notifyContinuationFinished?.(workspace, outOfBudget ? "budget_exhausted" : "failed", {
|
|
546
|
+
agentName: run.agentName,
|
|
547
|
+
...(outOfBudget ? { budgetSentence: this.budgetSentence(run) } : {}),
|
|
548
|
+
...(unreadyService ? { serviceSentence: serviceUnreadySentence([unreadyService]) } : {}),
|
|
549
|
+
...(protectedPath ? { protectedPathSentence: protectedPathSentence(protectedPath) } : {}),
|
|
550
|
+
});
|
|
493
551
|
}
|
|
494
552
|
this.emit({ stage: "cleanup", runId, jobId: handle?.id, cleanupSucceeded: true });
|
|
495
553
|
}
|
|
@@ -531,6 +589,26 @@ export class ContainerExecutor {
|
|
|
531
589
|
clearInterval(timer);
|
|
532
590
|
}
|
|
533
591
|
}
|
|
592
|
+
/**
|
|
593
|
+
* The run may use its repository only if the agent's current owner still
|
|
594
|
+
* has write on it, or the profile's approval (admin or grandfathered)
|
|
595
|
+
* covers exactly this repository. A profile changed since dispatch no
|
|
596
|
+
* longer covers the run's repository, so that falls back to the owner's
|
|
597
|
+
* access. Throws RepoAccessError (refused before anything is cloned).
|
|
598
|
+
*/
|
|
599
|
+
async authorizeRepository(run, opts) {
|
|
600
|
+
const sameRepository = run.profileRepository !== null && run.profileRepository === run.repository;
|
|
601
|
+
const decision = await this.options.repoAccess.authorizeUse({
|
|
602
|
+
ownerId: run.ownerId,
|
|
603
|
+
provider: "github",
|
|
604
|
+
repository: run.repository,
|
|
605
|
+
required: requiredLevel("coding"),
|
|
606
|
+
authorizedVia: sameRepository ? run.repositoryAuthorizedVia : "host_permission",
|
|
607
|
+
retryTransient: opts.inFlight,
|
|
608
|
+
});
|
|
609
|
+
if (!decision.ok)
|
|
610
|
+
throw new RepoAccessError(`repo_access_${decision.reason}`);
|
|
611
|
+
}
|
|
534
612
|
preflight(run) {
|
|
535
613
|
try {
|
|
536
614
|
if (run.status !== "pending" && run.status !== "running")
|
|
@@ -552,7 +630,6 @@ export class ContainerExecutor {
|
|
|
552
630
|
baseRef: run.baseRef,
|
|
553
631
|
defaultTask: null,
|
|
554
632
|
timeoutSec: run.timeoutSec,
|
|
555
|
-
allowedEgress: run.allowedEgress,
|
|
556
633
|
protectedPaths: run.protectedPaths,
|
|
557
634
|
});
|
|
558
635
|
const expectedHeadRunId = run.rootCodingRunId ?? run.runId;
|
|
@@ -577,6 +654,7 @@ export class ContainerExecutor {
|
|
|
577
654
|
baseRef: profile.baseRef,
|
|
578
655
|
headRef: run.headRef,
|
|
579
656
|
protectedPaths: profile.protectedPaths,
|
|
657
|
+
collectExclude: [...normalizeCollectExclusions(run.collectExclude).paths],
|
|
580
658
|
continuation: run.rootCodingRunId ? { rootRunId: run.rootCodingRunId } : undefined,
|
|
581
659
|
};
|
|
582
660
|
}
|
|
@@ -589,16 +667,34 @@ export class ContainerExecutor {
|
|
|
589
667
|
if (sessionId)
|
|
590
668
|
await this.options.sessions.cancelSession(sessionId).catch(() => undefined);
|
|
591
669
|
// For notifyContinuationFinished in the `finally` below -- defaults to
|
|
592
|
-
// "failed" and is only flipped right before
|
|
670
|
+
// "failed" and is only flipped right before a success or out-of-budget return.
|
|
593
671
|
let outcome = "failed";
|
|
594
672
|
let finishedSummary;
|
|
673
|
+
// Set when a failed job's session relayed a model-provider failure; names it on the host.
|
|
674
|
+
// Only a failed job: a transient provider error the worker retried past did not end the run.
|
|
675
|
+
let providerClass;
|
|
676
|
+
// Set when finalizeChanges refused the collected diff for touching a protected path; names it on the host.
|
|
677
|
+
let protectedPath;
|
|
595
678
|
try {
|
|
596
679
|
if (jobState !== "succeeded") {
|
|
597
680
|
const collected = await this.options.jobs.collect(handle).catch(() => null);
|
|
598
681
|
const reason = collected?.diagnostic ?? collected?.reason ?? jobState;
|
|
599
682
|
this.emit({ stage: "collected", runId: run.runId, jobId: handle.id });
|
|
683
|
+
// The worker usually fails outright when the proxy refuses a model
|
|
684
|
+
// request for budget; the session records that refusal.
|
|
685
|
+
if (await this.outOfBudget(sessionId)) {
|
|
686
|
+
const failure = this.budgetFailure(`job_${reason}`, { issues: collected?.diagnosticIssues });
|
|
687
|
+
await this.options.store.terminate(run.runId, "budget_exhausted", failure.error, failure.audit);
|
|
688
|
+
this.terminal(run, "budget_exhausted", failure.audit);
|
|
689
|
+
outcome = "budget_exhausted";
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
600
692
|
const status = jobState === "lost" ? "lost" : "failed";
|
|
601
|
-
const
|
|
693
|
+
const provider = status === "failed" ? await this.providerFailure(sessionId) : null;
|
|
694
|
+
const failure = provider
|
|
695
|
+
? this.providerFailureOf(run, provider, `job_${reason}`, { issues: collected?.diagnosticIssues })
|
|
696
|
+
: this.failure(`job_${reason}`, { issues: collected?.diagnosticIssues });
|
|
697
|
+
providerClass = provider?.class;
|
|
602
698
|
await this.options.store.terminate(run.runId, status, failure.error, failure.audit);
|
|
603
699
|
this.terminal(run, status, failure.audit);
|
|
604
700
|
return;
|
|
@@ -615,6 +711,7 @@ export class ContainerExecutor {
|
|
|
615
711
|
this.emit({ stage: "budget_cutoff", runId: run.runId, jobId: handle.id });
|
|
616
712
|
await this.options.store.complete(run.runId, "budget_exhausted", this.resultFor(output, current, "budget_exhausted"));
|
|
617
713
|
this.terminal(current, "budget_exhausted");
|
|
714
|
+
outcome = "budget_exhausted";
|
|
618
715
|
return;
|
|
619
716
|
}
|
|
620
717
|
if (output.outcome === "no_changes") {
|
|
@@ -629,17 +726,24 @@ export class ContainerExecutor {
|
|
|
629
726
|
if (!workspace)
|
|
630
727
|
throw new Error("coding_workspace_lost");
|
|
631
728
|
await this.options.jobs.materializeWorkspace(handle, workspace.workspacePath);
|
|
729
|
+
await normalizeCollectedLockfiles(current.provider, workspace.workspacePath);
|
|
632
730
|
current = await this.requireCurrent(run.runId);
|
|
633
731
|
if (current.costUsd > current.budgetUsd) {
|
|
634
732
|
this.emit({ stage: "budget_cutoff", runId: run.runId, jobId: handle.id });
|
|
635
733
|
await this.options.store.complete(run.runId, "budget_exhausted", this.resultFor(output, current, "budget_exhausted"));
|
|
636
734
|
this.terminal(current, "budget_exhausted");
|
|
735
|
+
outcome = "budget_exhausted";
|
|
637
736
|
return;
|
|
638
737
|
}
|
|
738
|
+
// One more (usually cached) check right before anything is pushed: access
|
|
739
|
+
// revoked while the run was working stops the push.
|
|
740
|
+
await this.authorizeRepository(current, { inFlight: true });
|
|
741
|
+
const report = await this.loadRegistryReport(run.runId);
|
|
639
742
|
const finalized = await this.options.vcs.finalizeChanges(workspace, {
|
|
640
743
|
summary: output.summary,
|
|
641
744
|
tests: output.tests,
|
|
642
745
|
tag: output.tag,
|
|
746
|
+
...report,
|
|
643
747
|
});
|
|
644
748
|
const result = this.resultFor(output, current, finalized.outcome, finalized);
|
|
645
749
|
await this.options.store.complete(run.runId, "succeeded", result);
|
|
@@ -651,9 +755,18 @@ export class ContainerExecutor {
|
|
|
651
755
|
finishedSummary = output.summary;
|
|
652
756
|
}
|
|
653
757
|
catch (error) {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
758
|
+
if (await this.outOfBudget(sessionId)) {
|
|
759
|
+
const failure = this.budgetFailure(error);
|
|
760
|
+
await this.options.store.terminate(run.runId, "budget_exhausted", failure.error, failure.audit);
|
|
761
|
+
this.terminal(run, "budget_exhausted", failure.audit);
|
|
762
|
+
outcome = "budget_exhausted";
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
const failure = this.failure(error);
|
|
766
|
+
await this.options.store.terminate(run.runId, "failed", failure.error, failure.audit);
|
|
767
|
+
this.terminal(run, "failed", failure.audit);
|
|
768
|
+
protectedPath = protectedPathFromError(error) ?? undefined;
|
|
769
|
+
}
|
|
657
770
|
}
|
|
658
771
|
finally {
|
|
659
772
|
await this.options.jobs.remove(handle).catch(() => undefined);
|
|
@@ -664,12 +777,34 @@ export class ContainerExecutor {
|
|
|
664
777
|
await this.options.vcs.notifyContinuationFinished?.(workspace, outcome, {
|
|
665
778
|
summary: finishedSummary,
|
|
666
779
|
agentName: run.agentName,
|
|
780
|
+
...(outcome === "budget_exhausted" ? { budgetSentence: this.budgetSentence(run) } : {}),
|
|
781
|
+
...(outcome === "failed" && providerClass ? { providerSentence: providerSentence(providerClass) } : {}),
|
|
782
|
+
...(outcome === "failed" && protectedPath
|
|
783
|
+
? { protectedPathSentence: protectedPathSentence(protectedPath) }
|
|
784
|
+
: {}),
|
|
667
785
|
});
|
|
668
786
|
}
|
|
669
787
|
await rm(this.artifactPath(run.runId), { recursive: true, force: true }).catch(() => undefined);
|
|
670
788
|
this.emit({ stage: "cleanup", runId: run.runId, jobId: handle.id, cleanupSucceeded: true });
|
|
671
789
|
}
|
|
672
790
|
}
|
|
791
|
+
/** The package report is informational: any failure loading it (a
|
|
792
|
+
* rejection, a synchronous throw, or a malformed result) yields no
|
|
793
|
+
* package section rather than failing finalization. */
|
|
794
|
+
async loadRegistryReport(runId) {
|
|
795
|
+
try {
|
|
796
|
+
const report = await this.options.registryReport?.(runId);
|
|
797
|
+
const packages = Array.isArray(report?.packages) ? report.packages : [];
|
|
798
|
+
const packageRefusals = Array.isArray(report?.packageRefusals) ? report.packageRefusals : [];
|
|
799
|
+
return {
|
|
800
|
+
...(packages.length > 0 ? { packages } : {}),
|
|
801
|
+
...(packageRefusals.length > 0 ? { packageRefusals } : {}),
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
catch {
|
|
805
|
+
return {};
|
|
806
|
+
}
|
|
807
|
+
}
|
|
673
808
|
resultFor(output, run, forcedOutcome, finalized) {
|
|
674
809
|
const outcome = forcedOutcome ?? (output.outcome === "budget_exhausted" ? "budget_exhausted" : "no_changes");
|
|
675
810
|
return CodingRunResultSchema.parse({
|
|
@@ -694,15 +829,12 @@ export class ContainerExecutor {
|
|
|
694
829
|
}
|
|
695
830
|
resolveCodingWorkerImage(selector) {
|
|
696
831
|
if (selector.provider === "claude-code") {
|
|
697
|
-
|
|
698
|
-
throw new Error("coding_toolchain_unsupported:claude-code");
|
|
699
|
-
}
|
|
832
|
+
this.claudeToolImage(selector);
|
|
700
833
|
const image = selector.workerImageRef ?? this.options.claudeWorkerImage;
|
|
701
|
-
if (!image
|
|
834
|
+
if (!image)
|
|
702
835
|
throw new Error("coding_provider_not_configured:claude-code");
|
|
703
|
-
if (!isImmutableDockerImage(image)
|
|
836
|
+
if (!isImmutableDockerImage(image))
|
|
704
837
|
throw new Error("coding_worker_image_invalid");
|
|
705
|
-
}
|
|
706
838
|
return image;
|
|
707
839
|
}
|
|
708
840
|
if (selector.provider !== "codex") {
|
|
@@ -722,6 +854,40 @@ export class ContainerExecutor {
|
|
|
722
854
|
}
|
|
723
855
|
return image;
|
|
724
856
|
}
|
|
857
|
+
/** Claude's tool-runner image for the agent's toolchain, at dispatch (Executor.resolveCodingToolImage); null for Codex. */
|
|
858
|
+
resolveCodingToolImage(selector) {
|
|
859
|
+
return selector.provider === "claude-code" ? this.claudeToolImage(selector) : null;
|
|
860
|
+
}
|
|
861
|
+
/** The tool runner is what runs a Claude agent's commands, so it is what the toolchain selects. */
|
|
862
|
+
claudeToolImage(selector) {
|
|
863
|
+
if (!this.options.claudeToolRunnerImage)
|
|
864
|
+
throw new Error("coding_provider_not_configured:claude-code");
|
|
865
|
+
const image = selector.toolchain === "node" && selector.toolchainVersion === null
|
|
866
|
+
? this.options.claudeToolRunnerImage
|
|
867
|
+
: selector.toolchainVersion !== null
|
|
868
|
+
? this.options.claudeToolRunnerImages?.[selector.toolchain]?.[selector.toolchainVersion]
|
|
869
|
+
: undefined;
|
|
870
|
+
if (!image)
|
|
871
|
+
throw new Error("coding_toolchain_unsupported:claude-code");
|
|
872
|
+
if (!isImmutableDockerImage(image))
|
|
873
|
+
throw new Error("coding_worker_image_invalid");
|
|
874
|
+
return image;
|
|
875
|
+
}
|
|
876
|
+
/** Coding-run services: the repository's declaration at its base ref (Executor.readCodingServiceDeclaration). */
|
|
877
|
+
async readCodingServiceDeclaration(input) {
|
|
878
|
+
if (!this.options.vcs.readRepositoryFile)
|
|
879
|
+
return null;
|
|
880
|
+
return this.options.vcs.readRepositoryFile({
|
|
881
|
+
repository: input.repository,
|
|
882
|
+
ref: input.baseRef,
|
|
883
|
+
path: SERVICE_DECLARATION_PATH,
|
|
884
|
+
maxBytes: MAX_SERVICE_DECLARATION_BYTES,
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
/** The job launcher decides (Kubernetes and Docker start services for both providers). */
|
|
888
|
+
supportsCodingServices(provider) {
|
|
889
|
+
return this.options.jobs.supportsServicesFor?.(provider) === true;
|
|
890
|
+
}
|
|
725
891
|
async requireCurrent(runId) {
|
|
726
892
|
const current = await this.options.store.load(runId);
|
|
727
893
|
if (!current)
|
|
@@ -736,16 +902,24 @@ export class ContainerExecutor {
|
|
|
736
902
|
if (run.workspaceDiskMb && run.workspaceDiskMb > this.options.maxDiskMb) {
|
|
737
903
|
throw new Error("coding_workspace_disk_exceeds_limit");
|
|
738
904
|
}
|
|
905
|
+
const services = parseStoredServices(run.services);
|
|
906
|
+
// Dispatch refuses services this deployment can't start; this is the backstop.
|
|
907
|
+
if (services.length > 0 && !this.supportsCodingServices(provider)) {
|
|
908
|
+
throw new Error("coding_services_unsupported_launcher");
|
|
909
|
+
}
|
|
739
910
|
return {
|
|
740
911
|
kind: "coding-agent",
|
|
741
912
|
runId: run.runId,
|
|
742
913
|
provider,
|
|
743
914
|
image: run.workerImage ?? this.options.workerImage,
|
|
744
|
-
|
|
915
|
+
// A run keeps the tool image it was dispatched with; rows from before CodingRun.toolImage use the default.
|
|
916
|
+
...(provider === "claude-code" ? { toolImage: run.toolImage ?? this.options.claudeToolRunnerImage } : {}),
|
|
745
917
|
inputArtifact,
|
|
746
918
|
timeoutSec: run.timeoutSec,
|
|
747
919
|
limits: { ...this.options.limits, ...(run.workspaceDiskMb ? { diskMb: run.workspaceDiskMb } : {}) },
|
|
748
920
|
labels: {},
|
|
921
|
+
collectExclude: normalizeCollectExclusions(run.collectExclude),
|
|
922
|
+
...(services.length > 0 ? { services } : {}),
|
|
749
923
|
};
|
|
750
924
|
}
|
|
751
925
|
credentialRef(provider) {
|
|
@@ -766,6 +940,7 @@ export class ContainerExecutor {
|
|
|
766
940
|
throw new Error("coding_artifact_path_invalid");
|
|
767
941
|
const destination = join(directory, "input.json");
|
|
768
942
|
const temporary = join(directory, `.input-${randomUUID()}.tmp`);
|
|
943
|
+
const services = parseStoredServices(run.services);
|
|
769
944
|
const input = CodingTaskInputSchema.parse({
|
|
770
945
|
schemaVersion: CODING_PROTOCOL_VERSION,
|
|
771
946
|
runId: run.runId,
|
|
@@ -777,6 +952,10 @@ export class ContainerExecutor {
|
|
|
777
952
|
budgetUsd: run.budgetUsd,
|
|
778
953
|
deadlineAt: deadlineAt.toISOString(),
|
|
779
954
|
continuationOf: run.rootCodingRunId ? { runId: run.rootCodingRunId } : undefined,
|
|
955
|
+
// Only when true: an untraced run's input stays exactly what older workers expect.
|
|
956
|
+
...(run.debugTrace ? { debugTrace: true } : {}),
|
|
957
|
+
// Only when there are some: every other run's input stays exactly what older workers expect.
|
|
958
|
+
...(services.length > 0 ? { services: workerServices(services) } : {}),
|
|
780
959
|
});
|
|
781
960
|
// The run directory is 0700; world-readable mode only crosses Docker's UID boundary.
|
|
782
961
|
await writeFile(temporary, JSON.stringify(input), { flag: "wx", mode: 0o444 });
|
|
@@ -789,12 +968,20 @@ export class ContainerExecutor {
|
|
|
789
968
|
preflightForCleanup(run) {
|
|
790
969
|
if (!Array.isArray(run.protectedPaths) || !run.protectedPaths.every((path) => typeof path === "string"))
|
|
791
970
|
return null;
|
|
971
|
+
let collectExclude;
|
|
972
|
+
try {
|
|
973
|
+
collectExclude = [...normalizeCollectExclusions(run.collectExclude).paths];
|
|
974
|
+
}
|
|
975
|
+
catch {
|
|
976
|
+
return null;
|
|
977
|
+
}
|
|
792
978
|
return {
|
|
793
979
|
runId: run.runId,
|
|
794
980
|
repository: run.repository,
|
|
795
981
|
baseRef: run.baseRef,
|
|
796
982
|
headRef: run.headRef,
|
|
797
983
|
protectedPaths: run.protectedPaths,
|
|
984
|
+
collectExclude,
|
|
798
985
|
continuation: run.rootCodingRunId ? { rootRunId: run.rootCodingRunId } : undefined,
|
|
799
986
|
};
|
|
800
987
|
}
|
|
@@ -838,11 +1025,71 @@ export class ContainerExecutor {
|
|
|
838
1025
|
// notification actually firing) -- safe to call again because
|
|
839
1026
|
// notifyContinuationFinished is find-and-update-or-no-op, never
|
|
840
1027
|
// find-or-create.
|
|
841
|
-
|
|
1028
|
+
const outcome = run.status === "succeeded" ? "succeeded" : run.status === "budget_exhausted" ? "budget_exhausted" : "failed";
|
|
1029
|
+
const providerClass = run.status === "failed" ? providerClassOfCategory(run.failureCategory) : null;
|
|
1030
|
+
const unreadyServices = run.status === "failed" && run.failureCategory === SERVICE_UNREADY_CATEGORY
|
|
1031
|
+
? storedServiceNames(run.services)
|
|
1032
|
+
: null;
|
|
1033
|
+
// No path survives to the stored run (only the category does -- see
|
|
1034
|
+
// protected-path-wording.ts), so this can only ever use the pathless sentence.
|
|
1035
|
+
const protectedPathFailed = run.status === "failed" && run.failureCategory === PROTECTED_PATH_CATEGORY;
|
|
1036
|
+
await this.options.vcs.notifyContinuationFinished?.(workspace, outcome, {
|
|
1037
|
+
agentName: run.agentName,
|
|
1038
|
+
...(outcome === "budget_exhausted" ? { budgetSentence: this.budgetSentence(run) } : {}),
|
|
1039
|
+
...(providerClass ? { providerSentence: providerSentence(providerClass) } : {}),
|
|
1040
|
+
...(unreadyServices ? { serviceSentence: serviceUnreadySentence(unreadyServices) } : {}),
|
|
1041
|
+
...(protectedPathFailed ? { protectedPathSentence: protectedPathSentence(null) } : {}),
|
|
1042
|
+
});
|
|
842
1043
|
}
|
|
843
1044
|
await rm(this.artifactPath(run.runId), { recursive: true, force: true }).catch(() => undefined);
|
|
844
1045
|
this.emit({ stage: "cleanup", runId: run.runId, jobId: run.jobHandle?.id, cleanupSucceeded: true });
|
|
845
1046
|
}
|
|
1047
|
+
/** Whether the proxy refused this run's session for budget. Never throws. */
|
|
1048
|
+
async outOfBudget(sessionId) {
|
|
1049
|
+
if (!sessionId || !this.options.sessions.budgetExhausted)
|
|
1050
|
+
return false;
|
|
1051
|
+
return this.options.sessions.budgetExhausted(sessionId).catch(() => false);
|
|
1052
|
+
}
|
|
1053
|
+
/** The model-provider failure the proxy relayed for this run's session, classified. Never throws. */
|
|
1054
|
+
async providerFailure(sessionId) {
|
|
1055
|
+
if (!sessionId || !this.options.sessions.upstreamFailure)
|
|
1056
|
+
return null;
|
|
1057
|
+
const code = await this.options.sessions.upstreamFailure(sessionId).catch(() => null);
|
|
1058
|
+
return code ? { class: classifyProviderFailure(code), code } : null;
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* A failure the model provider's refusal caused. The persisted error names
|
|
1062
|
+
* the class only; the raw upstream code (a fixed identifier, never a message)
|
|
1063
|
+
* goes to one operator line an alert can key on, and the failure itself is
|
|
1064
|
+
* still logged under its diagnostic id.
|
|
1065
|
+
*/
|
|
1066
|
+
providerFailureOf(run, provider, error, options = {}) {
|
|
1067
|
+
const logged = this.failure(error, options);
|
|
1068
|
+
containerLog.warn({ event: "coding.provider_failure", runId: run.runId, class: provider.class, upstreamCode: provider.code }, "the model provider refused this coding run's requests");
|
|
1069
|
+
return {
|
|
1070
|
+
error: `coding_provider_${provider.class}`,
|
|
1071
|
+
audit: { failureCategory: `provider_${provider.class}`, diagnosticId: logged.audit.diagnosticId },
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
/** The host-safe sentence for a run that ran out of budget. */
|
|
1075
|
+
budgetSentence(run) {
|
|
1076
|
+
return budgetSentence({
|
|
1077
|
+
runBudgetUsd: run.budgetUsd,
|
|
1078
|
+
agentBudgetUsd: run.agentBudgetUsd ?? run.budgetUsd,
|
|
1079
|
+
budgetGroupName: run.budgetGroupName,
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* A failure the proxy's budget refusal caused. The failure itself is still
|
|
1084
|
+
* logged under its diagnostic id, so the operator keeps the real reason.
|
|
1085
|
+
*/
|
|
1086
|
+
budgetFailure(error, options = {}) {
|
|
1087
|
+
const logged = this.failure(error, options);
|
|
1088
|
+
return {
|
|
1089
|
+
error: "coding_budget_exhausted",
|
|
1090
|
+
audit: { failureCategory: "budget", diagnosticId: logged.audit.diagnosticId },
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
846
1093
|
emit(event) {
|
|
847
1094
|
try {
|
|
848
1095
|
this.observer.emit(event);
|
|
@@ -913,7 +1160,36 @@ function safeError(error) {
|
|
|
913
1160
|
const message = error instanceof Error ? error.message : "unknown";
|
|
914
1161
|
return `coding_executor:${redactAndTruncate(message, 200).replace(/[^A-Za-z0-9_.:-]/g, "_")}`;
|
|
915
1162
|
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Error-code prefixes that name their category outright, checked before the
|
|
1165
|
+
* substring heuristics below. Those heuristics filed every `github_*` error
|
|
1166
|
+
* (a failed PR lookup or create after the push, a token mint) under
|
|
1167
|
+
* "workspace" because "github" contains "git".
|
|
1168
|
+
*/
|
|
1169
|
+
const CATEGORY_BY_PREFIX = [
|
|
1170
|
+
// A service sidecar never became ready (src/providers/jobs/kubernetes.ts).
|
|
1171
|
+
[SERVICE_UNREADY_ERROR, SERVICE_UNREADY_CATEGORY],
|
|
1172
|
+
// Services on a launcher that can't start them: refused at dispatch, failed here as a backstop.
|
|
1173
|
+
["coding_services_unsupported", "preflight"],
|
|
1174
|
+
// Claude Code's tool runner failed to start or never became ready: the same category on both launchers.
|
|
1175
|
+
["docker_tool_runner_", "job"],
|
|
1176
|
+
["kubernetes_tool_runner_", "job"],
|
|
1177
|
+
// Checked first: the host could not be asked (after one retry), not a lost permission.
|
|
1178
|
+
["repo_access_check_unavailable", "repo_access_unavailable"],
|
|
1179
|
+
["repo_access_", "repo_access"],
|
|
1180
|
+
["github_", "github"],
|
|
1181
|
+
// Checked before the generic "vcs_" fallback: the collected diff touched a
|
|
1182
|
+
// path this agent may not edit, not a generic workspace/git failure.
|
|
1183
|
+
["vcs_protected_path:", PROTECTED_PATH_CATEGORY],
|
|
1184
|
+
["vcs_", "workspace"],
|
|
1185
|
+
["git_", "workspace"],
|
|
1186
|
+
];
|
|
916
1187
|
function failureCategory(error) {
|
|
1188
|
+
const raw = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
1189
|
+
for (const [prefix, category] of CATEGORY_BY_PREFIX) {
|
|
1190
|
+
if (raw.startsWith(prefix))
|
|
1191
|
+
return category;
|
|
1192
|
+
}
|
|
917
1193
|
const message = safeError(error);
|
|
918
1194
|
if (message.includes("preflight") || message.includes("ownership") || message.includes("image"))
|
|
919
1195
|
return "preflight";
|
|
@@ -13,12 +13,11 @@
|
|
|
13
13
|
* `recover()` (Task 6) before declaring a DBOS run lost.
|
|
14
14
|
*/
|
|
15
15
|
import type { PrismaClient } from "#prisma";
|
|
16
|
-
import type { ProviderRegistry } from "../index.js";
|
|
17
16
|
import type { DbosConfig } from "../../config/providers.js";
|
|
18
|
-
import { type RunnerDb } from "../../core/runner.js";
|
|
17
|
+
import { type NativeRunProviders, type RunnerDb } from "../../core/runner.js";
|
|
19
18
|
import type { Executor, ExecutionRecoveryResult, PersistedExecutionHandle } from "./types.js";
|
|
20
19
|
export declare const DBOS_BACKEND = "dbos";
|
|
21
|
-
type Providers =
|
|
20
|
+
type Providers = NativeRunProviders;
|
|
22
21
|
type Db = RunnerDb & Pick<PrismaClient, "run">;
|
|
23
22
|
export declare class DbosExecutor implements Executor {
|
|
24
23
|
private readonly providers;
|
|
@@ -5,14 +5,13 @@
|
|
|
5
5
|
* heartbeat and recovers the run to `lost` rather than leaving it stuck
|
|
6
6
|
* `running` forever.
|
|
7
7
|
*/
|
|
8
|
-
import type
|
|
9
|
-
import { type RunnerDb } from "../../core/runner.js";
|
|
8
|
+
import { type NativeRunProviders, type RunnerDb } from "../../core/runner.js";
|
|
10
9
|
import type { Executor } from "./types.js";
|
|
11
10
|
export declare class InProcessExecutor implements Executor {
|
|
12
11
|
private readonly providers;
|
|
13
12
|
private readonly db;
|
|
14
13
|
private readonly heartbeatIntervalMs;
|
|
15
|
-
constructor(providers:
|
|
14
|
+
constructor(providers: NativeRunProviders, db?: RunnerDb, heartbeatIntervalMs?: number);
|
|
16
15
|
start(runId: string): Promise<void>;
|
|
17
16
|
stop(_runId: string, _reason?: string): Promise<void>;
|
|
18
17
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CodingImageSelector, Executor, ExecutionRecoveryResult, PersistedExecutionHandle } from "./types.js";
|
|
2
|
+
import type { CodingProvider } from "../../coding/provider.js";
|
|
2
3
|
export interface ExecutionKindResolver {
|
|
3
4
|
kindForRun(runId: string): Promise<"native" | "coding" | null>;
|
|
4
5
|
}
|
|
@@ -21,6 +22,12 @@ export declare class RoutingExecutor implements Executor {
|
|
|
21
22
|
launch(): Promise<void>;
|
|
22
23
|
close(): Promise<void>;
|
|
23
24
|
resolveCodingWorkerImage(selector: CodingImageSelector): string;
|
|
25
|
+
resolveCodingToolImage(selector: CodingImageSelector): string | null;
|
|
26
|
+
readCodingServiceDeclaration(input: {
|
|
27
|
+
repository: string;
|
|
28
|
+
baseRef: string;
|
|
29
|
+
}): Promise<string | null>;
|
|
30
|
+
supportsCodingServices(provider: CodingProvider): boolean;
|
|
24
31
|
}
|
|
25
32
|
export declare class PrismaExecutionKindResolver implements ExecutionKindResolver {
|
|
26
33
|
private readonly db;
|
|
@@ -54,6 +54,19 @@ export class RoutingExecutor {
|
|
|
54
54
|
throw new Error("coding_execution_not_configured");
|
|
55
55
|
return this.coding.resolveCodingWorkerImage(selector);
|
|
56
56
|
}
|
|
57
|
+
resolveCodingToolImage(selector) {
|
|
58
|
+
if (!this.coding.resolveCodingToolImage)
|
|
59
|
+
throw new Error("coding_execution_not_configured");
|
|
60
|
+
return this.coding.resolveCodingToolImage(selector);
|
|
61
|
+
}
|
|
62
|
+
async readCodingServiceDeclaration(input) {
|
|
63
|
+
if (!this.coding.readCodingServiceDeclaration)
|
|
64
|
+
return null;
|
|
65
|
+
return this.coding.readCodingServiceDeclaration(input);
|
|
66
|
+
}
|
|
67
|
+
supportsCodingServices(provider) {
|
|
68
|
+
return this.coding.supportsCodingServices?.(provider) === true;
|
|
69
|
+
}
|
|
57
70
|
}
|
|
58
71
|
export class PrismaExecutionKindResolver {
|
|
59
72
|
db;
|
|
@@ -51,5 +51,27 @@ export interface Executor {
|
|
|
51
51
|
* the LLM pricing tables' unknown-model throw.
|
|
52
52
|
*/
|
|
53
53
|
resolveCodingWorkerImage?(selector: CodingImageSelector): string;
|
|
54
|
+
/**
|
|
55
|
+
* Claude Code's tool-runner image (the container that runs its commands) for the agent's
|
|
56
|
+
* toolchain, resolved and persisted at dispatch alongside the worker image; null for Codex, whose
|
|
57
|
+
* one worker image carries the toolchain. Throws, like resolveCodingWorkerImage, on a toolchain
|
|
58
|
+
* with no image.
|
|
59
|
+
*/
|
|
60
|
+
resolveCodingToolImage?(selector: CodingImageSelector): string | null;
|
|
61
|
+
/**
|
|
62
|
+
* Coding-run services (docs/coding-services.md): the raw text of the
|
|
63
|
+
* repository's .wardby/services.yaml at `baseRef`, or null when it has none.
|
|
64
|
+
* Called by dispatch before its transaction (it is a network call). An
|
|
65
|
+
* executor without it gives coding runs no services.
|
|
66
|
+
*/
|
|
67
|
+
readCodingServiceDeclaration?(input: {
|
|
68
|
+
repository: string;
|
|
69
|
+
baseRef: string;
|
|
70
|
+
}): Promise<string | null>;
|
|
71
|
+
/**
|
|
72
|
+
* Whether this executor can start coding-run services for a run of `provider`: its job launcher
|
|
73
|
+
* says so (Kubernetes and Docker, for Codex and Claude Code).
|
|
74
|
+
*/
|
|
75
|
+
supportsCodingServices?(provider: CodingProvider): boolean;
|
|
54
76
|
}
|
|
55
77
|
import type { CodingProvider } from "../../coding/provider.js";
|