@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
@@ -0,0 +1,103 @@
1
+ import { z } from "zod";
2
+ export declare const MIN_BROKERED_VALUE_LENGTH = 6;
3
+ export declare const SecretBrokerConfigSchema: z.ZodObject<{
4
+ hosts: z.ZodArray<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, "many">;
5
+ pathPrefixes: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
6
+ placement: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
7
+ kind: z.ZodLiteral<"header">;
8
+ name: z.ZodEffects<z.ZodString, string, string>;
9
+ format: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
10
+ }, "strict", z.ZodTypeAny, {
11
+ name: string;
12
+ kind: "header";
13
+ format: string;
14
+ }, {
15
+ name: string;
16
+ kind: "header";
17
+ format: string;
18
+ }>, z.ZodObject<{
19
+ kind: z.ZodLiteral<"query">;
20
+ name: z.ZodString;
21
+ }, "strict", z.ZodTypeAny, {
22
+ name: string;
23
+ kind: "query";
24
+ }, {
25
+ name: string;
26
+ kind: "query";
27
+ }>, z.ZodObject<{
28
+ kind: z.ZodLiteral<"body">;
29
+ field: z.ZodString;
30
+ }, "strict", z.ZodTypeAny, {
31
+ kind: "body";
32
+ field: string;
33
+ }, {
34
+ kind: "body";
35
+ field: string;
36
+ }>, z.ZodObject<{
37
+ kind: z.ZodLiteral<"aws-sigv4">;
38
+ region: z.ZodString;
39
+ service: z.ZodString;
40
+ }, "strict", z.ZodTypeAny, {
41
+ kind: "aws-sigv4";
42
+ service: string;
43
+ region: string;
44
+ }, {
45
+ kind: "aws-sigv4";
46
+ service: string;
47
+ region: string;
48
+ }>]>;
49
+ }, "strict", z.ZodTypeAny, {
50
+ hosts: string[];
51
+ placement: {
52
+ name: string;
53
+ kind: "header";
54
+ format: string;
55
+ } | {
56
+ name: string;
57
+ kind: "query";
58
+ } | {
59
+ kind: "body";
60
+ field: string;
61
+ } | {
62
+ kind: "aws-sigv4";
63
+ service: string;
64
+ region: string;
65
+ };
66
+ pathPrefixes?: string[] | undefined;
67
+ }, {
68
+ hosts: string[];
69
+ placement: {
70
+ name: string;
71
+ kind: "header";
72
+ format: string;
73
+ } | {
74
+ name: string;
75
+ kind: "query";
76
+ } | {
77
+ kind: "body";
78
+ field: string;
79
+ } | {
80
+ kind: "aws-sigv4";
81
+ service: string;
82
+ region: string;
83
+ };
84
+ pathPrefixes?: string[] | undefined;
85
+ }>;
86
+ export type SecretBrokerConfig = z.infer<typeof SecretBrokerConfigSchema>;
87
+ export type SecretBrokerPlacement = SecretBrokerConfig["placement"];
88
+ export declare function parseSecretBrokerConfig(input: unknown): SecretBrokerConfig;
89
+ /**
90
+ * A fingerprint of one broker config (sha256 hex of its parsed JSON): an unbroker
91
+ * link carries the hash of the config it was minted for, so it can't remove a
92
+ * config that has changed since.
93
+ */
94
+ export declare function brokerConfigHash(config: SecretBrokerConfig): string;
95
+ export declare const SECRET_BROKER_CHANGED = "secret_broker_changed: brokering changed since this link was created; request a new link";
96
+ export type SigV4Credentials = {
97
+ accessKeyId: string;
98
+ secretAccessKey: string;
99
+ sessionToken?: string;
100
+ };
101
+ export declare function parseSigV4Value(value: string): SigV4Credentials;
102
+ /** Throws unless `value` can be brokered under `config` (and later scrubbed from responses). */
103
+ export declare function assertBrokerableValue(config: SecretBrokerConfig, value: string): void;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * A brokered secret's owner-set config: where its value may be sent and how it is
3
+ * placed. Validated on every write; the privileged host (sandbox/secret-broker.ts)
4
+ * trusts it at request time.
5
+ */
6
+ import { createHash } from "node:crypto";
7
+ import { z } from "zod";
8
+ export const MIN_BROKERED_VALUE_LENGTH = 6;
9
+ const FORBIDDEN_HEADERS = ["host", "connection", "transfer-encoding", "content-length", "upgrade", "proxy-connection"];
10
+ const TOKEN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/;
11
+ const HOSTNAME = /^(?=.{1,253}$)(?!-)[a-z0-9-]{1,63}(?:\.(?!-)[a-z0-9-]{1,63})+$/;
12
+ const IPV4 = /^\d{1,3}(?:\.\d{1,3}){3}$/;
13
+ /** False for a name a resolver could read as an IPv4 address ("127.1", "0x7f.0.0.1") or with a label ending in "-". */
14
+ function isExactHostname(h) {
15
+ if (!HOSTNAME.test(h) || IPV4.test(h))
16
+ return false;
17
+ const labels = h.split(".");
18
+ const last = labels[labels.length - 1];
19
+ return !/^\d+$/.test(last) && !last.startsWith("0x") && !labels.some((l) => l.endsWith("-"));
20
+ }
21
+ const host = z
22
+ .string()
23
+ .transform((h) => h.toLowerCase())
24
+ .refine(isExactHostname, "hosts must be exact hostnames (no scheme, port, wildcard, or IP)");
25
+ const pathPrefix = z
26
+ .string()
27
+ .max(512)
28
+ .startsWith("/")
29
+ .refine(
30
+ // eslint-disable-next-line no-control-regex
31
+ (p) => !/[?#\\\x00-\x1f\x7f]/.test(p) && !/%2f|%5c/i.test(p), "path prefixes must not contain ?, #, a backslash, control characters, or an encoded / or \\");
32
+ const placement = z.discriminatedUnion("kind", [
33
+ z
34
+ .object({
35
+ kind: z.literal("header"),
36
+ name: z
37
+ .string()
38
+ .regex(TOKEN, "header name must be an HTTP token")
39
+ .refine((n) => !FORBIDDEN_HEADERS.includes(n.toLowerCase()), "that header cannot be set"),
40
+ format: z
41
+ .string()
42
+ .max(256)
43
+ .refine((f) => f.split("{value}").length === 2, 'format must contain "{value}" exactly once')
44
+ .refine((f) => !/[\r\n]/.test(f), "format must not contain line breaks"),
45
+ })
46
+ .strict(),
47
+ z.object({ kind: z.literal("query"), name: z.string().min(1).max(128) }).strict(),
48
+ z.object({ kind: z.literal("body"), field: z.string().min(1).max(128) }).strict(),
49
+ z
50
+ .object({
51
+ kind: z.literal("aws-sigv4"),
52
+ region: z.string().regex(/^[a-z0-9-]{1,32}$/),
53
+ service: z.string().regex(/^[a-z0-9-]{1,64}$/),
54
+ })
55
+ .strict(),
56
+ ]);
57
+ export const SecretBrokerConfigSchema = z
58
+ .object({
59
+ hosts: z.array(host).min(1).max(20),
60
+ pathPrefixes: z.array(pathPrefix).max(20).optional(),
61
+ placement,
62
+ })
63
+ .strict();
64
+ export function parseSecretBrokerConfig(input) {
65
+ const parsed = SecretBrokerConfigSchema.safeParse(input);
66
+ if (!parsed.success) {
67
+ throw new Error(`secret_broker_config_invalid: ${parsed.error.issues.map((i) => i.message).join("; ")}`);
68
+ }
69
+ return parsed.data;
70
+ }
71
+ /**
72
+ * A fingerprint of one broker config (sha256 hex of its parsed JSON): an unbroker
73
+ * link carries the hash of the config it was minted for, so it can't remove a
74
+ * config that has changed since.
75
+ */
76
+ export function brokerConfigHash(config) {
77
+ return createHash("sha256")
78
+ .update(JSON.stringify(parseSecretBrokerConfig(config)))
79
+ .digest("hex");
80
+ }
81
+ export const SECRET_BROKER_CHANGED = "secret_broker_changed: brokering changed since this link was created; request a new link";
82
+ const SigV4ValueSchema = z
83
+ .object({
84
+ accessKeyId: z.string().min(1).max(256),
85
+ secretAccessKey: z.string().min(MIN_BROKERED_VALUE_LENGTH).max(1024),
86
+ sessionToken: z.string().min(MIN_BROKERED_VALUE_LENGTH).max(8192).optional(),
87
+ })
88
+ .strict();
89
+ export function parseSigV4Value(value) {
90
+ let json;
91
+ try {
92
+ json = JSON.parse(value);
93
+ }
94
+ catch {
95
+ throw new Error("secret_broker_value_invalid: an AWS SigV4 secret's value must be JSON {accessKeyId, secretAccessKey, sessionToken?}");
96
+ }
97
+ const parsed = SigV4ValueSchema.safeParse(json);
98
+ if (!parsed.success) {
99
+ throw new Error("secret_broker_value_invalid: an AWS SigV4 secret's value must be JSON {accessKeyId, secretAccessKey, sessionToken?}");
100
+ }
101
+ return parsed.data;
102
+ }
103
+ /** Throws unless `value` can be brokered under `config` (and later scrubbed from responses). */
104
+ export function assertBrokerableValue(config, value) {
105
+ if (config.placement.kind === "aws-sigv4") {
106
+ parseSigV4Value(value);
107
+ return;
108
+ }
109
+ if (value.length < MIN_BROKERED_VALUE_LENGTH) {
110
+ throw new Error(`secret_broker_value_invalid: a brokered value must be at least ${MIN_BROKERED_VALUE_LENGTH} characters`);
111
+ }
112
+ }
@@ -6,19 +6,52 @@
6
6
  * always resolve a name within one owner's (or, at get-time, one agent's)
7
7
  * scope, never globally.
8
8
  */
9
- import type { PrismaClient, Secret } from "#prisma";
9
+ import { type PrismaClient, type Secret } from "#prisma";
10
10
  import type { SecretCipher } from "../providers/secrets/types.js";
11
- export type SecretMetadata = Pick<Secret, "id" | "name" | "keyId" | "ownerId" | "createdAt" | "updatedAt">;
11
+ import { type SecretBrokerConfig } from "./secret-broker-config.js";
12
+ export type BrokerChangeVia = "mcp" | "browser";
13
+ export type SecretMetadata = Pick<Secret, "id" | "name" | "keyId" | "ownerId" | "createdAt" | "updatedAt"> & {
14
+ broker: SecretBrokerConfig | null;
15
+ };
16
+ export interface SecretEntry {
17
+ value: string;
18
+ broker: SecretBrokerConfig | null;
19
+ }
12
20
  export interface SecretsAccessor {
21
+ /** Readable secrets only: a brokered secret throws `secret_brokered`. */
13
22
  get(name: string): Promise<string | undefined>;
23
+ /** Host-internal: value and broker config, brokered or not. Never exposed to tool code. */
24
+ resolve?(name: string): Promise<SecretEntry | undefined>;
14
25
  }
15
26
  /**
16
27
  * Encrypts and stores a secret value. Upserts on (ownerId, name) — calling
17
28
  * this again for a name the owner already has rotates its value in place
18
29
  * (same id) rather than failing on the unique constraint. The plaintext is
19
- * never logged or returned.
30
+ * never logged or returned. With `options.broker`, the config is validated
31
+ * against the value, saved, and audited in the same transaction.
32
+ */
33
+ export declare function createSecret(name: string, value: string, ownerId: string, cipher: SecretCipher, db: PrismaClient, options?: {
34
+ broker?: SecretBrokerConfig;
35
+ via?: BrokerChangeVia;
36
+ }): Promise<Secret>;
37
+ /**
38
+ * Sets, changes, or (broker: null) removes a secret's broker config, with one audit
39
+ * row per actual change. `expectedBrokerHash` (brokerConfigHash of the config the
40
+ * caller confirmed) makes the change conditional, checked inside the transaction:
41
+ * if the secret is no longer brokered with exactly that config, it throws
42
+ * secret_broker_changed and changes nothing.
20
43
  */
21
- export declare function createSecret(name: string, value: string, ownerId: string, cipher: SecretCipher, db: PrismaClient): Promise<Secret>;
44
+ export declare function setSecretBroker(db: PrismaClient, cipher: SecretCipher, input: {
45
+ ownerId: string;
46
+ name: string;
47
+ broker: SecretBrokerConfig | null;
48
+ actorId: string;
49
+ via: BrokerChangeVia;
50
+ expectedBrokerHash?: string;
51
+ }): Promise<{
52
+ before: SecretBrokerConfig | null;
53
+ after: SecretBrokerConfig | null;
54
+ }>;
22
55
  /** Names/metadata only — never a value or ciphertext. */
23
56
  export declare function listSecrets(ownerId: string, db: PrismaClient): Promise<SecretMetadata[]>;
24
57
  /** Attaches one of the owner's own secrets (by canonical name) to an agent,
@@ -43,6 +76,8 @@ export declare function deleteSecret(secretId: string, db: PrismaClient): Promis
43
76
  * binding written before that rule, left behind by make_owner, or on an
44
77
  * owner-less agent behaves exactly like an unattached name, so one owner's
45
78
  * secret never reaches another owner's agent (A2/S2-2, R2-1).
79
+ *
80
+ * A brokered secret's `get` throws `secret_brokered`; only the privileged host's `resolve` sees its value.
46
81
  */
47
82
  export declare function buildSecretsAccessor(agentId: string, cipher: SecretCipher, db: Pick<PrismaClient, "agentSecret"> & Partial<Pick<PrismaClient, "$queryRaw">>): SecretsAccessor;
48
83
  /**
@@ -1,31 +1,120 @@
1
+ /**
2
+ * Secrets: encrypted-at-rest values, attached to an agent by name, readable
3
+ * only inside the sandbox at point-of-use (never by any read/list tool).
4
+ * `Secret.name` is unique per owner (`@@unique([ownerId, name])`), so two
5
+ * owners can each have their own "API_KEY" without collision — attach/get
6
+ * always resolve a name within one owner's (or, at get-time, one agent's)
7
+ * scope, never globally.
8
+ */
9
+ import { Prisma } from "#prisma";
1
10
  import { boundedString } from "../sandbox/bounded-json.js";
11
+ import { assertBrokerableValue, brokerConfigHash, parseSecretBrokerConfig, SECRET_BROKER_CHANGED, } from "./secret-broker-config.js";
12
+ function brokerOf(secret) {
13
+ return secret.broker === null || secret.broker === undefined ? null : parseSecretBrokerConfig(secret.broker);
14
+ }
15
+ const BROKERED_GET_MESSAGE = 'secret_brokered: this secret is brokered; send it with fetch(url, { secrets: ["NAME"] }) instead of reading it';
2
16
  /**
3
17
  * Encrypts and stores a secret value. Upserts on (ownerId, name) — calling
4
18
  * this again for a name the owner already has rotates its value in place
5
19
  * (same id) rather than failing on the unique constraint. The plaintext is
6
- * never logged or returned.
20
+ * never logged or returned. With `options.broker`, the config is validated
21
+ * against the value, saved, and audited in the same transaction.
7
22
  */
8
- export async function createSecret(name, value, ownerId, cipher, db) {
23
+ export async function createSecret(name, value, ownerId, cipher, db, options = {}) {
9
24
  boundedString(name, 1024);
10
25
  boundedString(value, 65_536);
26
+ const broker = options.broker ? parseSecretBrokerConfig(options.broker) : undefined;
27
+ if (broker)
28
+ assertBrokerableValue(broker, value);
11
29
  const ciphertext = await cipher.encrypt(value);
12
30
  const keyId = cipher.keyId();
13
- return db.secret.upsert({
14
- where: { ownerId_name: { ownerId, name } },
15
- create: { name, ciphertext, keyId, ownerId },
16
- update: { ciphertext, keyId },
31
+ if (!broker) {
32
+ // A rotation that omits `broker` keeps the stored config, so the new value must still be brokerable.
33
+ const existing = await db.secret.findUnique({
34
+ where: { ownerId_name: { ownerId, name } },
35
+ select: { broker: true },
36
+ });
37
+ if (existing?.broker)
38
+ assertBrokerableValue(brokerOf(existing), value);
39
+ return db.secret.upsert({
40
+ where: { ownerId_name: { ownerId, name } },
41
+ create: { name, ciphertext, keyId, ownerId },
42
+ update: { ciphertext, keyId },
43
+ });
44
+ }
45
+ return db.$transaction(async (tx) => {
46
+ const existing = await tx.secret.findUnique({ where: { ownerId_name: { ownerId, name } } });
47
+ const before = existing ? brokerOf(existing) : null;
48
+ const secret = await tx.secret.upsert({
49
+ where: { ownerId_name: { ownerId, name } },
50
+ create: { name, ciphertext, keyId, ownerId, broker },
51
+ update: { ciphertext, keyId, broker },
52
+ });
53
+ if (JSON.stringify(before) !== JSON.stringify(broker)) {
54
+ await tx.secretBrokerChange.create({
55
+ data: {
56
+ secretId: secret.id,
57
+ ownerId,
58
+ secretName: name,
59
+ actorId: ownerId,
60
+ before: before ?? Prisma.JsonNull,
61
+ after: broker,
62
+ via: options.via ?? "mcp",
63
+ },
64
+ });
65
+ }
66
+ return secret;
67
+ });
68
+ }
69
+ /**
70
+ * Sets, changes, or (broker: null) removes a secret's broker config, with one audit
71
+ * row per actual change. `expectedBrokerHash` (brokerConfigHash of the config the
72
+ * caller confirmed) makes the change conditional, checked inside the transaction:
73
+ * if the secret is no longer brokered with exactly that config, it throws
74
+ * secret_broker_changed and changes nothing.
75
+ */
76
+ export async function setSecretBroker(db, cipher, input) {
77
+ const after = input.broker === null ? null : parseSecretBrokerConfig(input.broker);
78
+ return db.$transaction(async (tx) => {
79
+ const secret = await tx.secret.findUnique({
80
+ where: { ownerId_name: { ownerId: input.ownerId, name: input.name } },
81
+ });
82
+ if (!secret)
83
+ throw new Error(`secret_not_found: no secret named "${input.name}"`);
84
+ const before = brokerOf(secret);
85
+ if (input.expectedBrokerHash !== undefined && (!before || brokerConfigHash(before) !== input.expectedBrokerHash)) {
86
+ throw new Error(SECRET_BROKER_CHANGED);
87
+ }
88
+ if (JSON.stringify(before) === JSON.stringify(after))
89
+ return { before, after };
90
+ if (after)
91
+ assertBrokerableValue(after, await cipher.decrypt(secret.ciphertext));
92
+ await tx.secret.update({ where: { id: secret.id }, data: { broker: after ?? Prisma.DbNull } });
93
+ await tx.secretBrokerChange.create({
94
+ data: {
95
+ secretId: secret.id,
96
+ ownerId: input.ownerId,
97
+ secretName: secret.name,
98
+ actorId: input.actorId,
99
+ before: before ?? Prisma.JsonNull,
100
+ after: after ?? Prisma.JsonNull,
101
+ via: input.via,
102
+ },
103
+ });
104
+ return { before, after };
17
105
  });
18
106
  }
19
107
  /** Names/metadata only — never a value or ciphertext. */
20
108
  export async function listSecrets(ownerId, db) {
21
109
  const secrets = await db.secret.findMany({ where: { ownerId } });
22
- return secrets.map(({ id, name, keyId, ownerId: owner, createdAt, updatedAt }) => ({
23
- id,
24
- name,
25
- keyId,
26
- ownerId: owner,
27
- createdAt,
28
- updatedAt,
110
+ return secrets.map((s) => ({
111
+ id: s.id,
112
+ name: s.name,
113
+ keyId: s.keyId,
114
+ ownerId: s.ownerId,
115
+ createdAt: s.createdAt,
116
+ updatedAt: s.updatedAt,
117
+ broker: brokerOf(s),
29
118
  }));
30
119
  }
31
120
  async function findOwnedSecretByName(db, ownerId, name) {
@@ -66,37 +155,53 @@ export async function deleteSecret(secretId, db) {
66
155
  * binding written before that rule, left behind by make_owner, or on an
67
156
  * owner-less agent behaves exactly like an unattached name, so one owner's
68
157
  * secret never reaches another owner's agent (A2/S2-2, R2-1).
158
+ *
159
+ * A brokered secret's `get` throws `secret_brokered`; only the privileged host's `resolve` sees its value.
69
160
  */
70
161
  export function buildSecretsAccessor(agentId, cipher, db) {
162
+ async function resolve(name) {
163
+ boundedString(name, 1024);
164
+ if (db.$queryRaw) {
165
+ // NULL-safe: "=" is never true when either owner is NULL.
166
+ const rows = await db.$queryRaw `
167
+ SELECT CASE WHEN octet_length(s."ciphertext") <= 262144 THEN s."ciphertext" ELSE NULL END AS "ciphertext",
168
+ s."broker" AS "broker"
169
+ FROM "Secret" s
170
+ JOIN "AgentSecret" a ON a."secretId" = s."id"
171
+ JOIN "Agent" g ON g."id" = a."agentId"
172
+ WHERE a."agentId" = ${agentId} AND a."boundName" = ${name} AND s."ownerId" = g."ownerId" LIMIT 1`;
173
+ if (!rows.length)
174
+ return undefined;
175
+ if (rows[0].ciphertext === null)
176
+ throw new Error("secret_value_limit");
177
+ return {
178
+ value: await cipher.decrypt(rows[0].ciphertext),
179
+ broker: brokerOf({ broker: rows[0].broker }),
180
+ };
181
+ }
182
+ // Lightweight provider doubles use the same post-read bound; production Prisma filters in SQL.
183
+ const attachment = await db.agentSecret.findFirst({
184
+ where: { agentId, boundName: name },
185
+ include: { secret: true, agent: { select: { ownerId: true } } },
186
+ });
187
+ if (!attachment)
188
+ return undefined;
189
+ const owner = attachment.agent.ownerId;
190
+ if (owner === null || attachment.secret.ownerId !== owner)
191
+ return undefined;
192
+ boundedString(attachment.secret.ciphertext, 256 * 1024);
193
+ return {
194
+ value: await cipher.decrypt(attachment.secret.ciphertext),
195
+ broker: brokerOf({ broker: attachment.secret.broker ?? null }),
196
+ };
197
+ }
71
198
  return {
199
+ resolve,
72
200
  async get(name) {
73
- boundedString(name, 1024);
74
- if (db.$queryRaw) {
75
- // NULL-safe: "=" is never true when either owner is NULL.
76
- const rows = await db.$queryRaw `
77
- SELECT CASE WHEN octet_length(s."ciphertext") <= 262144 THEN s."ciphertext" ELSE NULL END AS "ciphertext"
78
- FROM "Secret" s
79
- JOIN "AgentSecret" a ON a."secretId" = s."id"
80
- JOIN "Agent" g ON g."id" = a."agentId"
81
- WHERE a."agentId" = ${agentId} AND a."boundName" = ${name} AND s."ownerId" = g."ownerId" LIMIT 1`;
82
- if (!rows.length)
83
- return undefined;
84
- if (rows[0].ciphertext === null)
85
- throw new Error("secret_value_limit");
86
- return cipher.decrypt(rows[0].ciphertext);
87
- }
88
- // Lightweight provider doubles use the same post-read bound; production Prisma filters in SQL.
89
- const attachment = await db.agentSecret.findFirst({
90
- where: { agentId, boundName: name },
91
- include: { secret: true, agent: { select: { ownerId: true } } },
92
- });
93
- if (!attachment)
94
- return undefined;
95
- const owner = attachment.agent.ownerId;
96
- if (owner === null || attachment.secret.ownerId !== owner)
97
- return undefined;
98
- boundedString(attachment.secret.ciphertext, 256 * 1024);
99
- return cipher.decrypt(attachment.secret.ciphertext);
201
+ const entry = await resolve(name);
202
+ if (entry?.broker)
203
+ throw new Error(BROKERED_GET_MESSAGE);
204
+ return entry?.value;
100
205
  },
101
206
  };
102
207
  }
@@ -115,5 +220,8 @@ export function scopeSecretsAccessor(accessor, allowedNames) {
115
220
  return undefined;
116
221
  return accessor.get(name);
117
222
  },
223
+ ...(accessor.resolve && {
224
+ resolve: async (name) => (allowed.has(name) ? accessor.resolve(name) : undefined),
225
+ }),
118
226
  };
119
227
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Turns an emitted workflow event into outbox rows: resolves the event's work
3
+ * item (given, else the run's attribution, else the PR's issue link), finds
4
+ * the channel links that match (project links by the item's project, agent
5
+ * links by the event's agent), filters each link's event kinds, de-duplicates
6
+ * by channel, and writes one WorkflowEvent plus one NotificationDelivery per
7
+ * channel in a transaction. A dedupeKey that already exists is a no-op, so
8
+ * redeliveries and repeated finalizers record a fact once. Writes nothing
9
+ * when no link matches.
10
+ */
11
+ import { type PrismaClient } from "#prisma";
12
+ import { type WorkflowEventSink } from "./workflow-events.js";
13
+ export type RecorderDb = Pick<PrismaClient, "notificationChannel" | "workflowEvent" | "notificationDelivery" | "runAttribution" | "issuePullRequest" | "$transaction">;
14
+ export declare function createWorkflowEventRecorder(db: RecorderDb, enabledProviders: readonly string[]): WorkflowEventSink;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Turns an emitted workflow event into outbox rows: resolves the event's work
3
+ * item (given, else the run's attribution, else the PR's issue link), finds
4
+ * the channel links that match (project links by the item's project, agent
5
+ * links by the event's agent), filters each link's event kinds, de-duplicates
6
+ * by channel, and writes one WorkflowEvent plus one NotificationDelivery per
7
+ * channel in a transaction. A dedupeKey that already exists is a no-op, so
8
+ * redeliveries and repeated finalizers record a fact once. Writes nothing
9
+ * when no link matches.
10
+ */
11
+ import { Prisma } from "#prisma";
12
+ import { projectOf } from "../providers/issue-tracker/types.js";
13
+ import { threadKeys } from "./workflow-events.js";
14
+ async function resolveWorkItem(db, input) {
15
+ if (input.workItem)
16
+ return input.workItem;
17
+ if (input.runId) {
18
+ const attribution = await db.runAttribution.findUnique({
19
+ where: { runId: input.runId },
20
+ select: { workItem: { select: { provider: true, key: true } } },
21
+ });
22
+ if (attribution)
23
+ return attribution.workItem;
24
+ }
25
+ if (input.pullRequest) {
26
+ const pr = await db.issuePullRequest.findFirst({
27
+ where: {
28
+ codeProvider: input.pullRequest.codeProvider,
29
+ repository: input.pullRequest.repository,
30
+ number: input.pullRequest.number,
31
+ },
32
+ orderBy: { createdAt: "asc" },
33
+ select: { issueProvider: true, issueKey: true },
34
+ });
35
+ if (pr)
36
+ return { provider: pr.issueProvider, key: pr.issueKey };
37
+ }
38
+ return null;
39
+ }
40
+ export function createWorkflowEventRecorder(db, enabledProviders) {
41
+ return async (input) => {
42
+ if (enabledProviders.length === 0)
43
+ return;
44
+ const existing = await db.workflowEvent.findUnique({
45
+ where: { dedupeKey: input.dedupeKey },
46
+ select: { id: true },
47
+ });
48
+ if (existing)
49
+ return;
50
+ const item = await resolveWorkItem(db, input);
51
+ const or = [];
52
+ if (item)
53
+ or.push({ issueProvider: item.provider, projectKey: projectOf(item.key) });
54
+ if (input.agentId)
55
+ or.push({ agentId: input.agentId });
56
+ if (or.length === 0)
57
+ return;
58
+ const links = await db.notificationChannel.findMany({
59
+ where: { provider: { in: [...enabledProviders] }, OR: or },
60
+ select: { provider: true, channelId: true, events: true, includeCost: true },
61
+ });
62
+ const kind = input.payload.kind;
63
+ const byChannel = new Map();
64
+ for (const link of links) {
65
+ if (link.events.length > 0 && !link.events.includes(kind))
66
+ continue;
67
+ const key = `${link.provider}\u0000${link.channelId}`;
68
+ const prev = byChannel.get(key);
69
+ byChannel.set(key, {
70
+ provider: link.provider,
71
+ channelId: link.channelId,
72
+ includeCost: (prev?.includeCost ?? false) || link.includeCost,
73
+ });
74
+ }
75
+ if (byChannel.size === 0)
76
+ return;
77
+ const threadKey = item
78
+ ? threadKeys.issue(item.provider, item.key)
79
+ : input.pullRequest
80
+ ? threadKeys.pr(input.pullRequest.codeProvider, input.pullRequest.repository, input.pullRequest.number)
81
+ : input.runId
82
+ ? threadKeys.run(input.runId)
83
+ : null;
84
+ if (!threadKey)
85
+ return;
86
+ try {
87
+ await db.$transaction(async (tx) => {
88
+ const event = await tx.workflowEvent.create({
89
+ data: {
90
+ kind,
91
+ dedupeKey: input.dedupeKey,
92
+ runId: input.runId ?? null,
93
+ agentId: input.agentId ?? null,
94
+ workItemProvider: item?.provider ?? null,
95
+ workItemKey: item?.key ?? null,
96
+ codeProvider: input.pullRequest?.codeProvider ?? null,
97
+ repository: input.pullRequest?.repository ?? null,
98
+ prNumber: input.pullRequest?.number ?? null,
99
+ payload: input.payload,
100
+ },
101
+ });
102
+ await tx.notificationDelivery.createMany({
103
+ data: [...byChannel.values()].map((c) => ({
104
+ eventId: event.id,
105
+ provider: c.provider,
106
+ channelId: c.channelId,
107
+ threadKey,
108
+ includeCost: c.includeCost,
109
+ })),
110
+ });
111
+ });
112
+ }
113
+ catch (err) {
114
+ // A concurrent recording of the same fact won the dedupeKey: nothing to do.
115
+ if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002")
116
+ return;
117
+ throw err;
118
+ }
119
+ };
120
+ }