@wardby/cli 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/.env.example +33 -0
  2. package/README.md +25 -14
  3. package/dist/claude-coding-worker/context.d.ts +22 -0
  4. package/dist/claude-coding-worker/context.js +92 -0
  5. package/dist/claude-coding-worker/driver.d.ts +7 -1
  6. package/dist/claude-coding-worker/driver.js +38 -5
  7. package/dist/claude-coding-worker/sdk.js +13 -4
  8. package/dist/cli-help.d.ts +1 -1
  9. package/dist/cli-help.js +5 -2
  10. package/dist/cli-native-mode.d.ts +9 -0
  11. package/dist/cli-native-mode.js +20 -0
  12. package/dist/cli.d.ts +3 -1
  13. package/dist/cli.js +141 -5
  14. package/dist/coding/claude-context.d.ts +45 -0
  15. package/dist/coding/claude-context.js +434 -0
  16. package/dist/coding/profile.d.ts +12 -0
  17. package/dist/coding/profile.js +6 -0
  18. package/dist/coding/protocol.d.ts +104 -0
  19. package/dist/coding/protocol.js +87 -3
  20. package/dist/coding-worker/artifact.d.ts +8 -0
  21. package/dist/coding-worker/artifact.js +2 -2
  22. package/dist/coding-worker/driver.js +2 -0
  23. package/dist/coding-worker/sdk.d.ts +6 -0
  24. package/dist/coding-worker/sdk.js +1 -0
  25. package/dist/coding-worker/skills.d.ts +23 -0
  26. package/dist/coding-worker/skills.js +117 -0
  27. package/dist/coding-worker/types.d.ts +2 -0
  28. package/dist/config/github-allowed-accounts.d.ts +15 -0
  29. package/dist/config/github-allowed-accounts.js +36 -0
  30. package/dist/config/low-power.d.ts +12 -0
  31. package/dist/config/low-power.js +18 -0
  32. package/dist/config/mobile.d.ts +12 -0
  33. package/dist/config/mobile.js +18 -0
  34. package/dist/config/providers.d.ts +53 -0
  35. package/dist/config/providers.js +92 -6
  36. package/dist/core/db.d.ts +16 -6
  37. package/dist/core/db.js +12 -10
  38. package/dist/core/dispatch.d.ts +11 -0
  39. package/dist/core/dispatch.js +36 -1
  40. package/dist/core/engine-native.js +9 -5
  41. package/dist/core/host-events.d.ts +49 -3
  42. package/dist/core/host-events.js +289 -15
  43. package/dist/core/issue-bridge.js +42 -2
  44. package/dist/core/issue-events.js +12 -1
  45. package/dist/core/issue-status.js +14 -3
  46. package/dist/core/merge-order-check.d.ts +129 -0
  47. package/dist/core/merge-order-check.js +336 -0
  48. package/dist/core/notification-dispatcher.d.ts +70 -0
  49. package/dist/core/notification-dispatcher.js +368 -0
  50. package/dist/core/notification-templates.d.ts +25 -0
  51. package/dist/core/notification-templates.js +105 -0
  52. package/dist/core/notifications.d.ts +10 -0
  53. package/dist/core/notifications.js +38 -0
  54. package/dist/core/pg-auth.d.ts +22 -0
  55. package/dist/core/pg-auth.js +152 -0
  56. package/dist/core/power-composition.d.ts +16 -0
  57. package/dist/core/power-composition.js +37 -0
  58. package/dist/core/power-idle.d.ts +14 -0
  59. package/dist/core/power-idle.js +30 -0
  60. package/dist/core/power.d.ts +71 -0
  61. package/dist/core/power.js +202 -0
  62. package/dist/core/reconciler.d.ts +4 -2
  63. package/dist/core/reconciler.js +29 -5
  64. package/dist/core/related-pull-requests.d.ts +48 -4
  65. package/dist/core/related-pull-requests.js +113 -11
  66. package/dist/core/repo-access.d.ts +15 -0
  67. package/dist/core/repo-access.js +66 -0
  68. package/dist/core/review-fix.d.ts +37 -0
  69. package/dist/core/review-fix.js +207 -6
  70. package/dist/core/review-host-tools.d.ts +20 -0
  71. package/dist/core/review-host-tools.js +58 -2
  72. package/dist/core/runner.d.ts +336 -3
  73. package/dist/core/runner.js +894 -317
  74. package/dist/core/secret-broker-config.d.ts +103 -0
  75. package/dist/core/secret-broker-config.js +112 -0
  76. package/dist/core/secrets.d.ts +39 -4
  77. package/dist/core/secrets.js +148 -40
  78. package/dist/core/workflow-event-recorder.d.ts +14 -0
  79. package/dist/core/workflow-event-recorder.js +120 -0
  80. package/dist/core/workflow-events.d.ts +82 -0
  81. package/dist/core/workflow-events.js +44 -0
  82. package/dist/core/workflow-run-events.d.ts +16 -0
  83. package/dist/core/workflow-run-events.js +40 -0
  84. package/dist/generated/prisma/browser.d.ts +58 -0
  85. package/dist/generated/prisma/client.d.ts +58 -0
  86. package/dist/generated/prisma/commonInputTypes.d.ts +150 -0
  87. package/dist/generated/prisma/enums.d.ts +27 -0
  88. package/dist/generated/prisma/enums.js +23 -0
  89. package/dist/generated/prisma/internal/class.d.ts +88 -0
  90. package/dist/generated/prisma/internal/class.js +4 -4
  91. package/dist/generated/prisma/internal/prismaNamespace.d.ts +772 -1
  92. package/dist/generated/prisma/internal/prismaNamespace.js +135 -4
  93. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +139 -0
  94. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +135 -4
  95. package/dist/generated/prisma/models/Agent.d.ts +360 -1
  96. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +73 -1
  97. package/dist/generated/prisma/models/CodingRun.d.ts +205 -1
  98. package/dist/generated/prisma/models/DeferredReview.d.ts +78 -1
  99. package/dist/generated/prisma/models/NativeGatewayCall.d.ts +1675 -0
  100. package/dist/generated/prisma/models/NativeGatewayCall.js +1 -0
  101. package/dist/generated/prisma/models/NativeGatewaySession.d.ts +1517 -0
  102. package/dist/generated/prisma/models/NativeGatewaySession.js +1 -0
  103. package/dist/generated/prisma/models/NativeWarmWorker.d.ts +1296 -0
  104. package/dist/generated/prisma/models/NativeWarmWorker.js +1 -0
  105. package/dist/generated/prisma/models/NotificationChannel.d.ts +1543 -0
  106. package/dist/generated/prisma/models/NotificationChannel.js +1 -0
  107. package/dist/generated/prisma/models/NotificationDelivery.d.ts +1502 -0
  108. package/dist/generated/prisma/models/NotificationDelivery.js +1 -0
  109. package/dist/generated/prisma/models/NotificationThread.d.ts +1090 -0
  110. package/dist/generated/prisma/models/NotificationThread.js +1 -0
  111. package/dist/generated/prisma/models/Run.d.ts +826 -1
  112. package/dist/generated/prisma/models/RunHostCheck.d.ts +36 -1
  113. package/dist/generated/prisma/models/Secret.d.ts +37 -1
  114. package/dist/generated/prisma/models/SecretBrokerChange.d.ts +1101 -0
  115. package/dist/generated/prisma/models/SecretBrokerChange.js +1 -0
  116. package/dist/generated/prisma/models/WorkflowEvent.d.ts +1422 -0
  117. package/dist/generated/prisma/models/WorkflowEvent.js +1 -0
  118. package/dist/generated/prisma/models.d.ts +8 -0
  119. package/dist/help/build.js +4 -2
  120. package/dist/help/catalog.d.ts +1 -1
  121. package/dist/help/catalog.js +31 -6
  122. package/dist/help-index.json +1363 -176
  123. package/dist/import/create.js +17 -0
  124. package/dist/import/neutral-schema.d.ts +6 -2
  125. package/dist/import/neutral-schema.js +2 -0
  126. package/dist/mcp/auth/access.d.ts +1 -0
  127. package/dist/mcp/auth/repo-authorization.js +14 -0
  128. package/dist/mcp/auth/resource-server.d.ts +2 -1
  129. package/dist/mcp/auth/resource-server.js +7 -2
  130. package/dist/mcp/auth/self-hosted/browser.d.ts +1 -1
  131. package/dist/mcp/auth/self-hosted/browser.js +19 -6
  132. package/dist/mcp/context.d.ts +6 -0
  133. package/dist/mcp/host-events/github-ingress.d.ts +23 -1
  134. package/dist/mcp/host-events/github-ingress.js +68 -0
  135. package/dist/mcp/index.d.ts +13 -0
  136. package/dist/mcp/index.js +59 -10
  137. package/dist/mcp/tools/agents.js +82 -11
  138. package/dist/mcp/tools/notification-channels.d.ts +2 -0
  139. package/dist/mcp/tools/notification-channels.js +293 -0
  140. package/dist/mcp/tools/power.d.ts +2 -0
  141. package/dist/mcp/tools/power.js +77 -0
  142. package/dist/mcp/tools/runs.d.ts +0 -6
  143. package/dist/mcp/tools/runs.js +7 -2
  144. package/dist/mcp/tools/secret-broker-compat.d.ts +12 -0
  145. package/dist/mcp/tools/secret-broker-compat.js +23 -0
  146. package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -2
  147. package/dist/mcp/tools/secret-elicitation-form.js +200 -10
  148. package/dist/mcp/tools/secret-elicitation-server.js +8 -1
  149. package/dist/mcp/tools/secret-elicitation.d.ts +38 -3
  150. package/dist/mcp/tools/secret-elicitation.js +110 -12
  151. package/dist/mcp/tools/secrets.js +184 -7
  152. package/dist/mcp/tools/tools.d.ts +1 -1
  153. package/dist/mcp/tools/tools.js +22 -2
  154. package/dist/native-worker/composition.d.ts +23 -0
  155. package/dist/native-worker/composition.js +77 -0
  156. package/dist/native-worker/docker-isolation.d.ts +65 -0
  157. package/dist/native-worker/docker-isolation.js +183 -0
  158. package/dist/native-worker/docker-launcher.d.ts +74 -0
  159. package/dist/native-worker/docker-launcher.js +237 -0
  160. package/dist/native-worker/gateway-server.d.ts +39 -0
  161. package/dist/native-worker/gateway-server.js +315 -0
  162. package/dist/native-worker/gateway.d.ts +44 -0
  163. package/dist/native-worker/gateway.js +197 -0
  164. package/dist/native-worker/http-server.d.ts +22 -0
  165. package/dist/native-worker/http-server.js +109 -0
  166. package/dist/native-worker/http-transport.d.ts +17 -0
  167. package/dist/native-worker/http-transport.js +88 -0
  168. package/dist/native-worker/kubernetes-isolation.d.ts +90 -0
  169. package/dist/native-worker/kubernetes-isolation.js +218 -0
  170. package/dist/native-worker/kubernetes-launcher.d.ts +102 -0
  171. package/dist/native-worker/kubernetes-launcher.js +335 -0
  172. package/dist/native-worker/launch.d.ts +45 -0
  173. package/dist/native-worker/launch.js +44 -0
  174. package/dist/native-worker/ledger.d.ts +109 -0
  175. package/dist/native-worker/ledger.js +192 -0
  176. package/dist/native-worker/loopback.d.ts +12 -0
  177. package/dist/native-worker/loopback.js +49 -0
  178. package/dist/native-worker/main.d.ts +7 -0
  179. package/dist/native-worker/main.js +74 -0
  180. package/dist/native-worker/protocol.d.ts +744 -0
  181. package/dist/native-worker/protocol.js +200 -0
  182. package/dist/native-worker/sandbox-executor.d.ts +68 -0
  183. package/dist/native-worker/sandbox-executor.js +210 -0
  184. package/dist/native-worker/stdio.d.ts +29 -0
  185. package/dist/native-worker/stdio.js +188 -0
  186. package/dist/native-worker/warm-delivery.d.ts +16 -0
  187. package/dist/native-worker/warm-delivery.js +33 -0
  188. package/dist/native-worker/warm-pool-ledger.d.ts +39 -0
  189. package/dist/native-worker/warm-pool-ledger.js +73 -0
  190. package/dist/native-worker/warm-pool.d.ts +107 -0
  191. package/dist/native-worker/warm-pool.js +283 -0
  192. package/dist/native-worker/worker.d.ts +17 -0
  193. package/dist/native-worker/worker.js +115 -0
  194. package/dist/providers/auth/authorization-server.d.ts +3 -0
  195. package/dist/providers/auth/index.js +2 -0
  196. package/dist/providers/auth/self-hosted.d.ts +13 -1
  197. package/dist/providers/auth/self-hosted.js +54 -4
  198. package/dist/providers/chat/fake.d.ts +46 -0
  199. package/dist/providers/chat/fake.js +39 -0
  200. package/dist/providers/chat/index.d.ts +4 -0
  201. package/dist/providers/chat/index.js +8 -0
  202. package/dist/providers/chat/slack.d.ts +37 -0
  203. package/dist/providers/chat/slack.js +105 -0
  204. package/dist/providers/chat/types.d.ts +42 -0
  205. package/dist/providers/chat/types.js +19 -0
  206. package/dist/providers/engine/types.d.ts +19 -0
  207. package/dist/providers/engine/types.js +22 -0
  208. package/dist/providers/executor/composition.d.ts +24 -1
  209. package/dist/providers/executor/composition.js +39 -6
  210. package/dist/providers/executor/container.d.ts +24 -1
  211. package/dist/providers/executor/container.js +101 -11
  212. package/dist/providers/executor/dbos.d.ts +42 -0
  213. package/dist/providers/executor/dbos.js +118 -12
  214. package/dist/providers/executor/deferred.d.ts +56 -0
  215. package/dist/providers/executor/deferred.js +119 -0
  216. package/dist/providers/executor/routing.d.ts +37 -10
  217. package/dist/providers/executor/routing.js +73 -32
  218. package/dist/providers/executor/types.d.ts +12 -0
  219. package/dist/providers/jobs/docker.js +3 -3
  220. package/dist/providers/jobs/fake-kubernetes-api.d.ts +21 -2
  221. package/dist/providers/jobs/fake-kubernetes-api.js +42 -1
  222. package/dist/providers/jobs/kubernetes-api.d.ts +17 -1
  223. package/dist/providers/jobs/kubernetes-api.js +7 -0
  224. package/dist/providers/jobs/kubernetes-client.d.ts +10 -1
  225. package/dist/providers/jobs/kubernetes-client.js +52 -3
  226. package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +6 -1
  227. package/dist/providers/jobs/kubernetes-dry-run-fixture.js +6 -3
  228. package/dist/providers/jobs/kubernetes-isolation.d.ts +3 -1
  229. package/dist/providers/jobs/kubernetes-isolation.js +6 -0
  230. package/dist/providers/jobs/kubernetes-platform.d.ts +4 -1
  231. package/dist/providers/jobs/kubernetes-platform.js +38 -1
  232. package/dist/providers/jobs/kubernetes-preflight.d.ts +2 -1
  233. package/dist/providers/jobs/kubernetes-preflight.js +4 -2
  234. package/dist/providers/jobs/kubernetes.d.ts +36 -1
  235. package/dist/providers/jobs/kubernetes.js +74 -4
  236. package/dist/providers/jobs/metadata-probes.d.ts +11 -0
  237. package/dist/providers/jobs/metadata-probes.js +13 -0
  238. package/dist/providers/jobs/types.d.ts +9 -0
  239. package/dist/providers/review-host/github.d.ts +11 -1
  240. package/dist/providers/review-host/github.js +50 -0
  241. package/dist/providers/review-host/types.d.ts +23 -0
  242. package/dist/providers/vcs/github.d.ts +32 -6
  243. package/dist/providers/vcs/github.js +74 -16
  244. package/dist/quickstart/coding-seed.d.ts +10 -3
  245. package/dist/quickstart/coding-seed.js +74 -3
  246. package/dist/quickstart/images.d.ts +2 -0
  247. package/dist/quickstart/images.js +7 -1
  248. package/dist/quickstart/reviewer-prompt.d.ts +1 -1
  249. package/dist/quickstart/reviewer-prompt.js +1 -1
  250. package/dist/quickstart-images.json +1 -1
  251. package/dist/sandbox/brokered-fetch.d.ts +23 -0
  252. package/dist/sandbox/brokered-fetch.js +37 -0
  253. package/dist/sandbox/host-functions.d.ts +34 -0
  254. package/dist/sandbox/host-functions.js +144 -80
  255. package/dist/sandbox/prelude.js +7 -1
  256. package/dist/sandbox/run-in-sandbox.d.ts +19 -0
  257. package/dist/sandbox/run-in-sandbox.js +28 -10
  258. package/dist/sandbox/safe-fetch.d.ts +2 -0
  259. package/dist/sandbox/safe-fetch.js +3 -1
  260. package/dist/sandbox/secret-broker-sigv4.d.ts +2 -0
  261. package/dist/sandbox/secret-broker-sigv4.js +49 -0
  262. package/dist/sandbox/secret-broker.d.ts +34 -0
  263. package/dist/sandbox/secret-broker.js +162 -0
  264. package/dist/sandbox/user-tool.d.ts +14 -0
  265. package/dist/sandbox/user-tool.js +32 -0
  266. package/dist/serve.js +18 -0
  267. package/dist/viewer/api-schema.d.ts +210 -14
  268. package/dist/viewer/api-schema.js +22 -0
  269. package/dist/viewer/build-schemas.js +4 -1
  270. package/dist/viewer/event-bus.d.ts +6 -0
  271. package/dist/viewer/event-bus.js +16 -7
  272. package/dist/viewer/graph.d.ts +5 -2
  273. package/dist/viewer/graph.js +7 -2
  274. package/dist/viewer/http.d.ts +0 -1
  275. package/dist/viewer/http.js +2 -2
  276. package/dist/viewer/infra.js +25 -2
  277. package/dist/viewer/limits.d.ts +4 -0
  278. package/dist/viewer/limits.js +4 -0
  279. package/dist/viewer/openapi.d.ts +15 -0
  280. package/dist/viewer/openapi.js +172 -0
  281. package/docs/README.md +7 -0
  282. package/docs/agent-recipes.md +57 -8
  283. package/docs/architecture-runtime.md +16 -1
  284. package/docs/code-review-agents.md +139 -5
  285. package/docs/coding-agent-setup.md +113 -5
  286. package/docs/coding-packages.md +4 -2
  287. package/docs/coding-services.md +3 -1
  288. package/docs/coding-worker-byo-images.md +198 -6
  289. package/docs/coding-worker-isolation.md +68 -14
  290. package/docs/getting-started-gke.md +225 -9
  291. package/docs/getting-started-identity-provider.md +21 -10
  292. package/docs/getting-started.md +5 -4
  293. package/docs/jira-agents.md +5 -1
  294. package/docs/native-sandbox.md +635 -0
  295. package/docs/release-verification.md +9 -0
  296. package/docs/security-deployment.md +113 -12
  297. package/docs/slack-notifications.md +252 -0
  298. package/docs/tool-secrets.md +252 -0
  299. package/docs/viewer-api.md +46 -5
  300. package/docs/viewer-api.openapi.yml +1404 -0
  301. package/help/admin-viewer.md +6 -2
  302. package/help/agent-recipes.md +27 -0
  303. package/help/architecture-agent.md +1 -1
  304. package/help/brokered-secrets.md +79 -0
  305. package/help/build-claude-code-image.md +131 -0
  306. package/help/build-worker-image.md +3 -6
  307. package/help/code-review-agents.md +68 -6
  308. package/help/deploy-gke.md +8 -1
  309. package/help/deployment-targets.md +25 -14
  310. package/help/errors/coding-provider-not-configured.md +2 -2
  311. package/help/errors/database-auth-invalid.md +28 -0
  312. package/help/errors/github-account-not-allowed.md +25 -0
  313. package/help/errors/low-power-disabled.md +29 -0
  314. package/help/errors/low-power-scale-forbidden.md +35 -0
  315. package/help/errors/low-power-wake-timeout.md +30 -0
  316. package/help/errors/mobile-disabled.md +31 -0
  317. package/help/errors/native-sandbox-capacity.md +22 -0
  318. package/help/errors/native-sandbox-deadline-exceeded.md +19 -0
  319. package/help/errors/native-sandbox-docker-failed.md +20 -0
  320. package/help/errors/native-sandbox-gateway-unavailable.md +21 -0
  321. package/help/errors/native-sandbox-image-not-pinned.md +19 -0
  322. package/help/errors/native-sandbox-isolation-mismatch.md +20 -0
  323. package/help/errors/native-sandbox-network-unenforced.md +22 -0
  324. package/help/errors/native-sandbox-requires-catalog.md +31 -0
  325. package/help/errors/native-sandbox-unavailable.md +19 -0
  326. package/help/errors/native-sandbox-warm-delivery-failed.md +21 -0
  327. package/help/errors/native-sandbox-worker-exited.md +20 -0
  328. package/help/errors/native-sandbox-worker-lost.md +20 -0
  329. package/help/errors/native-sandbox-worker-unready.md +22 -0
  330. package/help/errors/secret-broker-body-invalid.md +26 -0
  331. package/help/errors/secret-broker-changed.md +20 -0
  332. package/help/errors/secret-broker-config-invalid.md +30 -0
  333. package/help/errors/secret-broker-conflict.md +27 -0
  334. package/help/errors/secret-broker-destination-denied.md +28 -0
  335. package/help/errors/secret-broker-value-invalid.md +28 -0
  336. package/help/errors/secret-brokered.md +24 -0
  337. package/help/errors/secret-not-brokered.md +25 -0
  338. package/help/errors/slack-auth-failed.md +48 -0
  339. package/help/errors/slack-channel-unreachable.md +39 -0
  340. package/help/errors/slack-not-configured.md +33 -0
  341. package/help/getting-started.md +4 -5
  342. package/help/github.md +18 -1
  343. package/help/identity-and-access.md +5 -1
  344. package/help/local-repositories.md +2 -3
  345. package/help/low-power-mode.md +125 -0
  346. package/help/merge-order-check.md +117 -0
  347. package/help/mobile-app.md +54 -0
  348. package/help/native-sandbox.md +136 -0
  349. package/help/observability.md +8 -0
  350. package/help/related-pull-requests.md +70 -9
  351. package/help/repo-instructions-and-skills.md +91 -0
  352. package/help/review-fix-rounds.md +10 -0
  353. package/help/slack-notifications.md +62 -0
  354. package/help/troubleshooting/coding-workers.md +21 -0
  355. package/package.json +11 -4
  356. package/prisma/migrations/20261009000003_native_execution_mode/migration.sql +12 -0
  357. package/prisma/migrations/20261009000004_native_gateway_ledger/migration.sql +65 -0
  358. package/prisma/migrations/20261009000005_native_gateway_call_child_agent/migration.sql +5 -0
  359. package/prisma/migrations/20261009000006_run_deferred_start_stop/migration.sql +14 -0
  360. package/prisma/migrations/20261009000007_native_gateway_network_ready/migration.sql +5 -0
  361. package/prisma/migrations/20261010000001_workflow_notifications/migration.sql +99 -0
  362. package/prisma/migrations/20261011000001_native_gateway_session_status_index/migration.sql +2 -0
  363. package/prisma/migrations/20261012000001_native_warm_worker/migration.sql +32 -0
  364. package/prisma/migrations/20261013000001_coding_run_merge_order/migration.sql +2 -0
  365. package/prisma/migrations/20261013000001_secret_broker/migration.sql +17 -0
  366. package/prisma/migrations/20261013000002_deferred_review_request/migration.sql +4 -0
  367. package/prisma/migrations/20261014000000_repo_skills/migration.sql +15 -0
  368. package/prisma/migrations/20261015000001_coding_run_merge_order_index/migration.sql +2 -0
  369. package/prisma/migrations/20261015000002_no_change_rereview/migration.sql +2 -0
  370. package/prisma/schema.prisma +362 -91
