@wardby/cli 0.6.0 → 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 (576) hide show
  1. package/deploy/local/docker-compose.quickstart-coding.yml +13 -0
  2. package/dist/claude-coding-worker/driver.d.ts +21 -3
  3. package/dist/claude-coding-worker/driver.js +125 -17
  4. package/dist/claude-coding-worker/main.js +34 -6
  5. package/dist/claude-coding-worker/sdk.js +12 -1
  6. package/dist/claude-coding-worker/tool-socket.d.ts +27 -0
  7. package/dist/claude-coding-worker/tool-socket.js +111 -0
  8. package/dist/claude-coding-worker/transcript.d.ts +18 -0
  9. package/dist/claude-coding-worker/transcript.js +77 -0
  10. package/dist/cli-help.d.ts +1 -1
  11. package/dist/cli-help.js +1 -0
  12. package/dist/cli.js +24 -10
  13. package/dist/coding/continuation-wording.d.ts +35 -0
  14. package/dist/coding/continuation-wording.js +73 -0
  15. package/dist/coding/linked-issue-note.d.ts +23 -0
  16. package/dist/coding/linked-issue-note.js +47 -0
  17. package/dist/coding/merge-wording.d.ts +28 -0
  18. package/dist/coding/merge-wording.js +78 -0
  19. package/dist/coding/pr-description-note.d.ts +9 -0
  20. package/dist/coding/pr-description-note.js +32 -0
  21. package/dist/coding/profile.d.ts +46 -126
  22. package/dist/coding/profile.js +45 -4
  23. package/dist/coding/protected-path-wording.d.ts +8 -0
  24. package/dist/coding/protected-path-wording.js +1 -1
  25. package/dist/coding/protocol.d.ts +264 -346
  26. package/dist/coding/protocol.js +235 -12
  27. package/dist/coding/redact.d.ts +3 -0
  28. package/dist/coding/redact.js +12 -0
  29. package/dist/coding/registry/allowlist.d.ts +12 -0
  30. package/dist/coding/registry/allowlist.js +14 -1
  31. package/dist/coding/registry/audit-mode.d.ts +153 -0
  32. package/dist/coding/registry/audit-mode.js +304 -0
  33. package/dist/coding/registry/lockfile-shared.d.ts +36 -0
  34. package/dist/coding/registry/lockfile-shared.js +128 -0
  35. package/dist/coding/registry/lockfiles.js +1 -1
  36. package/dist/coding/registry/npm-lockfile-formats.d.ts +11 -0
  37. package/dist/coding/registry/npm-lockfile-formats.js +41 -0
  38. package/dist/coding/registry/npm-lockfile.d.ts +8 -4
  39. package/dist/coding/registry/npm-lockfile.js +4 -6
  40. package/dist/coding/registry/npm.js +78 -8
  41. package/dist/coding/registry/pnpm-lockfile.d.ts +17 -0
  42. package/dist/coding/registry/pnpm-lockfile.js +189 -0
  43. package/dist/coding/registry/pypi-plan.d.ts +50 -0
  44. package/dist/coding/registry/pypi-plan.js +406 -0
  45. package/dist/coding/registry/pypi-requirements.d.ts +58 -0
  46. package/dist/coding/registry/pypi-requirements.js +155 -0
  47. package/dist/coding/registry/pypi.d.ts +3 -27
  48. package/dist/coding/registry/pypi.js +40 -154
  49. package/dist/coding/registry/refusal-hints.d.ts +20 -0
  50. package/dist/coding/registry/refusal-hints.js +61 -0
  51. package/dist/coding/registry/refusal-wording.d.ts +11 -0
  52. package/dist/coding/registry/refusal-wording.js +56 -0
  53. package/dist/coding/registry/report.d.ts +70 -8
  54. package/dist/coding/registry/report.js +112 -5
  55. package/dist/coding/registry/types.d.ts +58 -8
  56. package/dist/coding/registry/yarn-lockfile.d.ts +30 -0
  57. package/dist/coding/registry/yarn-lockfile.js +367 -0
  58. package/dist/coding/services/catalog.d.ts +37 -351
  59. package/dist/coding/transcript/claude-code.d.ts +13 -0
  60. package/dist/coding/transcript/claude-code.js +273 -0
  61. package/dist/coding/transcript/download-protocol.d.ts +30 -0
  62. package/dist/coding/transcript/download-protocol.js +18 -0
  63. package/dist/coding/transcript/format.d.ts +20 -0
  64. package/dist/coding/transcript/format.js +32 -0
  65. package/dist/coding/transcript/recorder.d.ts +51 -0
  66. package/dist/coding/transcript/recorder.js +225 -0
  67. package/dist/coding/transcript/resume/checkout.d.ts +31 -0
  68. package/dist/coding/transcript/resume/checkout.js +203 -0
  69. package/dist/coding/transcript/resume/command.d.ts +17 -0
  70. package/dist/coding/transcript/resume/command.js +176 -0
  71. package/dist/coding/transcript/resume/download.d.ts +20 -0
  72. package/dist/coding/transcript/resume/download.js +153 -0
  73. package/dist/coding/transcript/resume/launch.d.ts +38 -0
  74. package/dist/coding/transcript/resume/launch.js +128 -0
  75. package/dist/coding/transcript/resume/session-file.d.ts +13 -0
  76. package/dist/coding/transcript/resume/session-file.js +79 -0
  77. package/dist/coding/transcript/resume/stand-in.d.ts +11 -0
  78. package/dist/coding/transcript/resume/stand-in.js +49 -0
  79. package/dist/coding-worker/artifact.d.ts +8 -3
  80. package/dist/coding-worker/artifact.js +3 -1
  81. package/dist/coding-worker/debug-trace.d.ts +6 -5
  82. package/dist/coding-worker/debug-trace.js +13 -18
  83. package/dist/coding-worker/dependency-cache.d.mts +229 -0
  84. package/dist/coding-worker/dependency-cache.mjs +2123 -0
  85. package/dist/coding-worker/driver.d.ts +4 -3
  86. package/dist/coding-worker/driver.js +116 -38
  87. package/dist/coding-worker/errors.d.ts +1 -0
  88. package/dist/coding-worker/errors.js +32 -0
  89. package/dist/coding-worker/task-note.d.ts +11 -0
  90. package/dist/coding-worker/task-note.js +13 -0
  91. package/dist/coding-worker/types.d.ts +11 -0
  92. package/dist/config/dependency-cache.d.ts +23 -0
  93. package/dist/config/dependency-cache.js +32 -0
  94. package/dist/config/providers.d.ts +12 -3
  95. package/dist/config/providers.js +55 -5
  96. package/dist/config/test-simulators.d.ts +82 -0
  97. package/dist/config/test-simulators.js +305 -0
  98. package/dist/core/async-context.d.ts +2 -0
  99. package/dist/core/async-context.js +12 -0
  100. package/dist/core/budget-events.d.ts +108 -0
  101. package/dist/core/budget-events.js +346 -0
  102. package/dist/core/budget-groups.d.ts +50 -1
  103. package/dist/core/budget-groups.js +236 -27
  104. package/dist/core/budget-stopped-head.d.ts +46 -0
  105. package/dist/core/budget-stopped-head.js +81 -0
  106. package/dist/core/budget-topup.d.ts +51 -0
  107. package/dist/core/budget-topup.js +156 -0
  108. package/dist/core/budget-wording.d.ts +16 -2
  109. package/dist/core/budget-wording.js +40 -2
  110. package/dist/core/coding-queue.d.ts +2 -1
  111. package/dist/core/coding-queue.js +7 -0
  112. package/dist/core/cron.d.ts +19 -1
  113. package/dist/core/cron.js +17 -0
  114. package/dist/core/datastores.js +1 -1
  115. package/dist/core/deferred-coding.d.ts +14 -0
  116. package/dist/core/deferred-coding.js +62 -0
  117. package/dist/core/delegation-child.d.ts +25 -0
  118. package/dist/core/delegation-child.js +37 -0
  119. package/dist/core/dispatch.d.ts +42 -0
  120. package/dist/core/dispatch.js +131 -56
  121. package/dist/core/engine-native.js +1 -1
  122. package/dist/core/error-code.d.ts +38 -0
  123. package/dist/core/error-code.js +168 -0
  124. package/dist/core/grants.d.ts +19 -6
  125. package/dist/core/grants.js +55 -6
  126. package/dist/core/host-events.d.ts +18 -2
  127. package/dist/core/host-events.js +284 -24
  128. package/dist/core/host-status.d.ts +85 -3
  129. package/dist/core/host-status.js +298 -20
  130. package/dist/core/issue-bridge.d.ts +50 -6
  131. package/dist/core/issue-bridge.js +124 -33
  132. package/dist/core/issue-events.d.ts +1 -1
  133. package/dist/core/issue-events.js +4 -10
  134. package/dist/core/issue-status.d.ts +1 -1
  135. package/dist/core/issue-status.js +54 -26
  136. package/dist/core/logger.d.ts +5 -0
  137. package/dist/core/logger.js +14 -1
  138. package/dist/core/lost-run-children.d.ts +6 -0
  139. package/dist/core/lost-run-children.js +32 -0
  140. package/dist/core/mark-ready.d.ts +34 -0
  141. package/dist/core/mark-ready.js +152 -0
  142. package/dist/core/markdown-code.d.ts +8 -0
  143. package/dist/core/markdown-code.js +107 -0
  144. package/dist/core/memory-tools.js +7 -0
  145. package/dist/core/merge-order-check.d.ts +2 -1
  146. package/dist/core/merge-order-check.js +8 -1
  147. package/dist/core/notification-templates.d.ts +1 -7
  148. package/dist/core/notification-templates.js +36 -2
  149. package/dist/core/pull-request-state-sync.js +2 -2
  150. package/dist/core/reconciler.d.ts +4 -3
  151. package/dist/core/reconciler.js +8 -1
  152. package/dist/core/related-pull-requests.d.ts +11 -3
  153. package/dist/core/related-pull-requests.js +27 -14
  154. package/dist/core/reply-parts.d.ts +21 -0
  155. package/dist/core/reply-parts.js +73 -0
  156. package/dist/core/review-fix.d.ts +1 -1
  157. package/dist/core/review-fix.js +2 -1
  158. package/dist/core/review-host-checks.d.ts +1 -1
  159. package/dist/core/review-host-tools.d.ts +3 -0
  160. package/dist/core/review-host-tools.js +99 -13
  161. package/dist/core/review-reads.d.ts +55 -0
  162. package/dist/core/review-reads.js +233 -0
  163. package/dist/core/run-status.d.ts +12 -0
  164. package/dist/core/run-status.js +12 -0
  165. package/dist/core/runner.d.ts +80 -34
  166. package/dist/core/runner.js +439 -144
  167. package/dist/core/schedule-start.d.ts +12 -0
  168. package/dist/core/schedule-start.js +14 -0
  169. package/dist/core/secret-broker-config.d.ts +10 -74
  170. package/dist/core/secret-guard.d.ts +46 -0
  171. package/dist/core/secret-guard.js +104 -0
  172. package/dist/core/secret-guarded-stores.d.ts +12 -0
  173. package/dist/core/secret-guarded-stores.js +41 -0
  174. package/dist/core/secret-spans.d.ts +50 -0
  175. package/dist/core/secret-spans.js +94 -0
  176. package/dist/core/stop-run.d.ts +46 -0
  177. package/dist/core/stop-run.js +61 -0
  178. package/dist/core/workflow-events.d.ts +14 -2
  179. package/dist/core/workflow-events.js +4 -0
  180. package/dist/core/workflow-run-events.d.ts +5 -3
  181. package/dist/core/workflow-run-events.js +43 -8
  182. package/dist/core/zod-json-schema.d.ts +4 -0
  183. package/dist/core/zod-json-schema.js +39 -0
  184. package/dist/core/zod3-window.d.ts +35 -0
  185. package/dist/core/zod3-window.js +124 -0
  186. package/dist/generated/prisma/browser.d.ts +34 -3
  187. package/dist/generated/prisma/client.d.ts +34 -3
  188. package/dist/generated/prisma/commonInputTypes.d.ts +220 -60
  189. package/dist/generated/prisma/enums.d.ts +10 -0
  190. package/dist/generated/prisma/enums.js +8 -0
  191. package/dist/generated/prisma/internal/class.d.ts +44 -0
  192. package/dist/generated/prisma/internal/class.js +4 -4
  193. package/dist/generated/prisma/internal/prismaNamespace.d.ts +428 -9
  194. package/dist/generated/prisma/internal/prismaNamespace.js +92 -1
  195. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +95 -0
  196. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +92 -1
  197. package/dist/generated/prisma/models/Agent.d.ts +106 -8
  198. package/dist/generated/prisma/models/AgentRepository.d.ts +42 -1
  199. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +229 -1
  200. package/dist/generated/prisma/models/CodingProxyRequest.d.ts +207 -3
  201. package/dist/generated/prisma/models/CodingProxySession.d.ts +114 -1
  202. package/dist/generated/prisma/models/CodingRun.d.ts +1637 -1
  203. package/dist/generated/prisma/models/DependencySnapshot.d.ts +1668 -0
  204. package/dist/generated/prisma/models/DependencySnapshot.js +1 -0
  205. package/dist/generated/prisma/models/IssuePullRequest.d.ts +6 -2
  206. package/dist/generated/prisma/models/LocalReview.d.ts +40 -1
  207. package/dist/generated/prisma/models/NativeGatewayCall.d.ts +39 -1
  208. package/dist/generated/prisma/models/NativeGatewaySession.d.ts +35 -1
  209. package/dist/generated/prisma/models/RegistryAllowance.d.ts +41 -1
  210. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +75 -1
  211. package/dist/generated/prisma/models/RegistryAuditRecord.d.ts +1364 -0
  212. package/dist/generated/prisma/models/RegistryAuditRecord.js +1 -0
  213. package/dist/generated/prisma/models/Run.d.ts +738 -2
  214. package/dist/generated/prisma/models/RunBudgetEvent.d.ts +1211 -0
  215. package/dist/generated/prisma/models/RunBudgetEvent.js +1 -0
  216. package/dist/generated/prisma/models/RunTranscript.d.ts +1339 -0
  217. package/dist/generated/prisma/models/RunTranscript.js +1 -0
  218. package/dist/generated/prisma/models.d.ts +4 -0
  219. package/dist/help-index.json +1320 -69
  220. package/dist/import/create.js +32 -3
  221. package/dist/import/neutral-schema.d.ts +50 -281
  222. package/dist/import/neutral-schema.js +5 -4
  223. package/dist/knowledge/check.d.ts +1 -1
  224. package/dist/knowledge/cli.d.ts +11 -1
  225. package/dist/knowledge/cli.js +37 -4
  226. package/dist/knowledge/concept.d.ts +3 -19
  227. package/dist/knowledge/location.d.ts +31 -0
  228. package/dist/knowledge/location.js +78 -0
  229. package/dist/mcp/auth/access.d.ts +10 -2
  230. package/dist/mcp/auth/access.js +15 -1
  231. package/dist/mcp/auth/ownership.d.ts +7 -7
  232. package/dist/mcp/auth/self-hosted/credentials.d.ts +3 -3
  233. package/dist/mcp/auth/self-hosted/session.d.ts +2 -2
  234. package/dist/mcp/host-events/github-ingress.d.ts +2 -0
  235. package/dist/mcp/host-events/github-ingress.js +2 -1
  236. package/dist/mcp/index.d.ts +6 -0
  237. package/dist/mcp/index.js +58 -10
  238. package/dist/mcp/server.d.ts +13 -0
  239. package/dist/mcp/server.js +33 -14
  240. package/dist/mcp/tools/agents.d.ts +4 -0
  241. package/dist/mcp/tools/agents.js +198 -16
  242. package/dist/mcp/tools/datastore.js +17 -8
  243. package/dist/mcp/tools/grants.js +9 -3
  244. package/dist/mcp/tools/memory.js +11 -1
  245. package/dist/mcp/tools/notification-channels.js +2 -2
  246. package/dist/mcp/tools/packages.d.ts +2 -0
  247. package/dist/mcp/tools/packages.js +195 -0
  248. package/dist/mcp/tools/repositories.js +18 -5
  249. package/dist/mcp/tools/run-access.d.ts +16 -0
  250. package/dist/mcp/tools/run-access.js +25 -0
  251. package/dist/mcp/tools/run-transcript-download.d.ts +30 -0
  252. package/dist/mcp/tools/run-transcript-download.js +129 -0
  253. package/dist/mcp/tools/run-transcript.d.ts +19 -0
  254. package/dist/mcp/tools/run-transcript.js +70 -0
  255. package/dist/mcp/tools/runs.js +34 -12
  256. package/dist/mcp/tools/scheduling.js +3 -1
  257. package/dist/mcp/tools/secret-elicitation-form.js +5 -0
  258. package/dist/mcp/tools/secret-elicitation-server.d.ts +6 -0
  259. package/dist/mcp/tools/secret-elicitation-server.js +10 -61
  260. package/dist/mcp/tools/secrets.js +2 -2
  261. package/dist/mcp/tools/stop-run.d.ts +2 -0
  262. package/dist/mcp/tools/stop-run.js +82 -0
  263. package/dist/mcp/tools/subagents.js +2 -2
  264. package/dist/mcp/tools/tools.d.ts +1 -1
  265. package/dist/mcp/tools/tools.js +25 -9
  266. package/dist/mcp/transport/stdio-link-host.d.ts +20 -0
  267. package/dist/mcp/transport/stdio-link-host.js +67 -0
  268. package/dist/mcp/transport/streamable-http.d.ts +13 -0
  269. package/dist/mcp/transport/streamable-http.js +41 -1
  270. package/dist/native-worker/docker-launcher.d.ts +38 -2
  271. package/dist/native-worker/docker-launcher.js +65 -5
  272. package/dist/native-worker/gateway-server.d.ts +41 -3
  273. package/dist/native-worker/gateway-server.js +124 -27
  274. package/dist/native-worker/gateway.d.ts +27 -1
  275. package/dist/native-worker/gateway.js +70 -4
  276. package/dist/native-worker/http-server.d.ts +3 -1
  277. package/dist/native-worker/http-server.js +11 -7
  278. package/dist/native-worker/http-transport.js +1 -1
  279. package/dist/native-worker/kubernetes-launcher.d.ts +7 -3
  280. package/dist/native-worker/kubernetes-launcher.js +43 -3
  281. package/dist/native-worker/launch.d.ts +18 -1
  282. package/dist/native-worker/launch.js +6 -1
  283. package/dist/native-worker/ledger.d.ts +18 -2
  284. package/dist/native-worker/ledger.js +57 -4
  285. package/dist/native-worker/loopback.js +3 -8
  286. package/dist/native-worker/main.d.ts +2 -1
  287. package/dist/native-worker/main.js +14 -3
  288. package/dist/native-worker/protocol.d.ts +129 -577
  289. package/dist/native-worker/protocol.js +47 -1
  290. package/dist/native-worker/sandbox-executor.d.ts +17 -2
  291. package/dist/native-worker/sandbox-executor.js +78 -4
  292. package/dist/native-worker/stderr-redaction.d.ts +77 -0
  293. package/dist/native-worker/stderr-redaction.js +237 -0
  294. package/dist/native-worker/stdio.d.ts +1 -1
  295. package/dist/native-worker/stdio.js +6 -5
  296. package/dist/native-worker/warm-pool.d.ts +2 -2
  297. package/dist/native-worker/warm-pool.js +4 -4
  298. package/dist/native-worker/worker.js +28 -2
  299. package/dist/observability/metrics.js +6 -1
  300. package/dist/providers/auth/self-hosted.d.ts +1 -1
  301. package/dist/providers/coding-proxy/dependency-cache/eviction.d.ts +29 -0
  302. package/dist/providers/coding-proxy/dependency-cache/eviction.js +87 -0
  303. package/dist/providers/coding-proxy/dependency-cache/key.d.ts +33 -0
  304. package/dist/providers/coding-proxy/dependency-cache/key.js +86 -0
  305. package/dist/providers/coding-proxy/dependency-cache/model-request-seal.d.ts +18 -0
  306. package/dist/providers/coding-proxy/dependency-cache/model-request-seal.js +40 -0
  307. package/dist/providers/coding-proxy/dependency-cache/prisma-store.d.ts +25 -0
  308. package/dist/providers/coding-proxy/dependency-cache/prisma-store.js +131 -0
  309. package/dist/providers/coding-proxy/dependency-cache/service.d.ts +70 -0
  310. package/dist/providers/coding-proxy/dependency-cache/service.js +409 -0
  311. package/dist/providers/coding-proxy/dependency-cache/store.d.ts +85 -0
  312. package/dist/providers/coding-proxy/dependency-cache/store.js +95 -0
  313. package/dist/providers/coding-proxy/hold.d.ts +32 -0
  314. package/dist/providers/coding-proxy/hold.js +86 -0
  315. package/dist/providers/coding-proxy/interrupted.d.ts +70 -0
  316. package/dist/providers/coding-proxy/interrupted.js +104 -0
  317. package/dist/providers/coding-proxy/memory-ledger.d.ts +15 -2
  318. package/dist/providers/coding-proxy/memory-ledger.js +84 -8
  319. package/dist/providers/coding-proxy/metering.d.ts +16 -0
  320. package/dist/providers/coding-proxy/metering.js +27 -4
  321. package/dist/providers/coding-proxy/prisma-ledger.d.ts +15 -2
  322. package/dist/providers/coding-proxy/prisma-ledger.js +179 -67
  323. package/dist/providers/coding-proxy/proxy.d.ts +14 -1
  324. package/dist/providers/coding-proxy/proxy.js +166 -14
  325. package/dist/providers/coding-proxy/registry/audit.d.ts +53 -10
  326. package/dist/providers/coding-proxy/registry/audit.js +94 -17
  327. package/dist/providers/coding-proxy/registry/lockfile-parse-worker.d.ts +36 -0
  328. package/dist/providers/coding-proxy/registry/lockfile-parse-worker.js +67 -0
  329. package/dist/providers/coding-proxy/registry/lockfile-parse.d.ts +16 -0
  330. package/dist/providers/coding-proxy/registry/lockfile-parse.js +110 -0
  331. package/dist/providers/coding-proxy/registry/plan.d.ts +9 -0
  332. package/dist/providers/coding-proxy/registry/plan.js +201 -48
  333. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +11 -5
  334. package/dist/providers/coding-proxy/registry/prisma-store.js +120 -17
  335. package/dist/providers/coding-proxy/registry/service.d.ts +97 -16
  336. package/dist/providers/coding-proxy/registry/service.js +579 -112
  337. package/dist/providers/coding-proxy/registry/store.d.ts +84 -9
  338. package/dist/providers/coding-proxy/registry/store.js +54 -1
  339. package/dist/providers/coding-proxy/runtime.d.ts +4 -0
  340. package/dist/providers/coding-proxy/runtime.js +78 -16
  341. package/dist/providers/coding-proxy/server.d.ts +6 -1
  342. package/dist/providers/coding-proxy/server.js +45 -1
  343. package/dist/providers/coding-proxy/types.d.ts +65 -1
  344. package/dist/providers/datastore/types.d.ts +5 -0
  345. package/dist/providers/engine/types.d.ts +17 -2
  346. package/dist/providers/engine/types.js +17 -0
  347. package/dist/providers/executor/composition.d.ts +10 -3
  348. package/dist/providers/executor/composition.js +47 -7
  349. package/dist/providers/executor/container.d.ts +361 -8
  350. package/dist/providers/executor/container.js +1136 -91
  351. package/dist/providers/executor/dbos-status.d.ts +31 -0
  352. package/dist/providers/executor/dbos-status.js +64 -0
  353. package/dist/providers/executor/dbos.d.ts +50 -1
  354. package/dist/providers/executor/dbos.js +207 -20
  355. package/dist/providers/executor/deferred.d.ts +12 -24
  356. package/dist/providers/executor/deferred.js +10 -29
  357. package/dist/providers/executor/in-process.d.ts +9 -1
  358. package/dist/providers/executor/in-process.js +14 -4
  359. package/dist/providers/executor/linked-issue-reader.d.ts +16 -0
  360. package/dist/providers/executor/linked-issue-reader.js +30 -0
  361. package/dist/providers/executor/routing.d.ts +2 -0
  362. package/dist/providers/executor/routing.js +4 -0
  363. package/dist/providers/executor/types.d.ts +13 -0
  364. package/dist/providers/index.d.ts +2 -1
  365. package/dist/providers/issue-tracker/types.d.ts +17 -0
  366. package/dist/providers/issue-tracker/types.js +39 -1
  367. package/dist/providers/jobs/claude-tool-setup.d.ts +4 -0
  368. package/dist/providers/jobs/claude-tool-setup.js +5 -2
  369. package/dist/providers/jobs/docker-isolation.d.ts +8 -0
  370. package/dist/providers/jobs/docker-isolation.js +17 -0
  371. package/dist/providers/jobs/docker.d.ts +1 -0
  372. package/dist/providers/jobs/docker.js +27 -2
  373. package/dist/providers/jobs/fake-kubernetes-api.d.ts +2 -0
  374. package/dist/providers/jobs/fake-kubernetes-api.js +3 -0
  375. package/dist/providers/jobs/kubernetes-api.d.ts +8 -0
  376. package/dist/providers/jobs/kubernetes-api.js +10 -0
  377. package/dist/providers/jobs/kubernetes-client.js +10 -1
  378. package/dist/providers/jobs/kubernetes-isolation.d.ts +2 -0
  379. package/dist/providers/jobs/kubernetes-isolation.js +17 -1
  380. package/dist/providers/jobs/kubernetes.d.ts +6 -1
  381. package/dist/providers/jobs/kubernetes.js +105 -16
  382. package/dist/providers/jobs/types.d.ts +33 -0
  383. package/dist/providers/jobs/types.js +13 -1
  384. package/dist/providers/llm/anthropic.js +5 -1
  385. package/dist/providers/llm/bedrock.d.ts +0 -12
  386. package/dist/providers/llm/bedrock.js +4 -0
  387. package/dist/providers/llm/claude-messages.d.ts +3 -1
  388. package/dist/providers/llm/claude-messages.js +4 -1
  389. package/dist/providers/llm/claude-provider.d.ts +2 -2
  390. package/dist/providers/llm/claude-provider.js +2 -2
  391. package/dist/providers/llm/openai.d.ts +2 -2
  392. package/dist/providers/llm/openai.js +6 -2
  393. package/dist/providers/llm/routing.d.ts +2 -2
  394. package/dist/providers/llm/routing.js +2 -2
  395. package/dist/providers/llm/types.d.ts +12 -2
  396. package/dist/providers/review-host/github-events.d.ts +9 -1
  397. package/dist/providers/review-host/github-events.js +34 -12
  398. package/dist/providers/review-host/github.d.ts +29 -2
  399. package/dist/providers/review-host/github.js +224 -29
  400. package/dist/providers/review-host/local.d.ts +8 -4
  401. package/dist/providers/review-host/local.js +49 -31
  402. package/dist/providers/review-host/patch-pages.d.ts +41 -0
  403. package/dist/providers/review-host/patch-pages.js +176 -0
  404. package/dist/providers/review-host/review-format.d.ts +19 -1
  405. package/dist/providers/review-host/review-format.js +38 -7
  406. package/dist/providers/review-host/types.d.ts +134 -2
  407. package/dist/providers/review-host/types.js +8 -0
  408. package/dist/providers/storage/gcs.d.ts +63 -0
  409. package/dist/providers/storage/gcs.js +69 -0
  410. package/dist/providers/storage/index.d.ts +15 -0
  411. package/dist/providers/storage/index.js +14 -0
  412. package/dist/providers/storage/local.d.ts +24 -0
  413. package/dist/providers/storage/local.js +92 -0
  414. package/dist/providers/storage/meter.d.ts +14 -0
  415. package/dist/providers/storage/meter.js +34 -0
  416. package/dist/providers/storage/s3.d.ts +41 -0
  417. package/dist/providers/storage/s3.js +107 -0
  418. package/dist/providers/storage/types.d.ts +37 -3
  419. package/dist/providers/storage/types.js +21 -4
  420. package/dist/providers/vcs/git.d.ts +33 -2
  421. package/dist/providers/vcs/git.js +324 -22
  422. package/dist/providers/vcs/github-remote.d.ts +26 -3
  423. package/dist/providers/vcs/github-remote.js +124 -14
  424. package/dist/providers/vcs/github.d.ts +214 -6
  425. package/dist/providers/vcs/github.js +447 -45
  426. package/dist/providers/vcs/index.js +8 -1
  427. package/dist/providers/vcs/pr-description.d.ts +127 -0
  428. package/dist/providers/vcs/pr-description.js +270 -0
  429. package/dist/providers/vcs/remote.d.ts +33 -1
  430. package/dist/providers/vcs/routing.d.ts +4 -2
  431. package/dist/providers/vcs/routing.js +6 -0
  432. package/dist/providers/vcs/types.d.ts +80 -3
  433. package/dist/quickstart/coding-seed.d.ts +14 -3
  434. package/dist/quickstart/coding-seed.js +213 -4
  435. package/dist/quickstart/reviewer-prompt.d.ts +1 -1
  436. package/dist/quickstart/reviewer-prompt.js +6 -6
  437. package/dist/quickstart-images.json +1 -1
  438. package/dist/sandbox/generated/zod.bundle.js +62 -1
  439. package/dist/sandbox/generated/zod3.bundle.js +5 -0
  440. package/dist/sandbox/host-functions.d.ts +1 -0
  441. package/dist/sandbox/host-functions.js +6 -11
  442. package/dist/sandbox/secret-broker.js +3 -4
  443. package/dist/sandbox/tool-capabilities.d.ts +10 -30
  444. package/dist/sandbox/zod-params.d.ts +37 -5
  445. package/dist/sandbox/zod-params.js +181 -26
  446. package/dist/serve.js +8 -1
  447. package/dist/viewer/api-schema.d.ts +349 -2627
  448. package/dist/viewer/api-schema.js +7 -0
  449. package/dist/viewer/build-schemas.js +6 -2
  450. package/dist/viewer/openapi.js +2 -4
  451. package/dist/wardby-bin.js +10 -0
  452. package/docs/agent-recipes.md +58 -20
  453. package/docs/code-review-agents.md +551 -41
  454. package/docs/coding-agent-setup.md +489 -7
  455. package/docs/coding-packages.md +1033 -90
  456. package/docs/coding-services.md +27 -12
  457. package/docs/coding-worker-byo-images.md +20 -0
  458. package/docs/coding-worker-isolation.md +105 -30
  459. package/docs/getting-started-gke.md +124 -137
  460. package/docs/getting-started-identity-provider.md +10 -1
  461. package/docs/getting-started.md +9 -0
  462. package/docs/jira-agents.md +17 -8
  463. package/docs/knowledge/blob-not-found-semantics.md +41 -0
  464. package/docs/knowledge/bounded-file-descriptor.md +52 -0
  465. package/docs/knowledge/delegation-admission-slots.md +52 -0
  466. package/docs/knowledge/http-dispatcher-bootstrap.md +61 -0
  467. package/docs/knowledge/import-rename-target.md +52 -0
  468. package/docs/knowledge/index.md +57 -0
  469. package/docs/knowledge/log.md +9 -0
  470. package/docs/knowledge/mcp-cancelled-task-precedence.md +41 -0
  471. package/docs/knowledge/mcp-http-prompt-discovery.md +41 -0
  472. package/docs/knowledge/mcp-schema-module-boundary.md +41 -0
  473. package/docs/knowledge/nullable-tool-uniqueness.md +52 -0
  474. package/docs/knowledge/overview-cli.md +74 -0
  475. package/docs/knowledge/overview-coding-proxy.md +72 -0
  476. package/docs/knowledge/overview-coding.md +72 -0
  477. package/docs/knowledge/overview-config.md +60 -0
  478. package/docs/knowledge/overview-core.md +72 -0
  479. package/docs/knowledge/overview-deploy.md +83 -0
  480. package/docs/knowledge/overview-mcp.md +61 -0
  481. package/docs/knowledge/overview-prisma.md +72 -0
  482. package/docs/knowledge/overview-providers.md +89 -0
  483. package/docs/knowledge/overview-sandbox.md +83 -0
  484. package/docs/knowledge/overview-surfaces.md +83 -0
  485. package/docs/knowledge/private-datastore-shared-boundary.md +54 -0
  486. package/docs/knowledge/proxy-settlement-projections.md +41 -0
  487. package/docs/knowledge/run-pricing-first-writer.md +43 -0
  488. package/docs/knowledge/schedule-claim-transaction.md +41 -0
  489. package/docs/knowledge/secret-entry-replica-contract.md +52 -0
  490. package/docs/knowledge/terminal-run-first-writer.md +41 -0
  491. package/docs/knowledge/transactional-agent-authorization.md +52 -0
  492. package/docs/knowledge/viewer-notify-write-isolation.md +30 -0
  493. package/docs/knowledge/warm-input-publication.md +30 -0
  494. package/docs/knowledge/workspace-staging-failure.md +54 -0
  495. package/docs/knowledge.md +54 -14
  496. package/docs/native-sandbox.md +119 -23
  497. package/docs/observability.md +4 -0
  498. package/docs/security-deployment.md +33 -2
  499. package/docs/slack-notifications.md +35 -19
  500. package/docs/tool-secrets.md +42 -10
  501. package/docs/viewer-api.openapi.yml +1 -1
  502. package/help/admin-viewer.md +24 -2
  503. package/help/architecture-agent.md +11 -6
  504. package/help/build-worker-image.md +3 -1
  505. package/help/builder-agent.md +19 -6
  506. package/help/code-review-agents.md +177 -6
  507. package/help/coding-packages.md +156 -6
  508. package/help/cost-attribution.md +9 -5
  509. package/help/creating-agents.md +14 -1
  510. package/help/dependency-cache.md +143 -0
  511. package/help/deploy-gke.md +15 -8
  512. package/help/errors/budget-exhausted.md +187 -0
  513. package/help/errors/budget-group-exhausted.md +5 -1
  514. package/help/errors/coding-stream-quota-exceeded.md +33 -0
  515. package/help/errors/coding-workspace-disk-exceeds-limit.md +39 -0
  516. package/help/errors/continuation-closed.md +9 -12
  517. package/help/errors/delegation-conflict.md +24 -0
  518. package/help/errors/file-list-truncated.md +45 -0
  519. package/help/errors/full-read-required.md +49 -0
  520. package/help/errors/github-account-not-allowed.md +4 -0
  521. package/help/errors/graph-incomplete.md +43 -0
  522. package/help/errors/graph-limit.md +43 -0
  523. package/help/errors/merge-conflict.md +49 -0
  524. package/help/errors/native-sandbox-gateway-error.md +20 -0
  525. package/help/errors/native-sandbox-launch-abandoned.md +21 -0
  526. package/help/errors/native-sandbox-worker-exited.md +3 -2
  527. package/help/errors/package-not-allowed-pending.md +52 -0
  528. package/help/errors/package-not-allowed.md +51 -0
  529. package/help/errors/package-refusals.md +43 -0
  530. package/help/errors/patch-cursor-malformed.md +31 -0
  531. package/help/errors/run-lost.md +39 -0
  532. package/help/errors/run-transcript-download.md +44 -0
  533. package/help/errors/secret-in-value.md +69 -0
  534. package/help/errors/target-merge.md +40 -0
  535. package/help/fix-merge-conflicts.md +73 -0
  536. package/help/github-app-setup.md +132 -0
  537. package/help/github.md +22 -0
  538. package/help/identity-and-access.md +2 -0
  539. package/help/jira.md +23 -6
  540. package/help/knowledge.md +16 -7
  541. package/help/local-repositories.md +1 -1
  542. package/help/low-power-mode.md +7 -1
  543. package/help/merge-order-check.md +5 -0
  544. package/help/native-capabilities.md +29 -1
  545. package/help/native-sandbox.md +15 -1
  546. package/help/operating-agents.md +5 -0
  547. package/help/package-audit-mode.md +91 -0
  548. package/help/related-pull-requests.md +10 -7
  549. package/help/resume-runs-locally.md +131 -0
  550. package/help/review-fix-rounds.md +33 -1
  551. package/help/sharing-agents.md +59 -0
  552. package/help/slack-notifications.md +19 -5
  553. package/help/status-comments.md +31 -0
  554. package/help/stop-run.md +58 -0
  555. package/help/troubleshooting/budgets.md +28 -0
  556. package/help/troubleshooting/coding-workers.md +78 -7
  557. package/package.json +17 -10
  558. package/prisma/migrations/20261016000000_registry_approved_accepted_advisories/migration.sql +3 -0
  559. package/prisma/migrations/20261016000001_mark_ready_when_passing/migration.sql +3 -0
  560. package/prisma/migrations/20261017000000_debug_trace_max_mb/migration.sql +4 -0
  561. package/prisma/migrations/20261018000000_dependency_cache/migration.sql +53 -0
  562. package/prisma/migrations/20261019000000_claude_max_output_tokens/migration.sql +5 -0
  563. package/prisma/migrations/20261021000000_pr_description_hash/migration.sql +4 -0
  564. package/prisma/migrations/20261022000000_dependency_snapshot_validation_metadata/migration.sql +4 -0
  565. package/prisma/migrations/20261023000000_package_audit_mode/migration.sql +27 -0
  566. package/prisma/migrations/20261024000000_budget_events/migration.sql +26 -0
  567. package/prisma/migrations/20261025000000_cache_aware_holds/migration.sql +9 -0
  568. package/prisma/migrations/20261026000000_budget_overrun/migration.sql +4 -0
  569. package/prisma/migrations/20261027000000_coding_run_request/migration.sql +1 -0
  570. package/prisma/migrations/20261028000000_native_gateway_review_reads/migration.sql +6 -0
  571. package/prisma/migrations/20261029000000_lead_survives_deploy/migration.sql +6 -0
  572. package/prisma/migrations/20261030000000_local_review_covered_sha/migration.sql +2 -0
  573. package/prisma/migrations/20261031000000_coding_run_budget_limit/migration.sql +9 -0
  574. package/prisma/migrations/20261101000000_run_transcripts/migration.sql +22 -0
  575. package/prisma/schema.prisma +311 -97
  576. package/dist/sandbox/generated/zod-to-json-schema.bundle.js +0 -5
