@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
|
@@ -3,6 +3,10 @@ import { isPackageAuditMode, mergePackageAuditReports, } from "../../coding/regi
|
|
|
3
3
|
import { mkdir, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { join, resolve, sep } from "node:path";
|
|
5
5
|
import { withBaseCommit } from "../../coding/base-commit.js";
|
|
6
|
+
import { mcpTokenHash, mintMcpToken } from "../../coding/mcp-token.js";
|
|
7
|
+
import { isCodexCoding } from "../../core/mcp-tools.js";
|
|
8
|
+
import { CODING_MCP_GATEWAY_UNAVAILABLE } from "../../core/power.js";
|
|
9
|
+
import { codeProviderOf } from "../../coding/code-host.js";
|
|
6
10
|
import { withPullRequestDescription } from "../../coding/pr-description-note.js";
|
|
7
11
|
import { withLinkedIssue } from "../../coding/linked-issue-note.js";
|
|
8
12
|
import { buildClaudeContext, MAX_RECORDED_SKIPS } from "../../coding/claude-context.js";
|
|
@@ -169,8 +173,20 @@ export class PrismaContainerExecutionStore {
|
|
|
169
173
|
});
|
|
170
174
|
if (!row?.codingRun)
|
|
171
175
|
return null;
|
|
176
|
+
const link = row.codingRun.viaRepositoryLink
|
|
177
|
+
? await this.db.agentRepository.findFirst({
|
|
178
|
+
where: {
|
|
179
|
+
agentId: row.agentId,
|
|
180
|
+
repository: row.codingRun.repository,
|
|
181
|
+
access: "write",
|
|
182
|
+
triggers: { isEmpty: true },
|
|
183
|
+
},
|
|
184
|
+
select: { authorizedVia: true },
|
|
185
|
+
})
|
|
186
|
+
: null;
|
|
172
187
|
return {
|
|
173
188
|
runId: row.id,
|
|
189
|
+
runStepsLevel: row.runStepsLevel,
|
|
174
190
|
agentId: row.agentId,
|
|
175
191
|
status: row.status,
|
|
176
192
|
agentKind: row.agent.kind,
|
|
@@ -219,6 +235,9 @@ export class PrismaContainerExecutionStore {
|
|
|
219
235
|
failureCategory: row.codingRun.failureCategory,
|
|
220
236
|
profileRepository: row.agent.codingProfile?.repository ?? null,
|
|
221
237
|
repositoryAuthorizedVia: row.agent.codingProfile?.repositoryAuthorizedVia ?? null,
|
|
238
|
+
viaRepositoryLink: row.codingRun.viaRepositoryLink,
|
|
239
|
+
repositoryLinkExists: link !== null,
|
|
240
|
+
repositoryLinkAuthorizedVia: link?.authorizedVia ?? null,
|
|
222
241
|
pricingVersion: row.pricingVersion,
|
|
223
242
|
pricingSnapshot: row.pricingSnapshot,
|
|
224
243
|
};
|
|
@@ -596,9 +615,14 @@ export class PrismaContainerExecutionStore {
|
|
|
596
615
|
return changed;
|
|
597
616
|
}
|
|
598
617
|
}
|
|
599
|
-
/**
|
|
618
|
+
/**
|
|
619
|
+
* Plain capabilities live only for the short provisioning window. A run with MCP tools keeps its
|
|
620
|
+
* MCP token here too, next to the capability, so the launcher delivers it the same way; both are
|
|
621
|
+
* deleted together.
|
|
622
|
+
*/
|
|
600
623
|
export class RunCapabilityVault {
|
|
601
624
|
values = new Map();
|
|
625
|
+
mcpTokens = new Map();
|
|
602
626
|
set(runId, capability) {
|
|
603
627
|
if (this.values.has(runId))
|
|
604
628
|
throw new Error("coding_capability_exists");
|
|
@@ -610,8 +634,18 @@ export class RunCapabilityVault {
|
|
|
610
634
|
throw new Error("coding_capability_unavailable");
|
|
611
635
|
return capability;
|
|
612
636
|
}
|
|
637
|
+
setMcpToken(runId, token) {
|
|
638
|
+
if (this.mcpTokens.has(runId))
|
|
639
|
+
throw new Error("coding_mcp_token_exists");
|
|
640
|
+
this.mcpTokens.set(runId, token);
|
|
641
|
+
}
|
|
642
|
+
/** The run's MCP token; undefined for a run without MCP tools (and once the launch is over). */
|
|
643
|
+
async mcpToken(runId) {
|
|
644
|
+
return this.mcpTokens.get(runId);
|
|
645
|
+
}
|
|
613
646
|
delete(runId) {
|
|
614
647
|
this.values.delete(runId);
|
|
648
|
+
this.mcpTokens.delete(runId);
|
|
615
649
|
}
|
|
616
650
|
}
|
|
617
651
|
/** A run's deduplicated served packages and refusals (see summarizeRegistryFetches), and its package audit. */
|
|
@@ -827,6 +861,20 @@ export class ContainerExecutor {
|
|
|
827
861
|
if (TERMINAL_STATUSES.has(beforeSession.status))
|
|
828
862
|
throw new Error("coding_run_no_longer_active");
|
|
829
863
|
const deadlineAt = new Date(this.now().getTime() + run.timeoutSec * 1_000);
|
|
864
|
+
// The agent's MCP tools, pinned for the run; only a run with some gets a token. The token is
|
|
865
|
+
// never logged: only its hash goes to the session, the value only into the vault.
|
|
866
|
+
// A Codex run's shell commands can read the token, so it gets read-only tools only; decided by
|
|
867
|
+
// the provider the run was dispatched with, which is what jobSpec launches. A failed read fails
|
|
868
|
+
// the run with a fixed code (the cause goes to the operator log only, redacted).
|
|
869
|
+
let mcpTools;
|
|
870
|
+
try {
|
|
871
|
+
mcpTools =
|
|
872
|
+
(await this.options.mcpToolPins?.(run.agentId, { readOnlyOnly: isCodexCoding(run.provider) })) ?? [];
|
|
873
|
+
}
|
|
874
|
+
catch (error) {
|
|
875
|
+
throw new Error(MCP_PINS_UNAVAILABLE_ERROR, { cause: error });
|
|
876
|
+
}
|
|
877
|
+
const mcpToken = mcpTools.length > 0 ? mintMcpToken() : undefined;
|
|
830
878
|
const session = await this.options.sessions.createSession({
|
|
831
879
|
runId,
|
|
832
880
|
credentialRef: this.credentialRef(run.provider),
|
|
@@ -835,15 +883,19 @@ export class ContainerExecutor {
|
|
|
835
883
|
deadlineAt,
|
|
836
884
|
budgetUsd: run.budgetUsd,
|
|
837
885
|
terms: this.modelTerms(run),
|
|
886
|
+
...(mcpToken ? { mcpTools, mcpTokenHash: mcpTokenHash(mcpToken) } : {}),
|
|
887
|
+
...(run.runStepsLevel ? { runStepsLevel: run.runStepsLevel } : {}),
|
|
838
888
|
});
|
|
839
889
|
spendEnabled = true;
|
|
840
890
|
sessionId = session.id;
|
|
841
891
|
this.options.capabilities.set(runId, session.capability);
|
|
892
|
+
if (mcpToken)
|
|
893
|
+
this.options.capabilities.setMcpToken(runId, mcpToken);
|
|
842
894
|
const inputArtifact = await this.writeInput(run, deadlineAt, workspace);
|
|
843
895
|
const beforeLaunch = await this.requireCurrent(runId);
|
|
844
896
|
if (TERMINAL_STATUSES.has(beforeLaunch.status))
|
|
845
897
|
throw new Error("coding_run_no_longer_active");
|
|
846
|
-
const spec = this.jobSpec(run, inputArtifact);
|
|
898
|
+
const spec = this.jobSpec(run, inputArtifact, { mcpTools: Boolean(mcpToken) });
|
|
847
899
|
// Backends whose handle is derivable (Kubernetes) persist it first, so a crash between the
|
|
848
900
|
// launch and the write still leaves a handle `abandon()` can stop and remove the run with.
|
|
849
901
|
planned = this.options.jobs.plannedHandle?.(spec);
|
|
@@ -1005,13 +1057,21 @@ export class ContainerExecutor {
|
|
|
1005
1057
|
* access. Throws RepoAccessError (refused before anything is cloned).
|
|
1006
1058
|
*/
|
|
1007
1059
|
async authorizeRepository(run, opts) {
|
|
1060
|
+
// Checked first: a local repository is otherwise authorized by the trusted roots alone.
|
|
1061
|
+
if (run.viaRepositoryLink && !run.repositoryLinkExists)
|
|
1062
|
+
throw new RepoAccessError("repo_access_not_authorized");
|
|
1008
1063
|
const sameRepository = run.profileRepository !== null && run.profileRepository === run.repository;
|
|
1064
|
+
const authorizedVia = run.viaRepositoryLink
|
|
1065
|
+
? run.repositoryLinkAuthorizedVia
|
|
1066
|
+
: sameRepository
|
|
1067
|
+
? run.repositoryAuthorizedVia
|
|
1068
|
+
: "host_permission";
|
|
1009
1069
|
const decision = await this.options.repoAccess.authorizeUse({
|
|
1010
1070
|
ownerId: run.ownerId,
|
|
1011
|
-
provider:
|
|
1071
|
+
provider: codeProviderOf(run.repository),
|
|
1012
1072
|
repository: run.repository,
|
|
1013
1073
|
required: requiredLevel("coding"),
|
|
1014
|
-
authorizedVia
|
|
1074
|
+
authorizedVia,
|
|
1015
1075
|
retryTransient: opts.inFlight,
|
|
1016
1076
|
});
|
|
1017
1077
|
if (!decision.ok)
|
|
@@ -1510,7 +1570,8 @@ export class ContainerExecutor {
|
|
|
1510
1570
|
status.number > 0) {
|
|
1511
1571
|
candidates.push({ provider: "github", repository: run.repository, number: status.number });
|
|
1512
1572
|
}
|
|
1513
|
-
|
|
1573
|
+
// The closing keyword names a GitHub issue: no other host's repository has one to read.
|
|
1574
|
+
const closed = codeProviderOf(run.repository) === "github" ? closingIssueNumber(description, run.repository) : undefined;
|
|
1514
1575
|
if (closed !== undefined && !candidates.some((ref) => ref.provider === "github" && ref.number === closed)) {
|
|
1515
1576
|
candidates.push({ provider: "github", repository: run.repository, number: closed });
|
|
1516
1577
|
}
|
|
@@ -1724,7 +1785,8 @@ export class ContainerExecutor {
|
|
|
1724
1785
|
if (!workspace)
|
|
1725
1786
|
throw new Error("coding_workspace_lost");
|
|
1726
1787
|
// The same in-flight access check a push gets: a revoked owner's run edits nothing.
|
|
1727
|
-
|
|
1788
|
+
// Reloaded: the snapshot passed in predates the description reads above, and a link removed since then counts.
|
|
1789
|
+
await this.authorizeRepository(await this.requireCurrent(run.runId), { inFlight: true });
|
|
1728
1790
|
const details = {
|
|
1729
1791
|
summary: output.summary,
|
|
1730
1792
|
tests: output.tests,
|
|
@@ -1990,7 +2052,7 @@ export class ContainerExecutor {
|
|
|
1990
2052
|
this.emit({ stage: "queued", runId: run.runId });
|
|
1991
2053
|
return false;
|
|
1992
2054
|
}
|
|
1993
|
-
jobSpec(run, inputArtifact) {
|
|
2055
|
+
jobSpec(run, inputArtifact, options = {}) {
|
|
1994
2056
|
const provider = run.provider === "claude-code" ? "claude-code" : "codex";
|
|
1995
2057
|
if (provider === "claude-code" && (!run.workerImage || !this.options.claudeToolRunnerImage)) {
|
|
1996
2058
|
throw new Error("coding_provider_not_configured:claude-code");
|
|
@@ -2019,6 +2081,8 @@ export class ContainerExecutor {
|
|
|
2019
2081
|
...(run.dependencyCache ? { dependencyCache: true } : {}),
|
|
2020
2082
|
// Claude Code only: the launcher gives the SDK's config dir, where the session grows, its own scratch.
|
|
2021
2083
|
...(run.sessionResume && provider === "claude-code" ? { sessionResume: true } : {}),
|
|
2084
|
+
// Only for a run with MCP tools: the launcher then delivers WARDBY_MCP_TOKEN from the vault.
|
|
2085
|
+
...(options.mcpTools ? { mcpTools: true } : {}),
|
|
2022
2086
|
timeoutSec: run.timeoutSec,
|
|
2023
2087
|
limits: { ...this.options.limits, ...(run.workspaceDiskMb ? { diskMb: run.workspaceDiskMb } : {}) },
|
|
2024
2088
|
labels: {},
|
|
@@ -2420,13 +2484,19 @@ function safeError(error) {
|
|
|
2420
2484
|
const message = error instanceof Error ? error.message : "unknown";
|
|
2421
2485
|
return `coding_executor:${redactAndTruncate(message, 200).replace(/[^A-Za-z0-9_.:-]/g, "_")}`;
|
|
2422
2486
|
}
|
|
2487
|
+
/** The agent's MCP tool pins could not be read at launch (a database error): the run fails, nothing launched. */
|
|
2488
|
+
export const MCP_PINS_UNAVAILABLE_ERROR = "coding_mcp_pins_unavailable";
|
|
2489
|
+
/** Its category: the run's error is coding_failure_mcp_pins_unavailable:<diagnostic id>. */
|
|
2490
|
+
export const MCP_PINS_UNAVAILABLE_CATEGORY = "mcp_pins_unavailable";
|
|
2491
|
+
/** Low-power mode: the native gateway serving a run's MCP tools did not wake; nothing launched. */
|
|
2492
|
+
export const MCP_GATEWAY_UNAVAILABLE_CATEGORY = "mcp_gateway_unavailable";
|
|
2423
2493
|
/**
|
|
2424
2494
|
* Error-code prefixes that name their category outright, checked before the
|
|
2425
2495
|
* substring heuristics below. Those heuristics filed every `github_*` error
|
|
2426
2496
|
* (a failed PR lookup or create after the push, a token mint) under
|
|
2427
2497
|
* "workspace" because "github" contains "git".
|
|
2428
2498
|
*/
|
|
2429
|
-
const CATEGORY_BY_PREFIX = [
|
|
2499
|
+
export const CATEGORY_BY_PREFIX = [
|
|
2430
2500
|
// Claude Code reached the agent's turn limit (codingProfile.maxTurns) before finishing.
|
|
2431
2501
|
["job_coding_turn_limit", "turn_limit"],
|
|
2432
2502
|
// A service sidecar never became ready (src/providers/jobs/kubernetes.ts).
|
|
@@ -2453,7 +2523,13 @@ const CATEGORY_BY_PREFIX = [
|
|
|
2453
2523
|
["coding_provider_not_configured", "preflight"],
|
|
2454
2524
|
// A GitHub repository on a server with no GitHub App: configuration, like an unsupported service.
|
|
2455
2525
|
["vcs_github_not_configured", "preflight"],
|
|
2526
|
+
// The same for any other host: a repository whose provider this server has not registered.
|
|
2527
|
+
["vcs_host_provider_not_configured", "preflight"],
|
|
2456
2528
|
["vcs_", "workspace"],
|
|
2529
|
+
// The agent's MCP tool pins could not be read at launch: nothing was launched.
|
|
2530
|
+
[MCP_PINS_UNAVAILABLE_ERROR, MCP_PINS_UNAVAILABLE_CATEGORY],
|
|
2531
|
+
// Low-power mode: the native gateway serving the run's MCP tools did not wake (jobs/kubernetes.ts).
|
|
2532
|
+
[CODING_MCP_GATEWAY_UNAVAILABLE, MCP_GATEWAY_UNAVAILABLE_CATEGORY],
|
|
2457
2533
|
// Local repositories (LocalRepoError codes): the same family as the vcs_ errors.
|
|
2458
2534
|
["local_", "workspace"],
|
|
2459
2535
|
["git_", "workspace"],
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs one approved snapshot of a bound tool with a restricted privileged host: fetch
|
|
3
|
+
* reaches the provider's API hosts inside this call's repository, using the operation's
|
|
4
|
+
* methods, with no redirects followed and only plain content headers, and the only secret alias is `host`. Datastores and readable secrets are
|
|
5
|
+
* absent, and console output is dropped. Never throws: every failure is a fixed code,
|
|
6
|
+
* so no tool text reaches a model, a run record or a published comment.
|
|
7
|
+
*/
|
|
8
|
+
import type { SandboxLimits } from "../../sandbox/eval-core.js";
|
|
9
|
+
import { safeFetch } from "../../sandbox/safe-fetch.js";
|
|
10
|
+
import type { SecretEntry } from "../../core/secrets.js";
|
|
11
|
+
import { type HostOperation, type HostToolDeclineCode } from "./operations.js";
|
|
12
|
+
export declare const HOST_SECRET_ALIAS = "host";
|
|
13
|
+
export type HostToolErrorCode = "host_tool_failed" | "host_tool_timeout" | "host_tool_invalid_result" | "host_tool_invalid_params" | "host_tool_request_denied" | "host_tool_no_credential" | `host_${HostToolDeclineCode}`;
|
|
14
|
+
export type HostToolResult = {
|
|
15
|
+
ok: true;
|
|
16
|
+
value: unknown;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
code: HostToolErrorCode;
|
|
20
|
+
};
|
|
21
|
+
export interface HostToolInvocation {
|
|
22
|
+
provider: string;
|
|
23
|
+
operation: HostOperation;
|
|
24
|
+
snapshot: {
|
|
25
|
+
code: string;
|
|
26
|
+
paramsZod: string;
|
|
27
|
+
};
|
|
28
|
+
params: Record<string, unknown>;
|
|
29
|
+
apiHosts: readonly string[];
|
|
30
|
+
pathPrefix: string;
|
|
31
|
+
credential: () => Promise<SecretEntry | undefined>;
|
|
32
|
+
fetchImpl?: typeof safeFetch;
|
|
33
|
+
limits?: Partial<SandboxLimits>;
|
|
34
|
+
/** The approved snapshot's time limit (effectiveToolTimeoutMs); absent = the default. */
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
}
|
|
37
|
+
export declare function invokeHostTool(input: HostToolInvocation): Promise<HostToolResult>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createPrivilegedHost } from "../../sandbox/host-functions.js";
|
|
2
|
+
import { runToolCode, toolLimits } from "../../sandbox/run-in-sandbox.js";
|
|
3
|
+
import { fetchTimeoutFor } from "../../sandbox/tool-timeout.js";
|
|
4
|
+
import { WALL_TIME_LIMIT_MS } from "../../sandbox/limits.js";
|
|
5
|
+
import { safeFetch } from "../../sandbox/safe-fetch.js";
|
|
6
|
+
import { validateParams } from "../../sandbox/zod-params.js";
|
|
7
|
+
import { HOST_OPERATIONS, HOST_TOOL_DECLINE_CODES, } from "./operations.js";
|
|
8
|
+
import { assertHostToolHeaders, assertHostToolRequest, HostToolRequestDenied, MAX_HOST_TOOL_FETCHES, } from "./request-guard.js";
|
|
9
|
+
export const HOST_SECRET_ALIAS = "host";
|
|
10
|
+
const absent = async () => {
|
|
11
|
+
throw new Error("host_tool_capability_absent");
|
|
12
|
+
};
|
|
13
|
+
export async function invokeHostTool(input) {
|
|
14
|
+
const definition = HOST_OPERATIONS[input.operation];
|
|
15
|
+
const failures = { denied: false, noCredential: false };
|
|
16
|
+
const validated = await validateParams(input.snapshot.paramsZod, input.params);
|
|
17
|
+
if (!validated.ok)
|
|
18
|
+
return { ok: false, code: "host_tool_invalid_params" };
|
|
19
|
+
const privileged = (signal) => {
|
|
20
|
+
const inner = createPrivilegedHost({
|
|
21
|
+
agentId: `host-provider:${input.provider}`,
|
|
22
|
+
// Never reached: every bridge that would use them is `absent` below.
|
|
23
|
+
datastore: {},
|
|
24
|
+
sharedDatastore: {},
|
|
25
|
+
logTag: `host-tool:${input.operation}`,
|
|
26
|
+
allowedFetchHosts: [...input.apiHosts],
|
|
27
|
+
// Redirects are never followed here: a later hop would skip the guard, so the tool re-fetches through it.
|
|
28
|
+
fetchImpl: (u, i, o) => (input.fetchImpl ?? safeFetch)(u, i, { ...o, followRedirects: false }),
|
|
29
|
+
signal,
|
|
30
|
+
fetchTimeoutMs: fetchTimeoutFor(input.timeoutMs ?? input.limits?.wallTimeLimitMs ?? WALL_TIME_LIMIT_MS),
|
|
31
|
+
secrets: {
|
|
32
|
+
get: async () => undefined,
|
|
33
|
+
resolve: async (name) => {
|
|
34
|
+
if (name !== HOST_SECRET_ALIAS)
|
|
35
|
+
return undefined;
|
|
36
|
+
let entry;
|
|
37
|
+
try {
|
|
38
|
+
entry = await input.credential();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
entry = undefined;
|
|
42
|
+
}
|
|
43
|
+
// A thrown or partial credential must not carry its text into tool code. Registering
|
|
44
|
+
// checks the hosts, but a broker config can be narrowed afterwards.
|
|
45
|
+
if (!entry || !entry.broker || input.apiHosts.some((h) => !entry.broker.hosts.includes(h))) {
|
|
46
|
+
failures.noCredential = true;
|
|
47
|
+
throw new Error("host_tool_no_credential");
|
|
48
|
+
}
|
|
49
|
+
return entry;
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
let fetches = 0;
|
|
54
|
+
return {
|
|
55
|
+
__bridge_console: async () => null,
|
|
56
|
+
__bridge_fetch: async (argsJson) => {
|
|
57
|
+
try {
|
|
58
|
+
const [url, init, secretNames] = JSON.parse(argsJson);
|
|
59
|
+
if (++fetches > MAX_HOST_TOOL_FETCHES)
|
|
60
|
+
throw new HostToolRequestDenied();
|
|
61
|
+
if (secretNames !== undefined &&
|
|
62
|
+
!(Array.isArray(secretNames) && secretNames.every((n) => n === HOST_SECRET_ALIAS))) {
|
|
63
|
+
throw new HostToolRequestDenied();
|
|
64
|
+
}
|
|
65
|
+
assertHostToolHeaders(init?.headers);
|
|
66
|
+
assertHostToolRequest(url, init?.method ?? "GET", {
|
|
67
|
+
apiHosts: input.apiHosts,
|
|
68
|
+
pathPrefix: input.pathPrefix,
|
|
69
|
+
methods: definition.methods,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
failures.denied = true;
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
return inner.__bridge_fetch(argsJson);
|
|
77
|
+
},
|
|
78
|
+
__bridge_datastoreGet: absent,
|
|
79
|
+
__bridge_datastoreSet: absent,
|
|
80
|
+
__bridge_datastoreDelete: absent,
|
|
81
|
+
__bridge_datastoreList: absent,
|
|
82
|
+
__bridge_sharedDatastoreGet: absent,
|
|
83
|
+
__bridge_sharedDatastoreSet: absent,
|
|
84
|
+
__bridge_sharedDatastoreDelete: absent,
|
|
85
|
+
__bridge_sharedDatastoreList: absent,
|
|
86
|
+
__bridge_secretsGet: absent,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const result = await runToolCode({
|
|
90
|
+
code: input.snapshot.code,
|
|
91
|
+
params: validated.value,
|
|
92
|
+
limits: toolLimits(input.timeoutMs, input.limits),
|
|
93
|
+
privileged,
|
|
94
|
+
});
|
|
95
|
+
// Checked first: a tool that catches a refusal must not turn it into a success.
|
|
96
|
+
if (failures.denied)
|
|
97
|
+
return { ok: false, code: "host_tool_request_denied" };
|
|
98
|
+
if (failures.noCredential)
|
|
99
|
+
return { ok: false, code: "host_tool_no_credential" };
|
|
100
|
+
if (!result.ok) {
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
code: result.errorKind === "timeout" || result.errorKind === "cpu" ? "host_tool_timeout" : "host_tool_failed",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const decline = declineOf(result.value);
|
|
107
|
+
if (decline)
|
|
108
|
+
return { ok: false, code: `host_${decline}` };
|
|
109
|
+
const parsed = definition.result.safeParse(result.value);
|
|
110
|
+
return parsed.success ? { ok: true, value: parsed.data } : { ok: false, code: "host_tool_invalid_result" };
|
|
111
|
+
}
|
|
112
|
+
function declineOf(value) {
|
|
113
|
+
if (typeof value !== "object" || value === null || !("error" in value))
|
|
114
|
+
return null;
|
|
115
|
+
const { error } = value;
|
|
116
|
+
return HOST_TOOL_DECLINE_CODES.includes(error) ? error : null;
|
|
117
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract between Wardby and a host provider's bound tools. A bound tool makes
|
|
3
|
+
* one host operation; paging, markers, safeguards and wording stay in Wardby. Wardby
|
|
4
|
+
* calls each tool with `{ workspace, repo, repository, ...operation fields }`.
|
|
5
|
+
*
|
|
6
|
+
* A tool answers with its operation's result, or `{ error: <decline code> }` when the
|
|
7
|
+
* host refused. Results are untrusted and validated here, and a tool's own text never
|
|
8
|
+
* reaches a model, a run record or a comment.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
export declare const HOST_TOOL_CONTRACT_VERSION = 1;
|
|
12
|
+
/** What a tool may say about a host refusal: fixed words, so no tool text passes through. */
|
|
13
|
+
export declare const HOST_TOOL_DECLINE_CODES: readonly ["unauthorized", "forbidden", "not_found", "conflict", "rate_limited", "unavailable"];
|
|
14
|
+
export type HostToolDeclineCode = (typeof HOST_TOOL_DECLINE_CODES)[number];
|
|
15
|
+
type OperationDef = {
|
|
16
|
+
/** The HTTP methods this operation's requests may use. */
|
|
17
|
+
methods: readonly string[];
|
|
18
|
+
/** Changes nothing on the host: test_host_provider may call it. */
|
|
19
|
+
readOnly: boolean;
|
|
20
|
+
/** Which credential its `host` alias resolves to. */
|
|
21
|
+
credential: "repository" | "permission" | "none";
|
|
22
|
+
result: z.ZodType;
|
|
23
|
+
};
|
|
24
|
+
export declare const HOST_OPERATIONS: {
|
|
25
|
+
readonly getPullRequest: OperationDef;
|
|
26
|
+
readonly listPullRequestFiles: OperationDef;
|
|
27
|
+
readonly getDiff: OperationDef;
|
|
28
|
+
readonly findPullRequest: OperationDef;
|
|
29
|
+
readonly readFile: OperationDef;
|
|
30
|
+
readonly listFiles: OperationDef;
|
|
31
|
+
readonly listComments: OperationDef;
|
|
32
|
+
readonly listCommitStatuses: OperationDef;
|
|
33
|
+
readonly getRepositoryPermission: {
|
|
34
|
+
readonly methods: readonly ["GET"];
|
|
35
|
+
readonly readOnly: true;
|
|
36
|
+
readonly credential: "permission";
|
|
37
|
+
readonly result: z.ZodObject<{
|
|
38
|
+
permission: z.ZodEnum<{
|
|
39
|
+
read: "read";
|
|
40
|
+
write: "write";
|
|
41
|
+
none: "none";
|
|
42
|
+
triage: "triage";
|
|
43
|
+
maintain: "maintain";
|
|
44
|
+
admin: "admin";
|
|
45
|
+
}>;
|
|
46
|
+
}, z.core.$strict>;
|
|
47
|
+
};
|
|
48
|
+
readonly openPullRequest: OperationDef;
|
|
49
|
+
readonly updatePullRequest: OperationDef;
|
|
50
|
+
readonly createComment: OperationDef;
|
|
51
|
+
readonly editComment: OperationDef;
|
|
52
|
+
readonly resolveThread: OperationDef;
|
|
53
|
+
readonly submitVerdict: OperationDef;
|
|
54
|
+
readonly setCommitStatus: OperationDef;
|
|
55
|
+
readonly normalizeEvent: {
|
|
56
|
+
readonly methods: readonly [];
|
|
57
|
+
readonly readOnly: false;
|
|
58
|
+
readonly credential: "none";
|
|
59
|
+
readonly result: z.ZodUnknown;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
export type HostOperation = keyof typeof HOST_OPERATIONS;
|
|
63
|
+
export declare const REQUIRED_OPERATIONS: readonly HostOperation[];
|
|
64
|
+
export declare function isHostOperation(name: string): name is HostOperation;
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract between Wardby and a host provider's bound tools. A bound tool makes
|
|
3
|
+
* one host operation; paging, markers, safeguards and wording stay in Wardby. Wardby
|
|
4
|
+
* calls each tool with `{ workspace, repo, repository, ...operation fields }`.
|
|
5
|
+
*
|
|
6
|
+
* A tool answers with its operation's result, or `{ error: <decline code> }` when the
|
|
7
|
+
* host refused. Results are untrusted and validated here, and a tool's own text never
|
|
8
|
+
* reaches a model, a run record or a comment.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
export const HOST_TOOL_CONTRACT_VERSION = 1;
|
|
12
|
+
/** What a tool may say about a host refusal: fixed words, so no tool text passes through. */
|
|
13
|
+
export const HOST_TOOL_DECLINE_CODES = [
|
|
14
|
+
"unauthorized",
|
|
15
|
+
"forbidden",
|
|
16
|
+
"not_found",
|
|
17
|
+
"conflict",
|
|
18
|
+
"rate_limited",
|
|
19
|
+
"unavailable",
|
|
20
|
+
];
|
|
21
|
+
// What a paged operation returns; its tool takes the last one back as the optional `page` input.
|
|
22
|
+
const page = z.string().nullable();
|
|
23
|
+
const pullRequest = z
|
|
24
|
+
.object({
|
|
25
|
+
number: z.number().int().positive(),
|
|
26
|
+
title: z.string(),
|
|
27
|
+
body: z.string(),
|
|
28
|
+
author: z.string().nullable(),
|
|
29
|
+
// A merged pull request is "closed" with `merged` set: consumers compare state to "open".
|
|
30
|
+
state: z.enum(["open", "closed"]),
|
|
31
|
+
merged: z.boolean(),
|
|
32
|
+
draft: z.boolean(),
|
|
33
|
+
baseRef: z.string(),
|
|
34
|
+
headRef: z.string(),
|
|
35
|
+
headSha: z.string().min(1),
|
|
36
|
+
isFork: z.boolean(),
|
|
37
|
+
htmlUrl: z.string().url(),
|
|
38
|
+
})
|
|
39
|
+
.strict();
|
|
40
|
+
const written = z.object({ id: z.string().min(1) }).strict();
|
|
41
|
+
const read = (result) => ({
|
|
42
|
+
methods: ["GET"],
|
|
43
|
+
readOnly: true,
|
|
44
|
+
credential: "repository",
|
|
45
|
+
result,
|
|
46
|
+
});
|
|
47
|
+
const write = (methods, result) => ({
|
|
48
|
+
methods,
|
|
49
|
+
readOnly: false,
|
|
50
|
+
credential: "repository",
|
|
51
|
+
result,
|
|
52
|
+
});
|
|
53
|
+
export const HOST_OPERATIONS = {
|
|
54
|
+
getPullRequest: read(pullRequest),
|
|
55
|
+
listPullRequestFiles: read(z
|
|
56
|
+
.object({
|
|
57
|
+
files: z.array(z
|
|
58
|
+
.object({
|
|
59
|
+
filename: z.string(),
|
|
60
|
+
status: z.string(),
|
|
61
|
+
additions: z.number().int().nonnegative(),
|
|
62
|
+
deletions: z.number().int().nonnegative(),
|
|
63
|
+
previousFilename: z.string().optional(),
|
|
64
|
+
})
|
|
65
|
+
.strict()),
|
|
66
|
+
nextPage: page,
|
|
67
|
+
})
|
|
68
|
+
.strict()),
|
|
69
|
+
getDiff: read(z.object({ diff: z.string() }).strict()),
|
|
70
|
+
findPullRequest: read(z.object({ pullRequest: pullRequest.nullable() }).strict()),
|
|
71
|
+
readFile: read(z.discriminatedUnion("kind", [
|
|
72
|
+
z.object({ kind: z.literal("file"), content: z.string() }).strict(),
|
|
73
|
+
z.object({ kind: z.literal("directory") }).strict(),
|
|
74
|
+
z.object({ kind: z.literal("not_found") }).strict(),
|
|
75
|
+
])),
|
|
76
|
+
listFiles: read(z
|
|
77
|
+
.object({
|
|
78
|
+
entries: z.array(z
|
|
79
|
+
.object({
|
|
80
|
+
path: z.string(),
|
|
81
|
+
type: z.enum(["file", "directory"]),
|
|
82
|
+
size: z.number().int().nonnegative().optional(),
|
|
83
|
+
})
|
|
84
|
+
.strict()),
|
|
85
|
+
nextPage: page,
|
|
86
|
+
})
|
|
87
|
+
.strict()),
|
|
88
|
+
listComments: read(z
|
|
89
|
+
.object({
|
|
90
|
+
comments: z.array(z
|
|
91
|
+
.object({
|
|
92
|
+
id: z.string().min(1),
|
|
93
|
+
author: z.string().nullable(),
|
|
94
|
+
authorId: z.string().nullable(),
|
|
95
|
+
body: z.string(),
|
|
96
|
+
createdAt: z.string(),
|
|
97
|
+
path: z.string().nullable(),
|
|
98
|
+
line: z.number().int().positive().nullable(),
|
|
99
|
+
parentId: z.string().nullable(),
|
|
100
|
+
resolved: z.boolean(),
|
|
101
|
+
})
|
|
102
|
+
.strict()),
|
|
103
|
+
nextPage: page,
|
|
104
|
+
})
|
|
105
|
+
.strict()),
|
|
106
|
+
listCommitStatuses: read(z
|
|
107
|
+
.object({
|
|
108
|
+
statuses: z.array(z
|
|
109
|
+
.object({
|
|
110
|
+
key: z.string(),
|
|
111
|
+
state: z.enum(["pending", "success", "failure", "stopped"]),
|
|
112
|
+
url: z.string().nullable(),
|
|
113
|
+
})
|
|
114
|
+
.strict()),
|
|
115
|
+
nextPage: page,
|
|
116
|
+
})
|
|
117
|
+
.strict()),
|
|
118
|
+
getRepositoryPermission: {
|
|
119
|
+
methods: ["GET"],
|
|
120
|
+
readOnly: true,
|
|
121
|
+
credential: "permission",
|
|
122
|
+
result: z.object({ permission: z.enum(["none", "read", "triage", "write", "maintain", "admin"]) }).strict(),
|
|
123
|
+
},
|
|
124
|
+
openPullRequest: write(["POST"], pullRequest),
|
|
125
|
+
updatePullRequest: write(["PUT"], pullRequest),
|
|
126
|
+
createComment: write(["POST"], written),
|
|
127
|
+
editComment: write(["PUT"], written),
|
|
128
|
+
resolveThread: write(["POST"], z.object({}).strict()),
|
|
129
|
+
submitVerdict: write(["POST"], z.object({}).strict()),
|
|
130
|
+
setCommitStatus: write(["POST"], z.object({}).strict()),
|
|
131
|
+
// PR 5 gives this its HostEvent schema. Nothing calls it before then.
|
|
132
|
+
normalizeEvent: { methods: [], readOnly: false, credential: "none", result: z.unknown() },
|
|
133
|
+
};
|
|
134
|
+
export const REQUIRED_OPERATIONS = [
|
|
135
|
+
"getPullRequest",
|
|
136
|
+
"listPullRequestFiles",
|
|
137
|
+
"getDiff",
|
|
138
|
+
"readFile",
|
|
139
|
+
"listFiles",
|
|
140
|
+
"listComments",
|
|
141
|
+
"createComment",
|
|
142
|
+
"submitVerdict",
|
|
143
|
+
"findPullRequest",
|
|
144
|
+
"openPullRequest",
|
|
145
|
+
"updatePullRequest",
|
|
146
|
+
];
|
|
147
|
+
export function isHostOperation(name) {
|
|
148
|
+
return Object.hasOwn(HOST_OPERATIONS, name);
|
|
149
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { PrismaClient } from "#prisma";
|
|
3
|
+
import type { SecretCipher } from "../secrets/types.js";
|
|
4
|
+
export type HostProviderErrorCode = "host_provider_not_found" | "host_provider_missing_operation" | "host_provider_tool_not_found" | "host_provider_secret_not_found" | "host_provider_secret_not_brokered" | "host_provider_secret_hosts" | "host_provider_secret_placement" | "host_provider_webhook_secret_shared" | "host_provider_webhook_secret_short" | "host_provider_permission_secret_shared" | "host_provider_username_is_secret" | "host_provider_in_use" | "host_provider_operation_not_bound" | "host_provider_tool_deleted";
|
|
5
|
+
export declare class HostProviderError extends Error {
|
|
6
|
+
readonly code: HostProviderErrorCode;
|
|
7
|
+
readonly detail: string;
|
|
8
|
+
constructor(code: HostProviderErrorCode, detail: string);
|
|
9
|
+
}
|
|
10
|
+
export declare const RegisterHostProviderSchema: z.ZodObject<{
|
|
11
|
+
provider: z.ZodString;
|
|
12
|
+
gitHost: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
13
|
+
apiHosts: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
14
|
+
gitUsername: z.ZodString;
|
|
15
|
+
pullRequestUrlTemplate: z.ZodString;
|
|
16
|
+
repositoryApiPath: z.ZodString;
|
|
17
|
+
permissionApiPath: z.ZodOptional<z.ZodString>;
|
|
18
|
+
webhook: z.ZodObject<{
|
|
19
|
+
header: z.ZodString;
|
|
20
|
+
algorithm: z.ZodLiteral<"sha256">;
|
|
21
|
+
deliveryHeader: z.ZodString;
|
|
22
|
+
eventHeader: z.ZodString;
|
|
23
|
+
secret: z.ZodString;
|
|
24
|
+
}, z.core.$strict>;
|
|
25
|
+
credentials: z.ZodArray<z.ZodObject<{
|
|
26
|
+
repositories: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
27
|
+
secret: z.ZodString;
|
|
28
|
+
}, z.core.$strict>>;
|
|
29
|
+
permissionCredential: z.ZodOptional<z.ZodString>;
|
|
30
|
+
trustedAccounts: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
31
|
+
tools: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
32
|
+
}, z.core.$strict>;
|
|
33
|
+
export type RegisterHostProviderInput = z.infer<typeof RegisterHostProviderSchema>;
|
|
34
|
+
export declare function findCredential<T extends {
|
|
35
|
+
repositoryPattern: string;
|
|
36
|
+
}>(credentials: readonly T[], repositoryPath: string): T | null;
|
|
37
|
+
export interface OperationView {
|
|
38
|
+
operation: string;
|
|
39
|
+
toolName: string | null;
|
|
40
|
+
status: "approved" | "changed" | "tool_deleted";
|
|
41
|
+
approvedAt: Date;
|
|
42
|
+
approvedById: string;
|
|
43
|
+
approvedSha256: string;
|
|
44
|
+
currentSha256: string | null;
|
|
45
|
+
/** The approved snapshot's time limit (null: the default), and the tool's now; a change waits for approval. */
|
|
46
|
+
approvedTimeoutMs: number | null;
|
|
47
|
+
currentTimeoutMs: number | null;
|
|
48
|
+
approvedCode?: string;
|
|
49
|
+
approvedParamsZod?: string;
|
|
50
|
+
currentCode?: string;
|
|
51
|
+
currentParamsZod?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface HostProviderView {
|
|
54
|
+
provider: string;
|
|
55
|
+
contractVersion: number;
|
|
56
|
+
gitHost: string;
|
|
57
|
+
apiHosts: string[];
|
|
58
|
+
gitUsername: string;
|
|
59
|
+
pullRequestUrlTemplate: string;
|
|
60
|
+
repositoryApiPath: string;
|
|
61
|
+
permissionApiPath: string | null;
|
|
62
|
+
mode: "permission" | "allowlist";
|
|
63
|
+
webhook: unknown;
|
|
64
|
+
webhookSecret: string;
|
|
65
|
+
permissionSecret: string | null;
|
|
66
|
+
credentials: {
|
|
67
|
+
repositories: string;
|
|
68
|
+
secret: string;
|
|
69
|
+
kind: string;
|
|
70
|
+
}[];
|
|
71
|
+
trustedAccounts: string[];
|
|
72
|
+
operations: OperationView[];
|
|
73
|
+
}
|
|
74
|
+
export declare function registerHostProvider(db: PrismaClient, cipher: SecretCipher, principalId: string, input: RegisterHostProviderInput): Promise<HostProviderView>;
|
|
75
|
+
export declare function approveHostProviderTools(db: PrismaClient, principalId: string, provider: string, operations: readonly string[]): Promise<HostProviderView>;
|
|
76
|
+
export declare function getHostProviderView(db: PrismaClient, provider: string, options?: {
|
|
77
|
+
includeCode?: boolean;
|
|
78
|
+
}): Promise<HostProviderView | null>;
|
|
79
|
+
export declare function deleteHostProvider(db: PrismaClient, provider: string): Promise<void>;
|