@wardby/cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +34 -4
- package/README.md +41 -4
- package/dist/claude-coding-worker/driver.d.ts +6 -1
- package/dist/claude-coding-worker/driver.js +27 -1
- package/dist/claude-coding-worker/main.js +2 -0
- package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
- package/dist/claude-coding-worker/tool-socket.js +26 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +13 -4
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +219 -29
- package/dist/coding/collect-exclude.d.ts +25 -0
- package/dist/coding/collect-exclude.js +76 -0
- package/dist/coding/profile.d.ts +67 -22
- package/dist/coding/profile.js +58 -25
- package/dist/coding/protected-path-wording.d.ts +24 -0
- package/dist/coding/protected-path-wording.js +55 -0
- package/dist/coding/protected-paths.d.ts +31 -0
- package/dist/coding/protected-paths.js +48 -0
- package/dist/coding/protocol.d.ts +75 -4
- package/dist/coding/protocol.js +107 -7
- package/dist/coding/registry/adapters.d.ts +2 -0
- package/dist/coding/registry/adapters.js +9 -0
- package/dist/coding/registry/allowlist.d.ts +9 -0
- package/dist/coding/registry/allowlist.js +28 -0
- package/dist/coding/registry/json-scan.d.ts +55 -0
- package/dist/coding/registry/json-scan.js +191 -0
- package/dist/coding/registry/lockfiles.d.ts +6 -0
- package/dist/coding/registry/lockfiles.js +89 -0
- package/dist/coding/registry/npm-lockfile.d.ts +9 -0
- package/dist/coding/registry/npm-lockfile.js +73 -0
- package/dist/coding/registry/npm-plan.d.ts +29 -0
- package/dist/coding/registry/npm-plan.js +289 -0
- package/dist/coding/registry/npm.d.ts +7 -0
- package/dist/coding/registry/npm.js +252 -0
- package/dist/coding/registry/pypi.d.ts +4 -0
- package/dist/coding/registry/pypi.js +210 -0
- package/dist/coding/registry/report.d.ts +37 -0
- package/dist/coding/registry/report.js +31 -0
- package/dist/coding/registry/token.d.ts +4 -0
- package/dist/coding/registry/token.js +7 -0
- package/dist/coding/registry/types.d.ts +286 -0
- package/dist/coding/registry/types.js +19 -0
- package/dist/coding/registry/worker-config.d.ts +14 -0
- package/dist/coding/registry/worker-config.js +31 -0
- package/dist/coding/services/builtins.d.ts +22 -0
- package/dist/coding/services/builtins.js +85 -0
- package/dist/coding/services/catalog.d.ts +443 -0
- package/dist/coding/services/catalog.js +159 -0
- package/dist/coding/services/declaration.d.ts +11 -0
- package/dist/coding/services/declaration.js +114 -0
- package/dist/coding/services/note.d.ts +8 -0
- package/dist/coding/services/note.js +15 -0
- package/dist/coding/services/resolve.d.ts +25 -0
- package/dist/coding/services/resolve.js +50 -0
- package/dist/coding/services/wording.d.ts +25 -0
- package/dist/coding/services/wording.js +70 -0
- package/dist/coding-proxy/main.js +24 -4
- package/dist/coding-worker/artifact.d.ts +6 -0
- package/dist/coding-worker/debug-trace.d.ts +37 -0
- package/dist/coding-worker/debug-trace.js +117 -0
- package/dist/coding-worker/driver.d.ts +13 -1
- package/dist/coding-worker/driver.js +102 -13
- package/dist/coding-worker/errors.js +8 -0
- package/dist/coding-worker/main.js +10 -2
- package/dist/coding-worker/sdk.d.ts +2 -2
- package/dist/coding-worker/sdk.js +7 -2
- package/dist/coding-worker/types.d.ts +3 -0
- package/dist/config/providers.d.ts +41 -0
- package/dist/config/providers.js +66 -0
- package/dist/core/budget-groups.d.ts +108 -5
- package/dist/core/budget-groups.js +125 -19
- package/dist/core/budget-wording.d.ts +22 -0
- package/dist/core/budget-wording.js +55 -0
- package/dist/core/datastores.js +18 -2
- package/dist/core/db.d.ts +5 -1
- package/dist/core/db.js +8 -2
- package/dist/core/dispatch.d.ts +33 -6
- package/dist/core/dispatch.js +260 -43
- package/dist/core/engine-native.js +27 -10
- package/dist/core/grants.d.ts +86 -0
- package/dist/core/grants.js +126 -0
- package/dist/core/host-events.d.ts +49 -0
- package/dist/core/host-events.js +257 -0
- package/dist/core/host-identity-links.d.ts +54 -0
- package/dist/core/host-identity-links.js +189 -0
- package/dist/core/host-status.d.ts +61 -0
- package/dist/core/host-status.js +211 -0
- package/dist/core/in-flight-runs.d.ts +8 -0
- package/dist/core/in-flight-runs.js +56 -0
- package/dist/core/provider-wording.d.ts +12 -0
- package/dist/core/provider-wording.js +44 -0
- package/dist/core/reconciler.d.ts +38 -2
- package/dist/core/reconciler.js +86 -2
- package/dist/core/repo-access.d.ts +99 -0
- package/dist/core/repo-access.js +136 -0
- package/dist/core/review-host-checks.d.ts +11 -0
- package/dist/core/review-host-checks.js +41 -0
- package/dist/core/review-host-tools.d.ts +47 -0
- package/dist/core/review-host-tools.js +347 -0
- package/dist/core/run-heartbeat.d.ts +27 -0
- package/dist/core/run-heartbeat.js +54 -0
- package/dist/core/runner.d.ts +30 -8
- package/dist/core/runner.js +237 -40
- package/dist/core/secrets.d.ts +11 -2
- package/dist/core/secrets.js +25 -6
- package/dist/core/subagent-memory-tools.d.ts +1 -1
- package/dist/core/subagent-memory-tools.js +16 -3
- package/dist/core/tool-admin.d.ts +81 -0
- package/dist/core/tool-admin.js +129 -0
- package/dist/core/tool-names.d.ts +42 -0
- package/dist/core/tool-names.js +64 -0
- package/dist/core/untrusted-content.d.ts +32 -0
- package/dist/core/untrusted-content.js +72 -0
- package/dist/core/webhooks.d.ts +8 -1
- package/dist/core/webhooks.js +23 -2
- package/dist/generated/prisma/browser.d.ts +100 -0
- package/dist/generated/prisma/client.d.ts +100 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +30 -0
- package/dist/generated/prisma/enums.d.ts +6 -0
- package/dist/generated/prisma/enums.js +6 -1
- package/dist/generated/prisma/internal/class.d.ts +143 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +1746 -577
- package/dist/generated/prisma/internal/prismaNamespace.js +179 -6
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +186 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +179 -6
- package/dist/generated/prisma/models/Agent.d.ts +294 -1
- package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
- package/dist/generated/prisma/models/AgentRepository.js +1 -0
- package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
- package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
- package/dist/generated/prisma/models/CodingProxySession.d.ts +123 -2
- package/dist/generated/prisma/models/CodingRun.d.ts +1276 -95
- package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
- package/dist/generated/prisma/models/CodingService.js +1 -0
- package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
- package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
- package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
- package/dist/generated/prisma/models/HostIdentity.js +1 -0
- package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
- package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
- package/dist/generated/prisma/models/Principal.d.ts +455 -0
- package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
- package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
- package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
- package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
- package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
- package/dist/generated/prisma/models/RegistryFetch.js +1 -0
- package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
- package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
- package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
- package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
- package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
- package/dist/generated/prisma/models/ResourceGrant.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +389 -1
- package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
- package/dist/generated/prisma/models/RunHostCheck.js +1 -0
- package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
- package/dist/generated/prisma/models/RunHostStatus.js +1 -0
- package/dist/generated/prisma/models/Tool.d.ts +15 -3
- package/dist/generated/prisma/models.d.ts +13 -0
- package/dist/help/build.d.ts +1 -0
- package/dist/help/build.js +9 -0
- package/dist/help/catalog.d.ts +24 -0
- package/dist/help/catalog.js +160 -0
- package/dist/help/cli.d.ts +2 -0
- package/dist/help/cli.js +65 -0
- package/dist/help/runtime.d.ts +3 -0
- package/dist/help/runtime.js +44 -0
- package/dist/help/search.d.ts +9 -0
- package/dist/help/search.js +104 -0
- package/dist/help-index.json +660 -0
- package/dist/import/cli-args.js +3 -2
- package/dist/import/create.d.ts +5 -0
- package/dist/import/create.js +67 -13
- package/dist/import/index.js +19 -9
- package/dist/import/neutral-schema.d.ts +11 -11
- package/dist/mcp/auth/access.d.ts +70 -0
- package/dist/mcp/auth/access.js +58 -0
- package/dist/mcp/auth/grants-cli.d.ts +149 -0
- package/dist/mcp/auth/grants-cli.js +518 -0
- package/dist/mcp/auth/host-account-cli.d.ts +2 -0
- package/dist/mcp/auth/host-account-cli.js +47 -0
- package/dist/mcp/auth/ownership.d.ts +26 -52
- package/dist/mcp/auth/ownership.js +19 -14
- package/dist/mcp/auth/repo-authorization.d.ts +22 -0
- package/dist/mcp/auth/repo-authorization.js +51 -0
- package/dist/mcp/auth/resource-server.d.ts +31 -2
- package/dist/mcp/auth/resource-server.js +84 -3
- package/dist/mcp/auth/self-hosted/browser.js +2 -2
- package/dist/mcp/auth/self-hosted/cli.js +47 -13
- package/dist/mcp/auth/self-hosted/credentials.d.ts +20 -2
- package/dist/mcp/auth/self-hosted/credentials.js +62 -4
- package/dist/mcp/auth/self-hosted/session.d.ts +2 -1
- package/dist/mcp/context.d.ts +27 -1
- package/dist/mcp/errors.d.ts +25 -6
- package/dist/mcp/errors.js +98 -0
- package/dist/mcp/host-events/github-ingress.d.ts +36 -0
- package/dist/mcp/host-events/github-ingress.js +92 -0
- package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
- package/dist/mcp/host-events/github-user-callback.js +50 -0
- package/dist/mcp/index.d.ts +8 -1
- package/dist/mcp/index.js +106 -16
- package/dist/mcp/server.js +27 -9
- package/dist/mcp/tools/agents.js +446 -48
- package/dist/mcp/tools/budget-groups.js +3 -3
- package/dist/mcp/tools/datastore.js +22 -15
- package/dist/mcp/tools/grants.d.ts +2 -0
- package/dist/mcp/tools/grants.js +239 -0
- package/dist/mcp/tools/help.d.ts +5 -0
- package/dist/mcp/tools/help.js +67 -0
- package/dist/mcp/tools/host-accounts.d.ts +2 -0
- package/dist/mcp/tools/host-accounts.js +114 -0
- package/dist/mcp/tools/memory.d.ts +7 -1
- package/dist/mcp/tools/memory.js +5 -5
- package/dist/mcp/tools/repositories.d.ts +2 -0
- package/dist/mcp/tools/repositories.js +181 -0
- package/dist/mcp/tools/runs.d.ts +6 -0
- package/dist/mcp/tools/runs.js +60 -6
- package/dist/mcp/tools/scheduling.js +3 -6
- package/dist/mcp/tools/secrets.js +18 -6
- package/dist/mcp/tools/services.d.ts +2 -0
- package/dist/mcp/tools/services.js +222 -0
- package/dist/mcp/tools/subagents.js +52 -16
- package/dist/mcp/tools/tools.d.ts +41 -0
- package/dist/mcp/tools/tools.js +201 -40
- package/dist/mcp/tools/trigger.js +36 -6
- package/dist/mcp/tools/webhooks.js +15 -4
- package/dist/mcp/transport/streamable-http.d.ts +10 -0
- package/dist/mcp/transport/streamable-http.js +32 -2
- package/dist/providers/auth/delegating.d.ts +19 -0
- package/dist/providers/auth/delegating.js +71 -0
- package/dist/providers/auth/index.d.ts +2 -0
- package/dist/providers/auth/index.js +11 -0
- package/dist/providers/auth/self-hosted.d.ts +2 -1
- package/dist/providers/auth/self-hosted.js +16 -4
- package/dist/providers/auth/types.d.ts +6 -0
- package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
- package/dist/providers/coding-proxy/memory-ledger.js +21 -2
- package/dist/providers/coding-proxy/metering.d.ts +4 -0
- package/dist/providers/coding-proxy/metering.js +28 -2
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
- package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
- package/dist/providers/coding-proxy/proxy.d.ts +13 -0
- package/dist/providers/coding-proxy/proxy.js +479 -18
- package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
- package/dist/providers/coding-proxy/registry/audit.js +380 -0
- package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
- package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
- package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
- package/dist/providers/coding-proxy/registry/plan.js +304 -0
- package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
- package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
- package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
- package/dist/providers/coding-proxy/registry/service.js +1137 -0
- package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
- package/dist/providers/coding-proxy/registry/store.js +70 -0
- package/dist/providers/coding-proxy/runtime.d.ts +17 -0
- package/dist/providers/coding-proxy/runtime.js +63 -0
- package/dist/providers/coding-proxy/secure-fetch.js +0 -1
- package/dist/providers/coding-proxy/server.d.ts +11 -0
- package/dist/providers/coding-proxy/server.js +163 -0
- package/dist/providers/coding-proxy/types.d.ts +15 -0
- package/dist/providers/engine/types.d.ts +10 -0
- package/dist/providers/executor/build.d.ts +2 -2
- package/dist/providers/executor/composition.d.ts +3 -0
- package/dist/providers/executor/composition.js +19 -0
- package/dist/providers/executor/container.d.ts +105 -3
- package/dist/providers/executor/container.js +296 -20
- package/dist/providers/executor/dbos.d.ts +2 -3
- package/dist/providers/executor/in-process.d.ts +2 -3
- package/dist/providers/executor/routing.d.ts +7 -0
- package/dist/providers/executor/routing.js +13 -0
- package/dist/providers/executor/types.d.ts +22 -0
- package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
- package/dist/providers/jobs/claude-tool-setup.js +50 -0
- package/dist/providers/jobs/collect-prune.d.ts +7 -0
- package/dist/providers/jobs/collect-prune.js +27 -0
- package/dist/providers/jobs/docker-isolation.d.ts +48 -3
- package/dist/providers/jobs/docker-isolation.js +213 -22
- package/dist/providers/jobs/docker-services.d.ts +35 -0
- package/dist/providers/jobs/docker-services.js +191 -0
- package/dist/providers/jobs/docker.d.ts +49 -2
- package/dist/providers/jobs/docker.js +279 -32
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
- package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
- package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
- package/dist/providers/jobs/kubernetes-isolation.js +238 -57
- package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
- package/dist/providers/jobs/kubernetes-platform.js +11 -5
- package/dist/providers/jobs/kubernetes-preflight.js +3 -0
- package/dist/providers/jobs/kubernetes.d.ts +21 -2
- package/dist/providers/jobs/kubernetes.js +112 -21
- package/dist/providers/jobs/types.d.ts +18 -0
- package/dist/providers/llm/anthropic.js +6 -2
- package/dist/providers/llm/bedrock.js +2 -1
- package/dist/providers/llm/claude-messages.d.ts +5 -1
- package/dist/providers/llm/claude-messages.js +1 -0
- package/dist/providers/llm/claude-provider.d.ts +3 -1
- package/dist/providers/llm/claude-provider.js +5 -1
- package/dist/providers/llm/index.d.ts +1 -1
- package/dist/providers/llm/index.js +1 -1
- package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
- package/dist/providers/llm/pricing-anthropic.js +32 -14
- package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
- package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
- package/dist/providers/llm/routing.d.ts +10 -1
- package/dist/providers/llm/routing.js +18 -0
- package/dist/providers/llm/types.d.ts +12 -0
- package/dist/providers/llm/types.js +8 -1
- package/dist/providers/review-host/diff-lines.d.ts +16 -0
- package/dist/providers/review-host/diff-lines.js +59 -0
- package/dist/providers/review-host/github-events.d.ts +6 -0
- package/dist/providers/review-host/github-events.js +180 -0
- package/dist/providers/review-host/github-user-auth.d.ts +38 -0
- package/dist/providers/review-host/github-user-auth.js +128 -0
- package/dist/providers/review-host/github.d.ts +57 -0
- package/dist/providers/review-host/github.js +567 -0
- package/dist/providers/review-host/index.d.ts +12 -0
- package/dist/providers/review-host/index.js +30 -0
- package/dist/providers/review-host/review-format.d.ts +19 -0
- package/dist/providers/review-host/review-format.js +59 -0
- package/dist/providers/review-host/types.d.ts +284 -0
- package/dist/providers/review-host/types.js +24 -0
- package/dist/providers/vcs/git.d.ts +16 -6
- package/dist/providers/vcs/git.js +77 -13
- package/dist/providers/vcs/github.d.ts +74 -3
- package/dist/providers/vcs/github.js +195 -15
- package/dist/providers/vcs/types.d.ts +42 -4
- package/dist/quickstart/index.js +13 -2
- package/dist/sandbox/fetch-policy.d.ts +20 -2
- package/dist/sandbox/fetch-policy.js +64 -3
- package/dist/sandbox/host-functions.d.ts +9 -1
- package/dist/sandbox/host-functions.js +12 -5
- package/dist/serve.js +1 -1
- package/dist/wardby-bin.js +6 -0
- package/docs/README.md +30 -0
- package/docs/architecture-runtime.md +90 -0
- package/docs/assets/brand/wardby-icon-512.png +0 -0
- package/docs/assets/brand/wardby-icon.svg +16 -0
- package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
- package/docs/assets/brand/wardby-mascot.png +0 -0
- package/docs/assets/brand/wardby-mascot.svg +5 -0
- package/docs/assets/wardby-workflow.svg +106 -0
- package/docs/code-review-agents.md +481 -0
- package/docs/coding-agent-setup.md +172 -0
- package/docs/coding-packages.md +455 -0
- package/docs/coding-services.md +300 -0
- package/docs/coding-worker-byo-images.md +98 -0
- package/docs/coding-worker-isolation.md +1061 -0
- package/docs/getting-started-gke.md +615 -0
- package/docs/getting-started-identity-provider.md +308 -0
- package/docs/getting-started.md +133 -0
- package/docs/observability.md +53 -0
- package/docs/release-verification.md +66 -0
- package/docs/security-deployment.md +657 -0
- package/help/code-review-agents.md +31 -0
- package/help/coding-packages.md +31 -0
- package/help/coding-services.md +71 -0
- package/help/creating-agents.md +68 -0
- package/help/deploy-gke.md +39 -0
- package/help/deployment-targets.md +39 -0
- package/help/errors/budget-group-exhausted.md +25 -0
- package/help/errors/docker-isolation-unsupported.md +26 -0
- package/help/errors/protected-path.md +45 -0
- package/help/errors/repo-access.md +27 -0
- package/help/errors/service-declaration-invalid.md +27 -0
- package/help/errors/service-declaration-unavailable.md +25 -0
- package/help/errors/service-launcher-unsupported.md +30 -0
- package/help/errors/service-not-allowed.md +27 -0
- package/help/errors/service-unknown.md +23 -0
- package/help/errors/service-unready.md +49 -0
- package/help/getting-started.md +31 -0
- package/help/github.md +30 -0
- package/help/identity-and-access.md +39 -0
- package/help/mcp.md +30 -0
- package/help/native-capabilities.md +30 -0
- package/help/observability.md +41 -0
- package/help/operating-agents.md +30 -0
- package/help/security.md +27 -0
- package/help/troubleshooting/budgets.md +26 -0
- package/help/troubleshooting/coding-workers.md +47 -0
- package/help/troubleshooting/repository-access.md +27 -0
- package/package.json +12 -3
- package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
- package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
- package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
- package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
- package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
- package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
- package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
- package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
- package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
- package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
- package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
- package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
- package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
- package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
- package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
- package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
- package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
- package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
- package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
- package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
- package/prisma/schema.prisma +407 -21
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AgentSubAgent CRUD — agent-composition edges.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* AgentSubAgent CRUD — agent-composition edges. An edge runs the child
|
|
3
|
+
* (with its owner's bindings) whenever the parent delegates, and hands the
|
|
4
|
+
* child whatever the parent's runs pass it, so it is a binding of the
|
|
5
|
+
* parent: only the parent's owner attaches or detaches one (review I2; a
|
|
6
|
+
* write-grantee could otherwise route every run's data to their own agent).
|
|
7
|
+
* Across owners it also needs an explicit grant (resource-sharing grants
|
|
8
|
+
* spec §3.4.4, N1): the caller needs execute on the child, and the parent's
|
|
9
|
+
* owner must be the child's owner or hold execute on it (canDelegate,
|
|
10
|
+
* re-checked on every delegation at run time). The child's owner can
|
|
11
|
+
* always cut an edge. Attach also rejects any edge that would
|
|
12
|
+
* create a cycle in the parent/child graph (§7 of the design doc) — cycles
|
|
13
|
+
* are not something Postgres can enforce natively.
|
|
8
14
|
*/
|
|
9
15
|
import { Prisma } from "#prisma";
|
|
10
|
-
import {
|
|
16
|
+
import { atLeast, canDelegate } from "../../core/grants.js";
|
|
17
|
+
import { agentAccess, agentAccessResolver, agentNotFound, requireAgentAccess, requireBindingOwner, } from "../auth/access.js";
|
|
11
18
|
import { McpError } from "../errors.js";
|
|
12
19
|
import { textResult } from "./text-result.js";
|
|
13
20
|
/**
|
|
@@ -60,8 +67,16 @@ export function registerSubAgentTools(mcp) {
|
|
|
60
67
|
if (args.parentAgentId === args.childAgentId) {
|
|
61
68
|
throw new McpError(400, "An agent cannot be its own sub-agent.");
|
|
62
69
|
}
|
|
63
|
-
const parent = await
|
|
64
|
-
|
|
70
|
+
const { agent: parent } = await requireAgentAccess(ctx, args.parentAgentId, "read");
|
|
71
|
+
requireBindingOwner(ctx, parent);
|
|
72
|
+
// The caller could trigger the child anyway; the edge must not widen that.
|
|
73
|
+
const { agent: child } = await requireAgentAccess(ctx, args.childAgentId, "execute");
|
|
74
|
+
if (parent.ownerId === null) {
|
|
75
|
+
throw new McpError(403, `Agent "${parent.id}" has no owner, so it can't take sub-agents; an admin can assign one with make_owner.`);
|
|
76
|
+
}
|
|
77
|
+
if (!(await canDelegate(ctx.db, parent, child))) {
|
|
78
|
+
throw new McpError(403, `"${child.name}" belongs to another owner, who has not given the owner of "${parent.name}" execute access to it.`);
|
|
79
|
+
}
|
|
65
80
|
if (await canReach(ctx.db, args.childAgentId, args.parentAgentId)) {
|
|
66
81
|
throw new McpError(400, `Attaching "${child.name}" as a sub-agent of "${parent.name}" would create a cycle.`);
|
|
67
82
|
}
|
|
@@ -95,8 +110,20 @@ export function registerSubAgentTools(mcp) {
|
|
|
95
110
|
required: ["parentAgentId", "childAgentId"],
|
|
96
111
|
},
|
|
97
112
|
handler: async (args, ctx) => {
|
|
98
|
-
|
|
99
|
-
|
|
113
|
+
// The parent's owner (or the stdio operator), or the child's owner: a
|
|
114
|
+
// child's owner can always take its agent back out of someone else's
|
|
115
|
+
// composition.
|
|
116
|
+
const [parent, child] = await Promise.all([
|
|
117
|
+
ctx.db.agent.findUnique({ where: { id: args.parentAgentId } }),
|
|
118
|
+
ctx.db.agent.findUnique({ where: { id: args.childAgentId } }),
|
|
119
|
+
]);
|
|
120
|
+
const parentAccess = parent ? await agentAccess(ctx, parent) : "none";
|
|
121
|
+
const childAccess = child ? await agentAccess(ctx, child) : "none";
|
|
122
|
+
if (parentAccess !== "owner" && childAccess !== "owner") {
|
|
123
|
+
if (!atLeast("agent", parentAccess, "read"))
|
|
124
|
+
throw agentNotFound(args.parentAgentId);
|
|
125
|
+
throw new McpError(403, `Detaching needs ownership of "${args.parentAgentId}" or of "${args.childAgentId}".`);
|
|
126
|
+
}
|
|
100
127
|
await ctx.db.agentSubAgent.deleteMany({
|
|
101
128
|
where: { parentAgentId: args.parentAgentId, childAgentId: args.childAgentId },
|
|
102
129
|
});
|
|
@@ -113,20 +140,29 @@ export function registerSubAgentTools(mcp) {
|
|
|
113
140
|
required: ["agentId"],
|
|
114
141
|
},
|
|
115
142
|
handler: async (args, ctx) => {
|
|
116
|
-
await
|
|
143
|
+
await requireAgentAccess(ctx, args.agentId, "read");
|
|
144
|
+
const accessOf = await agentAccessResolver(ctx);
|
|
117
145
|
const [asParent, asChild] = await Promise.all([
|
|
118
146
|
ctx.db.agentSubAgent.findMany({
|
|
119
147
|
where: { parentAgentId: args.agentId },
|
|
120
|
-
include: { child: { select: { id: true, name: true } } },
|
|
148
|
+
include: { child: { select: { id: true, name: true, ownerId: true } } },
|
|
121
149
|
}),
|
|
122
150
|
ctx.db.agentSubAgent.findMany({
|
|
123
151
|
where: { childAgentId: args.agentId },
|
|
124
|
-
include: { parent: { select: { id: true, name: true } } },
|
|
152
|
+
include: { parent: { select: { id: true, name: true, ownerId: true } } },
|
|
125
153
|
}),
|
|
126
154
|
]);
|
|
155
|
+
// Only agents the caller can read are named; the rest are counted.
|
|
156
|
+
const readable = (agent) => atLeast("agent", accessOf(agent), "read");
|
|
157
|
+
const children = asParent.filter((e) => readable(e.child));
|
|
158
|
+
const parents = asChild.filter((e) => readable(e.parent));
|
|
159
|
+
const hiddenChildren = asParent.length - children.length;
|
|
160
|
+
const hiddenParents = asChild.length - parents.length;
|
|
127
161
|
return textResult({
|
|
128
|
-
children:
|
|
129
|
-
parents:
|
|
162
|
+
children: children.map((e) => ({ boundName: e.boundName, agentId: e.child.id, agentName: e.child.name })),
|
|
163
|
+
parents: parents.map((e) => ({ boundName: e.boundName, agentId: e.parent.id, agentName: e.parent.name })),
|
|
164
|
+
...(hiddenChildren > 0 ? { hiddenChildren } : {}),
|
|
165
|
+
...(hiddenParents > 0 ? { hiddenParents } : {}),
|
|
130
166
|
});
|
|
131
167
|
},
|
|
132
168
|
});
|
|
@@ -1,2 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool authoring — raw path only (Amendment A: the guided Elicitation/Tasks
|
|
3
|
+
* flow is deferred; this ships create_tool + dry_run_tool as the
|
|
4
|
+
* test-before-commit loop instead). A compile/validation failure is a
|
|
5
|
+
* NORMAL tool result carrying `{ok:false, errorKind, errorMessage}` — not
|
|
6
|
+
* an isError — since the call itself succeeded (compilation was attempted
|
|
7
|
+
* and correctly reported a problem the caller can fix and retry), matching
|
|
8
|
+
* runInSandbox's own SandboxResult convention. Ownership/scope failures
|
|
9
|
+
* (attach/detach on an agent or tool you don't own) DO throw, same
|
|
10
|
+
* convention as every other tool in this codebase.
|
|
11
|
+
*/
|
|
12
|
+
import { type Tool } from "#prisma";
|
|
1
13
|
import type { WardbyMcpServer } from "../server.js";
|
|
14
|
+
/**
|
|
15
|
+
* A tool row as a caller who doesn't own it may see it: name and
|
|
16
|
+
* description, never code or paramsZod (A7). `public` tells an owner-less
|
|
17
|
+
* tool apart from the caller's own same-named one (names are unique per
|
|
18
|
+
* owner, not globally). Shared by list_tools and get_agent.
|
|
19
|
+
*/
|
|
20
|
+
export declare function projectTool(tool: Tool, principalId: string): {
|
|
21
|
+
name: string;
|
|
22
|
+
code: string;
|
|
23
|
+
id: string;
|
|
24
|
+
ownerId: string | null;
|
|
25
|
+
createdAt: Date;
|
|
26
|
+
updatedAt: Date;
|
|
27
|
+
description: string;
|
|
28
|
+
paramsZod: string;
|
|
29
|
+
jsonSchema: import("@prisma/client/runtime/client").JsonValue;
|
|
30
|
+
} | {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
description: string;
|
|
34
|
+
public: boolean;
|
|
35
|
+
ownerIsCaller?: undefined;
|
|
36
|
+
} | {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
description: string;
|
|
40
|
+
public: boolean;
|
|
41
|
+
ownerIsCaller: boolean;
|
|
42
|
+
};
|
|
2
43
|
export declare function registerToolAuthoringTools(mcp: WardbyMcpServer): void;
|
package/dist/mcp/tools/tools.js
CHANGED
|
@@ -14,10 +14,46 @@ import { deriveJsonSchema, validateParams } from "../../sandbox/zod-params.js";
|
|
|
14
14
|
import { runInSandbox } from "../../sandbox/run-in-sandbox.js";
|
|
15
15
|
import { ToolCapabilitiesPatchSchema } from "../../sandbox/tool-capabilities.js";
|
|
16
16
|
import { buildSharedDatastoreAccessor } from "../../core/datastores.js";
|
|
17
|
-
import {
|
|
17
|
+
import { findSameNamedAttachedTool, reservedToolNameReason } from "../../core/tool-names.js";
|
|
18
|
+
import { ToolAttachedError, deleteToolGuarded, hasToolChanges, prepareToolUpdate, updateToolGuarded, } from "../../core/tool-admin.js";
|
|
18
19
|
import { McpError } from "../errors.js";
|
|
19
|
-
import {
|
|
20
|
+
import { requireOwnedTool, requireStrictlyOwnedTool, assertStrictlyOwnedTool, visibleToPrincipal, canRead, } from "../auth/ownership.js";
|
|
21
|
+
import { agentAccessResolver, assertAgentAccess, requireAgentAccess } from "../auth/access.js";
|
|
22
|
+
import { atLeast } from "../../core/grants.js";
|
|
20
23
|
import { textResult } from "./text-result.js";
|
|
24
|
+
/**
|
|
25
|
+
* Names the agents the caller can read (its own and ones granted to it)
|
|
26
|
+
* and only counts the rest, so a refusal never reveals another principal's
|
|
27
|
+
* agent names or ids.
|
|
28
|
+
*/
|
|
29
|
+
async function describeAgents(agents, ctx) {
|
|
30
|
+
const accessOf = await agentAccessResolver(ctx);
|
|
31
|
+
const readable = agents.filter((agent) => atLeast("agent", accessOf(agent), "read"));
|
|
32
|
+
const hidden = agents.length - readable.length;
|
|
33
|
+
const parts = readable.map((agent) => `"${agent.name}" (${agent.id})`);
|
|
34
|
+
if (hidden > 0)
|
|
35
|
+
parts.push(`${hidden} agent(s) owned by other principals`);
|
|
36
|
+
return parts.join(", ");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A tool row as a caller who doesn't own it may see it: name and
|
|
40
|
+
* description, never code or paramsZod (A7). `public` tells an owner-less
|
|
41
|
+
* tool apart from the caller's own same-named one (names are unique per
|
|
42
|
+
* owner, not globally). Shared by list_tools and get_agent.
|
|
43
|
+
*/
|
|
44
|
+
export function projectTool(tool, principalId) {
|
|
45
|
+
if (tool.ownerId !== null && tool.ownerId === principalId)
|
|
46
|
+
return tool;
|
|
47
|
+
return tool.ownerId === null
|
|
48
|
+
? { id: tool.id, name: tool.name, description: tool.description, public: true }
|
|
49
|
+
: { id: tool.id, name: tool.name, description: tool.description, public: false, ownerIsCaller: false };
|
|
50
|
+
}
|
|
51
|
+
/** Refuses a name one of the runner's built-ins would shadow (see core/tool-names.ts). */
|
|
52
|
+
function assertToolNameAllowed(name) {
|
|
53
|
+
const reason = reservedToolNameReason(name);
|
|
54
|
+
if (reason)
|
|
55
|
+
throw new McpError(400, reason);
|
|
56
|
+
}
|
|
21
57
|
export function registerToolAuthoringTools(mcp) {
|
|
22
58
|
mcp.registerTool({
|
|
23
59
|
name: "create_tool",
|
|
@@ -33,24 +69,108 @@ export function registerToolAuthoringTools(mcp) {
|
|
|
33
69
|
required: ["name", "description", "paramsZod", "code"],
|
|
34
70
|
},
|
|
35
71
|
handler: async (args, ctx) => {
|
|
36
|
-
|
|
37
|
-
throw new McpError(400, `Tool name "${args.name}" is reserved for the built-in agent-memory tools.`);
|
|
38
|
-
}
|
|
72
|
+
assertToolNameAllowed(args.name);
|
|
39
73
|
const schemaResult = await deriveJsonSchema(args.paramsZod);
|
|
40
74
|
if (!schemaResult.ok) {
|
|
41
75
|
return textResult({ ok: false, errorKind: schemaResult.errorKind, errorMessage: schemaResult.errorMessage });
|
|
42
76
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
77
|
+
try {
|
|
78
|
+
const tool = await ctx.db.tool.create({
|
|
79
|
+
data: {
|
|
80
|
+
name: args.name,
|
|
81
|
+
description: args.description,
|
|
82
|
+
paramsZod: args.paramsZod,
|
|
83
|
+
code: args.code,
|
|
84
|
+
jsonSchema: schemaResult.value,
|
|
85
|
+
ownerId: ctx.principal.id,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
return textResult(tool);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
|
|
92
|
+
throw new McpError(409, `A tool named "${args.name}" already exists for your principal.`);
|
|
93
|
+
}
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
mcp.registerTool({
|
|
99
|
+
name: "update_tool",
|
|
100
|
+
scope: "tools:write",
|
|
101
|
+
description: "Replaces a tool's description, paramsZod and/or code in place (its name can't change: agents' prompts call it by name). Owner-only: public tools can't be changed over MCP. Refused while the tool is attached to any agent you don't own, including owner-less ones and agents shared with you, since the change would reach another owner's agent; detach it there first or create a new tool. Runs already started keep the version they loaded for their whole lifetime; the next run picks up the new one.",
|
|
102
|
+
inputSchema: {
|
|
103
|
+
type: "object",
|
|
104
|
+
additionalProperties: false,
|
|
105
|
+
properties: {
|
|
106
|
+
toolId: { type: "string" },
|
|
107
|
+
description: { type: "string" },
|
|
108
|
+
paramsZod: { type: "string" },
|
|
109
|
+
code: { type: "string" },
|
|
110
|
+
},
|
|
111
|
+
required: ["toolId"],
|
|
112
|
+
},
|
|
113
|
+
handler: async (args, ctx) => {
|
|
114
|
+
const changes = { description: args.description, paramsZod: args.paramsZod, code: args.code };
|
|
115
|
+
if (!hasToolChanges(changes)) {
|
|
116
|
+
throw new McpError(400, "update_tool needs at least one of description, paramsZod or code.");
|
|
117
|
+
}
|
|
118
|
+
// Checked once up front so a non-owner never gets as far as a sandbox
|
|
119
|
+
// compile, and again (with the attachments) inside the transaction.
|
|
120
|
+
await requireStrictlyOwnedTool(ctx.db, args.toolId, ctx.principal.id);
|
|
121
|
+
const prepared = await prepareToolUpdate(changes);
|
|
122
|
+
if (!prepared.ok) {
|
|
123
|
+
return textResult({ ok: false, errorKind: prepared.errorKind, errorMessage: prepared.errorMessage });
|
|
124
|
+
}
|
|
125
|
+
// Any attachment to an agent the caller doesn't own (an owner-less one
|
|
126
|
+
// included) blocks every field; see core/tool-admin.ts for why, and
|
|
127
|
+
// for the Serializable transaction that keeps a concurrent attach from
|
|
128
|
+
// landing between the check and the write.
|
|
129
|
+
try {
|
|
130
|
+
const tool = await updateToolGuarded(ctx.db, args.toolId, prepared.data, (row) => assertStrictlyOwnedTool(row, args.toolId, ctx.principal.id));
|
|
131
|
+
return textResult(tool);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
if (err instanceof ToolAttachedError) {
|
|
135
|
+
throw new McpError(409, `Tool "${args.toolId}" is attached to agents you don't own: ${await describeAgents(err.agents, ctx)}. Detach it from those agents first (or ask their owners to), or create a new tool instead.`);
|
|
136
|
+
}
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
mcp.registerTool({
|
|
142
|
+
name: "delete_tool",
|
|
143
|
+
scope: "tools:write",
|
|
144
|
+
description: "Deletes a tool you own. Refused while it is attached to any agent; pass detach: true to detach it from your own agents first. It is never detached from an agent you don't own -- use detach_tool where you hold write on the agent, or ask its owner. Past runs are unaffected, and a run already under way keeps the version it loaded.",
|
|
145
|
+
inputSchema: {
|
|
146
|
+
type: "object",
|
|
147
|
+
additionalProperties: false,
|
|
148
|
+
properties: { toolId: { type: "string" }, detach: { type: "boolean" } },
|
|
149
|
+
required: ["toolId"],
|
|
150
|
+
},
|
|
151
|
+
handler: async (args, ctx) => {
|
|
152
|
+
try {
|
|
153
|
+
const detachedFrom = await deleteToolGuarded(ctx.db, args.toolId, {
|
|
154
|
+
detach: args.detach === true,
|
|
155
|
+
authorize: (row) => assertStrictlyOwnedTool(row, args.toolId, ctx.principal.id),
|
|
156
|
+
});
|
|
157
|
+
return textResult({ deleted: args.toolId, detachedFrom });
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
if (!(err instanceof ToolAttachedError))
|
|
161
|
+
throw err;
|
|
162
|
+
const agents = await describeAgents(err.agents, ctx);
|
|
163
|
+
switch (err.reason) {
|
|
164
|
+
// Public agents are included: pulling a tool from a shared agent
|
|
165
|
+
// should be an explicit detach_tool.
|
|
166
|
+
case "other_owners":
|
|
167
|
+
throw new McpError(409, `Tool "${args.toolId}" is still attached to ${agents}. Only your own agents can be detached here; use detach_tool where you hold write on the agent, or ask the other owners to.`);
|
|
168
|
+
case "needs_detach":
|
|
169
|
+
throw new McpError(409, `Tool "${args.toolId}" is still attached to ${agents}. Pass detach: true to detach it from these agents and delete it.`);
|
|
170
|
+
case "race":
|
|
171
|
+
throw new McpError(409, `Tool "${args.toolId}" is still attached to an agent; nothing was deleted.`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
54
174
|
},
|
|
55
175
|
});
|
|
56
176
|
mcp.registerTool({
|
|
@@ -112,6 +232,7 @@ export function registerToolAuthoringTools(mcp) {
|
|
|
112
232
|
mcp.registerTool({
|
|
113
233
|
name: "attach_tool",
|
|
114
234
|
scope: "tools:write",
|
|
235
|
+
description: "Attaches a tool to an agent (needs write on the agent). The four capability fields (allowedSecrets, allowedDatastorePrefixes, allowedHosts, allowedSharedDatastorePrefixes) are the agent owner's to grant: anyone else passing one gets 403, and their attachment runs with none until the owner re-runs attach_tool with the capabilities. When the owner grants an attachment it had not granted before (someone else attached it, or the agent changed owner), every capability not passed is reset to empty, so state each one you want. Granting capabilities to a tool someone else owns vouches for code you can't read (only its owner sees it); the tool's owner can't change it while it's attached to your agent, and detaching drops your grant.",
|
|
115
236
|
inputSchema: {
|
|
116
237
|
type: "object",
|
|
117
238
|
properties: {
|
|
@@ -141,35 +262,72 @@ export function registerToolAuthoringTools(mcp) {
|
|
|
141
262
|
if (!patch.success) {
|
|
142
263
|
throw new McpError(400, `Invalid tool capabilities: ${patch.error.issues.map((i) => i.message).join("; ")}`);
|
|
143
264
|
}
|
|
265
|
+
const capabilitiesPassed = Object.values(patch.data).some((value) => value !== undefined);
|
|
144
266
|
await ctx.db.$transaction(async (tx) => {
|
|
145
|
-
const agent = await tx.agent.findUnique({ where: { id: args.agentId } });
|
|
146
|
-
if (!agent)
|
|
147
|
-
throw new McpError(404, `Agent "${args.agentId}" not found.`);
|
|
148
|
-
assertCanMutate(agent.ownerId, ctx.principal.id, `Agent "${args.agentId}" is not owned by the caller.`);
|
|
267
|
+
const { agent } = await assertAgentAccess(ctx, await tx.agent.findUnique({ where: { id: args.agentId } }), args.agentId, "write", tx);
|
|
149
268
|
if (agent.kind === "coding") {
|
|
150
269
|
throw new McpError(400, "Native sandbox tools cannot be attached to coding agents.");
|
|
151
270
|
}
|
|
152
|
-
|
|
271
|
+
// Capabilities carry the owner's consent (resource-sharing grants
|
|
272
|
+
// spec §3.4.2). Strict ownership: neither a write-grantee nor the
|
|
273
|
+
// stdio operator may hand the owner's secrets, datastores or
|
|
274
|
+
// network to code on the agent.
|
|
275
|
+
const isOwner = agent.ownerId !== null && agent.ownerId === ctx.principal.id;
|
|
276
|
+
if (capabilitiesPassed && !isOwner) {
|
|
277
|
+
throw new McpError(403, "Only the agent's owner can grant capabilities to an attachment; ask them to re-run attach_tool with the capabilities.");
|
|
278
|
+
}
|
|
279
|
+
const existing = await tx.agentTool.findUnique({
|
|
280
|
+
where: { agentId_toolId: { agentId: args.agentId, toolId: args.toolId } },
|
|
281
|
+
});
|
|
282
|
+
// Creating an attachment needs a tool the caller may attach (Phase
|
|
283
|
+
// 1: its own or an owner-less one). Re-attaching an existing one
|
|
284
|
+
// only changes its capabilities: that is how the agent's owner
|
|
285
|
+
// grants them to a write-grantee's tool, or re-grants them after a
|
|
286
|
+
// make_owner transfer, so the tool check does not apply.
|
|
287
|
+
const tool = existing
|
|
288
|
+
? await tx.tool.findUniqueOrThrow({ where: { id: args.toolId } })
|
|
289
|
+
: await requireOwnedTool(tx, args.toolId, ctx.principal.id);
|
|
290
|
+
// The runtime dispatches by name, and names are only unique per
|
|
291
|
+
// owner: a second same-named tool on one agent would be a
|
|
292
|
+
// duplicate tool name to the model and silently shadow one of them.
|
|
293
|
+
const clash = await findSameNamedAttachedTool(tx, args.agentId, tool);
|
|
294
|
+
if (clash) {
|
|
295
|
+
// The clashing tool may be another principal's private one (a
|
|
296
|
+
// write-grantee's, or one left from before a transfer): name its
|
|
297
|
+
// id only when the caller could read it anyway.
|
|
298
|
+
const which = canRead(clash.ownerId, ctx.principal.id)
|
|
299
|
+
? ` (${clash.id})`
|
|
300
|
+
: " (a tool owned by another principal)";
|
|
301
|
+
throw new McpError(409, `Agent "${args.agentId}" already has a different tool named "${tool.name}" attached${which}; detach it first.`);
|
|
302
|
+
}
|
|
303
|
+
// Re-granting an attachment this owner never vouched for: what is
|
|
304
|
+
// not restated is reset, never silently adopted.
|
|
305
|
+
const keepUnstated = existing?.capabilitiesGrantedById === agent.ownerId;
|
|
306
|
+
const field = (key, empty) => patch.data[key] !== undefined ? { [key]: patch.data[key] } : keepUnstated ? {} : { [key]: empty };
|
|
307
|
+
const ownerGrant = isOwner
|
|
308
|
+
? {
|
|
309
|
+
...field("allowedSecrets", []),
|
|
310
|
+
...field("allowedDatastorePrefixes", []),
|
|
311
|
+
...field("allowedHosts", []),
|
|
312
|
+
...field("allowedSharedDatastorePrefixes", {}),
|
|
313
|
+
capabilitiesGrantedById: agent.ownerId,
|
|
314
|
+
}
|
|
315
|
+
: {};
|
|
153
316
|
await tx.agentTool.upsert({
|
|
154
317
|
where: { agentId_toolId: { agentId: args.agentId, toolId: args.toolId } },
|
|
155
318
|
create: {
|
|
156
319
|
agentId: args.agentId,
|
|
157
320
|
toolId: args.toolId,
|
|
158
|
-
allowedSecrets: patch.data.allowedSecrets
|
|
159
|
-
allowedDatastorePrefixes: patch.data.allowedDatastorePrefixes
|
|
160
|
-
allowedHosts: patch.data.allowedHosts
|
|
161
|
-
allowedSharedDatastorePrefixes: patch.data.allowedSharedDatastorePrefixes
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
...(patch.data.allowedSecrets !== undefined ? { allowedSecrets: patch.data.allowedSecrets } : {}),
|
|
165
|
-
...(patch.data.allowedDatastorePrefixes !== undefined
|
|
166
|
-
? { allowedDatastorePrefixes: patch.data.allowedDatastorePrefixes }
|
|
167
|
-
: {}),
|
|
168
|
-
...(patch.data.allowedHosts !== undefined ? { allowedHosts: patch.data.allowedHosts } : {}),
|
|
169
|
-
...(patch.data.allowedSharedDatastorePrefixes !== undefined
|
|
170
|
-
? { allowedSharedDatastorePrefixes: patch.data.allowedSharedDatastorePrefixes }
|
|
171
|
-
: {}),
|
|
321
|
+
allowedSecrets: (isOwner && patch.data.allowedSecrets) || [],
|
|
322
|
+
allowedDatastorePrefixes: (isOwner && patch.data.allowedDatastorePrefixes) || [],
|
|
323
|
+
allowedHosts: (isOwner && patch.data.allowedHosts) || [],
|
|
324
|
+
allowedSharedDatastorePrefixes: (isOwner && patch.data.allowedSharedDatastorePrefixes) || {},
|
|
325
|
+
attachedById: ctx.principal.id,
|
|
326
|
+
capabilitiesGrantedById: isOwner ? agent.ownerId : null,
|
|
172
327
|
},
|
|
328
|
+
// A non-owner's re-attach leaves an existing attachment's
|
|
329
|
+
// capabilities and consent stamp untouched.
|
|
330
|
+
update: ownerGrant,
|
|
173
331
|
});
|
|
174
332
|
}, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
|
|
175
333
|
return textResult({ attached: true });
|
|
@@ -184,8 +342,9 @@ export function registerToolAuthoringTools(mcp) {
|
|
|
184
342
|
required: ["agentId", "toolId"],
|
|
185
343
|
},
|
|
186
344
|
handler: async (args, ctx) => {
|
|
187
|
-
|
|
188
|
-
|
|
345
|
+
// Write on the agent is enough, whoever owns the tool: an agent's
|
|
346
|
+
// owner (or write-grantee) can always pull a tool off it.
|
|
347
|
+
await requireAgentAccess(ctx, args.agentId, "write");
|
|
189
348
|
await ctx.db.agentTool.deleteMany({ where: { agentId: args.agentId, toolId: args.toolId } });
|
|
190
349
|
return textResult({ detached: true });
|
|
191
350
|
},
|
|
@@ -195,13 +354,15 @@ export function registerToolAuthoringTools(mcp) {
|
|
|
195
354
|
scope: "tools:write",
|
|
196
355
|
inputSchema: { type: "object", properties: { agentId: { type: "string" } } },
|
|
197
356
|
handler: async (args, ctx) => {
|
|
198
|
-
const project = (tool) => tool
|
|
357
|
+
const project = (tool) => projectTool(tool, ctx.principal.id);
|
|
199
358
|
if (args.agentId) {
|
|
200
|
-
await
|
|
359
|
+
const { access } = await requireAgentAccess(ctx, args.agentId, "read");
|
|
201
360
|
const rows = await ctx.db.agentTool.findMany({ where: { agentId: args.agentId }, include: { tool: true } });
|
|
361
|
+
// The agent's owner sees every tool on it (projected: code only for
|
|
362
|
+
// the tool's own owner); anyone else only tools they could see anyway.
|
|
202
363
|
return textResult(rows
|
|
203
364
|
.map((r) => r.tool)
|
|
204
|
-
.filter((tool) => canRead(tool.ownerId, ctx.principal.id))
|
|
365
|
+
.filter((tool) => access === "owner" || canRead(tool.ownerId, ctx.principal.id))
|
|
205
366
|
.map(project));
|
|
206
367
|
}
|
|
207
368
|
const tools = await ctx.db.tool.findMany({ where: visibleToPrincipal(ctx.principal.id) });
|
|
@@ -33,7 +33,8 @@ import { CodingBaseRefSchema, CodingTaskOverrideSchema } from "../../coding/prot
|
|
|
33
33
|
import { z } from "zod";
|
|
34
34
|
import { McpError } from "../errors.js";
|
|
35
35
|
import { createTaskResult, getTask, cancelTask } from "../tasks/manager.js";
|
|
36
|
-
import {
|
|
36
|
+
import { requireOwnedTask } from "../auth/ownership.js";
|
|
37
|
+
import { assertAgentAccess } from "../auth/access.js";
|
|
37
38
|
import { textResult } from "./text-result.js";
|
|
38
39
|
const DEFAULT_TASK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
39
40
|
const TriggerAgentSchema = z
|
|
@@ -50,6 +51,29 @@ function parseTriggerArgs(args) {
|
|
|
50
51
|
const details = parsed.error.issues.map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`).join("; ");
|
|
51
52
|
throw new McpError(400, `Invalid trigger_agent arguments: ${details}`);
|
|
52
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* A non-owner's coding task or baseRef would steer what the owner's
|
|
56
|
+
* repository authority does, so it is allowed only when the owner opted in
|
|
57
|
+
* to non-owner task text (codingProfile.allowWebhookTaskOverride, the same
|
|
58
|
+
* opt-in webhooks use). Otherwise a non-owner run uses the owner's
|
|
59
|
+
* defaultTask. Resource-sharing grants spec §3.4.5.
|
|
60
|
+
*/
|
|
61
|
+
function assertOverrideAllowed(principalId, agent, args) {
|
|
62
|
+
// Strictly the owner: the stdio operator's owner-level access doesn't make
|
|
63
|
+
// it the owner whose repository authority the task steers (review M8).
|
|
64
|
+
const isOwner = agent.ownerId !== null && agent.ownerId === principalId;
|
|
65
|
+
if (isOwner || (args.task === undefined && args.baseRef === undefined))
|
|
66
|
+
return;
|
|
67
|
+
if (!agent.codingProfile?.allowWebhookTaskOverride) {
|
|
68
|
+
throw new McpError(403, `Agent "${agent.id}": only its owner can pass a task or baseRef, unless the owner allows it (codingProfile.allowWebhookTaskOverride).`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** execute on the agent, plus the override rule; `db` is the transaction when re-checking. */
|
|
72
|
+
async function requireTriggerable(ctx, row, id, args, db = ctx.db) {
|
|
73
|
+
const { agent } = await assertAgentAccess(ctx, row, id, "execute", db);
|
|
74
|
+
assertOverrideAllowed(ctx.principal.id, agent, args);
|
|
75
|
+
return agent;
|
|
76
|
+
}
|
|
53
77
|
export function registerTriggerTool(mcp) {
|
|
54
78
|
mcp.registerTool({
|
|
55
79
|
name: "trigger_agent",
|
|
@@ -62,7 +86,7 @@ export function registerTriggerTool(mcp) {
|
|
|
62
86
|
},
|
|
63
87
|
handler: async (rawArgs, ctx) => {
|
|
64
88
|
const args = parseTriggerArgs(rawArgs);
|
|
65
|
-
const agent = await
|
|
89
|
+
const agent = await requireTriggerable(ctx, await ctx.db.agent.findUnique({ where: { id: args.agentId }, include: { codingProfile: true } }), args.agentId, args);
|
|
66
90
|
if (agent.kind !== "coding" && (args.task !== undefined || args.baseRef !== undefined)) {
|
|
67
91
|
throw new McpError(400, "Task and baseRef overrides are only valid for coding agents.");
|
|
68
92
|
}
|
|
@@ -74,10 +98,11 @@ export function registerTriggerTool(mcp) {
|
|
|
74
98
|
codingTask: args.task,
|
|
75
99
|
codingBaseRef: args.baseRef,
|
|
76
100
|
task: ctx.clientSupportsTasks ? { principalId: ctx.principal.id, ttlMs: DEFAULT_TASK_TTL_MS } : undefined,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
101
|
+
triggeredById: ctx.principal.id,
|
|
102
|
+
// Re-checked through the transaction: a concurrent revoke or
|
|
103
|
+
// make_owner conflicts instead of racing the run in.
|
|
104
|
+
beforePersist: async (tx, current) => {
|
|
105
|
+
await requireTriggerable(ctx, current, agent.id, args, tx);
|
|
81
106
|
return true;
|
|
82
107
|
},
|
|
83
108
|
});
|
|
@@ -88,6 +113,11 @@ export function registerTriggerTool(mcp) {
|
|
|
88
113
|
throw new Error("Run task was not persisted.");
|
|
89
114
|
return textResult(createTaskResult(dispatched.task, DEFAULT_TASK_TTL_MS));
|
|
90
115
|
}
|
|
116
|
+
// A run refused at dispatch (its budget group or run tree is spent) is
|
|
117
|
+
// already terminal: say so now rather than only through get_run.
|
|
118
|
+
if (dispatched.run.status === "refused") {
|
|
119
|
+
return textResult({ runId: dispatched.run.id, status: "refused", error: dispatched.run.error });
|
|
120
|
+
}
|
|
91
121
|
return textResult({ runId: dispatched.run.id });
|
|
92
122
|
},
|
|
93
123
|
});
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { createWebhook, listWebhooks, deleteWebhook } from "../../core/webhooks.js";
|
|
2
|
-
import {
|
|
2
|
+
import { McpError } from "../errors.js";
|
|
3
|
+
import { requireAgentAccess } from "../auth/access.js";
|
|
3
4
|
import { textResult } from "./text-result.js";
|
|
4
5
|
export function registerWebhookTools(mcp) {
|
|
5
6
|
mcp.registerTool({
|
|
6
7
|
name: "create_webhook",
|
|
7
8
|
scope: "webhooks:write",
|
|
9
|
+
description: "Creates a standing trigger for an agent (needs write on it) and returns its secret once. It fires only while you still own the agent or hold execute on it.",
|
|
8
10
|
inputSchema: { type: "object", properties: { agentId: { type: "string" } }, required: ["agentId"] },
|
|
9
11
|
handler: async (args, ctx) => {
|
|
10
|
-
|
|
12
|
+
// A standing trigger that can also carry task text for a native agent:
|
|
13
|
+
// write, not just execute (resource-sharing grants spec §3.10).
|
|
14
|
+
await requireAgentAccess(ctx, args.agentId, "write");
|
|
11
15
|
const created = await createWebhook(args.agentId, ctx.principal.id, ctx.db);
|
|
12
16
|
return textResult(created);
|
|
13
17
|
},
|
|
@@ -15,18 +19,25 @@ export function registerWebhookTools(mcp) {
|
|
|
15
19
|
mcp.registerTool({
|
|
16
20
|
name: "list_webhooks",
|
|
17
21
|
scope: "webhooks:write",
|
|
22
|
+
description: "Lists webhooks you created, and every webhook on an agent you own.",
|
|
18
23
|
inputSchema: { type: "object", properties: {} },
|
|
19
24
|
handler: async (_args, ctx) => {
|
|
20
|
-
const webhooks = await listWebhooks(ctx.principal.id, ctx.db);
|
|
25
|
+
const webhooks = await listWebhooks(ctx.principal.id, ctx.db, { all: ctx.operator === true });
|
|
21
26
|
return textResult(webhooks);
|
|
22
27
|
},
|
|
23
28
|
});
|
|
24
29
|
mcp.registerTool({
|
|
25
30
|
name: "delete_webhook",
|
|
26
31
|
scope: "webhooks:write",
|
|
32
|
+
description: "Deletes a webhook you created, or any webhook on an agent you own.",
|
|
27
33
|
inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
|
|
28
34
|
handler: async (args, ctx) => {
|
|
29
|
-
await
|
|
35
|
+
const webhook = await ctx.db.webhook.findUnique({ where: { id: args.id }, include: { agent: true } });
|
|
36
|
+
const principalId = ctx.principal.id;
|
|
37
|
+
const allowed = webhook !== null &&
|
|
38
|
+
(ctx.operator === true || webhook.ownerId === principalId || webhook.agent.ownerId === principalId);
|
|
39
|
+
if (!allowed)
|
|
40
|
+
throw new McpError(403, `Webhook "${args.id}" is not owned by the caller.`);
|
|
30
41
|
await deleteWebhook(args.id, ctx.db);
|
|
31
42
|
return textResult({ deleted: args.id });
|
|
32
43
|
},
|
|
@@ -5,6 +5,8 @@ import type { AuthProvider } from "../../providers/auth/types.js";
|
|
|
5
5
|
import type { SelfHostedAuthProvider } from "../../providers/auth/self-hosted.js";
|
|
6
6
|
import type { WardbyMcpServer } from "../server.js";
|
|
7
7
|
import type { McpProviders } from "../context.js";
|
|
8
|
+
import { type GitHubIngressDeps } from "../host-events/github-ingress.js";
|
|
9
|
+
import { type HostUserCallbackDeps } from "../host-events/github-user-callback.js";
|
|
8
10
|
export interface HttpServerConfig {
|
|
9
11
|
canonicalUri: string;
|
|
10
12
|
httpBind: {
|
|
@@ -25,6 +27,14 @@ export interface StartHttpServerOptions {
|
|
|
25
27
|
providers: McpProviders;
|
|
26
28
|
};
|
|
27
29
|
selfHosted?: SelfHostedAuthProvider;
|
|
30
|
+
/** Code-review host webhooks; absent = the endpoints answer 404. */
|
|
31
|
+
hostEvents?: {
|
|
32
|
+
github?: GitHubIngressDeps;
|
|
33
|
+
};
|
|
34
|
+
/** The host identity-link browser callback (link_host_account); absent = 404. */
|
|
35
|
+
hostUserAuth?: {
|
|
36
|
+
github?: HostUserCallbackDeps;
|
|
37
|
+
};
|
|
28
38
|
}
|
|
29
39
|
export interface HttpServerHandle {
|
|
30
40
|
port: number;
|