@@ -4,13 +4,13 @@ export const LEVELS = {
4
4
  budget_group: ["read", "use", "write"],
5
5
  };
6
6
  /**
7
- * Highest level an "everyone" grant may carry: never write. Everyone-write
8
- * on an agent would bring back the old public-mutable model (anyone
9
- * rewriting the prompt of an agent that holds its owner's tools and
10
- * secrets). Also applied when a stored everyone row is read, so a row
11
- * written around grant_access can't exceed it either.
7
+ * Highest level an "everyone" grant may carry. Agents may be public-write
8
+ * (anyone signed in can edit them) but only while they hold none of their
9
+ * owner's bindings: see agentOwnerBindings and the guard in grant_access and
10
+ * the binding tools. Otherwise anyone could rewrite the prompt of an agent
11
+ * that holds its owner's secrets.
12
12
  */
13
- export const EVERYONE_MAX = { agent: "execute", tool: "use", budget_group: "use" };
13
+ export const EVERYONE_MAX = { agent: "write", tool: "use", budget_group: "use" };
14
14
  /** Types grant_access/revoke_access accept today (Phase 1: agents only). */
15
15
  export const SHAREABLE_TYPES = ["agent"];
16
16
  export const EVERYONE_KEY = "everyone";
@@ -124,3 +124,52 @@ export async function deleteGrantsFor(db, type, resourceId) {
124
124
  const { count } = await db.resourceGrant.deleteMany({ where: { resourceType: type, resourceId } });
125
125
  return count;
126
126
  }
