@wardby/cli 0.7.0 → 0.9.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 (367) hide show
  1. package/.env.example +5 -5
  2. package/README.md +6 -1
  3. package/deploy/local/docker-compose.quickstart-coding.yml +11 -0
  4. package/dist/claude-coding-worker/driver.d.ts +12 -0
  5. package/dist/claude-coding-worker/driver.js +29 -7
  6. package/dist/claude-coding-worker/main.js +5 -1
  7. package/dist/claude-coding-worker/sdk.js +17 -1
  8. package/dist/claude-plugin/command.d.ts +64 -0
  9. package/dist/claude-plugin/command.js +379 -0
  10. package/dist/cli-help.d.ts +1 -1
  11. package/dist/cli-help.js +2 -1
  12. package/dist/cli.js +71 -3
  13. package/dist/coding/code-host.d.ts +16 -0
  14. package/dist/coding/code-host.js +60 -0
  15. package/dist/coding/mcp-token.d.ts +12 -0
  16. package/dist/coding/mcp-token.js +26 -0
  17. package/dist/coding/protocol.js +2 -0
  18. package/dist/coding/redact.js +6 -1
  19. package/dist/coding/transcript/recorder.d.ts +5 -4
  20. package/dist/coding/transcript/recorder.js +3 -9
  21. package/dist/coding-worker/debug-trace.d.ts +3 -1
  22. package/dist/coding-worker/debug-trace.js +4 -2
  23. package/dist/coding-worker/driver.js +8 -1
  24. package/dist/coding-worker/main.js +5 -1
  25. package/dist/coding-worker/mcp-config.d.ts +27 -0
  26. package/dist/coding-worker/mcp-config.js +32 -0
  27. package/dist/coding-worker/sdk.d.ts +36 -0
  28. package/dist/coding-worker/sdk.js +28 -1
  29. package/dist/coding-worker/types.d.ts +4 -0
  30. package/dist/config/mcp-call-limit.d.ts +8 -0
  31. package/dist/config/mcp-call-limit.js +18 -0
  32. package/dist/config/mcp-servers.d.ts +8 -0
  33. package/dist/config/mcp-servers.js +12 -0
  34. package/dist/config/test-simulators.d.ts +10 -0
  35. package/dist/config/test-simulators.js +28 -0
  36. package/dist/config/tool-timeout.d.ts +1 -0
  37. package/dist/config/tool-timeout.js +18 -0
  38. package/dist/core/coding-mcp.d.ts +36 -0
  39. package/dist/core/coding-mcp.js +253 -0
  40. package/dist/core/coding-repository.d.ts +46 -0
  41. package/dist/core/coding-repository.js +92 -0
  42. package/dist/core/delegation-slots.d.ts +27 -0
  43. package/dist/core/delegation-slots.js +44 -0
  44. package/dist/core/dispatch.d.ts +6 -2
  45. package/dist/core/dispatch.js +39 -11
  46. package/dist/core/engine-native.d.ts +2 -0
  47. package/dist/core/engine-native.js +13 -0
  48. package/dist/core/error-code.js +2 -0
  49. package/dist/core/grants.d.ts +28 -3
  50. package/dist/core/grants.js +46 -4
  51. package/dist/core/host-events.js +7 -3
  52. package/dist/core/host-status.js +2 -2
  53. package/dist/core/issue-status.js +4 -3
  54. package/dist/core/listen-address.d.ts +10 -0
  55. package/dist/core/listen-address.js +14 -0
  56. package/dist/core/mark-ready.d.ts +1 -1
  57. package/dist/core/mark-ready.js +1 -1
  58. package/dist/core/mcp-client/client.d.ts +47 -0
  59. package/dist/core/mcp-client/client.js +141 -0
  60. package/dist/core/mcp-client/jsonrpc.d.ts +15 -0
  61. package/dist/core/mcp-client/jsonrpc.js +54 -0
  62. package/dist/core/mcp-client/results.d.ts +13 -0
  63. package/dist/core/mcp-client/results.js +42 -0
  64. package/dist/core/mcp-servers.d.ts +90 -0
  65. package/dist/core/mcp-servers.js +230 -0
  66. package/dist/core/mcp-tools.d.ts +108 -0
  67. package/dist/core/mcp-tools.js +287 -0
  68. package/dist/core/merge-order-check.js +38 -19
  69. package/dist/core/notification-dispatcher.js +2 -1
  70. package/dist/core/power-idle.d.ts +8 -2
  71. package/dist/core/power-idle.js +17 -3
  72. package/dist/core/power.d.ts +5 -0
  73. package/dist/core/power.js +5 -0
  74. package/dist/core/related-pull-requests.js +26 -10
  75. package/dist/core/retention-days.d.ts +5 -0
  76. package/dist/core/retention-days.js +14 -0
  77. package/dist/core/review-fix.js +2 -2
  78. package/dist/core/review-host-tools.js +3 -1
  79. package/dist/core/run-steps/config.d.ts +25 -0
  80. package/dist/core/run-steps/config.js +40 -0
  81. package/dist/core/run-steps/control-chars.d.ts +16 -0
  82. package/dist/core/run-steps/control-chars.js +26 -0
  83. package/dist/core/run-steps/detail.d.ts +32 -0
  84. package/dist/core/run-steps/detail.js +240 -0
  85. package/dist/core/run-steps/extract.d.ts +52 -0
  86. package/dist/core/run-steps/extract.js +467 -0
  87. package/dist/core/run-steps/ingress.d.ts +51 -0
  88. package/dist/core/run-steps/ingress.js +160 -0
  89. package/dist/core/run-steps/last-step.d.ts +15 -0
  90. package/dist/core/run-steps/last-step.js +35 -0
  91. package/dist/core/run-steps/native.d.ts +48 -0
  92. package/dist/core/run-steps/native.js +164 -0
  93. package/dist/core/run-steps/seen.d.ts +16 -0
  94. package/dist/core/run-steps/seen.js +35 -0
  95. package/dist/core/run-steps/summarize.d.ts +57 -0
  96. package/dist/core/run-steps/summarize.js +304 -0
  97. package/dist/core/run-steps/sweep.d.ts +21 -0
  98. package/dist/core/run-steps/sweep.js +41 -0
  99. package/dist/core/run-steps/types.d.ts +89 -0
  100. package/dist/core/run-steps/types.js +39 -0
  101. package/dist/core/run-steps/wire.d.ts +35 -0
  102. package/dist/core/run-steps/wire.js +47 -0
  103. package/dist/core/run-steps/writer.d.ts +49 -0
  104. package/dist/core/run-steps/writer.js +279 -0
  105. package/dist/core/runner.d.ts +70 -16
  106. package/dist/core/runner.js +296 -74
  107. package/dist/core/secret-broker-config.d.ts +2 -0
  108. package/dist/core/secret-broker-config.js +1 -1
  109. package/dist/core/secret-guard.d.ts +5 -2
  110. package/dist/core/secret-guard.js +22 -7
  111. package/dist/core/secrets.d.ts +11 -1
  112. package/dist/core/secrets.js +27 -1
  113. package/dist/core/serializable-retry.d.ts +18 -0
  114. package/dist/core/serializable-retry.js +43 -0
  115. package/dist/core/tool-admin.d.ts +5 -1
  116. package/dist/core/tool-admin.js +7 -2
  117. package/dist/core/tool-names.d.ts +13 -0
  118. package/dist/core/tool-names.js +20 -0
  119. package/dist/core/webhooks.d.ts +4 -0
  120. package/dist/core/webhooks.js +68 -49
  121. package/dist/core/workflow-run-events.js +2 -1
  122. package/dist/generated/prisma/browser.d.ts +39 -0
  123. package/dist/generated/prisma/client.d.ts +39 -0
  124. package/dist/generated/prisma/commonInputTypes.d.ts +80 -50
  125. package/dist/generated/prisma/enums.d.ts +6 -0
  126. package/dist/generated/prisma/enums.js +5 -0
  127. package/dist/generated/prisma/internal/class.d.ts +77 -0
  128. package/dist/generated/prisma/internal/class.js +4 -4
  129. package/dist/generated/prisma/internal/prismaNamespace.d.ts +632 -1
  130. package/dist/generated/prisma/internal/prismaNamespace.js +94 -3
  131. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +98 -0
  132. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +94 -3
  133. package/dist/generated/prisma/models/Agent.d.ts +372 -1
  134. package/dist/generated/prisma/models/AgentMcpServer.d.ts +1412 -0
  135. package/dist/generated/prisma/models/AgentMcpServer.js +1 -0
  136. package/dist/generated/prisma/models/AgentRepository.d.ts +40 -1
  137. package/dist/generated/prisma/models/AuthUser.d.ts +1 -1
  138. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +0 -7
  139. package/dist/generated/prisma/models/CodingProxySession.d.ts +159 -2
  140. package/dist/generated/prisma/models/CodingRun.d.ts +76 -1
  141. package/dist/generated/prisma/models/HostProvider.d.ts +2176 -0
  142. package/dist/generated/prisma/models/HostProvider.js +1 -0
  143. package/dist/generated/prisma/models/HostProviderCredential.d.ts +1285 -0
  144. package/dist/generated/prisma/models/HostProviderCredential.js +1 -0
  145. package/dist/generated/prisma/models/HostProviderOperation.d.ts +1470 -0
  146. package/dist/generated/prisma/models/HostProviderOperation.js +1 -0
  147. package/dist/generated/prisma/models/HostProviderTrustedAccount.d.ts +1145 -0
  148. package/dist/generated/prisma/models/HostProviderTrustedAccount.js +1 -0
  149. package/dist/generated/prisma/models/McpServer.d.ts +1500 -0
  150. package/dist/generated/prisma/models/McpServer.js +1 -0
  151. package/dist/generated/prisma/models/NativeGatewayCall.d.ts +39 -1
  152. package/dist/generated/prisma/models/Principal.d.ts +167 -0
  153. package/dist/generated/prisma/models/ResourceGrant.d.ts +1 -1
  154. package/dist/generated/prisma/models/Run.d.ts +503 -1
  155. package/dist/generated/prisma/models/RunStep.d.ts +1453 -0
  156. package/dist/generated/prisma/models/RunStep.js +1 -0
  157. package/dist/generated/prisma/models/Secret.d.ts +387 -0
  158. package/dist/generated/prisma/models/Tool.d.ts +223 -1
  159. package/dist/generated/prisma/models.d.ts +7 -0
  160. package/dist/help/search.js +12 -3
  161. package/dist/help-index.json +812 -52
  162. package/dist/import/create.js +16 -1
  163. package/dist/import/neutral-schema.d.ts +1 -0
  164. package/dist/import/neutral-schema.js +2 -0
  165. package/dist/mcp/auth/access.d.ts +1 -0
  166. package/dist/mcp/auth/grants-cli.d.ts +6 -4
  167. package/dist/mcp/auth/grants-cli.js +42 -3
  168. package/dist/mcp/auth/resource-server.d.ts +3 -1
  169. package/dist/mcp/auth/resource-server.js +17 -2
  170. package/dist/mcp/context.d.ts +6 -0
  171. package/dist/mcp/index.js +21 -0
  172. package/dist/mcp/tools/agents.js +135 -20
  173. package/dist/mcp/tools/grants.js +66 -28
  174. package/dist/mcp/tools/host-providers.d.ts +2 -0
  175. package/dist/mcp/tools/host-providers.js +215 -0
  176. package/dist/mcp/tools/mcp-servers.d.ts +26 -0
  177. package/dist/mcp/tools/mcp-servers.js +481 -0
  178. package/dist/mcp/tools/power.js +2 -2
  179. package/dist/mcp/tools/repositories.js +50 -10
  180. package/dist/mcp/tools/run-access.d.ts +7 -0
  181. package/dist/mcp/tools/run-access.js +14 -0
  182. package/dist/mcp/tools/run-steps.d.ts +16 -0
  183. package/dist/mcp/tools/run-steps.js +85 -0
  184. package/dist/mcp/tools/runs.js +186 -18
  185. package/dist/mcp/tools/secret-elicitation.d.ts +2 -0
  186. package/dist/mcp/tools/secret-elicitation.js +25 -2
  187. package/dist/mcp/tools/secrets.js +38 -4
  188. package/dist/mcp/tools/tools.d.ts +6 -4
  189. package/dist/mcp/tools/tools.js +40 -8
  190. package/dist/mcp/tools/trigger.js +28 -6
  191. package/dist/mcp/transport/http-limits.d.ts +2 -0
  192. package/dist/mcp/transport/http-limits.js +6 -2
  193. package/dist/mcp/webhooks/ingress.js +10 -0
  194. package/dist/native-worker/gateway-server.d.ts +18 -1
  195. package/dist/native-worker/gateway-server.js +38 -9
  196. package/dist/native-worker/gateway.d.ts +5 -0
  197. package/dist/native-worker/gateway.js +13 -1
  198. package/dist/native-worker/http-server.d.ts +6 -4
  199. package/dist/native-worker/http-server.js +53 -8
  200. package/dist/native-worker/ledger.d.ts +6 -2
  201. package/dist/native-worker/ledger.js +5 -4
  202. package/dist/native-worker/protocol.d.ts +1 -0
  203. package/dist/native-worker/protocol.js +4 -1
  204. package/dist/providers/coding-proxy/capability-hash.d.ts +2 -0
  205. package/dist/providers/coding-proxy/capability-hash.js +5 -0
  206. package/dist/providers/coding-proxy/mcp-route.d.ts +34 -0
  207. package/dist/providers/coding-proxy/mcp-route.js +166 -0
  208. package/dist/providers/coding-proxy/memory-ledger.d.ts +2 -0
  209. package/dist/providers/coding-proxy/memory-ledger.js +16 -0
  210. package/dist/providers/coding-proxy/prisma-ledger.d.ts +1 -0
  211. package/dist/providers/coding-proxy/prisma-ledger.js +36 -2
  212. package/dist/providers/coding-proxy/proxy.d.ts +33 -1
  213. package/dist/providers/coding-proxy/proxy.js +266 -75
  214. package/dist/providers/coding-proxy/run-step-forwarder.d.ts +42 -0
  215. package/dist/providers/coding-proxy/run-step-forwarder.js +206 -0
  216. package/dist/providers/coding-proxy/runtime.d.ts +13 -0
  217. package/dist/providers/coding-proxy/runtime.js +87 -4
  218. package/dist/providers/coding-proxy/server.d.ts +3 -0
  219. package/dist/providers/coding-proxy/server.js +10 -0
  220. package/dist/providers/coding-proxy/types.d.ts +18 -0
  221. package/dist/providers/engine/types.d.ts +26 -0
  222. package/dist/providers/executor/composition.js +5 -0
  223. package/dist/providers/executor/container.d.ts +47 -1
  224. package/dist/providers/executor/container.js +84 -8
  225. package/dist/providers/host-tools/invoke.d.ts +37 -0
  226. package/dist/providers/host-tools/invoke.js +117 -0
  227. package/dist/providers/host-tools/operations.d.ts +65 -0
  228. package/dist/providers/host-tools/operations.js +149 -0
  229. package/dist/providers/host-tools/registry.d.ts +79 -0
  230. package/dist/providers/host-tools/registry.js +362 -0
  231. package/dist/providers/host-tools/request-guard.d.ts +17 -0
  232. package/dist/providers/host-tools/request-guard.js +99 -0
  233. package/dist/providers/host-tools/self-test.d.ts +64 -0
  234. package/dist/providers/host-tools/self-test.js +142 -0
  235. package/dist/providers/jobs/docker-isolation.d.ts +2 -2
  236. package/dist/providers/jobs/docker-isolation.js +30 -8
  237. package/dist/providers/jobs/docker.d.ts +8 -0
  238. package/dist/providers/jobs/docker.js +33 -9
  239. package/dist/providers/jobs/kubernetes-isolation.d.ts +3 -1
  240. package/dist/providers/jobs/kubernetes-isolation.js +15 -2
  241. package/dist/providers/jobs/kubernetes.d.ts +13 -0
  242. package/dist/providers/jobs/kubernetes.js +28 -5
  243. package/dist/providers/jobs/types.d.ts +6 -0
  244. package/dist/providers/review-host/types.d.ts +6 -4
  245. package/dist/providers/review-host/types.js +1 -1
  246. package/dist/providers/vcs/git.d.ts +6 -2
  247. package/dist/providers/vcs/git.js +15 -4
  248. package/dist/providers/vcs/github.d.ts +2 -71
  249. package/dist/providers/vcs/github.js +8 -11
  250. package/dist/providers/vcs/index.d.ts +1 -1
  251. package/dist/providers/vcs/index.js +1 -1
  252. package/dist/providers/vcs/remote.d.ts +4 -0
  253. package/dist/providers/vcs/remote.js +7 -1
  254. package/dist/providers/vcs/routing.d.ts +12 -6
  255. package/dist/providers/vcs/routing.js +21 -6
  256. package/dist/providers/vcs/types.d.ts +71 -3
  257. package/dist/quickstart/index.d.ts +5 -0
  258. package/dist/quickstart/index.js +21 -0
  259. package/dist/quickstart-images.json +1 -1
  260. package/dist/sandbox/eval-core.d.ts +12 -2
  261. package/dist/sandbox/eval-core.js +20 -4
  262. package/dist/sandbox/host-functions.d.ts +4 -0
  263. package/dist/sandbox/host-functions.js +7 -2
  264. package/dist/sandbox/limits.d.ts +4 -1
  265. package/dist/sandbox/limits.js +4 -1
  266. package/dist/sandbox/run-in-sandbox.d.ts +6 -0
  267. package/dist/sandbox/run-in-sandbox.js +14 -2
  268. package/dist/sandbox/safe-fetch.d.ts +2 -0
  269. package/dist/sandbox/safe-fetch.js +1 -1
  270. package/dist/sandbox/secret-broker.d.ts +1 -0
  271. package/dist/sandbox/secret-broker.js +1 -1
  272. package/dist/sandbox/tool-timeout.d.ts +32 -0
  273. package/dist/sandbox/tool-timeout.js +52 -0
  274. package/dist/sandbox/user-tool.d.ts +8 -1
  275. package/dist/sandbox/user-tool.js +9 -3
  276. package/dist/serve.js +26 -18
  277. package/dist/viewer/api-schema.d.ts +66 -0
  278. package/dist/viewer/api-schema.js +31 -0
  279. package/dist/viewer/graph.js +2 -1
  280. package/dist/viewer/openapi.js +2 -1
  281. package/dist/viewer/run-detail.d.ts +2 -0
  282. package/dist/viewer/run-detail.js +26 -1
  283. package/dist/wardby-bin.js +5 -0
  284. package/docs/README.md +11 -1
  285. package/docs/agent-recipes.md +37 -4
  286. package/docs/architecture-runtime.md +1 -1
  287. package/docs/claude-code-plugin.md +208 -0
  288. package/docs/code-review-agents.md +6 -0
  289. package/docs/coding-agent-setup.md +94 -1
  290. package/docs/coding-worker-byo-images.md +4 -2
  291. package/docs/coding-worker-isolation.md +94 -7
  292. package/docs/debugging-agent-runs.md +354 -0
  293. package/docs/getting-started-gke.md +33 -11
  294. package/docs/getting-started-identity-provider.md +43 -37
  295. package/docs/getting-started.md +8 -0
  296. package/docs/host-providers.md +294 -0
  297. package/docs/mcp-servers.md +263 -0
  298. package/docs/native-sandbox.md +31 -9
  299. package/docs/security-deployment.md +161 -31
  300. package/docs/tool-secrets.md +3 -0
  301. package/docs/viewer-api.md +27 -12
  302. package/docs/viewer-api.openapi.yml +78 -2
  303. package/help/admin-viewer.md +5 -0
  304. package/help/agent-recipes.md +33 -2
  305. package/help/brokered-secrets.md +3 -0
  306. package/help/build-claude-code-image.md +1 -1
  307. package/help/claude-code-run-pane.md +63 -0
  308. package/help/deploy-gke.md +5 -0
  309. package/help/errors/coding-mcp-gateway-unavailable.md +46 -0
  310. package/help/errors/coding-mcp-pins-unavailable.md +35 -0
  311. package/help/errors/database-auth-invalid.md +1 -1
  312. package/help/errors/host-provider-error.md +40 -0
  313. package/help/errors/host-tool-failed.md +36 -0
  314. package/help/errors/mcp-call-limit.md +22 -0
  315. package/help/errors/mcp-call-timeout.md +22 -0
  316. package/help/errors/mcp-server-auth-failed.md +22 -0
  317. package/help/errors/mcp-server-credentials-unavailable.md +45 -0
  318. package/help/errors/mcp-server-error.md +28 -0
  319. package/help/errors/mcp-server-unreachable.md +22 -0
  320. package/help/errors/mcp-servers-off.md +21 -0
  321. package/help/errors/mcp-tool-not-allowed.md +26 -0
  322. package/help/errors/mcp-tool-removed.md +22 -0
  323. package/help/errors/mcp-unavailable.md +25 -0
  324. package/help/errors/native-sandbox-capacity.md +1 -1
  325. package/help/errors/native-sandbox-deadline-exceeded.md +1 -1
  326. package/help/errors/native-sandbox-docker-failed.md +1 -1
  327. package/help/errors/native-sandbox-gateway-error.md +1 -1
  328. package/help/errors/native-sandbox-gateway-unavailable.md +1 -1
  329. package/help/errors/native-sandbox-image-not-pinned.md +1 -1
  330. package/help/errors/native-sandbox-isolation-mismatch.md +1 -1
  331. package/help/errors/native-sandbox-network-unenforced.md +1 -1
  332. package/help/errors/native-sandbox-requires-catalog.md +1 -1
  333. package/help/errors/native-sandbox-unavailable.md +1 -1
  334. package/help/errors/native-sandbox-warm-delivery-failed.md +1 -1
  335. package/help/errors/native-sandbox-worker-exited.md +1 -1
  336. package/help/errors/native-sandbox-worker-lost.md +1 -1
  337. package/help/errors/native-sandbox-worker-unready.md +1 -1
  338. package/help/errors/repository-not-listed.md +36 -0
  339. package/help/errors/repository-required.md +36 -0
  340. package/help/errors/tool-timeout.md +38 -0
  341. package/help/host-providers.md +40 -0
  342. package/help/identity-and-access.md +4 -1
  343. package/help/low-power-mode.md +19 -8
  344. package/help/mcp-servers.md +73 -0
  345. package/help/mcp.md +9 -2
  346. package/help/native-capabilities.md +27 -1
  347. package/help/native-sandbox.md +1 -1
  348. package/help/operating-agents.md +16 -0
  349. package/help/related-pull-requests.md +7 -3
  350. package/help/repo-instructions-and-skills.md +3 -1
  351. package/help/resume-runs-locally.md +7 -0
  352. package/help/run-steps.md +140 -0
  353. package/help/stop-run.md +1 -1
  354. package/help/troubleshooting/coding-workers.md +9 -1
  355. package/help/troubleshooting/debugging-agent-runs.md +29 -0
  356. package/package.json +2 -1
  357. package/prisma/migrations/20261102000000_host_providers/migration.sql +56 -0
  358. package/prisma/migrations/20261104000000_coding_agent_repository_list/migration.sql +5 -0
  359. package/prisma/migrations/20261106000000_tool_timeout/migration.sql +4 -0
  360. package/prisma/migrations/20261107000000_mcp_servers/migration.sql +33 -0
  361. package/prisma/migrations/20261108000000_coding_mcp/migration.sql +5 -0
  362. package/prisma/migrations/20261109000000_task_run_index/migration.sql +3 -0
  363. package/prisma/migrations/20261110000000_run_steps/migration.sql +26 -0
  364. package/prisma/migrations/20261111000000_run_steps_viewer_notify/migration.sql +59 -0
  365. package/prisma/migrations/20261112000000_run_steps_detail/migration.sql +9 -0
  366. package/prisma/schema.prisma +211 -14
  367. package/scripts/git-askpass.sh +1 -1
