@wardby/cli 0.5.2 → 0.6.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 +33 -0
- package/README.md +25 -14
- package/dist/claude-coding-worker/context.d.ts +22 -0
- package/dist/claude-coding-worker/context.js +92 -0
- package/dist/claude-coding-worker/driver.d.ts +7 -1
- package/dist/claude-coding-worker/driver.js +38 -5
- package/dist/claude-coding-worker/sdk.js +13 -4
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +5 -2
- package/dist/cli-native-mode.d.ts +9 -0
- package/dist/cli-native-mode.js +20 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +141 -5
- package/dist/coding/claude-context.d.ts +45 -0
- package/dist/coding/claude-context.js +434 -0
- package/dist/coding/local-git.js +9 -1
- package/dist/coding/profile.d.ts +12 -0
- package/dist/coding/profile.js +6 -0
- package/dist/coding/protocol.d.ts +104 -0
- package/dist/coding/protocol.js +87 -3
- package/dist/coding/registry/pypi.d.ts +25 -1
- package/dist/coding/registry/pypi.js +140 -16
- package/dist/coding/registry/types.d.ts +17 -3
- package/dist/coding-worker/artifact.d.ts +8 -0
- package/dist/coding-worker/artifact.js +2 -2
- package/dist/coding-worker/driver.js +2 -0
- package/dist/coding-worker/sdk.d.ts +6 -0
- package/dist/coding-worker/sdk.js +1 -0
- package/dist/coding-worker/skills.d.ts +23 -0
- package/dist/coding-worker/skills.js +117 -0
- package/dist/coding-worker/types.d.ts +2 -0
- package/dist/config/github-allowed-accounts.d.ts +15 -0
- package/dist/config/github-allowed-accounts.js +36 -0
- package/dist/config/low-power.d.ts +12 -0
- package/dist/config/low-power.js +18 -0
- package/dist/config/mobile.d.ts +12 -0
- package/dist/config/mobile.js +18 -0
- package/dist/config/providers.d.ts +56 -2
- package/dist/config/providers.js +92 -6
- package/dist/core/db.d.ts +16 -6
- package/dist/core/db.js +12 -10
- package/dist/core/dispatch.d.ts +11 -0
- package/dist/core/dispatch.js +36 -1
- package/dist/core/engine-native.js +9 -5
- package/dist/core/host-events.d.ts +49 -3
- package/dist/core/host-events.js +289 -15
- package/dist/core/http-runtime.js +13 -6
- package/dist/core/issue-bridge.js +42 -2
- package/dist/core/issue-events.js +12 -1
- package/dist/core/issue-status.js +14 -3
- package/dist/core/merge-order-check.d.ts +129 -0
- package/dist/core/merge-order-check.js +336 -0
- package/dist/core/notification-dispatcher.d.ts +70 -0
- package/dist/core/notification-dispatcher.js +368 -0
- package/dist/core/notification-templates.d.ts +25 -0
- package/dist/core/notification-templates.js +105 -0
- package/dist/core/notifications.d.ts +10 -0
- package/dist/core/notifications.js +38 -0
- package/dist/core/pg-auth.d.ts +22 -0
- package/dist/core/pg-auth.js +152 -0
- package/dist/core/power-composition.d.ts +16 -0
- package/dist/core/power-composition.js +37 -0
- package/dist/core/power-idle.d.ts +14 -0
- package/dist/core/power-idle.js +30 -0
- package/dist/core/power.d.ts +71 -0
- package/dist/core/power.js +202 -0
- package/dist/core/reconciler.d.ts +4 -2
- package/dist/core/reconciler.js +29 -5
- package/dist/core/related-pull-requests.d.ts +48 -4
- package/dist/core/related-pull-requests.js +113 -11
- package/dist/core/repo-access.d.ts +15 -0
- package/dist/core/repo-access.js +66 -0
- package/dist/core/review-fix.d.ts +37 -0
- package/dist/core/review-fix.js +207 -6
- package/dist/core/review-host-tools.d.ts +20 -0
- package/dist/core/review-host-tools.js +58 -2
- package/dist/core/runner.d.ts +336 -3
- package/dist/core/runner.js +894 -317
- package/dist/core/secret-broker-config.d.ts +103 -0
- package/dist/core/secret-broker-config.js +112 -0
- package/dist/core/secrets.d.ts +39 -4
- package/dist/core/secrets.js +148 -40
- package/dist/core/workflow-event-recorder.d.ts +14 -0
- package/dist/core/workflow-event-recorder.js +120 -0
- package/dist/core/workflow-events.d.ts +82 -0
- package/dist/core/workflow-events.js +44 -0
- package/dist/core/workflow-run-events.d.ts +16 -0
- package/dist/core/workflow-run-events.js +40 -0
- package/dist/generated/prisma/browser.d.ts +58 -0
- package/dist/generated/prisma/client.d.ts +58 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +150 -0
- package/dist/generated/prisma/enums.d.ts +27 -0
- package/dist/generated/prisma/enums.js +23 -0
- package/dist/generated/prisma/internal/class.d.ts +88 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +772 -1
- package/dist/generated/prisma/internal/prismaNamespace.js +135 -4
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +139 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +135 -4
- package/dist/generated/prisma/models/Agent.d.ts +360 -1
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +73 -1
- package/dist/generated/prisma/models/CodingRun.d.ts +205 -1
- package/dist/generated/prisma/models/DeferredReview.d.ts +78 -1
- package/dist/generated/prisma/models/NativeGatewayCall.d.ts +1675 -0
- package/dist/generated/prisma/models/NativeGatewayCall.js +1 -0
- package/dist/generated/prisma/models/NativeGatewaySession.d.ts +1517 -0
- package/dist/generated/prisma/models/NativeGatewaySession.js +1 -0
- package/dist/generated/prisma/models/NativeWarmWorker.d.ts +1296 -0
- package/dist/generated/prisma/models/NativeWarmWorker.js +1 -0
- package/dist/generated/prisma/models/NotificationChannel.d.ts +1543 -0
- package/dist/generated/prisma/models/NotificationChannel.js +1 -0
- package/dist/generated/prisma/models/NotificationDelivery.d.ts +1502 -0
- package/dist/generated/prisma/models/NotificationDelivery.js +1 -0
- package/dist/generated/prisma/models/NotificationThread.d.ts +1090 -0
- package/dist/generated/prisma/models/NotificationThread.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +826 -1
- package/dist/generated/prisma/models/RunHostCheck.d.ts +36 -1
- package/dist/generated/prisma/models/Secret.d.ts +37 -1
- package/dist/generated/prisma/models/SecretBrokerChange.d.ts +1101 -0
- package/dist/generated/prisma/models/SecretBrokerChange.js +1 -0
- package/dist/generated/prisma/models/WorkflowEvent.d.ts +1422 -0
- package/dist/generated/prisma/models/WorkflowEvent.js +1 -0
- package/dist/generated/prisma/models.d.ts +8 -0
- package/dist/help/build.js +4 -2
- package/dist/help/catalog.d.ts +1 -1
- package/dist/help/catalog.js +31 -6
- package/dist/help-index.json +1492 -113
- package/dist/import/create.js +17 -0
- package/dist/import/neutral-schema.d.ts +6 -2
- package/dist/import/neutral-schema.js +2 -0
- package/dist/mcp/auth/access.d.ts +1 -0
- package/dist/mcp/auth/ownership.d.ts +1 -1
- package/dist/mcp/auth/repo-authorization.js +14 -0
- package/dist/mcp/auth/resource-server.d.ts +2 -1
- package/dist/mcp/auth/resource-server.js +7 -2
- package/dist/mcp/auth/self-hosted/browser.d.ts +1 -1
- package/dist/mcp/auth/self-hosted/browser.js +19 -6
- package/dist/mcp/context.d.ts +6 -0
- package/dist/mcp/host-events/github-ingress.d.ts +23 -1
- package/dist/mcp/host-events/github-ingress.js +68 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/index.js +59 -10
- package/dist/mcp/tools/agents.js +83 -12
- package/dist/mcp/tools/notification-channels.d.ts +2 -0
- package/dist/mcp/tools/notification-channels.js +293 -0
- package/dist/mcp/tools/power.d.ts +2 -0
- package/dist/mcp/tools/power.js +77 -0
- package/dist/mcp/tools/runs.d.ts +0 -6
- package/dist/mcp/tools/runs.js +7 -2
- package/dist/mcp/tools/secret-broker-compat.d.ts +12 -0
- package/dist/mcp/tools/secret-broker-compat.js +23 -0
- package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -2
- package/dist/mcp/tools/secret-elicitation-form.js +200 -10
- package/dist/mcp/tools/secret-elicitation-server.js +8 -1
- package/dist/mcp/tools/secret-elicitation.d.ts +38 -3
- package/dist/mcp/tools/secret-elicitation.js +110 -12
- package/dist/mcp/tools/secrets.js +184 -7
- package/dist/mcp/tools/tools.d.ts +1 -1
- package/dist/mcp/tools/tools.js +22 -2
- package/dist/native-worker/composition.d.ts +23 -0
- package/dist/native-worker/composition.js +77 -0
- package/dist/native-worker/docker-isolation.d.ts +65 -0
- package/dist/native-worker/docker-isolation.js +183 -0
- package/dist/native-worker/docker-launcher.d.ts +74 -0
- package/dist/native-worker/docker-launcher.js +237 -0
- package/dist/native-worker/gateway-server.d.ts +39 -0
- package/dist/native-worker/gateway-server.js +315 -0
- package/dist/native-worker/gateway.d.ts +44 -0
- package/dist/native-worker/gateway.js +197 -0
- package/dist/native-worker/http-server.d.ts +22 -0
- package/dist/native-worker/http-server.js +109 -0
- package/dist/native-worker/http-transport.d.ts +17 -0
- package/dist/native-worker/http-transport.js +88 -0
- package/dist/native-worker/kubernetes-isolation.d.ts +90 -0
- package/dist/native-worker/kubernetes-isolation.js +218 -0
- package/dist/native-worker/kubernetes-launcher.d.ts +102 -0
- package/dist/native-worker/kubernetes-launcher.js +335 -0
- package/dist/native-worker/launch.d.ts +45 -0
- package/dist/native-worker/launch.js +44 -0
- package/dist/native-worker/ledger.d.ts +109 -0
- package/dist/native-worker/ledger.js +192 -0
- package/dist/native-worker/loopback.d.ts +12 -0
- package/dist/native-worker/loopback.js +49 -0
- package/dist/native-worker/main.d.ts +7 -0
- package/dist/native-worker/main.js +74 -0
- package/dist/native-worker/protocol.d.ts +744 -0
- package/dist/native-worker/protocol.js +200 -0
- package/dist/native-worker/sandbox-executor.d.ts +68 -0
- package/dist/native-worker/sandbox-executor.js +210 -0
- package/dist/native-worker/stdio.d.ts +29 -0
- package/dist/native-worker/stdio.js +188 -0
- package/dist/native-worker/warm-delivery.d.ts +16 -0
- package/dist/native-worker/warm-delivery.js +33 -0
- package/dist/native-worker/warm-pool-ledger.d.ts +39 -0
- package/dist/native-worker/warm-pool-ledger.js +73 -0
- package/dist/native-worker/warm-pool.d.ts +107 -0
- package/dist/native-worker/warm-pool.js +283 -0
- package/dist/native-worker/worker.d.ts +17 -0
- package/dist/native-worker/worker.js +115 -0
- package/dist/providers/auth/authorization-server.d.ts +3 -0
- package/dist/providers/auth/index.js +2 -0
- package/dist/providers/auth/self-hosted.d.ts +13 -1
- package/dist/providers/auth/self-hosted.js +54 -4
- package/dist/providers/chat/fake.d.ts +46 -0
- package/dist/providers/chat/fake.js +39 -0
- package/dist/providers/chat/index.d.ts +4 -0
- package/dist/providers/chat/index.js +8 -0
- package/dist/providers/chat/slack.d.ts +37 -0
- package/dist/providers/chat/slack.js +105 -0
- package/dist/providers/chat/types.d.ts +42 -0
- package/dist/providers/chat/types.js +19 -0
- package/dist/providers/coding-proxy/mock-upstream.d.ts +33 -0
- package/dist/providers/coding-proxy/mock-upstream.js +116 -0
- package/dist/providers/coding-proxy/registry/prisma-store.d.ts +3 -0
- package/dist/providers/coding-proxy/registry/prisma-store.js +17 -0
- package/dist/providers/coding-proxy/registry/service.d.ts +4 -0
- package/dist/providers/coding-proxy/registry/service.js +20 -3
- package/dist/providers/coding-proxy/registry/store.d.ts +18 -1
- package/dist/providers/coding-proxy/registry/store.js +33 -0
- package/dist/providers/coding-proxy/runtime.js +12 -1
- package/dist/providers/coding-proxy/types.d.ts +2 -0
- package/dist/providers/engine/types.d.ts +19 -0
- package/dist/providers/engine/types.js +22 -0
- package/dist/providers/executor/composition.d.ts +24 -1
- package/dist/providers/executor/composition.js +39 -6
- package/dist/providers/executor/container.d.ts +24 -1
- package/dist/providers/executor/container.js +101 -11
- package/dist/providers/executor/dbos.d.ts +42 -0
- package/dist/providers/executor/dbos.js +118 -12
- package/dist/providers/executor/deferred.d.ts +56 -0
- package/dist/providers/executor/deferred.js +119 -0
- package/dist/providers/executor/routing.d.ts +37 -10
- package/dist/providers/executor/routing.js +73 -32
- package/dist/providers/executor/types.d.ts +12 -0
- package/dist/providers/jobs/docker.js +3 -3
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +21 -2
- package/dist/providers/jobs/fake-kubernetes-api.js +42 -1
- package/dist/providers/jobs/kubernetes-api.d.ts +17 -1
- package/dist/providers/jobs/kubernetes-api.js +7 -0
- package/dist/providers/jobs/kubernetes-client.d.ts +10 -1
- package/dist/providers/jobs/kubernetes-client.js +52 -3
- package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +6 -1
- package/dist/providers/jobs/kubernetes-dry-run-fixture.js +6 -3
- package/dist/providers/jobs/kubernetes-isolation.d.ts +16 -1
- package/dist/providers/jobs/kubernetes-isolation.js +58 -5
- package/dist/providers/jobs/kubernetes-platform.d.ts +4 -1
- package/dist/providers/jobs/kubernetes-platform.js +38 -1
- package/dist/providers/jobs/kubernetes-preflight.d.ts +2 -1
- package/dist/providers/jobs/kubernetes-preflight.js +4 -2
- package/dist/providers/jobs/kubernetes.d.ts +56 -7
- package/dist/providers/jobs/kubernetes.js +117 -36
- package/dist/providers/jobs/metadata-probes.d.ts +11 -0
- package/dist/providers/jobs/metadata-probes.js +13 -0
- package/dist/providers/jobs/types.d.ts +9 -0
- package/dist/providers/llm/openai.js +0 -1
- package/dist/providers/review-host/github.d.ts +11 -1
- package/dist/providers/review-host/github.js +50 -0
- package/dist/providers/review-host/types.d.ts +23 -0
- package/dist/providers/vcs/github.d.ts +32 -6
- package/dist/providers/vcs/github.js +74 -16
- package/dist/quickstart/coding-db.js +14 -2
- package/dist/quickstart/coding-doctor.d.ts +3 -0
- package/dist/quickstart/coding-doctor.js +3 -0
- package/dist/quickstart/coding-images.d.ts +9 -0
- package/dist/quickstart/coding-images.js +39 -0
- package/dist/quickstart/coding-seed.d.ts +20 -1
- package/dist/quickstart/coding-seed.js +96 -7
- package/dist/quickstart/coding.d.ts +2 -0
- package/dist/quickstart/coding.js +75 -3
- package/dist/quickstart/config.d.ts +2 -0
- package/dist/quickstart/config.js +4 -0
- package/dist/quickstart/images.d.ts +13 -0
- package/dist/quickstart/images.js +80 -12
- package/dist/quickstart/index.d.ts +0 -2
- package/dist/quickstart/index.js +10 -14
- package/dist/quickstart/manifests.d.ts +56 -0
- package/dist/quickstart/manifests.js +523 -0
- package/dist/quickstart/python-detect.d.ts +10 -0
- package/dist/quickstart/python-detect.js +32 -0
- package/dist/quickstart/repo-packages.d.ts +10 -0
- package/dist/quickstart/repo-packages.js +132 -0
- package/dist/quickstart/reviewer-prompt.d.ts +12 -0
- package/dist/quickstart/reviewer-prompt.js +84 -0
- package/dist/quickstart/scenarios.d.ts +19 -0
- package/dist/quickstart/scenarios.js +28 -0
- package/dist/quickstart/sync-reviewer-prompt.d.ts +1 -0
- package/dist/quickstart/sync-reviewer-prompt.js +10 -0
- package/dist/quickstart-images.json +1 -1
- package/dist/sandbox/brokered-fetch.d.ts +23 -0
- package/dist/sandbox/brokered-fetch.js +37 -0
- package/dist/sandbox/host-functions.d.ts +34 -0
- package/dist/sandbox/host-functions.js +144 -80
- package/dist/sandbox/prelude.js +7 -1
- package/dist/sandbox/run-in-sandbox.d.ts +19 -0
- package/dist/sandbox/run-in-sandbox.js +28 -10
- package/dist/sandbox/safe-fetch.d.ts +2 -0
- package/dist/sandbox/safe-fetch.js +3 -1
- package/dist/sandbox/secret-broker-sigv4.d.ts +2 -0
- package/dist/sandbox/secret-broker-sigv4.js +49 -0
- package/dist/sandbox/secret-broker.d.ts +34 -0
- package/dist/sandbox/secret-broker.js +162 -0
- package/dist/sandbox/user-tool.d.ts +14 -0
- package/dist/sandbox/user-tool.js +32 -0
- package/dist/serve.js +18 -0
- package/dist/viewer/api-schema.d.ts +214 -18
- package/dist/viewer/api-schema.js +22 -0
- package/dist/viewer/build-schemas.js +4 -1
- package/dist/viewer/event-bus.d.ts +6 -0
- package/dist/viewer/event-bus.js +16 -7
- package/dist/viewer/graph.d.ts +5 -2
- package/dist/viewer/graph.js +7 -2
- package/dist/viewer/http.d.ts +0 -1
- package/dist/viewer/http.js +2 -2
- package/dist/viewer/infra.js +25 -2
- package/dist/viewer/limits.d.ts +4 -0
- package/dist/viewer/limits.js +4 -0
- package/dist/viewer/openapi.d.ts +15 -0
- package/dist/viewer/openapi.js +172 -0
- package/docs/README.md +7 -0
- package/docs/agent-recipes.md +57 -8
- package/docs/architecture-runtime.md +16 -1
- package/docs/code-review-agents.md +139 -5
- package/docs/coding-agent-setup.md +127 -2
- package/docs/coding-packages.md +105 -5
- package/docs/coding-services.md +3 -1
- package/docs/coding-worker-byo-images.md +239 -2
- package/docs/coding-worker-isolation.md +88 -23
- package/docs/getting-started-gke.md +225 -9
- package/docs/getting-started-identity-provider.md +21 -10
- package/docs/getting-started.md +140 -11
- package/docs/jira-agents.md +5 -1
- package/docs/knowledge.md +11 -0
- package/docs/native-sandbox.md +635 -0
- package/docs/release-verification.md +9 -0
- package/docs/security-deployment.md +113 -12
- package/docs/slack-notifications.md +252 -0
- package/docs/tool-secrets.md +252 -0
- package/docs/viewer-api.md +46 -5
- package/docs/viewer-api.openapi.yml +1404 -0
- package/help/admin-viewer.md +6 -2
- package/help/agent-recipes.md +27 -0
- package/help/architecture-agent.md +136 -0
- package/help/brokered-secrets.md +79 -0
- package/help/build-claude-code-image.md +131 -0
- package/help/build-worker-image.md +195 -0
- package/help/code-review-agents.md +68 -6
- package/help/coding-packages.md +31 -2
- package/help/creating-agents.md +14 -0
- package/help/deploy-gke.md +37 -6
- package/help/deployment-targets.md +24 -13
- package/help/errors/coding-provider-not-configured.md +2 -2
- package/help/errors/database-auth-invalid.md +28 -0
- package/help/errors/github-account-not-allowed.md +25 -0
- package/help/errors/low-power-disabled.md +29 -0
- package/help/errors/low-power-scale-forbidden.md +35 -0
- package/help/errors/low-power-wake-timeout.md +30 -0
- package/help/errors/mobile-disabled.md +31 -0
- package/help/errors/native-sandbox-capacity.md +22 -0
- package/help/errors/native-sandbox-deadline-exceeded.md +19 -0
- package/help/errors/native-sandbox-docker-failed.md +20 -0
- package/help/errors/native-sandbox-gateway-unavailable.md +21 -0
- package/help/errors/native-sandbox-image-not-pinned.md +19 -0
- package/help/errors/native-sandbox-isolation-mismatch.md +20 -0
- package/help/errors/native-sandbox-network-unenforced.md +22 -0
- package/help/errors/native-sandbox-requires-catalog.md +31 -0
- package/help/errors/native-sandbox-unavailable.md +19 -0
- package/help/errors/native-sandbox-warm-delivery-failed.md +21 -0
- package/help/errors/native-sandbox-worker-exited.md +20 -0
- package/help/errors/native-sandbox-worker-lost.md +20 -0
- package/help/errors/native-sandbox-worker-unready.md +22 -0
- package/help/errors/secret-broker-body-invalid.md +26 -0
- package/help/errors/secret-broker-changed.md +20 -0
- package/help/errors/secret-broker-config-invalid.md +30 -0
- package/help/errors/secret-broker-conflict.md +27 -0
- package/help/errors/secret-broker-destination-denied.md +28 -0
- package/help/errors/secret-broker-value-invalid.md +28 -0
- package/help/errors/secret-brokered.md +24 -0
- package/help/errors/secret-not-brokered.md +25 -0
- package/help/errors/slack-auth-failed.md +48 -0
- package/help/errors/slack-channel-unreachable.md +39 -0
- package/help/errors/slack-not-configured.md +33 -0
- package/help/getting-started.md +18 -0
- package/help/github.md +18 -1
- package/help/identity-and-access.md +5 -1
- package/help/local-repositories.md +35 -5
- package/help/low-power-mode.md +125 -0
- package/help/merge-order-check.md +117 -0
- package/help/mobile-app.md +54 -0
- package/help/native-sandbox.md +136 -0
- package/help/observability.md +8 -0
- package/help/related-pull-requests.md +70 -9
- package/help/repo-instructions-and-skills.md +91 -0
- package/help/review-fix-rounds.md +10 -0
- package/help/slack-notifications.md +62 -0
- package/help/troubleshooting/coding-workers.md +21 -0
- package/package.json +13 -5
- package/prisma/migrations/20261009000003_native_execution_mode/migration.sql +12 -0
- package/prisma/migrations/20261009000004_native_gateway_ledger/migration.sql +65 -0
- package/prisma/migrations/20261009000005_native_gateway_call_child_agent/migration.sql +5 -0
- package/prisma/migrations/20261009000006_run_deferred_start_stop/migration.sql +14 -0
- package/prisma/migrations/20261009000007_native_gateway_network_ready/migration.sql +5 -0
- package/prisma/migrations/20261010000001_workflow_notifications/migration.sql +99 -0
- package/prisma/migrations/20261011000001_native_gateway_session_status_index/migration.sql +2 -0
- package/prisma/migrations/20261012000001_native_warm_worker/migration.sql +32 -0
- package/prisma/migrations/20261013000001_coding_run_merge_order/migration.sql +2 -0
- package/prisma/migrations/20261013000001_secret_broker/migration.sql +17 -0
- package/prisma/migrations/20261013000002_deferred_review_request/migration.sql +4 -0
- package/prisma/migrations/20261014000000_repo_skills/migration.sql +15 -0
- package/prisma/migrations/20261015000001_coding_run_merge_order_index/migration.sql +2 -0
- package/prisma/migrations/20261015000002_no_change_rereview/migration.sql +2 -0
- package/prisma/schema.prisma +362 -91
|
@@ -11,16 +11,67 @@
|
|
|
11
11
|
* The heartbeat is kept while the workflow runs here so the reconciler's
|
|
12
12
|
* stale-heartbeat detection is unchanged; the reconciler consults
|
|
13
13
|
* `recover()` (Task 6) before declaring a DBOS run lost.
|
|
14
|
+
*
|
|
15
|
+
* A stalled-but-alive owner can overlap an adopted attempt of the same run:
|
|
16
|
+
* whichever records a step second loses ownership, stops at that step
|
|
17
|
+
* without writing the run, and hands the SDK's conflict back so DBOS parks
|
|
18
|
+
* it as a duplicate execution (see dbosStep and runInsideWorkflow).
|
|
14
19
|
*/
|
|
15
20
|
import { DBOS, Error as DbosErrors } from "@dbos-inc/dbos-sdk";
|
|
21
|
+
import pg from "pg";
|
|
22
|
+
import { RunOwnershipLostError } from "../engine/types.js";
|
|
16
23
|
import { executeRun, RunCancelledError } from "../../core/runner.js";
|
|
17
24
|
import { markRunFailedFromExecutorError } from "../../core/dispatch.js";
|
|
18
25
|
import { prisma as defaultDb } from "../../core/db.js";
|
|
19
26
|
import { HEARTBEAT_INTERVAL_MS } from "../../core/timing.js";
|
|
20
27
|
import { logger } from "../../core/logger.js";
|
|
28
|
+
import { databaseAuthFromEnv, pgConnectionConfig } from "../../core/pg-auth.js";
|
|
21
29
|
import { ADOPTION_EXHAUSTED_REASON, decideRecovery, shouldGiveUpAdoption, versionMismatchReason, } from "./dbos-status.js";
|
|
22
30
|
export const DBOS_BACKEND = "dbos";
|
|
23
31
|
const dbosLog = logger.child({ module: "dbos-executor" });
|
|
32
|
+
/** DBOS's own system pool size (DEFAULT_POOL_SIZE in the SDK's system_database.js); kept for ours. */
|
|
33
|
+
const DBOS_SYSTEM_POOL_SIZE = 10;
|
|
34
|
+
/** DBOS's own connect timeout when the URL sets no connect_timeout (getClientConfig in the SDK's utils.js). */
|
|
35
|
+
const DBOS_CONNECT_TIMEOUT_MS = 10_000;
|
|
36
|
+
/**
|
|
37
|
+
* DBOS.setConfig input. Under password auth this is exactly what launch() always passed, and DBOS
|
|
38
|
+
* opens its own pool from the URL. Under rds-iam DBOS gets a pool whose password is the IAM token;
|
|
39
|
+
* DBOS then runs its migrations, queries and LISTEN through that pool and never opens a URL
|
|
40
|
+
* connection (system_database.js: a custom pool skips database creation and is what every
|
|
41
|
+
* connection borrows from). The pool copies what DBOS would have set on its own: size, connect
|
|
42
|
+
* timeout and application_name. DBOS never ends a pool it was given — see endSystemPool().
|
|
43
|
+
*/
|
|
44
|
+
export function dbosLaunchConfig(config, executorId, auth, pgAuth) {
|
|
45
|
+
const url = config.systemDatabaseUrl;
|
|
46
|
+
const launch = {
|
|
47
|
+
name: "wardby",
|
|
48
|
+
systemDatabaseUrl: url,
|
|
49
|
+
systemDatabaseSchemaName: config.schemaName,
|
|
50
|
+
executorID: executorId,
|
|
51
|
+
runAdminServer: false,
|
|
52
|
+
logLevel: "warn",
|
|
53
|
+
};
|
|
54
|
+
if (auth !== "rds-iam")
|
|
55
|
+
return launch;
|
|
56
|
+
const connection = pgConnectionConfig(url, auth, pgAuth); // validates the URL first
|
|
57
|
+
const connectTimeout = Number.parseInt(new URL(url).searchParams.get("connect_timeout") ?? "", 10);
|
|
58
|
+
const pool = new pg.Pool({
|
|
59
|
+
application_name: `dbos_transact_${executorId}`,
|
|
60
|
+
...connection,
|
|
61
|
+
max: DBOS_SYSTEM_POOL_SIZE,
|
|
62
|
+
connectionTimeoutMillis: connectTimeout ? connectTimeout * 1000 : DBOS_CONNECT_TIMEOUT_MS,
|
|
63
|
+
});
|
|
64
|
+
// DBOS attaches no listener to a caller's pool; an idle connection's error would otherwise crash the process.
|
|
65
|
+
pool.on("error", (err) => dbosLog.warn({ err }, "DBOS system database pool error"));
|
|
66
|
+
return { ...launch, systemDatabasePool: pool };
|
|
67
|
+
}
|
|
68
|
+
/** The pool launch() handed DBOS, if any. Module-level like DBOS itself: whoever shuts DBOS down ends it. */
|
|
69
|
+
let systemPool;
|
|
70
|
+
async function endSystemPool() {
|
|
71
|
+
const pool = systemPool;
|
|
72
|
+
systemPool = undefined;
|
|
73
|
+
await pool?.end().catch((err) => dbosLog.warn({ err }, "failed to close the DBOS system database pool"));
|
|
74
|
+
}
|
|
24
75
|
/** The executor whose deps the registered workflow uses. Set by launch(). */
|
|
25
76
|
let activeExecutor;
|
|
26
77
|
/** Setter (rather than `activeExecutor = this` inline) to avoid aliasing `this` to a variable. */
|
|
@@ -44,19 +95,48 @@ const cancellationReasons = new Map();
|
|
|
44
95
|
* out of the next step boundary. Translated here into a `RunCancelledError`
|
|
45
96
|
* so executeRun's backstop records `cancelled` plus the operator's reason,
|
|
46
97
|
* rather than `failed` plus an SDK-internal message.
|
|
98
|
+
*
|
|
99
|
+
* DBOS signals that another execution of this workflow recorded the step
|
|
100
|
+
* first by throwing `DBOSWorkflowConflictError` after this attempt's step
|
|
101
|
+
* body already ran. Translated into a `RunOwnershipLostError` (carrying the
|
|
102
|
+
* discarded step's usage, if any, for the log) so executeRun's backstop stops
|
|
103
|
+
* this attempt without writing the run; runInsideWorkflow turns it back into
|
|
104
|
+
* the SDK error.
|
|
47
105
|
*/
|
|
48
|
-
const dbosStep = async (name, fn) => {
|
|
106
|
+
export const dbosStep = async (name, fn) => {
|
|
107
|
+
let ran;
|
|
49
108
|
try {
|
|
50
|
-
return await DBOS.runStep(
|
|
109
|
+
return await DBOS.runStep(async () => {
|
|
110
|
+
const value = await fn();
|
|
111
|
+
ran = { value };
|
|
112
|
+
return value;
|
|
113
|
+
}, { name, retriesAllowed: false });
|
|
51
114
|
}
|
|
52
115
|
catch (err) {
|
|
53
116
|
if (err instanceof DbosErrors.DBOSWorkflowCancelledError) {
|
|
54
117
|
const reason = cancellationReasons.get(err.workflowID);
|
|
55
118
|
throw new RunCancelledError(`Run cancelled: ${reason ?? "no reason given"}`);
|
|
56
119
|
}
|
|
120
|
+
if (err instanceof DbosErrors.DBOSWorkflowConflictError) {
|
|
121
|
+
const discardedUsage = usageOf(ran?.value);
|
|
122
|
+
throw new RunOwnershipLostError(name, { cause: err, ...(discardedUsage ? { discardedUsage } : {}) });
|
|
123
|
+
}
|
|
57
124
|
throw err;
|
|
58
125
|
}
|
|
59
126
|
};
|
|
127
|
+
/**
|
|
128
|
+
* The numeric `usage` fields of a step result, if it has any (an LLM turn's
|
|
129
|
+
* token counts and cost). Tool steps return a string and yield nothing.
|
|
130
|
+
*/
|
|
131
|
+
function usageOf(value) {
|
|
132
|
+
if (typeof value !== "object" || value === null || !("usage" in value))
|
|
133
|
+
return undefined;
|
|
134
|
+
const usage = value.usage;
|
|
135
|
+
if (typeof usage !== "object" || usage === null)
|
|
136
|
+
return undefined;
|
|
137
|
+
const numeric = Object.entries(usage).filter((entry) => typeof entry[1] === "number");
|
|
138
|
+
return numeric.length > 0 ? Object.fromEntries(numeric) : undefined;
|
|
139
|
+
}
|
|
60
140
|
/**
|
|
61
141
|
* Registered once at module load — DBOS requires registration before
|
|
62
142
|
* launch(). The body must be deterministic between steps; everything
|
|
@@ -112,15 +192,24 @@ export class DbosExecutor {
|
|
|
112
192
|
}
|
|
113
193
|
setActiveExecutor(this);
|
|
114
194
|
if (!DBOS.isInitialized()) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
195
|
+
const launchConfig = dbosLaunchConfig(this.config, this.id, databaseAuthFromEnv());
|
|
196
|
+
systemPool = launchConfig.systemDatabasePool;
|
|
197
|
+
try {
|
|
198
|
+
DBOS.setConfig(launchConfig);
|
|
199
|
+
await DBOS.launch();
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
// DBOS.launch can throw after it initialized (registering the application version, starting
|
|
203
|
+
// event receivers). Shut that half-launched DBOS down so the next launch() starts clean, then
|
|
204
|
+
// end the pool either way: the next launch() makes a new one.
|
|
205
|
+
if (activeExecutor === this)
|
|
206
|
+
setActiveExecutor(undefined);
|
|
207
|
+
if (DBOS.isInitialized()) {
|
|
208
|
+
await DBOS.shutdown().catch((shutdownErr) => dbosLog.warn({ err: shutdownErr }, "failed to shut down DBOS after a failed launch"));
|
|
209
|
+
}
|
|
210
|
+
await endSystemPool();
|
|
211
|
+
throw err;
|
|
212
|
+
}
|
|
124
213
|
}
|
|
125
214
|
this.launched = true;
|
|
126
215
|
dbosLog.info({ executorId: DBOS.executorID }, "DBOS executor launched");
|
|
@@ -131,7 +220,12 @@ export class DbosExecutor {
|
|
|
131
220
|
this.launched = false;
|
|
132
221
|
if (activeExecutor === this)
|
|
133
222
|
setActiveExecutor(undefined);
|
|
134
|
-
|
|
223
|
+
try {
|
|
224
|
+
await DBOS.shutdown({ workflowCompletionTimeoutMS: 5_000 });
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
await endSystemPool();
|
|
228
|
+
}
|
|
135
229
|
}
|
|
136
230
|
/** Where a run this executor marks failed files its self-defect (no-op without a configured issue tracker). */
|
|
137
231
|
get selfDefects() {
|
|
@@ -263,6 +357,18 @@ export class DbosExecutor {
|
|
|
263
357
|
try {
|
|
264
358
|
await executeRun(runId, this.providers, this.db, undefined, dbosStep);
|
|
265
359
|
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
// An ownership loss leaves the workflow body as the SDK's own conflict error, so DBOS takes
|
|
362
|
+
// its duplicate-execution path: it records nothing and parks this attempt until the winner
|
|
363
|
+
// records the outcome. Any other error escaping here would be recorded as the workflow's
|
|
364
|
+
// outcome while the winner still runs. The runner already logged the loss.
|
|
365
|
+
if (err instanceof RunOwnershipLostError) {
|
|
366
|
+
throw err.cause instanceof DbosErrors.DBOSWorkflowConflictError
|
|
367
|
+
? err.cause
|
|
368
|
+
: new DbosErrors.DBOSWorkflowConflictError(runId);
|
|
369
|
+
}
|
|
370
|
+
throw err;
|
|
371
|
+
}
|
|
266
372
|
finally {
|
|
267
373
|
clearInterval(timer);
|
|
268
374
|
cancellationReasons.delete(runId);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Executor for a process that must never start or stop runs itself: the
|
|
3
|
+
* native sandbox gateway (docs/native-sandbox.md). Sandbox workers can reach
|
|
4
|
+
* the gateway, so it holds no Docker socket, cluster credential, or in-process
|
|
5
|
+
* engine; a child it dispatches is only recorded (Run.startDeferredAt) and a
|
|
6
|
+
* stop it needs only requested (Run.stopRequestedAt). The scheduler leader
|
|
7
|
+
* carries both out through the server's own executor (`drainDeferredRuns`).
|
|
8
|
+
*
|
|
9
|
+
* Coding dispatch still needs the worker image and the repository's service
|
|
10
|
+
* declaration and knowledge files, resolved at dispatch: those come from a
|
|
11
|
+
* resolve-only view of the configured coding executor, whose start/stop are
|
|
12
|
+
* never called here.
|
|
13
|
+
*/
|
|
14
|
+
import type { PrismaClient } from "#prisma";
|
|
15
|
+
import type { CodingImageSelector, Executor } from "./types.js";
|
|
16
|
+
import type { CodingProvider } from "../../coding/provider.js";
|
|
17
|
+
export type DeferredExecutorDb = Pick<PrismaClient, "run">;
|
|
18
|
+
export declare class DeferredExecutor implements Executor {
|
|
19
|
+
private readonly db;
|
|
20
|
+
/** Coding resolution only (image, service declaration, repository files); never started or stopped. */
|
|
21
|
+
private readonly resolver?;
|
|
22
|
+
constructor(db: DeferredExecutorDb,
|
|
23
|
+
/** Coding resolution only (image, service declaration, repository files); never started or stopped. */
|
|
24
|
+
resolver?: Executor | undefined);
|
|
25
|
+
start(runId: string): Promise<void>;
|
|
26
|
+
stop(runId: string, reason?: string): Promise<void>;
|
|
27
|
+
resolveCodingWorkerImage(selector: CodingImageSelector): string;
|
|
28
|
+
resolveCodingToolImage(selector: CodingImageSelector): string | null;
|
|
29
|
+
readCodingServiceDeclaration(input: {
|
|
30
|
+
repository: string;
|
|
31
|
+
baseRef: string;
|
|
32
|
+
}): Promise<string | null>;
|
|
33
|
+
readCodingRepositoryFile(input: {
|
|
34
|
+
repository: string;
|
|
35
|
+
baseRef: string;
|
|
36
|
+
path: string;
|
|
37
|
+
maxBytes: number;
|
|
38
|
+
}): Promise<string | null>;
|
|
39
|
+
supportsCodingServices(provider: CodingProvider): boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface DrainDeferredRunsOptions {
|
|
42
|
+
db: Pick<PrismaClient, "run">;
|
|
43
|
+
executor: Executor;
|
|
44
|
+
/** Where a start failure is recorded; defaults to marking the run failed with the error. */
|
|
45
|
+
onStartFailed?: (runId: string, err: unknown) => Promise<void>;
|
|
46
|
+
limit?: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Starts runs a deferred executor dispatched, and stops runs it asked to stop, through the
|
|
50
|
+
* server's executor. Called on the scheduler leader's tick. Each row is claimed by clearing its
|
|
51
|
+
* marker in a conditional update first, so two drains never start or stop the same run twice.
|
|
52
|
+
*/
|
|
53
|
+
export declare function drainDeferredRuns(options: DrainDeferredRunsOptions): Promise<{
|
|
54
|
+
started: number;
|
|
55
|
+
stopped: number;
|
|
56
|
+
}>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Executor for a process that must never start or stop runs itself: the
|
|
3
|
+
* native sandbox gateway (docs/native-sandbox.md). Sandbox workers can reach
|
|
4
|
+
* the gateway, so it holds no Docker socket, cluster credential, or in-process
|
|
5
|
+
* engine; a child it dispatches is only recorded (Run.startDeferredAt) and a
|
|
6
|
+
* stop it needs only requested (Run.stopRequestedAt). The scheduler leader
|
|
7
|
+
* carries both out through the server's own executor (`drainDeferredRuns`).
|
|
8
|
+
*
|
|
9
|
+
* Coding dispatch still needs the worker image and the repository's service
|
|
10
|
+
* declaration and knowledge files, resolved at dispatch: those come from a
|
|
11
|
+
* resolve-only view of the configured coding executor, whose start/stop are
|
|
12
|
+
* never called here.
|
|
13
|
+
*/
|
|
14
|
+
import { logger } from "../../core/logger.js";
|
|
15
|
+
const deferredLog = logger.child({ module: "deferred-executor" });
|
|
16
|
+
export class DeferredExecutor {
|
|
17
|
+
db;
|
|
18
|
+
resolver;
|
|
19
|
+
constructor(db,
|
|
20
|
+
/** Coding resolution only (image, service declaration, repository files); never started or stopped. */
|
|
21
|
+
resolver) {
|
|
22
|
+
this.db = db;
|
|
23
|
+
this.resolver = resolver;
|
|
24
|
+
}
|
|
25
|
+
async start(runId) {
|
|
26
|
+
await this.db.run.updateMany({ where: { id: runId, status: "pending" }, data: { startDeferredAt: new Date() } });
|
|
27
|
+
}
|
|
28
|
+
async stop(runId, reason) {
|
|
29
|
+
// Never started: nothing runs it, so it can simply end here.
|
|
30
|
+
const cancelled = await this.db.run.updateMany({
|
|
31
|
+
where: { id: runId, status: "pending", startDeferredAt: { not: null } },
|
|
32
|
+
data: {
|
|
33
|
+
status: "cancelled",
|
|
34
|
+
error: reason ?? "Cancelled before it started.",
|
|
35
|
+
finishedAt: new Date(),
|
|
36
|
+
startDeferredAt: null,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
if (cancelled.count > 0)
|
|
40
|
+
return;
|
|
41
|
+
await this.db.run.updateMany({
|
|
42
|
+
where: { id: runId, status: { in: ["pending", "running"] } },
|
|
43
|
+
data: { stopRequestedAt: new Date(), stopRequestReason: reason ?? null },
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
resolveCodingWorkerImage(selector) {
|
|
47
|
+
if (!this.resolver?.resolveCodingWorkerImage)
|
|
48
|
+
throw new Error("coding_execution_not_configured");
|
|
49
|
+
return this.resolver.resolveCodingWorkerImage(selector);
|
|
50
|
+
}
|
|
51
|
+
resolveCodingToolImage(selector) {
|
|
52
|
+
if (!this.resolver?.resolveCodingToolImage)
|
|
53
|
+
throw new Error("coding_execution_not_configured");
|
|
54
|
+
return this.resolver.resolveCodingToolImage(selector);
|
|
55
|
+
}
|
|
56
|
+
async readCodingServiceDeclaration(input) {
|
|
57
|
+
return this.resolver?.readCodingServiceDeclaration?.(input) ?? null;
|
|
58
|
+
}
|
|
59
|
+
async readCodingRepositoryFile(input) {
|
|
60
|
+
return this.resolver?.readCodingRepositoryFile?.(input) ?? null;
|
|
61
|
+
}
|
|
62
|
+
supportsCodingServices(provider) {
|
|
63
|
+
return this.resolver?.supportsCodingServices?.(provider) === true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Starts runs a deferred executor dispatched, and stops runs it asked to stop, through the
|
|
68
|
+
* server's executor. Called on the scheduler leader's tick. Each row is claimed by clearing its
|
|
69
|
+
* marker in a conditional update first, so two drains never start or stop the same run twice.
|
|
70
|
+
*/
|
|
71
|
+
export async function drainDeferredRuns(options) {
|
|
72
|
+
const { db, executor } = options;
|
|
73
|
+
const limit = options.limit ?? 50;
|
|
74
|
+
let started = 0;
|
|
75
|
+
let stopped = 0;
|
|
76
|
+
const toStart = await db.run.findMany({
|
|
77
|
+
where: { startDeferredAt: { not: null }, status: "pending" },
|
|
78
|
+
orderBy: { startDeferredAt: "asc" },
|
|
79
|
+
take: limit,
|
|
80
|
+
select: { id: true },
|
|
81
|
+
});
|
|
82
|
+
for (const { id } of toStart) {
|
|
83
|
+
const claimed = await db.run.updateMany({
|
|
84
|
+
where: { id, startDeferredAt: { not: null }, status: "pending" },
|
|
85
|
+
data: { startDeferredAt: null },
|
|
86
|
+
});
|
|
87
|
+
if (claimed.count === 0)
|
|
88
|
+
continue;
|
|
89
|
+
started += 1;
|
|
90
|
+
// Not awaited: an in-process executor's start runs the whole run.
|
|
91
|
+
void executor.start(id).catch(async (err) => {
|
|
92
|
+
deferredLog.error({ err, runId: id }, "deferred run failed to start");
|
|
93
|
+
await (options.onStartFailed?.(id, err) ??
|
|
94
|
+
db.run.updateMany({
|
|
95
|
+
where: { id, status: { in: ["pending", "running"] } },
|
|
96
|
+
data: { status: "failed", error: err instanceof Error ? err.message : String(err), finishedAt: new Date() },
|
|
97
|
+
}));
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const toStop = await db.run.findMany({
|
|
101
|
+
where: { stopRequestedAt: { not: null } },
|
|
102
|
+
orderBy: { stopRequestedAt: "asc" },
|
|
103
|
+
take: limit,
|
|
104
|
+
select: { id: true, stopRequestReason: true, status: true },
|
|
105
|
+
});
|
|
106
|
+
for (const row of toStop) {
|
|
107
|
+
const claimed = await db.run.updateMany({
|
|
108
|
+
where: { id: row.id, stopRequestedAt: { not: null } },
|
|
109
|
+
data: { stopRequestedAt: null, stopRequestReason: null },
|
|
110
|
+
});
|
|
111
|
+
if (claimed.count === 0 || !["pending", "running"].includes(row.status))
|
|
112
|
+
continue;
|
|
113
|
+
stopped += 1;
|
|
114
|
+
await executor.stop(row.id, row.stopRequestReason ?? undefined).catch((err) => {
|
|
115
|
+
deferredLog.warn({ err, runId: row.id }, "deferred stop request failed");
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return { started, stopped };
|
|
119
|
+
}
|
|
@@ -1,25 +1,49 @@
|
|
|
1
|
-
import type { CodingImageSelector, Executor, ExecutionRecoveryResult, PersistedExecutionHandle } from "./types.js";
|
|
2
1
|
import type { CodingProvider } from "../../coding/provider.js";
|
|
2
|
+
import type { CodingImageSelector, Executor, ExecutionRecoveryResult, PersistedExecutionHandle } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Which executor owns a run: a coding run, a native run in the control plane
|
|
5
|
+
* (in-process or DBOS), or a native run in an isolated sandbox worker. Read
|
|
6
|
+
* from the run's own row (agent kind plus Run.nativeExecutionMode), never from
|
|
7
|
+
* the agent's current setting, so a setting change reaches only later runs.
|
|
8
|
+
*/
|
|
9
|
+
export type ExecutionTarget = "native" | "native-sandbox" | "coding";
|
|
3
10
|
export interface ExecutionKindResolver {
|
|
4
|
-
kindForRun(runId: string): Promise<
|
|
11
|
+
kindForRun(runId: string): Promise<ExecutionTarget | null>;
|
|
5
12
|
}
|
|
6
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Keeps native execution unchanged while routing coding runs to isolation and
|
|
15
|
+
* sandbox-mode native runs to the native sandbox executor.
|
|
16
|
+
*
|
|
17
|
+
* With no sandbox executor composed in, a sandbox run goes to the native
|
|
18
|
+
* executor, whose runner refuses to execute it (executeRun fails it before
|
|
19
|
+
* any spend, closing its host check and status comments): fail closed through
|
|
20
|
+
* the one path that already finishes a run properly, never a silent run in
|
|
21
|
+
* the control plane.
|
|
22
|
+
*/
|
|
7
23
|
export declare class RoutingExecutor implements Executor {
|
|
8
24
|
private readonly resolver;
|
|
9
25
|
private readonly native;
|
|
10
26
|
private readonly coding;
|
|
11
|
-
|
|
27
|
+
private readonly sandbox?;
|
|
28
|
+
constructor(resolver: ExecutionKindResolver, native: Executor, coding: Executor, sandbox?: Executor | undefined);
|
|
29
|
+
private route;
|
|
12
30
|
start(runId: string): Promise<void>;
|
|
13
31
|
stop(runId: string, reason?: string): Promise<void>;
|
|
14
32
|
/**
|
|
15
|
-
* Recovery is routed by the run's
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* handle backend would need this class to know every
|
|
33
|
+
* Recovery is routed by the run's target, exactly like start/stop: a native
|
|
34
|
+
* run's handle belongs to the native executor (e.g. a DBOS workflow handle),
|
|
35
|
+
* a sandbox run's to the sandbox executor, a coding run's to the container
|
|
36
|
+
* executor. Routing by handle backend would need this class to know every
|
|
37
|
+
* backend name.
|
|
19
38
|
*/
|
|
20
39
|
recover(handle: PersistedExecutionHandle): Promise<ExecutionRecoveryResult>;
|
|
21
|
-
/**
|
|
40
|
+
/** Each distinct executor once, in a fixed order: native, coding, sandbox. */
|
|
41
|
+
private distinct;
|
|
42
|
+
/** Lifecycle fans out to every distinct executor; each hook is optional on the seam. */
|
|
22
43
|
launch(): Promise<void>;
|
|
44
|
+
/** Fans out like `launch`. */
|
|
45
|
+
warmUp(): Promise<void>;
|
|
46
|
+
/** Reverse of `launch`. */
|
|
23
47
|
close(): Promise<void>;
|
|
24
48
|
resolveCodingWorkerImage(selector: CodingImageSelector): string;
|
|
25
49
|
resolveCodingToolImage(selector: CodingImageSelector): string | null;
|
|
@@ -33,6 +57,7 @@ export declare class RoutingExecutor implements Executor {
|
|
|
33
57
|
path: string;
|
|
34
58
|
maxBytes: number;
|
|
35
59
|
}): Promise<string | null>;
|
|
60
|
+
supportsNativeSandbox(): boolean;
|
|
36
61
|
supportsCodingServices(provider: CodingProvider): boolean;
|
|
37
62
|
}
|
|
38
63
|
export declare class PrismaExecutionKindResolver implements ExecutionKindResolver {
|
|
@@ -44,6 +69,7 @@ export declare class PrismaExecutionKindResolver implements ExecutionKindResolve
|
|
|
44
69
|
id: string;
|
|
45
70
|
};
|
|
46
71
|
select: {
|
|
72
|
+
nativeExecutionMode: true;
|
|
47
73
|
agent: {
|
|
48
74
|
select: {
|
|
49
75
|
kind: true;
|
|
@@ -51,11 +77,12 @@ export declare class PrismaExecutionKindResolver implements ExecutionKindResolve
|
|
|
51
77
|
};
|
|
52
78
|
};
|
|
53
79
|
}): Promise<{
|
|
80
|
+
nativeExecutionMode: string | null;
|
|
54
81
|
agent: {
|
|
55
82
|
kind: string;
|
|
56
83
|
};
|
|
57
84
|
} | null>;
|
|
58
85
|
};
|
|
59
86
|
});
|
|
60
|
-
kindForRun(runId: string): Promise<
|
|
87
|
+
kindForRun(runId: string): Promise<ExecutionTarget | null>;
|
|
61
88
|
}
|
|
@@ -1,53 +1,86 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Keeps native execution unchanged while routing coding runs to isolation and
|
|
3
|
+
* sandbox-mode native runs to the native sandbox executor.
|
|
4
|
+
*
|
|
5
|
+
* With no sandbox executor composed in, a sandbox run goes to the native
|
|
6
|
+
* executor, whose runner refuses to execute it (executeRun fails it before
|
|
7
|
+
* any spend, closing its host check and status comments): fail closed through
|
|
8
|
+
* the one path that already finishes a run properly, never a silent run in
|
|
9
|
+
* the control plane.
|
|
10
|
+
*/
|
|
2
11
|
export class RoutingExecutor {
|
|
3
12
|
resolver;
|
|
4
13
|
native;
|
|
5
14
|
coding;
|
|
6
|
-
|
|
15
|
+
sandbox;
|
|
16
|
+
constructor(resolver, native, coding, sandbox) {
|
|
7
17
|
this.resolver = resolver;
|
|
8
18
|
this.native = native;
|
|
9
19
|
this.coding = coding;
|
|
20
|
+
this.sandbox = sandbox;
|
|
21
|
+
}
|
|
22
|
+
async route(runId) {
|
|
23
|
+
const target = await this.resolver.kindForRun(runId);
|
|
24
|
+
if (target === "native")
|
|
25
|
+
return { target, executor: this.native };
|
|
26
|
+
if (target === "native-sandbox")
|
|
27
|
+
return { target, executor: this.sandbox ?? this.native };
|
|
28
|
+
if (target === "coding")
|
|
29
|
+
return { target, executor: this.coding };
|
|
30
|
+
return null;
|
|
10
31
|
}
|
|
11
32
|
async start(runId) {
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return this.coding.start(runId);
|
|
17
|
-
throw new Error("executor_run_not_found");
|
|
33
|
+
const routed = await this.route(runId);
|
|
34
|
+
if (!routed)
|
|
35
|
+
throw new Error("executor_run_not_found");
|
|
36
|
+
return routed.executor.start(runId);
|
|
18
37
|
}
|
|
19
38
|
async stop(runId, reason) {
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
return
|
|
23
|
-
if (kind === "coding")
|
|
24
|
-
return this.coding.stop(runId, reason);
|
|
39
|
+
const routed = await this.route(runId);
|
|
40
|
+
if (routed)
|
|
41
|
+
return routed.executor.stop(runId, reason);
|
|
25
42
|
}
|
|
26
43
|
/**
|
|
27
|
-
* Recovery is routed by the run's
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* handle backend would need this class to know every
|
|
44
|
+
* Recovery is routed by the run's target, exactly like start/stop: a native
|
|
45
|
+
* run's handle belongs to the native executor (e.g. a DBOS workflow handle),
|
|
46
|
+
* a sandbox run's to the sandbox executor, a coding run's to the container
|
|
47
|
+
* executor. Routing by handle backend would need this class to know every
|
|
48
|
+
* backend name.
|
|
31
49
|
*/
|
|
32
50
|
async recover(handle) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
51
|
+
const routed = await this.route(handle.runId);
|
|
52
|
+
const target = routed?.target ?? "coding";
|
|
53
|
+
const executor = routed?.executor ?? this.coding;
|
|
54
|
+
if (!executor.recover) {
|
|
55
|
+
const reason = target === "native"
|
|
56
|
+
? "native_recovery_unavailable"
|
|
57
|
+
: target === "native-sandbox" && this.sandbox
|
|
58
|
+
? "native_sandbox_recovery_unavailable"
|
|
59
|
+
: target === "native-sandbox"
|
|
60
|
+
? "native_recovery_unavailable"
|
|
61
|
+
: "coding_recovery_unavailable";
|
|
62
|
+
return { state: "lost", reason };
|
|
38
63
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
64
|
+
return executor.recover(handle);
|
|
65
|
+
}
|
|
66
|
+
/** Each distinct executor once, in a fixed order: native, coding, sandbox. */
|
|
67
|
+
distinct() {
|
|
68
|
+
return [this.native, this.coding, this.sandbox].filter((executor, index, all) => executor !== undefined && all.indexOf(executor) === index);
|
|
42
69
|
}
|
|
43
|
-
/** Lifecycle fans out to
|
|
70
|
+
/** Lifecycle fans out to every distinct executor; each hook is optional on the seam. */
|
|
44
71
|
async launch() {
|
|
45
|
-
|
|
46
|
-
|
|
72
|
+
for (const executor of this.distinct())
|
|
73
|
+
await executor.launch?.();
|
|
74
|
+
}
|
|
75
|
+
/** Fans out like `launch`. */
|
|
76
|
+
async warmUp() {
|
|
77
|
+
for (const executor of this.distinct())
|
|
78
|
+
await executor.warmUp?.();
|
|
47
79
|
}
|
|
80
|
+
/** Reverse of `launch`. */
|
|
48
81
|
async close() {
|
|
49
|
-
|
|
50
|
-
|
|
82
|
+
for (const executor of this.distinct().reverse())
|
|
83
|
+
await executor.close?.();
|
|
51
84
|
}
|
|
52
85
|
resolveCodingWorkerImage(selector) {
|
|
53
86
|
if (!this.coding.resolveCodingWorkerImage)
|
|
@@ -69,6 +102,9 @@ export class RoutingExecutor {
|
|
|
69
102
|
return null;
|
|
70
103
|
return this.coding.readCodingRepositoryFile(input);
|
|
71
104
|
}
|
|
105
|
+
supportsNativeSandbox() {
|
|
106
|
+
return this.sandbox !== undefined;
|
|
107
|
+
}
|
|
72
108
|
supportsCodingServices(provider) {
|
|
73
109
|
return this.coding.supportsCodingServices?.(provider) === true;
|
|
74
110
|
}
|
|
@@ -81,8 +117,13 @@ export class PrismaExecutionKindResolver {
|
|
|
81
117
|
async kindForRun(runId) {
|
|
82
118
|
const run = await this.db.run.findUnique({
|
|
83
119
|
where: { id: runId },
|
|
84
|
-
select: { agent: { select: { kind: true } } },
|
|
120
|
+
select: { nativeExecutionMode: true, agent: { select: { kind: true } } },
|
|
85
121
|
});
|
|
86
|
-
|
|
122
|
+
if (run?.agent.kind === "coding")
|
|
123
|
+
return "coding";
|
|
124
|
+
if (run?.agent.kind !== "native")
|
|
125
|
+
return null;
|
|
126
|
+
// Null = a run from before the snapshot existed: the control plane, as it always ran.
|
|
127
|
+
return run.nativeExecutionMode === "sandbox" ? "native-sandbox" : "native";
|
|
87
128
|
}
|
|
88
129
|
}
|
|
@@ -43,6 +43,13 @@ export interface Executor {
|
|
|
43
43
|
launch?: () => Promise<void>;
|
|
44
44
|
/** Optional graceful shutdown counterpart to `launch`. */
|
|
45
45
|
close?: () => Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Fire-and-forget start-up warm-up, called only from a long-running server process's own
|
|
48
|
+
* start-up (`wardby serve`, the MCP server, `wardby scheduler`) — never from a one-shot CLI
|
|
49
|
+
* command. Delegates to the coding job launcher's own `warmUp` (e.g. Kubernetes's memoized
|
|
50
|
+
* cluster preflight); never throws.
|
|
51
|
+
*/
|
|
52
|
+
warmUp?: () => Promise<void>;
|
|
46
53
|
/**
|
|
47
54
|
* Resolves a coding agent's profile selection to an immutable worker
|
|
48
55
|
* image digest, once, at dispatch time (src/core/dispatch.ts) — never
|
|
@@ -85,5 +92,10 @@ export interface Executor {
|
|
|
85
92
|
* says so (Kubernetes and Docker, for Codex and Claude Code).
|
|
86
93
|
*/
|
|
87
94
|
supportsCodingServices?(provider: CodingProvider): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Whether this executor can run sandbox-mode native runs (a native sandbox executor is composed
|
|
97
|
+
* in). MCP refuses to set an agent to sandbox mode where it cannot (docs/native-sandbox.md).
|
|
98
|
+
*/
|
|
99
|
+
supportsNativeSandbox?(): boolean;
|
|
88
100
|
}
|
|
89
101
|
import type { CodingProvider } from "../../coding/provider.js";
|
|
@@ -7,7 +7,7 @@ import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
|
7
7
|
import { logger } from "../../core/logger.js";
|
|
8
8
|
import { ensurePrivateDirectory } from "../../core/private-directory.js";
|
|
9
9
|
import { readBoundedRegularFile } from "../../coding-worker/artifact.js";
|
|
10
|
-
import { parseCodingAgentOutputJson, redactAndTruncate, MAX_CODING_ARTIFACT_BYTES, MAX_CODING_OUTPUT_ISSUES, SAFE_CODING_OUTPUT_ISSUE, } from "../../coding/protocol.js";
|
|
10
|
+
import { parseCodingAgentOutputJson, redactAndTruncate, MAX_CODING_ARTIFACT_BYTES, MAX_CODING_INPUT_BYTES, MAX_CODING_OUTPUT_ISSUES, SAFE_CODING_OUTPUT_ISSUE, } from "../../coding/protocol.js";
|
|
11
11
|
import { assertDockerHostSupportsIsolation, assertClaudeAgentContainerInspection, assertClaudeToolRunnerContainerInspection, assertIsolationNetworkInspection, assertKeeperContainerInspection, assertNetworkKeeperContainerInspection, assertProxyContainerInspection, assertStorageVolumeInspection, assertWorkerContainerInspection, buildDockerIsolationPlan, CLAUDE_TOOL_SETUP_ENV, dockerSupportsServicesFor, } from "./docker-isolation.js";
|
|
12
12
|
import { serviceUnreadyError } from "../../coding/services/wording.js";
|
|
13
13
|
import { assertServiceContainerInspection, buildDockerServicePlan, dockerServiceContainerNames, } from "./docker-services.js";
|
|
@@ -272,7 +272,7 @@ export class NodeDockerArtifactTransfer {
|
|
|
272
272
|
}
|
|
273
273
|
async seedInput(sourceFile, container) {
|
|
274
274
|
const metadata = await lstat(sourceFile);
|
|
275
|
-
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size >
|
|
275
|
+
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > MAX_CODING_INPUT_BYTES) {
|
|
276
276
|
throw new Error("docker_input_artifact_invalid");
|
|
277
277
|
}
|
|
278
278
|
const docker = new NodeDockerCommandRunner({
|
|
@@ -1174,7 +1174,7 @@ export class DockerJobLauncher {
|
|
|
1174
1174
|
}
|
|
1175
1175
|
async validateInputArtifact(path) {
|
|
1176
1176
|
const metadata = await lstat(path);
|
|
1177
|
-
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size >
|
|
1177
|
+
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > MAX_CODING_INPUT_BYTES) {
|
|
1178
1178
|
throw new Error("docker_input_artifact_invalid");
|
|
1179
1179
|
}
|
|
1180
1180
|
}
|