@wardby/cli 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (555) hide show
  1. package/.env.example +53 -4
  2. package/README.md +52 -13
  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 +14 -4
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +284 -36
  12. package/dist/coding/base-commit.d.ts +6 -0
  13. package/dist/coding/base-commit.js +12 -0
  14. package/dist/coding/collect-exclude.d.ts +25 -0
  15. package/dist/coding/collect-exclude.js +76 -0
  16. package/dist/coding/profile.d.ts +67 -22
  17. package/dist/coding/profile.js +58 -25
  18. package/dist/coding/protected-path-wording.d.ts +24 -0
  19. package/dist/coding/protected-path-wording.js +55 -0
  20. package/dist/coding/protected-paths.d.ts +31 -0
  21. package/dist/coding/protected-paths.js +48 -0
  22. package/dist/coding/protocol.d.ts +91 -4
  23. package/dist/coding/protocol.js +123 -12
  24. package/dist/coding/provider.d.ts +6 -1
  25. package/dist/coding/provider.js +16 -9
  26. package/dist/coding/registry/adapters.d.ts +2 -0
  27. package/dist/coding/registry/adapters.js +9 -0
  28. package/dist/coding/registry/allowlist.d.ts +9 -0
  29. package/dist/coding/registry/allowlist.js +28 -0
  30. package/dist/coding/registry/json-scan.d.ts +55 -0
  31. package/dist/coding/registry/json-scan.js +191 -0
  32. package/dist/coding/registry/lockfiles.d.ts +6 -0
  33. package/dist/coding/registry/lockfiles.js +89 -0
  34. package/dist/coding/registry/npm-lockfile.d.ts +9 -0
  35. package/dist/coding/registry/npm-lockfile.js +73 -0
  36. package/dist/coding/registry/npm-plan.d.ts +29 -0
  37. package/dist/coding/registry/npm-plan.js +289 -0
  38. package/dist/coding/registry/npm.d.ts +7 -0
  39. package/dist/coding/registry/npm.js +252 -0
  40. package/dist/coding/registry/pypi.d.ts +4 -0
  41. package/dist/coding/registry/pypi.js +210 -0
  42. package/dist/coding/registry/report.d.ts +37 -0
  43. package/dist/coding/registry/report.js +31 -0
  44. package/dist/coding/registry/token.d.ts +4 -0
  45. package/dist/coding/registry/token.js +7 -0
  46. package/dist/coding/registry/types.d.ts +286 -0
  47. package/dist/coding/registry/types.js +19 -0
  48. package/dist/coding/registry/worker-config.d.ts +14 -0
  49. package/dist/coding/registry/worker-config.js +31 -0
  50. package/dist/coding/services/builtins.d.ts +22 -0
  51. package/dist/coding/services/builtins.js +85 -0
  52. package/dist/coding/services/catalog.d.ts +443 -0
  53. package/dist/coding/services/catalog.js +159 -0
  54. package/dist/coding/services/declaration.d.ts +11 -0
  55. package/dist/coding/services/declaration.js +114 -0
  56. package/dist/coding/services/note.d.ts +8 -0
  57. package/dist/coding/services/note.js +15 -0
  58. package/dist/coding/services/resolve.d.ts +25 -0
  59. package/dist/coding/services/resolve.js +50 -0
  60. package/dist/coding/services/wording.d.ts +25 -0
  61. package/dist/coding/services/wording.js +70 -0
  62. package/dist/coding-proxy/main.js +24 -4
  63. package/dist/coding-worker/artifact.d.ts +6 -0
  64. package/dist/coding-worker/debug-trace.d.ts +37 -0
  65. package/dist/coding-worker/debug-trace.js +117 -0
  66. package/dist/coding-worker/driver.d.ts +13 -1
  67. package/dist/coding-worker/driver.js +102 -13
  68. package/dist/coding-worker/errors.js +8 -0
  69. package/dist/coding-worker/main.js +10 -2
  70. package/dist/coding-worker/sdk.d.ts +2 -2
  71. package/dist/coding-worker/sdk.js +7 -2
  72. package/dist/coding-worker/types.d.ts +3 -0
  73. package/dist/config/providers.d.ts +66 -0
  74. package/dist/config/providers.js +137 -0
  75. package/dist/core/attribution.d.ts +101 -0
  76. package/dist/core/attribution.js +208 -0
  77. package/dist/core/budget-groups.d.ts +120 -10
  78. package/dist/core/budget-groups.js +133 -24
  79. package/dist/core/budget-wording.d.ts +22 -0
  80. package/dist/core/budget-wording.js +55 -0
  81. package/dist/core/coding-queue.d.ts +3 -0
  82. package/dist/core/coding-queue.js +6 -2
  83. package/dist/core/coding-service-status.d.ts +11 -0
  84. package/dist/core/coding-service-status.js +17 -0
  85. package/dist/core/cost-report.d.ts +88 -0
  86. package/dist/core/cost-report.js +248 -0
  87. package/dist/core/datastores.js +18 -2
  88. package/dist/core/db.d.ts +5 -1
  89. package/dist/core/db.js +8 -2
  90. package/dist/core/dispatch.d.ts +74 -7
  91. package/dist/core/dispatch.js +382 -47
  92. package/dist/core/engine-native.js +44 -14
  93. package/dist/core/glob.d.ts +10 -0
  94. package/dist/core/glob.js +33 -0
  95. package/dist/core/grants.d.ts +86 -0
  96. package/dist/core/grants.js +126 -0
  97. package/dist/core/host-events.d.ts +72 -0
  98. package/dist/core/host-events.js +597 -0
  99. package/dist/core/host-identity-links.d.ts +54 -0
  100. package/dist/core/host-identity-links.js +189 -0
  101. package/dist/core/host-status.d.ts +78 -0
  102. package/dist/core/host-status.js +228 -0
  103. package/dist/core/in-flight-runs.d.ts +8 -0
  104. package/dist/core/in-flight-runs.js +56 -0
  105. package/dist/core/issue-bridge.d.ts +60 -0
  106. package/dist/core/issue-bridge.js +189 -0
  107. package/dist/core/issue-dedupe.d.ts +70 -0
  108. package/dist/core/issue-dedupe.js +255 -0
  109. package/dist/core/issue-events.d.ts +42 -0
  110. package/dist/core/issue-events.js +155 -0
  111. package/dist/core/issue-status.d.ts +29 -0
  112. package/dist/core/issue-status.js +241 -0
  113. package/dist/core/issue-tracker-tools.d.ts +64 -0
  114. package/dist/core/issue-tracker-tools.js +850 -0
  115. package/dist/core/model-usage.d.ts +10 -0
  116. package/dist/core/model-usage.js +24 -0
  117. package/dist/core/provider-wording.d.ts +12 -0
  118. package/dist/core/provider-wording.js +44 -0
  119. package/dist/core/reconciler.d.ts +42 -2
  120. package/dist/core/reconciler.js +97 -2
  121. package/dist/core/repo-access.d.ts +99 -0
  122. package/dist/core/repo-access.js +136 -0
  123. package/dist/core/review-host-checks.d.ts +11 -0
  124. package/dist/core/review-host-checks.js +41 -0
  125. package/dist/core/review-host-tools.d.ts +47 -0
  126. package/dist/core/review-host-tools.js +354 -0
  127. package/dist/core/run-heartbeat.d.ts +27 -0
  128. package/dist/core/run-heartbeat.js +54 -0
  129. package/dist/core/run-pricing.d.ts +61 -0
  130. package/dist/core/run-pricing.js +56 -0
  131. package/dist/core/runner.d.ts +33 -8
  132. package/dist/core/runner.js +410 -62
  133. package/dist/core/scheduler.d.ts +4 -1
  134. package/dist/core/scheduler.js +3 -2
  135. package/dist/core/secrets.d.ts +11 -2
  136. package/dist/core/secrets.js +25 -6
  137. package/dist/core/self-defects.d.ts +80 -0
  138. package/dist/core/self-defects.js +180 -0
  139. package/dist/core/subagent-memory-tools.d.ts +1 -1
  140. package/dist/core/subagent-memory-tools.js +16 -3
  141. package/dist/core/tool-admin.d.ts +81 -0
  142. package/dist/core/tool-admin.js +129 -0
  143. package/dist/core/tool-names.d.ts +42 -0
  144. package/dist/core/tool-names.js +67 -0
  145. package/dist/core/untrusted-content.d.ts +32 -0
  146. package/dist/core/untrusted-content.js +72 -0
  147. package/dist/core/webhooks.d.ts +17 -2
  148. package/dist/core/webhooks.js +42 -3
  149. package/dist/generated/prisma/browser.d.ts +166 -0
  150. package/dist/generated/prisma/client.d.ts +166 -0
  151. package/dist/generated/prisma/commonInputTypes.d.ts +152 -52
  152. package/dist/generated/prisma/enums.d.ts +13 -0
  153. package/dist/generated/prisma/enums.js +12 -1
  154. package/dist/generated/prisma/internal/class.d.ts +242 -0
  155. package/dist/generated/prisma/internal/class.js +4 -4
  156. package/dist/generated/prisma/internal/prismaNamespace.d.ts +2571 -577
  157. package/dist/generated/prisma/internal/prismaNamespace.js +312 -6
  158. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +328 -0
  159. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +312 -6
  160. package/dist/generated/prisma/models/Agent.d.ts +682 -1
  161. package/dist/generated/prisma/models/AgentIssueProject.d.ts +1838 -0
  162. package/dist/generated/prisma/models/AgentIssueProject.js +1 -0
  163. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  164. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  165. package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
  166. package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
  167. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
  168. package/dist/generated/prisma/models/CodingProxySession.d.ts +195 -2
  169. package/dist/generated/prisma/models/CodingRun.d.ts +1405 -95
  170. package/dist/generated/prisma/models/CodingRunServiceStatus.d.ts +1404 -0
  171. package/dist/generated/prisma/models/CodingRunServiceStatus.js +1 -0
  172. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  173. package/dist/generated/prisma/models/CodingService.js +1 -0
  174. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  175. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  176. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  177. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  178. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  179. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  180. package/dist/generated/prisma/models/IssueFingerprint.d.ts +1183 -0
  181. package/dist/generated/prisma/models/IssueFingerprint.js +1 -0
  182. package/dist/generated/prisma/models/IssuePullRequest.d.ts +1255 -0
  183. package/dist/generated/prisma/models/IssuePullRequest.js +1 -0
  184. package/dist/generated/prisma/models/ModelCatalogEntry.d.ts +1322 -0
  185. package/dist/generated/prisma/models/ModelCatalogEntry.js +1 -0
  186. package/dist/generated/prisma/models/Principal.d.ts +455 -0
  187. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  188. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  189. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  190. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  191. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  192. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  193. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  194. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  195. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  196. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  197. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  198. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  199. package/dist/generated/prisma/models/Run.d.ts +1402 -82
  200. package/dist/generated/prisma/models/RunAttribution.d.ts +1259 -0
  201. package/dist/generated/prisma/models/RunAttribution.js +1 -0
  202. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  203. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  204. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  205. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  206. package/dist/generated/prisma/models/RunIssueStatus.d.ts +1199 -0
  207. package/dist/generated/prisma/models/RunIssueStatus.js +1 -0
  208. package/dist/generated/prisma/models/RunModelUsage.d.ts +1316 -0
  209. package/dist/generated/prisma/models/RunModelUsage.js +1 -0
  210. package/dist/generated/prisma/models/Tool.d.ts +15 -3
  211. package/dist/generated/prisma/models/WorkItem.d.ts +1408 -0
  212. package/dist/generated/prisma/models/WorkItem.js +1 -0
  213. package/dist/generated/prisma/models.d.ts +22 -0
  214. package/dist/help/build.d.ts +1 -0
  215. package/dist/help/build.js +9 -0
  216. package/dist/help/catalog.d.ts +24 -0
  217. package/dist/help/catalog.js +160 -0
  218. package/dist/help/cli.d.ts +2 -0
  219. package/dist/help/cli.js +65 -0
  220. package/dist/help/runtime.d.ts +3 -0
  221. package/dist/help/runtime.js +44 -0
  222. package/dist/help/search.d.ts +9 -0
  223. package/dist/help/search.js +104 -0
  224. package/dist/help-index.json +999 -0
  225. package/dist/import/cli-args.js +3 -2
  226. package/dist/import/create.d.ts +5 -0
  227. package/dist/import/create.js +67 -13
  228. package/dist/import/index.js +19 -9
  229. package/dist/import/neutral-schema.d.ts +18 -18
  230. package/dist/knowledge/check.d.ts +13 -0
  231. package/dist/knowledge/check.js +69 -0
  232. package/dist/knowledge/cli.d.ts +14 -0
  233. package/dist/knowledge/cli.js +67 -0
  234. package/dist/knowledge/concept.d.ts +54 -0
  235. package/dist/knowledge/concept.js +78 -0
  236. package/dist/knowledge/note.d.ts +11 -0
  237. package/dist/knowledge/note.js +39 -0
  238. package/dist/knowledge/relevance.d.ts +11 -0
  239. package/dist/knowledge/relevance.js +14 -0
  240. package/dist/knowledge/span-hash.d.ts +3 -0
  241. package/dist/knowledge/span-hash.js +16 -0
  242. package/dist/mcp/auth/access.d.ts +72 -0
  243. package/dist/mcp/auth/access.js +58 -0
  244. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  245. package/dist/mcp/auth/grants-cli.js +518 -0
  246. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  247. package/dist/mcp/auth/host-account-cli.js +47 -0
  248. package/dist/mcp/auth/ownership.d.ts +25 -51
  249. package/dist/mcp/auth/ownership.js +19 -14
  250. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  251. package/dist/mcp/auth/repo-authorization.js +51 -0
  252. package/dist/mcp/auth/resource-server.d.ts +33 -2
  253. package/dist/mcp/auth/resource-server.js +99 -3
  254. package/dist/mcp/auth/self-hosted/browser.js +2 -2
  255. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  256. package/dist/mcp/auth/self-hosted/credentials.d.ts +23 -5
  257. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  258. package/dist/mcp/auth/self-hosted/session.d.ts +7 -6
  259. package/dist/mcp/context.d.ts +30 -1
  260. package/dist/mcp/errors.d.ts +25 -6
  261. package/dist/mcp/errors.js +98 -0
  262. package/dist/mcp/host-events/deliveries.d.ts +9 -0
  263. package/dist/mcp/host-events/deliveries.js +17 -0
  264. package/dist/mcp/host-events/github-ingress.d.ts +38 -0
  265. package/dist/mcp/host-events/github-ingress.js +83 -0
  266. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  267. package/dist/mcp/host-events/github-user-callback.js +50 -0
  268. package/dist/mcp/host-events/jira-ingress.d.ts +29 -0
  269. package/dist/mcp/host-events/jira-ingress.js +92 -0
  270. package/dist/mcp/index.d.ts +10 -1
  271. package/dist/mcp/index.js +187 -19
  272. package/dist/mcp/server.js +32 -11
  273. package/dist/mcp/tools/agents.js +519 -50
  274. package/dist/mcp/tools/budget-groups.js +3 -3
  275. package/dist/mcp/tools/cost-report.d.ts +8 -0
  276. package/dist/mcp/tools/cost-report.js +60 -0
  277. package/dist/mcp/tools/datastore.js +22 -15
  278. package/dist/mcp/tools/grants.d.ts +2 -0
  279. package/dist/mcp/tools/grants.js +239 -0
  280. package/dist/mcp/tools/help.d.ts +5 -0
  281. package/dist/mcp/tools/help.js +67 -0
  282. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  283. package/dist/mcp/tools/host-accounts.js +114 -0
  284. package/dist/mcp/tools/issue-projects.d.ts +2 -0
  285. package/dist/mcp/tools/issue-projects.js +238 -0
  286. package/dist/mcp/tools/memory.d.ts +7 -1
  287. package/dist/mcp/tools/memory.js +5 -5
  288. package/dist/mcp/tools/model-catalog.d.ts +22 -0
  289. package/dist/mcp/tools/model-catalog.js +423 -0
  290. package/dist/mcp/tools/repositories.d.ts +2 -0
  291. package/dist/mcp/tools/repositories.js +182 -0
  292. package/dist/mcp/tools/runs.d.ts +6 -0
  293. package/dist/mcp/tools/runs.js +60 -6
  294. package/dist/mcp/tools/scheduling.js +3 -6
  295. package/dist/mcp/tools/secrets.js +18 -6
  296. package/dist/mcp/tools/services.d.ts +2 -0
  297. package/dist/mcp/tools/services.js +222 -0
  298. package/dist/mcp/tools/subagents.js +52 -16
  299. package/dist/mcp/tools/tools.d.ts +41 -0
  300. package/dist/mcp/tools/tools.js +201 -40
  301. package/dist/mcp/tools/trigger.js +65 -7
  302. package/dist/mcp/tools/webhooks.js +15 -4
  303. package/dist/mcp/transport/streamable-http.d.ts +15 -0
  304. package/dist/mcp/transport/streamable-http.js +55 -3
  305. package/dist/mcp/webhooks/ingress.d.ts +2 -1
  306. package/dist/mcp/webhooks/ingress.js +9 -2
  307. package/dist/providers/auth/delegating.d.ts +19 -0
  308. package/dist/providers/auth/delegating.js +71 -0
  309. package/dist/providers/auth/index.d.ts +2 -0
  310. package/dist/providers/auth/index.js +11 -0
  311. package/dist/providers/auth/self-hosted.d.ts +10 -2
  312. package/dist/providers/auth/self-hosted.js +55 -6
  313. package/dist/providers/auth/types.d.ts +6 -0
  314. package/dist/providers/coding-proxy/memory-ledger.d.ts +4 -1
  315. package/dist/providers/coding-proxy/memory-ledger.js +31 -3
  316. package/dist/providers/coding-proxy/metering.d.ts +6 -1
  317. package/dist/providers/coding-proxy/metering.js +41 -4
  318. package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
  319. package/dist/providers/coding-proxy/prisma-ledger.js +106 -8
  320. package/dist/providers/coding-proxy/proxy.d.ts +25 -2
  321. package/dist/providers/coding-proxy/proxy.js +561 -38
  322. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  323. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  324. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  325. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  326. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  327. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  328. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  329. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  330. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  331. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  332. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  333. package/dist/providers/coding-proxy/registry/store.js +70 -0
  334. package/dist/providers/coding-proxy/runtime.d.ts +17 -0
  335. package/dist/providers/coding-proxy/runtime.js +63 -0
  336. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  337. package/dist/providers/coding-proxy/server.d.ts +11 -0
  338. package/dist/providers/coding-proxy/server.js +163 -0
  339. package/dist/providers/coding-proxy/types.d.ts +33 -1
  340. package/dist/providers/coding-proxy/types.js +12 -1
  341. package/dist/providers/engine/types.d.ts +29 -0
  342. package/dist/providers/executor/build.d.ts +2 -2
  343. package/dist/providers/executor/composition.d.ts +3 -0
  344. package/dist/providers/executor/composition.js +28 -1
  345. package/dist/providers/executor/container.d.ts +134 -4
  346. package/dist/providers/executor/container.js +394 -37
  347. package/dist/providers/executor/dbos.d.ts +4 -3
  348. package/dist/providers/executor/dbos.js +7 -5
  349. package/dist/providers/executor/in-process.d.ts +2 -3
  350. package/dist/providers/executor/routing.d.ts +13 -0
  351. package/dist/providers/executor/routing.js +18 -0
  352. package/dist/providers/executor/types.d.ts +34 -0
  353. package/dist/providers/issue-tracker/adf.d.ts +31 -0
  354. package/dist/providers/issue-tracker/adf.js +181 -0
  355. package/dist/providers/issue-tracker/index.d.ts +5 -0
  356. package/dist/providers/issue-tracker/index.js +12 -0
  357. package/dist/providers/issue-tracker/jira-client.d.ts +41 -0
  358. package/dist/providers/issue-tracker/jira-client.js +151 -0
  359. package/dist/providers/issue-tracker/jira-events.d.ts +3 -0
  360. package/dist/providers/issue-tracker/jira-events.js +98 -0
  361. package/dist/providers/issue-tracker/jira.d.ts +116 -0
  362. package/dist/providers/issue-tracker/jira.js +502 -0
  363. package/dist/providers/issue-tracker/types.d.ts +269 -0
  364. package/dist/providers/issue-tracker/types.js +16 -0
  365. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  366. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  367. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  368. package/dist/providers/jobs/collect-prune.js +27 -0
  369. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  370. package/dist/providers/jobs/docker-isolation.js +213 -22
  371. package/dist/providers/jobs/docker-services.d.ts +35 -0
  372. package/dist/providers/jobs/docker-services.js +191 -0
  373. package/dist/providers/jobs/docker.d.ts +53 -2
  374. package/dist/providers/jobs/docker.js +307 -32
  375. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  376. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  377. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  378. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  379. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  380. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  381. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  382. package/dist/providers/jobs/kubernetes.d.ts +24 -2
  383. package/dist/providers/jobs/kubernetes.js +153 -22
  384. package/dist/providers/jobs/service-state.d.ts +22 -0
  385. package/dist/providers/jobs/service-state.js +17 -0
  386. package/dist/providers/jobs/types.d.ts +18 -0
  387. package/dist/providers/llm/anthropic.d.ts +3 -3
  388. package/dist/providers/llm/anthropic.js +3 -5
  389. package/dist/providers/llm/bedrock.d.ts +3 -3
  390. package/dist/providers/llm/bedrock.js +3 -8
  391. package/dist/providers/llm/catalog-lookup.d.ts +10 -0
  392. package/dist/providers/llm/catalog-lookup.js +15 -0
  393. package/dist/providers/llm/catalog-shipped.d.ts +18 -0
  394. package/dist/providers/llm/catalog-shipped.js +197 -0
  395. package/dist/providers/llm/catalog-store.d.ts +58 -0
  396. package/dist/providers/llm/catalog-store.js +138 -0
  397. package/dist/providers/llm/catalog-types.d.ts +66 -0
  398. package/dist/providers/llm/catalog-types.js +64 -0
  399. package/dist/providers/llm/catalog.d.ts +61 -0
  400. package/dist/providers/llm/catalog.js +147 -0
  401. package/dist/providers/llm/claude-messages.d.ts +5 -1
  402. package/dist/providers/llm/claude-messages.js +1 -0
  403. package/dist/providers/llm/claude-provider.d.ts +10 -12
  404. package/dist/providers/llm/claude-provider.js +15 -6
  405. package/dist/providers/llm/index.d.ts +9 -6
  406. package/dist/providers/llm/index.js +8 -5
  407. package/dist/providers/llm/openai.d.ts +14 -5
  408. package/dist/providers/llm/openai.js +24 -14
  409. package/dist/providers/llm/pricing-core.d.ts +5 -3
  410. package/dist/providers/llm/registration.js +8 -12
  411. package/dist/providers/llm/routing.d.ts +21 -11
  412. package/dist/providers/llm/routing.js +47 -13
  413. package/dist/providers/llm/types.d.ts +12 -0
  414. package/dist/providers/llm/types.js +8 -1
  415. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  416. package/dist/providers/review-host/diff-lines.js +59 -0
  417. package/dist/providers/review-host/github-events.d.ts +6 -0
  418. package/dist/providers/review-host/github-events.js +226 -0
  419. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  420. package/dist/providers/review-host/github-user-auth.js +128 -0
  421. package/dist/providers/review-host/github.d.ts +57 -0
  422. package/dist/providers/review-host/github.js +568 -0
  423. package/dist/providers/review-host/index.d.ts +12 -0
  424. package/dist/providers/review-host/index.js +30 -0
  425. package/dist/providers/review-host/review-format.d.ts +19 -0
  426. package/dist/providers/review-host/review-format.js +59 -0
  427. package/dist/providers/review-host/types.d.ts +309 -0
  428. package/dist/providers/review-host/types.js +24 -0
  429. package/dist/providers/vcs/git.d.ts +16 -6
  430. package/dist/providers/vcs/git.js +78 -34
  431. package/dist/providers/vcs/github.d.ts +94 -3
  432. package/dist/providers/vcs/github.js +223 -17
  433. package/dist/providers/vcs/types.d.ts +48 -4
  434. package/dist/quickstart/index.d.ts +2 -0
  435. package/dist/quickstart/index.js +36 -8
  436. package/dist/sandbox/fetch-policy.d.ts +20 -2
  437. package/dist/sandbox/fetch-policy.js +64 -3
  438. package/dist/sandbox/host-functions.d.ts +9 -1
  439. package/dist/sandbox/host-functions.js +12 -5
  440. package/dist/serve.js +8 -2
  441. package/dist/viewer/api-schema.d.ts +2757 -0
  442. package/dist/viewer/api-schema.js +165 -0
  443. package/dist/viewer/build-schemas.d.ts +2 -0
  444. package/dist/viewer/build-schemas.js +18 -0
  445. package/dist/viewer/event-bus.d.ts +38 -0
  446. package/dist/viewer/event-bus.js +232 -0
  447. package/dist/viewer/graph.d.ts +40 -0
  448. package/dist/viewer/graph.js +243 -0
  449. package/dist/viewer/http.d.ts +30 -0
  450. package/dist/viewer/http.js +133 -0
  451. package/dist/viewer/run-detail.d.ts +4 -0
  452. package/dist/viewer/run-detail.js +61 -0
  453. package/dist/wardby-bin.js +11 -0
  454. package/docs/README.md +40 -0
  455. package/docs/agent-recipes.md +383 -0
  456. package/docs/architecture-runtime.md +90 -0
  457. package/docs/assets/brand/wardby-icon-512.png +0 -0
  458. package/docs/assets/brand/wardby-icon.svg +16 -0
  459. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  460. package/docs/assets/brand/wardby-mascot.png +0 -0
  461. package/docs/assets/brand/wardby-mascot.svg +5 -0
  462. package/docs/assets/wardby-workflow.svg +106 -0
  463. package/docs/code-review-agents.md +508 -0
  464. package/docs/coding-agent-setup.md +175 -0
  465. package/docs/coding-packages.md +455 -0
  466. package/docs/coding-services.md +300 -0
  467. package/docs/coding-worker-byo-images.md +98 -0
  468. package/docs/coding-worker-isolation.md +1068 -0
  469. package/docs/getting-started-gke.md +632 -0
  470. package/docs/getting-started-identity-provider.md +319 -0
  471. package/docs/getting-started.md +147 -0
  472. package/docs/jira-agents.md +649 -0
  473. package/docs/knowledge.md +387 -0
  474. package/docs/models.md +221 -0
  475. package/docs/observability.md +53 -0
  476. package/docs/release-verification.md +66 -0
  477. package/docs/security-deployment.md +667 -0
  478. package/docs/viewer-api.md +142 -0
  479. package/help/admin-viewer.md +39 -0
  480. package/help/agent-recipes.md +173 -0
  481. package/help/architecture-agent.md +189 -0
  482. package/help/builder-agent.md +80 -0
  483. package/help/code-review-agents.md +37 -0
  484. package/help/coding-packages.md +31 -0
  485. package/help/coding-services.md +71 -0
  486. package/help/cost-attribution.md +67 -0
  487. package/help/creating-agents.md +90 -0
  488. package/help/deploy-gke.md +45 -0
  489. package/help/deployment-targets.md +39 -0
  490. package/help/errors/budget-group-exhausted.md +25 -0
  491. package/help/errors/docker-isolation-unsupported.md +26 -0
  492. package/help/errors/model-unavailable.md +63 -0
  493. package/help/errors/protected-path.md +45 -0
  494. package/help/errors/repo-access.md +27 -0
  495. package/help/errors/service-declaration-invalid.md +27 -0
  496. package/help/errors/service-declaration-unavailable.md +25 -0
  497. package/help/errors/service-launcher-unsupported.md +30 -0
  498. package/help/errors/service-not-allowed.md +27 -0
  499. package/help/errors/service-unknown.md +23 -0
  500. package/help/errors/service-unready.md +49 -0
  501. package/help/getting-started.md +32 -0
  502. package/help/github.md +48 -0
  503. package/help/identity-and-access.md +44 -0
  504. package/help/jira.md +135 -0
  505. package/help/knowledge.md +47 -0
  506. package/help/mcp.md +30 -0
  507. package/help/models.md +90 -0
  508. package/help/native-capabilities.md +30 -0
  509. package/help/observability.md +41 -0
  510. package/help/operating-agents.md +36 -0
  511. package/help/security.md +27 -0
  512. package/help/troubleshooting/budgets.md +32 -0
  513. package/help/troubleshooting/coding-workers.md +47 -0
  514. package/help/troubleshooting/repository-access.md +27 -0
  515. package/package.json +14 -3
  516. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  517. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  518. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  519. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  520. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  521. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  522. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  523. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  524. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  525. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  526. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  527. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  528. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  529. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  530. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  531. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  532. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  533. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  534. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  535. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  536. package/prisma/migrations/20260930000000_jira_issue_projects/migration.sql +34 -0
  537. package/prisma/migrations/20261001000000_jira_phase2_allowlists/migration.sql +3 -0
  538. package/prisma/migrations/20261001010000_jira_link_types_allowlist/migration.sql +2 -0
  539. package/prisma/migrations/20261002000000_jira_coding_bridge/migration.sql +28 -0
  540. package/prisma/migrations/20261002010000_jira_issue_creation/migration.sql +25 -0
  541. package/prisma/migrations/20261003000000_issue_cost_attribution/migration.sql +56 -0
  542. package/prisma/migrations/20261003010000_coding_run_service_status/migration.sql +23 -0
  543. package/prisma/migrations/20261003020000_viewer_notify/migration.sql +54 -0
  544. package/prisma/migrations/20261003030000_viewer_notify_fixes/migration.sql +47 -0
  545. package/prisma/migrations/20261003040000_viewer_indexes/migration.sql +12 -0
  546. package/prisma/migrations/20261004000000_model_catalog/migration.sql +26 -0
  547. package/prisma/schema.prisma +663 -21
  548. package/dist/mcp/tools/models.d.ts +0 -8
  549. package/dist/mcp/tools/models.js +0 -15
  550. package/dist/providers/llm/pricing-anthropic.d.ts +0 -4
  551. package/dist/providers/llm/pricing-anthropic.js +0 -30
  552. package/dist/providers/llm/pricing-bedrock-claude.d.ts +0 -12
  553. package/dist/providers/llm/pricing-bedrock-claude.js +0 -37
  554. package/dist/providers/llm/pricing.d.ts +0 -30
  555. package/dist/providers/llm/pricing.js +0 -74