@@ -95,7 +95,11 @@ export function createPrivilegedHost(options) {
95
95
  },
96
96
  async __bridge_fetch(argsJson) {
97
97
  const [url, init = {}, secretNames] = args(argsJson);
98
- const policy = { ...sandboxFetchPolicy(allowedFetchHosts ?? []), signal };
98
+ const policy = {
99
+ ...sandboxFetchPolicy(allowedFetchHosts ?? []),
100
+ signal,
101
+ ...(options.fetchTimeoutMs !== undefined ? { timeoutMs: options.fetchTimeoutMs } : {}),
102
+ };
99
103
  if (secretNames === undefined || (Array.isArray(secretNames) && secretNames.length === 0)) {
100
104
  return fetchImpl(url, init, policy);
101
105
  }
@@ -177,13 +181,14 @@ export function createPrivilegedHost(options) {
177
181
  /** Registers the whole sandbox API: the local bridges directly, the privileged ones through `privileged`. */
178
182
  export function installSandboxApi(context, runtime, options) {
179
183
  const { privileged, signal } = options;
184
+ const sleepLimitMs = options.wallTimeLimitMs ?? WALL_TIME_LIMIT_MS;
180
185
  const register = (name, fn) => registerJsonAsyncFunction(context, runtime, name, fn, signal);
181
186
  const parserPool = options.parserPool ?? (sharedParserPool ??= createParserWorkerPool());
182
187
  for (const name of PRIVILEGED_BRIDGE_NAMES)
183
188
  register(name, (argsJson) => privileged[name](argsJson));
184
189
  register("__bridge_sleep", async (argsJson) => {
185
190
  const [ms] = args(argsJson);
186
- if (!Number.isFinite(ms) || ms < 0 || ms > WALL_TIME_LIMIT_MS)
191
+ if (!Number.isFinite(ms) || ms < 0 || ms > sleepLimitMs)
187
192
  throw new Error("sleep_limit");
188
193
  await sleep(ms, undefined, { signal });
189
194
  return null;
@@ -15,7 +15,10 @@ export declare const MAX_INTERRUPT_CHECKS = 20000000;
15
15
  * awaiting a host async call (e.g. a slow `fetch`) — this does.
16
16
  */
17
17
  export declare const WALL_TIME_LIMIT_MS = 10000;
18
- /** Timeout for the sandbox's `fetch` host bridge, independent of the wall-time cap. */
18
+ /**
19
+ * safeFetch's timeout when its caller gives none. A sandboxed tool's fetches get
20
+ * fetchTimeoutFor(the tool's limit) instead (tool-timeout.ts), just past its wall time.
21
+ */
19
22
  export declare const FETCH_TIMEOUT_MS = 8000;
20
23
  /** QuickJS interpreter stack size cap (bytes). */
21
24
  export declare const MAX_STACK_SIZE_BYTES: number;
@@ -15,7 +15,10 @@ export const MAX_INTERRUPT_CHECKS = 20_000_000;
15
15
  * awaiting a host async call (e.g. a slow `fetch`) — this does.
16
16
  */
17
17
  export const WALL_TIME_LIMIT_MS = 10_000;
18
- /** Timeout for the sandbox's `fetch` host bridge, independent of the wall-time cap. */
18
+ /**
19
+ * safeFetch's timeout when its caller gives none. A sandboxed tool's fetches get
20
+ * fetchTimeoutFor(the tool's limit) instead (tool-timeout.ts), just past its wall time.
21
+ */
19
22
  export const FETCH_TIMEOUT_MS = 8_000;
20
23
  /** QuickJS interpreter stack size cap (bytes). */
21
24
  export const MAX_STACK_SIZE_BYTES = 1024 * 1024; // 1 MiB
@@ -27,6 +27,8 @@ export interface SandboxInvocation {
27
27
  toolName: string;
28
28
  /** Overrides the default limits (limits.ts) — mainly for fast, deterministic tests. */
29
29
  limits?: Partial<SandboxLimits>;
30
+ /** The tool's time limit (effectiveToolTimeoutMs); its fetches time out just before it. Overrides limits.wallTimeLimitMs. */
31
+ timeoutMs?: number;
30
32
  /** Omitted for a dry run (no real agent) — secrets.get always resolves undefined. */
31
33
  secrets?: SecretsAccessor;
32
34
  /** Overrides the default shared logger — mainly for tests that need to capture forwarded console output. */
@@ -42,12 +44,16 @@ export interface ToolCodeInvocation {
42
44
  code: string;
43
45
  params: unknown;
44
46
  limits?: Partial<SandboxLimits>;
47
+ /** Stops the call early (its run was stopped): it fails as `cancelled`, and its host calls are aborted. */
48
+ signal?: AbortSignal;
45
49
  /** Built once per invocation, with that invocation's abort signal (fetch needs it). */
46
50
  privileged: (signal: AbortSignal) => PrivilegedHost;
47
51
  parserPool?: ParserWorkerPool;
48
52
  }
49
53
  /** Evaluates one tool body in a fresh QuickJS context against the given privileged host. */
50
54
  export declare function runToolCode(invocation: ToolCodeInvocation): Promise<SandboxResult>;
55
+ /** The sandbox limits for a tool whose time limit is `timeoutMs` (absent: the defaults). */
56
+ export declare function toolLimits(timeoutMs: number | undefined, base?: Partial<SandboxLimits>): Partial<SandboxLimits>;
51
57
  /** The in-process privileged host for a sandbox invocation. */
52
58
  export declare function privilegedHostFor(invocation: SandboxInvocation, signal: AbortSignal): PrivilegedHost;
53
59
  export declare function runInSandbox(invocation: SandboxInvocation): Promise<SandboxResult>;
@@ -9,7 +9,8 @@
9
9
  */
10
10
  import { createPrivilegedHost, installSandboxApi } from "./host-functions.js";
11
11
  import { SANDBOX_PRELUDE } from "./prelude.js";
12
- import { evalToJson, NON_SERIALIZABLE_MARKER } from "./eval-core.js";
12
+ import { DEFAULT_SANDBOX_LIMITS, evalToJson, NON_SERIALIZABLE_MARKER, } from "./eval-core.js";
13
+ import { fetchTimeoutFor, TOOL_RUN_STOPPED_MESSAGE, toolTimeoutMessage } from "./tool-timeout.js";
13
14
  import { boundedJson, boundedString } from "./bounded-json.js";
14
15
  import { BRIDGE_INPUT_BYTES } from "./limits.js";
15
16
  /** Evaluates one tool body in a fresh QuickJS context against the given privileged host. */
@@ -39,14 +40,24 @@ export async function runToolCode(invocation) {
39
40
  throw new Error("${NON_SERIALIZABLE_MARKER}" + (__serializeErr && __serializeErr.message));
40
41
  }
41
42
  })()`;
43
+ const wallTimeLimitMs = invocation.limits?.wallTimeLimitMs ?? DEFAULT_SANDBOX_LIMITS.wallTimeLimitMs;
42
44
  return evalToJson(code, invocation.limits, (context, runtime, signal) => {
43
45
  installSandboxApi(context, runtime, {
44
46
  privileged: invocation.privileged(signal),
45
47
  signal,
46
48
  parserPool: invocation.parserPool,
49
+ wallTimeLimitMs,
47
50
  });
51
+ }, {
52
+ signal: invocation.signal,
53
+ aborted: () => ({ ok: false, errorKind: "cancelled", errorMessage: TOOL_RUN_STOPPED_MESSAGE }),
54
+ timeoutMessage: toolTimeoutMessage,
48
55
  });
49
56
  }
57
+ /** The sandbox limits for a tool whose time limit is `timeoutMs` (absent: the defaults). */
58
+ export function toolLimits(timeoutMs, base) {
59
+ return timeoutMs === undefined ? { ...base } : { ...base, wallTimeLimitMs: timeoutMs };
60
+ }
50
61
  /** The in-process privileged host for a sandbox invocation. */
51
62
  export function privilegedHostFor(invocation, signal) {
52
63
  return createPrivilegedHost({
@@ -59,13 +70,14 @@ export function privilegedHostFor(invocation, signal) {
59
70
  allowedFetchHosts: invocation.allowedFetchHosts,
60
71
  fetchImpl: invocation.fetchImpl,
61
72
  signal,
73
+ fetchTimeoutMs: fetchTimeoutFor(invocation.timeoutMs ?? invocation.limits?.wallTimeLimitMs ?? DEFAULT_SANDBOX_LIMITS.wallTimeLimitMs),
62
74
  });
63
75
  }
64
76
  export async function runInSandbox(invocation) {
65
77
  return runToolCode({
66
78
  code: invocation.code,
67
79
  params: invocation.params,
68
- limits: invocation.limits,
80
+ limits: toolLimits(invocation.timeoutMs, invocation.limits),
69
81
  privileged: (signal) => privilegedHostFor(invocation, signal),
70
82
  });
71
83
  }
@@ -11,6 +11,8 @@ export declare function pinnedLookup(destination: Destination): LookupFunction;
11
11
  export declare function requestPinned(destination: Destination, init: SafeFetchInit, signal: AbortSignal): Promise<IncomingMessage>;
12
12
  export interface SafeFetchOptions extends FetchPolicyOptions {
13
13
  signal?: AbortSignal;
14
+ /** This fetch's own timeout; FETCH_TIMEOUT_MS when absent. */
15
+ timeoutMs?: number;
14
16
  connect?: typeof requestPinned;
15
17
  /** False: a 3xx is returned to the caller as-is (brokered requests re-check every hop themselves). */
16
18
  followRedirects?: boolean;
@@ -39,7 +39,7 @@ async function untilAbort(promise, signal) {
39
39
  }
40
40
  export async function safeFetch(input, init = {}, options = {}) {
41
41
  const abort = new AbortController();
42
- const timer = setTimeout(() => abort.abort(new Error("fetch_timeout")), FETCH_TIMEOUT_MS);
42
+ const timer = setTimeout(() => abort.abort(new Error("fetch_timeout")), options.timeoutMs ?? FETCH_TIMEOUT_MS);
43
43
  const signal = options.signal ? AbortSignal.any([options.signal, abort.signal]) : abort.signal;
44
44
  let url = input;
45
45
  let method = init.method ?? "GET";
@@ -26,6 +26,7 @@ export declare function consoleRedactionValues(entry: {
26
26
  value: string;
27
27
  broker: SecretBrokerConfig | null;
28
28
  }): string[];
29
+ export declare function encodedForms(value: string): string[];
29
30
  export declare function scrubBrokeredResponse<T extends {
30
31
  headers: Record<string, string>;
31
32
  bodyBase64: string;
@@ -121,7 +121,7 @@ export function consoleRedactionValues(entry) {
121
121
  return [entry.value];
122
122
  }
123
123
  }
124
- function encodedForms(value) {
124
+ export function encodedForms(value) {
125
125
  const buf = Buffer.from(value, "utf8");
126
126
  // Query placement goes through URLSearchParams, which encodes ~ ! ' ( ) differently and space as "+",
127
127
  // so both percent-encodings are scrubbed, each also in lowercase-hex form (a response may echo either).
@@ -0,0 +1,32 @@
1
+ /**
2
+ * A sandboxed tool's time limit. A tool may ask for its own `timeoutMs`; the operator's
3
+ * WARDBY_TOOL_MAX_TIMEOUT_MS (config/tool-timeout.ts) caps it. Without one a tool keeps the
4
+ * default wall-time cap (limits.ts). Pure and worker-safe: no configuration is read here.
5
+ */
6
+ /** A tool that sets no `timeoutMs`. */
7
+ export declare const DEFAULT_TOOL_TIMEOUT_MS = 10000;
8
+ /** The shortest `timeoutMs` a tool may ask for. */
9
+ export declare const MIN_TOOL_TIMEOUT_MS = 1000;
10
+ /** WARDBY_TOOL_MAX_TIMEOUT_MS when unset: five minutes. */
11
+ export declare const DEFAULT_TOOL_MAX_TIMEOUT_MS = 300000;
12
+ /**
13
+ * How far past the tool's limit each fetch's own timeout lies. The wall time ends the call (and
14
+ * aborts its fetches) first, so a slow request fails with the wall-time error that names the
15
+ * limit, not a fetch error.
16
+ */
17
+ export declare const FETCH_TIMEOUT_MARGIN_MS = 1000;
18
+ /** The fetch timeout for a tool limit of `wallTimeLimitMs`. */
19
+ export declare function fetchTimeoutFor(wallTimeLimitMs: number): number;
20
+ /**
21
+ * The limit a run uses for a tool: its own `timeoutMs` (or the default when it has none), never
22
+ * above the current ceiling, so lowering the ceiling also covers tools that already exist.
23
+ */
24
+ export declare function effectiveToolTimeoutMs(timeoutMs: number | null | undefined, ceilingMs: number): number;
25
+ /** Why `timeoutMs` can't be stored on a tool under `ceilingMs`, or undefined when it can. */
26
+ export declare function toolTimeoutProblem(timeoutMs: unknown, ceilingMs: number): string | undefined;
27
+ /** The model-facing text for a tool that ran out of time. */
28
+ export declare function toolTimeoutMessage(limitMs: number): string;
29
+ /** The model-facing text for a tool call cut off at its run's deadline. */
30
+ export declare const TOOL_RUN_DEADLINE_MESSAGE = "Stopped at the run's deadline while this tool call was still running.";
31
+ /** The model-facing text for a tool call aborted because its run was stopped. */
32
+ export declare const TOOL_RUN_STOPPED_MESSAGE = "The run was stopped while this tool call was running.";
@@ -0,0 +1,52 @@
1
+ /**
2
+ * A sandboxed tool's time limit. A tool may ask for its own `timeoutMs`; the operator's
3
+ * WARDBY_TOOL_MAX_TIMEOUT_MS (config/tool-timeout.ts) caps it. Without one a tool keeps the
4
+ * default wall-time cap (limits.ts). Pure and worker-safe: no configuration is read here.
5
+ */
6
+ import { WALL_TIME_LIMIT_MS } from "./limits.js";
7
+ /** A tool that sets no `timeoutMs`. */
8
+ export const DEFAULT_TOOL_TIMEOUT_MS = WALL_TIME_LIMIT_MS;
9
+ /** The shortest `timeoutMs` a tool may ask for. */
10
+ export const MIN_TOOL_TIMEOUT_MS = 1_000;
11
+ /** WARDBY_TOOL_MAX_TIMEOUT_MS when unset: five minutes. */
12
+ export const DEFAULT_TOOL_MAX_TIMEOUT_MS = 300_000;
13
+ /**
14
+ * How far past the tool's limit each fetch's own timeout lies. The wall time ends the call (and
15
+ * aborts its fetches) first, so a slow request fails with the wall-time error that names the
16
+ * limit, not a fetch error.
17
+ */
18
+ export const FETCH_TIMEOUT_MARGIN_MS = 1_000;
19
+ /** The fetch timeout for a tool limit of `wallTimeLimitMs`. */
20
+ export function fetchTimeoutFor(wallTimeLimitMs) {
21
+ return wallTimeLimitMs + FETCH_TIMEOUT_MARGIN_MS;
22
+ }
23
+ /**
24
+ * The limit a run uses for a tool: its own `timeoutMs` (or the default when it has none), never
25
+ * above the current ceiling, so lowering the ceiling also covers tools that already exist.
26
+ */
27
+ export function effectiveToolTimeoutMs(timeoutMs, ceilingMs) {
28
+ if (timeoutMs === null || timeoutMs === undefined)
29
+ return DEFAULT_TOOL_TIMEOUT_MS;
30
+ return Math.min(timeoutMs, ceilingMs);
31
+ }
32
+ /** Why `timeoutMs` can't be stored on a tool under `ceilingMs`, or undefined when it can. */
33
+ export function toolTimeoutProblem(timeoutMs, ceilingMs) {
34
+ if (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs)) {
35
+ return "timeoutMs must be a whole number of milliseconds.";
36
+ }
37
+ if (timeoutMs < MIN_TOOL_TIMEOUT_MS)
38
+ return `timeoutMs must be at least ${MIN_TOOL_TIMEOUT_MS} ms.`;
39
+ if (timeoutMs > ceilingMs) {
40
+ return `timeoutMs ${timeoutMs} is above this server's ceiling of ${ceilingMs} ms (WARDBY_TOOL_MAX_TIMEOUT_MS).`;
41
+ }
42
+ return undefined;
43
+ }
44
+ /** The model-facing text for a tool that ran out of time. */
45
+ export function toolTimeoutMessage(limitMs) {
46
+ return (`Exceeded the ${limitMs}ms wall-time limit. A tool that needs longer can set timeoutMs ` +
47
+ "(create_tool or update_tool), up to the server's WARDBY_TOOL_MAX_TIMEOUT_MS.");
48
+ }
49
+ /** The model-facing text for a tool call cut off at its run's deadline. */
50
+ export const TOOL_RUN_DEADLINE_MESSAGE = "Stopped at the run's deadline while this tool call was still running.";
51
+ /** The model-facing text for a tool call aborted because its run was stopped. */
52
+ export const TOOL_RUN_STOPPED_MESSAGE = "The run was stopped while this tool call was running.";
@@ -10,5 +10,12 @@ import type { SandboxLimits } from "./eval-core.js";
10
10
  export interface UserToolDefinition {
11
11
  code: string;
12
12
  paramsZod: string;
13
+ /** The time limit this run gives the tool (effectiveToolTimeoutMs); absent = the default. */
14
+ timeoutMs?: number;
13
15
  }
14
- export declare function runUserToolCall(tool: UserToolDefinition, argsJson: string, privileged: (signal: AbortSignal) => PrivilegedHost, limits?: Partial<SandboxLimits>): Promise<string>;
16
+ export interface UserToolCallOptions {
17
+ limits?: Partial<SandboxLimits>;
18
+ /** Aborts the call when its run is stopped. */
19
+ signal?: AbortSignal;
20
+ }
21
+ export declare function runUserToolCall(tool: UserToolDefinition, argsJson: string, privileged: (signal: AbortSignal) => PrivilegedHost, options?: UserToolCallOptions): Promise<string>;
@@ -5,9 +5,9 @@
5
5
  * JSON error result. Worker-safe: it touches no database or provider, only the
6
6
  * privileged host it is given, so the native sandbox worker runs it as is.
7
7
  */
8
- import { runToolCode } from "./run-in-sandbox.js";
8
+ import { runToolCode, toolLimits } from "./run-in-sandbox.js";
9
9
  import { validateParams } from "./zod-params.js";
10
- export async function runUserToolCall(tool, argsJson, privileged, limits) {
10
+ export async function runUserToolCall(tool, argsJson, privileged, options = {}) {
11
11
  let parsedArgs;
12
12
  try {
13
13
  // Some providers stream no JSON delta at all for a zero-parameter
@@ -24,7 +24,13 @@ export async function runUserToolCall(tool, argsJson, privileged, limits) {
24
24
  if (!validation.ok) {
25
25
  return JSON.stringify({ error: "validation_failed", message: validation.errorMessage });
26
26
  }
27
- const result = await runToolCode({ code: tool.code, params: validation.value, limits, privileged });
27
+ const result = await runToolCode({
28
+ code: tool.code,
29
+ params: validation.value,
30
+ limits: toolLimits(tool.timeoutMs, options.limits),
31
+ signal: options.signal,
32
+ privileged,
33
+ });
28
34
  if (!result.ok) {
29
35
  return JSON.stringify({ error: result.errorKind, message: result.errorMessage });
30
36
  }
package/dist/serve.js CHANGED
@@ -1,21 +1,3 @@
1
- /**
2
- * `wardby serve`: the complete wardby process. Runs the MCP server, the
3
- * scheduler, and the reconciler together on ONE provider set and ONE
4
- * executor, which is what a single-container deployment (Cloud Run, a lone
5
- * VM) needs - `wardby mcp` alone never fires a schedule.
6
- *
7
- * Why one executor: DbosExecutor is a per-process singleton and refuses to
8
- * launch twice under different ids (providers/executor/dbos.ts), and its
9
- * recovery decisions key on that id. Building providers once and sharing
10
- * them is the whole trick; it also gives scheduled native runs the same
11
- * `providers.executor` wiring MCP-triggered runs get, so sub-agent dispatch
12
- * from a scheduled run works (it does not from `wardby scheduler`).
13
- *
14
- * Why the scheduler and reconciler can run in every replica: the scheduler
15
- * elects one ticker via a Postgres lease and enforces at-most-once with row
16
- * locks regardless (core/scheduler.ts); the reconciler is deliberately not
17
- * lease-gated and is safe under concurrency (core/reconciler.ts).
18
- */
19
1
  import { loadCodingConcurrencyConfig, loadMcpConfig } from "./config/providers.js";
20
2
  import { drainCodingQueue } from "./core/coding-queue.js";
21
3
  import { prisma } from "./core/db.js";
@@ -28,6 +10,8 @@ import { markRunFailedFromExecutorError } from "./core/dispatch.js";
28
10
  import { startScheduler } from "./core/scheduler.js";
29
11
  import { drainDeferredRuns } from "./providers/executor/deferred.js";
30
12
  import { resolveDeferredCodingRun } from "./core/deferred-coding.js";
13
+ import { RunStepSweeper } from "./core/run-steps/sweep.js";
14
+ import { parseRunStepsListen, startRunStepIngress } from "./core/run-steps/ingress.js";
31
15
  import { buildMcpProviders, startMcp } from "./mcp/index.js";
32
16
  const serveLog = logger.child({ module: "serve" });
33
17
  export async function startServe(options = {}) {
@@ -39,6 +23,8 @@ export async function startServe(options = {}) {
39
23
  // Parsed before anything starts, so a malformed CODING_MAX_CONCURRENT or
40
24
  // CODING_QUEUE_TIMEOUT_SEC fails fast instead of after HTTP is listening.
41
25
  const concurrency = loadCodingConcurrencyConfig();
26
+ // Run steps from the coding proxy (POST /internal/run-steps); parsed early so a bad value stops start-up.
27
+ const runStepsListen = parseRunStepsListen(process.env.WARDBY_RUN_STEPS_LISTEN);
42
28
  const providers = options.providers ?? buildMcpProviders().providers;
43
29
  const power = providers.power ?? DISABLED_POWER;
44
30
  if (power.enabled) {
@@ -48,6 +34,21 @@ export async function startServe(options = {}) {
48
34
  }, (err) => serveLog.warn({ err }, "low_power_access_check_failed"));
49
35
  }
50
36
  const mcp = await startMcp({ providers, schedulerAttached: true });
37
+ // Its own port (not the MCP listener's): only the coding proxy reaches it.
38
+ let runStepIngress;
39
+ if (runStepsListen && providers.runSteps) {
40
+ try {
41
+ runStepIngress = await startRunStepIngress(runStepsListen, { db: prisma, sink: providers.runSteps });
42
+ }
43
+ catch (err) {
44
+ await mcp.close();
45
+ throw err;
46
+ }
47
+ serveLog.info({ host: runStepsListen.host, port: runStepsListen.port }, "run steps listener started");
48
+ }
49
+ else if (runStepsListen) {
50
+ serveLog.warn("WARDBY_RUN_STEPS_LISTEN is set but this server has no run-steps writer; not listening");
51
+ }
51
52
  const reviewHosts = providers.reviewHosts;
52
53
  const reconciler = startReconciler({
53
54
  executor: providers.executor,
@@ -67,6 +68,8 @@ export async function startServe(options = {}) {
67
68
  });
68
69
  const notifications = await startNotifications({ db: prisma, chat: providers.chat ?? {} });
69
70
  const selfDefects = { db: prisma, issueTrackers: providers.issueTrackers };
71
+ // Expired run steps (RUN_STEP_RETENTION_DAYS), from the scheduler leader only.
72
+ const runStepSweeper = new RunStepSweeper(prisma);
70
73
  const scheduler = startScheduler({
71
74
  executor: providers.executor,
72
75
  scope: options.scope,
@@ -82,6 +85,7 @@ export async function startServe(options = {}) {
82
85
  });
83
86
  // Scale idle coding-proxy/native-gateway Deployments to zero (no-op unless WARDBY_LOW_POWER=1).
84
87
  await power.sleepIfIdle().catch((err) => serveLog.warn({ err }, "power_sleep_failed"));
88
+ await runStepSweeper.maybeSweep();
85
89
  },
86
90
  });
87
91
  return {
@@ -92,6 +96,10 @@ export async function startServe(options = {}) {
92
96
  scheduler.stop();
93
97
  reconciler.stop();
94
98
  notifications.stop();
99
+ // Before mcp.close(), which flushes the run-steps writer: no batch arrives after the flush.
100
+ const ingress = runStepIngress;
101
+ if (ingress)
102
+ await new Promise((resolve) => ingress.close(() => resolve()));
95
103
  await mcp.close();
96
104
  },
97
105
  };
@@ -320,6 +320,21 @@ export declare const GraphSnapshotSchema: z.ZodObject<{
320
320
  }, z.core.$strip>>;
321
321
  }, z.core.$strip>;
322
322
  }, z.core.$strip>;
323
+ /**
324
+ * One recorded run step (docs/viewer-api.md): a tool call or what the model said, one line. Stored
325
+ * already redacted (src/core/run-steps/writer.ts); a step's call id and detail text are never sent.
326
+ */
327
+ export declare const RunStepSchema: z.ZodObject<{
328
+ seq: z.ZodNumber;
329
+ at: z.ZodString;
330
+ kind: z.ZodEnum<{
331
+ tool: "tool";
332
+ said: "said";
333
+ }>;
334
+ tool: z.ZodNullable<z.ZodString>;
335
+ summary: z.ZodString;
336
+ status: z.ZodNullable<z.ZodString>;
337
+ }, z.core.$strip>;
323
338
  export declare const RunDetailSchema: z.ZodObject<{
324
339
  id: z.ZodString;
325
340
  parentRunId: z.ZodNullable<z.ZodString>;
@@ -429,6 +444,19 @@ export declare const RunDetailSchema: z.ZodObject<{
429
444
  error: z.ZodNullable<z.ZodString>;
430
445
  finalText: z.ZodNullable<z.ZodString>;
431
446
  childRunIds: z.ZodArray<z.ZodString>;
447
+ steps: z.ZodArray<z.ZodObject<{
448
+ seq: z.ZodNumber;
449
+ at: z.ZodString;
450
+ kind: z.ZodEnum<{
451
+ tool: "tool";
452
+ said: "said";
453
+ }>;
454
+ tool: z.ZodNullable<z.ZodString>;
455
+ summary: z.ZodString;
456
+ status: z.ZodNullable<z.ZodString>;
457
+ }, z.core.$strip>>;
458
+ stepsOmitted: z.ZodNumber;
459
+ stepsDropped: z.ZodNumber;
432
460
  coding: z.ZodNullable<z.ZodObject<{
433
461
  provider: z.ZodString;
434
462
  repository: z.ZodString;
@@ -484,6 +512,18 @@ export declare const ViewerEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
484
512
  issue_status: "issue_status";
485
513
  host_check: "host_check";
486
514
  }>;
515
+ }, z.core.$strip>, z.ZodObject<{
516
+ kind: z.ZodLiteral<"step">;
517
+ runId: z.ZodString;
518
+ seq: z.ZodNumber;
519
+ stepKind: z.ZodEnum<{
520
+ tool: "tool";
521
+ said: "said";
522
+ }>;
523
+ tool: z.ZodNullable<z.ZodString>;
524
+ summary: z.ZodString;
525
+ status: z.ZodNullable<z.ZodString>;
526
+ at: z.ZodString;
487
527
  }, z.core.$strip>], "kind">;
488
528
  export type RunTriggerInfo = z.infer<typeof RunTriggerSchema>;
489
529
  export type Outcome = z.infer<typeof OutcomeSchema>;
@@ -491,6 +531,7 @@ export type ServiceStatus = z.infer<typeof ServiceStatusSchema>;
491
531
  export type GraphRun = z.infer<typeof GraphRunSchema>;
492
532
  export type GraphSnapshot = z.infer<typeof GraphSnapshotSchema>;
493
533
  export type RunDetail = z.infer<typeof RunDetailSchema>;
534
+ export type RunStepView = z.infer<typeof RunStepSchema>;
494
535
  export type ViewerEvent = z.infer<typeof ViewerEventSchema>;
495
536
  /** How this deployment runs coding jobs, for the desktop viewer's Infrastructure view (GET /admin/api/infra). */
496
537
  export declare const InfraInfoSchema: z.ZodObject<{
@@ -758,6 +799,19 @@ export declare const VIEWER_SCHEMAS: {
758
799
  error: z.ZodNullable<z.ZodString>;
759
800
  finalText: z.ZodNullable<z.ZodString>;
760
801
  childRunIds: z.ZodArray<z.ZodString>;
802
+ steps: z.ZodArray<z.ZodObject<{
803
+ seq: z.ZodNumber;
804
+ at: z.ZodString;
805
+ kind: z.ZodEnum<{
806
+ tool: "tool";
807
+ said: "said";
808
+ }>;
809
+ tool: z.ZodNullable<z.ZodString>;
810
+ summary: z.ZodString;
811
+ status: z.ZodNullable<z.ZodString>;
812
+ }, z.core.$strip>>;
813
+ stepsOmitted: z.ZodNumber;
814
+ stepsDropped: z.ZodNumber;
761
815
  coding: z.ZodNullable<z.ZodObject<{
762
816
  provider: z.ZodString;
763
817
  repository: z.ZodString;
@@ -813,6 +867,18 @@ export declare const VIEWER_SCHEMAS: {
813
867
  issue_status: "issue_status";
814
868
  host_check: "host_check";
815
869
  }>;
870
+ }, z.core.$strip>, z.ZodObject<{
871
+ kind: z.ZodLiteral<"step">;
872
+ runId: z.ZodString;
873
+ seq: z.ZodNumber;
874
+ stepKind: z.ZodEnum<{
875
+ tool: "tool";
876
+ said: "said";
877
+ }>;
878
+ tool: z.ZodNullable<z.ZodString>;
879
+ summary: z.ZodString;
880
+ status: z.ZodNullable<z.ZodString>;
881
+ at: z.ZodString;
816
882
  }, z.core.$strip>], "kind">;
817
883
  readonly "infra-info": z.ZodObject<{
818
884
  launcher: z.ZodEnum<{
@@ -123,10 +123,30 @@ export const GraphSnapshotSchema = z.object({
123
123
  runs: z.array(GraphRunSchema),
124
124
  spend: z.object({ todayUsd: z.number(), groups: z.array(BudgetGroupSpendSchema) }),
125
125
  });
126
+ /**
127
+ * One recorded run step (docs/viewer-api.md): a tool call or what the model said, one line. Stored
128
+ * already redacted (src/core/run-steps/writer.ts); a step's call id and detail text are never sent.
129
+ */
130
+ export const RunStepSchema = z.object({
131
+ seq: z.number().int(),
132
+ at: z.string(),
133
+ kind: z.enum(["tool", "said"]),
134
+ /** The tool's display name (`Bash`, `Edit`, `mcp:<tool>`); null for said. */
135
+ tool: z.string().nullable(),
136
+ summary: z.string(),
137
+ /** `ok`, `error` or `exit N` once the result is back; null until then and for said. */
138
+ status: z.string().nullable(),
139
+ });
126
140
  export const RunDetailSchema = GraphRunSchema.extend({
127
141
  error: z.string().nullable(),
128
142
  finalText: z.string().nullable(),
129
143
  childRunIds: z.array(z.string()),
144
+ /** The run's newest steps (at most 200), oldest first; see `stepsOmitted` for earlier ones. */
145
+ steps: z.array(RunStepSchema),
146
+ /** Earlier recorded steps not included in `steps`. */
147
+ stepsOmitted: z.number().int(),
148
+ /** Steps past the per-run limit, never recorded. */
149
+ stepsDropped: z.number().int(),
130
150
  coding: z
131
151
  .object({
132
152
  provider: z.string(),
@@ -170,6 +190,17 @@ export const ViewerEventSchema = z.discriminatedUnion("kind", [
170
190
  /** Which table changed (the viewer NOTIFY trigger names them). */
171
191
  source: z.enum(["pull_request", "host_status", "issue_status", "host_check"]),
172
192
  }),
193
+ /** A run recorded a step, or a step's status arrived (the RunStep NOTIFY trigger). */
194
+ z.object({
195
+ kind: z.literal("step"),
196
+ runId: z.string(),
197
+ seq: RunStepSchema.shape.seq,
198
+ stepKind: RunStepSchema.shape.kind,
199
+ tool: RunStepSchema.shape.tool,
200
+ summary: RunStepSchema.shape.summary,
201
+ status: RunStepSchema.shape.status,
202
+ at: RunStepSchema.shape.at,
203
+ }),
173
204
  ]);
174
205
  /** How this deployment runs coding jobs, for the desktop viewer's Infrastructure view (GET /admin/api/infra). */
175
206
  export const InfraInfoSchema = z.object({
@@ -1,4 +1,5 @@
1
1
  import { periodStart } from "../core/budget-groups.js";
2
+ import { codeProviderOf } from "../coding/code-host.js";
2
3
  import { publicCodingRunResult } from "../coding/protocol.js";
3
4
  const DEFAULT_SINCE_MS = 60 * 60 * 1000;
4
5
  const RELATIVE_SINCE_MS = {
@@ -102,7 +103,7 @@ function outcomesFor(row, pullRequests, sites) {
102
103
  !pullRequests.some((pr) => pr.url === result.pullRequestUrl)) {
103
104
  outcomes.push({
104
105
  kind: "pull_request",
105
- provider: "github",
106
+ provider: codeProviderOf(result.repository),
106
107
  repository: result.repository,
107
108
  number: result.pullRequestNumber,
108
109
  url: result.pullRequestUrl,
@@ -59,7 +59,7 @@ export const VIEWER_ROUTES = [
59
59
  path: "/admin/api/runs/{id}",
60
60
  operationId: "getRun",
61
61
  summary: "Run detail",
62
- description: "Full detail for one run, including final text.",
62
+ description: "Full detail for one run, including final text and its newest steps.",
63
63
  parameters: [{ name: "id", in: "path", required: true, description: "Run id.", schema: { type: "string" } }],
64
64
  responses: {
65
65
  "200": { description: "The run.", content: { "application/json": { schema: ref("run-detail") } } },
@@ -85,6 +85,7 @@ export const VIEWER_ROUTES = [
85
85
  "- `retry: 3000`: suggested client reconnect delay in milliseconds.",
86
86
  '- `event: hello`: sent first. Data is `{"connected": <bool>}`, whether the server\'s live-event connection is up.',
87
87
  "- `event: run`, `event: service`, `event: outcome`: a run, coding-run service, or outcome changed. Each carries an `id:` line. Payloads match the `viewer-event` schema (see `x-event-schema`).",
88
+ "- `event: step`: a run recorded a step, or a step's status arrived. Carries an `id:` line. Data: the step's `seq`, `stepKind` (`tool` or `said`), `tool`, `summary` (redacted when it was recorded), `status` and `at`, never tool output.",
88
89
  '- `event: status`: the server\'s live-event connection changed. Data is `{"connected": <bool>}`.',
89
90
  "- `event: resync`: the live-event connection is up (again). Events may have been missed: refetch the graph.",
90
91
  `- \`: ping\`: comment sent every ${SSE_HEARTBEAT_MS / 1000} seconds to keep the connection open.`,
@@ -2,4 +2,6 @@
2
2
  import type { PrismaClient } from "#prisma";
3
3
  import type { RunDetail } from "./api-schema.js";
4
4
  import { type IssueSites } from "./graph.js";
5
+ /** The most steps a run detail carries: the newest, oldest first. */
6
+ export declare const RUN_DETAIL_STEPS = 200;
5
7
  export declare function loadRunDetail(db: PrismaClient, runId: string, issueSites?: IssueSites): Promise<RunDetail | null>;
@@ -11,16 +11,19 @@ function serviceDetails(snapshot) {
11
11
  envNames: isRecord(s.testEnv) ? Object.keys(s.testEnv).sort() : [],
12
12
  }));
13
13
  }
14
+ /** The most steps a run detail carries: the newest, oldest first. */
15
+ export const RUN_DETAIL_STEPS = 200;
14
16
  export async function loadRunDetail(db, runId, issueSites = {}) {
15
17
  const [graphRun] = await loadGraphRuns(db, [runId], issueSites);
16
18
  if (!graphRun)
17
19
  return null;
18
- const [row, children] = await Promise.all([
20
+ const [row, children, newest, stepCount] = await Promise.all([
19
21
  db.run.findUniqueOrThrow({
20
22
  where: { id: runId },
21
23
  select: {
22
24
  error: true,
23
25
  finalText: true,
26
+ stepsDropped: true,
24
27
  codingRun: {
25
28
  select: {
26
29
  provider: true,
@@ -39,13 +42,35 @@ export async function loadRunDetail(db, runId, issueSites = {}) {
39
42
  select: { id: true },
40
43
  orderBy: [{ startedAt: "asc" }, { id: "asc" }],
41
44
  }),
45
+ // Stored already redacted; the call id and the detail text are never selected.
46
+ db.runStep.findMany({
47
+ where: { runId },
48
+ orderBy: { seq: "desc" },
49
+ take: RUN_DETAIL_STEPS,
50
+ select: { seq: true, at: true, kind: true, tool: true, summary: true, status: true },
51
+ }),
52
+ db.runStep.count({ where: { runId } }),
42
53
  ]);
54
+ const steps = newest
55
+ .reverse()
56
+ .filter((s) => s.kind === "tool" || s.kind === "said")
57
+ .map((s) => ({
58
+ seq: s.seq,
59
+ at: s.at.toISOString(),
60
+ kind: s.kind,
61
+ tool: s.tool,
62
+ summary: s.summary,
63
+ status: s.status,
64
+ }));
43
65
  const coding = row.codingRun;
44
66
  return {
45
67
  ...graphRun,
46
68
  error: row.error,
47
69
  finalText: row.finalText,
48
70
  childRunIds: children.map((c) => c.id),
71
+ steps,
72
+ stepsOmitted: Math.max(0, stepCount - newest.length),
73
+ stepsDropped: row.stepsDropped,
49
74
  coding: coding
50
75
  ? {
51
76
  provider: coding.provider,