@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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export class FakeChatProvider {
|
|
2
|
+
name = "slack";
|
|
3
|
+
posts = [];
|
|
4
|
+
updates = [];
|
|
5
|
+
channels = new Map();
|
|
6
|
+
queuedErrors = {
|
|
7
|
+
postMessage: [],
|
|
8
|
+
updateMessage: [],
|
|
9
|
+
channelInfo: [],
|
|
10
|
+
authTest: [],
|
|
11
|
+
};
|
|
12
|
+
nextTs = 1;
|
|
13
|
+
failNext(method, err) {
|
|
14
|
+
this.queuedErrors[method].push(err);
|
|
15
|
+
}
|
|
16
|
+
consumeFailure(method) {
|
|
17
|
+
const err = this.queuedErrors[method].shift();
|
|
18
|
+
if (err)
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
async postMessage(channelId, msg, opts = {}) {
|
|
22
|
+
this.consumeFailure("postMessage");
|
|
23
|
+
const ts = `${this.nextTs++}.000`;
|
|
24
|
+
this.posts.push({ channelId, ts, msg, threadTs: opts.threadTs, broadcast: opts.broadcast });
|
|
25
|
+
return { ts };
|
|
26
|
+
}
|
|
27
|
+
async updateMessage(channelId, ts, msg) {
|
|
28
|
+
this.consumeFailure("updateMessage");
|
|
29
|
+
this.updates.push({ channelId, ts, msg });
|
|
30
|
+
}
|
|
31
|
+
async channelInfo(channel) {
|
|
32
|
+
this.consumeFailure("channelInfo");
|
|
33
|
+
return this.channels.get(channel) ?? null;
|
|
34
|
+
}
|
|
35
|
+
async authTest() {
|
|
36
|
+
this.consumeFailure("authTest");
|
|
37
|
+
return { team: "fake", botUserId: "UFAKE" };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ChatProviderRegistry } from "./types.js";
|
|
2
|
+
export * from "./types.js";
|
|
3
|
+
/** One provider per configured chat service; empty when Slack is not configured. */
|
|
4
|
+
export declare function buildChatProviders(env?: NodeJS.ProcessEnv, fetchImpl?: typeof fetch): ChatProviderRegistry;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { loadSlackConfig } from "../../config/providers.js";
|
|
2
|
+
import { SlackChatProvider } from "./slack.js";
|
|
3
|
+
export * from "./types.js";
|
|
4
|
+
/** One provider per configured chat service; empty when Slack is not configured. */
|
|
5
|
+
export function buildChatProviders(env = process.env, fetchImpl) {
|
|
6
|
+
const slack = loadSlackConfig(env);
|
|
7
|
+
return slack ? { slack: new SlackChatProvider(slack, fetchImpl) } : {};
|
|
8
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack Web API client for workflow notifications: chat.postMessage,
|
|
3
|
+
* chat.update, conversations.info, auth.test. Plain fetch (JSON bodies for
|
|
4
|
+
* writes, form-encoded for reads), bot token. Every failure becomes a ChatError with a normalized code the
|
|
5
|
+
* dispatcher acts on; Slack's own error string is kept for the operator.
|
|
6
|
+
*/
|
|
7
|
+
import type { SlackConfig } from "../../config/providers.js";
|
|
8
|
+
import { type ChatMessage, type ChatProvider } from "./types.js";
|
|
9
|
+
export declare class SlackChatProvider implements ChatProvider {
|
|
10
|
+
private readonly config;
|
|
11
|
+
private readonly fetchImpl;
|
|
12
|
+
readonly name: "slack";
|
|
13
|
+
constructor(config: SlackConfig, fetchImpl?: typeof fetch);
|
|
14
|
+
/**
|
|
15
|
+
* `form` for read methods (conversations.info, auth.test): Slack accepts JSON
|
|
16
|
+
* bodies only on write methods and answers a read method's JSON body with
|
|
17
|
+
* invalid_arguments.
|
|
18
|
+
*/
|
|
19
|
+
private call;
|
|
20
|
+
private messageBody;
|
|
21
|
+
postMessage(channelId: string, msg: ChatMessage, opts?: {
|
|
22
|
+
threadTs?: string;
|
|
23
|
+
broadcast?: boolean;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
ts: string;
|
|
26
|
+
}>;
|
|
27
|
+
updateMessage(channelId: string, ts: string, msg: ChatMessage): Promise<void>;
|
|
28
|
+
channelInfo(channel: string): Promise<{
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
isPrivate: boolean;
|
|
32
|
+
} | null>;
|
|
33
|
+
authTest(): Promise<{
|
|
34
|
+
team: string;
|
|
35
|
+
botUserId: string;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ChatError } from "./types.js";
|
|
2
|
+
const CHANNEL_UNREACHABLE = new Set(["channel_not_found", "not_in_channel", "is_archived"]);
|
|
3
|
+
const AUTH_FAILED = new Set([
|
|
4
|
+
"invalid_auth",
|
|
5
|
+
"not_authed",
|
|
6
|
+
"token_revoked",
|
|
7
|
+
"token_expired",
|
|
8
|
+
"account_inactive",
|
|
9
|
+
"missing_scope",
|
|
10
|
+
]);
|
|
11
|
+
const TIMEOUT_MS = 10_000;
|
|
12
|
+
function classify(slackError) {
|
|
13
|
+
if (CHANNEL_UNREACHABLE.has(slackError))
|
|
14
|
+
return "channel_unreachable";
|
|
15
|
+
if (AUTH_FAILED.has(slackError))
|
|
16
|
+
return "auth_failed";
|
|
17
|
+
if (slackError === "message_not_found")
|
|
18
|
+
return "message_not_found";
|
|
19
|
+
if (slackError === "ratelimited")
|
|
20
|
+
return "rate_limited";
|
|
21
|
+
return "transient";
|
|
22
|
+
}
|
|
23
|
+
export class SlackChatProvider {
|
|
24
|
+
config;
|
|
25
|
+
fetchImpl;
|
|
26
|
+
name = "slack";
|
|
27
|
+
constructor(config, fetchImpl = fetch) {
|
|
28
|
+
this.config = config;
|
|
29
|
+
this.fetchImpl = fetchImpl;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `form` for read methods (conversations.info, auth.test): Slack accepts JSON
|
|
33
|
+
* bodies only on write methods and answers a read method's JSON body with
|
|
34
|
+
* invalid_arguments.
|
|
35
|
+
*/
|
|
36
|
+
async call(method, body, encoding = "json") {
|
|
37
|
+
let res;
|
|
38
|
+
try {
|
|
39
|
+
res = await this.fetchImpl(`${this.config.apiBaseUrl}/${method}`, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: {
|
|
42
|
+
authorization: `Bearer ${this.config.botToken}`,
|
|
43
|
+
"content-type": encoding === "json" ? "application/json; charset=utf-8" : "application/x-www-form-urlencoded",
|
|
44
|
+
},
|
|
45
|
+
body: encoding === "json"
|
|
46
|
+
? JSON.stringify(body)
|
|
47
|
+
: new URLSearchParams(Object.fromEntries(Object.entries(body).map(([k, v]) => [k, String(v)]))).toString(),
|
|
48
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
throw new ChatError("transient", err instanceof Error ? err.message : String(err));
|
|
53
|
+
}
|
|
54
|
+
if (res.status === 429) {
|
|
55
|
+
const seconds = Number(res.headers.get("retry-after") ?? "1");
|
|
56
|
+
throw new ChatError("rate_limited", "ratelimited", (Number.isFinite(seconds) && seconds > 0 ? seconds : 1) * 1000);
|
|
57
|
+
}
|
|
58
|
+
if (!res.ok)
|
|
59
|
+
throw new ChatError("transient", `http_${res.status}`);
|
|
60
|
+
const json = (await res.json().catch(() => ({ ok: false, error: "invalid_json" })));
|
|
61
|
+
if (json.ok !== true) {
|
|
62
|
+
const slackError = typeof json.error === "string" ? json.error : "unknown_error";
|
|
63
|
+
throw new ChatError(classify(slackError), slackError);
|
|
64
|
+
}
|
|
65
|
+
return json;
|
|
66
|
+
}
|
|
67
|
+
messageBody(msg) {
|
|
68
|
+
return {
|
|
69
|
+
text: msg.text,
|
|
70
|
+
...(msg.blocks ? { blocks: msg.blocks } : {}),
|
|
71
|
+
...(this.config.customize && msg.username ? { username: msg.username } : {}),
|
|
72
|
+
...(this.config.customize && msg.iconEmoji ? { icon_emoji: msg.iconEmoji } : {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async postMessage(channelId, msg, opts = {}) {
|
|
76
|
+
const json = await this.call("chat.postMessage", {
|
|
77
|
+
channel: channelId,
|
|
78
|
+
...this.messageBody(msg),
|
|
79
|
+
...(opts.threadTs ? { thread_ts: opts.threadTs } : {}),
|
|
80
|
+
...(opts.threadTs && opts.broadcast ? { reply_broadcast: true } : {}),
|
|
81
|
+
unfurl_links: false,
|
|
82
|
+
unfurl_media: false,
|
|
83
|
+
});
|
|
84
|
+
return { ts: String(json.ts) };
|
|
85
|
+
}
|
|
86
|
+
async updateMessage(channelId, ts, msg) {
|
|
87
|
+
await this.call("chat.update", { channel: channelId, ts, ...this.messageBody(msg) });
|
|
88
|
+
}
|
|
89
|
+
async channelInfo(channel) {
|
|
90
|
+
try {
|
|
91
|
+
const json = await this.call("conversations.info", { channel }, "form");
|
|
92
|
+
const c = json.channel;
|
|
93
|
+
return { id: c.id, name: c.name, isPrivate: c.is_private === true };
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
if (err instanceof ChatError && err.code === "channel_unreachable")
|
|
97
|
+
return null;
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async authTest() {
|
|
102
|
+
const json = await this.call("auth.test", {}, "form");
|
|
103
|
+
return { team: String(json.team), botUserId: String(json.user_id) };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-neutral chat surface for outbound workflow notifications. Slack is
|
|
3
|
+
* the first (and only) implementation (./slack.ts). wardby only ever posts
|
|
4
|
+
* or updates messages here — it never reads channel history or listens for
|
|
5
|
+
* events.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CHAT_PROVIDERS: readonly ["slack"];
|
|
8
|
+
export type ChatProviderName = (typeof CHAT_PROVIDERS)[number];
|
|
9
|
+
export type ChatErrorCode = "rate_limited" | "channel_unreachable" | "auth_failed" | "message_not_found" | "transient";
|
|
10
|
+
export declare class ChatError extends Error {
|
|
11
|
+
readonly code: ChatErrorCode;
|
|
12
|
+
readonly slackError: string;
|
|
13
|
+
readonly retryAfterMs?: number | undefined;
|
|
14
|
+
constructor(code: ChatErrorCode, slackError: string, retryAfterMs?: number | undefined);
|
|
15
|
+
}
|
|
16
|
+
export interface ChatMessage {
|
|
17
|
+
text: string;
|
|
18
|
+
blocks?: unknown[];
|
|
19
|
+
username?: string;
|
|
20
|
+
iconEmoji?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ChatProvider {
|
|
23
|
+
readonly name: ChatProviderName;
|
|
24
|
+
postMessage(channelId: string, msg: ChatMessage, opts?: {
|
|
25
|
+
threadTs?: string;
|
|
26
|
+
broadcast?: boolean;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
ts: string;
|
|
29
|
+
}>;
|
|
30
|
+
updateMessage(channelId: string, ts: string, msg: ChatMessage): Promise<void>;
|
|
31
|
+
/** null when the bot cannot see the channel; throws ChatError("auth_failed") on missing scope. */
|
|
32
|
+
channelInfo(channel: string): Promise<{
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
isPrivate: boolean;
|
|
36
|
+
} | null>;
|
|
37
|
+
authTest(): Promise<{
|
|
38
|
+
team: string;
|
|
39
|
+
botUserId: string;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
export type ChatProviderRegistry = Partial<Record<ChatProviderName, ChatProvider>>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-neutral chat surface for outbound workflow notifications. Slack is
|
|
3
|
+
* the first (and only) implementation (./slack.ts). wardby only ever posts
|
|
4
|
+
* or updates messages here — it never reads channel history or listens for
|
|
5
|
+
* events.
|
|
6
|
+
*/
|
|
7
|
+
export const CHAT_PROVIDERS = ["slack"];
|
|
8
|
+
export class ChatError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
slackError;
|
|
11
|
+
retryAfterMs;
|
|
12
|
+
constructor(code, slackError, retryAfterMs) {
|
|
13
|
+
super(`chat_error:${code}:${slackError}`);
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.slackError = slackError;
|
|
16
|
+
this.retryAfterMs = retryAfterMs;
|
|
17
|
+
this.name = "ChatError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fake model upstream for load tests: a `fetch` that never opens a socket.
|
|
3
|
+
* It answers an OpenAI Responses streaming request with one assistant message
|
|
4
|
+
* whose text is the coding worker's final JSON (`outcome: "no_changes"`), so a
|
|
5
|
+
* run finishes without tool calls, changes or a pull request. Enabled only by
|
|
6
|
+
* `mockUpstreamFromEnv` (see its guard). Never use outside a load test.
|
|
7
|
+
*
|
|
8
|
+
* Codex/OpenAI Responses only (ruling R1): any other host, including the
|
|
9
|
+
* Anthropic Messages endpoint, gets a 501 rather than a fabricated reply.
|
|
10
|
+
*/
|
|
11
|
+
export interface MockUpstreamConfig {
|
|
12
|
+
latencyMs: number;
|
|
13
|
+
jitterMs: number;
|
|
14
|
+
inputTokens: number;
|
|
15
|
+
outputTokens: number;
|
|
16
|
+
}
|
|
17
|
+
export declare const DEFAULT_MOCK_UPSTREAM: MockUpstreamConfig;
|
|
18
|
+
/** The runId the coding worker's prompt names, or null when the request carries none. */
|
|
19
|
+
export declare function extractRunId(body: string): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Builds a `fetch` that simulates the OpenAI Responses streaming endpoint for
|
|
22
|
+
* load tests: no socket, no credential forwarding, deterministic latency and
|
|
23
|
+
* token counts. `sleep` is injectable so tests can assert on the delay without
|
|
24
|
+
* waiting it out.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createMockUpstream(overrides?: Partial<MockUpstreamConfig>, sleep?: (ms: number) => Promise<void>): typeof globalThis.fetch;
|
|
27
|
+
/**
|
|
28
|
+
* The mock model upstream is a load-test seam inside a production binary, so
|
|
29
|
+
* it needs two deliberate switches: WARDBY_LOAD_TEST=1 and
|
|
30
|
+
* WARDBY_CODING_PROXY_MOCK_UPSTREAM=1. One without the other, or any other
|
|
31
|
+
* value, refuses to start the proxy rather than silently picking a side.
|
|
32
|
+
*/
|
|
33
|
+
export declare function mockUpstreamFromEnv(env: NodeJS.ProcessEnv): MockUpstreamConfig | null;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export const DEFAULT_MOCK_UPSTREAM = {
|
|
2
|
+
latencyMs: 5000,
|
|
3
|
+
jitterMs: 0,
|
|
4
|
+
inputTokens: 1200,
|
|
5
|
+
outputTokens: 80,
|
|
6
|
+
};
|
|
7
|
+
const RUN_ID = /The final JSON runId must be ([A-Za-z0-9][A-Za-z0-9_-]*)\./;
|
|
8
|
+
/** The runId the coding worker's prompt names, or null when the request carries none. */
|
|
9
|
+
export function extractRunId(body) {
|
|
10
|
+
return RUN_ID.exec(body)?.[1] ?? null;
|
|
11
|
+
}
|
|
12
|
+
const sse = (event, data) => `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
13
|
+
async function bodyText(input, init) {
|
|
14
|
+
const raw = init?.body;
|
|
15
|
+
if (typeof raw === "string")
|
|
16
|
+
return raw;
|
|
17
|
+
if (raw instanceof Uint8Array)
|
|
18
|
+
return Buffer.from(raw).toString("utf8");
|
|
19
|
+
if (input instanceof Request)
|
|
20
|
+
return input.text();
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Builds a `fetch` that simulates the OpenAI Responses streaming endpoint for
|
|
25
|
+
* load tests: no socket, no credential forwarding, deterministic latency and
|
|
26
|
+
* token counts. `sleep` is injectable so tests can assert on the delay without
|
|
27
|
+
* waiting it out.
|
|
28
|
+
*/
|
|
29
|
+
export function createMockUpstream(overrides = {}, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
30
|
+
const config = { ...DEFAULT_MOCK_UPSTREAM, ...overrides };
|
|
31
|
+
return async (input, init) => {
|
|
32
|
+
const url = new URL(typeof input === "string" || input instanceof URL ? input : input.url);
|
|
33
|
+
if (url.hostname !== "api.openai.com") {
|
|
34
|
+
return Response.json({ error: { code: "mock_upstream_protocol_unsupported" } }, { status: 501 });
|
|
35
|
+
}
|
|
36
|
+
const runId = extractRunId(await bodyText(input, init));
|
|
37
|
+
if (!runId)
|
|
38
|
+
return Response.json({ error: { code: "mock_upstream_no_run_id" } }, { status: 400 });
|
|
39
|
+
await sleep(config.latencyMs + Math.floor(Math.random() * (config.jitterMs + 1)));
|
|
40
|
+
const text = JSON.stringify({
|
|
41
|
+
schemaVersion: 1,
|
|
42
|
+
runId,
|
|
43
|
+
outcome: "no_changes",
|
|
44
|
+
summary: "Load test: no changes.",
|
|
45
|
+
tag: null,
|
|
46
|
+
tests: [],
|
|
47
|
+
});
|
|
48
|
+
const id = `resp_mock_${runId}`;
|
|
49
|
+
const item = {
|
|
50
|
+
id: `msg_${runId}`,
|
|
51
|
+
type: "message",
|
|
52
|
+
role: "assistant",
|
|
53
|
+
status: "completed",
|
|
54
|
+
content: [{ type: "output_text", text, annotations: [] }],
|
|
55
|
+
};
|
|
56
|
+
const usage = {
|
|
57
|
+
input_tokens: config.inputTokens,
|
|
58
|
+
input_tokens_details: { cached_tokens: 0 },
|
|
59
|
+
output_tokens: config.outputTokens,
|
|
60
|
+
output_tokens_details: { reasoning_tokens: 0 },
|
|
61
|
+
total_tokens: config.inputTokens + config.outputTokens,
|
|
62
|
+
};
|
|
63
|
+
const stream = sse("response.created", { type: "response.created", response: { id, status: "in_progress", output: [] } }) +
|
|
64
|
+
sse("response.output_item.added", {
|
|
65
|
+
type: "response.output_item.added",
|
|
66
|
+
output_index: 0,
|
|
67
|
+
item: { ...item, status: "in_progress", content: [] },
|
|
68
|
+
}) +
|
|
69
|
+
sse("response.output_text.delta", {
|
|
70
|
+
type: "response.output_text.delta",
|
|
71
|
+
output_index: 0,
|
|
72
|
+
content_index: 0,
|
|
73
|
+
item_id: item.id,
|
|
74
|
+
delta: text,
|
|
75
|
+
}) +
|
|
76
|
+
sse("response.output_item.done", { type: "response.output_item.done", output_index: 0, item }) +
|
|
77
|
+
sse("response.completed", {
|
|
78
|
+
type: "response.completed",
|
|
79
|
+
response: { id, status: "completed", output: [item], usage },
|
|
80
|
+
});
|
|
81
|
+
return new Response(stream, { status: 200, headers: { "content-type": "text/event-stream" } });
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const FLAG_LOAD = "WARDBY_LOAD_TEST";
|
|
85
|
+
const FLAG_MOCK = "WARDBY_CODING_PROXY_MOCK_UPSTREAM";
|
|
86
|
+
function nonNegativeInt(env, name, fallback) {
|
|
87
|
+
const raw = env[name];
|
|
88
|
+
if (raw === undefined || raw === "")
|
|
89
|
+
return fallback;
|
|
90
|
+
// Plain decimal digits only: no sign, hex, exponent, or whitespace.
|
|
91
|
+
const n = /^\d+$/.test(raw) ? Number(raw) : NaN;
|
|
92
|
+
if (!Number.isSafeInteger(n) || n < 0) {
|
|
93
|
+
throw new Error(`mock_upstream_guard: ${name} must be a non-negative integer`);
|
|
94
|
+
}
|
|
95
|
+
return n;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The mock model upstream is a load-test seam inside a production binary, so
|
|
99
|
+
* it needs two deliberate switches: WARDBY_LOAD_TEST=1 and
|
|
100
|
+
* WARDBY_CODING_PROXY_MOCK_UPSTREAM=1. One without the other, or any other
|
|
101
|
+
* value, refuses to start the proxy rather than silently picking a side.
|
|
102
|
+
*/
|
|
103
|
+
export function mockUpstreamFromEnv(env) {
|
|
104
|
+
const load = env[FLAG_LOAD];
|
|
105
|
+
const mock = env[FLAG_MOCK];
|
|
106
|
+
if (load === undefined && mock === undefined)
|
|
107
|
+
return null;
|
|
108
|
+
if (load !== "1" || mock !== "1") {
|
|
109
|
+
throw new Error(`mock_upstream_guard: set both ${FLAG_LOAD}=1 and ${FLAG_MOCK}=1 to use the mock upstream, or neither`);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
...DEFAULT_MOCK_UPSTREAM,
|
|
113
|
+
latencyMs: nonNegativeInt(env, "WARDBY_LOAD_MOCK_LATENCY_MS", DEFAULT_MOCK_UPSTREAM.latencyMs),
|
|
114
|
+
jitterMs: nonNegativeInt(env, "WARDBY_LOAD_MOCK_JITTER_MS", DEFAULT_MOCK_UPSTREAM.jitterMs),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PrismaClient } from "#prisma";
|
|
2
|
+
import type { FileDependency } from "../../../coding/registry/types.js";
|
|
2
3
|
import type { ApprovedVersion, PlanRefusedVersion, RegistryFetchRecord, RegistryRunContext, RegistryStore, StoredVersionFact } from "./store.js";
|
|
3
4
|
export declare class PrismaRegistryStore implements RegistryStore {
|
|
4
5
|
private readonly db;
|
|
@@ -6,6 +7,8 @@ export declare class PrismaRegistryStore implements RegistryStore {
|
|
|
6
7
|
findRunByRegistryTokenHash(hash: string, now: Date): Promise<RegistryRunContext | null>;
|
|
7
8
|
isAllowedDependency(runId: string, ecosystem: string, name: string): Promise<boolean>;
|
|
8
9
|
addAllowances(runId: string, ecosystem: string, names: readonly string[]): Promise<void>;
|
|
10
|
+
allowedExtras(runId: string, ecosystem: string, name: string): Promise<string[]>;
|
|
11
|
+
addExtraAllowances(runId: string, ecosystem: string, dependencies: readonly FileDependency[]): Promise<void>;
|
|
9
12
|
recordFetch(record: RegistryFetchRecord): Promise<void>;
|
|
10
13
|
usage(runId: string): Promise<{
|
|
11
14
|
files: number;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { extraAllowanceName, extraOfAllowanceName } from "./store.js";
|
|
1
2
|
/** Versions looked up per query, so a 5000-entry lockfile is a handful of
|
|
2
3
|
* bounded queries rather than one enormous OR. */
|
|
3
4
|
const FACT_QUERY_CHUNK = 500;
|
|
@@ -26,6 +27,9 @@ export class PrismaRegistryStore {
|
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
async isAllowedDependency(runId, ecosystem, name) {
|
|
30
|
+
// An extra's row (`name[extra]`) is never a package allowance.
|
|
31
|
+
if (name.includes("["))
|
|
32
|
+
return false;
|
|
29
33
|
const row = await this.db.registryAllowance.findUnique({
|
|
30
34
|
where: { runId_ecosystem_name: { runId, ecosystem, name } },
|
|
31
35
|
select: { runId: true },
|
|
@@ -40,6 +44,19 @@ export class PrismaRegistryStore {
|
|
|
40
44
|
skipDuplicates: true,
|
|
41
45
|
});
|
|
42
46
|
}
|
|
47
|
+
async allowedExtras(runId, ecosystem, name) {
|
|
48
|
+
const rows = await this.db.registryAllowance.findMany({
|
|
49
|
+
where: { runId, ecosystem, name: { startsWith: `${name}[` } },
|
|
50
|
+
select: { name: true },
|
|
51
|
+
});
|
|
52
|
+
return rows
|
|
53
|
+
.map((row) => extraOfAllowanceName(row.name, name))
|
|
54
|
+
.filter((extra) => extra !== null)
|
|
55
|
+
.sort();
|
|
56
|
+
}
|
|
57
|
+
async addExtraAllowances(runId, ecosystem, dependencies) {
|
|
58
|
+
await this.addAllowances(runId, ecosystem, dependencies.flatMap((dependency) => dependency.extras.map((extra) => extraAllowanceName(dependency.name, extra))));
|
|
59
|
+
}
|
|
43
60
|
async recordFetch(record) {
|
|
44
61
|
await this.db.registryFetch.create({ data: record });
|
|
45
62
|
}
|
|
@@ -209,5 +209,9 @@ export declare class RegistryService {
|
|
|
209
209
|
* exactly what was reserved: the declared size when known, or the bytes
|
|
210
210
|
* actually streamed when it was not. */
|
|
211
211
|
private releaseDownload;
|
|
212
|
+
/** The extras the run allows for `name`: those its allowlist entries name
|
|
213
|
+
* plus those a served parent's dependency line asked of it. They decide
|
|
214
|
+
* which of the package's extra-gated dependencies its files allow. */
|
|
215
|
+
private allowedExtras;
|
|
212
216
|
private download;
|
|
213
217
|
}
|
|
@@ -168,7 +168,7 @@ function versionDependencies(adapter, meta, version) {
|
|
|
168
168
|
function isPackageName(adapter, name) {
|
|
169
169
|
try {
|
|
170
170
|
const entry = adapter.parseAllowlistEntry(name);
|
|
171
|
-
return !entry.wildcard && entry.range === undefined && entry.name === name;
|
|
171
|
+
return !entry.wildcard && entry.range === undefined && entry.extras === undefined && entry.name === name;
|
|
172
172
|
}
|
|
173
173
|
catch {
|
|
174
174
|
return false;
|
|
@@ -962,6 +962,16 @@ export class RegistryService {
|
|
|
962
962
|
else
|
|
963
963
|
this.inFlight.set(runId, slot);
|
|
964
964
|
}
|
|
965
|
+
/** The extras the run allows for `name`: those its allowlist entries name
|
|
966
|
+
* plus those a served parent's dependency line asked of it. They decide
|
|
967
|
+
* which of the package's extra-gated dependencies its files allow. */
|
|
968
|
+
async allowedExtras(adapter, context, name) {
|
|
969
|
+
const entries = parseAllowlist(context.allowlist, this.options.adapters).get(adapter.id) ?? [];
|
|
970
|
+
const extras = new Set(entries.flatMap((entry) => (!entry.wildcard && entry.name === name ? (entry.extras ?? []) : [])));
|
|
971
|
+
for (const extra of await this.options.store.allowedExtras(context.runId, adapter.id, name))
|
|
972
|
+
extras.add(extra);
|
|
973
|
+
return [...extras].sort();
|
|
974
|
+
}
|
|
965
975
|
async download(adapter, context, name, route, file, signal) {
|
|
966
976
|
const { limits, store } = this.options;
|
|
967
977
|
const runId = context.runId;
|
|
@@ -1097,8 +1107,15 @@ export class RegistryService {
|
|
|
1097
1107
|
served.bytes += bytes;
|
|
1098
1108
|
if (adapter.dependenciesFromFile && buffered.length > 0) {
|
|
1099
1109
|
const body = Buffer.concat(buffered);
|
|
1100
|
-
const
|
|
1101
|
-
await
|
|
1110
|
+
const extras = await this.allowedExtras(adapter, context, name);
|
|
1111
|
+
const dependencies = (await adapter.dependenciesFromFile(route, body, extras).catch(() => [])).map((dependency) => ({ name: adapter.normalizeName(dependency.name), extras: dependency.extras }));
|
|
1112
|
+
await store.addAllowances(context.runId, adapter.id, dependencies.map((dependency) => dependency.name));
|
|
1113
|
+
// Dependency extras are followed only below a package the run
|
|
1114
|
+
// allows with extras (an entry naming extras, or a dependency
|
|
1115
|
+
// such a package asked for with extras): a plain entry follows
|
|
1116
|
+
// no extras, its own or its dependencies'.
|
|
1117
|
+
if (extras.length > 0)
|
|
1118
|
+
await store.addExtraAllowances(context.runId, adapter.id, dependencies);
|
|
1102
1119
|
}
|
|
1103
1120
|
controller.close();
|
|
1104
1121
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PackageAllowlist } from "../../../coding/registry/allowlist.js";
|
|
2
|
-
import type { DeclaredDependency } from "../../../coding/registry/types.js";
|
|
2
|
+
import type { DeclaredDependency, FileDependency } from "../../../coding/registry/types.js";
|
|
3
3
|
export interface RegistryRunContext {
|
|
4
4
|
runId: string;
|
|
5
5
|
deadlineAt: Date;
|
|
@@ -41,6 +41,17 @@ export interface PlanRefusedVersion {
|
|
|
41
41
|
/** Set for a too-new refusal: it lapses once the version is old enough. */
|
|
42
42
|
publishedAt?: Date | null;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* An extra a run allows for a package (PyPI `uvicorn[standard]`) is kept as
|
|
46
|
+
* an allowance row named `name[extra]`, beside the package's own row. No
|
|
47
|
+
* registry route can ask for that name (routes accept only valid package
|
|
48
|
+
* names, which never contain `[`), so the row never allows a fetch itself:
|
|
49
|
+
* it only tells the proxy which of the package's extra-gated dependencies
|
|
50
|
+
* to follow when it serves the package's files.
|
|
51
|
+
*/
|
|
52
|
+
export declare function extraAllowanceName(name: string, extra: string): string;
|
|
53
|
+
/** The extra in an allowance row name for `name`, or null when the row is not one of its extras. */
|
|
54
|
+
export declare function extraOfAllowanceName(rowName: string, name: string): string | null;
|
|
44
55
|
/** Run-scoped store over the registry-only token session lookup and the
|
|
45
56
|
* RegistryAllowance/RegistryFetch tables (Task 5). Backs the registry proxy:
|
|
46
57
|
* it never sees the model capability, only the derived registry token. */
|
|
@@ -48,6 +59,10 @@ export interface RegistryStore {
|
|
|
48
59
|
findRunByRegistryTokenHash(hash: string, now: Date): Promise<RegistryRunContext | null>;
|
|
49
60
|
isAllowedDependency(runId: string, ecosystem: string, name: string): Promise<boolean>;
|
|
50
61
|
addAllowances(runId: string, ecosystem: string, names: readonly string[]): Promise<void>;
|
|
62
|
+
/** The extras the run allows for `name` (normalized, sorted). */
|
|
63
|
+
allowedExtras(runId: string, ecosystem: string, name: string): Promise<string[]>;
|
|
64
|
+
/** Records extras the run allows for each package (an existing one is kept). */
|
|
65
|
+
addExtraAllowances(runId: string, ecosystem: string, dependencies: readonly FileDependency[]): Promise<void>;
|
|
51
66
|
recordFetch(record: RegistryFetchRecord): Promise<void>;
|
|
52
67
|
usage(runId: string): Promise<{
|
|
53
68
|
files: number;
|
|
@@ -99,6 +114,8 @@ export declare class MemoryRegistryStore implements RegistryStore {
|
|
|
99
114
|
findRunByRegistryTokenHash(hash: string, now: Date): Promise<RegistryRunContext | null>;
|
|
100
115
|
isAllowedDependency(runId: string, ecosystem: string, name: string): Promise<boolean>;
|
|
101
116
|
addAllowances(runId: string, ecosystem: string, names: readonly string[]): Promise<void>;
|
|
117
|
+
allowedExtras(runId: string, ecosystem: string, name: string): Promise<string[]>;
|
|
118
|
+
addExtraAllowances(runId: string, ecosystem: string, dependencies: readonly FileDependency[]): Promise<void>;
|
|
102
119
|
recordFetch(record: RegistryFetchRecord): Promise<void>;
|
|
103
120
|
usage(runId: string): Promise<{
|
|
104
121
|
files: number;
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An extra a run allows for a package (PyPI `uvicorn[standard]`) is kept as
|
|
3
|
+
* an allowance row named `name[extra]`, beside the package's own row. No
|
|
4
|
+
* registry route can ask for that name (routes accept only valid package
|
|
5
|
+
* names, which never contain `[`), so the row never allows a fetch itself:
|
|
6
|
+
* it only tells the proxy which of the package's extra-gated dependencies
|
|
7
|
+
* to follow when it serves the package's files.
|
|
8
|
+
*/
|
|
9
|
+
export function extraAllowanceName(name, extra) {
|
|
10
|
+
return `${name}[${extra}]`;
|
|
11
|
+
}
|
|
12
|
+
/** The extra in an allowance row name for `name`, or null when the row is not one of its extras. */
|
|
13
|
+
export function extraOfAllowanceName(rowName, name) {
|
|
14
|
+
const prefix = `${name}[`;
|
|
15
|
+
if (!rowName.startsWith(prefix) || !rowName.endsWith("]"))
|
|
16
|
+
return null;
|
|
17
|
+
const extra = rowName.slice(prefix.length, -1);
|
|
18
|
+
return /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(extra) ? extra : null;
|
|
19
|
+
}
|
|
1
20
|
/** In-memory adapter for tests. `contexts` is settable directly so tests can
|
|
2
21
|
* seed a run without going through a full proxy session. */
|
|
3
22
|
export class MemoryRegistryStore {
|
|
@@ -15,12 +34,26 @@ export class MemoryRegistryStore {
|
|
|
15
34
|
return context && context.deadlineAt > now ? context : null;
|
|
16
35
|
}
|
|
17
36
|
async isAllowedDependency(runId, ecosystem, name) {
|
|
37
|
+
// An extra's row (`name[extra]`) is never a package allowance.
|
|
38
|
+
if (name.includes("["))
|
|
39
|
+
return false;
|
|
18
40
|
return this.allowances.has(`${runId}\0${ecosystem}\0${name}`);
|
|
19
41
|
}
|
|
20
42
|
async addAllowances(runId, ecosystem, names) {
|
|
21
43
|
for (const name of names)
|
|
22
44
|
this.allowances.add(`${runId}\0${ecosystem}\0${name}`);
|
|
23
45
|
}
|
|
46
|
+
async allowedExtras(runId, ecosystem, name) {
|
|
47
|
+
const prefix = `${runId}\0${ecosystem}\0`;
|
|
48
|
+
return [...this.allowances]
|
|
49
|
+
.filter((key) => key.startsWith(prefix))
|
|
50
|
+
.map((key) => extraOfAllowanceName(key.slice(prefix.length), name))
|
|
51
|
+
.filter((extra) => extra !== null)
|
|
52
|
+
.sort();
|
|
53
|
+
}
|
|
54
|
+
async addExtraAllowances(runId, ecosystem, dependencies) {
|
|
55
|
+
await this.addAllowances(runId, ecosystem, dependencies.flatMap((dependency) => dependency.extras.map((extra) => extraAllowanceName(dependency.name, extra))));
|
|
56
|
+
}
|
|
24
57
|
async recordFetch(record) {
|
|
25
58
|
this.fetches.push({ ...record, createdAt: new Date() });
|
|
26
59
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { logger } from "../../core/logger.js";
|
|
1
2
|
import { REGISTRY_ADAPTERS } from "../../coding/registry/adapters.js";
|
|
2
3
|
import { CODING_PROXY_ALIAS, CODING_PROXY_DENY_PORT, CODING_PROXY_PORT } from "../jobs/docker-isolation.js";
|
|
3
4
|
import { startDenyPortListener } from "./deny-port.js";
|
|
4
5
|
import { EnvironmentCredentialResolver } from "./environment-credentials.js";
|
|
6
|
+
import { createMockUpstream, mockUpstreamFromEnv } from "./mock-upstream.js";
|
|
5
7
|
import { CodingProxy } from "./proxy.js";
|
|
6
8
|
import { PrismaProxyLedger } from "./prisma-ledger.js";
|
|
7
9
|
import { OsvAudit } from "./registry/audit.js";
|
|
@@ -38,10 +40,19 @@ export function createRegistryUpstream(pinned) {
|
|
|
38
40
|
/** Starts the trusted proxy with the fixed worker-only Docker endpoint, plus the deny port the enforcement witness probes. */
|
|
39
41
|
export async function startConfiguredCodingProxy(options) {
|
|
40
42
|
const env = options.env ?? process.env;
|
|
43
|
+
// Throws (mock_upstream_guard) on a partial or malformed setting, before anything listens.
|
|
44
|
+
const mock = mockUpstreamFromEnv(env);
|
|
45
|
+
if (mock) {
|
|
46
|
+
logger.warn({ module: "coding-proxy-runtime", event: "proxy.mock_upstream_enabled" }, "coding proxy: MOCK MODEL UPSTREAM ENABLED (load test). No request reaches a model provider; never use this on a real deployment.");
|
|
47
|
+
}
|
|
48
|
+
const audit = mock
|
|
49
|
+
? (event) => options.audit?.({ ...event, mockUpstream: true })
|
|
50
|
+
: options.audit;
|
|
41
51
|
const proxy = new CodingProxy({
|
|
42
52
|
ledger: new PrismaProxyLedger(options.db),
|
|
43
53
|
credentials: new EnvironmentCredentialResolver(env),
|
|
44
|
-
audit
|
|
54
|
+
audit,
|
|
55
|
+
...(mock ? { fetch: createMockUpstream(mock) } : {}),
|
|
45
56
|
});
|
|
46
57
|
const buildPinnedFetch = options.createPinnedFetch ?? createPinnedProxyFetch;
|
|
47
58
|
const upstreamHosts = [...new Set([...REGISTRY_ADAPTERS.values()].flatMap((adapter) => adapter.upstreamHosts))];
|
|
@@ -124,6 +124,8 @@ export interface ProxyAuditEvent {
|
|
|
124
124
|
/** For a failed stream: the upstream response's content type and encoding headers, for diagnosis. */
|
|
125
125
|
contentType?: string;
|
|
126
126
|
contentEncoding?: string;
|
|
127
|
+
/** Set on every event while the load-test mock upstream is enabled. */
|
|
128
|
+
mockUpstream?: boolean;
|
|
127
129
|
}
|
|
128
130
|
export type ProxyAuditSink = (event: ProxyAuditEvent) => void;
|
|
129
131
|
export interface CredentialResolver {
|