@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,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>;
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Built-in code-review tools (`repo_*`) for native agents with at least one
3
+ * AgentRepository link. Like the memory built-ins they are synthesized by the
4
+ * runner, recognized by name, and never run in the sandbox. The security
5
+ * boundary is here: every call names a repository, which must resolve to one
6
+ * of this agent's links (with write access for write tools); the host then
7
+ * mints its own least-permission credential. The model never sees a token,
8
+ * a check id, or a marker. Never throws — failures are JSON tool results.
9
+ * See docs/private/2026-09-25-code-review-host-design.md §7.
10
+ */
11
+ import { z } from "zod";
12
+ import { REVIEW_HOST_PROVIDERS, ReviewHostError, } from "../providers/review-host/types.js";
13
+ import { normalizeGitHubRepository } from "../coding/protocol.js";
14
+ import { logger } from "./logger.js";
15
+ import { describeDenial } from "./repo-access.js";
16
+ const log = logger.child({ module: "review-host-tools" });
17
+ const DEFAULT_PATCH_CHARS = 60_000;
18
+ const SHA = z.string().regex(/^[0-9a-f]{40}$/);
19
+ const Repository = z.string().min(1).max(300);
20
+ const PrReadArgs = z
21
+ .object({
22
+ repository: Repository,
23
+ prNumber: z.number().int().positive(),
24
+ sinceSha: SHA.optional(),
25
+ maxPatchChars: z.number().int().positive().max(200_000).optional(),
26
+ })
27
+ .strict();
28
+ const ReadFileArgs = z
29
+ .object({
30
+ repository: Repository,
31
+ path: z.string().min(1).max(500),
32
+ ref: z.string().min(1).max(200).optional(),
33
+ startLine: z.number().int().positive().optional(),
34
+ maxLines: z.number().int().positive().max(2000).optional(),
35
+ })
36
+ .strict();
37
+ const ListFilesArgs = z
38
+ .object({
39
+ repository: Repository,
40
+ ref: z.string().min(1).max(200).optional(),
41
+ pathPrefix: z.string().max(500).optional(),
42
+ })
43
+ .strict();
44
+ const InlineCommentArgs = z
45
+ .object({
46
+ path: z.string().min(1).max(500),
47
+ line: z.number().int().positive(),
48
+ side: z.enum(["LEFT", "RIGHT"]).optional(),
49
+ severity: z.string().min(1).max(20),
50
+ body: z.string().min(1).max(4000),
51
+ })
52
+ .strict();
53
+ const PublishArgs = z
54
+ .object({
55
+ repository: Repository,
56
+ prNumber: z.number().int().positive(),
57
+ headSha: SHA,
58
+ verdict: z.enum(["APPROVE", "CHANGES_REQUESTED", "COMMENT"]),
59
+ summary: z.string().min(1).max(140),
60
+ body: z.string().min(1).max(60_000),
61
+ comments: z.array(InlineCommentArgs).max(50).optional(),
62
+ resolveThreadIds: z
63
+ .array(z.string().regex(/^[A-Za-z0-9_=-]{1,100}$/))
64
+ .max(100)
65
+ .optional(),
66
+ })
67
+ .strict();
68
+ const CommentArgs = z
69
+ .object({
70
+ repository: Repository,
71
+ number: z.number().int().positive(),
72
+ body: z.string().min(1).max(20_000),
73
+ replyToReviewCommentId: z
74
+ .string()
75
+ .regex(/^\d{1,20}$/)
76
+ .optional(),
77
+ })
78
+ .strict();
79
+ const REPOSITORY_PROP = {
80
+ type: "string",
81
+ description: 'The repository as "owner/name" (or "github.com/owner/name"); must be one this agent is linked to.',
82
+ };
83
+ export const REVIEW_HOST_TOOL_DEFS = [
84
+ {
85
+ name: "repo_pr_read",
86
+ description: "Reads a pull request: title, body, author, state, refs, headSha, isFork, each changed file's unified-diff patch (patches share a character budget; truncated ones are flagged), lastReviewedSha — the head you last reviewed on this PR, if any — and openThreads: your own unresolved inline review threads (id, path, line, outdated, body). Pass sinceSha (usually lastReviewedSha) to get only what changed since then; if baseMergedSince is true, the base branch was merged in meanwhile and each file shows the PR's full diff against the base, limited to files that changed since sinceSha.",
87
+ jsonSchema: {
88
+ type: "object",
89
+ properties: {
90
+ repository: REPOSITORY_PROP,
91
+ prNumber: { type: "integer", minimum: 1 },
92
+ sinceSha: { type: "string", pattern: "^[0-9a-f]{40}$" },
93
+ maxPatchChars: { type: "integer", minimum: 1, maximum: 200000 },
94
+ },
95
+ required: ["repository", "prNumber"],
96
+ additionalProperties: false,
97
+ },
98
+ },
99
+ {
100
+ name: "repo_read_file",
101
+ description: "Reads a file with line numbers (paged by startLine/maxLines, default 400 lines) or lists a directory, at a ref. Use the PR's headSha as the ref when reviewing.",
102
+ jsonSchema: {
103
+ type: "object",
104
+ properties: {
105
+ repository: REPOSITORY_PROP,
106
+ path: { type: "string" },
107
+ ref: { type: "string" },
108
+ startLine: { type: "integer", minimum: 1 },
109
+ maxLines: { type: "integer", minimum: 1, maximum: 2000 },
110
+ },
111
+ required: ["repository", "path"],
112
+ additionalProperties: false,
113
+ },
114
+ },
115
+ {
116
+ name: "repo_list_files",
117
+ description: 'Lists every file (path and size) at a ref (default: the default branch), optionally under a path prefix such as "tests/". Skips node_modules, dist, and lockfiles.',
118
+ jsonSchema: {
119
+ type: "object",
120
+ properties: { repository: REPOSITORY_PROP, ref: { type: "string" }, pathPrefix: { type: "string" } },
121
+ required: ["repository"],
122
+ additionalProperties: false,
123
+ },
124
+ },
125
+ {
126
+ name: "repo_publish_review",
127
+ description: "Publishes your review of one pull request head, in one call: inline comments on diff lines (a ```suggestion block in a comment body becomes a one-click fix; comments on lines outside the diff move to the summary automatically), one summary comment that is edited in place on later reviews, and — only on the pull request this run was started for, by an agent linked with a check name — the check conclusion (APPROVE = success, CHANGES_REQUESTED = failure, COMMENT = neutral). Pass resolveThreadIds with the ids of your openThreads that this head fixes; only your own open threads on this PR are resolved, and the result lists resolvedThreadIds and skippedThreadIds. Returns published:false with reason stale_head if the PR moved on; then stop.",
128
+ jsonSchema: {
129
+ type: "object",
130
+ properties: {
131
+ repository: REPOSITORY_PROP,
132
+ prNumber: { type: "integer", minimum: 1 },
133
+ headSha: { type: "string", pattern: "^[0-9a-f]{40}$" },
134
+ verdict: { type: "string", enum: ["APPROVE", "CHANGES_REQUESTED", "COMMENT"] },
135
+ summary: { type: "string", maxLength: 140, description: "One line, shown on the check." },
136
+ body: { type: "string", maxLength: 60000, description: "The full review in markdown." },
137
+ comments: {
138
+ type: "array",
139
+ maxItems: 50,
140
+ items: {
141
+ type: "object",
142
+ properties: {
143
+ path: { type: "string" },
144
+ line: {
145
+ type: "integer",
146
+ minimum: 1,
147
+ description: "Line number in the new file (side RIGHT) or the old file (side LEFT).",
148
+ },
149
+ side: { type: "string", enum: ["LEFT", "RIGHT"] },
150
+ severity: { type: "string", description: "e.g. CRITICAL, MAJOR, MINOR, NIT" },
151
+ body: { type: "string", maxLength: 4000 },
152
+ },
153
+ required: ["path", "line", "severity", "body"],
154
+ additionalProperties: false,
155
+ },
156
+ },
157
+ resolveThreadIds: {
158
+ type: "array",
159
+ maxItems: 100,
160
+ items: { type: "string", pattern: "^[A-Za-z0-9_=-]{1,100}$" },
161
+ description: "Ids from repo_pr_read's openThreads whose problem this head fixes. Leave a thread out when unsure.",
162
+ },
163
+ },
164
+ required: ["repository", "prNumber", "headSha", "verdict", "summary", "body"],
165
+ additionalProperties: false,
166
+ },
167
+ },
168
+ {
169
+ name: "repo_comment",
170
+ description: "Posts a comment on an issue or pull request conversation, or — with replyToReviewCommentId — replies inside an inline review thread.",
171
+ jsonSchema: {
172
+ type: "object",
173
+ properties: {
174
+ repository: REPOSITORY_PROP,
175
+ number: { type: "integer", minimum: 1, description: "The issue or pull request number." },
176
+ body: { type: "string", maxLength: 20000 },
177
+ replyToReviewCommentId: { type: "string", pattern: "^\\d{1,20}$" },
178
+ },
179
+ required: ["repository", "number", "body"],
180
+ additionalProperties: false,
181
+ },
182
+ },
183
+ ];
184
+ export const REVIEW_HOST_TOOL_NAMES = new Set(REVIEW_HOST_TOOL_DEFS.map((t) => t.name));
185
+ const WRITE_TOOLS = new Set(["repo_publish_review", "repo_comment"]);
186
+ const HOST_PREFIXES = { github: "github.com/" };
187
+ function normalizeFor(provider, value) {
188
+ try {
189
+ if (provider === "github")
190
+ return normalizeGitHubRepository(value);
191
+ }
192
+ catch {
193
+ return null;
194
+ }
195
+ return null;
196
+ }
197
+ export function resolveLink(links, repositoryArg) {
198
+ const raw = repositoryArg.trim();
199
+ for (const provider of REVIEW_HOST_PROVIDERS) {
200
+ const prefix = HOST_PREFIXES[provider];
201
+ if (raw.toLowerCase().startsWith(prefix)) {
202
+ const repository = normalizeFor(provider, raw.slice(prefix.length));
203
+ return links.find((l) => l.provider === provider && l.repository === repository) ?? null;
204
+ }
205
+ }
206
+ const matches = links.filter((l) => l.repository === normalizeFor(l.provider, raw));
207
+ if (matches.length > 1)
208
+ return "ambiguous";
209
+ return matches[0] ?? null;
210
+ }
211
+ function error(code, message = code) {
212
+ return JSON.stringify({ error: code, message });
213
+ }
214
+ /** Bookkeeping only: a failure to record the check completed is logged and never changes the tool result. */
215
+ async function markCompleted(ctx) {
216
+ try {
217
+ await ctx.markRunCheckCompleted();
218
+ }
219
+ catch (err) {
220
+ log.warn({ err, agentId: ctx.agentId }, "could not record the run's check completed");
221
+ }
222
+ }
223
+ /**
224
+ * Dispatches one built-in repo tool call. Never throws — mirrors
225
+ * runner.ts's `runSandboxTool` contract: a failure becomes a JSON error
226
+ * result fed back to the model as the tool's result, not an engine-halting
227
+ * exception.
228
+ */
229
+ export async function handleReviewHostTool(name, argsJson, ctx) {
230
+ let parsed;
231
+ try {
232
+ parsed = JSON.parse(argsJson || "{}");
233
+ }
234
+ catch (err) {
235
+ return error("invalid_arguments_json", err instanceof Error ? err.message : String(err));
236
+ }
237
+ const repositoryArg = parsed?.repository;
238
+ if (typeof repositoryArg !== "string")
239
+ return error("invalid_arguments", "repository is required");
240
+ const link = resolveLink(ctx.links, repositoryArg);
241
+ if (link === "ambiguous")
242
+ return error("repository_not_linked", "Ambiguous repository; qualify it as host/owner/name.");
243
+ if (!link)
244
+ return error("repository_not_linked", `This agent is not linked to "${repositoryArg}".`);
245
+ if (WRITE_TOOLS.has(name) && link.access !== "write") {
246
+ return error("write_access_required", `This agent's link to ${link.repository} is read-only.`);
247
+ }
248
+ const host = ctx.hosts[link.provider];
249
+ if (!host)
250
+ return error("host_not_configured", `No ${link.provider} host is configured on this deployment.`);
251
+ let decision;
252
+ try {
253
+ decision = await ctx.authorize(link);
254
+ }
255
+ catch (err) {
256
+ log.warn({ err, agentId: ctx.agentId }, "repository authorization failed; denying");
257
+ decision = { ok: false, reason: "check_failed" };
258
+ }
259
+ if (!decision.ok) {
260
+ log.info({ agentId: ctx.agentId, repository: link.repository, reason: decision.reason }, "repository use denied");
261
+ return error(decision.reason === "check_unavailable" ? "repository_access_unavailable" : "repository_access_denied", describeDenial(decision, link.repository));
262
+ }
263
+ try {
264
+ switch (name) {
265
+ case "repo_pr_read": {
266
+ const a = PrReadArgs.parse(parsed);
267
+ return JSON.stringify(await host.readPullRequest(link.repository, a.prNumber, {
268
+ sinceSha: a.sinceSha,
269
+ maxPatchChars: a.maxPatchChars ?? DEFAULT_PATCH_CHARS,
270
+ agentMarker: ctx.agentId,
271
+ }));
272
+ }
273
+ case "repo_read_file": {
274
+ const a = ReadFileArgs.parse(parsed);
275
+ return JSON.stringify(await host.readFile(link.repository, a.path, a.ref, {
276
+ startLine: a.startLine ?? 1,
277
+ maxLines: a.maxLines ?? 400,
278
+ }));
279
+ }
280
+ case "repo_list_files": {
281
+ const a = ListFilesArgs.parse(parsed);
282
+ return JSON.stringify(await host.listFiles(link.repository, a.ref, a.pathPrefix));
283
+ }
284
+ case "repo_publish_review": {
285
+ const a = PublishArgs.parse(parsed);
286
+ const runCheck = ctx.runCheck;
287
+ // E-08/H5-4: a verdict check is only ever published on the PR this
288
+ // run was dispatched for (never a fork: those are never dispatched).
289
+ // Any other publish is comments only, with no check at all.
290
+ const samePullRequest = runCheck !== null &&
291
+ runCheck.provider === link.provider &&
292
+ runCheck.repository === link.repository &&
293
+ runCheck.prNumber !== null &&
294
+ runCheck.prNumber === a.prNumber;
295
+ const ownsCheck = samePullRequest && a.headSha === runCheck.headSha;
296
+ if (samePullRequest && !ownsCheck) {
297
+ // The run's check sits on another head; close it rather than attach this review's verdict to it.
298
+ try {
299
+ await host.completeCheck(link.repository, {
300
+ checkId: runCheck.checkId,
301
+ conclusion: "neutral",
302
+ title: "Superseded by a newer push",
303
+ summary: "This run reviewed a newer commit; see that commit's check.",
304
+ });
305
+ await markCompleted(ctx);
306
+ }
307
+ catch (err) {
308
+ log.warn({ err, agentId: ctx.agentId }, "could not supersede the run's check");
309
+ }
310
+ }
311
+ const result = await host.publishReview(link.repository, {
312
+ prNumber: a.prNumber,
313
+ headSha: a.headSha,
314
+ verdict: a.verdict,
315
+ summary: a.summary,
316
+ body: a.body,
317
+ comments: (a.comments ?? []).map((c) => ({ ...c, side: c.side ?? "RIGHT" })),
318
+ agentMarker: ctx.agentId,
319
+ ...(samePullRequest && link.checkName ? { checkName: link.checkName } : {}),
320
+ ...(ownsCheck ? { checkId: runCheck.checkId } : {}),
321
+ ...(a.resolveThreadIds?.length ? { resolveThreadIds: a.resolveThreadIds } : {}),
322
+ });
323
+ if (ownsCheck)
324
+ await markCompleted(ctx);
325
+ return JSON.stringify(result);
326
+ }
327
+ case "repo_comment": {
328
+ const a = CommentArgs.parse(parsed);
329
+ return JSON.stringify(await host.comment(link.repository, {
330
+ number: a.number,
331
+ body: a.body,
332
+ replyToReviewCommentId: a.replyToReviewCommentId,
333
+ }));
334
+ }
335
+ default:
336
+ return error("unknown_tool", `No built-in tool named "${name}".`);
337
+ }
338
+ }
339
+ catch (err) {
340
+ if (err instanceof z.ZodError) {
341
+ return error("invalid_arguments", err.issues.map((i) => `${i.path.join(".") || "input"}: ${i.message}`).join("; "));
342
+ }
343
+ if (err instanceof ReviewHostError)
344
+ return error(err.code, err.message);
345
+ return error("host_api_error", "The host request failed.");
346
+ }
347
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Liveness for native runs that no executor watches: an attended
3
+ * `wardby run` and a native sub-agent child, which runs inline inside its
4
+ * parent's tool call. Managed runs already beat through their executor
5
+ * (InProcessExecutor, DbosExecutor). A beat is what keeps a run's budget
6
+ * hold counted against its budget group (budget-groups.ts isHoldLive): once a
7
+ * process dies and the beats stop, the hold lapses on its own instead of
8
+ * pinning the group for the rest of the period.
9
+ */
10
+ import type { PrismaClient } from "#prisma";
11
+ type RunDb = Pick<PrismaClient, "run">;
12
+ /** Beats `runId` once now and every `intervalMs` until `fn` settles. */
13
+ export declare function withRunHeartbeat<T>(db: RunDb, runId: string, fn: () => Promise<T>, intervalMs?: number): Promise<T>;
14
+ /** The part of `process` an interrupt handler needs (a fake in tests). */
15
+ export interface SignalTarget {
16
+ once(signal: NodeJS.Signals, listener: () => void): unknown;
17
+ off(signal: NodeJS.Signals, listener: () => void): unknown;
18
+ exit(code: number): never | void;
19
+ }
20
+ /**
21
+ * For an attended run: on SIGINT/SIGTERM, record the run as `cancelled`
22
+ * (only if it is still pending/running) and exit with the conventional
23
+ * 128+signal code, instead of leaving a `running` row behind. Returns a
24
+ * function that removes the handlers once the run has finished.
25
+ */
26
+ export declare function cancelRunOnSignal(db: RunDb, runId: string, target?: SignalTarget): () => void;
27
+ export {};
@@ -0,0 +1,54 @@
1
+ import { HEARTBEAT_INTERVAL_MS } from "./timing.js";
2
+ async function beat(db, runId) {
3
+ try {
4
+ await db.run.updateMany({
5
+ where: { id: runId, status: { in: ["pending", "running"] } },
6
+ data: { heartbeatAt: new Date() },
7
+ });
8
+ }
9
+ catch {
10
+ // A missed beat only lets the hold lapse a little sooner; never fatal to the run.
11
+ }
12
+ }
13
+ /** Beats `runId` once now and every `intervalMs` until `fn` settles. */
14
+ export async function withRunHeartbeat(db, runId, fn, intervalMs = HEARTBEAT_INTERVAL_MS) {
15
+ await beat(db, runId);
16
+ const timer = setInterval(() => void beat(db, runId), intervalMs);
17
+ timer.unref?.();
18
+ try {
19
+ return await fn();
20
+ }
21
+ finally {
22
+ clearInterval(timer);
23
+ }
24
+ }
25
+ const EXIT_CODES = { SIGINT: 130, SIGTERM: 143 };
26
+ /**
27
+ * For an attended run: on SIGINT/SIGTERM, record the run as `cancelled`
28
+ * (only if it is still pending/running) and exit with the conventional
29
+ * 128+signal code, instead of leaving a `running` row behind. Returns a
30
+ * function that removes the handlers once the run has finished.
31
+ */
32
+ export function cancelRunOnSignal(db, runId, target = process) {
33
+ const handlers = new Map();
34
+ const remove = () => {
35
+ for (const [signal, handler] of handlers)
36
+ target.off(signal, handler);
37
+ handlers.clear();
38
+ };
39
+ for (const signal of ["SIGINT", "SIGTERM"]) {
40
+ const handler = () => {
41
+ remove();
42
+ void db.run
43
+ .updateMany({
44
+ where: { id: runId, status: { in: ["pending", "running"] } },
45
+ data: { status: "cancelled", error: `Interrupted by ${signal}.`, finishedAt: new Date() },
46
+ })
47
+ .catch(() => undefined)
48
+ .finally(() => target.exit(EXIT_CODES[signal] ?? 1));
49
+ };
50
+ handlers.set(signal, handler);
51
+ target.once(signal, handler);
52
+ }
53
+ return remove;
54
+ }