@wardby/cli 0.2.1 → 0.3.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 (407) hide show
  1. package/.env.example +34 -4
  2. package/README.md +41 -4
  3. package/dist/claude-coding-worker/driver.d.ts +6 -1
  4. package/dist/claude-coding-worker/driver.js +27 -1
  5. package/dist/claude-coding-worker/main.js +2 -0
  6. package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
  7. package/dist/claude-coding-worker/tool-socket.js +26 -0
  8. package/dist/cli-help.d.ts +1 -1
  9. package/dist/cli-help.js +13 -4
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +219 -29
  12. package/dist/coding/collect-exclude.d.ts +25 -0
  13. package/dist/coding/collect-exclude.js +76 -0
  14. package/dist/coding/profile.d.ts +67 -22
  15. package/dist/coding/profile.js +58 -25
  16. package/dist/coding/protected-path-wording.d.ts +24 -0
  17. package/dist/coding/protected-path-wording.js +55 -0
  18. package/dist/coding/protected-paths.d.ts +31 -0
  19. package/dist/coding/protected-paths.js +48 -0
  20. package/dist/coding/protocol.d.ts +75 -4
  21. package/dist/coding/protocol.js +107 -7
  22. package/dist/coding/registry/adapters.d.ts +2 -0
  23. package/dist/coding/registry/adapters.js +9 -0
  24. package/dist/coding/registry/allowlist.d.ts +9 -0
  25. package/dist/coding/registry/allowlist.js +28 -0
  26. package/dist/coding/registry/json-scan.d.ts +55 -0
  27. package/dist/coding/registry/json-scan.js +191 -0
  28. package/dist/coding/registry/lockfiles.d.ts +6 -0
  29. package/dist/coding/registry/lockfiles.js +89 -0
  30. package/dist/coding/registry/npm-lockfile.d.ts +9 -0
  31. package/dist/coding/registry/npm-lockfile.js +73 -0
  32. package/dist/coding/registry/npm-plan.d.ts +29 -0
  33. package/dist/coding/registry/npm-plan.js +289 -0
  34. package/dist/coding/registry/npm.d.ts +7 -0
  35. package/dist/coding/registry/npm.js +252 -0
  36. package/dist/coding/registry/pypi.d.ts +4 -0
  37. package/dist/coding/registry/pypi.js +210 -0
  38. package/dist/coding/registry/report.d.ts +37 -0
  39. package/dist/coding/registry/report.js +31 -0
  40. package/dist/coding/registry/token.d.ts +4 -0
  41. package/dist/coding/registry/token.js +7 -0
  42. package/dist/coding/registry/types.d.ts +286 -0
  43. package/dist/coding/registry/types.js +19 -0
  44. package/dist/coding/registry/worker-config.d.ts +14 -0
  45. package/dist/coding/registry/worker-config.js +31 -0
  46. package/dist/coding/services/builtins.d.ts +22 -0
  47. package/dist/coding/services/builtins.js +85 -0
  48. package/dist/coding/services/catalog.d.ts +443 -0
  49. package/dist/coding/services/catalog.js +159 -0
  50. package/dist/coding/services/declaration.d.ts +11 -0
  51. package/dist/coding/services/declaration.js +114 -0
  52. package/dist/coding/services/note.d.ts +8 -0
  53. package/dist/coding/services/note.js +15 -0
  54. package/dist/coding/services/resolve.d.ts +25 -0
  55. package/dist/coding/services/resolve.js +50 -0
  56. package/dist/coding/services/wording.d.ts +25 -0
  57. package/dist/coding/services/wording.js +70 -0
  58. package/dist/coding-proxy/main.js +24 -4
  59. package/dist/coding-worker/artifact.d.ts +6 -0
  60. package/dist/coding-worker/debug-trace.d.ts +37 -0
  61. package/dist/coding-worker/debug-trace.js +117 -0
  62. package/dist/coding-worker/driver.d.ts +13 -1
  63. package/dist/coding-worker/driver.js +102 -13
  64. package/dist/coding-worker/errors.js +8 -0
  65. package/dist/coding-worker/main.js +10 -2
  66. package/dist/coding-worker/sdk.d.ts +2 -2
  67. package/dist/coding-worker/sdk.js +7 -2
  68. package/dist/coding-worker/types.d.ts +3 -0
  69. package/dist/config/providers.d.ts +41 -0
  70. package/dist/config/providers.js +66 -0
  71. package/dist/core/budget-groups.d.ts +108 -5
  72. package/dist/core/budget-groups.js +125 -19
  73. package/dist/core/budget-wording.d.ts +22 -0
  74. package/dist/core/budget-wording.js +55 -0
  75. package/dist/core/datastores.js +18 -2
  76. package/dist/core/db.d.ts +5 -1
  77. package/dist/core/db.js +8 -2
  78. package/dist/core/dispatch.d.ts +33 -6
  79. package/dist/core/dispatch.js +260 -43
  80. package/dist/core/engine-native.js +27 -10
  81. package/dist/core/grants.d.ts +86 -0
  82. package/dist/core/grants.js +126 -0
  83. package/dist/core/host-events.d.ts +49 -0
  84. package/dist/core/host-events.js +257 -0
  85. package/dist/core/host-identity-links.d.ts +54 -0
  86. package/dist/core/host-identity-links.js +189 -0
  87. package/dist/core/host-status.d.ts +61 -0
  88. package/dist/core/host-status.js +211 -0
  89. package/dist/core/in-flight-runs.d.ts +8 -0
  90. package/dist/core/in-flight-runs.js +56 -0
  91. package/dist/core/provider-wording.d.ts +12 -0
  92. package/dist/core/provider-wording.js +44 -0
  93. package/dist/core/reconciler.d.ts +38 -2
  94. package/dist/core/reconciler.js +86 -2
  95. package/dist/core/repo-access.d.ts +99 -0
  96. package/dist/core/repo-access.js +136 -0
  97. package/dist/core/review-host-checks.d.ts +11 -0
  98. package/dist/core/review-host-checks.js +41 -0
  99. package/dist/core/review-host-tools.d.ts +47 -0
  100. package/dist/core/review-host-tools.js +347 -0
  101. package/dist/core/run-heartbeat.d.ts +27 -0
  102. package/dist/core/run-heartbeat.js +54 -0
  103. package/dist/core/runner.d.ts +30 -8
  104. package/dist/core/runner.js +237 -40
  105. package/dist/core/secrets.d.ts +11 -2
  106. package/dist/core/secrets.js +25 -6
  107. package/dist/core/subagent-memory-tools.d.ts +1 -1
  108. package/dist/core/subagent-memory-tools.js +16 -3
  109. package/dist/core/tool-admin.d.ts +81 -0
  110. package/dist/core/tool-admin.js +129 -0
  111. package/dist/core/tool-names.d.ts +42 -0
  112. package/dist/core/tool-names.js +64 -0
  113. package/dist/core/untrusted-content.d.ts +32 -0
  114. package/dist/core/untrusted-content.js +72 -0
  115. package/dist/core/webhooks.d.ts +8 -1
  116. package/dist/core/webhooks.js +23 -2
  117. package/dist/generated/prisma/browser.d.ts +100 -0
  118. package/dist/generated/prisma/client.d.ts +100 -0
  119. package/dist/generated/prisma/commonInputTypes.d.ts +30 -0
  120. package/dist/generated/prisma/enums.d.ts +6 -0
  121. package/dist/generated/prisma/enums.js +6 -1
  122. package/dist/generated/prisma/internal/class.d.ts +143 -0
  123. package/dist/generated/prisma/internal/class.js +4 -4
  124. package/dist/generated/prisma/internal/prismaNamespace.d.ts +1746 -577
  125. package/dist/generated/prisma/internal/prismaNamespace.js +179 -6
  126. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +186 -0
  127. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +179 -6
  128. package/dist/generated/prisma/models/Agent.d.ts +294 -1
  129. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  130. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  131. package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
  132. package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
  133. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
  134. package/dist/generated/prisma/models/CodingProxySession.d.ts +123 -2
  135. package/dist/generated/prisma/models/CodingRun.d.ts +1276 -95
  136. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  137. package/dist/generated/prisma/models/CodingService.js +1 -0
  138. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  139. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  140. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  141. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  142. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  143. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  144. package/dist/generated/prisma/models/Principal.d.ts +455 -0
  145. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  146. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  147. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  148. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  149. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  150. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  151. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  152. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  153. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  154. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  155. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  156. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  157. package/dist/generated/prisma/models/Run.d.ts +389 -1
  158. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  159. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  160. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  161. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  162. package/dist/generated/prisma/models/Tool.d.ts +15 -3
  163. package/dist/generated/prisma/models.d.ts +13 -0
  164. package/dist/help/build.d.ts +1 -0
  165. package/dist/help/build.js +9 -0
  166. package/dist/help/catalog.d.ts +24 -0
  167. package/dist/help/catalog.js +160 -0
  168. package/dist/help/cli.d.ts +2 -0
  169. package/dist/help/cli.js +65 -0
  170. package/dist/help/runtime.d.ts +3 -0
  171. package/dist/help/runtime.js +44 -0
  172. package/dist/help/search.d.ts +9 -0
  173. package/dist/help/search.js +104 -0
  174. package/dist/help-index.json +660 -0
  175. package/dist/import/cli-args.js +3 -2
  176. package/dist/import/create.d.ts +5 -0
  177. package/dist/import/create.js +67 -13
  178. package/dist/import/index.js +19 -9
  179. package/dist/import/neutral-schema.d.ts +11 -11
  180. package/dist/mcp/auth/access.d.ts +70 -0
  181. package/dist/mcp/auth/access.js +58 -0
  182. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  183. package/dist/mcp/auth/grants-cli.js +518 -0
  184. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  185. package/dist/mcp/auth/host-account-cli.js +47 -0
  186. package/dist/mcp/auth/ownership.d.ts +26 -52
  187. package/dist/mcp/auth/ownership.js +19 -14
  188. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  189. package/dist/mcp/auth/repo-authorization.js +51 -0
  190. package/dist/mcp/auth/resource-server.d.ts +31 -2
  191. package/dist/mcp/auth/resource-server.js +84 -3
  192. package/dist/mcp/auth/self-hosted/browser.js +2 -2
  193. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  194. package/dist/mcp/auth/self-hosted/credentials.d.ts +20 -2
  195. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  196. package/dist/mcp/auth/self-hosted/session.d.ts +2 -1
  197. package/dist/mcp/context.d.ts +27 -1
  198. package/dist/mcp/errors.d.ts +25 -6
  199. package/dist/mcp/errors.js +98 -0
  200. package/dist/mcp/host-events/github-ingress.d.ts +36 -0
  201. package/dist/mcp/host-events/github-ingress.js +92 -0
  202. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  203. package/dist/mcp/host-events/github-user-callback.js +50 -0
  204. package/dist/mcp/index.d.ts +8 -1
  205. package/dist/mcp/index.js +106 -16
  206. package/dist/mcp/server.js +27 -9
  207. package/dist/mcp/tools/agents.js +446 -48
  208. package/dist/mcp/tools/budget-groups.js +3 -3
  209. package/dist/mcp/tools/datastore.js +22 -15
  210. package/dist/mcp/tools/grants.d.ts +2 -0
  211. package/dist/mcp/tools/grants.js +239 -0
  212. package/dist/mcp/tools/help.d.ts +5 -0
  213. package/dist/mcp/tools/help.js +67 -0
  214. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  215. package/dist/mcp/tools/host-accounts.js +114 -0
  216. package/dist/mcp/tools/memory.d.ts +7 -1
  217. package/dist/mcp/tools/memory.js +5 -5
  218. package/dist/mcp/tools/repositories.d.ts +2 -0
  219. package/dist/mcp/tools/repositories.js +181 -0
  220. package/dist/mcp/tools/runs.d.ts +6 -0
  221. package/dist/mcp/tools/runs.js +60 -6
  222. package/dist/mcp/tools/scheduling.js +3 -6
  223. package/dist/mcp/tools/secrets.js +18 -6
  224. package/dist/mcp/tools/services.d.ts +2 -0
  225. package/dist/mcp/tools/services.js +222 -0
  226. package/dist/mcp/tools/subagents.js +52 -16
  227. package/dist/mcp/tools/tools.d.ts +41 -0
  228. package/dist/mcp/tools/tools.js +201 -40
  229. package/dist/mcp/tools/trigger.js +36 -6
  230. package/dist/mcp/tools/webhooks.js +15 -4
  231. package/dist/mcp/transport/streamable-http.d.ts +10 -0
  232. package/dist/mcp/transport/streamable-http.js +32 -2
  233. package/dist/providers/auth/delegating.d.ts +19 -0
  234. package/dist/providers/auth/delegating.js +71 -0
  235. package/dist/providers/auth/index.d.ts +2 -0
  236. package/dist/providers/auth/index.js +11 -0
  237. package/dist/providers/auth/self-hosted.d.ts +2 -1
  238. package/dist/providers/auth/self-hosted.js +16 -4
  239. package/dist/providers/auth/types.d.ts +6 -0
  240. package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
  241. package/dist/providers/coding-proxy/memory-ledger.js +21 -2
  242. package/dist/providers/coding-proxy/metering.d.ts +4 -0
  243. package/dist/providers/coding-proxy/metering.js +28 -2
  244. package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
  245. package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
  246. package/dist/providers/coding-proxy/proxy.d.ts +13 -0
  247. package/dist/providers/coding-proxy/proxy.js +479 -18
  248. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  249. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  250. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  251. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  252. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  253. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  254. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  255. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  256. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  257. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  258. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  259. package/dist/providers/coding-proxy/registry/store.js +70 -0
  260. package/dist/providers/coding-proxy/runtime.d.ts +17 -0
  261. package/dist/providers/coding-proxy/runtime.js +63 -0
  262. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  263. package/dist/providers/coding-proxy/server.d.ts +11 -0
  264. package/dist/providers/coding-proxy/server.js +163 -0
  265. package/dist/providers/coding-proxy/types.d.ts +15 -0
  266. package/dist/providers/engine/types.d.ts +10 -0
  267. package/dist/providers/executor/build.d.ts +2 -2
  268. package/dist/providers/executor/composition.d.ts +3 -0
  269. package/dist/providers/executor/composition.js +19 -0
  270. package/dist/providers/executor/container.d.ts +105 -3
  271. package/dist/providers/executor/container.js +296 -20
  272. package/dist/providers/executor/dbos.d.ts +2 -3
  273. package/dist/providers/executor/in-process.d.ts +2 -3
  274. package/dist/providers/executor/routing.d.ts +7 -0
  275. package/dist/providers/executor/routing.js +13 -0
  276. package/dist/providers/executor/types.d.ts +22 -0
  277. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  278. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  279. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  280. package/dist/providers/jobs/collect-prune.js +27 -0
  281. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  282. package/dist/providers/jobs/docker-isolation.js +213 -22
  283. package/dist/providers/jobs/docker-services.d.ts +35 -0
  284. package/dist/providers/jobs/docker-services.js +191 -0
  285. package/dist/providers/jobs/docker.d.ts +49 -2
  286. package/dist/providers/jobs/docker.js +279 -32
  287. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  288. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  289. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  290. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  291. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  292. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  293. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  294. package/dist/providers/jobs/kubernetes.d.ts +21 -2
  295. package/dist/providers/jobs/kubernetes.js +112 -21
  296. package/dist/providers/jobs/types.d.ts +18 -0
  297. package/dist/providers/llm/anthropic.js +6 -2
  298. package/dist/providers/llm/bedrock.js +2 -1
  299. package/dist/providers/llm/claude-messages.d.ts +5 -1
  300. package/dist/providers/llm/claude-messages.js +1 -0
  301. package/dist/providers/llm/claude-provider.d.ts +3 -1
  302. package/dist/providers/llm/claude-provider.js +5 -1
  303. package/dist/providers/llm/index.d.ts +1 -1
  304. package/dist/providers/llm/index.js +1 -1
  305. package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
  306. package/dist/providers/llm/pricing-anthropic.js +32 -14
  307. package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
  308. package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
  309. package/dist/providers/llm/routing.d.ts +10 -1
  310. package/dist/providers/llm/routing.js +18 -0
  311. package/dist/providers/llm/types.d.ts +12 -0
  312. package/dist/providers/llm/types.js +8 -1
  313. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  314. package/dist/providers/review-host/diff-lines.js +59 -0
  315. package/dist/providers/review-host/github-events.d.ts +6 -0
  316. package/dist/providers/review-host/github-events.js +180 -0
  317. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  318. package/dist/providers/review-host/github-user-auth.js +128 -0
  319. package/dist/providers/review-host/github.d.ts +57 -0
  320. package/dist/providers/review-host/github.js +567 -0
  321. package/dist/providers/review-host/index.d.ts +12 -0
  322. package/dist/providers/review-host/index.js +30 -0
  323. package/dist/providers/review-host/review-format.d.ts +19 -0
  324. package/dist/providers/review-host/review-format.js +59 -0
  325. package/dist/providers/review-host/types.d.ts +284 -0
  326. package/dist/providers/review-host/types.js +24 -0
  327. package/dist/providers/vcs/git.d.ts +16 -6
  328. package/dist/providers/vcs/git.js +77 -13
  329. package/dist/providers/vcs/github.d.ts +74 -3
  330. package/dist/providers/vcs/github.js +195 -15
  331. package/dist/providers/vcs/types.d.ts +42 -4
  332. package/dist/quickstart/index.js +13 -2
  333. package/dist/sandbox/fetch-policy.d.ts +20 -2
  334. package/dist/sandbox/fetch-policy.js +64 -3
  335. package/dist/sandbox/host-functions.d.ts +9 -1
  336. package/dist/sandbox/host-functions.js +12 -5
  337. package/dist/serve.js +1 -1
  338. package/dist/wardby-bin.js +6 -0
  339. package/docs/README.md +30 -0
  340. package/docs/architecture-runtime.md +90 -0
  341. package/docs/assets/brand/wardby-icon-512.png +0 -0
  342. package/docs/assets/brand/wardby-icon.svg +16 -0
  343. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  344. package/docs/assets/brand/wardby-mascot.png +0 -0
  345. package/docs/assets/brand/wardby-mascot.svg +5 -0
  346. package/docs/assets/wardby-workflow.svg +106 -0
  347. package/docs/code-review-agents.md +481 -0
  348. package/docs/coding-agent-setup.md +172 -0
  349. package/docs/coding-packages.md +455 -0
  350. package/docs/coding-services.md +300 -0
  351. package/docs/coding-worker-byo-images.md +98 -0
  352. package/docs/coding-worker-isolation.md +1061 -0
  353. package/docs/getting-started-gke.md +615 -0
  354. package/docs/getting-started-identity-provider.md +308 -0
  355. package/docs/getting-started.md +133 -0
  356. package/docs/observability.md +53 -0
  357. package/docs/release-verification.md +66 -0
  358. package/docs/security-deployment.md +657 -0
  359. package/help/code-review-agents.md +31 -0
  360. package/help/coding-packages.md +31 -0
  361. package/help/coding-services.md +71 -0
  362. package/help/creating-agents.md +68 -0
  363. package/help/deploy-gke.md +39 -0
  364. package/help/deployment-targets.md +39 -0
  365. package/help/errors/budget-group-exhausted.md +25 -0
  366. package/help/errors/docker-isolation-unsupported.md +26 -0
  367. package/help/errors/protected-path.md +45 -0
  368. package/help/errors/repo-access.md +27 -0
  369. package/help/errors/service-declaration-invalid.md +27 -0
  370. package/help/errors/service-declaration-unavailable.md +25 -0
  371. package/help/errors/service-launcher-unsupported.md +30 -0
  372. package/help/errors/service-not-allowed.md +27 -0
  373. package/help/errors/service-unknown.md +23 -0
  374. package/help/errors/service-unready.md +49 -0
  375. package/help/getting-started.md +31 -0
  376. package/help/github.md +30 -0
  377. package/help/identity-and-access.md +39 -0
  378. package/help/mcp.md +30 -0
  379. package/help/native-capabilities.md +30 -0
  380. package/help/observability.md +41 -0
  381. package/help/operating-agents.md +30 -0
  382. package/help/security.md +27 -0
  383. package/help/troubleshooting/budgets.md +26 -0
  384. package/help/troubleshooting/coding-workers.md +47 -0
  385. package/help/troubleshooting/repository-access.md +27 -0
  386. package/package.json +12 -3
  387. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  388. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  389. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  390. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  391. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  392. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  393. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  394. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  395. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  396. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  397. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  398. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  399. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  400. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  401. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  402. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  403. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  404. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  405. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  406. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  407. package/prisma/schema.prisma +407 -21
