@wardby/cli 0.5.2 → 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 (411) 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/local-git.js +9 -1
  17. package/dist/coding/profile.d.ts +12 -0
  18. package/dist/coding/profile.js +6 -0
  19. package/dist/coding/protocol.d.ts +104 -0
  20. package/dist/coding/protocol.js +87 -3
  21. package/dist/coding/registry/pypi.d.ts +25 -1
  22. package/dist/coding/registry/pypi.js +140 -16
  23. package/dist/coding/registry/types.d.ts +17 -3
  24. package/dist/coding-worker/artifact.d.ts +8 -0
  25. package/dist/coding-worker/artifact.js +2 -2
  26. package/dist/coding-worker/driver.js +2 -0
  27. package/dist/coding-worker/sdk.d.ts +6 -0
  28. package/dist/coding-worker/sdk.js +1 -0
  29. package/dist/coding-worker/skills.d.ts +23 -0
  30. package/dist/coding-worker/skills.js +117 -0
  31. package/dist/coding-worker/types.d.ts +2 -0
  32. package/dist/config/github-allowed-accounts.d.ts +15 -0
  33. package/dist/config/github-allowed-accounts.js +36 -0
  34. package/dist/config/low-power.d.ts +12 -0
  35. package/dist/config/low-power.js +18 -0
  36. package/dist/config/mobile.d.ts +12 -0
  37. package/dist/config/mobile.js +18 -0
  38. package/dist/config/providers.d.ts +56 -2
  39. package/dist/config/providers.js +92 -6
  40. package/dist/core/db.d.ts +16 -6
  41. package/dist/core/db.js +12 -10
  42. package/dist/core/dispatch.d.ts +11 -0
  43. package/dist/core/dispatch.js +36 -1
  44. package/dist/core/engine-native.js +9 -5
  45. package/dist/core/host-events.d.ts +49 -3
  46. package/dist/core/host-events.js +289 -15
  47. package/dist/core/http-runtime.js +13 -6
  48. package/dist/core/issue-bridge.js +42 -2
  49. package/dist/core/issue-events.js +12 -1
  50. package/dist/core/issue-status.js +14 -3
  51. package/dist/core/merge-order-check.d.ts +129 -0
  52. package/dist/core/merge-order-check.js +336 -0
  53. package/dist/core/notification-dispatcher.d.ts +70 -0
  54. package/dist/core/notification-dispatcher.js +368 -0
  55. package/dist/core/notification-templates.d.ts +25 -0
  56. package/dist/core/notification-templates.js +105 -0
  57. package/dist/core/notifications.d.ts +10 -0
  58. package/dist/core/notifications.js +38 -0
  59. package/dist/core/pg-auth.d.ts +22 -0
  60. package/dist/core/pg-auth.js +152 -0
  61. package/dist/core/power-composition.d.ts +16 -0
  62. package/dist/core/power-composition.js +37 -0
  63. package/dist/core/power-idle.d.ts +14 -0
  64. package/dist/core/power-idle.js +30 -0
  65. package/dist/core/power.d.ts +71 -0
  66. package/dist/core/power.js +202 -0
  67. package/dist/core/reconciler.d.ts +4 -2
  68. package/dist/core/reconciler.js +29 -5
  69. package/dist/core/related-pull-requests.d.ts +48 -4
  70. package/dist/core/related-pull-requests.js +113 -11
  71. package/dist/core/repo-access.d.ts +15 -0
  72. package/dist/core/repo-access.js +66 -0
  73. package/dist/core/review-fix.d.ts +37 -0
  74. package/dist/core/review-fix.js +207 -6
  75. package/dist/core/review-host-tools.d.ts +20 -0
  76. package/dist/core/review-host-tools.js +58 -2
  77. package/dist/core/runner.d.ts +336 -3
  78. package/dist/core/runner.js +894 -317
  79. package/dist/core/secret-broker-config.d.ts +103 -0
  80. package/dist/core/secret-broker-config.js +112 -0
  81. package/dist/core/secrets.d.ts +39 -4
  82. package/dist/core/secrets.js +148 -40
  83. package/dist/core/workflow-event-recorder.d.ts +14 -0
  84. package/dist/core/workflow-event-recorder.js +120 -0
  85. package/dist/core/workflow-events.d.ts +82 -0
  86. package/dist/core/workflow-events.js +44 -0
  87. package/dist/core/workflow-run-events.d.ts +16 -0
  88. package/dist/core/workflow-run-events.js +40 -0
  89. package/dist/generated/prisma/browser.d.ts +58 -0
  90. package/dist/generated/prisma/client.d.ts +58 -0
  91. package/dist/generated/prisma/commonInputTypes.d.ts +150 -0
  92. package/dist/generated/prisma/enums.d.ts +27 -0
  93. package/dist/generated/prisma/enums.js +23 -0
  94. package/dist/generated/prisma/internal/class.d.ts +88 -0
  95. package/dist/generated/prisma/internal/class.js +4 -4
  96. package/dist/generated/prisma/internal/prismaNamespace.d.ts +772 -1
  97. package/dist/generated/prisma/internal/prismaNamespace.js +135 -4
  98. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +139 -0
  99. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +135 -4
  100. package/dist/generated/prisma/models/Agent.d.ts +360 -1
  101. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +73 -1
  102. package/dist/generated/prisma/models/CodingRun.d.ts +205 -1
  103. package/dist/generated/prisma/models/DeferredReview.d.ts +78 -1
  104. package/dist/generated/prisma/models/NativeGatewayCall.d.ts +1675 -0
  105. package/dist/generated/prisma/models/NativeGatewayCall.js +1 -0
  106. package/dist/generated/prisma/models/NativeGatewaySession.d.ts +1517 -0
  107. package/dist/generated/prisma/models/NativeGatewaySession.js +1 -0
  108. package/dist/generated/prisma/models/NativeWarmWorker.d.ts +1296 -0
  109. package/dist/generated/prisma/models/NativeWarmWorker.js +1 -0
  110. package/dist/generated/prisma/models/NotificationChannel.d.ts +1543 -0
  111. package/dist/generated/prisma/models/NotificationChannel.js +1 -0
  112. package/dist/generated/prisma/models/NotificationDelivery.d.ts +1502 -0
  113. package/dist/generated/prisma/models/NotificationDelivery.js +1 -0
  114. package/dist/generated/prisma/models/NotificationThread.d.ts +1090 -0
  115. package/dist/generated/prisma/models/NotificationThread.js +1 -0
  116. package/dist/generated/prisma/models/Run.d.ts +826 -1
  117. package/dist/generated/prisma/models/RunHostCheck.d.ts +36 -1
  118. package/dist/generated/prisma/models/Secret.d.ts +37 -1
  119. package/dist/generated/prisma/models/SecretBrokerChange.d.ts +1101 -0
  120. package/dist/generated/prisma/models/SecretBrokerChange.js +1 -0
  121. package/dist/generated/prisma/models/WorkflowEvent.d.ts +1422 -0
  122. package/dist/generated/prisma/models/WorkflowEvent.js +1 -0
  123. package/dist/generated/prisma/models.d.ts +8 -0
  124. package/dist/help/build.js +4 -2
  125. package/dist/help/catalog.d.ts +1 -1
  126. package/dist/help/catalog.js +31 -6
  127. package/dist/help-index.json +1492 -113
  128. package/dist/import/create.js +17 -0
  129. package/dist/import/neutral-schema.d.ts +6 -2
  130. package/dist/import/neutral-schema.js +2 -0
  131. package/dist/mcp/auth/access.d.ts +1 -0
  132. package/dist/mcp/auth/ownership.d.ts +1 -1
  133. package/dist/mcp/auth/repo-authorization.js +14 -0
  134. package/dist/mcp/auth/resource-server.d.ts +2 -1
  135. package/dist/mcp/auth/resource-server.js +7 -2
  136. package/dist/mcp/auth/self-hosted/browser.d.ts +1 -1
  137. package/dist/mcp/auth/self-hosted/browser.js +19 -6
  138. package/dist/mcp/context.d.ts +6 -0
  139. package/dist/mcp/host-events/github-ingress.d.ts +23 -1
  140. package/dist/mcp/host-events/github-ingress.js +68 -0
  141. package/dist/mcp/index.d.ts +13 -0
  142. package/dist/mcp/index.js +59 -10
  143. package/dist/mcp/tools/agents.js +83 -12
  144. package/dist/mcp/tools/notification-channels.d.ts +2 -0
  145. package/dist/mcp/tools/notification-channels.js +293 -0
  146. package/dist/mcp/tools/power.d.ts +2 -0
  147. package/dist/mcp/tools/power.js +77 -0
  148. package/dist/mcp/tools/runs.d.ts +0 -6
  149. package/dist/mcp/tools/runs.js +7 -2
  150. package/dist/mcp/tools/secret-broker-compat.d.ts +12 -0
  151. package/dist/mcp/tools/secret-broker-compat.js +23 -0
  152. package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -2
  153. package/dist/mcp/tools/secret-elicitation-form.js +200 -10
  154. package/dist/mcp/tools/secret-elicitation-server.js +8 -1
  155. package/dist/mcp/tools/secret-elicitation.d.ts +38 -3
  156. package/dist/mcp/tools/secret-elicitation.js +110 -12
  157. package/dist/mcp/tools/secrets.js +184 -7
  158. package/dist/mcp/tools/tools.d.ts +1 -1
  159. package/dist/mcp/tools/tools.js +22 -2
  160. package/dist/native-worker/composition.d.ts +23 -0
  161. package/dist/native-worker/composition.js +77 -0
  162. package/dist/native-worker/docker-isolation.d.ts +65 -0
  163. package/dist/native-worker/docker-isolation.js +183 -0
  164. package/dist/native-worker/docker-launcher.d.ts +74 -0
  165. package/dist/native-worker/docker-launcher.js +237 -0
  166. package/dist/native-worker/gateway-server.d.ts +39 -0
  167. package/dist/native-worker/gateway-server.js +315 -0
  168. package/dist/native-worker/gateway.d.ts +44 -0
  169. package/dist/native-worker/gateway.js +197 -0
  170. package/dist/native-worker/http-server.d.ts +22 -0
  171. package/dist/native-worker/http-server.js +109 -0
  172. package/dist/native-worker/http-transport.d.ts +17 -0
  173. package/dist/native-worker/http-transport.js +88 -0
  174. package/dist/native-worker/kubernetes-isolation.d.ts +90 -0
  175. package/dist/native-worker/kubernetes-isolation.js +218 -0
  176. package/dist/native-worker/kubernetes-launcher.d.ts +102 -0
  177. package/dist/native-worker/kubernetes-launcher.js +335 -0
  178. package/dist/native-worker/launch.d.ts +45 -0
  179. package/dist/native-worker/launch.js +44 -0
  180. package/dist/native-worker/ledger.d.ts +109 -0
  181. package/dist/native-worker/ledger.js +192 -0
  182. package/dist/native-worker/loopback.d.ts +12 -0
  183. package/dist/native-worker/loopback.js +49 -0
  184. package/dist/native-worker/main.d.ts +7 -0
  185. package/dist/native-worker/main.js +74 -0
  186. package/dist/native-worker/protocol.d.ts +744 -0
  187. package/dist/native-worker/protocol.js +200 -0
  188. package/dist/native-worker/sandbox-executor.d.ts +68 -0
  189. package/dist/native-worker/sandbox-executor.js +210 -0
  190. package/dist/native-worker/stdio.d.ts +29 -0
  191. package/dist/native-worker/stdio.js +188 -0
  192. package/dist/native-worker/warm-delivery.d.ts +16 -0
  193. package/dist/native-worker/warm-delivery.js +33 -0
  194. package/dist/native-worker/warm-pool-ledger.d.ts +39 -0
  195. package/dist/native-worker/warm-pool-ledger.js +73 -0
  196. package/dist/native-worker/warm-pool.d.ts +107 -0
  197. package/dist/native-worker/warm-pool.js +283 -0
  198. package/dist/native-worker/worker.d.ts +17 -0
  199. package/dist/native-worker/worker.js +115 -0
  200. package/dist/providers/auth/authorization-server.d.ts +3 -0
  201. package/dist/providers/auth/index.js +2 -0
  202. package/dist/providers/auth/self-hosted.d.ts +13 -1
  203. package/dist/providers/auth/self-hosted.js +54 -4
  204. package/dist/providers/chat/fake.d.ts +46 -0
  205. package/dist/providers/chat/fake.js +39 -0
  206. package/dist/providers/chat/index.d.ts +4 -0
  207. package/dist/providers/chat/index.js +8 -0
  208. package/dist/providers/chat/slack.d.ts +37 -0
  209. package/dist/providers/chat/slack.js +105 -0
  210. package/dist/providers/chat/types.d.ts +42 -0
  211. package/dist/providers/chat/types.js +19 -0
  212. package/dist/providers/coding-proxy/mock-upstream.d.ts +33 -0
  213. package/dist/providers/coding-proxy/mock-upstream.js +116 -0
  214. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +3 -0
  215. package/dist/providers/coding-proxy/registry/prisma-store.js +17 -0
  216. package/dist/providers/coding-proxy/registry/service.d.ts +4 -0
  217. package/dist/providers/coding-proxy/registry/service.js +20 -3
  218. package/dist/providers/coding-proxy/registry/store.d.ts +18 -1
  219. package/dist/providers/coding-proxy/registry/store.js +33 -0
  220. package/dist/providers/coding-proxy/runtime.js +12 -1
  221. package/dist/providers/coding-proxy/types.d.ts +2 -0
  222. package/dist/providers/engine/types.d.ts +19 -0
  223. package/dist/providers/engine/types.js +22 -0
  224. package/dist/providers/executor/composition.d.ts +24 -1
  225. package/dist/providers/executor/composition.js +39 -6
  226. package/dist/providers/executor/container.d.ts +24 -1
  227. package/dist/providers/executor/container.js +101 -11
  228. package/dist/providers/executor/dbos.d.ts +42 -0
  229. package/dist/providers/executor/dbos.js +118 -12
  230. package/dist/providers/executor/deferred.d.ts +56 -0
  231. package/dist/providers/executor/deferred.js +119 -0
  232. package/dist/providers/executor/routing.d.ts +37 -10
  233. package/dist/providers/executor/routing.js +73 -32
  234. package/dist/providers/executor/types.d.ts +12 -0
  235. package/dist/providers/jobs/docker.js +3 -3
  236. package/dist/providers/jobs/fake-kubernetes-api.d.ts +21 -2
  237. package/dist/providers/jobs/fake-kubernetes-api.js +42 -1
  238. package/dist/providers/jobs/kubernetes-api.d.ts +17 -1
  239. package/dist/providers/jobs/kubernetes-api.js +7 -0
  240. package/dist/providers/jobs/kubernetes-client.d.ts +10 -1
  241. package/dist/providers/jobs/kubernetes-client.js +52 -3
  242. package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +6 -1
  243. package/dist/providers/jobs/kubernetes-dry-run-fixture.js +6 -3
  244. package/dist/providers/jobs/kubernetes-isolation.d.ts +16 -1
  245. package/dist/providers/jobs/kubernetes-isolation.js +58 -5
  246. package/dist/providers/jobs/kubernetes-platform.d.ts +4 -1
  247. package/dist/providers/jobs/kubernetes-platform.js +38 -1
  248. package/dist/providers/jobs/kubernetes-preflight.d.ts +2 -1
  249. package/dist/providers/jobs/kubernetes-preflight.js +4 -2
  250. package/dist/providers/jobs/kubernetes.d.ts +56 -7
  251. package/dist/providers/jobs/kubernetes.js +117 -36
  252. package/dist/providers/jobs/metadata-probes.d.ts +11 -0
  253. package/dist/providers/jobs/metadata-probes.js +13 -0
  254. package/dist/providers/jobs/types.d.ts +9 -0
  255. package/dist/providers/llm/openai.js +0 -1
  256. package/dist/providers/review-host/github.d.ts +11 -1
  257. package/dist/providers/review-host/github.js +50 -0
  258. package/dist/providers/review-host/types.d.ts +23 -0
  259. package/dist/providers/vcs/github.d.ts +32 -6
  260. package/dist/providers/vcs/github.js +74 -16
  261. package/dist/quickstart/coding-db.js +14 -2
  262. package/dist/quickstart/coding-doctor.d.ts +3 -0
  263. package/dist/quickstart/coding-doctor.js +3 -0
  264. package/dist/quickstart/coding-images.d.ts +9 -0
  265. package/dist/quickstart/coding-images.js +39 -0
  266. package/dist/quickstart/coding-seed.d.ts +20 -1
  267. package/dist/quickstart/coding-seed.js +96 -7
  268. package/dist/quickstart/coding.d.ts +2 -0
  269. package/dist/quickstart/coding.js +75 -3
  270. package/dist/quickstart/config.d.ts +2 -0
  271. package/dist/quickstart/config.js +4 -0
  272. package/dist/quickstart/images.d.ts +13 -0
  273. package/dist/quickstart/images.js +80 -12
  274. package/dist/quickstart/index.d.ts +0 -2
  275. package/dist/quickstart/index.js +10 -14
  276. package/dist/quickstart/manifests.d.ts +56 -0
  277. package/dist/quickstart/manifests.js +523 -0
  278. package/dist/quickstart/python-detect.d.ts +10 -0
  279. package/dist/quickstart/python-detect.js +32 -0
  280. package/dist/quickstart/repo-packages.d.ts +10 -0
  281. package/dist/quickstart/repo-packages.js +132 -0
  282. package/dist/quickstart/reviewer-prompt.d.ts +12 -0
  283. package/dist/quickstart/reviewer-prompt.js +84 -0
  284. package/dist/quickstart/scenarios.d.ts +19 -0
  285. package/dist/quickstart/scenarios.js +28 -0
  286. package/dist/quickstart/sync-reviewer-prompt.d.ts +1 -0
  287. package/dist/quickstart/sync-reviewer-prompt.js +10 -0
  288. package/dist/quickstart-images.json +1 -1
  289. package/dist/sandbox/brokered-fetch.d.ts +23 -0
  290. package/dist/sandbox/brokered-fetch.js +37 -0
  291. package/dist/sandbox/host-functions.d.ts +34 -0
  292. package/dist/sandbox/host-functions.js +144 -80
  293. package/dist/sandbox/prelude.js +7 -1
  294. package/dist/sandbox/run-in-sandbox.d.ts +19 -0
  295. package/dist/sandbox/run-in-sandbox.js +28 -10
  296. package/dist/sandbox/safe-fetch.d.ts +2 -0
  297. package/dist/sandbox/safe-fetch.js +3 -1
  298. package/dist/sandbox/secret-broker-sigv4.d.ts +2 -0
  299. package/dist/sandbox/secret-broker-sigv4.js +49 -0
  300. package/dist/sandbox/secret-broker.d.ts +34 -0
  301. package/dist/sandbox/secret-broker.js +162 -0
  302. package/dist/sandbox/user-tool.d.ts +14 -0
  303. package/dist/sandbox/user-tool.js +32 -0
  304. package/dist/serve.js +18 -0
  305. package/dist/viewer/api-schema.d.ts +214 -18
  306. package/dist/viewer/api-schema.js +22 -0
  307. package/dist/viewer/build-schemas.js +4 -1
  308. package/dist/viewer/event-bus.d.ts +6 -0
  309. package/dist/viewer/event-bus.js +16 -7
  310. package/dist/viewer/graph.d.ts +5 -2
  311. package/dist/viewer/graph.js +7 -2
  312. package/dist/viewer/http.d.ts +0 -1
  313. package/dist/viewer/http.js +2 -2
  314. package/dist/viewer/infra.js +25 -2
  315. package/dist/viewer/limits.d.ts +4 -0
  316. package/dist/viewer/limits.js +4 -0
  317. package/dist/viewer/openapi.d.ts +15 -0
  318. package/dist/viewer/openapi.js +172 -0
  319. package/docs/README.md +7 -0
  320. package/docs/agent-recipes.md +57 -8
  321. package/docs/architecture-runtime.md +16 -1
  322. package/docs/code-review-agents.md +139 -5
  323. package/docs/coding-agent-setup.md +127 -2
  324. package/docs/coding-packages.md +105 -5
  325. package/docs/coding-services.md +3 -1
  326. package/docs/coding-worker-byo-images.md +239 -2
  327. package/docs/coding-worker-isolation.md +88 -23
  328. package/docs/getting-started-gke.md +225 -9
  329. package/docs/getting-started-identity-provider.md +21 -10
  330. package/docs/getting-started.md +140 -11
  331. package/docs/jira-agents.md +5 -1
  332. package/docs/knowledge.md +11 -0
  333. package/docs/native-sandbox.md +635 -0
  334. package/docs/release-verification.md +9 -0
  335. package/docs/security-deployment.md +113 -12
  336. package/docs/slack-notifications.md +252 -0
  337. package/docs/tool-secrets.md +252 -0
  338. package/docs/viewer-api.md +46 -5
  339. package/docs/viewer-api.openapi.yml +1404 -0
  340. package/help/admin-viewer.md +6 -2
  341. package/help/agent-recipes.md +27 -0
  342. package/help/architecture-agent.md +136 -0
  343. package/help/brokered-secrets.md +79 -0
  344. package/help/build-claude-code-image.md +131 -0
  345. package/help/build-worker-image.md +195 -0
  346. package/help/code-review-agents.md +68 -6
  347. package/help/coding-packages.md +31 -2
  348. package/help/creating-agents.md +14 -0
  349. package/help/deploy-gke.md +37 -6
  350. package/help/deployment-targets.md +24 -13
  351. package/help/errors/coding-provider-not-configured.md +2 -2
  352. package/help/errors/database-auth-invalid.md +28 -0
  353. package/help/errors/github-account-not-allowed.md +25 -0
  354. package/help/errors/low-power-disabled.md +29 -0
  355. package/help/errors/low-power-scale-forbidden.md +35 -0
  356. package/help/errors/low-power-wake-timeout.md +30 -0
  357. package/help/errors/mobile-disabled.md +31 -0
  358. package/help/errors/native-sandbox-capacity.md +22 -0
  359. package/help/errors/native-sandbox-deadline-exceeded.md +19 -0
  360. package/help/errors/native-sandbox-docker-failed.md +20 -0
  361. package/help/errors/native-sandbox-gateway-unavailable.md +21 -0
  362. package/help/errors/native-sandbox-image-not-pinned.md +19 -0
  363. package/help/errors/native-sandbox-isolation-mismatch.md +20 -0
  364. package/help/errors/native-sandbox-network-unenforced.md +22 -0
  365. package/help/errors/native-sandbox-requires-catalog.md +31 -0
  366. package/help/errors/native-sandbox-unavailable.md +19 -0
  367. package/help/errors/native-sandbox-warm-delivery-failed.md +21 -0
  368. package/help/errors/native-sandbox-worker-exited.md +20 -0
  369. package/help/errors/native-sandbox-worker-lost.md +20 -0
  370. package/help/errors/native-sandbox-worker-unready.md +22 -0
  371. package/help/errors/secret-broker-body-invalid.md +26 -0
  372. package/help/errors/secret-broker-changed.md +20 -0
  373. package/help/errors/secret-broker-config-invalid.md +30 -0
  374. package/help/errors/secret-broker-conflict.md +27 -0
  375. package/help/errors/secret-broker-destination-denied.md +28 -0
  376. package/help/errors/secret-broker-value-invalid.md +28 -0
  377. package/help/errors/secret-brokered.md +24 -0
  378. package/help/errors/secret-not-brokered.md +25 -0
  379. package/help/errors/slack-auth-failed.md +48 -0
  380. package/help/errors/slack-channel-unreachable.md +39 -0
  381. package/help/errors/slack-not-configured.md +33 -0
  382. package/help/getting-started.md +18 -0
  383. package/help/github.md +18 -1
  384. package/help/identity-and-access.md +5 -1
  385. package/help/local-repositories.md +35 -5
  386. package/help/low-power-mode.md +125 -0
  387. package/help/merge-order-check.md +117 -0
  388. package/help/mobile-app.md +54 -0
  389. package/help/native-sandbox.md +136 -0
  390. package/help/observability.md +8 -0
  391. package/help/related-pull-requests.md +70 -9
  392. package/help/repo-instructions-and-skills.md +91 -0
  393. package/help/review-fix-rounds.md +10 -0
  394. package/help/slack-notifications.md +62 -0
  395. package/help/troubleshooting/coding-workers.md +21 -0
  396. package/package.json +13 -5
  397. package/prisma/migrations/20261009000003_native_execution_mode/migration.sql +12 -0
  398. package/prisma/migrations/20261009000004_native_gateway_ledger/migration.sql +65 -0
  399. package/prisma/migrations/20261009000005_native_gateway_call_child_agent/migration.sql +5 -0
  400. package/prisma/migrations/20261009000006_run_deferred_start_stop/migration.sql +14 -0
  401. package/prisma/migrations/20261009000007_native_gateway_network_ready/migration.sql +5 -0
  402. package/prisma/migrations/20261010000001_workflow_notifications/migration.sql +99 -0
  403. package/prisma/migrations/20261011000001_native_gateway_session_status_index/migration.sql +2 -0
  404. package/prisma/migrations/20261012000001_native_warm_worker/migration.sql +32 -0
  405. package/prisma/migrations/20261013000001_coding_run_merge_order/migration.sql +2 -0
  406. package/prisma/migrations/20261013000001_secret_broker/migration.sql +17 -0
  407. package/prisma/migrations/20261013000002_deferred_review_request/migration.sql +4 -0
  408. package/prisma/migrations/20261014000000_repo_skills/migration.sql +15 -0
  409. package/prisma/migrations/20261015000001_coding_run_merge_order_index/migration.sql +2 -0
  410. package/prisma/migrations/20261015000002_no_change_rereview/migration.sql +2 -0
  411. package/prisma/schema.prisma +362 -91
