@wardby/cli 0.2.0 → 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 (491) hide show
  1. package/.env.example +34 -4
  2. package/README.md +183 -71
  3. package/dist/claude-coding-worker/driver.d.ts +7 -1
  4. package/dist/claude-coding-worker/driver.js +31 -2
  5. package/dist/claude-coding-worker/main.js +5 -2
  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 +99 -3
  21. package/dist/coding/protocol.js +155 -4
  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 +18 -0
  60. package/dist/coding-worker/artifact.js +48 -6
  61. package/dist/coding-worker/debug-trace.d.ts +37 -0
  62. package/dist/coding-worker/debug-trace.js +117 -0
  63. package/dist/coding-worker/driver.d.ts +14 -1
  64. package/dist/coding-worker/driver.js +106 -14
  65. package/dist/coding-worker/errors.d.ts +7 -0
  66. package/dist/coding-worker/errors.js +26 -0
  67. package/dist/coding-worker/main.js +13 -4
  68. package/dist/coding-worker/sdk.d.ts +2 -2
  69. package/dist/coding-worker/sdk.js +7 -2
  70. package/dist/coding-worker/types.d.ts +3 -0
  71. package/dist/config/providers.d.ts +41 -0
  72. package/dist/config/providers.js +66 -0
  73. package/dist/core/budget-groups.d.ts +109 -6
  74. package/dist/core/budget-groups.js +125 -19
  75. package/dist/core/budget-wording.d.ts +22 -0
  76. package/dist/core/budget-wording.js +55 -0
  77. package/dist/core/coding-queue.d.ts +1 -1
  78. package/dist/core/datastores.d.ts +1 -1
  79. package/dist/core/datastores.js +18 -2
  80. package/dist/core/db.d.ts +40 -2
  81. package/dist/core/db.js +75 -2
  82. package/dist/core/dispatch.d.ts +49 -7
  83. package/dist/core/dispatch.js +290 -45
  84. package/dist/core/engine-native.js +27 -10
  85. package/dist/core/grants.d.ts +86 -0
  86. package/dist/core/grants.js +126 -0
  87. package/dist/core/host-events.d.ts +49 -0
  88. package/dist/core/host-events.js +257 -0
  89. package/dist/core/host-identity-links.d.ts +54 -0
  90. package/dist/core/host-identity-links.js +189 -0
  91. package/dist/core/host-status.d.ts +61 -0
  92. package/dist/core/host-status.js +211 -0
  93. package/dist/core/http-runtime.js +3 -4
  94. package/dist/core/in-flight-runs.d.ts +8 -0
  95. package/dist/core/in-flight-runs.js +56 -0
  96. package/dist/core/lease.d.ts +1 -1
  97. package/dist/core/private-directory.d.ts +20 -0
  98. package/dist/core/private-directory.js +36 -0
  99. package/dist/core/provider-wording.d.ts +12 -0
  100. package/dist/core/provider-wording.js +44 -0
  101. package/dist/core/reconciler.d.ts +39 -3
  102. package/dist/core/reconciler.js +86 -2
  103. package/dist/core/repo-access.d.ts +99 -0
  104. package/dist/core/repo-access.js +136 -0
  105. package/dist/core/review-host-checks.d.ts +11 -0
  106. package/dist/core/review-host-checks.js +41 -0
  107. package/dist/core/review-host-tools.d.ts +47 -0
  108. package/dist/core/review-host-tools.js +347 -0
  109. package/dist/core/run-heartbeat.d.ts +27 -0
  110. package/dist/core/run-heartbeat.js +54 -0
  111. package/dist/core/runner.d.ts +31 -9
  112. package/dist/core/runner.js +237 -40
  113. package/dist/core/scheduler.d.ts +1 -1
  114. package/dist/core/secrets.d.ts +12 -3
  115. package/dist/core/secrets.js +25 -6
  116. package/dist/core/subagent-memory-tools.d.ts +2 -2
  117. package/dist/core/subagent-memory-tools.js +16 -3
  118. package/dist/core/tool-admin.d.ts +81 -0
  119. package/dist/core/tool-admin.js +129 -0
  120. package/dist/core/tool-names.d.ts +42 -0
  121. package/dist/core/tool-names.js +64 -0
  122. package/dist/core/untrusted-content.d.ts +32 -0
  123. package/dist/core/untrusted-content.js +72 -0
  124. package/dist/core/webhooks.d.ts +9 -2
  125. package/dist/core/webhooks.js +23 -2
  126. package/dist/generated/prisma/browser.d.ts +328 -0
  127. package/dist/generated/prisma/browser.js +17 -0
  128. package/dist/generated/prisma/client.d.ts +347 -0
  129. package/dist/generated/prisma/client.js +34 -0
  130. package/dist/generated/prisma/commonInputTypes.d.ts +839 -0
  131. package/dist/generated/prisma/commonInputTypes.js +10 -0
  132. package/dist/generated/prisma/enums.d.ts +59 -0
  133. package/dist/generated/prisma/enums.js +59 -0
  134. package/dist/generated/prisma/internal/class.d.ts +605 -0
  135. package/dist/generated/prisma/internal/class.js +49 -0
  136. package/dist/generated/prisma/internal/prismaNamespace.d.ts +4494 -0
  137. package/dist/generated/prisma/internal/prismaNamespace.js +628 -0
  138. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +635 -0
  139. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +599 -0
  140. package/dist/generated/prisma/models/Agent.d.ts +3560 -0
  141. package/dist/generated/prisma/models/Agent.js +1 -0
  142. package/dist/generated/prisma/models/AgentDatastore.d.ts +1210 -0
  143. package/dist/generated/prisma/models/AgentDatastore.js +1 -0
  144. package/dist/generated/prisma/models/AgentMemory.d.ts +986 -0
  145. package/dist/generated/prisma/models/AgentMemory.js +1 -0
  146. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  147. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  148. package/dist/generated/prisma/models/AgentSecret.d.ts +1212 -0
  149. package/dist/generated/prisma/models/AgentSecret.js +1 -0
  150. package/dist/generated/prisma/models/AgentSubAgent.d.ts +1217 -0
  151. package/dist/generated/prisma/models/AgentSubAgent.js +1 -0
  152. package/dist/generated/prisma/models/AgentTool.d.ts +1403 -0
  153. package/dist/generated/prisma/models/AgentTool.js +1 -0
  154. package/dist/generated/prisma/models/AuthFormChallenge.d.ts +1298 -0
  155. package/dist/generated/prisma/models/AuthFormChallenge.js +1 -0
  156. package/dist/generated/prisma/models/AuthLoginKey.d.ts +1245 -0
  157. package/dist/generated/prisma/models/AuthLoginKey.js +1 -0
  158. package/dist/generated/prisma/models/AuthRateLimit.d.ts +979 -0
  159. package/dist/generated/prisma/models/AuthRateLimit.js +1 -0
  160. package/dist/generated/prisma/models/AuthSession.d.ts +1376 -0
  161. package/dist/generated/prisma/models/AuthSession.js +1 -0
  162. package/dist/generated/prisma/models/AuthUser.d.ts +1700 -0
  163. package/dist/generated/prisma/models/AuthUser.js +1 -0
  164. package/dist/generated/prisma/models/BudgetGroup.d.ts +1548 -0
  165. package/dist/generated/prisma/models/BudgetGroup.js +1 -0
  166. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +1681 -0
  167. package/dist/generated/prisma/models/CodingAgentProfile.js +1 -0
  168. package/dist/generated/prisma/models/CodingProxyRequest.d.ts +1749 -0
  169. package/dist/generated/prisma/models/CodingProxyRequest.js +1 -0
  170. package/dist/generated/prisma/models/CodingProxySession.d.ts +1626 -0
  171. package/dist/generated/prisma/models/CodingProxySession.js +1 -0
  172. package/dist/generated/prisma/models/CodingRun.d.ts +3583 -0
  173. package/dist/generated/prisma/models/CodingRun.js +1 -0
  174. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  175. package/dist/generated/prisma/models/CodingService.js +1 -0
  176. package/dist/generated/prisma/models/Datastore.d.ts +1434 -0
  177. package/dist/generated/prisma/models/Datastore.js +1 -0
  178. package/dist/generated/prisma/models/DatastoreEntry.d.ts +1314 -0
  179. package/dist/generated/prisma/models/DatastoreEntry.js +1 -0
  180. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  181. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  182. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  183. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  184. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  185. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  186. package/dist/generated/prisma/models/OAuthAuthorizationCode.d.ts +1526 -0
  187. package/dist/generated/prisma/models/OAuthAuthorizationCode.js +1 -0
  188. package/dist/generated/prisma/models/OAuthAuthorizationRequest.d.ts +1350 -0
  189. package/dist/generated/prisma/models/OAuthAuthorizationRequest.js +1 -0
  190. package/dist/generated/prisma/models/OAuthClient.d.ts +1289 -0
  191. package/dist/generated/prisma/models/OAuthClient.js +1 -0
  192. package/dist/generated/prisma/models/OAuthFamily.d.ts +1540 -0
  193. package/dist/generated/prisma/models/OAuthFamily.js +1 -0
  194. package/dist/generated/prisma/models/OAuthGrant.d.ts +1280 -0
  195. package/dist/generated/prisma/models/OAuthGrant.js +1 -0
  196. package/dist/generated/prisma/models/Principal.d.ts +2245 -0
  197. package/dist/generated/prisma/models/Principal.js +1 -0
  198. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  199. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  200. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  201. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  202. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  203. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  204. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  205. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  206. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  207. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  208. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  209. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  210. package/dist/generated/prisma/models/Run.d.ts +2669 -0
  211. package/dist/generated/prisma/models/Run.js +1 -0
  212. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  213. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  214. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  215. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  216. package/dist/generated/prisma/models/SchedulerLease.d.ts +970 -0
  217. package/dist/generated/prisma/models/SchedulerLease.js +1 -0
  218. package/dist/generated/prisma/models/Secret.d.ts +1399 -0
  219. package/dist/generated/prisma/models/Secret.js +1 -0
  220. package/dist/generated/prisma/models/SecretElicitationOutcome.d.ts +973 -0
  221. package/dist/generated/prisma/models/SecretElicitationOutcome.js +1 -0
  222. package/dist/generated/prisma/models/Task.d.ts +1176 -0
  223. package/dist/generated/prisma/models/Task.js +1 -0
  224. package/dist/generated/prisma/models/Tool.d.ts +1490 -0
  225. package/dist/generated/prisma/models/Tool.js +1 -0
  226. package/dist/generated/prisma/models/Webhook.d.ts +1386 -0
  227. package/dist/generated/prisma/models/Webhook.js +1 -0
  228. package/dist/generated/prisma/models.d.ts +45 -0
  229. package/dist/generated/prisma/models.js +1 -0
  230. package/dist/help/build.d.ts +1 -0
  231. package/dist/help/build.js +9 -0
  232. package/dist/help/catalog.d.ts +24 -0
  233. package/dist/help/catalog.js +160 -0
  234. package/dist/help/cli.d.ts +2 -0
  235. package/dist/help/cli.js +65 -0
  236. package/dist/help/runtime.d.ts +3 -0
  237. package/dist/help/runtime.js +44 -0
  238. package/dist/help/search.d.ts +9 -0
  239. package/dist/help/search.js +104 -0
  240. package/dist/help-index.json +660 -0
  241. package/dist/import/cli-args.js +3 -2
  242. package/dist/import/create.d.ts +6 -1
  243. package/dist/import/create.js +68 -14
  244. package/dist/import/index.d.ts +1 -1
  245. package/dist/import/index.js +19 -9
  246. package/dist/import/neutral-schema.d.ts +11 -11
  247. package/dist/mcp/auth/access.d.ts +70 -0
  248. package/dist/mcp/auth/access.js +58 -0
  249. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  250. package/dist/mcp/auth/grants-cli.js +518 -0
  251. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  252. package/dist/mcp/auth/host-account-cli.js +47 -0
  253. package/dist/mcp/auth/ownership.d.ts +37 -63
  254. package/dist/mcp/auth/ownership.js +19 -14
  255. package/dist/mcp/auth/principal.d.ts +1 -1
  256. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  257. package/dist/mcp/auth/repo-authorization.js +51 -0
  258. package/dist/mcp/auth/resource-server.d.ts +32 -3
  259. package/dist/mcp/auth/resource-server.js +84 -3
  260. package/dist/mcp/auth/self-hosted/browser.js +22 -8
  261. package/dist/mcp/auth/self-hosted/cli.d.ts +1 -1
  262. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  263. package/dist/mcp/auth/self-hosted/credentials.d.ts +21 -3
  264. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  265. package/dist/mcp/auth/self-hosted/rate-limit.d.ts +1 -1
  266. package/dist/mcp/auth/self-hosted/session.d.ts +3 -2
  267. package/dist/mcp/context.d.ts +28 -2
  268. package/dist/mcp/errors.d.ts +25 -6
  269. package/dist/mcp/errors.js +98 -0
  270. package/dist/mcp/host-events/github-ingress.d.ts +36 -0
  271. package/dist/mcp/host-events/github-ingress.js +92 -0
  272. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  273. package/dist/mcp/host-events/github-user-callback.js +50 -0
  274. package/dist/mcp/index.d.ts +8 -1
  275. package/dist/mcp/index.js +106 -16
  276. package/dist/mcp/server.d.ts +1 -1
  277. package/dist/mcp/server.js +27 -9
  278. package/dist/mcp/shared/page-style.d.ts +16 -0
  279. package/dist/mcp/shared/page-style.js +129 -0
  280. package/dist/mcp/tasks/manager.d.ts +1 -1
  281. package/dist/mcp/tools/agents.js +447 -49
  282. package/dist/mcp/tools/budget-groups.js +3 -3
  283. package/dist/mcp/tools/datastore.js +23 -16
  284. package/dist/mcp/tools/grants.d.ts +2 -0
  285. package/dist/mcp/tools/grants.js +239 -0
  286. package/dist/mcp/tools/help.d.ts +5 -0
  287. package/dist/mcp/tools/help.js +67 -0
  288. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  289. package/dist/mcp/tools/host-accounts.js +114 -0
  290. package/dist/mcp/tools/memory.d.ts +7 -1
  291. package/dist/mcp/tools/memory.js +5 -5
  292. package/dist/mcp/tools/repositories.d.ts +2 -0
  293. package/dist/mcp/tools/repositories.js +181 -0
  294. package/dist/mcp/tools/runs.d.ts +6 -0
  295. package/dist/mcp/tools/runs.js +60 -6
  296. package/dist/mcp/tools/scheduling.js +4 -7
  297. package/dist/mcp/tools/secret-elicitation-form.d.ts +1 -1
  298. package/dist/mcp/tools/secret-elicitation-form.js +10 -28
  299. package/dist/mcp/tools/secret-elicitation.d.ts +1 -1
  300. package/dist/mcp/tools/secrets.js +18 -6
  301. package/dist/mcp/tools/services.d.ts +2 -0
  302. package/dist/mcp/tools/services.js +222 -0
  303. package/dist/mcp/tools/subagents.js +53 -17
  304. package/dist/mcp/tools/tools.d.ts +41 -0
  305. package/dist/mcp/tools/tools.js +202 -41
  306. package/dist/mcp/tools/trigger.js +36 -6
  307. package/dist/mcp/tools/webhooks.js +15 -4
  308. package/dist/mcp/transport/streamable-http.d.ts +11 -1
  309. package/dist/mcp/transport/streamable-http.js +32 -2
  310. package/dist/mcp/unattended-schedules.d.ts +1 -1
  311. package/dist/mcp/webhooks/ingress.d.ts +1 -1
  312. package/dist/providers/auth/delegating.d.ts +19 -0
  313. package/dist/providers/auth/delegating.js +71 -0
  314. package/dist/providers/auth/index.d.ts +3 -1
  315. package/dist/providers/auth/index.js +11 -0
  316. package/dist/providers/auth/self-hosted.d.ts +4 -3
  317. package/dist/providers/auth/self-hosted.js +16 -4
  318. package/dist/providers/auth/types.d.ts +6 -0
  319. package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
  320. package/dist/providers/coding-proxy/memory-ledger.js +21 -2
  321. package/dist/providers/coding-proxy/metering.d.ts +4 -0
  322. package/dist/providers/coding-proxy/metering.js +28 -2
  323. package/dist/providers/coding-proxy/prisma-ledger.d.ts +18 -1
  324. package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
  325. package/dist/providers/coding-proxy/proxy.d.ts +13 -0
  326. package/dist/providers/coding-proxy/proxy.js +479 -18
  327. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  328. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  329. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  330. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  331. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  332. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  333. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  334. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  335. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  336. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  337. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  338. package/dist/providers/coding-proxy/registry/store.js +70 -0
  339. package/dist/providers/coding-proxy/runtime.d.ts +18 -1
  340. package/dist/providers/coding-proxy/runtime.js +63 -0
  341. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  342. package/dist/providers/coding-proxy/server.d.ts +11 -0
  343. package/dist/providers/coding-proxy/server.js +163 -0
  344. package/dist/providers/coding-proxy/types.d.ts +15 -0
  345. package/dist/providers/datastore/postgres.d.ts +1 -1
  346. package/dist/providers/engine/types.d.ts +10 -0
  347. package/dist/providers/executor/build.d.ts +3 -3
  348. package/dist/providers/executor/composition.d.ts +4 -1
  349. package/dist/providers/executor/composition.js +19 -0
  350. package/dist/providers/executor/container.d.ts +106 -4
  351. package/dist/providers/executor/container.js +305 -22
  352. package/dist/providers/executor/dbos.d.ts +3 -4
  353. package/dist/providers/executor/in-process.d.ts +2 -3
  354. package/dist/providers/executor/routing.d.ts +7 -0
  355. package/dist/providers/executor/routing.js +13 -0
  356. package/dist/providers/executor/types.d.ts +22 -0
  357. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  358. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  359. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  360. package/dist/providers/jobs/collect-prune.js +27 -0
  361. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  362. package/dist/providers/jobs/docker-isolation.js +213 -22
  363. package/dist/providers/jobs/docker-services.d.ts +35 -0
  364. package/dist/providers/jobs/docker-services.js +191 -0
  365. package/dist/providers/jobs/docker.d.ts +60 -2
  366. package/dist/providers/jobs/docker.js +313 -50
  367. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  368. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  369. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  370. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  371. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  372. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  373. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  374. package/dist/providers/jobs/kubernetes.d.ts +21 -2
  375. package/dist/providers/jobs/kubernetes.js +117 -31
  376. package/dist/providers/jobs/types.d.ts +20 -0
  377. package/dist/providers/llm/anthropic.js +6 -2
  378. package/dist/providers/llm/bedrock.js +2 -1
  379. package/dist/providers/llm/claude-messages.d.ts +5 -1
  380. package/dist/providers/llm/claude-messages.js +1 -0
  381. package/dist/providers/llm/claude-provider.d.ts +3 -1
  382. package/dist/providers/llm/claude-provider.js +5 -1
  383. package/dist/providers/llm/index.d.ts +1 -1
  384. package/dist/providers/llm/index.js +1 -1
  385. package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
  386. package/dist/providers/llm/pricing-anthropic.js +32 -14
  387. package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
  388. package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
  389. package/dist/providers/llm/routing.d.ts +10 -1
  390. package/dist/providers/llm/routing.js +18 -0
  391. package/dist/providers/llm/types.d.ts +12 -0
  392. package/dist/providers/llm/types.js +8 -1
  393. package/dist/providers/memory/postgres.d.ts +1 -1
  394. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  395. package/dist/providers/review-host/diff-lines.js +59 -0
  396. package/dist/providers/review-host/github-events.d.ts +6 -0
  397. package/dist/providers/review-host/github-events.js +180 -0
  398. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  399. package/dist/providers/review-host/github-user-auth.js +128 -0
  400. package/dist/providers/review-host/github.d.ts +57 -0
  401. package/dist/providers/review-host/github.js +567 -0
  402. package/dist/providers/review-host/index.d.ts +12 -0
  403. package/dist/providers/review-host/index.js +30 -0
  404. package/dist/providers/review-host/review-format.d.ts +19 -0
  405. package/dist/providers/review-host/review-format.js +59 -0
  406. package/dist/providers/review-host/types.d.ts +284 -0
  407. package/dist/providers/review-host/types.js +24 -0
  408. package/dist/providers/vcs/git.d.ts +16 -6
  409. package/dist/providers/vcs/git.js +79 -14
  410. package/dist/providers/vcs/github.d.ts +74 -3
  411. package/dist/providers/vcs/github.js +195 -15
  412. package/dist/providers/vcs/types.d.ts +42 -4
  413. package/dist/quickstart/index.js +34 -32
  414. package/dist/quickstart/migrate.d.ts +23 -0
  415. package/dist/quickstart/migrate.js +125 -0
  416. package/dist/sandbox/fetch-policy.d.ts +20 -2
  417. package/dist/sandbox/fetch-policy.js +64 -3
  418. package/dist/sandbox/host-functions.d.ts +9 -1
  419. package/dist/sandbox/host-functions.js +12 -5
  420. package/dist/serve.js +1 -1
  421. package/dist/wardby-bin.js +6 -0
  422. package/docs/README.md +30 -0
  423. package/docs/architecture-runtime.md +90 -0
  424. package/docs/assets/brand/wardby-icon-512.png +0 -0
  425. package/docs/assets/brand/wardby-icon.svg +16 -0
  426. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  427. package/docs/assets/brand/wardby-mascot.png +0 -0
  428. package/docs/assets/brand/wardby-mascot.svg +5 -0
  429. package/docs/assets/wardby-workflow.svg +106 -0
  430. package/docs/code-review-agents.md +481 -0
  431. package/docs/coding-agent-setup.md +172 -0
  432. package/docs/coding-packages.md +455 -0
  433. package/docs/coding-services.md +300 -0
  434. package/docs/coding-worker-byo-images.md +98 -0
  435. package/docs/coding-worker-isolation.md +1061 -0
  436. package/docs/getting-started-gke.md +615 -0
  437. package/docs/getting-started-identity-provider.md +308 -0
  438. package/docs/getting-started.md +133 -0
  439. package/docs/observability.md +53 -0
  440. package/docs/release-verification.md +66 -0
  441. package/docs/security-deployment.md +657 -0
  442. package/help/code-review-agents.md +31 -0
  443. package/help/coding-packages.md +31 -0
  444. package/help/coding-services.md +71 -0
  445. package/help/creating-agents.md +68 -0
  446. package/help/deploy-gke.md +39 -0
  447. package/help/deployment-targets.md +39 -0
  448. package/help/errors/budget-group-exhausted.md +25 -0
  449. package/help/errors/docker-isolation-unsupported.md +26 -0
  450. package/help/errors/protected-path.md +45 -0
  451. package/help/errors/repo-access.md +27 -0
  452. package/help/errors/service-declaration-invalid.md +27 -0
  453. package/help/errors/service-declaration-unavailable.md +25 -0
  454. package/help/errors/service-launcher-unsupported.md +30 -0
  455. package/help/errors/service-not-allowed.md +27 -0
  456. package/help/errors/service-unknown.md +23 -0
  457. package/help/errors/service-unready.md +49 -0
  458. package/help/getting-started.md +31 -0
  459. package/help/github.md +30 -0
  460. package/help/identity-and-access.md +39 -0
  461. package/help/mcp.md +30 -0
  462. package/help/native-capabilities.md +30 -0
  463. package/help/observability.md +41 -0
  464. package/help/operating-agents.md +30 -0
  465. package/help/security.md +27 -0
  466. package/help/troubleshooting/budgets.md +26 -0
  467. package/help/troubleshooting/coding-workers.md +47 -0
  468. package/help/troubleshooting/repository-access.md +27 -0
  469. package/package.json +30 -11
  470. package/prisma/migrate.config.mjs +20 -0
  471. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  472. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  473. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  474. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  475. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  476. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  477. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  478. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  479. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  480. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  481. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  482. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  483. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  484. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  485. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  486. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  487. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  488. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  489. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  490. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  491. package/prisma/schema.prisma +417 -23
