@wardby/cli 0.7.0 → 0.9.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 +5 -5
- package/README.md +6 -1
- package/deploy/local/docker-compose.quickstart-coding.yml +11 -0
- package/dist/claude-coding-worker/driver.d.ts +12 -0
- package/dist/claude-coding-worker/driver.js +29 -7
- package/dist/claude-coding-worker/main.js +5 -1
- package/dist/claude-coding-worker/sdk.js +17 -1
- package/dist/claude-plugin/command.d.ts +64 -0
- package/dist/claude-plugin/command.js +379 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +2 -1
- package/dist/cli.js +71 -3
- package/dist/coding/code-host.d.ts +16 -0
- package/dist/coding/code-host.js +60 -0
- package/dist/coding/mcp-token.d.ts +12 -0
- package/dist/coding/mcp-token.js +26 -0
- package/dist/coding/protocol.js +2 -0
- package/dist/coding/redact.js +6 -1
- package/dist/coding/transcript/recorder.d.ts +5 -4
- package/dist/coding/transcript/recorder.js +3 -9
- package/dist/coding-worker/debug-trace.d.ts +3 -1
- package/dist/coding-worker/debug-trace.js +4 -2
- package/dist/coding-worker/driver.js +8 -1
- package/dist/coding-worker/main.js +5 -1
- package/dist/coding-worker/mcp-config.d.ts +27 -0
- package/dist/coding-worker/mcp-config.js +32 -0
- package/dist/coding-worker/sdk.d.ts +36 -0
- package/dist/coding-worker/sdk.js +28 -1
- package/dist/coding-worker/types.d.ts +4 -0
- package/dist/config/mcp-call-limit.d.ts +8 -0
- package/dist/config/mcp-call-limit.js +18 -0
- package/dist/config/mcp-servers.d.ts +8 -0
- package/dist/config/mcp-servers.js +12 -0
- package/dist/config/test-simulators.d.ts +10 -0
- package/dist/config/test-simulators.js +28 -0
- package/dist/config/tool-timeout.d.ts +1 -0
- package/dist/config/tool-timeout.js +18 -0
- package/dist/core/coding-mcp.d.ts +36 -0
- package/dist/core/coding-mcp.js +253 -0
- package/dist/core/coding-repository.d.ts +46 -0
- package/dist/core/coding-repository.js +92 -0
- package/dist/core/delegation-slots.d.ts +27 -0
- package/dist/core/delegation-slots.js +44 -0
- package/dist/core/dispatch.d.ts +6 -2
- package/dist/core/dispatch.js +39 -11
- package/dist/core/engine-native.d.ts +2 -0
- package/dist/core/engine-native.js +13 -0
- package/dist/core/error-code.js +2 -0
- package/dist/core/grants.d.ts +28 -3
- package/dist/core/grants.js +46 -4
- package/dist/core/host-events.js +7 -3
- package/dist/core/host-status.js +2 -2
- package/dist/core/issue-status.js +4 -3
- package/dist/core/listen-address.d.ts +10 -0
- package/dist/core/listen-address.js +14 -0
- package/dist/core/mark-ready.d.ts +1 -1
- package/dist/core/mark-ready.js +1 -1
- package/dist/core/mcp-client/client.d.ts +47 -0
- package/dist/core/mcp-client/client.js +141 -0
- package/dist/core/mcp-client/jsonrpc.d.ts +15 -0
- package/dist/core/mcp-client/jsonrpc.js +54 -0
- package/dist/core/mcp-client/results.d.ts +13 -0
- package/dist/core/mcp-client/results.js +42 -0
- package/dist/core/mcp-servers.d.ts +90 -0
- package/dist/core/mcp-servers.js +230 -0
- package/dist/core/mcp-tools.d.ts +108 -0
- package/dist/core/mcp-tools.js +287 -0
- package/dist/core/merge-order-check.js +38 -19
- package/dist/core/notification-dispatcher.js +2 -1
- package/dist/core/power-idle.d.ts +8 -2
- package/dist/core/power-idle.js +17 -3
- package/dist/core/power.d.ts +5 -0
- package/dist/core/power.js +5 -0
- package/dist/core/related-pull-requests.js +26 -10
- package/dist/core/retention-days.d.ts +5 -0
- package/dist/core/retention-days.js +14 -0
- package/dist/core/review-fix.js +2 -2
- package/dist/core/review-host-tools.js +3 -1
- package/dist/core/run-steps/config.d.ts +25 -0
- package/dist/core/run-steps/config.js +40 -0
- package/dist/core/run-steps/control-chars.d.ts +16 -0
- package/dist/core/run-steps/control-chars.js +26 -0
- package/dist/core/run-steps/detail.d.ts +32 -0
- package/dist/core/run-steps/detail.js +240 -0
- package/dist/core/run-steps/extract.d.ts +52 -0
- package/dist/core/run-steps/extract.js +467 -0
- package/dist/core/run-steps/ingress.d.ts +51 -0
- package/dist/core/run-steps/ingress.js +160 -0
- package/dist/core/run-steps/last-step.d.ts +15 -0
- package/dist/core/run-steps/last-step.js +35 -0
- package/dist/core/run-steps/native.d.ts +48 -0
- package/dist/core/run-steps/native.js +164 -0
- package/dist/core/run-steps/seen.d.ts +16 -0
- package/dist/core/run-steps/seen.js +35 -0
- package/dist/core/run-steps/summarize.d.ts +57 -0
- package/dist/core/run-steps/summarize.js +304 -0
- package/dist/core/run-steps/sweep.d.ts +21 -0
- package/dist/core/run-steps/sweep.js +41 -0
- package/dist/core/run-steps/types.d.ts +89 -0
- package/dist/core/run-steps/types.js +39 -0
- package/dist/core/run-steps/wire.d.ts +35 -0
- package/dist/core/run-steps/wire.js +47 -0
- package/dist/core/run-steps/writer.d.ts +49 -0
- package/dist/core/run-steps/writer.js +279 -0
- package/dist/core/runner.d.ts +70 -16
- package/dist/core/runner.js +296 -74
- package/dist/core/secret-broker-config.d.ts +2 -0
- package/dist/core/secret-broker-config.js +1 -1
- package/dist/core/secret-guard.d.ts +5 -2
- package/dist/core/secret-guard.js +22 -7
- package/dist/core/secrets.d.ts +11 -1
- package/dist/core/secrets.js +27 -1
- package/dist/core/serializable-retry.d.ts +18 -0
- package/dist/core/serializable-retry.js +43 -0
- package/dist/core/tool-admin.d.ts +5 -1
- package/dist/core/tool-admin.js +7 -2
- package/dist/core/tool-names.d.ts +13 -0
- package/dist/core/tool-names.js +20 -0
- package/dist/core/webhooks.d.ts +4 -0
- package/dist/core/webhooks.js +68 -49
- package/dist/core/workflow-run-events.js +2 -1
- package/dist/generated/prisma/browser.d.ts +39 -0
- package/dist/generated/prisma/client.d.ts +39 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +80 -50
- package/dist/generated/prisma/enums.d.ts +6 -0
- package/dist/generated/prisma/enums.js +5 -0
- package/dist/generated/prisma/internal/class.d.ts +77 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +632 -1
- package/dist/generated/prisma/internal/prismaNamespace.js +94 -3
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +98 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +94 -3
- package/dist/generated/prisma/models/Agent.d.ts +372 -1
- package/dist/generated/prisma/models/AgentMcpServer.d.ts +1412 -0
- package/dist/generated/prisma/models/AgentMcpServer.js +1 -0
- package/dist/generated/prisma/models/AgentRepository.d.ts +40 -1
- package/dist/generated/prisma/models/AuthUser.d.ts +1 -1
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +0 -7
- package/dist/generated/prisma/models/CodingProxySession.d.ts +159 -2
- package/dist/generated/prisma/models/CodingRun.d.ts +76 -1
- package/dist/generated/prisma/models/HostProvider.d.ts +2176 -0
- package/dist/generated/prisma/models/HostProvider.js +1 -0
- package/dist/generated/prisma/models/HostProviderCredential.d.ts +1285 -0
- package/dist/generated/prisma/models/HostProviderCredential.js +1 -0
- package/dist/generated/prisma/models/HostProviderOperation.d.ts +1470 -0
- package/dist/generated/prisma/models/HostProviderOperation.js +1 -0
- package/dist/generated/prisma/models/HostProviderTrustedAccount.d.ts +1145 -0
- package/dist/generated/prisma/models/HostProviderTrustedAccount.js +1 -0
- package/dist/generated/prisma/models/McpServer.d.ts +1500 -0
- package/dist/generated/prisma/models/McpServer.js +1 -0
- package/dist/generated/prisma/models/NativeGatewayCall.d.ts +39 -1
- package/dist/generated/prisma/models/Principal.d.ts +167 -0
- package/dist/generated/prisma/models/ResourceGrant.d.ts +1 -1
- package/dist/generated/prisma/models/Run.d.ts +503 -1
- package/dist/generated/prisma/models/RunStep.d.ts +1453 -0
- package/dist/generated/prisma/models/RunStep.js +1 -0
- package/dist/generated/prisma/models/Secret.d.ts +387 -0
- package/dist/generated/prisma/models/Tool.d.ts +223 -1
- package/dist/generated/prisma/models.d.ts +7 -0
- package/dist/help/search.js +12 -3
- package/dist/help-index.json +812 -52
- package/dist/import/create.js +16 -1
- package/dist/import/neutral-schema.d.ts +1 -0
- package/dist/import/neutral-schema.js +2 -0
- package/dist/mcp/auth/access.d.ts +1 -0
- package/dist/mcp/auth/grants-cli.d.ts +6 -4
- package/dist/mcp/auth/grants-cli.js +42 -3
- package/dist/mcp/auth/resource-server.d.ts +3 -1
- package/dist/mcp/auth/resource-server.js +17 -2
- package/dist/mcp/context.d.ts +6 -0
- package/dist/mcp/index.js +21 -0
- package/dist/mcp/tools/agents.js +135 -20
- package/dist/mcp/tools/grants.js +66 -28
- package/dist/mcp/tools/host-providers.d.ts +2 -0
- package/dist/mcp/tools/host-providers.js +215 -0
- package/dist/mcp/tools/mcp-servers.d.ts +26 -0
- package/dist/mcp/tools/mcp-servers.js +481 -0
- package/dist/mcp/tools/power.js +2 -2
- package/dist/mcp/tools/repositories.js +50 -10
- package/dist/mcp/tools/run-access.d.ts +7 -0
- package/dist/mcp/tools/run-access.js +14 -0
- package/dist/mcp/tools/run-steps.d.ts +16 -0
- package/dist/mcp/tools/run-steps.js +85 -0
- package/dist/mcp/tools/runs.js +186 -18
- package/dist/mcp/tools/secret-elicitation.d.ts +2 -0
- package/dist/mcp/tools/secret-elicitation.js +25 -2
- package/dist/mcp/tools/secrets.js +38 -4
- package/dist/mcp/tools/tools.d.ts +6 -4
- package/dist/mcp/tools/tools.js +40 -8
- package/dist/mcp/tools/trigger.js +28 -6
- package/dist/mcp/transport/http-limits.d.ts +2 -0
- package/dist/mcp/transport/http-limits.js +6 -2
- package/dist/mcp/webhooks/ingress.js +10 -0
- package/dist/native-worker/gateway-server.d.ts +18 -1
- package/dist/native-worker/gateway-server.js +38 -9
- package/dist/native-worker/gateway.d.ts +5 -0
- package/dist/native-worker/gateway.js +13 -1
- package/dist/native-worker/http-server.d.ts +6 -4
- package/dist/native-worker/http-server.js +53 -8
- package/dist/native-worker/ledger.d.ts +6 -2
- package/dist/native-worker/ledger.js +5 -4
- package/dist/native-worker/protocol.d.ts +1 -0
- package/dist/native-worker/protocol.js +4 -1
- package/dist/providers/coding-proxy/capability-hash.d.ts +2 -0
- package/dist/providers/coding-proxy/capability-hash.js +5 -0
- package/dist/providers/coding-proxy/mcp-route.d.ts +34 -0
- package/dist/providers/coding-proxy/mcp-route.js +166 -0
- package/dist/providers/coding-proxy/memory-ledger.d.ts +2 -0
- package/dist/providers/coding-proxy/memory-ledger.js +16 -0
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +1 -0
- package/dist/providers/coding-proxy/prisma-ledger.js +36 -2
- package/dist/providers/coding-proxy/proxy.d.ts +33 -1
- package/dist/providers/coding-proxy/proxy.js +266 -75
- package/dist/providers/coding-proxy/run-step-forwarder.d.ts +42 -0
- package/dist/providers/coding-proxy/run-step-forwarder.js +206 -0
- package/dist/providers/coding-proxy/runtime.d.ts +13 -0
- package/dist/providers/coding-proxy/runtime.js +87 -4
- package/dist/providers/coding-proxy/server.d.ts +3 -0
- package/dist/providers/coding-proxy/server.js +10 -0
- package/dist/providers/coding-proxy/types.d.ts +18 -0
- package/dist/providers/engine/types.d.ts +26 -0
- package/dist/providers/executor/composition.js +5 -0
- package/dist/providers/executor/container.d.ts +47 -1
- package/dist/providers/executor/container.js +84 -8
- package/dist/providers/host-tools/invoke.d.ts +37 -0
- package/dist/providers/host-tools/invoke.js +117 -0
- package/dist/providers/host-tools/operations.d.ts +65 -0
- package/dist/providers/host-tools/operations.js +149 -0
- package/dist/providers/host-tools/registry.d.ts +79 -0
- package/dist/providers/host-tools/registry.js +362 -0
- package/dist/providers/host-tools/request-guard.d.ts +17 -0
- package/dist/providers/host-tools/request-guard.js +99 -0
- package/dist/providers/host-tools/self-test.d.ts +64 -0
- package/dist/providers/host-tools/self-test.js +142 -0
- package/dist/providers/jobs/docker-isolation.d.ts +2 -2
- package/dist/providers/jobs/docker-isolation.js +30 -8
- package/dist/providers/jobs/docker.d.ts +8 -0
- package/dist/providers/jobs/docker.js +33 -9
- package/dist/providers/jobs/kubernetes-isolation.d.ts +3 -1
- package/dist/providers/jobs/kubernetes-isolation.js +15 -2
- package/dist/providers/jobs/kubernetes.d.ts +13 -0
- package/dist/providers/jobs/kubernetes.js +28 -5
- package/dist/providers/jobs/types.d.ts +6 -0
- package/dist/providers/review-host/types.d.ts +6 -4
- package/dist/providers/review-host/types.js +1 -1
- package/dist/providers/vcs/git.d.ts +6 -2
- package/dist/providers/vcs/git.js +15 -4
- package/dist/providers/vcs/github.d.ts +2 -71
- package/dist/providers/vcs/github.js +8 -11
- package/dist/providers/vcs/index.d.ts +1 -1
- package/dist/providers/vcs/index.js +1 -1
- package/dist/providers/vcs/remote.d.ts +4 -0
- package/dist/providers/vcs/remote.js +7 -1
- package/dist/providers/vcs/routing.d.ts +12 -6
- package/dist/providers/vcs/routing.js +21 -6
- package/dist/providers/vcs/types.d.ts +71 -3
- package/dist/quickstart/index.d.ts +5 -0
- package/dist/quickstart/index.js +21 -0
- package/dist/quickstart-images.json +1 -1
- package/dist/sandbox/eval-core.d.ts +12 -2
- package/dist/sandbox/eval-core.js +20 -4
- package/dist/sandbox/host-functions.d.ts +4 -0
- package/dist/sandbox/host-functions.js +7 -2
- package/dist/sandbox/limits.d.ts +4 -1
- package/dist/sandbox/limits.js +4 -1
- package/dist/sandbox/run-in-sandbox.d.ts +6 -0
- package/dist/sandbox/run-in-sandbox.js +14 -2
- package/dist/sandbox/safe-fetch.d.ts +2 -0
- package/dist/sandbox/safe-fetch.js +1 -1
- package/dist/sandbox/secret-broker.d.ts +1 -0
- package/dist/sandbox/secret-broker.js +1 -1
- package/dist/sandbox/tool-timeout.d.ts +32 -0
- package/dist/sandbox/tool-timeout.js +52 -0
- package/dist/sandbox/user-tool.d.ts +8 -1
- package/dist/sandbox/user-tool.js +9 -3
- package/dist/serve.js +26 -18
- package/dist/viewer/api-schema.d.ts +66 -0
- package/dist/viewer/api-schema.js +31 -0
- package/dist/viewer/graph.js +2 -1
- package/dist/viewer/openapi.js +2 -1
- package/dist/viewer/run-detail.d.ts +2 -0
- package/dist/viewer/run-detail.js +26 -1
- package/dist/wardby-bin.js +5 -0
- package/docs/README.md +11 -1
- package/docs/agent-recipes.md +37 -4
- package/docs/architecture-runtime.md +1 -1
- package/docs/claude-code-plugin.md +208 -0
- package/docs/code-review-agents.md +6 -0
- package/docs/coding-agent-setup.md +94 -1
- package/docs/coding-worker-byo-images.md +4 -2
- package/docs/coding-worker-isolation.md +94 -7
- package/docs/debugging-agent-runs.md +354 -0
- package/docs/getting-started-gke.md +33 -11
- package/docs/getting-started-identity-provider.md +43 -37
- package/docs/getting-started.md +8 -0
- package/docs/host-providers.md +294 -0
- package/docs/mcp-servers.md +263 -0
- package/docs/native-sandbox.md +31 -9
- package/docs/security-deployment.md +161 -31
- package/docs/tool-secrets.md +3 -0
- package/docs/viewer-api.md +27 -12
- package/docs/viewer-api.openapi.yml +78 -2
- package/help/admin-viewer.md +5 -0
- package/help/agent-recipes.md +33 -2
- package/help/brokered-secrets.md +3 -0
- package/help/build-claude-code-image.md +1 -1
- package/help/claude-code-run-pane.md +63 -0
- package/help/deploy-gke.md +5 -0
- package/help/errors/coding-mcp-gateway-unavailable.md +46 -0
- package/help/errors/coding-mcp-pins-unavailable.md +35 -0
- package/help/errors/database-auth-invalid.md +1 -1
- package/help/errors/host-provider-error.md +40 -0
- package/help/errors/host-tool-failed.md +36 -0
- package/help/errors/mcp-call-limit.md +22 -0
- package/help/errors/mcp-call-timeout.md +22 -0
- package/help/errors/mcp-server-auth-failed.md +22 -0
- package/help/errors/mcp-server-credentials-unavailable.md +45 -0
- package/help/errors/mcp-server-error.md +28 -0
- package/help/errors/mcp-server-unreachable.md +22 -0
- package/help/errors/mcp-servers-off.md +21 -0
- package/help/errors/mcp-tool-not-allowed.md +26 -0
- package/help/errors/mcp-tool-removed.md +22 -0
- package/help/errors/mcp-unavailable.md +25 -0
- package/help/errors/native-sandbox-capacity.md +1 -1
- package/help/errors/native-sandbox-deadline-exceeded.md +1 -1
- package/help/errors/native-sandbox-docker-failed.md +1 -1
- package/help/errors/native-sandbox-gateway-error.md +1 -1
- package/help/errors/native-sandbox-gateway-unavailable.md +1 -1
- package/help/errors/native-sandbox-image-not-pinned.md +1 -1
- package/help/errors/native-sandbox-isolation-mismatch.md +1 -1
- package/help/errors/native-sandbox-network-unenforced.md +1 -1
- package/help/errors/native-sandbox-requires-catalog.md +1 -1
- package/help/errors/native-sandbox-unavailable.md +1 -1
- package/help/errors/native-sandbox-warm-delivery-failed.md +1 -1
- package/help/errors/native-sandbox-worker-exited.md +1 -1
- package/help/errors/native-sandbox-worker-lost.md +1 -1
- package/help/errors/native-sandbox-worker-unready.md +1 -1
- package/help/errors/repository-not-listed.md +36 -0
- package/help/errors/repository-required.md +36 -0
- package/help/errors/tool-timeout.md +38 -0
- package/help/host-providers.md +40 -0
- package/help/identity-and-access.md +4 -1
- package/help/low-power-mode.md +19 -8
- package/help/mcp-servers.md +73 -0
- package/help/mcp.md +9 -2
- package/help/native-capabilities.md +27 -1
- package/help/native-sandbox.md +1 -1
- package/help/operating-agents.md +16 -0
- package/help/related-pull-requests.md +7 -3
- package/help/repo-instructions-and-skills.md +3 -1
- package/help/resume-runs-locally.md +7 -0
- package/help/run-steps.md +140 -0
- package/help/stop-run.md +1 -1
- package/help/troubleshooting/coding-workers.md +9 -1
- package/help/troubleshooting/debugging-agent-runs.md +29 -0
- package/package.json +2 -1
- package/prisma/migrations/20261102000000_host_providers/migration.sql +56 -0
- package/prisma/migrations/20261104000000_coding_agent_repository_list/migration.sql +5 -0
- package/prisma/migrations/20261106000000_tool_timeout/migration.sql +4 -0
- package/prisma/migrations/20261107000000_mcp_servers/migration.sql +33 -0
- package/prisma/migrations/20261108000000_coding_mcp/migration.sql +5 -0
- package/prisma/migrations/20261109000000_task_run_index/migration.sql +3 -0
- package/prisma/migrations/20261110000000_run_steps/migration.sql +26 -0
- package/prisma/migrations/20261111000000_run_steps_viewer_notify/migration.sql +59 -0
- package/prisma/migrations/20261112000000_run_steps_detail/migration.sql +9 -0
- package/prisma/schema.prisma +211 -14
- package/scripts/git-askpass.sh +1 -1
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hands coding runs' steps from the proxy to the control plane's run-steps listener
|
|
3
|
+
* (`POST <WARDBY_RUN_STEPS_URL>/internal/run-steps`, `Authorization: Bearer <the run capability>`).
|
|
4
|
+
* The proxy forwards raw step text over the authenticated in-cluster listener; the control plane
|
|
5
|
+
* redacts by exact value before anything is stored. Because the body is raw, this class never logs,
|
|
6
|
+
* echoes, or puts into an error any part of the body, the step text, or the capability: log lines
|
|
7
|
+
* carry a fixed reason and a count only.
|
|
8
|
+
*
|
|
9
|
+
* A bounded in-memory queue, drained one POST at a time off the relay path: a full queue or a slow
|
|
10
|
+
* or failing control plane drops steps and never delays a model request.
|
|
11
|
+
*/
|
|
12
|
+
import { logger } from "../../core/logger.js";
|
|
13
|
+
import { MAX_RESULTS_PER_POST, MAX_RUN_STEPS_POST_BYTES, MAX_STEPS_PER_POST, RUN_STEPS_INGRESS_PATH, } from "../../core/run-steps/types.js";
|
|
14
|
+
const forwarderLog = logger.child({ module: "coding-proxy" });
|
|
15
|
+
export const MAX_FORWARD_QUEUE = 256;
|
|
16
|
+
/** A queue holds at most this many bytes of (estimated) serialized submissions. */
|
|
17
|
+
export const MAX_FORWARD_QUEUE_BYTES = 8 * 1024 * 1024;
|
|
18
|
+
/** Margin over the listener's 15 s request limit (its write can take lock wait 5 s + transaction 10 s). */
|
|
19
|
+
const DEFAULT_TIMEOUT_MS = 20_000;
|
|
20
|
+
const DEFAULT_FLUSH_TIMEOUT_MS = 5_000;
|
|
21
|
+
/** The envelope around the arrays, with slack for the runId. */
|
|
22
|
+
const ENVELOPE_SLACK_BYTES = 1024;
|
|
23
|
+
/** A cheap upper-ish bound on a batch's serialized size, with no stringify on the caller's stack. */
|
|
24
|
+
function estimate(batch) {
|
|
25
|
+
let bytes = ENVELOPE_SLACK_BYTES;
|
|
26
|
+
for (const step of batch.steps) {
|
|
27
|
+
bytes += step.text.length * 3 + step.callId.length * 3 + 256;
|
|
28
|
+
if (step.kind === "tool")
|
|
29
|
+
bytes += (step.input?.length ?? 0) * 3;
|
|
30
|
+
}
|
|
31
|
+
for (const result of batch.results)
|
|
32
|
+
bytes += 128 + (result.output?.length ?? 0) * 3;
|
|
33
|
+
return bytes;
|
|
34
|
+
}
|
|
35
|
+
/** Splits by count and by serialized UTF-8 size, so no body passes MAX_RUN_STEPS_POST_BYTES. */
|
|
36
|
+
function split(batch, runId) {
|
|
37
|
+
const parts = [];
|
|
38
|
+
const base = ENVELOPE_SLACK_BYTES + Buffer.byteLength(runId);
|
|
39
|
+
let steps = [];
|
|
40
|
+
let size = base;
|
|
41
|
+
for (const step of batch.steps) {
|
|
42
|
+
const bytes = Buffer.byteLength(JSON.stringify(step)) + 1;
|
|
43
|
+
if (steps.length > 0 && (steps.length >= MAX_STEPS_PER_POST || size + bytes > MAX_RUN_STEPS_POST_BYTES)) {
|
|
44
|
+
parts.push({ steps, results: [] });
|
|
45
|
+
steps = [];
|
|
46
|
+
size = base;
|
|
47
|
+
}
|
|
48
|
+
steps.push(step);
|
|
49
|
+
size += bytes;
|
|
50
|
+
}
|
|
51
|
+
if (steps.length > 0)
|
|
52
|
+
parts.push({ steps, results: [] });
|
|
53
|
+
let results = [];
|
|
54
|
+
let resultBytes = base;
|
|
55
|
+
for (const result of batch.results) {
|
|
56
|
+
const bytes = Buffer.byteLength(JSON.stringify(result)) + 1;
|
|
57
|
+
if (results.length > 0 &&
|
|
58
|
+
(results.length >= MAX_RESULTS_PER_POST || resultBytes + bytes > MAX_RUN_STEPS_POST_BYTES)) {
|
|
59
|
+
parts.push({ steps: [], results });
|
|
60
|
+
results = [];
|
|
61
|
+
resultBytes = base;
|
|
62
|
+
}
|
|
63
|
+
results.push(result);
|
|
64
|
+
resultBytes += bytes;
|
|
65
|
+
}
|
|
66
|
+
if (results.length > 0)
|
|
67
|
+
parts.push({ steps: [], results });
|
|
68
|
+
return parts;
|
|
69
|
+
}
|
|
70
|
+
export class RunStepForwarder {
|
|
71
|
+
options;
|
|
72
|
+
queue = [];
|
|
73
|
+
queueBytes = 0;
|
|
74
|
+
draining;
|
|
75
|
+
scheduled = false;
|
|
76
|
+
closed = false;
|
|
77
|
+
inflight;
|
|
78
|
+
dropped = 0;
|
|
79
|
+
loggedReasons = new Set();
|
|
80
|
+
constructor(options) {
|
|
81
|
+
this.options = options;
|
|
82
|
+
}
|
|
83
|
+
submit(capability, runId, batch) {
|
|
84
|
+
try {
|
|
85
|
+
if (this.closed)
|
|
86
|
+
return this.drop("closed");
|
|
87
|
+
const size = estimate(batch);
|
|
88
|
+
if (this.queue.length >= (this.options.maxQueue ?? MAX_FORWARD_QUEUE) ||
|
|
89
|
+
this.queueBytes + size > (this.options.maxQueueBytes ?? MAX_FORWARD_QUEUE_BYTES)) {
|
|
90
|
+
return this.drop("queue_full");
|
|
91
|
+
}
|
|
92
|
+
this.queue.push({ capability, runId, batch, estimate: size });
|
|
93
|
+
this.queueBytes += size;
|
|
94
|
+
this.kick();
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
this.drop("submit_failed");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Resolves once everything submitted so far was posted or dropped, or after a bound. Never rejects. */
|
|
101
|
+
async flush() {
|
|
102
|
+
let timer;
|
|
103
|
+
const bound = new Promise((resolve) => {
|
|
104
|
+
timer = setTimeout(resolve, this.options.flushTimeoutMs ?? DEFAULT_FLUSH_TIMEOUT_MS);
|
|
105
|
+
timer.unref();
|
|
106
|
+
});
|
|
107
|
+
const settled = (async () => {
|
|
108
|
+
while (this.draining || this.scheduled)
|
|
109
|
+
await (this.draining ?? new Promise((r) => setImmediate(r)));
|
|
110
|
+
})().catch(() => undefined);
|
|
111
|
+
await Promise.race([settled, bound]);
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
}
|
|
114
|
+
/** Stops accepting, waits at most the flush bound, then drops what is left and aborts the in-flight POST. */
|
|
115
|
+
async close() {
|
|
116
|
+
this.closed = true;
|
|
117
|
+
await this.flush();
|
|
118
|
+
const left = this.queue.length;
|
|
119
|
+
this.queue = [];
|
|
120
|
+
this.queueBytes = 0;
|
|
121
|
+
for (let n = 0; n < left; n += 1)
|
|
122
|
+
this.drop("closed");
|
|
123
|
+
this.inflight?.abort();
|
|
124
|
+
}
|
|
125
|
+
kick() {
|
|
126
|
+
if (this.draining || this.scheduled || this.queue.length === 0)
|
|
127
|
+
return;
|
|
128
|
+
this.scheduled = true;
|
|
129
|
+
// Off the caller's stack: the first serialization must not run on the relay path.
|
|
130
|
+
setImmediate(() => {
|
|
131
|
+
this.scheduled = false;
|
|
132
|
+
if (this.draining || this.queue.length === 0)
|
|
133
|
+
return;
|
|
134
|
+
this.draining = this.drain().finally(() => {
|
|
135
|
+
this.draining = undefined;
|
|
136
|
+
this.kick();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async drain() {
|
|
141
|
+
for (let next = this.queue.shift(); next; next = this.queue.shift()) {
|
|
142
|
+
this.queueBytes -= next.estimate;
|
|
143
|
+
if (this.closed) {
|
|
144
|
+
this.drop("closed");
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
let bodies;
|
|
148
|
+
try {
|
|
149
|
+
bodies = split(next.batch, next.runId).map((part) => JSON.stringify({ runId: next.runId, ...part }));
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
this.drop("serialize_failed");
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
for (const body of bodies) {
|
|
156
|
+
if (this.closed)
|
|
157
|
+
this.drop("closed");
|
|
158
|
+
else
|
|
159
|
+
await this.post(next.capability, body);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The capability goes in the Authorization header only. The response body is never read, and a
|
|
165
|
+
* failure is logged by a fixed reason: an error's or a response's text never reaches a log line.
|
|
166
|
+
*/
|
|
167
|
+
async post(capability, body) {
|
|
168
|
+
const controller = new AbortController();
|
|
169
|
+
this.inflight = controller;
|
|
170
|
+
const timer = setTimeout(() => controller.abort(), this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
171
|
+
timer.unref();
|
|
172
|
+
try {
|
|
173
|
+
const response = await (this.options.fetchImpl ?? fetch)(`${this.options.url}${RUN_STEPS_INGRESS_PATH}`, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: { authorization: `Bearer ${capability}`, "content-type": "application/json" },
|
|
176
|
+
body,
|
|
177
|
+
redirect: "error",
|
|
178
|
+
signal: controller.signal,
|
|
179
|
+
});
|
|
180
|
+
await response.body?.cancel().catch(() => undefined);
|
|
181
|
+
if (!response.ok)
|
|
182
|
+
this.drop(`http_${response.status}`);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
this.drop(this.closed ? "closed" : controller.signal.aborted ? "timeout" : "unreachable");
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
clearTimeout(timer);
|
|
189
|
+
if (this.inflight === controller)
|
|
190
|
+
this.inflight = undefined;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
drop(reason) {
|
|
194
|
+
this.dropped += 1;
|
|
195
|
+
const first = !this.loggedReasons.has(reason);
|
|
196
|
+
this.loggedReasons.add(reason);
|
|
197
|
+
if (first || this.dropped % 100 === 0) {
|
|
198
|
+
try {
|
|
199
|
+
(this.options.log ?? forwarderLog).info({ event: "run_steps.forward_dropped", reason, droppedParts: this.dropped }, "coding proxy: run steps not delivered to the control plane");
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// logging must never break the relay
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -14,6 +14,19 @@ import type { ProxyAuditSink } from "./types.js";
|
|
|
14
14
|
* JSON). Exported so its header-mapping can be unit-tested directly,
|
|
15
15
|
* without needing a live upstream. */
|
|
16
16
|
export declare function createRegistryUpstream(pinned: typeof globalThis.fetch): UpstreamFetch;
|
|
17
|
+
/**
|
|
18
|
+
* WARDBY_MCP_GATEWAY_URL: the native gateway's base URL, which the /mcp route forwards to (its
|
|
19
|
+
* /coding-mcp route). Unset or empty: /mcp answers 503 mcp_unavailable. Anything but an http(s) URL
|
|
20
|
+
* without credentials, query or fragment stops start-up (the value is not echoed).
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadMcpGatewayUrl(env: NodeJS.ProcessEnv): string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* WARDBY_RUN_STEPS_URL: the control plane's run-steps listener (WARDBY_RUN_STEPS_LISTEN on the
|
|
25
|
+
* server), which the proxy posts coding runs' steps to. Unset or empty: coding runs record no steps.
|
|
26
|
+
* Anything but an http(s) origin without credentials, path, query or fragment stops start-up (the
|
|
27
|
+
* value is not echoed, so a credential in it never reaches a log).
|
|
28
|
+
*/
|
|
29
|
+
export declare function loadRunStepsUrl(env: NodeJS.ProcessEnv): string | undefined;
|
|
17
30
|
export interface CodingProxyRuntimeOptions {
|
|
18
31
|
db: PrismaClient;
|
|
19
32
|
/** The package registry's own client, so its traffic (many concurrent
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { loadDependencyCacheConfig } from "../../config/dependency-cache.js";
|
|
2
2
|
import { createSimulatorModelFetch, createSimulatorRegistryFetch, startupTestSimulators, } from "../../config/test-simulators.js";
|
|
3
3
|
import { logger } from "../../core/logger.js";
|
|
4
|
+
import { loadRunStepsLevel } from "../../core/run-steps/config.js";
|
|
4
5
|
import { REGISTRY_ADAPTERS } from "../../coding/registry/adapters.js";
|
|
5
6
|
import { CODING_PROXY_ALIAS, CODING_PROXY_DENY_PORT, CODING_PROXY_PORT } from "../jobs/docker-isolation.js";
|
|
6
7
|
import { createBlobStore } from "../storage/index.js";
|
|
@@ -10,9 +11,11 @@ import { ModelRequestSeal } from "./dependency-cache/model-request-seal.js";
|
|
|
10
11
|
import { DependencyCacheService } from "./dependency-cache/service.js";
|
|
11
12
|
import { startDenyPortListener } from "./deny-port.js";
|
|
12
13
|
import { EnvironmentCredentialResolver } from "./environment-credentials.js";
|
|
14
|
+
import { createMcpRoute } from "./mcp-route.js";
|
|
13
15
|
import { createMockUpstream, mockUpstreamFromEnv } from "./mock-upstream.js";
|
|
14
16
|
import { CodingProxy } from "./proxy.js";
|
|
15
17
|
import { PrismaProxyLedger } from "./prisma-ledger.js";
|
|
18
|
+
import { RunStepForwarder } from "./run-step-forwarder.js";
|
|
16
19
|
import { OsvAudit } from "./registry/audit.js";
|
|
17
20
|
import { PrismaRegistryStore } from "./registry/prisma-store.js";
|
|
18
21
|
import { DEFAULT_PLAN_MAX_ENTRIES, DEFAULT_PLAN_TIMEOUT_MS } from "./registry/plan.js";
|
|
@@ -44,6 +47,60 @@ export function createRegistryUpstream(pinned) {
|
|
|
44
47
|
},
|
|
45
48
|
});
|
|
46
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* WARDBY_MCP_GATEWAY_URL: the native gateway's base URL, which the /mcp route forwards to (its
|
|
52
|
+
* /coding-mcp route). Unset or empty: /mcp answers 503 mcp_unavailable. Anything but an http(s) URL
|
|
53
|
+
* without credentials, query or fragment stops start-up (the value is not echoed).
|
|
54
|
+
*/
|
|
55
|
+
export function loadMcpGatewayUrl(env) {
|
|
56
|
+
const value = env.WARDBY_MCP_GATEWAY_URL?.trim();
|
|
57
|
+
if (!value)
|
|
58
|
+
return undefined;
|
|
59
|
+
let url;
|
|
60
|
+
try {
|
|
61
|
+
url = new URL(value);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
url = undefined;
|
|
65
|
+
}
|
|
66
|
+
if (!url ||
|
|
67
|
+
(url.protocol !== "http:" && url.protocol !== "https:") ||
|
|
68
|
+
url.username ||
|
|
69
|
+
url.password ||
|
|
70
|
+
url.search ||
|
|
71
|
+
url.hash) {
|
|
72
|
+
throw new Error("WARDBY_MCP_GATEWAY_URL must be an http(s) URL without credentials, query or fragment");
|
|
73
|
+
}
|
|
74
|
+
return url.href.replace(/\/+$/, "");
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* WARDBY_RUN_STEPS_URL: the control plane's run-steps listener (WARDBY_RUN_STEPS_LISTEN on the
|
|
78
|
+
* server), which the proxy posts coding runs' steps to. Unset or empty: coding runs record no steps.
|
|
79
|
+
* Anything but an http(s) origin without credentials, path, query or fragment stops start-up (the
|
|
80
|
+
* value is not echoed, so a credential in it never reaches a log).
|
|
81
|
+
*/
|
|
82
|
+
export function loadRunStepsUrl(env) {
|
|
83
|
+
const value = env.WARDBY_RUN_STEPS_URL?.trim();
|
|
84
|
+
if (!value)
|
|
85
|
+
return undefined;
|
|
86
|
+
let url;
|
|
87
|
+
try {
|
|
88
|
+
url = new URL(value);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
url = undefined;
|
|
92
|
+
}
|
|
93
|
+
if (!url ||
|
|
94
|
+
(url.protocol !== "http:" && url.protocol !== "https:") ||
|
|
95
|
+
url.username ||
|
|
96
|
+
url.password ||
|
|
97
|
+
url.pathname !== "/" ||
|
|
98
|
+
url.search ||
|
|
99
|
+
url.hash) {
|
|
100
|
+
throw new Error("WARDBY_RUN_STEPS_URL must be an http(s) URL without credentials, path, query or fragment");
|
|
101
|
+
}
|
|
102
|
+
return url.origin;
|
|
103
|
+
}
|
|
47
104
|
/** Starts the trusted proxy with the fixed worker-only Docker endpoint, plus the deny port the enforcement witness probes. */
|
|
48
105
|
export async function startConfiguredCodingProxy(options) {
|
|
49
106
|
const env = options.env ?? process.env;
|
|
@@ -57,6 +114,12 @@ export async function startConfiguredCodingProxy(options) {
|
|
|
57
114
|
const audit = mock
|
|
58
115
|
? (event) => options.audit?.({ ...event, mockUpstream: true })
|
|
59
116
|
: options.audit;
|
|
117
|
+
// Throws on an invalid setting, before anything listens.
|
|
118
|
+
const mcpGatewayUrl = loadMcpGatewayUrl(env);
|
|
119
|
+
// Throws on an invalid setting, before anything listens.
|
|
120
|
+
const runStepsUrl = loadRunStepsUrl(env);
|
|
121
|
+
// Throws on an invalid WARDBY_RUN_STEPS. At off no step text leaves the proxy.
|
|
122
|
+
const runStepsCeiling = loadRunStepsLevel(env);
|
|
60
123
|
// Throws (test_simulators_refused) on any refusal, before anything listens.
|
|
61
124
|
const simulators = startupTestSimulators("coding-proxy", env);
|
|
62
125
|
const buildPinnedFetch = options.createPinnedFetch ?? createPinnedProxyFetch;
|
|
@@ -123,8 +186,13 @@ export async function startConfiguredCodingProxy(options) {
|
|
|
123
186
|
logger.warn({ module: "coding-proxy-runtime", event: "proxy.dependency_cache_no_blob_store" }, "coding proxy: WARDBY_DEPENDENCY_CACHE=true but no BLOB_STORE is configured; the dependency cache is off");
|
|
124
187
|
}
|
|
125
188
|
const seal = modelRequestSeal;
|
|
189
|
+
// Coding runs' steps go to the control plane only when WARDBY_RUN_STEPS_URL is set and
|
|
190
|
+
// WARDBY_RUN_STEPS is not off; otherwise no
|
|
191
|
+
// submitter is passed and the proxy does no step extraction.
|
|
192
|
+
const steps = runStepsUrl && runStepsCeiling !== "off" ? new RunStepForwarder({ url: runStepsUrl }) : undefined;
|
|
193
|
+
const ledger = new PrismaProxyLedger(options.db);
|
|
126
194
|
const proxy = new CodingProxy({
|
|
127
|
-
ledger
|
|
195
|
+
ledger,
|
|
128
196
|
credentials: new EnvironmentCredentialResolver(env),
|
|
129
197
|
audit,
|
|
130
198
|
// WARDBY_TEST_SIMULATORS (test only; refused alongside the mock upstream): the scripted model,
|
|
@@ -136,6 +204,8 @@ export async function startConfiguredCodingProxy(options) {
|
|
|
136
204
|
: {}),
|
|
137
205
|
// The run's first model request closes its dependency cache (no-op for a run with it off).
|
|
138
206
|
...(seal ? { onModelRequest: (runId) => seal.onModelRequest(runId) } : {}),
|
|
207
|
+
...(steps ? { steps } : {}),
|
|
208
|
+
runStepsCeiling,
|
|
139
209
|
});
|
|
140
210
|
const startServer = options.startServer ?? startCodingProxyServer;
|
|
141
211
|
let server;
|
|
@@ -146,11 +216,13 @@ export async function startConfiguredCodingProxy(options) {
|
|
|
146
216
|
expectedHost: `${CODING_PROXY_ALIAS}:${CODING_PROXY_PORT}`,
|
|
147
217
|
registry,
|
|
148
218
|
...(dependencyCache ? { dependencyCache } : {}),
|
|
219
|
+
mcp: createMcpRoute({ ledger, gatewayUrl: mcpGatewayUrl }),
|
|
149
220
|
onRequest: options.onRequest,
|
|
150
221
|
});
|
|
151
222
|
}
|
|
152
223
|
catch (error) {
|
|
153
224
|
eviction?.stop();
|
|
225
|
+
await steps?.close();
|
|
154
226
|
throw error;
|
|
155
227
|
}
|
|
156
228
|
let deny;
|
|
@@ -161,15 +233,26 @@ export async function startConfiguredCodingProxy(options) {
|
|
|
161
233
|
// A proxy without its witness must not stay up: the launcher's gate would never be able to
|
|
162
234
|
// prove a policy is enforced, and every launch would fail at the gate instead of at startup.
|
|
163
235
|
eviction?.stop();
|
|
164
|
-
|
|
236
|
+
try {
|
|
237
|
+
await server.close();
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
await steps?.close();
|
|
241
|
+
}
|
|
165
242
|
throw error;
|
|
166
243
|
}
|
|
167
244
|
return {
|
|
168
245
|
port: server.port,
|
|
169
246
|
close: async () => {
|
|
170
247
|
eviction?.stop();
|
|
171
|
-
|
|
172
|
-
|
|
248
|
+
try {
|
|
249
|
+
await deny.close();
|
|
250
|
+
await server.close();
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
// Last: requests are done, so this is the final chance to hand over queued steps (bounded).
|
|
254
|
+
await steps?.close();
|
|
255
|
+
}
|
|
173
256
|
},
|
|
174
257
|
};
|
|
175
258
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
1
2
|
import type { ProxyAuditEvent } from "./types.js";
|
|
2
3
|
import { type CodingProxy } from "./proxy.js";
|
|
3
4
|
import { type RegistryService } from "./registry/service.js";
|
|
@@ -19,6 +20,8 @@ export interface CodingProxyServerConfig {
|
|
|
19
20
|
dependencyCache?: Pick<DependencyCacheService, "handle" | "uploadMs">;
|
|
20
21
|
/** Time to receive a whole request (default HTTP_LIMITS.requestMs); a cache upload gets its upload window instead. */
|
|
21
22
|
requestMs?: number;
|
|
23
|
+
/** The worker's MCP route (`/mcp`, createMcpRoute); absent, /mcp is not found. */
|
|
24
|
+
mcp?: (request: IncomingMessage, response: ServerResponse) => Promise<void>;
|
|
22
25
|
onRequest?: (event: {
|
|
23
26
|
protocol: ProxyProtocol | "other";
|
|
24
27
|
status: number;
|
|
@@ -6,6 +6,7 @@ import { logger } from "../../core/logger.js";
|
|
|
6
6
|
import { CodingProxyError, PROXY_MAX_BODY_BYTES } from "./proxy.js";
|
|
7
7
|
import { RegistryError } from "../../coding/registry/types.js";
|
|
8
8
|
import { MAX_PLAN_BODY_BYTES } from "./registry/service.js";
|
|
9
|
+
import { MCP_ROUTE_PATH } from "./mcp-route.js";
|
|
9
10
|
const proxyLog = logger.child({ module: "coding-proxy" });
|
|
10
11
|
/**
|
|
11
12
|
* Writes a proxy audit event to the operator log. The events carry ids,
|
|
@@ -266,6 +267,15 @@ export async function startCodingProxyServer(proxy, config) {
|
|
|
266
267
|
});
|
|
267
268
|
return;
|
|
268
269
|
}
|
|
270
|
+
if (request.url === MCP_ROUTE_PATH || request.url?.startsWith(`${MCP_ROUTE_PATH}?`)) {
|
|
271
|
+
if (!config.mcp) {
|
|
272
|
+
closeIfBodyUnread(request, response);
|
|
273
|
+
sendError(response, 404, "not_found");
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
await config.mcp(request, response);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
269
279
|
if (request.method === "HEAD" && request.url === "/api/hello") {
|
|
270
280
|
response.writeHead(200, { "cache-control": "no-store" }).end();
|
|
271
281
|
return;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type CatalogEntry } from "../llm/catalog-types.js";
|
|
2
|
+
import type { RunStepsLevel } from "../../core/run-steps/config.js";
|
|
2
3
|
import type { ModelPricing } from "../llm/pricing-core.js";
|
|
3
4
|
import type { ProxyRefusalDetails } from "../../core/budget-events.js";
|
|
5
|
+
import type { McpToolPin } from "../../core/mcp-tools.js";
|
|
4
6
|
export type ProxySessionStatus = "active" | "cancelled";
|
|
5
7
|
export type ProxyRequestStatus = "reserved" | "completed" | "released" | "uncertain";
|
|
6
8
|
export type ProxyProtocol = "openai-responses" | "anthropic-messages";
|
|
@@ -46,6 +48,14 @@ export interface ProxySession {
|
|
|
46
48
|
upstreamFailure?: string | null;
|
|
47
49
|
/** The run's catalog entry, copied at session creation; null on sessions from before the catalog. */
|
|
48
50
|
terms?: ProxyModelTerms | null;
|
|
51
|
+
/** The run's pinned MCP tools (no URLs or secrets); null when the run has none, and on older sessions. */
|
|
52
|
+
mcpTools: McpToolPin[] | null;
|
|
53
|
+
/** SHA-256 (base64url) of the run's MCP token; null when the run has no MCP tools. */
|
|
54
|
+
mcpTokenHash: string | null;
|
|
55
|
+
/** The run's MCP tools/call count so far. */
|
|
56
|
+
mcpCallCount: number;
|
|
57
|
+
/** The run's Run.runStepsLevel, copied at session creation; null on sessions from before run steps detail. */
|
|
58
|
+
runStepsLevel: RunStepsLevel | null;
|
|
49
59
|
}
|
|
50
60
|
export interface ProxyRequest {
|
|
51
61
|
id: string;
|
|
@@ -89,6 +99,12 @@ export interface CreateProxySessionInput {
|
|
|
89
99
|
budgetUsd: number;
|
|
90
100
|
registryTokenHash: string;
|
|
91
101
|
terms?: ProxyModelTerms;
|
|
102
|
+
/** The run's pinned MCP tools; set together with mcpTokenHash, and only when there is at least one. */
|
|
103
|
+
mcpTools?: McpToolPin[];
|
|
104
|
+
/** SHA-256 (base64url) of the run's MCP token (mcpTokenHash in src/coding/mcp-token.ts). */
|
|
105
|
+
mcpTokenHash?: string;
|
|
106
|
+
/** The run's level, fixed at dispatch; absent counts as summary. */
|
|
107
|
+
runStepsLevel?: RunStepsLevel;
|
|
92
108
|
}
|
|
93
109
|
export interface ReserveProxyRequestInput {
|
|
94
110
|
id: string;
|
|
@@ -122,6 +138,8 @@ export type ReserveProxyRequestResult = {
|
|
|
122
138
|
export interface ProxyLedger {
|
|
123
139
|
createSession(input: CreateProxySessionInput): Promise<void>;
|
|
124
140
|
findSessionByCapabilityHash(capabilityHash: string): Promise<ProxySession | null>;
|
|
141
|
+
/** The session whose MCP token hashes to this; any status (the caller checks it is active and in time). */
|
|
142
|
+
findSessionByMcpTokenHash(mcpTokenHash: string): Promise<ProxySession | undefined>;
|
|
125
143
|
reserve(input: ReserveProxyRequestInput): Promise<ReserveProxyRequestResult>;
|
|
126
144
|
complete(requestId: string, usage: ProxyUsage, actualCostUsd: number, upstreamStatus: number): Promise<ProxyRequest>;
|
|
127
145
|
release(requestId: string, upstreamStatus: number): Promise<void>;
|
|
@@ -117,9 +117,35 @@ export interface EngineRunContext {
|
|
|
117
117
|
onText?: (delta: string) => void;
|
|
118
118
|
/** Called after each model turn that completed without error; the engine awaits it. */
|
|
119
119
|
onProgress?: (progress: EngineProgress) => Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Receives each model turn's text and tool calls, then the turn's tool results, as the engine saw
|
|
122
|
+
* them. The runner turns them into the run's steps (src/core/run-steps); the engine never imports
|
|
123
|
+
* that code, so the sandbox worker image does not ship it. Synchronous and not awaited. A replay
|
|
124
|
+
* delivers the same call ids again; the writer drops the repeats.
|
|
125
|
+
*/
|
|
126
|
+
onTurn?: (event: EngineTurnEvent) => void;
|
|
120
127
|
/** See StepRunner. Optional; the in-process executor leaves it unset. */
|
|
121
128
|
step?: StepRunner;
|
|
122
129
|
}
|
|
130
|
+
/** What EngineRunContext.onTurn receives: a turn's output, then (if it called tools) their results in call order. */
|
|
131
|
+
export type EngineTurnEvent = {
|
|
132
|
+
kind: "turn";
|
|
133
|
+
/** 1-based turn number. */
|
|
134
|
+
turn: number;
|
|
135
|
+
text: string;
|
|
136
|
+
toolCalls: readonly {
|
|
137
|
+
id: string;
|
|
138
|
+
name: string;
|
|
139
|
+
argsJson: string;
|
|
140
|
+
}[];
|
|
141
|
+
} | {
|
|
142
|
+
kind: "results";
|
|
143
|
+
turn: number;
|
|
144
|
+
toolCalls: readonly {
|
|
145
|
+
id: string;
|
|
146
|
+
}[];
|
|
147
|
+
results: readonly string[];
|
|
148
|
+
};
|
|
123
149
|
export type EngineStatus = "succeeded" | "budget_exhausted" | "refused" | "failed";
|
|
124
150
|
export interface EngineResult {
|
|
125
151
|
status: EngineStatus;
|
|
@@ -26,6 +26,7 @@ import { RunTranscriptRecorder, loadTranscriptMaxBytes, loadTranscriptRetentionD
|
|
|
26
26
|
import { loadAgentSecretValues } from "../../core/secret-guard.js";
|
|
27
27
|
import { createBlobStore } from "../storage/index.js";
|
|
28
28
|
import { ContainerExecutor, PrismaContainerExecutionStore, RunCapabilityVault } from "./container.js";
|
|
29
|
+
import { loadMcpToolPins } from "../../core/mcp-tools.js";
|
|
29
30
|
import { PrismaExecutionKindResolver, RoutingExecutor } from "./routing.js";
|
|
30
31
|
import { createRepoAccessGate } from "../../core/repo-access.js";
|
|
31
32
|
import { buildIssueTrackers } from "../issue-tracker/index.js";
|
|
@@ -116,6 +117,7 @@ export function buildConfiguredExecutor(options) {
|
|
|
116
117
|
config: kubernetes,
|
|
117
118
|
workspaceRoot,
|
|
118
119
|
resolveCapability: (runId) => capabilities.get(runId),
|
|
120
|
+
resolveMcpToken: (runId) => capabilities.mcpToken(runId),
|
|
119
121
|
readyTimeoutMs: kubernetes.readyTimeoutMs,
|
|
120
122
|
enforcementExecTimeoutMs: kubernetes.enforcementExecTimeoutMs,
|
|
121
123
|
power: options.power ?? DISABLED_POWER,
|
|
@@ -140,6 +142,7 @@ export function buildConfiguredExecutor(options) {
|
|
|
140
142
|
workspaceRoot,
|
|
141
143
|
proxyContainer: proxyContainer,
|
|
142
144
|
resolveCapability: (runId) => capabilities.get(runId),
|
|
145
|
+
resolveMcpToken: (runId) => capabilities.mcpToken(runId),
|
|
143
146
|
isRunActive: async (runId, handle) => {
|
|
144
147
|
const coding = await options.db.codingRun.findUnique({
|
|
145
148
|
where: { runId },
|
|
@@ -177,6 +180,8 @@ export function buildConfiguredExecutor(options) {
|
|
|
177
180
|
vcs,
|
|
178
181
|
sessions,
|
|
179
182
|
capabilities,
|
|
183
|
+
// Remote MCP servers: a coding run gets its agent's pinned tools and an MCP token ([] when off).
|
|
184
|
+
mcpToolPins: (agentId, pinOptions) => loadMcpToolPins(options.db, agentId, pinOptions),
|
|
180
185
|
artifactRoot,
|
|
181
186
|
workerImage: config.workerImage,
|
|
182
187
|
claudeWorkerImage: config.claudeWorkerImage,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { RunStepsLevel } from "../../core/run-steps/config.js";
|
|
1
2
|
import { type PackageAuditEntry, type PackageAuditMode } from "../../coding/registry/audit-mode.js";
|
|
2
3
|
import type { PrismaClient } from "#prisma";
|
|
3
4
|
import type { RunTranscriptRecorder } from "../../coding/transcript/recorder.js";
|
|
5
|
+
import { type McpToolPin } from "../../core/mcp-tools.js";
|
|
4
6
|
import { type CodingProvider } from "../../coding/provider.js";
|
|
5
7
|
import { type TopUpOutcome } from "../../core/budget-topup.js";
|
|
6
8
|
import type { IssueTrackerRegistry } from "../issue-tracker/types.js";
|
|
@@ -100,6 +102,8 @@ export type StoredDescriptionBaseline = DescriptionBaseline & {
|
|
|
100
102
|
legacySource?: LegacyDescriptionSource;
|
|
101
103
|
};
|
|
102
104
|
export interface ContainerRunSnapshot {
|
|
105
|
+
/** Run.runStepsLevel, fixed at dispatch; absent on older snapshots = summary. */
|
|
106
|
+
runStepsLevel?: RunStepsLevel | null;
|
|
103
107
|
runId: string;
|
|
104
108
|
agentId: string;
|
|
105
109
|
status: string;
|
|
@@ -172,6 +176,12 @@ export interface ContainerRunSnapshot {
|
|
|
172
176
|
profileRepository: string | null;
|
|
173
177
|
/** How the profile's repository was authorized (CodingAgentProfile.repositoryAuthorizedVia). */
|
|
174
178
|
repositoryAuthorizedVia: string | null;
|
|
179
|
+
/** The run's repository was chosen from the agent's list (CodingRun.viaRepositoryLink). */
|
|
180
|
+
viaRepositoryLink: boolean;
|
|
181
|
+
/** How the agent's current write link for the run's repository was authorized; null when there is none. */
|
|
182
|
+
repositoryLinkAuthorizedVia: string | null;
|
|
183
|
+
/** The agent's write link for the run's repository still exists (always false for a run not made via a link). */
|
|
184
|
+
repositoryLinkExists: boolean;
|
|
175
185
|
/** The run's catalog entry recorded at dispatch (Run.pricingSnapshot); null on runs from before the catalog. */
|
|
176
186
|
pricingVersion: string | null;
|
|
177
187
|
pricingSnapshot: unknown;
|
|
@@ -327,6 +337,12 @@ export interface CodingSessionController {
|
|
|
327
337
|
budgetUsd: number;
|
|
328
338
|
/** The run's catalog entry; the proxy prices and shapes the run's requests from it. */
|
|
329
339
|
terms?: ProxyModelTerms;
|
|
340
|
+
/** The run's pinned MCP tools; set with mcpTokenHash, only when the agent has at least one. */
|
|
341
|
+
mcpTools?: McpToolPin[];
|
|
342
|
+
/** The hash of the run's MCP token (src/coding/mcp-token.ts); the token itself stays in the vault. */
|
|
343
|
+
mcpTokenHash?: string;
|
|
344
|
+
/** The run's run-steps level (Run.runStepsLevel); the proxy reads tool output only at detail. */
|
|
345
|
+
runStepsLevel?: RunStepsLevel;
|
|
330
346
|
}): Promise<{
|
|
331
347
|
id: string;
|
|
332
348
|
capability: string;
|
|
@@ -337,11 +353,19 @@ export interface CodingSessionController {
|
|
|
337
353
|
/** The code of the first model-provider failure the proxy relayed for this session, if any. */
|
|
338
354
|
upstreamFailure?(sessionId: string): Promise<string | null>;
|
|
339
355
|
}
|
|
340
|
-
/**
|
|
356
|
+
/**
|
|
357
|
+
* Plain capabilities live only for the short provisioning window. A run with MCP tools keeps its
|
|
358
|
+
* MCP token here too, next to the capability, so the launcher delivers it the same way; both are
|
|
359
|
+
* deleted together.
|
|
360
|
+
*/
|
|
341
361
|
export declare class RunCapabilityVault {
|
|
342
362
|
private readonly values;
|
|
363
|
+
private readonly mcpTokens;
|
|
343
364
|
set(runId: string, capability: string): void;
|
|
344
365
|
get(runId: string): Promise<string>;
|
|
366
|
+
setMcpToken(runId: string, token: string): void;
|
|
367
|
+
/** The run's MCP token; undefined for a run without MCP tools (and once the launch is over). */
|
|
368
|
+
mcpToken(runId: string): Promise<string | undefined>;
|
|
345
369
|
delete(runId: string): void;
|
|
346
370
|
}
|
|
347
371
|
export interface ContainerExecutorOptions {
|
|
@@ -352,6 +376,15 @@ export interface ContainerExecutorOptions {
|
|
|
352
376
|
vcs: VcsProvider;
|
|
353
377
|
sessions: CodingSessionController;
|
|
354
378
|
capabilities: RunCapabilityVault;
|
|
379
|
+
/**
|
|
380
|
+
* The agent's MCP tool pins (composition wires loadMcpToolPins; [] when WARDBY_MCP_SERVERS=off).
|
|
381
|
+
* Absent = no coding run gets MCP tools. A run with pins gets an MCP token, delivered as
|
|
382
|
+
* WARDBY_MCP_TOKEN; one without gets neither. `readOnlyOnly` comes from the run's dispatched
|
|
383
|
+
* provider (isCodexCoding(run.provider)), never the agent's current profile.
|
|
384
|
+
*/
|
|
385
|
+
mcpToolPins?: (agentId: string, options: {
|
|
386
|
+
readOnlyOnly: boolean;
|
|
387
|
+
}) => Promise<McpToolPin[]>;
|
|
355
388
|
artifactRoot: string;
|
|
356
389
|
/**
|
|
357
390
|
* The Codex worker for the "node" toolchain (CODING_WORKER_IMAGE). Optional on a Claude-only
|
|
@@ -725,3 +758,16 @@ export declare class ContainerExecutor implements Executor {
|
|
|
725
758
|
}
|
|
726
759
|
/** Operator-facing failure text: the message plus its cause chain, redacted. */
|
|
727
760
|
export declare function describeFailure(error: unknown, depth?: number): string;
|
|
761
|
+
/** The agent's MCP tool pins could not be read at launch (a database error): the run fails, nothing launched. */
|
|
762
|
+
export declare const MCP_PINS_UNAVAILABLE_ERROR = "coding_mcp_pins_unavailable";
|
|
763
|
+
/** Its category: the run's error is coding_failure_mcp_pins_unavailable:<diagnostic id>. */
|
|
764
|
+
export declare const MCP_PINS_UNAVAILABLE_CATEGORY = "mcp_pins_unavailable";
|
|
765
|
+
/** Low-power mode: the native gateway serving a run's MCP tools did not wake; nothing launched. */
|
|
766
|
+
export declare const MCP_GATEWAY_UNAVAILABLE_CATEGORY = "mcp_gateway_unavailable";
|
|
767
|
+
/**
|
|
768
|
+
* Error-code prefixes that name their category outright, checked before the
|
|
769
|
+
* substring heuristics below. Those heuristics filed every `github_*` error
|
|
770
|
+
* (a failed PR lookup or create after the push, a token mint) under
|
|
771
|
+
* "workspace" because "github" contains "git".
|
|
772
|
+
*/
|
|
773
|
+
export declare const CATEGORY_BY_PREFIX: ReadonlyArray<readonly [prefix: string, category: string]>;
|