@wardby/cli 0.5.3 → 0.6.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 (370) hide show
  1. package/.env.example +33 -0
  2. package/README.md +25 -14
  3. package/dist/claude-coding-worker/context.d.ts +22 -0
  4. package/dist/claude-coding-worker/context.js +92 -0
  5. package/dist/claude-coding-worker/driver.d.ts +7 -1
  6. package/dist/claude-coding-worker/driver.js +38 -5
  7. package/dist/claude-coding-worker/sdk.js +13 -4
  8. package/dist/cli-help.d.ts +1 -1
  9. package/dist/cli-help.js +5 -2
  10. package/dist/cli-native-mode.d.ts +9 -0
  11. package/dist/cli-native-mode.js +20 -0
  12. package/dist/cli.d.ts +3 -1
  13. package/dist/cli.js +141 -5
  14. package/dist/coding/claude-context.d.ts +45 -0
  15. package/dist/coding/claude-context.js +434 -0
  16. package/dist/coding/profile.d.ts +12 -0
  17. package/dist/coding/profile.js +6 -0
  18. package/dist/coding/protocol.d.ts +104 -0
  19. package/dist/coding/protocol.js +87 -3
  20. package/dist/coding-worker/artifact.d.ts +8 -0
  21. package/dist/coding-worker/artifact.js +2 -2
  22. package/dist/coding-worker/driver.js +2 -0
  23. package/dist/coding-worker/sdk.d.ts +6 -0
  24. package/dist/coding-worker/sdk.js +1 -0
  25. package/dist/coding-worker/skills.d.ts +23 -0
  26. package/dist/coding-worker/skills.js +117 -0
  27. package/dist/coding-worker/types.d.ts +2 -0
  28. package/dist/config/github-allowed-accounts.d.ts +15 -0
  29. package/dist/config/github-allowed-accounts.js +36 -0
  30. package/dist/config/low-power.d.ts +12 -0
  31. package/dist/config/low-power.js +18 -0
  32. package/dist/config/mobile.d.ts +12 -0
  33. package/dist/config/mobile.js +18 -0
  34. package/dist/config/providers.d.ts +53 -0
  35. package/dist/config/providers.js +92 -6
  36. package/dist/core/db.d.ts +16 -6
  37. package/dist/core/db.js +12 -10
  38. package/dist/core/dispatch.d.ts +11 -0
  39. package/dist/core/dispatch.js +36 -1
  40. package/dist/core/engine-native.js +9 -5
  41. package/dist/core/host-events.d.ts +49 -3
  42. package/dist/core/host-events.js +289 -15
  43. package/dist/core/issue-bridge.js +42 -2
  44. package/dist/core/issue-events.js +12 -1
  45. package/dist/core/issue-status.js +14 -3
  46. package/dist/core/merge-order-check.d.ts +129 -0
  47. package/dist/core/merge-order-check.js +336 -0
  48. package/dist/core/notification-dispatcher.d.ts +70 -0
  49. package/dist/core/notification-dispatcher.js +368 -0
  50. package/dist/core/notification-templates.d.ts +25 -0
  51. package/dist/core/notification-templates.js +105 -0
  52. package/dist/core/notifications.d.ts +10 -0
  53. package/dist/core/notifications.js +38 -0
  54. package/dist/core/pg-auth.d.ts +22 -0
  55. package/dist/core/pg-auth.js +152 -0
  56. package/dist/core/power-composition.d.ts +16 -0
  57. package/dist/core/power-composition.js +37 -0
  58. package/dist/core/power-idle.d.ts +14 -0
  59. package/dist/core/power-idle.js +30 -0
  60. package/dist/core/power.d.ts +71 -0
  61. package/dist/core/power.js +202 -0
  62. package/dist/core/reconciler.d.ts +4 -2
  63. package/dist/core/reconciler.js +29 -5
  64. package/dist/core/related-pull-requests.d.ts +48 -4
  65. package/dist/core/related-pull-requests.js +113 -11
  66. package/dist/core/repo-access.d.ts +15 -0
  67. package/dist/core/repo-access.js +66 -0
  68. package/dist/core/review-fix.d.ts +37 -0
  69. package/dist/core/review-fix.js +207 -6
  70. package/dist/core/review-host-tools.d.ts +20 -0
  71. package/dist/core/review-host-tools.js +58 -2
  72. package/dist/core/runner.d.ts +336 -3
  73. package/dist/core/runner.js +894 -317
  74. package/dist/core/secret-broker-config.d.ts +103 -0
  75. package/dist/core/secret-broker-config.js +112 -0
  76. package/dist/core/secrets.d.ts +39 -4
  77. package/dist/core/secrets.js +148 -40
  78. package/dist/core/workflow-event-recorder.d.ts +14 -0
  79. package/dist/core/workflow-event-recorder.js +120 -0
  80. package/dist/core/workflow-events.d.ts +82 -0
  81. package/dist/core/workflow-events.js +44 -0
  82. package/dist/core/workflow-run-events.d.ts +16 -0
  83. package/dist/core/workflow-run-events.js +40 -0
  84. package/dist/generated/prisma/browser.d.ts +58 -0
  85. package/dist/generated/prisma/client.d.ts +58 -0
  86. package/dist/generated/prisma/commonInputTypes.d.ts +150 -0
  87. package/dist/generated/prisma/enums.d.ts +27 -0
  88. package/dist/generated/prisma/enums.js +23 -0
  89. package/dist/generated/prisma/internal/class.d.ts +88 -0
  90. package/dist/generated/prisma/internal/class.js +4 -4
  91. package/dist/generated/prisma/internal/prismaNamespace.d.ts +772 -1
  92. package/dist/generated/prisma/internal/prismaNamespace.js +135 -4
  93. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +139 -0
  94. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +135 -4
  95. package/dist/generated/prisma/models/Agent.d.ts +360 -1
  96. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +73 -1
  97. package/dist/generated/prisma/models/CodingRun.d.ts +205 -1
  98. package/dist/generated/prisma/models/DeferredReview.d.ts +78 -1
  99. package/dist/generated/prisma/models/NativeGatewayCall.d.ts +1675 -0
  100. package/dist/generated/prisma/models/NativeGatewayCall.js +1 -0
  101. package/dist/generated/prisma/models/NativeGatewaySession.d.ts +1517 -0
  102. package/dist/generated/prisma/models/NativeGatewaySession.js +1 -0
  103. package/dist/generated/prisma/models/NativeWarmWorker.d.ts +1296 -0
  104. package/dist/generated/prisma/models/NativeWarmWorker.js +1 -0
  105. package/dist/generated/prisma/models/NotificationChannel.d.ts +1543 -0
  106. package/dist/generated/prisma/models/NotificationChannel.js +1 -0
  107. package/dist/generated/prisma/models/NotificationDelivery.d.ts +1502 -0
  108. package/dist/generated/prisma/models/NotificationDelivery.js +1 -0
  109. package/dist/generated/prisma/models/NotificationThread.d.ts +1090 -0
  110. package/dist/generated/prisma/models/NotificationThread.js +1 -0
  111. package/dist/generated/prisma/models/Run.d.ts +826 -1
  112. package/dist/generated/prisma/models/RunHostCheck.d.ts +36 -1
  113. package/dist/generated/prisma/models/Secret.d.ts +37 -1
  114. package/dist/generated/prisma/models/SecretBrokerChange.d.ts +1101 -0
  115. package/dist/generated/prisma/models/SecretBrokerChange.js +1 -0
  116. package/dist/generated/prisma/models/WorkflowEvent.d.ts +1422 -0
  117. package/dist/generated/prisma/models/WorkflowEvent.js +1 -0
  118. package/dist/generated/prisma/models.d.ts +8 -0
  119. package/dist/help/build.js +4 -2
  120. package/dist/help/catalog.d.ts +1 -1
  121. package/dist/help/catalog.js +31 -6
  122. package/dist/help-index.json +1363 -176
  123. package/dist/import/create.js +17 -0
  124. package/dist/import/neutral-schema.d.ts +6 -2
  125. package/dist/import/neutral-schema.js +2 -0
  126. package/dist/mcp/auth/access.d.ts +1 -0
  127. package/dist/mcp/auth/repo-authorization.js +14 -0
  128. package/dist/mcp/auth/resource-server.d.ts +2 -1
  129. package/dist/mcp/auth/resource-server.js +7 -2
  130. package/dist/mcp/auth/self-hosted/browser.d.ts +1 -1
  131. package/dist/mcp/auth/self-hosted/browser.js +19 -6
  132. package/dist/mcp/context.d.ts +6 -0
  133. package/dist/mcp/host-events/github-ingress.d.ts +23 -1
  134. package/dist/mcp/host-events/github-ingress.js +68 -0
  135. package/dist/mcp/index.d.ts +13 -0
  136. package/dist/mcp/index.js +59 -10
  137. package/dist/mcp/tools/agents.js +82 -11
  138. package/dist/mcp/tools/notification-channels.d.ts +2 -0
  139. package/dist/mcp/tools/notification-channels.js +293 -0
  140. package/dist/mcp/tools/power.d.ts +2 -0
  141. package/dist/mcp/tools/power.js +77 -0
  142. package/dist/mcp/tools/runs.d.ts +0 -6
  143. package/dist/mcp/tools/runs.js +7 -2
  144. package/dist/mcp/tools/secret-broker-compat.d.ts +12 -0
  145. package/dist/mcp/tools/secret-broker-compat.js +23 -0
  146. package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -2
  147. package/dist/mcp/tools/secret-elicitation-form.js +200 -10
  148. package/dist/mcp/tools/secret-elicitation-server.js +8 -1
  149. package/dist/mcp/tools/secret-elicitation.d.ts +38 -3
  150. package/dist/mcp/tools/secret-elicitation.js +110 -12
  151. package/dist/mcp/tools/secrets.js +184 -7
  152. package/dist/mcp/tools/tools.d.ts +1 -1
  153. package/dist/mcp/tools/tools.js +22 -2
  154. package/dist/native-worker/composition.d.ts +23 -0
  155. package/dist/native-worker/composition.js +77 -0
  156. package/dist/native-worker/docker-isolation.d.ts +65 -0
  157. package/dist/native-worker/docker-isolation.js +183 -0
  158. package/dist/native-worker/docker-launcher.d.ts +74 -0
  159. package/dist/native-worker/docker-launcher.js +237 -0
  160. package/dist/native-worker/gateway-server.d.ts +39 -0
  161. package/dist/native-worker/gateway-server.js +315 -0
  162. package/dist/native-worker/gateway.d.ts +44 -0
  163. package/dist/native-worker/gateway.js +197 -0
  164. package/dist/native-worker/http-server.d.ts +22 -0
  165. package/dist/native-worker/http-server.js +109 -0
  166. package/dist/native-worker/http-transport.d.ts +17 -0
  167. package/dist/native-worker/http-transport.js +88 -0
  168. package/dist/native-worker/kubernetes-isolation.d.ts +90 -0
  169. package/dist/native-worker/kubernetes-isolation.js +218 -0
  170. package/dist/native-worker/kubernetes-launcher.d.ts +102 -0
  171. package/dist/native-worker/kubernetes-launcher.js +335 -0
  172. package/dist/native-worker/launch.d.ts +45 -0
  173. package/dist/native-worker/launch.js +44 -0
  174. package/dist/native-worker/ledger.d.ts +109 -0
  175. package/dist/native-worker/ledger.js +192 -0
  176. package/dist/native-worker/loopback.d.ts +12 -0
  177. package/dist/native-worker/loopback.js +49 -0
  178. package/dist/native-worker/main.d.ts +7 -0
  179. package/dist/native-worker/main.js +74 -0
  180. package/dist/native-worker/protocol.d.ts +744 -0
  181. package/dist/native-worker/protocol.js +200 -0
  182. package/dist/native-worker/sandbox-executor.d.ts +68 -0
  183. package/dist/native-worker/sandbox-executor.js +210 -0
  184. package/dist/native-worker/stdio.d.ts +29 -0
  185. package/dist/native-worker/stdio.js +188 -0
  186. package/dist/native-worker/warm-delivery.d.ts +16 -0
  187. package/dist/native-worker/warm-delivery.js +33 -0
  188. package/dist/native-worker/warm-pool-ledger.d.ts +39 -0
  189. package/dist/native-worker/warm-pool-ledger.js +73 -0
  190. package/dist/native-worker/warm-pool.d.ts +107 -0
  191. package/dist/native-worker/warm-pool.js +283 -0
  192. package/dist/native-worker/worker.d.ts +17 -0
  193. package/dist/native-worker/worker.js +115 -0
  194. package/dist/providers/auth/authorization-server.d.ts +3 -0
  195. package/dist/providers/auth/index.js +2 -0
  196. package/dist/providers/auth/self-hosted.d.ts +13 -1
  197. package/dist/providers/auth/self-hosted.js +54 -4
  198. package/dist/providers/chat/fake.d.ts +46 -0
  199. package/dist/providers/chat/fake.js +39 -0
  200. package/dist/providers/chat/index.d.ts +4 -0
  201. package/dist/providers/chat/index.js +8 -0
  202. package/dist/providers/chat/slack.d.ts +37 -0
  203. package/dist/providers/chat/slack.js +105 -0
  204. package/dist/providers/chat/types.d.ts +42 -0
  205. package/dist/providers/chat/types.js +19 -0
  206. package/dist/providers/engine/types.d.ts +19 -0
  207. package/dist/providers/engine/types.js +22 -0
  208. package/dist/providers/executor/composition.d.ts +24 -1
  209. package/dist/providers/executor/composition.js +39 -6
  210. package/dist/providers/executor/container.d.ts +24 -1
  211. package/dist/providers/executor/container.js +101 -11
  212. package/dist/providers/executor/dbos.d.ts +42 -0
  213. package/dist/providers/executor/dbos.js +118 -12
  214. package/dist/providers/executor/deferred.d.ts +56 -0
  215. package/dist/providers/executor/deferred.js +119 -0
  216. package/dist/providers/executor/routing.d.ts +37 -10
  217. package/dist/providers/executor/routing.js +73 -32
  218. package/dist/providers/executor/types.d.ts +12 -0
  219. package/dist/providers/jobs/docker.js +3 -3
  220. package/dist/providers/jobs/fake-kubernetes-api.d.ts +21 -2
  221. package/dist/providers/jobs/fake-kubernetes-api.js +42 -1
  222. package/dist/providers/jobs/kubernetes-api.d.ts +17 -1
  223. package/dist/providers/jobs/kubernetes-api.js +7 -0
  224. package/dist/providers/jobs/kubernetes-client.d.ts +10 -1
  225. package/dist/providers/jobs/kubernetes-client.js +52 -3
  226. package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +6 -1
  227. package/dist/providers/jobs/kubernetes-dry-run-fixture.js +6 -3
  228. package/dist/providers/jobs/kubernetes-isolation.d.ts +3 -1
  229. package/dist/providers/jobs/kubernetes-isolation.js +6 -0
  230. package/dist/providers/jobs/kubernetes-platform.d.ts +4 -1
  231. package/dist/providers/jobs/kubernetes-platform.js +38 -1
  232. package/dist/providers/jobs/kubernetes-preflight.d.ts +2 -1
  233. package/dist/providers/jobs/kubernetes-preflight.js +4 -2
  234. package/dist/providers/jobs/kubernetes.d.ts +36 -1
  235. package/dist/providers/jobs/kubernetes.js +74 -4
  236. package/dist/providers/jobs/metadata-probes.d.ts +11 -0
  237. package/dist/providers/jobs/metadata-probes.js +13 -0
  238. package/dist/providers/jobs/types.d.ts +9 -0
  239. package/dist/providers/review-host/github.d.ts +11 -1
  240. package/dist/providers/review-host/github.js +50 -0
  241. package/dist/providers/review-host/types.d.ts +23 -0
  242. package/dist/providers/vcs/github.d.ts +32 -6
  243. package/dist/providers/vcs/github.js +74 -16
  244. package/dist/quickstart/coding-seed.d.ts +10 -3
  245. package/dist/quickstart/coding-seed.js +74 -3
  246. package/dist/quickstart/images.d.ts +2 -0
  247. package/dist/quickstart/images.js +7 -1
  248. package/dist/quickstart/reviewer-prompt.d.ts +1 -1
  249. package/dist/quickstart/reviewer-prompt.js +1 -1
  250. package/dist/quickstart-images.json +1 -1
  251. package/dist/sandbox/brokered-fetch.d.ts +23 -0
  252. package/dist/sandbox/brokered-fetch.js +37 -0
  253. package/dist/sandbox/host-functions.d.ts +34 -0
  254. package/dist/sandbox/host-functions.js +144 -80
  255. package/dist/sandbox/prelude.js +7 -1
  256. package/dist/sandbox/run-in-sandbox.d.ts +19 -0
  257. package/dist/sandbox/run-in-sandbox.js +28 -10
  258. package/dist/sandbox/safe-fetch.d.ts +2 -0
  259. package/dist/sandbox/safe-fetch.js +3 -1
  260. package/dist/sandbox/secret-broker-sigv4.d.ts +2 -0
  261. package/dist/sandbox/secret-broker-sigv4.js +49 -0
  262. package/dist/sandbox/secret-broker.d.ts +34 -0
  263. package/dist/sandbox/secret-broker.js +162 -0
  264. package/dist/sandbox/user-tool.d.ts +14 -0
  265. package/dist/sandbox/user-tool.js +32 -0
  266. package/dist/serve.js +18 -0
  267. package/dist/viewer/api-schema.d.ts +210 -14
  268. package/dist/viewer/api-schema.js +22 -0
  269. package/dist/viewer/build-schemas.js +4 -1
  270. package/dist/viewer/event-bus.d.ts +6 -0
  271. package/dist/viewer/event-bus.js +16 -7
  272. package/dist/viewer/graph.d.ts +5 -2
  273. package/dist/viewer/graph.js +7 -2
  274. package/dist/viewer/http.d.ts +0 -1
  275. package/dist/viewer/http.js +2 -2
  276. package/dist/viewer/infra.js +25 -2
  277. package/dist/viewer/limits.d.ts +4 -0
  278. package/dist/viewer/limits.js +4 -0
  279. package/dist/viewer/openapi.d.ts +15 -0
  280. package/dist/viewer/openapi.js +172 -0
  281. package/docs/README.md +7 -0
  282. package/docs/agent-recipes.md +57 -8
  283. package/docs/architecture-runtime.md +16 -1
  284. package/docs/code-review-agents.md +139 -5
  285. package/docs/coding-agent-setup.md +113 -5
  286. package/docs/coding-packages.md +4 -2
  287. package/docs/coding-services.md +3 -1
  288. package/docs/coding-worker-byo-images.md +198 -6
  289. package/docs/coding-worker-isolation.md +68 -14
  290. package/docs/getting-started-gke.md +225 -9
  291. package/docs/getting-started-identity-provider.md +21 -10
  292. package/docs/getting-started.md +5 -4
  293. package/docs/jira-agents.md +5 -1
  294. package/docs/native-sandbox.md +635 -0
  295. package/docs/release-verification.md +9 -0
  296. package/docs/security-deployment.md +113 -12
  297. package/docs/slack-notifications.md +252 -0
  298. package/docs/tool-secrets.md +252 -0
  299. package/docs/viewer-api.md +46 -5
  300. package/docs/viewer-api.openapi.yml +1404 -0
  301. package/help/admin-viewer.md +6 -2
  302. package/help/agent-recipes.md +27 -0
  303. package/help/architecture-agent.md +1 -1
  304. package/help/brokered-secrets.md +79 -0
  305. package/help/build-claude-code-image.md +131 -0
  306. package/help/build-worker-image.md +3 -6
  307. package/help/code-review-agents.md +68 -6
  308. package/help/deploy-gke.md +8 -1
  309. package/help/deployment-targets.md +25 -14
  310. package/help/errors/coding-provider-not-configured.md +2 -2
  311. package/help/errors/database-auth-invalid.md +28 -0
  312. package/help/errors/github-account-not-allowed.md +25 -0
  313. package/help/errors/low-power-disabled.md +29 -0
  314. package/help/errors/low-power-scale-forbidden.md +35 -0
  315. package/help/errors/low-power-wake-timeout.md +30 -0
  316. package/help/errors/mobile-disabled.md +31 -0
  317. package/help/errors/native-sandbox-capacity.md +22 -0
  318. package/help/errors/native-sandbox-deadline-exceeded.md +19 -0
  319. package/help/errors/native-sandbox-docker-failed.md +20 -0
  320. package/help/errors/native-sandbox-gateway-unavailable.md +21 -0
  321. package/help/errors/native-sandbox-image-not-pinned.md +19 -0
  322. package/help/errors/native-sandbox-isolation-mismatch.md +20 -0
  323. package/help/errors/native-sandbox-network-unenforced.md +22 -0
  324. package/help/errors/native-sandbox-requires-catalog.md +31 -0
  325. package/help/errors/native-sandbox-unavailable.md +19 -0
  326. package/help/errors/native-sandbox-warm-delivery-failed.md +21 -0
  327. package/help/errors/native-sandbox-worker-exited.md +20 -0
  328. package/help/errors/native-sandbox-worker-lost.md +20 -0
  329. package/help/errors/native-sandbox-worker-unready.md +22 -0
  330. package/help/errors/secret-broker-body-invalid.md +26 -0
  331. package/help/errors/secret-broker-changed.md +20 -0
  332. package/help/errors/secret-broker-config-invalid.md +30 -0
  333. package/help/errors/secret-broker-conflict.md +27 -0
  334. package/help/errors/secret-broker-destination-denied.md +28 -0
  335. package/help/errors/secret-broker-value-invalid.md +28 -0
  336. package/help/errors/secret-brokered.md +24 -0
  337. package/help/errors/secret-not-brokered.md +25 -0
  338. package/help/errors/slack-auth-failed.md +48 -0
  339. package/help/errors/slack-channel-unreachable.md +39 -0
  340. package/help/errors/slack-not-configured.md +33 -0
  341. package/help/getting-started.md +4 -5
  342. package/help/github.md +18 -1
  343. package/help/identity-and-access.md +5 -1
  344. package/help/local-repositories.md +2 -3
  345. package/help/low-power-mode.md +125 -0
  346. package/help/merge-order-check.md +117 -0
  347. package/help/mobile-app.md +54 -0
  348. package/help/native-sandbox.md +136 -0
  349. package/help/observability.md +8 -0
  350. package/help/related-pull-requests.md +70 -9
  351. package/help/repo-instructions-and-skills.md +91 -0
  352. package/help/review-fix-rounds.md +10 -0
  353. package/help/slack-notifications.md +62 -0
  354. package/help/troubleshooting/coding-workers.md +21 -0
  355. package/package.json +11 -4
  356. package/prisma/migrations/20261009000003_native_execution_mode/migration.sql +12 -0
  357. package/prisma/migrations/20261009000004_native_gateway_ledger/migration.sql +65 -0
  358. package/prisma/migrations/20261009000005_native_gateway_call_child_agent/migration.sql +5 -0
  359. package/prisma/migrations/20261009000006_run_deferred_start_stop/migration.sql +14 -0
  360. package/prisma/migrations/20261009000007_native_gateway_network_ready/migration.sql +5 -0
  361. package/prisma/migrations/20261010000001_workflow_notifications/migration.sql +99 -0
  362. package/prisma/migrations/20261011000001_native_gateway_session_status_index/migration.sql +2 -0
  363. package/prisma/migrations/20261012000001_native_warm_worker/migration.sql +32 -0
  364. package/prisma/migrations/20261013000001_coding_run_merge_order/migration.sql +2 -0
  365. package/prisma/migrations/20261013000001_secret_broker/migration.sql +17 -0
  366. package/prisma/migrations/20261013000002_deferred_review_request/migration.sql +4 -0
  367. package/prisma/migrations/20261014000000_repo_skills/migration.sql +15 -0
  368. package/prisma/migrations/20261015000001_coding_run_merge_order_index/migration.sql +2 -0
  369. package/prisma/migrations/20261015000002_no_change_rereview/migration.sql +2 -0
  370. package/prisma/schema.prisma +362 -91