@@ -0,0 +1,44 @@
1
+ /**
2
+ * What a host (a PR comment, a check summary) is told when the model provider
3
+ * itself refused a run's requests: the class of problem and who has to act.
4
+ * Public repositories read these, so they never carry the provider's own
5
+ * error code or message; the code stays in the operator log.
6
+ */
7
+ const CLASSES = ["quota", "rate_limited", "unavailable", "rejected"];
8
+ const QUOTA_MARKERS = ["quota", "spend_limit", "billing", "insufficient_funds", "credit"];
9
+ const UNAVAILABLE_CODES = new Set([
10
+ "server_error",
11
+ "overloaded",
12
+ "overloaded_error",
13
+ "service_unavailable",
14
+ // Anthropic's name for an internal server error.
15
+ "api_error",
16
+ ]);
17
+ /** Files an upstream error code (or `http_<status>`) under the class of problem it names. */
18
+ export function classifyProviderFailure(code) {
19
+ const lower = code.toLowerCase();
20
+ // Checked first: an exhausted account is the one case only an operator can fix.
21
+ if (QUOTA_MARKERS.some((marker) => lower.includes(marker)))
22
+ return "quota";
23
+ if (lower.includes("rate_limit"))
24
+ return "rate_limited";
25
+ if (UNAVAILABLE_CODES.has(lower) || /^http_5\d\d$/.test(lower))
26
+ return "unavailable";
27
+ return "rejected";
28
+ }
29
+ const SENTENCES = {
30
+ quota: "The model provider refused the request: its account has reached a spending or quota limit. An operator needs to raise the limit with the provider, then retry.",
31
+ rate_limited: "The model provider is rate-limiting requests. Try again later.",
32
+ unavailable: "The model provider reported an outage or overload. Try again later.",
33
+ rejected: "The model provider rejected the request.",
34
+ };
35
+ export function providerSentence(providerClass) {
36
+ return SENTENCES[providerClass];
37
+ }
38
+ /** The class a run's `provider_<class>` failure category names, or null for any other category. */
39
+ export function providerClassOfCategory(category) {
40
+ if (!category?.startsWith("provider_"))
41
+ return null;
42
+ const value = category.slice("provider_".length);
43
+ return CLASSES.includes(value) ? value : null;
44
+ }
@@ -27,17 +27,53 @@
27
27
  * UPDATE commits first flips the row out of the matched status, so the
