@wardby/cli 0.5.3 → 0.7.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 (763) hide show
  1. package/.env.example +33 -0
  2. package/README.md +25 -14
  3. package/deploy/local/docker-compose.quickstart-coding.yml +13 -0
  4. package/dist/claude-coding-worker/context.d.ts +22 -0
  5. package/dist/claude-coding-worker/context.js +92 -0
  6. package/dist/claude-coding-worker/driver.d.ts +27 -3
  7. package/dist/claude-coding-worker/driver.js +161 -20
  8. package/dist/claude-coding-worker/main.js +34 -6
  9. package/dist/claude-coding-worker/sdk.js +25 -5
  10. package/dist/claude-coding-worker/tool-socket.d.ts +27 -0
  11. package/dist/claude-coding-worker/tool-socket.js +111 -0
  12. package/dist/claude-coding-worker/transcript.d.ts +18 -0
  13. package/dist/claude-coding-worker/transcript.js +77 -0
  14. package/dist/cli-help.d.ts +1 -1
  15. package/dist/cli-help.js +6 -2
  16. package/dist/cli-native-mode.d.ts +9 -0
  17. package/dist/cli-native-mode.js +20 -0
  18. package/dist/cli.d.ts +3 -1
  19. package/dist/cli.js +161 -11
  20. package/dist/coding/claude-context.d.ts +45 -0
  21. package/dist/coding/claude-context.js +434 -0
  22. package/dist/coding/continuation-wording.d.ts +35 -0
  23. package/dist/coding/continuation-wording.js +73 -0
  24. package/dist/coding/linked-issue-note.d.ts +23 -0
  25. package/dist/coding/linked-issue-note.js +47 -0
  26. package/dist/coding/merge-wording.d.ts +28 -0
  27. package/dist/coding/merge-wording.js +78 -0
  28. package/dist/coding/pr-description-note.d.ts +9 -0
  29. package/dist/coding/pr-description-note.js +32 -0
  30. package/dist/coding/profile.d.ts +50 -118
  31. package/dist/coding/profile.js +51 -4
  32. package/dist/coding/protected-path-wording.d.ts +8 -0
  33. package/dist/coding/protected-path-wording.js +1 -1
  34. package/dist/coding/protocol.d.ts +292 -270
  35. package/dist/coding/protocol.js +322 -15
  36. package/dist/coding/redact.d.ts +3 -0
  37. package/dist/coding/redact.js +12 -0
  38. package/dist/coding/registry/allowlist.d.ts +12 -0
  39. package/dist/coding/registry/allowlist.js +14 -1
  40. package/dist/coding/registry/audit-mode.d.ts +153 -0
  41. package/dist/coding/registry/audit-mode.js +304 -0
  42. package/dist/coding/registry/lockfile-shared.d.ts +36 -0
  43. package/dist/coding/registry/lockfile-shared.js +128 -0
  44. package/dist/coding/registry/lockfiles.js +1 -1
  45. package/dist/coding/registry/npm-lockfile-formats.d.ts +11 -0
  46. package/dist/coding/registry/npm-lockfile-formats.js +41 -0
  47. package/dist/coding/registry/npm-lockfile.d.ts +8 -4
  48. package/dist/coding/registry/npm-lockfile.js +4 -6
  49. package/dist/coding/registry/npm.js +78 -8
  50. package/dist/coding/registry/pnpm-lockfile.d.ts +17 -0
  51. package/dist/coding/registry/pnpm-lockfile.js +189 -0
  52. package/dist/coding/registry/pypi-plan.d.ts +50 -0
  53. package/dist/coding/registry/pypi-plan.js +406 -0
  54. package/dist/coding/registry/pypi-requirements.d.ts +58 -0
  55. package/dist/coding/registry/pypi-requirements.js +155 -0
  56. package/dist/coding/registry/pypi.d.ts +3 -27
  57. package/dist/coding/registry/pypi.js +40 -154
  58. package/dist/coding/registry/refusal-hints.d.ts +20 -0
  59. package/dist/coding/registry/refusal-hints.js +61 -0
  60. package/dist/coding/registry/refusal-wording.d.ts +11 -0
  61. package/dist/coding/registry/refusal-wording.js +56 -0
  62. package/dist/coding/registry/report.d.ts +70 -8
  63. package/dist/coding/registry/report.js +112 -5
  64. package/dist/coding/registry/types.d.ts +58 -8
  65. package/dist/coding/registry/yarn-lockfile.d.ts +30 -0
  66. package/dist/coding/registry/yarn-lockfile.js +367 -0
  67. package/dist/coding/services/catalog.d.ts +37 -351
  68. package/dist/coding/transcript/claude-code.d.ts +13 -0
  69. package/dist/coding/transcript/claude-code.js +273 -0
  70. package/dist/coding/transcript/download-protocol.d.ts +30 -0
  71. package/dist/coding/transcript/download-protocol.js +18 -0
  72. package/dist/coding/transcript/format.d.ts +20 -0
  73. package/dist/coding/transcript/format.js +32 -0
  74. package/dist/coding/transcript/recorder.d.ts +51 -0
  75. package/dist/coding/transcript/recorder.js +225 -0
  76. package/dist/coding/transcript/resume/checkout.d.ts +31 -0
  77. package/dist/coding/transcript/resume/checkout.js +203 -0
  78. package/dist/coding/transcript/resume/command.d.ts +17 -0
  79. package/dist/coding/transcript/resume/command.js +176 -0
  80. package/dist/coding/transcript/resume/download.d.ts +20 -0
  81. package/dist/coding/transcript/resume/download.js +153 -0
  82. package/dist/coding/transcript/resume/launch.d.ts +38 -0
  83. package/dist/coding/transcript/resume/launch.js +128 -0
  84. package/dist/coding/transcript/resume/session-file.d.ts +13 -0
  85. package/dist/coding/transcript/resume/session-file.js +79 -0
  86. package/dist/coding/transcript/resume/stand-in.d.ts +11 -0
  87. package/dist/coding/transcript/resume/stand-in.js +49 -0
  88. package/dist/coding-worker/artifact.d.ts +16 -3
  89. package/dist/coding-worker/artifact.js +5 -3
  90. package/dist/coding-worker/debug-trace.d.ts +6 -5
  91. package/dist/coding-worker/debug-trace.js +13 -18
  92. package/dist/coding-worker/dependency-cache.d.mts +229 -0
  93. package/dist/coding-worker/dependency-cache.mjs +2123 -0
  94. package/dist/coding-worker/driver.d.ts +4 -3
  95. package/dist/coding-worker/driver.js +118 -38
  96. package/dist/coding-worker/errors.d.ts +1 -0
  97. package/dist/coding-worker/errors.js +32 -0
  98. package/dist/coding-worker/sdk.d.ts +6 -0
  99. package/dist/coding-worker/sdk.js +1 -0
  100. package/dist/coding-worker/skills.d.ts +23 -0
  101. package/dist/coding-worker/skills.js +117 -0
  102. package/dist/coding-worker/task-note.d.ts +11 -0
  103. package/dist/coding-worker/task-note.js +13 -0
  104. package/dist/coding-worker/types.d.ts +13 -0
  105. package/dist/config/dependency-cache.d.ts +23 -0
  106. package/dist/config/dependency-cache.js +32 -0
  107. package/dist/config/github-allowed-accounts.d.ts +15 -0
  108. package/dist/config/github-allowed-accounts.js +36 -0
  109. package/dist/config/low-power.d.ts +12 -0
  110. package/dist/config/low-power.js +18 -0
  111. package/dist/config/mobile.d.ts +12 -0
  112. package/dist/config/mobile.js +18 -0
  113. package/dist/config/providers.d.ts +65 -3
  114. package/dist/config/providers.js +147 -11
  115. package/dist/config/test-simulators.d.ts +82 -0
  116. package/dist/config/test-simulators.js +305 -0
  117. package/dist/core/async-context.d.ts +2 -0
  118. package/dist/core/async-context.js +12 -0
  119. package/dist/core/budget-events.d.ts +108 -0
  120. package/dist/core/budget-events.js +346 -0
  121. package/dist/core/budget-groups.d.ts +50 -1
  122. package/dist/core/budget-groups.js +236 -27
  123. package/dist/core/budget-stopped-head.d.ts +46 -0
  124. package/dist/core/budget-stopped-head.js +81 -0
  125. package/dist/core/budget-topup.d.ts +51 -0
  126. package/dist/core/budget-topup.js +156 -0
  127. package/dist/core/budget-wording.d.ts +16 -2
  128. package/dist/core/budget-wording.js +40 -2
  129. package/dist/core/coding-queue.d.ts +2 -1
  130. package/dist/core/coding-queue.js +7 -0
  131. package/dist/core/cron.d.ts +19 -1
  132. package/dist/core/cron.js +17 -0
  133. package/dist/core/datastores.js +1 -1
  134. package/dist/core/db.d.ts +16 -6
  135. package/dist/core/db.js +12 -10
  136. package/dist/core/deferred-coding.d.ts +14 -0
  137. package/dist/core/deferred-coding.js +62 -0
  138. package/dist/core/delegation-child.d.ts +25 -0
  139. package/dist/core/delegation-child.js +37 -0
  140. package/dist/core/dispatch.d.ts +53 -0
  141. package/dist/core/dispatch.js +167 -57
  142. package/dist/core/engine-native.js +10 -6
  143. package/dist/core/error-code.d.ts +38 -0
  144. package/dist/core/error-code.js +168 -0
  145. package/dist/core/grants.d.ts +19 -6
  146. package/dist/core/grants.js +55 -6
  147. package/dist/core/host-events.d.ts +67 -5
  148. package/dist/core/host-events.js +571 -37
  149. package/dist/core/host-status.d.ts +85 -3
  150. package/dist/core/host-status.js +298 -20
  151. package/dist/core/issue-bridge.d.ts +50 -6
  152. package/dist/core/issue-bridge.js +151 -20
  153. package/dist/core/issue-events.d.ts +1 -1
  154. package/dist/core/issue-events.js +16 -11
  155. package/dist/core/issue-status.d.ts +1 -1
  156. package/dist/core/issue-status.js +68 -29
  157. package/dist/core/logger.d.ts +5 -0
  158. package/dist/core/logger.js +14 -1
  159. package/dist/core/lost-run-children.d.ts +6 -0
  160. package/dist/core/lost-run-children.js +32 -0
  161. package/dist/core/mark-ready.d.ts +34 -0
  162. package/dist/core/mark-ready.js +152 -0
  163. package/dist/core/markdown-code.d.ts +8 -0
  164. package/dist/core/markdown-code.js +107 -0
  165. package/dist/core/memory-tools.js +7 -0
  166. package/dist/core/merge-order-check.d.ts +130 -0
  167. package/dist/core/merge-order-check.js +343 -0
  168. package/dist/core/notification-dispatcher.d.ts +70 -0
  169. package/dist/core/notification-dispatcher.js +368 -0
  170. package/dist/core/notification-templates.d.ts +19 -0
  171. package/dist/core/notification-templates.js +139 -0
  172. package/dist/core/notifications.d.ts +10 -0
  173. package/dist/core/notifications.js +38 -0
  174. package/dist/core/pg-auth.d.ts +22 -0
  175. package/dist/core/pg-auth.js +152 -0
  176. package/dist/core/power-composition.d.ts +16 -0
  177. package/dist/core/power-composition.js +37 -0
  178. package/dist/core/power-idle.d.ts +14 -0
  179. package/dist/core/power-idle.js +30 -0
  180. package/dist/core/power.d.ts +71 -0
  181. package/dist/core/power.js +202 -0
  182. package/dist/core/pull-request-state-sync.js +2 -2
  183. package/dist/core/reconciler.d.ts +8 -5
  184. package/dist/core/reconciler.js +37 -6
  185. package/dist/core/related-pull-requests.d.ts +59 -7
  186. package/dist/core/related-pull-requests.js +140 -25
  187. package/dist/core/reply-parts.d.ts +21 -0
  188. package/dist/core/reply-parts.js +73 -0
  189. package/dist/core/repo-access.d.ts +15 -0
  190. package/dist/core/repo-access.js +66 -0
  191. package/dist/core/review-fix.d.ts +38 -1
  192. package/dist/core/review-fix.js +209 -7
  193. package/dist/core/review-host-checks.d.ts +1 -1
  194. package/dist/core/review-host-tools.d.ts +23 -0
  195. package/dist/core/review-host-tools.js +156 -14
  196. package/dist/core/review-reads.d.ts +55 -0
  197. package/dist/core/review-reads.js +233 -0
  198. package/dist/core/run-status.d.ts +12 -0
  199. package/dist/core/run-status.js +12 -0
  200. package/dist/core/runner.d.ts +384 -5
  201. package/dist/core/runner.js +1244 -372
  202. package/dist/core/schedule-start.d.ts +12 -0
  203. package/dist/core/schedule-start.js +14 -0
  204. package/dist/core/secret-broker-config.d.ts +39 -0
  205. package/dist/core/secret-broker-config.js +112 -0
  206. package/dist/core/secret-guard.d.ts +46 -0
  207. package/dist/core/secret-guard.js +104 -0
  208. package/dist/core/secret-guarded-stores.d.ts +12 -0
  209. package/dist/core/secret-guarded-stores.js +41 -0
  210. package/dist/core/secret-spans.d.ts +50 -0
  211. package/dist/core/secret-spans.js +94 -0
  212. package/dist/core/secrets.d.ts +39 -4
  213. package/dist/core/secrets.js +148 -40
  214. package/dist/core/stop-run.d.ts +46 -0
  215. package/dist/core/stop-run.js +61 -0
  216. package/dist/core/workflow-event-recorder.d.ts +14 -0
  217. package/dist/core/workflow-event-recorder.js +120 -0
  218. package/dist/core/workflow-events.d.ts +94 -0
  219. package/dist/core/workflow-events.js +48 -0
  220. package/dist/core/workflow-run-events.d.ts +18 -0
  221. package/dist/core/workflow-run-events.js +75 -0
  222. package/dist/core/zod-json-schema.d.ts +4 -0
  223. package/dist/core/zod-json-schema.js +39 -0
  224. package/dist/core/zod3-window.d.ts +35 -0
  225. package/dist/core/zod3-window.js +124 -0
  226. package/dist/generated/prisma/browser.d.ts +92 -3
  227. package/dist/generated/prisma/client.d.ts +92 -3
  228. package/dist/generated/prisma/commonInputTypes.d.ts +364 -54
  229. package/dist/generated/prisma/enums.d.ts +37 -0
  230. package/dist/generated/prisma/enums.js +31 -0
  231. package/dist/generated/prisma/internal/class.d.ts +132 -0
  232. package/dist/generated/prisma/internal/class.js +4 -4
  233. package/dist/generated/prisma/internal/prismaNamespace.d.ts +1332 -142
  234. package/dist/generated/prisma/internal/prismaNamespace.js +227 -5
  235. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +234 -0
  236. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +227 -5
  237. package/dist/generated/prisma/models/Agent.d.ts +464 -7
  238. package/dist/generated/prisma/models/AgentRepository.d.ts +42 -1
  239. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +301 -1
  240. package/dist/generated/prisma/models/CodingProxyRequest.d.ts +207 -3
  241. package/dist/generated/prisma/models/CodingProxySession.d.ts +114 -1
  242. package/dist/generated/prisma/models/CodingRun.d.ts +1841 -1
  243. package/dist/generated/prisma/models/DeferredReview.d.ts +78 -1
  244. package/dist/generated/prisma/models/DependencySnapshot.d.ts +1668 -0
  245. package/dist/generated/prisma/models/DependencySnapshot.js +1 -0
  246. package/dist/generated/prisma/models/IssuePullRequest.d.ts +6 -2
  247. package/dist/generated/prisma/models/LocalReview.d.ts +40 -1
  248. package/dist/generated/prisma/models/NativeGatewayCall.d.ts +1713 -0
  249. package/dist/generated/prisma/models/NativeGatewayCall.js +1 -0
  250. package/dist/generated/prisma/models/NativeGatewaySession.d.ts +1551 -0
  251. package/dist/generated/prisma/models/NativeGatewaySession.js +1 -0
  252. package/dist/generated/prisma/models/NativeWarmWorker.d.ts +1296 -0
  253. package/dist/generated/prisma/models/NativeWarmWorker.js +1 -0
  254. package/dist/generated/prisma/models/NotificationChannel.d.ts +1543 -0
  255. package/dist/generated/prisma/models/NotificationChannel.js +1 -0
  256. package/dist/generated/prisma/models/NotificationDelivery.d.ts +1502 -0
  257. package/dist/generated/prisma/models/NotificationDelivery.js +1 -0
  258. package/dist/generated/prisma/models/NotificationThread.d.ts +1090 -0
  259. package/dist/generated/prisma/models/NotificationThread.js +1 -0
  260. package/dist/generated/prisma/models/RegistryAllowance.d.ts +41 -1
  261. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +75 -1
  262. package/dist/generated/prisma/models/RegistryAuditRecord.d.ts +1364 -0
  263. package/dist/generated/prisma/models/RegistryAuditRecord.js +1 -0
  264. package/dist/generated/prisma/models/Run.d.ts +1619 -58
  265. package/dist/generated/prisma/models/RunBudgetEvent.d.ts +1211 -0
  266. package/dist/generated/prisma/models/RunBudgetEvent.js +1 -0
  267. package/dist/generated/prisma/models/RunHostCheck.d.ts +36 -1
  268. package/dist/generated/prisma/models/RunTranscript.d.ts +1339 -0
  269. package/dist/generated/prisma/models/RunTranscript.js +1 -0
  270. package/dist/generated/prisma/models/Secret.d.ts +37 -1
  271. package/dist/generated/prisma/models/SecretBrokerChange.d.ts +1101 -0
  272. package/dist/generated/prisma/models/SecretBrokerChange.js +1 -0
  273. package/dist/generated/prisma/models/WorkflowEvent.d.ts +1422 -0
  274. package/dist/generated/prisma/models/WorkflowEvent.js +1 -0
  275. package/dist/generated/prisma/models.d.ts +12 -0
  276. package/dist/help/build.js +4 -2
  277. package/dist/help/catalog.d.ts +1 -1
  278. package/dist/help/catalog.js +31 -6
  279. package/dist/help-index.json +2584 -146
  280. package/dist/import/create.js +49 -3
  281. package/dist/import/neutral-schema.d.ts +50 -277
  282. package/dist/import/neutral-schema.js +7 -4
  283. package/dist/knowledge/check.d.ts +1 -1
  284. package/dist/knowledge/cli.d.ts +11 -1
  285. package/dist/knowledge/cli.js +37 -4
  286. package/dist/knowledge/concept.d.ts +3 -19
  287. package/dist/knowledge/location.d.ts +31 -0
  288. package/dist/knowledge/location.js +78 -0
  289. package/dist/mcp/auth/access.d.ts +11 -2
  290. package/dist/mcp/auth/access.js +15 -1
  291. package/dist/mcp/auth/ownership.d.ts +7 -7
  292. package/dist/mcp/auth/repo-authorization.js +14 -0
  293. package/dist/mcp/auth/resource-server.d.ts +2 -1
  294. package/dist/mcp/auth/resource-server.js +7 -2
  295. package/dist/mcp/auth/self-hosted/browser.d.ts +1 -1
  296. package/dist/mcp/auth/self-hosted/browser.js +19 -6
  297. package/dist/mcp/auth/self-hosted/credentials.d.ts +3 -3
  298. package/dist/mcp/auth/self-hosted/session.d.ts +2 -2
  299. package/dist/mcp/context.d.ts +6 -0
  300. package/dist/mcp/host-events/github-ingress.d.ts +25 -1
  301. package/dist/mcp/host-events/github-ingress.js +70 -1
  302. package/dist/mcp/index.d.ts +19 -0
  303. package/dist/mcp/index.js +117 -20
  304. package/dist/mcp/server.d.ts +13 -0
  305. package/dist/mcp/server.js +33 -14
  306. package/dist/mcp/tools/agents.d.ts +4 -0
  307. package/dist/mcp/tools/agents.js +277 -24
  308. package/dist/mcp/tools/datastore.js +17 -8
  309. package/dist/mcp/tools/grants.js +9 -3
  310. package/dist/mcp/tools/memory.js +11 -1
  311. package/dist/mcp/tools/notification-channels.d.ts +2 -0
  312. package/dist/mcp/tools/notification-channels.js +293 -0
  313. package/dist/mcp/tools/packages.d.ts +2 -0
  314. package/dist/mcp/tools/packages.js +195 -0
  315. package/dist/mcp/tools/power.d.ts +2 -0
  316. package/dist/mcp/tools/power.js +77 -0
  317. package/dist/mcp/tools/repositories.js +18 -5
  318. package/dist/mcp/tools/run-access.d.ts +16 -0
  319. package/dist/mcp/tools/run-access.js +25 -0
  320. package/dist/mcp/tools/run-transcript-download.d.ts +30 -0
  321. package/dist/mcp/tools/run-transcript-download.js +129 -0
  322. package/dist/mcp/tools/run-transcript.d.ts +19 -0
  323. package/dist/mcp/tools/run-transcript.js +70 -0
  324. package/dist/mcp/tools/runs.d.ts +0 -6
  325. package/dist/mcp/tools/runs.js +41 -14
  326. package/dist/mcp/tools/scheduling.js +3 -1
  327. package/dist/mcp/tools/secret-broker-compat.d.ts +12 -0
  328. package/dist/mcp/tools/secret-broker-compat.js +23 -0
  329. package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -2
  330. package/dist/mcp/tools/secret-elicitation-form.js +205 -10
  331. package/dist/mcp/tools/secret-elicitation-server.d.ts +6 -0
  332. package/dist/mcp/tools/secret-elicitation-server.js +10 -54
  333. package/dist/mcp/tools/secret-elicitation.d.ts +38 -3
  334. package/dist/mcp/tools/secret-elicitation.js +110 -12
  335. package/dist/mcp/tools/secrets.js +186 -9
  336. package/dist/mcp/tools/stop-run.d.ts +2 -0
  337. package/dist/mcp/tools/stop-run.js +82 -0
  338. package/dist/mcp/tools/subagents.js +2 -2
  339. package/dist/mcp/tools/tools.d.ts +2 -2
  340. package/dist/mcp/tools/tools.js +47 -11
  341. package/dist/mcp/transport/stdio-link-host.d.ts +20 -0
  342. package/dist/mcp/transport/stdio-link-host.js +67 -0
  343. package/dist/mcp/transport/streamable-http.d.ts +13 -0
  344. package/dist/mcp/transport/streamable-http.js +41 -1
  345. package/dist/native-worker/composition.d.ts +23 -0
  346. package/dist/native-worker/composition.js +77 -0
  347. package/dist/native-worker/docker-isolation.d.ts +65 -0
  348. package/dist/native-worker/docker-isolation.js +183 -0
  349. package/dist/native-worker/docker-launcher.d.ts +110 -0
  350. package/dist/native-worker/docker-launcher.js +297 -0
  351. package/dist/native-worker/gateway-server.d.ts +77 -0
  352. package/dist/native-worker/gateway-server.js +412 -0
  353. package/dist/native-worker/gateway.d.ts +70 -0
  354. package/dist/native-worker/gateway.js +263 -0
  355. package/dist/native-worker/http-server.d.ts +24 -0
  356. package/dist/native-worker/http-server.js +113 -0
  357. package/dist/native-worker/http-transport.d.ts +17 -0
  358. package/dist/native-worker/http-transport.js +88 -0
  359. package/dist/native-worker/kubernetes-isolation.d.ts +90 -0
  360. package/dist/native-worker/kubernetes-isolation.js +218 -0
  361. package/dist/native-worker/kubernetes-launcher.d.ts +106 -0
  362. package/dist/native-worker/kubernetes-launcher.js +375 -0
  363. package/dist/native-worker/launch.d.ts +62 -0
  364. package/dist/native-worker/launch.js +49 -0
  365. package/dist/native-worker/ledger.d.ts +125 -0
  366. package/dist/native-worker/ledger.js +245 -0
  367. package/dist/native-worker/loopback.d.ts +12 -0
  368. package/dist/native-worker/loopback.js +44 -0
  369. package/dist/native-worker/main.d.ts +8 -0
  370. package/dist/native-worker/main.js +85 -0
  371. package/dist/native-worker/protocol.d.ts +296 -0
  372. package/dist/native-worker/protocol.js +246 -0
  373. package/dist/native-worker/sandbox-executor.d.ts +83 -0
  374. package/dist/native-worker/sandbox-executor.js +284 -0
  375. package/dist/native-worker/stderr-redaction.d.ts +77 -0
  376. package/dist/native-worker/stderr-redaction.js +237 -0
  377. package/dist/native-worker/stdio.d.ts +29 -0
  378. package/dist/native-worker/stdio.js +189 -0
  379. package/dist/native-worker/warm-delivery.d.ts +16 -0
  380. package/dist/native-worker/warm-delivery.js +33 -0
  381. package/dist/native-worker/warm-pool-ledger.d.ts +39 -0
  382. package/dist/native-worker/warm-pool-ledger.js +73 -0
  383. package/dist/native-worker/warm-pool.d.ts +107 -0
  384. package/dist/native-worker/warm-pool.js +283 -0
  385. package/dist/native-worker/worker.d.ts +17 -0
  386. package/dist/native-worker/worker.js +141 -0
  387. package/dist/observability/metrics.js +6 -1
  388. package/dist/providers/auth/authorization-server.d.ts +3 -0
  389. package/dist/providers/auth/index.js +2 -0
  390. package/dist/providers/auth/self-hosted.d.ts +14 -2
  391. package/dist/providers/auth/self-hosted.js +54 -4
  392. package/dist/providers/chat/fake.d.ts +46 -0
  393. package/dist/providers/chat/fake.js +39 -0
  394. package/dist/providers/chat/index.d.ts +4 -0
  395. package/dist/providers/chat/index.js +8 -0
  396. package/dist/providers/chat/slack.d.ts +37 -0
  397. package/dist/providers/chat/slack.js +105 -0
  398. package/dist/providers/chat/types.d.ts +42 -0
  399. package/dist/providers/chat/types.js +19 -0
  400. package/dist/providers/coding-proxy/dependency-cache/eviction.d.ts +29 -0
  401. package/dist/providers/coding-proxy/dependency-cache/eviction.js +87 -0
  402. package/dist/providers/coding-proxy/dependency-cache/key.d.ts +33 -0
  403. package/dist/providers/coding-proxy/dependency-cache/key.js +86 -0
  404. package/dist/providers/coding-proxy/dependency-cache/model-request-seal.d.ts +18 -0
  405. package/dist/providers/coding-proxy/dependency-cache/model-request-seal.js +40 -0
  406. package/dist/providers/coding-proxy/dependency-cache/prisma-store.d.ts +25 -0
  407. package/dist/providers/coding-proxy/dependency-cache/prisma-store.js +131 -0
  408. package/dist/providers/coding-proxy/dependency-cache/service.d.ts +70 -0
  409. package/dist/providers/coding-proxy/dependency-cache/service.js +409 -0
  410. package/dist/providers/coding-proxy/dependency-cache/store.d.ts +85 -0
  411. package/dist/providers/coding-proxy/dependency-cache/store.js +95 -0
  412. package/dist/providers/coding-proxy/hold.d.ts +32 -0
  413. package/dist/providers/coding-proxy/hold.js +86 -0
  414. package/dist/providers/coding-proxy/interrupted.d.ts +70 -0
  415. package/dist/providers/coding-proxy/interrupted.js +104 -0
  416. package/dist/providers/coding-proxy/memory-ledger.d.ts +15 -2
  417. package/dist/providers/coding-proxy/memory-ledger.js +84 -8
  418. package/dist/providers/coding-proxy/metering.d.ts +16 -0
  419. package/dist/providers/coding-proxy/metering.js +27 -4
  420. package/dist/providers/coding-proxy/prisma-ledger.d.ts +15 -2
  421. package/dist/providers/coding-proxy/prisma-ledger.js +179 -67
  422. package/dist/providers/coding-proxy/proxy.d.ts +14 -1
  423. package/dist/providers/coding-proxy/proxy.js +166 -14
  424. package/dist/providers/coding-proxy/registry/audit.d.ts +53 -10
  425. package/dist/providers/coding-proxy/registry/audit.js +94 -17
  426. package/dist/providers/coding-proxy/registry/lockfile-parse-worker.d.ts +36 -0
  427. package/dist/providers/coding-proxy/registry/lockfile-parse-worker.js +67 -0
  428. package/dist/providers/coding-proxy/registry/lockfile-parse.d.ts +16 -0
  429. package/dist/providers/coding-proxy/registry/lockfile-parse.js +110 -0
  430. package/dist/providers/coding-proxy/registry/plan.d.ts +9 -0
  431. package/dist/providers/coding-proxy/registry/plan.js +201 -48
  432. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +11 -5
  433. package/dist/providers/coding-proxy/registry/prisma-store.js +120 -17
  434. package/dist/providers/coding-proxy/registry/service.d.ts +97 -16
  435. package/dist/providers/coding-proxy/registry/service.js +579 -112
  436. package/dist/providers/coding-proxy/registry/store.d.ts +84 -9
  437. package/dist/providers/coding-proxy/registry/store.js +54 -1
  438. package/dist/providers/coding-proxy/runtime.d.ts +4 -0
  439. package/dist/providers/coding-proxy/runtime.js +78 -16
  440. package/dist/providers/coding-proxy/server.d.ts +6 -1
  441. package/dist/providers/coding-proxy/server.js +45 -1
  442. package/dist/providers/coding-proxy/types.d.ts +65 -1
  443. package/dist/providers/datastore/types.d.ts +5 -0
  444. package/dist/providers/engine/types.d.ts +36 -2
  445. package/dist/providers/engine/types.js +39 -0
  446. package/dist/providers/executor/composition.d.ts +31 -1
  447. package/dist/providers/executor/composition.js +81 -8
  448. package/dist/providers/executor/container.d.ts +385 -9
  449. package/dist/providers/executor/container.js +1234 -99
  450. package/dist/providers/executor/dbos-status.d.ts +31 -0
  451. package/dist/providers/executor/dbos-status.js +64 -0
  452. package/dist/providers/executor/dbos.d.ts +91 -0
  453. package/dist/providers/executor/dbos.js +319 -26
  454. package/dist/providers/executor/deferred.d.ts +44 -0
  455. package/dist/providers/executor/deferred.js +100 -0
  456. package/dist/providers/executor/in-process.d.ts +9 -1
  457. package/dist/providers/executor/in-process.js +14 -4
  458. package/dist/providers/executor/linked-issue-reader.d.ts +16 -0
  459. package/dist/providers/executor/linked-issue-reader.js +30 -0
  460. package/dist/providers/executor/routing.d.ts +39 -10
  461. package/dist/providers/executor/routing.js +77 -32
  462. package/dist/providers/executor/types.d.ts +25 -0
  463. package/dist/providers/index.d.ts +2 -1
  464. package/dist/providers/issue-tracker/types.d.ts +17 -0
  465. package/dist/providers/issue-tracker/types.js +39 -1
  466. package/dist/providers/jobs/claude-tool-setup.d.ts +4 -0
  467. package/dist/providers/jobs/claude-tool-setup.js +5 -2
  468. package/dist/providers/jobs/docker-isolation.d.ts +8 -0
  469. package/dist/providers/jobs/docker-isolation.js +17 -0
  470. package/dist/providers/jobs/docker.d.ts +1 -0
  471. package/dist/providers/jobs/docker.js +29 -4
  472. package/dist/providers/jobs/fake-kubernetes-api.d.ts +23 -2
  473. package/dist/providers/jobs/fake-kubernetes-api.js +45 -1
  474. package/dist/providers/jobs/kubernetes-api.d.ts +25 -1
  475. package/dist/providers/jobs/kubernetes-api.js +17 -0
  476. package/dist/providers/jobs/kubernetes-client.d.ts +10 -1
  477. package/dist/providers/jobs/kubernetes-client.js +61 -3
  478. package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +6 -1
  479. package/dist/providers/jobs/kubernetes-dry-run-fixture.js +6 -3
  480. package/dist/providers/jobs/kubernetes-isolation.d.ts +5 -1
  481. package/dist/providers/jobs/kubernetes-isolation.js +23 -1
  482. package/dist/providers/jobs/kubernetes-platform.d.ts +4 -1
  483. package/dist/providers/jobs/kubernetes-platform.js +38 -1
  484. package/dist/providers/jobs/kubernetes-preflight.d.ts +2 -1
  485. package/dist/providers/jobs/kubernetes-preflight.js +4 -2
  486. package/dist/providers/jobs/kubernetes.d.ts +42 -2
  487. package/dist/providers/jobs/kubernetes.js +178 -19
  488. package/dist/providers/jobs/metadata-probes.d.ts +11 -0
  489. package/dist/providers/jobs/metadata-probes.js +13 -0
  490. package/dist/providers/jobs/types.d.ts +42 -0
  491. package/dist/providers/jobs/types.js +13 -1
  492. package/dist/providers/llm/anthropic.js +5 -1
  493. package/dist/providers/llm/bedrock.d.ts +0 -12
  494. package/dist/providers/llm/bedrock.js +4 -0
  495. package/dist/providers/llm/claude-messages.d.ts +3 -1
  496. package/dist/providers/llm/claude-messages.js +4 -1
  497. package/dist/providers/llm/claude-provider.d.ts +2 -2
  498. package/dist/providers/llm/claude-provider.js +2 -2
  499. package/dist/providers/llm/openai.d.ts +2 -2
  500. package/dist/providers/llm/openai.js +6 -2
  501. package/dist/providers/llm/routing.d.ts +2 -2
  502. package/dist/providers/llm/routing.js +2 -2
  503. package/dist/providers/llm/types.d.ts +12 -2
  504. package/dist/providers/review-host/github-events.d.ts +9 -1
  505. package/dist/providers/review-host/github-events.js +34 -12
  506. package/dist/providers/review-host/github.d.ts +39 -2
  507. package/dist/providers/review-host/github.js +274 -29
  508. package/dist/providers/review-host/local.d.ts +8 -4
  509. package/dist/providers/review-host/local.js +49 -31
  510. package/dist/providers/review-host/patch-pages.d.ts +41 -0
  511. package/dist/providers/review-host/patch-pages.js +176 -0
  512. package/dist/providers/review-host/review-format.d.ts +19 -1
  513. package/dist/providers/review-host/review-format.js +38 -7
  514. package/dist/providers/review-host/types.d.ts +157 -2
  515. package/dist/providers/review-host/types.js +8 -0
  516. package/dist/providers/storage/gcs.d.ts +63 -0
  517. package/dist/providers/storage/gcs.js +69 -0
  518. package/dist/providers/storage/index.d.ts +15 -0
  519. package/dist/providers/storage/index.js +14 -0
  520. package/dist/providers/storage/local.d.ts +24 -0
  521. package/dist/providers/storage/local.js +92 -0
  522. package/dist/providers/storage/meter.d.ts +14 -0
  523. package/dist/providers/storage/meter.js +34 -0
  524. package/dist/providers/storage/s3.d.ts +41 -0
  525. package/dist/providers/storage/s3.js +107 -0
  526. package/dist/providers/storage/types.d.ts +37 -3
  527. package/dist/providers/storage/types.js +21 -4
  528. package/dist/providers/vcs/git.d.ts +33 -2
  529. package/dist/providers/vcs/git.js +324 -22
  530. package/dist/providers/vcs/github-remote.d.ts +26 -3
  531. package/dist/providers/vcs/github-remote.js +124 -14
  532. package/dist/providers/vcs/github.d.ts +246 -12
  533. package/dist/providers/vcs/github.js +521 -61
  534. package/dist/providers/vcs/index.js +8 -1
  535. package/dist/providers/vcs/pr-description.d.ts +127 -0
  536. package/dist/providers/vcs/pr-description.js +270 -0
  537. package/dist/providers/vcs/remote.d.ts +33 -1
  538. package/dist/providers/vcs/routing.d.ts +4 -2
  539. package/dist/providers/vcs/routing.js +6 -0
  540. package/dist/providers/vcs/types.d.ts +80 -3
  541. package/dist/quickstart/coding-seed.d.ts +21 -3
  542. package/dist/quickstart/coding-seed.js +283 -3
  543. package/dist/quickstart/images.d.ts +2 -0
  544. package/dist/quickstart/images.js +7 -1
  545. package/dist/quickstart/reviewer-prompt.d.ts +1 -1
  546. package/dist/quickstart/reviewer-prompt.js +6 -6
  547. package/dist/quickstart-images.json +1 -1
  548. package/dist/sandbox/brokered-fetch.d.ts +23 -0
  549. package/dist/sandbox/brokered-fetch.js +37 -0
  550. package/dist/sandbox/generated/zod.bundle.js +62 -1
  551. package/dist/sandbox/generated/zod3.bundle.js +5 -0
  552. package/dist/sandbox/host-functions.d.ts +35 -0
  553. package/dist/sandbox/host-functions.js +150 -91
  554. package/dist/sandbox/prelude.js +7 -1
  555. package/dist/sandbox/run-in-sandbox.d.ts +19 -0
  556. package/dist/sandbox/run-in-sandbox.js +28 -10
  557. package/dist/sandbox/safe-fetch.d.ts +2 -0
  558. package/dist/sandbox/safe-fetch.js +3 -1
  559. package/dist/sandbox/secret-broker-sigv4.d.ts +2 -0
  560. package/dist/sandbox/secret-broker-sigv4.js +49 -0
  561. package/dist/sandbox/secret-broker.d.ts +34 -0
  562. package/dist/sandbox/secret-broker.js +161 -0
  563. package/dist/sandbox/tool-capabilities.d.ts +10 -30
  564. package/dist/sandbox/user-tool.d.ts +14 -0
  565. package/dist/sandbox/user-tool.js +32 -0
  566. package/dist/sandbox/zod-params.d.ts +37 -5
  567. package/dist/sandbox/zod-params.js +181 -26
  568. package/dist/serve.js +25 -0
  569. package/dist/viewer/api-schema.d.ts +372 -2454
  570. package/dist/viewer/api-schema.js +29 -0
  571. package/dist/viewer/build-schemas.js +10 -3
  572. package/dist/viewer/event-bus.d.ts +6 -0
  573. package/dist/viewer/event-bus.js +16 -7
  574. package/dist/viewer/graph.d.ts +5 -2
  575. package/dist/viewer/graph.js +7 -2
  576. package/dist/viewer/http.d.ts +0 -1
  577. package/dist/viewer/http.js +2 -2
  578. package/dist/viewer/infra.js +25 -2
  579. package/dist/viewer/limits.d.ts +4 -0
  580. package/dist/viewer/limits.js +4 -0
  581. package/dist/viewer/openapi.d.ts +15 -0
  582. package/dist/viewer/openapi.js +170 -0
  583. package/dist/wardby-bin.js +10 -0
  584. package/docs/README.md +7 -0
  585. package/docs/agent-recipes.md +114 -27
  586. package/docs/architecture-runtime.md +16 -1
  587. package/docs/code-review-agents.md +689 -45
  588. package/docs/coding-agent-setup.md +602 -12
  589. package/docs/coding-packages.md +1037 -92
  590. package/docs/coding-services.md +30 -13
  591. package/docs/coding-worker-byo-images.md +218 -6
  592. package/docs/coding-worker-isolation.md +173 -44
  593. package/docs/getting-started-gke.md +332 -129
  594. package/docs/getting-started-identity-provider.md +31 -11
  595. package/docs/getting-started.md +14 -4
  596. package/docs/jira-agents.md +22 -9
  597. package/docs/knowledge/blob-not-found-semantics.md +41 -0
  598. package/docs/knowledge/bounded-file-descriptor.md +52 -0
  599. package/docs/knowledge/delegation-admission-slots.md +52 -0
  600. package/docs/knowledge/http-dispatcher-bootstrap.md +61 -0
  601. package/docs/knowledge/import-rename-target.md +52 -0
  602. package/docs/knowledge/index.md +57 -0
  603. package/docs/knowledge/log.md +9 -0
  604. package/docs/knowledge/mcp-cancelled-task-precedence.md +41 -0
  605. package/docs/knowledge/mcp-http-prompt-discovery.md +41 -0
  606. package/docs/knowledge/mcp-schema-module-boundary.md +41 -0
  607. package/docs/knowledge/nullable-tool-uniqueness.md +52 -0
  608. package/docs/knowledge/overview-cli.md +74 -0
  609. package/docs/knowledge/overview-coding-proxy.md +72 -0
  610. package/docs/knowledge/overview-coding.md +72 -0
  611. package/docs/knowledge/overview-config.md +60 -0
  612. package/docs/knowledge/overview-core.md +72 -0
  613. package/docs/knowledge/overview-deploy.md +83 -0
  614. package/docs/knowledge/overview-mcp.md +61 -0
  615. package/docs/knowledge/overview-prisma.md +72 -0
  616. package/docs/knowledge/overview-providers.md +89 -0
  617. package/docs/knowledge/overview-sandbox.md +83 -0
  618. package/docs/knowledge/overview-surfaces.md +83 -0
  619. package/docs/knowledge/private-datastore-shared-boundary.md +54 -0
  620. package/docs/knowledge/proxy-settlement-projections.md +41 -0
  621. package/docs/knowledge/run-pricing-first-writer.md +43 -0
  622. package/docs/knowledge/schedule-claim-transaction.md +41 -0
  623. package/docs/knowledge/secret-entry-replica-contract.md +52 -0
  624. package/docs/knowledge/terminal-run-first-writer.md +41 -0
  625. package/docs/knowledge/transactional-agent-authorization.md +52 -0
  626. package/docs/knowledge/viewer-notify-write-isolation.md +30 -0
  627. package/docs/knowledge/warm-input-publication.md +30 -0
  628. package/docs/knowledge/workspace-staging-failure.md +54 -0
  629. package/docs/knowledge.md +54 -14
  630. package/docs/native-sandbox.md +731 -0
  631. package/docs/observability.md +4 -0
  632. package/docs/release-verification.md +9 -0
  633. package/docs/security-deployment.md +146 -14
  634. package/docs/slack-notifications.md +268 -0
  635. package/docs/tool-secrets.md +284 -0
  636. package/docs/viewer-api.md +46 -5
  637. package/docs/viewer-api.openapi.yml +1404 -0
  638. package/help/admin-viewer.md +29 -3
  639. package/help/agent-recipes.md +27 -0
  640. package/help/architecture-agent.md +11 -6
  641. package/help/brokered-secrets.md +79 -0
  642. package/help/build-claude-code-image.md +131 -0
  643. package/help/build-worker-image.md +6 -7
  644. package/help/builder-agent.md +19 -6
  645. package/help/code-review-agents.md +243 -10
  646. package/help/coding-packages.md +156 -6
  647. package/help/cost-attribution.md +9 -5
  648. package/help/creating-agents.md +14 -1
  649. package/help/dependency-cache.md +143 -0
  650. package/help/deploy-gke.md +23 -9
  651. package/help/deployment-targets.md +25 -14
  652. package/help/errors/budget-exhausted.md +187 -0
  653. package/help/errors/budget-group-exhausted.md +5 -1
  654. package/help/errors/coding-provider-not-configured.md +2 -2
  655. package/help/errors/coding-stream-quota-exceeded.md +33 -0
  656. package/help/errors/coding-workspace-disk-exceeds-limit.md +39 -0
  657. package/help/errors/continuation-closed.md +9 -12
  658. package/help/errors/database-auth-invalid.md +28 -0
  659. package/help/errors/delegation-conflict.md +24 -0
  660. package/help/errors/file-list-truncated.md +45 -0
  661. package/help/errors/full-read-required.md +49 -0
  662. package/help/errors/github-account-not-allowed.md +29 -0
  663. package/help/errors/graph-incomplete.md +43 -0
  664. package/help/errors/graph-limit.md +43 -0
  665. package/help/errors/low-power-disabled.md +29 -0
  666. package/help/errors/low-power-scale-forbidden.md +35 -0
  667. package/help/errors/low-power-wake-timeout.md +30 -0
  668. package/help/errors/merge-conflict.md +49 -0
  669. package/help/errors/mobile-disabled.md +31 -0
  670. package/help/errors/native-sandbox-capacity.md +22 -0
  671. package/help/errors/native-sandbox-deadline-exceeded.md +19 -0
  672. package/help/errors/native-sandbox-docker-failed.md +20 -0
  673. package/help/errors/native-sandbox-gateway-error.md +20 -0
  674. package/help/errors/native-sandbox-gateway-unavailable.md +21 -0
  675. package/help/errors/native-sandbox-image-not-pinned.md +19 -0
  676. package/help/errors/native-sandbox-isolation-mismatch.md +20 -0
  677. package/help/errors/native-sandbox-launch-abandoned.md +21 -0
  678. package/help/errors/native-sandbox-network-unenforced.md +22 -0
  679. package/help/errors/native-sandbox-requires-catalog.md +31 -0
  680. package/help/errors/native-sandbox-unavailable.md +19 -0
  681. package/help/errors/native-sandbox-warm-delivery-failed.md +21 -0
  682. package/help/errors/native-sandbox-worker-exited.md +21 -0
  683. package/help/errors/native-sandbox-worker-lost.md +20 -0
  684. package/help/errors/native-sandbox-worker-unready.md +22 -0
  685. package/help/errors/package-not-allowed-pending.md +52 -0
  686. package/help/errors/package-not-allowed.md +51 -0
  687. package/help/errors/package-refusals.md +43 -0
  688. package/help/errors/patch-cursor-malformed.md +31 -0
  689. package/help/errors/run-lost.md +39 -0
  690. package/help/errors/run-transcript-download.md +44 -0
  691. package/help/errors/secret-broker-body-invalid.md +26 -0
  692. package/help/errors/secret-broker-changed.md +20 -0
  693. package/help/errors/secret-broker-config-invalid.md +30 -0
  694. package/help/errors/secret-broker-conflict.md +27 -0
  695. package/help/errors/secret-broker-destination-denied.md +28 -0
  696. package/help/errors/secret-broker-value-invalid.md +28 -0
  697. package/help/errors/secret-brokered.md +24 -0
  698. package/help/errors/secret-in-value.md +69 -0
  699. package/help/errors/secret-not-brokered.md +25 -0
  700. package/help/errors/slack-auth-failed.md +48 -0
  701. package/help/errors/slack-channel-unreachable.md +39 -0
  702. package/help/errors/slack-not-configured.md +33 -0
  703. package/help/errors/target-merge.md +40 -0
  704. package/help/fix-merge-conflicts.md +73 -0
  705. package/help/getting-started.md +4 -5
  706. package/help/github-app-setup.md +132 -0
  707. package/help/github.md +40 -1
  708. package/help/identity-and-access.md +7 -1
  709. package/help/jira.md +23 -6
  710. package/help/knowledge.md +16 -7
  711. package/help/local-repositories.md +3 -4
  712. package/help/low-power-mode.md +131 -0
  713. package/help/merge-order-check.md +122 -0
  714. package/help/mobile-app.md +54 -0
  715. package/help/native-capabilities.md +29 -1
  716. package/help/native-sandbox.md +150 -0
  717. package/help/observability.md +8 -0
  718. package/help/operating-agents.md +5 -0
  719. package/help/package-audit-mode.md +91 -0
  720. package/help/related-pull-requests.md +80 -16
  721. package/help/repo-instructions-and-skills.md +91 -0
  722. package/help/resume-runs-locally.md +131 -0
  723. package/help/review-fix-rounds.md +43 -1
  724. package/help/sharing-agents.md +59 -0
  725. package/help/slack-notifications.md +76 -0
  726. package/help/status-comments.md +31 -0
  727. package/help/stop-run.md +58 -0
  728. package/help/troubleshooting/budgets.md +28 -0
  729. package/help/troubleshooting/coding-workers.md +99 -7
  730. package/package.json +27 -13
  731. package/prisma/migrations/20261009000003_native_execution_mode/migration.sql +12 -0
  732. package/prisma/migrations/20261009000004_native_gateway_ledger/migration.sql +65 -0
  733. package/prisma/migrations/20261009000005_native_gateway_call_child_agent/migration.sql +5 -0
  734. package/prisma/migrations/20261009000006_run_deferred_start_stop/migration.sql +14 -0
  735. package/prisma/migrations/20261009000007_native_gateway_network_ready/migration.sql +5 -0
  736. package/prisma/migrations/20261010000001_workflow_notifications/migration.sql +99 -0
  737. package/prisma/migrations/20261011000001_native_gateway_session_status_index/migration.sql +2 -0
  738. package/prisma/migrations/20261012000001_native_warm_worker/migration.sql +32 -0
  739. package/prisma/migrations/20261013000001_coding_run_merge_order/migration.sql +2 -0
  740. package/prisma/migrations/20261013000001_secret_broker/migration.sql +17 -0
  741. package/prisma/migrations/20261013000002_deferred_review_request/migration.sql +4 -0
  742. package/prisma/migrations/20261014000000_repo_skills/migration.sql +15 -0
  743. package/prisma/migrations/20261015000001_coding_run_merge_order_index/migration.sql +2 -0
  744. package/prisma/migrations/20261015000002_no_change_rereview/migration.sql +2 -0
  745. package/prisma/migrations/20261016000000_registry_approved_accepted_advisories/migration.sql +3 -0
  746. package/prisma/migrations/20261016000001_mark_ready_when_passing/migration.sql +3 -0
  747. package/prisma/migrations/20261017000000_debug_trace_max_mb/migration.sql +4 -0
  748. package/prisma/migrations/20261018000000_dependency_cache/migration.sql +53 -0
  749. package/prisma/migrations/20261019000000_claude_max_output_tokens/migration.sql +5 -0
  750. package/prisma/migrations/20261021000000_pr_description_hash/migration.sql +4 -0
  751. package/prisma/migrations/20261022000000_dependency_snapshot_validation_metadata/migration.sql +4 -0
  752. package/prisma/migrations/20261023000000_package_audit_mode/migration.sql +27 -0
  753. package/prisma/migrations/20261024000000_budget_events/migration.sql +26 -0
  754. package/prisma/migrations/20261025000000_cache_aware_holds/migration.sql +9 -0
  755. package/prisma/migrations/20261026000000_budget_overrun/migration.sql +4 -0
  756. package/prisma/migrations/20261027000000_coding_run_request/migration.sql +1 -0
  757. package/prisma/migrations/20261028000000_native_gateway_review_reads/migration.sql +6 -0
  758. package/prisma/migrations/20261029000000_lead_survives_deploy/migration.sql +6 -0
  759. package/prisma/migrations/20261030000000_local_review_covered_sha/migration.sql +2 -0
  760. package/prisma/migrations/20261031000000_coding_run_budget_limit/migration.sql +9 -0
  761. package/prisma/migrations/20261101000000_run_transcripts/migration.sql +22 -0
  762. package/prisma/schema.prisma +631 -146
  763. package/dist/sandbox/generated/zod-to-json-schema.bundle.js +0 -5
