@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.
Files changed (412) hide show
  1. package/.env.example +131 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +2 -0
  4. package/README.md +282 -0
  5. package/bin/wardby.js +3 -0
  6. package/deploy/local/docker-compose.yml +19 -0
  7. package/dist/claude-coding-worker/driver.d.ts +70 -0
  8. package/dist/claude-coding-worker/driver.js +128 -0
  9. package/dist/claude-coding-worker/main.d.ts +2 -0
  10. package/dist/claude-coding-worker/main.js +41 -0
  11. package/dist/claude-coding-worker/sdk.d.ts +4 -0
  12. package/dist/claude-coding-worker/sdk.js +43 -0
  13. package/dist/claude-coding-worker/tool-relay.d.ts +1 -0
  14. package/dist/claude-coding-worker/tool-relay.js +9 -0
  15. package/dist/cli-help.d.ts +1 -0
  16. package/dist/cli-help.js +20 -0
  17. package/dist/cli.d.ts +18 -0
  18. package/dist/cli.js +629 -0
  19. package/dist/coding/observability.d.ts +59 -0
  20. package/dist/coding/observability.js +77 -0
  21. package/dist/coding/profile.d.ts +88 -0
  22. package/dist/coding/profile.js +137 -0
  23. package/dist/coding/protocol.d.ts +345 -0
  24. package/dist/coding/protocol.js +445 -0
  25. package/dist/coding/provider.d.ts +4 -0
  26. package/dist/coding/provider.js +18 -0
  27. package/dist/coding-proxy/main.d.ts +1 -0
  28. package/dist/coding-proxy/main.js +51 -0
  29. package/dist/coding-worker/artifact.d.ts +16 -0
  30. package/dist/coding-worker/artifact.js +37 -0
  31. package/dist/coding-worker/driver.d.ts +46 -0
  32. package/dist/coding-worker/driver.js +158 -0
  33. package/dist/coding-worker/errors.d.ts +1 -0
  34. package/dist/coding-worker/errors.js +23 -0
  35. package/dist/coding-worker/isolation-probe.d.ts +1 -0
  36. package/dist/coding-worker/isolation-probe.js +146 -0
  37. package/dist/coding-worker/keeper.d.ts +4 -0
  38. package/dist/coding-worker/keeper.js +29 -0
  39. package/dist/coding-worker/main.d.ts +2 -0
  40. package/dist/coding-worker/main.js +43 -0
  41. package/dist/coding-worker/sdk.d.ts +26 -0
  42. package/dist/coding-worker/sdk.js +28 -0
  43. package/dist/coding-worker/types.d.ts +64 -0
  44. package/dist/coding-worker/types.js +1 -0
  45. package/dist/config/providers.d.ts +158 -0
  46. package/dist/config/providers.js +162 -0
  47. package/dist/core/budget-groups.d.ts +72 -0
  48. package/dist/core/budget-groups.js +149 -0
  49. package/dist/core/budget.d.ts +63 -0
  50. package/dist/core/budget.js +69 -0
  51. package/dist/core/coding-queue.d.ts +32 -0
  52. package/dist/core/coding-queue.js +54 -0
  53. package/dist/core/cron.d.ts +31 -0
  54. package/dist/core/cron.js +43 -0
  55. package/dist/core/datastores.d.ts +41 -0
  56. package/dist/core/datastores.js +105 -0
  57. package/dist/core/db.d.ts +2 -0
  58. package/dist/core/db.js +2 -0
  59. package/dist/core/dispatch.d.ts +81 -0
  60. package/dist/core/dispatch.js +184 -0
  61. package/dist/core/engine-native.d.ts +29 -0
  62. package/dist/core/engine-native.js +259 -0
  63. package/dist/core/http-runtime.d.ts +7 -0
  64. package/dist/core/http-runtime.js +101 -0
  65. package/dist/core/lease.d.ts +13 -0
  66. package/dist/core/lease.js +22 -0
  67. package/dist/core/logger.d.ts +13 -0
  68. package/dist/core/logger.js +45 -0
  69. package/dist/core/memory-tools.d.ts +11 -0
  70. package/dist/core/memory-tools.js +122 -0
  71. package/dist/core/reconciler.d.ts +45 -0
  72. package/dist/core/reconciler.js +152 -0
  73. package/dist/core/runner.d.ts +87 -0
  74. package/dist/core/runner.js +529 -0
  75. package/dist/core/scheduler.d.ts +47 -0
  76. package/dist/core/scheduler.js +121 -0
  77. package/dist/core/secrets.d.ts +46 -0
  78. package/dist/core/secrets.js +100 -0
  79. package/dist/core/subagent-memory-tools.d.ts +18 -0
  80. package/dist/core/subagent-memory-tools.js +121 -0
  81. package/dist/core/timing.d.ts +7 -0
  82. package/dist/core/timing.js +7 -0
  83. package/dist/core/webhooks.d.ts +23 -0
  84. package/dist/core/webhooks.js +113 -0
  85. package/dist/env.d.ts +11 -0
  86. package/dist/env.js +13 -0
  87. package/dist/import/budgets.d.ts +16 -0
  88. package/dist/import/budgets.js +21 -0
  89. package/dist/import/bundle.d.ts +16 -0
  90. package/dist/import/bundle.js +61 -0
  91. package/dist/import/cli-args.d.ts +2 -0
  92. package/dist/import/cli-args.js +44 -0
  93. package/dist/import/create.d.ts +34 -0
  94. package/dist/import/create.js +363 -0
  95. package/dist/import/index.d.ts +25 -0
  96. package/dist/import/index.js +130 -0
  97. package/dist/import/model-gate.d.ts +1 -0
  98. package/dist/import/model-gate.js +3 -0
  99. package/dist/import/neutral-schema.d.ts +369 -0
  100. package/dist/import/neutral-schema.js +109 -0
  101. package/dist/import/preflight.d.ts +41 -0
  102. package/dist/import/preflight.js +71 -0
  103. package/dist/import/report.d.ts +5 -0
  104. package/dist/import/report.js +98 -0
  105. package/dist/import/tool-scan.d.ts +8 -0
  106. package/dist/import/tool-scan.js +20 -0
  107. package/dist/mcp/auth/ownership.d.ts +113 -0
  108. package/dist/mcp/auth/ownership.js +87 -0
  109. package/dist/mcp/auth/principal.d.ts +9 -0
  110. package/dist/mcp/auth/principal.js +9 -0
  111. package/dist/mcp/auth/resource-server.d.ts +47 -0
  112. package/dist/mcp/auth/resource-server.js +82 -0
  113. package/dist/mcp/auth/self-hosted/browser.d.ts +3 -0
  114. package/dist/mcp/auth/self-hosted/browser.js +273 -0
  115. package/dist/mcp/auth/self-hosted/cli.d.ts +2 -0
  116. package/dist/mcp/auth/self-hosted/cli.js +39 -0
  117. package/dist/mcp/auth/self-hosted/credentials.d.ts +58 -0
  118. package/dist/mcp/auth/self-hosted/credentials.js +106 -0
  119. package/dist/mcp/auth/self-hosted/rate-limit.d.ts +13 -0
  120. package/dist/mcp/auth/self-hosted/rate-limit.js +20 -0
  121. package/dist/mcp/auth/self-hosted/session.d.ts +36 -0
  122. package/dist/mcp/auth/self-hosted/session.js +110 -0
  123. package/dist/mcp/capabilities.d.ts +11 -0
  124. package/dist/mcp/capabilities.js +10 -0
  125. package/dist/mcp/context.d.ts +46 -0
  126. package/dist/mcp/context.js +1 -0
  127. package/dist/mcp/errors.d.ts +13 -0
  128. package/dist/mcp/errors.js +22 -0
  129. package/dist/mcp/index.d.ts +32 -0
  130. package/dist/mcp/index.js +187 -0
  131. package/dist/mcp/jsonrpc.d.ts +8 -0
  132. package/dist/mcp/jsonrpc.js +1 -0
  133. package/dist/mcp/server.d.ts +107 -0
  134. package/dist/mcp/server.js +203 -0
  135. package/dist/mcp/tasks/manager.d.ts +63 -0
  136. package/dist/mcp/tasks/manager.js +103 -0
  137. package/dist/mcp/tools/agents.d.ts +2 -0
  138. package/dist/mcp/tools/agents.js +375 -0
  139. package/dist/mcp/tools/budget-groups.d.ts +2 -0
  140. package/dist/mcp/tools/budget-groups.js +136 -0
  141. package/dist/mcp/tools/datastore.d.ts +2 -0
  142. package/dist/mcp/tools/datastore.js +217 -0
  143. package/dist/mcp/tools/memory.d.ts +3 -0
  144. package/dist/mcp/tools/memory.js +60 -0
  145. package/dist/mcp/tools/models.d.ts +8 -0
  146. package/dist/mcp/tools/models.js +15 -0
  147. package/dist/mcp/tools/runs.d.ts +2 -0
  148. package/dist/mcp/tools/runs.js +61 -0
  149. package/dist/mcp/tools/scheduling.d.ts +2 -0
  150. package/dist/mcp/tools/scheduling.js +48 -0
  151. package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -0
  152. package/dist/mcp/tools/secret-elicitation-form.js +93 -0
  153. package/dist/mcp/tools/secret-elicitation-server.d.ts +5 -0
  154. package/dist/mcp/tools/secret-elicitation-server.js +57 -0
  155. package/dist/mcp/tools/secret-elicitation.d.ts +38 -0
  156. package/dist/mcp/tools/secret-elicitation.js +52 -0
  157. package/dist/mcp/tools/secrets.d.ts +13 -0
  158. package/dist/mcp/tools/secrets.js +124 -0
  159. package/dist/mcp/tools/subagents.d.ts +2 -0
  160. package/dist/mcp/tools/subagents.js +133 -0
  161. package/dist/mcp/tools/text-result.d.ts +6 -0
  162. package/dist/mcp/tools/text-result.js +3 -0
  163. package/dist/mcp/tools/tools.d.ts +2 -0
  164. package/dist/mcp/tools/tools.js +211 -0
  165. package/dist/mcp/tools/trigger.d.ts +2 -0
  166. package/dist/mcp/tools/trigger.js +120 -0
  167. package/dist/mcp/tools/webhooks.d.ts +2 -0
  168. package/dist/mcp/tools/webhooks.js +34 -0
  169. package/dist/mcp/transport/http-limits.d.ts +17 -0
  170. package/dist/mcp/transport/http-limits.js +92 -0
  171. package/dist/mcp/transport/stdio.d.ts +23 -0
  172. package/dist/mcp/transport/stdio.js +6 -0
  173. package/dist/mcp/transport/streamable-http.d.ts +34 -0
  174. package/dist/mcp/transport/streamable-http.js +158 -0
  175. package/dist/mcp/unattended-schedules.d.ts +15 -0
  176. package/dist/mcp/unattended-schedules.js +21 -0
  177. package/dist/mcp/webhooks/ingress.d.ts +17 -0
  178. package/dist/mcp/webhooks/ingress.js +33 -0
  179. package/dist/observability/config.d.ts +6 -0
  180. package/dist/observability/config.js +20 -0
  181. package/dist/observability/metrics-server.d.ts +11 -0
  182. package/dist/observability/metrics-server.js +47 -0
  183. package/dist/observability/metrics.d.ts +36 -0
  184. package/dist/observability/metrics.js +158 -0
  185. package/dist/providers/auth/authorization-server.d.ts +41 -0
  186. package/dist/providers/auth/authorization-server.js +1 -0
  187. package/dist/providers/auth/delegating.d.ts +44 -0
  188. package/dist/providers/auth/delegating.js +105 -0
  189. package/dist/providers/auth/index.d.ts +11 -0
  190. package/dist/providers/auth/index.js +20 -0
  191. package/dist/providers/auth/release-gate.d.ts +1 -0
  192. package/dist/providers/auth/release-gate.js +4 -0
  193. package/dist/providers/auth/self-hosted.d.ts +78 -0
  194. package/dist/providers/auth/self-hosted.js +377 -0
  195. package/dist/providers/auth/subject.d.ts +1 -0
  196. package/dist/providers/auth/subject.js +6 -0
  197. package/dist/providers/auth/types.d.ts +63 -0
  198. package/dist/providers/auth/types.js +22 -0
  199. package/dist/providers/coding-proxy/deny-port.d.ts +5 -0
  200. package/dist/providers/coding-proxy/deny-port.js +45 -0
  201. package/dist/providers/coding-proxy/environment-credentials.d.ts +7 -0
  202. package/dist/providers/coding-proxy/environment-credentials.js +16 -0
  203. package/dist/providers/coding-proxy/index.d.ts +7 -0
  204. package/dist/providers/coding-proxy/index.js +7 -0
  205. package/dist/providers/coding-proxy/memory-ledger.d.ts +17 -0
  206. package/dist/providers/coding-proxy/memory-ledger.js +103 -0
  207. package/dist/providers/coding-proxy/metering.d.ts +20 -0
  208. package/dist/providers/coding-proxy/metering.js +161 -0
  209. package/dist/providers/coding-proxy/prisma-ledger.d.ts +15 -0
  210. package/dist/providers/coding-proxy/prisma-ledger.js +213 -0
  211. package/dist/providers/coding-proxy/proxy.d.ts +75 -0
  212. package/dist/providers/coding-proxy/proxy.js +721 -0
  213. package/dist/providers/coding-proxy/runtime.d.ts +18 -0
  214. package/dist/providers/coding-proxy/runtime.js +39 -0
  215. package/dist/providers/coding-proxy/secure-fetch.d.ts +9 -0
  216. package/dist/providers/coding-proxy/secure-fetch.js +72 -0
  217. package/dist/providers/coding-proxy/server.d.ts +17 -0
  218. package/dist/providers/coding-proxy/server.js +139 -0
  219. package/dist/providers/coding-proxy/types.d.ts +99 -0
  220. package/dist/providers/coding-proxy/types.js +1 -0
  221. package/dist/providers/datastore/index.d.ts +2 -0
  222. package/dist/providers/datastore/index.js +2 -0
  223. package/dist/providers/datastore/postgres.d.ts +20 -0
  224. package/dist/providers/datastore/postgres.js +130 -0
  225. package/dist/providers/datastore/scoped.d.ts +10 -0
  226. package/dist/providers/datastore/scoped.js +46 -0
  227. package/dist/providers/datastore/types.d.ts +31 -0
  228. package/dist/providers/datastore/types.js +7 -0
  229. package/dist/providers/email/types.d.ts +46 -0
  230. package/dist/providers/email/types.js +8 -0
  231. package/dist/providers/engine/index.d.ts +1 -0
  232. package/dist/providers/engine/index.js +1 -0
  233. package/dist/providers/engine/types.d.ts +67 -0
  234. package/dist/providers/engine/types.js +16 -0
  235. package/dist/providers/executor/build.d.ts +6 -0
  236. package/dist/providers/executor/build.js +15 -0
  237. package/dist/providers/executor/composition.d.ts +14 -0
  238. package/dist/providers/executor/composition.js +123 -0
  239. package/dist/providers/executor/container.d.ts +184 -0
  240. package/dist/providers/executor/container.js +936 -0
  241. package/dist/providers/executor/dbos-status.d.ts +50 -0
  242. package/dist/providers/executor/dbos-status.js +64 -0
  243. package/dist/providers/executor/dbos.d.ts +53 -0
  244. package/dist/providers/executor/dbos.js +269 -0
  245. package/dist/providers/executor/in-process.d.ts +18 -0
  246. package/dist/providers/executor/in-process.js +38 -0
  247. package/dist/providers/executor/index.d.ts +8 -0
  248. package/dist/providers/executor/index.js +8 -0
  249. package/dist/providers/executor/routing.d.ts +48 -0
  250. package/dist/providers/executor/routing.js +70 -0
  251. package/dist/providers/executor/types.d.ts +55 -0
  252. package/dist/providers/executor/types.js +1 -0
  253. package/dist/providers/index.d.ts +43 -0
  254. package/dist/providers/index.js +18 -0
  255. package/dist/providers/jobs/docker-isolation.d.ts +166 -0
  256. package/dist/providers/jobs/docker-isolation.js +724 -0
  257. package/dist/providers/jobs/docker.d.ts +127 -0
  258. package/dist/providers/jobs/docker.js +1027 -0
  259. package/dist/providers/jobs/fake-kubernetes-api.d.ts +98 -0
  260. package/dist/providers/jobs/fake-kubernetes-api.js +142 -0
  261. package/dist/providers/jobs/fake.d.ts +23 -0
  262. package/dist/providers/jobs/fake.js +132 -0
  263. package/dist/providers/jobs/kubernetes-api.d.ts +60 -0
  264. package/dist/providers/jobs/kubernetes-api.js +20 -0
  265. package/dist/providers/jobs/kubernetes-client.d.ts +36 -0
  266. package/dist/providers/jobs/kubernetes-client.js +171 -0
  267. package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +49 -0
  268. package/dist/providers/jobs/kubernetes-dry-run-fixture.js +141 -0
  269. package/dist/providers/jobs/kubernetes-isolation.d.ts +147 -0
  270. package/dist/providers/jobs/kubernetes-isolation.js +607 -0
  271. package/dist/providers/jobs/kubernetes-platform.d.ts +177 -0
  272. package/dist/providers/jobs/kubernetes-platform.js +280 -0
  273. package/dist/providers/jobs/kubernetes-preflight.d.ts +59 -0
  274. package/dist/providers/jobs/kubernetes-preflight.js +339 -0
  275. package/dist/providers/jobs/kubernetes-witness.d.ts +10 -0
  276. package/dist/providers/jobs/kubernetes-witness.js +193 -0
  277. package/dist/providers/jobs/kubernetes.d.ts +127 -0
  278. package/dist/providers/jobs/kubernetes.js +874 -0
  279. package/dist/providers/jobs/safe-extract.d.ts +9 -0
  280. package/dist/providers/jobs/safe-extract.js +325 -0
  281. package/dist/providers/jobs/types.d.ts +78 -0
  282. package/dist/providers/jobs/types.js +6 -0
  283. package/dist/providers/jobs/workspace-swap.d.ts +2 -0
  284. package/dist/providers/jobs/workspace-swap.js +47 -0
  285. package/dist/providers/llm/anthropic.d.ts +13 -0
  286. package/dist/providers/llm/anthropic.js +21 -0
  287. package/dist/providers/llm/bedrock.d.ts +19 -0
  288. package/dist/providers/llm/bedrock.js +33 -0
  289. package/dist/providers/llm/claude-messages.d.ts +106 -0
  290. package/dist/providers/llm/claude-messages.js +157 -0
  291. package/dist/providers/llm/claude-provider.d.ts +40 -0
  292. package/dist/providers/llm/claude-provider.js +31 -0
  293. package/dist/providers/llm/index.d.ts +10 -0
  294. package/dist/providers/llm/index.js +10 -0
  295. package/dist/providers/llm/openai.d.ts +23 -0
  296. package/dist/providers/llm/openai.js +156 -0
  297. package/dist/providers/llm/pricing-anthropic.d.ts +4 -0
  298. package/dist/providers/llm/pricing-anthropic.js +30 -0
  299. package/dist/providers/llm/pricing-bedrock-claude.d.ts +12 -0
  300. package/dist/providers/llm/pricing-bedrock-claude.js +37 -0
  301. package/dist/providers/llm/pricing-core.d.ts +26 -0
  302. package/dist/providers/llm/pricing-core.js +17 -0
  303. package/dist/providers/llm/pricing.d.ts +30 -0
  304. package/dist/providers/llm/pricing.js +74 -0
  305. package/dist/providers/llm/registration.d.ts +8 -0
  306. package/dist/providers/llm/registration.js +39 -0
  307. package/dist/providers/llm/routing.d.ts +25 -0
  308. package/dist/providers/llm/routing.js +32 -0
  309. package/dist/providers/llm/types.d.ts +85 -0
  310. package/dist/providers/llm/types.js +12 -0
  311. package/dist/providers/memory/index.d.ts +2 -0
  312. package/dist/providers/memory/index.js +2 -0
  313. package/dist/providers/memory/postgres.d.ts +20 -0
  314. package/dist/providers/memory/postgres.js +72 -0
  315. package/dist/providers/memory/types.d.ts +32 -0
  316. package/dist/providers/memory/types.js +17 -0
  317. package/dist/providers/secrets/app-key.d.ts +8 -0
  318. package/dist/providers/secrets/app-key.js +50 -0
  319. package/dist/providers/secrets/index.d.ts +10 -0
  320. package/dist/providers/secrets/index.js +13 -0
  321. package/dist/providers/secrets/transfer-envelope.d.ts +12 -0
  322. package/dist/providers/secrets/transfer-envelope.js +35 -0
  323. package/dist/providers/secrets/types.d.ts +18 -0
  324. package/dist/providers/secrets/types.js +12 -0
  325. package/dist/providers/storage/types.d.ts +18 -0
  326. package/dist/providers/storage/types.js +7 -0
  327. package/dist/providers/vcs/git.d.ts +105 -0
  328. package/dist/providers/vcs/git.js +710 -0
  329. package/dist/providers/vcs/github.d.ts +105 -0
  330. package/dist/providers/vcs/github.js +394 -0
  331. package/dist/providers/vcs/index.d.ts +6 -0
  332. package/dist/providers/vcs/index.js +26 -0
  333. package/dist/providers/vcs/types.d.ts +118 -0
  334. package/dist/providers/vcs/types.js +1 -0
  335. package/dist/sandbox/bounded-json.d.ts +2 -0
  336. package/dist/sandbox/bounded-json.js +36 -0
  337. package/dist/sandbox/bridge.d.ts +15 -0
  338. package/dist/sandbox/bridge.js +78 -0
  339. package/dist/sandbox/eval-core.d.ts +37 -0
  340. package/dist/sandbox/eval-core.js +159 -0
  341. package/dist/sandbox/fetch-policy.d.ts +24 -0
  342. package/dist/sandbox/fetch-policy.js +96 -0
  343. package/dist/sandbox/generated/zod-to-json-schema.bundle.js +5 -0
  344. package/dist/sandbox/generated/zod.bundle.js +1 -0
  345. package/dist/sandbox/host-functions.d.ts +29 -0
  346. package/dist/sandbox/host-functions.js +168 -0
  347. package/dist/sandbox/limits.d.ts +39 -0
  348. package/dist/sandbox/limits.js +39 -0
  349. package/dist/sandbox/parser-worker/__fixtures__/crash.d.ts +1 -0
  350. package/dist/sandbox/parser-worker/__fixtures__/crash.js +6 -0
  351. package/dist/sandbox/parser-worker/__fixtures__/echo.d.ts +1 -0
  352. package/dist/sandbox/parser-worker/__fixtures__/echo.js +5 -0
  353. package/dist/sandbox/parser-worker/__fixtures__/reject.d.ts +1 -0
  354. package/dist/sandbox/parser-worker/__fixtures__/reject.js +5 -0
  355. package/dist/sandbox/parser-worker/__fixtures__/spin-forever.d.ts +1 -0
  356. package/dist/sandbox/parser-worker/__fixtures__/spin-forever.js +11 -0
  357. package/dist/sandbox/parser-worker/pool.d.ts +13 -0
  358. package/dist/sandbox/parser-worker/pool.js +111 -0
  359. package/dist/sandbox/parser-worker/worker.d.ts +14 -0
  360. package/dist/sandbox/parser-worker/worker.js +63 -0
  361. package/dist/sandbox/pii-redaction.d.ts +1 -0
  362. package/dist/sandbox/pii-redaction.js +29 -0
  363. package/dist/sandbox/prelude.d.ts +26 -0
  364. package/dist/sandbox/prelude.js +408 -0
  365. package/dist/sandbox/quickjs-module.d.ts +13 -0
  366. package/dist/sandbox/quickjs-module.js +19 -0
  367. package/dist/sandbox/run-in-sandbox.d.ts +34 -0
  368. package/dist/sandbox/run-in-sandbox.js +53 -0
  369. package/dist/sandbox/safe-fetch.d.ts +26 -0
  370. package/dist/sandbox/safe-fetch.js +140 -0
  371. package/dist/sandbox/tool-capabilities.d.ts +66 -0
  372. package/dist/sandbox/tool-capabilities.js +103 -0
  373. package/dist/sandbox/zod-params.d.ts +22 -0
  374. package/dist/sandbox/zod-params.js +60 -0
  375. package/dist/serve.d.ts +14 -0
  376. package/dist/serve.js +54 -0
  377. package/dist/wardby-bin.d.ts +2 -0
  378. package/dist/wardby-bin.js +20 -0
  379. package/package.json +123 -0
  380. package/prisma/migrations/20260905000000_init/migration.sql +34 -0
  381. package/prisma/migrations/20260905010000_scheduler_durability/migration.sql +31 -0
  382. package/prisma/migrations/20260905020000_tools_multiturn/migration.sql +47 -0
  383. package/prisma/migrations/20260905030000_cache_tool_json_schema/migration.sql +13 -0
  384. package/prisma/migrations/20260906000000_mcp_phase4/migration.sql +115 -0
  385. package/prisma/migrations/20260906010000_run_final_text/migration.sql +7 -0
  386. package/prisma/migrations/20260906020000_run_turns/migration.sql +6 -0
  387. package/prisma/migrations/20260906030000_task_principal_id/migration.sql +5 -0
  388. package/prisma/migrations/20260906040000_secure_self_hosted_oauth/migration.sql +67 -0
  389. package/prisma/migrations/20260906050000_phase5_coding_agents/migration.sql +52 -0
  390. package/prisma/migrations/20260906060000_tool_capability_scoping/migration.sql +22 -0
  391. package/prisma/migrations/20260906070000_datastore_pii_flag/migration.sql +6 -0
  392. package/prisma/migrations/20260906080000_secret_elicitation_outcomes/migration.sql +13 -0
  393. package/prisma/migrations/20260907010000_coding_proxy_ledger/migration.sql +57 -0
  394. package/prisma/migrations/20260907020000_budget_groups/migration.sql +24 -0
  395. package/prisma/migrations/20260907030000_coding_webhook_task_override/migration.sql +4 -0
  396. package/prisma/migrations/20260908010000_run_execution_backend/migration.sql +2 -0
  397. package/prisma/migrations/20260908020000_coding_run_audit_metadata/migration.sql +7 -0
  398. package/prisma/migrations/20260910010000_agent_secret_bound_name/migration.sql +16 -0
  399. package/prisma/migrations/20260911010000_coding_worker_toolchains/migration.sql +5 -0
  400. package/prisma/migrations/20260912010000_coding_provider_contract/migration.sql +24 -0
  401. package/prisma/migrations/20260912020000_coding_proxy_protocol/migration.sql +10 -0
  402. package/prisma/migrations/20260912030000_agent_memory/migration.sql +18 -0
  403. package/prisma/migrations/20260912040000_named_shared_datastores/migration.sql +68 -0
  404. package/prisma/migrations/20260913000000_run_trigger_webhook/migration.sql +2 -0
  405. package/prisma/migrations/20260913010000_agent_sub_agent/migration.sql +32 -0
  406. package/prisma/migrations/20260913020000_run_task_override/migration.sql +5 -0
  407. package/prisma/migrations/20260913030000_coding_run_root/migration.sql +11 -0
  408. package/prisma/migrations/20260922010000_coding_run_queued_at/migration.sql +8 -0
  409. package/prisma/migrations/20260922020000_coding_workspace_disk/migration.sql +8 -0
  410. package/prisma/migrations/migration_lock.toml +3 -0
  411. package/prisma/schema.prisma +723 -0
  412. package/scripts/git-askpass.sh +11 -0
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Provider selection from the environment.
3
+ *
4
+ * Which adapter backs each seam is pure configuration — swapping the default
5
+ * (portable) deployment for a native cloud deployment is a config change, not a
6
+ * code change. The `assembleProviders` factory (which constructs the concrete
7
+ * adapters) is added once the adapters exist.
8
+ */
9
+ import { type KubernetesPlatform } from "../providers/jobs/kubernetes-platform.js";
10
+ export type JobLauncherKind = "local" | "docker" | "kubernetes";
11
+ export type EmailProviderKind = "smtp" | "ses";
12
+ export type SecretCipherKind = "app-key" | "kms";
13
+ export type AuthProviderKind = "delegating" | "self-hosted";
14
+ export type BlobStoreKind = "local" | "s3";
15
+ export type ExecutorKind = "in-process" | "dbos";
16
+ export type DatastoreKind = "postgres";
17
+ export type EngineKind = "native" | "langgraph";
18
+ export type VcsProviderKind = "github";
19
+ export interface ProviderConfig {
20
+ jobs: JobLauncherKind;
21
+ email: EmailProviderKind;
22
+ secrets: SecretCipherKind;
23
+ auth: AuthProviderKind;
24
+ storage: BlobStoreKind;
25
+ executor: ExecutorKind;
26
+ datastore: DatastoreKind;
27
+ engine: EngineKind;
28
+ vcs: VcsProviderKind;
29
+ }
30
+ /** Read provider selection from environment variables, defaulting to portable. */
31
+ export declare function loadProviderConfig(env?: NodeJS.ProcessEnv): ProviderConfig;
32
+ export interface GitHubVcsConfig {
33
+ appId?: string;
34
+ privateKey?: string;
35
+ workRoot?: string;
36
+ apiVersion?: string;
37
+ maxChangedFiles?: number;
38
+ maxDiffBytes?: number;
39
+ }
40
+ export declare function loadGitHubVcsConfig(env?: NodeJS.ProcessEnv): GitHubVcsConfig;
41
+ export interface ContainerExecutorConfig {
42
+ workerImage?: string;
43
+ claudeWorkerImage?: string;
44
+ claudeToolRunnerImage?: string;
45
+ proxyContainer?: string;
46
+ stateRoot?: string;
47
+ artifactRoot?: string;
48
+ credentialRef: string;
49
+ anthropicCredentialRef: string;
50
+ cpus: number;
51
+ memoryMb: number;
52
+ pids: number;
53
+ diskMb: number;
54
+ /**
55
+ * Operator ceiling on the per-agent CodingProfile.workspaceDiskMb (Task 9):
56
+ * without it, any agents:write caller could size the RAM-backed Docker
57
+ * tmpfs (and keeper memory) up to 32 GiB per run, times CODING_MAX_CONCURRENT.
58
+ */
59
+ maxDiskMb: number;
60
+ additionalWorkerImages: Record<string, Record<string, string>>;
61
+ }
62
+ export declare function loadContainerExecutorConfig(env?: NodeJS.ProcessEnv): ContainerExecutorConfig;
63
+ /**
64
+ * Caps coding runs holding a concurrency slot across every control-plane
65
+ * replica (enforced in Postgres, see PrismaContainerExecutionStore), and how
66
+ * long a run may wait for one before failing with coding_queue_timeout.
67
+ */
68
+ export interface CodingConcurrencyConfig {
69
+ maxConcurrent: number;
70
+ queueTimeoutSec: number;
71
+ }
72
+ export declare function loadCodingConcurrencyConfig(env?: NodeJS.ProcessEnv): CodingConcurrencyConfig;
73
+ /** JOB_LAUNCHER=kubernetes: where coding-run pods go, how they reach the in-cluster proxy, and which platform's admission rules apply. */
74
+ export interface KubernetesJobConfig {
75
+ namespace: string;
76
+ context?: string;
77
+ proxyService: string;
78
+ runtimeClassName?: string;
79
+ platform: KubernetesPlatform;
80
+ /**
81
+ * Bound for the whole cluster preflight (canary pod scheduling included) and for how long a
82
+ * launch waits for the keeper. Both default to the launcher's own values (90 s / 120 s), which
83
+ * a cold managed cluster scheduling a sandboxed pod and pulling an image routinely exceeds.
84
+ */
85
+ preflightTimeoutMs?: number;
86
+ readyTimeoutMs?: number;
87
+ }
88
+ export declare function loadKubernetesJobConfig(env?: NodeJS.ProcessEnv): KubernetesJobConfig;
89
+ export interface McpConfig {
90
+ allowedOrigins?: string[];
91
+ transport: "http" | "stdio";
92
+ httpBind?: {
93
+ host: string;
94
+ port: number;
95
+ };
96
+ canonicalUri?: string;
97
+ localPrincipal: string;
98
+ /**
99
+ * Use the MCP spec's own multi-round-trip URL-mode elicitation
100
+ * (`InputRequiredResult`) for interactive secret entry, instead of the
101
+ * plain-text "here's a link, call me again" fallback. Off by default:
102
+ * as of Claude Code 2.1.263, this stdio client doesn't declare the
103
+ * elicitation capability for local project servers, so the protocol
104
+ * path fails outright ("did not declare the required capability").
105
+ * Flip this on once that's fixed client-side — no server code changes
106
+ * needed, both paths share the same signed-token/browser-form core.
107
+ */
108
+ secretElicitationProtocol: boolean;
109
+ }
110
+ /** Read MCP server transport/binding config from the environment. */
111
+ export declare function loadMcpConfig(env?: NodeJS.ProcessEnv): McpConfig;
112
+ export interface AuthConfig {
113
+ /** Delegating mode: the external IdP's issuer URL. */
114
+ issuer?: string;
115
+ /** Delegating mode: JWKS endpoint for signature verification. */
116
+ jwksUri?: string;
117
+ /** Both modes: the audience a token must carry to be accepted (wardby's canonical URI). */
118
+ audience?: string;
119
+ /** Self-hosted mode: independent 32-byte hex keys. */
120
+ signingKey?: string;
121
+ credentialHashKey?: string;
122
+ maxClients?: number;
123
+ }
124
+ /** Read auth-adapter config (issuer/JWKS/audience/signing key) from the environment. */
125
+ export declare function loadAuthConfig(env?: NodeJS.ProcessEnv): AuthConfig;
126
+ export interface SecretConfig {
127
+ /** app-key mode: 32-byte hex-encoded AES-256-GCM master key. */
128
+ appKey?: string;
129
+ }
130
+ /** Read secret-cipher config (the app-key master key) from the environment. */
131
+ export declare function loadSecretConfig(env?: NodeJS.ProcessEnv): SecretConfig;
132
+ export interface DbosConfig {
133
+ /**
134
+ * Where DBOS keeps its workflow/step tables. Defaults to DATABASE_URL —
135
+ * the tables live in their own schema (`schemaName`), so Prisma's `public`
136
+ * schema and the migration drift check are untouched.
137
+ */
138
+ systemDatabaseUrl: string | undefined;
139
+ schemaName: string;
140
+ /**
141
+ * Stable per-*process* executor identity. At launch DBOS re-drives every
142
+ * PENDING workflow that this id owned, so a restarted process picks up its
143
+ * own interrupted runs — which also means two processes sharing an id each
144
+ * re-drive the other's live workflows. There is deliberately no default:
145
+ * `DbosExecutor` refuses to construct without one (a default would silently
146
+ * give the scheduler and the MCP server the same identity).
147
+ *
148
+ * Defaults to a freshly generated UUID per call (per process) when unset
149
+ * — not left undefined — so two replicas of a horizontally-scaled
150
+ * deployment (e.g. a Cloud Run service with min_instance_count > 1) never
151
+ * silently share an identity just because no one set one explicitly. An
152
+ * explicit DBOS_EXECUTOR_ID still always wins, for local dev or a small
153
+ * deployment wanting a stable, human-readable id across restarts.
154
+ */
155
+ executorId: string | undefined;
156
+ }
157
+ /** Read DBOS executor config from the environment (only used when EXECUTOR=dbos). */
158
+ export declare function loadDbosConfig(env?: NodeJS.ProcessEnv): DbosConfig;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Provider selection from the environment.
3
+ *
4
+ * Which adapter backs each seam is pure configuration — swapping the default
5
+ * (portable) deployment for a native cloud deployment is a config change, not a
6
+ * code change. The `assembleProviders` factory (which constructs the concrete
7
+ * adapters) is added once the adapters exist.
8
+ */
9
+ import { KUBERNETES_PLATFORMS } from "../providers/jobs/kubernetes-platform.js";
10
+ /** Read provider selection from environment variables, defaulting to portable. */
11
+ export function loadProviderConfig(env = process.env) {
12
+ return {
13
+ jobs: env.JOB_LAUNCHER ?? "local",
14
+ email: env.EMAIL_PROVIDER ?? "smtp",
15
+ secrets: env.SECRET_CIPHER ?? "app-key",
16
+ auth: env.AUTH_PROVIDER ?? "delegating",
17
+ storage: env.BLOB_STORE ?? "local",
18
+ executor: env.EXECUTOR ?? "in-process",
19
+ datastore: env.DATASTORE ?? "postgres",
20
+ engine: env.ENGINE ?? "native",
21
+ vcs: env.VCS_PROVIDER ?? "github",
22
+ };
23
+ }
24
+ function optionalPositiveInteger(value, name) {
25
+ if (value === undefined)
26
+ return undefined;
27
+ const parsed = Number(value);
28
+ if (!Number.isSafeInteger(parsed) || parsed <= 0)
29
+ throw new Error(`${name} must be a positive integer.`);
30
+ return parsed;
31
+ }
32
+ export function loadGitHubVcsConfig(env = process.env) {
33
+ return {
34
+ appId: env.GITHUB_APP_ID,
35
+ privateKey: env.GITHUB_APP_PRIVATE_KEY,
36
+ workRoot: env.VCS_WORK_ROOT,
37
+ apiVersion: env.GITHUB_API_VERSION,
38
+ maxChangedFiles: optionalPositiveInteger(env.VCS_MAX_CHANGED_FILES, "VCS_MAX_CHANGED_FILES"),
39
+ maxDiffBytes: optionalPositiveInteger(env.VCS_MAX_DIFF_BYTES, "VCS_MAX_DIFF_BYTES"),
40
+ };
41
+ }
42
+ function optionalPositiveNumber(value, name, fallback) {
43
+ if (value === undefined)
44
+ return fallback;
45
+ const parsed = Number(value);
46
+ if (!Number.isFinite(parsed) || parsed <= 0)
47
+ throw new Error(`${name} must be a positive number.`);
48
+ return parsed;
49
+ }
50
+ function optionalBoundedInteger(value, name, min, max) {
51
+ if (value === undefined)
52
+ return undefined;
53
+ const parsed = Number(value);
54
+ if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {
55
+ throw new Error(`${name} must be an integer between ${min} and ${max}.`);
56
+ }
57
+ return parsed;
58
+ }
59
+ export function loadContainerExecutorConfig(env = process.env) {
60
+ const additionalWorkerImages = {};
61
+ if (env.CODING_WORKER_IMAGE_NODE_PYTHON_3_12) {
62
+ additionalWorkerImages["node-python"] = { "3.12": env.CODING_WORKER_IMAGE_NODE_PYTHON_3_12 };
63
+ }
64
+ const diskMb = optionalPositiveInteger(env.CODING_DISK_MB, "CODING_DISK_MB") ?? 2048;
65
+ // Defaults to the effective diskMb: raising the ceiling an agents:write caller can request is an
66
+ // explicit operator choice, so upgrading with an unchanged environment changes nothing.
67
+ const maxDiskMb = optionalBoundedInteger(env.CODING_MAX_DISK_MB, "CODING_MAX_DISK_MB", 64, 32_768) ?? diskMb;
68
+ if (maxDiskMb < diskMb) {
69
+ throw new Error(`CODING_MAX_DISK_MB (${maxDiskMb}) must be at least the effective CODING_DISK_MB (${diskMb}).`);
70
+ }
71
+ return {
72
+ workerImage: env.CODING_WORKER_IMAGE,
73
+ claudeWorkerImage: env.CODING_CLAUDE_WORKER_IMAGE,
74
+ claudeToolRunnerImage: env.CODING_CLAUDE_TOOL_RUNNER_IMAGE,
75
+ proxyContainer: env.CODING_PROXY_CONTAINER,
76
+ stateRoot: env.CODING_JOB_STATE_ROOT,
77
+ artifactRoot: env.CODING_ARTIFACT_ROOT,
78
+ credentialRef: env.CODING_OPENAI_CREDENTIAL_REF ?? "env:OPENAI_API_KEY",
79
+ anthropicCredentialRef: env.CODING_ANTHROPIC_CREDENTIAL_REF ?? "env:ANTHROPIC_API_KEY",
80
+ cpus: optionalPositiveNumber(env.CODING_CPUS, "CODING_CPUS", 1),
81
+ memoryMb: optionalPositiveInteger(env.CODING_MEMORY_MB, "CODING_MEMORY_MB") ?? 2048,
82
+ pids: optionalPositiveInteger(env.CODING_PIDS, "CODING_PIDS") ?? 128,
83
+ diskMb,
84
+ maxDiskMb,
85
+ additionalWorkerImages,
86
+ };
87
+ }
88
+ export function loadCodingConcurrencyConfig(env = process.env) {
89
+ return {
90
+ maxConcurrent: optionalPositiveInteger(env.CODING_MAX_CONCURRENT, "CODING_MAX_CONCURRENT") ?? 4,
91
+ queueTimeoutSec: optionalPositiveInteger(env.CODING_QUEUE_TIMEOUT_SEC, "CODING_QUEUE_TIMEOUT_SEC") ?? 3600,
92
+ };
93
+ }
94
+ const DNS_1123_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
95
+ function dnsLabel(value, name, fallback) {
96
+ if (value === undefined)
97
+ return fallback;
98
+ if (!DNS_1123_LABEL.test(value))
99
+ throw new Error(`${name} must be a DNS-1123 label.`);
100
+ return value;
101
+ }
102
+ export function loadKubernetesJobConfig(env = process.env) {
103
+ const platform = (env.KUBERNETES_PLATFORM ?? "generic");
104
+ if (!KUBERNETES_PLATFORMS.includes(platform)) {
105
+ throw new Error(`KUBERNETES_PLATFORM must be one of: ${KUBERNETES_PLATFORMS.join(", ")}.`);
106
+ }
107
+ const config = {
108
+ namespace: dnsLabel(env.KUBERNETES_NAMESPACE, "KUBERNETES_NAMESPACE", "wardby-coding"),
109
+ proxyService: dnsLabel(env.KUBERNETES_PROXY_SERVICE, "KUBERNETES_PROXY_SERVICE", "wardby-coding-proxy"),
110
+ platform,
111
+ };
112
+ if (env.KUBERNETES_CONTEXT)
113
+ config.context = env.KUBERNETES_CONTEXT;
114
+ if (env.KUBERNETES_RUNTIME_CLASS) {
115
+ config.runtimeClassName = dnsLabel(env.KUBERNETES_RUNTIME_CLASS, "KUBERNETES_RUNTIME_CLASS", "");
116
+ }
117
+ const preflightTimeoutMs = optionalBoundedInteger(env.KUBERNETES_PREFLIGHT_TIMEOUT_MS, "KUBERNETES_PREFLIGHT_TIMEOUT_MS", 1_000, 900_000);
118
+ const readyTimeoutMs = optionalBoundedInteger(env.KUBERNETES_READY_TIMEOUT_MS, "KUBERNETES_READY_TIMEOUT_MS", 1_000, 900_000);
119
+ if (preflightTimeoutMs !== undefined)
120
+ config.preflightTimeoutMs = preflightTimeoutMs;
121
+ if (readyTimeoutMs !== undefined)
122
+ config.readyTimeoutMs = readyTimeoutMs;
123
+ return config;
124
+ }
125
+ /** Read MCP server transport/binding config from the environment. */
126
+ export function loadMcpConfig(env = process.env) {
127
+ const transport = env.MCP_TRANSPORT ?? "stdio";
128
+ const bind = env.MCP_HTTP_BIND?.split(":");
129
+ return {
130
+ transport,
131
+ httpBind: bind ? { host: bind[0], port: Number(bind[1]) } : undefined,
132
+ canonicalUri: env.MCP_CANONICAL_URI,
133
+ allowedOrigins: env.MCP_ALLOWED_ORIGINS ? env.MCP_ALLOWED_ORIGINS.split(",").map((s) => s.trim()) : undefined,
134
+ localPrincipal: env.LOCAL_PRINCIPAL ?? "local",
135
+ secretElicitationProtocol: env.MCP_SECRET_ELICITATION_PROTOCOL === "true",
136
+ };
137
+ }
138
+ /** Read auth-adapter config (issuer/JWKS/audience/signing key) from the environment. */
139
+ export function loadAuthConfig(env = process.env) {
140
+ return {
141
+ issuer: env.AUTH_ISSUER,
142
+ jwksUri: env.AUTH_JWKS_URI,
143
+ audience: env.AUTH_AUDIENCE,
144
+ signingKey: env.AUTH_SIGNING_KEY,
145
+ credentialHashKey: env.AUTH_CREDENTIAL_HASH_KEY,
146
+ maxClients: env.AUTH_MAX_CLIENTS ? Number(env.AUTH_MAX_CLIENTS) : undefined,
147
+ };
148
+ }
149
+ /** Read secret-cipher config (the app-key master key) from the environment. */
150
+ export function loadSecretConfig(env = process.env) {
151
+ return {
152
+ appKey: env.SECRET_APP_KEY,
153
+ };
154
+ }
155
+ /** Read DBOS executor config from the environment (only used when EXECUTOR=dbos). */
156
+ export function loadDbosConfig(env = process.env) {
157
+ return {
158
+ systemDatabaseUrl: env.DBOS_SYSTEM_DATABASE_URL ?? env.DATABASE_URL,
159
+ schemaName: env.DBOS_SCHEMA ?? "dbos",
160
+ executorId: env.DBOS_EXECUTOR_ID ?? crypto.randomUUID(),
161
+ };
162
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Daily/weekly/monthly spend caps for a *group* of agents (BudgetGroup),
3
+ * layered on top of each agent's own per-run Agent.budgetUsd. Unlike
4
+ * budget.ts (pure, no DB, no network), this module queries Postgres: it
5
+ * computes how much of a group's remaining period budget a given agent
6
+ * should be allowed to spend on its next run, and hands back an
7
+ * *effective* per-run budget the caller uses in place of the agent's raw
8
+ * budgetUsd. The engine's existing turn-by-turn machinery (pre-flight
9
+ * refuse, mid-stream cutoff, wind-down — see engine-native.ts) then
10
+ * enforces that tightened ceiling with no changes of its own: a group
11
+ * whose period budget is already exhausted simply produces an effective
12
+ * budget of 0, which the engine's turn-1 gate already refuses exactly
13
+ * like any other zero/negative budget.
14
+ */
15
+ import type { Agent, BudgetGroup, PrismaClient } from "@prisma/client";
16
+ export type Period = "day" | "week" | "month";
17
+ export type BudgetGroupsDb = Pick<PrismaClient, "budgetGroup" | "run" | "agent">;
18
+ /**
19
+ * Calendar-aligned UTC period start. "week" is the ISO week: Monday 00:00
20
+ * UTC. `now`'s own time-of-day/day-of-week is truncated away, never
21
+ * rounded up -- a period start is always <= now.
22
+ */
23
+ export declare function periodStart(period: Period, now: Date): Date;
24
+ export interface PeriodSpend {
25
+ period: Period;
26
+ capUsd: number;
27
+ spentUsd: number;
28
+ remainingUsd: number;
29
+ }
30
+ /**
31
+ * Live per-period spend for whichever of a group's daily/weekly/monthly
32
+ * caps are actually set -- unset periods are omitted from the result, not
33
+ * returned with a null cap. One query at the widest configured window
34
+ * (month contains week contains day), bucketed in JS, rather than a
35
+ * separate DB round trip per period.
36
+ */
37
+ export declare function computeGroupSpend(db: BudgetGroupsDb, group: Pick<BudgetGroup, "dailyBudgetUsd" | "weeklyBudgetUsd" | "monthlyBudgetUsd">, memberAgentIds: string[], now?: Date): Promise<PeriodSpend[]>;
38
+ export interface RunTreeSpend {
39
+ rootRunId: string;
40
+ capUsd: number;
41
+ spentUsd: number;
42
+ remainingUsd: number;
43
+ }
44
+ /**
45
+ * Real-time shared budget scope for a sub-agent dispatch: the whole run tree
46
+ * rooted at `parentRunId`'s ultimate ancestor shares one ceiling —
47
+ * the root's own effective budget (its own budgetUsd, itself tightened by
48
+ * its own BudgetGroup if any) minus everything every run in the tree has
49
+ * spent so far. The root's ceiling is recomputed fresh here, not pinned
50
+ * from whatever it was when the root run started — consistent with
51
+ * BudgetGroup periods already always being live-recomputed rather than
52
+ * snapshotted.
53
+ */
54
+ export declare function computeRunTreeSpend(db: BudgetGroupsDb, parentRunId: string, now?: Date): Promise<RunTreeSpend>;
55
+ export interface EffectiveBudgetResult {
56
+ effectiveBudgetUsd: number;
57
+ /** Which constraint(s), if any, are tighter than the agent's own budgetUsd right now. */
58
+ constrainedBy: (Period | "run-tree")[];
59
+ }
60
+ /**
61
+ * The per-run budget ceiling to actually use for `agent`: its own
62
+ * budgetUsd, tightened to whatever's left of its budget group's
63
+ * daily/weekly/monthly caps (whichever are set) AND, when `parentRunId` is
64
+ * given (this run was dispatched as a sub-agent — see AgentSubAgent),
65
+ * whatever's left of its run tree's shared ceiling. These are independent,
66
+ * composable constraints: the final ceiling is the minimum of all that
67
+ * apply, never one replacing another. An ungrouped, top-level agent run
68
+ * with no caps configured is returned unchanged. Also logs a warning
69
+ * (never blocks, never throws) once a period's spend crosses the group's
70
+ * own warnThresholdRatio.
71
+ */
72
+ export declare function effectiveBudgetForRun(db: BudgetGroupsDb, agent: Pick<Agent, "id" | "budgetGroupId" | "budgetUsd">, now?: Date, parentRunId?: string): Promise<EffectiveBudgetResult>;
@@ -0,0 +1,149 @@
1
+ import { logger } from "./logger.js";
2
+ const budgetGroupLog = logger.child({ module: "budget-groups" });
3
+ /**
4
+ * Calendar-aligned UTC period start. "week" is the ISO week: Monday 00:00
5
+ * UTC. `now`'s own time-of-day/day-of-week is truncated away, never
6
+ * rounded up -- a period start is always <= now.
7
+ */
8
+ export function periodStart(period, now) {
9
+ if (period === "day") {
10
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
11
+ }
12
+ if (period === "month") {
13
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
14
+ }
15
+ // getUTCDay(): Sunday=0 .. Saturday=6. Treat Sunday as 7 so "days since
16
+ // Monday" is always 0-6.
17
+ const daysSinceMonday = (now.getUTCDay() || 7) - 1;
18
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - daysSinceMonday));
19
+ }
20
+ function toNullableNumber(value) {
21
+ return value === null || value === undefined ? null : Number(value);
22
+ }
23
+ /**
24
+ * Live per-period spend for whichever of a group's daily/weekly/monthly
25
+ * caps are actually set -- unset periods are omitted from the result, not
26
+ * returned with a null cap. One query at the widest configured window
27
+ * (month contains week contains day), bucketed in JS, rather than a
28
+ * separate DB round trip per period.
29
+ */
30
+ export async function computeGroupSpend(db, group, memberAgentIds, now = new Date()) {
31
+ const caps = [
32
+ { period: "day", capUsd: toNullableNumber(group.dailyBudgetUsd) },
33
+ { period: "week", capUsd: toNullableNumber(group.weeklyBudgetUsd) },
34
+ { period: "month", capUsd: toNullableNumber(group.monthlyBudgetUsd) },
35
+ ];
36
+ const configured = caps.filter((c) => c.capUsd !== null);
37
+ if (configured.length === 0)
38
+ return [];
39
+ // A cap can be configured before any agent is ever attached to the group
40
+ // (e.g. right after create_budget_group) -- that's still a real,
41
+ // reportable state (spentUsd: 0), not "nothing to report." Only skip the
42
+ // query itself as a cheap no-op; an empty `agentId: { in: [] }` would
43
+ // match nothing anyway.
44
+ const widestStart = periodStart("month", now);
45
+ const runs = memberAgentIds.length === 0
46
+ ? []
47
+ : await db.run.findMany({
48
+ where: { agentId: { in: memberAgentIds }, startedAt: { gte: widestStart } },
49
+ select: { costUsd: true, startedAt: true },
50
+ });
51
+ return configured.map(({ period, capUsd }) => {
52
+ const start = periodStart(period, now);
53
+ const spentUsd = runs.filter((r) => r.startedAt >= start).reduce((sum, r) => sum + Number(r.costUsd), 0);
54
+ return { period, capUsd, spentUsd, remainingUsd: Math.max(0, capUsd - spentUsd) };
55
+ });
56
+ }
57
+ /** Walks parentRunId upward from `runId` to the tree's root (parentRunId null). */
58
+ async function findRootRun(db, runId) {
59
+ let current = await db.run.findUniqueOrThrow({
60
+ where: { id: runId },
61
+ select: { id: true, agentId: true, parentRunId: true },
62
+ });
63
+ while (current.parentRunId) {
64
+ current = await db.run.findUniqueOrThrow({
65
+ where: { id: current.parentRunId },
66
+ select: { id: true, agentId: true, parentRunId: true },
67
+ });
68
+ }
69
+ return current;
70
+ }
71
+ /** Breadth-first walk down childRuns from `rootRunId`, collecting every run id in the tree. */
72
+ async function collectTreeRunIds(db, rootRunId) {
73
+ const all = [rootRunId];
74
+ let frontier = [rootRunId];
75
+ while (frontier.length > 0) {
76
+ const children = await db.run.findMany({ where: { parentRunId: { in: frontier } }, select: { id: true } });
77
+ const childIds = children.map((c) => c.id);
78
+ all.push(...childIds);
79
+ frontier = childIds;
80
+ }
81
+ return all;
82
+ }
83
+ /**
84
+ * Real-time shared budget scope for a sub-agent dispatch: the whole run tree
85
+ * rooted at `parentRunId`'s ultimate ancestor shares one ceiling —
86
+ * the root's own effective budget (its own budgetUsd, itself tightened by
87
+ * its own BudgetGroup if any) minus everything every run in the tree has
88
+ * spent so far. The root's ceiling is recomputed fresh here, not pinned
89
+ * from whatever it was when the root run started — consistent with
90
+ * BudgetGroup periods already always being live-recomputed rather than
91
+ * snapshotted.
92
+ */
93
+ export async function computeRunTreeSpend(db, parentRunId, now = new Date()) {
94
+ const root = await findRootRun(db, parentRunId);
95
+ const treeRunIds = await collectTreeRunIds(db, root.id);
96
+ const rows = await db.run.findMany({ where: { id: { in: treeRunIds } }, select: { costUsd: true } });
97
+ const spentUsd = rows.reduce((sum, r) => sum + Number(r.costUsd), 0);
98
+ const rootAgent = await db.agent.findUniqueOrThrow({
99
+ where: { id: root.agentId },
100
+ select: { id: true, budgetGroupId: true, budgetUsd: true },
101
+ });
102
+ // Root has no parentRunId of its own, so this terminates in one level —
103
+ // no unbounded recursion regardless of how deep `parentRunId` itself was.
104
+ const rootCeiling = await effectiveBudgetForRun(db, rootAgent, now);
105
+ const capUsd = rootCeiling.effectiveBudgetUsd;
106
+ return { rootRunId: root.id, capUsd, spentUsd, remainingUsd: Math.max(0, capUsd - spentUsd) };
107
+ }
108
+ /**
109
+ * The per-run budget ceiling to actually use for `agent`: its own
110
+ * budgetUsd, tightened to whatever's left of its budget group's
111
+ * daily/weekly/monthly caps (whichever are set) AND, when `parentRunId` is
112
+ * given (this run was dispatched as a sub-agent — see AgentSubAgent),
113
+ * whatever's left of its run tree's shared ceiling. These are independent,
114
+ * composable constraints: the final ceiling is the minimum of all that
115
+ * apply, never one replacing another. An ungrouped, top-level agent run
116
+ * with no caps configured is returned unchanged. Also logs a warning
117
+ * (never blocks, never throws) once a period's spend crosses the group's
118
+ * own warnThresholdRatio.
119
+ */
120
+ export async function effectiveBudgetForRun(db, agent, now = new Date(), parentRunId) {
121
+ const ownBudgetUsd = Number(agent.budgetUsd);
122
+ const candidates = [];
123
+ if (agent.budgetGroupId) {
124
+ const group = await db.budgetGroup.findUnique({
125
+ where: { id: agent.budgetGroupId },
126
+ include: { agents: { select: { id: true } } },
127
+ });
128
+ if (group) {
129
+ const spend = await computeGroupSpend(db, group, group.agents.map((a) => a.id), now);
130
+ const warnThresholdRatio = Number(group.warnThresholdRatio);
131
+ for (const s of spend) {
132
+ candidates.push({ value: s.remainingUsd, reason: s.period });
133
+ if (s.spentUsd >= s.capUsd * warnThresholdRatio) {
134
+ budgetGroupLog.warn({ groupId: group.id, groupName: group.name, period: s.period, capUsd: s.capUsd, spentUsd: s.spentUsd }, `budget group "${group.name}"'s ${s.period} spend ($${s.spentUsd.toFixed(4)}) has reached ` +
135
+ `${(warnThresholdRatio * 100).toFixed(0)}% of its $${s.capUsd.toFixed(4)} cap`);
136
+ }
137
+ }
138
+ }
139
+ }
140
+ if (parentRunId) {
141
+ const tree = await computeRunTreeSpend(db, parentRunId, now);
142
+ candidates.push({ value: tree.remainingUsd, reason: "run-tree" });
143
+ }
144
+ if (candidates.length === 0)
145
+ return { effectiveBudgetUsd: ownBudgetUsd, constrainedBy: [] };
146
+ const constrainedBy = candidates.filter((c) => c.value < ownBudgetUsd).map((c) => c.reason);
147
+ const effectiveBudgetUsd = Math.min(ownBudgetUsd, ...candidates.map((c) => c.value));
148
+ return { effectiveBudgetUsd, constrainedBy };
149
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Budget guardrail — wardby's differentiator. Pure functions only: no
3
+ * network, no DB, so the math is unit-testable in isolation. The runner
4
+ * composes these with a live `LlmProvider` for the pre-flight refuse and the
5
+ * mid-stream cutoff.
6
+ */
7
+ import type { LlmMessage, LlmProvider, LlmToolDef } from "../providers/index.js";
8
+ export interface BudgetAgent {
9
+ model: string;
10
+ budgetUsd: number;
11
+ }
12
+ export interface InputEstimate {
13
+ tokens: number;
14
+ costUsd: number;
15
+ }
16
+ /**
17
+ * Input-only cost estimate: `countTokens` -> `priceUsd` with zero output
18
+ * tokens. `tools` must be passed whenever the turn being estimated will
19
+ * actually send them — a provider bills the serialized tool schemas as
20
+ * input tokens, so omitting them here when the real call includes them
21
+ * under-counts (this was a real bug: a turn-1 refuse with tools attached
22
+ * could admit a run whose real input cost already exceeded budget).
23
+ *
24
+ * `cacheRatio` is the fraction of input tokens expected to be served from
25
+ * a provider-side cache (e.g. observed from a prior turn's real usage),
26
+ * clamped to [0,1] and defaulting to 0 — today's full-rate behavior, where
27
+ * every input token is priced fresh.
28
+ */
29
+ export declare function estimateInputCost(agent: BudgetAgent, messages: LlmMessage[], llm: LlmProvider, tools?: LlmToolDef[], cacheRatio?: number): Promise<InputEstimate>;
30
+ /**
31
+ * Shared refuse/cutoff threshold: a projected cost that has reached the
32
+ * budget is treated as over budget, not just exceeded past it — the run
33
+ * must stop at the edge, not one token past it.
34
+ */
35
+ export declare function isOverBudget(costUsd: number, budgetUsd: number): boolean;
36
+ /**
37
+ * The pre-flight estimate is a local, un-verified tokenizer count — it must
38
+ * never let an over-budget run start because it happened to undercount.
39
+ * Rather than trust that the estimate is always biased high (measured once
40
+ * against a real call: 40 estimated vs. 38 actual for gpt-5.6-luna — high,
41
+ * but only one data point across one model/message shape), pad the refuse
42
+ * decision by a fixed margin. `checkTokenCalibration` below is how drift
43
+ * beyond this margin gets surfaced, so the margin can be tightened with
44
+ * evidence instead of guesswork.
45
+ */
46
+ export declare const PREFLIGHT_SAFETY_MARGIN_RATIO = 0.1;
47
+ export declare function applyPreflightSafetyMargin(costUsd: number): number;
48
+ export interface TokenCalibration {
49
+ estimatedTokens: number;
50
+ actualTokens: number;
51
+ /** (estimated - actual) / actual. Positive = overestimated (safe). Negative = underestimated (dangerous). */
52
+ deltaRatio: number;
53
+ diverged: boolean;
54
+ }
55
+ /**
56
+ * Compares the local pre-flight token estimate against the provider's
57
+ * authoritative post-call usage. This is calibration, not a runtime gate:
58
+ * the estimate is inherently approximate (fixed per-message overhead
59
+ * constants, possible encoding mismatch), so divergence should be logged
60
+ * for a human to investigate, never used to fail or block a run that
61
+ * already has real, authoritative usage numbers.
62
+ */
63
+ export declare function checkTokenCalibration(estimatedTokens: number, actualTokens: number, thresholdRatio?: number): TokenCalibration;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Budget guardrail — wardby's differentiator. Pure functions only: no
3
+ * network, no DB, so the math is unit-testable in isolation. The runner
4
+ * composes these with a live `LlmProvider` for the pre-flight refuse and the
5
+ * mid-stream cutoff.
6
+ */
7
+ /**
8
+ * Input-only cost estimate: `countTokens` -> `priceUsd` with zero output
9
+ * tokens. `tools` must be passed whenever the turn being estimated will
10
+ * actually send them — a provider bills the serialized tool schemas as
11
+ * input tokens, so omitting them here when the real call includes them
12
+ * under-counts (this was a real bug: a turn-1 refuse with tools attached
13
+ * could admit a run whose real input cost already exceeded budget).
14
+ *
15
+ * `cacheRatio` is the fraction of input tokens expected to be served from
16
+ * a provider-side cache (e.g. observed from a prior turn's real usage),
17
+ * clamped to [0,1] and defaulting to 0 — today's full-rate behavior, where
18
+ * every input token is priced fresh.
19
+ */
20
+ export async function estimateInputCost(agent, messages, llm, tools, cacheRatio = 0) {
21
+ const tokens = await llm.countTokens(agent.model, messages, tools);
22
+ const cachedInputTokens = Math.round(tokens * Math.min(Math.max(cacheRatio, 0), 1));
23
+ const costUsd = llm.priceUsd(agent.model, {
24
+ inputTokens: tokens,
25
+ outputTokens: 0,
26
+ cachedInputTokens,
27
+ });
28
+ return { tokens, costUsd };
29
+ }
30
+ /**
31
+ * Shared refuse/cutoff threshold: a projected cost that has reached the
32
+ * budget is treated as over budget, not just exceeded past it — the run
33
+ * must stop at the edge, not one token past it.
34
+ */
35
+ export function isOverBudget(costUsd, budgetUsd) {
36
+ return costUsd >= budgetUsd;
37
+ }
38
+ /**
39
+ * The pre-flight estimate is a local, un-verified tokenizer count — it must
40
+ * never let an over-budget run start because it happened to undercount.
41
+ * Rather than trust that the estimate is always biased high (measured once
42
+ * against a real call: 40 estimated vs. 38 actual for gpt-5.6-luna — high,
43
+ * but only one data point across one model/message shape), pad the refuse
44
+ * decision by a fixed margin. `checkTokenCalibration` below is how drift
45
+ * beyond this margin gets surfaced, so the margin can be tightened with
46
+ * evidence instead of guesswork.
47
+ */
48
+ export const PREFLIGHT_SAFETY_MARGIN_RATIO = 0.1;
49
+ export function applyPreflightSafetyMargin(costUsd) {
50
+ return costUsd * (1 + PREFLIGHT_SAFETY_MARGIN_RATIO);
51
+ }
52
+ const CALIBRATION_DIVERGENCE_THRESHOLD = 0.1;
53
+ /**
54
+ * Compares the local pre-flight token estimate against the provider's
55
+ * authoritative post-call usage. This is calibration, not a runtime gate:
56
+ * the estimate is inherently approximate (fixed per-message overhead
57
+ * constants, possible encoding mismatch), so divergence should be logged
58
+ * for a human to investigate, never used to fail or block a run that
59
+ * already has real, authoritative usage numbers.
60
+ */
61
+ export function checkTokenCalibration(estimatedTokens, actualTokens, thresholdRatio = CALIBRATION_DIVERGENCE_THRESHOLD) {
62
+ const deltaRatio = actualTokens === 0 ? 0 : (estimatedTokens - actualTokens) / actualTokens;
63
+ return {
64
+ estimatedTokens,
65
+ actualTokens,
66
+ deltaRatio,
67
+ diverged: Math.abs(deltaRatio) > thresholdRatio,
68
+ };
69
+ }