28
28
  * second one's WHERE simply matches zero rows — double-reconcile is a
29
29
  * no-op, not a race.
30
+ *
31
+ * Each pass also completes review-host checks left "in progress" by a run
32
+ * that ended without reaching `executeRun`'s own finalizer (reaped here as
33
+ * `lost`, failed to start, failed while loading, ...). One sweep covers all
34
+ * of those paths instead of patching each. Mention status comments get the
35
+ * same sweep: it also catches a run that ended before its comment was posted.
30
36
  */
31
- import type { PrismaClient } from "@prisma/client";
37
+ import type { PrismaClient } from "#prisma";
32
38
  import type { Executor } from "../providers/executor/types.js";
33
- export type ReconcilerDb = Pick<PrismaClient, "run">;
39
+ import type { ReviewHostRegistry } from "../providers/review-host/types.js";
40
+ export type ReconcilerDb = Pick<PrismaClient, "run" | "runHostCheck" | "runHostStatus">;
41
+ /**
42
+ * How long after a run finishes before its still-open check counts as
43
+ * orphaned: long enough that the sweep never races `executeRun`'s own
44
+ * `closeOpenHostCheck` for a run that just ended.
45
+ */
46
+ export declare const ORPHANED_CHECK_GRACE_MS = 60000;
47
+ /** Upper bound on checks completed per pass, so a backlog drains over several passes. */
48
+ export declare const ORPHANED_CHECK_BATCH = 50;
49
+ /** How long after a run finishes the sweep keeps trying to complete its check. */
50
+ export declare const ORPHANED_CHECK_MAX_AGE_MS: number;
51
+ /**
52
+ * Completes (as a failure) open host checks whose run reached a terminal status
53
+ * at least ORPHANED_CHECK_GRACE_MS and at most ORPHANED_CHECK_MAX_AGE_MS
54
+ * ago. Newest-finished first, so old checks the host keeps refusing cannot
55
+ * starve newer ones. A check the host still refuses after the max age (the
56
+ * check was deleted, the App was uninstalled, ...) is left as it is rather
57
+ * than retried forever; its Re-run button still works. Never queries when
58
+ * no host is configured. `closeOpenHostCheck` is best-effort and never throws.
59
+ */
60
+ export declare function closeOrphanedHostChecks(db: Pick<PrismaClient, "runHostCheck" | "run">, hosts: ReviewHostRegistry | undefined, now?: Date): Promise<void>;
61
+ /**
62
+ * Completes open mention status comments on the same terms as
63
+ * closeOrphanedHostChecks: the run ended between ORPHANED_CHECK_GRACE_MS and
64
+ * ORPHANED_CHECK_MAX_AGE_MS ago, newest first, at most ORPHANED_CHECK_BATCH per
65
+ * pass. `completeHostStatus` is best-effort and never throws.
66
+ */
67
+ export declare function closeOrphanedHostStatuses(db: Pick<PrismaClient, "runHostStatus" | "run">, hosts: ReviewHostRegistry | undefined, now?: Date): Promise<void>;
34
68
  /** Runs one reconciliation pass. Returns the number of runs marked `lost`. */