127
+ /**
128
+ * The owner-only bindings an agent holds, by kind (empty = none). An agent
129
+ * may be public-write only while this is empty; binding a secret, datastore,
130
+ * repository, sub-agent or owner-granted tool capability to a public-write
131
+ * agent is refused for the same reason. A coding profile counts: its
132
+ * repository is the owner's GitHub access, and the prompt is part of every
133
+ * coding task.
134
+ */
135
+ export async function agentOwnerBindings(db, agentId) {
136
+ const [secrets, datastores, repositories, subagents, capabilityTools, codingProfiles] = await Promise.all([
137
+ db.agentSecret.count({ where: { agentId } }),
138
+ db.agentDatastore.count({ where: { agentId } }),
139
+ db.agentRepository.count({ where: { agentId } }),
140
+ db.agentSubAgent.count({ where: { parentAgentId: agentId } }),
141
+ // The owner's attach_tool stamps its consent even when it grants nothing:
142
+ // only an attachment that carries a capability counts.
143
+ db.agentTool.count({
144
+ where: {
145
+ agentId,
146
+ capabilitiesGrantedById: { not: null },
147
+ NOT: {
148
+ allowedSecrets: { equals: [] },
149
+ allowedDatastorePrefixes: { equals: [] },
150
+ allowedHosts: { equals: [] },
151
+ allowedSharedDatastorePrefixes: { equals: {} },
152
+ },
153
+ },
154
+ }),
155
+ db.codingAgentProfile.count({ where: { agentId } }),
156
+ ]);
157
+ return Object.entries({
158
+ secrets,
159
+ datastores,
160
+ repositories,
161
+ "sub-agents": subagents,
162
+ "tool capabilities": capabilityTools,
163
+ "a coding repository": codingProfiles,
164
+ })
165
+ .filter(([, count]) => count > 0)
166
+ .map(([kind]) => kind);
167
+ }
168
+ /** True when everyone holds write on the agent. */
169
+ export async function isPublicWritable(db, agentId) {
170
+ const row = await db.resourceGrant.findFirst({
171
+ where: { resourceType: "agent", resourceId: agentId, granteeKey: EVERYONE_KEY },
172
+ select: { level: true },
173
+ });
174
+ return row !== null && atLeast("agent", row.level, "write");
175
+ }
@@ -13,7 +13,7 @@ import { type OpenSibling } from "./related-pull-requests.js";
13
13
  import { type RepoAccessGate } from "./repo-access.js";
