@wardby/cli 0.2.0 → 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 +183 -71
- package/dist/claude-coding-worker/driver.d.ts +7 -1
- package/dist/claude-coding-worker/driver.js +31 -2
- package/dist/claude-coding-worker/main.js +5 -2
- 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 +99 -3
- package/dist/coding/protocol.js +155 -4
- 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 +18 -0
- package/dist/coding-worker/artifact.js +48 -6
- 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 +14 -1
- package/dist/coding-worker/driver.js +106 -14
- package/dist/coding-worker/errors.d.ts +7 -0
- package/dist/coding-worker/errors.js +26 -0
- package/dist/coding-worker/main.js +13 -4
- 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 +109 -6
- 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/coding-queue.d.ts +1 -1
- package/dist/core/datastores.d.ts +1 -1
- package/dist/core/datastores.js +18 -2
- package/dist/core/db.d.ts +40 -2
- package/dist/core/db.js +75 -2
- package/dist/core/dispatch.d.ts +49 -7
- package/dist/core/dispatch.js +290 -45
- 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/http-runtime.js +3 -4
- package/dist/core/in-flight-runs.d.ts +8 -0
- package/dist/core/in-flight-runs.js +56 -0
- package/dist/core/lease.d.ts +1 -1
- package/dist/core/private-directory.d.ts +20 -0
- package/dist/core/private-directory.js +36 -0
- package/dist/core/provider-wording.d.ts +12 -0
- package/dist/core/provider-wording.js +44 -0
- package/dist/core/reconciler.d.ts +39 -3
- 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 +31 -9
- package/dist/core/runner.js +237 -40
- package/dist/core/scheduler.d.ts +1 -1
- package/dist/core/secrets.d.ts +12 -3
- package/dist/core/secrets.js +25 -6
- package/dist/core/subagent-memory-tools.d.ts +2 -2
- 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 +9 -2
- package/dist/core/webhooks.js +23 -2
- package/dist/generated/prisma/browser.d.ts +328 -0
- package/dist/generated/prisma/browser.js +17 -0
- package/dist/generated/prisma/client.d.ts +347 -0
- package/dist/generated/prisma/client.js +34 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +839 -0
- package/dist/generated/prisma/commonInputTypes.js +10 -0
- package/dist/generated/prisma/enums.d.ts +59 -0
- package/dist/generated/prisma/enums.js +59 -0
- package/dist/generated/prisma/internal/class.d.ts +605 -0
- package/dist/generated/prisma/internal/class.js +49 -0
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +4494 -0
- package/dist/generated/prisma/internal/prismaNamespace.js +628 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +635 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +599 -0
- package/dist/generated/prisma/models/Agent.d.ts +3560 -0
- package/dist/generated/prisma/models/Agent.js +1 -0
- package/dist/generated/prisma/models/AgentDatastore.d.ts +1210 -0
- package/dist/generated/prisma/models/AgentDatastore.js +1 -0
- package/dist/generated/prisma/models/AgentMemory.d.ts +986 -0
- package/dist/generated/prisma/models/AgentMemory.js +1 -0
- package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
- package/dist/generated/prisma/models/AgentRepository.js +1 -0
- package/dist/generated/prisma/models/AgentSecret.d.ts +1212 -0
- package/dist/generated/prisma/models/AgentSecret.js +1 -0
- package/dist/generated/prisma/models/AgentSubAgent.d.ts +1217 -0
- package/dist/generated/prisma/models/AgentSubAgent.js +1 -0
- package/dist/generated/prisma/models/AgentTool.d.ts +1403 -0
- package/dist/generated/prisma/models/AgentTool.js +1 -0
- package/dist/generated/prisma/models/AuthFormChallenge.d.ts +1298 -0
- package/dist/generated/prisma/models/AuthFormChallenge.js +1 -0
- package/dist/generated/prisma/models/AuthLoginKey.d.ts +1245 -0
- package/dist/generated/prisma/models/AuthLoginKey.js +1 -0
- package/dist/generated/prisma/models/AuthRateLimit.d.ts +979 -0
- package/dist/generated/prisma/models/AuthRateLimit.js +1 -0
- package/dist/generated/prisma/models/AuthSession.d.ts +1376 -0
- package/dist/generated/prisma/models/AuthSession.js +1 -0
- package/dist/generated/prisma/models/AuthUser.d.ts +1700 -0
- package/dist/generated/prisma/models/AuthUser.js +1 -0
- package/dist/generated/prisma/models/BudgetGroup.d.ts +1548 -0
- package/dist/generated/prisma/models/BudgetGroup.js +1 -0
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +1681 -0
- package/dist/generated/prisma/models/CodingAgentProfile.js +1 -0
- package/dist/generated/prisma/models/CodingProxyRequest.d.ts +1749 -0
- package/dist/generated/prisma/models/CodingProxyRequest.js +1 -0
- package/dist/generated/prisma/models/CodingProxySession.d.ts +1626 -0
- package/dist/generated/prisma/models/CodingProxySession.js +1 -0
- package/dist/generated/prisma/models/CodingRun.d.ts +3583 -0
- package/dist/generated/prisma/models/CodingRun.js +1 -0
- package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
- package/dist/generated/prisma/models/CodingService.js +1 -0
- package/dist/generated/prisma/models/Datastore.d.ts +1434 -0
- package/dist/generated/prisma/models/Datastore.js +1 -0
- package/dist/generated/prisma/models/DatastoreEntry.d.ts +1314 -0
- package/dist/generated/prisma/models/DatastoreEntry.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/OAuthAuthorizationCode.d.ts +1526 -0
- package/dist/generated/prisma/models/OAuthAuthorizationCode.js +1 -0
- package/dist/generated/prisma/models/OAuthAuthorizationRequest.d.ts +1350 -0
- package/dist/generated/prisma/models/OAuthAuthorizationRequest.js +1 -0
- package/dist/generated/prisma/models/OAuthClient.d.ts +1289 -0
- package/dist/generated/prisma/models/OAuthClient.js +1 -0
- package/dist/generated/prisma/models/OAuthFamily.d.ts +1540 -0
- package/dist/generated/prisma/models/OAuthFamily.js +1 -0
- package/dist/generated/prisma/models/OAuthGrant.d.ts +1280 -0
- package/dist/generated/prisma/models/OAuthGrant.js +1 -0
- package/dist/generated/prisma/models/Principal.d.ts +2245 -0
- package/dist/generated/prisma/models/Principal.js +1 -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 +2669 -0
- package/dist/generated/prisma/models/Run.js +1 -0
- 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/SchedulerLease.d.ts +970 -0
- package/dist/generated/prisma/models/SchedulerLease.js +1 -0
- package/dist/generated/prisma/models/Secret.d.ts +1399 -0
- package/dist/generated/prisma/models/Secret.js +1 -0
- package/dist/generated/prisma/models/SecretElicitationOutcome.d.ts +973 -0
- package/dist/generated/prisma/models/SecretElicitationOutcome.js +1 -0
- package/dist/generated/prisma/models/Task.d.ts +1176 -0
- package/dist/generated/prisma/models/Task.js +1 -0
- package/dist/generated/prisma/models/Tool.d.ts +1490 -0
- package/dist/generated/prisma/models/Tool.js +1 -0
- package/dist/generated/prisma/models/Webhook.d.ts +1386 -0
- package/dist/generated/prisma/models/Webhook.js +1 -0
- package/dist/generated/prisma/models.d.ts +45 -0
- package/dist/generated/prisma/models.js +1 -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 +6 -1
- package/dist/import/create.js +68 -14
- package/dist/import/index.d.ts +1 -1
- 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 +37 -63
- package/dist/mcp/auth/ownership.js +19 -14
- package/dist/mcp/auth/principal.d.ts +1 -1
- 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 +32 -3
- package/dist/mcp/auth/resource-server.js +84 -3
- package/dist/mcp/auth/self-hosted/browser.js +22 -8
- package/dist/mcp/auth/self-hosted/cli.d.ts +1 -1
- package/dist/mcp/auth/self-hosted/cli.js +47 -13
- package/dist/mcp/auth/self-hosted/credentials.d.ts +21 -3
- package/dist/mcp/auth/self-hosted/credentials.js +62 -4
- package/dist/mcp/auth/self-hosted/rate-limit.d.ts +1 -1
- package/dist/mcp/auth/self-hosted/session.d.ts +3 -2
- package/dist/mcp/context.d.ts +28 -2
- 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.d.ts +1 -1
- package/dist/mcp/server.js +27 -9
- package/dist/mcp/shared/page-style.d.ts +16 -0
- package/dist/mcp/shared/page-style.js +129 -0
- package/dist/mcp/tasks/manager.d.ts +1 -1
- package/dist/mcp/tools/agents.js +447 -49
- package/dist/mcp/tools/budget-groups.js +3 -3
- package/dist/mcp/tools/datastore.js +23 -16
- 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 +4 -7
- package/dist/mcp/tools/secret-elicitation-form.d.ts +1 -1
- package/dist/mcp/tools/secret-elicitation-form.js +10 -28
- package/dist/mcp/tools/secret-elicitation.d.ts +1 -1
- 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 +53 -17
- package/dist/mcp/tools/tools.d.ts +41 -0
- package/dist/mcp/tools/tools.js +202 -41
- package/dist/mcp/tools/trigger.js +36 -6
- package/dist/mcp/tools/webhooks.js +15 -4
- package/dist/mcp/transport/streamable-http.d.ts +11 -1
- package/dist/mcp/transport/streamable-http.js +32 -2
- package/dist/mcp/unattended-schedules.d.ts +1 -1
- package/dist/mcp/webhooks/ingress.d.ts +1 -1
- package/dist/providers/auth/delegating.d.ts +19 -0
- package/dist/providers/auth/delegating.js +71 -0
- package/dist/providers/auth/index.d.ts +3 -1
- package/dist/providers/auth/index.js +11 -0
- package/dist/providers/auth/self-hosted.d.ts +4 -3
- 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 +18 -1
- 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 +18 -1
- 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/datastore/postgres.d.ts +1 -1
- package/dist/providers/engine/types.d.ts +10 -0
- package/dist/providers/executor/build.d.ts +3 -3
- package/dist/providers/executor/composition.d.ts +4 -1
- package/dist/providers/executor/composition.js +19 -0
- package/dist/providers/executor/container.d.ts +106 -4
- package/dist/providers/executor/container.js +305 -22
- package/dist/providers/executor/dbos.d.ts +3 -4
- 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 +60 -2
- package/dist/providers/jobs/docker.js +313 -50
- 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 +117 -31
- package/dist/providers/jobs/types.d.ts +20 -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/memory/postgres.d.ts +1 -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 +79 -14
- 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 +34 -32
- package/dist/quickstart/migrate.d.ts +23 -0
- package/dist/quickstart/migrate.js +125 -0
- 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 +30 -11
- package/prisma/migrate.config.mjs +20 -0
- 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 +417 -23
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type { Prisma, PrismaClient } from "
|
|
1
|
+
import type { Prisma, PrismaClient } from "#prisma";
|
|
2
|
+
/** Rejects unknown role names loudly; returns the deduplicated list. */
|
|
3
|
+
export declare function validateRoles(roles: readonly string[]): string[];
|
|
2
4
|
export type AuthDb = Prisma.TransactionClient;
|
|
3
5
|
export declare const DAY = 86400000;
|
|
4
6
|
export declare function decodeKey(value: string, name: string): Buffer;
|
|
@@ -26,19 +28,34 @@ export declare function lockUser(tx: AuthDb, id: string): Promise<{
|
|
|
26
28
|
createdAt: Date;
|
|
27
29
|
updatedAt: Date;
|
|
28
30
|
principalId: string;
|
|
31
|
+
roles: string[];
|
|
29
32
|
displayName: string | null;
|
|
30
33
|
}>;
|
|
31
34
|
export declare class IdentityService {
|
|
32
35
|
readonly db: PrismaClient;
|
|
33
36
|
readonly credentials: Credentials;
|
|
34
37
|
constructor(db: PrismaClient, hashKey: string);
|
|
35
|
-
createUser(subject: string): Promise<{
|
|
38
|
+
createUser(subject: string, roles?: readonly string[]): Promise<{
|
|
36
39
|
userId: string;
|
|
37
40
|
subject: string;
|
|
41
|
+
roles: string[];
|
|
38
42
|
loginKey: string;
|
|
39
43
|
}>;
|
|
40
44
|
createKey(subject: string): Promise<string>;
|
|
41
45
|
disableUser(subject: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Adds and/or removes roles on an existing user (never creates one). Any
|
|
48
|
+
* actual change signs the user out in the same transaction: their OAuth
|
|
49
|
+
* grant families, refresh grants, browser sessions and unexchanged
|
|
50
|
+
* authorization codes are revoked, and they sign in and authorize afresh.
|
|
51
|
+
* (Roles are also read live on every request, so even a direct database
|
|
52
|
+
* edit applies to live tokens at once.) A no-op change revokes nothing.
|
|
53
|
+
*/
|
|
54
|
+
changeRoles(subject: string, add: readonly string[], remove: readonly string[]): Promise<{
|
|
55
|
+
subject: string;
|
|
56
|
+
roles: string[];
|
|
57
|
+
revokedGrants: boolean;
|
|
58
|
+
}>;
|
|
42
59
|
revokeKey(keyId: string): Promise<void>;
|
|
43
60
|
listUsers(): Prisma.PrismaPromise<{
|
|
44
61
|
status: string;
|
|
@@ -46,13 +63,14 @@ export declare class IdentityService {
|
|
|
46
63
|
subject: string;
|
|
47
64
|
};
|
|
48
65
|
id: string;
|
|
66
|
+
roles: string[];
|
|
49
67
|
displayName: string | null;
|
|
50
68
|
}[]>;
|
|
51
69
|
listKeys(subject: string): Prisma.PrismaPromise<{
|
|
52
70
|
createdAt: Date;
|
|
53
71
|
keyId: string;
|
|
54
|
-
lastUsedAt: Date | null;
|
|
55
72
|
expiresAt: Date;
|
|
73
|
+
lastUsedAt: Date | null;
|
|
56
74
|
revokedAt: Date | null;
|
|
57
75
|
}[]>;
|
|
58
76
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { requireSubject } from "../../../providers/auth/subject.js";
|
|
3
|
+
import { ROLE_NAMES, isRoleName } from "../resource-server.js";
|
|
4
|
+
/** Rejects unknown role names loudly; returns the deduplicated list. */
|
|
5
|
+
export function validateRoles(roles) {
|
|
6
|
+
for (const r of roles)
|
|
7
|
+
if (!isRoleName(r))
|
|
8
|
+
throw new Error(`Unknown role "${r}" (known roles: ${ROLE_NAMES.join(", ")}).`);
|
|
9
|
+
return [...new Set(roles)];
|
|
10
|
+
}
|
|
3
11
|
export const DAY = 86_400_000;
|
|
4
12
|
export function decodeKey(value, name) {
|
|
5
13
|
if (!/^[a-f\d]{64}$/i.test(value))
|
|
@@ -43,16 +51,17 @@ export class IdentityService {
|
|
|
43
51
|
this.db = db;
|
|
44
52
|
this.credentials = new Credentials(hashKey);
|
|
45
53
|
}
|
|
46
|
-
async createUser(subject) {
|
|
54
|
+
async createUser(subject, roles = []) {
|
|
47
55
|
requireSubject(subject);
|
|
56
|
+
const granted = validateRoles(roles);
|
|
48
57
|
return this.db.$transaction(async (tx) => {
|
|
49
58
|
const principal = await tx.principal.upsert({ where: { subject }, create: { subject }, update: {} });
|
|
50
|
-
const user = await tx.authUser.create({ data: { principalId: principal.id } });
|
|
59
|
+
const user = await tx.authUser.create({ data: { principalId: principal.id, roles: granted } });
|
|
51
60
|
const key = this.credentials.create("rvk");
|
|
52
61
|
await tx.authLoginKey.create({
|
|
53
62
|
data: { keyId: key.id, userId: user.id, secretHash: key.hash, expiresAt: new Date(Date.now() + 365 * DAY) },
|
|
54
63
|
});
|
|
55
|
-
return { userId: user.id, subject, loginKey: key.token };
|
|
64
|
+
return { userId: user.id, subject, roles: granted, loginKey: key.token };
|
|
56
65
|
});
|
|
57
66
|
}
|
|
58
67
|
async createKey(subject) {
|
|
@@ -81,6 +90,49 @@ export class IdentityService {
|
|
|
81
90
|
await tx.oAuthFamily.updateMany({ where: { userId: user.id }, data: { revokedAt: now } });
|
|
82
91
|
});
|
|
83
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Adds and/or removes roles on an existing user (never creates one). Any
|
|
95
|
+
* actual change signs the user out in the same transaction: their OAuth
|
|
96
|
+
* grant families, refresh grants, browser sessions and unexchanged
|
|
97
|
+
* authorization codes are revoked, and they sign in and authorize afresh.
|
|
98
|
+
* (Roles are also read live on every request, so even a direct database
|
|
99
|
+
* edit applies to live tokens at once.) A no-op change revokes nothing.
|
|
100
|
+
*/
|
|
101
|
+
async changeRoles(subject, add, remove) {
|
|
102
|
+
const adding = validateRoles(add);
|
|
103
|
+
const removing = validateRoles(remove);
|
|
104
|
+
const both = adding.filter((r) => removing.includes(r));
|
|
105
|
+
if (both.length)
|
|
106
|
+
throw new Error(`Cannot both grant and revoke: ${both.join(", ")}.`);
|
|
107
|
+
const user = await this.db.authUser.findFirst({ where: { principal: { subject: requireSubject(subject) } } });
|
|
108
|
+
if (!user)
|
|
109
|
+
throw new Error("Unknown user.");
|
|
110
|
+
return this.db.$transaction(async (tx) => {
|
|
111
|
+
await tx.$queryRaw `SELECT "id" FROM "AuthUser" WHERE "id" = ${user.id} FOR UPDATE`;
|
|
112
|
+
const current = (await tx.authUser.findUniqueOrThrow({ where: { id: user.id } })).roles ?? [];
|
|
113
|
+
const roles = ROLE_NAMES.filter((r) => (current.includes(r) || adding.includes(r)) && !removing.includes(r));
|
|
114
|
+
await tx.authUser.update({ where: { id: user.id }, data: { roles } });
|
|
115
|
+
// ANY change signs the user out (families, refresh grants, sessions,
|
|
116
|
+
// unexchanged codes): a revoked role must never come back through an
|
|
117
|
+
// old token, and a granted role must never silently widen a grant the
|
|
118
|
+
// user consented to while it was inert — they authorize afresh.
|
|
119
|
+
const changed = roles.length !== current.length || roles.some((r) => !current.includes(r));
|
|
120
|
+
if (changed) {
|
|
121
|
+
const now = new Date();
|
|
122
|
+
await tx.oAuthAuthorizationCode.updateMany({
|
|
123
|
+
where: { userId: user.id, consumedAt: null },
|
|
124
|
+
data: { consumedAt: now },
|
|
125
|
+
});
|
|
126
|
+
await tx.authSession.updateMany({ where: { userId: user.id, revokedAt: null }, data: { revokedAt: now } });
|
|
127
|
+
await tx.oAuthFamily.updateMany({ where: { userId: user.id, revokedAt: null }, data: { revokedAt: now } });
|
|
128
|
+
await tx.oAuthGrant.updateMany({
|
|
129
|
+
where: { family: { userId: user.id }, revokedAt: null },
|
|
130
|
+
data: { revokedAt: now },
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return { subject, roles, revokedGrants: changed };
|
|
134
|
+
});
|
|
135
|
+
}
|
|
84
136
|
async revokeKey(keyId) {
|
|
85
137
|
const key = await this.db.authLoginKey.findUnique({ where: { keyId } });
|
|
86
138
|
if (!key)
|
|
@@ -94,7 +146,13 @@ export class IdentityService {
|
|
|
94
146
|
}
|
|
95
147
|
listUsers() {
|
|
96
148
|
return this.db.authUser.findMany({
|
|
97
|
-
select: {
|
|
149
|
+
select: {
|
|
150
|
+
id: true,
|
|
151
|
+
status: true,
|
|
152
|
+
roles: true,
|
|
153
|
+
displayName: true,
|
|
154
|
+
principal: { select: { subject: true } },
|
|
155
|
+
},
|
|
98
156
|
});
|
|
99
157
|
}
|
|
100
158
|
listKeys(subject) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PrismaClient } from "
|
|
1
|
+
import type { PrismaClient } from "#prisma";
|
|
2
2
|
import type { Credentials } from "./credentials.js";
|
|
3
3
|
import { type AuthDb } from "./credentials.js";
|
|
4
4
|
export declare class Sessions {
|
|
@@ -21,13 +21,14 @@ export declare class Sessions {
|
|
|
21
21
|
createdAt: Date;
|
|
22
22
|
updatedAt: Date;
|
|
23
23
|
principalId: string;
|
|
24
|
+
roles: string[];
|
|
24
25
|
displayName: string | null;
|
|
25
26
|
};
|
|
26
27
|
} & {
|
|
27
28
|
createdAt: Date;
|
|
29
|
+
expiresAt: Date;
|
|
28
30
|
userId: string;
|
|
29
31
|
secretHash: string;
|
|
30
|
-
expiresAt: Date;
|
|
31
32
|
revokedAt: Date | null;
|
|
32
33
|
sessionId: string;
|
|
33
34
|
lastSeenAt: Date;
|
package/dist/mcp/context.d.ts
CHANGED
|
@@ -7,13 +7,39 @@
|
|
|
7
7
|
* registry also carries jobs/email/auth/storage seams no Phase 4 tool
|
|
8
8
|
* needs and this plan never builds real adapters for.
|
|
9
9
|
*/
|
|
10
|
-
import type { Principal, PrismaClient } from "
|
|
10
|
+
import type { Principal, PrismaClient } from "#prisma";
|
|
11
11
|
import type { RequestStateAccessor } from "@modelcontextprotocol/server";
|
|
12
12
|
import type { ProviderRegistry } from "../providers/index.js";
|
|
13
|
-
|
|
13
|
+
import type { RepoAccessGate } from "../core/repo-access.js";
|
|
14
|
+
import type { HostUserAuthorizerRegistry, ReviewHostRegistry } from "../providers/review-host/types.js";
|
|
15
|
+
export type McpProviders = Pick<ProviderRegistry, "llm" | "engine" | "datastore" | "secrets" | "executor" | "memory"> & {
|
|
16
|
+
reviewHosts?: ReviewHostRegistry;
|
|
17
|
+
/** Identity linking (link_host_account); empty or absent = linking disabled. */
|
|
18
|
+
hostUserAuthorizers?: HostUserAuthorizerRegistry;
|
|
19
|
+
/**
|
|
20
|
+
* Repository authorization (link_repository, coding profiles, host events).
|
|
21
|
+
* Absent = nothing can be authorized through host access (fail closed);
|
|
22
|
+
* only admin approvals still work.
|
|
23
|
+
*/
|
|
24
|
+
repoAccess?: RepoAccessGate;
|
|
25
|
+
};
|
|
14
26
|
export interface McpRequestContext {
|
|
15
27
|
principal: Principal;
|
|
16
28
|
scopes: Set<string>;
|
|
29
|
+
/**
|
|
30
|
+
* The caller's wardby roles (resource-server.ts ROLE_PERMISSIONS), resolved
|
|
31
|
+
* per request, never cached: a privileged scope is honoured only when one
|
|
32
|
+
* of these grants it (see requireScope). Absent/empty = member.
|
|
33
|
+
*/
|
|
34
|
+
roles?: readonly string[];
|
|
35
|
+
/**
|
|
36
|
+
* The stdio local operator (set only by index.ts localOperatorContext):
|
|
37
|
+
* owner of every resource for access checks, unfiltered listings. Depends
|
|
38
|
+
* on the transport, never on the principal, so an HTTP caller whose
|
|
39
|
+
* subject equals LOCAL_PRINCIPAL never gets it (A10). It never bypasses
|
|
40
|
+
* the binding rules (auth/access.ts requireBindingOwner).
|
|
41
|
+
*/
|
|
42
|
+
operator?: boolean;
|
|
17
43
|
/**
|
|
18
44
|
* The server's resource identifier, for tool handlers that need a
|
|
19
45
|
* field-level scope step-up beyond the tool's own declared scope (e.g.
|
package/dist/mcp/errors.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP-layer errors that carry an HTTP status + WWW-Authenticate challenge —
|
|
3
|
-
* the transport (Task 7) maps these to the actual HTTP response; JSON-RPC
|
|
4
|
-
* dispatch (Task 6) maps them to a JSON-RPC error object. One error type
|
|
5
|
-
* for both call sites avoids two parallel error hierarchies.
|
|
6
|
-
*/
|
|
7
1
|
export declare class McpError extends Error {
|
|
8
2
|
readonly httpStatus: number;
|
|
9
3
|
readonly wwwAuthenticate?: string;
|
|
@@ -11,3 +5,28 @@ export declare class McpError extends Error {
|
|
|
11
5
|
}
|
|
12
6
|
export declare function unauthorized(message: string, resourceMetadataUrl: string): McpError;
|
|
13
7
|
export declare function insufficientScope(scopes: string[], resourceMetadataUrl: string): McpError;
|
|
8
|
+
/**
|
|
9
|
+
* The one place a Prisma error becomes a client-facing McpError, applied at
|
|
10
|
+
* both MCP dispatch sites (server.ts) so no handler leaks the raw
|
|
11
|
+
* "Invalid `prisma.x.create()` invocation" text. A handler that already
|
|
12
|
+
* maps an error to its own, more specific McpError keeps that message: an
|
|
13
|
+
* McpError passes through here unchanged.
|
|
14
|
+
*
|
|
15
|
+
* - P2002 (unique violation) -> 409 naming the model and the unique fields.
|
|
16
|
+
* An `ownerId` component is described ("for this owner") rather than
|
|
17
|
+
* named, since per-owner uniques are the common case.
|
|
18
|
+
* - P2003 (foreign key) -> 409, worded for both directions: a delete blocked
|
|
19
|
+
* by a reference (ON DELETE RESTRICT), or an insert/update pointing at a
|
|
20
|
+
* row that no longer exists (e.g. attach_tool racing delete_tool).
|
|
21
|
+
* - A serialization failure or deadlock in one of the Serializable
|
|
22
|
+
* transactions, in any of the shapes isSerializationConflict knows (P2034,
|
|
23
|
+
* a raw-statement P2010, or an unwrapped driver adapter error at COMMIT)
|
|
24
|
+
* -> 409 asking the client to retry.
|
|
25
|
+
* - Any other Prisma client error -> a generic 500 carrying only a
|
|
26
|
+
* reference id. Its message ("Invalid `tx.tool.update()` invocation in
|
|
27
|
+
* /path/to/file.ts:…") can include server source paths and query detail,
|
|
28
|
+
* so it is logged here, under that id, and never sent.
|
|
29
|
+
* - Anything else (an McpError, a plain Error) is returned unchanged for the
|
|
30
|
+
* caller to rethrow.
|
|
31
|
+
*/
|
|
32
|
+
export declare function mapPrismaError(err: unknown): unknown;
|
package/dist/mcp/errors.js
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
* dispatch (Task 6) maps them to a JSON-RPC error object. One error type
|
|
5
5
|
* for both call sites avoids two parallel error hierarchies.
|
|
6
6
|
*/
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { Prisma } from "#prisma";
|
|
9
|
+
import { isSerializationConflict } from "../core/dispatch.js";
|
|
10
|
+
import { logger } from "../core/logger.js";
|
|
7
11
|
export class McpError extends Error {
|
|
8
12
|
httpStatus;
|
|
9
13
|
wwwAuthenticate;
|
|
@@ -20,3 +24,97 @@ export function unauthorized(message, resourceMetadataUrl) {
|
|
|
20
24
|
export function insufficientScope(scopes, resourceMetadataUrl) {
|
|
21
25
|
return new McpError(403, `Insufficient scope; this operation requires: ${scopes.join(" ")}`, `Bearer resource_metadata="${resourceMetadataUrl}", scope="${scopes.join(" ")}"`);
|
|
22
26
|
}
|
|
27
|
+
/** "BudgetGroup" -> "budget group". */
|
|
28
|
+
function humanizeModel(modelName) {
|
|
29
|
+
return modelName.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
function withArticle(noun) {
|
|
32
|
+
return `${/^[aeiou]/.test(noun) ? "An" : "A"} ${noun}`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The fields behind a P2002, from whichever shape reported them: a classic
|
|
36
|
+
* `meta.target`, the driver adapter's `constraint.fields`, or -- what
|
|
37
|
+
* @prisma/adapter-pg actually reports, since PostgreSQL always names the
|
|
38
|
+
* violated constraint -- the index name, which follows Prisma's default
|
|
39
|
+
* `<Model>_<field>[_<field>...]_key` naming for every @unique/@@unique in
|
|
40
|
+
* schema.prisma. Pinned against a real database in
|
|
41
|
+
* core/prisma-adapter.database.test.ts.
|
|
42
|
+
*/
|
|
43
|
+
function uniqueFields(meta, modelName) {
|
|
44
|
+
const target = meta?.target;
|
|
45
|
+
if (Array.isArray(target) && target.every((t) => typeof t === "string"))
|
|
46
|
+
return target;
|
|
47
|
+
if (typeof target === "string")
|
|
48
|
+
return [target];
|
|
49
|
+
const adapterError = meta?.driverAdapterError;
|
|
50
|
+
const constraint = adapterError?.cause?.constraint;
|
|
51
|
+
if (Array.isArray(constraint?.fields) && constraint.fields.every((f) => typeof f === "string")) {
|
|
52
|
+
return constraint.fields;
|
|
53
|
+
}
|
|
54
|
+
const index = constraint?.index;
|
|
55
|
+
if (typeof index === "string" && modelName && index.startsWith(`${modelName}_`) && index.endsWith("_key")) {
|
|
56
|
+
return index.slice(modelName.length + 1, -"_key".length).split("_");
|
|
57
|
+
}
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The one place a Prisma error becomes a client-facing McpError, applied at
|
|
62
|
+
* both MCP dispatch sites (server.ts) so no handler leaks the raw
|
|
63
|
+
* "Invalid `prisma.x.create()` invocation" text. A handler that already
|
|
64
|
+
* maps an error to its own, more specific McpError keeps that message: an
|
|
65
|
+
* McpError passes through here unchanged.
|
|
66
|
+
*
|
|
67
|
+
* - P2002 (unique violation) -> 409 naming the model and the unique fields.
|
|
68
|
+
* An `ownerId` component is described ("for this owner") rather than
|
|
69
|
+
* named, since per-owner uniques are the common case.
|
|
70
|
+
* - P2003 (foreign key) -> 409, worded for both directions: a delete blocked
|
|
71
|
+
* by a reference (ON DELETE RESTRICT), or an insert/update pointing at a
|
|
72
|
+
* row that no longer exists (e.g. attach_tool racing delete_tool).
|
|
73
|
+
* - A serialization failure or deadlock in one of the Serializable
|
|
74
|
+
* transactions, in any of the shapes isSerializationConflict knows (P2034,
|
|
75
|
+
* a raw-statement P2010, or an unwrapped driver adapter error at COMMIT)
|
|
76
|
+
* -> 409 asking the client to retry.
|
|
77
|
+
* - Any other Prisma client error -> a generic 500 carrying only a
|
|
78
|
+
* reference id. Its message ("Invalid `tx.tool.update()` invocation in
|
|
79
|
+
* /path/to/file.ts:…") can include server source paths and query detail,
|
|
80
|
+
* so it is logged here, under that id, and never sent.
|
|
81
|
+
* - Anything else (an McpError, a plain Error) is returned unchanged for the
|
|
82
|
+
* caller to rethrow.
|
|
83
|
+
*/
|
|
84
|
+
export function mapPrismaError(err) {
|
|
85
|
+
if (isSerializationConflict(err)) {
|
|
86
|
+
return new McpError(409, "A concurrent change conflicted with this one; retry the request.");
|
|
87
|
+
}
|
|
88
|
+
if (!isPrismaClientError(err))
|
|
89
|
+
return err;
|
|
90
|
+
if (!(err instanceof Prisma.PrismaClientKnownRequestError))
|
|
91
|
+
return internalDatabaseError(err);
|
|
92
|
+
const meta = err.meta;
|
|
93
|
+
const modelName = typeof meta?.modelName === "string" ? meta.modelName : undefined;
|
|
94
|
+
const noun = modelName ? humanizeModel(modelName) : undefined;
|
|
95
|
+
if (err.code === "P2002") {
|
|
96
|
+
const fields = uniqueFields(meta, modelName);
|
|
97
|
+
const named = fields.filter((f) => f !== "ownerId");
|
|
98
|
+
if (!noun || named.length === 0)
|
|
99
|
+
return new McpError(409, "A record with those values already exists.");
|
|
100
|
+
const scope = named.length < fields.length ? " for this owner" : "";
|
|
101
|
+
return new McpError(409, `${withArticle(noun)} with that ${named.join(" and ")} already exists${scope}.`);
|
|
102
|
+
}
|
|
103
|
+
if (err.code === "P2003") {
|
|
104
|
+
return new McpError(409, `The ${noun ?? "record"} conflicts with a related record: it is still referenced by another record, or refers to one that no longer exists.`);
|
|
105
|
+
}
|
|
106
|
+
return internalDatabaseError(err);
|
|
107
|
+
}
|
|
108
|
+
function isPrismaClientError(err) {
|
|
109
|
+
return ((err instanceof Error && err.name === "DriverAdapterError") ||
|
|
110
|
+
err instanceof Prisma.PrismaClientKnownRequestError ||
|
|
111
|
+
err instanceof Prisma.PrismaClientUnknownRequestError ||
|
|
112
|
+
err instanceof Prisma.PrismaClientValidationError ||
|
|
113
|
+
err instanceof Prisma.PrismaClientInitializationError ||
|
|
114
|
+
err instanceof Prisma.PrismaClientRustPanicError);
|
|
115
|
+
}
|
|
116
|
+
function internalDatabaseError(err) {
|
|
117
|
+
const reference = randomUUID();
|
|
118
|
+
logger.error({ err, reference }, "database error in an MCP handler; the client sees only the reference");
|
|
119
|
+
return new McpError(500, `Internal database error (reference: ${reference}).`);
|
|
120
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub App webhook ingress — framework-agnostic, mounted unauthenticated
|
|
3
|
+
* (by OAuth) in streamable-http.ts at /hosts/github/events. Authenticated by
|
|
4
|
+
* the App's webhook secret (X-Hub-Signature-256) over the raw body.
|
|
5
|
+
* Nothing from the body is logged.
|
|
6
|
+
*/
|
|
7
|
+
import { type PrismaClient } from "#prisma";
|
|
8
|
+
import type { HostEventDb } from "../../core/host-events.js";
|
|
9
|
+
import type { Executor } from "../../providers/executor/types.js";
|
|
10
|
+
import type { ReviewHostRegistry } from "../../providers/review-host/types.js";
|
|
11
|
+
import type { RepoAccessGate } from "../../core/repo-access.js";
|
|
12
|
+
export interface GitHubIngressDeps {
|
|
13
|
+
db: HostEventDb & Pick<PrismaClient, "hostEventDelivery">;
|
|
14
|
+
executor: Executor;
|
|
15
|
+
hosts: ReviewHostRegistry;
|
|
16
|
+
/** Repository authorization for every dispatch, and the mention author's permission. */
|
|
17
|
+
repoAccess: RepoAccessGate;
|
|
18
|
+
webhookSecret: string | undefined;
|
|
19
|
+
appIdentity: () => Promise<{
|
|
20
|
+
id: number;
|
|
21
|
+
slug: string;
|
|
22
|
+
}>;
|
|
23
|
+
now?: () => Date;
|
|
24
|
+
}
|
|
25
|
+
export interface GitHubIngressResult {
|
|
26
|
+
status: number;
|
|
27
|
+
body: Record<string, unknown>;
|
|
28
|
+
/** Cosmetic follow-ups (reactions) to run after the response is sent. */
|
|
29
|
+
afterResponse?: () => Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export declare function handleGitHubEventIngress(req: {
|
|
32
|
+
headers: Record<string, string | undefined>;
|
|
33
|
+
rawBody: string;
|
|
34
|
+
}, deps: GitHubIngressDeps): Promise<GitHubIngressResult>;
|
|
35
|
+
/** Test-only: reset the prune clock. */
|
|
36
|
+
export declare function resetPruneClockForTests(): void;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub App webhook ingress — framework-agnostic, mounted unauthenticated
|
|
3
|
+
* (by OAuth) in streamable-http.ts at /hosts/github/events. Authenticated by
|
|
4
|
+
* the App's webhook secret (X-Hub-Signature-256) over the raw body.
|
|
5
|
+
* Nothing from the body is logged.
|
|
6
|
+
*/
|
|
7
|
+
import { Prisma } from "#prisma";
|
|
8
|
+
import { routeHostEvent } from "../../core/host-events.js";
|
|
9
|
+
import { logger } from "../../core/logger.js";
|
|
10
|
+
import { normalizeGitHubEvent, verifyGitHubSignature } from "../../providers/review-host/github-events.js";
|
|
11
|
+
const log = logger.child({ module: "github-ingress" });
|
|
12
|
+
const DELIVERY_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
13
|
+
const PRUNE_INTERVAL_MS = 60 * 60 * 1000;
|
|
14
|
+
const SAFE_DELIVERY = /^[A-Za-z0-9-]{1,100}$/;
|
|
15
|
+
let lastPruneAt = 0;
|
|
16
|
+
export async function handleGitHubEventIngress(req, deps) {
|
|
17
|
+
if (!deps.webhookSecret)
|
|
18
|
+
return { status: 404, body: { error: "not_found" } };
|
|
19
|
+
if (!verifyGitHubSignature(req.rawBody, req.headers["x-hub-signature-256"], deps.webhookSecret)) {
|
|
20
|
+
return { status: 401, body: { error: "invalid_signature" } };
|
|
21
|
+
}
|
|
22
|
+
const deliveryId = req.headers["x-github-delivery"];
|
|
23
|
+
const eventName = req.headers["x-github-event"];
|
|
24
|
+
if (!deliveryId || !SAFE_DELIVERY.test(deliveryId) || !eventName) {
|
|
25
|
+
return { status: 400, body: { error: "missing_delivery_headers" } };
|
|
26
|
+
}
|
|
27
|
+
const now = (deps.now ?? (() => new Date()))();
|
|
28
|
+
let payload;
|
|
29
|
+
try {
|
|
30
|
+
payload = JSON.parse(req.rawBody);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return { status: 400, body: { error: "invalid_json" } };
|
|
34
|
+
}
|
|
35
|
+
const app = await deps.appIdentity();
|
|
36
|
+
const event = normalizeGitHubEvent(eventName, payload, app);
|
|
37
|
+
// An event we don't act on must never claim the delivery id: recording it
|
|
38
|
+
// here would let a later, real delivery of the same id (GitHub does reuse
|
|
39
|
+
// ids across distinct redeliveries of what it considers the same event)
|
|
40
|
+
// be silently swallowed as a "duplicate" of something we never routed.
|
|
41
|
+
if (!event)
|
|
42
|
+
return { status: 202, body: { ignored: true } };
|
|
43
|
+
try {
|
|
44
|
+
await deps.db.hostEventDelivery.create({ data: { provider: "github", deliveryId } });
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
|
|
48
|
+
return { status: 202, body: { duplicate: true } };
|
|
49
|
+
}
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
if (now.getTime() - lastPruneAt > PRUNE_INTERVAL_MS) {
|
|
53
|
+
lastPruneAt = now.getTime();
|
|
54
|
+
await deps.db.hostEventDelivery
|
|
55
|
+
.deleteMany({ where: { receivedAt: { lt: new Date(now.getTime() - DELIVERY_RETENTION_MS) } } })
|
|
56
|
+
.catch((err) => log.warn({ err }, "delivery prune failed"));
|
|
57
|
+
}
|
|
58
|
+
// The delivery row is recorded before routing so a duplicate can never
|
|
59
|
+
// race past it, but that means a transient failure inside routeHostEvent
|
|
60
|
+
// must not leave a delivery permanently marked "done" — GitHub's redelivery
|
|
61
|
+
// would then hit our P2002 dedup check and the event would be lost for
|
|
62
|
+
// good. On failure here, un-record the delivery and rethrow so the retry
|
|
63
|
+
// (ours or GitHub's redelivery) is routed for real, not reported duplicate.
|
|
64
|
+
try {
|
|
65
|
+
const routed = await routeHostEvent(event, {
|
|
66
|
+
db: deps.db,
|
|
67
|
+
executor: deps.executor,
|
|
68
|
+
hosts: deps.hosts,
|
|
69
|
+
repoAccess: deps.repoAccess,
|
|
70
|
+
mentionHandle: app.slug,
|
|
71
|
+
});
|
|
72
|
+
log.info({ event: event.kind, repository: event.repository, deliveryId, runIds: routed.runIds }, "host event routed");
|
|
73
|
+
return {
|
|
74
|
+
status: 202,
|
|
75
|
+
body: { runIds: routed.runIds },
|
|
76
|
+
afterResponse: async () => {
|
|
77
|
+
for (const followUp of routed.followUps)
|
|
78
|
+
await followUp();
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
await deps.db.hostEventDelivery
|
|
84
|
+
.deleteMany({ where: { provider: "github", deliveryId } })
|
|
85
|
+
.catch((delErr) => log.warn({ err: delErr }, "could not roll back the delivery record after a routing failure"));
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Test-only: reset the prune clock. */
|
|
90
|
+
export function resetPruneClockForTests() {
|
|
91
|
+
lastPruneAt = 0;
|
|
92
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser end of link_host_account: GitHub redirects here after the user
|
|
3
|
+
* authorizes the App (GET /hosts/github/user-callback?code&state). Mounted in
|
|
4
|
+
* streamable-http.ts behind its canonical-Host check, and only when the App's
|
|
5
|
+
* OAuth client credentials are configured. It shows the one-time
|
|
6
|
+
* confirmation code the initiating principal must submit over MCP, never a
|
|
7
|
+
* token. No script, no forms, no framing, nothing cached, no referrer.
|
|
8
|
+
*/
|
|
9
|
+
import type { ServerResponse } from "node:http";
|
|
10
|
+
import type { PrismaClient } from "#prisma";
|
|
11
|
+
import type { HostUserAuthorizer } from "../../providers/review-host/types.js";
|
|
12
|
+
export interface HostUserCallbackDeps {
|
|
13
|
+
db: Pick<PrismaClient, "hostIdentityLinkRequest">;
|
|
14
|
+
authorizer: HostUserAuthorizer;
|
|
15
|
+
/** Exactly the redirect_uri the authorize URL carried (the App's registered Callback URL). */
|
|
16
|
+
redirectUri: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function handleHostUserCallback(url: URL, res: ServerResponse, deps: HostUserCallbackDeps): Promise<void>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { completeHostIdentityCallback } from "../../core/host-identity-links.js";
|
|
2
|
+
import { PAGE_STYLE } from "../shared/page-style.js";
|
|
3
|
+
function escape(value) {
|
|
4
|
+
return value.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
5
|
+
}
|
|
6
|
+
function page(res, status, title, body) {
|
|
7
|
+
res.setHeader("content-security-policy", "default-src 'none'; style-src 'unsafe-inline'; form-action 'none'; frame-ancestors 'none'; base-uri 'none'");
|
|
8
|
+
res.setHeader("cache-control", "no-store");
|
|
9
|
+
res.setHeader("referrer-policy", "no-referrer");
|
|
10
|
+
res.setHeader("x-frame-options", "DENY");
|
|
11
|
+
res.setHeader("x-content-type-options", "nosniff");
|
|
12
|
+
res
|
|
13
|
+
.writeHead(status, { "content-type": "text/html; charset=utf-8" })
|
|
14
|
+
.end(`<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">` +
|
|
15
|
+
`<title>wardby: ${escape(title)}</title><style>${PAGE_STYLE}</style><body><div class="card"><div class="kicker">wardby</div>` +
|
|
16
|
+
`<h1>${escape(title)}</h1>${body}</div></body></html>`);
|
|
17
|
+
}
|
|
18
|
+
export async function handleHostUserCallback(url, res, deps) {
|
|
19
|
+
const error = url.searchParams.get("error");
|
|
20
|
+
if (error !== null) {
|
|
21
|
+
if (error === "access_denied") {
|
|
22
|
+
page(res, 200, "Linking cancelled", "<p>You cancelled on GitHub. Nothing was linked; you can close this page.</p>");
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
page(res, 400, "Linking failed", "<p>GitHub did not authorize the link. Start again from your MCP client.</p>");
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const outcome = await completeHostIdentityCallback({
|
|
30
|
+
db: deps.db,
|
|
31
|
+
authorizer: deps.authorizer,
|
|
32
|
+
state: url.searchParams.get("state"),
|
|
33
|
+
code: url.searchParams.get("code"),
|
|
34
|
+
redirectUri: deps.redirectUri,
|
|
35
|
+
});
|
|
36
|
+
if (outcome.kind === "invalid") {
|
|
37
|
+
page(res, 400, "Link not valid", "<p>This link is invalid or has expired. Start again from your MCP client with <code>link_host_account</code>.</p>");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (outcome.kind === "failed") {
|
|
41
|
+
page(res, 502, "Linking failed", "<p>GitHub did not complete the sign-in. Start again from your MCP client.</p>");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
page(res, 200, "Confirm the link", `<p>Signed in to GitHub as <strong>@${escape(outcome.login)}</strong>, to be linked to the wardby account ` +
|
|
45
|
+
`<strong>${escape(outcome.subject)}</strong>.</p>` +
|
|
46
|
+
`<p>Give your MCP client this code (call <code>link_host_account</code> with <code>confirmationCode</code>):</p>` +
|
|
47
|
+
`<p style="font-size:2em;font-family:monospace;letter-spacing:.1em"><strong>${escape(outcome.code)}</strong></p>` +
|
|
48
|
+
`<p>It works once, within 10 minutes. <strong>If you did not start this, close this page</strong> and do not ` +
|
|
49
|
+
`share the code: someone may be trying to use your GitHub account's access.</p>`);
|
|
50
|
+
}
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { type WardbyMcpServer } from "./server.js";
|
|
2
|
-
import type { McpProviders } from "./context.js";
|
|
2
|
+
import type { McpProviders, McpRequestContext } from "./context.js";
|
|
3
|
+
import type { Principal, PrismaClient } from "#prisma";
|
|
3
4
|
import { type SecretElicitationUrlBuilder } from "./tools/secrets.js";
|
|
5
|
+
/**
|
|
6
|
+
* stdio's one fixed identity: the local operator (LOCAL_PRINCIPAL), trusted
|
|
7
|
+
* with every supported scope and every role — whoever can start the
|
|
8
|
+
* process already holds the database and its keys.
|
|
9
|
+
*/
|
|
10
|
+
export declare function localOperatorContext(principal: Principal, providers: McpProviders, db: PrismaClient): McpRequestContext;
|
|
4
11
|
/** Registers the full Phase 4 tool surface — every module, in one place. */
|
|
5
12
|
export declare function registerAllTools(mcp: WardbyMcpServer, opts: {
|
|
6
13
|
secretElicitationUrl: SecretElicitationUrlBuilder;
|