@@ -1,12 +1,25 @@
1
- import { linkedPullRequestAttribution, openingRunAttribution, RESPONSE_PATH_SNAPSHOT_BUDGET } from "./attribution.js";
1
+ /**
2
+ * Host-neutral routing of code-review host events (GitHub App webhooks today)
3
+ * to the native agents linked to that repository. Adapters normalise their
4
+ * payloads into HostEvent (providers/review-host/github-events.ts); this
5
+ * module decides which agents run. See
6
+ * docs/private/2026-09-25-code-review-host-design.md §6.3.
7
+ */
8
+ import { Prisma } from "#prisma";
9
+ import { linkedPullRequestAttribution, openingRunAttribution, resolveWorkItem, RESPONSE_PATH_SNAPSHOT_BUDGET, } from "./attribution.js";
10
+ import { headStoppedAtBudget, pushingRunInFlight } from "./budget-stopped-head.js";
2
11
  import { checkContinuation, dispatchRun } from "./dispatch.js";
3
12
  import { mentionStatusRow, postMentionStatus } from "./host-status.js";
4
- import { handlePullRequestClosed } from "./issue-bridge.js";
13
+ import { githubIssueKey, handlePullRequestClosed } from "./issue-bridge.js";
5
14
  import { logger } from "./logger.js";
