@wardby/cli 0.1.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 +131 -0
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +282 -0
- package/bin/wardby.js +3 -0
- package/deploy/local/docker-compose.yml +19 -0
- package/dist/claude-coding-worker/driver.d.ts +70 -0
- package/dist/claude-coding-worker/driver.js +128 -0
- package/dist/claude-coding-worker/main.d.ts +2 -0
- package/dist/claude-coding-worker/main.js +41 -0
- package/dist/claude-coding-worker/sdk.d.ts +4 -0
- package/dist/claude-coding-worker/sdk.js +43 -0
- package/dist/claude-coding-worker/tool-relay.d.ts +1 -0
- package/dist/claude-coding-worker/tool-relay.js +9 -0
- package/dist/cli-help.d.ts +1 -0
- package/dist/cli-help.js +20 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +629 -0
- package/dist/coding/observability.d.ts +59 -0
- package/dist/coding/observability.js +77 -0
- package/dist/coding/profile.d.ts +88 -0
- package/dist/coding/profile.js +137 -0
- package/dist/coding/protocol.d.ts +345 -0
- package/dist/coding/protocol.js +445 -0
- package/dist/coding/provider.d.ts +4 -0
- package/dist/coding/provider.js +18 -0
- package/dist/coding-proxy/main.d.ts +1 -0
- package/dist/coding-proxy/main.js +51 -0
- package/dist/coding-worker/artifact.d.ts +16 -0
- package/dist/coding-worker/artifact.js +37 -0
- package/dist/coding-worker/driver.d.ts +46 -0
- package/dist/coding-worker/driver.js +158 -0
- package/dist/coding-worker/errors.d.ts +1 -0
- package/dist/coding-worker/errors.js +23 -0
- package/dist/coding-worker/isolation-probe.d.ts +1 -0
- package/dist/coding-worker/isolation-probe.js +146 -0
- package/dist/coding-worker/keeper.d.ts +4 -0
- package/dist/coding-worker/keeper.js +29 -0
- package/dist/coding-worker/main.d.ts +2 -0
- package/dist/coding-worker/main.js +43 -0
- package/dist/coding-worker/sdk.d.ts +26 -0
- package/dist/coding-worker/sdk.js +28 -0
- package/dist/coding-worker/types.d.ts +64 -0
- package/dist/coding-worker/types.js +1 -0
- package/dist/config/providers.d.ts +158 -0
- package/dist/config/providers.js +162 -0
- package/dist/core/budget-groups.d.ts +72 -0
- package/dist/core/budget-groups.js +149 -0
- package/dist/core/budget.d.ts +63 -0
- package/dist/core/budget.js +69 -0
- package/dist/core/coding-queue.d.ts +32 -0
- package/dist/core/coding-queue.js +54 -0
- package/dist/core/cron.d.ts +31 -0
- package/dist/core/cron.js +43 -0
- package/dist/core/datastores.d.ts +41 -0
- package/dist/core/datastores.js +105 -0
- package/dist/core/db.d.ts +2 -0
- package/dist/core/db.js +2 -0
- package/dist/core/dispatch.d.ts +81 -0
- package/dist/core/dispatch.js +184 -0
- package/dist/core/engine-native.d.ts +29 -0
- package/dist/core/engine-native.js +259 -0
- package/dist/core/http-runtime.d.ts +7 -0
- package/dist/core/http-runtime.js +101 -0
- package/dist/core/lease.d.ts +13 -0
- package/dist/core/lease.js +22 -0
- package/dist/core/logger.d.ts +13 -0
- package/dist/core/logger.js +45 -0
- package/dist/core/memory-tools.d.ts +11 -0
- package/dist/core/memory-tools.js +122 -0
- package/dist/core/reconciler.d.ts +45 -0
- package/dist/core/reconciler.js +152 -0
- package/dist/core/runner.d.ts +87 -0
- package/dist/core/runner.js +529 -0
- package/dist/core/scheduler.d.ts +47 -0
- package/dist/core/scheduler.js +121 -0
- package/dist/core/secrets.d.ts +46 -0
- package/dist/core/secrets.js +100 -0
- package/dist/core/subagent-memory-tools.d.ts +18 -0
- package/dist/core/subagent-memory-tools.js +121 -0
- package/dist/core/timing.d.ts +7 -0
- package/dist/core/timing.js +7 -0
- package/dist/core/webhooks.d.ts +23 -0
- package/dist/core/webhooks.js +113 -0
- package/dist/env.d.ts +11 -0
- package/dist/env.js +13 -0
- package/dist/import/budgets.d.ts +16 -0
- package/dist/import/budgets.js +21 -0
- package/dist/import/bundle.d.ts +16 -0
- package/dist/import/bundle.js +61 -0
- package/dist/import/cli-args.d.ts +2 -0
- package/dist/import/cli-args.js +44 -0
- package/dist/import/create.d.ts +34 -0
- package/dist/import/create.js +363 -0
- package/dist/import/index.d.ts +25 -0
- package/dist/import/index.js +130 -0
- package/dist/import/model-gate.d.ts +1 -0
- package/dist/import/model-gate.js +3 -0
- package/dist/import/neutral-schema.d.ts +369 -0
- package/dist/import/neutral-schema.js +109 -0
- package/dist/import/preflight.d.ts +41 -0
- package/dist/import/preflight.js +71 -0
- package/dist/import/report.d.ts +5 -0
- package/dist/import/report.js +98 -0
- package/dist/import/tool-scan.d.ts +8 -0
- package/dist/import/tool-scan.js +20 -0
- package/dist/mcp/auth/ownership.d.ts +113 -0
- package/dist/mcp/auth/ownership.js +87 -0
- package/dist/mcp/auth/principal.d.ts +9 -0
- package/dist/mcp/auth/principal.js +9 -0
- package/dist/mcp/auth/resource-server.d.ts +47 -0
- package/dist/mcp/auth/resource-server.js +82 -0
- package/dist/mcp/auth/self-hosted/browser.d.ts +3 -0
- package/dist/mcp/auth/self-hosted/browser.js +273 -0
- package/dist/mcp/auth/self-hosted/cli.d.ts +2 -0
- package/dist/mcp/auth/self-hosted/cli.js +39 -0
- package/dist/mcp/auth/self-hosted/credentials.d.ts +58 -0
- package/dist/mcp/auth/self-hosted/credentials.js +106 -0
- package/dist/mcp/auth/self-hosted/rate-limit.d.ts +13 -0
- package/dist/mcp/auth/self-hosted/rate-limit.js +20 -0
- package/dist/mcp/auth/self-hosted/session.d.ts +36 -0
- package/dist/mcp/auth/self-hosted/session.js +110 -0
- package/dist/mcp/capabilities.d.ts +11 -0
- package/dist/mcp/capabilities.js +10 -0
- package/dist/mcp/context.d.ts +46 -0
- package/dist/mcp/context.js +1 -0
- package/dist/mcp/errors.d.ts +13 -0
- package/dist/mcp/errors.js +22 -0
- package/dist/mcp/index.d.ts +32 -0
- package/dist/mcp/index.js +187 -0
- package/dist/mcp/jsonrpc.d.ts +8 -0
- package/dist/mcp/jsonrpc.js +1 -0
- package/dist/mcp/server.d.ts +107 -0
- package/dist/mcp/server.js +203 -0
- package/dist/mcp/tasks/manager.d.ts +63 -0
- package/dist/mcp/tasks/manager.js +103 -0
- package/dist/mcp/tools/agents.d.ts +2 -0
- package/dist/mcp/tools/agents.js +375 -0
- package/dist/mcp/tools/budget-groups.d.ts +2 -0
- package/dist/mcp/tools/budget-groups.js +136 -0
- package/dist/mcp/tools/datastore.d.ts +2 -0
- package/dist/mcp/tools/datastore.js +217 -0
- package/dist/mcp/tools/memory.d.ts +3 -0
- package/dist/mcp/tools/memory.js +60 -0
- package/dist/mcp/tools/models.d.ts +8 -0
- package/dist/mcp/tools/models.js +15 -0
- package/dist/mcp/tools/runs.d.ts +2 -0
- package/dist/mcp/tools/runs.js +61 -0
- package/dist/mcp/tools/scheduling.d.ts +2 -0
- package/dist/mcp/tools/scheduling.js +48 -0
- package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -0
- package/dist/mcp/tools/secret-elicitation-form.js +93 -0
- package/dist/mcp/tools/secret-elicitation-server.d.ts +5 -0
- package/dist/mcp/tools/secret-elicitation-server.js +57 -0
- package/dist/mcp/tools/secret-elicitation.d.ts +38 -0
- package/dist/mcp/tools/secret-elicitation.js +52 -0
- package/dist/mcp/tools/secrets.d.ts +13 -0
- package/dist/mcp/tools/secrets.js +124 -0
- package/dist/mcp/tools/subagents.d.ts +2 -0
- package/dist/mcp/tools/subagents.js +133 -0
- package/dist/mcp/tools/text-result.d.ts +6 -0
- package/dist/mcp/tools/text-result.js +3 -0
- package/dist/mcp/tools/tools.d.ts +2 -0
- package/dist/mcp/tools/tools.js +211 -0
- package/dist/mcp/tools/trigger.d.ts +2 -0
- package/dist/mcp/tools/trigger.js +120 -0
- package/dist/mcp/tools/webhooks.d.ts +2 -0
- package/dist/mcp/tools/webhooks.js +34 -0
- package/dist/mcp/transport/http-limits.d.ts +17 -0
- package/dist/mcp/transport/http-limits.js +92 -0
- package/dist/mcp/transport/stdio.d.ts +23 -0
- package/dist/mcp/transport/stdio.js +6 -0
- package/dist/mcp/transport/streamable-http.d.ts +34 -0
- package/dist/mcp/transport/streamable-http.js +158 -0
- package/dist/mcp/unattended-schedules.d.ts +15 -0
- package/dist/mcp/unattended-schedules.js +21 -0
- package/dist/mcp/webhooks/ingress.d.ts +17 -0
- package/dist/mcp/webhooks/ingress.js +33 -0
- package/dist/observability/config.d.ts +6 -0
- package/dist/observability/config.js +20 -0
- package/dist/observability/metrics-server.d.ts +11 -0
- package/dist/observability/metrics-server.js +47 -0
- package/dist/observability/metrics.d.ts +36 -0
- package/dist/observability/metrics.js +158 -0
- package/dist/providers/auth/authorization-server.d.ts +41 -0
- package/dist/providers/auth/authorization-server.js +1 -0
- package/dist/providers/auth/delegating.d.ts +44 -0
- package/dist/providers/auth/delegating.js +105 -0
- package/dist/providers/auth/index.d.ts +11 -0
- package/dist/providers/auth/index.js +20 -0
- package/dist/providers/auth/release-gate.d.ts +1 -0
- package/dist/providers/auth/release-gate.js +4 -0
- package/dist/providers/auth/self-hosted.d.ts +78 -0
- package/dist/providers/auth/self-hosted.js +377 -0
- package/dist/providers/auth/subject.d.ts +1 -0
- package/dist/providers/auth/subject.js +6 -0
- package/dist/providers/auth/types.d.ts +63 -0
- package/dist/providers/auth/types.js +22 -0
- package/dist/providers/coding-proxy/deny-port.d.ts +5 -0
- package/dist/providers/coding-proxy/deny-port.js +45 -0
- package/dist/providers/coding-proxy/environment-credentials.d.ts +7 -0
- package/dist/providers/coding-proxy/environment-credentials.js +16 -0
- package/dist/providers/coding-proxy/index.d.ts +7 -0
- package/dist/providers/coding-proxy/index.js +7 -0
- package/dist/providers/coding-proxy/memory-ledger.d.ts +17 -0
- package/dist/providers/coding-proxy/memory-ledger.js +103 -0
- package/dist/providers/coding-proxy/metering.d.ts +20 -0
- package/dist/providers/coding-proxy/metering.js +161 -0
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +15 -0
- package/dist/providers/coding-proxy/prisma-ledger.js +213 -0
- package/dist/providers/coding-proxy/proxy.d.ts +75 -0
- package/dist/providers/coding-proxy/proxy.js +721 -0
- package/dist/providers/coding-proxy/runtime.d.ts +18 -0
- package/dist/providers/coding-proxy/runtime.js +39 -0
- package/dist/providers/coding-proxy/secure-fetch.d.ts +9 -0
- package/dist/providers/coding-proxy/secure-fetch.js +72 -0
- package/dist/providers/coding-proxy/server.d.ts +17 -0
- package/dist/providers/coding-proxy/server.js +139 -0
- package/dist/providers/coding-proxy/types.d.ts +99 -0
- package/dist/providers/coding-proxy/types.js +1 -0
- package/dist/providers/datastore/index.d.ts +2 -0
- package/dist/providers/datastore/index.js +2 -0
- package/dist/providers/datastore/postgres.d.ts +20 -0
- package/dist/providers/datastore/postgres.js +130 -0
- package/dist/providers/datastore/scoped.d.ts +10 -0
- package/dist/providers/datastore/scoped.js +46 -0
- package/dist/providers/datastore/types.d.ts +31 -0
- package/dist/providers/datastore/types.js +7 -0
- package/dist/providers/email/types.d.ts +46 -0
- package/dist/providers/email/types.js +8 -0
- package/dist/providers/engine/index.d.ts +1 -0
- package/dist/providers/engine/index.js +1 -0
- package/dist/providers/engine/types.d.ts +67 -0
- package/dist/providers/engine/types.js +16 -0
- package/dist/providers/executor/build.d.ts +6 -0
- package/dist/providers/executor/build.js +15 -0
- package/dist/providers/executor/composition.d.ts +14 -0
- package/dist/providers/executor/composition.js +123 -0
- package/dist/providers/executor/container.d.ts +184 -0
- package/dist/providers/executor/container.js +936 -0
- package/dist/providers/executor/dbos-status.d.ts +50 -0
- package/dist/providers/executor/dbos-status.js +64 -0
- package/dist/providers/executor/dbos.d.ts +53 -0
- package/dist/providers/executor/dbos.js +269 -0
- package/dist/providers/executor/in-process.d.ts +18 -0
- package/dist/providers/executor/in-process.js +38 -0
- package/dist/providers/executor/index.d.ts +8 -0
- package/dist/providers/executor/index.js +8 -0
- package/dist/providers/executor/routing.d.ts +48 -0
- package/dist/providers/executor/routing.js +70 -0
- package/dist/providers/executor/types.d.ts +55 -0
- package/dist/providers/executor/types.js +1 -0
- package/dist/providers/index.d.ts +43 -0
- package/dist/providers/index.js +18 -0
- package/dist/providers/jobs/docker-isolation.d.ts +166 -0
- package/dist/providers/jobs/docker-isolation.js +724 -0
- package/dist/providers/jobs/docker.d.ts +127 -0
- package/dist/providers/jobs/docker.js +1027 -0
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +98 -0
- package/dist/providers/jobs/fake-kubernetes-api.js +142 -0
- package/dist/providers/jobs/fake.d.ts +23 -0
- package/dist/providers/jobs/fake.js +132 -0
- package/dist/providers/jobs/kubernetes-api.d.ts +60 -0
- package/dist/providers/jobs/kubernetes-api.js +20 -0
- package/dist/providers/jobs/kubernetes-client.d.ts +36 -0
- package/dist/providers/jobs/kubernetes-client.js +171 -0
- package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +49 -0
- package/dist/providers/jobs/kubernetes-dry-run-fixture.js +141 -0
- package/dist/providers/jobs/kubernetes-isolation.d.ts +147 -0
- package/dist/providers/jobs/kubernetes-isolation.js +607 -0
- package/dist/providers/jobs/kubernetes-platform.d.ts +177 -0
- package/dist/providers/jobs/kubernetes-platform.js +280 -0
- package/dist/providers/jobs/kubernetes-preflight.d.ts +59 -0
- package/dist/providers/jobs/kubernetes-preflight.js +339 -0
- package/dist/providers/jobs/kubernetes-witness.d.ts +10 -0
- package/dist/providers/jobs/kubernetes-witness.js +193 -0
- package/dist/providers/jobs/kubernetes.d.ts +127 -0
- package/dist/providers/jobs/kubernetes.js +874 -0
- package/dist/providers/jobs/safe-extract.d.ts +9 -0
- package/dist/providers/jobs/safe-extract.js +325 -0
- package/dist/providers/jobs/types.d.ts +78 -0
- package/dist/providers/jobs/types.js +6 -0
- package/dist/providers/jobs/workspace-swap.d.ts +2 -0
- package/dist/providers/jobs/workspace-swap.js +47 -0
- package/dist/providers/llm/anthropic.d.ts +13 -0
- package/dist/providers/llm/anthropic.js +21 -0
- package/dist/providers/llm/bedrock.d.ts +19 -0
- package/dist/providers/llm/bedrock.js +33 -0
- package/dist/providers/llm/claude-messages.d.ts +106 -0
- package/dist/providers/llm/claude-messages.js +157 -0
- package/dist/providers/llm/claude-provider.d.ts +40 -0
- package/dist/providers/llm/claude-provider.js +31 -0
- package/dist/providers/llm/index.d.ts +10 -0
- package/dist/providers/llm/index.js +10 -0
- package/dist/providers/llm/openai.d.ts +23 -0
- package/dist/providers/llm/openai.js +156 -0
- package/dist/providers/llm/pricing-anthropic.d.ts +4 -0
- package/dist/providers/llm/pricing-anthropic.js +30 -0
- package/dist/providers/llm/pricing-bedrock-claude.d.ts +12 -0
- package/dist/providers/llm/pricing-bedrock-claude.js +37 -0
- package/dist/providers/llm/pricing-core.d.ts +26 -0
- package/dist/providers/llm/pricing-core.js +17 -0
- package/dist/providers/llm/pricing.d.ts +30 -0
- package/dist/providers/llm/pricing.js +74 -0
- package/dist/providers/llm/registration.d.ts +8 -0
- package/dist/providers/llm/registration.js +39 -0
- package/dist/providers/llm/routing.d.ts +25 -0
- package/dist/providers/llm/routing.js +32 -0
- package/dist/providers/llm/types.d.ts +85 -0
- package/dist/providers/llm/types.js +12 -0
- package/dist/providers/memory/index.d.ts +2 -0
- package/dist/providers/memory/index.js +2 -0
- package/dist/providers/memory/postgres.d.ts +20 -0
- package/dist/providers/memory/postgres.js +72 -0
- package/dist/providers/memory/types.d.ts +32 -0
- package/dist/providers/memory/types.js +17 -0
- package/dist/providers/secrets/app-key.d.ts +8 -0
- package/dist/providers/secrets/app-key.js +50 -0
- package/dist/providers/secrets/index.d.ts +10 -0
- package/dist/providers/secrets/index.js +13 -0
- package/dist/providers/secrets/transfer-envelope.d.ts +12 -0
- package/dist/providers/secrets/transfer-envelope.js +35 -0
- package/dist/providers/secrets/types.d.ts +18 -0
- package/dist/providers/secrets/types.js +12 -0
- package/dist/providers/storage/types.d.ts +18 -0
- package/dist/providers/storage/types.js +7 -0
- package/dist/providers/vcs/git.d.ts +105 -0
- package/dist/providers/vcs/git.js +710 -0
- package/dist/providers/vcs/github.d.ts +105 -0
- package/dist/providers/vcs/github.js +394 -0
- package/dist/providers/vcs/index.d.ts +6 -0
- package/dist/providers/vcs/index.js +26 -0
- package/dist/providers/vcs/types.d.ts +118 -0
- package/dist/providers/vcs/types.js +1 -0
- package/dist/sandbox/bounded-json.d.ts +2 -0
- package/dist/sandbox/bounded-json.js +36 -0
- package/dist/sandbox/bridge.d.ts +15 -0
- package/dist/sandbox/bridge.js +78 -0
- package/dist/sandbox/eval-core.d.ts +37 -0
- package/dist/sandbox/eval-core.js +159 -0
- package/dist/sandbox/fetch-policy.d.ts +24 -0
- package/dist/sandbox/fetch-policy.js +96 -0
- package/dist/sandbox/generated/zod-to-json-schema.bundle.js +5 -0
- package/dist/sandbox/generated/zod.bundle.js +1 -0
- package/dist/sandbox/host-functions.d.ts +29 -0
- package/dist/sandbox/host-functions.js +168 -0
- package/dist/sandbox/limits.d.ts +39 -0
- package/dist/sandbox/limits.js +39 -0
- package/dist/sandbox/parser-worker/__fixtures__/crash.d.ts +1 -0
- package/dist/sandbox/parser-worker/__fixtures__/crash.js +6 -0
- package/dist/sandbox/parser-worker/__fixtures__/echo.d.ts +1 -0
- package/dist/sandbox/parser-worker/__fixtures__/echo.js +5 -0
- package/dist/sandbox/parser-worker/__fixtures__/reject.d.ts +1 -0
- package/dist/sandbox/parser-worker/__fixtures__/reject.js +5 -0
- package/dist/sandbox/parser-worker/__fixtures__/spin-forever.d.ts +1 -0
- package/dist/sandbox/parser-worker/__fixtures__/spin-forever.js +11 -0
- package/dist/sandbox/parser-worker/pool.d.ts +13 -0
- package/dist/sandbox/parser-worker/pool.js +111 -0
- package/dist/sandbox/parser-worker/worker.d.ts +14 -0
- package/dist/sandbox/parser-worker/worker.js +63 -0
- package/dist/sandbox/pii-redaction.d.ts +1 -0
- package/dist/sandbox/pii-redaction.js +29 -0
- package/dist/sandbox/prelude.d.ts +26 -0
- package/dist/sandbox/prelude.js +408 -0
- package/dist/sandbox/quickjs-module.d.ts +13 -0
- package/dist/sandbox/quickjs-module.js +19 -0
- package/dist/sandbox/run-in-sandbox.d.ts +34 -0
- package/dist/sandbox/run-in-sandbox.js +53 -0
- package/dist/sandbox/safe-fetch.d.ts +26 -0
- package/dist/sandbox/safe-fetch.js +140 -0
- package/dist/sandbox/tool-capabilities.d.ts +66 -0
- package/dist/sandbox/tool-capabilities.js +103 -0
- package/dist/sandbox/zod-params.d.ts +22 -0
- package/dist/sandbox/zod-params.js +60 -0
- package/dist/serve.d.ts +14 -0
- package/dist/serve.js +54 -0
- package/dist/wardby-bin.d.ts +2 -0
- package/dist/wardby-bin.js +20 -0
- package/package.json +123 -0
- package/prisma/migrations/20260905000000_init/migration.sql +34 -0
- package/prisma/migrations/20260905010000_scheduler_durability/migration.sql +31 -0
- package/prisma/migrations/20260905020000_tools_multiturn/migration.sql +47 -0
- package/prisma/migrations/20260905030000_cache_tool_json_schema/migration.sql +13 -0
- package/prisma/migrations/20260906000000_mcp_phase4/migration.sql +115 -0
- package/prisma/migrations/20260906010000_run_final_text/migration.sql +7 -0
- package/prisma/migrations/20260906020000_run_turns/migration.sql +6 -0
- package/prisma/migrations/20260906030000_task_principal_id/migration.sql +5 -0
- package/prisma/migrations/20260906040000_secure_self_hosted_oauth/migration.sql +67 -0
- package/prisma/migrations/20260906050000_phase5_coding_agents/migration.sql +52 -0
- package/prisma/migrations/20260906060000_tool_capability_scoping/migration.sql +22 -0
- package/prisma/migrations/20260906070000_datastore_pii_flag/migration.sql +6 -0
- package/prisma/migrations/20260906080000_secret_elicitation_outcomes/migration.sql +13 -0
- package/prisma/migrations/20260907010000_coding_proxy_ledger/migration.sql +57 -0
- package/prisma/migrations/20260907020000_budget_groups/migration.sql +24 -0
- package/prisma/migrations/20260907030000_coding_webhook_task_override/migration.sql +4 -0
- package/prisma/migrations/20260908010000_run_execution_backend/migration.sql +2 -0
- package/prisma/migrations/20260908020000_coding_run_audit_metadata/migration.sql +7 -0
- package/prisma/migrations/20260910010000_agent_secret_bound_name/migration.sql +16 -0
- package/prisma/migrations/20260911010000_coding_worker_toolchains/migration.sql +5 -0
- package/prisma/migrations/20260912010000_coding_provider_contract/migration.sql +24 -0
- package/prisma/migrations/20260912020000_coding_proxy_protocol/migration.sql +10 -0
- package/prisma/migrations/20260912030000_agent_memory/migration.sql +18 -0
- package/prisma/migrations/20260912040000_named_shared_datastores/migration.sql +68 -0
- package/prisma/migrations/20260913000000_run_trigger_webhook/migration.sql +2 -0
- package/prisma/migrations/20260913010000_agent_sub_agent/migration.sql +32 -0
- package/prisma/migrations/20260913020000_run_task_override/migration.sql +5 -0
- package/prisma/migrations/20260913030000_coding_run_root/migration.sql +11 -0
- package/prisma/migrations/20260922010000_coding_run_queued_at/migration.sql +8 -0
- package/prisma/migrations/20260922020000_coding_workspace_disk/migration.sql +8 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +723 -0
- package/scripts/git-askpass.sh +11 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Readable } from "node:stream";
|
|
2
|
+
export interface SafeExtractLimits {
|
|
3
|
+
maxBytes: number;
|
|
4
|
+
maxEntries: number;
|
|
5
|
+
}
|
|
6
|
+
export interface SafeExtractOptions {
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
}
|
|
9
|
+
export declare function safeExtract(source: Readable, root: string, limits: SafeExtractLimits, options?: SafeExtractOptions): Promise<void>;
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { chmod, lstat, mkdir, open, readlink, realpath, symlink as createSymlink } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, resolve, sep } from "node:path";
|
|
3
|
+
import tar from "tar-stream";
|
|
4
|
+
const MAX_SYMLINK_HOPS = 40;
|
|
5
|
+
function fail(code) {
|
|
6
|
+
throw new Error(code);
|
|
7
|
+
}
|
|
8
|
+
/** "./a/b" -> "a/b"; "./" -> "" (the root itself). Rejects anything that could leave the root. */
|
|
9
|
+
function normalize(name) {
|
|
10
|
+
if (name.includes("\0") || name.startsWith("/"))
|
|
11
|
+
fail("extract_path_invalid");
|
|
12
|
+
let path = name.replace(/^(\.\/)+/, "").replace(/\/+$/, "");
|
|
13
|
+
if (path === ".")
|
|
14
|
+
path = "";
|
|
15
|
+
if (path === "")
|
|
16
|
+
return "";
|
|
17
|
+
const segments = path.split("/");
|
|
18
|
+
if (segments.some((s) => s === "" || s === "." || s === ".."))
|
|
19
|
+
fail("extract_path_invalid");
|
|
20
|
+
return path;
|
|
21
|
+
}
|
|
22
|
+
function inside(root, target) {
|
|
23
|
+
return target === root || target.startsWith(`${root}${sep}`);
|
|
24
|
+
}
|
|
25
|
+
function executableMode(headerMode) {
|
|
26
|
+
return (headerMode ?? 0) & 0o111 ? 0o755 : 0o644;
|
|
27
|
+
}
|
|
28
|
+
/** Maps any error that escaped our own fixed extract_* codes to a path-free fallback. */
|
|
29
|
+
function mapError(err) {
|
|
30
|
+
if (err instanceof Error && /^extract_/.test(err.message))
|
|
31
|
+
return err;
|
|
32
|
+
const code = err?.code;
|
|
33
|
+
if (code === "EEXIST" || code === "ENOTDIR" || code === "ENOTEMPTY")
|
|
34
|
+
return new Error("extract_duplicate_entry");
|
|
35
|
+
return new Error("extract_io");
|
|
36
|
+
}
|
|
37
|
+
/** Lexically applies path components ("." skip, ".." pop, else append) — no filesystem access. */
|
|
38
|
+
function applyLexically(base, components) {
|
|
39
|
+
let current = base;
|
|
40
|
+
for (const component of components) {
|
|
41
|
+
if (component === "" || component === ".")
|
|
42
|
+
continue;
|
|
43
|
+
if (component === "..") {
|
|
44
|
+
current = dirname(current);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
current = current === sep ? `${sep}${component}` : `${current}${sep}${component}`;
|
|
48
|
+
}
|
|
49
|
+
return current;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolves `components` starting from the already-real `startReal`, like realpath but tolerant of a
|
|
53
|
+
* dangling tail: once a component doesn't exist (or a non-final one isn't a directory), the rest is
|
|
54
|
+
* applied lexically instead of failing. Any symlink encountered along the way is followed for real
|
|
55
|
+
* (readlink + splice its target in front of what's left), which is what catches a chain of in-tree
|
|
56
|
+
* symlinks that composes into a physical escape even though each hop looks fine on its own.
|
|
57
|
+
*/
|
|
58
|
+
async function resolveWalk(startReal, components) {
|
|
59
|
+
let current = startReal;
|
|
60
|
+
let remaining = components.slice();
|
|
61
|
+
let hops = 0;
|
|
62
|
+
while (remaining.length > 0) {
|
|
63
|
+
const component = remaining.shift();
|
|
64
|
+
if (component === "" || component === ".")
|
|
65
|
+
continue;
|
|
66
|
+
if (component === "..") {
|
|
67
|
+
current = dirname(current);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const candidate = current === sep ? `${sep}${component}` : `${current}${sep}${component}`;
|
|
71
|
+
let stats;
|
|
72
|
+
try {
|
|
73
|
+
stats = await lstat(candidate);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
const code = err.code;
|
|
77
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
78
|
+
return applyLexically(current, [component, ...remaining]);
|
|
79
|
+
}
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
if (stats.isSymbolicLink()) {
|
|
83
|
+
hops += 1;
|
|
84
|
+
if (hops > MAX_SYMLINK_HOPS)
|
|
85
|
+
fail("extract_symlink_escape");
|
|
86
|
+
const linkTarget = await readlink(candidate);
|
|
87
|
+
if (isAbsolute(linkTarget))
|
|
88
|
+
fail("extract_symlink_escape");
|
|
89
|
+
remaining = linkTarget.split("/").concat(remaining);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
current = candidate;
|
|
93
|
+
}
|
|
94
|
+
return current;
|
|
95
|
+
}
|
|
96
|
+
export async function safeExtract(source, root, limits, options) {
|
|
97
|
+
const base = resolve(root);
|
|
98
|
+
const recordedSymlinks = new Map();
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
let entries = 0;
|
|
101
|
+
let bytes = 0;
|
|
102
|
+
const extract = tar.extract();
|
|
103
|
+
let rejectFailed;
|
|
104
|
+
const failed = new Promise((_resolve, reject) => {
|
|
105
|
+
rejectFailed = reject;
|
|
106
|
+
});
|
|
107
|
+
failed.catch(() => undefined);
|
|
108
|
+
const onStreamError = (err) => rejectFailed(err);
|
|
109
|
+
extract.once("error", onStreamError);
|
|
110
|
+
source.once("error", onStreamError);
|
|
111
|
+
const signal = options?.signal;
|
|
112
|
+
const onAbort = () => rejectFailed(new Error("extract_aborted"));
|
|
113
|
+
if (signal) {
|
|
114
|
+
if (signal.aborted)
|
|
115
|
+
onAbort();
|
|
116
|
+
else
|
|
117
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
118
|
+
}
|
|
119
|
+
function guard(operation) {
|
|
120
|
+
return Promise.race([operation, failed]);
|
|
121
|
+
}
|
|
122
|
+
// Created (and so subscribed to tar-stream's internal 'entry'/'close' events) before any `await`,
|
|
123
|
+
// including the realpath below: tar-stream's async iterator only delivers an entry to listeners that
|
|
124
|
+
// were attached before it was emitted, so creating this after an async gap can silently miss the very
|
|
125
|
+
// first entry and hang forever waiting for one that already came and went.
|
|
126
|
+
const extractIterator = extract[Symbol.asyncIterator]();
|
|
127
|
+
source.pipe(extract);
|
|
128
|
+
try {
|
|
129
|
+
const rootReal = await guard(realpath(base));
|
|
130
|
+
/**
|
|
131
|
+
* Creates (if needed) and verifies every directory named by `segments`, one component at a time,
|
|
132
|
+
* starting from `rootReal`, and returns the resulting real path. For each component: `lstat` it; if
|
|
133
|
+
* it doesn't exist, `mkdir` just that one component (non-recursive — its parent was already verified
|
|
134
|
+
* real by the previous iteration), re-`lstat`ing on a raced `EEXIST`. Then — whether it already
|
|
135
|
+
* existed or was just created — `lstat` it again: if that's a symlink, `extract_through_symlink`
|
|
136
|
+
* (this is what stops phase 2a from creating or `chmod`ing through a symlink an *earlier* entry in
|
|
137
|
+
* this same extraction created, including one that's only a case-insensitive alias of the component
|
|
138
|
+
* being resolved); if it exists but isn't a directory at all, `extract_duplicate_entry` (preserves
|
|
139
|
+
* the existing "file blocking a nested path" behavior). Only once a component is freshly confirmed by
|
|
140
|
+
* `lstat` to be a real, non-symlink directory does it get `chmod`'d to 0o755 — never by a path that
|
|
141
|
+
* was merely assumed to still be what an earlier check saw.
|
|
142
|
+
*/
|
|
143
|
+
async function ensureDirectory(segments) {
|
|
144
|
+
let current = rootReal;
|
|
145
|
+
for (const component of segments) {
|
|
146
|
+
const child = current === sep ? `${sep}${component}` : `${current}${sep}${component}`;
|
|
147
|
+
let exists = true;
|
|
148
|
+
try {
|
|
149
|
+
await guard(lstat(child));
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
if (err.code !== "ENOENT")
|
|
153
|
+
throw err;
|
|
154
|
+
exists = false;
|
|
155
|
+
}
|
|
156
|
+
if (!exists) {
|
|
157
|
+
try {
|
|
158
|
+
await guard(mkdir(child, { mode: 0o755 }));
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
if (err.code !== "EEXIST")
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const stats = await guard(lstat(child));
|
|
166
|
+
if (stats.isSymbolicLink())
|
|
167
|
+
fail("extract_through_symlink");
|
|
168
|
+
if (!stats.isDirectory())
|
|
169
|
+
fail("extract_duplicate_entry");
|
|
170
|
+
await guard(chmod(child, 0o755));
|
|
171
|
+
current = child;
|
|
172
|
+
}
|
|
173
|
+
return current;
|
|
174
|
+
}
|
|
175
|
+
// Phase 1 (streaming): create directories and regular files; validate and record symlinks.
|
|
176
|
+
for (;;) {
|
|
177
|
+
const step = await guard(extractIterator.next());
|
|
178
|
+
if (step.done)
|
|
179
|
+
break;
|
|
180
|
+
const entry = step.value;
|
|
181
|
+
const { header } = entry;
|
|
182
|
+
entries += 1;
|
|
183
|
+
if (entries > limits.maxEntries) {
|
|
184
|
+
entry.resume();
|
|
185
|
+
fail("extract_entry_limit");
|
|
186
|
+
}
|
|
187
|
+
const path = normalize(header.name);
|
|
188
|
+
if (path === "") {
|
|
189
|
+
entry.resume();
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const segments = path.split("/");
|
|
193
|
+
for (let i = 1; i < segments.length; i += 1) {
|
|
194
|
+
if (recordedSymlinks.has(segments.slice(0, i).join("/"))) {
|
|
195
|
+
entry.resume();
|
|
196
|
+
fail("extract_through_symlink");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (seen.has(path)) {
|
|
200
|
+
entry.resume();
|
|
201
|
+
fail("extract_duplicate_entry");
|
|
202
|
+
}
|
|
203
|
+
seen.add(path);
|
|
204
|
+
const target = resolve(base, path);
|
|
205
|
+
if (!inside(base, target)) {
|
|
206
|
+
entry.resume();
|
|
207
|
+
fail("extract_path_invalid");
|
|
208
|
+
}
|
|
209
|
+
// Count every entry type's declared size before draining/consuming its body: a symlink (or any
|
|
210
|
+
// other type) can carry a body up to its declared size, and this must be rejected before we ever
|
|
211
|
+
// read a byte of it, not after.
|
|
212
|
+
const declaredSize = header.size ?? 0;
|
|
213
|
+
if (bytes + declaredSize > limits.maxBytes) {
|
|
214
|
+
entry.resume();
|
|
215
|
+
fail("extract_size_limit");
|
|
216
|
+
}
|
|
217
|
+
bytes += declaredSize;
|
|
218
|
+
switch (header.type) {
|
|
219
|
+
case "directory": {
|
|
220
|
+
entry.resume();
|
|
221
|
+
try {
|
|
222
|
+
await ensureDirectory(segments);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
throw mapError(err);
|
|
226
|
+
}
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
case "file":
|
|
230
|
+
case "contiguous-file": {
|
|
231
|
+
if (entry.destroyed)
|
|
232
|
+
throw mapError(new Error("entry stream destroyed"));
|
|
233
|
+
let fh;
|
|
234
|
+
try {
|
|
235
|
+
const parentReal = await ensureDirectory(segments.slice(0, -1));
|
|
236
|
+
const fileTarget = resolve(parentReal, segments[segments.length - 1]);
|
|
237
|
+
const openPromise = open(fileTarget, "wx", 0o644);
|
|
238
|
+
try {
|
|
239
|
+
fh = await guard(openPromise);
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
// If `failed` won the race, `open()` may still resolve later with a real handle that
|
|
243
|
+
// nothing else will ever close — close it then. Harmless (and a no-op) if `open()` itself
|
|
244
|
+
// is what rejected instead, since then it never produced a handle at all.
|
|
245
|
+
openPromise.then((handle) => handle.close().catch(() => undefined)).catch(() => undefined);
|
|
246
|
+
throw err;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
throw mapError(err);
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
let streamed = 0;
|
|
254
|
+
const bodyIterator = entry[Symbol.asyncIterator]();
|
|
255
|
+
for (;;) {
|
|
256
|
+
const chunkStep = await guard(bodyIterator.next());
|
|
257
|
+
if (chunkStep.done)
|
|
258
|
+
break;
|
|
259
|
+
const chunk = chunkStep.value;
|
|
260
|
+
streamed += chunk.byteLength;
|
|
261
|
+
if (streamed > limits.maxBytes)
|
|
262
|
+
fail("extract_size_limit");
|
|
263
|
+
await guard(fh.write(chunk));
|
|
264
|
+
}
|
|
265
|
+
await guard(fh.chmod(executableMode(header.mode)));
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
await fh.close().catch(() => undefined);
|
|
269
|
+
}
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
case "symlink": {
|
|
273
|
+
entry.resume();
|
|
274
|
+
const link = header.linkname ?? "";
|
|
275
|
+
if (link === "" || link.startsWith("/") || !inside(base, resolve(dirname(target), link))) {
|
|
276
|
+
fail("extract_symlink_escape");
|
|
277
|
+
}
|
|
278
|
+
recordedSymlinks.set(path, link);
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
default:
|
|
282
|
+
entry.resume();
|
|
283
|
+
fail("extract_special_entry");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Phase 2a: materialize every recorded symlink, now that the real tree is final. `ensureDirectory`
|
|
287
|
+
// rejects (extract_through_symlink) rather than create or chmod through a symlink an earlier
|
|
288
|
+
// iteration of this same loop created — including one that's merely a case-insensitive alias of the
|
|
289
|
+
// parent this symlink names — and a same-path collision with a phase-1-created path surfaces as
|
|
290
|
+
// EEXIST from `symlink()` itself, mapped to extract_duplicate_entry.
|
|
291
|
+
for (const [path, link] of recordedSymlinks) {
|
|
292
|
+
const segments = path.split("/");
|
|
293
|
+
try {
|
|
294
|
+
const parentReal = await ensureDirectory(segments.slice(0, -1));
|
|
295
|
+
const target = resolve(parentReal, segments[segments.length - 1]);
|
|
296
|
+
await guard(createSymlink(link, target));
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
throw mapError(err);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Phase 2b: validate every created symlink against the real filesystem. A lexical check can't see a
|
|
303
|
+
// chain of in-tree symlinks (e.g. s -> "..", t -> "s/..") whose composed, physically-resolved target
|
|
304
|
+
// lands outside root even though each link's own text looks contained.
|
|
305
|
+
for (const [path, link] of recordedSymlinks) {
|
|
306
|
+
const segments = path.split("/");
|
|
307
|
+
const parentReal = await guard(resolveWalk(rootReal, segments.slice(0, -1)));
|
|
308
|
+
const finalReal = await guard(resolveWalk(parentReal, link.split("/")));
|
|
309
|
+
if (!(finalReal === rootReal || finalReal.startsWith(`${rootReal}${sep}`))) {
|
|
310
|
+
fail("extract_symlink_escape");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
source.destroy();
|
|
316
|
+
extract.destroy();
|
|
317
|
+
throw mapError(err);
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
if (signal)
|
|
321
|
+
signal.removeEventListener("abort", onAbort);
|
|
322
|
+
extract.removeListener("error", onStreamError);
|
|
323
|
+
source.removeListener("error", onStreamError);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trusted control-plane contract for isolated coding jobs. User-controlled
|
|
3
|
+
* values are carried in a versioned input artifact, never expanded into an
|
|
4
|
+
* arbitrary environment map or shell command.
|
|
5
|
+
*/
|
|
6
|
+
export interface JobResourceLimits {
|
|
7
|
+
cpus: number;
|
|
8
|
+
memoryMb: number;
|
|
9
|
+
pids: number;
|
|
10
|
+
diskMb: number;
|
|
11
|
+
}
|
|
12
|
+
export interface JobSpec {
|
|
13
|
+
kind: "coding-agent";
|
|
14
|
+
runId: string;
|
|
15
|
+
/** Omitted specs are legacy Codex jobs and retain the single-worker topology. */
|
|
16
|
+
provider?: "codex" | "claude-code";
|
|
17
|
+
image: string;
|
|
18
|
+
/** Required only for Claude's credential-free repository tool container. */
|
|
19
|
+
toolImage?: string;
|
|
20
|
+
inputArtifact: string;
|
|
21
|
+
timeoutSec: number;
|
|
22
|
+
limits: JobResourceLimits;
|
|
23
|
+
labels: Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
/** Opaque provider reference. Callers persist both fields exactly as given. */
|
|
26
|
+
export interface JobHandle {
|
|
27
|
+
backend: string;
|
|
28
|
+
id: string;
|
|
29
|
+
}
|
|
30
|
+
export type JobStatus = {
|
|
31
|
+
state: "pending" | "running";
|
|
32
|
+
} | {
|
|
33
|
+
state: "succeeded";
|
|
34
|
+
} | {
|
|
35
|
+
state: "failed";
|
|
36
|
+
reason?: string;
|
|
37
|
+
} | {
|
|
38
|
+
state: "stopped";
|
|
39
|
+
} | {
|
|
40
|
+
state: "lost";
|
|
41
|
+
};
|
|
42
|
+
export interface JobResult {
|
|
43
|
+
exitCode: number;
|
|
44
|
+
reason: "completed" | "failed" | "stopped" | "timed_out" | "lost";
|
|
45
|
+
resultArtifact?: string;
|
|
46
|
+
/** Fixed worker-owned failure code; never raw container output. */
|
|
47
|
+
diagnostic?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Lifecycle invariants implemented by every launcher:
|
|
51
|
+
*
|
|
52
|
+
* - `launch` is idempotent by runId for an identical spec and rejects a
|
|
53
|
+
* different spec for the same runId.
|
|
54
|
+
* - pending may become running or terminal; running may become terminal;
|
|
55
|
+
* terminal states never change.
|
|
56
|
+
* - `collect` is repeatable for a terminal job until `remove` succeeds.
|
|
57
|
+
* - `stop` and `remove` are idempotent and best effort; removing an active
|
|
58
|
+
* job is rejected so cleanup cannot silently become an implicit stop.
|
|
59
|
+
* - no operation implicitly relaunches a missing, stopped, or removed job.
|
|
60
|
+
*/
|
|
61
|
+
export interface JobLauncher {
|
|
62
|
+
/**
|
|
63
|
+
* The handle `launch(spec)` will return, when a backend can derive it from the spec alone with no
|
|
64
|
+
* side effects. Callers persist it *before* launching, so a crash mid-launch still leaves a handle
|
|
65
|
+
* to stop and remove the run's resources with. `launch` must return an equal handle. Backends that
|
|
66
|
+
* cannot know the handle in advance omit this, and callers persist only after `launch` returns.
|
|
67
|
+
*/
|
|
68
|
+
plannedHandle?: (spec: JobSpec) => JobHandle | undefined;
|
|
69
|
+
launch: (spec: JobSpec) => Promise<JobHandle>;
|
|
70
|
+
status: (handle: JobHandle) => Promise<JobStatus>;
|
|
71
|
+
collect: (handle: JobHandle) => Promise<JobResult>;
|
|
72
|
+
stop: (handle: JobHandle, reason?: string) => Promise<void>;
|
|
73
|
+
remove: (handle: JobHandle) => Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
/** A launcher that can safely copy a terminal worker workspace back to trusted storage. */
|
|
76
|
+
export interface WorkspaceJobLauncher extends JobLauncher {
|
|
77
|
+
materializeWorkspace: (handle: JobHandle, destination: string) => Promise<void>;
|
|
78
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** Fills a fresh staging directory next to `destination`, validates it, then atomically swaps it into place. */
|
|
2
|
+
export declare function replaceDirectoryFromStaging(destination: string, maxBytes: number, destinationInvalidError: string, fill: (staging: string) => Promise<void>): Promise<void>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replaces a trusted workspace directory with a freshly filled copy, shared by
|
|
3
|
+
* the Docker and Kubernetes launchers. The copy is filled into a staging
|
|
4
|
+
* directory beside the destination, validated, and only then swapped into
|
|
5
|
+
* place by rename, with the original restored if the swap fails.
|
|
6
|
+
*
|
|
7
|
+
* A fill that rejects has, by definition, produced an untrusted tree (it may
|
|
8
|
+
* contain escaping symlinks): it is never walked, validated, or renamed — the
|
|
9
|
+
* staging directory is removed with `rm({ recursive, force })`, which unlinks
|
|
10
|
+
* symlinks rather than following them.
|
|
11
|
+
*/
|
|
12
|
+
import { lstat, mkdtemp, realpath, rename, rm } from "node:fs/promises";
|
|
13
|
+
import { dirname, join, resolve } from "node:path";
|
|
14
|
+
import { validateMaterializedWorkspace } from "./docker.js";
|
|
15
|
+
/** Fills a fresh staging directory next to `destination`, validates it, then atomically swaps it into place. */
|
|
16
|
+
export async function replaceDirectoryFromStaging(destination, maxBytes, destinationInvalidError, fill) {
|
|
17
|
+
const target = resolve(destination);
|
|
18
|
+
const parent = dirname(target);
|
|
19
|
+
const [parentReal, targetReal, targetStat] = await Promise.all([realpath(parent), realpath(target), lstat(target)]);
|
|
20
|
+
if (!targetStat.isDirectory() ||
|
|
21
|
+
targetStat.isSymbolicLink() ||
|
|
22
|
+
targetReal !== resolve(parentReal, target.slice(parent.length + 1))) {
|
|
23
|
+
throw new Error(destinationInvalidError);
|
|
24
|
+
}
|
|
25
|
+
const staging = await mkdtemp(join(parentReal, ".wardby-workspace-stage-"));
|
|
26
|
+
const backup = await mkdtemp(join(parentReal, ".wardby-workspace-backup-"));
|
|
27
|
+
await rm(backup, { recursive: true });
|
|
28
|
+
let targetMoved = false;
|
|
29
|
+
try {
|
|
30
|
+
await fill(staging);
|
|
31
|
+
await validateMaterializedWorkspace(staging, maxBytes);
|
|
32
|
+
await rename(targetReal, backup);
|
|
33
|
+
targetMoved = true;
|
|
34
|
+
await rename(staging, targetReal);
|
|
35
|
+
targetMoved = false;
|
|
36
|
+
await rm(backup, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (targetMoved)
|
|
40
|
+
await rename(backup, targetReal).catch(() => undefined);
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
await rm(staging, { recursive: true, force: true });
|
|
45
|
+
await rm(backup, { recursive: true, force: true });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct Anthropic (Claude) adapter for the LlmProvider seam. `@anthropic-ai/sdk`
|
|
3
|
+
* is imported only here; all protocol/pricing-dispatch logic lives in the
|
|
4
|
+
* shared ClaudeLlmProvider base class (claude-provider.ts) — this file's only
|
|
5
|
+
* job is constructing the right client and pricing module.
|
|
6
|
+
*/
|
|
7
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
8
|
+
import { ClaudeLlmProvider } from "./claude-provider.js";
|
|
9
|
+
export { anthropicSupportedModels } from "./pricing-anthropic.js";
|
|
10
|
+
export declare function anthropicCredentialsPresent(env?: NodeJS.ProcessEnv): boolean;
|
|
11
|
+
export declare class AnthropicLlmProvider extends ClaudeLlmProvider {
|
|
12
|
+
constructor(apiKey?: string, client?: Anthropic);
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct Anthropic (Claude) adapter for the LlmProvider seam. `@anthropic-ai/sdk`
|
|
3
|
+
* is imported only here; all protocol/pricing-dispatch logic lives in the
|
|
4
|
+
* shared ClaudeLlmProvider base class (claude-provider.ts) — this file's only
|
|
5
|
+
* job is constructing the right client and pricing module.
|
|
6
|
+
*/
|
|
7
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
8
|
+
import { ClaudeLlmProvider } from "./claude-provider.js";
|
|
9
|
+
import { anthropicPriceUsd, getAnthropicPricing } from "./pricing-anthropic.js";
|
|
10
|
+
export { anthropicSupportedModels } from "./pricing-anthropic.js";
|
|
11
|
+
export function anthropicCredentialsPresent(env = process.env) {
|
|
12
|
+
return Boolean(env.ANTHROPIC_API_KEY);
|
|
13
|
+
}
|
|
14
|
+
export class AnthropicLlmProvider extends ClaudeLlmProvider {
|
|
15
|
+
constructor(apiKey = process.env.ANTHROPIC_API_KEY ?? "", client) {
|
|
16
|
+
if (!client && !apiKey) {
|
|
17
|
+
throw new Error("ANTHROPIC_API_KEY is not set — required by the Anthropic LlmProvider adapter.");
|
|
18
|
+
}
|
|
19
|
+
super(client ?? new Anthropic({ apiKey }), { getPricing: getAnthropicPricing, priceUsd: anthropicPriceUsd });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bedrock-Claude adapter for the LlmProvider seam. `@anthropic-ai/bedrock-sdk`
|
|
3
|
+
* is imported only here; all protocol/pricing-dispatch logic lives in the
|
|
4
|
+
* shared ClaudeLlmProvider base class (claude-provider.ts) — AnthropicBedrock
|
|
5
|
+
* exposes the identical .messages.stream() surface as @anthropic-ai/sdk, so
|
|
6
|
+
* this file's only job is constructing the right client and pricing module.
|
|
7
|
+
*
|
|
8
|
+
* Named BedrockClaudeLlmProvider, not BedrockLlmProvider: Bedrock hosts
|
|
9
|
+
* multiple model vendors, and this adapter only ever speaks the
|
|
10
|
+
* Anthropic-native protocol for Claude models. A future non-Claude Bedrock
|
|
11
|
+
* adapter gets its own name, not a rename of this one.
|
|
12
|
+
*/
|
|
13
|
+
import { AnthropicBedrock } from "@anthropic-ai/bedrock-sdk";
|
|
14
|
+
import { ClaudeLlmProvider } from "./claude-provider.js";
|
|
15
|
+
export { bedrockClaudeSupportedModels } from "./pricing-bedrock-claude.js";
|
|
16
|
+
export declare function bedrockCredentialsPresent(env?: NodeJS.ProcessEnv): boolean;
|
|
17
|
+
export declare class BedrockClaudeLlmProvider extends ClaudeLlmProvider {
|
|
18
|
+
constructor(region?: string, client?: AnthropicBedrock);
|
|
19
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bedrock-Claude adapter for the LlmProvider seam. `@anthropic-ai/bedrock-sdk`
|
|
3
|
+
* is imported only here; all protocol/pricing-dispatch logic lives in the
|
|
4
|
+
* shared ClaudeLlmProvider base class (claude-provider.ts) — AnthropicBedrock
|
|
5
|
+
* exposes the identical .messages.stream() surface as @anthropic-ai/sdk, so
|
|
6
|
+
* this file's only job is constructing the right client and pricing module.
|
|
7
|
+
*
|
|
8
|
+
* Named BedrockClaudeLlmProvider, not BedrockLlmProvider: Bedrock hosts
|
|
9
|
+
* multiple model vendors, and this adapter only ever speaks the
|
|
10
|
+
* Anthropic-native protocol for Claude models. A future non-Claude Bedrock
|
|
11
|
+
* adapter gets its own name, not a rename of this one.
|
|
12
|
+
*/
|
|
13
|
+
import { AnthropicBedrock } from "@anthropic-ai/bedrock-sdk";
|
|
14
|
+
import { ClaudeLlmProvider } from "./claude-provider.js";
|
|
15
|
+
import { bedrockClaudePriceUsd, getBedrockClaudePricing } from "./pricing-bedrock-claude.js";
|
|
16
|
+
export { bedrockClaudeSupportedModels } from "./pricing-bedrock-claude.js";
|
|
17
|
+
export function bedrockCredentialsPresent(env = process.env) {
|
|
18
|
+
// A region is the one thing Bedrock always needs; AWS resolves actual
|
|
19
|
+
// credentials from the standard default chain (env keys, shared config,
|
|
20
|
+
// IAM role) — this only gates whether the adapter registers at all.
|
|
21
|
+
return Boolean(env.BEDROCK_REGION ?? env.AWS_REGION);
|
|
22
|
+
}
|
|
23
|
+
export class BedrockClaudeLlmProvider extends ClaudeLlmProvider {
|
|
24
|
+
constructor(region = process.env.BEDROCK_REGION ?? process.env.AWS_REGION ?? "", client) {
|
|
25
|
+
if (!client && !region) {
|
|
26
|
+
throw new Error("BEDROCK_REGION (or AWS_REGION) is required by the Bedrock Claude LlmProvider adapter.");
|
|
27
|
+
}
|
|
28
|
+
super(client ?? new AnthropicBedrock({ awsRegion: region }), {
|
|
29
|
+
getPricing: getBedrockClaudePricing,
|
|
30
|
+
priceUsd: bedrockClaudePriceUsd,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { LlmMessage, LlmRequest, LlmToolDef, LlmStreamEvent, LlmUsage } from "./types.js";
|
|
2
|
+
export interface CacheControl {
|
|
3
|
+
type: "ephemeral";
|
|
4
|
+
}
|
|
5
|
+
export interface ClaudeTextBlock {
|
|
6
|
+
type: "text";
|
|
7
|
+
text: string;
|
|
8
|
+
cache_control?: CacheControl;
|
|
9
|
+
}
|
|
10
|
+
export interface ClaudeToolUseBlock {
|
|
11
|
+
type: "tool_use";
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
input: unknown;
|
|
15
|
+
cache_control?: CacheControl;
|
|
16
|
+
}
|
|
17
|
+
export interface ClaudeToolResultBlock {
|
|
18
|
+
type: "tool_result";
|
|
19
|
+
tool_use_id: string;
|
|
20
|
+
content: string;
|
|
21
|
+
cache_control?: CacheControl;
|
|
22
|
+
}
|
|
23
|
+
export type ClaudeContentBlock = ClaudeTextBlock | ClaudeToolUseBlock | ClaudeToolResultBlock;
|
|
24
|
+
export interface ClaudeMessage {
|
|
25
|
+
role: "user" | "assistant";
|
|
26
|
+
content: ClaudeContentBlock[];
|
|
27
|
+
}
|
|
28
|
+
export interface ClaudeTool {
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
input_schema: Record<string, unknown>;
|
|
32
|
+
cache_control?: CacheControl;
|
|
33
|
+
}
|
|
34
|
+
export interface ClaudeRequest {
|
|
35
|
+
system?: ClaudeTextBlock[];
|
|
36
|
+
messages: ClaudeMessage[];
|
|
37
|
+
tools?: ClaudeTool[];
|
|
38
|
+
max_tokens: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Same shape sent to the API in `toClaudeRequest` — kept as one function so
|
|
42
|
+
* a pre-flight token estimate (AnthropicLlmProvider.countTokens) can never
|
|
43
|
+
* drift from what's actually serialized into the request. Mirrors the
|
|
44
|
+
* OpenAI adapter's `toOpenAiTools`, which guards against the same failure
|
|
45
|
+
* class: a duplicated tool-schema mapping once desynced the pre-flight
|
|
46
|
+
* estimate from the real request and broke the budget guardrail's turn-1
|
|
47
|
+
* refuse guarantee (measured 44-52% under-count).
|
|
48
|
+
*/
|
|
49
|
+
export declare function toClaudeTools(tools: LlmToolDef[]): ClaudeTool[];
|
|
50
|
+
/**
|
|
51
|
+
* Raw (pre-inflation) offline token estimate shared by every Claude-protocol
|
|
52
|
+
* adapter. Each caller applies its own inflation constant on top.
|
|
53
|
+
*/
|
|
54
|
+
export declare function estimateClaudeTokens(messages: LlmMessage[], tools?: LlmToolDef[]): number;
|
|
55
|
+
export declare function toClaudeRequest(req: LlmRequest, defaultMaxTokens: number): ClaudeRequest;
|
|
56
|
+
/**
|
|
57
|
+
* Two-breakpoint canonical caching. Anthropic's cache order is
|
|
58
|
+
* tools -> system -> messages, so a breakpoint on the last system block
|
|
59
|
+
* caches tools+system (static across the run). A rolling breakpoint on the
|
|
60
|
+
* last message's final block grows the conversation cache each turn.
|
|
61
|
+
* Sub-minimum prefixes (1024/2048 tokens) are silently un-cached by Anthropic.
|
|
62
|
+
*/
|
|
63
|
+
export declare function withCacheBreakpoints(req: ClaudeRequest): ClaudeRequest;
|
|
64
|
+
interface ClaudeUsage {
|
|
65
|
+
input_tokens?: number;
|
|
66
|
+
cache_read_input_tokens?: number;
|
|
67
|
+
cache_creation_input_tokens?: number;
|
|
68
|
+
output_tokens?: number;
|
|
69
|
+
}
|
|
70
|
+
export type ClaudeStreamEvent = {
|
|
71
|
+
type: "message_start";
|
|
72
|
+
message: {
|
|
73
|
+
usage: ClaudeUsage;
|
|
74
|
+
};
|
|
75
|
+
} | {
|
|
76
|
+
type: "content_block_start";
|
|
77
|
+
index: number;
|
|
78
|
+
content_block: {
|
|
79
|
+
type: "text" | "tool_use";
|
|
80
|
+
id?: string;
|
|
81
|
+
name?: string;
|
|
82
|
+
};
|
|
83
|
+
} | {
|
|
84
|
+
type: "content_block_delta";
|
|
85
|
+
index: number;
|
|
86
|
+
delta: {
|
|
87
|
+
type: "text_delta";
|
|
88
|
+
text: string;
|
|
89
|
+
} | {
|
|
90
|
+
type: "input_json_delta";
|
|
91
|
+
partial_json: string;
|
|
92
|
+
};
|
|
93
|
+
} | {
|
|
94
|
+
type: "content_block_stop";
|
|
95
|
+
index: number;
|
|
96
|
+
} | {
|
|
97
|
+
type: "message_delta";
|
|
98
|
+
delta: {
|
|
99
|
+
stop_reason?: string;
|
|
100
|
+
};
|
|
101
|
+
usage?: ClaudeUsage;
|
|
102
|
+
} | {
|
|
103
|
+
type: "message_stop";
|
|
104
|
+
};
|
|
105
|
+
export declare function mapClaudeStream(events: AsyncIterable<ClaudeStreamEvent>, priceUsd: (usage: LlmUsage) => number): AsyncIterable<LlmStreamEvent>;
|
|
106
|
+
export {};
|