@wardby/cli 0.1.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 (412) hide show
  1. package/.env.example +131 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +2 -0
  4. package/README.md +282 -0
  5. package/bin/wardby.js +3 -0
  6. package/deploy/local/docker-compose.yml +19 -0
  7. package/dist/claude-coding-worker/driver.d.ts +70 -0
  8. package/dist/claude-coding-worker/driver.js +128 -0
  9. package/dist/claude-coding-worker/main.d.ts +2 -0
  10. package/dist/claude-coding-worker/main.js +41 -0
  11. package/dist/claude-coding-worker/sdk.d.ts +4 -0
  12. package/dist/claude-coding-worker/sdk.js +43 -0
  13. package/dist/claude-coding-worker/tool-relay.d.ts +1 -0
  14. package/dist/claude-coding-worker/tool-relay.js +9 -0
  15. package/dist/cli-help.d.ts +1 -0
  16. package/dist/cli-help.js +20 -0
  17. package/dist/cli.d.ts +18 -0
  18. package/dist/cli.js +629 -0
  19. package/dist/coding/observability.d.ts +59 -0
  20. package/dist/coding/observability.js +77 -0
  21. package/dist/coding/profile.d.ts +88 -0
  22. package/dist/coding/profile.js +137 -0
  23. package/dist/coding/protocol.d.ts +345 -0
  24. package/dist/coding/protocol.js +445 -0
  25. package/dist/coding/provider.d.ts +4 -0
  26. package/dist/coding/provider.js +18 -0
  27. package/dist/coding-proxy/main.d.ts +1 -0
  28. package/dist/coding-proxy/main.js +51 -0
  29. package/dist/coding-worker/artifact.d.ts +16 -0
  30. package/dist/coding-worker/artifact.js +37 -0
  31. package/dist/coding-worker/driver.d.ts +46 -0
  32. package/dist/coding-worker/driver.js +158 -0
  33. package/dist/coding-worker/errors.d.ts +1 -0
  34. package/dist/coding-worker/errors.js +23 -0
  35. package/dist/coding-worker/isolation-probe.d.ts +1 -0
  36. package/dist/coding-worker/isolation-probe.js +146 -0
  37. package/dist/coding-worker/keeper.d.ts +4 -0
  38. package/dist/coding-worker/keeper.js +29 -0
  39. package/dist/coding-worker/main.d.ts +2 -0
  40. package/dist/coding-worker/main.js +43 -0
  41. package/dist/coding-worker/sdk.d.ts +26 -0
  42. package/dist/coding-worker/sdk.js +28 -0
  43. package/dist/coding-worker/types.d.ts +64 -0
  44. package/dist/coding-worker/types.js +1 -0
  45. package/dist/config/providers.d.ts +158 -0
  46. package/dist/config/providers.js +162 -0
  47. package/dist/core/budget-groups.d.ts +72 -0
  48. package/dist/core/budget-groups.js +149 -0
  49. package/dist/core/budget.d.ts +63 -0
  50. package/dist/core/budget.js +69 -0
  51. package/dist/core/coding-queue.d.ts +32 -0
  52. package/dist/core/coding-queue.js +54 -0
  53. package/dist/core/cron.d.ts +31 -0
  54. package/dist/core/cron.js +43 -0
  55. package/dist/core/datastores.d.ts +41 -0
  56. package/dist/core/datastores.js +105 -0
  57. package/dist/core/db.d.ts +2 -0
  58. package/dist/core/db.js +2 -0
  59. package/dist/core/dispatch.d.ts +81 -0
  60. package/dist/core/dispatch.js +184 -0
  61. package/dist/core/engine-native.d.ts +29 -0
  62. package/dist/core/engine-native.js +259 -0
  63. package/dist/core/http-runtime.d.ts +7 -0
  64. package/dist/core/http-runtime.js +101 -0
  65. package/dist/core/lease.d.ts +13 -0
  66. package/dist/core/lease.js +22 -0
  67. package/dist/core/logger.d.ts +13 -0
  68. package/dist/core/logger.js +45 -0
  69. package/dist/core/memory-tools.d.ts +11 -0
  70. package/dist/core/memory-tools.js +122 -0
  71. package/dist/core/reconciler.d.ts +45 -0
  72. package/dist/core/reconciler.js +152 -0
  73. package/dist/core/runner.d.ts +87 -0
  74. package/dist/core/runner.js +529 -0
  75. package/dist/core/scheduler.d.ts +47 -0
  76. package/dist/core/scheduler.js +121 -0
  77. package/dist/core/secrets.d.ts +46 -0
  78. package/dist/core/secrets.js +100 -0
  79. package/dist/core/subagent-memory-tools.d.ts +18 -0
  80. package/dist/core/subagent-memory-tools.js +121 -0
  81. package/dist/core/timing.d.ts +7 -0
  82. package/dist/core/timing.js +7 -0
  83. package/dist/core/webhooks.d.ts +23 -0
  84. package/dist/core/webhooks.js +113 -0
  85. package/dist/env.d.ts +11 -0
  86. package/dist/env.js +13 -0
  87. package/dist/import/budgets.d.ts +16 -0
  88. package/dist/import/budgets.js +21 -0
  89. package/dist/import/bundle.d.ts +16 -0
  90. package/dist/import/bundle.js +61 -0
  91. package/dist/import/cli-args.d.ts +2 -0
  92. package/dist/import/cli-args.js +44 -0
  93. package/dist/import/create.d.ts +34 -0
  94. package/dist/import/create.js +363 -0
  95. package/dist/import/index.d.ts +25 -0
  96. package/dist/import/index.js +130 -0
  97. package/dist/import/model-gate.d.ts +1 -0
  98. package/dist/import/model-gate.js +3 -0
  99. package/dist/import/neutral-schema.d.ts +369 -0
  100. package/dist/import/neutral-schema.js +109 -0
  101. package/dist/import/preflight.d.ts +41 -0
  102. package/dist/import/preflight.js +71 -0
  103. package/dist/import/report.d.ts +5 -0
  104. package/dist/import/report.js +98 -0
  105. package/dist/import/tool-scan.d.ts +8 -0
  106. package/dist/import/tool-scan.js +20 -0
  107. package/dist/mcp/auth/ownership.d.ts +113 -0
  108. package/dist/mcp/auth/ownership.js +87 -0
  109. package/dist/mcp/auth/principal.d.ts +9 -0
  110. package/dist/mcp/auth/principal.js +9 -0
  111. package/dist/mcp/auth/resource-server.d.ts +47 -0
  112. package/dist/mcp/auth/resource-server.js +82 -0
  113. package/dist/mcp/auth/self-hosted/browser.d.ts +3 -0
  114. package/dist/mcp/auth/self-hosted/browser.js +273 -0
  115. package/dist/mcp/auth/self-hosted/cli.d.ts +2 -0
  116. package/dist/mcp/auth/self-hosted/cli.js +39 -0
  117. package/dist/mcp/auth/self-hosted/credentials.d.ts +58 -0
  118. package/dist/mcp/auth/self-hosted/credentials.js +106 -0
  119. package/dist/mcp/auth/self-hosted/rate-limit.d.ts +13 -0
  120. package/dist/mcp/auth/self-hosted/rate-limit.js +20 -0
  121. package/dist/mcp/auth/self-hosted/session.d.ts +36 -0
  122. package/dist/mcp/auth/self-hosted/session.js +110 -0
  123. package/dist/mcp/capabilities.d.ts +11 -0
  124. package/dist/mcp/capabilities.js +10 -0
  125. package/dist/mcp/context.d.ts +46 -0
  126. package/dist/mcp/context.js +1 -0
  127. package/dist/mcp/errors.d.ts +13 -0
  128. package/dist/mcp/errors.js +22 -0
  129. package/dist/mcp/index.d.ts +32 -0
  130. package/dist/mcp/index.js +187 -0
  131. package/dist/mcp/jsonrpc.d.ts +8 -0
  132. package/dist/mcp/jsonrpc.js +1 -0
  133. package/dist/mcp/server.d.ts +107 -0
  134. package/dist/mcp/server.js +203 -0
  135. package/dist/mcp/tasks/manager.d.ts +63 -0
  136. package/dist/mcp/tasks/manager.js +103 -0
  137. package/dist/mcp/tools/agents.d.ts +2 -0
  138. package/dist/mcp/tools/agents.js +375 -0
  139. package/dist/mcp/tools/budget-groups.d.ts +2 -0
  140. package/dist/mcp/tools/budget-groups.js +136 -0
  141. package/dist/mcp/tools/datastore.d.ts +2 -0
  142. package/dist/mcp/tools/datastore.js +217 -0
  143. package/dist/mcp/tools/memory.d.ts +3 -0
  144. package/dist/mcp/tools/memory.js +60 -0
  145. package/dist/mcp/tools/models.d.ts +8 -0
  146. package/dist/mcp/tools/models.js +15 -0
  147. package/dist/mcp/tools/runs.d.ts +2 -0
  148. package/dist/mcp/tools/runs.js +61 -0
  149. package/dist/mcp/tools/scheduling.d.ts +2 -0
  150. package/dist/mcp/tools/scheduling.js +48 -0
  151. package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -0
  152. package/dist/mcp/tools/secret-elicitation-form.js +93 -0
  153. package/dist/mcp/tools/secret-elicitation-server.d.ts +5 -0
  154. package/dist/mcp/tools/secret-elicitation-server.js +57 -0
  155. package/dist/mcp/tools/secret-elicitation.d.ts +38 -0
  156. package/dist/mcp/tools/secret-elicitation.js +52 -0
  157. package/dist/mcp/tools/secrets.d.ts +13 -0
  158. package/dist/mcp/tools/secrets.js +124 -0
  159. package/dist/mcp/tools/subagents.d.ts +2 -0
  160. package/dist/mcp/tools/subagents.js +133 -0
  161. package/dist/mcp/tools/text-result.d.ts +6 -0
  162. package/dist/mcp/tools/text-result.js +3 -0
  163. package/dist/mcp/tools/tools.d.ts +2 -0
  164. package/dist/mcp/tools/tools.js +211 -0
  165. package/dist/mcp/tools/trigger.d.ts +2 -0
  166. package/dist/mcp/tools/trigger.js +120 -0
  167. package/dist/mcp/tools/webhooks.d.ts +2 -0
  168. package/dist/mcp/tools/webhooks.js +34 -0
  169. package/dist/mcp/transport/http-limits.d.ts +17 -0
  170. package/dist/mcp/transport/http-limits.js +92 -0
  171. package/dist/mcp/transport/stdio.d.ts +23 -0
  172. package/dist/mcp/transport/stdio.js +6 -0
  173. package/dist/mcp/transport/streamable-http.d.ts +34 -0
  174. package/dist/mcp/transport/streamable-http.js +158 -0
  175. package/dist/mcp/unattended-schedules.d.ts +15 -0
  176. package/dist/mcp/unattended-schedules.js +21 -0
  177. package/dist/mcp/webhooks/ingress.d.ts +17 -0
  178. package/dist/mcp/webhooks/ingress.js +33 -0
  179. package/dist/observability/config.d.ts +6 -0
  180. package/dist/observability/config.js +20 -0
  181. package/dist/observability/metrics-server.d.ts +11 -0
  182. package/dist/observability/metrics-server.js +47 -0
  183. package/dist/observability/metrics.d.ts +36 -0
  184. package/dist/observability/metrics.js +158 -0
  185. package/dist/providers/auth/authorization-server.d.ts +41 -0
  186. package/dist/providers/auth/authorization-server.js +1 -0
  187. package/dist/providers/auth/delegating.d.ts +44 -0
  188. package/dist/providers/auth/delegating.js +105 -0
  189. package/dist/providers/auth/index.d.ts +11 -0
  190. package/dist/providers/auth/index.js +20 -0
  191. package/dist/providers/auth/release-gate.d.ts +1 -0
  192. package/dist/providers/auth/release-gate.js +4 -0
  193. package/dist/providers/auth/self-hosted.d.ts +78 -0
  194. package/dist/providers/auth/self-hosted.js +377 -0
  195. package/dist/providers/auth/subject.d.ts +1 -0
  196. package/dist/providers/auth/subject.js +6 -0
  197. package/dist/providers/auth/types.d.ts +63 -0
  198. package/dist/providers/auth/types.js +22 -0
  199. package/dist/providers/coding-proxy/deny-port.d.ts +5 -0
  200. package/dist/providers/coding-proxy/deny-port.js +45 -0
  201. package/dist/providers/coding-proxy/environment-credentials.d.ts +7 -0
  202. package/dist/providers/coding-proxy/environment-credentials.js +16 -0
  203. package/dist/providers/coding-proxy/index.d.ts +7 -0
  204. package/dist/providers/coding-proxy/index.js +7 -0
  205. package/dist/providers/coding-proxy/memory-ledger.d.ts +17 -0
  206. package/dist/providers/coding-proxy/memory-ledger.js +103 -0
  207. package/dist/providers/coding-proxy/metering.d.ts +20 -0
  208. package/dist/providers/coding-proxy/metering.js +161 -0
  209. package/dist/providers/coding-proxy/prisma-ledger.d.ts +15 -0
  210. package/dist/providers/coding-proxy/prisma-ledger.js +213 -0
  211. package/dist/providers/coding-proxy/proxy.d.ts +75 -0
  212. package/dist/providers/coding-proxy/proxy.js +721 -0
  213. package/dist/providers/coding-proxy/runtime.d.ts +18 -0
  214. package/dist/providers/coding-proxy/runtime.js +39 -0
  215. package/dist/providers/coding-proxy/secure-fetch.d.ts +9 -0
  216. package/dist/providers/coding-proxy/secure-fetch.js +72 -0
  217. package/dist/providers/coding-proxy/server.d.ts +17 -0
  218. package/dist/providers/coding-proxy/server.js +139 -0
  219. package/dist/providers/coding-proxy/types.d.ts +99 -0
  220. package/dist/providers/coding-proxy/types.js +1 -0
  221. package/dist/providers/datastore/index.d.ts +2 -0
  222. package/dist/providers/datastore/index.js +2 -0
  223. package/dist/providers/datastore/postgres.d.ts +20 -0
  224. package/dist/providers/datastore/postgres.js +130 -0
  225. package/dist/providers/datastore/scoped.d.ts +10 -0
  226. package/dist/providers/datastore/scoped.js +46 -0
  227. package/dist/providers/datastore/types.d.ts +31 -0
  228. package/dist/providers/datastore/types.js +7 -0
  229. package/dist/providers/email/types.d.ts +46 -0
  230. package/dist/providers/email/types.js +8 -0
  231. package/dist/providers/engine/index.d.ts +1 -0
  232. package/dist/providers/engine/index.js +1 -0
  233. package/dist/providers/engine/types.d.ts +67 -0
  234. package/dist/providers/engine/types.js +16 -0
  235. package/dist/providers/executor/build.d.ts +6 -0
  236. package/dist/providers/executor/build.js +15 -0
  237. package/dist/providers/executor/composition.d.ts +14 -0
  238. package/dist/providers/executor/composition.js +123 -0
  239. package/dist/providers/executor/container.d.ts +184 -0
  240. package/dist/providers/executor/container.js +936 -0
  241. package/dist/providers/executor/dbos-status.d.ts +50 -0
  242. package/dist/providers/executor/dbos-status.js +64 -0
  243. package/dist/providers/executor/dbos.d.ts +53 -0
  244. package/dist/providers/executor/dbos.js +269 -0
  245. package/dist/providers/executor/in-process.d.ts +18 -0
  246. package/dist/providers/executor/in-process.js +38 -0
  247. package/dist/providers/executor/index.d.ts +8 -0
  248. package/dist/providers/executor/index.js +8 -0
  249. package/dist/providers/executor/routing.d.ts +48 -0
  250. package/dist/providers/executor/routing.js +70 -0
  251. package/dist/providers/executor/types.d.ts +55 -0
  252. package/dist/providers/executor/types.js +1 -0
  253. package/dist/providers/index.d.ts +43 -0
  254. package/dist/providers/index.js +18 -0
  255. package/dist/providers/jobs/docker-isolation.d.ts +166 -0
  256. package/dist/providers/jobs/docker-isolation.js +724 -0
  257. package/dist/providers/jobs/docker.d.ts +127 -0
  258. package/dist/providers/jobs/docker.js +1027 -0
  259. package/dist/providers/jobs/fake-kubernetes-api.d.ts +98 -0
  260. package/dist/providers/jobs/fake-kubernetes-api.js +142 -0
  261. package/dist/providers/jobs/fake.d.ts +23 -0
  262. package/dist/providers/jobs/fake.js +132 -0
  263. package/dist/providers/jobs/kubernetes-api.d.ts +60 -0
  264. package/dist/providers/jobs/kubernetes-api.js +20 -0
  265. package/dist/providers/jobs/kubernetes-client.d.ts +36 -0
  266. package/dist/providers/jobs/kubernetes-client.js +171 -0
  267. package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +49 -0
  268. package/dist/providers/jobs/kubernetes-dry-run-fixture.js +141 -0
  269. package/dist/providers/jobs/kubernetes-isolation.d.ts +147 -0
  270. package/dist/providers/jobs/kubernetes-isolation.js +607 -0
  271. package/dist/providers/jobs/kubernetes-platform.d.ts +177 -0
  272. package/dist/providers/jobs/kubernetes-platform.js +280 -0
  273. package/dist/providers/jobs/kubernetes-preflight.d.ts +59 -0
  274. package/dist/providers/jobs/kubernetes-preflight.js +339 -0
  275. package/dist/providers/jobs/kubernetes-witness.d.ts +10 -0
  276. package/dist/providers/jobs/kubernetes-witness.js +193 -0
  277. package/dist/providers/jobs/kubernetes.d.ts +127 -0
  278. package/dist/providers/jobs/kubernetes.js +874 -0
  279. package/dist/providers/jobs/safe-extract.d.ts +9 -0
  280. package/dist/providers/jobs/safe-extract.js +325 -0
  281. package/dist/providers/jobs/types.d.ts +78 -0
  282. package/dist/providers/jobs/types.js +6 -0
  283. package/dist/providers/jobs/workspace-swap.d.ts +2 -0
  284. package/dist/providers/jobs/workspace-swap.js +47 -0
  285. package/dist/providers/llm/anthropic.d.ts +13 -0
  286. package/dist/providers/llm/anthropic.js +21 -0
  287. package/dist/providers/llm/bedrock.d.ts +19 -0
  288. package/dist/providers/llm/bedrock.js +33 -0
  289. package/dist/providers/llm/claude-messages.d.ts +106 -0
  290. package/dist/providers/llm/claude-messages.js +157 -0
  291. package/dist/providers/llm/claude-provider.d.ts +40 -0
  292. package/dist/providers/llm/claude-provider.js +31 -0
  293. package/dist/providers/llm/index.d.ts +10 -0
  294. package/dist/providers/llm/index.js +10 -0
  295. package/dist/providers/llm/openai.d.ts +23 -0
  296. package/dist/providers/llm/openai.js +156 -0
  297. package/dist/providers/llm/pricing-anthropic.d.ts +4 -0
  298. package/dist/providers/llm/pricing-anthropic.js +30 -0
  299. package/dist/providers/llm/pricing-bedrock-claude.d.ts +12 -0
  300. package/dist/providers/llm/pricing-bedrock-claude.js +37 -0
  301. package/dist/providers/llm/pricing-core.d.ts +26 -0
  302. package/dist/providers/llm/pricing-core.js +17 -0
  303. package/dist/providers/llm/pricing.d.ts +30 -0
  304. package/dist/providers/llm/pricing.js +74 -0
  305. package/dist/providers/llm/registration.d.ts +8 -0
  306. package/dist/providers/llm/registration.js +39 -0
  307. package/dist/providers/llm/routing.d.ts +25 -0
  308. package/dist/providers/llm/routing.js +32 -0
  309. package/dist/providers/llm/types.d.ts +85 -0
  310. package/dist/providers/llm/types.js +12 -0
  311. package/dist/providers/memory/index.d.ts +2 -0
  312. package/dist/providers/memory/index.js +2 -0
  313. package/dist/providers/memory/postgres.d.ts +20 -0
  314. package/dist/providers/memory/postgres.js +72 -0
  315. package/dist/providers/memory/types.d.ts +32 -0
  316. package/dist/providers/memory/types.js +17 -0
  317. package/dist/providers/secrets/app-key.d.ts +8 -0
  318. package/dist/providers/secrets/app-key.js +50 -0
  319. package/dist/providers/secrets/index.d.ts +10 -0
  320. package/dist/providers/secrets/index.js +13 -0
  321. package/dist/providers/secrets/transfer-envelope.d.ts +12 -0
  322. package/dist/providers/secrets/transfer-envelope.js +35 -0
  323. package/dist/providers/secrets/types.d.ts +18 -0
  324. package/dist/providers/secrets/types.js +12 -0
  325. package/dist/providers/storage/types.d.ts +18 -0
  326. package/dist/providers/storage/types.js +7 -0
  327. package/dist/providers/vcs/git.d.ts +105 -0
  328. package/dist/providers/vcs/git.js +710 -0
  329. package/dist/providers/vcs/github.d.ts +105 -0
  330. package/dist/providers/vcs/github.js +394 -0
  331. package/dist/providers/vcs/index.d.ts +6 -0
  332. package/dist/providers/vcs/index.js +26 -0
  333. package/dist/providers/vcs/types.d.ts +118 -0
  334. package/dist/providers/vcs/types.js +1 -0
  335. package/dist/sandbox/bounded-json.d.ts +2 -0
  336. package/dist/sandbox/bounded-json.js +36 -0
  337. package/dist/sandbox/bridge.d.ts +15 -0
  338. package/dist/sandbox/bridge.js +78 -0
  339. package/dist/sandbox/eval-core.d.ts +37 -0
  340. package/dist/sandbox/eval-core.js +159 -0
  341. package/dist/sandbox/fetch-policy.d.ts +24 -0
  342. package/dist/sandbox/fetch-policy.js +96 -0
  343. package/dist/sandbox/generated/zod-to-json-schema.bundle.js +5 -0
  344. package/dist/sandbox/generated/zod.bundle.js +1 -0
  345. package/dist/sandbox/host-functions.d.ts +29 -0
  346. package/dist/sandbox/host-functions.js +168 -0
  347. package/dist/sandbox/limits.d.ts +39 -0
  348. package/dist/sandbox/limits.js +39 -0
  349. package/dist/sandbox/parser-worker/__fixtures__/crash.d.ts +1 -0
  350. package/dist/sandbox/parser-worker/__fixtures__/crash.js +6 -0
  351. package/dist/sandbox/parser-worker/__fixtures__/echo.d.ts +1 -0
  352. package/dist/sandbox/parser-worker/__fixtures__/echo.js +5 -0
  353. package/dist/sandbox/parser-worker/__fixtures__/reject.d.ts +1 -0
  354. package/dist/sandbox/parser-worker/__fixtures__/reject.js +5 -0
  355. package/dist/sandbox/parser-worker/__fixtures__/spin-forever.d.ts +1 -0
  356. package/dist/sandbox/parser-worker/__fixtures__/spin-forever.js +11 -0
  357. package/dist/sandbox/parser-worker/pool.d.ts +13 -0
  358. package/dist/sandbox/parser-worker/pool.js +111 -0
  359. package/dist/sandbox/parser-worker/worker.d.ts +14 -0
  360. package/dist/sandbox/parser-worker/worker.js +63 -0
  361. package/dist/sandbox/pii-redaction.d.ts +1 -0
  362. package/dist/sandbox/pii-redaction.js +29 -0
  363. package/dist/sandbox/prelude.d.ts +26 -0
  364. package/dist/sandbox/prelude.js +408 -0
  365. package/dist/sandbox/quickjs-module.d.ts +13 -0
  366. package/dist/sandbox/quickjs-module.js +19 -0
  367. package/dist/sandbox/run-in-sandbox.d.ts +34 -0
  368. package/dist/sandbox/run-in-sandbox.js +53 -0
  369. package/dist/sandbox/safe-fetch.d.ts +26 -0
  370. package/dist/sandbox/safe-fetch.js +140 -0
  371. package/dist/sandbox/tool-capabilities.d.ts +66 -0
  372. package/dist/sandbox/tool-capabilities.js +103 -0
  373. package/dist/sandbox/zod-params.d.ts +22 -0
  374. package/dist/sandbox/zod-params.js +60 -0
  375. package/dist/serve.d.ts +14 -0
  376. package/dist/serve.js +54 -0
  377. package/dist/wardby-bin.d.ts +2 -0
  378. package/dist/wardby-bin.js +20 -0
  379. package/package.json +123 -0
  380. package/prisma/migrations/20260905000000_init/migration.sql +34 -0
  381. package/prisma/migrations/20260905010000_scheduler_durability/migration.sql +31 -0
  382. package/prisma/migrations/20260905020000_tools_multiturn/migration.sql +47 -0
  383. package/prisma/migrations/20260905030000_cache_tool_json_schema/migration.sql +13 -0
  384. package/prisma/migrations/20260906000000_mcp_phase4/migration.sql +115 -0
  385. package/prisma/migrations/20260906010000_run_final_text/migration.sql +7 -0
  386. package/prisma/migrations/20260906020000_run_turns/migration.sql +6 -0
  387. package/prisma/migrations/20260906030000_task_principal_id/migration.sql +5 -0
  388. package/prisma/migrations/20260906040000_secure_self_hosted_oauth/migration.sql +67 -0
  389. package/prisma/migrations/20260906050000_phase5_coding_agents/migration.sql +52 -0
  390. package/prisma/migrations/20260906060000_tool_capability_scoping/migration.sql +22 -0
  391. package/prisma/migrations/20260906070000_datastore_pii_flag/migration.sql +6 -0
  392. package/prisma/migrations/20260906080000_secret_elicitation_outcomes/migration.sql +13 -0
  393. package/prisma/migrations/20260907010000_coding_proxy_ledger/migration.sql +57 -0
  394. package/prisma/migrations/20260907020000_budget_groups/migration.sql +24 -0
  395. package/prisma/migrations/20260907030000_coding_webhook_task_override/migration.sql +4 -0
  396. package/prisma/migrations/20260908010000_run_execution_backend/migration.sql +2 -0
  397. package/prisma/migrations/20260908020000_coding_run_audit_metadata/migration.sql +7 -0
  398. package/prisma/migrations/20260910010000_agent_secret_bound_name/migration.sql +16 -0
  399. package/prisma/migrations/20260911010000_coding_worker_toolchains/migration.sql +5 -0
  400. package/prisma/migrations/20260912010000_coding_provider_contract/migration.sql +24 -0
  401. package/prisma/migrations/20260912020000_coding_proxy_protocol/migration.sql +10 -0
  402. package/prisma/migrations/20260912030000_agent_memory/migration.sql +18 -0
  403. package/prisma/migrations/20260912040000_named_shared_datastores/migration.sql +68 -0
  404. package/prisma/migrations/20260913000000_run_trigger_webhook/migration.sql +2 -0
  405. package/prisma/migrations/20260913010000_agent_sub_agent/migration.sql +32 -0
  406. package/prisma/migrations/20260913020000_run_task_override/migration.sql +5 -0
  407. package/prisma/migrations/20260913030000_coding_run_root/migration.sql +11 -0
  408. package/prisma/migrations/20260922010000_coding_run_queued_at/migration.sql +8 -0
  409. package/prisma/migrations/20260922020000_coding_workspace_disk/migration.sql +8 -0
  410. package/prisma/migrations/migration_lock.toml +3 -0
  411. package/prisma/schema.prisma +723 -0
  412. package/scripts/git-askpass.sh +11 -0
