@coreplane/switchboard 1.204.0 → 1.205.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 (372) hide show
  1. package/dist/assets/config/config.example.yaml +32 -3
  2. package/dist/assets/deploy/cloudflare-memory/worker.ts +129 -3
  3. package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +7 -2
  4. package/dist/assets/deploy/cloudflare-sandbox/worker.ts +4 -1
  5. package/dist/assets/package-lock.json +291 -97
  6. package/dist/assets/package.json +1 -1
  7. package/dist/assets/source.json +3 -3
  8. package/dist/assets/src/agents/registry.ts +466 -0
  9. package/dist/assets/src/config/profile.ts +200 -0
  10. package/dist/assets/src/core/delivery.ts +775 -0
  11. package/dist/assets/src/core/deliverySnapshotStore.ts +200 -0
  12. package/dist/assets/src/core/runLedger/types.ts +6 -0
  13. package/dist/assets/src/core/runRecord.ts +44 -0
  14. package/dist/assets/src/core/ship/contract.ts +502 -0
  15. package/dist/assets/src/core/workerError.ts +12 -0
  16. package/dist/assets/web/dist/.vite/manifest.json +2770 -70
  17. package/dist/assets/web/dist/assets/AppShell-lYVcj-k7.js +1 -0
  18. package/dist/assets/web/dist/assets/{CostsPage-XQ-fQzdz.js → CostsPage-DGSE48l1.js} +1 -1
  19. package/dist/assets/web/dist/assets/DeliveryPage-Cx7kQC_e.js +1 -0
  20. package/dist/assets/web/dist/assets/NotFoundPage-BzWS4Ca1.js +1 -0
  21. package/dist/assets/web/dist/assets/ResidentDetailPage-Bcasasjb.js +1 -0
  22. package/dist/assets/web/dist/assets/ResidentsIndexPage-BJwyrphn.js +1 -0
  23. package/dist/assets/web/dist/assets/RunRoutePage-6eStApqP.js +12 -0
  24. package/dist/assets/web/dist/assets/RunRoutePage-f6TSNPeW.css +1 -0
  25. package/dist/assets/web/dist/assets/RunsIndexPage-CWrkv7v8.js +1 -0
  26. package/dist/assets/web/dist/assets/RunsTabs-DEICQ4FY.js +1 -0
  27. package/dist/assets/web/dist/assets/ScheduledPage-BX2py1X3.js +1 -0
  28. package/dist/assets/web/dist/assets/StatusDot-CwCK84JN.js +1 -0
  29. package/dist/assets/web/dist/assets/Tooltip-CFC88_-Z.js +1 -0
  30. package/dist/assets/web/dist/assets/abap-CLvhMVsD.js +1 -0
  31. package/dist/assets/web/dist/assets/actionscript-3-Cs6JJXT5.js +1 -0
  32. package/dist/assets/web/dist/assets/ada-C5qYipkI.js +1 -0
  33. package/dist/assets/web/dist/assets/ahk-CA9gdOdF.js +1 -0
  34. package/dist/assets/web/dist/assets/ahk2-_6rzW9BS.js +1 -0
  35. package/dist/assets/web/dist/assets/andromeeda-vGVdxbeo.js +1 -0
  36. package/dist/assets/web/dist/assets/angular-html-Cw130Zyi.js +1 -0
  37. package/dist/assets/web/dist/assets/angular-ts-DY1m4C2s.js +1 -0
  38. package/dist/assets/web/dist/assets/apache-U0d_L8uA.js +1 -0
  39. package/dist/assets/web/dist/assets/apex-KFGFyKwG.js +1 -0
  40. package/dist/assets/web/dist/assets/apl-W6Ty7v05.js +1 -0
  41. package/dist/assets/web/dist/assets/applescript-CCn79oCD.js +1 -0
  42. package/dist/assets/web/dist/assets/ara-4CJ0cIlV.js +1 -0
  43. package/dist/assets/web/dist/assets/asciidoc-SCjQUq34.js +1 -0
  44. package/dist/assets/web/dist/assets/asm-Cmm7eHzH.js +1 -0
  45. package/dist/assets/web/dist/assets/astro-DlLVHwVk.js +1 -0
  46. package/dist/assets/web/dist/assets/aurora-x-CDeNXAV0.js +1 -0
  47. package/dist/assets/web/dist/assets/awk-BWXHIvNe.js +1 -0
  48. package/dist/assets/web/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
  49. package/dist/assets/web/dist/assets/ayu-light-C3h-C4tm.js +1 -0
  50. package/dist/assets/web/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  51. package/dist/assets/web/dist/assets/ballerina-B7ZEbQpA.js +1 -0
  52. package/dist/assets/web/dist/assets/bat-qdxAdBWQ.js +1 -0
  53. package/dist/assets/web/dist/assets/beancount-D-usSTwE.js +1 -0
  54. package/dist/assets/web/dist/assets/berry-DKpUyyne.js +1 -0
  55. package/dist/assets/web/dist/assets/bibtex-Ci_nEsc7.js +1 -0
  56. package/dist/assets/web/dist/assets/bicep-CUHmPFLl.js +1 -0
  57. package/dist/assets/web/dist/assets/bird2-CYIUgYqB.js +1 -0
  58. package/dist/assets/web/dist/assets/blade-Bco3tDh4.js +1 -0
  59. package/dist/assets/web/dist/assets/bsl-_VjlB9mv.js +1 -0
  60. package/dist/assets/web/dist/assets/c-1LaFY_cj.js +1 -0
  61. package/dist/assets/web/dist/assets/c3-D0apG41V.js +1 -0
  62. package/dist/assets/web/dist/assets/cadence-CQ2zXKGN.js +1 -0
  63. package/dist/assets/web/dist/assets/cairo-DLTphjLi.js +1 -0
  64. package/dist/assets/web/dist/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
  65. package/dist/assets/web/dist/assets/catppuccin-latte-CtsyrtPq.js +1 -0
  66. package/dist/assets/web/dist/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
  67. package/dist/assets/web/dist/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
  68. package/dist/assets/web/dist/assets/chapel-B-OvrOL5.js +1 -0
  69. package/dist/assets/web/dist/assets/clarity-CtfBBspg.js +1 -0
  70. package/dist/assets/web/dist/assets/clojure-DqKBuwfJ.js +1 -0
  71. package/dist/assets/web/dist/assets/cmake-Bj61d0ZC.js +1 -0
  72. package/dist/assets/web/dist/assets/cobol-Ch3EPlHu.js +1 -0
  73. package/dist/assets/web/dist/assets/codeowners-C8r90Shi.js +1 -0
  74. package/dist/assets/web/dist/assets/codeql-oeQT6MSM.js +1 -0
  75. package/dist/assets/web/dist/assets/coffee-hZuITnto.js +1 -0
  76. package/dist/assets/web/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
  77. package/dist/assets/web/dist/assets/coq-DuBRPxYg.js +1 -0
  78. package/dist/assets/web/dist/assets/cpp-B8iRVo8m.js +1 -0
  79. package/dist/assets/web/dist/assets/crystal-DKveI7lr.js +1 -0
  80. package/dist/assets/web/dist/assets/csharp-oqKa8noW.js +1 -0
  81. package/dist/assets/web/dist/assets/css-_2_CD-rr.js +1 -0
  82. package/dist/assets/web/dist/assets/csv-Dx-8-gkx.js +1 -0
  83. package/dist/assets/web/dist/assets/cue-CE9AQfxI.js +1 -0
  84. package/dist/assets/web/dist/assets/cypher-ClKdZ_lG.js +1 -0
  85. package/dist/assets/web/dist/assets/d-qD-0Kul2.js +1 -0
  86. package/dist/assets/web/dist/assets/dark-plus-Cs2F2srj.js +1 -0
  87. package/dist/assets/web/dist/assets/dart-CnvKMtbv.js +1 -0
  88. package/dist/assets/web/dist/assets/dax-BkyTk9wS.js +1 -0
  89. package/dist/assets/web/dist/assets/desktop-Dlh5hvp9.js +1 -0
  90. package/dist/assets/web/dist/assets/diff-woXpYk--.js +1 -0
  91. package/dist/assets/web/dist/assets/dist-BoLiHpua.js +1547 -0
  92. package/dist/assets/web/dist/assets/docker-IyjqRm3v.js +1 -0
  93. package/dist/assets/web/dist/assets/dotenv-_5a1GRtc.js +1 -0
  94. package/dist/assets/web/dist/assets/dracula-BHWKrbxM.js +1 -0
  95. package/dist/assets/web/dist/assets/dracula-soft-5eyTD99u.js +1 -0
  96. package/dist/assets/web/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
  97. package/dist/assets/web/dist/assets/edge-BenMDPct.js +1 -0
  98. package/dist/assets/web/dist/assets/elixir-CBbdEqG6.js +1 -0
  99. package/dist/assets/web/dist/assets/elm-DMub_2lY.js +1 -0
  100. package/dist/assets/web/dist/assets/emacs-lisp-c_oH4hRZ.js +1 -0
  101. package/dist/assets/web/dist/assets/erb-CI9He_Up.js +1 -0
  102. package/dist/assets/web/dist/assets/erlang-Cphh6RMH.js +1 -0
  103. package/dist/assets/web/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
  104. package/dist/assets/web/dist/assets/everforest-light-Df2xbC6M.js +1 -0
  105. package/dist/assets/web/dist/assets/{favicon-CFfbl2AI.js → favicon-CWPcvWvp.js} +1 -1
  106. package/dist/assets/web/dist/assets/fennel-DQxkIbk2.js +1 -0
  107. package/dist/assets/web/dist/assets/fish-BJitypiv.js +1 -0
  108. package/dist/assets/web/dist/assets/fluent-C03EYrpw.js +1 -0
  109. package/dist/assets/web/dist/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  110. package/dist/assets/web/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
  111. package/dist/assets/web/dist/assets/fsharp-D13ZGOAj.js +1 -0
  112. package/dist/assets/web/dist/assets/gdresource-xMC7jr7x.js +1 -0
  113. package/dist/assets/web/dist/assets/gdscript-DYvRD3hg.js +1 -0
  114. package/dist/assets/web/dist/assets/gdshader-CBce3t8t.js +1 -0
  115. package/dist/assets/web/dist/assets/genie-CV2tkWYe.js +1 -0
  116. package/dist/assets/web/dist/assets/gherkin-DExj1W_8.js +1 -0
  117. package/dist/assets/web/dist/assets/git-commit-BSykSTBG.js +1 -0
  118. package/dist/assets/web/dist/assets/git-rebase-BBfhnyF8.js +1 -0
  119. package/dist/assets/web/dist/assets/github-dark-C-LZuMrd.js +1 -0
  120. package/dist/assets/web/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
  121. package/dist/assets/web/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  122. package/dist/assets/web/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  123. package/dist/assets/web/dist/assets/github-light-EUqPIrTm.js +1 -0
  124. package/dist/assets/web/dist/assets/github-light-default-BXViO-2h.js +1 -0
  125. package/dist/assets/web/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  126. package/dist/assets/web/dist/assets/gleam-CSRkHgEL.js +1 -0
  127. package/dist/assets/web/dist/assets/glimmer-js-CR9Fxrau.js +1 -0
  128. package/dist/assets/web/dist/assets/glimmer-ts-CwFwX_XS.js +1 -0
  129. package/dist/assets/web/dist/assets/glsl-Daiodp1r.js +1 -0
  130. package/dist/assets/web/dist/assets/gn-ilITqXS6.js +1 -0
  131. package/dist/assets/web/dist/assets/gnuplot-7GGW24-e.js +1 -0
  132. package/dist/assets/web/dist/assets/go-rLFTqkRN.js +1 -0
  133. package/dist/assets/web/dist/assets/graphql-DZvrsEkB.js +1 -0
  134. package/dist/assets/web/dist/assets/groovy-CacY0gHj.js +1 -0
  135. package/dist/assets/web/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  136. package/dist/assets/web/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  137. package/dist/assets/web/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  138. package/dist/assets/web/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  139. package/dist/assets/web/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  140. package/dist/assets/web/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  141. package/dist/assets/web/dist/assets/hack-BaXOzdg7.js +1 -0
  142. package/dist/assets/web/dist/assets/haml-CMskq_VA.js +1 -0
  143. package/dist/assets/web/dist/assets/handlebars-Dhw_f7jD.js +1 -0
  144. package/dist/assets/web/dist/assets/haskell-D8IpX4py.js +1 -0
  145. package/dist/assets/web/dist/assets/haxe-C8oCnwJJ.js +1 -0
  146. package/dist/assets/web/dist/assets/hcl-Dh228itO.js +1 -0
  147. package/dist/assets/web/dist/assets/hjson-CxZEssPk.js +1 -0
  148. package/dist/assets/web/dist/assets/hlsl-Cvrh5tZx.js +1 -0
  149. package/dist/assets/web/dist/assets/horizon-CE9ld1lL.js +1 -0
  150. package/dist/assets/web/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
  151. package/dist/assets/web/dist/assets/houston-CsvMBhTu.js +1 -0
  152. package/dist/assets/web/dist/assets/html-C9k99z0z.js +1 -0
  153. package/dist/assets/web/dist/assets/html-derivative-VckPItXN.js +1 -0
  154. package/dist/assets/web/dist/assets/http-DXU_3h9v.js +1 -0
  155. package/dist/assets/web/dist/assets/hurl-KU24JNdp.js +1 -0
  156. package/dist/assets/web/dist/assets/hxml-Dcm88srW.js +1 -0
  157. package/dist/assets/web/dist/assets/hy-CZbG8q4J.js +1 -0
  158. package/dist/assets/web/dist/assets/imba-DsUTQ-LC.js +1 -0
  159. package/dist/assets/web/dist/assets/indexFormat-B-pd4r7Y.js +1 -0
  160. package/dist/assets/web/dist/assets/ini-B5eOa1yu.js +1 -0
  161. package/dist/assets/web/dist/assets/java-BbZjTNgf.js +1 -0
  162. package/dist/assets/web/dist/assets/javascript-DXCdqcZZ.js +1 -0
  163. package/dist/assets/web/dist/assets/jinja-pDlsjTVp.js +1 -0
  164. package/dist/assets/web/dist/assets/jison-DsXDk66e.js +1 -0
  165. package/dist/assets/web/dist/assets/json-tCxXfRgG.js +1 -0
  166. package/dist/assets/web/dist/assets/json5-BR5RXkoi.js +1 -0
  167. package/dist/assets/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
  168. package/dist/assets/web/dist/assets/jsonl-CmCQp5Yx.js +1 -0
  169. package/dist/assets/web/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
  170. package/dist/assets/web/dist/assets/jssm-DXw9l8Rf.js +1 -0
  171. package/dist/assets/web/dist/assets/jsx-B0ZYkCmi.js +1 -0
  172. package/dist/assets/web/dist/assets/julia-BLzzeI6x.js +1 -0
  173. package/dist/assets/web/dist/assets/just-DvD2_60c.js +1 -0
  174. package/dist/assets/web/dist/assets/kanagawa-dragon-Bozd7klA.js +1 -0
  175. package/dist/assets/web/dist/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
  176. package/dist/assets/web/dist/assets/kanagawa-wave-BML5mF2J.js +1 -0
  177. package/dist/assets/web/dist/assets/kdl-CsD5j6eV.js +1 -0
  178. package/dist/assets/web/dist/assets/kotlin-DhhofPvG.js +1 -0
  179. package/dist/assets/web/dist/assets/kusto-C7mF5XQf.js +1 -0
  180. package/dist/assets/web/dist/assets/laserwave-C_8bwKvT.js +1 -0
  181. package/dist/assets/web/dist/assets/latex-511zn3h7.js +1 -0
  182. package/dist/assets/web/dist/assets/lean-CewbzKMR.js +1 -0
  183. package/dist/assets/web/dist/assets/less-DVTAwKKz.js +1 -0
  184. package/dist/assets/web/dist/assets/light-plus-DVQuIRkW.js +1 -0
  185. package/dist/assets/web/dist/assets/liquid-BD0BkMox.js +1 -0
  186. package/dist/assets/web/dist/assets/llvm-DqHOMS-s.js +1 -0
  187. package/dist/assets/web/dist/assets/log-BNLmms1o.js +1 -0
  188. package/dist/assets/web/dist/assets/logo-Cluzi2Zq.js +1 -0
  189. package/dist/assets/web/dist/assets/lua-NFicKm6N.js +1 -0
  190. package/dist/assets/web/dist/assets/luau-C2pBCSgt.js +1 -0
  191. package/dist/assets/web/dist/assets/main-i3ZNDRLK.css +1 -0
  192. package/dist/assets/web/dist/assets/main-tYcFk9Dc.js +28 -0
  193. package/dist/assets/web/dist/assets/make-Dixweg8N.js +1 -0
  194. package/dist/assets/web/dist/assets/markdown-BYOwaDjH.js +1 -0
  195. package/dist/assets/web/dist/assets/marko-1-G_nEXK.js +1 -0
  196. package/dist/assets/web/dist/assets/material-theme-Bm3Qr25_.js +1 -0
  197. package/dist/assets/web/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
  198. package/dist/assets/web/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
  199. package/dist/assets/web/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  200. package/dist/assets/web/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  201. package/dist/assets/web/dist/assets/matlab-D7qyCx1q.js +1 -0
  202. package/dist/assets/web/dist/assets/mdc-DUe3AQLp.js +1 -0
  203. package/dist/assets/web/dist/assets/mdx-DQZ5AkYe.js +1 -0
  204. package/dist/assets/web/dist/assets/mermaid-BxpfTIzt.js +1 -0
  205. package/dist/assets/web/dist/assets/min-dark-BSWPekZh.js +1 -0
  206. package/dist/assets/web/dist/assets/min-light-DDpmG2fV.js +1 -0
  207. package/dist/assets/web/dist/assets/mipsasm-BMqwQI7S.js +1 -0
  208. package/dist/assets/web/dist/assets/mojo-fg40_ntQ.js +1 -0
  209. package/dist/assets/web/dist/assets/monokai-B_bV9PQ9.js +1 -0
  210. package/dist/assets/web/dist/assets/moonbit-DgjMoLWr.js +1 -0
  211. package/dist/assets/web/dist/assets/move-BYnGhZoM.js +1 -0
  212. package/dist/assets/web/dist/assets/narrat-_X_XdTYD.js +1 -0
  213. package/dist/assets/web/dist/assets/nextflow-BJtWHP5T.js +1 -0
  214. package/dist/assets/web/dist/assets/nextflow-groovy-DJMQeKeT.js +1 -0
  215. package/dist/assets/web/dist/assets/nginx-Dg379_bA.js +1 -0
  216. package/dist/assets/web/dist/assets/night-owl-DhmEMT88.js +1 -0
  217. package/dist/assets/web/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
  218. package/dist/assets/web/dist/assets/nim-BnFq97ZO.js +1 -0
  219. package/dist/assets/web/dist/assets/nix-IvuFDN5E.js +1 -0
  220. package/dist/assets/web/dist/assets/nord-Cb4Vim4T.js +1 -0
  221. package/dist/assets/web/dist/assets/nsis-CjNLYCLg.js +1 -0
  222. package/dist/assets/web/dist/assets/nushell-DHYfO66s.js +1 -0
  223. package/dist/assets/web/dist/assets/objective-c-D1A_Heim.js +1 -0
  224. package/dist/assets/web/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
  225. package/dist/assets/web/dist/assets/ocaml-O90oeIOV.js +1 -0
  226. package/dist/assets/web/dist/assets/odin-B1RWQWA5.js +1 -0
  227. package/dist/assets/web/dist/assets/one-dark-pro-CLwyXe_n.js +1 -0
  228. package/dist/assets/web/dist/assets/one-light-D7Lr4KcI.js +1 -0
  229. package/dist/assets/web/dist/assets/openscad-BUDT5pXO.js +1 -0
  230. package/dist/assets/web/dist/assets/org-F0uiwGvq.js +1 -0
  231. package/dist/assets/web/dist/assets/pascal-4ZHwLPI5.js +1 -0
  232. package/dist/assets/web/dist/assets/perl-B0euczkl.js +1 -0
  233. package/dist/assets/web/dist/assets/php-DkL3k_n7.js +1 -0
  234. package/dist/assets/web/dist/assets/pierre-dark-CpLgRqie.js +1 -0
  235. package/dist/assets/web/dist/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  236. package/dist/assets/web/dist/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  237. package/dist/assets/web/dist/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  238. package/dist/assets/web/dist/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  239. package/dist/assets/web/dist/assets/pierre-light-CoaEpmwp.js +1 -0
  240. package/dist/assets/web/dist/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  241. package/dist/assets/web/dist/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  242. package/dist/assets/web/dist/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  243. package/dist/assets/web/dist/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  244. package/dist/assets/web/dist/assets/pkl-ot-7Btpt.js +1 -0
  245. package/dist/assets/web/dist/assets/plastic-DQwYfKfQ.js +1 -0
  246. package/dist/assets/web/dist/assets/plsql-DGHpHOYJ.js +1 -0
  247. package/dist/assets/web/dist/assets/po-BiJDBrnU.js +1 -0
  248. package/dist/assets/web/dist/assets/poimandres-DRFjx7u4.js +1 -0
  249. package/dist/assets/web/dist/assets/polar-C7UOKdEL.js +1 -0
  250. package/dist/assets/web/dist/assets/postcss-BXeXVLqQ.js +1 -0
  251. package/dist/assets/web/dist/assets/powerquery-DNMTfnFr.js +1 -0
  252. package/dist/assets/web/dist/assets/powershell-CCSX75yM.js +1 -0
  253. package/dist/assets/web/dist/assets/prisma-DMgNhnYS.js +1 -0
  254. package/dist/assets/web/dist/assets/prolog-iXnhIJG7.js +1 -0
  255. package/dist/assets/web/dist/assets/proto-DB4EqR-F.js +1 -0
  256. package/dist/assets/web/dist/assets/pug-L_OjjZP4.js +1 -0
  257. package/dist/assets/web/dist/assets/puppet-CDv2pdJW.js +1 -0
  258. package/dist/assets/web/dist/assets/purescript-9MfHhQsQ.js +1 -0
  259. package/dist/assets/web/dist/assets/python-gzcpVVnB.js +1 -0
  260. package/dist/assets/web/dist/assets/qml-BMjB00Zz.js +1 -0
  261. package/dist/assets/web/dist/assets/qmldir-DCQb3MpD.js +1 -0
  262. package/dist/assets/web/dist/assets/qss-Fe1Jh2GI.js +1 -0
  263. package/dist/assets/web/dist/assets/r-DoeLdnqR.js +1 -0
  264. package/dist/assets/web/dist/assets/racket-DcIDlBhZ.js +1 -0
  265. package/dist/assets/web/dist/assets/raku-B3gFvitq.js +1 -0
  266. package/dist/assets/web/dist/assets/razor-BzYNkAWy.js +1 -0
  267. package/dist/assets/web/dist/assets/rbs-C0MpOrj4.js +1 -0
  268. package/dist/assets/web/dist/assets/red-DX1BQOs8.js +1 -0
  269. package/dist/assets/web/dist/assets/reg-CRGYupPL.js +1 -0
  270. package/dist/assets/web/dist/assets/regexp-CPmElMk3.js +1 -0
  271. package/dist/assets/web/dist/assets/rel-BtDbiS_P.js +1 -0
  272. package/dist/assets/web/dist/assets/riscv-Ckw8ddFX.js +1 -0
  273. package/dist/assets/web/dist/assets/ron-VUp2lXgN.js +1 -0
  274. package/dist/assets/web/dist/assets/rose-pine-aMsaMD1D.js +1 -0
  275. package/dist/assets/web/dist/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
  276. package/dist/assets/web/dist/assets/rose-pine-moon-DO-qXg-I.js +1 -0
  277. package/dist/assets/web/dist/assets/rosmsg-CAekHB0j.js +1 -0
  278. package/dist/assets/web/dist/assets/rst-DrsjgfI7.js +1 -0
  279. package/dist/assets/web/dist/assets/ruby-cB42ppvx.js +1 -0
  280. package/dist/assets/web/dist/assets/rust-Cfkwpbl8.js +1 -0
  281. package/dist/assets/web/dist/assets/sas-R5Y3NM_I.js +1 -0
  282. package/dist/assets/web/dist/assets/sass-DXrisJhu.js +1 -0
  283. package/dist/assets/web/dist/assets/scala-BiFt_7mu.js +1 -0
  284. package/dist/assets/web/dist/assets/scheme-DQCgrYNe.js +1 -0
  285. package/dist/assets/web/dist/assets/scss-C-zWxV9s.js +1 -0
  286. package/dist/assets/web/dist/assets/sdbl-bTVj8UrX.js +1 -0
  287. package/dist/assets/web/dist/assets/seed-DeKSHrt2.js +6 -0
  288. package/dist/assets/web/dist/assets/shaderlab-TOUzSsQk.js +1 -0
  289. package/dist/assets/web/dist/assets/shellscript-oJF96aAU.js +1 -0
  290. package/dist/assets/web/dist/assets/shellsession-CznECKyi.js +1 -0
  291. package/dist/assets/web/dist/assets/slack-dark-DnToyrRv.js +1 -0
  292. package/dist/assets/web/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
  293. package/dist/assets/web/dist/assets/smalltalk-BHpQb5nM.js +1 -0
  294. package/dist/assets/web/dist/assets/smithy-Dgaz7aur.js +1 -0
  295. package/dist/assets/web/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
  296. package/dist/assets/web/dist/assets/solarized-dark-UdsmMDCL.js +1 -0
  297. package/dist/assets/web/dist/assets/solarized-light-C1IUL_tW.js +1 -0
  298. package/dist/assets/web/dist/assets/solidity-Bj7E1mRn.js +1 -0
  299. package/dist/assets/web/dist/assets/soy-wjHLSRag.js +1 -0
  300. package/dist/assets/web/dist/assets/sparql-D_iOobhT.js +1 -0
  301. package/dist/assets/web/dist/assets/splunk-BC2Px7Mm.js +1 -0
  302. package/dist/assets/web/dist/assets/sql-C_BM8IOW.js +1 -0
  303. package/dist/assets/web/dist/assets/ssh-config-BgfXC-Er.js +1 -0
  304. package/dist/assets/web/dist/assets/stata-D31HO8aQ.js +1 -0
  305. package/dist/assets/web/dist/assets/stylus-B6D30XZt.js +1 -0
  306. package/dist/assets/web/dist/assets/surrealql-CKCLyplA.js +1 -0
  307. package/dist/assets/web/dist/assets/svelte-3geWk2iu.js +1 -0
  308. package/dist/assets/web/dist/assets/swift-CyEgAFGc.js +1 -0
  309. package/dist/assets/web/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
  310. package/dist/assets/web/dist/assets/system-verilog-Dh5ihWbf.js +1 -0
  311. package/dist/assets/web/dist/assets/systemd-BxMlprV5.js +1 -0
  312. package/dist/assets/web/dist/assets/talonscript-CohzipZa.js +1 -0
  313. package/dist/assets/web/dist/assets/tasl-DMoTqEGO.js +1 -0
  314. package/dist/assets/web/dist/assets/tcl-CZd0xW_V.js +1 -0
  315. package/dist/assets/web/dist/assets/templ-DiJr_hTD.js +1 -0
  316. package/dist/assets/web/dist/assets/terraform-DswuEJGm.js +1 -0
  317. package/dist/assets/web/dist/assets/tex-BEJGWyeF.js +1 -0
  318. package/dist/assets/web/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
  319. package/dist/assets/web/dist/assets/toml-CcmNWLt0.js +1 -0
  320. package/dist/assets/web/dist/assets/ts-tags-C8Mzdpxx.js +1 -0
  321. package/dist/assets/web/dist/assets/tsv-sltzmVWM.js +1 -0
  322. package/dist/assets/web/dist/assets/tsx-UQiSm_p3.js +1 -0
  323. package/dist/assets/web/dist/assets/turtle-ByJddavk.js +1 -0
  324. package/dist/assets/web/dist/assets/twig-_Pvzkeo9.js +1 -0
  325. package/dist/assets/web/dist/assets/typescript-BWMkINKK.js +1 -0
  326. package/dist/assets/web/dist/assets/typespec-BRdr0IET.js +1 -0
  327. package/dist/assets/web/dist/assets/typst-Bdg9m-e7.js +1 -0
  328. package/dist/assets/web/dist/assets/v-DBj4K9d_.js +1 -0
  329. package/dist/assets/web/dist/assets/vala-zf12oZj6.js +1 -0
  330. package/dist/assets/web/dist/assets/vb-DOwzaHZ0.js +1 -0
  331. package/dist/assets/web/dist/assets/verilog-B2gwz-sJ.js +1 -0
  332. package/dist/assets/web/dist/assets/vesper-DdrHHSXu.js +1 -0
  333. package/dist/assets/web/dist/assets/vhdl-BroJfC0k.js +1 -0
  334. package/dist/assets/web/dist/assets/viml-DvXPmvsu.js +1 -0
  335. package/dist/assets/web/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
  336. package/dist/assets/web/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
  337. package/dist/assets/web/dist/assets/vitesse-light-VbXTXTou.js +1 -0
  338. package/dist/assets/web/dist/assets/vue-CDZrrAZ8.js +1 -0
  339. package/dist/assets/web/dist/assets/vue-html-tAlpNhfN.js +1 -0
  340. package/dist/assets/web/dist/assets/vue-vine-C27UF_rh.js +1 -0
  341. package/dist/assets/web/dist/assets/vyper-CgoNMtux.js +1 -0
  342. package/dist/assets/web/dist/assets/wallClock-61NpVkY6.js +1 -0
  343. package/dist/assets/web/dist/assets/wasm-BnjxR4X6.js +1 -0
  344. package/dist/assets/web/dist/assets/wasm-ByWQv1Qj.js +1 -0
  345. package/dist/assets/web/dist/assets/wenyan-C8pVoKbM.js +1 -0
  346. package/dist/assets/web/dist/assets/wgsl-BsKzXJz4.js +1 -0
  347. package/dist/assets/web/dist/assets/wikitext-ClFFjSW2.js +1 -0
  348. package/dist/assets/web/dist/assets/wit-DdvCle-K.js +1 -0
  349. package/dist/assets/web/dist/assets/wolfram-DLL8P-h_.js +1 -0
  350. package/dist/assets/web/dist/assets/xml-CxeDr9Zh.js +1 -0
  351. package/dist/assets/web/dist/assets/xsl-Bpi0uUnM.js +1 -0
  352. package/dist/assets/web/dist/assets/yaml-BtUgZ1GN.js +1 -0
  353. package/dist/assets/web/dist/assets/zenscript-BnlCZFoB.js +1 -0
  354. package/dist/assets/web/dist/assets/zig-CMLA9XwU.js +1 -0
  355. package/dist/cli.js +1005 -233
  356. package/package.json +1 -1
  357. package/dist/assets/web/dist/assets/AppShell-Bw3-_TTE.js +0 -1
  358. package/dist/assets/web/dist/assets/DeliveryPage-BRwcQyr7.js +0 -1
  359. package/dist/assets/web/dist/assets/NotFoundPage-RjH-9dPy.js +0 -1
  360. package/dist/assets/web/dist/assets/ResidentDetailPage-BRy5wkv9.js +0 -1
  361. package/dist/assets/web/dist/assets/ResidentsIndexPage-DAEVLN8j.js +0 -1
  362. package/dist/assets/web/dist/assets/RunRoutePage-B1KHmkZ9.js +0 -126
  363. package/dist/assets/web/dist/assets/RunRoutePage-CdbrbqU2.css +0 -1
  364. package/dist/assets/web/dist/assets/RunsIndexPage-3hUWFpFV.js +0 -1
  365. package/dist/assets/web/dist/assets/RunsTabs-Qn_5TVJU.js +0 -1
  366. package/dist/assets/web/dist/assets/ScheduledPage-DysVvVm1.js +0 -1
  367. package/dist/assets/web/dist/assets/StatusDot-Bug2a6T6.js +0 -1
  368. package/dist/assets/web/dist/assets/Tooltip-C2eEUbwn.js +0 -1
  369. package/dist/assets/web/dist/assets/main-0uhp-taL.js +0 -28
  370. package/dist/assets/web/dist/assets/main-Xickfv8V.css +0 -1
  371. package/dist/assets/web/dist/assets/seed-B8mQEhhw.js +0 -6
  372. package/dist/assets/web/dist/assets/wallClock-DI4HIEN5.js +0 -1
