@wardby/cli 0.5.3 → 0.7.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/deploy/local/docker-compose.quickstart-coding.yml +13 -0
- 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 +27 -3
- package/dist/claude-coding-worker/driver.js +161 -20
- package/dist/claude-coding-worker/main.js +34 -6
- package/dist/claude-coding-worker/sdk.js +25 -5
- package/dist/claude-coding-worker/tool-socket.d.ts +27 -0
- package/dist/claude-coding-worker/tool-socket.js +111 -0
- package/dist/claude-coding-worker/transcript.d.ts +18 -0
- package/dist/claude-coding-worker/transcript.js +77 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +6 -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 +161 -11
- package/dist/coding/claude-context.d.ts +45 -0
- package/dist/coding/claude-context.js +434 -0
- package/dist/coding/continuation-wording.d.ts +35 -0
- package/dist/coding/continuation-wording.js +73 -0
- package/dist/coding/linked-issue-note.d.ts +23 -0
- package/dist/coding/linked-issue-note.js +47 -0
- package/dist/coding/merge-wording.d.ts +28 -0
- package/dist/coding/merge-wording.js +78 -0
- package/dist/coding/pr-description-note.d.ts +9 -0
- package/dist/coding/pr-description-note.js +32 -0
- package/dist/coding/profile.d.ts +50 -118
- package/dist/coding/profile.js +51 -4
- package/dist/coding/protected-path-wording.d.ts +8 -0
- package/dist/coding/protected-path-wording.js +1 -1
- package/dist/coding/protocol.d.ts +292 -270
- package/dist/coding/protocol.js +322 -15
- package/dist/coding/redact.d.ts +3 -0
- package/dist/coding/redact.js +12 -0
- package/dist/coding/registry/allowlist.d.ts +12 -0
- package/dist/coding/registry/allowlist.js +14 -1
- package/dist/coding/registry/audit-mode.d.ts +153 -0
- package/dist/coding/registry/audit-mode.js +304 -0
- package/dist/coding/registry/lockfile-shared.d.ts +36 -0
- package/dist/coding/registry/lockfile-shared.js +128 -0
- package/dist/coding/registry/lockfiles.js +1 -1
- package/dist/coding/registry/npm-lockfile-formats.d.ts +11 -0
- package/dist/coding/registry/npm-lockfile-formats.js +41 -0
- package/dist/coding/registry/npm-lockfile.d.ts +8 -4
- package/dist/coding/registry/npm-lockfile.js +4 -6
- package/dist/coding/registry/npm.js +78 -8
- package/dist/coding/registry/pnpm-lockfile.d.ts +17 -0
- package/dist/coding/registry/pnpm-lockfile.js +189 -0
- package/dist/coding/registry/pypi-plan.d.ts +50 -0
- package/dist/coding/registry/pypi-plan.js +406 -0
- package/dist/coding/registry/pypi-requirements.d.ts +58 -0
- package/dist/coding/registry/pypi-requirements.js +155 -0
- package/dist/coding/registry/pypi.d.ts +3 -27
- package/dist/coding/registry/pypi.js +40 -154
- package/dist/coding/registry/refusal-hints.d.ts +20 -0
- package/dist/coding/registry/refusal-hints.js +61 -0
- package/dist/coding/registry/refusal-wording.d.ts +11 -0
- package/dist/coding/registry/refusal-wording.js +56 -0
- package/dist/coding/registry/report.d.ts +70 -8
- package/dist/coding/registry/report.js +112 -5
- package/dist/coding/registry/types.d.ts +58 -8
- package/dist/coding/registry/yarn-lockfile.d.ts +30 -0
- package/dist/coding/registry/yarn-lockfile.js +367 -0
- package/dist/coding/services/catalog.d.ts +37 -351
- package/dist/coding/transcript/claude-code.d.ts +13 -0
- package/dist/coding/transcript/claude-code.js +273 -0
- package/dist/coding/transcript/download-protocol.d.ts +30 -0
- package/dist/coding/transcript/download-protocol.js +18 -0
- package/dist/coding/transcript/format.d.ts +20 -0
- package/dist/coding/transcript/format.js +32 -0
- package/dist/coding/transcript/recorder.d.ts +51 -0
- package/dist/coding/transcript/recorder.js +225 -0
- package/dist/coding/transcript/resume/checkout.d.ts +31 -0
- package/dist/coding/transcript/resume/checkout.js +203 -0
- package/dist/coding/transcript/resume/command.d.ts +17 -0
- package/dist/coding/transcript/resume/command.js +176 -0
- package/dist/coding/transcript/resume/download.d.ts +20 -0
- package/dist/coding/transcript/resume/download.js +153 -0
- package/dist/coding/transcript/resume/launch.d.ts +38 -0
- package/dist/coding/transcript/resume/launch.js +128 -0
- package/dist/coding/transcript/resume/session-file.d.ts +13 -0
- package/dist/coding/transcript/resume/session-file.js +79 -0
- package/dist/coding/transcript/resume/stand-in.d.ts +11 -0
- package/dist/coding/transcript/resume/stand-in.js +49 -0
- package/dist/coding-worker/artifact.d.ts +16 -3
- package/dist/coding-worker/artifact.js +5 -3
- package/dist/coding-worker/debug-trace.d.ts +6 -5
- package/dist/coding-worker/debug-trace.js +13 -18
- package/dist/coding-worker/dependency-cache.d.mts +229 -0
- package/dist/coding-worker/dependency-cache.mjs +2123 -0
- package/dist/coding-worker/driver.d.ts +4 -3
- package/dist/coding-worker/driver.js +118 -38
- package/dist/coding-worker/errors.d.ts +1 -0
- package/dist/coding-worker/errors.js +32 -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/task-note.d.ts +11 -0
- package/dist/coding-worker/task-note.js +13 -0
- package/dist/coding-worker/types.d.ts +13 -0
- package/dist/config/dependency-cache.d.ts +23 -0
- package/dist/config/dependency-cache.js +32 -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 +65 -3
- package/dist/config/providers.js +147 -11
- package/dist/config/test-simulators.d.ts +82 -0
- package/dist/config/test-simulators.js +305 -0
- package/dist/core/async-context.d.ts +2 -0
- package/dist/core/async-context.js +12 -0
- package/dist/core/budget-events.d.ts +108 -0
- package/dist/core/budget-events.js +346 -0
- package/dist/core/budget-groups.d.ts +50 -1
- package/dist/core/budget-groups.js +236 -27
- package/dist/core/budget-stopped-head.d.ts +46 -0
- package/dist/core/budget-stopped-head.js +81 -0
- package/dist/core/budget-topup.d.ts +51 -0
- package/dist/core/budget-topup.js +156 -0
- package/dist/core/budget-wording.d.ts +16 -2
- package/dist/core/budget-wording.js +40 -2
- package/dist/core/coding-queue.d.ts +2 -1
- package/dist/core/coding-queue.js +7 -0
- package/dist/core/cron.d.ts +19 -1
- package/dist/core/cron.js +17 -0
- package/dist/core/datastores.js +1 -1
- package/dist/core/db.d.ts +16 -6
- package/dist/core/db.js +12 -10
- package/dist/core/deferred-coding.d.ts +14 -0
- package/dist/core/deferred-coding.js +62 -0
- package/dist/core/delegation-child.d.ts +25 -0
- package/dist/core/delegation-child.js +37 -0
- package/dist/core/dispatch.d.ts +53 -0
- package/dist/core/dispatch.js +167 -57
- package/dist/core/engine-native.js +10 -6
- package/dist/core/error-code.d.ts +38 -0
- package/dist/core/error-code.js +168 -0
- package/dist/core/grants.d.ts +19 -6
- package/dist/core/grants.js +55 -6
- package/dist/core/host-events.d.ts +67 -5
- package/dist/core/host-events.js +571 -37
- package/dist/core/host-status.d.ts +85 -3
- package/dist/core/host-status.js +298 -20
- package/dist/core/issue-bridge.d.ts +50 -6
- package/dist/core/issue-bridge.js +151 -20
- package/dist/core/issue-events.d.ts +1 -1
- package/dist/core/issue-events.js +16 -11
- package/dist/core/issue-status.d.ts +1 -1
- package/dist/core/issue-status.js +68 -29
- package/dist/core/logger.d.ts +5 -0
- package/dist/core/logger.js +14 -1
- package/dist/core/lost-run-children.d.ts +6 -0
- package/dist/core/lost-run-children.js +32 -0
- package/dist/core/mark-ready.d.ts +34 -0
- package/dist/core/mark-ready.js +152 -0
- package/dist/core/markdown-code.d.ts +8 -0
- package/dist/core/markdown-code.js +107 -0
- package/dist/core/memory-tools.js +7 -0
- package/dist/core/merge-order-check.d.ts +130 -0
- package/dist/core/merge-order-check.js +343 -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 +19 -0
- package/dist/core/notification-templates.js +139 -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/pull-request-state-sync.js +2 -2
- package/dist/core/reconciler.d.ts +8 -5
- package/dist/core/reconciler.js +37 -6
- package/dist/core/related-pull-requests.d.ts +59 -7
- package/dist/core/related-pull-requests.js +140 -25
- package/dist/core/reply-parts.d.ts +21 -0
- package/dist/core/reply-parts.js +73 -0
- package/dist/core/repo-access.d.ts +15 -0
- package/dist/core/repo-access.js +66 -0
- package/dist/core/review-fix.d.ts +38 -1
- package/dist/core/review-fix.js +209 -7
- package/dist/core/review-host-checks.d.ts +1 -1
- package/dist/core/review-host-tools.d.ts +23 -0
- package/dist/core/review-host-tools.js +156 -14
- package/dist/core/review-reads.d.ts +55 -0
- package/dist/core/review-reads.js +233 -0
- package/dist/core/run-status.d.ts +12 -0
- package/dist/core/run-status.js +12 -0
- package/dist/core/runner.d.ts +384 -5
- package/dist/core/runner.js +1244 -372
- package/dist/core/schedule-start.d.ts +12 -0
- package/dist/core/schedule-start.js +14 -0
- package/dist/core/secret-broker-config.d.ts +39 -0
- package/dist/core/secret-broker-config.js +112 -0
- package/dist/core/secret-guard.d.ts +46 -0
- package/dist/core/secret-guard.js +104 -0
- package/dist/core/secret-guarded-stores.d.ts +12 -0
- package/dist/core/secret-guarded-stores.js +41 -0
- package/dist/core/secret-spans.d.ts +50 -0
- package/dist/core/secret-spans.js +94 -0
- package/dist/core/secrets.d.ts +39 -4
- package/dist/core/secrets.js +148 -40
- package/dist/core/stop-run.d.ts +46 -0
- package/dist/core/stop-run.js +61 -0
- 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 +94 -0
- package/dist/core/workflow-events.js +48 -0
- package/dist/core/workflow-run-events.d.ts +18 -0
- package/dist/core/workflow-run-events.js +75 -0
- package/dist/core/zod-json-schema.d.ts +4 -0
- package/dist/core/zod-json-schema.js +39 -0
- package/dist/core/zod3-window.d.ts +35 -0
- package/dist/core/zod3-window.js +124 -0
- package/dist/generated/prisma/browser.d.ts +92 -3
- package/dist/generated/prisma/client.d.ts +92 -3
- package/dist/generated/prisma/commonInputTypes.d.ts +364 -54
- package/dist/generated/prisma/enums.d.ts +37 -0
- package/dist/generated/prisma/enums.js +31 -0
- package/dist/generated/prisma/internal/class.d.ts +132 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +1332 -142
- package/dist/generated/prisma/internal/prismaNamespace.js +227 -5
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +234 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +227 -5
- package/dist/generated/prisma/models/Agent.d.ts +464 -7
- package/dist/generated/prisma/models/AgentRepository.d.ts +42 -1
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +301 -1
- package/dist/generated/prisma/models/CodingProxyRequest.d.ts +207 -3
- package/dist/generated/prisma/models/CodingProxySession.d.ts +114 -1
- package/dist/generated/prisma/models/CodingRun.d.ts +1841 -1
- package/dist/generated/prisma/models/DeferredReview.d.ts +78 -1
- package/dist/generated/prisma/models/DependencySnapshot.d.ts +1668 -0
- package/dist/generated/prisma/models/DependencySnapshot.js +1 -0
- package/dist/generated/prisma/models/IssuePullRequest.d.ts +6 -2
- package/dist/generated/prisma/models/LocalReview.d.ts +40 -1
- package/dist/generated/prisma/models/NativeGatewayCall.d.ts +1713 -0
- package/dist/generated/prisma/models/NativeGatewayCall.js +1 -0
- package/dist/generated/prisma/models/NativeGatewaySession.d.ts +1551 -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/RegistryAllowance.d.ts +41 -1
- package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +75 -1
- package/dist/generated/prisma/models/RegistryAuditRecord.d.ts +1364 -0
- package/dist/generated/prisma/models/RegistryAuditRecord.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +1619 -58
- package/dist/generated/prisma/models/RunBudgetEvent.d.ts +1211 -0
- package/dist/generated/prisma/models/RunBudgetEvent.js +1 -0
- package/dist/generated/prisma/models/RunHostCheck.d.ts +36 -1
- package/dist/generated/prisma/models/RunTranscript.d.ts +1339 -0
- package/dist/generated/prisma/models/RunTranscript.js +1 -0
- 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 +12 -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 +2584 -146
- package/dist/import/create.js +49 -3
- package/dist/import/neutral-schema.d.ts +50 -277
- package/dist/import/neutral-schema.js +7 -4
- package/dist/knowledge/check.d.ts +1 -1
- package/dist/knowledge/cli.d.ts +11 -1
- package/dist/knowledge/cli.js +37 -4
- package/dist/knowledge/concept.d.ts +3 -19
- package/dist/knowledge/location.d.ts +31 -0
- package/dist/knowledge/location.js +78 -0
- package/dist/mcp/auth/access.d.ts +11 -2
- package/dist/mcp/auth/access.js +15 -1
- package/dist/mcp/auth/ownership.d.ts +7 -7
- 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/auth/self-hosted/credentials.d.ts +3 -3
- package/dist/mcp/auth/self-hosted/session.d.ts +2 -2
- package/dist/mcp/context.d.ts +6 -0
- package/dist/mcp/host-events/github-ingress.d.ts +25 -1
- package/dist/mcp/host-events/github-ingress.js +70 -1
- package/dist/mcp/index.d.ts +19 -0
- package/dist/mcp/index.js +117 -20
- package/dist/mcp/server.d.ts +13 -0
- package/dist/mcp/server.js +33 -14
- package/dist/mcp/tools/agents.d.ts +4 -0
- package/dist/mcp/tools/agents.js +277 -24
- package/dist/mcp/tools/datastore.js +17 -8
- package/dist/mcp/tools/grants.js +9 -3
- package/dist/mcp/tools/memory.js +11 -1
- package/dist/mcp/tools/notification-channels.d.ts +2 -0
- package/dist/mcp/tools/notification-channels.js +293 -0
- package/dist/mcp/tools/packages.d.ts +2 -0
- package/dist/mcp/tools/packages.js +195 -0
- package/dist/mcp/tools/power.d.ts +2 -0
- package/dist/mcp/tools/power.js +77 -0
- package/dist/mcp/tools/repositories.js +18 -5
- package/dist/mcp/tools/run-access.d.ts +16 -0
- package/dist/mcp/tools/run-access.js +25 -0
- package/dist/mcp/tools/run-transcript-download.d.ts +30 -0
- package/dist/mcp/tools/run-transcript-download.js +129 -0
- package/dist/mcp/tools/run-transcript.d.ts +19 -0
- package/dist/mcp/tools/run-transcript.js +70 -0
- package/dist/mcp/tools/runs.d.ts +0 -6
- package/dist/mcp/tools/runs.js +41 -14
- package/dist/mcp/tools/scheduling.js +3 -1
- 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 +205 -10
- package/dist/mcp/tools/secret-elicitation-server.d.ts +6 -0
- package/dist/mcp/tools/secret-elicitation-server.js +10 -54
- 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 +186 -9
- package/dist/mcp/tools/stop-run.d.ts +2 -0
- package/dist/mcp/tools/stop-run.js +82 -0
- package/dist/mcp/tools/subagents.js +2 -2
- package/dist/mcp/tools/tools.d.ts +2 -2
- package/dist/mcp/tools/tools.js +47 -11
- package/dist/mcp/transport/stdio-link-host.d.ts +20 -0
- package/dist/mcp/transport/stdio-link-host.js +67 -0
- package/dist/mcp/transport/streamable-http.d.ts +13 -0
- package/dist/mcp/transport/streamable-http.js +41 -1
- 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 +110 -0
- package/dist/native-worker/docker-launcher.js +297 -0
- package/dist/native-worker/gateway-server.d.ts +77 -0
- package/dist/native-worker/gateway-server.js +412 -0
- package/dist/native-worker/gateway.d.ts +70 -0
- package/dist/native-worker/gateway.js +263 -0
- package/dist/native-worker/http-server.d.ts +24 -0
- package/dist/native-worker/http-server.js +113 -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 +106 -0
- package/dist/native-worker/kubernetes-launcher.js +375 -0
- package/dist/native-worker/launch.d.ts +62 -0
- package/dist/native-worker/launch.js +49 -0
- package/dist/native-worker/ledger.d.ts +125 -0
- package/dist/native-worker/ledger.js +245 -0
- package/dist/native-worker/loopback.d.ts +12 -0
- package/dist/native-worker/loopback.js +44 -0
- package/dist/native-worker/main.d.ts +8 -0
- package/dist/native-worker/main.js +85 -0
- package/dist/native-worker/protocol.d.ts +296 -0
- package/dist/native-worker/protocol.js +246 -0
- package/dist/native-worker/sandbox-executor.d.ts +83 -0
- package/dist/native-worker/sandbox-executor.js +284 -0
- package/dist/native-worker/stderr-redaction.d.ts +77 -0
- package/dist/native-worker/stderr-redaction.js +237 -0
- package/dist/native-worker/stdio.d.ts +29 -0
- package/dist/native-worker/stdio.js +189 -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 +141 -0
- package/dist/observability/metrics.js +6 -1
- 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 +14 -2
- 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/dependency-cache/eviction.d.ts +29 -0
- package/dist/providers/coding-proxy/dependency-cache/eviction.js +87 -0
- package/dist/providers/coding-proxy/dependency-cache/key.d.ts +33 -0
- package/dist/providers/coding-proxy/dependency-cache/key.js +86 -0
- package/dist/providers/coding-proxy/dependency-cache/model-request-seal.d.ts +18 -0
- package/dist/providers/coding-proxy/dependency-cache/model-request-seal.js +40 -0
- package/dist/providers/coding-proxy/dependency-cache/prisma-store.d.ts +25 -0
- package/dist/providers/coding-proxy/dependency-cache/prisma-store.js +131 -0
- package/dist/providers/coding-proxy/dependency-cache/service.d.ts +70 -0
- package/dist/providers/coding-proxy/dependency-cache/service.js +409 -0
- package/dist/providers/coding-proxy/dependency-cache/store.d.ts +85 -0
- package/dist/providers/coding-proxy/dependency-cache/store.js +95 -0
- package/dist/providers/coding-proxy/hold.d.ts +32 -0
- package/dist/providers/coding-proxy/hold.js +86 -0
- package/dist/providers/coding-proxy/interrupted.d.ts +70 -0
- package/dist/providers/coding-proxy/interrupted.js +104 -0
- package/dist/providers/coding-proxy/memory-ledger.d.ts +15 -2
- package/dist/providers/coding-proxy/memory-ledger.js +84 -8
- package/dist/providers/coding-proxy/metering.d.ts +16 -0
- package/dist/providers/coding-proxy/metering.js +27 -4
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +15 -2
- package/dist/providers/coding-proxy/prisma-ledger.js +179 -67
- package/dist/providers/coding-proxy/proxy.d.ts +14 -1
- package/dist/providers/coding-proxy/proxy.js +166 -14
- package/dist/providers/coding-proxy/registry/audit.d.ts +53 -10
- package/dist/providers/coding-proxy/registry/audit.js +94 -17
- package/dist/providers/coding-proxy/registry/lockfile-parse-worker.d.ts +36 -0
- package/dist/providers/coding-proxy/registry/lockfile-parse-worker.js +67 -0
- package/dist/providers/coding-proxy/registry/lockfile-parse.d.ts +16 -0
- package/dist/providers/coding-proxy/registry/lockfile-parse.js +110 -0
- package/dist/providers/coding-proxy/registry/plan.d.ts +9 -0
- package/dist/providers/coding-proxy/registry/plan.js +201 -48
- package/dist/providers/coding-proxy/registry/prisma-store.d.ts +11 -5
- package/dist/providers/coding-proxy/registry/prisma-store.js +120 -17
- package/dist/providers/coding-proxy/registry/service.d.ts +97 -16
- package/dist/providers/coding-proxy/registry/service.js +579 -112
- package/dist/providers/coding-proxy/registry/store.d.ts +84 -9
- package/dist/providers/coding-proxy/registry/store.js +54 -1
- package/dist/providers/coding-proxy/runtime.d.ts +4 -0
- package/dist/providers/coding-proxy/runtime.js +78 -16
- package/dist/providers/coding-proxy/server.d.ts +6 -1
- package/dist/providers/coding-proxy/server.js +45 -1
- package/dist/providers/coding-proxy/types.d.ts +65 -1
- package/dist/providers/datastore/types.d.ts +5 -0
- package/dist/providers/engine/types.d.ts +36 -2
- package/dist/providers/engine/types.js +39 -0
- package/dist/providers/executor/composition.d.ts +31 -1
- package/dist/providers/executor/composition.js +81 -8
- package/dist/providers/executor/container.d.ts +385 -9
- package/dist/providers/executor/container.js +1234 -99
- package/dist/providers/executor/dbos-status.d.ts +31 -0
- package/dist/providers/executor/dbos-status.js +64 -0
- package/dist/providers/executor/dbos.d.ts +91 -0
- package/dist/providers/executor/dbos.js +319 -26
- package/dist/providers/executor/deferred.d.ts +44 -0
- package/dist/providers/executor/deferred.js +100 -0
- package/dist/providers/executor/in-process.d.ts +9 -1
- package/dist/providers/executor/in-process.js +14 -4
- package/dist/providers/executor/linked-issue-reader.d.ts +16 -0
- package/dist/providers/executor/linked-issue-reader.js +30 -0
- package/dist/providers/executor/routing.d.ts +39 -10
- package/dist/providers/executor/routing.js +77 -32
- package/dist/providers/executor/types.d.ts +25 -0
- package/dist/providers/index.d.ts +2 -1
- package/dist/providers/issue-tracker/types.d.ts +17 -0
- package/dist/providers/issue-tracker/types.js +39 -1
- package/dist/providers/jobs/claude-tool-setup.d.ts +4 -0
- package/dist/providers/jobs/claude-tool-setup.js +5 -2
- package/dist/providers/jobs/docker-isolation.d.ts +8 -0
- package/dist/providers/jobs/docker-isolation.js +17 -0
- package/dist/providers/jobs/docker.d.ts +1 -0
- package/dist/providers/jobs/docker.js +29 -4
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +23 -2
- package/dist/providers/jobs/fake-kubernetes-api.js +45 -1
- package/dist/providers/jobs/kubernetes-api.d.ts +25 -1
- package/dist/providers/jobs/kubernetes-api.js +17 -0
- package/dist/providers/jobs/kubernetes-client.d.ts +10 -1
- package/dist/providers/jobs/kubernetes-client.js +61 -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 +5 -1
- package/dist/providers/jobs/kubernetes-isolation.js +23 -1
- 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 +42 -2
- package/dist/providers/jobs/kubernetes.js +178 -19
- 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 +42 -0
- package/dist/providers/jobs/types.js +13 -1
- package/dist/providers/llm/anthropic.js +5 -1
- package/dist/providers/llm/bedrock.d.ts +0 -12
- package/dist/providers/llm/bedrock.js +4 -0
- package/dist/providers/llm/claude-messages.d.ts +3 -1
- package/dist/providers/llm/claude-messages.js +4 -1
- package/dist/providers/llm/claude-provider.d.ts +2 -2
- package/dist/providers/llm/claude-provider.js +2 -2
- package/dist/providers/llm/openai.d.ts +2 -2
- package/dist/providers/llm/openai.js +6 -2
- package/dist/providers/llm/routing.d.ts +2 -2
- package/dist/providers/llm/routing.js +2 -2
- package/dist/providers/llm/types.d.ts +12 -2
- package/dist/providers/review-host/github-events.d.ts +9 -1
- package/dist/providers/review-host/github-events.js +34 -12
- package/dist/providers/review-host/github.d.ts +39 -2
- package/dist/providers/review-host/github.js +274 -29
- package/dist/providers/review-host/local.d.ts +8 -4
- package/dist/providers/review-host/local.js +49 -31
- package/dist/providers/review-host/patch-pages.d.ts +41 -0
- package/dist/providers/review-host/patch-pages.js +176 -0
- package/dist/providers/review-host/review-format.d.ts +19 -1
- package/dist/providers/review-host/review-format.js +38 -7
- package/dist/providers/review-host/types.d.ts +157 -2
- package/dist/providers/review-host/types.js +8 -0
- package/dist/providers/storage/gcs.d.ts +63 -0
- package/dist/providers/storage/gcs.js +69 -0
- package/dist/providers/storage/index.d.ts +15 -0
- package/dist/providers/storage/index.js +14 -0
- package/dist/providers/storage/local.d.ts +24 -0
- package/dist/providers/storage/local.js +92 -0
- package/dist/providers/storage/meter.d.ts +14 -0
- package/dist/providers/storage/meter.js +34 -0
- package/dist/providers/storage/s3.d.ts +41 -0
- package/dist/providers/storage/s3.js +107 -0
- package/dist/providers/storage/types.d.ts +37 -3
- package/dist/providers/storage/types.js +21 -4
- package/dist/providers/vcs/git.d.ts +33 -2
- package/dist/providers/vcs/git.js +324 -22
- package/dist/providers/vcs/github-remote.d.ts +26 -3
- package/dist/providers/vcs/github-remote.js +124 -14
- package/dist/providers/vcs/github.d.ts +246 -12
- package/dist/providers/vcs/github.js +521 -61
- package/dist/providers/vcs/index.js +8 -1
- package/dist/providers/vcs/pr-description.d.ts +127 -0
- package/dist/providers/vcs/pr-description.js +270 -0
- package/dist/providers/vcs/remote.d.ts +33 -1
- package/dist/providers/vcs/routing.d.ts +4 -2
- package/dist/providers/vcs/routing.js +6 -0
- package/dist/providers/vcs/types.d.ts +80 -3
- package/dist/quickstart/coding-seed.d.ts +21 -3
- package/dist/quickstart/coding-seed.js +283 -3
- package/dist/quickstart/images.d.ts +2 -0
- package/dist/quickstart/images.js +7 -1
- package/dist/quickstart/reviewer-prompt.d.ts +1 -1
- package/dist/quickstart/reviewer-prompt.js +6 -6
- 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/generated/zod.bundle.js +62 -1
- package/dist/sandbox/generated/zod3.bundle.js +5 -0
- package/dist/sandbox/host-functions.d.ts +35 -0
- package/dist/sandbox/host-functions.js +150 -91
- 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 +161 -0
- package/dist/sandbox/tool-capabilities.d.ts +10 -30
- package/dist/sandbox/user-tool.d.ts +14 -0
- package/dist/sandbox/user-tool.js +32 -0
- package/dist/sandbox/zod-params.d.ts +37 -5
- package/dist/sandbox/zod-params.js +181 -26
- package/dist/serve.js +25 -0
- package/dist/viewer/api-schema.d.ts +372 -2454
- package/dist/viewer/api-schema.js +29 -0
- package/dist/viewer/build-schemas.js +10 -3
- 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 +170 -0
- package/dist/wardby-bin.js +10 -0
- package/docs/README.md +7 -0
- package/docs/agent-recipes.md +114 -27
- package/docs/architecture-runtime.md +16 -1
- package/docs/code-review-agents.md +689 -45
- package/docs/coding-agent-setup.md +602 -12
- package/docs/coding-packages.md +1037 -92
- package/docs/coding-services.md +30 -13
- package/docs/coding-worker-byo-images.md +218 -6
- package/docs/coding-worker-isolation.md +173 -44
- package/docs/getting-started-gke.md +332 -129
- package/docs/getting-started-identity-provider.md +31 -11
- package/docs/getting-started.md +14 -4
- package/docs/jira-agents.md +22 -9
- package/docs/knowledge/blob-not-found-semantics.md +41 -0
- package/docs/knowledge/bounded-file-descriptor.md +52 -0
- package/docs/knowledge/delegation-admission-slots.md +52 -0
- package/docs/knowledge/http-dispatcher-bootstrap.md +61 -0
- package/docs/knowledge/import-rename-target.md +52 -0
- package/docs/knowledge/index.md +57 -0
- package/docs/knowledge/log.md +9 -0
- package/docs/knowledge/mcp-cancelled-task-precedence.md +41 -0
- package/docs/knowledge/mcp-http-prompt-discovery.md +41 -0
- package/docs/knowledge/mcp-schema-module-boundary.md +41 -0
- package/docs/knowledge/nullable-tool-uniqueness.md +52 -0
- package/docs/knowledge/overview-cli.md +74 -0
- package/docs/knowledge/overview-coding-proxy.md +72 -0
- package/docs/knowledge/overview-coding.md +72 -0
- package/docs/knowledge/overview-config.md +60 -0
- package/docs/knowledge/overview-core.md +72 -0
- package/docs/knowledge/overview-deploy.md +83 -0
- package/docs/knowledge/overview-mcp.md +61 -0
- package/docs/knowledge/overview-prisma.md +72 -0
- package/docs/knowledge/overview-providers.md +89 -0
- package/docs/knowledge/overview-sandbox.md +83 -0
- package/docs/knowledge/overview-surfaces.md +83 -0
- package/docs/knowledge/private-datastore-shared-boundary.md +54 -0
- package/docs/knowledge/proxy-settlement-projections.md +41 -0
- package/docs/knowledge/run-pricing-first-writer.md +43 -0
- package/docs/knowledge/schedule-claim-transaction.md +41 -0
- package/docs/knowledge/secret-entry-replica-contract.md +52 -0
- package/docs/knowledge/terminal-run-first-writer.md +41 -0
- package/docs/knowledge/transactional-agent-authorization.md +52 -0
- package/docs/knowledge/viewer-notify-write-isolation.md +30 -0
- package/docs/knowledge/warm-input-publication.md +30 -0
- package/docs/knowledge/workspace-staging-failure.md +54 -0
- package/docs/knowledge.md +54 -14
- package/docs/native-sandbox.md +731 -0
- package/docs/observability.md +4 -0
- package/docs/release-verification.md +9 -0
- package/docs/security-deployment.md +146 -14
- package/docs/slack-notifications.md +268 -0
- package/docs/tool-secrets.md +284 -0
- package/docs/viewer-api.md +46 -5
- package/docs/viewer-api.openapi.yml +1404 -0
- package/help/admin-viewer.md +29 -3
- package/help/agent-recipes.md +27 -0
- package/help/architecture-agent.md +11 -6
- package/help/brokered-secrets.md +79 -0
- package/help/build-claude-code-image.md +131 -0
- package/help/build-worker-image.md +6 -7
- package/help/builder-agent.md +19 -6
- package/help/code-review-agents.md +243 -10
- package/help/coding-packages.md +156 -6
- package/help/cost-attribution.md +9 -5
- package/help/creating-agents.md +14 -1
- package/help/dependency-cache.md +143 -0
- package/help/deploy-gke.md +23 -9
- package/help/deployment-targets.md +25 -14
- package/help/errors/budget-exhausted.md +187 -0
- package/help/errors/budget-group-exhausted.md +5 -1
- package/help/errors/coding-provider-not-configured.md +2 -2
- package/help/errors/coding-stream-quota-exceeded.md +33 -0
- package/help/errors/coding-workspace-disk-exceeds-limit.md +39 -0
- package/help/errors/continuation-closed.md +9 -12
- package/help/errors/database-auth-invalid.md +28 -0
- package/help/errors/delegation-conflict.md +24 -0
- package/help/errors/file-list-truncated.md +45 -0
- package/help/errors/full-read-required.md +49 -0
- package/help/errors/github-account-not-allowed.md +29 -0
- package/help/errors/graph-incomplete.md +43 -0
- package/help/errors/graph-limit.md +43 -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/merge-conflict.md +49 -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-error.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-launch-abandoned.md +21 -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 +21 -0
- package/help/errors/native-sandbox-worker-lost.md +20 -0
- package/help/errors/native-sandbox-worker-unready.md +22 -0
- package/help/errors/package-not-allowed-pending.md +52 -0
- package/help/errors/package-not-allowed.md +51 -0
- package/help/errors/package-refusals.md +43 -0
- package/help/errors/patch-cursor-malformed.md +31 -0
- package/help/errors/run-lost.md +39 -0
- package/help/errors/run-transcript-download.md +44 -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-in-value.md +69 -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/errors/target-merge.md +40 -0
- package/help/fix-merge-conflicts.md +73 -0
- package/help/getting-started.md +4 -5
- package/help/github-app-setup.md +132 -0
- package/help/github.md +40 -1
- package/help/identity-and-access.md +7 -1
- package/help/jira.md +23 -6
- package/help/knowledge.md +16 -7
- package/help/local-repositories.md +3 -4
- package/help/low-power-mode.md +131 -0
- package/help/merge-order-check.md +122 -0
- package/help/mobile-app.md +54 -0
- package/help/native-capabilities.md +29 -1
- package/help/native-sandbox.md +150 -0
- package/help/observability.md +8 -0
- package/help/operating-agents.md +5 -0
- package/help/package-audit-mode.md +91 -0
- package/help/related-pull-requests.md +80 -16
- package/help/repo-instructions-and-skills.md +91 -0
- package/help/resume-runs-locally.md +131 -0
- package/help/review-fix-rounds.md +43 -1
- package/help/sharing-agents.md +59 -0
- package/help/slack-notifications.md +76 -0
- package/help/status-comments.md +31 -0
- package/help/stop-run.md +58 -0
- package/help/troubleshooting/budgets.md +28 -0
- package/help/troubleshooting/coding-workers.md +99 -7
- package/package.json +27 -13
- 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/migrations/20261016000000_registry_approved_accepted_advisories/migration.sql +3 -0
- package/prisma/migrations/20261016000001_mark_ready_when_passing/migration.sql +3 -0
- package/prisma/migrations/20261017000000_debug_trace_max_mb/migration.sql +4 -0
- package/prisma/migrations/20261018000000_dependency_cache/migration.sql +53 -0
- package/prisma/migrations/20261019000000_claude_max_output_tokens/migration.sql +5 -0
- package/prisma/migrations/20261021000000_pr_description_hash/migration.sql +4 -0
- package/prisma/migrations/20261022000000_dependency_snapshot_validation_metadata/migration.sql +4 -0
- package/prisma/migrations/20261023000000_package_audit_mode/migration.sql +27 -0
- package/prisma/migrations/20261024000000_budget_events/migration.sql +26 -0
- package/prisma/migrations/20261025000000_cache_aware_holds/migration.sql +9 -0
- package/prisma/migrations/20261026000000_budget_overrun/migration.sql +4 -0
- package/prisma/migrations/20261027000000_coding_run_request/migration.sql +1 -0
- package/prisma/migrations/20261028000000_native_gateway_review_reads/migration.sql +6 -0
- package/prisma/migrations/20261029000000_lead_survives_deploy/migration.sql +6 -0
- package/prisma/migrations/20261030000000_local_review_covered_sha/migration.sql +2 -0
- package/prisma/migrations/20261031000000_coding_run_budget_limit/migration.sql +9 -0
- package/prisma/migrations/20261101000000_run_transcripts/migration.sql +22 -0
- package/prisma/schema.prisma +631 -146
- package/dist/sandbox/generated/zod-to-json-schema.bundle.js +0 -5
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The native sandbox warm pool (phase 6, docs/native-sandbox.md): up to NATIVE_SANDBOX_WARM_POOL_SIZE
|
|
3
|
+
* idle workers, each already isolated (on Kubernetes: attested and probed from inside), holding no
|
|
4
|
+
* run data and no capability. A run claims one with a compare-and-swap on its row
|
|
5
|
+
* (warm-pool-ledger.ts), its input arrives by exec only after the claim, and the worker is
|
|
6
|
+
* destroyed after that one run like any other. With no idle worker, or any doubt about the one it
|
|
7
|
+
* claimed, a run cold-launches exactly as without a pool.
|
|
8
|
+
*
|
|
9
|
+
* Maintenance runs in the process that calls the executor's `launch()`: every tick it reaps rows
|
|
10
|
+
* whose runs have ended, retires idle workers that are too old, of another spec, gone, or over the
|
|
11
|
+
* size (only ever unclaimed ones), and replenishes the deficit. Claims work from any process.
|
|
12
|
+
*/
|
|
13
|
+
import { randomBytes } from "node:crypto";
|
|
14
|
+
import { logger } from "../core/logger.js";
|
|
15
|
+
import { nativeWarmWorkerName } from "./docker-isolation.js";
|
|
16
|
+
const poolLog = logger.child({ module: "native-warm-pool" });
|
|
17
|
+
export const NATIVE_SANDBOX_WARM_DELIVERY_FAILED = "native_sandbox_warm_delivery_failed";
|
|
18
|
+
/** A pool worker waits this much longer than the max age, so a claim never meets one about to exit. */
|
|
19
|
+
const WAIT_MARGIN_MS = 5 * 60_000;
|
|
20
|
+
/** How long another configuration's idle worker is left alone before it is retired (rolling updates). */
|
|
21
|
+
const OTHER_SPEC_GRACE_MS = 60_000;
|
|
22
|
+
/** How long a removed worker that may hold a capability has to be confirmed gone before a cold launch. */
|
|
23
|
+
const GONE_TIMEOUT_MS = 30_000;
|
|
24
|
+
const runEnded = (status) => status !== "pending" && status !== "running";
|
|
25
|
+
/** A ManagedWorkerLauncher that hands a run a warm worker when it can, and cold-launches otherwise. */
|
|
26
|
+
export class PooledWorkerLauncher {
|
|
27
|
+
options;
|
|
28
|
+
networkReadyAtLaunch;
|
|
29
|
+
waitMs;
|
|
30
|
+
specHash;
|
|
31
|
+
timer;
|
|
32
|
+
ticking;
|
|
33
|
+
/** The one pass queued behind a running one: a tick asked for mid-pass sees state from after the ask. */
|
|
34
|
+
queued;
|
|
35
|
+
warming = new Set();
|
|
36
|
+
/** Tokens this process is still starting: never reaped as abandoned, however slow. */
|
|
37
|
+
warmingHere = new Set();
|
|
38
|
+
/** The first pass (orphan sweep included) has run. */
|
|
39
|
+
swept = false;
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.options = options;
|
|
42
|
+
this.networkReadyAtLaunch = options.launcher.networkReadyAtLaunch;
|
|
43
|
+
this.waitMs = options.maxAgeMs + WAIT_MARGIN_MS;
|
|
44
|
+
this.specHash = options.launcher.warmSpecHash(this.waitMs);
|
|
45
|
+
}
|
|
46
|
+
get cold() {
|
|
47
|
+
return this.options.launcher;
|
|
48
|
+
}
|
|
49
|
+
now() {
|
|
50
|
+
return this.options.now?.() ?? Date.now();
|
|
51
|
+
}
|
|
52
|
+
sleep(ms) {
|
|
53
|
+
return (this.options.sleep ?? ((t) => new Promise((resolve) => setTimeout(resolve, t))))(ms);
|
|
54
|
+
}
|
|
55
|
+
async launch(input, launchOptions) {
|
|
56
|
+
const runId = input.runId;
|
|
57
|
+
const claimed = this.options.size > 0
|
|
58
|
+
? await this.options.ledger
|
|
59
|
+
.claim(runId, this.specHash, new Date(this.now() - this.options.maxAgeMs))
|
|
60
|
+
.catch((err) => {
|
|
61
|
+
poolLog.warn({ err, runId }, "warm pool claim failed; launching cold");
|
|
62
|
+
return null;
|
|
63
|
+
})
|
|
64
|
+
: null;
|
|
65
|
+
if (!claimed) {
|
|
66
|
+
if (this.options.size > 0)
|
|
67
|
+
poolLog.info({ runId, event: "miss" }, "no idle warm worker; launching cold");
|
|
68
|
+
this.nudge();
|
|
69
|
+
return this.cold.launch(input, launchOptions);
|
|
70
|
+
}
|
|
71
|
+
const token = claimed.id;
|
|
72
|
+
this.nudge();
|
|
73
|
+
const intact = await this.cold.reattestWarm(token, this.waitMs).catch(() => false);
|
|
74
|
+
if (!intact) {
|
|
75
|
+
poolLog.warn({ runId, worker: claimed.name, event: "fallback" }, "claimed warm worker failed re-attest");
|
|
76
|
+
await this.discard(token);
|
|
77
|
+
return this.cold.launch(input, launchOptions);
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
await this.cold.deliver(token, input);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
// The worker may hold the capability now: it must be gone before another worker gets it.
|
|
84
|
+
poolLog.warn({ err, runId, worker: claimed.name, event: "fallback" }, "warm delivery failed");
|
|
85
|
+
await this.cold.removeWarm(token).catch(() => { });
|
|
86
|
+
const gone = await this.confirmGone(token);
|
|
87
|
+
if (!gone) {
|
|
88
|
+
throw new Error(`${NATIVE_SANDBOX_WARM_DELIVERY_FAILED}: the run's input could not be delivered to its warm worker, which could not be confirmed stopped.`, { cause: err });
|
|
89
|
+
}
|
|
90
|
+
await this.options.ledger.remove(token);
|
|
91
|
+
return this.cold.launch(input, launchOptions);
|
|
92
|
+
}
|
|
93
|
+
if (this.networkReadyAtLaunch === false)
|
|
94
|
+
await this.options.onDelivered?.(runId);
|
|
95
|
+
poolLog.info({ runId, worker: claimed.name, event: "claimed" }, "run claimed a warm worker");
|
|
96
|
+
return this.cold.warmHandle(token);
|
|
97
|
+
}
|
|
98
|
+
async confirmGone(token) {
|
|
99
|
+
const until = this.now() + GONE_TIMEOUT_MS;
|
|
100
|
+
for (;;) {
|
|
101
|
+
const state = await this.cold.inspectWarm(token).catch(() => undefined);
|
|
102
|
+
if (state?.state === "missing")
|
|
103
|
+
return true;
|
|
104
|
+
if (this.now() >= until)
|
|
105
|
+
return false;
|
|
106
|
+
await this.sleep(1_000);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Removes a pool worker and its row. */
|
|
110
|
+
async discard(token) {
|
|
111
|
+
await this.cold.removeWarm(token).catch((err) => {
|
|
112
|
+
poolLog.warn({ err, token }, "removing a warm worker failed; the next tick retries");
|
|
113
|
+
});
|
|
114
|
+
await this.options.ledger.remove(token);
|
|
115
|
+
}
|
|
116
|
+
async claimedToken(runId) {
|
|
117
|
+
return (await this.options.ledger.forRun(runId))?.id;
|
|
118
|
+
}
|
|
119
|
+
handle(runId) {
|
|
120
|
+
const token = this.claimedToken(runId);
|
|
121
|
+
const handle = token.then((t) => (t ? this.cold.warmHandle(t) : this.cold.handle(runId)));
|
|
122
|
+
return {
|
|
123
|
+
exited: handle.then((h) => h.exited),
|
|
124
|
+
kill: () => void handle.then((h) => h.kill()),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async inspect(runId) {
|
|
128
|
+
const token = await this.claimedToken(runId);
|
|
129
|
+
return token ? this.cold.inspectWarm(token) : this.cold.inspect(runId);
|
|
130
|
+
}
|
|
131
|
+
async kill(runId) {
|
|
132
|
+
const token = await this.claimedToken(runId);
|
|
133
|
+
return token ? this.cold.killWarm(token) : this.cold.kill(runId);
|
|
134
|
+
}
|
|
135
|
+
async remove(runId) {
|
|
136
|
+
const token = await this.claimedToken(runId);
|
|
137
|
+
if (!token)
|
|
138
|
+
return this.cold.remove(runId);
|
|
139
|
+
await this.cold.removeWarm(token);
|
|
140
|
+
await this.options.ledger.remove(token);
|
|
141
|
+
}
|
|
142
|
+
listWorkers() {
|
|
143
|
+
return this.cold.listWorkers();
|
|
144
|
+
}
|
|
145
|
+
removeByWorkerName(name) {
|
|
146
|
+
return this.cold.removeByWorkerName(name);
|
|
147
|
+
}
|
|
148
|
+
get resolveGatewayUrl() {
|
|
149
|
+
return this.cold.resolveGatewayUrl?.bind(this.cold);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Starts maintenance: one pass now, then one every interval. At size 0 a pass is a single count
|
|
153
|
+
* of pool rows until one appears: a replica still running a bigger size during a rolling update
|
|
154
|
+
* can refill the pool after this replica's first pass, and only a later pass retires those.
|
|
155
|
+
*/
|
|
156
|
+
async start() {
|
|
157
|
+
await this.tick();
|
|
158
|
+
this.swept = true;
|
|
159
|
+
if (!this.timer) {
|
|
160
|
+
this.timer = setInterval(() => this.nudge(), this.options.intervalMs ?? 15_000);
|
|
161
|
+
this.timer.unref?.();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
stop() {
|
|
165
|
+
if (this.timer)
|
|
166
|
+
clearInterval(this.timer);
|
|
167
|
+
this.timer = undefined;
|
|
168
|
+
}
|
|
169
|
+
/** Asks for a pass soon, only in the process running maintenance: a short-lived CLI never warms workers. */
|
|
170
|
+
nudge() {
|
|
171
|
+
if (this.timer)
|
|
172
|
+
void this.tick();
|
|
173
|
+
}
|
|
174
|
+
/** One maintenance pass; resolves once reaping and retiring are done (warming continues behind it). */
|
|
175
|
+
tick() {
|
|
176
|
+
if (this.ticking) {
|
|
177
|
+
this.queued ??= this.ticking.then(() => {
|
|
178
|
+
this.queued = undefined;
|
|
179
|
+
return this.tick();
|
|
180
|
+
});
|
|
181
|
+
return this.queued;
|
|
182
|
+
}
|
|
183
|
+
this.ticking = this.maintain()
|
|
184
|
+
.catch((err) => poolLog.warn({ err }, "warm pool maintenance failed"))
|
|
185
|
+
.finally(() => {
|
|
186
|
+
this.ticking = undefined;
|
|
187
|
+
});
|
|
188
|
+
return this.ticking;
|
|
189
|
+
}
|
|
190
|
+
/** Resolves once every worker this process is warming has settled (tests, shutdown). */
|
|
191
|
+
async settled() {
|
|
192
|
+
while (this.warming.size > 0)
|
|
193
|
+
await Promise.allSettled([...this.warming]);
|
|
194
|
+
}
|
|
195
|
+
async maintain() {
|
|
196
|
+
const { ledger, size } = this.options;
|
|
197
|
+
// With no pool configured, a pass costs one count until another replica leaves rows behind.
|
|
198
|
+
if (size === 0 && this.swept && (await ledger.count()) === 0)
|
|
199
|
+
return;
|
|
200
|
+
// Listed before the rows are read: a row always exists before its worker, so a worker listed
|
|
201
|
+
// here with no row below is an orphan, never one another replica is still creating.
|
|
202
|
+
const live = new Set(await this.cold.listWarm());
|
|
203
|
+
const rows = await ledger.workers();
|
|
204
|
+
const now = this.now();
|
|
205
|
+
const bornAfter = now - this.options.maxAgeMs;
|
|
206
|
+
const keepIdle = [];
|
|
207
|
+
for (const row of rows) {
|
|
208
|
+
live.delete(row.id);
|
|
209
|
+
if (row.status === "claimed") {
|
|
210
|
+
if (!row.run || runEnded(row.run.status))
|
|
211
|
+
await this.discard(row.id);
|
|
212
|
+
}
|
|
213
|
+
else if (row.status === "warming") {
|
|
214
|
+
if (row.createdAt.getTime() < now - this.options.warmTimeoutMs - 60_000 && !this.warmingHere.has(row.id)) {
|
|
215
|
+
await this.discard(row.id);
|
|
216
|
+
poolLog.info({ worker: row.name, event: "reaped" }, "abandoned warming worker removed");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else if (row.status === "retiring") {
|
|
220
|
+
await this.discard(row.id);
|
|
221
|
+
}
|
|
222
|
+
else if (row.specHash !== this.specHash) {
|
|
223
|
+
// Another configuration's worker is never claimed here. It is retired once it is past the
|
|
224
|
+
// grace, not at once: during a rolling update the old and new replicas would otherwise
|
|
225
|
+
// retire each other's fresh workers and refill their own, over and over.
|
|
226
|
+
if (row.createdAt.getTime() < now - OTHER_SPEC_GRACE_MS)
|
|
227
|
+
await this.retire(row, "stale");
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
const stale = row.createdAt.getTime() <= bornAfter;
|
|
231
|
+
const running = !stale && (await this.cold.inspectWarm(row.id).catch(() => undefined))?.state === "running";
|
|
232
|
+
if (running)
|
|
233
|
+
keepIdle.push(row);
|
|
234
|
+
else
|
|
235
|
+
await this.retire(row, stale ? "stale" : "gone");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Over the size (it was lowered): retire the oldest idle workers first.
|
|
239
|
+
keepIdle.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
240
|
+
for (const row of keepIdle.slice(0, Math.max(0, keepIdle.length - size)))
|
|
241
|
+
await this.retire(row, "excess");
|
|
242
|
+
for (const orphan of live) {
|
|
243
|
+
await this.cold.removeWarm(orphan).catch(() => { });
|
|
244
|
+
poolLog.info({ token: orphan, event: "reaped" }, "orphan warm worker removed");
|
|
245
|
+
}
|
|
246
|
+
if (size > 0)
|
|
247
|
+
await this.replenish();
|
|
248
|
+
}
|
|
249
|
+
async retire(row, reason) {
|
|
250
|
+
// Only an idle worker is retired: the compare-and-swap loses to a claim.
|
|
251
|
+
if (!(await this.options.ledger.retire(row.id)))
|
|
252
|
+
return;
|
|
253
|
+
await this.discard(row.id);
|
|
254
|
+
poolLog.info({ worker: row.name, reason, event: "retired" }, "warm worker retired");
|
|
255
|
+
}
|
|
256
|
+
async replenish() {
|
|
257
|
+
const newToken = this.options.newToken ?? (() => randomBytes(10).toString("hex"));
|
|
258
|
+
const reserved = await this.options.ledger.reserve(this.specHash, this.options.size, newToken, nativeWarmWorkerName);
|
|
259
|
+
for (const { id, name } of reserved) {
|
|
260
|
+
this.warmingHere.add(id);
|
|
261
|
+
const started = this.now();
|
|
262
|
+
const warming = this.cold
|
|
263
|
+
.startWarm(id, this.waitMs)
|
|
264
|
+
.then(async () => {
|
|
265
|
+
if (await this.options.ledger.markIdle(id)) {
|
|
266
|
+
poolLog.info({ worker: name, ms: this.now() - started, event: "warmed" }, "warm worker ready");
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
await this.discard(id);
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
.catch(async (err) => {
|
|
273
|
+
poolLog.warn({ err, worker: name }, "starting a warm worker failed");
|
|
274
|
+
await this.options.ledger.remove(id).catch(() => { });
|
|
275
|
+
})
|
|
276
|
+
.finally(() => {
|
|
277
|
+
this.warmingHere.delete(id);
|
|
278
|
+
this.warming.delete(warming);
|
|
279
|
+
});
|
|
280
|
+
this.warming.add(warming);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The worker side of the native sandbox contract: runs the unchanged
|
|
3
|
+
* NativeEngine with an EngineRunContext whose every outside effect is a
|
|
4
|
+
* gateway call. User tools run here, in QuickJS, with their privileged
|
|
5
|
+
* bridges (fetch, datastore, secrets, console) served by the gateway; built-in
|
|
6
|
+
* tools run on the gateway. Worker-safe: no database, provider adapter, or
|
|
7
|
+
* credential is reachable from this module's imports (worker-imports.test.ts).
|
|
8
|
+
*/
|
|
9
|
+
import type { LlmStreamEvent } from "../providers/llm/types.js";
|
|
10
|
+
import { type GatewayRequest, type WorkerInput } from "./protocol.js";
|
|
11
|
+
/** How the worker reaches the gateway. Errors arrive as GatewayError. */
|
|
12
|
+
export interface GatewayTransport {
|
|
13
|
+
call(request: GatewayRequest): Promise<unknown>;
|
|
14
|
+
stream(request: GatewayRequest): AsyncIterable<LlmStreamEvent>;
|
|
15
|
+
}
|
|
16
|
+
/** Runs one native run to its result and reports it with `finish`. Resolves when done. */
|
|
17
|
+
export declare function runNativeWorker(input: WorkerInput, transport: GatewayTransport): Promise<void>;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The worker side of the native sandbox contract: runs the unchanged
|
|
3
|
+
* NativeEngine with an EngineRunContext whose every outside effect is a
|
|
4
|
+
* gateway call. User tools run here, in QuickJS, with their privileged
|
|
5
|
+
* bridges (fetch, datastore, secrets, console) served by the gateway; built-in
|
|
6
|
+
* tools run on the gateway. Worker-safe: no database, provider adapter, or
|
|
7
|
+
* credential is reachable from this module's imports (worker-imports.test.ts).
|
|
8
|
+
*/
|
|
9
|
+
import { NativeEngine } from "../core/engine-native.js";
|
|
10
|
+
import { computeCost } from "../providers/llm/pricing-core.js";
|
|
11
|
+
import { PRIVILEGED_BRIDGE_NAMES } from "../sandbox/host-functions.js";
|
|
12
|
+
import { runUserToolCall } from "../sandbox/user-tool.js";
|
|
13
|
+
import { rememberSecretValue } from "./stderr-redaction.js";
|
|
14
|
+
import { GatewayError, NATIVE_WORKER_PROTOCOL_VERSION, } from "./protocol.js";
|
|
15
|
+
const isPending = (value) => typeof value === "object" && value !== null && value.pending === true;
|
|
16
|
+
/** Runs one native run to its result and reports it with `finish`. Resolves when done. */
|
|
17
|
+
export async function runNativeWorker(input, transport) {
|
|
18
|
+
let sequence = 0;
|
|
19
|
+
const nextCallId = (kind) => `${kind}-${++sequence}`;
|
|
20
|
+
const request = (method, params) => ({
|
|
21
|
+
v: NATIVE_WORKER_PROTOCOL_VERSION,
|
|
22
|
+
runId: input.runId,
|
|
23
|
+
callId: nextCallId(method),
|
|
24
|
+
method,
|
|
25
|
+
params,
|
|
26
|
+
});
|
|
27
|
+
const call = (method, params) => transport.call(request(method, params));
|
|
28
|
+
// Set when the gateway refuses a model call for budget: the run ends budget_exhausted, as in
|
|
29
|
+
// process, not failed.
|
|
30
|
+
let budgetRefused = false;
|
|
31
|
+
const llm = {
|
|
32
|
+
stream: (req) => (async function* () {
|
|
33
|
+
try {
|
|
34
|
+
yield* transport.stream(request("llm.stream", { request: req }));
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (err instanceof GatewayError && err.code === "budget_exhausted")
|
|
38
|
+
budgetRefused = true;
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
})(),
|
|
42
|
+
countTokens: async (model, messages, tools) => (await call("llm.countTokens", {
|
|
43
|
+
model,
|
|
44
|
+
messages: messages,
|
|
45
|
+
...(tools ? { tools } : {}),
|
|
46
|
+
})),
|
|
47
|
+
// Local and exact: the run's pinned entry, the same computeCost the gateway's adapters use.
|
|
48
|
+
priceUsd: (_model, usage) => computeCost(input.pricing, usage),
|
|
49
|
+
};
|
|
50
|
+
const builtins = new Set(input.builtinTools);
|
|
51
|
+
let invocations = 0;
|
|
52
|
+
const remoteHost = (tool) => {
|
|
53
|
+
const invocation = `t${++invocations}`;
|
|
54
|
+
return Object.fromEntries(PRIVILEGED_BRIDGE_NAMES.map((bridge) => [
|
|
55
|
+
bridge,
|
|
56
|
+
async (argsJson) => {
|
|
57
|
+
try {
|
|
58
|
+
const result = await call("host.call", { tool, invocation, bridge, argsJson });
|
|
59
|
+
// A secret value now lives in this process: keep it out of everything it writes to stderr.
|
|
60
|
+
if (bridge === "__bridge_secretsGet" && typeof result === "string")
|
|
61
|
+
rememberSecretValue(result);
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
// A bridge failure reaches the tool as the same error message it would see in-process.
|
|
66
|
+
if (err instanceof GatewayError && err.code === "bridge_error")
|
|
67
|
+
throw new Error(err.message, { cause: err });
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
]));
|
|
72
|
+
};
|
|
73
|
+
const runSandboxTool = async (name, argsJson) => {
|
|
74
|
+
if (builtins.has(name)) {
|
|
75
|
+
// A long-running built-in (a delegation) answers `pending`; ask again with the same callId,
|
|
76
|
+
// which the gateway resumes wherever it got to — on any replica.
|
|
77
|
+
const builtinRequest = request("builtin.call", { name, argsJson });
|
|
78
|
+
let result = await transport.call(builtinRequest);
|
|
79
|
+
while (isPending(result))
|
|
80
|
+
result = await transport.call(builtinRequest);
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
const tool = input.userTools[name];
|
|
84
|
+
if (!tool) {
|
|
85
|
+
return JSON.stringify({ error: "unknown_tool", message: `No tool named "${name}" is attached to this agent.` });
|
|
86
|
+
}
|
|
87
|
+
const host = remoteHost(name);
|
|
88
|
+
return runUserToolCall(tool, argsJson, () => host);
|
|
89
|
+
};
|
|
90
|
+
let textSeq = 0;
|
|
91
|
+
let textSent = Promise.resolve();
|
|
92
|
+
const concurrent = new Set(input.runsConcurrently);
|
|
93
|
+
const ctx = {
|
|
94
|
+
agent: input.agent,
|
|
95
|
+
tools: input.tools,
|
|
96
|
+
providers: { llm },
|
|
97
|
+
runSandboxTool,
|
|
98
|
+
...(concurrent.size > 0 ? { runsConcurrently: (toolName) => concurrent.has(toolName) } : {}),
|
|
99
|
+
onText: (delta) => {
|
|
100
|
+
// One text call at a time: the gateway admits each call asynchronously, so concurrent ones
|
|
101
|
+
// can arrive out of order, and it drops any delta older than the last one it delivered.
|
|
102
|
+
const seq = textSeq++;
|
|
103
|
+
textSent = textSent.then(() => call("text", { seq, delta }).then(() => { }, () => {
|
|
104
|
+
// Streamed text is for live observers only; the final text travels with `finish`.
|
|
105
|
+
}));
|
|
106
|
+
},
|
|
107
|
+
onProgress: async (progress) => {
|
|
108
|
+
await call("progress", { turns: progress.turns, usage: progress.usage });
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
let result;
|
|
112
|
+
try {
|
|
113
|
+
result = await new NativeEngine().run(ctx);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
// A gateway failure (a delegation that could not be created, say) ends the run as failed with
|
|
117
|
+
// its code, not as an opaque worker exit. run_not_drivable means the run already ended.
|
|
118
|
+
if (!(err instanceof GatewayError) || err.code === "run_not_drivable")
|
|
119
|
+
throw err;
|
|
120
|
+
await textSent;
|
|
121
|
+
await call("finish", {
|
|
122
|
+
status: "failed",
|
|
123
|
+
finalText: "",
|
|
124
|
+
turns: 0,
|
|
125
|
+
// `internal` wraps raw exception text on some transports, so only its code (and the cause
|
|
126
|
+
// code the gateway carried with it, if any) is persisted, never its message.
|
|
127
|
+
error: err.code === "internal"
|
|
128
|
+
? `native_sandbox_gateway_error: internal${err.causeCode ? `: ${err.causeCode}` : ""}`
|
|
129
|
+
: `native_sandbox_gateway_error: ${err.code}: ${err.message.slice(0, 1024)}`,
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Live text reaches observers before the run is reported finished.
|
|
134
|
+
await textSent;
|
|
135
|
+
await call("finish", {
|
|
136
|
+
status: budgetRefused && result.status === "failed" ? "budget_exhausted" : result.status,
|
|
137
|
+
finalText: result.finalText,
|
|
138
|
+
turns: result.turns,
|
|
139
|
+
...(result.error !== undefined ? { error: result.error } : {}),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
@@ -133,8 +133,13 @@ export class WardbyMetrics {
|
|
|
133
133
|
}
|
|
134
134
|
observeProxyAudit = (event) => {
|
|
135
135
|
this.proxyAuditEvents.inc({ event: event.type });
|
|
136
|
-
|
|
136
|
+
// A completed reply repeats its request's hold (for the overshoot audit): count it once, when held.
|
|
137
|
+
// A refused request carries the hold that did not fit, which was never reserved.
|
|
138
|
+
if (event.reservationUsd !== undefined &&
|
|
139
|
+
event.type !== "response.completed" &&
|
|
140
|
+
event.type !== "request.rejected") {
|
|
137
141
|
this.proxyBudgetReserved.inc(event.reservationUsd);
|
|
142
|
+
}
|
|
138
143
|
if (event.type !== "response.completed")
|
|
139
144
|
return;
|
|
140
145
|
if (event.costUsd !== undefined)
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import type { MobileConfig } from "../../config/mobile.js";
|
|
1
2
|
export interface SelfHostedAuthConfig {
|
|
2
3
|
canonicalUri: string;
|
|
3
4
|
signingKey: string;
|
|
4
5
|
credentialHashKey: string;
|
|
5
6
|
maxClients?: number;
|
|
7
|
+
/** Mobile sign-in: its one private-use redirect is accepted only while enabled. */
|
|
8
|
+
mobile?: MobileConfig;
|
|
6
9
|
}
|
|
7
10
|
export interface RegisterClientParams {
|
|
8
11
|
redirectUris: string[];
|
|
@@ -20,11 +20,13 @@ export function buildAuthProvider(kind, config, db) {
|
|
|
20
20
|
}
|
|
21
21
|
if (config.credentialHashKey.toLowerCase() === process.env.SECRET_APP_KEY?.toLowerCase())
|
|
22
22
|
throw new Error("Credential and encryption keys must be distinct.");
|
|
23
|
+
logger.info({ module: "auth", mobileEnabled: config.mobile.enabled, mobileRedirectUri: config.mobile.redirectUri }, "self-hosted OAuth mobile sign-in");
|
|
23
24
|
return new SelfHostedAuthProvider({
|
|
24
25
|
canonicalUri: config.audience,
|
|
25
26
|
signingKey: config.signingKey,
|
|
26
27
|
credentialHashKey: config.credentialHashKey,
|
|
27
28
|
maxClients: config.maxClients,
|
|
29
|
+
mobile: config.mobile,
|
|
28
30
|
}, db);
|
|
29
31
|
}
|
|
30
32
|
return new DelegatingAuthProvider(config);
|
|
@@ -11,6 +11,10 @@ export type { AuthorizeParams, RegisterClientParams, SelfHostedAuthConfig, Token
|
|
|
11
11
|
* scheme, host, path, query — must match exactly, with no userinfo or fragment.
|
|
12
12
|
*/
|
|
13
13
|
export declare function redirectMatches(registered: readonly string[], requested: string): boolean;
|
|
14
|
+
/** The mobile app's redirect while WARDBY_MOBILE_ENABLED is off. The message is safe to show to the client. */
|
|
15
|
+
export declare class MobileDisabledError extends Error {
|
|
16
|
+
constructor();
|
|
17
|
+
}
|
|
14
18
|
export declare class SelfHostedAuthProvider implements AuthProvider {
|
|
15
19
|
readonly db: PrismaClient;
|
|
16
20
|
readonly credentials: Credentials;
|
|
@@ -49,9 +53,9 @@ export declare class SelfHostedAuthProvider implements AuthProvider {
|
|
|
49
53
|
createdAt: Date;
|
|
50
54
|
id: string;
|
|
51
55
|
state: string | null;
|
|
52
|
-
clientId: string;
|
|
53
|
-
expiresAt: Date;
|
|
54
56
|
resource: string;
|
|
57
|
+
expiresAt: Date;
|
|
58
|
+
clientId: string;
|
|
55
59
|
consumedAt: Date | null;
|
|
56
60
|
redirectUri: string;
|
|
57
61
|
codeChallenge: string;
|
|
@@ -74,6 +78,14 @@ export declare class SelfHostedAuthProvider implements AuthProvider {
|
|
|
74
78
|
private withinCanonical;
|
|
75
79
|
private exchange;
|
|
76
80
|
private rotate;
|
|
81
|
+
/**
|
|
82
|
+
* Whether `redirectUri` is a mobile (non-web, non-loopback) redirect this
|
|
83
|
+
* server refuses now. Only the currently configured mobile redirect, while
|
|
84
|
+
* mobile sign-in is enabled, passes - so a client registered under an earlier
|
|
85
|
+
* WARDBY_MOBILE_REDIRECT_URI is refused whether mobile is on or off.
|
|
86
|
+
* Web and loopback redirects are never affected.
|
|
87
|
+
*/
|
|
88
|
+
private refusedRedirect;
|
|
77
89
|
private issue;
|
|
78
90
|
private revokeFamily;
|
|
79
91
|
revoke(token: string, clientId: string): Promise<void>;
|
|
@@ -7,6 +7,7 @@ import { SCOPES_SUPPORTED, limitScope } from "../../mcp/auth/resource-server.js"
|
|
|
7
7
|
import { canonicalUrl } from "../../mcp/transport/http-limits.js";
|
|
8
8
|
import { Credentials, DAY, decodeKey, lockUser } from "../../mcp/auth/self-hosted/credentials.js";
|
|
9
9
|
import { Sessions } from "../../mcp/auth/self-hosted/session.js";
|
|
10
|
+
import { MOBILE_DISABLED } from "../../config/mobile.js";
|
|
10
11
|
const LOOPBACK_HOSTS = ["localhost", "127.0.0.1", "[::1]"];
|
|
11
12
|
/**
|
|
12
13
|
* Whether `requested` is one of a client's registered redirect URIs. A native
|
|
@@ -43,7 +44,21 @@ export function redirectMatches(registered, requested) {
|
|
|
43
44
|
}
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
|
-
|
|
47
|
+
/** The mobile app's redirect while WARDBY_MOBILE_ENABLED is off. The message is safe to show to the client. */
|
|
48
|
+
export class MobileDisabledError extends Error {
|
|
49
|
+
constructor() {
|
|
50
|
+
super(`${MOBILE_DISABLED}: mobile sign-in is not enabled on this server (WARDBY_MOBILE_ENABLED).`);
|
|
51
|
+
this.name = "MobileDisabledError";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A registrable redirect: https, loopback http, or exactly the configured
|
|
56
|
+
* mobile redirect (RFC 8252 §7.1). This checks format only; whether mobile
|
|
57
|
+
* sign-in is enabled is enforced in each flow.
|
|
58
|
+
*/
|
|
59
|
+
function validRedirect(value, mobileRedirect) {
|
|
60
|
+
if (mobileRedirect !== undefined && value === mobileRedirect)
|
|
61
|
+
return true;
|
|
47
62
|
try {
|
|
48
63
|
const u = new URL(value);
|
|
49
64
|
return (!value.includes("#") &&
|
|
@@ -55,9 +70,16 @@ function validRedirect(value) {
|
|
|
55
70
|
return false;
|
|
56
71
|
}
|
|
57
72
|
}
|
|
58
|
-
const registration = z
|
|
73
|
+
const registration = (mobileRedirect) => z
|
|
59
74
|
.object({
|
|
60
|
-
redirectUris: z
|
|
75
|
+
redirectUris: z
|
|
76
|
+
.array(z
|
|
77
|
+
.string()
|
|
78
|
+
.min(1)
|
|
79
|
+
.max(2048)
|
|
80
|
+
.refine((value) => validRedirect(value, mobileRedirect)))
|
|
81
|
+
.min(1)
|
|
82
|
+
.max(10),
|
|
61
83
|
clientName: z.string().max(100).optional(),
|
|
62
84
|
grantTypes: z
|
|
63
85
|
.array(z.enum(["authorization_code", "refresh_token"]))
|
|
@@ -114,7 +136,9 @@ export class SelfHostedAuthProvider {
|
|
|
114
136
|
};
|
|
115
137
|
}
|
|
116
138
|
async registerClient(params) {
|
|
117
|
-
const p = registration.parse(params);
|
|
139
|
+
const p = registration(this.config.mobile?.redirectUri).parse(params);
|
|
140
|
+
if (p.redirectUris.some((uri) => this.refusedRedirect(uri)))
|
|
141
|
+
throw new MobileDisabledError();
|
|
118
142
|
return this.db.$transaction(async (tx) => {
|
|
119
143
|
await tx.$queryRawUnsafe("SELECT 1 AS locked FROM pg_advisory_xact_lock(7412901)");
|
|
120
144
|
if ((await tx.oAuthClient.count()) >= (this.config.maxClients ?? 1000))
|
|
@@ -143,6 +167,8 @@ export class SelfHostedAuthProvider {
|
|
|
143
167
|
if (!redirectMatches(metadata?.redirect_uris ?? [], p.redirectUri) ||
|
|
144
168
|
!metadata?.grant_types?.includes("authorization_code"))
|
|
145
169
|
throw new Error("Invalid client or redirect.");
|
|
170
|
+
if (this.refusedRedirect(p.redirectUri))
|
|
171
|
+
throw new MobileDisabledError();
|
|
146
172
|
const scope = limitScope(p.scope, SCOPES_SUPPORTED);
|
|
147
173
|
const request = await this.db.oAuthAuthorizationRequest.create({
|
|
148
174
|
data: {
|
|
@@ -258,6 +284,8 @@ export class SelfHostedAuthProvider {
|
|
|
258
284
|
if (!/^[A-Za-z0-9._~-]{43,128}$/.test(p.codeVerifier) ||
|
|
259
285
|
createHash("sha256").update(p.codeVerifier).digest("base64url") !== code.codeChallenge)
|
|
260
286
|
throw new Error("Invalid grant: PKCE verifier mismatch.");
|
|
287
|
+
if (this.refusedRedirect(code.redirectUri))
|
|
288
|
+
throw new MobileDisabledError();
|
|
261
289
|
return this.db.$transaction(async (tx) => {
|
|
262
290
|
const user = await lockUser(tx, code.userId);
|
|
263
291
|
const consumed = await tx.oAuthAuthorizationCode.updateMany({
|
|
@@ -305,6 +333,13 @@ export class SelfHostedAuthProvider {
|
|
|
305
333
|
await this.revokeFamily(tx, grant.familyId);
|
|
306
334
|
return "refresh token reused; family revoked";
|
|
307
335
|
}
|
|
336
|
+
// Not token reuse: refuse without revoking, so the family works again
|
|
337
|
+
// once mobile sign-in is re-enabled. This checks every redirect the
|
|
338
|
+
// client registered, not just the one its code used: a client registered
|
|
339
|
+
// with both an https and a mobile redirect is refused while mobile is off.
|
|
340
|
+
const redirects = grant.family.client.metadata.redirect_uris ?? [];
|
|
341
|
+
if (redirects.some((uri) => this.refusedRedirect(uri)))
|
|
342
|
+
return MOBILE_DISABLED;
|
|
308
343
|
const result = await this.issue(tx, grant.family, user.principal.subject);
|
|
309
344
|
await tx.oAuthGrant.update({
|
|
310
345
|
where: { id: grant.id },
|
|
@@ -312,10 +347,25 @@ export class SelfHostedAuthProvider {
|
|
|
312
347
|
});
|
|
313
348
|
return result;
|
|
314
349
|
});
|
|
350
|
+
if (result === MOBILE_DISABLED)
|
|
351
|
+
throw new MobileDisabledError();
|
|
315
352
|
if (typeof result === "string")
|
|
316
353
|
throw new Error(`Invalid grant: ${result}.`);
|
|
317
354
|
return result;
|
|
318
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* Whether `redirectUri` is a mobile (non-web, non-loopback) redirect this
|
|
358
|
+
* server refuses now. Only the currently configured mobile redirect, while
|
|
359
|
+
* mobile sign-in is enabled, passes - so a client registered under an earlier
|
|
360
|
+
* WARDBY_MOBILE_REDIRECT_URI is refused whether mobile is on or off.
|
|
361
|
+
* Web and loopback redirects are never affected.
|
|
362
|
+
*/
|
|
363
|
+
refusedRedirect(redirectUri) {
|
|
364
|
+
if (validRedirect(redirectUri))
|
|
365
|
+
return false;
|
|
366
|
+
const mobile = this.config.mobile;
|
|
367
|
+
return !(mobile?.enabled === true && redirectUri === mobile.redirectUri);
|
|
368
|
+
}
|
|
319
369
|
async issue(tx, family, subject) {
|
|
320
370
|
const refresh = this.credentials.create("rvr");
|
|
321
371
|
await tx.oAuthGrant.create({
|