@@ -1,11 +1,28 @@
1
- /** Agent CRUD with scope checks in server.ts and ownership checks here. */
1
+ /**
2
+ * Agent CRUD with scope checks in server.ts and access checks here
3
+ * (auth/access.ts: owner, or a grant of read < execute < write). Owner-only
4
+ * whatever the grants: delete_agent and repository changes; make_owner is
5
+ * the agents:admin escape hatch. See
6
+ * docs/private/2026-09-26-resource-sharing-grants-spec-and-plan.md.
7
+ */
2
8
  import { Prisma } from "#prisma";
3
9
  import { z } from "zod";
4
10
  import { CodingProfilePatchSchema, CodingProfileSchema } from "../../coding/profile.js";
5
11
  import { codingProviderSupportsModel } from "../../coding/provider.js";
12
+ import { assertAgentModelAvailable } from "../../core/run-pricing.js";
13
+ import { ModelUnavailableError } from "../../providers/llm/catalog-types.js";
14
+ import { currentModelCatalog } from "../../providers/llm/catalog-store.js";
6
15
  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";
16
+ import { modelSupportedEfforts } from "../../providers/llm/routing.js";
17
+ import { PROJECT_KEY } from "../../providers/issue-tracker/types.js";
18
+ import { LLM_EFFORT_LEVELS, isLlmEffort } from "../../providers/llm/types.js";
19
+ import { requireReadableBudgetGroup } from "../auth/ownership.js";
20
+ import { agentAccessResolver, agentNotFound, assertAgentAccess, readableAgentsWhere, requireAgentAccess, } from "../auth/access.js";
21
+ import { EVERYONE_KEY, atLeast, canDelegate, deleteGrantsFor, effectiveAccess } from "../../core/grants.js";
22
+ import { projectTool } from "./tools.js";
23
+ import { requireAnyScope, requireScope } from "../auth/resource-server.js";
24
+ import { logger } from "../../core/logger.js";
25
+ import { authorizeRepositoryForSet } from "../auth/repo-authorization.js";
9
26
  import { McpError } from "../errors.js";