35
- export declare function reconcileOnce(db: ReconcilerDb, now?: Date, heartbeatTimeoutMs?: number, executor?: Executor): Promise<number>;
69
+ export declare function reconcileOnce(db: ReconcilerDb, now?: Date, heartbeatTimeoutMs?: number, executor?: Executor, reviewHosts?: ReviewHostRegistry): Promise<number>;
36
70
  export interface ReconcilerOptions {
37
71
  db?: ReconcilerDb;
38
72
  intervalMs?: number;
39
73
  heartbeatTimeoutMs?: number;
40
74
  executor?: Executor;
75
+ /** Hosts used to complete checks orphaned by runs that ended abnormally; none configured = no sweep. */
76
+ reviewHosts?: ReviewHostRegistry;
41
77
  }
42
78
  export interface ReconcilerHandle {
43
79
  stop(): void;
@@ -27,13 +27,95 @@
27
27
  * UPDATE commits first flips the row out of the matched status, so the
28
28
  * second one's WHERE simply matches zero rows — double-reconcile is a
29
29
  * no-op, not a race.
30
+ *
31
+ * Each pass also completes review-host checks left "in progress" by a run
32
+ * that ended without reaching `executeRun`'s own finalizer (reaped here as
33
+ * `lost`, failed to start, failed while loading, ...). One sweep covers all
34
+ * of those paths instead of patching each. Mention status comments get the
35
+ * same sweep: it also catches a run that ended before its comment was posted.
30
36
  */
