@wardby/cli 0.7.0 → 0.8.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 +6 -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 +61 -0
- package/dist/claude-plugin/command.js +376 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +2 -1
- package/dist/cli.js +47 -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 +1 -1
- 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 +36 -11
- package/dist/core/error-code.js +1 -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/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/related-pull-requests.js +26 -10
- package/dist/core/review-fix.js +2 -2
- package/dist/core/review-host-tools.js +3 -1
- package/dist/core/runner.d.ts +66 -16
- package/dist/core/runner.js +281 -73
- 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 +31 -0
- package/dist/generated/prisma/client.d.ts +31 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +50 -50
- package/dist/generated/prisma/internal/class.d.ts +66 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +530 -1
- package/dist/generated/prisma/internal/prismaNamespace.js +75 -2
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +79 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +75 -2
- package/dist/generated/prisma/models/Agent.d.ts +271 -0
- 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 +119 -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/Secret.d.ts +387 -0
- package/dist/generated/prisma/models/Tool.d.ts +223 -1
- package/dist/generated/prisma/models.d.ts +6 -0
- package/dist/help/search.js +12 -3
- package/dist/help-index.json +719 -47
- 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/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 +3 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/tools/agents.js +100 -15
- 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/repositories.js +50 -10
- package/dist/mcp/tools/runs.js +178 -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 +14 -0
- package/dist/native-worker/gateway-server.js +28 -7
- package/dist/native-worker/gateway.d.ts +2 -0
- package/dist/native-worker/gateway.js +5 -1
- package/dist/native-worker/http-server.d.ts +4 -0
- package/dist/native-worker/http-server.js +51 -0
- 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 +15 -0
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +1 -0
- package/dist/providers/coding-proxy/prisma-ledger.js +30 -2
- package/dist/providers/coding-proxy/proxy.d.ts +7 -1
- package/dist/providers/coding-proxy/proxy.js +134 -34
- package/dist/providers/coding-proxy/runtime.d.ts +6 -0
- package/dist/providers/coding-proxy/runtime.js +32 -1
- 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 +13 -0
- package/dist/providers/executor/composition.js +5 -0
- package/dist/providers/executor/container.d.ts +33 -1
- package/dist/providers/executor/container.js +76 -7
- 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 +5 -0
- package/dist/providers/jobs/kubernetes.js +6 -1
- 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/viewer/graph.js +2 -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 +164 -0
- package/docs/code-review-agents.md +6 -0
- package/docs/coding-agent-setup.md +77 -1
- package/docs/coding-worker-byo-images.md +4 -2
- package/docs/coding-worker-isolation.md +78 -7
- package/docs/debugging-agent-runs.md +242 -0
- package/docs/getting-started-gke.md +5 -4
- 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 +254 -0
- package/docs/native-sandbox.md +25 -9
- package/docs/security-deployment.md +110 -31
- package/docs/tool-secrets.md +3 -0
- package/docs/viewer-api.md +6 -0
- package/docs/viewer-api.openapi.yml +1 -1
- package/help/admin-viewer.md +4 -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 +40 -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/mcp-servers.md +73 -0
- package/help/mcp.md +7 -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 +3 -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/schema.prisma +159 -14
- package/scripts/git-askpass.sh +1 -1
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { MIN_REDACTABLE_SECRET_LENGTH, redactExactSecretValues, withEscapedForms } from "./secret-spans.js";
|
|
2
2
|
import { buildSecretsAccessor } from "./secrets.js";
|
|
3
3
|
import { consoleRedactionValues } from "../sandbox/secret-broker.js";
|
|
4
|
-
|
|
4
|
+
import { mcpNamedSecretValues } from "./mcp-tools.js";
|
|
5
|
+
/**
|
|
6
|
+
* Every secret attached to the agent (readable and brokered), and every secret of an MCP server
|
|
7
|
+
* attached to it (named `mcp:<server>/<secret>`), with every form to look for. Rejects if any read fails.
|
|
8
|
+
*/
|
|
5
9
|
export async function loadAgentSecretValues(db, cipher, agentId) {
|
|
6
|
-
const rows = await
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
const [rows, mcp] = await Promise.all([
|
|
11
|
+
db.agentSecret.findMany({ where: { agentId }, select: { boundName: true } }),
|
|
12
|
+
mcpNamedSecretValues(db, cipher, agentId),
|
|
13
|
+
]);
|
|
9
14
|
const accessor = buildSecretsAccessor(agentId, cipher, db);
|
|
10
|
-
|
|
15
|
+
const own = await Promise.all(rows.map(async ({ boundName }) => {
|
|
11
16
|
const entry = await accessor.resolve(boundName);
|
|
12
17
|
return { name: boundName, values: entry ? withEscapedForms(consoleRedactionValues(entry)) : [] };
|
|
13
18
|
}));
|
|
19
|
+
return [...own, ...mcp];
|
|
14
20
|
}
|
|
15
21
|
function textOf(value) {
|
|
16
22
|
return typeof value === "string" ? value : (JSON.stringify(value) ?? "");
|
|
@@ -26,13 +32,22 @@ export function secretNamesIn(value, named) {
|
|
|
26
32
|
return [...hits].sort();
|
|
27
33
|
}
|
|
28
34
|
const MAX_MESSAGE_LENGTH = 1000;
|
|
35
|
+
const MCP_SECRET_PREFIX = "mcp:";
|
|
29
36
|
function refusal(names, listed) {
|
|
30
37
|
const shown = names.slice(0, listed).map((n) => JSON.stringify(n));
|
|
31
38
|
const more = names.length - listed;
|
|
32
39
|
const list = shown.join(", ") + (more > 0 ? `, and ${more} more` : "");
|
|
33
40
|
const noun = names.length > 1 ? "secrets" : "secret";
|
|
34
|
-
const
|
|
35
|
-
|
|
41
|
+
const head = `secret_in_value: not stored. The value contains the value of ${noun} ${list} attached to this agent, and secret values are never stored.`;
|
|
42
|
+
// An MCP server's secret (`mcp:<server>/<secret>`) is not readable by the agent, so it gets no secrets.get hint.
|
|
43
|
+
const readable = names.find((n) => !n.startsWith(MCP_SECRET_PREFIX));
|
|
44
|
+
const mcpNote = names.some((n) => n.startsWith(MCP_SECRET_PREFIX))
|
|
45
|
+
? " A name starting with mcp: is an MCP server's secret: it is used only by that server's tools and can't be stored or read."
|
|
46
|
+
: "";
|
|
47
|
+
if (readable === undefined)
|
|
48
|
+
return head + mcpNote;
|
|
49
|
+
const first = JSON.stringify(readable);
|
|
50
|
+
return `${head} Store the secret's name instead and read it when you need it: secrets.get(${first}), or fetch(url, { secrets: [${first}] }) if it is brokered.${mcpNote}`;
|
|
36
51
|
}
|
|
37
52
|
/** The agent-visible refusal: names the secrets, never a value. At most 1000 characters. */
|
|
38
53
|
export function secretInValueMessage(names) {
|
package/dist/core/secrets.d.ts
CHANGED
|
@@ -23,6 +23,15 @@ export interface SecretsAccessor {
|
|
|
23
23
|
/** Host-internal: value and broker config, brokered or not. Never exposed to tool code. */
|
|
24
24
|
resolve?(name: string): Promise<SecretEntry | undefined>;
|
|
25
25
|
}
|
|
26
|
+
/** A webhook secret signs what a host sends us, so a short one is refused at registration and at rotation. */
|
|
27
|
+
export declare const MIN_WEBHOOK_SECRET_LENGTH = 16;
|
|
28
|
+
export declare const WEBHOOK_SECRET_SHORT = "host_provider_webhook_secret_short: a host provider's webhook secret needs at least 16 characters";
|
|
29
|
+
export declare const WEBHOOK_SECRET_ATTACHED = "host_provider_webhook_secret_attached: this secret is a host provider's webhook secret and can't be attached to an agent";
|
|
30
|
+
/** The host providers using a secret, by name, and whether it's the webhook secret of any. */
|
|
31
|
+
export declare function hostProvidersUsingSecret(db: Pick<PrismaClient, "hostProvider" | "hostProviderCredential">, secretId: string): Promise<{
|
|
32
|
+
providers: string[];
|
|
33
|
+
webhook: boolean;
|
|
34
|
+
}>;
|
|
26
35
|
/**
|
|
27
36
|
* Encrypts and stores a secret value. Upserts on (ownerId, name) — calling
|
|
28
37
|
* this again for a name the owner already has rotates its value in place
|
|
@@ -56,7 +65,8 @@ export declare function setSecretBroker(db: PrismaClient, cipher: SecretCipher,
|
|
|
56
65
|
export declare function listSecrets(ownerId: string, db: PrismaClient): Promise<SecretMetadata[]>;
|
|
57
66
|
/** Attaches one of the owner's own secrets (by canonical name) to an agent,
|
|
58
67
|
* under `boundName` — the point-of-use name the agent's tools resolve
|
|
59
|
-
* (`secrets.get(boundName)`). Defaults to the secret's own name.
|
|
68
|
+
* (`secrets.get(boundName)`). Defaults to the secret's own name. A host
|
|
69
|
+
* provider's webhook secret is kept off every agent, so no tool can read it. */
|
|
60
70
|
export declare function attachSecret(agentId: string, name: string, ownerId: string, db: PrismaClient, boundName?: string): Promise<void>;
|
|
61
71
|
/** Detaches by point-of-use name — the pair (agentId, boundName) uniquely
|
|
62
72
|
* identifies the edge — falling back to the secret's own name when no edge
|
package/dist/core/secrets.js
CHANGED
|
@@ -13,6 +13,22 @@ function brokerOf(secret) {
|
|
|
13
13
|
return secret.broker === null || secret.broker === undefined ? null : parseSecretBrokerConfig(secret.broker);
|
|
14
14
|
}
|
|
15
15
|
const BROKERED_GET_MESSAGE = 'secret_brokered: this secret is brokered; send it with fetch(url, { secrets: ["NAME"] }) instead of reading it';
|
|
16
|
+
/** A webhook secret signs what a host sends us, so a short one is refused at registration and at rotation. */
|
|
17
|
+
export const MIN_WEBHOOK_SECRET_LENGTH = 16;
|
|
18
|
+
export const WEBHOOK_SECRET_SHORT = `host_provider_webhook_secret_short: a host provider's webhook secret needs at least ${MIN_WEBHOOK_SECRET_LENGTH} characters`;
|
|
19
|
+
export const WEBHOOK_SECRET_ATTACHED = "host_provider_webhook_secret_attached: this secret is a host provider's webhook secret and can't be attached to an agent";
|
|
20
|
+
/** The host providers using a secret, by name, and whether it's the webhook secret of any. */
|
|
21
|
+
export async function hostProvidersUsingSecret(db, secretId) {
|
|
22
|
+
const credentials = await db.hostProviderCredential.findMany({ where: { secretId }, select: { provider: true } });
|
|
23
|
+
const rows = await db.hostProvider.findMany({
|
|
24
|
+
where: { OR: [{ webhookSecretId: secretId }, { permissionSecretId: secretId }] },
|
|
25
|
+
select: { name: true, webhookSecretId: true },
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
providers: [...new Set([...credentials.map((c) => c.provider), ...rows.map((r) => r.name)])].sort(),
|
|
29
|
+
webhook: rows.some((r) => r.webhookSecretId === secretId),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
16
32
|
/**
|
|
17
33
|
* Encrypts and stores a secret value. Upserts on (ownerId, name) — calling
|
|
18
34
|
* this again for a name the owner already has rotates its value in place
|
|
@@ -23,6 +39,12 @@ const BROKERED_GET_MESSAGE = 'secret_brokered: this secret is brokered; send it
|
|
|
23
39
|
export async function createSecret(name, value, ownerId, cipher, db, options = {}) {
|
|
24
40
|
boundedString(name, 1024);
|
|
25
41
|
boundedString(value, 65_536);
|
|
42
|
+
if (value.length < MIN_WEBHOOK_SECRET_LENGTH) {
|
|
43
|
+
const current = await db.secret.findUnique({ where: { ownerId_name: { ownerId, name } }, select: { id: true } });
|
|
44
|
+
if (current && (await db.hostProvider.count({ where: { webhookSecretId: current.id } })) > 0) {
|
|
45
|
+
throw new Error(WEBHOOK_SECRET_SHORT);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
26
48
|
const broker = options.broker ? parseSecretBrokerConfig(options.broker) : undefined;
|
|
27
49
|
if (broker)
|
|
28
50
|
assertBrokerableValue(broker, value);
|
|
@@ -122,11 +144,15 @@ async function findOwnedSecretByName(db, ownerId, name) {
|
|
|
122
144
|
}
|
|
123
145
|
/** Attaches one of the owner's own secrets (by canonical name) to an agent,
|
|
124
146
|
* under `boundName` — the point-of-use name the agent's tools resolve
|
|
125
|
-
* (`secrets.get(boundName)`). Defaults to the secret's own name.
|
|
147
|
+
* (`secrets.get(boundName)`). Defaults to the secret's own name. A host
|
|
148
|
+
* provider's webhook secret is kept off every agent, so no tool can read it. */
|
|
126
149
|
export async function attachSecret(agentId, name, ownerId, db, boundName = name) {
|
|
127
150
|
const secret = await findOwnedSecretByName(db, ownerId, name);
|
|
128
151
|
if (!secret)
|
|
129
152
|
throw new Error(`No secret named "${name}" owned by this caller.`);
|
|
153
|
+
if ((await db.hostProvider.count({ where: { webhookSecretId: secret.id } })) > 0) {
|
|
154
|
+
throw new Error(WEBHOOK_SECRET_ATTACHED);
|
|
155
|
+
}
|
|
130
156
|
await db.agentSecret.create({ data: { agentId, secretId: secret.id, boundName } });
|
|
131
157
|
}
|
|
132
158
|
/** Detaches by point-of-use name — the pair (agentId, boundName) uniquely
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run first inside each of those transactions (make_owner, attach_mcp_server, grant/revoke). Postgres's Serializable mode takes a predicate (SIREAD)
|
|
3
|
+
* lock on everything a statement reads, and a sequential scan locks the whole relation and checks every
|
|
4
|
+
* row in it for conflicts. On small tables (a fresh deployment, or a test database) the planner picks a
|
|
5
|
+
* sequential scan even for a primary-key lookup, so two admin actions on unrelated agents read each
|
|
6
|
+
* other's rows and abort one another, retry after retry, while either keeps running. The Postgres manual
|
|
7
|
+
* ("Serializable Isolation Level") recommends steering these transactions to index scans; their queries
|
|
8
|
+
* are keyed by indexed agent, server or resource ids, so turning sequential scans off for this
|
|
9
|
+
* transaction only (SET LOCAL) narrows the locks to the rows and index pages actually read. It changes
|
|
10
|
+
* plans, never results: a query with no usable index still scans.
|
|
11
|
+
*/
|
|
12
|
+
export declare function preferIndexScans(tx: {
|
|
13
|
+
$executeRawUnsafe(query: string): Promise<number>;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
export declare function withSerializableRetry<T>(fn: () => Promise<T>, options?: {
|
|
16
|
+
attempts?: number;
|
|
17
|
+
sleep?: (ms: number) => Promise<void>;
|
|
18
|
+
}): Promise<T>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A bounded retry for short Serializable transactions on admin and owner actions (make_owner,
|
|
3
|
+
* attach_mcp_server, grant/revoke that prune attachments). Postgres aborts one side of a conflicting
|
|
4
|
+
* pair; nothing of the aborted attempt was committed, so running it again is safe. Only serialization
|
|
5
|
+
* conflicts (isSerializationConflict: P2034, a raw-statement 40001/40P01, or one at COMMIT) are
|
|
6
|
+
* retried; anything else, and the last attempt's conflict, is rethrown for mapPrismaError (409).
|
|
7
|
+
*/
|
|
8
|
+
import { isSerializationConflict } from "./dispatch.js";
|
|
9
|
+
const BASE_DELAY_MS = 20;
|
|
10
|
+
const MAX_DELAY_MS = 250;
|
|
11
|
+
/** Jittered exponential backoff: half the ceiling plus up to half again. */
|
|
12
|
+
function delayMs(attempt) {
|
|
13
|
+
const ceiling = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * 2 ** attempt);
|
|
14
|
+
return Math.floor(ceiling / 2 + Math.random() * (ceiling / 2));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Run first inside each of those transactions (make_owner, attach_mcp_server, grant/revoke). Postgres's Serializable mode takes a predicate (SIREAD)
|
|
18
|
+
* lock on everything a statement reads, and a sequential scan locks the whole relation and checks every
|
|
19
|
+
* row in it for conflicts. On small tables (a fresh deployment, or a test database) the planner picks a
|
|
20
|
+
* sequential scan even for a primary-key lookup, so two admin actions on unrelated agents read each
|
|
21
|
+
* other's rows and abort one another, retry after retry, while either keeps running. The Postgres manual
|
|
22
|
+
* ("Serializable Isolation Level") recommends steering these transactions to index scans; their queries
|
|
23
|
+
* are keyed by indexed agent, server or resource ids, so turning sequential scans off for this
|
|
24
|
+
* transaction only (SET LOCAL) narrows the locks to the rows and index pages actually read. It changes
|
|
25
|
+
* plans, never results: a query with no usable index still scans.
|
|
26
|
+
*/
|
|
27
|
+
export async function preferIndexScans(tx) {
|
|
28
|
+
await tx.$executeRawUnsafe("SET LOCAL enable_seqscan = off");
|
|
29
|
+
}
|
|
30
|
+
export async function withSerializableRetry(fn, options = {}) {
|
|
31
|
+
const attempts = Math.max(1, options.attempts ?? 3);
|
|
32
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
33
|
+
for (let attempt = 0;; attempt += 1) {
|
|
34
|
+
try {
|
|
35
|
+
return await fn();
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
if (!isSerializationConflict(err) || attempt >= attempts - 1)
|
|
39
|
+
throw err;
|
|
40
|
+
await sleep(delayMs(attempt));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -33,6 +33,8 @@ export interface ToolChanges {
|
|
|
33
33
|
description?: string;
|
|
34
34
|
paramsZod?: string;
|
|
35
35
|
code?: string;
|
|
36
|
+
/** null clears it (back to the default limit). Checked against the ceiling by the caller (toolTimeoutProblem). */
|
|
37
|
+
timeoutMs?: number | null;
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
40
|
* Why a tool change was refused because of its attachments:
|
|
@@ -76,6 +78,8 @@ export declare function deleteToolGuarded(db: Pick<PrismaClient, "$transaction">
|
|
|
76
78
|
authorize: AuthorizeTool;
|
|
77
79
|
}): Promise<string[]>;
|
|
78
80
|
/** One `wardby tool list` line: names are only unique per owner, so the id and owner disambiguate. */
|
|
79
|
-
export declare function formatToolLine(tool: Pick<Tool, "id" | "name" | "description" | "ownerId">
|
|
81
|
+
export declare function formatToolLine(tool: Pick<Tool, "id" | "name" | "description" | "ownerId"> & {
|
|
82
|
+
timeoutMs?: number | null;
|
|
83
|
+
}): string;
|
|
80
84
|
/** Attached agents for an operator-facing refusal: unlike MCP's, nothing is redacted. */
|
|
81
85
|
export declare function formatAttachedAgents(agents: AttachedAgent[]): string;
|
package/dist/core/tool-admin.js
CHANGED
|
@@ -46,7 +46,10 @@ export class ToolAttachedError extends Error {
|
|
|
46
46
|
}
|
|
47
47
|
/** Whether `changes` names at least one field to change. */
|
|
48
48
|
export function hasToolChanges(changes) {
|
|
49
|
-
return changes.description !== undefined ||
|
|
49
|
+
return (changes.description !== undefined ||
|
|
50
|
+
changes.paramsZod !== undefined ||
|
|
51
|
+
changes.code !== undefined ||
|
|
52
|
+
changes.timeoutMs !== undefined);
|
|
50
53
|
}
|
|
51
54
|
/**
|
|
52
55
|
* The row update for `changes`, with jsonSchema re-derived exactly as tool
|
|
@@ -59,6 +62,7 @@ export async function prepareToolUpdate(changes) {
|
|
|
59
62
|
const data = {
|
|
60
63
|
...(changes.description !== undefined ? { description: changes.description } : {}),
|
|
61
64
|
...(changes.code !== undefined ? { code: changes.code } : {}),
|
|
65
|
+
...(changes.timeoutMs !== undefined ? { timeoutMs: changes.timeoutMs } : {}),
|
|
62
66
|
};
|
|
63
67
|
if (changes.paramsZod !== undefined) {
|
|
64
68
|
const schemaResult = await deriveJsonSchema(changes.paramsZod);
|
|
@@ -119,7 +123,8 @@ export async function deleteToolGuarded(db, toolId, options) {
|
|
|
119
123
|
}
|
|
120
124
|
/** One `wardby tool list` line: names are only unique per owner, so the id and owner disambiguate. */
|
|
121
125
|
export function formatToolLine(tool) {
|
|
122
|
-
|
|
126
|
+
const timeout = tool.timeoutMs === null || tool.timeoutMs === undefined ? "" : ` timeoutMs:${tool.timeoutMs}`;
|
|
127
|
+
return `${tool.id} ${tool.name} owner:${tool.ownerId ?? "public"}${timeout} ${tool.description}`;
|
|
123
128
|
}
|
|
124
129
|
/** Attached agents for an operator-facing refusal: unlike MCP's, nothing is redacted. */
|
|
125
130
|
export function formatAttachedAgents(agents) {
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* the model would see a duplicate tool name and one tool would silently
|
|
10
10
|
* shadow the other; every attach path refuses that instead
|
|
11
11
|
* (findSameNamedAttachedTool), and the runner asserts it at load time.
|
|
12
|
+
* The same holds against an attached MCP server's exposed tool names
|
|
13
|
+
* (findSameNamedMcpTool); the runner leaves a clashing MCP tool out.
|
|
12
14
|
*/
|
|
13
15
|
import type { Prisma, PrismaClient, Tool } from "#prisma";
|
|
14
16
|
/** The runner's synthesized sub-agent tools are `delegate_to_<boundName>`. */
|
|
@@ -28,6 +30,17 @@ export type ToolLookupDb = Pick<PrismaClient | Prisma.TransactionClient, "tool"
|
|
|
28
30
|
* is Serializable) so a concurrent attach can't slip in between.
|
|
29
31
|
*/
|
|
30
32
|
export declare function findSameNamedAttachedTool(db: Pick<ToolLookupDb, "agentTool">, agentId: string, tool: Pick<Tool, "id" | "name">): Promise<Pick<Tool, "id" | "name" | "ownerId"> | null>;
|
|
33
|
+
/**
|
|
34
|
+
* An MCP attachment of `agentId` whose exposed tool name (`<server>__<tool>`) equals `toolName`,
|
|
35
|
+
* or null: the other direction of attach_mcp_server's clash check, for every path that attaches a
|
|
36
|
+
* Tool. Every exposed name contains "__", so any other name needs no lookup.
|
|
37
|
+
*/
|
|
38
|
+
export declare function findSameNamedMcpTool(db: Pick<PrismaClient | Prisma.TransactionClient, "agentMcpServer">, agentId: string, toolName: string): Promise<{
|
|
39
|
+
serverId: string;
|
|
40
|
+
serverName: string;
|
|
41
|
+
} | null>;
|
|
42
|
+
/** The refusal for a Tool whose name is an exposed MCP tool name on the agent (findSameNamedMcpTool). */
|
|
43
|
+
export declare function sameNamedMcpToolMessage(agentRef: string, toolName: string, serverName: string): string;
|
|
31
44
|
/**
|
|
32
45
|
* Resolves a CLI tool reference: an id, else a name. A name is no longer
|
|
33
46
|
* unique across owners, so more than one match is refused -- with the
|
package/dist/core/tool-names.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MEMORY_TOOL_NAMES } from "./memory-tools.js";
|
|
2
2
|
import { SUBAGENT_MEMORY_TOOL_NAMES } from "./subagent-memory-tools.js";
|
|
3
3
|
import { ISSUE_TRACKER_TOOL_NAMES } from "./issue-tracker-tools.js";
|
|
4
|
+
import { exposedNames } from "./mcp-servers.js";
|
|
4
5
|
/** The runner's synthesized sub-agent tools are `delegate_to_<boundName>`. */
|
|
5
6
|
export const DELEGATE_TOOL_PREFIX = "delegate_to_";
|
|
6
7
|
/**
|
|
@@ -44,6 +45,25 @@ export async function findSameNamedAttachedTool(db, agentId, tool) {
|
|
|
44
45
|
});
|
|
45
46
|
return clash?.tool ?? null;
|
|
46
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* An MCP attachment of `agentId` whose exposed tool name (`<server>__<tool>`) equals `toolName`,
|
|
50
|
+
* or null: the other direction of attach_mcp_server's clash check, for every path that attaches a
|
|
51
|
+
* Tool. Every exposed name contains "__", so any other name needs no lookup.
|
|
52
|
+
*/
|
|
53
|
+
export async function findSameNamedMcpTool(db, agentId, toolName) {
|
|
54
|
+
if (!toolName.includes("__"))
|
|
55
|
+
return null;
|
|
56
|
+
const rows = await db.agentMcpServer.findMany({
|
|
57
|
+
where: { agentId },
|
|
58
|
+
select: { allowedTools: true, server: { select: { id: true, name: true } } },
|
|
59
|
+
});
|
|
60
|
+
const hit = rows.find((row) => exposedNames(row.server.name, row.allowedTools).includes(toolName));
|
|
61
|
+
return hit ? { serverId: hit.server.id, serverName: hit.server.name } : null;
|
|
62
|
+
}
|
|
63
|
+
/** The refusal for a Tool whose name is an exposed MCP tool name on the agent (findSameNamedMcpTool). */
|
|
64
|
+
export function sameNamedMcpToolMessage(agentRef, toolName, serverName) {
|
|
65
|
+
return `Agent "${agentRef}" already has the tool "${toolName}" from MCP server "${serverName}"; detach it or allow a different tool first.`;
|
|
66
|
+
}
|
|
47
67
|
/**
|
|
48
68
|
* Resolves a CLI tool reference: an id, else a name. A name is no longer
|
|
49
69
|
* unique across owners, so more than one match is refused -- with the
|
package/dist/core/webhooks.d.ts
CHANGED
|
@@ -22,6 +22,9 @@ export type ResolveWebhookRunResult = {
|
|
|
22
22
|
} | {
|
|
23
23
|
ok: false;
|
|
24
24
|
reason: "not_found" | "invalid_secret" | "disabled";
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
reason: "invalid_repository";
|
|
25
28
|
} | {
|
|
26
29
|
ok: false;
|
|
27
30
|
reason: "invalid_issue";
|
|
@@ -35,4 +38,5 @@ export type ResolveWebhookRunResult = {
|
|
|
35
38
|
export declare function resolveWebhookRun(id: string, presentedSecret: string, db: PrismaClient, executor: Executor, codingTask?: string, opts?: {
|
|
36
39
|
issue?: unknown;
|
|
37
40
|
issueTrackers?: IssueTrackerRegistry;
|
|
41
|
+
repository?: string;
|
|
38
42
|
}): Promise<ResolveWebhookRunResult>;
|
package/dist/core/webhooks.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
12
12
|
import { AttributionError, explicitAttribution, RESPONSE_PATH_SNAPSHOT_BUDGET, } from "./attribution.js";
|
|
13
|
+
import { RepositoryChoiceRefusedError } from "./coding-repository.js";
|
|
13
14
|
import { dispatchRun } from "./dispatch.js";
|
|
14
15
|
import { atLeast, effectiveAccess } from "./grants.js";
|
|
15
16
|
function hashSecret(secret) {
|
|
@@ -81,6 +82,13 @@ export async function resolveWebhookRun(id, presentedSecret, db, executor, codin
|
|
|
81
82
|
if (codingTask !== undefined && !taskAllowed(agent.kind, agent.codingProfile)) {
|
|
82
83
|
return { ok: false, reason: "disabled" };
|
|
83
84
|
}
|
|
85
|
+
// Choosing a repository takes the same opt-in as a task, and only a coding agent has one.
|
|
86
|
+
if (opts.repository !== undefined) {
|
|
87
|
+
if (agent.kind !== "coding")
|
|
88
|
+
return { ok: false, reason: "invalid_repository" };
|
|
89
|
+
if (!taskAllowed(agent.kind, agent.codingProfile))
|
|
90
|
+
return { ok: false, reason: "disabled" };
|
|
91
|
+
}
|
|
84
92
|
// An invalid or unlinked issue refuses the call before any run exists.
|
|
85
93
|
let attribution;
|
|
86
94
|
if (opts.issue !== undefined) {
|
|
@@ -97,55 +105,66 @@ export async function resolveWebhookRun(id, presentedSecret, db, executor, codin
|
|
|
97
105
|
}
|
|
98
106
|
}
|
|
99
107
|
let rejected = "disabled";
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
108
|
+
let dispatched;
|
|
109
|
+
try {
|
|
110
|
+
dispatched = await dispatchRun({
|
|
111
|
+
db,
|
|
112
|
+
executor,
|
|
113
|
+
selfDefects: { db, issueTrackers: opts.issueTrackers },
|
|
114
|
+
agentId: agent.id,
|
|
115
|
+
trigger: "webhook",
|
|
116
|
+
attribution,
|
|
117
|
+
// Coding agents keep the existing per-agent opt-in (allowWebhookTaskOverride);
|
|
118
|
+
// a native agent's system prompt has no per-run input at all otherwise, so
|
|
119
|
+
// accepting task text over an authenticated webhook call needs no separate
|
|
120
|
+
// opt-in — it's a much lower-risk capability than a coding agent being told
|
|
121
|
+
// to modify a real repository.
|
|
122
|
+
codingTask: agent.kind === "coding" ? codingTask : undefined,
|
|
123
|
+
taskOverride: agent.kind === "native" ? codingTask : undefined,
|
|
124
|
+
codingRepository: agent.kind === "coding" ? opts.repository : undefined,
|
|
125
|
+
// The webhook is its creator's standing trigger: its runs are visible to
|
|
126
|
+
// the creator (and the agent owner), like a trigger_agent call.
|
|
127
|
+
triggeredById: webhook.ownerId,
|
|
128
|
+
beforePersist: async (tx, currentAgent) => {
|
|
129
|
+
const current = await tx.webhook.findUnique({ where: { id } });
|
|
130
|
+
if (!current) {
|
|
131
|
+
rejected = "not_found";
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
if (!secretMatches(presentedSecret, current.secretHash)) {
|
|
135
|
+
rejected = "invalid_secret";
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (current.status !== "enabled") {
|
|
139
|
+
rejected = "disabled";
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
if ((codingTask !== undefined || opts.repository !== undefined) &&
|
|
143
|
+
!taskAllowed(currentAgent.kind, currentAgent.codingProfile)) {
|
|
144
|
+
rejected = "disabled";
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
// The creator must still be allowed to trigger the agent (resource-
|
|
148
|
+
// sharing grants spec §3.10): its owner, or a holder of execute. Read
|
|
149
|
+
// through tx, so a revoke or make_owner takes effect on the next fire.
|
|
150
|
+
// A webhook on a formerly public agent keeps firing through the
|
|
151
|
+
// migration's everyone-execute grant.
|
|
152
|
+
const creatorAccess = await effectiveAccess(tx, "agent", currentAgent, current.ownerId);
|
|
153
|
+
if (!atLeast("agent", creatorAccess, "execute")) {
|
|
154
|
+
rejected = "disabled";
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
await tx.webhook.update({ where: { id }, data: { lastFiredAt: new Date() } });
|
|
158
|
+
return true;
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
// The refusal's message lists the agent's repositories; the caller gets only the code.
|
|
164
|
+
if (err instanceof RepositoryChoiceRefusedError)
|
|
165
|
+
return { ok: false, reason: "invalid_repository" };
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
149
168
|
if (!dispatched)
|
|
150
169
|
return { ok: false, reason: rejected };
|
|
151
170
|
return { ok: true, runId: dispatched.run.id };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pullRequestWebUrl } from "../coding/code-host.js";
|
|
1
2
|
import { packageAuditDigest } from "../coding/registry/audit-mode.js";
|
|
2
3
|
import { blockingRefusals, collectPackageAudit, collectRunOutcome, isBlockedByRefusals } from "./host-status.js";
|
|
3
4
|
import { logger } from "./logger.js";
|
|
@@ -63,7 +64,7 @@ export async function emitRunFinishedEvents(db, run) {
|
|
|
63
64
|
agentName,
|
|
64
65
|
verdict: check.verdict,
|
|
65
66
|
prLabel,
|
|
66
|
-
prUrl: check.provider
|
|
67
|
+
prUrl: pullRequestWebUrl(check.provider, check.repository, check.prNumber),
|
|
67
68
|
ciPending: check.ciPendingAtReview === true,
|
|
68
69
|
},
|
|
69
70
|
});
|
|
@@ -421,6 +421,16 @@ export type HostIdentityLinkRequest = Prisma.HostIdentityLinkRequestModel;
|
|
|
421
421
|
* docs/private/2026-09-30-jira-issue-tracker-design.md §3.
|
|
422
422
|
*/
|
|
423
423
|
export type AgentIssueProject = Prisma.AgentIssueProjectModel;
|
|
424
|
+
/**
|
|
425
|
+
* Model McpServer
|
|
426
|
+
* A remote MCP server an owner registered (Streamable HTTP, HTTPS only).
|
|
427
|
+
*/
|
|
428
|
+
export type McpServer = Prisma.McpServerModel;
|
|
429
|
+
/**
|
|
430
|
+
* Model AgentMcpServer
|
|
431
|
+
* An MCP server attached to an agent, with the server tools it may call.
|
|
432
|
+
*/
|
|
433
|
+
export type AgentMcpServer = Prisma.AgentMcpServerModel;
|
|
424
434
|
/**
|
|
425
435
|
* Model IssueFingerprint
|
|
426
436
|
* Dedupe record: an issue wardby created for a fingerprint (several over time — regressions).
|
|
@@ -504,3 +514,24 @@ export type NotificationDelivery = Prisma.NotificationDeliveryModel;
|
|
|
504
514
|
* The parent message of a work item's (or PR's) thread in one channel.
|
|
505
515
|
*/
|
|
506
516
|
export type NotificationThread = Prisma.NotificationThreadModel;
|
|
517
|
+
/**
|
|
518
|
+
* Model HostProvider
|
|
519
|
+
* A code host implemented by operator-approved custom tools (docs/host-providers.md).
|
|
520
|
+
* `name` is the prefix of its repository ids: `<name>:<workspace>/<repo>`. Holds no secret values.
|
|
521
|
+
*/
|
|
522
|
+
export type HostProvider = Prisma.HostProviderModel;
|
|
523
|
+
/**
|
|
524
|
+
* Model HostProviderOperation
|
|
525
|
+
* One approved snapshot: the code Wardby runs. `update_tool` changes nothing here until approved again.
|
|
526
|
+
*/
|
|
527
|
+
export type HostProviderOperation = Prisma.HostProviderOperationModel;
|
|
528
|
+
/**
|
|
529
|
+
* Model HostProviderCredential
|
|
530
|
+
* Which brokered secret serves which repositories. `credentialKind` is `static` only in v1.
|
|
531
|
+
*/
|
|
532
|
+
export type HostProviderCredential = Prisma.HostProviderCredentialModel;
|
|
533
|
+
/**
|
|
534
|
+
* Model HostProviderTrustedAccount
|
|
535
|
+
* Host accounts allowed to start runs when the provider has no permission secret.
|
|
536
|
+
*/
|
|
537
|
+
export type HostProviderTrustedAccount = Prisma.HostProviderTrustedAccountModel;
|
|
@@ -440,6 +440,16 @@ export type HostIdentityLinkRequest = Prisma.HostIdentityLinkRequestModel;
|
|
|
440
440
|
* docs/private/2026-09-30-jira-issue-tracker-design.md §3.
|
|
441
441
|
*/
|
|
442
442
|
export type AgentIssueProject = Prisma.AgentIssueProjectModel;
|
|
443
|
+
/**
|
|
444
|
+
* Model McpServer
|
|
445
|
+
* A remote MCP server an owner registered (Streamable HTTP, HTTPS only).
|
|
446
|
+
*/
|
|
447
|
+
export type McpServer = Prisma.McpServerModel;
|
|
448
|
+
/**
|
|
449
|
+
* Model AgentMcpServer
|
|
450
|
+
* An MCP server attached to an agent, with the server tools it may call.
|
|
451
|
+
*/
|
|
452
|
+
export type AgentMcpServer = Prisma.AgentMcpServerModel;
|
|
443
453
|
/**
|
|
444
454
|
* Model IssueFingerprint
|
|
445
455
|
* Dedupe record: an issue wardby created for a fingerprint (several over time — regressions).
|
|
@@ -523,3 +533,24 @@ export type NotificationDelivery = Prisma.NotificationDeliveryModel;
|
|
|
523
533
|
* The parent message of a work item's (or PR's) thread in one channel.
|
|
524
534
|
*/
|
|
525
535
|
export type NotificationThread = Prisma.NotificationThreadModel;
|
|
536
|
+
/**
|
|
537
|
+
* Model HostProvider
|
|
538
|
+
* A code host implemented by operator-approved custom tools (docs/host-providers.md).
|
|
539
|
+
* `name` is the prefix of its repository ids: `<name>:<workspace>/<repo>`. Holds no secret values.
|
|
540
|
+
*/
|
|
541
|
+
export type HostProvider = Prisma.HostProviderModel;
|
|
542
|
+
/**
|
|
543
|
+
* Model HostProviderOperation
|
|
544
|
+
* One approved snapshot: the code Wardby runs. `update_tool` changes nothing here until approved again.
|
|
545
|
+
*/
|
|
546
|
+
export type HostProviderOperation = Prisma.HostProviderOperationModel;
|
|
547
|
+
/**
|
|
548
|
+
* Model HostProviderCredential
|
|
549
|
+
* Which brokered secret serves which repositories. `credentialKind` is `static` only in v1.
|
|
550
|
+
*/
|
|
551
|
+
export type HostProviderCredential = Prisma.HostProviderCredentialModel;
|
|
552
|
+
/**
|
|
553
|
+
* Model HostProviderTrustedAccount
|
|
554
|
+
* Host accounts allowed to start runs when the provider has no permission secret.
|
|
555
|
+
*/
|
|
556
|
+
export type HostProviderTrustedAccount = Prisma.HostProviderTrustedAccountModel;
|