@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,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure decision table for reconciling a stale `Run` that a DBOS workflow
|
|
3
|
+
* owns. Kept free of DBOS imports so it is unit-testable without Postgres.
|
|
4
|
+
*/
|
|
5
|
+
export interface WorkflowStatusLike {
|
|
6
|
+
status: string;
|
|
7
|
+
executorId?: string;
|
|
8
|
+
applicationVersion?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* How many times the reconciler will try to adopt (resume) one orphaned
|
|
12
|
+
* workflow before declaring the run lost.
|
|
13
|
+
*
|
|
14
|
+
* DBOS gates both recovery and dequeue on `application_version`: a workflow
|
|
15
|
+
* recorded under an older version is re-`resume`d on every reconciler pass
|
|
16
|
+
* and never dequeued, so without a bound the run sits `running` forever and
|
|
17
|
+
* the reconciler re-drives it every pass. `recover()` also short-circuits an
|
|
18
|
+
* outright version mismatch (see `versionMismatchReason`); this bound is the
|
|
19
|
+
* catch-all for every other reason a resume can fail to take.
|
|
20
|
+
*/
|
|
21
|
+
export declare const MAX_RESUME_ATTEMPTS = 3;
|
|
22
|
+
export declare const ADOPTION_EXHAUSTED_REASON = "Durable workflow could not be re-driven after 3 adoption attempts (executor/version mismatch).";
|
|
23
|
+
/**
|
|
24
|
+
* Given the number of adoption attempts already made for a run, should the
|
|
25
|
+
* reconciler stop trying and declare it lost? The counter lives in the
|
|
26
|
+
* executor (in memory, per process) — this is just the bound, kept pure so
|
|
27
|
+
* it can be tested without Postgres.
|
|
28
|
+
*/
|
|
29
|
+
export declare function shouldGiveUpAdoption(attemptsAlreadyMade: number): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Reason to give up immediately when the recorded workflow belongs to a
|
|
32
|
+
* different application version than this process runs — DBOS will never
|
|
33
|
+
* dequeue it here, so resuming it is pure churn. `undefined` when the two
|
|
34
|
+
* agree, or when either version is unknown (nothing to compare).
|
|
35
|
+
*/
|
|
36
|
+
export declare function versionMismatchReason(workflowVersion: string | undefined, processVersion: string | undefined): string | undefined;
|
|
37
|
+
export type RecoveryDecision = {
|
|
38
|
+
action: "active";
|
|
39
|
+
} | {
|
|
40
|
+
action: "resume";
|
|
41
|
+
} | {
|
|
42
|
+
action: "terminal";
|
|
43
|
+
} | {
|
|
44
|
+
action: "mark-failed";
|
|
45
|
+
error: string;
|
|
46
|
+
} | {
|
|
47
|
+
action: "lost";
|
|
48
|
+
reason: string;
|
|
49
|
+
};
|
|
50
|
+
export declare function decideRecovery(status: WorkflowStatusLike | null, runIsTerminal: boolean, selfExecutorId: string): RecoveryDecision;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure decision table for reconciling a stale `Run` that a DBOS workflow
|
|
3
|
+
* owns. Kept free of DBOS imports so it is unit-testable without Postgres.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* How many times the reconciler will try to adopt (resume) one orphaned
|
|
7
|
+
* workflow before declaring the run lost.
|
|
8
|
+
*
|
|
9
|
+
* DBOS gates both recovery and dequeue on `application_version`: a workflow
|
|
10
|
+
* recorded under an older version is re-`resume`d on every reconciler pass
|
|
11
|
+
* and never dequeued, so without a bound the run sits `running` forever and
|
|
12
|
+
* the reconciler re-drives it every pass. `recover()` also short-circuits an
|
|
13
|
+
* outright version mismatch (see `versionMismatchReason`); this bound is the
|
|
14
|
+
* catch-all for every other reason a resume can fail to take.
|
|
15
|
+
*/
|
|
16
|
+
export const MAX_RESUME_ATTEMPTS = 3;
|
|
17
|
+
export const ADOPTION_EXHAUSTED_REASON = `Durable workflow could not be re-driven after ${MAX_RESUME_ATTEMPTS} adoption attempts (executor/version mismatch).`;
|
|
18
|
+
/**
|
|
19
|
+
* Given the number of adoption attempts already made for a run, should the
|
|
20
|
+
* reconciler stop trying and declare it lost? The counter lives in the
|
|
21
|
+
* executor (in memory, per process) — this is just the bound, kept pure so
|
|
22
|
+
* it can be tested without Postgres.
|
|
23
|
+
*/
|
|
24
|
+
export function shouldGiveUpAdoption(attemptsAlreadyMade) {
|
|
25
|
+
return attemptsAlreadyMade >= MAX_RESUME_ATTEMPTS;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Reason to give up immediately when the recorded workflow belongs to a
|
|
29
|
+
* different application version than this process runs — DBOS will never
|
|
30
|
+
* dequeue it here, so resuming it is pure churn. `undefined` when the two
|
|
31
|
+
* agree, or when either version is unknown (nothing to compare).
|
|
32
|
+
*/
|
|
33
|
+
export function versionMismatchReason(workflowVersion, processVersion) {
|
|
34
|
+
if (!workflowVersion || !processVersion || workflowVersion === processVersion)
|
|
35
|
+
return undefined;
|
|
36
|
+
return `Durable workflow belongs to application version ${workflowVersion}; this process runs ${processVersion}.`;
|
|
37
|
+
}
|
|
38
|
+
const LIVE = new Set(["PENDING", "ENQUEUED", "DELAYED"]);
|
|
39
|
+
const FINISHED = new Set(["SUCCESS", "ERROR", "CANCELLED", "MAX_RECOVERY_ATTEMPTS_EXCEEDED"]);
|
|
40
|
+
export function decideRecovery(status, runIsTerminal, selfExecutorId) {
|
|
41
|
+
if (!status) {
|
|
42
|
+
return {
|
|
43
|
+
action: "lost",
|
|
44
|
+
reason: "Durable workflow record not found; the run was never started or its record was purged.",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (LIVE.has(status.status)) {
|
|
48
|
+
// A live workflow this process owns is simply still running (its
|
|
49
|
+
// heartbeat lapsed under load). One owned by another executor id whose
|
|
50
|
+
// heartbeat lapsed is orphaned — that executor died — so adopt it.
|
|
51
|
+
return status.status === "PENDING" && status.executorId === selfExecutorId
|
|
52
|
+
? { action: "active" }
|
|
53
|
+
: { action: "resume" };
|
|
54
|
+
}
|
|
55
|
+
if (FINISHED.has(status.status)) {
|
|
56
|
+
return runIsTerminal
|
|
57
|
+
? { action: "terminal" }
|
|
58
|
+
: {
|
|
59
|
+
action: "mark-failed",
|
|
60
|
+
error: `Durable workflow finished (${status.status}) without persisting a terminal run state.`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return { action: "lost", reason: `Durable workflow is in unrecognised status "${status.status}".` };
|
|
64
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 6 `Executor`: each native run is a DBOS durable workflow whose
|
|
3
|
+
* steps are the engine's LLM turns and tool calls (see StepRunner in
|
|
4
|
+
* providers/engine/types.ts). DBOS records every step result in Postgres;
|
|
5
|
+
* after a crash or redeploy `launch()` re-drives the workflows this
|
|
6
|
+
* executor id owned, and completed steps replay from the record — no
|
|
7
|
+
* repeated LLM spend for finished turns. The at-least-once window is one
|
|
8
|
+
* in-flight step: a turn that was mid-stream when the process died runs
|
|
9
|
+
* again on resume.
|
|
10
|
+
*
|
|
11
|
+
* The heartbeat is kept while the workflow runs here so the reconciler's
|
|
12
|
+
* stale-heartbeat detection is unchanged; the reconciler consults
|
|
13
|
+
* `recover()` (Task 6) before declaring a DBOS run lost.
|
|
14
|
+
*/
|
|
15
|
+
import type { PrismaClient } from "@prisma/client";
|
|
16
|
+
import type { ProviderRegistry } from "../index.js";
|
|
17
|
+
import type { DbosConfig } from "../../config/providers.js";
|
|
18
|
+
import { type RunnerDb } from "../../core/runner.js";
|
|
19
|
+
import type { Executor, ExecutionRecoveryResult, PersistedExecutionHandle } from "./types.js";
|
|
20
|
+
export declare const DBOS_BACKEND = "dbos";
|
|
21
|
+
type Providers = Pick<ProviderRegistry, "llm" | "engine" | "datastore" | "secrets" | "memory">;
|
|
22
|
+
type Db = RunnerDb & Pick<PrismaClient, "run">;
|
|
23
|
+
export declare class DbosExecutor implements Executor {
|
|
24
|
+
private readonly providers;
|
|
25
|
+
private readonly config;
|
|
26
|
+
private readonly db;
|
|
27
|
+
private readonly heartbeatIntervalMs;
|
|
28
|
+
private launched;
|
|
29
|
+
/** Non-optional once constructed — see the constructor's DBOS_EXECUTOR_ID guard. */
|
|
30
|
+
private readonly id;
|
|
31
|
+
/**
|
|
32
|
+
* Adoption attempts per run id, bounding `recover()`'s resume branch (see
|
|
33
|
+
* MAX_RESUME_ATTEMPTS). In memory and per process: a restart resets it,
|
|
34
|
+
* which is the right default — a restart is also the most likely thing to
|
|
35
|
+
* have fixed the version/executor mismatch that made adoption fail.
|
|
36
|
+
*/
|
|
37
|
+
private readonly resumeAttempts;
|
|
38
|
+
constructor(providers: Providers, config: DbosConfig, db?: Db, heartbeatIntervalMs?: number);
|
|
39
|
+
get executorId(): string;
|
|
40
|
+
launch(): Promise<void>;
|
|
41
|
+
close(): Promise<void>;
|
|
42
|
+
start(runId: string): Promise<void>;
|
|
43
|
+
stop(runId: string, reason?: string): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Answer the reconciler for a stale run. Never relaunches: `resume`
|
|
46
|
+
* re-drives the existing workflow from its last completed step, which is
|
|
47
|
+
* the same thing launch() does for this executor's own workflows.
|
|
48
|
+
*/
|
|
49
|
+
recover(handle: PersistedExecutionHandle): Promise<ExecutionRecoveryResult>;
|
|
50
|
+
/** Workflow body. Public only so the module-level registration can reach it. */
|
|
51
|
+
runInsideWorkflow(runId: string): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 6 `Executor`: each native run is a DBOS durable workflow whose
|
|
3
|
+
* steps are the engine's LLM turns and tool calls (see StepRunner in
|
|
4
|
+
* providers/engine/types.ts). DBOS records every step result in Postgres;
|
|
5
|
+
* after a crash or redeploy `launch()` re-drives the workflows this
|
|
6
|
+
* executor id owned, and completed steps replay from the record — no
|
|
7
|
+
* repeated LLM spend for finished turns. The at-least-once window is one
|
|
8
|
+
* in-flight step: a turn that was mid-stream when the process died runs
|
|
9
|
+
* again on resume.
|
|
10
|
+
*
|
|
11
|
+
* The heartbeat is kept while the workflow runs here so the reconciler's
|
|
12
|
+
* stale-heartbeat detection is unchanged; the reconciler consults
|
|
13
|
+
* `recover()` (Task 6) before declaring a DBOS run lost.
|
|
14
|
+
*/
|
|
15
|
+
import { DBOS, Error as DbosErrors } from "@dbos-inc/dbos-sdk";
|
|
16
|
+
import { executeRun, RunCancelledError } from "../../core/runner.js";
|
|
17
|
+
import { markRunFailedFromExecutorError } from "../../core/dispatch.js";
|
|
18
|
+
import { prisma as defaultDb } from "../../core/db.js";
|
|
19
|
+
import { HEARTBEAT_INTERVAL_MS } from "../../core/timing.js";
|
|
20
|
+
import { logger } from "../../core/logger.js";
|
|
21
|
+
import { ADOPTION_EXHAUSTED_REASON, decideRecovery, shouldGiveUpAdoption, versionMismatchReason, } from "./dbos-status.js";
|
|
22
|
+
export const DBOS_BACKEND = "dbos";
|
|
23
|
+
const dbosLog = logger.child({ module: "dbos-executor" });
|
|
24
|
+
/** The executor whose deps the registered workflow uses. Set by launch(). */
|
|
25
|
+
let activeExecutor;
|
|
26
|
+
/** Setter (rather than `activeExecutor = this` inline) to avoid aliasing `this` to a variable. */
|
|
27
|
+
function setActiveExecutor(executor) {
|
|
28
|
+
activeExecutor = executor;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Cancellation reasons by run id, recorded by `stop()` so the workflow body
|
|
32
|
+
* can put the operator's own words on the Run row instead of DBOS's internal
|
|
33
|
+
* "workflow was cancelled" text. Module-level because `stop()` and the
|
|
34
|
+
* workflow body are not guaranteed to be on the same DbosExecutor instance
|
|
35
|
+
* (the reconciler can stop a run this process adopted). Cleared in
|
|
36
|
+
* `runInsideWorkflow`'s finally.
|
|
37
|
+
*/
|
|
38
|
+
const cancellationReasons = new Map();
|
|
39
|
+
/**
|
|
40
|
+
* Bind the engine's step boundary to a DBOS checkpointed step. Never
|
|
41
|
+
* retried: a retry would re-spend.
|
|
42
|
+
*
|
|
43
|
+
* DBOS signals a cancelled workflow by throwing `DBOSWorkflowCancelledError`
|
|
44
|
+
* out of the next step boundary. Translated here into a `RunCancelledError`
|
|
45
|
+
* so executeRun's backstop records `cancelled` plus the operator's reason,
|
|
46
|
+
* rather than `failed` plus an SDK-internal message.
|
|
47
|
+
*/
|
|
48
|
+
const dbosStep = async (name, fn) => {
|
|
49
|
+
try {
|
|
50
|
+
return await DBOS.runStep(fn, { name, retriesAllowed: false });
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
if (err instanceof DbosErrors.DBOSWorkflowCancelledError) {
|
|
54
|
+
const reason = cancellationReasons.get(err.workflowID);
|
|
55
|
+
throw new RunCancelledError(`Run cancelled: ${reason ?? "no reason given"}`);
|
|
56
|
+
}
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Registered once at module load — DBOS requires registration before
|
|
62
|
+
* launch(). The body must be deterministic between steps; everything
|
|
63
|
+
* non-deterministic in executeRun/engine is inside a runStep.
|
|
64
|
+
*/
|
|
65
|
+
const runWorkflow = DBOS.registerWorkflow(async (runId) => {
|
|
66
|
+
const self = activeExecutor;
|
|
67
|
+
if (!self)
|
|
68
|
+
throw new Error("DbosExecutor workflow invoked before launch().");
|
|
69
|
+
await self.runInsideWorkflow(runId);
|
|
70
|
+
}, { name: "wardby.run" });
|
|
71
|
+
export class DbosExecutor {
|
|
72
|
+
providers;
|
|
73
|
+
config;
|
|
74
|
+
db;
|
|
75
|
+
heartbeatIntervalMs;
|
|
76
|
+
launched = false;
|
|
77
|
+
/** Non-optional once constructed — see the constructor's DBOS_EXECUTOR_ID guard. */
|
|
78
|
+
id;
|
|
79
|
+
/**
|
|
80
|
+
* Adoption attempts per run id, bounding `recover()`'s resume branch (see
|
|
81
|
+
* MAX_RESUME_ATTEMPTS). In memory and per process: a restart resets it,
|
|
82
|
+
* which is the right default — a restart is also the most likely thing to
|
|
83
|
+
* have fixed the version/executor mismatch that made adoption fail.
|
|
84
|
+
*/
|
|
85
|
+
resumeAttempts = new Map();
|
|
86
|
+
constructor(providers, config, db = defaultDb, heartbeatIntervalMs = HEARTBEAT_INTERVAL_MS) {
|
|
87
|
+
this.providers = providers;
|
|
88
|
+
this.config = config;
|
|
89
|
+
this.db = db;
|
|
90
|
+
this.heartbeatIntervalMs = heartbeatIntervalMs;
|
|
91
|
+
if (!config.systemDatabaseUrl) {
|
|
92
|
+
throw new Error("EXECUTOR=dbos requires DBOS_SYSTEM_DATABASE_URL or DATABASE_URL.");
|
|
93
|
+
}
|
|
94
|
+
if (!config.executorId) {
|
|
95
|
+
throw new Error("EXECUTOR=dbos requires DBOS_EXECUTOR_ID, unique per running process (scheduler and MCP need different values).");
|
|
96
|
+
}
|
|
97
|
+
this.id = config.executorId;
|
|
98
|
+
}
|
|
99
|
+
get executorId() {
|
|
100
|
+
return this.id;
|
|
101
|
+
}
|
|
102
|
+
async launch() {
|
|
103
|
+
if (this.launched)
|
|
104
|
+
return;
|
|
105
|
+
// DBOS is a process-wide singleton: the executor id is fixed by whoever
|
|
106
|
+
// launched it first, and setConfig below is skipped for everyone after.
|
|
107
|
+
// Fail loudly rather than run with an id that disagrees with reality —
|
|
108
|
+
// recover()'s ownership decisions are made against DBOS.executorID.
|
|
109
|
+
if (DBOS.isInitialized() && DBOS.executorID !== this.id) {
|
|
110
|
+
throw new Error(`DBOS is already launched with executor id "${DBOS.executorID}"; ` +
|
|
111
|
+
`cannot launch a second DbosExecutor with id "${this.id}" in the same process.`);
|
|
112
|
+
}
|
|
113
|
+
setActiveExecutor(this);
|
|
114
|
+
if (!DBOS.isInitialized()) {
|
|
115
|
+
DBOS.setConfig({
|
|
116
|
+
name: "wardby",
|
|
117
|
+
systemDatabaseUrl: this.config.systemDatabaseUrl,
|
|
118
|
+
systemDatabaseSchemaName: this.config.schemaName,
|
|
119
|
+
executorID: this.id,
|
|
120
|
+
runAdminServer: false,
|
|
121
|
+
logLevel: "warn",
|
|
122
|
+
});
|
|
123
|
+
await DBOS.launch();
|
|
124
|
+
}
|
|
125
|
+
this.launched = true;
|
|
126
|
+
dbosLog.info({ executorId: DBOS.executorID }, "DBOS executor launched");
|
|
127
|
+
}
|
|
128
|
+
async close() {
|
|
129
|
+
if (!this.launched)
|
|
130
|
+
return;
|
|
131
|
+
this.launched = false;
|
|
132
|
+
if (activeExecutor === this)
|
|
133
|
+
setActiveExecutor(undefined);
|
|
134
|
+
await DBOS.shutdown({ workflowCompletionTimeoutMS: 5_000 });
|
|
135
|
+
}
|
|
136
|
+
async start(runId) {
|
|
137
|
+
if (!this.launched)
|
|
138
|
+
await this.launch();
|
|
139
|
+
await this.db.run.updateMany({
|
|
140
|
+
where: { id: runId, executionBackend: null },
|
|
141
|
+
data: { executionBackend: DBOS_BACKEND },
|
|
142
|
+
});
|
|
143
|
+
// `workflowID: runId` alone gives the idempotency this executor needs: a
|
|
144
|
+
// second `startWorkflow` call with the same id attaches to the workflow
|
|
145
|
+
// already recorded under that id instead of re-running it. In this SDK
|
|
146
|
+
// (4.27.6) `duplicationPolicy: "return-existing"` is a *queue* dedup
|
|
147
|
+
// mechanism — it throws `DBOSInvalidWorkflowTransitionError` ("requires
|
|
148
|
+
// a queueName") when passed without `queueName`/`enqueueOptions.
|
|
149
|
+
// deduplicationID`, which don't apply to this non-queued, direct-start
|
|
150
|
+
// workflow. See dist/src/dbos.js `#invokeSingletonWorkflow`.
|
|
151
|
+
const handle = await DBOS.startWorkflow(runWorkflow, { workflowID: runId })(runId);
|
|
152
|
+
try {
|
|
153
|
+
await handle.getResult();
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
// executeRun's own catch backstop owns every terminal write for a run
|
|
157
|
+
// whose workflow body actually reached it — this only catches a
|
|
158
|
+
// workflow that failed or was cancelled *before* the body got that
|
|
159
|
+
// far, leaving the row pending/running. (A cancellation that races
|
|
160
|
+
// past a run's final step can leave the row's real, already-terminal
|
|
161
|
+
// outcome in place instead; that's expected — the reason is in the
|
|
162
|
+
// DBOS log, not here.) markRunFailedFromExecutorError's conditional
|
|
163
|
+
// update only ever touches a non-terminal row, so it never clobbers
|
|
164
|
+
// one executeRun already finished. It can also run a second time for
|
|
165
|
+
// the same failure — dispatchRun's own `.catch` around `start()` calls
|
|
166
|
+
// it too — which is harmless precisely because it is conditional and
|
|
167
|
+
// idempotent: the second call matches zero rows.
|
|
168
|
+
await markRunFailedFromExecutorError(this.db, runId, err);
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async stop(runId, reason) {
|
|
173
|
+
// Cancellation goes through DBOS, so this process has to be launched
|
|
174
|
+
// even when it never started this run (the reconciler/MCP can stop a run
|
|
175
|
+
// another instance is executing).
|
|
176
|
+
if (!this.launched)
|
|
177
|
+
await this.launch();
|
|
178
|
+
dbosLog.info({ runId, reason }, "cancelling durable run");
|
|
179
|
+
// Recorded before the cancel so the workflow's next step boundary can
|
|
180
|
+
// find it: DBOS only reports "cancelled", never why.
|
|
181
|
+
if (reason)
|
|
182
|
+
cancellationReasons.set(runId, reason);
|
|
183
|
+
await DBOS.cancelWorkflow(runId);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Answer the reconciler for a stale run. Never relaunches: `resume`
|
|
187
|
+
* re-drives the existing workflow from its last completed step, which is
|
|
188
|
+
* the same thing launch() does for this executor's own workflows.
|
|
189
|
+
*/
|
|
190
|
+
async recover(handle) {
|
|
191
|
+
if (handle.backend !== DBOS_BACKEND) {
|
|
192
|
+
return { state: "lost", reason: `DbosExecutor cannot recover backend "${handle.backend}".` };
|
|
193
|
+
}
|
|
194
|
+
if (!this.launched)
|
|
195
|
+
await this.launch();
|
|
196
|
+
const status = await DBOS.getWorkflowStatus(handle.id);
|
|
197
|
+
const row = await this.db.run.findUnique({ where: { id: handle.runId }, select: { status: true } });
|
|
198
|
+
const runIsTerminal = !!row && !["pending", "running"].includes(row.status);
|
|
199
|
+
// DBOS.executorID, not this.config.executorId: launch() skips setConfig
|
|
200
|
+
// when DBOS was already initialised elsewhere in the process, so the
|
|
201
|
+
// configured id is a request, and DBOS's is the fact ownership is
|
|
202
|
+
// decided on.
|
|
203
|
+
const decision = decideRecovery(status, runIsTerminal, DBOS.executorID);
|
|
204
|
+
switch (decision.action) {
|
|
205
|
+
case "active":
|
|
206
|
+
this.resumeAttempts.delete(handle.runId);
|
|
207
|
+
return { state: "active" };
|
|
208
|
+
case "resume": {
|
|
209
|
+
// DBOS gates recovery and dequeue on application_version. A workflow
|
|
210
|
+
// recorded under a different version is never dequeued here, so
|
|
211
|
+
// resuming it would be an unbounded loop that holds the run
|
|
212
|
+
// `running` forever: give up at once and let the reconciler reap it.
|
|
213
|
+
const mismatch = versionMismatchReason(status?.applicationVersion, DBOS.applicationVersion);
|
|
214
|
+
if (mismatch) {
|
|
215
|
+
this.resumeAttempts.delete(handle.runId);
|
|
216
|
+
dbosLog.warn({ runId: handle.runId, workflowVersion: status?.applicationVersion }, mismatch);
|
|
217
|
+
return { state: "lost", reason: mismatch };
|
|
218
|
+
}
|
|
219
|
+
// Belt and braces for every other reason a resume can fail to take
|
|
220
|
+
// (an id we can't dequeue for, a queue that never drains): bound the
|
|
221
|
+
// number of adoptions of one run so the reconciler stops re-driving
|
|
222
|
+
// it every pass.
|
|
223
|
+
const attempts = this.resumeAttempts.get(handle.runId) ?? 0;
|
|
224
|
+
if (shouldGiveUpAdoption(attempts)) {
|
|
225
|
+
this.resumeAttempts.delete(handle.runId);
|
|
226
|
+
dbosLog.warn({ runId: handle.runId, attempts }, "giving up on adopting orphaned durable run");
|
|
227
|
+
return { state: "lost", reason: ADOPTION_EXHAUSTED_REASON };
|
|
228
|
+
}
|
|
229
|
+
this.resumeAttempts.set(handle.runId, attempts + 1);
|
|
230
|
+
dbosLog.warn({ runId: handle.runId, owner: status?.executorId, attempt: attempts + 1 }, "adopting orphaned durable run");
|
|
231
|
+
// `resumeWorkflow` *enqueues* the workflow for recovery; whichever
|
|
232
|
+
// process dequeues it runs it, which need not be this one. So
|
|
233
|
+
// "active" here means "re-driven somewhere", not "running here" —
|
|
234
|
+
// and the handle below only resolves if we are the one who got it.
|
|
235
|
+
const resumed = await DBOS.resumeWorkflow(handle.id);
|
|
236
|
+
void resumed.getResult().catch((err) => dbosLog.error({ err, runId: handle.runId }, "adopted run failed"));
|
|
237
|
+
return { state: "active" };
|
|
238
|
+
}
|
|
239
|
+
case "terminal":
|
|
240
|
+
this.resumeAttempts.delete(handle.runId);
|
|
241
|
+
return { state: "terminal" };
|
|
242
|
+
case "mark-failed":
|
|
243
|
+
this.resumeAttempts.delete(handle.runId);
|
|
244
|
+
await this.db.run.updateMany({
|
|
245
|
+
where: { id: handle.runId, status: { in: ["pending", "running"] } },
|
|
246
|
+
data: { status: "failed", error: decision.error, finishedAt: new Date() },
|
|
247
|
+
});
|
|
248
|
+
return { state: "terminal" };
|
|
249
|
+
case "lost":
|
|
250
|
+
this.resumeAttempts.delete(handle.runId);
|
|
251
|
+
return { state: "lost", reason: decision.reason };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/** Workflow body. Public only so the module-level registration can reach it. */
|
|
255
|
+
async runInsideWorkflow(runId) {
|
|
256
|
+
const beat = () => this.db.run.update({ where: { id: runId }, data: { heartbeatAt: new Date() } }).catch(() => {
|
|
257
|
+
// A missed beat only makes the reconciler look sooner; recover() answers it.
|
|
258
|
+
});
|
|
259
|
+
await beat();
|
|
260
|
+
const timer = setInterval(() => void beat(), this.heartbeatIntervalMs);
|
|
261
|
+
try {
|
|
262
|
+
await executeRun(runId, this.providers, this.db, undefined, dbosStep);
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
clearInterval(timer);
|
|
266
|
+
cancellationReasons.delete(runId);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 2's default `Executor`: runs `executeRun` in-process, providing
|
|
3
|
+
* durability via a heartbeat. If this process dies mid-run the heartbeat
|
|
4
|
+
* stops, and the reconciler (core/reconciler.ts) detects the stale
|
|
5
|
+
* heartbeat and recovers the run to `lost` rather than leaving it stuck
|
|
6
|
+
* `running` forever.
|
|
7
|
+
*/
|
|
8
|
+
import type { ProviderRegistry } from "../index.js";
|
|
9
|
+
import { type RunnerDb } from "../../core/runner.js";
|
|
10
|
+
import type { Executor } from "./types.js";
|
|
11
|
+
export declare class InProcessExecutor implements Executor {
|
|
12
|
+
private readonly providers;
|
|
13
|
+
private readonly db;
|
|
14
|
+
private readonly heartbeatIntervalMs;
|
|
15
|
+
constructor(providers: Pick<ProviderRegistry, "llm" | "engine" | "datastore" | "secrets" | "memory">, db?: RunnerDb, heartbeatIntervalMs?: number);
|
|
16
|
+
start(runId: string): Promise<void>;
|
|
17
|
+
stop(_runId: string, _reason?: string): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 2's default `Executor`: runs `executeRun` in-process, providing
|
|
3
|
+
* durability via a heartbeat. If this process dies mid-run the heartbeat
|
|
4
|
+
* stops, and the reconciler (core/reconciler.ts) detects the stale
|
|
5
|
+
* heartbeat and recovers the run to `lost` rather than leaving it stuck
|
|
6
|
+
* `running` forever.
|
|
7
|
+
*/
|
|
8
|
+
import { executeRun } from "../../core/runner.js";
|
|
9
|
+
import { prisma as defaultDb } from "../../core/db.js";
|
|
10
|
+
import { HEARTBEAT_INTERVAL_MS } from "../../core/timing.js";
|
|
11
|
+
export class InProcessExecutor {
|
|
12
|
+
providers;
|
|
13
|
+
db;
|
|
14
|
+
heartbeatIntervalMs;
|
|
15
|
+
constructor(providers, db = defaultDb, heartbeatIntervalMs = HEARTBEAT_INTERVAL_MS) {
|
|
16
|
+
this.providers = providers;
|
|
17
|
+
this.db = db;
|
|
18
|
+
this.heartbeatIntervalMs = heartbeatIntervalMs;
|
|
19
|
+
}
|
|
20
|
+
async start(runId) {
|
|
21
|
+
const beat = () => this.db.run.update({ where: { id: runId }, data: { heartbeatAt: new Date() } }).catch(() => {
|
|
22
|
+
// A missed heartbeat write just makes the reconciler's stale-heartbeat
|
|
23
|
+
// check trigger a little sooner — never fatal to the run itself.
|
|
24
|
+
});
|
|
25
|
+
await beat();
|
|
26
|
+
const timer = setInterval(() => void beat(), this.heartbeatIntervalMs);
|
|
27
|
+
try {
|
|
28
|
+
await executeRun(runId, this.providers, this.db);
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
clearInterval(timer);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async stop(_runId, _reason) {
|
|
35
|
+
// Native engine cancellation remains cooperative until Engine accepts an
|
|
36
|
+
// AbortSignal. The task state still records the caller's request.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CodingImageSelector, Executor, ExecutionRecoveryResult, PersistedExecutionHandle } from "./types.js";
|
|
2
|
+
export interface ExecutionKindResolver {
|
|
3
|
+
kindForRun(runId: string): Promise<"native" | "coding" | null>;
|
|
4
|
+
}
|
|
5
|
+
/** Keeps native execution unchanged while routing coding runs to isolation. */
|
|
6
|
+
export declare class RoutingExecutor implements Executor {
|
|
7
|
+
private readonly resolver;
|
|
8
|
+
private readonly native;
|
|
9
|
+
private readonly coding;
|
|
10
|
+
constructor(resolver: ExecutionKindResolver, native: Executor, coding: Executor);
|
|
11
|
+
start(runId: string): Promise<void>;
|
|
12
|
+
stop(runId: string, reason?: string): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Recovery is routed by the run's agent kind, exactly like start/stop: a
|
|
15
|
+
* native run's handle belongs to the native executor (e.g. a DBOS
|
|
16
|
+
* workflow handle), a coding run's to the container executor. Routing by
|
|
17
|
+
* handle backend would need this class to know every backend name.
|
|
18
|
+
*/
|
|
19
|
+
recover(handle: PersistedExecutionHandle): Promise<ExecutionRecoveryResult>;
|
|
20
|
+
/** Lifecycle fans out to both executors; each is optional on the seam. */
|
|
21
|
+
launch(): Promise<void>;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
resolveCodingWorkerImage(selector: CodingImageSelector): string;
|
|
24
|
+
}
|
|
25
|
+
export declare class PrismaExecutionKindResolver implements ExecutionKindResolver {
|
|
26
|
+
private readonly db;
|
|
27
|
+
constructor(db: {
|
|
28
|
+
run: {
|
|
29
|
+
findUnique(input: {
|
|
30
|
+
where: {
|
|
31
|
+
id: string;
|
|
32
|
+
};
|
|
33
|
+
select: {
|
|
34
|
+
agent: {
|
|
35
|
+
select: {
|
|
36
|
+
kind: true;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
}): Promise<{
|
|
41
|
+
agent: {
|
|
42
|
+
kind: string;
|
|
43
|
+
};
|
|
44
|
+
} | null>;
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
kindForRun(runId: string): Promise<"native" | "coding" | null>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** Keeps native execution unchanged while routing coding runs to isolation. */
|
|
2
|
+
export class RoutingExecutor {
|
|
3
|
+
resolver;
|
|
4
|
+
native;
|
|
5
|
+
coding;
|
|
6
|
+
constructor(resolver, native, coding) {
|
|
7
|
+
this.resolver = resolver;
|
|
8
|
+
this.native = native;
|
|
9
|
+
this.coding = coding;
|
|
10
|
+
}
|
|
11
|
+
async start(runId) {
|
|
12
|
+
const kind = await this.resolver.kindForRun(runId);
|
|
13
|
+
if (kind === "native")
|
|
14
|
+
return this.native.start(runId);
|
|
15
|
+
if (kind === "coding")
|
|
16
|
+
return this.coding.start(runId);
|
|
17
|
+
throw new Error("executor_run_not_found");
|
|
18
|
+
}
|
|
19
|
+
async stop(runId, reason) {
|
|
20
|
+
const kind = await this.resolver.kindForRun(runId);
|
|
21
|
+
if (kind === "native")
|
|
22
|
+
return this.native.stop(runId, reason);
|
|
23
|
+
if (kind === "coding")
|
|
24
|
+
return this.coding.stop(runId, reason);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Recovery is routed by the run's agent kind, exactly like start/stop: a
|
|
28
|
+
* native run's handle belongs to the native executor (e.g. a DBOS
|
|
29
|
+
* workflow handle), a coding run's to the container executor. Routing by
|
|
30
|
+
* handle backend would need this class to know every backend name.
|
|
31
|
+
*/
|
|
32
|
+
async recover(handle) {
|
|
33
|
+
const kind = await this.resolver.kindForRun(handle.runId);
|
|
34
|
+
if (kind === "native") {
|
|
35
|
+
if (!this.native.recover)
|
|
36
|
+
return { state: "lost", reason: "native_recovery_unavailable" };
|
|
37
|
+
return this.native.recover(handle);
|
|
38
|
+
}
|
|
39
|
+
if (!this.coding.recover)
|
|
40
|
+
return { state: "lost", reason: "coding_recovery_unavailable" };
|
|
41
|
+
return this.coding.recover(handle);
|
|
42
|
+
}
|
|
43
|
+
/** Lifecycle fans out to both executors; each is optional on the seam. */
|
|
44
|
+
async launch() {
|
|
45
|
+
await this.native.launch?.();
|
|
46
|
+
await this.coding.launch?.();
|
|
47
|
+
}
|
|
48
|
+
async close() {
|
|
49
|
+
await this.coding.close?.();
|
|
50
|
+
await this.native.close?.();
|
|
51
|
+
}
|
|
52
|
+
resolveCodingWorkerImage(selector) {
|
|
53
|
+
if (!this.coding.resolveCodingWorkerImage)
|
|
54
|
+
throw new Error("coding_execution_not_configured");
|
|
55
|
+
return this.coding.resolveCodingWorkerImage(selector);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class PrismaExecutionKindResolver {
|
|
59
|
+
db;
|
|
60
|
+
constructor(db) {
|
|
61
|
+
this.db = db;
|
|
62
|
+
}
|
|
63
|
+
async kindForRun(runId) {
|
|
64
|
+
const run = await this.db.run.findUnique({
|
|
65
|
+
where: { id: runId },
|
|
66
|
+
select: { agent: { select: { kind: true } } },
|
|
67
|
+
});
|
|
68
|
+
return run?.agent.kind === "native" || run?.agent.kind === "coding" ? run.agent.kind : null;
|
|
69
|
+
}
|
|
70
|
+
}
|