@@ -27,9 +27,12 @@ import { authorizeRepositoryForSet, canonicalLocalRepository, } from "../auth/re
27
27
  import { McpError } from "../errors.js";
28
28
  import { textResult } from "./text-result.js";
29
29
  // workerImageRef is the BYO-arbitrary-image escape hatch (see
30
- // resolveCodingWorkerImage in container.ts) — setting or changing it
31
- // requires a step-up beyond agents:write, the same pattern make_owner below
32
- // uses for its own sensitive, ownership-bypassing mutation.
30
+ // resolveCodingWorkerImage/resolveCodingToolImage in container.ts). For a Codex agent it
31
+ // replaces the one container that runs the whole agent; for a Claude Code agent it replaces
32
+ // only the tool-runner container that runs the agent's commands, never the agent container
33
+ // (which always stays the deployment's standard Claude Code image). Either way, setting or
34
+ // changing it requires a step-up beyond agents:write, the same pattern make_owner below uses
35
+ // for its own sensitive, ownership-bypassing mutation.
33
36
  function requireWorkerImageRefScope(ctx) {
34
37
  requireScope(ctx, ctx.canonicalUri, "agents:admin");
35
38
  }
@@ -54,6 +57,16 @@ const MAX_DELEGATIONS_PER_RUN = 20;
54
57
  const PARALLEL_DELEGATIONS_DESCRIPTION = "Native agents with sub-agents: when true, the delegate_to_* calls the agent makes in one model turn run at the same time " +
