@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,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trigger_agent + the Tasks extension's tasks/get, tasks/cancel, and
|
|
3
|
+
* tasks/update methods. A separate module from agents.ts (Task 9's pure
|
|
4
|
+
* CRUD) — this is the one place in Phase 4 that actually produces Tasks
|
|
5
|
+
* (kind:"run"; tool_authoring is reserved/unused this phase), so
|
|
6
|
+
* registering all three here — rather than splitting them into yet
|
|
7
|
+
* another file with nothing else to register — keeps the one
|
|
8
|
+
* task-producing concern together.
|
|
9
|
+
*
|
|
10
|
+
* tasks/update is registered but always rejects: a run-backed task never
|
|
11
|
+
* enters "input_required" (runs need no mid-flight input, and guided tool
|
|
12
|
+
* authoring — the one flow that would — is deferred per Amendment A), so
|
|
13
|
+
* there is never legitimately pending input to submit. Registering it
|
|
14
|
+
* anyway (rather than leaving it unregistered, -32601) gives a client that
|
|
15
|
+
* calls it a clear, on-protocol "this task isn't awaiting input" instead
|
|
16
|
+
* of a generic method-not-found.
|
|
17
|
+
*
|
|
18
|
+
* Cancellation calls the Executor's best-effort stop seam after the Task is
|
|
19
|
+
* marked cancelled. Native execution remains cooperative until the Engine
|
|
20
|
+
* accepts an AbortSignal; coding executors must stop the isolated job.
|
|
21
|
+
*
|
|
22
|
+
* Ownership: a Task is stamped with the triggering principal's id at
|
|
23
|
+
* creation (Task.principalId) — a direct column, not resolved transitively
|
|
24
|
+
* through Run -> Agent -> ownerId, so it works uniformly even for a future
|
|
25
|
+
* tool_authoring task with no Run/Agent chain. requireOwnedTask (in
|
|
26
|
+
* ../auth/ownership.js, the one seam every ownership check in this codebase
|
|
27
|
+
* routes through) checks it before tasks/get and tasks/cancel touch the
|
|
28
|
+
* task manager — a caller who guesses or is handed someone else's taskId
|
|
29
|
+
* gets 404, not another principal's final text/cost.
|
|
30
|
+
*/
|
|
31
|
+
import { dispatchRun } from "../../core/dispatch.js";
|
|
32
|
+
import { CodingBaseRefSchema, CodingTaskOverrideSchema } from "../../coding/protocol.js";
|
|
33
|
+
import { z } from "zod";
|
|
34
|
+
import { McpError } from "../errors.js";
|
|
35
|
+
import { createTaskResult, getTask, cancelTask } from "../tasks/manager.js";
|
|
36
|
+
import { canMutate, requireOwnedAgent, requireOwnedTask } from "../auth/ownership.js";
|
|
37
|
+
import { textResult } from "./text-result.js";
|
|
38
|
+
const DEFAULT_TASK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
39
|
+
const TriggerAgentSchema = z
|
|
40
|
+
.object({
|
|
41
|
+
agentId: z.string().trim().min(1).max(128),
|
|
42
|
+
task: CodingTaskOverrideSchema.optional(),
|
|
43
|
+
baseRef: CodingBaseRefSchema.optional(),
|
|
44
|
+
})
|
|
45
|
+
.strict();
|
|
46
|
+
function parseTriggerArgs(args) {
|
|
47
|
+
const parsed = TriggerAgentSchema.safeParse(args);
|
|
48
|
+
if (parsed.success)
|
|
49
|
+
return parsed.data;
|
|
50
|
+
const details = parsed.error.issues.map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`).join("; ");
|
|
51
|
+
throw new McpError(400, `Invalid trigger_agent arguments: ${details}`);
|
|
52
|
+
}
|
|
53
|
+
export function registerTriggerTool(mcp) {
|
|
54
|
+
mcp.registerTool({
|
|
55
|
+
name: "trigger_agent",
|
|
56
|
+
scope: "runs:trigger",
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: "object",
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
properties: { agentId: { type: "string" }, task: { type: "string" }, baseRef: { type: "string" } },
|
|
61
|
+
required: ["agentId"],
|
|
62
|
+
},
|
|
63
|
+
handler: async (rawArgs, ctx) => {
|
|
64
|
+
const args = parseTriggerArgs(rawArgs);
|
|
65
|
+
const agent = await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
|
|
66
|
+
if (agent.kind !== "coding" && (args.task !== undefined || args.baseRef !== undefined)) {
|
|
67
|
+
throw new McpError(400, "Task and baseRef overrides are only valid for coding agents.");
|
|
68
|
+
}
|
|
69
|
+
const dispatched = await dispatchRun({
|
|
70
|
+
db: ctx.db,
|
|
71
|
+
executor: ctx.providers.executor,
|
|
72
|
+
agentId: agent.id,
|
|
73
|
+
trigger: "manual",
|
|
74
|
+
codingTask: args.task,
|
|
75
|
+
codingBaseRef: args.baseRef,
|
|
76
|
+
task: ctx.clientSupportsTasks ? { principalId: ctx.principal.id, ttlMs: DEFAULT_TASK_TTL_MS } : undefined,
|
|
77
|
+
beforePersist: async (_tx, current) => {
|
|
78
|
+
if (!canMutate(current.ownerId, ctx.principal.id)) {
|
|
79
|
+
throw new McpError(403, `Agent "${agent.id}" is not owned by the caller.`);
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
if (!dispatched)
|
|
85
|
+
throw new Error("Run dispatch was not claimed.");
|
|
86
|
+
if (ctx.clientSupportsTasks) {
|
|
87
|
+
if (!dispatched.task)
|
|
88
|
+
throw new Error("Run task was not persisted.");
|
|
89
|
+
return textResult(createTaskResult(dispatched.task, DEFAULT_TASK_TTL_MS));
|
|
90
|
+
}
|
|
91
|
+
return textResult({ runId: dispatched.run.id });
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
// Neither handler sets `resultType` itself: it's wire-only protocol
|
|
95
|
+
// machinery (`WireOnlyResultKey` in the SDK's own types) that the SDK
|
|
96
|
+
// stamps on encode and strips on decode before application code — ours
|
|
97
|
+
// AND the calling client's — ever sees it. Confirmed empirically: a
|
|
98
|
+
// handler-returned `resultType` field never survives the round trip
|
|
99
|
+
// through this SDK's own Client, while any other field does. A real
|
|
100
|
+
// wire capture would show it; `client.request()`'s return value won't.
|
|
101
|
+
mcp.registerRequestHandler("tasks/get", "agents:read", async (params, ctx) => {
|
|
102
|
+
const { taskId } = params;
|
|
103
|
+
await requireOwnedTask(ctx.db, taskId, ctx.principal.id);
|
|
104
|
+
return getTask(taskId, ctx.db);
|
|
105
|
+
});
|
|
106
|
+
mcp.registerRequestHandler("tasks/cancel", "runs:trigger", async (params, ctx) => {
|
|
107
|
+
const { taskId } = params;
|
|
108
|
+
await requireOwnedTask(ctx.db, taskId, ctx.principal.id);
|
|
109
|
+
await cancelTask(taskId, ctx.db, async (runId) => {
|
|
110
|
+
await ctx.providers.executor.stop(runId, "cancelled by caller");
|
|
111
|
+
});
|
|
112
|
+
return {};
|
|
113
|
+
});
|
|
114
|
+
mcp.registerRequestHandler("tasks/update", "runs:trigger", async (params, ctx) => {
|
|
115
|
+
const { taskId } = params;
|
|
116
|
+
await requireOwnedTask(ctx.db, taskId, ctx.principal.id);
|
|
117
|
+
const result = await getTask(taskId, ctx.db);
|
|
118
|
+
throw new McpError(400, `Task "${taskId}" is not awaiting input (status: ${result.status}).`);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createWebhook, listWebhooks, deleteWebhook } from "../../core/webhooks.js";
|
|
2
|
+
import { requireOwnedAgent, requireOwnedWebhook } from "../auth/ownership.js";
|
|
3
|
+
import { textResult } from "./text-result.js";
|
|
4
|
+
export function registerWebhookTools(mcp) {
|
|
5
|
+
mcp.registerTool({
|
|
6
|
+
name: "create_webhook",
|
|
7
|
+
scope: "webhooks:write",
|
|
8
|
+
inputSchema: { type: "object", properties: { agentId: { type: "string" } }, required: ["agentId"] },
|
|
9
|
+
handler: async (args, ctx) => {
|
|
10
|
+
await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
|
|
11
|
+
const created = await createWebhook(args.agentId, ctx.principal.id, ctx.db);
|
|
12
|
+
return textResult(created);
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
mcp.registerTool({
|
|
16
|
+
name: "list_webhooks",
|
|
17
|
+
scope: "webhooks:write",
|
|
18
|
+
inputSchema: { type: "object", properties: {} },
|
|
19
|
+
handler: async (_args, ctx) => {
|
|
20
|
+
const webhooks = await listWebhooks(ctx.principal.id, ctx.db);
|
|
21
|
+
return textResult(webhooks);
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
mcp.registerTool({
|
|
25
|
+
name: "delete_webhook",
|
|
26
|
+
scope: "webhooks:write",
|
|
27
|
+
inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
|
|
28
|
+
handler: async (args, ctx) => {
|
|
29
|
+
await requireOwnedWebhook(ctx.db, args.id, ctx.principal.id);
|
|
30
|
+
await deleteWebhook(args.id, ctx.db);
|
|
31
|
+
return textResult({ deleted: args.id });
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IncomingMessage } from "node:http";
|
|
2
|
+
export declare const HTTP_LIMITS: {
|
|
3
|
+
json: number;
|
|
4
|
+
auth: number;
|
|
5
|
+
requestMs: number;
|
|
6
|
+
headersMs: number;
|
|
7
|
+
};
|
|
8
|
+
export declare class HttpBoundaryError extends Error {
|
|
9
|
+
readonly status: number;
|
|
10
|
+
constructor(status: number, message: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class PayloadTooLargeError extends HttpBoundaryError {
|
|
13
|
+
constructor();
|
|
14
|
+
}
|
|
15
|
+
export declare function canonicalUrl(value: string): URL;
|
|
16
|
+
export declare function readBody(req: IncomingMessage, maxBytes: number, signal: AbortSignal): Promise<string>;
|
|
17
|
+
export declare function parseBody(raw: string, contentType: string | undefined): unknown;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export const HTTP_LIMITS = { json: 1024 * 1024, auth: 64 * 1024, requestMs: 15_000, headersMs: 10_000 };
|
|
2
|
+
export class HttpBoundaryError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
constructor(status, message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.status = status;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class PayloadTooLargeError extends HttpBoundaryError {
|
|
10
|
+
constructor() {
|
|
11
|
+
super(413, "payload_too_large");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function canonicalUrl(value) {
|
|
15
|
+
const url = new URL(value);
|
|
16
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
17
|
+
if (url.username ||
|
|
18
|
+
url.password ||
|
|
19
|
+
url.hash ||
|
|
20
|
+
url.search ||
|
|
21
|
+
(url.protocol !== "https:" && !(url.protocol === "http:" && loopback))) {
|
|
22
|
+
throw new Error("Canonical URI must be HTTPS (HTTP is allowed only for loopback development), without credentials, query, or fragment.");
|
|
23
|
+
}
|
|
24
|
+
return url;
|
|
25
|
+
}
|
|
26
|
+
export function readBody(req, maxBytes, signal) {
|
|
27
|
+
const length = req.headers["content-length"];
|
|
28
|
+
if (length && (!/^\d+$/.test(length) || Number(length) > maxBytes))
|
|
29
|
+
return Promise.reject(new PayloadTooLargeError());
|
|
30
|
+
if (req.headers["content-encoding"] && req.headers["content-encoding"] !== "identity")
|
|
31
|
+
return Promise.reject(new HttpBoundaryError(415, "unsupported_encoding"));
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
let size = 0;
|
|
34
|
+
const chunks = [];
|
|
35
|
+
const clean = () => {
|
|
36
|
+
req.off("data", data);
|
|
37
|
+
req.off("end", end);
|
|
38
|
+
req.off("error", error);
|
|
39
|
+
req.off("aborted", aborted);
|
|
40
|
+
signal.removeEventListener("abort", timeout);
|
|
41
|
+
};
|
|
42
|
+
const error = (err) => {
|
|
43
|
+
clean();
|
|
44
|
+
req.pause();
|
|
45
|
+
reject(err);
|
|
46
|
+
};
|
|
47
|
+
const aborted = () => error(new HttpBoundaryError(400, "incomplete_body"));
|
|
48
|
+
const timeout = () => error(new HttpBoundaryError(408, "request_timeout"));
|
|
49
|
+
const data = (chunk) => {
|
|
50
|
+
size += chunk.length;
|
|
51
|
+
if (size > maxBytes)
|
|
52
|
+
error(new PayloadTooLargeError());
|
|
53
|
+
else
|
|
54
|
+
chunks.push(chunk);
|
|
55
|
+
};
|
|
56
|
+
const end = () => {
|
|
57
|
+
clean();
|
|
58
|
+
resolve(Buffer.concat(chunks, size).toString("utf8"));
|
|
59
|
+
};
|
|
60
|
+
req.on("data", data);
|
|
61
|
+
req.once("end", end);
|
|
62
|
+
req.once("error", error);
|
|
63
|
+
req.once("aborted", aborted);
|
|
64
|
+
signal.addEventListener("abort", timeout, { once: true });
|
|
65
|
+
if (signal.aborted)
|
|
66
|
+
timeout();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export function parseBody(raw, contentType) {
|
|
70
|
+
const type = contentType?.split(";", 1)[0].trim().toLowerCase();
|
|
71
|
+
if (type === "application/json") {
|
|
72
|
+
try {
|
|
73
|
+
const value = JSON.parse(raw);
|
|
74
|
+
if (value === null || typeof value !== "object")
|
|
75
|
+
throw new Error();
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new HttpBoundaryError(400, "invalid_json");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (type === "application/x-www-form-urlencoded") {
|
|
83
|
+
if (/%(?![a-f\d]{2})/i.test(raw))
|
|
84
|
+
throw new HttpBoundaryError(400, "invalid_form");
|
|
85
|
+
const params = new URLSearchParams(raw);
|
|
86
|
+
for (const key of params.keys())
|
|
87
|
+
if (params.getAll(key).length !== 1)
|
|
88
|
+
throw new HttpBoundaryError(400, "duplicate_parameter");
|
|
89
|
+
return params;
|
|
90
|
+
}
|
|
91
|
+
throw new HttpBoundaryError(415, "unsupported_content_type");
|
|
92
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stdio transport: newline-delimited JSON-RPC over the process's standard
|
|
3
|
+
* streams, no OAuth (per spec: stdio SHOULD NOT use OAuth — it retrieves
|
|
4
|
+
* credentials from the environment). `serveStdio` (the real SDK's stdio
|
|
5
|
+
* serving entry) owns era negotiation and per-connection instance
|
|
6
|
+
* construction from `mcp.factory`; this file only wires the transport.
|
|
7
|
+
*
|
|
8
|
+
* Caller identity: stdio never carries per-message AuthInfo (serveStdio
|
|
9
|
+
* never sets it), so the composition root (Task 15) must call
|
|
10
|
+
* `mcp.setFixedContext(...)` with the resolved LOCAL_PRINCIPAL identity
|
|
11
|
+
* BEFORE starting the stdio server — every tool call in the connection
|
|
12
|
+
* resolves to that one fixed context, matching "the operator is trusted."
|
|
13
|
+
*/
|
|
14
|
+
import type { Readable, Writable } from "node:stream";
|
|
15
|
+
import type { WardbyMcpServer } from "../server.js";
|
|
16
|
+
export interface StdioIo {
|
|
17
|
+
input?: Readable;
|
|
18
|
+
output?: Writable;
|
|
19
|
+
}
|
|
20
|
+
export interface StdioHandle {
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export declare function runStdioServer(mcp: WardbyMcpServer, io?: StdioIo): StdioHandle;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { serveStdio, StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
2
|
+
export function runStdioServer(mcp, io = {}) {
|
|
3
|
+
const transport = new StdioServerTransport(io.input, io.output);
|
|
4
|
+
const handle = serveStdio(mcp.factory, { transport });
|
|
5
|
+
return { close: () => handle.close() };
|
|
6
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ServerResponse } from "node:http";
|
|
2
|
+
import { type PerRequestResponseMode } from "@modelcontextprotocol/server";
|
|
3
|
+
import type { PrismaClient } from "@prisma/client";
|
|
4
|
+
import type { AuthProvider } from "../../providers/auth/types.js";
|
|
5
|
+
import type { SelfHostedAuthProvider } from "../../providers/auth/self-hosted.js";
|
|
6
|
+
import type { WardbyMcpServer } from "../server.js";
|
|
7
|
+
import type { McpProviders } from "../context.js";
|
|
8
|
+
export interface HttpServerConfig {
|
|
9
|
+
canonicalUri: string;
|
|
10
|
+
httpBind: {
|
|
11
|
+
host: string;
|
|
12
|
+
port: number;
|
|
13
|
+
};
|
|
14
|
+
authProviderKind: "delegating" | "self-hosted";
|
|
15
|
+
authorizationServer?: string;
|
|
16
|
+
allowedOrigins?: string[];
|
|
17
|
+
responseMode?: PerRequestResponseMode;
|
|
18
|
+
}
|
|
19
|
+
export interface StartHttpServerOptions {
|
|
20
|
+
mcp: WardbyMcpServer;
|
|
21
|
+
config: HttpServerConfig;
|
|
22
|
+
auth: {
|
|
23
|
+
authProvider: AuthProvider;
|
|
24
|
+
db: PrismaClient;
|
|
25
|
+
providers: McpProviders;
|
|
26
|
+
};
|
|
27
|
+
selfHosted?: SelfHostedAuthProvider;
|
|
28
|
+
}
|
|
29
|
+
export interface HttpServerHandle {
|
|
30
|
+
port: number;
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export declare function sendJson(res: ServerResponse, status: number, body: unknown, headers?: Record<string, string>): void;
|
|
34
|
+
export declare function startHttpServer(opts: StartHttpServerOptions): Promise<HttpServerHandle>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { createMcpHandler } from "@modelcontextprotocol/server";
|
|
4
|
+
import { toNodeHandler, hostHeaderValidation } from "@modelcontextprotocol/node";
|
|
5
|
+
import { authenticate, protectedResourceMetadata } from "../auth/resource-server.js";
|
|
6
|
+
import { McpError } from "../errors.js";
|
|
7
|
+
import { handleWebhookIngress } from "../webhooks/ingress.js";
|
|
8
|
+
import { canonicalUrl, HTTP_LIMITS, HttpBoundaryError, parseBody, readBody } from "./http-limits.js";
|
|
9
|
+
import { browserHandler } from "../auth/self-hosted/browser.js";
|
|
10
|
+
import { handleSecretElicitationForm, SECRET_ELICITATION_PATH } from "../tools/secret-elicitation-form.js";
|
|
11
|
+
import { logger } from "../../core/logger.js";
|
|
12
|
+
const httpLog = logger.child({ module: "streamable-http" });
|
|
13
|
+
export function sendJson(res, status, body, headers = {}) {
|
|
14
|
+
res
|
|
15
|
+
.writeHead(status, { "content-type": "application/json", "cache-control": "no-store", ...headers })
|
|
16
|
+
.end(JSON.stringify(body));
|
|
17
|
+
}
|
|
18
|
+
export async function startHttpServer(opts) {
|
|
19
|
+
const canonical = canonicalUrl(opts.config.canonicalUri);
|
|
20
|
+
if (opts.config.authProviderKind === "self-hosted" && !opts.selfHosted)
|
|
21
|
+
throw new Error("Missing self-hosted provider.");
|
|
22
|
+
const oauth = opts.config.authProviderKind === "self-hosted" ? browserHandler(opts.selfHosted) : undefined;
|
|
23
|
+
const origins = new Set([
|
|
24
|
+
canonical.origin,
|
|
25
|
+
...(opts.config.allowedOrigins ?? []).map((value) => {
|
|
26
|
+
const url = canonicalUrl(value);
|
|
27
|
+
if (url.origin !== value)
|
|
28
|
+
throw new Error("Allowed origins must be exact origins without paths.");
|
|
29
|
+
return value;
|
|
30
|
+
}),
|
|
31
|
+
]);
|
|
32
|
+
const validateHost = hostHeaderValidation([canonical.hostname]);
|
|
33
|
+
const nodeHandler = toNodeHandler(createMcpHandler(opts.mcp.factory, { responseMode: opts.config.responseMode }));
|
|
34
|
+
const server = createServer({ maxHeaderSize: 16 * 1024, requireHostHeader: false }, (req, res) => {
|
|
35
|
+
void route(req, res).catch((err) => {
|
|
36
|
+
if (res.headersSent) {
|
|
37
|
+
res.destroy();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (err instanceof McpError) {
|
|
41
|
+
sendJson(res, err.httpStatus, { error: "invalid_token" }, err.wwwAuthenticate ? { "www-authenticate": err.wwwAuthenticate } : {});
|
|
42
|
+
}
|
|
43
|
+
else if (err instanceof HttpBoundaryError) {
|
|
44
|
+
res.shouldKeepAlive = false;
|
|
45
|
+
// Drain without buffering while the error response flushes, then bound socket cleanup.
|
|
46
|
+
req.resume();
|
|
47
|
+
const timer = setTimeout(() => req.destroy(), 1000);
|
|
48
|
+
timer.unref();
|
|
49
|
+
req.socket.once("close", () => clearTimeout(timer));
|
|
50
|
+
sendJson(res, err.status, { error: err.message }, { connection: "close" });
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const id = randomUUID();
|
|
54
|
+
httpLog.error({ err, requestId: id }, "HTTP request failed");
|
|
55
|
+
sendJson(res, 500, { error: "internal_error", id });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
async function route(req, res) {
|
|
60
|
+
const hostCount = req.rawHeaders.filter((_, i) => i % 2 === 0 && req.rawHeaders[i].toLowerCase() === "host").length;
|
|
61
|
+
if (hostCount !== 1 || req.headers.host?.toLowerCase() !== canonical.host || !validateHost(req, res)) {
|
|
62
|
+
if (!res.headersSent)
|
|
63
|
+
sendJson(res, 403, { error: "invalid_host" }, { connection: "close" });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (req.headers.origin && !origins.has(req.headers.origin)) {
|
|
67
|
+
sendJson(res, 403, { error: "invalid_origin" }, { connection: "close" });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!req.url?.startsWith("/") || req.url.startsWith("//"))
|
|
71
|
+
throw new HttpBoundaryError(400, "invalid_target");
|
|
72
|
+
const url = new URL(req.url, canonical.origin);
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
const timer = setTimeout(() => controller.abort(), HTTP_LIMITS.requestMs);
|
|
75
|
+
let body;
|
|
76
|
+
try {
|
|
77
|
+
const hasBody = req.method === "POST" ||
|
|
78
|
+
req.method === "PUT" ||
|
|
79
|
+
req.method === "PATCH" ||
|
|
80
|
+
req.headers["content-length"] ||
|
|
81
|
+
req.headers["transfer-encoding"];
|
|
82
|
+
if (hasBody) {
|
|
83
|
+
const limit = url.pathname === "/mcp" || url.pathname.startsWith("/webhooks/") ? HTTP_LIMITS.json : HTTP_LIMITS.auth;
|
|
84
|
+
const raw = await readBody(req, limit, controller.signal);
|
|
85
|
+
body = parseBody(raw, req.headers["content-type"]);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
}
|
|
91
|
+
if (req.method === "GET" &&
|
|
92
|
+
(url.pathname === "/.well-known/oauth-protected-resource" ||
|
|
93
|
+
url.pathname ===
|
|
94
|
+
"/.well-known/oauth-protected-resource" + (canonical.pathname === "/" ? "" : canonical.pathname))) {
|
|
95
|
+
const authorizationServers = opts.config.authProviderKind === "self-hosted"
|
|
96
|
+
? [canonical.href]
|
|
97
|
+
: [opts.config.authorizationServer].filter((s) => !!s);
|
|
98
|
+
sendJson(res, 200, protectedResourceMetadata({ canonicalUri: canonical.href, authorizationServers }));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (url.pathname === SECRET_ELICITATION_PATH && (req.method === "GET" || req.method === "POST")) {
|
|
102
|
+
await handleSecretElicitationForm(req.method, url.searchParams.get("t"), () => Promise.resolve(body instanceof URLSearchParams ? body : new URLSearchParams()), res, {
|
|
103
|
+
verify: (token) => opts.mcp.verifyRequestState(token),
|
|
104
|
+
secrets: opts.auth.providers.secrets,
|
|
105
|
+
db: opts.auth.db,
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (oauth && (await oauth(req, res, url, body)))
|
|
110
|
+
return;
|
|
111
|
+
const webhook = /^\/webhooks\/([^/]+)$/.exec(url.pathname);
|
|
112
|
+
if (webhook && req.method === "POST") {
|
|
113
|
+
if (!body || Array.isArray(body) || body instanceof URLSearchParams)
|
|
114
|
+
throw new HttpBoundaryError(415, "expected_json_object");
|
|
115
|
+
const headers = Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v]));
|
|
116
|
+
const result = await handleWebhookIngress(webhook[1], { headers, body: body }, opts.auth.db, opts.auth.providers.executor);
|
|
117
|
+
sendJson(res, result.status, result.body);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (url.pathname === "/mcp") {
|
|
121
|
+
if (req.method === "POST" && (!body || body instanceof URLSearchParams))
|
|
122
|
+
throw new HttpBoundaryError(415, "expected_json");
|
|
123
|
+
const ctx = await authenticate({ authorization: req.headers.authorization }, { ...opts.auth, canonicalUri: canonical.href });
|
|
124
|
+
req.auth = {
|
|
125
|
+
token: "",
|
|
126
|
+
clientId: ctx.principal.subject,
|
|
127
|
+
scopes: [...ctx.scopes],
|
|
128
|
+
extra: { principal: ctx.principal },
|
|
129
|
+
};
|
|
130
|
+
await nodeHandler(req, res, body);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
sendJson(res, 404, { error: "not_found" });
|
|
134
|
+
}
|
|
135
|
+
server.requestTimeout = HTTP_LIMITS.requestMs;
|
|
136
|
+
server.headersTimeout = HTTP_LIMITS.headersMs;
|
|
137
|
+
server.keepAliveTimeout = 5_000;
|
|
138
|
+
server.maxRequestsPerSocket = 100;
|
|
139
|
+
const cleanupTimer = opts.config.authProviderKind === "self-hosted"
|
|
140
|
+
? setInterval(() => {
|
|
141
|
+
void opts.selfHosted.cleanup().catch((err) => httpLog.error({ err }, "OAuth cleanup failed"));
|
|
142
|
+
}, 15 * 60_000)
|
|
143
|
+
: undefined;
|
|
144
|
+
cleanupTimer?.unref();
|
|
145
|
+
server.once("close", () => clearInterval(cleanupTimer));
|
|
146
|
+
await new Promise((resolve, reject) => {
|
|
147
|
+
server.once("error", reject);
|
|
148
|
+
server.listen(opts.config.httpBind.port, opts.config.httpBind.host, resolve);
|
|
149
|
+
});
|
|
150
|
+
const address = server.address();
|
|
151
|
+
return {
|
|
152
|
+
port: typeof address === "object" && address ? address.port : opts.config.httpBind.port,
|
|
153
|
+
close: () => new Promise((resolve, reject) => {
|
|
154
|
+
server.close((err) => (err ? reject(err) : resolve()));
|
|
155
|
+
server.closeIdleConnections();
|
|
156
|
+
}),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PrismaClient } from "@prisma/client";
|
|
2
|
+
/**
|
|
3
|
+
* `wardby mcp` serves the MCP surface and launches the executor, but the
|
|
4
|
+
* scheduler and reconciler live in `wardby scheduler` (or in `wardby serve`,
|
|
5
|
+
* which runs everything). A deployment running only `mcp` accepts schedules
|
|
6
|
+
* through set_schedule and then never fires them: no error, no crash,
|
|
7
|
+
* `lastScheduledAt` simply stays null. Nothing else surfaces that.
|
|
8
|
+
*
|
|
9
|
+
* Kept free of logging and process state so it can be unit-tested and reused
|
|
10
|
+
* by any composition root.
|
|
11
|
+
*/
|
|
12
|
+
/** Same predicate the scheduler uses to find candidates (core/scheduler.ts). */
|
|
13
|
+
export declare function countUnattendedSchedules(db: Pick<PrismaClient, "agent">): Promise<number>;
|
|
14
|
+
/** The warning to log for `count` unattended schedules, or null when there is nothing to warn about. */
|
|
15
|
+
export declare function unattendedSchedulesWarning(count: number): string | null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `wardby mcp` serves the MCP surface and launches the executor, but the
|
|
3
|
+
* scheduler and reconciler live in `wardby scheduler` (or in `wardby serve`,
|
|
4
|
+
* which runs everything). A deployment running only `mcp` accepts schedules
|
|
5
|
+
* through set_schedule and then never fires them: no error, no crash,
|
|
6
|
+
* `lastScheduledAt` simply stays null. Nothing else surfaces that.
|
|
7
|
+
*
|
|
8
|
+
* Kept free of logging and process state so it can be unit-tested and reused
|
|
9
|
+
* by any composition root.
|
|
10
|
+
*/
|
|
11
|
+
/** Same predicate the scheduler uses to find candidates (core/scheduler.ts). */
|
|
12
|
+
export async function countUnattendedSchedules(db) {
|
|
13
|
+
return db.agent.count({ where: { scheduleEnabled: true, schedule: { not: null } } });
|
|
14
|
+
}
|
|
15
|
+
/** The warning to log for `count` unattended schedules, or null when there is nothing to warn about. */
|
|
16
|
+
export function unattendedSchedulesWarning(count) {
|
|
17
|
+
if (count === 0)
|
|
18
|
+
return null;
|
|
19
|
+
return (`${count} agent(s) have an enabled schedule, but this process does not run the scheduler. ` +
|
|
20
|
+
`Run "wardby scheduler" alongside it, or run "wardby serve" instead, or those schedules will never fire.`);
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound webhook ingress — HTTP-framework-agnostic. Authenticated by the
|
|
3
|
+
* per-webhook secret only (never OAuth): mounted as an unauthenticated
|
|
4
|
+
* route in streamable-http.ts, matching the design's "route is
|
|
5
|
+
* unauthenticated by OAuth" invariant.
|
|
6
|
+
*/
|
|
7
|
+
import type { PrismaClient } from "@prisma/client";
|
|
8
|
+
import type { Executor } from "../../providers/executor/types.js";
|
|
9
|
+
export interface WebhookIngressRequest {
|
|
10
|
+
headers: Record<string, string | undefined>;
|
|
11
|
+
body: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
export interface WebhookIngressResult {
|
|
14
|
+
status: number;
|
|
15
|
+
body: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
export declare function handleWebhookIngress(webhookId: string, req: WebhookIngressRequest, db: PrismaClient, executor: Executor): Promise<WebhookIngressResult>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { resolveWebhookRun } from "../../core/webhooks.js";
|
|
2
|
+
import { CodingTaskOverrideSchema } from "../../coding/protocol.js";
|
|
3
|
+
function extractSecret(req) {
|
|
4
|
+
return req.headers["x-webhook-secret"] ?? (typeof req.body.secret === "string" ? req.body.secret : undefined);
|
|
5
|
+
}
|
|
6
|
+
function extractCodingTask(req) {
|
|
7
|
+
if (req.body.task === undefined)
|
|
8
|
+
return undefined;
|
|
9
|
+
const parsed = CodingTaskOverrideSchema.safeParse(req.body.task);
|
|
10
|
+
return parsed.success ? parsed.data : undefined;
|
|
11
|
+
}
|
|
12
|
+
export async function handleWebhookIngress(webhookId, req, db, executor) {
|
|
13
|
+
const secret = extractSecret(req);
|
|
14
|
+
if (!secret) {
|
|
15
|
+
return { status: 401, body: { error: "invalid_secret", error_description: "No webhook secret presented." } };
|
|
16
|
+
}
|
|
17
|
+
const codingTask = extractCodingTask(req);
|
|
18
|
+
if (req.body.task !== undefined && codingTask === undefined) {
|
|
19
|
+
return { status: 400, body: { error: "invalid_task" } };
|
|
20
|
+
}
|
|
21
|
+
const result = await resolveWebhookRun(webhookId, secret, db, executor, codingTask);
|
|
22
|
+
if (result.ok) {
|
|
23
|
+
return { status: 202, body: { runId: result.runId } };
|
|
24
|
+
}
|
|
25
|
+
switch (result.reason) {
|
|
26
|
+
case "not_found":
|
|
27
|
+
return { status: 404, body: { error: "not_found" } };
|
|
28
|
+
case "invalid_secret":
|
|
29
|
+
return { status: 401, body: { error: "invalid_secret" } };
|
|
30
|
+
case "disabled":
|
|
31
|
+
return { status: 403, body: { error: "disabled" } };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { MetricsServerConfig } from "./metrics-server.js";
|
|
2
|
+
export interface MetricsConfig {
|
|
3
|
+
bind?: MetricsServerConfig;
|
|
4
|
+
}
|
|
5
|
+
/** Metrics remain off unless an operator explicitly configures a bind address. */
|
|
6
|
+
export declare function loadMetricsConfig(env?: NodeJS.ProcessEnv): MetricsConfig;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function parseBind(value, allowNonLoopback) {
|
|
2
|
+
const match = /^(127\.0\.0\.1|::1|0\.0\.0\.0):([0-9]{1,5})$/.exec(value);
|
|
3
|
+
if (!match) {
|
|
4
|
+
throw new Error("METRICS_BIND must be 127.0.0.1:port, ::1:port, or 0.0.0.0:port");
|
|
5
|
+
}
|
|
6
|
+
const port = Number(match[2]);
|
|
7
|
+
if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) {
|
|
8
|
+
throw new Error("METRICS_BIND port must be between 1 and 65535");
|
|
9
|
+
}
|
|
10
|
+
if (match[1] === "0.0.0.0" && !allowNonLoopback) {
|
|
11
|
+
throw new Error("METRICS_BIND may use 0.0.0.0 only when METRICS_ALLOW_NON_LOOPBACK=true");
|
|
12
|
+
}
|
|
13
|
+
return { host: match[1], port };
|
|
14
|
+
}
|
|
15
|
+
/** Metrics remain off unless an operator explicitly configures a bind address. */
|
|
16
|
+
export function loadMetricsConfig(env = process.env) {
|
|
17
|
+
if (!env.METRICS_BIND)
|
|
18
|
+
return {};
|
|
19
|
+
return { bind: parseBind(env.METRICS_BIND, env.METRICS_ALLOW_NON_LOOPBACK === "true") };
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Registry } from "@prometheus-io/client";
|
|
2
|
+
export interface MetricsServerConfig {
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
}
|
|
6
|
+
export interface MetricsServerHandle {
|
|
7
|
+
port: number;
|
|
8
|
+
close(): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
/** Serve the Prometheus exposition endpoint on an explicitly private listener. */
|
|
11
|
+
export declare function startMetricsServer(registry: Registry, config: MetricsServerConfig): Promise<MetricsServerHandle>;
|