@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,105 @@
|
|
|
1
|
+
export interface GitHubAppConfig {
|
|
2
|
+
appId: string;
|
|
3
|
+
privateKey: string;
|
|
4
|
+
apiBaseUrl?: string;
|
|
5
|
+
apiVersion?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PullRequestTestResult {
|
|
8
|
+
command: string;
|
|
9
|
+
outcome: "passed" | "failed" | "skipped";
|
|
10
|
+
}
|
|
11
|
+
export interface PullRequestInput {
|
|
12
|
+
runId: string;
|
|
13
|
+
repository: string;
|
|
14
|
+
baseRef: string;
|
|
15
|
+
headRef: string;
|
|
16
|
+
/** Already-validated/redacted agent-authored fields (coding/protocol.ts); optional for callers with none. */
|
|
17
|
+
summary?: string;
|
|
18
|
+
tests?: readonly PullRequestTestResult[];
|
|
19
|
+
tag?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PullRequestResult {
|
|
22
|
+
number: number;
|
|
23
|
+
url: string;
|
|
24
|
+
}
|
|
25
|
+
/** See VcsProvider.notifyContinuationStarted/Finished (types.ts) — these back that GitHub-specific mechanism. */
|
|
26
|
+
export interface ContinuationStatusCommentInput {
|
|
27
|
+
/** This continuation round's own run id — the comment's identity/marker key. */
|
|
28
|
+
runId: string;
|
|
29
|
+
/** The run whose PR this belongs to (used only to re-find the PR via the existing marker lookup). */
|
|
30
|
+
rootRunId: string;
|
|
31
|
+
repository: string;
|
|
32
|
+
baseRef: string;
|
|
33
|
+
headRef: string;
|
|
34
|
+
body: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ContinuationCheckRunInput {
|
|
37
|
+
repository: string;
|
|
38
|
+
headSha: string;
|
|
39
|
+
/** Stored as the check run's external_id — the re-lookup key, so no numeric id needs to be cached anywhere. */
|
|
40
|
+
runId: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ContinuationCheckRunCompleteInput extends ContinuationCheckRunInput {
|
|
43
|
+
outcome: "succeeded" | "failed";
|
|
44
|
+
}
|
|
45
|
+
export interface GitHubRepositoryAccess {
|
|
46
|
+
withRepositoryToken<T>(repository: string, action: (token: string) => Promise<T>): Promise<T>;
|
|
47
|
+
createOrFindDraftPullRequest(input: PullRequestInput): Promise<PullRequestResult>;
|
|
48
|
+
/** Creates the status comment if none exists yet for this run, else updates it. */
|
|
49
|
+
upsertContinuationStatusComment(input: ContinuationStatusCommentInput): Promise<void>;
|
|
50
|
+
/** Updates the status comment if one already exists for this run; a no-op otherwise (never creates). */
|
|
51
|
+
updateContinuationStatusComment(input: ContinuationStatusCommentInput): Promise<void>;
|
|
52
|
+
/** Creates an in-progress check run if none exists yet for this run+commit; a no-op if one already does. */
|
|
53
|
+
createContinuationCheckRun(input: ContinuationCheckRunInput): Promise<void>;
|
|
54
|
+
/** Completes the check run if one exists for this run+commit; a no-op otherwise (never creates). */
|
|
55
|
+
completeContinuationCheckRun(input: ContinuationCheckRunCompleteInput): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
type Fetch = typeof globalThis.fetch;
|
|
58
|
+
/** Accepts bounded opaque installation tokens without allowing control characters. */
|
|
59
|
+
export declare function isSafeGitHubInstallationToken(value: unknown): value is string;
|
|
60
|
+
export declare class GitHubAppClient implements GitHubRepositoryAccess {
|
|
61
|
+
private readonly config;
|
|
62
|
+
private readonly fetchImpl;
|
|
63
|
+
private readonly now;
|
|
64
|
+
private readonly apiBaseUrl;
|
|
65
|
+
private readonly apiVersion;
|
|
66
|
+
constructor(config: GitHubAppConfig, fetchImpl?: Fetch, now?: () => Date);
|
|
67
|
+
withRepositoryToken<T>(repository: string, action: (token: string) => Promise<T>): Promise<T>;
|
|
68
|
+
/**
|
|
69
|
+
* Isolated from withRepositoryToken on purpose: "Checks: write" is a
|
|
70
|
+
* genuinely distinct GitHub App permission from contents/pull_requests
|
|
71
|
+
* (confirmed empirically). Minting it separately means an ungranted-permission
|
|
72
|
+
* failure here can only ever disable the check-run mechanism, never the
|
|
73
|
+
* real git push, PR, or status comment. (Status comments, below, do NOT
|
|
74
|
+
* need this treatment: PR-comment writes work fine on the existing
|
|
75
|
+
* contents/pull_requests token -- confirmed empirically after "Issues:
|
|
76
|
+
* write" alone, on its own isolated token, kept returning 403 "Resource
|
|
77
|
+
* not accessible by integration" even once granted; commenting on a PR
|
|
78
|
+
* apparently isn't gated the same way a genuine issue comment might be.)
|
|
79
|
+
*/
|
|
80
|
+
private withChecksToken;
|
|
81
|
+
upsertContinuationStatusComment(input: ContinuationStatusCommentInput): Promise<void>;
|
|
82
|
+
updateContinuationStatusComment(input: ContinuationStatusCommentInput): Promise<void>;
|
|
83
|
+
createContinuationCheckRun(input: ContinuationCheckRunInput): Promise<void>;
|
|
84
|
+
completeContinuationCheckRun(input: ContinuationCheckRunCompleteInput): Promise<void>;
|
|
85
|
+
createOrFindDraftPullRequest(input: PullRequestInput): Promise<PullRequestResult>;
|
|
86
|
+
private appJwt;
|
|
87
|
+
private mintRepositoryToken;
|
|
88
|
+
private mintChecksToken;
|
|
89
|
+
/**
|
|
90
|
+
* Shared installation-token mint: requests exactly `permissions`, then
|
|
91
|
+
* verifies the response granted exactly that (no more, no less, modulo
|
|
92
|
+
* the always-implicit `metadata: read`) before trusting the token. Each
|
|
93
|
+
* caller requests the narrowest permission set it needs — see
|
|
94
|
+
* withIssuesToken/withChecksToken for why these are minted separately
|
|
95
|
+
* from withRepositoryToken rather than requesting a union of everything.
|
|
96
|
+
*/
|
|
97
|
+
private mintScopedToken;
|
|
98
|
+
private revokeToken;
|
|
99
|
+
private findPullRequest;
|
|
100
|
+
private parsePullRequest;
|
|
101
|
+
private findStatusComment;
|
|
102
|
+
private findCheckRun;
|
|
103
|
+
private requestJson;
|
|
104
|
+
}
|
|
105
|
+
export {};
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { createPrivateKey } from "node:crypto";
|
|
2
|
+
import { importPKCS8, SignJWT } from "jose";
|
|
3
|
+
import { normalizeGitHubRepository, normalizeGitRef } from "../../coding/protocol.js";
|
|
4
|
+
const DEFAULT_API_BASE_URL = "https://api.github.com";
|
|
5
|
+
const DEFAULT_API_VERSION = "2026-03-10";
|
|
6
|
+
const RUN_MARKER_PREFIX = "<!-- wardby:";
|
|
7
|
+
const STATUS_COMMENT_MARKER_PREFIX = "<!-- wardby-status:";
|
|
8
|
+
const CONTINUATION_CHECK_RUN_NAME = "wardby/continuation";
|
|
9
|
+
/** Mirrors coding/protocol.ts's tagSchema — validated independently here since this is where it reaches GitHub. */
|
|
10
|
+
const SAFE_PULL_REQUEST_TAG = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,31}$/;
|
|
11
|
+
/** Mirrors the runId shape validated elsewhere (coding/protocol.ts, git.ts) — validated independently here too. */
|
|
12
|
+
const SAFE_RUN_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
13
|
+
const SAFE_COMMIT_SHA = /^[0-9a-f]{40}$/;
|
|
14
|
+
function record(value) {
|
|
15
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
16
|
+
throw new Error("github_api_invalid_response");
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
function positiveInteger(value) {
|
|
20
|
+
if (!Number.isSafeInteger(value) || Number(value) <= 0)
|
|
21
|
+
throw new Error("github_api_invalid_response");
|
|
22
|
+
return Number(value);
|
|
23
|
+
}
|
|
24
|
+
function safeApiError(response) {
|
|
25
|
+
const requestId = response.headers.get("x-github-request-id");
|
|
26
|
+
return new Error(`github_api_error:${response.status}${requestId ? `:${requestId}` : ""}`);
|
|
27
|
+
}
|
|
28
|
+
/** Accepts bounded opaque installation tokens without allowing control characters. */
|
|
29
|
+
export function isSafeGitHubInstallationToken(value) {
|
|
30
|
+
return (typeof value === "string" &&
|
|
31
|
+
value.length >= 20 &&
|
|
32
|
+
Buffer.byteLength(value, "utf8") <= 512 &&
|
|
33
|
+
/^[A-Za-z0-9._-]+$/.test(value));
|
|
34
|
+
}
|
|
35
|
+
function pullRequestTitle(input) {
|
|
36
|
+
return input.tag ? `[${input.tag}] Wardby run ${input.runId}` : `Wardby run ${input.runId}`;
|
|
37
|
+
}
|
|
38
|
+
/** The hidden run marker stays first and unconditional: createOrFindDraftPullRequest's idempotent lookup depends on it. */
|
|
39
|
+
function pullRequestBody(input) {
|
|
40
|
+
const sections = [`${RUN_MARKER_PREFIX}${input.runId} -->`];
|
|
41
|
+
if (input.summary)
|
|
42
|
+
sections.push(input.summary);
|
|
43
|
+
if (input.tests?.length) {
|
|
44
|
+
sections.push(["**Tests:**", ...input.tests.map((test) => `- \`${test.command}\`: ${test.outcome}`)].join("\n"));
|
|
45
|
+
}
|
|
46
|
+
return sections.join("\n\n");
|
|
47
|
+
}
|
|
48
|
+
/** The hidden marker stays first and unconditional: findStatusComment's lookup depends on it. */
|
|
49
|
+
function statusCommentBody(runId, body) {
|
|
50
|
+
return `${STATUS_COMMENT_MARKER_PREFIX}${runId} -->\n\n${body}`;
|
|
51
|
+
}
|
|
52
|
+
function normalizeApiBaseUrl(value) {
|
|
53
|
+
const url = new URL(value);
|
|
54
|
+
if (url.protocol !== "https:" ||
|
|
55
|
+
url.hostname.toLowerCase() !== "api.github.com" ||
|
|
56
|
+
url.port ||
|
|
57
|
+
url.username ||
|
|
58
|
+
url.password ||
|
|
59
|
+
url.search ||
|
|
60
|
+
url.hash ||
|
|
61
|
+
url.pathname !== "/") {
|
|
62
|
+
throw new Error("github_api_base_url_invalid");
|
|
63
|
+
}
|
|
64
|
+
return url.toString().replace(/\/$/, "");
|
|
65
|
+
}
|
|
66
|
+
function normalizePrivateKey(value) {
|
|
67
|
+
return value.includes("\\n") && !value.includes("\n") ? value.replaceAll("\\n", "\n") : value;
|
|
68
|
+
}
|
|
69
|
+
export class GitHubAppClient {
|
|
70
|
+
config;
|
|
71
|
+
fetchImpl;
|
|
72
|
+
now;
|
|
73
|
+
apiBaseUrl;
|
|
74
|
+
apiVersion;
|
|
75
|
+
constructor(config, fetchImpl = globalThis.fetch, now = () => new Date()) {
|
|
76
|
+
this.config = config;
|
|
77
|
+
this.fetchImpl = fetchImpl;
|
|
78
|
+
this.now = now;
|
|
79
|
+
if (!/^[1-9]\d*$/.test(config.appId))
|
|
80
|
+
throw new Error("github_app_id_invalid");
|
|
81
|
+
if (!config.privateKey.trim())
|
|
82
|
+
throw new Error("github_app_private_key_invalid");
|
|
83
|
+
this.apiBaseUrl = normalizeApiBaseUrl(config.apiBaseUrl ?? DEFAULT_API_BASE_URL);
|
|
84
|
+
this.apiVersion = config.apiVersion ?? DEFAULT_API_VERSION;
|
|
85
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(this.apiVersion))
|
|
86
|
+
throw new Error("github_api_version_invalid");
|
|
87
|
+
}
|
|
88
|
+
async withRepositoryToken(repository, action) {
|
|
89
|
+
const normalized = normalizeGitHubRepository(repository);
|
|
90
|
+
const token = await this.mintRepositoryToken(normalized);
|
|
91
|
+
try {
|
|
92
|
+
return await action(token);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
await this.revokeToken(token).catch(() => undefined);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Isolated from withRepositoryToken on purpose: "Checks: write" is a
|
|
100
|
+
* genuinely distinct GitHub App permission from contents/pull_requests
|
|
101
|
+
* (confirmed empirically). Minting it separately means an ungranted-permission
|
|
102
|
+
* failure here can only ever disable the check-run mechanism, never the
|
|
103
|
+
* real git push, PR, or status comment. (Status comments, below, do NOT
|
|
104
|
+
* need this treatment: PR-comment writes work fine on the existing
|
|
105
|
+
* contents/pull_requests token -- confirmed empirically after "Issues:
|
|
106
|
+
* write" alone, on its own isolated token, kept returning 403 "Resource
|
|
107
|
+
* not accessible by integration" even once granted; commenting on a PR
|
|
108
|
+
* apparently isn't gated the same way a genuine issue comment might be.)
|
|
109
|
+
*/
|
|
110
|
+
async withChecksToken(repository, action) {
|
|
111
|
+
const normalized = normalizeGitHubRepository(repository);
|
|
112
|
+
const token = await this.mintChecksToken(normalized);
|
|
113
|
+
try {
|
|
114
|
+
return await action(token);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
await this.revokeToken(token).catch(() => undefined);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async upsertContinuationStatusComment(input) {
|
|
121
|
+
const repository = normalizeGitHubRepository(input.repository);
|
|
122
|
+
const baseRef = normalizeGitRef(input.baseRef);
|
|
123
|
+
const headRef = normalizeGitRef(input.headRef);
|
|
124
|
+
if (!SAFE_RUN_ID.test(input.runId) || !SAFE_RUN_ID.test(input.rootRunId)) {
|
|
125
|
+
throw new Error("github_status_comment_input_invalid");
|
|
126
|
+
}
|
|
127
|
+
await this.withRepositoryToken(repository, async (token) => {
|
|
128
|
+
const pr = await this.findPullRequest(token, { runId: input.rootRunId, repository, baseRef, headRef });
|
|
129
|
+
if (!pr)
|
|
130
|
+
return;
|
|
131
|
+
const existing = await this.findStatusComment(token, repository, pr.number, input.runId);
|
|
132
|
+
const [owner, name] = repository.split("/");
|
|
133
|
+
const body = statusCommentBody(input.runId, input.body);
|
|
134
|
+
if (existing) {
|
|
135
|
+
await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/comments/${existing.id}`, token, { method: "PATCH", body: JSON.stringify({ body }) }, [200]);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/${pr.number}/comments`, token, { method: "POST", body: JSON.stringify({ body }) }, [201]);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
async updateContinuationStatusComment(input) {
|
|
143
|
+
const repository = normalizeGitHubRepository(input.repository);
|
|
144
|
+
const baseRef = normalizeGitRef(input.baseRef);
|
|
145
|
+
const headRef = normalizeGitRef(input.headRef);
|
|
146
|
+
if (!SAFE_RUN_ID.test(input.runId) || !SAFE_RUN_ID.test(input.rootRunId)) {
|
|
147
|
+
throw new Error("github_status_comment_input_invalid");
|
|
148
|
+
}
|
|
149
|
+
await this.withRepositoryToken(repository, async (token) => {
|
|
150
|
+
const pr = await this.findPullRequest(token, { runId: input.rootRunId, repository, baseRef, headRef });
|
|
151
|
+
if (!pr)
|
|
152
|
+
return;
|
|
153
|
+
const existing = await this.findStatusComment(token, repository, pr.number, input.runId);
|
|
154
|
+
if (!existing)
|
|
155
|
+
return;
|
|
156
|
+
const [owner, name] = repository.split("/");
|
|
157
|
+
await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/comments/${existing.id}`, token, { method: "PATCH", body: JSON.stringify({ body: statusCommentBody(input.runId, input.body) }) }, [200]);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
async createContinuationCheckRun(input) {
|
|
161
|
+
const repository = normalizeGitHubRepository(input.repository);
|
|
162
|
+
if (!SAFE_RUN_ID.test(input.runId) || !SAFE_COMMIT_SHA.test(input.headSha)) {
|
|
163
|
+
throw new Error("github_check_run_input_invalid");
|
|
164
|
+
}
|
|
165
|
+
await this.withChecksToken(repository, async (token) => {
|
|
166
|
+
const existing = await this.findCheckRun(token, repository, input.headSha, input.runId);
|
|
167
|
+
if (existing)
|
|
168
|
+
return;
|
|
169
|
+
const [owner, name] = repository.split("/");
|
|
170
|
+
await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/check-runs`, token, {
|
|
171
|
+
method: "POST",
|
|
172
|
+
body: JSON.stringify({
|
|
173
|
+
name: CONTINUATION_CHECK_RUN_NAME,
|
|
174
|
+
head_sha: input.headSha,
|
|
175
|
+
status: "in_progress",
|
|
176
|
+
external_id: input.runId,
|
|
177
|
+
}),
|
|
178
|
+
}, [201]);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async completeContinuationCheckRun(input) {
|
|
182
|
+
const repository = normalizeGitHubRepository(input.repository);
|
|
183
|
+
if (!SAFE_RUN_ID.test(input.runId) || !SAFE_COMMIT_SHA.test(input.headSha)) {
|
|
184
|
+
throw new Error("github_check_run_input_invalid");
|
|
185
|
+
}
|
|
186
|
+
await this.withChecksToken(repository, async (token) => {
|
|
187
|
+
const existing = await this.findCheckRun(token, repository, input.headSha, input.runId);
|
|
188
|
+
if (!existing)
|
|
189
|
+
return;
|
|
190
|
+
const [owner, name] = repository.split("/");
|
|
191
|
+
await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/check-runs/${existing.id}`, token, {
|
|
192
|
+
method: "PATCH",
|
|
193
|
+
body: JSON.stringify({
|
|
194
|
+
status: "completed",
|
|
195
|
+
conclusion: input.outcome === "succeeded" ? "success" : "failure",
|
|
196
|
+
}),
|
|
197
|
+
}, [200]);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
async createOrFindDraftPullRequest(input) {
|
|
201
|
+
const repository = normalizeGitHubRepository(input.repository);
|
|
202
|
+
const baseRef = normalizeGitRef(input.baseRef);
|
|
203
|
+
const headRef = normalizeGitRef(input.headRef);
|
|
204
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(input.runId) || headRef !== `wardby/run-${input.runId}`) {
|
|
205
|
+
throw new Error("github_pull_request_input_invalid");
|
|
206
|
+
}
|
|
207
|
+
if (input.tag !== undefined && !SAFE_PULL_REQUEST_TAG.test(input.tag)) {
|
|
208
|
+
throw new Error("github_pull_request_tag_invalid");
|
|
209
|
+
}
|
|
210
|
+
return this.withRepositoryToken(repository, async (token) => {
|
|
211
|
+
const existing = await this.findPullRequest(token, { ...input, repository, baseRef, headRef });
|
|
212
|
+
if (existing)
|
|
213
|
+
return existing;
|
|
214
|
+
const [owner, name] = repository.split("/");
|
|
215
|
+
const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/pulls`, token, {
|
|
216
|
+
method: "POST",
|
|
217
|
+
body: JSON.stringify({
|
|
218
|
+
title: pullRequestTitle(input),
|
|
219
|
+
head: headRef,
|
|
220
|
+
base: baseRef,
|
|
221
|
+
body: pullRequestBody(input),
|
|
222
|
+
draft: true,
|
|
223
|
+
}),
|
|
224
|
+
}, [201, 422]);
|
|
225
|
+
if (response.status === 201)
|
|
226
|
+
return this.parsePullRequest(await response.json(), repository);
|
|
227
|
+
const raced = await this.findPullRequest(token, { ...input, repository, baseRef, headRef });
|
|
228
|
+
if (raced)
|
|
229
|
+
return raced;
|
|
230
|
+
throw safeApiError(response);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
async appJwt() {
|
|
234
|
+
let keyObject;
|
|
235
|
+
try {
|
|
236
|
+
keyObject = createPrivateKey(normalizePrivateKey(this.config.privateKey));
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
throw new Error("github_app_private_key_invalid");
|
|
240
|
+
}
|
|
241
|
+
const pkcs8 = keyObject.export({ type: "pkcs8", format: "pem" }).toString();
|
|
242
|
+
const key = await importPKCS8(pkcs8, "RS256");
|
|
243
|
+
const now = Math.floor(this.now().getTime() / 1000);
|
|
244
|
+
return new SignJWT({})
|
|
245
|
+
.setProtectedHeader({ alg: "RS256", typ: "JWT" })
|
|
246
|
+
.setIssuedAt(now - 60)
|
|
247
|
+
.setExpirationTime(now + 9 * 60)
|
|
248
|
+
.setIssuer(this.config.appId)
|
|
249
|
+
.sign(key);
|
|
250
|
+
}
|
|
251
|
+
async mintRepositoryToken(repository) {
|
|
252
|
+
return this.mintScopedToken(repository, { contents: "write", pull_requests: "write" }, (name, level) => (name === "contents" && level === "write") ||
|
|
253
|
+
(name === "pull_requests" && level === "write") ||
|
|
254
|
+
(name === "metadata" && level === "read"));
|
|
255
|
+
}
|
|
256
|
+
async mintChecksToken(repository) {
|
|
257
|
+
return this.mintScopedToken(repository, { checks: "write" }, (name, level) => (name === "checks" && level === "write") || (name === "metadata" && level === "read"));
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Shared installation-token mint: requests exactly `permissions`, then
|
|
261
|
+
* verifies the response granted exactly that (no more, no less, modulo
|
|
262
|
+
* the always-implicit `metadata: read`) before trusting the token. Each
|
|
263
|
+
* caller requests the narrowest permission set it needs — see
|
|
264
|
+
* withIssuesToken/withChecksToken for why these are minted separately
|
|
265
|
+
* from withRepositoryToken rather than requesting a union of everything.
|
|
266
|
+
*/
|
|
267
|
+
async mintScopedToken(repository, permissions, isAllowedPermission) {
|
|
268
|
+
const [owner, name] = repository.split("/");
|
|
269
|
+
const appJwt = await this.appJwt();
|
|
270
|
+
const installationResponse = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/installation`, appJwt);
|
|
271
|
+
const installation = record(await installationResponse.json());
|
|
272
|
+
const installationId = positiveInteger(installation.id);
|
|
273
|
+
const tokenResponse = await this.requestJson(`/app/installations/${installationId}/access_tokens`, appJwt, { method: "POST", body: JSON.stringify({ repositories: [name], permissions }) }, [201]);
|
|
274
|
+
const payload = record(await tokenResponse.json());
|
|
275
|
+
const grantedPermissions = record(payload.permissions);
|
|
276
|
+
const repositories = Array.isArray(payload.repositories) ? payload.repositories.map(record) : [];
|
|
277
|
+
const scopedRepository = repositories.some((candidate) => typeof candidate.full_name === "string" && candidate.full_name.toLowerCase() === repository);
|
|
278
|
+
const expiresAt = typeof payload.expires_at === "string" ? Date.parse(payload.expires_at) : Number.NaN;
|
|
279
|
+
const unexpectedPermission = Object.entries(grantedPermissions).some(([permissionName, level]) => !isAllowedPermission(permissionName, level));
|
|
280
|
+
const requestedGranted = Object.entries(permissions).every(([permissionName, level]) => grantedPermissions[permissionName] === level);
|
|
281
|
+
if (!isSafeGitHubInstallationToken(payload.token) ||
|
|
282
|
+
!Number.isFinite(expiresAt) ||
|
|
283
|
+
expiresAt <= this.now().getTime() + 60_000 ||
|
|
284
|
+
!requestedGranted ||
|
|
285
|
+
unexpectedPermission ||
|
|
286
|
+
!scopedRepository) {
|
|
287
|
+
throw new Error("github_installation_token_scope_invalid");
|
|
288
|
+
}
|
|
289
|
+
return payload.token;
|
|
290
|
+
}
|
|
291
|
+
async revokeToken(token) {
|
|
292
|
+
await this.requestJson("/installation/token", token, { method: "DELETE" }, [204]);
|
|
293
|
+
}
|
|
294
|
+
async findPullRequest(token, input) {
|
|
295
|
+
const [owner, name] = input.repository.split("/");
|
|
296
|
+
const query = new URLSearchParams({
|
|
297
|
+
state: "open",
|
|
298
|
+
head: `${owner}:${input.headRef}`,
|
|
299
|
+
base: input.baseRef,
|
|
300
|
+
per_page: "100",
|
|
301
|
+
});
|
|
302
|
+
const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/pulls?${query.toString()}`, token);
|
|
303
|
+
const payload = await response.json();
|
|
304
|
+
if (!Array.isArray(payload))
|
|
305
|
+
throw new Error("github_api_invalid_response");
|
|
306
|
+
const marker = `${RUN_MARKER_PREFIX}${input.runId} -->`;
|
|
307
|
+
for (const item of payload) {
|
|
308
|
+
const candidate = record(item);
|
|
309
|
+
if (typeof candidate.body === "string" && candidate.body.includes(marker)) {
|
|
310
|
+
if (candidate.draft !== true)
|
|
311
|
+
throw new Error("github_pull_request_not_draft");
|
|
312
|
+
return this.parsePullRequest(candidate, input.repository);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
parsePullRequest(value, repository) {
|
|
318
|
+
const payload = record(value);
|
|
319
|
+
const number = positiveInteger(payload.number);
|
|
320
|
+
if (payload.draft !== true)
|
|
321
|
+
throw new Error("github_pull_request_not_draft");
|
|
322
|
+
const expectedUrl = `https://github.com/${repository}/pull/${number}`;
|
|
323
|
+
if (typeof payload.html_url !== "string")
|
|
324
|
+
throw new Error("github_pull_request_response_invalid");
|
|
325
|
+
let receivedUrl;
|
|
326
|
+
try {
|
|
327
|
+
receivedUrl = new URL(payload.html_url);
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
throw new Error("github_pull_request_response_invalid");
|
|
331
|
+
}
|
|
332
|
+
if (receivedUrl.protocol !== "https:" ||
|
|
333
|
+
receivedUrl.hostname.toLowerCase() !== "github.com" ||
|
|
334
|
+
receivedUrl.port ||
|
|
335
|
+
receivedUrl.username ||
|
|
336
|
+
receivedUrl.password ||
|
|
337
|
+
receivedUrl.search ||
|
|
338
|
+
receivedUrl.hash ||
|
|
339
|
+
receivedUrl.pathname.toLowerCase() !== new URL(expectedUrl).pathname.toLowerCase()) {
|
|
340
|
+
throw new Error("github_pull_request_response_invalid");
|
|
341
|
+
}
|
|
342
|
+
return { number, url: expectedUrl };
|
|
343
|
+
}
|
|
344
|
+
async findStatusComment(token, repository, pullRequestNumber, runId) {
|
|
345
|
+
const [owner, name] = repository.split("/");
|
|
346
|
+
const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/${pullRequestNumber}/comments?per_page=100`, token);
|
|
347
|
+
const payload = await response.json();
|
|
348
|
+
if (!Array.isArray(payload))
|
|
349
|
+
throw new Error("github_api_invalid_response");
|
|
350
|
+
const marker = `${STATUS_COMMENT_MARKER_PREFIX}${runId} -->`;
|
|
351
|
+
for (const item of payload) {
|
|
352
|
+
const candidate = record(item);
|
|
353
|
+
if (typeof candidate.body === "string" && candidate.body.includes(marker)) {
|
|
354
|
+
return { id: positiveInteger(candidate.id) };
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
async findCheckRun(token, repository, headSha, runId) {
|
|
360
|
+
const [owner, name] = repository.split("/");
|
|
361
|
+
const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/commits/${headSha}/check-runs?per_page=100`, token);
|
|
362
|
+
const payload = record(await response.json());
|
|
363
|
+
const checkRuns = Array.isArray(payload.check_runs) ? payload.check_runs.map(record) : [];
|
|
364
|
+
for (const candidate of checkRuns) {
|
|
365
|
+
if (candidate.external_id === runId)
|
|
366
|
+
return { id: positiveInteger(candidate.id) };
|
|
367
|
+
}
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
async requestJson(path, bearer, init = {}, expectedStatuses = [200]) {
|
|
371
|
+
let response;
|
|
372
|
+
try {
|
|
373
|
+
response = await this.fetchImpl(`${this.apiBaseUrl}${path}`, {
|
|
374
|
+
...init,
|
|
375
|
+
headers: {
|
|
376
|
+
accept: "application/vnd.github+json",
|
|
377
|
+
authorization: `Bearer ${bearer}`,
|
|
378
|
+
"content-type": "application/json",
|
|
379
|
+
"user-agent": "wardby",
|
|
380
|
+
"x-github-api-version": this.apiVersion,
|
|
381
|
+
...init.headers,
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
// Keep the cause: the operator log needs the transport reason (DNS,
|
|
387
|
+
// TLS, connect timeout); the message itself stays fixed and safe.
|
|
388
|
+
throw new Error("github_api_unavailable", { cause: error });
|
|
389
|
+
}
|
|
390
|
+
if (!expectedStatuses.includes(response.status))
|
|
391
|
+
throw safeApiError(response);
|
|
392
|
+
return response;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { GitHubVcsConfig, ProviderConfig } from "../../config/providers.js";
|
|
2
|
+
import type { VcsProvider } from "./types.js";
|
|
3
|
+
export * from "./types.js";
|
|
4
|
+
export { GitHubAppClient } from "./github.js";
|
|
5
|
+
export { GitVcsProvider, NodeGitCommandRunner } from "./git.js";
|
|
6
|
+
export declare function buildVcsProvider(providerConfig: Pick<ProviderConfig, "vcs">, config: GitHubVcsConfig): VcsProvider;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { tmpdir } from "node:os";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { GitHubAppClient } from "./github.js";
|
|
4
|
+
import { GitVcsProvider } from "./git.js";
|
|
5
|
+
export * from "./types.js";
|
|
6
|
+
export { GitHubAppClient } from "./github.js";
|
|
7
|
+
export { GitVcsProvider, NodeGitCommandRunner } from "./git.js";
|
|
8
|
+
export function buildVcsProvider(providerConfig, config) {
|
|
9
|
+
if (providerConfig.vcs !== "github")
|
|
10
|
+
throw new Error(`VCS_PROVIDER=${String(providerConfig.vcs)} is not supported.`);
|
|
11
|
+
if (!config.appId || !config.privateKey) {
|
|
12
|
+
throw new Error("GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY are required by the GitHub VCS adapter.");
|
|
13
|
+
}
|
|
14
|
+
const rootDir = resolve(config.workRoot ?? resolve(tmpdir(), "wardby-vcs"));
|
|
15
|
+
const github = new GitHubAppClient({
|
|
16
|
+
appId: config.appId,
|
|
17
|
+
privateKey: config.privateKey,
|
|
18
|
+
apiVersion: config.apiVersion,
|
|
19
|
+
});
|
|
20
|
+
return new GitVcsProvider({
|
|
21
|
+
rootDir,
|
|
22
|
+
github,
|
|
23
|
+
maxChangedFiles: config.maxChangedFiles,
|
|
24
|
+
maxDiffBytes: config.maxDiffBytes,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export interface VcsPrepareInput {
|
|
2
|
+
runId: string;
|
|
3
|
+
repository: string;
|
|
4
|
+
baseRef: string;
|
|
5
|
+
headRef: string;
|
|
6
|
+
protectedPaths: string[];
|
|
7
|
+
/**
|
|
8
|
+
* Revision-in-place: set when headRef is an EXISTING branch to continue
|
|
9
|
+
* rather than a fresh one
|
|
10
|
+
* to create off baseRef. `rootRunId` is the id of the run that originally
|
|
11
|
+
* opened the PR headRef belongs to (used for PR lookup identity, since
|
|
12
|
+
* GitHub's find-by-marker keys on that run's id, not this one's) -- the
|
|
13
|
+
* caller (dispatch.ts) must already have verified, against the database,
|
|
14
|
+
* that this run is legitimately allowed to continue that branch before
|
|
15
|
+
* ever setting this; this layer trusts it exactly as much as it already
|
|
16
|
+
* trusts baseRef, no more, and cannot independently re-verify a database
|
|
17
|
+
* fact.
|
|
18
|
+
*/
|
|
19
|
+
continuation?: {
|
|
20
|
+
rootRunId: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Trusted paths are returned by the provider, never accepted from worker output. */
|
|
24
|
+
export interface PreparedWorkspace {
|
|
25
|
+
id: string;
|
|
26
|
+
runId: string;
|
|
27
|
+
repository: string;
|
|
28
|
+
baseRef: string;
|
|
29
|
+
headRef: string;
|
|
30
|
+
/** For a fresh workspace, the commit baseRef pointed at when cloned. For
|
|
31
|
+
* a continuation, the tip of the branch being continued at clone time --
|
|
32
|
+
* each round is independently validated against its own agent's rules at
|
|
33
|
+
* commit time (see design doc §8.3), so there's no need to re-derive or
|
|
34
|
+
* re-validate the PR's true original root here; "where this round
|
|
35
|
+
* started" is the only anchor the single-new-commit and protected-path
|
|
36
|
+
* checks need. */
|
|
37
|
+
baseCommit: string;
|
|
38
|
+
workspacePath: string;
|
|
39
|
+
gitMetadataPath: string;
|
|
40
|
+
protectedPaths: string[];
|
|
41
|
+
continuation?: {
|
|
42
|
+
rootRunId: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Already-validated/redacted agent-authored fields (coding/protocol.ts) surfaced in the opened PR, if any. */
|
|
46
|
+
export interface FinalizeChangesDetails {
|
|
47
|
+
summary?: string;
|
|
48
|
+
tests?: readonly {
|
|
49
|
+
command: string;
|
|
50
|
+
outcome: "passed" | "failed" | "skipped";
|
|
51
|
+
}[];
|
|
52
|
+
tag?: string;
|
|
53
|
+
}
|
|
54
|
+
export type FinalizeChangesResult = {
|
|
55
|
+
outcome: "no_changes";
|
|
56
|
+
repository: string;
|
|
57
|
+
baseRef: string;
|
|
58
|
+
baseCommit: string;
|
|
59
|
+
} | {
|
|
60
|
+
outcome: "pull_request_opened";
|
|
61
|
+
repository: string;
|
|
62
|
+
baseRef: string;
|
|
63
|
+
baseCommit: string;
|
|
64
|
+
headRef: string;
|
|
65
|
+
commitSha: string;
|
|
66
|
+
pullRequestNumber: number;
|
|
67
|
+
pullRequestUrl: string;
|
|
68
|
+
} | {
|
|
69
|
+
/** Revision-in-place: pushed a new commit onto an existing open PR's
|
|
70
|
+
* branch instead of opening a new one. */
|
|
71
|
+
outcome: "pull_request_updated";
|
|
72
|
+
repository: string;
|
|
73
|
+
baseRef: string;
|
|
74
|
+
baseCommit: string;
|
|
75
|
+
headRef: string;
|
|
76
|
+
commitSha: string;
|
|
77
|
+
pullRequestNumber: number;
|
|
78
|
+
pullRequestUrl: string;
|
|
79
|
+
};
|
|
80
|
+
export interface VcsProvider {
|
|
81
|
+
prepareWorkspace(input: VcsPrepareInput): Promise<PreparedWorkspace>;
|
|
82
|
+
recoverWorkspace(input: VcsPrepareInput): Promise<PreparedWorkspace | null>;
|
|
83
|
+
finalizeChanges(workspace: PreparedWorkspace, details?: FinalizeChangesDetails): Promise<FinalizeChangesResult>;
|
|
84
|
+
cleanup(workspace: PreparedWorkspace): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Best-effort "wardby is working on this" signal for a continuation
|
|
87
|
+
* a no-op when `workspace.continuation` is unset, since a fresh run has
|
|
88
|
+
* no PR to attach anything to until its one commit lands. Optional
|
|
89
|
+
* because this is a GitHub-specific concept, not a universal VCS one --
|
|
90
|
+
* a future non-GitHub provider (or a test double) can simply omit it,
|
|
91
|
+
* mirroring `Executor.resolveCodingWorkerImage?` (dispatch.ts calls it
|
|
92
|
+
* with `?.()`). Implementations MUST NOT throw: this is strictly
|
|
93
|
+
* observability, never allowed to affect the real coding run.
|
|
94
|
+
* `details.agentName`, when present, is the human-readable agent name
|
|
95
|
+
* (e.g. "knock-knock-implement") surfaced alongside the opaque run id --
|
|
96
|
+
* particularly useful for the cross-agent case, where the agent
|
|
97
|
+
* continuing the PR isn't the one that originally opened it.
|
|
98
|
+
*/
|
|
99
|
+
notifyContinuationStarted?(workspace: PreparedWorkspace, details?: {
|
|
100
|
+
agentName?: string;
|
|
101
|
+
}): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Companion to `notifyContinuationStarted` -- must find and update
|
|
104
|
+
* whatever that call created, never create fresh state itself (a
|
|
105
|
+
* "finished" status with no preceding "in progress" one would be
|
|
106
|
+
* confusing, and could happen if the process crashed between the two
|
|
107
|
+
* calls). Safe to call more than once for the same run. Same
|
|
108
|
+
* never-throw contract as `notifyContinuationStarted`. `details.summary`,
|
|
109
|
+
* when present, is the agent's own already-validated/redacted summary
|
|
110
|
+
* (the same text that goes in the PR body -- see
|
|
111
|
+
* `FinalizeChangesDetails.summary`) so the "done" status reflects what
|
|
112
|
+
* actually happened instead of a caller having to go find out.
|
|
113
|
+
*/
|
|
114
|
+
notifyContinuationFinished?(workspace: PreparedWorkspace, outcome: "succeeded" | "failed", details?: {
|
|
115
|
+
summary?: string;
|
|
116
|
+
agentName?: string;
|
|
117
|
+
}): Promise<void>;
|
|
118
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|