package/dist/cli.js CHANGED
@@ -615,8 +615,8 @@ var init_policy = __esm({
615
615
  };
616
616
  PLACEHOLDER = /\{([^{}]*)\}/g;
617
617
  PolicyError = class extends Error {
618
- constructor(message, rule) {
619
- super(`authz policy: ${message} (rule ${describeRule(rule)})`);
618
+ constructor(message2, rule) {
619
+ super(`authz policy: ${message2} (rule ${describeRule(rule)})`);
620
620
  this.name = "PolicyError";
621
621
  }
622
622
  };
@@ -1188,6 +1188,42 @@ var init_schedules = __esm({
1188
1188
  }
1189
1189
  });
1190
1190
 
1191
+ // ../../src/execution/bashTimeout.ts
1192
+ function clampBashTimeout(requested) {
1193
+ if (typeof requested !== "number" || !Number.isFinite(requested)) return BASH_TIMEOUT_MS;
1194
+ return Math.min(Math.max(Math.trunc(requested), BASH_TIMEOUT_MIN_MS), BASH_TIMEOUT_MAX_MS);
1195
+ }
1196
+ function bashBudgetWithinRun(wantedMs, remainingMs) {
1197
+ const secs = (ms) => Math.max(0, Math.round(ms / 1e3));
1198
+ const cap = Math.trunc(remainingMs - RUN_DEADLINE_RESERVE_MS);
1199
+ if (cap < BASH_TIMEOUT_MIN_MS) {
1200
+ return {
1201
+ kind: "exhausted",
1202
+ note: `run budget exhausted \u2014 ${secs(remainingMs)}s of wall clock left, inside the ${secs(RUN_DEADLINE_RESERVE_MS)}s write-up reserve, so the command was not run; write up what you have now`
1203
+ };
1204
+ }
1205
+ if (cap >= wantedMs) return { kind: "unchanged" };
1206
+ return {
1207
+ kind: "clipped",
1208
+ timeoutMs: cap,
1209
+ note: `[timeout clipped to ${secs(cap)}s \u2014 the run's wall clock ends in ${secs(remainingMs)}s]`
1210
+ };
1211
+ }
1212
+ function bashTimeoutNote(timeoutMs) {
1213
+ return `aborted at the ${Math.round(timeoutMs / 1e3)}s command timeout (pass the bash tool's timeoutMs for longer commands, max ${BASH_TIMEOUT_MAX_MS} ms)`;
1214
+ }
1215
+ var BASH_TIMEOUT_MS, BASH_TIMEOUT_MIN_MS, BASH_TIMEOUT_MAX_MS, RUN_DEADLINE_RESERVE_MS, EXEC_CALL_MARGIN_MS;
1216
+ var init_bashTimeout = __esm({
1217
+ "../../src/execution/bashTimeout.ts"() {
1218
+ "use strict";
1219
+ BASH_TIMEOUT_MS = 5 * 6e4;
1220
+ BASH_TIMEOUT_MIN_MS = 1e3;
1221
+ BASH_TIMEOUT_MAX_MS = 20 * 6e4;
1222
+ RUN_DEADLINE_RESERVE_MS = 6e4;
1223
+ EXEC_CALL_MARGIN_MS = 3e4;
1224
+ }
1225
+ });
1226
+
1191
1227
  // ../../src/core/ship/contract.ts
1192
1228
  function parsePlanUnit(planMarkdown, unitId) {
1193
1229
  const lines = planMarkdown.split("\n");
@@ -1495,11 +1531,14 @@ function getAgent(name) {
1495
1531
  }
1496
1532
  return a;
1497
1533
  }
1498
- var PR_DESCRIPTION_TEMPLATE, NEVER_MERGE, CONTRACT_HEADINGS_LIST, UNIT_CONTRACT, UNIT_HANDOFF, CODING_SYSTEM, CODING_SYSTEM_RESIDENT, REVIEW_VERDICT_INSTRUCTION, REVIEW_SPEC_CHECK, REVIEW_UNIT_CONTRACT, REVIEW_WHOLE_CHANGE, REVIEW_SYSTEM, REVIEW_SYSTEM_RESIDENT, RESEARCH_SYSTEM, GENERAL_SYSTEM, AGENTS;
1534
+ var MACHINE_CLASSES, IDENTITIES, PR_DESCRIPTION_TEMPLATE, NEVER_MERGE, CONTRACT_HEADINGS_LIST, UNIT_CONTRACT, UNIT_HANDOFF, CODING_SYSTEM, CODING_SYSTEM_RESIDENT, REVIEW_VERDICT_INSTRUCTION, REVIEW_SPEC_CHECK, REVIEW_UNIT_CONTRACT, REVIEW_WHOLE_CHANGE, REVIEW_SYSTEM, REVIEW_SYSTEM_RESIDENT, RESEARCH_SYSTEM, GENERAL_SYSTEM, EXPLORE_SYSTEM, AGENTS;
1499
1535
  var init_registry = __esm({
1500
1536
  "../../src/agents/registry.ts"() {
1501
1537
  "use strict";
1538
+ init_bashTimeout();
1502
1539
  init_contract();
1540
+ MACHINE_CLASSES = ["none", "blank", "repo-cold", "repo-resident"];
1541
+ IDENTITIES = ["none", "read", "write"];
1503
1542
  PR_DESCRIPTION_TEMPLATE = `PR description \u2014 submit it with the submit_pr_description tool for EVERY PR (this is the default, not something to wait to be asked for). Switchboard renders the GitHub body from the object you submit, so never author PR-body markdown yourself. Content contract per field (each renders as its own section): prose is unwrapped \u2014 no hard line breaks inside a paragraph. Always hyperlink the triggering issue/request. Never fabricate validation \u2014 state exactly what you ran and the real result. Keep each field concise, not padded.
1504
1543
  EVERY PR includes one that already exists when you push \u2014 opened by a person, by dependabot, or by an earlier run. After EVERY push to such a PR: read its current title and body (\`github_issue_get\` with the PR number works for pull requests; \`gh pr view\` where gh exists), judge them against the change as it now stands at the pushed head, and submit the object that describes the PR as it is NOW \u2014 carry forward what the existing body says that is still true (a dependency bump's release notes belong in whatWhy), add what you changed, and anchor the Tour at the new head. Switchboard replaces the PR's title and body with your rendering. A description that describes an earlier state of its branch is a bug; "it is someone else's PR" is never a reason to leave it.
1505
1544
  - **title**: the PR title \u2014 one line naming the change, specific enough to pick out of a PR list.
@@ -1651,6 +1690,19 @@ Answer directly and concisely. Use Slack-friendly formatting (no markdown header
1651
1690
  Your tools work without a workspace: the GitHub tools \u2014 \`github_repos\` (the org repositories you can reach), \`github_tree\` / \`github_file\` / \`github_search_code\` (browse, read, search their code and docs, private repos included), \`github_issue_list\` / \`github_issue_get\` (read issues), \`github_issue_create\` / \`github_issue_update\` / \`github_issue_comment\` / \`github_issue_delete\` (act on issues) \u2014 and \`web_fetch\` (read a public URL). Use them: when the user names a repo loosely ("the switchboard app"), resolve it with github_repos (or the thread) rather than asking; when asked about one of our repos, read it before answering. Report exactly what a tool did (issue number + URL) \u2014 never claim an action you did not perform, and never fabricate file contents, URLs, or command output.
1652
1691
 
1653
1692
  You cannot run commands, clone repositories, edit code, or review pull requests, and you cannot search the web. Other Switchboard agents can: for code changes or PRs tell the user to re-send with \`agent:coding\`; for a PR review, \`agent:review\`; for a web-research question, \`agent:research\` (e.g. "\`agent:coding fix the failing login test in acme/api\`", "\`agent:research compare X and Y\`"). Delete an issue only when the user explicitly asked to delete it (closing is an update).`;
1693
+ EXPLORE_SYSTEM = `You are Switchboard's explore agent: a long, read-only investigation of a repository, answering a request from Slack.
1694
+
1695
+ You work in a fresh sandbox with a shell (bash), read_file, and a read-scoped GitHub credential: git and gh are authenticated for reads, so clone the target repository into your workspace first (\`gh repo clone <owner/name>\` or \`git clone\`; check out the ref the request names), install what you need and run whatever the investigation calls for \u2014 builds, test suites, benchmarks, \`act\` (Docker is available). You cannot push. Your other tools: \`web_search\` and \`web_fetch\` (sources and pages), the GitHub reads \u2014 \`github_repos\`, \`github_tree\` / \`github_file\` (browse and read our repos at any ref), \`github_search_code\`, \`github_issue_list\` / \`github_issue_get\` \u2014 and \`list_skills\` / \`use_skill\`.
1696
+
1697
+ THE DELIVERABLE IS A CLAIM TABLE. Turn the request into the claims it makes or asks about \u2014 explicit ones ("the suite runs in 4 minutes") and the implicit ones a careful engineer would check \u2014 and verify each one by running it, not by reading about it. One row per claim: the claim, the exact command you ran to check it, the number or output it produced, and a verdict (holds / does not hold / could not check \u2014 and why). Numbers over adjectives: measure a duration, count the failures, quote the version. Say what you did not get to.
1698
+
1699
+ TIME. Your budget is up to two hours \u2014 less when a boundary or the request's \`budget:\` directive clipped it, which the runtime-config block above says \u2014 and the wrap-up warning tells you when to stop starting new checks. A single command is capped at ${BASH_TIMEOUT_MAX_MS / 6e4} minutes (pass the bash tool's \`timeoutMs\`, up to ${BASH_TIMEOUT_MAX_MS} ms, for a long one). A job that needs longer \u2014 a full suite, a build, a pipeline run \u2014 is started detached and polled across tool calls: \`setsid -f sh -c '<command> > /tmp/job.log 2>&1; echo $? > /tmp/job.exit'\`, then \`tail -n 40 /tmp/job.log\` and \`cat /tmp/job.exit\` on later calls (a plain background job dies with the command that started it; a \`setsid -f\` job outlives it). Batch commands into few tool calls; never explore file by file.
1700
+
1701
+ READ-ONLY: NEVER open a pull request, and never commit or push \u2014 no branch, no \`gh pr create\`, no PR or issue write of any kind. You hold a read credential and your job is to find out, not to change. If the investigation shows a change is needed, say exactly what and where in your write-up and point the user at \`agent:coding\`.
1702
+
1703
+ Maintain the user-facing status card with the update_status tool: post your plan as a checklist (\u25CB pending) once you have it, and update items as they start (\u2731) and finish (\u2713 \u2014 only after they actually happened). Items are short outcomes ("Clone and install", "Time the full suite"), never commands.
1704
+
1705
+ Report outcomes faithfully: a check you could not run is "could not check", never a guess. Use Slack-friendly formatting (no markdown headers; *bold*, bullets, code blocks \u2014 render the claim table as aligned rows inside a code block). Your final message is posted to Slack: lead with the overall verdict in one line, then the claim table, then what a follow-up should do.`;
1654
1706
  AGENTS = {
1655
1707
  general: {
1656
1708
  name: "general",
@@ -1658,8 +1710,10 @@ You cannot run commands, clone repositories, edit code, or review pull requests,
1658
1710
  system: GENERAL_SYSTEM,
1659
1711
  toolset: "assistant",
1660
1712
  // The GitHub tools are REST in the bot process, so a general ask never
1661
- // provisions a workspace or sandbox (docs/reference/specs/agent-general.md item 4).
1713
+ // provisions a workspace or sandbox (docs/reference/specs/agent-general.md item 4)
1714
+ // and mints no credential of its own.
1662
1715
  machine: "none",
1716
+ identity: "none",
1663
1717
  maxTurns: 8,
1664
1718
  // a repo read is 2-3 calls (repos → tree → file); an issue action 1-2; still fast
1665
1719
  maxTokens: 16e3,
@@ -1681,7 +1735,9 @@ You cannot run commands, clone repositories, edit code, or review pull requests,
1681
1735
  cacheTtl: "1h",
1682
1736
  // No built-in effort: the deployment decides (`defaults.efforts.coding`,
1683
1737
  // `config set channel efforts.coding=…`, or `effort:` per request).
1684
- machine: "repo-resident"
1738
+ machine: "repo-resident",
1739
+ identity: "write"
1740
+ // pushes branches and opens pull requests
1685
1741
  },
1686
1742
  review: {
1687
1743
  name: "review",
@@ -1690,6 +1746,8 @@ You cannot run commands, clone repositories, edit code, or review pull requests,
1690
1746
  residentSystem: REVIEW_SYSTEM_RESIDENT,
1691
1747
  toolset: "readonly",
1692
1748
  machine: "repo-resident",
1749
+ identity: "read",
1750
+ // a read-scoped token and a read-only worktree: it cannot post or push from inside
1693
1751
  maxTurns: 30,
1694
1752
  // backstop only; wall clock is the real budget (12 bound at ~4 min in practice)
1695
1753
  maxTokens: 64e3,
@@ -1706,14 +1764,19 @@ You cannot run commands, clone repositories, edit code, or review pull requests,
1706
1764
  // on the coding/review defs above — runAgent is never called with THIS def.
1707
1765
  system: "You are Switchboard's ship pipeline. This prompt is never sent to a model \u2014 the pipeline orchestrates coding and review child runs on their own definitions.",
1708
1766
  // Full toolset and the coding machine class, so repo and PR resolution
1709
- // gate a ship thread like a coding one; placeholder budgets — the pipeline
1710
- // is bounded by the `ship` config caps and by each child's own budgets
1711
- // clipped to the remaining wall clock, never by these numbers.
1767
+ // gate a ship thread like a coding one. `maxMinutes` is the pipeline's
1768
+ // wall clock (docs/reference/specs/agent-ship.md item 8): the ship preset's
1769
+ // declared budget, which a deployment's `ship.maxMinutes` knob replaces
1770
+ // (`shipPresetFor`) and a boundary or a `budget:` directive clips like any
1771
+ // preset's; every child round runs its own agent's budget clipped to what
1772
+ // remains of it. Turns and tokens are placeholders: no model call is ever
1773
+ // made with this def.
1712
1774
  toolset: "full",
1713
1775
  machine: "repo-resident",
1776
+ identity: "write",
1714
1777
  maxTurns: 1,
1715
1778
  maxTokens: 16e3,
1716
- maxMinutes: 5
1779
+ maxMinutes: 120
1717
1780
  },
1718
1781
  research: {
1719
1782
  name: "research",
@@ -1722,10 +1785,30 @@ You cannot run commands, clone repositories, edit code, or review pull requests,
1722
1785
  toolset: "web",
1723
1786
  machine: "none",
1724
1787
  // web I/O only; no workspace is provisioned
1788
+ identity: "none",
1725
1789
  maxTurns: 12,
1726
1790
  maxTokens: 24e3,
1727
1791
  maxMinutes: 8,
1728
1792
  effort: "medium"
1793
+ },
1794
+ explore: {
1795
+ name: "explore",
1796
+ description: "Long, read-only investigation of a repository in a cold sandbox: runs builds, suites and pipelines, searches the web, and reports a claim table with commands and numbers. Never opens a PR. Up to two hours.",
1797
+ system: EXPLORE_SYSTEM,
1798
+ toolset: "explore",
1799
+ // Always a cold per-thread sandbox with the checkout, never the resident a
1800
+ // review depends on: a two-hour job shares no container with anyone.
1801
+ machine: "repo-cold",
1802
+ identity: "read",
1803
+ // a read-scoped token: it can clone and read, never push — whatever the caller holds
1804
+ maxTurns: 150,
1805
+ // a backstop for a two-hour loop of batched checks; the wall clock is the budget
1806
+ maxTokens: 64e3,
1807
+ maxMinutes: 120,
1808
+ // A detached job polled across calls makes long steps: a 5m cache entry
1809
+ // would expire between them, so the 2× write buys reads for the whole run.
1810
+ cacheTtl: "1h"
1811
+ // No built-in effort: the deployment decides, as for coding.
1729
1812
  }
1730
1813
  };
1731
1814
  }
@@ -2014,8 +2097,8 @@ var init_web = __esm({
2014
2097
  "../../src/tools/web.ts"() {
2015
2098
  "use strict";
2016
2099
  WebSearchUnavailableError = class extends Error {
2017
- constructor(message) {
2018
- super(message);
2100
+ constructor(message2) {
2101
+ super(message2);
2019
2102
  this.name = "WebSearchUnavailableError";
2020
2103
  }
2021
2104
  };
@@ -2053,8 +2136,8 @@ var init_web = __esm({
2053
2136
  return records.map((r) => r.address);
2054
2137
  };
2055
2138
  BlockedUrlError = class extends Error {
2056
- constructor(message) {
2057
- super(message);
2139
+ constructor(message2) {
2140
+ super(message2);
2058
2141
  this.name = "BlockedUrlError";
2059
2142
  }
2060
2143
  };
@@ -2323,6 +2406,38 @@ function validateScopeEfforts(layer, source2) {
2323
2406
  }
2324
2407
  }
2325
2408
  }
2409
+ function boundaryProblem(path, raw) {
2410
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return `${path} must be a mapping`;
2411
+ for (const key of unknownKeys(raw, BOUNDARY_KEYS)) return `${path}: unknown field ${key}`;
2412
+ const b = raw;
2413
+ if (b.maxMinutes !== void 0 && (!Number.isInteger(b.maxMinutes) || b.maxMinutes < MIN_BOUNDARY_MINUTES))
2414
+ return `${path}.maxMinutes must be an integer >= ${MIN_BOUNDARY_MINUTES}`;
2415
+ if (b.maxIdentity !== void 0 && !IDENTITIES.includes(b.maxIdentity))
2416
+ return `${path}.maxIdentity is "${String(b.maxIdentity)}" \u2014 valid identities: ${IDENTITIES.join(", ")}`;
2417
+ if (b.machines !== void 0) {
2418
+ if (!Array.isArray(b.machines)) return `${path}.machines must be a list of classes`;
2419
+ if (b.machines.length === 0) return `${path}.machines must name at least one class`;
2420
+ for (const m of b.machines) {
2421
+ if (!MACHINE_CLASSES.includes(m))
2422
+ return `${path}.machines names "${String(m)}" \u2014 valid classes: ${MACHINE_CLASSES.join(", ")}`;
2423
+ }
2424
+ }
2425
+ return void 0;
2426
+ }
2427
+ function validateBoundaries(layer, source2) {
2428
+ const check = (path, raw) => {
2429
+ if (raw === void 0) return;
2430
+ const problem = boundaryProblem(path, raw);
2431
+ if (problem) throw new Error(`${source2}: ${problem}`);
2432
+ };
2433
+ check("defaults.boundary", layer.defaults?.boundary);
2434
+ for (const [kind, scopes] of [
2435
+ ["channels", layer.channels],
2436
+ ["users", layer.users]
2437
+ ]) {
2438
+ for (const [id, scope] of Object.entries(scopes ?? {})) check(`${kind}.${id}.boundary`, scope.boundary);
2439
+ }
2440
+ }
2326
2441
  function validateMcpServers(layer, source2) {
2327
2442
  const check = (path, tier, servers) => {
2328
2443
  if (servers === void 0) return;
@@ -2375,6 +2490,7 @@ function validateMcpServers(layer, source2) {
2375
2490
  function validateConfig(cfg) {
2376
2491
  for (const key of unknownKeys(cfg, CONFIG_KEYS)) throw new Error(`config.yaml: unknown key \`${key}\``);
2377
2492
  validateScopeEfforts(cfg, "config.yaml");
2493
+ validateBoundaries(cfg, "config.yaml");
2378
2494
  validateMcpServers(cfg, "config.yaml");
2379
2495
  if (typeof cfg.organization !== "string" || cfg.organization.trim() === "") {
2380
2496
  throw new Error(
@@ -2474,7 +2590,7 @@ function validateRuntimeOverrides(ro) {
2474
2590
  }
2475
2591
  }
2476
2592
  }
2477
- var MAX_INSTRUCTIONS_LENGTH, CONFIG_KEYS, SELF_IMPROVEMENT_KEYS;
2593
+ var MAX_INSTRUCTIONS_LENGTH, CONFIG_KEYS, SELF_IMPROVEMENT_KEYS, BOUNDARY_KEYS, MIN_BOUNDARY_MINUTES;
2478
2594
  var init_validate = __esm({
2479
2595
  "../../src/config/validate.ts"() {
2480
2596
  "use strict";
@@ -2516,6 +2632,90 @@ var init_validate = __esm({
2516
2632
  minRuns: true,
2517
2633
  top: true
2518
2634
  };
2635
+ BOUNDARY_KEYS = { maxMinutes: true, maxIdentity: true, machines: true };
2636
+ MIN_BOUNDARY_MINUTES = 2;
2637
+ }
2638
+ });
2639
+
2640
+ // ../../src/config/profile.ts
2641
+ function clipSourceLabel(scope) {
2642
+ return scope === "directive" ? "budget directive" : `${scope} boundary`;
2643
+ }
2644
+ function identityWithin(identity2, cap) {
2645
+ return IDENTITY_ORDER[identity2] <= IDENTITY_ORDER[cap];
2646
+ }
2647
+ function declaredProfile(preset) {
2648
+ return { machine: preset.machine, identity: preset.identity, minutes: preset.maxMinutes };
2649
+ }
2650
+ function budgetedAgent(agent, profile) {
2651
+ return { ...agent, maxMinutes: profile.minutes };
2652
+ }
2653
+ function intersectBoundaries(layers) {
2654
+ const out = {};
2655
+ for (const { scope, boundary } of layers) {
2656
+ if (boundary.maxMinutes !== void 0 && (!out.maxMinutes || boundary.maxMinutes < out.maxMinutes.value)) {
2657
+ out.maxMinutes = { value: boundary.maxMinutes, scope };
2658
+ }
2659
+ if (boundary.maxIdentity !== void 0 && (!out.maxIdentity || !identityWithin(out.maxIdentity.value, boundary.maxIdentity))) {
2660
+ out.maxIdentity = { value: boundary.maxIdentity, scope };
2661
+ }
2662
+ if (boundary.machines !== void 0) {
2663
+ const allowed = boundary.machines;
2664
+ const value = (out.machines ? out.machines.value : allowed).filter((m) => allowed.includes(m)).sort((a, b) => MACHINE_ORDER[a] - MACHINE_ORDER[b]);
2665
+ out.machines = { value, by: [...out.machines?.by ?? [], { scope, machines: [...allowed] }] };
2666
+ }
2667
+ }
2668
+ return out.maxMinutes || out.maxIdentity || out.machines ? out : void 0;
2669
+ }
2670
+ function effectiveProfile(preset, directives, boundary) {
2671
+ if (boundary?.maxIdentity && !identityWithin(preset.identity, boundary.maxIdentity.value)) {
2672
+ return {
2673
+ kind: "refused",
2674
+ refusal: {
2675
+ axis: "identity",
2676
+ needs: preset.identity,
2677
+ cap: boundary.maxIdentity.value,
2678
+ scope: boundary.maxIdentity.scope
2679
+ }
2680
+ };
2681
+ }
2682
+ if (boundary?.machines && !boundary.machines.value.includes(preset.machine)) {
2683
+ return {
2684
+ kind: "refused",
2685
+ refusal: {
2686
+ axis: "machine",
2687
+ needs: preset.machine,
2688
+ allowed: boundary.machines.value,
2689
+ scopes: boundary.machines.by.filter((b) => !b.machines.includes(preset.machine)).map((b) => b.scope)
2690
+ }
2691
+ };
2692
+ }
2693
+ let minutes = preset.maxMinutes;
2694
+ let boundedBy;
2695
+ if (directives.budget !== void 0 && directives.budget < minutes) {
2696
+ minutes = directives.budget;
2697
+ boundedBy = "directive";
2698
+ }
2699
+ if (boundary?.maxMinutes && boundary.maxMinutes.value < minutes) {
2700
+ minutes = boundary.maxMinutes.value;
2701
+ boundedBy = boundary.maxMinutes.scope;
2702
+ }
2703
+ return {
2704
+ kind: "profile",
2705
+ profile: {
2706
+ machine: preset.machine,
2707
+ identity: preset.identity,
2708
+ minutes,
2709
+ ...boundedBy !== void 0 ? { boundedBy } : {}
2710
+ }
2711
+ };
2712
+ }
2713
+ var IDENTITY_ORDER, MACHINE_ORDER;
2714
+ var init_profile = __esm({
2715
+ "../../src/config/profile.ts"() {
2716
+ "use strict";
2717
+ IDENTITY_ORDER = { none: 0, read: 1, write: 2 };
2718
+ MACHINE_ORDER = { none: 0, blank: 1, "repo-cold": 2, "repo-resident": 3 };
2519
2719
  }
2520
2720
  });
2521
2721
 
@@ -2594,6 +2794,7 @@ function formatConfigDescription(d) {
2594
2794
  const orgMcp = d.org?.mcpServers && Object.keys(d.org.mcpServers).length > 0 ? `, mcp ${Object.keys(d.org.mcpServers).map((n2) => `\`${n2}\``).join(" ")}` : "";
2595
2795
  const lines = [
2596
2796
  `*Effective for you in this channel:* ${effective}`,
2797
+ ...d.effective.boundary ? [`*Effective boundary:* ${fmtEffectiveBoundary(d.effective.boundary)}`] : [],
2597
2798
  `*Defaults:* ${defaults}${orgMcp}`,
2598
2799
  `*Channel scope:* ${fmtScope(d.channel)}`,
2599
2800
  `*Your scope:* ${fmtScope(d.user)}`
@@ -2618,8 +2819,26 @@ function fmtScope(s) {
2618
2819
  parts.push(
2619
2820
  `mcp ${Object.keys(s.mcpServers).map((n2) => `\`${n2}\``).join(" ")}`
2620
2821
  );
2822
+ if (s.boundary) parts.push(`boundary ${fmtBoundary(s.boundary)}`);
2621
2823
  return parts.length > 0 ? parts.join(", ") : "_none_";
2622
2824
  }
2825
+ function fmtBoundary(b) {
2826
+ const parts = [];
2827
+ if (b.maxMinutes !== void 0) parts.push(`maxMinutes=${b.maxMinutes}`);
2828
+ if (b.maxIdentity !== void 0) parts.push(`maxIdentity=${b.maxIdentity}`);
2829
+ if (b.machines !== void 0) parts.push(`machines=${b.machines.join(",")}`);
2830
+ return parts.length > 0 ? parts.join(" ") : "(caps nothing)";
2831
+ }
2832
+ function fmtEffectiveBoundary(b) {
2833
+ const parts = [];
2834
+ if (b.maxMinutes) parts.push(`maxMinutes ${b.maxMinutes.value} (${b.maxMinutes.scope})`);
2835
+ if (b.maxIdentity) parts.push(`maxIdentity \`${b.maxIdentity.value}\` (${b.maxIdentity.scope})`);
2836
+ if (b.machines)
2837
+ parts.push(
2838
+ `machines ${b.machines.value.map((m) => `\`${m}\``).join(", ")} (${b.machines.by.map((l) => l.scope).join(", ")})`
2839
+ );
2840
+ return parts.join(", ");
2841
+ }
2623
2842
  function fmtModels(m) {
2624
2843
  return Object.entries(m).map(([k, v]) => `\`${k}=${v}\``).join(" ");
2625
2844
  }
@@ -2636,6 +2855,7 @@ var init_config = __esm({
2636
2855
  init_schedules();
2637
2856
  init_registry();
2638
2857
  init_validate();
2858
+ init_profile();
2639
2859
  FileOverridesBacking = class {
2640
2860
  path;
2641
2861
  constructor(path) {
@@ -2756,6 +2976,7 @@ var init_config = __esm({
2756
2976
  doc.users ??= {};
2757
2977
  validateInstructions(doc, `overrides (${this.backing.describe()})`);
2758
2978
  validateScopeEfforts(doc, `overrides (${this.backing.describe()})`);
2979
+ validateBoundaries(doc, `overrides (${this.backing.describe()})`);
2759
2980
  validateMcpServers(
2760
2981
  { channels: doc.channels, users: doc.users, defaults: doc.org },
2761
2982
  `overrides (${this.backing.describe()})`
@@ -2847,22 +3068,43 @@ var init_config = __esm({
2847
3068
  return { channel: this.channelScope(channelId), user: this.userScope(userId) };
2848
3069
  }
2849
3070
  /**
2850
- * Resolve which agent, model, and effort serve a request.
2851
- * Agent: request directive > user scope > channel scope > default.
2852
- * Model: request directive > (user > channel) forced model
2853
- * > (user > channel > defaults) per-agent model.
2854
- * Effort: the same ladder as model; unset at every layer → undefined.
3071
+ * Resolve which agent, model, and effort serve a request, and the boundary
3072
+ * every run on this path meets.
3073
+ * Agent: request directive > user scope > channel scope > default.
3074
+ * Model: request directive > (user > channel) forced model
3075
+ * > (user > channel > defaults) per-agent model.
3076
+ * Effort: the same ladder as model; unset at every layer → undefined.
3077
+ * Boundary: NOT a ladder — the defaults', the channel's and the user's
3078
+ * boundaries intersect (the smallest budget, the lowest identity,
3079
+ * the classes every layer allows), so a scope can only tighten what
3080
+ * the layers below it allow; no layer set → absent.
2855
3081
  */
2856
3082
  resolve(opts) {
2857
3083
  const ch = this.channelScope(opts.channelId);
2858
3084
  const us = this.userScope(opts.userId);
2859
3085
  const agentName = opts.request.agent ?? us.agent ?? ch.agent ?? this.config.defaults.agent;
3086
+ const boundary = intersectBoundaries(this.boundaryLayers(ch, us));
2860
3087
  const modelRef2 = opts.request.model ?? us.model ?? ch.model ?? us.models?.[agentName] ?? ch.models?.[agentName] ?? this.config.defaults.models[agentName] ?? this.config.defaults.models["general"];
2861
3088
  if (!modelRef2) {
2862
3089
  throw new Error(`No model configured for agent "${agentName}" \u2014 set defaults.models.${agentName} in config.yaml`);
2863
3090
  }
2864
3091
  const effort2 = opts.request.effort ?? us.effort ?? ch.effort ?? us.efforts?.[agentName] ?? ch.efforts?.[agentName] ?? this.config.defaults.efforts?.[agentName];
2865
- return { agentName, modelRef: modelRef2, ...effort2 !== void 0 ? { effort: effort2 } : {} };
3092
+ return {
3093
+ agentName,
3094
+ modelRef: modelRef2,
3095
+ ...effort2 !== void 0 ? { effort: effort2 } : {},
3096
+ ...boundary !== void 0 ? { boundary } : {}
3097
+ };
3098
+ }
3099
+ /** The boundary layers on a path, in resolution order, keeping only the
3100
+ * scopes that set one — `intersectBoundaries` names each axis's scope from it. */
3101
+ boundaryLayers(channel, user) {
3102
+ const layers = [
3103
+ ["defaults", this.config.defaults.boundary],
3104
+ ["channel", channel.boundary],
3105
+ ["user", user.boundary]
3106
+ ];
3107
+ return layers.flatMap(([scope, boundary]) => boundary ? [{ scope, boundary }] : []);
2866
3108
  }
2867
3109
  // ---- authorization gates ----------------------------------------------------
2868
3110
  // Every gate reads the grants table (authorization.md item 9): what an actor
@@ -2987,7 +3229,8 @@ var init_config = __esm({
2987
3229
  effective: {
2988
3230
  agent: resolved.agentName,
2989
3231
  model: resolved.modelRef,
2990
- ...resolved.effort ? { effort: resolved.effort } : {}
3232
+ ...resolved.effort ? { effort: resolved.effort } : {},
3233
+ ...resolved.boundary ? { boundary: resolved.boundary } : {}
2991
3234
  },
2992
3235
  defaults: {
2993
3236
  agent: this.config.defaults.agent,
@@ -3971,8 +4214,8 @@ var init_tracer = __esm({
3971
4214
  delete this.rec.errorMessage;
3972
4215
  return;
3973
4216
  }
3974
- const message = err2 instanceof Error ? err2.message : String(err2);
3975
- this.rec.errorMessage = redactAndCap(message, ERROR_MESSAGE_CAP);
4217
+ const message2 = err2 instanceof Error ? err2.message : String(err2);
4218
+ this.rec.errorMessage = redactAndCap(message2, ERROR_MESSAGE_CAP);
3976
4219
  }
3977
4220
  setAttrs(attrs) {
3978
4221
  this.rec.attrs = { ...this.rec.attrs, ...attrs };
@@ -4191,7 +4434,7 @@ function isExampleProfile(p) {
4191
4434
  return p.account === EXAMPLE_ACCOUNT;
4192
4435
  }
4193
4436
  var WORKER_KINDS, PROFILE_PATH, PROFILE_EXAMPLE_PATH, PROFILE_ENV, hostname, endpoint, source, IMAGE_MODES, profileSchema, EXAMPLE_ACCOUNT;
4194
- var init_profile = __esm({
4437
+ var init_profile2 = __esm({
4195
4438
  "../../src/deploy/profile.ts"() {
4196
4439
  "use strict";
4197
4440
  WORKER_KINDS = ["memory", "bot", "resident", "sandbox"];
@@ -4456,7 +4699,7 @@ var init_plan = __esm({
4456
4699
  init_configDocument();
4457
4700
  init_images();
4458
4701
  init_operatorRoot();
4459
- init_profile();
4702
+ init_profile2();
4460
4703
  UNSET_ENV = ["CLOUDFLARE_ACCOUNT_ID"];
4461
4704
  SANDBOX_CONTAINER_CLASS = "SwitchboardSandbox";
4462
4705
  SANDBOX_BEARER_ENV = "SANDBOX_TOKEN";
@@ -5207,7 +5450,7 @@ var init_wranglerTemplate = __esm({
5207
5450
  "use strict";
5208
5451
  init_images();
5209
5452
  init_plan();
5210
- init_profile();
5453
+ init_profile2();
5211
5454
  init_images();
5212
5455
  TEMPLATE_FILE = "wrangler.template.jsonc";
5213
5456
  RENDERED_FILE = "wrangler.jsonc";
@@ -5406,7 +5649,7 @@ var init_restart = __esm({
5406
5649
  init_ingressTokens();
5407
5650
  init_authorize();
5408
5651
  init_liveGate();
5409
- init_profile();
5652
+ init_profile2();
5410
5653
  RESTART_TOKEN_ENV = "SWITCHBOARD_DEPLOY_TOKEN";
5411
5654
  RESTART_SCOPE = "deploy:write";
5412
5655
  RESTART_AUTHORIZE_PATH = "/admin/restart/authorize";
@@ -6387,7 +6630,7 @@ var init_run = __esm({
6387
6630
  init_wranglerTemplate();
6388
6631
  init_configSource();
6389
6632
  init_images();
6390
- init_profile();
6633
+ init_profile2();
6391
6634
  init_restart();
6392
6635
  init_wranglerTemplate();
6393
6636
  init_sandboxLiveGate();
@@ -6869,8 +7112,8 @@ var init_registryTransferHost = __esm({
6869
7112
  SHORT_MS = 6e4;
6870
7113
  LONG_MS = 30 * 6e4;
6871
7114
  TransferProblem = class extends Error {
6872
- constructor(message, unauthorized = false) {
6873
- super(message);
7115
+ constructor(message2, unauthorized = false) {
7116
+ super(message2);
6874
7117
  this.unauthorized = unauthorized;
6875
7118
  }
6876
7119
  unauthorized;
@@ -7497,7 +7740,7 @@ var TEMPLATE_PATHS;
7497
7740
  var init_host3 = __esm({
7498
7741
  "../../src/setup/host.ts"() {
7499
7742
  "use strict";
7500
- init_profile();
7743
+ init_profile2();
7501
7744
  init_packageRoot();
7502
7745
  init_host();
7503
7746
  init_meatProcess();
@@ -7509,42 +7752,6 @@ var init_host3 = __esm({
7509
7752
  }
7510
7753
  });
7511
7754
 
7512
- // ../../src/execution/bashTimeout.ts
7513
- function clampBashTimeout(requested) {
7514
- if (typeof requested !== "number" || !Number.isFinite(requested)) return BASH_TIMEOUT_MS;
7515
- return Math.min(Math.max(Math.trunc(requested), BASH_TIMEOUT_MIN_MS), BASH_TIMEOUT_MAX_MS);
7516
- }
7517
- function bashBudgetWithinRun(wantedMs, remainingMs) {
7518
- const secs = (ms) => Math.max(0, Math.round(ms / 1e3));
7519
- const cap = Math.trunc(remainingMs - RUN_DEADLINE_RESERVE_MS);
7520
- if (cap < BASH_TIMEOUT_MIN_MS) {
7521
- return {
7522
- kind: "exhausted",
7523
- note: `run budget exhausted \u2014 ${secs(remainingMs)}s of wall clock left, inside the ${secs(RUN_DEADLINE_RESERVE_MS)}s write-up reserve, so the command was not run; write up what you have now`
7524
- };
7525
- }
7526
- if (cap >= wantedMs) return { kind: "unchanged" };
7527
- return {
7528
- kind: "clipped",
7529
- timeoutMs: cap,
7530
- note: `[timeout clipped to ${secs(cap)}s \u2014 the run's wall clock ends in ${secs(remainingMs)}s]`
7531
- };
7532
- }
7533
- function bashTimeoutNote(timeoutMs) {
7534
- return `aborted at the ${Math.round(timeoutMs / 1e3)}s command timeout (pass the bash tool's timeoutMs for longer commands, max ${BASH_TIMEOUT_MAX_MS} ms)`;
7535
- }
7536
- var BASH_TIMEOUT_MS, BASH_TIMEOUT_MIN_MS, BASH_TIMEOUT_MAX_MS, RUN_DEADLINE_RESERVE_MS, EXEC_CALL_MARGIN_MS;
7537
- var init_bashTimeout = __esm({
7538
- "../../src/execution/bashTimeout.ts"() {
7539
- "use strict";
7540
- BASH_TIMEOUT_MS = 5 * 6e4;
7541
- BASH_TIMEOUT_MIN_MS = 1e3;
7542
- BASH_TIMEOUT_MAX_MS = 20 * 6e4;
7543
- RUN_DEADLINE_RESERVE_MS = 6e4;
7544
- EXEC_CALL_MARGIN_MS = 3e4;
7545
- }
7546
- });
7547
-
7548
7755
  // ../../src/execution/executor.ts
7549
7756
  import { execFile } from "node:child_process";
7550
7757
  import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
@@ -7602,15 +7809,15 @@ var init_executor = __esm({
7602
7809
  init_bashTimeout();
7603
7810
  ExecInfraError = class extends Error {
7604
7811
  infra = true;
7605
- constructor(message) {
7606
- super(message);
7812
+ constructor(message2) {
7813
+ super(message2);
7607
7814
  this.name = "ExecInfraError";
7608
7815
  }
7609
7816
  };
7610
7817
  ExecCapacityError = class extends Error {
7611
7818
  capacity = true;
7612
- constructor(message) {
7613
- super(message);
7819
+ constructor(message2) {
7820
+ super(message2);
7614
7821
  this.name = "ExecCapacityError";
7615
7822
  }
7616
7823
  };
@@ -8905,7 +9112,7 @@ var init_residentState = __esm({
8905
9112
  import { resolve as resolve7 } from "node:path";
8906
9113
  import { mkdirSync as mkdirSync7 } from "node:fs";
8907
9114
  async function makeExecutor(opts, ctx, span) {
8908
- const machine = ctx.agent.machine;
9115
+ const machine = ctx.profile.machine;
8909
9116
  if (machine === "none") {
8910
9117
  return { executor: new NullExecutor(ctx.agent.name), backend: "local" };
8911
9118
  }
@@ -8930,7 +9137,7 @@ async function makeExecutor(opts, ctx, span) {
8930
9137
  if (probe.kind === "status" && isServiceable(probe.state, probe.reason)) {
8931
9138
  try {
8932
9139
  const nonWarm = probe.state === "warm" ? void 0 : oneLine(`${probe.state}${probe.reason ? ` (${probe.reason})` : ""}`);
8933
- const readonly = ctx.agent.toolset === "readonly" ? true : void 0;
9140
+ const readonly = ctx.profile.identity === "read" ? true : void 0;
8934
9141
  return await openResident(
8935
9142
  {
8936
9143
  baseUrl: resident.baseUrl,
@@ -9056,7 +9263,12 @@ function localWorkspaceDir(baseDir, threadKey) {
9056
9263
  return resolve7(baseDir, safe);
9057
9264
  }
9058
9265
  function perThreadCheckout(ctx) {
9059
- return { threadKey: ctx.threadKey, repo: ctx.repo, ref: ctx.ref, resolveEnvs: () => githubEnvs(ctx.agent) };
9266
+ return {
9267
+ threadKey: ctx.threadKey,
9268
+ repo: ctx.repo,
9269
+ ref: ctx.ref,
9270
+ resolveEnvs: () => githubEnvs(ctx.profile.identity)
9271
+ };
9060
9272
  }
9061
9273
  async function makePerThreadExecutor(opts, input) {
9062
9274
  const { threadKey } = input;
@@ -9098,11 +9310,13 @@ async function makePerThreadExecutor(opts, input) {
9098
9310
  }
9099
9311
  throw new Error(`Unknown execution.type "${type}" (valid: local, e2b, cloudflare)`);
9100
9312
  }
9101
- function githubTokenScopeFor(agent) {
9102
- return agent.toolset === "readonly" ? "read" : "write";
9313
+ function githubTokenScopeFor(identity2) {
9314
+ return identity2 === "none" ? void 0 : identity2;
9103
9315
  }
9104
- async function githubEnvs(agent) {
9105
- const token = await resolveGithubToken(githubTokenScopeFor(agent));
9316
+ async function githubEnvs(identity2) {
9317
+ const scope = githubTokenScopeFor(identity2);
9318
+ if (scope === void 0) return {};
9319
+ const token = await resolveGithubToken(scope);
9106
9320
  return token ? { GH_TOKEN: token } : {};
9107
9321
  }
9108
9322
  function perThreadBackend(opts) {
@@ -9170,9 +9384,9 @@ function parseFindings(body) {
9170
9384
  function isAgentLogin(login, identities) {
9171
9385
  return login.endsWith("[bot]") || identities.agentLogins.includes(login);
9172
9386
  }
9173
- function coauthorsOf(message) {
9387
+ function coauthorsOf(message2) {
9174
9388
  const out = [];
9175
- for (const line3 of message.split("\n")) {
9389
+ for (const line3 of message2.split("\n")) {
9176
9390
  const m = /^\s*co-authored-by:\s*(.+?)\s*$/i.exec(line3);
9177
9391
  if (m) out.push(m[1]);
9178
9392
  }
@@ -9365,9 +9579,25 @@ function indicatorLines(x) {
9365
9579
  `agent runs: ${x.agentRuns.count} \xB7 ${x.agentRuns.minutes.toFixed(1)} min`
9366
9580
  ];
9367
9581
  }
9368
- function renderDeliveryReport(report2) {
9582
+ function snapshotAgeText(snapshotAt, nowMs) {
9583
+ const minutes = Math.max(0, Math.floor((nowMs - Date.parse(snapshotAt)) / 6e4));
9584
+ if (!Number.isFinite(minutes)) return "";
9585
+ const unit = (n2, word) => `${n2} ${word}${n2 === 1 ? "" : "s"} ago`;
9586
+ if (minutes < 1) return "just now";
9587
+ if (minutes < 90) return unit(minutes, "minute");
9588
+ const hours2 = Math.round(minutes / 60);
9589
+ if (hours2 < 48) return unit(hours2, "hour");
9590
+ return unit(Math.round(hours2 / 24), "day");
9591
+ }
9592
+ function asOf(snapshotAt, nowMs) {
9593
+ return `as of ${snapshotAt.slice(0, 10)} ${snapshotAt.slice(11, 16)} UTC, ${snapshotAgeText(snapshotAt, nowMs)}`;
9594
+ }
9595
+ function renderDeliveryReport(report2, nowMs = systemClock()) {
9369
9596
  const lines = [
9370
- `${report2.repo} \xB7 ${report2.range.since} \u2192 ${report2.range.until} \xB7 ${report2.range.weeks} week${report2.range.weeks === 1 ? "" : "s"}`
9597
+ [
9598
+ `${report2.repo} \xB7 ${report2.range.since} \u2192 ${report2.range.until} \xB7 ${report2.range.weeks} week${report2.range.weeks === 1 ? "" : "s"}`,
9599
+ ...report2.snapshotAt !== void 0 ? [asOf(report2.snapshotAt, nowMs)] : []
9600
+ ].join(" \xB7 ")
9371
9601
  ];
9372
9602
  if (report2.truncated) lines.push("(the newest pull requests only \u2014 the fetch stopped before the range's start)");
9373
9603
  for (const w of report2.weeks) {
@@ -9409,6 +9639,18 @@ function stringList(raw, what, valid = () => true) {
9409
9639
  throw new Error(`${what} must be a list of ${what.endsWith("repos") ? "owner/name repositories" : "names"}`);
9410
9640
  return raw;
9411
9641
  }
9642
+ function snapshotConfig(raw) {
9643
+ if (raw === void 0) return { everyMinutes: SNAPSHOT_EVERY_MINUTES.default };
9644
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
9645
+ throw new Error("delivery.snapshot must be a mapping");
9646
+ const every = raw.everyMinutes;
9647
+ if (every === void 0) return { everyMinutes: SNAPSHOT_EVERY_MINUTES.default };
9648
+ if (typeof every !== "number" || !Number.isInteger(every) || every < SNAPSHOT_EVERY_MINUTES.min || every > SNAPSHOT_EVERY_MINUTES.max)
9649
+ throw new Error(
9650
+ `delivery.snapshot.everyMinutes must be a whole number of minutes between ${SNAPSHOT_EVERY_MINUTES.min} and ${SNAPSHOT_EVERY_MINUTES.max}`
9651
+ );
9652
+ return { everyMinutes: every };
9653
+ }
9412
9654
  function parseDeliveryConfig(raw) {
9413
9655
  if (raw === void 0 || raw === null) return void 0;
9414
9656
  if (typeof raw !== "object" || Array.isArray(raw)) throw new Error("delivery: must be a mapping");
@@ -9417,7 +9659,8 @@ function parseDeliveryConfig(raw) {
9417
9659
  repos: stringList(r.repos, "delivery.repos", (s) => REPO_SLUG.test(s)),
9418
9660
  reviewers: stringList(r.reviewers, "delivery.reviewers"),
9419
9661
  agentLogins: stringList(r.agentLogins, "delivery.agentLogins"),
9420
- agentCoauthors: stringList(r.agentCoauthors, "delivery.agentCoauthors")
9662
+ agentCoauthors: stringList(r.agentCoauthors, "delivery.agentCoauthors"),
9663
+ snapshot: snapshotConfig(r.snapshot)
9421
9664
  };
9422
9665
  }
9423
9666
  function createDeliveryService(cfg, source2, opts = {}) {
@@ -9427,9 +9670,10 @@ function createDeliveryService(cfg, source2, opts = {}) {
9427
9670
  repos: () => [...cfg?.repos ?? []],
9428
9671
  async report(repo, o) {
9429
9672
  if (!REPO_SLUG.test(repo)) throw new Error(`repository must be owner/name, got ${JSON.stringify(repo)}`);
9430
- const range = resolveDeliveryRange({ since: o.since, weeks: o.weeks }, now());
9673
+ const at = now();
9674
+ const range = resolveDeliveryRange({ since: o.since, weeks: o.weeks }, at);
9431
9675
  const [fetched, process2, runs] = await Promise.all([
9432
- source2.fetchPullRequests(repo, range),
9676
+ source2.fetchPullRequests(repo, range, o.fresh ? { fresh: true } : void 0),
9433
9677
  opts.identities?.() ?? Promise.resolve({}),
9434
9678
  o.runs?.(range) ?? Promise.resolve([])
9435
9679
  ]);
@@ -9438,18 +9682,22 @@ function createDeliveryService(cfg, source2, opts = {}) {
9438
9682
  agentLogins: [.../* @__PURE__ */ new Set([...cfg?.agentLogins ?? [], ...process2.agentLogins ?? []])],
9439
9683
  agentCoauthors: [.../* @__PURE__ */ new Set([...cfg?.agentCoauthors ?? [], ...process2.agentCoauthors ?? []])]
9440
9684
  };
9441
- return buildDeliveryReport({
9442
- repo,
9443
- range,
9444
- prs: fetched.prs,
9445
- runs,
9446
- identities,
9447
- truncated: fetched.truncated
9448
- });
9685
+ return {
9686
+ ...buildDeliveryReport({
9687
+ repo,
9688
+ range,
9689
+ prs: fetched.prs,
9690
+ runs,
9691
+ identities,
9692
+ truncated: fetched.truncated
9693
+ }),
9694
+ // A source behind a snapshot says when it read; a bare one read just now.
9695
+ snapshotAt: fetched.fetchedAt ?? at.toISOString()
9696
+ };
9449
9697
  }
9450
9698
  };
9451
9699
  }
9452
- var FINDING_SEVERITIES, DAY_MS, HOUR_MS, emptyCounts, CI_TRIGGERS, CI_DECISIVE, byTime, EMPTY_IDENTITIES, DEFAULT_WEEKS, MAX_WEEKS, ISO_DAY, pct, hours, REPO_SLUG, DELIVERY_OFF_MESSAGE, NullDeliveryService;
9700
+ var FINDING_SEVERITIES, DAY_MS, HOUR_MS, emptyCounts, CI_TRIGGERS, CI_DECISIVE, byTime, EMPTY_IDENTITIES, DEFAULT_WEEKS, MAX_WEEKS, ISO_DAY, pct, hours, SNAPSHOT_EVERY_MINUTES, REPO_SLUG, DELIVERY_OFF_MESSAGE, NullDeliveryService;
9453
9701
  var init_delivery = __esm({
9454
9702
  "../../src/core/delivery.ts"() {
9455
9703
  "use strict";
@@ -9477,6 +9725,7 @@ var init_delivery = __esm({
9477
9725
  ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
9478
9726
  pct = (share) => share === null ? "\u2014" : `${Math.round(share * 100)}%`;
9479
9727
  hours = (h, digits2 = 1) => h === null ? "\u2014" : `${h.toFixed(digits2)} h`;
9728
+ SNAPSHOT_EVERY_MINUTES = Object.freeze({ default: 60, min: 5, max: 1440 });
9480
9729
  REPO_SLUG = /^(?!\.+\/)[\w.-]+\/(?!\.+$)[\w.-]+$/;
9481
9730
  DELIVERY_OFF_MESSAGE = "Delivery indicators aren't available \u2014 this process has no GitHub credential (the GitHub App triple or GH_TOKEN); set one, then name repositories under delivery.repos.";
9482
9731
  NullDeliveryService = class {
@@ -9730,12 +9979,16 @@ var init_githubDelivery = __esm({
9730
9979
  const json = async (res) => res.status === 404 ? void 0 : res.json();
9731
9980
  const merged = [];
9732
9981
  let truncated = false;
9982
+ let oldestUpdated;
9733
9983
  for (let page2 = 1; ; page2++) {
9734
9984
  const res = await get(
9735
9985
  "pulls",
9736
9986
  `/repos/${repo}/pulls?state=closed&sort=updated&direction=desc&per_page=${PAGE_SIZE}&page=${page2}`
9737
9987
  );
9738
9988
  const items = parsePullListPage(await json(res));
9989
+ for (const item of items) {
9990
+ if (!oldestUpdated || Date.parse(item.updatedAt) < Date.parse(oldestUpdated.updatedAt)) oldestUpdated = item;
9991
+ }
9739
9992
  const selected = selectMerged(items, range);
9740
9993
  merged.push(...selected.merged);
9741
9994
  if (selected.olderSeen || items.length < PAGE_SIZE) break;
@@ -9744,6 +9997,7 @@ var init_githubDelivery = __esm({
9744
9997
  break;
9745
9998
  }
9746
9999
  }
10000
+ const completeFrom = truncated && oldestUpdated ? oldestUpdated.updatedAt : `${range.since}T00:00:00Z`;
9747
10001
  const issues = /* @__PURE__ */ new Map();
9748
10002
  const issueOf = (n2) => {
9749
10003
  let p = issues.get(n2);
@@ -9790,12 +10044,257 @@ var init_githubDelivery = __esm({
9790
10044
  ...issue ? { issue } : {}
9791
10045
  };
9792
10046
  });
9793
- return { prs, truncated };
10047
+ return { prs, truncated, completeFrom };
9794
10048
  }
9795
10049
  };
9796
10050
  }
9797
10051
  });
9798
10052
 
10053
+ // ../../src/core/deliverySnapshot.ts
10054
+ function snapshotAgeMinutes(snapshotAt, nowMs) {
10055
+ return Math.max(0, Math.floor((nowMs - Date.parse(snapshotAt)) / 6e4));
10056
+ }
10057
+ function serve(snapshot, range) {
10058
+ return {
10059
+ prs: snapshot.prs,
10060
+ truncated: dayStart(range.since) < Date.parse(snapshot.completeFrom),
10061
+ fetchedAt: snapshot.snapshotAt
10062
+ };
10063
+ }
10064
+ var SNAPSHOT_WEEKS, SNAPSHOT_TICK_MS, dayStart, message, SnapshottingDeliverySource;
10065
+ var init_deliverySnapshot = __esm({
10066
+ "../../src/core/deliverySnapshot.ts"() {
10067
+ "use strict";
10068
+ init_delivery();
10069
+ init_clock();
10070
+ SNAPSHOT_WEEKS = 13;
10071
+ SNAPSHOT_TICK_MS = 6e4;
10072
+ dayStart = (day) => Date.parse(`${day}T00:00:00Z`);
10073
+ message = (err2) => err2 instanceof Error ? err2.message : String(err2);
10074
+ SnapshottingDeliverySource = class {
10075
+ constructor(inner, store, opts = {}) {
10076
+ this.inner = inner;
10077
+ this.store = store;
10078
+ this.now = opts.now ?? (() => new Date(systemClock()));
10079
+ this.warn = opts.warn ?? (() => void 0);
10080
+ this.weeks = opts.weeks ?? SNAPSHOT_WEEKS;
10081
+ }
10082
+ inner;
10083
+ store;
10084
+ memory = /* @__PURE__ */ new Map();
10085
+ /** Store reads in flight, one per repository. */
10086
+ loading = /* @__PURE__ */ new Map();
10087
+ /** GitHub reads in flight, one per repository: concurrent callers share it. */
10088
+ refreshing = /* @__PURE__ */ new Map();
10089
+ now;
10090
+ warn;
10091
+ weeks;
10092
+ async fetchPullRequests(repo, range, opts) {
10093
+ const snapshot = opts?.fresh ? void 0 : await this.current(repo);
10094
+ if (snapshot && range.since >= snapshot.range.since) return serve(snapshot, range);
10095
+ if (range.since < this.window().since) return this.live(repo, range);
10096
+ return serve(await this.refresh(repo), range);
10097
+ }
10098
+ /** The repository's snapshot as this process knows it: memory, else the store, read once. */
10099
+ current(repo) {
10100
+ const known = this.memory.get(repo);
10101
+ if (known) return Promise.resolve(known);
10102
+ let loading = this.loading.get(repo);
10103
+ if (!loading) {
10104
+ loading = this.store.get(repo).then((stored) => {
10105
+ if (stored) this.memory.set(repo, stored);
10106
+ return stored;
10107
+ }).catch((err2) => {
10108
+ this.warn(`delivery snapshot for ${repo} not read from the store: ${message(err2)}`);
10109
+ return void 0;
10110
+ }).finally(() => this.loading.delete(repo));
10111
+ this.loading.set(repo, loading);
10112
+ }
10113
+ return loading;
10114
+ }
10115
+ /** Read the window from GitHub now, keep it in memory and store it. One read at a time per repository. */
10116
+ refresh(repo) {
10117
+ let refreshing = this.refreshing.get(repo);
10118
+ if (!refreshing) {
10119
+ refreshing = this.read(repo).finally(() => this.refreshing.delete(repo));
10120
+ this.refreshing.set(repo, refreshing);
10121
+ }
10122
+ return refreshing;
10123
+ }
10124
+ /**
10125
+ * Keep every named repository's snapshot within `everyMinutes`: a tick a
10126
+ * minute (unref'd — it never holds the process open) refreshes the ones
10127
+ * missing or older than the interval, one repository at a time, and runs
10128
+ * once at start so a stale snapshot after a restart is refreshed at once
10129
+ * and a young one is left alone. A failing refresh is a warning; the next
10130
+ * tick tries again. `tick()` is the pass itself, for tests.
10131
+ */
10132
+ startRefreshLoop(opts) {
10133
+ let running;
10134
+ const pass = async () => {
10135
+ for (const repo of opts.repos) {
10136
+ try {
10137
+ const current = await this.current(repo);
10138
+ if (current && snapshotAgeMinutes(current.snapshotAt, this.now().getTime()) < opts.everyMinutes) continue;
10139
+ await this.refresh(repo);
10140
+ } catch (err2) {
10141
+ this.warn(`delivery snapshot for ${repo} not refreshed: ${message(err2)}`);
10142
+ }
10143
+ }
10144
+ };
10145
+ const tick = () => running ??= pass().finally(() => {
10146
+ running = void 0;
10147
+ });
10148
+ const start2 = opts.setInterval ?? ((fn, ms) => {
10149
+ const t = setInterval(fn, ms);
10150
+ t.unref?.();
10151
+ return t;
10152
+ });
10153
+ const stop = opts.clearInterval ?? ((t) => clearInterval(t));
10154
+ const timer = start2(() => void tick(), SNAPSHOT_TICK_MS);
10155
+ timer.unref?.();
10156
+ void tick();
10157
+ return { stop: () => stop(timer), tick };
10158
+ }
10159
+ window() {
10160
+ return resolveDeliveryRange({ weeks: this.weeks }, this.now());
10161
+ }
10162
+ async live(repo, range) {
10163
+ const startedAt = this.now();
10164
+ const fetched = await this.inner.fetchPullRequests(repo, range);
10165
+ return { prs: fetched.prs, truncated: fetched.truncated, fetchedAt: startedAt.toISOString() };
10166
+ }
10167
+ async read(repo) {
10168
+ const startedAt = this.now();
10169
+ const range = resolveDeliveryRange({ weeks: this.weeks }, startedAt);
10170
+ const fetched = await this.inner.fetchPullRequests(repo, range);
10171
+ const snapshot = {
10172
+ repo,
10173
+ snapshotAt: startedAt.toISOString(),
10174
+ range,
10175
+ prs: fetched.prs,
10176
+ truncated: fetched.truncated,
10177
+ completeFrom: fetched.completeFrom ?? `${range.since}T00:00:00Z`
10178
+ };
10179
+ this.memory.set(repo, snapshot);
10180
+ try {
10181
+ await this.store.put(snapshot);
10182
+ } catch (err2) {
10183
+ this.warn(`delivery snapshot for ${repo} not stored: ${message(err2)}`);
10184
+ }
10185
+ return snapshot;
10186
+ }
10187
+ };
10188
+ }
10189
+ });
10190
+
10191
+ // ../../src/core/workerError.ts
10192
+ async function errorSuffix(res) {
10193
+ const text = await res.text().catch(() => "");
10194
+ try {
10195
+ const parsed = JSON.parse(text);
10196
+ return parsed?.error ? `: ${String(parsed.error)}` : "";
10197
+ } catch {
10198
+ return "";
10199
+ }
10200
+ }
10201
+ var init_workerError = __esm({
10202
+ "../../src/core/workerError.ts"() {
10203
+ "use strict";
10204
+ }
10205
+ });
10206
+
10207
+ // ../../src/core/deliverySnapshotStore.ts
10208
+ function isPullRequestFacts(v) {
10209
+ if (typeof v !== "object" || v === null) return false;
10210
+ const p = v;
10211
+ return typeof p.number === "number" && Number.isFinite(p.number) && typeof p.title === "string" && typeof p.author === "string" && isInstant(p.createdAt) && isInstant(p.mergedAt) && (p.firstHeadSha === void 0 || typeof p.firstHeadSha === "string") && Array.isArray(p.ci) && Array.isArray(p.reviews) && Array.isArray(p.pushes) && (p.issue === void 0 || typeof p.issue === "object" && p.issue !== null);
10212
+ }
10213
+ function isDeliverySnapshot(v) {
10214
+ if (typeof v !== "object" || v === null) return false;
10215
+ const s = v;
10216
+ if (typeof s.repo !== "string" || !REPO_SLUG.test(s.repo)) return false;
10217
+ if (!isInstant(s.snapshotAt) || !isInstant(s.completeFrom)) return false;
10218
+ if (typeof s.truncated !== "boolean") return false;
10219
+ const range = s.range;
10220
+ if (typeof range !== "object" || range === null || typeof range.since !== "string" || !ISO_DAY2.test(range.since) || typeof range.until !== "string" || !ISO_DAY2.test(range.until) || typeof range.weeks !== "number" || !Number.isFinite(range.weeks))
10221
+ return false;
10222
+ return Array.isArray(s.prs) && s.prs.every(isPullRequestFacts);
10223
+ }
10224
+ function buildDeliverySnapshotStore(config, secrets, warn) {
10225
+ const worker = [config.runtimeOverrides, config.runHistory, config.schedules, config.memory].find(
10226
+ (block) => block?.worker?.baseUrl
10227
+ )?.worker;
10228
+ if (!worker) {
10229
+ warn(
10230
+ "delivery snapshots are kept in memory \u2014 a restart loses them and the first request after one reads GitHub live. Name the state Worker (runHistory.worker, runtimeOverrides.worker, schedules.worker or memory.worker) to keep them on it."
10231
+ );
10232
+ return new InMemoryDeliverySnapshotStore();
10233
+ }
10234
+ const tokenEnv = worker.tokenEnv ?? DEFAULT_TOKEN_ENV;
10235
+ const token = secrets.named(tokenEnv);
10236
+ if (!token) {
10237
+ warn(
10238
+ `the state Worker is configured but ${tokenEnv} is unset \u2014 delivery snapshots are kept in memory and a restart loses them. Set ${tokenEnv} to the state Worker's bearer.`
10239
+ );
10240
+ return new InMemoryDeliverySnapshotStore();
10241
+ }
10242
+ return new WorkerDeliverySnapshotStore({ baseUrl: worker.baseUrl, token: token.reveal() });
10243
+ }
10244
+ var ISO_DAY2, isInstant, InMemoryDeliverySnapshotStore, SNAPSHOT_WORKER_TIMEOUT_MS, WorkerDeliverySnapshotStore, DEFAULT_TOKEN_ENV;
10245
+ var init_deliverySnapshotStore = __esm({
10246
+ "../../src/core/deliverySnapshotStore.ts"() {
10247
+ "use strict";
10248
+ init_delivery();
10249
+ init_workerError();
10250
+ ISO_DAY2 = /^\d{4}-\d{2}-\d{2}$/;
10251
+ isInstant = (v) => typeof v === "string" && Number.isFinite(Date.parse(v));
10252
+ InMemoryDeliverySnapshotStore = class {
10253
+ byRepo = /* @__PURE__ */ new Map();
10254
+ async get(repo) {
10255
+ const s = this.byRepo.get(repo);
10256
+ return s ? structuredClone(s) : void 0;
10257
+ }
10258
+ async put(snapshot) {
10259
+ this.byRepo.set(snapshot.repo, structuredClone(snapshot));
10260
+ }
10261
+ };
10262
+ SNAPSHOT_WORKER_TIMEOUT_MS = 3e4;
10263
+ WorkerDeliverySnapshotStore = class {
10264
+ constructor(opts) {
10265
+ this.opts = opts;
10266
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
10267
+ this.fetchImpl = opts.fetch ?? fetch;
10268
+ }
10269
+ opts;
10270
+ baseUrl;
10271
+ fetchImpl;
10272
+ async get(repo) {
10273
+ const res = await this.post("/delivery/get", { repo });
10274
+ if (!res.ok) throw new Error(`state Worker /delivery/get HTTP ${res.status}${await errorSuffix(res)}`);
10275
+ const data = await res.json().catch(() => ({}));
10276
+ if (data.snapshot === null || data.snapshot === void 0) return void 0;
10277
+ if (!isDeliverySnapshot(data.snapshot))
10278
+ throw new Error(`state Worker /delivery/get: the answer for ${repo} is not a delivery snapshot`);
10279
+ return data.snapshot;
10280
+ }
10281
+ async put(snapshot) {
10282
+ const res = await this.post("/delivery/put", { snapshot });
10283
+ if (!res.ok) throw new Error(`state Worker /delivery/put HTTP ${res.status}${await errorSuffix(res)}`);
10284
+ }
10285
+ post(path, body) {
10286
+ return this.fetchImpl(`${this.baseUrl}${path}`, {
10287
+ method: "POST",
10288
+ headers: { "content-type": "application/json", authorization: `Bearer ${this.opts.token}` },
10289
+ body: JSON.stringify(body),
10290
+ signal: AbortSignal.timeout(SNAPSHOT_WORKER_TIMEOUT_MS)
10291
+ });
10292
+ }
10293
+ };
10294
+ DEFAULT_TOKEN_ENV = "MEMORY_TOKEN";
10295
+ }
10296
+ });
10297
+
9799
10298
  // ../../src/core/runDuration.ts
9800
10299
  function runDurationMs(stamps, now) {
9801
10300
  const end = stamps.finishedAt ?? now;
@@ -10678,8 +11177,8 @@ function bindCommands(registry, deps) {
10678
11177
  settle: (id, value, caller) => registry.settle(id, value, caller, deps)
10679
11178
  };
10680
11179
  }
10681
- function fail(error, message, decidedBy = "registry") {
10682
- return { ok: false, error, status: ERROR_STATUS[error], message, decidedBy };
11180
+ function fail(error, message2, decidedBy = "registry") {
11181
+ return { ok: false, error, status: ERROR_STATUS[error], message: message2, decidedBy };
10683
11182
  }
10684
11183
  function rawInput(input) {
10685
11184
  const args = typeof input === "object" && input !== null && Array.isArray(input.args) ? input.args : [];
@@ -10798,8 +11297,8 @@ var init_commandRegistry = __esm({
10798
11297
  CAMEL_KEY = /^[a-z][A-Za-z0-9]*$/;
10799
11298
  flag = z4.union([z4.boolean(), z4.enum(["true", "false"]).transform((v) => v === "true")]);
10800
11299
  CommandError = class extends Error {
10801
- constructor(code, message) {
10802
- super(message);
11300
+ constructor(code, message2) {
11301
+ super(message2);
10803
11302
  this.code = code;
10804
11303
  this.name = "CommandError";
10805
11304
  }
@@ -10953,12 +11452,13 @@ function summarizeScope(s) {
10953
11452
  function registerConfigCommands(registry) {
10954
11453
  for (const cmd of configCommands) registry.register(cmd);
10955
11454
  }
10956
- var defineCommand2, scopeArg, channelOption, effort, effortLevels, modelRef, who, configShow, configSet, configClear, quote, configInstructions, configCommands;
11455
+ var defineCommand2, scopeArg, channelOption, effort, effortLevels, modelRef, boundaryOption, who, configShow, configSet, configClear, quote, configInstructions, configCommands;
10957
11456
  var init_config3 = __esm({
10958
11457
  "../../src/core/commands/config.ts"() {
10959
11458
  "use strict";
10960
11459
  init_config();
10961
11460
  init_validate();
11461
+ init_registry();
10962
11462
  init_effort();
10963
11463
  init_authorize();
10964
11464
  init_commandRegistry();
@@ -10972,6 +11472,11 @@ var init_config3 = __esm({
10972
11472
  effort = z5.enum(EFFORT_LEVELS);
10973
11473
  effortLevels = `<${EFFORT_LEVELS.join("|")}>`;
10974
11474
  modelRef = z5.string().min(1);
11475
+ boundaryOption = z5.object({
11476
+ maxMinutes: z5.coerce.number().int().min(MIN_BOUNDARY_MINUTES).optional().describe(`cap the wall-clock budget of every run in this scope (minutes, >= ${MIN_BOUNDARY_MINUTES})`),
11477
+ maxIdentity: z5.enum(IDENTITIES).optional().describe(`cap the identity runs may act as <${IDENTITIES.join("|")}> \u2014 a preset above it is refused`),
11478
+ machines: z5.string().optional().describe(`the machine classes runs may execute on, comma-separated from ${MACHINE_CLASSES.join(", ")}`)
11479
+ }).optional();
10975
11480
  who = (scope) => scope === "channel" ? "channel" : "your";
10976
11481
  configShow = defineCommand2({
10977
11482
  id: "config.show",
@@ -10998,11 +11503,12 @@ var init_config3 = __esm({
10998
11503
  models: z5.record(z5.string(), modelRef).optional().describe("per-agent model: --models.<agent> provider/model"),
10999
11504
  effort: effort.optional().describe(`force a model effort ${effortLevels}`),
11000
11505
  efforts: z5.record(z5.string(), effort).optional().describe(`per-agent effort: --efforts.<agent> ${effortLevels}`),
11506
+ boundary: boundaryOption,
11001
11507
  channel: channelOption
11002
11508
  }),
11003
11509
  action: "config:write",
11004
11510
  effect: "write",
11005
- describe: "Set the agent, model, or effort for a channel (gated) or for yourself; per-agent forms take --models.<agent> / --efforts.<agent>.",
11511
+ describe: "Set the agent, model, effort or boundary for a channel (gated) or for yourself; per-agent forms take --models.<agent> / --efforts.<agent>, the boundary's axes --boundary.<axis> (a boundary caps every run in the scope and never grants).",
11006
11512
  render: (output) => {
11007
11513
  const o = output;
11008
11514
  return `Updated ${who(o.scope)} scope. Now: ${JSON.stringify(o.effective)}`;
@@ -11031,10 +11537,26 @@ var init_config3 = __esm({
11031
11537
  if (options.models) patch.models = options.models;
11032
11538
  if (options.effort !== void 0) patch.effort = options.effort;
11033
11539
  if (options.efforts) patch.efforts = options.efforts;
11540
+ if (options.boundary) {
11541
+ const { machines, ...axes } = options.boundary;
11542
+ const boundary = { ...axes };
11543
+ if (machines !== void 0) {
11544
+ const classes = machines.split(",").map((m) => m.trim());
11545
+ if (classes.length === 0 || classes.some((m) => !MACHINE_CLASSES.includes(m)))
11546
+ throw new CommandError(
11547
+ "invalid_input",
11548
+ `boundary.machines: expected a comma-separated list of ${MACHINE_CLASSES.join(", ")}`
11549
+ );
11550
+ boundary.machines = classes;
11551
+ }
11552
+ const problem = boundaryProblem("boundary", boundary);
11553
+ if (problem) throw new CommandError("invalid_input", problem);
11554
+ if (Object.keys(boundary).length > 0) patch.boundary = boundary;
11555
+ }
11034
11556
  if (Object.keys(patch).length === 0)
11035
11557
  throw new CommandError(
11036
11558
  "invalid_input",
11037
- "nothing to set: pass --agent, --model, --models.<agent>, --effort, or --efforts.<agent>"
11559
+ "nothing to set: pass --agent, --model, --models.<agent>, --effort, --efforts.<agent>, or --boundary.<maxMinutes|maxIdentity|machines>"
11038
11560
  );
11039
11561
  let effective;
11040
11562
  if (args.scope === "channel") {
@@ -12244,6 +12766,17 @@ var init_runFriction = __esm({
12244
12766
  });
12245
12767
 
12246
12768
  // ../../src/core/runRecord.ts
12769
+ function isRunProfileRecord(v) {
12770
+ if (typeof v !== "object" || v === null) return false;
12771
+ const p = v;
12772
+ if (typeof p.preset !== "string") return false;
12773
+ if (typeof p.machine !== "string" || !Object.hasOwn(MACHINE_CLASSES_IN_RECORD, p.machine)) return false;
12774
+ if (typeof p.identity !== "string" || !Object.hasOwn(IDENTITIES_IN_RECORD, p.identity)) return false;
12775
+ if (!isFiniteNumber(p.minutes) || p.minutes <= 0) return false;
12776
+ if (p.boundedBy !== void 0 && (typeof p.boundedBy !== "string" || !Object.hasOwn(BOUNDARY_SCOPES_IN_RECORD, p.boundedBy)))
12777
+ return false;
12778
+ return true;
12779
+ }
12247
12780
  function storedEventSeqs(events) {
12248
12781
  let prev = 0;
12249
12782
  for (const e of events) {
@@ -12369,6 +12902,7 @@ function isRunRecord(v) {
12369
12902
  return false;
12370
12903
  if (!isOptionalString(r.activity) || !isOptionalString(r.sourceUrl) || !isOptionalString(r.userName)) return false;
12371
12904
  if (r.handoff !== void 0 && !isHandoffShape(r.handoff)) return false;
12905
+ if (r.profile !== void 0 && !isRunProfileRecord(r.profile)) return false;
12372
12906
  if (typeof r.channelId !== "string" || typeof r.userId !== "string" || typeof r.threadKey !== "string") return false;
12373
12907
  if (r.channelVisibility !== void 0 && !CHANNEL_VISIBILITIES2.includes(r.channelVisibility))
12374
12908
  return false;
@@ -12495,7 +13029,7 @@ function dropSpanPairsFromTheMiddle(events, eventBudget) {
12495
13029
  }
12496
13030
  return events.filter((_, k) => !dropped.has(k));
12497
13031
  }
12498
- var RUN_STATUSES, RUN_ID_PATTERN, RUN_LIST_DEFAULT_LIMIT, RUN_LIST_MAX_LIMIT, RUN_EVENTS_DEFAULT_PAGE, RUN_EVENTS_MAX_PAGE, CHANNEL_VISIBILITIES2, KIB, MIB, GIB2, DEFAULT_RETENTION_POLICY, RETENTION_BOUNDS, MAX_RECORD_BYTES, MAX_EVENT_BYTES, ELLIPSIS, encoder;
13032
+ var RUN_STATUSES, RUN_ID_PATTERN, MACHINE_CLASSES_IN_RECORD, IDENTITIES_IN_RECORD, BOUNDARY_SCOPES_IN_RECORD, RUN_LIST_DEFAULT_LIMIT, RUN_LIST_MAX_LIMIT, RUN_EVENTS_DEFAULT_PAGE, RUN_EVENTS_MAX_PAGE, CHANNEL_VISIBILITIES2, KIB, MIB, GIB2, DEFAULT_RETENTION_POLICY, RETENTION_BOUNDS, MAX_RECORD_BYTES, MAX_EVENT_BYTES, ELLIPSIS, encoder;
12499
13033
  var init_runRecord = __esm({
12500
13034
  "../../src/core/runRecord.ts"() {
12501
13035
  "use strict";
@@ -12504,6 +13038,19 @@ var init_runRecord = __esm({
12504
13038
  init_runFriction();
12505
13039
  RUN_STATUSES = ["completed", "stopped_soft", "stopped_hard", "failed", "interrupted"];
12506
13040
  RUN_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
13041
+ MACHINE_CLASSES_IN_RECORD = {
13042
+ none: true,
13043
+ blank: true,
13044
+ "repo-cold": true,
13045
+ "repo-resident": true
13046
+ };
13047
+ IDENTITIES_IN_RECORD = { none: true, read: true, write: true };
13048
+ BOUNDARY_SCOPES_IN_RECORD = {
13049
+ defaults: true,
13050
+ channel: true,
13051
+ user: true,
13052
+ directive: true
13053
+ };
12507
13054
  RUN_LIST_DEFAULT_LIMIT = 50;
12508
13055
  RUN_LIST_MAX_LIMIT = 200;
12509
13056
  RUN_EVENTS_DEFAULT_PAGE = 1e3;
@@ -12534,7 +13081,7 @@ import { z as z7 } from "zod";
12534
13081
  function registerDeliveryCommands(registry) {
12535
13082
  for (const cmd of deliveryCommands) registry.register(cmd);
12536
13083
  }
12537
- var defineCommand4, REPO_SLUG2, ISO_DAY2, asJson, deliveryReport, deliveryCommands;
13084
+ var defineCommand4, REPO_SLUG2, ISO_DAY3, asJson, deliveryReport, deliveryCommands;
12538
13085
  var init_delivery2 = __esm({
12539
13086
  "../../src/core/commands/delivery.ts"() {
12540
13087
  "use strict";
@@ -12544,21 +13091,22 @@ var init_delivery2 = __esm({
12544
13091
  init_runRecord();
12545
13092
  defineCommand4 = commandDefiner();
12546
13093
  REPO_SLUG2 = /^(?!\.+\/)[\w.-]+\/(?!\.+$)[\w.-]+$/;
12547
- ISO_DAY2 = /^\d{4}-\d{2}-\d{2}$/;
13094
+ ISO_DAY3 = /^\d{4}-\d{2}-\d{2}$/;
12548
13095
  asJson = (v) => v;
12549
13096
  deliveryReport = defineCommand4({
12550
13097
  id: "delivery.report",
12551
13098
  options: z7.object({
12552
13099
  repo: z7.string().regex(REPO_SLUG2).optional().describe("the repository (`owner/name`); default: the first under `delivery.repos`"),
12553
- since: z7.string().regex(ISO_DAY2).optional().describe("the first day of the range (`YYYY-MM-DD`, UTC); the range ends today"),
13100
+ since: z7.string().regex(ISO_DAY3).optional().describe("the first day of the range (`YYYY-MM-DD`, UTC); the range ends today"),
12554
13101
  weeks: z7.coerce.number().int().min(1).max(MAX_WEEKS).optional().describe(
12555
13102
  `Monday-start weeks back from this one (default 4, at most ${MAX_WEEKS}); --since, when given, decides instead`
12556
- )
13103
+ ),
13104
+ fresh: flag.optional().describe("read GitHub now instead of the repository's snapshot, and refresh the snapshot")
12557
13105
  }),
12558
13106
  action: "delivery:read",
12559
13107
  effect: "read",
12560
13108
  enabledWhen: (caps) => caps.github,
12561
- describe: "Delivery indicators per week and per unit \u2014 issue-to-merge time, first-pass CI, review rounds, findings and the share resolved with no human edit \u2014 read from GitHub and the run history; nothing written.",
13109
+ describe: "Delivery indicators per week and per unit \u2014 issue-to-merge time, first-pass CI, review rounds, findings and the share resolved with no human edit \u2014 from the repository's snapshot of GitHub's facts (--fresh reads GitHub now) and the run history; nothing written.",
12562
13110
  render: (output) => renderDeliveryReport(output),
12563
13111
  handler: async ({ options, caller, deps }) => {
12564
13112
  const service = await deps.delivery.service();
@@ -12578,11 +13126,13 @@ var init_delivery2 = __esm({
12578
13126
  return runFactsOf(page2.runs, repo);
12579
13127
  };
12580
13128
  try {
12581
- return asJson(await service.report(repo, { since: options.since, weeks: options.weeks, runs }));
13129
+ return asJson(
13130
+ await service.report(repo, { since: options.since, weeks: options.weeks, fresh: options.fresh, runs })
13131
+ );
12582
13132
  } catch (err2) {
12583
- const message = err2 instanceof Error ? err2.message : String(err2);
12584
- if (message === DELIVERY_OFF_MESSAGE) throw new CommandError("unavailable", message);
12585
- throw new CommandError("unavailable", `delivery indicators unavailable: ${message}`);
13133
+ const message2 = err2 instanceof Error ? err2.message : String(err2);
13134
+ if (message2 === DELIVERY_OFF_MESSAGE) throw new CommandError("unavailable", message2);
13135
+ throw new CommandError("unavailable", `delivery indicators unavailable: ${message2}`);
12586
13136
  }
12587
13137
  }
12588
13138
  });
@@ -12712,7 +13262,7 @@ var init_deploy = __esm({
12712
13262
  init_operatorRoot();
12713
13263
  init_restart();
12714
13264
  init_run();
12715
- init_profile();
13265
+ init_profile2();
12716
13266
  init_wranglerTemplate();
12717
13267
  init_secrets();
12718
13268
  init_commandRegistry();
@@ -14307,7 +14857,7 @@ var init_help = __esm({
14307
14857
  init_commandRegistry();
14308
14858
  init_commandSurface();
14309
14859
  defineCommand8 = commandDefiner();
14310
- DIRECTIVES_HELP = "`agent:review model:anthropic/claude-opus-5 effort:low look at PR 42` (effort: low | medium | high \u2014 lower = faster turns)";
14860
+ DIRECTIVES_HELP = "`agent:review model:anthropic/claude-opus-5 effort:low budget:20 look at PR 42` (effort: low | medium | high \u2014 lower = faster turns; budget: whole minutes, at least 2 \u2014 narrows this run's wall clock, never widens it)";
14311
14861
  helpShow = defineCommand8({
14312
14862
  id: "help.show",
14313
14863
  action: "help:read",
@@ -14446,8 +14996,8 @@ var init_types2 = __esm({
14446
14996
  "../../src/mcp/types.ts"() {
14447
14997
  "use strict";
14448
14998
  McpError = class extends Error {
14449
- constructor(code, message) {
14450
- super(message);
14999
+ constructor(code, message2) {
15000
+ super(message2);
14451
15001
  this.code = code;
14452
15002
  this.name = "McpError";
14453
15003
  }
@@ -14630,12 +15180,12 @@ var init_client = __esm({
14630
15180
  const res = await this.post({ jsonrpc: "2.0", id, method, params }, signal, o);
14631
15181
  const session = res.headers.get("mcp-session-id");
14632
15182
  if (session && o?.initializing) this.sessionId = session;
14633
- const message = await this.readResponse(res, id, signal);
14634
- if (message.error) {
14635
- const code = typeof message.error.code === "number" ? message.error.code : "protocol";
14636
- throw new McpError(code, `${method}: ${String(message.error.message ?? "error")}`);
15183
+ const message2 = await this.readResponse(res, id, signal);
15184
+ if (message2.error) {
15185
+ const code = typeof message2.error.code === "number" ? message2.error.code : "protocol";
15186
+ throw new McpError(code, `${method}: ${String(message2.error.message ?? "error")}`);
14637
15187
  }
14638
- return message.result;
15188
+ return message2.result;
14639
15189
  }
14640
15190
  async post(body, signal, o) {
14641
15191
  const headers = {
@@ -15042,8 +15592,8 @@ var init_oauth = __esm({
15042
15592
  init_web();
15043
15593
  init_client();
15044
15594
  OAuthError = class extends Error {
15045
- constructor(message) {
15046
- super(message);
15595
+ constructor(message2) {
15596
+ super(message2);
15047
15597
  this.name = "OAuthError";
15048
15598
  }
15049
15599
  };
@@ -15186,9 +15736,9 @@ function bridgeMcpTools(server, client, tools, opts) {
15186
15736
  try {
15187
15737
  result = await client.callTool(t.name, input ?? {}, { signal: ctx.signal });
15188
15738
  } catch (err2) {
15189
- const message = err2 instanceof McpError ? err2.message : err2 instanceof Error ? err2.message : String(err2);
15739
+ const message2 = err2 instanceof McpError ? err2.message : err2 instanceof Error ? err2.message : String(err2);
15190
15740
  const failed = classifyError(
15191
- new Error(`MCP ${server.name}/${t.name} failed: ${message}`, { cause: err2 }),
15741
+ new Error(`MCP ${server.name}/${t.name} failed: ${message2}`, { cause: err2 }),
15192
15742
  mcpClass(err2)
15193
15743
  );
15194
15744
  span?.fail(failed);
@@ -15395,8 +15945,8 @@ var init_service = __esm({
15395
15945
  init_source();
15396
15946
  MCP_OFF_MESSAGE = "External MCP servers are not enabled in this deployment (no `mcp` block in config.yaml).";
15397
15947
  McpServiceError = class extends Error {
15398
- constructor(code, message) {
15399
- super(message);
15948
+ constructor(code, message2) {
15949
+ super(message2);
15400
15950
  this.code = code;
15401
15951
  this.name = "McpServiceError";
15402
15952
  }
@@ -16004,12 +16554,12 @@ var init_service = __esm({
16004
16554
  const tools = await this.opts.factory(spec).listTools();
16005
16555
  return { kind: "ok", toolCount: tools.length };
16006
16556
  } catch (err2) {
16007
- const message = err2 instanceof Error ? err2.message : String(err2);
16008
- if (/HTTP 40[13]\b/.test(message))
16009
- return { kind: "rejected", error: `the server rejected the token (${message})` };
16557
+ const message2 = err2 instanceof Error ? err2.message : String(err2);
16558
+ if (/HTTP 40[13]\b/.test(message2))
16559
+ return { kind: "rejected", error: `the server rejected the token (${message2})` };
16010
16560
  return {
16011
16561
  kind: "error",
16012
- error: `stored, but the server could not be reached to verify it: ${message.slice(0, 200)}`
16562
+ error: `stored, but the server could not be reached to verify it: ${message2.slice(0, 200)}`
16013
16563
  };
16014
16564
  }
16015
16565
  }
@@ -17899,8 +18449,8 @@ var init_githubApi = __esm({
17899
18449
  MAX_FILE_CHARS = 2e5;
17900
18450
  COMPARE_DIFF_MAX_CHARS = 12e5;
17901
18451
  GithubApiError = class extends Error {
17902
- constructor(status3, message) {
17903
- super(message);
18452
+ constructor(status3, message2) {
18453
+ super(message2);
17904
18454
  this.status = status3;
17905
18455
  this.name = "GithubApiError";
17906
18456
  }
@@ -18222,8 +18772,8 @@ var init_reviewAbridge = __esm({
18222
18772
  init_clock();
18223
18773
  isReadingDiff = (e) => e.type === "review_artifact" && e.artifact === "reading_diff";
18224
18774
  AbridgeRefusal = class extends Error {
18225
- constructor(code, message) {
18226
- super(message);
18775
+ constructor(code, message2) {
18776
+ super(message2);
18227
18777
  this.code = code;
18228
18778
  this.name = "AbridgeRefusal";
18229
18779
  }
@@ -19268,7 +19818,7 @@ var init_plan2 = __esm({
19268
19818
  init_config();
19269
19819
  init_capabilities();
19270
19820
  init_secrets2();
19271
- init_profile();
19821
+ init_profile2();
19272
19822
  ENV_PATH = ".env";
19273
19823
  CONFIG_PATH = "config/config.yaml";
19274
19824
  PROFILE_PATH2 = "deploy/profile.json";
@@ -19281,7 +19831,7 @@ var init_plan2 = __esm({
19281
19831
  ];
19282
19832
  OPENAI_PROVIDER = "openai";
19283
19833
  OPENAI_KEY_ENV = "OPENAI_API_KEY";
19284
- DEFAULT_MODEL_AGENTS = ["general", "coding", "review"];
19834
+ DEFAULT_MODEL_AGENTS = ["general", "coding", "review", "explore"];
19285
19835
  ENV_LINE = /^(#\s*)?([A-Z][A-Z0-9_]*)=(.*)$/;
19286
19836
  }
19287
19837
  });
@@ -19772,7 +20322,12 @@ function buildCoreCommands(config, store, wiring) {
19772
20322
  const delivery = once(async () => {
19773
20323
  if (wiring.delivery) return wiring.delivery();
19774
20324
  if (wiring.capabilities && !wiring.capabilities.github) return new NullDeliveryService();
19775
- return deliveryServiceFromConfig(parseDeliveryConfig((await cfg()).config.delivery));
20325
+ const config2 = (await cfg()).config;
20326
+ const warn = (m) => wiring.warn(`[delivery] ${m}`);
20327
+ return deliveryFromConfig(parseDeliveryConfig(config2.delivery), {
20328
+ snapshots: buildDeliverySnapshotStore(config2, wiring.secrets, warn),
20329
+ warn
20330
+ }).service;
19776
20331
  });
19777
20332
  const deps = {
19778
20333
  help: {
@@ -19837,13 +20392,19 @@ function buildCoreCommands(config, store, wiring) {
19837
20392
  };
19838
20393
  return bindCommands(registry, deps);
19839
20394
  }
19840
- function deliveryServiceFromConfig(cfg) {
19841
- return createDeliveryService(cfg, new GithubDeliverySource(), {
20395
+ function deliveryFromConfig(cfg, opts = {}) {
20396
+ const source2 = new SnapshottingDeliverySource(
20397
+ new GithubDeliverySource(),
20398
+ opts.snapshots ?? new InMemoryDeliverySnapshotStore(),
20399
+ { ...opts.warn ? { warn: opts.warn } : {} }
20400
+ );
20401
+ const service = createDeliveryService(cfg, source2, {
19842
20402
  identities: async () => {
19843
20403
  const identity2 = await resolveGithubIdentity();
19844
20404
  return identity2 ? { reviewers: [identity2.login] } : {};
19845
20405
  }
19846
20406
  });
20407
+ return { service, source: source2 };
19847
20408
  }
19848
20409
  var init_commandCatalogue = __esm({
19849
20410
  "../../src/core/commandCatalogue.ts"() {
@@ -19862,6 +20423,8 @@ var init_commandCatalogue = __esm({
19862
20423
  init_githubDelivery();
19863
20424
  init_resident();
19864
20425
  init_delivery();
20426
+ init_deliverySnapshot();
20427
+ init_deliverySnapshotStore();
19865
20428
  init_commandRegistry();
19866
20429
  init_secrets2();
19867
20430
  init_all();
@@ -22320,6 +22883,21 @@ ${raw.trim()}`;
22320
22883
  * and URL reading, so a plain mention can answer from the repos and act on
22321
22884
  * issues without being re-sent to another agent. */
22322
22885
  assistant: [webFetchTool, updateStatusTool, ...GITHUB_READ_TOOLS, ...GITHUB_ISSUE_WRITE_TOOLS],
22886
+ /** The explore agent (docs/reference/specs/agent-explore.md): a shell and
22887
+ * file reads in a cold sandbox, the web with search, the skills and the
22888
+ * GitHub reads — and nothing that writes: no `write_file`, no `submit_*`,
22889
+ * no issue writes. Read-only is a toolset-and-prompt contract; the wall is
22890
+ * the read-scoped credential its machine holds. */
22891
+ explore: [
22892
+ bashTool,
22893
+ readFileTool,
22894
+ updateStatusTool,
22895
+ webFetchTool,
22896
+ webSearchTool,
22897
+ listSkillsTool,
22898
+ useSkillTool,
22899
+ ...GITHUB_READ_TOOLS
22900
+ ],
22323
22901
  none: []
22324
22902
  };
22325
22903
  }
@@ -22524,9 +23102,9 @@ async function runLoop(opts, now, note, emit, agentSpan) {
22524
23102
  );
22525
23103
  throw err2;
22526
23104
  }
22527
- const message = err2 instanceof Error ? err2.message : String(err2);
23105
+ const message2 = err2 instanceof Error ? err2.message : String(err2);
22528
23106
  if (err2 instanceof ExecCapacityError) {
22529
- const text2 = `\u23F3 Sandbox fleet busy \u2014 ${message}. Retry the command in a minute or finish with what you have.`;
23107
+ const text2 = `\u23F3 Sandbox fleet busy \u2014 ${message2}. Retry the command in a minute or finish with what you have.`;
22530
23108
  emit({ type: "tool_result", tool: tu.name, ok: false, callId: tu.id, ...prepareToolResult(text2), ...spanId });
22531
23109
  note("fleet_busy", text2);
22532
23110
  settle2(false, { infra: true });
@@ -22538,7 +23116,7 @@ async function runLoop(opts, now, note, emit, agentSpan) {
22538
23116
  tool: tu.name,
22539
23117
  ok: false,
22540
23118
  callId: tu.id,
22541
- ...prepareToolResult(message),
23119
+ ...prepareToolResult(message2),
22542
23120
  ...infra ? { infra: true } : {},
22543
23121
  ...spanId
22544
23122
  });
@@ -22547,7 +23125,7 @@ async function runLoop(opts, now, note, emit, agentSpan) {
22547
23125
  return {
22548
23126
  type: "tool_result",
22549
23127
  toolUseId: tu.id,
22550
- content: capToolResultContent(`Error: ${message}`),
23128
+ content: capToolResultContent(`Error: ${message2}`),
22551
23129
  isError: true
22552
23130
  };
22553
23131
  }
@@ -22916,8 +23494,8 @@ function rereviewFollowUp(input) {
22916
23494
  `Re-review at ${c}: focus on what changed since ${r} (\`git diff ${r} HEAD\` if ${r} is still present, otherwise diff against the base), re-check any finding above that the new commits address, and report the complete updated review \u2014 same format, verdict line first. Then call \`submit_verdict\` again with \`head\` = \`${input.current}\`; the earlier verdict is void.`
22917
23495
  ].join("\n");
22918
23496
  }
22919
- function subject(message) {
22920
- return message.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
23497
+ function subject(message2) {
23498
+ return message2.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
22921
23499
  }
22922
23500
  function short3(sha) {
22923
23501
  return sha.trim().toLowerCase().slice(0, 7);
@@ -23043,16 +23621,17 @@ var init_digestCoverage = __esm({
23043
23621
  });
23044
23622
 
23045
23623
  // ../../src/core/reviewRound.ts
23046
- function releaseModeFor(agent, opts) {
23047
- return agent.toolset === "readonly" || opts.hardStopped ? "always" : "if-clean";
23624
+ function releaseModeFor(identity2, opts) {
23625
+ return identity2 === "read" || opts.hardStopped ? "always" : "if-clean";
23048
23626
  }
23049
23627
  async function attachRoundWorkspace(input) {
23050
- const { agent } = input.round;
23628
+ const { agent, profile } = input.round;
23051
23629
  const selection = await makeExecutor(
23052
23630
  input.factory,
23053
23631
  {
23054
23632
  threadKey: input.round.threadKey,
23055
23633
  agent,
23634
+ profile,
23056
23635
  repo: input.round.repo,
23057
23636
  ref: input.round.ref,
23058
23637
  headSha: input.round.headSha
@@ -23062,7 +23641,7 @@ async function attachRoundWorkspace(input) {
23062
23641
  const release = async (opts) => {
23063
23642
  const { executor } = selection;
23064
23643
  if (!executor.release) return;
23065
- const mode = releaseModeFor(agent, opts);
23644
+ const mode = releaseModeFor(profile.identity, opts);
23066
23645
  try {
23067
23646
  const r = await executor.release(mode, opts.span ? { span: opts.span } : void 0);
23068
23647
  console.log(`[release] ${input.logKey} ${r.released ? "released" : "kept"}${r.reason ? ` (${r.reason})` : ""}`);
@@ -23776,6 +24355,9 @@ function reclaimedRunRecord(input) {
23776
24355
  },
23777
24356
  channelVisibility: row.meta.channelVisibility ?? "unknown",
23778
24357
  repo: row.meta.repo,
24358
+ // The row carries the profile the run was admitted with (a clipped budget
24359
+ // included); a row from before profiles existed leaves the record without one.
24360
+ ...row.meta.profile && row.meta.agent ? { profile: { preset: row.meta.agent, ...row.meta.profile } } : {},
23779
24361
  finishedAt,
23780
24362
  status: status3,
23781
24363
  diagnosis: analyzeRunFriction(events, {
@@ -23834,12 +24416,16 @@ function assembleRunRecord(input) {
23834
24416
  ...activityOfEvents(events) !== void 0 ? { activity: activityOfEvents(events) } : {},
23835
24417
  ...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
23836
24418
  ...msg.userName !== void 0 ? { userName: msg.userName } : {},
23837
- ...input.handoff !== void 0 ? { handoff: redactHandoff(input.handoff) } : {}
24419
+ ...input.handoff !== void 0 ? { handoff: redactHandoff(input.handoff) } : {},
24420
+ ...input.profile !== void 0 ? { profile: input.profile } : {}
23838
24421
  });
23839
24422
  return fitted.eventCount !== fitted.storedEventCount ? { ...fitted, truncated: true } : fitted;
23840
24423
  }
24424
+ function profileRecordOf(agent, profile) {
24425
+ return { preset: agent.name, ...profile };
24426
+ }
23841
24427
  function writeTombstone(deps, ctx) {
23842
- const { msg, agent, resolved, repoCtx, channelVisibility, run: run2, registry, resume } = ctx;
24428
+ const { msg, agent, profile, resolved, repoCtx, channelVisibility, run: run2, registry, resume } = ctx;
23843
24429
  if (!resume) {
23844
24430
  const startSnap = registry.snapshot(run2.id, run2.token);
23845
24431
  if (startSnap) {
@@ -23852,6 +24438,7 @@ function writeTombstone(deps, ctx) {
23852
24438
  msg,
23853
24439
  channelVisibility,
23854
24440
  repo: repoCtx.repo,
24441
+ profile: profileRecordOf(agent, profile),
23855
24442
  finishedAt: startSnap.startedAt,
23856
24443
  status: "interrupted",
23857
24444
  diagnosis: analyzeRunFriction(startSnap.events, {
@@ -23870,6 +24457,7 @@ function registerFinishRecord(deps, ctx) {
23870
24457
  run: run2,
23871
24458
  snap,
23872
24459
  agent,
24460
+ profile,
23873
24461
  resolved,
23874
24462
  msg,
23875
24463
  channelVisibility,
@@ -23893,6 +24481,7 @@ function registerFinishRecord(deps, ctx) {
23893
24481
  msg,
23894
24482
  channelVisibility,
23895
24483
  repo: repoCtx.repo,
24484
+ profile: profileRecordOf(agent, profile),
23896
24485
  finishedAt,
23897
24486
  status: failedAfterFinish && status3 === "completed" ? "failed" : status3,
23898
24487
  diagnosis,
@@ -24244,10 +24833,10 @@ ${chatCatalogueText(inGroup)}` };
24244
24833
  const id = `${group}.${verb}`;
24245
24834
  const cmd = exposed.find((c) => c.id === id);
24246
24835
  if (!cmd) return null;
24247
- const rejected = (message) => ({
24836
+ const rejected = (message2) => ({
24248
24837
  kind: "reply",
24249
24838
  error: "invalid_input",
24250
- text: `\u26A0\uFE0F \`${chatForm(id)}\`: ${message}`
24839
+ text: `\u26A0\uFE0F \`${chatForm(id)}\`: ${message2}`
24251
24840
  });
24252
24841
  const tokens = tokenize(text);
24253
24842
  if (!tokens.ok) return rejected(tokens.error);
@@ -24269,15 +24858,15 @@ function chatCallerFor(msg, config, resolveRepo) {
24269
24858
  origin: { channelId: msg.channelId, threadKey: msg.threadKey, ...resolveRepo ? { repo: resolveRepo } : {} }
24270
24859
  };
24271
24860
  }
24272
- function chatErrorLine(id, error, message, config, decidedBy = "registry") {
24861
+ function chatErrorLine(id, error, message2, config, decidedBy = "registry") {
24273
24862
  const name = chatForm(id);
24274
24863
  switch (error) {
24275
24864
  case "unauthorized":
24276
- return decidedBy === "handler" ? `\u{1F6AB} \`${name}\`: ${message} Ask ${config.adminsHint()}.` : `\u{1F6AB} \`${name}\` is restricted. Ask ${config.adminsHint()}.`;
24865
+ return decidedBy === "handler" ? `\u{1F6AB} \`${name}\`: ${message2} Ask ${config.adminsHint()}.` : `\u{1F6AB} \`${name}\` is restricted. Ask ${config.adminsHint()}.`;
24277
24866
  case "internal":
24278
- return `\u26A0\uFE0F \`${name}\` failed: ${message}`;
24867
+ return `\u26A0\uFE0F \`${name}\` failed: ${message2}`;
24279
24868
  default:
24280
- return `\u26A0\uFE0F \`${name}\`: ${message}`;
24869
+ return `\u26A0\uFE0F \`${name}\`: ${message2}`;
24281
24870
  }
24282
24871
  }
24283
24872
  async function invokeChatCommand({
@@ -25082,6 +25671,11 @@ var init_fastPath = __esm({
25082
25671
  });
25083
25672
 
25084
25673
  // ../../src/directives.ts
25674
+ function parseBudgetMinutes(value) {
25675
+ if (!/^\d+$/.test(value)) return void 0;
25676
+ const minutes = Number(value);
25677
+ return minutes >= MIN_BOUNDARY_MINUTES ? minutes : void 0;
25678
+ }
25085
25679
  function lastThreadDirectives(history) {
25086
25680
  const out = {};
25087
25681
  for (const h of history) {
@@ -25114,6 +25708,14 @@ function parseDirectives(input) {
25114
25708
  throw new Error(`Unknown effort "${f.value}". Valid: ${EFFORT_LEVELS_HINT}`);
25115
25709
  }
25116
25710
  out.effort = f.value;
25711
+ } else if (f.key === "budget") {
25712
+ const minutes = parseBudgetMinutes(f.value);
25713
+ if (minutes === void 0) {
25714
+ throw new Error(
25715
+ `Invalid budget "${f.value}": budget:<minutes> takes a whole number of minutes, at least ${MIN_BOUNDARY_MINUTES} (e.g. budget:30). It narrows this run's wall clock and never widens it.`
25716
+ );
25717
+ }
25718
+ out.budget = minutes;
25117
25719
  }
25118
25720
  text = text.replace(f.match, " ");
25119
25721
  }
@@ -25125,8 +25727,9 @@ var init_directives = __esm({
25125
25727
  "../../src/directives.ts"() {
25126
25728
  "use strict";
25127
25729
  init_registry();
25730
+ init_validate();
25128
25731
  init_effort();
25129
- DIRECTIVE_RE = /(?:^|\s)(agent|model|effort)[:=](\S+)/g;
25732
+ DIRECTIVE_RE = /(?:^|\s)(agent|model|effort|budget)[:=](\S+)/g;
25130
25733
  }
25131
25734
  });
25132
25735
 
@@ -25138,7 +25741,7 @@ function githubRepoProbe(opts) {
25138
25741
  if (!SLUG.test(slug)) return false;
25139
25742
  let bearer;
25140
25743
  try {
25141
- bearer = await token(opts.scope);
25744
+ bearer = opts.scope === void 0 ? null : await token(opts.scope);
25142
25745
  } catch {
25143
25746
  return "unreachable";
25144
25747
  }
@@ -25189,29 +25792,44 @@ function resolveRun(deps, ctx) {
25189
25792
  });
25190
25793
  return { sticky, resolved };
25191
25794
  }
25795
+ function resolveProfile(ctx) {
25796
+ const carried = ctx.resume?.row.meta.profile;
25797
+ const declared = carried ? { machine: carried.machine, identity: carried.identity, maxMinutes: carried.minutes } : ctx.agent;
25798
+ const resolution = effectiveProfile(
25799
+ declared,
25800
+ ctx.budget !== void 0 ? { budget: ctx.budget } : {},
25801
+ ctx.resolved.boundary
25802
+ );
25803
+ if (resolution.kind === "profile" && carried?.boundedBy && !resolution.profile.boundedBy) {
25804
+ return { kind: "profile", profile: { ...resolution.profile, boundedBy: carried.boundedBy } };
25805
+ }
25806
+ return resolution;
25807
+ }
25192
25808
  function resolveTarget(deps, ctx) {
25193
- const { msg, history, agent, resolved, resume, root } = ctx;
25809
+ const { msg, history, profile, resolved, resume, root } = ctx;
25194
25810
  const { provider: providerName, model } = parseModelRef(resolved.modelRef);
25195
25811
  const provider = deps.providers.get(providerName);
25196
- const needsRepo = machineNeedsRepo(agent.machine);
25812
+ const needsRepo = machineNeedsRepo(profile.machine);
25197
25813
  const repoCtxP = root.span(
25198
25814
  "dispatch.repo_context",
25199
25815
  () => resume ? Promise.resolve(resume.repoCtx) : needsRepo ? Promise.resolve(
25200
- deps.resolveRepoContext ? deps.resolveRepoContext(msg, history) : resolveRepoContext(msg, history, ...repoVetFor(agent, deps.config.config.execution?.resident))
25816
+ deps.resolveRepoContext ? deps.resolveRepoContext(msg, history) : resolveRepoContext(msg, history, ...repoVetFor(profile, deps.config.config.execution?.resident))
25201
25817
  ).then((ctx2) => ctx2 ?? {}) : Promise.resolve({})
25202
25818
  );
25203
25819
  repoCtxP.catch(() => {
25204
25820
  });
25205
25821
  return { provider, model, needsRepo, repoCtxP };
25206
25822
  }
25207
- function repoVetFor(agent, resident) {
25208
- if (agent.machine === "repo-cold") return [githubRepoProbe({ scope: githubTokenScopeFor(agent) }), void 0];
25823
+ function repoVetFor(profile, resident) {
25824
+ if (profile.machine === "repo-cold")
25825
+ return [githubRepoProbe({ scope: githubTokenScopeFor(profile.identity) }), void 0];
25209
25826
  return [residentOnboardedProbe(resident), residentSlugsLister(resident)];
25210
25827
  }
25211
25828
  var init_resolve = __esm({
25212
25829
  "../../src/core/dispatch/resolve.ts"() {
25213
25830
  "use strict";
25214
25831
  init_registry();
25832
+ init_profile();
25215
25833
  init_directives();
25216
25834
  init_types3();
25217
25835
  init_factory();
@@ -25234,9 +25852,63 @@ async function authorizeAgent(deps, ctx) {
25234
25852
  }
25235
25853
  return { kind: "allowed" };
25236
25854
  }
25855
+ async function authorizeProfile(deps, ctx) {
25856
+ const { msg, io, refuse: refuse3, agent, resolution } = ctx;
25857
+ if (resolution.kind === "profile") return { kind: "allowed", profile: resolution.profile };
25858
+ const { refusal } = resolution;
25859
+ console.log(`[dispatch] ${msg.threadKey} not started: profile bounded (${agent.name}: ${refusalSummary(refusal)})`);
25860
+ await refuse3("profile_bounded", () => io.reply(profileRefusalReply(agent.name, refusal, deps.config.adminsHint())));
25861
+ return { kind: "refused", reason: "profile_bounded" };
25862
+ }
25863
+ function refusalSummary(refusal) {
25864
+ return refusal.axis === "identity" ? `identity ${refusal.needs} > ${refusal.cap} (${refusal.scope})` : `machine ${refusal.needs} not in ${refusal.allowed.join(",")} (${refusal.scopes.join(",")})`;
25865
+ }
25866
+ function boundaryOf(scope) {
25867
+ switch (scope) {
25868
+ case "channel":
25869
+ return "this channel's boundary";
25870
+ case "user":
25871
+ return "your own boundary";
25872
+ case "defaults":
25873
+ return "the installation's default boundary";
25874
+ case "directive":
25875
+ return "this message's own budget";
25876
+ }
25877
+ }
25878
+ function wayForward(scope, axis, needs, adminsHint) {
25879
+ switch (scope) {
25880
+ case "channel":
25881
+ return `run it in a channel that allows \`${needs}\`, or ask ${adminsHint} to raise this channel's boundary`;
25882
+ case "user":
25883
+ return `raise your own boundary with \`config set me --boundary.${axis} ${needs}\`, or drop your overrides with \`config clear me\``;
25884
+ case "defaults":
25885
+ return `ask ${adminsHint} to raise \`defaults.boundary\` in the configuration`;
25886
+ case "directive":
25887
+ return "send the message again without the budget directive";
25888
+ }
25889
+ }
25890
+ function profileRefusalReply(agentName, refusal, adminsHint) {
25891
+ const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
25892
+ if (refusal.axis === "identity") {
25893
+ const how2 = wayForward(refusal.scope, "maxIdentity", refusal.needs, adminsHint);
25894
+ return `\u{1F6AB} \`${agentName}\` needs a \`${refusal.needs}\` credential; ${boundaryOf(refusal.scope)} caps runs at \`${refusal.cap}\`. ${capitalize(how2)}.`;
25895
+ }
25896
+ const scopes = refusal.scopes.map(boundaryOf);
25897
+ const who2 = scopes.length > 1 ? `${scopes.join(" and ")} allow` : `${scopes[0] ?? "the boundary"} allows`;
25898
+ const allowed = refusal.allowed.map((m) => `\`${m}\``).join(", ");
25899
+ const how = refusal.scopes.map(
25900
+ (scope) => wayForward(
25901
+ scope,
25902
+ "machines",
25903
+ scope === "user" ? `<classes including ${refusal.needs}>` : refusal.needs,
25904
+ adminsHint
25905
+ )
25906
+ ).join("; ");
25907
+ return `\u{1F6AB} \`${agentName}\` runs on a \`${refusal.needs}\` machine; ${who2} only ${allowed}. ${capitalize(how)}.`;
25908
+ }
25237
25909
  async function authorizeRepo(deps, ctx) {
25238
- const { msg, io, refuse: refuse3, card, shell, closeLines, clock: clock2, agent, needsRepo, repoCtx } = ctx;
25239
- if (agent.machine === "repo-cold" && needsRepo && !repoCtx.repo) {
25910
+ const { msg, io, refuse: refuse3, card, shell, closeLines, clock: clock2, agent, profile, needsRepo, repoCtx } = ctx;
25911
+ if (profile.machine === "repo-cold" && needsRepo && !repoCtx.repo) {
25240
25912
  if (repoCtx.rejectedRepo) {
25241
25913
  const slug = repoCtx.rejectedRepo;
25242
25914
  console.log(`[dispatch] ${msg.threadKey} not started: repo not visible (${slug}: GitHub answered 404)`);
@@ -25647,10 +26319,24 @@ function configAwarenessBlock(i) {
25647
26319
  );
25648
26320
  }
25649
26321
  }
26322
+ if (i.boundary) {
26323
+ lines.push(
26324
+ `Boundary in force: ${fmtEffectiveBoundary(i.boundary)} \u2014 a boundary caps what any run in its scope may have and never grants; a preset above a cap is refused, a budget above it clipped. Users set their own with \`config set me --boundary.maxMinutes <n> --boundary.maxIdentity <none|read|write> --boundary.machines <a,b>\` (it can only tighten the channel's and the defaults'); \`config set channel --boundary.\u2026\` caps a channel.`
26325
+ );
26326
+ }
26327
+ if (i.budget?.boundedBy !== void 0) {
26328
+ lines.push(
26329
+ `Budget: ${i.budget.minutes} min (clipped by the ${clipSourceLabel(i.budget.boundedBy)}; the preset asks ${i.budget.presetMinutes}).`
26330
+ );
26331
+ }
26332
+ if (i.budget?.directive !== void 0 && i.budget.boundedBy !== "directive") {
26333
+ const stands = i.budget.boundedBy === void 0 ? `the preset's ${i.budget.presetMinutes} min` : `${i.budget.minutes} min`;
26334
+ lines.push(`This message's \`budget:${i.budget.directive}\` narrowed nothing: the run's budget is ${stands}.`);
26335
+ }
25650
26336
  const channelGate = i.canEditChannelConfig ? "per-channel" : "per-channel; restricted for this user \u2014 ask an admin";
25651
26337
  const levels = `<${EFFORT_LEVELS.join("|")}>`;
25652
26338
  lines.push(
25653
- `Users inspect and tune settings: \`config show\`, \`config set me --agent <name> --model <provider>/<model> --effort ${levels}\` (per-user; \`--models.<agent>\` / \`--efforts.<agent>\` per agent), \`config set channel \u2026\` (${channelGate}), \`config instructions me "<free text>"\` (custom instructions; \`config instructions channel \u2026\` channel-wide), \`config clear me|channel\`, and per-message \`agent:<name>\` / \`model:<provider>/<model>\` / \`effort:<level>\` directives.`,
26339
+ `Users inspect and tune settings: \`config show\`, \`config set me --agent <name> --model <provider>/<model> --effort ${levels}\` (per-user; \`--models.<agent>\` / \`--efforts.<agent>\` per agent), \`config set channel \u2026\` (${channelGate}), \`config instructions me|channel "<free text>"\` (custom instructions), \`config clear me|channel\`, and per-message \`agent:<name>\` / \`model:<provider>/<model>\` / \`effort:<level>\` / \`budget:<minutes>\` directives.`,
25654
26340
  "When asked about your settings or tuning, answer from this block \u2014 you are not stateless or untunable."
25655
26341
  );
25656
26342
  return lines.join("\n");
@@ -25675,12 +26361,15 @@ function fmtDirective(d) {
25675
26361
  if (d.agent) parts.push(`agent:${d.agent}`);
25676
26362
  if (d.model) parts.push(`model:${d.model}`);
25677
26363
  if (d.effort) parts.push(`effort:${d.effort}`);
26364
+ if (d.budget !== void 0) parts.push(`budget:${d.budget}`);
25678
26365
  return parts.length > 0 ? parts.join(" ") : void 0;
25679
26366
  }
25680
26367
  var CONFIG_AWARENESS_HEADER;
25681
26368
  var init_configAwareness = __esm({
25682
26369
  "../../src/core/configAwareness.ts"() {
25683
26370
  "use strict";
26371
+ init_config();
26372
+ init_profile();
25684
26373
  init_effort();
25685
26374
  CONFIG_AWARENESS_HEADER = "Switchboard runtime config for this run:";
25686
26375
  }
@@ -25891,6 +26580,15 @@ async function openAckCard(deps, ctx) {
25891
26580
  const heartbeat = setInterval(() => card.update(shell.live()), 5e3);
25892
26581
  return { shell, card, heartbeat };
25893
26582
  }
26583
+ function budgetClipLabel(agent, profile, budgetDirective) {
26584
+ const idle = budgetDirective !== void 0 && profile.boundedBy !== "directive";
26585
+ if (profile.boundedBy === void 0) {
26586
+ return idle ? `budget:${budgetDirective} narrowed nothing (preset asks ${agent.maxMinutes})` : void 0;
26587
+ }
26588
+ const facts = [`${clipSourceLabel(profile.boundedBy)}; preset asks ${agent.maxMinutes}`];
26589
+ if (idle) facts.push(`budget:${budgetDirective} narrowed nothing`);
26590
+ return `budget ${profile.minutes} min (${facts.join("; ")})`;
26591
+ }
25894
26592
  async function registerRun(deps, ctx) {
25895
26593
  const {
25896
26594
  msg,
@@ -25993,6 +26691,7 @@ async function reserveRun(deps, ctx) {
25993
26691
  const {
25994
26692
  msg,
25995
26693
  agent,
26694
+ profile,
25996
26695
  resolved,
25997
26696
  repoCtx,
25998
26697
  channelVisibility,
@@ -26028,7 +26727,8 @@ async function reserveRun(deps, ctx) {
26028
26727
  ...repoCtx.ref !== void 0 ? { ref: repoCtx.ref } : {},
26029
26728
  ...repoCtx.headSha !== void 0 ? { headSha: repoCtx.headSha } : {},
26030
26729
  ...repoCtx.pr !== void 0 ? { pr: repoCtx.pr } : {},
26031
- readonly: agent.toolset === "readonly",
26730
+ readonly: profile.identity === "read",
26731
+ profile,
26032
26732
  request: requestRow
26033
26733
  },
26034
26734
  card: card.handle ?? null,
@@ -26041,7 +26741,7 @@ async function reserveRun(deps, ctx) {
26041
26741
  return void 0;
26042
26742
  }
26043
26743
  async function attachWorkspace(deps, ctx) {
26044
- const { msg, io, refuse: refuse3, card, shell, closeLines, clock: clock2, agent, repoCtx, root } = ctx;
26744
+ const { msg, io, refuse: refuse3, card, shell, closeLines, clock: clock2, agent, profile, repoCtx, root } = ctx;
26045
26745
  let round;
26046
26746
  try {
26047
26747
  round = await root.span("dispatch.workspace.attach", async (span) => {
@@ -26060,7 +26760,14 @@ async function attachWorkspace(deps, ctx) {
26060
26760
  workspaceDir: deps.config.config.workspaceDir ?? "./workspaces",
26061
26761
  dataDir: deps.dataDir ?? "./data"
26062
26762
  },
26063
- round: { threadKey: msg.threadKey, agent, repo: repoCtx.repo, ref: repoCtx.ref, headSha: repoCtx.headSha },
26763
+ round: {
26764
+ threadKey: msg.threadKey,
26765
+ agent,
26766
+ profile,
26767
+ repo: repoCtx.repo,
26768
+ ref: repoCtx.ref,
26769
+ headSha: repoCtx.headSha
26770
+ },
26064
26771
  logKey: msg.threadKey,
26065
26772
  span
26066
26773
  });
@@ -26094,6 +26801,7 @@ async function composePrompt(deps, ctx) {
26094
26801
  const {
26095
26802
  msg,
26096
26803
  agent,
26804
+ profile,
26097
26805
  resolved,
26098
26806
  directives,
26099
26807
  sticky,
@@ -26119,9 +26827,23 @@ async function composePrompt(deps, ctx) {
26119
26827
  effort: resolved.effort,
26120
26828
  channel: scopes.channel,
26121
26829
  user: scopes.user,
26122
- messageDirective: { agent: directives.agent, model: directives.model, effort: directives.effort },
26830
+ messageDirective: {
26831
+ agent: directives.agent,
26832
+ model: directives.model,
26833
+ effort: directives.effort,
26834
+ budget: directives.budget
26835
+ },
26123
26836
  threadDirective: { agent: sticky.agent, model: sticky.model, effort: sticky.effort },
26124
26837
  canEditChannelConfig: deps.config.canEditChannelConfig(msg.userId),
26838
+ // The boundary in force and the budget this run actually has — the same
26839
+ // values the gate judged, so "how long do you have?" is answered from fact.
26840
+ ...resolved.boundary ? { boundary: resolved.boundary } : {},
26841
+ budget: {
26842
+ minutes: profile.minutes,
26843
+ presetMinutes: agent.maxMinutes,
26844
+ ...profile.boundedBy !== void 0 ? { boundedBy: profile.boundedBy } : {},
26845
+ ...directives.budget !== void 0 ? { directive: directives.budget } : {}
26846
+ },
26125
26847
  mcp: {
26126
26848
  registryOn: deps.capabilities.mcp,
26127
26849
  served: mcpForRun.servers.filter((s) => s.toolCount !== void 0).map((s) => s.server),
@@ -26167,6 +26889,7 @@ var init_provision = __esm({
26167
26889
  "../../src/core/dispatch/provision.ts"() {
26168
26890
  "use strict";
26169
26891
  init_registry();
26892
+ init_profile();
26170
26893
  init_residentTrace();
26171
26894
  init_resident();
26172
26895
  init_memory();
@@ -26198,6 +26921,7 @@ async function claimRun(deps, ctx) {
26198
26921
  channelVisibility,
26199
26922
  run: run2,
26200
26923
  registry,
26924
+ profile,
26201
26925
  selection,
26202
26926
  requestRow,
26203
26927
  reserved,
@@ -26233,7 +26957,8 @@ async function claimRun(deps, ctx) {
26233
26957
  ...repoCtx.ref !== void 0 ? { ref: repoCtx.ref } : {},
26234
26958
  ...repoCtx.headSha !== void 0 ? { headSha: repoCtx.headSha } : {},
26235
26959
  ...repoCtx.pr !== void 0 ? { pr: repoCtx.pr } : {},
26236
- readonly: agent.toolset === "readonly",
26960
+ readonly: profile.identity === "read",
26961
+ profile,
26237
26962
  selection: resident === true ? "resident" : "sandbox",
26238
26963
  ...binding?.workspace !== void 0 ? { workspace: binding.workspace } : {},
26239
26964
  ...requestRow !== void 0 ? { request: requestRow } : {}
@@ -26246,7 +26971,9 @@ async function claimRun(deps, ctx) {
26246
26971
  tools: mergeTools(TOOLSETS[agent.toolset] ?? [], mcpForRun?.tools).map(
26247
26972
  ({ name, description, inputSchema }) => ({ name, description, inputSchema })
26248
26973
  ),
26249
- seed: { messages, budgetMs: agent.maxMinutes * 6e4 },
26974
+ // The seed carries the EFFECTIVE budget, so a resume runs on what
26975
+ // this run was admitted with, not on the preset's own number.
26976
+ seed: { messages, budgetMs: profile.minutes * 6e4 },
26250
26977
  // A stop asked of another container (`/runs/stop` there) reaches this
26251
26978
  // run through its heartbeat and is honored like a local one; a fence
26252
26979
  // (another generation took the run) is a hard stop — nothing more may
@@ -26738,7 +27465,7 @@ async function runLoop2(deps, ctx) {
26738
27465
  const {
26739
27466
  msg,
26740
27467
  io,
26741
- agent,
27468
+ profile,
26742
27469
  resolved,
26743
27470
  provider,
26744
27471
  model,
@@ -26765,6 +27492,7 @@ async function runLoop2(deps, ctx) {
26765
27492
  publishText,
26766
27493
  ending
26767
27494
  } = ctx;
27495
+ const agent = budgetedAgent(ctx.agent, profile);
26768
27496
  const { executor, binding } = round.selection;
26769
27497
  let reviewHead = ctx.reviewHead;
26770
27498
  let lastActivityAt = ctx.loopStartedAt;
@@ -27092,6 +27820,7 @@ async function runLoop2(deps, ctx) {
27092
27820
  run: run2,
27093
27821
  snap,
27094
27822
  agent,
27823
+ profile,
27095
27824
  resolved,
27096
27825
  msg,
27097
27826
  channelVisibility,
@@ -27128,6 +27857,7 @@ async function runLoop2(deps, ctx) {
27128
27857
  var init_runLoop = __esm({
27129
27858
  "../../src/core/dispatch/runLoop.ts"() {
27130
27859
  "use strict";
27860
+ init_profile();
27131
27861
  init_runner();
27132
27862
  init_githubPulls();
27133
27863
  init_repoContext();
@@ -27183,6 +27913,7 @@ async function runShipCodingChild(input, ctx, opts, roundSpan) {
27183
27913
  round: {
27184
27914
  threadKey: input.threadKey,
27185
27915
  agent: spec.agent,
27916
+ profile: declaredProfile(spec.agent),
27186
27917
  repo: entry.repo,
27187
27918
  ref: entry.branch,
27188
27919
  headSha: opts.attachHeadSha
@@ -27371,6 +28102,7 @@ var init_codingChild = __esm({
27371
28102
  "../../src/core/ship/codingChild.ts"() {
27372
28103
  "use strict";
27373
28104
  init_runner();
28105
+ init_profile();
27374
28106
  init_descriptionTurn();
27375
28107
  init_reviewVerdict();
27376
28108
  init_codingPrPostStep();
@@ -27412,7 +28144,14 @@ async function runShipReviewChild(input, ctx, round, roundSpan) {
27412
28144
  if (!pf.ok) return { refusal: pf.reply };
27413
28145
  const ws = await attachRoundWorkspace({
27414
28146
  factory: input.factory,
27415
- round: { threadKey: input.threadKey, agent: spec.agent, repo: entry.repo, ref: entry.branch, headSha: pinned },
28147
+ round: {
28148
+ threadKey: input.threadKey,
28149
+ agent: spec.agent,
28150
+ profile: declaredProfile(spec.agent),
28151
+ repo: entry.repo,
28152
+ ref: entry.branch,
28153
+ headSha: pinned
28154
+ },
27416
28155
  logKey
27417
28156
  });
27418
28157
  const { executor, resident, binding, note } = ws.selection;
@@ -27546,6 +28285,7 @@ var init_reviewChild = __esm({
27546
28285
  "../../src/core/ship/reviewChild.ts"() {
27547
28286
  "use strict";
27548
28287
  init_runner();
28288
+ init_profile();
27549
28289
  init_reviewVerdict();
27550
28290
  init_reviewedHead();
27551
28291
  init_reviewRound();
@@ -27560,6 +28300,9 @@ function resolveShipCaps(cfg) {
27560
28300
  maxMinutes: cfg?.maxMinutes ?? SHIP_DEFAULT_MAX_MINUTES
27561
28301
  };
27562
28302
  }
28303
+ function shipPresetFor(cfg) {
28304
+ return { ...AGENTS.ship, maxMinutes: resolveShipCaps(cfg).maxMinutes };
28305
+ }
27563
28306
  function shipInterruptedNote(prUrl) {
27564
28307
  const stands = prUrl ? `Its work stands on GitHub: ${prUrl}.` : "Whatever it pushed stands on its pipeline branch; no PR was opened yet.";
27565
28308
  const reissue = prUrl ? `To continue the review loop, re-issue \`agent:ship\` in this thread with only the PR URL (${prUrl}).` : "To continue, re-issue `agent:ship` in this thread with the task \u2014 round 0 runs again on the same branch.";
@@ -27881,12 +28624,13 @@ var SHIP_DEFAULT_MAX_ROUNDS, SHIP_DEFAULT_MAX_MINUTES, SHIP_ROUND_RESERVE_MS;
27881
28624
  var init_shipPipeline = __esm({
27882
28625
  "../../src/core/shipPipeline.ts"() {
27883
28626
  "use strict";
28627
+ init_registry();
27884
28628
  init_reviewVerdict();
27885
28629
  init_reviewedHead();
27886
28630
  init_codingChild();
27887
28631
  init_reviewChild();
27888
28632
  SHIP_DEFAULT_MAX_ROUNDS = 3;
27889
- SHIP_DEFAULT_MAX_MINUTES = 120;
28633
+ SHIP_DEFAULT_MAX_MINUTES = AGENTS.ship.maxMinutes;
27890
28634
  SHIP_ROUND_RESERVE_MS = 3 * 6e4;
27891
28635
  }
27892
28636
  });
@@ -28044,7 +28788,7 @@ var init_preflight = __esm({
28044
28788
 
28045
28789
  // ../../src/core/dispatch/ship.ts
28046
28790
  async function runShipBranch(deps, msg, io, ctx) {
28047
- const { agent, card, directives, history, repoCtx, label, ending, trace, closeLines, refuse: refuse3, doneLines } = ctx;
28791
+ const { agent, profile, card, directives, history, repoCtx, label, ending, trace, closeLines, refuse: refuse3, doneLines } = ctx;
28048
28792
  const root = trace.root;
28049
28793
  const clock2 = deps.clock ?? systemClock;
28050
28794
  const shell = createCardShell({ label, startedAt: ctx.startedAt, now: clock2 });
@@ -28146,7 +28890,8 @@ async function runShipBranch(deps, msg, io, ctx) {
28146
28890
  diagnosis: analyzeRunFriction(startSnap.events, {
28147
28891
  finished: false,
28148
28892
  truncated: startSnap.truncated
28149
- })
28893
+ }),
28894
+ profile: profileRecordOf(agent, profile)
28150
28895
  }),
28151
28896
  { provisional: true }
28152
28897
  );
@@ -28167,7 +28912,8 @@ async function runShipBranch(deps, msg, io, ctx) {
28167
28912
  ...repoCtx.repo !== void 0 ? { repo: repoCtx.repo } : {},
28168
28913
  ...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
28169
28914
  ...msg.userName !== void 0 ? { userName: msg.userName } : {},
28170
- ...entry.resume !== void 0 ? { pr: entry.resume.pr } : {}
28915
+ ...entry.resume !== void 0 ? { pr: entry.resume.pr } : {},
28916
+ profile
28171
28917
  },
28172
28918
  card: card.handle ?? null,
28173
28919
  system: "",
@@ -28242,7 +28988,12 @@ async function runShipBranch(deps, msg, io, ctx) {
28242
28988
  effort: spec.effort,
28243
28989
  channel: scopes.channel,
28244
28990
  user: scopes.user,
28245
- messageDirective: { agent: directives.agent, model: directives.model, effort: directives.effort },
28991
+ messageDirective: {
28992
+ agent: directives.agent,
28993
+ model: directives.model,
28994
+ effort: directives.effort,
28995
+ budget: directives.budget
28996
+ },
28246
28997
  threadDirective: { agent: ctx.sticky.agent, model: ctx.sticky.model, effort: ctx.sticky.effort },
28247
28998
  canEditChannelConfig: deps.config.canEditChannelConfig(msg.userId)
28248
28999
  // No `mcp` here: ship rounds receive no MCP tools yet (docs/reference/specs/mcp-tools.md
@@ -28281,7 +29032,11 @@ async function runShipBranch(deps, msg, io, ctx) {
28281
29032
  dataDir: deps.dataDir ?? "./data"
28282
29033
  },
28283
29034
  threadKey: msg.threadKey,
28284
- caps: resolveShipCaps(deps.config.config.ship),
29035
+ // The rounds cap is the config block's; the wall clock is the parent's
29036
+ // effective budget — the preset's declared `ship.maxMinutes` as a
29037
+ // boundary or a `budget:` directive clipped it (agent-ship.md item 8) —
29038
+ // so every child round is clipped to what remains of THAT.
29039
+ caps: { ...resolveShipCaps(deps.config.config.ship), maxMinutes: profile.minutes },
28285
29040
  control: run2.control,
28286
29041
  inbox: ctx.live.inbox,
28287
29042
  // thread follow-ups steered into this run reach the child round in flight (thread-admission item 2)
@@ -28347,7 +29102,8 @@ async function runShipBranch(deps, msg, io, ctx) {
28347
29102
  seal,
28348
29103
  // The last coding round's handoff (agent-ship.md item 14) — the
28349
29104
  // pipeline's, so the ship record carries what its child handed back.
28350
- ...outcome?.handoff !== void 0 ? { handoff: outcome.handoff } : {}
29105
+ ...outcome?.handoff !== void 0 ? { handoff: outcome.handoff } : {},
29106
+ profile: profileRecordOf(agent, profile)
28351
29107
  }),
28352
29108
  { span: root, ...ledgerRun ? { via: ledgerRun.sink } : {} }
28353
29109
  )
@@ -28601,7 +29357,16 @@ async function dispatch(deps, msg, io, opts = {}) {
28601
29357
  if (await answerOperation(deps, { msg, io, ending, trace, directives, history })) return;
28602
29358
  const { sticky, resolved } = resolveRun(deps, { msg, directives, history });
28603
29359
  if ((await authorizeAgent(deps, { msg, io, refuse: refuse3, agentName: resolved.agentName })).kind === "refused") return;
28604
- const agent = getAgent(resolved.agentName);
29360
+ const agent = resolved.agentName === "ship" ? shipPresetFor(deps.config.config.ship) : getAgent(resolved.agentName);
29361
+ const profileGate = await authorizeProfile(deps, {
29362
+ msg,
29363
+ io,
29364
+ refuse: refuse3,
29365
+ agent,
29366
+ resolution: resolveProfile({ agent, resolved, resume, budget: directives.budget })
29367
+ });
29368
+ if (profileGate.kind === "refused") return;
29369
+ const { profile } = profileGate;
28605
29370
  const carriedRow = resume?.row ?? restart?.row;
28606
29371
  const admissionCtx = {
28607
29372
  msg,
@@ -28636,6 +29401,7 @@ async function dispatch(deps, msg, io, opts = {}) {
28636
29401
  msg,
28637
29402
  history,
28638
29403
  agent,
29404
+ profile,
28639
29405
  resolved,
28640
29406
  resume,
28641
29407
  root
@@ -28657,15 +29423,19 @@ async function dispatch(deps, msg, io, opts = {}) {
28657
29423
  closeLines,
28658
29424
  clock: clock2,
28659
29425
  agent,
29426
+ profile,
28660
29427
  needsRepo,
28661
29428
  repoCtx
28662
29429
  });
28663
29430
  if (repoGate.kind === "refused") return;
29431
+ const clip3 = budgetClipLabel(agent, profile, directives.budget);
29432
+ if (clip3) shell.setLabel(`${shell.label} \xB7 ${clip3}`);
28664
29433
  if (agent.name === "ship") {
28665
29434
  setupCard = void 0;
28666
29435
  clearInterval(setupHeartbeat);
28667
29436
  await runShipBranch(deps, msg, io, {
28668
29437
  agent,
29438
+ profile,
28669
29439
  modelRef: resolved.modelRef,
28670
29440
  label: shell.label,
28671
29441
  startedAt,
@@ -28722,6 +29492,7 @@ async function dispatch(deps, msg, io, opts = {}) {
28722
29492
  const reservation = await reserveRun(deps, {
28723
29493
  msg,
28724
29494
  agent,
29495
+ profile,
28725
29496
  resolved,
28726
29497
  repoCtx,
28727
29498
  channelVisibility,
@@ -28748,6 +29519,7 @@ async function dispatch(deps, msg, io, opts = {}) {
28748
29519
  closeLines,
28749
29520
  clock: clock2,
28750
29521
  agent,
29522
+ profile,
28751
29523
  repoCtx,
28752
29524
  root
28753
29525
  });
@@ -28785,6 +29557,7 @@ async function dispatch(deps, msg, io, opts = {}) {
28785
29557
  const prompt = await composePrompt(deps, {
28786
29558
  msg,
28787
29559
  agent,
29560
+ profile,
28788
29561
  resolved,
28789
29562
  directives,
28790
29563
  sticky,
@@ -28812,10 +29585,11 @@ async function dispatch(deps, msg, io, opts = {}) {
28812
29585
  `[resume] ${msg.threadKey} run ${run2.id} continues under ${deps.runLedger.gen}: from step ${resume.plan.step}, ${resume.plan.settlements.length} call(s) to settle, ${resume.events.length} event(s) replayed`
28813
29586
  );
28814
29587
  }
28815
- writeTombstone(deps, { msg, agent, resolved, repoCtx, channelVisibility, run: run2, registry, resume });
29588
+ writeTombstone(deps, { msg, agent, profile, resolved, repoCtx, channelVisibility, run: run2, registry, resume });
28816
29589
  ledgerRun = await claimRun(deps, {
28817
29590
  msg,
28818
29591
  agent,
29592
+ profile,
28819
29593
  resolved,
28820
29594
  repoCtx,
28821
29595
  channelVisibility,
@@ -28837,6 +29611,7 @@ async function dispatch(deps, msg, io, opts = {}) {
28837
29611
  msg,
28838
29612
  io,
28839
29613
  agent,
29614
+ profile,
28840
29615
  resolved,
28841
29616
  provider,
28842
29617
  model,
@@ -29000,6 +29775,7 @@ var init_dispatcher = __esm({
29000
29775
  init_reply();
29001
29776
  init_record();
29002
29777
  init_ship();
29778
+ init_shipPipeline();
29003
29779
  init_settle();
29004
29780
  init_runRegistry();
29005
29781
  init_runEnding();
@@ -29007,22 +29783,6 @@ var init_dispatcher = __esm({
29007
29783
  }
29008
29784
  });
29009
29785
 
29010
- // ../../src/core/workerError.ts
29011
- async function errorSuffix(res) {
29012
- const text = await res.text().catch(() => "");
29013
- try {
29014
- const parsed = JSON.parse(text);
29015
- return parsed?.error ? `: ${String(parsed.error)}` : "";
29016
- } catch {
29017
- return "";
29018
- }
29019
- }
29020
- var init_workerError = __esm({
29021
- "../../src/core/workerError.ts"() {
29022
- "use strict";
29023
- }
29024
- });
29025
-
29026
29786
  // ../../src/core/runStoreWorker.ts
29027
29787
  function describeError2(err2, depth = 0) {
29028
29788
  if (!(err2 instanceof Error)) return String(err2);
@@ -29137,10 +29897,10 @@ var init_runStoreWorker = __esm({
29137
29897
  throw new TransientStoreError(`run store ${path}: ${describeError2(err2)}`);
29138
29898
  }
29139
29899
  if (!res.ok) {
29140
- const message = `run store ${path} HTTP ${res.status}${await errorSuffix(res)}`;
29141
- if (res.status === 404) throw new RouteMissingError(message);
29142
- if (res.status >= 500 || res.status === 408 || res.status === 429) throw new TransientStoreError(message);
29143
- throw new PermanentStoreError(message);
29900
+ const message2 = `run store ${path} HTTP ${res.status}${await errorSuffix(res)}`;
29901
+ if (res.status === 404) throw new RouteMissingError(message2);
29902
+ if (res.status >= 500 || res.status === 408 || res.status === 429) throw new TransientStoreError(message2);
29903
+ throw new PermanentStoreError(message2);
29144
29904
  }
29145
29905
  const data = await res.json().catch(() => null);
29146
29906
  if (typeof data !== "object" || data === null)
@@ -29710,7 +30470,7 @@ function createLedgerWriteThrough(opts) {
29710
30470
  * no record at all was killed before its conversation was stored and
29711
30471
  * closes `interrupted`. */
29712
30472
  async seed(messages, budgetMs) {
29713
- const turns = messages.map((message, idx) => ({ idx, message }));
30473
+ const turns = messages.map((message2, idx) => ({ idx, message: message2 }));
29714
30474
  try {
29715
30475
  const seeded = await ledger.seed(this.runId, gen, turns);
29716
30476
  if (!seeded.ok) {
@@ -29740,7 +30500,7 @@ function createLedgerWriteThrough(opts) {
29740
30500
  }
29741
30501
  async step(report2) {
29742
30502
  if (this.detached) return;
29743
- const turns = report2.turns.map((message, i) => ({ idx: report2.firstIdx + i, message }));
30503
+ const turns = report2.turns.map((message2, i) => ({ idx: report2.firstIdx + i, message: message2 }));
29744
30504
  const record = {
29745
30505
  step: ++this.stepNo,
29746
30506
  seq: this.lastSeq,
@@ -29905,9 +30665,9 @@ function createLedgerWriteThrough(opts) {
29905
30665
  return run2;
29906
30666
  },
29907
30667
  liveRuns: () => [...live],
29908
- async pushInbox(runId3, message) {
30668
+ async pushInbox(runId3, message2) {
29909
30669
  try {
29910
- const result = await ledger.pushInbox(runId3, message);
30670
+ const result = await ledger.pushInbox(runId3, message2);
29911
30671
  if (result.ok && result.seq !== void 0) return result.seq;
29912
30672
  warn(`[ledger] inbox push refused for run ${runId3}: no live row \u2014 the follow-up rides in memory only`);
29913
30673
  return void 0;
@@ -31448,8 +32208,8 @@ function createSlackApp(deps) {
31448
32208
  }
31449
32209
  );
31450
32210
  });
31451
- app.message(async ({ message, client }) => {
31452
- const m = message;
32211
+ app.message(async ({ message: message2, client }) => {
32212
+ const m = message2;
31453
32213
  if (classifyMessage(m) === "skip") return;
31454
32214
  botUserId ??= (await client.auth.test()).user_id ?? void 0;
31455
32215
  const decision = classifyMessage(m, botUserId);
@@ -32138,8 +32898,8 @@ function toIncomingMessage2(identity2, args) {
32138
32898
  function ok(id, result) {
32139
32899
  return { jsonrpc: "2.0", id, result };
32140
32900
  }
32141
- function err(id, code, message, data) {
32142
- return { jsonrpc: "2.0", id, error: data ? { code, message, data } : { code, message } };
32901
+ function err(id, code, message2, data) {
32902
+ return { jsonrpc: "2.0", id, error: data ? { code, message: message2, data } : { code, message: message2 } };
32143
32903
  }
32144
32904
  function readId(msg) {
32145
32905
  const id = msg.id;
@@ -33095,7 +33855,7 @@ function loadWebAssets(distDir) {
33095
33855
  const full = join13(assetsDir, name);
33096
33856
  if (statSync3(full).isFile()) files.set(`/assets/${name}`, readFileSync16(full));
33097
33857
  }
33098
- const serve = (req, res) => {
33858
+ const serve2 = (req, res) => {
33099
33859
  const path = (req.url ?? "/").split("?")[0];
33100
33860
  if (!path.startsWith("/assets/")) return false;
33101
33861
  const method = (req.method ?? "GET").toUpperCase();
@@ -33119,7 +33879,7 @@ function loadWebAssets(distDir) {
33119
33879
  else res.end(body);
33120
33880
  return true;
33121
33881
  };
33122
- return { entry, serve };
33882
+ return { entry, serve: serve2 };
33123
33883
  }
33124
33884
  var CONTENT_TYPES, WEB_DIST_DIR;
33125
33885
  var init_webAssets = __esm({
@@ -33323,6 +34083,7 @@ function createDeliveryViewHandler(deps, shell) {
33323
34083
  }
33324
34084
  const weeksParam = url.searchParams.get("weeks");
33325
34085
  const sinceParam = url.searchParams.get("since");
34086
+ const fresh = url.searchParams.get("fresh") === "1";
33326
34087
  const runs = async (range) => {
33327
34088
  if (!deps.runs) return [];
33328
34089
  const page2 = await deps.runs.listRuns({
@@ -33336,6 +34097,7 @@ function createDeliveryViewHandler(deps, shell) {
33336
34097
  deps.service.report(repo, {
33337
34098
  ...weeksParam !== null ? { weeks: Number(weeksParam) } : {},
33338
34099
  ...sinceParam !== null ? { since: sinceParam } : {},
34100
+ ...fresh ? { fresh } : {},
33339
34101
  runs
33340
34102
  }).then((report2) => {
33341
34103
  if (route2.kind === "json") {
@@ -33719,14 +34481,14 @@ function createMcpConnectViewHandler(deps) {
33719
34481
  async function complete(svc, nonce, identity2, token, res) {
33720
34482
  const { decision, verified, toolCount, warning, server } = await svc.completeTicket(nonce, identity2, token);
33721
34483
  if (!decision.ok) {
33722
- const message = refusalMessage(decision.refusal);
34484
+ const message2 = refusalMessage(decision.refusal);
33723
34485
  if (decision.refusal.kind === "bad_token" && server)
33724
- return page(res, 400, "Token not accepted", `<p class="err">${esc(message)}</p>${entryForm(server, nonce)}`);
34486
+ return page(res, 400, "Token not accepted", `<p class="err">${esc(message2)}</p>${entryForm(server, nonce)}`);
33725
34487
  return page(
33726
34488
  res,
33727
34489
  statusFor(decision.refusal.kind),
33728
34490
  "Connect link unavailable",
33729
- `<p class="err">${esc(message)}</p>`
34491
+ `<p class="err">${esc(message2)}</p>`
33730
34492
  );
33731
34493
  }
33732
34494
  if (verified === false) {
@@ -33742,15 +34504,15 @@ async function complete(svc, nonce, identity2, token, res) {
33742
34504
  async function start(svc, nonce, identity2, res) {
33743
34505
  const result = await svc.startOAuth(nonce, identity2);
33744
34506
  if (!result.ok) {
33745
- const message = refusalMessage(result.refusal);
34507
+ const message2 = refusalMessage(result.refusal);
33746
34508
  if (result.refusal.kind === "oauth_failed" && result.server)
33747
34509
  return page(
33748
34510
  res,
33749
34511
  502,
33750
34512
  "Sign-in could not start",
33751
- `<p class="err">${esc(message)}</p><p>Nothing was stored.</p>${entryForm(result.server, nonce)}`
34513
+ `<p class="err">${esc(message2)}</p><p>Nothing was stored.</p>${entryForm(result.server, nonce)}`
33752
34514
  );
33753
- return page(res, statusFor(result.refusal.kind), "Connect link unavailable", `<p class="err">${esc(message)}</p>`);
34515
+ return page(res, statusFor(result.refusal.kind), "Connect link unavailable", `<p class="err">${esc(message2)}</p>`);
33754
34516
  }
33755
34517
  const host = hostOf(result.redirectUrl);
33756
34518
  page(
@@ -33784,13 +34546,13 @@ function callback(svc, params, identity2, res) {
33784
34546
  }).then((result) => {
33785
34547
  if (!result.ok) {
33786
34548
  const refusal = result.refusal ?? { kind: "not_found" };
33787
- const message = refusalMessage(refusal);
34549
+ const message2 = refusalMessage(refusal);
33788
34550
  const retry = result.server && refusal.kind === "oauth_failed" ? `<p>Nothing was stored. Open the connect link again to retry.</p>` : "";
33789
34551
  return page(
33790
34552
  res,
33791
34553
  refusal.kind === "oauth_failed" ? 502 : statusFor(refusal.kind),
33792
34554
  "Sign-in did not complete",
33793
- `<p class="err">${esc(message)}</p>${retry}`
34555
+ `<p class="err">${esc(message2)}</p>${retry}`
33794
34556
  );
33795
34557
  }
33796
34558
  return connected(res, result.server, result.toolCount, result.warning);
@@ -33989,19 +34751,19 @@ var init_processMetrics = __esm({
33989
34751
  function hasData(part) {
33990
34752
  return (part.type === "image" || part.type === "document") && typeof part.data === "string";
33991
34753
  }
33992
- function turnRows(idx, message, opts = {}) {
34754
+ function turnRows(idx, message2, opts = {}) {
33993
34755
  const partBytes = opts.partBytes ?? TRANSCRIPT_PART_BYTES;
33994
34756
  const refBytes = opts.attachmentRefBytes ?? ATTACHMENT_REF_BYTES;
33995
34757
  const rows = [];
33996
34758
  const attachments = [];
33997
- message.content.forEach((part, i) => {
34759
+ message2.content.forEach((part, i) => {
33998
34760
  let stored = part;
33999
34761
  if (hasData(part) && part.data.length > refBytes) {
34000
34762
  const ref = attachmentRef(idx, i);
34001
34763
  attachments.push({ ref, mediaType: part.mediaType, data: part.data });
34002
34764
  stored = { ...part, data: "", dataRef: ref };
34003
34765
  }
34004
- const json = JSON.stringify({ role: message.role, part: stored });
34766
+ const json = JSON.stringify({ role: message2.role, part: stored });
34005
34767
  const bytes = utf8ByteLength(json);
34006
34768
  if (bytes > partBytes) {
34007
34769
  throw new Error(`transcript: part ${i} of turn ${idx} is ${bytes} bytes, over the ${partBytes} row budget`);
@@ -34210,9 +34972,9 @@ var init_runLedgerWorker = __esm({
34210
34972
  this.checkIds(runId3, gen);
34211
34973
  return this.fenceResult(await this.post("/runs/state", { storeKey: this.opts.storeKey, runId: runId3, gen, state }));
34212
34974
  }
34213
- async pushInbox(runId3, message) {
34975
+ async pushInbox(runId3, message2) {
34214
34976
  this.checkIds(runId3);
34215
- const r = await this.post("/runs/inbox", { storeKey: this.opts.storeKey, runId: runId3, message });
34977
+ const r = await this.post("/runs/inbox", { storeKey: this.opts.storeKey, runId: runId3, message: message2 });
34216
34978
  return { ok: r.data.ok === true, ...typeof r.data.seq === "number" ? { seq: r.data.seq } : {} };
34217
34979
  }
34218
34980
  async readInbox(runId3, afterSeq) {
@@ -34599,7 +35361,7 @@ var init_resume = __esm({
34599
35361
  "submit_handoff",
34600
35362
  "submit_dispositions"
34601
35363
  ]);
34602
- toolUsesOf = (message) => message?.role === "assistant" ? message.content.filter((p) => p.type === "tool_use") : [];
35364
+ toolUsesOf = (message2) => message2?.role === "assistant" ? message2.content.filter((p) => p.type === "tool_use") : [];
34603
35365
  }
34604
35366
  });
34605
35367
 
@@ -35292,7 +36054,16 @@ async function runBot() {
35292
36054
  };
35293
36055
  const runsService = createRunsService({ registry: defaultRunRegistry, store: runStore, ledger: ledgerClient });
35294
36056
  const scheduleStore = buildScheduleStore(config.config.schedules, processSecrets, (m) => console.warn(`[schedules] ${m}`)) ?? new NullScheduleStore();
35295
- const deliveryService = capabilities.github ? deliveryServiceFromConfig(parseDeliveryConfig(config.config.delivery)) : new NullDeliveryService();
36057
+ const deliveryWarn = (m) => console.warn(`[delivery] ${m}`);
36058
+ const deliveryConfig = parseDeliveryConfig(config.config.delivery);
36059
+ const delivery = capabilities.github ? deliveryFromConfig(deliveryConfig, {
36060
+ snapshots: buildDeliverySnapshotStore(config.config, processSecrets, deliveryWarn),
36061
+ warn: deliveryWarn
36062
+ }) : void 0;
36063
+ const deliveryService = delivery?.service ?? new NullDeliveryService();
36064
+ const deliveryEveryMinutes = deliveryConfig?.snapshot.everyMinutes ?? SNAPSHOT_EVERY_MINUTES.default;
36065
+ if (delivery && deliveryService.repos().length > 0)
36066
+ delivery.source.startRefreshLoop({ repos: deliveryService.repos(), everyMinutes: deliveryEveryMinutes });
35296
36067
  const commands = buildCoreCommands(config, runStore, {
35297
36068
  registry: defaultRunRegistry,
35298
36069
  secrets: processSecrets,
@@ -35350,7 +36121,7 @@ async function runBot() {
35350
36121
  const costsView = createCostsViewHandler(costsService, shell);
35351
36122
  const costsState = capabilities.costs ? `GET /costs (${costsService.groups().join(",")}; LLM ${anthropicAdminKey ? "on" : "off"})` : costsCfg ? `GET /costs (503 \u2014 ${costsCfg.cloudflareTokenEnv} not set)` : "GET /costs (503 \u2014 no costs config)";
35352
36123
  const deliveryView = createDeliveryViewHandler({ service: deliveryService, runs: runsService }, shell);
35353
- const deliveryState = !capabilities.github ? "GET /delivery (503 \u2014 no GitHub credential)" : deliveryService.repos().length === 0 ? "GET /delivery (503 \u2014 no delivery.repos)" : `GET /delivery (${deliveryService.repos().join(",")})`;
36124
+ const deliveryState = !capabilities.github ? "GET /delivery (503 \u2014 no GitHub credential)" : deliveryService.repos().length === 0 ? "GET /delivery (503 \u2014 no delivery.repos)" : `GET /delivery (${deliveryService.repos().join(",")}; snapshot every ${deliveryEveryMinutes} min)`;
35354
36125
  const tokenCount = Object.keys(auth.tokens).length;
35355
36126
  const liveViewState = process.env.PUBLIC_BASE_URL ? "GET /runs (index) + /runs/:id (live view)" : "GET /runs (index) + live view (no PUBLIC_BASE_URL \u2014 per-run links omitted)";
35356
36127
  const accessConfig = parseAccessConfig(publicEnv());
@@ -35676,6 +36447,7 @@ var init_index = __esm({
35676
36447
  init_costsView();
35677
36448
  init_deliveryView();
35678
36449
  init_delivery();
36450
+ init_deliverySnapshotStore();
35679
36451
  init_costs();
35680
36452
  init_residentAdmin();
35681
36453
  init_residentFleet();
@@ -35883,8 +36655,8 @@ function exitCodeFor(error) {
35883
36655
  if (error === "busy") return 75;
35884
36656
  return 1;
35885
36657
  }
35886
- function errorLine(code, message) {
35887
- return `error (${code}): ${message}`;
36658
+ function errorLine(code, message2) {
36659
+ return `error (${code}): ${message2}`;
35888
36660
  }
35889
36661
  async function runCommand(commands, parsed, caller, opts = {}) {
35890
36662
  const cmd = commands.get(parsed.id);