55
58
  "instead of one after another (other tool calls still run in order). The run tree still shares one budget, so later " +
56
59
  "sub-agents get what earlier ones leave. Coding sub-agents still queue for CODING_MAX_CONCURRENT slots. Default false.";
60
+ /** Operator-facing spellings of Agent.nativeExecutionMode (the Prisma enum is `control_plane` | `sandbox`). */
61
+ const NATIVE_EXECUTION_MODES = ["control-plane", "sandbox"];
62
+ const toStoredMode = (mode) => mode === "sandbox" ? "sandbox" : "control_plane";
63
+ /** An agent row as returned to operators: the mode in its hyphenated spelling, null for coding agents. */
64
+ function withOperatorMode(agent) {
65
+ return {
66
+ ...agent,
67
+ nativeExecutionMode: agent.kind !== "native" ? null : agent.nativeExecutionMode === "sandbox" ? "sandbox" : "control-plane",
68
+ };
69
+ }
57
70
  const agentFields = {
58
71
  name: z.string().trim().min(1).max(MAX_AGENT_NAME_CHARS),
59
72
  systemPrompt: z.string().min(1).max(MAX_SYSTEM_PROMPT_CHARS),
@@ -69,6 +82,7 @@ const agentFields = {
69
82
  budgetGroupId: z.string().min(1).max(128),
70
83
  memoryEnabled: z.boolean(),
71
84
  effort: z.enum(LLM_EFFORT_LEVELS),
85
+ nativeExecutionMode: z.enum(NATIVE_EXECUTION_MODES),
72
86
  defectProjectKey: z.string().regex(PROJECT_KEY, "must be an upper-case Jira project key"),
73
87
  defectIssueType: z.string().trim().min(1).max(100),
74
88
  };
@@ -102,6 +116,7 @@ const CreateAgentSchema = z
102
116
  budgetGroupId: agentFields.budgetGroupId.optional(),
103
117
  memoryEnabled: agentFields.memoryEnabled.default(false),
104
118
  effort: agentFields.effort.optional(),
119
+ nativeExecutionMode: agentFields.nativeExecutionMode.optional(),
105
120
  defectProjectKey: agentFields.defectProjectKey.optional(),
106
121
  defectIssueType: agentFields.defectIssueType.optional(),
107
122
  codingProfile: CodingProfileSchema.optional(),
@@ -158,6 +173,7 @@ const UpdateAgentSchema = z
158
173
  budgetGroupId: agentFields.budgetGroupId.nullable().optional(),
159
174
  memoryEnabled: agentFields.memoryEnabled.optional(),
160
175
  effort: agentFields.effort.nullable().optional(),
176
+ nativeExecutionMode: agentFields.nativeExecutionMode.optional(),
161
177
  defectProjectKey: agentFields.defectProjectKey.nullable().optional(),
162
178
  defectIssueType: agentFields.defectIssueType.nullable().optional(),
163
179
  codingProfile: CodingProfilePatchSchema.extend({
@@ -210,7 +226,10 @@ const profileJsonSchema = {
210
226
  collectExclude: { type: "array", maxItems: 64, items: { type: "string" } },
211
227
  toolchain: { type: "string", enum: ["node", "node-python"] },
212
228
  toolchainVersion: { type: ["string", "null"] },
213
- workerImageRef: { type: ["string", "null"] },
229
+ workerImageRef: {
230
+ type: ["string", "null"],
231
+ description: "A digest-pinned custom image that this agent's commands run in, for your own languages and tools. Codex: build it FROM the coding-worker driver image; it runs the whole agent. Claude Code: build it FROM this release's wardby-claude-tool-runner image; it runs the agent's commands, while Claude Code itself stays on the standard image. Needs agents:admin. Null uses the toolchain's standard image.",
232
+ },
214
233
  workspaceDiskMb: {
215
234
  type: ["integer", "null"],
216
235
  minimum: 64,
@@ -223,9 +242,17 @@ const profileJsonSchema = {
223
242
  maximum: MAX_CODING_TURNS,
224
243
  description: `Claude Code runs only: the most agent turns (model calls) a run may take before it stops with coding_turn_limit. Null uses the worker default (${DEFAULT_CLAUDE_MAX_TURNS}). The run's budget and timeout still apply. Codex runs have no turn limit.`,
225
244
  },
245
+ repoSkills: {
246
+ type: "boolean",
247
+ description: "Load the repository's agent skills into runs (Codex: .agents/skills and .codex/skills; Claude Code: .claude/skills). Default true. Repository instructions (AGENTS.md for Codex, CLAUDE.md for Claude Code) load either way.",
248
+ },
249
+ claudeBareMode: {
250
+ type: "boolean",
251
+ description: "Claude Code runs only. Default true: Claude Code runs in bare mode and wardby adds the repository's CLAUDE.md and its skill list to the system prompt; the model reads a skill's SKILL.md with run_command. False: bare mode is off and Claude Code loads CLAUDE.md and skills itself (native Skill tool). Repository settings, hooks, and MCP config are never loaded either way.",
252
+ },
226
253
  packageAllowlist: {
227
254
  type: "object",
228
- description: "Approved top-level packages per ecosystem (npm, pypi). Needs packages:approve or agents:admin.",
255
+ description: "Approved top-level packages per ecosystem (npm, pypi). A PyPI entry may name extras, e.g. psycopg[binary]. Needs packages:approve or agents:admin.",
229
256
  additionalProperties: { type: "array", maxItems: 256, items: { type: "string" } },
230
257
  },
231
258
  packagePolicy: {
@@ -285,6 +312,23 @@ function validateEffort(kind, model, effort) {
285
312
  : "It accepts no effort setting; leave effort unset (or null)."));
286
313
  }
287
314
  }
315
+ const NATIVE_SANDBOX_MODE_DESCRIPTION = "Where a native agent's runs execute: control-plane (default, in the server process) or sandbox (an isolated container; needs the server's native sandbox configured).";
316
+ /**
317
+ * The mode only applies to native agents, and `sandbox` only works where the
318
+ * server composed a native sandbox executor; refuse it otherwise rather than
319
+ * store a mode whose every run would fail closed.
320
+ */
321
+ function validateNativeExecutionMode(ctx, kind, mode) {
322
+ if (mode == null)
323
+ return;
324
+ if (kind !== "native") {
325
+ throw new McpError(400, "nativeExecutionMode is only valid for native agents; coding agents do not use it.");
326
+ }
327
+ if (mode === "sandbox" && ctx.providers.executor.supportsNativeSandbox?.() !== true) {
328
+ throw new McpError(400, "native_sandbox_unavailable: this server has no native sandbox configured (set NATIVE_SANDBOX_LAUNCHER), " +
329
+ 'so an agent cannot use nativeExecutionMode "sandbox".');
330
+ }
331
+ }
288
332
  /**
289
333
  * A model being set (create, or update with `model`) must be runnable here:
290
334
  * in the catalog and enabled; for a native agent, its provider must also be
@@ -322,6 +366,8 @@ function storedProfile(profile) {
322
366
  packageAllowlist: profile.packageAllowlist,
323
367
  packagePolicy: profile.packagePolicy,
324
368
  services: profile.services,
369
+ repoSkills: profile.repoSkills,
370
+ claudeBareMode: profile.claudeBareMode,
325
371
  });
326
372
  }
327
373
  /** A service name an agent is allowed must exist in the catalog (any version); typos fail here, not at a run. */
@@ -366,6 +412,11 @@ export function registerAgentTools(mcp) {
366
412
  enum: [...LLM_EFFORT_LEVELS],
367
413
  description: "Reasoning effort for native agents. Unset uses the provider default.",
368
414
  },
415
+ nativeExecutionMode: {
416
+ type: "string",
417
+ enum: [...NATIVE_EXECUTION_MODES],
418
+ description: NATIVE_SANDBOX_MODE_DESCRIPTION,
419
+ },
369
420
  defectProjectKey: { type: "string", pattern: PROJECT_KEY.source, description: DEFECT_DESCRIPTION },
370
421
  defectIssueType: { type: "string", minLength: 1, maxLength: 100, description: DEFECT_DESCRIPTION },
371
422
  codingProfile: { ...profileJsonSchema, required: ["repository"] },
@@ -385,12 +436,17 @@ export function registerAgentTools(mcp) {
385
436
  validateSchedule(args.schedule, args.timezone ?? "UTC");
386
437
  requireModelAvailable(ctx, args.model, args.kind);
387
438
  validateEffort(args.kind, args.model, args.effort);
439
+ validateNativeExecutionMode(ctx, args.kind, args.nativeExecutionMode);
388
440
  if (args.codingProfile)
389
441
  await requireCatalogServiceNames(ctx.db, args.codingProfile.services);
390
442
  if (args.budgetGroupId) {
391
443
  await requireReadableBudgetGroup(ctx.db, args.budgetGroupId, ctx.principal.id);
392
444
  }
393
- const { codingProfile, repositoryAdminOverride, ...agentData } = args;
445
+ const { codingProfile, repositoryAdminOverride, nativeExecutionMode, ...agentFieldsData } = args;
446
+ const agentData = {
447
+ ...agentFieldsData,
448
+ ...(nativeExecutionMode ? { nativeExecutionMode: toStoredMode(nativeExecutionMode) } : {}),
449
+ };
394
450
  if (repositoryAdminOverride === true && !codingProfile) {
395
451
  throw new McpError(400, "repositoryAdminOverride only applies with a codingProfile repository.");
396
452
  }
@@ -414,7 +470,7 @@ export function registerAgentTools(mcp) {
414
470
  },
415
471
  include: { codingProfile: true },
416
472
  });
417
- return textResult(agent);
473
+ return textResult(withOperatorMode(agent));
418
474
  },
419
475
  });