6
- import { MAX_SIBLING_HINTS, openSiblings, SIBLING_GUIDANCE } from "./related-pull-requests.js";
15
+ import { withoutMarkdownCode } from "./markdown-code.js";
16
+ import { maybeMarkReady } from "./mark-ready.js";
17
+ import { syncMergeOrderChecksForPullRequest } from "./merge-order-check.js";
18
+ import { MAX_SIBLING_HINTS, openPullRequestHints, openSiblings, openSiblingsForIssue, runTreeRoot, SIBLING_GUIDANCE, } from "./related-pull-requests.js";
7
19
  import { requiredLevel } from "./repo-access.js";
8
20
  import { composeTaskOverride } from "./untrusted-content.js";
9
21
  import { DEFAULT_KNOWLEDGE_BUNDLE_PATH, parseConcept, RESERVED_BUNDLE_FILES, } from "../knowledge/concept.js";
22
+ import { KNOWLEDGE_CONFIG_MAX_BYTES, KNOWLEDGE_CONFIG_PATH, resolveKnowledgeLocation } from "../knowledge/location.js";
10
23
  import { driftScope } from "../knowledge/relevance.js";
11
24
  import { spanHash } from "../knowledge/span-hash.js";
12
25
  const log = logger.child({ module: "host-events" });