14
14
  import { type DriftScope } from "../knowledge/relevance.js";
15
15
  export type { HostEvent };
16
- export type HostEventDb = Pick<PrismaClient, "agentRepository" | "deferredReview" | "agent" | "run" | "runHostCheck" | "runHostStatus" | "codingRun" | "task" | "webhook" | "budgetGroup" | "issuePullRequest" | "workItem" | "runAttribution" | "agentIssueProject" | "$transaction" | "$queryRaw">;
16
+ export type HostEventDb = Pick<PrismaClient, "runBudgetEvent" | "agentRepository" | "deferredReview" | "agent" | "run" | "runHostCheck" | "runHostStatus" | "codingRun" | "registryFetch" | "registryAuditRecord" | "task" | "webhook" | "budgetGroup" | "issuePullRequest" | "workItem" | "runAttribution" | "agentIssueProject" | "$transaction" | "$queryRaw">;
17
17
  export interface RouteHostEventDeps {
18
18
  db: HostEventDb;
19
19
  executor: Executor;
@@ -24,6 +24,12 @@ export interface RouteHostEventDeps {
24
24
  repoAccess: RepoAccessGate;
25
25
  /** Issue trackers for moving/commenting on the issue a closed PR was opened for; absent → pr_closed is ignored. */
26
26
  issueTrackers?: IssueTrackerRegistry;
27
+ /**
28
+ * False (WARDBY_MENTION_EXPLANATIONS=false): a mention that starts nothing gets no
29
+ * reply saying why. The reply is a comment anyone who can see the repository reads,
30
+ * so a deployment may keep what is linked there to itself. Absent = true.
31
+ */
32
+ mentionExplanations?: boolean;
27
33
  }
28
34
  /** What starting a review needs: everything routing needs except the mention handle. */
29
35
  export type ReviewStartDeps = Omit<RouteHostEventDeps, "mentionHandle">;
@@ -38,6 +44,7 @@ export interface RouteResult {
38
44
  /** Cosmetic work to do after the webhook response is sent (reactions, status comments). */
39
45
  followUps: Array<() => Promise<void>>;
40
46
  }
47
+ /** A `@app review` outside code: quoting the command in code documents it. */
41
48
  export declare function isReviewCommand(body: string, mentionHandle: string): boolean;
42
49
  type PushEvent = Extract<HostEvent, {
43
50
  kind: "push";
@@ -56,7 +63,7 @@ type ConceptCitationStatus = {
56
63
  * commits, so they and the affected concept paths travel as untrusted context.
57
64
  * Commit messages and author names are never included.
58
65
  */
59
- export declare function mergeTaskText(event: PushEvent, scope: DriftScope | null, bundleComplete?: boolean, citations?: ReadonlyMap<string, ConceptCitationStatus>): string;
66
+ export declare function mergeTaskText(event: PushEvent, scope: DriftScope | null, bundleComplete?: boolean, citations?: ReadonlyMap<string, ConceptCitationStatus>, bundlePath?: string): string;
60
67
  type MentionEvent = Extract<HostEvent, {
61
68
  kind: "mention";
62
69
  }>;
@@ -83,7 +90,16 @@ export declare function siblingContinuationHints(siblings: readonly OpenSibling[
83
90
  * taskOverride column.
84
91
  */
85
92
  export declare function mentionTaskText(event: MentionEvent, siblings?: readonly OpenSibling[]): string;
93
+ /** The reply to a mention whose author can see the repository but cannot push to it. */
94
+ export declare const NO_WRITE_ACCESS_BODY: string;
86
95
  export declare function unknownPriorRunBody(priorRunId: string): string;
96
+ /** Why a mention from someone allowed to use wardby started nothing; posted so they are not left guessing. */
97
+ export type MentionDeclineReason = "no_responder" | "no_reviewer" | "agent_not_authorized" | "pull_request_not_reviewable";
98
+ /**
99
+ * Never names the App's handle: this comment is posted by the App, and a
100
+ * body that mentioned it could be matched as a mention of itself.
101
+ */
102
+ export declare function mentionDeclinedBody(reason: MentionDeclineReason): string;
87
103
  export interface ReviewTarget {
88
104
  agentId: string;
89
105
  checkName: string;
@@ -6,16 +6,20 @@
6
6
  * docs/private/2026-09-25-code-review-host-design.md §6.3.
7
7
  */
8
8
  import { Prisma } from "#prisma";
9
- import { linkedPullRequestAttribution, openingRunAttribution, RESPONSE_PATH_SNAPSHOT_BUDGET } from "./attribution.js";
9
+ import { linkedPullRequestAttribution, openingRunAttribution, resolveWorkItem, RESPONSE_PATH_SNAPSHOT_BUDGET, } from "./attribution.js";
10
+ import { headStoppedAtBudget, pushingRunInFlight } from "./budget-stopped-head.js";
10
11
  import { checkContinuation, dispatchRun } from "./dispatch.js";
11
12
  import { mentionStatusRow, postMentionStatus } from "./host-status.js";
12
- import { handlePullRequestClosed } from "./issue-bridge.js";
13
+ import { githubIssueKey, handlePullRequestClosed } from "./issue-bridge.js";
13
14
  import { logger } from "./logger.js";
15
+ import { withoutMarkdownCode } from "./markdown-code.js";
16
+ import { maybeMarkReady } from "./mark-ready.js";
14
17
  import { syncMergeOrderChecksForPullRequest } from "./merge-order-check.js";
15
- import { MAX_SIBLING_HINTS, openSiblings, runTreeRoot, SIBLING_GUIDANCE, } from "./related-pull-requests.js";
18
+ import { MAX_SIBLING_HINTS, openPullRequestHints, openSiblings, openSiblingsForIssue, runTreeRoot, SIBLING_GUIDANCE, } from "./related-pull-requests.js";
16
19
  import { requiredLevel } from "./repo-access.js";
17
20
  import { composeTaskOverride } from "./untrusted-content.js";
18
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";
19
23
  import { driftScope } from "../knowledge/relevance.js";
20
24
  import { spanHash } from "../knowledge/span-hash.js";
21
25
  const log = logger.child({ module: "host-events" });
@@ -31,11 +35,14 @@ export const DEFERRED_REVIEW_BATCH = 50;
31
35
  function escapeRegExp(value) {
32
36
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
33
37
  }
38
+ /** A `@app review` outside code: quoting the command in code documents it. */
34
39
  export function isReviewCommand(body, mentionHandle) {
35
- 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));
36
41
  }
37
42
  const MAX_BUNDLE_FILES = 200;
38
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;
39
46
  const MAX_LISTED_CHANGES = 200;
40
47
  /** The whole bundle load (listing plus reads) must finish inside GitHub's ~10 s webhook window. */
41
48
  const BUNDLE_LOAD_DEADLINE_MS = 4000;
@@ -49,7 +56,7 @@ const BUNDLE_INCOMPLETE_TEXT = "The knowledge bundle could not be fully read; tr
49
56
  * commits, so they and the affected concept paths travel as untrusted context.
50
57
  * Commit messages and author names are never included.
51
58
  */
52
- 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) {
53
60
  const statusOf = (concept) => citations.get(concept) ?? { state: "unverified" };
54
61
  const changed = event.changedPathsComplete
55
62
  ? `Changed files:\n${[
@@ -61,7 +68,7 @@ export function mergeTaskText(event, scope, bundleComplete = true, citations = n
61
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.";
62
69
  const listed = scope
63
70
  ? `Knowledge concepts whose citations, affects globs, or files changed (${scope.reason}):\n` +
64
- 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")
65
72
  : null;
66
73
  let context;
67
74
  if (!bundleComplete)
@@ -76,7 +83,7 @@ export function mergeTaskText(event, scope, bundleComplete = true, citations = n
76
83
  if (scope && scope.concepts.length > 0) {
77
84
  const states = scope.concepts.map((p) => statusOf(p).state);
78
85
  const count = (state) => states.filter((x) => x === state).length;
79
- 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}/`));
80
87
  summary =
81
88
  `Citation check at ${event.after.slice(0, 12)}: ${count("verified")} of ${states.length} affected concepts verified, ` +
82
89
  `${count("stale")} stale, ${count("unverified")} not verified. Only knowledge files changed: ${onlyKnowledge ? "yes" : "no"}.`;
@@ -106,13 +113,36 @@ function startBudget(ms) {
106
113
  });
107
114
  return { promise, expired: () => expired, clear: () => clearTimeout(timer) };
108
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
+ }
109
139
  /**
110
140
  * The knowledge bundle at the pushed commit, read with bounded concurrency
111
141
  * under one overall deadline. A failure never fails the route; it yields an
112
142
  * incomplete result so the watcher treats every concept as possibly affected.
113
143
  */
