@coreplane/switchboard 1.203.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 (373) 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/project.json +1 -1
  8. package/dist/assets/source.json +3 -3
  9. package/dist/assets/src/agents/registry.ts +466 -0
  10. package/dist/assets/src/config/profile.ts +200 -0
  11. package/dist/assets/src/core/delivery.ts +775 -0
  12. package/dist/assets/src/core/deliverySnapshotStore.ts +200 -0
  13. package/dist/assets/src/core/runLedger/types.ts +6 -0
  14. package/dist/assets/src/core/runRecord.ts +44 -0
  15. package/dist/assets/src/core/ship/contract.ts +502 -0
  16. package/dist/assets/src/core/workerError.ts +12 -0
  17. package/dist/assets/web/dist/.vite/manifest.json +2770 -70
  18. package/dist/assets/web/dist/assets/AppShell-lYVcj-k7.js +1 -0
  19. package/dist/assets/web/dist/assets/{CostsPage-XQ-fQzdz.js → CostsPage-DGSE48l1.js} +1 -1
  20. package/dist/assets/web/dist/assets/DeliveryPage-Cx7kQC_e.js +1 -0
  21. package/dist/assets/web/dist/assets/NotFoundPage-BzWS4Ca1.js +1 -0
  22. package/dist/assets/web/dist/assets/ResidentDetailPage-Bcasasjb.js +1 -0
  23. package/dist/assets/web/dist/assets/ResidentsIndexPage-BJwyrphn.js +1 -0
  24. package/dist/assets/web/dist/assets/RunRoutePage-6eStApqP.js +12 -0
  25. package/dist/assets/web/dist/assets/RunRoutePage-f6TSNPeW.css +1 -0
  26. package/dist/assets/web/dist/assets/RunsIndexPage-CWrkv7v8.js +1 -0
  27. package/dist/assets/web/dist/assets/RunsTabs-DEICQ4FY.js +1 -0
  28. package/dist/assets/web/dist/assets/ScheduledPage-BX2py1X3.js +1 -0
  29. package/dist/assets/web/dist/assets/StatusDot-CwCK84JN.js +1 -0
  30. package/dist/assets/web/dist/assets/Tooltip-CFC88_-Z.js +1 -0
  31. package/dist/assets/web/dist/assets/abap-CLvhMVsD.js +1 -0
  32. package/dist/assets/web/dist/assets/actionscript-3-Cs6JJXT5.js +1 -0
  33. package/dist/assets/web/dist/assets/ada-C5qYipkI.js +1 -0
  34. package/dist/assets/web/dist/assets/ahk-CA9gdOdF.js +1 -0
  35. package/dist/assets/web/dist/assets/ahk2-_6rzW9BS.js +1 -0
  36. package/dist/assets/web/dist/assets/andromeeda-vGVdxbeo.js +1 -0
  37. package/dist/assets/web/dist/assets/angular-html-Cw130Zyi.js +1 -0
  38. package/dist/assets/web/dist/assets/angular-ts-DY1m4C2s.js +1 -0
  39. package/dist/assets/web/dist/assets/apache-U0d_L8uA.js +1 -0
  40. package/dist/assets/web/dist/assets/apex-KFGFyKwG.js +1 -0
  41. package/dist/assets/web/dist/assets/apl-W6Ty7v05.js +1 -0
  42. package/dist/assets/web/dist/assets/applescript-CCn79oCD.js +1 -0
  43. package/dist/assets/web/dist/assets/ara-4CJ0cIlV.js +1 -0
  44. package/dist/assets/web/dist/assets/asciidoc-SCjQUq34.js +1 -0
  45. package/dist/assets/web/dist/assets/asm-Cmm7eHzH.js +1 -0
  46. package/dist/assets/web/dist/assets/astro-DlLVHwVk.js +1 -0
  47. package/dist/assets/web/dist/assets/aurora-x-CDeNXAV0.js +1 -0
  48. package/dist/assets/web/dist/assets/awk-BWXHIvNe.js +1 -0
  49. package/dist/assets/web/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
  50. package/dist/assets/web/dist/assets/ayu-light-C3h-C4tm.js +1 -0
  51. package/dist/assets/web/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  52. package/dist/assets/web/dist/assets/ballerina-B7ZEbQpA.js +1 -0
  53. package/dist/assets/web/dist/assets/bat-qdxAdBWQ.js +1 -0
  54. package/dist/assets/web/dist/assets/beancount-D-usSTwE.js +1 -0
  55. package/dist/assets/web/dist/assets/berry-DKpUyyne.js +1 -0
  56. package/dist/assets/web/dist/assets/bibtex-Ci_nEsc7.js +1 -0
  57. package/dist/assets/web/dist/assets/bicep-CUHmPFLl.js +1 -0
  58. package/dist/assets/web/dist/assets/bird2-CYIUgYqB.js +1 -0
  59. package/dist/assets/web/dist/assets/blade-Bco3tDh4.js +1 -0
  60. package/dist/assets/web/dist/assets/bsl-_VjlB9mv.js +1 -0
  61. package/dist/assets/web/dist/assets/c-1LaFY_cj.js +1 -0
  62. package/dist/assets/web/dist/assets/c3-D0apG41V.js +1 -0
  63. package/dist/assets/web/dist/assets/cadence-CQ2zXKGN.js +1 -0
  64. package/dist/assets/web/dist/assets/cairo-DLTphjLi.js +1 -0
  65. package/dist/assets/web/dist/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
  66. package/dist/assets/web/dist/assets/catppuccin-latte-CtsyrtPq.js +1 -0
  67. package/dist/assets/web/dist/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
  68. package/dist/assets/web/dist/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
  69. package/dist/assets/web/dist/assets/chapel-B-OvrOL5.js +1 -0
  70. package/dist/assets/web/dist/assets/clarity-CtfBBspg.js +1 -0
  71. package/dist/assets/web/dist/assets/clojure-DqKBuwfJ.js +1 -0
  72. package/dist/assets/web/dist/assets/cmake-Bj61d0ZC.js +1 -0
  73. package/dist/assets/web/dist/assets/cobol-Ch3EPlHu.js +1 -0
  74. package/dist/assets/web/dist/assets/codeowners-C8r90Shi.js +1 -0
  75. package/dist/assets/web/dist/assets/codeql-oeQT6MSM.js +1 -0
  76. package/dist/assets/web/dist/assets/coffee-hZuITnto.js +1 -0
  77. package/dist/assets/web/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
  78. package/dist/assets/web/dist/assets/coq-DuBRPxYg.js +1 -0
  79. package/dist/assets/web/dist/assets/cpp-B8iRVo8m.js +1 -0
  80. package/dist/assets/web/dist/assets/crystal-DKveI7lr.js +1 -0
  81. package/dist/assets/web/dist/assets/csharp-oqKa8noW.js +1 -0
  82. package/dist/assets/web/dist/assets/css-_2_CD-rr.js +1 -0
  83. package/dist/assets/web/dist/assets/csv-Dx-8-gkx.js +1 -0
  84. package/dist/assets/web/dist/assets/cue-CE9AQfxI.js +1 -0
  85. package/dist/assets/web/dist/assets/cypher-ClKdZ_lG.js +1 -0
  86. package/dist/assets/web/dist/assets/d-qD-0Kul2.js +1 -0
  87. package/dist/assets/web/dist/assets/dark-plus-Cs2F2srj.js +1 -0
  88. package/dist/assets/web/dist/assets/dart-CnvKMtbv.js +1 -0
  89. package/dist/assets/web/dist/assets/dax-BkyTk9wS.js +1 -0
  90. package/dist/assets/web/dist/assets/desktop-Dlh5hvp9.js +1 -0
  91. package/dist/assets/web/dist/assets/diff-woXpYk--.js +1 -0
  92. package/dist/assets/web/dist/assets/dist-BoLiHpua.js +1547 -0
  93. package/dist/assets/web/dist/assets/docker-IyjqRm3v.js +1 -0
  94. package/dist/assets/web/dist/assets/dotenv-_5a1GRtc.js +1 -0
  95. package/dist/assets/web/dist/assets/dracula-BHWKrbxM.js +1 -0
  96. package/dist/assets/web/dist/assets/dracula-soft-5eyTD99u.js +1 -0
  97. package/dist/assets/web/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
  98. package/dist/assets/web/dist/assets/edge-BenMDPct.js +1 -0
  99. package/dist/assets/web/dist/assets/elixir-CBbdEqG6.js +1 -0
  100. package/dist/assets/web/dist/assets/elm-DMub_2lY.js +1 -0
  101. package/dist/assets/web/dist/assets/emacs-lisp-c_oH4hRZ.js +1 -0
  102. package/dist/assets/web/dist/assets/erb-CI9He_Up.js +1 -0
  103. package/dist/assets/web/dist/assets/erlang-Cphh6RMH.js +1 -0
  104. package/dist/assets/web/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
  105. package/dist/assets/web/dist/assets/everforest-light-Df2xbC6M.js +1 -0
  106. package/dist/assets/web/dist/assets/{favicon-CFfbl2AI.js → favicon-CWPcvWvp.js} +1 -1
  107. package/dist/assets/web/dist/assets/fennel-DQxkIbk2.js +1 -0
  108. package/dist/assets/web/dist/assets/fish-BJitypiv.js +1 -0
  109. package/dist/assets/web/dist/assets/fluent-C03EYrpw.js +1 -0
  110. package/dist/assets/web/dist/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  111. package/dist/assets/web/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
  112. package/dist/assets/web/dist/assets/fsharp-D13ZGOAj.js +1 -0
  113. package/dist/assets/web/dist/assets/gdresource-xMC7jr7x.js +1 -0
  114. package/dist/assets/web/dist/assets/gdscript-DYvRD3hg.js +1 -0
  115. package/dist/assets/web/dist/assets/gdshader-CBce3t8t.js +1 -0
  116. package/dist/assets/web/dist/assets/genie-CV2tkWYe.js +1 -0
  117. package/dist/assets/web/dist/assets/gherkin-DExj1W_8.js +1 -0
  118. package/dist/assets/web/dist/assets/git-commit-BSykSTBG.js +1 -0
  119. package/dist/assets/web/dist/assets/git-rebase-BBfhnyF8.js +1 -0
  120. package/dist/assets/web/dist/assets/github-dark-C-LZuMrd.js +1 -0
  121. package/dist/assets/web/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
  122. package/dist/assets/web/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  123. package/dist/assets/web/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  124. package/dist/assets/web/dist/assets/github-light-EUqPIrTm.js +1 -0
  125. package/dist/assets/web/dist/assets/github-light-default-BXViO-2h.js +1 -0
  126. package/dist/assets/web/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  127. package/dist/assets/web/dist/assets/gleam-CSRkHgEL.js +1 -0
  128. package/dist/assets/web/dist/assets/glimmer-js-CR9Fxrau.js +1 -0
  129. package/dist/assets/web/dist/assets/glimmer-ts-CwFwX_XS.js +1 -0
  130. package/dist/assets/web/dist/assets/glsl-Daiodp1r.js +1 -0
  131. package/dist/assets/web/dist/assets/gn-ilITqXS6.js +1 -0
  132. package/dist/assets/web/dist/assets/gnuplot-7GGW24-e.js +1 -0
  133. package/dist/assets/web/dist/assets/go-rLFTqkRN.js +1 -0
  134. package/dist/assets/web/dist/assets/graphql-DZvrsEkB.js +1 -0
  135. package/dist/assets/web/dist/assets/groovy-CacY0gHj.js +1 -0
  136. package/dist/assets/web/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  137. package/dist/assets/web/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  138. package/dist/assets/web/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  139. package/dist/assets/web/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  140. package/dist/assets/web/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  141. package/dist/assets/web/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  142. package/dist/assets/web/dist/assets/hack-BaXOzdg7.js +1 -0
  143. package/dist/assets/web/dist/assets/haml-CMskq_VA.js +1 -0
  144. package/dist/assets/web/dist/assets/handlebars-Dhw_f7jD.js +1 -0
  145. package/dist/assets/web/dist/assets/haskell-D8IpX4py.js +1 -0
  146. package/dist/assets/web/dist/assets/haxe-C8oCnwJJ.js +1 -0
  147. package/dist/assets/web/dist/assets/hcl-Dh228itO.js +1 -0
  148. package/dist/assets/web/dist/assets/hjson-CxZEssPk.js +1 -0
  149. package/dist/assets/web/dist/assets/hlsl-Cvrh5tZx.js +1 -0
  150. package/dist/assets/web/dist/assets/horizon-CE9ld1lL.js +1 -0
  151. package/dist/assets/web/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
  152. package/dist/assets/web/dist/assets/houston-CsvMBhTu.js +1 -0
  153. package/dist/assets/web/dist/assets/html-C9k99z0z.js +1 -0
  154. package/dist/assets/web/dist/assets/html-derivative-VckPItXN.js +1 -0
  155. package/dist/assets/web/dist/assets/http-DXU_3h9v.js +1 -0
  156. package/dist/assets/web/dist/assets/hurl-KU24JNdp.js +1 -0
  157. package/dist/assets/web/dist/assets/hxml-Dcm88srW.js +1 -0
  158. package/dist/assets/web/dist/assets/hy-CZbG8q4J.js +1 -0
  159. package/dist/assets/web/dist/assets/imba-DsUTQ-LC.js +1 -0
  160. package/dist/assets/web/dist/assets/indexFormat-B-pd4r7Y.js +1 -0
  161. package/dist/assets/web/dist/assets/ini-B5eOa1yu.js +1 -0
  162. package/dist/assets/web/dist/assets/java-BbZjTNgf.js +1 -0
  163. package/dist/assets/web/dist/assets/javascript-DXCdqcZZ.js +1 -0
  164. package/dist/assets/web/dist/assets/jinja-pDlsjTVp.js +1 -0
  165. package/dist/assets/web/dist/assets/jison-DsXDk66e.js +1 -0
  166. package/dist/assets/web/dist/assets/json-tCxXfRgG.js +1 -0
  167. package/dist/assets/web/dist/assets/json5-BR5RXkoi.js +1 -0
  168. package/dist/assets/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
  169. package/dist/assets/web/dist/assets/jsonl-CmCQp5Yx.js +1 -0
  170. package/dist/assets/web/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
  171. package/dist/assets/web/dist/assets/jssm-DXw9l8Rf.js +1 -0
  172. package/dist/assets/web/dist/assets/jsx-B0ZYkCmi.js +1 -0
  173. package/dist/assets/web/dist/assets/julia-BLzzeI6x.js +1 -0
  174. package/dist/assets/web/dist/assets/just-DvD2_60c.js +1 -0
  175. package/dist/assets/web/dist/assets/kanagawa-dragon-Bozd7klA.js +1 -0
  176. package/dist/assets/web/dist/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
  177. package/dist/assets/web/dist/assets/kanagawa-wave-BML5mF2J.js +1 -0
  178. package/dist/assets/web/dist/assets/kdl-CsD5j6eV.js +1 -0
  179. package/dist/assets/web/dist/assets/kotlin-DhhofPvG.js +1 -0
  180. package/dist/assets/web/dist/assets/kusto-C7mF5XQf.js +1 -0
  181. package/dist/assets/web/dist/assets/laserwave-C_8bwKvT.js +1 -0
  182. package/dist/assets/web/dist/assets/latex-511zn3h7.js +1 -0
  183. package/dist/assets/web/dist/assets/lean-CewbzKMR.js +1 -0
  184. package/dist/assets/web/dist/assets/less-DVTAwKKz.js +1 -0
  185. package/dist/assets/web/dist/assets/light-plus-DVQuIRkW.js +1 -0
  186. package/dist/assets/web/dist/assets/liquid-BD0BkMox.js +1 -0
  187. package/dist/assets/web/dist/assets/llvm-DqHOMS-s.js +1 -0
  188. package/dist/assets/web/dist/assets/log-BNLmms1o.js +1 -0
  189. package/dist/assets/web/dist/assets/logo-Cluzi2Zq.js +1 -0
  190. package/dist/assets/web/dist/assets/lua-NFicKm6N.js +1 -0
  191. package/dist/assets/web/dist/assets/luau-C2pBCSgt.js +1 -0
  192. package/dist/assets/web/dist/assets/main-i3ZNDRLK.css +1 -0
  193. package/dist/assets/web/dist/assets/main-tYcFk9Dc.js +28 -0
  194. package/dist/assets/web/dist/assets/make-Dixweg8N.js +1 -0
  195. package/dist/assets/web/dist/assets/markdown-BYOwaDjH.js +1 -0
  196. package/dist/assets/web/dist/assets/marko-1-G_nEXK.js +1 -0
  197. package/dist/assets/web/dist/assets/material-theme-Bm3Qr25_.js +1 -0
  198. package/dist/assets/web/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
  199. package/dist/assets/web/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
  200. package/dist/assets/web/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  201. package/dist/assets/web/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  202. package/dist/assets/web/dist/assets/matlab-D7qyCx1q.js +1 -0
  203. package/dist/assets/web/dist/assets/mdc-DUe3AQLp.js +1 -0
  204. package/dist/assets/web/dist/assets/mdx-DQZ5AkYe.js +1 -0
  205. package/dist/assets/web/dist/assets/mermaid-BxpfTIzt.js +1 -0
  206. package/dist/assets/web/dist/assets/min-dark-BSWPekZh.js +1 -0
  207. package/dist/assets/web/dist/assets/min-light-DDpmG2fV.js +1 -0
  208. package/dist/assets/web/dist/assets/mipsasm-BMqwQI7S.js +1 -0
  209. package/dist/assets/web/dist/assets/mojo-fg40_ntQ.js +1 -0
  210. package/dist/assets/web/dist/assets/monokai-B_bV9PQ9.js +1 -0
  211. package/dist/assets/web/dist/assets/moonbit-DgjMoLWr.js +1 -0
  212. package/dist/assets/web/dist/assets/move-BYnGhZoM.js +1 -0
  213. package/dist/assets/web/dist/assets/narrat-_X_XdTYD.js +1 -0
  214. package/dist/assets/web/dist/assets/nextflow-BJtWHP5T.js +1 -0
  215. package/dist/assets/web/dist/assets/nextflow-groovy-DJMQeKeT.js +1 -0
  216. package/dist/assets/web/dist/assets/nginx-Dg379_bA.js +1 -0
  217. package/dist/assets/web/dist/assets/night-owl-DhmEMT88.js +1 -0
  218. package/dist/assets/web/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
  219. package/dist/assets/web/dist/assets/nim-BnFq97ZO.js +1 -0
  220. package/dist/assets/web/dist/assets/nix-IvuFDN5E.js +1 -0
  221. package/dist/assets/web/dist/assets/nord-Cb4Vim4T.js +1 -0
  222. package/dist/assets/web/dist/assets/nsis-CjNLYCLg.js +1 -0
  223. package/dist/assets/web/dist/assets/nushell-DHYfO66s.js +1 -0
  224. package/dist/assets/web/dist/assets/objective-c-D1A_Heim.js +1 -0
  225. package/dist/assets/web/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
  226. package/dist/assets/web/dist/assets/ocaml-O90oeIOV.js +1 -0
  227. package/dist/assets/web/dist/assets/odin-B1RWQWA5.js +1 -0
  228. package/dist/assets/web/dist/assets/one-dark-pro-CLwyXe_n.js +1 -0
  229. package/dist/assets/web/dist/assets/one-light-D7Lr4KcI.js +1 -0
  230. package/dist/assets/web/dist/assets/openscad-BUDT5pXO.js +1 -0
  231. package/dist/assets/web/dist/assets/org-F0uiwGvq.js +1 -0
  232. package/dist/assets/web/dist/assets/pascal-4ZHwLPI5.js +1 -0
  233. package/dist/assets/web/dist/assets/perl-B0euczkl.js +1 -0
  234. package/dist/assets/web/dist/assets/php-DkL3k_n7.js +1 -0
  235. package/dist/assets/web/dist/assets/pierre-dark-CpLgRqie.js +1 -0
  236. package/dist/assets/web/dist/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  237. package/dist/assets/web/dist/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  238. package/dist/assets/web/dist/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  239. package/dist/assets/web/dist/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  240. package/dist/assets/web/dist/assets/pierre-light-CoaEpmwp.js +1 -0
  241. package/dist/assets/web/dist/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  242. package/dist/assets/web/dist/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  243. package/dist/assets/web/dist/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  244. package/dist/assets/web/dist/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  245. package/dist/assets/web/dist/assets/pkl-ot-7Btpt.js +1 -0
  246. package/dist/assets/web/dist/assets/plastic-DQwYfKfQ.js +1 -0
  247. package/dist/assets/web/dist/assets/plsql-DGHpHOYJ.js +1 -0
  248. package/dist/assets/web/dist/assets/po-BiJDBrnU.js +1 -0
  249. package/dist/assets/web/dist/assets/poimandres-DRFjx7u4.js +1 -0
  250. package/dist/assets/web/dist/assets/polar-C7UOKdEL.js +1 -0
  251. package/dist/assets/web/dist/assets/postcss-BXeXVLqQ.js +1 -0
  252. package/dist/assets/web/dist/assets/powerquery-DNMTfnFr.js +1 -0
  253. package/dist/assets/web/dist/assets/powershell-CCSX75yM.js +1 -0
  254. package/dist/assets/web/dist/assets/prisma-DMgNhnYS.js +1 -0
  255. package/dist/assets/web/dist/assets/prolog-iXnhIJG7.js +1 -0
  256. package/dist/assets/web/dist/assets/proto-DB4EqR-F.js +1 -0
  257. package/dist/assets/web/dist/assets/pug-L_OjjZP4.js +1 -0
  258. package/dist/assets/web/dist/assets/puppet-CDv2pdJW.js +1 -0
  259. package/dist/assets/web/dist/assets/purescript-9MfHhQsQ.js +1 -0
  260. package/dist/assets/web/dist/assets/python-gzcpVVnB.js +1 -0
  261. package/dist/assets/web/dist/assets/qml-BMjB00Zz.js +1 -0
  262. package/dist/assets/web/dist/assets/qmldir-DCQb3MpD.js +1 -0
  263. package/dist/assets/web/dist/assets/qss-Fe1Jh2GI.js +1 -0
  264. package/dist/assets/web/dist/assets/r-DoeLdnqR.js +1 -0
  265. package/dist/assets/web/dist/assets/racket-DcIDlBhZ.js +1 -0
  266. package/dist/assets/web/dist/assets/raku-B3gFvitq.js +1 -0
  267. package/dist/assets/web/dist/assets/razor-BzYNkAWy.js +1 -0
  268. package/dist/assets/web/dist/assets/rbs-C0MpOrj4.js +1 -0
  269. package/dist/assets/web/dist/assets/red-DX1BQOs8.js +1 -0
  270. package/dist/assets/web/dist/assets/reg-CRGYupPL.js +1 -0
  271. package/dist/assets/web/dist/assets/regexp-CPmElMk3.js +1 -0
  272. package/dist/assets/web/dist/assets/rel-BtDbiS_P.js +1 -0
  273. package/dist/assets/web/dist/assets/riscv-Ckw8ddFX.js +1 -0
  274. package/dist/assets/web/dist/assets/ron-VUp2lXgN.js +1 -0
  275. package/dist/assets/web/dist/assets/rose-pine-aMsaMD1D.js +1 -0
  276. package/dist/assets/web/dist/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
  277. package/dist/assets/web/dist/assets/rose-pine-moon-DO-qXg-I.js +1 -0
  278. package/dist/assets/web/dist/assets/rosmsg-CAekHB0j.js +1 -0
  279. package/dist/assets/web/dist/assets/rst-DrsjgfI7.js +1 -0
  280. package/dist/assets/web/dist/assets/ruby-cB42ppvx.js +1 -0
  281. package/dist/assets/web/dist/assets/rust-Cfkwpbl8.js +1 -0
  282. package/dist/assets/web/dist/assets/sas-R5Y3NM_I.js +1 -0
  283. package/dist/assets/web/dist/assets/sass-DXrisJhu.js +1 -0
  284. package/dist/assets/web/dist/assets/scala-BiFt_7mu.js +1 -0
  285. package/dist/assets/web/dist/assets/scheme-DQCgrYNe.js +1 -0
  286. package/dist/assets/web/dist/assets/scss-C-zWxV9s.js +1 -0
  287. package/dist/assets/web/dist/assets/sdbl-bTVj8UrX.js +1 -0
  288. package/dist/assets/web/dist/assets/seed-DeKSHrt2.js +6 -0
  289. package/dist/assets/web/dist/assets/shaderlab-TOUzSsQk.js +1 -0
  290. package/dist/assets/web/dist/assets/shellscript-oJF96aAU.js +1 -0
  291. package/dist/assets/web/dist/assets/shellsession-CznECKyi.js +1 -0
  292. package/dist/assets/web/dist/assets/slack-dark-DnToyrRv.js +1 -0
  293. package/dist/assets/web/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
  294. package/dist/assets/web/dist/assets/smalltalk-BHpQb5nM.js +1 -0
  295. package/dist/assets/web/dist/assets/smithy-Dgaz7aur.js +1 -0
  296. package/dist/assets/web/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
  297. package/dist/assets/web/dist/assets/solarized-dark-UdsmMDCL.js +1 -0
  298. package/dist/assets/web/dist/assets/solarized-light-C1IUL_tW.js +1 -0
  299. package/dist/assets/web/dist/assets/solidity-Bj7E1mRn.js +1 -0
  300. package/dist/assets/web/dist/assets/soy-wjHLSRag.js +1 -0
  301. package/dist/assets/web/dist/assets/sparql-D_iOobhT.js +1 -0
  302. package/dist/assets/web/dist/assets/splunk-BC2Px7Mm.js +1 -0
  303. package/dist/assets/web/dist/assets/sql-C_BM8IOW.js +1 -0
  304. package/dist/assets/web/dist/assets/ssh-config-BgfXC-Er.js +1 -0
  305. package/dist/assets/web/dist/assets/stata-D31HO8aQ.js +1 -0
  306. package/dist/assets/web/dist/assets/stylus-B6D30XZt.js +1 -0
  307. package/dist/assets/web/dist/assets/surrealql-CKCLyplA.js +1 -0
  308. package/dist/assets/web/dist/assets/svelte-3geWk2iu.js +1 -0
  309. package/dist/assets/web/dist/assets/swift-CyEgAFGc.js +1 -0
  310. package/dist/assets/web/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
  311. package/dist/assets/web/dist/assets/system-verilog-Dh5ihWbf.js +1 -0
  312. package/dist/assets/web/dist/assets/systemd-BxMlprV5.js +1 -0
  313. package/dist/assets/web/dist/assets/talonscript-CohzipZa.js +1 -0
  314. package/dist/assets/web/dist/assets/tasl-DMoTqEGO.js +1 -0
  315. package/dist/assets/web/dist/assets/tcl-CZd0xW_V.js +1 -0
  316. package/dist/assets/web/dist/assets/templ-DiJr_hTD.js +1 -0
  317. package/dist/assets/web/dist/assets/terraform-DswuEJGm.js +1 -0
  318. package/dist/assets/web/dist/assets/tex-BEJGWyeF.js +1 -0
  319. package/dist/assets/web/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
  320. package/dist/assets/web/dist/assets/toml-CcmNWLt0.js +1 -0
  321. package/dist/assets/web/dist/assets/ts-tags-C8Mzdpxx.js +1 -0
  322. package/dist/assets/web/dist/assets/tsv-sltzmVWM.js +1 -0
  323. package/dist/assets/web/dist/assets/tsx-UQiSm_p3.js +1 -0
  324. package/dist/assets/web/dist/assets/turtle-ByJddavk.js +1 -0
  325. package/dist/assets/web/dist/assets/twig-_Pvzkeo9.js +1 -0
  326. package/dist/assets/web/dist/assets/typescript-BWMkINKK.js +1 -0
  327. package/dist/assets/web/dist/assets/typespec-BRdr0IET.js +1 -0
  328. package/dist/assets/web/dist/assets/typst-Bdg9m-e7.js +1 -0
  329. package/dist/assets/web/dist/assets/v-DBj4K9d_.js +1 -0
  330. package/dist/assets/web/dist/assets/vala-zf12oZj6.js +1 -0
  331. package/dist/assets/web/dist/assets/vb-DOwzaHZ0.js +1 -0
  332. package/dist/assets/web/dist/assets/verilog-B2gwz-sJ.js +1 -0
  333. package/dist/assets/web/dist/assets/vesper-DdrHHSXu.js +1 -0
  334. package/dist/assets/web/dist/assets/vhdl-BroJfC0k.js +1 -0
  335. package/dist/assets/web/dist/assets/viml-DvXPmvsu.js +1 -0
  336. package/dist/assets/web/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
  337. package/dist/assets/web/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
  338. package/dist/assets/web/dist/assets/vitesse-light-VbXTXTou.js +1 -0
  339. package/dist/assets/web/dist/assets/vue-CDZrrAZ8.js +1 -0
  340. package/dist/assets/web/dist/assets/vue-html-tAlpNhfN.js +1 -0
  341. package/dist/assets/web/dist/assets/vue-vine-C27UF_rh.js +1 -0
  342. package/dist/assets/web/dist/assets/vyper-CgoNMtux.js +1 -0
  343. package/dist/assets/web/dist/assets/wallClock-61NpVkY6.js +1 -0
  344. package/dist/assets/web/dist/assets/wasm-BnjxR4X6.js +1 -0
  345. package/dist/assets/web/dist/assets/wasm-ByWQv1Qj.js +1 -0
  346. package/dist/assets/web/dist/assets/wenyan-C8pVoKbM.js +1 -0
  347. package/dist/assets/web/dist/assets/wgsl-BsKzXJz4.js +1 -0
  348. package/dist/assets/web/dist/assets/wikitext-ClFFjSW2.js +1 -0
  349. package/dist/assets/web/dist/assets/wit-DdvCle-K.js +1 -0
  350. package/dist/assets/web/dist/assets/wolfram-DLL8P-h_.js +1 -0
  351. package/dist/assets/web/dist/assets/xml-CxeDr9Zh.js +1 -0
  352. package/dist/assets/web/dist/assets/xsl-Bpi0uUnM.js +1 -0
  353. package/dist/assets/web/dist/assets/yaml-BtUgZ1GN.js +1 -0
  354. package/dist/assets/web/dist/assets/zenscript-BnlCZFoB.js +1 -0
  355. package/dist/assets/web/dist/assets/zig-CMLA9XwU.js +1 -0
  356. package/dist/cli.js +1120 -252
  357. package/package.json +1 -1
  358. package/dist/assets/web/dist/assets/AppShell-Bw3-_TTE.js +0 -1
  359. package/dist/assets/web/dist/assets/DeliveryPage-BRwcQyr7.js +0 -1
  360. package/dist/assets/web/dist/assets/NotFoundPage-RjH-9dPy.js +0 -1
  361. package/dist/assets/web/dist/assets/ResidentDetailPage-BRy5wkv9.js +0 -1
  362. package/dist/assets/web/dist/assets/ResidentsIndexPage-DAEVLN8j.js +0 -1
  363. package/dist/assets/web/dist/assets/RunRoutePage-B1KHmkZ9.js +0 -126
  364. package/dist/assets/web/dist/assets/RunRoutePage-CdbrbqU2.css +0 -1
  365. package/dist/assets/web/dist/assets/RunsIndexPage-3hUWFpFV.js +0 -1
  366. package/dist/assets/web/dist/assets/RunsTabs-Qn_5TVJU.js +0 -1
  367. package/dist/assets/web/dist/assets/ScheduledPage-DysVvVm1.js +0 -1
  368. package/dist/assets/web/dist/assets/StatusDot-Bug2a6T6.js +0 -1
  369. package/dist/assets/web/dist/assets/Tooltip-C2eEUbwn.js +0 -1
  370. package/dist/assets/web/dist/assets/main-0uhp-taL.js +0 -28
  371. package/dist/assets/web/dist/assets/main-Xickfv8V.css +0 -1
  372. package/dist/assets/web/dist/assets/seed-B8mQEhhw.js +0 -6
  373. 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");
