@wardby/cli 0.1.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 +131 -0
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +282 -0
- package/bin/wardby.js +3 -0
- package/deploy/local/docker-compose.yml +19 -0
- package/dist/claude-coding-worker/driver.d.ts +70 -0
- package/dist/claude-coding-worker/driver.js +128 -0
- package/dist/claude-coding-worker/main.d.ts +2 -0
- package/dist/claude-coding-worker/main.js +41 -0
- package/dist/claude-coding-worker/sdk.d.ts +4 -0
- package/dist/claude-coding-worker/sdk.js +43 -0
- package/dist/claude-coding-worker/tool-relay.d.ts +1 -0
- package/dist/claude-coding-worker/tool-relay.js +9 -0
- package/dist/cli-help.d.ts +1 -0
- package/dist/cli-help.js +20 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +629 -0
- package/dist/coding/observability.d.ts +59 -0
- package/dist/coding/observability.js +77 -0
- package/dist/coding/profile.d.ts +88 -0
- package/dist/coding/profile.js +137 -0
- package/dist/coding/protocol.d.ts +345 -0
- package/dist/coding/protocol.js +445 -0
- package/dist/coding/provider.d.ts +4 -0
- package/dist/coding/provider.js +18 -0
- package/dist/coding-proxy/main.d.ts +1 -0
- package/dist/coding-proxy/main.js +51 -0
- package/dist/coding-worker/artifact.d.ts +16 -0
- package/dist/coding-worker/artifact.js +37 -0
- package/dist/coding-worker/driver.d.ts +46 -0
- package/dist/coding-worker/driver.js +158 -0
- package/dist/coding-worker/errors.d.ts +1 -0
- package/dist/coding-worker/errors.js +23 -0
- package/dist/coding-worker/isolation-probe.d.ts +1 -0
- package/dist/coding-worker/isolation-probe.js +146 -0
- package/dist/coding-worker/keeper.d.ts +4 -0
- package/dist/coding-worker/keeper.js +29 -0
- package/dist/coding-worker/main.d.ts +2 -0
- package/dist/coding-worker/main.js +43 -0
- package/dist/coding-worker/sdk.d.ts +26 -0
- package/dist/coding-worker/sdk.js +28 -0
- package/dist/coding-worker/types.d.ts +64 -0
- package/dist/coding-worker/types.js +1 -0
- package/dist/config/providers.d.ts +158 -0
- package/dist/config/providers.js +162 -0
- package/dist/core/budget-groups.d.ts +72 -0
- package/dist/core/budget-groups.js +149 -0
- package/dist/core/budget.d.ts +63 -0
- package/dist/core/budget.js +69 -0
- package/dist/core/coding-queue.d.ts +32 -0
- package/dist/core/coding-queue.js +54 -0
- package/dist/core/cron.d.ts +31 -0
- package/dist/core/cron.js +43 -0
- package/dist/core/datastores.d.ts +41 -0
- package/dist/core/datastores.js +105 -0
- package/dist/core/db.d.ts +2 -0
- package/dist/core/db.js +2 -0
- package/dist/core/dispatch.d.ts +81 -0
- package/dist/core/dispatch.js +184 -0
- package/dist/core/engine-native.d.ts +29 -0
- package/dist/core/engine-native.js +259 -0
- package/dist/core/http-runtime.d.ts +7 -0
- package/dist/core/http-runtime.js +101 -0
- package/dist/core/lease.d.ts +13 -0
- package/dist/core/lease.js +22 -0
- package/dist/core/logger.d.ts +13 -0
- package/dist/core/logger.js +45 -0
- package/dist/core/memory-tools.d.ts +11 -0
- package/dist/core/memory-tools.js +122 -0
- package/dist/core/reconciler.d.ts +45 -0
- package/dist/core/reconciler.js +152 -0
- package/dist/core/runner.d.ts +87 -0
- package/dist/core/runner.js +529 -0
- package/dist/core/scheduler.d.ts +47 -0
- package/dist/core/scheduler.js +121 -0
- package/dist/core/secrets.d.ts +46 -0
- package/dist/core/secrets.js +100 -0
- package/dist/core/subagent-memory-tools.d.ts +18 -0
- package/dist/core/subagent-memory-tools.js +121 -0
- package/dist/core/timing.d.ts +7 -0
- package/dist/core/timing.js +7 -0
- package/dist/core/webhooks.d.ts +23 -0
- package/dist/core/webhooks.js +113 -0
- package/dist/env.d.ts +11 -0
- package/dist/env.js +13 -0
- package/dist/import/budgets.d.ts +16 -0
- package/dist/import/budgets.js +21 -0
- package/dist/import/bundle.d.ts +16 -0
- package/dist/import/bundle.js +61 -0
- package/dist/import/cli-args.d.ts +2 -0
- package/dist/import/cli-args.js +44 -0
- package/dist/import/create.d.ts +34 -0
- package/dist/import/create.js +363 -0
- package/dist/import/index.d.ts +25 -0
- package/dist/import/index.js +130 -0
- package/dist/import/model-gate.d.ts +1 -0
- package/dist/import/model-gate.js +3 -0
- package/dist/import/neutral-schema.d.ts +369 -0
- package/dist/import/neutral-schema.js +109 -0
- package/dist/import/preflight.d.ts +41 -0
- package/dist/import/preflight.js +71 -0
- package/dist/import/report.d.ts +5 -0
- package/dist/import/report.js +98 -0
- package/dist/import/tool-scan.d.ts +8 -0
- package/dist/import/tool-scan.js +20 -0
- package/dist/mcp/auth/ownership.d.ts +113 -0
- package/dist/mcp/auth/ownership.js +87 -0
- package/dist/mcp/auth/principal.d.ts +9 -0
- package/dist/mcp/auth/principal.js +9 -0
- package/dist/mcp/auth/resource-server.d.ts +47 -0
- package/dist/mcp/auth/resource-server.js +82 -0
- package/dist/mcp/auth/self-hosted/browser.d.ts +3 -0
- package/dist/mcp/auth/self-hosted/browser.js +273 -0
- package/dist/mcp/auth/self-hosted/cli.d.ts +2 -0
- package/dist/mcp/auth/self-hosted/cli.js +39 -0
- package/dist/mcp/auth/self-hosted/credentials.d.ts +58 -0
- package/dist/mcp/auth/self-hosted/credentials.js +106 -0
- package/dist/mcp/auth/self-hosted/rate-limit.d.ts +13 -0
- package/dist/mcp/auth/self-hosted/rate-limit.js +20 -0
- package/dist/mcp/auth/self-hosted/session.d.ts +36 -0
- package/dist/mcp/auth/self-hosted/session.js +110 -0
- package/dist/mcp/capabilities.d.ts +11 -0
- package/dist/mcp/capabilities.js +10 -0
- package/dist/mcp/context.d.ts +46 -0
- package/dist/mcp/context.js +1 -0
- package/dist/mcp/errors.d.ts +13 -0
- package/dist/mcp/errors.js +22 -0
- package/dist/mcp/index.d.ts +32 -0
- package/dist/mcp/index.js +187 -0
- package/dist/mcp/jsonrpc.d.ts +8 -0
- package/dist/mcp/jsonrpc.js +1 -0
- package/dist/mcp/server.d.ts +107 -0
- package/dist/mcp/server.js +203 -0
- package/dist/mcp/tasks/manager.d.ts +63 -0
- package/dist/mcp/tasks/manager.js +103 -0
- package/dist/mcp/tools/agents.d.ts +2 -0
- package/dist/mcp/tools/agents.js +375 -0
- package/dist/mcp/tools/budget-groups.d.ts +2 -0
- package/dist/mcp/tools/budget-groups.js +136 -0
- package/dist/mcp/tools/datastore.d.ts +2 -0
- package/dist/mcp/tools/datastore.js +217 -0
- package/dist/mcp/tools/memory.d.ts +3 -0
- package/dist/mcp/tools/memory.js +60 -0
- package/dist/mcp/tools/models.d.ts +8 -0
- package/dist/mcp/tools/models.js +15 -0
- package/dist/mcp/tools/runs.d.ts +2 -0
- package/dist/mcp/tools/runs.js +61 -0
- package/dist/mcp/tools/scheduling.d.ts +2 -0
- package/dist/mcp/tools/scheduling.js +48 -0
- package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -0
- package/dist/mcp/tools/secret-elicitation-form.js +93 -0
- package/dist/mcp/tools/secret-elicitation-server.d.ts +5 -0
- package/dist/mcp/tools/secret-elicitation-server.js +57 -0
- package/dist/mcp/tools/secret-elicitation.d.ts +38 -0
- package/dist/mcp/tools/secret-elicitation.js +52 -0
- package/dist/mcp/tools/secrets.d.ts +13 -0
- package/dist/mcp/tools/secrets.js +124 -0
- package/dist/mcp/tools/subagents.d.ts +2 -0
- package/dist/mcp/tools/subagents.js +133 -0
- package/dist/mcp/tools/text-result.d.ts +6 -0
- package/dist/mcp/tools/text-result.js +3 -0
- package/dist/mcp/tools/tools.d.ts +2 -0
- package/dist/mcp/tools/tools.js +211 -0
- package/dist/mcp/tools/trigger.d.ts +2 -0
- package/dist/mcp/tools/trigger.js +120 -0
- package/dist/mcp/tools/webhooks.d.ts +2 -0
- package/dist/mcp/tools/webhooks.js +34 -0
- package/dist/mcp/transport/http-limits.d.ts +17 -0
- package/dist/mcp/transport/http-limits.js +92 -0
- package/dist/mcp/transport/stdio.d.ts +23 -0
- package/dist/mcp/transport/stdio.js +6 -0
- package/dist/mcp/transport/streamable-http.d.ts +34 -0
- package/dist/mcp/transport/streamable-http.js +158 -0
- package/dist/mcp/unattended-schedules.d.ts +15 -0
- package/dist/mcp/unattended-schedules.js +21 -0
- package/dist/mcp/webhooks/ingress.d.ts +17 -0
- package/dist/mcp/webhooks/ingress.js +33 -0
- package/dist/observability/config.d.ts +6 -0
- package/dist/observability/config.js +20 -0
- package/dist/observability/metrics-server.d.ts +11 -0
- package/dist/observability/metrics-server.js +47 -0
- package/dist/observability/metrics.d.ts +36 -0
- package/dist/observability/metrics.js +158 -0
- package/dist/providers/auth/authorization-server.d.ts +41 -0
- package/dist/providers/auth/authorization-server.js +1 -0
- package/dist/providers/auth/delegating.d.ts +44 -0
- package/dist/providers/auth/delegating.js +105 -0
- package/dist/providers/auth/index.d.ts +11 -0
- package/dist/providers/auth/index.js +20 -0
- package/dist/providers/auth/release-gate.d.ts +1 -0
- package/dist/providers/auth/release-gate.js +4 -0
- package/dist/providers/auth/self-hosted.d.ts +78 -0
- package/dist/providers/auth/self-hosted.js +377 -0
- package/dist/providers/auth/subject.d.ts +1 -0
- package/dist/providers/auth/subject.js +6 -0
- package/dist/providers/auth/types.d.ts +63 -0
- package/dist/providers/auth/types.js +22 -0
- package/dist/providers/coding-proxy/deny-port.d.ts +5 -0
- package/dist/providers/coding-proxy/deny-port.js +45 -0
- package/dist/providers/coding-proxy/environment-credentials.d.ts +7 -0
- package/dist/providers/coding-proxy/environment-credentials.js +16 -0
- package/dist/providers/coding-proxy/index.d.ts +7 -0
- package/dist/providers/coding-proxy/index.js +7 -0
- package/dist/providers/coding-proxy/memory-ledger.d.ts +17 -0
- package/dist/providers/coding-proxy/memory-ledger.js +103 -0
- package/dist/providers/coding-proxy/metering.d.ts +20 -0
- package/dist/providers/coding-proxy/metering.js +161 -0
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +15 -0
- package/dist/providers/coding-proxy/prisma-ledger.js +213 -0
- package/dist/providers/coding-proxy/proxy.d.ts +75 -0
- package/dist/providers/coding-proxy/proxy.js +721 -0
- package/dist/providers/coding-proxy/runtime.d.ts +18 -0
- package/dist/providers/coding-proxy/runtime.js +39 -0
- package/dist/providers/coding-proxy/secure-fetch.d.ts +9 -0
- package/dist/providers/coding-proxy/secure-fetch.js +72 -0
- package/dist/providers/coding-proxy/server.d.ts +17 -0
- package/dist/providers/coding-proxy/server.js +139 -0
- package/dist/providers/coding-proxy/types.d.ts +99 -0
- package/dist/providers/coding-proxy/types.js +1 -0
- package/dist/providers/datastore/index.d.ts +2 -0
- package/dist/providers/datastore/index.js +2 -0
- package/dist/providers/datastore/postgres.d.ts +20 -0
- package/dist/providers/datastore/postgres.js +130 -0
- package/dist/providers/datastore/scoped.d.ts +10 -0
- package/dist/providers/datastore/scoped.js +46 -0
- package/dist/providers/datastore/types.d.ts +31 -0
- package/dist/providers/datastore/types.js +7 -0
- package/dist/providers/email/types.d.ts +46 -0
- package/dist/providers/email/types.js +8 -0
- package/dist/providers/engine/index.d.ts +1 -0
- package/dist/providers/engine/index.js +1 -0
- package/dist/providers/engine/types.d.ts +67 -0
- package/dist/providers/engine/types.js +16 -0
- package/dist/providers/executor/build.d.ts +6 -0
- package/dist/providers/executor/build.js +15 -0
- package/dist/providers/executor/composition.d.ts +14 -0
- package/dist/providers/executor/composition.js +123 -0
- package/dist/providers/executor/container.d.ts +184 -0
- package/dist/providers/executor/container.js +936 -0
- package/dist/providers/executor/dbos-status.d.ts +50 -0
- package/dist/providers/executor/dbos-status.js +64 -0
- package/dist/providers/executor/dbos.d.ts +53 -0
- package/dist/providers/executor/dbos.js +269 -0
- package/dist/providers/executor/in-process.d.ts +18 -0
- package/dist/providers/executor/in-process.js +38 -0
- package/dist/providers/executor/index.d.ts +8 -0
- package/dist/providers/executor/index.js +8 -0
- package/dist/providers/executor/routing.d.ts +48 -0
- package/dist/providers/executor/routing.js +70 -0
- package/dist/providers/executor/types.d.ts +55 -0
- package/dist/providers/executor/types.js +1 -0
- package/dist/providers/index.d.ts +43 -0
- package/dist/providers/index.js +18 -0
- package/dist/providers/jobs/docker-isolation.d.ts +166 -0
- package/dist/providers/jobs/docker-isolation.js +724 -0
- package/dist/providers/jobs/docker.d.ts +127 -0
- package/dist/providers/jobs/docker.js +1027 -0
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +98 -0
- package/dist/providers/jobs/fake-kubernetes-api.js +142 -0
- package/dist/providers/jobs/fake.d.ts +23 -0
- package/dist/providers/jobs/fake.js +132 -0
- package/dist/providers/jobs/kubernetes-api.d.ts +60 -0
- package/dist/providers/jobs/kubernetes-api.js +20 -0
- package/dist/providers/jobs/kubernetes-client.d.ts +36 -0
- package/dist/providers/jobs/kubernetes-client.js +171 -0
- package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +49 -0
- package/dist/providers/jobs/kubernetes-dry-run-fixture.js +141 -0
- package/dist/providers/jobs/kubernetes-isolation.d.ts +147 -0
- package/dist/providers/jobs/kubernetes-isolation.js +607 -0
- package/dist/providers/jobs/kubernetes-platform.d.ts +177 -0
- package/dist/providers/jobs/kubernetes-platform.js +280 -0
- package/dist/providers/jobs/kubernetes-preflight.d.ts +59 -0
- package/dist/providers/jobs/kubernetes-preflight.js +339 -0
- package/dist/providers/jobs/kubernetes-witness.d.ts +10 -0
- package/dist/providers/jobs/kubernetes-witness.js +193 -0
- package/dist/providers/jobs/kubernetes.d.ts +127 -0
- package/dist/providers/jobs/kubernetes.js +874 -0
- package/dist/providers/jobs/safe-extract.d.ts +9 -0
- package/dist/providers/jobs/safe-extract.js +325 -0
- package/dist/providers/jobs/types.d.ts +78 -0
- package/dist/providers/jobs/types.js +6 -0
- package/dist/providers/jobs/workspace-swap.d.ts +2 -0
- package/dist/providers/jobs/workspace-swap.js +47 -0
- package/dist/providers/llm/anthropic.d.ts +13 -0
- package/dist/providers/llm/anthropic.js +21 -0
- package/dist/providers/llm/bedrock.d.ts +19 -0
- package/dist/providers/llm/bedrock.js +33 -0
- package/dist/providers/llm/claude-messages.d.ts +106 -0
- package/dist/providers/llm/claude-messages.js +157 -0
- package/dist/providers/llm/claude-provider.d.ts +40 -0
- package/dist/providers/llm/claude-provider.js +31 -0
- package/dist/providers/llm/index.d.ts +10 -0
- package/dist/providers/llm/index.js +10 -0
- package/dist/providers/llm/openai.d.ts +23 -0
- package/dist/providers/llm/openai.js +156 -0
- package/dist/providers/llm/pricing-anthropic.d.ts +4 -0
- package/dist/providers/llm/pricing-anthropic.js +30 -0
- package/dist/providers/llm/pricing-bedrock-claude.d.ts +12 -0
- package/dist/providers/llm/pricing-bedrock-claude.js +37 -0
- package/dist/providers/llm/pricing-core.d.ts +26 -0
- package/dist/providers/llm/pricing-core.js +17 -0
- package/dist/providers/llm/pricing.d.ts +30 -0
- package/dist/providers/llm/pricing.js +74 -0
- package/dist/providers/llm/registration.d.ts +8 -0
- package/dist/providers/llm/registration.js +39 -0
- package/dist/providers/llm/routing.d.ts +25 -0
- package/dist/providers/llm/routing.js +32 -0
- package/dist/providers/llm/types.d.ts +85 -0
- package/dist/providers/llm/types.js +12 -0
- package/dist/providers/memory/index.d.ts +2 -0
- package/dist/providers/memory/index.js +2 -0
- package/dist/providers/memory/postgres.d.ts +20 -0
- package/dist/providers/memory/postgres.js +72 -0
- package/dist/providers/memory/types.d.ts +32 -0
- package/dist/providers/memory/types.js +17 -0
- package/dist/providers/secrets/app-key.d.ts +8 -0
- package/dist/providers/secrets/app-key.js +50 -0
- package/dist/providers/secrets/index.d.ts +10 -0
- package/dist/providers/secrets/index.js +13 -0
- package/dist/providers/secrets/transfer-envelope.d.ts +12 -0
- package/dist/providers/secrets/transfer-envelope.js +35 -0
- package/dist/providers/secrets/types.d.ts +18 -0
- package/dist/providers/secrets/types.js +12 -0
- package/dist/providers/storage/types.d.ts +18 -0
- package/dist/providers/storage/types.js +7 -0
- package/dist/providers/vcs/git.d.ts +105 -0
- package/dist/providers/vcs/git.js +710 -0
- package/dist/providers/vcs/github.d.ts +105 -0
- package/dist/providers/vcs/github.js +394 -0
- package/dist/providers/vcs/index.d.ts +6 -0
- package/dist/providers/vcs/index.js +26 -0
- package/dist/providers/vcs/types.d.ts +118 -0
- package/dist/providers/vcs/types.js +1 -0
- package/dist/sandbox/bounded-json.d.ts +2 -0
- package/dist/sandbox/bounded-json.js +36 -0
- package/dist/sandbox/bridge.d.ts +15 -0
- package/dist/sandbox/bridge.js +78 -0
- package/dist/sandbox/eval-core.d.ts +37 -0
- package/dist/sandbox/eval-core.js +159 -0
- package/dist/sandbox/fetch-policy.d.ts +24 -0
- package/dist/sandbox/fetch-policy.js +96 -0
- package/dist/sandbox/generated/zod-to-json-schema.bundle.js +5 -0
- package/dist/sandbox/generated/zod.bundle.js +1 -0
- package/dist/sandbox/host-functions.d.ts +29 -0
- package/dist/sandbox/host-functions.js +168 -0
- package/dist/sandbox/limits.d.ts +39 -0
- package/dist/sandbox/limits.js +39 -0
- package/dist/sandbox/parser-worker/__fixtures__/crash.d.ts +1 -0
- package/dist/sandbox/parser-worker/__fixtures__/crash.js +6 -0
- package/dist/sandbox/parser-worker/__fixtures__/echo.d.ts +1 -0
- package/dist/sandbox/parser-worker/__fixtures__/echo.js +5 -0
- package/dist/sandbox/parser-worker/__fixtures__/reject.d.ts +1 -0
- package/dist/sandbox/parser-worker/__fixtures__/reject.js +5 -0
- package/dist/sandbox/parser-worker/__fixtures__/spin-forever.d.ts +1 -0
- package/dist/sandbox/parser-worker/__fixtures__/spin-forever.js +11 -0
- package/dist/sandbox/parser-worker/pool.d.ts +13 -0
- package/dist/sandbox/parser-worker/pool.js +111 -0
- package/dist/sandbox/parser-worker/worker.d.ts +14 -0
- package/dist/sandbox/parser-worker/worker.js +63 -0
- package/dist/sandbox/pii-redaction.d.ts +1 -0
- package/dist/sandbox/pii-redaction.js +29 -0
- package/dist/sandbox/prelude.d.ts +26 -0
- package/dist/sandbox/prelude.js +408 -0
- package/dist/sandbox/quickjs-module.d.ts +13 -0
- package/dist/sandbox/quickjs-module.js +19 -0
- package/dist/sandbox/run-in-sandbox.d.ts +34 -0
- package/dist/sandbox/run-in-sandbox.js +53 -0
- package/dist/sandbox/safe-fetch.d.ts +26 -0
- package/dist/sandbox/safe-fetch.js +140 -0
- package/dist/sandbox/tool-capabilities.d.ts +66 -0
- package/dist/sandbox/tool-capabilities.js +103 -0
- package/dist/sandbox/zod-params.d.ts +22 -0
- package/dist/sandbox/zod-params.js +60 -0
- package/dist/serve.d.ts +14 -0
- package/dist/serve.js +54 -0
- package/dist/wardby-bin.d.ts +2 -0
- package/dist/wardby-bin.js +20 -0
- package/package.json +123 -0
- package/prisma/migrations/20260905000000_init/migration.sql +34 -0
- package/prisma/migrations/20260905010000_scheduler_durability/migration.sql +31 -0
- package/prisma/migrations/20260905020000_tools_multiturn/migration.sql +47 -0
- package/prisma/migrations/20260905030000_cache_tool_json_schema/migration.sql +13 -0
- package/prisma/migrations/20260906000000_mcp_phase4/migration.sql +115 -0
- package/prisma/migrations/20260906010000_run_final_text/migration.sql +7 -0
- package/prisma/migrations/20260906020000_run_turns/migration.sql +6 -0
- package/prisma/migrations/20260906030000_task_principal_id/migration.sql +5 -0
- package/prisma/migrations/20260906040000_secure_self_hosted_oauth/migration.sql +67 -0
- package/prisma/migrations/20260906050000_phase5_coding_agents/migration.sql +52 -0
- package/prisma/migrations/20260906060000_tool_capability_scoping/migration.sql +22 -0
- package/prisma/migrations/20260906070000_datastore_pii_flag/migration.sql +6 -0
- package/prisma/migrations/20260906080000_secret_elicitation_outcomes/migration.sql +13 -0
- package/prisma/migrations/20260907010000_coding_proxy_ledger/migration.sql +57 -0
- package/prisma/migrations/20260907020000_budget_groups/migration.sql +24 -0
- package/prisma/migrations/20260907030000_coding_webhook_task_override/migration.sql +4 -0
- package/prisma/migrations/20260908010000_run_execution_backend/migration.sql +2 -0
- package/prisma/migrations/20260908020000_coding_run_audit_metadata/migration.sql +7 -0
- package/prisma/migrations/20260910010000_agent_secret_bound_name/migration.sql +16 -0
- package/prisma/migrations/20260911010000_coding_worker_toolchains/migration.sql +5 -0
- package/prisma/migrations/20260912010000_coding_provider_contract/migration.sql +24 -0
- package/prisma/migrations/20260912020000_coding_proxy_protocol/migration.sql +10 -0
- package/prisma/migrations/20260912030000_agent_memory/migration.sql +18 -0
- package/prisma/migrations/20260912040000_named_shared_datastores/migration.sql +68 -0
- package/prisma/migrations/20260913000000_run_trigger_webhook/migration.sql +2 -0
- package/prisma/migrations/20260913010000_agent_sub_agent/migration.sql +32 -0
- package/prisma/migrations/20260913020000_run_task_override/migration.sql +5 -0
- package/prisma/migrations/20260913030000_coding_run_root/migration.sql +11 -0
- package/prisma/migrations/20260922010000_coding_run_queued_at/migration.sql +8 -0
- package/prisma/migrations/20260922020000_coding_workspace_disk/migration.sql +8 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +723 -0
- package/scripts/git-askpass.sh +11 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createSecret } from "../../core/secrets.js";
|
|
2
|
+
const ELICITATION_TTL_MS = 600_000;
|
|
3
|
+
/** Sweeps every expired entry, not just the one being looked up, so a set-and-never-polled-again entry doesn't linger forever. */
|
|
4
|
+
async function pruneExpired(db, now) {
|
|
5
|
+
await db.secretElicitationOutcome.deleteMany({ where: { expiresAt: { lte: now } } });
|
|
6
|
+
}
|
|
7
|
+
export async function getSecretElicitationOutcome(ownerId, secretName, db) {
|
|
8
|
+
const now = new Date();
|
|
9
|
+
await pruneExpired(db, now);
|
|
10
|
+
const row = await db.secretElicitationOutcome.findUnique({ where: { ownerId_secretName: { ownerId, secretName } } });
|
|
11
|
+
return row?.outcome;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Called by the browser form's POST handler. Idempotent — a resubmit for
|
|
15
|
+
* the same (ownerId, secretName) that's already fulfilled returns the
|
|
16
|
+
* recorded outcome rather than writing again.
|
|
17
|
+
*/
|
|
18
|
+
export async function fulfillSecretElicitation(payload, value, cipher, db) {
|
|
19
|
+
const now = new Date();
|
|
20
|
+
await pruneExpired(db, now);
|
|
21
|
+
const { ownerId, secretName } = payload;
|
|
22
|
+
const existing = await db.secretElicitationOutcome.findUnique({
|
|
23
|
+
where: { ownerId_secretName: { ownerId, secretName } },
|
|
24
|
+
});
|
|
25
|
+
if (existing)
|
|
26
|
+
return existing.outcome;
|
|
27
|
+
let outcome;
|
|
28
|
+
try {
|
|
29
|
+
const secret = await createSecret(secretName, value, ownerId, cipher, db);
|
|
30
|
+
outcome = {
|
|
31
|
+
ok: true,
|
|
32
|
+
secret: {
|
|
33
|
+
id: secret.id,
|
|
34
|
+
name: secret.name,
|
|
35
|
+
keyId: secret.keyId,
|
|
36
|
+
ownerId: secret.ownerId,
|
|
37
|
+
createdAt: secret.createdAt,
|
|
38
|
+
updatedAt: secret.updatedAt,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
outcome = { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
44
|
+
}
|
|
45
|
+
const expiresAt = new Date(now.getTime() + ELICITATION_TTL_MS);
|
|
46
|
+
await db.secretElicitationOutcome.upsert({
|
|
47
|
+
where: { ownerId_secretName: { ownerId, secretName } },
|
|
48
|
+
create: { ownerId, secretName, outcome, expiresAt },
|
|
49
|
+
update: { outcome, expiresAt },
|
|
50
|
+
});
|
|
51
|
+
return outcome;
|
|
52
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WardbyMcpServer } from "../server.js";
|
|
2
|
+
/** Builds the browser-form URL for a minted token — stdio's ephemeral loopback server or the HTTP transport's mounted route (see mcp/index.ts and streamable-http.ts). */
|
|
3
|
+
export type SecretElicitationUrlBuilder = (token: string) => Promise<string>;
|
|
4
|
+
export interface SecretsToolsOptions {
|
|
5
|
+
buildElicitationUrl: SecretElicitationUrlBuilder;
|
|
6
|
+
/**
|
|
7
|
+
* Use the MCP protocol's own URL-mode elicitation (InputRequiredResult)
|
|
8
|
+
* instead of the plain-text "here's a link, call me again" fallback.
|
|
9
|
+
* See McpConfig.secretElicitationProtocol for why this defaults off.
|
|
10
|
+
*/
|
|
11
|
+
protocolElicitation: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function registerSecretsTools(mcp: WardbyMcpServer, opts: SecretsToolsOptions): void;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP secrets tools — thin wrappers over core/secrets.ts. The write-only /
|
|
3
|
+
* never-returned invariants are enforced there (listSecrets already omits
|
|
4
|
+
* value/ciphertext at the source); this file adds ownership on top.
|
|
5
|
+
*/
|
|
6
|
+
import { inputRequired, inputResponse } from "@modelcontextprotocol/server";
|
|
7
|
+
import { createSecret, listSecrets, attachSecret, detachSecret, deleteSecret } from "../../core/secrets.js";
|
|
8
|
+
import { McpError } from "../errors.js";
|
|
9
|
+
import { requireOwnedAgent, requireOwnedSecret } from "../auth/ownership.js";
|
|
10
|
+
import { getSecretElicitationOutcome } from "./secret-elicitation.js";
|
|
11
|
+
import { textResult } from "./text-result.js";
|
|
12
|
+
export function registerSecretsTools(mcp, opts) {
|
|
13
|
+
mcp.registerTool({
|
|
14
|
+
name: "create_secret",
|
|
15
|
+
scope: "secrets:write",
|
|
16
|
+
description: 'Creates a secret. Omit "value" to enter it securely via a one-time browser link instead of passing it as a plaintext argument.',
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: { name: { type: "string" }, value: { type: "string" } },
|
|
20
|
+
required: ["name"],
|
|
21
|
+
},
|
|
22
|
+
handler: async (args, ctx) => {
|
|
23
|
+
if (args.value !== undefined) {
|
|
24
|
+
const secret = await createSecret(args.name, args.value, ctx.principal.id, ctx.providers.secrets, ctx.db);
|
|
25
|
+
return textResult({ id: secret.id, name: secret.name, keyId: secret.keyId, createdAt: secret.createdAt });
|
|
26
|
+
}
|
|
27
|
+
if (opts.protocolElicitation) {
|
|
28
|
+
const state = ctx.mcpReq.requestState();
|
|
29
|
+
if (state) {
|
|
30
|
+
const view = inputResponse(ctx.mcpReq.inputResponses, "secretValue");
|
|
31
|
+
if (view.kind === "elicit" && view.action !== "accept") {
|
|
32
|
+
throw new McpError(400, `Secret entry was ${view.action}d.`);
|
|
33
|
+
}
|
|
34
|
+
// Not yet submitted in the browser falls through to the shared
|
|
35
|
+
// outcome check below, then re-mints so the client's retry/cancel
|
|
36
|
+
// controls apply to the fresh elicitation.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const existingOutcome = await getSecretElicitationOutcome(ctx.principal.id, args.name, ctx.db);
|
|
40
|
+
if (existingOutcome) {
|
|
41
|
+
if (!existingOutcome.ok)
|
|
42
|
+
throw new McpError(400, existingOutcome.error);
|
|
43
|
+
return textResult(existingOutcome.secret);
|
|
44
|
+
}
|
|
45
|
+
const payload = { ownerId: ctx.principal.id, secretName: args.name };
|
|
46
|
+
const token = await mcp.mintRequestState(payload);
|
|
47
|
+
const url = await opts.buildElicitationUrl(token);
|
|
48
|
+
if (opts.protocolElicitation) {
|
|
49
|
+
return inputRequired({
|
|
50
|
+
requestState: token,
|
|
51
|
+
inputRequests: {
|
|
52
|
+
secretValue: inputRequired.elicitUrl({
|
|
53
|
+
url,
|
|
54
|
+
message: `Enter the value for secret "${args.name}" in your browser.`,
|
|
55
|
+
}),
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Polling fallback: no MCP protocol feature required, so it works
|
|
60
|
+
// regardless of what the connecting client declares support for.
|
|
61
|
+
return textResult({
|
|
62
|
+
status: "pending",
|
|
63
|
+
message: `Open this link in your browser to enter the value for secret "${args.name}", then call create_secret again with the same name to finish.`,
|
|
64
|
+
url,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
mcp.registerTool({
|
|
69
|
+
name: "list_secrets",
|
|
70
|
+
scope: "secrets:write",
|
|
71
|
+
inputSchema: { type: "object", properties: {} },
|
|
72
|
+
handler: async (_args, ctx) => {
|
|
73
|
+
const secrets = await listSecrets(ctx.principal.id, ctx.db);
|
|
74
|
+
return textResult(secrets);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
mcp.registerTool({
|
|
78
|
+
name: "attach_secret",
|
|
79
|
+
scope: "secrets:write",
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
agentId: { type: "string" },
|
|
84
|
+
name: { type: "string" },
|
|
85
|
+
alias: { type: "string" },
|
|
86
|
+
},
|
|
87
|
+
required: ["agentId", "name"],
|
|
88
|
+
},
|
|
89
|
+
handler: async (args, ctx) => {
|
|
90
|
+
await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
|
|
91
|
+
try {
|
|
92
|
+
await attachSecret(args.agentId, args.name, ctx.principal.id, ctx.db, args.alias ?? args.name);
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
throw new McpError(404, err instanceof Error ? err.message : String(err));
|
|
96
|
+
}
|
|
97
|
+
return textResult({ attached: true });
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
mcp.registerTool({
|
|
101
|
+
name: "detach_secret",
|
|
102
|
+
scope: "secrets:write",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: { agentId: { type: "string" }, name: { type: "string" } },
|
|
106
|
+
required: ["agentId", "name"],
|
|
107
|
+
},
|
|
108
|
+
handler: async (args, ctx) => {
|
|
109
|
+
await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
|
|
110
|
+
await detachSecret(args.agentId, args.name, ctx.db);
|
|
111
|
+
return textResult({ detached: true });
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
mcp.registerTool({
|
|
115
|
+
name: "delete_secret",
|
|
116
|
+
scope: "secrets:write",
|
|
117
|
+
inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
|
|
118
|
+
handler: async (args, ctx) => {
|
|
119
|
+
await requireOwnedSecret(ctx.db, args.id, ctx.principal.id);
|
|
120
|
+
await deleteSecret(args.id, ctx.db);
|
|
121
|
+
return textResult({ deleted: args.id });
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentSubAgent CRUD — agent-composition edges. Attach/detach require
|
|
3
|
+
* ownership of both the parent and child agent (same convention as
|
|
4
|
+
* attach_tool requiring ownership of both the agent and the tool), and
|
|
5
|
+
* attach rejects any edge that would create a cycle in the parent/child
|
|
6
|
+
* graph (§7 of the design doc) — cycles are not something Postgres can
|
|
7
|
+
* enforce natively.
|
|
8
|
+
*/
|
|
9
|
+
import { Prisma } from "@prisma/client";
|
|
10
|
+
import { requireOwnedAgent } from "../auth/ownership.js";
|
|
11
|
+
import { McpError } from "../errors.js";
|
|
12
|
+
import { textResult } from "./text-result.js";
|
|
13
|
+
/**
|
|
14
|
+
* True if `fromAgentId` can already reach `toAgentId` by following existing
|
|
15
|
+
* parent -> child edges. Attaching a new edge parentAgentId -> childAgentId
|
|
16
|
+
* is only safe when childAgentId cannot already reach parentAgentId --
|
|
17
|
+
* otherwise the new edge would close a cycle. Loads the whole edge set and
|
|
18
|
+
* walks it in JS rather than a recursive SQL query: the edge count is
|
|
19
|
+
* expected to stay small (agent-composition graphs, not a general graph
|
|
20
|
+
* store), and this keeps the check readable and easy to unit test.
|
|
21
|
+
*/
|
|
22
|
+
async function canReach(db, fromAgentId, toAgentId) {
|
|
23
|
+
const edges = await db.agentSubAgent.findMany({ select: { parentAgentId: true, childAgentId: true } });
|
|
24
|
+
const childrenOf = new Map();
|
|
25
|
+
for (const edge of edges) {
|
|
26
|
+
const list = childrenOf.get(edge.parentAgentId) ?? [];
|
|
27
|
+
list.push(edge.childAgentId);
|
|
28
|
+
childrenOf.set(edge.parentAgentId, list);
|
|
29
|
+
}
|
|
30
|
+
const seen = new Set([fromAgentId]);
|
|
31
|
+
const stack = [fromAgentId];
|
|
32
|
+
while (stack.length > 0) {
|
|
33
|
+
const current = stack.pop();
|
|
34
|
+
if (current === toAgentId)
|
|
35
|
+
return true;
|
|
36
|
+
for (const next of childrenOf.get(current) ?? []) {
|
|
37
|
+
if (!seen.has(next)) {
|
|
38
|
+
seen.add(next);
|
|
39
|
+
stack.push(next);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
export function registerSubAgentTools(mcp) {
|
|
46
|
+
mcp.registerTool({
|
|
47
|
+
name: "attach_subagent",
|
|
48
|
+
scope: "agents:write",
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
additionalProperties: false,
|
|
52
|
+
properties: {
|
|
53
|
+
parentAgentId: { type: "string" },
|
|
54
|
+
childAgentId: { type: "string" },
|
|
55
|
+
boundName: { type: "string", minLength: 1 },
|
|
56
|
+
},
|
|
57
|
+
required: ["parentAgentId", "childAgentId"],
|
|
58
|
+
},
|
|
59
|
+
handler: async (args, ctx) => {
|
|
60
|
+
if (args.parentAgentId === args.childAgentId) {
|
|
61
|
+
throw new McpError(400, "An agent cannot be its own sub-agent.");
|
|
62
|
+
}
|
|
63
|
+
const parent = await requireOwnedAgent(ctx.db, args.parentAgentId, ctx.principal.id);
|
|
64
|
+
const child = await requireOwnedAgent(ctx.db, args.childAgentId, ctx.principal.id);
|
|
65
|
+
if (await canReach(ctx.db, args.childAgentId, args.parentAgentId)) {
|
|
66
|
+
throw new McpError(400, `Attaching "${child.name}" as a sub-agent of "${parent.name}" would create a cycle.`);
|
|
67
|
+
}
|
|
68
|
+
const boundName = args.boundName ?? child.name;
|
|
69
|
+
try {
|
|
70
|
+
await ctx.db.agentSubAgent.upsert({
|
|
71
|
+
where: { parentAgentId_childAgentId: { parentAgentId: args.parentAgentId, childAgentId: args.childAgentId } },
|
|
72
|
+
create: { parentAgentId: args.parentAgentId, childAgentId: args.childAgentId, boundName },
|
|
73
|
+
update: { boundName },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
|
|
78
|
+
throw new McpError(409, `"${parent.name}" already has a different sub-agent bound to name "${boundName}".`);
|
|
79
|
+
}
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
return textResult({ attached: true, boundName });
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
mcp.registerTool({
|
|
86
|
+
name: "detach_subagent",
|
|
87
|
+
scope: "agents:write",
|
|
88
|
+
inputSchema: {
|
|
89
|
+
type: "object",
|
|
90
|
+
additionalProperties: false,
|
|
91
|
+
properties: {
|
|
92
|
+
parentAgentId: { type: "string" },
|
|
93
|
+
childAgentId: { type: "string" },
|
|
94
|
+
},
|
|
95
|
+
required: ["parentAgentId", "childAgentId"],
|
|
96
|
+
},
|
|
97
|
+
handler: async (args, ctx) => {
|
|
98
|
+
await requireOwnedAgent(ctx.db, args.parentAgentId, ctx.principal.id);
|
|
99
|
+
await requireOwnedAgent(ctx.db, args.childAgentId, ctx.principal.id);
|
|
100
|
+
await ctx.db.agentSubAgent.deleteMany({
|
|
101
|
+
where: { parentAgentId: args.parentAgentId, childAgentId: args.childAgentId },
|
|
102
|
+
});
|
|
103
|
+
return textResult({ detached: true });
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
mcp.registerTool({
|
|
107
|
+
name: "list_subagents",
|
|
108
|
+
scope: "agents:read",
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: "object",
|
|
111
|
+
additionalProperties: false,
|
|
112
|
+
properties: { agentId: { type: "string" } },
|
|
113
|
+
required: ["agentId"],
|
|
114
|
+
},
|
|
115
|
+
handler: async (args, ctx) => {
|
|
116
|
+
await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
|
|
117
|
+
const [asParent, asChild] = await Promise.all([
|
|
118
|
+
ctx.db.agentSubAgent.findMany({
|
|
119
|
+
where: { parentAgentId: args.agentId },
|
|
120
|
+
include: { child: { select: { id: true, name: true } } },
|
|
121
|
+
}),
|
|
122
|
+
ctx.db.agentSubAgent.findMany({
|
|
123
|
+
where: { childAgentId: args.agentId },
|
|
124
|
+
include: { parent: { select: { id: true, name: true } } },
|
|
125
|
+
}),
|
|
126
|
+
]);
|
|
127
|
+
return textResult({
|
|
128
|
+
children: asParent.map((e) => ({ boundName: e.boundName, agentId: e.child.id, agentName: e.child.name })),
|
|
129
|
+
parents: asChild.map((e) => ({ boundName: e.boundName, agentId: e.parent.id, agentName: e.parent.name })),
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
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 { Prisma } from "@prisma/client";
|
|
13
|
+
import { deriveJsonSchema, validateParams } from "../../sandbox/zod-params.js";
|
|
14
|
+
import { runInSandbox } from "../../sandbox/run-in-sandbox.js";
|
|
15
|
+
import { ToolCapabilitiesPatchSchema } from "../../sandbox/tool-capabilities.js";
|
|
16
|
+
import { buildSharedDatastoreAccessor } from "../../core/datastores.js";
|
|
17
|
+
import { MEMORY_TOOL_NAMES } from "../../core/memory-tools.js";
|
|
18
|
+
import { McpError } from "../errors.js";
|
|
19
|
+
import { assertCanMutate, requireOwnedAgent, requireOwnedTool, requireReadableAgent, visibleToPrincipal, canRead, } from "../auth/ownership.js";
|
|
20
|
+
import { textResult } from "./text-result.js";
|
|
21
|
+
export function registerToolAuthoringTools(mcp) {
|
|
22
|
+
mcp.registerTool({
|
|
23
|
+
name: "create_tool",
|
|
24
|
+
scope: "tools:write",
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {
|
|
28
|
+
name: { type: "string" },
|
|
29
|
+
description: { type: "string" },
|
|
30
|
+
paramsZod: { type: "string" },
|
|
31
|
+
code: { type: "string" },
|
|
32
|
+
},
|
|
33
|
+
required: ["name", "description", "paramsZod", "code"],
|
|
34
|
+
},
|
|
35
|
+
handler: async (args, ctx) => {
|
|
36
|
+
if (MEMORY_TOOL_NAMES.has(args.name)) {
|
|
37
|
+
throw new McpError(400, `Tool name "${args.name}" is reserved for the built-in agent-memory tools.`);
|
|
38
|
+
}
|
|
39
|
+
const schemaResult = await deriveJsonSchema(args.paramsZod);
|
|
40
|
+
if (!schemaResult.ok) {
|
|
41
|
+
return textResult({ ok: false, errorKind: schemaResult.errorKind, errorMessage: schemaResult.errorMessage });
|
|
42
|
+
}
|
|
43
|
+
const tool = await ctx.db.tool.create({
|
|
44
|
+
data: {
|
|
45
|
+
name: args.name,
|
|
46
|
+
description: args.description,
|
|
47
|
+
paramsZod: args.paramsZod,
|
|
48
|
+
code: args.code,
|
|
49
|
+
jsonSchema: schemaResult.value,
|
|
50
|
+
ownerId: ctx.principal.id,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
return textResult(tool);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
mcp.registerTool({
|
|
57
|
+
name: "dry_run_tool",
|
|
58
|
+
scope: "tools:write",
|
|
59
|
+
description: "Test-runs tool code against sample args without persisting it. Fetch is denied by default, same as a freshly attached tool — pass allowedHosts to test code that calls fetch().",
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: "object",
|
|
62
|
+
properties: {
|
|
63
|
+
paramsZod: { type: "string" },
|
|
64
|
+
code: { type: "string" },
|
|
65
|
+
sampleArgs: { type: "object" },
|
|
66
|
+
allowedHosts: { type: "array", items: { type: "string" } },
|
|
67
|
+
},
|
|
68
|
+
required: ["paramsZod", "code", "sampleArgs"],
|
|
69
|
+
},
|
|
70
|
+
handler: async (args, ctx) => {
|
|
71
|
+
const hosts = ToolCapabilitiesPatchSchema.pick({ allowedHosts: true }).safeParse({
|
|
72
|
+
allowedHosts: args.allowedHosts,
|
|
73
|
+
});
|
|
74
|
+
if (!hosts.success) {
|
|
75
|
+
throw new McpError(400, `Invalid allowedHosts: ${hosts.error.issues.map((i) => i.message).join("; ")}`);
|
|
76
|
+
}
|
|
77
|
+
const schemaResult = await deriveJsonSchema(args.paramsZod);
|
|
78
|
+
if (!schemaResult.ok) {
|
|
79
|
+
return textResult({ ok: false, errorKind: schemaResult.errorKind, errorMessage: schemaResult.errorMessage });
|
|
80
|
+
}
|
|
81
|
+
const jsonSchema = schemaResult.value;
|
|
82
|
+
const validated = await validateParams(args.paramsZod, args.sampleArgs);
|
|
83
|
+
if (!validated.ok) {
|
|
84
|
+
return textResult({
|
|
85
|
+
jsonSchema,
|
|
86
|
+
result: { ok: false, errorKind: validated.errorKind, errorMessage: validated.errorMessage },
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// No real agent exists for a dry run — the caller's own principal
|
|
90
|
+
// scopes the sandbox's datastore access, keeping dry-run reads/writes
|
|
91
|
+
// isolated per-caller rather than colliding across authors. Fetch is
|
|
92
|
+
// deny-by-default here too — same posture as a freshly attached tool
|
|
93
|
+
// (OWASP LLM08) — rather than the unconditional wildcard this used to
|
|
94
|
+
// pass; a caller testing fetch-dependent code must declare the hosts
|
|
95
|
+
// it needs, same as at attach_tool time. sharedDatastore is likewise
|
|
96
|
+
// unscoped here: there's no tool attachment to carry
|
|
97
|
+
// allowedSharedDatastorePrefixes for a dry run, so it resolves exactly
|
|
98
|
+
// like a real agent with no shared-datastore attachments at all
|
|
99
|
+
// (empty reads, throwing writes) — never a capability-scoping bypass.
|
|
100
|
+
const sandboxResult = await runInSandbox({
|
|
101
|
+
code: args.code,
|
|
102
|
+
params: validated.value,
|
|
103
|
+
agentId: ctx.principal.id,
|
|
104
|
+
datastore: ctx.providers.datastore,
|
|
105
|
+
sharedDatastore: buildSharedDatastoreAccessor(ctx.principal.id, ctx.providers.datastore, ctx.db),
|
|
106
|
+
toolName: "dry_run_tool",
|
|
107
|
+
allowedFetchHosts: hosts.data.allowedHosts ?? [],
|
|
108
|
+
});
|
|
109
|
+
return textResult({ jsonSchema, result: sandboxResult });
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
mcp.registerTool({
|
|
113
|
+
name: "attach_tool",
|
|
114
|
+
scope: "tools:write",
|
|
115
|
+
inputSchema: {
|
|
116
|
+
type: "object",
|
|
117
|
+
properties: {
|
|
118
|
+
agentId: { type: "string" },
|
|
119
|
+
toolId: { type: "string" },
|
|
120
|
+
allowedSecrets: { type: "array", items: { type: "string" } },
|
|
121
|
+
allowedDatastorePrefixes: { type: "array", items: { type: "string" } },
|
|
122
|
+
allowedHosts: { type: "array", items: { type: "string" } },
|
|
123
|
+
// { [boundName]: allowedKeyPrefixes[] } — per shared-datastore
|
|
124
|
+
// attachment, the same default-deny grant as allowedDatastorePrefixes
|
|
125
|
+
// is for the agent's private store. A boundName absent from this map
|
|
126
|
+
// denies that shared store entirely.
|
|
127
|
+
allowedSharedDatastorePrefixes: {
|
|
128
|
+
type: "object",
|
|
129
|
+
additionalProperties: { type: "array", items: { type: "string" } },
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
required: ["agentId", "toolId"],
|
|
133
|
+
},
|
|
134
|
+
handler: async (args, ctx) => {
|
|
135
|
+
const patch = ToolCapabilitiesPatchSchema.safeParse({
|
|
136
|
+
allowedSecrets: args.allowedSecrets,
|
|
137
|
+
allowedDatastorePrefixes: args.allowedDatastorePrefixes,
|
|
138
|
+
allowedHosts: args.allowedHosts,
|
|
139
|
+
allowedSharedDatastorePrefixes: args.allowedSharedDatastorePrefixes,
|
|
140
|
+
});
|
|
141
|
+
if (!patch.success) {
|
|
142
|
+
throw new McpError(400, `Invalid tool capabilities: ${patch.error.issues.map((i) => i.message).join("; ")}`);
|
|
143
|
+
}
|
|
144
|
+
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.`);
|
|
149
|
+
if (agent.kind === "coding") {
|
|
150
|
+
throw new McpError(400, "Native sandbox tools cannot be attached to coding agents.");
|
|
151
|
+
}
|
|
152
|
+
await requireOwnedTool(tx, args.toolId, ctx.principal.id);
|
|
153
|
+
await tx.agentTool.upsert({
|
|
154
|
+
where: { agentId_toolId: { agentId: args.agentId, toolId: args.toolId } },
|
|
155
|
+
create: {
|
|
156
|
+
agentId: args.agentId,
|
|
157
|
+
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
|
+
update: {
|
|
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
|
+
: {}),
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
|
|
175
|
+
return textResult({ attached: true });
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
mcp.registerTool({
|
|
179
|
+
name: "detach_tool",
|
|
180
|
+
scope: "tools:write",
|
|
181
|
+
inputSchema: {
|
|
182
|
+
type: "object",
|
|
183
|
+
properties: { agentId: { type: "string" }, toolId: { type: "string" } },
|
|
184
|
+
required: ["agentId", "toolId"],
|
|
185
|
+
},
|
|
186
|
+
handler: async (args, ctx) => {
|
|
187
|
+
await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
|
|
188
|
+
await requireOwnedTool(ctx.db, args.toolId, ctx.principal.id);
|
|
189
|
+
await ctx.db.agentTool.deleteMany({ where: { agentId: args.agentId, toolId: args.toolId } });
|
|
190
|
+
return textResult({ detached: true });
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
mcp.registerTool({
|
|
194
|
+
name: "list_tools",
|
|
195
|
+
scope: "tools:write",
|
|
196
|
+
inputSchema: { type: "object", properties: { agentId: { type: "string" } } },
|
|
197
|
+
handler: async (args, ctx) => {
|
|
198
|
+
const project = (tool) => tool.ownerId === ctx.principal.id ? tool : { id: tool.id, name: tool.name, description: tool.description };
|
|
199
|
+
if (args.agentId) {
|
|
200
|
+
await requireReadableAgent(ctx.db, args.agentId, ctx.principal.id);
|
|
201
|
+
const rows = await ctx.db.agentTool.findMany({ where: { agentId: args.agentId }, include: { tool: true } });
|
|
202
|
+
return textResult(rows
|
|
203
|
+
.map((r) => r.tool)
|
|
204
|
+
.filter((tool) => canRead(tool.ownerId, ctx.principal.id))
|
|
205
|
+
.map(project));
|
|
206
|
+
}
|
|
207
|
+
const tools = await ctx.db.tool.findMany({ where: visibleToPrincipal(ctx.principal.id) });
|
|
208
|
+
return textResult(tools.filter((tool) => canRead(tool.ownerId, ctx.principal.id)).map(project));
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|