@wardby/cli 0.2.1 → 0.4.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 +53 -4
- package/README.md +52 -13
- package/dist/claude-coding-worker/driver.d.ts +6 -1
- package/dist/claude-coding-worker/driver.js +27 -1
- package/dist/claude-coding-worker/main.js +2 -0
- package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
- package/dist/claude-coding-worker/tool-socket.js +26 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +14 -4
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +284 -36
- package/dist/coding/base-commit.d.ts +6 -0
- package/dist/coding/base-commit.js +12 -0
- package/dist/coding/collect-exclude.d.ts +25 -0
- package/dist/coding/collect-exclude.js +76 -0
- package/dist/coding/profile.d.ts +67 -22
- package/dist/coding/profile.js +58 -25
- package/dist/coding/protected-path-wording.d.ts +24 -0
- package/dist/coding/protected-path-wording.js +55 -0
- package/dist/coding/protected-paths.d.ts +31 -0
- package/dist/coding/protected-paths.js +48 -0
- package/dist/coding/protocol.d.ts +91 -4
- package/dist/coding/protocol.js +123 -12
- package/dist/coding/provider.d.ts +6 -1
- package/dist/coding/provider.js +16 -9
- package/dist/coding/registry/adapters.d.ts +2 -0
- package/dist/coding/registry/adapters.js +9 -0
- package/dist/coding/registry/allowlist.d.ts +9 -0
- package/dist/coding/registry/allowlist.js +28 -0
- package/dist/coding/registry/json-scan.d.ts +55 -0
- package/dist/coding/registry/json-scan.js +191 -0
- package/dist/coding/registry/lockfiles.d.ts +6 -0
- package/dist/coding/registry/lockfiles.js +89 -0
- package/dist/coding/registry/npm-lockfile.d.ts +9 -0
- package/dist/coding/registry/npm-lockfile.js +73 -0
- package/dist/coding/registry/npm-plan.d.ts +29 -0
- package/dist/coding/registry/npm-plan.js +289 -0
- package/dist/coding/registry/npm.d.ts +7 -0
- package/dist/coding/registry/npm.js +252 -0
- package/dist/coding/registry/pypi.d.ts +4 -0
- package/dist/coding/registry/pypi.js +210 -0
- package/dist/coding/registry/report.d.ts +37 -0
- package/dist/coding/registry/report.js +31 -0
- package/dist/coding/registry/token.d.ts +4 -0
- package/dist/coding/registry/token.js +7 -0
- package/dist/coding/registry/types.d.ts +286 -0
- package/dist/coding/registry/types.js +19 -0
- package/dist/coding/registry/worker-config.d.ts +14 -0
- package/dist/coding/registry/worker-config.js +31 -0
- package/dist/coding/services/builtins.d.ts +22 -0
- package/dist/coding/services/builtins.js +85 -0
- package/dist/coding/services/catalog.d.ts +443 -0
- package/dist/coding/services/catalog.js +159 -0
- package/dist/coding/services/declaration.d.ts +11 -0
- package/dist/coding/services/declaration.js +114 -0
- package/dist/coding/services/note.d.ts +8 -0
- package/dist/coding/services/note.js +15 -0
- package/dist/coding/services/resolve.d.ts +25 -0
- package/dist/coding/services/resolve.js +50 -0
- package/dist/coding/services/wording.d.ts +25 -0
- package/dist/coding/services/wording.js +70 -0
- package/dist/coding-proxy/main.js +24 -4
- package/dist/coding-worker/artifact.d.ts +6 -0
- package/dist/coding-worker/debug-trace.d.ts +37 -0
- package/dist/coding-worker/debug-trace.js +117 -0
- package/dist/coding-worker/driver.d.ts +13 -1
- package/dist/coding-worker/driver.js +102 -13
- package/dist/coding-worker/errors.js +8 -0
- package/dist/coding-worker/main.js +10 -2
- package/dist/coding-worker/sdk.d.ts +2 -2
- package/dist/coding-worker/sdk.js +7 -2
- package/dist/coding-worker/types.d.ts +3 -0
- package/dist/config/providers.d.ts +66 -0
- package/dist/config/providers.js +137 -0
- package/dist/core/attribution.d.ts +101 -0
- package/dist/core/attribution.js +208 -0
- package/dist/core/budget-groups.d.ts +120 -10
- package/dist/core/budget-groups.js +133 -24
- package/dist/core/budget-wording.d.ts +22 -0
- package/dist/core/budget-wording.js +55 -0
- package/dist/core/coding-queue.d.ts +3 -0
- package/dist/core/coding-queue.js +6 -2
- package/dist/core/coding-service-status.d.ts +11 -0
- package/dist/core/coding-service-status.js +17 -0
- package/dist/core/cost-report.d.ts +88 -0
- package/dist/core/cost-report.js +248 -0
- package/dist/core/datastores.js +18 -2
- package/dist/core/db.d.ts +5 -1
- package/dist/core/db.js +8 -2
- package/dist/core/dispatch.d.ts +74 -7
- package/dist/core/dispatch.js +382 -47
- package/dist/core/engine-native.js +44 -14
- package/dist/core/glob.d.ts +10 -0
- package/dist/core/glob.js +33 -0
- package/dist/core/grants.d.ts +86 -0
- package/dist/core/grants.js +126 -0
- package/dist/core/host-events.d.ts +72 -0
- package/dist/core/host-events.js +597 -0
- package/dist/core/host-identity-links.d.ts +54 -0
- package/dist/core/host-identity-links.js +189 -0
- package/dist/core/host-status.d.ts +78 -0
- package/dist/core/host-status.js +228 -0
- package/dist/core/in-flight-runs.d.ts +8 -0
- package/dist/core/in-flight-runs.js +56 -0
- package/dist/core/issue-bridge.d.ts +60 -0
- package/dist/core/issue-bridge.js +189 -0
- package/dist/core/issue-dedupe.d.ts +70 -0
- package/dist/core/issue-dedupe.js +255 -0
- package/dist/core/issue-events.d.ts +42 -0
- package/dist/core/issue-events.js +155 -0
- package/dist/core/issue-status.d.ts +29 -0
- package/dist/core/issue-status.js +241 -0
- package/dist/core/issue-tracker-tools.d.ts +64 -0
- package/dist/core/issue-tracker-tools.js +850 -0
- package/dist/core/model-usage.d.ts +10 -0
- package/dist/core/model-usage.js +24 -0
- package/dist/core/provider-wording.d.ts +12 -0
- package/dist/core/provider-wording.js +44 -0
- package/dist/core/reconciler.d.ts +42 -2
- package/dist/core/reconciler.js +97 -2
- package/dist/core/repo-access.d.ts +99 -0
- package/dist/core/repo-access.js +136 -0
- package/dist/core/review-host-checks.d.ts +11 -0
- package/dist/core/review-host-checks.js +41 -0
- package/dist/core/review-host-tools.d.ts +47 -0
- package/dist/core/review-host-tools.js +354 -0
- package/dist/core/run-heartbeat.d.ts +27 -0
- package/dist/core/run-heartbeat.js +54 -0
- package/dist/core/run-pricing.d.ts +61 -0
- package/dist/core/run-pricing.js +56 -0
- package/dist/core/runner.d.ts +33 -8
- package/dist/core/runner.js +410 -62
- package/dist/core/scheduler.d.ts +4 -1
- package/dist/core/scheduler.js +3 -2
- package/dist/core/secrets.d.ts +11 -2
- package/dist/core/secrets.js +25 -6
- package/dist/core/self-defects.d.ts +80 -0
- package/dist/core/self-defects.js +180 -0
- package/dist/core/subagent-memory-tools.d.ts +1 -1
- package/dist/core/subagent-memory-tools.js +16 -3
- package/dist/core/tool-admin.d.ts +81 -0
- package/dist/core/tool-admin.js +129 -0
- package/dist/core/tool-names.d.ts +42 -0
- package/dist/core/tool-names.js +67 -0
- package/dist/core/untrusted-content.d.ts +32 -0
- package/dist/core/untrusted-content.js +72 -0
- package/dist/core/webhooks.d.ts +17 -2
- package/dist/core/webhooks.js +42 -3
- package/dist/generated/prisma/browser.d.ts +166 -0
- package/dist/generated/prisma/client.d.ts +166 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +152 -52
- package/dist/generated/prisma/enums.d.ts +13 -0
- package/dist/generated/prisma/enums.js +12 -1
- package/dist/generated/prisma/internal/class.d.ts +242 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +2571 -577
- package/dist/generated/prisma/internal/prismaNamespace.js +312 -6
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +328 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +312 -6
- package/dist/generated/prisma/models/Agent.d.ts +682 -1
- package/dist/generated/prisma/models/AgentIssueProject.d.ts +1838 -0
- package/dist/generated/prisma/models/AgentIssueProject.js +1 -0
- package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
- package/dist/generated/prisma/models/AgentRepository.js +1 -0
- package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
- package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
- package/dist/generated/prisma/models/CodingProxySession.d.ts +195 -2
- package/dist/generated/prisma/models/CodingRun.d.ts +1405 -95
- package/dist/generated/prisma/models/CodingRunServiceStatus.d.ts +1404 -0
- package/dist/generated/prisma/models/CodingRunServiceStatus.js +1 -0
- package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
- package/dist/generated/prisma/models/CodingService.js +1 -0
- package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
- package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
- package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
- package/dist/generated/prisma/models/HostIdentity.js +1 -0
- package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
- package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
- package/dist/generated/prisma/models/IssueFingerprint.d.ts +1183 -0
- package/dist/generated/prisma/models/IssueFingerprint.js +1 -0
- package/dist/generated/prisma/models/IssuePullRequest.d.ts +1255 -0
- package/dist/generated/prisma/models/IssuePullRequest.js +1 -0
- package/dist/generated/prisma/models/ModelCatalogEntry.d.ts +1322 -0
- package/dist/generated/prisma/models/ModelCatalogEntry.js +1 -0
- package/dist/generated/prisma/models/Principal.d.ts +455 -0
- package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
- package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
- package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
- package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
- package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
- package/dist/generated/prisma/models/RegistryFetch.js +1 -0
- package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
- package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
- package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
- package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
- package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
- package/dist/generated/prisma/models/ResourceGrant.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +1402 -82
- package/dist/generated/prisma/models/RunAttribution.d.ts +1259 -0
- package/dist/generated/prisma/models/RunAttribution.js +1 -0
- package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
- package/dist/generated/prisma/models/RunHostCheck.js +1 -0
- package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
- package/dist/generated/prisma/models/RunHostStatus.js +1 -0
- package/dist/generated/prisma/models/RunIssueStatus.d.ts +1199 -0
- package/dist/generated/prisma/models/RunIssueStatus.js +1 -0
- package/dist/generated/prisma/models/RunModelUsage.d.ts +1316 -0
- package/dist/generated/prisma/models/RunModelUsage.js +1 -0
- package/dist/generated/prisma/models/Tool.d.ts +15 -3
- package/dist/generated/prisma/models/WorkItem.d.ts +1408 -0
- package/dist/generated/prisma/models/WorkItem.js +1 -0
- package/dist/generated/prisma/models.d.ts +22 -0
- package/dist/help/build.d.ts +1 -0
- package/dist/help/build.js +9 -0
- package/dist/help/catalog.d.ts +24 -0
- package/dist/help/catalog.js +160 -0
- package/dist/help/cli.d.ts +2 -0
- package/dist/help/cli.js +65 -0
- package/dist/help/runtime.d.ts +3 -0
- package/dist/help/runtime.js +44 -0
- package/dist/help/search.d.ts +9 -0
- package/dist/help/search.js +104 -0
- package/dist/help-index.json +999 -0
- package/dist/import/cli-args.js +3 -2
- package/dist/import/create.d.ts +5 -0
- package/dist/import/create.js +67 -13
- package/dist/import/index.js +19 -9
- package/dist/import/neutral-schema.d.ts +18 -18
- package/dist/knowledge/check.d.ts +13 -0
- package/dist/knowledge/check.js +69 -0
- package/dist/knowledge/cli.d.ts +14 -0
- package/dist/knowledge/cli.js +67 -0
- package/dist/knowledge/concept.d.ts +54 -0
- package/dist/knowledge/concept.js +78 -0
- package/dist/knowledge/note.d.ts +11 -0
- package/dist/knowledge/note.js +39 -0
- package/dist/knowledge/relevance.d.ts +11 -0
- package/dist/knowledge/relevance.js +14 -0
- package/dist/knowledge/span-hash.d.ts +3 -0
- package/dist/knowledge/span-hash.js +16 -0
- package/dist/mcp/auth/access.d.ts +72 -0
- package/dist/mcp/auth/access.js +58 -0
- package/dist/mcp/auth/grants-cli.d.ts +149 -0
- package/dist/mcp/auth/grants-cli.js +518 -0
- package/dist/mcp/auth/host-account-cli.d.ts +2 -0
- package/dist/mcp/auth/host-account-cli.js +47 -0
- package/dist/mcp/auth/ownership.d.ts +25 -51
- package/dist/mcp/auth/ownership.js +19 -14
- package/dist/mcp/auth/repo-authorization.d.ts +22 -0
- package/dist/mcp/auth/repo-authorization.js +51 -0
- package/dist/mcp/auth/resource-server.d.ts +33 -2
- package/dist/mcp/auth/resource-server.js +99 -3
- package/dist/mcp/auth/self-hosted/browser.js +2 -2
- package/dist/mcp/auth/self-hosted/cli.js +47 -13
- package/dist/mcp/auth/self-hosted/credentials.d.ts +23 -5
- package/dist/mcp/auth/self-hosted/credentials.js +62 -4
- package/dist/mcp/auth/self-hosted/session.d.ts +7 -6
- package/dist/mcp/context.d.ts +30 -1
- package/dist/mcp/errors.d.ts +25 -6
- package/dist/mcp/errors.js +98 -0
- package/dist/mcp/host-events/deliveries.d.ts +9 -0
- package/dist/mcp/host-events/deliveries.js +17 -0
- package/dist/mcp/host-events/github-ingress.d.ts +38 -0
- package/dist/mcp/host-events/github-ingress.js +83 -0
- package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
- package/dist/mcp/host-events/github-user-callback.js +50 -0
- package/dist/mcp/host-events/jira-ingress.d.ts +29 -0
- package/dist/mcp/host-events/jira-ingress.js +92 -0
- package/dist/mcp/index.d.ts +10 -1
- package/dist/mcp/index.js +187 -19
- package/dist/mcp/server.js +32 -11
- package/dist/mcp/tools/agents.js +519 -50
- package/dist/mcp/tools/budget-groups.js +3 -3
- package/dist/mcp/tools/cost-report.d.ts +8 -0
- package/dist/mcp/tools/cost-report.js +60 -0
- package/dist/mcp/tools/datastore.js +22 -15
- package/dist/mcp/tools/grants.d.ts +2 -0
- package/dist/mcp/tools/grants.js +239 -0
- package/dist/mcp/tools/help.d.ts +5 -0
- package/dist/mcp/tools/help.js +67 -0
- package/dist/mcp/tools/host-accounts.d.ts +2 -0
- package/dist/mcp/tools/host-accounts.js +114 -0
- package/dist/mcp/tools/issue-projects.d.ts +2 -0
- package/dist/mcp/tools/issue-projects.js +238 -0
- package/dist/mcp/tools/memory.d.ts +7 -1
- package/dist/mcp/tools/memory.js +5 -5
- package/dist/mcp/tools/model-catalog.d.ts +22 -0
- package/dist/mcp/tools/model-catalog.js +423 -0
- package/dist/mcp/tools/repositories.d.ts +2 -0
- package/dist/mcp/tools/repositories.js +182 -0
- package/dist/mcp/tools/runs.d.ts +6 -0
- package/dist/mcp/tools/runs.js +60 -6
- package/dist/mcp/tools/scheduling.js +3 -6
- package/dist/mcp/tools/secrets.js +18 -6
- package/dist/mcp/tools/services.d.ts +2 -0
- package/dist/mcp/tools/services.js +222 -0
- package/dist/mcp/tools/subagents.js +52 -16
- package/dist/mcp/tools/tools.d.ts +41 -0
- package/dist/mcp/tools/tools.js +201 -40
- package/dist/mcp/tools/trigger.js +65 -7
- package/dist/mcp/tools/webhooks.js +15 -4
- package/dist/mcp/transport/streamable-http.d.ts +15 -0
- package/dist/mcp/transport/streamable-http.js +55 -3
- package/dist/mcp/webhooks/ingress.d.ts +2 -1
- package/dist/mcp/webhooks/ingress.js +9 -2
- package/dist/providers/auth/delegating.d.ts +19 -0
- package/dist/providers/auth/delegating.js +71 -0
- package/dist/providers/auth/index.d.ts +2 -0
- package/dist/providers/auth/index.js +11 -0
- package/dist/providers/auth/self-hosted.d.ts +10 -2
- package/dist/providers/auth/self-hosted.js +55 -6
- package/dist/providers/auth/types.d.ts +6 -0
- package/dist/providers/coding-proxy/memory-ledger.d.ts +4 -1
- package/dist/providers/coding-proxy/memory-ledger.js +31 -3
- package/dist/providers/coding-proxy/metering.d.ts +6 -1
- package/dist/providers/coding-proxy/metering.js +41 -4
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
- package/dist/providers/coding-proxy/prisma-ledger.js +106 -8
- package/dist/providers/coding-proxy/proxy.d.ts +25 -2
- package/dist/providers/coding-proxy/proxy.js +561 -38
- package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
- package/dist/providers/coding-proxy/registry/audit.js +380 -0
- package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
- package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
- package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
- package/dist/providers/coding-proxy/registry/plan.js +304 -0
- package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
- package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
- package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
- package/dist/providers/coding-proxy/registry/service.js +1137 -0
- package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
- package/dist/providers/coding-proxy/registry/store.js +70 -0
- package/dist/providers/coding-proxy/runtime.d.ts +17 -0
- package/dist/providers/coding-proxy/runtime.js +63 -0
- package/dist/providers/coding-proxy/secure-fetch.js +0 -1
- package/dist/providers/coding-proxy/server.d.ts +11 -0
- package/dist/providers/coding-proxy/server.js +163 -0
- package/dist/providers/coding-proxy/types.d.ts +33 -1
- package/dist/providers/coding-proxy/types.js +12 -1
- package/dist/providers/engine/types.d.ts +29 -0
- package/dist/providers/executor/build.d.ts +2 -2
- package/dist/providers/executor/composition.d.ts +3 -0
- package/dist/providers/executor/composition.js +28 -1
- package/dist/providers/executor/container.d.ts +134 -4
- package/dist/providers/executor/container.js +394 -37
- package/dist/providers/executor/dbos.d.ts +4 -3
- package/dist/providers/executor/dbos.js +7 -5
- package/dist/providers/executor/in-process.d.ts +2 -3
- package/dist/providers/executor/routing.d.ts +13 -0
- package/dist/providers/executor/routing.js +18 -0
- package/dist/providers/executor/types.d.ts +34 -0
- package/dist/providers/issue-tracker/adf.d.ts +31 -0
- package/dist/providers/issue-tracker/adf.js +181 -0
- package/dist/providers/issue-tracker/index.d.ts +5 -0
- package/dist/providers/issue-tracker/index.js +12 -0
- package/dist/providers/issue-tracker/jira-client.d.ts +41 -0
- package/dist/providers/issue-tracker/jira-client.js +151 -0
- package/dist/providers/issue-tracker/jira-events.d.ts +3 -0
- package/dist/providers/issue-tracker/jira-events.js +98 -0
- package/dist/providers/issue-tracker/jira.d.ts +116 -0
- package/dist/providers/issue-tracker/jira.js +502 -0
- package/dist/providers/issue-tracker/types.d.ts +269 -0
- package/dist/providers/issue-tracker/types.js +16 -0
- package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
- package/dist/providers/jobs/claude-tool-setup.js +50 -0
- package/dist/providers/jobs/collect-prune.d.ts +7 -0
- package/dist/providers/jobs/collect-prune.js +27 -0
- package/dist/providers/jobs/docker-isolation.d.ts +48 -3
- package/dist/providers/jobs/docker-isolation.js +213 -22
- package/dist/providers/jobs/docker-services.d.ts +35 -0
- package/dist/providers/jobs/docker-services.js +191 -0
- package/dist/providers/jobs/docker.d.ts +53 -2
- package/dist/providers/jobs/docker.js +307 -32
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
- package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
- package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
- package/dist/providers/jobs/kubernetes-isolation.js +238 -57
- package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
- package/dist/providers/jobs/kubernetes-platform.js +11 -5
- package/dist/providers/jobs/kubernetes-preflight.js +3 -0
- package/dist/providers/jobs/kubernetes.d.ts +24 -2
- package/dist/providers/jobs/kubernetes.js +153 -22
- package/dist/providers/jobs/service-state.d.ts +22 -0
- package/dist/providers/jobs/service-state.js +17 -0
- package/dist/providers/jobs/types.d.ts +18 -0
- package/dist/providers/llm/anthropic.d.ts +3 -3
- package/dist/providers/llm/anthropic.js +3 -5
- package/dist/providers/llm/bedrock.d.ts +3 -3
- package/dist/providers/llm/bedrock.js +3 -8
- package/dist/providers/llm/catalog-lookup.d.ts +10 -0
- package/dist/providers/llm/catalog-lookup.js +15 -0
- package/dist/providers/llm/catalog-shipped.d.ts +18 -0
- package/dist/providers/llm/catalog-shipped.js +197 -0
- package/dist/providers/llm/catalog-store.d.ts +58 -0
- package/dist/providers/llm/catalog-store.js +138 -0
- package/dist/providers/llm/catalog-types.d.ts +66 -0
- package/dist/providers/llm/catalog-types.js +64 -0
- package/dist/providers/llm/catalog.d.ts +61 -0
- package/dist/providers/llm/catalog.js +147 -0
- package/dist/providers/llm/claude-messages.d.ts +5 -1
- package/dist/providers/llm/claude-messages.js +1 -0
- package/dist/providers/llm/claude-provider.d.ts +10 -12
- package/dist/providers/llm/claude-provider.js +15 -6
- package/dist/providers/llm/index.d.ts +9 -6
- package/dist/providers/llm/index.js +8 -5
- package/dist/providers/llm/openai.d.ts +14 -5
- package/dist/providers/llm/openai.js +24 -14
- package/dist/providers/llm/pricing-core.d.ts +5 -3
- package/dist/providers/llm/registration.js +8 -12
- package/dist/providers/llm/routing.d.ts +21 -11
- package/dist/providers/llm/routing.js +47 -13
- package/dist/providers/llm/types.d.ts +12 -0
- package/dist/providers/llm/types.js +8 -1
- package/dist/providers/review-host/diff-lines.d.ts +16 -0
- package/dist/providers/review-host/diff-lines.js +59 -0
- package/dist/providers/review-host/github-events.d.ts +6 -0
- package/dist/providers/review-host/github-events.js +226 -0
- package/dist/providers/review-host/github-user-auth.d.ts +38 -0
- package/dist/providers/review-host/github-user-auth.js +128 -0
- package/dist/providers/review-host/github.d.ts +57 -0
- package/dist/providers/review-host/github.js +568 -0
- package/dist/providers/review-host/index.d.ts +12 -0
- package/dist/providers/review-host/index.js +30 -0
- package/dist/providers/review-host/review-format.d.ts +19 -0
- package/dist/providers/review-host/review-format.js +59 -0
- package/dist/providers/review-host/types.d.ts +309 -0
- package/dist/providers/review-host/types.js +24 -0
- package/dist/providers/vcs/git.d.ts +16 -6
- package/dist/providers/vcs/git.js +78 -34
- package/dist/providers/vcs/github.d.ts +94 -3
- package/dist/providers/vcs/github.js +223 -17
- package/dist/providers/vcs/types.d.ts +48 -4
- package/dist/quickstart/index.d.ts +2 -0
- package/dist/quickstart/index.js +36 -8
- package/dist/sandbox/fetch-policy.d.ts +20 -2
- package/dist/sandbox/fetch-policy.js +64 -3
- package/dist/sandbox/host-functions.d.ts +9 -1
- package/dist/sandbox/host-functions.js +12 -5
- package/dist/serve.js +8 -2
- package/dist/viewer/api-schema.d.ts +2757 -0
- package/dist/viewer/api-schema.js +165 -0
- package/dist/viewer/build-schemas.d.ts +2 -0
- package/dist/viewer/build-schemas.js +18 -0
- package/dist/viewer/event-bus.d.ts +38 -0
- package/dist/viewer/event-bus.js +232 -0
- package/dist/viewer/graph.d.ts +40 -0
- package/dist/viewer/graph.js +243 -0
- package/dist/viewer/http.d.ts +30 -0
- package/dist/viewer/http.js +133 -0
- package/dist/viewer/run-detail.d.ts +4 -0
- package/dist/viewer/run-detail.js +61 -0
- package/dist/wardby-bin.js +11 -0
- package/docs/README.md +40 -0
- package/docs/agent-recipes.md +383 -0
- package/docs/architecture-runtime.md +90 -0
- package/docs/assets/brand/wardby-icon-512.png +0 -0
- package/docs/assets/brand/wardby-icon.svg +16 -0
- package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
- package/docs/assets/brand/wardby-mascot.png +0 -0
- package/docs/assets/brand/wardby-mascot.svg +5 -0
- package/docs/assets/wardby-workflow.svg +106 -0
- package/docs/code-review-agents.md +508 -0
- package/docs/coding-agent-setup.md +175 -0
- package/docs/coding-packages.md +455 -0
- package/docs/coding-services.md +300 -0
- package/docs/coding-worker-byo-images.md +98 -0
- package/docs/coding-worker-isolation.md +1068 -0
- package/docs/getting-started-gke.md +632 -0
- package/docs/getting-started-identity-provider.md +319 -0
- package/docs/getting-started.md +147 -0
- package/docs/jira-agents.md +649 -0
- package/docs/knowledge.md +387 -0
- package/docs/models.md +221 -0
- package/docs/observability.md +53 -0
- package/docs/release-verification.md +66 -0
- package/docs/security-deployment.md +667 -0
- package/docs/viewer-api.md +142 -0
- package/help/admin-viewer.md +39 -0
- package/help/agent-recipes.md +173 -0
- package/help/architecture-agent.md +189 -0
- package/help/builder-agent.md +80 -0
- package/help/code-review-agents.md +37 -0
- package/help/coding-packages.md +31 -0
- package/help/coding-services.md +71 -0
- package/help/cost-attribution.md +67 -0
- package/help/creating-agents.md +90 -0
- package/help/deploy-gke.md +45 -0
- package/help/deployment-targets.md +39 -0
- package/help/errors/budget-group-exhausted.md +25 -0
- package/help/errors/docker-isolation-unsupported.md +26 -0
- package/help/errors/model-unavailable.md +63 -0
- package/help/errors/protected-path.md +45 -0
- package/help/errors/repo-access.md +27 -0
- package/help/errors/service-declaration-invalid.md +27 -0
- package/help/errors/service-declaration-unavailable.md +25 -0
- package/help/errors/service-launcher-unsupported.md +30 -0
- package/help/errors/service-not-allowed.md +27 -0
- package/help/errors/service-unknown.md +23 -0
- package/help/errors/service-unready.md +49 -0
- package/help/getting-started.md +32 -0
- package/help/github.md +48 -0
- package/help/identity-and-access.md +44 -0
- package/help/jira.md +135 -0
- package/help/knowledge.md +47 -0
- package/help/mcp.md +30 -0
- package/help/models.md +90 -0
- package/help/native-capabilities.md +30 -0
- package/help/observability.md +41 -0
- package/help/operating-agents.md +36 -0
- package/help/security.md +27 -0
- package/help/troubleshooting/budgets.md +32 -0
- package/help/troubleshooting/coding-workers.md +47 -0
- package/help/troubleshooting/repository-access.md +27 -0
- package/package.json +14 -3
- package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
- package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
- package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
- package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
- package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
- package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
- package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
- package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
- package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
- package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
- package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
- package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
- package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
- package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
- package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
- package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
- package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
- package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
- package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
- package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
- package/prisma/migrations/20260930000000_jira_issue_projects/migration.sql +34 -0
- package/prisma/migrations/20261001000000_jira_phase2_allowlists/migration.sql +3 -0
- package/prisma/migrations/20261001010000_jira_link_types_allowlist/migration.sql +2 -0
- package/prisma/migrations/20261002000000_jira_coding_bridge/migration.sql +28 -0
- package/prisma/migrations/20261002010000_jira_issue_creation/migration.sql +25 -0
- package/prisma/migrations/20261003000000_issue_cost_attribution/migration.sql +56 -0
- package/prisma/migrations/20261003010000_coding_run_service_status/migration.sql +23 -0
- package/prisma/migrations/20261003020000_viewer_notify/migration.sql +54 -0
- package/prisma/migrations/20261003030000_viewer_notify_fixes/migration.sql +47 -0
- package/prisma/migrations/20261003040000_viewer_indexes/migration.sql +12 -0
- package/prisma/migrations/20261004000000_model_catalog/migration.sql +26 -0
- package/prisma/schema.prisma +663 -21
- package/dist/mcp/tools/models.d.ts +0 -8
- package/dist/mcp/tools/models.js +0 -15
- package/dist/providers/llm/pricing-anthropic.d.ts +0 -4
- package/dist/providers/llm/pricing-anthropic.js +0 -30
- package/dist/providers/llm/pricing-bedrock-claude.d.ts +0 -12
- package/dist/providers/llm/pricing-bedrock-claude.js +0 -37
- package/dist/providers/llm/pricing.d.ts +0 -30
- package/dist/providers/llm/pricing.js +0 -74
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { ISSUE_KEY, ISSUE_TRACKER_PROVIDERS, projectOf, } from "../providers/issue-tracker/types.js";
|
|
2
|
+
import { logger } from "./logger.js";
|
|
3
|
+
const log = logger.child({ module: "attribution" });
|
|
4
|
+
/** A WorkItem snapshotted this recently is reused as-is, without calling the tracker. */
|
|
5
|
+
export const SNAPSHOT_CACHE_MS = 10 * 60 * 1000;
|
|
6
|
+
export const SNAPSHOT_TIMEOUT_MS = 3000;
|
|
7
|
+
/**
|
|
8
|
+
* Snapshot budget for a caller that is answering a request (a webhook, an
|
|
9
|
+
* MCP tool call, a host event): a short timeout and no 429 retry, so a slow
|
|
10
|
+
* or rate-limited tracker degrades to key-only attribution instead of
|
|
11
|
+
* stalling the response.
|
|
12
|
+
*/
|
|
13
|
+
export const RESPONSE_PATH_SNAPSHOT_BUDGET = { timeoutMs: 2000, retryOn429: false };
|
|
14
|
+
/**
|
|
15
|
+
* The work item a new attribution points at, snapshotted from the tracker
|
|
16
|
+
* unless a fresh snapshot is already stored. Never throws: a failed lookup or
|
|
17
|
+
* snapshot attributes by key only.
|
|
18
|
+
*/
|
|
19
|
+
export async function resolveWorkItem(db, trackers, provider, key, opts = {}) {
|
|
20
|
+
const now = opts.now ?? new Date();
|
|
21
|
+
const keyOnly = (scopeKey = projectOf(key)) => ({ provider, key, scopeKey, snapshot: null });
|
|
22
|
+
let existing;
|
|
23
|
+
try {
|
|
24
|
+
existing = await db.workItem.findUnique({
|
|
25
|
+
where: { provider_key: { provider, key } },
|
|
26
|
+
select: { refreshedAt: true, scopeKey: true },
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
log.warn({ err, provider, key }, "work item lookup failed; attributing by key only");
|
|
31
|
+
return keyOnly();
|
|
32
|
+
}
|
|
33
|
+
if (existing?.refreshedAt && now.getTime() - existing.refreshedAt.getTime() < SNAPSHOT_CACHE_MS) {
|
|
34
|
+
return keyOnly(existing.scopeKey);
|
|
35
|
+
}
|
|
36
|
+
const tracker = trackers?.[provider];
|
|
37
|
+
if (!tracker)
|
|
38
|
+
return keyOnly(existing?.scopeKey);
|
|
39
|
+
try {
|
|
40
|
+
const snapshot = await tracker.snapshotIssue(key, {
|
|
41
|
+
timeoutMs: opts.timeoutMs ?? SNAPSHOT_TIMEOUT_MS,
|
|
42
|
+
retryOn429: opts.retryOn429,
|
|
43
|
+
});
|
|
44
|
+
return { provider, key, scopeKey: snapshot.scopeKey, snapshot };
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
log.warn({ err, provider, key }, "issue snapshot failed; attributing by key only");
|
|
48
|
+
return keyOnly(existing?.scopeKey);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Upserts the item (and its parent) and returns the item's id and the parent to freeze. A key-only result never clobbers stored fields. */
|
|
52
|
+
async function upsertWorkItem(tx, item, now) {
|
|
53
|
+
const s = item.snapshot;
|
|
54
|
+
if (s?.parent) {
|
|
55
|
+
// The parent's own type/scope/title are refreshed when it is snapshotted itself; here only make sure it exists
|
|
56
|
+
// and take a title we were handed for free.
|
|
57
|
+
await tx.workItem.upsert({
|
|
58
|
+
where: { provider_key: { provider: item.provider, key: s.parent.key } },
|
|
59
|
+
create: {
|
|
60
|
+
provider: item.provider,
|
|
61
|
+
key: s.parent.key,
|
|
62
|
+
scopeKey: projectOf(s.parent.key),
|
|
63
|
+
...(s.parent.title ? { title: s.parent.title } : {}),
|
|
64
|
+
},
|
|
65
|
+
update: s.parent.title ? { title: s.parent.title } : {},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const snapshotFields = s
|
|
69
|
+
? {
|
|
70
|
+
title: s.title ?? null,
|
|
71
|
+
type: s.type ?? null,
|
|
72
|
+
url: s.url,
|
|
73
|
+
scopeKey: s.scopeKey,
|
|
74
|
+
parentKey: s.parent?.key ?? null,
|
|
75
|
+
parentKind: s.parent?.kind ?? null,
|
|
76
|
+
refreshedAt: now,
|
|
77
|
+
}
|
|
78
|
+
: {};
|
|
79
|
+
const row = await tx.workItem.upsert({
|
|
80
|
+
where: { provider_key: { provider: item.provider, key: item.key } },
|
|
81
|
+
create: { provider: item.provider, key: item.key, scopeKey: item.scopeKey, ...snapshotFields },
|
|
82
|
+
update: snapshotFields,
|
|
83
|
+
select: { id: true, parentKey: true },
|
|
84
|
+
});
|
|
85
|
+
return row;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Covers runs created before attribution existed (no backfill): a continued
|
|
89
|
+
* coding run that already carries CodingRun.issueKey, else a direct parent
|
|
90
|
+
* with a RunIssueStatus. Grandchildren then inherit the child's new
|
|
91
|
+
* RunAttribution, so only the direct parent is checked.
|
|
92
|
+
*/
|
|
93
|
+
async function legacyIssue(tx, from) {
|
|
94
|
+
if (from.continuesCodingRunId) {
|
|
95
|
+
const prior = await tx.codingRun.findUnique({
|
|
96
|
+
where: { runId: from.continuesCodingRunId },
|
|
97
|
+
select: { issueProvider: true, issueKey: true },
|
|
98
|
+
});
|
|
99
|
+
if (prior?.issueProvider && prior.issueKey)
|
|
100
|
+
return { provider: prior.issueProvider, key: prior.issueKey };
|
|
101
|
+
}
|
|
102
|
+
if (from.parentRunId) {
|
|
103
|
+
const status = await tx.runIssueStatus.findUnique({
|
|
104
|
+
where: { runId: from.parentRunId },
|
|
105
|
+
select: { provider: true, issueKey: true },
|
|
106
|
+
});
|
|
107
|
+
if (status)
|
|
108
|
+
return { provider: status.provider, key: status.issueKey };
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Writes the run's attribution inside dispatch's persist transaction.
|
|
114
|
+
* Precedence: the parent run's attribution, then the continued coding run's,
|
|
115
|
+
* then a pre-attribution issue (legacyIssue), then the caller's intent. Returns the item's provider/key (for
|
|
116
|
+
* CodingRun.issueProvider/issueKey), or null when the run is unattributed.
|
|
117
|
+
*/
|
|
118
|
+
export async function attributeRun(tx, runId, from, now = new Date()) {
|
|
119
|
+
for (const ancestorId of [from.parentRunId, from.continuesCodingRunId]) {
|
|
120
|
+
if (!ancestorId)
|
|
121
|
+
continue;
|
|
122
|
+
const inherited = await tx.runAttribution.findUnique({
|
|
123
|
+
where: { runId: ancestorId },
|
|
124
|
+
select: { workItemId: true, parentKeyAtRun: true, workItem: { select: { provider: true, key: true } } },
|
|
125
|
+
});
|
|
126
|
+
if (!inherited)
|
|
127
|
+
continue;
|
|
128
|
+
await tx.runAttribution.create({
|
|
129
|
+
data: { runId, workItemId: inherited.workItemId, parentKeyAtRun: inherited.parentKeyAtRun, source: "inherited" },
|
|
130
|
+
});
|
|
131
|
+
return { provider: inherited.workItem.provider, key: inherited.workItem.key };
|
|
132
|
+
}
|
|
133
|
+
const legacy = await legacyIssue(tx, from);
|
|
134
|
+
if (legacy) {
|
|
135
|
+
const item = { ...legacy, scopeKey: projectOf(legacy.key), snapshot: null };
|
|
136
|
+
const { id, parentKey } = await upsertWorkItem(tx, item, now);
|
|
137
|
+
await tx.runAttribution.create({ data: { runId, workItemId: id, parentKeyAtRun: parentKey, source: "inherited" } });
|
|
138
|
+
return legacy;
|
|
139
|
+
}
|
|
140
|
+
if (!from.intent)
|
|
141
|
+
return null;
|
|
142
|
+
const { id, parentKey } = await upsertWorkItem(tx, from.intent.item, now);
|
|
143
|
+
await tx.runAttribution.create({
|
|
144
|
+
data: { runId, workItemId: id, parentKeyAtRun: parentKey, source: from.intent.source },
|
|
145
|
+
});
|
|
146
|
+
return { provider: from.intent.item.provider, key: from.intent.item.key };
|
|
147
|
+
}
|
|
148
|
+
/** The issue a pull request was opened for (phase-3 IssuePullRequest); the earliest link when several exist. */
|
|
149
|
+
export async function linkedPullRequestIssue(db, pr) {
|
|
150
|
+
try {
|
|
151
|
+
const row = await db.issuePullRequest.findFirst({
|
|
152
|
+
where: { codeProvider: pr.codeProvider, repository: pr.repository, number: pr.number },
|
|
153
|
+
orderBy: { createdAt: "asc" },
|
|
154
|
+
select: { issueProvider: true, issueKey: true },
|
|
155
|
+
});
|
|
156
|
+
return row ? { provider: row.issueProvider, key: row.issueKey } : null;
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
log.warn({ err, ...pr }, "linked issue lookup failed; the run is unattributed");
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/** Attribution for a run on a pull request that was opened for an issue; undefined when it has none. */
|
|
164
|
+
export async function linkedPullRequestAttribution(db, trackers, pr, opts) {
|
|
165
|
+
const issue = await linkedPullRequestIssue(db, pr);
|
|
166
|
+
if (!issue)
|
|
167
|
+
return undefined;
|
|
168
|
+
return { source: "linked_pr", item: await resolveWorkItem(db, trackers, issue.provider, issue.key, opts) };
|
|
169
|
+
}
|
|
170
|
+
/** A refusal whose message is safe to return to the caller. */
|
|
171
|
+
export class AttributionError extends Error {
|
|
172
|
+
constructor(message) {
|
|
173
|
+
super(message);
|
|
174
|
+
this.name = "AttributionError";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* An issue a caller names on trigger_agent or a webhook. It decides whose
|
|
179
|
+
* cost this run counts toward, so it must be well formed and in a project the
|
|
180
|
+
* agent is linked to (AgentIssueProject) — otherwise the dispatch is refused.
|
|
181
|
+
* Issue keys are uppercase, so a key that differs only by case or surrounding
|
|
182
|
+
* spaces ("pay-241") is corrected rather than refused. `field` is the caller's
|
|
183
|
+
* name for the value (trigger_agent: issue; webhooks: wardbyIssue), so
|
|
184
|
+
* refusals name the field the caller actually sent.
|
|
185
|
+
*/
|
|
186
|
+
export async function validateExplicitIssue(db, agentId, issue, field = "issue") {
|
|
187
|
+
const candidate = issue && typeof issue === "object" ? issue : {};
|
|
188
|
+
const provider = candidate.provider;
|
|
189
|
+
const key = typeof candidate.key === "string" ? candidate.key.trim().toUpperCase() : candidate.key;
|
|
190
|
+
if (typeof provider !== "string" || !ISSUE_TRACKER_PROVIDERS.includes(provider)) {
|
|
191
|
+
throw new AttributionError(`${field}.provider must be one of: ${ISSUE_TRACKER_PROVIDERS.join(", ")}.`);
|
|
192
|
+
}
|
|
193
|
+
if (typeof key !== "string" || !ISSUE_KEY.test(key)) {
|
|
194
|
+
throw new AttributionError(`${field}.key must be an issue key such as PROJ-123.`);
|
|
195
|
+
}
|
|
196
|
+
const link = await db.agentIssueProject.findUnique({
|
|
197
|
+
where: { agentId_provider_projectKey: { agentId, provider, projectKey: projectOf(key) } },
|
|
198
|
+
select: { agentId: true },
|
|
199
|
+
});
|
|
200
|
+
if (!link)
|
|
201
|
+
throw new AttributionError(`This agent is not linked to ${provider} project ${projectOf(key)}.`);
|
|
202
|
+
return { provider: provider, key };
|
|
203
|
+
}
|
|
204
|
+
export async function explicitAttribution(db, trackers, agentId, issue, opts) {
|
|
205
|
+
const { field, ...budget } = opts ?? {};
|
|
206
|
+
const { provider, key } = await validateExplicitIssue(db, agentId, issue, field);
|
|
207
|
+
return { source: "explicit", item: await resolveWorkItem(db, trackers, provider, key, budget) };
|
|
208
|
+
}
|
|
@@ -10,7 +10,28 @@
|
|
|
10
10
|
* enforces that tightened ceiling with no changes of its own: a group
|
|
11
11
|
* whose period budget is already exhausted simply produces an effective
|
|
12
12
|
* budget of 0, which the engine's turn-1 gate already refuses exactly
|
|
13
|
-
* like any other zero/negative budget.
|
|
13
|
+
* like any other zero/negative budget. A coding run gets the same effective
|
|
14
|
+
* budget at dispatch (dispatch.ts), where it becomes the run's reservation.
|
|
15
|
+
*
|
|
16
|
+
* Spend is the real cost of the period's runs plus the unspent part of every
|
|
17
|
+
* live run still in flight (pending or running): its hold, minus what it has
|
|
18
|
+
* spent so far, never below zero. A coding run's hold is its
|
|
19
|
+
* CodingRun.budgetReservedUsd; a native run's is its agent's current
|
|
20
|
+
* per-run budgetUsd, an upper bound on the effective budget it pinned at load.
|
|
21
|
+
* Counting holds is what stops concurrent runs from each claiming the
|
|
22
|
+
* same remainder (security review E-04).
|
|
23
|
+
*
|
|
24
|
+
* Only live runs hold anything (see isHoldLive): a run that stopped
|
|
25
|
+
* heartbeating (a crashed process, a Ctrl-C'd `wardby run`) or a coding run
|
|
26
|
+
* past its queue + run timeout releases its hold even while its row still
|
|
27
|
+
* says pending/running, so a zombie row cannot pin a group for a whole period.
|
|
28
|
+
*
|
|
29
|
+
* A native run asks for its budget at load, after its row already exists, and
|
|
30
|
+
* counts only the holds of runs that started before it (startedAt, then id):
|
|
31
|
+
* first come, first served. Two members dispatched on the same tick never
|
|
32
|
+
* both see the other's hold and starve each other; the earlier one gets the
|
|
33
|
+
* remainder and the later one gets what the earlier one leaves. A coding
|
|
34
|
+
* dispatch has no row yet, so it counts every live hold.
|
|
14
35
|
*/
|
|
15
36
|
import type { Agent, BudgetGroup, PrismaClient } from "#prisma";
|
|
16
37
|
export type Period = "day" | "week" | "month";
|
|
@@ -24,9 +45,75 @@ export declare function periodStart(period: Period, now: Date): Date;
|
|
|
24
45
|
export interface PeriodSpend {
|
|
25
46
|
period: Period;
|
|
26
47
|
capUsd: number;
|
|
48
|
+
/** Real cost recorded by the period's runs, finished or not. */
|
|
27
49
|
spentUsd: number;
|
|
50
|
+
/** Unspent reservations of the period's in-flight (pending/running) runs. */
|
|
51
|
+
reservedUsd: number;
|
|
52
|
+
/** capUsd - spentUsd - reservedUsd, never below zero. */
|
|
28
53
|
remainingUsd: number;
|
|
29
54
|
}
|
|
55
|
+
/** A group member as computeGroupSpend needs it: a native run in flight holds its agent's budgetUsd. */
|
|
56
|
+
export interface GroupMember {
|
|
57
|
+
id: string;
|
|
58
|
+
budgetUsd: unknown;
|
|
59
|
+
}
|
|
60
|
+
/** A run's position in first-come-first-served order: startedAt, then id. */
|
|
61
|
+
export interface RunOrder {
|
|
62
|
+
id: string;
|
|
63
|
+
startedAt: Date;
|
|
64
|
+
}
|
|
65
|
+
export interface SpendOptions {
|
|
66
|
+
/**
|
|
67
|
+
* Runs whose unspent hold is left out (their real cost still counts): the
|
|
68
|
+
* run asking for its own budget, and the run tree a sub-agent spends from,
|
|
69
|
+
* whose hold already covers it.
|
|
70
|
+
*/
|
|
71
|
+
excludeReservationRunIds?: readonly string[];
|
|
72
|
+
/**
|
|
73
|
+
* Runs left out of the group's spend entirely, real cost and hold alike:
|
|
74
|
+
* the run tree when computing the tree's own ceiling, whose spend is
|
|
75
|
+
* subtracted from that ceiling separately and must count only once.
|
|
76
|
+
*/
|
|
77
|
+
excludeRunIds?: readonly string[];
|
|
78
|
+
/**
|
|
79
|
+
* First come, first served: count only the holds of runs ordered strictly
|
|
80
|
+
* before this one. Unset (a coding dispatch, whose row does not exist yet)
|
|
81
|
+
* counts every live hold.
|
|
82
|
+
*/
|
|
83
|
+
holdsBefore?: RunOrder;
|
|
84
|
+
/** CODING_QUEUE_TIMEOUT_SEC; read from the environment when omitted. */
|
|
85
|
+
codingQueueTimeoutSec?: number;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* How long a run's last heartbeat keeps its hold alive: the reconciler's
|
|
89
|
+
* heartbeat timeout plus one reconcile interval, so a managed run is reaped
|
|
90
|
+
* (or re-claimed, which refreshes heartbeatAt) before its hold lapses.
|
|
91
|
+
* Managed native runs beat every HEARTBEAT_INTERVAL_MS (InProcessExecutor,
|
|
92
|
+
* DbosExecutor), as do `wardby run` and native sub-agent children
|
|
93
|
+
* (withRunHeartbeat); a coding run beats while its job is polled.
|
|
94
|
+
*/
|
|
95
|
+
export declare const HOLD_HEARTBEAT_TTL_MS: number;
|
|
96
|
+
/** Slack past a coding run's queue + run timeout before its hold lapses (matches the sub-agent wait grace). */
|
|
97
|
+
export declare const CODING_HOLD_GRACE_SEC = 60;
|
|
98
|
+
/** Smallest amount worth holding or reserving: CodingRun.budgetReservedUsd has 6 decimal places. */
|
|
99
|
+
export declare const MIN_RESERVATION_USD = 0.000001;
|
|
100
|
+
interface HoldRow {
|
|
101
|
+
id: string;
|
|
102
|
+
status: string;
|
|
103
|
+
startedAt: Date;
|
|
104
|
+
heartbeatAt: Date | null;
|
|
105
|
+
codingRun: {
|
|
106
|
+
timeoutSec: number;
|
|
107
|
+
} | null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Whether an in-flight row still holds budget. A run holds while its last
|
|
111
|
+
* sign of life (heartbeatAt, or startedAt before its first beat) is within
|
|
112
|
+
* HOLD_HEARTBEAT_TTL_MS. A coding run also holds, beat or not, until its
|
|
113
|
+
* queue timeout + run timeout + grace have passed since dispatch: a queued
|
|
114
|
+
* coding run does not beat, and nothing can legitimately spend past that.
|
|
115
|
+
*/
|
|
116
|
+
export declare function isHoldLive(run: HoldRow, now: Date, codingQueueTimeoutSec: number): boolean;
|
|
30
117
|
/**
|
|
31
118
|
* Live per-period spend for whichever of a group's daily/weekly/monthly
|
|
32
119
|
* caps are actually set -- unset periods are omitted from the result, not
|
|
@@ -34,7 +121,7 @@ export interface PeriodSpend {
|
|
|
34
121
|
* (month contains week contains day), bucketed in JS, rather than a
|
|
35
122
|
* separate DB round trip per period.
|
|
36
123
|
*/
|
|
37
|
-
export declare function computeGroupSpend(db: BudgetGroupsDb, group: Pick<BudgetGroup, "dailyBudgetUsd" | "weeklyBudgetUsd" | "monthlyBudgetUsd">,
|
|
124
|
+
export declare function computeGroupSpend(db: BudgetGroupsDb, group: Pick<BudgetGroup, "dailyBudgetUsd" | "weeklyBudgetUsd" | "monthlyBudgetUsd">, members: readonly GroupMember[], now?: Date, options?: SpendOptions): Promise<PeriodSpend[]>;
|
|
38
125
|
export interface RunTreeSpend {
|
|
39
126
|
rootRunId: string;
|
|
40
127
|
capUsd: number;
|
|
@@ -44,18 +131,34 @@ export interface RunTreeSpend {
|
|
|
44
131
|
/**
|
|
45
132
|
* Real-time shared budget scope for a sub-agent dispatch: the whole run tree
|
|
46
133
|
* rooted at `parentRunId`'s ultimate ancestor shares one ceiling —
|
|
47
|
-
* the root's own
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
134
|
+
* the root's own budgetUsd, itself tightened by the root agent's BudgetGroup
|
|
135
|
+
* if any — minus everything every run in the tree has spent so far. The
|
|
136
|
+
* ceiling is recomputed fresh here, not pinned from whatever it was when the
|
|
137
|
+
* root run started — consistent with BudgetGroup periods already always
|
|
138
|
+
* being live-recomputed rather than snapshotted. Its group check leaves the
|
|
139
|
+
* tree's runs out entirely, cost and hold alike: the tree's spend is
|
|
140
|
+
* subtracted once, below, so counting it in the group too would charge it
|
|
141
|
+
* twice, and the tree spends inside the root's hold, so counting that hold
|
|
142
|
+
* would leave the tree nothing. It also leaves out holds of runs that started
|
|
143
|
+
* after the root (they already counted the root's hold). capUsd is therefore
|
|
144
|
+
* the tree's whole ceiling, and remainingUsd = capUsd - spentUsd.
|
|
53
145
|
*/
|
|
54
146
|
export declare function computeRunTreeSpend(db: BudgetGroupsDb, parentRunId: string, now?: Date): Promise<RunTreeSpend>;
|
|
147
|
+
export type BudgetConstraint = Period | "run-tree";
|
|
55
148
|
export interface EffectiveBudgetResult {
|
|
56
149
|
effectiveBudgetUsd: number;
|
|
57
150
|
/** Which constraint(s), if any, are tighter than the agent's own budgetUsd right now. */
|
|
58
|
-
constrainedBy:
|
|
151
|
+
constrainedBy: BudgetConstraint[];
|
|
152
|
+
/** The first constraint with nothing left (< MIN_RESERVATION_USD), set only when there is one. */
|
|
153
|
+
exhaustedBy?: BudgetConstraint;
|
|
154
|
+
}
|
|
155
|
+
export interface EffectiveBudgetOptions {
|
|
156
|
+
/**
|
|
157
|
+
* The run this budget is for, when its row already exists (a native run's
|
|
158
|
+
* load step): its own hold is not counted against itself, and neither are
|
|
159
|
+
* the holds of runs that started after it (first come, first served).
|
|
160
|
+
*/
|
|
161
|
+
self?: RunOrder;
|
|
59
162
|
}
|
|
60
163
|
/**
|
|
61
164
|
* The per-run budget ceiling to actually use for `agent`: its own
|
|
@@ -68,5 +171,12 @@ export interface EffectiveBudgetResult {
|
|
|
68
171
|
* with no caps configured is returned unchanged. Also logs a warning
|
|
69
172
|
* (never blocks, never throws) once a period's spend crosses the group's
|
|
70
173
|
* own warnThresholdRatio.
|
|
174
|
+
*
|
|
175
|
+
* A sub-agent in the root's own budget group spends inside the root's hold,
|
|
176
|
+
* so its group check is the root's: the tree's holds and the holds of runs
|
|
177
|
+
* after the root are left out. A sub-agent in any other group is an
|
|
178
|
+
* ordinary member of that group and counts every earlier live hold there,
|
|
179
|
+
* the tree's included.
|
|
71
180
|
*/
|
|
72
|
-
export declare function effectiveBudgetForRun(db: BudgetGroupsDb, agent: Pick<Agent, "id" | "budgetGroupId" | "budgetUsd">, now?: Date, parentRunId?: string): Promise<EffectiveBudgetResult>;
|
|
181
|
+
export declare function effectiveBudgetForRun(db: BudgetGroupsDb, agent: Pick<Agent, "id" | "budgetGroupId" | "budgetUsd">, now?: Date, parentRunId?: string, options?: EffectiveBudgetOptions): Promise<EffectiveBudgetResult>;
|
|
182
|
+
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { loadCodingConcurrencyConfig } from "../config/providers.js";
|
|
1
2
|
import { logger } from "./logger.js";
|
|
3
|
+
import { HEARTBEAT_TIMEOUT_MS, RECONCILE_INTERVAL_MS } from "./timing.js";
|
|
2
4
|
const budgetGroupLog = logger.child({ module: "budget-groups" });
|
|
3
5
|
/**
|
|
4
6
|
* Calendar-aligned UTC period start. "week" is the ISO week: Monday 00:00
|
|
@@ -20,6 +22,44 @@ export function periodStart(period, now) {
|
|
|
20
22
|
function toNullableNumber(value) {
|
|
21
23
|
return value === null || value === undefined ? null : Number(value);
|
|
22
24
|
}
|
|
25
|
+
const IN_FLIGHT = new Set(["pending", "running"]);
|
|
26
|
+
/**
|
|
27
|
+
* How long a run's last heartbeat keeps its hold alive: the reconciler's
|
|
28
|
+
* heartbeat timeout plus one reconcile interval, so a managed run is reaped
|
|
29
|
+
* (or re-claimed, which refreshes heartbeatAt) before its hold lapses.
|
|
30
|
+
* Managed native runs beat every HEARTBEAT_INTERVAL_MS (InProcessExecutor,
|
|
31
|
+
* DbosExecutor), as do `wardby run` and native sub-agent children
|
|
32
|
+
* (withRunHeartbeat); a coding run beats while its job is polled.
|
|
33
|
+
*/
|
|
34
|
+
export const HOLD_HEARTBEAT_TTL_MS = HEARTBEAT_TIMEOUT_MS + RECONCILE_INTERVAL_MS;
|
|
35
|
+
/** Slack past a coding run's queue + run timeout before its hold lapses (matches the sub-agent wait grace). */
|
|
36
|
+
export const CODING_HOLD_GRACE_SEC = 60;
|
|
37
|
+
/** Smallest amount worth holding or reserving: CodingRun.budgetReservedUsd has 6 decimal places. */
|
|
38
|
+
export const MIN_RESERVATION_USD = 0.000001;
|
|
39
|
+
/**
|
|
40
|
+
* Whether an in-flight row still holds budget. A run holds while its last
|
|
41
|
+
* sign of life (heartbeatAt, or startedAt before its first beat) is within
|
|
42
|
+
* HOLD_HEARTBEAT_TTL_MS. A coding run also holds, beat or not, until its
|
|
43
|
+
* queue timeout + run timeout + grace have passed since dispatch: a queued
|
|
44
|
+
* coding run does not beat, and nothing can legitimately spend past that.
|
|
45
|
+
*/
|
|
46
|
+
export function isHoldLive(run, now, codingQueueTimeoutSec) {
|
|
47
|
+
if (!IN_FLIGHT.has(run.status))
|
|
48
|
+
return false;
|
|
49
|
+
const lastSign = (run.heartbeatAt ?? run.startedAt).getTime();
|
|
50
|
+
if (now.getTime() - lastSign <= HOLD_HEARTBEAT_TTL_MS)
|
|
51
|
+
return true;
|
|
52
|
+
if (run.codingRun) {
|
|
53
|
+
const deadlineMs = run.startedAt.getTime() + (codingQueueTimeoutSec + run.codingRun.timeoutSec + CODING_HOLD_GRACE_SEC) * 1000;
|
|
54
|
+
return now.getTime() <= deadlineMs;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
function isOrderedBefore(run, other) {
|
|
59
|
+
const a = run.startedAt.getTime();
|
|
60
|
+
const b = other.startedAt.getTime();
|
|
61
|
+
return a < b || (a === b && run.id < other.id);
|
|
62
|
+
}
|
|
23
63
|
/**
|
|
24
64
|
* Live per-period spend for whichever of a group's daily/weekly/monthly
|
|
25
65
|
* caps are actually set -- unset periods are omitted from the result, not
|
|
@@ -27,7 +67,7 @@ function toNullableNumber(value) {
|
|
|
27
67
|
* (month contains week contains day), bucketed in JS, rather than a
|
|
28
68
|
* separate DB round trip per period.
|
|
29
69
|
*/
|
|
30
|
-
export async function computeGroupSpend(db, group,
|
|
70
|
+
export async function computeGroupSpend(db, group, members, now = new Date(), options = {}) {
|
|
31
71
|
const caps = [
|
|
32
72
|
{ period: "day", capUsd: toNullableNumber(group.dailyBudgetUsd) },
|
|
33
73
|
{ period: "week", capUsd: toNullableNumber(group.weeklyBudgetUsd) },
|
|
@@ -42,28 +82,58 @@ export async function computeGroupSpend(db, group, memberAgentIds, now = new Dat
|
|
|
42
82
|
// query itself as a cheap no-op; an empty `agentId: { in: [] }` would
|
|
43
83
|
// match nothing anyway.
|
|
44
84
|
const widestStart = periodStart("month", now);
|
|
45
|
-
const
|
|
85
|
+
const memberBudgets = new Map(members.map((m) => [m.id, Number(m.budgetUsd)]));
|
|
86
|
+
const excluded = new Set(options.excludeReservationRunIds ?? []);
|
|
87
|
+
const dropped = new Set(options.excludeRunIds ?? []);
|
|
88
|
+
const queueTimeoutSec = options.codingQueueTimeoutSec ?? loadCodingConcurrencyConfig().queueTimeoutSec;
|
|
89
|
+
const runs = members.length === 0
|
|
46
90
|
? []
|
|
47
91
|
: await db.run.findMany({
|
|
48
|
-
where: { agentId: { in:
|
|
49
|
-
select: {
|
|
92
|
+
where: { agentId: { in: [...memberBudgets.keys()] }, startedAt: { gte: widestStart } },
|
|
93
|
+
select: {
|
|
94
|
+
id: true,
|
|
95
|
+
agentId: true,
|
|
96
|
+
status: true,
|
|
97
|
+
costUsd: true,
|
|
98
|
+
startedAt: true,
|
|
99
|
+
heartbeatAt: true,
|
|
100
|
+
codingRun: { select: { budgetReservedUsd: true, timeoutSec: true } },
|
|
101
|
+
},
|
|
50
102
|
});
|
|
103
|
+
const counted = runs.filter((r) => !dropped.has(r.id));
|
|
104
|
+
const accounted = counted.map((r) => {
|
|
105
|
+
const costUsd = Number(r.costUsd);
|
|
106
|
+
let reservedUsd = 0;
|
|
107
|
+
const holds = !excluded.has(r.id) &&
|
|
108
|
+
(options.holdsBefore === undefined || isOrderedBefore(r, options.holdsBefore)) &&
|
|
109
|
+
isHoldLive(r, now, queueTimeoutSec);
|
|
110
|
+
if (holds) {
|
|
111
|
+
// A coding run holds exactly what dispatch reserved. A native run (or
|
|
112
|
+
// a coding run whose CodingRun row is missing) holds its agent's
|
|
113
|
+
// per-run budget: never less than what it can actually spend.
|
|
114
|
+
const reservation = r.codingRun != null ? Number(r.codingRun.budgetReservedUsd) : (memberBudgets.get(r.agentId) ?? 0);
|
|
115
|
+
reservedUsd = Math.max(0, reservation - costUsd);
|
|
116
|
+
}
|
|
117
|
+
return { startedAt: r.startedAt, costUsd, reservedUsd };
|
|
118
|
+
});
|
|
51
119
|
return configured.map(({ period, capUsd }) => {
|
|
52
120
|
const start = periodStart(period, now);
|
|
53
|
-
const
|
|
54
|
-
|
|
121
|
+
const inPeriod = accounted.filter((r) => r.startedAt >= start);
|
|
122
|
+
const spentUsd = inPeriod.reduce((sum, r) => sum + r.costUsd, 0);
|
|
123
|
+
const reservedUsd = inPeriod.reduce((sum, r) => sum + r.reservedUsd, 0);
|
|
124
|
+
return { period, capUsd, spentUsd, reservedUsd, remainingUsd: Math.max(0, capUsd - spentUsd - reservedUsd) };
|
|
55
125
|
});
|
|
56
126
|
}
|
|
57
127
|
/** Walks parentRunId upward from `runId` to the tree's root (parentRunId null). */
|
|
58
128
|
async function findRootRun(db, runId) {
|
|
59
129
|
let current = await db.run.findUniqueOrThrow({
|
|
60
130
|
where: { id: runId },
|
|
61
|
-
select: { id: true, agentId: true, parentRunId: true },
|
|
131
|
+
select: { id: true, agentId: true, parentRunId: true, startedAt: true },
|
|
62
132
|
});
|
|
63
133
|
while (current.parentRunId) {
|
|
64
134
|
current = await db.run.findUniqueOrThrow({
|
|
65
135
|
where: { id: current.parentRunId },
|
|
66
|
-
select: { id: true, agentId: true, parentRunId: true },
|
|
136
|
+
select: { id: true, agentId: true, parentRunId: true, startedAt: true },
|
|
67
137
|
});
|
|
68
138
|
}
|
|
69
139
|
return current;
|
|
@@ -83,14 +153,23 @@ async function collectTreeRunIds(db, rootRunId) {
|
|
|
83
153
|
/**
|
|
84
154
|
* Real-time shared budget scope for a sub-agent dispatch: the whole run tree
|
|
85
155
|
* rooted at `parentRunId`'s ultimate ancestor shares one ceiling —
|
|
86
|
-
* the root's own
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
156
|
+
* the root's own budgetUsd, itself tightened by the root agent's BudgetGroup
|
|
157
|
+
* if any — minus everything every run in the tree has spent so far. The
|
|
158
|
+
* ceiling is recomputed fresh here, not pinned from whatever it was when the
|
|
159
|
+
* root run started — consistent with BudgetGroup periods already always
|
|
160
|
+
* being live-recomputed rather than snapshotted. Its group check leaves the
|
|
161
|
+
* tree's runs out entirely, cost and hold alike: the tree's spend is
|
|
162
|
+
* subtracted once, below, so counting it in the group too would charge it
|
|
163
|
+
* twice, and the tree spends inside the root's hold, so counting that hold
|
|
164
|
+
* would leave the tree nothing. It also leaves out holds of runs that started
|
|
165
|
+
* after the root (they already counted the root's hold). capUsd is therefore
|
|
166
|
+
* the tree's whole ceiling, and remainingUsd = capUsd - spentUsd.
|
|
92
167
|
*/
|
|
93
168
|
export async function computeRunTreeSpend(db, parentRunId, now = new Date()) {
|
|
169
|
+
const { rootRunId, capUsd, spentUsd, remainingUsd } = await runTreeSpend(db, parentRunId, now);
|
|
170
|
+
return { rootRunId, capUsd, spentUsd, remainingUsd };
|
|
171
|
+
}
|
|
172
|
+
async function runTreeSpend(db, parentRunId, now) {
|
|
94
173
|
const root = await findRootRun(db, parentRunId);
|
|
95
174
|
const treeRunIds = await collectTreeRunIds(db, root.id);
|
|
96
175
|
const rows = await db.run.findMany({ where: { id: { in: treeRunIds } }, select: { costUsd: true } });
|
|
@@ -99,11 +178,23 @@ export async function computeRunTreeSpend(db, parentRunId, now = new Date()) {
|
|
|
99
178
|
where: { id: root.agentId },
|
|
100
179
|
select: { id: true, budgetGroupId: true, budgetUsd: true },
|
|
101
180
|
});
|
|
181
|
+
const rootOrder = { id: root.id, startedAt: root.startedAt };
|
|
102
182
|
// Root has no parentRunId of its own, so this terminates in one level —
|
|
103
183
|
// no unbounded recursion regardless of how deep `parentRunId` itself was.
|
|
104
|
-
const rootCeiling = await
|
|
184
|
+
const rootCeiling = await groupCappedBudget(db, rootAgent, now, {
|
|
185
|
+
excludeRunIds: treeRunIds,
|
|
186
|
+
holdsBefore: rootOrder,
|
|
187
|
+
});
|
|
105
188
|
const capUsd = rootCeiling.effectiveBudgetUsd;
|
|
106
|
-
return {
|
|
189
|
+
return {
|
|
190
|
+
rootRunId: root.id,
|
|
191
|
+
capUsd,
|
|
192
|
+
spentUsd,
|
|
193
|
+
remainingUsd: Math.max(0, capUsd - spentUsd),
|
|
194
|
+
treeRunIds,
|
|
195
|
+
root: rootOrder,
|
|
196
|
+
rootBudgetGroupId: rootAgent.budgetGroupId,
|
|
197
|
+
};
|
|
107
198
|
}
|
|
108
199
|
/**
|
|
109
200
|
* The per-run budget ceiling to actually use for `agent`: its own
|
|
@@ -116,17 +207,35 @@ export async function computeRunTreeSpend(db, parentRunId, now = new Date()) {
|
|
|
116
207
|
* with no caps configured is returned unchanged. Also logs a warning
|
|
117
208
|
* (never blocks, never throws) once a period's spend crosses the group's
|
|
118
209
|
* own warnThresholdRatio.
|
|
210
|
+
*
|
|
211
|
+
* A sub-agent in the root's own budget group spends inside the root's hold,
|
|
212
|
+
* so its group check is the root's: the tree's holds and the holds of runs
|
|
213
|
+
* after the root are left out. A sub-agent in any other group is an
|
|
214
|
+
* ordinary member of that group and counts every earlier live hold there,
|
|
215
|
+
* the tree's included.
|
|
119
216
|
*/
|
|
120
|
-
export async function effectiveBudgetForRun(db, agent, now = new Date(), parentRunId) {
|
|
217
|
+
export async function effectiveBudgetForRun(db, agent, now = new Date(), parentRunId, options = {}) {
|
|
218
|
+
const self = options.self;
|
|
219
|
+
const ownSpend = { excludeReservationRunIds: self ? [self.id] : [], holdsBefore: self };
|
|
220
|
+
if (!parentRunId)
|
|
221
|
+
return groupCappedBudget(db, agent, now, ownSpend);
|
|
222
|
+
const tree = await runTreeSpend(db, parentRunId, now);
|
|
223
|
+
const sharesRootGroup = agent.budgetGroupId !== null && agent.budgetGroupId === tree.rootBudgetGroupId;
|
|
224
|
+
const spend = sharesRootGroup
|
|
225
|
+
? { excludeReservationRunIds: [...tree.treeRunIds, ...(self ? [self.id] : [])], holdsBefore: tree.root }
|
|
226
|
+
: ownSpend;
|
|
227
|
+
return groupCappedBudget(db, agent, now, spend, { value: tree.remainingUsd, reason: "run-tree" });
|
|
228
|
+
}
|
|
229
|
+
async function groupCappedBudget(db, agent, now, spendOptions, extra) {
|
|
121
230
|
const ownBudgetUsd = Number(agent.budgetUsd);
|
|
122
231
|
const candidates = [];
|
|
123
232
|
if (agent.budgetGroupId) {
|
|
124
233
|
const group = await db.budgetGroup.findUnique({
|
|
125
234
|
where: { id: agent.budgetGroupId },
|
|
126
|
-
include: { agents: { select: { id: true } } },
|
|
235
|
+
include: { agents: { select: { id: true, budgetUsd: true } } },
|
|
127
236
|
});
|
|
128
237
|
if (group) {
|
|
129
|
-
const spend = await computeGroupSpend(db, group, group.agents
|
|
238
|
+
const spend = await computeGroupSpend(db, group, group.agents, now, spendOptions);
|
|
130
239
|
const warnThresholdRatio = Number(group.warnThresholdRatio);
|
|
131
240
|
for (const s of spend) {
|
|
132
241
|
candidates.push({ value: s.remainingUsd, reason: s.period });
|
|
@@ -137,13 +246,13 @@ export async function effectiveBudgetForRun(db, agent, now = new Date(), parentR
|
|
|
137
246
|
}
|
|
138
247
|
}
|
|
139
248
|
}
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
candidates.push({ value: tree.remainingUsd, reason: "run-tree" });
|
|
143
|
-
}
|
|
249
|
+
if (extra)
|
|
250
|
+
candidates.push(extra);
|
|
144
251
|
if (candidates.length === 0)
|
|
145
252
|
return { effectiveBudgetUsd: ownBudgetUsd, constrainedBy: [] };
|
|
146
253
|
const constrainedBy = candidates.filter((c) => c.value < ownBudgetUsd).map((c) => c.reason);
|
|
147
254
|
const effectiveBudgetUsd = Math.min(ownBudgetUsd, ...candidates.map((c) => c.value));
|
|
148
|
-
|
|
255
|
+
// Less than the smallest reservable amount is nothing left.
|
|
256
|
+
const exhausted = candidates.find((c) => c.value < MIN_RESERVATION_USD);
|
|
257
|
+
return { effectiveBudgetUsd, constrainedBy, ...(exhausted ? { exhaustedBy: exhausted.reason } : {}) };
|
|
149
258
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one sentence a host (a PR comment, a check summary) is told when a run
|
|
3
|
+
* ran out of budget, or could not start for lack of it. Public repositories
|
|
4
|
+
* read these, so it names amounts and the budget group only: never error
|
|
5
|
+
* text, token counts, or ids.
|
|
6
|
+
*/
|
|
7
|
+
import type { PrismaClient } from "#prisma";
|
|
8
|
+
export interface BudgetFacts {
|
|
9
|
+
/** The run's own budget (a coding run's reservation); falls back to the agent's. */
|
|
10
|
+
runBudgetUsd?: number | null;
|
|
11
|
+
agentBudgetUsd: number;
|
|
12
|
+
budgetGroupName?: string | null;
|
|
13
|
+
/** The run was refused before it started. */
|
|
14
|
+
refused?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function budgetSentence(facts: BudgetFacts): string;
|
|
17
|
+
/**
|
|
18
|
+
* The sentence for a finished run, read from the database: a coding run's
|
|
19
|
+
* reserved budget, otherwise the agent's. Falls back to a sentence without
|
|
20
|
+
* amounts when they cannot be read. Never throws.
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadBudgetSentence(db: Pick<PrismaClient, "run">, runId: string, status: string): Promise<string>;
|