10
27
  import { textResult } from "./text-result.js";
11
28
  // workerImageRef is the BYO-arbitrary-image escape hatch (see
@@ -15,6 +32,18 @@ import { textResult } from "./text-result.js";
15
32
  function requireWorkerImageRefScope(ctx) {
16
33
  requireScope(ctx, ctx.canonicalUri, "agents:admin");
17
34
  }
35
+ // A debug trace makes the worker log prompts and repository content to its
36
+ // pod log, so turning it on (or off) is an admin action, gated like
37
+ // workerImageRef and audited.
38
+ function requireDebugTraceScope(ctx) {
39
+ requireScope(ctx, ctx.canonicalUri, "agents:admin");
40
+ }
41
+ const agentsLog = logger.child({ module: "mcp-agents" });
42
+ const MAX_DEBUG_TRACE_MINUTES = 24 * 60;
43
+ /** Package allowlists widen what a run may download, so they need their own approval. */
44
+ function requirePackageApproval(ctx) {
45
+ requireAnyScope(ctx, ctx.canonicalUri, "packages:approve", "agents:admin");
46
+ }
18
47
  const MAX_AGENT_NAME_CHARS = 200;
19
48
  const MAX_SYSTEM_PROMPT_CHARS = 64 * 1024;
20
49
  const MAX_MODEL_CHARS = 128;
