@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,217 @@
1
+ import { Prisma } from "@prisma/client";
2
+ import { requireOwnedAgent, requireReadableAgent, requireOwnedDatastore, canRead, assertCanMutate, } from "../auth/ownership.js";
3
+ import { createDatastore, listDatastores, deleteDatastore, attachDatastore, detachDatastore, buildSharedDatastoreAccessor, } from "../../core/datastores.js";
4
+ import { McpError } from "../errors.js";
5
+ import { textResult } from "./text-result.js";
6
+ /**
7
+ * A `boundName` names an *attachment* (agentId, boundName) -> Datastore.
8
+ * requireOwnedAgent/requireReadableAgent only check the agent's own
9
+ * ownership — for a public (null-owner) agent that's readable/mutable by
10
+ * anyone, so gating solely on the agent would let a principal who attaches
11
+ * their own private Datastore to a public agent inadvertently expose that
12
+ * Datastore's full read/write access to every other principal, with no
13
+ * sandbox mediation. So every boundName branch additionally checks the
14
+ * underlying Datastore's own ownership. When no attachment exists under
15
+ * that boundName, this resolves to `undefined` and callers fall through to
16
+ * today's unchanged behavior (miss for reads, throw for writes) — this
17
+ * check only ever narrows access when an attachment IS found, it never
18
+ * grants access an attachment didn't already imply.
19
+ */
20
+ async function findAttachedDatastore(db, agentId, boundName) {
21
+ const attachment = await db.agentDatastore.findFirst({ where: { agentId, boundName }, include: { datastore: true } });
22
+ return attachment?.datastore;
23
+ }
24
+ async function requireReadableSharedDatastore(db, agentId, boundName, principalId) {
25
+ const datastore = await findAttachedDatastore(db, agentId, boundName);
26
+ if (!datastore)
27
+ return;
28
+ if (!canRead(datastore.ownerId, principalId)) {
29
+ throw new McpError(404, `Datastore bound as "${boundName}" not found.`);
30
+ }
31
+ }
32
+ async function requireMutableSharedDatastore(db, agentId, boundName, principalId) {
33
+ const datastore = await findAttachedDatastore(db, agentId, boundName);
34
+ if (!datastore)
35
+ return;
36
+ assertCanMutate(datastore.ownerId, principalId, `Datastore bound as "${boundName}" is not owned by the caller.`);
37
+ }
38
+ export function registerDatastoreTools(mcp) {
39
+ mcp.registerTool({
40
+ name: "create_datastore",
41
+ scope: "datastore:write",
42
+ inputSchema: {
43
+ type: "object",
44
+ properties: { name: { type: "string" } },
45
+ required: ["name"],
46
+ },
47
+ handler: async (args, ctx) => {
48
+ try {
49
+ const datastore = await createDatastore(args.name, ctx.principal.id, ctx.db);
50
+ return textResult({ id: datastore.id, name: datastore.name, createdAt: datastore.createdAt });
51
+ }
52
+ catch (err) {
53
+ if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
54
+ throw new McpError(409, `A datastore named "${args.name}" already exists.`);
55
+ }
56
+ throw err;
57
+ }
58
+ },
59
+ });
60
+ mcp.registerTool({
61
+ name: "list_datastores",
62
+ scope: "datastore:write",
63
+ inputSchema: { type: "object", properties: {} },
64
+ handler: async (_args, ctx) => {
65
+ const datastores = await listDatastores(ctx.principal.id, ctx.db);
66
+ return textResult(datastores);
67
+ },
68
+ });
69
+ mcp.registerTool({
70
+ name: "delete_datastore",
71
+ scope: "datastore:write",
72
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
73
+ handler: async (args, ctx) => {
74
+ await requireOwnedDatastore(ctx.db, args.id, ctx.principal.id);
75
+ try {
76
+ await deleteDatastore(args.id, ctx.db);
77
+ }
78
+ catch (err) {
79
+ if (err instanceof Prisma.PrismaClientKnownRequestError && (err.code === "P2003" || err.code === "P2014")) {
80
+ throw new McpError(409, "Datastore still has attached agents or entries — detach/clear it first.");
81
+ }
82
+ throw err;
83
+ }
84
+ return textResult({ deleted: args.id });
85
+ },
86
+ });
87
+ mcp.registerTool({
88
+ name: "attach_datastore",
89
+ scope: "datastore:write",
90
+ inputSchema: {
91
+ type: "object",
92
+ properties: {
93
+ agentId: { type: "string" },
94
+ datastoreId: { type: "string" },
95
+ boundName: { type: "string", minLength: 1 },
96
+ },
97
+ required: ["agentId", "datastoreId"],
98
+ },
99
+ handler: async (args, ctx) => {
100
+ await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
101
+ const datastore = await requireOwnedDatastore(ctx.db, args.datastoreId, ctx.principal.id);
102
+ const boundName = args.boundName ?? datastore.name;
103
+ try {
104
+ await attachDatastore(args.agentId, args.datastoreId, ctx.db, boundName);
105
+ }
106
+ catch (err) {
107
+ if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
108
+ throw new McpError(409, `Agent already binds a datastore under name "${boundName}".`);
109
+ }
110
+ throw err;
111
+ }
112
+ return textResult({ attached: true });
113
+ },
114
+ });
115
+ mcp.registerTool({
116
+ name: "detach_datastore",
117
+ scope: "datastore:write",
118
+ inputSchema: {
119
+ type: "object",
120
+ properties: { agentId: { type: "string" }, boundName: { type: "string" } },
121
+ required: ["agentId", "boundName"],
122
+ },
123
+ handler: async (args, ctx) => {
124
+ await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
125
+ await requireMutableSharedDatastore(ctx.db, args.agentId, args.boundName, ctx.principal.id);
126
+ await detachDatastore(args.agentId, args.boundName, ctx.db);
127
+ return textResult({ detached: true });
128
+ },
129
+ });
130
+ mcp.registerTool({
131
+ name: "datastore_get",
132
+ scope: "agents:read",
133
+ inputSchema: {
134
+ type: "object",
135
+ properties: { agentId: { type: "string" }, key: { type: "string" }, boundName: { type: "string" } },
136
+ required: ["agentId", "key"],
137
+ },
138
+ handler: async (args, ctx) => {
139
+ await requireReadableAgent(ctx.db, args.agentId, ctx.principal.id);
140
+ if (args.boundName) {
141
+ await requireReadableSharedDatastore(ctx.db, args.agentId, args.boundName, ctx.principal.id);
142
+ const accessor = buildSharedDatastoreAccessor(args.agentId, ctx.providers.datastore, ctx.db);
143
+ const value = await accessor.get(args.boundName, args.key);
144
+ return textResult({ value: value ?? null });
145
+ }
146
+ const value = await ctx.providers.datastore.get(args.agentId, args.key);
147
+ return textResult({ value: value ?? null });
148
+ },
149
+ });
150
+ mcp.registerTool({
151
+ name: "datastore_list",
152
+ scope: "agents:read",
153
+ inputSchema: {
154
+ type: "object",
155
+ properties: { agentId: { type: "string" }, prefix: { type: "string" }, boundName: { type: "string" } },
156
+ required: ["agentId"],
157
+ },
158
+ handler: async (args, ctx) => {
159
+ await requireReadableAgent(ctx.db, args.agentId, ctx.principal.id);
160
+ if (args.boundName) {
161
+ await requireReadableSharedDatastore(ctx.db, args.agentId, args.boundName, ctx.principal.id);
162
+ const accessor = buildSharedDatastoreAccessor(args.agentId, ctx.providers.datastore, ctx.db);
163
+ const keys = await accessor.list(args.boundName, args.prefix);
164
+ return textResult(keys);
165
+ }
166
+ const keys = await ctx.providers.datastore.list(args.agentId, args.prefix);
167
+ return textResult(keys);
168
+ },
169
+ });
170
+ mcp.registerTool({
171
+ name: "datastore_set",
172
+ scope: "datastore:write",
173
+ description: 'Sets a datastore value. Pass "boundName" to write to a shared datastore attached under that name instead of the agent\'s private one. Pass "pii": true to encrypt the value at rest (AES-256-GCM) — opt in only for values that actually carry PII, never inferred automatically.',
174
+ inputSchema: {
175
+ type: "object",
176
+ properties: {
177
+ agentId: { type: "string" },
178
+ key: { type: "string" },
179
+ value: {},
180
+ pii: { type: "boolean" },
181
+ boundName: { type: "string" },
182
+ },
183
+ required: ["agentId", "key", "value"],
184
+ },
185
+ handler: async (args, ctx) => {
186
+ await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
187
+ if (args.boundName) {
188
+ await requireMutableSharedDatastore(ctx.db, args.agentId, args.boundName, ctx.principal.id);
189
+ const accessor = buildSharedDatastoreAccessor(args.agentId, ctx.providers.datastore, ctx.db);
190
+ await accessor.set(args.boundName, args.key, args.value, { pii: args.pii });
191
+ return textResult({ ok: true });
192
+ }
193
+ await ctx.providers.datastore.set(args.agentId, args.key, args.value, { pii: args.pii });
194
+ return textResult({ ok: true });
195
+ },
196
+ });
197
+ mcp.registerTool({
198
+ name: "datastore_delete",
199
+ scope: "datastore:write",
200
+ inputSchema: {
201
+ type: "object",
202
+ properties: { agentId: { type: "string" }, key: { type: "string" }, boundName: { type: "string" } },
203
+ required: ["agentId", "key"],
204
+ },
205
+ handler: async (args, ctx) => {
206
+ await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
207
+ if (args.boundName) {
208
+ await requireMutableSharedDatastore(ctx.db, args.agentId, args.boundName, ctx.principal.id);
209
+ const accessor = buildSharedDatastoreAccessor(args.agentId, ctx.providers.datastore, ctx.db);
210
+ await accessor.delete(args.boundName, args.key);
211
+ return textResult({ ok: true });
212
+ }
213
+ await ctx.providers.datastore.delete(args.agentId, args.key);
214
+ return textResult({ ok: true });
215
+ },
216
+ });
217
+ }
@@ -0,0 +1,3 @@
1
+ /** Operator inspection of agent memory, mirroring the Datastore MCP tools (`./datastore.ts`). */
2
+ import type { WardbyMcpServer } from "../server.js";
3
+ export declare function registerMemoryTools(mcp: WardbyMcpServer): void;
@@ -0,0 +1,60 @@
1
+ import { requireOwnedAgent, requireReadableAgent } from "../auth/ownership.js";
2
+ import { textResult } from "./text-result.js";
3
+ export function registerMemoryTools(mcp) {
4
+ mcp.registerTool({
5
+ name: "get_agent_memory",
6
+ scope: "agents:read",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: { agentId: { type: "string" }, key: { type: "string" } },
10
+ required: ["agentId", "key"],
11
+ },
12
+ handler: async (args, ctx) => {
13
+ await requireReadableAgent(ctx.db, args.agentId, ctx.principal.id);
14
+ const content = await ctx.providers.memory.get(args.agentId, args.key);
15
+ return textResult({ content: content ?? null });
16
+ },
17
+ });
18
+ mcp.registerTool({
19
+ name: "list_agent_memory",
20
+ scope: "agents:read",
21
+ inputSchema: {
22
+ type: "object",
23
+ properties: { agentId: { type: "string" } },
24
+ required: ["agentId"],
25
+ },
26
+ handler: async (args, ctx) => {
27
+ await requireReadableAgent(ctx.db, args.agentId, ctx.principal.id);
28
+ const keys = await ctx.providers.memory.list(args.agentId);
29
+ return textResult(keys);
30
+ },
31
+ });
32
+ mcp.registerTool({
33
+ name: "set_agent_memory",
34
+ scope: "memory:write",
35
+ inputSchema: {
36
+ type: "object",
37
+ properties: { agentId: { type: "string" }, key: { type: "string" }, content: { type: "string" } },
38
+ required: ["agentId", "key", "content"],
39
+ },
40
+ handler: async (args, ctx) => {
41
+ await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
42
+ await ctx.providers.memory.set(args.agentId, args.key, args.content);
43
+ return textResult({ ok: true });
44
+ },
45
+ });
46
+ mcp.registerTool({
47
+ name: "delete_agent_memory",
48
+ scope: "memory:write",
49
+ inputSchema: {
50
+ type: "object",
51
+ properties: { agentId: { type: "string" }, key: { type: "string" } },
52
+ required: ["agentId", "key"],
53
+ },
54
+ handler: async (args, ctx) => {
55
+ await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
56
+ await ctx.providers.memory.delete(args.agentId, args.key);
57
+ return textResult({ ok: true });
58
+ },
59
+ });
60
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Read-only lookup for which model IDs the deployment actually has an LLM
3
+ * provider registered for — without it, the only way to learn a valid
4
+ * model name was to trigger a run and read the "Known models" list off a
5
+ * failed run's error message.
6
+ */
7
+ import type { WardbyMcpServer } from "../server.js";
8
+ export declare function registerModelTools(mcp: WardbyMcpServer): void;
@@ -0,0 +1,15 @@
1
+ import { textResult } from "./text-result.js";
2
+ export function registerModelTools(mcp) {
3
+ mcp.registerTool({
4
+ name: "list_models",
5
+ scope: "agents:read",
6
+ inputSchema: { type: "object", properties: {} },
7
+ handler: async (_args, ctx) => {
8
+ // Production always wires ctx.providers.llm as a RoutingLlmProvider
9
+ // (see buildMcpProviders in mcp/index.ts) — listModels() is specific
10
+ // to that composite router, not part of the single-provider LlmProvider seam.
11
+ const llm = ctx.providers.llm;
12
+ return textResult({ models: llm.listModels() });
13
+ },
14
+ });
15
+ }
@@ -0,0 +1,2 @@
1
+ import type { WardbyMcpServer } from "../server.js";
2
+ export declare function registerRunTools(mcp: WardbyMcpServer): void;
@@ -0,0 +1,61 @@
1
+ import { McpError } from "../errors.js";
2
+ import { requireReadableAgent } from "../auth/ownership.js";
3
+ import { publicCodingRunResult } from "../../coding/protocol.js";
4
+ import { textResult } from "./text-result.js";
5
+ export function registerRunTools(mcp) {
6
+ mcp.registerTool({
7
+ name: "list_runs",
8
+ scope: "agents:read",
9
+ inputSchema: {
10
+ type: "object",
11
+ properties: { agentId: { type: "string" }, status: { type: "string" }, limit: { type: "number" } },
12
+ required: ["agentId"],
13
+ },
14
+ handler: async (args, ctx) => {
15
+ await requireReadableAgent(ctx.db, args.agentId, ctx.principal.id);
16
+ const runs = await ctx.db.run.findMany({
17
+ where: { agentId: args.agentId, ...(args.status ? { status: args.status } : {}) },
18
+ take: args.limit,
19
+ orderBy: { startedAt: "desc" },
20
+ });
21
+ // Same rule as get_run: a pending coding run with queuedAt is waiting
22
+ // for a concurrency slot (CODING_MAX_CONCURRENT), not stuck.
23
+ const pendingIds = runs.filter((run) => run.status === "pending").map((run) => run.id);
24
+ const queued = pendingIds.length > 0
25
+ ? await ctx.db.codingRun.findMany({
26
+ where: { runId: { in: pendingIds }, queuedAt: { not: null } },
27
+ select: { runId: true, queuedAt: true },
28
+ })
29
+ : [];
30
+ const queuedAtByRun = new Map(queued.map((row) => [row.runId, row.queuedAt?.toISOString()]));
31
+ return textResult(runs.map((run) => {
32
+ const codingQueuedAt = queuedAtByRun.get(run.id);
33
+ return codingQueuedAt ? { ...run, codingQueuedAt } : run;
34
+ }));
35
+ },
36
+ });
37
+ mcp.registerTool({
38
+ name: "get_run",
39
+ scope: "agents:read",
40
+ inputSchema: { type: "object", properties: { runId: { type: "string" } }, required: ["runId"] },
41
+ handler: async (args, ctx) => {
42
+ const run = await ctx.db.run.findUnique({ where: { id: args.runId } });
43
+ if (!run)
44
+ throw new McpError(404, `Run "${args.runId}" not found.`);
45
+ await requireReadableAgent(ctx.db, run.agentId, ctx.principal.id);
46
+ const codingRun = await ctx.db.codingRun.findUnique({
47
+ where: { runId: run.id },
48
+ select: { result: true, queuedAt: true },
49
+ });
50
+ const codingResult = publicCodingRunResult(codingRun?.result);
51
+ // A pending coding run with queuedAt is waiting for a concurrency slot
52
+ // (CODING_MAX_CONCURRENT), not stuck.
53
+ const codingQueuedAt = run.status === "pending" && codingRun?.queuedAt ? codingRun.queuedAt.toISOString() : undefined;
54
+ return textResult({
55
+ ...run,
56
+ ...(codingResult ? { codingResult } : {}),
57
+ ...(codingQueuedAt ? { codingQueuedAt } : {}),
58
+ });
59
+ },
60
+ });
61
+ }
@@ -0,0 +1,2 @@
1
+ import type { WardbyMcpServer } from "../server.js";
2
+ export declare function registerSchedulingTools(mcp: WardbyMcpServer): void;
@@ -0,0 +1,48 @@
1
+ import { Prisma } from "@prisma/client";
2
+ import { validateCronExpression } from "../../core/cron.js";
3
+ import { McpError } from "../errors.js";
4
+ import { assertCanMutate, requireOwnedAgent } from "../auth/ownership.js";
5
+ import { textResult } from "./text-result.js";
6
+ export function registerSchedulingTools(mcp) {
7
+ mcp.registerTool({
8
+ name: "set_schedule",
9
+ scope: "agents:write",
10
+ inputSchema: {
11
+ type: "object",
12
+ properties: { agentId: { type: "string" }, schedule: { type: "string" }, timezone: { type: "string" } },
13
+ required: ["agentId", "schedule", "timezone"],
14
+ },
15
+ handler: async (args, ctx) => {
16
+ try {
17
+ validateCronExpression(args.schedule, args.timezone);
18
+ }
19
+ catch (err) {
20
+ throw new McpError(400, `Invalid schedule/timezone: ${err instanceof Error ? err.message : String(err)}`);
21
+ }
22
+ const agent = await ctx.db.$transaction(async (tx) => {
23
+ const existing = await tx.agent.findUnique({ where: { id: args.agentId }, include: { codingProfile: true } });
24
+ if (!existing)
25
+ throw new McpError(404, `Agent "${args.agentId}" not found.`);
26
+ assertCanMutate(existing.ownerId, ctx.principal.id, `Agent "${args.agentId}" is not owned by the caller.`);
27
+ if (existing.kind === "coding" && !existing.codingProfile?.defaultTask) {
28
+ throw new McpError(400, "A default task is required before enabling a coding-agent schedule.");
29
+ }
30
+ return tx.agent.update({
31
+ where: { id: args.agentId },
32
+ data: { schedule: args.schedule, timezone: args.timezone, scheduleEnabled: true },
33
+ });
34
+ }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
35
+ return textResult(agent);
36
+ },
37
+ });
38
+ mcp.registerTool({
39
+ name: "disable_schedule",
40
+ scope: "agents:write",
41
+ inputSchema: { type: "object", properties: { agentId: { type: "string" } }, required: ["agentId"] },
42
+ handler: async (args, ctx) => {
43
+ await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
44
+ const agent = await ctx.db.agent.update({ where: { id: args.agentId }, data: { scheduleEnabled: false } });
45
+ return textResult(agent);
46
+ },
47
+ });
48
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The one-time browser form a secret-elicitation URL points to — shared by
3
+ * both hosts: stdio's ephemeral loopback server (secret-elicitation-server.ts)
4
+ * and the HTTP transport's mounted route (wired in streamable-http.ts). No
5
+ * JavaScript: a plain HTML form POST is enough for one password field, so
6
+ * CSP can forbid scripts outright rather than needing a nonce.
7
+ */
8
+ import type { IncomingMessage, ServerResponse } from "node:http";
9
+ import type { PrismaClient } from "@prisma/client";
10
+ import type { SecretCipher } from "../../providers/secrets/types.js";
11
+ import { type SecretElicitationPayload } from "./secret-elicitation.js";
12
+ /** The HTTP-transport route path this form is mounted at (see streamable-http.ts). */
13
+ export declare const SECRET_ELICITATION_PATH = "/elicit/secret";
14
+ export interface SecretFormDeps {
15
+ verify: (token: string) => Promise<SecretElicitationPayload>;
16
+ secrets: SecretCipher;
17
+ db: PrismaClient;
18
+ }
19
+ /** Handles one GET (render) or POST (submit) for the secret-entry form at the given token. */
20
+ export declare function handleSecretElicitationForm(method: string | undefined, token: string | null, readFormBody: () => Promise<URLSearchParams>, res: ServerResponse, deps: SecretFormDeps): Promise<void>;
21
+ /** Reads a small `application/x-www-form-urlencoded` body (8 KiB cap — a secret value, nothing more). */
22
+ export declare function readFormBody(req: IncomingMessage): Promise<URLSearchParams>;
@@ -0,0 +1,93 @@
1
+ import { fulfillSecretElicitation } from "./secret-elicitation.js";
2
+ /** The HTTP-transport route path this form is mounted at (see streamable-http.ts). */
3
+ export const SECRET_ELICITATION_PATH = "/elicit/secret";
4
+ function escape(value) {
5
+ return value.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
6
+ }
7
+ const STYLE = `
8
+ :root { color-scheme: light dark; }
9
+ * { box-sizing: border-box; }
10
+ body { margin: 0; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px;
11
+ background: #f5f5f7; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
12
+ .card { background: #fff; border-radius: 14px; box-shadow: 0 1px 3px rgba(0,0,0,.1), 0 8px 24px rgba(0,0,0,.08);
13
+ padding: 36px; max-width: 480px; width: 100%; }
14
+ h1 { margin: 0 0 10px; font-size: 21px; }
15
+ p { margin: 0 0 24px; color: #555; font-size: 14px; line-height: 1.5; }
16
+ p strong { color: #111; }
17
+ input[type=password] { display: block; width: 100%; padding: 16px 18px; font-size: 17px; letter-spacing: .02em;
18
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; border: 1px solid #d1d1d6; border-radius: 10px;
19
+ margin-bottom: 18px; }
20
+ input[type=password]:focus { outline: none; border-color: #0071e3; box-shadow: 0 0 0 3px rgba(0,113,227,.15); }
21
+ button { width: 100%; padding: 15px; font-size: 15px; font-weight: 600; color: #fff; background: #0071e3;
22
+ border: none; border-radius: 10px; cursor: pointer; }
23
+ button:hover { background: #0077ed; }
24
+ @media (prefers-color-scheme: dark) {
25
+ body { background: #1c1c1e; }
26
+ .card { background: #2c2c2e; box-shadow: none; }
27
+ h1 { color: #f5f5f7; }
28
+ p { color: #a1a1a6; }
29
+ p strong { color: #f5f5f7; }
30
+ input[type=password] { background: #1c1c1e; border-color: #48484a; color: #f5f5f7; }
31
+ }`;
32
+ function html(res, status, body) {
33
+ res.setHeader("content-security-policy", "default-src 'none'; script-src 'none'; style-src 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'");
34
+ res.setHeader("cache-control", "no-store");
35
+ res.setHeader("x-content-type-options", "nosniff");
36
+ res.setHeader("referrer-policy", "no-referrer");
37
+ res
38
+ .writeHead(status, { "content-type": "text/html; charset=utf-8" })
39
+ .end(`<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">` +
40
+ `<title>wardby secret entry</title><style>${STYLE}</style><body><div class="card">${body}</div></body></html>`);
41
+ }
42
+ /** Handles one GET (render) or POST (submit) for the secret-entry form at the given token. */
43
+ export async function handleSecretElicitationForm(method, token, readFormBody, res, deps) {
44
+ let payload;
45
+ try {
46
+ if (!token)
47
+ throw new Error("missing token");
48
+ payload = await deps.verify(token);
49
+ }
50
+ catch {
51
+ html(res, 400, "<h1>Link expired</h1><p>This link is invalid or has expired. Return to your MCP client and try again.</p>");
52
+ return;
53
+ }
54
+ if (method === "GET") {
55
+ html(res, 200, `<h1>Enter secret value</h1><p>Name: <strong>${escape(payload.secretName)}</strong></p>` +
56
+ `<form method="post"><input type="password" name="value" autocomplete="off" required autofocus placeholder="Paste the value here">` +
57
+ `<button type="submit">Save</button></form>`);
58
+ return;
59
+ }
60
+ if (method === "POST") {
61
+ const body = await readFormBody();
62
+ const value = body.get("value") ?? "";
63
+ if (!value) {
64
+ html(res, 400, "<h1>A value is required.</h1>");
65
+ return;
66
+ }
67
+ const outcome = await fulfillSecretElicitation(payload, value, deps.secrets, deps.db);
68
+ if (outcome.ok)
69
+ html(res, 200, "<h1>Saved</h1><p>You can close this tab and return to your MCP client.</p>");
70
+ else
71
+ html(res, 400, `<h1>Could not save</h1><p>${escape(outcome.error)}</p>`);
72
+ return;
73
+ }
74
+ res.writeHead(405).end();
75
+ }
76
+ /** Reads a small `application/x-www-form-urlencoded` body (8 KiB cap — a secret value, nothing more). */
77
+ export function readFormBody(req) {
78
+ return new Promise((resolve, reject) => {
79
+ let size = 0;
80
+ const chunks = [];
81
+ req.on("data", (chunk) => {
82
+ size += chunk.length;
83
+ if (size > 8192) {
84
+ reject(new Error("body_too_large"));
85
+ req.destroy();
86
+ return;
87
+ }
88
+ chunks.push(chunk);
89
+ });
90
+ req.on("end", () => resolve(new URLSearchParams(Buffer.concat(chunks).toString("utf8"))));
91
+ req.on("error", reject);
92
+ });
93
+ }
@@ -0,0 +1,5 @@
1
+ import type { SecretFormDeps } from "./secret-elicitation-form.js";
2
+ export interface StdioSecretElicitationHost {
3
+ urlFor(token: string): Promise<string>;
4
+ }
5
+ export declare function createStdioSecretElicitationHost(deps: SecretFormDeps): StdioSecretElicitationHost;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * stdio-only host for the secret-entry form: an ephemeral, loopback-only
3
+ * HTTP server, started lazily on the first pending elicitation and closed
4
+ * after a period of inactivity. stdio's MCP process has no other listener
5
+ * (see mcp/transport/stdio.ts) — this one exists only while a secret entry
6
+ * is actually pending, consistent with stdio's "no token; the operator is
7
+ * trusted" model: reachable only from this same machine, and only a holder
8
+ * of the one-time signed URL can do anything with it.
9
+ */
10
+ import { createServer } from "node:http";
11
+ import { handleSecretElicitationForm, readFormBody } from "./secret-elicitation-form.js";
12
+ /** Matches the requestState codec's own TTL (server.ts) — no point outliving the tokens it serves. */
13
+ const IDLE_SHUTDOWN_MS = 10 * 60_000;
14
+ export function createStdioSecretElicitationHost(deps) {
15
+ let server;
16
+ let port = 0;
17
+ let idleTimer;
18
+ function scheduleIdleShutdown() {
19
+ if (idleTimer)
20
+ clearTimeout(idleTimer);
21
+ idleTimer = setTimeout(() => {
22
+ server?.close();
23
+ server = undefined;
24
+ }, IDLE_SHUTDOWN_MS);
25
+ idleTimer.unref();
26
+ }
27
+ async function ensureServer() {
28
+ if (server) {
29
+ scheduleIdleShutdown();
30
+ return port;
31
+ }
32
+ const s = createServer((req, res) => {
33
+ scheduleIdleShutdown();
34
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
35
+ if (url.pathname !== "/secret") {
36
+ res.writeHead(404).end();
37
+ return;
38
+ }
39
+ void handleSecretElicitationForm(req.method, url.searchParams.get("t"), () => readFormBody(req), res, deps);
40
+ });
41
+ server = s;
42
+ await new Promise((resolve, reject) => {
43
+ s.once("error", reject);
44
+ s.listen(0, "127.0.0.1", resolve);
45
+ });
46
+ const address = s.address();
47
+ port = typeof address === "object" && address ? address.port : 0;
48
+ scheduleIdleShutdown();
49
+ return port;
50
+ }
51
+ return {
52
+ async urlFor(token) {
53
+ const p = await ensureServer();
54
+ return `http://127.0.0.1:${p}/secret?t=${encodeURIComponent(token)}`;
55
+ },
56
+ };
57
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Out-of-band secret entry: `create_secret` called without a `value`
3
+ * mints a signed token (server.ts's mintRequestState/verifyRequestState)
4
+ * and hands the caller a one-time browser-form URL instead. The browser
5
+ * form writes the secret directly via `createSecret` — this module's
6
+ * `SecretElicitationOutcome` table is how the retried `tools/call` learns
7
+ * whether that write has happened yet.
8
+ *
9
+ * Backed by Postgres, not an in-process Map: a multi-instance HTTP
10
+ * deployment can serve the mint, the browser-form POST, and the polling
11
+ * retry from three different processes (see server.ts's `REQUEST_STATE_KEY`
12
+ * doc comment for the matching fix on the token side). Entries expire after
13
+ * ELICITATION_TTL_MS (matches server.ts's mintRequestState ttlSeconds: an
14
+ * elicitation nobody completes or polls within that window is abandoned).
15
+ * Expiry is swept lazily on every read and write via a `deleteMany` rather
16
+ * than a timer, so an idle process holds no interval.
17
+ */
18
+ import type { PrismaClient } from "@prisma/client";
19
+ import { type SecretMetadata } from "../../core/secrets.js";
20
+ import type { SecretCipher } from "../../providers/secrets/types.js";
21
+ export interface SecretElicitationPayload {
22
+ ownerId: string;
23
+ secretName: string;
24
+ }
25
+ export type SecretElicitationOutcome = {
26
+ ok: true;
27
+ secret: SecretMetadata;
28
+ } | {
29
+ ok: false;
30
+ error: string;
31
+ };
32
+ export declare function getSecretElicitationOutcome(ownerId: string, secretName: string, db: PrismaClient): Promise<SecretElicitationOutcome | undefined>;
33
+ /**
34
+ * Called by the browser form's POST handler. Idempotent — a resubmit for
35
+ * the same (ownerId, secretName) that's already fulfilled returns the
36
+ * recorded outcome rather than writing again.
37
+ */
38
+ export declare function fulfillSecretElicitation(payload: SecretElicitationPayload, value: string, cipher: SecretCipher, db: PrismaClient): Promise<SecretElicitationOutcome>;