@@ -1485,6 +1521,9 @@ var init_contract = __esm({
1485
1521
  });
1486
1522
 
1487
1523
  // ../../src/agents/registry.ts
1524
+ function machineNeedsRepo(machine) {
1525
+ return machine === "repo-cold" || machine === "repo-resident";
1526
+ }
1488
1527
  function getAgent(name) {
1489
1528
  const a = AGENTS[name];
1490
1529
  if (!a) {
@@ -1492,11 +1531,14 @@ function getAgent(name) {
1492
1531
  }
1493
1532
  return a;
1494
1533
  }
1495
- 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;
1496
1535
  var init_registry = __esm({
1497
1536
  "../../src/agents/registry.ts"() {
1498
1537
  "use strict";
1538
+ init_bashTimeout();
1499
1539
  init_contract();
1540
+ MACHINE_CLASSES = ["none", "blank", "repo-cold", "repo-resident"];
1541
+ IDENTITIES = ["none", "read", "write"];
1500
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.
1501
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.
1502
1544
  - **title**: the PR title \u2014 one line naming the change, specific enough to pick out of a PR list.
@@ -1648,18 +1690,34 @@ Answer directly and concisely. Use Slack-friendly formatting (no markdown header
1648
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.
1649
1691
 
1650
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.`;
1651
1706
  AGENTS = {
1652
1707
  general: {
1653
1708
  name: "general",
1654
1709
  description: "Default assistant on the configured model: answers directly, reads the org's repos and manages their issues over GitHub, reads URLs. No workspace or shell.",
1655
1710
  system: GENERAL_SYSTEM,
1656
1711
  toolset: "assistant",
1712
+ // The GitHub tools are REST in the bot process, so a general ask never
1713
+ // provisions a workspace or sandbox (docs/reference/specs/agent-general.md item 4)
1714
+ // and mints no credential of its own.
1715
+ machine: "none",
1716
+ identity: "none",
1657
1717
  maxTurns: 8,
1658
1718
  // a repo read is 2-3 calls (repos → tree → file); an issue action 1-2; still fast
1659
1719
  maxTokens: 16e3,
1660
1720
  maxMinutes: 5
1661
- // No `resources`: the GitHub tools are REST in the bot process, so a
1662
- // general ask still never provisions a workspace or sandbox (item 4).
1663
1721
  },
1664
1722
  coding: {
1665
1723
  name: "coding",
@@ -1677,7 +1735,9 @@ You cannot run commands, clone repositories, edit code, or review pull requests,
1677
1735
  cacheTtl: "1h",
1678
1736
  // No built-in effort: the deployment decides (`defaults.efforts.coding`,
1679
1737
  // `config set channel efforts.coding=…`, or `effort:` per request).
1680
- resources: { repo: "required" }
1738
+ machine: "repo-resident",
1739
+ identity: "write"
1740
+ // pushes branches and opens pull requests
1681
1741
  },
1682
1742
  review: {
1683
1743
  name: "review",
@@ -1685,7 +1745,9 @@ You cannot run commands, clone repositories, edit code, or review pull requests,
1685
1745
  system: REVIEW_SYSTEM,
1686
1746
  residentSystem: REVIEW_SYSTEM_RESIDENT,
1687
1747
  toolset: "readonly",
1688
- resources: { repo: "required" },
1748
+ machine: "repo-resident",
1749
+ identity: "read",
1750
+ // a read-scoped token and a read-only worktree: it cannot post or push from inside
1689
1751
  maxTurns: 30,
1690
1752
  // backstop only; wall clock is the real budget (12 bound at ~4 min in practice)
1691
1753
  maxTokens: 64e3,
@@ -1701,27 +1763,52 @@ You cannot run commands, clone repositories, edit code, or review pull requests,
1701
1763
  // pipeline orchestrator (src/core/shipPipeline.ts), whose child rounds run
1702
1764
  // on the coding/review defs above — runAgent is never called with THIS def.
1703
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.",
1704
- // Full toolset so a ship thread provisions a writable workspace class like
1705
- // coding; placeholder budgets — the pipeline is bounded by the `ship` config
1706
- // caps and by each child's own budgets clipped to the remaining wall clock,
1707
- // never by these numbers.
1766
+ // Full toolset and the coding machine class, so repo and PR resolution
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.
1708
1774
  toolset: "full",
1775
+ machine: "repo-resident",
1776
+ identity: "write",
1709
1777
  maxTurns: 1,
1710
1778
  maxTokens: 16e3,
1711
- maxMinutes: 5,
1712
- resources: { repo: "required" }
1779
+ maxMinutes: 120
1713
1780
  },
1714
1781
  research: {
1715
1782
  name: "research",
1716
1783
  description: "Answers questions with web search, URL reading, and read access to the org's repos and issues over GitHub. No workspace.",
1717
1784
  system: RESEARCH_SYSTEM,
1718
1785
  toolset: "web",
1719
- resources: { repo: "none" },
1786
+ machine: "none",
1720
1787
  // web I/O only; no workspace is provisioned
1788
+ identity: "none",
1721
1789
  maxTurns: 12,
1722
1790
  maxTokens: 24e3,
1723
1791
  maxMinutes: 8,
1724
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.
1725
1812
  }
1726
1813
  };
1727
1814
  }
@@ -2010,8 +2097,8 @@ var init_web = __esm({
2010
2097
  "../../src/tools/web.ts"() {
2011
2098
  "use strict";
2012
2099
  WebSearchUnavailableError = class extends Error {
2013
- constructor(message) {
2014
- super(message);
2100
+ constructor(message2) {
2101
+ super(message2);
2015
2102
  this.name = "WebSearchUnavailableError";
2016
2103
  }
2017
2104
  };
@@ -2049,8 +2136,8 @@ var init_web = __esm({
2049
2136
  return records.map((r) => r.address);
2050
2137
  };
2051
2138
  BlockedUrlError = class extends Error {
2052
- constructor(message) {
2053
- super(message);
2139
+ constructor(message2) {
2140
+ super(message2);
2054
2141
  this.name = "BlockedUrlError";
2055
2142
  }
2056
2143
  };
@@ -2319,6 +2406,38 @@ function validateScopeEfforts(layer, source2) {
2319
2406
  }
2320
2407
  }
2321
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
+ }
2322
2441
  function validateMcpServers(layer, source2) {
2323
2442
  const check = (path, tier, servers) => {
2324
2443
  if (servers === void 0) return;
@@ -2371,6 +2490,7 @@ function validateMcpServers(layer, source2) {
2371
2490
  function validateConfig(cfg) {
2372
2491
  for (const key of unknownKeys(cfg, CONFIG_KEYS)) throw new Error(`config.yaml: unknown key \`${key}\``);
2373
2492
  validateScopeEfforts(cfg, "config.yaml");
2493
+ validateBoundaries(cfg, "config.yaml");
2374
2494
  validateMcpServers(cfg, "config.yaml");
2375
2495
  if (typeof cfg.organization !== "string" || cfg.organization.trim() === "") {
2376
2496
  throw new Error(
@@ -2470,7 +2590,7 @@ function validateRuntimeOverrides(ro) {
2470
2590
  }
2471
2591
  }
2472
2592
  }
2473
- var MAX_INSTRUCTIONS_LENGTH, CONFIG_KEYS, SELF_IMPROVEMENT_KEYS;
2593
+ var MAX_INSTRUCTIONS_LENGTH, CONFIG_KEYS, SELF_IMPROVEMENT_KEYS, BOUNDARY_KEYS, MIN_BOUNDARY_MINUTES;
2474
2594
  var init_validate = __esm({
2475
2595
  "../../src/config/validate.ts"() {
2476
2596
  "use strict";
@@ -2512,6 +2632,90 @@ var init_validate = __esm({
2512
2632
  minRuns: true,
2513
2633
  top: true
2514
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 };
2515
2719
  }
2516
2720
  });
2517
2721
 
@@ -2590,6 +2794,7 @@ function formatConfigDescription(d) {
2590
2794
  const orgMcp = d.org?.mcpServers && Object.keys(d.org.mcpServers).length > 0 ? `, mcp ${Object.keys(d.org.mcpServers).map((n2) => `\`${n2}\``).join(" ")}` : "";
2591
2795
  const lines = [
2592
2796
  `*Effective for you in this channel:* ${effective}`,
2797
+ ...d.effective.boundary ? [`*Effective boundary:* ${fmtEffectiveBoundary(d.effective.boundary)}`] : [],
2593
2798
  `*Defaults:* ${defaults}${orgMcp}`,
2594
2799
  `*Channel scope:* ${fmtScope(d.channel)}`,
2595
2800
  `*Your scope:* ${fmtScope(d.user)}`
@@ -2614,8 +2819,26 @@ function fmtScope(s) {
2614
2819
  parts.push(
2615
2820
  `mcp ${Object.keys(s.mcpServers).map((n2) => `\`${n2}\``).join(" ")}`
2616
2821
  );
2822
+ if (s.boundary) parts.push(`boundary ${fmtBoundary(s.boundary)}`);
2617
2823
  return parts.length > 0 ? parts.join(", ") : "_none_";
2618
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
+ }
2619
2842
  function fmtModels(m) {
2620
2843
  return Object.entries(m).map(([k, v]) => `\`${k}=${v}\``).join(" ");
2621
2844
  }
@@ -2632,6 +2855,7 @@ var init_config = __esm({
2632
2855
  init_schedules();
2633
2856
  init_registry();
2634
2857
  init_validate();
2858
+ init_profile();
2635
2859
  FileOverridesBacking = class {
2636
2860
  path;
2637
2861
  constructor(path) {
@@ -2752,6 +2976,7 @@ var init_config = __esm({
2752
2976
  doc.users ??= {};
2753
2977
  validateInstructions(doc, `overrides (${this.backing.describe()})`);
2754
2978
  validateScopeEfforts(doc, `overrides (${this.backing.describe()})`);
2979
+ validateBoundaries(doc, `overrides (${this.backing.describe()})`);
2755
2980
  validateMcpServers(
2756
2981
  { channels: doc.channels, users: doc.users, defaults: doc.org },
2757
2982
  `overrides (${this.backing.describe()})`
@@ -2843,22 +3068,43 @@ var init_config = __esm({
2843
3068
  return { channel: this.channelScope(channelId), user: this.userScope(userId) };
2844
3069
  }
2845
3070
  /**
2846
- * Resolve which agent, model, and effort serve a request.
2847
- * Agent: request directive > user scope > channel scope > default.
2848
- * Model: request directive > (user > channel) forced model
2849
- * > (user > channel > defaults) per-agent model.
2850
- * 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.
2851
3081
  */
2852
3082
  resolve(opts) {
2853
3083
  const ch = this.channelScope(opts.channelId);
2854
3084
  const us = this.userScope(opts.userId);
2855
3085
  const agentName = opts.request.agent ?? us.agent ?? ch.agent ?? this.config.defaults.agent;
3086
+ const boundary = intersectBoundaries(this.boundaryLayers(ch, us));
2856
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"];
2857
3088
  if (!modelRef2) {
2858
3089
  throw new Error(`No model configured for agent "${agentName}" \u2014 set defaults.models.${agentName} in config.yaml`);
2859
3090
  }
2860
3091
  const effort2 = opts.request.effort ?? us.effort ?? ch.effort ?? us.efforts?.[agentName] ?? ch.efforts?.[agentName] ?? this.config.defaults.efforts?.[agentName];
2861
- 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 }] : []);
2862
3108
  }
2863
3109
  // ---- authorization gates ----------------------------------------------------
2864
3110
  // Every gate reads the grants table (authorization.md item 9): what an actor
@@ -2983,7 +3229,8 @@ var init_config = __esm({
2983
3229
  effective: {
2984
3230
  agent: resolved.agentName,
2985
3231
  model: resolved.modelRef,
2986
- ...resolved.effort ? { effort: resolved.effort } : {}
3232
+ ...resolved.effort ? { effort: resolved.effort } : {},
3233
+ ...resolved.boundary ? { boundary: resolved.boundary } : {}
2987
3234
  },
2988
3235
  defaults: {
2989
3236
  agent: this.config.defaults.agent,
@@ -3967,8 +4214,8 @@ var init_tracer = __esm({
3967
4214
  delete this.rec.errorMessage;
3968
4215
  return;
3969
4216
  }
3970
- const message = err2 instanceof Error ? err2.message : String(err2);
3971
- 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);
3972
4219
  }
3973
4220
  setAttrs(attrs) {
3974
4221
  this.rec.attrs = { ...this.rec.attrs, ...attrs };
@@ -4187,7 +4434,7 @@ function isExampleProfile(p) {
4187
4434
  return p.account === EXAMPLE_ACCOUNT;
4188
4435
  }
4189
4436
  var WORKER_KINDS, PROFILE_PATH, PROFILE_EXAMPLE_PATH, PROFILE_ENV, hostname, endpoint, source, IMAGE_MODES, profileSchema, EXAMPLE_ACCOUNT;
4190
- var init_profile = __esm({
4437
+ var init_profile2 = __esm({
4191
4438
  "../../src/deploy/profile.ts"() {
4192
4439
  "use strict";
4193
4440
  WORKER_KINDS = ["memory", "bot", "resident", "sandbox"];
@@ -4452,7 +4699,7 @@ var init_plan = __esm({
4452
4699
  init_configDocument();
4453
4700
  init_images();
4454
4701
  init_operatorRoot();
4455
- init_profile();
4702
+ init_profile2();
4456
4703
  UNSET_ENV = ["CLOUDFLARE_ACCOUNT_ID"];
4457
4704
  SANDBOX_CONTAINER_CLASS = "SwitchboardSandbox";
4458
4705
  SANDBOX_BEARER_ENV = "SANDBOX_TOKEN";
@@ -5203,7 +5450,7 @@ var init_wranglerTemplate = __esm({
5203
5450
  "use strict";
5204
5451
  init_images();
5205
5452
  init_plan();
5206
- init_profile();
5453
+ init_profile2();
5207
5454
  init_images();
5208
5455
  TEMPLATE_FILE = "wrangler.template.jsonc";
5209
5456
  RENDERED_FILE = "wrangler.jsonc";
@@ -5402,7 +5649,7 @@ var init_restart = __esm({
5402
5649
  init_ingressTokens();
5403
5650
  init_authorize();
5404
5651
  init_liveGate();
5405
- init_profile();
5652
+ init_profile2();
5406
5653
  RESTART_TOKEN_ENV = "SWITCHBOARD_DEPLOY_TOKEN";
5407
5654
  RESTART_SCOPE = "deploy:write";
5408
5655
  RESTART_AUTHORIZE_PATH = "/admin/restart/authorize";
@@ -6383,7 +6630,7 @@ var init_run = __esm({
6383
6630
  init_wranglerTemplate();
6384
6631
  init_configSource();
6385
6632
  init_images();
6386
- init_profile();
6633
+ init_profile2();
6387
6634
  init_restart();
6388
6635
  init_wranglerTemplate();
6389
6636
  init_sandboxLiveGate();
@@ -6865,8 +7112,8 @@ var init_registryTransferHost = __esm({
6865
7112
  SHORT_MS = 6e4;
6866
7113
  LONG_MS = 30 * 6e4;
6867
7114
  TransferProblem = class extends Error {
6868
- constructor(message, unauthorized = false) {
6869
- super(message);
7115
+ constructor(message2, unauthorized = false) {
7116
+ super(message2);
6870
7117
  this.unauthorized = unauthorized;
6871
7118
  }
6872
7119
  unauthorized;
@@ -7493,7 +7740,7 @@ var TEMPLATE_PATHS;
7493
7740
  var init_host3 = __esm({
7494
7741
  "../../src/setup/host.ts"() {
7495
7742
  "use strict";
7496
- init_profile();
7743
+ init_profile2();
7497
7744
  init_packageRoot();
7498
7745
  init_host();
7499
7746
  init_meatProcess();
@@ -7505,42 +7752,6 @@ var init_host3 = __esm({
7505
7752
  }
7506
7753
  });
7507
7754
 
7508
- // ../../src/execution/bashTimeout.ts
7509
- function clampBashTimeout(requested) {
7510
- if (typeof requested !== "number" || !Number.isFinite(requested)) return BASH_TIMEOUT_MS;
7511
- return Math.min(Math.max(Math.trunc(requested), BASH_TIMEOUT_MIN_MS), BASH_TIMEOUT_MAX_MS);
7512
- }
7513
- function bashBudgetWithinRun(wantedMs, remainingMs) {
7514
- const secs = (ms) => Math.max(0, Math.round(ms / 1e3));
7515
- const cap = Math.trunc(remainingMs - RUN_DEADLINE_RESERVE_MS);
7516
- if (cap < BASH_TIMEOUT_MIN_MS) {
7517
- return {
7518
- kind: "exhausted",
7519
- 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`
7520
- };
7521
- }
7522
- if (cap >= wantedMs) return { kind: "unchanged" };
7523
- return {
7524
- kind: "clipped",
7525
- timeoutMs: cap,
7526
- note: `[timeout clipped to ${secs(cap)}s \u2014 the run's wall clock ends in ${secs(remainingMs)}s]`
7527
- };
7528
- }
7529
- function bashTimeoutNote(timeoutMs) {
7530
- 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)`;
7531
- }
7532
- var BASH_TIMEOUT_MS, BASH_TIMEOUT_MIN_MS, BASH_TIMEOUT_MAX_MS, RUN_DEADLINE_RESERVE_MS, EXEC_CALL_MARGIN_MS;
7533
- var init_bashTimeout = __esm({
7534
- "../../src/execution/bashTimeout.ts"() {
7535
- "use strict";
7536
- BASH_TIMEOUT_MS = 5 * 6e4;
7537
- BASH_TIMEOUT_MIN_MS = 1e3;
7538
- BASH_TIMEOUT_MAX_MS = 20 * 6e4;
7539
- RUN_DEADLINE_RESERVE_MS = 6e4;
7540
- EXEC_CALL_MARGIN_MS = 3e4;
7541
- }
7542
- });
7543
-
7544
7755
  // ../../src/execution/executor.ts
7545
7756
  import { execFile } from "node:child_process";
7546
7757
  import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
@@ -7598,15 +7809,15 @@ var init_executor = __esm({
7598
7809
  init_bashTimeout();
7599
7810
  ExecInfraError = class extends Error {
7600
7811
  infra = true;
7601
- constructor(message) {
7602
- super(message);
7812
+ constructor(message2) {
7813
+ super(message2);
7603
7814
  this.name = "ExecInfraError";
7604
7815
  }
7605
7816
  };
7606
7817
  ExecCapacityError = class extends Error {
7607
7818
  capacity = true;
7608
- constructor(message) {
7609
- super(message);
7819
+ constructor(message2) {
7820
+ super(message2);
7610
7821
  this.name = "ExecCapacityError";
7611
7822
  }
7612
7823
  };
@@ -8901,9 +9112,19 @@ var init_residentState = __esm({
8901
9112
  import { resolve as resolve7 } from "node:path";
8902
9113
  import { mkdirSync as mkdirSync7 } from "node:fs";
8903
9114
  async function makeExecutor(opts, ctx, span) {
8904
- if (ctx.agent.resources?.repo !== "required") {
9115
+ const machine = ctx.profile.machine;
9116
+ if (machine === "none") {
8905
9117
  return { executor: new NullExecutor(ctx.agent.name), backend: "local" };
8906
9118
  }
9119
+ if (machine === "blank") {
9120
+ return {
9121
+ executor: await makePerThreadExecutor(opts, { threadKey: ctx.threadKey, resolveEnvs: async () => ({}) }),
9122
+ backend: perThreadBackend(opts)
9123
+ };
9124
+ }
9125
+ if (machine === "repo-cold") {
9126
+ return { executor: await makePerThreadExecutor(opts, perThreadCheckout(ctx)), backend: perThreadBackend(opts) };
9127
+ }
8907
9128
  let note;
8908
9129
  let failedAttach;
8909
9130
  if (ctx.repo && opts.execution?.resident) {
@@ -8916,7 +9137,7 @@ async function makeExecutor(opts, ctx, span) {
8916
9137
  if (probe.kind === "status" && isServiceable(probe.state, probe.reason)) {
8917
9138
  try {
8918
9139
  const nonWarm = probe.state === "warm" ? void 0 : oneLine(`${probe.state}${probe.reason ? ` (${probe.reason})` : ""}`);
8919
- const readonly = ctx.agent.toolset === "readonly" ? true : void 0;
9140
+ const readonly = ctx.profile.identity === "read" ? true : void 0;
8920
9141
  return await openResident(
8921
9142
  {
8922
9143
  baseUrl: resident.baseUrl,
@@ -8946,7 +9167,7 @@ async function makeExecutor(opts, ctx, span) {
8946
9167
  }
8947
9168
  }
8948
9169
  return {
8949
- executor: await makePerThreadExecutor(opts, ctx),
9170
+ executor: await makePerThreadExecutor(opts, perThreadCheckout(ctx)),
8950
9171
  note,
8951
9172
  backend: perThreadBackend(opts),
8952
9173
  ...failedAttach ? { trace: failedAttach.steps } : {}
@@ -9041,8 +9262,16 @@ function localWorkspaceDir(baseDir, threadKey) {
9041
9262
  const safe = threadKey.replace(/[^a-zA-Z0-9_.-]/g, "_");
9042
9263
  return resolve7(baseDir, safe);
9043
9264
  }
9044
- async function makePerThreadExecutor(opts, ctx) {
9045
- const { threadKey } = ctx;
9265
+ function perThreadCheckout(ctx) {
9266
+ return {
9267
+ threadKey: ctx.threadKey,
9268
+ repo: ctx.repo,
9269
+ ref: ctx.ref,
9270
+ resolveEnvs: () => githubEnvs(ctx.profile.identity)
9271
+ };
9272
+ }
9273
+ async function makePerThreadExecutor(opts, input) {
9274
+ const { threadKey } = input;
9046
9275
  const type = opts.execution?.type ?? "local";
9047
9276
  if (type === "local") {
9048
9277
  const dir = localWorkspaceDir(opts.workspaceDir, threadKey);
@@ -9058,9 +9287,9 @@ async function makePerThreadExecutor(opts, ctx) {
9058
9287
  threadKey,
9059
9288
  timeoutMs: (opts.execution?.timeoutMinutes ?? 30) * 6e4,
9060
9289
  statePath: resolve7(opts.dataDir, "sandboxes.json"),
9061
- resolveEnvs: () => githubEnvs(ctx.agent),
9062
- repo: ctx.repo,
9063
- ref: ctx.ref
9290
+ resolveEnvs: input.resolveEnvs,
9291
+ repo: input.repo,
9292
+ ref: input.ref
9064
9293
  });
9065
9294
  }
9066
9295
  if (type === "cloudflare") {
@@ -9074,15 +9303,19 @@ async function makePerThreadExecutor(opts, ctx) {
9074
9303
  url: opts.execution.url,
9075
9304
  token: token.reveal(),
9076
9305
  threadKey,
9077
- resolveEnvs: () => githubEnvs(ctx.agent),
9078
- repo: ctx.repo,
9079
- ref: ctx.ref
9306
+ resolveEnvs: input.resolveEnvs,
9307
+ repo: input.repo,
9308
+ ref: input.ref
9080
9309
  });
9081
9310
  }
9082
9311
  throw new Error(`Unknown execution.type "${type}" (valid: local, e2b, cloudflare)`);
9083
9312
  }
9084
- async function githubEnvs(agent) {
9085
- const scope = agent.toolset === "readonly" ? "read" : "write";
9313
+ function githubTokenScopeFor(identity2) {
9314
+ return identity2 === "none" ? void 0 : identity2;
9315
+ }
9316
+ async function githubEnvs(identity2) {
9317
+ const scope = githubTokenScopeFor(identity2);
9318
+ if (scope === void 0) return {};
9086
9319
  const token = await resolveGithubToken(scope);
9087
9320
  return token ? { GH_TOKEN: token } : {};
9088
9321
  }
@@ -9113,7 +9346,7 @@ var init_factory = __esm({
9113
9346
  agentName;
9114
9347
  fail() {
9115
9348
  throw new Error(
9116
- `Agent "${this.agentName}" declares no repo resource, so it has no execution workspace. Declare resources: { repo: "required" } on the agent if its tools need one.`
9349
+ `Agent "${this.agentName}" runs on machine class "none", so it has no execution workspace. Declare a machine class that provisions one (\`repo-resident\`, \`repo-cold\` or \`blank\`) if its tools need one.`
9117
9350
  );
9118
9351
  }
9119
9352
  async exec() {
@@ -9151,9 +9384,9 @@ function parseFindings(body) {
9151
9384
  function isAgentLogin(login, identities) {
9152
9385
  return login.endsWith("[bot]") || identities.agentLogins.includes(login);
9153
9386
  }
9154
- function coauthorsOf(message) {
9387
+ function coauthorsOf(message2) {
9155
9388
  const out = [];
9156
- for (const line3 of message.split("\n")) {
9389
+ for (const line3 of message2.split("\n")) {
9157
9390
  const m = /^\s*co-authored-by:\s*(.+?)\s*$/i.exec(line3);
9158
9391
  if (m) out.push(m[1]);
9159
9392
  }
@@ -9346,9 +9579,25 @@ function indicatorLines(x) {
9346
9579
  `agent runs: ${x.agentRuns.count} \xB7 ${x.agentRuns.minutes.toFixed(1)} min`
9347
9580
  ];
9348
9581
  }
9349
- 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()) {
9350
9596
  const lines = [
9351
- `${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 ")
9352
9601
  ];
9353
9602
  if (report2.truncated) lines.push("(the newest pull requests only \u2014 the fetch stopped before the range's start)");
9354
9603
  for (const w of report2.weeks) {
@@ -9390,6 +9639,18 @@ function stringList(raw, what, valid = () => true) {
9390
9639
  throw new Error(`${what} must be a list of ${what.endsWith("repos") ? "owner/name repositories" : "names"}`);
9391
9640
  return raw;
9392
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
+ }
9393
9654
  function parseDeliveryConfig(raw) {
9394
9655
  if (raw === void 0 || raw === null) return void 0;
9395
9656
  if (typeof raw !== "object" || Array.isArray(raw)) throw new Error("delivery: must be a mapping");
@@ -9398,7 +9659,8 @@ function parseDeliveryConfig(raw) {
9398
9659
  repos: stringList(r.repos, "delivery.repos", (s) => REPO_SLUG.test(s)),
9399
9660
  reviewers: stringList(r.reviewers, "delivery.reviewers"),
9400
9661
  agentLogins: stringList(r.agentLogins, "delivery.agentLogins"),
9401
- agentCoauthors: stringList(r.agentCoauthors, "delivery.agentCoauthors")
9662
+ agentCoauthors: stringList(r.agentCoauthors, "delivery.agentCoauthors"),
9663
+ snapshot: snapshotConfig(r.snapshot)
9402
9664
  };
9403
9665
  }
9404
9666
  function createDeliveryService(cfg, source2, opts = {}) {
@@ -9408,9 +9670,10 @@ function createDeliveryService(cfg, source2, opts = {}) {
9408
9670
  repos: () => [...cfg?.repos ?? []],
9409
9671
  async report(repo, o) {
9410
9672
  if (!REPO_SLUG.test(repo)) throw new Error(`repository must be owner/name, got ${JSON.stringify(repo)}`);
9411
- 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);
9412
9675
  const [fetched, process2, runs] = await Promise.all([
9413
- source2.fetchPullRequests(repo, range),
9676
+ source2.fetchPullRequests(repo, range, o.fresh ? { fresh: true } : void 0),
9414
9677
  opts.identities?.() ?? Promise.resolve({}),
9415
9678
  o.runs?.(range) ?? Promise.resolve([])
9416
9679
  ]);
@@ -9419,18 +9682,22 @@ function createDeliveryService(cfg, source2, opts = {}) {
9419
9682
  agentLogins: [.../* @__PURE__ */ new Set([...cfg?.agentLogins ?? [], ...process2.agentLogins ?? []])],
9420
9683
  agentCoauthors: [.../* @__PURE__ */ new Set([...cfg?.agentCoauthors ?? [], ...process2.agentCoauthors ?? []])]
9421
9684
  };
9422
- return buildDeliveryReport({
9423
- repo,
9424
- range,
9425
- prs: fetched.prs,
9426
- runs,
9427
- identities,
9428
- truncated: fetched.truncated
9429
- });
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
+ };
9430
9697
  }
9431
9698
  };
9432
9699
  }
9433
- 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;
9434
9701
  var init_delivery = __esm({
9435
9702
  "../../src/core/delivery.ts"() {
9436
9703
  "use strict";
@@ -9458,6 +9725,7 @@ var init_delivery = __esm({
9458
9725
  ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
9459
9726
  pct = (share) => share === null ? "\u2014" : `${Math.round(share * 100)}%`;
9460
9727
  hours = (h, digits2 = 1) => h === null ? "\u2014" : `${h.toFixed(digits2)} h`;
9728
+ SNAPSHOT_EVERY_MINUTES = Object.freeze({ default: 60, min: 5, max: 1440 });
9461
9729
  REPO_SLUG = /^(?!\.+\/)[\w.-]+\/(?!\.+$)[\w.-]+$/;
9462
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.";
9463
9731
  NullDeliveryService = class {
@@ -9711,12 +9979,16 @@ var init_githubDelivery = __esm({
9711
9979
  const json = async (res) => res.status === 404 ? void 0 : res.json();
9712
9980
  const merged = [];
9713
9981
  let truncated = false;
9982
+ let oldestUpdated;
9714
9983
  for (let page2 = 1; ; page2++) {
9715
9984
  const res = await get(
9716
9985
  "pulls",
9717
9986
  `/repos/${repo}/pulls?state=closed&sort=updated&direction=desc&per_page=${PAGE_SIZE}&page=${page2}`
9718
9987
  );
9719
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
+ }
9720
9992
  const selected = selectMerged(items, range);
9721
9993
  merged.push(...selected.merged);
9722
9994
  if (selected.olderSeen || items.length < PAGE_SIZE) break;
@@ -9725,6 +9997,7 @@ var init_githubDelivery = __esm({
9725
9997
  break;
9726
9998
  }
9727
9999
  }
10000
+ const completeFrom = truncated && oldestUpdated ? oldestUpdated.updatedAt : `${range.since}T00:00:00Z`;
9728
10001
  const issues = /* @__PURE__ */ new Map();
9729
10002
  const issueOf = (n2) => {
9730
10003
  let p = issues.get(n2);
@@ -9771,12 +10044,257 @@ var init_githubDelivery = __esm({
9771
10044
  ...issue ? { issue } : {}
9772
10045
  };
9773
10046
  });
9774
- return { prs, truncated };
10047
+ return { prs, truncated, completeFrom };
10048
+ }
10049
+ };
10050
+ }
10051
+ });
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;
9775
10186
  }
9776
10187
  };
9777
10188
  }
9778
10189
  });
9779
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
+
9780
10298
  // ../../src/core/runDuration.ts
9781
10299
  function runDurationMs(stamps, now) {
9782
10300
  const end = stamps.finishedAt ?? now;
@@ -10659,8 +11177,8 @@ function bindCommands(registry, deps) {
10659
11177
  settle: (id, value, caller) => registry.settle(id, value, caller, deps)
10660
11178
  };
10661
11179
  }
10662
- function fail(error, message, decidedBy = "registry") {
10663
- 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 };
10664
11182
  }
10665
11183
  function rawInput(input) {
10666
11184
  const args = typeof input === "object" && input !== null && Array.isArray(input.args) ? input.args : [];
@@ -10779,8 +11297,8 @@ var init_commandRegistry = __esm({
10779
11297
  CAMEL_KEY = /^[a-z][A-Za-z0-9]*$/;
10780
11298
  flag = z4.union([z4.boolean(), z4.enum(["true", "false"]).transform((v) => v === "true")]);
10781
11299
  CommandError = class extends Error {
10782
- constructor(code, message) {
10783
- super(message);
11300
+ constructor(code, message2) {
11301
+ super(message2);
10784
11302
  this.code = code;
10785
11303
  this.name = "CommandError";
10786
11304
  }
@@ -10934,12 +11452,13 @@ function summarizeScope(s) {
10934
11452
  function registerConfigCommands(registry) {
10935
11453
  for (const cmd of configCommands) registry.register(cmd);
10936
11454
  }
10937
- 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;
10938
11456
  var init_config3 = __esm({
10939
11457
  "../../src/core/commands/config.ts"() {
10940
11458
  "use strict";
10941
11459
  init_config();
10942
11460
  init_validate();
11461
+ init_registry();
10943
11462
  init_effort();
10944
11463
  init_authorize();
10945
11464
  init_commandRegistry();
@@ -10953,6 +11472,11 @@ var init_config3 = __esm({
10953
11472
  effort = z5.enum(EFFORT_LEVELS);
10954
11473
  effortLevels = `<${EFFORT_LEVELS.join("|")}>`;
10955
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();
10956
11480
  who = (scope) => scope === "channel" ? "channel" : "your";
10957
11481
  configShow = defineCommand2({
10958
11482
  id: "config.show",
@@ -10979,11 +11503,12 @@ var init_config3 = __esm({
10979
11503
  models: z5.record(z5.string(), modelRef).optional().describe("per-agent model: --models.<agent> provider/model"),
10980
11504
  effort: effort.optional().describe(`force a model effort ${effortLevels}`),
10981
11505
  efforts: z5.record(z5.string(), effort).optional().describe(`per-agent effort: --efforts.<agent> ${effortLevels}`),
11506
+ boundary: boundaryOption,
10982
11507
  channel: channelOption
10983
11508
  }),
10984
11509
  action: "config:write",
10985
11510
  effect: "write",
10986
- 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).",
10987
11512
  render: (output) => {
10988
11513
  const o = output;
10989
11514
  return `Updated ${who(o.scope)} scope. Now: ${JSON.stringify(o.effective)}`;
@@ -11012,10 +11537,26 @@ var init_config3 = __esm({
11012
11537
  if (options.models) patch.models = options.models;
11013
11538
  if (options.effort !== void 0) patch.effort = options.effort;
11014
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
+ }
11015
11556
  if (Object.keys(patch).length === 0)
11016
11557
  throw new CommandError(
11017
11558
  "invalid_input",
11018
- "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>"
11019
11560
  );
11020
11561
  let effective;
11021
11562
  if (args.scope === "channel") {
@@ -12225,6 +12766,17 @@ var init_runFriction = __esm({
12225
12766
  });
12226
12767
 
12227
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
+ }
12228
12780
  function storedEventSeqs(events) {
12229
12781
  let prev = 0;
12230
12782
  for (const e of events) {
@@ -12350,6 +12902,7 @@ function isRunRecord(v) {
12350
12902
  return false;
12351
12903
  if (!isOptionalString(r.activity) || !isOptionalString(r.sourceUrl) || !isOptionalString(r.userName)) return false;
12352
12904
  if (r.handoff !== void 0 && !isHandoffShape(r.handoff)) return false;
12905
+ if (r.profile !== void 0 && !isRunProfileRecord(r.profile)) return false;
12353
12906
  if (typeof r.channelId !== "string" || typeof r.userId !== "string" || typeof r.threadKey !== "string") return false;
12354
12907
  if (r.channelVisibility !== void 0 && !CHANNEL_VISIBILITIES2.includes(r.channelVisibility))
12355
12908
  return false;
@@ -12476,7 +13029,7 @@ function dropSpanPairsFromTheMiddle(events, eventBudget) {
12476
13029
  }
12477
13030
  return events.filter((_, k) => !dropped.has(k));
12478
13031
  }
12479
- 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;
12480
13033
  var init_runRecord = __esm({
12481
13034
  "../../src/core/runRecord.ts"() {
12482
13035
  "use strict";
@@ -12485,6 +13038,19 @@ var init_runRecord = __esm({
12485
13038
  init_runFriction();
12486
13039
  RUN_STATUSES = ["completed", "stopped_soft", "stopped_hard", "failed", "interrupted"];
12487
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
+ };
12488
13054
  RUN_LIST_DEFAULT_LIMIT = 50;
12489
13055
  RUN_LIST_MAX_LIMIT = 200;
12490
13056
  RUN_EVENTS_DEFAULT_PAGE = 1e3;
@@ -12515,7 +13081,7 @@ import { z as z7 } from "zod";
12515
13081
  function registerDeliveryCommands(registry) {
12516
13082
  for (const cmd of deliveryCommands) registry.register(cmd);
12517
13083
  }
12518
- var defineCommand4, REPO_SLUG2, ISO_DAY2, asJson, deliveryReport, deliveryCommands;
13084
+ var defineCommand4, REPO_SLUG2, ISO_DAY3, asJson, deliveryReport, deliveryCommands;
12519
13085
  var init_delivery2 = __esm({
12520
13086
  "../../src/core/commands/delivery.ts"() {
12521
13087
  "use strict";
@@ -12525,21 +13091,22 @@ var init_delivery2 = __esm({
12525
13091
  init_runRecord();
12526
13092
  defineCommand4 = commandDefiner();
12527
13093
  REPO_SLUG2 = /^(?!\.+\/)[\w.-]+\/(?!\.+$)[\w.-]+$/;
12528
- ISO_DAY2 = /^\d{4}-\d{2}-\d{2}$/;
13094
+ ISO_DAY3 = /^\d{4}-\d{2}-\d{2}$/;
12529
13095
  asJson = (v) => v;
12530
13096
  deliveryReport = defineCommand4({
12531
13097
  id: "delivery.report",
12532
13098
  options: z7.object({
12533
13099
  repo: z7.string().regex(REPO_SLUG2).optional().describe("the repository (`owner/name`); default: the first under `delivery.repos`"),
12534
- 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"),
12535
13101
  weeks: z7.coerce.number().int().min(1).max(MAX_WEEKS).optional().describe(
12536
13102
  `Monday-start weeks back from this one (default 4, at most ${MAX_WEEKS}); --since, when given, decides instead`
12537
- )
13103
+ ),
13104
+ fresh: flag.optional().describe("read GitHub now instead of the repository's snapshot, and refresh the snapshot")
12538
13105
  }),
12539
13106
  action: "delivery:read",
12540
13107
  effect: "read",
12541
13108
  enabledWhen: (caps) => caps.github,
12542
- 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.",
12543
13110
  render: (output) => renderDeliveryReport(output),
12544
13111
  handler: async ({ options, caller, deps }) => {
12545
13112
  const service = await deps.delivery.service();
@@ -12559,11 +13126,13 @@ var init_delivery2 = __esm({
12559
13126
  return runFactsOf(page2.runs, repo);
12560
13127
  };
12561
13128
  try {
12562
- 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
+ );
12563
13132
  } catch (err2) {
12564
- const message = err2 instanceof Error ? err2.message : String(err2);
12565
- if (message === DELIVERY_OFF_MESSAGE) throw new CommandError("unavailable", message);
12566
- 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}`);
12567
13136
  }
12568
13137
  }
12569
13138
  });
@@ -12693,7 +13262,7 @@ var init_deploy = __esm({
12693
13262
  init_operatorRoot();
12694
13263
  init_restart();
12695
13264
  init_run();
12696
- init_profile();
13265
+ init_profile2();
12697
13266
  init_wranglerTemplate();
12698
13267
  init_secrets();
12699
13268
  init_commandRegistry();
@@ -14288,7 +14857,7 @@ var init_help = __esm({
14288
14857
  init_commandRegistry();
14289
14858
  init_commandSurface();
14290
14859
  defineCommand8 = commandDefiner();
14291
- 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)";
14292
14861
  helpShow = defineCommand8({
14293
14862
  id: "help.show",
14294
14863
  action: "help:read",
@@ -14427,8 +14996,8 @@ var init_types2 = __esm({
14427
14996
  "../../src/mcp/types.ts"() {
14428
14997
  "use strict";
14429
14998
  McpError = class extends Error {
14430
- constructor(code, message) {
14431
- super(message);
14999
+ constructor(code, message2) {
15000
+ super(message2);
14432
15001
  this.code = code;
14433
15002
  this.name = "McpError";
14434
15003
  }
@@ -14611,12 +15180,12 @@ var init_client = __esm({
14611
15180
  const res = await this.post({ jsonrpc: "2.0", id, method, params }, signal, o);
14612
15181
  const session = res.headers.get("mcp-session-id");
14613
15182
  if (session && o?.initializing) this.sessionId = session;
14614
- const message = await this.readResponse(res, id, signal);
14615
- if (message.error) {
14616
- const code = typeof message.error.code === "number" ? message.error.code : "protocol";
14617
- 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")}`);
14618
15187
  }
14619
- return message.result;
15188
+ return message2.result;
14620
15189
  }
14621
15190
  async post(body, signal, o) {
14622
15191
  const headers = {
@@ -15023,8 +15592,8 @@ var init_oauth = __esm({
15023
15592
  init_web();
15024
15593
  init_client();
15025
15594
  OAuthError = class extends Error {
15026
- constructor(message) {
15027
- super(message);
15595
+ constructor(message2) {
15596
+ super(message2);
15028
15597
  this.name = "OAuthError";
15029
15598
  }
15030
15599
  };
@@ -15167,9 +15736,9 @@ function bridgeMcpTools(server, client, tools, opts) {
15167
15736
  try {
15168
15737
  result = await client.callTool(t.name, input ?? {}, { signal: ctx.signal });
15169
15738
  } catch (err2) {
15170
- 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);
15171
15740
  const failed = classifyError(
15172
- new Error(`MCP ${server.name}/${t.name} failed: ${message}`, { cause: err2 }),
15741
+ new Error(`MCP ${server.name}/${t.name} failed: ${message2}`, { cause: err2 }),
15173
15742
  mcpClass(err2)
15174
15743
  );
15175
15744
  span?.fail(failed);
@@ -15376,8 +15945,8 @@ var init_service = __esm({
15376
15945
  init_source();
15377
15946
  MCP_OFF_MESSAGE = "External MCP servers are not enabled in this deployment (no `mcp` block in config.yaml).";
15378
15947
  McpServiceError = class extends Error {
15379
- constructor(code, message) {
15380
- super(message);
15948
+ constructor(code, message2) {
15949
+ super(message2);
15381
15950
  this.code = code;
15382
15951
  this.name = "McpServiceError";
15383
15952
  }
@@ -15985,12 +16554,12 @@ var init_service = __esm({
15985
16554
  const tools = await this.opts.factory(spec).listTools();
15986
16555
  return { kind: "ok", toolCount: tools.length };
15987
16556
  } catch (err2) {
15988
- const message = err2 instanceof Error ? err2.message : String(err2);
15989
- if (/HTTP 40[13]\b/.test(message))
15990
- 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})` };
15991
16560
  return {
15992
16561
  kind: "error",
15993
- 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)}`
15994
16563
  };
15995
16564
  }
15996
16565
  }
@@ -17880,8 +18449,8 @@ var init_githubApi = __esm({
17880
18449
  MAX_FILE_CHARS = 2e5;
17881
18450
  COMPARE_DIFF_MAX_CHARS = 12e5;
17882
18451
  GithubApiError = class extends Error {
17883
- constructor(status3, message) {
17884
- super(message);
18452
+ constructor(status3, message2) {
18453
+ super(message2);
17885
18454
  this.status = status3;
17886
18455
  this.name = "GithubApiError";
17887
18456
  }
@@ -18203,8 +18772,8 @@ var init_reviewAbridge = __esm({
18203
18772
  init_clock();
18204
18773
  isReadingDiff = (e) => e.type === "review_artifact" && e.artifact === "reading_diff";
18205
18774
  AbridgeRefusal = class extends Error {
18206
- constructor(code, message) {
18207
- super(message);
18775
+ constructor(code, message2) {
18776
+ super(message2);
18208
18777
  this.code = code;
18209
18778
  this.name = "AbridgeRefusal";
18210
18779
  }
@@ -19249,7 +19818,7 @@ var init_plan2 = __esm({
19249
19818
  init_config();
19250
19819
  init_capabilities();
19251
19820
  init_secrets2();
19252
- init_profile();
19821
+ init_profile2();
19253
19822
  ENV_PATH = ".env";
19254
19823
  CONFIG_PATH = "config/config.yaml";
19255
19824
  PROFILE_PATH2 = "deploy/profile.json";
@@ -19262,7 +19831,7 @@ var init_plan2 = __esm({
19262
19831
  ];
19263
19832
  OPENAI_PROVIDER = "openai";
19264
19833
  OPENAI_KEY_ENV = "OPENAI_API_KEY";
19265
- DEFAULT_MODEL_AGENTS = ["general", "coding", "review"];
19834
+ DEFAULT_MODEL_AGENTS = ["general", "coding", "review", "explore"];
19266
19835
  ENV_LINE = /^(#\s*)?([A-Z][A-Z0-9_]*)=(.*)$/;
19267
19836
  }
19268
19837
  });
@@ -19753,7 +20322,12 @@ function buildCoreCommands(config, store, wiring) {
19753
20322
  const delivery = once(async () => {
19754
20323
  if (wiring.delivery) return wiring.delivery();
19755
20324
  if (wiring.capabilities && !wiring.capabilities.github) return new NullDeliveryService();
19756
- 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;
19757
20331
  });
19758
20332
  const deps = {
19759
20333
  help: {
@@ -19818,13 +20392,19 @@ function buildCoreCommands(config, store, wiring) {
19818
20392
  };
19819
20393
  return bindCommands(registry, deps);
19820
20394
  }
19821
- function deliveryServiceFromConfig(cfg) {
19822
- 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, {
19823
20402
  identities: async () => {
19824
20403
  const identity2 = await resolveGithubIdentity();
19825
20404
  return identity2 ? { reviewers: [identity2.login] } : {};
19826
20405
  }
19827
20406
  });
20407
+ return { service, source: source2 };
19828
20408
  }
19829
20409
  var init_commandCatalogue = __esm({
19830
20410
  "../../src/core/commandCatalogue.ts"() {
@@ -19843,6 +20423,8 @@ var init_commandCatalogue = __esm({
19843
20423
  init_githubDelivery();
19844
20424
  init_resident();
19845
20425
  init_delivery();
20426
+ init_deliverySnapshot();
20427
+ init_deliverySnapshotStore();
19846
20428
  init_commandRegistry();
19847
20429
  init_secrets2();
19848
20430
  init_all();
@@ -20442,10 +21024,10 @@ function safePredicate(isResident, slug) {
20442
21024
  return false;
20443
21025
  }
20444
21026
  }
20445
- async function safeProbe(isResident, slug) {
20446
- if (!isResident) return true;
21027
+ async function safeProbe(probe, slug) {
21028
+ if (!probe) return true;
20447
21029
  try {
20448
- const answer = await isResident(slug);
21030
+ const answer = await probe(slug);
20449
21031
  return answer === true || answer === "unreachable" ? answer : false;
20450
21032
  } catch {
20451
21033
  return "unreachable";
@@ -20454,7 +21036,7 @@ async function safeProbe(isResident, slug) {
20454
21036
  function repoFromThread(history, isResident) {
20455
21037
  return threadSignals(history, isResident).repo;
20456
21038
  }
20457
- async function resolveRepoContext(msg, history = [], isResident, residentSlugs) {
21039
+ async function resolveRepoContext(msg, history = [], probe, residentSlugs) {
20458
21040
  const s = extractSignals(msg.text);
20459
21041
  const thread = threadSignals(history);
20460
21042
  let rejected;
@@ -20463,7 +21045,7 @@ async function resolveRepoContext(msg, history = [], isResident, residentSlugs)
20463
21045
  const vet = async (cand) => {
20464
21046
  let p = vetted.get(cand);
20465
21047
  if (!p) {
20466
- p = safeProbe(isResident, cand);
21048
+ p = safeProbe(probe, cand);
20467
21049
  vetted.set(cand, p);
20468
21050
  }
20469
21051
  const answer = await p;
@@ -20479,7 +21061,7 @@ async function resolveRepoContext(msg, history = [], isResident, residentSlugs)
20479
21061
  return slugsOnce;
20480
21062
  };
20481
21063
  const resolveAddressed = async (a) => {
20482
- if (a.slug !== void 0) return isResident && await vet(a.slug) ? a.slug : void 0;
21064
+ if (a.slug !== void 0) return probe && await vet(a.slug) ? a.slug : void 0;
20483
21065
  const matches = (await listSlugs() ?? []).filter((slug) => slug.split("/")[1] === a.name);
20484
21066
  return matches.length === 1 ? matches[0] : void 0;
20485
21067
  };
@@ -22301,6 +22883,21 @@ ${raw.trim()}`;
22301
22883
  * and URL reading, so a plain mention can answer from the repos and act on
22302
22884
  * issues without being re-sent to another agent. */
22303
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
+ ],
22304
22901
  none: []
22305
22902
  };
22306
22903
  }
@@ -22505,9 +23102,9 @@ async function runLoop(opts, now, note, emit, agentSpan) {
22505
23102
  );
22506
23103
  throw err2;
22507
23104
  }
22508
- const message = err2 instanceof Error ? err2.message : String(err2);
23105
+ const message2 = err2 instanceof Error ? err2.message : String(err2);
22509
23106
  if (err2 instanceof ExecCapacityError) {
22510
- 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.`;
22511
23108
  emit({ type: "tool_result", tool: tu.name, ok: false, callId: tu.id, ...prepareToolResult(text2), ...spanId });
22512
23109
  note("fleet_busy", text2);
22513
23110
  settle2(false, { infra: true });
@@ -22519,7 +23116,7 @@ async function runLoop(opts, now, note, emit, agentSpan) {
22519
23116
  tool: tu.name,
22520
23117
  ok: false,
22521
23118
  callId: tu.id,
22522
- ...prepareToolResult(message),
23119
+ ...prepareToolResult(message2),
22523
23120
  ...infra ? { infra: true } : {},
22524
23121
  ...spanId
22525
23122
  });
@@ -22528,7 +23125,7 @@ async function runLoop(opts, now, note, emit, agentSpan) {
22528
23125
  return {
22529
23126
  type: "tool_result",
22530
23127
  toolUseId: tu.id,
22531
- content: capToolResultContent(`Error: ${message}`),
23128
+ content: capToolResultContent(`Error: ${message2}`),
22532
23129
  isError: true
22533
23130
  };
22534
23131
  }
@@ -22897,8 +23494,8 @@ function rereviewFollowUp(input) {
22897
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.`
22898
23495
  ].join("\n");
22899
23496
  }
22900
- function subject(message) {
22901
- return message.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
23497
+ function subject(message2) {
23498
+ return message2.trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
22902
23499
  }
22903
23500
  function short3(sha) {
22904
23501
  return sha.trim().toLowerCase().slice(0, 7);
@@ -23024,16 +23621,17 @@ var init_digestCoverage = __esm({
23024
23621
  });
23025
23622
 
23026
23623
  // ../../src/core/reviewRound.ts
23027
- function releaseModeFor(agent, opts) {
23028
- return agent.toolset === "readonly" || opts.hardStopped ? "always" : "if-clean";
23624
+ function releaseModeFor(identity2, opts) {
23625
+ return identity2 === "read" || opts.hardStopped ? "always" : "if-clean";
23029
23626
  }
23030
23627
  async function attachRoundWorkspace(input) {
23031
- const { agent } = input.round;
23628
+ const { agent, profile } = input.round;
23032
23629
  const selection = await makeExecutor(
23033
23630
  input.factory,
23034
23631
  {
23035
23632
  threadKey: input.round.threadKey,
23036
23633
  agent,
23634
+ profile,
23037
23635
  repo: input.round.repo,
23038
23636
  ref: input.round.ref,
23039
23637
  headSha: input.round.headSha
@@ -23043,7 +23641,7 @@ async function attachRoundWorkspace(input) {
23043
23641
  const release = async (opts) => {
23044
23642
  const { executor } = selection;
23045
23643
  if (!executor.release) return;
23046
- const mode = releaseModeFor(agent, opts);
23644
+ const mode = releaseModeFor(profile.identity, opts);
23047
23645
  try {
23048
23646
  const r = await executor.release(mode, opts.span ? { span: opts.span } : void 0);
23049
23647
  console.log(`[release] ${input.logKey} ${r.released ? "released" : "kept"}${r.reason ? ` (${r.reason})` : ""}`);
@@ -23757,6 +24355,9 @@ function reclaimedRunRecord(input) {
23757
24355
  },
23758
24356
  channelVisibility: row.meta.channelVisibility ?? "unknown",
23759
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 } } : {},
23760
24361
  finishedAt,
23761
24362
  status: status3,
23762
24363
  diagnosis: analyzeRunFriction(events, {
@@ -23815,12 +24416,16 @@ function assembleRunRecord(input) {
23815
24416
  ...activityOfEvents(events) !== void 0 ? { activity: activityOfEvents(events) } : {},
23816
24417
  ...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
23817
24418
  ...msg.userName !== void 0 ? { userName: msg.userName } : {},
23818
- ...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 } : {}
23819
24421
  });
23820
24422
  return fitted.eventCount !== fitted.storedEventCount ? { ...fitted, truncated: true } : fitted;
23821
24423
  }
24424
+ function profileRecordOf(agent, profile) {
24425
+ return { preset: agent.name, ...profile };
24426
+ }
23822
24427
  function writeTombstone(deps, ctx) {
23823
- const { msg, agent, resolved, repoCtx, channelVisibility, run: run2, registry, resume } = ctx;
24428
+ const { msg, agent, profile, resolved, repoCtx, channelVisibility, run: run2, registry, resume } = ctx;
23824
24429
  if (!resume) {
23825
24430
  const startSnap = registry.snapshot(run2.id, run2.token);
23826
24431
  if (startSnap) {
@@ -23833,6 +24438,7 @@ function writeTombstone(deps, ctx) {
23833
24438
  msg,
23834
24439
  channelVisibility,
23835
24440
  repo: repoCtx.repo,
24441
+ profile: profileRecordOf(agent, profile),
23836
24442
  finishedAt: startSnap.startedAt,
23837
24443
  status: "interrupted",
23838
24444
  diagnosis: analyzeRunFriction(startSnap.events, {
@@ -23851,6 +24457,7 @@ function registerFinishRecord(deps, ctx) {
23851
24457
  run: run2,
23852
24458
  snap,
23853
24459
  agent,
24460
+ profile,
23854
24461
  resolved,
23855
24462
  msg,
23856
24463
  channelVisibility,
@@ -23874,6 +24481,7 @@ function registerFinishRecord(deps, ctx) {
23874
24481
  msg,
23875
24482
  channelVisibility,
23876
24483
  repo: repoCtx.repo,
24484
+ profile: profileRecordOf(agent, profile),
23877
24485
  finishedAt,
23878
24486
  status: failedAfterFinish && status3 === "completed" ? "failed" : status3,
23879
24487
  diagnosis,
@@ -24225,10 +24833,10 @@ ${chatCatalogueText(inGroup)}` };
24225
24833
  const id = `${group}.${verb}`;
24226
24834
  const cmd = exposed.find((c) => c.id === id);
24227
24835
  if (!cmd) return null;
24228
- const rejected = (message) => ({
24836
+ const rejected = (message2) => ({
24229
24837
  kind: "reply",
24230
24838
  error: "invalid_input",
24231
- text: `\u26A0\uFE0F \`${chatForm(id)}\`: ${message}`
24839
+ text: `\u26A0\uFE0F \`${chatForm(id)}\`: ${message2}`
24232
24840
  });
24233
24841
  const tokens = tokenize(text);
24234
24842
  if (!tokens.ok) return rejected(tokens.error);
@@ -24250,15 +24858,15 @@ function chatCallerFor(msg, config, resolveRepo) {
24250
24858
  origin: { channelId: msg.channelId, threadKey: msg.threadKey, ...resolveRepo ? { repo: resolveRepo } : {} }
24251
24859
  };
24252
24860
  }
24253
- function chatErrorLine(id, error, message, config, decidedBy = "registry") {
24861
+ function chatErrorLine(id, error, message2, config, decidedBy = "registry") {
24254
24862
  const name = chatForm(id);
24255
24863
  switch (error) {
24256
24864
  case "unauthorized":
24257
- 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()}.`;
24258
24866
  case "internal":
24259
- return `\u26A0\uFE0F \`${name}\` failed: ${message}`;
24867
+ return `\u26A0\uFE0F \`${name}\` failed: ${message2}`;
24260
24868
  default:
24261
- return `\u26A0\uFE0F \`${name}\`: ${message}`;
24869
+ return `\u26A0\uFE0F \`${name}\`: ${message2}`;
24262
24870
  }
24263
24871
  }
24264
24872
  async function invokeChatCommand({
@@ -25063,6 +25671,11 @@ var init_fastPath = __esm({
25063
25671
  });
25064
25672
 
25065
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
+ }
25066
25679
  function lastThreadDirectives(history) {
25067
25680
  const out = {};
25068
25681
  for (const h of history) {
@@ -25095,6 +25708,14 @@ function parseDirectives(input) {
25095
25708
  throw new Error(`Unknown effort "${f.value}". Valid: ${EFFORT_LEVELS_HINT}`);
25096
25709
  }
25097
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;
25098
25719
  }
25099
25720
  text = text.replace(f.match, " ");
25100
25721
  }
@@ -25106,8 +25727,47 @@ var init_directives = __esm({
25106
25727
  "../../src/directives.ts"() {
25107
25728
  "use strict";
25108
25729
  init_registry();
25730
+ init_validate();
25109
25731
  init_effort();
25110
- DIRECTIVE_RE = /(?:^|\s)(agent|model|effort)[:=](\S+)/g;
25732
+ DIRECTIVE_RE = /(?:^|\s)(agent|model|effort|budget)[:=](\S+)/g;
25733
+ }
25734
+ });
25735
+
25736
+ // ../../src/execution/githubRepoProbe.ts
25737
+ function githubRepoProbe(opts) {
25738
+ const fetchImpl = opts.fetch ?? fetch;
25739
+ const token = opts.token ?? ((scope) => resolveGithubToken(scope));
25740
+ return async (slug) => {
25741
+ if (!SLUG.test(slug)) return false;
25742
+ let bearer;
25743
+ try {
25744
+ bearer = opts.scope === void 0 ? null : await token(opts.scope);
25745
+ } catch {
25746
+ return "unreachable";
25747
+ }
25748
+ const headers = { accept: "application/vnd.github+json", "user-agent": "switchboard" };
25749
+ if (bearer) headers.authorization = `Bearer ${bearer}`;
25750
+ let res;
25751
+ try {
25752
+ res = await fetchImpl(`https://api.github.com/repos/${slug}`, {
25753
+ headers,
25754
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS6)
25755
+ });
25756
+ } catch {
25757
+ return "unreachable";
25758
+ }
25759
+ if (res.ok) return true;
25760
+ if (res.status === 404) return false;
25761
+ return "unreachable";
25762
+ };
25763
+ }
25764
+ var REQUEST_TIMEOUT_MS6, SLUG;
25765
+ var init_githubRepoProbe = __esm({
25766
+ "../../src/execution/githubRepoProbe.ts"() {
25767
+ "use strict";
25768
+ init_githubApp();
25769
+ REQUEST_TIMEOUT_MS6 = 1e4;
25770
+ SLUG = /^[A-Za-z0-9][A-Za-z0-9-]*\/[A-Za-z0-9._-]+$/;
25111
25771
  }
25112
25772
  });
25113
25773
 
@@ -25132,32 +25792,48 @@ function resolveRun(deps, ctx) {
25132
25792
  });
25133
25793
  return { sticky, resolved };
25134
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
+ }
25135
25808
  function resolveTarget(deps, ctx) {
25136
- const { msg, history, agent, resolved, resume, root } = ctx;
25809
+ const { msg, history, profile, resolved, resume, root } = ctx;
25137
25810
  const { provider: providerName, model } = parseModelRef(resolved.modelRef);
25138
25811
  const provider = deps.providers.get(providerName);
25139
- const needsRepo = agent.resources?.repo === "required";
25812
+ const needsRepo = machineNeedsRepo(profile.machine);
25140
25813
  const repoCtxP = root.span(
25141
25814
  "dispatch.repo_context",
25142
25815
  () => resume ? Promise.resolve(resume.repoCtx) : needsRepo ? Promise.resolve(
25143
- deps.resolveRepoContext ? deps.resolveRepoContext(msg, history) : resolveRepoContext(
25144
- msg,
25145
- history,
25146
- residentOnboardedProbe(deps.config.config.execution?.resident),
25147
- residentSlugsLister(deps.config.config.execution?.resident)
25148
- )
25816
+ deps.resolveRepoContext ? deps.resolveRepoContext(msg, history) : resolveRepoContext(msg, history, ...repoVetFor(profile, deps.config.config.execution?.resident))
25149
25817
  ).then((ctx2) => ctx2 ?? {}) : Promise.resolve({})
25150
25818
  );
25151
25819
  repoCtxP.catch(() => {
25152
25820
  });
25153
25821
  return { provider, model, needsRepo, repoCtxP };
25154
25822
  }
25823
+ function repoVetFor(profile, resident) {
25824
+ if (profile.machine === "repo-cold")
25825
+ return [githubRepoProbe({ scope: githubTokenScopeFor(profile.identity) }), void 0];
25826
+ return [residentOnboardedProbe(resident), residentSlugsLister(resident)];
25827
+ }
25155
25828
  var init_resolve = __esm({
25156
25829
  "../../src/core/dispatch/resolve.ts"() {
25157
25830
  "use strict";
25831
+ init_registry();
25832
+ init_profile();
25158
25833
  init_directives();
25159
25834
  init_types3();
25160
25835
  init_factory();
25836
+ init_githubRepoProbe();
25161
25837
  init_repoContext();
25162
25838
  }
25163
25839
  });
@@ -25176,8 +25852,97 @@ async function authorizeAgent(deps, ctx) {
25176
25852
  }
25177
25853
  return { kind: "allowed" };
25178
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
+ }
25179
25909
  async function authorizeRepo(deps, ctx) {
25180
- const { msg, io, refuse: refuse3, card, shell, closeLines, clock: clock2, agent, needsRepo, repoCtx } = ctx;
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) {
25912
+ if (repoCtx.rejectedRepo) {
25913
+ const slug = repoCtx.rejectedRepo;
25914
+ console.log(`[dispatch] ${msg.threadKey} not started: repo not visible (${slug}: GitHub answered 404)`);
25915
+ await refuse3("repo_not_visible", async () => {
25916
+ await card.done(
25917
+ shell.close({ kind: "not_started", icon: "\u{1F4E6}", reason: "repo not visible", ...closeLines(clock2(), false) })
25918
+ );
25919
+ await io.reply(
25920
+ `\u{1F4E6} \`${slug}\` is not a repository this installation can see \u2014 GitHub answered 404 \u2014 so I did not start ${aRun(agent.name)} for it. The repository is outside the Switchboard GitHub App installation (\`github_repos\` lists the reachable ones), or the name is wrong.`
25921
+ );
25922
+ });
25923
+ return { kind: "refused", reason: "repo_not_visible" };
25924
+ }
25925
+ if (repoCtx.unverifiedRepo) {
25926
+ const slug = repoCtx.unverifiedRepo;
25927
+ console.log(
25928
+ `[dispatch] ${msg.threadKey} not started: repo could not be verified (${slug}: GitHub did not answer)`
25929
+ );
25930
+ await refuse3("repo_unverified", async () => {
25931
+ await card.done(
25932
+ shell.close({
25933
+ kind: "not_started",
25934
+ icon: "\u{1F4E6}",
25935
+ reason: "repo could not be verified",
25936
+ ...closeLines(clock2(), false)
25937
+ })
25938
+ );
25939
+ await io.reply(
25940
+ `\u26A0\uFE0F I couldn't verify \`${slug}\` against GitHub \u2014 it didn't answer \u2014 so I did not start ${aRun(agent.name)} rather than guess which repository you meant. Try again in a minute.`
25941
+ );
25942
+ });
25943
+ return { kind: "refused", reason: "repo_unverified" };
25944
+ }
25945
+ }
25181
25946
  if (deps.capabilities.residents && needsRepo && !repoCtx.repo && repoCtx.rejectedRepo) {
25182
25947
  const slug = repoCtx.rejectedRepo;
25183
25948
  console.log(`[dispatch] ${msg.threadKey} not started: repo not onboarded (${slug})`);
@@ -25226,6 +25991,9 @@ async function authorizeRepo(deps, ctx) {
25226
25991
  }
25227
25992
  return { kind: "allowed" };
25228
25993
  }
25994
+ function aRun(agentName) {
25995
+ return `${/^[aeiou]/i.test(agentName) ? "an" : "a"} *${agentName}* run`;
25996
+ }
25229
25997
  async function authorizePrHead(ctx) {
25230
25998
  const { msg, io, refuse: refuse3, card, shell, closeLines, clock: clock2, agent, directives, repoCtx } = ctx;
25231
25999
  const preflight = checkPrHeadPreflight({ agent, requestText: directives.text, repoCtx });
@@ -25551,10 +26319,24 @@ function configAwarenessBlock(i) {
25551
26319
  );
25552
26320
  }
25553
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
+ }
25554
26336
  const channelGate = i.canEditChannelConfig ? "per-channel" : "per-channel; restricted for this user \u2014 ask an admin";
25555
26337
  const levels = `<${EFFORT_LEVELS.join("|")}>`;
25556
26338
  lines.push(
25557
- `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.`,
25558
26340
  "When asked about your settings or tuning, answer from this block \u2014 you are not stateless or untunable."
25559
26341
  );
25560
26342
  return lines.join("\n");
@@ -25579,12 +26361,15 @@ function fmtDirective(d) {
25579
26361
  if (d.agent) parts.push(`agent:${d.agent}`);
25580
26362
  if (d.model) parts.push(`model:${d.model}`);
25581
26363
  if (d.effort) parts.push(`effort:${d.effort}`);
26364
+ if (d.budget !== void 0) parts.push(`budget:${d.budget}`);
25582
26365
  return parts.length > 0 ? parts.join(" ") : void 0;
25583
26366
  }
25584
26367
  var CONFIG_AWARENESS_HEADER;
25585
26368
  var init_configAwareness = __esm({
25586
26369
  "../../src/core/configAwareness.ts"() {
25587
26370
  "use strict";
26371
+ init_config();
26372
+ init_profile();
25588
26373
  init_effort();
25589
26374
  CONFIG_AWARENESS_HEADER = "Switchboard runtime config for this run:";
25590
26375
  }
@@ -25795,6 +26580,15 @@ async function openAckCard(deps, ctx) {
25795
26580
  const heartbeat = setInterval(() => card.update(shell.live()), 5e3);
25796
26581
  return { shell, card, heartbeat };
25797
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
+ }
25798
26592
  async function registerRun(deps, ctx) {
25799
26593
  const {
25800
26594
  msg,
@@ -25897,6 +26691,7 @@ async function reserveRun(deps, ctx) {
25897
26691
  const {
25898
26692
  msg,
25899
26693
  agent,
26694
+ profile,
25900
26695
  resolved,
25901
26696
  repoCtx,
25902
26697
  channelVisibility,
@@ -25932,7 +26727,8 @@ async function reserveRun(deps, ctx) {
25932
26727
  ...repoCtx.ref !== void 0 ? { ref: repoCtx.ref } : {},
25933
26728
  ...repoCtx.headSha !== void 0 ? { headSha: repoCtx.headSha } : {},
25934
26729
  ...repoCtx.pr !== void 0 ? { pr: repoCtx.pr } : {},
25935
- readonly: agent.toolset === "readonly",
26730
+ readonly: profile.identity === "read",
26731
+ profile,
25936
26732
  request: requestRow
25937
26733
  },
25938
26734
  card: card.handle ?? null,
@@ -25945,7 +26741,7 @@ async function reserveRun(deps, ctx) {
25945
26741
  return void 0;
25946
26742
  }
25947
26743
  async function attachWorkspace(deps, ctx) {
25948
- 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;
25949
26745
  let round;
25950
26746
  try {
25951
26747
  round = await root.span("dispatch.workspace.attach", async (span) => {
@@ -25964,7 +26760,14 @@ async function attachWorkspace(deps, ctx) {
25964
26760
  workspaceDir: deps.config.config.workspaceDir ?? "./workspaces",
25965
26761
  dataDir: deps.dataDir ?? "./data"
25966
26762
  },
25967
- 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
+ },
25968
26771
  logKey: msg.threadKey,
25969
26772
  span
25970
26773
  });
@@ -25998,6 +26801,7 @@ async function composePrompt(deps, ctx) {
25998
26801
  const {
25999
26802
  msg,
26000
26803
  agent,
26804
+ profile,
26001
26805
  resolved,
26002
26806
  directives,
26003
26807
  sticky,
@@ -26023,9 +26827,23 @@ async function composePrompt(deps, ctx) {
26023
26827
  effort: resolved.effort,
26024
26828
  channel: scopes.channel,
26025
26829
  user: scopes.user,
26026
- 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
+ },
26027
26836
  threadDirective: { agent: sticky.agent, model: sticky.model, effort: sticky.effort },
26028
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
+ },
26029
26847
  mcp: {
26030
26848
  registryOn: deps.capabilities.mcp,
26031
26849
  served: mcpForRun.servers.filter((s) => s.toolCount !== void 0).map((s) => s.server),
@@ -26071,6 +26889,7 @@ var init_provision = __esm({
26071
26889
  "../../src/core/dispatch/provision.ts"() {
26072
26890
  "use strict";
26073
26891
  init_registry();
26892
+ init_profile();
26074
26893
  init_residentTrace();
26075
26894
  init_resident();
26076
26895
  init_memory();
@@ -26102,6 +26921,7 @@ async function claimRun(deps, ctx) {
26102
26921
  channelVisibility,
26103
26922
  run: run2,
26104
26923
  registry,
26924
+ profile,
26105
26925
  selection,
26106
26926
  requestRow,
26107
26927
  reserved,
@@ -26137,7 +26957,8 @@ async function claimRun(deps, ctx) {
26137
26957
  ...repoCtx.ref !== void 0 ? { ref: repoCtx.ref } : {},
26138
26958
  ...repoCtx.headSha !== void 0 ? { headSha: repoCtx.headSha } : {},
26139
26959
  ...repoCtx.pr !== void 0 ? { pr: repoCtx.pr } : {},
26140
- readonly: agent.toolset === "readonly",
26960
+ readonly: profile.identity === "read",
26961
+ profile,
26141
26962
  selection: resident === true ? "resident" : "sandbox",
26142
26963
  ...binding?.workspace !== void 0 ? { workspace: binding.workspace } : {},
26143
26964
  ...requestRow !== void 0 ? { request: requestRow } : {}
@@ -26150,7 +26971,9 @@ async function claimRun(deps, ctx) {
26150
26971
  tools: mergeTools(TOOLSETS[agent.toolset] ?? [], mcpForRun?.tools).map(
26151
26972
  ({ name, description, inputSchema }) => ({ name, description, inputSchema })
26152
26973
  ),
26153
- 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 },
26154
26977
  // A stop asked of another container (`/runs/stop` there) reaches this
26155
26978
  // run through its heartbeat and is honored like a local one; a fence
26156
26979
  // (another generation took the run) is a hard stop — nothing more may
@@ -26642,7 +27465,7 @@ async function runLoop2(deps, ctx) {
26642
27465
  const {
26643
27466
  msg,
26644
27467
  io,
26645
- agent,
27468
+ profile,
26646
27469
  resolved,
26647
27470
  provider,
26648
27471
  model,
@@ -26669,6 +27492,7 @@ async function runLoop2(deps, ctx) {
26669
27492
  publishText,
26670
27493
  ending
26671
27494
  } = ctx;
27495
+ const agent = budgetedAgent(ctx.agent, profile);
26672
27496
  const { executor, binding } = round.selection;
26673
27497
  let reviewHead = ctx.reviewHead;
26674
27498
  let lastActivityAt = ctx.loopStartedAt;
@@ -26996,6 +27820,7 @@ async function runLoop2(deps, ctx) {
26996
27820
  run: run2,
26997
27821
  snap,
26998
27822
  agent,
27823
+ profile,
26999
27824
  resolved,
27000
27825
  msg,
27001
27826
  channelVisibility,
@@ -27032,6 +27857,7 @@ async function runLoop2(deps, ctx) {
27032
27857
  var init_runLoop = __esm({
27033
27858
  "../../src/core/dispatch/runLoop.ts"() {
27034
27859
  "use strict";
27860
+ init_profile();
27035
27861
  init_runner();
27036
27862
  init_githubPulls();
27037
27863
  init_repoContext();
@@ -27087,6 +27913,7 @@ async function runShipCodingChild(input, ctx, opts, roundSpan) {
27087
27913
  round: {
27088
27914
  threadKey: input.threadKey,
27089
27915
  agent: spec.agent,
27916
+ profile: declaredProfile(spec.agent),
27090
27917
  repo: entry.repo,
27091
27918
  ref: entry.branch,
27092
27919
  headSha: opts.attachHeadSha
@@ -27275,6 +28102,7 @@ var init_codingChild = __esm({
27275
28102
  "../../src/core/ship/codingChild.ts"() {
27276
28103
  "use strict";
27277
28104
  init_runner();
28105
+ init_profile();
27278
28106
  init_descriptionTurn();
27279
28107
  init_reviewVerdict();
27280
28108
  init_codingPrPostStep();
@@ -27316,7 +28144,14 @@ async function runShipReviewChild(input, ctx, round, roundSpan) {
27316
28144
  if (!pf.ok) return { refusal: pf.reply };
27317
28145
  const ws = await attachRoundWorkspace({
27318
28146
  factory: input.factory,
27319
- 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
+ },
27320
28155
  logKey
27321
28156
  });
27322
28157
  const { executor, resident, binding, note } = ws.selection;
@@ -27450,6 +28285,7 @@ var init_reviewChild = __esm({
27450
28285
  "../../src/core/ship/reviewChild.ts"() {
27451
28286
  "use strict";
27452
28287
  init_runner();
28288
+ init_profile();
27453
28289
  init_reviewVerdict();
27454
28290
  init_reviewedHead();
27455
28291
  init_reviewRound();
@@ -27464,6 +28300,9 @@ function resolveShipCaps(cfg) {
27464
28300
  maxMinutes: cfg?.maxMinutes ?? SHIP_DEFAULT_MAX_MINUTES
27465
28301
  };
27466
28302
  }
28303
+ function shipPresetFor(cfg) {
28304
+ return { ...AGENTS.ship, maxMinutes: resolveShipCaps(cfg).maxMinutes };
28305
+ }
27467
28306
  function shipInterruptedNote(prUrl) {
27468
28307
  const stands = prUrl ? `Its work stands on GitHub: ${prUrl}.` : "Whatever it pushed stands on its pipeline branch; no PR was opened yet.";
27469
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.";
@@ -27785,12 +28624,13 @@ var SHIP_DEFAULT_MAX_ROUNDS, SHIP_DEFAULT_MAX_MINUTES, SHIP_ROUND_RESERVE_MS;
27785
28624
  var init_shipPipeline = __esm({
27786
28625
  "../../src/core/shipPipeline.ts"() {
27787
28626
  "use strict";
28627
+ init_registry();
27788
28628
  init_reviewVerdict();
27789
28629
  init_reviewedHead();
27790
28630
  init_codingChild();
27791
28631
  init_reviewChild();
27792
28632
  SHIP_DEFAULT_MAX_ROUNDS = 3;
27793
- SHIP_DEFAULT_MAX_MINUTES = 120;
28633
+ SHIP_DEFAULT_MAX_MINUTES = AGENTS.ship.maxMinutes;
27794
28634
  SHIP_ROUND_RESERVE_MS = 3 * 6e4;
27795
28635
  }
27796
28636
  });
@@ -27948,7 +28788,7 @@ var init_preflight = __esm({
27948
28788
 
27949
28789
  // ../../src/core/dispatch/ship.ts
27950
28790
  async function runShipBranch(deps, msg, io, ctx) {
27951
- 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;
27952
28792
  const root = trace.root;
27953
28793
  const clock2 = deps.clock ?? systemClock;
27954
28794
  const shell = createCardShell({ label, startedAt: ctx.startedAt, now: clock2 });
@@ -28050,7 +28890,8 @@ async function runShipBranch(deps, msg, io, ctx) {
28050
28890
  diagnosis: analyzeRunFriction(startSnap.events, {
28051
28891
  finished: false,
28052
28892
  truncated: startSnap.truncated
28053
- })
28893
+ }),
28894
+ profile: profileRecordOf(agent, profile)
28054
28895
  }),
28055
28896
  { provisional: true }
28056
28897
  );
@@ -28071,7 +28912,8 @@ async function runShipBranch(deps, msg, io, ctx) {
28071
28912
  ...repoCtx.repo !== void 0 ? { repo: repoCtx.repo } : {},
28072
28913
  ...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
28073
28914
  ...msg.userName !== void 0 ? { userName: msg.userName } : {},
28074
- ...entry.resume !== void 0 ? { pr: entry.resume.pr } : {}
28915
+ ...entry.resume !== void 0 ? { pr: entry.resume.pr } : {},
28916
+ profile
28075
28917
  },
28076
28918
  card: card.handle ?? null,
28077
28919
  system: "",
@@ -28146,7 +28988,12 @@ async function runShipBranch(deps, msg, io, ctx) {
28146
28988
  effort: spec.effort,
28147
28989
  channel: scopes.channel,
28148
28990
  user: scopes.user,
28149
- 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
+ },
28150
28997
  threadDirective: { agent: ctx.sticky.agent, model: ctx.sticky.model, effort: ctx.sticky.effort },
28151
28998
  canEditChannelConfig: deps.config.canEditChannelConfig(msg.userId)
28152
28999
  // No `mcp` here: ship rounds receive no MCP tools yet (docs/reference/specs/mcp-tools.md
@@ -28185,7 +29032,11 @@ async function runShipBranch(deps, msg, io, ctx) {
28185
29032
  dataDir: deps.dataDir ?? "./data"
28186
29033
  },
28187
29034
  threadKey: msg.threadKey,
28188
- 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 },
28189
29040
  control: run2.control,
28190
29041
  inbox: ctx.live.inbox,
28191
29042
  // thread follow-ups steered into this run reach the child round in flight (thread-admission item 2)
@@ -28251,7 +29102,8 @@ async function runShipBranch(deps, msg, io, ctx) {
28251
29102
  seal,
28252
29103
  // The last coding round's handoff (agent-ship.md item 14) — the
28253
29104
  // pipeline's, so the ship record carries what its child handed back.
28254
- ...outcome?.handoff !== void 0 ? { handoff: outcome.handoff } : {}
29105
+ ...outcome?.handoff !== void 0 ? { handoff: outcome.handoff } : {},
29106
+ profile: profileRecordOf(agent, profile)
28255
29107
  }),
28256
29108
  { span: root, ...ledgerRun ? { via: ledgerRun.sink } : {} }
28257
29109
  )
@@ -28505,7 +29357,16 @@ async function dispatch(deps, msg, io, opts = {}) {
28505
29357
  if (await answerOperation(deps, { msg, io, ending, trace, directives, history })) return;
28506
29358
  const { sticky, resolved } = resolveRun(deps, { msg, directives, history });
28507
29359
  if ((await authorizeAgent(deps, { msg, io, refuse: refuse3, agentName: resolved.agentName })).kind === "refused") return;
28508
- 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;
28509
29370
  const carriedRow = resume?.row ?? restart?.row;
28510
29371
  const admissionCtx = {
28511
29372
  msg,
@@ -28540,6 +29401,7 @@ async function dispatch(deps, msg, io, opts = {}) {
28540
29401
  msg,
28541
29402
  history,
28542
29403
  agent,
29404
+ profile,
28543
29405
  resolved,
28544
29406
  resume,
28545
29407
  root
@@ -28561,15 +29423,19 @@ async function dispatch(deps, msg, io, opts = {}) {
28561
29423
  closeLines,
28562
29424
  clock: clock2,
28563
29425
  agent,
29426
+ profile,
28564
29427
  needsRepo,
28565
29428
  repoCtx
28566
29429
  });
28567
29430
  if (repoGate.kind === "refused") return;
29431
+ const clip3 = budgetClipLabel(agent, profile, directives.budget);
29432
+ if (clip3) shell.setLabel(`${shell.label} \xB7 ${clip3}`);
28568
29433
  if (agent.name === "ship") {
28569
29434
  setupCard = void 0;
28570
29435
  clearInterval(setupHeartbeat);
28571
29436
  await runShipBranch(deps, msg, io, {
28572
29437
  agent,
29438
+ profile,
28573
29439
  modelRef: resolved.modelRef,
28574
29440
  label: shell.label,
28575
29441
  startedAt,
@@ -28626,6 +29492,7 @@ async function dispatch(deps, msg, io, opts = {}) {
28626
29492
  const reservation = await reserveRun(deps, {
28627
29493
  msg,
28628
29494
  agent,
29495
+ profile,
28629
29496
  resolved,
28630
29497
  repoCtx,
28631
29498
  channelVisibility,
@@ -28652,6 +29519,7 @@ async function dispatch(deps, msg, io, opts = {}) {
28652
29519
  closeLines,
28653
29520
  clock: clock2,
28654
29521
  agent,
29522
+ profile,
28655
29523
  repoCtx,
28656
29524
  root
28657
29525
  });
@@ -28689,6 +29557,7 @@ async function dispatch(deps, msg, io, opts = {}) {
28689
29557
  const prompt = await composePrompt(deps, {
28690
29558
  msg,
28691
29559
  agent,
29560
+ profile,
28692
29561
  resolved,
28693
29562
  directives,
28694
29563
  sticky,
@@ -28716,10 +29585,11 @@ async function dispatch(deps, msg, io, opts = {}) {
28716
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`
28717
29586
  );
28718
29587
  }
28719
- writeTombstone(deps, { msg, agent, resolved, repoCtx, channelVisibility, run: run2, registry, resume });
29588
+ writeTombstone(deps, { msg, agent, profile, resolved, repoCtx, channelVisibility, run: run2, registry, resume });
28720
29589
  ledgerRun = await claimRun(deps, {
28721
29590
  msg,
28722
29591
  agent,
29592
+ profile,
28723
29593
  resolved,
28724
29594
  repoCtx,
28725
29595
  channelVisibility,
@@ -28741,6 +29611,7 @@ async function dispatch(deps, msg, io, opts = {}) {
28741
29611
  msg,
28742
29612
  io,
28743
29613
  agent,
29614
+ profile,
28744
29615
  resolved,
28745
29616
  provider,
28746
29617
  model,
@@ -28904,6 +29775,7 @@ var init_dispatcher = __esm({
28904
29775
  init_reply();
28905
29776
  init_record();
28906
29777
  init_ship();
29778
+ init_shipPipeline();
28907
29779
  init_settle();
28908
29780
  init_runRegistry();
28909
29781
  init_runEnding();
@@ -28911,22 +29783,6 @@ var init_dispatcher = __esm({
28911
29783
  }
28912
29784
  });
28913
29785
 
28914
- // ../../src/core/workerError.ts
28915
- async function errorSuffix(res) {
28916
- const text = await res.text().catch(() => "");
28917
- try {
28918
- const parsed = JSON.parse(text);
28919
- return parsed?.error ? `: ${String(parsed.error)}` : "";
28920
- } catch {
28921
- return "";
28922
- }
28923
- }
28924
- var init_workerError = __esm({
28925
- "../../src/core/workerError.ts"() {
28926
- "use strict";
28927
- }
28928
- });
28929
-
28930
29786
  // ../../src/core/runStoreWorker.ts
28931
29787
  function describeError2(err2, depth = 0) {
28932
29788
  if (!(err2 instanceof Error)) return String(err2);
@@ -29041,10 +29897,10 @@ var init_runStoreWorker = __esm({
29041
29897
  throw new TransientStoreError(`run store ${path}: ${describeError2(err2)}`);
29042
29898
  }
29043
29899
  if (!res.ok) {
29044
- const message = `run store ${path} HTTP ${res.status}${await errorSuffix(res)}`;
29045
- if (res.status === 404) throw new RouteMissingError(message);
29046
- if (res.status >= 500 || res.status === 408 || res.status === 429) throw new TransientStoreError(message);
29047
- 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);
29048
29904
  }
29049
29905
  const data = await res.json().catch(() => null);
29050
29906
  if (typeof data !== "object" || data === null)
@@ -29614,7 +30470,7 @@ function createLedgerWriteThrough(opts) {
29614
30470
  * no record at all was killed before its conversation was stored and
29615
30471
  * closes `interrupted`. */
29616
30472
  async seed(messages, budgetMs) {
29617
- const turns = messages.map((message, idx) => ({ idx, message }));
30473
+ const turns = messages.map((message2, idx) => ({ idx, message: message2 }));
29618
30474
  try {
29619
30475
  const seeded = await ledger.seed(this.runId, gen, turns);
29620
30476
  if (!seeded.ok) {
@@ -29644,7 +30500,7 @@ function createLedgerWriteThrough(opts) {
29644
30500
  }
29645
30501
  async step(report2) {
29646
30502
  if (this.detached) return;
29647
- 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 }));
29648
30504
  const record = {
29649
30505
  step: ++this.stepNo,
29650
30506
  seq: this.lastSeq,
@@ -29809,9 +30665,9 @@ function createLedgerWriteThrough(opts) {
29809
30665
  return run2;
29810
30666
  },
29811
30667
  liveRuns: () => [...live],
29812
- async pushInbox(runId3, message) {
30668
+ async pushInbox(runId3, message2) {
29813
30669
  try {
29814
- const result = await ledger.pushInbox(runId3, message);
30670
+ const result = await ledger.pushInbox(runId3, message2);
29815
30671
  if (result.ok && result.seq !== void 0) return result.seq;
29816
30672
  warn(`[ledger] inbox push refused for run ${runId3}: no live row \u2014 the follow-up rides in memory only`);
29817
30673
  return void 0;
@@ -31352,8 +32208,8 @@ function createSlackApp(deps) {
31352
32208
  }
31353
32209
  );
31354
32210
  });
31355
- app.message(async ({ message, client }) => {
31356
- const m = message;
32211
+ app.message(async ({ message: message2, client }) => {
32212
+ const m = message2;
31357
32213
  if (classifyMessage(m) === "skip") return;
31358
32214
  botUserId ??= (await client.auth.test()).user_id ?? void 0;
31359
32215
  const decision = classifyMessage(m, botUserId);
@@ -32042,8 +32898,8 @@ function toIncomingMessage2(identity2, args) {
32042
32898
  function ok(id, result) {
32043
32899
  return { jsonrpc: "2.0", id, result };
32044
32900
  }
32045
- function err(id, code, message, data) {
32046
- 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 } };
32047
32903
  }
32048
32904
  function readId(msg) {
32049
32905
  const id = msg.id;
@@ -32999,7 +33855,7 @@ function loadWebAssets(distDir) {
32999
33855
  const full = join13(assetsDir, name);
33000
33856
  if (statSync3(full).isFile()) files.set(`/assets/${name}`, readFileSync16(full));
33001
33857
  }
33002
- const serve = (req, res) => {
33858
+ const serve2 = (req, res) => {
33003
33859
  const path = (req.url ?? "/").split("?")[0];
33004
33860
  if (!path.startsWith("/assets/")) return false;
33005
33861
  const method = (req.method ?? "GET").toUpperCase();
@@ -33023,7 +33879,7 @@ function loadWebAssets(distDir) {
33023
33879
  else res.end(body);
33024
33880
  return true;
33025
33881
  };
33026
- return { entry, serve };
33882
+ return { entry, serve: serve2 };
33027
33883
  }
33028
33884
  var CONTENT_TYPES, WEB_DIST_DIR;
33029
33885
  var init_webAssets = __esm({
@@ -33227,6 +34083,7 @@ function createDeliveryViewHandler(deps, shell) {
33227
34083
  }
33228
34084
  const weeksParam = url.searchParams.get("weeks");
33229
34085
  const sinceParam = url.searchParams.get("since");
34086
+ const fresh = url.searchParams.get("fresh") === "1";
33230
34087
  const runs = async (range) => {
33231
34088
  if (!deps.runs) return [];
33232
34089
  const page2 = await deps.runs.listRuns({
@@ -33240,6 +34097,7 @@ function createDeliveryViewHandler(deps, shell) {
33240
34097
  deps.service.report(repo, {
33241
34098
  ...weeksParam !== null ? { weeks: Number(weeksParam) } : {},
33242
34099
  ...sinceParam !== null ? { since: sinceParam } : {},
34100
+ ...fresh ? { fresh } : {},
33243
34101
  runs
33244
34102
  }).then((report2) => {
33245
34103
  if (route2.kind === "json") {
@@ -33623,14 +34481,14 @@ function createMcpConnectViewHandler(deps) {
33623
34481
  async function complete(svc, nonce, identity2, token, res) {
33624
34482
  const { decision, verified, toolCount, warning, server } = await svc.completeTicket(nonce, identity2, token);
33625
34483
  if (!decision.ok) {
33626
- const message = refusalMessage(decision.refusal);
34484
+ const message2 = refusalMessage(decision.refusal);
33627
34485
  if (decision.refusal.kind === "bad_token" && server)
33628
- 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)}`);
33629
34487
  return page(
33630
34488
  res,
33631
34489
  statusFor(decision.refusal.kind),
33632
34490
  "Connect link unavailable",
33633
- `<p class="err">${esc(message)}</p>`
34491
+ `<p class="err">${esc(message2)}</p>`
33634
34492
  );
33635
34493
  }
33636
34494
  if (verified === false) {
@@ -33646,15 +34504,15 @@ async function complete(svc, nonce, identity2, token, res) {
33646
34504
  async function start(svc, nonce, identity2, res) {
33647
34505
  const result = await svc.startOAuth(nonce, identity2);
33648
34506
  if (!result.ok) {
33649
- const message = refusalMessage(result.refusal);
34507
+ const message2 = refusalMessage(result.refusal);
33650
34508
  if (result.refusal.kind === "oauth_failed" && result.server)
33651
34509
  return page(
33652
34510
  res,
33653
34511
  502,
33654
34512
  "Sign-in could not start",
33655
- `<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)}`
33656
34514
  );
33657
- 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>`);
33658
34516
  }
33659
34517
  const host = hostOf(result.redirectUrl);
33660
34518
  page(
@@ -33688,13 +34546,13 @@ function callback(svc, params, identity2, res) {
33688
34546
  }).then((result) => {
33689
34547
  if (!result.ok) {
33690
34548
  const refusal = result.refusal ?? { kind: "not_found" };
33691
- const message = refusalMessage(refusal);
34549
+ const message2 = refusalMessage(refusal);
33692
34550
  const retry = result.server && refusal.kind === "oauth_failed" ? `<p>Nothing was stored. Open the connect link again to retry.</p>` : "";
33693
34551
  return page(
33694
34552
  res,
33695
34553
  refusal.kind === "oauth_failed" ? 502 : statusFor(refusal.kind),
33696
34554
  "Sign-in did not complete",
33697
- `<p class="err">${esc(message)}</p>${retry}`
34555
+ `<p class="err">${esc(message2)}</p>${retry}`
33698
34556
  );
33699
34557
  }
33700
34558
  return connected(res, result.server, result.toolCount, result.warning);
@@ -33893,19 +34751,19 @@ var init_processMetrics = __esm({
33893
34751
  function hasData(part) {
33894
34752
  return (part.type === "image" || part.type === "document") && typeof part.data === "string";
33895
34753
  }
33896
- function turnRows(idx, message, opts = {}) {
34754
+ function turnRows(idx, message2, opts = {}) {
33897
34755
  const partBytes = opts.partBytes ?? TRANSCRIPT_PART_BYTES;
33898
34756
  const refBytes = opts.attachmentRefBytes ?? ATTACHMENT_REF_BYTES;
33899
34757
  const rows = [];
33900
34758
  const attachments = [];
33901
- message.content.forEach((part, i) => {
34759
+ message2.content.forEach((part, i) => {
33902
34760
  let stored = part;
33903
34761
  if (hasData(part) && part.data.length > refBytes) {
33904
34762
  const ref = attachmentRef(idx, i);
33905
34763
  attachments.push({ ref, mediaType: part.mediaType, data: part.data });
33906
34764
  stored = { ...part, data: "", dataRef: ref };
33907
34765
  }
33908
- const json = JSON.stringify({ role: message.role, part: stored });
34766
+ const json = JSON.stringify({ role: message2.role, part: stored });
33909
34767
  const bytes = utf8ByteLength(json);
33910
34768
  if (bytes > partBytes) {
33911
34769
  throw new Error(`transcript: part ${i} of turn ${idx} is ${bytes} bytes, over the ${partBytes} row budget`);
@@ -34114,9 +34972,9 @@ var init_runLedgerWorker = __esm({
34114
34972
  this.checkIds(runId3, gen);
34115
34973
  return this.fenceResult(await this.post("/runs/state", { storeKey: this.opts.storeKey, runId: runId3, gen, state }));
34116
34974
  }
34117
- async pushInbox(runId3, message) {
34975
+ async pushInbox(runId3, message2) {
34118
34976
  this.checkIds(runId3);
34119
- 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 });
34120
34978
  return { ok: r.data.ok === true, ...typeof r.data.seq === "number" ? { seq: r.data.seq } : {} };
34121
34979
  }
34122
34980
  async readInbox(runId3, afterSeq) {
@@ -34503,7 +35361,7 @@ var init_resume = __esm({
34503
35361
  "submit_handoff",
34504
35362
  "submit_dispositions"
34505
35363
  ]);
34506
- 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") : [];
34507
35365
  }
34508
35366
  });
34509
35367
 
@@ -35196,7 +36054,16 @@ async function runBot() {
35196
36054
  };
35197
36055
  const runsService = createRunsService({ registry: defaultRunRegistry, store: runStore, ledger: ledgerClient });
35198
36056
  const scheduleStore = buildScheduleStore(config.config.schedules, processSecrets, (m) => console.warn(`[schedules] ${m}`)) ?? new NullScheduleStore();
35199
- 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 });
35200
36067
  const commands = buildCoreCommands(config, runStore, {
35201
36068
  registry: defaultRunRegistry,
35202
36069
  secrets: processSecrets,
@@ -35254,7 +36121,7 @@ async function runBot() {
35254
36121
  const costsView = createCostsViewHandler(costsService, shell);
35255
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)";
35256
36123
  const deliveryView = createDeliveryViewHandler({ service: deliveryService, runs: runsService }, shell);
35257
- 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)`;
35258
36125
  const tokenCount = Object.keys(auth.tokens).length;
35259
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)";
35260
36127
  const accessConfig = parseAccessConfig(publicEnv());
@@ -35580,6 +36447,7 @@ var init_index = __esm({
35580
36447
  init_costsView();
35581
36448
  init_deliveryView();
35582
36449
  init_delivery();
36450
+ init_deliverySnapshotStore();
35583
36451
  init_costs();
35584
36452
  init_residentAdmin();
35585
36453
  init_residentFleet();
@@ -35787,8 +36655,8 @@ function exitCodeFor(error) {
35787
36655
  if (error === "busy") return 75;
35788
36656
  return 1;
35789
36657
  }
35790
- function errorLine(code, message) {
35791
- return `error (${code}): ${message}`;
36658
+ function errorLine(code, message2) {
36659
+ return `error (${code}): ${message2}`;
35792
36660
  }
35793
36661
  async function runCommand(commands, parsed, caller, opts = {}) {
35794
36662
  const cmd = commands.get(parsed.id);