@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,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Executor for sandbox-mode native runs (docs/native-sandbox.md). Phase 1's
|
|
3
|
+
* RoutingExecutor sends every run whose snapshot says `sandbox` here.
|
|
4
|
+
*
|
|
5
|
+
* - start: creates the run's gateway session (pinning the run and recording
|
|
6
|
+
* `native-sandbox` as its backend before any worker exists), launches the
|
|
7
|
+
* worker, and returns. Whichever gateway replica serves the worker's
|
|
8
|
+
* `finish` makes the terminal write; this executor only watches for a
|
|
9
|
+
* worker that exits without one, and for its deadline.
|
|
10
|
+
* - stop: records the run cancelled, ends the session (every further gateway
|
|
11
|
+
* call is refused, so the worker exits at its next one), gives the worker a
|
|
12
|
+
* short grace to exit, then kills and removes it (a warm pool worker is
|
|
13
|
+
* discarded, never reused), and stops its open delegations.
|
|
14
|
+
* - recover: answers the reconciler from the worker's container state, and
|
|
15
|
+
* never launches one.
|
|
16
|
+
* - launch: the janitor removes workers whose runs have ended.
|
|
17
|
+
*/
|
|
18
|
+
import { logger } from "../core/logger.js";
|
|
19
|
+
import { failNativeRun, nativeRunIntegrations, RunCancelledError, NATIVE_SANDBOX_BACKEND, } from "../core/runner.js";
|
|
20
|
+
import { nativeRunLabel } from "./docker-isolation.js";
|
|
21
|
+
import { HEARTBEAT_TIMEOUT_MS } from "../core/timing.js";
|
|
22
|
+
import { startSandboxRun } from "./launch.js";
|
|
23
|
+
import { NATIVE_SANDBOX_CAPACITY } from "./kubernetes-launcher.js";
|
|
24
|
+
import { PrismaGatewayLedger } from "./ledger.js";
|
|
25
|
+
const executorLog = logger.child({ module: "native-sandbox-executor" });
|
|
26
|
+
const isUniqueViolation = (err) => typeof err === "object" && err !== null && err.code === "P2002";
|
|
27
|
+
/** A stopped sandbox run's worker exits at its next (refused) gateway call; past this it is killed. */
|
|
28
|
+
export const NATIVE_SANDBOX_STOP_GRACE_MS = 5_000;
|
|
29
|
+
export const NATIVE_SANDBOX_WORKER_EXITED = "native_sandbox_worker_exited";
|
|
30
|
+
export const NATIVE_SANDBOX_DEADLINE = "native_sandbox_deadline_exceeded";
|
|
31
|
+
export class NativeSandboxExecutor {
|
|
32
|
+
options;
|
|
33
|
+
ledger;
|
|
34
|
+
watching = new Map();
|
|
35
|
+
deadlineKilled = new Set();
|
|
36
|
+
constructor(options) {
|
|
37
|
+
this.options = options;
|
|
38
|
+
this.ledger = new PrismaGatewayLedger(options.db);
|
|
39
|
+
}
|
|
40
|
+
now() {
|
|
41
|
+
return this.options.now?.() ?? Date.now();
|
|
42
|
+
}
|
|
43
|
+
finishContext(runId) {
|
|
44
|
+
const { db, providers } = this.options;
|
|
45
|
+
return { runId, db, providers, ...nativeRunIntegrations(providers, db) };
|
|
46
|
+
}
|
|
47
|
+
async start(runId) {
|
|
48
|
+
const { db, providers, launcher, gatewayUrl } = this.options;
|
|
49
|
+
// A second start (a retried dispatch) attaches to the run's worker; it never launches another.
|
|
50
|
+
const existing = await db.nativeGatewaySession.findUnique({ where: { runId }, select: { deadlineAt: true } });
|
|
51
|
+
if (existing) {
|
|
52
|
+
this.watch(runId, launcher.handle(runId), existing.deadlineAt);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Fail fast rather than queue (a warm pool's job): every replica counts the same sessions, and a
|
|
56
|
+
// near-simultaneous start that overshoots by one is still bounded by the cluster's ResourceQuota.
|
|
57
|
+
const cap = this.options.maxConcurrent;
|
|
58
|
+
if (cap !== undefined && (await db.nativeGatewaySession.count({ where: { status: "active" } })) >= cap) {
|
|
59
|
+
throw new Error(`${NATIVE_SANDBOX_CAPACITY}: ${cap} sandbox runs are already active (NATIVE_SANDBOX_MAX_CONCURRENT).`);
|
|
60
|
+
}
|
|
61
|
+
let outcome;
|
|
62
|
+
try {
|
|
63
|
+
const url = launcher.resolveGatewayUrl ? await launcher.resolveGatewayUrl() : gatewayUrl;
|
|
64
|
+
// A cold launch (a low-power gateway wake, a sandbox node scale-up) can outlast the reconciler's
|
|
65
|
+
// heartbeat timeout, and the worker has no pod to find yet: beat for it until it exists. The
|
|
66
|
+
// launch's own timeouts bound this (wake, pod ready, isolation proof), each failing the run.
|
|
67
|
+
outcome = await this.whileHeartbeating(runId, () => startSandboxRun({
|
|
68
|
+
runId,
|
|
69
|
+
providers,
|
|
70
|
+
db,
|
|
71
|
+
gatewayUrl: url,
|
|
72
|
+
launcher,
|
|
73
|
+
stillWanted: () => this.stillWanted(runId),
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
// A concurrent start won the race for the run's one session: attach to its worker.
|
|
78
|
+
if (!isUniqueViolation(err)) {
|
|
79
|
+
// The launch failed after the session was created (e.g. isolation could not be proven):
|
|
80
|
+
// end the session so nothing can use its capability; the caller fails the run.
|
|
81
|
+
await this.endSession(runId, "cancelled").catch(() => { });
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
const raced = await db.nativeGatewaySession.findUniqueOrThrow({ where: { runId }, select: { deadlineAt: true } });
|
|
85
|
+
this.watch(runId, launcher.handle(runId), raced.deadlineAt);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (outcome.kind === "ended")
|
|
89
|
+
return;
|
|
90
|
+
const session = await db.nativeGatewaySession.findUniqueOrThrow({
|
|
91
|
+
where: { id: outcome.sessionId },
|
|
92
|
+
select: { deadlineAt: true },
|
|
93
|
+
});
|
|
94
|
+
this.watch(runId, outcome.handle, session.deadlineAt);
|
|
95
|
+
}
|
|
96
|
+
/** Refreshes a still-live run's heartbeat while `work` runs. */
|
|
97
|
+
async whileHeartbeating(runId, work) {
|
|
98
|
+
const beat = () => this.options.db.run
|
|
99
|
+
.updateMany({
|
|
100
|
+
where: { id: runId, status: { in: ["pending", "running"] } },
|
|
101
|
+
data: { heartbeatAt: new Date(this.now()) },
|
|
102
|
+
})
|
|
103
|
+
.catch((err) => executorLog.warn({ err, runId }, "could not refresh a launching run's heartbeat"));
|
|
104
|
+
// At most one beat in flight: a tick while one is pending is skipped. Overlapping beats could
|
|
105
|
+
// commit out of order, and only the last one sent would be awaited below.
|
|
106
|
+
let inFlight;
|
|
107
|
+
const timer = setInterval(() => {
|
|
108
|
+
inFlight ??= beat().finally(() => {
|
|
109
|
+
inFlight = undefined;
|
|
110
|
+
});
|
|
111
|
+
}, this.options.heartbeatIntervalMs ?? Math.floor(HEARTBEAT_TIMEOUT_MS / 3));
|
|
112
|
+
// Never hold the process open for a heartbeat: shutdown should not wait on one.
|
|
113
|
+
timer.unref?.();
|
|
114
|
+
try {
|
|
115
|
+
return await work();
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
clearInterval(timer);
|
|
119
|
+
// The beat in flight, if any, lands before the launch returns, never after the worker's own beats.
|
|
120
|
+
await inFlight;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** The run is still live and its session open: a worker launched now would have work to do. */
|
|
124
|
+
async stillWanted(runId) {
|
|
125
|
+
const { db } = this.options;
|
|
126
|
+
const [run, session] = await Promise.all([
|
|
127
|
+
db.run.findUnique({ where: { id: runId }, select: { status: true } }),
|
|
128
|
+
db.nativeGatewaySession.findUnique({ where: { runId }, select: { status: true } }),
|
|
129
|
+
]);
|
|
130
|
+
return !!run && (run.status === "pending" || run.status === "running") && session?.status === "active";
|
|
131
|
+
}
|
|
132
|
+
/** Watches one worker in this process: its exit, and its deadline. */
|
|
133
|
+
watch(runId, handle, deadlineAt) {
|
|
134
|
+
if (this.watching.has(runId))
|
|
135
|
+
return;
|
|
136
|
+
const msLeft = deadlineAt.getTime() - this.now();
|
|
137
|
+
const timer = msLeft > 0
|
|
138
|
+
? setTimeout(() => {
|
|
139
|
+
this.deadlineKilled.add(runId);
|
|
140
|
+
handle.kill();
|
|
141
|
+
}, msLeft)
|
|
142
|
+
: undefined;
|
|
143
|
+
if (msLeft <= 0) {
|
|
144
|
+
this.deadlineKilled.add(runId);
|
|
145
|
+
handle.kill();
|
|
146
|
+
}
|
|
147
|
+
this.watching.set(runId, timer);
|
|
148
|
+
void handle.exited
|
|
149
|
+
.then((code) => this.onWorkerExit(runId, code))
|
|
150
|
+
.catch((err) => executorLog.error({ err, runId }, "watching a native sandbox worker failed"));
|
|
151
|
+
}
|
|
152
|
+
async onWorkerExit(runId, exitCode) {
|
|
153
|
+
const timer = this.watching.get(runId);
|
|
154
|
+
if (timer)
|
|
155
|
+
clearTimeout(timer);
|
|
156
|
+
this.watching.delete(runId);
|
|
157
|
+
const { db } = this.options;
|
|
158
|
+
const run = await db.run.findUnique({ where: { id: runId }, select: { status: true } });
|
|
159
|
+
if (run && (run.status === "pending" || run.status === "running")) {
|
|
160
|
+
// The worker ended without its `finish`: nothing will write a result for it, so this does.
|
|
161
|
+
const reason = this.deadlineKilled.has(runId)
|
|
162
|
+
? `${NATIVE_SANDBOX_DEADLINE}: the sandbox run passed its deadline and its worker was stopped.`
|
|
163
|
+
: `${NATIVE_SANDBOX_WORKER_EXITED}: the sandbox worker exited (code ${exitCode ?? "unknown"}) without a result.`;
|
|
164
|
+
await failNativeRun(this.finishContext(runId), new Error(reason));
|
|
165
|
+
}
|
|
166
|
+
this.deadlineKilled.delete(runId);
|
|
167
|
+
await this.endSession(runId, "finished");
|
|
168
|
+
await this.options.launcher.remove(runId).catch((err) => {
|
|
169
|
+
executorLog.warn({ err, runId }, "removing a native sandbox worker failed; the janitor will retry");
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async endSession(runId, status) {
|
|
173
|
+
const session = await this.options.db.nativeGatewaySession.findUnique({ where: { runId }, select: { id: true } });
|
|
174
|
+
if (session)
|
|
175
|
+
await this.ledger.endSession(session.id, status);
|
|
176
|
+
return session?.id;
|
|
177
|
+
}
|
|
178
|
+
async stop(runId, reason) {
|
|
179
|
+
// Recorded first, before the session ends or the worker is killed: either of those makes the
|
|
180
|
+
// worker exit, and its exit watcher must then find the run already ended (and leave it
|
|
181
|
+
// cancelled) rather than record the exit as a failure.
|
|
182
|
+
await failNativeRun(this.finishContext(runId), new RunCancelledError(reason ?? "The run was cancelled."));
|
|
183
|
+
// The stop reaches the worker through the gateway: with its run cancelled and its session ended,
|
|
184
|
+
// its next call (a model turn, a tool, its result) is refused and it exits on its own. It gets a
|
|
185
|
+
// bounded grace for that, then is killed either way.
|
|
186
|
+
const sessionId = await this.endSession(runId, "cancelled");
|
|
187
|
+
if (!(await this.awaitWorkerExit(runId))) {
|
|
188
|
+
executorLog.info({ runId }, "a stopped sandbox run's worker did not exit within its grace; killing it");
|
|
189
|
+
await this.options.launcher.kill(runId).catch(() => { });
|
|
190
|
+
}
|
|
191
|
+
if (sessionId) {
|
|
192
|
+
// Its delegations' children belong to the server's executor; a child left running would
|
|
193
|
+
// spend on behalf of a parent that no longer waits for it.
|
|
194
|
+
for (const child of await this.ledger.openDelegationChildren(sessionId)) {
|
|
195
|
+
await this.options.providers.executor?.stop(child, "parent run cancelled").catch((err) => {
|
|
196
|
+
executorLog.warn({ err, runId, child }, "failed to stop a cancelled sandbox run's child");
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Removed, never returned to a warm pool: a claimed pool worker is discarded with its row.
|
|
201
|
+
await this.options.launcher.remove(runId).catch(() => { });
|
|
202
|
+
}
|
|
203
|
+
/** Waits up to stopGraceMs for the run's worker to exit or be gone; false when it still runs. Never throws. */
|
|
204
|
+
async awaitWorkerExit(runId) {
|
|
205
|
+
const graceMs = this.options.stopGraceMs ?? NATIVE_SANDBOX_STOP_GRACE_MS;
|
|
206
|
+
const sleep = this.options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
207
|
+
const until = this.now() + graceMs;
|
|
208
|
+
for (;;) {
|
|
209
|
+
const state = await this.options.launcher.inspect(runId).catch(() => undefined);
|
|
210
|
+
// An unreadable state counts as still running: the kill below is the safe default.
|
|
211
|
+
if (state && state.state !== "running")
|
|
212
|
+
return true;
|
|
213
|
+
const left = until - this.now();
|
|
214
|
+
if (left <= 0)
|
|
215
|
+
return false;
|
|
216
|
+
await sleep(Math.min(250, left));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async recover(handle) {
|
|
220
|
+
if (handle.backend !== NATIVE_SANDBOX_BACKEND) {
|
|
221
|
+
return { state: "lost", reason: `NativeSandboxExecutor cannot recover backend "${handle.backend}".` };
|
|
222
|
+
}
|
|
223
|
+
const runId = handle.runId;
|
|
224
|
+
const { db, launcher } = this.options;
|
|
225
|
+
const [state, run, session] = await Promise.all([
|
|
226
|
+
launcher.inspect(runId),
|
|
227
|
+
db.run.findUnique({ where: { id: runId }, select: { status: true } }),
|
|
228
|
+
db.nativeGatewaySession.findUnique({ where: { runId }, select: { status: true, deadlineAt: true } }),
|
|
229
|
+
]);
|
|
230
|
+
const runEnded = !run || !(run.status === "pending" || run.status === "running");
|
|
231
|
+
if (state.state === "running") {
|
|
232
|
+
if (runEnded) {
|
|
233
|
+
await launcher.kill(runId).catch(() => { });
|
|
234
|
+
await launcher.remove(runId).catch(() => { });
|
|
235
|
+
return { state: "terminal" };
|
|
236
|
+
}
|
|
237
|
+
// Re-attach (a restarted server): watch its exit and deadline from here.
|
|
238
|
+
if (session)
|
|
239
|
+
this.watch(runId, launcher.handle(runId), session.deadlineAt);
|
|
240
|
+
return { state: "active" };
|
|
241
|
+
}
|
|
242
|
+
if (state.state === "exited") {
|
|
243
|
+
await this.onWorkerExit(runId, state.exitCode);
|
|
244
|
+
return { state: "terminal" };
|
|
245
|
+
}
|
|
246
|
+
if (runEnded || session?.status === "finished")
|
|
247
|
+
return { state: "terminal" };
|
|
248
|
+
return {
|
|
249
|
+
state: "lost",
|
|
250
|
+
reason: "native_sandbox_worker_lost: the run's sandbox worker is gone and left no result; it was not relaunched.",
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/** Startup janitor: removes worker containers whose runs have ended or passed their deadline. Starts the warm pool. */
|
|
254
|
+
async launch() {
|
|
255
|
+
await this.sweep().catch((err) => executorLog.warn({ err }, "native sandbox janitor failed"));
|
|
256
|
+
await this.options.launcher
|
|
257
|
+
.start?.()
|
|
258
|
+
.catch((err) => executorLog.warn({ err }, "native warm pool failed to start"));
|
|
259
|
+
}
|
|
260
|
+
async sweep() {
|
|
261
|
+
const { db, launcher } = this.options;
|
|
262
|
+
const workers = await launcher.listWorkers();
|
|
263
|
+
if (workers.length === 0)
|
|
264
|
+
return 0;
|
|
265
|
+
const sessions = await db.nativeGatewaySession.findMany({
|
|
266
|
+
select: { runId: true, status: true, deadlineAt: true, run: { select: { status: true } } },
|
|
267
|
+
});
|
|
268
|
+
// Docker labels carry the full run hash; Kubernetes label values carry its first 40 characters.
|
|
269
|
+
const labelled = sessions.map((s) => ({ hash: nativeRunLabel(s.runId), session: s }));
|
|
270
|
+
let removed = 0;
|
|
271
|
+
for (const worker of workers) {
|
|
272
|
+
const session = labelled.find((l) => l.hash.startsWith(worker.runHash))?.session;
|
|
273
|
+
const live = session &&
|
|
274
|
+
session.status === "active" &&
|
|
275
|
+
session.deadlineAt.getTime() > this.now() &&
|
|
276
|
+
(session.run.status === "pending" || session.run.status === "running");
|
|
277
|
+
if (live)
|
|
278
|
+
continue;
|
|
279
|
+
await launcher.removeByWorkerName(worker.name);
|
|
280
|
+
removed += 1;
|
|
281
|
+
}
|
|
282
|
+
return removed;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sandbox worker's stderr is not a redacted channel by itself: a failed worker's stderr tail
|
|
3
|
+
* reaches the server's log (docker-launcher.ts, kubernetes-launcher.ts; the launcher masks it
|
|
4
|
+
* again, as the worker is untrusted), and a tool's host calls hand the worker secret
|
|
5
|
+
* values (`secrets.get`). The worker knows those exact values (raw and JSON-escaped), so it masks
|
|
6
|
+
* them in everything it writes to stderr — its own failure line and the shared logger's lines —
|
|
7
|
+
* with the shared redactor's spans (secretSpans: the union of every occurrence, overlapping ones
|
|
8
|
+
* included, minimum length included). One set per worker process.
|
|
9
|
+
*
|
|
10
|
+
* process.stderr writes are buffered (StderrRedactionBuffer), because a value may arrive split
|
|
11
|
+
* across writes. A string is matched as text, whatever encoding it was written with (its trailing
|
|
12
|
+
* high surrogate is held for the next write, so a split surrogate pair still matches); a hex or
|
|
13
|
+
* base64 string and any ArrayBufferView go through one UTF-8 byte decoder; any other chunk type is
|
|
14
|
+
* refused, never written raw. Emitted text is written as UTF-8. On each write the buffer:
|
|
15
|
+
* 1. holds back `hold`: the longest suffix of the buffer that is a proper prefix of a remembered
|
|
16
|
+
* value, since those characters could still become a value;
|
|
17
|
+
* 2. picks a cut `c` <= length − hold: after the last "\n" there, or, once the text after that
|
|
18
|
+
* exceeds MAX_PENDING_STDERR_CHARS, at length − hold; otherwise it emits nothing yet;
|
|
19
|
+
* 3. masks spans over the whole buffer, with its first `maskedLead` characters forced into a span;
|
|
20
|
+
* 4. when `c` falls inside a span [s, e), emits [s, c) as the marker and carries `maskedLead` =
|
|
21
|
+
* e − c, so the rest of that span is masked when it is emitted later;
|
|
22
|
+
* 5. emits the masked text before `c` and keeps the rest.
|
|
23
|
+
* Masking is safe at any cut, so no cut is ever searched for. On exit, SIGTERM/SIGINT and the
|
|
24
|
+
* worker's fail path the remainder is emitted with its spans, `maskedLead` and the held suffix all
|
|
25
|
+
* masked: fail closed, a possible value start is emitted as `[REDACTED]`, never raw.
|
|
26
|
+
*
|
|
27
|
+
* Why no value character is emitted: an occurrence that begins before `c` either ends within the
|
|
28
|
+
* buffer (it is a span, masked here, and its part past `c` is carried as `maskedLead`) or runs
|
|
29
|
+
* past the buffer's end, in which case the text from its start to the end is a proper prefix of
|
|
30
|
+
* the value, so `hold` >= length − start and `c` <= its start, a contradiction. By induction over
|
|
31
|
+
* writes, every character emitted that belongs to an occurrence is inside a masked span.
|
|
32
|
+
*
|
|
33
|
+
* The shared logger does not write through process.stderr: pino serializes each log call to one
|
|
34
|
+
* whole line (ending in "\n") and hands that line to its `streamWrite` hook before writing it to
|
|
35
|
+
* fd 2 itself, so the line filter below (the shared redactor) sees whole lines.
|
|
36
|
+
*/
|
|
37
|
+
/** Records a secret value this process received, and its JSON-escaped form (as a log line holds it). */
|
|
38
|
+
export declare function rememberSecretValue(value: string): void;
|
|
39
|
+
/** `text` without any secret value this process received. */
|
|
40
|
+
export declare function redactReceivedSecrets(text: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Text after the last "\n" longer than this (64 KiB) is emitted without waiting for its "\n", so an
|
|
43
|
+
* unterminated stream cannot grow the buffer without bound: it stays within this plus the longest
|
|
44
|
+
* remembered value plus one write.
|
|
45
|
+
*/
|
|
46
|
+
export declare const MAX_PENDING_STDERR_CHARS: number;
|
|
47
|
+
/**
|
|
48
|
+
* The stderr buffer as a value: text in (`write`), masked text out, never a remembered value's
|
|
49
|
+
* character unmasked (see the module comment). `values` is read on every call, so a value
|
|
50
|
+
* remembered later applies to what is still buffered.
|
|
51
|
+
*/
|
|
52
|
+
export declare class StderrRedactionBuffer {
|
|
53
|
+
private readonly values;
|
|
54
|
+
private readonly bound;
|
|
55
|
+
private pending;
|
|
56
|
+
private maskedLead;
|
|
57
|
+
private atLineStart;
|
|
58
|
+
constructor(values: () => readonly string[], bound?: number);
|
|
59
|
+
/** Characters still buffered. */
|
|
60
|
+
get pendingLength(): number;
|
|
61
|
+
/** Adds `text` and returns what may be emitted now (possibly ""). */
|
|
62
|
+
write(text: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Everything still buffered, masked, fail closed (the held suffix too), ending with "\n" if output
|
|
65
|
+
* is mid-line. `maskTail` masks one more character at the end: a write's trailing lone high
|
|
66
|
+
* surrogate that never got its low half.
|
|
67
|
+
*/
|
|
68
|
+
flush(maskTail?: boolean): string;
|
|
69
|
+
private redactable;
|
|
70
|
+
/** Spans over the whole buffer, the carried `maskedLead` and any `extra` forced in. */
|
|
71
|
+
private spans;
|
|
72
|
+
private emitted;
|
|
73
|
+
}
|
|
74
|
+
/** Emits the buffered remainder, masked fail closed and ended with "\n". Safe to call any number of times. */
|
|
75
|
+
export declare function flushStderrRedaction(): void;
|
|
76
|
+
/** Routes process.stderr writes through the buffer, and the shared logger's lines through redactReceivedSecrets. */
|
|
77
|
+
export declare function installStderrRedaction(): void;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sandbox worker's stderr is not a redacted channel by itself: a failed worker's stderr tail
|
|
3
|
+
* reaches the server's log (docker-launcher.ts, kubernetes-launcher.ts; the launcher masks it
|
|
4
|
+
* again, as the worker is untrusted), and a tool's host calls hand the worker secret
|
|
5
|
+
* values (`secrets.get`). The worker knows those exact values (raw and JSON-escaped), so it masks
|
|
6
|
+
* them in everything it writes to stderr — its own failure line and the shared logger's lines —
|
|
7
|
+
* with the shared redactor's spans (secretSpans: the union of every occurrence, overlapping ones
|
|
8
|
+
* included, minimum length included). One set per worker process.
|
|
9
|
+
*
|
|
10
|
+
* process.stderr writes are buffered (StderrRedactionBuffer), because a value may arrive split
|
|
11
|
+
* across writes. A string is matched as text, whatever encoding it was written with (its trailing
|
|
12
|
+
* high surrogate is held for the next write, so a split surrogate pair still matches); a hex or
|
|
13
|
+
* base64 string and any ArrayBufferView go through one UTF-8 byte decoder; any other chunk type is
|
|
14
|
+
* refused, never written raw. Emitted text is written as UTF-8. On each write the buffer:
|
|
15
|
+
* 1. holds back `hold`: the longest suffix of the buffer that is a proper prefix of a remembered
|
|
16
|
+
* value, since those characters could still become a value;
|
|
17
|
+
* 2. picks a cut `c` <= length − hold: after the last "\n" there, or, once the text after that
|
|
18
|
+
* exceeds MAX_PENDING_STDERR_CHARS, at length − hold; otherwise it emits nothing yet;
|
|
19
|
+
* 3. masks spans over the whole buffer, with its first `maskedLead` characters forced into a span;
|
|
20
|
+
* 4. when `c` falls inside a span [s, e), emits [s, c) as the marker and carries `maskedLead` =
|
|
21
|
+
* e − c, so the rest of that span is masked when it is emitted later;
|
|
22
|
+
* 5. emits the masked text before `c` and keeps the rest.
|
|
23
|
+
* Masking is safe at any cut, so no cut is ever searched for. On exit, SIGTERM/SIGINT and the
|
|
24
|
+
* worker's fail path the remainder is emitted with its spans, `maskedLead` and the held suffix all
|
|
25
|
+
* masked: fail closed, a possible value start is emitted as `[REDACTED]`, never raw.
|
|
26
|
+
*
|
|
27
|
+
* Why no value character is emitted: an occurrence that begins before `c` either ends within the
|
|
28
|
+
* buffer (it is a span, masked here, and its part past `c` is carried as `maskedLead`) or runs
|
|
29
|
+
* past the buffer's end, in which case the text from its start to the end is a proper prefix of
|
|
30
|
+
* the value, so `hold` >= length − start and `c` <= its start, a contradiction. By induction over
|
|
31
|
+
* writes, every character emitted that belongs to an occurrence is inside a masked span.
|
|
32
|
+
*
|
|
33
|
+
* The shared logger does not write through process.stderr: pino serializes each log call to one
|
|
34
|
+
* whole line (ending in "\n") and hands that line to its `streamWrite` hook before writing it to
|
|
35
|
+
* fd 2 itself, so the line filter below (the shared redactor) sees whole lines.
|
|
36
|
+
*/
|
|
37
|
+
import { StringDecoder } from "node:string_decoder";
|
|
38
|
+
import { setLogLineFilter } from "../core/logger.js";
|
|
39
|
+
import { maskSpans, mergeSpans, MIN_REDACTABLE_SECRET_LENGTH, redactExactSecretValues, secretSpans, withEscapedForms, } from "../core/secret-spans.js";
|
|
40
|
+
const received = new Set();
|
|
41
|
+
/** Records a secret value this process received, and its JSON-escaped form (as a log line holds it). */
|
|
42
|
+
export function rememberSecretValue(value) {
|
|
43
|
+
for (const form of withEscapedForms([value]))
|
|
44
|
+
received.add(form);
|
|
45
|
+
}
|
|
46
|
+
/** `text` without any secret value this process received. */
|
|
47
|
+
export function redactReceivedSecrets(text) {
|
|
48
|
+
return received.size === 0 ? text : redactExactSecretValues(text, received);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Text after the last "\n" longer than this (64 KiB) is emitted without waiting for its "\n", so an
|
|
52
|
+
* unterminated stream cannot grow the buffer without bound: it stays within this plus the longest
|
|
53
|
+
* remembered value plus one write.
|
|
54
|
+
*/
|
|
55
|
+
export const MAX_PENDING_STDERR_CHARS = 64 * 1024;
|
|
56
|
+
/** The longest suffix of `text` that is a proper prefix of one of `values`. */
|
|
57
|
+
function heldLength(text, values) {
|
|
58
|
+
let hold = 0;
|
|
59
|
+
for (const value of values) {
|
|
60
|
+
for (let k = Math.min(value.length - 1, text.length); k > hold; k -= 1) {
|
|
61
|
+
if (text.charCodeAt(text.length - k) === value.charCodeAt(0) && text.endsWith(value.slice(0, k))) {
|
|
62
|
+
hold = k;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return hold;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The stderr buffer as a value: text in (`write`), masked text out, never a remembered value's
|
|
71
|
+
* character unmasked (see the module comment). `values` is read on every call, so a value
|
|
72
|
+
* remembered later applies to what is still buffered.
|
|
73
|
+
*/
|
|
74
|
+
export class StderrRedactionBuffer {
|
|
75
|
+
values;
|
|
76
|
+
bound;
|
|
77
|
+
pending = "";
|
|
78
|
+
maskedLead = 0;
|
|
79
|
+
atLineStart = true;
|
|
80
|
+
constructor(values, bound = MAX_PENDING_STDERR_CHARS) {
|
|
81
|
+
this.values = values;
|
|
82
|
+
this.bound = bound;
|
|
83
|
+
}
|
|
84
|
+
/** Characters still buffered. */
|
|
85
|
+
get pendingLength() {
|
|
86
|
+
return this.pending.length;
|
|
87
|
+
}
|
|
88
|
+
/** Adds `text` and returns what may be emitted now (possibly ""). */
|
|
89
|
+
write(text) {
|
|
90
|
+
this.pending += text;
|
|
91
|
+
const values = this.redactable();
|
|
92
|
+
const hold = heldLength(this.pending, values);
|
|
93
|
+
const limit = this.pending.length - hold;
|
|
94
|
+
// (lastIndexOf treats a negative start as 0, so an empty window is checked first.)
|
|
95
|
+
let cut = limit > 0 ? this.pending.lastIndexOf("\n", limit - 1) + 1 : 0;
|
|
96
|
+
if (this.pending.length - cut > this.bound)
|
|
97
|
+
cut = limit;
|
|
98
|
+
if (cut <= 0)
|
|
99
|
+
return "";
|
|
100
|
+
const spans = this.spans(values, []);
|
|
101
|
+
const inside = spans.find(([start, end]) => start < cut && cut < end);
|
|
102
|
+
this.maskedLead = inside ? inside[1] - cut : 0;
|
|
103
|
+
const ready = maskSpans(this.pending.slice(0, cut), spans.filter(([start]) => start < cut).map(([start, end]) => [start, Math.min(end, cut)]));
|
|
104
|
+
this.pending = this.pending.slice(cut);
|
|
105
|
+
return this.emitted(ready);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Everything still buffered, masked, fail closed (the held suffix too), ending with "\n" if output
|
|
109
|
+
* is mid-line. `maskTail` masks one more character at the end: a write's trailing lone high
|
|
110
|
+
* surrogate that never got its low half.
|
|
111
|
+
*/
|
|
112
|
+
flush(maskTail = false) {
|
|
113
|
+
if (maskTail)
|
|
114
|
+
this.pending += "\uFFFD";
|
|
115
|
+
const values = this.redactable();
|
|
116
|
+
const text = this.pending;
|
|
117
|
+
const hold = heldLength(text, values);
|
|
118
|
+
const extra = [];
|
|
119
|
+
if (hold > 0)
|
|
120
|
+
extra.push([text.length - hold, text.length]);
|
|
121
|
+
if (maskTail)
|
|
122
|
+
extra.push([text.length - 1, text.length]);
|
|
123
|
+
const out = maskSpans(text, this.spans(values, extra));
|
|
124
|
+
this.pending = "";
|
|
125
|
+
this.maskedLead = 0;
|
|
126
|
+
const ready = this.emitted(out);
|
|
127
|
+
if (this.atLineStart)
|
|
128
|
+
return ready;
|
|
129
|
+
this.atLineStart = true;
|
|
130
|
+
return `${ready}\n`;
|
|
131
|
+
}
|
|
132
|
+
redactable() {
|
|
133
|
+
return this.values().filter((value) => value.length >= MIN_REDACTABLE_SECRET_LENGTH);
|
|
134
|
+
}
|
|
135
|
+
/** Spans over the whole buffer, the carried `maskedLead` and any `extra` forced in. */
|
|
136
|
+
spans(values, extra) {
|
|
137
|
+
const forced = this.maskedLead > 0 ? [[0, this.maskedLead], ...extra] : extra;
|
|
138
|
+
return mergeSpans([...secretSpans(this.pending, values), ...forced]);
|
|
139
|
+
}
|
|
140
|
+
emitted(text) {
|
|
141
|
+
if (text)
|
|
142
|
+
this.atLineStart = text.endsWith("\n");
|
|
143
|
+
return text;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const buffer = new StderrRedactionBuffer(() => [...received]);
|
|
147
|
+
let installed = false;
|
|
148
|
+
let rawWrite;
|
|
149
|
+
const bytes = new StringDecoder("utf8");
|
|
150
|
+
/**
|
|
151
|
+
* A string write's trailing high surrogate, held for the next write: encoded alone it would become
|
|
152
|
+
* U+FFFD, and a value whose non-BMP character is split between two writes would never match.
|
|
153
|
+
*/
|
|
154
|
+
let heldHighSurrogate = "";
|
|
155
|
+
const isHighSurrogate = (code) => code >= 0xd800 && code <= 0xdbff;
|
|
156
|
+
/** String encodings that name bytes rather than text: such a string is decoded to its bytes first. */
|
|
157
|
+
const BYTE_STRING_ENCODINGS = new Set(["hex", "base64", "base64url"]);
|
|
158
|
+
/**
|
|
159
|
+
* `chunk` as text for the buffer. A string is matched as the JavaScript string itself, whatever
|
|
160
|
+
* text encoding it was written with (utf16le, latin1, ...): re-encoding it and decoding the bytes as
|
|
161
|
+
* UTF-8 would turn an ASCII value into one with NULs that never matches. Its trailing high surrogate
|
|
162
|
+
* is held for the next write. A hex or base64 string, and any ArrayBufferView, goes through the one
|
|
163
|
+
* byte decoder. Any other chunk type throws (after emitting a held surrogate masked).
|
|
164
|
+
*/
|
|
165
|
+
function decodeChunk(chunk, encoding) {
|
|
166
|
+
if (typeof chunk === "string" && !BYTE_STRING_ENCODINGS.has(encoding?.toLowerCase() ?? "")) {
|
|
167
|
+
// An incomplete multi-byte character from an earlier byte write ends here (as U+FFFD), in order.
|
|
168
|
+
let text = bytes.end() + heldHighSurrogate + chunk;
|
|
169
|
+
heldHighSurrogate = "";
|
|
170
|
+
if (text.length > 0 && isHighSurrogate(text.charCodeAt(text.length - 1))) {
|
|
171
|
+
heldHighSurrogate = text.slice(-1);
|
|
172
|
+
text = text.slice(0, -1);
|
|
173
|
+
}
|
|
174
|
+
return text;
|
|
175
|
+
}
|
|
176
|
+
if (typeof chunk !== "string" && !ArrayBuffer.isView(chunk)) {
|
|
177
|
+
// Never handed to the raw write: its content is unknown. A held surrogate goes out masked
|
|
178
|
+
// first (fail closed, with everything buffered before it). The message names no content.
|
|
179
|
+
if (heldHighSurrogate) {
|
|
180
|
+
heldHighSurrogate = "";
|
|
181
|
+
const out = buffer.flush(true);
|
|
182
|
+
if (out)
|
|
183
|
+
rawWrite?.(out);
|
|
184
|
+
}
|
|
185
|
+
throw new TypeError("process.stderr.write takes a string or bytes (an ArrayBufferView).");
|
|
186
|
+
}
|
|
187
|
+
// Bytes end a held surrogate's chance of a low half: it is decoded alone (as U+FFFD).
|
|
188
|
+
const lone = heldHighSurrogate ? bytes.write(Buffer.from(heldHighSurrogate, "utf8")) : "";
|
|
189
|
+
heldHighSurrogate = "";
|
|
190
|
+
if (typeof chunk === "string")
|
|
191
|
+
return lone + bytes.write(Buffer.from(chunk, encoding));
|
|
192
|
+
return lone + bytes.write(Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength));
|
|
193
|
+
}
|
|
194
|
+
/** Emits the buffered remainder, masked fail closed and ended with "\n". Safe to call any number of times. */
|
|
195
|
+
export function flushStderrRedaction() {
|
|
196
|
+
if (!rawWrite)
|
|
197
|
+
return;
|
|
198
|
+
const lone = heldHighSurrogate !== "";
|
|
199
|
+
heldHighSurrogate = "";
|
|
200
|
+
const tail = bytes.end();
|
|
201
|
+
const ready = (tail ? buffer.write(tail) : "") + buffer.flush(lone);
|
|
202
|
+
if (ready)
|
|
203
|
+
rawWrite(ready);
|
|
204
|
+
}
|
|
205
|
+
/** Routes process.stderr writes through the buffer, and the shared logger's lines through redactReceivedSecrets. */
|
|
206
|
+
export function installStderrRedaction() {
|
|
207
|
+
if (installed)
|
|
208
|
+
return;
|
|
209
|
+
installed = true;
|
|
210
|
+
setLogLineFilter(redactReceivedSecrets);
|
|
211
|
+
const write = process.stderr.write.bind(process.stderr);
|
|
212
|
+
rawWrite = (chunk) => write(chunk);
|
|
213
|
+
process.stderr.write = (chunk, ...rest) => {
|
|
214
|
+
const encoding = typeof rest[0] === "string" ? rest[0] : undefined;
|
|
215
|
+
const callback = rest.find((arg) => typeof arg === "function");
|
|
216
|
+
// Every write goes through the one decoder, so a string after an incomplete multi-byte
|
|
217
|
+
// character from an earlier byte write keeps its place.
|
|
218
|
+
const text = decodeChunk(chunk, encoding);
|
|
219
|
+
const ready = buffer.write(text);
|
|
220
|
+
if (ready)
|
|
221
|
+
return write(ready, callback);
|
|
222
|
+
// Held for its line's end: the write is accepted, so its callback runs as if written.
|
|
223
|
+
if (callback)
|
|
224
|
+
process.nextTick(callback, null);
|
|
225
|
+
return true;
|
|
226
|
+
};
|
|
227
|
+
process.on("exit", flushStderrRedaction);
|
|
228
|
+
// A signal's default action would drop the buffered tail: flush it, then exit as the signal would.
|
|
229
|
+
process.once("SIGTERM", () => {
|
|
230
|
+
flushStderrRedaction();
|
|
231
|
+
process.exit(143);
|
|
232
|
+
});
|
|
233
|
+
process.once("SIGINT", () => {
|
|
234
|
+
flushStderrRedaction();
|
|
235
|
+
process.exit(130);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The worker contract over a child process's stdin/stdout, as newline-delimited
|
|
3
|
+
* JSON. The control plane writes the WorkerInput as the first line, then
|
|
4
|
+
* answers the worker's requests; the worker writes requests and reads answers.
|
|
5
|
+
* Both directions are capped at MAX_MESSAGE_BYTES per line. stderr is the
|
|
6
|
+
* worker's diagnostics only.
|
|
7
|
+
*
|
|
8
|
+
* Frames, worker → control: { id, request }
|
|
9
|
+
* Frames, control → worker: { id, ok: true, result } | { id, ok: false, error: { code, message, causeCode? } }
|
|
10
|
+
* | { id, event } (llm.stream events) | { id, end: true }
|
|
11
|
+
*/
|
|
12
|
+
import type { Readable, Writable } from "node:stream";
|
|
13
|
+
import type { NativeGateway, WorkerLauncher } from "./gateway.js";
|
|
14
|
+
import type { GatewayTransport } from "./worker.js";
|
|
15
|
+
/** Splits a byte stream into lines, refusing any line over the cap. */
|
|
16
|
+
export declare function readLines(stream: Readable, onLine: (line: string) => void, onError: (err: Error) => void): void;
|
|
17
|
+
/** The worker's side: requests out on `output`, answers in on `input`. */
|
|
18
|
+
export declare function createStdioTransport(input: Readable, output: Writable): GatewayTransport;
|
|
19
|
+
/** Serves one worker's requests from `gateway`, answering on `output`. */
|
|
20
|
+
export declare function serveStdioWorker(input: Readable, output: Writable, gateway: NativeGateway): void;
|
|
21
|
+
export interface WorkerProcessSpec {
|
|
22
|
+
command: string;
|
|
23
|
+
args: string[];
|
|
24
|
+
/** The worker's whole environment. Defaults to empty: it needs nothing from the server's. */
|
|
25
|
+
env?: Record<string, string>;
|
|
26
|
+
cwd?: string;
|
|
27
|
+
}
|
|
28
|
+
/** Launches the worker as a child process speaking this transport on its stdio. */
|
|
29
|
+
export declare function createProcessLauncher(spec: WorkerProcessSpec): WorkerLauncher;
|