@@ -0,0 +1,18 @@
1
+ import type { PrismaClient } from "@prisma/client";
2
+ import { startDenyPortListener } from "./deny-port.js";
3
+ import { startCodingProxyServer, type CodingProxyServerHandle } from "./server.js";
4
+ import type { ProxyAuditSink } from "./types.js";
5
+ export interface CodingProxyRuntimeOptions {
6
+ db: PrismaClient;
7
+ env?: NodeJS.ProcessEnv;
8
+ startServer?: typeof startCodingProxyServer;
9
+ startDenyPort?: typeof startDenyPortListener;
10
+ audit?: ProxyAuditSink;
11
+ onRequest?: (event: {
12
+ protocol: "openai-responses" | "anthropic-messages" | "other";
13
+ status: number;
14
+ durationMs: number;
15
+ }) => void;
16
+ }
17
+ /** Starts the trusted proxy with the fixed worker-only Docker endpoint, plus the deny port the enforcement witness probes. */
18
+ export declare function startConfiguredCodingProxy(options: CodingProxyRuntimeOptions): Promise<CodingProxyServerHandle>;
@@ -0,0 +1,39 @@
1
+ import { CODING_PROXY_ALIAS, CODING_PROXY_DENY_PORT, CODING_PROXY_PORT } from "../jobs/docker-isolation.js";
2
+ import { startDenyPortListener } from "./deny-port.js";
3
+ import { EnvironmentCredentialResolver } from "./environment-credentials.js";
4
+ import { CodingProxy } from "./proxy.js";
5
+ import { PrismaProxyLedger } from "./prisma-ledger.js";
6
+ import { startCodingProxyServer } from "./server.js";
7
+ /** Starts the trusted proxy with the fixed worker-only Docker endpoint, plus the deny port the enforcement witness probes. */
8
+ export async function startConfiguredCodingProxy(options) {
9
+ const env = options.env ?? process.env;
10
+ const proxy = new CodingProxy({
11
+ ledger: new PrismaProxyLedger(options.db),
12
+ credentials: new EnvironmentCredentialResolver(env),
13
+ audit: options.audit,
14
+ });
15
+ const startServer = options.startServer ?? startCodingProxyServer;
16
+ const server = await startServer(proxy, {
17
+ host: "0.0.0.0",
18
+ port: CODING_PROXY_PORT,
19
+ expectedHost: `${CODING_PROXY_ALIAS}:${CODING_PROXY_PORT}`,
20
+ onRequest: options.onRequest,
21
+ });
22
+ let deny;
23
+ try {
24
+ deny = await (options.startDenyPort ?? startDenyPortListener)("0.0.0.0", CODING_PROXY_DENY_PORT);
25
+ }
26
+ catch (error) {
27
+ // A proxy without its witness must not stay up: the launcher's gate would never be able to
28
+ // prove a policy is enforced, and every launch would fail at the gate instead of at startup.
29
+ await server.close();
30
+ throw error;
31
+ }
32
+ return {
33
+ port: server.port,
34
+ close: async () => {
35
+ await deny.close();
36
+ await server.close();
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,9 @@
1
+ import { type Resolver } from "../../sandbox/fetch-policy.js";
2
+ import { requestPinned } from "../../sandbox/safe-fetch.js";
3
+ export declare const PROXY_EGRESS_ERROR = "proxy_egress_blocked";
4
+ export interface PinnedProxyFetchOptions {
5
+ allowedHosts: string[];
6
+ resolve?: Resolver;
7
+ connect?: typeof requestPinned;
8
+ }
9
+ export declare function createPinnedProxyFetch(options: PinnedProxyFetchOptions): typeof globalThis.fetch;
@@ -0,0 +1,72 @@
1
+ import { isIP } from "node:net";
2
+ import { Readable } from "node:stream";
3
+ import { FetchPolicyError, normalizeHost, resolveDestination } from "../../sandbox/fetch-policy.js";
4
+ import { requestPinned } from "../../sandbox/safe-fetch.js";
5
+ export const PROXY_EGRESS_ERROR = "proxy_egress_blocked";
6
+ function blocked(cause) {
7
+ return new Error(PROXY_EGRESS_ERROR, { cause });
8
+ }
9
+ function responseHeaders(message) {
10
+ const headers = new Headers();
11
+ for (const [name, value] of Object.entries(message.headers)) {
12
+ if (value === undefined)
13
+ continue;
14
+ if (Array.isArray(value))
15
+ for (const item of value)
16
+ headers.append(name, item);
17
+ else
18
+ headers.set(name, value);
19
+ }
20
+ return headers;
21
+ }
22
+ export function createPinnedProxyFetch(options) {
23
+ const allowedHosts = options.allowedHosts.map(normalizeHost);
24
+ if (allowedHosts.length === 0 || allowedHosts.some((host) => isIP(host) !== 0))
25
+ throw blocked();
26
+ return async (input, init = {}) => {
27
+ try {
28
+ if (typeof input !== "string" && !(input instanceof URL))
29
+ throw blocked();
30
+ const url = input.toString();
31
+ const parsed = new URL(url);
32
+ if (parsed.protocol !== "https:" || isIP(normalizeHost(parsed.hostname)) !== 0)
33
+ throw blocked();
34
+ if (init.redirect && init.redirect !== "error")
35
+ throw blocked();
36
+ if (init.body !== undefined && init.body !== null && typeof init.body !== "string")
37
+ throw blocked();
38
+ const destination = await resolveDestination(url, {
39
+ allowedHosts,
40
+ allowPrivateHosts: false,
41
+ restrictToAllowedHosts: true,
42
+ resolve: options.resolve,
43
+ });
44
+ const signal = init.signal ?? new AbortController().signal;
45
+ const headers = Object.fromEntries(new Headers(init.headers).entries());
46
+ const message = await (options.connect ?? requestPinned)(destination, { method: init.method, headers, body: init.body }, signal);
47
+ const status = message.statusCode ?? 0;
48
+ if (status >= 300 && status < 400) {
49
+ message.destroy();
50
+ throw blocked();
51
+ }
52
+ if (status < 100 || status > 599) {
53
+ message.destroy();
54
+ throw blocked();
55
+ }
56
+ const noBody = status === 204 || status === 205 || status === 304;
57
+ const body = noBody ? null : Readable.toWeb(message);
58
+ return new Response(body, {
59
+ status,
60
+ statusText: message.statusMessage,
61
+ headers: responseHeaders(message),
62
+ });
63
+ }
64
+ catch (error) {
65
+ if (error instanceof Error && error.message === PROXY_EGRESS_ERROR)
66
+ throw error;
67
+ if (error instanceof FetchPolicyError)
68
+ throw blocked(error);
69
+ throw error;
70
+ }
71
+ };
72
+ }
@@ -0,0 +1,17 @@
1
+ import { type CodingProxy } from "./proxy.js";
2
+ import type { ProxyProtocol } from "./types.js";
3
+ export interface CodingProxyServerConfig {
4
+ host: string;
5
+ port: number;
6
+ expectedHost?: string;
7
+ onRequest?: (event: {
8
+ protocol: ProxyProtocol | "other";
9
+ status: number;
10
+ durationMs: number;
11
+ }) => void;
12
+ }
13
+ export interface CodingProxyServerHandle {
14
+ port: number;
15
+ close(): Promise<void>;
16
+ }
17
+ export declare function startCodingProxyServer(proxy: CodingProxy, config: CodingProxyServerConfig): Promise<CodingProxyServerHandle>;
@@ -0,0 +1,139 @@
1
+ import { createServer } from "node:http";
2
+ import { HTTP_LIMITS, HttpBoundaryError, readBody } from "../../mcp/transport/http-limits.js";
3
+ import { logger } from "../../core/logger.js";
4
+ import { CodingProxyError, PROXY_MAX_BODY_BYTES } from "./proxy.js";
5
+ const proxyLog = logger.child({ module: "coding-proxy" });
6
+ function sendError(response, status, code) {
7
+ response
8
+ .writeHead(status, { "content-type": "application/json", "cache-control": "no-store" })
9
+ .end(JSON.stringify({ error: { type: code, message: code } }));
10
+ }
11
+ function bearer(value) {
12
+ if (!value?.startsWith("Bearer ") || value.indexOf(" ", 7) !== -1)
13
+ return "";
14
+ return value.slice(7);
15
+ }
16
+ function routeProtocol(method, url) {
17
+ if (method !== "POST")
18
+ return undefined;
19
+ if (url === "/v1/responses")
20
+ return "openai-responses";
21
+ if (url === "/v1/messages" || url === "/v1/messages?beta=true")
22
+ return "anthropic-messages";
23
+ return undefined;
24
+ }
25
+ function responseSink(response) {
26
+ return {
27
+ start(status, headers) {
28
+ if (!response.destroyed)
29
+ response.writeHead(status, headers);
30
+ },
31
+ write(chunk) {
32
+ if (response.destroyed)
33
+ return Promise.reject(new Error("client_disconnected"));
34
+ return new Promise((resolve, reject) => response.write(chunk, (error) => (error ? reject(error) : resolve())));
35
+ },
36
+ end() {
37
+ if (!response.destroyed)
38
+ response.end();
39
+ },
40
+ destroy() {
41
+ if (!response.destroyed)
42
+ response.destroy();
43
+ },
44
+ };
45
+ }
46
+ export async function startCodingProxyServer(proxy, config) {
47
+ const server = createServer({ maxHeaderSize: 16 * 1024, requireHostHeader: true }, (request, response) => {
48
+ const startedAt = performance.now();
49
+ const protocol = routeProtocol(request.method, request.url) ?? "other";
50
+ response.once("finish", () => {
51
+ try {
52
+ config.onRequest?.({ protocol, status: response.statusCode, durationMs: performance.now() - startedAt });
53
+ }
54
+ catch (error) {
55
+ proxyLog.warn({ event: "metrics.observe_failed", err: error }, "coding proxy metrics observer failed");
56
+ }
57
+ });
58
+ void (async () => {
59
+ if (config.expectedHost && request.headers.host !== config.expectedHost) {
60
+ sendError(response, 403, "invalid_host");
61
+ return;
62
+ }
63
+ if (request.method === "HEAD" && request.url === "/api/hello") {
64
+ response.writeHead(200, { "cache-control": "no-store" }).end();
65
+ return;
66
+ }
67
+ const protocol = routeProtocol(request.method, request.url);
68
+ if (!protocol) {
69
+ sendError(response, 404, "not_found");
70
+ return;
71
+ }
72
+ if (request.headers["content-type"]?.split(";", 1)[0].trim().toLowerCase() !== "application/json") {
73
+ sendError(response, 415, "unsupported_content_type");
74
+ return;
75
+ }
76
+ const controller = new AbortController();
77
+ const timer = setTimeout(() => controller.abort(), HTTP_LIMITS.requestMs);
78
+ let rawBody;
79
+ try {
80
+ rawBody = await readBody(request, PROXY_MAX_BODY_BYTES, controller.signal);
81
+ }
82
+ finally {
83
+ clearTimeout(timer);
84
+ }
85
+ // x-client-request-id is a tracing identifier that Codex may reuse across a multi-request tool loop.
86
+ const requestKeyHeader = request.headers["idempotency-key"];
87
+ const apiKeyHeader = request.headers["x-api-key"];
88
+ const anthropicBetaHeader = request.headers["anthropic-beta"];
89
+ const capability = protocol === "anthropic-messages"
90
+ ? request.headers.authorization === undefined && typeof apiKeyHeader === "string"
91
+ ? apiKeyHeader
92
+ : ""
93
+ : apiKeyHeader === undefined
94
+ ? bearer(request.headers.authorization)
95
+ : "";
96
+ await proxy.execute({
97
+ bearer: capability,
98
+ protocol,
99
+ rawBody,
100
+ requestKey: Array.isArray(requestKeyHeader) ? undefined : requestKeyHeader,
101
+ anthropicBeta: Array.isArray(anthropicBetaHeader) ? "" : anthropicBetaHeader,
102
+ }, responseSink(response));
103
+ })().catch((error) => {
104
+ if (response.headersSent) {
105
+ if (!response.destroyed)
106
+ response.destroy();
107
+ return;
108
+ }
109
+ if (error instanceof CodingProxyError || error instanceof HttpBoundaryError) {
110
+ proxyLog.info({ event: "request.rejected", code: error instanceof CodingProxyError ? error.code : error.message }, "coding proxy request rejected");
111
+ sendError(response, error.status, error instanceof CodingProxyError ? error.code : error.message);
112
+ return;
113
+ }
114
+ // The client (an untrusted worker) still gets only "internal_error";
115
+ // the operator needs the reason. This log is control-plane side and the
116
+ // serializer redacts token-shaped values -- without it an upstream
117
+ // failure is indistinguishable from any other 500 (cost a live smoke
118
+ // several blind runs).
119
+ proxyLog.error({ event: "request.failed", err: error }, "coding proxy request failed");
120
+ sendError(response, 500, "internal_error");
121
+ });
122
+ });
123
+ server.requestTimeout = HTTP_LIMITS.requestMs;
124
+ server.headersTimeout = HTTP_LIMITS.headersMs;
125
+ server.keepAliveTimeout = 5_000;
126
+ server.maxRequestsPerSocket = 100;
127
+ await new Promise((resolve, reject) => {
128
+ server.once("error", reject);
129
+ server.listen(config.port, config.host, resolve);
130
+ });
131
+ const address = server.address();
132
+ return {
133
+ port: typeof address === "object" && address ? address.port : config.port,
134
+ close: () => new Promise((resolve, reject) => {
135
+ server.close((error) => (error ? reject(error) : resolve()));
136
+ server.closeIdleConnections();
137
+ }),
138
+ };
139
+ }
@@ -0,0 +1,99 @@
1
+ import type { ModelPricing } from "../llm/pricing.js";
2
+ export type ProxySessionStatus = "active" | "cancelled";
3
+ export type ProxyRequestStatus = "reserved" | "completed" | "released" | "uncertain";
4
+ export type ProxyProtocol = "openai-responses" | "anthropic-messages";
5
+ export interface PricingSnapshot extends ModelPricing {
6
+ version: string;
7
+ }
8
+ export interface ProxyUsage {
9
+ inputTokens: number;
10
+ outputTokens: number;
11
+ cachedInputTokens: number;
12
+ cacheWriteTokens: number;
13
+ reasoningTokens: number;
14
+ }
15
+ export interface ProxySession {
16
+ id: string;
17
+ runId: string;
18
+ capabilityHash: string;
19
+ credentialRef: string;
20
+ protocol: ProxyProtocol;
21
+ allowedModels: string[];
22
+ deadlineAt: Date;
23
+ budgetUsd: number;
24
+ status: ProxySessionStatus;
25
+ }
26
+ export interface ProxyRequest {
27
+ id: string;
28
+ sessionId: string;
29
+ requestKey: string;
30
+ requestFingerprint: string;
31
+ model: string;
32
+ status: ProxyRequestStatus;
33
+ reservationUsd: number;
34
+ actualCostUsd?: number;
35
+ pricing: PricingSnapshot;
36
+ usage?: ProxyUsage;
37
+ upstreamStatus?: number;
38
+ }
39
+ export interface CreateProxySessionInput {
40
+ id: string;
41
+ runId: string;
42
+ capabilityHash: string;
43
+ credentialRef: string;
44
+ protocol: ProxyProtocol;
45
+ allowedModels: string[];
46
+ deadlineAt: Date;
47
+ budgetUsd: number;
48
+ }
49
+ export interface ReserveProxyRequestInput {
50
+ id: string;
51
+ sessionId: string;
52
+ requestKey: string;
53
+ requestFingerprint: string;
54
+ model: string;
55
+ reservationUsd: number;
56
+ pricing: PricingSnapshot;
57
+ now: Date;
58
+ }
59
+ export type ReserveProxyRequestResult = {
60
+ outcome: "reserved";
61
+ request: ProxyRequest;
62
+ } | {
63
+ outcome: "duplicate";
64
+ request: ProxyRequest;
65
+ } | {
66
+ outcome: "budget_exhausted";
67
+ } | {
68
+ outcome: "inactive";
69
+ reason: "cancelled" | "expired";
70
+ };
71
+ export interface ProxyLedger {
72
+ createSession(input: CreateProxySessionInput): Promise<void>;
73
+ findSessionByCapabilityHash(capabilityHash: string): Promise<ProxySession | null>;
74
+ reserve(input: ReserveProxyRequestInput): Promise<ReserveProxyRequestResult>;
75
+ complete(requestId: string, usage: ProxyUsage, actualCostUsd: number, upstreamStatus: number): Promise<ProxyRequest>;
76
+ release(requestId: string, upstreamStatus: number): Promise<void>;
77
+ markUncertain(requestId: string, upstreamStatus?: number): Promise<void>;
78
+ cancelSession(sessionId: string): Promise<void>;
79
+ getRequest(requestId: string): Promise<ProxyRequest | null>;
80
+ }
81
+ export interface ProxyAuditEvent {
82
+ type: "session.created" | "session.cancelled" | "request.reserved" | "request.rejected" | "request.released" | "request.uncertain" | "response.completed";
83
+ runId: string;
84
+ requestId?: string;
85
+ model?: string;
86
+ status?: number;
87
+ reason?: string;
88
+ reservationUsd?: number;
89
+ costUsd?: number;
90
+ inputTokens?: number;
91
+ outputTokens?: number;
92
+ cachedInputTokens?: number;
93
+ cacheWriteTokens?: number;
94
+ reasoningTokens?: number;
95
+ }
96
+ export type ProxyAuditSink = (event: ProxyAuditEvent) => void;
97
+ export interface CredentialResolver {
98
+ resolve(reference: string): Promise<string>;
99
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export * from "./types.js";
2
+ export * from "./postgres.js";
@@ -0,0 +1,2 @@
1
+ export * from "./types.js";
2
+ export * from "./postgres.js";
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Postgres-backed default Datastore adapter, via the `DatastoreEntry` table.
3
+ */
4
+ import type { PrismaClient } from "@prisma/client";
5
+ import type { Datastore, DatastoreSetOptions, DatastoreValue } from "./types.js";
6
+ import type { SecretCipher } from "../secrets/types.js";
7
+ export type DatastoreDb = Pick<PrismaClient, "datastoreEntry" | "datastore" | "$queryRaw">;
8
+ export declare class PostgresDatastore implements Datastore {
9
+ private readonly db;
10
+ private readonly cipher?;
11
+ constructor(db: DatastoreDb, cipher?: SecretCipher | undefined);
12
+ get(agentId: string, key: string): Promise<DatastoreValue | undefined>;
13
+ set(agentId: string, key: string, value: DatastoreValue, opts?: DatastoreSetOptions): Promise<void>;
14
+ delete(agentId: string, key: string): Promise<void>;
15
+ list(agentId: string, prefix?: string): Promise<string[]>;
16
+ getShared(datastoreId: string, key: string): Promise<DatastoreValue | undefined>;
17
+ setShared(datastoreId: string, key: string, value: DatastoreValue, opts?: DatastoreSetOptions): Promise<void>;
18
+ deleteShared(datastoreId: string, key: string): Promise<void>;
19
+ listShared(datastoreId: string, prefix?: string): Promise<string[]>;
20
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Postgres-backed default Datastore adapter, via the `DatastoreEntry` table.
3
+ */
4
+ import { boundedJson, boundedString } from "../../sandbox/bounded-json.js";
5
+ import { BRIDGE_INPUT_BYTES } from "../../sandbox/limits.js";
6
+ /**
7
+ * A `pii: true` value is stored as a base64url cipher blob, which runs
8
+ * ~33% larger than its plaintext plus a small fixed iv/tag overhead. Capping
9
+ * the plaintext here (rather than at BRIDGE_INPUT_BYTES) guarantees the
10
+ * encrypted blob still clears the read-side BRIDGE_INPUT_BYTES guard below —
11
+ * a plaintext allowed right up to that guard would otherwise encrypt into an
12
+ * unreadable row.
13
+ */
14
+ const PII_PLAINTEXT_BYTES = 750_000;
15
+ export class PostgresDatastore {
16
+ db;
17
+ cipher;
18
+ constructor(db, cipher) {
19
+ this.db = db;
20
+ this.cipher = cipher;
21
+ }
22
+ async get(agentId, key) {
23
+ boundedString(key, 1024);
24
+ const rows = await this.db.$queryRaw `
25
+ SELECT CASE WHEN octet_length("value"::text) <= ${BRIDGE_INPUT_BYTES} THEN "value" ELSE NULL END AS "value",
26
+ octet_length("value"::text) > ${BRIDGE_INPUT_BYTES} AS "oversized",
27
+ "pii" AS "pii"
28
+ FROM "DatastoreEntry" WHERE "agentId" = ${agentId} AND "key" = ${key}`;
29
+ const row = rows[0];
30
+ if (!row)
31
+ return undefined;
32
+ if (row.oversized)
33
+ throw new Error("datastore_value_limit");
34
+ if (!row.pii)
35
+ return row.value;
36
+ if (!this.cipher)
37
+ throw new Error("datastore_pii_cipher_unavailable");
38
+ const plaintext = await this.cipher.decrypt(row.value);
39
+ return JSON.parse(plaintext);
40
+ }
41
+ async set(agentId, key, value, opts) {
42
+ boundedString(key, 1024);
43
+ if (!opts?.pii) {
44
+ boundedJson(value, BRIDGE_INPUT_BYTES);
45
+ await this.db.datastoreEntry.upsert({
46
+ where: { agentId_key: { agentId, key } },
47
+ create: { agentId, key, value: value, pii: false, keyId: null },
48
+ update: { value: value, pii: false, keyId: null },
49
+ });
50
+ return;
51
+ }
52
+ if (!this.cipher)
53
+ throw new Error("datastore_pii_cipher_unavailable");
54
+ const plaintext = boundedJson(value, PII_PLAINTEXT_BYTES);
55
+ const ciphertext = await this.cipher.encrypt(plaintext);
56
+ const keyId = this.cipher.keyId();
57
+ await this.db.datastoreEntry.upsert({
58
+ where: { agentId_key: { agentId, key } },
59
+ create: { agentId, key, value: ciphertext, pii: true, keyId },
60
+ update: { value: ciphertext, pii: true, keyId },
61
+ });
62
+ }
63
+ async delete(agentId, key) {
64
+ await this.db.datastoreEntry.deleteMany({ where: { agentId, key } });
65
+ }
66
+ async list(agentId, prefix) {
67
+ boundedString(prefix ?? "", 1024);
68
+ const entries = await this.db.$queryRaw `
69
+ SELECT CASE WHEN octet_length("key") <= 1024 THEN "key" ELSE NULL END AS "key"
70
+ FROM "DatastoreEntry" WHERE "agentId" = ${agentId} AND starts_with("key", ${prefix ?? ""})
71
+ ORDER BY "key" LIMIT 1001`;
72
+ if (entries.length > 1000 || entries.some((e) => e.key === null))
73
+ throw new Error("datastore_list_limit");
74
+ return entries.map((e) => e.key);
75
+ }
76
+ async getShared(datastoreId, key) {
77
+ boundedString(key, 1024);
78
+ const rows = await this.db.$queryRaw `
79
+ SELECT CASE WHEN octet_length("value"::text) <= ${BRIDGE_INPUT_BYTES} THEN "value" ELSE NULL END AS "value",
80
+ octet_length("value"::text) > ${BRIDGE_INPUT_BYTES} AS "oversized",
81
+ "pii" AS "pii"
82
+ FROM "DatastoreEntry" WHERE "datastoreId" = ${datastoreId} AND "key" = ${key}`;
83
+ const row = rows[0];
84
+ if (!row)
85
+ return undefined;
86
+ if (row.oversized)
87
+ throw new Error("datastore_value_limit");
88
+ if (!row.pii)
89
+ return row.value;
90
+ if (!this.cipher)
91
+ throw new Error("datastore_pii_cipher_unavailable");
92
+ const plaintext = await this.cipher.decrypt(row.value);
93
+ return JSON.parse(plaintext);
94
+ }
95
+ async setShared(datastoreId, key, value, opts) {
96
+ boundedString(key, 1024);
97
+ if (!opts?.pii) {
98
+ boundedJson(value, BRIDGE_INPUT_BYTES);
99
+ await this.db.datastoreEntry.upsert({
100
+ where: { datastoreId_key: { datastoreId, key } },
101
+ create: { datastoreId, key, value: value, pii: false, keyId: null },
102
+ update: { value: value, pii: false, keyId: null },
103
+ });
104
+ return;
105
+ }
106
+ if (!this.cipher)
107
+ throw new Error("datastore_pii_cipher_unavailable");
108
+ const plaintext = boundedJson(value, PII_PLAINTEXT_BYTES);
109
+ const ciphertext = await this.cipher.encrypt(plaintext);
110
+ const keyId = this.cipher.keyId();
111
+ await this.db.datastoreEntry.upsert({
112
+ where: { datastoreId_key: { datastoreId, key } },
113
+ create: { datastoreId, key, value: ciphertext, pii: true, keyId },
114
+ update: { value: ciphertext, pii: true, keyId },
115
+ });
116
+ }
117
+ async deleteShared(datastoreId, key) {
118
+ await this.db.datastoreEntry.deleteMany({ where: { datastoreId, key } });
119
+ }
120
+ async listShared(datastoreId, prefix) {
121
+ boundedString(prefix ?? "", 1024);
122
+ const entries = await this.db.$queryRaw `
123
+ SELECT CASE WHEN octet_length("key") <= 1024 THEN "key" ELSE NULL END AS "key"
124
+ FROM "DatastoreEntry" WHERE "datastoreId" = ${datastoreId} AND starts_with("key", ${prefix ?? ""})
125
+ ORDER BY "key" LIMIT 1001`;
126
+ if (entries.length > 1000 || entries.some((e) => e.key === null))
127
+ throw new Error("datastore_list_limit");
128
+ return entries.map((e) => e.key);
129
+ }
130
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Wraps a `Datastore` so every operation is confined to keys under one of
3
+ * the tool attachment's declared prefixes (OWASP LLM08 — Excessive Agency
4
+ * fix). Reads/lists on a disallowed key behave like the key doesn't exist
5
+ * (consistent with a plain cache-miss); a write outside every allowed
6
+ * prefix throws loudly instead — a tool author needs to know their write
7
+ * was rejected, not silently believe it landed.
8
+ */
9
+ import type { Datastore } from "./types.js";
10
+ export declare function scopeDatastore(datastore: Datastore, allowedPrefixes: readonly string[]): Datastore;
@@ -0,0 +1,46 @@
1
+ function isAllowed(key, allowedPrefixes) {
2
+ return allowedPrefixes.some((prefix) => key.startsWith(prefix));
3
+ }
4
+ export function scopeDatastore(datastore, allowedPrefixes) {
5
+ return {
6
+ async get(agentId, key) {
7
+ if (!isAllowed(key, allowedPrefixes))
8
+ return undefined;
9
+ return datastore.get(agentId, key);
10
+ },
11
+ async set(agentId, key, value, opts) {
12
+ if (!isAllowed(key, allowedPrefixes))
13
+ throw new Error("datastore_prefix_not_allowed");
14
+ await datastore.set(agentId, key, value, opts);
15
+ },
16
+ async delete(agentId, key) {
17
+ if (!isAllowed(key, allowedPrefixes))
18
+ return;
19
+ await datastore.delete(agentId, key);
20
+ },
21
+ async list(agentId, prefix) {
22
+ const keys = await datastore.list(agentId, prefix);
23
+ return keys.filter((key) => isAllowed(key, allowedPrefixes));
24
+ },
25
+ // Shared-store access has its own dedicated scoping (scopeSharedDatastoreAccessor,
26
+ // src/core/datastores.ts) applied separately to the unscoped provider — these four
27
+ // methods are unreachable through a scopeDatastore(...)-wrapped object in this
28
+ // codebase's call graph. This wrapper is the security boundary for the
29
+ // private per-agent store, though, so rather than quietly pass an
30
+ // unreachable-today call straight through to the unscoped datastore (a
31
+ // hole waiting to happen if that ever changes), every shared-store
32
+ // method throws — same fail-closed posture as an unscoped write above.
33
+ async getShared() {
34
+ throw new Error("datastore_shared_access_not_allowed");
35
+ },
36
+ async setShared() {
37
+ throw new Error("datastore_shared_access_not_allowed");
38
+ },
39
+ async deleteShared() {
40
+ throw new Error("datastore_shared_access_not_allowed");
41
+ },
42
+ async listShared() {
43
+ throw new Error("datastore_shared_access_not_allowed");
44
+ },
45
+ };
46
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Datastore seam — key/value state scoped per agent, exposed to sandboxed
3
+ * tools as `datastore.*`. The one new seam Phase 3 introduces (Medium host
4
+ * API surface); a cloud KV can swap in later via the same provider-config
5
+ * pattern as the other seams.
6
+ */
7
+ export type DatastoreValue = null | boolean | number | string | DatastoreValue[] | {
8
+ [key: string]: DatastoreValue;
9
+ };
10
+ export interface DatastoreSetOptions {
11
+ /**
12
+ * Opt-in, never inferred: encrypts the value at rest (AES-256-GCM, same
13
+ * cipher as Secret) instead of storing it as plain JSON. Costs an
14
+ * encrypt/decrypt round trip on every set/get of that key, so reserve it
15
+ * for values that actually carry PII.
16
+ */
17
+ pii?: boolean;
18
+ }
19
+ export interface Datastore {
20
+ get(agentId: string, key: string): Promise<DatastoreValue | undefined>;
21
+ set(agentId: string, key: string, value: DatastoreValue, opts?: DatastoreSetOptions): Promise<void>;
22
+ delete(agentId: string, key: string): Promise<void>;
23
+ /** Lists keys (not values) under an optional prefix, scoped to the agent. */
24
+ list(agentId: string, prefix?: string): Promise<string[]>;
25
+ /** Sibling methods for a named shared store (see Datastore/AgentDatastore
26
+ * in schema.prisma), keyed by datastoreId instead of agentId. */
27
+ getShared(datastoreId: string, key: string): Promise<DatastoreValue | undefined>;
28
+ setShared(datastoreId: string, key: string, value: DatastoreValue, opts?: DatastoreSetOptions): Promise<void>;
29
+ deleteShared(datastoreId: string, key: string): Promise<void>;
30
+ listShared(datastoreId: string, prefix?: string): Promise<string[]>;
31
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Datastore seam — key/value state scoped per agent, exposed to sandboxed
3
+ * tools as `datastore.*`. The one new seam Phase 3 introduces (Medium host
4
+ * API surface); a cloud KV can swap in later via the same provider-config
5
+ * pattern as the other seams.
6
+ */
7
+ export {};