@@ -0,0 +1,518 @@
1
+ /**
2
+ * `wardby grants migration-report [--json] | adopt-public --owner <subject>
3
+ * [--dry-run] | prune-bindings [--dry-run]`: the operator's side of the
4
+ * move from "owner-less = public" to explicit grants (resource-sharing
5
+ * grants spec §3.7). Direct database access, like `auth host-account`.
6
+ *
7
+ * - migration-report is read-only and runs before AND after `migrate
8
+ * deploy`: it uses raw SQL over columns that exist in both, and predicts
9
+ * what the grants migration will do when its table isn't there yet.
10
+ * - adopt-public gives every owner-less agent an owner (one Serializable
11
+ * transaction per agent), keeping its grants (but lowering an everyone
12
+ * grant to read unless --keep-everyone-execute: the agent is about to
13
+ * regain the adopter's secrets and capabilities), keeping and reviving the new
14
+ * owner's own bindings, deleting the rest, and stamping the new owner's
15
+ * consent on attachments of its own tools. Other attachments stay inert
16
+ * and are printed with the attach_tool call that re-grants them.
17
+ * - prune-bindings deletes the secret/datastore bindings on owned agents
18
+ * whose resource another principal owns (inert at run time since the
19
+ * grants release) and prints each one: the "removed and listed" record.
20
+ */
21
+ import { parseArgs } from "node:util";
22
+ import { Prisma } from "#prisma";
23
+ import { canDelegate } from "../../core/grants.js";
24
+ import { resolvePrincipal } from "./principal.js";
25
+ const USAGE = "Use grants migration-report [--json] | grants adopt-public --owner <subject> [--dry-run] [--keep-everyone-execute] | grants prune-bindings [--dry-run].";
26
+ /** The attach_tool call (MCP) that re-grants an attachment's capabilities, for its owner to review and run. */
27
+ function regrantCall(agentId, toolId, caps) {
28
+ return `attach_tool ${JSON.stringify({
29
+ agentId,
30
+ toolId,
31
+ allowedSecrets: caps.allowedSecrets,
32
+ allowedDatastorePrefixes: caps.allowedDatastorePrefixes,
33
+ allowedHosts: caps.allowedHosts,
34
+ allowedSharedDatastorePrefixes: caps.allowedSharedDatastorePrefixes,
35
+ })}`;
36
+ }
37
+ function hasCapabilities(caps) {
38
+ const nonEmpty = (v) => Array.isArray(v) ? v.length > 0 : v !== null && typeof v === "object" && Object.keys(v).length > 0;
39
+ return (nonEmpty(caps.allowedSecrets) ||
40
+ nonEmpty(caps.allowedDatastorePrefixes) ||
41
+ nonEmpty(caps.allowedHosts) ||
42
+ nonEmpty(caps.allowedSharedDatastorePrefixes));
43
+ }
44
+ export const BEHAVIOUR_CHANGES = [
45
+ "Other principals can no longer edit a formerly public agent: update_agent, set/disable_schedule, delete_agent, attach/detach_tool, attach/detach_subagent, attach/detach_secret, attach/detach_datastore, datastore_set/delete, set/delete_agent_memory, create_webhook, unlink_repository.",
46
+ "They can no longer read its data: get/list_agent_memory, datastore_get/list, or other principals' runs.",
47
+ "They keep list_agents, get_agent (other owners' tool code hidden), list_subagents, list_repositories, list_tools, trigger_agent and the MCP prompts (everyone holds execute until adoption; adopt-public lowers it to read unless --keep-everyone-execute).",
48
+ "Until adopt-public runs, a formerly public agent runs with no owned secrets or datastores and without its tools' capabilities.",
49
+ "make_owner no longer accepts ownerId: null; non-owner coding task/baseRef needs the owner's allowWebhookTaskOverride.",
50
+ ];
51
+ export async function migrationReport(db) {
52
+ const [{ present }] = await db.$queryRaw `
53
+ SELECT to_regclass('"ResourceGrant"') IS NOT NULL AS "present"`;
54
+ const ownerless = present
55
+ ? await db.$queryRaw `
56
+ SELECT a."id" AS "agentId", a."name",
57
+ (SELECT g."level" FROM "ResourceGrant" g
58
+ WHERE g."resourceType" = 'agent' AND g."resourceId" = a."id" AND g."granteeKey" = 'everyone') AS "everyoneGrant"
59
+ FROM "Agent" a WHERE a."ownerId" IS NULL ORDER BY a."name"`
60
+ : (await db.$queryRaw `
61
+ SELECT a."id" AS "agentId", a."name" FROM "Agent" a WHERE a."ownerId" IS NULL ORDER BY a."name"`).map((row) => ({ ...row, everyoneGrant: "execute (pending migration)" }));
62
+ const revivedSecrets = await db.$queryRaw `
63
+ SELECT a."id" AS "agentId", p."id" AS "principalId", p."subject", x."boundName" AS "label"
64
+ FROM "AgentSecret" x JOIN "Secret" s ON s."id" = x."secretId" JOIN "Agent" a ON a."id" = x."agentId"
65
+ JOIN "Principal" p ON p."id" = s."ownerId"
66
+ WHERE a."ownerId" IS NULL ORDER BY x."boundName"`;
67
+ const revivedDatastores = await db.$queryRaw `
68
+ SELECT a."id" AS "agentId", p."id" AS "principalId", p."subject", x."boundName" AS "label"
69
+ FROM "AgentDatastore" x JOIN "Datastore" d ON d."id" = x."datastoreId" JOIN "Agent" a ON a."id" = x."agentId"
70
+ JOIN "Principal" p ON p."id" = d."ownerId"
71
+ WHERE a."ownerId" IS NULL ORDER BY x."boundName"`;
72
+ const revivedTools = await db.$queryRaw `
73
+ SELECT a."id" AS "agentId", p."id" AS "principalId", p."subject", t."name" AS "label",
74
+ x."allowedSecrets", x."allowedDatastorePrefixes", x."allowedHosts", x."allowedSharedDatastorePrefixes"
75
+ FROM "AgentTool" x JOIN "Tool" t ON t."id" = x."toolId" JOIN "Agent" a ON a."id" = x."agentId"
76
+ JOIN "Principal" p ON p."id" = t."ownerId"
77
+ WHERE a."ownerId" IS NULL ORDER BY t."name"`;
78
+ const revivedFor = (agentId) => {
79
+ const byPrincipal = new Map();
80
+ const entry = (row) => {
81
+ let e = byPrincipal.get(row.principalId);
82
+ if (!e) {
83
+ e = { principalId: row.principalId, subject: row.subject, secrets: [], datastores: [], toolCapabilities: [] };
84
+ byPrincipal.set(row.principalId, e);
85
+ }
86
+ return e;
87
+ };
88
+ for (const r of revivedSecrets)
89
+ if (r.agentId === agentId)
90
+ entry(r).secrets.push(r.label);
91
+ for (const r of revivedDatastores)
92
+ if (r.agentId === agentId)
93
+ entry(r).datastores.push(r.label);
94
+ for (const r of revivedTools)
95
+ if (r.agentId === agentId && hasCapabilities(r))
96
+ entry(r).toolCapabilities.push(r.label);
97
+ return [...byPrincipal.values()].sort((a, b) => a.subject.localeCompare(b.subject));
98
+ };
99
+ // A binding resolves only while the resource's owner is the agent's owner,
100
+ // both non-null (core/secrets.ts, core/datastores.ts).
101
+ const bindings = await db.$queryRaw `
102
+ SELECT 'secret' AS "kind", a."id" AS "agentId", a."name" AS "agentName", a."ownerId" AS "agentOwnerId",
103
+ x."boundName", s."id" AS "resourceId", s."ownerId" AS "resourceOwnerId"
104
+ FROM "AgentSecret" x JOIN "Secret" s ON s."id" = x."secretId" JOIN "Agent" a ON a."id" = x."agentId"
105
+ WHERE a."ownerId" IS NULL OR s."ownerId" IS NULL OR s."ownerId" <> a."ownerId"
106
+ UNION ALL
107
+ SELECT 'datastore', a."id", a."name", a."ownerId", x."boundName", d."id", d."ownerId"
108
+ FROM "AgentDatastore" x JOIN "Datastore" d ON d."id" = x."datastoreId" JOIN "Agent" a ON a."id" = x."agentId"
109
+ WHERE a."ownerId" IS NULL OR d."ownerId" IS NULL OR d."ownerId" <> a."ownerId"
110
+ ORDER BY "agentName", "boundName"`;
111
+ const [{ stamped }] = await db.$queryRaw `
112
+ SELECT EXISTS (
113
+ SELECT 1 FROM information_schema.columns
114
+ WHERE table_schema = current_schema() AND table_name = 'AgentTool' AND column_name = 'capabilitiesGrantedById'
115
+ ) AS "stamped"`;
116
+ // After the migration: honoured only when stamped by the current owner.
117
+ // Before it: what the migration will leave unstamped.
118
+ const suspended = stamped
119
+ ? await db.$queryRaw `
120
+ SELECT a."id" AS "agentId", a."name" AS "agentName", t."id" AS "toolId", t."name" AS "toolName",
121
+ x."allowedSecrets", x."allowedDatastorePrefixes", x."allowedHosts", x."allowedSharedDatastorePrefixes"
122
+ FROM "AgentTool" x JOIN "Agent" a ON a."id" = x."agentId" JOIN "Tool" t ON t."id" = x."toolId"
123
+ WHERE a."ownerId" IS NULL OR x."capabilitiesGrantedById" IS DISTINCT FROM a."ownerId"
124
+ ORDER BY a."name", t."name"`
125
+ : await db.$queryRaw `
126
+ SELECT a."id" AS "agentId", a."name" AS "agentName", t."id" AS "toolId", t."name" AS "toolName",
127
+ x."allowedSecrets", x."allowedDatastorePrefixes", x."allowedHosts", x."allowedSharedDatastorePrefixes"
128
+ FROM "AgentTool" x JOIN "Agent" a ON a."id" = x."agentId" JOIN "Tool" t ON t."id" = x."toolId"
129
+ WHERE a."ownerId" IS NULL OR (t."ownerId" IS NOT NULL AND t."ownerId" <> a."ownerId")
130
+ ORDER BY a."name", t."name"`;
131
+ const review = stamped
132
+ ? await db.$queryRaw `
133
+ SELECT a."id" AS "agentId", a."name" AS "agentName", t."id" AS "toolId", t."name" AS "toolName",
134
+ x."allowedSecrets", x."allowedDatastorePrefixes", x."allowedHosts", x."allowedSharedDatastorePrefixes"
135
+ FROM "AgentTool" x JOIN "Agent" a ON a."id" = x."agentId" JOIN "Tool" t ON t."id" = x."toolId"
136
+ WHERE a."ownerId" IS NOT NULL AND t."ownerId" IS NULL AND x."capabilitiesGrantedById" = a."ownerId"
137
+ ORDER BY a."name", t."name"`
138
+ : await db.$queryRaw `
139
+ SELECT a."id" AS "agentId", a."name" AS "agentName", t."id" AS "toolId", t."name" AS "toolName",
140
+ x."allowedSecrets", x."allowedDatastorePrefixes", x."allowedHosts", x."allowedSharedDatastorePrefixes"
141
+ FROM "AgentTool" x JOIN "Agent" a ON a."id" = x."agentId" JOIN "Tool" t ON t."id" = x."toolId"
142
+ WHERE a."ownerId" IS NOT NULL AND t."ownerId" IS NULL
143
+ ORDER BY a."name", t."name"`;
144
+ // canDelegate: same owner (null = null), or the parent's owner holds
145
+ // execute on the child. Before the migration, an owner-less child will
146
+ // hold everyone-execute.
147
+ const failingEdges = present
148
+ ? await db.$queryRaw `
149
+ SELECT e."parentAgentId", p."ownerId" AS "parentOwnerId", e."childAgentId", c."ownerId" AS "childOwnerId", e."boundName"
150
+ FROM "AgentSubAgent" e JOIN "Agent" p ON p."id" = e."parentAgentId" JOIN "Agent" c ON c."id" = e."childAgentId"
151
+ WHERE p."ownerId" IS DISTINCT FROM c."ownerId"
152
+ AND NOT (p."ownerId" IS NOT NULL AND EXISTS (
153
+ SELECT 1 FROM "ResourceGrant" g
154
+ WHERE g."resourceType" = 'agent' AND g."resourceId" = c."id"
155
+ AND g."granteeKey" IN ('principal:' || p."ownerId", 'everyone') AND g."level" IN ('execute', 'write')))
156
+ ORDER BY e."boundName"`
157
+ : await db.$queryRaw `
158
+ SELECT e."parentAgentId", p."ownerId" AS "parentOwnerId", e."childAgentId", c."ownerId" AS "childOwnerId", e."boundName"
159
+ FROM "AgentSubAgent" e JOIN "Agent" p ON p."id" = e."parentAgentId" JOIN "Agent" c ON c."id" = e."childAgentId"
160
+ WHERE p."ownerId" IS DISTINCT FROM c."ownerId" AND NOT (p."ownerId" IS NOT NULL AND c."ownerId" IS NULL)
161
+ ORDER BY e."boundName"`;
162
+ const onOwnerlessAgents = await db.$queryRaw `
163
+ SELECT w."id", w."agentId", w."ownerId" AS "createdBy"
164
+ FROM "Webhook" w JOIN "Agent" a ON a."id" = w."agentId" WHERE a."ownerId" IS NULL ORDER BY w."id"`;
165
+ // A webhook fires only while its creator owns the agent or holds execute.
166
+ const creatorLacksExecute = present
167
+ ? await db.$queryRaw `
168
+ SELECT w."id", w."agentId", w."ownerId" AS "createdBy"
169
+ FROM "Webhook" w JOIN "Agent" a ON a."id" = w."agentId"
170
+ WHERE (a."ownerId" IS NULL OR w."ownerId" IS DISTINCT FROM a."ownerId")
171
+ AND NOT EXISTS (
172
+ SELECT 1 FROM "ResourceGrant" g
173
+ WHERE g."resourceType" = 'agent' AND g."resourceId" = a."id"
174
+ AND (g."granteeKey" = 'everyone' OR (w."ownerId" IS NOT NULL AND g."granteeKey" = 'principal:' || w."ownerId"))
175
+ AND g."level" IN ('execute', 'write'))
176
+ ORDER BY w."id"`
177
+ : await db.$queryRaw `
178
+ SELECT w."id", w."agentId", w."ownerId" AS "createdBy"
179
+ FROM "Webhook" w JOIN "Agent" a ON a."id" = w."agentId"
180
+ WHERE a."ownerId" IS NOT NULL AND w."ownerId" IS DISTINCT FROM a."ownerId"
181
+ ORDER BY w."id"`;
182
+ const orphanGrants = present
183
+ ? await db.$queryRaw `
184
+ SELECT g."id", g."resourceType", g."resourceId" FROM "ResourceGrant" g
185
+ WHERE (g."resourceType" = 'agent' AND NOT EXISTS (SELECT 1 FROM "Agent" a WHERE a."id" = g."resourceId"))
186
+ OR (g."resourceType" = 'tool' AND NOT EXISTS (SELECT 1 FROM "Tool" t WHERE t."id" = g."resourceId"))
187
+ OR (g."resourceType" = 'budget_group'
188
+ AND NOT EXISTS (SELECT 1 FROM "BudgetGroup" b WHERE b."id" = g."resourceId"))
189
+ OR g."resourceType" NOT IN ('agent', 'tool', 'budget_group')
190
+ ORDER BY g."id"`
191
+ : null;
192
+ return {
193
+ grantsTable: present,
194
+ ownerlessAgents: ownerless.map((a) => ({ ...a, revivedByAdopter: revivedFor(a.agentId) })),
195
+ inertBindings: bindings.map((b) => ({ ...b, prunable: b.agentOwnerId !== null })),
196
+ suspendedCapabilities: suspended.filter(hasCapabilities).map((row) => ({
197
+ agentId: row.agentId,
198
+ agentName: row.agentName,
199
+ toolId: row.toolId,
200
+ toolName: row.toolName,
201
+ regrant: regrantCall(row.agentId, row.toolId, row),
202
+ })),
203
+ reviewCapabilities: review.filter(hasCapabilities).map((row) => ({
204
+ agentId: row.agentId,
205
+ agentName: row.agentName,
206
+ toolId: row.toolId,
207
+ toolName: row.toolName,
208
+ regrant: regrantCall(row.agentId, row.toolId, row),
209
+ })),
210
+ failingEdges,
211
+ webhooks: { onOwnerlessAgents, creatorLacksExecute },
212
+ behaviourChanges: BEHAVIOUR_CHANGES,
213
+ orphanGrants,
214
+ };
215
+ }
216
+ export function formatMigrationReport(report) {
217
+ const lines = [];
218
+ const section = (title, rows, empty = "none") => {
219
+ lines.push("", `== ${title}`);
220
+ lines.push(...(rows.length > 0 ? rows.map((r) => ` - ${r}`) : [` ${empty}`]));
221
+ };
222
+ lines.push(report.grantsTable
223
+ ? "Resource grants migration: applied."
224
+ : "Resource grants migration: NOT applied yet (predicting its effect).");
225
+ section("1. Owner-less agents (everyone grant)", report.ownerlessAgents.flatMap((a) => [
226
+ `${a.name} (${a.agentId}): everyone ${a.everyoneGrant ?? "none"}` +
227
+ (a.everyoneGrant ? " (adopt-public lowers it to read unless --keep-everyone-execute)" : ""),
228
+ ...a.revivedByAdopter.map((r) => ` adopting as ${r.subject} would bring back: secrets [${r.secrets.join(", ")}], datastores [${r.datastores.join(", ")}], capabilities of tools [${r.toolCapabilities.join(", ")}]`),
229
+ ]));
230
+ section("2. Cross-owner secret/datastore bindings (inert at run time)", report.inertBindings.map((b) => `${b.kind} "${b.boundName}" on ${b.agentName} (${b.agentId}): resource ${b.resourceId} owned by ${b.resourceOwnerId ?? "nobody"}, agent owned by ${b.agentOwnerId ?? "nobody"}` +
231
+ (b.prunable ? " -- removed by prune-bindings" : " -- decided by adopt-public")));
232
+ section("3. Attachments whose capabilities are (or will be) suspended", report.suspendedCapabilities.map((c) => `${c.agentName} / ${c.toolName}: re-grant with ${c.regrant}`));
233
+ section("3b. Owner-less-tool capabilities kept in force on owned agents (review: a previous owner may have set them)", report.reviewCapabilities.map((c) => `${c.agentName} / ${c.toolName}: currently ${c.regrant}`));
234
+ section("4. Sub-agent edges refused at run time (canDelegate)", report.failingEdges.map((e) => `${e.parentAgentId} (owner ${e.parentOwnerId ?? "nobody"}) -> ${e.childAgentId} (owner ${e.childOwnerId ?? "nobody"}) as "${e.boundName}"`));
235
+ section("5a. Webhooks on owner-less agents (fire through everyone-execute)", report.webhooks.onOwnerlessAgents.map((w) => `${w.id} on ${w.agentId}, created by ${w.createdBy ?? "unknown"}`));
236
+ section("5b. Webhooks whose creator lacks execute (will not fire)", report.webhooks.creatorLacksExecute.map((w) => `${w.id} on ${w.agentId}, created by ${w.createdBy ?? "unknown"}`));
237
+ section("6. Behaviour changes for formerly public agents", [
238
+ ...(report.ownerlessAgents.length > 0
239
+ ? [`affected: ${report.ownerlessAgents.map((a) => a.name).join(", ")}`]
240
+ : ["affected: none on this deployment"]),
241
+ ...report.behaviourChanges,
242
+ ]);
243
+ section("7. Orphan grants (resource gone)", report.orphanGrants === null ? [] : report.orphanGrants.map((g) => `${g.id}: ${g.resourceType} ${g.resourceId}`), report.orphanGrants === null ? "skipped: the ResourceGrant table does not exist yet" : "none");
244
+ return lines.join("\n").trimStart();
245
+ }
246
+ class DryRunRollback extends Error {
247
+ }
248
+ /** Gives every owner-less agent `ownerSubject` as its owner. See the module header. */
249
+ export async function adoptPublic(db, opts) {
250
+ // findUnique, never upsert: a typo must not mint a principal that then owns everything.
251
+ const owner = await db.principal.findUnique({ where: { subject: opts.ownerSubject } });
252
+ if (!owner) {
253
+ throw new Error(`No principal with subject "${opts.ownerSubject}" (they must have signed in at least once).`);
254
+ }
255
+ const candidates = await db.agent.findMany({
256
+ where: { ownerId: null },
257
+ select: { id: true },
258
+ orderBy: { name: "asc" },
259
+ });
260
+ const agents = [];
261
+ for (const { id } of candidates) {
262
+ let adopted = null;
263
+ try {
264
+ await db.$transaction(async (tx) => {
265
+ const agent = await tx.agent.findUnique({ where: { id } });
266
+ if (!agent || agent.ownerId !== null)
267
+ return; // adopted concurrently
268
+ await tx.agent.update({ where: { id }, data: { ownerId: owner.id } });
269
+ const secrets = await tx.agentSecret.findMany({
270
+ where: { agentId: id },
271
+ include: { secret: { select: { ownerId: true } } },
272
+ orderBy: { boundName: "asc" },
273
+ });
274
+ const datastores = await tx.agentDatastore.findMany({
275
+ where: { agentId: id },
276
+ include: { datastore: { select: { ownerId: true } } },
277
+ orderBy: { boundName: "asc" },
278
+ });
279
+ const foreignSecrets = secrets.filter((b) => b.secret.ownerId !== owner.id);
280
+ const foreignDatastores = datastores.filter((b) => b.datastore.ownerId !== owner.id);
281
+ for (const b of foreignSecrets) {
282
+ await tx.agentSecret.delete({ where: { agentId_secretId: { agentId: id, secretId: b.secretId } } });
283
+ }
284
+ for (const b of foreignDatastores) {
285
+ await tx.agentDatastore.delete({
286
+ where: { agentId_datastoreId: { agentId: id, datastoreId: b.datastoreId } },
287
+ });
288
+ }
289
+ // Only the tool's owner could attach its private tool to a public
290
+ // agent or change that attachment's capabilities, so those carry
291
+ // the new owner's consent. Everything else stays inert.
292
+ const attachments = await tx.agentTool.findMany({
293
+ where: { agentId: id },
294
+ include: { tool: { select: { id: true, name: true, ownerId: true } } },
295
+ orderBy: { toolId: "asc" },
296
+ });
297
+ const own = attachments.filter((at) => at.tool.ownerId === owner.id);
298
+ for (const at of own) {
299
+ await tx.agentTool.update({
300
+ where: { agentId_toolId: { agentId: id, toolId: at.toolId } },
301
+ data: { capabilitiesGrantedById: owner.id },
302
+ });
303
+ }
304
+ const edges = await tx.agentSubAgent.findMany({
305
+ where: { OR: [{ parentAgentId: id }, { childAgentId: id }] },
306
+ include: {
307
+ parent: { select: { id: true, ownerId: true } },
308
+ child: { select: { id: true, ownerId: true } },
309
+ },
310
+ orderBy: { boundName: "asc" },
311
+ });
312
+ // The agent is about to regain the adopter's secrets and tool
313
+ // capabilities: an everyone grant above read would let anyone run
314
+ // it with them (review I3). Lowered to read unless the operator
315
+ // explicitly keeps execute.
316
+ const everyone = await tx.resourceGrant.findUnique({
317
+ where: {
318
+ resourceType_resourceId_granteeKey: { resourceType: "agent", resourceId: id, granteeKey: "everyone" },
319
+ },
320
+ });
321
+ const everyoneGrant = { before: everyone?.level ?? null, after: everyone?.level ?? null };
322
+ if (everyone && everyone.level !== "read" && !opts.keepEveryoneExecute) {
323
+ await tx.resourceGrant.update({
324
+ where: { id: everyone.id },
325
+ data: { level: "read", source: "operator", grantedById: owner.id },
326
+ });
327
+ everyoneGrant.after = "read";
328
+ }
329
+ const failingEdges = [];
330
+ for (const edge of edges) {
331
+ if (!(await canDelegate(tx, edge.parent, edge.child))) {
332
+ failingEdges.push({
333
+ parentAgentId: edge.parentAgentId,
334
+ childAgentId: edge.childAgentId,
335
+ boundName: edge.boundName,
336
+ });
337
+ }
338
+ }
339
+ adopted = {
340
+ agentId: id,
341
+ name: agent.name,
342
+ bindingsRemoved: [
343
+ ...foreignSecrets.map((b) => ({
344
+ kind: "secret",
345
+ boundName: b.boundName,
346
+ resourceId: b.secretId,
347
+ })),
348
+ ...foreignDatastores.map((b) => ({
349
+ kind: "datastore",
350
+ boundName: b.boundName,
351
+ resourceId: b.datastoreId,
352
+ })),
353
+ ],
354
+ bindingsKept: [
355
+ ...secrets
356
+ .filter((b) => b.secret.ownerId === owner.id)
357
+ .map((b) => ({ kind: "secret", boundName: b.boundName })),
358
+ ...datastores
359
+ .filter((b) => b.datastore.ownerId === owner.id)
360
+ .map((b) => ({ kind: "datastore", boundName: b.boundName })),
361
+ ],
362
+ capabilitiesStamped: own.map((at) => at.tool.name),
363
+ foreignTools: attachments
364
+ .filter((at) => at.tool.ownerId !== owner.id)
365
+ .map((at) => ({
366
+ toolId: at.toolId,
367
+ toolName: at.tool.name,
368
+ toolOwnerId: at.tool.ownerId,
369
+ regrant: hasCapabilities(at) ? regrantCall(id, at.toolId, at) : null,
370
+ })),
371
+ failingEdges,
372
+ everyoneGrant,
373
+ };
374
+ if (opts.dryRun)
375
+ throw new DryRunRollback();
376
+ }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
377
+ }
378
+ catch (err) {
379
+ if (!(err instanceof DryRunRollback))
380
+ throw err;
381
+ }
382
+ if (adopted)
383
+ agents.push(adopted);
384
+ }
385
+ return { owner: { id: owner.id, subject: owner.subject }, dryRun: opts.dryRun, agents };
386
+ }
387
+ export function formatAdoptResult(result) {
388
+ const lines = [
389
+ `${result.dryRun ? "[dry run] would adopt" : "Adopted"} ${result.agents.length} owner-less agent(s) for ${result.owner.subject} (${result.owner.id}).`,
390
+ ];
391
+ for (const a of result.agents) {
392
+ lines.push("", `${a.name} (${a.agentId}): owner ${result.owner.subject}; named grants are kept.`);
393
+ for (const b of a.bindingsKept)
394
+ lines.push(` kept ${b.kind} binding "${b.boundName}"`);
395
+ for (const b of a.bindingsRemoved)
396
+ lines.push(` removed ${b.kind} binding "${b.boundName}" (${b.resourceId})`);
397
+ for (const t of a.capabilitiesStamped)
398
+ lines.push(` capabilities of own tool "${t}" now in force`);
399
+ if (a.everyoneGrant.before !== null) {
400
+ lines.push(a.everyoneGrant.before === a.everyoneGrant.after
401
+ ? ` everyone keeps ${a.everyoneGrant.after}${a.everyoneGrant.after === "execute" ? ": anyone can run it with the secrets and capabilities it regains (revoke_access to stop that)" : ""}`
402
+ : ` everyone lowered from ${a.everyoneGrant.before} to ${a.everyoneGrant.after} (webhooks others created stop firing; grant execute to named principals instead)`);
403
+ }
404
+ for (const t of a.foreignTools) {
405
+ lines.push(` tool "${t.toolName}" (${t.toolId}) is owned by ${t.toolOwnerId ?? "nobody"}: review or detach_tool it` +
406
+ (t.regrant ? `; its capabilities stay inert, re-grant: ${t.regrant}` : ""));
407
+ }
408
+ for (const e of a.failingEdges) {
409
+ lines.push(` sub-agent edge ${e.parentAgentId} -> ${e.childAgentId} as "${e.boundName}" is refused at run time (not deleted)`);
410
+ }
411
+ }
412
+ return lines.join("\n");
413
+ }
414
+ /** Deletes cross-owner secret/datastore bindings on owned agents, in one transaction. */
415
+ export async function pruneBindings(db, opts) {
416
+ return db.$transaction(async (tx) => {
417
+ const secrets = await tx.agentSecret.findMany({
418
+ where: { agent: { ownerId: { not: null } } },
419
+ include: { agent: { select: { name: true, ownerId: true } }, secret: { select: { ownerId: true } } },
420
+ orderBy: [{ agentId: "asc" }, { boundName: "asc" }],
421
+ });
422
+ const datastores = await tx.agentDatastore.findMany({
423
+ where: { agent: { ownerId: { not: null } } },
424
+ include: { agent: { select: { name: true, ownerId: true } }, datastore: { select: { ownerId: true } } },
425
+ orderBy: [{ agentId: "asc" }, { boundName: "asc" }],
426
+ });
427
+ const foreignSecrets = secrets.filter((b) => b.secret.ownerId !== b.agent.ownerId);
428
+ const foreignDatastores = datastores.filter((b) => b.datastore.ownerId !== b.agent.ownerId);
429
+ if (!opts.dryRun) {
430
+ for (const b of foreignSecrets) {
431
+ await tx.agentSecret.delete({ where: { agentId_secretId: { agentId: b.agentId, secretId: b.secretId } } });
432
+ }
433
+ for (const b of foreignDatastores) {
434
+ await tx.agentDatastore.delete({
435
+ where: { agentId_datastoreId: { agentId: b.agentId, datastoreId: b.datastoreId } },
436
+ });
437
+ }
438
+ }
439
+ return {
440
+ dryRun: opts.dryRun,
441
+ removed: [
442
+ ...foreignSecrets.map((b) => ({
443
+ kind: "secret",
444
+ agentId: b.agentId,
445
+ agentName: b.agent.name,
446
+ boundName: b.boundName,
447
+ resourceId: b.secretId,
448
+ resourceOwnerId: b.secret.ownerId,
449
+ })),
450
+ ...foreignDatastores.map((b) => ({
451
+ kind: "datastore",
452
+ agentId: b.agentId,
453
+ agentName: b.agent.name,
454
+ boundName: b.boundName,
455
+ resourceId: b.datastoreId,
456
+ resourceOwnerId: b.datastore.ownerId,
457
+ })),
458
+ ],
459
+ };
460
+ }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
461
+ }
462
+ export function formatPruneResult(result) {
463
+ const verb = result.dryRun ? "would remove" : "removed";
464
+ if (result.removed.length === 0)
465
+ return `No cross-owner bindings to ${result.dryRun ? "remove" : "prune"}.`;
466
+ return [
467
+ `${result.dryRun ? "[dry run] " : ""}${result.removed.length} cross-owner binding(s):`,
468
+ ...result.removed.map((b) => ` ${verb} ${b.kind} binding "${b.boundName}" on ${b.agentName} (${b.agentId}): ${b.resourceId} owned by ${b.resourceOwnerId ?? "nobody"}`),
469
+ ].join("\n");
470
+ }
471
+ /**
472
+ * The owner of an agent made with `wardby agent create` (resource-sharing
473
+ * grants spec §3.8): `--owner <subject>` must already exist (a typo never
474
+ * mints a principal), otherwise the local operator (LOCAL_PRINCIPAL),
475
+ * found or created exactly as stdio does.
476
+ */
477
+ export async function resolveCliAgentOwner(db, ownerSubject, localPrincipal) {
478
+ if (ownerSubject === undefined)
479
+ return (await resolvePrincipal(localPrincipal, db)).id;
480
+ const principal = await db.principal.findUnique({ where: { subject: ownerSubject } });
481
+ if (!principal)
482
+ throw new Error(`No principal with subject "${ownerSubject}" (they must have signed in at least once).`);
483
+ return principal.id;
484
+ }
485
+ export async function grantsCommand(args, db, output = console.log) {
486
+ const [action, ...rest] = args;
487
+ if (action === "migration-report") {
488
+ const { values } = parseArgs({ args: rest, strict: true, options: { json: { type: "boolean" } } });
489
+ const report = await migrationReport(db);
490
+ output(values.json ? JSON.stringify(report, null, 2) : formatMigrationReport(report));
491
+ return;
492
+ }
493
+ if (action === "adopt-public") {
494
+ const { values } = parseArgs({
495
+ args: rest,
496
+ strict: true,
497
+ options: {
498
+ owner: { type: "string" },
499
+ "dry-run": { type: "boolean" },
500
+ "keep-everyone-execute": { type: "boolean" },
501
+ },
502
+ });
503
+ if (!values.owner)
504
+ throw new Error("grants adopt-public requires --owner <subject>.");
505
+ output(formatAdoptResult(await adoptPublic(db, {
506
+ ownerSubject: values.owner,
507
+ dryRun: values["dry-run"] === true,
508
+ keepEveryoneExecute: values["keep-everyone-execute"] === true,
509
+ })));
510
+ return;
511
+ }
512
+ if (action === "prune-bindings") {
513
+ const { values } = parseArgs({ args: rest, strict: true, options: { "dry-run": { type: "boolean" } } });
514
+ output(formatPruneResult(await pruneBindings(db, { dryRun: values["dry-run"] === true })));
515
+ return;
516
+ }
517
+ throw new Error(USAGE);
518
+ }
@@ -0,0 +1,2 @@
1
+ import type { PrismaClient } from "#prisma";
2
+ export declare function hostAccountCommand(args: string[], db: PrismaClient, output?: (value: string) => void): Promise<void>;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * `wardby auth host-account list [--subject <s>] | unlink --subject <s> [--provider github]`:
3
+ * the operator's view of principals' linked host identities. Keyed on
4
+ * Principal.subject, so it works in both auth modes (self-hosted and
5
+ * delegating). Unlinking frees a GitHub account to be linked by another
6
+ * principal (link_host_account refuses one already linked elsewhere).
7
+ */
8
+ import { parseArgs } from "node:util";
9
+ import { REVIEW_HOST_PROVIDERS } from "../../providers/review-host/types.js";
10
+ const USAGE = "Use auth host-account list [--subject <s>] | host-account unlink --subject <s> [--provider github].";
11
+ export async function hostAccountCommand(args, db, output = console.log) {
12
+ const [action, ...rest] = args;
13
+ if (action !== "list" && action !== "unlink")
14
+ throw new Error(USAGE);
15
+ const { values } = parseArgs({
16
+ args: rest,
17
+ strict: true,
18
+ allowPositionals: false,
19
+ options: { subject: { type: "string" }, provider: { type: "string" } },
20
+ });
21
+ const provider = values.provider ?? "github";
22
+ if (!REVIEW_HOST_PROVIDERS.includes(provider))
23
+ throw new Error(`Unknown provider "${provider}".`);
24
+ if (action === "list") {
25
+ const rows = await db.hostIdentity.findMany({
26
+ where: { provider, ...(values.subject ? { principal: { subject: values.subject } } : {}) },
27
+ include: { principal: { select: { subject: true } } },
28
+ orderBy: { linkedAt: "asc" },
29
+ });
30
+ output(JSON.stringify(rows.map((r) => ({
31
+ subject: r.principal.subject,
32
+ provider: r.provider,
33
+ login: r.login,
34
+ hostUserId: r.hostUserId,
35
+ linkedAt: r.linkedAt,
36
+ })), null, 2));
37
+ return;
38
+ }
39
+ if (!values.subject)
40
+ throw new Error("auth host-account unlink requires --subject.");
41
+ const principal = await db.principal.findUnique({ where: { subject: values.subject } });
42
+ if (!principal)
43
+ throw new Error(`No principal with subject "${values.subject}".`);
44
+ const { count } = await db.hostIdentity.deleteMany({ where: { principalId: principal.id, provider } });
45
+ await db.hostIdentityLinkRequest.deleteMany({ where: { principalId: principal.id, provider } });
46
+ output(count > 0 ? "Host account unlinked." : "No host account was linked.");
47
+ }