@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,105 @@
1
+ export interface GitHubAppConfig {
2
+ appId: string;
3
+ privateKey: string;
4
+ apiBaseUrl?: string;
5
+ apiVersion?: string;
6
+ }
7
+ export interface PullRequestTestResult {
8
+ command: string;
9
+ outcome: "passed" | "failed" | "skipped";
10
+ }
11
+ export interface PullRequestInput {
12
+ runId: string;
13
+ repository: string;
14
+ baseRef: string;
15
+ headRef: string;
16
+ /** Already-validated/redacted agent-authored fields (coding/protocol.ts); optional for callers with none. */
17
+ summary?: string;
18
+ tests?: readonly PullRequestTestResult[];
19
+ tag?: string;
20
+ }
21
+ export interface PullRequestResult {
22
+ number: number;
23
+ url: string;
24
+ }
25
+ /** See VcsProvider.notifyContinuationStarted/Finished (types.ts) — these back that GitHub-specific mechanism. */
26
+ export interface ContinuationStatusCommentInput {
27
+ /** This continuation round's own run id — the comment's identity/marker key. */
28
+ runId: string;
29
+ /** The run whose PR this belongs to (used only to re-find the PR via the existing marker lookup). */
30
+ rootRunId: string;
31
+ repository: string;
32
+ baseRef: string;
33
+ headRef: string;
34
+ body: string;
35
+ }
36
+ export interface ContinuationCheckRunInput {
37
+ repository: string;
38
+ headSha: string;
39
+ /** Stored as the check run's external_id — the re-lookup key, so no numeric id needs to be cached anywhere. */
40
+ runId: string;
41
+ }
42
+ export interface ContinuationCheckRunCompleteInput extends ContinuationCheckRunInput {
43
+ outcome: "succeeded" | "failed";
44
+ }
45
+ export interface GitHubRepositoryAccess {
46
+ withRepositoryToken<T>(repository: string, action: (token: string) => Promise<T>): Promise<T>;
47
+ createOrFindDraftPullRequest(input: PullRequestInput): Promise<PullRequestResult>;
48
+ /** Creates the status comment if none exists yet for this run, else updates it. */
49
+ upsertContinuationStatusComment(input: ContinuationStatusCommentInput): Promise<void>;
50
+ /** Updates the status comment if one already exists for this run; a no-op otherwise (never creates). */
51
+ updateContinuationStatusComment(input: ContinuationStatusCommentInput): Promise<void>;
52
+ /** Creates an in-progress check run if none exists yet for this run+commit; a no-op if one already does. */
53
+ createContinuationCheckRun(input: ContinuationCheckRunInput): Promise<void>;
54
+ /** Completes the check run if one exists for this run+commit; a no-op otherwise (never creates). */
55
+ completeContinuationCheckRun(input: ContinuationCheckRunCompleteInput): Promise<void>;
56
+ }
57
+ type Fetch = typeof globalThis.fetch;
58
+ /** Accepts bounded opaque installation tokens without allowing control characters. */
59
+ export declare function isSafeGitHubInstallationToken(value: unknown): value is string;
60
+ export declare class GitHubAppClient implements GitHubRepositoryAccess {
61
+ private readonly config;
62
+ private readonly fetchImpl;
63
+ private readonly now;
64
+ private readonly apiBaseUrl;
65
+ private readonly apiVersion;
66
+ constructor(config: GitHubAppConfig, fetchImpl?: Fetch, now?: () => Date);
67
+ withRepositoryToken<T>(repository: string, action: (token: string) => Promise<T>): Promise<T>;
68
+ /**
69
+ * Isolated from withRepositoryToken on purpose: "Checks: write" is a
70
+ * genuinely distinct GitHub App permission from contents/pull_requests
71
+ * (confirmed empirically). Minting it separately means an ungranted-permission
72
+ * failure here can only ever disable the check-run mechanism, never the
73
+ * real git push, PR, or status comment. (Status comments, below, do NOT
74
+ * need this treatment: PR-comment writes work fine on the existing
75
+ * contents/pull_requests token -- confirmed empirically after "Issues:
76
+ * write" alone, on its own isolated token, kept returning 403 "Resource
77
+ * not accessible by integration" even once granted; commenting on a PR
78
+ * apparently isn't gated the same way a genuine issue comment might be.)
79
+ */
80
+ private withChecksToken;
81
+ upsertContinuationStatusComment(input: ContinuationStatusCommentInput): Promise<void>;
82
+ updateContinuationStatusComment(input: ContinuationStatusCommentInput): Promise<void>;
83
+ createContinuationCheckRun(input: ContinuationCheckRunInput): Promise<void>;
84
+ completeContinuationCheckRun(input: ContinuationCheckRunCompleteInput): Promise<void>;
85
+ createOrFindDraftPullRequest(input: PullRequestInput): Promise<PullRequestResult>;
86
+ private appJwt;
87
+ private mintRepositoryToken;
88
+ private mintChecksToken;
89
+ /**
90
+ * Shared installation-token mint: requests exactly `permissions`, then
91
+ * verifies the response granted exactly that (no more, no less, modulo
92
+ * the always-implicit `metadata: read`) before trusting the token. Each
93
+ * caller requests the narrowest permission set it needs — see
94
+ * withIssuesToken/withChecksToken for why these are minted separately
95
+ * from withRepositoryToken rather than requesting a union of everything.
96
+ */
97
+ private mintScopedToken;
98
+ private revokeToken;
99
+ private findPullRequest;
100
+ private parsePullRequest;
101
+ private findStatusComment;
102
+ private findCheckRun;
103
+ private requestJson;
104
+ }
105
+ export {};
@@ -0,0 +1,394 @@
1
+ import { createPrivateKey } from "node:crypto";
2
+ import { importPKCS8, SignJWT } from "jose";
3
+ import { normalizeGitHubRepository, normalizeGitRef } from "../../coding/protocol.js";
4
+ const DEFAULT_API_BASE_URL = "https://api.github.com";
5
+ const DEFAULT_API_VERSION = "2026-03-10";
6
+ const RUN_MARKER_PREFIX = "<!-- wardby:";
7
+ const STATUS_COMMENT_MARKER_PREFIX = "<!-- wardby-status:";
8
+ const CONTINUATION_CHECK_RUN_NAME = "wardby/continuation";
9
+ /** Mirrors coding/protocol.ts's tagSchema — validated independently here since this is where it reaches GitHub. */
10
+ const SAFE_PULL_REQUEST_TAG = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,31}$/;
11
+ /** Mirrors the runId shape validated elsewhere (coding/protocol.ts, git.ts) — validated independently here too. */
12
+ const SAFE_RUN_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
13
+ const SAFE_COMMIT_SHA = /^[0-9a-f]{40}$/;
14
+ function record(value) {
15
+ if (!value || typeof value !== "object" || Array.isArray(value))
16
+ throw new Error("github_api_invalid_response");
17
+ return value;
18
+ }
19
+ function positiveInteger(value) {
20
+ if (!Number.isSafeInteger(value) || Number(value) <= 0)
21
+ throw new Error("github_api_invalid_response");
22
+ return Number(value);
23
+ }
24
+ function safeApiError(response) {
25
+ const requestId = response.headers.get("x-github-request-id");
26
+ return new Error(`github_api_error:${response.status}${requestId ? `:${requestId}` : ""}`);
27
+ }
28
+ /** Accepts bounded opaque installation tokens without allowing control characters. */
29
+ export function isSafeGitHubInstallationToken(value) {
30
+ return (typeof value === "string" &&
31
+ value.length >= 20 &&
32
+ Buffer.byteLength(value, "utf8") <= 512 &&
33
+ /^[A-Za-z0-9._-]+$/.test(value));
34
+ }
35
+ function pullRequestTitle(input) {
36
+ return input.tag ? `[${input.tag}] Wardby run ${input.runId}` : `Wardby run ${input.runId}`;
37
+ }
38
+ /** The hidden run marker stays first and unconditional: createOrFindDraftPullRequest's idempotent lookup depends on it. */
39
+ function pullRequestBody(input) {
40
+ const sections = [`${RUN_MARKER_PREFIX}${input.runId} -->`];
41
+ if (input.summary)
42
+ sections.push(input.summary);
43
+ if (input.tests?.length) {
44
+ sections.push(["**Tests:**", ...input.tests.map((test) => `- \`${test.command}\`: ${test.outcome}`)].join("\n"));
45
+ }
46
+ return sections.join("\n\n");
47
+ }
48
+ /** The hidden marker stays first and unconditional: findStatusComment's lookup depends on it. */
49
+ function statusCommentBody(runId, body) {
50
+ return `${STATUS_COMMENT_MARKER_PREFIX}${runId} -->\n\n${body}`;
51
+ }
52
+ function normalizeApiBaseUrl(value) {
53
+ const url = new URL(value);
54
+ if (url.protocol !== "https:" ||
55
+ url.hostname.toLowerCase() !== "api.github.com" ||
56
+ url.port ||
57
+ url.username ||
58
+ url.password ||
59
+ url.search ||
60
+ url.hash ||
61
+ url.pathname !== "/") {
62
+ throw new Error("github_api_base_url_invalid");
63
+ }
64
+ return url.toString().replace(/\/$/, "");
65
+ }
66
+ function normalizePrivateKey(value) {
67
+ return value.includes("\\n") && !value.includes("\n") ? value.replaceAll("\\n", "\n") : value;
68
+ }
69
+ export class GitHubAppClient {
70
+ config;
71
+ fetchImpl;
72
+ now;
73
+ apiBaseUrl;
74
+ apiVersion;
75
+ constructor(config, fetchImpl = globalThis.fetch, now = () => new Date()) {
76
+ this.config = config;
77
+ this.fetchImpl = fetchImpl;
78
+ this.now = now;
79
+ if (!/^[1-9]\d*$/.test(config.appId))
80
+ throw new Error("github_app_id_invalid");
81
+ if (!config.privateKey.trim())
82
+ throw new Error("github_app_private_key_invalid");
83
+ this.apiBaseUrl = normalizeApiBaseUrl(config.apiBaseUrl ?? DEFAULT_API_BASE_URL);
84
+ this.apiVersion = config.apiVersion ?? DEFAULT_API_VERSION;
85
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(this.apiVersion))
86
+ throw new Error("github_api_version_invalid");
87
+ }
88
+ async withRepositoryToken(repository, action) {
89
+ const normalized = normalizeGitHubRepository(repository);
90
+ const token = await this.mintRepositoryToken(normalized);
91
+ try {
92
+ return await action(token);
93
+ }
94
+ finally {
95
+ await this.revokeToken(token).catch(() => undefined);
96
+ }
97
+ }
98
+ /**
99
+ * Isolated from withRepositoryToken on purpose: "Checks: write" is a
100
+ * genuinely distinct GitHub App permission from contents/pull_requests
101
+ * (confirmed empirically). Minting it separately means an ungranted-permission
102
+ * failure here can only ever disable the check-run mechanism, never the
103
+ * real git push, PR, or status comment. (Status comments, below, do NOT
104
+ * need this treatment: PR-comment writes work fine on the existing
105
+ * contents/pull_requests token -- confirmed empirically after "Issues:
106
+ * write" alone, on its own isolated token, kept returning 403 "Resource
107
+ * not accessible by integration" even once granted; commenting on a PR
108
+ * apparently isn't gated the same way a genuine issue comment might be.)
109
+ */
110
+ async withChecksToken(repository, action) {
111
+ const normalized = normalizeGitHubRepository(repository);
112
+ const token = await this.mintChecksToken(normalized);
113
+ try {
114
+ return await action(token);
115
+ }
116
+ finally {
117
+ await this.revokeToken(token).catch(() => undefined);
118
+ }
119
+ }
120
+ async upsertContinuationStatusComment(input) {
121
+ const repository = normalizeGitHubRepository(input.repository);
122
+ const baseRef = normalizeGitRef(input.baseRef);
123
+ const headRef = normalizeGitRef(input.headRef);
124
+ if (!SAFE_RUN_ID.test(input.runId) || !SAFE_RUN_ID.test(input.rootRunId)) {
125
+ throw new Error("github_status_comment_input_invalid");
126
+ }
127
+ await this.withRepositoryToken(repository, async (token) => {
128
+ const pr = await this.findPullRequest(token, { runId: input.rootRunId, repository, baseRef, headRef });
129
+ if (!pr)
130
+ return;
131
+ const existing = await this.findStatusComment(token, repository, pr.number, input.runId);
132
+ const [owner, name] = repository.split("/");
133
+ const body = statusCommentBody(input.runId, input.body);
134
+ if (existing) {
135
+ await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/comments/${existing.id}`, token, { method: "PATCH", body: JSON.stringify({ body }) }, [200]);
136
+ }
137
+ else {
138
+ await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/${pr.number}/comments`, token, { method: "POST", body: JSON.stringify({ body }) }, [201]);
139
+ }
140
+ });
141
+ }
142
+ async updateContinuationStatusComment(input) {
143
+ const repository = normalizeGitHubRepository(input.repository);
144
+ const baseRef = normalizeGitRef(input.baseRef);
145
+ const headRef = normalizeGitRef(input.headRef);
146
+ if (!SAFE_RUN_ID.test(input.runId) || !SAFE_RUN_ID.test(input.rootRunId)) {
147
+ throw new Error("github_status_comment_input_invalid");
148
+ }
149
+ await this.withRepositoryToken(repository, async (token) => {
150
+ const pr = await this.findPullRequest(token, { runId: input.rootRunId, repository, baseRef, headRef });
151
+ if (!pr)
152
+ return;
153
+ const existing = await this.findStatusComment(token, repository, pr.number, input.runId);
154
+ if (!existing)
155
+ return;
156
+ const [owner, name] = repository.split("/");
157
+ await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/comments/${existing.id}`, token, { method: "PATCH", body: JSON.stringify({ body: statusCommentBody(input.runId, input.body) }) }, [200]);
158
+ });
159
+ }
160
+ async createContinuationCheckRun(input) {
161
+ const repository = normalizeGitHubRepository(input.repository);
162
+ if (!SAFE_RUN_ID.test(input.runId) || !SAFE_COMMIT_SHA.test(input.headSha)) {
163
+ throw new Error("github_check_run_input_invalid");
164
+ }
165
+ await this.withChecksToken(repository, async (token) => {
166
+ const existing = await this.findCheckRun(token, repository, input.headSha, input.runId);
167
+ if (existing)
168
+ return;
169
+ const [owner, name] = repository.split("/");
170
+ await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/check-runs`, token, {
171
+ method: "POST",
172
+ body: JSON.stringify({
173
+ name: CONTINUATION_CHECK_RUN_NAME,
174
+ head_sha: input.headSha,
175
+ status: "in_progress",
176
+ external_id: input.runId,
177
+ }),
178
+ }, [201]);
179
+ });
180
+ }
181
+ async completeContinuationCheckRun(input) {
182
+ const repository = normalizeGitHubRepository(input.repository);
183
+ if (!SAFE_RUN_ID.test(input.runId) || !SAFE_COMMIT_SHA.test(input.headSha)) {
184
+ throw new Error("github_check_run_input_invalid");
185
+ }
186
+ await this.withChecksToken(repository, async (token) => {
187
+ const existing = await this.findCheckRun(token, repository, input.headSha, input.runId);
188
+ if (!existing)
189
+ return;
190
+ const [owner, name] = repository.split("/");
191
+ await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/check-runs/${existing.id}`, token, {
192
+ method: "PATCH",
193
+ body: JSON.stringify({
194
+ status: "completed",
195
+ conclusion: input.outcome === "succeeded" ? "success" : "failure",
196
+ }),
197
+ }, [200]);
198
+ });
199
+ }
200
+ async createOrFindDraftPullRequest(input) {
201
+ const repository = normalizeGitHubRepository(input.repository);
202
+ const baseRef = normalizeGitRef(input.baseRef);
203
+ const headRef = normalizeGitRef(input.headRef);
204
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(input.runId) || headRef !== `wardby/run-${input.runId}`) {
205
+ throw new Error("github_pull_request_input_invalid");
206
+ }
207
+ if (input.tag !== undefined && !SAFE_PULL_REQUEST_TAG.test(input.tag)) {
208
+ throw new Error("github_pull_request_tag_invalid");
209
+ }
210
+ return this.withRepositoryToken(repository, async (token) => {
211
+ const existing = await this.findPullRequest(token, { ...input, repository, baseRef, headRef });
212
+ if (existing)
213
+ return existing;
214
+ const [owner, name] = repository.split("/");
215
+ const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/pulls`, token, {
216
+ method: "POST",
217
+ body: JSON.stringify({
218
+ title: pullRequestTitle(input),
219
+ head: headRef,
220
+ base: baseRef,
221
+ body: pullRequestBody(input),
222
+ draft: true,
223
+ }),
224
+ }, [201, 422]);
225
+ if (response.status === 201)
226
+ return this.parsePullRequest(await response.json(), repository);
227
+ const raced = await this.findPullRequest(token, { ...input, repository, baseRef, headRef });
228
+ if (raced)
229
+ return raced;
230
+ throw safeApiError(response);
231
+ });
232
+ }
233
+ async appJwt() {
234
+ let keyObject;
235
+ try {
236
+ keyObject = createPrivateKey(normalizePrivateKey(this.config.privateKey));
237
+ }
238
+ catch {
239
+ throw new Error("github_app_private_key_invalid");
240
+ }
241
+ const pkcs8 = keyObject.export({ type: "pkcs8", format: "pem" }).toString();
242
+ const key = await importPKCS8(pkcs8, "RS256");
243
+ const now = Math.floor(this.now().getTime() / 1000);
244
+ return new SignJWT({})
245
+ .setProtectedHeader({ alg: "RS256", typ: "JWT" })
246
+ .setIssuedAt(now - 60)
247
+ .setExpirationTime(now + 9 * 60)
248
+ .setIssuer(this.config.appId)
249
+ .sign(key);
250
+ }
251
+ async mintRepositoryToken(repository) {
252
+ return this.mintScopedToken(repository, { contents: "write", pull_requests: "write" }, (name, level) => (name === "contents" && level === "write") ||
253
+ (name === "pull_requests" && level === "write") ||
254
+ (name === "metadata" && level === "read"));
255
+ }
256
+ async mintChecksToken(repository) {
257
+ return this.mintScopedToken(repository, { checks: "write" }, (name, level) => (name === "checks" && level === "write") || (name === "metadata" && level === "read"));
258
+ }
259
+ /**
260
+ * Shared installation-token mint: requests exactly `permissions`, then
261
+ * verifies the response granted exactly that (no more, no less, modulo
262
+ * the always-implicit `metadata: read`) before trusting the token. Each
263
+ * caller requests the narrowest permission set it needs — see
264
+ * withIssuesToken/withChecksToken for why these are minted separately
265
+ * from withRepositoryToken rather than requesting a union of everything.
266
+ */
267
+ async mintScopedToken(repository, permissions, isAllowedPermission) {
268
+ const [owner, name] = repository.split("/");
269
+ const appJwt = await this.appJwt();
270
+ const installationResponse = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/installation`, appJwt);
271
+ const installation = record(await installationResponse.json());
272
+ const installationId = positiveInteger(installation.id);
273
+ const tokenResponse = await this.requestJson(`/app/installations/${installationId}/access_tokens`, appJwt, { method: "POST", body: JSON.stringify({ repositories: [name], permissions }) }, [201]);
274
+ const payload = record(await tokenResponse.json());
275
+ const grantedPermissions = record(payload.permissions);
276
+ const repositories = Array.isArray(payload.repositories) ? payload.repositories.map(record) : [];
277
+ const scopedRepository = repositories.some((candidate) => typeof candidate.full_name === "string" && candidate.full_name.toLowerCase() === repository);
278
+ const expiresAt = typeof payload.expires_at === "string" ? Date.parse(payload.expires_at) : Number.NaN;
279
+ const unexpectedPermission = Object.entries(grantedPermissions).some(([permissionName, level]) => !isAllowedPermission(permissionName, level));
280
+ const requestedGranted = Object.entries(permissions).every(([permissionName, level]) => grantedPermissions[permissionName] === level);
281
+ if (!isSafeGitHubInstallationToken(payload.token) ||
282
+ !Number.isFinite(expiresAt) ||
283
+ expiresAt <= this.now().getTime() + 60_000 ||
284
+ !requestedGranted ||
285
+ unexpectedPermission ||
286
+ !scopedRepository) {
287
+ throw new Error("github_installation_token_scope_invalid");
288
+ }
289
+ return payload.token;
290
+ }
291
+ async revokeToken(token) {
292
+ await this.requestJson("/installation/token", token, { method: "DELETE" }, [204]);
293
+ }
294
+ async findPullRequest(token, input) {
295
+ const [owner, name] = input.repository.split("/");
296
+ const query = new URLSearchParams({
297
+ state: "open",
298
+ head: `${owner}:${input.headRef}`,
299
+ base: input.baseRef,
300
+ per_page: "100",
301
+ });
302
+ const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/pulls?${query.toString()}`, token);
303
+ const payload = await response.json();
304
+ if (!Array.isArray(payload))
305
+ throw new Error("github_api_invalid_response");
306
+ const marker = `${RUN_MARKER_PREFIX}${input.runId} -->`;
307
+ for (const item of payload) {
308
+ const candidate = record(item);
309
+ if (typeof candidate.body === "string" && candidate.body.includes(marker)) {
310
+ if (candidate.draft !== true)
311
+ throw new Error("github_pull_request_not_draft");
312
+ return this.parsePullRequest(candidate, input.repository);
313
+ }
314
+ }
315
+ return null;
316
+ }
317
+ parsePullRequest(value, repository) {
318
+ const payload = record(value);
319
+ const number = positiveInteger(payload.number);
320
+ if (payload.draft !== true)
321
+ throw new Error("github_pull_request_not_draft");
322
+ const expectedUrl = `https://github.com/${repository}/pull/${number}`;
323
+ if (typeof payload.html_url !== "string")
324
+ throw new Error("github_pull_request_response_invalid");
325
+ let receivedUrl;
326
+ try {
327
+ receivedUrl = new URL(payload.html_url);
328
+ }
329
+ catch {
330
+ throw new Error("github_pull_request_response_invalid");
331
+ }
332
+ if (receivedUrl.protocol !== "https:" ||
333
+ receivedUrl.hostname.toLowerCase() !== "github.com" ||
334
+ receivedUrl.port ||
335
+ receivedUrl.username ||
336
+ receivedUrl.password ||
337
+ receivedUrl.search ||
338
+ receivedUrl.hash ||
339
+ receivedUrl.pathname.toLowerCase() !== new URL(expectedUrl).pathname.toLowerCase()) {
340
+ throw new Error("github_pull_request_response_invalid");
341
+ }
342
+ return { number, url: expectedUrl };
343
+ }
344
+ async findStatusComment(token, repository, pullRequestNumber, runId) {
345
+ const [owner, name] = repository.split("/");
346
+ const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/issues/${pullRequestNumber}/comments?per_page=100`, token);
347
+ const payload = await response.json();
348
+ if (!Array.isArray(payload))
349
+ throw new Error("github_api_invalid_response");
350
+ const marker = `${STATUS_COMMENT_MARKER_PREFIX}${runId} -->`;
351
+ for (const item of payload) {
352
+ const candidate = record(item);
353
+ if (typeof candidate.body === "string" && candidate.body.includes(marker)) {
354
+ return { id: positiveInteger(candidate.id) };
355
+ }
356
+ }
357
+ return null;
358
+ }
359
+ async findCheckRun(token, repository, headSha, runId) {
360
+ const [owner, name] = repository.split("/");
361
+ const response = await this.requestJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/commits/${headSha}/check-runs?per_page=100`, token);
362
+ const payload = record(await response.json());
363
+ const checkRuns = Array.isArray(payload.check_runs) ? payload.check_runs.map(record) : [];
364
+ for (const candidate of checkRuns) {
365
+ if (candidate.external_id === runId)
366
+ return { id: positiveInteger(candidate.id) };
367
+ }
368
+ return null;
369
+ }
370
+ async requestJson(path, bearer, init = {}, expectedStatuses = [200]) {
371
+ let response;
372
+ try {
373
+ response = await this.fetchImpl(`${this.apiBaseUrl}${path}`, {
374
+ ...init,
375
+ headers: {
376
+ accept: "application/vnd.github+json",
377
+ authorization: `Bearer ${bearer}`,
378
+ "content-type": "application/json",
379
+ "user-agent": "wardby",
380
+ "x-github-api-version": this.apiVersion,
381
+ ...init.headers,
382
+ },
383
+ });
384
+ }
385
+ catch (error) {
386
+ // Keep the cause: the operator log needs the transport reason (DNS,
387
+ // TLS, connect timeout); the message itself stays fixed and safe.
388
+ throw new Error("github_api_unavailable", { cause: error });
389
+ }
390
+ if (!expectedStatuses.includes(response.status))
391
+ throw safeApiError(response);
392
+ return response;
393
+ }
394
+ }
@@ -0,0 +1,6 @@
1
+ import type { GitHubVcsConfig, ProviderConfig } from "../../config/providers.js";
2
+ import type { VcsProvider } from "./types.js";
3
+ export * from "./types.js";
4
+ export { GitHubAppClient } from "./github.js";
5
+ export { GitVcsProvider, NodeGitCommandRunner } from "./git.js";
6
+ export declare function buildVcsProvider(providerConfig: Pick<ProviderConfig, "vcs">, config: GitHubVcsConfig): VcsProvider;
@@ -0,0 +1,26 @@
1
+ import { tmpdir } from "node:os";
2
+ import { resolve } from "node:path";
3
+ import { GitHubAppClient } from "./github.js";
4
+ import { GitVcsProvider } from "./git.js";
5
+ export * from "./types.js";
6
+ export { GitHubAppClient } from "./github.js";
7
+ export { GitVcsProvider, NodeGitCommandRunner } from "./git.js";
8
+ export function buildVcsProvider(providerConfig, config) {
9
+ if (providerConfig.vcs !== "github")
10
+ throw new Error(`VCS_PROVIDER=${String(providerConfig.vcs)} is not supported.`);
11
+ if (!config.appId || !config.privateKey) {
12
+ throw new Error("GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY are required by the GitHub VCS adapter.");
13
+ }
14
+ const rootDir = resolve(config.workRoot ?? resolve(tmpdir(), "wardby-vcs"));
15
+ const github = new GitHubAppClient({
16
+ appId: config.appId,
17
+ privateKey: config.privateKey,
18
+ apiVersion: config.apiVersion,
19
+ });
20
+ return new GitVcsProvider({
21
+ rootDir,
22
+ github,
23
+ maxChangedFiles: config.maxChangedFiles,
24
+ maxDiffBytes: config.maxDiffBytes,
25
+ });
26
+ }
@@ -0,0 +1,118 @@
1
+ export interface VcsPrepareInput {
2
+ runId: string;
3
+ repository: string;
4
+ baseRef: string;
5
+ headRef: string;
6
+ protectedPaths: string[];
7
+ /**
8
+ * Revision-in-place: set when headRef is an EXISTING branch to continue
9
+ * rather than a fresh one
10
+ * to create off baseRef. `rootRunId` is the id of the run that originally
11
+ * opened the PR headRef belongs to (used for PR lookup identity, since
12
+ * GitHub's find-by-marker keys on that run's id, not this one's) -- the
13
+ * caller (dispatch.ts) must already have verified, against the database,
14
+ * that this run is legitimately allowed to continue that branch before
15
+ * ever setting this; this layer trusts it exactly as much as it already
16
+ * trusts baseRef, no more, and cannot independently re-verify a database
17
+ * fact.
18
+ */
19
+ continuation?: {
20
+ rootRunId: string;
21
+ };
22
+ }
23
+ /** Trusted paths are returned by the provider, never accepted from worker output. */
24
+ export interface PreparedWorkspace {
25
+ id: string;
26
+ runId: string;
27
+ repository: string;
28
+ baseRef: string;
29
+ headRef: string;
30
+ /** For a fresh workspace, the commit baseRef pointed at when cloned. For
31
+ * a continuation, the tip of the branch being continued at clone time --
32
+ * each round is independently validated against its own agent's rules at
33
+ * commit time (see design doc §8.3), so there's no need to re-derive or
34
+ * re-validate the PR's true original root here; "where this round
35
+ * started" is the only anchor the single-new-commit and protected-path
36
+ * checks need. */
37
+ baseCommit: string;
38
+ workspacePath: string;
39
+ gitMetadataPath: string;
40
+ protectedPaths: string[];
41
+ continuation?: {
42
+ rootRunId: string;
43
+ };
44
+ }
45
+ /** Already-validated/redacted agent-authored fields (coding/protocol.ts) surfaced in the opened PR, if any. */
46
+ export interface FinalizeChangesDetails {
47
+ summary?: string;
48
+ tests?: readonly {
49
+ command: string;
50
+ outcome: "passed" | "failed" | "skipped";
51
+ }[];
52
+ tag?: string;
53
+ }
54
+ export type FinalizeChangesResult = {
55
+ outcome: "no_changes";
56
+ repository: string;
57
+ baseRef: string;
58
+ baseCommit: string;
59
+ } | {
60
+ outcome: "pull_request_opened";
61
+ repository: string;
62
+ baseRef: string;
63
+ baseCommit: string;
64
+ headRef: string;
65
+ commitSha: string;
66
+ pullRequestNumber: number;
67
+ pullRequestUrl: string;
68
+ } | {
69
+ /** Revision-in-place: pushed a new commit onto an existing open PR's
70
+ * branch instead of opening a new one. */
71
+ outcome: "pull_request_updated";
72
+ repository: string;
73
+ baseRef: string;
74
+ baseCommit: string;
75
+ headRef: string;
76
+ commitSha: string;
77
+ pullRequestNumber: number;
78
+ pullRequestUrl: string;
79
+ };
80
+ export interface VcsProvider {
81
+ prepareWorkspace(input: VcsPrepareInput): Promise<PreparedWorkspace>;
82
+ recoverWorkspace(input: VcsPrepareInput): Promise<PreparedWorkspace | null>;
83
+ finalizeChanges(workspace: PreparedWorkspace, details?: FinalizeChangesDetails): Promise<FinalizeChangesResult>;
84
+ cleanup(workspace: PreparedWorkspace): Promise<void>;
85
+ /**
86
+ * Best-effort "wardby is working on this" signal for a continuation
87
+ * a no-op when `workspace.continuation` is unset, since a fresh run has
88
+ * no PR to attach anything to until its one commit lands. Optional
89
+ * because this is a GitHub-specific concept, not a universal VCS one --
90
+ * a future non-GitHub provider (or a test double) can simply omit it,
91
+ * mirroring `Executor.resolveCodingWorkerImage?` (dispatch.ts calls it
92
+ * with `?.()`). Implementations MUST NOT throw: this is strictly
93
+ * observability, never allowed to affect the real coding run.
94
+ * `details.agentName`, when present, is the human-readable agent name
95
+ * (e.g. "knock-knock-implement") surfaced alongside the opaque run id --
96
+ * particularly useful for the cross-agent case, where the agent
97
+ * continuing the PR isn't the one that originally opened it.
98
+ */
99
+ notifyContinuationStarted?(workspace: PreparedWorkspace, details?: {
100
+ agentName?: string;
101
+ }): Promise<void>;
102
+ /**
103
+ * Companion to `notifyContinuationStarted` -- must find and update
104
+ * whatever that call created, never create fresh state itself (a
105
+ * "finished" status with no preceding "in progress" one would be
106
+ * confusing, and could happen if the process crashed between the two
107
+ * calls). Safe to call more than once for the same run. Same
108
+ * never-throw contract as `notifyContinuationStarted`. `details.summary`,
109
+ * when present, is the agent's own already-validated/redacted summary
110
+ * (the same text that goes in the PR body -- see
111
+ * `FinalizeChangesDetails.summary`) so the "done" status reflects what
112
+ * actually happened instead of a caller having to go find out.
113
+ */
114
+ notifyContinuationFinished?(workspace: PreparedWorkspace, outcome: "succeeded" | "failed", details?: {
115
+ summary?: string;
116
+ agentName?: string;
117
+ }): Promise<void>;
118
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare function boundedJson(value: unknown, maxBytes: number): string;
2
+ export declare function boundedString(value: unknown, maxBytes: number): asserts value is string;