@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,103 @@
1
+ import { INTERNAL_ERROR } from "@modelcontextprotocol/server";
2
+ import { publicCodingRunResult } from "../../coding/protocol.js";
3
+ const DEFAULT_POLL_INTERVAL_MS = 1000;
4
+ export function createTaskResult(row, ttlMs) {
5
+ return {
6
+ resultType: "task",
7
+ taskId: row.id,
8
+ status: "working",
9
+ createdAt: row.createdAt.toISOString(),
10
+ lastUpdatedAt: row.updatedAt.toISOString(),
11
+ ttlMs,
12
+ pollIntervalMs: DEFAULT_POLL_INTERVAL_MS,
13
+ };
14
+ }
15
+ /** Starts a run-backed task: persists the Task row BEFORE returning, so a client's first tasks/get always finds it. */
16
+ export async function createRunTask(runId, principalId, db, ttlMs) {
17
+ const row = await db.task.create({
18
+ data: {
19
+ kind: "run",
20
+ runId,
21
+ principalId,
22
+ status: "working",
23
+ ttlAt: new Date(Date.now() + ttlMs),
24
+ },
25
+ });
26
+ return createTaskResult(row, ttlMs);
27
+ }
28
+ function mapTerminalRunStatus(status, run) {
29
+ const usage = { tokensIn: run.tokensIn, tokensOut: run.tokensOut, costUsd: Number(run.costUsd) };
30
+ const codingResult = publicCodingRunResult(run.codingRun?.result);
31
+ if (codingResult)
32
+ return { status: "completed", result: codingResult };
33
+ switch (status) {
34
+ case "succeeded":
35
+ return { status: "completed", result: { finalText: run.finalText ?? "", usage } };
36
+ case "budget_exhausted":
37
+ // A successful terminal, not an error — the run did what the budget allowed.
38
+ return { status: "completed", result: { summary: run.finalText ?? "", usage, budgetExhausted: true } };
39
+ case "refused":
40
+ return {
41
+ status: "completed",
42
+ result: { refusalReason: run.error ?? "", usage: { tokensIn: 0, tokensOut: 0, costUsd: 0 } },
43
+ };
44
+ case "failed":
45
+ case "lost":
46
+ return { status: "failed", error: { code: INTERNAL_ERROR, message: run.error ?? `Run ${status}.` } };
47
+ }
48
+ }
49
+ /** Reads a task's current state, deriving a run task's status from the live Run.status. */
50
+ export async function getTask(taskId, db) {
51
+ const task = await db.task.findUniqueOrThrow({ where: { id: taskId } });
52
+ const base = {
53
+ resultType: "complete",
54
+ taskId: task.id,
55
+ createdAt: task.createdAt.toISOString(),
56
+ lastUpdatedAt: task.updatedAt.toISOString(),
57
+ ttlMs: task.ttlAt.getTime() - Date.now(),
58
+ pollIntervalMs: DEFAULT_POLL_INTERVAL_MS,
59
+ };
60
+ if (task.status === "cancelled") {
61
+ return { ...base, status: "cancelled" };
62
+ }
63
+ if (!task.runId) {
64
+ throw new Error(`Task "${taskId}" has kind "${task.kind}" with no runId — only kind:"run" tasks are supported.`);
65
+ }
66
+ const run = await db.run.findUniqueOrThrow({
67
+ where: { id: task.runId },
68
+ include: { codingRun: { select: { result: true } } },
69
+ });
70
+ switch (run.status) {
71
+ case "pending":
72
+ case "running":
73
+ return { ...base, status: "working", statusMessage: `cost so far: $${Number(run.costUsd).toFixed(6)}` };
74
+ case "cancelled":
75
+ return { ...base, status: "cancelled" };
76
+ case "succeeded":
77
+ case "budget_exhausted":
78
+ case "refused":
79
+ case "failed":
80
+ case "lost": {
81
+ const mapped = mapTerminalRunStatus(run.status, run);
82
+ return { ...base, ...mapped };
83
+ }
84
+ }
85
+ }
86
+ /**
87
+ * Cooperatively stops a task's underlying run (best-effort — cancellation
88
+ * is eventually consistent). A no-op when the run has already reached a
89
+ * terminal status on its own; the stop hook is never called for a run
90
+ * that's already finished.
91
+ */
92
+ export async function cancelTask(taskId, db, stopRun) {
93
+ const task = await db.task.findUniqueOrThrow({ where: { id: taskId } });
94
+ if (task.status === "cancelled" || !task.runId)
95
+ return;
96
+ const run = await db.run.findUniqueOrThrow({ where: { id: task.runId } });
97
+ if (run.status !== "pending" && run.status !== "running") {
98
+ // Already terminal — nothing to cancel.
99
+ return;
100
+ }
101
+ await stopRun(task.runId);
102
+ await db.task.update({ where: { id: taskId }, data: { status: "cancelled" } });
103
+ }
@@ -0,0 +1,2 @@
1
+ import type { WardbyMcpServer } from "../server.js";
2
+ export declare function registerAgentTools(mcp: WardbyMcpServer): void;
@@ -0,0 +1,375 @@
1
+ /** Agent CRUD with scope checks in server.ts and ownership checks here. */
2
+ import { Prisma } from "@prisma/client";
3
+ import { z } from "zod";
4
+ import { CodingProfilePatchSchema, CodingProfileSchema } from "../../coding/profile.js";
5
+ import { codingProviderSupportsModel } from "../../coding/provider.js";
6
+ import { validateCronExpression } from "../../core/cron.js";
7
+ import { assertCanMutate, canRead, requireOwnedAgent, requireReadableBudgetGroup, visibleToPrincipal, } from "../auth/ownership.js";
8
+ import { requireScope } from "../auth/resource-server.js";
9
+ import { McpError } from "../errors.js";
10
+ import { textResult } from "./text-result.js";
11
+ // workerImageRef is the BYO-arbitrary-image escape hatch (see
12
+ // resolveCodingWorkerImage in container.ts) — setting or changing it
13
+ // requires a step-up beyond agents:write, the same pattern make_owner below
14
+ // uses for its own sensitive, ownership-bypassing mutation.
15
+ function requireWorkerImageRefScope(ctx) {
16
+ requireScope(ctx, ctx.canonicalUri, "agents:admin");
17
+ }
18
+ const MAX_AGENT_NAME_CHARS = 200;
19
+ const MAX_SYSTEM_PROMPT_CHARS = 64 * 1024;
20
+ const MAX_MODEL_CHARS = 128;
21
+ const MAX_BUDGET_USD = 1_000_000;
22
+ const agentFields = {
23
+ name: z.string().trim().min(1).max(MAX_AGENT_NAME_CHARS),
24
+ systemPrompt: z.string().min(1).max(MAX_SYSTEM_PROMPT_CHARS),
25
+ model: z.string().trim().min(1).max(MAX_MODEL_CHARS),
26
+ budgetUsd: z.number().finite().positive().max(MAX_BUDGET_USD),
27
+ maxTurns: z.number().int().min(1).max(100),
28
+ schedule: z.string().trim().min(1).max(256),
29
+ timezone: z.string().trim().min(1).max(128),
30
+ scheduleEnabled: z.boolean(),
31
+ kind: z.enum(["native", "coding"]),
32
+ budgetGroupId: z.string().min(1).max(128),
33
+ memoryEnabled: z.boolean(),
34
+ };
35
+ const CreateAgentSchema = z
36
+ .object({
37
+ name: agentFields.name,
38
+ systemPrompt: agentFields.systemPrompt,
39
+ model: agentFields.model,
40
+ budgetUsd: agentFields.budgetUsd,
41
+ maxTurns: agentFields.maxTurns.optional(),
42
+ schedule: agentFields.schedule.optional(),
43
+ timezone: agentFields.timezone.optional(),
44
+ scheduleEnabled: agentFields.scheduleEnabled.optional(),
45
+ kind: agentFields.kind.default("native"),
46
+ budgetGroupId: agentFields.budgetGroupId.optional(),
47
+ memoryEnabled: agentFields.memoryEnabled.default(false),
48
+ codingProfile: CodingProfileSchema.optional(),
49
+ })
50
+ .strict()
51
+ .superRefine((value, ctx) => {
52
+ if (value.kind === "coding" && !value.codingProfile) {
53
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["codingProfile"], message: "is required for coding agents" });
54
+ }
55
+ if (value.kind === "native" && value.codingProfile) {
56
+ ctx.addIssue({
57
+ code: z.ZodIssueCode.custom,
58
+ path: ["codingProfile"],
59
+ message: "is only valid for coding agents",
60
+ });
61
+ }
62
+ if (value.kind === "coding" &&
63
+ value.codingProfile &&
64
+ !codingProviderSupportsModel(value.codingProfile.provider, value.model)) {
65
+ ctx.addIssue({
66
+ code: z.ZodIssueCode.custom,
67
+ path: ["model"],
68
+ message: `is not supported by coding provider "${value.codingProfile.provider}"`,
69
+ });
70
+ }
71
+ const enabledSchedule = value.schedule && value.scheduleEnabled !== false;
72
+ if (value.kind === "coding" && enabledSchedule && !value.codingProfile?.defaultTask) {
73
+ ctx.addIssue({
74
+ code: z.ZodIssueCode.custom,
75
+ path: ["codingProfile", "defaultTask"],
76
+ message: "is required for an enabled schedule",
77
+ });
78
+ }
79
+ });
80
+ const UpdateAgentSchema = z
81
+ .object({
82
+ id: z.string().min(1).max(128),
83
+ name: agentFields.name.optional(),
84
+ systemPrompt: agentFields.systemPrompt.optional(),
85
+ model: agentFields.model.optional(),
86
+ budgetUsd: agentFields.budgetUsd.optional(),
87
+ maxTurns: agentFields.maxTurns.optional(),
88
+ schedule: agentFields.schedule.nullable().optional(),
89
+ timezone: agentFields.timezone.optional(),
90
+ scheduleEnabled: agentFields.scheduleEnabled.optional(),
91
+ kind: agentFields.kind.optional(),
92
+ budgetGroupId: agentFields.budgetGroupId.nullable().optional(),
93
+ memoryEnabled: agentFields.memoryEnabled.optional(),
94
+ codingProfile: CodingProfilePatchSchema.optional(),
95
+ })
96
+ .strict();
97
+ const profileJsonSchema = {
98
+ type: "object",
99
+ additionalProperties: false,
100
+ properties: {
101
+ provider: { type: "string", enum: ["codex", "claude-code"] },
102
+ repository: { type: "string" },
103
+ baseRef: { type: "string" },
104
+ defaultTask: { type: ["string", "null"] },
105
+ allowWebhookTaskOverride: { type: "boolean" },
106
+ timeoutSec: { type: "integer", minimum: 60, maximum: 7200 },
107
+ allowedEgress: { type: "array", maxItems: 64, items: { type: "string" } },
108
+ protectedPaths: { type: "array", minItems: 1, maxItems: 128, items: { type: "string" } },
109
+ toolchain: { type: "string", enum: ["node", "node-python"] },
110
+ toolchainVersion: { type: ["string", "null"] },
111
+ workerImageRef: { type: ["string", "null"] },
112
+ workspaceDiskMb: {
113
+ type: ["integer", "null"],
114
+ minimum: 64,
115
+ maximum: 32768,
116
+ description: "Workspace disk size in MiB. Null uses the deployment default (CODING_DISK_MB), capped by the operator's CODING_MAX_DISK_MB.",
117
+ },
118
+ },
119
+ };
120
+ function invalidArguments(label, error) {
121
+ const details = error.issues.map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`).join("; ");
122
+ return new McpError(400, `Invalid ${label}: ${details}`);
123
+ }
124
+ function parseCreateAgent(args) {
125
+ const result = CreateAgentSchema.safeParse(args);
126
+ if (!result.success)
127
+ throw invalidArguments("create_agent arguments", result.error);
128
+ return result.data;
129
+ }
130
+ function parseUpdateAgent(args) {
131
+ const result = UpdateAgentSchema.safeParse(args);
132
+ if (!result.success)
133
+ throw invalidArguments("update_agent arguments", result.error);
134
+ return result.data;
135
+ }
136
+ function validateSchedule(schedule, timezone) {
137
+ if (!schedule)
138
+ return;
139
+ try {
140
+ validateCronExpression(schedule, timezone);
141
+ }
142
+ catch (error) {
143
+ throw new McpError(400, `Invalid schedule/timezone: ${error instanceof Error ? error.message : String(error)}`);
144
+ }
145
+ }
146
+ function storedProfile(profile) {
147
+ return CodingProfileSchema.parse({
148
+ provider: profile.provider,
149
+ repository: profile.repository,
150
+ baseRef: profile.baseRef,
151
+ defaultTask: profile.defaultTask,
152
+ allowWebhookTaskOverride: profile.allowWebhookTaskOverride,
153
+ timeoutSec: profile.timeoutSec,
154
+ allowedEgress: profile.allowedEgress,
155
+ protectedPaths: profile.protectedPaths,
156
+ toolchain: profile.toolchain,
157
+ toolchainVersion: profile.toolchainVersion,
158
+ workerImageRef: profile.workerImageRef,
159
+ workspaceDiskMb: profile.workspaceDiskMb,
160
+ });
161
+ }
162
+ export function registerAgentTools(mcp) {
163
+ mcp.registerTool({
164
+ name: "create_agent",
165
+ scope: "agents:write",
166
+ inputSchema: {
167
+ type: "object",
168
+ additionalProperties: false,
169
+ properties: {
170
+ name: { type: "string" },
171
+ systemPrompt: { type: "string" },
172
+ model: { type: "string" },
173
+ budgetUsd: { type: "number" },
174
+ maxTurns: { type: "integer" },
175
+ schedule: { type: "string" },
176
+ timezone: { type: "string" },
177
+ scheduleEnabled: { type: "boolean" },
178
+ kind: { type: "string", enum: ["native", "coding"] },
179
+ budgetGroupId: { type: "string" },
180
+ memoryEnabled: { type: "boolean" },
181
+ codingProfile: { ...profileJsonSchema, required: ["repository"] },
182
+ },
183
+ required: ["name", "systemPrompt", "model", "budgetUsd"],
184
+ },
185
+ handler: async (rawArgs, ctx) => {
186
+ const args = parseCreateAgent(rawArgs);
187
+ if (args.codingProfile?.workerImageRef != null)
188
+ requireWorkerImageRefScope(ctx);
189
+ validateSchedule(args.schedule, args.timezone ?? "UTC");
190
+ if (args.budgetGroupId) {
191
+ await requireReadableBudgetGroup(ctx.db, args.budgetGroupId, ctx.principal.id);
192
+ }
193
+ const { codingProfile, ...agentData } = args;
194
+ const agent = await ctx.db.agent.create({
195
+ data: {
196
+ ...agentData,
197
+ ownerId: ctx.principal.id,
198
+ ...(codingProfile ? { codingProfile: { create: codingProfile } } : {}),
199
+ },
200
+ include: { codingProfile: true },
201
+ });
202
+ return textResult(agent);
203
+ },
204
+ });
205
+ mcp.registerTool({
206
+ name: "update_agent",
207
+ scope: "agents:write",
208
+ inputSchema: {
209
+ type: "object",
210
+ additionalProperties: false,
211
+ properties: {
212
+ id: { type: "string" },
213
+ name: { type: "string" },
214
+ systemPrompt: { type: "string" },
215
+ model: { type: "string" },
216
+ budgetUsd: { type: "number" },
217
+ maxTurns: { type: "integer" },
218
+ schedule: { type: ["string", "null"] },
219
+ timezone: { type: "string" },
220
+ scheduleEnabled: { type: "boolean" },
221
+ kind: { type: "string", enum: ["native", "coding"] },
222
+ budgetGroupId: { type: ["string", "null"] },
223
+ memoryEnabled: { type: "boolean" },
224
+ codingProfile: profileJsonSchema,
225
+ },
226
+ required: ["id"],
227
+ },
228
+ handler: async (rawArgs, ctx) => {
229
+ const args = parseUpdateAgent(rawArgs);
230
+ if (args.codingProfile?.workerImageRef !== undefined)
231
+ requireWorkerImageRefScope(ctx);
232
+ const agent = await ctx.db.$transaction(async (tx) => {
233
+ const existing = await tx.agent.findUnique({ where: { id: args.id }, include: { codingProfile: true } });
234
+ if (!existing)
235
+ throw new McpError(404, `Agent "${args.id}" not found.`);
236
+ assertCanMutate(existing.ownerId, ctx.principal.id, `Agent "${args.id}" is not owned by the caller.`);
237
+ if (args.budgetGroupId) {
238
+ await requireReadableBudgetGroup(ctx.db, args.budgetGroupId, ctx.principal.id);
239
+ }
240
+ const nextKind = args.kind ?? existing.kind;
241
+ if (nextKind === "native" && args.codingProfile) {
242
+ throw new McpError(400, "A coding profile is only valid for coding agents.");
243
+ }
244
+ let nextProfile = null;
245
+ if (nextKind === "coding") {
246
+ const attachedTools = await tx.agentTool.count({ where: { agentId: args.id } });
247
+ if (attachedTools > 0) {
248
+ throw new McpError(400, "Detach all native sandbox tools before changing an agent to coding.");
249
+ }
250
+ const currentProfile = existing.codingProfile ? storedProfile(existing.codingProfile) : undefined;
251
+ const profileResult = CodingProfileSchema.safeParse({ ...currentProfile, ...args.codingProfile });
252
+ if (!profileResult.success)
253
+ throw invalidArguments("coding profile", profileResult.error);
254
+ nextProfile = profileResult.data;
255
+ const nextModel = args.model ?? existing.model;
256
+ if (!codingProviderSupportsModel(nextProfile.provider, nextModel)) {
257
+ throw new McpError(400, `Model "${nextModel}" is not supported by coding provider "${nextProfile.provider}".`);
258
+ }
259
+ }
260
+ const nextSchedule = args.schedule !== undefined ? args.schedule : existing.schedule;
261
+ const nextTimezone = args.timezone ?? existing.timezone;
262
+ const nextScheduleEnabled = args.scheduleEnabled ?? existing.scheduleEnabled;
263
+ validateSchedule(nextSchedule, nextTimezone);
264
+ if (nextKind === "coding" && nextSchedule && nextScheduleEnabled && !nextProfile?.defaultTask) {
265
+ throw new McpError(400, "A default task is required for an enabled coding-agent schedule.");
266
+ }
267
+ const { id, codingProfile: _profilePatch, ...updates } = args;
268
+ const profileMutation = nextKind === "native"
269
+ ? existing.codingProfile
270
+ ? { delete: true }
271
+ : undefined
272
+ : existing.codingProfile
273
+ ? { update: nextProfile }
274
+ : { create: nextProfile };
275
+ return tx.agent.update({
276
+ where: { id },
277
+ data: {
278
+ ...updates,
279
+ ...(profileMutation ? { codingProfile: profileMutation } : {}),
280
+ },
281
+ include: { codingProfile: true },
282
+ });
283
+ }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
284
+ return textResult(agent);
285
+ },
286
+ });
287
+ mcp.registerTool({
288
+ name: "list_agents",
289
+ scope: "agents:read",
290
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
291
+ handler: async (_args, ctx) => {
292
+ const agents = await ctx.db.agent.findMany({
293
+ where: visibleToPrincipal(ctx.principal.id),
294
+ include: { codingProfile: true },
295
+ });
296
+ return textResult(agents);
297
+ },
298
+ });
299
+ mcp.registerTool({
300
+ name: "get_agent",
301
+ scope: "agents:read",
302
+ inputSchema: {
303
+ type: "object",
304
+ properties: { id: { type: "string" } },
305
+ required: ["id"],
306
+ additionalProperties: false,
307
+ },
308
+ handler: async (args, ctx) => {
309
+ const agent = await ctx.db.agent.findUnique({
310
+ where: { id: args.id },
311
+ include: { tools: { include: { tool: true } }, codingProfile: true },
312
+ });
313
+ if (!agent || !canRead(agent.ownerId, ctx.principal.id)) {
314
+ throw new McpError(404, `Agent "${args.id}" not found.`);
315
+ }
316
+ return textResult(agent);
317
+ },
318
+ });
319
+ mcp.registerTool({
320
+ name: "delete_agent",
321
+ scope: "agents:write",
322
+ inputSchema: {
323
+ type: "object",
324
+ properties: { id: { type: "string" } },
325
+ required: ["id"],
326
+ additionalProperties: false,
327
+ },
328
+ handler: async (args, ctx) => {
329
+ await requireOwnedAgent(ctx.db, args.id, ctx.principal.id);
330
+ try {
331
+ await ctx.db.agent.delete({ where: { id: args.id } });
332
+ }
333
+ catch (err) {
334
+ // Run.agentId has no onDelete rule, so Postgres refuses to delete an
335
+ // agent that has ever run. That is deliberate - runs are the cost and
336
+ // budget history - so say so instead of surfacing the FK violation.
337
+ if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2003") {
338
+ throw new McpError(409, "Agent has run history, which is kept for cost and budget accounting, so it cannot be deleted. " +
339
+ "Use disable_schedule to stop it running.");
340
+ }
341
+ throw err;
342
+ }
343
+ return textResult({ deleted: args.id });
344
+ },
345
+ });
346
+ // Deliberately bypasses ownership entirely (no assertCanMutate/
347
+ // requireOwnedAgent call) — that's the point of this tool vs. the
348
+ // self-service null-owner-is-public-mutable rule everything else here
349
+ // follows. Gated on agents:admin, a step up from agents:write, so an
350
+ // ordinary caller can never reach it regardless of what they own.
351
+ // ownerId: null releases the agent back to public, same as any agent
352
+ // that was never assigned an owner.
353
+ mcp.registerTool({
354
+ name: "make_owner",
355
+ scope: "agents:admin",
356
+ inputSchema: {
357
+ type: "object",
358
+ additionalProperties: false,
359
+ properties: { agentId: { type: "string" }, ownerId: { type: ["string", "null"] } },
360
+ required: ["agentId", "ownerId"],
361
+ },
362
+ handler: async (args, ctx) => {
363
+ const agent = await ctx.db.agent.findUnique({ where: { id: args.agentId } });
364
+ if (!agent)
365
+ throw new McpError(404, `Agent "${args.agentId}" not found.`);
366
+ if (args.ownerId !== null) {
367
+ const principal = await ctx.db.principal.findUnique({ where: { id: args.ownerId } });
368
+ if (!principal)
369
+ throw new McpError(400, `Principal "${args.ownerId}" not found.`);
370
+ }
371
+ const updated = await ctx.db.agent.update({ where: { id: args.agentId }, data: { ownerId: args.ownerId } });
372
+ return textResult(updated);
373
+ },
374
+ });
375
+ }
@@ -0,0 +1,2 @@
1
+ import type { WardbyMcpServer } from "../server.js";
2
+ export declare function registerBudgetGroupTools(mcp: WardbyMcpServer): void;
@@ -0,0 +1,136 @@
1
+ /** BudgetGroup CRUD with scope checks in server.ts and ownership checks here. */
2
+ import { z } from "zod";
3
+ import { computeGroupSpend } from "../../core/budget-groups.js";
4
+ import { canRead, requireOwnedBudgetGroup, visibleToPrincipal } from "../auth/ownership.js";
5
+ import { McpError } from "../errors.js";
6
+ import { textResult } from "./text-result.js";
7
+ const MAX_GROUP_NAME_CHARS = 200;
8
+ const MAX_BUDGET_USD = 1_000_000;
9
+ const capField = z.number().finite().positive().max(MAX_BUDGET_USD);
10
+ const CreateBudgetGroupSchema = z
11
+ .object({
12
+ name: z.string().trim().min(1).max(MAX_GROUP_NAME_CHARS),
13
+ dailyBudgetUsd: capField.optional(),
14
+ weeklyBudgetUsd: capField.optional(),
15
+ monthlyBudgetUsd: capField.optional(),
16
+ warnThresholdRatio: z.number().finite().gt(0).lte(1).optional(),
17
+ })
18
+ .strict()
19
+ .superRefine((value, ctx) => {
20
+ if (value.dailyBudgetUsd === undefined &&
21
+ value.weeklyBudgetUsd === undefined &&
22
+ value.monthlyBudgetUsd === undefined) {
23
+ ctx.addIssue({
24
+ code: z.ZodIssueCode.custom,
25
+ path: ["dailyBudgetUsd"],
26
+ message: "at least one of dailyBudgetUsd/weeklyBudgetUsd/monthlyBudgetUsd is required",
27
+ });
28
+ }
29
+ });
30
+ const UpdateBudgetGroupSchema = z
31
+ .object({
32
+ id: z.string().min(1).max(128),
33
+ name: z.string().trim().min(1).max(MAX_GROUP_NAME_CHARS).optional(),
34
+ dailyBudgetUsd: capField.nullable().optional(),
35
+ weeklyBudgetUsd: capField.nullable().optional(),
36
+ monthlyBudgetUsd: capField.nullable().optional(),
37
+ warnThresholdRatio: z.number().finite().gt(0).lte(1).optional(),
38
+ })
39
+ .strict();
40
+ function invalidArguments(label, error) {
41
+ const details = error.issues.map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`).join("; ");
42
+ return new McpError(400, `Invalid ${label}: ${details}`);
43
+ }
44
+ const capSchemaProps = {
45
+ dailyBudgetUsd: { type: ["number", "null"] },
46
+ weeklyBudgetUsd: { type: ["number", "null"] },
47
+ monthlyBudgetUsd: { type: ["number", "null"] },
48
+ warnThresholdRatio: { type: "number" },
49
+ };
50
+ export function registerBudgetGroupTools(mcp) {
51
+ mcp.registerTool({
52
+ name: "create_budget_group",
53
+ scope: "budget_groups:write",
54
+ inputSchema: {
55
+ type: "object",
56
+ additionalProperties: false,
57
+ properties: { name: { type: "string" }, ...capSchemaProps },
58
+ required: ["name"],
59
+ },
60
+ handler: async (rawArgs, ctx) => {
61
+ const result = CreateBudgetGroupSchema.safeParse(rawArgs);
62
+ if (!result.success)
63
+ throw invalidArguments("create_budget_group arguments", result.error);
64
+ const group = await ctx.db.budgetGroup.create({
65
+ data: { ...result.data, ownerId: ctx.principal.id },
66
+ });
67
+ return textResult(group);
68
+ },
69
+ });
70
+ mcp.registerTool({
71
+ name: "update_budget_group",
72
+ scope: "budget_groups:write",
73
+ inputSchema: {
74
+ type: "object",
75
+ additionalProperties: false,
76
+ properties: { id: { type: "string" }, name: { type: "string" }, ...capSchemaProps },
77
+ required: ["id"],
78
+ },
79
+ handler: async (rawArgs, ctx) => {
80
+ const result = UpdateBudgetGroupSchema.safeParse(rawArgs);
81
+ if (!result.success)
82
+ throw invalidArguments("update_budget_group arguments", result.error);
83
+ const { id, ...updates } = result.data;
84
+ await requireOwnedBudgetGroup(ctx.db, id, ctx.principal.id);
85
+ const group = await ctx.db.budgetGroup.update({ where: { id }, data: updates });
86
+ return textResult(group);
87
+ },
88
+ });
89
+ mcp.registerTool({
90
+ name: "list_budget_groups",
91
+ scope: "agents:read",
92
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
93
+ handler: async (_args, ctx) => {
94
+ const groups = await ctx.db.budgetGroup.findMany({ where: visibleToPrincipal(ctx.principal.id) });
95
+ return textResult(groups);
96
+ },
97
+ });
98
+ mcp.registerTool({
99
+ name: "get_budget_group",
100
+ scope: "agents:read",
101
+ inputSchema: {
102
+ type: "object",
103
+ properties: { id: { type: "string" } },
104
+ required: ["id"],
105
+ additionalProperties: false,
106
+ },
107
+ handler: async (args, ctx) => {
108
+ const group = await ctx.db.budgetGroup.findUnique({
109
+ where: { id: args.id },
110
+ include: { agents: { select: { id: true, name: true } } },
111
+ });
112
+ if (!group || !canRead(group.ownerId, ctx.principal.id)) {
113
+ throw new McpError(404, `Budget group "${args.id}" not found.`);
114
+ }
115
+ const spend = await computeGroupSpend(ctx.db, group, group.agents.map((a) => a.id), new Date());
116
+ return textResult({ ...group, spend });
117
+ },
118
+ });
119
+ mcp.registerTool({
120
+ name: "delete_budget_group",
121
+ scope: "budget_groups:write",
122
+ inputSchema: {
123
+ type: "object",
124
+ properties: { id: { type: "string" } },
125
+ required: ["id"],
126
+ additionalProperties: false,
127
+ },
128
+ handler: async (args, ctx) => {
129
+ await requireOwnedBudgetGroup(ctx.db, args.id, ctx.principal.id);
130
+ // Agent.budgetGroupId -> BudgetGroup is ON DELETE SET NULL: member
131
+ // agents are ungrouped automatically, never blocked or cascaded.
132
+ await ctx.db.budgetGroup.delete({ where: { id: args.id } });
133
+ return textResult({ deleted: args.id });
134
+ },
135
+ });
136
+ }
@@ -0,0 +1,2 @@
1
+ import type { WardbyMcpServer } from "../server.js";
2
+ export declare function registerDatastoreTools(mcp: WardbyMcpServer): void;