@wardby/cli 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +5 -5
- package/README.md +6 -1
- package/deploy/local/docker-compose.quickstart-coding.yml +11 -0
- package/dist/claude-coding-worker/driver.d.ts +12 -0
- package/dist/claude-coding-worker/driver.js +29 -7
- package/dist/claude-coding-worker/main.js +5 -1
- package/dist/claude-coding-worker/sdk.js +17 -1
- package/dist/claude-plugin/command.d.ts +64 -0
- package/dist/claude-plugin/command.js +379 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +2 -1
- package/dist/cli.js +71 -3
- package/dist/coding/code-host.d.ts +16 -0
- package/dist/coding/code-host.js +60 -0
- package/dist/coding/mcp-token.d.ts +12 -0
- package/dist/coding/mcp-token.js +26 -0
- package/dist/coding/protocol.js +2 -0
- package/dist/coding/redact.js +6 -1
- package/dist/coding/transcript/recorder.d.ts +5 -4
- package/dist/coding/transcript/recorder.js +3 -9
- package/dist/coding-worker/debug-trace.d.ts +3 -1
- package/dist/coding-worker/debug-trace.js +4 -2
- package/dist/coding-worker/driver.js +8 -1
- package/dist/coding-worker/main.js +5 -1
- package/dist/coding-worker/mcp-config.d.ts +27 -0
- package/dist/coding-worker/mcp-config.js +32 -0
- package/dist/coding-worker/sdk.d.ts +36 -0
- package/dist/coding-worker/sdk.js +28 -1
- package/dist/coding-worker/types.d.ts +4 -0
- package/dist/config/mcp-call-limit.d.ts +8 -0
- package/dist/config/mcp-call-limit.js +18 -0
- package/dist/config/mcp-servers.d.ts +8 -0
- package/dist/config/mcp-servers.js +12 -0
- package/dist/config/test-simulators.d.ts +10 -0
- package/dist/config/test-simulators.js +28 -0
- package/dist/config/tool-timeout.d.ts +1 -0
- package/dist/config/tool-timeout.js +18 -0
- package/dist/core/coding-mcp.d.ts +36 -0
- package/dist/core/coding-mcp.js +253 -0
- package/dist/core/coding-repository.d.ts +46 -0
- package/dist/core/coding-repository.js +92 -0
- package/dist/core/delegation-slots.d.ts +27 -0
- package/dist/core/delegation-slots.js +44 -0
- package/dist/core/dispatch.d.ts +6 -2
- package/dist/core/dispatch.js +39 -11
- package/dist/core/engine-native.d.ts +2 -0
- package/dist/core/engine-native.js +13 -0
- package/dist/core/error-code.js +2 -0
- package/dist/core/grants.d.ts +28 -3
- package/dist/core/grants.js +46 -4
- package/dist/core/host-events.js +7 -3
- package/dist/core/host-status.js +2 -2
- package/dist/core/issue-status.js +4 -3
- package/dist/core/listen-address.d.ts +10 -0
- package/dist/core/listen-address.js +14 -0
- package/dist/core/mark-ready.d.ts +1 -1
- package/dist/core/mark-ready.js +1 -1
- package/dist/core/mcp-client/client.d.ts +47 -0
- package/dist/core/mcp-client/client.js +141 -0
- package/dist/core/mcp-client/jsonrpc.d.ts +15 -0
- package/dist/core/mcp-client/jsonrpc.js +54 -0
- package/dist/core/mcp-client/results.d.ts +13 -0
- package/dist/core/mcp-client/results.js +42 -0
- package/dist/core/mcp-servers.d.ts +90 -0
- package/dist/core/mcp-servers.js +230 -0
- package/dist/core/mcp-tools.d.ts +108 -0
- package/dist/core/mcp-tools.js +287 -0
- package/dist/core/merge-order-check.js +38 -19
- package/dist/core/notification-dispatcher.js +2 -1
- package/dist/core/power-idle.d.ts +8 -2
- package/dist/core/power-idle.js +17 -3
- package/dist/core/power.d.ts +5 -0
- package/dist/core/power.js +5 -0
- package/dist/core/related-pull-requests.js +26 -10
- package/dist/core/retention-days.d.ts +5 -0
- package/dist/core/retention-days.js +14 -0
- package/dist/core/review-fix.js +2 -2
- package/dist/core/review-host-tools.js +3 -1
- package/dist/core/run-steps/config.d.ts +25 -0
- package/dist/core/run-steps/config.js +40 -0
- package/dist/core/run-steps/control-chars.d.ts +16 -0
- package/dist/core/run-steps/control-chars.js +26 -0
- package/dist/core/run-steps/detail.d.ts +32 -0
- package/dist/core/run-steps/detail.js +240 -0
- package/dist/core/run-steps/extract.d.ts +52 -0
- package/dist/core/run-steps/extract.js +467 -0
- package/dist/core/run-steps/ingress.d.ts +51 -0
- package/dist/core/run-steps/ingress.js +160 -0
- package/dist/core/run-steps/last-step.d.ts +15 -0
- package/dist/core/run-steps/last-step.js +35 -0
- package/dist/core/run-steps/native.d.ts +48 -0
- package/dist/core/run-steps/native.js +164 -0
- package/dist/core/run-steps/seen.d.ts +16 -0
- package/dist/core/run-steps/seen.js +35 -0
- package/dist/core/run-steps/summarize.d.ts +57 -0
- package/dist/core/run-steps/summarize.js +304 -0
- package/dist/core/run-steps/sweep.d.ts +21 -0
- package/dist/core/run-steps/sweep.js +41 -0
- package/dist/core/run-steps/types.d.ts +89 -0
- package/dist/core/run-steps/types.js +39 -0
- package/dist/core/run-steps/wire.d.ts +35 -0
- package/dist/core/run-steps/wire.js +47 -0
- package/dist/core/run-steps/writer.d.ts +49 -0
- package/dist/core/run-steps/writer.js +279 -0
- package/dist/core/runner.d.ts +70 -16
- package/dist/core/runner.js +296 -74
- package/dist/core/secret-broker-config.d.ts +2 -0
- package/dist/core/secret-broker-config.js +1 -1
- package/dist/core/secret-guard.d.ts +5 -2
- package/dist/core/secret-guard.js +22 -7
- package/dist/core/secrets.d.ts +11 -1
- package/dist/core/secrets.js +27 -1
- package/dist/core/serializable-retry.d.ts +18 -0
- package/dist/core/serializable-retry.js +43 -0
- package/dist/core/tool-admin.d.ts +5 -1
- package/dist/core/tool-admin.js +7 -2
- package/dist/core/tool-names.d.ts +13 -0
- package/dist/core/tool-names.js +20 -0
- package/dist/core/webhooks.d.ts +4 -0
- package/dist/core/webhooks.js +68 -49
- package/dist/core/workflow-run-events.js +2 -1
- package/dist/generated/prisma/browser.d.ts +39 -0
- package/dist/generated/prisma/client.d.ts +39 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +80 -50
- package/dist/generated/prisma/enums.d.ts +6 -0
- package/dist/generated/prisma/enums.js +5 -0
- package/dist/generated/prisma/internal/class.d.ts +77 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +632 -1
- package/dist/generated/prisma/internal/prismaNamespace.js +94 -3
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +98 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +94 -3
- package/dist/generated/prisma/models/Agent.d.ts +372 -1
- package/dist/generated/prisma/models/AgentMcpServer.d.ts +1412 -0
- package/dist/generated/prisma/models/AgentMcpServer.js +1 -0
- package/dist/generated/prisma/models/AgentRepository.d.ts +40 -1
- package/dist/generated/prisma/models/AuthUser.d.ts +1 -1
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +0 -7
- package/dist/generated/prisma/models/CodingProxySession.d.ts +159 -2
- package/dist/generated/prisma/models/CodingRun.d.ts +76 -1
- package/dist/generated/prisma/models/HostProvider.d.ts +2176 -0
- package/dist/generated/prisma/models/HostProvider.js +1 -0
- package/dist/generated/prisma/models/HostProviderCredential.d.ts +1285 -0
- package/dist/generated/prisma/models/HostProviderCredential.js +1 -0
- package/dist/generated/prisma/models/HostProviderOperation.d.ts +1470 -0
- package/dist/generated/prisma/models/HostProviderOperation.js +1 -0
- package/dist/generated/prisma/models/HostProviderTrustedAccount.d.ts +1145 -0
- package/dist/generated/prisma/models/HostProviderTrustedAccount.js +1 -0
- package/dist/generated/prisma/models/McpServer.d.ts +1500 -0
- package/dist/generated/prisma/models/McpServer.js +1 -0
- package/dist/generated/prisma/models/NativeGatewayCall.d.ts +39 -1
- package/dist/generated/prisma/models/Principal.d.ts +167 -0
- package/dist/generated/prisma/models/ResourceGrant.d.ts +1 -1
- package/dist/generated/prisma/models/Run.d.ts +503 -1
- package/dist/generated/prisma/models/RunStep.d.ts +1453 -0
- package/dist/generated/prisma/models/RunStep.js +1 -0
- package/dist/generated/prisma/models/Secret.d.ts +387 -0
- package/dist/generated/prisma/models/Tool.d.ts +223 -1
- package/dist/generated/prisma/models.d.ts +7 -0
- package/dist/help/search.js +12 -3
- package/dist/help-index.json +812 -52
- package/dist/import/create.js +16 -1
- package/dist/import/neutral-schema.d.ts +1 -0
- package/dist/import/neutral-schema.js +2 -0
- package/dist/mcp/auth/access.d.ts +1 -0
- package/dist/mcp/auth/grants-cli.d.ts +6 -4
- package/dist/mcp/auth/grants-cli.js +42 -3
- package/dist/mcp/auth/resource-server.d.ts +3 -1
- package/dist/mcp/auth/resource-server.js +17 -2
- package/dist/mcp/context.d.ts +6 -0
- package/dist/mcp/index.js +21 -0
- package/dist/mcp/tools/agents.js +135 -20
- package/dist/mcp/tools/grants.js +66 -28
- package/dist/mcp/tools/host-providers.d.ts +2 -0
- package/dist/mcp/tools/host-providers.js +215 -0
- package/dist/mcp/tools/mcp-servers.d.ts +26 -0
- package/dist/mcp/tools/mcp-servers.js +481 -0
- package/dist/mcp/tools/power.js +2 -2
- package/dist/mcp/tools/repositories.js +50 -10
- package/dist/mcp/tools/run-access.d.ts +7 -0
- package/dist/mcp/tools/run-access.js +14 -0
- package/dist/mcp/tools/run-steps.d.ts +16 -0
- package/dist/mcp/tools/run-steps.js +85 -0
- package/dist/mcp/tools/runs.js +186 -18
- package/dist/mcp/tools/secret-elicitation.d.ts +2 -0
- package/dist/mcp/tools/secret-elicitation.js +25 -2
- package/dist/mcp/tools/secrets.js +38 -4
- package/dist/mcp/tools/tools.d.ts +6 -4
- package/dist/mcp/tools/tools.js +40 -8
- package/dist/mcp/tools/trigger.js +28 -6
- package/dist/mcp/transport/http-limits.d.ts +2 -0
- package/dist/mcp/transport/http-limits.js +6 -2
- package/dist/mcp/webhooks/ingress.js +10 -0
- package/dist/native-worker/gateway-server.d.ts +18 -1
- package/dist/native-worker/gateway-server.js +38 -9
- package/dist/native-worker/gateway.d.ts +5 -0
- package/dist/native-worker/gateway.js +13 -1
- package/dist/native-worker/http-server.d.ts +6 -4
- package/dist/native-worker/http-server.js +53 -8
- package/dist/native-worker/ledger.d.ts +6 -2
- package/dist/native-worker/ledger.js +5 -4
- package/dist/native-worker/protocol.d.ts +1 -0
- package/dist/native-worker/protocol.js +4 -1
- package/dist/providers/coding-proxy/capability-hash.d.ts +2 -0
- package/dist/providers/coding-proxy/capability-hash.js +5 -0
- package/dist/providers/coding-proxy/mcp-route.d.ts +34 -0
- package/dist/providers/coding-proxy/mcp-route.js +166 -0
- package/dist/providers/coding-proxy/memory-ledger.d.ts +2 -0
- package/dist/providers/coding-proxy/memory-ledger.js +16 -0
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +1 -0
- package/dist/providers/coding-proxy/prisma-ledger.js +36 -2
- package/dist/providers/coding-proxy/proxy.d.ts +33 -1
- package/dist/providers/coding-proxy/proxy.js +266 -75
- package/dist/providers/coding-proxy/run-step-forwarder.d.ts +42 -0
- package/dist/providers/coding-proxy/run-step-forwarder.js +206 -0
- package/dist/providers/coding-proxy/runtime.d.ts +13 -0
- package/dist/providers/coding-proxy/runtime.js +87 -4
- package/dist/providers/coding-proxy/server.d.ts +3 -0
- package/dist/providers/coding-proxy/server.js +10 -0
- package/dist/providers/coding-proxy/types.d.ts +18 -0
- package/dist/providers/engine/types.d.ts +26 -0
- package/dist/providers/executor/composition.js +5 -0
- package/dist/providers/executor/container.d.ts +47 -1
- package/dist/providers/executor/container.js +84 -8
- package/dist/providers/host-tools/invoke.d.ts +37 -0
- package/dist/providers/host-tools/invoke.js +117 -0
- package/dist/providers/host-tools/operations.d.ts +65 -0
- package/dist/providers/host-tools/operations.js +149 -0
- package/dist/providers/host-tools/registry.d.ts +79 -0
- package/dist/providers/host-tools/registry.js +362 -0
- package/dist/providers/host-tools/request-guard.d.ts +17 -0
- package/dist/providers/host-tools/request-guard.js +99 -0
- package/dist/providers/host-tools/self-test.d.ts +64 -0
- package/dist/providers/host-tools/self-test.js +142 -0
- package/dist/providers/jobs/docker-isolation.d.ts +2 -2
- package/dist/providers/jobs/docker-isolation.js +30 -8
- package/dist/providers/jobs/docker.d.ts +8 -0
- package/dist/providers/jobs/docker.js +33 -9
- package/dist/providers/jobs/kubernetes-isolation.d.ts +3 -1
- package/dist/providers/jobs/kubernetes-isolation.js +15 -2
- package/dist/providers/jobs/kubernetes.d.ts +13 -0
- package/dist/providers/jobs/kubernetes.js +28 -5
- package/dist/providers/jobs/types.d.ts +6 -0
- package/dist/providers/review-host/types.d.ts +6 -4
- package/dist/providers/review-host/types.js +1 -1
- package/dist/providers/vcs/git.d.ts +6 -2
- package/dist/providers/vcs/git.js +15 -4
- package/dist/providers/vcs/github.d.ts +2 -71
- package/dist/providers/vcs/github.js +8 -11
- package/dist/providers/vcs/index.d.ts +1 -1
- package/dist/providers/vcs/index.js +1 -1
- package/dist/providers/vcs/remote.d.ts +4 -0
- package/dist/providers/vcs/remote.js +7 -1
- package/dist/providers/vcs/routing.d.ts +12 -6
- package/dist/providers/vcs/routing.js +21 -6
- package/dist/providers/vcs/types.d.ts +71 -3
- package/dist/quickstart/index.d.ts +5 -0
- package/dist/quickstart/index.js +21 -0
- package/dist/quickstart-images.json +1 -1
- package/dist/sandbox/eval-core.d.ts +12 -2
- package/dist/sandbox/eval-core.js +20 -4
- package/dist/sandbox/host-functions.d.ts +4 -0
- package/dist/sandbox/host-functions.js +7 -2
- package/dist/sandbox/limits.d.ts +4 -1
- package/dist/sandbox/limits.js +4 -1
- package/dist/sandbox/run-in-sandbox.d.ts +6 -0
- package/dist/sandbox/run-in-sandbox.js +14 -2
- package/dist/sandbox/safe-fetch.d.ts +2 -0
- package/dist/sandbox/safe-fetch.js +1 -1
- package/dist/sandbox/secret-broker.d.ts +1 -0
- package/dist/sandbox/secret-broker.js +1 -1
- package/dist/sandbox/tool-timeout.d.ts +32 -0
- package/dist/sandbox/tool-timeout.js +52 -0
- package/dist/sandbox/user-tool.d.ts +8 -1
- package/dist/sandbox/user-tool.js +9 -3
- package/dist/serve.js +26 -18
- package/dist/viewer/api-schema.d.ts +66 -0
- package/dist/viewer/api-schema.js +31 -0
- package/dist/viewer/graph.js +2 -1
- package/dist/viewer/openapi.js +2 -1
- package/dist/viewer/run-detail.d.ts +2 -0
- package/dist/viewer/run-detail.js +26 -1
- package/dist/wardby-bin.js +5 -0
- package/docs/README.md +11 -1
- package/docs/agent-recipes.md +37 -4
- package/docs/architecture-runtime.md +1 -1
- package/docs/claude-code-plugin.md +208 -0
- package/docs/code-review-agents.md +6 -0
- package/docs/coding-agent-setup.md +94 -1
- package/docs/coding-worker-byo-images.md +4 -2
- package/docs/coding-worker-isolation.md +94 -7
- package/docs/debugging-agent-runs.md +354 -0
- package/docs/getting-started-gke.md +33 -11
- package/docs/getting-started-identity-provider.md +43 -37
- package/docs/getting-started.md +8 -0
- package/docs/host-providers.md +294 -0
- package/docs/mcp-servers.md +263 -0
- package/docs/native-sandbox.md +31 -9
- package/docs/security-deployment.md +161 -31
- package/docs/tool-secrets.md +3 -0
- package/docs/viewer-api.md +27 -12
- package/docs/viewer-api.openapi.yml +78 -2
- package/help/admin-viewer.md +5 -0
- package/help/agent-recipes.md +33 -2
- package/help/brokered-secrets.md +3 -0
- package/help/build-claude-code-image.md +1 -1
- package/help/claude-code-run-pane.md +63 -0
- package/help/deploy-gke.md +5 -0
- package/help/errors/coding-mcp-gateway-unavailable.md +46 -0
- package/help/errors/coding-mcp-pins-unavailable.md +35 -0
- package/help/errors/database-auth-invalid.md +1 -1
- package/help/errors/host-provider-error.md +40 -0
- package/help/errors/host-tool-failed.md +36 -0
- package/help/errors/mcp-call-limit.md +22 -0
- package/help/errors/mcp-call-timeout.md +22 -0
- package/help/errors/mcp-server-auth-failed.md +22 -0
- package/help/errors/mcp-server-credentials-unavailable.md +45 -0
- package/help/errors/mcp-server-error.md +28 -0
- package/help/errors/mcp-server-unreachable.md +22 -0
- package/help/errors/mcp-servers-off.md +21 -0
- package/help/errors/mcp-tool-not-allowed.md +26 -0
- package/help/errors/mcp-tool-removed.md +22 -0
- package/help/errors/mcp-unavailable.md +25 -0
- package/help/errors/native-sandbox-capacity.md +1 -1
- package/help/errors/native-sandbox-deadline-exceeded.md +1 -1
- package/help/errors/native-sandbox-docker-failed.md +1 -1
- package/help/errors/native-sandbox-gateway-error.md +1 -1
- package/help/errors/native-sandbox-gateway-unavailable.md +1 -1
- package/help/errors/native-sandbox-image-not-pinned.md +1 -1
- package/help/errors/native-sandbox-isolation-mismatch.md +1 -1
- package/help/errors/native-sandbox-network-unenforced.md +1 -1
- package/help/errors/native-sandbox-requires-catalog.md +1 -1
- package/help/errors/native-sandbox-unavailable.md +1 -1
- package/help/errors/native-sandbox-warm-delivery-failed.md +1 -1
- package/help/errors/native-sandbox-worker-exited.md +1 -1
- package/help/errors/native-sandbox-worker-lost.md +1 -1
- package/help/errors/native-sandbox-worker-unready.md +1 -1
- package/help/errors/repository-not-listed.md +36 -0
- package/help/errors/repository-required.md +36 -0
- package/help/errors/tool-timeout.md +38 -0
- package/help/host-providers.md +40 -0
- package/help/identity-and-access.md +4 -1
- package/help/low-power-mode.md +19 -8
- package/help/mcp-servers.md +73 -0
- package/help/mcp.md +9 -2
- package/help/native-capabilities.md +27 -1
- package/help/native-sandbox.md +1 -1
- package/help/operating-agents.md +16 -0
- package/help/related-pull-requests.md +7 -3
- package/help/repo-instructions-and-skills.md +3 -1
- package/help/resume-runs-locally.md +7 -0
- package/help/run-steps.md +140 -0
- package/help/stop-run.md +1 -1
- package/help/troubleshooting/coding-workers.md +9 -1
- package/help/troubleshooting/debugging-agent-runs.md +29 -0
- package/package.json +2 -1
- package/prisma/migrations/20261102000000_host_providers/migration.sql +56 -0
- package/prisma/migrations/20261104000000_coding_agent_repository_list/migration.sql +5 -0
- package/prisma/migrations/20261106000000_tool_timeout/migration.sql +4 -0
- package/prisma/migrations/20261107000000_mcp_servers/migration.sql +33 -0
- package/prisma/migrations/20261108000000_coding_mcp/migration.sql +5 -0
- package/prisma/migrations/20261109000000_task_run_index/migration.sql +3 -0
- package/prisma/migrations/20261110000000_run_steps/migration.sql +26 -0
- package/prisma/migrations/20261111000000_run_steps_viewer_notify/migration.sql +59 -0
- package/prisma/migrations/20261112000000_run_steps_detail/migration.sql +9 -0
- package/prisma/schema.prisma +211 -14
- package/scripts/git-askpass.sh +1 -1
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns a tool call or a model message into the text a step's summary is cut from, and cuts it.
|
|
3
|
+
* Redaction runs on the control plane (stepSummarizer), on the raw step text, before anything is
|
|
4
|
+
* stored, for native and coding runs alike: whitespace is collapsed, then one pass of the shared
|
|
5
|
+
* redactor masks every form of every secret value together (secretForms) and any secret start the
|
|
6
|
+
* text ends in, then token shapes. The summary is cut only after redaction, so a cut never
|
|
7
|
+
* splits a secret and leaves half of it visible.
|
|
8
|
+
*/
|
|
9
|
+
import { CODING_PROTOCOL_VERSION } from "../../coding/protocol.js";
|
|
10
|
+
import { REDACTED, redactSecrets } from "../../coding/redact.js";
|
|
11
|
+
import { CLAUDE_MCP_TOOL_PREFIX, WORKER_MCP_SERVER_KEY } from "../../coding/mcp-token.js";
|
|
12
|
+
import { MIN_REDACTABLE_SECRET_LENGTH, withEscapedForms } from "../secret-spans.js";
|
|
13
|
+
import { MAX_STEP_TEXT_CHARS, SAID_SUMMARY_CHARS, STEP_TOOL_NAME, TOOL_SUMMARY_CHARS, } from "./types.js";
|
|
14
|
+
/** Claude Code's one shell tool through the coding proxy (src/providers/coding-proxy/proxy.ts). */
|
|
15
|
+
export const CLAUDE_COMMAND_TOOL = "mcp__wardby_tools__run_command";
|
|
16
|
+
/** The namespace Codex gives the worker's MCP tools. */
|
|
17
|
+
const CODEX_MCP_NAMESPACE = `mcp__${WORKER_MCP_SERVER_KEY}`;
|
|
18
|
+
/** Arguments longer than this are not parsed whole; their leading field is read instead. */
|
|
19
|
+
const MAX_PARSE_CHARS = 256 * 1024;
|
|
20
|
+
export function bounded(text) {
|
|
21
|
+
return text.length > MAX_STEP_TEXT_CHARS
|
|
22
|
+
? { text: text.slice(0, MAX_STEP_TEXT_CHARS), truncated: true }
|
|
23
|
+
: { text, truncated: false };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Codex's shell tools (unified exec and the older shell tools). Shown as Bash, the name Claude Code's
|
|
27
|
+
* shell tool has, so every coding agent's commands read and filter the same (CLAUDE.md, "Common
|
|
28
|
+
* interfaces for like systems"). Only the bare names: a namespaced tool is another server's.
|
|
29
|
+
*/
|
|
30
|
+
const CODEX_SHELL_TOOLS = new Set(["exec_command", "shell", "local_shell", "shell_command"]);
|
|
31
|
+
/** A tool's name as a step shows it; anything that isn't a plain name becomes "tool". */
|
|
32
|
+
export function displayToolName(name, namespace) {
|
|
33
|
+
let shown;
|
|
34
|
+
// Claude Code's one shell tool, and Codex's, show as the shell tool they stand for.
|
|
35
|
+
if (name === CLAUDE_COMMAND_TOOL || name === "run_command" || (!namespace && CODEX_SHELL_TOOLS.has(name))) {
|
|
36
|
+
shown = "Bash";
|
|
37
|
+
}
|
|
38
|
+
else if (name.startsWith(CLAUDE_MCP_TOOL_PREFIX))
|
|
39
|
+
shown = `mcp:${name.slice(CLAUDE_MCP_TOOL_PREFIX.length)}`;
|
|
40
|
+
else if (namespace === CODEX_MCP_NAMESPACE)
|
|
41
|
+
shown = `mcp:${name}`;
|
|
42
|
+
else if (namespace)
|
|
43
|
+
shown = `${namespace}.${name}`;
|
|
44
|
+
else
|
|
45
|
+
shown = name;
|
|
46
|
+
return STEP_TOOL_NAME.test(shown) ? shown : "tool";
|
|
47
|
+
}
|
|
48
|
+
const SHELLS = new Set(["bash", "sh", "zsh", "/bin/bash", "/bin/sh", "/bin/zsh", "/usr/bin/bash"]);
|
|
49
|
+
const COMMAND_TOOLS = new Set(["run_command", "Bash", "shell", "exec_command", "local_shell", "shell_command"]);
|
|
50
|
+
const PATH_KEYS = ["file_path", "path", "notebook_path"];
|
|
51
|
+
const PATCH_FILE = /^\*\*\* (?:Add|Update|Delete) File: (.+)$/gm;
|
|
52
|
+
const CODE_MODE_CMD = /\bcmd\s*:\s*("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\[[^\]]*\])/;
|
|
53
|
+
const LEADING_FIELD = /"(?:command|cmd|file_path|path|pattern)"\s*:\s*"((?:[^"\\]|\\.)*)/;
|
|
54
|
+
/** A command given as a string or an argv array; `bash -lc "<script>"` shows the script. */
|
|
55
|
+
function commandText(value) {
|
|
56
|
+
if (typeof value === "string")
|
|
57
|
+
return value;
|
|
58
|
+
if (!Array.isArray(value) || !value.every((part) => typeof part === "string"))
|
|
59
|
+
return undefined;
|
|
60
|
+
const argv = value;
|
|
61
|
+
if (argv.length === 3 && SHELLS.has(argv[0]) && (argv[1] === "-lc" || argv[1] === "-c"))
|
|
62
|
+
return argv[2];
|
|
63
|
+
return argv.join(" ");
|
|
64
|
+
}
|
|
65
|
+
function parseJson(text) {
|
|
66
|
+
try {
|
|
67
|
+
return JSON.parse(text);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** A JavaScript string or array literal from Codex code mode, as a value; its inner text if it won't parse. */
|
|
74
|
+
function parseLiteral(literal) {
|
|
75
|
+
if (literal.startsWith("'"))
|
|
76
|
+
return literal.slice(1, -1).replace(/\\'/g, "'");
|
|
77
|
+
return parseJson(literal) ?? literal.slice(1, -1);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A free-form tool input (a patch, Codex code-mode JavaScript, anything else). Otherwise its first
|
|
81
|
+
* non-blank line, `cut` when later lines are dropped: that line may be the start of a secret.
|
|
82
|
+
*/
|
|
83
|
+
function customInput(tool, input) {
|
|
84
|
+
if (tool === "apply_patch" || input.startsWith("*** Begin Patch")) {
|
|
85
|
+
const files = [...input.matchAll(PATCH_FILE)].map((match) => match[1].trim());
|
|
86
|
+
if (files.length > 0)
|
|
87
|
+
return { text: files.join(", "), cut: false };
|
|
88
|
+
}
|
|
89
|
+
const cmd = CODE_MODE_CMD.exec(input);
|
|
90
|
+
if (cmd) {
|
|
91
|
+
const command = commandText(parseLiteral(cmd[1]));
|
|
92
|
+
if (command !== undefined)
|
|
93
|
+
return { text: command, cut: false };
|
|
94
|
+
}
|
|
95
|
+
const lines = input.split("\n");
|
|
96
|
+
const first = lines.findIndex((line) => line.trim().length > 0);
|
|
97
|
+
if (first < 0)
|
|
98
|
+
return { text: "", cut: false };
|
|
99
|
+
return { text: lines[first].trim(), cut: lines.slice(first + 1).some((line) => line.trim().length > 0) };
|
|
100
|
+
}
|
|
101
|
+
/** The text a tool step's summary is cut from: the command, the path, the pattern, or the first argument. */
|
|
102
|
+
export function toolInputText(tool, input) {
|
|
103
|
+
return toolInput(tool, input).text;
|
|
104
|
+
}
|
|
105
|
+
/** toolInputText, and whether part of a free-form input was dropped (customInput). */
|
|
106
|
+
function toolInput(tool, input) {
|
|
107
|
+
if (typeof input === "string")
|
|
108
|
+
return customInput(tool, input);
|
|
109
|
+
if (tool === "apply_patch" && input && typeof input === "object" && !Array.isArray(input)) {
|
|
110
|
+
const patch = input.input;
|
|
111
|
+
if (typeof patch === "string")
|
|
112
|
+
return customInput(tool, patch);
|
|
113
|
+
}
|
|
114
|
+
return { text: plainInputText(tool, input), cut: false };
|
|
115
|
+
}
|
|
116
|
+
function plainInputText(tool, input) {
|
|
117
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
118
|
+
return "";
|
|
119
|
+
const args = input;
|
|
120
|
+
if (COMMAND_TOOLS.has(tool)) {
|
|
121
|
+
const command = commandText(args.command ?? args.cmd);
|
|
122
|
+
if (command !== undefined)
|
|
123
|
+
return command;
|
|
124
|
+
}
|
|
125
|
+
if (typeof args.pattern === "string") {
|
|
126
|
+
return typeof args.path === "string" ? `${args.pattern} in ${args.path}` : args.pattern;
|
|
127
|
+
}
|
|
128
|
+
for (const key of PATH_KEYS) {
|
|
129
|
+
const value = args[key];
|
|
130
|
+
if (typeof value === "string")
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
for (const value of Object.values(args)) {
|
|
134
|
+
if (typeof value === "string")
|
|
135
|
+
return value;
|
|
136
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
137
|
+
return String(value);
|
|
138
|
+
}
|
|
139
|
+
return "";
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* A string field of cut-off JSON (the first match of `field`, whose group 1 is the raw string
|
|
143
|
+
* body); `open` when the string never closed (it ran to the end of the input). Only an escape the
|
|
144
|
+
* cut left unfinished is dropped (an unpaired trailing backslash, or a partial `\\u` escape); a
|
|
145
|
+
* field that still won't decode is `undefined` text, never raw escapes.
|
|
146
|
+
*/
|
|
147
|
+
function stringField(json, field) {
|
|
148
|
+
const match = field.exec(json);
|
|
149
|
+
if (!match)
|
|
150
|
+
return undefined;
|
|
151
|
+
const open = json[match.index + match[0].length] !== '"';
|
|
152
|
+
const raw = match[1].replace(/(?<!\\)((?:\\\\)*)(?:\\u[0-9a-fA-F]{0,3}|\\)$/, "$1");
|
|
153
|
+
const decoded = parseJson(`"${raw}"`);
|
|
154
|
+
return { text: typeof decoded === "string" ? decoded : undefined, open };
|
|
155
|
+
}
|
|
156
|
+
/** The leading command, path or pattern field of cut-off tool arguments. */
|
|
157
|
+
const leadingStringField = (json) => stringField(json, LEADING_FIELD);
|
|
158
|
+
/** A tool step's text from its JSON arguments (complete or cut off). */
|
|
159
|
+
export function toolStepText(tool, argsJson) {
|
|
160
|
+
const parsed = argsJson.length <= MAX_PARSE_CHARS ? parseJson(argsJson) : undefined;
|
|
161
|
+
if (parsed !== undefined) {
|
|
162
|
+
const input = toolInput(tool, parsed);
|
|
163
|
+
const out = bounded(input.text);
|
|
164
|
+
return { text: out.text, truncated: out.truncated || input.cut };
|
|
165
|
+
}
|
|
166
|
+
const leading = leadingStringField(argsJson.slice(0, MAX_STEP_TEXT_CHARS));
|
|
167
|
+
// A field that won't decode could show a secret in escaped form: nothing of it is shown.
|
|
168
|
+
if (leading && leading.text === undefined)
|
|
169
|
+
return { text: REDACTED, truncated: true };
|
|
170
|
+
const out = bounded(leading?.text ?? "");
|
|
171
|
+
return {
|
|
172
|
+
text: out.text,
|
|
173
|
+
// An unclosed string was cut off by the provider or by us: it may end in the start of a secret.
|
|
174
|
+
truncated: out.truncated || argsJson.length > MAX_STEP_TEXT_CHARS || !!leading?.open,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/** Claude Code's tool for a coding run's final answer (the coding proxy allows it by this name). */
|
|
178
|
+
export const CLAUDE_RESULT_TOOL = "StructuredOutput";
|
|
179
|
+
/** A final answer's step when it has no summary of its own. */
|
|
180
|
+
export const FINISHED_STEP_TEXT = "Finished";
|
|
181
|
+
const RESULT_VERSION_FIELD = new RegExp(`"schemaVersion"\\s*:\\s*${CODING_PROTOCOL_VERSION}(?![0-9.])`);
|
|
182
|
+
const RESULT_SUMMARY_FIELD = /"summary"\s*:\s*"((?:[^"\\]|\\.)*)/;
|
|
183
|
+
const FINISHED_BY_OUTCOME = new Map([
|
|
184
|
+
["changes_ready", "Finished: changes ready"],
|
|
185
|
+
["no_changes", "Finished: no changes"],
|
|
186
|
+
["budget_exhausted", "Finished: budget exhausted"],
|
|
187
|
+
]);
|
|
188
|
+
const finishedLine = (outcome) => (typeof outcome === "string" ? FINISHED_BY_OUTCOME.get(outcome) : undefined) ?? FINISHED_STEP_TEXT;
|
|
189
|
+
/**
|
|
190
|
+
* A coding run's final structured answer (`{"schemaVersion":1,...,"summary":"..."}`: Claude Code's
|
|
191
|
+
* StructuredOutput input, Codex's last message) as the text of one said step: the answer's own
|
|
192
|
+
* summary, else a fixed Finished line. `undefined` when the text is not such an answer. A cut answer
|
|
193
|
+
* gives its summary as far as it goes, marked truncated. The text is redacted later, with every
|
|
194
|
+
* other step's, by stepSummarizer.
|
|
195
|
+
*/
|
|
196
|
+
export function codingResultStepText(json) {
|
|
197
|
+
const text = json.trimStart();
|
|
198
|
+
if (!text.startsWith("{"))
|
|
199
|
+
return undefined;
|
|
200
|
+
const parsed = text.length <= MAX_PARSE_CHARS ? parseJson(text) : undefined;
|
|
201
|
+
if (parsed !== undefined) {
|
|
202
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
203
|
+
return undefined;
|
|
204
|
+
const answer = parsed;
|
|
205
|
+
if (answer.schemaVersion !== CODING_PROTOCOL_VERSION)
|
|
206
|
+
return undefined;
|
|
207
|
+
if (typeof answer.summary === "string" && answer.summary.trim())
|
|
208
|
+
return bounded(answer.summary);
|
|
209
|
+
return { text: finishedLine(answer.outcome), truncated: false };
|
|
210
|
+
}
|
|
211
|
+
// Cut off (a block past 16 KiB, a reply that stopped early): an answer only if it says so.
|
|
212
|
+
const head = text.slice(0, MAX_STEP_TEXT_CHARS);
|
|
213
|
+
if (!RESULT_VERSION_FIELD.test(head))
|
|
214
|
+
return undefined;
|
|
215
|
+
const summary = stringField(head, RESULT_SUMMARY_FIELD);
|
|
216
|
+
if (!summary)
|
|
217
|
+
return { text: FINISHED_STEP_TEXT, truncated: false };
|
|
218
|
+
// A field that won't decode could show a secret in escaped form: nothing of it is shown.
|
|
219
|
+
if (summary.text === undefined)
|
|
220
|
+
return { text: REDACTED, truncated: true };
|
|
221
|
+
return { text: bounded(summary.text).text, truncated: true };
|
|
222
|
+
}
|
|
223
|
+
export const collapseWhitespace = (text) => text.replace(/\s+/g, " ").trim();
|
|
224
|
+
/**
|
|
225
|
+
* Every form a secret value can take in a collapsed step text, longest first: each value as
|
|
226
|
+
* written and whitespace-collapsed (the model wrote it with other whitespace), each of those
|
|
227
|
+
* JSON-escaped (withEscapedForms: a value inside JSON text), and each of those collapsed.
|
|
228
|
+
*/
|
|
229
|
+
export function secretForms(secretValues) {
|
|
230
|
+
const forms = new Set();
|
|
231
|
+
for (const escaped of withEscapedForms(secretValues.flatMap((value) => [value, collapseWhitespace(value)]))) {
|
|
232
|
+
for (const form of [escaped, collapseWhitespace(escaped)])
|
|
233
|
+
if (form)
|
|
234
|
+
forms.add(form);
|
|
235
|
+
}
|
|
236
|
+
return [...forms].sort((a, b) => b.length - a.length);
|
|
237
|
+
}
|
|
238
|
+
/** KMP failure table of `pattern`: entry i is the longest proper border of pattern[0..i]. */
|
|
239
|
+
function failureTable(pattern) {
|
|
240
|
+
const fail = new Int32Array(pattern.length);
|
|
241
|
+
for (let i = 1, k = 0; i < pattern.length; i++) {
|
|
242
|
+
while (k > 0 && pattern.charCodeAt(i) !== pattern.charCodeAt(k))
|
|
243
|
+
k = fail[k - 1];
|
|
244
|
+
if (pattern.charCodeAt(i) === pattern.charCodeAt(k))
|
|
245
|
+
k++;
|
|
246
|
+
fail[i] = k;
|
|
247
|
+
}
|
|
248
|
+
return fail;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The longest proper prefix of any form (at least the minimum length) that `text` ends with, or
|
|
252
|
+
* "" if none. Linear in the text per form (a KMP scan), whatever the forms look like. `tables`
|
|
253
|
+
* holds each form's failure table, built the first time a cut text needs it.
|
|
254
|
+
*/
|
|
255
|
+
export function secretTail(text, forms, tables) {
|
|
256
|
+
let best = 0;
|
|
257
|
+
for (const form of forms) {
|
|
258
|
+
// A form's proper prefixes; the text is never longer than the step text bound.
|
|
259
|
+
const pattern = form.length - 1 > MAX_STEP_TEXT_CHARS ? form.slice(0, MAX_STEP_TEXT_CHARS) : form.slice(0, -1);
|
|
260
|
+
if (pattern.length < MIN_REDACTABLE_SECRET_LENGTH || pattern.length <= best)
|
|
261
|
+
continue;
|
|
262
|
+
let fail = tables.get(form);
|
|
263
|
+
if (!fail)
|
|
264
|
+
tables.set(form, (fail = failureTable(pattern)));
|
|
265
|
+
let k = 0;
|
|
266
|
+
for (let i = Math.max(0, text.length - pattern.length); i < text.length; i++) {
|
|
267
|
+
if (k === pattern.length)
|
|
268
|
+
k = fail[k - 1];
|
|
269
|
+
while (k > 0 && text.charCodeAt(i) !== pattern.charCodeAt(k))
|
|
270
|
+
k = fail[k - 1];
|
|
271
|
+
if (text.charCodeAt(i) === pattern.charCodeAt(k))
|
|
272
|
+
k++;
|
|
273
|
+
}
|
|
274
|
+
if (k >= MIN_REDACTABLE_SECRET_LENGTH && k > best)
|
|
275
|
+
best = k;
|
|
276
|
+
}
|
|
277
|
+
return best > 0 ? text.slice(text.length - best) : "";
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Summaries for one batch, against every secret value delivered to the run (the agent's and the
|
|
281
|
+
* run's own, such as its capability). The forms are built once per batch. Each step's text (at
|
|
282
|
+
* most 16 KiB) is whitespace collapsed, then redacted once with the shared redactor
|
|
283
|
+
* (redactSecrets: the union of every occurrence of every form, then token shapes), then cut to 100
|
|
284
|
+
* characters (tool) or 120 (said). Any text may end in the start of a secret (it was cut, or a
|
|
285
|
+
* stream stopped early without saying so), so on every step the longest such start (secretTail,
|
|
286
|
+
* on the collapsed text before any masking) is redacted in the same pass as one more exact value,
|
|
287
|
+
* so it merges with any whole secret it overlaps.
|
|
288
|
+
*/
|
|
289
|
+
export function stepSummarizer(secretValues) {
|
|
290
|
+
const forms = secretForms(secretValues);
|
|
291
|
+
const tables = new Map();
|
|
292
|
+
return (step) => {
|
|
293
|
+
const collapsed = collapseWhitespace(step.text.slice(0, MAX_STEP_TEXT_CHARS));
|
|
294
|
+
// Every step: a text can end mid-secret without being marked cut (a stream that stopped early).
|
|
295
|
+
const tail = secretTail(collapsed, forms, tables);
|
|
296
|
+
const text = redactSecrets(collapsed, tail ? [...forms, tail] : forms);
|
|
297
|
+
const limit = step.kind === "tool" ? TOOL_SUMMARY_CHARS : SAID_SUMMARY_CHARS;
|
|
298
|
+
return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/** One step's stored summary (stepSummarizer, for a single step). */
|
|
302
|
+
export function stepSummary(step, secretValues) {
|
|
303
|
+
return stepSummarizer(secretValues)(step);
|
|
304
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deletes run steps past RUN_STEP_RETENTION_DAYS (their expiresAt), in bounded batches. Runs on the
|
|
3
|
+
* scheduler leader's tick, at most every RUN_STEP_SWEEP_INTERVAL_MS, so one replica sweeps.
|
|
4
|
+
*/
|
|
5
|
+
import type { PrismaClient } from "#prisma";
|
|
6
|
+
export declare const RUN_STEP_SWEEP_INTERVAL_MS: number;
|
|
7
|
+
export declare function sweepExpiredRunSteps(db: Pick<PrismaClient, "$executeRaw">, now?: Date, opts?: {
|
|
8
|
+
batchSize?: number;
|
|
9
|
+
maxBatches?: number;
|
|
10
|
+
}): Promise<number>;
|
|
11
|
+
export declare class RunStepSweeper {
|
|
12
|
+
private readonly db;
|
|
13
|
+
private readonly options;
|
|
14
|
+
private lastAt;
|
|
15
|
+
constructor(db: Pick<PrismaClient, "$executeRaw">, options?: {
|
|
16
|
+
intervalMs?: number;
|
|
17
|
+
now?: () => Date;
|
|
18
|
+
});
|
|
19
|
+
/** Sweeps if the interval has passed since the last attempt. Never rejects. */
|
|
20
|
+
maybeSweep(): Promise<number>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { logger } from "../logger.js";
|
|
2
|
+
const sweepLog = logger.child({ module: "run-steps" });
|
|
3
|
+
export const RUN_STEP_SWEEP_INTERVAL_MS = 10 * 60_000;
|
|
4
|
+
export async function sweepExpiredRunSteps(db, now = new Date(), opts = {}) {
|
|
5
|
+
const batchSize = opts.batchSize ?? 1_000;
|
|
6
|
+
const maxBatches = opts.maxBatches ?? 10;
|
|
7
|
+
let total = 0;
|
|
8
|
+
for (let batch = 0; batch < maxBatches; batch++) {
|
|
9
|
+
const deleted = await db.$executeRaw `
|
|
10
|
+
DELETE FROM "RunStep" WHERE "id" IN (
|
|
11
|
+
SELECT "id" FROM "RunStep" WHERE "expiresAt" < ${now} ORDER BY "expiresAt" LIMIT ${batchSize}
|
|
12
|
+
)`;
|
|
13
|
+
total += deleted;
|
|
14
|
+
if (deleted < batchSize)
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
return total;
|
|
18
|
+
}
|
|
19
|
+
export class RunStepSweeper {
|
|
20
|
+
db;
|
|
21
|
+
options;
|
|
22
|
+
lastAt = Number.NEGATIVE_INFINITY;
|
|
23
|
+
constructor(db, options = {}) {
|
|
24
|
+
this.db = db;
|
|
25
|
+
this.options = options;
|
|
26
|
+
}
|
|
27
|
+
/** Sweeps if the interval has passed since the last attempt. Never rejects. */
|
|
28
|
+
async maybeSweep() {
|
|
29
|
+
const now = (this.options.now ?? (() => new Date()))();
|
|
30
|
+
if (now.getTime() - this.lastAt < (this.options.intervalMs ?? RUN_STEP_SWEEP_INTERVAL_MS))
|
|
31
|
+
return 0;
|
|
32
|
+
this.lastAt = now.getTime();
|
|
33
|
+
try {
|
|
34
|
+
return await sweepExpiredRunSteps(this.db, now);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
sweepLog.warn({ event: "run_steps_sweep_failed", errorName: err instanceof Error ? err.name : typeof err }, "could not delete expired run steps; the next pass tries again");
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run steps: one line per tool call (with its status once the result is back) or per model
|
|
3
|
+
* message. Every source (the native engine, the native gateways, the coding proxy) produces these
|
|
4
|
+
* shapes; only the control plane's RunStepWriter turns them into stored rows.
|
|
5
|
+
*/
|
|
6
|
+
/** A step as an extractor saw it: `text` is what the summary is cut from (at most MAX_STEP_TEXT_CHARS). */
|
|
7
|
+
export type ExtractedStep = {
|
|
8
|
+
kind: "tool";
|
|
9
|
+
callId: string;
|
|
10
|
+
tool: string;
|
|
11
|
+
text: string;
|
|
12
|
+
truncated: boolean;
|
|
13
|
+
/** Detail level only: the tool's raw input (at most MAX_STEP_TEXT_CHARS). */
|
|
14
|
+
input?: string;
|
|
15
|
+
/** The input was cut (it may end in the start of a secret). */
|
|
16
|
+
inputTruncated?: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
kind: "said";
|
|
19
|
+
callId: string;
|
|
20
|
+
text: string;
|
|
21
|
+
truncated: boolean;
|
|
22
|
+
};
|
|
23
|
+
export type StepStatus = "ok" | "error" | `exit ${number}`;
|
|
24
|
+
/** A tool call's status, matched to its step by callId. Never the result's content below the detail level. */
|
|
25
|
+
export interface ExtractedResult {
|
|
26
|
+
callId: string;
|
|
27
|
+
status: StepStatus;
|
|
28
|
+
/** Detail level only: the end of the result's text output (at most MAX_RESULT_OUTPUT_CHARS). */
|
|
29
|
+
output?: string;
|
|
30
|
+
/** Text before `output` was dropped (it may start with the end of a secret). */
|
|
31
|
+
outputCut?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface RunStepBatch {
|
|
34
|
+
steps: ExtractedStep[];
|
|
35
|
+
results: ExtractedResult[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Where trusted code hands a run's steps: redacts by exact value, then writes. Never rejects.
|
|
39
|
+
* `extraSecretValues` are values the caller knows that the agent's own secrets do not cover (the
|
|
40
|
+
* native-sandbox gateway's run capability); they are redacted like the agent's.
|
|
41
|
+
*/
|
|
42
|
+
export interface RunStepSink {
|
|
43
|
+
record(runId: string, batch: RunStepBatch, extraSecretValues?: readonly string[]): Promise<void>;
|
|
44
|
+
/** Resolves when the writes queued so far settle (bounded; never rejects): before a shutdown disconnects. */
|
|
45
|
+
flush?(): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The coding proxy's side: queues a batch for the control plane, for the run the capability belongs
|
|
49
|
+
* to (the listener refuses any other). The step text is sent unmasked: the control plane redacts it
|
|
50
|
+
* by exact value (stepSummarizer) before anything is stored. Synchronous; never throws.
|
|
51
|
+
*/
|
|
52
|
+
export interface RunStepSubmitter {
|
|
53
|
+
submit(capability: string, runId: string, batch: RunStepBatch): void;
|
|
54
|
+
}
|
|
55
|
+
/** The most text an extractor keeps for one step (a longer block is cut, not buffered). */
|
|
56
|
+
export declare const MAX_STEP_TEXT_CHARS: number;
|
|
57
|
+
/** The most steps one run records; later ones are counted in Run.stepsDropped. */
|
|
58
|
+
export declare const MAX_RUN_STEPS = 2000;
|
|
59
|
+
export declare const TOOL_SUMMARY_CHARS = 100;
|
|
60
|
+
export declare const SAID_SUMMARY_CHARS = 120;
|
|
61
|
+
/** The summary column's bound. */
|
|
62
|
+
export declare const MAX_SUMMARY_CHARS = 300;
|
|
63
|
+
/** The detail level's parts: a tool's input head, its output's tail, a model message's head. */
|
|
64
|
+
export declare const DETAIL_INPUT_CHARS: number;
|
|
65
|
+
export declare const DETAIL_OUTPUT_CHARS: number;
|
|
66
|
+
export declare const DETAIL_SAID_CHARS: number;
|
|
67
|
+
/** The detail column's bound. */
|
|
68
|
+
export declare const MAX_STEP_DETAIL_CHARS: number;
|
|
69
|
+
/** The most of a tool result's output a source sends (its end): more than DETAIL_OUTPUT_CHARS, so a secret across the cut is still whole. */
|
|
70
|
+
export declare const MAX_RESULT_OUTPUT_CHARS: number;
|
|
71
|
+
/** The end of `text`, at most `max` characters, and whether anything before it was dropped. */
|
|
72
|
+
export declare function tailOf(text: string, max?: number): {
|
|
73
|
+
text: string;
|
|
74
|
+
cut: boolean;
|
|
75
|
+
};
|
|
76
|
+
/** Between a tool's input and its output in the detail text. */
|
|
77
|
+
export declare const DETAIL_OUTPUT_HEADING = "\n\n--- output (end) ---\n";
|
|
78
|
+
/** ok, error, or exit 1-999: fixed shapes only, so no free text can reach a status. */
|
|
79
|
+
export declare const STEP_STATUS: RegExp;
|
|
80
|
+
export declare const STEP_TOOL_NAME: RegExp;
|
|
81
|
+
/** Printable ASCII, as the providers' and workers' ids are. */
|
|
82
|
+
export declare const STEP_CALL_ID: RegExp;
|
|
83
|
+
/** The control plane's run-steps listener route (WARDBY_RUN_STEPS_LISTEN). */
|
|
84
|
+
export declare const RUN_STEPS_INGRESS_PATH = "/internal/run-steps";
|
|
85
|
+
/** One POST's bounds; the forwarder splits larger batches. */
|
|
86
|
+
export declare const MAX_STEPS_PER_POST = 64;
|
|
87
|
+
export declare const MAX_RESULTS_PER_POST = 256;
|
|
88
|
+
/** The most bytes one POST body may carry (the listener refuses a larger one); parts are split to fit. */
|
|
89
|
+
export declare const MAX_RUN_STEPS_POST_BYTES: number;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run steps: one line per tool call (with its status once the result is back) or per model
|
|
3
|
+
* message. Every source (the native engine, the native gateways, the coding proxy) produces these
|
|
4
|
+
* shapes; only the control plane's RunStepWriter turns them into stored rows.
|
|
5
|
+
*/
|
|
6
|
+
/** The most text an extractor keeps for one step (a longer block is cut, not buffered). */
|
|
7
|
+
export const MAX_STEP_TEXT_CHARS = 16 * 1024;
|
|
8
|
+
/** The most steps one run records; later ones are counted in Run.stepsDropped. */
|
|
9
|
+
export const MAX_RUN_STEPS = 2_000;
|
|
10
|
+
export const TOOL_SUMMARY_CHARS = 100;
|
|
11
|
+
export const SAID_SUMMARY_CHARS = 120;
|
|
12
|
+
/** The summary column's bound. */
|
|
13
|
+
export const MAX_SUMMARY_CHARS = 300;
|
|
14
|
+
/** The detail level's parts: a tool's input head, its output's tail, a model message's head. */
|
|
15
|
+
export const DETAIL_INPUT_CHARS = 4 * 1024;
|
|
16
|
+
export const DETAIL_OUTPUT_CHARS = 2 * 1024;
|
|
17
|
+
export const DETAIL_SAID_CHARS = 8 * 1024;
|
|
18
|
+
/** The detail column's bound. */
|
|
19
|
+
export const MAX_STEP_DETAIL_CHARS = 16 * 1024;
|
|
20
|
+
/** The most of a tool result's output a source sends (its end): more than DETAIL_OUTPUT_CHARS, so a secret across the cut is still whole. */
|
|
21
|
+
export const MAX_RESULT_OUTPUT_CHARS = 4 * 1024;
|
|
22
|
+
/** The end of `text`, at most `max` characters, and whether anything before it was dropped. */
|
|
23
|
+
export function tailOf(text, max = MAX_RESULT_OUTPUT_CHARS) {
|
|
24
|
+
return text.length > max ? { text: text.slice(text.length - max), cut: true } : { text, cut: false };
|
|
25
|
+
}
|
|
26
|
+
/** Between a tool's input and its output in the detail text. */
|
|
27
|
+
export const DETAIL_OUTPUT_HEADING = "\n\n--- output (end) ---\n";
|
|
28
|
+
/** ok, error, or exit 1-999: fixed shapes only, so no free text can reach a status. */
|
|
29
|
+
export const STEP_STATUS = /^(?:ok|error|exit [1-9]\d{0,2})$/;
|
|
30
|
+
export const STEP_TOOL_NAME = /^[A-Za-z0-9_.:-]{1,128}$/;
|
|
31
|
+
/** Printable ASCII, as the providers' and workers' ids are. */
|
|
32
|
+
export const STEP_CALL_ID = /^[\x21-\x7e]{1,600}$/;
|
|
33
|
+
/** The control plane's run-steps listener route (WARDBY_RUN_STEPS_LISTEN). */
|
|
34
|
+
export const RUN_STEPS_INGRESS_PATH = "/internal/run-steps";
|
|
35
|
+
/** One POST's bounds; the forwarder splits larger batches. */
|
|
36
|
+
export const MAX_STEPS_PER_POST = 64;
|
|
37
|
+
export const MAX_RESULTS_PER_POST = 256;
|
|
38
|
+
/** The most bytes one POST body may carry (the listener refuses a larger one); parts are split to fit. */
|
|
39
|
+
export const MAX_RUN_STEPS_POST_BYTES = 1 * 1024 * 1024;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run-steps listener's request body: the run it is for, and the batch. Strict, bounded,
|
|
3
|
+
* fixed-shape statuses only (the forwarder's split keeps every POST within these bounds).
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { type RunStepBatch } from "./types.js";
|
|
7
|
+
export declare const RunStepPostSchema: z.ZodObject<{
|
|
8
|
+
runId: z.ZodString;
|
|
9
|
+
steps: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
10
|
+
kind: z.ZodLiteral<"tool">;
|
|
11
|
+
callId: z.ZodString;
|
|
12
|
+
tool: z.ZodString;
|
|
13
|
+
text: z.ZodString;
|
|
14
|
+
truncated: z.ZodBoolean;
|
|
15
|
+
input: z.ZodOptional<z.ZodString>;
|
|
16
|
+
inputTruncated: z.ZodOptional<z.ZodBoolean>;
|
|
17
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
18
|
+
kind: z.ZodLiteral<"said">;
|
|
19
|
+
callId: z.ZodString;
|
|
20
|
+
text: z.ZodString;
|
|
21
|
+
truncated: z.ZodBoolean;
|
|
22
|
+
}, z.core.$strict>], "kind">>;
|
|
23
|
+
results: z.ZodArray<z.ZodObject<{
|
|
24
|
+
callId: z.ZodString;
|
|
25
|
+
status: z.ZodString;
|
|
26
|
+
output: z.ZodOptional<z.ZodString>;
|
|
27
|
+
outputCut: z.ZodOptional<z.ZodBoolean>;
|
|
28
|
+
}, z.core.$strict>>;
|
|
29
|
+
}, z.core.$strict>;
|
|
30
|
+
export interface RunStepPost {
|
|
31
|
+
runId: string;
|
|
32
|
+
batch: RunStepBatch;
|
|
33
|
+
}
|
|
34
|
+
/** The POST's run and batch; undefined for any body outside the schema (never an error's text). */
|
|
35
|
+
export declare function parseRunStepPost(value: unknown): RunStepPost | undefined;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run-steps listener's request body: the run it is for, and the batch. Strict, bounded,
|
|
3
|
+
* fixed-shape statuses only (the forwarder's split keeps every POST within these bounds).
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { MAX_RESULT_OUTPUT_CHARS, MAX_RESULTS_PER_POST, MAX_STEP_TEXT_CHARS, MAX_STEPS_PER_POST, STEP_CALL_ID, STEP_STATUS, STEP_TOOL_NAME, } from "./types.js";
|
|
7
|
+
const callId = z.string().regex(STEP_CALL_ID);
|
|
8
|
+
const text = z.string().max(MAX_STEP_TEXT_CHARS);
|
|
9
|
+
export const RunStepPostSchema = z
|
|
10
|
+
.object({
|
|
11
|
+
runId: z.string().min(1).max(200),
|
|
12
|
+
steps: z
|
|
13
|
+
.array(z.discriminatedUnion("kind", [
|
|
14
|
+
z
|
|
15
|
+
.object({
|
|
16
|
+
kind: z.literal("tool"),
|
|
17
|
+
callId,
|
|
18
|
+
tool: z.string().regex(STEP_TOOL_NAME),
|
|
19
|
+
text,
|
|
20
|
+
truncated: z.boolean(),
|
|
21
|
+
input: text.optional(),
|
|
22
|
+
inputTruncated: z.boolean().optional(),
|
|
23
|
+
})
|
|
24
|
+
.strict(),
|
|
25
|
+
z.object({ kind: z.literal("said"), callId, text, truncated: z.boolean() }).strict(),
|
|
26
|
+
]))
|
|
27
|
+
.max(MAX_STEPS_PER_POST),
|
|
28
|
+
results: z
|
|
29
|
+
.array(z
|
|
30
|
+
.object({
|
|
31
|
+
callId,
|
|
32
|
+
status: z.string().regex(STEP_STATUS),
|
|
33
|
+
output: z.string().max(MAX_RESULT_OUTPUT_CHARS).optional(),
|
|
34
|
+
outputCut: z.boolean().optional(),
|
|
35
|
+
})
|
|
36
|
+
.strict())
|
|
37
|
+
.max(MAX_RESULTS_PER_POST),
|
|
38
|
+
})
|
|
39
|
+
.strict();
|
|
40
|
+
/** The POST's run and batch; undefined for any body outside the schema (never an error's text). */
|
|
41
|
+
export function parseRunStepPost(value) {
|
|
42
|
+
const parsed = RunStepPostSchema.safeParse(value);
|
|
43
|
+
if (!parsed.success)
|
|
44
|
+
return undefined;
|
|
45
|
+
const { runId, steps, results } = parsed.data;
|
|
46
|
+
return { runId, batch: { steps, results } };
|
|
47
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { PrismaClient } from "#prisma";
|
|
2
|
+
import { loadAgentSecretValues } from "../secret-guard.js";
|
|
3
|
+
import { type RunStepsLevel } from "./config.js";
|
|
4
|
+
import { type RunStepBatch, type RunStepSink } from "./types.js";
|
|
5
|
+
/** How long flush() waits for queued writes at shutdown. */
|
|
6
|
+
export declare const FLUSH_TIMEOUT_MS = 5000;
|
|
7
|
+
export type RunStepWriterDb = Pick<PrismaClient, "run" | "runStep" | "$transaction">;
|
|
8
|
+
export interface RunStepWriterDeps {
|
|
9
|
+
db: RunStepWriterDb;
|
|
10
|
+
/** Every secret value of the agent (already with JSON-escaped forms); rejects when any can't be read. */
|
|
11
|
+
secretValues: (agentId: string) => Promise<string[]>;
|
|
12
|
+
/** The process's ceiling: off stops this writer; otherwise used only for runs without a fixed level. */
|
|
13
|
+
level: RunStepsLevel;
|
|
14
|
+
retentionDays: number;
|
|
15
|
+
/** Tests only; MAX_RUN_STEPS otherwise. */
|
|
16
|
+
maxSteps?: number;
|
|
17
|
+
/** Tests only; the remembered-callId limits (per run, in all). */
|
|
18
|
+
droppedLimits?: {
|
|
19
|
+
perRun: number;
|
|
20
|
+
total: number;
|
|
21
|
+
};
|
|
22
|
+
now?: () => Date;
|
|
23
|
+
log?: {
|
|
24
|
+
warn(fields: Record<string, unknown>, message: string): void;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export declare class RunStepWriter implements RunStepSink {
|
|
28
|
+
private readonly deps;
|
|
29
|
+
private readonly queues;
|
|
30
|
+
/**
|
|
31
|
+
* Hashes of the callIds already counted in stepsDropped, so history resent past the cap is not
|
|
32
|
+
* counted again. Least recently used run first; bounded in total, whatever the ids' length.
|
|
33
|
+
*/
|
|
34
|
+
private readonly droppedIds;
|
|
35
|
+
constructor(deps: RunStepWriterDeps);
|
|
36
|
+
record(runId: string, batch: RunStepBatch, extraSecretValues?: readonly string[]): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Resolves when every write queued so far has settled, or after `timeoutMs`: called before the
|
|
39
|
+
* process disconnects from the database, so a run's last steps are not cut off. Never rejects.
|
|
40
|
+
*/
|
|
41
|
+
flush(timeoutMs?: number): Promise<void>;
|
|
42
|
+
private now;
|
|
43
|
+
private write;
|
|
44
|
+
private rememberDropped;
|
|
45
|
+
/** How many dropped callIds are remembered in all (tests). */
|
|
46
|
+
rememberedDroppedCount(): number;
|
|
47
|
+
}
|
|
48
|
+
/** The process's writer: settings from the environment (an invalid value throws, naming it). */
|
|
49
|
+
export declare function buildRunStepWriter(db: PrismaClient, cipher: Parameters<typeof loadAgentSecretValues>[1], env?: NodeJS.ProcessEnv): RunStepWriter;
|