@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
@@ -14,6 +14,16 @@ import { PostgresDatastore } from "../providers/datastore/postgres.js";
14
14
  import { decryptTransferEnvelope } from "../providers/secrets/transfer-envelope.js";
15
15
  import { mapBudget } from "./budgets.js";
16
16
  import { EVERYONE_KEY, everyoneGrantData } from "../core/grants.js";
17
+ import { loadNativeSandboxConfig } from "../config/providers.js";
18
+ /** Whether this process has a native sandbox configured; a bad configuration counts as not configured. */
19
+ function nativeSandboxConfigured() {
20
+ try {
21
+ return loadNativeSandboxConfig(process.env) !== undefined;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
17
27
  export async function createFromBundle(bundle, recon, opts) {
18
28
  const { db, cipher, ownerId, secretMode, transferPrivateKey, allowOpenFetch } = opts;
19
29
  const agentOwnerId = opts.agentOwnerId === undefined ? ownerId : opts.agentOwnerId;
@@ -91,9 +101,16 @@ export async function createFromBundle(bundle, recon, opts) {
91
101
  maxTurns: a.maxTurns,
92
102
  scheduleEnabled,
93
103
  ownerId: agentOwnerId,
104
+ ...(a.nativeExecutionMode
105
+ ? { nativeExecutionMode: a.nativeExecutionMode === "sandbox" ? "sandbox" : "control_plane" }
106
+ : {}),
94
107
  },
95
108
  update: {},
96
109
  });
110
+ if (a.nativeExecutionMode === "sandbox" && !nativeSandboxConfigured()) {
111
+ warnings.push(`agent ${a.name}: nativeExecutionMode is "sandbox" but NATIVE_SANDBOX_LAUNCHER is not configured here; ` +
112
+ "its runs fail with native_sandbox_unavailable until the server that runs it has a native sandbox");
113
+ }
97
114
  agentIdMap.set(a.name, agent.id);
98
115
  if (agentOwnerId !== null && agent.ownerId === agentOwnerId) {
99
116
  ownedAgentIds.add(agent.id);
@@ -93,6 +93,8 @@ export declare const NeutralAgentSchema: z.ZodObject<{
93
93
  ownerEmail: z.ZodDefault<z.ZodNullable<z.ZodString>>;
94
94
  kind: z.ZodDefault<z.ZodEnum<["native"]>>;
95
95
  memoryEnabled: z.ZodDefault<z.ZodBoolean>;
96
+ /** Where the agent's runs execute; absent = control-plane. Native agents only (every imported agent is native). */
97
+ nativeExecutionMode: z.ZodOptional<z.ZodEnum<["control-plane", "sandbox"]>>;
96
98
  unmodeled: z.ZodDefault<z.ZodObject<{
97
99
  description: z.ZodOptional<z.ZodDefault<z.ZodNullable<z.ZodString>>>;
98
100
  slug: z.ZodOptional<z.ZodDefault<z.ZodNullable<z.ZodString>>>;
@@ -125,6 +127,7 @@ export declare const NeutralAgentSchema: z.ZodObject<{
125
127
  slug?: string | null | undefined;
126
128
  emailAllowlist?: unknown;
127
129
  };
130
+ nativeExecutionMode?: "sandbox" | "control-plane" | undefined;
128
131
  }, {
129
132
  name: string;
130
133
  model: string;
@@ -134,6 +137,7 @@ export declare const NeutralAgentSchema: z.ZodObject<{
134
137
  systemPrompt: string;
135
138
  scheduleEnabled: boolean;
136
139
  kind?: "native" | undefined;
140
+ nativeExecutionMode?: "sandbox" | "control-plane" | undefined;
137
141
  schedule?: string | null | undefined;
138
142
  timezone?: string | undefined;
139
143
  memoryEnabled?: boolean | undefined;
@@ -252,12 +256,12 @@ export declare const NeutralAgentSecretSchema: z.ZodObject<{
252
256
  secretName: z.ZodString;
253
257
  alias: z.ZodDefault<z.ZodNullable<z.ZodString>>;
254
258
  }, "strip", z.ZodTypeAny, {
255
- agentName: string;
256
259
  secretName: string;
260
+ agentName: string;
257
261
  alias: string | null;
258
262
  }, {
259
- agentName: string;
260
263
  secretName: string;
264
+ agentName: string;
261
265
  alias?: string | null | undefined;
262
266
  }>;
263
267
  export type NeutralAgentSecret = z.infer<typeof NeutralAgentSecretSchema>;
@@ -34,6 +34,8 @@ export const NeutralAgentSchema = z.object({
34
34
  ownerEmail: z.string().nullable().default(null),
35
35
  kind: z.enum(["native"]).default("native"),
36
36
  memoryEnabled: z.boolean().default(false),
37
+ /** Where the agent's runs execute; absent = control-plane. Native agents only (every imported agent is native). */
38
+ nativeExecutionMode: z.enum(["control-plane", "sandbox"]).optional(),
37
39
  unmodeled: z
38
40
  .object({
39
41
  description: z.string().nullable().default(null),
@@ -46,6 +46,7 @@ export declare function requireAgentAccess(ctx: McpRequestContext, id: string, r
46
46
  kind: import("#prisma").AgentKind;
47
47
  id: string;
48
48
  ownerId: string | null;
49
+ nativeExecutionMode: import("#prisma").NativeExecutionMode;
49
50
  systemPrompt: string;
50
51
  schedule: string | null;
51
52
  timezone: string;
@@ -6,6 +6,7 @@
6
6
  * side and the rules; the host call happens here, before any transaction.
7
7
  */
8
8
  import { LocalRepoError, isLocalRepository, loadLocalRepoRoots, resolveLocalRepository, } from "../../coding/local-repo.js";
9
+ import { GITHUB_ACCOUNT_NOT_ALLOWED } from "../../config/github-allowed-accounts.js";
9
10
  import { requiredLevel } from "../../core/repo-access.js";
10
11
  import { McpError } from "../errors.js";
11
12
  import { requireScope } from "./resource-server.js";
@@ -39,6 +40,13 @@ export async function authorizeRepositoryForSet(ctx, input) {
39
40
  await canonicalLocalRepository(input.repository);
40
41
  return stamp("local_root");
41
42
  }
43
+ // A server-wide boundary: no admin approval reaches past it.
44
+ const accountDenied = await ctx.providers.repoAccess?.checkAccount?.(input.provider, input.repository);
45
+ if (accountDenied && !accountDenied.ok) {
46
+ if (accountDenied.reason === "github_account_not_allowed")
47
+ throw accountNotAllowed(input.repository);
48
+ throw new McpError(503, `Could not check whether ${input.repository}'s GitHub account is served right now; try again later.`);
49
+ }
42
50
  if (input.adminOverride) {
43
51
  // Role-gated: the scope alone never suffices (resource-server.ts).
44
52
  requireScope(ctx, ctx.canonicalUri, "agents:admin");
@@ -66,7 +74,13 @@ export async function authorizeRepositoryForSet(ctx, input) {
66
74
  case "insufficient_permission":
67
75
  throw new McpError(403, `Your linked GitHub account has ${decision.level ?? "no"} access to ${input.repository}; ` +
68
76
  `this needs ${required}. Ask a repository admin for access, or a wardby admin to approve it (adminOverride).`);
77
+ case "github_account_not_allowed":
78
+ throw accountNotAllowed(input.repository);
69
79
  default:
70
80
  throw new McpError(503, `Could not verify your GitHub access to ${input.repository} right now (is the GitHub App installed on it?). Try again.`);
71
81
  }
72
82
  }
83
+ function accountNotAllowed(repository) {
84
+ return new McpError(403, `${GITHUB_ACCOUNT_NOT_ALLOWED}: ${repository} belongs to a GitHub account this server does not serve. ` +
85
+ "An operator can add the account to WARDBY_GITHUB_ALLOWED_ACCOUNTS.");
86
+ }
@@ -18,7 +18,8 @@ export declare const SCOPES_SUPPORTED: string[];
18
18
  * services:manage changes the coding-run service catalog, which decides what
19
19
  * runs next to every coding run that names an entry; admin:view reads every
20
20
  * owner's runs (the admin viewer API); models:admin changes the model catalog,
21
- * which prices every principal's runs.
21
+ * which prices every principal's runs; power:manage scales the shared coding
22
+ * proxy and native gateway to zero (power_sleep) and reads their state.
22
23
  * A token scope only DELEGATES — it never authorizes on its own:
23
24
  * requireScope/requireAnyScope honour one only when one of the caller's
24
25
  * roles (McpRequestContext.roles, resolved live per request) grants it.
@@ -27,6 +27,9 @@ export const SCOPES_SUPPORTED = [
27
27
  // Add, override, disable and reset model catalog entries (see docs/models.md).
28
28
  // Reading the catalog (list_models, get_model) is agents:read.
29
29
  "models:admin",
30
+ // Scale the coding proxy and native gateway to zero now (power_sleep) and
31
+ // read their state (power_status); low-power mode only (docs/getting-started-gke.md).
32
+ "power:manage",
30
33
  ];
31
34
  /**
32
35
  * Scopes whose operations reach beyond the caller's own (or public)
@@ -36,7 +39,8 @@ export const SCOPES_SUPPORTED = [
36
39
  * services:manage changes the coding-run service catalog, which decides what
37
40
  * runs next to every coding run that names an entry; admin:view reads every
38
41
  * owner's runs (the admin viewer API); models:admin changes the model catalog,
39
- * which prices every principal's runs.
42
+ * which prices every principal's runs; power:manage scales the shared coding
43
+ * proxy and native gateway to zero (power_sleep) and reads their state.
40
44
  * A token scope only DELEGATES — it never authorizes on its own:
41
45
  * requireScope/requireAnyScope honour one only when one of the caller's
42
46
  * roles (McpRequestContext.roles, resolved live per request) grants it.
@@ -47,13 +51,14 @@ export const PRIVILEGED_SCOPES = [
47
51
  "services:manage",
48
52
  "admin:view",
49
53
  "models:admin",
54
+ "power:manage",
50
55
  ];
51
56
  /**
52
57
  * The built-in roles and the permissions (privileged scope names) each
53
58
  * grants. No roles = member: every non-privileged scope, nothing privileged.
54
59
  */
55
60
  export const ROLE_PERMISSIONS = {
56
- admin: ["agents:admin", "packages:approve", "services:manage", "admin:view", "models:admin"],
61
+ admin: ["agents:admin", "packages:approve", "services:manage", "admin:view", "models:admin", "power:manage"],
57
62
  "package-approver": ["packages:approve"],
58
63
  "service-manager": ["services:manage"],
59
64
  "model-manager": ["models:admin"],
@@ -1,3 +1,3 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
- import type { SelfHostedAuthProvider } from "../../../providers/auth/self-hosted.js";
2
+ import { type SelfHostedAuthProvider } from "../../../providers/auth/self-hosted.js";
3
3
  export declare function browserHandler(provider: SelfHostedAuthProvider): (req: IncomingMessage, res: ServerResponse, url: URL, body: unknown) => Promise<boolean>;
@@ -1,4 +1,5 @@
1
1
  import { randomUUID, randomBytes } from "node:crypto";
2
+ import { MobileDisabledError } from "../../../providers/auth/self-hosted.js";
2
3
  import { PostgresRateLimiter, RateLimitError } from "./rate-limit.js";
3
4
  import { logger } from "../../../core/logger.js";
4
5
  import { PAGE_STYLE } from "../../shared/page-style.js";
@@ -120,12 +121,24 @@ export function browserHandler(provider) {
120
121
  // here is already a public PKCE client with validated redirect URIs.
121
122
  if (p.application_type !== undefined && p.application_type !== "native" && p.application_type !== "web")
122
123
  throw new Error("unsupported application_type");
123
- const registered = await provider.registerClient({
124
- redirectUris: p.redirect_uris,
125
- clientName: p.client_name,
126
- grantTypes: p.grant_types,
127
- tokenEndpointAuthMethod: p.token_endpoint_auth_method,
128
- });
124
+ let registered;
125
+ try {
126
+ registered = await provider.registerClient({
127
+ redirectUris: p.redirect_uris,
128
+ clientName: p.client_name,
129
+ grantTypes: p.grant_types,
130
+ tokenEndpointAuthMethod: p.token_endpoint_auth_method,
131
+ });
132
+ }
133
+ catch (err) {
134
+ // The one registration error that names its reason: the operator
135
+ // must turn mobile sign-in on. Everything else stays generic below.
136
+ if (err instanceof MobileDisabledError) {
137
+ json(res, 400, { error: "invalid_redirect_uri", error_description: err.message });
138
+ return true;
139
+ }
140
+ throw err;
141
+ }
129
142
  json(res, 201, {
130
143
  client_id: registered.clientId,
131
144
  token_endpoint_auth_method: "none",
@@ -13,10 +13,14 @@ import type { ProviderRegistry } from "../providers/index.js";
13
13
  import type { RepoAccessGate } from "../core/repo-access.js";
14
14
  import type { IssueTrackerRegistry } from "../providers/issue-tracker/types.js";
15
15
  import type { HostUserAuthorizerRegistry, ReviewHostRegistry } from "../providers/review-host/types.js";
16
+ import type { Power } from "../core/power.js";
17
+ import type { ChatProviderRegistry } from "../providers/chat/types.js";
16
18
  export type McpProviders = Pick<ProviderRegistry, "llm" | "engine" | "datastore" | "secrets" | "executor" | "memory"> & {
17
19
  reviewHosts?: ReviewHostRegistry;
18
20
  /** Issue trackers (Jira); empty or absent = no issue-tracker integration. */
19
21
  issueTrackers?: IssueTrackerRegistry;
22
+ /** Chat providers for workflow notifications (Slack); empty or absent = notifications off. */
23
+ chat?: ChatProviderRegistry;
20
24
  /** Identity linking (link_host_account); empty or absent = linking disabled. */
21
25
  hostUserAuthorizers?: HostUserAuthorizerRegistry;
22
26
  /**
@@ -25,6 +29,8 @@ export type McpProviders = Pick<ProviderRegistry, "llm" | "engine" | "datastore"
25
29
  * only admin approvals still work.
26
30
  */
27
31
  repoAccess?: RepoAccessGate;
32
+ /** Low-power mode (scale idle proxy/gateway to zero); absent = DISABLED_POWER. */
33
+ power?: Power;
28
34
  };
29
35
  export interface McpRequestContext {
30
36
  principal: Principal;
@@ -19,10 +19,22 @@ export interface GitHubIngressDeps {
19
19
  /** For a merged/closed PR's linked issue; absent → closed PRs are ignored. */
20
20
  issueTrackers?: IssueTrackerRegistry;
21
21
  webhookSecret: string | undefined;
22
- appIdentity: () => Promise<{
22
+ /**
23
+ * The App's id, slug, and owner (the owner is always served). Throwing fails
24
+ * the request closed. `fresh` bypasses any cache; it is used before an
25
+ * uninstall, since the owner can change while the server runs.
26
+ */
27
+ appIdentity: (opts?: {
28
+ fresh?: boolean;
29
+ }) => Promise<{
23
30
  id: number;
24
31
  slug: string;
32
+ ownerLogin: string;
25
33
  }>;
34
+ /** Accounts served in addition to the App's owner (WARDBY_GITHUB_ALLOWED_ACCOUNTS). */
35
+ allowedAccounts: () => ReadonlySet<string>;
36
+ /** Removes the App from an installation on an account that isn't served; absent = ignore only. */
37
+ uninstallInstallation?: (installationId: number) => Promise<void>;
26
38
  now?: () => Date;
27
39
  }
28
40
  export interface GitHubIngressResult {
@@ -31,6 +43,16 @@ export interface GitHubIngressResult {
31
43
  /** Cosmetic follow-ups (reactions) to run after the response is sent. */
32
44
  afterResponse?: () => Promise<void>;
33
45
  }
46
+ /**
47
+ * Which GitHub account an event belongs to. Installation events name it on
48
+ * the installation; repository events carry only installation.id, so the
49
+ * repository's owner stands in.
50
+ */
51
+ export declare function eventAccount(eventName: string, payload: unknown): {
52
+ login: string;
53
+ installationId?: number;
54
+ uninstall: boolean;
55
+ } | undefined;
34
56
  export declare function handleGitHubEventIngress(req: {
35
57
  headers: Record<string, string | undefined>;
36
58
  rawBody: string;
@@ -9,8 +9,38 @@ import { maybePruneHostEventDeliveries } from "./deliveries.js";
9
9
  import { routeHostEvent } from "../../core/host-events.js";
10
10
  import { logger } from "../../core/logger.js";
11
11
  import { normalizeGitHubEvent, verifyGitHubSignature } from "../../providers/review-host/github-events.js";
12
+ import { isGitHubAccountAllowed, trustedAccounts } from "../../config/github-allowed-accounts.js";
12
13
  const log = logger.child({ module: "github-ingress" });
13
14
  const SAFE_DELIVERY = /^[A-Za-z0-9-]{1,100}$/;
15
+ const INSTALLATION_EVENTS = new Set(["installation", "installation_repositories"]);
16
+ function field(value, key) {
17
+ return value !== null && typeof value === "object" ? value[key] : undefined;
18
+ }
19
+ /**
20
+ * Which GitHub account an event belongs to. Installation events name it on
21
+ * the installation; repository events carry only installation.id, so the
22
+ * repository's owner stands in.
23
+ */
24
+ export function eventAccount(eventName, payload) {
25
+ if (INSTALLATION_EVENTS.has(eventName)) {
26
+ const installation = field(payload, "installation");
27
+ const login = field(field(installation, "account"), "login");
28
+ const id = field(installation, "id");
29
+ if (typeof login !== "string" || login === "")
30
+ return undefined;
31
+ return {
32
+ login,
33
+ ...(typeof id === "number" && Number.isSafeInteger(id) && id > 0 ? { installationId: id } : {}),
34
+ uninstall: field(payload, "action") !== "deleted",
35
+ };
36
+ }
37
+ const repository = field(payload, "repository");
38
+ const fullName = field(repository, "full_name");
39
+ const owner = typeof fullName === "string" && fullName.includes("/")
40
+ ? fullName.split("/", 1)[0]
41
+ : field(field(repository, "owner"), "login");
42
+ return typeof owner === "string" && owner !== "" ? { login: owner, uninstall: false } : undefined;
43
+ }
14
44
  export async function handleGitHubEventIngress(req, deps) {
15
45
  if (!deps.webhookSecret)
16
46
  return { status: 404, body: { error: "not_found" } };
@@ -31,6 +61,44 @@ export async function handleGitHubEventIngress(req, deps) {
31
61
  return { status: 400, body: { error: "invalid_json" } };
32
62
  }
33
63
  const app = await deps.appIdentity();
64
+ const account = eventAccount(eventName, payload);
65
+ if (account && !isGitHubAccountAllowed(trustedAccounts(app.ownerLogin, deps.allowedAccounts()), account.login)) {
66
+ // Never the body: only the account and what we do about it.
67
+ log.warn({ event: eventName, account: account.login, installationId: account.installationId, deliveryId }, "GitHub event from an account this server does not serve ignored");
68
+ const uninstall = deps.uninstallInstallation;
69
+ const installationId = account.installationId;
70
+ return {
71
+ status: 202,
72
+ body: { ignored: "account_not_allowed" },
73
+ ...(account.uninstall && uninstall && installationId !== undefined
74
+ ? {
75
+ afterResponse: async () => {
76
+ // Destructive, so re-check against a fresh owner read: the cached
77
+ // owner may be stale after an ownership transfer or rename.
78
+ let fresh;
79
+ try {
80
+ fresh = await deps.appIdentity({ fresh: true });
81
+ }
82
+ catch (err) {
83
+ log.warn({ err, account: account.login, installationId }, "could not re-read the App owner; not removing the App from the installation");
84
+ return;
85
+ }
86
+ if (isGitHubAccountAllowed(trustedAccounts(fresh.ownerLogin, deps.allowedAccounts()), account.login)) {
87
+ log.info({ account: account.login, installationId }, "account is served after re-reading the App owner; not removing the App from the installation");
88
+ return;
89
+ }
90
+ try {
91
+ await uninstall(installationId);
92
+ log.warn({ account: account.login, installationId }, "removed the App from an installation on an account this server does not serve");
93
+ }
94
+ catch (err) {
95
+ log.warn({ err, account: account.login, installationId }, "could not remove the App from a foreign installation");
96
+ }
97
+ },
98
+ }
99
+ : {}),
100
+ };
101
+ }
34
102
  const event = normalizeGitHubEvent(eventName, payload, app);
35
103
  // An event we don't act on must never claim the delivery id: recording it
36
104
  // here would let a later, real delivery of the same id (GitHub does reuse
@@ -37,5 +37,18 @@ export interface StartMcpOptions {
37
37
  */
38
38
  schedulerAttached?: boolean;
39
39
  }
40
+ /**
41
+ * Fire-and-forget: starts the executor's start-up warm-up (e.g. KubernetesJobLauncher's memoized
42
+ * cluster preflight, via RoutingExecutor -> ContainerExecutor -> the job launcher's own `warmUp`)
43
+ * right when a long-running server process starts, so its first coding run doesn't pay for it.
44
+ * Never awaited by its caller and never throws: `warmUp()` on every implementation already swallows
45
+ * its own failure (logging it instead), but this still guards the call site against a surprise
46
+ * rejection, since nothing here may block or fail server start-up. Called from `startMcp` below —
47
+ * i.e. from `wardby mcp` (stdio and HTTP) and from `wardby serve`, which shares this same start-up
48
+ * path — and, separately, from `cli.ts`'s `scheduler()` (`wardby scheduler`), which dispatches
49
+ * scheduled coding runs through its own Kubernetes executor but never starts an MCP server. Never
50
+ * called from a one-shot CLI command (`wardby run`, `coding preflight`, migrations, imports).
51
+ */
52
+ export declare function warmUpExecutor(executor: Pick<McpProviders["executor"], "warmUp">): void;
40
53
  /** The real CLI entry point: `wardby mcp`. Reads config from the environment, starts stdio or HTTP per MCP_TRANSPORT. */
41
54
  export declare function startMcp(options?: StartMcpOptions): Promise<McpServerHandle>;
package/dist/mcp/index.js CHANGED
@@ -22,14 +22,18 @@ import { startModelCatalog } from "../providers/llm/catalog-store.js";
22
22
  import { PostgresDatastore } from "../providers/datastore/index.js";
23
23
  import { PostgresAgentMemory } from "../providers/memory/index.js";
24
24
  import { buildConfiguredExecutor, buildExecutor } from "../providers/executor/index.js";
25
+ import { buildNativeSandboxExecutor } from "../native-worker/composition.js";
25
26
  import { buildSecretCipher } from "../providers/secrets/index.js";
26
27
  import { buildIssueTrackers } from "../providers/issue-tracker/index.js";
27
28
  import { buildHostUserAuthorizers, buildReviewHosts } from "../providers/review-host/index.js";
29
+ import { buildChatProviders } from "../providers/chat/index.js";
28
30
  import { createRepoAccessGate } from "../core/repo-access.js";
29
31
  import { userCallbackPath } from "../core/host-identity-links.js";
30
32
  import { buildAuthProvider } from "../providers/auth/index.js";
31
33
  import { GitHubAppClient } from "../providers/vcs/github.js";
34
+ import { loadGitHubAllowedAccounts } from "../config/github-allowed-accounts.js";
32
35
  import { buildMcpServer } from "./server.js";
36
+ import { buildPower } from "../core/power-composition.js";
33
37
  import { countUnattendedSchedules, unattendedSchedulesWarning } from "./unattended-schedules.js";
34
38
  import { runStdioServer } from "./transport/stdio.js";
35
39
  import { startHttpServer } from "./transport/streamable-http.js";
@@ -43,6 +47,7 @@ import { registerAgentTools } from "./tools/agents.js";
43
47
  import { registerServiceTools } from "./tools/services.js";
44
48
  import { registerBudgetGroupTools } from "./tools/budget-groups.js";
45
49
  import { registerModelCatalogTools } from "./tools/model-catalog.js";
50
+ import { registerPowerTools } from "./tools/power.js";
46
51
  import { registerHelpTools } from "./tools/help.js";
47
52
  import { registerTriggerTool } from "./tools/trigger.js";
48
53
  import { registerToolAuthoringTools } from "./tools/tools.js";
@@ -54,12 +59,14 @@ import { registerSubAgentTools } from "./tools/subagents.js";
54
59
  import { registerGrantTools } from "./tools/grants.js";
55
60
  import { registerRepositoryTools } from "./tools/repositories.js";
56
61
  import { registerIssueProjectTools } from "./tools/issue-projects.js";
62
+ import { registerNotificationChannelTools } from "./tools/notification-channels.js";
57
63
  import { registerHostAccountTools } from "./tools/host-accounts.js";
58
64
  import { registerMemoryTools } from "./tools/memory.js";
59
65
  import { registerSecretsTools } from "./tools/secrets.js";
60
66
  import { registerWebhookTools } from "./tools/webhooks.js";
61
67
  import { createStdioSecretElicitationHost } from "./tools/secret-elicitation-server.js";
62
68
  import { SECRET_ELICITATION_PATH } from "./tools/secret-elicitation-form.js";
69
+ import { installWorkflowEventRecorder } from "../core/notifications.js";
63
70
  import { logger } from "../core/logger.js";
64
71
  const mcpLog = logger.child({ module: "mcp-index" });
65
72
  const SELF_HOSTED_CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // hourly
@@ -105,6 +112,7 @@ export function registerAllTools(mcp, opts) {
105
112
  registerServiceTools(mcp);
106
113
  registerBudgetGroupTools(mcp);
107
114
  registerModelCatalogTools(mcp, { refresh: opts.refreshModelCatalog });
115
+ registerPowerTools(mcp);
108
116
  registerHelpTools(mcp);
109
117
  registerTriggerTool(mcp);
110
118
  registerToolAuthoringTools(mcp);
@@ -116,6 +124,7 @@ export function registerAllTools(mcp, opts) {
116
124
  registerGrantTools(mcp);
117
125
  registerRepositoryTools(mcp);
118
126
  registerIssueProjectTools(mcp);
127
+ registerNotificationChannelTools(mcp);
119
128
  registerHostAccountTools(mcp);
120
129
  registerMemoryTools(mcp);
121
130
  registerSecretsTools(mcp, {
@@ -139,6 +148,11 @@ export function buildMcpProviders() {
139
148
  const reviewHosts = buildReviewHosts(process.env, prisma);
140
149
  const issueTrackers = buildIssueTrackers();
141
150
  const hostUserAuthorizers = buildHostUserAuthorizers();
151
+ // Installed here (not just serve/scheduler) so `wardby mcp`, which handles
152
+ // webhooks, records events too; only serve and the scheduler command start
153
+ // the dispatcher.
154
+ const chat = buildChatProviders();
155
+ installWorkflowEventRecorder(prisma, chat);
142
156
  // One gate (and one cache) for the whole process: set-time checks in the
143
157
  // MCP tools, repo_* calls in native runs, coding runs, and host events.
144
158
  const repoAccess = createRepoAccessGate({ db: prisma, hosts: reviewHosts });
@@ -161,7 +175,17 @@ export function buildMcpProviders() {
161
175
  repoAccess,
162
176
  };
163
177
  const nativeExecutor = buildExecutor(providerConfig, nativeProviders, prisma);
164
- const executor = buildConfiguredExecutor({ native: nativeExecutor, db: prisma, providerConfig, repoAccess });
178
+ // One Power for the process, shared by both executors (wake) and the scheduler leader (sleep).
179
+ const power = buildPower({ env: process.env, db: prisma });
180
+ const executor = buildConfiguredExecutor({
181
+ native: nativeExecutor,
182
+ db: prisma,
183
+ providerConfig,
184
+ repoAccess,
185
+ power,
186
+ // Sandbox-mode native runs (docs/native-sandbox.md); undefined when NATIVE_SANDBOX_LAUNCHER is unset.
187
+ nativeSandbox: buildNativeSandboxExecutor({ db: prisma, providers: nativeProviders, power }),
188
+ });
165
189
  nativeProviders.executor = executor;
166
190
  return {
167
191
  providers: {
@@ -175,6 +199,8 @@ export function buildMcpProviders() {
175
199
  issueTrackers,
176
200
  hostUserAuthorizers,
177
201
  repoAccess,
202
+ chat,
203
+ power,
178
204
  },
179
205
  };
180
206
  }
@@ -187,11 +213,31 @@ async function closeQuietly(promise, what) {
187
213
  mcpLog.warn({ err }, `${what} failed during MCP shutdown`);
188
214
  }
189
215
  }
216
+ /**
217
+ * Fire-and-forget: starts the executor's start-up warm-up (e.g. KubernetesJobLauncher's memoized
218
+ * cluster preflight, via RoutingExecutor -> ContainerExecutor -> the job launcher's own `warmUp`)
219
+ * right when a long-running server process starts, so its first coding run doesn't pay for it.
220
+ * Never awaited by its caller and never throws: `warmUp()` on every implementation already swallows
221
+ * its own failure (logging it instead), but this still guards the call site against a surprise
222
+ * rejection, since nothing here may block or fail server start-up. Called from `startMcp` below —
223
+ * i.e. from `wardby mcp` (stdio and HTTP) and from `wardby serve`, which shares this same start-up
224
+ * path — and, separately, from `cli.ts`'s `scheduler()` (`wardby scheduler`), which dispatches
225
+ * scheduled coding runs through its own Kubernetes executor but never starts an MCP server. Never
226
+ * called from a one-shot CLI command (`wardby run`, `coding preflight`, migrations, imports).
227
+ */
228
+ export function warmUpExecutor(executor) {
229
+ void executor.warmUp?.()?.catch((err) => {
230
+ mcpLog.warn({ err }, "executor warm-up failed unexpectedly");
231
+ });
232
+ }
190
233
  /** The real CLI entry point: `wardby mcp`. Reads config from the environment, starts stdio or HTTP per MCP_TRANSPORT. */
191
234
  export async function startMcp(options = {}) {
192
235
  const mcpConfig = loadMcpConfig();
193
236
  // Parsed up front so a malformed value fails at startup, not at shutdown.
194
237
  const drainSeconds = loadShutdownDrainSeconds();
238
+ // Parsed now so a malformed list stops startup instead of failing every event.
239
+ const allowedAccounts = loadGitHubAllowedAccounts();
240
+ mcpLog.info({ additionalAccounts: [...allowedAccounts] }, "GitHub App serves its owner plus WARDBY_GITHUB_ALLOWED_ACCOUNTS");
195
241
  // Loads the model catalog before anything else: running on the shipped
196
242
  // catalog alone (the fallback currentModelCatalog() uses before a store
197
243
  // is installed) would quietly re-enable models an admin turned off.
@@ -202,6 +248,7 @@ export async function startMcp(options = {}) {
202
248
  const modelCatalog = await startModelCatalog(prisma);
203
249
  const providers = options.providers ?? buildMcpProviders().providers;
204
250
  await providers.executor.launch?.();
251
+ warmUpExecutor(providers.executor);
205
252
  if (!options.schedulerAttached)
206
253
  await warnIfNothingWillFireSchedules();
207
254
  if (mcpConfig.transport === "stdio") {
@@ -254,7 +301,14 @@ export async function startMcp(options = {}) {
254
301
  const eventConfig = loadGitHubEventConfig();
255
302
  const githubConfig = loadGitHubVcsConfig();
256
303
  const reviewHosts = providers.reviewHosts;
257
- const githubEvents = eventConfig.webhookSecret && reviewHosts?.github && githubConfig.appId && githubConfig.privateKey
304
+ const githubApp = githubConfig.appId && githubConfig.privateKey
305
+ ? new GitHubAppClient({
306
+ appId: githubConfig.appId,
307
+ privateKey: githubConfig.privateKey,
308
+ apiVersion: githubConfig.apiVersion,
309
+ })
310
+ : undefined;
311
+ const githubEvents = eventConfig.webhookSecret && reviewHosts?.github && githubApp
258
312
  ? {
259
313
  github: {
260
314
  db: prisma,
@@ -263,14 +317,9 @@ export async function startMcp(options = {}) {
263
317
  repoAccess: providers.repoAccess ?? createRepoAccessGate({ db: prisma, hosts: reviewHosts }),
264
318
  ...(providers.issueTrackers ? { issueTrackers: providers.issueTrackers } : {}),
265
319
  webhookSecret: eventConfig.webhookSecret,
266
- appIdentity: (() => {
267
- const client = new GitHubAppClient({
268
- appId: githubConfig.appId,
269
- privateKey: githubConfig.privateKey,
270
- apiVersion: githubConfig.apiVersion,
271
- });
272
- return () => client.appIdentity();
273
- })(),
320
+ appIdentity: (opts) => githubApp.appIdentity(opts),
321
+ allowedAccounts: () => allowedAccounts,
322
+ uninstallInstallation: (id) => githubApp.deleteInstallation(id),
274
323
  },
275
324
  }
276
325
  : undefined;