@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
package/dist/mcp/tools/tools.js
CHANGED
|
@@ -13,9 +13,11 @@ import { Prisma } from "#prisma";
|
|
|
13
13
|
import { deriveJsonSchema, validateParams } from "../../sandbox/zod-params.js";
|
|
14
14
|
import { cachedZod4Compatibility, toolSubject, withZod3ToolWarnings, zod3SampleArgsWarning, zod3ToolWarnings, zod3WindowWarning, } from "../../core/zod3-window.js";
|
|
15
15
|
import { runInSandbox } from "../../sandbox/run-in-sandbox.js";
|
|
16
|
+
import { effectiveToolTimeoutMs, toolTimeoutProblem } from "../../sandbox/tool-timeout.js";
|
|
17
|
+
import { loadToolMaxTimeoutMs } from "../../config/tool-timeout.js";
|
|
16
18
|
import { ToolCapabilitiesPatchSchema } from "../../sandbox/tool-capabilities.js";
|
|
17
19
|
import { buildSharedDatastoreAccessor } from "../../core/datastores.js";
|
|
18
|
-
import { findSameNamedAttachedTool, reservedToolNameReason } from "../../core/tool-names.js";
|
|
20
|
+
import { findSameNamedAttachedTool, findSameNamedMcpTool, reservedToolNameReason, sameNamedMcpToolMessage, } from "../../core/tool-names.js";
|
|
19
21
|
import { ToolAttachedError, deleteToolGuarded, hasToolChanges, prepareToolUpdate, updateToolGuarded, } from "../../core/tool-admin.js";
|
|
20
22
|
import { McpError } from "../errors.js";
|
|
21
23
|
import { requireOwnedTool, requireStrictlyOwnedTool, assertStrictlyOwnedTool, visibleToPrincipal, canRead, } from "../auth/ownership.js";
|
|
@@ -46,9 +48,16 @@ async function describeAgents(agents, ctx) {
|
|
|
46
48
|
export function projectTool(tool, principalId) {
|
|
47
49
|
if (tool.ownerId !== null && tool.ownerId === principalId)
|
|
48
50
|
return tool;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
// timeoutMs is not code: anyone who sees the tool sees how long its calls may take.
|
|
52
|
+
const shown = { id: tool.id, name: tool.name, description: tool.description, timeoutMs: tool.timeoutMs };
|
|
53
|
+
return tool.ownerId === null ? { ...shown, public: true } : { ...shown, public: false, ownerIsCaller: false };
|
|
54
|
+
}
|
|
55
|
+
const TIMEOUT_MS_DESCRIPTION = "The tool's time limit per call, in milliseconds (at least 1000). Omitted: the default of 10000. Capped by the server's WARDBY_TOOL_MAX_TIMEOUT_MS (default 300000); a larger value is refused. Each fetch times out just before it, and sleep() is capped at it.";
|
|
56
|
+
/** Refuses (400) a timeoutMs this server can't store, naming its ceiling. */
|
|
57
|
+
function assertToolTimeout(timeoutMs) {
|
|
58
|
+
const problem = toolTimeoutProblem(timeoutMs, loadToolMaxTimeoutMs());
|
|
59
|
+
if (problem)
|
|
60
|
+
throw new McpError(400, problem);
|
|
52
61
|
}
|
|
53
62
|
/**
|
|
54
63
|
* Non-blocking: which of the secrets just granted to this attachment are
|
|
@@ -83,11 +92,14 @@ export function registerToolAuthoringTools(mcp) {
|
|
|
83
92
|
description: { type: "string" },
|
|
84
93
|
paramsZod: { type: "string" },
|
|
85
94
|
code: { type: "string" },
|
|
95
|
+
timeoutMs: { type: "integer", description: TIMEOUT_MS_DESCRIPTION },
|
|
86
96
|
},
|
|
87
97
|
required: ["name", "description", "paramsZod", "code"],
|
|
88
98
|
},
|
|
89
99
|
handler: async (args, ctx) => {
|
|
90
100
|
assertToolNameAllowed(args.name);
|
|
101
|
+
if (args.timeoutMs !== undefined)
|
|
102
|
+
assertToolTimeout(args.timeoutMs);
|
|
91
103
|
const schemaResult = await deriveJsonSchema(args.paramsZod);
|
|
92
104
|
if (!schemaResult.ok) {
|
|
93
105
|
return textResult({ ok: false, errorKind: schemaResult.errorKind, errorMessage: schemaResult.errorMessage });
|
|
@@ -101,6 +113,7 @@ export function registerToolAuthoringTools(mcp) {
|
|
|
101
113
|
code: args.code,
|
|
102
114
|
jsonSchema: schemaResult.value,
|
|
103
115
|
ownerId: ctx.principal.id,
|
|
116
|
+
...(args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {}),
|
|
104
117
|
},
|
|
105
118
|
});
|
|
106
119
|
const warnings = await zod3ToolWarnings(toolSubject(tool.name), args.paramsZod);
|
|
@@ -117,7 +130,7 @@ export function registerToolAuthoringTools(mcp) {
|
|
|
117
130
|
mcp.registerTool({
|
|
118
131
|
name: "update_tool",
|
|
119
132
|
scope: "tools:write",
|
|
120
|
-
description: "Replaces a tool's description, paramsZod and/or
|
|
133
|
+
description: "Replaces a tool's description, paramsZod, code and/or timeoutMs (null: back to the default limit) in place (its name can't change: agents' prompts call it by name). Owner-only: public tools can't be changed over MCP. Refused while the tool is attached to any agent you don't own, including owner-less ones and agents shared with you, since the change would reach another owner's agent; detach it there first or create a new tool. Runs already started keep the version they loaded for their whole lifetime; the next run picks up the new one.",
|
|
121
134
|
inputSchema: {
|
|
122
135
|
type: "object",
|
|
123
136
|
additionalProperties: false,
|
|
@@ -126,14 +139,25 @@ export function registerToolAuthoringTools(mcp) {
|
|
|
126
139
|
description: { type: "string" },
|
|
127
140
|
paramsZod: { type: "string" },
|
|
128
141
|
code: { type: "string" },
|
|
142
|
+
timeoutMs: {
|
|
143
|
+
type: ["integer", "null"],
|
|
144
|
+
description: `${TIMEOUT_MS_DESCRIPTION} Pass null to go back to the default.`,
|
|
145
|
+
},
|
|
129
146
|
},
|
|
130
147
|
required: ["toolId"],
|
|
131
148
|
},
|
|
132
149
|
handler: async (args, ctx) => {
|
|
133
|
-
const changes = {
|
|
150
|
+
const changes = {
|
|
151
|
+
description: args.description,
|
|
152
|
+
paramsZod: args.paramsZod,
|
|
153
|
+
code: args.code,
|
|
154
|
+
timeoutMs: args.timeoutMs,
|
|
155
|
+
};
|
|
134
156
|
if (!hasToolChanges(changes)) {
|
|
135
|
-
throw new McpError(400, "update_tool needs at least one of description, paramsZod or
|
|
157
|
+
throw new McpError(400, "update_tool needs at least one of description, paramsZod, code or timeoutMs.");
|
|
136
158
|
}
|
|
159
|
+
if (args.timeoutMs !== undefined && args.timeoutMs !== null)
|
|
160
|
+
assertToolTimeout(args.timeoutMs);
|
|
137
161
|
// Checked once up front so a non-owner never gets as far as a sandbox
|
|
138
162
|
// compile, and again (with the attachments) inside the transaction.
|
|
139
163
|
await requireStrictlyOwnedTool(ctx.db, args.toolId, ctx.principal.id);
|
|
@@ -196,7 +220,7 @@ export function registerToolAuthoringTools(mcp) {
|
|
|
196
220
|
mcp.registerTool({
|
|
197
221
|
name: "dry_run_tool",
|
|
198
222
|
scope: "tools:write",
|
|
199
|
-
description: "Test-runs tool code against sample args without persisting it. Fetch is denied by default, same as a freshly attached tool — pass allowedHosts to test code that calls fetch(). For this release a paramsZod is still compiled with Zod 3; a `warnings` list in the result means the next release, which compiles it with Zod 4 only, will reject it or treat the sample args differently.",
|
|
223
|
+
description: "Test-runs tool code against sample args without persisting it, with the same time limit a run would give it (timeoutMs, as on create_tool). Fetch is denied by default, same as a freshly attached tool — pass allowedHosts to test code that calls fetch(). For this release a paramsZod is still compiled with Zod 3; a `warnings` list in the result means the next release, which compiles it with Zod 4 only, will reject it or treat the sample args differently.",
|
|
200
224
|
inputSchema: {
|
|
201
225
|
type: "object",
|
|
202
226
|
properties: {
|
|
@@ -204,10 +228,13 @@ export function registerToolAuthoringTools(mcp) {
|
|
|
204
228
|
code: { type: "string" },
|
|
205
229
|
sampleArgs: { type: "object" },
|
|
206
230
|
allowedHosts: { type: "array", items: { type: "string" } },
|
|
231
|
+
timeoutMs: { type: "integer", description: TIMEOUT_MS_DESCRIPTION },
|
|
207
232
|
},
|
|
208
233
|
required: ["paramsZod", "code", "sampleArgs"],
|
|
209
234
|
},
|
|
210
235
|
handler: async (args, ctx) => {
|
|
236
|
+
if (args.timeoutMs !== undefined)
|
|
237
|
+
assertToolTimeout(args.timeoutMs);
|
|
211
238
|
const hosts = ToolCapabilitiesPatchSchema.pick({ allowedHosts: true }).safeParse({
|
|
212
239
|
allowedHosts: args.allowedHosts,
|
|
213
240
|
});
|
|
@@ -254,6 +281,7 @@ export function registerToolAuthoringTools(mcp) {
|
|
|
254
281
|
sharedDatastore: buildSharedDatastoreAccessor(ctx.principal.id, ctx.providers.datastore, ctx.db),
|
|
255
282
|
toolName: "dry_run_tool",
|
|
256
283
|
allowedFetchHosts: hosts.data.allowedHosts ?? [],
|
|
284
|
+
timeoutMs: effectiveToolTimeoutMs(args.timeoutMs, loadToolMaxTimeoutMs()),
|
|
257
285
|
});
|
|
258
286
|
return textResult({ jsonSchema, result: sandboxResult, ...warnings });
|
|
259
287
|
},
|
|
@@ -334,6 +362,10 @@ export function registerToolAuthoringTools(mcp) {
|
|
|
334
362
|
: " (a tool owned by another principal)";
|
|
335
363
|
throw new McpError(409, `Agent "${args.agentId}" already has a different tool named "${tool.name}" attached${which}; detach it first.`);
|
|
336
364
|
}
|
|
365
|
+
// Nor a name one of the agent's MCP servers already gives the model.
|
|
366
|
+
const mcpClash = await findSameNamedMcpTool(tx, args.agentId, tool.name);
|
|
367
|
+
if (mcpClash)
|
|
368
|
+
throw new McpError(409, sameNamedMcpToolMessage(args.agentId, tool.name, mcpClash.serverName));
|
|
337
369
|
// Re-granting an attachment this owner never vouched for: what is
|
|
338
370
|
// not restated is reset, never silently adopted.
|
|
339
371
|
const keepUnstated = existing?.capabilitiesGrantedById === agent.ownerId;
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* gets 404, not another principal's final text/cost.
|
|
30
30
|
*/
|
|
31
31
|
import { dispatchRun } from "../../core/dispatch.js";
|
|
32
|
+
import { RepositoryChoiceRefusedError, resolveCodingRepository } from "../../core/coding-repository.js";
|
|
32
33
|
import { AttributionError, explicitAttribution, RESPONSE_PATH_SNAPSHOT_BUDGET, } from "../../core/attribution.js";
|
|
33
34
|
import { CodingBaseRefSchema, CodingTaskOverrideSchema, isLocalRepository } from "../../coding/protocol.js";
|
|
34
35
|
import { loadLocalRepoRoots, resolveLocalRepository, LocalRepoError } from "../../coding/local-repo.js";
|
|
@@ -46,6 +47,7 @@ const TriggerAgentSchema = z
|
|
|
46
47
|
agentId: z.string().trim().min(1).max(128),
|
|
47
48
|
task: CodingTaskOverrideSchema.optional(),
|
|
48
49
|
baseRef: CodingBaseRefSchema.optional(),
|
|
50
|
+
repository: z.string().min(1).max(300).optional(),
|
|
49
51
|
issue: z.unknown().optional(),
|
|
50
52
|
review: z
|
|
51
53
|
.object({
|
|
@@ -127,10 +129,10 @@ function assertOverrideAllowed(principalId, agent, args) {
|
|
|
127
129
|
// Strictly the owner: the stdio operator's owner-level access doesn't make
|
|
128
130
|
// it the owner whose repository authority the task steers (review M8).
|
|
129
131
|
const isOwner = agent.ownerId !== null && agent.ownerId === principalId;
|
|
130
|
-
if (isOwner || (args.task === undefined && args.baseRef === undefined))
|
|
132
|
+
if (isOwner || (args.task === undefined && args.baseRef === undefined && args.repository === undefined))
|
|
131
133
|
return;
|
|
132
134
|
if (!agent.codingProfile?.allowWebhookTaskOverride) {
|
|
133
|
-
throw new McpError(403, `Agent "${agent.id}": only its owner can pass a task or
|
|
135
|
+
throw new McpError(403, `Agent "${agent.id}": only its owner can pass a task, baseRef or repository, unless the owner allows it (codingProfile.allowWebhookTaskOverride).`);
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
138
|
/** execute on the agent, plus the override rule; `db` is the transaction when re-checking. */
|
|
@@ -150,6 +152,10 @@ export function registerTriggerTool(mcp) {
|
|
|
150
152
|
agentId: { type: "string" },
|
|
151
153
|
task: { type: "string" },
|
|
152
154
|
baseRef: { type: "string" },
|
|
155
|
+
repository: {
|
|
156
|
+
type: "string",
|
|
157
|
+
description: "Coding agents only: a repository on the agent's list (see get_agent). Omit to use codingProfile.repository.",
|
|
158
|
+
},
|
|
153
159
|
review: {
|
|
154
160
|
type: "object",
|
|
155
161
|
additionalProperties: false,
|
|
@@ -189,13 +195,28 @@ export function registerTriggerTool(mcp) {
|
|
|
189
195
|
}
|
|
190
196
|
review = await resolveLocalReview(ctx, agent.id, args.review);
|
|
191
197
|
}
|
|
192
|
-
if (agent.kind !== "coding" &&
|
|
193
|
-
|
|
198
|
+
if (agent.kind !== "coding" &&
|
|
199
|
+
(args.task !== undefined || args.baseRef !== undefined || args.repository !== undefined)) {
|
|
200
|
+
throw new McpError(400, "Task, baseRef and repository overrides are only valid for coding agents.");
|
|
194
201
|
}
|
|
195
202
|
// A local repository is re-checked against the roots as they are now
|
|
196
203
|
// (they can narrow after the agent was saved), before any run exists.
|
|
197
204
|
let warnings = [];
|
|
198
|
-
|
|
205
|
+
// The resolver's refusal lists the agent's repositories, so it runs only
|
|
206
|
+
// after requireTriggerable (execute on the agent) and never echoes the
|
|
207
|
+
// requested value.
|
|
208
|
+
const refused = (err) => {
|
|
209
|
+
if (err instanceof RepositoryChoiceRefusedError)
|
|
210
|
+
throw new McpError(400, `${err.code}: ${err.message}`);
|
|
211
|
+
throw err;
|
|
212
|
+
};
|
|
213
|
+
const repository = agent.kind === "coding" && agent.codingProfile
|
|
214
|
+
? (await resolveCodingRepository(ctx.db, {
|
|
215
|
+
agentId: agent.id,
|
|
216
|
+
profile: agent.codingProfile,
|
|
217
|
+
requested: args.repository,
|
|
218
|
+
}).catch(refused)).repository
|
|
219
|
+
: undefined;
|
|
199
220
|
if (repository !== undefined && isLocalRepository(repository)) {
|
|
200
221
|
try {
|
|
201
222
|
const resolved = await resolveLocalRepository(repository, loadLocalRepoRoots(process.env).roots);
|
|
@@ -238,6 +259,7 @@ export function registerTriggerTool(mcp) {
|
|
|
238
259
|
trigger: "manual",
|
|
239
260
|
codingTask: args.task,
|
|
240
261
|
codingBaseRef: args.baseRef,
|
|
262
|
+
codingRepository: args.repository,
|
|
241
263
|
attribution,
|
|
242
264
|
task: ctx.clientSupportsTasks ? { principalId: ctx.principal.id, ttlMs: DEFAULT_TASK_TTL_MS } : undefined,
|
|
243
265
|
triggeredById: ctx.principal.id,
|
|
@@ -262,7 +284,7 @@ export function registerTriggerTool(mcp) {
|
|
|
262
284
|
catch (err) {
|
|
263
285
|
if (pullRequest)
|
|
264
286
|
await ctx.db.localPullRequest.deleteMany({ where: { id: pullRequest.id } }).catch(() => { });
|
|
265
|
-
|
|
287
|
+
return refused(err);
|
|
266
288
|
}
|
|
267
289
|
if (ctx.clientSupportsTasks) {
|
|
268
290
|
if (!dispatched.task)
|
|
@@ -14,4 +14,6 @@ export declare class PayloadTooLargeError extends HttpBoundaryError {
|
|
|
14
14
|
}
|
|
15
15
|
export declare function canonicalUrl(value: string): URL;
|
|
16
16
|
export declare function readBody(req: IncomingMessage, maxBytes: number, signal: AbortSignal): Promise<string>;
|
|
17
|
+
/** readBody's bytes, undecoded (for a route that forwards the body unchanged). */
|
|
18
|
+
export declare function readBodyBytes(req: IncomingMessage, maxBytes: number, signal: AbortSignal): Promise<Buffer>;
|
|
17
19
|
export declare function parseBody(raw: string, contentType: string | undefined): unknown;
|
|
@@ -23,7 +23,11 @@ export function canonicalUrl(value) {
|
|
|
23
23
|
}
|
|
24
24
|
return url;
|
|
25
25
|
}
|
|
26
|
-
export function readBody(req, maxBytes, signal) {
|
|
26
|
+
export async function readBody(req, maxBytes, signal) {
|
|
27
|
+
return (await readBodyBytes(req, maxBytes, signal)).toString("utf8");
|
|
28
|
+
}
|
|
29
|
+
/** readBody's bytes, undecoded (for a route that forwards the body unchanged). */
|
|
30
|
+
export function readBodyBytes(req, maxBytes, signal) {
|
|
27
31
|
const length = req.headers["content-length"];
|
|
28
32
|
if (length && (!/^\d+$/.test(length) || Number(length) > maxBytes))
|
|
29
33
|
return Promise.reject(new PayloadTooLargeError());
|
|
@@ -55,7 +59,7 @@ export function readBody(req, maxBytes, signal) {
|
|
|
55
59
|
};
|
|
56
60
|
const end = () => {
|
|
57
61
|
clean();
|
|
58
|
-
resolve(Buffer.concat(chunks, size)
|
|
62
|
+
resolve(Buffer.concat(chunks, size));
|
|
59
63
|
};
|
|
60
64
|
req.on("data", data);
|
|
61
65
|
req.once("end", end);
|
|
@@ -18,11 +18,19 @@ export async function handleWebhookIngress(webhookId, req, db, executor, issueTr
|
|
|
18
18
|
if (req.body.task !== undefined && codingTask === undefined) {
|
|
19
19
|
return { status: 400, body: { error: "invalid_task" } };
|
|
20
20
|
}
|
|
21
|
+
// `wardbyRepository`, not `repository`, for the same reason: a forwarded GitHub
|
|
22
|
+
// payload carries its own top-level `repository` object.
|
|
23
|
+
const repository = req.body.wardbyRepository;
|
|
24
|
+
if (repository !== undefined &&
|
|
25
|
+
(typeof repository !== "string" || repository.length === 0 || repository.length > 300)) {
|
|
26
|
+
return { status: 400, body: { error: "invalid_repository" } };
|
|
27
|
+
}
|
|
21
28
|
// `wardbyIssue`, not `issue`: forwarded third-party payloads (GitHub, Jira)
|
|
22
29
|
// carry their own top-level `issue` object, which is not ours to read.
|
|
23
30
|
const result = await resolveWebhookRun(webhookId, secret, db, executor, codingTask, {
|
|
24
31
|
issue: req.body.wardbyIssue,
|
|
25
32
|
issueTrackers,
|
|
33
|
+
repository,
|
|
26
34
|
});
|
|
27
35
|
if (result.ok) {
|
|
28
36
|
return { status: 202, body: { runId: result.runId } };
|
|
@@ -30,6 +38,8 @@ export async function handleWebhookIngress(webhookId, req, db, executor, issueTr
|
|
|
30
38
|
switch (result.reason) {
|
|
31
39
|
case "invalid_issue":
|
|
32
40
|
return { status: 400, body: { error: "invalid_issue", error_description: result.message } };
|
|
41
|
+
case "invalid_repository":
|
|
42
|
+
return { status: 400, body: { error: "invalid_repository" } };
|
|
33
43
|
case "not_found":
|
|
34
44
|
return { status: 404, body: { error: "not_found" } };
|
|
35
45
|
case "invalid_secret":
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* session budget before the provider is called and settles at the provider's
|
|
11
11
|
* reported usage; the worker's own usage numbers are never read.
|
|
12
12
|
*/
|
|
13
|
+
import type { RunStepSink } from "../core/run-steps/types.js";
|
|
13
14
|
import { createNativeRunTools, durableDelegate, type NativeRunFinishContext, type NativeRunProviders, type RunnerDb, type SandboxSessionSnapshot } from "../core/runner.js";
|
|
15
|
+
import type { CodingMcpDeps } from "../core/coding-mcp.js";
|
|
14
16
|
import { ReviewReadLedger } from "../core/review-reads.js";
|
|
15
17
|
import type { LlmProvider, LlmStreamEvent } from "../providers/llm/types.js";
|
|
16
18
|
import { PrismaGatewayLedger, type GatewaySessionRecord } from "./ledger.js";
|
|
@@ -26,6 +28,8 @@ export interface GatewayServerDeps {
|
|
|
26
28
|
providers: NativeRunProviders;
|
|
27
29
|
/** Delegation long-poll window; tests shorten it. */
|
|
28
30
|
pollWindowMs?: number;
|
|
31
|
+
/** Serves POST /coding-mcp on the same listener (remote MCP servers for coding runs); unset → 404. */
|
|
32
|
+
codingMcp?: CodingMcpDeps;
|
|
29
33
|
}
|
|
30
34
|
/** What a request gets back: one JSON value, or a stream of model events. */
|
|
31
35
|
export type GatewayResponse = {
|
|
@@ -63,7 +67,9 @@ interface RebuiltRun {
|
|
|
63
67
|
delegation: Omit<Parameters<typeof durableDelegate>[0], "ledger" | "sessionId">;
|
|
64
68
|
}
|
|
65
69
|
/** Exported for its unit test; the HTTP handler is its only production caller. */
|
|
66
|
-
export declare function streamModel(ctx: RebuiltRun, ledger: PrismaGatewayLedger, callId: string, params: GatewayParams<"llm.stream"
|
|
70
|
+
export declare function streamModel(ctx: RebuiltRun, ledger: PrismaGatewayLedger, callId: string, params: GatewayParams<"llm.stream">, steps?: RunStepSink,
|
|
71
|
+
/** The request's bearer capability, redacted from the recorded steps like a secret value. */
|
|
72
|
+
capability?: string): AsyncIterable<LlmStreamEvent>;
|
|
67
73
|
/**
|
|
68
74
|
* Logged (at warn) in place of a user tool's console line when that tool's redaction list cannot
|
|
69
75
|
* be read: without the complete list the line cannot be shown to be free of its secret values, so
|
|
@@ -72,6 +78,17 @@ export declare function streamModel(ctx: RebuiltRun, ledger: PrismaGatewayLedger
|
|
|
72
78
|
export declare const CONSOLE_WITHHELD_MESSAGE = "sandbox tool console line withheld: the tool's secret values could not be read";
|
|
73
79
|
/** Serves one privileged bridge call for a worker (exported for tests). */
|
|
74
80
|
export declare function hostCall(ctx: RebuiltRun, ledger: PrismaGatewayLedger, callId: string, params: GatewayParams<"host.call">): Promise<unknown>;
|
|
81
|
+
/** Added to a tool's limit for one host call: the worker's own wall time ends the call first. */
|
|
82
|
+
export declare const GATEWAY_HOST_CALL_MARGIN_MS = 20000;
|
|
83
|
+
/** How long one host call of `tool` may take: its limit plus the margin, cut to the run's deadline. */
|
|
84
|
+
export declare function hostCallTimeoutMs(ctx: {
|
|
85
|
+
session: {
|
|
86
|
+
deadlineAt: Date;
|
|
87
|
+
};
|
|
88
|
+
tools: {
|
|
89
|
+
toolTimeoutMs: (name: string) => number | undefined;
|
|
90
|
+
};
|
|
91
|
+
}, tool: string, now?: number): number;
|
|
75
92
|
/** The worker's terminal call: the run's one terminal write (masked by finishNativeRun), then the session ends. */
|
|
76
93
|
export declare function finish(ctx: RebuiltRun, ledger: PrismaGatewayLedger, callId: string, params: GatewayParams<"finish">): Promise<null>;
|
|
77
94
|
export {};
|
|
@@ -10,12 +10,14 @@
|
|
|
10
10
|
* session budget before the provider is called and settles at the provider's
|
|
11
11
|
* reported usage; the worker's own usage numbers are never read.
|
|
12
12
|
*/
|
|
13
|
+
import { NativeStreamSteps, recordStreamSteps } from "../core/run-steps/native.js";
|
|
13
14
|
import { createNativeRunTools, durableDelegate, failNativeRun, finishNativeRun, nativeRunIntegrations, recordNativeRunProgress, runDrivability, sandboxCapabilityHash, userToolSecretValues, } from "../core/runner.js";
|
|
14
15
|
import { ReviewReadLedger } from "../core/review-reads.js";
|
|
15
16
|
import { DELEGATE_TOOL_PREFIX } from "../core/tool-names.js";
|
|
16
17
|
import { relayedOutputTokens } from "../providers/coding-proxy/interrupted.js";
|
|
17
18
|
import { RoutingLlmProvider } from "../providers/llm/routing.js";
|
|
18
19
|
import { isPrivilegedBridgeName } from "../sandbox/host-functions.js";
|
|
20
|
+
import { DEFAULT_TOOL_TIMEOUT_MS } from "../sandbox/tool-timeout.js";
|
|
19
21
|
import { forWorker, gatewayLog } from "./gateway.js";
|
|
20
22
|
import { PrismaGatewayLedger } from "./ledger.js";
|
|
21
23
|
import { GatewayError, GatewayRequestSchema, parseParams, } from "./protocol.js";
|
|
@@ -65,6 +67,7 @@ export function runSecretValues(ctx, capability) {
|
|
|
65
67
|
return async () => [
|
|
66
68
|
capability,
|
|
67
69
|
...(await userToolSecretValues(ctx.tools, Object.keys(ctx.snapshot.input.userTools))),
|
|
70
|
+
...(await ctx.tools.mcpSecretValues()),
|
|
68
71
|
];
|
|
69
72
|
}
|
|
70
73
|
/**
|
|
@@ -127,7 +130,7 @@ async function handle(deps, authorization, body, scope) {
|
|
|
127
130
|
return {
|
|
128
131
|
kind: "stream",
|
|
129
132
|
status: 200,
|
|
130
|
-
events: checkedEvents(streamModel(ctx, ledger, request.callId, params), scope.secretValues),
|
|
133
|
+
events: checkedEvents(streamModel(ctx, ledger, request.callId, params, deps.providers.runSteps, token), scope.secretValues),
|
|
131
134
|
};
|
|
132
135
|
case "llm.countTokens": {
|
|
133
136
|
const p = params;
|
|
@@ -178,6 +181,7 @@ async function rebuild(deps, session) {
|
|
|
178
181
|
repoAccess,
|
|
179
182
|
issueTrackers,
|
|
180
183
|
reviewReads,
|
|
184
|
+
deadlineAt: session.deadlineAt,
|
|
181
185
|
}),
|
|
182
186
|
finishContext: { runId: session.runId, db, providers, reviewHosts, repoAccess, issueTrackers },
|
|
183
187
|
delegation: {
|
|
@@ -192,7 +196,9 @@ async function rebuild(deps, session) {
|
|
|
192
196
|
};
|
|
193
197
|
}
|
|
194
198
|
/** Exported for its unit test; the HTTP handler is its only production caller. */
|
|
195
|
-
export async function* streamModel(ctx, ledger, callId, params
|
|
199
|
+
export async function* streamModel(ctx, ledger, callId, params, steps,
|
|
200
|
+
/** The request's bearer capability, redacted from the recorded steps like a secret value. */
|
|
201
|
+
capability) {
|
|
196
202
|
const req = params.request;
|
|
197
203
|
if (req.model !== ctx.snapshot.input.agent.model)
|
|
198
204
|
throw new GatewayError("not_allowed", "A run may only call its own model.");
|
|
@@ -207,6 +213,8 @@ export async function* streamModel(ctx, ledger, callId, params) {
|
|
|
207
213
|
if (reserved.outcome === "budget_exhausted") {
|
|
208
214
|
throw new GatewayError("budget_exhausted", "This run's budget cannot cover another model call.");
|
|
209
215
|
}
|
|
216
|
+
// Run steps: what this call streams, and the results its request carries for the last turn.
|
|
217
|
+
const tap = steps ? new NativeStreamSteps(callId) : undefined;
|
|
210
218
|
let settled = false;
|
|
211
219
|
let sawEvent = false;
|
|
212
220
|
// Set once the provider's response starts: it may bill hidden thinking or reasoning before any event.
|
|
@@ -235,6 +243,7 @@ export async function* streamModel(ctx, ledger, callId, params) {
|
|
|
235
243
|
costUsd: event.usage.costUsd,
|
|
236
244
|
});
|
|
237
245
|
}
|
|
246
|
+
tap?.observe(event);
|
|
238
247
|
yield event;
|
|
239
248
|
}
|
|
240
249
|
}
|
|
@@ -281,6 +290,8 @@ export async function* streamModel(ctx, ledger, callId, params) {
|
|
|
281
290
|
costUsd: ctx.llm.priceUsd(req.model, { inputTokens, outputTokens: output }),
|
|
282
291
|
});
|
|
283
292
|
}
|
|
293
|
+
// Recorded before the stream ends (bounded), so the next call's results find these steps.
|
|
294
|
+
await recordStreamSteps(steps, ctx.session.runId, tap, req.messages, capability ? [capability] : []);
|
|
284
295
|
}
|
|
285
296
|
}
|
|
286
297
|
/** Output tokens of a cut stream's output: counted, else the deliberately high bytes / 3 estimate. */
|
|
@@ -335,9 +346,15 @@ export async function hostCall(ctx, ledger, callId, params) {
|
|
|
335
346
|
}
|
|
336
347
|
redact = values;
|
|
337
348
|
}
|
|
338
|
-
|
|
339
|
-
if
|
|
349
|
+
// The tool's own limit plus a margin (30 s for a default tool), never past the run's deadline,
|
|
350
|
+
// and aborted if the run is stopped meanwhile.
|
|
351
|
+
const stop = ctx.tools.watchStop();
|
|
352
|
+
const signal = AbortSignal.any([AbortSignal.timeout(hostCallTimeoutMs(ctx, params.tool)), stop.signal]);
|
|
353
|
+
const host = ctx.tools.privilegedHostFor(params.tool, signal, redact);
|
|
354
|
+
if (!host) {
|
|
355
|
+
stop.done();
|
|
340
356
|
throw new GatewayError("not_allowed", `"${params.tool}" is not a user tool of this run.`);
|
|
357
|
+
}
|
|
341
358
|
const bridge = host[params.bridge];
|
|
342
359
|
const run = async () => {
|
|
343
360
|
try {
|
|
@@ -348,11 +365,23 @@ export async function hostCall(ctx, ledger, callId, params) {
|
|
|
348
365
|
throw new GatewayError("bridge_error", err instanceof Error ? err.message.slice(0, 1024) : "Host function failed.");
|
|
349
366
|
}
|
|
350
367
|
};
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
368
|
+
try {
|
|
369
|
+
// Console lines are not worth a ledger row, and a secret value must never be stored in one
|
|
370
|
+
// (a secrets.get is a side-effect-free read, so a retried callId simply reads it again).
|
|
371
|
+
if (params.bridge === "__bridge_console" || params.bridge === "__bridge_secretsGet")
|
|
372
|
+
return await run();
|
|
373
|
+
return await replayable(ledger, ctx.session.id, callId, "host.call", run);
|
|
374
|
+
}
|
|
375
|
+
finally {
|
|
376
|
+
stop.done();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/** Added to a tool's limit for one host call: the worker's own wall time ends the call first. */
|
|
380
|
+
export const GATEWAY_HOST_CALL_MARGIN_MS = 20_000;
|
|
381
|
+
/** How long one host call of `tool` may take: its limit plus the margin, cut to the run's deadline. */
|
|
382
|
+
export function hostCallTimeoutMs(ctx, tool, now = Date.now()) {
|
|
383
|
+
const limitMs = (ctx.tools.toolTimeoutMs(tool) ?? DEFAULT_TOOL_TIMEOUT_MS) + GATEWAY_HOST_CALL_MARGIN_MS;
|
|
384
|
+
return Math.max(0, Math.min(limitMs, ctx.session.deadlineAt.getTime() - now));
|
|
356
385
|
}
|
|
357
386
|
/** Runs a one-shot call once per callId; a retry replays its recorded result. */
|
|
358
387
|
async function replayable(ledger, sessionId, callId, method, run) {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import type { EngineResult, EngineRunContext } from "../providers/engine/types.js";
|
|
14
14
|
import type { LlmStreamEvent } from "../providers/llm/types.js";
|
|
15
15
|
import pino from "pino";
|
|
16
|
+
import type { RunStepSink } from "../core/run-steps/types.js";
|
|
16
17
|
import { type PrivilegedHost } from "../sandbox/host-functions.js";
|
|
17
18
|
import { logger } from "../core/logger.js";
|
|
18
19
|
import { GatewayError, type WorkerInput } from "./protocol.js";
|
|
@@ -63,8 +64,12 @@ export interface SandboxedEngineOptions {
|
|
|
63
64
|
* the run's values is not sent to the worker; without this, no cause code is sent.
|
|
64
65
|
*/
|
|
65
66
|
readableSecretValues?: (tool: string) => Promise<string[]>;
|
|
67
|
+
/** Every secret value of the run's attached MCP servers, checked the same way. */
|
|
68
|
+
mcpSecretValues?: () => Promise<string[]>;
|
|
66
69
|
drivability: () => Promise<RunDrivability>;
|
|
67
70
|
launcher: WorkerLauncher;
|
|
71
|
+
/** Records the run's steps (src/core/run-steps); absent = none. */
|
|
72
|
+
runSteps?: RunStepSink;
|
|
68
73
|
}
|
|
69
74
|
/** Runs one native run's engine in a worker and returns its result with gateway-metered usage. */
|
|
70
75
|
export declare function runSandboxedEngine(options: SandboxedEngineOptions): Promise<EngineResult>;
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* sandbox bridges are served only by name from the run's own surface.
|
|
12
12
|
*/
|
|
13
13
|
import pino from "pino";
|
|
14
|
+
import { NativeStreamSteps, recordStreamSteps } from "../core/run-steps/native.js";
|
|
14
15
|
import { isPrivilegedBridgeName, redactExactSecretValues, withEscapedForms, } from "../sandbox/host-functions.js";
|
|
15
16
|
import { logger } from "../core/logger.js";
|
|
16
17
|
import { declaredCodeOf } from "../core/error-code.js";
|
|
@@ -116,8 +117,12 @@ export async function runSandboxedEngine(options) {
|
|
|
116
117
|
};
|
|
117
118
|
/** The run's readable secret values, for checking a failure's cause code before the worker sees it. */
|
|
118
119
|
const readable = options.readableSecretValues;
|
|
120
|
+
const mcpValues = options.mcpSecretValues;
|
|
119
121
|
const runSecretValues = readable
|
|
120
|
-
? async () =>
|
|
122
|
+
? async () => [
|
|
123
|
+
...(await Promise.all(Object.keys(input.userTools).map((tool) => readable(tool)))).flat(),
|
|
124
|
+
...(mcpValues ? await mcpValues() : []),
|
|
125
|
+
]
|
|
121
126
|
: undefined;
|
|
122
127
|
const served = {
|
|
123
128
|
async *stream(raw) {
|
|
@@ -130,6 +135,9 @@ export async function runSandboxedEngine(options) {
|
|
|
130
135
|
const req = params.request;
|
|
131
136
|
let sawDone = false;
|
|
132
137
|
let streamedText = "";
|
|
138
|
+
// Run steps: what this call streams, and the results its request carries for the last turn.
|
|
139
|
+
const sink = options.runSteps;
|
|
140
|
+
const steps = sink ? new NativeStreamSteps(request.callId) : undefined;
|
|
133
141
|
try {
|
|
134
142
|
for await (const event of llm.stream(req, controller.signal)) {
|
|
135
143
|
if (event.type === "done") {
|
|
@@ -139,6 +147,7 @@ export async function runSandboxedEngine(options) {
|
|
|
139
147
|
else if (event.type === "text") {
|
|
140
148
|
streamedText += event.delta;
|
|
141
149
|
}
|
|
150
|
+
steps?.observe(event);
|
|
142
151
|
yield event;
|
|
143
152
|
}
|
|
144
153
|
}
|
|
@@ -153,6 +162,9 @@ export async function runSandboxedEngine(options) {
|
|
|
153
162
|
: 0;
|
|
154
163
|
meter({ inputTokens, outputTokens, costUsd: llm.priceUsd(req.model, { inputTokens, outputTokens }) });
|
|
155
164
|
}
|
|
165
|
+
// Recorded before the stream ends (bounded), so the next call's results find these steps.
|
|
166
|
+
// The run's capability (when the worker has one) is redacted like a secret value.
|
|
167
|
+
await recordStreamSteps(sink, runId, steps, req.messages, input.gateway ? [input.gateway.capability] : []);
|
|
156
168
|
}
|
|
157
169
|
},
|
|
158
170
|
async call(raw) {
|
|
@@ -7,16 +7,18 @@
|
|
|
7
7
|
* POST /native-gateway/v1/call body: GatewayRequest
|
|
8
8
|
* → application/json { ok: true, result } | { ok: false, error: { code, message } }
|
|
9
9
|
* → application/x-ndjson (llm.stream) { event } … then { end: true } or { error: { code, message, causeCode? } }
|
|
10
|
+
*
|
|
11
|
+
* POST /coding-mcp a coding run's MCP calls, forwarded by the coding proxy's /mcp route
|
|
12
|
+
* (src/core/coding-mcp.ts); its own auth: the run's MCP token, not a
|
|
13
|
+
* gateway capability. 404 when the listener was started without it.
|
|
10
14
|
*/
|
|
11
15
|
import { type Server } from "node:http";
|
|
16
|
+
import { type ListenAddress } from "../core/listen-address.js";
|
|
12
17
|
import { type GatewayServerDeps } from "./gateway-server.js";
|
|
13
18
|
/** The HTTP listener's own log. Exported so tests can watch it. */
|
|
14
19
|
export declare const httpLog: import("pino").Logger<never, boolean>;
|
|
15
20
|
export declare const NATIVE_GATEWAY_PATH = "/native-gateway/v1/call";
|
|
16
|
-
export
|
|
17
|
-
host: string;
|
|
18
|
-
port: number;
|
|
19
|
-
}
|
|
21
|
+
export type NativeGatewayListen = ListenAddress;
|
|
20
22
|
/** Parses NATIVE_GATEWAY_LISTEN ("host:port" or ":port"); undefined when unset. */
|
|
21
23
|
export declare function parseGatewayListen(value: string | undefined): NativeGatewayListen | undefined;
|
|
22
24
|
export declare function createGatewayServer(deps: GatewayServerDeps): Server;
|
|
@@ -7,9 +7,16 @@
|
|
|
7
7
|
* POST /native-gateway/v1/call body: GatewayRequest
|
|
8
8
|
* → application/json { ok: true, result } | { ok: false, error: { code, message } }
|
|
9
9
|
* → application/x-ndjson (llm.stream) { event } … then { end: true } or { error: { code, message, causeCode? } }
|
|
10
|
+
*
|
|
11
|
+
* POST /coding-mcp a coding run's MCP calls, forwarded by the coding proxy's /mcp route
|
|
12
|
+
* (src/core/coding-mcp.ts); its own auth: the run's MCP token, not a
|
|
13
|
+
* gateway capability. 404 when the listener was started without it.
|
|
10
14
|
*/
|
|
11
15
|
import { createServer } from "node:http";
|
|
16
|
+
import { CODING_MCP_PATH, handleCodingMcpRequest } from "../core/coding-mcp.js";
|
|
17
|
+
import { parseListenAddress } from "../core/listen-address.js";
|
|
12
18
|
import { logger } from "../core/logger.js";
|
|
19
|
+
import { HTTP_LIMITS, HttpBoundaryError, readBody as readLimitedBody } from "../mcp/transport/http-limits.js";
|
|
13
20
|
import { logUnexpectedError } from "./gateway.js";
|
|
14
21
|
import { serveGatewayRequest } from "./gateway-server.js";
|
|
15
22
|
import { GatewayError, MAX_MESSAGE_BYTES, toGatewayError } from "./protocol.js";
|
|
@@ -18,14 +25,7 @@ export const httpLog = logger.child({ module: "native-gateway-http" });
|
|
|
18
25
|
export const NATIVE_GATEWAY_PATH = "/native-gateway/v1/call";
|
|
19
26
|
/** Parses NATIVE_GATEWAY_LISTEN ("host:port" or ":port"); undefined when unset. */
|
|
20
27
|
export function parseGatewayListen(value) {
|
|
21
|
-
|
|
22
|
-
return undefined;
|
|
23
|
-
const match = /^(.*):(\d{1,5})$/.exec(value.trim());
|
|
24
|
-
const port = match ? Number(match[2]) : NaN;
|
|
25
|
-
if (!match || !Number.isInteger(port) || port < 1 || port > 65_535) {
|
|
26
|
-
throw new Error(`NATIVE_GATEWAY_LISTEN must be host:port or :port (got "${value}").`);
|
|
27
|
-
}
|
|
28
|
-
return { host: match[1] || "0.0.0.0", port };
|
|
28
|
+
return parseListenAddress("NATIVE_GATEWAY_LISTEN", value);
|
|
29
29
|
}
|
|
30
30
|
function readBody(req) {
|
|
31
31
|
return new Promise((resolve, reject) => {
|
|
@@ -58,6 +58,10 @@ export function createGatewayServer(deps) {
|
|
|
58
58
|
send(healthy ? 200 : 503, { ok: healthy });
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
|
+
if (new URL(req.url ?? "/", "http://gateway").pathname === CODING_MCP_PATH && deps.codingMcp) {
|
|
62
|
+
await serveCodingMcp(req, res, deps.codingMcp);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
61
65
|
if (req.method !== "POST" || req.url !== NATIVE_GATEWAY_PATH) {
|
|
62
66
|
send(404, { ok: false, error: { code: "not_found", message: "not found" } });
|
|
63
67
|
return;
|
|
@@ -101,6 +105,47 @@ export function createGatewayServer(deps) {
|
|
|
101
105
|
});
|
|
102
106
|
});
|
|
103
107
|
}
|
|
108
|
+
/** /coding-mcp: one JSON-RPC message per POST, answered as one JSON body (or 202 with none). */
|
|
109
|
+
async function serveCodingMcp(req, res, deps) {
|
|
110
|
+
if (req.method !== "POST") {
|
|
111
|
+
res.setHeader("allow", "POST");
|
|
112
|
+
res.setHeader("content-type", "application/json");
|
|
113
|
+
res.writeHead(405);
|
|
114
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "Use POST." } }));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
let body;
|
|
118
|
+
try {
|
|
119
|
+
body = await readLimitedBody(req, HTTP_LIMITS.json, AbortSignal.timeout(HTTP_LIMITS.requestMs));
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const status = err instanceof HttpBoundaryError ? err.status : 400;
|
|
123
|
+
res.setHeader("content-type", "application/json");
|
|
124
|
+
res.writeHead(status);
|
|
125
|
+
res.end(JSON.stringify({
|
|
126
|
+
jsonrpc: "2.0",
|
|
127
|
+
id: null,
|
|
128
|
+
error: { code: -32600, message: "The request body could not be read." },
|
|
129
|
+
}));
|
|
130
|
+
// A body left unread would otherwise keep the socket busy.
|
|
131
|
+
if (status === 413)
|
|
132
|
+
req.resume();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// The proxy gives up on a call at its forward limit (or its worker goes away): the closed response
|
|
136
|
+
// aborts the call in flight rather than leave it running against the MCP server.
|
|
137
|
+
const closed = new AbortController();
|
|
138
|
+
res.on("close", () => {
|
|
139
|
+
if (!res.writableFinished)
|
|
140
|
+
closed.abort();
|
|
141
|
+
});
|
|
142
|
+
const response = await handleCodingMcpRequest(req.headers.authorization, body, deps, closed.signal);
|
|
143
|
+
if (closed.signal.aborted)
|
|
144
|
+
return;
|
|
145
|
+
res.setHeader("content-type", response.contentType);
|
|
146
|
+
res.writeHead(response.status);
|
|
147
|
+
res.end(response.body);
|
|
148
|
+
}
|
|
104
149
|
/** Starts the listener; resolves once it is accepting connections. */
|
|
105
150
|
export async function startGatewayServer(listen, deps) {
|
|
106
151
|
const server = createGatewayServer(deps);
|