@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,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The coding concurrency queue's driver. claimProvisioning (container.ts)
|
|
3
|
+
* refuses a slot and sets CodingRun.queuedAt when CODING_MAX_CONCURRENT
|
|
4
|
+
* coding runs already hold one; this module later:
|
|
5
|
+
*
|
|
6
|
+
* 1. fails queued runs that waited longer than CODING_QUEUE_TIMEOUT_SEC
|
|
7
|
+
* (coding_queue_timeout), with a conditional update so concurrent
|
|
8
|
+
* drains never double-fail a run;
|
|
9
|
+
* 2. starts the oldest queued runs, at most as many as there are free
|
|
10
|
+
* slots, so order stays FIFO in practice. Starting is fire-and-forget,
|
|
11
|
+
* like dispatchRun: Executor.start resolves only when the run ends.
|
|
12
|
+
* A run that loses a slot race is simply re-queued by claimProvisioning.
|
|
13
|
+
*
|
|
14
|
+
* Called on the scheduler leader's tick and immediately in any process
|
|
15
|
+
* whose coding run just finished (ContainerExecutor onSlotReleased).
|
|
16
|
+
*/
|
|
17
|
+
import type { PrismaClient } from "@prisma/client";
|
|
18
|
+
import type { Executor } from "../providers/executor/types.js";
|
|
19
|
+
export declare const CODING_QUEUE_TIMEOUT_ERROR = "coding_queue_timeout";
|
|
20
|
+
export type CodingQueueDb = Pick<PrismaClient, "run" | "codingRun" | "$transaction">;
|
|
21
|
+
export interface DrainCodingQueueOptions {
|
|
22
|
+
db: CodingQueueDb;
|
|
23
|
+
executor: Executor;
|
|
24
|
+
maxConcurrent: number;
|
|
25
|
+
queueTimeoutSec: number;
|
|
26
|
+
now?: () => Date;
|
|
27
|
+
}
|
|
28
|
+
export interface DrainCodingQueueResult {
|
|
29
|
+
timedOut: number;
|
|
30
|
+
started: string[];
|
|
31
|
+
}
|
|
32
|
+
export declare function drainCodingQueue(options: DrainCodingQueueOptions): Promise<DrainCodingQueueResult>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { markRunFailedFromExecutorError } from "./dispatch.js";
|
|
2
|
+
import { logger } from "./logger.js";
|
|
3
|
+
const queueLog = logger.child({ module: "coding-queue" });
|
|
4
|
+
export const CODING_QUEUE_TIMEOUT_ERROR = "coding_queue_timeout";
|
|
5
|
+
export async function drainCodingQueue(options) {
|
|
6
|
+
const { db, executor } = options;
|
|
7
|
+
const now = options.now?.() ?? new Date();
|
|
8
|
+
const cutoff = new Date(now.getTime() - options.queueTimeoutSec * 1000);
|
|
9
|
+
let timedOut = 0;
|
|
10
|
+
const expired = await db.codingRun.findMany({
|
|
11
|
+
where: { queuedAt: { lt: cutoff }, run: { status: "pending" } },
|
|
12
|
+
select: { runId: true },
|
|
13
|
+
});
|
|
14
|
+
for (const { runId } of expired) {
|
|
15
|
+
// Both writes commit together: a run is only ever observed with
|
|
16
|
+
// status=failed/error=coding_queue_timeout and no matching
|
|
17
|
+
// failureCategory, never one without the other.
|
|
18
|
+
const didTimeOut = await db.$transaction(async (tx) => {
|
|
19
|
+
const failed = await tx.run.updateMany({
|
|
20
|
+
where: { id: runId, status: "pending" },
|
|
21
|
+
data: { status: "failed", error: CODING_QUEUE_TIMEOUT_ERROR, finishedAt: now },
|
|
22
|
+
});
|
|
23
|
+
if (failed.count !== 1)
|
|
24
|
+
return false;
|
|
25
|
+
await tx.codingRun.update({ where: { runId }, data: { failureCategory: CODING_QUEUE_TIMEOUT_ERROR } });
|
|
26
|
+
return true;
|
|
27
|
+
});
|
|
28
|
+
if (didTimeOut)
|
|
29
|
+
timedOut += 1;
|
|
30
|
+
}
|
|
31
|
+
const active = await db.codingRun.count({
|
|
32
|
+
where: { jobBackend: { not: null }, run: { status: { in: ["pending", "running"] } } },
|
|
33
|
+
});
|
|
34
|
+
const free = options.maxConcurrent - active;
|
|
35
|
+
if (free <= 0)
|
|
36
|
+
return { timedOut, started: [] };
|
|
37
|
+
const next = await db.codingRun.findMany({
|
|
38
|
+
where: { queuedAt: { not: null }, jobBackend: null, run: { status: "pending" } },
|
|
39
|
+
// runId breaks queuedAt ties, matching claimProvisioning's queue order.
|
|
40
|
+
orderBy: [{ queuedAt: "asc" }, { runId: "asc" }],
|
|
41
|
+
take: free,
|
|
42
|
+
select: { runId: true },
|
|
43
|
+
});
|
|
44
|
+
for (const { runId } of next) {
|
|
45
|
+
// Promise.resolve().then(...) defers executor.start's call itself into
|
|
46
|
+
// the microtask queue, so even a synchronous throw from a misbehaving
|
|
47
|
+
// Executor implementation lands in this .catch instead of escaping
|
|
48
|
+
// drainCodingQueue's own call stack.
|
|
49
|
+
void Promise.resolve()
|
|
50
|
+
.then(() => executor.start(runId))
|
|
51
|
+
.catch((err) => markRunFailedFromExecutorError(db, runId, err).catch((err2) => queueLog.error({ err: err2, runId }, "failed to persist queued-run start failure")));
|
|
52
|
+
}
|
|
53
|
+
return { timedOut, started: next.map((row) => row.runId) };
|
|
54
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron window computation for the scheduler — pure wrapper around `croner`
|
|
3
|
+
* so the due-window / DST logic is unit-testable without a live clock or DB.
|
|
4
|
+
*/
|
|
5
|
+
export interface DueCheckInput {
|
|
6
|
+
schedule: string;
|
|
7
|
+
timezone: string;
|
|
8
|
+
/** Start of the most recent window this agent already fired for, or null if never. */
|
|
9
|
+
lastScheduledAt: Date | null;
|
|
10
|
+
now: Date;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The start of the most recent due window at/before `now` that hasn't
|
|
14
|
+
* already been fired for, or `null` if not due.
|
|
15
|
+
*
|
|
16
|
+
* Only ever returns the single latest window — this is what implements
|
|
17
|
+
* "skip, don't backfill": if several windows elapsed since
|
|
18
|
+
* `lastScheduledAt` (e.g. the scheduler was down), the caller advances
|
|
19
|
+
* `lastScheduledAt` straight to this value, abandoning the earlier ones.
|
|
20
|
+
*
|
|
21
|
+
* Boundary note: croner's `previousRuns` treats its reference instant as
|
|
22
|
+
* excluded (strictly-before), so a `now` that lands on the exact
|
|
23
|
+
* millisecond of a scheduled tick reports that tick as not-yet-due until a
|
|
24
|
+
* subsequent check. Irrelevant in practice at a multi-second tick
|
|
25
|
+
* interval, but worth knowing if this is ever called with a synthetic
|
|
26
|
+
* `now` that lands exactly on a boundary.
|
|
27
|
+
*/
|
|
28
|
+
export declare function dueWindow(input: DueCheckInput): Date | null;
|
|
29
|
+
export declare function isDue(input: DueCheckInput): boolean;
|
|
30
|
+
/** Throws a descriptive error for an invalid cron expression or timezone. */
|
|
31
|
+
export declare function validateCronExpression(schedule: string, timezone: string): void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron window computation for the scheduler — pure wrapper around `croner`
|
|
3
|
+
* so the due-window / DST logic is unit-testable without a live clock or DB.
|
|
4
|
+
*/
|
|
5
|
+
import { Cron } from "croner";
|
|
6
|
+
/**
|
|
7
|
+
* The start of the most recent due window at/before `now` that hasn't
|
|
8
|
+
* already been fired for, or `null` if not due.
|
|
9
|
+
*
|
|
10
|
+
* Only ever returns the single latest window — this is what implements
|
|
11
|
+
* "skip, don't backfill": if several windows elapsed since
|
|
12
|
+
* `lastScheduledAt` (e.g. the scheduler was down), the caller advances
|
|
13
|
+
* `lastScheduledAt` straight to this value, abandoning the earlier ones.
|
|
14
|
+
*
|
|
15
|
+
* Boundary note: croner's `previousRuns` treats its reference instant as
|
|
16
|
+
* excluded (strictly-before), so a `now` that lands on the exact
|
|
17
|
+
* millisecond of a scheduled tick reports that tick as not-yet-due until a
|
|
18
|
+
* subsequent check. Irrelevant in practice at a multi-second tick
|
|
19
|
+
* interval, but worth knowing if this is ever called with a synthetic
|
|
20
|
+
* `now` that lands exactly on a boundary.
|
|
21
|
+
*/
|
|
22
|
+
export function dueWindow(input) {
|
|
23
|
+
const { schedule, timezone, lastScheduledAt, now } = input;
|
|
24
|
+
const job = new Cron(schedule, { timezone });
|
|
25
|
+
const [mostRecent] = job.previousRuns(1, now);
|
|
26
|
+
if (!mostRecent) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
if (lastScheduledAt && mostRecent.getTime() <= lastScheduledAt.getTime()) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return mostRecent;
|
|
33
|
+
}
|
|
34
|
+
export function isDue(input) {
|
|
35
|
+
return dueWindow(input) !== null;
|
|
36
|
+
}
|
|
37
|
+
/** Throws a descriptive error for an invalid cron expression or timezone. */
|
|
38
|
+
export function validateCronExpression(schedule, timezone) {
|
|
39
|
+
// The Cron constructor validates the pattern synchronously, but an
|
|
40
|
+
// invalid timezone only throws once a run is actually computed.
|
|
41
|
+
const job = new Cron(schedule, { timezone });
|
|
42
|
+
job.nextRun();
|
|
43
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named datastores shared across agents: an owned resource (like Secret)
|
|
3
|
+
* attached to agents via AgentDatastore, addressed at sandbox point-of-use
|
|
4
|
+
* by a per-attachment `boundName` (sharedDatastore.get(boundName, key)) —
|
|
5
|
+
* same boundName convention as AgentSecret.
|
|
6
|
+
*/
|
|
7
|
+
import type { PrismaClient, Datastore as DatastoreRow } from "@prisma/client";
|
|
8
|
+
import type { Datastore, DatastoreSetOptions, DatastoreValue } from "../providers/datastore/types.js";
|
|
9
|
+
export type DatastoreMetadata = Pick<DatastoreRow, "id" | "name" | "ownerId" | "createdAt" | "updatedAt">;
|
|
10
|
+
export interface SharedDatastoreAccessor {
|
|
11
|
+
get(boundName: string, key: string): Promise<DatastoreValue | undefined>;
|
|
12
|
+
set(boundName: string, key: string, value: DatastoreValue, opts?: DatastoreSetOptions): Promise<void>;
|
|
13
|
+
delete(boundName: string, key: string): Promise<void>;
|
|
14
|
+
list(boundName: string, prefix?: string): Promise<string[]>;
|
|
15
|
+
}
|
|
16
|
+
/** Creates a named datastore owned by `ownerId`. */
|
|
17
|
+
export declare function createDatastore(name: string, ownerId: string, db: PrismaClient): Promise<DatastoreRow>;
|
|
18
|
+
export declare function listDatastores(ownerId: string, db: PrismaClient): Promise<DatastoreMetadata[]>;
|
|
19
|
+
export declare function deleteDatastore(datastoreId: string, db: PrismaClient): Promise<void>;
|
|
20
|
+
/** Attaches a datastore (by id — the caller already resolved/verified ownership) to an agent under `boundName`. */
|
|
21
|
+
export declare function attachDatastore(agentId: string, datastoreId: string, db: PrismaClient, boundName: string): Promise<void>;
|
|
22
|
+
/** Detaches by point-of-use name — (agentId, boundName) uniquely identifies the edge. */
|
|
23
|
+
export declare function detachDatastore(agentId: string, boundName: string, db: PrismaClient): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Resolves `boundName` to the Datastore attached to this agent under that
|
|
26
|
+
* name, then delegates to its *Shared methods. A `boundName` with no
|
|
27
|
+
* attachment behaves like a cache-miss for get/list (undefined/[]), and
|
|
28
|
+
* throws for set (a write must never silently no-op) — delete is a no-op,
|
|
29
|
+
* matching scopeDatastore's existing "disallowed delete is a no-op"
|
|
30
|
+
* convention for the private per-agent store.
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildSharedDatastoreAccessor(agentId: string, datastore: Datastore, db: Pick<PrismaClient, "agentDatastore">): SharedDatastoreAccessor;
|
|
33
|
+
/**
|
|
34
|
+
* Wraps a `SharedDatastoreAccessor` so every operation is confined to a
|
|
35
|
+
* bound name explicitly granted a prefix list, and within it, keys under one
|
|
36
|
+
* of those prefixes — mirrors scopeDatastore's OWASP LLM08 fix, per bound
|
|
37
|
+
* name instead of one flat prefix list. A boundName absent from the map
|
|
38
|
+
* denies everything, the same default-deny convention as an empty
|
|
39
|
+
* allowedDatastorePrefixes.
|
|
40
|
+
*/
|
|
41
|
+
export declare function scopeSharedDatastoreAccessor(accessor: SharedDatastoreAccessor, allowedPrefixes: Readonly<Record<string, readonly string[]>>): SharedDatastoreAccessor;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { boundedString } from "../sandbox/bounded-json.js";
|
|
2
|
+
/** Creates a named datastore owned by `ownerId`. */
|
|
3
|
+
export async function createDatastore(name, ownerId, db) {
|
|
4
|
+
boundedString(name, 1024);
|
|
5
|
+
return db.datastore.create({ data: { name, ownerId } });
|
|
6
|
+
}
|
|
7
|
+
export async function listDatastores(ownerId, db) {
|
|
8
|
+
const rows = await db.datastore.findMany({ where: { ownerId } });
|
|
9
|
+
return rows.map(({ id, name, ownerId: owner, createdAt, updatedAt }) => ({
|
|
10
|
+
id,
|
|
11
|
+
name,
|
|
12
|
+
ownerId: owner,
|
|
13
|
+
createdAt,
|
|
14
|
+
updatedAt,
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
export async function deleteDatastore(datastoreId, db) {
|
|
18
|
+
await db.datastore.delete({ where: { id: datastoreId } });
|
|
19
|
+
}
|
|
20
|
+
/** Attaches a datastore (by id — the caller already resolved/verified ownership) to an agent under `boundName`. */
|
|
21
|
+
export async function attachDatastore(agentId, datastoreId, db, boundName) {
|
|
22
|
+
await db.agentDatastore.create({ data: { agentId, datastoreId, boundName } });
|
|
23
|
+
}
|
|
24
|
+
/** Detaches by point-of-use name — (agentId, boundName) uniquely identifies the edge. */
|
|
25
|
+
export async function detachDatastore(agentId, boundName, db) {
|
|
26
|
+
await db.agentDatastore.deleteMany({ where: { agentId, boundName } });
|
|
27
|
+
}
|
|
28
|
+
async function resolveDatastoreId(agentId, boundName, db) {
|
|
29
|
+
const attachment = await db.agentDatastore.findFirst({ where: { agentId, boundName } });
|
|
30
|
+
return attachment?.datastoreId;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolves `boundName` to the Datastore attached to this agent under that
|
|
34
|
+
* name, then delegates to its *Shared methods. A `boundName` with no
|
|
35
|
+
* attachment behaves like a cache-miss for get/list (undefined/[]), and
|
|
36
|
+
* throws for set (a write must never silently no-op) — delete is a no-op,
|
|
37
|
+
* matching scopeDatastore's existing "disallowed delete is a no-op"
|
|
38
|
+
* convention for the private per-agent store.
|
|
39
|
+
*/
|
|
40
|
+
export function buildSharedDatastoreAccessor(agentId, datastore, db) {
|
|
41
|
+
return {
|
|
42
|
+
async get(boundName, key) {
|
|
43
|
+
const datastoreId = await resolveDatastoreId(agentId, boundName, db);
|
|
44
|
+
if (!datastoreId)
|
|
45
|
+
return undefined;
|
|
46
|
+
return datastore.getShared(datastoreId, key);
|
|
47
|
+
},
|
|
48
|
+
async set(boundName, key, value, opts) {
|
|
49
|
+
const datastoreId = await resolveDatastoreId(agentId, boundName, db);
|
|
50
|
+
if (!datastoreId)
|
|
51
|
+
throw new Error("datastore_not_bound");
|
|
52
|
+
await datastore.setShared(datastoreId, key, value, opts);
|
|
53
|
+
},
|
|
54
|
+
async delete(boundName, key) {
|
|
55
|
+
const datastoreId = await resolveDatastoreId(agentId, boundName, db);
|
|
56
|
+
if (!datastoreId)
|
|
57
|
+
return;
|
|
58
|
+
await datastore.deleteShared(datastoreId, key);
|
|
59
|
+
},
|
|
60
|
+
async list(boundName, prefix) {
|
|
61
|
+
const datastoreId = await resolveDatastoreId(agentId, boundName, db);
|
|
62
|
+
if (!datastoreId)
|
|
63
|
+
return [];
|
|
64
|
+
return datastore.listShared(datastoreId, prefix);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Wraps a `SharedDatastoreAccessor` so every operation is confined to a
|
|
70
|
+
* bound name explicitly granted a prefix list, and within it, keys under one
|
|
71
|
+
* of those prefixes — mirrors scopeDatastore's OWASP LLM08 fix, per bound
|
|
72
|
+
* name instead of one flat prefix list. A boundName absent from the map
|
|
73
|
+
* denies everything, the same default-deny convention as an empty
|
|
74
|
+
* allowedDatastorePrefixes.
|
|
75
|
+
*/
|
|
76
|
+
export function scopeSharedDatastoreAccessor(accessor, allowedPrefixes) {
|
|
77
|
+
function isAllowed(boundName, key) {
|
|
78
|
+
const prefixes = allowedPrefixes[boundName];
|
|
79
|
+
return prefixes !== undefined && prefixes.some((prefix) => key.startsWith(prefix));
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
async get(boundName, key) {
|
|
83
|
+
if (!isAllowed(boundName, key))
|
|
84
|
+
return undefined;
|
|
85
|
+
return accessor.get(boundName, key);
|
|
86
|
+
},
|
|
87
|
+
async set(boundName, key, value, opts) {
|
|
88
|
+
if (!isAllowed(boundName, key))
|
|
89
|
+
throw new Error("datastore_prefix_not_allowed");
|
|
90
|
+
await accessor.set(boundName, key, value, opts);
|
|
91
|
+
},
|
|
92
|
+
async delete(boundName, key) {
|
|
93
|
+
if (!isAllowed(boundName, key))
|
|
94
|
+
return;
|
|
95
|
+
await accessor.delete(boundName, key);
|
|
96
|
+
},
|
|
97
|
+
async list(boundName, prefix) {
|
|
98
|
+
const prefixes = allowedPrefixes[boundName];
|
|
99
|
+
if (!prefixes)
|
|
100
|
+
return [];
|
|
101
|
+
const keys = await accessor.list(boundName, prefix);
|
|
102
|
+
return keys.filter((key) => prefixes.some((p) => key.startsWith(p)));
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
package/dist/core/db.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Prisma, PrismaClient, Run, RunTrigger, Task } from "@prisma/client";
|
|
2
|
+
import type { Executor } from "../providers/executor/types.js";
|
|
3
|
+
export type DispatchDb = Pick<PrismaClient, "agent" | "run" | "codingRun" | "task" | "webhook" | "$transaction" | "$queryRaw">;
|
|
4
|
+
type DispatchTx = Pick<Prisma.TransactionClient, "agent" | "run" | "codingRun" | "task" | "webhook" | "$queryRaw">;
|
|
5
|
+
type DispatchAgent = Prisma.AgentGetPayload<{
|
|
6
|
+
include: {
|
|
7
|
+
codingProfile: true;
|
|
8
|
+
};
|
|
9
|
+
}>;
|
|
10
|
+
export interface DispatchRunOptions {
|
|
11
|
+
db: DispatchDb;
|
|
12
|
+
executor: Executor;
|
|
13
|
+
agentId: string;
|
|
14
|
+
trigger?: RunTrigger;
|
|
15
|
+
codingTask?: string;
|
|
16
|
+
codingBaseRef?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Revision-in-place: the id of a prior CodingRun whose branch/PR this
|
|
19
|
+
* dispatch should push a new
|
|
20
|
+
* commit onto instead of opening a fresh branch. Resolved and verified
|
|
21
|
+
* here against the database (same repository, and it actually reached a
|
|
22
|
+
* PR-opening outcome) -- never trusted as a raw branch name, and
|
|
23
|
+
* deliberately not restricted to the same agent (cross-role continuation,
|
|
24
|
+
* e.g. plan -> implement on one PR, is allowed by design). Mutually
|
|
25
|
+
* exclusive with `codingBaseRef`. Coding agents only.
|
|
26
|
+
*/
|
|
27
|
+
continuesCodingRunId?: string;
|
|
28
|
+
now?: Date;
|
|
29
|
+
lockAgent?: boolean;
|
|
30
|
+
task?: {
|
|
31
|
+
principalId: string;
|
|
32
|
+
ttlMs: number;
|
|
33
|
+
};
|
|
34
|
+
beforePersist?: (tx: DispatchTx, agent: DispatchAgent) => Promise<boolean>;
|
|
35
|
+
/** Sub-agent dispatch (see AgentSubAgent): links this run into a run tree. */
|
|
36
|
+
parentRunId?: string;
|
|
37
|
+
grantedParentMemoryKeys?: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Per-run task text for a NATIVE agent (appended to its systemPrompt at
|
|
40
|
+
* load time — see runner.ts). The native equivalent of `codingTask`;
|
|
41
|
+
* mutually exclusive with it, since a native run has no CodingRun.
|
|
42
|
+
*/
|
|
43
|
+
taskOverride?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Overrides `agent.budgetUsd` for this run's coding budget reservation —
|
|
46
|
+
* used by sub-agent dispatch to apply the run-tree's tightened effective
|
|
47
|
+
* budget (see src/core/budget-groups.ts) instead of the agent's raw
|
|
48
|
+
* per-run ceiling, the same composability already applied to native runs.
|
|
49
|
+
* Ignored for native agents, which compute their own effective budget
|
|
50
|
+
* inside executeRun's load step.
|
|
51
|
+
*/
|
|
52
|
+
budgetUsdOverride?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Default (false/undefined): fire-and-forget, matching every existing
|
|
55
|
+
* caller (webhook ingress, trigger_agent, the scheduler) — none of them
|
|
56
|
+
* want to block on a potentially long-running coding container. Set true
|
|
57
|
+
* only when the caller genuinely wants to await the run's terminal state,
|
|
58
|
+
* e.g. a sub-agent dispatch tool blocking the parent's own turn.
|
|
59
|
+
*/
|
|
60
|
+
awaitExecution?: boolean;
|
|
61
|
+
}
|
|
62
|
+
export interface DispatchRunResult {
|
|
63
|
+
run: Run;
|
|
64
|
+
task?: Task;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Record an executor-level failure on a run that never reached a terminal
|
|
68
|
+
* state itself. Deliberately idempotent: the same failure can arrive twice —
|
|
69
|
+
* DbosExecutor.start() calls this when the workflow handle rejects, and
|
|
70
|
+
* dispatchRun's `.catch` around that same `start()` call calls it again — and
|
|
71
|
+
* the conditional `updateMany` makes the second call match zero rows rather
|
|
72
|
+
* than overwrite whatever landed in between.
|
|
73
|
+
*/
|
|
74
|
+
export declare function markRunFailedFromExecutorError(db: Pick<DispatchDb, "run">, runId: string, err: unknown): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Persists every detached run input in one transaction, then invokes the
|
|
77
|
+
* executor only after commit. A null result means the caller's transactional
|
|
78
|
+
* claim was no longer valid (for example, a schedule was already claimed).
|
|
79
|
+
*/
|
|
80
|
+
export declare function dispatchRun(options: DispatchRunOptions): Promise<DispatchRunResult | null>;
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { CODING_PROTOCOL_VERSION, CodingTaskInputSchema, normalizeGitHubRepository, publicCodingRunResult, } from "../coding/protocol.js";
|
|
2
|
+
import { assertCodingProviderModel } from "../coding/provider.js";
|
|
3
|
+
import { logger } from "./logger.js";
|
|
4
|
+
const dispatchLog = logger.child({ module: "dispatch" });
|
|
5
|
+
function isSerializationConflict(err) {
|
|
6
|
+
if (!err || typeof err !== "object")
|
|
7
|
+
return false;
|
|
8
|
+
const candidate = err;
|
|
9
|
+
return candidate.code === "P2034" || (candidate.code === "P2010" && candidate.meta?.code === "40001");
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Record an executor-level failure on a run that never reached a terminal
|
|
13
|
+
* state itself. Deliberately idempotent: the same failure can arrive twice —
|
|
14
|
+
* DbosExecutor.start() calls this when the workflow handle rejects, and
|
|
15
|
+
* dispatchRun's `.catch` around that same `start()` call calls it again — and
|
|
16
|
+
* the conditional `updateMany` makes the second call match zero rows rather
|
|
17
|
+
* than overwrite whatever landed in between.
|
|
18
|
+
*/
|
|
19
|
+
export async function markRunFailedFromExecutorError(db, runId, err) {
|
|
20
|
+
await db.run.updateMany({
|
|
21
|
+
where: { id: runId, status: { in: ["pending", "running"] } },
|
|
22
|
+
data: {
|
|
23
|
+
status: "failed",
|
|
24
|
+
error: err instanceof Error ? err.message : String(err),
|
|
25
|
+
finishedAt: new Date(),
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Persists every detached run input in one transaction, then invokes the
|
|
31
|
+
* executor only after commit. A null result means the caller's transactional
|
|
32
|
+
* claim was no longer valid (for example, a schedule was already claimed).
|
|
33
|
+
*/
|
|
34
|
+
export async function dispatchRun(options) {
|
|
35
|
+
const now = options.now ?? new Date();
|
|
36
|
+
const persistOnce = () => options.db.$transaction(async (tx) => {
|
|
37
|
+
if (options.lockAgent) {
|
|
38
|
+
const locked = await tx.$queryRaw `
|
|
39
|
+
SELECT "id" FROM "Agent" WHERE "id" = ${options.agentId} FOR UPDATE SKIP LOCKED
|
|
40
|
+
`;
|
|
41
|
+
if (locked.length === 0)
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const agent = await tx.agent.findUnique({
|
|
45
|
+
where: { id: options.agentId },
|
|
46
|
+
include: { codingProfile: true },
|
|
47
|
+
});
|
|
48
|
+
if (!agent)
|
|
49
|
+
throw new Error(`Unknown agent "${options.agentId}".`);
|
|
50
|
+
if (options.beforePersist && !(await options.beforePersist(tx, agent)))
|
|
51
|
+
return null;
|
|
52
|
+
if (options.taskOverride !== undefined && agent.kind !== "native") {
|
|
53
|
+
throw new Error("taskOverride can only be supplied for a native agent.");
|
|
54
|
+
}
|
|
55
|
+
const run = await tx.run.create({
|
|
56
|
+
data: {
|
|
57
|
+
agentId: agent.id,
|
|
58
|
+
trigger: options.trigger ?? "manual",
|
|
59
|
+
executionManaged: true,
|
|
60
|
+
parentRunId: options.parentRunId,
|
|
61
|
+
grantedParentMemoryKeys: options.grantedParentMemoryKeys ?? [],
|
|
62
|
+
taskOverride: options.taskOverride,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
if (agent.kind === "coding") {
|
|
66
|
+
if (!agent.codingProfile)
|
|
67
|
+
throw new Error(`Coding agent "${agent.id}" has no coding profile.`);
|
|
68
|
+
assertCodingProviderModel(agent.codingProfile.provider, agent.model);
|
|
69
|
+
const task = options.codingTask ?? agent.codingProfile.defaultTask;
|
|
70
|
+
if (!task)
|
|
71
|
+
throw new Error(`Coding agent "${agent.id}" requires a task.`);
|
|
72
|
+
const budgetUsd = options.budgetUsdOverride ?? Number(agent.budgetUsd);
|
|
73
|
+
let baseRef = options.codingBaseRef ?? agent.codingProfile.baseRef;
|
|
74
|
+
let headRef = `wardby/run-${run.id}`;
|
|
75
|
+
let continuationOf;
|
|
76
|
+
let rootCodingRunId;
|
|
77
|
+
if (options.continuesCodingRunId !== undefined) {
|
|
78
|
+
if (options.codingBaseRef !== undefined) {
|
|
79
|
+
throw new Error("continuesCodingRunId cannot be combined with codingBaseRef.");
|
|
80
|
+
}
|
|
81
|
+
const candidate = await tx.codingRun.findUnique({ where: { runId: options.continuesCodingRunId } });
|
|
82
|
+
if (!candidate)
|
|
83
|
+
throw new Error("Cannot continue an unknown coding run.");
|
|
84
|
+
const root = candidate.rootCodingRunId
|
|
85
|
+
? await tx.codingRun.findUnique({ where: { runId: candidate.rootCodingRunId } })
|
|
86
|
+
: candidate;
|
|
87
|
+
if (!root)
|
|
88
|
+
throw new Error("Cannot continue an unknown coding run.");
|
|
89
|
+
if (normalizeGitHubRepository(root.repository) !== normalizeGitHubRepository(agent.codingProfile.repository)) {
|
|
90
|
+
throw new Error("Cannot continue a coding run from a different repository.");
|
|
91
|
+
}
|
|
92
|
+
const rootResult = publicCodingRunResult(root.result);
|
|
93
|
+
if (rootResult?.outcome !== "pull_request_opened" && rootResult?.outcome !== "pull_request_updated") {
|
|
94
|
+
throw new Error("Cannot continue a coding run that never opened a pull request.");
|
|
95
|
+
}
|
|
96
|
+
baseRef = root.baseRef;
|
|
97
|
+
headRef = root.headRef;
|
|
98
|
+
continuationOf = { runId: root.runId };
|
|
99
|
+
rootCodingRunId = root.runId;
|
|
100
|
+
}
|
|
101
|
+
const input = CodingTaskInputSchema.parse({
|
|
102
|
+
schemaVersion: CODING_PROTOCOL_VERSION,
|
|
103
|
+
runId: run.id,
|
|
104
|
+
repository: agent.codingProfile.repository,
|
|
105
|
+
baseRef,
|
|
106
|
+
headRef,
|
|
107
|
+
task,
|
|
108
|
+
model: agent.model,
|
|
109
|
+
budgetUsd,
|
|
110
|
+
deadlineAt: new Date(now.getTime() + agent.codingProfile.timeoutSec * 1000).toISOString(),
|
|
111
|
+
continuationOf,
|
|
112
|
+
});
|
|
113
|
+
const workerImage = options.executor.resolveCodingWorkerImage?.({
|
|
114
|
+
provider: agent.codingProfile.provider,
|
|
115
|
+
toolchain: agent.codingProfile.toolchain,
|
|
116
|
+
toolchainVersion: agent.codingProfile.toolchainVersion,
|
|
117
|
+
workerImageRef: agent.codingProfile.workerImageRef,
|
|
118
|
+
});
|
|
119
|
+
await tx.codingRun.create({
|
|
120
|
+
data: {
|
|
121
|
+
runId: run.id,
|
|
122
|
+
task: input.task,
|
|
123
|
+
repository: input.repository,
|
|
124
|
+
baseRef: input.baseRef,
|
|
125
|
+
headRef: input.headRef,
|
|
126
|
+
provider: agent.codingProfile.provider,
|
|
127
|
+
model: input.model,
|
|
128
|
+
timeoutSec: agent.codingProfile.timeoutSec,
|
|
129
|
+
allowedEgress: agent.codingProfile.allowedEgress,
|
|
130
|
+
protectedPaths: agent.codingProfile.protectedPaths,
|
|
131
|
+
workerImage,
|
|
132
|
+
budgetReservedUsd: budgetUsd,
|
|
133
|
+
rootCodingRunId,
|
|
134
|
+
workspaceDiskMb: agent.codingProfile.workspaceDiskMb,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
else if (options.codingTask !== undefined ||
|
|
139
|
+
options.codingBaseRef !== undefined ||
|
|
140
|
+
options.continuesCodingRunId !== undefined) {
|
|
141
|
+
throw new Error("Coding overrides cannot be supplied for a native agent.");
|
|
142
|
+
}
|
|
143
|
+
const task = options.task
|
|
144
|
+
? await tx.task.create({
|
|
145
|
+
data: {
|
|
146
|
+
kind: "run",
|
|
147
|
+
runId: run.id,
|
|
148
|
+
principalId: options.task.principalId,
|
|
149
|
+
status: "working",
|
|
150
|
+
ttlAt: new Date(now.getTime() + options.task.ttlMs),
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
: undefined;
|
|
154
|
+
return { run, task };
|
|
155
|
+
}, { isolationLevel: "Serializable" });
|
|
156
|
+
let persisted = null;
|
|
157
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
158
|
+
try {
|
|
159
|
+
persisted = await persistOnce();
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
if (!isSerializationConflict(err) || attempt === 2)
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (!persisted)
|
|
168
|
+
return null;
|
|
169
|
+
const runStart = async () => {
|
|
170
|
+
try {
|
|
171
|
+
await options.executor.start(persisted.run.id);
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
await markRunFailedFromExecutorError(options.db, persisted.run.id, err).catch((err2) => dispatchLog.error({ err: err2, runId: persisted.run.id }, "failed to persist executor start failure"));
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
if (options.awaitExecution) {
|
|
178
|
+
await runStart();
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
void runStart();
|
|
182
|
+
}
|
|
183
|
+
return persisted;
|
|
184
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The native multi-turn engine — the default (and, in Phase 3, only)
|
|
3
|
+
* `Engine` implementation. Owns the whole turn loop and the whole budget
|
|
4
|
+
* lifecycle: pre-flight refuse, cumulative pre-turn gate, mid-stream
|
|
5
|
+
* cutoff, graceful wind-down. See providers/engine/types.ts for why this
|
|
6
|
+
* lives behind a seam (a future LangGraph engine swaps in here) and why
|
|
7
|
+
* the budget guardrail must stay above whichever engine is running.
|
|
8
|
+
*/
|
|
9
|
+
import type { Engine, EngineResult, EngineRunContext } from "../providers/engine/types.js";
|
|
10
|
+
export declare class NativeEngine implements Engine {
|
|
11
|
+
run(ctx: EngineRunContext): Promise<EngineResult>;
|
|
12
|
+
/**
|
|
13
|
+
* One final turn with tools disabled, checked against *remaining*
|
|
14
|
+
* budget and mid-stream-capped so it cannot overshoot. If remaining
|
|
15
|
+
* budget can't even afford this turn's estimated input, hard-stop
|
|
16
|
+
* without it — either way the terminal status is `budget_exhausted`.
|
|
17
|
+
*/
|
|
18
|
+
private windDown;
|
|
19
|
+
private finish;
|
|
20
|
+
/**
|
|
21
|
+
* Streams one model turn, applying the mid-stream cutoff against the
|
|
22
|
+
* *cumulative* running total (this extends Phase 1's single-turn
|
|
23
|
+
* AbortController logic across the whole loop). Per-delta output-token
|
|
24
|
+
* counting mirrors runner.ts's O(delta)-per-event approach — see its
|
|
25
|
+
* comment for why summing per-delta counts beats re-tokenizing the
|
|
26
|
+
* whole accumulated output on every event.
|
|
27
|
+
*/
|
|
28
|
+
private runOneTurn;
|
|
29
|
+
}
|