@wardby/cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (491) hide show
  1. package/.env.example +34 -4
  2. package/README.md +183 -71
  3. package/dist/claude-coding-worker/driver.d.ts +7 -1
  4. package/dist/claude-coding-worker/driver.js +31 -2
  5. package/dist/claude-coding-worker/main.js +5 -2
  6. package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
  7. package/dist/claude-coding-worker/tool-socket.js +26 -0
  8. package/dist/cli-help.d.ts +1 -1
  9. package/dist/cli-help.js +13 -4
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +219 -29
  12. package/dist/coding/collect-exclude.d.ts +25 -0
  13. package/dist/coding/collect-exclude.js +76 -0
  14. package/dist/coding/profile.d.ts +67 -22
  15. package/dist/coding/profile.js +58 -25
  16. package/dist/coding/protected-path-wording.d.ts +24 -0
  17. package/dist/coding/protected-path-wording.js +55 -0
  18. package/dist/coding/protected-paths.d.ts +31 -0
  19. package/dist/coding/protected-paths.js +48 -0
  20. package/dist/coding/protocol.d.ts +99 -3
  21. package/dist/coding/protocol.js +155 -4
  22. package/dist/coding/registry/adapters.d.ts +2 -0
  23. package/dist/coding/registry/adapters.js +9 -0
  24. package/dist/coding/registry/allowlist.d.ts +9 -0
  25. package/dist/coding/registry/allowlist.js +28 -0
  26. package/dist/coding/registry/json-scan.d.ts +55 -0
  27. package/dist/coding/registry/json-scan.js +191 -0
  28. package/dist/coding/registry/lockfiles.d.ts +6 -0
  29. package/dist/coding/registry/lockfiles.js +89 -0
  30. package/dist/coding/registry/npm-lockfile.d.ts +9 -0
  31. package/dist/coding/registry/npm-lockfile.js +73 -0
  32. package/dist/coding/registry/npm-plan.d.ts +29 -0
  33. package/dist/coding/registry/npm-plan.js +289 -0
  34. package/dist/coding/registry/npm.d.ts +7 -0
  35. package/dist/coding/registry/npm.js +252 -0
  36. package/dist/coding/registry/pypi.d.ts +4 -0
  37. package/dist/coding/registry/pypi.js +210 -0
  38. package/dist/coding/registry/report.d.ts +37 -0
  39. package/dist/coding/registry/report.js +31 -0
  40. package/dist/coding/registry/token.d.ts +4 -0
  41. package/dist/coding/registry/token.js +7 -0
  42. package/dist/coding/registry/types.d.ts +286 -0
  43. package/dist/coding/registry/types.js +19 -0
  44. package/dist/coding/registry/worker-config.d.ts +14 -0
  45. package/dist/coding/registry/worker-config.js +31 -0
  46. package/dist/coding/services/builtins.d.ts +22 -0
  47. package/dist/coding/services/builtins.js +85 -0
  48. package/dist/coding/services/catalog.d.ts +443 -0
  49. package/dist/coding/services/catalog.js +159 -0
  50. package/dist/coding/services/declaration.d.ts +11 -0
  51. package/dist/coding/services/declaration.js +114 -0
  52. package/dist/coding/services/note.d.ts +8 -0
  53. package/dist/coding/services/note.js +15 -0
  54. package/dist/coding/services/resolve.d.ts +25 -0
  55. package/dist/coding/services/resolve.js +50 -0
  56. package/dist/coding/services/wording.d.ts +25 -0
  57. package/dist/coding/services/wording.js +70 -0
  58. package/dist/coding-proxy/main.js +24 -4
  59. package/dist/coding-worker/artifact.d.ts +18 -0
  60. package/dist/coding-worker/artifact.js +48 -6
  61. package/dist/coding-worker/debug-trace.d.ts +37 -0
  62. package/dist/coding-worker/debug-trace.js +117 -0
  63. package/dist/coding-worker/driver.d.ts +14 -1
  64. package/dist/coding-worker/driver.js +106 -14
  65. package/dist/coding-worker/errors.d.ts +7 -0
  66. package/dist/coding-worker/errors.js +26 -0
  67. package/dist/coding-worker/main.js +13 -4
  68. package/dist/coding-worker/sdk.d.ts +2 -2
  69. package/dist/coding-worker/sdk.js +7 -2
  70. package/dist/coding-worker/types.d.ts +3 -0
  71. package/dist/config/providers.d.ts +41 -0
  72. package/dist/config/providers.js +66 -0
  73. package/dist/core/budget-groups.d.ts +109 -6
  74. package/dist/core/budget-groups.js +125 -19
  75. package/dist/core/budget-wording.d.ts +22 -0
  76. package/dist/core/budget-wording.js +55 -0
  77. package/dist/core/coding-queue.d.ts +1 -1
  78. package/dist/core/datastores.d.ts +1 -1
  79. package/dist/core/datastores.js +18 -2
  80. package/dist/core/db.d.ts +40 -2
  81. package/dist/core/db.js +75 -2
  82. package/dist/core/dispatch.d.ts +49 -7
  83. package/dist/core/dispatch.js +290 -45
  84. package/dist/core/engine-native.js +27 -10
  85. package/dist/core/grants.d.ts +86 -0
  86. package/dist/core/grants.js +126 -0
  87. package/dist/core/host-events.d.ts +49 -0
  88. package/dist/core/host-events.js +257 -0
  89. package/dist/core/host-identity-links.d.ts +54 -0
  90. package/dist/core/host-identity-links.js +189 -0
  91. package/dist/core/host-status.d.ts +61 -0
  92. package/dist/core/host-status.js +211 -0
  93. package/dist/core/http-runtime.js +3 -4
  94. package/dist/core/in-flight-runs.d.ts +8 -0
  95. package/dist/core/in-flight-runs.js +56 -0
  96. package/dist/core/lease.d.ts +1 -1
  97. package/dist/core/private-directory.d.ts +20 -0
  98. package/dist/core/private-directory.js +36 -0
  99. package/dist/core/provider-wording.d.ts +12 -0
  100. package/dist/core/provider-wording.js +44 -0
  101. package/dist/core/reconciler.d.ts +39 -3
  102. package/dist/core/reconciler.js +86 -2
  103. package/dist/core/repo-access.d.ts +99 -0
  104. package/dist/core/repo-access.js +136 -0
  105. package/dist/core/review-host-checks.d.ts +11 -0
  106. package/dist/core/review-host-checks.js +41 -0
  107. package/dist/core/review-host-tools.d.ts +47 -0
  108. package/dist/core/review-host-tools.js +347 -0
  109. package/dist/core/run-heartbeat.d.ts +27 -0
  110. package/dist/core/run-heartbeat.js +54 -0
  111. package/dist/core/runner.d.ts +31 -9
  112. package/dist/core/runner.js +237 -40
  113. package/dist/core/scheduler.d.ts +1 -1
  114. package/dist/core/secrets.d.ts +12 -3
  115. package/dist/core/secrets.js +25 -6
  116. package/dist/core/subagent-memory-tools.d.ts +2 -2
  117. package/dist/core/subagent-memory-tools.js +16 -3
  118. package/dist/core/tool-admin.d.ts +81 -0
  119. package/dist/core/tool-admin.js +129 -0
  120. package/dist/core/tool-names.d.ts +42 -0
  121. package/dist/core/tool-names.js +64 -0
  122. package/dist/core/untrusted-content.d.ts +32 -0
  123. package/dist/core/untrusted-content.js +72 -0
  124. package/dist/core/webhooks.d.ts +9 -2
  125. package/dist/core/webhooks.js +23 -2
  126. package/dist/generated/prisma/browser.d.ts +328 -0
  127. package/dist/generated/prisma/browser.js +17 -0
  128. package/dist/generated/prisma/client.d.ts +347 -0
  129. package/dist/generated/prisma/client.js +34 -0
  130. package/dist/generated/prisma/commonInputTypes.d.ts +839 -0
  131. package/dist/generated/prisma/commonInputTypes.js +10 -0
  132. package/dist/generated/prisma/enums.d.ts +59 -0
  133. package/dist/generated/prisma/enums.js +59 -0
  134. package/dist/generated/prisma/internal/class.d.ts +605 -0
  135. package/dist/generated/prisma/internal/class.js +49 -0
  136. package/dist/generated/prisma/internal/prismaNamespace.d.ts +4494 -0
  137. package/dist/generated/prisma/internal/prismaNamespace.js +628 -0
  138. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +635 -0
  139. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +599 -0
  140. package/dist/generated/prisma/models/Agent.d.ts +3560 -0
  141. package/dist/generated/prisma/models/Agent.js +1 -0
  142. package/dist/generated/prisma/models/AgentDatastore.d.ts +1210 -0
  143. package/dist/generated/prisma/models/AgentDatastore.js +1 -0
  144. package/dist/generated/prisma/models/AgentMemory.d.ts +986 -0
  145. package/dist/generated/prisma/models/AgentMemory.js +1 -0
  146. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  147. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  148. package/dist/generated/prisma/models/AgentSecret.d.ts +1212 -0
  149. package/dist/generated/prisma/models/AgentSecret.js +1 -0
  150. package/dist/generated/prisma/models/AgentSubAgent.d.ts +1217 -0
  151. package/dist/generated/prisma/models/AgentSubAgent.js +1 -0
  152. package/dist/generated/prisma/models/AgentTool.d.ts +1403 -0
  153. package/dist/generated/prisma/models/AgentTool.js +1 -0
  154. package/dist/generated/prisma/models/AuthFormChallenge.d.ts +1298 -0
  155. package/dist/generated/prisma/models/AuthFormChallenge.js +1 -0
  156. package/dist/generated/prisma/models/AuthLoginKey.d.ts +1245 -0
  157. package/dist/generated/prisma/models/AuthLoginKey.js +1 -0
  158. package/dist/generated/prisma/models/AuthRateLimit.d.ts +979 -0
  159. package/dist/generated/prisma/models/AuthRateLimit.js +1 -0
  160. package/dist/generated/prisma/models/AuthSession.d.ts +1376 -0
  161. package/dist/generated/prisma/models/AuthSession.js +1 -0
  162. package/dist/generated/prisma/models/AuthUser.d.ts +1700 -0
  163. package/dist/generated/prisma/models/AuthUser.js +1 -0
  164. package/dist/generated/prisma/models/BudgetGroup.d.ts +1548 -0
  165. package/dist/generated/prisma/models/BudgetGroup.js +1 -0
  166. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +1681 -0
  167. package/dist/generated/prisma/models/CodingAgentProfile.js +1 -0
  168. package/dist/generated/prisma/models/CodingProxyRequest.d.ts +1749 -0
  169. package/dist/generated/prisma/models/CodingProxyRequest.js +1 -0
  170. package/dist/generated/prisma/models/CodingProxySession.d.ts +1626 -0
  171. package/dist/generated/prisma/models/CodingProxySession.js +1 -0
  172. package/dist/generated/prisma/models/CodingRun.d.ts +3583 -0
  173. package/dist/generated/prisma/models/CodingRun.js +1 -0
  174. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  175. package/dist/generated/prisma/models/CodingService.js +1 -0
  176. package/dist/generated/prisma/models/Datastore.d.ts +1434 -0
  177. package/dist/generated/prisma/models/Datastore.js +1 -0
  178. package/dist/generated/prisma/models/DatastoreEntry.d.ts +1314 -0
  179. package/dist/generated/prisma/models/DatastoreEntry.js +1 -0
  180. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  181. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  182. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  183. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  184. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  185. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  186. package/dist/generated/prisma/models/OAuthAuthorizationCode.d.ts +1526 -0
  187. package/dist/generated/prisma/models/OAuthAuthorizationCode.js +1 -0
  188. package/dist/generated/prisma/models/OAuthAuthorizationRequest.d.ts +1350 -0
  189. package/dist/generated/prisma/models/OAuthAuthorizationRequest.js +1 -0
  190. package/dist/generated/prisma/models/OAuthClient.d.ts +1289 -0
  191. package/dist/generated/prisma/models/OAuthClient.js +1 -0
  192. package/dist/generated/prisma/models/OAuthFamily.d.ts +1540 -0
  193. package/dist/generated/prisma/models/OAuthFamily.js +1 -0
  194. package/dist/generated/prisma/models/OAuthGrant.d.ts +1280 -0
  195. package/dist/generated/prisma/models/OAuthGrant.js +1 -0
  196. package/dist/generated/prisma/models/Principal.d.ts +2245 -0
  197. package/dist/generated/prisma/models/Principal.js +1 -0
  198. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  199. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  200. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  201. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  202. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  203. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  204. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  205. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  206. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  207. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  208. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  209. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  210. package/dist/generated/prisma/models/Run.d.ts +2669 -0
  211. package/dist/generated/prisma/models/Run.js +1 -0
  212. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  213. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  214. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  215. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  216. package/dist/generated/prisma/models/SchedulerLease.d.ts +970 -0
  217. package/dist/generated/prisma/models/SchedulerLease.js +1 -0
  218. package/dist/generated/prisma/models/Secret.d.ts +1399 -0
  219. package/dist/generated/prisma/models/Secret.js +1 -0
  220. package/dist/generated/prisma/models/SecretElicitationOutcome.d.ts +973 -0
  221. package/dist/generated/prisma/models/SecretElicitationOutcome.js +1 -0
  222. package/dist/generated/prisma/models/Task.d.ts +1176 -0
  223. package/dist/generated/prisma/models/Task.js +1 -0
  224. package/dist/generated/prisma/models/Tool.d.ts +1490 -0
  225. package/dist/generated/prisma/models/Tool.js +1 -0
  226. package/dist/generated/prisma/models/Webhook.d.ts +1386 -0
  227. package/dist/generated/prisma/models/Webhook.js +1 -0
  228. package/dist/generated/prisma/models.d.ts +45 -0
  229. package/dist/generated/prisma/models.js +1 -0
  230. package/dist/help/build.d.ts +1 -0
  231. package/dist/help/build.js +9 -0
  232. package/dist/help/catalog.d.ts +24 -0
  233. package/dist/help/catalog.js +160 -0
  234. package/dist/help/cli.d.ts +2 -0
  235. package/dist/help/cli.js +65 -0
  236. package/dist/help/runtime.d.ts +3 -0
  237. package/dist/help/runtime.js +44 -0
  238. package/dist/help/search.d.ts +9 -0
  239. package/dist/help/search.js +104 -0
  240. package/dist/help-index.json +660 -0
  241. package/dist/import/cli-args.js +3 -2
  242. package/dist/import/create.d.ts +6 -1
  243. package/dist/import/create.js +68 -14
  244. package/dist/import/index.d.ts +1 -1
  245. package/dist/import/index.js +19 -9
  246. package/dist/import/neutral-schema.d.ts +11 -11
  247. package/dist/mcp/auth/access.d.ts +70 -0
  248. package/dist/mcp/auth/access.js +58 -0
  249. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  250. package/dist/mcp/auth/grants-cli.js +518 -0
  251. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  252. package/dist/mcp/auth/host-account-cli.js +47 -0
  253. package/dist/mcp/auth/ownership.d.ts +37 -63
  254. package/dist/mcp/auth/ownership.js +19 -14
  255. package/dist/mcp/auth/principal.d.ts +1 -1
  256. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  257. package/dist/mcp/auth/repo-authorization.js +51 -0
  258. package/dist/mcp/auth/resource-server.d.ts +32 -3
  259. package/dist/mcp/auth/resource-server.js +84 -3
  260. package/dist/mcp/auth/self-hosted/browser.js +22 -8
  261. package/dist/mcp/auth/self-hosted/cli.d.ts +1 -1
  262. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  263. package/dist/mcp/auth/self-hosted/credentials.d.ts +21 -3
  264. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  265. package/dist/mcp/auth/self-hosted/rate-limit.d.ts +1 -1
  266. package/dist/mcp/auth/self-hosted/session.d.ts +3 -2
  267. package/dist/mcp/context.d.ts +28 -2
  268. package/dist/mcp/errors.d.ts +25 -6
  269. package/dist/mcp/errors.js +98 -0
  270. package/dist/mcp/host-events/github-ingress.d.ts +36 -0
  271. package/dist/mcp/host-events/github-ingress.js +92 -0
  272. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  273. package/dist/mcp/host-events/github-user-callback.js +50 -0
  274. package/dist/mcp/index.d.ts +8 -1
  275. package/dist/mcp/index.js +106 -16
  276. package/dist/mcp/server.d.ts +1 -1
  277. package/dist/mcp/server.js +27 -9
  278. package/dist/mcp/shared/page-style.d.ts +16 -0
  279. package/dist/mcp/shared/page-style.js +129 -0
  280. package/dist/mcp/tasks/manager.d.ts +1 -1
  281. package/dist/mcp/tools/agents.js +447 -49
  282. package/dist/mcp/tools/budget-groups.js +3 -3
  283. package/dist/mcp/tools/datastore.js +23 -16
  284. package/dist/mcp/tools/grants.d.ts +2 -0
  285. package/dist/mcp/tools/grants.js +239 -0
  286. package/dist/mcp/tools/help.d.ts +5 -0
  287. package/dist/mcp/tools/help.js +67 -0
  288. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  289. package/dist/mcp/tools/host-accounts.js +114 -0
  290. package/dist/mcp/tools/memory.d.ts +7 -1
  291. package/dist/mcp/tools/memory.js +5 -5
  292. package/dist/mcp/tools/repositories.d.ts +2 -0
  293. package/dist/mcp/tools/repositories.js +181 -0
  294. package/dist/mcp/tools/runs.d.ts +6 -0
  295. package/dist/mcp/tools/runs.js +60 -6
  296. package/dist/mcp/tools/scheduling.js +4 -7
  297. package/dist/mcp/tools/secret-elicitation-form.d.ts +1 -1
  298. package/dist/mcp/tools/secret-elicitation-form.js +10 -28
  299. package/dist/mcp/tools/secret-elicitation.d.ts +1 -1
  300. package/dist/mcp/tools/secrets.js +18 -6
  301. package/dist/mcp/tools/services.d.ts +2 -0
  302. package/dist/mcp/tools/services.js +222 -0
  303. package/dist/mcp/tools/subagents.js +53 -17
  304. package/dist/mcp/tools/tools.d.ts +41 -0
  305. package/dist/mcp/tools/tools.js +202 -41
  306. package/dist/mcp/tools/trigger.js +36 -6
  307. package/dist/mcp/tools/webhooks.js +15 -4
  308. package/dist/mcp/transport/streamable-http.d.ts +11 -1
  309. package/dist/mcp/transport/streamable-http.js +32 -2
  310. package/dist/mcp/unattended-schedules.d.ts +1 -1
  311. package/dist/mcp/webhooks/ingress.d.ts +1 -1
  312. package/dist/providers/auth/delegating.d.ts +19 -0
  313. package/dist/providers/auth/delegating.js +71 -0
  314. package/dist/providers/auth/index.d.ts +3 -1
  315. package/dist/providers/auth/index.js +11 -0
  316. package/dist/providers/auth/self-hosted.d.ts +4 -3
  317. package/dist/providers/auth/self-hosted.js +16 -4
  318. package/dist/providers/auth/types.d.ts +6 -0
  319. package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
  320. package/dist/providers/coding-proxy/memory-ledger.js +21 -2
  321. package/dist/providers/coding-proxy/metering.d.ts +4 -0
  322. package/dist/providers/coding-proxy/metering.js +28 -2
  323. package/dist/providers/coding-proxy/prisma-ledger.d.ts +18 -1
  324. package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
  325. package/dist/providers/coding-proxy/proxy.d.ts +13 -0
  326. package/dist/providers/coding-proxy/proxy.js +479 -18
  327. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  328. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  329. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  330. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  331. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  332. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  333. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  334. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  335. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  336. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  337. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  338. package/dist/providers/coding-proxy/registry/store.js +70 -0
  339. package/dist/providers/coding-proxy/runtime.d.ts +18 -1
  340. package/dist/providers/coding-proxy/runtime.js +63 -0
  341. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  342. package/dist/providers/coding-proxy/server.d.ts +11 -0
  343. package/dist/providers/coding-proxy/server.js +163 -0
  344. package/dist/providers/coding-proxy/types.d.ts +15 -0
  345. package/dist/providers/datastore/postgres.d.ts +1 -1
  346. package/dist/providers/engine/types.d.ts +10 -0
  347. package/dist/providers/executor/build.d.ts +3 -3
  348. package/dist/providers/executor/composition.d.ts +4 -1
  349. package/dist/providers/executor/composition.js +19 -0
  350. package/dist/providers/executor/container.d.ts +106 -4
  351. package/dist/providers/executor/container.js +305 -22
  352. package/dist/providers/executor/dbos.d.ts +3 -4
  353. package/dist/providers/executor/in-process.d.ts +2 -3
  354. package/dist/providers/executor/routing.d.ts +7 -0
  355. package/dist/providers/executor/routing.js +13 -0
  356. package/dist/providers/executor/types.d.ts +22 -0
  357. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  358. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  359. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  360. package/dist/providers/jobs/collect-prune.js +27 -0
  361. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  362. package/dist/providers/jobs/docker-isolation.js +213 -22
  363. package/dist/providers/jobs/docker-services.d.ts +35 -0
  364. package/dist/providers/jobs/docker-services.js +191 -0
  365. package/dist/providers/jobs/docker.d.ts +60 -2
  366. package/dist/providers/jobs/docker.js +313 -50
  367. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  368. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  369. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  370. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  371. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  372. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  373. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  374. package/dist/providers/jobs/kubernetes.d.ts +21 -2
  375. package/dist/providers/jobs/kubernetes.js +117 -31
  376. package/dist/providers/jobs/types.d.ts +20 -0
  377. package/dist/providers/llm/anthropic.js +6 -2
  378. package/dist/providers/llm/bedrock.js +2 -1
  379. package/dist/providers/llm/claude-messages.d.ts +5 -1
  380. package/dist/providers/llm/claude-messages.js +1 -0
  381. package/dist/providers/llm/claude-provider.d.ts +3 -1
  382. package/dist/providers/llm/claude-provider.js +5 -1
  383. package/dist/providers/llm/index.d.ts +1 -1
  384. package/dist/providers/llm/index.js +1 -1
  385. package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
  386. package/dist/providers/llm/pricing-anthropic.js +32 -14
  387. package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
  388. package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
  389. package/dist/providers/llm/routing.d.ts +10 -1
  390. package/dist/providers/llm/routing.js +18 -0
  391. package/dist/providers/llm/types.d.ts +12 -0
  392. package/dist/providers/llm/types.js +8 -1
  393. package/dist/providers/memory/postgres.d.ts +1 -1
  394. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  395. package/dist/providers/review-host/diff-lines.js +59 -0
  396. package/dist/providers/review-host/github-events.d.ts +6 -0
  397. package/dist/providers/review-host/github-events.js +180 -0
  398. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  399. package/dist/providers/review-host/github-user-auth.js +128 -0
  400. package/dist/providers/review-host/github.d.ts +57 -0
  401. package/dist/providers/review-host/github.js +567 -0
  402. package/dist/providers/review-host/index.d.ts +12 -0
  403. package/dist/providers/review-host/index.js +30 -0
  404. package/dist/providers/review-host/review-format.d.ts +19 -0
  405. package/dist/providers/review-host/review-format.js +59 -0
  406. package/dist/providers/review-host/types.d.ts +284 -0
  407. package/dist/providers/review-host/types.js +24 -0
  408. package/dist/providers/vcs/git.d.ts +16 -6
  409. package/dist/providers/vcs/git.js +79 -14
  410. package/dist/providers/vcs/github.d.ts +74 -3
  411. package/dist/providers/vcs/github.js +195 -15
  412. package/dist/providers/vcs/types.d.ts +42 -4
  413. package/dist/quickstart/index.js +34 -32
  414. package/dist/quickstart/migrate.d.ts +23 -0
  415. package/dist/quickstart/migrate.js +125 -0
  416. package/dist/sandbox/fetch-policy.d.ts +20 -2
  417. package/dist/sandbox/fetch-policy.js +64 -3
  418. package/dist/sandbox/host-functions.d.ts +9 -1
  419. package/dist/sandbox/host-functions.js +12 -5
  420. package/dist/serve.js +1 -1
  421. package/dist/wardby-bin.js +6 -0
  422. package/docs/README.md +30 -0
  423. package/docs/architecture-runtime.md +90 -0
  424. package/docs/assets/brand/wardby-icon-512.png +0 -0
  425. package/docs/assets/brand/wardby-icon.svg +16 -0
  426. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  427. package/docs/assets/brand/wardby-mascot.png +0 -0
  428. package/docs/assets/brand/wardby-mascot.svg +5 -0
  429. package/docs/assets/wardby-workflow.svg +106 -0
  430. package/docs/code-review-agents.md +481 -0
  431. package/docs/coding-agent-setup.md +172 -0
  432. package/docs/coding-packages.md +455 -0
  433. package/docs/coding-services.md +300 -0
  434. package/docs/coding-worker-byo-images.md +98 -0
  435. package/docs/coding-worker-isolation.md +1061 -0
  436. package/docs/getting-started-gke.md +615 -0
  437. package/docs/getting-started-identity-provider.md +308 -0
  438. package/docs/getting-started.md +133 -0
  439. package/docs/observability.md +53 -0
  440. package/docs/release-verification.md +66 -0
  441. package/docs/security-deployment.md +657 -0
  442. package/help/code-review-agents.md +31 -0
  443. package/help/coding-packages.md +31 -0
  444. package/help/coding-services.md +71 -0
  445. package/help/creating-agents.md +68 -0
  446. package/help/deploy-gke.md +39 -0
  447. package/help/deployment-targets.md +39 -0
  448. package/help/errors/budget-group-exhausted.md +25 -0
  449. package/help/errors/docker-isolation-unsupported.md +26 -0
  450. package/help/errors/protected-path.md +45 -0
  451. package/help/errors/repo-access.md +27 -0
  452. package/help/errors/service-declaration-invalid.md +27 -0
  453. package/help/errors/service-declaration-unavailable.md +25 -0
  454. package/help/errors/service-launcher-unsupported.md +30 -0
  455. package/help/errors/service-not-allowed.md +27 -0
  456. package/help/errors/service-unknown.md +23 -0
  457. package/help/errors/service-unready.md +49 -0
  458. package/help/getting-started.md +31 -0
  459. package/help/github.md +30 -0
  460. package/help/identity-and-access.md +39 -0
  461. package/help/mcp.md +30 -0
  462. package/help/native-capabilities.md +30 -0
  463. package/help/observability.md +41 -0
  464. package/help/operating-agents.md +30 -0
  465. package/help/security.md +27 -0
  466. package/help/troubleshooting/budgets.md +26 -0
  467. package/help/troubleshooting/coding-workers.md +47 -0
  468. package/help/troubleshooting/repository-access.md +27 -0
  469. package/package.json +30 -11
  470. package/prisma/migrate.config.mjs +20 -0
  471. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  472. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  473. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  474. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  475. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  476. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  477. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  478. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  479. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  480. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  481. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  482. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  483. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  484. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  485. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  486. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  487. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  488. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  489. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  490. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  491. package/prisma/schema.prisma +417 -23
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Linking a wardby principal to its verified host identity (GitHub today).
3
+ *
4
+ * 1. startHostIdentityLink: a single-use OAuth state and a PKCE verifier are
5
+ * stored (the state only as a hash) for 10 minutes; the caller gets the
6
+ * host's authorize URL.
7
+ * 2. completeHostIdentityCallback: the host redirects the browser back with
8
+ * the state; it is consumed atomically, the code is exchanged (the user
9
+ * token is revoked at once by the authorizer), and a one-time
10
+ * confirmation code is shown to the person in the browser.
11
+ * 3. confirmHostIdentityLink: the SAME principal that started the link
12
+ * submits the code (at most 5 tries). Only then is the identity stored.
13
+ *
14
+ * Step 3 is what stops an attacker from sending a victim their own authorize
15
+ * URL: the host skips its consent screen for users who already authorized the
16
+ * App, so one click would otherwise bind the victim's identity to the
17
+ * attacker's principal. The code is shown only in the victim's browser.
18
+ * A host user belongs to at most one principal.
19
+ */
20
+ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
21
+ import { Prisma } from "#prisma";
22
+ import { logger } from "./logger.js";
23
+ const log = logger.child({ module: "host-identity-links" });
24
+ export const HOST_LINK_TTL_MS = 10 * 60_000;
25
+ export const HOST_LINK_MAX_ATTEMPTS = 5;
26
+ export const HOST_LINK_MAX_PENDING = 5;
27
+ const BASE32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
28
+ const CODE_LENGTH = 8;
29
+ export class HostLinkError extends Error {
30
+ status;
31
+ constructor(status, message) {
32
+ super(message);
33
+ this.status = status;
34
+ this.name = "HostLinkError";
35
+ }
36
+ }
37
+ /** The browser callback a provider's link flow returns to (registered as the App's Callback URL). */
38
+ export function userCallbackPath(provider) {
39
+ return `/hosts/${provider}/user-callback`;
40
+ }
41
+ function sha256(value) {
42
+ return createHash("sha256").update(value, "utf8").digest("hex");
43
+ }
44
+ function confirmationCode() {
45
+ // 5 bits per character from a uniformly random byte's low bits (256 % 32 === 0: no bias).
46
+ return [...randomBytes(CODE_LENGTH)].map((b) => BASE32[b & 31]).join("");
47
+ }
48
+ export async function startHostIdentityLink(input) {
49
+ const now = input.now ?? new Date();
50
+ const provider = input.authorizer.provider;
51
+ await input.db.hostIdentityLinkRequest.deleteMany({ where: { expiresAt: { lte: now } } });
52
+ const pending = await input.db.hostIdentityLinkRequest.count({
53
+ where: { principalId: input.principalId, provider },
54
+ });
55
+ if (pending >= HOST_LINK_MAX_PENDING) {
56
+ throw new HostLinkError(429, "Too many account links in progress; finish one or wait 10 minutes.");
57
+ }
58
+ const state = randomBytes(32).toString("base64url");
59
+ const codeVerifier = randomBytes(32).toString("base64url");
60
+ const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
61
+ const expiresAt = new Date(now.getTime() + HOST_LINK_TTL_MS);
62
+ await input.db.hostIdentityLinkRequest.create({
63
+ data: { principalId: input.principalId, provider, stateHash: sha256(state), codeVerifier, expiresAt },
64
+ });
65
+ return {
66
+ authorizeUrl: input.authorizer.authorizeUrl({ state, codeChallenge, redirectUri: input.redirectUri }),
67
+ expiresAt,
68
+ };
69
+ }
70
+ export async function completeHostIdentityCallback(input) {
71
+ const now = input.now ?? new Date();
72
+ if (!input.state || !input.code || input.state.length > 256 || input.code.length > 256)
73
+ return { kind: "invalid" };
74
+ const stateHash = sha256(input.state);
75
+ // Single use: only the first callback for a live state wins.
76
+ const { count } = await input.db.hostIdentityLinkRequest.updateMany({
77
+ where: { stateHash, provider: input.authorizer.provider, callbackAt: null, expiresAt: { gt: now } },
78
+ data: { callbackAt: now },
79
+ });
80
+ if (count !== 1)
81
+ return { kind: "invalid" };
82
+ const request = await input.db.hostIdentityLinkRequest.findUnique({
83
+ where: { stateHash },
84
+ include: { principal: { select: { subject: true } } },
85
+ });
86
+ if (!request)
87
+ return { kind: "invalid" };
88
+ let user;
89
+ try {
90
+ user = await input.authorizer.complete({
91
+ code: input.code,
92
+ codeVerifier: request.codeVerifier,
93
+ redirectUri: input.redirectUri,
94
+ });
95
+ }
96
+ catch (err) {
97
+ log.warn({ err, provider: request.provider }, "host account link: the host refused the authorization");
98
+ await input.db.hostIdentityLinkRequest.delete({ where: { id: request.id } }).catch(() => undefined);
99
+ return { kind: "failed" };
100
+ }
101
+ const code = confirmationCode();
102
+ await input.db.hostIdentityLinkRequest.update({
103
+ where: { id: request.id },
104
+ // The verifier has done its job; keep nothing reusable.
105
+ data: { hostUserId: user.hostUserId, login: user.login, confirmHash: sha256(code), codeVerifier: "" },
106
+ });
107
+ return {
108
+ kind: "confirm",
109
+ code: `${code.slice(0, 4)}-${code.slice(4)}`,
110
+ login: user.login,
111
+ subject: request.principal.subject,
112
+ };
113
+ }
114
+ export async function confirmHostIdentityLink(input) {
115
+ const now = input.now ?? new Date();
116
+ const code = input.confirmationCode.toUpperCase().replace(/[\s-]/g, "");
117
+ if (!new RegExp(`^[${BASE32}]{${CODE_LENGTH}}$`).test(code)) {
118
+ throw new HostLinkError(400, "The confirmation code is 8 letters and digits, as shown on the page (XXXX-XXXX).");
119
+ }
120
+ const request = await input.db.hostIdentityLinkRequest.findFirst({
121
+ where: {
122
+ principalId: input.principalId,
123
+ provider: input.provider,
124
+ confirmHash: { not: null },
125
+ hostUserId: { not: null },
126
+ login: { not: null },
127
+ attempts: { lt: HOST_LINK_MAX_ATTEMPTS },
128
+ expiresAt: { gt: now },
129
+ },
130
+ orderBy: { callbackAt: "desc" },
131
+ });
132
+ if (!request?.confirmHash || !request.hostUserId || !request.login)
133
+ throw noPendingLink();
134
+ // Spend an attempt BEFORE comparing, atomically: concurrent guesses can't
135
+ // each get a comparison before the counter saturates.
136
+ const claimed = await input.db.hostIdentityLinkRequest.updateMany({
137
+ where: { id: request.id, attempts: { lt: HOST_LINK_MAX_ATTEMPTS }, expiresAt: { gt: now } },
138
+ data: { attempts: { increment: 1 } },
139
+ });
140
+ if (claimed.count !== 1)
141
+ throw noPendingLink();
142
+ const presented = Buffer.from(sha256(code), "hex");
143
+ const expected = Buffer.from(request.confirmHash, "hex");
144
+ if (presented.length !== expected.length || !timingSafeEqual(presented, expected)) {
145
+ const after = await input.db.hostIdentityLinkRequest.findUnique({ where: { id: request.id } });
146
+ const left = Math.max(0, HOST_LINK_MAX_ATTEMPTS - (after?.attempts ?? HOST_LINK_MAX_ATTEMPTS));
147
+ throw new HostLinkError(400, left > 0
148
+ ? `That confirmation code is not right; ${left} attempt${left === 1 ? "" : "s"} left.`
149
+ : "That confirmation code is not right, and this link attempt is used up. Start again with link_host_account.");
150
+ }
151
+ const hostUserId = request.hostUserId;
152
+ const login = request.login;
153
+ try {
154
+ await input.db.$transaction(async (tx) => {
155
+ // Consume the request first: a concurrent confirmation of the same code loses here.
156
+ // (Its attempt was already spent above, so attempts may now equal the limit.)
157
+ const consumed = await tx.hostIdentityLinkRequest.deleteMany({
158
+ where: { id: request.id, expiresAt: { gt: now } },
159
+ });
160
+ if (consumed.count !== 1)
161
+ throw new HostLinkError(404, "This link attempt is no longer valid; start again.");
162
+ const holder = await tx.hostIdentity.findUnique({
163
+ where: { provider_hostUserId: { provider: input.provider, hostUserId } },
164
+ });
165
+ if (holder && holder.principalId !== input.principalId)
166
+ throw alreadyLinked();
167
+ await tx.hostIdentity.upsert({
168
+ where: { principalId_provider: { principalId: input.principalId, provider: input.provider } },
169
+ create: { principalId: input.principalId, provider: input.provider, hostUserId, login },
170
+ update: { hostUserId, login, linkedAt: now },
171
+ });
172
+ }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
173
+ }
174
+ catch (err) {
175
+ if (err instanceof HostLinkError)
176
+ throw err;
177
+ if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002")
178
+ throw alreadyLinked();
179
+ throw err;
180
+ }
181
+ return { hostUserId, login };
182
+ }
183
+ function noPendingLink() {
184
+ return new HostLinkError(404, "No account link is waiting for a confirmation code from you. Start again with link_host_account (no code).");
185
+ }
186
+ function alreadyLinked() {
187
+ return new HostLinkError(409, "That GitHub account is already linked to another wardby account. An operator can unlink it " +
188
+ "(`wardby auth host-account unlink --subject <subject>`) if it should move to you.");
189
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The status comment for a run started by an @-mention. Where to comment is
3
+ * recorded with the run itself (mentionStatusRow, written in the dispatch
4
+ * transaction); the "working on it" comment is posted right after the webhook
5
+ * is answered, and edited once with the outcome — the pull requests its coding
6
+ * sub-runs opened or updated, the agent's reply when none came out, or why it
7
+ * did not succeed. A run that ends before its comment was posted (the instance
8
+ * died first, say) still gets one: the reconciler posts the outcome as a new
9
+ * comment. Best effort throughout: nothing here throws.
10
+ */
11
+ import type { Prisma, PrismaClient, Run } from "#prisma";
12
+ import type { CodeReviewHost, ReviewHostProvider, ReviewHostRegistry } from "../providers/review-host/types.js";
13
+ export type HostStatusDb = Pick<PrismaClient, "runHostStatus" | "run">;
14
+ type FinishedRun = Pick<Run, "id" | "status" | "finalText">;
15
+ /** The parts of a CodingRun result this comment uses. */
16
+ export interface PullRequestOutcome {
17
+ outcome: "pull_request_opened" | "pull_request_updated";
18
+ repository: string;
19
+ pullRequestNumber: number;
20
+ }
21
+ /** A sub-run that ended without succeeding. */
22
+ export interface FailedChild {
23
+ id: string;
24
+ status: string;
25
+ /** The coding sub-run's failure category; `provider_<class>` names a model-provider refusal. */
26
+ failureCategory?: string | null;
27
+ /** The sub-run's Run.error; read only for a service refusal's host sentence (coding/services/wording.ts). */
28
+ error?: string | null;
29
+ /** The service names on the coding sub-run (CodingRun.services), for one that never became ready. */
30
+ services?: string[];
31
+ }
32
+ export declare function workingBody(runId: string): string;
33
+ /** The status row for a mention run, created in the same transaction as the run. */
34
+ export declare function mentionStatusRow(provider: ReviewHostProvider, event: {
35
+ repository: string;
36
+ number: number;
37
+ replyToReviewCommentId?: string;
38
+ }, runId: string): Prisma.RunHostStatusUncheckedCreateInput;
39
+ export declare function outcomeBody(run: FinishedRun, repository: string, pullRequests: PullRequestOutcome[], failedChildren?: FailedChild[], opts?: {
40
+ budgetSentence?: string;
41
+ }): string;
42
+ /**
43
+ * Writes a finished run's outcome to its status comment and marks it
44
+ * complete: edits the comment when it exists; when it does not yet, posts the
45
+ * outcome as a new comment only if `postIfMissing` (the reconciler, well after
46
+ * the run ended), and otherwise leaves the row for postMentionStatus, which
47
+ * is about to post it. A failed host call leaves the row open for the
48
+ * reconciler to retry.
49
+ */
50
+ export declare function completeHostStatus(db: HostStatusDb, run: FinishedRun, hosts: ReviewHostRegistry | undefined, opts?: {
51
+ postIfMissing?: boolean;
52
+ }): Promise<void>;
53
+ /**
54
+ * Posts the "working on it" comment for a mention run whose status row was
55
+ * written at dispatch, and records the comment on the row. When the run
56
+ * already ended (a fast failure can beat this follow-up), completes it at
57
+ * once. Does nothing when the row is gone, already has a comment, or is
58
+ * already complete. Never throws.
59
+ */
60
+ export declare function postMentionStatus(db: HostStatusDb, host: CodeReviewHost, runId: string, hosts: ReviewHostRegistry | undefined): Promise<void>;
61
+ export {};
@@ -0,0 +1,211 @@
1
+ import { storedServiceNames } from "../coding/services/catalog.js";
2
+ import { SERVICE_UNREADY_CATEGORY, serviceRefusalSentence, serviceUnreadySentence, } from "../coding/services/wording.js";
3
+ import { PROTECTED_PATH_CATEGORY, PROTECTED_PATH_HOST_LINE } from "../coding/protected-path-wording.js";
4
+ import { loadBudgetSentence } from "./budget-wording.js";
5
+ import { logger } from "./logger.js";
6
+ import { providerClassOfCategory, providerSentence } from "./provider-wording.js";
7
+ const log = logger.child({ module: "host-status" });
8
+ /** Keeps the edited comment readable; the full reply stays on the run. */
9
+ const MAX_REPLY_CHARS = 2000;
10
+ const TERMINAL = new Set(["succeeded", "failed", "refused", "lost", "budget_exhausted", "cancelled"]);
11
+ const runLine = (runId) => `<sub>wardby run \`${runId}\`</sub>`;
12
+ export function workingBody(runId) {
13
+ return `👀 Working on it.\n\n${runLine(runId)}`;
14
+ }
15
+ /** The status row for a mention run, created in the same transaction as the run. */
16
+ export function mentionStatusRow(provider, event, runId) {
17
+ return {
18
+ runId,
19
+ provider,
20
+ repository: event.repository,
21
+ number: event.number,
22
+ commentKind: event.replyToReviewCommentId ? "inline" : "conversation",
23
+ replyToReviewCommentId: event.replyToReviewCommentId ?? null,
24
+ };
25
+ }
26
+ function pullRequestOutcome(result) {
27
+ if (!result || typeof result !== "object")
28
+ return null;
29
+ const r = result;
30
+ if (r.outcome !== "pull_request_opened" && r.outcome !== "pull_request_updated")
31
+ return null;
32
+ if (typeof r.repository !== "string" || typeof r.pullRequestNumber !== "number")
33
+ return null;
34
+ if (!Number.isInteger(r.pullRequestNumber) || r.pullRequestNumber <= 0)
35
+ return null;
36
+ return { outcome: r.outcome, repository: r.repository, pullRequestNumber: r.pullRequestNumber };
37
+ }
38
+ /** The agent's reply as a quote, cut to MAX_REPLY_CHARS, with @-mentions defused so nobody is pinged. */
39
+ function quoteReply(text) {
40
+ const cut = text.length > MAX_REPLY_CHARS ? `${text.slice(0, MAX_REPLY_CHARS)}…` : text;
41
+ return cut
42
+ .replace(/@(?=[\w-])/g, "@​")
43
+ .split("\n")
44
+ .map((line) => `> ${line}`)
45
+ .join("\n");
46
+ }
47
+ export function outcomeBody(run, repository, pullRequests, failedChildren = [], opts = {}) {
48
+ const links = pullRequests.map((pr) => {
49
+ const ref = pr.repository.toLowerCase() === repository.toLowerCase()
50
+ ? `#${pr.pullRequestNumber}`
51
+ : `${pr.repository}#${pr.pullRequestNumber}`;
52
+ return pr.outcome === "pull_request_opened" ? `Opened ${ref}` : `Pushed changes to ${ref}`;
53
+ });
54
+ const partial = links.length > 0 ? `\n\n${links.join(", ")}.` : "";
55
+ const footer = runLine(run.id);
56
+ if (run.status === "lost") {
57
+ return `❌ Interrupted before it finished (for example, wardby restarted). Repeat your request to retry.${partial}\n\n${footer}`;
58
+ }
59
+ if (run.status === "budget_exhausted" || run.status === "refused") {
60
+ // A refused run could not start for lack of budget; see budgetSentence.
61
+ return `❌ ${opts.budgetSentence ?? "Out of budget."}${partial}\n\n${footer}`;
62
+ }
63
+ if (run.status !== "succeeded") {
64
+ // Only the status: a run's error text can carry internal detail that does not belong on the host.
65
+ return `❌ Stopped: the run ended with status \`${run.status}\`.${partial}\n\n${footer}`;
66
+ }
67
+ const reply = run.finalText?.trim();
68
+ const quoted = reply ? `\n\n${quoteReply(reply)}` : "";
69
+ if (failedChildren.length > 0) {
70
+ // The agent itself finished, but the work it handed off did not.
71
+ const outOfBudget = failedChildren.filter((c) => c.status === "budget_exhausted");
72
+ const providerClassOf = (c) => c.status === "failed" ? providerClassOfCategory(c.failureCategory) : null;
73
+ // A coding sub-run its services stopped: refused at dispatch, or a sidecar never became ready.
74
+ const serviceSentenceOf = (c) => c.status === "refused"
75
+ ? serviceRefusalSentence(c.error)
76
+ : c.status === "failed" && c.failureCategory === SERVICE_UNREADY_CATEGORY
77
+ ? serviceUnreadySentence(c.services ?? [])
78
+ : null;
79
+ // No path reaches this comment (see coding/protected-path-wording.ts), only the category.
80
+ const protectedPathOf = (c) => c.status === "failed" && c.failureCategory === PROTECTED_PATH_CATEGORY;
81
+ const providerClasses = new Set(failedChildren.map(providerClassOf).filter((c) => c !== null));
82
+ const serviceSentences = new Set(failedChildren.map(serviceSentenceOf).filter((s) => s !== null));
83
+ const protectedPathFailed = failedChildren.some(protectedPathOf);
84
+ const other = failedChildren.filter((c) => c.status !== "budget_exhausted" &&
85
+ providerClassOf(c) === null &&
86
+ serviceSentenceOf(c) === null &&
87
+ !protectedPathOf(c));
88
+ const lines = [];
89
+ if (outOfBudget.length > 0) {
90
+ lines.push(`A sub-run ran out of budget: ${outOfBudget.map((c) => `\`${c.id}\``).join(", ")}.`);
91
+ }
92
+ // The class only, never the provider's code: the sentence says who has to act.
93
+ for (const providerClass of providerClasses) {
94
+ lines.push(`A sub-run could not reach the model: ${providerSentence(providerClass)}`);
95
+ }
96
+ // The fixed sentence only, never the Run.error code in front of it.
97
+ for (const sentence of serviceSentences) {
98
+ lines.push(`A sub-run could not start: ${sentence}`);
99
+ }
100
+ if (protectedPathFailed)
101
+ lines.push(PROTECTED_PATH_HOST_LINE);
102
+ if (other.length > 0) {
103
+ lines.push(`A sub-run did not succeed: ${other.map((c) => `\`${c.id}\` (\`${c.status}\`)`).join(", ")}.`);
104
+ }
105
+ return `❌ ${lines.join(" ")}${partial}${quoted}\n\n${footer}`;
106
+ }
107
+ if (links.length > 0)
108
+ return `✅ ${links.join(", ")}.\n\n${footer}`;
109
+ return `✅ Finished without opening a pull request.${quoted}\n\n${footer}`;
110
+ }
111
+ /**
112
+ * Writes a finished run's outcome to its status comment and marks it
113
+ * complete: edits the comment when it exists; when it does not yet, posts the
114
+ * outcome as a new comment only if `postIfMissing` (the reconciler, well after
115
+ * the run ended), and otherwise leaves the row for postMentionStatus, which
116
+ * is about to post it. A failed host call leaves the row open for the
117
+ * reconciler to retry.
118
+ */
119
+ export async function completeHostStatus(db, run, hosts, opts = {}) {
120
+ if (!hosts)
121
+ return;
122
+ try {
123
+ const status = await db.runHostStatus.findUnique({ where: { runId: run.id } });
124
+ if (!status || status.completedAt)
125
+ return;
126
+ if (!status.commentId && !opts.postIfMissing)
127
+ return;
128
+ const host = hosts[status.provider];
129
+ if (!host)
130
+ return;
131
+ const children = await db.run.findMany({
132
+ where: { parentRunId: run.id },
133
+ select: {
134
+ id: true,
135
+ status: true,
136
+ error: true,
137
+ codingRun: { select: { result: true, failureCategory: true, services: true } },
138
+ },
139
+ orderBy: { startedAt: "asc" },
140
+ });
141
+ const pullRequests = children
142
+ .map((c) => pullRequestOutcome(c.codingRun?.result))
143
+ .filter((pr) => pr !== null);
144
+ const failedChildren = children
145
+ .filter((c) => TERMINAL.has(c.status) && c.status !== "succeeded")
146
+ .map((c) => ({
147
+ id: c.id,
148
+ status: c.status,
149
+ failureCategory: c.codingRun?.failureCategory ?? null,
150
+ error: c.error ?? null,
151
+ services: storedServiceNames(c.codingRun?.services),
152
+ }));
153
+ const budgetSentence = run.status === "budget_exhausted" || run.status === "refused"
154
+ ? await loadBudgetSentence(db, run.id, run.status)
155
+ : undefined;
156
+ const body = outcomeBody(run, status.repository, pullRequests, failedChildren, { budgetSentence });
157
+ let commentId = status.commentId;
158
+ if (commentId) {
159
+ await host.editComment(status.repository, {
160
+ kind: status.commentKind === "inline" ? "inline" : "conversation",
161
+ id: commentId,
162
+ body,
163
+ });
164
+ }
165
+ else {
166
+ commentId = (await host.comment(status.repository, {
167
+ number: status.number,
168
+ body,
169
+ ...(status.replyToReviewCommentId ? { replyToReviewCommentId: status.replyToReviewCommentId } : {}),
170
+ })).id;
171
+ }
172
+ await db.runHostStatus.update({ where: { runId: run.id }, data: { commentId, completedAt: new Date() } });
173
+ }
174
+ catch (err) {
175
+ log.warn({ err, runId: run.id }, "could not complete the run's status comment");
176
+ }
177
+ }
178
+ /**
179
+ * Posts the "working on it" comment for a mention run whose status row was
180
+ * written at dispatch, and records the comment on the row. When the run
181
+ * already ended (a fast failure can beat this follow-up), completes it at
182
+ * once. Does nothing when the row is gone, already has a comment, or is
183
+ * already complete. Never throws.
184
+ */
185
+ export async function postMentionStatus(db, host, runId, hosts) {
186
+ try {
187
+ const status = await db.runHostStatus.findUnique({ where: { runId } });
188
+ if (!status || status.commentId || status.completedAt)
189
+ return;
190
+ const posted = await host.comment(status.repository, {
191
+ number: status.number,
192
+ body: workingBody(runId),
193
+ ...(status.replyToReviewCommentId ? { replyToReviewCommentId: status.replyToReviewCommentId } : {}),
194
+ });
195
+ const claimed = await db.runHostStatus.updateMany({
196
+ where: { runId, commentId: null, completedAt: null },
197
+ data: { commentId: posted.id },
198
+ });
199
+ if (claimed.count === 0) {
200
+ // The reconciler posted the outcome meanwhile; this comment is surplus.
201
+ log.warn({ runId }, "status comment posted after the outcome; leaving both");
202
+ return;
203
+ }
204
+ const run = await db.run.findUnique({ where: { id: runId }, select: { id: true, status: true, finalText: true } });
205
+ if (run && TERMINAL.has(run.status))
206
+ await completeHostStatus(db, run, hosts);
207
+ }
208
+ catch (err) {
209
+ log.warn({ err, runId }, "could not post the status comment");
210
+ }
211
+ }
@@ -57,11 +57,10 @@
57
57
  * later in the process, would replace this Agent with an h2-enabled one and
58
58
  * silently restore the corruption — the price of fixing this with a dispatcher
59
59
  * instead of owning the fetch stack. Nothing in the runtime image does that
60
- * today: the only other `setGlobalDispatcher` calls in the tree are undici
61
- * copies bundled inside `@prisma/client/runtime/binary.*` (guarded by the same
62
- * `=== undefined` check, and the binary engine is not the default) and
60
+ * today: the only other `setGlobalDispatcher` call in the tree is
63
61
  * `node-fetch-native` under the `prisma` CLI, which `deploy/Dockerfile:25`
64
- * asserts is absent from the runtime image.
62
+ * asserts is absent from the runtime image. (Prisma 7's client runtime and
63
+ * `@prisma/adapter-pg` contain none; Prisma 6's `runtime/binary.*` did.)
65
64
  *
66
65
  * Restoring Node's OWN dispatcher instead is not possible, though not for the
67
66
  * reason one might guess: undici defines the legacy symbol `configurable: false`
@@ -0,0 +1,8 @@
1
+ /** Registers a run for the duration of `work`. */
2
+ export declare function trackRun<T>(runId: string, work: () => Promise<T>): Promise<T>;
3
+ export declare function inFlightRunIds(): string[];
4
+ /**
5
+ * Resolves once no run is executing, or after `timeoutMs`, whichever is
6
+ * first. Returns the ids still executing at that point (empty when drained).
7
+ */
8
+ export declare function waitForInFlightRuns(timeoutMs: number): Promise<string[]>;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Counts the native runs executing in this process, so a shutdown can wait
3
+ * for them instead of abandoning them. Every native run, however it was
4
+ * started (a direct start, a DBOS workflow, a workflow adopted from a dead
5
+ * instance), goes through executeRun, which registers here. Coding jobs are
6
+ * not counted: they run in their own pods and a new instance collects them.
7
+ */
8
+ import { logger } from "./logger.js";
9
+ const log = logger.child({ module: "in-flight-runs" });
10
+ const active = new Set();
11
+ let waiters = [];
12
+ /** Registers a run for the duration of `work`. */
13
+ export async function trackRun(runId, work) {
14
+ active.add(runId);
15
+ try {
16
+ return await work();
17
+ }
18
+ finally {
19
+ active.delete(runId);
20
+ if (active.size === 0) {
21
+ const done = waiters;
22
+ waiters = [];
23
+ for (const wake of done)
24
+ wake();
25
+ }
26
+ }
27
+ }
28
+ export function inFlightRunIds() {
29
+ return [...active];
30
+ }
31
+ /**
32
+ * Resolves once no run is executing, or after `timeoutMs`, whichever is
33
+ * first. Returns the ids still executing at that point (empty when drained).
34
+ */
35
+ export async function waitForInFlightRuns(timeoutMs) {
36
+ if (active.size === 0 || timeoutMs <= 0)
37
+ return inFlightRunIds();
38
+ log.info({ runIds: inFlightRunIds(), timeoutMs }, "waiting for in-flight runs before shutdown");
39
+ await new Promise((resolve) => {
40
+ const timer = setTimeout(() => {
41
+ waiters = waiters.filter((w) => w !== wake);
42
+ resolve();
43
+ }, timeoutMs);
44
+ const wake = () => {
45
+ clearTimeout(timer);
46
+ resolve();
47
+ };
48
+ waiters.push(wake);
49
+ });
50
+ const remaining = inFlightRunIds();
51
+ if (remaining.length > 0)
52
+ log.warn({ runIds: remaining }, "shutdown drain timed out; these runs are abandoned");
53
+ else
54
+ log.info("in-flight runs finished; shutting down");
55
+ return remaining;
56
+ }
@@ -7,7 +7,7 @@
7
7
  * expired, or already ours, and `RETURNING` tells the caller which case it
8
8
  * was — zero rows means someone else holds an unexpired lease.
9
9
  */
10
- import type { PrismaClient } from "@prisma/client";
10
+ import type { PrismaClient } from "#prisma";
11
11
  export type LeaseDb = Pick<PrismaClient, "$queryRaw">;
12
12
  /** Attempts to acquire (or renew) the lease for `scope`. Returns whether `holder` now holds it. */
13
13
  export declare function tryAcquireLease(db: LeaseDb, scope: string, holder: string, ttlMs: number): Promise<boolean>;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Creates `path` as a private directory, or verifies an existing one is safe to
3
+ * trust.
4
+ *
5
+ * `mkdir(..., { mode: 0o700 })` does nothing to a directory that already
6
+ * exists. The coding state roots default to fixed names under the OS temp
7
+ * directory, so on a shared host another user could create one first -- or
8
+ * point it somewhere else with a symlink -- and then read or plant the job
9
+ * records, workspaces and artifacts wardby keeps there. This refuses:
10
+ *
11
+ * - a symlink, or anything that is not a directory;
12
+ * - a directory owned by a different user (root excepted: an operator-mounted
13
+ * volume is commonly root-owned);
14
+ * - a directory any other user can write to.
15
+ *
16
+ * Group write is allowed, since a Kubernetes `fsGroup` volume is group-writable
17
+ * by design and its group is chosen by the operator. Ownership and mode are not
18
+ * checked on platforms without POSIX uids.
19
+ */
20
+ export declare function ensurePrivateDirectory(path: string): Promise<void>;
@@ -0,0 +1,36 @@
1
+ import { lstat, mkdir } from "node:fs/promises";
2
+ /**
3
+ * Creates `path` as a private directory, or verifies an existing one is safe to
4
+ * trust.
5
+ *
6
+ * `mkdir(..., { mode: 0o700 })` does nothing to a directory that already
7
+ * exists. The coding state roots default to fixed names under the OS temp
8
+ * directory, so on a shared host another user could create one first -- or
9
+ * point it somewhere else with a symlink -- and then read or plant the job
10
+ * records, workspaces and artifacts wardby keeps there. This refuses:
11
+ *
12
+ * - a symlink, or anything that is not a directory;
13
+ * - a directory owned by a different user (root excepted: an operator-mounted
14
+ * volume is commonly root-owned);
15
+ * - a directory any other user can write to.
16
+ *
17
+ * Group write is allowed, since a Kubernetes `fsGroup` volume is group-writable
18
+ * by design and its group is chosen by the operator. Ownership and mode are not
19
+ * checked on platforms without POSIX uids.
20
+ */
21
+ export async function ensurePrivateDirectory(path) {
22
+ await mkdir(path, { recursive: true, mode: 0o700 });
23
+ const metadata = await lstat(path);
24
+ if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
25
+ throw new Error(`${path} must be a real directory, not a symlink.`);
26
+ }
27
+ const uid = process.getuid?.();
28
+ if (uid === undefined)
29
+ return;
30
+ if (metadata.uid !== uid && metadata.uid !== 0) {
31
+ throw new Error(`${path} is owned by uid ${metadata.uid}, not this process (uid ${uid}). Remove it or choose another path.`);
32
+ }
33
+ if ((metadata.mode & 0o002) !== 0) {
34
+ throw new Error(`${path} is writable by other users. Run chmod o-w on it or choose another path.`);
35
+ }
36
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * What a host (a PR comment, a check summary) is told when the model provider
3
+ * itself refused a run's requests: the class of problem and who has to act.
4
+ * Public repositories read these, so they never carry the provider's own
5
+ * error code or message; the code stays in the operator log.
6
+ */
7
+ export type ProviderFailureClass = "quota" | "rate_limited" | "unavailable" | "rejected";
8
+ /** Files an upstream error code (or `http_<status>`) under the class of problem it names. */
9
+ export declare function classifyProviderFailure(code: string): ProviderFailureClass;
10
+ export declare function providerSentence(providerClass: ProviderFailureClass): string;
11
+ /** The class a run's `provider_<class>` failure category names, or null for any other category. */
12
+ export declare function providerClassOfCategory(category: string | null | undefined): ProviderFailureClass | null;