31
37
  import { HEARTBEAT_TIMEOUT_MS, RECONCILE_INTERVAL_MS } from "./timing.js";
32
38
  import { prisma as defaultDb } from "./db.js";
33
39
  import { logger } from "./logger.js";
40
+ import { closeOpenHostCheck } from "./review-host-checks.js";
41
+ import { completeHostStatus } from "./host-status.js";
34
42
  const reconcilerLog = logger.child({ module: "reconciler" });
43
+ /**
44
+ * How long after a run finishes before its still-open check counts as
45
+ * orphaned: long enough that the sweep never races `executeRun`'s own
46
+ * `closeOpenHostCheck` for a run that just ended.
47
+ */
48
+ export const ORPHANED_CHECK_GRACE_MS = 60_000;
49
+ /** Upper bound on checks completed per pass, so a backlog drains over several passes. */
50
+ export const ORPHANED_CHECK_BATCH = 50;
51
+ /** How long after a run finishes the sweep keeps trying to complete its check. */
52
+ export const ORPHANED_CHECK_MAX_AGE_MS = 24 * 60 * 60 * 1000;
53
+ /**
54
+ * Completes (as a failure) open host checks whose run reached a terminal status
55
+ * at least ORPHANED_CHECK_GRACE_MS and at most ORPHANED_CHECK_MAX_AGE_MS
56
+ * ago. Newest-finished first, so old checks the host keeps refusing cannot
57
+ * starve newer ones. A check the host still refuses after the max age (the
58
+ * check was deleted, the App was uninstalled, ...) is left as it is rather
59
+ * than retried forever; its Re-run button still works. Never queries when
60
+ * no host is configured. `closeOpenHostCheck` is best-effort and never throws.
61
+ */
62
+ export async function closeOrphanedHostChecks(db, hosts, now = new Date()) {
63
+ const providers = Object.keys(hosts ?? {});
64
+ if (!hosts || providers.length === 0)
65
+ return;
66
+ const orphans = await db.runHostCheck.findMany({
67
+ where: {
68
+ completedAt: null,
69
+ // A check for a provider that isn't configured could never be completed; keep it out of the batch.
70
+ provider: { in: providers },
71
+ run: {
72
+ status: { notIn: ["pending", "running"] },
73
+ finishedAt: {
74
+ lte: new Date(now.getTime() - ORPHANED_CHECK_GRACE_MS),
75
+ gte: new Date(now.getTime() - ORPHANED_CHECK_MAX_AGE_MS),
76
+ },
77
+ },
78
+ },
79
+ select: { run: { select: { id: true, status: true } } },
80
+ orderBy: { run: { finishedAt: "desc" } },
81
+ take: ORPHANED_CHECK_BATCH,
82
+ });
83
+ for (const { run } of orphans)
84
+ await closeOpenHostCheck(db, run, hosts);
85
+ }
86
+ /**
87
+ * Completes open mention status comments on the same terms as
88
+ * closeOrphanedHostChecks: the run ended between ORPHANED_CHECK_GRACE_MS and
89
+ * ORPHANED_CHECK_MAX_AGE_MS ago, newest first, at most ORPHANED_CHECK_BATCH per
90
+ * pass. `completeHostStatus` is best-effort and never throws.
91
+ */
92
+ export async function closeOrphanedHostStatuses(db, hosts, now = new Date()) {
93
+ const providers = Object.keys(hosts ?? {});
94
+ if (!hosts || providers.length === 0)
95
+ return;
96
+ const orphans = await db.runHostStatus.findMany({
97
+ where: {
98
+ completedAt: null,
99
+ provider: { in: providers },
100
+ run: {
101
+ status: { notIn: ["pending", "running"] },
102
+ finishedAt: {
103
+ lte: new Date(now.getTime() - ORPHANED_CHECK_GRACE_MS),
104
+ gte: new Date(now.getTime() - ORPHANED_CHECK_MAX_AGE_MS),
105
+ },
106
+ },
107
+ },
108
+ select: { run: { select: { id: true, status: true, finalText: true } } },
109
+ orderBy: { run: { finishedAt: "desc" } },
110
+ take: ORPHANED_CHECK_BATCH,
111
+ });
112
+ // postIfMissing: by now the dispatching instance's follow-up has had its
113
+ // chance; a run that ended with no comment (it died first) gets one here.
114
+ for (const { run } of orphans)
115
+ await completeHostStatus(db, run, hosts, { postIfMissing: true });
116
+ }
35
117
  /** Runs one reconciliation pass. Returns the number of runs marked `lost`. */