@@ -22,11 +35,14 @@ export const DEFERRED_REVIEW_BATCH = 50;
22
35
  function escapeRegExp(value) {
23
36
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24
37
  }
38
+ /** A `@app review` outside code: quoting the command in code documents it. */
25
39
  export function isReviewCommand(body, mentionHandle) {
26
- return new RegExp(`(^|[^\\w-])@${escapeRegExp(mentionHandle)}\\s+review(?![\\w-])`, "i").test(body);
40
+ return new RegExp(`(^|[^\\w-])@${escapeRegExp(mentionHandle)}\\s+review(?![\\w-])`, "i").test(withoutMarkdownCode(body));
27
41
  }
28
42
  const MAX_BUNDLE_FILES = 200;
29
43
  const BUNDLE_FILE_LINES = 2000;
44
+ /** `.wardby/knowledge.yaml` is one short key; a longer read is refused as too large. */
45
+ const KNOWLEDGE_CONFIG_MAX_LINES = 200;
30
46
  const MAX_LISTED_CHANGES = 200;
31
47
  /** The whole bundle load (listing plus reads) must finish inside GitHub's ~10 s webhook window. */
32
48
  const BUNDLE_LOAD_DEADLINE_MS = 4000;
@@ -40,7 +56,7 @@ const BUNDLE_INCOMPLETE_TEXT = "The knowledge bundle could not be fully read; tr
40
56
  * commits, so they and the affected concept paths travel as untrusted context.