@@ -1,4 +1,4 @@
1
- import type { V1NetworkPolicy, V1Pod, V1Secret } from "@kubernetes/client-node";
1
+ import type { V1Container, V1NetworkPolicy, V1Pod, V1Secret } from "@kubernetes/client-node";
2
2
  import { type ResolvedCodingService } from "../../coding/services/catalog.js";
3
3
  import type { JobSpec } from "./types.js";
4
4
  import { type KubernetesPlatform } from "./kubernetes-platform.js";
@@ -81,6 +81,8 @@ export interface RunPodOptions {
81
81
  export declare function buildRunPod(spec: JobSpec, options: RunPodOptions): V1Pod;
82
82
  export declare function buildRunNetworkPolicy(spec: JobSpec, namespace: string): V1NetworkPolicy;
83
83
  export declare function buildCapabilitySecret(spec: JobSpec, namespace: string, capability: string, toolSetup?: string): V1Secret;
84
+ /** A copy of a container's resources with every quantity in one rendering ("1" and "1000m" alike); see normalizeResources. */
85
+ export declare function canonicalResources(r?: V1Container["resources"]): V1Container["resources"];
84
86
  /**
85
87
  * A copy of `pod` with ONLY the API server's own defaulting and aliasing undone
86
88
  * — the same `normalizeSpec` pass `assertRunPodMatches` runs, and nothing else.
@@ -561,6 +561,12 @@ function normalizeResources(r) {
561
561
  bag["ephemeral-storage"] = memoryBytes(bag["ephemeral-storage"]);
562
562
  }
563
563
  }
564
+ /** A copy of a container's resources with every quantity in one rendering ("1" and "1000m" alike); see normalizeResources. */
565
+ export function canonicalResources(r) {
566
+ const copy = r ? JSON.parse(JSON.stringify(r)) : undefined;
567
+ normalizeResources(copy);
568
+ return copy;
569
+ }
564
570
  function normalizeProbe(p) {
565
571
  if (!p)
566
572
  return;
@@ -29,7 +29,7 @@
29
29
  * regresses to a documentation-only fixture again.
30
30
  */
31
31
  import type { V1PodSpec, V1Toleration } from "@kubernetes/client-node";
32
- export type KubernetesPlatform = "generic" | "gke-autopilot";
32
+ export type KubernetesPlatform = "generic" | "gke-autopilot" | "eks";
33
33
  export declare const KUBERNETES_PLATFORMS: readonly KubernetesPlatform[];
34
34
  export declare const KUBERNETES_PLATFORM_ERROR = "kubernetes_platform_unconformable";
35
35
  /** A request the platform cannot legally run, refused here instead of being silently adjusted by the platform. */
@@ -84,6 +84,9 @@ export interface KubernetesPlatformProfile {
84
84
  /** gVisor is mandatory: an unset or different runtime class is refused at startup, not warned about. */
85
85
  requiresGvisor: boolean;
86
86
  }
87
+ /** Node label and taint key of the EKS sandbox node group; deploy/aws and overlays/eks use the same strings. */
88
+ export declare const EKS_SANDBOX_NODE_LABEL = "wardby.dev/sandbox";
89
+ export declare const EKS_SANDBOX_TAINT_KEY = "sandbox";
87
90
  export declare function platformProfile(name: KubernetesPlatform): KubernetesPlatformProfile;
88
91
  export interface ContainerResourceRequest {
89
92
  cpuMillicores: number;
@@ -1,4 +1,8 @@
1
- export const KUBERNETES_PLATFORMS = Object.freeze(["generic", "gke-autopilot"]);
1
+ export const KUBERNETES_PLATFORMS = Object.freeze([
2
+ "generic",
3
+ "gke-autopilot",
4
+ "eks",
5
+ ]);
2
6
  export const KUBERNETES_PLATFORM_ERROR = "kubernetes_platform_unconformable";
3
7
  /** A request the platform cannot legally run, refused here instead of being silently adjusted by the platform. */
4
8
  export class KubernetesPlatformError extends Error {
@@ -109,9 +113,42 @@ const GKE_AUTOPILOT = {
109
113
  },
110
114
  requiresGvisor: true,
111
115
  };
116
+ /** Node label and taint key of the EKS sandbox node group; deploy/aws and overlays/eks use the same strings. */
117
+ export const EKS_SANDBOX_NODE_LABEL = "wardby.dev/sandbox";
118
+ export const EKS_SANDBOX_TAINT_KEY = "sandbox";
119
+ /**
120
+ * Amazon EKS with a dedicated gVisor node group (deploy/aws). EKS has no managed sandbox runtime, so
121
+ * the node group's launch template installs runsc and the overlay's `gvisor` RuntimeClass schedules
122
+ * onto it: the RuntimeClass admission plugin merges its `scheduling` block into the pod, which is
123
+ * the toleration and nodeSelector below — wardby never sets either itself (see SYMMETRY CAVEAT).
124
+ *
125
+ * PROVISIONAL: written from the RuntimeClass spec, not yet a server-side dry-run capture. EKS has no
126
+ * Autopilot-style resource admission, so resources are the generic rules plus explicit ephemeral
127
+ * storage (kubelet eviction accounting on a shared sandbox node). Replace the fixture with a real
128
+ * capture before relying on the allowance list (fixtures/eks-dry-run.json).
129
+ */
130
+ const EKS = {
131
+ name: "eks",
132
+ resources: {
133
+ cpuFloorMillicores: 0,
134
+ cpuIncrementMillicores: 1,
135
+ memoryPerCpuMinMib: 0,
136
+ memoryPerCpuMaxMib: Number.POSITIVE_INFINITY,
137
+ explicitEphemeralStorage: true,
138
+ },
139
+ metadata: {
140
+ podAnnotationKeyPrefixes: [],
141
+ podLabelKeyPrefixes: [],
142
+ tolerations: [{ key: EKS_SANDBOX_TAINT_KEY, operator: "Equal", value: GVISOR_RUNTIME_CLASS, effect: "NoSchedule" }],
143
+ nodeSelector: { [EKS_SANDBOX_NODE_LABEL]: GVISOR_RUNTIME_CLASS },
144
+ dropsPodSeccompProfile: false,
145
+ },
146
+ requiresGvisor: true,
147
+ };
112
148
  const PROFILES = Object.freeze({
113
149
  generic: freezeProfile(GENERIC),
114
150
  "gke-autopilot": freezeProfile(GKE_AUTOPILOT),
151
+ eks: freezeProfile(EKS),
115
152
  });
116
153
  export function platformProfile(name) {
117
154
  const profile = PROFILES[name];
@@ -40,7 +40,8 @@ export interface CanaryResult {
40
40
  * probe (`enforcementProbeScript`), which had to make the same distinction after a live cluster
41
41
  * with no NetworkPolicy at all passed a boolean version of this check; preflight and gate must
42
42
  * agree on what counts as evidence, or one of them is vacuous. `internet`, `metadata` and `proxy`
43
- * stay plain reachability, where "connected" is the only thing that matters.
43
+ * stay plain reachability, where "connected" is the only thing that matters; `metadata` is true if
44
+ * **any** cloud metadata endpoint connects.
44
45
  *
45
46
  * CNIs program a new pod's policy a few seconds after it starts, so the script first waits (up to
46
47
  * 20 s) for the deny-port connect to be *dropped*; if it never is, `proxyDeny` reports true.
@@ -11,6 +11,7 @@ import { randomBytes } from "node:crypto";
11
11
  import { CODING_PROXY_DENY_PORT, CODING_PROXY_PORT } from "./docker-isolation.js";
12
12
  import { KUBERNETES_ISOLATION_ERROR, WORKER_CONTAINER, buildRunNetworkPolicy, buildRunPod, isRegistryDigest, } from "./kubernetes-isolation.js";
13
13
  import { KubernetesPlatformError, assertPlatformConfig, platformProfile } from "./kubernetes-platform.js";
14
+ import { CLOUD_METADATA_PROBES } from "./metadata-probes.js";
14
15
  import { readProxyWitness } from "./kubernetes-witness.js";
15
16
  /**
16
17
  * The image the preflight canary runs (it only needs node): the Codex worker when configured,
@@ -40,7 +41,8 @@ const CANARY_KEYS = Object.keys(EXPECTED).sort();
40
41
  * probe (`enforcementProbeScript`), which had to make the same distinction after a live cluster
41
42
  * with no NetworkPolicy at all passed a boolean version of this check; preflight and gate must
42
43
  * agree on what counts as evidence, or one of them is vacuous. `internet`, `metadata` and `proxy`
43
- * stay plain reachability, where "connected" is the only thing that matters.
44
+ * stay plain reachability, where "connected" is the only thing that matters; `metadata` is true if
45
+ * **any** cloud metadata endpoint connects.
44
46
  *
45
47
  * CNIs program a new pod's policy a few seconds after it starts, so the script first waits (up to
46
48
  * 20 s) for the deny-port connect to be *dropped*; if it never is, `proxyDeny` reports true.
@@ -81,7 +83,7 @@ export const CANARY_SCRIPT = [
81
83
  " ),",
82
84
  ` proxyDeny: !(await dropped(process.env.WARDBY_CANARY_PROXY_IP, ${CODING_PROXY_DENY_PORT})),`,
83
85
  ' internet: await reachable("1.1.1.1", 443),',
84
- ' metadata: await reachable("169.254.169.254", 80),',
86
+ ` metadata: (await Promise.all(${JSON.stringify(CLOUD_METADATA_PROBES.map((p) => [p.host, p.port]))}.map(([host, port]) => reachable(host, port)))).some(Boolean),`,
85
87
  ` proxy: await reachable(process.env.WARDBY_CANARY_PROXY_IP, ${CODING_PROXY_PORT}),`,
86
88
  " };",
87
89
  " console.log(JSON.stringify({ wardbyCanary }));",
@@ -1,5 +1,6 @@
1
1
  import { type Readable } from "node:stream";
2
2
  import type { KubernetesJobConfig } from "../../config/providers.js";
3
+ import { type Power } from "../../core/power.js";
3
4
  import { type KubernetesApi } from "./kubernetes-api.js";
4
5
  import { type ServiceStateReporter } from "./service-state.js";
5
6
  import type { CodingProvider } from "../../coding/provider.js";
@@ -19,6 +20,11 @@ export interface KubernetesJobLauncherOptions {
19
20
  * the gate probes the address `provision` re-reads for that launch, not this memoized one.
20
21
  */
21
22
  preflight?: () => Promise<KubernetesClusterInfo | void>;
23
+ /**
24
+ * Low-power mode: `launch()` wakes the (possibly scaled-to-zero) coding proxy before its preflight,
25
+ * and `warmUp()` defers the preflight while the proxy is asleep. Default `DISABLED_POWER` (no-op).
26
+ */
27
+ power?: Power;
22
28
  now?: () => number;
23
29
  sleep?: (milliseconds: number) => Promise<void>;
24
30
  readyTimeoutMs?: number;
@@ -67,6 +73,7 @@ export declare class KubernetesJobLauncher implements WorkspaceJobLauncher {
67
73
  /** Timeout of one streak exec: ENFORCEMENT_BLOCKED_STREAK probes plus the gaps between them. */
68
74
  private readonly streakExecTimeoutMs;
69
75
  private preflightResult?;
76
+ private readonly power;
70
77
  constructor(options: KubernetesJobLauncherOptions);
71
78
  /**
72
79
  * Services run as native sidecars in the run pod (kubernetes-isolation.ts), for either provider:
@@ -94,7 +101,35 @@ export declare class KubernetesJobLauncher implements WorkspaceJobLauncher {
94
101
  materializeWorkspace(handle: JobHandle, destination: string): Promise<void>;
95
102
  stop(handle: JobHandle, _reason?: string): Promise<void>;
96
103
  remove(handle: JobHandle): Promise<void>;
97
- /** Memoized: the preflight (or, without one, a single proxy-witness read) runs once per launcher. */
104
+ /**
105
+ * Low-power mode: whether the proxy is scaled to zero. A failed check is logged by code only (the
106
+ * leading reason code, else the error's class name -- never the raw message, which could carry
107
+ * cluster detail) and counts as awake, so warm-up runs the preflight as it would without the mode.
108
+ */
109
+ private proxyAsleep;
110
+ /**
111
+ * Starts the memoized cluster preflight (`runPreflight`) right away, instead of waiting for the
112
+ * first `launch()`, so a long-running server process's own start-up pays the preflight's cost (a
113
+ * canary pod on `kind`: ~20s) rather than that process's first coding run. Never throws: it only
114
+ * reports the outcome (an info line on success, a warning naming the failure code otherwise) so
115
+ * an operator sees a misconfigured cluster at boot. Any `launch()` already awaiting this same
116
+ * attempt (concurrent with this call) still fails exactly as it does today when the preflight
117
+ * failed — it shares the same rejected promise. But unlike a preflight `launch()` starts on its
118
+ * own, a start-up failure here is **not** left stuck memoized: a transient cluster problem at
119
+ * process start must not fail every coding run until restart, so the failed attempt is cleared
120
+ * (only if nothing has already started a newer one — compared by identity, never by state) and
121
+ * the *next* `launch()` runs the preflight fresh. A successful warm-up stays memoized as always.
122
+ * In low-power mode, while the proxy is asleep, warm-up is skipped (the memo stays unset) rather
123
+ * than waking the proxy only for the preflight; the first `launch()` wakes it and runs the preflight.
124
+ */
125
+ warmUp(): Promise<void>;
126
+ /**
127
+ * Memoized: the preflight (or, without one, a single proxy-witness read) runs once per launcher —
128
+ * including its failure, which stays memoized and fails every subsequent `launch()` immediately,
129
+ * for a preflight `launch()` itself started (there was no `warmUp()`, or `warmUp()`'s own failed
130
+ * attempt was already cleared). `warmUp()` is the one caller that clears a failed attempt instead
131
+ * of leaving it stuck; see its own doc comment.
132
+ */
98
133
  private runPreflight;
99
134
  /**
100
135
  * Waits until the run's NetworkPolicy is enforced on this pod: the keeper (same network namespace
@@ -29,7 +29,8 @@ import { finished } from "node:stream/promises";
29
29
  import { createHash } from "node:crypto";
30
30
  import { isIP } from "node:net";
31
31
  import { logger } from "../../core/logger.js";
32
- import { MAX_CODING_ARTIFACT_BYTES, MAX_DEBUG_TRACE_LINE_BYTES, parseCodingAgentOutputJson, } from "../../coding/protocol.js";
32
+ import { DISABLED_POWER, wakeTimeoutMs } from "../../core/power.js";
33
+ import { MAX_CODING_ARTIFACT_BYTES, MAX_CODING_INPUT_BYTES, MAX_DEBUG_TRACE_LINE_BYTES, parseCodingAgentOutputJson, } from "../../coding/protocol.js";
33
34
  import { CODING_PROXY_DENY_PORT, CODING_PROXY_PORT } from "./docker-isolation.js";
34
35
  import { parseWorkerDiagnosticLine } from "./docker.js";
35
36
  import { KubernetesAlreadyExistsError, KubernetesConflictError } from "./kubernetes-api.js";
@@ -386,9 +387,11 @@ export class KubernetesJobLauncher {
386
387
  /** Timeout of one streak exec: ENFORCEMENT_BLOCKED_STREAK probes plus the gaps between them. */
387
388
  streakExecTimeoutMs;
388
389
  preflightResult;
390
+ power;
389
391
  constructor(options) {
390
392
  this.options = options;
391
393
  this.api = options.api;
394
+ this.power = options.power ?? DISABLED_POWER;
392
395
  this.namespace = options.config.namespace;
393
396
  this.workspaceRoot = resolve(options.workspaceRoot);
394
397
  if (this.workspaceRoot === resolve("/"))
@@ -495,6 +498,11 @@ export class KubernetesJobLauncher {
495
498
  this.warn(`kubernetes_spec_rejected: ${error.message}`);
496
499
  throw error;
497
500
  }
501
+ // Low-power mode: the proxy may be scaled to zero; wake it before the preflight reads it. Guarded
502
+ // (not just a no-op await) so mode off keeps launch() joining an in-flight warm-up synchronously.
503
+ if (this.power.enabled) {
504
+ await this.power.ensureAwake("proxy", { timeoutMs: wakeTimeoutMs(this.readyTimeoutMs), runId: spec.runId });
505
+ }
498
506
  await this.runPreflight();
499
507
  const names = kubernetesRunNames(spec.runId);
500
508
  const handle = { backend: BACKEND, id: `${this.namespace}/${names.token}` };
@@ -618,7 +626,69 @@ export class KubernetesJobLauncher {
618
626
  }
619
627
  // -------------------------------------------------------------------------
620
628
  // Launch
621
- /** Memoized: the preflight (or, without one, a single proxy-witness read) runs once per launcher. */
629
+ /**
630
+ * Low-power mode: whether the proxy is scaled to zero. A failed check is logged by code only (the
631
+ * leading reason code, else the error's class name -- never the raw message, which could carry
632
+ * cluster detail) and counts as awake, so warm-up runs the preflight as it would without the mode.
633
+ */
634
+ async proxyAsleep() {
635
+ try {
636
+ return await this.power.isAsleep("proxy");
637
+ }
638
+ catch (error) {
639
+ const code = (error instanceof Error && /^[a-z][a-z0-9_]*(?=:)/.exec(error.message)?.[0]) ||
640
+ (error instanceof Error ? error.name : "unknown");
641
+ this.warn(`kubernetes_preflight_power_check_failed: ${code}`);
642
+ return false;
643
+ }
644
+ }
645
+ /**
646
+ * Starts the memoized cluster preflight (`runPreflight`) right away, instead of waiting for the
647
+ * first `launch()`, so a long-running server process's own start-up pays the preflight's cost (a
648
+ * canary pod on `kind`: ~20s) rather than that process's first coding run. Never throws: it only
649
+ * reports the outcome (an info line on success, a warning naming the failure code otherwise) so
650
+ * an operator sees a misconfigured cluster at boot. Any `launch()` already awaiting this same
651
+ * attempt (concurrent with this call) still fails exactly as it does today when the preflight
652
+ * failed — it shares the same rejected promise. But unlike a preflight `launch()` starts on its
653
+ * own, a start-up failure here is **not** left stuck memoized: a transient cluster problem at
654
+ * process start must not fail every coding run until restart, so the failed attempt is cleared
655
+ * (only if nothing has already started a newer one — compared by identity, never by state) and
656
+ * the *next* `launch()` runs the preflight fresh. A successful warm-up stays memoized as always.
657
+ * In low-power mode, while the proxy is asleep, warm-up is skipped (the memo stays unset) rather
658
+ * than waking the proxy only for the preflight; the first `launch()` wakes it and runs the preflight.
659
+ */
660
+ async warmUp() {
661
+ if (this.power.enabled && (await this.proxyAsleep())) {
662
+ kubernetesLog.info("kubernetes_preflight_deferred: proxy asleep (low-power mode); the first coding run runs the preflight");
663
+ return;
664
+ }
665
+ const attempt = this.runPreflight();
666
+ try {
667
+ await attempt;
668
+ kubernetesLog.info("kubernetes_preflight_warm_up_succeeded: cluster preflight passed at start-up");
669
+ }
670
+ catch (error) {
671
+ // Only clear the memo if it still holds this exact attempt: a concurrent launch() or warmUp()
672
+ // may have already seen this same rejection and started a fresh one (which must survive), and
673
+ // nothing else could have replaced this attempt with anything but a newer one, since
674
+ // runPreflight() never overwrites an already-set, still-pending-or-resolved promise.
675
+ if (this.preflightResult === attempt)
676
+ this.preflightResult = undefined;
677
+ // Same code-only convention as every other failure this launcher logs (e.g. launch()'s
678
+ // kubernetes_spec_rejected above): the error's own message is the fixed reason code set by
679
+ // runPreflight's errorWithCode (KUBERNETES_ISOLATION_ERROR) — never the raw cause, which could
680
+ // carry cluster detail.
681
+ const code = error instanceof Error ? error.message : String(error);
682
+ this.warn(`kubernetes_preflight_warm_up_failed: ${code}`);
683
+ }
684
+ }
685
+ /**
686
+ * Memoized: the preflight (or, without one, a single proxy-witness read) runs once per launcher —
687
+ * including its failure, which stays memoized and fails every subsequent `launch()` immediately,
688
+ * for a preflight `launch()` itself started (there was no `warmUp()`, or `warmUp()`'s own failed
689
+ * attempt was already cleared). `warmUp()` is the one caller that clears a failed attempt instead
690
+ * of leaving it stuck; see its own doc comment.
691
+ */
622
692
  runPreflight() {
623
693
  this.preflightResult ??= (async () => {
624
694
  try {
@@ -862,14 +932,14 @@ export class KubernetesJobLauncher {
862
932
  const staging = await mkdtemp(join(tmpdir(), "wardby-k8s-input-"));
863
933
  try {
864
934
  const source = await lstat(inputArtifact);
865
- if (!source.isFile() || source.size > MAX_CODING_ARTIFACT_BYTES) {
935
+ if (!source.isFile() || source.size > MAX_CODING_INPUT_BYTES) {
866
936
  throw new Error("kubernetes_input_artifact_invalid");
867
937
  }
868
938
  const copy = join(staging, "input.json");
869
939
  await copyFile(inputArtifact, copy);
870
940
  // Re-check the copy: the source could have changed between lstat and copy.
871
941
  const copied = await lstat(copy);
872
- if (!copied.isFile() || copied.size > MAX_CODING_ARTIFACT_BYTES) {
942
+ if (!copied.isFile() || copied.size > MAX_CODING_INPUT_BYTES) {
873
943
  throw new Error("kubernetes_input_artifact_invalid");
874
944
  }
875
945
  await this.seedDirectory(names, staging, INPUT_STORAGE, timeoutMs);
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Cloud metadata endpoints a coding or native worker pod must never reach: they hand out node or
3
+ * workload credentials. The instance metadata service (GCE, EC2 IPv4), the EKS Pod Identity agent (IPv4
4
+ * and IPv6), and EC2's IPv6 IMDS endpoint. Probed on port 80, where every one of them serves. A pod without
5
+ * IPv6 fails the last connect immediately, which counts as unreachable — correct either way.
6
+ * src/sandbox/fetch-policy.ts blocks the same addresses for in-process fetches.
7
+ */
8
+ export declare const CLOUD_METADATA_PROBES: readonly {
9
+ host: string;
10
+ port: number;
11
+ }[];
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Cloud metadata endpoints a coding or native worker pod must never reach: they hand out node or
3
+ * workload credentials. The instance metadata service (GCE, EC2 IPv4), the EKS Pod Identity agent (IPv4
4
+ * and IPv6), and EC2's IPv6 IMDS endpoint. Probed on port 80, where every one of them serves. A pod without
5
+ * IPv6 fails the last connect immediately, which counts as unreachable — correct either way.
6
+ * src/sandbox/fetch-policy.ts blocks the same addresses for in-process fetches.
7
+ */
8
+ export const CLOUD_METADATA_PROBES = Object.freeze([
9
+ Object.freeze({ host: "169.254.169.254", port: 80 }),
10
+ Object.freeze({ host: "169.254.170.23", port: 80 }),
11
+ Object.freeze({ host: "fd00:ec2::254", port: 80 }),
12
+ Object.freeze({ host: "fd00:ec2::23", port: 80 }),
13
+ ]);
@@ -88,6 +88,15 @@ export interface JobLauncher {
88
88
  * itself stays the authority.
89
89
  */
90
90
  hasCapacityFor?: (spec: JobSpec) => Promise<boolean>;
91
+ /**
92
+ * Starts any one-time start-up check (e.g. Kubernetes's memoized cluster preflight) right away,
93
+ * instead of waiting for the first `launch()`. Never throws — a failure is only logged — and
94
+ * `launch()` still runs and reports the same check itself, memoized exactly as if this had never
95
+ * been called. Composition roots call this, fire-and-forget, only from a long-running server
96
+ * process's own start-up (`wardby serve`, the MCP server, `wardby scheduler`); never from a
97
+ * one-shot CLI command. Absent on a launcher with nothing to warm up (e.g. Docker).
98
+ */
99
+ warmUp?: () => Promise<void>;
91
100
  launch: (spec: JobSpec) => Promise<JobHandle>;
92
101
  status: (handle: JobHandle) => Promise<JobStatus>;
93
102
  collect: (handle: JobHandle) => Promise<JobResult>;
@@ -5,7 +5,7 @@
5
5
  * and revokes it afterwards. See docs/private/2026-09-25-code-review-host-design.md §5.1.
6
6
  */
7
7
  import { type GitHubAppClient } from "../vcs/github.js";
8
- import { ReviewHostError, type CiView, type CodeReviewHost, type CommentInput, type CommentRef, type CompleteCheckInput, type EditCommentInput, type FileListView, type FileReadResult, type HostPermission, type HostUser, type PublishReviewInput, type PublishReviewResult, type PullRequestHead, type PullRequestOrigin, type PullRequestView, type StartCheckInput } from "./types.js";
8
+ import { ReviewHostError, type CiView, type CodeReviewHost, type CommentInput, type CommentRef, type CompleteCheckInput, type EditCommentInput, type FileListView, type FileReadResult, type HostPermission, type HostUser, type PublishReviewInput, type PublishReviewResult, type PullRequestHead, type PullRequestOrigin, type PullRequestView, type StartCheckInput, type UpsertNamedCheckInput } from "./types.js";
9
9
  /** listFiles leaves out vendored trees and lockfiles; every host applies the same filter. */
10
10
  export declare function skippedInListing(path: string): boolean;
11
11
  type Json = Record<string, unknown>;
@@ -16,6 +16,7 @@ export declare class GitHubReviewHost implements CodeReviewHost {
16
16
  private readonly client;
17
17
  readonly provider: "github";
18
18
  constructor(client: GitHubAppClient);
19
+ appOwnerLogin(): Promise<string>;
19
20
  /** Runs `action` with a token for exactly `permissions`, translating failures to ReviewHostError. */
20
21
  protected withToken<T>(repository: string, permissions: Record<string, "read" | "write">, action: (get: (path: string, init?: RequestInit, expected?: number[]) => Promise<Response>, graphql: GraphQl) => Promise<T>): Promise<T>;
21
22
  repositoryPermission(repository: string, user: HostUser): Promise<{
@@ -70,5 +71,14 @@ export declare class GitHubReviewHost implements CodeReviewHost {
70
71
  checkId: string;
71
72
  }>;
72
73
  completeCheck(repository: string, input: CompleteCheckInput): Promise<void>;
74
+ /**
75
+ * Creates or updates, by fixed name, a check run on `input.headSha` that
76
+ * this App owns: lists the commit's check runs filtered to that name and
77
+ * patches the newest one `fromApp` owns, else creates one. Writes nothing
78
+ * when that run already carries the same status, conclusion, and output.
79
+ * A completed run is never moved back to in_progress (GitHub does not
80
+ * allow reopening a completed check run): a fresh one is created instead.
81
+ */
82
+ upsertNamedCheck(repository: string, input: UpsertNamedCheckInput): Promise<void>;
73
83
  }
74
84
  export {};
@@ -22,6 +22,12 @@ export function skippedInListing(path) {
22
22
  return VENDORED.test(path) || path.endsWith("package-lock.json");
23
23
  }
24
24
  const CHECK_TEXT_LIMIT = 65_535;
25
+ /**
26
+ * Cap on a named check's output.title. Not a documented GitHub limit (none
27
+ * is published for the title); a conservative length that keeps the title
28
+ * a one-line heading in the checks UI.
29
+ */
30
+ const CHECK_TITLE_LIMIT = 255;
25
31
  const COMMENT_WRITE = { issues: "write", pull_requests: "write" };
26
32
  const REVIEW_WRITE = { pull_requests: "write", checks: "write" };
27
33
  /** Always implicitly granted to an installation token; enough for the collaborator-permission endpoint. */
@@ -201,6 +207,9 @@ export class GitHubReviewHost {
201
207
  constructor(client) {
202
208
  this.client = client;
203
209
  }
210
+ async appOwnerLogin() {
211
+ return (await this.client.appIdentity()).ownerLogin;
212
+ }
204
213
  /** Runs `action` with a token for exactly `permissions`, translating failures to ReviewHostError. */
205
214
  async withToken(repository, permissions, action) {
206
215
  try {
@@ -708,4 +717,45 @@ export class GitHubReviewHost {
708
717
  const base = repoPath(repository);
709
718
  await this.withToken(repository, { checks: "write" }, (get) => this.patchCheck(get, base, input));
710
719
  }
720
+ /**
721
+ * Creates or updates, by fixed name, a check run on `input.headSha` that
722
+ * this App owns: lists the commit's check runs filtered to that name and
723
+ * patches the newest one `fromApp` owns, else creates one. Writes nothing
724
+ * when that run already carries the same status, conclusion, and output.
725
+ * A completed run is never moved back to in_progress (GitHub does not
726
+ * allow reopening a completed check run): a fresh one is created instead.
727
+ */
728
+ async upsertNamedCheck(repository, input) {
729
+ // A caller's bad input, like path_invalid: never interpolated into the URL.
730
+ if (!SAFE_SHA.test(input.headSha))
731
+ throw new ReviewHostError("host_api_error", "head_sha_invalid");
732
+ const base = repoPath(repository);
733
+ const output = {
734
+ title: input.title.slice(0, CHECK_TITLE_LIMIT),
735
+ summary: input.summary.slice(0, CHECK_TEXT_LIMIT),
736
+ };
737
+ const conclusion = input.status === "completed" ? input.conclusion : undefined;
738
+ await this.withToken(repository, { checks: "write" }, async (get) => {
739
+ const { id: appId } = await this.client.appIdentity();
740
+ const payload = record(await (await get(`${base}/commits/${input.headSha}/check-runs?check_name=${encodeURIComponent(input.name)}`)).json());
741
+ const existing = list(payload.check_runs ?? [])
742
+ .filter((r) => fromApp(r, appId))
743
+ .sort((a, b) => num(b.id) - num(a.id))[0];
744
+ const body = { status: input.status, ...(conclusion ? { conclusion } : {}), output };
745
+ if (existing) {
746
+ const current = record(existing.output ?? {});
747
+ const unchanged = existing.status === input.status &&
748
+ (existing.conclusion ?? undefined) === conclusion &&
749
+ current.title === output.title &&
750
+ current.summary === output.summary;
751
+ if (unchanged)
752
+ return;
753
+ if (!(existing.status === "completed" && input.status !== "completed")) {
754
+ await get(`${base}/check-runs/${encodeURIComponent(String(num(existing.id)))}`, { method: "PATCH", body: JSON.stringify(body) }, [200]);
755
+ return;
756
+ }
757
+ }
758
+ await get(`${base}/check-runs`, { method: "POST", body: JSON.stringify({ name: input.name, head_sha: input.headSha, ...body }) }, [201]);
759
+ });
760
+ }
711
761
  }
@@ -207,6 +207,18 @@ export interface CompleteCheckInput {
207
207
  text?: string;
208
208
  detailsUrl?: string;
209
209
  }
210
+ export type UpsertNamedCheckInput = {
211
+ headSha: string;
212
+ /** The check's fixed name, e.g. MERGE_ORDER_CHECK_NAME ("wardby merge order"); identifies which run to update. */
213
+ name: string;
214
+ title: string;
215
+ summary: string;
216
+ } & ({
217
+ status: "in_progress";
218
+ } | {
219
+ status: "completed";
220
+ conclusion: "success" | "failure";
221
+ });
210
222
  /**
211
223
  * A user's effective permission on one repository, host-neutral and ranked
212
224
  * none < read < triage < write < maintain < admin. GitHub maps its
@@ -222,6 +234,8 @@ export interface HostUser {
222
234
  }
223
235
  export interface CodeReviewHost {
224
236
  readonly provider: ReviewHostProvider;
237
+ /** The GitHub App's owner account (always served; see WARDBY_GITHUB_ALLOWED_ACCOUNTS). Hosts without an App omit it. */
238
+ appOwnerLogin?(): Promise<string>;
225
239
  /**
226
240
  * The user's effective permission on `repository`, asked with the App's own
227
241
  * installation credential (never a user token). Answers only for the user
@@ -274,6 +288,15 @@ export interface CodeReviewHost {
274
288
  checkId: string;
275
289
  }>;
276
290
  completeCheck(repository: string, input: CompleteCheckInput): Promise<void>;
291
+ /**
292
+ * Creates or updates, by fixed name, a check run on one commit that isn't
293
+ * tied to a run's own startCheck/completeCheck lifecycle (e.g. the
294
+ * `wardby merge order` check, recomputed fresh and reposted on demand).
295
+ * Finds the one this App owns among the commit's check runs with that
296
+ * name and patches it; creates one when none exists. Hosts that can't
297
+ * post checks (e.g. a local repository) leave this out.
298
+ */
299
+ upsertNamedCheck?(repository: string, input: UpsertNamedCheckInput): Promise<void>;
277
300
  }
278
301
  export type ReviewHostErrorCode = "host_not_installed" | "host_permission_missing" | "host_api_error" | "host_invalid_response";
279
302
  export declare class ReviewHostError extends Error {
@@ -149,9 +149,16 @@ export interface RelatedPullRequestEntry {
149
149
  state?: RelatedPullRequestState;
150
150
  /** The pull request whose description this section is in. */
151
151
  self?: boolean;
152
+ /**
153
+ * Step in the delegating agent's declared merge order (1-99), from
154
+ * CodingRun.mergeOrder; absent = the delegating agent set no order
155
+ * (an open entry is listed under "Not ordered:" when any other entry,
156
+ * open or done, has one).
157
+ */
158
+ mergeOrder?: number;
152
159
  }
153
160
  export interface RelatedPullRequestsInput {
154
- /** In suggested merge order (the order the request's coding runs were dispatched). */
161
+ /** In merge order: by mergeOrder when set, else the order the request's coding runs were dispatched. */
155
162
  entries: readonly RelatedPullRequestEntry[];
156
163
  issue?: {
157
164
  key: string;
@@ -162,9 +169,18 @@ export interface RelatedPullRequestsInput {
162
169
  /**
163
170
  * The "Related pull requests" block, markers included, built only from
164
171
  * stored rows (never agent text). Open (and draft, and not-yet-known) pull
165
- * requests form the numbered suggested merge order; merged and closed ones
166
- * follow as context. Undefined when no other valid pull request is left after
167
- * validation: a set of one is not a set.
172
+ * requests form the merge order; merged and closed ones follow as context.
173
+ * Undefined when no other valid pull request is left after validation: a
174
+ * set of one is not a set.
175
+ *
176
+ * When any valid entry (open, merged or closed) carries a `mergeOrder` (set
177
+ * by the delegating agent), steps are counted over all of them, so a
178
+ * label never changes as pull requests merge: the open entries that have
179
+ * one are numbered under "Merge order" and labelled with their step (equal
180
+ * `mergeOrder` values share a step label), any open entry without one is
181
+ * listed after under "Not ordered:", and a merged or closed entry that has
182
+ * one carries its step label in the context list too. Otherwise every open
183
+ * entry is numbered in the unordered "Suggested merge order" list.
168
184
  */
169
185
  export declare function renderRelatedSection(input: RelatedPullRequestsInput): string | undefined;
170
186
  /**
@@ -228,11 +244,21 @@ export declare class GitHubAppClient implements GitHubRepositoryAccess {
228
244
  */
229
245
  readFileAtRef(input: RepositoryFileInput): Promise<string | null>;
230
246
  private identity?;
231
- /** The App's id and slug (its @-mention handle), read once with the App JWT. */
232
- appIdentity(): Promise<{
247
+ /**
248
+ * The App's id, slug (its @-mention handle), and owner account, read once
249
+ * with the App JWT and memoized. `fresh` re-reads GET /app and replaces the
250
+ * memo (used before a destructive action, since the owner can change).
251
+ * A rejection is never cached.
252
+ */
253
+ appIdentity(opts?: {
254
+ fresh?: boolean;
255
+ }): Promise<{
233
256
  id: number;
234
257
  slug: string;
258
+ ownerLogin: string;
235
259
  }>;
260
+ /** Removes this App from an installation (an account outside the trusted set). */
261
+ deleteInstallation(installationId: number): Promise<void>;
236
262
  upsertContinuationStatusComment(input: ContinuationStatusCommentInput): Promise<void>;
237
263
  updateContinuationStatusComment(input: ContinuationStatusCommentInput): Promise<void>;
238
264
  createContinuationCheckRun(input: ContinuationCheckRunInput): Promise<void>;