@@ -31,7 +60,24 @@ const agentFields = {
31
60
  kind: z.enum(["native", "coding"]),
32
61
  budgetGroupId: z.string().min(1).max(128),
33
62
  memoryEnabled: z.boolean(),
63
+ effort: z.enum(LLM_EFFORT_LEVELS),
64
+ defectProjectKey: z.string().regex(PROJECT_KEY, "must be an upper-case Jira project key"),
65
+ defectIssueType: z.string().trim().min(1).max(100),
34
66
  };
67
+ /** Self-defect config is both-or-neither; on update, both null clears it. */
68
+ function refineDefectPair(value, ctx) {
69
+ const key = value.defectProjectKey;
70
+ const type = value.defectIssueType;
71
+ if ((key === undefined) !== (type === undefined) || (key === null) !== (type === null)) {
72
+ ctx.addIssue({
73
+ code: z.ZodIssueCode.custom,
74
+ path: ["defectProjectKey"],
75
+ message: "defectProjectKey and defectIssueType must be set together (or both null to clear on update)",
76
+ });
77
+ }
78
+ }
79
+ const DEFECT_DESCRIPTION = "Self-defects (optional, both or neither with defectIssueType): the Jira project key wardby files this agent's own failures into. " +
80
+ "Filing needs a write link (link_issue_project) to that project whose creatableIssueTypes includes defectIssueType; it is checked at filing time and fails closed.";
35
81
  const CreateAgentSchema = z
36
82
  .object({
37
83
  name: agentFields.name,
@@ -45,10 +91,15 @@ const CreateAgentSchema = z
45
91
  kind: agentFields.kind.default("native"),
46
92
  budgetGroupId: agentFields.budgetGroupId.optional(),
47
93
  memoryEnabled: agentFields.memoryEnabled.default(false),
94
+ effort: agentFields.effort.optional(),
95
+ defectProjectKey: agentFields.defectProjectKey.optional(),
96
+ defectIssueType: agentFields.defectIssueType.optional(),
48
97
  codingProfile: CodingProfileSchema.optional(),
98
+ repositoryAdminOverride: z.boolean().optional(),
49
99
  })