41
57
  * Commit messages and author names are never included.
42
58
  */
43
- export function mergeTaskText(event, scope, bundleComplete = true, citations = new Map()) {
59
+ export function mergeTaskText(event, scope, bundleComplete = true, citations = new Map(), bundlePath = DEFAULT_KNOWLEDGE_BUNDLE_PATH) {
44
60
  const statusOf = (concept) => citations.get(concept) ?? { state: "unverified" };
45
61
  const changed = event.changedPathsComplete
46
62
  ? `Changed files:\n${[
@@ -52,7 +68,7 @@ export function mergeTaskText(event, scope, bundleComplete = true, citations = n
52
68
  : "The changed-file list is incomplete (GitHub includes at most 2048 commits per push and the list is capped at 1000 paths); treat every knowledge concept as possibly affected.";
53
69
  const listed = scope
54
70
  ? `Knowledge concepts whose citations, affects globs, or files changed (${scope.reason}):\n` +
55
- scope.concepts.map((p) => `- ${DEFAULT_KNOWLEDGE_BUNDLE_PATH}/${p} — ${describeStatus(statusOf(p))}`).join("\n")
71
+ scope.concepts.map((p) => `- ${bundlePath}/${p} — ${describeStatus(statusOf(p))}`).join("\n")
56
72
  : null;
57
73
  let context;
58
74
  if (!bundleComplete)
@@ -67,7 +83,7 @@ export function mergeTaskText(event, scope, bundleComplete = true, citations = n
67
83
  if (scope && scope.concepts.length > 0) {
68
84
  const states = scope.concepts.map((p) => statusOf(p).state);
69
85
  const count = (state) => states.filter((x) => x === state).length;
70
- const onlyKnowledge = event.changedPathsComplete && event.changedPaths.every((p) => p.startsWith(`${DEFAULT_KNOWLEDGE_BUNDLE_PATH}/`));
86
+ const onlyKnowledge = event.changedPathsComplete && event.changedPaths.every((p) => p.startsWith(`${bundlePath}/`));
71
87
  summary =
72
88
  `Citation check at ${event.after.slice(0, 12)}: ${count("verified")} of ${states.length} affected concepts verified, ` +
73
89
  `${count("stale")} stale, ${count("unverified")} not verified. Only knowledge files changed: ${onlyKnowledge ? "yes" : "no"}.`;
@@ -97,13 +113,36 @@ function startBudget(ms) {
97
113
  });
98
114
  return { promise, expired: () => expired, clear: () => clearTimeout(timer) };
99
115
  }
116
+ /**
117
+ * `.wardby/knowledge.yaml` at the pushed commit, or null when it does not
118
+ * exist. Reads within the shared budget; a deadline or a host error throws,
119
+ * which resolveKnowledgeLocation turns into the default bundle with a warning.
120
+ */
121
+ async function readKnowledgeConfig(host, event, budget) {
122
+ const read = await Promise.race([
123
+ host.readFile(event.repository, KNOWLEDGE_CONFIG_PATH, event.after, {
124
+ startLine: 1,
125
+ maxLines: KNOWLEDGE_CONFIG_MAX_LINES,
126
+ }),
127
+ budget.promise,
128
+ ]);
129
+ if (read === "deadline")
130
+ throw new Error("the read hit its deadline");
131
+ if (read.kind === "not_found")
132
+ return null;
133
+ if (read.kind !== "file")
134
+ throw new Error("it is not a file");
135
+ if (read.truncated || Buffer.byteLength(read.text, "utf8") > KNOWLEDGE_CONFIG_MAX_BYTES)
136
+ throw new Error(`it is larger than ${KNOWLEDGE_CONFIG_MAX_BYTES} bytes`);
137
+ return read.text;
138
+ }
100
139
  /**
101
140
  * The knowledge bundle at the pushed commit, read with bounded concurrency
102
141
  * under one overall deadline. A failure never fails the route; it yields an
103
142
  * incomplete result so the watcher treats every concept as possibly affected.
104
143
  */
105
- async function loadBundle(host, event, budget) {
106
- const prefix = `${DEFAULT_KNOWLEDGE_BUNDLE_PATH}/`;
144
+ async function loadBundle(host, event, budget, bundlePath) {
145
+ const prefix = `${bundlePath}/`;
107
146
  const concepts = [];
108
147
  const where = { repository: event.repository, after: event.after };
109
148
  let filesRead = 0;
@@ -297,6 +336,12 @@ export function mentionTaskText(event, siblings = []) {
297
336
  if (hints)
298
337
  sections.push(hints);
299
338
  }
339
+ else if (!event.isPullRequest) {
340
+ // An issue mention: the issue's open wardby pull requests, worded as a Jira issue's are.
341
+ const hints = openPullRequestHints(siblings);
342
+ if (hints)
343
+ sections.push(hints);
344
+ }
300
345
  const title = event.subject?.title.replace(/\s+/g, " ").trim().slice(0, MAX_TITLE);
301
346
  const description = event.subject?.body.trim() ? event.subject.body.slice(0, MAX_TASK_BODY) : "";
302
347
  sections.push([
@@ -333,11 +378,54 @@ async function priorRunContinues(deps, event) {
333
378
  const check = await checkContinuation(deps.db, event.priorRunId, event.repository);
334
379
  return check.ok && check.root.pullRequestNumber === event.number;
335
380
  }
381
+ /** The reply to a mention whose author can see the repository but cannot push to it. */
382
+ export const NO_WRITE_ACCESS_BODY = "😕 I can't act on this: a mention starts work only for people with write access to this repository. " +
383
+ "Ask someone with write access to mention me instead.";
384
+ /** Associations that mean the account holds a role on the repository, not just a view of a public one. */
385
+ const ROLE_ASSOCIATIONS = new Set(["OWNER", "MEMBER", "COLLABORATOR"]);
386
+ /**
387
+ * Whether a refused author should be told why. Triage is granted explicitly, so
388
+ * it always counts. GitHub reports `read` for every account on a public
389
+ * repository, so there `read` counts only with a role association; on a private
390
+ * repository `read` means the account was given access (a private organization
391
+ * member is labelled CONTRIBUTOR or NONE). Unknown facts stay silent.
392
+ */
393
+ function explainsRefusal(level, event) {
394
+ if (level === "triage")
395
+ return true;
396
+ if (level !== "read")
397
+ return false;
398
+ return event.repositoryPrivate === true || ROLE_ASSOCIATIONS.has(event.authorAssociation ?? "");
399
+ }
336
400
  export function unknownPriorRunBody(priorRunId) {
337
401
  return (`❌ I can't continue this pull request. Its description names wardby run \`${priorRunId}\`, but this wardby ` +
338
402
  "deployment has no record of that run opening this pull request (another wardby deployment may have opened it). " +
339
403
  "Ask the deployment that opened it, or make the change on this branch by hand.");
340
404
  }
405
+ /**
406
+ * Never names the App's handle: this comment is posted by the App, and a
407
+ * body that mentioned it could be matched as a mention of itself.
408
+ */
409
+ export function mentionDeclinedBody(reason) {
410
+ switch (reason) {
411
+ case "no_responder":
412
+ return (`ℹ️ I didn't start a run: no agent on this repository is set up to answer mentions. ` +
413
+ 'An agent owner can link one with the `mention` trigger (`link_repository`, `triggers: ["mention"]`).');
414
+ case "no_reviewer":
415
+ return (`ℹ️ I didn't start a review: no reviewer agent is linked to this repository. ` +
416
+ "An agent owner can link one with the `pull_request` trigger and a `checkName` (`link_repository`).");
417
+ case "agent_not_authorized":
418
+ return ("ℹ️ I didn't start a run: the agent linked here can no longer use this repository. " +
419
+ "Its owner's GitHub account needs access to it again; they can re-authorize it or re-link the agent.");
420
+ case "pull_request_not_reviewable":
421
+ return "ℹ️ I didn't start a review: this pull request is closed or comes from a fork, and wardby reviews only open pull requests from branches of this repository.";
422
+ }
423
+ }
424
+ /** A review run's task: the head to review, plus any extra context its trigger adds. */
425
+ export function reviewTaskText(repository, prNumber, headSha, context) {
426
+ const task = `Review pull request #${prNumber} in ${repository} (head ${headSha}).`;
427
+ return context ? composeTaskOverride(`${task}\n\n${context.task}`, context.untrustedContext) : task;
428
+ }
341
429
  /** Run states that already cover a commit: a review of it is under way or done. */
342
430
  const COVERING_REVIEW_STATUSES = ["pending", "running", "succeeded"];