420
476
  mcp.registerTool({
@@ -449,6 +505,11 @@ export function registerAgentTools(mcp) {
449
505
  enum: [...LLM_EFFORT_LEVELS, null],
450
506
  description: "Reasoning effort for native agents. Null clears it back to the provider default.",
451
507
  },
508
+ nativeExecutionMode: {
509
+ type: "string",
510
+ enum: [...NATIVE_EXECUTION_MODES],
511
+ description: NATIVE_SANDBOX_MODE_DESCRIPTION,
512
+ },
452
513
  defectProjectKey: {
453
514
  type: ["string", "null"],
454
515
  pattern: PROJECT_KEY.source,
@@ -585,6 +646,12 @@ export function registerAgentTools(mcp) {
585
646
  if (args.model !== undefined || args.effort !== undefined || nextKind !== existing.kind) {
586
647
  validateEffort(nextKind, args.model ?? existing.model, args.effort !== undefined ? args.effort : existing.effort);
587
648
  }
649
+ if (args.nativeExecutionMode !== undefined) {
650
+ validateNativeExecutionMode(ctx, nextKind, args.nativeExecutionMode);
651
+ }
652
+ else if (nextKind === "coding" && existing.nativeExecutionMode === "sandbox") {
653
+ throw new McpError(400, 'nativeExecutionMode is only valid for native agents; set nativeExecutionMode to "control-plane" before changing this agent to coding.');
654
+ }
588
655
  let nextProfile = null;
589
656
  if (nextKind === "coding") {
590
657
  const attachedTools = await tx.agentTool.count({ where: { agentId: args.id } });
@@ -614,7 +681,11 @@ export function registerAgentTools(mcp) {
614
681
  if (nextKind === "coding" && nextSchedule && nextScheduleEnabled && !nextProfile?.defaultTask) {
615
682
  throw new McpError(400, "A default task is required for an enabled coding-agent schedule.");
616
683
  }
617
- const { id, codingProfile: _profilePatch, repositoryAdminOverride: _override, ...updates } = args;
684
+ const { id, codingProfile: _profilePatch, repositoryAdminOverride: _override, nativeExecutionMode: modeArg, ...fieldUpdates } = args;
685
+ const updates = {
686
+ ...fieldUpdates,
687
+ ...(modeArg ? { nativeExecutionMode: toStoredMode(modeArg) } : {}),
688
+ };
618
689
  const stamp = authorization ? profileStamp(authorization) : {};
619
690
  const trace = debugTraceUntil === undefined ? {} : { debugTraceUntil };
620
691
  const profileMutation = nextKind === "native"
@@ -641,7 +712,7 @@ export function registerAgentTools(mcp) {
641
712
  by: ctx.principal.id,
642
713
  }, "coding debug trace set");
643
714
  }
644
- return textResult(agent);
715
+ return textResult(withOperatorMode(agent));
645
716
  },
646
717
  });
647
718
  mcp.registerTool({
@@ -651,7 +722,7 @@ export function registerAgentTools(mcp) {
651
722
  handler: async (_args, ctx) => {
652
723
  const [where, accessOf] = await Promise.all([readableAgentsWhere(ctx), agentAccessResolver(ctx)]);
653
724
  const agents = await ctx.db.agent.findMany({ where, include: { codingProfile: true } });
654
- return textResult(agents.map((agent) => ({ ...agent, access: accessOf(agent) })));
725
+ return textResult(agents.map((agent) => ({ ...withOperatorMode(agent), access: accessOf(agent) })));
655
726
  },
656
727
  });
657
728
  mcp.registerTool({
@@ -671,7 +742,7 @@ export function registerAgentTools(mcp) {
671
742
  // A7: a tool's code and schema are its owner's, whoever can read the
672
743
  // agent (the agent's owner included). Capabilities stay visible: config.
673
744
  return textResult({
674
- ...agent,
745
+ ...withOperatorMode(agent),
675
746
  access,
676
747
  tools: agent.tools.map((attachment) => ({
677
748
  ...attachment,
@@ -870,7 +941,7 @@ export function registerAgentTools(mcp) {
870
941
  }
871
942
  }
872
943
  return {
873
- ...updated,
944
+ ...withOperatorMode(updated),
874
945
  repositoryApprovalsRevoked: revoked,
875
946
  bindingsRemoved,
876
947
  toolCapabilitiesSuspended,
@@ -0,0 +1,2 @@
1
+ import type { WardbyMcpServer } from "../server.js";
2
+ export declare function registerNotificationChannelTools(mcp: WardbyMcpServer): void;
@@ -0,0 +1,293 @@
1
+ /**
2
+ * NotificationChannel CRUD — admin-approved links from a Slack channel to a
3
+ * Jira project or to an agent (native or coding), so wardby's workflow dispatcher
4
+ * (core/notification-dispatcher.ts) knows where to post a card's lifecycle:
5
+ * picked up, PR opened, review verdict, fix rounds, merge. Outbound only —
6
+ * wardby never reads a channel's history, and linking posts nothing.
7
+ *
8
+ * Same trust model as link_issue_project (src/mcp/tools/issue-projects.ts):
9
+ * wardby cannot verify a caller's own Slack workspace access (one bot token
10
+ * for the whole deployment, no per-user OAuth), so link/unlink/test need
11
+ * agents:admin with the admin role, and the approver is stamped on the
12
+ * link. Exactly one of projectKey/agentId is the link's subject (enforced
13
+ * again by a DB CHECK constraint); `events` narrows which of the six
14
+ * workflow event kinds post to it (empty/omitted = all); private Slack
15
+ * channels always need /invite @<app> before the bot can see or post to
16
+ * them. See docs/private/2026-10-08-slack-workflow-notifications-design.md §8.
17
+ */
18
+ import { Prisma } from "#prisma";
19
+ import { requireAgentAccess } from "../auth/access.js";
20
+ import { requireScope } from "../auth/resource-server.js";
21
+ import { McpError } from "../errors.js";
22
+ import { PROJECT_KEY } from "../../providers/issue-tracker/types.js";
23
+ import { ChatError } from "../../providers/chat/types.js";
24
+ import { WORKFLOW_EVENT_KINDS } from "../../core/workflow-events.js";
25
+ import { textResult } from "./text-result.js";
26
+ const CHAT_PROVIDERS = ["slack"];
27
+ /** Slack channel id: C… (public) or G… (private), case-insensitive, normalized upper-case. */
28
+ const CHANNEL_ID = /^[CG][A-Z0-9]{2,20}$/i;
29
+ function normalizeProjectKey(projectKey) {
30
+ const key = projectKey.trim().toUpperCase();
31
+ if (!PROJECT_KEY.test(key))
32
+ throw new McpError(400, `invalid projectKey "${projectKey}"`);
33
+ return key;
34
+ }
35
+ function normalizeChannelId(channel) {
36
+ const id = channel.trim().toUpperCase();
37
+ if (!CHANNEL_ID.test(id)) {
38
+ throw new McpError(400, "Pass the channel id (C…); find it in the channel's details in Slack.");
39
+ }
40
+ return id;
41
+ }
42
+ export function registerNotificationChannelTools(mcp) {
43
+ mcp.registerTool({
44
+ name: "link_notification_channel",
45
+ scope: "agents:write",
46
+ description: "Links a Slack channel to a Jira project or to an agent (native or coding), so wardby posts that project's or " +
47
+ "agent's workflow updates there: a card picked up, a PR opened, a review verdict, fix rounds, and the merge (one thread per card). " +
48
+ "Outbound only — wardby never reads the channel's history, and nothing in Slack starts work. Admin-approved " +
49
+ "(agents:admin with the admin role), the same trust model as link_issue_project: wardby cannot verify a caller's " +
50
+ "own Slack workspace access, so an admin approves every link and is stamped as its authorizer. " +
51
+ "Exactly one of projectKey or agentId is the link's subject: a project link (with issueProvider, default jira) " +
52
+ "matches events for issues in that project; an agent link matches events for that agent's runs. " +
53
+ "channel is the Slack channel id (C… for public, G… for private), never a #name — find it in the channel's " +
54
+ "details in Slack. Private channels always need /invite @<app> before the bot can see or post to them. " +
55
+ "events narrows which of the six workflow event kinds post to this link; empty or omitted means all. " +
56
+ "includeCost appends the run's spend line to messages. Re-linking the same channel + subject replaces events and " +
57
+ "includeCost and clears any previously recorded delivery error — always send the full desired state.",
58
+ inputSchema: {
59
+ type: "object",
60
+ additionalProperties: false,
61
+ properties: {
62
+ provider: { type: "string", enum: [...CHAT_PROVIDERS] },
63
+ channel: { type: "string", minLength: 1, maxLength: 64 },
64
+ projectKey: { type: "string", minLength: 1, maxLength: 255 },
65
+ issueProvider: { type: "string", enum: ["jira"] },
66
+ agentId: { type: "string" },
67
+ events: { type: "array", items: { type: "string", enum: [...WORKFLOW_EVENT_KINDS] }, uniqueItems: true },
68
+ includeCost: { type: "boolean" },
69
+ },
70
+ required: ["channel"],
71
+ },
72
+ handler: async (args, ctx) => {
73
+ requireScope(ctx, ctx.canonicalUri, "agents:admin");
74
+ const provider = args.provider ?? "slack";
75
+ const chat = ctx.providers.chat?.[provider];
76
+ if (!chat) {
77
+ throw new McpError(400, "Slack is not configured (WARDBY_SLACK_BOT_TOKEN). See help article errors/slack-not-configured.");
78
+ }
79
+ const hasProject = args.projectKey !== undefined;
80
+ const hasAgent = args.agentId !== undefined;
81
+ if (hasProject === hasAgent) {
82
+ throw new McpError(400, "Give exactly one of projectKey or agentId.");
83
+ }
84
+ let issueProvider = null;
85
+ let projectKey = null;
86
+ let agentId = null;
87
+ if (hasProject) {
88
+ issueProvider = args.issueProvider ?? "jira";
89
+ projectKey = normalizeProjectKey(args.projectKey);
90
+ }
91
+ else {
92
+ agentId = args.agentId;
93
+ const agent = await ctx.db.agent.findUnique({ where: { id: agentId } });
94
+ if (!agent)
95
+ throw new McpError(404, `Agent "${agentId}" not found.`);
96
+ }
97
+ const channelId = normalizeChannelId(args.channel);
98
+ // `info` is undefined when channelInfo can't be validated at all
99
+ // (missing_scope: no channels:read/groups:read — accept the id
100
+ // unvalidated); any other auth failure (revoked or invalid token) is a
101
+ // real error the operator must fix before linking. `info` is
102
+ // null when the bot genuinely cannot see the channel (an error, not a
103
+ // validation gap) — kept out of the try/catch below so the 400 for
104
+ // "bot cannot see this channel" isn't routed through ChatError handling.
105
+ let info;
106
+ try {
107
+ info = await chat.channelInfo(channelId);
108
+ }
109
+ catch (err) {
110
+ if (!(err instanceof ChatError && err.code === "auth_failed"))
111
+ throw err;
112
+ if (err.slackError !== "missing_scope") {
113
+ throw new McpError(400, `Slack rejected the bot token (${err.slackError}). See help article errors/slack-auth-failed.`);
114
+ }
115
+ info = undefined;
116
+ }
117
+ if (info === null) {
118
+ throw new McpError(400, `The bot cannot see channel ${channelId}: invite it with /invite @<app> (private channels always need ` +
119
+ "this). See errors/slack-channel-unreachable.");
120
+ }
121
+ const channelName = info ? info.name : null;
122
+ const events = [...new Set(args.events ?? [])];
123
+ const includeCost = args.includeCost ?? false;
124
+ const fields = {
125
+ channelName,
126
+ events,
127
+ includeCost,
128
+ lastError: null,
129
+ lastErrorAt: null,
130
+ authorizedById: ctx.principal.id,
131
+ authorizedAt: new Date(),
132
+ };
133
+ // Can't use Prisma `upsert` here: Postgres treats NULLs as distinct
134
+ // within a unique index, and this link's unique keys
135
+ // (provider, channelId, issueProvider, projectKey) / (provider,
136
+ // channelId, agentId) each have two columns that are null for the
137
+ // other subject kind — so the where-unique input Prisma generates for
138
+ // this compound key never matches an existing row the way a fully
139
+ // non-null key would. Find the existing row explicitly instead, then
140
+ // update it by id or create.
141
+ const subjectWhere = hasProject
142
+ ? { provider, channelId, issueProvider, projectKey }
143
+ : { provider, channelId, agentId };
144
+ const existing = await ctx.db.notificationChannel.findFirst({ where: subjectWhere });
145
+ let link;
146
+ if (existing) {
147
+ link = await ctx.db.notificationChannel.update({ where: { id: existing.id }, data: fields });
148
+ }
149
+ else {
150
+ try {
151
+ link = await ctx.db.notificationChannel.create({
152
+ data: { provider, channelId, issueProvider, projectKey, agentId, ...fields },
153
+ });
154
+ }
155
+ catch (err) {
156
+ if (!(err instanceof Prisma.PrismaClientKnownRequestError) || err.code !== "P2002")
157
+ throw err;
158
+ // Lost a create race: another concurrent link_notification_channel
159
+ // call for the same channel + subject committed first. Re-linking
160
+ // an existing link is normal, documented behavior (it replaces
161
+ // events/includeCost and clears lastError) — so re-query and
162
+ // update that row instead of surfacing the race as an error.
163
+ const raced = await ctx.db.notificationChannel.findFirst({ where: subjectWhere });
164
+ if (!raced)
165
+ throw err;
166
+ link = await ctx.db.notificationChannel.update({ where: { id: raced.id }, data: fields });
167
+ }
168
+ }
169
+ return textResult({ link });
170
+ },
171
+ });
172
+ mcp.registerTool({
173
+ name: "unlink_notification_channel",
174
+ scope: "agents:write",
175
+ description: "Removes a notification channel link. Admin only (agents:admin with the admin role).",
176
+ inputSchema: {
177
+ type: "object",
178
+ additionalProperties: false,
179
+ properties: { id: { type: "string" } },
180
+ required: ["id"],
181
+ },
182
+ handler: async (args, ctx) => {
183
+ requireScope(ctx, ctx.canonicalUri, "agents:admin");
184
+ const { count } = await ctx.db.notificationChannel.deleteMany({ where: { id: args.id } });
185
+ return textResult({ unlinked: count > 0 });
186
+ },
187
+ });
188
+ mcp.registerTool({
189
+ name: "list_notification_channels",
190
+ scope: "agents:read",
191
+ description: "Lists notification channel links, each with its pending and failed delivery counts. Filter by projectKey, " +
192
+ "agentId, or channel (the Slack channel id). Listing an agent's own links needs read access to that agent; " +
193
+ "listing without agentId (every link, including project links) needs agents:admin with the admin role.",
194
+ inputSchema: {
195
+ type: "object",
196
+ additionalProperties: false,
197
+ properties: {
198
+ projectKey: { type: "string", minLength: 1, maxLength: 255 },
199
+ agentId: { type: "string" },
200
+ channel: { type: "string", minLength: 1, maxLength: 64 },
201
+ },
202
+ },
203
+ handler: async (args, ctx) => {
204
+ if (args.agentId !== undefined) {
205
+ await requireAgentAccess(ctx, args.agentId, "read");
206
+ }
207
+ else {
208
+ requireScope(ctx, ctx.canonicalUri, "agents:admin");
209
+ }
210
+ const where = {};
211
+ if (args.agentId !== undefined)
212
+ where.agentId = args.agentId;
213
+ if (args.projectKey !== undefined)
214
+ where.projectKey = normalizeProjectKey(args.projectKey);
215
+ if (args.channel !== undefined)
216
+ where.channelId = normalizeChannelId(args.channel);
217
+ const links = await ctx.db.notificationChannel.findMany({ where, orderBy: { createdAt: "asc" } });
218
+ // Counts only for the listed links' own (provider, channel) pairs, so a
219
+ // caller never learns about deliveries to channels it cannot list.
220
+ const pairKey = (r) => `${r.provider}\u0000${r.channelId}`;
221
+ const pairs = [
222
+ ...new Map(links.map((l) => [pairKey(l), { provider: l.provider, channelId: l.channelId }])).values(),
223
+ ];
224
+ const counts = pairs.length === 0
225
+ ? []
226
+ : await ctx.db.notificationDelivery.groupBy({
227
+ by: ["provider", "channelId", "state"],
228
+ where: { state: { in: ["pending", "failed"] }, OR: pairs },
229
+ _count: true,
230
+ });
231
+ const byChannel = new Map();
232
+ for (const row of counts) {
233
+ const entry = byChannel.get(pairKey(row)) ?? { pending: 0, failed: 0 };
234
+ if (row.state === "pending")
235
+ entry.pending = row._count;
236
+ else if (row.state === "failed")
237
+ entry.failed = row._count;
238
+ byChannel.set(pairKey(row), entry);
239
+ }
240
+ const channels = links.map((l) => ({
241
+ ...l,
242
+ pending: byChannel.get(pairKey(l))?.pending ?? 0,
243
+ failed: byChannel.get(pairKey(l))?.failed ?? 0,
244
+ }));
245
+ return textResult({ channels });
246
+ },
247
+ });
248
+ mcp.registerTool({
249
+ name: "test_notification_channel",
250
+ scope: "agents:write",
251
+ description: "Posts a one-off confirmation message to a linked channel, to prove the bot can write there before relying on it " +
252
+ "for workflow notifications (a private channel needs /invite @<app> first; a channel the bot can't reach " +
253
+ "surfaces its error immediately instead of silently queueing). Admin only (agents:admin with the admin role).",
254
+ inputSchema: {
255
+ type: "object",
256
+ additionalProperties: false,
257
+ properties: { id: { type: "string" } },
258
+ required: ["id"],
259
+ },
260
+ handler: async (args, ctx) => {
261
+ requireScope(ctx, ctx.canonicalUri, "agents:admin");
262
+ const link = await ctx.db.notificationChannel.findUnique({ where: { id: args.id } });
263
+ if (!link)
264
+ throw new McpError(404, `Notification channel link "${args.id}" not found.`);
265
+ const chat = ctx.providers.chat?.[link.provider];
266
+ if (!chat) {
267
+ throw new McpError(400, "Slack is not configured (WARDBY_SLACK_BOT_TOKEN). See help article errors/slack-not-configured.");
268
+ }
269
+ try {
270
+ await chat.postMessage(link.channelId, { text: "✅ wardby is connected to this channel." });
271
+ }
272
+ catch (err) {
273
+ if (!(err instanceof ChatError))
274
+ throw err;
275
+ if (err.code === "channel_unreachable") {
276
+ await ctx.db.notificationChannel.update({
277
+ where: { id: link.id },
278
+ data: { lastError: err.slackError, lastErrorAt: new Date() },
279
+ });
280
+ }
281
+ throw new McpError(400, `${err.code}: ${err.slackError}`);
282
+ }
283
+ // The bot can post here again: clear any recorded delivery error.
284
+ if (link.lastError !== null || link.lastErrorAt !== null) {
285
+ await ctx.db.notificationChannel.update({
286
+ where: { id: link.id },
287
+ data: { lastError: null, lastErrorAt: null },
288
+ });
289
+ }
290
+ return textResult({ tested: true });
291
+ },
292
+ });
293
+ }
@@ -0,0 +1,2 @@
1
+ import type { WardbyMcpServer } from "../server.js";
2
+ export declare function registerPowerTools(mcp: WardbyMcpServer): void;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Low-power mode over MCP: power_status reads the coding proxy's and native
3
+ * gateway's state, power_sleep scales them to zero now instead of waiting out
4
+ * the idle window. Both need power:manage, a privileged scope honoured only
5
+ * for the admin role (resource-server.ts). power_sleep never stops a run: a
6
+ * target with active or queued runs (or, for the gateway, an active gateway
7
+ * session) is refused and its run ids returned. Each power_sleep is audited
8
+ * (event power.sleep, outcome ok or error, naming the principal), including a
9
+ * failed or refused call, since a failure can follow a target already slept.
10
+ */
11
+ import { z } from "zod";
12
+ import { logger } from "../../core/logger.js";
13
+ import { DISABLED_POWER, LOW_POWER_DISABLED } from "../../core/power.js";
14
+ import { McpError } from "../errors.js";
15
+ import { textResult } from "./text-result.js";
16
+ const powerToolLog = logger.child({ module: "mcp-power" });
17
+ const TARGETS = ["proxy", "gateway"];
18
+ const SleepSchema = z.object({ target: z.enum(TARGETS).optional() }).strict();
19
+ const StatusSchema = z.object({}).strict();
20
+ function parse(schema, label, value) {
21
+ const result = schema.safeParse(value ?? {});
22
+ if (!result.success) {
23
+ const details = result.error.issues
24
+ .map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`)
25
+ .join("; ");
26
+ throw new McpError(400, `Invalid ${label}: ${details}`);
27
+ }
28
+ return result.data;
29
+ }
30
+ export function registerPowerTools(mcp) {
31
+ mcp.registerTool({
32
+ name: "power_status",
33
+ scope: "power:manage",
34
+ description: "Admins (power:manage): low-power mode state. For the coding proxy and the native gateway: replicas, whether a ready endpoint exists, when it last woke (wokeAt), and blockers (run ids keeping it busy). Returns { enabled: false } when low-power mode is off.",
35
+ inputSchema: { type: "object", additionalProperties: false, properties: {} },
36
+ handler: async (rawArgs, ctx) => {
37
+ parse(StatusSchema, "power_status arguments", rawArgs);
38
+ return textResult(await (ctx.providers.power ?? DISABLED_POWER).status());
39
+ },
40
+ });
41
+ mcp.registerTool({
42
+ name: "power_sleep",
43
+ scope: "power:manage",
44
+ description: "Admins (power:manage): scale the coding proxy and/or native gateway to zero now, skipping low-power mode's idle window. It never stops a run: a target is refused while it has active or queued runs (for the gateway, also an active gateway session), and those run ids are returned under blocked. Recently finished runs do not block it. The next run wakes the target again. Fails (409 low_power_disabled) when low-power mode is off.",
45
+ inputSchema: {
46
+ type: "object",
47
+ additionalProperties: false,
48
+ properties: {
49
+ target: {
50
+ type: "string",
51
+ enum: [...TARGETS],
52
+ description: "Sleep only this target. Omit to sleep both.",
53
+ },
54
+ },
55
+ },
56
+ handler: async (rawArgs, ctx) => {
57
+ const { target } = parse(SleepSchema, "power_sleep arguments", rawArgs);
58
+ const targets = target ? [target] : [...TARGETS];
59
+ const power = ctx.providers.power ?? DISABLED_POWER;
60
+ const audit = { event: "power.sleep", principal: ctx.principal.id, targets };
61
+ let result;
62
+ try {
63
+ result = await power.sleepNow(targets);
64
+ }
65
+ catch (err) {
66
+ // Audited before rethrowing: sleepNow may already have slept an earlier target.
67
+ const error = err instanceof Error ? err.message : String(err);
68
+ powerToolLog.warn({ ...audit, outcome: "error", error }, "power_sleep");
69
+ if (err instanceof Error && err.message.startsWith(`${LOW_POWER_DISABLED}:`))
70
+ throw new McpError(409, err.message);
71
+ throw err;
72
+ }
73
+ powerToolLog.info({ ...audit, outcome: "ok", slept: result.slept, blocked: result.blocked }, "power_sleep");
74
+ return textResult(result);
75
+ },
76
+ });
77
+ }
@@ -1,8 +1,2 @@
1
1
  import type { WardbyMcpServer } from "../server.js";
2
- /**
3
- * Run visibility (resource-sharing grants spec §3.5, A6): a run's output is
4
- * the triggerer's and the agent owner's, never every reader's. The owner
5
- * (and the stdio operator) sees every run; anyone else only runs with
6
- * Run.triggeredById equal to themselves.
7
- */
8
2
  export declare function registerRunTools(mcp: WardbyMcpServer): void;
@@ -10,6 +10,11 @@ import { textResult } from "./text-result.js";
10
10
  * (and the stdio operator) sees every run; anyone else only runs with
11
11
  * Run.triggeredById equal to themselves.
12
12
  */
13
+ /** A run row with its execution mode in the operator spelling agent tools use; null for coding runs. */
14
+ function withOperatorMode(run) {
15
+ const mode = run.nativeExecutionMode;
16
+ return { ...run, nativeExecutionMode: mode == null ? null : mode === "sandbox" ? "sandbox" : "control-plane" };
17
+ }
13
18
  export function registerRunTools(mcp) {
14
19
  mcp.registerTool({
15
20
  name: "list_runs",
@@ -42,7 +47,7 @@ export function registerRunTools(mcp) {
42
47
  const queuedAtByRun = new Map(queued.map((row) => [row.runId, row.queuedAt?.toISOString()]));
43
48
  return textResult(runs.map((run) => {
44
49
  const codingQueuedAt = queuedAtByRun.get(run.id);
45
- return codingQueuedAt ? { ...run, codingQueuedAt } : run;
50
+ return codingQueuedAt ? { ...withOperatorMode(run), codingQueuedAt } : withOperatorMode(run);
46
51
  }));
47
52
  },
48
53
  });
@@ -106,7 +111,7 @@ export function registerRunTools(mcp) {
106
111
  // Which services the run started with ("postgres 16"); never their images or environments.
107
112
  const services = codingRun ? storedServiceLabels(codingRun.services) : [];
108
113
  return textResult({
109
- ...run,
114
+ ...withOperatorMode(run),
110
115
  ...(codingResult ? { codingResult } : {}),
111
116
  ...(failureCategory ? { failureCategory } : {}),
112
117
  ...(diagnosticId ? { diagnosticId } : {}),