@wardby/cli 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (407) hide show
  1. package/.env.example +34 -4
  2. package/README.md +41 -4
  3. package/dist/claude-coding-worker/driver.d.ts +6 -1
  4. package/dist/claude-coding-worker/driver.js +27 -1
  5. package/dist/claude-coding-worker/main.js +2 -0
  6. package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
  7. package/dist/claude-coding-worker/tool-socket.js +26 -0
  8. package/dist/cli-help.d.ts +1 -1
  9. package/dist/cli-help.js +13 -4
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +219 -29
  12. package/dist/coding/collect-exclude.d.ts +25 -0
  13. package/dist/coding/collect-exclude.js +76 -0
  14. package/dist/coding/profile.d.ts +67 -22
  15. package/dist/coding/profile.js +58 -25
  16. package/dist/coding/protected-path-wording.d.ts +24 -0
  17. package/dist/coding/protected-path-wording.js +55 -0
  18. package/dist/coding/protected-paths.d.ts +31 -0
  19. package/dist/coding/protected-paths.js +48 -0
  20. package/dist/coding/protocol.d.ts +75 -4
  21. package/dist/coding/protocol.js +107 -7
  22. package/dist/coding/registry/adapters.d.ts +2 -0
  23. package/dist/coding/registry/adapters.js +9 -0
  24. package/dist/coding/registry/allowlist.d.ts +9 -0
  25. package/dist/coding/registry/allowlist.js +28 -0
  26. package/dist/coding/registry/json-scan.d.ts +55 -0
  27. package/dist/coding/registry/json-scan.js +191 -0
  28. package/dist/coding/registry/lockfiles.d.ts +6 -0
  29. package/dist/coding/registry/lockfiles.js +89 -0
  30. package/dist/coding/registry/npm-lockfile.d.ts +9 -0
  31. package/dist/coding/registry/npm-lockfile.js +73 -0
  32. package/dist/coding/registry/npm-plan.d.ts +29 -0
  33. package/dist/coding/registry/npm-plan.js +289 -0
  34. package/dist/coding/registry/npm.d.ts +7 -0
  35. package/dist/coding/registry/npm.js +252 -0
  36. package/dist/coding/registry/pypi.d.ts +4 -0
  37. package/dist/coding/registry/pypi.js +210 -0
  38. package/dist/coding/registry/report.d.ts +37 -0
  39. package/dist/coding/registry/report.js +31 -0
  40. package/dist/coding/registry/token.d.ts +4 -0
  41. package/dist/coding/registry/token.js +7 -0
  42. package/dist/coding/registry/types.d.ts +286 -0
  43. package/dist/coding/registry/types.js +19 -0
  44. package/dist/coding/registry/worker-config.d.ts +14 -0
  45. package/dist/coding/registry/worker-config.js +31 -0
  46. package/dist/coding/services/builtins.d.ts +22 -0
  47. package/dist/coding/services/builtins.js +85 -0
  48. package/dist/coding/services/catalog.d.ts +443 -0
  49. package/dist/coding/services/catalog.js +159 -0
  50. package/dist/coding/services/declaration.d.ts +11 -0
  51. package/dist/coding/services/declaration.js +114 -0
  52. package/dist/coding/services/note.d.ts +8 -0
  53. package/dist/coding/services/note.js +15 -0
  54. package/dist/coding/services/resolve.d.ts +25 -0
  55. package/dist/coding/services/resolve.js +50 -0
  56. package/dist/coding/services/wording.d.ts +25 -0
  57. package/dist/coding/services/wording.js +70 -0
  58. package/dist/coding-proxy/main.js +24 -4
  59. package/dist/coding-worker/artifact.d.ts +6 -0
  60. package/dist/coding-worker/debug-trace.d.ts +37 -0
  61. package/dist/coding-worker/debug-trace.js +117 -0
  62. package/dist/coding-worker/driver.d.ts +13 -1
  63. package/dist/coding-worker/driver.js +102 -13
  64. package/dist/coding-worker/errors.js +8 -0
  65. package/dist/coding-worker/main.js +10 -2
  66. package/dist/coding-worker/sdk.d.ts +2 -2
  67. package/dist/coding-worker/sdk.js +7 -2
  68. package/dist/coding-worker/types.d.ts +3 -0
  69. package/dist/config/providers.d.ts +41 -0
  70. package/dist/config/providers.js +66 -0
  71. package/dist/core/budget-groups.d.ts +108 -5
  72. package/dist/core/budget-groups.js +125 -19
  73. package/dist/core/budget-wording.d.ts +22 -0
  74. package/dist/core/budget-wording.js +55 -0
  75. package/dist/core/datastores.js +18 -2
  76. package/dist/core/db.d.ts +5 -1
  77. package/dist/core/db.js +8 -2
  78. package/dist/core/dispatch.d.ts +33 -6
  79. package/dist/core/dispatch.js +260 -43
  80. package/dist/core/engine-native.js +27 -10
  81. package/dist/core/grants.d.ts +86 -0
  82. package/dist/core/grants.js +126 -0
  83. package/dist/core/host-events.d.ts +49 -0
  84. package/dist/core/host-events.js +257 -0
  85. package/dist/core/host-identity-links.d.ts +54 -0
  86. package/dist/core/host-identity-links.js +189 -0
  87. package/dist/core/host-status.d.ts +61 -0
  88. package/dist/core/host-status.js +211 -0
  89. package/dist/core/in-flight-runs.d.ts +8 -0
  90. package/dist/core/in-flight-runs.js +56 -0
  91. package/dist/core/provider-wording.d.ts +12 -0
  92. package/dist/core/provider-wording.js +44 -0
  93. package/dist/core/reconciler.d.ts +38 -2
  94. package/dist/core/reconciler.js +86 -2
  95. package/dist/core/repo-access.d.ts +99 -0
  96. package/dist/core/repo-access.js +136 -0
  97. package/dist/core/review-host-checks.d.ts +11 -0
  98. package/dist/core/review-host-checks.js +41 -0
  99. package/dist/core/review-host-tools.d.ts +47 -0
  100. package/dist/core/review-host-tools.js +347 -0
  101. package/dist/core/run-heartbeat.d.ts +27 -0
  102. package/dist/core/run-heartbeat.js +54 -0
  103. package/dist/core/runner.d.ts +30 -8
  104. package/dist/core/runner.js +237 -40
  105. package/dist/core/secrets.d.ts +11 -2
  106. package/dist/core/secrets.js +25 -6
  107. package/dist/core/subagent-memory-tools.d.ts +1 -1
  108. package/dist/core/subagent-memory-tools.js +16 -3
  109. package/dist/core/tool-admin.d.ts +81 -0
  110. package/dist/core/tool-admin.js +129 -0
  111. package/dist/core/tool-names.d.ts +42 -0
  112. package/dist/core/tool-names.js +64 -0
  113. package/dist/core/untrusted-content.d.ts +32 -0
  114. package/dist/core/untrusted-content.js +72 -0
  115. package/dist/core/webhooks.d.ts +8 -1
  116. package/dist/core/webhooks.js +23 -2
  117. package/dist/generated/prisma/browser.d.ts +100 -0
  118. package/dist/generated/prisma/client.d.ts +100 -0
  119. package/dist/generated/prisma/commonInputTypes.d.ts +30 -0
  120. package/dist/generated/prisma/enums.d.ts +6 -0
  121. package/dist/generated/prisma/enums.js +6 -1
  122. package/dist/generated/prisma/internal/class.d.ts +143 -0
  123. package/dist/generated/prisma/internal/class.js +4 -4
  124. package/dist/generated/prisma/internal/prismaNamespace.d.ts +1746 -577
  125. package/dist/generated/prisma/internal/prismaNamespace.js +179 -6
  126. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +186 -0
  127. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +179 -6
  128. package/dist/generated/prisma/models/Agent.d.ts +294 -1
  129. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  130. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  131. package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
  132. package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
  133. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
  134. package/dist/generated/prisma/models/CodingProxySession.d.ts +123 -2
  135. package/dist/generated/prisma/models/CodingRun.d.ts +1276 -95
  136. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  137. package/dist/generated/prisma/models/CodingService.js +1 -0
  138. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  139. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  140. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  141. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  142. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  143. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  144. package/dist/generated/prisma/models/Principal.d.ts +455 -0
  145. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  146. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  147. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  148. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  149. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  150. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  151. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  152. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  153. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  154. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  155. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  156. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  157. package/dist/generated/prisma/models/Run.d.ts +389 -1
  158. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  159. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  160. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  161. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  162. package/dist/generated/prisma/models/Tool.d.ts +15 -3
  163. package/dist/generated/prisma/models.d.ts +13 -0
  164. package/dist/help/build.d.ts +1 -0
  165. package/dist/help/build.js +9 -0
  166. package/dist/help/catalog.d.ts +24 -0
  167. package/dist/help/catalog.js +160 -0
  168. package/dist/help/cli.d.ts +2 -0
  169. package/dist/help/cli.js +65 -0
  170. package/dist/help/runtime.d.ts +3 -0
  171. package/dist/help/runtime.js +44 -0
  172. package/dist/help/search.d.ts +9 -0
  173. package/dist/help/search.js +104 -0
  174. package/dist/help-index.json +660 -0
  175. package/dist/import/cli-args.js +3 -2
  176. package/dist/import/create.d.ts +5 -0
  177. package/dist/import/create.js +67 -13
  178. package/dist/import/index.js +19 -9
  179. package/dist/import/neutral-schema.d.ts +11 -11
  180. package/dist/mcp/auth/access.d.ts +70 -0
  181. package/dist/mcp/auth/access.js +58 -0
  182. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  183. package/dist/mcp/auth/grants-cli.js +518 -0
  184. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  185. package/dist/mcp/auth/host-account-cli.js +47 -0
  186. package/dist/mcp/auth/ownership.d.ts +26 -52
  187. package/dist/mcp/auth/ownership.js +19 -14
  188. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  189. package/dist/mcp/auth/repo-authorization.js +51 -0
  190. package/dist/mcp/auth/resource-server.d.ts +31 -2
  191. package/dist/mcp/auth/resource-server.js +84 -3
  192. package/dist/mcp/auth/self-hosted/browser.js +2 -2
  193. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  194. package/dist/mcp/auth/self-hosted/credentials.d.ts +20 -2
  195. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  196. package/dist/mcp/auth/self-hosted/session.d.ts +2 -1
  197. package/dist/mcp/context.d.ts +27 -1
  198. package/dist/mcp/errors.d.ts +25 -6
  199. package/dist/mcp/errors.js +98 -0
  200. package/dist/mcp/host-events/github-ingress.d.ts +36 -0
  201. package/dist/mcp/host-events/github-ingress.js +92 -0
  202. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  203. package/dist/mcp/host-events/github-user-callback.js +50 -0
  204. package/dist/mcp/index.d.ts +8 -1
  205. package/dist/mcp/index.js +106 -16
  206. package/dist/mcp/server.js +27 -9
  207. package/dist/mcp/tools/agents.js +446 -48
  208. package/dist/mcp/tools/budget-groups.js +3 -3
  209. package/dist/mcp/tools/datastore.js +22 -15
  210. package/dist/mcp/tools/grants.d.ts +2 -0
  211. package/dist/mcp/tools/grants.js +239 -0
  212. package/dist/mcp/tools/help.d.ts +5 -0
  213. package/dist/mcp/tools/help.js +67 -0
  214. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  215. package/dist/mcp/tools/host-accounts.js +114 -0
  216. package/dist/mcp/tools/memory.d.ts +7 -1
  217. package/dist/mcp/tools/memory.js +5 -5
  218. package/dist/mcp/tools/repositories.d.ts +2 -0
  219. package/dist/mcp/tools/repositories.js +181 -0
  220. package/dist/mcp/tools/runs.d.ts +6 -0
  221. package/dist/mcp/tools/runs.js +60 -6
  222. package/dist/mcp/tools/scheduling.js +3 -6
  223. package/dist/mcp/tools/secrets.js +18 -6
  224. package/dist/mcp/tools/services.d.ts +2 -0
  225. package/dist/mcp/tools/services.js +222 -0
  226. package/dist/mcp/tools/subagents.js +52 -16
  227. package/dist/mcp/tools/tools.d.ts +41 -0
  228. package/dist/mcp/tools/tools.js +201 -40
  229. package/dist/mcp/tools/trigger.js +36 -6
  230. package/dist/mcp/tools/webhooks.js +15 -4
  231. package/dist/mcp/transport/streamable-http.d.ts +10 -0
  232. package/dist/mcp/transport/streamable-http.js +32 -2
  233. package/dist/providers/auth/delegating.d.ts +19 -0
  234. package/dist/providers/auth/delegating.js +71 -0
  235. package/dist/providers/auth/index.d.ts +2 -0
  236. package/dist/providers/auth/index.js +11 -0
  237. package/dist/providers/auth/self-hosted.d.ts +2 -1
  238. package/dist/providers/auth/self-hosted.js +16 -4
  239. package/dist/providers/auth/types.d.ts +6 -0
  240. package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
  241. package/dist/providers/coding-proxy/memory-ledger.js +21 -2
  242. package/dist/providers/coding-proxy/metering.d.ts +4 -0
  243. package/dist/providers/coding-proxy/metering.js +28 -2
  244. package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
  245. package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
  246. package/dist/providers/coding-proxy/proxy.d.ts +13 -0
  247. package/dist/providers/coding-proxy/proxy.js +479 -18
  248. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  249. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  250. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  251. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  252. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  253. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  254. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  255. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  256. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  257. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  258. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  259. package/dist/providers/coding-proxy/registry/store.js +70 -0
  260. package/dist/providers/coding-proxy/runtime.d.ts +17 -0
  261. package/dist/providers/coding-proxy/runtime.js +63 -0
  262. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  263. package/dist/providers/coding-proxy/server.d.ts +11 -0
  264. package/dist/providers/coding-proxy/server.js +163 -0
  265. package/dist/providers/coding-proxy/types.d.ts +15 -0
  266. package/dist/providers/engine/types.d.ts +10 -0
  267. package/dist/providers/executor/build.d.ts +2 -2
  268. package/dist/providers/executor/composition.d.ts +3 -0
  269. package/dist/providers/executor/composition.js +19 -0
  270. package/dist/providers/executor/container.d.ts +105 -3
  271. package/dist/providers/executor/container.js +296 -20
  272. package/dist/providers/executor/dbos.d.ts +2 -3
  273. package/dist/providers/executor/in-process.d.ts +2 -3
  274. package/dist/providers/executor/routing.d.ts +7 -0
  275. package/dist/providers/executor/routing.js +13 -0
  276. package/dist/providers/executor/types.d.ts +22 -0
  277. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  278. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  279. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  280. package/dist/providers/jobs/collect-prune.js +27 -0
  281. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  282. package/dist/providers/jobs/docker-isolation.js +213 -22
  283. package/dist/providers/jobs/docker-services.d.ts +35 -0
  284. package/dist/providers/jobs/docker-services.js +191 -0
  285. package/dist/providers/jobs/docker.d.ts +49 -2
  286. package/dist/providers/jobs/docker.js +279 -32
  287. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  288. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  289. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  290. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  291. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  292. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  293. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  294. package/dist/providers/jobs/kubernetes.d.ts +21 -2
  295. package/dist/providers/jobs/kubernetes.js +112 -21
  296. package/dist/providers/jobs/types.d.ts +18 -0
  297. package/dist/providers/llm/anthropic.js +6 -2
  298. package/dist/providers/llm/bedrock.js +2 -1
  299. package/dist/providers/llm/claude-messages.d.ts +5 -1
  300. package/dist/providers/llm/claude-messages.js +1 -0
  301. package/dist/providers/llm/claude-provider.d.ts +3 -1
  302. package/dist/providers/llm/claude-provider.js +5 -1
  303. package/dist/providers/llm/index.d.ts +1 -1
  304. package/dist/providers/llm/index.js +1 -1
  305. package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
  306. package/dist/providers/llm/pricing-anthropic.js +32 -14
  307. package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
  308. package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
  309. package/dist/providers/llm/routing.d.ts +10 -1
  310. package/dist/providers/llm/routing.js +18 -0
  311. package/dist/providers/llm/types.d.ts +12 -0
  312. package/dist/providers/llm/types.js +8 -1
  313. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  314. package/dist/providers/review-host/diff-lines.js +59 -0
  315. package/dist/providers/review-host/github-events.d.ts +6 -0
  316. package/dist/providers/review-host/github-events.js +180 -0
  317. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  318. package/dist/providers/review-host/github-user-auth.js +128 -0
  319. package/dist/providers/review-host/github.d.ts +57 -0
  320. package/dist/providers/review-host/github.js +567 -0
  321. package/dist/providers/review-host/index.d.ts +12 -0
  322. package/dist/providers/review-host/index.js +30 -0
  323. package/dist/providers/review-host/review-format.d.ts +19 -0
  324. package/dist/providers/review-host/review-format.js +59 -0
  325. package/dist/providers/review-host/types.d.ts +284 -0
  326. package/dist/providers/review-host/types.js +24 -0
  327. package/dist/providers/vcs/git.d.ts +16 -6
  328. package/dist/providers/vcs/git.js +77 -13
  329. package/dist/providers/vcs/github.d.ts +74 -3
  330. package/dist/providers/vcs/github.js +195 -15
  331. package/dist/providers/vcs/types.d.ts +42 -4
  332. package/dist/quickstart/index.js +13 -2
  333. package/dist/sandbox/fetch-policy.d.ts +20 -2
  334. package/dist/sandbox/fetch-policy.js +64 -3
  335. package/dist/sandbox/host-functions.d.ts +9 -1
  336. package/dist/sandbox/host-functions.js +12 -5
  337. package/dist/serve.js +1 -1
  338. package/dist/wardby-bin.js +6 -0
  339. package/docs/README.md +30 -0
  340. package/docs/architecture-runtime.md +90 -0
  341. package/docs/assets/brand/wardby-icon-512.png +0 -0
  342. package/docs/assets/brand/wardby-icon.svg +16 -0
  343. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  344. package/docs/assets/brand/wardby-mascot.png +0 -0
  345. package/docs/assets/brand/wardby-mascot.svg +5 -0
  346. package/docs/assets/wardby-workflow.svg +106 -0
  347. package/docs/code-review-agents.md +481 -0
  348. package/docs/coding-agent-setup.md +172 -0
  349. package/docs/coding-packages.md +455 -0
  350. package/docs/coding-services.md +300 -0
  351. package/docs/coding-worker-byo-images.md +98 -0
  352. package/docs/coding-worker-isolation.md +1061 -0
  353. package/docs/getting-started-gke.md +615 -0
  354. package/docs/getting-started-identity-provider.md +308 -0
  355. package/docs/getting-started.md +133 -0
  356. package/docs/observability.md +53 -0
  357. package/docs/release-verification.md +66 -0
  358. package/docs/security-deployment.md +657 -0
  359. package/help/code-review-agents.md +31 -0
  360. package/help/coding-packages.md +31 -0
  361. package/help/coding-services.md +71 -0
  362. package/help/creating-agents.md +68 -0
  363. package/help/deploy-gke.md +39 -0
  364. package/help/deployment-targets.md +39 -0
  365. package/help/errors/budget-group-exhausted.md +25 -0
  366. package/help/errors/docker-isolation-unsupported.md +26 -0
  367. package/help/errors/protected-path.md +45 -0
  368. package/help/errors/repo-access.md +27 -0
  369. package/help/errors/service-declaration-invalid.md +27 -0
  370. package/help/errors/service-declaration-unavailable.md +25 -0
  371. package/help/errors/service-launcher-unsupported.md +30 -0
  372. package/help/errors/service-not-allowed.md +27 -0
  373. package/help/errors/service-unknown.md +23 -0
  374. package/help/errors/service-unready.md +49 -0
  375. package/help/getting-started.md +31 -0
  376. package/help/github.md +30 -0
  377. package/help/identity-and-access.md +39 -0
  378. package/help/mcp.md +30 -0
  379. package/help/native-capabilities.md +30 -0
  380. package/help/observability.md +41 -0
  381. package/help/operating-agents.md +30 -0
  382. package/help/security.md +27 -0
  383. package/help/troubleshooting/budgets.md +26 -0
  384. package/help/troubleshooting/coding-workers.md +47 -0
  385. package/help/troubleshooting/repository-access.md +27 -0
  386. package/package.json +12 -3
  387. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  388. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  389. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  390. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  391. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  392. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  393. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  394. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  395. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  396. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  397. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  398. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  399. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  400. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  401. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  402. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  403. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  404. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  405. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  406. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  407. package/prisma/schema.prisma +407 -21
