@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
@@ -33,20 +33,50 @@ export function sandboxFetchPolicy(toolHosts, operatorHosts = FETCH_ALLOWED_HOST
33
33
  function args(argsJson) {
34
34
  return JSON.parse(argsJson);
35
35
  }
36
- export function installHostFunctions(context, runtime, options) {
36
+ /**
37
+ * The bridges that touch credentials, storage, the network, or the operator's
38
+ * logs. They always run on the trusted side: in-process today, and served by
39
+ * the native sandbox gateway for a run whose engine runs in a worker.
40
+ */
41
+ export const PRIVILEGED_BRIDGE_NAMES = [
42
+ "__bridge_console",
43
+ "__bridge_fetch",
44
+ "__bridge_datastoreGet",
45
+ "__bridge_datastoreSet",
46
+ "__bridge_datastoreDelete",
47
+ "__bridge_datastoreList",
48
+ "__bridge_sharedDatastoreGet",
49
+ "__bridge_sharedDatastoreSet",
50
+ "__bridge_sharedDatastoreDelete",
51
+ "__bridge_sharedDatastoreList",
52
+ "__bridge_secretsGet",
53
+ ];
54
+ /** The bridges that need no privilege, run wherever QuickJS runs. */
55
+ export const LOCAL_BRIDGE_NAMES = [
56
+ "__bridge_sleep",
57
+ "__bridge_randomUUID",
58
+ "__bridge_randomBytes",
59
+ "__bridge_parseHTML",
60
+ "__bridge_parseCSV",
61
+ "__bridge_parseXML",
62
+ ];
63
+ export function isPrivilegedBridgeName(name) {
64
+ return PRIVILEGED_BRIDGE_NAMES.includes(name);
65
+ }
66
+ /** The privileged bridges for one tool invocation, scoped by its options. */
67
+ export function createPrivilegedHost(options) {
37
68
  const { agentId, datastore, sharedDatastore, logTag, secrets, signal, allowedFetchHosts } = options;
38
- const register = (name, fn) => registerJsonAsyncFunction(context, runtime, name, fn, signal);
69
+ const fetchImpl = options.fetchImpl ?? safeFetch;
39
70
  const sandboxLog = (options.logger ?? defaultLogger).child({
40
71
  module: "sandbox-tool",
41
72
  agentId,
42
73
  tool: logTag.slice(0, 100),
43
74
  });
44
- const parserPool = options.parserPool ?? (sharedParserPool ??= createParserWorkerPool());
45
75
  // Values fetched via secrets.get() during THIS invocation only — a tool
46
76
  // that logs a secret it never fetched has nothing to redact, and one that
47
77
  // fetches but doesn't log it costs nothing extra. Fresh per invocation
48
- // (installHostFunctions runs once per sandbox context, one per tool call).
49
- const fetchedSecretValues = new Set();
78
+ // (one privileged host per sandbox context, one per tool call).
79
+ const fetchedSecretValues = new Set(options.redactSecretValues ?? []);
50
80
  function redactSecrets(text) {
51
81
  let out = text;
52
82
  for (const value of fetchedSecretValues) {
@@ -56,17 +86,106 @@ export function installHostFunctions(context, runtime, options) {
56
86
  }
57
87
  return out;
58
88
  }
59
- register("__bridge_console", async (argsJson) => {
60
- const [level, logArgs] = args(argsJson);
61
- const message = redactPii(redactSecrets(boundedJson(logArgs, LOG_BYTES)));
62
- if (level === "warn")
63
- sandboxLog.warn(message);
64
- else if (level === "error")
65
- sandboxLog.error(message);
66
- else
67
- sandboxLog.info(message);
68
- return null;
69
- });
89
+ return {
90
+ async __bridge_console(argsJson) {
91
+ const [level, logArgs] = args(argsJson);
92
+ const message = redactPii(redactSecrets(boundedJson(logArgs, LOG_BYTES)));
93
+ if (level === "warn")
94
+ sandboxLog.warn(message);
95
+ else if (level === "error")
96
+ sandboxLog.error(message);
97
+ else
98
+ sandboxLog.info(message);
99
+ return null;
100
+ },
101
+ async __bridge_fetch(argsJson) {
102
+ const [url, init = {}, secretNames] = args(argsJson);
103
+ const policy = { ...sandboxFetchPolicy(allowedFetchHosts ?? []), signal };
104
+ if (secretNames === undefined || (Array.isArray(secretNames) && secretNames.length === 0)) {
105
+ return fetchImpl(url, init, policy);
106
+ }
107
+ // Loaded only here: the brokering code needs packages the native worker image does not ship,
108
+ // and the worker never runs this bridge (the gateway does).
109
+ const { brokeredFetch } = await import("./brokered-fetch.js");
110
+ return brokeredFetch({
111
+ url,
112
+ init,
113
+ secretNames,
114
+ secrets,
115
+ fetchImpl,
116
+ policy,
117
+ redactFromConsole: (value) => fetchedSecretValues.add(value),
118
+ });
119
+ },
120
+ async __bridge_datastoreGet(argsJson) {
121
+ const [key] = args(argsJson);
122
+ boundedString(key, 1024);
123
+ const value = await datastore.get(agentId, key);
124
+ return value === undefined ? null : value;
125
+ },
126
+ async __bridge_datastoreSet(argsJson) {
127
+ const [key, value, opts] = args(argsJson);
128
+ boundedString(key, 1024);
129
+ await datastore.set(agentId, key, value, opts);
130
+ return null;
131
+ },
132
+ async __bridge_datastoreDelete(argsJson) {
133
+ const [key] = args(argsJson);
134
+ boundedString(key, 1024);
135
+ await datastore.delete(agentId, key);
136
+ return null;
137
+ },
138
+ async __bridge_datastoreList(argsJson) {
139
+ const [prefix] = args(argsJson);
140
+ if (prefix !== null)
141
+ boundedString(prefix, 1024);
142
+ return datastore.list(agentId, prefix ?? undefined);
143
+ },
144
+ async __bridge_sharedDatastoreGet(argsJson) {
145
+ const [boundName, key] = args(argsJson);
146
+ boundedString(boundName, 1024);
147
+ boundedString(key, 1024);
148
+ const value = await sharedDatastore.get(boundName, key);
149
+ return value === undefined ? null : value;
150
+ },
151
+ async __bridge_sharedDatastoreSet(argsJson) {
152
+ const [boundName, key, value, opts] = args(argsJson);
153
+ boundedString(boundName, 1024);
154
+ boundedString(key, 1024);
155
+ await sharedDatastore.set(boundName, key, value, opts);
156
+ return null;
157
+ },
158
+ async __bridge_sharedDatastoreDelete(argsJson) {
159
+ const [boundName, key] = args(argsJson);
160
+ boundedString(boundName, 1024);
161
+ boundedString(key, 1024);
162
+ await sharedDatastore.delete(boundName, key);
163
+ return null;
164
+ },
165
+ async __bridge_sharedDatastoreList(argsJson) {
166
+ const [boundName, prefix] = args(argsJson);
167
+ boundedString(boundName, 1024);
168
+ if (prefix !== null)
169
+ boundedString(prefix, 1024);
170
+ return sharedDatastore.list(boundName, prefix ?? undefined);
171
+ },
172
+ async __bridge_secretsGet(argsJson) {
173
+ const [name] = args(argsJson);
174
+ boundedString(name, 1024);
175
+ const value = secrets ? await secrets.get(name) : undefined;
176
+ if (value)
177
+ fetchedSecretValues.add(value);
178
+ return value ?? null;
179
+ },
180
+ };
181
+ }
182
+ /** Registers the whole sandbox API: the local bridges directly, the privileged ones through `privileged`. */
183
+ export function installSandboxApi(context, runtime, options) {
184
+ const { privileged, signal } = options;
185
+ const register = (name, fn) => registerJsonAsyncFunction(context, runtime, name, fn, signal);
186
+ const parserPool = options.parserPool ?? (sharedParserPool ??= createParserWorkerPool());
187
+ for (const name of PRIVILEGED_BRIDGE_NAMES)
188
+ register(name, (argsJson) => privileged[name](argsJson));
70
189
  register("__bridge_sleep", async (argsJson) => {
71
190
  const [ms] = args(argsJson);
72
191
  if (!Number.isFinite(ms) || ms < 0 || ms > WALL_TIME_LIMIT_MS)
@@ -83,70 +202,6 @@ export function installHostFunctions(context, runtime, options) {
83
202
  throw new Error("random_bytes_limit");
84
203
  return Array.from(nodeRandomBytes(length));
85
204
  });
86
- register("__bridge_fetch", async (argsJson) => {
87
- const [url, init] = args(argsJson);
88
- return safeFetch(url, init, { ...sandboxFetchPolicy(allowedFetchHosts ?? []), signal });
89
- });
90
- register("__bridge_datastoreGet", async (argsJson) => {
91
- const [key] = args(argsJson);
92
- boundedString(key, 1024);
93
- const value = await datastore.get(agentId, key);
94
- return value === undefined ? null : value;
95
- });
96
- register("__bridge_datastoreSet", async (argsJson) => {
97
- const [key, value, opts] = args(argsJson);
98
- boundedString(key, 1024);
99
- await datastore.set(agentId, key, value, opts);
100
- return null;
101
- });
102
- register("__bridge_datastoreDelete", async (argsJson) => {
103
- const [key] = args(argsJson);
104
- boundedString(key, 1024);
105
- await datastore.delete(agentId, key);
106
- return null;
107
- });
108
- register("__bridge_datastoreList", async (argsJson) => {
109
- const [prefix] = args(argsJson);
110
- if (prefix !== null)
111
- boundedString(prefix, 1024);
112
- return datastore.list(agentId, prefix ?? undefined);
113
- });
114
- register("__bridge_sharedDatastoreGet", async (argsJson) => {
115
- const [boundName, key] = args(argsJson);
116
- boundedString(boundName, 1024);
117
- boundedString(key, 1024);
118
- const value = await sharedDatastore.get(boundName, key);
119
- return value === undefined ? null : value;
120
- });
121
- register("__bridge_sharedDatastoreSet", async (argsJson) => {
122
- const [boundName, key, value, opts] = args(argsJson);
123
- boundedString(boundName, 1024);
124
- boundedString(key, 1024);
125
- await sharedDatastore.set(boundName, key, value, opts);
126
- return null;
127
- });
128
- register("__bridge_sharedDatastoreDelete", async (argsJson) => {
129
- const [boundName, key] = args(argsJson);
130
- boundedString(boundName, 1024);
131
- boundedString(key, 1024);
132
- await sharedDatastore.delete(boundName, key);
133
- return null;
134
- });
135
- register("__bridge_sharedDatastoreList", async (argsJson) => {
136
- const [boundName, prefix] = args(argsJson);
137
- boundedString(boundName, 1024);
138
- if (prefix !== null)
139
- boundedString(prefix, 1024);
140
- return sharedDatastore.list(boundName, prefix ?? undefined);
141
- });
142
- register("__bridge_secretsGet", async (argsJson) => {
143
- const [name] = args(argsJson);
144
- boundedString(name, 1024);
145
- const value = secrets ? await secrets.get(name) : undefined;
146
- if (value)
147
- fetchedSecretValues.add(value);
148
- return value ?? null;
149
- });
150
205
  // The actual node-html-parser/papaparse/fast-xml-parser calls run in a
151
206
  // disposable worker thread (see ./parser-worker/pool.ts) — this host
152
207
  // process runs fully attacker-controlled (but size-capped) input through
@@ -173,3 +228,12 @@ export function installHostFunctions(context, runtime, options) {
173
228
  return parserPool.run("xml", { xml, xmlOptions }, signal);
174
229
  });
175
230
  }
231
+ /** The in-process sandbox API for one tool invocation: privileged bridges served right here. */
232
+ export function installHostFunctions(context, runtime, options) {
233
+ const { parserPool, ...privilegedOptions } = options;
234
+ installSandboxApi(context, runtime, {
235
+ privileged: createPrivilegedHost(privilegedOptions),
236
+ signal: options.signal,
237
+ parserPool,
238
+ });
239
+ }
@@ -340,8 +340,12 @@ globalThis.fetch = async (url, init) => {
340
340
  if (body instanceof Blob) body = await body.text();
341
341
  else body = JSON.stringify(body);
342
342
  }
343
+ // Brokered secrets: named here, placed by the host, never visible to this code. Anything but a list
344
+ // goes through unchanged, so the host refuses it rather than fetching without the secret.
345
+ const secretNames =
346
+ !init || init.secrets === undefined ? [] : Array.isArray(init.secrets) ? init.secrets.map(String) : init.secrets;
343
347
  const raw = await __bridge_fetch(
344
- JSON.stringify([String(url), { method: (init && init.method) || "GET", headers: (init && init.headers) || {}, body }]),
348
+ JSON.stringify([String(url), { method: (init && init.method) || "GET", headers: (init && init.headers) || {}, body }, secretNames]),
345
349
  );
346
350
  const data = JSON.parse(raw);
347
351
  const headersLower = {};
@@ -380,6 +384,8 @@ globalThis.sharedDatastore = {
380
384
  };
381
385
 
382
386
  // ---- secrets (bridged; decrypt-on-demand, scoped per agent by the host) ----
387
+ // A brokered secret is never readable: secrets.get throws secret_brokered for
388
+ // it; name it in fetch(url, { secrets: [name] }) instead.
383
389
 
384
390
  globalThis.secrets = {
385
391
  get: async (name) => {
@@ -7,11 +7,14 @@
7
7
  * exhaustion, or non-serializable return all fail *that call* cleanly
8
8
  * instead of crashing the run or the host process.
9
9
  */
10
+ import { type PrivilegedHost } from "./host-functions.js";
11
+ import type { ParserWorkerPool } from "./parser-worker/pool.js";
10
12
  import { type SandboxLimits, type SandboxResult } from "./eval-core.js";
11
13
  import type { Datastore } from "../providers/datastore/types.js";
12
14
  import type { SecretsAccessor } from "../core/secrets.js";
13
15
  import type { SharedDatastoreAccessor } from "../core/datastores.js";
14
16
  import type { Logger } from "../core/logger.js";
17
+ import type { safeFetch } from "./safe-fetch.js";
15
18
  export type { SandboxErrorKind, SandboxLimits, SandboxResult } from "./eval-core.js";
16
19
  export interface SandboxInvocation {
17
20
  /** The tool body source. Runs as an async function body receiving `params`. */
@@ -30,5 +33,21 @@ export interface SandboxInvocation {
30
33
  logger?: Logger;
31
34
  /** Forwarded to installHostFunctions — which hosts this tool attachment may fetch. Omitted/empty = no outbound fetch at all; a literal "*" element lifts the restriction. */
32
35
  allowedFetchHosts?: string[];
36
+ /** Overrides the sandbox's outbound network call — tests only. */
37
+ fetchImpl?: typeof safeFetch;
33
38
  }
39
+ /** Tool code plus whichever privileged host serves its bridges (in-process, or a gateway). */
40
+ export interface ToolCodeInvocation {
41
+ /** The tool body source. Runs as an async function body receiving `params`. */
42
+ code: string;
43
+ params: unknown;
44
+ limits?: Partial<SandboxLimits>;
45
+ /** Built once per invocation, with that invocation's abort signal (fetch needs it). */
46
+ privileged: (signal: AbortSignal) => PrivilegedHost;
47
+ parserPool?: ParserWorkerPool;
48
+ }
49
+ /** Evaluates one tool body in a fresh QuickJS context against the given privileged host. */
50
+ export declare function runToolCode(invocation: ToolCodeInvocation): Promise<SandboxResult>;
51
+ /** The in-process privileged host for a sandbox invocation. */
52
+ export declare function privilegedHostFor(invocation: SandboxInvocation, signal: AbortSignal): PrivilegedHost;
34
53
  export declare function runInSandbox(invocation: SandboxInvocation): Promise<SandboxResult>;
@@ -7,12 +7,13 @@
7
7
  * exhaustion, or non-serializable return all fail *that call* cleanly
8
8
  * instead of crashing the run or the host process.
9
9
  */
10
- import { installHostFunctions } from "./host-functions.js";
10
+ import { createPrivilegedHost, installSandboxApi } from "./host-functions.js";
11
11
  import { SANDBOX_PRELUDE } from "./prelude.js";
12
12
  import { evalToJson, NON_SERIALIZABLE_MARKER } from "./eval-core.js";
13
13
  import { boundedJson, boundedString } from "./bounded-json.js";
14
14
  import { BRIDGE_INPUT_BYTES } from "./limits.js";
15
- export async function runInSandbox(invocation) {
15
+ /** Evaluates one tool body in a fresh QuickJS context against the given privileged host. */
16
+ export async function runToolCode(invocation) {
16
17
  let params;
17
18
  try {
18
19
  boundedString(invocation.code, BRIDGE_INPUT_BYTES);
@@ -39,15 +40,32 @@ export async function runInSandbox(invocation) {
39
40
  }
40
41
  })()`;
41
42
  return evalToJson(code, invocation.limits, (context, runtime, signal) => {
42
- installHostFunctions(context, runtime, {
43
- agentId: invocation.agentId,
44
- datastore: invocation.datastore,
45
- sharedDatastore: invocation.sharedDatastore,
46
- logTag: invocation.toolName,
47
- secrets: invocation.secrets,
48
- logger: invocation.logger,
49
- allowedFetchHosts: invocation.allowedFetchHosts,
43
+ installSandboxApi(context, runtime, {
44
+ privileged: invocation.privileged(signal),
50
45
  signal,
46
+ parserPool: invocation.parserPool,
51
47
  });
52
48
  });
53
49
  }
50
+ /** The in-process privileged host for a sandbox invocation. */
51
+ export function privilegedHostFor(invocation, signal) {
52
+ return createPrivilegedHost({
53
+ agentId: invocation.agentId,
54
+ datastore: invocation.datastore,
55
+ sharedDatastore: invocation.sharedDatastore,
56
+ logTag: invocation.toolName,
57
+ secrets: invocation.secrets,
58
+ logger: invocation.logger,
59
+ allowedFetchHosts: invocation.allowedFetchHosts,
60
+ fetchImpl: invocation.fetchImpl,
61
+ signal,
62
+ });
63
+ }
64
+ export async function runInSandbox(invocation) {
65
+ return runToolCode({
66
+ code: invocation.code,
67
+ params: invocation.params,
68
+ limits: invocation.limits,
69
+ privileged: (signal) => privilegedHostFor(invocation, signal),
70
+ });
71
+ }
@@ -12,6 +12,8 @@ export declare function requestPinned(destination: Destination, init: SafeFetchI
12
12
  export interface SafeFetchOptions extends FetchPolicyOptions {
13
13
  signal?: AbortSignal;
14
14
  connect?: typeof requestPinned;
15
+ /** False: a 3xx is returned to the caller as-is (brokered requests re-check every hop themselves). */
16
+ followRedirects?: boolean;
15
17
  }
16
18
  export declare function safeFetch(input: string, init?: SafeFetchInit, options?: SafeFetchOptions): Promise<{
17
19
  ok: boolean;
@@ -60,7 +60,9 @@ export async function safeFetch(input, init = {}, options = {}) {
60
60
  signal.throwIfAborted();
61
61
  response = await (options.connect ?? requestPinned)(destination, { method, headers, body }, signal);
62
62
  const status = response.statusCode ?? 0;
63
- if ([301, 302, 303, 307, 308].includes(status) && response.headers.location) {
63
+ if (options.followRedirects !== false &&
64
+ [301, 302, 303, 307, 308].includes(status) &&
65
+ response.headers.location) {
64
66
  response.destroy();
65
67
  if (hop >= MAX_REDIRECTS)
66
68
  throw new Error("fetch_redirect_limit");
@@ -0,0 +1,2 @@
1
+ import type { BrokeredSecret, BrokerRequest } from "./secret-broker.js";
2
+ export declare function signSigV4(request: BrokerRequest, secret: BrokeredSecret, now?: Date): Promise<BrokerRequest>;
@@ -0,0 +1,49 @@
1
+ /** AWS SigV4 for a brokered secret, using AWS's own signer. The tool never sees the keys. */
2
+ import { SignatureV4 } from "@smithy/signature-v4";
3
+ import { Sha256 } from "@aws-crypto/sha256-js";
4
+ import { parseSigV4Value } from "../core/secret-broker-config.js";
5
+ const SIGNER_HEADERS = ["authorization", "x-amz-date", "x-amz-security-token", "x-amz-content-sha256"];
6
+ /** Group query params by key so repeated keys survive (Object.fromEntries would drop them). */
7
+ function queryOf(url) {
8
+ const query = {};
9
+ for (const key of new Set(url.searchParams.keys())) {
10
+ const all = url.searchParams.getAll(key);
11
+ query[key] = all.length === 1 ? all[0] : all;
12
+ }
13
+ return query;
14
+ }
15
+ export async function signSigV4(request, secret, now = new Date()) {
16
+ if (secret.broker.placement.kind !== "aws-sigv4") {
17
+ throw new Error("signSigV4 needs an aws-sigv4 secret");
18
+ }
19
+ const headers = Object.fromEntries(Object.entries(request.headers).map(([k, v]) => [k.toLowerCase(), v]));
20
+ for (const h of SIGNER_HEADERS) {
21
+ if (h in headers) {
22
+ throw new Error(`secret_broker_conflict: header "${h}" is set by the SigV4 signer for "${secret.name}"`);
23
+ }
24
+ }
25
+ // safeFetch replaces accept-encoding after signing; a signed one would no longer match.
26
+ delete headers["accept-encoding"];
27
+ const { region, service } = secret.broker.placement;
28
+ const signer = new SignatureV4({
29
+ credentials: parseSigV4Value(secret.value),
30
+ region,
31
+ service,
32
+ sha256: Sha256,
33
+ applyChecksum: service === "s3",
34
+ uriEscapePath: service !== "s3",
35
+ });
36
+ const url = new URL(request.url);
37
+ const signed = await signer.sign({
38
+ method: request.method,
39
+ protocol: url.protocol,
40
+ hostname: url.hostname,
41
+ path: url.pathname,
42
+ query: queryOf(url),
43
+ headers: { ...headers, host: url.host },
44
+ body: request.body,
45
+ }, { signingDate: now });
46
+ const out = Object.fromEntries(Object.entries(signed.headers).map(([k, v]) => [k.toLowerCase(), String(v)]));
47
+ delete out.host; // safeFetch forbids setting Host; Node sets the same value from the URL.
48
+ return { ...request, headers: out };
49
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Brokered secrets at request time: check the destination, place each value where its
3
+ * owner said, and scrub values from the response. Pure — the privileged host
4
+ * (host-functions.ts) does the I/O. SigV4 signing is in secret-broker-sigv4.ts.
5
+ */
6
+ import { type SecretBrokerConfig } from "../core/secret-broker-config.js";
7
+ export interface BrokeredSecret {
8
+ name: string;
9
+ value: string;
10
+ broker: SecretBrokerConfig;
11
+ }
12
+ export interface BrokerRequest {
13
+ url: string;
14
+ method: string;
15
+ headers: Record<string, string>;
16
+ body?: string;
17
+ }
18
+ export declare function checkBrokerDestination(url: string, secrets: readonly BrokeredSecret[]): void;
19
+ export declare function applyBrokerPlacements(request: BrokerRequest, secrets: readonly BrokeredSecret[]): BrokerRequest;
20
+ export declare function brokerScrubValues(secrets: readonly BrokeredSecret[]): string[];
21
+ /**
22
+ * The strings console output must not show for one secret the tool may use: its value and, for a
23
+ * brokered SigV4 secret, the secret key and session token alone. A value that won't parse adds nothing.
24
+ */
25
+ export declare function consoleRedactionValues(entry: {
26
+ value: string;
27
+ broker: SecretBrokerConfig | null;
28
+ }): string[];
29
+ export declare function scrubBrokeredResponse<T extends {
30
+ headers: Record<string, string>;
31
+ bodyBase64: string;
32
+ url: string;
33
+ statusText?: string;
34
+ }>(response: T, values: readonly string[]): T;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Brokered secrets at request time: check the destination, place each value where its
3
+ * owner said, and scrub values from the response. Pure — the privileged host
4
+ * (host-functions.ts) does the I/O. SigV4 signing is in secret-broker-sigv4.ts.
5
+ */
6
+ import { parseSigV4Value } from "../core/secret-broker-config.js";
7
+ const REDACTED = "[REDACTED]";
8
+ function deny(reason) {
9
+ throw new Error(`secret_broker_destination_denied: ${reason}`);
10
+ }
11
+ function conflict(reason) {
12
+ throw new Error(`secret_broker_conflict: ${reason}`);
13
+ }
14
+ export function checkBrokerDestination(url, secrets) {
15
+ let parsed;
16
+ try {
17
+ parsed = new URL(url);
18
+ }
19
+ catch {
20
+ deny("invalid URL");
21
+ }
22
+ if (parsed.protocol !== "https:")
23
+ deny("brokered secrets are only sent over https");
24
+ if (parsed.port !== "")
25
+ deny("brokered secrets are only sent to the default https port");
26
+ const host = parsed.hostname.toLowerCase();
27
+ for (const s of secrets) {
28
+ if (!s.broker.hosts.includes(host))
29
+ deny(`"${s.name}" may not be sent to ${host}`);
30
+ const prefixes = s.broker.pathPrefixes ?? [];
31
+ if (prefixes.length && !prefixes.some((p) => parsed.pathname.startsWith(p))) {
32
+ deny(`"${s.name}" may not be sent to path ${parsed.pathname}`);
33
+ }
34
+ // An encoded slash or backslash may be decoded upstream into a separator, escaping the prefix.
35
+ if (prefixes.length && /%2f|%5c/i.test(parsed.pathname)) {
36
+ deny(`"${s.name}" may not be sent to a path with an encoded separator`);
37
+ }
38
+ }
39
+ }
40
+ function contentType(headers) {
41
+ return (headers["content-type"] ?? "").split(";")[0].trim().toLowerCase();
42
+ }
43
+ export function applyBrokerPlacements(request, secrets) {
44
+ const headers = Object.fromEntries(Object.entries(request.headers).map(([k, v]) => [k.toLowerCase(), v]));
45
+ const url = new URL(request.url);
46
+ let body = request.body;
47
+ const placed = new Set();
48
+ for (const s of secrets) {
49
+ const p = s.broker.placement;
50
+ if (p.kind === "header") {
51
+ const key = p.name.toLowerCase();
52
+ if (key in headers || placed.has(`h:${key}`))
53
+ conflict(`header "${p.name}" is set by the broker for "${s.name}"`);
54
+ // A replacer function, not a string: "$$", "$&", "$'" in a value are literal, not patterns.
55
+ headers[key] = p.format.replace("{value}", () => s.value);
56
+ placed.add(`h:${key}`);
57
+ }
58
+ else if (p.kind === "query") {
59
+ if (url.searchParams.has(p.name) || placed.has(`q:${p.name}`))
60
+ conflict(`query parameter "${p.name}" is set by the broker for "${s.name}"`);
61
+ url.searchParams.append(p.name, s.value);
62
+ placed.add(`q:${p.name}`);
63
+ }
64
+ else if (p.kind === "body") {
65
+ if (placed.has(`b:${p.field}`))
66
+ conflict(`body field "${p.field}" is set by the broker for "${s.name}"`);
67
+ body = placeBodyField(headers, body, p.field, s);
68
+ placed.add(`b:${p.field}`);
69
+ }
70
+ }
71
+ return { ...request, url: url.href, headers, body };
72
+ }
73
+ function placeBodyField(headers, body, field, s) {
74
+ const type = contentType(headers);
75
+ if (body === undefined)
76
+ throw new Error(`secret_broker_body_invalid: "${s.name}" is placed in the body, but the request has none`);
77
+ if (type === "application/json" || type.endsWith("+json")) {
78
+ let json;
79
+ try {
80
+ json = JSON.parse(body);
81
+ }
82
+ catch {
83
+ throw new Error("secret_broker_body_invalid: the body is not valid JSON");
84
+ }
85
+ if (json === null || typeof json !== "object" || Array.isArray(json)) {
86
+ throw new Error("secret_broker_body_invalid: a brokered JSON body must be an object");
87
+ }
88
+ if (Object.hasOwn(json, field))
89
+ conflict(`body field "${field}" is set by the broker for "${s.name}"`);
90
+ return JSON.stringify({ ...json, [field]: s.value });
91
+ }
92
+ if (type === "application/x-www-form-urlencoded") {
93
+ const form = new URLSearchParams(body);
94
+ if (form.has(field))
95
+ conflict(`body field "${field}" is set by the broker for "${s.name}"`);
96
+ form.append(field, s.value);
97
+ return form.toString();
98
+ }
99
+ throw new Error("secret_broker_body_invalid: body placement needs a JSON-object or form-encoded body");
100
+ }
101
+ export function brokerScrubValues(secrets) {
102
+ return secrets.flatMap((s) => {
103
+ if (s.broker.placement.kind !== "aws-sigv4")
104
+ return [s.value];
105
+ const creds = parseSigV4Value(s.value);
106
+ return [creds.secretAccessKey, ...(creds.sessionToken ? [creds.sessionToken] : [])];
107
+ });
108
+ }
109
+ /**
110
+ * The strings console output must not show for one secret the tool may use: its value and, for a
111
+ * brokered SigV4 secret, the secret key and session token alone. A value that won't parse adds nothing.
112
+ */
113
+ export function consoleRedactionValues(entry) {
114
+ if (!entry.broker)
115
+ return [entry.value];
116
+ try {
117
+ return [entry.value, ...brokerScrubValues([{ name: "", value: entry.value, broker: entry.broker }])].filter((v, i, all) => all.indexOf(v) === i);
118
+ }
119
+ catch {
120
+ return [entry.value];
121
+ }
122
+ }
123
+ function encodedForms(value) {
124
+ const buf = Buffer.from(value, "utf8");
125
+ // Query placement goes through URLSearchParams, which encodes ~ ! ' ( ) differently and space as "+",
126
+ // so both percent-encodings are scrubbed, each also in lowercase-hex form (a response may echo either).
127
+ const percent = [encodeURIComponent(value), new URLSearchParams({ k: value }).toString().slice("k=".length)];
128
+ const lowerHex = percent.map((f) => f.replace(/%[0-9A-F]{2}/g, (m) => m.toLowerCase()));
129
+ return [
130
+ ...new Set([
131
+ value,
132
+ buf.toString("base64"),
133
+ buf.toString("base64").replace(/=+$/, ""),
134
+ buf.toString("base64url"),
135
+ ...percent,
136
+ ...lowerHex,
137
+ ]),
138
+ ]
139
+ .filter((form) => form.length >= 6)
140
+ .sort((a, b) => b.length - a.length);
141
+ }
142
+ function scrubText(text, forms) {
143
+ let out = text;
144
+ for (const form of forms)
145
+ out = out.split(form).join(REDACTED);
146
+ return out;
147
+ }
148
+ export function scrubBrokeredResponse(response, values) {
149
+ // Longest first across every value: a value that prefixes another must not split the longer one.
150
+ const forms = [...new Set(values.flatMap(encodedForms))].sort((a, b) => b.length - a.length);
151
+ const body = Buffer.from(response.bodyBase64, "base64").toString("latin1");
152
+ // latin1 is byte-preserving, so scrubbing never corrupts non-UTF-8 bodies; every encoded form is ASCII,
153
+ // and a UTF-8 plaintext's bytes appear verbatim in latin1 as the latin1 decode of its UTF-8 bytes.
154
+ const latinForms = forms.map((f) => Buffer.from(f, "utf8").toString("latin1"));
155
+ return {
156
+ ...response,
157
+ url: scrubText(response.url, forms),
158
+ ...(response.statusText === undefined ? {} : { statusText: scrubText(response.statusText, forms) }),
159
+ headers: Object.fromEntries(Object.entries(response.headers).map(([k, v]) => [k, scrubText(v, forms)])),
160
+ bodyBase64: Buffer.from(scrubText(body, latinForms), "latin1").toString("base64"),
161
+ };
162
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * One user-tool call, end to end: parse the model's arguments, validate them
3
+ * against the tool's Zod schema (in its own sandbox), run the body, and format
4
+ * the result string fed back to the model. Never throws — every failure is a
5
+ * JSON error result. Worker-safe: it touches no database or provider, only the
6
+ * privileged host it is given, so the native sandbox worker runs it as is.
7
+ */
8
+ import type { PrivilegedHost } from "./host-functions.js";
9
+ import type { SandboxLimits } from "./eval-core.js";
10
+ export interface UserToolDefinition {
11
+ code: string;
12
+ paramsZod: string;
13
+ }
14
+ export declare function runUserToolCall(tool: UserToolDefinition, argsJson: string, privileged: (signal: AbortSignal) => PrivilegedHost, limits?: Partial<SandboxLimits>): Promise<string>;