50
100
  .strict()
51
101
  .superRefine((value, ctx) => {
102
+ refineDefectPair(value, ctx);
52
103
  if (value.kind === "coding" && !value.codingProfile) {
53
104
  ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["codingProfile"], message: "is required for coding agents" });
54
105
  }
@@ -59,8 +110,11 @@ const CreateAgentSchema = z
59
110
  message: "is only valid for coding agents",
60
111
  });
61
112
  }
113
+ // A model the catalog doesn't offer is left to the handler, which reports
114
+ // model_unavailable with its reason instead of a provider mismatch.
62
115
  if (value.kind === "coding" &&
63
116
  value.codingProfile &&
117
+ currentModelCatalog().get(value.model) !== undefined &&
64
118
  !codingProviderSupportsModel(value.codingProfile.provider, value.model)) {
65
119
  ctx.addIssue({
66
120
  code: z.ZodIssueCode.custom,
@@ -91,21 +145,50 @@ const UpdateAgentSchema = z
91
145
  kind: agentFields.kind.optional(),
92
146
  budgetGroupId: agentFields.budgetGroupId.nullable().optional(),
93
147
  memoryEnabled: agentFields.memoryEnabled.optional(),
94
- codingProfile: CodingProfilePatchSchema.optional(),
148
+ effort: agentFields.effort.nullable().optional(),
149
+ defectProjectKey: agentFields.defectProjectKey.nullable().optional(),
150
+ defectIssueType: agentFields.defectIssueType.nullable().optional(),
151
+ codingProfile: CodingProfilePatchSchema.extend({
152
+ /** Not a stored profile field: sets CodingAgentProfile.debugTraceUntil = now + minutes (null clears). */
153
+ debugTraceMinutes: z.number().int().min(1).max(MAX_DEBUG_TRACE_MINUTES).nullable().optional(),
154
+ }).optional(),
155
+ repositoryAdminOverride: z.boolean().optional(),
95
156
  })
96
- .strict();
157
+ .strict()
158
+ .superRefine(refineDefectPair);
159
+ const REPOSITORY_ADMIN_OVERRIDE = {
160
+ type: "boolean",
161
+ description: "Admins only (agents:admin with the admin role): approve codingProfile.repository without checking GitHub access, recorded as an admin approval. On an agent the admin doesn't own, only codingProfile.repository may change.",
162
+ };
163
+ /** The profile columns a repository authorization is stamped into. */
164
+ function profileStamp(authorization) {
165
+ return {
166
+ repositoryAuthorizedVia: authorization.authorizedVia,
167
+ repositoryAuthorizedById: authorization.authorizedById,
168
+ repositoryAuthorizedAt: authorization.authorizedAt,
169
+ };
170
+ }
97
171
  const profileJsonSchema = {
98
172
  type: "object",
99
173
  additionalProperties: false,
100
174
  properties: {
101
175
  provider: { type: "string", enum: ["codex", "claude-code"] },
102
- repository: { type: "string" },
176
+ repository: {
177
+ type: "string",
178
+ description: "owner/name on GitHub. Your linked GitHub account (link_host_account) must have write access to it, unless a wardby admin approves it (repositoryAdminOverride). Re-checked on every run.",
179
+ },
103
180
  baseRef: { type: "string" },
104
181
  defaultTask: { type: ["string", "null"] },
105
182
  allowWebhookTaskOverride: { type: "boolean" },
106
183
  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" } },
184
+ protectedPaths: {
185
+ type: "array",
186
+ minItems: 1,
187
+ maxItems: 128,
188
+ items: { type: "string" },
189
+ description: 'Glob patterns (*, **, ?) a run may not change. A leading ! makes an exception, a literal file path without wildcards: ["docs/**", "!docs/changelog.md"] protects docs/ except the changelog. Every run also protects .wardby/** except .wardby/services.yaml, whatever this list says; exceptions cannot unprotect .wardby/.',
190
+ },
191
+ collectExclude: { type: "array", maxItems: 64, items: { type: "string" } },
109
192
  toolchain: { type: "string", enum: ["node", "node-python"] },
110
193
  toolchainVersion: { type: ["string", "null"] },
111
194
  workerImageRef: { type: ["string", "null"] },
@@ -115,6 +198,22 @@ const profileJsonSchema = {
115
198
  maximum: 32768,
116
199
  description: "Workspace disk size in MiB. Null uses the deployment default (CODING_DISK_MB), capped by the operator's CODING_MAX_DISK_MB.",
117
200
  },
201
+ packageAllowlist: {
202
+ type: "object",
203
+ description: "Approved top-level packages per ecosystem (npm, pypi). Needs packages:approve or agents:admin.",
204
+ additionalProperties: { type: "array", maxItems: 256, items: { type: "string" } },
205
+ },
206
+ packagePolicy: {
207
+ type: "object",
208
+ properties: { minReleaseAgeDays: { type: "integer", minimum: 0, maximum: 30 } },
209
+ additionalProperties: false,
210
+ },
211
+ services: {
212
+ type: "array",
213
+ maxItems: 16,
214
+ items: { type: "string" },
215
+ description: 'Coding-run services this agent\'s runs may start: catalog names such as "postgres" (any version the catalog has). A repository asks for them in .wardby/services.yaml on its base branch; a run asking for one not listed here is refused. Empty = none.',
216
+ },
118
217
  },
119
218
  };
120
219
  function invalidArguments(label, error) {
@@ -143,6 +242,43 @@ function validateSchedule(schedule, timezone) {
143
242
  throw new McpError(400, `Invalid schedule/timezone: ${error instanceof Error ? error.message : String(error)}`);
144
243
  }
145
244
  }
245
+ /**
246
+ * Effort only drives the native engine, and only on a model that accepts the
247
+ * level; reject rather than store a setting that would silently do nothing.
248
+ */
249
+ function validateEffort(kind, model, effort) {
250
+ if (effort == null)
251
+ return;
252
+ if (kind !== "native") {
253
+ throw new McpError(400, "effort is only valid for native agents; coding agents do not use it.");
254
+ }
255
+ const accepted = modelSupportedEfforts(model);
256
+ if (!isLlmEffort(effort) || !accepted.includes(effort)) {
257
+ throw new McpError(400, `Model "${model}" does not accept effort "${effort}". ` +
258
+ (accepted.length > 0
259
+ ? `Accepted levels: ${accepted.join(", ")}.`
260
+ : "It accepts no effort setting; leave effort unset (or null)."));
261
+ }
262
+ }
263
+ /**
264
+ * A model being set (create, or update with `model`) must be runnable here:
265
+ * in the catalog and enabled; for a native agent, its provider must also be
266
+ * configured on the native router. A coding agent is checked against the
267
+ * catalog only: its runs go through the coding proxy, whose credentials
268
+ * (CODING_*_CREDENTIAL_REF) are not the native router's. An existing agent's
269
+ * unchanged model is not re-checked: if an admin later disabled it, the agent
270
+ * fails at its next run, not at an unrelated edit.
271
+ */
272
+ function requireModelAvailable(ctx, model, kind) {
273
+ try {
274
+ assertAgentModelAvailable(model, kind === "native" ? ctx.providers.llm : undefined);
275
+ }
276
+ catch (err) {
277
+ if (err instanceof ModelUnavailableError)
278
+ throw new McpError(400, err.message);
279
+ throw err;
280
+ }
281
+ }
146
282
  function storedProfile(profile) {
147
283
  return CodingProfileSchema.parse({
148
284
  provider: profile.provider,
@@ -151,14 +287,27 @@ function storedProfile(profile) {
151
287
  defaultTask: profile.defaultTask,
152
288
  allowWebhookTaskOverride: profile.allowWebhookTaskOverride,
153
289
  timeoutSec: profile.timeoutSec,
154
- allowedEgress: profile.allowedEgress,
155
290
  protectedPaths: profile.protectedPaths,
291
+ collectExclude: profile.collectExclude,
156
292
  toolchain: profile.toolchain,
157
293
  toolchainVersion: profile.toolchainVersion,
158
294
  workerImageRef: profile.workerImageRef,
159
295
  workspaceDiskMb: profile.workspaceDiskMb,
296
+ packageAllowlist: profile.packageAllowlist,
297
+ packagePolicy: profile.packagePolicy,
298
+ services: profile.services,
160
299
  });
161
300
  }
301
+ /** A service name an agent is allowed must exist in the catalog (any version); typos fail here, not at a run. */
302
+ async function requireCatalogServiceNames(db, names) {
303
+ if (names.length === 0)
304
+ return;
305
+ const known = new Set((await db.codingService.findMany({ where: { name: { in: [...names] } }, select: { name: true } })).map((row) => row.name));
306
+ const unknown = names.filter((name) => !known.has(name));
307
+ if (unknown.length > 0) {
308
+ throw new McpError(400, `wardby's service catalog has no service named ${unknown.map((name) => `"${name}"`).join(", ")}; list_services shows the catalog.`);
309
+ }
310
+ }
162
311
  export function registerAgentTools(mcp) {
163
312
  mcp.registerTool({
164
313
  name: "create_agent",
@@ -178,7 +327,15 @@ export function registerAgentTools(mcp) {
178
327
  kind: { type: "string", enum: ["native", "coding"] },
179
328
  budgetGroupId: { type: "string" },
180
329
  memoryEnabled: { type: "boolean" },
330
+ effort: {
331
+ type: "string",
332
+ enum: [...LLM_EFFORT_LEVELS],
333
+ description: "Reasoning effort for native agents. Unset uses the provider default.",
334
+ },
335
+ defectProjectKey: { type: "string", pattern: PROJECT_KEY.source, description: DEFECT_DESCRIPTION },
336
+ defectIssueType: { type: "string", minLength: 1, maxLength: 100, description: DEFECT_DESCRIPTION },
181
337
  codingProfile: { ...profileJsonSchema, required: ["repository"] },
338
+ repositoryAdminOverride: REPOSITORY_ADMIN_OVERRIDE,
182
339
  },
183
340
  required: ["name", "systemPrompt", "model", "budgetUsd"],
184
341
  },
@@ -186,16 +343,40 @@ export function registerAgentTools(mcp) {
186
343
  const args = parseCreateAgent(rawArgs);
187
344
  if (args.codingProfile?.workerImageRef != null)
188
345
  requireWorkerImageRefScope(ctx);
346
+ const packages = args.codingProfile;
347
+ if (packages &&
348
+ (Object.keys(packages.packageAllowlist ?? {}).length > 0 ||
349
+ Object.keys(packages.packagePolicy ?? {}).length > 0))
350
+ requirePackageApproval(ctx);
189
351
  validateSchedule(args.schedule, args.timezone ?? "UTC");
352
+ requireModelAvailable(ctx, args.model, args.kind);
353
+ validateEffort(args.kind, args.model, args.effort);
354
+ if (args.codingProfile)
355
+ await requireCatalogServiceNames(ctx.db, args.codingProfile.services);
190
356
  if (args.budgetGroupId) {
191
357
  await requireReadableBudgetGroup(ctx.db, args.budgetGroupId, ctx.principal.id);
192
358
  }
193
- const { codingProfile, ...agentData } = args;
359
+ const { codingProfile, repositoryAdminOverride, ...agentData } = args;
360
+ if (repositoryAdminOverride === true && !codingProfile) {
361
+ throw new McpError(400, "repositoryAdminOverride only applies with a codingProfile repository.");
362
+ }
363
+ // The creator is the owner, so it's the creator's GitHub access that counts.
364
+ const authorization = codingProfile
365
+ ? await authorizeRepositoryForSet(ctx, {
366
+ ownerId: ctx.principal.id,
367
+ provider: "github",
368
+ repository: codingProfile.repository,
369
+ kind: "coding",
370
+ adminOverride: repositoryAdminOverride === true,
371
+ })
372
+ : null;
194
373
  const agent = await ctx.db.agent.create({
195
374
  data: {
196
375
  ...agentData,
197
376
  ownerId: ctx.principal.id,
198
- ...(codingProfile ? { codingProfile: { create: codingProfile } } : {}),
377
+ ...(codingProfile && authorization
378
+ ? { codingProfile: { create: { ...codingProfile, ...profileStamp(authorization) } } }
379
+ : {}),
199
380
  },
200
381
  include: { codingProfile: true },
201
382
  });
@@ -221,7 +402,35 @@ export function registerAgentTools(mcp) {
221
402
  kind: { type: "string", enum: ["native", "coding"] },
222
403
  budgetGroupId: { type: ["string", "null"] },
223
404
  memoryEnabled: { type: "boolean" },
224
- codingProfile: profileJsonSchema,
405
+ effort: {
406
+ type: ["string", "null"],
407
+ enum: [...LLM_EFFORT_LEVELS, null],
408
+ description: "Reasoning effort for native agents. Null clears it back to the provider default.",
409
+ },
410
+ defectProjectKey: {
411
+ type: ["string", "null"],
412
+ pattern: PROJECT_KEY.source,
413
+ description: `${DEFECT_DESCRIPTION} Null (together with defectIssueType) clears it.`,
414
+ },
415
+ defectIssueType: {
416
+ type: ["string", "null"],
417
+ minLength: 1,
418
+ maxLength: 100,
419
+ description: `${DEFECT_DESCRIPTION} Null (together with defectProjectKey) clears it.`,
420
+ },
421
+ codingProfile: {
422
+ ...profileJsonSchema,
423
+ properties: {
424
+ ...profileJsonSchema.properties,
425
+ debugTraceMinutes: {
426
+ type: ["integer", "null"],
427
+ minimum: 1,
428
+ maximum: MAX_DEBUG_TRACE_MINUTES,
429
+ description: "Admins only (agents:admin): trace every coding run of this agent for this many minutes from now; the worker writes its full stream trace, which can include prompts and repository content, to the run pod's log. Null turns it off.",
430
+ },
431
+ },
432
+ },
433
+ repositoryAdminOverride: REPOSITORY_ADMIN_OVERRIDE,
225
434
  },
226
435
  required: ["id"],
227
436
  },
@@ -229,18 +438,111 @@ export function registerAgentTools(mcp) {
229
438
  const args = parseUpdateAgent(rawArgs);
230
439
  if (args.codingProfile?.workerImageRef !== undefined)
231
440
  requireWorkerImageRefScope(ctx);
441
+ const debugTraceMinutes = args.codingProfile?.debugTraceMinutes;
442
+ if (debugTraceMinutes !== undefined)
443
+ requireDebugTraceScope(ctx);
444
+ if (args.codingProfile?.packageAllowlist !== undefined || args.codingProfile?.packagePolicy !== undefined)
445
+ requirePackageApproval(ctx);
446
+ // Repository authorization calls GitHub, so it runs before (outside) the
447
+ // serializable transaction, on a pre-read; the transaction then verifies
448
+ // the pre-read still holds. Only a changed repository (or becoming a
449
+ // coding agent) is re-authorized; other edits keep the existing stamp.
450
+ const before = await ctx.db.agent.findUnique({ where: { id: args.id }, include: { codingProfile: true } });
451
+ if (!before)
452
+ throw agentNotFound(args.id);
453
+ // Strict: a repository is a binding, so only the agent's owner sets
454
+ // one (or an admin's explicit approval, below). Not a write-grantee,
455
+ // not the stdio operator.
456
+ const isOwner = before.ownerId !== null && before.ownerId === ctx.principal.id;
457
+ // An admin may approve a repository (repositoryAdminOverride) on an agent
458
+ // they don't own, but change nothing else on it.
459
+ const adminEdit = args.repositoryAdminOverride === true && !isOwner;
460
+ if (adminEdit) {
461
+ requireScope(ctx, ctx.canonicalUri, "agents:admin");
462
+ const { id: _id, repositoryAdminOverride: _flag, codingProfile, ...others } = args;
463
+ const profileKeys = Object.keys(codingProfile ?? {});
464
+ if (Object.values(others).some((v) => v !== undefined) ||
465
+ profileKeys.length !== 1 ||
466
+ profileKeys[0] !== "repository") {
467
+ throw new McpError(403, "On an agent you don't own, an admin may change only codingProfile.repository (with repositoryAdminOverride).");
468
+ }
469
+ }
470
+ else {
471
+ await assertAgentAccess(ctx, before, args.id, "write");
472
+ }
473
+ const repositoryAfter = (kind, profile) => kind === "coding" ? (args.codingProfile?.repository ?? profile?.repository ?? null) : null;
474
+ const plannedRepository = repositoryAfter(args.kind ?? before.kind, before.codingProfile);
475
+ const repositoryChanges = (existing) => {
476
+ const next = repositoryAfter(args.kind ?? existing.kind, existing.codingProfile);
477
+ return next !== null && (existing.kind !== "coding" || existing.codingProfile?.repository !== next);
478
+ };
479
+ const refuseRepositoryBinding = () => {
480
+ throw new McpError(403, `Agent "${args.id}": only its owner can give it a repository (codingProfile.repository, or becoming a coding agent); a repository is a binding, not shared config.`);
481
+ };
482
+ if (!adminEdit && !isOwner && repositoryChanges(before))
483
+ refuseRepositoryBinding();
484
+ // The coding profile as a whole (task, base ref, protected paths,
485
+ // task-override opt-in, image, packages, limits...), leaving coding,
486
+ // and budget-group membership direct what runs do with the owner's
487
+ // repository access and money: owner-only, like the repository itself
488
+ // (review I1/M1/M6). Write keeps name, prompt, model, budget amount,
489
+ // turns, effort, memory and schedule.
490
+ const ownerOnlyChange = (existingKind) => args.codingProfile !== undefined ||
491
+ (args.kind !== undefined && args.kind !== existingKind) ||
492
+ args.budgetGroupId !== undefined;
493
+ const refuseOwnerOnly = () => {
494
+ throw new McpError(403, `Agent "${args.id}": only its owner can change its coding profile, kind or budget group.`);
495
+ };
496
+ if (!adminEdit && !isOwner && ownerOnlyChange(before.kind))
497
+ refuseOwnerOnly();
498
+ const authorization = plannedRepository !== null && repositoryChanges(before)
499
+ ? await authorizeRepositoryForSet(ctx, {
500
+ ownerId: before.ownerId,
501
+ provider: "github",
502
+ repository: plannedRepository,
503
+ kind: "coding",
504
+ adminOverride: args.repositoryAdminOverride === true,
505
+ })
506
+ : null;
507
+ if (args.repositoryAdminOverride === true && !authorization) {
508
+ throw new McpError(400, "repositoryAdminOverride only applies when the coding repository changes.");
509
+ }
510
+ const debugTraceUntil = debugTraceMinutes === undefined
511
+ ? undefined
512
+ : debugTraceMinutes === null
513
+ ? null
514
+ : new Date(Date.now() + debugTraceMinutes * 60_000);
232
515
  const agent = await ctx.db.$transaction(async (tx) => {
233
516
  const existing = await tx.agent.findUnique({ where: { id: args.id }, include: { codingProfile: true } });
234
517
  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.`);
518
+ throw agentNotFound(args.id);
519
+ if (!adminEdit) {
520
+ await assertAgentAccess(ctx, existing, args.id, "write", tx);
521
+ if (!isOwner && repositoryChanges(existing))
522
+ refuseRepositoryBinding();
523
+ if (!isOwner && ownerOnlyChange(existing.kind))
524
+ refuseOwnerOnly();
525
+ }
526
+ if (existing.ownerId !== before.ownerId ||
527
+ (repositoryChanges(existing) &&
528
+ (!authorization ||
529
+ repositoryAfter(args.kind ?? existing.kind, existing.codingProfile) !== plannedRepository))) {
530
+ throw new McpError(409, `Agent "${args.id}" changed while it was being updated; try again.`);
531
+ }
237
532
  if (args.budgetGroupId) {
238
- await requireReadableBudgetGroup(ctx.db, args.budgetGroupId, ctx.principal.id);
533
+ await requireReadableBudgetGroup(tx, args.budgetGroupId, ctx.principal.id);
239
534
  }
240
535
  const nextKind = args.kind ?? existing.kind;
241
536
  if (nextKind === "native" && args.codingProfile) {
242
537
  throw new McpError(400, "A coding profile is only valid for coding agents.");
243
538
  }
539
+ if (args.model !== undefined)
540
+ requireModelAvailable(ctx, args.model, nextKind);
541
+ // Only re-checked when the model, effort or kind changes: an agent whose
542
+ // model was since disabled can still take unrelated edits.
543
+ if (args.model !== undefined || args.effort !== undefined || nextKind !== existing.kind) {
544
+ validateEffort(nextKind, args.model ?? existing.model, args.effort !== undefined ? args.effort : existing.effort);
545
+ }
244
546
  let nextProfile = null;
245
547
  if (nextKind === "coding") {
246
548
  const attachedTools = await tx.agentTool.count({ where: { agentId: args.id } });
@@ -248,12 +550,18 @@ export function registerAgentTools(mcp) {
248
550
  throw new McpError(400, "Detach all native sandbox tools before changing an agent to coding.");
249
551
  }
250
552
  const currentProfile = existing.codingProfile ? storedProfile(existing.codingProfile) : undefined;
251
- const profileResult = CodingProfileSchema.safeParse({ ...currentProfile, ...args.codingProfile });
553
+ const { debugTraceMinutes: _minutes, ...profilePatch } = args.codingProfile ?? {};
554
+ const profileResult = CodingProfileSchema.safeParse({ ...currentProfile, ...profilePatch });
252
555
  if (!profileResult.success)
253
556
  throw invalidArguments("coding profile", profileResult.error);
254
557
  nextProfile = profileResult.data;
558
+ if (args.codingProfile?.services !== undefined)
559
+ await requireCatalogServiceNames(tx, nextProfile.services);
255
560
  const nextModel = args.model ?? existing.model;
256
- if (!codingProviderSupportsModel(nextProfile.provider, nextModel)) {
561
+ const pairingChanges = args.model !== undefined ||
562
+ existing.kind !== "coding" ||
563
+ existing.codingProfile?.provider !== nextProfile.provider;
564
+ if (pairingChanges && !codingProviderSupportsModel(nextProfile.provider, nextModel)) {
257
565
  throw new McpError(400, `Model "${nextModel}" is not supported by coding provider "${nextProfile.provider}".`);
258
566
  }
259
567
  }
@@ -264,14 +572,16 @@ export function registerAgentTools(mcp) {
264
572
  if (nextKind === "coding" && nextSchedule && nextScheduleEnabled && !nextProfile?.defaultTask) {
265
573
  throw new McpError(400, "A default task is required for an enabled coding-agent schedule.");
266
574
  }
267
- const { id, codingProfile: _profilePatch, ...updates } = args;
575
+ const { id, codingProfile: _profilePatch, repositoryAdminOverride: _override, ...updates } = args;
576
+ const stamp = authorization ? profileStamp(authorization) : {};
577
+ const trace = debugTraceUntil === undefined ? {} : { debugTraceUntil };
268
578
  const profileMutation = nextKind === "native"
269
579
  ? existing.codingProfile
270
580
  ? { delete: true }
271
581
  : undefined
272
582
  : existing.codingProfile
273
- ? { update: nextProfile }
274
- : { create: nextProfile };
583
+ ? { update: { ...nextProfile, ...stamp, ...trace } }
584
+ : { create: { ...nextProfile, ...stamp, ...trace } };
275
585
  return tx.agent.update({
276
586
  where: { id },
277
587
  data: {
@@ -281,6 +591,14 @@ export function registerAgentTools(mcp) {
281
591
  include: { codingProfile: true },
282
592
  });
283
593
  }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
594
+ if (debugTraceUntil !== undefined) {
595
+ agentsLog.info({
596
+ event: "coding.debug_trace.set",
597
+ agentId: agent.id,
598
+ until: debugTraceUntil?.toISOString() ?? null,
599
+ by: ctx.principal.id,
600
+ }, "coding debug trace set");
601
+ }
284
602
  return textResult(agent);
285
603
  },
286
604
  });
@@ -289,11 +607,9 @@ export function registerAgentTools(mcp) {
289
607
  scope: "agents:read",
290
608
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
291
609
  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);
610
+ const [where, accessOf] = await Promise.all([readableAgentsWhere(ctx), agentAccessResolver(ctx)]);
611
+ const agents = await ctx.db.agent.findMany({ where, include: { codingProfile: true } });
612
+ return textResult(agents.map((agent) => ({ ...agent, access: accessOf(agent) })));
297
613
  },
298
614
  });
299
615
  mcp.registerTool({
@@ -306,14 +622,20 @@ export function registerAgentTools(mcp) {
306
622
  additionalProperties: false,
307
623
  },
308
624
  handler: async (args, ctx) => {
309
- const agent = await ctx.db.agent.findUnique({
625
+ const { agent, access } = await assertAgentAccess(ctx, await ctx.db.agent.findUnique({
310
626
  where: { id: args.id },
311
- include: { tools: { include: { tool: true } }, codingProfile: true },
627
+ include: { tools: { include: { tool: true } }, codingProfile: true, repositories: true },
628
+ }), args.id, "read");
629
+ // A7: a tool's code and schema are its owner's, whoever can read the
630
+ // agent (the agent's owner included). Capabilities stay visible: config.
631
+ return textResult({
632
+ ...agent,
633
+ access,
634
+ tools: agent.tools.map((attachment) => ({
635
+ ...attachment,
636
+ tool: projectTool(attachment.tool, ctx.principal.id),
637
+ })),
312
638
  });
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
639
  },
318
640
  });
319
641
  mcp.registerTool({
@@ -326,9 +648,13 @@ export function registerAgentTools(mcp) {
326
648
  additionalProperties: false,
327
649
  },
328
650
  handler: async (args, ctx) => {
329
- await requireOwnedAgent(ctx.db, args.id, ctx.principal.id);
651
+ await requireAgentAccess(ctx, args.id, "owner");
330
652
  try {
331
- await ctx.db.agent.delete({ where: { id: args.id } });
653
+ // Grants have no FK to the resource: removed in the same transaction.
654
+ await ctx.db.$transaction(async (tx) => {
655
+ await deleteGrantsFor(tx, "agent", args.id);
656
+ await tx.agent.delete({ where: { id: args.id } });
657
+ });
332
658
  }
333
659
  catch (err) {
334
660
  // Run.agentId has no onDelete rule, so Postgres refuses to delete an
@@ -343,33 +669,176 @@ export function registerAgentTools(mcp) {
343
669
  return textResult({ deleted: args.id });
344
670
  },
345
671
  });
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.
672
+ // Deliberately bypasses access checks entirely: that's the point of this
673
+ // tool, the audited agents:admin escape hatch. Gated on agents:admin, a
674
+ // step up from agents:write, so an ordinary caller can never reach it
675
+ // regardless of what they own. The new owner must exist; releasing an
676
+ // agent to owner-less is gone (sharing is done with grants). Everything
677
+ // bound to the agent belongs to its owner, so a transfer drops what the
678
+ // new owner doesn't own (resource-sharing grants spec §3.6, R2-1).
353
679
  mcp.registerTool({
354
680
  name: "make_owner",
355
681
  scope: "agents:admin",
682
+ description: "Admins: gives an agent a new owner (an existing principal id). Secret and datastore bindings the new owner doesn't own are removed; tool capabilities the new owner never granted are suspended until they re-run attach_tool; sub-agent edges the new owner can't delegate are removed; between two owners every grant on the agent is reset (adopting an owner-less agent keeps them). Returns what changed.",
356
683
  inputSchema: {
357
684
  type: "object",
358
685
  additionalProperties: false,
359
- properties: { agentId: { type: "string" }, ownerId: { type: ["string", "null"] } },
686
+ properties: {
687
+ agentId: { type: "string" },
688
+ ownerId: { type: ["string", "null"] },
689
+ keepEveryoneExecute: {
690
+ type: "boolean",
691
+ description: "When adopting an owner-less agent: keep an everyone grant at execute instead of lowering it to read. Anyone could then run it with the new owner's secrets and capabilities.",
692
+ },
693
+ },
360
694
  required: ["agentId", "ownerId"],
361
695
  },
362
696
  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.`);
697
+ if (args.ownerId === null) {
698
+ throw new McpError(400, "make_owner no longer releases an agent to owner-less; share it with grant_access instead (everyone at execute, or named principals).");
370
699
  }
371
- const updated = await ctx.db.agent.update({ where: { id: args.agentId }, data: { ownerId: args.ownerId } });
372
- return textResult(updated);
700
+ const newOwner = args.ownerId;
701
+ const principal = await ctx.db.principal.findUnique({ where: { id: newOwner } });
702
+ if (!principal)
703
+ throw new McpError(400, `Principal "${newOwner}" not found.`);
704
+ const result = await ctx.db.$transaction(async (tx) => {
705
+ const agent = await tx.agent.findUnique({ where: { id: args.agentId }, include: { codingProfile: true } });
706
+ if (!agent)
707
+ throw agentNotFound(args.agentId);
708
+ // Admin and grandfathered repository approvals were granted to the
709
+ // agent under its current owner. Moving it away from that owner
710
+ // turns them into ordinary checks of the next owner's own GitHub
711
+ // access, so an approval never travels with the agent. An
712
+ // owner-less agent getting its first owner keeps them: that is the
713
+ // pre-upgrade step for owner-less agents with repositories.
714
+ const revoked = [];
715
+ const transfer = agent.ownerId !== null && agent.ownerId !== newOwner;
716
+ if (transfer) {
717
+ const approved = { in: ["admin", "grandfathered"] };
718
+ const reset = { authorizedVia: "host_permission", authorizedById: null, authorizedAt: new Date() };
719
+ const links = await tx.agentRepository.findMany({
720
+ where: { agentId: agent.id, authorizedVia: approved },
721
+ orderBy: { repository: "asc" },
722
+ });
723
+ await tx.agentRepository.updateMany({ where: { agentId: agent.id, authorizedVia: approved }, data: reset });
724
+ revoked.push(...links.map((l) => l.repository));
725
+ const profile = await tx.codingAgentProfile.updateMany({
726
+ where: { agentId: agent.id, repositoryAuthorizedVia: approved },
727
+ data: {
728
+ repositoryAuthorizedVia: reset.authorizedVia,
729
+ repositoryAuthorizedById: null,
730
+ repositoryAuthorizedAt: reset.authorizedAt,
731
+ },
732
+ });
733
+ if (profile.count > 0 && agent.codingProfile)
734
+ revoked.push(agent.codingProfile.repository);
735
+ }
736
+ // Grants: between two owners every grant is reset (the new owner
737
+ // decides who shares it); adopting an owner-less agent keeps them,
738
+ // but lowers an everyone grant to read unless keepEveryoneExecute:
739
+ // the agent may regain the new owner's secrets and capabilities
740
+ // (review I3).
741
+ const grantsReset = transfer ? await deleteGrantsFor(tx, "agent", agent.id) : 0;
742
+ let everyoneGrant = null;
743
+ if (agent.ownerId === null) {
744
+ const everyone = await tx.resourceGrant.findFirst({
745
+ where: { resourceType: "agent", resourceId: agent.id, granteeKey: EVERYONE_KEY },
746
+ });
747
+ if (everyone) {
748
+ everyoneGrant = { before: everyone.level, after: everyone.level };
749
+ if (everyone.level !== "read" && args.keepEveryoneExecute !== true) {
750
+ await tx.resourceGrant.update({
751
+ where: { id: everyone.id },
752
+ data: { level: "read", source: "operator", grantedById: ctx.principal.id },
753
+ });
754
+ everyoneGrant.after = "read";
755
+ }
756
+ }
757
+ }
758
+ const updated = await tx.agent.update({ where: { id: args.agentId }, data: { ownerId: newOwner } });
759
+ // Secret/datastore bindings the new owner doesn't own: removed.
760
+ const secrets = await tx.agentSecret.findMany({
761
+ where: { agentId: agent.id },
762
+ include: { secret: { select: { ownerId: true } } },
763
+ });
764
+ const foreignSecrets = secrets.filter((b) => b.secret.ownerId !== newOwner);
765
+ if (foreignSecrets.length > 0) {
766
+ await tx.agentSecret.deleteMany({
767
+ where: { agentId: agent.id, secretId: { in: foreignSecrets.map((b) => b.secretId) } },
768
+ });
769
+ }
770
+ const datastores = await tx.agentDatastore.findMany({
771
+ where: { agentId: agent.id },
772
+ include: { datastore: { select: { ownerId: true } } },
773
+ });
774
+ const foreignDatastores = datastores.filter((b) => b.datastore.ownerId !== newOwner);
775
+ if (foreignDatastores.length > 0) {
776
+ await tx.agentDatastore.deleteMany({
777
+ where: { agentId: agent.id, datastoreId: { in: foreignDatastores.map((b) => b.datastoreId) } },
778
+ });
779
+ }
780
+ const bindingsRemoved = [
781
+ ...foreignSecrets.map((b) => ({ kind: "secret", boundName: b.boundName, resourceId: b.secretId })),
782
+ ...foreignDatastores.map((b) => ({ kind: "datastore", boundName: b.boundName, resourceId: b.datastoreId })),
783
+ ];
784
+ // Tool capabilities: rows aren't rewritten; the runner ignores any
785
+ // the new owner never stamped (capabilitiesGrantedById).
786
+ const attachments = await tx.agentTool.findMany({
787
+ where: { agentId: agent.id },
788
+ include: { tool: { select: { id: true, name: true } } },
789
+ });
790
+ const toolCapabilitiesSuspended = attachments
791
+ .filter((at) => at.capabilitiesGrantedById !== newOwner)
792
+ .map((at) => ({ toolId: at.tool.id, toolName: at.tool.name }));
793
+ // Sub-agent edges (both directions) the new owner can't delegate.
794
+ const edges = await tx.agentSubAgent.findMany({
795
+ where: { OR: [{ parentAgentId: agent.id }, { childAgentId: agent.id }] },
796
+ include: {
797
+ parent: { select: { id: true, ownerId: true } },
798
+ child: { select: { id: true, ownerId: true } },
799
+ },
800
+ });
801
+ const subAgentEdgesRemoved = [];
802
+ for (const edge of edges) {
803
+ const parent = edge.parentAgentId === agent.id ? { ownerId: newOwner } : edge.parent;
804
+ const child = edge.childAgentId === agent.id ? { id: agent.id, ownerId: newOwner } : edge.child;
805
+ if (!(await canDelegate(tx, parent, child))) {
806
+ subAgentEdgesRemoved.push({
807
+ parentAgentId: edge.parentAgentId,
808
+ childAgentId: edge.childAgentId,
809
+ boundName: edge.boundName,
810
+ });
811
+ }
812
+ }
813
+ for (const edge of subAgentEdgesRemoved) {
814
+ await tx.agentSubAgent.deleteMany({
815
+ where: { parentAgentId: edge.parentAgentId, childAgentId: edge.childAgentId },
816
+ });
817
+ }
818
+ // Webhooks others created stay, but fire only while their creator
819
+ // may still trigger the agent (core/webhooks.ts).
820
+ const webhooks = await tx.webhook.findMany({ where: { agentId: agent.id } });
821
+ const webhooksInactive = [];
822
+ for (const webhook of webhooks) {
823
+ if (webhook.ownerId === newOwner)
824
+ continue;
825
+ const creatorAccess = await effectiveAccess(tx, "agent", updated, webhook.ownerId);
826
+ if (!atLeast("agent", creatorAccess, "execute")) {
827
+ webhooksInactive.push({ id: webhook.id, createdBy: webhook.ownerId });
828
+ }
829
+ }
830
+ return {
831
+ ...updated,
832
+ repositoryApprovalsRevoked: revoked,
833
+ bindingsRemoved,
834
+ toolCapabilitiesSuspended,
835
+ subAgentEdgesRemoved,
836
+ grantsReset,
837
+ everyoneGrant,
838
+ webhooksInactive,
839
+ };
840
+ }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
841
+ return textResult(result);
373
842
  },
374
843
  });
375
844
  }