@@ -0,0 +1,211 @@
1
+ import { storedServiceNames } from "../coding/services/catalog.js";
2
+ import { SERVICE_UNREADY_CATEGORY, serviceRefusalSentence, serviceUnreadySentence, } from "../coding/services/wording.js";
3
+ import { PROTECTED_PATH_CATEGORY, PROTECTED_PATH_HOST_LINE } from "../coding/protected-path-wording.js";
4
+ import { loadBudgetSentence } from "./budget-wording.js";
5
+ import { logger } from "./logger.js";
6
+ import { providerClassOfCategory, providerSentence } from "./provider-wording.js";
7
+ const log = logger.child({ module: "host-status" });
8
+ /** Keeps the edited comment readable; the full reply stays on the run. */
9
+ const MAX_REPLY_CHARS = 2000;
10
+ const TERMINAL = new Set(["succeeded", "failed", "refused", "lost", "budget_exhausted", "cancelled"]);
11
+ const runLine = (runId) => `<sub>wardby run \`${runId}\`</sub>`;
12
+ export function workingBody(runId) {
13
+ return `👀 Working on it.\n\n${runLine(runId)}`;
14
+ }
15
+ /** The status row for a mention run, created in the same transaction as the run. */
16
+ export function mentionStatusRow(provider, event, runId) {
17
+ return {
18
+ runId,
19
+ provider,
20
+ repository: event.repository,
21
+ number: event.number,
22
+ commentKind: event.replyToReviewCommentId ? "inline" : "conversation",
23
+ replyToReviewCommentId: event.replyToReviewCommentId ?? null,
24
+ };
25
+ }
26
+ function pullRequestOutcome(result) {
27
+ if (!result || typeof result !== "object")
28
+ return null;
29
+ const r = result;
30
+ if (r.outcome !== "pull_request_opened" && r.outcome !== "pull_request_updated")
31
+ return null;
32
+ if (typeof r.repository !== "string" || typeof r.pullRequestNumber !== "number")
33
+ return null;
34
+ if (!Number.isInteger(r.pullRequestNumber) || r.pullRequestNumber <= 0)
35
+ return null;
36
+ return { outcome: r.outcome, repository: r.repository, pullRequestNumber: r.pullRequestNumber };
37
+ }
38
+ /** The agent's reply as a quote, cut to MAX_REPLY_CHARS, with @-mentions defused so nobody is pinged. */
39
+ function quoteReply(text) {
40
+ const cut = text.length > MAX_REPLY_CHARS ? `${text.slice(0, MAX_REPLY_CHARS)}…` : text;
41
+ return cut
42
+ .replace(/@(?=[\w-])/g, "@​")
43
+ .split("\n")
44
+ .map((line) => `> ${line}`)
45
+ .join("\n");
46
+ }
47
+ export function outcomeBody(run, repository, pullRequests, failedChildren = [], opts = {}) {
48
+ const links = pullRequests.map((pr) => {
49
+ const ref = pr.repository.toLowerCase() === repository.toLowerCase()
50
+ ? `#${pr.pullRequestNumber}`
51
+ : `${pr.repository}#${pr.pullRequestNumber}`;
52
+ return pr.outcome === "pull_request_opened" ? `Opened ${ref}` : `Pushed changes to ${ref}`;
53
+ });
54
+ const partial = links.length > 0 ? `\n\n${links.join(", ")}.` : "";
55
+ const footer = runLine(run.id);
56
+ if (run.status === "lost") {
57
+ return `❌ Interrupted before it finished (for example, wardby restarted). Repeat your request to retry.${partial}\n\n${footer}`;
58
+ }
59
+ if (run.status === "budget_exhausted" || run.status === "refused") {
60
+ // A refused run could not start for lack of budget; see budgetSentence.
61
+ return `❌ ${opts.budgetSentence ?? "Out of budget."}${partial}\n\n${footer}`;
62
+ }
63
+ if (run.status !== "succeeded") {
64
+ // Only the status: a run's error text can carry internal detail that does not belong on the host.
65
+ return `❌ Stopped: the run ended with status \`${run.status}\`.${partial}\n\n${footer}`;
66
+ }
67
+ const reply = run.finalText?.trim();
68
+ const quoted = reply ? `\n\n${quoteReply(reply)}` : "";
69
+ if (failedChildren.length > 0) {
70
+ // The agent itself finished, but the work it handed off did not.
71
+ const outOfBudget = failedChildren.filter((c) => c.status === "budget_exhausted");
72
+ const providerClassOf = (c) => c.status === "failed" ? providerClassOfCategory(c.failureCategory) : null;
73
+ // A coding sub-run its services stopped: refused at dispatch, or a sidecar never became ready.
74
+ const serviceSentenceOf = (c) => c.status === "refused"
75
+ ? serviceRefusalSentence(c.error)
76
+ : c.status === "failed" && c.failureCategory === SERVICE_UNREADY_CATEGORY
77
+ ? serviceUnreadySentence(c.services ?? [])
78
+ : null;
79
+ // No path reaches this comment (see coding/protected-path-wording.ts), only the category.
80
+ const protectedPathOf = (c) => c.status === "failed" && c.failureCategory === PROTECTED_PATH_CATEGORY;
81
+ const providerClasses = new Set(failedChildren.map(providerClassOf).filter((c) => c !== null));
82
+ const serviceSentences = new Set(failedChildren.map(serviceSentenceOf).filter((s) => s !== null));
83
+ const protectedPathFailed = failedChildren.some(protectedPathOf);
84
+ const other = failedChildren.filter((c) => c.status !== "budget_exhausted" &&
85
+ providerClassOf(c) === null &&
86
+ serviceSentenceOf(c) === null &&
87
+ !protectedPathOf(c));
88
+ const lines = [];
89
+ if (outOfBudget.length > 0) {
90
+ lines.push(`A sub-run ran out of budget: ${outOfBudget.map((c) => `\`${c.id}\``).join(", ")}.`);
91
+ }
92
+ // The class only, never the provider's code: the sentence says who has to act.
93
+ for (const providerClass of providerClasses) {
94
+ lines.push(`A sub-run could not reach the model: ${providerSentence(providerClass)}`);
95
+ }
96
+ // The fixed sentence only, never the Run.error code in front of it.
97
+ for (const sentence of serviceSentences) {
98
+ lines.push(`A sub-run could not start: ${sentence}`);
99
+ }
100
+ if (protectedPathFailed)
101
+ lines.push(PROTECTED_PATH_HOST_LINE);
102
+ if (other.length > 0) {
103
+ lines.push(`A sub-run did not succeed: ${other.map((c) => `\`${c.id}\` (\`${c.status}\`)`).join(", ")}.`);
104
+ }
105
+ return `❌ ${lines.join(" ")}${partial}${quoted}\n\n${footer}`;
106
+ }
107
+ if (links.length > 0)
108
+ return `✅ ${links.join(", ")}.\n\n${footer}`;
109
+ return `✅ Finished without opening a pull request.${quoted}\n\n${footer}`;
110
+ }
111
+ /**
112
+ * Writes a finished run's outcome to its status comment and marks it
113
+ * complete: edits the comment when it exists; when it does not yet, posts the
114
+ * outcome as a new comment only if `postIfMissing` (the reconciler, well after
115
+ * the run ended), and otherwise leaves the row for postMentionStatus, which
116
+ * is about to post it. A failed host call leaves the row open for the
117
+ * reconciler to retry.
118
+ */
119
+ export async function completeHostStatus(db, run, hosts, opts = {}) {
120
+ if (!hosts)
121
+ return;
122
+ try {
123
+ const status = await db.runHostStatus.findUnique({ where: { runId: run.id } });
124
+ if (!status || status.completedAt)
125
+ return;
126
+ if (!status.commentId && !opts.postIfMissing)
127
+ return;
128
+ const host = hosts[status.provider];
129
+ if (!host)
130
+ return;
131
+ const children = await db.run.findMany({
132
+ where: { parentRunId: run.id },
133
+ select: {
134
+ id: true,
135
+ status: true,
136
+ error: true,
137
+ codingRun: { select: { result: true, failureCategory: true, services: true } },
138
+ },
139
+ orderBy: { startedAt: "asc" },
140
+ });
141
+ const pullRequests = children
142
+ .map((c) => pullRequestOutcome(c.codingRun?.result))
143
+ .filter((pr) => pr !== null);
144
+ const failedChildren = children
145
+ .filter((c) => TERMINAL.has(c.status) && c.status !== "succeeded")
146
+ .map((c) => ({
147
+ id: c.id,
148
+ status: c.status,
149
+ failureCategory: c.codingRun?.failureCategory ?? null,
150
+ error: c.error ?? null,
151
+ services: storedServiceNames(c.codingRun?.services),
152
+ }));
153
+ const budgetSentence = run.status === "budget_exhausted" || run.status === "refused"
154
+ ? await loadBudgetSentence(db, run.id, run.status)
155
+ : undefined;
156
+ const body = outcomeBody(run, status.repository, pullRequests, failedChildren, { budgetSentence });
157
+ let commentId = status.commentId;
158
+ if (commentId) {
159
+ await host.editComment(status.repository, {
160
+ kind: status.commentKind === "inline" ? "inline" : "conversation",
161
+ id: commentId,
162
+ body,
163
+ });
164
+ }
165
+ else {
166
+ commentId = (await host.comment(status.repository, {
167
+ number: status.number,
168
+ body,
169
+ ...(status.replyToReviewCommentId ? { replyToReviewCommentId: status.replyToReviewCommentId } : {}),
170
+ })).id;
171
+ }
172
+ await db.runHostStatus.update({ where: { runId: run.id }, data: { commentId, completedAt: new Date() } });
173
+ }
174
+ catch (err) {
175
+ log.warn({ err, runId: run.id }, "could not complete the run's status comment");
176
+ }
177
+ }
178
+ /**
179
+ * Posts the "working on it" comment for a mention run whose status row was
180
+ * written at dispatch, and records the comment on the row. When the run
181
+ * already ended (a fast failure can beat this follow-up), completes it at
182
+ * once. Does nothing when the row is gone, already has a comment, or is
183
+ * already complete. Never throws.
184
+ */
185
+ export async function postMentionStatus(db, host, runId, hosts) {
186
+ try {
187
+ const status = await db.runHostStatus.findUnique({ where: { runId } });
188
+ if (!status || status.commentId || status.completedAt)
189
+ return;
190
+ const posted = await host.comment(status.repository, {
191
+ number: status.number,
192
+ body: workingBody(runId),
193
+ ...(status.replyToReviewCommentId ? { replyToReviewCommentId: status.replyToReviewCommentId } : {}),
194
+ });
195
+ const claimed = await db.runHostStatus.updateMany({
196
+ where: { runId, commentId: null, completedAt: null },
197
+ data: { commentId: posted.id },
198
+ });
199
+ if (claimed.count === 0) {
200
+ // The reconciler posted the outcome meanwhile; this comment is surplus.
201
+ log.warn({ runId }, "status comment posted after the outcome; leaving both");
202
+ return;
203
+ }
204
+ const run = await db.run.findUnique({ where: { id: runId }, select: { id: true, status: true, finalText: true } });
205
+ if (run && TERMINAL.has(run.status))
206
+ await completeHostStatus(db, run, hosts);
207
+ }
208
+ catch (err) {
209
+ log.warn({ err, runId }, "could not post the status comment");
210
+ }
211
+ }
@@ -0,0 +1,8 @@
1
+ /** Registers a run for the duration of `work`. */
2
+ export declare function trackRun<T>(runId: string, work: () => Promise<T>): Promise<T>;
3
+ export declare function inFlightRunIds(): string[];
4
+ /**
5
+ * Resolves once no run is executing, or after `timeoutMs`, whichever is
6
+ * first. Returns the ids still executing at that point (empty when drained).
7
+ */
8
+ export declare function waitForInFlightRuns(timeoutMs: number): Promise<string[]>;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Counts the native runs executing in this process, so a shutdown can wait
3
+ * for them instead of abandoning them. Every native run, however it was
4
+ * started (a direct start, a DBOS workflow, a workflow adopted from a dead
5
+ * instance), goes through executeRun, which registers here. Coding jobs are
6
+ * not counted: they run in their own pods and a new instance collects them.
7
+ */
8
+ import { logger } from "./logger.js";
9
+ const log = logger.child({ module: "in-flight-runs" });
10
+ const active = new Set();
11
+ let waiters = [];
12
+ /** Registers a run for the duration of `work`. */
13
+ export async function trackRun(runId, work) {
14
+ active.add(runId);
15
+ try {
16
+ return await work();
17
+ }
18
+ finally {
19
+ active.delete(runId);
20
+ if (active.size === 0) {
21
+ const done = waiters;
22
+ waiters = [];
23
+ for (const wake of done)
24
+ wake();
25
+ }
26
+ }
27
+ }
28
+ export function inFlightRunIds() {
29
+ return [...active];
30
+ }
31
+ /**
32
+ * Resolves once no run is executing, or after `timeoutMs`, whichever is
33
+ * first. Returns the ids still executing at that point (empty when drained).
34
+ */
35
+ export async function waitForInFlightRuns(timeoutMs) {
36
+ if (active.size === 0 || timeoutMs <= 0)
37
+ return inFlightRunIds();
38
+ log.info({ runIds: inFlightRunIds(), timeoutMs }, "waiting for in-flight runs before shutdown");
39
+ await new Promise((resolve) => {
40
+ const timer = setTimeout(() => {
41
+ waiters = waiters.filter((w) => w !== wake);
42
+ resolve();
43
+ }, timeoutMs);
44
+ const wake = () => {
45
+ clearTimeout(timer);
46
+ resolve();
47
+ };
48
+ waiters.push(wake);
49
+ });
50
+ const remaining = inFlightRunIds();
51
+ if (remaining.length > 0)
52
+ log.warn({ runIds: remaining }, "shutdown drain timed out; these runs are abandoned");
53
+ else
54
+ log.info("in-flight runs finished; shutting down");
55
+ return remaining;
56
+ }
@@ -0,0 +1,12 @@
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
+ export type ProviderFailureClass = "quota" | "rate_limited" | "unavailable" | "rejected";
8
+ /** Files an upstream error code (or `http_<status>`) under the class of problem it names. */
9
+ export declare function classifyProviderFailure(code: string): ProviderFailureClass;
10
+ export declare function providerSentence(providerClass: ProviderFailureClass): string;
11
+ /** The class a run's `provider_<class>` failure category names, or null for any other category. */
12
+ export declare function providerClassOfCategory(category: string | null | undefined): ProviderFailureClass | null;
@@ -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
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;