114
- async function loadBundle(host, event, budget) {
115
- const prefix = `${DEFAULT_KNOWLEDGE_BUNDLE_PATH}/`;
144
+ async function loadBundle(host, event, budget, bundlePath) {
145
+ const prefix = `${bundlePath}/`;
116
146
  const concepts = [];
117
147
  const where = { repository: event.repository, after: event.after };
118
148
  let filesRead = 0;
@@ -306,6 +336,12 @@ export function mentionTaskText(event, siblings = []) {
306
336
  if (hints)
307
337
  sections.push(hints);
308
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
+ }
309
345
  const title = event.subject?.title.replace(/\s+/g, " ").trim().slice(0, MAX_TITLE);
310
346
  const description = event.subject?.body.trim() ? event.subject.body.slice(0, MAX_TASK_BODY) : "";
311
347
  sections.push([
@@ -342,11 +378,49 @@ async function priorRunContinues(deps, event) {
342
378
  const check = await checkContinuation(deps.db, event.priorRunId, event.repository);
343
379
  return check.ok && check.root.pullRequestNumber === event.number;
344
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
+ }
345
400
  export function unknownPriorRunBody(priorRunId) {
346
401
  return (`❌ I can't continue this pull request. Its description names wardby run \`${priorRunId}\`, but this wardby ` +
347
402
  "deployment has no record of that run opening this pull request (another wardby deployment may have opened it). " +
348
403
  "Ask the deployment that opened it, or make the change on this branch by hand.");
349
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
+ }
350
424
  /** A review run's task: the head to review, plus any extra context its trigger adds. */
351
425
  export function reviewTaskText(repository, prNumber, headSha, context) {
352
426
  const task = `Review pull request #${prNumber} in ${repository} (head ${headSha}).`;
@@ -457,6 +531,59 @@ context) {
457
531
  }
458
532
  return runIds;
459
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
+ }
460
587
  /**
461
588
  * CI finished on a pull request's head: re-runs, once per head, each review
462
589
  * that published only a COMMENT while CI there was still running or had not
@@ -492,6 +619,16 @@ async function rereviewAfterCi(deps, host, event, prNumber, reviewerLinks) {
492
619
  // Only reviewers still authorized are claimed, so a denied one keeps its chance for later.
493
620
  const allowed = await authorizedLinks(deps, event, reviewerLinks.filter((l) => waiting.some((r) => r.run.agentId === l.agentId)));
494
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
+ }
495
632
  // Claimed per row, so concurrent completions re-run each review only once.
496
633
  const agentIds = new Set();
497
634
  for (const row of waiting.filter((r) => allowedIds.has(r.run.agentId))) {
@@ -624,6 +761,22 @@ async function deferUntilCi(deps, host, key, links) {
624
761
  }
625
762
  return { startNow: [], runIds };
626
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
+ }
627
780
  /**
628
781
  * Starts deferred reviews for one PR head. The head must still be that sha,
629
782
  * open, and not a fork; otherwise every row is dropped (a newer push records
@@ -641,6 +794,9 @@ async function startDeferred(deps, host, key, rows, reviewerLinks, mode, reason)
641
794
  log.info({ ...where, state: head.state }, "deferred review dropped: the pull request moved on or closed");
642
795
  return [];
643
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);
644
800
  const allowed = await authorizedLinks(deps, key, reviewerLinks.filter((l) => rows.some((r) => r.agentId === l.agentId)));
645
801
  const allowedIds = new Set(allowed.map((l) => l.agentId));
646
802
  const targets = [];
@@ -659,6 +815,10 @@ async function startDeferred(deps, host, key, rows, reviewerLinks, mode, reason)
659
815
  return [];
660
816
  log.info({ ...where, agentIds: targets.map((t) => t.agentId), reason }, "starting the deferred review");
661
817
  try {
818
+ if (decision.kind === "skip") {
819
+ await postSkippedReviewChecks(deps, host, key.repository, key.prNumber, key.headSha, targets);
820
+ return [];
821
+ }
662
822
  return await startReviews(deps, host, key.repository, key.prNumber, key.headSha, targets, true);
663
823
  }
664
824
  catch (err) {
@@ -809,13 +969,36 @@ export async function requestLeadRunId(db, prMarkerRunId, repository) {
809
969
  }
810
970
  /** Run statuses that mean the delegating run has not finished. */
811
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
+ }
812
992
  /**
813
993
  * pr_updated for a pull request a delegated coding run opened, while the
814
994
  * request's lead run is still running: records one DeferredReview row per
815
995
  * reviewer (reason "request", with or without waitForCi) and starts nothing;
816
996
  * the lead's finalizer (startDeferredForRequest) or the sweep starts them.
817
997
  * Returns null, to review as without it, for any other pull request, or when
818
- * the lead cannot be resolved or the deferral cannot be recorded.
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.
819
1002
  */
820
1003
  async function deferUntilRequest(deps, host, key, links,
821
1004
  /** The pull request's origin, read at most once per event (shared with the merge order check). */
@@ -828,6 +1011,15 @@ readOrigin) {
828
1011
  if (!origin.markerRunId)
829
1012
  return null;
830
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
+ }
831
1023
  }
832
1024
  catch (err) {
833
1025
  log.warn({ err, repository: key.repository, prNumber: key.prNumber }, "could not read the pull request's delegating run; reviewing as usual");
@@ -1022,11 +1214,14 @@ export async function routeHostEvent(event, deps) {
1022
1214
  const budget = startBudget(BUNDLE_LOAD_DEADLINE_MS);
1023
1215
  let taskOverride;
1024
1216
  try {
1025
- 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);
1026
1221
  const scope = driftScope({
1027
1222
  changedPaths: event.changedPaths,
1028
1223
  changedPathsComplete: event.changedPathsComplete,
1029
- bundlePath: DEFAULT_KNOWLEDGE_BUNDLE_PATH,
1224
+ bundlePath,
1030
1225
  concepts,
1031
1226
  });
1032
1227
  // Only the concepts the merge affects are checked, and only against a bundle that was fully read.
@@ -1034,7 +1229,7 @@ export async function routeHostEvent(event, deps) {
1034
1229
  const citations = complete
1035
1230
  ? await verifyCitations(host, event, concepts.filter((c) => affected.has(c.path)), budget)
1036
1231
  : new Map();
1037
- taskOverride = mergeTaskText(event, scope, complete, citations);
1232
+ taskOverride = mergeTaskText(event, scope, complete, citations, bundlePath);
1038
1233
  }
1039
1234
  finally {
1040
1235
  budget.clear();
@@ -1101,7 +1296,15 @@ export async function routeHostEvent(event, deps) {
1101
1296
  const reviewers = reviewTargets(allowed.filter((l) => !l.waitForCi || startNow.has(l)));
1102
1297
  const runIds = [...deferral.runIds];
1103
1298
  if (reviewers.length > 0) {
1104
- 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
+ }
1105
1308
  }
1106
1309
  return { runIds, followUps };
1107
1310
  }
@@ -1109,11 +1312,16 @@ export async function routeHostEvent(event, deps) {
1109
1312
  if (reviewerLinks.length === 0)
1110
1313
  return none;
1111
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
+ });
1112
1320
  for (const prNumber of event.prNumbers) {
1113
1321
  runIds.push(...(await rereviewAfterCi(deps, host, event, prNumber, reviewerLinks)));
1114
1322
  runIds.push(...(await startDeferredAfterCi(deps, host, event, prNumber, reviewerLinks)));
1115
1323
  }
1116
- return { runIds, followUps: [] };
1324
+ return { runIds, followUps };
1117
1325
  }
1118
1326
  case "check_rerun": {
1119
1327
  const owners = reviewerLinks.filter((l) => l.checkName === event.checkName);
@@ -1136,12 +1344,15 @@ export async function routeHostEvent(event, deps) {
1136
1344
  const reviewCommand = event.isPullRequest && isReviewCommand(event.body, deps.mentionHandle);
1137
1345
  const responderLink = links.find((l) => l.access === "write" && l.triggers.includes("mention"));
1138
1346
  const candidates = reviewCommand ? reviewerLinks : responderLink ? [responderLink] : [];
1139
- 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)
1140
1349
  return none;
1141
1350
  // H5-3: a mention drives a write-capable agent holding its owner's
1142
1351
  // tools and secrets, so its author needs real push access to the
1143
1352
  // repository (author_association, checked upstream, is only a
1144
- // 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.
1145
1356
  const author = await deps.repoAccess.authorizeHostUser({
1146
1357
  provider: event.provider,
1147
1358
  repository: event.repository,
@@ -1150,15 +1361,54 @@ export async function routeHostEvent(event, deps) {
1150
1361
  });
1151
1362
  if (!author.ok) {
1152
1363
  log.info({ repository: event.repository, number: event.number, reason: author.reason, level: author.level }, "mention ignored: its author lacks write access");
1153
- 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] };
1154
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");
1155
1405
  const allowed = await authorizedLinks(deps, event, candidates);
1156
1406
  if (allowed.length === 0)
1157
- return none;
1407
+ return decline("agent_not_authorized");
1158
1408
  if (reviewCommand) {
1159
1409
  const head = await host.pullRequestHead(event.repository, event.number);
1160
1410
  if (head.isFork || head.state !== "open")
1161
- return none;
1411
+ return decline("pull_request_not_reviewable");
1162
1412
  return {
1163
1413
  runIds: await startReviews(deps, host, event.repository, event.number, head.headSha, reviewTargets(allowed)),
1164
1414
  followUps: [react],
@@ -1183,9 +1433,15 @@ export async function routeHostEvent(event, deps) {
1183
1433
  return { runIds: [], followUps: [react, reply] };
1184
1434
  }
1185
1435
  // Seeded from the PR's validated marker run: the request it belongs to. Stored state only.
1186
- const siblings = event.isPullRequest && event.priorRunId
1187
- ? await openSiblings(deps.db, event.priorRunId, { repository: event.repository, number: event.number })
1188
- : [];
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
+ });
1189
1445
  const dispatched = await dispatchRun({
1190
1446
  db: deps.db,
1191
1447
  executor: deps.executor,
@@ -1195,7 +1451,11 @@ export async function routeHostEvent(event, deps) {
1195
1451
  taskOverride: mentionTaskText(event, siblings),
1196
1452
  attribution: event.isPullRequest
1197
1453
  ? await linkedPullRequestAttribution(deps.db, deps.issueTrackers, { codeProvider: host.provider, repository: event.repository, number: event.number }, RESPONSE_PATH_SNAPSHOT_BUDGET)
1198
- : 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
+ },
1199
1459
  // Where to report, written with the run: even if this instance dies
1200
1460
  // before the follow-up below, the run's outcome still gets a comment.
1201
1461
  afterPersist: async (tx, run) => {