@wardby/cli 0.6.0 → 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/deploy/local/docker-compose.quickstart-coding.yml +13 -0
- package/dist/claude-coding-worker/driver.d.ts +21 -3
- package/dist/claude-coding-worker/driver.js +125 -17
- package/dist/claude-coding-worker/main.js +34 -6
- package/dist/claude-coding-worker/sdk.js +12 -1
- 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 +1 -0
- package/dist/cli.js +24 -10
- 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 +46 -126
- package/dist/coding/profile.js +45 -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 +264 -346
- package/dist/coding/protocol.js +235 -12
- 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 +8 -3
- package/dist/coding-worker/artifact.js +3 -1
- 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 +116 -38
- package/dist/coding-worker/errors.d.ts +1 -0
- package/dist/coding-worker/errors.js +32 -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 +11 -0
- package/dist/config/dependency-cache.d.ts +23 -0
- package/dist/config/dependency-cache.js +32 -0
- package/dist/config/providers.d.ts +12 -3
- package/dist/config/providers.js +55 -5
- 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/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 +42 -0
- package/dist/core/dispatch.js +131 -56
- package/dist/core/engine-native.js +1 -1
- 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 +18 -2
- package/dist/core/host-events.js +284 -24
- 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 +124 -33
- package/dist/core/issue-events.d.ts +1 -1
- package/dist/core/issue-events.js +4 -10
- package/dist/core/issue-status.d.ts +1 -1
- package/dist/core/issue-status.js +54 -26
- 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 +2 -1
- package/dist/core/merge-order-check.js +8 -1
- package/dist/core/notification-templates.d.ts +1 -7
- package/dist/core/notification-templates.js +36 -2
- package/dist/core/pull-request-state-sync.js +2 -2
- package/dist/core/reconciler.d.ts +4 -3
- package/dist/core/reconciler.js +8 -1
- package/dist/core/related-pull-requests.d.ts +11 -3
- package/dist/core/related-pull-requests.js +27 -14
- package/dist/core/reply-parts.d.ts +21 -0
- package/dist/core/reply-parts.js +73 -0
- package/dist/core/review-fix.d.ts +1 -1
- package/dist/core/review-fix.js +2 -1
- package/dist/core/review-host-checks.d.ts +1 -1
- package/dist/core/review-host-tools.d.ts +3 -0
- package/dist/core/review-host-tools.js +99 -13
- 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 +80 -34
- package/dist/core/runner.js +439 -144
- 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 +10 -74
- 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/stop-run.d.ts +46 -0
- package/dist/core/stop-run.js +61 -0
- package/dist/core/workflow-events.d.ts +14 -2
- package/dist/core/workflow-events.js +4 -0
- package/dist/core/workflow-run-events.d.ts +5 -3
- package/dist/core/workflow-run-events.js +43 -8
- 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 +34 -3
- package/dist/generated/prisma/client.d.ts +34 -3
- package/dist/generated/prisma/commonInputTypes.d.ts +220 -60
- package/dist/generated/prisma/enums.d.ts +10 -0
- package/dist/generated/prisma/enums.js +8 -0
- package/dist/generated/prisma/internal/class.d.ts +44 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +428 -9
- package/dist/generated/prisma/internal/prismaNamespace.js +92 -1
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +95 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +92 -1
- package/dist/generated/prisma/models/Agent.d.ts +106 -8
- package/dist/generated/prisma/models/AgentRepository.d.ts +42 -1
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +229 -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 +1637 -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 +39 -1
- package/dist/generated/prisma/models/NativeGatewaySession.d.ts +35 -1
- 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 +738 -2
- package/dist/generated/prisma/models/RunBudgetEvent.d.ts +1211 -0
- package/dist/generated/prisma/models/RunBudgetEvent.js +1 -0
- package/dist/generated/prisma/models/RunTranscript.d.ts +1339 -0
- package/dist/generated/prisma/models/RunTranscript.js +1 -0
- package/dist/generated/prisma/models.d.ts +4 -0
- package/dist/help-index.json +1320 -69
- package/dist/import/create.js +32 -3
- package/dist/import/neutral-schema.d.ts +50 -281
- package/dist/import/neutral-schema.js +5 -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 +10 -2
- package/dist/mcp/auth/access.js +15 -1
- package/dist/mcp/auth/ownership.d.ts +7 -7
- 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/host-events/github-ingress.d.ts +2 -0
- package/dist/mcp/host-events/github-ingress.js +2 -1
- package/dist/mcp/index.d.ts +6 -0
- package/dist/mcp/index.js +58 -10
- 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 +198 -16
- 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.js +2 -2
- package/dist/mcp/tools/packages.d.ts +2 -0
- package/dist/mcp/tools/packages.js +195 -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.js +34 -12
- package/dist/mcp/tools/scheduling.js +3 -1
- package/dist/mcp/tools/secret-elicitation-form.js +5 -0
- package/dist/mcp/tools/secret-elicitation-server.d.ts +6 -0
- package/dist/mcp/tools/secret-elicitation-server.js +10 -61
- package/dist/mcp/tools/secrets.js +2 -2
- 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 +1 -1
- package/dist/mcp/tools/tools.js +25 -9
- 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/docker-launcher.d.ts +38 -2
- package/dist/native-worker/docker-launcher.js +65 -5
- package/dist/native-worker/gateway-server.d.ts +41 -3
- package/dist/native-worker/gateway-server.js +124 -27
- package/dist/native-worker/gateway.d.ts +27 -1
- package/dist/native-worker/gateway.js +70 -4
- package/dist/native-worker/http-server.d.ts +3 -1
- package/dist/native-worker/http-server.js +11 -7
- package/dist/native-worker/http-transport.js +1 -1
- package/dist/native-worker/kubernetes-launcher.d.ts +7 -3
- package/dist/native-worker/kubernetes-launcher.js +43 -3
- package/dist/native-worker/launch.d.ts +18 -1
- package/dist/native-worker/launch.js +6 -1
- package/dist/native-worker/ledger.d.ts +18 -2
- package/dist/native-worker/ledger.js +57 -4
- package/dist/native-worker/loopback.js +3 -8
- package/dist/native-worker/main.d.ts +2 -1
- package/dist/native-worker/main.js +14 -3
- package/dist/native-worker/protocol.d.ts +129 -577
- package/dist/native-worker/protocol.js +47 -1
- package/dist/native-worker/sandbox-executor.d.ts +17 -2
- package/dist/native-worker/sandbox-executor.js +78 -4
- 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 +1 -1
- package/dist/native-worker/stdio.js +6 -5
- package/dist/native-worker/warm-pool.d.ts +2 -2
- package/dist/native-worker/warm-pool.js +4 -4
- package/dist/native-worker/worker.js +28 -2
- package/dist/observability/metrics.js +6 -1
- package/dist/providers/auth/self-hosted.d.ts +1 -1
- 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 +17 -2
- package/dist/providers/engine/types.js +17 -0
- package/dist/providers/executor/composition.d.ts +10 -3
- package/dist/providers/executor/composition.js +47 -7
- package/dist/providers/executor/container.d.ts +361 -8
- package/dist/providers/executor/container.js +1136 -91
- 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 +50 -1
- package/dist/providers/executor/dbos.js +207 -20
- package/dist/providers/executor/deferred.d.ts +12 -24
- package/dist/providers/executor/deferred.js +10 -29
- 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 +2 -0
- package/dist/providers/executor/routing.js +4 -0
- package/dist/providers/executor/types.d.ts +13 -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 +27 -2
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +2 -0
- package/dist/providers/jobs/fake-kubernetes-api.js +3 -0
- package/dist/providers/jobs/kubernetes-api.d.ts +8 -0
- package/dist/providers/jobs/kubernetes-api.js +10 -0
- package/dist/providers/jobs/kubernetes-client.js +10 -1
- package/dist/providers/jobs/kubernetes-isolation.d.ts +2 -0
- package/dist/providers/jobs/kubernetes-isolation.js +17 -1
- package/dist/providers/jobs/kubernetes.d.ts +6 -1
- package/dist/providers/jobs/kubernetes.js +105 -16
- package/dist/providers/jobs/types.d.ts +33 -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 +29 -2
- package/dist/providers/review-host/github.js +224 -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 +134 -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 +214 -6
- package/dist/providers/vcs/github.js +447 -45
- 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 +14 -3
- package/dist/quickstart/coding-seed.js +213 -4
- 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/generated/zod.bundle.js +62 -1
- package/dist/sandbox/generated/zod3.bundle.js +5 -0
- package/dist/sandbox/host-functions.d.ts +1 -0
- package/dist/sandbox/host-functions.js +6 -11
- package/dist/sandbox/secret-broker.js +3 -4
- package/dist/sandbox/tool-capabilities.d.ts +10 -30
- package/dist/sandbox/zod-params.d.ts +37 -5
- package/dist/sandbox/zod-params.js +181 -26
- package/dist/serve.js +8 -1
- package/dist/viewer/api-schema.d.ts +349 -2627
- package/dist/viewer/api-schema.js +7 -0
- package/dist/viewer/build-schemas.js +6 -2
- package/dist/viewer/openapi.js +2 -4
- package/dist/wardby-bin.js +10 -0
- package/docs/agent-recipes.md +58 -20
- package/docs/code-review-agents.md +551 -41
- package/docs/coding-agent-setup.md +489 -7
- package/docs/coding-packages.md +1033 -90
- package/docs/coding-services.md +27 -12
- package/docs/coding-worker-byo-images.md +20 -0
- package/docs/coding-worker-isolation.md +105 -30
- package/docs/getting-started-gke.md +124 -137
- package/docs/getting-started-identity-provider.md +10 -1
- package/docs/getting-started.md +9 -0
- package/docs/jira-agents.md +17 -8
- 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 +119 -23
- package/docs/observability.md +4 -0
- package/docs/security-deployment.md +33 -2
- package/docs/slack-notifications.md +35 -19
- package/docs/tool-secrets.md +42 -10
- package/docs/viewer-api.openapi.yml +1 -1
- package/help/admin-viewer.md +24 -2
- package/help/architecture-agent.md +11 -6
- package/help/build-worker-image.md +3 -1
- package/help/builder-agent.md +19 -6
- package/help/code-review-agents.md +177 -6
- 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 +15 -8
- package/help/errors/budget-exhausted.md +187 -0
- package/help/errors/budget-group-exhausted.md +5 -1
- 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/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 +4 -0
- package/help/errors/graph-incomplete.md +43 -0
- package/help/errors/graph-limit.md +43 -0
- package/help/errors/merge-conflict.md +49 -0
- package/help/errors/native-sandbox-gateway-error.md +20 -0
- package/help/errors/native-sandbox-launch-abandoned.md +21 -0
- package/help/errors/native-sandbox-worker-exited.md +3 -2
- 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-in-value.md +69 -0
- package/help/errors/target-merge.md +40 -0
- package/help/fix-merge-conflicts.md +73 -0
- package/help/github-app-setup.md +132 -0
- package/help/github.md +22 -0
- package/help/identity-and-access.md +2 -0
- package/help/jira.md +23 -6
- package/help/knowledge.md +16 -7
- package/help/local-repositories.md +1 -1
- package/help/low-power-mode.md +7 -1
- package/help/merge-order-check.md +5 -0
- package/help/native-capabilities.md +29 -1
- package/help/native-sandbox.md +15 -1
- package/help/operating-agents.md +5 -0
- package/help/package-audit-mode.md +91 -0
- package/help/related-pull-requests.md +10 -7
- package/help/resume-runs-locally.md +131 -0
- package/help/review-fix-rounds.md +33 -1
- package/help/sharing-agents.md +59 -0
- package/help/slack-notifications.md +19 -5
- 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 +78 -7
- package/package.json +17 -10
- 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 +311 -97
- package/dist/sandbox/generated/zod-to-json-schema.bundle.js +0 -5
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
* child process's stdio, and (phase 3) over HTTP.
|
|
12
12
|
*/
|
|
13
13
|
import { z } from "zod";
|
|
14
|
+
import { causeCodeOf, GATEWAY_CAUSE_CODES } from "../core/error-code.js";
|
|
14
15
|
import { LLM_EFFORT_LEVELS } from "../providers/llm/types.js";
|
|
16
|
+
import { overlapsSecretValue } from "../sandbox/host-functions.js";
|
|
17
|
+
// The allow-list and its checked helper live in core/error-code.ts (shared with the runner); re-exported here.
|
|
18
|
+
export { causeCodeOf, GATEWAY_CAUSE_CODES };
|
|
15
19
|
export const NATIVE_WORKER_PROTOCOL_VERSION = 1;
|
|
16
20
|
/** Above the sandbox's 12 MiB bridge-result cap, plus envelope. */
|
|
17
21
|
export const MAX_MESSAGE_BYTES = 16 * 1024 * 1024;
|
|
@@ -25,12 +29,54 @@ export class ProtocolError extends Error {
|
|
|
25
29
|
}
|
|
26
30
|
export class GatewayError extends Error {
|
|
27
31
|
code;
|
|
28
|
-
|
|
32
|
+
causeCode;
|
|
33
|
+
constructor(code, message,
|
|
34
|
+
/**
|
|
35
|
+
* For `internal` only: the code the underlying error's message led with (causeCodeOf), so the
|
|
36
|
+
* run can name the cause without the rest of that message.
|
|
37
|
+
*/
|
|
38
|
+
causeCode) {
|
|
29
39
|
super(message);
|
|
30
40
|
this.code = code;
|
|
41
|
+
this.causeCode = causeCode;
|
|
31
42
|
this.name = "GatewayError";
|
|
32
43
|
}
|
|
33
44
|
}
|
|
45
|
+
/** What the worker is told about an unexpected gateway failure: never the error's own text. */
|
|
46
|
+
export const GATEWAY_INTERNAL_MESSAGE = "The gateway could not serve this call.";
|
|
47
|
+
/**
|
|
48
|
+
* `err` as the worker may see it, on every transport (HTTP, stdio, loopback). An unexpected error
|
|
49
|
+
* becomes `internal` with the generic message; a GatewayError keeps its code and message. A cause
|
|
50
|
+
* code is carried only when it is declared (GATEWAY_CAUSE_CODES) and checked: not when it equals, contains, or is contained in a secret
|
|
51
|
+
* value the run can read (`secretValues`, read only when there is a code to check), and not at
|
|
52
|
+
* all when there is nothing to check it against or reading the values fails.
|
|
53
|
+
*/
|
|
54
|
+
export async function workerGatewayError(err, secretValues) {
|
|
55
|
+
const leading = err instanceof GatewayError ? err.causeCode : causeCodeOf(err);
|
|
56
|
+
// Declared codes only; the exact-value check below is a second guard.
|
|
57
|
+
const candidate = leading && GATEWAY_CAUSE_CODES.has(leading) ? leading : undefined;
|
|
58
|
+
let causeCode;
|
|
59
|
+
if (candidate && secretValues) {
|
|
60
|
+
const values = await secretValues().catch(() => undefined);
|
|
61
|
+
if (values && !overlapsSecretValue(candidate, values))
|
|
62
|
+
causeCode = candidate;
|
|
63
|
+
}
|
|
64
|
+
if (err instanceof GatewayError)
|
|
65
|
+
return new GatewayError(err.code, err.message, causeCode);
|
|
66
|
+
return new GatewayError("internal", causeCode ? `The gateway could not serve this call (${causeCode}).` : GATEWAY_INTERNAL_MESSAGE, causeCode);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* `err` as a transport carries it to the worker: a GatewayError (already checked by the gateway
|
|
70
|
+
* that threw it) as is, with a declared cause code only; anything else `internal` with the generic message and no cause code, since
|
|
71
|
+
* a transport has no secret values to check one against.
|
|
72
|
+
*/
|
|
73
|
+
export function toGatewayError(err) {
|
|
74
|
+
if (err instanceof GatewayError) {
|
|
75
|
+
const causeCode = err.causeCode && GATEWAY_CAUSE_CODES.has(err.causeCode) ? err.causeCode : undefined;
|
|
76
|
+
return new GatewayError(err.code, err.message, causeCode);
|
|
77
|
+
}
|
|
78
|
+
return new GatewayError("internal", GATEWAY_INTERNAL_MESSAGE);
|
|
79
|
+
}
|
|
34
80
|
const runId = z.string().min(1).max(64);
|
|
35
81
|
const callId = z.string().min(1).max(128);
|
|
36
82
|
const LlmMessageSchema = z
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* worker, and returns. Whichever gateway replica serves the worker's
|
|
8
8
|
* `finish` makes the terminal write; this executor only watches for a
|
|
9
9
|
* worker that exits without one, and for its deadline.
|
|
10
|
-
* - stop: ends the session (every further gateway
|
|
11
|
-
*
|
|
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.
|
|
12
14
|
* - recover: answers the reconciler from the worker's container state, and
|
|
13
15
|
* never launches one.
|
|
14
16
|
* - launch: the janitor removes workers whose runs have ended.
|
|
@@ -43,8 +45,15 @@ export interface NativeSandboxExecutorOptions {
|
|
|
43
45
|
gatewayUrl: string;
|
|
44
46
|
/** NATIVE_SANDBOX_MAX_CONCURRENT: a start past this many active sessions fails with native_sandbox_capacity. */
|
|
45
47
|
maxConcurrent?: number;
|
|
48
|
+
/** How long a stopped run's worker gets to exit on its own before it is killed (default NATIVE_SANDBOX_STOP_GRACE_MS). */
|
|
49
|
+
stopGraceMs?: number;
|
|
50
|
+
/** How often a launching run's heartbeat is refreshed (default a third of HEARTBEAT_TIMEOUT_MS). */
|
|
51
|
+
heartbeatIntervalMs?: number;
|
|
46
52
|
now?: () => number;
|
|
53
|
+
sleep?: (ms: number) => Promise<void>;
|
|
47
54
|
}
|
|
55
|
+
/** A stopped sandbox run's worker exits at its next (refused) gateway call; past this it is killed. */
|
|
56
|
+
export declare const NATIVE_SANDBOX_STOP_GRACE_MS = 5000;
|
|
48
57
|
export declare const NATIVE_SANDBOX_WORKER_EXITED = "native_sandbox_worker_exited";
|
|
49
58
|
export declare const NATIVE_SANDBOX_DEADLINE = "native_sandbox_deadline_exceeded";
|
|
50
59
|
export declare class NativeSandboxExecutor implements Executor {
|
|
@@ -56,11 +65,17 @@ export declare class NativeSandboxExecutor implements Executor {
|
|
|
56
65
|
private now;
|
|
57
66
|
private finishContext;
|
|
58
67
|
start(runId: string): Promise<void>;
|
|
68
|
+
/** Refreshes a still-live run's heartbeat while `work` runs. */
|
|
69
|
+
private whileHeartbeating;
|
|
70
|
+
/** The run is still live and its session open: a worker launched now would have work to do. */
|
|
71
|
+
private stillWanted;
|
|
59
72
|
/** Watches one worker in this process: its exit, and its deadline. */
|
|
60
73
|
private watch;
|
|
61
74
|
private onWorkerExit;
|
|
62
75
|
private endSession;
|
|
63
76
|
stop(runId: string, reason?: string): Promise<void>;
|
|
77
|
+
/** Waits up to stopGraceMs for the run's worker to exit or be gone; false when it still runs. Never throws. */
|
|
78
|
+
private awaitWorkerExit;
|
|
64
79
|
recover(handle: PersistedExecutionHandle): Promise<ExecutionRecoveryResult>;
|
|
65
80
|
/** Startup janitor: removes worker containers whose runs have ended or passed their deadline. Starts the warm pool. */
|
|
66
81
|
launch(): Promise<void>;
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* worker, and returns. Whichever gateway replica serves the worker's
|
|
8
8
|
* `finish` makes the terminal write; this executor only watches for a
|
|
9
9
|
* worker that exits without one, and for its deadline.
|
|
10
|
-
* - stop: ends the session (every further gateway
|
|
11
|
-
*
|
|
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.
|
|
12
14
|
* - recover: answers the reconciler from the worker's container state, and
|
|
13
15
|
* never launches one.
|
|
14
16
|
* - launch: the janitor removes workers whose runs have ended.
|
|
@@ -16,11 +18,14 @@
|
|
|
16
18
|
import { logger } from "../core/logger.js";
|
|
17
19
|
import { failNativeRun, nativeRunIntegrations, RunCancelledError, NATIVE_SANDBOX_BACKEND, } from "../core/runner.js";
|
|
18
20
|
import { nativeRunLabel } from "./docker-isolation.js";
|
|
21
|
+
import { HEARTBEAT_TIMEOUT_MS } from "../core/timing.js";
|
|
19
22
|
import { startSandboxRun } from "./launch.js";
|
|
20
23
|
import { NATIVE_SANDBOX_CAPACITY } from "./kubernetes-launcher.js";
|
|
21
24
|
import { PrismaGatewayLedger } from "./ledger.js";
|
|
22
25
|
const executorLog = logger.child({ module: "native-sandbox-executor" });
|
|
23
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;
|
|
24
29
|
export const NATIVE_SANDBOX_WORKER_EXITED = "native_sandbox_worker_exited";
|
|
25
30
|
export const NATIVE_SANDBOX_DEADLINE = "native_sandbox_deadline_exceeded";
|
|
26
31
|
export class NativeSandboxExecutor {
|
|
@@ -56,7 +61,17 @@ export class NativeSandboxExecutor {
|
|
|
56
61
|
let outcome;
|
|
57
62
|
try {
|
|
58
63
|
const url = launcher.resolveGatewayUrl ? await launcher.resolveGatewayUrl() : gatewayUrl;
|
|
59
|
-
|
|
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
|
+
}));
|
|
60
75
|
}
|
|
61
76
|
catch (err) {
|
|
62
77
|
// A concurrent start won the race for the run's one session: attach to its worker.
|
|
@@ -78,6 +93,42 @@ export class NativeSandboxExecutor {
|
|
|
78
93
|
});
|
|
79
94
|
this.watch(runId, outcome.handle, session.deadlineAt);
|
|
80
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
|
+
}
|
|
81
132
|
/** Watches one worker in this process: its exit, and its deadline. */
|
|
82
133
|
watch(runId, handle, deadlineAt) {
|
|
83
134
|
if (this.watching.has(runId))
|
|
@@ -129,8 +180,14 @@ export class NativeSandboxExecutor {
|
|
|
129
180
|
// worker exit, and its exit watcher must then find the run already ended (and leave it
|
|
130
181
|
// cancelled) rather than record the exit as a failure.
|
|
131
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.
|
|
132
186
|
const sessionId = await this.endSession(runId, "cancelled");
|
|
133
|
-
await this.
|
|
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
|
+
}
|
|
134
191
|
if (sessionId) {
|
|
135
192
|
// Its delegations' children belong to the server's executor; a child left running would
|
|
136
193
|
// spend on behalf of a parent that no longer waits for it.
|
|
@@ -140,8 +197,25 @@ export class NativeSandboxExecutor {
|
|
|
140
197
|
});
|
|
141
198
|
}
|
|
142
199
|
}
|
|
200
|
+
// Removed, never returned to a warm pool: a claimed pool worker is discarded with its row.
|
|
143
201
|
await this.options.launcher.remove(runId).catch(() => { });
|
|
144
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
|
+
}
|
|
145
219
|
async recover(handle) {
|
|
146
220
|
if (handle.backend !== NATIVE_SANDBOX_BACKEND) {
|
|
147
221
|
return { state: "lost", reason: `NativeSandboxExecutor cannot recover backend "${handle.backend}".` };
|
|
@@ -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
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* worker's diagnostics only.
|
|
7
7
|
*
|
|
8
8
|
* Frames, worker → control: { id, request }
|
|
9
|
-
* Frames, control → worker: { id, ok: true, result } | { id, ok: false, error: { code, message } }
|
|
9
|
+
* Frames, control → worker: { id, ok: true, result } | { id, ok: false, error: { code, message, causeCode? } }
|
|
10
10
|
* | { id, event } (llm.stream events) | { id, end: true }
|
|
11
11
|
*/
|
|
12
12
|
import type { Readable, Writable } from "node:stream";
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* worker's diagnostics only.
|
|
7
7
|
*
|
|
8
8
|
* Frames, worker → control: { id, request }
|
|
9
|
-
* Frames, control → worker: { id, ok: true, result } | { id, ok: false, error: { code, message } }
|
|
9
|
+
* Frames, control → worker: { id, ok: true, result } | { id, ok: false, error: { code, message, causeCode? } }
|
|
10
10
|
* | { id, event } (llm.stream events) | { id, end: true }
|
|
11
11
|
*/
|
|
12
12
|
import { spawn } from "node:child_process";
|
|
13
|
-
import { GatewayError, MAX_MESSAGE_BYTES, ProtocolError, } from "./protocol.js";
|
|
13
|
+
import { GatewayError, MAX_MESSAGE_BYTES, ProtocolError, toGatewayError, } from "./protocol.js";
|
|
14
14
|
/** Splits a byte stream into lines, refusing any line over the cap. */
|
|
15
15
|
export function readLines(stream, onLine, onError) {
|
|
16
16
|
let buffer = "";
|
|
@@ -36,9 +36,10 @@ function writeLine(stream, frame) {
|
|
|
36
36
|
throw new ProtocolError("message_too_large");
|
|
37
37
|
stream.write(`${line}\n`);
|
|
38
38
|
}
|
|
39
|
+
/** An error as the worker sees it: exactly what the HTTP path carries (protocol.ts toGatewayError). */
|
|
39
40
|
const errorFrame = (err) => {
|
|
40
|
-
const
|
|
41
|
-
return { code
|
|
41
|
+
const { code, message, causeCode } = toGatewayError(err);
|
|
42
|
+
return { code, message, ...(causeCode ? { causeCode } : {}) };
|
|
42
43
|
};
|
|
43
44
|
/** The worker's side: requests out on `output`, answers in on `input`. */
|
|
44
45
|
export function createStdioTransport(input, output) {
|
|
@@ -62,7 +63,7 @@ export function createStdioTransport(input, output) {
|
|
|
62
63
|
if (!entry)
|
|
63
64
|
return;
|
|
64
65
|
const error = frame.error
|
|
65
|
-
? new GatewayError(frame.error.code, frame.error.message)
|
|
66
|
+
? new GatewayError(frame.error.code, frame.error.message, typeof frame.error.causeCode === "string" ? frame.error.causeCode : undefined)
|
|
66
67
|
: undefined;
|
|
67
68
|
if (entry.kind === "call") {
|
|
68
69
|
pending.delete(frame.id);
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* size (only ever unclaimed ones), and replenishes the deficit. Claims work from any process.
|
|
12
12
|
*/
|
|
13
13
|
import type { NativeWorkerState } from "./docker-launcher.js";
|
|
14
|
-
import type { WorkerHandle } from "./launch.js";
|
|
14
|
+
import type { WorkerHandle, WorkerLaunchOptions } from "./launch.js";
|
|
15
15
|
import type { WorkerInput } from "./protocol.js";
|
|
16
16
|
import type { ManagedWorkerLauncher } from "./sandbox-executor.js";
|
|
17
17
|
import type { PrismaWarmPoolLedger } from "./warm-pool-ledger.js";
|
|
@@ -73,7 +73,7 @@ export declare class PooledWorkerLauncher implements ManagedWorkerLauncher {
|
|
|
73
73
|
private get cold();
|
|
74
74
|
private now;
|
|
75
75
|
private sleep;
|
|
76
|
-
launch(input: WorkerInput): Promise<WorkerHandle>;
|
|
76
|
+
launch(input: WorkerInput, launchOptions?: WorkerLaunchOptions): Promise<WorkerHandle>;
|
|
77
77
|
private confirmGone;
|
|
78
78
|
/** Removes a pool worker and its row. */
|
|
79
79
|
private discard;
|
|
@@ -52,7 +52,7 @@ export class PooledWorkerLauncher {
|
|
|
52
52
|
sleep(ms) {
|
|
53
53
|
return (this.options.sleep ?? ((t) => new Promise((resolve) => setTimeout(resolve, t))))(ms);
|
|
54
54
|
}
|
|
55
|
-
async launch(input) {
|
|
55
|
+
async launch(input, launchOptions) {
|
|
56
56
|
const runId = input.runId;
|
|
57
57
|
const claimed = this.options.size > 0
|
|
58
58
|
? await this.options.ledger
|
|
@@ -66,7 +66,7 @@ export class PooledWorkerLauncher {
|
|
|
66
66
|
if (this.options.size > 0)
|
|
67
67
|
poolLog.info({ runId, event: "miss" }, "no idle warm worker; launching cold");
|
|
68
68
|
this.nudge();
|
|
69
|
-
return this.cold.launch(input);
|
|
69
|
+
return this.cold.launch(input, launchOptions);
|
|
70
70
|
}
|
|
71
71
|
const token = claimed.id;
|
|
72
72
|
this.nudge();
|
|
@@ -74,7 +74,7 @@ export class PooledWorkerLauncher {
|
|
|
74
74
|
if (!intact) {
|
|
75
75
|
poolLog.warn({ runId, worker: claimed.name, event: "fallback" }, "claimed warm worker failed re-attest");
|
|
76
76
|
await this.discard(token);
|
|
77
|
-
return this.cold.launch(input);
|
|
77
|
+
return this.cold.launch(input, launchOptions);
|
|
78
78
|
}
|
|
79
79
|
try {
|
|
80
80
|
await this.cold.deliver(token, input);
|
|
@@ -88,7 +88,7 @@ export class PooledWorkerLauncher {
|
|
|
88
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
89
|
}
|
|
90
90
|
await this.options.ledger.remove(token);
|
|
91
|
-
return this.cold.launch(input);
|
|
91
|
+
return this.cold.launch(input, launchOptions);
|
|
92
92
|
}
|
|
93
93
|
if (this.networkReadyAtLaunch === false)
|
|
94
94
|
await this.options.onDelivered?.(runId);
|