@@ -4,26 +4,109 @@
4
4
  * value/ciphertext at the source); this file adds ownership on top.
5
5
  */
6
6
  import { inputRequired, inputResponse } from "@modelcontextprotocol/server";
7
- import { createSecret, listSecrets, attachSecret, detachSecret, deleteSecret } from "../../core/secrets.js";
7
+ import { createSecret, listSecrets, attachSecret, detachSecret, deleteSecret, setSecretBroker, } from "../../core/secrets.js";
8
+ import { brokerConfigHash, parseSecretBrokerConfig } from "../../core/secret-broker-config.js";
8
9
  import { McpError } from "../errors.js";
9
10
  import { requireOwnedSecret } from "../auth/ownership.js";
10
11
  import { requireAgentAccess, requireBindingOwner } from "../auth/access.js";
11
- import { getSecretElicitationOutcome } from "./secret-elicitation.js";
12
+ import { clearUnbrokerOutcome, getSecretElicitationOutcome, } from "./secret-elicitation.js";
13
+ import { allowedSecretNames, brokerCompatibilityWarnings } from "./secret-broker-compat.js";
12
14
  import { textResult } from "./text-result.js";
15
+ const BROKER_SCHEMA_DOC = '{ hosts: exact hostnames, pathPrefixes?: ["/..."], placement: { kind: "header", name, format containing {value} } | { kind: "query", name } | { kind: "body", field } | { kind: "aws-sigv4", region, service } }';
16
+ /** Validates a caller-supplied broker config; an invalid one is the caller's 400. */
17
+ function parseBrokerArg(input) {
18
+ try {
19
+ return parseSecretBrokerConfig(input);
20
+ }
21
+ catch (err) {
22
+ throw new McpError(400, err instanceof Error ? err.message : String(err));
23
+ }
24
+ }
25
+ /** Maps core/secrets.ts's coded errors to MCP statuses; anything else propagates unchanged. */
26
+ function mapSecretError(err) {
27
+ const message = err instanceof Error ? err.message : String(err);
28
+ if (message.startsWith("secret_not_found"))
29
+ throw new McpError(404, message);
30
+ if (message.startsWith("secret_broker_config_invalid") || message.startsWith("secret_broker_value_invalid")) {
31
+ throw new McpError(400, message);
32
+ }
33
+ throw err;
34
+ }
35
+ /**
36
+ * Non-blocking warnings: tools that can see this secret (under any alias it is
37
+ * attached as) but still read it with secrets.get(), which a brokered secret refuses.
38
+ */
39
+ async function brokerWarningsForSecret(db, ownerId, name) {
40
+ const bindings = await db.agentSecret.findMany({
41
+ where: { secret: { ownerId, name } },
42
+ select: { agentId: true, boundName: true },
43
+ });
44
+ if (!bindings.length)
45
+ return [];
46
+ const aliasesByAgent = new Map();
47
+ for (const b of bindings) {
48
+ const aliases = aliasesByAgent.get(b.agentId) ?? new Set();
49
+ aliases.add(b.boundName);
50
+ aliasesByAgent.set(b.agentId, aliases);
51
+ }
52
+ const attachments = await db.agentTool.findMany({
53
+ where: { agentId: { in: [...aliasesByAgent.keys()] } },
54
+ include: { tool: { select: { name: true, code: true } } },
55
+ });
56
+ const warnings = new Set();
57
+ for (const a of attachments) {
58
+ const tools = [{ name: a.tool.name, code: a.tool.code, allowedSecrets: allowedSecretNames(a.allowedSecrets) }];
59
+ for (const w of brokerCompatibilityWarnings(tools, aliasesByAgent.get(a.agentId)))
60
+ warnings.add(w);
61
+ }
62
+ return [...warnings];
63
+ }
64
+ function withWarnings(body, warnings) {
65
+ return warnings.length ? { ...body, warnings } : body;
66
+ }
67
+ async function findOwnSecret(ctx, name) {
68
+ const secret = await ctx.db.secret.findUnique({ where: { ownerId_name: { ownerId: ctx.principal.id, name } } });
69
+ if (!secret)
70
+ throw new McpError(404, `secret_not_found: no secret named "${name}"`);
71
+ return secret;
72
+ }
13
73
  export function registerSecretsTools(mcp, opts) {
14
74
  mcp.registerTool({
15
75
  name: "create_secret",
16
76
  scope: "secrets:write",
17
- description: 'Creates a secret. Omit "value" to enter it securely via a one-time browser link instead of passing it as a plaintext argument.',
77
+ description: 'Creates a secret. Omit "value" to enter it securely via a one-time browser link instead of passing it as a plaintext argument. Pass "broker" ({ hosts, pathPrefixes?, placement }) to make it brokered: tools can then only send it to those hosts with fetch(url, { secrets: [name] }) and can never read it. See the help article "brokered-secrets".',
18
78
  inputSchema: {
19
79
  type: "object",
20
- properties: { name: { type: "string" }, value: { type: "string" } },
80
+ properties: {
81
+ name: { type: "string" },
82
+ value: { type: "string" },
83
+ broker: { type: "object", description: `Optional broker config: ${BROKER_SCHEMA_DOC}` },
84
+ },
21
85
  required: ["name"],
22
86
  },
23
87
  handler: async (args, ctx) => {
88
+ const broker = args.broker === undefined ? undefined : parseBrokerArg(args.broker);
24
89
  if (args.value !== undefined) {
25
- const secret = await createSecret(args.name, args.value, ctx.principal.id, ctx.providers.secrets, ctx.db);
26
- return textResult({ id: secret.id, name: secret.name, keyId: secret.keyId, createdAt: secret.createdAt });
90
+ let secret;
91
+ try {
92
+ secret = await createSecret(args.name, args.value, ctx.principal.id, ctx.providers.secrets, ctx.db, {
93
+ broker,
94
+ via: "mcp",
95
+ });
96
+ }
97
+ catch (err) {
98
+ mapSecretError(err);
99
+ }
100
+ // A fresh broker config supersedes any earlier, browser-confirmed removal still on record.
101
+ if (broker)
102
+ await clearUnbrokerOutcome(ctx.principal.id, args.name, ctx.db);
103
+ return textResult({
104
+ id: secret.id,
105
+ name: secret.name,
106
+ keyId: secret.keyId,
107
+ createdAt: secret.createdAt,
108
+ broker: secret.broker == null ? null : parseSecretBrokerConfig(secret.broker),
109
+ });
27
110
  }
28
111
  if (opts.protocolElicitation) {
29
112
  const state = ctx.mcpReq.requestState();
@@ -43,7 +126,12 @@ export function registerSecretsTools(mcp, opts) {
43
126
  throw new McpError(400, existingOutcome.error);
44
127
  return textResult(existingOutcome.secret);
45
128
  }
46
- const payload = { ownerId: ctx.principal.id, secretName: args.name };
129
+ const payload = {
130
+ ownerId: ctx.principal.id,
131
+ secretName: args.name,
132
+ kind: "create",
133
+ ...(broker ? { broker } : {}),
134
+ };
47
135
  const token = await mcp.mintRequestState(payload);
48
136
  const url = await opts.buildElicitationUrl(token);
49
137
  if (opts.protocolElicitation) {
@@ -66,6 +154,95 @@ export function registerSecretsTools(mcp, opts) {
66
154
  });
67
155
  },
68
156
  });
157
+ mcp.registerTool({
158
+ name: "set_secret_broker",
159
+ scope: "secrets:write",
160
+ description: 'Sets or changes a secret\'s broker config ({ hosts, pathPrefixes?, placement }), so tools can only send it to those hosts with fetch(url, { secrets: [name] }) and never read it. broker: null removes brokering, which must be confirmed by a person in the browser link this returns. Every change is audited. See the help article "brokered-secrets".',
161
+ inputSchema: {
162
+ type: "object",
163
+ properties: {
164
+ name: { type: "string" },
165
+ broker: {
166
+ type: ["object", "null"],
167
+ description: `The broker config ${BROKER_SCHEMA_DOC}, or null to remove brokering (browser-confirmed).`,
168
+ },
169
+ },
170
+ required: ["name", "broker"],
171
+ },
172
+ handler: async (args, ctx) => {
173
+ const ownerId = ctx.principal.id;
174
+ if (args.broker !== null) {
175
+ const broker = parseBrokerArg(args.broker);
176
+ await findOwnSecret(ctx, args.name);
177
+ let change;
178
+ try {
179
+ change = await setSecretBroker(ctx.db, ctx.providers.secrets, {
180
+ ownerId,
181
+ name: args.name,
182
+ broker,
183
+ actorId: ownerId,
184
+ via: "mcp",
185
+ });
186
+ }
187
+ catch (err) {
188
+ mapSecretError(err);
189
+ }
190
+ await clearUnbrokerOutcome(ownerId, args.name, ctx.db);
191
+ const warnings = await brokerWarningsForSecret(ctx.db, ownerId, args.name);
192
+ return textResult(withWarnings({ name: args.name, before: change.before, broker: change.after }, warnings));
193
+ }
194
+ // broker: null — never removed here. Only a person submitting the
195
+ // browser form (fulfillUnbrokerElicitation) removes it; this call hands
196
+ // out the link and, once that has happened, reports it.
197
+ if (opts.protocolElicitation) {
198
+ const state = ctx.mcpReq.requestState();
199
+ if (state) {
200
+ const view = inputResponse(ctx.mcpReq.inputResponses, "confirmUnbroker");
201
+ if (view.kind === "elicit" && view.action !== "accept") {
202
+ throw new McpError(400, `Removing brokering was ${view.action}d.`);
203
+ }
204
+ }
205
+ }
206
+ const existingOutcome = await getSecretElicitationOutcome(ownerId, args.name, ctx.db, "unbroker");
207
+ if (existingOutcome) {
208
+ if (!existingOutcome.ok)
209
+ mapSecretError(new Error(existingOutcome.error));
210
+ return textResult({ unbrokered: true, secret: existingOutcome.secret });
211
+ }
212
+ const secret = await findOwnSecret(ctx, args.name);
213
+ if (secret.broker == null) {
214
+ // Nothing to remove, so nothing for a person to confirm.
215
+ const metadata = (await listSecrets(ownerId, ctx.db)).find((s) => s.id === secret.id);
216
+ return textResult({ unbrokered: true, secret: metadata });
217
+ }
218
+ // The hash pins the link to the config being confirmed: if it changes before
219
+ // the person submits, fulfillUnbrokerElicitation refuses (secret_broker_changed).
220
+ const payload = {
221
+ ownerId,
222
+ secretName: args.name,
223
+ kind: "unbroker",
224
+ brokerHash: brokerConfigHash(parseSecretBrokerConfig(secret.broker)),
225
+ };
226
+ const token = await mcp.mintRequestState(payload);
227
+ const url = await opts.buildElicitationUrl(token);
228
+ if (opts.protocolElicitation) {
229
+ return inputRequired({
230
+ requestState: token,
231
+ inputRequests: {
232
+ confirmUnbroker: inputRequired.elicitUrl({
233
+ url,
234
+ message: `Confirm removing brokering for secret "${args.name}" in your browser.`,
235
+ }),
236
+ },
237
+ });
238
+ }
239
+ return textResult({
240
+ status: "pending",
241
+ message: `Open this link in your browser to confirm removing brokering for secret "${args.name}", then call set_secret_broker again with broker: null to finish.`,
242
+ url,
243
+ });
244
+ },
245
+ });
69
246
  mcp.registerTool({
70
247
  name: "list_secrets",
71
248
  scope: "secrets:write",
@@ -25,8 +25,8 @@ export declare function projectTool(tool: Tool, principalId: string): {
25
25
  id: string;
26
26
  ownerId: string | null;
27
27
  description: string;
28
- paramsZod: string;
29
28
  jsonSchema: import("@prisma/client/runtime/client").JsonValue;
29
+ paramsZod: string;
30
30
  } | {
31
31
  id: string;
32
32
  name: string;
@@ -21,6 +21,7 @@ import { requireOwnedTool, requireStrictlyOwnedTool, assertStrictlyOwnedTool, vi
21
21
  import { agentAccessResolver, assertAgentAccess, requireAgentAccess } from "../auth/access.js";
22
22
  import { atLeast } from "../../core/grants.js";
23
23
  import { textResult } from "./text-result.js";
24
+ import { brokerCompatibilityWarnings } from "./secret-broker-compat.js";
24
25
  /**
25
26
  * Names the agents the caller can read (its own and ones granted to it)
26
27
  * and only counts the rest, so a refusal never reveals another principal's
@@ -48,6 +49,22 @@ export function projectTool(tool, principalId) {
48
49
  ? { id: tool.id, name: tool.name, description: tool.description, public: true }
49
50
  : { id: tool.id, name: tool.name, description: tool.description, public: false, ownerIsCaller: false };
50
51
  }
52
+ /**
53
+ * Non-blocking: which of the secrets just granted to this attachment are
54
+ * brokered on the agent, and does the tool still read them with
55
+ * secrets.get()? Reveals only that fact about a tool the caller may not own
56
+ * (attach_tool already lets the agent's owner grant capabilities to such code).
57
+ */
58
+ async function brokeredSecretWarnings(db, agentId, tool, allowedSecrets) {
59
+ if (!tool || !allowedSecrets?.length)
60
+ return [];
61
+ const bindings = await db.agentSecret.findMany({
62
+ where: { agentId, boundName: { in: [...allowedSecrets] } },
63
+ include: { secret: { select: { broker: true } } },
64
+ });
65
+ const brokered = new Set(bindings.filter((b) => b.secret.broker != null).map((b) => b.boundName));
66
+ return brokerCompatibilityWarnings([{ ...tool, allowedSecrets }], brokered);
67
+ }
51
68
  /** Refuses a name one of the runner's built-ins would shadow (see core/tool-names.ts). */
52
69
  function assertToolNameAllowed(name) {
53
70
  const reason = reservedToolNameReason(name);
@@ -232,7 +249,7 @@ export function registerToolAuthoringTools(mcp) {
232
249
  mcp.registerTool({
233
250
  name: "attach_tool",
234
251
  scope: "tools:write",
235
- description: "Attaches a tool to an agent (needs write on the agent). The four capability fields (allowedSecrets, allowedDatastorePrefixes, allowedHosts, allowedSharedDatastorePrefixes) are the agent owner's to grant: anyone else passing one gets 403, and their attachment runs with none until the owner re-runs attach_tool with the capabilities. When the owner grants an attachment it had not granted before (someone else attached it, or the agent changed owner), every capability not passed is reset to empty, so state each one you want. Granting capabilities to a tool someone else owns vouches for code you can't read (only its owner sees it); the tool's owner can't change it while it's attached to your agent, and detaching drops your grant.",
252
+ description: "Attaches a tool to an agent (needs write on the agent). The four capability fields (allowedSecrets, allowedDatastorePrefixes, allowedHosts, allowedSharedDatastorePrefixes) are the agent owner's to grant: anyone else passing one gets 403, and their attachment runs with none until the owner re-runs attach_tool with the capabilities. When the owner grants an attachment it had not granted before (someone else attached it, or the agent changed owner), every capability not passed is reset to empty, so state each one you want. Granting capabilities to a tool someone else owns vouches for code you can't read (only its owner sees it); the tool's owner can't change it while it's attached to your agent, and detaching drops your grant. Granting a brokered secret lets the tool send it only via fetch(url, { secrets: [name] }), never read it; the result includes warnings when the tool calls secrets.get() on one.",
236
253
  inputSchema: {
237
254
  type: "object",
238
255
  properties: {
@@ -263,6 +280,7 @@ export function registerToolAuthoringTools(mcp) {
263
280
  throw new McpError(400, `Invalid tool capabilities: ${patch.error.issues.map((i) => i.message).join("; ")}`);
264
281
  }
265
282
  const capabilitiesPassed = Object.values(patch.data).some((value) => value !== undefined);
283
+ let attachedTool;
266
284
  await ctx.db.$transaction(async (tx) => {
267
285
  const { agent } = await assertAgentAccess(ctx, await tx.agent.findUnique({ where: { id: args.agentId } }), args.agentId, "write", tx);
268
286
  if (agent.kind === "coding") {
@@ -290,6 +308,7 @@ export function registerToolAuthoringTools(mcp) {
290
308
  // The runtime dispatches by name, and names are only unique per
291
309
  // owner: a second same-named tool on one agent would be a
292
310
  // duplicate tool name to the model and silently shadow one of them.
311
+ attachedTool = { name: tool.name, code: tool.code };
293
312
  const clash = await findSameNamedAttachedTool(tx, args.agentId, tool);
294
313
  if (clash) {
295
314
  // The clashing tool may be another principal's private one (a
@@ -330,7 +349,8 @@ export function registerToolAuthoringTools(mcp) {
330
349
  update: ownerGrant,
331
350
  });
332
351
  }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
333
- return textResult({ attached: true });
352
+ const warnings = await brokeredSecretWarnings(ctx.db, args.agentId, attachedTool, patch.data.allowedSecrets);
353
+ return textResult(warnings.length ? { attached: true, warnings } : { attached: true });
334
354
  },
335
355
  });
336
356
  mcp.registerTool({
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Builds the native sandbox executor from configuration (NATIVE_SANDBOX_*, docs/native-sandbox.md),
3
+ * or nothing when the native sandbox is not configured — sandbox-mode runs then fail closed.
4
+ */
5
+ import { type Power } from "../core/power.js";
6
+ import type { NativeRunProviders, RunnerDb } from "../core/runner.js";
7
+ import { type GatewayLedgerDb } from "./ledger.js";
8
+ import type { KubernetesApi } from "../providers/jobs/kubernetes-api.js";
9
+ import { NativeSandboxExecutor, type ManagedWorkerLauncher } from "./sandbox-executor.js";
10
+ import { type WarmWorkerLauncher } from "./warm-pool.js";
11
+ import { type WarmPoolDb } from "./warm-pool-ledger.js";
12
+ export declare function buildNativeSandboxExecutor(options: {
13
+ db: RunnerDb & GatewayLedgerDb & WarmPoolDb;
14
+ /** Read at call time: the composition root patches `executor` on after wrapping this one. */
15
+ providers: NativeRunProviders;
16
+ env?: NodeJS.ProcessEnv;
17
+ /** Tests only: replaces the launcher. */
18
+ launcher?: ManagedWorkerLauncher & Partial<WarmWorkerLauncher>;
19
+ /** Tests only: replaces the kubeconfig-backed client for NATIVE_SANDBOX_LAUNCHER=kubernetes. */
20
+ kubernetesApi?: KubernetesApi;
21
+ /** Low-power mode: the shared Power the Kubernetes launcher wakes the native gateway through. */
22
+ power?: Power;
23
+ }): NativeSandboxExecutor | undefined;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Builds the native sandbox executor from configuration (NATIVE_SANDBOX_*, docs/native-sandbox.md),
3
+ * or nothing when the native sandbox is not configured — sandbox-mode runs then fail closed.
4
+ */
5
+ import { loadNativeSandboxConfig } from "../config/providers.js";
6
+ import { DISABLED_POWER } from "../core/power.js";
7
+ import { lowPowerActive } from "../core/power-composition.js";
8
+ import { nativeGatewayUrl } from "./docker-isolation.js";
9
+ import { DockerNativeWorkerLauncher } from "./docker-launcher.js";
10
+ import { PrismaGatewayLedger } from "./ledger.js";
11
+ import { KubernetesNativeWorkerLauncher } from "./kubernetes-launcher.js";
12
+ import { ClientNodeKubernetesApi } from "../providers/jobs/kubernetes-client.js";
13
+ import { SANDBOX_RUN_MAX_SEC } from "../core/runner.js";
14
+ import { NativeSandboxExecutor } from "./sandbox-executor.js";
15
+ import { PooledWorkerLauncher } from "./warm-pool.js";
16
+ import { PrismaWarmPoolLedger } from "./warm-pool-ledger.js";
17
+ export function buildNativeSandboxExecutor(options) {
18
+ const env = options.env ?? process.env;
19
+ const config = loadNativeSandboxConfig(env);
20
+ if (!config)
21
+ return undefined;
22
+ // Only when low-power mode is actually on (as buildPower decides); otherwise it is ignored.
23
+ if (config.warmPoolSize > 0 && lowPowerActive(env)) {
24
+ throw new Error("WARDBY_LOW_POWER=1 requires NATIVE_SANDBOX_WARM_POOL_SIZE=0: an always-on warm pool defeats scale-to-zero.");
25
+ }
26
+ const limits = { cpus: config.cpus, memoryMb: config.memoryMb, pids: config.pids };
27
+ const markReady = (runId) => new PrismaGatewayLedger(options.db).markNetworkReadyForRun(runId);
28
+ const cold = options.launcher ??
29
+ (config.launcher === "kubernetes"
30
+ ? new KubernetesNativeWorkerLauncher({
31
+ api: options.kubernetesApi ?? new ClientNodeKubernetesApi({ context: config.context }),
32
+ namespace: config.namespace,
33
+ image: config.workerImage,
34
+ limits,
35
+ gatewayService: config.gatewayService,
36
+ runtimeClassName: config.runtimeClassName,
37
+ platform: config.platform,
38
+ priorityClassName: config.priorityClassName,
39
+ readyTimeoutMs: config.readyTimeoutMs,
40
+ enforcementTimeoutMs: config.enforcementTimeoutMs,
41
+ gatewayUrl: config.gatewayUrl,
42
+ deadlineSeconds: SANDBOX_RUN_MAX_SEC,
43
+ power: options.power ?? DISABLED_POWER,
44
+ // Isolation proven inside the pod: the gateway may now serve the run.
45
+ onNetworkProven: markReady,
46
+ })
47
+ : new DockerNativeWorkerLauncher({
48
+ image: config.workerImage,
49
+ gatewayContainer: config.gatewayContainer,
50
+ limits,
51
+ }));
52
+ // The pool also starts with size 0 when the launcher can run one: its first tick removes workers
53
+ // left from an earlier configuration, and nothing runs after that.
54
+ const launcher = isWarmCapable(cold)
55
+ ? new PooledWorkerLauncher({
56
+ ledger: new PrismaWarmPoolLedger(options.db),
57
+ launcher: cold,
58
+ size: config.warmPoolSize,
59
+ maxAgeMs: config.warmMaxAgeMs,
60
+ warmTimeoutMs: config.launcher === "kubernetes"
61
+ ? (config.readyTimeoutMs ?? 120_000) + (config.enforcementTimeoutMs ?? 30_000)
62
+ : 120_000,
63
+ onDelivered: markReady,
64
+ })
65
+ : cold;
66
+ return new NativeSandboxExecutor({
67
+ db: options.db,
68
+ providers: options.providers,
69
+ launcher,
70
+ // Kubernetes resolves the gateway's ClusterIP per launch (resolveGatewayUrl) unless overridden.
71
+ gatewayUrl: config.gatewayUrl ?? nativeGatewayUrl(),
72
+ maxConcurrent: config.maxConcurrent,
73
+ });
74
+ }
75
+ function isWarmCapable(launcher) {
76
+ return typeof launcher.startWarm === "function" && typeof launcher.deliver === "function";
77
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Docker objects for one sandbox-mode native run (docs/native-sandbox.md),
3
+ * built the way the coding launcher builds its own (providers/jobs/
4
+ * docker-isolation.ts): an internal per-run network whose only other member is
5
+ * the native gateway container, and a single-use worker container that can
6
+ * reach nothing else — no internet, no database, no Docker socket. Names carry
7
+ * an opaque hash of the run id, never the id itself.
8
+ */
9
+ export declare const NATIVE_WORKER_UID = 10001;
10
+ export declare const NATIVE_GATEWAY_ALIAS = "wardby-native-gateway";
11
+ export declare const NATIVE_GATEWAY_PORT = 8790;
12
+ /** The gateway's witness port: never reachable from a worker (see coding-proxy/deny-port.ts). */
13
+ export declare const NATIVE_GATEWAY_DENY_PORT = 8791;
14
+ export declare const NATIVE_WORKER_TMP_MB = 64;
15
+ export interface NativeWorkerLimits {
16
+ cpus: number;
17
+ memoryMb: number;
18
+ pids: number;
19
+ }
20
+ export declare const DEFAULT_NATIVE_WORKER_LIMITS: NativeWorkerLimits;
21
+ export interface NativeIsolationNames {
22
+ network: string;
23
+ worker: string;
24
+ }
25
+ export declare function nativeIsolationNames(runId: string): NativeIsolationNames;
26
+ /** The label value tying a Docker object to its run, without exposing the run id. */
27
+ export declare function nativeRunLabel(runId: string): string;
28
+ /** A warm pool worker's name (native sandbox phase 6): its random token, never a run's hash. */
29
+ export declare function nativeWarmWorkerName(token: string): string;
30
+ export declare function nativeWarmIsolationNames(token: string): NativeIsolationNames;
31
+ /** Docker's label filter for every warm pool object, and the label carrying its token. */
32
+ export declare const NATIVE_WARM_LABEL_FILTER = "io.wardby.pool=warm";
33
+ export declare const NATIVE_WARM_TOKEN_LABEL = "io.wardby.warm-worker";
34
+ /** Docker's label filter for every native worker object (the janitor lists by it). */
35
+ export declare const NATIVE_WORKER_LABEL_FILTER = "io.wardby.component=native-worker";
36
+ export declare function buildNativeNetworkCreateArgs(runId: string): string[];
37
+ export declare function buildNativeWarmNetworkCreateArgs(token: string): string[];
38
+ /** Joins the gateway container to the run's network under the name workers dial. */
39
+ export declare function buildGatewayConnectArgs(runId: string, gatewayContainer: string): string[];
40
+ export declare function gatewayConnectArgs(network: string, gatewayContainer: string): string[];
41
+ export declare function buildGatewayDisconnectArgs(runId: string, gatewayContainer: string): string[];
42
+ export declare function gatewayDisconnectArgs(network: string, gatewayContainer: string): string[];
43
+ /**
44
+ * `docker run -i` for the worker: its WorkerInput (capability included) arrives on stdin, so the
45
+ * capability is never in its environment, argv, labels, or `docker inspect`. Not `--rm`: the
46
+ * executor reads the exit code, then removes it.
47
+ */
48
+ export declare function buildNativeWorkerRunArgs(input: {
49
+ runId: string;
50
+ image: string;
51
+ limits: NativeWorkerLimits;
52
+ }): string[];
53
+ /**
54
+ * `docker run -d` for a warm pool worker: no input yet. It waits for one written into its /tmp by
55
+ * `docker exec -i` after a run claims it (warm-delivery.ts), so the capability still never reaches
56
+ * its environment, argv, or labels.
57
+ */
58
+ export declare function buildNativeWarmWorkerRunArgs(input: {
59
+ token: string;
60
+ image: string;
61
+ limits: NativeWorkerLimits;
62
+ waitMs: number;
63
+ }): string[];
64
+ /** The URL a worker dials: the gateway's alias on its own run network. */
65
+ export declare function nativeGatewayUrl(port?: number): string;
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Docker objects for one sandbox-mode native run (docs/native-sandbox.md),
3
+ * built the way the coding launcher builds its own (providers/jobs/
4
+ * docker-isolation.ts): an internal per-run network whose only other member is
5
+ * the native gateway container, and a single-use worker container that can
6
+ * reach nothing else — no internet, no database, no Docker socket. Names carry
7
+ * an opaque hash of the run id, never the id itself.
8
+ */
9
+ import { isImmutableDockerImage, isolationToken } from "../providers/jobs/docker-isolation.js";
10
+ import { createHash } from "node:crypto";
11
+ import { WARM_INPUT_FILE } from "./warm-delivery.js";
12
+ export const NATIVE_WORKER_UID = 10001;
13
+ export const NATIVE_GATEWAY_ALIAS = "wardby-native-gateway";
14
+ export const NATIVE_GATEWAY_PORT = 8790;
15
+ /** The gateway's witness port: never reachable from a worker (see coding-proxy/deny-port.ts). */
16
+ export const NATIVE_GATEWAY_DENY_PORT = 8791;
17
+ export const NATIVE_WORKER_TMP_MB = 64;
18
+ const LABEL_MANAGED = "io.wardby.managed=true";
19
+ const LABEL_COMPONENT = "io.wardby.component=native-worker";
20
+ export const DEFAULT_NATIVE_WORKER_LIMITS = { cpus: 1, memoryMb: 512, pids: 128 };
21
+ export function nativeIsolationNames(runId) {
22
+ const token = isolationToken(runId);
23
+ return { network: `wardby-nnet-${token}`, worker: `wardby-native-${token}` };
24
+ }
25
+ /** The label value tying a Docker object to its run, without exposing the run id. */
26
+ export function nativeRunLabel(runId) {
27
+ return createHash("sha256").update(runId).digest("hex");
28
+ }
29
+ function labels(runId) {
30
+ return [
31
+ "--label",
32
+ LABEL_MANAGED,
33
+ "--label",
34
+ LABEL_COMPONENT,
35
+ "--label",
36
+ `io.wardby.run-sha256=${nativeRunLabel(runId)}`,
37
+ ];
38
+ }
39
+ /** A warm pool worker's name (native sandbox phase 6): its random token, never a run's hash. */
40
+ export function nativeWarmWorkerName(token) {
41
+ if (!/^[0-9a-f]{20}$/.test(token))
42
+ throw new Error("native_sandbox_invalid_warm_token");
43
+ return `wardby-nwarm-${token}`;
44
+ }
45
+ export function nativeWarmIsolationNames(token) {
46
+ return { network: `wardby-nwnet-${token}`, worker: nativeWarmWorkerName(token) };
47
+ }
48
+ /** Docker's label filter for every warm pool object, and the label carrying its token. */
49
+ export const NATIVE_WARM_LABEL_FILTER = "io.wardby.pool=warm";
50
+ export const NATIVE_WARM_TOKEN_LABEL = "io.wardby.warm-worker";
51
+ function warmLabels(token) {
52
+ return [
53
+ "--label",
54
+ LABEL_MANAGED,
55
+ "--label",
56
+ LABEL_COMPONENT,
57
+ "--label",
58
+ NATIVE_WARM_LABEL_FILTER,
59
+ "--label",
60
+ `${NATIVE_WARM_TOKEN_LABEL}=${token}`,
61
+ ];
62
+ }
63
+ /** Docker's label filter for every native worker object (the janitor lists by it). */
64
+ export const NATIVE_WORKER_LABEL_FILTER = LABEL_COMPONENT;
65
+ export function buildNativeNetworkCreateArgs(runId) {
66
+ return networkCreateArgs(nativeIsolationNames(runId).network, labels(runId));
67
+ }
68
+ export function buildNativeWarmNetworkCreateArgs(token) {
69
+ return networkCreateArgs(nativeWarmIsolationNames(token).network, warmLabels(token));
70
+ }
71
+ function networkCreateArgs(network, objectLabels) {
72
+ return [
73
+ "network",
74
+ "create",
75
+ "--driver",
76
+ "bridge",
77
+ "--internal",
78
+ "--ipv6=false",
79
+ "--opt",
80
+ "com.docker.network.bridge.gateway_mode_ipv4=isolated",
81
+ "--opt",
82
+ "com.docker.network.bridge.gateway_mode_ipv6=isolated",
83
+ ...objectLabels,
84
+ network,
85
+ ];
86
+ }
87
+ /** Joins the gateway container to the run's network under the name workers dial. */
88
+ export function buildGatewayConnectArgs(runId, gatewayContainer) {
89
+ return gatewayConnectArgs(nativeIsolationNames(runId).network, gatewayContainer);
90
+ }
91
+ export function gatewayConnectArgs(network, gatewayContainer) {
92
+ return ["network", "connect", "--alias", NATIVE_GATEWAY_ALIAS, network, gatewayContainer];
93
+ }
94
+ export function buildGatewayDisconnectArgs(runId, gatewayContainer) {
95
+ return gatewayDisconnectArgs(nativeIsolationNames(runId).network, gatewayContainer);
96
+ }
97
+ export function gatewayDisconnectArgs(network, gatewayContainer) {
98
+ return ["network", "disconnect", "--force", network, gatewayContainer];
99
+ }
100
+ /**
101
+ * `docker run -i` for the worker: its WorkerInput (capability included) arrives on stdin, so the
102
+ * capability is never in its environment, argv, labels, or `docker inspect`. Not `--rm`: the
103
+ * executor reads the exit code, then removes it.
104
+ */
105
+ export function buildNativeWorkerRunArgs(input) {
106
+ const names = nativeIsolationNames(input.runId);
107
+ return workerRunArgs({ ...input, names, mode: ["-i"], env: [], objectLabels: labels(input.runId) });
108
+ }
109
+ /**
110
+ * `docker run -d` for a warm pool worker: no input yet. It waits for one written into its /tmp by
111
+ * `docker exec -i` after a run claims it (warm-delivery.ts), so the capability still never reaches
112
+ * its environment, argv, or labels.
113
+ */
114
+ export function buildNativeWarmWorkerRunArgs(input) {
115
+ return workerRunArgs({
116
+ ...input,
117
+ names: nativeWarmIsolationNames(input.token),
118
+ mode: ["-d"],
119
+ env: [
120
+ "--env",
121
+ `NATIVE_WORKER_INPUT_FILE=${WARM_INPUT_FILE}`,
122
+ "--env",
123
+ `NATIVE_WORKER_INPUT_WAIT_MS=${Math.floor(input.waitMs)}`,
124
+ ],
125
+ objectLabels: warmLabels(input.token),
126
+ });
127
+ }
128
+ function workerRunArgs(input) {
129
+ const { image, limits, names } = input;
130
+ if (!isImmutableDockerImage(image)) {
131
+ throw new Error("native_sandbox_image_not_pinned: the worker image must be a digest (repo@sha256:...) or a local image id.");
132
+ }
133
+ return [
134
+ "run",
135
+ ...input.mode,
136
+ "--name",
137
+ names.worker,
138
+ "--pull",
139
+ "never",
140
+ "--user",
141
+ `${NATIVE_WORKER_UID}:${NATIVE_WORKER_UID}`,
142
+ "--network",
143
+ names.network,
144
+ "--read-only",
145
+ "--tmpfs",
146
+ `/tmp:rw,noexec,nosuid,nodev,size=${NATIVE_WORKER_TMP_MB}m`,
147
+ "--cap-drop",
148
+ "ALL",
149
+ "--security-opt",
150
+ "no-new-privileges=true",
151
+ "--security-opt",
152
+ "seccomp=builtin",
153
+ "--cgroupns",
154
+ "private",
155
+ "--ipc",
156
+ "none",
157
+ "--cpus",
158
+ String(limits.cpus),
159
+ "--memory",
160
+ `${limits.memoryMb}m`,
161
+ "--memory-swap",
162
+ `${limits.memoryMb}m`,
163
+ "--memory-swappiness",
164
+ "0",
165
+ "--pids-limit",
166
+ String(limits.pids),
167
+ "--restart",
168
+ "no",
169
+ "--log-driver",
170
+ "local",
171
+ "--log-opt",
172
+ "max-size=1m",
173
+ "--log-opt",
174
+ "max-file=2",
175
+ ...input.env,
176
+ ...input.objectLabels,
177
+ image,
178
+ ];
179
+ }
180
+ /** The URL a worker dials: the gateway's alias on its own run network. */
181
+ export function nativeGatewayUrl(port = NATIVE_GATEWAY_PORT) {
182
+ return `http://${NATIVE_GATEWAY_ALIAS}:${port}/native-gateway/v1/call`;
183
+ }