343
431
  /**
@@ -360,9 +448,16 @@ async function alreadyReviewed(deps, repository, prNumber, headSha, agentId) {
360
448
  });
361
449
  return prior !== null;
362
450
  }
363
- async function startReviews(deps, host, repository, prNumber, headSha, targets,
451
+ /** Starts one review run per target on a pull request head; returns the started run ids. */
452
+ export async function startReviews(deps, host, repository, prNumber, headSha, targets,
364
453
  /** True for automatic triggers; an explicit re-run or `@wardby review` always runs. */
365
- skipReviewedCommits = false) {
454
+ skipReviewedCommits = false,
455
+ /**
456
+ * Extra context for this review: `task` (written by wardby, trusted) is appended to the
457
+ * review task; `untrustedContext` (anything a model or a person wrote) travels separately,
458
+ * delivered as data.
459
+ */
460
+ context) {
366
461
  const runIds = [];
367
462
  // One lookup per PR, not per reviewer, and none at all when every reviewer
368
463
  // skips this commit: taken at the first actual dispatch.
@@ -404,7 +499,7 @@ skipReviewedCommits = false) {
404
499
  selfDefects: { db: deps.db, issueTrackers: deps.issueTrackers },
405
500
  agentId: target.agentId,
406
501
  trigger: "host_event",
407
- taskOverride: `Review pull request #${prNumber} in ${repository} (head ${headSha}).`,
502
+ taskOverride: reviewTaskText(repository, prNumber, headSha, context),
408
503
  attribution: await linkedAttribution(),
409
504
  afterPersist: checkId
410
505
  ? async (tx, run) => {
@@ -436,6 +531,59 @@ skipReviewedCommits = false) {
436
531
  }
437
532
  return runIds;
438
533
  }
534
+ async function budgetDraftDecision(deps, host, repository, prNumber, headSha,
535
+ /** The pull request's origin, when the caller already reads it once per event. */
536
+ readOrigin) {
537
+ const review = { kind: "review" };
538
+ const read = readOrigin ?? (host.pullRequestOrigin ? () => host.pullRequestOrigin(repository, prNumber) : undefined);
539
+ if (!read)
540
+ return review;
541
+ try {
542
+ const origin = await read();
543
+ if (origin.draft !== true || !origin.markerRunId)
544
+ return review;
545
+ const runId = await pushingRunInFlight(deps.db, { markerRunId: origin.markerRunId, repository });
546
+ if (runId)
547
+ return { kind: "wait", runId: await pushingRunHold(deps.db, runId) };
548
+ const stopped = await headStoppedAtBudget(deps.db, {
549
+ markerRunId: origin.markerRunId,
550
+ repository,
551
+ prNumber,
552
+ headSha,
553
+ });
554
+ return stopped ? { kind: "skip" } : review;
555
+ }
556
+ catch (err) {
557
+ log.warn({ err, repository, prNumber }, "could not read the pull request's coding run; reviewing as usual");
558
+ return review;
559
+ }
560
+ }
561
+ /**
562
+ * Instead of reviewing a budget-stopped draft (budgetDraftDecision `skip`): completes a neutral
563
+ * check per reviewer. No review run, so no RunHostCheck and no fix round. A reviewer that already
564
+ * reviewed this commit (an explicit request) keeps that review's check: a neutral one over it
565
+ * would stand, unreviewed again, once the draft is marked ready (that automatic review skips a
566
+ * reviewed commit).
567
+ */
568
+ async function postSkippedReviewChecks(deps, host, repository, prNumber, headSha, targets) {
569
+ log.info({ repository, prNumber, headSha, agentIds: targets.map((t) => t.agentId) }, "review skipped: the draft's last push stopped at its budget");
570
+ for (const target of targets) {
571
+ try {
572
+ if (await alreadyReviewed(deps, repository, prNumber, headSha, target.agentId))
573
+ continue;
574
+ const { checkId } = await host.startCheck(repository, { headSha, name: target.checkName });
575
+ await host.completeCheck(repository, {
576
+ checkId,
577
+ conclusion: "neutral",
578
+ title: "Skipped: stopped at its budget",
579
+ summary: "The last push came from a run that stopped at its budget before finishing. This draft is reviewed when the work continues or when it is marked ready for review.",
580
+ });
581
+ }
582
+ catch (err) {
583
+ log.warn({ err, repository, prNumber, agentId: target.agentId }, "could not post the skipped review check");
584
+ }
585
+ }
586
+ }
439
587
  /**
440
588
  * CI finished on a pull request's head: re-runs, once per head, each review
441
589
  * that published only a COMMENT while CI there was still running or had not
@@ -471,6 +619,16 @@ async function rereviewAfterCi(deps, host, event, prNumber, reviewerLinks) {
471
619
  // Only reviewers still authorized are claimed, so a denied one keeps its chance for later.
472
620
  const allowed = await authorizedLinks(deps, event, reviewerLinks.filter((l) => waiting.some((r) => r.run.agentId === l.agentId)));
473
621
  const allowedIds = new Set(allowed.map((l) => l.agentId));
622
+ // Decided before the claim, so a re-review not started here is not used up.
623
+ const decision = await budgetDraftDecision(deps, host, event.repository, prNumber, event.headSha);
624
+ if (decision.kind === "wait") {
625
+ log.info({ repository: event.repository, prNumber, headSha: event.headSha, runId: decision.runId }, "CI finished: re-review held while the pull request's coding run is still running");
626
+ return [];
627
+ }
628
+ if (decision.kind === "skip") {
629
+ await postSkippedReviewChecks(deps, host, event.repository, prNumber, event.headSha, reviewTargets(allowed));
630
+ return [];
631
+ }
474
632
  // Claimed per row, so concurrent completions re-run each review only once.
475
633
  const agentIds = new Set();
476
634
  for (const row of waiting.filter((r) => allowedIds.has(r.run.agentId))) {
@@ -590,6 +748,7 @@ async function deferUntilCi(deps, host, key, links) {
590
748
  prNumber: key.prNumber,
591
749
  headSha: key.headSha,
592
750
  agentId: { in: links.map((l) => l.agentId) },
751
+ reason: "ci",
593
752
  },
594
753
  select: { id: true, agentId: true, checkName: true },
595
754
  });
@@ -602,12 +761,28 @@ async function deferUntilCi(deps, host, key, links) {
602
761
  }
603
762
  return { startNow: [], runIds };
604
763
  }
764
+ /**
765
+ * Deferred rows whose pull request's coding run is still in flight (budgetDraftDecision `wait`):
766
+ * they now wait for that run, as for a delegating run (reason "request", leadRunId = the run),
767
+ * and are released when it finishes (startDeferredForRequest from its terminal hook, or the
768
+ * sweep). The run may have finished meanwhile, its release finding no row: released here then.
769
+ */
770
+ async function waitForPushingRun(deps, key, rows, runId) {
771
+ const where = { repository: key.repository, prNumber: key.prNumber, headSha: key.headSha };
772
+ await deps.db.deferredReview.updateMany({
773
+ where: { id: { in: rows.map((r) => r.id) } },
774
+ data: { reason: "request", leadRunId: runId },
775
+ });
776
+ log.info({ ...where, runId }, "deferred review held while the pull request's coding run is still running");
777
+ const run = await deps.db.run.findUnique({ where: { id: runId }, select: { status: true } });
778
+ return !run || !RUN_NOT_FINISHED.has(run.status) ? releaseRequestRows(deps, runId, "request") : [];
779
+ }
605
780
  /**
606
781
  * Starts deferred reviews for one PR head. The head must still be that sha,
607
782
  * open, and not a fork; otherwise every row is dropped (a newer push records
608
783
  * its own). Each row is claimed by deleting it, so a concurrent ci_completed
609
- * delivery or sweep starts each review only once. On `ci`, only reviewers
610
- * still authorized are claimed (the rest wait for the sweep); on `sweep` (the
784
+ * delivery or sweep starts each review only once. On `ci` and `request`, only
785
+ * reviewers still authorized are claimed (the rest wait for the sweep); on `sweep` (the
611
786
  * last attempt) every row is claimed and unauthorized or unlinked reviewers
612
787
  * are dropped.
613
788
  */
@@ -619,11 +794,14 @@ async function startDeferred(deps, host, key, rows, reviewerLinks, mode, reason)
619
794
  log.info({ ...where, state: head.state }, "deferred review dropped: the pull request moved on or closed");
620
795
  return [];
621
796
  }
797
+ const decision = await budgetDraftDecision(deps, host, key.repository, key.prNumber, key.headSha);
798
+ if (decision.kind === "wait")
799
+ return waitForPushingRun(deps, key, rows, decision.runId);
622
800
  const allowed = await authorizedLinks(deps, key, reviewerLinks.filter((l) => rows.some((r) => r.agentId === l.agentId)));
623
801
  const allowedIds = new Set(allowed.map((l) => l.agentId));
624
802
  const targets = [];
