@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
package/dist/mcp/tools/agents.js
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Agent CRUD with scope checks in server.ts and access checks here
|
|
3
|
+
* (auth/access.ts: owner, or a grant of read < execute < write). Owner-only
|
|
4
|
+
* whatever the grants: delete_agent and repository changes; make_owner is
|
|
5
|
+
* the agents:admin escape hatch. See
|
|
6
|
+
* docs/private/2026-09-26-resource-sharing-grants-spec-and-plan.md.
|
|
7
|
+
*/
|
|
2
8
|
import { Prisma } from "#prisma";
|
|
3
9
|
import { z } from "zod";
|
|
4
10
|
import { CodingProfilePatchSchema, CodingProfileSchema } from "../../coding/profile.js";
|
|
5
11
|
import { codingProviderSupportsModel } from "../../coding/provider.js";
|
|
6
12
|
import { validateCronExpression } from "../../core/cron.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
13
|
+
import { modelSupportedEfforts } from "../../providers/llm/routing.js";
|
|
14
|
+
import { LLM_EFFORT_LEVELS, isLlmEffort } from "../../providers/llm/types.js";
|
|
15
|
+
import { requireReadableBudgetGroup } from "../auth/ownership.js";
|
|
16
|
+
import { agentAccessResolver, agentNotFound, assertAgentAccess, readableAgentsWhere, requireAgentAccess, } from "../auth/access.js";
|
|
17
|
+
import { EVERYONE_KEY, atLeast, canDelegate, deleteGrantsFor, effectiveAccess } from "../../core/grants.js";
|
|
18
|
+
import { projectTool } from "./tools.js";
|
|
19
|
+
import { requireAnyScope, requireScope } from "../auth/resource-server.js";
|
|
20
|
+
import { logger } from "../../core/logger.js";
|
|
21
|
+
import { authorizeRepositoryForSet } from "../auth/repo-authorization.js";
|
|
9
22
|
import { McpError } from "../errors.js";
|
|
10
23
|
import { textResult } from "./text-result.js";
|
|
11
24
|
// workerImageRef is the BYO-arbitrary-image escape hatch (see
|
|
@@ -15,6 +28,18 @@ import { textResult } from "./text-result.js";
|
|
|
15
28
|
function requireWorkerImageRefScope(ctx) {
|
|
16
29
|
requireScope(ctx, ctx.canonicalUri, "agents:admin");
|
|
17
30
|
}
|
|
31
|
+
// A debug trace makes the worker log prompts and repository content to its
|
|
32
|
+
// pod log, so turning it on (or off) is an admin action, gated like
|
|
33
|
+
// workerImageRef and audited.
|
|
34
|
+
function requireDebugTraceScope(ctx) {
|
|
35
|
+
requireScope(ctx, ctx.canonicalUri, "agents:admin");
|
|
36
|
+
}
|
|
37
|
+
const agentsLog = logger.child({ module: "mcp-agents" });
|
|
38
|
+
const MAX_DEBUG_TRACE_MINUTES = 24 * 60;
|
|
39
|
+
/** Package allowlists widen what a run may download, so they need their own approval. */
|
|
40
|
+
function requirePackageApproval(ctx) {
|
|
41
|
+
requireAnyScope(ctx, ctx.canonicalUri, "packages:approve", "agents:admin");
|
|
42
|
+
}
|
|
18
43
|
const MAX_AGENT_NAME_CHARS = 200;
|
|
19
44
|
const MAX_SYSTEM_PROMPT_CHARS = 64 * 1024;
|
|
20
45
|
const MAX_MODEL_CHARS = 128;
|
|
@@ -31,6 +56,7 @@ const agentFields = {
|
|
|
31
56
|
kind: z.enum(["native", "coding"]),
|
|
32
57
|
budgetGroupId: z.string().min(1).max(128),
|
|
33
58
|
memoryEnabled: z.boolean(),
|
|
59
|
+
effort: z.enum(LLM_EFFORT_LEVELS),
|
|
34
60
|
};
|
|
35
61
|
const CreateAgentSchema = z
|
|
36
62
|
.object({
|
|
@@ -45,7 +71,9 @@ const CreateAgentSchema = z
|
|
|
45
71
|
kind: agentFields.kind.default("native"),
|
|
46
72
|
budgetGroupId: agentFields.budgetGroupId.optional(),
|
|
47
73
|
memoryEnabled: agentFields.memoryEnabled.default(false),
|
|
74
|
+
effort: agentFields.effort.optional(),
|
|
48
75
|
codingProfile: CodingProfileSchema.optional(),
|
|
76
|
+
repositoryAdminOverride: z.boolean().optional(),
|
|
49
77
|
})
|
|
50
78
|
.strict()
|
|
51
79
|
.superRefine((value, ctx) => {
|
|
@@ -91,21 +119,47 @@ const UpdateAgentSchema = z
|
|
|
91
119
|
kind: agentFields.kind.optional(),
|
|
92
120
|
budgetGroupId: agentFields.budgetGroupId.nullable().optional(),
|
|
93
121
|
memoryEnabled: agentFields.memoryEnabled.optional(),
|
|
94
|
-
|
|
122
|
+
effort: agentFields.effort.nullable().optional(),
|
|
123
|
+
codingProfile: CodingProfilePatchSchema.extend({
|
|
124
|
+
/** Not a stored profile field: sets CodingAgentProfile.debugTraceUntil = now + minutes (null clears). */
|
|
125
|
+
debugTraceMinutes: z.number().int().min(1).max(MAX_DEBUG_TRACE_MINUTES).nullable().optional(),
|
|
126
|
+
}).optional(),
|
|
127
|
+
repositoryAdminOverride: z.boolean().optional(),
|
|
95
128
|
})
|
|
96
129
|
.strict();
|
|
130
|
+
const REPOSITORY_ADMIN_OVERRIDE = {
|
|
131
|
+
type: "boolean",
|
|
132
|
+
description: "Admins only (agents:admin with the admin role): approve codingProfile.repository without checking GitHub access, recorded as an admin approval. On an agent the admin doesn't own, only codingProfile.repository may change.",
|
|
133
|
+
};
|
|
134
|
+
/** The profile columns a repository authorization is stamped into. */
|
|
135
|
+
function profileStamp(authorization) {
|
|
136
|
+
return {
|
|
137
|
+
repositoryAuthorizedVia: authorization.authorizedVia,
|
|
138
|
+
repositoryAuthorizedById: authorization.authorizedById,
|
|
139
|
+
repositoryAuthorizedAt: authorization.authorizedAt,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
97
142
|
const profileJsonSchema = {
|
|
98
143
|
type: "object",
|
|
99
144
|
additionalProperties: false,
|
|
100
145
|
properties: {
|
|
101
146
|
provider: { type: "string", enum: ["codex", "claude-code"] },
|
|
102
|
-
repository: {
|
|
147
|
+
repository: {
|
|
148
|
+
type: "string",
|
|
149
|
+
description: "owner/name on GitHub. Your linked GitHub account (link_host_account) must have write access to it, unless a wardby admin approves it (repositoryAdminOverride). Re-checked on every run.",
|
|
150
|
+
},
|
|
103
151
|
baseRef: { type: "string" },
|
|
104
152
|
defaultTask: { type: ["string", "null"] },
|
|
105
153
|
allowWebhookTaskOverride: { type: "boolean" },
|
|
106
154
|
timeoutSec: { type: "integer", minimum: 60, maximum: 7200 },
|
|
107
|
-
|
|
108
|
-
|
|
155
|
+
protectedPaths: {
|
|
156
|
+
type: "array",
|
|
157
|
+
minItems: 1,
|
|
158
|
+
maxItems: 128,
|
|
159
|
+
items: { type: "string" },
|
|
160
|
+
description: 'Glob patterns (*, **, ?) a run may not change. A leading ! makes an exception, a literal file path without wildcards: ["docs/**", "!docs/changelog.md"] protects docs/ except the changelog. Every run also protects .wardby/** except .wardby/services.yaml, whatever this list says; exceptions cannot unprotect .wardby/.',
|
|
161
|
+
},
|
|
162
|
+
collectExclude: { type: "array", maxItems: 64, items: { type: "string" } },
|
|
109
163
|
toolchain: { type: "string", enum: ["node", "node-python"] },
|
|
110
164
|
toolchainVersion: { type: ["string", "null"] },
|
|
111
165
|
workerImageRef: { type: ["string", "null"] },
|
|
@@ -115,6 +169,22 @@ const profileJsonSchema = {
|
|
|
115
169
|
maximum: 32768,
|
|
116
170
|
description: "Workspace disk size in MiB. Null uses the deployment default (CODING_DISK_MB), capped by the operator's CODING_MAX_DISK_MB.",
|
|
117
171
|
},
|
|
172
|
+
packageAllowlist: {
|
|
173
|
+
type: "object",
|
|
174
|
+
description: "Approved top-level packages per ecosystem (npm, pypi). Needs packages:approve or agents:admin.",
|
|
175
|
+
additionalProperties: { type: "array", maxItems: 256, items: { type: "string" } },
|
|
176
|
+
},
|
|
177
|
+
packagePolicy: {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties: { minReleaseAgeDays: { type: "integer", minimum: 0, maximum: 30 } },
|
|
180
|
+
additionalProperties: false,
|
|
181
|
+
},
|
|
182
|
+
services: {
|
|
183
|
+
type: "array",
|
|
184
|
+
maxItems: 16,
|
|
185
|
+
items: { type: "string" },
|
|
186
|
+
description: 'Coding-run services this agent\'s runs may start: catalog names such as "postgres" (any version the catalog has). A repository asks for them in .wardby/services.yaml on its base branch; a run asking for one not listed here is refused. Empty = none.',
|
|
187
|
+
},
|
|
118
188
|
},
|
|
119
189
|
};
|
|
120
190
|
function invalidArguments(label, error) {
|
|
@@ -143,6 +213,24 @@ function validateSchedule(schedule, timezone) {
|
|
|
143
213
|
throw new McpError(400, `Invalid schedule/timezone: ${error instanceof Error ? error.message : String(error)}`);
|
|
144
214
|
}
|
|
145
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Effort only drives the native engine, and only on a model that accepts the
|
|
218
|
+
* level; reject rather than store a setting that would silently do nothing.
|
|
219
|
+
*/
|
|
220
|
+
function validateEffort(kind, model, effort) {
|
|
221
|
+
if (effort == null)
|
|
222
|
+
return;
|
|
223
|
+
if (kind !== "native") {
|
|
224
|
+
throw new McpError(400, "effort is only valid for native agents; coding agents do not use it.");
|
|
225
|
+
}
|
|
226
|
+
const accepted = modelSupportedEfforts(model);
|
|
227
|
+
if (!isLlmEffort(effort) || !accepted.includes(effort)) {
|
|
228
|
+
throw new McpError(400, `Model "${model}" does not accept effort "${effort}". ` +
|
|
229
|
+
(accepted.length > 0
|
|
230
|
+
? `Accepted levels: ${accepted.join(", ")}.`
|
|
231
|
+
: "It accepts no effort setting; leave effort unset (or null)."));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
146
234
|
function storedProfile(profile) {
|
|
147
235
|
return CodingProfileSchema.parse({
|
|
148
236
|
provider: profile.provider,
|
|
@@ -151,14 +239,27 @@ function storedProfile(profile) {
|
|
|
151
239
|
defaultTask: profile.defaultTask,
|
|
152
240
|
allowWebhookTaskOverride: profile.allowWebhookTaskOverride,
|
|
153
241
|
timeoutSec: profile.timeoutSec,
|
|
154
|
-
allowedEgress: profile.allowedEgress,
|
|
155
242
|
protectedPaths: profile.protectedPaths,
|
|
243
|
+
collectExclude: profile.collectExclude,
|
|
156
244
|
toolchain: profile.toolchain,
|
|
157
245
|
toolchainVersion: profile.toolchainVersion,
|
|
158
246
|
workerImageRef: profile.workerImageRef,
|
|
159
247
|
workspaceDiskMb: profile.workspaceDiskMb,
|
|
248
|
+
packageAllowlist: profile.packageAllowlist,
|
|
249
|
+
packagePolicy: profile.packagePolicy,
|
|
250
|
+
services: profile.services,
|
|
160
251
|
});
|
|
161
252
|
}
|
|
253
|
+
/** A service name an agent is allowed must exist in the catalog (any version); typos fail here, not at a run. */
|
|
254
|
+
async function requireCatalogServiceNames(db, names) {
|
|
255
|
+
if (names.length === 0)
|
|
256
|
+
return;
|
|
257
|
+
const known = new Set((await db.codingService.findMany({ where: { name: { in: [...names] } }, select: { name: true } })).map((row) => row.name));
|
|
258
|
+
const unknown = names.filter((name) => !known.has(name));
|
|
259
|
+
if (unknown.length > 0) {
|
|
260
|
+
throw new McpError(400, `wardby's service catalog has no service named ${unknown.map((name) => `"${name}"`).join(", ")}; list_services shows the catalog.`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
162
263
|
export function registerAgentTools(mcp) {
|
|
163
264
|
mcp.registerTool({
|
|
164
265
|
name: "create_agent",
|
|
@@ -178,7 +279,13 @@ export function registerAgentTools(mcp) {
|
|
|
178
279
|
kind: { type: "string", enum: ["native", "coding"] },
|
|
179
280
|
budgetGroupId: { type: "string" },
|
|
180
281
|
memoryEnabled: { type: "boolean" },
|
|
282
|
+
effort: {
|
|
283
|
+
type: "string",
|
|
284
|
+
enum: [...LLM_EFFORT_LEVELS],
|
|
285
|
+
description: "Reasoning effort for native agents. Unset uses the provider default.",
|
|
286
|
+
},
|
|
181
287
|
codingProfile: { ...profileJsonSchema, required: ["repository"] },
|
|
288
|
+
repositoryAdminOverride: REPOSITORY_ADMIN_OVERRIDE,
|
|
182
289
|
},
|
|
183
290
|
required: ["name", "systemPrompt", "model", "budgetUsd"],
|
|
184
291
|
},
|
|
@@ -186,16 +293,39 @@ export function registerAgentTools(mcp) {
|
|
|
186
293
|
const args = parseCreateAgent(rawArgs);
|
|
187
294
|
if (args.codingProfile?.workerImageRef != null)
|
|
188
295
|
requireWorkerImageRefScope(ctx);
|
|
296
|
+
const packages = args.codingProfile;
|
|
297
|
+
if (packages &&
|
|
298
|
+
(Object.keys(packages.packageAllowlist ?? {}).length > 0 ||
|
|
299
|
+
Object.keys(packages.packagePolicy ?? {}).length > 0))
|
|
300
|
+
requirePackageApproval(ctx);
|
|
189
301
|
validateSchedule(args.schedule, args.timezone ?? "UTC");
|
|
302
|
+
validateEffort(args.kind, args.model, args.effort);
|
|
303
|
+
if (args.codingProfile)
|
|
304
|
+
await requireCatalogServiceNames(ctx.db, args.codingProfile.services);
|
|
190
305
|
if (args.budgetGroupId) {
|
|
191
306
|
await requireReadableBudgetGroup(ctx.db, args.budgetGroupId, ctx.principal.id);
|
|
192
307
|
}
|
|
193
|
-
const { codingProfile, ...agentData } = args;
|
|
308
|
+
const { codingProfile, repositoryAdminOverride, ...agentData } = args;
|
|
309
|
+
if (repositoryAdminOverride === true && !codingProfile) {
|
|
310
|
+
throw new McpError(400, "repositoryAdminOverride only applies with a codingProfile repository.");
|
|
311
|
+
}
|
|
312
|
+
// The creator is the owner, so it's the creator's GitHub access that counts.
|
|
313
|
+
const authorization = codingProfile
|
|
314
|
+
? await authorizeRepositoryForSet(ctx, {
|
|
315
|
+
ownerId: ctx.principal.id,
|
|
316
|
+
provider: "github",
|
|
317
|
+
repository: codingProfile.repository,
|
|
318
|
+
kind: "coding",
|
|
319
|
+
adminOverride: repositoryAdminOverride === true,
|
|
320
|
+
})
|
|
321
|
+
: null;
|
|
194
322
|
const agent = await ctx.db.agent.create({
|
|
195
323
|
data: {
|
|
196
324
|
...agentData,
|
|
197
325
|
ownerId: ctx.principal.id,
|
|
198
|
-
...(codingProfile
|
|
326
|
+
...(codingProfile && authorization
|
|
327
|
+
? { codingProfile: { create: { ...codingProfile, ...profileStamp(authorization) } } }
|
|
328
|
+
: {}),
|
|
199
329
|
},
|
|
200
330
|
include: { codingProfile: true },
|
|
201
331
|
});
|
|
@@ -221,7 +351,24 @@ export function registerAgentTools(mcp) {
|
|
|
221
351
|
kind: { type: "string", enum: ["native", "coding"] },
|
|
222
352
|
budgetGroupId: { type: ["string", "null"] },
|
|
223
353
|
memoryEnabled: { type: "boolean" },
|
|
224
|
-
|
|
354
|
+
effort: {
|
|
355
|
+
type: ["string", "null"],
|
|
356
|
+
enum: [...LLM_EFFORT_LEVELS, null],
|
|
357
|
+
description: "Reasoning effort for native agents. Null clears it back to the provider default.",
|
|
358
|
+
},
|
|
359
|
+
codingProfile: {
|
|
360
|
+
...profileJsonSchema,
|
|
361
|
+
properties: {
|
|
362
|
+
...profileJsonSchema.properties,
|
|
363
|
+
debugTraceMinutes: {
|
|
364
|
+
type: ["integer", "null"],
|
|
365
|
+
minimum: 1,
|
|
366
|
+
maximum: MAX_DEBUG_TRACE_MINUTES,
|
|
367
|
+
description: "Admins only (agents:admin): trace every coding run of this agent for this many minutes from now; the worker writes its full stream trace, which can include prompts and repository content, to the run pod's log. Null turns it off.",
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
repositoryAdminOverride: REPOSITORY_ADMIN_OVERRIDE,
|
|
225
372
|
},
|
|
226
373
|
required: ["id"],
|
|
227
374
|
},
|
|
@@ -229,18 +376,105 @@ export function registerAgentTools(mcp) {
|
|
|
229
376
|
const args = parseUpdateAgent(rawArgs);
|
|
230
377
|
if (args.codingProfile?.workerImageRef !== undefined)
|
|
231
378
|
requireWorkerImageRefScope(ctx);
|
|
379
|
+
const debugTraceMinutes = args.codingProfile?.debugTraceMinutes;
|
|
380
|
+
if (debugTraceMinutes !== undefined)
|
|
381
|
+
requireDebugTraceScope(ctx);
|
|
382
|
+
if (args.codingProfile?.packageAllowlist !== undefined || args.codingProfile?.packagePolicy !== undefined)
|
|
383
|
+
requirePackageApproval(ctx);
|
|
384
|
+
// Repository authorization calls GitHub, so it runs before (outside) the
|
|
385
|
+
// serializable transaction, on a pre-read; the transaction then verifies
|
|
386
|
+
// the pre-read still holds. Only a changed repository (or becoming a
|
|
387
|
+
// coding agent) is re-authorized; other edits keep the existing stamp.
|
|
388
|
+
const before = await ctx.db.agent.findUnique({ where: { id: args.id }, include: { codingProfile: true } });
|
|
389
|
+
if (!before)
|
|
390
|
+
throw agentNotFound(args.id);
|
|
391
|
+
// Strict: a repository is a binding, so only the agent's owner sets
|
|
392
|
+
// one (or an admin's explicit approval, below). Not a write-grantee,
|
|
393
|
+
// not the stdio operator.
|
|
394
|
+
const isOwner = before.ownerId !== null && before.ownerId === ctx.principal.id;
|
|
395
|
+
// An admin may approve a repository (repositoryAdminOverride) on an agent
|
|
396
|
+
// they don't own, but change nothing else on it.
|
|
397
|
+
const adminEdit = args.repositoryAdminOverride === true && !isOwner;
|
|
398
|
+
if (adminEdit) {
|
|
399
|
+
requireScope(ctx, ctx.canonicalUri, "agents:admin");
|
|
400
|
+
const { id: _id, repositoryAdminOverride: _flag, codingProfile, ...others } = args;
|
|
401
|
+
const profileKeys = Object.keys(codingProfile ?? {});
|
|
402
|
+
if (Object.values(others).some((v) => v !== undefined) ||
|
|
403
|
+
profileKeys.length !== 1 ||
|
|
404
|
+
profileKeys[0] !== "repository") {
|
|
405
|
+
throw new McpError(403, "On an agent you don't own, an admin may change only codingProfile.repository (with repositoryAdminOverride).");
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
await assertAgentAccess(ctx, before, args.id, "write");
|
|
410
|
+
}
|
|
411
|
+
const repositoryAfter = (kind, profile) => kind === "coding" ? (args.codingProfile?.repository ?? profile?.repository ?? null) : null;
|
|
412
|
+
const plannedRepository = repositoryAfter(args.kind ?? before.kind, before.codingProfile);
|
|
413
|
+
const repositoryChanges = (existing) => {
|
|
414
|
+
const next = repositoryAfter(args.kind ?? existing.kind, existing.codingProfile);
|
|
415
|
+
return next !== null && (existing.kind !== "coding" || existing.codingProfile?.repository !== next);
|
|
416
|
+
};
|
|
417
|
+
const refuseRepositoryBinding = () => {
|
|
418
|
+
throw new McpError(403, `Agent "${args.id}": only its owner can give it a repository (codingProfile.repository, or becoming a coding agent); a repository is a binding, not shared config.`);
|
|
419
|
+
};
|
|
420
|
+
if (!adminEdit && !isOwner && repositoryChanges(before))
|
|
421
|
+
refuseRepositoryBinding();
|
|
422
|
+
// The coding profile as a whole (task, base ref, protected paths,
|
|
423
|
+
// task-override opt-in, image, packages, limits...), leaving coding,
|
|
424
|
+
// and budget-group membership direct what runs do with the owner's
|
|
425
|
+
// repository access and money: owner-only, like the repository itself
|
|
426
|
+
// (review I1/M1/M6). Write keeps name, prompt, model, budget amount,
|
|
427
|
+
// turns, effort, memory and schedule.
|
|
428
|
+
const ownerOnlyChange = (existingKind) => args.codingProfile !== undefined ||
|
|
429
|
+
(args.kind !== undefined && args.kind !== existingKind) ||
|
|
430
|
+
args.budgetGroupId !== undefined;
|
|
431
|
+
const refuseOwnerOnly = () => {
|
|
432
|
+
throw new McpError(403, `Agent "${args.id}": only its owner can change its coding profile, kind or budget group.`);
|
|
433
|
+
};
|
|
434
|
+
if (!adminEdit && !isOwner && ownerOnlyChange(before.kind))
|
|
435
|
+
refuseOwnerOnly();
|
|
436
|
+
const authorization = plannedRepository !== null && repositoryChanges(before)
|
|
437
|
+
? await authorizeRepositoryForSet(ctx, {
|
|
438
|
+
ownerId: before.ownerId,
|
|
439
|
+
provider: "github",
|
|
440
|
+
repository: plannedRepository,
|
|
441
|
+
kind: "coding",
|
|
442
|
+
adminOverride: args.repositoryAdminOverride === true,
|
|
443
|
+
})
|
|
444
|
+
: null;
|
|
445
|
+
if (args.repositoryAdminOverride === true && !authorization) {
|
|
446
|
+
throw new McpError(400, "repositoryAdminOverride only applies when the coding repository changes.");
|
|
447
|
+
}
|
|
448
|
+
const debugTraceUntil = debugTraceMinutes === undefined
|
|
449
|
+
? undefined
|
|
450
|
+
: debugTraceMinutes === null
|
|
451
|
+
? null
|
|
452
|
+
: new Date(Date.now() + debugTraceMinutes * 60_000);
|
|
232
453
|
const agent = await ctx.db.$transaction(async (tx) => {
|
|
233
454
|
const existing = await tx.agent.findUnique({ where: { id: args.id }, include: { codingProfile: true } });
|
|
234
455
|
if (!existing)
|
|
235
|
-
throw
|
|
236
|
-
|
|
456
|
+
throw agentNotFound(args.id);
|
|
457
|
+
if (!adminEdit) {
|
|
458
|
+
await assertAgentAccess(ctx, existing, args.id, "write", tx);
|
|
459
|
+
if (!isOwner && repositoryChanges(existing))
|
|
460
|
+
refuseRepositoryBinding();
|
|
461
|
+
if (!isOwner && ownerOnlyChange(existing.kind))
|
|
462
|
+
refuseOwnerOnly();
|
|
463
|
+
}
|
|
464
|
+
if (existing.ownerId !== before.ownerId ||
|
|
465
|
+
(repositoryChanges(existing) &&
|
|
466
|
+
(!authorization ||
|
|
467
|
+
repositoryAfter(args.kind ?? existing.kind, existing.codingProfile) !== plannedRepository))) {
|
|
468
|
+
throw new McpError(409, `Agent "${args.id}" changed while it was being updated; try again.`);
|
|
469
|
+
}
|
|
237
470
|
if (args.budgetGroupId) {
|
|
238
|
-
await requireReadableBudgetGroup(
|
|
471
|
+
await requireReadableBudgetGroup(tx, args.budgetGroupId, ctx.principal.id);
|
|
239
472
|
}
|
|
240
473
|
const nextKind = args.kind ?? existing.kind;
|
|
241
474
|
if (nextKind === "native" && args.codingProfile) {
|
|
242
475
|
throw new McpError(400, "A coding profile is only valid for coding agents.");
|
|
243
476
|
}
|
|
477
|
+
validateEffort(nextKind, args.model ?? existing.model, args.effort !== undefined ? args.effort : existing.effort);
|
|
244
478
|
let nextProfile = null;
|
|
245
479
|
if (nextKind === "coding") {
|
|
246
480
|
const attachedTools = await tx.agentTool.count({ where: { agentId: args.id } });
|
|
@@ -248,10 +482,13 @@ export function registerAgentTools(mcp) {
|
|
|
248
482
|
throw new McpError(400, "Detach all native sandbox tools before changing an agent to coding.");
|
|
249
483
|
}
|
|
250
484
|
const currentProfile = existing.codingProfile ? storedProfile(existing.codingProfile) : undefined;
|
|
251
|
-
const
|
|
485
|
+
const { debugTraceMinutes: _minutes, ...profilePatch } = args.codingProfile ?? {};
|
|
486
|
+
const profileResult = CodingProfileSchema.safeParse({ ...currentProfile, ...profilePatch });
|
|
252
487
|
if (!profileResult.success)
|
|
253
488
|
throw invalidArguments("coding profile", profileResult.error);
|
|
254
489
|
nextProfile = profileResult.data;
|
|
490
|
+
if (args.codingProfile?.services !== undefined)
|
|
491
|
+
await requireCatalogServiceNames(tx, nextProfile.services);
|
|
255
492
|
const nextModel = args.model ?? existing.model;
|
|
256
493
|
if (!codingProviderSupportsModel(nextProfile.provider, nextModel)) {
|
|
257
494
|
throw new McpError(400, `Model "${nextModel}" is not supported by coding provider "${nextProfile.provider}".`);
|
|
@@ -264,14 +501,16 @@ export function registerAgentTools(mcp) {
|
|
|
264
501
|
if (nextKind === "coding" && nextSchedule && nextScheduleEnabled && !nextProfile?.defaultTask) {
|
|
265
502
|
throw new McpError(400, "A default task is required for an enabled coding-agent schedule.");
|
|
266
503
|
}
|
|
267
|
-
const { id, codingProfile: _profilePatch, ...updates } = args;
|
|
504
|
+
const { id, codingProfile: _profilePatch, repositoryAdminOverride: _override, ...updates } = args;
|
|
505
|
+
const stamp = authorization ? profileStamp(authorization) : {};
|
|
506
|
+
const trace = debugTraceUntil === undefined ? {} : { debugTraceUntil };
|
|
268
507
|
const profileMutation = nextKind === "native"
|
|
269
508
|
? existing.codingProfile
|
|
270
509
|
? { delete: true }
|
|
271
510
|
: undefined
|
|
272
511
|
: existing.codingProfile
|
|
273
|
-
? { update: nextProfile }
|
|
274
|
-
: { create: nextProfile };
|
|
512
|
+
? { update: { ...nextProfile, ...stamp, ...trace } }
|
|
513
|
+
: { create: { ...nextProfile, ...stamp, ...trace } };
|
|
275
514
|
return tx.agent.update({
|
|
276
515
|
where: { id },
|
|
277
516
|
data: {
|
|
@@ -281,6 +520,14 @@ export function registerAgentTools(mcp) {
|
|
|
281
520
|
include: { codingProfile: true },
|
|
282
521
|
});
|
|
283
522
|
}, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
|
|
523
|
+
if (debugTraceUntil !== undefined) {
|
|
524
|
+
agentsLog.info({
|
|
525
|
+
event: "coding.debug_trace.set",
|
|
526
|
+
agentId: agent.id,
|
|
527
|
+
until: debugTraceUntil?.toISOString() ?? null,
|
|
528
|
+
by: ctx.principal.id,
|
|
529
|
+
}, "coding debug trace set");
|
|
530
|
+
}
|
|
284
531
|
return textResult(agent);
|
|
285
532
|
},
|
|
286
533
|
});
|
|
@@ -289,11 +536,9 @@ export function registerAgentTools(mcp) {
|
|
|
289
536
|
scope: "agents:read",
|
|
290
537
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
291
538
|
handler: async (_args, ctx) => {
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
});
|
|
296
|
-
return textResult(agents);
|
|
539
|
+
const [where, accessOf] = await Promise.all([readableAgentsWhere(ctx), agentAccessResolver(ctx)]);
|
|
540
|
+
const agents = await ctx.db.agent.findMany({ where, include: { codingProfile: true } });
|
|
541
|
+
return textResult(agents.map((agent) => ({ ...agent, access: accessOf(agent) })));
|
|
297
542
|
},
|
|
298
543
|
});
|
|
299
544
|
mcp.registerTool({
|
|
@@ -306,14 +551,20 @@ export function registerAgentTools(mcp) {
|
|
|
306
551
|
additionalProperties: false,
|
|
307
552
|
},
|
|
308
553
|
handler: async (args, ctx) => {
|
|
309
|
-
const agent = await ctx.db.agent.findUnique({
|
|
554
|
+
const { agent, access } = await assertAgentAccess(ctx, await ctx.db.agent.findUnique({
|
|
310
555
|
where: { id: args.id },
|
|
311
|
-
include: { tools: { include: { tool: true } }, codingProfile: true },
|
|
556
|
+
include: { tools: { include: { tool: true } }, codingProfile: true, repositories: true },
|
|
557
|
+
}), args.id, "read");
|
|
558
|
+
// A7: a tool's code and schema are its owner's, whoever can read the
|
|
559
|
+
// agent (the agent's owner included). Capabilities stay visible: config.
|
|
560
|
+
return textResult({
|
|
561
|
+
...agent,
|
|
562
|
+
access,
|
|
563
|
+
tools: agent.tools.map((attachment) => ({
|
|
564
|
+
...attachment,
|
|
565
|
+
tool: projectTool(attachment.tool, ctx.principal.id),
|
|
566
|
+
})),
|
|
312
567
|
});
|
|
313
|
-
if (!agent || !canRead(agent.ownerId, ctx.principal.id)) {
|
|
314
|
-
throw new McpError(404, `Agent "${args.id}" not found.`);
|
|
315
|
-
}
|
|
316
|
-
return textResult(agent);
|
|
317
568
|
},
|
|
318
569
|
});
|
|
319
570
|
mcp.registerTool({
|
|
@@ -326,9 +577,13 @@ export function registerAgentTools(mcp) {
|
|
|
326
577
|
additionalProperties: false,
|
|
327
578
|
},
|
|
328
579
|
handler: async (args, ctx) => {
|
|
329
|
-
await
|
|
580
|
+
await requireAgentAccess(ctx, args.id, "owner");
|
|
330
581
|
try {
|
|
331
|
-
|
|
582
|
+
// Grants have no FK to the resource: removed in the same transaction.
|
|
583
|
+
await ctx.db.$transaction(async (tx) => {
|
|
584
|
+
await deleteGrantsFor(tx, "agent", args.id);
|
|
585
|
+
await tx.agent.delete({ where: { id: args.id } });
|
|
586
|
+
});
|
|
332
587
|
}
|
|
333
588
|
catch (err) {
|
|
334
589
|
// Run.agentId has no onDelete rule, so Postgres refuses to delete an
|
|
@@ -343,33 +598,176 @@ export function registerAgentTools(mcp) {
|
|
|
343
598
|
return textResult({ deleted: args.id });
|
|
344
599
|
},
|
|
345
600
|
});
|
|
346
|
-
// Deliberately bypasses
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
//
|
|
352
|
-
//
|
|
601
|
+
// Deliberately bypasses access checks entirely: that's the point of this
|
|
602
|
+
// tool, the audited agents:admin escape hatch. Gated on agents:admin, a
|
|
603
|
+
// step up from agents:write, so an ordinary caller can never reach it
|
|
604
|
+
// regardless of what they own. The new owner must exist; releasing an
|
|
605
|
+
// agent to owner-less is gone (sharing is done with grants). Everything
|
|
606
|
+
// bound to the agent belongs to its owner, so a transfer drops what the
|
|
607
|
+
// new owner doesn't own (resource-sharing grants spec §3.6, R2-1).
|
|
353
608
|
mcp.registerTool({
|
|
354
609
|
name: "make_owner",
|
|
355
610
|
scope: "agents:admin",
|
|
611
|
+
description: "Admins: gives an agent a new owner (an existing principal id). Secret and datastore bindings the new owner doesn't own are removed; tool capabilities the new owner never granted are suspended until they re-run attach_tool; sub-agent edges the new owner can't delegate are removed; between two owners every grant on the agent is reset (adopting an owner-less agent keeps them). Returns what changed.",
|
|
356
612
|
inputSchema: {
|
|
357
613
|
type: "object",
|
|
358
614
|
additionalProperties: false,
|
|
359
|
-
properties: {
|
|
615
|
+
properties: {
|
|
616
|
+
agentId: { type: "string" },
|
|
617
|
+
ownerId: { type: ["string", "null"] },
|
|
618
|
+
keepEveryoneExecute: {
|
|
619
|
+
type: "boolean",
|
|
620
|
+
description: "When adopting an owner-less agent: keep an everyone grant at execute instead of lowering it to read. Anyone could then run it with the new owner's secrets and capabilities.",
|
|
621
|
+
},
|
|
622
|
+
},
|
|
360
623
|
required: ["agentId", "ownerId"],
|
|
361
624
|
},
|
|
362
625
|
handler: async (args, ctx) => {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
throw new McpError(404, `Agent "${args.agentId}" not found.`);
|
|
366
|
-
if (args.ownerId !== null) {
|
|
367
|
-
const principal = await ctx.db.principal.findUnique({ where: { id: args.ownerId } });
|
|
368
|
-
if (!principal)
|
|
369
|
-
throw new McpError(400, `Principal "${args.ownerId}" not found.`);
|
|
626
|
+
if (args.ownerId === null) {
|
|
627
|
+
throw new McpError(400, "make_owner no longer releases an agent to owner-less; share it with grant_access instead (everyone at execute, or named principals).");
|
|
370
628
|
}
|
|
371
|
-
const
|
|
372
|
-
|
|
629
|
+
const newOwner = args.ownerId;
|
|
630
|
+
const principal = await ctx.db.principal.findUnique({ where: { id: newOwner } });
|
|
631
|
+
if (!principal)
|
|
632
|
+
throw new McpError(400, `Principal "${newOwner}" not found.`);
|
|
633
|
+
const result = await ctx.db.$transaction(async (tx) => {
|
|
634
|
+
const agent = await tx.agent.findUnique({ where: { id: args.agentId }, include: { codingProfile: true } });
|
|
635
|
+
if (!agent)
|
|
636
|
+
throw agentNotFound(args.agentId);
|
|
637
|
+
// Admin and grandfathered repository approvals were granted to the
|
|
638
|
+
// agent under its current owner. Moving it away from that owner
|
|
639
|
+
// turns them into ordinary checks of the next owner's own GitHub
|
|
640
|
+
// access, so an approval never travels with the agent. An
|
|
641
|
+
// owner-less agent getting its first owner keeps them: that is the
|
|
642
|
+
// pre-upgrade step for owner-less agents with repositories.
|
|
643
|
+
const revoked = [];
|
|
644
|
+
const transfer = agent.ownerId !== null && agent.ownerId !== newOwner;
|
|
645
|
+
if (transfer) {
|
|
646
|
+
const approved = { in: ["admin", "grandfathered"] };
|
|
647
|
+
const reset = { authorizedVia: "host_permission", authorizedById: null, authorizedAt: new Date() };
|
|
648
|
+
const links = await tx.agentRepository.findMany({
|
|
649
|
+
where: { agentId: agent.id, authorizedVia: approved },
|
|
650
|
+
orderBy: { repository: "asc" },
|
|
651
|
+
});
|
|
652
|
+
await tx.agentRepository.updateMany({ where: { agentId: agent.id, authorizedVia: approved }, data: reset });
|
|
653
|
+
revoked.push(...links.map((l) => l.repository));
|
|
654
|
+
const profile = await tx.codingAgentProfile.updateMany({
|
|
655
|
+
where: { agentId: agent.id, repositoryAuthorizedVia: approved },
|
|
656
|
+
data: {
|
|
657
|
+
repositoryAuthorizedVia: reset.authorizedVia,
|
|
658
|
+
repositoryAuthorizedById: null,
|
|
659
|
+
repositoryAuthorizedAt: reset.authorizedAt,
|
|
660
|
+
},
|
|
661
|
+
});
|
|
662
|
+
if (profile.count > 0 && agent.codingProfile)
|
|
663
|
+
revoked.push(agent.codingProfile.repository);
|
|
664
|
+
}
|
|
665
|
+
// Grants: between two owners every grant is reset (the new owner
|
|
666
|
+
// decides who shares it); adopting an owner-less agent keeps them,
|
|
667
|
+
// but lowers an everyone grant to read unless keepEveryoneExecute:
|
|
668
|
+
// the agent may regain the new owner's secrets and capabilities
|
|
669
|
+
// (review I3).
|
|
670
|
+
const grantsReset = transfer ? await deleteGrantsFor(tx, "agent", agent.id) : 0;
|
|
671
|
+
let everyoneGrant = null;
|
|
672
|
+
if (agent.ownerId === null) {
|
|
673
|
+
const everyone = await tx.resourceGrant.findFirst({
|
|
674
|
+
where: { resourceType: "agent", resourceId: agent.id, granteeKey: EVERYONE_KEY },
|
|
675
|
+
});
|
|
676
|
+
if (everyone) {
|
|
677
|
+
everyoneGrant = { before: everyone.level, after: everyone.level };
|
|
678
|
+
if (everyone.level !== "read" && args.keepEveryoneExecute !== true) {
|
|
679
|
+
await tx.resourceGrant.update({
|
|
680
|
+
where: { id: everyone.id },
|
|
681
|
+
data: { level: "read", source: "operator", grantedById: ctx.principal.id },
|
|
682
|
+
});
|
|
683
|
+
everyoneGrant.after = "read";
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
const updated = await tx.agent.update({ where: { id: args.agentId }, data: { ownerId: newOwner } });
|
|
688
|
+
// Secret/datastore bindings the new owner doesn't own: removed.
|
|
689
|
+
const secrets = await tx.agentSecret.findMany({
|
|
690
|
+
where: { agentId: agent.id },
|
|
691
|
+
include: { secret: { select: { ownerId: true } } },
|
|
692
|
+
});
|
|
693
|
+
const foreignSecrets = secrets.filter((b) => b.secret.ownerId !== newOwner);
|
|
694
|
+
if (foreignSecrets.length > 0) {
|
|
695
|
+
await tx.agentSecret.deleteMany({
|
|
696
|
+
where: { agentId: agent.id, secretId: { in: foreignSecrets.map((b) => b.secretId) } },
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
const datastores = await tx.agentDatastore.findMany({
|
|
700
|
+
where: { agentId: agent.id },
|
|
701
|
+
include: { datastore: { select: { ownerId: true } } },
|
|
702
|
+
});
|
|
703
|
+
const foreignDatastores = datastores.filter((b) => b.datastore.ownerId !== newOwner);
|
|
704
|
+
if (foreignDatastores.length > 0) {
|
|
705
|
+
await tx.agentDatastore.deleteMany({
|
|
706
|
+
where: { agentId: agent.id, datastoreId: { in: foreignDatastores.map((b) => b.datastoreId) } },
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
const bindingsRemoved = [
|
|
710
|
+
...foreignSecrets.map((b) => ({ kind: "secret", boundName: b.boundName, resourceId: b.secretId })),
|
|
711
|
+
...foreignDatastores.map((b) => ({ kind: "datastore", boundName: b.boundName, resourceId: b.datastoreId })),
|
|
712
|
+
];
|
|
713
|
+
// Tool capabilities: rows aren't rewritten; the runner ignores any
|
|
714
|
+
// the new owner never stamped (capabilitiesGrantedById).
|
|
715
|
+
const attachments = await tx.agentTool.findMany({
|
|
716
|
+
where: { agentId: agent.id },
|
|
717
|
+
include: { tool: { select: { id: true, name: true } } },
|
|
718
|
+
});
|
|
719
|
+
const toolCapabilitiesSuspended = attachments
|
|
720
|
+
.filter((at) => at.capabilitiesGrantedById !== newOwner)
|
|
721
|
+
.map((at) => ({ toolId: at.tool.id, toolName: at.tool.name }));
|
|
722
|
+
// Sub-agent edges (both directions) the new owner can't delegate.
|
|
723
|
+
const edges = await tx.agentSubAgent.findMany({
|
|
724
|
+
where: { OR: [{ parentAgentId: agent.id }, { childAgentId: agent.id }] },
|
|
725
|
+
include: {
|
|
726
|
+
parent: { select: { id: true, ownerId: true } },
|
|
727
|
+
child: { select: { id: true, ownerId: true } },
|
|
728
|
+
},
|
|
729
|
+
});
|
|
730
|
+
const subAgentEdgesRemoved = [];
|
|
731
|
+
for (const edge of edges) {
|
|
732
|
+
const parent = edge.parentAgentId === agent.id ? { ownerId: newOwner } : edge.parent;
|
|
733
|
+
const child = edge.childAgentId === agent.id ? { id: agent.id, ownerId: newOwner } : edge.child;
|
|
734
|
+
if (!(await canDelegate(tx, parent, child))) {
|
|
735
|
+
subAgentEdgesRemoved.push({
|
|
736
|
+
parentAgentId: edge.parentAgentId,
|
|
737
|
+
childAgentId: edge.childAgentId,
|
|
738
|
+
boundName: edge.boundName,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
for (const edge of subAgentEdgesRemoved) {
|
|
743
|
+
await tx.agentSubAgent.deleteMany({
|
|
744
|
+
where: { parentAgentId: edge.parentAgentId, childAgentId: edge.childAgentId },
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
// Webhooks others created stay, but fire only while their creator
|
|
748
|
+
// may still trigger the agent (core/webhooks.ts).
|
|
749
|
+
const webhooks = await tx.webhook.findMany({ where: { agentId: agent.id } });
|
|
750
|
+
const webhooksInactive = [];
|
|
751
|
+
for (const webhook of webhooks) {
|
|
752
|
+
if (webhook.ownerId === newOwner)
|
|
753
|
+
continue;
|
|
754
|
+
const creatorAccess = await effectiveAccess(tx, "agent", updated, webhook.ownerId);
|
|
755
|
+
if (!atLeast("agent", creatorAccess, "execute")) {
|
|
756
|
+
webhooksInactive.push({ id: webhook.id, createdBy: webhook.ownerId });
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
...updated,
|
|
761
|
+
repositoryApprovalsRevoked: revoked,
|
|
762
|
+
bindingsRemoved,
|
|
763
|
+
toolCapabilitiesSuspended,
|
|
764
|
+
subAgentEdgesRemoved,
|
|
765
|
+
grantsReset,
|
|
766
|
+
everyoneGrant,
|
|
767
|
+
webhooksInactive,
|
|
768
|
+
};
|
|
769
|
+
}, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
|
|
770
|
+
return textResult(result);
|
|
373
771
|
},
|
|
374
772
|
});
|
|
375
773
|
}
|