@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
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { PackageAllowlist } from "../../../coding/registry/allowlist.js";
|
|
2
|
+
import type { DeclaredDependency } from "../../../coding/registry/types.js";
|
|
3
|
+
export interface RegistryRunContext {
|
|
4
|
+
runId: string;
|
|
5
|
+
deadlineAt: Date;
|
|
6
|
+
allowlist: PackageAllowlist;
|
|
7
|
+
policy: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface RegistryFetchRecord {
|
|
10
|
+
runId: string;
|
|
11
|
+
ecosystem: string;
|
|
12
|
+
name: string;
|
|
13
|
+
version?: string;
|
|
14
|
+
filename?: string;
|
|
15
|
+
integrity?: string;
|
|
16
|
+
sizeBytes?: number;
|
|
17
|
+
outcome: "served" | "refused";
|
|
18
|
+
reason?: string;
|
|
19
|
+
}
|
|
20
|
+
/** A registry-verified version fact as stored: always with its publish time. */
|
|
21
|
+
export interface StoredVersionFact {
|
|
22
|
+
name: string;
|
|
23
|
+
version: string;
|
|
24
|
+
publishedAt: Date;
|
|
25
|
+
integrity: string;
|
|
26
|
+
downloadUrl: string;
|
|
27
|
+
dependencies: readonly DeclaredDependency[];
|
|
28
|
+
}
|
|
29
|
+
/** One exact version a lockfile plan approved for a run. */
|
|
30
|
+
export interface ApprovedVersion {
|
|
31
|
+
name: string;
|
|
32
|
+
version: string;
|
|
33
|
+
integrity: string;
|
|
34
|
+
}
|
|
35
|
+
/** One exact version a lockfile plan definitively refused for a run. */
|
|
36
|
+
export interface PlanRefusedVersion {
|
|
37
|
+
name: string;
|
|
38
|
+
version: string;
|
|
39
|
+
code: string;
|
|
40
|
+
reason: string;
|
|
41
|
+
/** Set for a too-new refusal: it lapses once the version is old enough. */
|
|
42
|
+
publishedAt?: Date | null;
|
|
43
|
+
}
|
|
44
|
+
/** Run-scoped store over the registry-only token session lookup and the
|
|
45
|
+
* RegistryAllowance/RegistryFetch tables (Task 5). Backs the registry proxy:
|
|
46
|
+
* it never sees the model capability, only the derived registry token. */
|
|
47
|
+
export interface RegistryStore {
|
|
48
|
+
findRunByRegistryTokenHash(hash: string, now: Date): Promise<RegistryRunContext | null>;
|
|
49
|
+
isAllowedDependency(runId: string, ecosystem: string, name: string): Promise<boolean>;
|
|
50
|
+
addAllowances(runId: string, ecosystem: string, names: readonly string[]): Promise<void>;
|
|
51
|
+
recordFetch(record: RegistryFetchRecord): Promise<void>;
|
|
52
|
+
usage(runId: string): Promise<{
|
|
53
|
+
files: number;
|
|
54
|
+
bytes: number;
|
|
55
|
+
}>;
|
|
56
|
+
/** Number of refused RegistryFetch rows recorded for the run. */
|
|
57
|
+
refusalCount(runId: string): Promise<number>;
|
|
58
|
+
listFetches(runId: string): Promise<(RegistryFetchRecord & {
|
|
59
|
+
createdAt: Date;
|
|
60
|
+
})[]>;
|
|
61
|
+
/** Stored facts for these versions (any not stored are absent). */
|
|
62
|
+
getVersionFacts(ecosystem: string, versions: readonly {
|
|
63
|
+
name: string;
|
|
64
|
+
version: string;
|
|
65
|
+
}[]): Promise<StoredVersionFact[]>;
|
|
66
|
+
/** Stores facts; one already stored is kept as it is (facts are immutable). */
|
|
67
|
+
putVersionFacts(ecosystem: string, facts: readonly StoredVersionFact[]): Promise<void>;
|
|
68
|
+
/** Approves exact versions for the run (an existing approval is kept). */
|
|
69
|
+
approveVersions(runId: string, ecosystem: string, versions: readonly ApprovedVersion[]): Promise<void>;
|
|
70
|
+
/** The run's approval of this exact version, with its download URL (from
|
|
71
|
+
* the version's fact), or null. */
|
|
72
|
+
findApprovedVersion(runId: string, ecosystem: string, name: string, version: string): Promise<{
|
|
73
|
+
integrity: string;
|
|
74
|
+
downloadUrl: string;
|
|
75
|
+
} | null>;
|
|
76
|
+
/** Stores the run's plan refusals (an existing one is kept). */
|
|
77
|
+
refusePlanVersions(runId: string, ecosystem: string, versions: readonly PlanRefusedVersion[]): Promise<void>;
|
|
78
|
+
/** The run's plan refusal of this exact version, or null. */
|
|
79
|
+
findPlanRefusal(runId: string, ecosystem: string, name: string, version: string): Promise<{
|
|
80
|
+
code: string;
|
|
81
|
+
reason: string;
|
|
82
|
+
publishedAt: Date | null;
|
|
83
|
+
} | null>;
|
|
84
|
+
}
|
|
85
|
+
/** In-memory adapter for tests. `contexts` is settable directly so tests can
|
|
86
|
+
* seed a run without going through a full proxy session. */
|
|
87
|
+
export declare class MemoryRegistryStore implements RegistryStore {
|
|
88
|
+
readonly contexts: Map<string, RegistryRunContext>;
|
|
89
|
+
readonly allowances: Set<string>;
|
|
90
|
+
readonly fetches: (RegistryFetchRecord & {
|
|
91
|
+
createdAt: Date;
|
|
92
|
+
})[];
|
|
93
|
+
/** `ecosystem\0name\0version` -> fact. */
|
|
94
|
+
readonly facts: Map<string, StoredVersionFact>;
|
|
95
|
+
/** `runId\0ecosystem\0name\0version` -> approval. */
|
|
96
|
+
readonly approvals: Map<string, ApprovedVersion>;
|
|
97
|
+
/** `runId\0ecosystem\0name\0version` -> plan refusal. */
|
|
98
|
+
readonly planRefusals: Map<string, PlanRefusedVersion>;
|
|
99
|
+
findRunByRegistryTokenHash(hash: string, now: Date): Promise<RegistryRunContext | null>;
|
|
100
|
+
isAllowedDependency(runId: string, ecosystem: string, name: string): Promise<boolean>;
|
|
101
|
+
addAllowances(runId: string, ecosystem: string, names: readonly string[]): Promise<void>;
|
|
102
|
+
recordFetch(record: RegistryFetchRecord): Promise<void>;
|
|
103
|
+
usage(runId: string): Promise<{
|
|
104
|
+
files: number;
|
|
105
|
+
bytes: number;
|
|
106
|
+
}>;
|
|
107
|
+
refusalCount(runId: string): Promise<number>;
|
|
108
|
+
listFetches(runId: string): Promise<(RegistryFetchRecord & {
|
|
109
|
+
createdAt: Date;
|
|
110
|
+
})[]>;
|
|
111
|
+
getVersionFacts(ecosystem: string, versions: readonly {
|
|
112
|
+
name: string;
|
|
113
|
+
version: string;
|
|
114
|
+
}[]): Promise<StoredVersionFact[]>;
|
|
115
|
+
putVersionFacts(ecosystem: string, facts: readonly StoredVersionFact[]): Promise<void>;
|
|
116
|
+
approveVersions(runId: string, ecosystem: string, versions: readonly ApprovedVersion[]): Promise<void>;
|
|
117
|
+
findApprovedVersion(runId: string, ecosystem: string, name: string, version: string): Promise<{
|
|
118
|
+
integrity: string;
|
|
119
|
+
downloadUrl: string;
|
|
120
|
+
} | null>;
|
|
121
|
+
refusePlanVersions(runId: string, ecosystem: string, versions: readonly PlanRefusedVersion[]): Promise<void>;
|
|
122
|
+
findPlanRefusal(runId: string, ecosystem: string, name: string, version: string): Promise<{
|
|
123
|
+
code: string;
|
|
124
|
+
reason: string;
|
|
125
|
+
publishedAt: Date | null;
|
|
126
|
+
} | null>;
|
|
127
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** In-memory adapter for tests. `contexts` is settable directly so tests can
|
|
2
|
+
* seed a run without going through a full proxy session. */
|
|
3
|
+
export class MemoryRegistryStore {
|
|
4
|
+
contexts = new Map();
|
|
5
|
+
allowances = new Set();
|
|
6
|
+
fetches = [];
|
|
7
|
+
/** `ecosystem\0name\0version` -> fact. */
|
|
8
|
+
facts = new Map();
|
|
9
|
+
/** `runId\0ecosystem\0name\0version` -> approval. */
|
|
10
|
+
approvals = new Map();
|
|
11
|
+
/** `runId\0ecosystem\0name\0version` -> plan refusal. */
|
|
12
|
+
planRefusals = new Map();
|
|
13
|
+
async findRunByRegistryTokenHash(hash, now) {
|
|
14
|
+
const context = this.contexts.get(hash);
|
|
15
|
+
return context && context.deadlineAt > now ? context : null;
|
|
16
|
+
}
|
|
17
|
+
async isAllowedDependency(runId, ecosystem, name) {
|
|
18
|
+
return this.allowances.has(`${runId}\0${ecosystem}\0${name}`);
|
|
19
|
+
}
|
|
20
|
+
async addAllowances(runId, ecosystem, names) {
|
|
21
|
+
for (const name of names)
|
|
22
|
+
this.allowances.add(`${runId}\0${ecosystem}\0${name}`);
|
|
23
|
+
}
|
|
24
|
+
async recordFetch(record) {
|
|
25
|
+
this.fetches.push({ ...record, createdAt: new Date() });
|
|
26
|
+
}
|
|
27
|
+
async usage(runId) {
|
|
28
|
+
const served = this.fetches.filter((fetch) => fetch.runId === runId && fetch.outcome === "served");
|
|
29
|
+
return { files: served.length, bytes: served.reduce((sum, fetch) => sum + (fetch.sizeBytes ?? 0), 0) };
|
|
30
|
+
}
|
|
31
|
+
async refusalCount(runId) {
|
|
32
|
+
return this.fetches.filter((fetch) => fetch.runId === runId && fetch.outcome === "refused").length;
|
|
33
|
+
}
|
|
34
|
+
async listFetches(runId) {
|
|
35
|
+
return this.fetches.filter((fetch) => fetch.runId === runId);
|
|
36
|
+
}
|
|
37
|
+
async getVersionFacts(ecosystem, versions) {
|
|
38
|
+
return versions.flatMap(({ name, version }) => this.facts.get(`${ecosystem}\0${name}\0${version}`) ?? []);
|
|
39
|
+
}
|
|
40
|
+
async putVersionFacts(ecosystem, facts) {
|
|
41
|
+
for (const fact of facts) {
|
|
42
|
+
const key = `${ecosystem}\0${fact.name}\0${fact.version}`;
|
|
43
|
+
if (!this.facts.has(key))
|
|
44
|
+
this.facts.set(key, fact);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async approveVersions(runId, ecosystem, versions) {
|
|
48
|
+
for (const approval of versions) {
|
|
49
|
+
const key = `${runId}\0${ecosystem}\0${approval.name}\0${approval.version}`;
|
|
50
|
+
if (!this.approvals.has(key))
|
|
51
|
+
this.approvals.set(key, approval);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async findApprovedVersion(runId, ecosystem, name, version) {
|
|
55
|
+
const approval = this.approvals.get(`${runId}\0${ecosystem}\0${name}\0${version}`);
|
|
56
|
+
const fact = this.facts.get(`${ecosystem}\0${name}\0${version}`);
|
|
57
|
+
return approval && fact ? { integrity: approval.integrity, downloadUrl: fact.downloadUrl } : null;
|
|
58
|
+
}
|
|
59
|
+
async refusePlanVersions(runId, ecosystem, versions) {
|
|
60
|
+
for (const refusal of versions) {
|
|
61
|
+
const key = `${runId}\0${ecosystem}\0${refusal.name}\0${refusal.version}`;
|
|
62
|
+
if (!this.planRefusals.has(key))
|
|
63
|
+
this.planRefusals.set(key, refusal);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async findPlanRefusal(runId, ecosystem, name, version) {
|
|
67
|
+
const refusal = this.planRefusals.get(`${runId}\0${ecosystem}\0${name}\0${version}`);
|
|
68
|
+
return refusal ? { code: refusal.code, reason: refusal.reason, publishedAt: refusal.publishedAt ?? null } : null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import type { PrismaClient } from "#prisma";
|
|
2
|
+
import type { UpstreamFetch } from "../../coding/registry/types.js";
|
|
2
3
|
import { startDenyPortListener } from "./deny-port.js";
|
|
4
|
+
import { type PinnedProxyFetchOptions } from "./secure-fetch.js";
|
|
3
5
|
import { startCodingProxyServer, type CodingProxyServerHandle } from "./server.js";
|
|
4
6
|
import type { ProxyAuditSink } from "./types.js";
|
|
7
|
+
/** Adapts the pinned upstream `fetch` (which only forwards `init.headers`)
|
|
8
|
+
* to `UpstreamFetch`'s narrower init shape: `accept` becomes an `accept`
|
|
9
|
+
* header, `acceptEncoding` an `accept-encoding` header (the pinned fetch
|
|
10
|
+
* never decompresses; the caller that asks for gzip does), and a `content-type: application/json` header is set whenever a
|
|
11
|
+
* body is present (every registry/OSV upstream call sends or expects
|
|
12
|
+
* JSON). Exported so its header-mapping can be unit-tested directly,
|
|
13
|
+
* without needing a live upstream. */
|
|
14
|
+
export declare function createRegistryUpstream(pinned: typeof globalThis.fetch): UpstreamFetch;
|
|
5
15
|
export interface CodingProxyRuntimeOptions {
|
|
6
16
|
db: PrismaClient;
|
|
17
|
+
/** The package registry's own client, so its traffic (many concurrent
|
|
18
|
+
* downloads during an install) cannot starve the budget ledger that every
|
|
19
|
+
* model request needs. Defaults to `db`. */
|
|
20
|
+
registryDb?: PrismaClient;
|
|
7
21
|
env?: NodeJS.ProcessEnv;
|
|
8
22
|
startServer?: typeof startCodingProxyServer;
|
|
9
23
|
startDenyPort?: typeof startDenyPortListener;
|
|
24
|
+
/** Overridable for tests: proves the OSV host and every adapter's
|
|
25
|
+
* upstream hosts are actually passed to the pinned-fetch allowlist. */
|
|
26
|
+
createPinnedFetch?: (options: PinnedProxyFetchOptions) => typeof globalThis.fetch;
|
|
10
27
|
audit?: ProxyAuditSink;
|
|
11
28
|
onRequest?: (event: {
|
|
12
29
|
protocol: "openai-responses" | "anthropic-messages" | "other";
|
|
@@ -1,9 +1,40 @@
|
|
|
1
|
+
import { REGISTRY_ADAPTERS } from "../../coding/registry/adapters.js";
|
|
1
2
|
import { CODING_PROXY_ALIAS, CODING_PROXY_DENY_PORT, CODING_PROXY_PORT } from "../jobs/docker-isolation.js";
|
|
2
3
|
import { startDenyPortListener } from "./deny-port.js";
|
|
3
4
|
import { EnvironmentCredentialResolver } from "./environment-credentials.js";
|
|
4
5
|
import { CodingProxy } from "./proxy.js";
|
|
5
6
|
import { PrismaProxyLedger } from "./prisma-ledger.js";
|
|
7
|
+
import { OsvAudit } from "./registry/audit.js";
|
|
8
|
+
import { PrismaRegistryStore } from "./registry/prisma-store.js";
|
|
9
|
+
import { DEFAULT_PLAN_MAX_ENTRIES, DEFAULT_PLAN_TIMEOUT_MS } from "./registry/plan.js";
|
|
10
|
+
import { DEFAULT_GRAPH_TIMEOUT_MS, DEFAULT_MAX_GRAPH_PACKAGES, DEFAULT_PLAN_MAX_PER_RUN, RegistryService, } from "./registry/service.js";
|
|
11
|
+
import { createPinnedProxyFetch } from "./secure-fetch.js";
|
|
6
12
|
import { startCodingProxyServer } from "./server.js";
|
|
13
|
+
const MIB = 1024 * 1024;
|
|
14
|
+
function positiveInt(value, fallback) {
|
|
15
|
+
const parsed = Number(value);
|
|
16
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
17
|
+
}
|
|
18
|
+
/** Adapts the pinned upstream `fetch` (which only forwards `init.headers`)
|
|
19
|
+
* to `UpstreamFetch`'s narrower init shape: `accept` becomes an `accept`
|
|
20
|
+
* header, `acceptEncoding` an `accept-encoding` header (the pinned fetch
|
|
21
|
+
* never decompresses; the caller that asks for gzip does), and a `content-type: application/json` header is set whenever a
|
|
22
|
+
* body is present (every registry/OSV upstream call sends or expects
|
|
23
|
+
* JSON). Exported so its header-mapping can be unit-tested directly,
|
|
24
|
+
* without needing a live upstream. */
|
|
25
|
+
export function createRegistryUpstream(pinned) {
|
|
26
|
+
return (url, init = {}) => pinned(url, {
|
|
27
|
+
method: init.method ?? "GET",
|
|
28
|
+
body: init.body,
|
|
29
|
+
signal: init.signal,
|
|
30
|
+
redirect: "error",
|
|
31
|
+
headers: {
|
|
32
|
+
...(init.accept ? { accept: init.accept } : {}),
|
|
33
|
+
...(init.acceptEncoding ? { "accept-encoding": init.acceptEncoding } : {}),
|
|
34
|
+
...(init.body ? { "content-type": "application/json" } : {}),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
7
38
|
/** Starts the trusted proxy with the fixed worker-only Docker endpoint, plus the deny port the enforcement witness probes. */
|
|
8
39
|
export async function startConfiguredCodingProxy(options) {
|
|
9
40
|
const env = options.env ?? process.env;
|
|
@@ -12,11 +43,43 @@ export async function startConfiguredCodingProxy(options) {
|
|
|
12
43
|
credentials: new EnvironmentCredentialResolver(env),
|
|
13
44
|
audit: options.audit,
|
|
14
45
|
});
|
|
46
|
+
const buildPinnedFetch = options.createPinnedFetch ?? createPinnedProxyFetch;
|
|
47
|
+
const upstreamHosts = [...new Set([...REGISTRY_ADAPTERS.values()].flatMap((adapter) => adapter.upstreamHosts))];
|
|
48
|
+
const pinned = buildPinnedFetch({ allowedHosts: [...upstreamHosts, "api.osv.dev"] });
|
|
49
|
+
const upstream = createRegistryUpstream(pinned);
|
|
50
|
+
const metadataTimeoutMs = positiveInt(env.REGISTRY_METADATA_TIMEOUT_MS, 30_000);
|
|
51
|
+
const maxMetadataBytes = positiveInt(env.REGISTRY_MAX_METADATA_MB, 64) * MIB;
|
|
52
|
+
const registry = new RegistryService({
|
|
53
|
+
adapters: REGISTRY_ADAPTERS,
|
|
54
|
+
store: new PrismaRegistryStore(options.registryDb ?? options.db),
|
|
55
|
+
audit: new OsvAudit({
|
|
56
|
+
fetch: upstream,
|
|
57
|
+
failOpen: env.REGISTRY_AUDIT_FAIL_OPEN === "true",
|
|
58
|
+
timeoutMs: metadataTimeoutMs,
|
|
59
|
+
maxBytes: maxMetadataBytes,
|
|
60
|
+
}),
|
|
61
|
+
upstream,
|
|
62
|
+
metadataTimeoutMs,
|
|
63
|
+
maxMetadataBytes,
|
|
64
|
+
maxGraphPackages: positiveInt(env.REGISTRY_MAX_GRAPH_PACKAGES, DEFAULT_MAX_GRAPH_PACKAGES),
|
|
65
|
+
graphTimeoutMs: positiveInt(env.REGISTRY_GRAPH_TIMEOUT_MS, DEFAULT_GRAPH_TIMEOUT_MS),
|
|
66
|
+
planMaxEntries: positiveInt(env.REGISTRY_PLAN_MAX_ENTRIES, DEFAULT_PLAN_MAX_ENTRIES),
|
|
67
|
+
planTimeoutMs: positiveInt(env.REGISTRY_PLAN_TIMEOUT_MS, DEFAULT_PLAN_TIMEOUT_MS),
|
|
68
|
+
planMaxPerRun: positiveInt(env.REGISTRY_PLAN_MAX_PER_RUN, DEFAULT_PLAN_MAX_PER_RUN),
|
|
69
|
+
proxyBase: `http://${CODING_PROXY_ALIAS}:${CODING_PROXY_PORT}/registry/`,
|
|
70
|
+
limits: {
|
|
71
|
+
maxFileBytes: positiveInt(env.REGISTRY_MAX_FILE_MB, 200) * MIB,
|
|
72
|
+
maxTotalBytes: positiveInt(env.REGISTRY_MAX_TOTAL_MB, 2048) * MIB,
|
|
73
|
+
maxFiles: positiveInt(env.REGISTRY_MAX_FILES, 5000),
|
|
74
|
+
idleTimeoutMs: positiveInt(env.REGISTRY_IDLE_TIMEOUT_MS, 120_000),
|
|
75
|
+
},
|
|
76
|
+
});
|
|
15
77
|
const startServer = options.startServer ?? startCodingProxyServer;
|
|
16
78
|
const server = await startServer(proxy, {
|
|
17
79
|
host: "0.0.0.0",
|
|
18
80
|
port: CODING_PROXY_PORT,
|
|
19
81
|
expectedHost: `${CODING_PROXY_ALIAS}:${CODING_PROXY_PORT}`,
|
|
82
|
+
registry,
|
|
20
83
|
onRequest: options.onRequest,
|
|
21
84
|
});
|
|
22
85
|
let deny;
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
import type { ProxyAuditEvent } from "./types.js";
|
|
1
2
|
import { type CodingProxy } from "./proxy.js";
|
|
3
|
+
import { type RegistryService } from "./registry/service.js";
|
|
2
4
|
import type { ProxyProtocol } from "./types.js";
|
|
5
|
+
declare const proxyLog: import("pino").Logger<never, boolean>;
|
|
6
|
+
/**
|
|
7
|
+
* Writes a proxy audit event to the operator log. The events carry ids,
|
|
8
|
+
* models, amounts, statuses and fixed reason codes only, never request or
|
|
9
|
+
* response content. `sink` defaults to the proxy's own logger.
|
|
10
|
+
*/
|
|
11
|
+
export declare function logProxyAudit(event: ProxyAuditEvent, sink?: Pick<typeof proxyLog, "info">): void;
|
|
3
12
|
export interface CodingProxyServerConfig {
|
|
4
13
|
host: string;
|
|
5
14
|
port: number;
|
|
6
15
|
expectedHost?: string;
|
|
16
|
+
registry?: Pick<RegistryService, "handle"> & Partial<Pick<RegistryService, "plan">>;
|
|
7
17
|
onRequest?: (event: {
|
|
8
18
|
protocol: ProxyProtocol | "other";
|
|
9
19
|
status: number;
|
|
@@ -15,3 +25,4 @@ export interface CodingProxyServerHandle {
|
|
|
15
25
|
close(): Promise<void>;
|
|
16
26
|
}
|
|
17
27
|
export declare function startCodingProxyServer(proxy: CodingProxy, config: CodingProxyServerConfig): Promise<CodingProxyServerHandle>;
|
|
28
|
+
export {};
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
+
import { StringDecoder } from "node:string_decoder";
|
|
3
|
+
import { pipeline, Readable } from "node:stream";
|
|
2
4
|
import { HTTP_LIMITS, HttpBoundaryError, readBody } from "../../mcp/transport/http-limits.js";
|
|
3
5
|
import { logger } from "../../core/logger.js";
|
|
4
6
|
import { CodingProxyError, PROXY_MAX_BODY_BYTES } from "./proxy.js";
|
|
7
|
+
import { RegistryError } from "../../coding/registry/types.js";
|
|
8
|
+
import { MAX_PLAN_BODY_BYTES } from "./registry/service.js";
|
|
5
9
|
const proxyLog = logger.child({ module: "coding-proxy" });
|
|
10
|
+
/**
|
|
11
|
+
* Writes a proxy audit event to the operator log. The events carry ids,
|
|
12
|
+
* models, amounts, statuses and fixed reason codes only, never request or
|
|
13
|
+
* response content. `sink` defaults to the proxy's own logger.
|
|
14
|
+
*/
|
|
15
|
+
export function logProxyAudit(event, sink = proxyLog) {
|
|
16
|
+
const { type, ...fields } = event;
|
|
17
|
+
sink.info({ ...fields, event: `audit.${type}` }, "coding proxy audit");
|
|
18
|
+
}
|
|
6
19
|
function sendError(response, status, code) {
|
|
7
20
|
response
|
|
8
21
|
.writeHead(status, { "content-type": "application/json", "cache-control": "no-store" })
|
|
@@ -13,6 +26,79 @@ function bearer(value) {
|
|
|
13
26
|
return "";
|
|
14
27
|
return value.slice(7);
|
|
15
28
|
}
|
|
29
|
+
/** The registry's own auth: a registry-only token (`rrg_…`) carried as either
|
|
30
|
+
* a bearer token or the password half of HTTP basic auth (some clients,
|
|
31
|
+
* e.g. pip, only support basic auth for a package index). */
|
|
32
|
+
function registryToken(authorization) {
|
|
33
|
+
if (!authorization)
|
|
34
|
+
return "";
|
|
35
|
+
const bearerToken = bearer(authorization);
|
|
36
|
+
if (bearerToken)
|
|
37
|
+
return bearerToken;
|
|
38
|
+
const basic = authorization.match(/^Basic ([A-Za-z0-9+/=]+)$/);
|
|
39
|
+
if (!basic)
|
|
40
|
+
return "";
|
|
41
|
+
const decoded = Buffer.from(basic[1], "base64").toString("utf8");
|
|
42
|
+
const colon = decoded.indexOf(":");
|
|
43
|
+
return colon >= 0 ? decoded.slice(colon + 1) : "";
|
|
44
|
+
}
|
|
45
|
+
/** Time a client has to upload a lockfile to the plan route. */
|
|
46
|
+
const PLAN_UPLOAD_MS = 60_000;
|
|
47
|
+
function sendRegistry(response, result) {
|
|
48
|
+
response
|
|
49
|
+
.writeHead(result.status, { "content-type": result.contentType, "cache-control": "no-store" })
|
|
50
|
+
.end(result.body);
|
|
51
|
+
}
|
|
52
|
+
/** Reads a lockfile body within MAX_PLAN_BODY_BYTES, decoding each chunk
|
|
53
|
+
* as it arrives (no buffered copy of the raw bytes), and joins the text
|
|
54
|
+
* once. A RegistryError says why it could not be read. */
|
|
55
|
+
function readPlanBody(request, signal) {
|
|
56
|
+
const tooLarge = () => new RegistryError(413, "wardby_lockfile_too_large", "the lockfile is larger than 20 MiB");
|
|
57
|
+
const length = request.headers["content-length"];
|
|
58
|
+
if (length !== undefined && (!/^\d+$/.test(length) || Number(length) > MAX_PLAN_BODY_BYTES))
|
|
59
|
+
return Promise.reject(tooLarge());
|
|
60
|
+
const encoding = request.headers["content-encoding"];
|
|
61
|
+
if (encoding && encoding !== "identity")
|
|
62
|
+
return Promise.reject(new RegistryError(400, "wardby_bad_request", "the lockfile must not be content-encoded"));
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const decoder = new StringDecoder("utf8");
|
|
65
|
+
const parts = [];
|
|
66
|
+
let size = 0;
|
|
67
|
+
const cleanup = () => {
|
|
68
|
+
request.off("data", data);
|
|
69
|
+
request.off("end", end);
|
|
70
|
+
request.off("error", failed);
|
|
71
|
+
request.off("aborted", failed);
|
|
72
|
+
signal.removeEventListener("abort", failed);
|
|
73
|
+
};
|
|
74
|
+
const fail = (error) => {
|
|
75
|
+
cleanup();
|
|
76
|
+
parts.length = 0;
|
|
77
|
+
request.pause();
|
|
78
|
+
reject(error);
|
|
79
|
+
};
|
|
80
|
+
const failed = () => fail(new RegistryError(400, "wardby_bad_request", "the lockfile could not be read"));
|
|
81
|
+
const data = (chunk) => {
|
|
82
|
+
size += chunk.length;
|
|
83
|
+
if (size > MAX_PLAN_BODY_BYTES)
|
|
84
|
+
fail(tooLarge());
|
|
85
|
+
else
|
|
86
|
+
parts.push(decoder.write(chunk));
|
|
87
|
+
};
|
|
88
|
+
const end = () => {
|
|
89
|
+
cleanup();
|
|
90
|
+
parts.push(decoder.end());
|
|
91
|
+
resolve(parts.join(""));
|
|
92
|
+
};
|
|
93
|
+
request.on("data", data);
|
|
94
|
+
request.once("end", end);
|
|
95
|
+
request.once("error", failed);
|
|
96
|
+
request.once("aborted", failed);
|
|
97
|
+
signal.addEventListener("abort", failed, { once: true });
|
|
98
|
+
if (signal.aborted)
|
|
99
|
+
failed();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
16
102
|
function routeProtocol(method, url) {
|
|
17
103
|
if (method !== "POST")
|
|
18
104
|
return undefined;
|
|
@@ -60,6 +146,83 @@ export async function startCodingProxyServer(proxy, config) {
|
|
|
60
146
|
sendError(response, 403, "invalid_host");
|
|
61
147
|
return;
|
|
62
148
|
}
|
|
149
|
+
if (request.url?.startsWith("/registry/")) {
|
|
150
|
+
if (!config.registry) {
|
|
151
|
+
sendError(response, 404, "not_found");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const plan = request.url.match(/^\/registry\/([a-z0-9-]+)\/-\/plan(?:\?.*)?$/);
|
|
155
|
+
if (plan && request.method === "POST" && config.registry.plan) {
|
|
156
|
+
// Lockfile verification. The registry authenticates the token and
|
|
157
|
+
// takes a plan slot before it asks for the body, which is then read
|
|
158
|
+
// within its own size and time bounds.
|
|
159
|
+
const controller = new AbortController();
|
|
160
|
+
response.on("close", () => {
|
|
161
|
+
if (!response.writableFinished)
|
|
162
|
+
controller.abort();
|
|
163
|
+
});
|
|
164
|
+
let bodyRead = false;
|
|
165
|
+
const result = await config.registry.plan({
|
|
166
|
+
ecosystem: plan[1],
|
|
167
|
+
token: registryToken(request.headers.authorization),
|
|
168
|
+
readBody: () => {
|
|
169
|
+
bodyRead = true;
|
|
170
|
+
return readPlanBody(request, AbortSignal.any([controller.signal, AbortSignal.timeout(PLAN_UPLOAD_MS)]));
|
|
171
|
+
},
|
|
172
|
+
signal: controller.signal,
|
|
173
|
+
});
|
|
174
|
+
// Refused before the body was read: close the connection rather
|
|
175
|
+
// than read (or keep) what the client is still sending.
|
|
176
|
+
if (!bodyRead)
|
|
177
|
+
response.setHeader("connection", "close");
|
|
178
|
+
if ("body" in result)
|
|
179
|
+
sendRegistry(response, result);
|
|
180
|
+
else {
|
|
181
|
+
void result.stream.cancel().catch(() => undefined);
|
|
182
|
+
sendError(response, 500, "internal_error");
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
187
|
+
sendError(response, 405, "method_not_allowed");
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const match = request.url.match(/^\/registry\/([a-z0-9-]+)\/(.*)$/);
|
|
191
|
+
if (!match) {
|
|
192
|
+
sendError(response, 404, "not_found");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const controller = new AbortController();
|
|
196
|
+
response.on("close", () => controller.abort());
|
|
197
|
+
const result = await config.registry.handle({
|
|
198
|
+
method: request.method,
|
|
199
|
+
ecosystem: match[1],
|
|
200
|
+
subpath: match[2].split("?")[0],
|
|
201
|
+
token: registryToken(request.headers.authorization),
|
|
202
|
+
signal: controller.signal,
|
|
203
|
+
});
|
|
204
|
+
response.statusCode = result.status;
|
|
205
|
+
response.setHeader("content-type", result.contentType);
|
|
206
|
+
if ("body" in result) {
|
|
207
|
+
response.end(request.method === "HEAD" ? undefined : result.body);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (request.method === "HEAD") {
|
|
211
|
+
// The registry answers HEAD without starting a download; cancel
|
|
212
|
+
// defensively anyway so a stream can never hold a reservation.
|
|
213
|
+
void result.stream.cancel().catch(() => undefined);
|
|
214
|
+
response.end();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// pipeline (not .pipe) destroys the source when the client
|
|
218
|
+
// disconnects or the response errors, which cancels the web stream
|
|
219
|
+
// so the registry releases the run's in-flight reservation.
|
|
220
|
+
pipeline(Readable.fromWeb(result.stream), response, (error) => {
|
|
221
|
+
if (error && !response.destroyed)
|
|
222
|
+
response.destroy();
|
|
223
|
+
});
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
63
226
|
if (request.method === "HEAD" && request.url === "/api/hello") {
|
|
64
227
|
response.writeHead(200, { "cache-control": "no-store" }).end();
|
|
65
228
|
return;
|
|
@@ -22,6 +22,11 @@ export interface ProxySession {
|
|
|
22
22
|
deadlineAt: Date;
|
|
23
23
|
budgetUsd: number;
|
|
24
24
|
status: ProxySessionStatus;
|
|
25
|
+
registryTokenHash?: string | null;
|
|
26
|
+
/** When the ledger first refused a request of this session for budget. */
|
|
27
|
+
budgetExhaustedAt?: Date | null;
|
|
28
|
+
/** The code of the first upstream failure the proxy relayed for this session. */
|
|
29
|
+
upstreamFailure?: string | null;
|
|
25
30
|
}
|
|
26
31
|
export interface ProxyRequest {
|
|
27
32
|
id: string;
|
|
@@ -45,6 +50,7 @@ export interface CreateProxySessionInput {
|
|
|
45
50
|
allowedModels: string[];
|
|
46
51
|
deadlineAt: Date;
|
|
47
52
|
budgetUsd: number;
|
|
53
|
+
registryTokenHash: string;
|
|
48
54
|
}
|
|
49
55
|
export interface ReserveProxyRequestInput {
|
|
50
56
|
id: string;
|
|
@@ -76,6 +82,12 @@ export interface ProxyLedger {
|
|
|
76
82
|
release(requestId: string, upstreamStatus: number): Promise<void>;
|
|
77
83
|
markUncertain(requestId: string, upstreamStatus?: number): Promise<void>;
|
|
78
84
|
cancelSession(sessionId: string): Promise<void>;
|
|
85
|
+
/** Whether a request of this session was ever refused for budget. */
|
|
86
|
+
budgetExhausted(sessionId: string): Promise<boolean>;
|
|
87
|
+
/** Records an upstream failure code on the session; only the first one is kept. */
|
|
88
|
+
recordUpstreamFailure(sessionId: string, code: string): Promise<void>;
|
|
89
|
+
/** The first upstream failure code relayed for this session, if any. */
|
|
90
|
+
upstreamFailure(sessionId: string): Promise<string | null>;
|
|
79
91
|
getRequest(requestId: string): Promise<ProxyRequest | null>;
|
|
80
92
|
}
|
|
81
93
|
export interface ProxyAuditEvent {
|
|
@@ -92,6 +104,9 @@ export interface ProxyAuditEvent {
|
|
|
92
104
|
cachedInputTokens?: number;
|
|
93
105
|
cacheWriteTokens?: number;
|
|
94
106
|
reasoningTokens?: number;
|
|
107
|
+
/** For a failed stream: the upstream response's content type and encoding headers, for diagnosis. */
|
|
108
|
+
contentType?: string;
|
|
109
|
+
contentEncoding?: string;
|
|
95
110
|
}
|
|
96
111
|
export type ProxyAuditSink = (event: ProxyAuditEvent) => void;
|
|
97
112
|
export interface CredentialResolver {
|
|
@@ -13,12 +13,22 @@
|
|
|
13
13
|
* blinds the pre-flight refuse and mid-stream cutoff, and is not allowed.
|
|
14
14
|
*/
|
|
15
15
|
import type { ProviderRegistry } from "../index.js";
|
|
16
|
+
import type { LlmEffort } from "../llm/types.js";
|
|
16
17
|
/** The subset of Agent fields the engine needs — not the Prisma type, to keep this seam Prisma-agnostic. */
|
|
17
18
|
export interface EngineAgent {
|
|
18
19
|
systemPrompt: string;
|
|
19
20
|
model: string;
|
|
20
21
|
budgetUsd: number;
|
|
21
22
|
maxTurns: number;
|
|
23
|
+
/** Reasoning effort sent on every model call; absent = provider default. */
|
|
24
|
+
effort?: LlmEffort;
|
|
25
|
+
/**
|
|
26
|
+
* Background for this run that nobody vetted (e.g. the title and
|
|
27
|
+
* description of the GitHub issue a mention was made on). Delivered in the
|
|
28
|
+
* first user message inside untrusted_context tags, never in the system
|
|
29
|
+
* prompt. Absent = the first user message is just "Begin.".
|
|
30
|
+
*/
|
|
31
|
+
untrustedContext?: string;
|
|
22
32
|
}
|
|
23
33
|
export interface LoadedTool {
|
|
24
34
|
name: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PrismaClient } from "#prisma";
|
|
2
|
-
import type {
|
|
2
|
+
import type { NativeRunProviders } from "../../core/runner.js";
|
|
3
3
|
import { type ProviderConfig } from "../../config/providers.js";
|
|
4
4
|
import type { Executor } from "./types.js";
|
|
5
5
|
/** Select the Executor adapter from EXECUTOR. Constructing never connects; call `launch?.()` for that. */
|
|
6
|
-
export declare function buildExecutor(config: Pick<ProviderConfig, "executor">, providers:
|
|
6
|
+
export declare function buildExecutor(config: Pick<ProviderConfig, "executor">, providers: NativeRunProviders, db?: PrismaClient, env?: NodeJS.ProcessEnv): Executor;
|
|
@@ -2,6 +2,7 @@ import type { PrismaClient } from "#prisma";
|
|
|
2
2
|
import { type ProviderConfig } from "../../config/providers.js";
|
|
3
3
|
import type { KubernetesApi } from "../jobs/kubernetes-api.js";
|
|
4
4
|
import type { Executor } from "./types.js";
|
|
5
|
+
import { type RepoAccessGate } from "../../core/repo-access.js";
|
|
5
6
|
export interface ConfiguredExecutorOptions {
|
|
6
7
|
native: Executor;
|
|
7
8
|
db: PrismaClient;
|
|
@@ -9,6 +10,8 @@ export interface ConfiguredExecutorOptions {
|
|
|
9
10
|
providerConfig?: ProviderConfig;
|
|
10
11
|
/** Tests only: replaces the kubeconfig-backed client when JOB_LAUNCHER=kubernetes. */
|
|
11
12
|
kubernetesApi?: KubernetesApi;
|
|
13
|
+
/** Repository authorization for coding runs; built from the configured review hosts when absent. */
|
|
14
|
+
repoAccess?: RepoAccessGate;
|
|
12
15
|
}
|
|
13
16
|
/** Builds container execution only when Docker or Kubernetes is explicitly selected. */
|
|
14
17
|
export declare function buildConfiguredExecutor(options: ConfiguredExecutorOptions): Executor;
|