625
803
  for (const row of rows) {
626
- if (mode === "ci" && !allowedIds.has(row.agentId))
804
+ if (mode !== "sweep" && !allowedIds.has(row.agentId))
627
805
  continue;
628
806
  const claimed = await deps.db.deferredReview.deleteMany({ where: { id: row.id } });
629
807
  if (claimed.count !== 1)
@@ -637,6 +815,10 @@ async function startDeferred(deps, host, key, rows, reviewerLinks, mode, reason)
637
815
  return [];
638
816
  log.info({ ...where, agentIds: targets.map((t) => t.agentId), reason }, "starting the deferred review");
639
817
  try {
818
+ if (decision.kind === "skip") {
819
+ await postSkippedReviewChecks(deps, host, key.repository, key.prNumber, key.headSha, targets);
820
+ return [];
821
+ }
640
822
  return await startReviews(deps, host, key.repository, key.prNumber, key.headSha, targets, true);
641
823
  }
642
824
  catch (err) {
@@ -647,14 +829,16 @@ async function startDeferred(deps, host, key, rows, reviewerLinks, mode, reason)
647
829
  }
648
830
  /**
649
831
  * ci_completed: starts the reviews deferred on this head (see deferUntilCi),
650
- * unless CI there is still pending (a later completion decides). Never
832
+ * unless CI there is still pending (a later completion decides). Rows still
833
+ * waiting for their delegating run (reason "request") are not CI's to start:
834
+ * startDeferredForRequest hands them over once that run is terminal. Never
651
835
  * throws; a failure is logged and the rows stay for the reconciler sweep.
652
836
  */
653
837
  async function startDeferredAfterCi(deps, host, event, prNumber, reviewerLinks) {
654
838
  try {
655
839
  const key = { provider: event.provider, repository: event.repository, prNumber, headSha: event.headSha };
656
840
  const rows = await deps.db.deferredReview.findMany({
657
- where: key,
841
+ where: { ...key, reason: "ci" },
658
842
  select: { id: true, agentId: true, checkName: true },
659
843
  });
660
844
  if (rows.length === 0)
@@ -676,9 +860,21 @@ async function startDeferredAfterCi(deps, host, event, prNumber, reviewerLinks)
676
860
  * DEFERRED_REVIEW_MAX_AGE_MS, and starts (regardless of CI) those waiting
677
861
  * longer than DEFERRED_REVIEW_MAX_WAIT_MS, at most DEFERRED_REVIEW_BATCH per
678
862
  * pass, oldest first. Covers CI that never reported, ran very long, or whose
679
- * completion webhook was missed. Never throws; returns the runs started.
863
+ * completion webhook was missed. Rows waiting for a delegating run (reason
864
+ * "request") are released the same way once they are that old, but only when
865
+ * that run is terminal (or gone): a lead the reconciler marked lost never
866
+ * reaches its finalizer. Never throws; returns the runs started.
680
867
  */
681
868
  export async function startDeferredReviews(deps, now = new Date()) {
869
+ const providers = Object.keys(deps.hosts);
870
+ const runIds = await sweepCiRows(deps, providers, now);
871
+ // Outside the CI half's try: a failure there never keeps request rows from being released.
872
+ if (providers.length > 0)
873
+ runIds.push(...(await sweepRequestRows(deps, providers, now)));
874
+ return runIds;
875
+ }
876
+ /** The sweep's half for expiry and CI rows: drops rows past the max age, then starts CI rows past the max wait. */
877
+ async function sweepCiRows(deps, providers, now) {
682
878
  const runIds = [];
683
879
  try {
684
880
  const expired = await deps.db.deferredReview.deleteMany({
@@ -686,12 +882,12 @@ export async function startDeferredReviews(deps, now = new Date()) {
686
882
  });
687
883
  if (expired.count > 0)
688
884
  log.info({ count: expired.count }, "dropped deferred reviews older than 24 h");
689
- const providers = Object.keys(deps.hosts);
690
885
  if (providers.length === 0)
691
886
  return runIds;
692
887
  const due = await deps.db.deferredReview.findMany({
693
888
  where: {
694
889
  provider: { in: providers },
890
+ reason: "ci",
695
891
  createdAt: { lte: new Date(now.getTime() - DEFERRED_REVIEW_MAX_WAIT_MS) },
696
892
  },
697
893
  select: {
@@ -732,6 +928,246 @@ export async function startDeferredReviews(deps, now = new Date()) {
732
928
  }
733
929
  return runIds;
734
930
  }
931
+ /** The sweep's half for request rows: releases those older than the max wait whose delegating run is terminal or gone. */
932
+ async function sweepRequestRows(deps, providers, now) {
933
+ const runIds = [];
934
+ try {
935
+ // Joined in the query, so leads still running never fill the batch ahead of finished ones.
936
+ const leads = await deps.db.$queryRaw `
937
+ SELECT DISTINCT d."leadRunId"
938
+ FROM "DeferredReview" d
939
+ LEFT JOIN "Run" r ON r."id" = d."leadRunId"
940
+ WHERE d."reason" = 'request'
941
+ AND d."leadRunId" IS NOT NULL
942
+ AND d."provider" IN (${Prisma.join(providers)})
943
+ AND d."createdAt" <= ${new Date(now.getTime() - DEFERRED_REVIEW_MAX_WAIT_MS)}
944
+ AND (r."id" IS NULL OR r."status"::text NOT IN ('pending', 'running'))
945
+ LIMIT ${DEFERRED_REVIEW_BATCH}`;
946
+ for (const { leadRunId } of leads)
947
+ runIds.push(...(await releaseRequestRows(deps, leadRunId, "sweep")));
948
+ }
949
+ catch (err) {
950
+ log.warn({ err }, "deferred review sweep failed for reviews waiting on a delegating run");
951
+ }
952
+ return runIds;
953
+ }
954
+ /**
955
+ * The run whose finish a review of this pull request waits for: the root of
956
+ * the run tree when the PR's opening coding run was delegated (it has a
957
+ * parent run) and that root is not terminal yet; otherwise null (a
958
+ * non-delegated coding PR, a finished request, or an unknown marker). A lead's
959
+ * delegate_to_* calls block until each child is terminal, so a terminal root
960
+ * means every sibling coding run of the request has finished and opened its
961
+ * pull request. A continuation's marker resolves to its root coding run, then
962
+ * up that run's parents. `repository`, when given, must be the coding run's.
963
+ */
964
+ export async function requestLeadRunId(db, prMarkerRunId, repository) {
965
+ const root = await runTreeRoot(db, prMarkerRunId, repository);
966
+ if (!root || root.isOpener || !RUN_NOT_FINISHED.has(root.status))
967
+ return null;
968
+ return root.id;
969
+ }
970
+ /** Run statuses that mean the delegating run has not finished. */
971
+ const RUN_NOT_FINISHED = new Set(["pending", "running"]);
972
+ /**
973
+ * The run a draft's review waits for while its coding run `pushingRunId` may still be pushing:
974
+ * that run's top-level lead while the lead is pending or running, else the coding run itself.
975
+ * A follow-up's lead is not the one requestLeadRunId finds (that walks up from the pull
976
+ * request's opening run), yet the review should still wait for the whole request (#259); and
977
+ * the lead's finalizer releases it outside every workflow step, where the coding run's own
978
+ * terminal hook, inside the lead's delegation step, could not start a review run.
979
+ */
980
+ async function pushingRunHold(db, pushingRunId) {
981
+ // UNION, not UNION ALL: a parentRunId cycle (never written, but not a constraint) can't recurse forever.
982
+ const rows = await db.$queryRaw `
983
+ WITH RECURSIVE up AS (
984
+ SELECT r."id", r."parentRunId", r."status"::text AS "status" FROM "Run" r WHERE r."id" = ${pushingRunId}
985
+ UNION
986
+ SELECT p."id", p."parentRunId", p."status"::text FROM "Run" p JOIN up u ON p."id" = u."parentRunId"
987
+ )
988
+ SELECT u."id", u."status" FROM up u WHERE u."parentRunId" IS NULL LIMIT 1`;
989
+ const root = rows[0];
990
+ return root && root.id !== pushingRunId && RUN_NOT_FINISHED.has(root.status) ? root.id : pushingRunId;
991
+ }
992
+ /**
993
+ * pr_updated for a pull request a delegated coding run opened, while the
994
+ * request's lead run is still running: records one DeferredReview row per
995
+ * reviewer (reason "request", with or without waitForCi) and starts nothing;
996
+ * the lead's finalizer (startDeferredForRequest) or the sweep starts them.
997
+ * Returns null, to review as without it, for any other pull request, or when
998
+ * the lead cannot be resolved or the deferral cannot be recorded. A draft whose
999
+ * own coding run (root or continuation) is still in flight waits for that run
1000
+ * the same way: it may have pushed this head before recording whether it
1001
+ * stopped at its budget (budgetDraftDecision); its terminal hook releases them.
1002
+ */
1003
+ async function deferUntilRequest(deps, host, key, links,
1004
+ /** The pull request's origin, read at most once per event (shared with the merge order check). */
1005
+ readOrigin) {
1006
+ if (links.length === 0 || !readOrigin)
1007
+ return null;
1008
+ let leadRunId;
1009
+ try {
1010
+ const origin = await readOrigin();
1011
+ if (!origin.markerRunId)
1012
+ return null;
1013
+ leadRunId = await requestLeadRunId(deps.db, origin.markerRunId, key.repository);
1014
+ // A draft's own coding run still in flight may have pushed this head before recording its
1015
+ // result (a stop at its budget among them): wait for it the same way (budgetDraftDecision).
1016
+ if (!leadRunId && origin.draft === true) {
1017
+ const pushing = await pushingRunInFlight(deps.db, {
1018
+ markerRunId: origin.markerRunId,
1019
+ repository: key.repository,
1020
+ });
1021
+ leadRunId = pushing && (await pushingRunHold(deps.db, pushing));
1022
+ }
1023
+ }
1024
+ catch (err) {
1025
+ log.warn({ err, repository: key.repository, prNumber: key.prNumber }, "could not read the pull request's delegating run; reviewing as usual");
1026
+ return null;
1027
+ }
1028
+ if (!leadRunId)
1029
+ return null;
1030
+ const head = { provider: key.provider, repository: key.repository, prNumber: key.prNumber, headSha: key.headSha };
1031
+ try {
1032
+ // Idempotent per head and reviewer, like deferUntilCi.
1033
+ await deps.db.deferredReview.createMany({
1034
+ data: links.map((l) => ({ ...head, agentId: l.agentId, checkName: l.checkName, reason: "request", leadRunId })),
1035
+ skipDuplicates: true,
1036
+ });
1037
+ // A row this head already had for CI waits for the request too: released only when both are done.
1038
+ await deps.db.deferredReview.updateMany({
1039
+ where: { ...head, agentId: { in: links.map((l) => l.agentId) }, reason: "ci" },
1040
+ data: { reason: "request", leadRunId },
1041
+ });
1042
+ }
1043
+ catch (err) {
1044
+ log.warn({ err, repository: key.repository, prNumber: key.prNumber }, "could not record the deferred review; reviewing as usual");
1045
+ return null;
1046
+ }
1047
+ log.info({ ...head, leadRunId, agentIds: links.map((l) => l.agentId) }, "review deferred until the delegating run finishes");
1048
+ // The lead may have finished between the lookup and the insert, its
1049
+ // finalizer finding no row yet: check once more (rows are claimed by
1050
+ // delete, so a racing finalizer starts each review once). A failure leaves
1051
+ // the rows for the sweep.
1052
+ const runIds = [];
1053
+ try {
1054
+ const lead = await deps.db.run.findUnique({ where: { id: leadRunId }, select: { status: true } });
1055
+ if (!lead || !RUN_NOT_FINISHED.has(lead.status)) {
1056
+ runIds.push(...(await releaseRequestRows(deps, leadRunId, "request")));
1057
+ }
1058
+ }
1059
+ catch (err) {
1060
+ log.warn({ err, repository: key.repository, prNumber: key.prNumber, leadRunId }, "could not re-check the delegating run after deferring; the sweep starts the review");
1061
+ }
1062
+ return { runIds };
1063
+ }
1064
+ /**
1065
+ * The delegating run `leadRunId` is terminal (it succeeded, failed, or was
1066
+ * cancelled): releases every review that waited for it. A reviewer linked
1067
+ * with waitForCi whose head's CI is still pending is handed to CI (the row
1068
+ * becomes reason "ci", its max wait counted from now); every other row is
1069
+ * started (claimed by delete, so concurrent releases start each review once).
1070
+ * Called from the lead's finalizer, after the related pull requests sections
1071
+ * were rewritten. Never throws.
1072
+ */
1073
+ export async function startDeferredForRequest(deps, leadRunId) {
1074
+ try {
1075
+ await releaseRequestRows(deps, leadRunId, "request");
1076
+ }
1077
+ catch (err) {
1078
+ log.warn({ err, leadRunId }, "could not start the reviews waiting for the delegating run");
1079
+ }
1080
+ }
1081
+ async function releaseRequestRows(deps, leadRunId, mode) {
1082
+ const rows = await deps.db.deferredReview.findMany({
1083
+ where: { leadRunId, reason: "request" },
1084
+ select: {
1085
+ id: true,
1086
+ provider: true,
1087
+ repository: true,
1088
+ prNumber: true,
1089
+ headSha: true,
1090
+ agentId: true,
1091
+ checkName: true,
1092
+ },
1093
+ orderBy: { createdAt: "asc" },
1094
+ });
1095
+ const groups = new Map();
1096
+ for (const row of rows) {
1097
+ const id = JSON.stringify([row.provider, row.repository, row.prNumber, row.headSha]);
1098
+ groups.set(id, [...(groups.get(id) ?? []), row]);
1099
+ }
1100
+ const runIds = [];
1101
+ for (const group of groups.values()) {
1102
+ const first = group[0];
1103
+ const provider = first.provider;
1104
+ const key = { provider, repository: first.repository, prNumber: first.prNumber, headSha: first.headSha };
1105
+ const host = deps.hosts[provider];
1106
+ if (!host)
1107
+ continue;
1108
+ try {
1109
+ const reviewerLinks = (await loadLinks(deps, provider, key.repository)).filter(isReviewerLink);
1110
+ runIds.push(...(await releaseRequestHead(deps, host, key, group, reviewerLinks, mode)));
1111
+ }
1112
+ catch (err) {
1113
+ log.warn({ err, repository: key.repository, prNumber: key.prNumber, leadRunId }, "could not start a review waiting for the delegating run");
1114
+ }
1115
+ }
1116
+ return runIds;
1117
+ }
1118
+ /** One head's request rows: hands waitForCi rows with CI still pending to CI, starts the rest. */
1119
+ async function releaseRequestHead(deps, host, key, rows, reviewerLinks, mode) {
1120
+ const waitsForCi = new Set(reviewerLinks.filter((l) => l.waitForCi).map((l) => l.agentId));
1121
+ let ciRows = rows.filter((r) => waitsForCi.has(r.agentId));
1122
+ if (ciRows.length > 0 && host.readCi) {
1123
+ let state;
1124
+ try {
1125
+ state = (await host.readCi(key.repository, key.headSha)).state;
1126
+ }
1127
+ catch (err) {
1128
+ log.warn({ err, repository: key.repository, prNumber: key.prNumber }, "could not read CI; reviewing now");
1129
+ }
1130
+ if (state === undefined || !CI_NOT_FINISHED_ON_PUSH.has(state))
1131
+ ciRows = [];
1132
+ }
1133
+ else
1134
+ ciRows = [];
1135
+ const startNow = rows.filter((r) => !ciRows.includes(r));
1136
+ const runIds = startNow.length > 0
1137
+ ? await startDeferred(deps, host, key, startNow, reviewerLinks, mode, "the delegating run finished")
1138
+ : [];
1139
+ if (ciRows.length === 0 || !host.readCi)
1140
+ return runIds;
1141
+ const ids = ciRows.map((r) => r.id);
1142
+ // Only a row still waiting for the request flips: a concurrent release that claimed it already started it.
1143
+ await deps.db.deferredReview.updateMany({
1144
+ where: { id: { in: ids }, reason: "request" },
1145
+ data: { reason: "ci", createdAt: new Date() },
1146
+ });
1147
+ log.info({
1148
+ repository: key.repository,
1149
+ prNumber: key.prNumber,
1150
+ headSha: key.headSha,
1151
+ agentIds: ciRows.map((r) => r.agentId),
1152
+ }, "the delegating run finished; review deferred until CI finishes");
1153
+ // As in deferUntilCi: CI may have finished before the flip, its ci_completed skipping the request row.
1154
+ try {
1155
+ const again = (await host.readCi(key.repository, key.headSha)).state;
1156
+ if (!CI_NOT_FINISHED_ON_PUSH.has(again)) {
1157
+ const handed = await deps.db.deferredReview.findMany({
1158
+ where: { id: { in: ids }, reason: "ci" },
1159
+ select: { id: true, agentId: true, checkName: true },
1160
+ });
1161
+ if (handed.length > 0) {
1162
+ runIds.push(...(await startDeferred(deps, host, key, handed, reviewerLinks, "ci", "CI finished")));
1163
+ }
1164
+ }
1165
+ }
1166
+ catch (err) {
1167
+ log.warn({ err, repository: key.repository, prNumber: key.prNumber }, "could not re-read CI after the delegating run finished; the sweep starts the review");
1168
+ }
1169
+ return runIds;
1170
+ }
735
1171
  export async function routeHostEvent(event, deps) {
736
1172
  const none = { runIds: [], followUps: [] };
737
1173
  if (event.kind === "pr_closed") {
@@ -742,7 +1178,18 @@ export async function routeHostEvent(event, deps) {
742
1178
  number: event.prNumber,
743
1179
  merged: event.merged,
744
1180
  });
745
- return none;
1181
+ // The merge order checks of the sets this PR is a step of, after the response. Never throws.
1182
+ if (!deps.hosts[event.provider]?.upsertNamedCheck)
1183
+ return none;
1184
+ const pr = { repository: event.repository, number: event.prNumber };
1185
+ return {
1186
+ runIds: [],
1187
+ followUps: [
1188
+ () => syncMergeOrderChecksForPullRequest(deps, pr, {
1189
+ closed: { ...pr, merged: event.merged },
1190
+ }),
1191
+ ],
1192
+ };
746
1193
  }
747
1194
  const host = deps.hosts[event.provider];
748
1195
  if (!host)
@@ -767,11 +1214,14 @@ export async function routeHostEvent(event, deps) {
767
1214
  const budget = startBudget(BUNDLE_LOAD_DEADLINE_MS);
768
1215
  let taskOverride;
769
1216
  try {
770
- const { concepts, complete } = await loadBundle(host, event, budget);
1217
+ const { bundlePath, warning } = await resolveKnowledgeLocation(() => readKnowledgeConfig(host, event, budget));
1218
+ if (warning)
1219
+ log.warn({ repository: event.repository, after: event.after }, warning);
1220
+ const { concepts, complete } = await loadBundle(host, event, budget, bundlePath);
771
1221
  const scope = driftScope({
772
1222
  changedPaths: event.changedPaths,
773
1223
  changedPathsComplete: event.changedPathsComplete,
774
- bundlePath: DEFAULT_KNOWLEDGE_BUNDLE_PATH,
1224
+ bundlePath,
775
1225
  concepts,
776
1226
  });
777
1227
  // Only the concepts the merge affects are checked, and only against a bundle that was fully read.
@@ -779,7 +1229,7 @@ export async function routeHostEvent(event, deps) {
779
1229
  const citations = complete
780
1230
  ? await verifyCitations(host, event, concepts.filter((c) => affected.has(c.path)), budget)
781
1231
  : new Map();
782
- taskOverride = mergeTaskText(event, scope, complete, citations);
1232
+ taskOverride = mergeTaskText(event, scope, complete, citations, bundlePath);
783
1233
  }
784
1234
  finally {
785
1235
  budget.clear();
@@ -816,30 +1266,62 @@ export async function routeHostEvent(event, deps) {
816
1266
  return { runIds, followUps: [] };
817
1267
  }
818
1268
  case "pr_updated": {
819
- if (event.isFork || reviewerLinks.length === 0)
1269
+ if (event.isFork)
820
1270
  return none;
1271
+ // Read at most once per event, by the request deferral and the merge order check alike.
1272
+ let origin;
1273
+ const readOrigin = host.pullRequestOrigin
1274
+ ? () => (origin ??= host.pullRequestOrigin(event.repository, event.prNumber))
1275
+ : undefined;
1276
+ // A new head needs the merge order check re-posted (check runs are per commit), on every
1277
+ // path below, a review held for the delegating run included. After the response; never throws.
1278
+ const followUps = host.upsertNamedCheck && readOrigin
1279
+ ? [
1280
+ () => syncMergeOrderChecksForPullRequest(deps, { repository: event.repository, number: event.prNumber }, { origin: { repository: event.repository, number: event.prNumber, read: readOrigin } }),
1281
+ ]
1282
+ : [];
1283
+ if (reviewerLinks.length === 0)
1284
+ return { runIds: [], followUps };
821
1285
  const allowed = await authorizedLinks(deps, event, reviewerLinks);
822
1286
  if (allowed.length === 0)
823
- return none;
1287
+ return { runIds: [], followUps };
1288
+ // A PR a delegated coding run opened is reviewed once the whole request
1289
+ // finished, so its sibling PRs exist and the related section is current (#259).
1290
+ const request = await deferUntilRequest(deps, host, event, allowed, readOrigin);
1291
+ if (request)
1292
+ return { runIds: request.runIds, followUps };
824
1293
  // Reviewers linked with waitForCi review this head only once its CI finished.
825
1294
  const deferral = await deferUntilCi(deps, host, event, allowed.filter((l) => l.waitForCi));
826
1295
  const startNow = new Set(deferral.startNow);
827
1296
  const reviewers = reviewTargets(allowed.filter((l) => !l.waitForCi || startNow.has(l)));
828
1297
  const runIds = [...deferral.runIds];
829
1298
  if (reviewers.length > 0) {
830
- runIds.push(...(await startReviews(deps, host, event.repository, event.prNumber, event.headSha, reviewers, true)));
1299
+ // A coding run still in flight was already waited for (deferUntilRequest); should
1300
+ // recording that have failed, the head is reviewed as usual.
1301
+ const decision = await budgetDraftDecision(deps, host, event.repository, event.prNumber, event.headSha, readOrigin);
1302
+ if (decision.kind === "skip") {
1303
+ await postSkippedReviewChecks(deps, host, event.repository, event.prNumber, event.headSha, reviewers);
1304
+ }
1305
+ else {
1306
+ runIds.push(...(await startReviews(deps, host, event.repository, event.prNumber, event.headSha, reviewers, true)));
1307
+ }
831
1308
  }
832
- return { runIds, followUps: [] };
1309
+ return { runIds, followUps };
833
1310
  }
834
1311
  case "ci_completed": {
835
1312
  if (reviewerLinks.length === 0)
836
1313
  return none;
837
1314
  const runIds = [];
1315
+ // CI going green may be the last thing a pull request waited for (#287); evaluated
1316
+ // after the response (a host read per PR could otherwise exceed the webhook timeout).
1317
+ const followUps = event.prNumbers.map((prNumber) => async () => {
1318
+ await maybeMarkReady({ db: deps.db, hosts: deps.hosts, log }, event.repository, prNumber, event.provider);
1319
+ });
838
1320
  for (const prNumber of event.prNumbers) {
839
1321
  runIds.push(...(await rereviewAfterCi(deps, host, event, prNumber, reviewerLinks)));
840
1322
  runIds.push(...(await startDeferredAfterCi(deps, host, event, prNumber, reviewerLinks)));
841
1323
  }
842
- return { runIds, followUps: [] };
1324
+ return { runIds, followUps };
843
1325
  }
844
1326
  case "check_rerun": {
845
1327
  const owners = reviewerLinks.filter((l) => l.checkName === event.checkName);
@@ -862,12 +1344,15 @@ export async function routeHostEvent(event, deps) {
862
1344
  const reviewCommand = event.isPullRequest && isReviewCommand(event.body, deps.mentionHandle);
863
1345
  const responderLink = links.find((l) => l.access === "write" && l.triggers.includes("mention"));
864
1346
  const candidates = reviewCommand ? reviewerLinks : responderLink ? [responderLink] : [];
865
- if (candidates.length === 0)
1347
+ // Explanations off and nothing to start: say nothing, and skip the author lookup.
1348
+ if (candidates.length === 0 && deps.mentionExplanations === false)
866
1349
  return none;
867
1350
  // H5-3: a mention drives a write-capable agent holding its owner's
868
1351
  // tools and secrets, so its author needs real push access to the
869
1352
  // repository (author_association, checked upstream, is only a
870
- // pre-filter). Checked by the author's immutable id.
1353
+ // pre-filter). Checked by the author's immutable id. Checked before
1354
+ // anything is said back: someone without write access gets no reply at
1355
+ // all, so a mention can never be used to probe what is linked here.
871
1356
  const author = await deps.repoAccess.authorizeHostUser({
872
1357
  provider: event.provider,
873
1358
  repository: event.repository,
@@ -876,15 +1361,54 @@ export async function routeHostEvent(event, deps) {
876
1361
  });
877
1362
  if (!author.ok) {
878
1363
  log.info({ repository: event.repository, number: event.number, reason: author.reason, level: author.level }, "mention ignored: its author lacks write access");
879
- return none;
1364
+ // Someone who can read the repository gets told why; a stranger on a public repository (GitHub reports `read` for everyone) learns nothing.
1365
+ if (author.reason !== "insufficient_permission" || !explainsRefusal(author.level, event)) {
1366
+ return none;
1367
+ }
1368
+ const refuse = async () => {
1369
+ await host.acknowledge(event.repository, event.comment, "refused").catch((err) => {
1370
+ log.warn({ err, repository: event.repository, number: event.number }, "could not add the reaction");
1371
+ });
1372
+ await host
1373
+ .comment(event.repository, {
1374
+ number: event.number,
1375
+ body: NO_WRITE_ACCESS_BODY,
1376
+ ...(event.replyToReviewCommentId ? { replyToReviewCommentId: event.replyToReviewCommentId } : {}),
1377
+ })
1378
+ .catch((err) => {
1379
+ log.warn({ err, repository: event.repository, number: event.number }, "could not post the refusal");
1380
+ });
1381
+ };
1382
+ return { runIds: [], followUps: [refuse] };
880
1383
  }
1384
+ // The author may use wardby here, so tell them why nothing started
1385
+ // instead of leaving the mention unanswered.
1386
+ const decline = (reason) => {
1387
+ log.info({ repository: event.repository, number: event.number, reason }, "mention declined");
1388
+ if (deps.mentionExplanations === false)
1389
+ return none;
1390
+ const reply = async () => {
1391
+ await host
1392
+ .comment(event.repository, {
1393
+ number: event.number,
1394
+ body: mentionDeclinedBody(reason),
1395
+ ...(event.replyToReviewCommentId ? { replyToReviewCommentId: event.replyToReviewCommentId } : {}),
1396
+ })
1397
+ .catch((err) => {
1398
+ log.warn({ err, repository: event.repository, number: event.number }, "could not post the explanation");
1399
+ });
1400
+ };
1401
+ return { runIds: [], followUps: [reply] };
1402
+ };
1403
+ if (candidates.length === 0)
1404
+ return decline(reviewCommand ? "no_reviewer" : "no_responder");
881
1405
  const allowed = await authorizedLinks(deps, event, candidates);
882
1406
  if (allowed.length === 0)
883
- return none;
1407
+ return decline("agent_not_authorized");
884
1408
  if (reviewCommand) {
885
1409
  const head = await host.pullRequestHead(event.repository, event.number);
886
1410
  if (head.isFork || head.state !== "open")
887
- return none;
1411
+ return decline("pull_request_not_reviewable");
888
1412
  return {
889
1413
  runIds: await startReviews(deps, host, event.repository, event.number, head.headSha, reviewTargets(allowed)),
890
1414
  followUps: [react],
@@ -909,9 +1433,15 @@ export async function routeHostEvent(event, deps) {
909
1433
  return { runIds: [], followUps: [react, reply] };
910
1434
  }
911
1435
  // Seeded from the PR's validated marker run: the request it belongs to. Stored state only.
912
- const siblings = event.isPullRequest && event.priorRunId
913
- ? await openSiblings(deps.db, event.priorRunId, { repository: event.repository, number: event.number })
914
- : [];
1436
+ // An issue mention is seeded from the issue's recorded pull requests instead.
1437
+ const siblings = event.isPullRequest
1438
+ ? event.priorRunId
1439
+ ? await openSiblings(deps.db, event.priorRunId, { repository: event.repository, number: event.number })
1440
+ : []
1441
+ : await openSiblingsForIssue(deps.db, {
1442
+ provider: "github",
1443
+ key: githubIssueKey(event.repository, event.number),
1444
+ });
915
1445
  const dispatched = await dispatchRun({
916
1446
  db: deps.db,
917
1447
  executor: deps.executor,
@@ -921,7 +1451,11 @@ export async function routeHostEvent(event, deps) {
921
1451
  taskOverride: mentionTaskText(event, siblings),
922
1452
  attribution: event.isPullRequest
923
1453
  ? await linkedPullRequestAttribution(deps.db, deps.issueTrackers, { codeProvider: host.provider, repository: event.repository, number: event.number }, RESPONSE_PATH_SNAPSHOT_BUDGET)
924
- : undefined,
1454
+ : // A mention on an issue is that issue's own run tree, like a Jira issue event's.
1455
+ {
1456
+ source: "issue_event",
1457
+ item: await resolveWorkItem(deps.db, deps.issueTrackers, "github", githubIssueKey(event.repository, event.number), RESPONSE_PATH_SNAPSHOT_BUDGET),
1458
+ },
925
1459
  // Where to report, written with the run: even if this instance dies
926
1460
  // before the follow-up below, the run's outcome still gets a comment.
927
1461
  afterPersist: async (tx, run) => {