36
- export async function reconcileOnce(db, now = new Date(), heartbeatTimeoutMs = HEARTBEAT_TIMEOUT_MS, executor) {
118
+ export async function reconcileOnce(db, now = new Date(), heartbeatTimeoutMs = HEARTBEAT_TIMEOUT_MS, executor, reviewHosts) {
37
119
  const cutoff = new Date(now.getTime() - heartbeatTimeoutMs);
38
120
  const stale = {
39
121
  executionManaged: true,
@@ -133,6 +215,8 @@ export async function reconcileOnce(db, now = new Date(), heartbeatTimeoutMs = H
133
215
  });
134
216
  lost += result.count;
135
217
  }
218
+ await closeOrphanedHostChecks(db, reviewHosts, now);
219
+ await closeOrphanedHostStatuses(db, reviewHosts, now);
136
220
  return lost;
137
221
  }
138
222
  export function startReconciler(options = {}) {
@@ -140,7 +224,7 @@ export function startReconciler(options = {}) {
140
224
  const intervalMs = options.intervalMs ?? RECONCILE_INTERVAL_MS;
141
225
  const heartbeatTimeoutMs = options.heartbeatTimeoutMs ?? HEARTBEAT_TIMEOUT_MS;
142
226
  const timer = setInterval(() => {
143
- reconcileOnce(db, new Date(), heartbeatTimeoutMs, options.executor).catch((err) => {
227
+ reconcileOnce(db, new Date(), heartbeatTimeoutMs, options.executor, options.reviewHosts).catch((err) => {
144
228
  reconcilerLog.error({ err }, "reconcile pass failed");
145
229
  });
146
230
  }, intervalMs);
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Repository access authorization, host-neutral (security findings
3
+ * H5-1/A3/C3-2 and H5-3). An agent may use a repository only when its current
4
+ * owner's verified host identity has enough permission on it, or when the
5
+ * authorization was an explicit, recorded admin approval (or predates
6
+ * enforcement: "grandfathered"). Owner-less (public) agents never hold
7
+ * repository authority: anyone can edit them.
8
+ *
9
+ * One gate serves every point of use (a coding run before its workspace is
10
+ * prepared, each repo_* tool call, each host-event dispatch and mention
11
+ * author) and every set-time check (link_repository, create/update_agent).
12
+ * Host answers are cached per (provider, repository, host user id) for a
13
+ * short TTL, positive and negative alike; set-time checks bypass the cache
14
+ * and refresh it. A host error always denies: fail closed.
15
+ * See docs/private/2026-09-26-repo-access-authorization-spec-and-plan.md.
16
+ */
17
+ import type { PrismaClient } from "#prisma";
18
+ import { type HostPermission, type HostUser, type ReviewHostRegistry } from "../providers/review-host/types.js";
19
+ export type { HostPermission };
20
+ /** How an authorization was granted; stored on the row that grants the authority. */
21
+ export type AuthorizedVia = "host_permission" | "admin" | "grandfathered";
22
+ export declare const AUTHORIZED_VIA: readonly AuthorizedVia[];
23
+ export type RepoAccessDenial = "owner_required" | "not_authorized" | "identity_not_linked" | "insufficient_permission" | "check_failed"
24
+ /** In-flight use only: the host was unreachable or rate-limited, even after one retry. */
25
+ | "check_unavailable";
26
+ export type RepoAccessDecision = {
27
+ ok: true;
28
+ level?: HostPermission;
29
+ } | {
30
+ ok: false;
31
+ reason: RepoAccessDenial;
32
+ level?: HostPermission;
33
+ };
34
+ /** What a use of the repository needs. */
35
+ export type RepoAccessKind = "coding" | "write" | "read" | "mention";
36
+ /**
37
+ * Coding runs and write links push, comment, and publish checks; a mention
38
+ * drives a write-capable agent holding its owner's tools and secrets, so
39
+ * triage is not enough for it either.
40
+ */
41
+ export declare function requiredLevel(kind: RepoAccessKind): HostPermission;
42
+ export declare function atLeast(level: HostPermission, required: HostPermission): boolean;
43
+ export interface AuthorizeUseInput {
44
+ /** The agent's CURRENT owner — never whoever authorized the link. */
45
+ ownerId: string | null;
46
+ provider: string;
47
+ repository: string;
48
+ required: HostPermission;
49
+ /** The stamp on the row granting the authority (null = never authorized). */
50
+ authorizedVia: string | null;
51
+ /**
52
+ * A use by a run already under way (a launched coding run, its final push,
53
+ * a repo_* call): a transient host error (5xx, timeout, rate limit) is
54
+ * retried once, then denied as `check_unavailable`. Starting something new
55
+ * and set-time checks stay strict: any error is `check_failed`, no retry.
56
+ */
57
+ retryTransient?: boolean;
58
+ }
59
+ export interface RepoAccessGate {
60
+ /** Point of use: may this agent (by its owner and stamp) use the repository now? */
61
+ authorizeUse(input: AuthorizeUseInput): Promise<RepoAccessDecision>;
62
+ /** Does this principal's linked identity have `required` on the repository? */
63
+ authorizePrincipal(input: {
64
+ principalId: string;
65
+ provider: string;
66
+ repository: string;
67
+ required: HostPermission;
68
+ /** Bypass (and refresh) the cache: set-time checks. */
69
+ fresh?: boolean;
70
+ }): Promise<RepoAccessDecision>;
71
+ /** Does this host user (e.g. a mention's author) have `required` on the repository? */
72
+ authorizeHostUser(input: {
73
+ provider: string;
74
+ repository: string;
75
+ user: HostUser;
76
+ required: HostPermission;
77
+ fresh?: boolean;
78
+ }): Promise<RepoAccessDecision>;
79
+ }
80
+ export interface RepoAccessGateOptions {
81
+ db: Pick<PrismaClient, "hostIdentity">;
82
+ hosts: ReviewHostRegistry;
83
+ /** Default 5 minutes. */
84
+ ttlMs?: number;
85
+ /** Default 1000; the oldest entry is evicted first. */
86
+ maxEntries?: number;
87
+ now?: () => number;
88
+ /** Delay before the one retry of a transient error (default 1s). */
89
+ retryDelayMs?: number;
90
+ sleep?: (ms: number) => Promise<void>;
91
+ }
92
+ /** 5xx, rate limits (403/429), and transport failures: worth one retry for a run already under way. */
93
+ export declare function isTransientHostError(err: unknown): boolean;
94
+ export declare const DEFAULT_REPO_ACCESS_TTL_MS: number;
95
+ export declare function createRepoAccessGate(options: RepoAccessGateOptions): RepoAccessGate;
96
+ /** Actionable, non-leaking text for a denial, for tool results and run errors. */
97
+ export declare function describeDenial(decision: Extract<RepoAccessDecision, {
98
+ ok: false;
99
+ }>, repository: string): string;
@@ -0,0 +1,136 @@
1
+ import { HOST_PERMISSION_RANK, ReviewHostError, } from "../providers/review-host/types.js";
2
+ import { logger } from "./logger.js";
3
+ const log = logger.child({ module: "repo-access" });
4
+ export const AUTHORIZED_VIA = ["host_permission", "admin", "grandfathered"];
5
+ /**
6
+ * Coding runs and write links push, comment, and publish checks; a mention
7
+ * drives a write-capable agent holding its owner's tools and secrets, so
8
+ * triage is not enough for it either.
9
+ */
10
+ export function requiredLevel(kind) {
11
+ return kind === "read" ? "read" : "write";
12
+ }
13
+ export function atLeast(level, required) {
14
+ return HOST_PERMISSION_RANK[level] >= HOST_PERMISSION_RANK[required];
15
+ }
16
+ /** 5xx, rate limits (403/429), and transport failures: worth one retry for a run already under way. */
17
+ export function isTransientHostError(err) {
18
+ if (!(err instanceof ReviewHostError) || err.code !== "host_api_error")
19
+ return false;
20
+ return err.message === "github_api_unavailable" || /^github_api_error:(5\d\d|429|403)(:|$)/.test(err.message);
21
+ }
22
+ export const DEFAULT_REPO_ACCESS_TTL_MS = 5 * 60_000;
23
+ export function createRepoAccessGate(options) {
24
+ const ttlMs = options.ttlMs ?? DEFAULT_REPO_ACCESS_TTL_MS;
25
+ const maxEntries = options.maxEntries ?? 1000;
26
+ const now = options.now ?? (() => Date.now());
27
+ const retryDelayMs = options.retryDelayMs ?? 1000;
28
+ const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
29
+ const cache = new Map();
30
+ /** The user's level, or why it could not be determined (host missing or failing). */
31
+ async function levelOf(provider, repository, user, fresh, retryTransient) {
32
+ const key = `${provider}\u0000${repository}\u0000${user.id}`;
33
+ const hit = cache.get(key);
34
+ if (!fresh && hit && hit.expiresAt > now())
35
+ return { ok: true, level: hit.level, login: user.login };
36
+ const host = options.hosts[provider];
37
+ if (!host) {
38
+ log.warn({ provider, repository }, "no review host configured for a repository access check");
39
+ return { ok: false, transient: false };
40
+ }
41
+ let answer;
42
+ for (let attempt = 0; !answer; attempt++) {
43
+ if (attempt > 0)
44
+ await sleep(retryDelayMs);
45
+ try {
46
+ answer = await host.repositoryPermission(repository, user);
47
+ }
48
+ catch (err) {
49
+ const transient = isTransientHostError(err);
50
+ log.warn({ err, provider, repository, hostUserId: user.id, transient, attempt }, "repository access check failed");
51
+ // Strict: no retry. In flight: one retry of a transient error, then check_unavailable.
52
+ if (!transient || !retryTransient || attempt > 0)
53
+ return { ok: false, transient: transient && retryTransient };
54
+ }
55
+ }
56
+ cache.delete(key);
57
+ cache.set(key, { level: answer.level, expiresAt: now() + ttlMs });
58
+ while (cache.size > maxEntries)
59
+ cache.delete(cache.keys().next().value);
60
+ return { ok: true, ...answer };
61
+ }
62
+ const unavailable = (answer) => ({
63
+ ok: false,
64
+ reason: answer.transient ? "check_unavailable" : "check_failed",
65
+ });
66
+ function decide(level, required) {
67
+ return atLeast(level, required) ? { ok: true, level } : { ok: false, reason: "insufficient_permission", level };
68
+ }
69
+ async function authorizePrincipal(input) {
70
+ let identity;
71
+ try {
72
+ identity = await options.db.hostIdentity.findUnique({
73
+ where: { principalId_provider: { principalId: input.principalId, provider: input.provider } },
74
+ });
75
+ }
76
+ catch (err) {
77
+ log.warn({ err, provider: input.provider }, "could not read a host identity; denying");
78
+ return { ok: false, reason: "check_failed" };
79
+ }
80
+ if (!identity)
81
+ return { ok: false, reason: "identity_not_linked" };
82
+ const answer = await levelOf(input.provider, input.repository, { id: identity.hostUserId, login: identity.login }, input.fresh === true, input.retryTransient === true);
83
+ if (!answer.ok)
84
+ return unavailable(answer);
85
+ if (answer.login !== identity.login) {
86
+ await options.db.hostIdentity
87
+ .updateMany({
88
+ where: { principalId: input.principalId, provider: input.provider, hostUserId: identity.hostUserId },
89
+ data: { login: answer.login },
90
+ })
91
+ .catch((err) => log.warn({ err }, "could not record a renamed host login"));
92
+ }
93
+ return decide(answer.level, input.required);
94
+ }
95
+ return {
96
+ async authorizeUse(input) {
97
+ if (!input.ownerId)
98
+ return { ok: false, reason: "owner_required" };
99
+ if (input.authorizedVia === "admin" || input.authorizedVia === "grandfathered")
100
+ return { ok: true };
101
+ if (input.authorizedVia !== "host_permission")
102
+ return { ok: false, reason: "not_authorized" };
103
+ return authorizePrincipal({
104
+ principalId: input.ownerId,
105
+ provider: input.provider,
106
+ repository: input.repository,
107
+ required: input.required,
108
+ retryTransient: input.retryTransient,
109
+ });
110
+ },
111
+ authorizePrincipal,
112
+ async authorizeHostUser(input) {
113
+ const answer = await levelOf(input.provider, input.repository, input.user, input.fresh === true, false);
114
+ if (!answer.ok)
115
+ return unavailable(answer);
116
+ return decide(answer.level, input.required);
117
+ },
118
+ };
119
+ }
120
+ /** Actionable, non-leaking text for a denial, for tool results and run errors. */
121
+ export function describeDenial(decision, repository) {
122
+ switch (decision.reason) {
123
+ case "owner_required":
124
+ return `Agents without an owner cannot use repositories; an admin can assign one with make_owner.`;
125
+ case "not_authorized":
126
+ return `This agent's access to ${repository} was never authorized; re-link it (link_repository) or update the coding profile.`;
127
+ case "identity_not_linked":
128
+ return `The agent owner has not linked a GitHub account; run link_host_account first.`;
129
+ case "insufficient_permission":
130
+ return `The agent owner's GitHub account has ${decision.level ?? "no"} access to ${repository}, which is not enough.`;
131
+ case "check_failed":
132
+ return `Could not verify access to ${repository} right now; try again later.`;
133
+ case "check_unavailable":
134
+ return `GitHub could not be reached (or rate-limited wardby) while re-checking access to ${repository}, so the use was refused for safety. Try again later.`;
135
+ }
136
+ }
@@ -0,0 +1,11 @@
1
+ import type { PrismaClient, Run } from "#prisma";
2
+ import type { ReviewHostRegistry } from "../providers/review-host/types.js";
3
+ /**
4
+ * A check the control plane started for a run must never stay "in progress":
5
+ * when the run reaches any terminal state without the review tool having
6
+ * completed it, complete it as a failure. Not neutral: branch protection
7
+ * counts a neutral required check as passing, which would let a pull request
8
+ * merge with no review. Best effort — a failure is logged, not retried; the
9
+ * check's Re-run button still works. Never throws.
10
+ */
11
+ export declare function closeOpenHostCheck(db: Pick<PrismaClient, "runHostCheck" | "run">, run: Pick<Run, "id" | "status">, hosts: ReviewHostRegistry | undefined): Promise<void>;
@@ -0,0 +1,41 @@
1
+ import { loadBudgetSentence } from "./budget-wording.js";
2
+ import { logger } from "./logger.js";
3
+ const log = logger.child({ module: "review-host-checks" });
4
+ /**
5
+ * A check the control plane started for a run must never stay "in progress":
6
+ * when the run reaches any terminal state without the review tool having
7
+ * completed it, complete it as a failure. Not neutral: branch protection
8
+ * counts a neutral required check as passing, which would let a pull request
9
+ * merge with no review. Best effort — a failure is logged, not retried; the
10
+ * check's Re-run button still works. Never throws.
11
+ */
12
+ export async function closeOpenHostCheck(db, run, hosts) {
13
+ if (!hosts)
14
+ return;
15
+ try {
16
+ const check = await db.runHostCheck.findUnique({ where: { runId: run.id } });
17
+ if (!check || check.completedAt)
18
+ return;
19
+ const host = hosts[check.provider];
20
+ if (!host)
21
+ return;
22
+ const outOfBudget = run.status === "refused" || run.status === "budget_exhausted";
23
+ await host.completeCheck(check.repository, {
24
+ checkId: check.checkId,
25
+ conclusion: "failure",
26
+ ...(outOfBudget
27
+ ? {
28
+ title: "Review could not run: out of budget",
29
+ summary: `${await loadBudgetSentence(db, run.id, run.status)} Use Re-run after raising the budget or when it resets.`,
30
+ }
31
+ : {
32
+ title: "Review did not complete",
33
+ summary: `wardby run ${run.id} ended with status "${run.status}" before publishing a review. Use Re-run to try again.`,
34
+ }),
35
+ });
36
+ await db.runHostCheck.update({ where: { runId: run.id }, data: { completedAt: new Date() } });
37
+ }
38
+ catch (err) {
39
+ log.warn({ err, runId: run.id }, "could not complete the run's host check");
40
+ }
41
+ }
@@ -0,0 +1,47 @@
1
+ import type { LoadedTool } from "../providers/engine/types.js";
2
+ import { type ReviewHostProvider, type ReviewHostRegistry } from "../providers/review-host/types.js";
3
+ import { type RepoAccessDecision } from "./repo-access.js";
4
+ export interface RepositoryLink {
5
+ provider: ReviewHostProvider;
6
+ repository: string;
7
+ access: "read" | "write";
8
+ checkName: string | null;
9
+ }
10
+ export interface RunHostCheckRef {
11
+ provider: string;
12
+ repository: string;
13
+ checkId: string;
14
+ /** The head the check was started on; the check is only the review's if the heads match. */
15
+ headSha: string;
16
+ /**
17
+ * The pull request the run was dispatched for. A check (the run's own, or a
18
+ * new one under the link's checkName) is only ever published for this PR;
19
+ * null (a row from before it was recorded) binds to no PR.
20
+ */
21
+ prNumber: number | null;
22
+ }
23
+ export interface ReviewToolContext {
24
+ agentId: string;
25
+ links: readonly RepositoryLink[];
26
+ hosts: ReviewHostRegistry;
27
+ /** The run's open check, if the control plane started one. */
28
+ runCheck: RunHostCheckRef | null;
29
+ /** Records that the run's check is now completed (RunHostCheck.completedAt). */
30
+ markRunCheckCompleted: () => Promise<void>;
31
+ /**
32
+ * Re-authorizes the link against its current stamp and the agent's current
33
+ * owner (core/repo-access.ts). Called before every host call; a denial or a
34
+ * throw refuses the call.
35
+ */
36
+ authorize: (link: RepositoryLink) => Promise<RepoAccessDecision>;
37
+ }
38
+ export declare const REVIEW_HOST_TOOL_DEFS: LoadedTool[];
39
+ export declare const REVIEW_HOST_TOOL_NAMES: ReadonlySet<string>;
40
+ export declare function resolveLink(links: readonly RepositoryLink[], repositoryArg: string): RepositoryLink | "ambiguous" | null;
41
+ /**
42
+ * Dispatches one built-in repo tool call. Never throws — mirrors
43
+ * runner.ts's `runSandboxTool` contract: a failure becomes a JSON error
44
+ * result fed back to the model as the tool's result, not an engine-halting
45
+ * exception.
46
+ */
47
+ export declare function handleReviewHostTool(name: string, argsJson: string, ctx: ReviewToolContext): Promise<string>;